初級読了 2 分

Docker で DynamoDB Local を動かす完全ガイド

DynamoDB Local は、DynamoDB を単一プロセスで再現した AWS のダウンロード可能なエミュレーション です。同じ API、AWS アカウント不要、ネットワーク不要、リクエストごとの請求もありません。 ローカルの開発と統合テストに使い、本番では同じコードをクラウドに向ければ済みます。 プロビジョンドスループットを無視し、決してスロットリングしないので、負荷テストや上限テストの 代わりにはなりません。

Docker で DynamoDB Local はどう動かしますか?

docker run -p 8000:8000 amazon/dynamodb-local を実行して公式イメージを起動すると、 http://localhost:8000 で DynamoDB エンジンが公開されます。AWS SDK か CLI を任意の ダミー認証情報でそのエンドポイントに向け、クラウドに対するのとまったく同じようにテーブルを 作成しリクエストを実行します。再起動をまたいでデータを残すには、-sharedDb とマウントした -dbPath ボリュームを追加します。

コンテナを起動する

docker run -p 8000:8000 amazon/dynamodb-local

これで http://localhost:8000 にエンジンが公開されます。

docker-compose

ほとんどのプロジェクトは、チーム全員が同じエンドポイントを得られるよう docker-compose.yml に固定します。

services:
  dynamodb:
    image: amazon/dynamodb-local
    user: root
    command: '-jar DynamoDBLocal.jar -sharedDb -dbPath /data'
    ports:
      - '8000:8000'
    volumes:
      - dynamodb-data:/data
volumes:
  dynamodb-data:

イメージは非 root の dynamodblocal ユーザーで動作しますが、このユーザーは root 所有の 名前付きボリューム内のデータベースファイルを開けません。user: root を付けないと SQLiteException [14] unable to open database file に当たり、あらゆる呼び出しがハングします。

永続化

デフォルトでは DynamoDB Local はインメモリです。コンテナを停止するとすべての テーブルが消えます。2つのフラグでこれを永続化できます。

  • -sharedDb はすべてのクライアントを1つの共有データベースファイルに載せます (これが ないと、認証情報/リージョンの組み合わせごとに独立した DB を持ち、よくある「テーブルが どこに行った?」という驚きの原因になります)。
  • -dbPath /data とマウントしたボリュームがそのファイルをディスクに書き込むので、 データは docker compose down を越えて残ります。

SDK をそこに向ける

変わるのはエンドポイントだけで、認証情報は任意のダミー値で構いません。

import {DynamoDBClient} from '@aws-sdk/client-dynamodb';

const client = new DynamoDBClient({
  endpoint: 'http://localhost:8000',
  region: 'local',
  credentials: {accessKeyId: 'x', secretAccessKey: 'x'}
});

テーブルを作成する

aws dynamodb create-table \
  --endpoint-url http://localhost:8000 \
  --table-name AppData \
  --attribute-definitions AttributeName=PK,AttributeType=S AttributeName=SK,AttributeType=S \
  --key-schema AttributeName=PK,KeyType=HASH AttributeName=SK,KeyType=RANGE \
  --billing-mode PAY_PER_REQUEST

こうしたシングルテーブルPK/SK スキーマは良い デフォルトです。フィクスチャを読み込むときは、DynamoDB-JSON コンバーターでプレーンな JSON をワイヤー形式に変換します。

コンテナが立ち上がっていてテーブルが作られたことを確認します。

aws dynamodb list-tables --endpoint-url http://localhost:8000

GUI で閲覧する

CLI 呼び出しはすぐに面倒になります。よくある選択肢はオープンソースの dynamodb-admin ウェブ UI か、デスクトップクライアントです。DynoTablelocalhost:8000 (あるいは任意の LocalStack エンドポイント。DynamoDB Local と LocalStack への接続 を参照) に直接つながり、クラウドのテーブルに使うのと同じ UI で、ローカルテーブルの閲覧、 でのクエリ、編集ができます。aws CLI の往復は不要です。

Local がエミュレートしないもの

Local はキャパシティのシミュレーターではなく、API の互換レイヤーだと考えてください。 プロビジョンドスループットを無視し、 ProvisionedThroughputExceededException を決して返さず、オンデマンドのバースト挙動もモデル化しません。Local に対する負荷テストは、 AWS におけるパーティション上限やアダプティブキャパシティについて何も教えてくれません。

計画に入れておかないと、統合テストで表に出てくるギャップは他にもあります。

挙動DynamoDB LocalAWS DynamoDB
課金 / RCU / WCUなしリクエストごとに計測
スロットリング起きないあり。テーブル/インデックスの上限で
TTL の削除タイミングベストエフォート、SLA なしAWS のスケジュールでのバックグラウンド掃引
DynamoDB Streams の配信簡略化されている完全なストリームのセマンティクス + Lambda 連携
テーブルをまたぐトランザクション最近のビルドでサポート文書化された制限付きの完全な ACID
グローバルテーブル / PITR利用不可本番の機能

テストがスロットリング、数秒以内の TTL 失効、ストリームのファンアウトをアサートするなら、 少なくとも1つのスイートは、使い捨てのクラウドテーブルか、必要な機能を有効にした LocalStack に対して実行しましょう。

実践的なローカルのワークフロー

ほとんどのチームは Local を3つの層に組み込んでいます。

  1. ユニットテスト — CI でコンテナを立ち上げ、beforeAll でテーブルを作成し、 afterAll で片付けます。フィクスチャは小さく保ちましょう。テストが属性マップを手で 貼り付けるときは、DynamoDB JSON コンバーターで プレーンな JSON をマーシャルします。
  2. 統合テスト — アプリが使っているのと同じ SDK クライアントのファクトリを、 endpoint と認証情報だけ差し替えて動かします。アサートするのはアイテムの形と条件付き 書き込みであって、消費キャパシティではありません (Local は予算に使える意味のある ConsumedCapacity を返しません)。
  3. 手動での探索 — Local のプロファイルで DynoTable をつなぎ、編集をステージングし、 スキーマ変更をデプロイする前に PartiQL やキー条件のクエリを実行します。

単一プロセスでは足りなくなったら — 複数のサービス、S3 のトリガー、IAM 相当のルーティング — LocalStackか開発用アカウントに進みましょう。 Local は「自分のアクセスパターンは通るか?」を確かめる最速のループであり続けます。

手でマーシャルせずにデータを流し込む

JSON ファイルから10件のフィクスチャを読み込む作業は、すべての値に自分で型を付けなくて 済むなら速くなります。配列を DynamoDB JSON コンバーターに貼り付け、マーシャルされた 出力をコピーし、--endpoint-url http://localhost:8000 に対して BatchWriteItem で 一括書き込みします。更新の多いフィクスチャでは、 DynamoDB 式ビルダーUpdateExpression を組み立て、 生成された属性マップをテストハーネスに貼り付けましょう。

DynoTable のアイテムエディターはコミット時に同じマーシャリングを行います。テストの失敗で CLI の生の {"S":...} を眺めるはめになったときに役立ちます。

Local を離れるとき

次のうちどれかを AWS 上で実測する必要があるなら、実際のテーブルに出しましょう。

  • キャパシティ計画 — 1 KB のアイテムを秒間1,000回クエリすると、オンデマンド課金では 秒あたりおよそ250の結果整合性 RCU を消費しますが、Local はゼロと報告します。 アイテムサイズ計算機で得たサイズを使って 料金計算ツールでモデル化してください。
  • インデックス伝播の遅延 — 本番では GSI の読み取りは結果整合性ですが、Local は インデックスの行を十分速く返すので、古い読み取りのバグがデプロイまで隠れます。
  • クロスアカウントの IAM — リソース単位のロールや条件キーはクラウドにしかありません。

Local はスキーマと式の構文に対する速いフィードバックのために取っておき、コストと整合性の 前提は本番トラフィックの前にステージングのテーブルで検証しましょう。

スクリプトで回避する価値のある落とし穴

  • -sharedDb の付け忘れ — 認証情報の組み合わせごとに独立したデータベースになり、 CI と自分のノート PC が別世界に見えます。
  • user: root なしの root 所有ボリューム — 上のセクションの compose の上書きを 追加するまで、SQLite バックエンドが静かに失敗します。
  • Streams が同等だと思い込む — ストリーム連動の Lambda にはクラウドか LocalStack の ターゲットが必要です。Local だけではファンアウトを動かせません。
  • 空文字列のキー — 2020年以降、非キー属性では許可されていますが、キーでは依然として 拒否されます。AWS と同じようにフィクスチャを検証しましょう。

DynoTable をダウンロードして http://localhost:8000 を指すプロファイルを 追加し、いま作ったテーブルを閲覧してみてください。本番で使うのと同じグリッド、 フィルタービルダー、SQL Workbench が、ループ中の AWS 支出ゼロで使えます。

更新日