Riprovare le funzioni asincrone

Questo documento descrive come richiedere alle funzioni in background asincrone (non HTTPS) di riprovare in caso di errore.

Semantica del nuovo tentativo

Cloud Functions fornisce l'esecuzione almeno una volta di una funzione basata su eventi per ogni evento emesso da un'origine evento. Per impostazione predefinita, se l'invocazione di una funzione termina con un errore, la funzione non viene invocata di nuovo e l'evento viene ignorato. Quando attivi i tentativi su una funzione basata su eventi, Cloud Functions riprova a invocare una funzione non riuscita finché non viene completata correttamente o non scade il periodo di tempo per i tentativi.

Per le funzioni di 2ª generazione, questa finestra di ripetizione scade dopo 24 ore. Per le funzioni di 1ª gen., scade dopo 7 giorni. Cloud Functions esegue nuovamente le funzioni basate su eventi appena create utilizzando una strategia di backoff esponenziale, con un backoff crescente compreso tra 10 e 600 secondi. Questo criterio viene applicato alle nuove funzioni la prima volta che ne esegui il deployment. Non viene applicata retroattivamente alle funzioni esistenti di cui è stato eseguito il primo dispiegamento prima dell'applicazione delle modifiche descritte in questa nota di rilascio, anche se le funzioni vengono ridistribuite.

Quando i tentativi di nuovo non sono abilitati per una funzione, che è l'impostazione predefinita, la funzione segnala sempre che è stata eseguita correttamente e nei log potrebbero essere visualizzati i codici di risposta 200 OK. Ciò si verifica anche se la funzione ha riscontrato un errore. Per chiarire quando la funzione rileva un errore, assicurati di segnalare gli errori in modo appropriato.

Perché le funzioni basate su eventi non vengono completate

In rare occasioni, una funzione potrebbe uscire prematuramente a causa di un errore interno. Per impostazione predefinita, la funzione potrebbe essere oggetto di un nuovo tentativo automatico.

Più in generale, una funzione basata su eventi potrebbe non essere completata correttamente a causa di errori generati nel codice della funzione stessa. Ecco alcuni motivi per cui questo potrebbe accadere:

  • La funzione contiene un bug e il runtime genera un'eccezione.
  • La funzione non riesce a raggiungere un endpoint del servizio o il timeout si verifica durante il tentativo di farlo.
  • La funzione genera intenzionalmente un'eccezione (ad esempio quando un parametro non supera la convalida).
  • Una funzione Node.js restituisce una promessa rifiutata o passa un valore diverso da null a un callback.

In tutti questi casi, la funzione interrompe l'esecuzione per impostazione predefinita e l'evento viene ignorato. Per riprovare la funzione quando si verifica un errore, puoi modificare il criterio predefinito per i nuovi tentativi impostando la proprietà "Riprova in caso di errore". Di conseguenza, l'evento viene tentato ripetutamente finché la funzione non viene completata correttamente o non scade il timeout del nuovo tentativo.

Attivare o disattivare i tentativi di nuovo invio

Configurare i tentativi di nuovo invio dalla console

Se stai creando una nuova funzione:

  1. Nella schermata Crea funzione, in Attivatore, scegli il tipo di evento che agisca da trigger per la funzione.
  2. Seleziona la casella di controllo Riprova in caso di errore per attivare i tentativi di nuovo invio.

Se stai aggiornando una funzione esistente:

  1. Nella pagina Panoramica di Cloud Functions, fai clic sul nome della funzione che stai aggiornando per aprire la schermata Dettagli funzione, quindi scegli Modifica dalla barra dei menu per visualizzare il riquadro Attivazione.
  2. Seleziona o deseleziona la casella di controllo Riprova in caso di errore per attivare o disattivare i tentativi di nuovo invio.

Configurare i tentativi di nuovo invio dal codice della funzione

Con Cloud Functions for Firebase, puoi attivare i tentativi di nuovo nel codice per una funzione. Per farlo per una funzione in background come functions.foo.onBar(myHandler);, utilizza runWith e configura un criterio di errore:

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

L'impostazione true come mostrato configura una funzione per ritentare in caso di errore.

Best practice

Questa sezione descrive le best practice per l'utilizzo dei tentativi di nuovo invio.

Utilizzare il nuovo tentativo per gestire gli errori temporanei

Poiché la funzione viene tentata continuamente fino a quando non viene eseguita correttamente, gli errori permanenti come i bug devono essere eliminati dal codice tramite i test prima di attivare i tentativi. I tentativi sono ideali per gestire errori intermittenti o temporanei con un'alta probabilità di risoluzione al successivo tentativo, ad esempio un endpoint di servizio inaffidabile o un timeout.

Imposta una condizione di fine per evitare loop infiniti di nuovi tentativi

È buona prassi proteggere la funzione da un ciclo continuo quando si utilizzano i tentativi. Puoi farlo includendo una condizione di fine ben definita, prima dell'inizio dell'elaborazione della funzione. Tieni presente che questa tecnica funziona solo se la funzione si avvia correttamente ed è in grado di valutare la condizione di fine.

Un approccio semplice ma efficace è eliminare gli eventi con timestamp precedenti a un determinato momento. In questo modo si evitano esecuzioni eccessive quando gli errori sono persistenti o durano più a lungo del previsto.

Ad esempio, questo snippet di codice ignora tutti gli eventi precedenti a 10 secondi:

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

Utilizzare catch con le promesse

Se per la funzione sono abilitati nuovi tentativi, qualsiasi errore non gestito attiverà un nuovo tentativo. Assicurati che il codice catturi eventuali errori che non devono comportare un nuovo tentativo.

Ecco un esempio di ciò che dovresti fare:

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

Rendere idempotenti le funzioni basate su eventi ripetibili

Le funzioni basate su eventi che possono essere riprovate devono essere idempotenti. Ecco alcune linee guida generali per rendere idempotente una funzione di questo tipo:

  • Molte API esterne (come Stripe) ti consentono di fornire una chiave di idempotenza come parametro. Se utilizzi un'API di questo tipo, devi utilizzare l'ID evento come chiave di iride.
  • L'idempotenza funziona bene con la consegna "almeno una volta", perché consente di eseguire nuovamente il tentativo in sicurezza. Pertanto, una best practice generale per scrivere codice affidabile è combinare l'idempotenza con nuovi tentativi.
  • Assicurati che il tuo codice sia internamente idempotente. Ad esempio:
    • Assicurati che le mutazioni possano verificarsi più di una volta senza modificare il risultato.
    • Esegui query sullo stato del database in una transazione prima di modificarlo.
    • Assicurati che tutti gli effetti collaterali siano idempotenti.
  • Imposta un controllo transazionale esterno alla funzione, indipendente dal codice. Ad esempio, uno stato permanente che registra che un determinato ID evento è già stato elaborato.
  • Gestisci le chiamate di funzioni duplicate out-of-band. Ad esempio, puoi avere un processo di pulizia distinto che elimina i dati dopo le chiamate di funzioni duplicate.

Configura il criterio per i tentativi ripetuti

A seconda delle esigenze della funzione, ti consigliamo di configurare direttamente il criterio per il nuovo tentativo. Questo ti consente di configurare qualsiasi combinazione di quanto segue:

  • Riduci il periodo di attesa per il nuovo tentativo da 7 giorni a un minimo di 10 minuti.
  • Modifica il tempo di attesa minimo e massimo per la strategia di ripetizione con tempo di attesa esponenziale.
  • Modifica la strategia di ripetizione in modo da riprovare immediatamente.
  • Configura un argomento messaggi non recapitabili.
  • Imposta un numero massimo e minimo di tentativi di consegna.

Per configurare il criterio di ripetizione:

  1. Scrivi una funzione HTTP.
  2. Utilizza l'API Pub/Sub per creare un abbonamento Pub/Sub, specificando l'URL della funzione come target.

Consulta la documentazione di Pub/Sub sulla gestione degli errori per ulteriori informazioni sulla configurazione diretta di Pub/Sub.