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

Z tego dokumentu dowiesz się, jak możesz poprosić o ponowienie próby wykonania asynchronicznych funkcji działających w tle (innych niż HTTP) w przypadku niepowodzenia.

Dlaczego funkcje reagujące na zdarzenia nie mogą się zakończyć

W rzadkich przypadkach funkcja może zostać przedwcześnie zakończona z powodu błędu wewnętrznego. Domyślnie funkcja może zostać automatycznie ponowiona lub nie.

Zazwyczaj jednak funkcja reagująca na zdarzenia może nie zostać ukończona z powodu błędów występujących w samym kodzie funkcji. Przyczyny mogą być następujące:

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

W każdym z tych przypadków funkcja przestanie się wykonywać i zwróci błąd. Triggery zdarzeń generujące wiadomości mają zasady ponawiania, które możesz dostosować do potrzeb swojej funkcji.

Semantyka ponawiania

Cloud Functions zapewnia co najmniej jednokrotne wykonanie funkcji reagującej na zdarzenia dla każdego zdarzenia emitowanego 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 ponawianie w funkcji reagującej na zdarzenia, Cloud Functions ponawia nieudane wywołanie funkcji, dopóki nie zostanie ono ukończone lub nie upłynie czas ponawiania.

Gdy ponawianie nie jest włączone w przypadku funkcji (co jest ustawieniem domyślnym), funkcja zawsze zgłasza, że została wykonana pomyślnie, a w jej logach mogą pojawiać się kody odpowiedzi 200 OK. Dzieje się tak nawet wtedy, gdy funkcja napotka błąd. Aby było jasne, kiedy funkcja napotka błąd, pamiętaj o odpowiednim zgłaszaniu błędów.

Konfigurowanie ponawiania w kodzie funkcji

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

1 generacja

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

2 generacja

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

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

Ustawienie true w sposób pokazany powyżej powoduje, że funkcja będzie ponawiana w przypadku niepowodzenia.

Okres ponawiania

W przypadku funkcji 2 generacji ten okres ponawiania wygasa po 24 godzinach. W przypadku funkcji 1 generacji wygasa po 7 dniach. Cloud Functions ponawia nowo utworzone funkcje reagujące na zdarzenia, stosując strategię wzrastającego czasu do ponowienia, z rosnącym czasem do ponowienia od 10 do 600 sekund. Ta zasada jest stosowana do nowych funkcji przy pierwszym wdrożeniu. Nie jest ona stosowana z mocą wsteczną do istniejących funkcji, które zostały wdrożone po raz pierwszy przed wejściem w życie zmian opisanych w tej informacji o wersji, nawet jeśli ponownie wdrożysz te funkcje.

Sprawdzone metody

W tej sekcji opisujemy sprawdzone metody korzystania z ponawiania.

Używanie ponawiania do obsługi błędów przejściowych

Ponieważ funkcja jest ponawiana w sposób ciągły, dopóki nie zostanie wykonana pomyślnie, przed włączeniem ponawiania należy wyeliminować z kodu błędy trwałe, takie jak błędy, za pomocą testów. Ponawianie najlepiej sprawdza się w przypadku błędów sporadycznych lub przejściowych, które z dużym prawdopodobieństwem zostaną rozwiązane po ponowieniu próby, takich jak zawodny punkt końcowy usługi lub przekroczenie limitu czasu.

Ustawianie warunku zakończenia, aby uniknąć nieskończonych pętli ponawiania

Sprawdzoną metodą jest zabezpieczenie funkcji przed ciągłym powtarzaniem w przypadku korzystania z ponawiania. Możesz to zrobić, dodając dobrze zdefiniowany warunek zakończenia zanim funkcja zacznie przetwarzać. Pamiętaj, że ta technika działa tylko wtedy, gdy funkcja uruchomi się pomyślnie i będzie mogła ocenić warunek zakończenia.

Prostym, ale skutecznym rozwiązaniem jest odrzucanie zdarzeń ze znacznikami czasu starszymi niż określony czas. Pomaga to uniknąć nadmiernego wykonywania, gdy błędy są trwałe lub trwają dłużej niż oczekiwano.

Ten fragment kodu odrzuca na przykład 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 obietnicami

Jeśli w funkcji włączono ponawianie, każdy nieobsłużony błąd spowoduje ponowienie próby. Upewnij się, że kod przechwytuje wszystkie błędy, które nie powinny powodować ponowienia 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);
});

Zapewnianie idempotentności funkcji reagujących na zdarzenia, które można ponawiać

Funkcje reagujące na zdarzenia, które można ponawiać, muszą być idempotentne. Oto kilka ogólnych wskazówek dotyczących zapewniania idempotentności takiej funkcji:

  • Wiele zewnętrznych interfejsów API (np. Stripe) umożliwia podanie klucza idempotentności jako parametru. Jeśli używasz takiego interfejsu API, jako klucza idempotentności użyj identyfikatora zdarzenia.
  • Idempotentność dobrze współpracuje z dostarczaniem co najmniej raz, ponieważ umożliwia bezpieczne ponawianie. Dlatego ogólną sprawdzoną metodą pisania niezawodnego kodu jest łączenie idempotentności z ponawianiem.
  • Upewnij się, że kod jest wewnętrznie idempotentny. Na przykład:
    • Upewnij się, że mutacje mogą występować więcej niż raz bez zmiany wyniku.
    • Przed zmianą stanu wykonaj zapytanie o stan bazy danych w transakcji.
    • Upewnij się, że wszystkie efekty uboczne są idempotentne.
  • Wymuś sprawdzenie transakcyjne poza funkcją, niezależnie od kodu. Na przykład zapisz stan w miejscu, w którym jest rejestrowane, że dany identyfikator zdarzenia został już przetworzony.
  • Obsługuj zduplikowane wywołania funkcji poza pasmem. Na przykład utwórz osobny proces zwalniania miejsca, który będzie usuwać zduplikowane wywołania funkcji.

Konfigurowanie zasad ponawiania

W zależności od potrzeb funkcji możesz skonfigurować zasady ponawiania bezpośrednio. Umożliwi to skonfigurowanie dowolnej kombinacji tych ustawień:

  • Skrócenie okresu ponawiania z 7 dni do zaledwie 10 minut.
  • Zmiana minimalnego i maksymalnego czasu do ponowienia dla strategii ponawiania z wzrastającym czasem do ponowienia.
  • Zmiana strategii ponawiania na ponawianie natychmiastowe.
  • Skonfigurowanie tematu niedostarczonych komunikatów.
  • Ustawienie maksymalnej i minimalnej liczby prób dostarczenia.

Aby skonfigurować zasady ponawiania:

  1. Napisz funkcję HTTP.
  2. Użyj interfejsu Pub/Sub API, aby utworzyć subskrypcję Pub/Sub, określają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.