AWS Step Functions+Amazon ECS(Fargate)をイベントトリガーで実行するときにハマるポイント

AWS Step FunctionsでAmazon S3のイベントをトリガーにしたときにいろいろとハマったのでメモ。

## ハマる構成

下記を参考にAmazon S3(S3)イベントをトリガーにAWS Step Functions(Step Functions)のステートマシンが実行される構成を作ってみました。

Amazon S3​ イベント発生時にステートマシンの実行を開始する - AWS Step Functions
https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/tutorial-cloudwatch-events-s3.html

構成はこんな感じです。
<img width="539" alt="スクリーンショット 2020-03-06 12.09.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48549/e9530680-4155-730c-61a0-18d1fc456994.png">

ステートマシンからAmazon ECS(Fargate)のタスクを起動してなにかしらの処理を行います。

## ハマるポイント

上記構成でも利用頻度が低ければ問題ありませんが、もし大量に処理を行うのであればいろいろとハマることになります。

### ECS(Fargate)のタスク同時実行数制限

Amazon ECS Endpoints and Quotas - AWS General Reference
https://docs.aws.amazon.com/general/latest/gr/ecs-service.html#limits_ecs

Fargateの場合、同時実行数に制限があります。制限値は`100` です。
引っかかるとステートマシンの状態で`ECS.AmazonECSException (ThrottlingException)` の例外が発生します。

>**Tasks using the Fargate launch type, per Region, per account: 100**
>The maximum number of tasks using the Fargate launch type, per Region.
>
>**Public IP addresses for tasks using the Fargate launch type: 100**
>The maximum number of public IP addresses used by tasks using the Fargate launch type, per Region.

これら制限は上限緩和申請が可能なので必要であれば、申請するのもありです。

### ECS(Fargate)のRunTask APIの同時コール数制限

ECS(Fargate)のRunTask APIの同時コール数制限に引っかかる可能性があります。制限値は`10` です。
これも引っかかるとステートマシンの状態で`ECS.AmazonECSException (ThrottlingException)` の例外が発生します。

<img width="618" alt="スクリーンショット 2020-03-06 12.35.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48549/70163793-103d-610c-61ea-c9ca69189333.png">


Amazon ECS Endpoints and Quotas - AWS General Reference
https://docs.aws.amazon.com/general/latest/gr/ecs-service.html#limits_ecs

>**Tasks launched (count) per run-task: 10**
>The maximum number of tasks that can be launched per RunTask API action.

この制限は上限緩和申請の対象となっていないみたいなので、何かしらの制限回避を考える必要があります。


## 対策を考える

### ステートマシンにエラー処理を追加する

こちらの記事でも同じにようにハマっておられてリトライ設定で回避されていました。記事中には`ECS APIコール数が秒間1回まで` とありますが、おそらくはAPIの同時コール数制限のことだと思われます。

Step Functions & Fargate バッチでやらかしたこと - Qiita
https://qiita.com/a_sh_blue/items/8ccf7502d1512933d226

>調べてみると、どうもECSタスク起動のためのECS APIコール数が秒間1回までという制限にひっかかっていたらしく、
>リトライ設定をしても結構な確率で再衝突しました。結果リトライ上限に達し無事死亡……。

`ErrorEquals` に`ECS.AmazonECSException`、待機時間(`IntervalSeconds` )とリトライの最大回数(`MaxAttempts` )、バックオフのレート(`BackoffRate` )を指定します。
下記の例だと、リトライ回数が3回で待機時間が3秒、6秒、12秒となります。
同時実行数制限に対応するにはECSタスクの処理時間に応じて待機時間やリトライ回数を調整する必要があります。

Step Functions でのエラー処理 - AWS Step Functions
https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/concepts-error-handling.html#error-handling-retrying-after-an-error

```json
"Retry": [ {
   "ErrorEquals": [ "ECS.AmazonECSException" ],
   "IntervalSeconds": 3,
   "MaxAttempts": 3,
   "BackoffRate": 2.0
} ]
```
<img width="521" alt="スクリーンショット 2020-03-06 12.19.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48549/711a96e4-5e94-c4f9-7dc3-9e86d6e7aa0f.png">


これで多少イベント数が増えても耐えることができます。ただし相当数のイベントがあると、リトライ対応だけでは耐えることができません(でした)。同時実行数制限の場合、ECSタスクの実行前に対策をいれたいところです。


### Lambda関数で実装して制御する

Step Functionsのステートマシンに同時実行数を制御するLambda関数を追加してみました。

Lambda関数でイベント発生元のS3バケットとは**別の**S3バケットにファイルを置き、それをカウントして同時実行数を制御します。

**イベント発生元のS3バケットにファイルを置くと無限ループにハマって無事にﾀﾋ亡することができるのでご注意ください。**

同時実行数を超える場合は、`Wait` に遷移して待機します。

ファイル削除するのに、ECSタスクの処理の後続にLambda関数を追加して処理します。

<img width="566" alt="スクリーンショット 2020-03-06 14.58.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48549/2db1e189-4a29-3ae3-0195-26bdcbc7827a.png">


欠点として、ロック機構がないのでLambda関数が同時に実行されるとカウントがうまくさせない点があります。


### Lambda関数の同時実行数を制限する

同時実行数を制限することでカウントの精度を高めてみました。Lambda関数の同時実行数は標準設定だと`1,000` になっているはずなので値を小さくします。

注意点はLambda関数の同時実行数を制限すると、大量のLambda関数呼び出しがあった場合に、`Lambda.TooManyRequestsException` が発生することです。こちらはECSタスクの呼び出しでも利用したリトライ設定で回避します。

```json
"Retry": [
  {
    "ErrorEquals": ["Lambda.TooManyRequestsException"],
    "IntervalSeconds": 3,
    "MaxAttempts": 3,
    "BackoffRate": 2.0
  }
],
```

<img width="558" alt="スクリーンショット 2020-03-06 15.01.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48549/3fe271a3-1722-a4ea-cf4c-d38c08548d4c.png">

これでもLambda関数の同時実行数分は同時にカウントする可能性があるので、ランダムな秒数待ち受けるなどの小細工をするともう少し制御の精度が上がります。


## Step Functionsの上限にひっかかる可能性もある

Step Functionsにも制限があるため、それを超えるようなイベント数になる可能性があるならば、Amazon SQSなどを間に挟むなどの対策が必要になると思います。

標準ワークフローのクォータ - AWS Step Functions
https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/limits.html

>**API アクションのスロットリングに関連するクォータ**
> 一部の Step Functions API アクションは、サービスの帯域幅を維持するため、トークンのバケットスキームを使用してスロットリングされます。

<img width="708" alt="スクリーンショット 2020-03-06 15.08.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48549/1e2d5837-0c5a-cc28-b2f1-d8c84efc940d.png">

[新機能] CloudWatch EventsがAmazon SQSのQueueをターゲット指定できるようになりました ｜ Developers.IO
https://dev.classmethod.jp/cloud/aws/cloudwatch-events-support-sqs/

Amazon SQSからLambdaを実行できるようになったので試して見た。 - Qiita
https://qiita.com/keni_w/items/dc651c9fc794f5a8ad64

## おわりに

いろいろと検討したものの、まだバシッとハマる(ポジティブ)構成が思い浮かびません。
よいプラクティスがあれば教えてください！

## 参考

Amazon S3​ イベント発生時にステートマシンの実行を開始する - AWS Step Functions
https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/tutorial-cloudwatch-events-s3.html

Amazon ECS Endpoints and Quotas - AWS General Reference
https://docs.aws.amazon.com/general/latest/gr/ecs-service.html#limits_ecs

Step Functions & Fargate バッチでやらかしたこと - Qiita
https://qiita.com/a_sh_blue/items/8ccf7502d1512933d226

Step Functions でのエラー処理 - AWS Step Functions
https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/concepts-error-handling.html#error-handling-retrying-after-an-error

標準ワークフローのクォータ - AWS Step Functions
https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/limits.html

[新機能] CloudWatch EventsがAmazon SQSのQueueをターゲット指定できるようになりました ｜ Developers.IO
https://dev.classmethod.jp/cloud/aws/cloudwatch-events-support-sqs/

Amazon SQSからLambdaを実行できるようになったので試して見た。 - Qiita
https://qiita.com/keni_w/items/dc651c9fc794f5a8ad64