Reducing Firestore Cold Start times in Firebase / Google Cloud Functions

I’m currently working on a new project, rack manage, which runs in a combination of Cloudflare and Google serverless environments. I decided to use Firebase for the cost and flexibility, thinking that the app wouldn’t need much besides a database connection, but as I’ve continued development, I’ve started leaning more and more on Google Cloud Functions to provide a secure backend for the app.

While this system has worked great, one problem I’ve been experiencing is cold start times for the app. Where the serverless instance must warm up, installing the required dependencies and setting up the function environment. While this wasn’t a big issue for most operations, there was one particular call that must run before render on the first session page load that was causing a very unpleasant load time of around 5 seconds from cold start and only 800 ms from warm start.

Recommendations

I found a few good articles and videos talking about how to reduce cold start times in Google Cloud Functions. The best article I found was Improving Cloud Function cold start time by Colt McAnlis of the Google Cloud Platform team (along with the supplemental video):

as well as this video guide from the Google Firebase team:

So I started going through the steps:

1: Trim Dependencies

I didn’t have any dependencies I wasn’t using or could remove, so no luck here.

2: Use Dependency Cache

I wasn’t using much besides firebase-functions, firebase-admin, and express, so there wasn’t much to do here either. I updated my dependencies to make sure I was on a popular version, but still no luck. 4 – 5 second load times on cold start.

3: Lazy Loading

This must be the one. I converted all of my function imports to named imports and moved the import statements inside of the function calls for lazy loading. Still no change. What’s going on???

Firestore Bugs & Solution

I did some more testing and found that my lazy loading was working. A basic “Hello World” function was responding in only 20ms but my simple data retrieval from Firestore was still taking 4 seconds on a cold start.

Finally, I stumbled upon this issue: https://issuetracker.google.com/issues/158014637

It turns out, there’s a known bug in Firestore that leads to long cold start times. Thankfully, this two year old bug had been resolved in the Google API repo 3 months prior, and had been released in the firebase-admin module just two weeks prior to writing this post.

See: https://github.com/firebase/firebase-admin-node/issues/1879

According to the Google engineers, cold start times were linked to load times accessing the file system and loading the gRPC library. In order to resolve the issue, a new HTTP/1.1 transport mode was added, with a constructor option preferRest: true to enable it.

While it hasn’t been set to the default method just yet, it sounds like this will likely be the case in the future.

In the meantime in firebase-admin, in order to use the new HTTP/1.1 transport mode, you must use the initializeFirestore() function rather than admin.firestore() or getFirestore() functions, as those don’t support passing settings.

One issue is that initializeFirestore() requires you to pass the Firebase app object as a parameter, while the other methods will automatically detect the current app.

This means that you will have to retrieve the app using

JavaScript
const { app } = require("firebase-admin");

or

JavaScript
const { initializeApp } = require("firebase-admin");
const app = initializeApp();

in order to get your app instance.

Using HTTP/1.1 with Express Functions

When working with an express app inside of Firebase Functions, you may not be able to retrieve your app instance inside of the router. One way to get around this is to convert your router export to a function that takes the app instance as a parameter, so you can pass the app object from index.js to router.js for example.

index.js:

JavaScript
const { https } = require("firebase-functions");
const { initializeApp } = require("firebase-admin/app");
const fbApp = initializeApp();

exports.api = https.onRequest((request, response) => {
    const router = require("./api/router.js")(fbApp);
    return router(request, response);
  });

router.js:

JavaScript

const express = require("express");
const expressApp = express();
const { initializeFirestore } = require("firebase-admin/firestore");

module.exports = function (fbApp) {
	expressApp.get("/", function (req, res, next) {
		let db = initializeFirestore(fbApp, {preferRest: true});
		let user = await db.collection("users").doc("demo").get();

		return res.json({
			success: true,
			code: 200,
			errors: [],
			result: user,
		});
	});

	return expressApp;
}

Results

In the end, switching Firestore from gRPC to HTTP/1.1 reduced my cold start times from 4 – 5 seconds to around 1 – 2 seconds, while warm response times also increased slightly from around 800ms to 400ms.

That’s more than twice the speed by simply enabling HTTP/1.1!