NO IMAGE

Step Functions Express 本番運用完全ガイド Vol4 5判断軸

NO IMAGE
目次

1. なぜ Express Workflow か

fig01: SF実践編Vol1-Vol4シリーズ全体マップ

この記事で得られること

  • 自ワークロードの月次実行回数・実行時間から 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)
この記事では扱わないこと (関連記事へ委譲)

1-1. 本記事のゴール

本記事は Step Functions 実践編 Vol4 として、Express Workflow を本番の高頻度ワークロードで運用する際に必要な 5 つの設計判断軸を提供します。

Terraform HCL + ASL + CLI 実挙動の 3 点セットで、「どのワークロードで Express をどう呼ぶか」を再現可能な形で解説します。読了後、1000万実行/月規模のワークロードに Express を採用すべきかを即断できる基準と、採用後の運用設計指針が揃います。

読了後に自力で実行できる 5 つのアウトカムを示します。

  1. 自ワークロードの月次実行回数・平均実行時間を入力して Express vs Standard のコスト差を即算出し、経営層への説明資料を作成できる
  2. 同期呼出 (StartSyncExecution) と非同期呼出 (StartExecution) を 5 判断軸で選択し、API Gateway 統合・EventBridge 統合の双方を Terraform で実装できる
  3. Express の At-least-once 配信特性を把握し、SQS FIFO + DynamoDB ConditionExpression で重複実行を防ぐ設計を実装できる
  4. CloudWatch Logs のレベル (ALL/ERROR/OFF) をコスト試算付きで選択し、月次ログ量を事前試算できる
  5. 実戦 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 の位置付けを下表に整理します。

VolWP IDテーマワークフロータイプ
Vol1 (5大入出力フィルタ)1439InputPath/OutputPath/Parameters/ResultPath/Context ObjectStandard
Vol2 (Callback 実践)1449waitForTaskToken による外部システム連携の非同期境界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 点

ID:1101「Express vs Standard 基礎比較」との違い

  • (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 入門1033SF 基礎構文の前提
Retry/Catch/Timeout 完全ガイド1057§5 冪等性と Catch の境界
Express vs Standard 基礎比較1101本記事の前提
5 大入出力フィルタ実践編 Vol11439§4 input transformer 連携
Callback パターン実践編 Vol21449§5 非同期と Callback の使い分け
Distributed Map 本番運用 Vol31488§3 Express child の使い分け

1-6. 執筆方針

  • Terraform HCL + ASL + CLI の 3 点セット: IaC で再現可能な設計を提供します
  • 2026-04 時点の公式料金準拠: 料金数値は時点注記あり。最新値は公式料金ページで確認してください
  • コスト試算式の明示: 自ワークロードに代入して検算できる計算式を提示します

前提環境 (本記事の実機確認環境・2026-04 時点)

ツールバージョン
Terraform1.9.x
hashicorp/aws provider~> 5.0
AWS CLI2.x
確認リージョンap-northeast-1 (本記事例)

各実戦パターン (§7) には terraform init && terraform apply でプロビジョニングできる
Terraform コードと、動作確認に使う CLI コマンドをセットで提供します。
コスト試算に用いる料金は最新の公式ページ (Step Functions 料金) でご確認ください。


2. Express Workflow アーキテクチャ全体像

fig02: Express Workflowアーキテクチャ

2-1. 実行モデル

Express と Standard の最大の違いは実行モデルにあります。

観点ExpressStandard
実行履歴の保持なし (CloudWatch Logs に出力)あり (90 日保持・コンソールで参照可)
最大実行時間5 分1 年
実行保証At-least-onceExactly-once
課金モデルリクエスト + duration + CW LogsState Transition 回数
ネストの実行タイプExpress / Standard 子に可Standard 子のみ

Express は実行履歴を Step Functions の内部ストレージに保持しません。代わりに CloudWatch Logs へ状態遷移ログを出力します。デバッグは CW Logs 経由が基本となります。

Express で実行状態を確認する主な 3 手段を整理します。

確認手段コマンド / 操作取得できる情報
DescribeExecution APIaws 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 KBS3 参照パターンで回避
実行履歴参照不可 (CW Logs のみ)Logs Insights で execution ID 逆引き
Retry 課金Retry 中も duration 課金が継続Retry は最小限・Catch で代替経路
同時実行 TPSアカウントデフォルト 1000 TPSService 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 / GBALL≈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 HTTPLambda 経由で非同期低コスト非同期トリガー
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 は 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 TPS2,500アカウント全体の上限
StartSyncExecution TPS2,500同上
入力ペイロード最大256 KB不可S3 参照パターンで回避

スループット設計の 3 原則:

  1. SQS バッファリング: Lambda が SQS をポーリングし BatchSize=1 または BatchSize=10 で StartExecution を呼び出す。キューがスパイクを吸収し、TPS 超過を防ぐ
  2. 同時実行上限の事前申請: 本番リリース 2 週間前に Service Quota 緩和申請を提出する。承認には 5〜10 営業日かかることがある
  3. 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 倍安
② 呼出タイプ同期 / 非同期どちらか§4API バックエンド → StartSyncExecution / 29秒制約確認
③ 冪等性設計重複実行で損害が出るか§5決済処理 → SQS FIFO + DDB ConditionExpression 必須
④ ログ戦略ALL / ERROR / OFF どれか§6高頻度 API → ERROR 固定・予算アラームとセット
⑤ 実戦パターン呼出元は何か§7EventBridge → パターン 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 WorkflowStandard Workflow設計ポイント
最大実行時間5 分1 年5 分超えは Standard 一択
課金モデルリクエスト数 + duration (GB-s) + CW Logsstate 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,0001/62
B: バッチ (100 万 req・30 秒・ALL)Express$42 ≈ ¥6,300$125 ≈ ¥18,7501/3
C: イベント駆動 (1,000 万 req・2 秒・ERROR)Express$31 ≈ ¥4,650$750 ≈ ¥112,5001/24

3 シナリオとも Express が有利。ただし実行時間が長くなるほど Standard との差が縮まる。シナリオ B で実行時間が 4 分を超えると逆転するケースがある。CW Logs を ALL モードで運用する場合は §6 のログ量試算を必ず実施すること。

fig03: Express vs Standard 選択フロー

3-7. コスト試算の 3 つの落とし穴

⚠️ QG-1: コスト試算で見落としやすい 3 つの落とし穴 (2026-04 時点)

  • 落とし穴①: 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_configurationlog_levelOFF にすると完全にブラックボックスになるため、本番では 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 APIHTTP 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 秒タイムアウト対策

⚠️ QG-4: API Gateway 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 秒を超えるワークロードは非同期化すべき

fig04: StartSyncExecution vs StartExecution 判定フロー

⚠️ 同期/非同期の判断境界: 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 はバックグラウンドで実行される。

項目StartSyncExecutionStartExecution
レスポンス実行完了まで待機即時 (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 とは異なり、インフラ障害やリトライ機構により同一ペイロードが複数回実行される可能性がある。

配信保証ExpressStandard
At-least-once
Exactly-once✅ (実行履歴で一意性保証)
重複実行リスクありなし
冪等性設計の必要性必須任意

EventBridge や SQS から Express を呼ぶ構成では、障害時のリトライで 同じ注文・決済・メール送信が複数回実行されるリスクがある。冪等性設計は後付けではなくアーキテクチャ設計時の必須要件として組み込む。

5-5. 重複実行で発生する 3 類型事故

🚨 QG-2: At-least-once 重複実行の 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 数を汚染しない。

fig05: SQS FIFO+冪等性フロー


6. CloudWatch Logs 設計

fig06: 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-monthStandard クラス (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_level1 実行あたり月次 1000 万実行ingestion 費用storage (30 日)月次合計
ALL約 20KB200GB$100$6$106
ERROR約 5KB50GB$25$1.5$26.5
OFF00$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_levelinclude_execution_data理由
高頻度 API (1 億 req/月)1 億ERRORfalseALL では ingestion $1,000+ のリスク
バッチ (100 万/月・長時間)100 万ALLtrue実行状況逐次確認が重要 / コスト ~$10 以下
イベント駆動 (1000 万/月)1000 万ERRORfalse高頻度・ingestion コスト抑制が必須
開発・デバッグ環境任意ALLtrueステート間の入出力を全件追跡
コスト最重視 (監査不要)任意OFFトレーサビリティを割り切る場合のみ
⚠️ 事故例: CloudWatch Logs ALL 運用で月次コスト 6 桁超過

状況: 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 倍)

対策:

  1. log_level = "ERROR" に変更 → ingestion を $100 以下に圧縮
  2. include_execution_data = false で入出力 JSON を除外
  3. デバッグ時のみ ALL (ステージング限定)、本番は ERROR 固定
  4. CloudWatch アラームで ingestion 量 > 100GB/日 を検知・Slack 通知する予算ガードを設置

6-5. Terraform logging_configuration 設定例

logging_configuration ブロックで levelinclude_execution_datalog_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. パターン選択ガイド

実戦 3 パターン 選択フロー

| 判断軸 | パターン 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 秒)StartSyncExecutionERROR / falseDDB ConditionExpressionA
REST API (≥ 29 秒)StartExecution (非同期)ERROR / falseDDB ConditionExpressionB
バッチ・通知 (非同期)StartExecutionERROR / false不要 (or DDB TTL)B
決済・在庫更新StartExecutionERROR / falseSQS FIFO + DDB 多層C
開発・デバッグどちらでもALL / true
コスト最重視・監査不要StartExecutionOFFSQS FIFO or DDBB/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. トラブルシューティング早見表

本番運用で頻出するエラーと対処を一覧化した。

エラー / 症状原因対処参照
InvalidLoggingConfigurationlog_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 = trueERROR + false に変更・予算アラーム設置§6-5
DynamoDB.ConditionalCheckFailedException が FAILED 計上Catch で Fail ステートに誘導AlreadyProcessed (Succeed ステート) に Catch§5-7
Terraform type 省略で STANDARD 作成デフォルトが STANDARDtype = "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)
  • [ ] StartExecutionname パラメータを設定し、実行 ARN を追跡できるようにしたか
  • [ ] Express は DescribeExecution 不可のため、CW Logs / X-Ray でのトレース手段を準備したか

8-3. 関連記事

本記事を読み終えたら、以下の関連記事でさらに理解を深めることができる。

#タイトル概要
1AWS Step Functions 入門Step Functions の基本概念・ステートマシン・ASL 入門
2Step Functions エラーハンドリング完全ガイドRetry / Catch / Fallback の設計パターン詳解
3Express vs Standard 徹底比較2 種類の違い・選択基準・コスト比較 (本記事の前提)
4Step Functions I/O フィルタ設計InputPath / OutputPath / ResultPath の完全解説
5Callback / WaitForTaskToken 本番ガイド非同期コールバック・人間承認フローの実装
6Distributed Map 本番運用完全ガイド (Vol3)大規模並列処理・S3 ItemReader・エラー集計

8-4. 次回予告: Vol5 SDK Direct Integration

🔜 次回 Vol5: AWS SDK Integration で Lambda をゼロに

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 を読む

→ Vol3 Distributed Map 本番運用を読む

→ Express vs Standard 基礎を復習 (Vol1)