2

As you may have seen below; here are two different approaches to run different cloud functions as foo.js and bar.js.

In Method #1, admin, database and messaging modules initialized in index.js and passed as parameter to each related functions. Moreover, in Method #2, those parameters are defined inside each functions.

Which approach is applicable to minimize cold start time during running each function?

Method #1

index.js

const functions = require('firebase-functions');

const admin = require('firebase-admin');
admin.initializeApp();
const database = admin.database();
const messaging = admin.messaging();


const fooFunction = require('./foo');
exports.fooFunction = functions.database.ref('/users/messages_inbox').onCreate( (snapshot, context) => { fooFunction.handler(database, messaging, snapshot, context) });

const barFunction = require('./bar');
exports.barFunction = functions.database.ref('/users').onCreate( (snapshot, context) => { barFunction.handler(database, snapshot, context) });

foo.js

exports.handler = (database, messaging, snapshot, context) => {

  // some function
}

bar.js

exports.handler = (database, snapshot, context) => {

  // some function
}

Method #2

index.js

const functions = require('firebase-functions');

const fooFunction = require('./foo');
exports.fooFunction = functions.database.ref('/users/messages_inbox').onCreate( fooFunction.handler );

const barFunction = require('./bar');
exports.barFunction = functions.database.ref('/users').onCreate( barFunction.handler );

foo.js

exports.handler = (snapshot, context) => {
  const admin = require('firebase-admin');
  const database = admin.database();
  const messaging = admin.messaging();

  // some function
}

bar.js

exports.handler = (snapshot, context) => {
  const admin = require('firebase-admin');
  const database = admin.database();

  // some function
}
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Sunrise17
  • 379
  • 3
  • 18
  • You have the complete code so you should be able to test and see the actual difference between the two approaches. This link may help reach your findings: https://stackoverflow.com/questions/9132772/lazy-loading-in-node-js. The thing to consider is avoid loading unused modules on your function. – Donnald Cucharo Mar 15 '21 at 07:42
  • have a look at https://www.youtube.com/watch?v=IOXrwFqR6kY and https://medium.com/firebase-developers/organize-cloud-functions-for-max-cold-start-performance-and-readability-with-typescript-and-9261ee8450f0 – Renaud Tarnec Mar 15 '21 at 16:27
  • Hi @Sunrise17, as stated by the two comments above and elaborated in my answer below, "method #2" is generally speaking more applicable to minimize cold start time. If there is something unclear in my answer, let me know, otherwise please consider marking this question as resolved. thanks. – Felix K. Mar 25 '21 at 11:57

1 Answers1

2

TL;DR: From a cold-start perspective "Method 2" looks more favorable because you are (A) avoiding to load unneeded modules and (B) avoid to instantiate unnecessary services until they are actually required in your code

Long Answer:

Mainly two aspects play a role why "Method #2" leads to faster cold starts:

The first aspect is that by employing require syntax locally within your handlers instead of using global imports, you effectively avoid loading any unneeded modules. In a project with many handlers depending on entirely different sets of modules and services, this can result a noticable speed improvement.

On top of that, it will also optimize the resource usage of your cloud function server instance. This is because when deploying your project and using cloud functions, firebase will run your complete project code per cloud functions server instance. It then uses this dedicated server instance only to run one dedicated cloud function of your project. So if you used import syntax (as in "Method #1"), this would then result in the unnecessary inclusion of all imported dependencies of all functions in your project, even if they are never needed. – Your approach to only locally lazy-load modules via require effectively prevents this and therefore theoretically speeds up the cold start.

The second aspect of "Method #2" is that besides avoiding the loading of unnecessary modules (through the help of require()), you are delaying the instantiation of "services" (such as admin.database()) until you actually need them.

From an absolute-performance perspective, you are better off not instantiating any services until they are needed as done in "Method #2". So if your firebase event handlers do not necessarily rely on the "database" or "messaging" services, you can speed up things by not instantiating them in the global project scope. This is especially true when considering that the initialization performance of firebase services may change in the future – they are fast now, but who knows, maybe a call to admin.database() becomes computational heavier in the near future. That's why it is best practice to assume the worst and treat 3rd party libraries as blackboxes that are not under your control and on which you cannot rely to much on performance wise.

Drawbacks:

Instantiating services within your functions (as done in "Method #2") goes against the idea of dependency injection and makes unit testing harder, you should consider this.

Moreover, conditionally loading JS modules inside a function body via require() comes at the cost of readability and code maintainability. Moreover, it makes automated static code analyzability a challenging task and prevents that tools like webpack can conduct proper tree shacking. It may seem ironical, but while you might succeed in optimizing initialization time of your JS code, on a lower level, an increased bundle size could sub-optimally impact the gcloud container spinoff and thereby coldstart time (see Does Package Size Matter?) because the whole project image is larger and the container instance itself takes longer to be created.

In the end you should be careful not to microoptimize your project unless you hit performance bottlenecks and rather keep a good readable project structure.

Felix K.
  • 14,171
  • 9
  • 58
  • 72