Dicas e truques
Neste documento, descrevemos as práticas recomendadas para projetar, implementar, testar e implantar o Cloud Run functions.
Correção
Nesta seção, você verá as práticas recomendadas gerais para projetar e implementar o Cloud Run functions.
Escreva funções idempotentes
As funções devem produzir o mesmo resultado, mesmo que sejam chamadas várias vezes. Isso permite que você tente executar uma invocação novamente caso a anterior falhe no seu código. Para mais informações, consulte Como repetir funções orientadas a eventos.
Confirme se as funções HTTP enviam uma resposta HTTP
Se a função for acionada por HTTP, lembre-se de enviar uma resposta HTTP, como mostrado abaixo. Não fazer isso pode resultar na execução da função até o tempo limite. Se isso ocorrer, você será cobrado por todo o tempo limite. Os tempos limite também podem causar um comportamento imprevisível ou inicializações a frio em invocações subsequentes, resultando em um comportamento imprevisível ou latência adicional.
Node.js
Python
Go
Java
C#
Ruby
PHP
Não inicie atividades em segundo plano
Atividade em segundo plano é tudo que ocorre depois que a função é encerrada.
A invocação de uma função termina quando a função retorna ou sinaliza
a conclusão, por exemplo, chamando o argumento callback
nas funções orientadas por eventos
do Node.js. Qualquer código executado após a finalização normal não pode acessar a CPU e não progredirá.
Além disso, quando uma invocação subsequente é executada no mesmo ambiente, a atividade em segundo plano é retomada, interferindo na nova invocação. Isso pode levar a um comportamento inesperado e erros difíceis de diagnosticar. O acesso à rede depois que uma função é concluída normalmente causa a redefinição das conexões (código do erro ECONNRESET
).
Ela normalmente pode ser detectada em registros de invocações individuais, encontrando tudo o que será registrado depois da linha que informa sobre o término da invocação. Às vezes, a atividade em segundo plano pode ser aprofundada no código, especialmente quando operações assíncronas, como callbacks ou timers, estão presentes. Revise o código para verificar se todas as operações assíncronas foram concluídas antes de você finalizar a função.
Sempre exclua arquivos temporários
O armazenamento de 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, permanecem entre as invocações. A não exclusão deles pode resultar em um erro de memória insuficiente e uma subsequente inicialização a frio.
Veja a memória usada por uma função individual selecionando-a na lista de funções do console do Google Cloud e escolhendo o gráfico Uso da memória.
Se você precisar de acesso ao armazenamento de longo prazo, use as montagens de volume do Cloud Run com o Cloud Storage ou volumes do NFS.
É possível reduzir os requisitos de memória ao processar arquivos maiores usando pipelines. Por exemplo, processe um arquivo no Cloud Storage criando um stream de leitura, transmitindo-o por um processo baseado em stream e gravando o stream de saída diretamente no Cloud Storage.
Functions Framework
Para garantir que as mesmas dependências sejam instaladas de maneira consistente em diferentes ambientes, recomendamos que você inclua a biblioteca do Functions Framework no gerenciador de pacotes e fixe a dependência a uma versão específica do Functions Framework.
Para isso, inclua a versão de sua preferência no arquivo de bloqueio relevante (por exemplo,
package-lock.json
para Node.js ou requirements.txt
para Python).
Se o Functions Framework não estiver listado explicitamente como uma dependência, ele será adicionado automaticamente durante o processo de build usando a versão mais recente disponível.
Ferramentas
Nesta seção, você verá diretrizes sobre como usar ferramentas para implementar, testar e interagir com o Google Run functions.
Desenvolvimento local
A implantação de funções demora um pouco, então geralmente é mais rápido testar o código da sua função localmente.
Error Reporting
Em linguagens que usam processamento de exceção, não crie exceções não capturadas, pois elas forçam inicialização a frio em futuras invocações. Consulte o guia do Error Reporting para ver informações sobre como relatar erros corretamente.
Não sair manualmente
Sair manualmente pode causar um comportamento inesperado. Use os seguintes idiomas específicos de linguagem:
Node.js
Não use process.exit()
. As funções HTTP precisam enviar uma resposta com
res.status(200).send(message)
, e as funções
orientadas a eventos sairão quando elas retornarem, implicitamente ou explicitamente.
Python
Não use sys.exit()
. As funções HTTP retornarão
uma resposta como uma string de maneira explícita, e as funções orientadas a eventos sairão assim que
retornarem um valor, implicitamente ou explicitamente.
Go
Não use os.Exit()
. As funções HTTP retornarão
uma resposta como uma string de maneira explícita, e as funções orientadas a eventos sairão assim que
retornarem um valor, implicitamente ou explicitamente.
Java
Não use System.exit()
. As funções HTTP precisam enviar uma resposta com
response.getWriter().write(message)
, e as funções
orientadas a eventos sairão quando elas retornarem, implicitamente ou explicitamente.
C#
Não use System.Environment.Exit()
. As funções HTTP precisam enviar uma resposta com
context.Response.WriteAsync(message)
, e as funções
orientadas a eventos sairão quando elas retornarem, implicitamente ou explicitamente.
Ruby
Não use exit()
ou abort()
. As funções HTTP retornarão
uma resposta como uma string de maneira explícita, e as funções orientadas a eventos sairão assim que
retornarem um valor, implicitamente ou explicitamente.
PHP
Não use exit()
ou die()
. As funções HTTP retornarão
uma resposta como uma string de maneira explícita, e as funções orientadas a eventos sairão assim que
retornarem um valor, implicitamente ou explicitamente.
Use SendGrid para enviar e-mails
O Cloud Run functions não permite conexões de saída na porta 25, então não é possível estabelecer conexões não seguras com um servidor SMTP. A maneira recomendada de enviar e-mails é usar um serviço de terceiros, como o SendGrid. Confira outras opções para enviar e-mails no tutorial Como enviar e-mails a partir de uma instância do Google Compute Engine.
Desempenho
Nesta seção, você verá as práticas recomendadas para otimizar o desempenho.
Evite baixa simultaneidade
Como as inicializações a frio são caras, a capacidade de reutilizar instâncias iniciadas recentemente durante um pico é uma ótima otimização para lidar com a carga. Limitar a contemporaneidade limita como as instâncias atuais podem ser aproveitadas, o que gera mais inicializações a frio.
O aumento da simultaneidade ajuda a adiar várias solicitações por instância, facilitando o processamento de picos de carga. Observação: as funções de 1ª geração têm simultaneidade limitada a 1. Recomendamos que você migre para as funções do Cloud Run.Use dependências com sabedoria
Como as funções não têm estado, o ambiente de execução normalmente é inicializado do zero, o que é chamado de inicialização a frio. Quando ocorre uma inicialização a frio, o contexto global da função é avaliado.
Se suas funções importam módulos, o tempo de carregamento deles pode ser adicionado à latência de invocação durante uma inicialização a frio. É possível reduzir essa latência, bem como o tempo necessário para implantar sua função, carregando as dependências necessárias e não carregando as dependências que sua função não utiliza.
Use variáveis globais para reutilizar objetos em futuras invocações
Não há garantia de que o estado de um Cloud Run function seja preservado para futuras invocações. No entanto, essas funções reciclam frequentemente o ambiente de execução de uma invocação anterior. Se você declarar uma variável no escopo global, o valor dela pode ser reutilizado em invocações subsequentes sem necessidade de recálculo.
Dessa forma, é possível armazenar em cache os objetos cuja recriação em cada invocação de função pode ser cara. Mover esses objetos do corpo da função para o escopo global pode resultar em melhorias significativas de desempenho. No exemplo a seguir, um objeto pesado é criado apenas uma vez por instância de função e é compartilhado em todas as invocações da função que alcançam a instância determinada:
Node.js
Python
Go
Java
C#
Ruby
PHP
É muito importante fazer o armazenamento em cache de conexões de rede, referências de biblioteca e objetos de cliente de API em escopo global. Consulte Otimizar redes para exemplos.
Reduza a inicialização a frio definindo um número mínimo de instâncias
Por padrão, o Cloud Run functions dimensiona o número de instâncias com base no número de solicitações de entrada. É possível alterar esse comportamento padrão definindo um número mínimo de instâncias que o Cloud Run functions precisa manter prontas para exibir solicitações. Definir um número mínimo de instâncias reduz as inicializações a frio do aplicativo. Recomendamos definir um número mínimo de instâncias e concluir a inicialização no momento do carregamento se o aplicativo for sensível à latência.
Para saber como definir um número mínimo de instâncias, consulte Como usar o mínimo instâncias.
Observações sobre inicialização a frio e inicialização
A inicialização global acontece no momento do carregamento. Sem ele, a primeira solicitação precisaria concluir a inicialização e carregar módulos, o que resultaria em uma latência maior.
No entanto, a inicialização global também tem um impacto nas inicializações a frio. Para minimizar esse impacto, inicialize apenas o que for necessário para a primeira solicitação, para manter a latência da primeira solicitação o mais baixa possível.
Isso é especialmente importante se você configurou 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 garante que a primeira solicitação não precise fazer isso e seja enviada com baixa latência.
Se você inicializar variáveis no escopo global, dependendo da linguagem, tempos de inicialização longos podem resultar em dois comportamentos: - Para algumas combinações de linguagens e bibliotecas assíncronas, o framework de função pode ser executado de forma assíncrona e retornar imediatamente, fazendo com que o código continue sendo executado em segundo plano, o que pode causar problemas, como não conseguir acessar a CPU. Para evitar isso, bloqueie a inicialização do módulo, conforme descrito abaixo. Isso também garante que as solicitações não sejam atendidas até que a inicialização seja concluída. - Por outro lado, se a inicialização for síncrona, o tempo de inicialização longo vai causar inicializações a frio mais longas, o que pode ser um problema, especialmente com funções de baixa simultaneidade durante picos de carga.
Exemplo de pré-aquecimento de uma biblioteca node.js assíncrona
O Node.js com o Firestore é um exemplo de biblioteca assíncrona do Node.js. Para aproveitar o min_instances, o código abaixo conclui o carregamento e a inicialização no momento do carregamento, bloqueando o carregamento do módulo.
O TLA é usado, o que significa que o ES6 é necessário, usando uma extensão .mjs
para
o código node.js ou adicionando type: module
ao arquivo 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
Python
Go
Java
C#
Ruby
PHP
As funções PHP não preservam variáveis entre solicitações. A amostra de escopos acima usa o carregamento lento para armazenar em cache os valores de variáveis globais em um arquivo.
Isso é importante principalmente se você definir várias funções em um único arquivo e diferentes funções usarem variáveis distintas. A menos que você use a inicialização lenta, poderá desperdiçar recursos em variáveis que são inicializadas, mas nunca usadas.
Outros recursos
Saiba mais sobre como otimizar o desempenho no vídeo do "Atlas de desempenho do Google Cloud", Tempo de inicialização a frio do Cloud Run functions.