Mastering Firebase Cloud Functions to Avoid Infinite Loops
Written on
Understanding Firebase Firestore Limitations
In my applications, I utilize Firebase Firestore, which generally performs well. However, it does have some limitations, particularly with full-text search capabilities. Today, I want to address another challenge I encountered: managing timestamps.
While timestamps may seem straightforward—simply save DateTime.now() to the database—the accuracy of this approach hinges on the user's device time being correct. Although most users maintain accurate device times, some may deliberately set their clocks forward to exploit certain games. I want to ensure my application remains robust in these scenarios. So, what are the alternatives?
Fortunately, Firebase offers FieldValue.serverTimestamp(), which allows you to update a document with a server-generated timestamp. This feature works well for obtaining the current server time, but if you need a timestamp set for a future time (e.g., five hours ahead), it falls short. Although the Firebase team might address this in the future—there's already FieldValue.increment(int num) for numerical fields—there's currently no straightforward method for manipulating timestamps. This is where Firebase Cloud Functions come into play.
Firebase Cloud Functions provide the ability to execute functions on Google's servers, enabling powerful integrations with other Firebase services. Additionally, you can listen for various database events, specifically:
- onDocumentCreated
- onDocumentUpdated
- onDocumentDeleted
- onDocumentWritten
However, proceed with caution:
Infinite Loop Risks in Document Listeners
When working with document listeners, updating a document can trigger the listener again, leading to potential infinite loops if not handled correctly. I experienced this firsthand but fortunately had a failsafe in place. Previously, I discussed this failsafe in a post about securing API keys, where I shared a function that rate limits API calls.
This same approach is effective for preventing infinite loops caused by document updates. For example, by using the following code:
if (await isRateLimited("Read", 10)) {
return;
}
This line of code prevented my function from entering an endless cycle of recursive calls. You might think that you’d never make such a mistake, but I was surprised to discover two bugs that led to such issues.
The first bug stemmed from how I checked whether to cancel the request. My logic involved checking if the timestamp had changed; if not, the code would do nothing. However, the timestamp shifted by a few nanoseconds with each invocation, causing the loop to persist. Whoops!
The second issue was the infamous Firestore proprietary timestamp format. Instead of providing a native JavaScript Date object, Firestore returns its own timestamp object. I had encountered this problem before in Flutter and learned that you must convert each Firestore timestamp to a native object manually. Here’s how I handle it:
const nativeDate = new Date(firestoreTimestamp.seconds * 1000);
Key Takeaway
The main lesson here is the importance of exercising caution with recursive functions that can incur costs for every invocation. Consequently, I’ve decided to keep the rate-limiting code in production—albeit set to a higher threshold, around 100 invocations (I’m aiming for more users). I also modified it to log the current time when the rate limit is reached, allowing me to investigate and adjust as needed.
Here’s the final code snippet I implemented:
// Final code example...
Initially, when I wrote this cloud function, I didn’t anticipate encountering any issues. I had tested getNewDateTimeForAccountDeletion() and was confident it would work. However, I realized I hadn’t conducted sufficient testing. Thankfully, my failsafe was in place; otherwise, my Firebase bill could have skyrocketed, and I might have found myself pleading with Google to waive those charges.
This experience has been a mixed blessing. I've learned how easily infinite loops can arise in code, yet I also feel more confident in my ability to work with Cloud Functions. While I could have relied on the user's device for accurate timestamps, I wanted to deepen my understanding of Cloud Functions—and I certainly achieved that goal.
Future Aspirations
I’ve been contemplating the migration of my entire backend from a somewhat clunky VPS setup to a fully Firebase-based backend. This exercise has significantly advanced that ambition.
If you found this post insightful and wish to keep up with my future articles, consider using my RSS app, Stratum, available on iOS and Android. Also, check out my language learning app, Litany (iOS, Android).
Chapter 2: Implementing Location-Based Apps with Google Maps
Explore how to create location-based applications using Google Maps with FlutterFlow. This video provides a comprehensive overview of the development process.
Chapter 3: Crafting Subtle Yet Engaging Scroll Animations
Discover techniques for implementing elegant scroll animations that enhance user experience in your applications. This video showcases practical examples and tips.