非同期関数の再試行

このドキュメントでは、失敗時に非同期(非 HTTPS)バックグラウンド ファンクションをリクエストして再試行する方法について説明します。

再試行のセマンティクス

Cloud Functions では、イベントソースによって生成された各イベントにつき、最低 1 回はイベント ドリブン関数が必ず実行されることになっています。デフォルトでは、関数の呼び出しがエラーによって終了した場合、その関数が再度呼び出されることはなく、そのイベントはドロップします。イベント ドリブン関数の再試行を有効にすると、Cloud Functions で失敗した関数の呼び出しが正常に完了するか、再試行期間が終了するまで再試行が行われます。

第 2 世代の関数では、この再試行期間は 24 時間後に終了します。第 1 世代の関数では、再試行期間は 7 日後に終了します。 Cloud Functions は、指数バックオフ戦略を使用して、新しく作成されたイベント ドリブン関数を再試行します。バックオフ時間は 10~600 秒の間で増加します。このポリシーは、新しい関数を初めてデプロイすると適用されます。このリリースノートに記載されている変更が有効になる前に最初にデプロイされた既存の関数に対しては、たとえその関数を再デプロイしても、遡って適用されることはありません。

関数の再試行がデフォルトで有効になっていない場合、再試行は正常に実行されたと関数により常に報告され、200 OK レスポンス コードがログに記録されます。これは、関数でエラーが発生した場合でも同様です。関数でエラーが発生したことを明確にするために、適切にエラーを報告してください。

イベント ドリブン関数が完了しない理由

まれに、内部エラーのため関数が早期に終了することがあります。関数は、デフォルトで自動的に再試行される場合もあれば、再試行されない場合もあります。

よくあるのは、関数コード内でエラーがスローされるためにイベント ドリブン関数が正常に完了できないケースです。これには、次のような理由が考えられます。

  • 関数にバグがあるため、ランタイムから例外がスローされる。
  • 関数がサービス エンドポイントに到達できないか、エンドポイントに到達しようとしている間にタイムアウトする。
  • 関数から意図的に例外がスローされる(たとえば、パラメータの検証で不合格だった場合)。
  • Node.js 関数は、拒否された Promise を返すか、コールバックに null 以外の値を渡します。

いずれの場合も、関数はデフォルトで実行を停止し、イベントが破棄されます。エラーが発生したときに関数を再試行するには、「失敗時に再試行する」プロパティを設定して、デフォルトの再試行ポリシーを変更できます。これにより、関数が正常に完了するか、再試行タイムアウトになるまで、イベントが繰り返し再試行されます。

再試行を有効または無効にする

コンソールから再試行を構成する

新しい関数を作成する場合:

  1. [関数を作成] 画面で [トリガーを追加] を選択して、関数のトリガーとして機能するイベントのタイプを選択します。
  2. [失敗時に再試行する] チェックボックスをオンにして、再試行を有効にします。

既存の関数を更新する場合:

  1. Cloud Functions の概要ページで、更新する関数の名前をクリックして [関数の詳細] 画面を開き、メニューバーで [編集] を選択して [トリガー] ペインを表示します。
  2. [失敗時に再試行する] チェックボックスをオンまたはオフにして、再試行を有効または無効にします。

関数コードから再試行を構成する

Cloud Functions for Firebase を使用すると、関数のコード内で再試行を有効にできます。functions.foo.onBar(myHandler); のようなバックグラウンド関数に対してこれを行うには、runWith を使用して失敗ポリシーを構成します。

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

このコードのように true と設定すると、失敗時に再試行する関数が構成されます。

ベスト プラクティス

ここでは、再試行の使用に関するベスト プラクティスを説明します。

再試行を使用して一時的なエラーを処理する

関数は成功するまで継続的に再試行されるため、テストを通じてバグなどの永続的なエラーをコードから除去してから、再試行を有効にしてください。再試行によって解決される可能性が高い断続的なエラーや一時的なエラー(サービス エンドポイントの不安定さやタイムアウトなど)を処理するには、再試行が最適です。

無限再試行ループを避けるための終了条件の設定

再試行を使用する場合は、関数が連続ループに陥らないように保護することをおすすめします。そのためには、明確に定義された終了条件を含めてから関数の処理を開始します。この手法が機能するのは、関数が正常に開始し、終了条件を評価できる場合のみです。

簡単で効果的なアプローチは、特定の時間よりも古いタイムスタンプを持つイベントを破棄することです。これにより、エラーが持続的である場合や継続時間が予想よりも長い場合に、実行時間が過度に長くなるのを回避できます。

たとえば、次のコード スニペットは 10 秒を超えるすべてのイベントを破棄します。

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

Promises で catch を使用する

関数の再試行が有効になっている場合、未処理のエラーがあると再試行がトリガーされます。再試行を行わないエラーをコードがキャプチャしていることを確認してください。

対処例を次に示します。

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

再試行可能なイベント ドリブン関数をべき等にする

再試行可能なイベント ドリブン関数は、べき等にする必要があります。このような関数をべき等化するための一般的なガイドラインを次に示します。

  • 多くの外部 API(Stripe など)では、べき等のキーをパラメータとして指定できます。このような API を使用している場合は、イベント ID をべき等のキーとして使用します。
  • べき等では再試行が安全に行われるため、at-least-once 配信でうまく機能します。したがって、信頼性の高いコードを書くための一般的なベスト プラクティスは、べき等と再試行を組み合わせることです。
  • コードが内部でべき等であることを確認します。次に例を示します。
    • 結果が変わらずにミューテーションが 2 回以上起こることを確認する。
    • 状態を変更する前にトランザクション内のデータベース状態を照会する。
    • すべての副作用がそれ自体べき等であることを確認する。
  • コードとは関係なく、トランザクション チェックを関数の外側に置く。たとえば、指定されたイベント ID がすでに処理されたことを記録している場所の状態を保持します。
  • 重複した関数呼び出しを帯域外で処理する。たとえば、重複した関数呼び出しの後にクリーンアップする別のクリーンアップ プロセスを用意します。

再試行ポリシーを構成する

関数のニーズに応じて、再試行ポリシーを直接構成することもできます。以下を任意の組み合わせで設定できます。

  • 再試行期間を 7 日間から 10 分に短縮する。
  • 指数バックオフの再試行方法の最小および最大バックオフ時間を変更する。
  • すぐに再試行できるように再試行方法を変更する。
  • デッドレター トピックを構成する。
  • 配信の試行回数の最大値と最小値を設定する。

再試行ポリシーを構成するには:

  1. HTTP 関数を記述します。
  2. Pub/Sub API を使用して Pub/Sub サブスクリプションを作成し、関数の URL をターゲットとして指定します。

Pub/Sub の直接構成の詳細については、失敗の処理に関する Pub/Sub のドキュメントをご覧ください。