
It is cold outside: keeping your Firebase Functions Trigger warm
5 min read
Firebase Functions provides a robust serverless backend offering. While it’s excellent for developing backends for applications that don’t require a traditional server, a significant selling point for me is Firebase Functions triggers. These triggers allow you to execute code in response to events occurring within the Firebase ecosystem, such as a new document creation in Firestore, a new user signup, or the upload of a cat photo to Firebase Storage.
Disclaimer: There are two versions of Firebase Functions: V1 (the original) and V2 (the latest iteration, built on Cloud Run). This article will primarily focus on V2.
The possibilities for what you can accomplish within your function’s code are truly limited only by your imagination. You could, for instance, send a WhatsApp message whenever an order document’s status changes from “processing” to “shipped,” dispatch a coupon code to a new user upon registration, or automatically publish an uploaded cat photo to a Facebook group.
Understanding the Cold Start Problem
However, a critical consideration with Firebase Functions triggers, and serverless functions in general, is the “cold start” phenomenon. This occurs when a function receives requests (or background triggers, as discussed) but no active instances are available to process them. A new instance must then be provisioned and booted from scratch, which can take several seconds until it’s ready to handle the request. This delay can significantly impact your application’s responsiveness, for example, causing a noticeable lag in a payment successful notification.
The Advantage of Warm Starts
Fortunately, a single Cloud Run instance can serve multiple requests concurrently. Once an instance is ready, subsequent requests will be routed to the same instance. This instance remains active until there are no further requests to handle within a defined period. This optimized scenario is known as a “warm start.”
So, what happens if a request arrives after an instance is no longer alive? You’re back to a cold start! The duration of a cold start is influenced by a multitude of factors, which are beyond the scope of this article. Instead, we’ll concentrate on strategies to keep your functions warm.
One certainty is that once a function is ready to handle requests, it can concurrently manage others. Therefore, our goal is to maintain at least one instance in a warm state. But wouldn’t keeping an instance running constantly incur costs? The excellent news is that Firebase and Cloud Run only bill you for the time spent processing requests, not for idle periods. This means if we can periodically “ping” our background functions, we can essentially have a 24/7 function that is only billed when actively handling a request.
Implementing a Keep-Alive Mechanism
Let’s explore how to achieve this. We’ll begin by writing a background function that is invoked whenever a Firestore document is created.
export const event = functions.firestore.onDocumentCreated(
{ document: `orders/{id}`, retry: true },
async (firebaseEvent) => {
const data = firebaseEvent.data?.data();
// do stuffs, send message, push to pubsub, etc
}
);
Once you’ve deployed this function to your Firebase project, navigate to the Google Cloud Console and open the Cloud Run administration interface. Select your deployed function, and within the “Metrics” tab (which should be the default view), you’ll find a button labeled “Create uptime check.” This is the key ingredient we’ll leverage.
Uptime checks are a sub-product of Google’s Stackdriver monitoring toolkit. As the name suggests, they periodically verify the availability of your functions at predefined intervals. Before configuring an uptime check, let’s return to our function’s code. Since our function expects to be invoked by a trigger/Cloud Event, we must modify it to gracefully handle a ping request.
export const event = functions.firestore.onDocumentCreated(
{ document: `orders/{id}`, retry: true },
async (firebaseEvent) => {
+ if (!firebaseEvent.params.id) {
+ // uptime check
+ return;
+ }
const data = firebaseEvent.data?.data();
// do stuffs, send message, push to pubsub, etc
},
);
This conditional check relies on the invocation source. In this example, a Firestore document creation trigger will always provide an id parameter. If id is missing, we can infer that the invocation is not from a Cloud Event. Once modified, redeploy the function to Firebase.
Configuring the Uptime Check
Now, let’s create the uptime check. I recommend keeping most settings at their default values and primarily adjusting the check frequency and regions. In my experience, a 5-minute interval is acceptable, but you can experiment and observe the results in your function invocation metrics. For regions, select approximately three, as this will impact pricing, which we’ll discuss next.
Cost Implications
What about the cost? What if I told you it could be free? As of May 2025, the pricing is $0.30 per 1,000 executions. However, there’s a generous free tier of 1 million executions per project per month. If we calculate the total number of executions from an uptime check with a 5-minute interval and 3 regions, we get approximately 26,280 executions, which is well within the free tier. So, there you have it!
Real-World Impact
Does it actually work? Since I’m currently too lazy to create a new demonstration for this blog, I’ll share an example from my recent usage of this technique.
As you can see, since the uptime check technique was applied around May 18th, the request latency for this particular function dropped significantly from over 3 seconds to well below 1 second.
There you have it, folks! I hope you’ve learned something new today. Keep on coding, because competence feels great.