[検証]性能観点でパラメータ情報はAWSサービスのどこに保存するのが良い？

こんにちは

某SIerでソリューションアーキテクトをしております、たくまです。

Lambdaなどの処理で実行するパラメータ等の情報はどこに格納していますか？
SSMパラメータストア？S3？DynamoDB？

色々なサービスがあると思いますが、それぞれの性能観点。主にレイテンシーを考えた時に各サービスに違いはあるのか？という事が気になり検証してみました。

比較したサービスは、以下サービスです。

- AWS Systems Manager Parameter Store (以降SSMパラメータストア)
- AWS Systems Manager Parameter Store KMSを有効にした場合
- AWS Secrets Manager
- Amazon S3（以降S3）
- DynamoDB

それぞれのサービスでの性能に関わるマニュアルは以下辺りです。

####AWS Systems Manager Parameter Store
特に上限についての記載は見当たりませんでしたが、スループットの引き上げが可能な様です。
[Parameter Store のスループットを引き上げる](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/parameter-store-throughput.html)

####AWS Secrets Manager
5,000/秒の読み込みが上限の様です。
[AWS Secrets Manager quotas](https://docs.aws.amazon.com/secretsmanager/latest/userguide/reference_limits.html)

####S3
5,500/Sが GETリクエストの上限の様です。
[Best practices design patterns: optimizing Amazon S3 performance](https://docs.aws.amazon.com/AmazonS3/latest/userguide/optimizing-performance.html)

####DynamoDB
40,000 read request unitsが上限の様です。
[Amazon DynamoDB のサービス、アカウント、およびテーブルのクォータ](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Limits.html#default-limits-throughput-capacity-modes)

####AWS Key Management Service
KMS辺りの許可50,000/秒が上限のようです。
[AWS Key Management Serviceリソースクォータ](https://docs.aws.amazon.com/ja_jp/kms/latest/developerguide/resource-limits.html)

###検証結果1

それぞれ、「Value=AWS」というパラメータを登録し、Lambdaから実行させました。
Lambda側の性能もある為、メモリの割り当ては128MBと1769MBで比較しました。（3回実行し平均をとっています）

※Lambdaのメモリを1769Mにしてる理由は、こちらの記事[Lambdaが6vCPUまで拡張されたので性能検証してみました](https://qiita.com/takuma818t/items/a25e22fec1863707be08)
を参照してください。


AWSサービス|Lambda 128M|Lambda 1769M|備考
---|---|---|---
SSM パラメータストア|41.08ms|25.24ms|
SSM パラメータストアKMS有|53.67ms|44.38ms|KMS利用で1割ぐらいお遅くなる
SecretsManager|41.59ms|31.10ms|
S3|161.93ms|29.53ms|遅かったが、Lambdaメモリを上げると普通ぐらいに
DynamoDB|40.01ms|4.07ms|Lamndaメメモリを上げると爆速

Lambdaのメモリの割り当てが少ないとS3は異様に遅かったですが、それ以外のサービスはそれほど大きな差はありませんでした。
KMSを使うと2割程度遅くなるようです。

Lambdaのメモリ割り当てを増やすと早くなりましたがサービスによって改善具合に差がありました。
DynamoDBやS3はLambdaの割り当を増やすとレイテンシーも大きく改善しました。


###検証結果2

性能に関わるパラメータを変更させて比較しました。(Lambdaのメモリは、1769MBとしています。)

AWSサービス|Lambda 1769M|設定変更時|設定変更の内容
---|---|---|---
SSM パラメータストア|25.24ms|27.89ms|スループットの引き上げ設定
SSM パラメータストアKMS有|44.38ms|43.91|スループットの引き上げ設定
S3|29.53ms|57.97ms|KMS利用しバケットキー無効
S3|29.53ms|41.26|KMSを利用しバケットキー有効化
DynamoDB|4.07ms|5.44|RCを1→100に変更

性能の上限をあげたとしても、レイテンシーにはあまり違いはなさそうでした。
こちらの結果からもKMSを利用するとレイテンシーが上がるようです。その為、S3は理由が無い限りはバケットキーも有効にして運用する方がよさそうです。

※S3のバケットキーについては[AmazonS3バケットキーを使用したSSE-KMSのコストの削減](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-key.html)を参照ください。

#最後に

Lambdaに適切なメモリを割り当てて上げた場合は、DynamoDB以外はどのサービスもレイテンシーに大きく違いはなく、レイテンシーという観点では使いやすいサービスを選択すればよさそうです。

ただし、秒間リクエスト数の上限はあるのでリンクした上限を確認しリクエストに耐えられるか、減らす方法は無いか確認が必要となると思います。


#検証したpythonコード
```python:get-parametar.py
import time
import boto3

ssm = boto3.client('ssm')
secretsmanager = boto3.client('secretsmanager')
s3 = boto3.resource('s3')
bucket = s3.Bucket("<S3バケット名>")
obj = bucket.Object("parameter")

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('parameter')

def lambda_handler(event, context):
    loop_num=100
    
    #SSM パラメータストア
    start_unixtime = time.time()
    for counter in range(loop_num):
        response = ssm.get_parameters(
            Names=[
                'parameter',
            ],
            WithDecryption=True
        )

    end_unixtime = time.time()  
    execuete_time =end_unixtime - start_unixtime
    print("SSM パラメータストア:" , execuete_time) #実行時間出力

        
    #SSM パラメータストアKMS利用
    start_unixtime = time.time()
    for counter in range(loop_num):
        response = ssm.get_parameters(
            Names=[
                'parameterkms',
            ],
            WithDecryption=True
        )
        
    end_unixtime = time.time()  
    execuete_time =end_unixtime - start_unixtime
    print("SSM パラメータストアKMS利用:" , execuete_time) #実行時間出力

   
    #SecretsManager
    start_unixtime = time.time()
    for counter in range(loop_num):
        response = secretsmanager.get_secret_value(
            SecretId="parameter"
        )

    end_unixtime = time.time()  
    execuete_time =end_unixtime - start_unixtime
    print("SecretsManager:" , execuete_time) #実行時間出力

    #S3 resource
    start_unixtime = time.time()
    for counter in range(loop_num):
        response = obj.get()
        
    end_unixtime = time.time()  
    execuete_time =end_unixtime - start_unixtime
    print("S3:" , execuete_time) #実行時間出力

    
    #DynamoDB
    start_unixtime = time.time()
    for counter in range(loop_num):
        response = table.get_item(Key={'id': '0'})
        
    end_unixtime = time.time()  
    execuete_time =end_unixtime - start_unixtime
    print("DynamoDB:" , execuete_time) #実行時間出力

    return 0
```