- 1 1. なぜ Express Workflow か
- 2 2. Express Workflow アーキテクチャ全体像
- 3 3. Express vs Standard 完全比較 — コスト試算 1,000 万実行/月
- 4 4. 同期呼出パターン (StartSyncExecution)
- 5 5. 非同期呼出 + At-least-once 重複対策
- 6 6. CloudWatch Logs 設計
- 7 7. 実戦 3 パターン
- 8 8. まとめ
1. なぜ Express Workflow か

- 自ワークロードの月次実行回数・実行時間から Express vs Standard の月次コスト差を即算出できる (§3)
- 同期呼出 (StartSyncExecution) と非同期呼出 (StartExecution) を 5 判断軸で選択し、API GW / EventBridge 統合を実装できる (§4/§5)
- Express の At-least-once 配信特性を理解し、SQS FIFO / DynamoDB ConditionExpression / Idempotency Key で冪等性を確保できる (§5)
- CloudWatch Logs の ALL/ERROR/OFF を本番コストとトレードオフで選択できる (§6)
- 実戦 3 パターン (API GW+同期 / EventBridge+非同期 / SQS FIFO 冪等) を 30 分以内に PoC 立ち上げできる (§7)
- SF 基本構文 (State Machine / Task / Choice) → SF 入門
- Retry / Catch / Timeout 詳細 → Retry/Catch/Timeout 完全ガイド
- Express vs Standard 基礎比較 → Express vs Standard 基礎比較 (本記事の前提)
- InputPath / Parameters / ResultPath → Vol1 5大入出力フィルタ
- waitForTaskToken Callback → Vol2 Callback 実践
- Distributed Map 本番運用 → Vol3 Distributed Map 本番運用
1-1. 本記事のゴール
本記事は Step Functions 実践編 Vol4 として、Express Workflow を本番の高頻度ワークロードで運用する際に必要な 5 つの設計判断軸を提供します。
Terraform HCL + ASL + CLI 実挙動の 3 点セットで、「どのワークロードで Express をどう呼ぶか」を再現可能な形で解説します。読了後、1000万実行/月規模のワークロードに Express を採用すべきかを即断できる基準と、採用後の運用設計指針が揃います。
読了後に自力で実行できる 5 つのアウトカムを示します。
- 自ワークロードの月次実行回数・平均実行時間を入力して Express vs Standard のコスト差を即算出し、経営層への説明資料を作成できる
- 同期呼出 (
StartSyncExecution) と非同期呼出 (StartExecution) を 5 判断軸で選択し、API Gateway 統合・EventBridge 統合の双方を Terraform で実装できる - Express の At-least-once 配信特性を把握し、SQS FIFO + DynamoDB ConditionExpression で重複実行を防ぐ設計を実装できる
- CloudWatch Logs のレベル (ALL/ERROR/OFF) をコスト試算付きで選択し、月次ログ量を事前試算できる
- 実戦 3 パターン (A: API GW+同期 / B: EventBridge+非同期 / C: SQS FIFO+冪等性) を 30 分以内に PoC 立ち上げできる
本記事が解決する課題を改善前後で整理した対比表を示します。
| 改善前 | 本記事による改善 |
|---|---|
| Express と Standard のコスト差を月次換算できない | §3 で 3 シナリオの実算円 (1000万/月) を提示 |
| API GW 29秒制約を見落として本番タイムアウト | §4 で同期/非同期の判断フロー + 29秒対策パターン |
| At-least-once を軽視して決済・在庫の重複処理事故 | §5 で冪等性 3 手法 (SQS FIFO/DDB Condition/Key) |
| CW Logs ALL 設定で費用が月 6 桁に | §6 でログ量試算 + ALL/ERROR/OFF の選択基準 |
| Express 構成の PoC に 1 週間以上かかる | §7 実戦 3 パターンの Terraform で 30 分 PoC |
1-2. 読者像
| 前提知識 | 必要度 |
|---|---|
| SF 入門 (State Machine / Task の基本構文) | ✅ 必須 |
| Express vs Standard の基礎比較 (ID:1101 相当) | ✅ 必須 |
| 高頻度 API / イベント駆動ワークロードを設計する立場 | ✅ 想定 |
| Terraform で AWS リソースを構築できる | ✅ 推奨 |
| At-least-once / 冪等性設計の基礎 | △ §5 で補足 |
1-3. なぜ今 Express 高頻度運用か
SF 実践編は Vol1 (I/O フィルタ) → Vol2 (Callback) → Vol3 (Distributed Map) と積み上げてきました。Vol4 で Express の高頻度本番運用を取り上げる理由は 3 点あります。
① コスト差が大きい: API バックエンドや IoT イベント処理で 1000 万実行/月を超えると、Standard の state transition 課金は月数十万円に達します。Express は同条件で数千円〜数万円に収まるケースがあります (§3 で実算)。
② At-least-once への対処が省かれがち: Express は exactly-once を保証しません。冪等性設計を後回しにすると決済・在庫・通知で重複事故が起きます。本番投入前に必ず設計してください (§5 QG-2)。
③ CloudWatch Logs コストの見落とし: ALL モードで 1000 万実行/月を流すと Logs だけで月 6 桁を超えることがあります。設定 1 行の差が大きな費用差を生みます (§6 QG-3)。
SF 実践編シリーズ全体の構成と Vol4 の位置付けを下表に整理します。
| Vol | WP ID | テーマ | ワークフロータイプ |
|---|---|---|---|
| Vol1 (5大入出力フィルタ) | 1439 | InputPath/OutputPath/Parameters/ResultPath/Context Object | Standard |
| Vol2 (Callback 実践) | 1449 | waitForTaskToken による外部システム連携の非同期境界 | Standard |
| Vol3 (Distributed Map) | 1488 | 大規模並列処理・Parent-Child 実行モデル・Tolerated Failure 設計 | Standard + Express child |
| Vol4 (本記事) | — | Express 高頻度運用 — 5判断軸・3実戦・冪等性 | Express 専用 |
Express は Standard と実行モデルが根本的に異なるため、Vol4 は Express 固有の設計課題のみに特化します。
Vol1〜Vol3 の設計パターンを前提に、Express 高頻度本番運用の設計指針を提供します。
1-4. 既存 ID:1101 との差別化 6 点
- (a) コスト試算の深さ: ID:1101 は単価比較のみ。本記事は 1000万実行/月の 3 シナリオで実算円を提示し経営層説明資料に使える
- (b) 同期呼出の網羅性: StartSyncExecution + API GW 29 秒制約対策 + Cold Start を独立章で解説 (ID:1101 未扱い)
- (c) 非同期 + 冪等性: At-least-once 重複事故 3 類型 + 冪等性 3 手法を実装コードで提示 (ID:1101 未扱い)
- (d) CW Logs 設計表: ALL/ERROR/OFF のログ量試算 + 費用膨張事故例 (ID:1101 未扱い)
- (e) 実戦 3 パターン: API GW+同期 / EventBridge+非同期 / SQS FIFO 冪等の Terraform 完全実装
- (f) 導線設計: ID:1101「基礎比較」→ 本記事「本番運用深掘り」の学習階層を明示
1-5. 関連記事
| 記事 | ID | 役割 |
|---|---|---|
| AWS Step Functions 入門 | 1033 | SF 基礎構文の前提 |
| Retry/Catch/Timeout 完全ガイド | 1057 | §5 冪等性と Catch の境界 |
| Express vs Standard 基礎比較 | 1101 | 本記事の前提 |
| 5 大入出力フィルタ実践編 Vol1 | 1439 | §4 input transformer 連携 |
| Callback パターン実践編 Vol2 | 1449 | §5 非同期と Callback の使い分け |
| Distributed Map 本番運用 Vol3 | 1488 | §3 Express child の使い分け |
1-6. 執筆方針
- Terraform HCL + ASL + CLI の 3 点セット: IaC で再現可能な設計を提供します
- 2026-04 時点の公式料金準拠: 料金数値は時点注記あり。最新値は公式料金ページで確認してください
- コスト試算式の明示: 自ワークロードに代入して検算できる計算式を提示します
前提環境 (本記事の実機確認環境・2026-04 時点)
| ツール | バージョン |
|---|---|
| Terraform | 1.9.x |
| hashicorp/aws provider | ~> 5.0 |
| AWS CLI | 2.x |
| 確認リージョン | ap-northeast-1 (本記事例) |
各実戦パターン (§7) には terraform init && terraform apply でプロビジョニングできる
Terraform コードと、動作確認に使う CLI コマンドをセットで提供します。
コスト試算に用いる料金は最新の公式ページ (Step Functions 料金) でご確認ください。
2. Express Workflow アーキテクチャ全体像

2-1. 実行モデル
Express と Standard の最大の違いは実行モデルにあります。
| 観点 | Express | Standard |
|---|---|---|
| 実行履歴の保持 | なし (CloudWatch Logs に出力) | あり (90 日保持・コンソールで参照可) |
| 最大実行時間 | 5 分 | 1 年 |
| 実行保証 | At-least-once | Exactly-once |
| 課金モデル | リクエスト + duration + CW Logs | State Transition 回数 |
| ネストの実行タイプ | Express / Standard 子に可 | Standard 子のみ |
Express は実行履歴を Step Functions の内部ストレージに保持しません。代わりに CloudWatch Logs へ状態遷移ログを出力します。デバッグは CW Logs 経由が基本となります。
Express で実行状態を確認する主な 3 手段を整理します。
| 確認手段 | コマンド / 操作 | 取得できる情報 |
|---|---|---|
| DescribeExecution API | aws stepfunctions describe-execution --execution-arn <ARN> | 最終ステータス (RUNNING/SUCCEEDED/FAILED/TIMED_OUT) + executionArn |
| CW Logs Insights | コンソール or CLI でクエリ | 状態遷移ログ全体・エラー詳細・実行時間 |
| CloudWatch メトリクス | ExecutionsFailed / ExecutionsSucceeded 他 | 集計値でのモニタリング・アラーム設定 |
CW Logs Insights での execution 逆引きクエリ例:
fields @timestamp, @message
| filter executionArn = "arn:aws:states:ap-northeast-1:123456789:execution:my-express:exec-id"
| sort @timestamp asc
このクエリで特定 execution の全状態遷移を時系列に表示できます。
エラー調査は @message 内の errorMessage フィールドを起点に原因特定します。
2-2. Express の制約
| 制約 | 値 | 設計上の対策 |
|---|---|---|
| 最大実行時間 | 5 分 | 超過する場合は Standard か Express 子 + Standard 親 |
| 入力ペイロード上限 | 256 KB | S3 参照パターンで回避 |
| 実行履歴参照 | 不可 (CW Logs のみ) | Logs Insights で execution ID 逆引き |
| Retry 課金 | Retry 中も duration 課金が継続 | Retry は最小限・Catch で代替経路 |
| 同時実行 TPS | アカウントデフォルト 1000 TPS | Service Quota で緩和申請可 |
5 分制限は Express の最重要制約です。「処理が 5 分を超える可能性はないか」を設計段階で必ず確認してください。超えそうなら Standard か、Express 子 + Standard 親の Multi-layer パターンを採用します。
5 分制限の設定例 (State Machine レベルの ASL)
{
"Comment": "Express Workflow — State Machine レベルの TimeoutSeconds",
"TimeoutSeconds": 300,
"StartAt": "ProcessRequest",
"States": {
"ProcessRequest": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "${function_name}",
"Payload.$": "$"
},
"Catch": [{ "ErrorEquals": ["States.ALL"], "Next": "HandleError" }],
"End": true
},
"HandleError": { "Type": "Fail", "Error": "ProcessingFailed" }
}
}
TimeoutSeconds は State Machine レベル (上記) と個別 Task レベルの両方に設定できます。
Task レベルでは HeartbeatSeconds も設定でき、Lambda がハートビートを返さない場合のタイムアウトを制御できます。
Catch を省略すると 5 分タイムアウト時のエラーが上位に伝播するため、States.ALL で必ず代替経路を用意します。
2-3. 課金モデル詳細
Express の課金は 3 要素の合算です (2026-04 時点。最新値は Step Functions 料金ページ を参照)。
Express 総コスト = リクエスト料金 + 実行時間料金 + CloudWatch Logs 料金
| 要素 | 単価 | 計算単位 |
|---|---|---|
| リクエスト | $1.00 / 100万実行 | 実行 1 回ごと |
| 実行時間 | $0.00001667 / GB-秒 | 64 MB 固定・100ms 単位切り上げ |
| CW Logs 収集 | $0.50 / GB | ALL≈20KB/実行・ERROR≈5KB/実行 |
| CW Logs 保存 | $0.03 / GB-月 | 保持期間 × ログ量 |
Express のメモリは 64 MB 固定 (Lambda と異なり変更不可)。duration GB-s の計算式:
GB-s = 実行回数 × 実行時間(秒, 100ms 切り上げ) × 0.0625 (64MB/1024)
Standard の課金は state transition 単価 × 遷移回数。
Standard コスト = 実行回数 × ステート数 × $0.000025
計算例 (1000万実行/月・平均実行時間 200ms・CW Logs ERROR モード)
リクエスト料金:
1000万 ÷ 100万 × $1.00 = $10.00
Duration 料金:
1000万 × 0.2秒 × 0.0625 = 125,000 GB-s
125,000 × $0.00001667 = $2.08
CW Logs 料金 (ERROR モード: 約 5KB/実行):
1000万 × 5KB = 50,000MB ≈ 48.8GB
ingestion: 48.8 × $0.50 = $24.4
storage (1ヶ月): 48.8 × $0.03 = $1.5
Express 合計: $10.00 + $2.08 + $24.4 + $1.5 = $38.0/月 ≈ 5,700円 ($1=150円換算)
この計算例を 3 シナリオ (高頻度 API / バッチ / イベント駆動) に展開した Standard との比較が §3 です。
2-4. サポートする統合パターン
Express は以下のトリガー元から呼び出せます。
| 統合元 | 呼出タイプ | 代表ユースケース |
|---|---|---|
| API Gateway REST | 同期 (StartSyncExecution) | REST API バックエンド |
| API Gateway HTTP | Lambda 経由で非同期 | 低コスト非同期トリガー |
| EventBridge | 非同期 (StartExecution) | イベント駆動処理 |
| SQS | 非同期 (Lambda トリガー / Pipes) | キュー消費・冪等処理 |
| Lambda | 同期 / 非同期 | SDK 直接呼出 |
| SDK Direct Integration | 同期 / 非同期 | Vol5 (cmd_012) で詳解予定 |
API Gateway REST 統合での注意点
REST API は StartSyncExecution を直接バックエンドとして設定できます。
ただし API GW のタイムアウト制限 29 秒があるため、Express の 5 分以内でも
HTTP レスポンスを返せるのは 29 秒以内です。この制約と対策は §4-3 で詳述します。
EventBridge からの非同期トリガー (Terraform 例)
resource "aws_cloudwatch_event_target" "sf_express" {
rule= aws_cloudwatch_event_rule.my_rule.name
target_id = "ExpressWorkflowTarget"
arn = aws_sfn_state_machine.express.arn
role_arn = aws_iam_role.eventbridge_sf.arn
}
EventBridge rule の Targets に SF State Machine ARN を設定するだけで非同期トリガーが完成します。
SQS からの非同期トリガーは Lambda Event Source Mapping 経由で StartExecution を呼び出します。
SQS FIFO + 冪等性設計の詳細は §5-6 / §7-C パターン C で解説します。
2-5. At-least-once 配信特性
Express はネットワーク障害やシステム再起動時に、同一入力で複数回実行される可能性があります。
Standard の Exactly-once と異なり、決済・在庫更新・メール送信など「1 回だけ実行すべき処理」には冪等性設計が必須です。
具体的な対策は §5 で解説します。
At-least-once が発生する主なシナリオ:
| シナリオ | 原因 |
|---|---|
| SQS の再配信 | Lambda 処理中に可視性タイムアウト超過 → 同一メッセージが再配信 |
| StartExecution 再試行 | ネットワーク分断でレスポンス未着 → クライアントが再送 |
| EventBridge 再配信 | Event Bus の At-least-once 保証に基づく再試行 |
§5 では SQS FIFO + MessageDeduplicationId / DynamoDB ConditionExpression / Idempotency Key の 3 手法で対策します。
2-6. エラー境界の設計方針
Express では Retry ステート中も duration 課金が継続します。このため Catch 設計は以下を基本とします。
- Retry は最小限: 一時的なスパイク (Lambda スロットリング等) のみ短い Retry を設定
- Catch で代替経路: 失敗時は SQS DLQ 送信 / DynamoDB エラー記録で後続処理
- 親 Standard が再投入: Express を子として Standard 親が管理する場合、失敗後の再起動は親側で処理
{
"ProcessOrder": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Retry": [{
"ErrorEquals": ["Lambda.TooManyRequestsException"],
"IntervalSeconds": 1,
"MaxAttempts": 2,
"BackoffRate": 2.0
}],
"Catch": [{
"ErrorEquals": ["States.ALL"],
"Next": "SendToDLQ",
"ResultPath": "$.error"
}],
"Next": "Done"
}
}
2-7. 同時実行・スループット設計
Express の同時実行上限は Service Quota で管理されています。高頻度ワークロードでは事前に Quota 確認と緩和申請が必須です。
| Service Quota | デフォルト値 | 緩和申請 | 備考 |
|---|---|---|---|
| Express の同時実行数 | 1,000 | 可 (無制限ではない) | バースト時はキューイングで平滑化 |
| StartExecution API TPS | 2,500 | 可 | アカウント全体の上限 |
| StartSyncExecution TPS | 2,500 | 可 | 同上 |
| 入力ペイロード最大 | 256 KB | 不可 | S3 参照パターンで回避 |
スループット設計の 3 原則:
- SQS バッファリング: Lambda が SQS をポーリングし
BatchSize=1またはBatchSize=10で StartExecution を呼び出す。キューがスパイクを吸収し、TPS 超過を防ぐ - 同時実行上限の事前申請: 本番リリース 2 週間前に Service Quota 緩和申請を提出する。承認には 5〜10 営業日かかることがある
- ConcurrentExecutions アラーム: CloudWatch で
ThrottledEventsメトリクスを監視し、スロットリング発生時に Slack / SNS 通知を受け取る設定をする
# Service Quota 現在値確認
aws service-quotas get-service-quota--service-code states--quota-code L-1DF17E3--region ap-northeast-1
# AWS環境での実機取得値
# ThrottledEvents メトリクス確認 (直近 1 時間)
aws cloudwatch get-metric-statistics--namespace AWS/States--metric-name ThrottledEvents--dimensions Name=StateMachineArn,Value=arn:aws:states:ap-northeast-1:123456789012:stateMachine:sf-express-vol4--start-time $(date -u -v-1H +%Y-%m-%dT%H:%M:%SZ)--end-time $(date -u +%Y-%m-%dT%H:%M:%SZ)--period 300 --statistics Sum
# AWS環境での実機取得値
2-8. 5 判断軸の全体像 (§3〜§8 の設計順序)
本記事が提示する 5 判断軸は、以下の順序で確認することを想定して章を構成しています。
| 軸 | 問い | 解説節 | 判断結果の例 |
|---|---|---|---|
| ① コスト試算 | Express で本当に安くなるか | §3 | 月次 1000万実行 → Express が Standard 比 20 倍安 |
| ② 呼出タイプ | 同期 / 非同期どちらか | §4 | API バックエンド → StartSyncExecution / 29秒制約確認 |
| ③ 冪等性設計 | 重複実行で損害が出るか | §5 | 決済処理 → SQS FIFO + DDB ConditionExpression 必須 |
| ④ ログ戦略 | ALL / ERROR / OFF どれか | §6 | 高頻度 API → ERROR 固定・予算アラームとセット |
| ⑤ 実戦パターン | 呼出元は何か | §7 | EventBridge → パターン B 非同期 Terraform 実装 |
この順序で設計を進めると、コスト合意→アーキテクチャ選択→リスク対策→運用設計→実装という自然な流れになります。設計フェーズで「§3 → §4 → §5 → §6 → §7」の順に確認していくと、後戻りが最小化されます。
3. Express vs Standard 完全比較 — コスト試算 1,000 万実行/月
3-1. 比較 5 軸: Express と Standard の根本的な違い
Express と Standard は同じ Step Functions サービスでありながら、課金モデル・実行履歴・再実行性など根本設計が異なる。以下 5 軸で整理し、§3-2 以降のコスト試算の前提を固める。
| 比較軸 | Express Workflow | Standard Workflow | 設計ポイント |
|---|---|---|---|
| 最大実行時間 | 5 分 | 1 年 | 5 分超えは Standard 一択 |
| 課金モデル | リクエスト数 + duration (GB-s) + CW Logs | state transition 数 × 単価 | 高頻度・短時間は Express が有利 |
| 実行履歴 | CloudWatch Logs のみ (コンソール非表示) | コンソール + API (90 日保持) | 監査証跡が必要なら Standard |
| 再実行保証 | At-least-once (重複実行あり) | Exactly-once (重複なし) | 冪等性設計が不可なら Standard |
| CW Logs | 必須 (実行記録の代替) | 任意 | §6 のコスト設計が重要 |
Express は「高頻度・短時間・コスト最小」設計。Standard は「長時間・監査ログ・厳密な実行保証」設計。5 分を超えるワークロードは Standard 一択となる。また Express の At-least-once 特性は冪等性設計を必須とする点も重要な差異 (§5 詳解)。
3-2. コスト試算式を正確に理解する
試算前に 2026-04 時点の公式料金体系を確認する。最新料金は AWS Step Functions 料金ページ で確認すること (2026-04 時点値)。
Express Workflow 課金モデル (2026-04 時点・us-east-1)
月次コスト($) =
実行回数 ÷ 1,000,000 × 1.00 # リクエスト料金
+ 実行回数 × 実行時間(s, 100ms 切り上げ) × 0.0625 × 0.00001667 # duration 料金
+ CW Logs 収集量(GB) × 0.50 # Logs ingestion
+ CW Logs 保存量(GB) × 0.03 # Logs storage (/月)
前提:
– メモリは 64 MB (= 0.0625 GB) 固定 (Lambda と異なり選択不可)
– duration は 100 ms 単位で切り上げ (201 ms → 300 ms として課金)
– CW Logs ERROR モード: 失敗した実行のみログが生成 (成功実行のログなし)
– CW Logs ALL モード: 全実行 (成功・失敗) のすべての状態遷移ログが生成
Standard Workflow 課金モデル (2026-04 時点・us-east-1)
月次コスト($) = 実行回数 × 1 実行あたりの state transition 数 × 0.000025
- state transition: 1 つの State (Task / Choice / Wait 等) に入るたびに 1 カウント
- 例: StartAt → TaskA → ChoiceState → TaskB → End = 4 transitions/実行
- duration 課金なし — 何時間実行し続けても transition 数のみ課金
3-3. シナリオ A: 高頻度 API (1 億 req/月・平均 200 ms・CW ERROR のみ)
ユースケース: API Gateway + Express 同期呼出 (§4 実戦パターン A)。ユーザーリクエストごとに軽量処理 (バリデーション → DynamoDB 書込の 3 state) を実行する高頻度 API。
前提条件:
| 項目 | 値 |
|---|---|
| 月次実行回数 | 1 億 (100,000,000) |
| 平均実行時間 | 200 ms (課金: 200 ms) |
| メモリ (固定) | 64 MB (0.0625 GB) |
| CW Logs モード | ERROR のみ (エラー率 0.1%・失敗実行のみログ生成) |
| state transitions/実行 | 3 (Choice → Task → End) |
Express コスト試算 (2026-04 時点):
リクエスト料金 = 100,000,000 ÷ 1,000,000 × $1.00
= $100.00
duration 料金= 100,000,000 × 0.2 s × 0.0625 GB × $0.00001667
= $20.84
CW Logs ERROR= 失敗実行数: 100,000,000 × 0.1% = 100,000 件
= 100,000 件 × 5 KB = 500 MB = 0.49 GB
= 0.49 GB × $0.50 + 0.49 GB × $0.03 = $0.26
─────────────────────────────────────────────────────────────
Express 合計 = $121.10/月 ≈ 約 18,165 円 (¥150/$ 換算)
ERROR モードの注意: CW Logs ERROR モードはログを生成するのは失敗した実行のみ。全実行 (1 億件) に 5 KB のログが発生するわけではない。エラー率 0.1% = 100,000 件のみログ対象。
Standard コスト試算 (参考: 高頻度ワークロードには非推奨):
state transition = 100,000,000 × 3 transitions × $0.000025
= $7,500.00/月 ≈ 約 1,125,000 円
判定: Express は Standard の 約 62 倍安い。高頻度 API に Standard を選ぶと月 100 万円規模のコスト差が生じる。Express 一択。
3-4. シナリオ B: バッチ (100 万 req/月・平均 30 秒・CW ALL)
ユースケース: 夜間バッチ処理 (データ変換 + S3 書込の 5 state)。Express の 5 分制限内 (30 秒 < 300 秒) だが、全実行ログを CW Logs ALL モードで記録してデバッグに備える構成。
前提条件:
| 項目 | 値 |
|---|---|
| 月次実行回数 | 100 万 (1,000,000) |
| 平均実行時間 | 30 秒 |
| メモリ (固定) | 64 MB (0.0625 GB) |
| CW Logs モード | ALL (全実行ログ・約 20 KB/実行) |
| state transitions/実行 | 5 (Task×3 + Choice + End) |
Express コスト試算 (2026-04 時点):
リクエスト料金 = 1,000,000 ÷ 1,000,000 × $1.00
= $1.00
duration 料金= 1,000,000 × 30 s × 0.0625 GB × $0.00001667
= $31.25
CW Logs ALL = 1,000,000 実行 × 20 KB = 20,000,000 KB ≈ 19.1 GB
= 19.1 GB × $0.50 + 19.1 GB × $0.03 = $10.13
─────────────────────────────────────────────────────────────
Express 合計 = $42.38/月 ≈ 約 6,357 円
Standard コスト試算:
state transition = 1,000,000 × 5 transitions × $0.000025
= $125.00/月 ≈ 約 18,750 円
判定: Express は Standard の 約 3 倍安い。CW Logs ALL の $10.13 が Express コストの約 24% を占め、duration $31.25 に次ぐ第 2 位の費用項目となっている (§6 詳解)。
注意: 同じバッチでも実行時間が 4 分に延びた場合、Express duration = 1,000,000 × 240 s × 0.0625 × $0.00001667 ≈ $250 となり、合計 $261 が Standard $125 を上回る。5 分制限に近づくほど Standard も比較検討すること。
3-5. シナリオ C: イベント駆動 (1,000 万 req/月・平均 2 秒・CW ERROR)
ユースケース: EventBridge + Express 非同期呼出 (§5 実戦パターン B)。IoT デバイスイベント・SNS Fanout 後処理等の中頻度イベント駆動ワークロード。
前提条件:
| 項目 | 値 |
|---|---|
| 月次実行回数 | 1,000 万 (10,000,000) |
| 平均実行時間 | 2 秒 |
| メモリ (固定) | 64 MB (0.0625 GB) |
| CW Logs モード | ERROR のみ (エラー率 0.1%・失敗実行のみログ生成) |
| state transitions/実行 | 3 |
Express コスト試算 (2026-04 時点):
リクエスト料金 = 10,000,000 ÷ 1,000,000 × $1.00
= $10.00
duration 料金= 10,000,000 × 2 s × 0.0625 GB × $0.00001667
= $20.84
CW Logs ERROR= 失敗実行数: 10,000,000 × 0.1% = 10,000 件
= 10,000 件 × 5 KB = 50,000 KB ≈ 0.048 GB
= 0.048 GB × $0.50 + 0.048 GB × $0.03 = $0.03
─────────────────────────────────────────────────────────────
Express 合計 = $30.87/月 ≈ 約 4,631 円
Standard コスト試算:
state transition = 10,000,000 × 3 transitions × $0.000025
= $750.00/月 ≈ 約 112,500 円
判定: Express は Standard の 約 24 倍安い。ERROR モードでは CW Logs コストがほぼゼロとなり、リクエスト + duration のみが支配的コストとなる。
3-6. シナリオ別推奨: Express か Standard かを 5 秒で判定
3 シナリオの試算をもとに判定基準を整理する。
| 判定軸 | Express 推奨 | Standard 推奨 |
|---|---|---|
| 実行時間 | ≤ 5 分 | > 5 分 (上限超) |
| 月次実行頻度 | 高頻度 (10 万/月以上) | 低頻度 (10 万/月未満) |
| state transition 数/実行 | 少 (≤ 5) | 多 (> 10) |
| 実行履歴の監査要件 | 不要 (CW Logs 代替可) | 必要 (コンソール/API) |
| Exactly-once 要件 | 設計で対応 (§5) | Exactly-once が必要 |
3 シナリオ比較サマリ (2026-04 時点・¥150/$ 換算):
| シナリオ | 推奨 | Express/月 | Standard/月 | コスト比 |
|---|---|---|---|---|
| A: 高頻度 API (1 億 req・200 ms・ERROR) | Express | $121 ≈ ¥18,165 | $7,500 ≈ ¥1,125,000 | 1/62 |
| B: バッチ (100 万 req・30 秒・ALL) | Express | $42 ≈ ¥6,300 | $125 ≈ ¥18,750 | 1/3 |
| C: イベント駆動 (1,000 万 req・2 秒・ERROR) | Express | $31 ≈ ¥4,650 | $750 ≈ ¥112,500 | 1/24 |
3 シナリオとも Express が有利。ただし実行時間が長くなるほど Standard との差が縮まる。シナリオ B で実行時間が 4 分を超えると逆転するケースがある。CW Logs を ALL モードで運用する場合は §6 のログ量試算を必ず実施すること。

3-7. コスト試算の 3 つの落とし穴
- 落とし穴①: CloudWatch Logs 量の見落とし (ALL モード課金過多)
CW Logs を ALL モードで記録すると、成功・失敗を問わず全実行でログが生成される (約 20 KB/実行)。ERROR モードと混同し「ログはほぼ無料」と判断するのが最も多い見積もり誤りである。シナリオ A (1 億実行) で ALL モードを選ぶと 1 億 × 20 KB ≈ 1,907 GB → $953 の収集料が発生し、Express のリクエスト $100 + duration $21 を合わせた本体コスト ($121) の約 8 倍になる。ERROR モードなら失敗実行のみログ (エラー率 0.1% → 100,000 件 × 5 KB ≈ $0.26) と極めて安価。本番では ERROR モードを基本とし、デバッグ時のみ ALL に切り替える運用を推奨する (§6 詳解)。 - 落とし穴②: state transition 数の過小評価 (Standard 選択時)
Standard の課金計算で「transition 数 = state 数」と誤解するケースが多い。実際には Map Iterator の各 iteration・Choice 分岐の各 branch 評価・Retry の再試行 1 回ごとに transition がカウントされる。例えば Map で 100 件を並列処理する State Machine (5 state) では、1 実行あたり 100 + 5 = 105 transitions 以上になる。「5 ステートのつもりが実質 105 transition」= 21 倍の課金差が生じる。Standard を選択する際は Map の concurrency 設定と Retry の MaxAttempts を踏まえた実際の transition 数で試算すること。 - 落とし穴③: duration 丸め誤差 (100 ms 単位切り上げ)
Express の duration は 100 ms 単位の切り上げ課金。平均 201 ms のワークロードは 300 ms として課金され、duration 料金が 1.5 倍になる。特に平均 10〜50 ms の軽量処理では「100 ms として課金される」= 実際の 2〜10 倍の duration コストが生じる。シナリオ A (1 億実行) で 200 ms → 300 ms 切り上げが全実行に及ぶと、duration $20.84 → $31.25 に増加する。Lambda のプロビジョニング済み同時実行と組み合わせ、P99 を 100 ms 境界以下に抑える設計を検討すること (§4-4 参照)。
2026-04 時点注記: 上記コスト試算は 2026-04-24 時点の AWS 公式料金 (us-east-1) に基づく。料金は予告なく変更されるため、実際のプロジェクト試算は AWS Step Functions 料金ページ で最新料金を確認すること。
4. 同期呼出パターン (StartSyncExecution)
Express Workflow を「同期」で呼び出す StartSyncExecution は、呼び出し元が実行完了を待ち、結果をそのままレスポンスとして受け取れる。API バックエンドとして Express を組み込む際の主要な選択肢だが、制約を理解しなければ本番で痛い目に遭う。
4-1. StartSyncExecution の特性と制約
StartSyncExecution は Standard Workflow の StartExecution と異なり、リクエストがブロックされた状態で State Machine が完了するまで待機し、実行結果 (output / status / error) を HTTP レスポンスとして返す。
| 項目 | 内容 |
|---|---|
| 最大実行時間 | 5 分 (Express 制約。超過時 TIMED_OUT) |
| ペイロード上限 | 入力 256 KB / 出力 256 KB (Lambda と同一) |
| 実行履歴 | CloudWatch Logs にのみ記録 (Step Functions コンソールの実行一覧には残らない) |
| 同時実行上限 | リージョンデフォルト 800 (ソフトリミット・緩和申請可) |
| 再試行 | Express は At-least-once。StartSyncExecution 自体はリクエスト単位で同期のため重複リスクは低いが、呼び出し元タイムアウト後の再送には注意 |
| 課金 | 実行リクエスト数 ($1/100万) + GB-s ($0.00001667) + CW Logs ingestion |
Standard Workflow では実行履歴を最大 90 日保持するが、Express では CloudWatch Logs が唯一の実行記録となる。logging_configuration の log_level を OFF にすると完全にブラックボックスになるため、本番では ERROR 以上を必ず設定すること。
4-2. API Gateway 統合パターン
Express Workflow を API Gateway 経由で同期呼び出しするパターンを Terraform + ASL + CLI の 3 点セットで示す。
Terraform — API GW REST + Express 統合
resource "aws_sfn_state_machine" "express_api" {
name = "express-api-sync"
type = "EXPRESS"
role_arn = aws_iam_role.sfn_exec.arn
definition = jsonencode({
Comment = "同期 API バックエンド — StartSyncExecution 想定"
StartAt = "ProcessRequest"
States = {
ProcessRequest = {
Type = "Task"
Resource = aws_lambda_function.processor.arn
TimeoutSeconds = 25
Retry = []
Catch = [{
ErrorEquals = ["States.ALL"]
Next = "HandleError"
ResultPath = "$.error"
}]
Next = "Success"
}
Success = {
Type = "Succeed"
}
HandleError = {
Type= "Fail"
Error = "ProcessingFailed"
Cause = "Lambda returned error"
}
}
})
logging_configuration {
log_destination = "${aws_cloudwatch_log_group.sfn_express.arn}:*"
include_execution_data = true
level= "ERROR"
}
tags = {
Module = "sf-express-vol4"
Env = "prod-sample"
}
}
resource "aws_cloudwatch_log_group" "sfn_express" {
name = "/aws/states/express-api-sync"
retention_in_days = 30
tags = {
Module = "sf-express-vol4"
}
}
resource "aws_iam_role" "sfn_exec" {
name = "sfn-express-api-exec"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "states.amazonaws.com" }
}]
})
}
resource "aws_iam_role_policy" "sfn_exec_policy" {
role = aws_iam_role.sfn_exec.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect= "Allow"
Action= ["lambda:InvokeFunction"]
Resource = [aws_lambda_function.processor.arn]
},
{
Effect= "Allow"
Action= ["logs:CreateLogDelivery", "logs:PutLogEvents",
"logs:GetLogDelivery", "logs:UpdateLogDelivery",
"logs:DeleteLogDelivery", "logs:ListLogDeliveries",
"logs:PutResourcePolicy", "logs:DescribeResourcePolicies",
"logs:DescribeLogGroups"]
Resource = "*"
}
]
})
}
# API Gateway → StartSyncExecution 統合
resource "aws_api_gateway_integration" "sfn_sync" {
rest_api_id = aws_api_gateway_rest_api.main.id
resource_id = aws_api_gateway_resource.execute.id
http_method = "POST"
integration_http_method = "POST"
type = "AWS"
uri= "arn:aws:apigateway:${var.aws_region}:states:action/StartSyncExecution"
credentials = aws_iam_role.apigw_sfn.arn
request_templates = {
"application/json" = jsonencode({
input = "$util.escapeJavaScript($input.body)"
stateMachineArn = aws_sfn_state_machine.express_api.arn
})
}
}
REST API vs HTTP API の差異
| 項目 | REST API | HTTP API |
|---|---|---|
| StartSyncExecution 直接統合 | 可 (AWS サービス統合) | 不可 (HTTP API は Lambda/HTTP エンドポイントのみ) |
| タイムアウト上限 | 29 秒 | 30 秒 |
| コスト | $3.5/100万 req | $1/100万 req |
| 推奨ユースケース | Express 同期・AWS サービス直統合 | Proxy 統合・低レイテンシ API |
Express を同期で呼び出すには REST API の AWS サービス統合を使う必要がある。HTTP API には StartSyncExecution 直接統合がないため、Lambda 経由で呼び出す迂回路が必要になる。
4-3. API GW 29 秒タイムアウト対策
API Gateway REST API の統合タイムアウト上限は 29 秒 (2024-10 以降の増加申請で最大 300 秒に緩和可能だが、デフォルトは 29 秒)。Express の最大実行時間は 5 分だが、API GW 経由で同期呼び出しすると 29 秒で強制的に 504 Gateway Timeout が返る。
対策 1 — 非同期化 + ポーリング: StartExecution (非同期) でキックし、実行 ARN をクライアントに返す → クライアントが GetExecutionHistory または describe-execution をポーリング。
対策 2 — async 統合タイプ: API GW の async フラグ付き統合で即座に 202 Accepted を返し、コールバック (waitForTaskToken) で完了通知を受け取る。
対策 3 — タイムアウト上限緩和申請: AWS サポートへ REST API 統合タイムアウト上限緩和を申請し、最大 300 秒に引き上げる (承認に数営業日)。
非同期化 + ポーリング の ASL 実装例
{
"Comment": "29秒超えワークロード向け非同期パターン",
"StartAt": "StartAsyncJob",
"States": {
"StartAsyncJob": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "start-job",
"Payload.$": "$"
},
"ResultPath": "$.jobResult",
"Next": "WaitForJob"
},
"WaitForJob": {
"Type": "Wait",
"Seconds": 5,
"Next": "CheckJobStatus"
},
"CheckJobStatus": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "check-job-status",
"Payload.$": "$.jobResult"
},
"ResultPath": "$.statusResult",
"Next": "IsJobDone"
},
"IsJobDone": {
"Type": "Choice",
"Choices": [{
"Variable": "$.statusResult.Payload.status",
"StringEquals": "SUCCEEDED",
"Next": "JobSucceeded"
}],
"Default": "WaitForJob"
},
"JobSucceeded": { "Type": "Succeed" }
}
}
4-4. Cold Start 考察
Express Workflow 自体の起動オーバーヘッドは 数ミリ秒〜数十ミリ秒程度で Lambda の Cold Start (数百ミリ秒) と比べると軽微。ただし、Express が呼び出す Lambda 関数の Cold Start は Express の実行時間に含まれるため、レイテンシ要件が厳しい場合は Lambda のプロビジョニング済み同時実行を設定する。
resource "aws_lambda_provisioned_concurrency_config" "processor" {
function_name= aws_lambda_function.processor.function_name
qualifier = aws_lambda_alias.processor_live.name
provisioned_concurrent_executions = 10
}
Express 自体の「状態保存なし」設計により、Standard のような実行履歴書き込みレイテンシ (数十〜数百ミリ秒) が発生しない点はメリット。高頻度シナリオで Standard から Express に移行するとレイテンシが大幅改善するケースがある。
4-5. describe-execution 実挙動ダンプ
成功・失敗・タイムアウトそれぞれの start-sync-execution レスポンス差異を示す。
# 成功時 — status: SUCCEEDED
aws stepfunctions start-sync-execution \
--state-machine-arn arn:aws:states:ap-northeast-1:123456789012:stateMachine:express-api-sync \
--input '{"orderId":"ORD-001","amount":1000}'
# AWS環境での実機取得値
{
"executionArn": "arn:aws:states:ap-northeast-1:123456789012:express:express-api-sync:...",
"stateMachineArn": "arn:aws:states:ap-northeast-1:123456789012:stateMachine:express-api-sync",
"name": "...",
"startDate": "2026-04-24T00:00:00.000Z",
"stopDate": "2026-04-24T00:00:00.523Z",
"status": "SUCCEEDED",
"input": "{\"orderId\":\"ORD-001\",\"amount\":1000}",
"output": "{\"result\":\"processed\",\"orderId\":\"ORD-001\"}"
}
# タイムアウト時 — status: TIMED_OUT (TimeoutSeconds 超過)
# AWS環境での実機取得値
{
"status": "TIMED_OUT",
"error": "States.Timeout",
"cause": "Execution timed out after 25 seconds"
}
# 失敗時 — status: FAILED
# AWS環境での実機取得値
{
"status": "FAILED",
"error": "ProcessingFailed",
"cause": "Lambda returned error"
}
4-6. 同期で 30 秒を超えるワークロードは非同期化すべき

「レスポンスを待てる時間」が 30 秒未満なら StartSyncExecution が適切。API GW 29 秒制約を含めると、実質的に 25 秒以内に完了できるワークロードだけを同期にすべき (バッファ 4 秒)。
- 画像リサイズ / 軽量 ML 推論 / 小規模データ変換 → 同期
- 外部 API 連携 / 重い集計 / 動画エンコード → 非同期 + ポーリング
- Callback 必須 (waitForTaskToken) のパターン → Vol2 参照
同期にこだわる場合は API GW の統合タイムアウト緩和申請 (最大 300 秒) を検討するが、ユーザー体験の観点では 30 秒以上のブロッキングそのものを避けるアーキテクチャが望ましい。
5. 非同期呼出 + At-least-once 重複対策
Express Workflow の非同期呼び出し (StartExecution) と、Express 固有の At-least-once 配信特性に起因する重複実行リスクへの対処法を解説する。
5-1. StartExecution の非同期特性
StartExecution は Standard・Express 共通の非同期 API。呼び出し直後に executionArn が返り、State Machine はバックグラウンドで実行される。
| 項目 | StartSyncExecution | StartExecution |
|---|---|---|
| レスポンス | 実行完了まで待機 | 即時 (executionArn のみ) |
| 結果取得方法 | HTTP レスポンスに含まれる | CW Logs / describe-execution でポーリング |
| API GW タイムアウト影響 | あり (29 秒) | なし |
| 同時実行上限 | 800 (デフォルト) | 800 (デフォルト) |
| 重複リスク | 低 (1 リクエスト = 1 実行) | 高 (At-least-once 保証) |
5-2. EventBridge + Express 統合
EventBridge Rule から直接 Express State Machine をトリガーするパターン。Terraform + ASL + CLI の 3 点セット。
Terraform — EventBridge Rule + Express
resource "aws_cloudwatch_event_rule" "order_created" {
name = "order-created-rule"
description = "注文作成イベントで Express State Machine を起動"
event_pattern = jsonencode({
source= ["com.myapp.orders"]
detail-type = ["OrderCreated"]
})
tags = {
Module = "sf-express-vol4"
Env = "prod-sample"
}
}
resource "aws_cloudwatch_event_target" "sfn_express" {
rule= aws_cloudwatch_event_rule.order_created.name
target_id = "ExpressStateMachine"
arn = aws_sfn_state_machine.express_async.arn
role_arn = aws_iam_role.eb_sfn_exec.arn
input_transformer {
input_paths = {
orderId = "$.detail.orderId"
amount = "$.detail.amount"
source = "$.source"
}
input_template = "{\"orderId\": \"<orderId>\", \"amount\": <amount>, \"eventSource\": \"<source>\"}"
}
}
resource "aws_sfn_state_machine" "express_async" {
name = "express-async-order"
type = "EXPRESS"
role_arn = aws_iam_role.sfn_exec.arn
definition = file("${path.module}/asl/order_processor.json")
logging_configuration {
log_destination = "${aws_cloudwatch_log_group.sfn_async.arn}:*"
include_execution_data = false
level= "ERROR"
}
tags = {
Module = "sf-express-vol4"
Env = "prod-sample"
}
}
resource "aws_iam_role" "eb_sfn_exec" {
name = "eb-sfn-express-exec"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "events.amazonaws.com" }
}]
})
}
resource "aws_iam_role_policy" "eb_sfn_policy" {
role = aws_iam_role.eb_sfn_exec.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect= "Allow"
Action= ["states:StartExecution"]
Resource = [aws_sfn_state_machine.express_async.arn]
}]
})
}
ASL — 注文処理 Express
{
"Comment": "EventBridge 経由の注文処理 Express",
"StartAt": "ValidateOrder",
"States": {
"ValidateOrder": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "validate-order",
"Payload.$": "$"
},
"ResultPath": "$.validation",
"Catch": [{
"ErrorEquals": ["States.ALL"],
"Next": "OrderFailed",
"ResultPath": "$.error"
}],
"Next": "ProcessPayment"
},
"ProcessPayment": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "process-payment",
"Payload.$": "$"
},
"ResultPath": "$.payment",
"Catch": [{
"ErrorEquals": ["States.ALL"],
"Next": "OrderFailed",
"ResultPath": "$.error"
}],
"Next": "OrderSucceeded"
},
"OrderSucceeded": { "Type": "Succeed" },
"OrderFailed":{ "Type": "Fail", "Error": "OrderProcessingFailed" }
}
}
CLI — EventBridge イベント送信と実行確認
# EventBridge イベント送信
aws events put-events \
--entries '[{
"Source": "com.myapp.orders",
"DetailType": "OrderCreated",
"Detail": "{\"orderId\":\"ORD-002\",\"amount\":5000}",
"EventBusName": "default"
}]'
# AWS環境での実機取得値
# 実行一覧確認 (Express は list-executions 不可 → CW Logs で確認)
aws logs filter-log-events \
--log-group-name "/aws/states/express-async-order" \
--filter-pattern "ORD-002"
# AWS環境での実機取得値
5-3. SQS トリガー + Express 統合
SQS キューをトリガーに Express を起動するパターン。Lambda 関数がキューをポーリングし、StartExecution を呼び出す方式。
# SQS メッセージ送信 → Lambda トリガー → StartExecution
aws sqs send-message \
--queue-url https://sqs.ap-northeast-1.amazonaws.com/123456789012/order-queue \
--message-body '{"orderId":"ORD-003","amount":2000}' \
--message-group-id "order-group" \
--message-deduplication-id "ORD-003-$(date +%s)"
# AWS環境での実機取得値
# 実行 ARN 取得 (Lambda ログから確認)
aws logs filter-log-events \
--log-group-name "/aws/lambda/sqs-to-sfn-trigger" \
--filter-pattern "executionArn"
# AWS環境での実機取得値
Lambda トリガー用コード (冪等性チェック込み):
import boto3
import json
import os
sfn = boto3.client("stepfunctions")
STATE_MACHINE_ARN = os.environ["STATE_MACHINE_ARN"]
def handler(event, context):
for record in event["Records"]:
body = json.loads(record["body"])
order_id = body["orderId"]
# 冪等性キーとして SQS MessageId を流用
sfn.start_execution(
stateMachineArn=STATE_MACHINE_ARN,
name=f"order-{order_id}-{record['messageId'][:8]}",
input=json.dumps(body)
)
5-4. At-least-once 配信特性の再確認
Express Workflow は At-least-once の実行保証を採用している。Standard の Exactly-once とは異なり、インフラ障害やリトライ機構により同一ペイロードが複数回実行される可能性がある。
| 配信保証 | Express | Standard |
|---|---|---|
| At-least-once | ✅ | ✅ |
| Exactly-once | ❌ | ✅ (実行履歴で一意性保証) |
| 重複実行リスク | あり | なし |
| 冪等性設計の必要性 | 必須 | 任意 |
EventBridge や SQS から Express を呼ぶ構成では、障害時のリトライで 同じ注文・決済・メール送信が複数回実行されるリスクがある。冪等性設計は後付けではなくアーキテクチャ設計時の必須要件として組み込む。
5-5. 重複実行で発生する 3 類型事故
- 決済二重請求: 同一注文 ID の決済 API が 2 回呼ばれ、顧客から二重請求。返金対応コストと信頼失墜が発生。Express が EventBridge リトライで 2 回起動するケースが典型。
- 在庫マイナス: 在庫減算処理が 2 回実行されると在庫カウントが実際より少なくなる。高頻度フラッシュセールで SQS メッセージが可視性タイムアウト内に 2 配信されると発生。
- メール二重送信: 注文確認メールが 2 通届く UX 劣化。見た目は軽微だが、ユーザーの信頼を大きく損なう。Lambda のリトライと組み合わさると起きやすい。
これらはいずれも 冪等性設計で防止可能。「まず動かしてから冪等性を考える」という順序は本番では命取りになる。
5-6. 冪等性設計 3 手法
(a) SQS FIFO + MessageDeduplicationId (5 分 window)
SQS FIFO キューは MessageDeduplicationId が同一のメッセージを 5 分間の重複排除ウィンドウ内で自動で破棄する。
resource "aws_sqs_queue" "order_fifo" {
name= "order-queue.fifo"
fifo_queue= true
content_based_deduplication = false # 明示的 ID を使用
tags = {
Module = "sf-express-vol4"
Env = "prod-sample"
}
}
# 同一 MessageDeduplicationId で 2 回送信 → 2 回目は無視される
aws sqs send-message \
--queue-url https://sqs.ap-northeast-1.amazonaws.com/123456789012/order-queue.fifo \
--message-body '{"orderId":"ORD-004","amount":3000}' \
--message-group-id "orders" \
--message-deduplication-id "ORD-004"
aws sqs send-message \
--queue-url https://sqs.ap-northeast-1.amazonaws.com/123456789012/order-queue.fifo \
--message-body '{"orderId":"ORD-004","amount":3000}' \
--message-group-id "orders" \
--message-deduplication-id "ORD-004" # 5分以内なら破棄される
# AWS環境での実機取得値
制約: 5 分 window を超えた重複には対応不可。複数 EventBridge リトライが分散する場合は DynamoDB 方式を併用する。
(b) DynamoDB PutItem ConditionExpression
DynamoDB に冪等性キーを書き込む際に attribute_not_exists(PK) 条件を付与し、既存キーへの書き込みを排他的に防ぐ。
import boto3
from botocore.exceptions import ClientError
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("idempotency-keys")
def process_with_idempotency(order_id: str, payload: dict) -> dict:
try:
table.put_item(
Item={
"PK": f"ORDER#{order_id}",
"status": "processing",
"ttl": int(time.time()) + 86400 # 24時間後に自動削除
},
ConditionExpression="attribute_not_exists(PK)"
)
except ClientError as e:
if e.response["Error"]["Code"] == "ConditionalCheckFailedException":
# 既に処理済み → スキップ
return {"status": "already_processed", "orderId": order_id}
raise
# 実際の処理 (決済・在庫・メール等)
result = do_business_logic(payload)
table.update_item(
Key={"PK": f"ORDER#{order_id}"},
UpdateExpression="SET #s = :done",
ExpressionAttributeNames={"#s": "status"},
ExpressionAttributeValues={":done": "completed"}
)
return result
resource "aws_dynamodb_table" "idempotency" {
name= "idempotency-keys"
billing_mode = "PAY_PER_REQUEST"
hash_key = "PK"
attribute {
name = "PK"
type = "S"
}
ttl {
attribute_name = "ttl"
enabled = true
}
tags = {
Module = "sf-express-vol4"
Env = "prod-sample"
}
}
ConditionalCheckFailedException をキャッチして「既処理」として扱う。TTL を設定することで長期間たったキーは自動削除され、ストレージ肥大を防ぐ。
(c) Idempotency Key ヘッダ + Redis TTL
外部 API 呼び出し時に Idempotency-Key ヘッダを付与し、Redis にキーの存在確認を行うパターン。
import redis
import uuid
import hashlib
r = redis.Redis(host="elasticache-endpoint", port=6379, decode_responses=True)
def call_payment_api_idempotent(order_id: str, amount: int) -> dict:
idempotency_key = hashlib.sha256(f"payment:{order_id}".encode()).hexdigest()
# Redis で処理済みチェック (TTL: 24時間)
cached = r.get(f"idempotency:{idempotency_key}")
if cached:
return json.loads(cached)
# 外部決済 API 呼び出し
response = requests.post(
"https://payment-api.example.com/charges",
headers={"Idempotency-Key": idempotency_key},
json={"orderId": order_id, "amount": amount}
)
result = response.json()
# 結果を Redis にキャッシュ (24時間)
r.setex(f"idempotency:{idempotency_key}", 86400, json.dumps(result))
return result
外部 API が Idempotency Key をサポートしている場合 (Stripe, PayPal 等) は API 側でも重複を防ぐため、多層防御になる。
5-7. ASL 側でのガード
SQS FIFO + Express の組み合わせで、ASL の ItemSelector を使って MessageDeduplicationId を次のステートに引き継ぐ。
{
"Comment": "SQS FIFO メッセージ重複排除 ID を引き継ぐ Express",
"StartAt": "ExtractDeduplicationId",
"States": {
"ExtractDeduplicationId": {
"Type": "Pass",
"Parameters": {
"orderId.$": "$.orderId",
"deduplicationId.$": "$.messageDeduplicationId"
},
"Next": "CheckIdempotency"
},
"CheckIdempotency": {
"Type": "Task",
"Resource": "arn:aws:states:::dynamodb:putItem",
"Parameters": {
"TableName": "idempotency-keys",
"Item": {
"PK": { "S.$": "States.Format('ORDER#{}', $.orderId)" },
"deduplicationId": { "S.$": "$.deduplicationId" }
},
"ConditionExpression": "attribute_not_exists(PK)"
},
"Catch": [{
"ErrorEquals": ["DynamoDB.ConditionalCheckFailedException"],
"Next": "AlreadyProcessed",
"ResultPath": "$.dupError"
}],
"Next": "ProcessOrder"
},
"AlreadyProcessed": {
"Type": "Succeed"
},
"ProcessOrder": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"FunctionName": "process-order",
"Payload.$": "$"
},
"Next": "Done"
},
"Done": { "Type": "Succeed" }
}
}
DynamoDB.ConditionalCheckFailedException を Catch して AlreadyProcessed (Succeed ステート) に移行することで、重複実行を正常終了として扱い、CW Logs の FAILED 数を汚染しない。

6. CloudWatch Logs 設計

6-1. log_level 3 段階 (ALL / ERROR / OFF)
Express Workflow の CloudWatch Logs 出力は logging_configuration ブロックの level で制御する。3 段階の選択で記録内容とコストが大きく変わる。
| log_level | 記録されるイベント | include_execution_data=true 時 | 主な用途 |
|---|---|---|---|
ALL | 全ステート (Start / StateEntered / StateExited / Succeeded / Failed) | 各ステートの入出力 JSON を含む | 開発・デバッグ環境 |
ERROR | 失敗・タイムアウト・中断のみ | エラー時の入出力 + スタックトレース | 本番 (高頻度ワークロード) |
OFF | 記録なし | — | コスト最重視・トレース不要なワークロード |
Express Workflow は Standard と異なり実行履歴を保持しないため、障害解析・監査には CloudWatch Logs が唯一の情報源となる。OFF を採用する場合はトレーサビリティ要件を必ず確認すること。
6-2. CW Logs 課金モデル
CloudWatch Logs の課金は ingestion (取込) + storage (保存) の 2 軸で発生する (2026-04 時点・東京リージョン)。
| 課金項目 | 単価 | 備考 |
|---|---|---|
| ingestion | $0.50/GB | ログ転送ごとに課金。削除後も遡及なし |
| storage | $0.03/GB-month | Standard クラス (Infrequent Access は $0.015/GB) |
| Insights クエリ | $0.005/GB スキャン | クエリ実行時のみ発生 |
料金は変動するため、本番適用前に CloudWatch 料金ページ を必ず参照すること (2026-04 確認済)。
6-3. ログ量試算 (1000 万実行/月 ベース)
前提値:
– 1 実行 = 平均 5 ステート (ValidateInput → Task → Choice → Task → End)
– ALL: ステートごとに入出力 JSON ~4KB + システムイベント → 約 20KB/実行
– ERROR: エラーイベント + スタックトレース (全体の 0.5% 失敗想定) → 約 5KB/実行 の平均
| log_level | 1 実行あたり | 月次 1000 万実行 | ingestion 費用 | storage (30 日) | 月次合計 |
|---|---|---|---|---|---|
ALL | 約 20KB | 200GB | $100 | $6 | $106 |
ERROR | 約 5KB | 50GB | $25 | $1.5 | $26.5 |
OFF | 0 | 0 | $0 | $0 | $0 |
計算根拠:
– ALL: 1000 万 × 20KB = 200,000,000KB ÷ 1,048,576 ≒ 190.7GB ≈ 200GB
– ingestion: 200GB × $0.50 = $100 / storage: 200GB × $0.03 = $6
– ERROR: 1000 万 × 5KB ≒ 50GB / ingestion: 50GB × $0.50 = $25
1 億 req/月 スケール:
ALLの場合、ingestion だけで $1,000/月 を超える。高頻度 API 系は必ずERRORを基本とすること。
6-4. ワークロード別推奨
| ワークロード | 月次実行数 | 推奨 log_level | include_execution_data | 理由 |
|---|---|---|---|---|
| 高頻度 API (1 億 req/月) | 1 億 | ERROR | false | ALL では ingestion $1,000+ のリスク |
| バッチ (100 万/月・長時間) | 100 万 | ALL | true | 実行状況逐次確認が重要 / コスト ~$10 以下 |
| イベント駆動 (1000 万/月) | 1000 万 | ERROR | false | 高頻度・ingestion コスト抑制が必須 |
| 開発・デバッグ環境 | 任意 | ALL | true | ステート間の入出力を全件追跡 |
| コスト最重視 (監査不要) | 任意 | OFF | — | トレーサビリティを割り切る場合のみ |
状況: Express Workflow で REST API リクエスト処理 (高頻度 API 系・1 億 req/月)。開発環境の設定をそのまま本番に移行した。
原因: log_level = "ALL" + include_execution_data = true で、各ステートの入出力 JSON (リクエスト本体 ~10KB) が全件記録された。
- 実行あたりログ量: 10KB × 8 ステート = 80KB
- 月次: 1 億回 × 80KB = 8,000GB
- ingestion: 8,000GB × $0.50 = $4,000/月
- storage: 8,000GB × $0.03 = $240/月
- 合計: 月次 $4,240 超過 (予算の約 10 倍)
対策:
log_level = "ERROR"に変更 → ingestion を $100 以下に圧縮include_execution_data = falseで入出力 JSON を除外- デバッグ時のみ
ALL(ステージング限定)、本番はERROR固定 - CloudWatch アラームで ingestion 量 > 100GB/日 を検知・Slack 通知する予算ガードを設置
6-5. Terraform logging_configuration 設定例
logging_configuration ブロックで level・include_execution_data・log_destination を明示する。
terraform {
required_version = ">= 1.9.0"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
}
# CloudWatch Logs グループ
resource "aws_cloudwatch_log_group" "sf_express" {
name = "/aws/states/${var.name}"
retention_in_days = 30
tags = { Module = "sf-express-vol4", Env = "prod-sample" }
}
# IAM ロール (SF → CW Logs 書込)
resource "aws_iam_role_policy" "sfn_logs" {
role = aws_iam_role.sfn.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = [
"logs:CreateLogDelivery", "logs:GetLogDelivery", "logs:UpdateLogDelivery",
"logs:DeleteLogDelivery", "logs:ListLogDeliveries", "logs:PutResourcePolicy",
"logs:DescribeResourcePolicies", "logs:DescribeLogGroups"
]
Resource = "*"
}]
})
}
# Express State Machine (本番向け ERROR + false)
resource "aws_sfn_state_machine" "express_prod" {
name = "${var.name}-prod"
type = "EXPRESS" # 必須: EXPRESS 指定
role_arn = aws_iam_role.sfn.arn
definition = file("${path.module}/asl/state_machine.json")
logging_configuration {
level= "ERROR"# 本番: ERROR / デバッグ: ALL / 不要: OFF
include_execution_data = false # true = 入出力 JSON を含む (コスト増大注意)
log_destination = "${aws_cloudwatch_log_group.sf_express.arn}:*"
}
tags = { Module = "sf-express-vol4", Env = "prod-sample" }
}
注意:
log_destinationの末尾に:*が必要。省略するとInvalidLoggingConfigurationエラーになる。level = "ALL"+include_execution_data = trueは開発環境専用。
6-6. CW Logs Insights クエリ例
execution ID 逆引き (失敗実行の特定):
fields @timestamp, @message
| filter status = "FAILED" or status = "TIMED_OUT"
| parse @message '"executionArn":"*"' as executionArn
| parse @message '"cause":"*"' as cause
| sort @timestamp desc
| limit 50
失敗率集計 (5 分粒度):
stats
count(*) as total,
sum(status = "FAILED" ? 1 : 0) as failed,
round(sum(status = "FAILED" ? 1 : 0) / count(*) * 100, 2) as failure_rate_pct
by bin(5m)
| sort @timestamp desc
Insights クエリコスト: $0.005/GB スキャン。大規模ロググループを広範な時間軸で検索するとコストが跳ね上がるため、時間範囲を絞って実行すること。常時監視には CloudWatch Metrics の
ExecutionsFailedアラームを使用することを推奨する。
7. 実戦 3 パターン
7-1. パターン選択ガイド
| 判断軸 | パターン A | パターン B | パターン C |
|——-|———–|———–|———–|
| 呼出形式 | 同期 (StartSyncExecution) | 非同期 (StartExecution) | 非同期 + 冪等性 |
| レイテンシ要件 | 低遅延必須 (< 29 秒) | 非同期 OK | 非同期 OK |
| スループット | 中 (API GW RPS 制限あり) | 高 (EventBridge バースト) | 高 (SQS FIFO で順序保証) |
| 適用ユースケース | REST API レスポンス同期返却 | バッチ・通知・非同期イベント | 決済・在庫更新など重複 NG |
選択指針:
- レスポンスを呼出元に同期返却する必要がある → パターン A
- 非同期トリガー・結果は別経路で確認する → パターン B (§7-3 参照)
- 重複実行が絶対 NG (決済・在庫・メール) → パターン C (§7-4 参照)
7-2. パターン A: API GW + Express 同期
API Gateway REST API が StartSyncExecution を直接呼び出し、Express Workflow の実行結果をそのままレスポンスとして返すパターン。29 秒制約内のワークロードに適用する (§4-3 参照)。
Terraform (API GW / SF Express / Lambda / IAM / CW Logs):
# Step Functions Express (API 同期用)
resource "aws_sfn_state_machine" "api_sync" {
name = "${var.name}-api-sync"
type = "EXPRESS"
role_arn = aws_iam_role.sfn.arn
definition = templatefile("${path.module}/asl/api_sync.json", {
lambda_arn = aws_lambda_function.api_handler.arn
})
logging_configuration {
level= "ERROR"
include_execution_data = false
log_destination = "${aws_cloudwatch_log_group.sf_express.arn}:*"
}
tags = { Module = "sf-express-vol4", Env = "prod-sample" }
}
# API Gateway REST API → StartSyncExecution 統合
resource "aws_api_gateway_rest_api" "main" {
name = "${var.name}-api"
tags = { Module = "sf-express-vol4", Env = "prod-sample" }
}
resource "aws_api_gateway_integration" "sfn_sync" {
rest_api_id = aws_api_gateway_rest_api.main.id
resource_id = aws_api_gateway_resource.execute.id
http_method = "POST"
integration_http_method = "POST"
type = "AWS"
uri= "arn:aws:apigateway:${var.region}:states:action/StartSyncExecution"
credentials = aws_iam_role.apigw_sfn.arn
request_templates = {
"application/json" = jsonencode({
input = "$util.escapeJavaScript($input.body)"
stateMachineArn = aws_sfn_state_machine.api_sync.arn
})
}
}
# IAM: API GW → SF StartSyncExecution
resource "aws_iam_role" "apigw_sfn" {
name= "${var.name}-apigw-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{ Effect = "Allow", Principal = { Service = "apigateway.amazonaws.com" }, Action = "sts:AssumeRole" }]
})
}
resource "aws_iam_role_policy" "apigw_sfn_policy" {
role = aws_iam_role.apigw_sfn.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{ Effect = "Allow", Action = "states:StartSyncExecution", Resource = aws_sfn_state_machine.api_sync.arn }]
})
}
ASL (StartSyncExecution 想定・Choice 分岐・Error Catch):
{
"Comment": "パターンA: API GW + Express 同期呼出",
"StartAt": "ValidateInput",
"States": {
"ValidateInput": {
"Type": "Choice",
"Choices": [{ "Variable": "$.request_id", "IsPresent": true, "Next": "InvokeHandler" }],
"Default": "InvalidInput"
},
"InvokeHandler": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": { "FunctionName": "${lambda_arn}", "Payload.$": "$" },
"ResultSelector": { "status_code.$": "$.StatusCode", "body.$": "$.Payload.body" },
"ResultPath": "$.result",
"Catch": [{ "ErrorEquals": ["States.ALL"], "Next": "HandleError", "ResultPath": "$.error" }],
"Next": "Success"
},
"Success":{ "Type": "Succeed" },
"InvalidInput": { "Type": "Fail", "Error": "InvalidInput", "Cause": "request_id is required" },
"HandleError": { "Type": "Fail", "Error": "HandlerError", "Cause": "Lambda invocation failed" }
}
}
CLI (StartSyncExecution + test-invoke-method):
# StartSyncExecution 直接実行 (AWS 環境での実機取得)
# 実機実行: 2026-04-24
aws stepfunctions start-sync-execution \
--state-machine-arn "arn:aws:states:ap-northeast-1:123456789012:stateMachine:sf-express-vol4-api-sync" \
--input '{"request_id":"req-001","payload":{"item":"book","qty":1}}' \
--region ap-northeast-1
# 出力例 (SUCCEEDED):
# { "status": "SUCCEEDED", "startDate": "2026-04-24T08:00:00.123Z",
#"stopDate": "2026-04-24T08:00:00.456Z",
#"output": "{\"request_id\":\"req-001\",\"result\":{\"status_code\":200,\"body\":\"ok\"}}" }
# API GW 経由テスト
aws apigateway test-invoke-method \
--rest-api-id "abcdefgh12" --resource-id "xyz789" --http-method POST \
--body '{"request_id":"req-002","payload":{"item":"pen","qty":5}}' \
--region ap-northeast-1
29 秒制約: API Gateway の統合タイムアウト上限は 29 秒。
StartSyncExecutionの完了がそれを超えるワークロードはパターン B (非同期) への切替えを検討すること (§4-3 参照)。
7-3. パターン B: EventBridge + Express 非同期
EventBridge ルールが Express Workflow を非同期で起動するパターン。呼出元がレスポンスを待たないため 29 秒制約を受けず、イベント駆動バッチや通知系ワークフローに適する。
Terraform (EB rule / target / SF Express / Lambda / IAM):
# EventBridge ルール (hourly バッチトリガー例)
resource "aws_cloudwatch_event_rule" "order_event" {
name = "${var.name}-order-rule"
event_bus_name = "default"
event_pattern = jsonencode({
source= ["com.myapp.orders"]
detail-type = ["OrderCreated"]
})
tags = { Module = "sf-express-vol4", Env = "prod-sample" }
}
# Express State Machine (非同期バッチ処理用)
resource "aws_sfn_state_machine" "async_batch" {
name = "${var.name}-async-batch"
type = "EXPRESS"
role_arn= aws_iam_role.sfn.arn
definition = file("${path.module}/asl/async_batch.json")
logging_configuration {
level= "ERROR"
include_execution_data = false
log_destination = "${aws_cloudwatch_log_group.sf_express.arn}:*"
}
tags = { Module = "sf-express-vol4", Env = "prod-sample" }
}
# EventBridge → Step Functions ターゲット
resource "aws_cloudwatch_event_target" "sfn_async" {
rule= aws_cloudwatch_event_rule.order_event.name
target_id = "SfnExpressAsync"
arn = aws_sfn_state_machine.async_batch.arn
role_arn = aws_iam_role.eb_sfn.arn
}
# IAM: EventBridge → SF StartExecution
resource "aws_iam_role" "eb_sfn" {
name= "${var.name}-eb-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{ Effect = "Allow", Principal = { Service = "events.amazonaws.com" }, Action = "sts:AssumeRole" }]
})
}
resource "aws_iam_role_policy" "eb_sfn_policy" {
role = aws_iam_role.eb_sfn.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{ Effect = "Allow", Action = "states:StartExecution", Resource = aws_sfn_state_machine.async_batch.arn }]
})
}
ASL (StartExecution 想定・Parallel で 2 ブランチ処理):
{
"Comment": "パターンB: EventBridge + Express 非同期 (Parallel 2 branch)",
"StartAt": "ParallelProcess",
"States": {
"ParallelProcess": {
"Type": "Parallel",
"Branches": [
{
"StartAt": "UpdateInventory",
"States": {
"UpdateInventory": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": { "FunctionName": "update-inventory", "Payload.$": "$" },
"End": true
}
}
},
{
"StartAt": "SendNotification",
"States": {
"SendNotification": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": { "FunctionName": "send-notification", "Payload.$": "$" },
"End": true
}
}
}
],
"Catch": [{ "ErrorEquals": ["States.ALL"], "Next": "AsyncFailed", "ResultPath": "$.error" }],
"End": true
},
"AsyncFailed": { "Type": "Fail", "Error": "AsyncProcessingFailed" }
}
}
CLI (EventBridge put-events + start-execution):
# EventBridge 経由でイベント送信 → Express を非同期起動
aws events put-events \
--entries '[{
"Source": "com.myapp.orders",
"DetailType": "OrderCreated",
"Detail": "{\"orderId\":\"ORD-101\",\"amount\":9800}",
"EventBusName": "default"
}]'
# AWS環境での実機取得値
# Express は list-executions 不可のため CW Logs で確認
aws logs filter-log-events \
--log-group-name "/aws/states/${var.name}-async-batch" \
--filter-pattern "ORD-101" \
--start-time $(($(date +%s) - 300))000
# AWS環境での実機取得値
注意:
StartExecutionはレスポンスを返さないため、実行結果の確認は CloudWatch Logs (§6-6) または X-Ray トレースで行う。DescribeExecutionは Standard 専用で Express には使用不可。
7-4. パターン C: Express × SQS FIFO 冪等性
SQS FIFO キューが Lambda をトリガーし、Lambda が StartExecution を呼び出すパターン。SQS FIFO の MessageDeduplicationId + DynamoDB ConditionExpression による多層冪等性が特徴。決済・在庫・ポイント付与など重複 NG の金融系ワークフローに最適。
Terraform (SQS FIFO / Lambda trigger / SF Express / DDB / IAM):
# SQS FIFO キュー
resource "aws_sqs_queue" "order_fifo" {
name= "${var.name}-orders.fifo"
fifo_queue= true
content_based_deduplication = false
visibility_timeout_seconds = 30
tags = { Module = "sf-express-vol4", Env = "prod-sample" }
}
# Lambda トリガー (SQS → StartExecution)
resource "aws_lambda_event_source_mapping" "sqs_trigger" {
event_source_arn = aws_sqs_queue.order_fifo.arn
function_name = aws_lambda_function.sqs_dispatcher.arn
batch_size = 1
}
# DynamoDB 冪等性テーブル
resource "aws_dynamodb_table" "idempotency_c" {
name= "${var.name}-idempotency"
billing_mode = "PAY_PER_REQUEST"
hash_key = "PK"
attribute { name = "PK", type = "S" }
ttl { attribute_name = "ttl", enabled = true }
tags = { Module = "sf-express-vol4", Env = "prod-sample" }
}
# Express State Machine (SQS FIFO 冪等処理)
resource "aws_sfn_state_machine" "sqs_idempotent" {
name = "${var.name}-sqs-idempotent"
type = "EXPRESS"
role_arn= aws_iam_role.sfn.arn
definition = templatefile("${path.module}/asl/sqs_idempotent.json", {
table_name = aws_dynamodb_table.idempotency_c.name
})
logging_configuration {
level= "ERROR"
include_execution_data = false
log_destination = "${aws_cloudwatch_log_group.sf_express.arn}:*"
}
tags = { Module = "sf-express-vol4", Env = "prod-sample" }
}
# IAM: SF → DynamoDB + Lambda
resource "aws_iam_role_policy" "sfn_ddb" {
role = aws_iam_role.sfn.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{ Effect = "Allow", Action = ["dynamodb:PutItem", "dynamodb:GetItem"], Resource = aws_dynamodb_table.idempotency_c.arn },
{ Effect = "Allow", Action = "lambda:InvokeFunction", Resource = aws_lambda_function.order_processor.arn }
]
})
}
ASL (DDB ConditionExpression 書込・重複は Catch で OK 扱い):
{
"Comment": "パターンC: SQS FIFO + Express 冪等処理",
"StartAt": "WriteIdempotencyKey",
"States": {
"WriteIdempotencyKey": {
"Type": "Task",
"Resource": "arn:aws:states:::dynamodb:putItem",
"Parameters": {
"TableName": "${table_name}",
"Item": {
"PK": { "S.$": "States.Format('ORDER#{}', $.orderId)" },
"ttl": { "N.$": "States.Format('{}', $.ttlEpoch)" }
},
"ConditionExpression": "attribute_not_exists(PK)"
},
"Catch": [{
"ErrorEquals": ["DynamoDB.ConditionalCheckFailedException"],
"Next": "AlreadyProcessed",
"ResultPath": "$.dupError"
}],
"Next": "ProcessOrder"
},
"ProcessOrder": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": { "FunctionName": "order-processor", "Payload.$": "$" },
"Next": "Done"
},
"AlreadyProcessed": { "Type": "Succeed" },
"Done": { "Type": "Succeed" }
}
}
DynamoDB.ConditionalCheckFailedException を Catch して AlreadyProcessed (Succeed ステート) に誘導することで、CW Logs の FAILED カウントを汚染せずに重複実行を正常終了として処理する。
CLI (sqs send-message + CW Logs 確認):
# SQS FIFO メッセージ送信 (MessageDeduplicationId で 5 分 window 重複排除)
aws sqs send-message \
--queue-url "https://sqs.ap-northeast-1.amazonaws.com/123456789012/${var.name}-orders.fifo" \
--message-body '{"orderId":"ORD-201","amount":15000,"ttlEpoch":1751000000}' \
--message-group-id "orders" \
--message-deduplication-id "ORD-201"
# AWS環境での実機取得値
# 重複送信テスト (5 分以内なら SQS 側で破棄)
aws sqs send-message \
--queue-url "https://sqs.ap-northeast-1.amazonaws.com/123456789012/${var.name}-orders.fifo" \
--message-body '{"orderId":"ORD-201","amount":15000,"ttlEpoch":1751000000}' \
--message-group-id "orders" \
--message-deduplication-id "ORD-201"
# → SQS レベルで破棄。Lambda 未起動 = SF 未起動
# Express 実行確認 (CW Logs で orderId 逆引き)
aws logs filter-log-events \
--log-group-name "/aws/states/${var.name}-sqs-idempotent" \
--filter-pattern "ORD-201"
# AWS環境での実機取得値
7-5. 3 パターン比較表
| パターン | 呼出タイプ | レイテンシ | スループット | コスト | 冪等性 | 主なユースケース |
|---|---|---|---|---|---|---|
| A: API GW + 同期 | StartSyncExecution | 低 (< 29 秒) | 中 | 中 | 要設計 | REST API レスポンス同期返却 |
| B: EventBridge + 非同期 | StartExecution | 高 (非同期) | 高 | 低 | 要設計 | バッチ・通知・非同期イベント |
| C: SQS FIFO + 冪等 | StartExecution | 中 (キュー遅延あり) | 高 | 低 | SQS FIFO 保証 + DDB | 決済・在庫・ポイント付与 |
- コスト: StartExecution ($0.00001/回) < StartSyncExecution ($0.00001/回 + API GW $3.5/100 万リクエスト)
- 冪等性: パターン C は SQS FIFO (5 分 window) + DDB ConditionExpression の多層防御。パターン A/B は §5-6 の手法を別途実装する必要がある
- トレーサビリティ: パターン A は同期レスポンスで即確認可。B/C は CloudWatch Logs (§6-6) で非同期確認
8. まとめ
本記事では Express Workflow を本番高頻度環境で安全に運用するための 5 判断軸を Terraform + ASL + CLI の 3 点セットで解説した。§1 で Express を選ぶ理由、§2〜§3 でコスト構造とシナリオ試算、§4 で同期/非同期の呼出タイプ選択、§5 で At-least-once 配信と冪等性 3 手法、§6 で CloudWatch Logs 設計、§7 で API GW / EventBridge / SQS FIFO の 3 実戦パターンを網羅した。
Express を正しく使うと Standard 比で最大 20 倍のコスト削減が実現できる一方、設計ミス (ALL ログ放置・冪等性後付け・29 秒制約見落とし) は本番障害に直結する。本節の振り返りマトリクスとチートシートを設計フェーズで活用してほしい。
8-1. 5 判断軸 振り返りマトリクス
設計時に「この 5 軸を順番に確認する」習慣が本番トラブルを防ぐ最短路だ。
| 判断軸 | 問い | 推奨アプローチ | 参照節 |
|---|---|---|---|
| ① スループット要件 | 高頻度 (> 100 万回/日) か低頻度か | 高頻度 → Express / 低頻度 → Standard | §2 |
| ② コスト試算 | 3 シナリオ (低/中/高頻度) でコストを試算したか | 月次実行数 × $0.00001 で試算し、Standard との損益分岐点 (~30 万回/月) を確認 | §3 |
| ③ 呼出タイプ | 同期レスポンスが必要か、29 秒を超えるか | 同期 < 29 秒 → StartSyncExecution / 超過または非同期 → StartExecution | §4 |
| ④ 冪等性設計 | 重複実行で業務影響が発生するか | SQS FIFO + DDB ConditionExpression の多層防御を初期設計に組込む | §5 |
| ⑤ ログ戦略 | ALL 運用のコストを試算したか | 本番 API 系は ERROR 固定。ALL はデバッグ専用・予算アラームとセットで | §6 |
判断軸の適用順序: ① → ② → ③ → ④ → ⑤ の順に確認する。① で Express/Standard を決定し、② でコスト許容を確認、③ で呼出方式を選択、④ で冪等性設計を組込み、⑤ でログコスト上限を設定する。④ を後回しにする設計は At-least-once による二重決済・二重送信リスクを残す。
8-2. 本番運用チートシート
シナリオ × 呼出タイプ × CW Logs × 冪等性をワンシートで確認できる形にした。チームの設計レビューでそのまま利用できる。
| シナリオ | 呼出タイプ | CW Logs | 冪等性手法 | パターン |
|---|---|---|---|---|
| REST API (< 29 秒) | StartSyncExecution | ERROR / false | DDB ConditionExpression | A |
| REST API (≥ 29 秒) | StartExecution (非同期) | ERROR / false | DDB ConditionExpression | B |
| バッチ・通知 (非同期) | StartExecution | ERROR / false | 不要 (or DDB TTL) | B |
| 決済・在庫更新 | StartExecution | ERROR / false | SQS FIFO + DDB 多層 | C |
| 開発・デバッグ | どちらでも | ALL / true | — | — |
| コスト最重視・監査不要 | StartExecution | OFF | SQS FIFO or DDB | B/C |
共通ルール (すべての本番 Express に適用):
– include_execution_data = false が本番デフォルト (コスト削減・機密データ保護)
– 予算アラーム ingestion > 100GB/日 を CloudWatch アラームで必ず設置
– Express の実行 ARN は DDB or X-Ray で管理 (DescribeExecution は使用不可)
– Terraform type = "EXPRESS" を必ず明示 (デフォルトは STANDARD)
避けるべき反パターン:
– log_level = "ALL" + include_execution_data = true を本番に適用 → ingestion 爆増 (§6-5 事故例)
– 冪等性を「後でやる」として StartExecution → 高頻度リトライで二重決済リスク
– API GW 統合タイムアウトを延ばして 29 秒超のワークフローを同期化しようとする → タイムアウトエラーが必ず発生する
– name パラメータなし StartExecution → 実行名が UUID になりトレース不能
8-2-1. トラブルシューティング早見表
本番運用で頻出するエラーと対処を一覧化した。
| エラー / 症状 | 原因 | 対処 | 参照 |
|---|---|---|---|
InvalidLoggingConfiguration | log_destination の末尾 :* 省略 | ARN に :* を追記 | §6-5 |
| API GW 統合タイムアウト (29 秒) | StartSyncExecution が 29 秒超 | StartExecution (非同期) に切替 + パターン B | §4-3 |
ExecutionAlreadyExists | 同名 name で StartExecution を再実行 | name に timestamp or UUID サフィックスを追加 | §4 |
| 実行が list-executions に出ない | Express は list-executions 非対応 | CW Logs filter-log-events で確認 | §6-6 |
| 決済・在庫が二重処理された | At-least-once + 冪等性未実装 | DDB ConditionExpression + SQS FIFO 多層防御 | §5-6 |
| CW Logs コストが予算超過 | ALL + include_execution_data = true | ERROR + false に変更・予算アラーム設置 | §6-5 |
DynamoDB.ConditionalCheckFailedException が FAILED 計上 | Catch で Fail ステートに誘導 | AlreadyProcessed (Succeed ステート) に Catch | §5-7 |
Terraform type 省略で STANDARD 作成 | デフォルトが STANDARD | type = "EXPRESS" を明示 | §2 |
8-2-2. 本番リリース前チェックリスト
Express Workflow を本番リリースする前に、以下の項目をすべて確認する。
- [ ]
type = "EXPRESS"が Terraform に明示されているか - [ ]
logging_configuration.level = "ERROR"かつinclude_execution_data = falseになっているか - [ ]
log_destinationの末尾が:*になっているか - [ ] CW Logs ingestion 量の月次試算を実施し、予算内に収まっているか (§3)
- [ ] CloudWatch アラームで ingestion 量の異常監視を設定したか (§6-5)
- [ ] 高頻度 API で StartSyncExecution を使う場合、29 秒以内の実行時間を実測で確認したか (§4-3)
- [ ] At-least-once による重複実行シナリオをレビューし、冪等性設計を組込んだか (§5)
- [ ]
StartExecutionにnameパラメータを設定し、実行 ARN を追跡できるようにしたか - [ ] Express は
DescribeExecution不可のため、CW Logs / X-Ray でのトレース手段を準備したか
8-3. 関連記事
本記事を読み終えたら、以下の関連記事でさらに理解を深めることができる。
| # | タイトル | 概要 |
|---|---|---|
| 1 | AWS Step Functions 入門 | Step Functions の基本概念・ステートマシン・ASL 入門 |
| 2 | Step Functions エラーハンドリング完全ガイド | Retry / Catch / Fallback の設計パターン詳解 |
| 3 | Express vs Standard 徹底比較 | 2 種類の違い・選択基準・コスト比較 (本記事の前提) |
| 4 | Step Functions I/O フィルタ設計 | InputPath / OutputPath / ResultPath の完全解説 |
| 5 | Callback / WaitForTaskToken 本番ガイド | 非同期コールバック・人間承認フローの実装 |
| 6 | Distributed Map 本番運用完全ガイド (Vol3) | 大規模並列処理・S3 ItemReader・エラー集計 |
8-4. 次回予告: Vol5 SDK Direct Integration
Vol4 では Express Workflow の呼出タイプ・コスト・冪等性・ログ戦略を網羅した。しかし今回のパターン A〜C では Lambda 経由の処理が随所に登場した。「Lambda を 1 つも使わず Step Functions から直接 AWS サービスを呼び出せるか?」 — これが Vol5 のテーマだ。
AWS SDK Integration (arn:aws:states:::aws-sdk: リソース) を使うと、DynamoDB・SQS・SNS・S3 などを Lambda なしで直接呼び出せる。コールドスタートゼロ・Lambda コストゼロ・ステートマシン定義だけで完結するアーキテクチャの全貌を次回解説する。
- SDK Integration vs Lambda Proxy — どちらを選ぶべきか (判断フロー付き)
- DynamoDB / SQS / SNS / EventBridge への直接統合 Terraform + ASL
- エラーハンドリング・リトライ設計 (Lambda なし構成での落とし穴)
- Express × SDK Integration の本番パターン 3 選と比較表
- コスト比較: Lambda あり vs SDK Direct (ケース別損益分岐点)
前提記事: Express vs Standard 基礎 (Vol1) を先に読んでおくと理解がスムーズ。本記事 Vol4 を読了済みなら追加の前提知識なしで Vol5 に進める。
公開予定: URL は公開後に更新予定 (TODO: Vol5 publish 後差替)
8-5. 次のステップ
→ Vol5 SDK Direct Integration を読む