👾

DynamoDB を体系的に学習した記録 - 実践的なサンプルアプリケーション付き

に公開

はじめに

最近、AWS DynamoDB について体系的に学習しましたので、その学習記録を共有します。

NoSQL の基礎知識があまりない状態から始め、実際に動作するサンプルアプリケーションまで作成しましたので、これから DynamoDB を学習される方の参考になれば幸いです。

リポジトリはこちらです。
https://github.com/Mo3g4u/dynamodb-study-todo/tree/main

なぜ DynamoDB を学ぶことにしたのか

きっかけは、AWS でサーバーレスアプリケーションを構築したいと考えたことでした。

Lambda + DynamoDB の組み合わせは、スケーラブルで管理が容易、かつコスト最適化も可能という優れた特性を持っています。しかし、実際に使おうとすると、いくつかの疑問が浮かびました。

  • テーブル設計はどのように行うべきか
  • JOIN ができない環境でリレーションをどう表現するか
  • インデックスの適切な設計方法

RDB での設計手法がそのまま適用できないことに気づき、本格的な学習の必要性を感じました。

学習の進め方

Step 1: AWS 公式ドキュメントの整理

まず、AWS 公式の DynamoDB 開発者ガイドを読み込みました。

ただし、公式ドキュメントは非常にボリュームがあるため、Claude Code と MCP サーバーを利用して重要なトピックを8つのMarkdownファイルに整理しました。

00_学習ガイド.md              # 学習の順序と進め方
01_DynamoDB基礎概念.md        # NoSQL の基本と DynamoDB の特徴
02_テーブル設計とキー戦略.md   # 設計の考え方
03_データモデリングパターン.md # よく使うパターン
04_読み書き操作.md            # CRUD 操作の基本
05_インデックス戦略.md        # GSI/LSI の使い方
06_ベストプラクティス.md      # 推奨事項とアンチパターン
07_実践的なユースケース例.md  # 実際の使用例

各ファイルには、AWS 公式ドキュメントの内容を整理し、コード例と共にまとめました。

Step 2: 実践的なサンプルアプリケーションの作成

理論だけでは理解が不十分なため、実際に動作するサンプルアプリケーションを Claude Code を利用して作成しました。

テーマは「タスク管理システム」です。User、Project、Task の3つのエンティティを、DynamoDB の単一テーブル設計パターンで実装しました。

作成したもの

サンプルアプリケーションの構成

sample-app/
├── docker-compose.yml      # DynamoDB Local 環境
├── setup/                  # テーブル作成スクリプト
├── src/
│   ├── models/            # データモデル
│   ├── repository/        # DB 接続層
│   ├── services/          # ビジネスロジック
│   └── examples/          # 学習用サンプルコード
└── tests/                 # ユニットテスト

5つの段階的な学習サンプル

重要なポイントは、段階的に学習できる5つのサンプルコードを作成したことです。

Phase 1: 基本的な CRUD 操作 (01_basic_operations.py)

DynamoDB の基本操作である PutItem、GetItem、UpdateItem、DeleteItem を使った、データの作成・取得・更新・削除を学習します。

# アイテムの作成
db.put_item({
    'PK': 'USER#user123',
    'SK': 'PROFILE',
    'Name': 'テストユーザー',
    'Email': 'test@example.com'
})

# アイテムの取得
user = db.get_item('USER#user123', 'PROFILE')

このPhaseで学習する重要な概念:

  • 条件付き書き込みによる一意性の保証
  • アトミックカウンターによる安全な数値操作
  • 楽観的ロックによるバージョン管理

Phase 2: クエリパターン (02_query_patterns.py)

Query と Scan の違いを実際に体験し、効率的なデータ取得方法を学習します。

# Query(高速・低コスト)
items = db.query(
    pk='USER#user123',
    sk_begins_with='PROJECT#'
)

# Scan(低速・高コスト)- 本番環境では使用を避けるべき
items = db.scan(filter_expression=Attr('Status').eq('active'))

実際に実行してみると、10件程度のデータでも Query と Scan では処理時間に数倍の差があることが確認できました。本番環境では Scan の使用を避けるべき理由が実感できます。

Phase 3: インデックス活用 (03_index_usage.py)

GSI(Global Secondary Index)の効果的な使い方を学習します。

例えば、「ステータスが"進行中"のタスク一覧」を取得したい場合:

# GSI1: StatusIndex
items = db.query(
    index_name='StatusIndex',
    pk='STATUS#in_progress'
)

メインテーブルではTASK#taskIdがパーティションキーであるため、ステータスでの検索ができません。しかし GSI を使用することで、異なる視点からデータにアクセスすることが可能になります。

学習する重要な概念:

  • スパースインデックスによるコスト削減
  • インデックスオーバーローディングによる1つの GSI の複数用途での使用
  • Write Shardingによるホットパーティション対策

Phase 4: トランザクション処理 (04_transactions.py)

在庫管理や銀行振込など、データ整合性が重要な処理の実装方法を学習します。

# 在庫を減らして注文を作成(All or Nothing)
transaction_items = [
    {
        'Update': {
            'Key': {'PK': 'PRODUCT#prod001', 'SK': 'INVENTORY'},
            'UpdateExpression': 'SET Stock = Stock - :qty',
            'ConditionExpression': 'Stock >= :qty',  # 在庫不足時は失敗
            'ExpressionAttributeValues': {':qty': 2}
        }
    },
    {
        'Put': {
            'PK': 'ORDER#order123',
            'SK': 'DETAIL',
            'Quantity': 2,
            'Status': 'confirmed'
        }
    }
]

db.transact_write_items(transaction_items)

在庫が不足している場合、注文も作成されません。これが ACID 保証です。

ただし、トランザクションは通常の書き込みの2倍のコストがかかるため、真に必要な場合のみ使用すべきという点も重要な学びでした。

Phase 5: バッチ操作 (05_batch_operations.py)

大量データを扱う際の効率的な処理方法を学習します。

# 25件を一括で書き込み
items = [
    {'PK': f'BATCH#001', 'SK': f'ITEM#{i:03d}', 'Value': i}
    for i in range(25)
]

db.batch_write_items(items, operation='put')

個別書き込みと比較して、約5倍程度の速度向上が確認できました。

ただし、部分的な失敗が発生する可能性があるため、Exponential Backoff + Jitter パターンを使用したリトライロジックの実装が必要です。

学習を通じて得られた知見

1. NoSQL 設計の考え方

「アクセスパターンファースト」 という考え方が最も重要な学びでした。

RDB では正規化を行ってからテーブルを作成し、後で JOIN を使用しますが、DynamoDB では逆のアプローチを取ります。

  1. まず必要なクエリパターンをすべて洗い出す
  2. それに合わせてテーブル構造を設計する
  3. 単一テーブル設計を採用する

当初は「User、Project、Taskを1つのテーブルに格納する」という概念に戸惑いましたが、慣れると非常に効率的な設計であることが理解できました。

2. 実践的なパターンの習得

  • 隣接リストパターン:多対多の関係を効率的に表現
  • 時系列データ:ソートキーに日時を使用
  • 複合キー:階層構造を#で表現(例:USER#user123PROJECT#proj001

これらのパターンを知っているかどうかで、設計の質が大きく変わります。

3. コスト最適化の重要性

DynamoDB は使用方法によってコストが大きく変動します。

  • Scan の使用を避ける
  • スパースインデックスによるインデックスサイズの削減
  • プロジェクションの最適化
  • バッチ処理による効率化

これらの最適化手法の積み重ねが、本番環境では大きな差となります。

学習中に直面した課題

学習過程では、いくつかの技術的な課題に直面しました。

1. トランザクションのデータ型エラー

boto3のクライアントAPIとリソースAPIでは、データ型の扱いが異なります。

TypeSerializerを使用してDynamoDB形式に変換する必要があることを学びました。

2. GSIのキー属性名の理解

GSI(Global Secondary Index)を使用する際、メインテーブルとは異なるキー属性名を使用する必要があります。

データ投入時の構造:

# タスクデータをDynamoDBに保存する際の実際の構造
{
    'PK': 'TASK#task123',           # メインテーブルのパーティションキー
    'SK': 'DETAIL',                  # メインテーブルのソートキー
    'GSI1PK': 'STATUS#in_progress',  # StatusIndex用のパーティションキー
    'GSI1SK': '2025-10-03T10:00:00', # StatusIndex用のソートキー(作成日時)
    'TaskName': 'サンプルタスク',
    ...
}

クエリ実行時:

# StatusIndexでクエリする場合
items = db.query(
    index_name='StatusIndex',
    pk='STATUS#in_progress'  # この値はGSI1PKと比較される
)

# メインテーブルでクエリする場合
items = db.query(
    pk='TASK#task123'  # この値はPKと比較される
)

つまり、GSIを定義する際には専用のキー属性(GSI1PK/GSI1SKなど)を指定し、データ投入時にこれらの属性に適切な値を設定しておく必要があります。クエリ時にはindex_nameを指定することで、DynamoDBが自動的に適切なキー属性を使用してくれます。

3. フィルター式の動作

フィルター式はデータ取得後に適用されるため、RCU は取得したすべてのアイテムに対して消費されます。この点を理解していないと、予想外のコストが発生する可能性があります。

サンプルコードの使用方法

GitHub にて公開しておりますので、どなたでも試していただけます。

セットアップ(約5分)

# リポジトリのクローン
git clone https://github.com/Mo3g4u/dynamodb-study-todo.git
cd dynamodb-study-todo/sample-app

# DynamoDB Localの起動
docker-compose up -d

# Python環境のセットアップ
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt

# テーブルの作成
python setup/create_tables.py

# サンプルデータの投入
python setup/seed_data.py

サンプルの実行

# Phase 1: 基本操作
python src/examples/01_basic_operations.py

# Phase 2: クエリパターン
python src/examples/02_query_patterns.py

# Phase 3: インデックス活用
python src/examples/03_index_usage.py

# Phase 4: トランザクション処理
python src/examples/04_transactions.py

# Phase 5: バッチ操作
python src/examples/05_batch_operations.py

各サンプルはカラー出力で結果が表示されるため、視覚的に理解しやすくなっています。

ドキュメント

サンプルコードに加えて、詳細な解説ドキュメントも作成しました。

examples/README.md

各Phaseで学習する内容、使用方法、注意点について記載しています。

  • 学習の流れ
  • コード例と詳細な解説
  • ベストプラクティスとアンチパターン
  • パフォーマンス比較
  • よくある質問

このドキュメントを参照しながらサンプルを実行することで、DynamoDB の基礎から応用まで体系的に学習できます。

まとめ

DynamoDB を体系的に学習したことで、NoSQL データベースの設計思想と実践的な使用方法を理解することができました。

当初は「RDB で十分ではないか」と考えていましたが、現在では適切なユースケースを判断できるようになりました。

特に以下のような要件がある場合、DynamoDB は優れた選択肢となります:

  • 大量のトラフィックを処理する必要がある
  • 低レイテンシが求められる
  • サーバーレスアーキテクチャで運用したい
  • 高いスケーラビリティが必要

おわりに

本記事が、これからDynamoDBを学習される方の参考になれば幸いです。

DynamoDBの学習において重要なのは:

  1. 公式ドキュメントをしっかりと読む
  2. 実際にコードを書いて動かす
  3. エラーから学ぶ

参考資料

AWS公式ドキュメント

本記事関連リソース

  • GitHub: dynamodb-study-todo
  • 学習ガイド全8ファイル
  • 実践サンプルアプリケーション(タスク管理システム)
  • 5つの段階的学習サンプルコード
レスキューナウテックブログ

Discussion