Firebase云函数非常慢

qvtsj1bj  于 2023-08-07  发布在  其他
关注(0)|答案(7)|浏览(162)

我们正在开发一个使用新的firebase云函数的应用程序。当前发生的情况是将事务放入队列节点中。然后函数删除该节点并将其放在正确的节点中。由于能够脱机工作,因此已实施了此操作。
我们当前的问题是函数的速度。该函数本身大约需要400ms,所以这是正常的。但有时候,当条目已经添加到队列中时,这些函数会花费很长时间(大约8秒)。
我们怀疑服务器需要时间来 Boot ,因为当我们在第一次之后再次执行该操作时。它需要的时间更少。
有没有办法解决这个问题?在这里,我添加了我们的函数的代码。我们怀疑它没有问题,但我们添加了它以防万一。

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

字符串

bpsygsoo

bpsygsoo1#

  • 燃烧器在这里 *

听起来你正在经历一个所谓的函数冷启动。
当您的函数在一段时间内没有执行时,Cloud Functions会将其置于使用更少资源的模式,以便您不必为未使用的计算时间付费。然后,当您再次点击该函数时,它将从该模式恢复环境。恢复所需的时间包括固定成本(例如恢复容器)和部分可变成本(例如,如果使用了很多节点模块,则可能需要更长时间)。
我们不断监控这些操作的性能,以确保开发人员体验和资源使用之间的最佳组合。因此,预计这些时间会随着时间的推移而改善。
好消息是,您只应该在开发过程中体验到这一点。一旦您的功能在生产中频繁被触发,它们很可能再也不会冷启动,特别是如果它们有稳定的流量。然而,如果某些函数倾向于看到流量峰值,您仍然会看到每个峰值的冷启动。在这种情况下,您可能需要考虑minInstances设置,以便始终保持延迟关键函数的设定数量的示例。

cfh9epnr

cfh9epnr2#

更新2021年3月可能值得查看下面来自@George43g的答案,它提供了一个自动化以下过程的简洁解决方案。注意-我自己没有试过这个,所以不能保证它,但它似乎自动化这里描述的过程。你可以在https://github.com/gramstr/better-firebase-functions上阅读更多的内容--或者继续阅读如何自己实现它,并理解函数内部发生了什么。
2020年5月更新感谢maganap的评论-在Node 10中,FUNCTION_NAME被替换为K_SERVICEFUNCTION_TARGET是函数本身,而不是它的名字,替换ENTRY_POINT)。下面的代码示例已在下面更新。

更多信息请访问https://cloud.google.com/functions/docs/migrating/nodejs-runtimes#nodejs-10-changes

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

字符串
然后所有的文件都将被加载,所有这些文件的需求也将被加载,这将导致大量的开销,并污染所有函数的全局作用域。
相反,将您的包含项分隔为:

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


这将只加载所需的文件时,该函数是专门调用;允许你保持你的全局作用域更干净,这将导致更快的冷启动。
这应该允许一个比我下面所做的更简洁的解决方案(尽管下面的解释仍然有效)。

原始答案

看起来在全局范围内需要文件和一般的初始化是冷启动过程中速度减慢的一个重要原因。
当一个项目得到更多的函数时,全局作用域被污染得越来越严重,使问题变得更糟--特别是当你将函数作用域放在单独的文件中时(比如在你的index.js中使用Object.assign(exports, require('./more-functions.js'));)。
我已经成功地看到了冷启动性能的巨大提高,方法是将所有的request移到一个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团队在未来有一些计划,允许适当的函数范围,以便只有相关的模块需要为每个函数加载。

guicsvcw

guicsvcw3#

我在FireStore云功能上也遇到了类似的问题。最大的是业绩。特别是在早期阶段的创业公司,当你不能负担你的早期客户看到“呆滞”的应用程序。例如,一个简单的文档生成函数给出了以下内容:
--函数执行耗时9522 ms,完成状态码:200
然后:我有一个直截了当的条款和条件页面。使用云函数,由于冷启动而导致的执行甚至有时将花费10-15秒。然后,我将其移动到node.js应用程序,托管在appengine容器上。时间已经下降到2-3秒。
我一直在比较mongodb和firestore的许多特性,有时我也想知道在我的产品的早期阶段,我是否也应该转移到不同的数据库。我在firestore中最大的优势是文档对象的onCreate和onUpdate触发器功能。
https://db-engines.com/en/system/Google+Cloud+Firestore%3BMongoDB
基本上,如果你的网站有静态部分可以卸载到应用引擎环境中,也许不是一个坏主意。

ct3nt3jp

ct3nt3jp4#

更新:2022 - lib再次维护。Firebase现在有能力保持示例温暖,但仍然有潜在的性能和代码结构优势。
更新/编辑:新语法和更新即将于2020年5月发布
我刚刚发布了一个名为better-firebase-functions的包,它会自动搜索函数目录,并将所有找到的函数正确嵌套到导出对象中,同时将函数相互隔离以提高冷引导性能。
如果你只对模块范围内的每个函数延迟加载和缓存所需的依赖项,你会发现这是在快速增长的项目中保持函数最佳效率的最简单和最简单的方法。

import { exportFunctions } from 'better-firebase-functions'
exportFunctions({__filename, exports})

字符串

omqzjyyz

omqzjyyz5#

我也做过这些事情,一旦功能预热,这就提高了性能,但冷启动让我受不了。我遇到的另一个问题是cors,因为它需要两次访问云功能来完成工作。不过,我肯定我能搞定。
当你有一个应用程序处于早期(演示)阶段时,它不经常使用,性能不会很好。这是应该考虑的事情,因为早期产品的早期采用者需要在潜在客户/投资者面前表现得最好。我们喜欢这项技术,所以我们从旧的久经考验的框架迁移,但我们的应用程序在这一点上似乎相当缓慢。接下来我要尝试一些热身策略,让它看起来更好

dddzy1tm

dddzy1tm6#

我在Firebase Functions中的第一个项目中经历了非常糟糕的性能,其中一个简单的函数将在几分钟内执行(知道函数执行的60秒限制,我知道我的函数有问题)。我的问题是我没有正确地终止函数
如果有人遇到相同的问题,请确保通过以下方式终止函数:
1.发送HTTP触发器的响应
1.返回后台触发器的promise
下面是Firebase的youtube link,它帮助我解决了这个问题

wgeznvg7

wgeznvg77#

云函数与firestore库一起使用时,由于其中使用的gRpc库,其冷启动时间不一致。
我们最近做了一个完全兼容的Rest客户端(@bountyrush/firestore),旨在与官方的nodejs-firestore客户端并行更新。
幸运的是,冷启动现在好多了,我们甚至放弃了使用redis内存存储作为缓存,这是我们之前使用的。
整合步骤:

1. npm install @bountyrush/firestore
2. Replace require('@google-cloud/firestore') with require('@bountyrush/firestore')
3. Have FIRESTORE_USE_REST_API = 'true' in your environment variables. (process.env.FIRESTORE_USE_REST_API should be set to 'true' for using in rest mode. If its not set, it just standard firestore with grpc connections)

字符串

相关问题