最近、DynamoDB(とLambda)を使ったサーバーレスなサービスを開発する機会があり、その時に得た知見についていくつか
- ローカルでdynamodb-localとServerless Frameworkを使ってLambda関数を開発
- dynamodb-localへのテーブル作成は別途立てたawscliコンテナからAWS CLIのコマンドによって作成
- 本番環境はServerless Frameworkのresources句の設定によってデプロイ(スタックにDynamoDBテーブルの作成を含める)
開発環境
- WSL2(Ubuntu20.04)
- Docker(20.10.24)
- docker-compose(v2.17.2)
- app
- node:18-slimイメージをベースとしたLambda開発用のコンテナ
- Node.jsのコンテナなのでAWS SDK for JavaScript v3を使用 (以後aws-sdk)
- Serverless Framework v3
- dynamodb
- amazon/dynamodb-localイメージを使用したローカル開発用のDynamoDBを動かすコンテナ
- awscli
- amazon/aws-cliイメージを使用したdynamodb-local操作用のコンテナ
- app
以下、開発環境を作るための設定ファイル等
services:
app:
build:
context: ./
dockerfile: ./.docker/app/Dockerfile
volumes:
- ./serverless:/serverless:cached
tty: true
environment:
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
- AWS_DEFAULT_REGION=ap-northeast-1
dynamodb:
image: amazon/dynamodb-local
command: -jar DynamoDBLocal.jar -sharedDb -dbPath . -optimizeDbBeforeStartup
volumes:
- dynamodb:/home/dynamodblocal
ports:
- 8000:8000
awscli:
image: amazon/aws-cli
environment:
- AWS_ACCESS_KEY_ID=fake_access_key
- AWS_SECRET_ACCESS_KEY=face_secret_access_key
- AWS_DEFAULT_REGION=ap-northeast-1
tty: true
command:
- /bin/sh
entrypoint: [""]
volumes:
dynamodb:
driver: local
FROM node:18-slim
SHELL ["/bin/bash", "-oeux", "pipefail", "-c"]
ENV TZ=Asia/Tokyo
USER node
WORKDIR /serverless
COMPOSE_PATH_SEPARATOR=:
COMPOSE_FILE=docker-compose.yml
AWS_ACCESS_KEY_ID=<AWSアクセスキー>
AWS_SECRET_ACCESS_KEY=<AWSシークレットアクセスキー>
- awscliコンテナに対して設定しているAWSアクセスキー/シークレットアクセスキーはダミーでOKみたい
- appコンテナはLambda関数の開発およびServerless Frameworkを使用したデプロイを行うためのもの。AWS本番にデプロイを行うのに必要となるポリシーがついたIAMアカウントのAWSアクセスキー/シークレットアクセスキーを.envより指定(次項)
AWS本番へのデプロイに必要となったポリシー
マネジメントコンソール上のポリシーエディタより取得
※Lambda関数を作成するためのもの、Lambda関数の中で行っているDynamoDBテーブルの操作で必要となるもの、Lambda関数にアタッチされるAPI GatewayやEventBridgeを作成するのに必要となるポリシーなどを含みます。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"dynamodb:DeleteItem",
"cloudformation:DescribeStackResource",
"cloudformation:CreateChangeSet",
"dynamodb:DeleteTable",
"logs:DeleteLogStream",
"cloudformation:DeleteChangeSet",
"logs:CreateLogStream",
"cloudformation:DescribeStackEvents",
"dynamodb:DescribeTable",
"dynamodb:GetItem",
"cloudformation:DescribeChangeSet",
"cloudformation:ExecuteChangeSet",
"events:*",
"cloudformation:ListStackResources",
"dynamodb:PutItem",
"apigateway:*",
"logs:DeleteLogGroup",
"s3:*",
"dynamodb:Scan",
"dynamodb:Query",
"logs:TagResource",
"s3-object-lambda:*",
"logs:CreateLogGroup",
"cloudformation:DescribeStacks",
"iam:*",
"dynamodb:CreateTable",
"cloudformation:GetTemplate",
"cloudformation:DeleteStack",
"lambda:*",
"cloudformation:ValidateTemplate",
"dynamodb:GetRecords"
],
"Resource": "*"
}
]
}
備忘録
以下、自分用
awscliのcreate-tableではTTLの設定はできない
CloudFormationのAWS::DynamoDB::Tableのリソースを作成するときにはTTLの設定もできる
aws-sdkのCreateTableやUpdateTableについて
DynamoDBのCreateTableやUpdateTableなどのコマンドは非同期でテーブルに関する指示をしているのであって、作成や更新の完了は待っていない(CreateTableコマンドの先頭の方に書いてあった)。
CreateTable is an asynchronous operation. Upon receiving a CreateTable request, DynamoDB immediately returns a response with a TableStatus of CREATING. After the table is created, DynamoDB sets the TableStatus to ACTIVE. You can perform read and write operations only on an ACTIVE table.
You can use the DescribeTable action to check the table status.
CreateTableコマンド実行直後にテーブルに対してTTLを設定したりレコードをインサートしたい場合はDescribeTableコマンドを数秒ごとに実行するなどしてテーブルのステータスがアクティブになったことを確認する再帰の処理を書く必要がありました。
なお、dynamodb-localを使って開発していたときは即時でテーブルがアクティブとなっていたため、AWS環境にデプロイするまで問題に気づけませんでした。。
テーブル作成時のattribute-definitionsに関するエラーについて
- attribute-definitionsで定義した属性について、KeySchema(プライマリキー)、ローカルセカンダリインデックス(LSI)、グローバルセカンダリインデックス(GSI)等で全てを消費していない場合にエラーが発生する(間違っているかも。。)
以下のような文字列の属性を2つ、数値の属性を1つ持つテーブルを作成するコマンドを例とした場合
$ aws dynamodb --endpoint-url http://dynamodb:8000 \
create-table \
--table-name sample1 \
--attribute-definitions \
AttributeName=attr1,AttributeType=S \
AttributeName=attr2,AttributeType=N \
AttributeName=attr3,AttributeType=S \
--key-schema \
AttributeName=attr1,KeyType=HASH \
AttributeName=attr2,KeyType=RANGE \
--billing-mode PROVISIONED \
--provisioned-throughput \
ReadCapacityUnits=1,WriteCapacityUnits=1 \
--local-secondary-indexes \
'[{ "IndexName": "attr3_index", "KeySchema": [ { "AttributeName": "attr1", "KeyType": "HASH" },{ "AttributeName": "attr3", "KeyType": "RANGE" } ], "Projection": { "ProjectionType": "ALL" } } ]'
$ aws dynamodb --endpoint-url http://dynamodb:8000 \
create-table \
--table-name sample2 \
--attribute-definitions \
AttributeName=attr1,AttributeType=S \
AttributeName=attr2,AttributeType=N \
AttributeName=attr3,AttributeType=S \
--key-schema \
AttributeName=attr1,KeyType=HASH \
AttributeName=attr2,KeyType=RANGE \
--billing-mode PROVISIONED \
--provisioned-throughput \
ReadCapacityUnits=1,WriteCapacityUnits=1
An error occurred (ValidationException) when calling the CreateTable operation: The number of attributes in key schema must match the number of attributes defined in attribute definitions.
$ aws dynamodb --endpoint-url http://dynamodb:8000 \
create-table \
--table-name sample3 \
--attribute-definitions \
AttributeName=attr1,AttributeType=S \
AttributeName=attr2,AttributeType=N \
AttributeName=attr3,AttributeType=S \
--key-schema \
AttributeName=attr1,KeyType=HASH \
--billing-mode PROVISIONED \
--provisioned-throughput \
ReadCapacityUnits=1,WriteCapacityUnits=1 \
--global-secondary-indexes \
'[{ "IndexName": "attr3_index", "KeySchema": [ { "AttributeName": "attr3", "KeyType": "HASH" },{ "AttributeName": "attr2", "KeyType": "RANGE" } ], "Projection": { "ProjectionType": "ALL" }, "ProvisionedThroughput": { "ReadCapacityUnits": 1, "WriteCapacityUnits": 1 } } ]'
- 属性はデータをインサートするときに任意で追加できるため、ソート条件などで使用しない属性はテーブル作成のタイミングで含める必要が無さそう。
- LSIはともかく、attribute-definitionsに含めるためだけに使わないGSIを作ると不要なプロビジョンドスループットの消費が行われてしまうはず。
ちなみに上記のsample1テーブルをserverless.ymlのresources句で表現すると以下のような感じ
resources:
Resources:
SampleTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: sample1
AttributeDefinitions:
- AttributeName: attr1
AttributeType: S
- AttributeName: attr2
AttributeType: N
- AttributeName: attr3
AttributeType: S
KeySchema:
- AttributeName: attr1
KeyType: HASH
- AttributeName: attr2
KeyType: RANGE
BillingMode: PROVISIONED
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
LocalSecondaryIndexes:
- IndexName: attr3_index
KeySchema:
- AttributeName: attr1
KeyType: HASH
- AttributeName: attr3
KeyType: RANGE
Projection:
ProjectionType: ALL
動作確認について
serverless-offlineプラグインを使うことでローカルでAPI Gatewayを模した動作確認が行えるみたいですが、今回は使わずに以下のようなコードを書いて確認しました。
appコンテナ内から以下のようなコマンドで動作確認。
$ npx serverless invoke local \
--stage local \
--function Sample \
--data '{"queryStringParameters": {"param1": "aaa", "param2": "bbb"} }'
import { APIGatewayEventRequestContextV2, APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from "aws-lambda";
export const handler = async (event: APIGatewayProxyEventV2, context: APIGatewayEventRequestContextV2): Promise<APIGatewayProxyResultV2> => {
const param1 = event.queryStringParameters.param1;
const param2 = event.queryStringParameters.param2;
...
}
その他、よくわかってないこと
DynamoDBについて開発環境はAWS CLIを使ったテーブル操作を行いましたが、本番にはServerless Frameworkを使用してデプロイ時のCloudFormationのスタック作成で作成する都合上、1つのテーブルを定義するのに2種類の記述が必要となってしまいました。。
こちらどう解決すればよかったのか悩み中。。
参考リンク
今回のDynamoDBとLambdaを使ったサービスを作った際に読んだページ
-
Amazon DynamoDB のコアコンポーネント - Amazon DynamoDB
- DynamoDB固有の用語などの理解が進むページ(公式)
-
コンピュータ上で DynamoDB をローカルでデプロイする - Amazon DynamoDB
- 記事内のdocker-compose.ymlに記載したdynamodbとawscliコンテナはこちらのページを参考にしました(公式)
-
DynamoDB でのエラー処理 - Amazon DynamoDB
- DynamoDBの例外がまとまったページ(公式)
-
AWS::DynamoDB::Table - AWS CloudFormation
- CloudFormationを使用してDynamoDBテーブルを作成するときに読んだページ(公式)
-
Amazon DynamoDB のテーブル設計で悩んだら最初に読もう -これだけ知ればある程度の検索には対応できる- – TechHarmony
- DynamoDBの入門としてGSI、LSIについて理解するのに参考になりました。
-
DynamoDBの各データを自動で削除する機能(TTL:Time to Live)を試してみた | DevelopersIO
- 今回の記事の中では特に書かなかったですがTTLの存在はこちらの記事で知りました
- 今回作成したサービスでは直近数日のデータのみを扱うことになっており、不要となったデータが自動で消えてくれるというDynamoDBのTTL機能が非常に便利でした
-
え、CloudFormationでdynamoDBを作成するときは全カラムをプライマリー・ソートキーにしなきゃいけないんですか!? - Qiita
- DynamoDB入門中、テーブル設計中に発生したエラーの原因を理解するのに参考になりました
-
無料枠で頑張るためにDynamoDBのキャパシティを理解する - ITと筋トレの二刀流
- キャパシティユニットの見積り方などの参考になりました