【AWS Lambda】AWS Lambda Power Tuningでコストを1円でも安く

記事タイトルとURLをコピーする

こんにちは、ディベロップメントサービス1課の山本です。

もう12月で、1年過ぎるのがとても早いですね。

今回は、少し早いクリスマスプレゼントとして、
AWS Lambda(以下、Lambda)を1円でも安くする方法を説明します。

AWS Lambda Power Tuningというツールを使って、Lambdaの割り当てメモリ量の最適化を行います。

この記事の対象者は?

この記事は以下の方々に向けて書かれています:

  • Lambdaのコストを1円でも下げたい方々
  • Lambdaの割り当てメモリを何となく決めている方々
  • Lambdaの基本的なコストの計算方法を知りたい方々

Lambdaのコスト

まず、おさらいです。

Lambdaのコストは下記の表に従って決まります。 ※東京リージョン x86のみ抜粋

料金 - AWS Lambda |AWS

アーキテクチャ 期間 リクエスト
x86
最初の60億GB秒/月 GB-秒あたり 0.0000166667USD リクエスト 100 万件あたり 0.20USD
次の 90億GB秒/月 GB-秒あたり 0.000015USD リクエスト 100 万件あたり 0.20USD
150億GB秒/月 以上 GB-秒あたり 0.0000133334USD リクエスト 100 万件あたり 0.20USD

リクエストは、0.20USD/100万件ということもあり、コストに大きな影響は与えません。

大事なのは、期間の方です。

GB-秒あたりとは、Lambdaが、メモリ1GBで1秒間動作する単位のことを言います。

そのため、期間の価格は下記の計算式で出ます。

期間の価格 = 割り当てメモリ量(GB) × 月稼働時間(秒) × GB-秒あたりの単価

結果、Lambdaのコストが増加する要因としては、下記の二つになります。

  • 割り当てメモリ量(GB)が大きくなる
  • 稼働時間(秒)が長くなる

なら、コストを下げるには、割り当てメモリ量を限界まで下げればいいんだ!

それは、違います。

LambdaのメモリとCPUの関係

Lambdaでは、割り当てメモリ量が増えると、それに比例して、利用できるCPUリソースも増加する特徴があります。

先ほどの価格ページから引用します。

AWS Lambda のリソースモデルでは、お客様が関数に必要なメモリ量を指定すると、それに比例した CPU パワーとその他のリソースが割り当てられます。メモリサイズが増えると、関数で利用可能な CPU にも同等の増加が発生します。

簡単にいうと、メモリ量を半分に減らすと、処理性能も半分に減ります。

なので、メモリ量を限界まで下げると、今度は稼働時間が増加して、コストが増加する可能性があります。

逆に、メモリ量を上げ過ぎても、処理速度には限界があるので、コストは増加します。

割り当てメモリ量(GB) × 月稼働時間(秒)最小
となる割り当てメモリ量を見つけることがコスト最適化の鍵です。

AWS Lambda Power Tuning

概要

割り当てメモリ量(GB) × 月稼働時間(秒)が最小となるメモリ量を探してくれるツールがAWS Lambda Power Tuningとなります。

docs.aws.amazon.com

仕組みは簡単です。

作成したLambda関数に対して、複数パターンでメモリ量を割り当てて、その稼働時間を計測するツールです。

その後、計測した時間を可視化して、一番低いコストとなるパラメータを見つけてくれます。

以下、可視化される際のサンプルです。

可視化サンプル

構成図

AWS Lambda Power Tuningの構成図は下記の通りです。

構成図

デプロイ方法

まず、AWS Lambda Power Tuningを自身のAWSアカウントにデプロイします。

デプロイ方法は、下記Githubに格納されてます。

aws-lambda-power-tuning/README-DEPLOY.md at master · alexcasalboni/aws-lambda-power-tuning · GitHub

いくつか手段はあるのですが、今回は一番簡単なAWS Serverless Application Repository (SAR)を利用する方法でデプロイします。

Option 1: AWS Serverless Application Repositoryの Serverless Application Repositoryをクリックして、SARへと遷移します。

Git_SAR遷移

SAR画面

Deployをクリックすると、ログインしているAWSコンソールのLambdaのアプリケーション画面へと遷移します。

Lambda:アプリケーション画面

アプリケーションの設定項目から、いくつかのツールの設定が可能です。

特にこだわりがなければ、全てデフォルト値で利用可能です。

項目 デフォルト値 説明
アプリケーション名 aws-lambda-power-tuning スタック名
PowerValues 128,256,512,1024,1536,3008 テストする割り当てメモリ量
lambdaResource * 呼び出しを許可するLambdaリソース。ARN指定やプレフィックス指定が可能。
layerSdkName ツールで利用するSDKレイヤー。
logGroupRetentionInDays 7 ログ保持日数
payloadS3Bucket 大きいペイロードを利用する際の、取得先バケット。
payloadS3Key * 上記バケットのオブジェクトキー。
permissionsBoundary
securityGroupIds ツールに割り当てるセキュリティグループID(VPC内のLambdaテスト時のみ必要)
subnetIds ツールに割り当てるセキュリティグループID(VPC内のLambdaテスト時のみ必要)
totalExecutionTimeout 300 テスト実行時のタイムアウト時間
visualizationURL https://lambda-power-tuning.show/ 可視化する際のURL

設定項目の変更を終えたら、デプロイをクリックします。

CloudFormationのスタックが生成され、各種LambdaとStep Functionsのリソースが構築されます。

生成されるCloudFormationスタック

テスト実行方法

Step Functionsのステートマシンから、powerTuningStateMachineを選択します。

Step Functions:ステートマシン画面

実行を開始をクリックします。

powerTuningStateMachine画面

設定値入力画面に遷移するので、設定値を入力後に、実行を開始を選択します。

設定項目入力画面

パラメータ 説明
lambdaARN テスト対象のLambdaARN
powerValues テストするメモリ量
num 並列テスト数
payload eventデータの内容

他にも入力できる項目はありますので、詳細知りたい方は、READMEを参照ください。

aws-lambda-power-tuning/README-INPUT-OUTPUT.md at master · alexcasalboni/aws-lambda-power-tuning · GitHub

結果確認

実行ステータスが成功に変わると、テスト完了です。

実行の入力と出力タブに切り替えると、出力に以下の内容が記載されます。

{
  "power": 1024,
  "cost": 5.376e-7,
  "duration": 31.166666666666668,
  "stateMachine": {
    "executionCost": 0.0003,
    "lambdaCost": 0.000039679500000000005,
    "visualization": "https://lambda-power-tuning.show/*******"
  }
}
パラメータ 説明
power 最適なメモリ量
cost Lambda1コールの費用
duration 稼働時間
stateMachine.executionCost テストにかかった費用
stateMachine.lambdaCost テストにかかったLambdaの費用
stateMachine.visualization 可視化用のURL

可視化用のURLに遷移すると、メモリ量 vs 稼働時間、 メモリ量 vs コストがグラフ化されてます。

可視化サンプル

この内容を見ることで、最適なコストとなるメモリ量が1024MBとなることが分かります。

検証

せっかくなので、特性の異なる2つのLambdaを作成して、それぞれ最適となるメモリ量を検証してみます。

  • サンプル1(計算負荷が高い)
  • サンプル2(通信負荷が高い)

サンプル1(計算負荷が高い)

まず、サンプルとして計算負荷が高いLambdaを作成します。

とりあえず、X番目のフィボナッチ行列を導出させます。

def fibonacci(n: int):
    if n <= 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fibonacci(n-1) + fibonacci(n-2)
  
  
def lambda_handler(event, context):
    input_number = event.get('input_number', 30)
    fib_result = fibonacci(input_number)
  
    return fib_result

直感だと、CPUの価値が高まるので、ある程度高いメモリでコストが最適化されるイメージです。

結果は以下の通りです。

想像通り、パラメータ(計算数)が増えるにつれて、最適メモリ量も増加しました。

パラメータ 最適メモリ量(MB)
10 128
20 256
30 1024

サンプル1(計算負荷が高い):パラメータ10

サンプル1(計算負荷が高い):パラメータ20

サンプル1(計算負荷が高い):パラメータ30

サンプル2(通信負荷が高い)

次に、サンプルとして通信負荷が高いLambdaを作成します。

DynamoDBにX回Get Itemを行い、通信回数を多くして、負荷を上げます。

import boto3
  
  
def get_item_dynamodb(table_name, key):
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table(table_name)
    response = table.get_item(Key=key)
    return response['Item']
  
  
def lambda_handler(event, context):
    count = event.get('count', 1)
    item_list = []
  
    for i in range(count):
        item = get_item_dynamodb('sample_table', {'Key': 'sample'}) 
        item_list.append(item)
  
    return {
        'statusCode': 200,
        'body': item_list
    }

直感だと、待ち時間が大半なので、低いメモリでコストが最適化されるイメージです。

結果は以下の通りです。想像通りですね。

パラメータ 最適メモリ量(MB)
10 256
50 256
100 256

サンプル2(通信負荷が高い):パラメータ10

サンプル2(通信負荷が高い):パラメータ50

サンプル2(通信負荷が高い):パラメータ100

さいごに

少し早い、クリスマスプレゼントいかがでしたでしょうか。

Lambdaのメモリ量を適切にチューニングすることで、コスト削減/応答速度の向上が見込めます。 特に、無駄に大きいメモリを割り当てて、性能限界に達している場合は効果絶大です。

本ブログがどなかたのお役に立てれば幸いです。

山本 真大(執筆記事の一覧)

アプリケーションサービス部 ディベロップメントサービス1課

2023年8月入社。カピバラさんが好き。