node.js Firebase 云功能非常慢
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/42726870/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Firebase cloud functions is very slow
提问by Stan van Heumen
We're working on an application that uses the new firebase cloud functions. What currently is happening is that a transaction is put in the queue node. And then the function removes that node and puts it in the correct node. This has been implemented because of the ability to work offline.
我们正在开发一个使用新的 firebase 云功能的应用程序。当前发生的是将事务放入队列节点。然后该函数删除该节点并将其放入正确的节点中。由于能够脱机工作,因此已实施。
Our current problem is the speed of the function. The function itself takes about 400ms, so that's alright. But sometimes the functions take a very long time (around 8 seconds), while the entry was already added to the queue.
我们当前的问题是函数的速度。该函数本身大约需要 400 毫秒,所以没关系。但有时函数需要很长时间(大约 8 秒),而条目已经添加到队列中。
We suspect that the server takes time to boot up, because when we do the action once more after the first. It takes way less time.
我们怀疑服务器需要时间来启动,因为当我们在第一次之后再次执行操作时。它需要更少的时间。
Is there any way to fix this problem? Down here i added the code of our function. We suspect there's nothing wrong with it, but we added it just in case.
有没有办法解决这个问题?在这里,我添加了我们函数的代码。我们怀疑它没有任何问题,但我们添加了它以防万一。
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;
}
采纳答案by Frank van Puffelen
firebaser here
火力士在这里
It sounds like you're experiencing a so-called cold start of the function.
听起来您正在经历所谓的函数冷启动。
When your function hasn't been executed in some time, Cloud Functions puts it in a mode that uses fewer resources. Then when you hit the function again, it restores the environment from this mode. The time it takes to restore consists of a fixed cost (e.g. restore the container) and a part variable cost (e.g. if you use a lot of node modules, it may take longer).
当您的函数在一段时间内未执行时,Cloud Functions 会将其置于使用较少资源的模式。然后当您再次点击该功能时,它会从此模式恢复环境。恢复所需的时间包括固定成本(例如恢复容器)和部分可变成本(例如,如果您使用大量节点模块,则可能需要更长的时间)。
We're continually monitoring the performance of these operations to ensure the best mix between developer experience and resource usage. So expect these times to improve over time.
我们会持续监控这些操作的性能,以确保开发人员体验和资源使用之间的最佳组合。因此,预计这些时间会随着时间的推移而改善。
The good news is that you should only experience this during development. Once your functions are being frequently triggered in production, chances are they'll hardly ever hit a cold start again.
好消息是你应该只在开发过程中体验到这一点。一旦您的函数在生产中被频繁触发,它们很有可能再也不会冷启动了。
回答by Tyris
Update May 2020Thanks for the comment by maganap - in Node 10+ FUNCTION_NAMEis replaced with K_SERVICE(FUNCTION_TARGETis the function itself, not it's name, replacing ENTRY_POINT). Code samples below have been udpated below.
2020 年 5 月更新感谢 maganap 的评论 - 在 Node 10+ 中FUNCTION_NAME被替换为K_SERVICE(FUNCTION_TARGET是函数本身,而不是它的名称,正在替换ENTRY_POINT)。下面的代码示例已在下面更新。
More info at https://cloud.google.com/functions/docs/migrating/nodejs-runtimes#nodejs-10-changes
更多信息请访问https://cloud.google.com/functions/docs/migrating/nodejs-runtimes#nodejs-10-changes
Update- looks like a lot of these problems can be solved using the hidden variable process.env.FUNCTION_NAMEas seen here: https://github.com/firebase/functions-samples/issues/170#issuecomment-323375462
更新- 看起来很多这些问题可以使用隐藏变量解决,process.env.FUNCTION_NAME如下所示:https: //github.com/firebase/functions-samples/issues/170#issuecomment-323375462
Update with code- For example, if you have the following index file:
使用代码更新- 例如,如果您有以下索引文件:
...
exports.doSomeThing = require('./doSomeThing');
exports.doSomeThingElse = require('./doSomeThingElse');
exports.doOtherStuff = require('./doOtherStuff');
// and more.......
Then all of your files will be loaded, and all of those files' requirements will also be loaded, resulting in a lot of overhead and polluting your global scope for all of your functions.
然后你的所有文件都将被加载,所有这些文件的要求也将被加载,导致大量开销并污染你所有函数的全局范围。
Instead separating your includes out as:
而是将您的包含内容分离为:
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');
}
This will only load the required file(s) when that function is specifically called; allowing you to keep your global scope much cleaner which should result in faster cold-boots.
这只会在特定调用该函数时加载所需的文件;允许您保持全局范围更清洁,这将导致更快的冷启动。
This should allow for a much neater solution than what I've done below (though the explanation below still holds).
这应该允许比我在下面所做的更简洁的解决方案(尽管下面的解释仍然成立)。
Original Answer
原答案
It looks like requiring files and general initialisation happening in the global scope is a huge cause of slow-down during cold-boot.
看起来需要文件和在全局范围内发生的一般初始化是冷启动期间速度变慢的一个重要原因。
As a project gets more functions the global scope is polluted more and more making the problem worse - especially if you scope your functions into separate files (such as by using Object.assign(exports, require('./more-functions.js'));in your index.js.
随着项目获得更多函数,全局作用域受到越来越多的污染,从而使问题变得更糟 - 特别是如果您将函数作用于单独的文件中(例如Object.assign(exports, require('./more-functions.js'));在index.js.
I've managed to see huge gains in cold-boot performance by moving all my requires into an init method as below and then calling it as the first line inside any function definition for that file. Eg:
通过将我的所有需求移动到如下 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;
}
I've seen improvements from about 7-8s down to 2-3s when applying this technique to a project with ~30 functions across 8 files. This also seems to cause functions to need to be cold-booted less often (presumably due to lower memory usage?)
当将此技术应用于具有 8 个文件的约 30 个函数的项目时,我已经看到了从大约 7-8 秒缩短到 2-3 秒的改进。这似乎也导致函数需要较少地冷启动(大概是由于内存使用率较低?)
Unfortunately this still makes HTTP functions barely usable for user-facing production use.
不幸的是,这仍然使得 HTTP 函数几乎无法用于面向用户的生产用途。
Hoping the Firebase team have some plans in future to allow for proper scoping of functions so that only the relevant modules ever need to be loaded for each function.
希望 Firebase 团队在未来有一些计划,以允许适当的功能范围,以便每个功能只需要加载相关模块。
回答by Sudhakar R
I am facing similar issues with firestore cloud functions. The biggest is performance. Specially in case of early stage startups, when you can't afford your early customers to see "sluggish" apps. A simple documentation generation function for e.g gives this:
我在 Firestore 云功能方面面临着类似的问题。最大的是性能。特别是在早期初创公司的情况下,当您无法让早期客户看到“缓慢”的应用程序时。例如,一个简单的文档生成函数给出了这个:
-- Function execution took 9522 ms, finished with status code: 200
-- 函数执行耗时 9522 ms,完成状态码:200
Then: I had a straighforward terms and conditions page. With cloud functions the execution due to the cold start would take 10-15 seconds even at times. I then moved it to a node.js app, hosted on appengine container. The time has come down to 2-3 seconds.
然后:我有一个简单的条款和条件页面。使用云函数,由于冷启动而导致的执行有时甚至需要 10-15 秒。然后我将它移动到一个 node.js 应用程序,托管在 appengine 容器上。时间已经缩短到 2-3 秒。
I have been comparing many of the features of mongodb with firestore and sometimes I too wonder if during this early phase of my product I should also move to a different database. The biggest adv I had in firestore was the trigger functionality onCreate, onUpdate of document objects.
我一直在比较 mongodb 和 firestore 的许多功能,有时我也想知道在我产品的这个早期阶段我是否也应该转移到不同的数据库。我在 firestore 中最大的广告是文档对象的 onCreate 和 onUpdate 触发功能。
https://db-engines.com/en/system/Google+Cloud+Firestore%3BMongoDB
https://db-engines.com/en/system/Google+Cloud+Firestore%3BMongoDB
Basically if there are static portions of your site that can be offloaded to appengine environment, perhaps not a bad idea.
基本上,如果您网站的静态部分可以卸载到 appengine 环境,这也许不是一个坏主意。
回答by Stan Swiniarski
I have done these things as well, which improves performance once the functions are warmed up, but the cold start is killing me. One of the other issues I've encountered is with cors, because it takes two trips to the cloud functions to get the job done. I'm sure I can fix that, though.
我也做过这些事情,一旦功能预热就提高了性能,但是冷启动让我很沮丧。我遇到的其他问题之一是 cors,因为它需要两次访问云功能才能完成工作。不过,我确定我可以解决这个问题。
When you have an app in its early (demo) phase when it is not used frequently, the performance is not going to be great. This is something that should be considered, as early adopters with early product need to look their best in front of potential customers/investors. We loved the technology so we migrated from older tried-and-true frameworks, but our app seems pretty sluggish at this point. I'm going to next try some warm-up strategies to make it look better
当您的应用程序处于早期(演示)阶段且不经常使用时,性能不会很好。这是应该考虑的事情,因为早期产品的早期采用者需要在潜在客户/投资者面前展示自己的最佳状态。我们喜欢这项技术,所以我们从旧的久经考验的框架迁移,但我们的应用程序在这一点上似乎非常缓慢。接下来我要尝试一些热身策略,让它看起来更好
回答by George43g
I just published a package called firebase-cloud-functions, it automatically searches your function directory and correctly nests all the found functions in your exports object, while isolating the functions from each other to improve cold-boot performance.
我刚刚发布了一个名为 的包firebase-cloud-functions,它会自动搜索您的函数目录并将所有找到的函数正确嵌套在您的导出对象中,同时将这些函数彼此隔离以提高冷启动性能。
If you lazy-load and cache only the dependencies you need for each function within the module scope, you'll find it's the simplest and easiest way to keep your functions optimally efficient over a fast-growing project.
如果您只延迟加载和缓存模块范围内每个函数所需的依赖项,您会发现这是在快速增长的项目中保持函数最佳效率的最简单和最简单的方法。
import exportCloudFunctions from 'better-firebase-functions'
exportCloudFunctions(__dirname, __filename, exports, './', './**/*.js')

