General development tips

This guide provides best practices for designing, implementing, testing, and deploying a Cloud Run service. For more tips, see Migrating an Existing Service.

Write effective services

This section describes general best practices for designing and implementing a Cloud Run service.

Background activity

Background activity is anything that happens after your HTTP response has been delivered. To determine whether there is background activity in your service that is not readily apparent, check your logs for anything that is logged after the entry for the HTTP request.

Configure instance-based billing to use background activities

If you want to support background activities in your Cloud Run service, set your Cloud Run service to instance-based billing so you can run background activities outside of requests and still have CPU access.

Avoid background activities if using request-based billing

If you need to set your service to request-based billing, when the Cloud Run service finishes handling a request, the instance's access to CPU will be disabled or severely limited. You shouldn't start background threads or routines that run outside the scope of the request handlers if you use this type of billing.

Review your code to make sure all asynchronous operations finish before you deliver your response.

Running background threads with request-based billing enabled can result in unexpected behavior because any subsequent request to the same container instance resumes any suspended background activity.

Delete temporary files

In the Cloud Run environment, disk storage is an in-memory filesystem. Files written to disk consume memory otherwise available to your service, and can persist between invocations. Failing to delete these files can eventually lead to an out-of-memory error and a subsequent slow container startup times.

Report errors

Handle all exceptions and do not let your service crash on errors. A crash leads to a slow container startup while traffic is queued for a replacement instance.

See the Error reporting guide for information on how to properly report errors.

Optimize performance

This section describes best practices for optimizing performance.

Start containers quickly

Because instances are scaled as needed, their startup time has impact on the latency of your service. Cloud Run de-couples instance startup and request processing, so in some cases a request must wait for a new instance to start before the request is processed. This commonly happens when a service scales from zero.

The startup routine consists of:

  • Downloading the container image (using Cloud Run's container image streaming technology)
  • Starting the container by running the entrypoint command.
  • Waiting for the container to start listening on the configured port.

Optimizing for container startup speed minimizes the request processing latency.

Use startup CPU boost to reduce startup latency

You can enable startup CPU boost to temporarily increase CPU allocation during instance startup in order to reduce startup latency.

Use minimum instances to reduce container startup times

You can configure minimum instances and concurrency to minimize container startup times. For example, using a minimum instances of 1 means that your service is ready to receive up to the number of concurrent requests configured for your service without needing to start a new instance. When using minimum instances, avoid using system exits that will shut down an instance and potentially increase cold starts.

Note that a request waiting for an instance to start will be kept pending in a queue as follows:

Requests will pend for up to 3.5 times average startup time of container instances of this service, or 10 seconds, whichever is greater.

Use dependencies wisely

If you use a dynamic language with dependent libraries, such as importing modules in Node.js, the load time for those modules adds to the startup latency.

Reduce startup latency in these ways:

  • Minimize the number and size of dependencies to build a lean service.
  • Lazily load code that is infrequently used, if your language supports it.
  • Use code-loading optimizations such as PHP's composer autoloader optimization.

Use global variables

In Cloud Run, you cannot assume that service state is preserved between requests. However, Cloud Run does reuse individual instances to serve ongoing traffic, so you can declare a variable in global scope to allow its value to be reused in subsequent invocations. Whether any individual request receives the benefit of this reuse cannot be known ahead of time.

You can also cache objects in memory if they are expensive to recreate on each service request. Moving this from the request logic to global scope results in better performance.

Node.js

const functions = require('@google-cloud/functions-framework');

// TODO(developer): Define your own computations
const {lightComputation, heavyComputation} = require('./computations');

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

/**
 * HTTP function that declares a variable.
 *
 * @param {Object} req request context.
 * @param {Object} res response context.
 */
functions.http('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}`);
});

Python

import time

import functions_framework


# Placeholder
def heavy_computation():
    return time.time()


# Placeholder
def light_computation():
    return time.time()


# Global (instance-wide) scope
# This computation runs at instance cold-start
instance_var = heavy_computation()


@functions_framework.http
def scope_demo(request):
    """
    HTTP Cloud Function that declares a variable.
    Args:
        request (flask.Request): The request object.
        <http://flask.pocoo.org/docs/1.0/api/#flask.Request>
    Returns:
        The response text, or any set of values that can be turned into a
        Response object using `make_response`
        <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>.
    """

    # Per-function scope
    # This computation runs every time this function is called
    function_var = light_computation()
    return f"Instance: {instance_var}; function: {function_var}"

Go


// h is in the global (instance-wide) scope.
var h string

// init runs during package initialization. So, this will only run during an
// an instance's cold start.
func init() {
	h = heavyComputation()
	functions.HTTP("ScopeDemo", ScopeDemo)
}

// ScopeDemo is an example of using globally and locally
// scoped variables in a function.
func ScopeDemo(w http.ResponseWriter, r *http.Request) {
	l := lightComputation()
	fmt.Fprintf(w, "Global: %q, Local: %q", h, l)
}

Java


import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;

public class Scopes implements HttpFunction {
  // Global (instance-wide) scope
  // This computation runs at instance cold-start.
  // Warning: Class variables used in functions code must be thread-safe.
  private static final int INSTANCE_VAR = heavyComputation();

  @Override
  public void service(HttpRequest request, HttpResponse response)
      throws IOException {
    // Per-function scope
    // This computation runs every time this function is called
    int functionVar = lightComputation();

    var writer = new PrintWriter(response.getWriter());
    writer.printf("Instance: %s; function: %s", INSTANCE_VAR, functionVar);
  }

  private static int lightComputation() {
    int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    return Arrays.stream(numbers).sum();
  }

  private static int heavyComputation() {
    int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    return Arrays.stream(numbers).reduce((t, x) -> t * x).getAsInt();
  }
}

Perform lazy initialization of global variables

The initialization of global variables always occurs during startup, which increases container startup time. Use lazy initialization for infrequently used objects to defer the time cost and decrease container startup times.

One drawback of lazy initialization is an increased latency for first requests to new instances. This can cause overscaling and dropped requests when you deploy a new revision of a service that is actively handling many requests.

Node.js

const functions = require('@google-cloud/functions-framework');

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

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

/**
 * HTTP function that uses lazy-initialized globals
 *
 * @param {Object} req request context.
 * @param {Object} res response context.
 */
functions.http('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}`);
});

Python

import functions_framework

# Always initialized (at cold-start)
non_lazy_global = file_wide_computation()

# Declared at cold-start, but only initialized if/when the function executes
lazy_global = None


@functions_framework.http
def lazy_globals(request):
    """
    HTTP Cloud Function that uses lazily-initialized globals.
    Args:
        request (flask.Request): The request object.
        <http://flask.pocoo.org/docs/1.0/api/#flask.Request>
    Returns:
        The response text, or any set of values that can be turned into a
        Response object using `make_response`
        <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>.
    """
    global lazy_global, non_lazy_global  # noqa: F824

    # This value is initialized only if (and when) the function is called
    if not lazy_global:
        lazy_global = function_specific_computation()

    return f"Lazy: {lazy_global}, non-lazy: {non_lazy_global}