Firebase云功能非常慢

时间:2017-03-10 20:07:29

标签: node.js firebase firebase-realtime-database google-cloud-functions

我们正在开发使用新的firebase云功能的应用程序。目前正在发生的事情是将事务放入队列节点。然后该函数删除该节点并将其放入正确的节点。由于能够脱机工作,因此已实施。

我们目前的问题是功能的速度。该功能本身需要大约400毫秒,所以没关系。但有时功能需要很长时间(大约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;
}

6 个答案:

答案 0 :(得分:78)

firebaser here

听起来你正在经历一个所谓的冷启动功能。

当您的功能在一段时间内未执行时,Cloud Functions会将其置于使用较少资源的模式。然后,当您再次点击该功能时,它将从此模式恢复环境。恢复所需的时间包括固定成本(例如恢复容器)和部分可变成本(例如,如果使用大量节点模块,则可能需要更长时间)。

我们不断监控这些操作的性能,以确保开发人员体验和资源使用之间的最佳组合。所以期望这些时间随着时间的推移而改善。

好消息是你应该只在开发期间体验这一点。一旦你的作品在制作中经常被触发,他们很可能再也不会再遇到冷遇了。

答案 1 :(得分:37)

更新 - 看起来可以使用隐藏变量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.......

然后将加载您的所有文件,以及所有这些文件&#39;还将加载需求,从而导致大量开销并污染您所有功能的全局范围。

而是将您的包含分开为:

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

这只会在专门调用该函数时加载所需的文件;让您的全球范围更加清洁,从而加快冷靴的使用。

这应该允许一个比我在下面所做的更简洁的解决方案(尽管下面的解释仍然有效)。

原始答案

看起来需要文件和全局范围内的一般初始化是冷启动期间减速的一个重要原因。

随着项目获得更多功能,全局范围受到越来越多的污染,使问题变得更糟 - 特别是如果您将功能定位到单独的文件中(例如在Object.assign(exports, require('./more-functions.js'));中使用index.js。 / p>

我通过将所有需求转移到如下所示的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-8s到2-3s的改进。这似乎也导致函数需要不经常冷启动(可能是由于较低的内存使用量?)

不幸的是,这仍然使HTTP功能几乎不能用于面向用户的生产用途。

希望Firebase团队在未来有一些计划允许正确的功能范围,以便只需要为每个功能加载相关的模块。

答案 2 :(得分:5)

我在使用Firestore云功能时遇到了类似的问题。最大的是性能。特别是在早期创业公司的情况下,当您无法让早期客户看到“呆滞”的应用程序时。一个简单的文档生成功能,例如:

-函数执行耗时9522毫秒,状态码为200

然后:我有一个简单明了的条款和条件页面。使用云功能时,由于冷启动而导致的执行有时甚至需要10-15秒。然后,我将其移至托管在appengine容器上的node.js应用程序。时间已降至2-3秒。

我一直在比较mongodb和firestore的许多功能,有时我也想知道在产品的早期阶段是否也应该转移到其他数据库。我在Firestore中遇到的最大建议是触发功能onDocument,onUpdate文档对象。

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

基本上,如果您网站的静态部分可以卸载到Appengine环境中,也许不是一个坏主意。

答案 3 :(得分:0)

我也做过这些事情,一旦功能预热就可以提高性能,但冷启动让我感到害怕。我遇到的其他问题之一是与cors,因为它需要两次访问云功能才能完成工作。不过,我相信我能解决这个问题。

如果应用程序处于早期(演示)阶段,当它不经常使用时,性能不会很好。这是应该考虑的事情,因为早期产品的早期采用者需要在潜在客户/投资者面前展现最佳状态。我们喜欢这项技术,因此我们从较旧的可靠框架迁移,但我们的应用程序在这一点上似乎相当缓慢。我接下来会尝试一些热身策略让它看起来更好

答案 4 :(得分:0)

我刚刚发布了一个名为firebase-cloud-functions的软件包,它会自动搜索您的函数目录并将所有找到的函数正确地嵌套在导出对象中,同时将这些函数彼此隔离以提高冷启动性能。

如果仅延迟加载和缓存模块范围内每个功能所需的依赖项,您会发现这是在快速发展的项目中保持功能最佳效率的最简单,最简单的方法。

import exportCloudFunctions from 'better-firebase-functions'
exportCloudFunctions(__dirname, __filename, exports, './', './**/*.js')

答案 5 :(得分:0)

我在 Firebase Functions 的第一个项目中遇到了非常糟糕的性能,其中一个简单的函数将在几分钟内执行(知道函数执行的 60 秒限制,我知道我的函数有问题)。我的情况的问题是我没有正确终止该功能

如果有人遇到同样的问题,请确保通过以下方式终止该功能:

  1. 为 HTTP 触发器发送响应
  2. 返回后台触发器的承诺

这是 Firebase 帮助我解决问题的 youtube link

相关问题