Prácticas recomendadas para las funciones

En este documento se describen las prácticas recomendadas para diseñar, implementar, probar y desplegar funciones de Cloud Run.

Corrección

En esta sección se describen las prácticas recomendadas generales para diseñar e implementar funciones de Cloud Run.

Escribir funciones idempotentes

Tus funciones deben producir el mismo resultado aunque se llamen varias veces. Esto te permite volver a intentar una invocación si la anterior falla a mitad del código. Para obtener más información, consulta Reintentar funciones controladas por eventos.

Asegurarse de que las funciones HTTP envían una respuesta HTTP

Si tu función se activa mediante HTTP, recuerda enviar una respuesta HTTP, como se muestra a continuación. Si no lo haces, la función se ejecutará hasta que se agote el tiempo de espera. Si esto ocurre, se te cobrará todo el tiempo de espera. Los tiempos de espera también pueden provocar comportamientos impredecibles o arranques en frío en invocaciones posteriores, lo que da lugar a comportamientos impredecibles o latencia adicional.

Node.js

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

/**
 * Responds to an HTTP request using data from the request body parsed according
 * to the "content-type" header.
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
functions.http('helloHttp', (req, res) => {
  res.send(`Hello ${escapeHtml(req.query.name || req.body.name || 'World')}!`);
});

Python


import functions_framework


from markupsafe import escape

@functions_framework.http
def hello_http(request):
    """HTTP Cloud Function.
    Args:
        request (flask.Request): The request object.
        <https://flask.palletsprojects.com/en/1.1.x/api/#incoming-request-data>
    Returns:
        The response text, or any set of values that can be turned into a
        Response object using `make_response`
        <https://flask.palletsprojects.com/en/1.1.x/api/#flask.make_response>.
    """
    request_json = request.get_json(silent=True)
    request_args = request.args

    if request_json and "name" in request_json:
        name = request_json["name"]
    elif request_args and "name" in request_args:
        name = request_args["name"]
    else:
        name = "World"
    return f"Hello {escape(name)}!"

Go


// Package helloworld provides a set of Cloud Functions samples.
package helloworld

import (
	"encoding/json"
	"fmt"
	"html"
	"net/http"

	"github.com/GoogleCloudPlatform/functions-framework-go/functions"
)

func init() {
	functions.HTTP("HelloHTTP", HelloHTTP)
}

// HelloHTTP is an HTTP Cloud Function with a request parameter.
func HelloHTTP(w http.ResponseWriter, r *http.Request) {
	var d struct {
		Name string `json:"name"`
	}
	if err := json.NewDecoder(r.Body).Decode(&d); err != nil {
		fmt.Fprint(w, "Hello, World!")
		return
	}
	if d.Name == "" {
		fmt.Fprint(w, "Hello, World!")
		return
	}
	fmt.Fprintf(w, "Hello, %s!", html.EscapeString(d.Name))
}

Java


import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.HttpRequest;
import com.google.cloud.functions.HttpResponse;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.logging.Logger;

public class HelloHttp implements HttpFunction {
  private static final Logger logger = Logger.getLogger(HelloHttp.class.getName());

  private static final Gson gson = new Gson();

  @Override
  public void service(HttpRequest request, HttpResponse response)
      throws IOException {
    // Check URL parameters for "name" field
    // "world" is the default value
    String name = request.getFirstQueryParameter("name").orElse("world");

    // Parse JSON request and check for "name" field
    try {
      JsonElement requestParsed = gson.fromJson(request.getReader(), JsonElement.class);
      JsonObject requestJson = null;

      if (requestParsed != null && requestParsed.isJsonObject()) {
        requestJson = requestParsed.getAsJsonObject();
      }

      if (requestJson != null && requestJson.has("name")) {
        name = requestJson.get("name").getAsString();
      }
    } catch (JsonParseException e) {
      logger.severe("Error parsing JSON: " + e.getMessage());
    }

    var writer = new PrintWriter(response.getWriter());
    writer.printf("Hello %s!", name);
  }
}

C#

using Google.Cloud.Functions.Framework;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;

namespace HelloHttp;

public class Function : IHttpFunction
{
    private readonly ILogger _logger;

    public Function(ILogger<Function> logger) =>
        _logger = logger;

    public async Task HandleAsync(HttpContext context)
    {
        HttpRequest request = context.Request;
        // Check URL parameters for "name" field
        // "world" is the default value
        string name = ((string) request.Query["name"]) ?? "world";

        // If there's a body, parse it as JSON and check for "name" field.
        using TextReader reader = new StreamReader(request.Body);
        string text = await reader.ReadToEndAsync();
        if (text.Length > 0)
        {
            try
            {
                JsonElement json = JsonSerializer.Deserialize<JsonElement>(text);
                if (json.TryGetProperty("name", out JsonElement nameElement) &&
                    nameElement.ValueKind == JsonValueKind.String)
                {
                    name = nameElement.GetString();
                }
            }
            catch (JsonException parseException)
            {
                _logger.LogError(parseException, "Error parsing JSON request");
            }
        }

        await context.Response.WriteAsync($"Hello {name}!", context.RequestAborted);
    }
}

Ruby

require "functions_framework"
require "cgi"
require "json"

FunctionsFramework.http "hello_http" do |request|
  # The request parameter is a Rack::Request object.
  # See https://www.rubydoc.info/gems/rack/Rack/Request
  name = request.params["name"] ||
         (request.body.rewind && JSON.parse(request.body.read)["name"] rescue nil) ||
         "World"
  # Return the response body as a string.
  # You can also return a Rack::Response object, a Rack response array, or
  # a hash which will be JSON-encoded into a response.
  "Hello #{CGI.escape_html name}!"
end

PHP

<?php

use Google\CloudFunctions\FunctionsFramework;
use Psr\Http\Message\ServerRequestInterface;

// Register the function with Functions Framework.
// This enables omitting the `FUNCTIONS_SIGNATURE_TYPE=http` environment
// variable when deploying. The `FUNCTION_TARGET` environment variable should
// match the first parameter.
FunctionsFramework::http('helloHttp', 'helloHttp');

function helloHttp(ServerRequestInterface $request): string
{
    $name = 'World';
    $body = $request->getBody()->getContents();
    if (!empty($body)) {
        $json = json_decode($body, true);
        if (json_last_error() != JSON_ERROR_NONE) {
            throw new RuntimeException(sprintf(
                'Could not parse body: %s',
                json_last_error_msg()
            ));
        }
        $name = $json['name'] ?? $name;
    }
    $queryString = $request->getQueryParams();
    $name = $queryString['name'] ?? $name;

    return sprintf('Hello, %s!', htmlspecialchars($name));
}

No iniciar actividades en segundo plano

La actividad en segundo plano es todo lo que ocurre después de que tu función haya finalizado. Una invocación de función finaliza cuando la función devuelve un valor o indica que se ha completado de otro modo, como llamando al argumento callback en las funciones basadas en eventos de Node.js. Cualquier código que se ejecute después de la finalización correcta no podrá acceder a la CPU y no avanzará.

Además, cuando se ejecuta una invocación posterior en el mismo entorno, tu actividad en segundo plano se reanuda, lo que interfiere con la nueva invocación. Esto puede provocar un comportamiento inesperado y errores difíciles de diagnosticar. Acceder a la red después de que finalice una función suele provocar que se restablezcan las conexiones (código de error ECONNRESET).

La actividad en segundo plano se puede detectar en los registros de invocaciones individuales. Para ello, busca cualquier elemento que se registre después de la línea que indica que la invocación ha finalizado. A veces, la actividad en segundo plano puede estar más oculta en el código, sobre todo cuando hay operaciones asíncronas, como retrollamadas o temporizadores. Revisa el código para asegurarte de que todas las operaciones asíncronas finalicen antes de terminar la función.

Eliminar siempre los archivos temporales

El almacenamiento en disco local en el directorio temporal es un sistema de archivos en memoria. Los archivos que escribes consumen la memoria disponible para tu función y, a veces, se conservan entre invocaciones. Si no se eliminan explícitamente estos archivos, puede que se produzca un error de falta de memoria y un arranque en frío.

Para ver la memoria que usa una función concreta, selecciónala en la lista de funciones de laGoogle Cloud consola y elige el gráfico Uso de memoria.

Si necesitas acceder a almacenamiento a largo plazo, considera la posibilidad de usar los montajes de volúmenes de Cloud Run con Cloud Storage o volúmenes NFS.

Puedes reducir los requisitos de memoria al procesar archivos más grandes mediante el procesamiento simultáneo. Por ejemplo, puedes procesar un archivo en Cloud Storage creando un flujo de lectura, pasándolo por un proceso basado en flujos y escribiendo el flujo de salida directamente en Cloud Storage.

Framework de Functions

Para asegurarte de que las mismas dependencias se instalan de forma coherente en todos los entornos, te recomendamos que incluyas la biblioteca Functions Framework en tu gestor de paquetes y que fijes la dependencia a una versión específica de Functions Framework.

Para ello, incluye la versión que prefieras en el archivo de bloqueo correspondiente (por ejemplo, package-lock.json para Node.js o requirements.txt para Python).

Si Functions Framework no aparece explícitamente como dependencia, se añadirá automáticamente durante el proceso de compilación con la última versión disponible.

Herramientas

En esta sección se proporcionan directrices sobre cómo usar herramientas para implementar, probar e interactuar con funciones de Cloud Run.

Desarrollo local

El despliegue de funciones lleva un poco de tiempo, por lo que suele ser más rápido probar el código de tu función de forma local.

Informes de errores

En los lenguajes que usan el control de excepciones, no se deben lanzar excepciones no detectadas, ya que fuerzan los inicios en frío en invocaciones futuras.

No salgas manualmente

Si sales manualmente, puede que se produzca un comportamiento inesperado. En su lugar, usa las siguientes expresiones idiomáticas específicas de cada idioma:

Node.js

No uses process.exit(). Las funciones HTTP deben enviar una respuesta con res.status(200).send(message), y las funciones basadas en eventos se cerrarán cuando devuelvan un valor (ya sea de forma implícita o explícita).

Python

No uses sys.exit(). Las funciones HTTP deben devolver explícitamente una respuesta como una cadena, y las funciones basadas en eventos se cerrarán cuando devuelvan un valor (ya sea implícita o explícitamente).

Go

No uses os.Exit(). Las funciones HTTP deben devolver explícitamente una respuesta como una cadena, y las funciones basadas en eventos se cerrarán cuando devuelvan un valor (ya sea implícita o explícitamente).

Java

No uses System.exit(). Las funciones HTTP deben enviar una respuesta con response.getWriter().write(message), y las funciones basadas en eventos se cerrarán cuando devuelvan un valor (ya sea de forma implícita o explícita).

C#

No uses System.Environment.Exit(). Las funciones HTTP deben enviar una respuesta con context.Response.WriteAsync(message), y las funciones basadas en eventos se cerrarán cuando devuelvan un valor (ya sea de forma implícita o explícita).

Ruby

No uses exit() ni abort(). Las funciones HTTP deben devolver explícitamente una respuesta como una cadena, y las funciones basadas en eventos se cerrarán cuando devuelvan un valor (ya sea implícita o explícitamente).

PHP

No uses exit() ni die(). Las funciones HTTP deben devolver explícitamente una respuesta como una cadena, y las funciones basadas en eventos se cerrarán cuando devuelvan un valor (ya sea implícita o explícitamente).

Usar SendGrid para enviar correos

Las funciones de Cloud Run no permiten conexiones salientes en el puerto 25, por lo que no puedes establecer conexiones no seguras con un servidor SMTP. La forma recomendada de enviar correos es usar un servicio de terceros, como SendGrid. Puedes consultar otras opciones para enviar correos en el tutorial Enviar correos desde una instancia de Google Compute Engine.

Rendimiento

En esta sección se describen las prácticas recomendadas para optimizar el rendimiento.

Evitar la baja simultaneidad

Como los arranques en frío son costosos, poder reutilizar instancias iniciadas recientemente durante un pico es una optimización excelente para gestionar la carga. Limitar la simultaneidad limita el uso que se puede hacer de las instancias, lo que provoca más arranques en frío.

Aumentar la simultaneidad ayuda a diferir varias solicitudes por instancia, lo que facilita la gestión de los picos de carga.

Usa las dependencias con cabeza

Como las funciones no tienen estado, el entorno de ejecución se inicializa a menudo desde cero (durante lo que se conoce como arranque en frío). Cuando se produce un arranque en frío, se evalúa el contexto global de la función.

Si tus funciones importan módulos, el tiempo de carga de esos módulos puede aumentar la latencia de invocación durante un arranque en frío. Puedes reducir esta latencia, así como el tiempo necesario para desplegar tu función, cargando las dependencias correctamente y no cargando las que no utilice.

Usar variables globales para reutilizar objetos en invocaciones futuras

No se garantiza que el estado de una función de Cloud Run se conserve para futuras invocaciones. Sin embargo, las funciones de Cloud Run suelen reutilizar el entorno de ejecución de una invocación anterior. Si declaras una variable en el ámbito global, su valor se puede reutilizar en invocaciones posteriores sin tener que volver a calcularse.

De esta forma, puedes almacenar en caché objetos que pueden ser caros de recrear en cada invocación de función. Mover estos objetos del cuerpo de la función al ámbito global puede mejorar significativamente el rendimiento. En el siguiente ejemplo, se crea un objeto pesado solo una vez por instancia de función y se comparte en todas las invocaciones de función que llegan a la instancia dada:

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();
  }
}

C#

using Google.Cloud.Functions.Framework;
using Microsoft.AspNetCore.Http;
using System.Linq;
using System.Threading.Tasks;

namespace Scopes;

public class Function : IHttpFunction
{
    // Global (server-wide) scope.
    // This computation runs at server cold-start.
    // Warning: Class variables used in functions code must be thread-safe.
    private static readonly int GlobalVariable = HeavyComputation();

    // Note that one instance of this class (Function) is created per invocation,
    // so calling HeavyComputation in the constructor would not have the same
    // benefit.

    public async Task HandleAsync(HttpContext context)
    {
        // Per-function-invocation scope.
        // This computation runs every time this function is called.
        int functionVariable = LightComputation();

        await context.Response.WriteAsync(
            $"Global: {GlobalVariable}; function: {functionVariable}",
            context.RequestAborted);
    }

    private static int LightComputation()
    {
        int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        return numbers.Sum();
    }

    private static int HeavyComputation()
    {
        int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        return numbers.Aggregate((current, next) => current * next);
    }
}

Ruby

# Global (instance-wide) scope.
# This block runs on cold start, before any function is invoked.
#
# Note: It is usually best to run global initialization in an on_startup block
# instead at the top level of the Ruby file. This is because top-level code
# could be executed to verify the function during deployment, whereas an
# on_startup block is run only when an actual function instance is starting up.
FunctionsFramework.on_startup do
  instance_data = perform_heavy_computation

  # To pass data into function invocations, the best practice is to set a
  # key-value pair using the Ruby Function Framework's built-in "set_global"
  # method. Functions can call the "global" method to retrieve the data by key.
  # (You can also use Ruby global variables or "toplevel" local variables, but
  # they can make it difficult to isolate global data for testing.)
  set_global :my_instance_data, instance_data
end

FunctionsFramework.http "tips_scopes" do |_request|
  # Per-function scope.
  # This method is called every time this function is called.
  invocation_data = perform_light_computation

  # Retrieve the data computed by the on_startup block.
  instance_data = global :my_instance_data

  "instance: #{instance_data}; function: #{invocation_data}"
end

PHP


use Psr\Http\Message\ServerRequestInterface;

function scopeDemo(ServerRequestInterface $request): string
{
    // Heavy computations should be cached between invocations.
    // The PHP runtime does NOT preserve variables between invocations, so we
    // must write their values to a file or otherwise cache them.
    // (All writable directories in Cloud Functions are in-memory, so
    // file-based caching operations are typically fast.)
    // You can also use PSR-6 caching libraries for this task:
    // https://packagist.org/providers/psr/cache-implementation
    $cachePath = sys_get_temp_dir() . '/cached_value.txt';

    $response = '';
    if (file_exists($cachePath)) {
        // Read cached value from file, using file locking to prevent race
        // conditions between function executions.
        $response .= 'Reading cached value.' . PHP_EOL;
        $fh = fopen($cachePath, 'r');
        flock($fh, LOCK_EX);
        $instanceVar = stream_get_contents($fh);
        flock($fh, LOCK_UN);
    } else {
        // Compute cached value + write to file, using file locking to prevent
        // race conditions between function executions.
        $response .= 'Cache empty, computing value.' . PHP_EOL;
        $instanceVar = _heavyComputation();
        file_put_contents($cachePath, $instanceVar, LOCK_EX);
    }

    // Lighter computations can re-run on each function invocation.
    $functionVar = _lightComputation();

    $response .= 'Per instance: ' . $instanceVar . PHP_EOL;
    $response .= 'Per function: ' . $functionVar . PHP_EOL;

    return $response;
}

Es especialmente importante almacenar en caché las conexiones de red, las referencias de bibliotecas y los objetos de cliente de API en el ámbito global. Consulta las prácticas recomendadas de redes para ver ejemplos.

Reduce los arranques en frío definiendo un número mínimo de instancias

De forma predeterminada, Cloud Run Functions escala el número de instancias en función del número de solicitudes entrantes. Puedes cambiar este comportamiento predeterminado definiendo un número mínimo de instancias que las funciones de Cloud Run deben mantener listas para atender solicitudes. Si defines un número mínimo de instancias, se reducirán los arranques en frío de tu aplicación. Si tu aplicación es sensible a la latencia, te recomendamos que definas un número mínimo de instancias y que completes la inicialización en el tiempo de carga.

Para saber cómo definir un número mínimo de instancias, consulta Usar instancias mínimas.

Notas sobre el arranque en frío y la inicialización

La inicialización global se produce en el momento de la carga. Sin él, la primera solicitud tendría que completar la inicialización y cargar los módulos, lo que provocaría una latencia mayor.

Sin embargo, la inicialización global también influye en los arranques en frío. Para minimizar este impacto, inicializa solo lo que se necesite para la primera solicitud, de modo que la latencia de la primera solicitud sea lo más baja posible.

Esto es especialmente importante si has configurado el número mínimo de instancias como se ha descrito anteriormente para una función sensible a la latencia. En ese caso, completar la inicialización en el tiempo de carga y almacenar en caché los datos útiles asegura que la primera solicitud no tenga que hacerlo y se sirva con una latencia baja.

Si inicializas variables en el ámbito global, en función del lenguaje, los tiempos de inicialización largos pueden dar lugar a dos comportamientos: - En algunas combinaciones de lenguajes y bibliotecas asíncronas, el framework de funciones puede ejecutarse de forma asíncrona y devolver resultados inmediatamente, lo que provoca que el código siga ejecutándose en segundo plano, lo que podría causar problemas como no poder acceder a la CPU. Para evitarlo, debes bloquear la inicialización del módulo, tal como se describe a continuación. De esta forma, también se asegura de que las solicitudes no se sirvan hasta que se complete la inicialización. - Por otro lado, si la inicialización es síncrona, el tiempo de inicialización prolongado provocará arranques en frío más largos, lo que podría ser un problema, especialmente con funciones de baja simultaneidad durante picos de carga.

Ejemplo de precalentamiento de una biblioteca asíncrona de Node.js

Node.js con Firestore es un ejemplo de biblioteca asíncrona de Node.js. Para aprovechar las ventajas de min_instances, el siguiente código completa la carga y la inicialización en el tiempo de carga, lo que bloquea la carga del módulo.

Se usa TLA, lo que significa que se requiere ES6. Para ello, se usa una extensión .mjs para el código de Node.js o se añade type: module al archivo package.json.

{
  "main": "main.js",
  "type": "module",
  "dependencies": {
    "@google-cloud/firestore": "^7.10.0",
    "@google-cloud/functions-framework": "^3.4.5"
  }
}

Node.js

import Firestore from '@google-cloud/firestore';
import * as functions from '@google-cloud/functions-framework';

const firestore = new Firestore({preferRest: true});

// Pre-warm firestore connection pool, and preload our global config
// document in cache. In order to ensure no other request comes in,
// block the module loading with a synchronous global request:
const config = await firestore.collection('collection').doc('config').get();

functions.http('fetch', (req, res) => {

// Do something with config and firestore client, which are now preloaded
// and will execute at lower latency.
});

Ejemplos de inicialización global

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}."

Go


// Package tips contains tips for writing Cloud Functions in Go.
package tips

import (
	"context"
	"log"
	"net/http"
	"sync"

	"cloud.google.com/go/storage"
	"github.com/GoogleCloudPlatform/functions-framework-go/functions"
)

// client is lazily initialized by LazyGlobal.
var client *storage.Client
var clientOnce sync.Once

func init() {
	functions.HTTP("LazyGlobal", LazyGlobal)
}

// LazyGlobal is an example of lazily initializing a Google Cloud Storage client.
func LazyGlobal(w http.ResponseWriter, r *http.Request) {
	// You may wish to add different checks to see if the client is needed for
	// this request.
	clientOnce.Do(func() {
		// Pre-declare an err variable to avoid shadowing client.
		var err error
		client, err = storage.NewClient(context.Background())
		if err != nil {
			http.Error(w, "Internal error", http.StatusInternalServerError)
			log.Printf("storage.NewClient: %v", err)
			return
		}
	})
	// Use client.
}

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 LazyFields implements HttpFunction {
  // Always initialized (at cold-start)
  // Warning: Class variables used in Servlet classes must be thread-safe,
  // or else might introduce race conditions in your code.
  private static final int NON_LAZY_GLOBAL = fileWideComputation();

  // Declared at cold-start, but only initialized if/when the function executes
  // Uses the "initialization-on-demand holder" idiom
  // More information: https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom
  private static class LazyGlobalHolder {
    // Making the default constructor private prohibits instantiation of this class
    private LazyGlobalHolder() {}

    // This value is initialized only if (and when) the getLazyGlobal() function below is called
    private static final Integer INSTANCE = functionSpecificComputation();

    private static Integer getInstance() {
      return LazyGlobalHolder.INSTANCE;
    }
  }

  @Override
  public void service(HttpRequest request, HttpResponse response)
      throws IOException {
    Integer lazyGlobal = LazyGlobalHolder.getInstance();

    var writer = new PrintWriter(response.getWriter());
    writer.printf("Lazy global: %s; non-lazy global: %s%n", lazyGlobal, NON_LAZY_GLOBAL);
  }

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

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

C#

using Google.Cloud.Functions.Framework;
using Microsoft.AspNetCore.Http;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace LazyFields;

public class Function : IHttpFunction
{
    // This computation runs at server cold-start.
    // Warning: Class variables used in functions code must be thread-safe.
    private static readonly int NonLazyGlobal = FileWideComputation();

    // This variable is initialized at server cold-start, but the
    // computation is only performed when the function needs the result.
    private static readonly Lazy<int> LazyGlobal = new Lazy<int>(
        FunctionSpecificComputation,
        LazyThreadSafetyMode.ExecutionAndPublication);

    public async Task HandleAsync(HttpContext context)
    {
        // In a more complex function, there might be some paths that use LazyGlobal.Value,
        // and others that don't. The computation is only performed when necessary, and
        // only once per server.
        await context.Response.WriteAsync(
            $"Lazy global: {LazyGlobal.Value}; non-lazy global: {NonLazyGlobal}",
            context.RequestAborted);
    }

    private static int FunctionSpecificComputation()
    {
        int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        return numbers.Sum();
    }

    private static int FileWideComputation()
    {
        int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        return numbers.Aggregate((current, next) => current * next);
    }
}

Ruby

FunctionsFramework.on_startup do
  # This method is called when the function is initialized, not on each
  # invocation.

  # Declare and set non_lazy_global
  set_global :non_lazy_global, file_wide_computation

  # Declare, but do not set, lazy_global
  set_global :lazy_global do
    function_specific_computation
  end
end

FunctionsFramework.http "tips_lazy" do |_request|
  # This method is called every time this function is called.

  "Lazy: #{global :lazy_global}; non_lazy: #{global :non_lazy_global}"
end

PHP

Las funciones de PHP no pueden conservar variables entre solicitudes. En el ejemplo de ámbitos anterior, se usa la carga diferida para almacenar en caché los valores de las variables globales en un archivo.

Esto es especialmente importante si defines varias funciones en un mismo archivo y cada una de ellas usa variables diferentes. Si no usas la inicialización diferida, puedes desperdiciar recursos en variables que se inicializan pero que nunca se usan.

Recursos adicionales

Consulta más información sobre cómo optimizar el rendimiento en el vídeo "Google Cloud Performance Atlas" (Atlas de rendimiento de Google Cloud) sobre el tiempo de arranque en frío de las funciones de Cloud Run.