Riprovare le funzioni asincrone

Questo documento descrive come richiedere il nuovo tentativo di esecuzione delle funzioni asincrone (non HTTPS) in background in caso di errore.

Perché le funzioni basate su eventi non vengono completate

In rare occasioni, una funzione potrebbe terminare prematuramente a causa di un errore interno e, per impostazione predefinita, il tentativo di esecuzione della funzione potrebbe essere ripetuto o meno automaticamente.

In genere, una funzione basata su eventi potrebbe non essere completata correttamente a causa di errori generati nel codice della funzione stesso. I motivi per cui ciò potrebbe accadere includono:

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

In uno qualsiasi dei casi precedenti, la funzione interromperà l'esecuzione e restituirà un errore. I trigger di eventi che producono i messaggi hanno criteri di ripetizione che puoi personalizzare per soddisfare le esigenze della tua funzione.

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 eventi. Il modo in cui configuri i tentativi dipende da come hai creato la funzione:

  • Le funzioni create nella console Google Cloud o con l'Cloud RunAPI Admin richiedono la creazione e la gestione separata dei trigger di eventi. I trigger hanno comportamenti di ripetizione predefiniti che puoi personalizzare in base alle esigenze della tua funzione.
  • Le funzioni create con l'API Cloud Functions v2 creeranno implicitamente i trigger di eventi necessari, ad esempio argomenti Pub/Sub o trigger Eventarc. Per impostazione predefinita, i tentativi vengono disattivati per questi trigger e possono essere riattivati utilizzando l'API Cloud Functions v2.

Funzioni basate su eventi create con Cloud Run

Le funzioni create nella console Google Cloud o con l'API Admin richiedono la creazione e la gestione separate dei trigger di eventi.Cloud Run Ti consigliamo vivamente di esaminare il comportamento predefinito di ogni tipo di trigger:

Funzioni basate sugli eventi create con l'API Cloud Functions v2

Le funzioni create utilizzando l'API Cloud Functions v2, ad esempio utilizzando gcloud CLI Cloud Functions, l'API REST o Terraform, creeranno e gestiranno i trigger di eventi per tuo conto. Per impostazione predefinita, se una chiamata di funzione termina con un errore, la funzione non viene richiamata di nuovo e l'evento viene eliminato. Quando abiliti i tentativi sulla funzione basata su eventi, Cloud Functions riprova a richiamare una funzione non riuscita finché non viene completata correttamente o finché non scade la finestra di tentativi.

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

Attivare o disattivare i tentativi

Configura i tentativi dal codice della funzione

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

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

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

Finestra di tentativi

Per le funzioni di 2ª gen., questa finestra di nuovi tentativi scade dopo 24 ore. Per le funzioni di 1ª gen., scade dopo 7 giorni. Cloud Functions esegue nuovamente i tentativi per le funzioni basate su eventi appena create utilizzando una strategia di backoff esponenziale, con un backoff crescente compreso tra 10 e 600 secondi. Queste norme vengono applicate alle nuove funzioni la prima volta che le esegui il deployment. Non viene applicata retroattivamente alle funzioni esistenti che sono state implementate per la prima volta prima dell'entrata in vigore delle modifiche descritte in queste note di rilascio, anche se le implementi di nuovo.

Best practice

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

Utilizzare i tentativi per gestire gli errori temporanei

Poiché la funzione viene ritentata continuamente fino all'esecuzione riuscita, gli errori permanenti come i bug devono essere eliminati dal codice tramite test prima di attivare i nuovi tentativi. I tentativi vengono utilizzati al meglio per gestire errori intermittenti o temporanei che hanno un'alta probabilità di risoluzione al nuovo tentativo, ad esempio un endpoint di servizio instabile o un timeout.

Imposta una condizione di fine per evitare loop di tentativi ripetuti all'infinito

La best practice consiste nel proteggere la funzione da loop continui quando utilizzi i tentativi. Puoi farlo includendo una condizione di fine ben definita prima che la funzione inizi l'elaborazione. Tieni presente che questa tecnica funziona solo se la funzione viene avviata correttamente ed è in grado di valutare la condizione finale.

Un approccio semplice ma efficace è quello di eliminare gli eventi con timestamp precedenti a un determinato periodo di tempo. In questo modo si evitano esecuzioni eccessive quando gli errori sono persistenti o di durata superiore al previsto.

Ad esempio, questo snippet di codice elimina tutti gli eventi più vecchi di 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 i tentativi sono abilitati per la funzione, qualsiasi errore non gestito attiverà un nuovo tentativo. Assicurati che il codice acquisisca eventuali errori che non devono comportare un nuovo tentativo.

Ecco un esempio di cosa 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:

  • 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 idempotenza.
  • L'idempotenza funziona bene con la consegna "at-least-once", perché consente di riprovare in sicurezza. Pertanto, una best practice generale per scrivere codice affidabile è combinare l'idempotenza con i tentativi.
  • Assicurati che il codice sia idempotente internamente. 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.
  • Imponi un controllo transazionale al di fuori della funzione, indipendentemente dal codice. Ad esempio, mantieni lo stato in un punto in cui viene registrato che un determinato ID evento è già stato elaborato.
  • Gestisci le chiamate di funzione duplicate fuori banda. Ad esempio, esegui una pulizia separata dopo le chiamate di funzioni duplicate.

Configura il criterio per i tentativi ripetuti

A seconda delle esigenze della tua funzione, potresti voler configurare direttamente la policy di ripetizione dei tentativi. In questo modo potrai configurare qualsiasi combinazione dei seguenti elementi:

  • Ridurre la finestra di nuovi tentativi da 7 giorni a un minimo di 10 minuti.
  • Modifica il tempo di backoff minimo e massimo per la strategia di ripetizione di backoff esponenziale.
  • Modifica la strategia di ripetizione per riprovare immediatamente.
  • Configura un argomento messaggi non recapitabili.
  • Imposta un numero massimo e minimo di tentativi di consegna.

Per configurare i criteri 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 destinazione.

Per ulteriori informazioni sulla configurazione diretta di Pub/Sub, consulta la documentazione di Pub/Sub sulla gestione degli errori.