非同期関数の再試行

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

再試行のセマンティクス

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

バックグラウンド関数が完了しない理由

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

よくあるのは、ファンクション コード内でエラーがスローされるためにバックグラウンド ファンクションが正常に完了できないケースです。これが起こる理由としては、次のようなものがあります。

  • ファンクションにバグがあるため、ランタイムから例外がスローされる。
  • ファンクションがサービス エンドポイントに到達できないか、エンドポイントに到達しようとしている間にタイムアウトする。
  • ファンクションから意図的に例外がスローされる(たとえば、パラメータの検証に失敗した場合)。
  • Node.js に記述されているファンクションが Promise のリジェクトを返すか、コールバックに null 以外の値を渡す場合。

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

再試行の有効化と無効化

GCP Console の使用

GCP Console で再試行を有効または無効にする手順は次のとおりです。

  1. Cloud Platform Console の [Cloud Functions の概要] ページに移動します。

  2. [関数を作成] をクリックします。または、既存のファンクションをクリックしてその詳細ページに移動し、[編集] をクリックします。

  3. ファンクションの必須フィールドを入力します。

  4. [トリガー] フィールドがバックグラウンド ファンクションのトリガータイプ(Cloud Pub/Sub や Cloud Storage など)に設定されていることを確認します。

  5. [詳細] をクリックして詳細設定を表示します。

  6. [失敗時に再試行する] というラベルが付いたチェックボックスをオンまたはオフにします。

ベスト プラクティス

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

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

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

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

再試行を使用する場合は、ファンクションが連続ループに陥らないように保護することをおすすめします。そのためには、明確に定義された終了条件を含めてからファンクションの処理を開始します。簡単で効果的なアプローチは、特定の時間よりも古いタイムスタンプを持つイベントを破棄することです。これにより、エラーが持続的である場合や継続時間が予想よりも長い場合に、実行時間が過度に長くなるのを回避できます。

たとえば、次のコード スニペットは 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 を使用する

再試行を有効にした場合は、Promises に catch を追加することを検討してください。Cloud Functions は、再試行すべきでない致命的なエラーを認識できないため、致命的なエラーに対応する場合はコードを適宜変更する必要があります。

対処例を次に示します。

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 がすでに処理されたことを記録しているどこかの状態を保持します。
  • 重複したファンクション呼び出しにアウトバンドで対応します。たとえば、重複したファンクション呼び出しの後にクリーンアップする別のクリーンアップ プロセスを用意します。

フィードバックを送信...

ご不明な点がありましたら、Google のサポートページをご覧ください。