AWS Batch のジョブパターン (SEQUENTIAL, N_TO_N) の挙動を確認してみた

2020.12.13

はじめに

おはようございます、もきゅりんです。

皆さん、今日も AWS Batch 使ってますか?

Fargate も来ましたねー。

[アップデート]ゼロキャパシティからのバッチ処理に最適!AWS Batch で AWS Fargate が利用可能になりました! #reinvent | Developers.IO

それはさておき、 AWS Batch の配列ジョブのドキュメントと図を読んでいて、たぶん記載されている通り、挙動はこうだろう、とイメージしていたのですが、実際そうなのか気になったので試してみました。

本稿では、AWS Batch の基本的な概念や構成要素、使い方は把握している方を対象としています。

AWS Batch とは?という方は、まずは下記弊社ブログをご確認頂ければと思います。

また、環境を構築したい場合は、下記ブログを参考して下さい。(本稿は ECS on EC2 で検証しています。)

[アップデート] AWS Batch がコンピューティング環境での Amazon Linux2 の利用をサポートしました | Developers.IO

おさらい

下記スライドの通り、AWS Batch では配列ジョブとジョブ依存関係を使って様々なジョブパターンを設計できます。

なお、より複雑なジョブワークフローについては、Step Functions を使うと良いでしょう。

依存関係とは

あるジョブを送信するときに、そのジョブが依存するジョブIDを指定します。依存関係で指定されたジョブが正常に完了した後にのみ、そのジョブは実行されます。最大20つの他のジョブへの依存関係を設定することが出来ます。

使い方として、ジョブを送信するとジョブIDが出力されるので、それを次に設定したいジョブの依存関係に指定します。

配列ジョブとは

配列ジョブは、ジョブ定義、vCPU、メモリなどの共通パラメータを共有するジョブです。複数のホストに分散されたり、同時に実行される場合もあります。

配列ジョブには SEQUENTIALN_TO_N があります。

SEQUENTIAL はジョブIDを指定せずに、配列ジョブがインデックス0から順番に完了して実行するように設定できます。つまり、配列ジョブのジョブIDを指定せずに、実行タイプを SEQUENTIAL にすることで、インデックス0から順番に完了するようにすることができます。

インデックス順にタスクを実行するため、同時実行する配列ジョブより時間がかかります。

N_TO_N は、依存関係を持つジョブIDを指定します。 SEQUENTIAL ジョブIDを指定することで、SEQUENTIAL ジョブのインデックス0が完了したら、子ジョブ0を実行、インデックス1が完了したら、子ジョブ1を実行...といった、N_TO_N のジョブを設定できます。

検証と仮説

S3 に配置された 0 と記載された csvファイルを開いて5回繰り返し +1 してファイルを更新するプログラムと、同じファイルを開いて値を平方してログ出力するプログラムで検証します。

それぞれ以下のような検証と結果になると想定されます。

検証1. 配列ジョブ(SEQUENTIAL)

  • 予想結果1: 0,1,...,5 まで記載されたファイルがS3に格納

検証2. 検証1と依存関係をもつ平方ジョブ

  • 予想結果2: 0,1,...,5 まで記載されたファイル (SEQUENTIAL) と 最後の 5 の平方の 25 がログ出力される

検証3. 配列ジョブ(NO-Dependency)

  • 予想結果3: たぶん 0,1 みたいに途切れた更新のファイル(ほぼ同時に実行されてちゃんとカウントされない)

検証4. 検証1と平方ジョブを子ジョブに持つ配列ジョブ(N2N)

  • 予想結果4: 以下表のような結果
Arrayインデックス SEQUENTIAL(S3) N2N(ログ)
0 0,1 1
1 0,1,2 4
2 0,1,2,3 9
3 0,1,2,3,4 16
4 0,1,2,3,4,5 25

結果

検証 1~4 、すべて仮説の通りでした。

現在 (2020/12/13) 、コンソールから SEQUENTIAL を実行するとうまく設定されないのか、順番通りに実行されません。 (ここで結構時間を費やしました。。)

一時的な不具合なのか、 AWS Batch でジョブフローを実行するのに、コンソールを使うことがあまりないために誰も気付かなかったのか、CLI や SDK を使わないとうまくできなそうです。。(CLI ver.2 では確認済み) 注意しましょう。

やってみた

前提

下記が構築済み。

[アップデート] AWS Batch がコンピューティング環境での Amazon Linux2 の利用をサポートしました | Developers.IO

S3にファイルをアップロード

## csvファイル作成
echo -e 0 > count.csv

CodeCommit に登録するスクリプト

環境変数の BUCKET_NAME, KEY にはそれぞれ環境に応じてタスク定義に設定してください。

### count_s3csv.py
import os
import time
import boto3

BUCKET_NAME = os.environ['BUCKET_NAME']
KEY = os.environ['KEY']
LOCAL_FILE = '/tmp/test.txt'
s3 = boto3.resource('s3')


def one_add():
    s3.Bucket(BUCKET_NAME).download_file(KEY, LOCAL_FILE)
    last_line = extract_last_line(LOCAL_FILE)
    print(last_line)
    with open(LOCAL_FILE, 'a') as f:
        f.write(str(last_line + 1) + "\n")
    s3.meta.client.upload_file(LOCAL_FILE, BUCKET_NAME, KEY)
    time.sleep(30)


def extract_last_line(file):
    with open(file, 'r') as f:
        lines = f.read().splitlines()
        last_line = lines[-1]
        return int(last_line)


if __name__ == '__main__':
    one_add()
### square_s3csv.py
import os
import time
import boto3

BUCKET_NAME = os.environ['BUCKET_NAME']
KEY = os.environ['KEY']
LOCAL_FILE = '/tmp/test.txt'
s3 = boto3.resource('s3')


def square():
    s3.Bucket(BUCKET_NAME).download_file(KEY, LOCAL_FILE)
    last_line = extract_last_line(LOCAL_FILE)
    print(last_line)
    print(last_line**2)


def extract_last_line(file):
    with open(file, 'r') as f:
        lines = f.read().splitlines()
        last_line = lines[-1]
        return int(last_line)


if __name__ == '__main__':
    square()

AWS Batch 実行コマンド

### seq-count-s3csv-job.json
{
  "jobName": "JOB_NAME",
  "jobQueue": "JOB_QUEUE",
  "arrayProperties": {
    "size": 5
  },
  "dependsOn": [
    {
      "type": "SEQUENTIAL"
    }
  ],
  "jobDefinition": "SEQ_JOB_DEFINITION_NAME"
}
### n2n-square-s3csv-job.json
{
  "jobName": "N2N_JOB_NAME",
  "jobQueue": "JOB_QUEUE",
  "arrayProperties": {
    "size": 5
  },
  "dependsOn": [
    {
      "jobId": "SEQUENTIAL_JOB_ID",
      "type": "N_TO_N"
    }
  ],
  "jobDefinition": "N2N_JOB_DEFINITION_NAME"
}
aws batch submit-job --cli-input-json file://seq-count-s3csv-job.json
### JOB_IDを取得してjsonを更新して実行します。
aws batch submit-job --cli-input-json file://n2n-square-s3csv-job.json

その他のパターンは、 jsonファイルの dependsOn の項を編集して実行すれば容易なため、省略します。

最後に

挙動をちゃんと確認できてスッキリしました。

そして、実際にやってみると色々と付随して学べるものですね!(ファイルの読み書きって結構めんどくさい...)

どなたかのお役に立てば幸いです。

参考