web-dev-qa-db-ja.com

Firebaseクラウド機能が非常に遅い

新しいFirebaseクラウド機能を使用するアプリケーションに取り組んでいます。現在起こっていることは、トランザクションがキューノードに置かれていることです。そして、関数はそのノードを削除し、正しいノードに配置します。これは、オフラインで作業できるため実装されています。

現在の問題は、関数の速度です。関数自体は約400msかかりますので、大丈夫です。ただし、関数が非常に長い時間(約8秒)かかり、エントリが既にキューに追加されている場合があります。

サーバーが起動するのに時間がかかるのは、最初のアクションの後にもう一度アクションを実行するためだと思われます。時間がかかりません。

この問題を修正する方法はありますか?ここに関数のコードを追加しました。何も問題はないと思いますが、念のため追加しました。

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const database = admin.database();

exports.insertTransaction = functions.database
    .ref('/userPlacePromotionTransactionsQueue/{userKey}/{placeKey}/{promotionKey}/{transactionKey}')
    .onWrite(event => {
        if (event.data.val() == null) return null;

        // get keys
        const userKey = event.params.userKey;
        const placeKey = event.params.placeKey;
        const promotionKey = event.params.promotionKey;
        const transactionKey = event.params.transactionKey;

        // init update object
        const data = {};

        // get the transaction
        const transaction = event.data.val();

        // transfer transaction
        saveTransaction(data, transaction, userKey, placeKey, promotionKey, transactionKey);
        // remove from queue
        data[`/userPlacePromotionTransactionsQueue/${userKey}/${placeKey}/${promotionKey}/${transactionKey}`] = null;

        // fetch promotion
        database.ref(`promotions/${promotionKey}`).once('value', (snapshot) => {
            // Check if the promotion exists.
            if (!snapshot.exists()) {
                return null;
            }

            const promotion = snapshot.val();

            // fetch the current stamp count
            database.ref(`userPromotionStampCount/${userKey}/${promotionKey}`).once('value', (snapshot) => {
                let currentStampCount = 0;
                if (snapshot.exists()) currentStampCount = parseInt(snapshot.val());

                data[`userPromotionStampCount/${userKey}/${promotionKey}`] = currentStampCount + transaction.amount;

                // determines if there are new full cards
                const currentFullcards = Math.floor(currentStampCount > 0 ? currentStampCount / promotion.stamps : 0);
                const newStamps = currentStampCount + transaction.amount;
                const newFullcards = Math.floor(newStamps / promotion.stamps);

                if (newFullcards > currentFullcards) {
                    for (let i = 0; i < (newFullcards - currentFullcards); i++) {
                        const cardTransaction = {
                            action: "pending",
                            promotion_id: promotionKey,
                            user_id: userKey,
                            amount: 0,
                            type: "stamp",
                            date: transaction.date,
                            is_reversed: false
                        };

                        saveTransaction(data, cardTransaction, userKey, placeKey, promotionKey);

                        const completedPromotion = {
                            promotion_id: promotionKey,
                            user_id: userKey,
                            has_used: false,
                            date: admin.database.ServerValue.TIMESTAMP
                        };

                        const promotionPushKey = database
                            .ref()
                            .child(`userPlaceCompletedPromotions/${userKey}/${placeKey}`)
                            .Push()
                            .key;

                        data[`userPlaceCompletedPromotions/${userKey}/${placeKey}/${promotionPushKey}`] = completedPromotion;
                        data[`userCompletedPromotions/${userKey}/${promotionPushKey}`] = completedPromotion;
                    }
                }

                return database.ref().update(data);
            }, (error) => {
                // Log to the console if an error happened.
                console.log('The read failed: ' + error.code);
                return null;
            });

        }, (error) => {
            // Log to the console if an error happened.
            console.log('The read failed: ' + error.code);
            return null;
        });
    });

function saveTransaction(data, transaction, userKey, placeKey, promotionKey, transactionKey) {
    if (!transactionKey) {
        transactionKey = database.ref('transactions').Push().key;
    }

    data[`transactions/${transactionKey}`] = transaction;
    data[`placeTransactions/${placeKey}/${transactionKey}`] = transaction;
    data[`userPlacePromotionTransactions/${userKey}/${placeKey}/${promotionKey}/${transactionKey}`] = transaction;
}
97
Stan van Heumen

ここのファイヤーベースラー

機能のいわゆるコールドスタートが発生しているようです。

関数がしばらく実行されていない場合、Cloud Functionsは、より少ないリソースを使用するモードにします。その後、関数を再度押すと、このモードから環境が復元されます。復元にかか​​る時間は、固定コスト(コンテナーの復元など)と部分的な可変コスト(たとえば、多くのノードモジュールを使用する場合は時間がかかる場合があります)で構成されます。

開発者のエクスペリエンスとリソース使用量の最適な組み合わせを確保するために、これらの操作のパフォーマンスを継続的に監視しています。したがって、これらの時間は時間の経過とともに改善されると予想してください。

良いニュースは、開発中にのみこれを経験する必要があるということです。本番環境で機能が頻繁にトリガーされると、コールドスタートが再び発生する可能性はほとんどありません。

81

Update-これらの問題の多くは、次のように隠し変数process.env.FUNCTION_NAMEを使用して解決できるようです: https://github.com/firebase/functions-samples/issues/170#issuecomment-323375462

コードで更新-たとえば、次のインデックスファイルがある場合:

...
exports.doSomeThing = require('./doSomeThing');
exports.doSomeThingElse = require('./doSomeThingElse');
exports.doOtherStuff = require('./doOtherStuff');
// and more.......

その後、すべてのファイルがロードされ、それらのファイルの要件もすべてロードされるため、多くのオーバーヘッドが発生し、すべての関数のグローバルスコープが汚染されます。

代わりに、インクルードを次のように分離します。

if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === 'doSomeThing') {
  exports.doSomeThing = require('./doSomeThing');
}
if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === 'doSomeThingElse') {
  exports. doSomeThingElse = require('./doSomeThingElse');
}
if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === 'doOtherStuff') {
  exports. doOtherStuff = require('./doOtherStuff');
}

これは、その関数が特に呼び出されたときにのみ必要なファイルをロードします。グローバルスコープをよりクリーンに保つことができるため、コールドブートが高速になります。


これにより、以下で説明したよりもはるかにきれいなソリューションが可能になります(ただし、以下の説明は引き続き有効です)。


元の回答

ファイルが必要であり、グローバルスコープで発生する一般的な初期化は、コールドブート中の速度低下の大きな原因のようです。

プロジェクトがより多くの関数を取得すると、グローバルスコープがますます汚染され、問題が悪化します。特に、関数を別のファイルにスコープする場合(index.jsObject.assign(exports, require('./more-functions.js'));を使用するなど)。

コールドブートのパフォーマンスを大幅に向上させることができました。以下のようにすべての要件をinitメソッドに移動し、そのファイルの関数定義内の最初の行として呼び出します。例えば:

const functions = require('firebase-functions');
const admin = require('firebase-admin');
// Late initialisers for performance
let initialised = false;
let handlebars;
let fs;
let path;
let encrypt;

function init() {
  if (initialised) { return; }

  handlebars = require('handlebars');
  fs = require('fs');
  path = require('path');
  ({ encrypt } = require('../common'));
  // Maybe do some handlebars compilation here too

  initialised = true;
}

この手法を8つのファイルにまたがる約30個の関数を持つプロジェクトに適用すると、約7〜8秒から2〜3秒に改善が見られました。また、これにより、関数のコールドブートの頻度が少なくなるようです(おそらくメモリ使用量が少ないためですか?)

残念ながら、これにより、HTTP機能はユーザー向けの実稼働環境で使用することができなくなります。

Firebaseチームが将来、機能の適切なスコープを考慮して、各機能に関連するモジュールのみをロードする必要があるようにすることを期待しています。

38
Tyris

私は、Firestoreクラウド機能で同様の問題に直面しています。最大のものはパフォーマンスです。特に初期段階のスタートアップの場合、初期顧客に「低迷」アプリを見る余裕がないとき。たとえば、単純なドキュメント生成関数はこれを提供します:

-関数の実行には9522ミリ秒かかり、ステータスコードは200で終了しました

その後:簡単な利用規約ページがありました。クラウド機能を使用すると、コールドスタートによる実行は、場合によっても10〜15秒かかります。その後、appengineコンテナーでホストされるnode.jsアプリに移動しました。時間は2〜3秒になりました。

私はmongodbの多くの機能をFirestoreと比較してきましたが、製品のこの初期段階で別のデータベースに移動する必要があるのではないかと思うこともあります。 Firestoreでの最大の利点は、ドキュメントオブジェクトのonCreate、onUpdateのトリガー機能でした。

https://db-engines.com/en/system/Google+Cloud+Firestore%3BMongoDB

基本的に、サイトの静的な部分がappengine環境にオフロードできる場合は、おそらく悪い考えではありません。

6
Mr.hands-on

もう1つの問題は、無料プラン(Spark)です。

spark

有料プラン(私の場合はBlaze)に切り替えると、機能がすぐに動作し始めます。

blaze

0
Oleksii K.

私もこれらのことをしました。関数がウォームアップされるとパフォーマンスが向上しますが、コールドスタートは私を殺します。私が遭遇した他の問題の1つはcorsにあります。仕事を成し遂げるにはクラウド機能への2回の旅行が必要だからです。しかし、私はそれを修正できると確信しています。

アプリが頻繁に使用されない初期(デモ)フェーズにある場合、パフォーマンスは大きくなりません。早期の製品を使用する早期導入者は、潜在的な顧客/投資家の前で最善を尽くす必要があるため、これは考慮すべき事項です。テクノロジーが大好きだったため、実績のある古いフレームワークから移行しましたが、この時点ではアプリはかなり遅いようです。次に、見栄えを良くするためにいくつかのウォームアップ戦略を試してみます

0
Stan Swiniarski