web-dev-qa-db-fra.com

Les fonctions cloud de Firebase sont très lentes

Nous travaillons sur une application qui utilise les nouvelles fonctions cloud de Firebase. Ce qui se passe actuellement, c'est qu'une transaction est mise dans le nœud de la file d'attente. Et puis la fonction supprime ce noeud et le place dans le bon noeud. Cela a été mis en œuvre en raison de la possibilité de travailler en mode hors connexion. 

Notre problème actuel est la rapidité de la fonction. La fonction elle-même prend environ 400 ms, donc ça va. Mais parfois, les fonctions prennent beaucoup de temps (environ 8 secondes), alors que l'entrée était déjà ajoutée à la file d'attente. 

Nous pensons que le serveur met du temps à démarrer, car lorsque nous effectuons l'action une fois de plus après le premier. Cela prend beaucoup moins de temps. 

Y at-il un moyen de résoudre ce problème? En bas, j'ai ajouté le code de notre fonction. Nous pensons qu'il n'y a rien de mal à cela, mais nous l'avons ajouté au cas où.

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

firebaser ici

On dirait que vous rencontrez un soi-disant démarrage à froid de la fonction.

Lorsque votre fonction n'a pas été exécutée depuis un certain temps, Cloud Functions la met dans un mode utilisant moins de ressources. Ensuite, lorsque vous appuyez à nouveau sur la fonction, l'environnement de ce mode est restauré. Le temps nécessaire à la restauration comprend un coût fixe (par exemple, la restauration du conteneur) et un coût partiel (par exemple, si vous utilisez un grand nombre de modules de nœud, cela peut prendre plus longtemps).

Nous surveillons en permanence les performances de ces opérations pour assurer la meilleure combinaison possible entre l'expérience du développeur et l'utilisation des ressources. Attendez-vous donc à ce que ces temps s'améliorent avec le temps.

La bonne nouvelle est que vous ne devriez en faire l'expérience que pendant le développement. Une fois que vos fonctions sont fréquemment déclenchées en production, il est probable qu’elles ne recommencent presque jamais à froid.

74
Frank van Puffelen

Update - il semble qu'un grand nombre de ces problèmes puissent être résolus à l'aide de la variable masquée process.env.FUNCTION_NAME, comme indiqué ici: https://github.com/firebase/functions-samples/issues/170#issuecomment-323375462

Mise à jour avec le code - Par exemple, si vous avez le fichier d'index suivant:

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

Ensuite, tous vos fichiers seront chargés, et toutes les exigences de ces fichiers le seront également, ce qui occasionnera beaucoup de travail supplémentaire et polluera votre étendue globale pour toutes vos fonctions.

Au lieu de cela, séparez votre inclut comme:

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

Cela ne chargera le (s) fichier (s) requis que lorsque cette fonction sera spécifiquement appelée; vous permettant de garder votre vision globale beaucoup plus propre, ce qui devrait donner lieu à des bottes plus rapides.


Cela devrait permettre une solution beaucoup plus ordonnée que ce que j'ai fait ci-dessous (bien que l'explication ci-dessous soit toujours valable).


Réponse originale

Il semble que le fait de nécessiter des fichiers et que l’initialisation générale se produisant au niveau global soient une cause importante de ralentissement au démarrage à froid.

Au fur et à mesure que le projet reçoit plus de fonctions, la portée globale est de plus en plus polluée, ce qui aggrave le problème, en particulier si vous répartissez vos fonctions dans des fichiers distincts (en utilisant par exemple Object.assign(exports, require('./more-functions.js')); dans votre index.js.

J'ai réussi à constater des gains énormes en performances de démarrage à froid en déplaçant tous mes éléments requis dans une méthode init comme ci-dessous, puis en l'appelant en tant que première ligne de la définition de fonction de ce fichier. Par exemple:

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

J'ai constaté des améliorations d'environ 7-8 à 2-3 secondes lors de l'application de cette technique à un projet comportant environ 30 fonctions sur 8 fichiers. Cela semble également avoir pour conséquence que les fonctions doivent être démarrées à froid moins souvent (probablement en raison d'une utilisation moindre de la mémoire?)

Malheureusement, cela rend toujours les fonctions HTTP difficilement utilisables pour une utilisation en production orientée utilisateur.

En espérant que l’équipe de Firebase ait des projets à l’avenir pour permettre une délimitation correcte des fonctions afin que seuls les modules pertinents aient besoin d’être chargés pour chaque fonction.

32
Tyris

Je suis confronté à des problèmes similaires avec les fonctions cloud de Firestore. Le plus gros est la performance. Particulièrement dans le cas des jeunes entreprises en démarrage, lorsque vous ne pouvez pas permettre à vos premiers clients de voir des applications "lentes". Une simple fonction de génération de documentation pour, par exemple, donne ceci:

- L'exécution de la fonction a pris 9522 ms et s'est terminée avec le code d'état: 200.

Ensuite: j’avais une page de termes et conditions très simple. Avec les fonctions de nuage, l'exécution due au démarrage à froid prendrait 10-15 secondes, même parfois. Je l'ai ensuite déplacé vers une application node.js, hébergée sur un conteneur Appengine. Le temps est descendu à 2-3 secondes. 

J'ai comparé de nombreuses fonctionnalités de mongodb à celles de firestore et parfois je me demande aussi si, au cours de cette première phase de mon produit, je devrais également passer à une base de données différente. Le plus gros avantage que j'ai eu dans firestore était la fonctionnalité de déclencheur onCreate, onUpdate des objets de document.

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

En gros, s’il existe des parties statiques de votre site qui peuvent être déchargées dans un environnement de logiciel, ce n’est peut-être pas une mauvaise idée.

5
Mr.hands-on

Un autre problème est le plan gratuit (Spark).

 spark

Une fois que je passe au forfait payé (Blaze dans mon cas), mes fonctions commencent à fonctionner rapidement.

 blaze

0
Oleksii K.

J'ai également fait ces choses, ce qui améliore les performances une fois que les fonctions sont réchauffées, mais le démarrage à froid me tue. Un des autres problèmes que j'ai rencontré est avec cors, car il faut deux allers-retours vers les fonctions cloud pour faire le travail. Je suis sûr que je peux résoudre ce problème, cependant.

Lorsque vous avez une application dans sa phase initiale (démo) lorsqu'elle n'est pas utilisée fréquemment, les performances ne seront pas excellentes. C’est quelque chose qui devrait être pris en compte, car les premiers utilisateurs d’un produit doivent regarder de leur mieux devant les clients/investisseurs potentiels. Nous avons adoré la technologie, nous avons donc migré d'anciens frameworks éprouvés, mais notre application semble plutôt lente à ce stade. Je vais maintenant essayer quelques stratégies d'échauffement pour améliorer l'apparence

0
Stan Swiniarski