0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Firestore トリガーによるCloud Functionsの無限ループを抑える工夫

Last updated at Posted at 2024-11-25

結論

Firestore トリガーによるCloud Functionsの無限ループを防ぐために、以下の管理フィールドを導入します。

prevent_trigger_at: timestamp
  • フィールドの役割: ドキュメント更新時に、Cloud Functions内で現在時刻をこのフィールドに設定します
  • 仕組み: 次回トリガー発動時に、prevent_trigger_atの値が前回と異なるかを確認し、一致しない場合は処理をスキップします
  • ポイント: このフィールドはクライアントから更新せず、サーバー側からのみ更新します

サーバー側(CloudFunction)からのみ、この項目を更新することにより、無限ループを抑止し、トリガー処理の安全性を向上させます。

ここからは余談

みなさん、Firestore トリガーを使っていますか?便利すぎますよね。

私が1年以上関わっているモバイルアプリプロジェクトでは、FirestoreをメインDBとして採用しています。そして、Cloud Functionsを利用したバックエンド処理も多いため、Firestore トリガーは欠かせない存在です。

ところが、開発初期のスキーマ設計が不十分だったため、無限ループに悩まされる事態に陥りました。
コレクションに書き込みが発生すると、トリガー条件と同じコレクションを処理の中で更新して、それが再びトリガーを発動し、さらなる更新を引き起こす…という無限ループです。

初期対応として、トリガー内で「更新内容が前回と同じなら処理をスキップする」というロジックを導入しました。しかし、処理内容が増えるにつれ、スキップ条件が複雑化して破綻。結果、設計全体を見直す必要に迫られました。

そこでprevent_trigger_atという、トリガー実行を判断するフィールドを設けることにしたのです。

現在のスキップするコード(JavaScript)としては以下のようになっています。

/**
 * Firestore トリガーのスキップロジック
 * - 新規作成時はスキップしない
 * - 削除イベントはスキップ
 * - prevent_trigger_atが更新されている場合はスキップ
 * - 古いイベント(一定時間経過)はスキップ
 */
private isSkip(event: FirestoreEvent<Change<DocumentSnapshot>>): boolean {
  const prev = event?.data?.before?.data?.(); // 前の状態
  const doc = event?.data?.after?.data?.();  // 現在の状態

  if (_.isNil(prev)) return false; // 新規作成イベント
  if (_.isNil(doc)) return true;  // 削除イベント

  // prevent_trigger_atが変更されている場合はスキップ
  if (prev.prevent_trigger_at?.toMillis() !== doc.prevent_trigger_at?.toMillis()) {
    return true;
  }

  /**
   * 古いイベントの破棄. サーキットブレーカー
   * https://cloud.google.com/functions/docs/samples/functions-tips-infinite-retries#functions_tips_infinite_retries-nodejs
   */
  const eventAge = Date.now() - Date.parse(event.time);
  const eventMaxAge = 10000; // 最大許容時間: 10秒
  if (eventAge > eventMaxAge) {
    logger.error(`Dropping event ${event} with age ${eventAge} ms.`);
    return true;
  }

  return false; // スキップ条件に該当しない場合は処理を実行
}

この処理がトリガーされた場合の共通処理として実行されるようにしてあり、これにより無限ループを止めることができています。
上記コードには、prevent_trigger_at以外にも共通のチェック項目はいれています。

それではよいFirestoreライフを。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?