AWS Lambdaにおける並列データ処理におけるパフォーマンス対応のメモ

Lambdaで並列処理のパフォーマンス対応をしたので残しておきます。

目的

ここで書く内容は色々とベストプラクティスでは無いです。むしろ考え方によってはアンチパターンも含んでいます。ただ考え方や詰まりポイントが誰かの何かのヒントになれば幸いです。

要件

  • S3上に定期的に最新データがファイル保存される
  • データファイルは多次元構造の特殊ファイルで読み込みにはライブラリが必要
  • データファイルはライブラリ制約でインメモリで処理できない
  • REST API経由でデータをクエリして取得したい
  • 一回のリクエストで解析が必要なファイルは 最大60MB × 10ファイル程度
  • 分析時のみアクセスするため、常時起動は不要
  • サーバーレスで実装したい(できればECS/EKSではなくAPI GatewayとLambdaでやりたい)

特に最後のところから、検証しました。

アプローチ① 単一Lambdaシーケンシャル

最初はAPIへのリクエストに伴い単一Lambdaで処理する方式です。

Lamdbaの /tmp容量制限(512 MB)があり並列ダウンロードしたファイルを保持できない場合があるのでシーケンシャルに処理を書いてみました。

f:id:yomon8:20220203171004p:plain:w500

これはすぐに却下でした。小さい処理リクエストは処理できますが、大きい処理リクエストはAPI Gatewayの30秒制限にて504エラーになってしまいます。

この時点で制限となっているのは、Lamdbaの /tmp容量制限(512 MB)でした。

アプローチ② 単一Lambda 並列ダウンロード・並列処理・EFS利用

LambdaにEFSをアタッチすることで、Lambdaのディスク制限が無くなり、ファイルを並列ダウンロードすることができるようになりました。

ダウンロードはマルチスレッド、解析処理はマルチプロセスで処理することで大幅にレスポンス速度が向上しました。

f:id:yomon8:20220203171027p:plain:w500

後からの要件追加によりファイルから読み込む次元が増えたことで解析処理の時間が数倍に増えてしまい、またAPI Gatewayの30秒制限に504てエラーになってしまうようになりました。

この時点でボトルネックとなっているのはLamdaの処理速度です。 CPUボトルネック となってます。

参考

yomon.hatenablog.com

qiita.com

アプローチ➂ 並列Lambda 並列ダウンロード・並列処理・EFS利用

LambdaからLambdaを並列実行するアプローチを取ってみました。この方法でまた大幅にパフォーマンス向上になりました。

f:id:yomon8:20220203171057p:plain:w500

ただ、ユーザ側から並列で大量のAPIリクエストが来ると、また504エラーとなりました。X-ray等を使ってトレース見てみるとEFSのI/Oで詰まっていることがわかりました。試しにProvisioned IOPSにするとかなり速度が改善しましたが、Provisioned IOPSは高額なので一旦却下。次の方法考えます。

参考

Lamdaから解析結果を取得する時にはPickle形式を介しています。

qiita.com

アプローチ④ 並列Lambda 並列ダウンロード・並列処理

よく考えればLambdaを並列実行するようになったので、一つのLambdaで処理するファイルは1ファイルになっています。つまり①で制限だった /tmp の容量の制限にはひっから無くなっています。

EFSを削除し、個別のLambdaの /tmp を使うことで、APIの並列実行時も安定して高速に処理できるようになりました。

f:id:yomon8:20220203171124p:plain:w500

アプローチ⑤ 並列Lambda 並列ダウンロード(インメモリ)・並列処理

/tmp 使うことで問題起きなくなったと思ったら大量リクエスト時にいきなりエラーが。わかる人もいるかもしれませんが、 /tmp は Lambdaのインスタンスが再利用されるときにファイルが残ってしまうことがあります。当然、削除処理を入れて使っているのですが、それでも何らかのタイミングでLambdaが失敗したりすると残ってしまったりするので、大量リクエストで問題が顕在化しディスクフルを起こしていました。

記載時点以降にアップデートがあり、 /tmp を広げられるようになりました。

dev.classmethod.jp

結局、「データファイルはライブラリ制約でインメモリで処理できない」の要件を抜けるしかないとなり、ライブラリでファイルシステム必須だった部分はインメモリで処理できるように自身で実装しました。

勉強にはなりましたが、色々と遠回りしてしまいました。。。

さいごに

最初にも書いた通り、ベストプラクティスでも何でもないですが、引っかかりポイント等が誰かの参考になればと思って残しておきました。