AWS Batch開発で直面した3つの課題と実践的な解決アプローチ
本記事では、AWS Batchを使った開発プロジェクトで得られた学びと、効率的な開発環境構築の工夫について共有いたします。
初めてAWS Batchを扱う中で直面した技術的な課題を、チームで協力しながら解決した経験をまとめました。
なお、顧客プロジェクトに関する内容のため、AWS上の設定など、詳細は省略させていただきます。
本記事で扱うトピック:
- Batch処理およびAWS Batchの概要
- [学び1] AWS Batch処理の起動トラブルシューティング
- [学び2] 効率的な開発サイクルの構築
- [学び3] 保守性の高いコード設計への改善
AWS BatchおよびBatch処理の概要
当たった課題の説明の前に、まずはAWS BatchとBatch処理の概要について概要を説明します。
概要:Batch処理
本プロジェクトでは、以下のようなバッチ処理の実現に向けて開発を行いました。
実際は上記より複雑なのですが、ポイントとしては以下3点です。
- 1つのバッチ処理の中で、複数のsubprocessを走らせる
- subprocessのみでの実行が可能
- 各サブプロセスは、サブプロセスに対応づけられたテーブル(AWS RDS)にレコードを保存することがゴール
- 各サブプロセスの処理時間は短いとは言えず、処理が少なくなるように引数等を工夫しても、1~2時間程度かかるものも多い。
処理の概要については以上です。
概要: AWS Batchについて
AWS Batchとは、数時間、数日以上かかるような処理の実行をするために設けられた、バッチジョブ専用のフルマネージドサービスです。
AWS Lambdaとは異なり、15分以上の処理や、定義した複数バッチ処理を跨いだ処理も可能とのことです。(今回は1つのバッチ処理のみを使っております)
バッチ処理の実行にあたり、以下の4つを設定する必要があります。
- コンピューティング環境: バッチジョブを実行するEC2マシン。バッチ処理のジョブを実行し、ジョブの状態がRUNNABLEになった時点で、EC2インスタンスが立つ。
- ジョブキュー. ジョブキュー: 優先度の高いジョブから順番に、上記マシン群で実行する待ち行列
- ジョブ定義. バッチ処理自体の定義。pythonなどで処理を記載し、ECRに送信した処理をジョブ定義の設定で参照することで、定義が完了する。
- ジョブ. ジョブ定義で定義されたジョブのインスタンス。
- 同じジョブ定義であっても、実行コマンドを変更することで別のジョブとして扱うようなイメージ。
それぞれ別で設定することも可能ですが、下画像の今すぐ始めるボタンを押して、まとめて設定もできます。
簡単ではありますが、AWS Batchについての説明は以上です。
1.batch処理がスタートしない
1つ目は、batch処理がスタートしない問題です。
はじめに原因と解法をいかに述べます。
- 原因.VPCに配置されたEC2インスタンスが、AWS Batchの管理サービスと通信できなかった。
- 解法.ネットワーク設定(EC2インスタンスのVPCと外部)の見直し
続けて、現象にあたって解決するまでの時系列的な流れを、記憶している範囲で共有いたします。
1.1. Runableから動かない
AWS Batchのジョブを送信すると、[runnable]->[starting]->[running]->[completed/failed]の流れで状態が遷移します。running状態になると、cloud watchでログが確認できるのですが、私の場合はrunnableの状態で止まってしまい、10~20分経過してもstartingに状態が移行しませんでした。
1.2. ユーザーガイドを確認し、ポリシーなどを見直した
AWSユーザーガイドのrunnable状態でジョブが止まるを確認し、必要そうなポリシーのアタッチ等を実行しました。
アタッチをした後に再度ジョブ送信を行いましたが、runnableで状態が止まる事象は変わりませんでした。
1.3. EC2インスタンスの中身を見てみる
調査をし、AWS Batchのジョブ実行後にEC2インスタンスがあることを突き止めました。
ジョブ実行後に作成されるインスタンスにSSH接続をし、中身を見てみました。
* SSH接続のためには、(確か)コンピューティング環境設定の際に専用の設定をする必要があります。
docker ps
をEC2インスタンスで叩いてみたところ、バッチ処理専用のdocker containerが立ち上がっていました。docke execで処理をみたところ、外部との通信ができていないことが原因で発生しているようなエラーを見つけました。
1.4. ネットワーク設定の見直し
セキュリティなどの観点から、各種リソース配置をする上で、適宜VPCを利用していました。
またそれとは別でEC2インスタンスがジョブを実行開始するために、EC2外部のバッチ管理サービスと通信することを知識として確認しました。
通信自体ができていないと考え、(詳細は省きますが)EC2と外部の通信設定を見直したところ、無事にジョブがrunnable->startingに移行しました。🎉
現象: batch処理を設定し、runnable->starting->running->[success/failed]と移行するはずだが、runnableのまま固まり、startingに移行しない。
原因:
- VPCに配置したEC2(batch処理を実行するマシン)が、batch処理動作全体をオーケストレーションする外部のものと通信できていなかった。(ネットワーク通信の問題)
解決策
- EC2を配置した(コンピューティング環境)サブネットに対して、インバウンド、アウトバウンドの設定を行う。(外部との通信経路を確保する)
デバッグの流れ
- runnableになったあと、EC2にマシンが追加されたことを確認。
- EC2マシンにSSH接続し、バッチ処理用のコンテナが作成されていることを確認。
- コンテナにdocker execで入って、ログを確認したところ、外部とのネットワーク通信に失敗していたことを特定。
解決策
- EC2を配置した(コンピューティング環境)サブネットに対して、インバウンド、アウトバウンドの設定を行う。(外部との通信経路を確保する)
2.デバッグに時間がかかる
2つ目は、デバッグに時間がかかる点です。
はじめに原因と解決策をいかに述べます。
- 原因.
- バッチ処理実行開始までの工数が20~30分かかる
- バッチ処理自体にも平均1.0時間以上かかる
- 解決策
- ORM/migrationツールの導入
- できる限りローカルでデバッグし、実行テストを行う
2.1 どう困ったか
上述の通り、本Batch処理は、サブプロセスの数が多く、それぞれのサブプロセスの処理時間も(平均して)1時間以上と長いです。
加えて、ローカルで開発した処理をジョブとして開始するには、以下のステップがあります。
- docker imageとしてbuild. 5~10分
- ECRへdocker imageをpush. 2~10分. (上り100Mbps以上を想定)
- ジョブ定義の設定(image変更のたびに行う). 2~3分
- ジョブの送信.
- runnable->starting->runningの待ち時間. 5~10分
体感ですが、↑の1~5で平均20~30分くらいかかりました。
軽微な変更、エラーであっても上記対応を行う必要があるため、バッチ処理の開発が進むごとに↑を繰り返す数も増えていきました。
2.2 ローカルデバッグ/マイグレーションツールの導入
2.1の内容を社内で相談し、ローカルデバッグを導入することにしました。具体的には以下です。
- ORM/マイグレーションツールの導入、運用
- ローカルにdbを立てておき、そこのテーブルにinsertをする。
2.2.1. ORM/マイグレーションツールの導入、運用
1つ目について、ORMはsclalchemyを使用しました。これにより、例えば簡単なクエリであればSQLを書かずに処理を記載できるメリットがあります。(SQLに慣れている場合はこちらのメリットは大きくないと思います)
# ORM使用
users = User.objects.filter(age__gte=20).order_by('name')
# 生SQL
users = execute_sql("SELECT * FROM users WHERE age >= 20 ORDER BY name")
マイグレーションツールはalembicを使用しました。
こちらはスキーマの変更管理、DBへの変更反映を実施してくれるツールで、
alembic revision XXX ## sqlalchemyの枠組みで定義したモデルの変更を検知し、リビジョンファイルを生成
alembic update # 変更を反映
のようなコマンドで上記を実現します。
PJT最初はpsycopg2
ライブラリでINSERT文を書いていたため、導入時は初期コストがかかりましたが、スキーマ変更専用で作っていたスクリプトと比べるとスキーマの管理や変更が用意になったので恩恵を受けられたと感じています。
2.2.2. ローカルにdbを立てておき、そこのテーブルにinsertをする。
上記ツールとは別に、ローカルにAWS RDSと同じ規格のDBを立てて、そちらにINSERT等の処理をするように見直しを行いました。
具体的には以下3点です。
- args(argparse)でローカルとAWSで処理を分岐するようにする。
- .env.XXをローカル/AWSで分けることでalembicコマンドの実行対象を分ける
- DBクライアントツールの導入
- 詳しくないですがDBeaverを使いました。
これにより、軽微なバグを見つけるスピードを早めることができたため、docker imageのbuilc~ジョブの実行の往復回数が減ったと思います。
また、argsでlocalが指定された時は、INSERTするレコード数を制限するなどすることでサブプロセスの実行時間を短縮化することもできました。(ただし、AWS Batchとは同じ条件ではないため検知できないバグもありました)
3. コード設計の継続的改善
3つ目は、ソースコードについてです。
プロジェクトが進むにつれて、より保守性の高いコード設計の重要性を実感しました。
複雑なバッチ処理を扱う中で、以下の点が今後の改善ポイントとして見えてきました。
3.1 設計段階での工夫ポイント
当初は以下の設計を行っていました:
- スキーマ設計
- 処理のシーケンス図作成
プロジェクトを通じて、さらに以下の設計要素の重要性を認識しました:
追加で効果的だった設計要素
- ディレクトリ構成の事前設計: 機能単位でのモジュール分割を明確化
- サブプロセス単位での詳細設計: 各機能の責務を明確にし、テスタビリティを向上
- 3層アーキテクチャの採用検討: プレゼンテーション層、ビジネスロジック層、データアクセス層の分離
3.2 得られた学び
このプロジェクトを通じて、バッチ処理特有の設計パターンや、大規模データ処理における設計の重要性を学ぶことができました。
今後は、これらの経験を活かし、より効率的で保守性の高いシステム開発に取り組んでいきたいと考えています。
まとめ
本記事では、AWS Batchを使った開発で得られた実践的な知見を共有させていただきました。
主な学び
- ネットワーク設定の重要性: VPC環境でのAWS Batch実行には適切な通信経路の確保が必須
- 開発効率化の工夫: ローカル環境の活用とORM/マイグレーションツールの導入により、開発サイクルを大幅に短縮
- 設計の継続的改善: プロジェクトを通じて、より良い設計パターンを発見し適用
これらの経験は、AWS Batchに限らず、様々なバッチ処理開発に応用できる知見だと考えています。
特に、開発環境の最適化と設計の重要性は、どのようなプロジェクトでも活かせる学びでした。
今後も、このような技術的な挑戦を通じて得られた知見を共有し、エンジニアコミュニティに貢献していければと思います。
最後までお読みいただきありがとうございました。
Nishikaについて
Nishikaは2019年に創業、「テクノロジーですべての人が誇りを持てる社会の実現」をビジョンに掲げ、「テクノロジーを、普段テクノロジーからは縁の遠い人にとっても当たり前の存在としていき、皆の仕事の付加価値・業務効率を向上させることに貢献したい」と考え、活動しています。
AIプロダクト事業/AIコンサルティング・開発事業/AI人材事業を手掛け、AIコンサルティング・開発事業では「生成AIを使うと何が嬉しいのか、通り一遍ではない使い方を知りたい」という段階のお客様から、伴走してご支援するアプローチを強みとしています。
We're hiring!
Nishikaテックチームでは、「テクノロジーを、普段テクノロジーからは縁の遠い人にとっても当たり前の存在としていく」を目指し、音声AIプロダクトの開発・生成AIを活用した課題解決ソリューションの構築を行なっています。
興味をお持ちいただけた方は、以下リンクからご応募お待ちしています。インターンも募集しております!
Discussion