Step FunctionsのMapステートは、配列やS3上のファイル群を1件ずつ同じ手順で処理するためのステートです。小さな配列ならインラインモード、数万件のファイルや大きなCSVなら分散マップ(Distributed Map)と、2つのモードを使い分ける仕組みです。この記事では両モードの境界を公式の上限値で確かめたうえで、インラインMapの定義例、S3のファイル一覧を分散マップで並列処理する定義とIAMポリシー、Lambdaの同時実行数を超えないための設定、失敗時の再実行、料金の増え方までをJSONata形式の定義とAWS CLIの手順で扱います。数値と仕様は2026年10月時点のAWS公式ドキュメントに基づきます。
まとめ:Step FunctionsのMapはインラインと分散マップを件数と同時実行数で選ぶ
判断の基準は3つの数字です。処理する配列が256KiBを超える、実行履歴が25,000イベントを超える、同時に40件を超えて並列に回したい。このどれかに当たるなら分散マップ、どれにも当たらないならインラインMapで足ります。S3のファイル一覧やCSV・JSON Linesを直接読めるのは分散マップだけです。
分散マップは何も指定しないと子ワークフローを最大10,000並列で起動します。一方、Lambdaの同時実行数の既定値はリージョンあたり1,000です。MaxConcurrency を指定しないまま数万件を流すと、Lambda側がスロットリングを起こします。MaxConcurrency と ItemBatcher で並列数と1回あたりの件数を決めてから動かしてください。
失敗への備えは、Retryを子ワークフロー内のTaskに置き、Mapステート全体には許容しきい値(ToleratedFailurePercentage)を設定する形が基本です。Mapステートに置いたRetryは成功した子実行まで含めてやり直すため、大量処理では使いどころを選びます。
Step FunctionsのMapステートとは:配列を反復するインラインと分散の2モード
Mapステートは、入力のデータセットの各要素に対して、ItemProcessor に書いた手順を繰り返し実行します。結果として返されるのは、要素ごとの出力を並べた配列です。Step Functions全体の仕組みや標準・Expressの違いはAWS Step Functionsとは?ステートマシンの仕組み・料金と2種のワークフロー・採用判断を実装者目線で解説にまとめています。
インラインモードと分散モードの入力・実行履歴・同時反復の上限値
両モードの差は、反復を実行する場所の違いです。インラインモードの公式ドキュメントによると、インラインでは各反復が親ワークフローの中で動き、その履歴も親の実行履歴に積まれます。分散モードの公式ドキュメントでは、各反復は独立した子ワークフロー実行として起動され、履歴も親とは別に残ります。
| 項目 | インライン | 分散マップ |
|---|---|---|
| 同時反復の上限 | 40 | 10,000 |
| 入力の取り方 | 前段のJSON配列 | 配列かS3 |
| 反復の履歴 | 親に積まれる | 子ごとに分離 |
| 親ワークフロー | 標準とExpress | 標準のみ |
| 失敗の扱い | 1件で全体失敗 | しきい値を設定 |
インラインMapは既定のモードで、ProcessorConfig を省略するとインラインになります。分散マップは標準ワークフローの中でしか使えず、Expressワークフローの親からは指定できません。
25,000イベントの履歴上限がインラインMapの実質的な天井になる
インラインMapで見落としやすいのが、標準ワークフローの実行履歴の上限です。Step Functionsのクォータでは1実行あたり25,000イベントのハードクォータとされ、達すると実行そのものが失敗します。インラインMapの反復は1件ごとに複数のイベントを親の履歴へ書き込むため、配列が数千件になると上限に近づきます。
同時反復40という数字も、指定すれば必ず出る値ではありません。公式は、入力配列が40件を超えると一部の反復が前の反復の完了を待つ可能性が高まると注記しています。並列度を上げたい処理を最初からインラインで組むと、件数が増えた段階で作り直しになります。
インラインMapで配列を並列処理する定義例とMaxConcurrencyの指定
件数が数十〜数百で、前のステートが返した配列をそのまま処理する場面では、インラインMapが最も手軽です。注文明細の各行を検証する例で書き方を確認します。
JSONata形式でItemsとItemProcessorを書く基本形
JSONata形式では、反復する配列を Items に式で渡します。各反復の中では $states.input がその1件分になります。JSONPathからの書き換え手順やArguments・Output・Assignの書き方はStep FunctionsのJSONataの記事にまとめています。以下は注文の明細配列を最大10並列で検証し、結果を元の入力にまとめて返す定義例です。関数名とアカウントIDは自分の環境に置き換えてください。
{
"Comment": "注文明細の各行をインラインMapで検証する",
"QueryLanguage": "JSONata",
"StartAt": "ValidateLines",
"States": {
"ValidateLines": {
"Type": "Map",
"Items": "{% $states.input.order.lines %}",
"MaxConcurrency": 10,
"ItemProcessor": {
"ProcessorConfig": { "Mode": "INLINE" },
"StartAt": "ValidateLine",
"States": {
"ValidateLine": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Arguments": {
"FunctionName": "arn:aws:lambda:ap-northeast-1:123456789012:function:validate-line",
"Payload": "{% $states.input %}"
},
"Output": "{% $states.result.Payload %}",
"End": true
}
}
},
"Output": "{% $merge([$states.input, {'validated': $states.result}]) %}",
"End": true
}
}
}
ItemProcessor は旧来の Iterator フィールドを置き換えたもので、公式は新規の定義では ItemProcessor を使うよう勧めています。Lambdaを呼ぶTaskの書き方と出力の取り出し方は、Step FunctionsとLambdaの違い:役割の切り分けとlambda:invoke・リトライの実装手順で詳しく扱っています。
MaxConcurrencyの0と1で変わる反復の順序と並列度
MaxConcurrency の既定値は0で、上限を設けずにできるだけ並列に動かします。1を指定すると、配列の並び順に1件ずつ処理し、前の反復が終わるまで次を始めません。外部APIの呼び出し回数に制限がある、処理の順序に意味がある、といった場合は1や小さな値を指定します。
なお、インラインMapではどれか1件の反復が失敗するとMapステート全体が失敗し、残りの反復も止まります。1件の失敗を許して残りを続けたい場合は、子の手順の中でCatchしてエラー内容を結果として返すか、分散マップのしきい値を使います。
分散マップでS3の大量ファイルを並列処理する定義とIAMポリシーの書き方
S3に溜まった数万件のファイルを1件ずつ処理する、数GBのCSVを行ごとに処理する。こうした用途が分散マップの本来の守備範囲です。前段のステートで一覧を作らなくても、Mapステート自身がS3から読み込みます。S3の基本はAmazon S3とは?オブジェクトストレージの仕組み・ストレージクラスと料金・採用判断を実装者目線で解説で整理しています。
ItemReaderで読めるS3のデータ形式とプレフィックス指定の注意
ItemReaderの公式ドキュメントによると、2026年10月時点で読み込める入力は次のとおりです。
s3:listObjectsV2によるオブジェクト一覧(既定ではキーやサイズなどのメタデータが1件になる)s3:getObjectで読むCSV・JSON・JSON Lines・Parquetの単一ファイル(1ファイル10GBまで)- AthenaのUNLOADが出力するマニフェストと、S3インベントリのマニフェスト
2025年9月の更新で、一覧取得に Transformation: LOAD_AND_FLATTEN を指定すると、一覧に含まれる各ファイルの中身を行単位で読めるようになりました。以前はファイル一覧を回す分散マップの中に、さらにファイルの中身を回すMapを入れ子にする必要がありました。Athenaの集計結果を後段で処理する構成はAmazon Athena(アテナ)とはとあわせて検討してください。
プレフィックスには末尾のスラッシュを付ける指定が必要です。公式の例では、folder1 と書くと folder10 配下まで対象になると注記されています。また、S3コンソールで作ったフォルダは一覧に1件として含まれ、余分な子ワークフローが起動されます。処理側でキーがスラッシュで終わるものを読み飛ばすか、フォルダをCLIで作る運用にしてください。
S3のファイル一覧を100件ずつLambdaへ渡す分散マップの定義例
以下は、S3の incoming プレフィックス配下のファイルを100件ずつまとめ、子ワークフロー(Express)からLambdaで処理し、結果をS3へ書き出す定義例です。Lambdaの例外ごとのRetryは子ワークフローの中に置いています。
{
"Comment": "S3のファイル一覧を分散マップで100件ずつ並列処理する",
"QueryLanguage": "JSONata",
"StartAt": "ProcessFiles",
"States": {
"ProcessFiles": {
"Type": "Map",
"Label": "ProcessFiles",
"ItemReader": {
"Resource": "arn:aws:states:::s3:listObjectsV2",
"Arguments": {
"Bucket": "example-input-bucket",
"Prefix": "{% $states.input.prefix %}"
}
},
"ItemBatcher": {
"MaxItemsPerBatch": 100,
"BatchInput": { "bucket": "example-input-bucket" }
},
"MaxConcurrency": 50,
"ToleratedFailurePercentage": 5,
"ItemProcessor": {
"ProcessorConfig": { "Mode": "DISTRIBUTED", "ExecutionType": "EXPRESS" },
"StartAt": "ProcessBatch",
"States": {
"ProcessBatch": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Arguments": {
"FunctionName": "arn:aws:lambda:ap-northeast-1:123456789012:function:process-files:prod",
"Payload": "{% $states.input %}"
},
"Output": "{% $states.result.Payload %}",
"Retry": [
{
"ErrorEquals": ["Lambda.ServiceException", "Lambda.AWSLambdaException", "Lambda.SdkClientException"],
"IntervalSeconds": 2,
"MaxAttempts": 6,
"BackoffRate": 2
},
{
"ErrorEquals": ["Lambda.TooManyRequestsException"],
"IntervalSeconds": 5,
"MaxAttempts": 5,
"BackoffRate": 2,
"MaxDelaySeconds": 60,
"JitterStrategy": "FULL"
}
],
"End": true
}
}
},
"ResultWriter": {
"Resource": "arn:aws:states:::s3:putObject",
"Arguments": {
"Bucket": "example-result-bucket",
"Prefix": "map-results/"
}
},
"End": true
}
}
}
実行時の入力は {"prefix": "incoming/2026-10-17/"} のように渡します。Label はMap RunのARNに付く名前で、40文字以内・定義内で一意という決まりがあります。子ワークフローをExpressにしたのは、1件あたり数秒で終わる処理を大量に回すと、Expressのほうが料金が下がるためです(後述)。Expressの子は最大5分で打ち切られるので、100件の処理がその時間に収まるかを先に確かめてください。
子ワークフローが受け取る入力とPythonのLambdaでの処理例
ItemBatcherの公式ドキュメントのとおり、バッチ化すると子ワークフローには Items 配列と BatchInput がまとめて渡されます。一覧取得の各要素には Key・Size・Etag などが入ります。
import boto3
s3 = boto3.client("s3")
def lambda_handler(event, context):
bucket = event["BatchInput"]["bucket"]
processed = []
for item in event["Items"]:
key = item["Key"]
# コンソールで作ったフォルダは一覧に含まれるので読み飛ばす
if key.endswith("/") or item["Size"] == 0:
continue
body = s3.get_object(Bucket=bucket, Key=key)["Body"].read()
processed.append({"key": key, "bytes": len(body)}) # 実処理は省略
# 戻り値は256KiB以内に収める(大きな結果はS3に書いてキーだけ返す)
return {"count": len(processed), "items": processed}
子ワークフローに渡す入力と返す出力は、それぞれ256KiBが上限です。MaxItemsPerBatch に加えて MaxInputBytesPerBatch を指定すると、件数とバイト数の両方でバッチの大きさを抑えられます。
ステートマシンの実行ロールに付ける子実行とS3用IAMポリシーの最小構成
分散マップでは、実行ロールが自分自身のステートマシンに対して子実行を起動する権限を持つ必要があります。S3の一覧取得・読み込み・結果の書き込み、Lambdaの呼び出し権限も加えた最小構成は次のとおりです。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["states:StartExecution"],
"Resource": "arn:aws:states:ap-northeast-1:123456789012:stateMachine:s3-file-processor"
},
{
"Effect": "Allow",
"Action": ["states:DescribeExecution", "states:StopExecution"],
"Resource": "arn:aws:states:ap-northeast-1:123456789012:execution:s3-file-processor:*"
},
{
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": "arn:aws:s3:::example-input-bucket",
"Condition": { "StringLike": { "s3:prefix": ["incoming/*"] } }
},
{
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::example-input-bucket/incoming/*"
},
{
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:GetObject", "s3:ListMultipartUploadParts", "s3:AbortMultipartUpload"],
"Resource": "arn:aws:s3:::example-result-bucket/map-results/*"
},
{
"Effect": "Allow",
"Action": ["lambda:InvokeFunction"],
"Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:process-files:*"
}
]
}
入力と出力のバケットは、ステートマシンと同じアカウント・同じリージョンに置きます。ResultWriterの公式ドキュメントでは、別アカウントや別リージョンのバケットに書き出そうとすると States.ResultWriterFailed で失敗すると明記されています。
Lambdaのスロットリングを防ぐ同時実行数とItemBatcherの決め方
分散マップの並列度は、Step Functions側よりも呼び出し先の上限で決まります。ここを設計せずに本番データを流すと、処理が進まないのに料金だけが積み上がります。
既定の10,000並列とLambda同時実行数1,000の衝突
分散マップは MaxConcurrency を省略するか0にすると、子ワークフローを最大10,000並列で起動します。対してLambdaのクォータでは、同時実行数の既定値はリージョンあたり1,000で、新しいアカウントではさらに低く設定されています。しかもこの1,000は同じリージョンの全関数で共有する枠です。
分散マップがこの枠を使い切ると、同じアカウントで動いているAPI用のLambdaまで TooManyRequestsException を返し始めます。夜間バッチのつもりが、日中のサービスを止める原因になりかねません。公式も、下流のサービスの容量を超える並列数を指定しないよう勧めています。
目安は「Lambdaの同時実行数の枠のうち、このバッチに割ける数」を MaxConcurrency にすることです。処理用の関数に予約同時実行数を設定しておけば、他の関数の枠を食い潰す事態も防げます。子ワークフローの起動速度にも上限があり、Expressの子は毎秒1,000件、標準の子は毎秒100件です。
ItemBatcherで子ワークフローの数と起動回数を減らす
1ファイル1子ワークフローにすると、10万ファイルなら10万回の起動とLambda呼び出しが発生します。MaxItemsPerBatch を100にすれば、子ワークフローの本数は1,000本です。公式の例では、1,130件を100件ずつにまとめると、100件のバッチが11個と残り30件のバッチ1個の計12バッチになると説明されています。
バッチを大きくしすぎると、今度は1本の子ワークフローが長くなり、Lambdaの900秒やExpressの5分に当たります。1件の処理時間を計測し、「1件の時間×バッチ件数」が上限の半分程度に収まる値から始めると調整しやすくなります。
失敗の許容しきい値・Retryの置き場所・Map Runの再実行の運用
数万件を処理すれば、壊れたファイルや一時的なエラーは必ず混ざります。分散マップでは「何件までの失敗を許すか」「失敗分をどう回収するか」を定義と運用の両方で決めておきます。
Mapステート全体のRetryが成功した子実行までやり直す理由
分散マップの公式ドキュメントには、MapステートにRetryを定義すると、その再試行は失敗した子実行だけでなく、Mapステートが起動したすべての子実行に適用されると書かれています。Mapステートを再試行すると、新しいMap Runが作られるためです。10万件のうち1件が失敗しただけで全件をやり直す設計は避け、Retryは定義例のように子ワークフロー内のTaskに置いてください。
許容しきい値の既定値は0%で、子実行が1件でも失敗またはタイムアウトするとMap Runが失敗します。ToleratedFailurePercentage か ToleratedFailureCount を指定すると、その範囲内の失敗では止まらず、超えた時点で返すエラーは States.ExceedToleratedFailureThreshold です。失敗した子実行の入力と出力はResultWriterの出力先に FAILED_0.json としてまとめて書き出されるので、後から原因を調べられます。
AWS CLIでMap Runの進捗を確認し失敗分だけを再実行する手順
実行中のMap Runは、CLIで件数の内訳を確認できます。並列数は実行中でも変更でき、Lambdaのスロットリングが出たら下げる、余裕があれば上げる、という調整ができます。
# 実行を開始する
aws stepfunctions start-execution \
--state-machine-arn arn:aws:states:ap-northeast-1:123456789012:stateMachine:s3-file-processor \
--input '{"prefix":"incoming/2026-10-17/"}' \
--region ap-northeast-1
# 実行に紐づくMap Runの一覧を取得する
aws stepfunctions list-map-runs \
--execution-arn arn:aws:states:ap-northeast-1:123456789012:execution:s3-file-processor:実行ID \
--region ap-northeast-1
# 件数の内訳(成功・失敗・実行中)を確認する
aws stepfunctions describe-map-run \
--map-run-arn 前のコマンドで得たmapRunArn \
--query '{status:status,items:itemCounts}' \
--region ap-northeast-1
# 実行中に並列数を20へ下げる
aws stepfunctions update-map-run \
--map-run-arn 前のコマンドで得たmapRunArn \
--max-concurrency 20 \
--region ap-northeast-1
# 失敗で終わった実行を再開する(失敗・未実行の子だけが再実行される)
aws stepfunctions redrive-execution \
--execution-arn arn:aws:states:ap-northeast-1:123456789012:execution:s3-file-processor:実行ID \
--region ap-northeast-1
Map Runの再実行(redrive)の公式ドキュメントによると、親の実行をredriveすると、失敗やタイムアウトで終わった子実行だけが再び動きます。Expressの子は新しい実行として先頭のステートから、標準の子は失敗したステートから再開する点が違います。1つのMap Runに対するredriveは1,000回まで、標準の子実行がredriveできるのは終了から14日以内です。redriveには実行ロールに states:RedriveExecution の権限も要ります。原因を直したら、全件をやり直すのではなくredriveで失敗分だけを回収する運用にしてください。
分散マップの料金を左右するバッチ化と子ワークフローの型ごとの課金単位
分散マップの料金は、件数そのものより「子ワークフローを何本起動するか」で決まります。Step Functionsの料金ページは、分散マップでは反復1回につき1回の状態遷移が課金されると明記しています。インラインMapには、反復の開始そのものに対する状態遷移の課金はありません。
10万ファイル処理時のバッチ件数別の状態遷移数と料金の試算例
米国東部(バージニア北部)の標準ワークフローの単価は1遷移0.000025USDです。10万ファイルを1件1子ワークフローで処理すると、反復の開始だけで10万遷移、約2.5USDになります。100件ずつバッチ化すれば子ワークフローは1,000本で、同じ部分は約0.025USDまで下がります。
子ワークフローを標準型にすると、子の中のTaskやChoiceも標準ワークフローとして遷移ごとに数えられます。Expressの子は、リクエスト数(米国東部で100万件あたり1.00USD)と、メモリと実行時間に応じた料金で課金されます。数秒で終わる処理を大量に回すならExpress、1件が5分を超える、人の承認を挟むなら標準、という分け方が基本です。東京リージョンの単価は料金ページのリージョン選択で確認してください。
Lambda側の呼び出し回数と実行時間の料金・再試行による追加費用
Step Functionsの料金に加えて、Lambdaのリクエスト料金と実行時間の料金がそのまま発生します。バッチ化はLambdaの呼び出し回数も減らすので、両方の料金を抑える方法です。Lambda側の実額の計算はAWS Lambdaの料金を東京リージョンの実額で計算するで扱っています。
再試行もそれぞれ追加の状態遷移として課金されます。スロットリングで子ワークフローが一斉に再試行すると、障害の日だけ請求が増える構造です。並列数をLambdaの枠に合わせておくことは、処理を止めないためだけでなく、料金を読めるものにするための設定でもあります。
分散マップを採用する条件とインラインMapや別サービスで済ませる場面
ここまでの内容を、採用と見送りの条件として整理します。
分散マップを採用する条件は、S3上の数千件以上のファイルや、256KiBを超えるCSV・JSON Linesを1件ずつ処理し、かつ1件の処理がLambdaで完結する場合です。失敗の許容しきい値・失敗分だけのredrive・結果のS3書き出しまでが標準機能でそろうため、同じ仕組みをLambdaとSQSで自作するよりも作る量が少なく済みます。
インラインMapで済ませる場面は、前のステートが返す数十〜数百件の配列を処理する場合です。分散マップにすると子ワークフローの起動とIAMの設定が増えるだけで、得るものはありません。
別のサービスを選ぶ場面もあります。1件の処理が15分を超え、コンテナで動かしたいならAWS Batch、処理の中身が結合や集計といったデータ変換そのものならAWS GlueやAthenaのSQLのほうが向いています。分散マップは「各要素に同じ処理を並列で当てる」ための仕組みで、データ同士を突き合わせる処理には向きません。Mapの反復ごとにコンテナを起動したい場合は、Step FunctionsでECSタスクを実行する記事のrunTask.syncの定義を子の手順のTaskとして置けます。
既存のバッチ処理を分散マップへ移すかどうかの判断や、Lambdaの同時実行数の枠の配分を含めた設計から相談したい場合は、インフラ構築(AWS・Google Cloud・Azure)で対応しています。
よくある質問
Step FunctionsのMapステートとParallelステートの違いは何ですか?
Mapは同じ手順を配列の要素の数だけ繰り返し、Parallelは異なる手順の分岐を同時に実行します。たとえば100件の注文を同じ手順で検証するならMap、1件の注文に対して在庫確認と与信確認を同時に行うならParallelです。Mapは件数が実行時に決まり、Parallelは分岐の数が定義の時点で決まる点が違います。
分散マップはExpressワークフローの中で使えますか?
使えません。分散モードは標準ワークフローでのみサポートされ、Expressワークフローの親には定義できません。ただし、分散マップが起動する子ワークフローの型には、標準とExpressのどちらも指定できます。短時間の処理を大量に回すなら、親を標準、子をExpressにする構成が一般的です。
分散マップでS3以外のデータを処理できますか?
S3以外のデータも処理の対象です。前のステートが返したJSON配列やJSONオブジェクトも入力にでき、その場合は ItemReader を書かずに配列をそのまま渡します。DynamoDBやRDSのデータを処理したい場合は、前段でクエリ結果をS3にJSON LinesやCSVとして書き出し、それを分散マップで読む形にすると件数の上限を気にせず扱えます。
Mapステートの同時実行数はどのくらいに設定すればよいですか?
呼び出し先が受けられる数から逆算します。Lambdaを呼ぶなら、リージョンの同時実行数の枠(既定1,000)のうち、このバッチに割ける数が上限です。外部APIを呼ぶなら、そのAPIのレート制限が上限になります。最初は小さめの値で動かし、update-map-run で実行中に上げながらスロットリングの有無を確かめる進め方が安全です。
分散マップの実行中に失敗した子ワークフローの原因はどこで確認できますか?
コンソールのMap Runの詳細ページで、子実行ごとの状態と入力・出力を確認できます。ResultWriterを設定していれば、失敗した子実行の内容が出力先のS3に FAILED_0.json のような名前のファイルとしてまとめて書き出されます。件数が多い場合は、このファイルをAthenaで読んでエラー種別ごとに集計すると、原因の偏りを確認できる形です。
関連記事
- AWS Step Functionsとは?ステートマシンの仕組み・料金と2種のワークフロー・採用判断を実装者目線で解説:標準とExpressの違い・料金の全体像
- Step FunctionsとLambdaの違い:役割の切り分けとlambda:invoke・リトライの実装手順:Lambda呼び出しとRetryの書き方
- AWS Lambdaとは?仕組み・料金体系とコールドスタート対策・採用判断を実装者目線で解説:処理を担う関数側の仕組みと制約
- Amazon S3とは?オブジェクトストレージの仕組み・ストレージクラスと料金・採用判断を実装者目線で解説:入力と結果を置くストレージの基本
- Amazon Athena(アテナ)とは?使い方・クエリの書き方・料金・Redshiftとの違いを解説:UNLOADのマニフェストと結果の集計
- AWS Batchとは?ジョブキュー型マネージドバッチ処理の仕組みと採用判断【2026年版】:1件の処理が長い場合の選択肢