Repetir funções assíncronas

Neste documento, você verá como solicitar que funções assíncronas (não HTTPS) de segundo plano realizem uma nova tentativa em caso de falha.

Por que as funções baseadas em eventos não são concluídas?

Em raras ocasiões, uma função pode ser encerrada prematuramente devido a um erro interno e, por padrão, ela pode ser repetida automaticamente ou não.

Normalmente, uma função orientada a eventos pode não ser concluída com sucesso devido a erros gerados no próprio código da função. Os motivos para isso incluem o seguinte:

  • A função contém um bug e o ambiente de execução gera uma exceção.
  • A função não consegue acessar um endpoint de serviço ou expira durante a tentativa.
  • A função gera uma exceção intencionalmente (por exemplo, quando há falha na validação de um parâmetro).
  • Uma função Node.js retorna uma promessa rejeitada ou transmite um valor diferente de null para uma chamada de retorno.

Em qualquer um dos casos acima, a função vai parar de ser executada e retornar um erro. Os acionadores de eventos que produzem as mensagens têm políticas de repetição que podem ser personalizadas para atender às necessidades da função.

Semântica da nova tentativa

O Cloud Functions garante que uma função orientada a eventos seja executada pelo menos uma vez para cada evento emitido por uma fonte de eventos. A maneira de configurar as novas tentativas depende de como você criou a função:

  • As funções criadas no console do Google Cloud ou com a API Admin Cloud Run exigem que você crie e gerencie os gatilhos de evento separadamente. Os acionadores têm comportamentos de nova tentativa padrão que podem ser personalizados de acordo com as necessidades da função.
  • As funções criadas com a API Cloud Functions v2 vão criar implicitamente os gatilhos de evento necessários, por exemplo, tópicos Pub/Sub ou gatilhos Eventarc. Por padrão, as novas tentativas são desativadas para esses acionadores e podem ser reativadas usando a API Cloud Functions v2.

Funções orientadas a eventos criadas com Cloud Run

As funções criadas no console do Google Cloud ou com a API Admin Cloud Run exigem que você crie e gerencie os gatilhos de evento separadamente. Recomendamos que você analise o comportamento padrão de cada tipo de acionador:

Funções acionadas por eventos criadas com a API Cloud Functions v2

Funções criadas usando a API Cloud Functions v2. Por exemplo, o uso do gcloud CLI do Cloud Functions, da API REST ou do Terraform cria e gerencia gatilhos de eventos em seu nome. Por padrão, se uma invocação de função terminar com um erro, a função não será chamada novamente e o evento será descartado. Quando você permite novas tentativas em uma função orientada a eventos, o Cloud Functions tenta invocar a função com falha novamente até que ela seja concluída ou a janela de novas tentativas expire.

Quando novas tentativas não estão ativadas para uma função, que é o padrão, a função sempre informa que foi executada com sucesso, e os códigos de resposta 200 OK aparecem nos registros. Isso vai acontecer mesmo se a função encontrar um erro. Para tornar claro quando sua função encontra um erro, registre os erros adequadamente.

Ativar ou desativar novas tentativas

Configurar novas tentativas no código da função

Com o Cloud Functions for Firebase, é possível ativar novas tentativas no código de uma função. Para fazer isso em uma função de segundo plano, como functions.foo.onBar(myHandler);, use runWith e configure uma política de falha:

functions.runWith({failurePolicy: true}).foo.onBar(myHandler);

Definir true como mostrado configura uma função para tentar novamente em caso de falha.

Janela de nova tentativa

Para as funções de 2ª geração, a janela de novas tentativas expira após 24 horas. Para funções de primeira geração, ela expira depois de sete dias. O Cloud Functions repete as funções orientadas a eventos recém-criadas usando uma estratégia de espera exponencial, com uma espera crescente entre 10 e 600 segundos. Essa política é aplicada a novas funções na primeira vez que você as implanta. Ela não é aplicada retroativamente às funções que foram implantadas antes que as alterações descritas nesta nota de versão entrassem em vigor, mesmo que você reimplante as funções.

Práticas recomendadas

Nesta seção, você verá as práticas recomendadas para o uso de novas tentativas.

Usar novas tentativas para lidar com erros transitórios

Como novas tentativas de executar a função são realizadas continuamente até a conclusão bem-sucedida, erros permanentes, como bugs, devem ser eliminados do código por meio de teste detalhado antes de ativá-las. As tentativas funcionam melhor para lidar com falhas intermitentes ou transitórias que têm alta probabilidade de serem solucionadas ao tentar novamente, como um endpoint de serviço ou tempo limite lentos.

Definir uma condição final para evitar loops infinitos de tentativas

A prática recomendada ao usar tentativas é proteger a função contra loop contínuo. Para isso, inclua uma condição final bem definida antes de a função iniciar o processamento. Essa técnica só funciona se sua função for iniciada com sucesso e puder avaliar a condição final.

Uma abordagem simples e eficaz é descartar eventos com carimbos de data/hora anteriores a um determinado momento. Isso ajuda a evitar o excesso de execuções quando as falhas são persistentes ou mais duradouras do que o esperado.

Por exemplo, este snippet de código descarta todos os eventos ocorridos há mais de 10 segundos:

const eventAgeMs = Date.now() - Date.parse(event.timestamp);
const eventMaxAgeMs = 10000;
if (eventAgeMs > eventMaxAgeMs) {
  console.log(`Dropping event ${event} with age[ms]: ${eventAgeMs}`);
  callback();
  return;
}

Usar catch com promessas

Se a função tiver novas tentativas ativadas, qualquer erro não processado acionará uma nova tentativa. Verifique se o código captura todos os erros que não precisam resultar em uma nova tentativa.

Veja um exemplo do que fazer:

return doFooAsync().catch((err) => {
    if (isFatal(err)) {
        console.error(`Fatal error ${err}`);
    }
    return Promise.reject(err);
});

Tornar idempotentes as funções baseadas em eventos repetíveis

As funções orientadas a eventos que podem ser repetidas precisam ser idempotentes. Veja abaixo algumas diretrizes gerais para tornar essa função idempotente:

  • Muitas APIs externas, como a Stripe, permitem que você forneça uma chave de idempotência como um parâmetro. Se estiver usando uma API como essa, utilize o código do evento como chave de idempotência.
  • A idempotência funciona bem com a entrega do tipo "pelo menos uma vez" porque torna mais seguro tentar novamente. Dessa forma, para escrever um código confiável, a prática recomendada é combinar idempotência com tentativas.
  • Verifique se o código é idempotente internamente. Por exemplo:
    • Garanta que mutações possam ocorrer mais de uma vez sem alterar o resultado.
    • Consulte o estado do banco de dados em uma transação antes de alterar o estado.
    • Certifique-se de que todos os efeitos colaterais sejam idempotentes.
  • Execute uma verificação transacional fora da função, independente do código. Por exemplo, mantenha a persistência de estado em algum local e registre que um determinado código de evento já foi processado.
  • Lide com chamadas de função duplicadas fora de banda. Por exemplo, tenha um processo de limpeza que seja executado após chamadas de função duplicadas.

Configurar a política de repetição

Dependendo das necessidades da função, é possível configurar a política de repetição diretamente. Isso permite que você configure qualquer combinação dos itens a seguir:

  • Reduza a janela de nova tentativa de 7 para 10 minutos.
  • Mude os tempos mínimo e máximo de espera da estratégia de nova tentativa com espera exponencial.
  • Altere a estratégia para tentar de novo imediatamente.
  • Configure um tópico de mensagens inativas.
  • Defina um número máximo e mínimo de tentativas de entrega.

Para configurar a política de tentativas:

  1. Escreva uma função HTTP.
  2. Use a API do Pub/Sub para criar uma assinatura do Pub/Sub, especificando o URL da função como destino.

Consulte a documentação do Pub/Sub sobre como lidar com falhas para saber como configurar diretamente o Pub/Sub.