Avoiding GCF anti-patterns part 1: How to write event-driven Cloud Functions properly by coding with idempotency in mind
Sara Ford
Senior Developer Relations Engineer
Martin Skoviera
Technical Solutions Engineer
Try Google Cloud
Start building on Google Cloud with $300 in free credits and 20+ always free products.
Free trialEditor's note: Over the next several weeks, you'll see a series of blog posts focusing on best practices for writing Google Cloud Functions based on common questions or misconceptions as seen by the Support team. We refer to these as "anti-patterns" and offer you ways to avoid them. This article is the first post in the series.
Scenario
You noticed that your Cloud Function executes multiple times for a single request. For example, your Function saves duplicate entries to Firestore for a single request.
Most common anti-pattern
Your Cloud Function is not idempotent. In this context, idempotent means that you should be able to call your Function multiple times with the same input data and the result of the initial call does not change. Examples are provided below.
How to investigate:
For background functions, it is important to be familiar with execution guarantees. A background Function is guaranteed to be invoked at least once, which implies the following:
- You need to test that you can call your Function multiple times with the same input data that does not change the initial result.
- Or if you cannot write your Function in an idempotent manner, you have a plan on how to handle these additional calls.
Examples of idempotent functions
In Serverless, it is important to write your code with idempotency in mind. If it is not possible to achieve idempotency, it's important to have a plan on how to handle duplicate invocations.
Let's first examine idempotent functions by exploring HTTP-triggered functions. Then you'll see an example of idempotency for background functions.
HTTP Function example
In the idempotent example below, a request contains a unique value (i.e. sensor ID & timestamp) which is used as the key to save to Firestore. Notice how Firestore.set() is used to specify this unique key. Therefore, even if the HTTP Function gets the request twice (theoretically speaking), you won't get duplicate entries in Firestore because the same unique key was used to create the document.
Now consider a non-idempotent HTTP Function example to show how the behavior differs from an idempotent example. This HTTP Function shown below uses Firestore auto-generated keys to save data by calling Firestore.add(). Therefore, if the HTTP Function gets the request twice (theoretically speaking), the Function will create two new entries in Firestore.
Background Function example
Now that you've seen an example using HTTP Functions, let's explore idempotency in background Functions. There are many ways to make retriable background Functions idempotent.
Let's continue with this example of saving data to Firestore, but this time let's change the Function to be triggered by PubSub instead of an HTTP request.
Generally speaking, you have two options when writing idempotent functions:
- Check to see if the data you are about to save already exists.
- Check to see if there's an idempotent version of the API call available e.g. Firestore.set() vs Firestore.add().
What if you cannot write an idempotent Function?
If you cannot make your function idempotent, e.g. you are relying on data from an outside source like stock inventory, you will need to implement workarounds or have a plan to deal with duplicate entries.
The key here is awareness. You want to strive for idempotency. And if you cannot write an idempotent function, it is important to be aware of any potential side effects of multiple requests.
Other helpful tips
- Once you have written an background Function with idempotency in mind, you may want to consider implementing retries in case of an intermittent or application error.
- Before enabling retries, you will want to confirm that your code cannot get into a continuous retry loop.