ブログ

【AWS】Lambda vs ECS|実行制限とコストで決めるバッチ処理選定基準

この記事をSNSでシェア!

はじめに

AWS でサーバーレスアーキテクチャを検討する際、まず最初に候補に挙がるのは AWS Lambda ではないでしょうか。Lambdaは運用負荷が低く、イベント駆動とも相性が良いため、初期構築ではとても扱いやすいサービスです。一方で、実データを使った検証や本番運用の段階になると、処理時間や一時領域といった「実行制限」という壁に突き当たることがあります。

本記事では、実際に Lambda のデータサイズ・タイムアウト課題に直面し、Amazon ECS Run Task へ移行した経験をもとに、バッチ処理における計算リソースの選定基準と、ECS Run Taskを使った実装で得られた学びを整理して紹介します。

本記事の対象読者

  • Lambdaの実行制限(時間・リソース)により、アーキテクチャの変更を検討している方
  • ECS Run Taskを使った長時間ジョブの実装パターンを知りたい方
  • AWSのサーバーレスアーキテクチャで大容量ファイル処理の実装に興味がある方

本記事で取り扱わないこと

  • VPCやサブネット等のネットワーク基礎インフラの構築手順
  • IAMロールの具体的なポリシー記述例
  • DockerイメージのビルドおよびCI/CDパイプラインの詳細

Amazon ECS on Fargate・Run Taskとは?

Amazon ECS は、Dockerコンテナの起動や停止、スケジューリングをマネージドで行えるコンテナオーケストレーションサービスです。今回利用した Fargate は ECS の実行基盤の1つで、EC2インスタンスを自前で用意・管理せずにコンテナを実行できるサーバーレスオプションです。OS やミドルウェアのパッチ適用が不要で、Lambda と同様にインフラ運用の負荷を抑えられます。

ECS Run Task は、ECS タスクを単発で直接起動するための API です。ここでいうタスクとは、あらかじめ定義したコンテナ設定に従って実行される処理単位を指します。Lambda から ECS Run Task を呼び出すことで、重い処理だけを必要なタイミングでコンテナに委譲できます。

開発中に見えてきた課題

開発初期はS3にオブジェクトが保存されたことをトリガーに随時起動し、変換処理を行うシンプルなバッチ処理で設計していました。当初は処理するデータ量が未確定だったこともあり、サンプルデータでロジックの検証を行い、問題なく動いていたためLambda で十分だと考えていました。

図1:Lambda単体構成のアーキテクチャ

ところが詳細な要件が確定し、本番想定のサンプルデータサイズを確認したところ、次の課題が顕在化しました。

  • リソース不足:数ギガバイトに及ぶ中間ファイルを展開しようとした際、メモリ不足エラーが発生。
  • 15分の壁:大容量ファイルのパース処理がLambdaの最大実行時間(15分)に間に合わず、タイムアウトエラーが発生。

Lambda を用いたアーキテクチャで実現できる想定で設計を進めていた私たちにとって、これはロジックの改修やアーキテクチャ自体の変更という大きな方向転換を迫られる事態でした。運用負荷を抑えつつ、これらの制限をどう回避すべきか。その答えが、ECS Run Taskによる処理のオフロードでした。

選定の基準:Lambdaの「限界」をどう見極めるか

LambdaとECS、どちらを選択すべきかの判断は、単純な「好き嫌い」ではなく、ワークロードの特性に基づいた客観的な基準が必要です。私たちは以下の4つの軸で評価を行いました。

① 実行時間

Lambdaの最大実行時間は15分、対してECSには実行時間の制約はありません。データの増加に伴い線形的に処理時間が延びる場合、今は良くても将来的に必ず限界が来ます。そのため、15分以内に確実に終わる保証があるのか考慮が必要です。

② 一時ストレージとメモリ

Lambdaでもメモリ・一時ストレージはそれぞれ最大10GBまで拡張可能ですが、大きなファイルを展開したり、中間生成物を多く持ったりする処理では余裕が不足しやすくなります。コンテナ実行を前提に設計できるECSは、CPU、メモリ、ストレージの自由度が高く、重い処理に向いています。

③ 処理頻度と応答性

高頻度で短時間の処理はLambdaと相性が良いです。一方で、完了まで数十分以上かかる処理はECSの方が設計しやすくなります。また、Lambdaは数秒で起動が可能ですが、ECSは数分のプロビジョニングが発生するため、即時応答が必要かどうかも判断材料になります。

④ コストモデル

Lambdaはリクエスト数と実行時間、メモリサイズに応じた従量課金です。ECS Run Task (Fargate)はvCPU、メモリ、ストレージ利用量に応じて、タスク起動から終了まで課金されます。短時間処理はLambdaが有利な場面が多い一方で、長時間かつ重い処理ではECS Run Task (Fargate) の方が有利な場面もあります。

  • Lambda:リクエスト数 + 実行時間(1ms単位)x メモリサイズ の従量課金。
  • ECS Run Task (Fargate):タスクの起動から終了までの時間課金。
    • CPU:CPUサイズ x 単価 x 稼働時間
    • メモリ:メモリサイズ x 単価 x 稼働時間
    • ストレージ:20GBを超えるストレージ容量(GB) x 単価 x 時間

月あたりで処理される想定データ量から計算した実行時間を元に、それぞれの推定費用を算出したところほぼ同等であり、将来的にデータ量が増えた場合にはECS Run Taskの方がコストメリットがあるような結果になりました。

採用比較:Lambda vs ECS Run Task

両者の主な違いを比較表にまとめました。

比較項目LambdaECS Run Task
最大実行時間最大15分無制限
CPU/メモリ割り当てに連動(最大10GB)CPU・メモリの柔軟な組み合わせが可能
一時ストレージ最大10GB最大200GBまで拡張可能(20GBまで無料)
起動速度ミリ秒〜秒単位分単位(プロビジョニングが必要)
管理サーバレスサービスのため最低限の設定のみコンテナのレジストリ・イメージ管理が必要
料金短時間・散発的な処理なら最安長時間・高負荷なバッチ処理で高コスパ
向いている処理短時間・高頻度長時間・大容量・非同期

ECS Run Taskを採用

結果としてECS Run Taskを採用しました。今回のケースではデータサイズが数GBのJSONファイルということで、オンメモリでの展開がネックとなりました。Lambdaで実装するにはメモリや一時ストレージを最大まで引き上げる必要があり、過剰な設定となります。リアルタイムな処理が不要で非同期バッチ処理で十分という点も踏まえ、ECS Run Taskへの移行を決定しました。

図2:ECS単体構成のアーキテクチャ

実装してみてわかったこと:ECS Run Taskの成果と課題

実際に導入した結果として、課題となっていたメモリ不足エラーとタイムアウトを解消することができました。ECSに移行したことで20GBの一時ストレージを活用できるため、一度に全ての情報をオンメモリで展開するのではなく、一時ストレージに展開した情報をメモリで読み取る方式に処理ロジックを変更しました。

最終的には、Lambdaを完全にECSに置き換えるのではなく、Lambdaをオーケストレーターとして残し、重い処理だけをECS Run Taskへ委譲する構成にしました。検証中に処理対象ファイルのサイズが小さすぎる場合、ECSでの処理時間が1分未満で終わる可能性があったためです。ECSの最低課金時間は1分なので、1分以内に処理が完了した場合はコストが余分にかかります。順次処理ではなく、日次バッチとしてオーケストレーションLambdaを起動し、ECSに対象ファイルを振り分ける構成がよいと考えました。これによりECSのタスク稼働時間を1分以上にでき、タスク起動数を軽減できます。

図3:Lambda + ECS構成のアーキテクチャ

新たに向き合った課題

ECSへの移行によって、新たな対処が必要な課題も生まれました。

Dockerイメージ管理とデプロイフローの整備

Lambdaはコードをそのままデプロイできますが、ECSではDockerイメージのビルド・プッシュ・タスク定義の更新が必要となります。CI/CDパイプラインの整備やイメージのバージョン管理などデプロイフローを見直す必要がありました。

ECSタスクの状態監視とエラーハンドリング

Lambdaはタイムアウトや例外が自動的にエラーとして記録されますが、ECSのタスクは「起動した」「終了した」という状態しかAWS側から返ってきません。タスクが正常完了したのか、内部でエラーが発生して終了したのかを判定する仕組みを自前で設計する必要がありました。CloudWatch Logsのエラー検知やタスク終了コードの確認など、Lambda以上に監視設計を意識した作りが求められます。今回はCloudWatchカスタムメトリクスを作成し、成功・エラーを記録するとともにLambda側で再実行を促すように設計しました。

Fargate起動時間(プロビジョニング待機)

ECS Run Taskを呼び出してから実際にコンテナが動き出すまで、1〜2分程度の待機時間が発生します。リアルタイム性が求められる処理には不向きですが、今回の日次バッチ処理では許容範囲でした。

なぜAWS Batch・Step FunctionsではなくECS 直呼び出しにしたか

フロー管理やエラーハンドリングを行うため、AWS BatchとAWS Step Functionsも選定候補に挙がりましたが、それぞれ採用しなかった理由は次のとおりです。

AWS Batch を採用しなかった理由

  • ジョブキューの管理が不要:今回のバッチ処理は「対象ファイル群をまとめてECSに投げる」というシンプルな構成で、ジョブの順序管理や複雑なスケジューリングは必要ありませんでした。
  • 設定の複雑さ:AWS Batch はコンピューティング環境・ジョブキュー・ジョブ定義の整備が必要で、単純なタスク実行に対してはオーバーヘッドが大きかったためです。

Step Functions を採用しなかった理由

  • 構成の複雑さ:今回のバッチ処理は「起動→処理→終了」のシンプルな流れで、状態遷移の管理が不要でした。
  • コスト:状態遷移ごとに課金されるため、今回の用途では割高になります。

LambdaからECS Run Taskを直接呼び出す構成で十分と考えました。

まとめ

解消された課題

数GBのJSONファイルを処理する際、Lambdaではメモリ不足で停止していました。ECSへの移行により20GBの一時ストレージが利用可能になったため、処理方式を改善し、全データをメモリに載せるのではなく、ストレージへの書き出しと読み込みを組み合わせることで、安定した処理を実現しました。また、ECS には実行時間の制限がないため、今後データ量が増えた場合にタイムアウトする可能性を考慮する必要がなくなり、リトライ設計やエラーハンドリングに集中できるようになったことも、設計品質の向上につながっていると思います。

最終構成:Lambdaをオーケストレーターとして残した理由

上記のアーキテクチャ図のとおり、最終的にはLambdaをオーケストレーターとして残し、重い処理だけをECS Run Taskへ委譲する構成にしました。この設計の背景には、Fargateの最低課金時間(1分)があります。ファイルサイズによっては1分未満で処理が終わるケースもあり、各ファイルごとに逐次タスクを起動すると無駄なコストが発生します。そこで、日次バッチとしてオーケストレーションLambdaを起動し、対象ファイルをまとめてECSに振り分ける構成にしました。これにより、ECSタスクの稼働時間を1分以上に保ちつつ、タスク起動回数を抑えることができます。

最後に

Lambdaは非常に便利なツールですが、すべてのバッチ処理に最適とは限りません。今回のように「本番データで制限に当たる」というリスクを早期に見極め、早い段階でECS on Fargateを候補に入れる価値があります。

  • 15分を超える可能性があるか?
  • CPUやメモリの要件が重いか?
  • 特殊なリソース制限(ディスク、メモリ)が必要か?
  • 非同期で安定重視のバッチ処理が必要か?

今回の移行では、Lambdaの制限に開発途中で気づいたこともあり、アーキテクチャの再設計とCI/CDフローの見直しを同時に進める必要がありました。振り返ると、選定基準を4つの軸で早めに整理しておいたことで、チーム内の合意形成がスムーズに進んだと感じています。想定される要件を確認し、サービス選定を見直すことが必要です。

参考文献

AWS公式ドキュメント:Amazon ECS ⼊⾨

AWS公式ドキュメント:AWS Fargate の料金

投稿者プロフィール

羽賀 夢馬
羽賀 夢馬
2023年入社。BS事業部所属。
AWS を活用したシステム開発・インフラ構築を主に担当しています。
この記事をSNSでシェア!