LoginSignup
0
0

DynamoDBについて備忘録

Posted at

最近、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
    • dynamodb
      • amazon/dynamodb-localイメージを使用したローカル開発用のDynamoDBを動かすコンテナ
    • awscli
      • amazon/aws-cliイメージを使用したdynamodb-local操作用のコンテナ

以下、開発環境を作るための設定ファイル等

docker-compose.yml
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
.docker/app/Dockerfile
FROM node:18-slim
SHELL ["/bin/bash", "-oeux", "pipefail", "-c"]

ENV TZ=Asia/Tokyo

USER node

WORKDIR /serverless
.env
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のCreateTableUpdateTableなどのコマンドは非同期でテーブルに関する指示をしているのであって、作成や更新の完了は待っていない(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" } } ]'
エラーあり(attr3がどこでも使用されていない)
$ 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.
エラーなし(attr3をプライマリキーとしたQuery、Scanを行わないのであれば作らない方が良い?)
$ 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句で表現すると以下のような感じ

serverless/serverless.yml
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"} }'
sample.ts
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を使ったサービスを作った際に読んだページ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0