Tips & Tricks

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

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.

Always call the callback

The end of a function invocation is signalled by calling the callback for background functions, or sending the response (res.send(), res.end(), or res.redirect()) for HTTP functions. If you fail to call these objects in your execution flow, the function will time out.

Do not start background activities

A function invocation finishes when a callback (or response object for HTTP functions) is called. While it is technically possible to run a background activity, this background activity will not have access to the CPU and won’t make any progress. 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.

Background activity is anything that happens after you call the callback. Background activity can sometimes be buried deeper in the code--for example, in a promise that wasn't resolved, in a callback that wasn't called yet, or in a timer whose invocation is still scheduled. Carefully 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. All files that you write consume memory available to your function. Files that you write are usually available at consecutive invocations, therefore failing to 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. Also, use platform-independent methods such as os.tmpdir() and path.join() to construct the temporary file path, so that your functions also work on the emulator on any platform.

You can bypass the problem of creating temporary files by using pipelining. For example, you can process a file on Cloud Storage by creating a read stream, passing it through a scaling pipe of sharp, and writing directly back to Cloud Storage using a write stream. For details, see Image resizing using Node.js Stream and Sharp.

Tools

This section provides guidelines on how to use tools to implement, test, and interact with Cloud Functions.

Use existing tools

Function deployment takes a bit of time, so it is often faster to test the code of your function locally using the open source Cloud Functions Emulator.

Use a linter such as Semistandard or Prettier to check the syntax before you deploy a function.

Finally, try to use existing popular modules rather than creating your own.

Error reporting

Use console.error(new Error('message')) to report errors to Stackdriver Error Reporting. Do not throw uncaught exceptions, because they force cold starts in future invocations. For more information, see Reporting Errors.

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.

Minimize dependencies

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

If your functions specify dependent packages with require(), the load time for those packages adds to the invocation latency during a cold start. You can reduce this latency as well as function deploy time by minimizing the number of dependencies loaded by your function.

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:

Node.js

// Global (instance-wide) scope
// This computation runs at instance cold-start
const instanceVar = heavyComputation();

/**
 * HTTP Cloud Function that declares a variable.
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
exports.scopeDemo = (req, res) => {
  // Per-function scope
  // This computation runs every time this function is called
  const functionVar = lightComputation();

  res.send(`Per instance: ${instanceVar}, per function: ${functionVar}`);
};

It is particularly important to cache network connections, library references, and API clients 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 its latency. If some objects are not used in all code paths, consider initializing them lazily on demand:

Node.js

// Always initialized (at cold-start)
const nonLazyGlobal = fileWideComputation();

// Declared at cold-start, but only initialized if/when the function executes
let lazyGlobal;

/**
 * HTTP Cloud Function that uses lazy-initialized globals
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
exports.lazyGlobals = (req, res) => {
  // This value is initialized only if (and when) the function is called
  lazyGlobal = lazyGlobal || functionSpecificComputation();

  res.send(`Lazy global: ${lazyGlobal}, non-lazy global: ${nonLazyGlobal}`);
};

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 lose resources initializing variables that are never subsequently used.

Send feedback about...

Cloud Functions Documentation