1.はじめに
APIやマイクロサービスを呼び出す際、レート制限による429エラー(Too Many Requests)や一時的なサーバーエラー(500系)に遭遇することがあります。
こうした一時的な障害を乗り越えるために、Google Cloud Workflowsにはリトライ機能を設定することが可能です。
本記事では、Workflowsのリトライ設定を実装し、実際に429エラーが発生した場合にも対応できるハンズオンを通じて、具体的な設定方法とその動作を確認していきます。
2.ハンズオン
2.1.前提
2.1.1.実行環境
- 実行環境:Google Cloud Shell
2.1.2.事前準備
- Google Cloudプロジェクトの作成がされていること
2.1.3.本構成の説明
- このハンズオンでは、以下のような構成でリトライ機能を検証する
項番 | リソース名 | 役割 |
---|---|---|
1 | Cloud Run | 429エラーを返すエンドポイントを想定した Cloud Run |
2 | Workflows | リトライロジックを実装した Workflows |
2.2.環境設定
- 環境設定 の YOUR_PROJECT_ID="あなたのプロジェクトID" の箇所は、ご自身の実際のプロジェクトIDに置き換えてください。
# 環境変数
YOUR_PROJECT_ID="あなたのプロジェクトID"
# Google Cloud CLIログイン確認
gcloud auth list
# プロジェクト設定
gcloud config set project $YOUR_PROJECT_ID
2.3.サービスアカウント作成
# ワークフロー用サービスアカウント作成
gcloud iam service-accounts create workflow-retry-sa --display-name="Workflow Retry Service Account"
# 権限付与(Cloud Run呼び出し用)
gcloud projects add-iam-policy-binding $YOUR_PROJECT_ID \
--member="serviceAccount:workflow-retry-sa@$YOUR_PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/run.invoker"
# 権限付与(ログ書き込み用)
gcloud projects add-iam-policy-binding $YOUR_PROJECT_ID \
--member="serviceAccount:workflow-retry-sa@$YOUR_PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/logging.logWriter"
2.4.Cloud Run作成(429エラー返却用)
- デプロイ完了後、CloudRunのURLが表示されるのでメモする(次のステップで利用)。
- この設定はテスト目的のため、誰でもアクセス可能な状態です。本番環境では –no-allow-unauthenticated を指定し、IAMなどを利用した適切な認証を設定してください。
# フォルダ作成
mkdir -p ~/cloudrun-test && cd ~/cloudrun-test
# package.json作成
cat > package.json << EOF
{
"name": "cloudrun-429-test",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"express": "^4.17.1"
}
}
EOF
# index.js作成(429エラーを返すサービス)
cat > index.js << EOF
const express = require('express');
const app = express();
app.use(express.json());
app.post('/', (req, res) => {
// 常に429エラーを返す
res.status(429).json({
error: 'Too Many Requests',
message: 'Rate limit exceeded'
});
});
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(\`Server running on port \${PORT}\`);
});
EOF
# Dockerfile作成
cat > Dockerfile << EOF
FROM node:18-slim
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . ./
CMD [ "node", "index.js" ]
EOF
# ビルドとデプロイ
gcloud builds submit --tag gcr.io/${YOUR_PROJECT_ID}/cloudrun-429-test
gcloud run deploy cloudrun-429-test \
--image gcr.io/${YOUR_PROJECT_ID}/cloudrun-429-test \
--platform managed \
--region us-central1 \
--allow-unauthenticated
2.5.Workflowsファイル作成
url: "https://cloudrun-429-test-xxxxxxxx-uc.a.run.app
部分を先ほど作成した、Cloud RunのURLにしてファイルを作成
# ワークフローディレクトリ作成
mkdir -p ~/retry-workflow && cd ~/retry-workflow
cat > workflow.yaml << 'EOF'
main:
params: [input]
steps:
# リトライポリシー設定
- init:
assign:
- retry_policy:
predicate: ${retry_predicate} # リトライ判定を行うサブルーチンを指定
max_retries: 5 # 最大リトライ回数
backoff:
initial_delay: 2 # 初回リトライまでの待機時間 (秒)
max_delay: 60 # 最大待機時間 (秒)
multiplier: 2 # 待機時間に乗算する係数 (指数バックオフ)
# CloudRun呼び出し部分
- call_cloud_run:
try:
call: http.post # HTTP POSTリクエストを実行
args:
url: "https://cloudrun-429-test-xxxxxxxx-uc.a.run.app" # ★★★ ご自身のCloud Run URLに置き換えてください ★★★
body: ${input} # ワークフローの入力データをリクエストボディに設定
auth:
type: OIDC # OIDC認証を使用 (Workflowsのサービスアカウントを利用)
result: api_response # 成功時のレスポンスを変数 api_response に格納
retry: ${retry_policy} # initステップで定義したリトライポリシーを適用
# 最終結果を返す
- return_result:
return: ${api_response} # Cloud Runからのレスポンスをワークフローの出力として返す
# リトライ判定関数 (サブルーチン)
retry_predicate:
params: [error] # http.post が失敗した場合のエラー情報を受け取る
steps:
# エラーコードに基づいてリトライするかどうかを判断
- check_error_code:
switch: # 条件分岐
- condition: ${error.code == 429} # エラーコードが 429 (Too Many Requests) の場合
next: log_and_retry # log_and_retry ステップへ進む
- condition: ${error.code >= 500 and error.code < 600} # エラーコードが 5xx (Server Error) の場合
next: log_and_retry # log_and_retry ステップへ進む
# 上記以外のエラーコードの場合
- condition: true
return: false # リトライしない (false を返す)
# リトライする場合のログ出力
- log_and_retry:
call: sys.log # ログを出力
args:
text: "リトライします" # ログメッセージ
severity: "INFO" # ログレベル
next: return_true # return_true ステップへ進む
# リトライ実行を指示
- return_true:
return: true # リトライする (true を返す)
EOF
2.6.Workflows作成・デプロイ
# コマンド実行
gcloud workflows deploy retry-test-workflow \
--source=workflow.yaml \
--service-account=workflow-retry-sa@$YOUR_PROJECT_ID.iam.gserviceaccount.com \
--location=us-central1
2.7.Workflow実行
2.7.1.実行コマンド
# Workflows実行コマンド
gcloud workflows run retry-test-workflow \
--location=us-central1 \
--data='{}'
2.7.2.レスポンス
- 上記コマンドが実行されると以下のようなレスポンスが返ってくる。
- 実行時間が約65秒あることからもリトライが行われたことが推測できる。
項番 | 項目 | 値 | 説明 |
---|---|---|---|
1 | 実行状態 (state) | FAILED | ワークフローの実行がエラーにより失敗したことを示す |
2 | エラーコード (error.code) | 429 | 呼び出し先のHTTPサーバー(Cloud Run)が「Too Many Requests」エラーを返す |
3 | エラーメッセージ (error.payload.body.message) | "Rate limit exceeded" | Cloud Runサービス側で設定されたリクエスト数のレート制限を超えたため、リクエストが拒否されました。 |
4 | エラー発生ステップ (error.context) | call_cloud_run (main ルーチン内) | Cloud Runを呼び出す call_cloud_run ステップでエラーが発生。 |
5 | 実行時間 (duration) | 65.177827083s | ワークフロー全体の実行時間。リトライ処理(最大5回、バックオフあり)を試みた合計の時間 |
2.8.ログでの確認
gcloud logging read "resource.type=workflows.googleapis.com/Workflow AND resource.labels.workflow_id=retry-test-workflow" \
--project=$YOUR_PROJECT_ID \
--format=json \
--limit=10
- 以下のような
リトライします
というログメッセージが5回出力されており、設定した max_retries: 5 に基づきリトライが実行されたことがわかります。
Workflowsのバックオフ設定 (initial_delay: 2, multiplier: 2) により、リトライ間の待機時間は理論上 2秒、4秒、8秒、16秒、32秒 と指数関数的に増加します。
実際のログのタイムスタンプ(例:12:58:27 → 12:58:30 (+3秒) → 12:58:35 (+5秒) …)では、Workflows内部の処理時間などの影響で若干のずれがありますが、待機時間が増加している傾向が確認できます。
{
"insertId": "n2jdnyf80r3bx",
"labels": {
"execution_id": "c3b1d5af-7f15-4f48-be8e-11cd729c215b",
"revision_id": "000004-030"
},
"logName": "projects/pdf-analyzer-20250405/logs/Workflows",
"receiveTimestamp": "2025-05-01T12:52:16.783674098Z",
"resource": {
"labels": {
"location": "us-central1",
"resource_container": "{プロジェクトID}",
"workflow_id": "retry-test-workflow"
},
"type": "workflows.googleapis.com/Workflow"
},
"severity": "INFO",
"textPayload": "リトライします",
"timestamp": "2025-05-01T12:52:16.783674098Z"
}
3.おわりに
3.1.得られた知見
- 定型でリトライするのではなく、指数バックオフを使用することで負荷分散の設計を考慮すること
- 特定のエラーコード(429、500系)に限定してリトライすることで無駄なリトライの防止
3.2.今後の課題
- 継続的なエラーが発生した場合、一定期間リクエストを停止する機能の追加(サーキットブレーカーパターン)
- エラー通知やメッセージキューへの格納など