Lambdaのメモリ割り当てを自動で最適化!!AWS Lambda Power Tuning

143件のシェア(ちょっぴり話題の記事)

CX事業本部@大阪の岩田です。re:invent2019のセッションで知ったAWS Lambda Power Tuningを試してみたので簡単にご紹介します。

AWS Lambda Power Tuningとは?

Lambdaのメモリ割り当てを自動で最適化するためのツールです。Step Functionsを利用して対象のLambdaを様々なメモリ設定で実行し、どの設定値が一番パフォーマンスが良いのか?どの設定値が一番コスト効率が良いのか?といった情報をレポートしてくれます。

なにが嬉しいの?

Lambdaのチューニング手法として割り当てるメモリサイズを増やすという手法が広く知られています。メモリサイズを増やすことで、Lambda実行環境のCPUパワーや、利用可能なNW帯域が向上します。また、メモリサイズを増やすことで処理時間が短くなり、結果的にコストが下がるという効果も期待できます。しかしながら、全てのLambdaについて様々な設定での計測を行い、最適なメモリ設定を算出するというのは骨の折れる作業です。AWS Lambda Power Tuningを利用することで、こういった面倒な作業を自動化することができます。

AWS Lambda Power Tuningの使い方

まずはStep Functionsのステートマシンと関連するリソースをデプロイします。SARで公開されているので、以下のリンクからサクッとデプロイしてしまいましょう。

aws-lambda-power-tuning

デプロイできたらステートマシンに以下のような入力値を渡して実行し、Lambdaのチューニングを行います。

{
    "lambdaARN": "...",
    "num": 10,
    "payload": {},
    "powerValues": [128, 256, 512, ...],
    "autoOptimize": true,
    "autoOptimizeAlias": "prod"
}

各パラメータの意味は以下の通りです。

lambdaARN

チューニング対象とするLambdaのARN。必須項目

powerValues

チューニング対象のLambdaに割り当てるメモリの設定値をリストで指定。文字列で"ALL"と指定することも可能で、"ALL"を指定した場合は128M~3008Mまでの範囲で64MBずつ増加させながら実行&計測を行います。任意項目でデフォルトは128MB, 256MB, 512MB, 1024MB, 1536MB, 3008MBの6パターンを利用します

num

各設定で何回づつLambdaの実行と所要時間の計測を行うか。最低値は5、推奨値は10~100。必須項目

payload

チューニング対象のLambdaに渡すペイロード。任意項目

parallelInvocation

trueに設定すると、各Lambdaの呼び出しを並列実行します。numの値次第ではスロットリングエラーが発生するので、設定する際は注意が必要です。任意項目でデフォルトはfalse

strategy

最適な設定値を判断するためのロジックを"cost","speed","balanced"の3種から指定します。"cost"を指定した場合はコストが最低になる設定値を最適と判定し、"speed"を指定した場合は処理速度が最速になる設定値を最適と判定します。"balanced"を指定した場合はbalancedWeightの値に従ってコストと処理速度のバランスの取れた設定値を最適と判定します。デフォルト値は"cost"です

balancedWeight

strategyに"balanced"を指定した場合の、コストと処理速度のトレードオフを調整するパラメータです。0.0~1.0の間で指定します。0.0を指定した場合はstrategyに"cost"を指定した場合と同様の挙動となり、1.0を指定した場合はstrategyに"speed"を指定した場合と同様の挙動となります。デフォルトは0.5

autoOptimize

trueに設定するとステートマシンの実行完了後に実行結果に基づく最適なメモリ設定で対象Lambdaの設定を更新します。任意項目でデフォルトはfalse

autoOptimizeAlias

autoOptimizeがtrueかつ、このパラメータが指定されている場合に指定されたAliasを設定値を最適化した設定で更新します。CI/CDパイプラインとAWS Lambda Power Tuningを組み合わせてチューニングを自動化するのに最適なパラメータですね

ステートマシンの実行が完了すると、以下のように実行結果が出力されます。

{
  "power": 378,
  "cost": 0.000002457,
  "duration": 399.5566666666667,
  "stateMachine": {
    "executionCost": 0.00028,
    "lambdaCost": 0.00007112950000000001,
    "visualization": "https://lambda-power-tuning.show/#gAAAAXoBAAIABA==;6CSTRJP/GERBx8dD6PSKQ+g0HkM=;9IAnNsdrQzbw4iQ29IAnNptWXzY="
  }
}

各パラメータの意味は以下の通りです。

power

最適なメモリ設定値

cost

最適な設定値での呼び出し毎の平均コスト

duration

最適な設定値での平均処理時間

stateMachine.executionCost

ステートマシンの実行によって発生するStep Functionsのコスト

stateMachine.lambdaCost

このステートマシンの実行によって発生するLambdaのコスト(numとLambdaの平均実行時間に依存して変動)

stateMachine.visualization

このURLにアクセスすることで、以下の画像のようなグラフィカルなUIからステートマシンの実行結果を確認することができます

AWS Lambda Power Tuningの実行結果

可視化にはAWS Lambda Power Tuning UIというツールが利用されており、必要に応じてAWS Lambda Power Tuning UIを自前でホスティングすることも可能です。

やってみる

実際にいくつかのパターンでAWS Lambda Power Tuningを試してみます。LambdaのランタイムにはPython3.7を利用しています。

numpyによる行列計算

以下のコードを実行するLambdaに対してAWS Lambda Power Tuningを試してみます。なおnumpyを利用するためにAWSから提供されているarn:aws:lambda:ap-northeast-1:249908578461:layer:AWSLambda-Python37-SciPy1x:2のレイヤーを使用しています。

import numpy 

def handler(event, context):
    
    numpy.dot(
        numpy.random.rand(1000, 1000).astype(numpy.float32),
        numpy.random.rand(1000, 1000).astype(numpy.float32)
    )

CPUバウンドな処理になるので、メモリを1792MBまで上げれば高速化が期待できそうです。ステートマシンに以下の入力を渡して実行してみます。

{
  "lambdaARN": "<対象LambdaのARN>",
  "num": 5,
  "payload": {},
  "parallelInvocation": false,
  "powerValues": "ALL"
}

実行結果です。

CPUバウンドな処理に対するAWS Lambda Power Tuningの実行結果

一応メモリ3008MBが最速という結果になりましたが、1vCPU分のCPUパワーを使える1792MBを超えたあたりをからは処理時間の改善効果が非常に小さくなっていることが分かります。

外部APIをコールする処理

今度は以下のコードを実行するLambdaに対してAWS Lambda Power Tuningを試してみます。

import urllib.request


def handler(event, context):

    req = urllib.request.Request('https://petstore.swagger.io/v2/pet/findByStatus?status=pending')
    
    urllib.request.urlopen(req)
    urllib.request.urlopen(req)
    urllib.request.urlopen(req)

https://petstore.swagger.io/で提供されているペット検索処理のAPIを3回実行する処理です。処理時間の多くが外部APIに依存するため、メモリを増やしても対して高速化は期待できないはずです。先ほどと同様にステートマシンに以下の入力を渡して実行してみます。

{
  "lambdaARN": "<対象LambdaのARN>",
  "num": 5,
  "payload": {},
  "parallelInvocation": false,
  "powerValues": "ALL"
}

実行結果です。

外部APIをコールする処理に対するAWS Lambda Power Tuningの実行結果

メモリを増やすことによる改善効果が小さいということが視覚的にも良く分かります。

まとめ

Lambdaのメモリ割り当て調節によるパフォーマンスチューニングは知識としては知りつつも、実際に様々なパターンで計測するのが面倒で、とりあえず128Mに設定してみたり、とりあえず1Gに設定してみたり、、、といったパターンも多いかと思います。AWS Lambda Power Tuningを使うことで、このあたりの面倒な作業を自動化しつつ、計測結果に基づいた根拠のあるチューニングが実施できるようになります。是非一度試してみてはいかがでしょうか?