webentwicklung-frage-antwort-db.com.de

Firebase-Cloud-Funktionen sind sehr langsam

Wir arbeiten an einer Anwendung, die die neuen Firebase-Cloud-Funktionen verwendet. Derzeit geschieht, dass eine Transaktion im Warteschlangenknoten abgelegt wird. Und dann entfernt die Funktion diesen Knoten und versetzt ihn in den richtigen Knoten. Dies wurde implementiert, weil offline gearbeitet werden kann. 

Unser aktuelles Problem ist die Geschwindigkeit der Funktion. Die Funktion selbst dauert etwa 400 ms, das ist also in Ordnung. Manchmal dauern die Funktionen jedoch sehr lange (etwa 8 Sekunden), während der Eintrag bereits zur Warteschlange hinzugefügt wurde. 

Wir vermuten, dass der Server Zeit zum Hochfahren benötigt, denn wenn wir die Aktion nach der ersten Aktion noch einmal durchführen. Es dauert viel weniger Zeit. 

Gibt es eine Möglichkeit, dieses Problem zu beheben? Hier unten habe ich den Code unserer Funktion hinzugefügt. Wir vermuten, dass nichts daran falsch ist, aber wir haben es nur für den Fall hinzugefügt.

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;
}
90
Stan van Heumen

Feuerball hier

Es hört sich an, als ob Sie einen sogenannten Kaltstart der Funktion erleben.

Wenn Ihre Funktion längere Zeit nicht ausgeführt wurde, versetzt Cloud Functions sie in einen Modus, in dem weniger Ressourcen verwendet werden. Wenn Sie dann die Funktion erneut drücken, wird die Umgebung aus diesem Modus wiederhergestellt. Die Zeit, die für die Wiederherstellung erforderlich ist, besteht aus Fixkosten (z. B. Wiederherstellung des Containers) und variablen Teilkosten (z. B. wenn Sie viele Knotenmodule verwenden, kann dies länger dauern).

Wir überwachen ständig die Leistung dieser Vorgänge, um die beste Mischung aus Entwicklererfahrung und Ressourcennutzung sicherzustellen. Erwarten Sie also, dass sich diese Zeiten im Laufe der Zeit verbessern.

Die gute Nachricht ist, dass Sie dies nur während der Entwicklung erfahren sollten. Wenn Ihre Funktionen häufig in der Produktion ausgelöst werden, besteht die Chance, dass sie kaum wieder einen Kaltstart einlegen.

74

Update - viele dieser Probleme können mit der versteckten Variablen process.env.FUNCTION_NAME gelöst werden: https://github.com/firebase/functions-samples/issues/170#issuecomment-323375462

Update mit Code - Zum Beispiel, wenn Sie die folgende Indexdatei haben:

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

Dann werden alle Ihre Dateien geladen, und alle Anforderungen dieser Dateien werden ebenfalls geladen, was zu einem erheblichen Aufwand führt und Ihren globalen Geltungsbereich für alle Funktionen verschmutzt.

Trennen Sie stattdessen Ihre Includes als:

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');
}

Dadurch werden nur die erforderlichen Dateien geladen, wenn diese Funktion speziell aufgerufen wird. So können Sie Ihren globalen Geltungsbereich viel sauberer halten, was zu schnelleren Kaltstiefeln führen sollte.


Dies sollte eine viel schönere Lösung ermöglichen als das, was ich unten gemacht habe (obwohl die Erklärung weiter unten gilt).


Originalantwort

Es sieht so aus, als ob das Erfordernis von Dateien und die allgemeine Initialisierung im globalen Bereich eine große Ursache für die Verlangsamung während des Kaltstarts sind.

Wenn ein Projekt mehr Funktionen erhält, wird der globale Geltungsbereich immer mehr verschmutzt, was das Problem verschlimmert. Dies gilt insbesondere, wenn Sie Ihre Funktionen in separate Dateien umwandeln (z. B. durch Verwendung von Object.assign(exports, require('./more-functions.js')); in Ihrem index.js).

Es ist mir gelungen, enorme Fortschritte bei der Kaltstart-Leistung zu erzielen, indem ich alle meine Anforderungen wie unten in eine init-Methode überführte und sie dann als erste Zeile innerhalb einer Funktionsdefinition für diese Datei aufrief. Z.B:

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;
}

Bei der Anwendung dieser Technik auf ein Projekt mit ~ 30 Funktionen in 8 Dateien habe ich Verbesserungen von etwa 7 bis 8 Sekunden bis hin zu 2-3 Sekunden gesehen. Dies scheint auch dazu zu führen, dass Funktionen seltener kalt gestartet werden müssen (vermutlich aufgrund eines geringeren Speicherverbrauchs?).

Leider sind die HTTP-Funktionen für die benutzerorientierte Verwendung in der Produktion jedoch kaum nutzbar.

Wir hoffen, dass das Firebase-Team in der Zukunft einige Pläne hat, um eine korrekte Festlegung der Funktionen zu ermöglichen, sodass nur die relevanten Module für jede Funktion geladen werden müssen.

32
Tyris

Ich habe ähnliche Probleme mit Firestore-Cloud-Funktionen. Der größte ist Leistung. Speziell für Start-Ups im Frühstadium, wenn Sie es Ihren frühen Kunden nicht leisten können, "träge" Apps zu sehen. Eine einfache Dokumentationsgenerierungsfunktion für beispielsweise gibt dies:

- Die Funktionsausführung dauerte 9522 ms und wurde mit dem Statuscode 200 beendet

Dann: Ich hatte eine klare Seite mit den Allgemeinen Geschäftsbedingungen. Bei Cloud-Funktionen würde die Ausführung aufgrund des Kaltstarts auch zeitweise 10-15 Sekunden dauern. Ich habe es dann in eine node.js-App verschoben, die auf einem Maschinencontainer gehostet wird. Die Zeit ist auf 2-3 Sekunden gesunken. 

Ich habe viele der Features von Mongodb mit Firestore verglichen und manchmal frage ich mich auch, ob ich in dieser frühen Phase meines Produkts auch in eine andere Datenbank wechseln sollte. Der größte Vorteil, den ich im Firestore hatte, war die Trigger-Funktion onCreate, onUpdate von Dokumentobjekten.

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

Grundsätzlich, wenn statische Teile Ihrer Site in die Umgebung der Anwendung verschoben werden können, ist dies möglicherweise keine schlechte Idee.

5
Mr.hands-on

Ein weiteres Problem ist der freie Plan (Spark).

 spark

Sobald ich zu einem bezahlten Plan wechsle (in meinem Fall Blaze), beginnen meine Funktionen schnell zu arbeiten.

 blaze

0
Oleksii K.

Ich habe auch diese Dinge getan, was die Leistung verbessert, sobald die Funktionen aufgewärmt sind, aber der Kaltstart bringt mich um. Eines der anderen Probleme, die mir begegnet sind, betrifft cors, da zwei Arbeitsschritte zu den Cloud-Funktionen erforderlich sind, um die Arbeit zu erledigen. Ich bin sicher, dass ich das reparieren kann.

Wenn Sie eine App in der frühen (Demo) -Phase haben und sie nicht häufig verwendet wird, ist die Leistung nicht besonders gut. Dies ist etwas, das in Betracht gezogen werden sollte, da frühe Anwender mit frühen Produkten vor potenziellen Kunden/Investoren ihr Bestes geben müssen. Wir haben die Technologie geliebt, also haben wir von älteren, bewährten Frameworks gewechselt, aber unsere App scheint an diesem Punkt ziemlich träge zu sein. Als nächstes werde ich einige Aufwärmstrategien ausprobieren, damit es besser aussieht

0
Stan Swiniarski