3
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?

Eventarc Standard と Cloud Run functions のリトライ挙動を検証してみた

Posted at

はじめに

調査背景

既存PoCのアーキテクチャについて設定見直しを行う中で、Eventarc 部分のリトライ設定についても調査することになった。
PoCのためリトライにおける方針としては「アプリケーションによる失敗時のリトライは不要、失敗したイベントは別途処理する」という要件とし、リトライ回数を0回に設定できるかを調査・検証をした。

なお、リトライへの対処として本来は冪等性を担保する設計が推奨される。
冪等性があれば、同じイベントが複数回処理されても結果が変わらないため、リトライを気にする必要がなくなる。しかし、外部APIの制約やシステムの複雑性から冪等性の担保が難しいケースもある。

本記事は、そのような場合の次善策として「リトライ自体を発生させない方法」があるか調査検討したものである。

本調査のきっかけとなったPoCの構成

以下が今回設定見直しを行った対象のPoCアーキテクチャである。
GCS にファイルがアップロードされたイベントを Eventarc Standard 経由で Cloud Run functions に配信する構成を採用している。

image.png

結論

Eventarc Standard のリトライ回数を「設定で完全な0回」にすることはできないが、実質的に「0回(再試行なし)」と同等の挙動にすることは可能。

ただし、設定(retryPolicy)だけで制御できるのは「関数実行が開始された後」のエラーのみ。
認証エラーやサービスの中断など、関数が呼ばれる前に発生する「インフラ起因のエラー」については、Pub/Sub 側のリトライが発生する。

そのため、アプリケーション側での対処(イベント年齢チェック等の作りこみ)や、デッドレターキュー(DLQ)の活用が必要となる。

項番 エラー発生レイヤ デフォルトでの再送 対処法
1 インフラ 認証エラー、Rate Limit、サーバーエラー等
(ACK以外のHTTPステータス)
される 作りこみ or DLQ
2 アプリケーション(コード部分) 関数内 Exception されない 対処不要
(デフォルト設定で0回)

※ ACK(Acknowledgment): メッセージを正常に受信・処理したことを示す確認応答。
Pub/Sub では 102, 200, 201, 202, 204 のステータスコードが ACK として扱われ、再送されない。

Eventarc について

Eventarc とは

GCS へのファイルアップロードなど、 Google Cloud プロバイダのイベントを Cloud Run functions 等に配信できるサービス。
AWSで例えると EventBridge が類似サービスとなる。

Eventarc を使用すると、基盤となるインフラストラクチャを実装、カスタマイズ、またはメンテナンスすることなく、イベント ドリブン アーキテクチャを構築できます。

― Google Cloud 公式ドキュメント「Eventarc の概要」より

2つのエディション

Eventarc には Eventarc AdvancedEventarc Standard の2つのエディションがある。
ユースケースの複雑さに応じて使い分ける。
本記事では Eventarc Standard を対象とする。

ユースケースの複雑さが増すにつれて、Eventarc Standard の使用から Eventarc Advanced の使用にシームレスに移行できます。

― Google Cloud 公式ドキュメント「Eventarc の概要」より

Pub/Sub との関係性

Eventarc Standard は、内部で Pub/Sub を利用してイベントを配信している。

image.png

公式ドキュメントの機能比較表にも以下の記載がある。

項番 機能 Eventarc Standard
1 デッドレターキューがサポートされている ○(Pub/Sub デッドレタートピックを使用)
2 再試行と保持 ターゲットへのイベントの 1 回以上の配信。デフォルトのメッセージ保持期間は 24 時間(指数バックオフ遅延あり)

出典: Eventarc の概要

2種類のリトライについて

Eventarc Standard (Pub/Sub) + Cloud Run functions の構成では、 それぞれが持つリトライ設定が影響する。

項番 リトライの種類 何を制御するか デフォルト 無効化
1 Pub/Sub subscription の retry policy メッセージがACKされるまで再送するか 指数バックオフ 不可
2 Cloud Run functions の retryPolicy 関数実行失敗時に再実行するか RETRY_POLICY_DO_NOT_RETRY 可能

Pub/Sub subscription の retry policy

Eventarc トリガを作成すると、Pub/Sub トピックとサブスクリプションが自動生成される。
このサブスクリプションには、デフォルトで指数バックオフリトライ(10秒〜600秒)が設定されており、公式ドキュメントにも以下の記載がある。

This redelivery attempt is known as the subscription retry policy. This isn't a feature that you can turn on or off.

— 参考: サブスクリプション リトライポリシー

つまり、Pub/Sub のリトライポリシー自体をオフにすることはできず「即時再配信(Immediate redelivery)」か「指数バックオフ(Exponential backoff)」のどちらかのみを選択できる。

Cloud Run functions の retryPolicy

gcloud functions deploy でデプロイした場合、デフォルトで retryPolicy: RETRY_POLICY_DO_NOT_RETRY が設定される。

公式ドキュメントには以下の記載がある。

Describes the retry policy in case of function's execution failure.

― 参考: Google Cloud Functions リトライポリシー

ここで重要なのは「function's execution failure(関数の実行失敗)」という部分で、あくまで関数コードが実行された結果に対しての失敗を指しており、関数に到達する前のエラー(認証エラー等)は対象外を示しています。

retryPolicyに設定できる値

以下3つの値について設定が可能である。
何のオプションもつけずにデプロイした場合は、デフォルト設定でRETRY_POLICY_DO_NOT_RETRY が適用される。

項番 項目の意味 デフォルト
1 RETRY_POLICY_UNSPECIFIED 再試行ポリシーが指定されていません -
2 RETRY_POLICY_DO_NOT_RETRY 再試行ポリシーを再試行しない
3 RETRY_POLICY_RETRY 失敗した場合は再試行し、指数バックオフ(10秒上限)で最大7日間再試行 -

出典: Google Cloud Functions リトライポリシー

リトライを有効にしたい場合は --retry フラグを付けてデプロイする。

gcloud functions deploy FUNCTION_NAME --retry FLAGS...

出典: Google Cloud Functions イベントドリブン関数の再試行を有効にする

リトライの関係について

Pub/Sub push subscription は HTTP レスポンスで ACK/NACK を判断するため、エラーが発生するレイヤによって再送の挙動が異なる。

項番 エラー発生レイヤ HTTP レスポンス 再送
1 インフラレイヤ Cloud Run 認証エラー(401/403) そのまま 4XX される
2 アプリケーションレイヤ 関数内 Exception ランタイムが 200 OK に変換 されない

概要図

image.png
参考:Cloud Run 上のコンテナのライフサイクル

  • インフラレイヤでエラーが発生した場合、Pub/Subのリトライが実行される。
    そして、アプリケーションまで届いていないためのRETRY_POLICY_DO_NOT_RETRY の設定は効かない。

  • アプリケーションレイヤーでエラーが発生した場合、ランタイムが 200 レスポンス を返すため、Pub/Subのリトライは実行されない。
    そして、アプリケーション側が RETRY_POLICY_DO_NOT_RETRY のデフォルト設定の場合は実行されない。
    ただし、 RETRY_POLICY_RETRYにしている場合リトライが実行される。

詳細1:Pub/Sub は HTTP レスポンスで判断する

項番 HTTP レスポンス Pub/Sub の判断 再送
1 2XX ACK(成功) しない
2 それ以外 (4XX, 5XX) NACK(失敗)

If the endpoint returns an error response code, then the message delivery is considered to have failed and is retried later.
(エンドポイントがエラー応答コードを返す場合、メッセージの配信は失敗したとみなされ、後で再試行されます。)
— 参考:プッシュサブスクリプションのトラブルシューティング

詳細2:RETRY_POLICY_DO_NOT_RETRY の挙動

関数の再試行が有効になっていない(デフォルト)だと、常に 200 OKをレスポンスし、関数エラーであっても同様である

When retries are not enabled for a function, which is the default, the function always reports that it executed successfully, and 200 OK response codes might appear in its logs. This occurs even if the function encountered an error.
(関数の再試行が有効になっていない場合(デフォルト)、関数は常に正常に実行されたと報告し、200 OKログにレスポンスコードが記録されることがあります。これは、関数でエラーが発生した場合でも同様です。)
— 参考:非同期関数を再試行する(再試行のセマンティクス)

検証内容

上記の理解が正しいか、以下の3パタンでの検証を実施した。

検証ケース

Pub/Subはリトライされるかについて検証を行う。
アプリでのエラーは 200 OKが返されるためリトライがされないこと。
インフラでのエラーは認証を一時的に外すことで 403が返されるためリトライ発生が想定される。

ケース エラー発生個所 内容 想定結果
Case 1 アプリ 意図的にExceptionを発生させる リトライされない
Case 2 アプリ 意図的に403エラーを発生させる リトライされない
Case 3 インフラ 認証を外して403エラーを発生させる リトライされる

検証環境の設定内容

Cloud Run functions の設定 デフォルトでのデプロイで設定される値
項番 設定項目 設定値 説明
1 RETRY_POLICY RETRY_POLICY_DO_NOT_RETRY リトライしない設定
Eventarc(Pub/Sub サブスクリプション}の設定 デフォルトでのデプロイで設定される値
項番 設定項目 設定値 説明
1 ackDeadlineSeconds 600 確認応答の期限(秒)
2 messageRetentionDuration 86400s メッセージ保持期間
3 retryPolicy.minimumBackoff 10s 最小バックオフ
4 retryPolicy.maximumBackoff 600s 最大バックオフ
5 pushEndpoint https://eventarc-retry-test-... Cloud Run functions の URL
Cloud Run functions / Eventarc デプロイコマンド
gcloud functions deploy ${FUNCTION_NAME} \
  --gen2 \
  --runtime=python312 \
  --region=${REGION} \
  --source=. \
  --entry-point=handle_gcs_event \
  --trigger-event-filters="type=google.cloud.storage.object.v1.finalized" \
  --trigger-event-filters="bucket=${BUCKET_NAME}" \
  --service-account=${SA_EMAIL}
Cloud Run functions / Eventarc 設定内容確認コマンド
# Cloud Run functions の設定確認
gcloud functions describe ${FUNCTION_NAME} \
  --region=${REGION} \
  --format="yaml(eventTrigger)"

# Eventarc(Pub/Sub)の設定確認
SUBSCRIPTION_ID=$(gcloud eventarc triggers describe ${FUNCTION_NAME} \
  --location=${REGION} \
  --format="value(transport.pubsub.subscription)")

gcloud pubsub subscriptions describe ${SUBSCRIPTION_ID}

Case 1 と Case 2 での補足

PoCのプロジェクトや本検証含め、Eventarc 経由の Cloud Run functions の呼び出しは Eventトリガを利用している。

HTTP トリガと Event トリガについて

Cloud Run functionsはデプロイ時、呼び出され方によってHTTP経由のHTTPトリガと、Google Cloudサービス経由のEvent トリガの2種類が存在する。

リトライを踏まえた違いについて

Eventトリガ(※PoCプロジェクトや本検証での設定)では、関数内で abort(403) のように HTTP ステータスを返そうとしても、RETRY_POLICY_DO_NOT_RETRY の場合はランタイムが 200 OK に変換し、アプリケーションレイヤでは HTTP ステータスを制御できない。

トリガ種別 挙動
HTTP トリガ エラーコードが そのまま返る
Event トリガ(CloudEvents) エラーコードが 200 OK に変換される

検証に利用したコード

Case 1.関数内で Exception を発生させるコード
import functions_framework
from cloudevents.http import CloudEvent


@functions_framework.cloud_event
def handle_gcs_event(cloud_event: CloudEvent) -> None:
    """GCSイベントを受信し、意図的にExceptionを発生させる"""
    event_id = cloud_event["id"]
    event_time = cloud_event["time"]
    event_type = cloud_event["type"]
    event_source = cloud_event["source"]

    print(f"[RECEIVED] id={event_id}, time={event_time}, type={event_type}, source={event_source}")

    raise Exception(f"Intentional error for retry test. event_id={event_id}")
Case 1.結果ログ 一度しか実行されないことが以下より、確認できる(※5分程度待機)。
LEVEL: 
NAME: eventarc-retry-test
EXECUTION_ID: lCnemARIVaa3
TIME_UTC: 2026-01-10 08:20:30.425
LOG: [RECEIVED] id=17408881445455955, time=2026-01-10T08:04:19.355629Z, type=google.cloud.storage.object.v1.finalized, source=//storage.googleapis.com/projects/_/buckets/alert-library-333106-eventarc-retry-test
Case 2.関数内で意図的に 403 を返すコード
import functions_framework
from cloudevents.http import CloudEvent
from flask import abort


@functions_framework.cloud_event
def handle_gcs_event(cloud_event: CloudEvent) -> None:
    """GCSイベントを受信し、意図的に403エラーを返す"""
    event_id = cloud_event["id"]
    event_time = cloud_event["time"]
    event_type = cloud_event["type"]
    event_source = cloud_event["source"]

    print(f"[RECEIVED] id={event_id}, time={event_time}, type={event_type}, source={event_source}")

    abort(403, f"Intentional 403 error. event_id={event_id}")
Case 2.結果ログ Case 1と同様に一度しか実行されないことが以下より、確認できる(※5分程度待機)。
LEVEL: 
NAME: eventarc-retry-test
EXECUTION_ID: lCnemARIVaa3
TIME_UTC: 2026-01-10 08:12:20.230
LOG: [RECEIVED] id=17408881445455955, time=2026-01-10T08:04:19.355629Z, type=google.cloud.storage.object.v1.finalized, source=//storage.googleapis.com/projects/_/buckets/alert-library-333106-eventarc-retry-test
Case 3. 認証エラー(invoker 権限剥奪)
# invoker権限を剥奪
gcloud run services remove-iam-policy-binding ${FUNCTION_NAME} \
  --region=${REGION} \
  --member="serviceAccount:${SA_EMAIL}" \
  --role="roles/run.invoker"

# テスト実行
echo "test" | gcloud storage cp - gs://${BUCKET_NAME}/test.txt
Case 3.結果ログ(一部抜粋) 約14秒間隔でリトライがされていることが確認できる(minimumBackoff: 10s に合致)
LEVEL: WARNING
NAME: eventarc-retry-test
TIME_UTC: 2026-01-10 08:00:54.610
LOG: The request was not authenticated. Either allow unauthenticated invocations or set the proper Authorization header.

LEVEL: WARNING
NAME: eventarc-retry-test
TIME_UTC: 2026-01-10 08:01:08.351
LOG: The request was not authenticated. Either allow unauthenticated invocations or set the proper Authorization header.

LEVEL: WARNING
NAME: eventarc-retry-test
TIME_UTC: 2026-01-10 08:01:22.297
LOG: The request was not authenticated. Either allow unauthenticated invocations or set the proper Authorization header.

検証結果

以下記載のように、想定通りの結果となった。

ケース 再送発生
Case 1 なし
Case 2 なし
Case 3 あり

検証結果から分かったこと

検証の結果、RETRY_POLICY_DO_NOT_RETRY(デフォルト設定)の状態では、関数内で発生したエラーに対して Pub/Sub からの再送は発生しないことが確認できた。

つまり、元の要件「アプリケーション失敗時のリトライを0回にしたい」は、gcloud functions deploy のデフォルト設定(--retry フラグなし)で達成されている

ただし、インフラレイヤのエラーについては別途対処が必要となる。

インフラレイヤのエラーへの対処

インフラレベルのエラー(認証設定ミス、インスタンス起動失敗等)による再送で重複処理を防ぎたい場合は、イベント年齢チェックや DLQ が有効とされる。

ただし、これは正常運用時には発生しないエラーであり、「アプリ失敗時のリトライ0回」という要件とは異なる観点になる。

観点 対象 対処法
アプリ失敗時のリトライ防止 関数内 Exception 等 デフォルト設定で対処済み
インフラ障害時の重複処理防止 認証エラー、起動失敗等 イベント年齢チェック or DLQ

参考①: イベント年齢チェックの実装例

インフラレベルのエラー対策として実装する場合の参考コード
from datetime import datetime, timezone
from dateutil import parser
import functions_framework
from cloudevents.http import CloudEvent

@functions_framework.cloud_event
def handle_gcs_event(cloud_event: CloudEvent):
    # イベント発生時刻を取得
    timestamp = cloud_event["time"]
    event_time = parser.parse(timestamp)
    
    # イベント年齢を計算(秒)
    event_age = (datetime.now(timezone.utc) - event_time).total_seconds()
    event_age_ms = event_age * 1000
    
    # 閾値(例: 10秒)を超えたらドロップ
    max_age_ms = 10000
    if event_age_ms > max_age_ms:
        print(f"Dropped {cloud_event['id']} (age {event_age_ms}ms)")
        return  # ACKして処理をスキップ
    
    # 通常の処理
    print(f"Processed {cloud_event['id']} (age {event_age_ms}ms)")

参考: イベント駆動型関数の再試行を有効にする

参考②:デッドレターキュー(DLQ)の活用

DLQ を設定してリトライ回数を最小化する方法も存在する。
ただし、Pub/Sub の仕様上 max-delivery-attempts の最小値は 5回 であり、1回にすることはできない。

DLQの設定コマンド
# DLQ を設定(最小5回試行後に退避)
gcloud pubsub subscriptions update <SUBSCRIPTION_ID> \
  --dead-letter-topic=projects/<PROJECT_ID>/topics/<DEAD_LETTER_TOPIC> \
  --max-delivery-attempts=5

参考: メッセージ エラーの処理

まとめ

項番 ポイント 内容
1 リトライの構成 Eventarc Standard(Pub/Sub)と Cloud Run functions では、それぞれ独立したリトライの設定がある
2 Cloud Run functions の設定 RETRY_POLICY_DO_NOT_RETRY により、関数内エラーでもランタイムが 200 OK と変換し再送されない
3 Eventarc(Pub/Sub) の設定 インフラレイヤのエラー認証エラー等は retryPolicy の影響を受けず、再送が発生する
4 インフラエラーへの対処 イベント年齢チェックや DLQ で対処できる

参考ドキュメント

3
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
3
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?