Dicas e dicas truques

Este documento descreve as práticas recomendadas para projetar, implementar, testar e implantar Cloud Functions.

Correção

Esta seção descreve as práticas recomendadas gerais para projetar e implementar o Cloud Functions.

Escreva funções idempotentes

Suas funções devem produzir o mesmo resultado mesmo se forem chamadas várias vezes. Isso permite que você tente novamente uma invocação se a invocação anterior falhar no meio do seu código. Para obter mais informações, consulte Tentando novamente funções orientadas a eventos .

Não inicie atividades em segundo plano

Atividade em segundo plano é qualquer coisa que aconteça após o término da sua função. Uma invocação de função termina quando a função retorna ou sinaliza a conclusão, como chamando o argumento callback em funções orientadas a eventos do Node.js. Qualquer código executado após o encerramento normal não poderá acessar a CPU e não fará nenhum progresso.

Além disso, quando uma invocação subsequente é executada no mesmo ambiente, sua atividade em segundo plano é retomada, interferindo na nova invocação. Isso pode levar a comportamentos inesperados e erros difíceis de diagnosticar. Acessar a rede após o término de uma função geralmente leva à redefinição das conexões (código de erro ECONNRESET ).

A atividade em segundo plano muitas vezes pode ser detectada em logs de invocações individuais, encontrando qualquer coisa registrada após a linha informando que a invocação foi concluída. Às vezes, a atividade em segundo plano pode estar mais profundamente oculta no código, especialmente quando operações assíncronas, como retornos de chamada ou temporizadores, estão presentes. Revise seu código para garantir que todas as operações assíncronas sejam concluídas antes de encerrar a função.

Sempre exclua arquivos temporários

O armazenamento em disco local no diretório temporário é um sistema de arquivos na memória. Os arquivos que você grava consomem memória disponível para sua função e, às vezes, persistem entre as invocações. Deixar de excluir explicitamente esses arquivos pode levar a um erro de falta de memória e a uma inicialização a frio subsequente.

Você pode ver a memória usada por uma função individual selecionando-a na lista de funções no Console GCP e escolhendo o gráfico de uso de memória .

Não tente gravar fora do diretório temporário e certifique-se de usar métodos independentes de plataforma/sistema operacional para construir caminhos de arquivo.

Você pode reduzir os requisitos de memória ao processar arquivos maiores usando pipeline. Por exemplo, você pode processar um arquivo no Cloud Storage criando um stream de leitura, passando-o por um processo baseado em stream e gravando o stream de saída diretamente no Cloud Storage.

Estrutura de Funções

Ao implantar uma função, o Functions Framework é adicionado automaticamente como uma dependência, usando sua versão atual. Para garantir que as mesmas dependências sejam instaladas de forma consistente em ambientes diferentes, recomendamos que você fixe sua função em uma versão específica do Functions Framework.

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

Ferramentas

Esta seção fornece diretrizes sobre como usar ferramentas para implementar, testar e interagir com o Cloud Functions.

Desenvolvimento local

A implantação da função leva um pouco de tempo, por isso geralmente é mais rápido testar o código da sua função localmente.

Os desenvolvedores do Firebase podem usar o Firebase CLI Cloud Functions Emulator .

Use Sendgrid para enviar e-mails

O Cloud Functions não permite conexões de saída na porta 25, portanto, não é possível fazer conexões não seguras com um servidor SMTP. A forma recomendada de enviar e-mails é usar SendGrid . Você pode encontrar outras opções para enviar e-mail no tutorial Enviando e-mail de uma instância do Google Compute Engine.

Desempenho

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

Use dependências com sabedoria

Como as funções não têm estado, o ambiente de execução geralmente é inicializado do zero (durante o que é conhecido como inicialização a frio ). Quando ocorre uma inicialização a frio, o contexto global da função é avaliado.

Se suas funções importar módulos, o tempo de carregamento desses módulos poderá aumentar a latência de invocação durante uma inicialização a frio. Você pode reduzir essa latência, bem como o tempo necessário para implantar sua função, carregando as dependências corretamente e não carregando dependências que sua função não usa.

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

Não há garantia de que o estado de uma Cloud Function será preservado para invocações futuras. No entanto, o Cloud Functions geralmente recicla o ambiente de execução de uma invocação anterior. Se você declarar uma variável no escopo global, seu valor poderá ser reutilizado em invocações subsequentes sem precisar ser recalculado.

Dessa forma, você pode armazenar em cache objetos cuja recriação pode ser cara em cada invocação de função. Mover esses objetos do corpo da função para o escopo global pode resultar em melhorias significativas de desempenho. O exemplo a seguir cria um objeto pesado apenas uma vez por instância de função e o compartilha em todas as invocações de função que atingem a instância especificada:

Node.js

console.log('Global scope');
const perInstance = heavyComputation();
const functions = require('firebase-functions');

exports.function = functions.https.onRequest((req, res) => {
  console.log('Function invocation');
  const perFunction = lightweightComputation();

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

Pitão

import time

from firebase_functions import https_fn

# 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()

@https_fn.on_request()
def scope_demo(request):

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

Esta função HTTP pega um objeto de solicitação ( flask.Request ) e retorna o texto de resposta ou qualquer conjunto de valores que possa ser transformado em um objeto Response usando make_response .

É particularmente importante armazenar em cache conexões de rede, referências de biblioteca e objetos de cliente de API em escopo global. Consulte Otimizando a rede para obter exemplos.

Faça inicialização lenta de variáveis ​​globais

Se você inicializar variáveis ​​no escopo global, o código de inicialização sempre será executado por meio de uma invocação de inicialização a frio, aumentando a latência da sua função. Em certos casos, isso causa tempos limite intermitentes para os serviços que estão sendo chamados se eles não forem tratados adequadamente em um bloco try / catch . Se alguns objetos não forem usados ​​em todos os caminhos de código, considere inicializá-los lentamente sob demanda:

Node.js

const functions = require('firebase-functions');
let myCostlyVariable;

exports.function = functions.https.onRequest((req, res) => {
  doUsualWork();
  if(unlikelyCondition()){
      myCostlyVariable = myCostlyVariable || buildCostlyVariable();
  }
  res.status(200).send('OK');
});

Pitão

from firebase_functions import https_fn

# 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

@https_fn.on_request()
def lazy_globals(request):

  global lazy_global, non_lazy_global

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

  return https_fn.Response(f"Lazy: {lazy_global}, non-lazy: {non_lazy_global}.")
  

Esta função HTTP usa globais inicializados lentamente. Ele pega um objeto de solicitação ( flask.Request ) e retorna o texto de resposta ou qualquer conjunto de valores que possa ser transformado em um objeto Response usando make_response .

Isto é particularmente importante se você definir diversas funções em um único arquivo e funções diferentes usarem variáveis ​​diferentes. A menos que você use a inicialização lenta, você poderá desperdiçar recursos em variáveis ​​que são inicializadas, mas nunca usadas.

Reduza as inicializações a frio definindo um número mínimo de instâncias

Por padrão, o Cloud Functions dimensiona o número de instâncias com base no número de solicitações recebidas. Você pode alterar esse comportamento padrão definindo um número mínimo de instâncias que o Cloud Functions deve manter prontas para atender às solicitações. Definir um número mínimo de instâncias reduz inicializações a frio do seu aplicativo. Recomendamos definir um número mínimo de instâncias se seu aplicativo for sensível à latência.

Consulte Controlar o comportamento de escalabilidade para obter mais informações sobre essas opções de tempo de execução.

Recursos adicionais

Saiba mais sobre como otimizar o desempenho no vídeo "Google Cloud Performance Atlas" Cloud Functions Cold Boot Time .