Avoiding GCF anti-patterns part 2: How to reuse Cloud Function instances for future invocations

Editor’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 second post in the series.ScenarioYou notice that your Function is exhibiting one of the follow:slow to respond to a requestdemonstrates unexpected behavior on subsequent executionsruns out of memory over timeMost common root issueIf your Cloud Function is slow to respond, have you considered moving code into the global scope? However, if your Function is demonstrating unexpected behavior on subsequent executions or is running out of memory over time, do you have code written in the global scope that could be causing the issue?   How to investigateDoes your Function perform an expensive operation, e.g. time or network intensive operation, on every invocation within the Function event handler body? Examples include:opening a network connectionimporting a library referenceinstantiates an API client objectYou should consider moving such expensive operations into the global scope. What is the global scopeGlobal scope is defined as any code that is written outside the Function event handler. Code in the global scope is only executed once on instance startup. If a future Function invocation reuses that warm instance, the code in the global scope will not re-run again.  Technically speaking, code in global scope is executed additionally on the initial deployment for a “health check” – see Other helpful tips section below for more information about health checks.How to update your Function to use the global scopeSuppose you’re saving to Firestore. Instead of making the connection on each invocation, you can make the connection in the global scope. Cloud Functions tries to reuse the execution environment of the previous function when possible, e.g. the previous instance is still warm. This means you can potentially speed up your Functions by declaring variables in the global scope. Note: to be clear, there is no guarantee the previous environment will be used. But when the instance can be used, you should see performance benefits.In the example below, you’ll see how the connection to Firebase is outside the body of the Function event handler. Anything outside the Function event handler is in global scope.Lazy Initialization and global scopeWhen using global scope, it’s important to be aware of lazy initialization. When you use lazy initialization, you only initialize the code if or when you actually need it, while persisting that object in global scope for potential reuse. To illustrate, suppose your Function might have to create 2 or more network connections; however, you don’t know which of these connections you’ll need until runtime. You can use lazy initialization to delay making the connection until it is required, with the potential of retaining that connection for the next invocation. Other helpful tipsA couple of things to note when writing code in the global scope:It is paramount to have correct error handling and logging in global scope. If your code performs a deterministic operation (e.g. initializing a library) and crashes in the global scope, your Function will fail to deploy (known as a “health check” – more below). However, if you are performing an non-deterministic operation in the global scope (e.g. calling an API that could fail intermittently on any Function invocation), you will see an error “Could not load the function, shutting down” or “Function failed on loading user code.” in your logs. For background functions that have enabled the automatically retry on failure feature, PubSub will retry such failures due to errors in user code. The most important takeaway is that you have tested your code in global scope and have written proper error handling and logging. You can read more about Function deployments failing while executing code in the global scope in the troubleshooting guide. You might be surprised to see extra information in your logs coming from your code in global scope, e.g. the output from a `console.log()` statement. When a Cloud Function is deployed, a health check is performed to make sure the build succeeds and your Function has appropriate service account permissions. This health check will execute your code written in the global scope, hence the “extra” call in your logs.Related ArticleAvoiding GCF anti-patterns part 1: How to write event-driven Cloud Functions properly by coding with idempotency in mindFirst post in a series on how to avoid anti-patterns in Google Cloud Functions as seen by the Support team. This post explores what idemp…Read Article
Quelle: Google Cloud Platform