[AWS Lambda Powertools Python]パラメータ機能のキャッシュ動作を確認

# 概要

## パラメータ機能

[AWS Lambda Powertools Python](https://awslabs.github.io/aws-lambda-powertools-python/latest/) [^1] の機能のうち、[パラメータ機能(Parameters)](https://awslabs.github.io/aws-lambda-powertools-python/latest/utilities/parameters/) [^2] はAWSの下記を利用したアプリケーションパラメータ読み取り機能を提供してくれます。

[^1]: [AWS Lambda Powertools Python](https://awslabs.github.io/aws-lambda-powertools-python/latest/)
[^2]: [Parameters](https://awslabs.github.io/aws-lambda-powertools-python/latest/utilities/parameters/)

- AWS Systems Manager Parameter Store
- AWS Secrets Manager
- AWS AppConfig
- Amazon DynamoDB

基本的にはキーを指定してパラメータを読み込むシンプルな機能となっており、それぞれは自身で実装してもそれほど負担にはならない部分ではあります。（実際、私も独自実装していました）
他の目的でPowertoolsを利用するのであれば、実績のある実装済みの機能を使うのが良いかなと思います。


## 今回の目的

パラメータ機能にはキャッシュ機能があります。
一度のLambda処理内で同じパラメータを参照した場合、AWSサービスへリクエストせずに値を返す機能となっています。
本当にリクエストしていないのか確認してみます。

※もちろんソースコードを追えばわかることですが、一連の処理内で同じパラメータを複数回参照するケースで、指定期間内であれば実際にAWSサービスへ問い合わせせずに値を返す処理になっています。
※複数のAWSサービスに対応していますが、同じ仕組み（いわゆるTemplate method パターン）でキャッシュしています。


# 確認手順

対象はSSM Parameter Storeとします。
Lambda Powetoolsではありますが、ローカル環境でテストします。

## モジュール

```
aws-lambda-powertools 1.31.1
boto3                 1.24.96
botocore              1.27.96
```

## ソースコード

botocoreのリクエストを表示させるためにログレベルを`DEBUG`に設定しています。

```parameter_test.py
import logging
from time import sleep
from aws_lambda_powertools.utilities import parameters

logging.basicConfig(level='DEBUG')

print('========== 1st get')
value = parameters.get_parameter("/my/parameter", max_age=5)  # 初回:リクエストする
print(f'========== 1st {value=}')

print('========== 2nd get')
value = parameters.get_parameter("/my/parameter", max_age=5)  # 2回目:5秒以内なのでリクエストしない
print(f'========== 2nd {value=}')

print('========== sleep')
sleep(5)
print('========== 3rd get')
value = parameters.get_parameter("/my/parameter", max_age=5)  # 3回目:直前から5秒経過しているためリクエストする
print(f'========== 3rd {value=}')

print('========== 4th get')
value = parameters.get_parameter("/my/parameter", max_age=5)  # 4回目:5秒以内なのでリクエストしない
print(f'========== 4th {value=}')
```

## パラメータ設定

AWS CLIで設定しました。（ＡＷＳコンソールでも問題ありません）

```sh
$ aws ssm put-parameter \
    --name "/my/parameter" \
    --value "Hello AWS Lambda Powertools for Python" \
    --type String

{
    "Version": 1,
    "Tier": "Standard"
}
```

## 実行結果

DEBUG出力すると大量にログが出力されますが、今回確認したい部分のみ抽出しました。
上からログを確認します。

- `1st get` の次にssmサービスへリクエストしていることが確認できます
- `2nd get` の後はリクエスト無しで結果を返しています
- 5秒スリープ後、 `3rd get` すると、再度ssmサービスへリクエストしていることが確認できます
- `4th get` は `3rd get` から5秒以内のため、リクエスト無しで結果を返しています

期待通りの動作となっていました。

```sh
$ python parameter_test.py
========== 1st get
(snip)
DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): ssm.ap-northeast-1.amazonaws.com:443
DEBUG:urllib3.connectionpool:https://ssm.ap-northeast-1.amazonaws.com:443 "POST / HTTP/1.1" 200 240
DEBUG:botocore.parsers:Response headers: {'Server': 'Server', 'Date': 'Sun, 23 Oct 2022 06:59:03 GMT', 'Content-Type': 'application/x-amz-json-1.1', 'Content-Length': '240', 'Connection': 'keep-alive', 'x-amzn-RequestId': '205f628f-7eca-40aa-b748-a45798b0a30d'}
DEBUG:botocore.parsers:Response body:
b'{"Parameter":{"ARN":"arn:aws:ssm:ap-northeast-1:999999999999:parameter/my/parameter","DataType":"text","LastModifiedDate":1.666494575803E9,"Name":"/my/parameter","Type":"String","Value":"Hello AWS Lambda Powertools for Python","Version":1}}'
(snip)
========== 1st value='Hello AWS Lambda Powertools for Python'
========== 2nd get
========== 2nd value='Hello AWS Lambda Powertools for Python'
========== sleep
========== 3rd get
(snip)
DEBUG:urllib3.connectionpool:https://ssm.ap-northeast-1.amazonaws.com:443 "POST / HTTP/1.1" 200 240
DEBUG:botocore.parsers:Response headers: {'Server': 'Server', 'Date': 'Sun, 23 Oct 2022 06:59:08 GMT', 'Content-Type': 'application/x-amz-json-1.1', 'Content-Length': '240', 'Connection': 'keep-alive', 'x-amzn-RequestId': 'f680b6de-6898-4d59-8294-7b35a7522a77'}
DEBUG:botocore.parsers:Response body:
b'{"Parameter":{"ARN":"arn:aws:ssm:ap-northeast-1:999999999999:parameter/my/parameter","DataType":"text","LastModifiedDate":1.666494575803E9,"Name":"/my/parameter","Type":"String","Value":"Hello AWS Lambda Powertools for Python","Version":1}}'
(snip)
========== 3rd value='Hello AWS Lambda Powertools for Python'
========== 4th get
========== 4th value='Hello AWS Lambda Powertools for Python'
```

# 最後に

そもそも一度のLambda呼び出し処理内で同じパラメータを取得するという実装は避けるべきかなとは思います。
ただ、複数人で開発しているとそういう場面もあるかもしれませんし、その場合はキャッシュ機能は有用と思います。
リクエストすることで処理コスト・費用の増加につながりますし、最初に読みだしたパラメータと途中で読みだしたパラメータが異なることで処理として不整合になるケースもありそうです。

動作を把握して利用したい機能でした。