Ponów próbę użycia funkcji asynchronicznych

W tym dokumencie opisano, jak można żądać ponowienia asynchronicznych (innych niż HTTPS) funkcji działających w tle w przypadku niepowodzenia.

Dlaczego funkcje oparte na zdarzeniach nie są wykonywane

W rzadkich przypadkach funkcja może zakończyć działanie przedwcześnie z powodu błędu wewnętrznego. Domyślnie może ona zostać automatycznie wykonana jeszcze raz, ale nie musi.

Częściej jednak funkcja oparta na zdarzeniach może nie zostać wykonana z powodu błędów w samym kodzie funkcji. Możliwe przyczyny:

  • Funkcja zawiera błąd, a środowisko wykonawcze zgłasza wyjątek.
  • Funkcja nie może połączyć się z punktem końcowym usługi lub przekracza limit czasu podczas próby połączenia.
  • Funkcja celowo zgłasza wyjątek (np. gdy parametr nie przejdzie weryfikacji).
  • Funkcja Node.js zwraca odrzuconą obietnicę lub przekazuje do wywołania zwrotnego wartość inną niż null.

W każdym z powyższych przypadków funkcja przestanie działać i zwróci błąd. Aktywatory zdarzeń, które generują wiadomości, mają zasady ponawiania, które możesz dostosować do potrzeb funkcji.

Semantyka ponownej próby

Cloud Functions zapewnia co najmniej jednokrotne wykonanie funkcji opartej na zdarzeniach dla każdego zdarzenia wygenerowanego przez źródło zdarzeń. Domyślnie, jeśli wywołanie funkcji zakończy się błędem, funkcja nie zostanie wywołana ponownie, a zdarzenie zostanie odrzucone. Gdy włączysz ponowne próby w funkcji opartej na zdarzeniach, Cloud Functions będzie ponawiać nieudane wywołanie funkcji, dopóki nie zakończy się ono powodzeniem lub nie upłynie czas na ponowną próbę.

Jeśli ponowne próby nie są włączone dla funkcji (jest to ustawienie domyślne), funkcja zawsze zgłasza, że została wykonana pomyślnie, a w jej logach mogą pojawić się kody odpowiedzi 200 OK. Dzieje się tak nawet wtedy, gdy w funkcji wystąpi błąd. Aby wyraźnie wskazać, kiedy funkcja napotka błąd, pamiętaj o odpowiednim raportowaniu błędów.

Konfigurowanie ponownych prób w kodzie funkcji

W Cloud Functions for Firebase możesz włączyć ponowne próby w kodzie funkcji. Aby to zrobić w przypadku zdarzenia w tle, takiego jak utworzenie nowego dokumentu Firestore, ustaw opcję zasady failurePolicy (1 generacja) lub retry (2 generacja) na true:

Pierwsza generacja

exports.docCreated = functions
  .runWith({
    // retry on failure
    failurePolicy: true,
  })
  .firestore.document("my-collection/{docId}")
  .onCreate((change, context) => {
    /* ... */
  });

Druga generacja

const { onDocumentCreated } = require("firebase-functions/firestore");

exports.docCreated = onDocumentCreated(
  {
    // retry on failure
    retry: true,
  },
  "my-collection/{docId}",
  (event) => {
    /* ... */
  },
);

Ustawienie true w pokazany sposób konfiguruje funkcję tak, aby ponawiała próbę w przypadku niepowodzenia.

Okno ponownej próby

W przypadku funkcji 2 generacji to okno ponawiania wygasa po 24 godzinach. W przypadku funkcji 1 generacji wygasa po 7 dniach. Cloud Functions ponawia próby wykonania nowo utworzonych funkcji opartych na zdarzeniach, stosując strategię wzrastającego czasu do ponowienia, który wynosi od 10 do 600 sekund. Ta zasada jest stosowana do nowych funkcji przy ich pierwszym wdrożeniu. Nie jest ona stosowana wstecznie do istniejących funkcji, które zostały wdrożone po raz pierwszy przed wejściem w życie zmian opisanych w tych informacjach o wersji, nawet jeśli wdrożysz te funkcje ponownie.

Sprawdzone metody

W tej sekcji opisano sprawdzone metody korzystania z ponownych prób.

Obsługa błędów przejściowych za pomocą ponownych prób

Ponieważ funkcja jest wykonywana wielokrotnie, aż do pomyślnego zakończenia, przed włączeniem ponownych prób należy wyeliminować z kodu trwałe błędy, takie jak błędy w kodzie. Ponowne próby najlepiej sprawdzają się w przypadku sporadycznych lub przejściowych awarii, które z dużym prawdopodobieństwem zostaną rozwiązane po ponownej próbie, takich jak niestabilny punkt końcowy usługi lub przekroczenie limitu czasu.

Ustaw warunek zakończenia, aby uniknąć nieskończonych pętli ponownych prób

Podczas korzystania z ponownych prób sprawdzoną metodą jest zabezpieczenie funkcji przed ciągłym zapętleniem. Możesz to zrobić, dodając dobrze zdefiniowany warunek zakończenia, zanim funkcja zacznie przetwarzać dane. Pamiętaj, że ta technika działa tylko wtedy, gdy funkcja uruchamia się pomyślnie i może ocenić warunek zakończenia.

Prostym, ale skutecznym podejściem jest odrzucanie zdarzeń ze znacznikami czasu starszymi niż określony czas. Pomaga to uniknąć nadmiernej liczby wykonań, gdy błędy są trwałe lub utrzymują się dłużej niż oczekiwano.

Na przykład ten fragment kodu odrzuca wszystkie zdarzenia starsze niż 10 sekund:

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

Używanie catch z obiektami Promise

Jeśli funkcja ma włączone ponawianie prób, każdy nieobsługiwany błąd spowoduje ponowienie próby. Upewnij się, że kod przechwytuje wszystkie błędy, które nie powinny powodować ponownej próby.

Oto przykład, co należy zrobić:

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

Ustawianie funkcji sterowanych zdarzeniami z możliwością ponowienia jako idempotentnych

Funkcje oparte na zdarzeniach, które można uruchomić ponownie, muszą być idempotentne. Oto kilka ogólnych wskazówek, jak sprawić, aby taka funkcja była idempotentna:

  • Wiele zewnętrznych interfejsów API (np. Stripe) umożliwia podanie klucza idempotencji jako parametru. Jeśli używasz takiego interfejsu API, jako klucza idempotencji użyj identyfikatora zdarzenia.
  • Idempotentność dobrze współgra z dostarczaniem co najmniej raz, ponieważ umożliwia bezpieczne ponawianie prób. Dlatego sprawdzoną metodą pisania niezawodnego kodu jest łączenie idempotencji z ponownymi próbami.
  • Upewnij się, że kod jest wewnętrznie idempotentny. Na przykład:
    • Upewnij się, że mutacje mogą wystąpić więcej niż raz bez zmiany wyniku.
    • Sprawdź stan bazy danych w transakcji przed zmianą stanu.
    • Upewnij się, że wszystkie efekty uboczne są idempotentne.
  • Nałożyć kontrolę transakcyjną poza funkcją, niezależnie od kodu. Możesz na przykład zapisać stan, który wskazuje, że dany identyfikator zdarzenia został już przetworzony.
  • Obsługuj zduplikowane wywołania funkcji poza pasmem. Możesz na przykład utworzyć osobny proces czyszczenia, który będzie usuwał skutki zduplikowanych wywołań funkcji.

Konfigurowanie zasad ponawiania

W zależności od potrzeb funkcji możesz skonfigurować zasadę ponawiania bezpośrednio. Umożliwi Ci to skonfigurowanie dowolnej kombinacji tych elementów:

  • Skróć okres ponawiania prób z 7 dni do zaledwie 10 minut.
  • Zmień minimalny i maksymalny czas do ponowienia w przypadku strategii ponawiania ze wzrastającym czasem do ponowienia.
  • Zmień strategię ponawiania prób na natychmiastowe ponawianie.
  • Skonfiguruj temat niedostarczonych komunikatów.
  • Ustaw maksymalną i minimalną liczbę prób dostarczenia.

Aby skonfigurować zasadę ponawiania:

  1. napisać funkcję HTTP,
  2. Użyj interfejsu API Pub/Sub, aby utworzyć subskrypcję Pub/Sub, podając adres URL funkcji jako cel.

Więcej informacji o bezpośrednim konfigurowaniu Pub/Sub znajdziesz w dokumentacji Pub/Sub dotyczącej obsługi błędów.