Join us for Firebase Summit on November 10, 2021. Tune in to learn how Firebase can help you accelerate app development, release with confidence, and scale with ease. Register

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.

Semântica da nova tentativa

O Cloud Functions garante que uma função baseada em eventos seja executada pelo menos uma vez para cada evento emitido por uma fonte de eventos. No entanto, por padrão, se uma chamada 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 de uma função baseada em eventos, o Cloud Functions tentará chamar a função com falha novamente até que ela seja concluída com êxito ou até que a janela de novas tentativas, que é de sete dias por padrão, expire.

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 baseada em eventos pode não ser concluída com sucesso devido a erros gerados no próprio código da função. Veja a seguir alguns motivos desses erros:

  • A função contém um bug e o ambiente de execução gera uma exceção.
  • A função não consegue alcançar um ponto de extremidade do 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).
  • Quando as funções escritas em Node.js retornam uma promessa rejeitada ou passam um null sem valor para um retorno de chamada.

Em qualquer um dos casos acima, a função para a execução por padrão e o evento é descartado. Se quiser executar a função novamente quando ocorre um erro, é possível alterar a política padrão de nova tentativa padrão. Para isso, ative a propriedade "Tentar novamente em caso de falha". Isso faz com que o evento seja repetido durante vários dias até que a função seja concluída com êxito.

Ativar e desativar novas tentativas

Com o Console do GCP

Ative ou desative novas tentativas no console do GCP da seguinte maneira:

  1. Acesse a página Visão geral do Cloud Functions no console do Cloud Platform.

  2. Clique em Criar função. Como alternativa, clique em uma função existente para ir para a página de detalhes dela e clique em Editar.

  3. Preencha os campos obrigatórios da função.

  4. No campo Gatilho, selecione um tipo de gatilho baseado em evento, como Cloud Pub/Sub ou Cloud Storage.

  5. Expanda as configurações avançadas clicando em Mais.

  6. Marque ou desmarque a caixa de seleção Tentar novamente em caso de falha.

No código de função

Com o Cloud Functions para 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.

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.