AWS Step Functions入門|ASL×Standard/Express×エラーハンドリング

目次

AWS Step Functions 入門 — 状態機械 × ASL × Standard/Express × エラーハンドリング × 本番運用への道

Step Functions 全体アーキテクチャ|ASL × State種別 × Standard/Express × Catch/Retry

AWS Step Functions シリーズ起点 — 入門編から本番運用までの道筋
本記事は AWS Step Functions を初めて触る中級エンジニア向けの入門編です。状態機械 (State Machine) の本質 × Amazon States Language (ASL) 構文 × Standard vs Express Workflow 選定 × エラーハンドリング (Catch/Retry) の4本柱で、Lambda連鎖の手作業から Step Functions 宣言的オーケストレーションへ移行する設計力を確立します。

本記事の対象

  • Lambda関数の連鎖実装で if-else・try-catch が複雑化したエンジニア
  • 非同期ジョブの状態管理を手作りSQS/DynamoDBで実装している運用チーム
  • Step Functions本番運用 (Callback/Distributed Map/Express/SDK Direct Integration) の基礎を固めたいエンジニア

本記事完遂後の発展: Step Functions本番運用シリーズ (Callback Pattern / Distributed Map / Express vs Standard / SDK Direct Integration) で実本番設計へ。


1. なぜ Step Functions か — Lambda連鎖の限界 + 状態機械が解く3つの課題

マイクロサービス化が進むにつれ、多くのチームが直面するのが「Lambda 連鎖地獄」です。受注処理・メディア変換・機械学習パイプラインなど、複数の非同期処理を手作りで結合しようとした瞬間に、コードベースは状態管理コードに侵食されていきます。

1-1. Lambda 連鎖で直面する5つの壁

壁1: 状態管理コードのビジネスロジック侵食

最もよく見かけるパターンが「オーケストレーター Lambda」です。注文処理を例にとると、1つの Lambda が在庫確認→決済処理→配送手配を逐次呼び出し、途中状態を DynamoDB に書き込みながら進捗を管理します。

def orchestrate_order(event, context):
 order_id = event["order_id"]

 # 在庫確認
 inventory = lambda_client.invoke(FunctionName="check-inventory", ...)
 if inventory["status"] == "out_of_stock":
  update_order_status(order_id, "FAILED_INVENTORY")
  return {"status": "failed"}

 # 決済処理
 payment = lambda_client.invoke(FunctionName="process-payment", ...)
 if payment["status"] == "declined":
  update_order_status(order_id, "FAILED_PAYMENT")
  notify_customer(order_id, "payment_declined")
  return {"status": "failed"}

 # 配送手配
 shipping = lambda_client.invoke(FunctionName="arrange-shipping", ...)
 update_order_status(order_id, "COMPLETED")
 return {"status": "success"}

このコードの問題は、ビジネスロジック (在庫確認・決済・配送) と状態管理コード (update_order_status 呼び出し) が混在し、テストも変更も困難になる点です。

壁2: エラーハンドリングの散在と重複

各 Lambda に try-except が散在し、DLQ (Dead Letter Queue) の設定も Lambda ごとにバラバラになります。再試行ポリシーが統一されないため、一時的な AWS API スロットリングで連鎖失敗が発生します。

壁3: 並列実行の手作りコスト爆発

複数商品の在庫確認を並列化しようとすると、SQS + Lambda + DynamoDB カウンター管理で数百行の「並列実行基盤」を手作りする羽目になります。全完了を待つ join ロジックが特に複雑で、タイムアウト処理と組み合わせると保守不能なコードが生まれます。

壁4: 待ち合わせ時の Lambda 課金浪費

外部 API の非同期コールバック待ちで Lambda を time.sleep(60) で待機させると、Lambda 料金 (100ms 単位課金) が無駄に発生します。15 分の制約も超えられないため、長時間ジョブ (動画エンコード・バッチ ETL) の管理に Lambda は不適です。

壁5: 実行履歴の可視化不能

CloudWatch Logs が Lambda ごとに分散しており、「どの順序でどのステップが成功し、どこで失敗したか」を追跡するには複数ロググループを手動で突き合わせる必要があります。インシデント時の RCA (根本原因分析) に数時間を要します。

Lambda 連鎖の5つの壁 — 実務チェックリスト

  • 壁1: 状態管理コードがビジネスロジックを侵食し、関数が200行超に膨張している
  • 壁2: try-except と DLQ 設定が Lambda ごとに重複し、再試行ポリシーが統一されていない
  • 壁3: 並列実行のために SQS+DynamoDB カウンター管理を手作りしている
  • 壁4: 外部 API 待ちで Lambda を sleep させており、タイムアウト15分制約に悩んでいる
  • 壁5: 実行履歴が CloudWatch Logs 複数グループに散在し、障害追跡に時間がかかる

3項目以上に該当する場合、Step Functions への移行を強く推奨します。

1-2. 状態機械 (State Machine) の本質

状態機械とは、有限個の状態状態間の遷移ルールで処理フローを定義する数学的モデルです。Step Functions はこの概念を AWS マネージドサービスとして実装しており、3つの特性をもたらします。

特性1: 宣言的記述によるロジックとインフラの分離

処理フロー全体を JSON (Amazon States Language) で宣言的に記述します。「次に何をするか」という制御フローが ASL に集約され、各 Lambda はビジネスロジックのみに集中できます。

特性2: 状態履歴の完全保存

Standard Workflow では、全 State の入出力・遷移タイムスタンプ・エラー詳細が最大 1 年間保存されます。AWS Console から実行ツリーをビジュアルで確認でき、障害発生時の RCA が数分で完了します。

特性3: マネージドな長時間ジョブ管理

Callback Pattern (.waitForTaskToken) を使えば、Lambda を起動したまま待機させることなく、外部コールバックを最大 1 年間待ち続けられます。人間の承認フローや外部 SaaS API の非同期応答にも対応します。

1-3. Step Functions が解く3つの課題

課題Lambda 連鎖での対処Step Functions での解決
オーケストレーションLambda 内 if-else 連鎖 + DynamoDB 状態管理ASL 宣言的定義 + Console ビジュアル
長時間ジョブ管理sleep / SQS ポーリング / 手作りカウンターCallback (.waitForTaskToken) / Wait State
失敗時の自動回復各 Lambda に try-except + DLQ 個別設定ASL Catch+Retry 統一管理

1-4. 本記事で得られる4成果

  1. ASL 習熟: State Machine 定義構文を実コード例で習得
  2. Standard/Express 選定力: ユースケース別 Workflow 選択基準の確立
  3. State 種別マスター: Pass/Task/Choice/Wait/Succeed/Fail/Parallel/Map の全 8 種習得
  4. Catch/Retry 本番設計: 実務レベルのエラーハンドリングパターン習得

1-5. Step Functions 基礎運用5原則

Step Functions 基礎運用5原則

  • 原則1: 宣言的記述優先 — 制御フローは ASL に集約し、Lambda はビジネスロジックのみ担当
  • 原則2: Standard Workflow を基本 — 1年状態保持+At-least-once が本番ビジネスプロセスの第一選択
  • 原則3: Catch+Retry をセットで設計 — リカバリ不可能エラーは Catch、一時的失敗は Retry
  • 原則4: 全 Task に TimeoutSeconds 必須 — デフォルト無制限は本番環境で絶対禁止
  • 原則5: IAM 最小権限 — State Machine ごとに Service Role を分離、過剰な * 付与禁止

Serverless本番運用 Vol1 — Lambda × API GW × Step Functions 本番設計


2. Step Functions 基礎概念 — ASL × State種別 × Standard vs Express Workflow

ASL構文と Standard/Express Workflow 選定マトリクス

Step Functions の中核は Amazon States Language (ASL) です。JSON ベースの DSL で State Machine 全体を宣言的に定義します。このセクションでは ASL 構文・State 種別・Workflow タイプ・Service Integration の4軸を解説します。

2-1. State Machine の定義と ASL 構造

State Machine は以下の ASL トップレベル構造で定義されます。

{
  "Comment": "注文処理 State Machine — 在庫確認→決済→配送",
  "StartAt": "CheckInventory",
  "TimeoutSeconds": 3600,
  "States": {
 "CheckInventory": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
  "FunctionName": "arn:aws:lambda:ap-northeast-1:123456789012:function:check-inventory",
  "Payload.$": "$"
},
"Next": "ProcessPayment",
"Catch": [
  {
 "ErrorEquals": ["InventoryError"],
 "Next": "HandleInventoryError"
  }
],
"TimeoutSeconds": 30
 },
 "ProcessPayment": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
  "FunctionName": "arn:aws:lambda:ap-northeast-1:123456789012:function:process-payment",
  "Payload.$": "$"
},
"End": true
 },
 "HandleInventoryError": {
"Type": "Fail",
"Error": "InventoryCheckFailed",
"Cause": "在庫確認で予期しないエラーが発生しました"
 }
  }
}

ASL トップレベルフィールド:

フィールド必須説明
Comment任意State Machine の説明文
StartAt必須最初に実行する State 名
States必須全 State 定義のマップ
TimeoutSeconds推奨State Machine 全体のタイムアウト (秒)
Version任意ASL バージョン (現在は “1.0”)

2-2. State 種別 7+1 — 全 8 種の解説

flowchart TD
 Start([実行開始]) --> TaskState[Task State\nLambda/API呼び出し]
 TaskState --> ChoiceState{Choice State\n条件分岐}
 ChoiceState -->|条件A| PassState[Pass State\nデータ変換]
 ChoiceState -->|条件B| WaitState[Wait State\n時間待機]
 ChoiceState -->|条件C| ParallelState[Parallel State\n並列実行]
 PassState --> SucceedState([Succeed\n正常終了])
 WaitState --> TaskState2[Task State\n後続処理]
 ParallelState --> MapState[Map State\n配列処理]
 TaskState2 --> SucceedState
 MapState --> SucceedState
 TaskState -->|エラー| FailState([Fail\n異常終了])

State 種別一覧:

Pass State — データ変換・固定値挿入

ワークフローの入力をそのまま出力するか、Result フィールドで固定データを挿入します。Lambda を呼ばずにデータ変換だけ行いたい場合に使います。

{
  "InjectDefaults": {
 "Type": "Pass",
 "Result": {
"retry_count": 0,
"environment": "production"
 },
 "ResultPath": "$.defaults",
 "Next": "ValidateInput"
  }
}

Task State — 実処理の実行

最も頻繁に使う State です。Lambda 呼び出し・DynamoDB 操作・SNS Publish・ECS タスク起動など、実際の処理を担います。

{
  "InvokeProcessor": {
 "Type": "Task",
 "Resource": "arn:aws:states:::lambda:invoke",
 "Parameters": {
"FunctionName": "arn:aws:lambda:ap-northeast-1:123456789012:function:data-processor",
"Payload.$": "$"
 },
 "ResultSelector": {
"processed_count.$": "$.Payload.count",
"status.$": "$.Payload.status"
 },
 "ResultPath": "$.result",
 "TimeoutSeconds": 60,
 "Retry": [
{
  "ErrorEquals": ["Lambda.ServiceException", "Lambda.AWSLambdaException"],
  "IntervalSeconds": 2,
  "MaxAttempts": 3,
  "BackoffRate": 2.0
}
 ],
 "Next": "CheckResult"
  }
}

Choice State — 条件分岐

入力データの値に基づいて次の State を決定します。Default フィールドを必ず定義し、未定義条件での実行停止を防ぎます。

{
  "CheckOrderType": {
 "Type": "Choice",
 "Choices": [
{
  "Variable": "$.order_type",
  "StringEquals": "express",
  "Next": "ProcessExpressOrder"
},
{
  "Variable": "$.order_amount",
  "NumericGreaterThan": 100000,
  "Next": "RequireApproval"
}
 ],
 "Default": "ProcessStandardOrder"
  }
}

Wait State — 時間待機

固定秒数またはタイムスタンプまで処理を一時停止します。Lambda を稼働させることなく待機するため、コスト効率が高い待ち合わせ実装です。

{
  "WaitForScheduledTime": {
 "Type": "Wait",
 "TimestampPath": "$.scheduled_at",
 "Next": "ExecuteScheduledJob"
  }
}

Succeed / Fail State — 終端

Succeed は正常終了、Fail はエラー終了を宣言します。Fail には ErrorCause を設定し、デバッグ情報を残します。

Parallel State — 並列実行

複数のブランチを同時実行し、全ブランチの完了を待って次の State に進みます。各ブランチは独立した mini State Machine として動作します。

{
  "ValidateInParallel": {
 "Type": "Parallel",
 "Branches": [
{
  "StartAt": "ValidateInventory",
  "States": {
 "ValidateInventory": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {"FunctionName": "validate-inventory", "Payload.$": "$"},
"End": true
 }
  }
}
 ],
 "Next": "ProcessOrder"
  }
}

Map State — 配列要素の並列処理

入力配列の各要素に対して同じ処理を並列実行します。MaxConcurrency で並列度を制御し、Downstream の Lambda Concurrency Limit を超えないよう設計します。

{
  "ProcessItems": {
 "Type": "Map",
 "ItemsPath": "$.items",
 "MaxConcurrency": 10,
 "Iterator": {
"StartAt": "ProcessSingleItem",
"States": {
  "ProcessSingleItem": {
 "Type": "Task",
 "Resource": "arn:aws:states:::lambda:invoke",
 "Parameters": {"FunctionName": "item-processor", "Payload.$": "$"},
 "End": true
  }
}
 },
 "Next": "AggregateResults"
  }
}

2-3. Standard Workflow vs Express Workflow

Standard vs Express Workflow 詳細比較

| 特性 | Standard Workflow | Express Workflow (Async) | Express Workflow (Sync) |
|——|——————|————————|————————|
| 最大実行時間 | 1 年 | 5 分 | 5 分 |
| 実行セマンティクス | At-least-once | At-most-once | At-most-once |
| 実行履歴保存 | 90 日 (EventBridge) | CloudWatch Logs のみ | CloudWatch Logs のみ |
| 状態遷移上限 | 25,000 回 | 無制限 | 無制限 |
| 料金モデル | 状態遷移回数 | 実行時間 + メモリ | 実行時間 + メモリ |
| 代表ユースケース | ビジネスプロセス・長期ETL | Webhook/IoT イベント | API Gateway 統合 |

Standard Workflow を選ぶ条件:
– 処理が 5 分を超える可能性がある
– 実行履歴を後から追跡・監査する必要がある
– At-least-once (少なくとも 1 回実行保証) が必要
– 人間の承認フロー (Callback Pattern) を含む

Express Workflow を選ぶ条件:
– 処理が常に 5 分以内に完結する
– 高スループット (秒間 1,000 実行以上) が必要
– At-most-once (重複実行の可能性を許容) が受け入れられる
– CloudWatch Logs への実行ログ出力で十分

2-4. Service Integration 種別

Step Functions から AWS サービスを呼び出す方法は3種類あります。

Service Integration 3種別の使い分け

  • Optimized Integration (arn:aws:states:::lambda:invoke 形式): Step Functions が直接 API を最適化呼び出し。Lambda/DynamoDB/SNS/SQS/ECS/Glue など主要サービスで利用可。第一選択
  • SDK Integration (arn:aws:states:::aws-sdk:serviceName:apiAction 形式): AWS SDK の全 API を呼び出し可能。Optimized Integration が未対応サービス (例: Bedrock/Rekognition) に使用。
  • Activity (旧方式): 外部ワーカーがポーリングして Task を取得。EC2/ECS 上の長時間処理向けだが、Callback Pattern (.waitForTaskToken) を推奨

Optimized Integration の例 (DynamoDB PutItem):

{
  "SaveResult": {
 "Type": "Task",
 "Resource": "arn:aws:states:::dynamodb:putItem",
 "Parameters": {
"TableName": "order-results",
"Item": {
  "order_id": {"S.$": "$.order_id"},
  "status": {"S.$": "$.status"},
  "processed_at": {"S.$": "$$.Execution.StartTime"}
}
 },
 "Next": "NotifyCompletion"
  }
}

SDK Integration の例 (Bedrock InvokeModel)arn:aws:states:::aws-sdk:bedrockruntime:invokeModel Resource で呼び出し、ModelIdBody.$ を Parameters に指定します。Optimized Integration が未対応のサービスでこの形式を使います。

2-5. Terraform による State Machine 定義

本番環境では Terraform の aws_sfn_state_machine リソースで State Machine を IaC 管理します。definition フィールドに jsonencode で ASL を記述するのが推奨パターンです。

resource "aws_sfn_state_machine" "order_processor" {
  name  = "order-processor-${var.environment}"
  role_arn = aws_iam_role.sfn_role.arn
  type  = "STANDARD"

  definition = jsonencode({
 Comment  = "注文処理 State Machine"
 StartAt  = "CheckInventory"
 TimeoutSeconds = 3600
 States = {
CheckInventory = {
  Type = "Task"
  Resource= "arn:aws:states:::lambda:invoke"
  Parameters = { FunctionName = aws_lambda_function.check_inventory.arn, "Payload.$" = "$" }
  TimeoutSeconds = 30
  Retry = [{ ErrorEquals = ["Lambda.ServiceException"], IntervalSeconds = 2, MaxAttempts = 3, BackoffRate = 2.0 }]
  Next = "ProcessPayment"
}
ProcessPayment = {
  Type = "Task"
  Resource= "arn:aws:states:::lambda:invoke"
  Parameters = { FunctionName = aws_lambda_function.process_payment.arn, "Payload.$" = "$" }
  TimeoutSeconds = 60
  End = true
}
 }
  })

  logging_configuration {
 log_destination  = "${aws_cloudwatch_log_group.sfn.arn}:*"
 include_execution_data = true
 level= "ALL"
  }
  tracing_configuration { enabled = true }
  tags = { Environment = var.environment, Project = "order-system" }
}

resource "aws_iam_role" "sfn_role" {
  name= "sfn-order-processor-${var.environment}"
  assume_role_policy = jsonencode({
 Version = "2012-10-17"
 Statement = [{ Effect = "Allow", Principal = { Service = "states.amazonaws.com" }, Action = "sts:AssumeRole" }]
  })
}
Terraform IaC 設計の5原則

  • 原則1: jsonencode で ASL を記述し、JSON ファイル分離より Terraform 内集約を優先 (差分が一目瞭然)
  • 原則2: State Machine ごとに専用 IAM Role を作成 (共有 Role は権限過多になりやすい)
  • 原則3: logging_configurationinclude_execution_data = true を標準設定 (障害調査必須)
  • 原則4: tracing_configuration { enabled = true } で X-Ray 連携を有効化
  • 原則5: type = "STANDARD" をデフォルト。Express が必要な場合のみ明示的に切り替える

3. 実装 Vol1 — Hello World × Pass/Task/Choice × Terraform IaC

AWS Step Functions の基礎を実装で固める。最小構成の Hello World から始め、Task・Choice という主要 State Type を深掘りし、Terraform で IaC 化する流れで進める。

実装 Vol1 ベストプラクティス5原則

  • 原則1: State名はキャメルケース + 動詞始まり(CheckOrderStatus / ProcessPayment)
  • 原則2: Task State Resource は Optimized Integration 優先(arn:aws:states::: 形式)
  • 原則3: Choice State の Default 必須(未定義条件で実行停止防止)
  • 原則4: Service Role は State Machine 毎に分離(権限の最小化)
  • 原則5: Terraform で aws_sfn_state_machine + IAM Role + Lambda を統合管理

3.1 Hello World — 最小 State Machine

Step Functions の最小構成は「1つの Pass State と End フラグ」だけで動く。まずこの骨格を理解してから発展させていく。

ASL 最小構成

ASL(Amazon States Language)は JSON で記述するワークフロー定義言語だ。最小の Hello World は以下の通りになる。

{
  "Comment": "最小 State Machine — Hello World",
  "StartAt": "HelloWorld",
  "States": {
 "HelloWorld": {
"Type": "Pass",
"Result": {
  "message": "Hello, Step Functions!"
},
"End": true
 }
  }
}
フィールド説明
Comment人間向けの説明文(実行に影響しない)
StartAt最初に実行する State 名(States 内のキーと一致必須)
TypeState の種別(Pass / Task / Choice / Wait / Succeed / Fail / Parallel / Map)
Endtrue で State Machine 終了。遷移先を持つ場合は Next を使う

AWS CLI での実行

# State Machine を作成
aws stepfunctions create-state-machine \
  --name "HelloWorldMachine" \
  --definition file://hello-world.json \
  --role-arn arn:aws:iam::123456789012:role/StepFunctionsRole \
  --region ap-northeast-1

# 実行開始(Standard Workflow)
aws stepfunctions start-execution \
  --state-machine-arn arn:aws:states:ap-northeast-1:123456789012:stateMachine:HelloWorldMachine \
  --input '{"name": "World"}' \
  --region ap-northeast-1

# 実行結果の確認
aws stepfunctions describe-execution \
  --execution-arn arn:aws:states:ap-northeast-1:123456789012:execution:HelloWorldMachine:run-001 \
  --region ap-northeast-1

start-execution は非同期で返る(Standard Workflow の場合)。describe-executionstatus: SUCCEEDED を確認するか、start-sync-execution コマンドで同期実行(Express Workflow 限定)する。


3.2 Task State 詳解 — Lambda 呼び出し × データフロー制御

Task State は外部サービスを呼び出す中核 State だ。Lambda 呼び出しを例に、データフロー制御の全オプションを押さえていく。

Lambda 呼び出し基本構成

{
  "ProcessOrder": {
 "Type": "Task",
 "Resource": "arn:aws:states:::lambda:invoke",
 "Parameters": {
"FunctionName": "arn:aws:lambda:ap-northeast-1:123456789012:function:ProcessOrderFn",
"Payload.$": "$"
 },
 "ResultSelector": {
"result.$": "$.Payload"
 },
 "ResultPath": "$.lambdaOutput",
 "TimeoutSeconds": 30,
 "HeartbeatSeconds": 10,
 "Retry": [
{
  "ErrorEquals": ["Lambda.ServiceException", "Lambda.AWSLambdaException"],
  "IntervalSeconds": 2,
  "MaxAttempts": 3,
  "BackoffRate": 2.0
}
 ],
 "Catch": [
{
  "ErrorEquals": ["States.ALL"],
  "Next": "HandleError",
  "ResultPath": "$.error"
}
 ],
 "Next": "CheckOrderStatus"
  }
}

arn:aws:states:::lambda:invokeOptimized Integration 形式で、旧来の SDK Integration(arn:aws:lambda:... 形式)より再試行・エラーハンドリングが統合されている。

InputPath / OutputPath / ResultPath の使い分け

Step Functions は State 実行前後でデータを精密にコントロールできる。以下の4段階で処理が進む。

入力 → InputPath でフィルタ → Task 実行 → ResultSelector で整形
  → ResultPath で書き込み位置指定 → OutputPath でフィルタ → 出力
{
  "TransformAndStore": {
 "Type": "Task",
 "Resource": "arn:aws:states:::lambda:invoke",
 "Parameters": {
"FunctionName": "arn:aws:lambda:ap-northeast-1:123456789012:function:TransformFn",
"Payload.$": "$.orderData"
 },
 "InputPath": "$.orderData",
 "ResultSelector": {
"transformedId.$": "$.Payload.id",
"status.$": "$.Payload.status"
 },
 "ResultPath": "$.transformResult",
 "OutputPath": "$.transformResult",
 "Next": "StoreResult"
  }
}
フィールドタイミング説明
InputPathTask 実行前入力の一部だけを Task に渡す(null で空オブジェクト)
ResultSelectorTask 完了後Task 出力の一部を選択・名前変更
ResultPathResultSelector 後元入力のどのパスに結果を書くか(null で入力を完全置換)
OutputPath最後次の State に渡すデータを絞り込む

TimeoutSeconds / HeartbeatSeconds

{
  "LongRunningTask": {
 "Type": "Task",
 "Resource": "arn:aws:states:::lambda:invoke",
 "Parameters": {
"FunctionName": "arn:aws:lambda:ap-northeast-1:123456789012:function:LongRunFn",
"Payload.$": "$"
 },
 "TimeoutSeconds": 300,
 "HeartbeatSeconds": 60,
 "Next": "Done"
  }
}

TimeoutSeconds を超えると States.Timeout エラーが発生し、Catch で捕捉できる。HeartbeatSeconds はアクティビティタスクや .waitForTaskToken パターンで使い、設定間隔内にハートビートが来なければタイムアウト扱いになる。Lambda の実行タイムアウト(最大15分)とは独立して設定できる点に注意。


3.3 Choice State 詳解 — 条件分岐

Choice State は if-else に相当する分岐ステートだ。Choices 配列の各ルールを上から順に評価し、最初にマッチしたルールの Next に遷移する。

基本構成

{
  "RouteOrder": {
 "Type": "Choice",
 "Choices": [
{
  "Variable": "$.order.status",
  "StringEquals": "CANCELLED",
  "Next": "HandleCancellation"
},
{
  "Variable": "$.order.amount",
  "NumericGreaterThan": 10000,
  "Next": "RequireApproval"
},
{
  "And": [
 {
"Variable": "$.order.priority",
"StringEquals": "express"
 },
 {
"Variable": "$.order.items",
"NumericLessThanEquals": 5
 }
  ],
  "Next": "FastTrackProcess"
}
 ],
 "Default": "StandardProcess"
  }
}

Default が未定義のまま全条件に非マッチだと States.NoChoiceMatched エラーで実行失敗する。Default は必ず定義すること。

アンチパターン: Choice State の Default 未定義
Choice State で条件にマッチしない Input が来ると State Machine 実行失敗になる。Default branch を必ず定義し、Fail State へ流すか想定外データを記録する Log Task へ流すのが正解。本番では Default 未定義の State Machine は CI/CD で reject する運用が望ましい。

主要な比較演算子

演算子説明
StringEquals / StringEqualsPath文字列完全一致(Path 付きは比較値も JSONPath 参照)
StringMatches文字列ワイルドカード(* のみ)
NumericGreaterThan / NumericLessThanEquals数値数値比較
BooleanEquals真偽値true / false
IsPresent / IsNull任意フィールドの存在・null 判定
And / Or / Not論理複合条件

3.4 Terraform IaC — State Machine を宣言的に管理

State Machine を Terraform で管理すると、ASL の変更・ロールバック・環境差異が Git で完全に追跡できる。

aws_sfn_state_machine リソース定義

resource "aws_sfn_state_machine" "order_processing" {
  name  = "${var.env}-order-processing"
  role_arn = aws_iam_role.sfn_execution.arn

  # templatefile() で ASL ファイルを外部化し、変数展開
  definition = templatefile("${path.module}/asl/order_processing.json", {
 process_order_fn_arn = aws_lambda_function.process_order.arn
 orders_table_name = aws_dynamodb_table.orders.name
 region= var.aws_region
 account_id  = data.aws_caller_identity.current.account_id
  })

  logging_configuration {
 log_destination  = "${aws_cloudwatch_log_group.sfn_logs.arn}:*"
 include_execution_data = true
 level= "ERROR"
  }

  tracing_configuration {
 enabled = true
  }

  tags = {
 Environment = var.env
 ManagedBy= "terraform"
  }
}

resource "aws_cloudwatch_log_group" "sfn_logs" {
  name  = "/aws/states/${var.env}-order-processing"
  retention_in_days = 30
}

data "aws_caller_identity" "current" {}

templatefile() で ASL を外部化

ASL ファイル内に Terraform 変数を ${変数名} 形式で埋め込む。これにより Lambda ARN などの環境依存値をコードから分離できる。

{
  "Comment": "Order Processing Workflow",
  "StartAt": "ProcessOrder",
  "States": {
 "ProcessOrder": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
  "FunctionName": "${process_order_fn_arn}",
  "Payload.$": "$"
},
"ResultPath": "$.processResult",
"TimeoutSeconds": 30,
"Next": "StoreOrder"
 },
 "StoreOrder": {
"Type": "Task",
"Resource": "arn:aws:states:::dynamodb:putItem",
"Parameters": {
  "TableName": "${orders_table_name}",
  "Item": {
 "orderId": { "S.$": "$.orderId" },
 "status": { "S": "PROCESSED" }
  }
},
"ResultPath": null,
"End": true
 }
  }
}

3.5 IAM Role 最小権限設計

Step Functions は実行時に Service Role を Assume して外部サービスを呼び出す。State Machine ごとにロールを分離し、必要最小限の権限だけを付与する。

Terraform による IAM Role + Policy 定義

# Step Functions 実行ロール
resource "aws_iam_role" "sfn_execution" {
  name = "${var.env}-sfn-order-processing-role"

  assume_role_policy = jsonencode({
 Version = "2012-10-17"
 Statement = [
{
  Effect = "Allow"
  Principal = { Service = "states.amazonaws.com" }
  Action = "sts:AssumeRole"
  Condition = {
 StringEquals = {
"aws:SourceAccount" = data.aws_caller_identity.current.account_id
 }
  }
}
 ]
  })
}

# Lambda 呼び出し権限
resource "aws_iam_role_policy" "sfn_lambda_invoke" {
  name = "lambda-invoke"
  role = aws_iam_role.sfn_execution.id

  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [
{
  Effect= "Allow"
  Action= ["lambda:InvokeFunction"]
  Resource = [
 aws_lambda_function.process_order.arn,
 "${aws_lambda_function.process_order.arn}:*"
  ]
}
 ]
  })
}

# CloudWatch Logs 書き込み権限(ログデリバリー API は Resource: "*" が必要)
resource "aws_iam_role_policy" "sfn_logs" {
  name = "cloudwatch-logs"
  role = aws_iam_role.sfn_execution.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 = "*"
}
 ]
  })
}

# X-Ray トレーシング権限
resource "aws_iam_role_policy" "sfn_xray" {
  name = "xray-tracing"
  role = aws_iam_role.sfn_execution.id

  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [
{
  Effect = "Allow"
  Action = [
 "xray:PutTraceSegments",
 "xray:PutTelemetryRecords",
 "xray:GetSamplingRules",
 "xray:GetSamplingTargets"
  ]
  Resource = "*"
}
 ]
  })
}
IAM Role 設計の4つのポイント

  • ロールは State Machine 毎に分離: 複数 State Machine で同一ロールを共有すると、一方の変更が他方に影響する。命名規則に State Machine 名を含めて明確に分離する。
  • Lambda ARN は具体的に指定: Resource に * を使わず、呼び出す Lambda の ARN を明示する。バージョン/エイリアスも考慮して :* サフィックスを付けると安全。
  • Condition で SourceAccount を指定: aws:SourceAccount Condition を付けると混乱した代理(Confused Deputy)攻撃を防げる。
  • CloudWatch Logs 権限は Resource: “*” が必要: ログデリバリー API はリソース ARN 指定に対応していないため "*" が必要。これは AWS の制約であり設計ミスではない。

4. 実装 Vol2 — Parallel × Map × Wait × Catch × Retry

Parallel/Map/Catch/Retry 並列度設計とエラー伝播フロー

実装 Vol2 ベストプラクティス5原則

  • 原則1: Retry は MaxAttempts=3 + BackoffRate=2.0 + IntervalSeconds=2 が標準
  • 原則2: Catch で States.ALL を最後に置きフォールバック確保
  • 原則3: Parallel ブランチは独立性確保 (相互依存禁止・別Map検討)
  • 原則4: Map State MaxConcurrency は Downstream Quota で決定 (例: Lambda 1000 → 100並列)
  • 原則5: Wait State は タイムスタンプ ベース推奨 (秒数ベースは経路依存性大)
Catch+Retry パターン選定マトリクス

  • 一時的失敗 (Throttling/Network): Retry 3回 + BackoffRate=2.0 / Catch不要
  • 永続的失敗 (Invalid Input): Retry なし / Catch でFail Stateへ
  • 業務的失敗 (Order未承認): Retry なし / Catch でAlternative Branchへ
  • 不明エラー: Retry 1回 + Catch States.ALL でDLQ Taskへ

4-1. Parallel State — 複数ブランチの並列実行

Parallel State は複数のブランチを同時実行し、全ブランチの完了を待って次の State へ進む。注文処理で在庫確認・与信チェック・配送見積もりを同時実行するようなシナリオで威力を発揮する。

{
  "ProcessOrderParallel": {
 "Type": "Parallel",
 "Branches": [
{
  "StartAt": "CheckInventory",
  "States": {
 "CheckInventory": {
"Type": "Task",
"Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:CheckInventory",
"TimeoutSeconds": 10,
"End": true
 }
  }
},
{
  "StartAt": "CheckCredit",
  "States": {
 "CheckCredit": {
"Type": "Task",
"Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:CheckCredit",
"TimeoutSeconds": 15,
"End": true
 }
  }
},
{
  "StartAt": "EstimateShipping",
  "States": {
 "EstimateShipping": {
"Type": "Task",
"Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:EstimateShipping",
"TimeoutSeconds": 10,
"End": true
 }
  }
}
 ],
 "ResultPath": "$.parallelResults",
 "Next": "MergeResults"
  }
}

ブランチ失敗時の挙動は重要な設計ポイントだ。いずれか1ブランチが失敗すると、Parallel State 全体が即座に失敗状態へ遷移し、実行中の他ブランチはキャンセルされる。このため、各ブランチには独立した Catch/Retry を設けることが推奨される。

ResultPath による結果結合では、各ブランチの出力が配列としてまとめられる。"ResultPath": "$.parallelResults" と指定すると、元の入力 JSON を保持しつつ parallelResults キーに各ブランチの出力配列が追加される。

{
  "orderId": "ORD-001",
  "parallelResults": [
 { "inventoryStatus": "available", "quantity": 10 },
 { "creditStatus": "approved", "limit": 50000 },
 { "estimatedDays": 2, "carrier": "JP-Express" }
  ]
}

Parallel State の同時実行上限は最大40ブランチ。40を超える場合は Map State への分割を検討する。


4-2. Map State — 配列要素への繰り返し処理

Map State は配列の各要素に対して同じ State Machine サブフローを適用する。大量注文の一括処理や、複数ファイルの並列変換処理などに適している。

{
  "ProcessAllOrders": {
 "Type": "Map",
 "ItemsPath": "$.orders",
 "Parameters": {
"orderId.$": "$$.Map.Item.Value.orderId",
"customerId.$": "$$.Map.Item.Value.customerId",
"executionId.$": "$$.Execution.Id"
 },
 "MaxConcurrency": 10,
 "Iterator": {
"StartAt": "ProcessSingleOrder",
"States": {
  "ProcessSingleOrder": {
 "Type": "Task",
 "Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:ProcessOrder",
 "TimeoutSeconds": 30,
 "Retry": [
{
  "ErrorEquals": ["Lambda.ServiceException", "Lambda.TooManyRequestsException"],
  "IntervalSeconds": 2,
  "MaxAttempts": 3,
  "BackoffRate": 2.0
}
 ],
 "End": true
  }
}
 },
 "ResultPath": "$.processedOrders",
 "Next": "SendBatchSummary"
  }
}

ItemsPath は処理対象配列のパスを指定する。デフォルトは $ (入力全体が配列前提)。通常は $.orders のように配列フィールドを明示する。

Parameters では $$.Map.Item.Value を使って現在の配列要素にアクセスできる。$$.Map.Item.Index で0始まりのインデックスも取得可能だ。$$.Execution.Id のように実行固有の値を引き渡すことで、各サブフローで一意識別が行える。

MaxConcurrency は並列実行数の上限を制御する。

MaxConcurrency 設定指針

  • MaxConcurrency=0: 無制限並列 (配列全要素を同時実行)。Downstream の Quota に注意
  • MaxConcurrency=1: 順次実行 (相互依存があるケースや DynamoDB 書き込み競合回避)
  • 推奨デフォルト: 10〜50: Lambda 同時実行数 Quota (デフォルト1000) の 1〜5% を目安に設定
  • 大規模バッチ: Distributed Map: Map State 上限 40 items/s を超える場合は Distributed Map (inline mode) へ移行

4-3. Wait State — Lambda Sleep不要の時間制御

Wait State は指定時間が経過するまで実行を一時停止する。Lambda 内で time.sleep() を使うと待機中もLambda課金が発生するが、Wait State なら Step Functions 側で管理するためLambda課金ゼロで時間制御が実現できる。

{
  "WaitForApprovalWindow": {
 "Type": "Wait",
 "Seconds": 3600,
 "Next": "CheckApprovalStatus"
  },
  "WaitUntilScheduledTime": {
 "Type": "Wait",
 "TimestampPath": "$.scheduledAt",
 "Next": "ExecuteScheduledJob"
  },
  "WaitDynamicSeconds": {
 "Type": "Wait",
 "SecondsPath": "$.retryAfterSeconds",
 "Next": "RetryAPICall"
  }
}

Seconds: 固定秒数での待機。シンプルだが「いつ開始したかによって完了時刻が変わる」という経路依存性があるため、スケジュール実行には不向き。

Timestamp / TimestampPath: ISO 8601 形式の絶対時刻まで待機。"2026-05-20T10:00:00Z" のような固定タイムスタンプを直接指定するか、入力データから動的に取得できる。スケジュールジョブやビジネスサービス時間との連携で威力を発揮する。

SecondsPath: 入力データの数値フィールドから動的に待機秒数を取得。外部 API が Retry-After ヘッダで待機時間を指定するケースなどで活用できる。


4-4. Catch — エラータイプ別ハンドリング

Catch は Task や Parallel、Map が失敗したときにどの State へ遷移するかを宣言する。複数の Catch エントリを並べると、上から順に評価され最初にマッチしたエントリが適用される。

{
  "ProcessPayment": {
 "Type": "Task",
 "Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:ProcessPayment",
 "TimeoutSeconds": 30,
 "Catch": [
{
  "ErrorEquals": ["PaymentDeclinedException"],
  "ResultPath": "$.errorInfo",
  "Next": "HandleDeclinedPayment"
},
{
  "ErrorEquals": ["States.Timeout"],
  "ResultPath": "$.errorInfo",
  "Next": "HandleTimeout"
},
{
  "ErrorEquals": ["States.ALL"],
  "ResultPath": "$.errorInfo",
  "Next": "HandleUnknownError"
}
 ],
 "Next": "ConfirmOrder"
  }
}

ErrorEquals にはエラータイプの配列を指定する。主要なエラータイプを以下に整理する。

エラータイプ発生条件
States.ALLすべてのエラーにマッチ (最後に置く全捕捉)
States.TimeoutTimeoutSeconds を超過
States.TaskFailedTask が失敗 (Lambdaが例外スロー等)
States.HeartbeatTimeoutHeartbeatSeconds 超過 (Activity/Callback)
States.RuntimeASL 実行時エラー (型不一致等)
カスタムエラーLambda が raise CustomError でスローした名前

ResultPath でエラー情報を引き回すパターンでは "ResultPath": "$.errorInfo" と指定することで、元の入力データを保持しつつエラー情報を追加できる。"ResultPath": null にするとエラー情報を破棄して元の入力のみを渡す。エラー情報には CauseError フィールドが含まれる。

{
  "errorInfo": {
 "Error": "PaymentDeclinedException",
 "Cause": "{\"message\": \"Card declined: insufficient funds\", \"code\": \"DECLINED\"}"
  }
}

Catch → Fallback Task パターンは本番環境で必須の設計だ。HandleDeclinedPayment State では代替決済手段の提示や、DLQ への送信、アラート通知など業務フローに応じたフォールバック処理を実装する。


4-5. Retry — 一時的失敗の自動再試行

Retry は Task 失敗時に自動的に再試行を行う宣言的な仕組みだ。Lambda スロットリングやネットワーク瞬断など一時的な失敗を Task ロジックに手を入れずに吸収できる。

{
  "CallExternalAPI": {
 "Type": "Task",
 "Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:CallAPI",
 "TimeoutSeconds": 30,
 "Retry": [
{
  "ErrorEquals": ["Lambda.TooManyRequestsException", "Lambda.ServiceException"],
  "IntervalSeconds": 2,
  "MaxAttempts": 3,
  "BackoffRate": 2.0,
  "MaxDelaySeconds": 30
},
{
  "ErrorEquals": ["States.Timeout"],
  "IntervalSeconds": 5,
  "MaxAttempts": 2,
  "BackoffRate": 1.5
}
 ],
 "Catch": [
{
  "ErrorEquals": ["States.ALL"],
  "ResultPath": "$.retryExhaustedError",
  "Next": "HandleAPIFailure"
}
 ],
 "Next": "ProcessAPIResponse"
  }
}

各パラメータの意味を整理する。

パラメータ説明推奨値
IntervalSeconds初回リトライまでの待機秒数2〜5秒
MaxAttempts最大リトライ回数 (0=リトライなし)3回
BackoffRate待機時間の倍率 (指数バックオフ)2.0
MaxDelaySeconds待機時間の上限 (秒)30〜60秒
JitterStrategyランダムゆらぎ (FULL or NONE)FULL 推奨

指数バックオフの計算式: 待機時間 = min(IntervalSeconds × BackoffRate^(試行回数-1), MaxDelaySeconds) となる。IntervalSeconds=2, BackoffRate=2.0 の場合、1回目=2秒、2回目=4秒、3回目=8秒。

冪等性設計 (Retry安全なLambda設計原則) は Retry を使う前提で必須の考慮点だ。同一入力で複数回呼ばれても副作用が発生しない設計が必要になる。

冪等性確保の実装パターン3選

  • べき等キー (Idempotency Key): orderId + executionId を DynamoDB に記録し、処理済みなら結果をそのまま返す。AWS Lambda Powertools の @idempotent デコレータで実装が容易。
  • 条件付き書き込み: DynamoDB condition_expression="attribute_not_exists(pk)" で二重書き込みを防ぐ。条件失敗は例外ではなくスキップとして扱う。
  • 状態チェック先行: 処理前に現在状態を確認し「すでに完了済み」なら処理をスキップして成功を返す。外部API呼び出しの前段チェックに有効。

Retry + Catch 組み合わせパターンでは、Retry が先に評価され MaxAttempts を使い果たすと Catch へフォールアウトする。上記コード例では Lambda スロットリングを最大3回リトライし、それでも失敗した場合に HandleAPIFailure Catch フォールバックへ遷移する。


4-6. mermaid02: Catch+Retry 評価フロー

Task 失敗時に Step Functions がどの順序で Retry と Catch を評価するかを図示する。

flowchart TD
 A([Task 実行]) --> B{成功?}
 B -- はい --> C([次の State へ進む])
 B -- いいえ --> D{Retry 定義あり?}
 D -- なし --> G
 D -- あり --> E{MaxAttempts\n残数 > 0?}
 E -- あり --> F[IntervalSeconds × BackoffRate^N 待機]
 F --> A
 E -- 残数=0 --> G{Catch 定義あり?}
 G -- なし --> H([Execution 失敗])
 G -- あり --> I{ErrorEquals\nマッチ?}
 I -- マッチなし\n次の Catch へ --> I
 I -- 全 Catch 不一致 --> H
 I -- マッチ --> J[ResultPath に\nエラー情報追加]
 J --> K([Fallback State へ遷移])

評価の順序はRetry → Catchであり、Retry が消耗した後に初めて Catch が評価される。同一 ErrorEquals エントリに対して Retry と Catch が両方定義されている場合、Retry が優先される点を押さえておく。


4-7. 並列度設計 — MaxConcurrency 選定基準

Parallel State と Map State の並列度設計は、Downstream サービスの Quota と処理特性の両面から決定する。

シナリオ推奨 MaxConcurrency根拠
Lambda 連携 (標準)50〜100Lambda 同時実行数デフォルト Quota 1000 の 5〜10%
DynamoDB 書き込み10〜25WCU 消費とバースト Quota を考慮
外部 REST API 連携5〜20レート制限値の 10〜20% で余裕確保
S3 Put 操作100〜500S3 prefix 分散で高スループット可能
RDS/Aurora 直接接続5〜10コネクション数制限が律速
順次依存処理1前の結果が次の入力になる場合

Parallel State の最大ブランチ数は40。これを超える並列処理が必要な場合は Map State (MaxConcurrency=N) への設計変更を検討する。Map State は配列入力を前提とした設計になるため、データ構造の統一が必要になる。

バースト制限への対処: Lambda のバースト上限 (ap-northeast-1: 初期3000、以降 +500/分) に対して MaxConcurrency が高すぎると、バースト上限到達後にスロットリングが発生し Retry が連鎖する。新規デプロイ直後は MaxConcurrency を低めに設定し、Lambda がウォームアップした後に段階的に引き上げるアプローチが安全だ。

並列度設計 チェックリスト

  • Downstream サービスの同時接続数・レート制限を事前確認 (CloudWatch Metrics / Service Quotas コンソール)
  • Map State 処理件数 × MaxConcurrency × 1件あたりの処理時間で総実行時間を試算
  • Retry + バックオフが重なった場合の最大並列数をシミュレーション (MaxAttempts=3 なら最大3倍のスパイク)
  • Distributed Map (inline/S3-csv モード) は1億件を超えるバッチ処理向け — 通常の Map State と使い分ける
  • 本番投入前に小規模データで MaxConcurrency=1 → 10 → 本番値と段階的に引き上げてテスト

5. 本番運用パターン — Terraform IaC × CloudWatch監視 × X-Ray連携 × EventBridge Schedule統合

Step Functions 本番運用 Terraform IaC + CloudWatch + X-Ray 統合

本番環境で Step Functions を安定稼働させるには、インフラのコード化・可観測性の確保・定期実行の自動化という3つの柱が欠かせない。このセクションでは Terraform を使った完全な IaC 実装から、CloudWatch 監視・X-Ray 分散トレース・EventBridge Schedule 統合まで、実プロジェクトで使えるコード付きで解説する。


5-1. Terraform IaC 完全実装

ディレクトリ構成

step-functions/
├── main.tf
├── variables.tf
├── outputs.tf
├── iam.tf
├── cloudwatch.tf
├── scheduler.tf
└── state-machine/
 └── order-processing.asl.json

ASL 定義は state-machine/ ディレクトリに分離し、Terraform の templatefile() 関数で読み込む。これにより ASL の diff が単独ファイルで確認でき、PR レビューが容易になる。


main.tf — State Machine 定義

terraform {
  required_version = ">= 1.5"
  required_providers {
 aws = {
source  = "hashicorp/aws"
version = "~> 5.0"
 }
  }
}

provider "aws" {
  region = var.aws_region
}

# -------------------------------------------------------
# Step Functions State Machine
# -------------------------------------------------------
resource "aws_sfn_state_machine" "order_processing" {
  name  = "${var.env}-order-processing"
  role_arn = aws_iam_role.sfn_execution.arn
  type  = var.state_machine_type  # STANDARD or EXPRESS

  # ASL を外部ファイルから templatefile() で読み込む
  definition = templatefile(
 "${path.module}/state-machine/order-processing.asl.json",
 {
validate_order_lambda_arn  = var.validate_order_lambda_arn
process_payment_lambda_arn = var.process_payment_lambda_arn
notify_lambda_arn = var.notify_lambda_arn
max_concurrency= var.map_max_concurrency
 }
  )

  # CloudWatch Logs 統合
  logging_configuration {
 log_destination  = "${aws_cloudwatch_log_group.sfn_logs.arn}:*"
 include_execution_data = true
 level= var.log_level  # ALL / ERROR / FATAL / OFF
  }

  # X-Ray 統合
  tracing_configuration {
 enabled = var.xray_tracing_enabled
  }

  tags = local.common_tags
}

variables.tf

variable "env" {
  description = "デプロイ環境 (dev / stg / prd)"
  type  = string
  validation {
 condition  = contains(["dev", "stg", "prd"], var.env)
 error_message = "env は dev / stg / prd のいずれかを指定してください。"
  }
}

variable "aws_region" {
  description = "AWSリージョン"
  type  = string
  default  = "ap-northeast-1"
}

variable "state_machine_type" {
  description = "State Machine タイプ (STANDARD or EXPRESS)"
  type  = string
  default  = "STANDARD"
  validation {
 condition  = contains(["STANDARD", "EXPRESS"], var.state_machine_type)
 error_message = "state_machine_type は STANDARD または EXPRESS を指定してください。"
  }
}

variable "log_level" {
  description = "CloudWatch Logs のログレベル (ALL / ERROR / FATAL / OFF)"
  type  = string
  default  = "ALL"
}

variable "xray_tracing_enabled" {
  description = "X-Ray トレースを有効化するか"
  type  = bool
  default  = true
}

variable "map_max_concurrency" {
  description = "Map State の最大並列数 (0 = 無制限)"
  type  = number
  default  = 10
}

variable "validate_order_lambda_arn" {
  description = "注文検証 Lambda の ARN"
  type  = string
}

variable "process_payment_lambda_arn" {
  description = "決済処理 Lambda の ARN"
  type  = string
}

variable "notify_lambda_arn" {
  description = "通知 Lambda の ARN"
  type  = string
}

variable "alert_email" {
  description = "CloudWatch Alarm 通知先メールアドレス"
  type  = string
}

locals {
  common_tags = {
 Environment = var.env
 ManagedBy= "terraform"
 Service  = "step-functions"
  }
}

outputs.tf

output "state_machine_arn" {
  description = "State Machine の ARN"
  value = aws_sfn_state_machine.order_processing.arn
}

output "state_machine_name" {
  description = "State Machine の名前"
  value = aws_sfn_state_machine.order_processing.name
}

output "log_group_name" {
  description = "CloudWatch Logs グループ名"
  value = aws_cloudwatch_log_group.sfn_logs.name
}

iam.tf — 最小権限 IAM Role

# Step Functions 実行ロール
resource "aws_iam_role" "sfn_execution" {
  name = "${var.env}-sfn-order-processing-role"

  assume_role_policy = jsonencode({
 Version = "2012-10-17"
 Statement = [
{
  Effect = "Allow"
  Principal = {
 Service = "states.amazonaws.com"
  }
  Action = "sts:AssumeRole"
  Condition = {
 ArnLike = {
"aws:SourceArn" = "arn:aws:states:${var.aws_region}:${data.aws_caller_identity.current.account_id}:stateMachine:*"
 }
  }
}
 ]
  })

  tags = local.common_tags
}

data "aws_caller_identity" "current" {}

# Lambda 呼び出し権限
resource "aws_iam_role_policy" "sfn_lambda_invoke" {
  name = "${var.env}-sfn-lambda-invoke"
  role = aws_iam_role.sfn_execution.id

  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [
{
  Effect = "Allow"
  Action = [
 "lambda:InvokeFunction"
  ]
  Resource = [
 var.validate_order_lambda_arn,
 var.process_payment_lambda_arn,
 var.notify_lambda_arn,
 "${var.validate_order_lambda_arn}:*",
 "${var.process_payment_lambda_arn}:*",
 "${var.notify_lambda_arn}:*"
  ]
}
 ]
  })
}

# CloudWatch Logs 書き込み権限
resource "aws_iam_role_policy" "sfn_cloudwatch_logs" {
  name = "${var.env}-sfn-cloudwatch-logs"
  role = aws_iam_role.sfn_execution.id

  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [
{
  Effect = "Allow"
  Action = [
 "logs:CreateLogDelivery",
 "logs:CreateLogGroup",
 "logs:CreateLogStream",
 "logs:DescribeLogGroups",
 "logs:DescribeLogStreams",
 "logs:DescribeResourcePolicies",
 "logs:GetLogDelivery",
 "logs:ListLogDeliveries",
 "logs:PutLogEvents",
 "logs:PutResourcePolicy",
 "logs:UpdateLogDelivery"
  ]
  Resource = "*"
}
 ]
  })
}

# X-Ray トレース書き込み権限
resource "aws_iam_role_policy_attachment" "sfn_xray" {
  role = aws_iam_role.sfn_execution.name
  policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
}
IAM Role 設計の要点

  • Resource ARN 明示: Lambda ARN をワイルドカードでなく個別リソース指定。最小権限の原則を徹底する
  • Condition で SourceArn 制限: 他 State Machine からの横断呼び出しを防止
  • State Machine 毎に独立ロール: 1 State Machine = 1 Role で責任範囲を明確化
  • CloudWatch Logs 権限は専用 inline policy: ログ設定変更時の影響範囲を局所化

5-2. CloudWatch Logs 統合とログレベル選択基準

cloudwatch.tf — ロググループとアラーム

# -------------------------------------------------------
# CloudWatch Logs Group
# -------------------------------------------------------
resource "aws_cloudwatch_log_group" "sfn_logs" {
  name  = "/aws/states/${var.env}-order-processing"
  retention_in_days = var.env == "prd" ? 90 : 14

  tags = local.common_tags
}

# -------------------------------------------------------
# CloudWatch Alarms — 本番監視 4指標
# -------------------------------------------------------
resource "aws_sns_topic" "sfn_alert" {
  name = "${var.env}-sfn-alert"
}

resource "aws_sns_topic_subscription" "sfn_alert_email" {
  topic_arn = aws_sns_topic.sfn_alert.arn
  protocol  = "email"
  endpoint  = var.alert_email
}

# ExecutionsFailed アラーム
resource "aws_cloudwatch_metric_alarm" "sfn_executions_failed" {
  alarm_name = "${var.env}-sfn-executions-failed"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 1
  metric_name= "ExecutionsFailed"
  namespace  = "AWS/States"
  period  = 300
  statistic  = "Sum"
  threshold  = 1
  alarm_description= "Step Functions 実行失敗を検知"
  treat_missing_data  = "notBreaching"

  dimensions = {
 StateMachineArn = aws_sfn_state_machine.order_processing.arn
  }

  alarm_actions = [aws_sns_topic.sfn_alert.arn]
  ok_actions = [aws_sns_topic.sfn_alert.arn]
}

# ExecutionsTimedOut アラーム
resource "aws_cloudwatch_metric_alarm" "sfn_executions_timed_out" {
  alarm_name = "${var.env}-sfn-executions-timed-out"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 1
  metric_name= "ExecutionsTimedOut"
  namespace  = "AWS/States"
  period  = 300
  statistic  = "Sum"
  threshold  = 1
  alarm_description= "Step Functions タイムアウト発生を検知"
  treat_missing_data  = "notBreaching"

  dimensions = {
 StateMachineArn = aws_sfn_state_machine.order_processing.arn
  }

  alarm_actions = [aws_sns_topic.sfn_alert.arn]
}

# ExecutionThrottled アラーム
resource "aws_cloudwatch_metric_alarm" "sfn_executions_throttled" {
  alarm_name = "${var.env}-sfn-executions-throttled"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 2
  metric_name= "ExecutionThrottled"
  namespace  = "AWS/States"
  period  = 60
  statistic  = "Sum"
  threshold  = 5
  alarm_description= "Step Functions スロットリング多発を検知"
  treat_missing_data  = "notBreaching"

  dimensions = {
 StateMachineArn = aws_sfn_state_machine.order_processing.arn
  }

  alarm_actions = [aws_sns_topic.sfn_alert.arn]
}

# ExecutionTime P99 アラーム (ミリ秒)
resource "aws_cloudwatch_metric_alarm" "sfn_execution_time_p99" {
  alarm_name = "${var.env}-sfn-execution-time-p99"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 3
  extended_statistic  = "p99"
  metric_name= "ExecutionTime"
  namespace  = "AWS/States"
  period  = 300
  threshold  = 30000  # 30秒
  alarm_description= "Step Functions P99 実行時間が 30 秒超過"
  treat_missing_data  = "notBreaching"

  dimensions = {
 StateMachineArn = aws_sfn_state_machine.order_processing.arn
  }

  alarm_actions = [aws_sns_topic.sfn_alert.arn]
}

ログレベル選択基準

レベル記録される実行イベント推奨ユースケース
ALL全イベント (入力・出力・遷移・エラー)開発・ステージング・本番デバッグ時
ERRORFailed / Aborted / Timeout のみ本番でコスト最適化が必要な場合
FATALAborted のみ極めて高頻度実行で Logs コストを最小化
OFF記録なしExpress Workflow で独自ログ実装済みの場合
ログレベルは本番でも ALL を推奨する理由

CloudWatch Logs の料金は GB 単位課金。Step Functions の ASL ログは平均数 KB/実行と軽量なため、中規模以下(月 100 万回未満)であれば ALL と ERROR のコスト差は僅少。一方、障害発生時に ERROR レベルでは どの State で何が入力されていたかが追跡できず、根本原因特定に数時間を要することがある。本番でも ALL 推奨。


5-3. X-Ray 連携 — 分散トレース設定

X-Ray トレースの有効化

Terraform での設定は main.tftracing_configuration ブロックで完結する(前掲コード参照)。ASL 側の追加記述は不要。

tracing_configuration {
  enabled = true  # これだけで X-Ray トレース開始
}

Lambda + Step Functions 統合トレース

Lambda 関数側にも X-Ray トレースを有効化することで、State Machine 全体のサービスマップが自動生成される。

resource "aws_lambda_function" "validate_order" {
  function_name = "${var.env}-validate-order"
  # ... 他の設定 ...

  tracing_config {
 mode = "Active"  # PassThrough ではなく Active を指定
  }
}

Active モードと PassThrough モードの違い:

モード動作推奨場面
Active常にサンプリング・トレース送信Step Functions 統合 (トレース漏れを防止)
PassThrough上流からの X-Ray ヘッダーがある場合のみ追跡単独 API 呼び出しが多い高頻度 Lambda
X-Ray で確認できる情報

  • Service Map: Step Functions → Lambda → DynamoDB の依存グラフを可視化
  • Trace Timeline: 各 State の実行時間内訳をウォーターフォール表示
  • エラー発生箇所の特定: どの State のどの Lambda 呼び出しで例外が発生したか
  • Cold Start 検出: Lambda の Init フェーズをトレース上で識別可能

CloudWatch コンソールの Application SignalsService Map から State Machine 実行のトレースを視覚的に確認できる。X-Ray コンソールでは実行単位のトレース詳細(各 State の入出力を含む)が参照可能。


5-4. EventBridge Schedule 統合 — 定期実行設定

scheduler.tf

# -------------------------------------------------------
# EventBridge Scheduler — Step Functions 定期実行
# -------------------------------------------------------

# Scheduler 実行ロール
resource "aws_iam_role" "scheduler_sfn" {
  name = "${var.env}-scheduler-sfn-role"

  assume_role_policy = jsonencode({
 Version = "2012-10-17"
 Statement = [
{
  Effect = "Allow"
  Principal = {
 Service = "scheduler.amazonaws.com"
  }
  Action = "sts:AssumeRole"
  Condition = {
 StringEquals = {
"aws:SourceAccount" = data.aws_caller_identity.current.account_id
 }
  }
}
 ]
  })
}

resource "aws_iam_role_policy" "scheduler_sfn_start" {
  name = "${var.env}-scheduler-sfn-start"
  role = aws_iam_role.scheduler_sfn.id

  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [
{
  Effect= "Allow"
  Action= "states:StartExecution"
  Resource = aws_sfn_state_machine.order_processing.arn
}
 ]
  })
}

# 日次バッチ処理 スケジュール (毎日 02:00 JST)
resource "aws_scheduler_schedule" "daily_order_batch" {
  name = "${var.env}-daily-order-batch"
  group_name = "default"

  flexible_time_window {
 mode = "FLEXIBLE"
 maximum_window_in_minutes = 5  # 2:00〜2:05 JST の間に実行
  }

  # cron 式: UTC 17:00 = JST 02:00
  schedule_expression = "cron(0 17 * * ? *)"
  schedule_expression_timezone = "Asia/Tokyo"

  target {
 arn= aws_sfn_state_machine.order_processing.arn
 role_arn = aws_iam_role.scheduler_sfn.arn

 input = jsonencode({
batch_date  = "<aws.scheduler.scheduled-time>"
trigger  = "scheduled"
environment = var.env
 })

 retry_policy {
maximum_event_age_in_seconds = 3600
maximum_retry_attempts = 2
 }

 dead_letter_config {
arn = aws_sqs_queue.scheduler_dlq.arn
 }
  }
}

# Dead Letter Queue (実行失敗時のイベント保存)
resource "aws_sqs_queue" "scheduler_dlq" {
  name = "${var.env}-sfn-scheduler-dlq"
  message_retention_seconds = 86400  # 24時間保持
}

Cron 式 vs Rate 式 比較

形式書式例用途特徴
Croncron(0 17 * * ? *)特定時刻に実行曜日・日付指定可。? で曜日/日フィールドの一方を無視
Raterate(1 hour)一定間隔で実行相対間隔。初回実行後から計算
Croncron(0 9 ? * MON-FRI *)平日 09:00 実行週次・月次バッチに最適
Raterate(5 minutes)5 分毎のポーリング短周期ジョブ。EventBridge Pipe との組み合わせも多い

注意: EventBridge Scheduler の Cron 式は CloudWatch Events と書式が異なる。? ワイルドカードは曜日フィールドか日フィールドのどちらか一方にのみ使用可能。

失敗時 SNS 通知の連携

スケジュール実行が DLQ に落ちた場合の通知は、別途 CloudWatch Alarm → SNS で補完する。

resource "aws_cloudwatch_metric_alarm" "scheduler_dlq_depth" {
  alarm_name = "${var.env}-sfn-scheduler-dlq-depth"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 1
  metric_name= "ApproximateNumberOfMessagesVisible"
  namespace  = "AWS/SQS"
  period  = 60
  statistic  = "Sum"
  threshold  = 1
  alarm_description= "Scheduler DLQ にメッセージが滞留: スケジュール実行失敗の可能性"
  treat_missing_data  = "notBreaching"

  dimensions = {
 QueueName = aws_sqs_queue.scheduler_dlq.name
  }

  alarm_actions = [aws_sns_topic.sfn_alert.arn]
}

5-5. 本番 Quota 管理

Step Functions には以下のサービスクォータが存在する。本番設計前に必ず確認すること。

クォータ項目StandardExpress上限緩和可否
同時実行数1,000,000 実行100,000 リクエスト/秒
状態遷移数/秒5,000/秒無制限可 (Standard)
実行履歴イベント数25,000 イベント/実行ログのみ (実行履歴なし)不可
実行時間上限1 年5 分不可
ASL 定義サイズ1 MB1 MB不可
Quota 超過で起きる典型障害

  • 状態遷移 5,000/秒超過: Map State で大量アイテムを MaxConcurrency 無制限で処理すると一瞬で上限到達。ExecutionThrottled が急増し、Retry が雪崩式に発生する
  • 25,000 イベント/実行上限: 1 回の実行で 25,000 件の State 遷移を超えると実行が強制 Aborted。ループ数が不定な処理は Express Workflow + Map State 分割に切り替える
  • 対策: Map State に必ず MaxConcurrency を設定 (例: 10〜100)。Standard の大規模バッチは Express Workflow のサブ実行に切り出す

5-6. 本番運用5原則

本番運用パターン 5原則 — まとめ

  • 原則1: ASL は Terraform templatefile() で外部ファイル管理
    state-machine/*.asl.json に分離することで ASL の diff が独立し、レビューと変更管理が容易になる。jsonencode による Terraform 内インライン定義は変数展開は便利だが可読性が下がるため非推奨。
  • 原則2: CloudWatch Logs は本番でも LogLevel=ALL
    コストより可観測性を優先。GB 単価は安価で、State 遷移ログは軽量。障害時の MTTR 短縮効果が圧倒的に上回る。
  • 原則3: X-Ray TracingConfiguration enabled で統合トレース
    Lambda・DynamoDB との依存グラフを自動生成。Cold Start・Throttle・外部依存レイテンシを一画面で把握できる。
  • 原則4: EventBridge Scheduler + DLQ + SNS で定期実行の信頼性確保
    スケジュール実行の失敗を Dead Letter Queue で捕捉し、CloudWatch Alarm → SNS で通知。cronジョブのサイレント失敗を排除する。
  • 原則5: IAM Role は State Machine 毎に分離・Resource ARN 明示
    ワイルドカード ARN を使わず、呼び出す Lambda の ARN を列挙。Condition で SourceArn を絞り、横断呼び出しを防止する。

デプロイフロー参考: Terraform apply → State Machine 更新 → CloudWatch Alarms 自動再設定 → EventBridge Scheduler 継続稼働(スケジュール式は変更なし)。Blue/Green デプロイは aws_sfn_state_machinenameprd-v2-order-processing のように別名で作成し、Scheduler の target ARN を切り替えることで実現できる。


6. 詰まりポイント7選 — Step Functions 入門で直面する地雷

Step Functions を本番投入する前に知っておきたい7つの落とし穴を、実務で頻発する順に解説する。これらは「AWS公式ドキュメントには書いてあるが実害が出るまで気づかない」類の問題が多い。

詰まりポイント1: Choice State の Default branch 未定義で実行が突然停止

Choice State は条件分岐を宣言的に定義するが、どの条件にも一致しない入力が来たとき、Default branch が定義されていなければ State Machine はエラーで停止する。エラーメッセージは States.NoChoiceMatched で、CloudWatch Logs に出力されない設定だと原因特定に時間がかかる。

{
  "Type": "Choice",
  "Choices": [
 {
"Variable": "$.orderType",
"StringEquals": "express",
"Next": "ProcessExpress"
 }
  ],
  "Default": "HandleUnknownOrderType"
}

Default は必ず定義し、遷移先は Fail State か専用ログ Lambda にする。"Default": "Fail" でも構わないが、CauseError を付けてデバッグしやすくせよ。

詰まりポイント2: Task Timeout 未設定で Lambda timeout 後に State Machine が無限待機

Lambda 関数の最大実行時間は 15 分だが、Step Functions Task のデフォルト timeout は 1年 (31536000 秒) である。Lambda がタイムアウトして再試行も尽きたあと、Step Functions 側が応答を待ち続けて実行が宙吊りになるケースが本番で発生する。

{
  "Type": "Task",
  "Resource": "arn:aws:states:::lambda:invoke",
  "Parameters": {
 "FunctionName": "process-order"
  },
  "TimeoutSeconds": 300,
  "HeartbeatSeconds": 60,
  "Next": "Done"
}

TimeoutSeconds は Lambda timeout + 30 秒程度を目安に設定する。HeartbeatSeconds は長時間 Task (外部API待ち等) に設定し、ハングを早期検出する。

Timeout 未設定は本番禁止

  • Lambda timeout=15分 でも Task timeout=1年 → 実行が最長1年宙吊りになる
  • Standard Workflow の実行は 課金対象 → 宙吊り実行がコスト爆発を引き起こす
  • Terraform では timeoutSeconds = 300 をリソース定義に必ず含める
  • 全 Task に TimeoutSeconds 必須をコードレビューチェックリストに追加せよ

詰まりポイント3: ResultPath 設定ミスで入力データがロスト

Task State の出力はデフォルトで Lambda の return value 全体になる。ResultPath を正しく設定しないと、前 State から受け取った入力データが Task 出力で上書きされ消滅する

{
  "Type": "Task",
  "Resource": "arn:aws:states:::lambda:invoke",
  "Parameters": {
 "FunctionName": "enrich-data",
 "Payload.$": "$"
  },
  "ResultPath": "$.enrichmentResult",
  "OutputPath": "$",
  "Next": "Store"
}
設定意味
ResultPath: nullTask 結果を捨て、入力をそのまま次 State へ
ResultPath: "$.result"Task 結果を入力の $.result に追記して渡す
ResultPath 未設定Task 結果が入力を完全上書き (ロスト発生)

InputPath / ResultPath / OutputPath の3つは役割が異なる。混乱したら AWS 公式 I/O Processing in Step Functions を参照せよ。

詰まりポイント4: Express Workflow での重複実行 (At-most-once の落とし穴)

Express Workflow は At-least-once 実行保証であり、同じ入力が重複処理される可能性がある。特に EventBridge → Express Workflow のトリガーは再試行ポリシーの影響で重複が起きやすい。

Standard Workflow との比較:

項目StandardExpress
実行保証Exactly-onceAt-least-once
最大実行時間1年5分
実行履歴CloudWatch + ConsoleCloudWatch Logs のみ
単価状態遷移数課金実行時間×メモリ課金

べき等性設計が必須: 決済処理に Express を使う場合、Lambda 側で idempotency_key を DynamoDB に記録し重複 skip を実装すること。

詰まりポイント5: IAM Role の Lambda 実行権限不足で AccessDeniedException

State Machine の IAM Role には lambda:InvokeFunction が必要だが、Lambda Resource-based Policy と IAM Role の両方が絡むため設定漏れが起きやすい。エラーは AccessDeniedException だが、CloudWatch Logs が OFF だとコンソールの実行詳細でしか確認できない。

resource "aws_iam_role_policy" "sfn_lambda_invoke" {
  name= "sfn-lambda-invoke"
  role= aws_iam_role.sfn_exec.id
  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [
{
  Effect= "Allow"
  Action= ["lambda:InvokeFunction"]
  Resource = [
 aws_lambda_function.process_order.arn,
 "${aws_lambda_function.process_order.arn}:*"
  ]
}
 ]
  })
}

Lambda ARN の末尾に :* を付けないと、バージョン・エイリアス付き呼び出しで権限エラーが出る。Terraform で管理する際はリソース定義に必ず両形式を含める。

IAM トラブルシューティング即効チェックリスト

  • State Machine 実行 IAM Role に lambda:InvokeFunction 付与済みか確認
  • Lambda ARN が arn + arn:* の両方対象に含まれているか確認
  • Lambda Resource-based Policy で Step Functions サービスプリンシパルを許可しているか確認
  • CloudWatch Logs に LogLevel=ALL を設定して詳細エラーを出力させよ
  • IAM Policy Simulator で実際の権限テストを実施してからデプロイせよ

詰まりポイント6: 実行履歴の 25,000 件上限 (Standard Workflow のみ)

Standard Workflow は1実行あたり最大 25,000 件の状態遷移履歴を保持する。Map State で大量アイテムを処理すると1実行でこの上限に達し、以降の遷移が記録されず ExecutionHistoryLimitExceeded エラーになる。

対処法:
1. Map State の MaxConcurrency を制限: 並列度を下げて単位時間あたりの遷移数を減らす
2. Distributed Map を使用: 大規模並列は子 Express Workflow に委譲し、親 Standard Workflow の遷移数を節約する
3. バッチ分割: 1万件超の入力は事前に分割し、複数の State Machine 実行に分散する

詰まりポイント7: Map State の MaxConcurrency=0 (無制限) で下流サービス過負荷

MaxConcurrency のデフォルト値は 0 (無制限) である。S3 上の 10,000 ファイルを Map State で処理すると、起動時に Lambda が 10,000 並列で呼び出され、DynamoDB や RDS などの下流が Throttling でクラッシュする。

{
  "Type": "Map",
  "ItemsPath": "$.files",
  "MaxConcurrency": 50,
  "Iterator": {
 "StartAt": "ProcessFile",
 "States": {
"ProcessFile": {
  "Type": "Task",
  "Resource": "arn:aws:states:::lambda:invoke",
  "End": true
}
 }
  }
}

MaxConcurrency は下流サービスの Quota に合わせて設定する。Lambda Reserved Concurrency も忘れずに確保し、並列度 × 実行時間 × コストを事前に試算せよ。


7. アンチパターン→正解パターン変換演習5問

実際の本番コードで見かけるアンチパターンを5問出題する。それぞれ「問題点の特定」「正解パターンへの移行」「Terraform/ASL実装例」の3ステップで解説する。

演習を始める前に: アンチパターン発見のコツ

  • ASL JSON に "Default" が存在しない Choice State は即NG
  • Task リソースに "TimeoutSeconds" がない Task は全て要修正
  • Lambda を Step Functions から呼ぶのに Lambda 内でさらに SF 実行しているコードは循環依存疑い
  • Map State の "MaxConcurrency" が 0 または未設定は負荷試験前に修正必須

演習1: Lambda 内 if-else 連鎖 → Choice State で宣言的分岐

アンチパターン (NG):

def handler(event, context):
 order_type = event.get("orderType")
 if order_type == "express":
  result = process_express(event)
 elif order_type == "standard":
  result = process_standard(event)
 elif order_type == "bulk":
  result = process_bulk(event)
 else:
  raise ValueError(f"Unknown order type: {order_type}")
 return result

問題点: 分岐ロジックが Lambda コード内に埋め込まれ、Step Functions のフロー可視化に現れない。分岐追加のたびにデプロイが必要。

正解パターン (OK):

{
  "Type": "Choice",
  "Choices": [
 {
"Variable": "$.orderType",
"StringEquals": "express",
"Next": "ProcessExpress"
 },
 {
"Variable": "$.orderType",
"StringEquals": "standard",
"Next": "ProcessStandard"
 },
 {
"Variable": "$.orderType",
"StringEquals": "bulk",
"Next": "ProcessBulk"
 }
  ],
  "Default": "HandleUnknownType"
}

各 Lambda は単一責務 (express 処理のみ等) に絞り、フロー制御は Step Functions に委譲する。

演習2: Choice State に Default なし → Default → Fail State 必須化

アンチパターン (NG):

{
  "Type": "Choice",
  "Choices": [
 { "Variable": "$.status", "StringEquals": "active", "Next": "ProcessActive" }
  ]
}

問題点: status"inactive"null の場合に States.NoChoiceMatched で実行停止。本番データは予期しない値を含む。

正解パターン (OK):

{
  "Type": "Choice",
  "Choices": [
 { "Variable": "$.status", "StringEquals": "active", "Next": "ProcessActive" }
  ],
  "Default": "HandleUnexpectedStatus"
},
"HandleUnexpectedStatus": {
  "Type": "Fail",
  "Error": "UnexpectedStatus",
  "Cause": "Input $.status did not match any known value"
}

Fail State の ErrorCause は必ず記述し、CloudWatch Logs でのトレースを容易にする。

演習3: Retry BackoffRate=1.0 即連続再試行 → 指数バックオフ設定

アンチパターン (NG):

{
  "Retry": [
 {
"ErrorEquals": ["Lambda.ServiceException"],
"IntervalSeconds": 1,
"MaxAttempts": 5,
"BackoffRate": 1.0
 }
  ]
}

問題点: BackoffRate=1.0 は線形再試行 (1秒→1秒→1秒…)。Throttling 発生時に全 Retry が同間隔で殺到し、下流の回復を妨げる。

正解パターン (OK):

{
  "Retry": [
 {
"ErrorEquals": [
  "Lambda.ServiceException",
  "Lambda.AWSLambdaException",
  "Lambda.SdkClientException",
  "Lambda.TooManyRequestsException"
],
"IntervalSeconds": 2,
"MaxAttempts": 6,
"BackoffRate": 2.0,
"JitterStrategy": "FULL"
 }
  ]
}

JitterStrategy: "FULL" は 2024年 GA の新機能。指数バックオフに完全ランダムジッターを追加し、Thundering Herd 問題を解消する。

演習4: Express Workflow で 5 分超ジョブ → Standard Workflow 切替

アンチパターン (NG):

resource "aws_sfn_state_machine" "batch_job" {
  name  = "batch-processing"
  type  = "EXPRESS"  # 最大5分制約を無視した設計
  role_arn = aws_iam_role.sfn.arn
  definition = jsonencode({...})
}

バッチ処理が 8 分かかると判明した時点で Express Workflow は適用不可。type = "STANDARD" に変更しリデプロイが必要になる。

正解パターン (OK): ワークロード特性で Workflow タイプを選定する。

Express が適切:
  - 実行時間 < 5分
  - 高頻度 (10 TPS 超)
  - 重複処理許容 (べき等処理)
  - ストリーム処理 / イベント駆動

Standard が適切:
  - 実行時間 5分超の可能性あり
  - Exactly-once 保証が必要 (決済/在庫更新)
  - 実行履歴の長期保持が必要
  - 手動承認フロー含む
resource "aws_sfn_state_machine" "batch_job" {
  name  = "batch-processing"
  type  = "STANDARD"  # 長時間バッチは Standard 必須
  role_arn = aws_iam_role.sfn.arn
  definition = jsonencode({...})
}

演習5: Map MaxConcurrency 未設定 → MaxConcurrency + Reserved Concurrency セット設定

アンチパターン (NG):

{
  "Type": "Map",
  "ItemsPath": "$.records",
  "Iterator": {
 "StartAt": "ProcessRecord",
 "States": {
"ProcessRecord": {
  "Type": "Task",
  "Resource": "arn:aws:states:::lambda:invoke",
  "End": true
}
 }
  }
}

問題点: MaxConcurrency 省略でデフォルト0 (無制限)。1,000件処理で Lambda が 1,000 並列起動し、DynamoDB が Throttling。

正解パターン (OK):

{
  "Type": "Map",
  "ItemsPath": "$.records",
  "MaxConcurrency": 20,
  "Iterator": {
 "StartAt": "ProcessRecord",
 "States": {
"ProcessRecord": {
  "Type": "Task",
  "Resource": "arn:aws:states:::lambda:invoke",
  "Parameters": {
 "FunctionName": "process-record",
 "Payload.$": "$"
  },
  "TimeoutSeconds": 60,
  "Retry": [
 {
"ErrorEquals": ["Lambda.TooManyRequestsException"],
"IntervalSeconds": 2,
"MaxAttempts": 3,
"BackoffRate": 2.0
 }
  ],
  "End": true
}
 }
  }
}
resource "aws_lambda_function" "process_record" {
  # ... 省略 ...
  reserved_concurrent_executions = 100  # MaxConcurrency × 5 を目安に設定
}

MaxConcurrency と Lambda reserved_concurrent_executions をセットで設定する。下流の DynamoDB Write Capacity Units も試算して事前に設定すること。


8. まとめ + Step Functions 本番運用シリーズ予告 + 全クロスリンク

本記事で習得した4本柱

本記事を通じて、Step Functions 入門に必要な4つの設計力を習得した。

  1. ASL による宣言的フロー定義: Pass / Task / Choice / Parallel / Map / Wait の各 State 型を使い分け、Lambda コードからフロー制御ロジックを完全分離する設計ができる
  2. Standard vs Express Workflow の選定: 実行時間・耐久性・コスト・スループット要件を軸に最適な Workflow タイプを選べる。5分以内の高頻度イベント駆動は Express、長時間・Exactly-once・監査ログが必要な処理は Standard
  3. Catch + Retry によるエラーハンドリング: ErrorEquals で対象エラーを絞り、BackoffRate=2.0 + JitterStrategy=FULL の指数バックオフで Thundering Herd を防ぐ。Catch では Fail State か補正 Lambda へ安全に分岐する
  4. Terraform IaC + CloudWatch + X-Ray による本番運用基盤: State Machine・IAM Role・CloudWatch Logs・X-Ray トレーシングを Terraform で一元管理し、再現性のある運用体制を構築できる
本記事完遂で得られる Step Functions 基礎運用設計力

  • ASL 構文 (Pass/Task/Choice/Parallel/Map/Wait) を宣言的に記述できる
  • Standard vs Express Workflow を要件 (時間/耐久性/スループット) で選定できる
  • Catch + Retry を組合せて本番品質エラーハンドリングを実装できる
  • Terraform IaC + CloudWatch + X-Ray で本番運用基盤を構築できる
  • 詰まりポイント7選を予防・即対応できる

入門+本番運用横断 チェックリスト10選

本記事と本番運用シリーズを通じて押さえるべき最重要チェック項目をまとめる。

#チェック項目参照
1Choice State に必ず Default を定義している§6-1
2全 Task に TimeoutSeconds を設定している§6-2
3ResultPath / InputPath / OutputPath を明示設定している§6-3
4Express Workflow でべき等性を保証している§6-4
5State Machine IAM Role に必要な権限が全て付与されている§6-5
6Standard Workflow の遷移数が 25,000 件を超えない設計になっている§6-6
7Map State に MaxConcurrency を明示設定している§6-7
8Retry に BackoffRate=2.0 + JitterStrategy=FULL を設定している§7-3
9CloudWatch Logs LogLevel=ALL を本番でも維持している§6-7
10Lambda ARN に :* を含む両形式で IAM 権限付与している§6-5
Step Functions 本番運用シリーズ予告 — 次の4ステップ

  • Callback Pattern (WaitForTaskToken): 人間承認フロー・外部システム完了待ちを Task Token で実装。タスクトークン発行→外部コールバック→SF再開の設計パターンを解説
  • Distributed Map (大規模並列処理): S3 オブジェクト一覧を直接 Map 入力に指定し、数千〜数万件の並列処理を子 Express Workflow で実行する。バッチ処理の新定番アーキテクチャ
  • Express vs Standard 詳細選定: ユースケース別の定量比較。コスト試算・Exactly-once の実現方法・ハイブリッド構成 (Standard が Express を呼ぶ) の実装パターン
  • SDK Direct Integration (200+ AWSサービス直接呼出): Lambda を経由せず DynamoDB / S3 / SQS / SNS 等を Step Functions から直接操作。Lambda-less アーキテクチャでコスト削減と遅延短縮を実現

← Serverless本番運用 Vol1 — Lambda×API GW×Step Functions 本番設計

Step Functions 本番運用シリーズ第1弾: Callback Pattern →