AWS Lambdaの15分制約について

:::note
[Supershipグループ Advent Calendar 2022](https://qiita.com/advent-calendar/2022/supership)の17日目の記事になります。
:::

## はじめに

2022年も終わりが近づき、2022年にやったことを振返り反省も踏まえ今回の記事を書かせていただきます。
タイトルにあります通り、AWSのLambdaを利用し定期的に動くバッチ処理を作成したのですが
その際、Lambdaの15分制限に達し実行環境を変えた話をしようと思います。

## Lambda期
### 開発
バッチ処理は至ってシンプルなものでして、APIをからデータを取得してCSVファイルに出力しS3に格納するというものでした。
データ件数もそこまで多くはなく、既存のバッチが動いているサーバも無いためLambdaを選択いたしました。
言語はLambdaと非常に相性がいいPythonを選択いたしました。Pythonを選択した理由としましては[chalice](https://github.com/aws/chalice)が凄く魅力的でこれを使わない手はないと思ったからです。
chaliceを少し説明いたしますと、AWS Lambdaを構築する際のフレームワークでコマンドラインを使って環境の作成、更新、削除やAppのデプロイなどが可能です。

公式のReadmeから拝借しますと

>- アプリを作成、デプロイ、管理するためのコマンドラインを提供
>- Amazon API Gateway、Amazon S3、Amazon SNS、Amazon SQS、およびその他の AWS サービスと統合するためのデコレータベースのAPI
>- IAMポリシーの自動生成

IAMのポリシーも自動で作成してくれるなんて！！
サクッとAppを作ってLambdaで動かしたいときにはこれ以上の選択肢はないのかと思います。
しかも、AWSが提供してくれているのでメンテナンスも期待できそうでした。

もう少し、Chaliceの魅力をお話させていただくと、

```python
from chalice import Chalice, Rate

app = Chalice(app_name="helloworld")

# Automatically runs every 5 minutes
@app.schedule(Rate(5, unit=Rate.MINUTES))
def periodic_task(event):
    return {"hello": "world"}
```
（公式[README](https://github.com/aws/chalice/blob/master/README.rst)から）

```python
@app.schedule(Rate(5, unit=Rate.MINUTES))
```
たったこの一行を書いとけさえすれば、AWS上にスケジュールを設定してLambdaを構築しCronスケジュールまで構築してくれるなんて、、便利すぎる！
他にも
```python
@app.route("/")
```
の一行を書いておくだけで**API Gatway**も作成してくれて**REST API**なんかもすぐ作れちゃいます。便利すぎる。
他にもS3のイベントをトリガーにするものや、SQSをトリガーに設定することも一行で設定出来るのでLambdaでやりたい大抵用意されています。

しかし、ローカルで開発し本稼働させていたところ想定していた以上に実行時間が長かったのです。
本環境構築時にある程度データが有る想定でメモリ増やすなどしていたのですが、実行時間がここまで伸びるとは想定していませんでした。
(公式アナウンス [AWS Lambda で Lambda 関数の最大メモリ容量を倍増](https://aws.amazon.com/jp/about-aws/whats-new/2017/11/aws-lambda-doubles-maximum-memory-capacity-for-lambda-functions/))

また、Lambdaには実行時間が15分という制約があるので上限に達する前に対策が必要でした。
（公式Blogより [AWS Lambda のタイムアウトが15分になりました。](https://aws.amazon.com/jp/blogs/news/aws-lambda-timeout-15min/)）

## 並列処理期

上記の問題があったので、まずはコードの見直しを行いました。一番時間がかかっていたのがAPIからデータを取得するところだったので
この処理が並列化出来そうだったので修正しました。

余談ですが、並列化は `ThreadPoolExecutor`を使用しました。
```python
with ThreadPoolExecutor(max_workers=MAX_WORKER_CNT) as executor:
    for datum in data:
        future = executor.submit(fetch_data, args)
        futures.append(future)
```

コードの改修後状況は改善され、3分の1まで短縮することができました。
しかし、今後もデータが増える予定でデータ参照範囲も広がる予定だったのでLambdaを辞め他の実行環境に移す必要がありました。

## Elastic Beanstalk期

他の実行環境を検討し２つのサービスに絞り込みました。
- AWS Batch
- Elastic Beanstalk

結論から言いますと`Elastic Beanstalk`を選択いたしました。
理由といたしましては、
AWS Batchはそのサービス名通り、バッチ処理をフルマネージドしてくれるサービスで良さそうに感じましたが、
仕組みとしてJobキューを詰めAWS ECS,EKS,Fargateのいずれかの環境に対して実行環境を立ち上げ実行の後実行環境を停止する仕組みとなっており、Dockerイメージを用意したりそれにあたりECRにデプロイする必要があるなど少しやりたいことに対して大げさな構成になってしまうと思ったからです。
（公式サイト [AWS Batch の特徴](https://aws.amazon.com/jp/batch/features/?nc=sn&loc=2)）

Elastic BeanstalkはAWS版のHerokuみたいなやつなのですが、構成的にもEC2を立ち上げデプロイもS3にアプリケーションをアップロードするだけのものなので非常にとっつきやすかったです。また、他のシステムでも採用しており知見もありました。
Elastic BeanstalkにはWebアプリケーション環境とワーカー環境の2つを選べ、ワーカー環境が今回のやりたいことにマッチしていたのでこちらを選択し、今はこちらの環境で動作しております。
（公式サイト [Elastic Beanstalk ワーカー環境](https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/using-features-managing-env-tiers.html)）

## まとめ

いかがでしたでしょうか。手軽にLambda環境で試したのですがLambdaの制約に引っかかり、最終的にはEBで動かす様になった話でした。この記事が誰かの役に立てば幸いです！！

## 最後に
:::note
Supershipではプロダクト開発やサービス開発に関わる人を絶賛募集しております。
ご興味がある方は以下リンクよりご確認ください。

[Supership 採用サイト](https://supership.jp/recruit/)

是非ともよろしくお願いします。
:::