Firebase cloud functions is very slow

Update March 2021 It may be worth checking out the answer below from @George43g which offers a neat solution to automating the below process. Note - I haven't tried this myself and so cannot vouch for it, but it seems to automate the process described here. You can read more at https://github.com/gramstr/better-firebase-functions - otherwise read on for how to implement it yourself and understand what is happening inside functions.

Update May 2020 Thanks for the comment by maganap - in Node 10+ FUNCTION_NAME is replaced with K_SERVICE (FUNCTION_TARGET is the function itself, not it's name, replacing ENTRY_POINT). Code samples below have been udpated below.

More info at 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_NAME as seen here: 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.

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:

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?)

Unfortunately this still makes HTTP functions barely usable for user-facing production use.

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.


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 so that you don't pay for compute time that you're not using. 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).

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, especially if they have consistent traffic. If some functions tend to see spikes of traffic, however, you'll still see cold starts for every spike. In that case, you may want to consider the minInstances setting to keep a set number of instances of a latency-critical function warm at all times.


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:

-- Function execution took 9522 ms, finished with status code: 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.

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.

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.