every Tech Blog

株式会社エブリーのTech Blogです。

Step Functions の 256KB 制限にハマった話 — ResultWriter で解決する方法

目次

この記事は every Tech Blog Advent Calendar 2025 の 8 日目の記事です。

はじめに

こんにちは。開発本部開発1部デリッシュキッチンMS2に所属している惟高です。

私が担当しているプロジェクトでは、AWS Step Functions を使って Athena でデータを集計し、結果をエクスポートするバッチ処理を実行しています。ある日、処理対象のデータ量が増えたタイミングで突然ワークフローが停止し、調査の結果 Step Functions のペイロードサイズ制限に起因する問題だと判明しました。

この記事では、問題の原因と Step Functions の制限について調査した内容、そして ResultWriterItemReader を使った解決方法を紹介します。

Step Functions とは

AWS Step Functions は、複数の AWS サービスをワークフローとして連携させるサーバーレスのオーケストレーションサービスです。JSON ベースの Amazon States Language(ASL)でワークフローを定義し、Lambda 関数や Athena クエリなどを順次・並列に実行できます。

今回のワークフローでは、Map ステートを使って複数のアイテムを並列処理しています。Map ステートには以下の2つのモードがあります。

モード 特徴
インライン Map 状態を含むワークフローのコンテキストで実行。最大 40 の同時反復
分散 子ワークフローとして分散実行。10,000 を超える同時反復が可能

今回問題が発生したワークフローでは、大量のデータを処理するために分散モードを使用していました。

突然のエラー発生

ある日、定期実行していたワークフローが以下のエラーで停止しました。

The state/task 'Map' returned a result with a size exceeding the maximum number of bytes service limit.

エラーメッセージを見ると、Map ステートの出力サイズが上限を超えたことが原因のようです。処理対象のデータ量が増えたことで、分散 Map で並列処理した結果が蓄積され、次のステートに渡せなくなっていました。

Step Functions のペイロードサイズ制限

調査したところ、Step Functions には 256KB のペイロードサイズ制限 があることがわかりました。

制限の概要

AWS の公式ドキュメントによると、Step Functions では以下の制限があります。

  • ステート間で受け渡しできるデータの最大サイズは 256KB
  • この制限は入力・出力の両方に適用される
  • 制限を超えると States.DataLimitExceeded エラーが発生する

なぜこの制限があるのか

Step Functions はサーバーレスのワークフローサービスであり、ステート間のデータはサービス内部で管理されます。AWS のベストプラクティスでは、256KB を超える可能性があるデータは S3 に保存することが推奨されています。

問題のワークフロー構成

エラーが発生していた当時のワークフローは、以下のような構成でした。

Map(分散モード)─── S3から入力データを読み込み(ItemReader)
  └── Map(1)(インラインモード)─── 各クエリを実行
        └── SQL生成 → 実行 → 結果取得
      ↓
  結果を収集
      ↓
  【ここで 256KB を超過】
      ↓
Map(2)(インラインモード)
  └── エクスポート処理

※ Athena のクエリ結果そのものではなく、結果が格納された S3 パスのみを受け渡していましたが、処理対象のアイテム数が増えるとメタデータが蓄積され、256KB を超えてしまいました。

修正前の定義(抜粋)

{
  "Map": {
    "Type": "Map",
    "Next": "Map (2)",
    "Iterator": {
      "StartAt": "Map (1)",
      "States": {
        "Map (1)": {
          "Type": "Map",
          "ItemsPath": "$.queries",
          "Iterator": {
            // ... Athena クエリ実行処理
          }
        }
      },
      "ProcessorConfig": {
        "Mode": "DISTRIBUTED",
        "ExecutionType": "STANDARD"
      }
    },
    "ItemReader": {
      "Resource": "arn:aws:states:::s3:getObject",
      "ReaderConfig": {
        "InputType": "JSON"
      },
      "Parameters": {
        "Bucket.$": "$.target.bucket",
        "Key.$": "$.target.key"
      }
    }
    // ResultWriter がない → 結果が直接次のステートへ
  },
  "Map (2)": {
    "Type": "Map",
    "ItemProcessor": {
      "ProcessorConfig": {
        "Mode": "INLINE"
      }
      // ... エクスポート処理
    }
  }
}

解決策: ResultWriter と ItemReader の活用

Step Functions の分散 Map には、大きな結果を S3 に書き出す ResultWriter という機能があります。これと ItemReader を組み合わせることで、256KB の制限を回避できます。

ResultWriter とは

ResultWriter は、分散 Map の実行結果を S3 に書き出す機能です。設定すると、以下のような構造でファイルが出力されます。

指定したS3プレフィックス/
└── {実行ID}/
    ├── manifest.json       # マニフェストファイル
    ├── SUCCEEDED_0.json    # 成功した子ワークフローの結果
    ├── SUCCEEDED_1.json    # (5GB を超える場合は分割される)
    ├── FAILED_0.json       # 失敗した子ワークフローの結果
    └── PENDING_0.json      # 未実行の子ワークフローの情報

マニフェストファイルには、エクスポート場所やマップ実行 ARN、各結果ファイルへの参照などのメタデータが含まれています。

ItemReader とは

ItemReader は、分散 Map の入力データを S3 から読み込む機能です。通常、Map ステートは ItemsPath で指定した配列をループしますが、ItemReader を使うと S3 に保存された JSON や CSV ファイルから直接データを読み込めます。

これにより、以下のメリットがあります。

  • 大量データの処理: 256KB の入力制限を気にせず、S3 上の大きなデータセットを直接処理できる
  • 柔軟なデータソース: JSON、CSV、マニフェストファイルなど様々な形式に対応
  • ポインタ指定: ItemsPointer を使って、ファイル内の特定のパスにあるデータを抽出できる

今回のワークフローでは、ResultWriter で書き出したマニフェストファイルを ItemReader で読み込み、結果ファイルの一覧を取得するために使用しています。

修正後のワークフロー構成

Map(分散モード)─── S3から入力データを読み込み(ItemReader)
  └── Map(1)(インラインモード)─── 各クエリを実行
        └── SQL生成 → 実行 → 結果取得
      ↓
  ResultWriter で S3 に結果を書き出し ← 【ポイント】
      ↓
  【S3 経由でデータを受け渡し】
      ↓
「S3に保存した処理取得」Map(分散モード)─── マニフェストから結果ファイル一覧を取得(ItemReader)
  └── Map(2)(分散モード)─── 各結果ファイルを読み込み(ItemReader)
       └── エクスポート処理

※ マニフェストには複数の結果ファイル(SUCCEEDED_0.json, SUCCEEDED_1.json など)への参照が含まれるため、それぞれを並列処理するために Map を使用しています。

ポイントは、最初の MapResultWriter を追加して結果を S3 に書き出し、後続の処理では ItemReader を使って S3 から読み込むようにした点です。

修正後の定義(抜粋)

1. ResultWriter の追加

最初の Map に ResultWriter を追加し、結果を S3 に書き出すようにしました。

{
  "Map": {
    "Type": "Map",
    "Next": "S3に保存した処理を取得",
    "Iterator": {
      // ... 既存の処理
    },
    "ResultWriter": {
      "Resource": "arn:aws:states:::s3:putObject",
      "Parameters": {
        "Bucket": "your-bucket-name",
        "Prefix": "workflow-results/"
      },
      "WriterConfig": {
        "OutputType": "JSON",
        "Transformation": "FLATTEN"
      }
    }
  }
}

2. マニフェストから結果ファイルを読み込む Map の追加

ResultWriter の出力には ResultWriterDetails というフィールドが含まれ、マニフェストファイルの場所が記載されています。これを ItemReader で読み込みます。

{
  "S3に保存した処理を取得": {
    "Type": "Map",
    "ItemProcessor": {
      "ProcessorConfig": {
        "Mode": "DISTRIBUTED",
        "ExecutionType": "STANDARD"
      },
      "StartAt": "Bucket付与",
      "States": {
        "Bucket付与": {
          "Type": "Pass",
          "Next": "Map (2)",
          "Parameters": {
            "Bucket": "your-bucket-name",
            "Key.$": "$.Key"
          }
        },
        "Map (2)": {
          // ... 後続の処理
        }
      }
    },
    "ItemReader": {
      "Resource": "arn:aws:states:::s3:getObject",
      "ReaderConfig": {
        "InputType": "JSON",
        "ItemsPointer": "/ResultFiles/SUCCEEDED"
      },
      "Parameters": {
        "Bucket.$": "$.ResultWriterDetails.Bucket",
        "Key.$": "$.ResultWriterDetails.Key"
      }
    }
  }
}

ポイントは ItemsPointer: "/ResultFiles/SUCCEEDED" の部分です。マニフェストファイル内の成功した結果ファイル一覧を直接参照し、それぞれを並列処理のアイテムとして扱います。

3. 各結果ファイルを読み込んで処理

最後に、各結果ファイルを ItemReader で読み込み、エクスポート処理を実行します。

{
  "Map (2)": {
    "Type": "Map",
    "ItemProcessor": {
      // ... エクスポート処理
    },
    "ItemReader": {
      "Resource": "arn:aws:states:::s3:getObject",
      "ReaderConfig": {
        "InputType": "JSON"
      },
      "Parameters": {
        "Bucket.$": "$.Bucket",
        "Key.$": "$.Key"
      }
    }
  }
}

まとめ

Step Functions の 256KB ペイロードサイズ制限に遭遇した際の解決方法を紹介しました。

ポイント:

  • Step Functions のステート間で受け渡せるデータは 256KB まで
  • 大きなデータを扱う場合は S3 を経由 する設計が必要
  • 分散 Map の ResultWriter で結果を S3 に書き出せる
  • ItemReaderItemsPointer でマニフェストから必要な情報を抽出できる

今回のエラーをきっかけに Step Functions の制限と、それを回避するための機能について理解を深めることができました。同様の問題に遭遇した方の参考になれば幸いです。

最後まで読んでいただき、ありがとうございました。