Programming Self-Study Notebook

勉強したことを忘れないように! 思い出せるように!!

AWS Lambdaを少しでも速くする

f:id:overworker:20210304234231p:plain:h200f:id:overworker:20210312003225p:plain:h200

Lambdaのコールドスタート対策の調査をする中で、「AWS Summit Tokyo 2017全部教えます!サーバレスアプリのアンチパターンとチューニング」というセッションに到達しました。
上記スライド以外にもYouTubeの動画(約40分)があるようですのでご興味がある方は探してみてください。

遅くなる理由

  • プログラムの問題
  • コンピューティングリソースの不足
  • コールドスタート
  • アーキテクチャの問題
  • 同時実行数

プログラムの問題

  • Lambdaファンクションとして実装されているプログラムのロジックそのものの問題
    • 実際のところ、プラットフォーム側でできることはない
  • 各言語のベストプラクティス、最適化手法はそのまま当てはまる

遅く処理されるように書かれたコードは遅く処理されるので各言語で早く処理されるように記述してください』ということ。 (当然ですね (-_-;))

コンピューティングリソースの不足

メモリ設定
• 設定値としてはメモリとなっているが実際はコンピューティングリソース全体の設定
• メモリサイズと比例してCPU能力も割り当てられる
• メモリ設定はパフォーマンス設定と同義
• コストを気にしがちだが、メモリを増やすことで処理時間がガクンと減り、結果的にコストはそれほど変わらずとも性能があがることもある
少しずつ調整し、変更しても性能が変わらない値が最適値

以下の内容を断言してくれているので、安心してパラメータ設計できます。
- メモリサイズと比例してCPU能力も割り当てられる
- 少しずつ調整し、変更しても性能が変わらない値が最適値

コールドスタート

Lambdaファンクション実行時に起きていること

順番 内容 備考
1 (ENIの作成) VPCからアクセスする場合だけ
10秒~30秒かかる
CloudWatchメトリクスのDurationには含まれない
2 コンテナの作成 CloudWatchメトリクスのDurationには含まれない
3 デプロイパッケージのロード CloudWatchメトリクスのDurationには含まれない
4 デプロイパッケージの展開 CloudWatchメトリクスのDurationには含まれない
5 ランタイム起動・初期化 各ランタイムの初期化処理
グローバルスコープの処理
 - ハンドラー
 - コンストラクタ(java)
タイムアウトがある
CloudWatchメトリクスのDurationには含まれない
6 関数/メソッドの実行 ハンドラーで指定した関数/メソッドの実行
Duration値として計測している値
  • Lambdaは裏側ではコンテナが使用されている。
  • CloudWatchメトリクスのDurationには含まれない=支払い対象にならない
種類 特徴
コールドスタート 上記、①~⑥をすべて実行すること
ウォームスタート 上記、①~⑤を再利用することで省略して効率化

※ ①~⑤は基本的に毎回同じ内容が実行される。

コールドスタートが起こる条件

簡単に言うと、利用可能なコンテナがない場合に発生
• そもそも1つもコンテナがない状態
• 利用可能な数以上に同時に処理すべきリクエストが来た
• コード、設定を変更した
 • コード、設定を変更するとそれまでのコンテナは利用できない

安定的にリクエストが来ているならば、コールドスタートはほとんど発生しない

コールドスタートを速くする

コンピューティングリソースを増やす
• コンピューティングリソースの割当を増やすことで初期化処理自体も速くなる
  ランタイムを変える
• 例えばAWS Lambdaに限らず、JVMの起動は遅い
• ただし、一度温まるとコンパイル言語のほうが速い傾向

  • Javaコンパイル言語)はコールドスタートでは遅いがウォームスタートでは速い。

パッケージサイズを小さくする
• サイズが大きくなるとコールドスタート時のコードのロードおよびZipの展開に時間がかかる
• 不要なコードは減らす
• 依存関係を減らす
 • 不要なモジュールは含めない
 • 特にJavaは肥大しがち
JavaだとProGuardなどのコード最適化ツールを使って減らすという手もある
 • 他の言語でも同様のものはある  

VPCは必要でない限り使用しない
• 使うのはVPC内のリソースにどうしてもアクセスする必要があるときだけ
VPCアクセスを有効にしているとコールドスタート時に10秒から30秒程度余計に必要になる
 

同期実行が必要な箇所やコールドスタートを許容できない箇所ではなるだけ使わない
VPC内のリソースとの通信が必要なのであれば非同期にする
RDBMSのデータ同期が必要なのであればDynamoDB StreamsとAWSLambdaを使って非同期に
 

Javaの場合は以下も検討
POJOではなくバイトストリームを使う
• 内部で利用するJSONリアライゼーションライブラリは多少時間がかかるので、バイトストリームにしてより軽量なJSONライブラリを使ったり最適化することも可能
https://github.com/FasterXML/jackson-jr
http://docs.aws.amazon.com/lambda/latest/dg/java-handler-io-typestream.html
• 匿名クラスをリプレースするようなJava8の機能を利用しない
(lambda、メソッド参照、コンストラクタ参照など)
  初期化処理をハンドラの外に書くとコールドスタートが遅くなるので遅延ロードを行う

import boto3
client = None
def my_handler(event, context):
  global client
  if not client:
    client = boto3.client("s3")
  # process

アーキテクチャの問題

同期でInvokeすると同時実行数の制限に引っかかってつまりがち
• 非同期呼び出しの場合、許可された同時実行数内で順次処理をし、バーストも許容されている
• 同期呼び出しの場合、許可された同時実行数を超えた時点でエラーが返されてしまう
• 非同期の場合はリトライされる
  できるだけ非同期でInvokeするのがスケーラビリティの観点ではオススメ
• その処理、本当にレスポンス必要ですか?
• 特にAmazon API Gatewayとの組み合わせの場合、PUT系の処理をAWSLambdaで直接処理するのではなく、サービスプロキシとして構成してAmazon SQS、Amazon Kinesisに流すなどする

Think Parallel
AWS Lambdaの最大限活かすにはいかに並列処理を行うか

1つあたりのイベントを小さくして同時に並列で動かせるようなアーキテクチャにする
• 1回のInvokeでループさせるのではなく、ループ回数分Lambdaファンクションを非同期Invokeする
• 長時間実行のLambdaファンクションが減るので、同時実行数の制限にも引っかかりにくくなる

同時実行数

  • DynamoDB、kinesisの場合はシャードで計算する。

Limit Increaseについて

標準では1000、実績がない状態でいきなり数千とか数万を申請しても通らない
• 実際にThrottleされているかどうかの確認を
• Throttleされているかなどはメトリクスで確認可能
• サービスローンチなどの場合は、余裕をもって担当セールス、SAにご相談を

Limit Increaseしても性能上のボトルネックが解消しないこともある
• ストリーム系の場合、シャード数を増やしたりバッチサイズを調整したりしないと変わらない
• そもそもファンクションの実行に時間がかかっている場合、レイテンシは改善しない

アンチパターン

AWS LambdaでRDBMS使いがち問題

AWS Lambda + RDBMSアンチパターンな理由
• コネクション数の問題
AWS Lambdaはステートレスなプラットフォームであるため、コネクションプールの実装は難しい
AWS Lambdaがスケールする、つまりファンクションのコンテナが大量に生成された場合に、各コンテナからDBへコネクションが張られることになり、耐えられないケースがある
VPCコールドスタートの問題
VPCのコールドスタートが発生する場合、通常のコールドスタートに比べて10秒程度の時間を必要とする

ベストプラクティス
Amazon DynamoDBを使う
• 何らかの理由でRDBMSとの連携が必要な場合はDynamoDB StreamsとAWS Lambdaを利用して非同期にする

IP固定したがり問題

Amazon API GatewayAWS Lambdaから別システムや外部APIにアクセスする際のソースIPアドレスを固定したい署名や証明書などで担保すべき
IPアドレスを固定するということはスケーラビリティを捨てることにもつながる
AWS LambdaではVPCを利用してNATインスタンスを使うという方法もなくはないが…
VPCのコールドスタート問題
• 自前のNATインスタンスの場合、その可用性、信頼性、スケーラビリティを考慮する必要がある

サーバレスに夢見がち問題

サーバレスであれば全く運用が必要ない、インフラ費用が10分の1になる
• サーバの管理は不要だが運用は必要
• コスト効率が高いため、サーバを並べて同様のことを実装するよりは安くなる可能性が高いが、リクエスト数が多い場合などはそれなりの費用になる
• インフラ費用だけでなく、トータルコストで考える必要がある
• 複雑なことをやろうとすると、設計・開発コストがあがる可能性も大きい
シンプルに使うべきものはシンプルに使いましょう

監視しなくていいと思ってる問題

Serverless != Monitorless
処理の異常を検知して対応するのはユーザの仕事
• 適切にログ出力を行い、適切にモニタする
ベストプラクティス
• CloudWatchのメトリクスを利用(Errors, Throttles)
• CloudWatchのカスタムメトリクス
• CloudWatch Logsへのログ出力とアラーム設定

その他

通常の他のサービスと同様に障害発生を前提として実装をする
• リトライ
• Dead Letter Queueの活用(非同期の場合)
冪等性はお客様のコードで確保する必要がある
AWS Lambdaで保証しているのは最低1回実行することであり1回しか実行しないことではない
• 同一イベントで同一Lambdaファンクションが2回起動されることがまれに発生する
Amazon DynamoDBを利用するなどして冪等性を担保する実装を行うこと

  • 最低1回実行されることは保証されている。
  • 1回しか実行されないことは保証されていない

参考文献

全部教えます!サーバレスアプリのアンチパターンとチューニング