イベントトリガーCloud Functionsが1回のイベントに対し2回以上起動することがある理由を調べてみた

重複(ちょうふく?じゅうふく?)イベントについてです。
2023.11.08

クラスメソッド株式会社データアナリティクス事業本部所属のニューシロです。
今回はGoogle CloudのサービスであるCloud Functionsのイベントトリガーを扱う方向けの注意事項として、イベントトリガーのCloud Functionsが1回のイベントに対し2回以上起動することがある理由を調べてみました。

例えばイベントトリガーCloud FunctionsでCloud Storageトリガーを設定した場合、Cloud Storageでオブジェクトが1件作成されたことをトリガーにして、Cloud Functionsを1回起動させることができます。
作成されたバケット名やオブジェクト名などの情報もCloud Functionsへ送られるため、それらを用いて対象のオブジェクトを処理できます。

非常に便利な機能ですが、表題にあるようにイベントトリガーCloud Functionsは1回のイベントに対し2回以上起動することがあるということに注意しなければいけません。

本題

イベントトリガーはイベント送受信にPub/Subを使用している

第1世代と第2世代では若干仕組みが変わってきます。今回は第2世代Cloud Functionsについて調べました。
イベントトリガーにはEventarcというイベントを管理するサービスが用いられています。ドキュメントに以下の記載があります。

イベントは、Pub/Sub push サブスクリプションを介して、イベント レシーバ(またはコンシューマ)と呼ばれる特定の宛先(ターゲット)に転送されます。

このように、イベントトリガーはイベント送受信にPub/Subを使用しています。
実際にイベントトリガーで第2世代Cloud Functionsを作成すると、自動でPub/SubトピックとPub/Subサブスクリプションが作成されます。
イベントトリガーにも種類がありますので、この記事では例として、先ほども記載したCloud Storageトリガーについて細かい処理の流れを見ていきます。Pub/Subを利用していることを踏まえて、先ほどの図をより正確に記すと以下のようになります。

重複イベントが発生する理由①

ここでのポイントは、Eventarcで用いられるPub/Subのpushサブスクリプションの性質です。Pub/Subサブスクリプションにはpush形式とpull形式がありますが、push形式には以下の性質があります。

デフォルトでは、Pub/Sub は at-least-once(少なくとも 1 回)配信を提供しますが、すべてのサブスクリプションタイプで順序は保証されません。

少なくとも1回、つまり同じ内容のメッセージが2回以上送られてしまうことがあるということです。
図で表すと、以下のことが起きる可能性があるということです。

もちろん、同じ内容のメッセージを2回受け取ったCloud Functionsは、同じ処理を2回行います。
これがCloud Functionsのイベントトリガーは1回のイベントに対し複数回起きることがある理由です。
イベントトリガーを用いてCloud Functionsを設計する際は、このようなことが発生することも考慮しなければいけません。

補足

補足ですが、1回限りの配信にする設定は、現時点においてpush型ではできないようです。

Pub/Sub は 1 回限りの配信(pull サブスクリプションのみ)をサポートします。

重複イベントが発生する理由②

上記以外にもう1つ理由があります。ドキュメントに以下の記載があります。

Cloud Storage トリガーは、Cloud Storage の Pub/Sub 通知を使用して実装されます。イベントには Pub/Sub 通知の配信の保証が適用されます。

では、使用される Pub/Sub通知の配信の保証について見てみましょう。

Cloud Storage は Pub/Sub に少なくとも 1 回以上配信を行います。

Pub/Subサブスクリプションと同様に、Cloud Storageからの通知も複数回の配信を行う可能性があるようです

Cloud Storageから2回配信した場合の図は以下のようになります。

このように、Cloud Storage側から同内容のメッセージが2回送信されると、当然受け取ったEventarcも同内容のメッセージを2回Cloud Functionsに送信します。 もちろん、受け取ったCloud Functionsは同じ処理を2回行ってしまうことになります。

①と②、以上2つがイベントトリガーCloud Functionsは1回のイベントに対し2回以上起動することがある理由です。

対策の方針

①の場合

ではこのように重複して起動した場合の対策を考えてみましょう。
公式ドキュメントに対策の方針について記載があります。詳しくは引用元をご確認ください。  

コードとは関係なく、トランザクション チェックをサービスの外側に置きます。たとえば、指定されたイベント ID がすでに処理されたことを記録している場所の状態を保持します。

対策の一例として、Cloud Functionsが受け取るイベントには一意のIDがありますので、そのIDを元に重複イベントを見分け、別の処理を行う方法があるようです。

②の場合

しかし今回のCloud Storageトリガーだと、IDでは全ての重複イベントを見分けられない場合があります。それが、重複イベントが発生する理由②の場合です。改めて図を記載します。

この場合、①とは異なり、重複メッセージを受け取ったPub/Subは、2通のメッセージにそれぞれ異なるIDを付与します。

送信が開始されると、Cloud Storage は Pub/Sub に少なくとも 1 回以上配信を行います。Pub/Sub は、受信者にも 1 回以上の配信を行います。つまり、同じ Cloud Storage イベントを表す異なる ID のメッセージを受信する場合があります

ややこしいですが、①のようにイベントIDを基準にすれば良いとは限らないようです。

①も②もどちらも起こる可能性があります。どちらの重複イベントも処理するにはどのようにしたら良いでしょうか。こちらについても公式ドキュメントに記載があります。

通知に基づいて Cloud Storage オブジェクトを変更する場合には、オブジェクトの世代番号とメタ世代番号を基準に更新リクエストを行うことをおすすめします。

①と②両方に対応するには、オブジェクトの世代番号、メタ世代番号を用いると良いようです。世代番号、メタ世代番号ともにCloud Functionsが受信するイベントに情報として含まれていますので利用することができます。

ちなみに世代番号やメタ世代番号は私がこちらの記事でまとめています。

終わりに

今回はCloud Storageトリガーを例として紹介しました。他にもイベントトリガーはありますので、Eventarcやトリガー元、Cloud Functionsの性質を理解してうまく設計していきましょう。
また、この記事を執筆するにあたりGoogle Cloudのご担当者様にもご協力をいただきました。この場を借りてお礼申し上げます。

引用・参照まとめ