Práticas recomendadas para funções

Este documento descreve as práticas recomendadas para conceber, implementar, testar e implementar funções do Cloud Run.

Exatidão

Esta secção descreve as práticas recomendadas gerais para conceber e implementar funções do Cloud Run.

Escreva funções idempotentes

As suas funções devem produzir o mesmo resultado, mesmo que sejam chamadas várias vezes. Isto permite-lhe repetir uma invocação se a invocação anterior falhar durante a execução do código. Para mais informações, consulte o artigo sobre como tentar novamente funções acionadas por eventos.

Certifique-se de que as funções HTTP enviam uma resposta HTTP

Se a sua função for acionada por HTTP, lembre-se de enviar uma resposta HTTP, como mostrado abaixo. Se não o fizer, a função é executada até atingir o limite de tempo. Se isto ocorrer, é-lhe cobrado o tempo limite total. Os limites de tempo também podem causar um comportamento imprevisível ou inícios a frio em invocações subsequentes, o que resulta num comportamento imprevisível ou latência 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)}!"

Ir


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

Não iniciar atividades em segundo plano

A atividade em segundo plano é tudo o que acontece depois de a sua função ter terminado. Uma invocação de função termina quando a função devolve ou sinaliza de outra forma a conclusão, como através da chamada do argumento callback em funções baseadas em eventos do Node.js. Qualquer código executado após a terminação normal não pode aceder à CPU e não faz qualquer progresso.

Além disso, quando uma invocação subsequente é executada no mesmo ambiente, a sua atividade em segundo plano é retomada, interferindo com a nova invocação. Isto pode resultar num comportamento inesperado e em erros difíceis de diagnosticar. Aceder à rede após a terminação de uma função geralmente leva à reposição das ligações (código de erro ECONNRESET).

A atividade em segundo plano pode ser frequentemente detetada nos registos de invocações individuais, encontrando tudo o que é registado após a linha que indica que a invocação terminou. Por vezes, a atividade em segundo plano pode estar mais oculta no código, especialmente quando estão presentes operações assíncronas, como callbacks ou temporizadores. Reveja o código para se certificar de que todas as operações assíncronas terminam antes de terminar a função.

Elimine sempre ficheiros temporários

O armazenamento em disco local no diretório temporário é um sistema de ficheiros na memória. Os ficheiros que escreve consomem a memória disponível para a sua função e, por vezes, persistem entre invocações. Se não eliminar explicitamente estes ficheiros, pode ocorrer um erro de falta de memória e um reinício a frio subsequente.

Pode ver a memória usada por uma função individual selecionando-a na lista de funções naGoogle Cloud consola e escolhendo o gráfico Utilização de memória.

Se precisar de acesso ao armazenamento a longo prazo, considere usar montagens de volumes do Cloud Run com o Cloud Storage ou volumes NFS.

Pode reduzir os requisitos de memória quando processa ficheiros maiores através do processamento em pipeline. Por exemplo, pode processar um ficheiro no Cloud Storage criando um fluxo de leitura, passando-o por um processo baseado em fluxo e escrevendo o fluxo de saída diretamente no Cloud Storage.

Functions Framework

Para garantir que as mesmas dependências são instaladas de forma consistente em todos os ambientes, recomendamos que inclua a biblioteca Functions Framework no seu gestor de pacotes e fixe a dependência a uma versão específica do Functions Framework.

Para o fazer, inclua a sua versão preferencial no ficheiro de bloqueio relevante (por exemplo, package-lock.json para Node.js ou requirements.txt para Python).

Se o Functions Framework não estiver explicitamente listado como uma dependência, é adicionado automaticamente durante o processo de compilação através da versão disponível mais recente.

Ferramentas

Esta secção fornece diretrizes sobre como usar ferramentas para implementar, testar e interagir com funções do Cloud Run.

Desenvolvimento local

A implementação de funções demora algum tempo, pelo que é frequentemente mais rápido testar o código da sua função localmente.

Relatório de erros

Em linguagens que usam o processamento de exceções, não lance exceções não capturadas, porque forçam inícios a frio em invocações futuras.

Não sair manualmente

A saída manual pode causar um comportamento inesperado. Em alternativa, use os seguintes idiomatismos específicos do idioma:

Node.js

Não use process.exit(). As funções HTTP devem enviar uma resposta com res.status(200).send(message), e as funções acionadas por eventos terminam assim que são devolvidas (implícita ou explicitamente).

Python

Não use sys.exit(). As funções HTTP devem devolver explicitamente uma resposta como uma string e as funções orientadas por eventos terminam assim que devolvem um valor (implícita ou explicitamente).

Ir

Não use os.Exit(). As funções HTTP devem devolver explicitamente uma resposta como uma string e as funções orientadas por eventos terminam assim que devolvem um valor (implícita ou explicitamente).

Java

Não use System.exit(). As funções HTTP devem enviar uma resposta com response.getWriter().write(message), e as funções acionadas por eventos terminam assim que são devolvidas (implícita ou explicitamente).

C#

Não use System.Environment.Exit(). As funções HTTP devem enviar uma resposta com context.Response.WriteAsync(message), e as funções acionadas por eventos terminam assim que são devolvidas (implícita ou explicitamente).

Ruby

Não use exit() nem abort(). As funções HTTP devem devolver explicitamente uma resposta como uma string e as funções orientadas por eventos terminam assim que devolvem um valor (implícita ou explicitamente).

PHP

Não use exit() nem die(). As funções HTTP devem devolver explicitamente uma resposta como uma string e as funções orientadas por eventos terminam assim que devolvem um valor (implícita ou explicitamente).

Use o Sendgrid para enviar emails

As funções do Cloud Run não permitem ligações de saída na porta 25, pelo que não pode estabelecer ligações não seguras a um servidor SMTP. A forma recomendada de enviar emails é usar um serviço de terceiros, como o SendGrid. Pode encontrar outras opções para enviar emails no tutorial Enviar emails a partir de uma instância do Google Compute Engine.

Desempenho

Esta secção descreve as práticas recomendadas para otimizar o desempenho.

Evite a baixa concorrência

Como os inícios a frio são dispendiosos, a capacidade de reutilizar instâncias iniciadas recentemente durante um pico é uma excelente otimização para processar a carga. A limitação da simultaneidade limita a forma como as instâncias existentes podem ser usadas, o que gera mais inícios a frio.

Aumentar a simultaneidade ajuda a diferir vários pedidos por instância, o que facilita o processamento de picos de carga.

Use as dependências de forma inteligente

Uma vez que as funções não têm estado, o ambiente de execução é frequentemente inicializado de raiz (durante o que é conhecido como um início a frio). Quando ocorre um arranque a frio, o contexto global da função é avaliado.

Se as suas funções importarem módulos, o tempo de carregamento desses módulos pode aumentar a latência de invocação durante um arranque a frio. Pode reduzir esta latência, bem como o tempo necessário para implementar a sua função, carregando as dependências corretamente e não carregando as dependências que a sua função não usa.

Use variáveis globais para reutilizar objetos em invocações futuras

Não existe garantia de que o estado de uma função do Cloud Run seja preservado para invocações futuras. No entanto, as funções do Cloud Run reciclam frequentemente o ambiente de execução de uma invocação anterior. Se declarar uma variável no âmbito global, o respetivo valor pode ser reutilizado em invocações subsequentes sem ter de ser recalculado.

Desta forma, pode colocar em cache objetos que podem ser dispendiosos de recriar em cada invocação de função. Mover esses objetos do corpo da função para o âmbito global pode resultar em melhorias significativas no desempenho. O exemplo seguinte cria um objeto pesado apenas uma vez por instância da função e partilha-o em todas as invocações da função que atingem a instância 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}"

Ir


// 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;
}

É particularmente importante colocar em cache as ligações de rede, as referências de bibliotecas e os objetos de cliente da API no âmbito global. Consulte as práticas recomendadas de rede para ver exemplos.

Reduza os inícios a frio definindo um número mínimo de instâncias

Por predefinição, as funções do Cloud Run escalam o número de instâncias com base no número de pedidos recebidos. Pode alterar este comportamento predefinido definindo um número mínimo de instâncias que as funções do Cloud Run têm de manter prontas para atender pedidos. A definição de um número mínimo de instâncias reduz os inícios a frio da sua aplicação. Recomendamos que defina um número mínimo de instâncias e conclua a inicialização no momento do carregamento se a sua aplicação for sensível à latência.

Para saber como definir um número mínimo de instâncias, consulte o artigo Usar instâncias mínimas.

Notas sobre o início a frio e a inicialização

A inicialização global ocorre no momento do carregamento. Sem ele, o primeiro pedido teria de concluir a inicialização e carregar módulos, o que incorreria numa latência mais elevada.

No entanto, a inicialização global também tem um impacto nos inícios a frio. Para minimizar este impacto, inicialize apenas o que é necessário para o primeiro pedido, de modo a manter a latência do primeiro pedido o mais baixa possível.

Isto é especialmente importante se tiver configurado instâncias mínimas, conforme descrito acima, para uma função sensível à latência. Nesse cenário, a conclusão da inicialização no momento do carregamento e o armazenamento em cache de dados úteis garantem que o primeiro pedido não precisa de o fazer e é apresentado com baixa latência.

Se inicializar variáveis no âmbito global, consoante o idioma, os tempos de inicialização longos podem resultar em dois comportamentos: - Para algumas combinações de idiomas e bibliotecas assíncronas, a estrutura de funções pode ser executada de forma assíncrona e devolvida imediatamente, o que faz com que o código continue a ser executado em segundo plano, o que pode causar problemas, como não conseguir aceder à CPU. Para evitar esta situação, deve bloquear a inicialização do módulo, conforme descrito abaixo. Isto também garante que os pedidos não são publicados até a inicialização estar concluída. - Por outro lado, se a inicialização for síncrona, o longo tempo de inicialização vai causar inícios a frio mais longos, o que pode ser um problema, especialmente com funções de concorrência baixa durante picos de carga.

Exemplo de pré-aquecimento de uma biblioteca assíncrona do Node.js

O Node.js com o Firestore é um exemplo de biblioteca node.js assíncrona. Para tirar partido de min_instances, o código seguinte conclui o carregamento e a inicialização no momento do carregamento, bloqueando o carregamento do módulo.

É usado o TLA, o que significa que o ES6 é necessário, usando uma extensão .mjs para o código node.js ou adicionando type: module ao ficheiro 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.
});

Exemplos de inicialização 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}."

Ir


// 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

As funções PHP não podem preservar variáveis entre pedidos. O exemplo de âmbitos acima usa o carregamento diferido para colocar em cache os valores das variáveis globais num ficheiro.

Isto é particularmente importante se definir várias funções num único ficheiro e as diferentes funções usarem variáveis diferentes. A menos que use a inicialização preguiçosa, pode desperdiçar recursos em variáveis que são inicializadas, mas nunca usadas.

Recursos adicionais

Saiba mais sobre a otimização do desempenho no vídeo "Google Cloud Performance Atlas" (Google Cloud Performance Atlas) Tempo de arranque a frio das funções do Cloud Run.