kakakakakku blog

Weekly Tech Blog: Keep on Learning!

Powertools for AWS Lambda (Python) の Parameters で DynamoDB GSI からパラメータを取得するカスタムプロバイダ

Powertools for AWS Lambda (Python) の Parameters を使うと AWS Systems Manager Parameter Store / AWS Secrets Manager / AWS AppConfig / Amazon DynamoDB から AWS Lambda 関数で使うパラメータ(何かしらの値)を簡単に取得できる❗️また取得したパラメータを内部的にキャッシュして,パラメータの過剰な取得を抑制する仕組みもある.個人的には AWS Systems Manager Parameter Store をバックエンドによく使っている👌

docs.powertools.aws.dev

Amazon DynamoDB からパラメータを取得する場合は Powertools for AWS Lambda (Python) の DynamoDBProvider を使う.しかし DynamoDBProvider は現状 Amazon DynamoDB テーブルからしかパラメータを取得できず,データ構造的に Amazon DynamoDB テーブルの GSI (Global Secondary Index) からパラメータを取得したいという場面があったりする.今回は「カスタムプロバイダ」を実装して GSI からパラメータを取得してみた \( 'ω')/

1. Amazon DynamoDB テーブルからパラメータを取得する

まずは Amazon DynamoDB テーブルからパラメータを取得する.今回は複数値を取得する get_multiple() を前提にする.

👾 template.yaml(一部)

Amazon DynamoDB テーブルは Powertools ドキュメントの例を参考に AWS SAM (AWS CloudFormation) で以下のように構築した.パーティションキーは id で,ソートキーは sk となる.

Resources:
  Table:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: parameters
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: S
        - AttributeName: sk
          AttributeType: S
      KeySchema:
        - AttributeName: id
          KeyType: HASH
        - AttributeName: sk
          KeyType: RANGE
      BillingMode: PAY_PER_REQUEST

そして,同じく Powertools ドキュメントに載っているサンプルデータを AWS CLI で登録しておく.

$ aws dynamodb put-item --table-name parameters \
  --item '{ "id": { "S": "config" }, "sk": { "S": "endpoint_comments" }, "value": { "S": "https://jsonplaceholder.typicode.com/comments/" } }'
$ aws dynamodb put-item --table-name parameters \
  --item '{ "id": { "S": "config" }, "sk": { "S": "limit" }, "value": { "S": "10" } }'

最終的に以下のようになる👌

id sk value
config endpoint_comments https://jsonplaceholder.typicode.com/comments/
config limit 10

👾 app.py

AWS Lambda 関数は簡単に実装できる❗️Amazon DynamoDB テーブル parameters を参照するように DynamoDBProvider を初期化して,get_multiple() を使って config という値をキーにパラメータを取得している.デフォルトではパラメータを 5秒間 キャッシュするけど,今回は動作確認も兼ねて max_age を設定して 30秒間 にした.

from aws_lambda_powertools.utilities import parameters

dynamodb_provider = parameters.DynamoDBProvider(table_name='parameters')


def lambda_handler(event, context):
    configs = dynamodb_provider.get_multiple('config', max_age=30)
    print(configs)

動作確認

AWS Lambda 関数を定期的に実行しつつ,途中で以下のコマンドを実行して3つ目の config を追加する.値は適当💨

$ aws dynamodb put-item --table-name parameters \
  --item '{ "id": { "S": "config" }, "sk": { "S": "sort" }, "value": { "S": "ASC" } }'

結果的にプロパティのキャッシュが切れてから3つ目の config も取得された👌

{'endpoint_comments': 'https://jsonplaceholder.typicode.com/comments/', 'limit': '10'}
{'endpoint_comments': 'https://jsonplaceholder.typicode.com/comments/', 'limit': '10'}
{'endpoint_comments': 'https://jsonplaceholder.typicode.com/comments/', 'limit': '10'}
{'endpoint_comments': 'https://jsonplaceholder.typicode.com/comments/', 'limit': '10', 'sort': 'ASC'}

2. Amazon DynamoDB GSI (Global Secondary Index) パラメータを取得する

今度は Amazon DynamoDB GSI (Global Secondary Index) からパラメータを取得する.しかし Powertools for AWS Lambda (Python) の DynamoDBProvider は GSI をサポートしていないためカスタムプロバイダを実装する.同じく今回は複数値を取得する get_multiple() を前提にする.GSI サポートは issue にもなかった💨(特に需要なさそう?)

👾 template.yaml(一部)

Amazon DynamoDB テーブルは少し構造を変えて uuid をパーティションキーにした.そして,id-index GSI では id をパーティションキーにして,最初の例と同じデータを取得できるようにした.

Resources:
  Table:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: parameters
      AttributeDefinitions:
        - AttributeName: uuid
          AttributeType: S
        - AttributeName: id
          AttributeType: S
      KeySchema:
        - AttributeName: uuid
          KeyType: HASH
      BillingMode: PAY_PER_REQUEST
      GlobalSecondaryIndexes:
        - IndexName: id-index
          KeySchema:
            - AttributeName: id
              KeyType: HASH
          Projection:
            ProjectionType: ALL

同じようにサンプルデータを AWS CLI で登録しておく.

$ aws dynamodb put-item --table-name parameters \
  --item '{ "uuid": { "S": "d33b9dca-952c-4218-b05e-cbd2222ef766" }, "id": { "S": "config" }, "sk": { "S": "endpoint_comments" }, "value": { "S": "https://jsonplaceholder.typicode.com/comments/" } }'
$ aws dynamodb put-item --table-name parameters \
  --item '{ "uuid": { "S": "c2be3412-1107-48d0-b992-750b8fcd4d42" }, "id": { "S": "config" }, "sk": { "S": "limit" }, "value": { "S": "10" } }'

最終的に以下のようになる👌

uuid id sk value
d33b9dca-952c-4218-b05e-cbd2222ef766 config endpoint_comments https://jsonplaceholder.typicode.com/comments/
c2be3412-1107-48d0-b992-750b8fcd4d42 config limit 10

👾 dynamodb.py

次にカスタムプロバイダ DynamoDBIndexProvider を実装する.サンプルとして実装量をできる限り減らしているけど,汎用的に実装するのであれば DynamoDBProvider の実装を参考にして,指定できる値を増やしたり,LastEvaluatedKey を評価したりすると良いと思う👀

github.com

カスタムプロバイダの実装方法は Powertools ドキュメントにも載っているけど,BaseProvider を継承して _get()_get_multiple() を実装すれば OK👌今回は複数値を前提にしているため,_get()NotImplementedError を返して,_get_multiple() では GSI に Query を実行している.

import boto3
from aws_lambda_powertools.utilities.parameters import BaseProvider


class DynamoDBIndexProvider(BaseProvider):
    def __init__(self, table_name, index_name):
        self.index_name = index_name
        self.table = boto3.resource('dynamodb').Table(table_name)
        super().__init__()

    def _get(self):
        raise NotImplementedError

    def _get_multiple(self, id):
        response = self.table.query(
            IndexName=self.index_name,
            KeyConditionExpression='id = :id',
            ExpressionAttributeValues={':id': id},
        )

        return {item['sk']: item['value'] for item in response['Items']}

👾 app.py

こっちはほぼ同じで,Powertools for AWS Lambda (Python) の DynamoDBProviderDynamoDBIndexProvider に置き換えた程度👌 GSI 名を index_name で指定している〜

from dynamodb import DynamoDBIndexProvider

dynamodb_provider = DynamoDBIndexProvider(table_name='parameters', index_name='id-index')


def lambda_handler(event, context):
    configs = dynamodb_provider.get_multiple('config', max_age=30)
    print(configs)

動作確認

同じく AWS Lambda 関数を定期的に実行しつつ,途中で3つ目の config を追加する.

$ aws dynamodb put-item --table-name parameters \
  --item '{ "uuid": { "S": "571a981c-9002-4e2f-b3e5-207f8a0ce1fd" }, "id": { "S": "config" }, "sk": { "S": "sort" }, "value": { "S": "ASC" } }'

結果的にプロパティのキャッシュが切れてから3つ目の config も取得された👌

{'limit': '10', 'endpoint_comments': 'https://jsonplaceholder.typicode.com/comments/'}
{'limit': '10', 'endpoint_comments': 'https://jsonplaceholder.typicode.com/comments/'}
{'limit': '10', 'endpoint_comments': 'https://jsonplaceholder.typicode.com/comments/'}
{'limit': '10', 'endpoint_comments': 'https://jsonplaceholder.typicode.com/comments/', 'sort': 'ASC'}

まとめ

Powertools for AWS Lambda (Python) の Parameters で Amazon DynamoDB テーブルの GSI (Global Secondary Index) からパラメータを取得する場合はカスタムプロバイダを実装しよう❗️