直列なバッチをAWSBatchとLambdaで作る

5 min読了の目安(約3400字TECH技術記事

Hatena の転載です。
https://hayata-yamamoto.hatenablog.com/entry/2020/08/09/205552

はじめに

データ取得や前処理などで、バッチをつなぎ合わせて一連の処理を一気にかけてしまいたいシーンは、結構あるのではないでしょうか?例えば、画像を S3 から取得してリサイズする、その後、リサイズした画像を API に投げる。みたいな。そして、多くの場合、タスク同士が依存していて、実行計画をちゃんと考えてあげる必要があります。要するに、「A がおわったら B を行う」をちゃんと表現できるように、プログラムやリソースを作成してあげないといけないね、と言うことです。

データ処理のワークフローを、データパイプラインと呼んだりすることもあります。成熟したチームだと、Airflow とか luigi などのツールをいい感じに使いこなして、高可用かつ高効率のシステムを作ったりするかもしれません。実際にやってみると、いろいろと考えないといけないポイントがあるため、ツールで補えるところは積極的に補っていきたいと思うのは自然でしょう。

さて、今回は、データパイプラインをとりあえず作りたい時に使える簡易的な構成を紹介します。結構、ありふれた構成だとは思いますので、私だったらこうする、みたいなコメントをお待ちしています。PR でも OK です。

どういう時に使えるか

今回は、以下のようなケースで使えそうな構成を作成しました

  • とりあえず管理を手放したい
  • バッチはコンテナになっている
  • 別のタスクと処理が依存していて、他のタスク実行の待ちが必要
  • バッチは何度やっても同じ結果になる
  • 1つ1つの処理時間がまあまあかかる(例えば、30 分以上とか)

また、この記事では Terraform の具体的なコードは出てきません。記事のボリュームが多くなってしまい、読んでいただくみなさんの負担が増えると考えたためです。リンクは貼っておりますので、気になる方はそちらを見ながら読んでいただくと良いかと。

どういう構成か

構成はとてもシンプルです。要するにやっていることは 3 つだけです。

  1. 実行したい Batch の定義と、計算環境、ジョブのキューを作成する
  2. Lambda から Batch のジョブキューにジョブを送信する。その時、前に終わっていて欲しいタスクの jobId を dependsOn に含めて渡す
  3. ジョブをコントロールする Lambda が CloudWatch のイベントルールで起動されるようにする

本記事の残りでは、この要点のうち最初の2つについて具体的な説明を行います。

バッチを実行するリソースを作る

ここの話をします。AWS Batch に対して以下のリソースを作成します。

  • 2 つのジョブ定義
  • 1 つの計算環境
  • 1 つのジョブキュー

計算環境と、ジョブキューは1つで十分です。1つのジョブキューを2つのジョブ定義が共有するようにして、管理するリソースを減らします。今回のケースでは、計算リソースの vCPU の数を小さくしていますが、ここを大きくして、optimal インスタンスタイプを選択しておけば、インスタンスをあまり意識しなくてよくなるため楽です。

また、SPOT インスタンスを使って、コストを効率化することもできます。私の理解が未熟なため、今回のコードでは SPOT インスタンスは使っていません。(トライしましたが、案の定詰まりました...)

https://aws.amazon.com/jp/ec2/spot/

ジョブをコントロールする Lambda を作る

ここの話をします。意外と、この Lambda でバッチを管理するところが調べても出てきませんでした。

そんな時に参考になった記事がこちら。まさに、今回のケースにぴったりの記事でした。(正直言うと、ほとんどこれを真似してます)
https://engineering.door2door.io/orchestrating-scheduled-data-batch-jobs-on-aws-ee45f940696f

ちなみに、彼らも以下のように語っています。

So we expected there was a configurable cron to schedule the jobs. But there we couldn’t find a way to schedule the Batch jobs. The only information the docs provide is this short paragraph describing job scheduling:

私も同じ状況になり、大変困りました。彼らは AWS サポートに聞いて解決したそうですが、そんな苦労した実装をちゃんと公開してくれているあたりとても好感が持てますよね。上記の記事でも紹介されていますが、似たようなサービスに StepFunctions と言う分散アプリケーション調整サービスがありますので、ユースケースによっては StepFunctions の方がもっと楽できるかもしれません。

https://aws.amazon.com/jp/step-functions/

さて、やっていることはすごく簡単です。ここでは、簡単化のため、先に実行したいジョブを JobA、後に実行したいジョブを JobB として説明します。

  1. Lambda から、JobA に対して、ジョブを送信する
  2. ジョブ送信が完了したら、送信したジョブの ID を取得する
  3. JobB に対して、ジョブを送信する。この時、JobA のジョブ ID を依存関係に設定して送信する。

具体的な処理の流れはこちら
簡単ですよね。ジョブキューにジョブを送信してるため、ジョブ送信後すぐに実行されるわけではありません。依存関係がある場合は、Pending 状態になりしばらくバッチの実行は保留されます。

これで無事、直列にバッチが実行されるようになりました。めでたし。

おわりに

今回は、 直列なバッチ処理を AWS Batch + Lambda + CloudWatch で定期的に行う構成を紹介しました。実装は Terraform で行っています。Terraform Cloud を用いて、Plan と Apply はリモートで行いました。結構便利で、オススメです。

Lambda の部分は管理が蔑ろになりがちだなと思ったため、GitHub Actions で言い訳程度の CI/CD を入れています。Lambda は対応している Runtime がしばしば更新されるため、最初から CI 上で Matrix 実行しておき、バージョンアップがきてもコードが動く状態を保っておくのが良いのかなと思っています。また、lambda が増えることを想定して、 functions ごとに CI が走るようにもしています。

ここまで読んでいただきありがとうございました。それでは、また。