Wir arbeiten an einer Anwendung, die die neuen Firebase-Cloud-Funktionen verwendet. Derzeit wird eine Transaktion in den Warteschlangenknoten gestellt. Und dann entfernt die Funktion diesen Knoten und fügt ihn in den richtigen Knoten ein. Dies wurde aufgrund der Möglichkeit implementiert, offline zu arbeiten.
Unser aktuelles Problem ist die Geschwindigkeit der Funktion. Die Funktion selbst dauert ungefähr 400 ms, das ist also in Ordnung. Manchmal dauern die Funktionen jedoch sehr lange (ca. 8 Sekunden), während der Eintrag bereits zur Warteschlange hinzugefügt wurde.
Wir vermuten, dass der Server Zeit braucht, um hochzufahren, denn wenn wir die Aktion nach dem ersten noch einmal ausfü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 daran nichts falsch ist, aber wir haben es für alle Fälle 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;
}
quelle
Antworten:
Firebaser hier
Es hört sich so an, als würden Sie einen sogenannten Kaltstart der Funktion erleben.
Wenn Ihre Funktion seit einiger Zeit nicht mehr ausgeführt wurde, versetzt Cloud Functions sie in einen Modus, der weniger Ressourcen verbraucht. Wenn Sie die Funktion dann erneut drücken, wird die Umgebung aus diesem Modus wiederhergestellt. Die für die Wiederherstellung erforderliche Zeit besteht aus festen Kosten (z. B. Wiederherstellung des Containers) und variablen Teilkosten (z. B. wenn Sie viele Knotenmodule verwenden, kann dies länger dauern).
Wir überwachen kontinuierlich die Leistung dieser Vorgänge, um die beste Mischung zwischen 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 erleben sollten. Sobald Ihre Funktionen in der Produktion häufig ausgelöst werden, werden sie wahrscheinlich kaum wieder einen Kaltstart erleben.
quelle
Update Mai 2020 Danke für den Kommentar von maganap - in Node 10+
FUNCTION_NAME
wird durch ersetztK_SERVICE
(FUNCTION_TARGET
ist die Funktion selbst, nicht der Name, ersetztENTRY_POINT
). Die folgenden Codebeispiele wurden unten aufgeführt.Weitere Informationen finden Sie unter https://cloud.google.com/functions/docs/migrating/nodejs-runtimes#nodejs-10-changes
Update - Es sieht so aus, als könnten viele dieser Probleme mithilfe der versteckten Variablen gelöst werden,
process.env.FUNCTION_NAME
wie hier dargestellt: https://github.com/firebase/functions-samples/issues/170#issuecomment-323375462Mit Code aktualisieren - Wenn Sie beispielsweise die folgende Indexdatei haben:
Dann werden alle Ihre Dateien geladen, und alle Anforderungen dieser Dateien werden auch geladen, was zu viel Overhead führt und Ihren globalen Bereich für alle Ihre Funktionen verschmutzt.
Trennen Sie stattdessen Ihre Includes wie folgt:
Dadurch werden die erforderlichen Dateien nur geladen, wenn diese Funktion speziell aufgerufen wird. So können Sie Ihr globales Zielfernrohr viel sauberer halten, was zu schnelleren Kaltstarts führen sollte.
Dies sollte eine viel sauberere Lösung ermöglichen als das, was ich unten getan habe (obwohl die folgende Erklärung immer noch gilt).
Ursprüngliche Antwort
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 beim Kaltstart ist.
Wenn ein Projekt mehr Funktionen erhält, wird der globale Bereich immer mehr verschmutzt, was das Problem verschlimmert - insbesondere, wenn Sie Ihre Funktionen in separate Dateien einteilen (z. B. durch Verwendung
Object.assign(exports, require('./more-functions.js'));
in Ihremindex.js
.Ich habe es geschafft, enorme Verbesserungen bei der Kaltstartleistung zu erzielen, indem ich alle meine Anforderungen in eine Init-Methode wie unten verschoben und sie dann als erste Zeile in einer Funktionsdefinition für diese Datei aufgerufen habe. Z.B:
Ich habe Verbesserungen von ungefähr 7-8s auf 2-3s gesehen, als ich diese Technik auf ein Projekt mit ~ 30 Funktionen in 8 Dateien angewendet habe. Dies scheint auch dazu zu führen, dass Funktionen seltener kalt gestartet werden müssen (vermutlich aufgrund einer geringeren Speichernutzung?).
Leider sind HTTP-Funktionen dadurch immer noch kaum für die benutzerbezogene Produktion geeignet.
In der Hoffnung, dass das Firebase-Team in Zukunft einige Pläne hat, um einen ordnungsgemäßen Funktionsumfang zu ermöglichen, sodass für jede Funktion nur die relevanten Module geladen werden müssen.
quelle
process.env.FUNCTION_NAME
und damit die für diese Funktion erforderlichen Dateien bedingt einschließen. Der Kommentar unter github.com/firebase/functions-samples/issues/… gibt eine wirklich gute Beschreibung dieser Arbeitsweise! Es stellt sicher, dass der globale Bereich nicht mit Methoden verschmutzt wird und irrelevante Funktionen enthält.FUNCTIONS_NAME
gilt nur für Knoten 6 und 8, wie hier erläutert: cloud.google.com/functions/docs/… . Knoten 10 sollte verwendenFUNCTION_TARGET
K_SERVICE
laut doco unter cloud.google.com/functions/docs/migrating/… verwendet werden. - Ich habe meine Antwort aktualisiert.Ich habe ähnliche Probleme mit Firestore-Cloud-Funktionen. Das größte ist die Leistung. Insbesondere bei Startups im Frühstadium, wenn Sie es Ihren frühen Kunden nicht leisten können, "träge" Apps zu sehen. Eine einfache Dokumentationsgenerierungsfunktion für z.
- Die Funktionsausführung dauerte 9522 ms und endete mit dem Statuscode: 200
Dann: Ich hatte eine einfache 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 Appengine-Container gehostet wird. Die Zeit ist auf 2-3 Sekunden gesunken.
Ich habe viele Funktionen 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 Triggerfunktion onCreate, onUpdate von Dokumentobjekten.
https://db-engines.com/de/system/Google+Cloud+Firestore%3BMongoDB
Grundsätzlich ist es keine schlechte Idee, wenn statische Teile Ihrer Site in die Appengine-Umgebung ausgelagert werden können.
quelle
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, auf die ich gestoßen bin, betrifft Cors, da zwei Fahrten zu den Cloud-Funktionen erforderlich sind, um die Aufgabe zu erledigen. Ich bin mir sicher, dass ich das beheben kann.
Wenn sich eine App in der frühen (Demo-) Phase befindet, in der sie nicht häufig verwendet wird, ist die Leistung nicht besonders gut. Dies sollte berücksichtigt werden, da Early Adopters mit Early Product vor potenziellen Kunden / Investoren ihr Bestes geben müssen. Wir haben die Technologie geliebt und sind daher von älteren, bewährten Frameworks migriert, aber unsere App scheint zu diesem Zeitpunkt ziemlich träge zu sein. Ich werde als nächstes einige Aufwärmstrategien ausprobieren, damit es besser aussieht
quelle
UPDATE / EDIT: Neue Syntax und Updates kommen im Mai 2020
Ich habe gerade ein Paket mit dem Namen
better-firebase-functions
veröffentlicht. Es durchsucht automatisch Ihr Funktionsverzeichnis und verschachtelt alle gefundenen Funktionen in Ihrem Exportobjekt korrekt, während die Funktionen voneinander isoliert werden, um die Kaltstartleistung zu verbessern.Wenn Sie nur die Abhängigkeiten, die Sie für jede Funktion innerhalb des Modulbereichs benötigen, verzögert laden und zwischenspeichern, ist dies der einfachste und einfachste Weg, um Ihre Funktionen in einem schnell wachsenden Projekt optimal effizient zu halten.
quelle