Tips & tricks

This document describes best practices for designing, implementing, testing,
and deploying Cloud Functions.

Note: Several of the recommendations in this document center around what is
known as a cold start. Functions are stateless, and the execution environment
is often initialized from scratch, which is called a cold start. Cold starts can
take significant amounts of time to complete. It is best practice to avoid
unnecessary cold starts, and to streamline the cold start process to whatever
extent possible (for example, by avoiding unnecessary dependencies).

Correctness

This section describes general best practices for designing and implementing
Cloud Functions.

Write idempotent functions

Your functions should produce the same result even if they are called multiple
times. This lets you retry an invocation if the previous invocation fails
partway through your code. For more information, see
retrying background functions.

Do not start background activities

Background activity is anything that happens after your function has terminated.
A function invocation finishes once the function returns or otherwise signals
completion, such as by calling the callback argument in Node.js background
functions. Any code run after graceful termination cannot access the CPU and
will not make any progress.

Note: If a Node.js background function returns a Promise, Cloud Functions
ensures that the Promise is settled before terminating.

In addition, when a subsequent invocation is executed in the same environment,
your background activity resumes, interfering with the new invocation. This may
lead to unexpected behavior and errors that are hard to diagnose. Accessing
the network after a function terminates usually leads to connections being reset
(ECONNRESET error code).

Background activity can often be detected in logs from individual invocations,
by finding anything that is logged after the line saying that the invocation
finished. Background activity can sometimes be buried deeper in the code,
especially when asynchronous operations such as callbacks or timers are present.
Review your code to make sure all asynchronous operations finish before you
terminate the function.

Always delete temporary files

Local disk storage in the temporary directory is an in-memory filesystem. Files
that you write consume memory available to your function, and sometimes persist
between invocations. Failing to explicitly delete these files may eventually
lead to an out-of-memory error and a subsequent cold start.

You can see the memory used by an individual function by selecting it in the
list of functions in the GCP Console
and choosing the Memory usage plot.

Do not attempt to write outside of the temporary directory, and be sure to use
platform/OS-independent methods to construct file paths.

You can bypass the size restrictions on temporary files by using pipelining. For
example, you can process a file on Cloud Storage by creating a read stream,
passing it through a stream-based process, and writing the output stream
directly to Cloud Storage.

Local development

Use Sendgrid to send emails

Cloud Functions does not allow outbound connections on port 25, so you cannot
make non-secure connections to an SMTP server. The recommended way to send
emails is to use SendGrid. You can find a complete
example in the SendGrid Tutorial,
and other options for sending email in the Google Compute Engine document
Sending Email from an Instance.

Performance

This section describes best practices for optimizing performance.

Use dependencies wisely

Because functions are stateless, the execution environment is often initialized
from scratch (during what is known as a cold start). When a cold start occurs,
the global context of the function is evaluated.

If your functions import modules, the load time for those modules can add to the
invocation latency during a cold start. You can reduce this latency, as well as
the time needed to deploy your function, by loading dependencies correctly and
not loading dependencies your function doesn't use.

Use global variables to reuse objects in future invocations

There is no guarantee that the state of a Cloud Function will be preserved for
future invocations. However, Cloud Functions often recycles the execution
environment of a previous invocation. If you declare a variable in global scope,
its value can be reused in subsequent invocations without having to be
recomputed.

This way you can cache objects that may be expensive to recreate on each
function invocation. Moving such objects from the function body to global scope
may result in significant performance improvements. The following example
creates a heavy object only once per function instance, and shares it across all
function invocations reaching the given instance:

It is particularly important to cache network connections, library references,
and API client objects in global scope.
See Optimizing Networking for examples.

Do lazy initialization of global variables

If you initialize variables in global scope, the initialization code will always
be executed via a cold start invocation, increasing your function's latency. If
some objects are not used in all code paths, consider initializing them lazily
on demand:

This is particularly important if you define several functions in a single file,
and different functions use different variables. Unless you use lazy
initialization, you may waste resources on variables that are initialized but
never used.

Note: this technique can also be used when importing dependencies in Node.js and
Python, albeit at the expense of code readability.