コンテンツにスキップ

DynamoDBをSQLと同じ感覚で使うとハマるポイント

概要

SQLに慣れたエンジニアがDynamoDBを初めて使うと、RDB的な思考パターンのままでハマりがち。DynamoDBはNoSQLであり設計思想がRDBと根本的に異なる。

SQLとDynamoDBの主な違い

観点 SQL (RDB) DynamoDB
スキーマ 固定スキーマ スキーマレス(アイテムごとに異なる属性可)
クエリの柔軟性 JOINや任意条件で検索可 Primary Key / GSI での検索のみ
トランザクション 複数テーブルをまたぐACID 同一テーブル内の限定的トランザクション
スケーリング 垂直スケーリングが基本 水平スケーリング(無制限)
コスト構造 インスタンスコスト 読み書きユニット数課金
JOIN 自由にできる できない(アプリ側で処理)
インデックス 任意カラムに追加可 GSI(グローバルセカンダリインデックス)で代替

よくハマるポイントと対処法

1. 「とりあえず検索」ができない

# SQLなら:
SELECT * FROM users WHERE age > 30 AND city = 'Tokyo'

# DynamoDBではこれは Scan(全件取得後フィルタ)になる
# → テーブルが大きいと爆遅・爆課金
response = table.scan(
    FilterExpression=Attr('age').gt(30) & Attr('city').eq('Tokyo')
)
# NG: データ量に比例してコストが上がる

対処法: アクセスパターンを先に決めてから設計する。agecityで頻繁に検索するなら GSI を作る。

# GSI を使ったクエリ(効率的)
response = table.query(
    IndexName='city-age-index',
    KeyConditionExpression=Key('city').eq('Tokyo') & Key('age').gt(30)
)

2. Primary Key の設計が全て

DynamoDBはPrimary Key(Partition Key + Sort Key)の設計で性能が決まる。

悪い設計:
  PK: user_id (単調増加するID)
  → 特定のパーティションに書き込みが集中(ホットパーティション)

良い設計:
  PK: user_id#timestamp のようにハッシュを分散させる
  SK: 日時や種別でソートできる値

3. JOINはできない

-- SQLなら1クエリで済む
SELECT u.name, o.amount
FROM users u JOIN orders o ON u.id = o.user_id
WHERE u.id = 123
# DynamoDBでは2回クエリが必要
user = get_user(user_id=123)
orders = get_orders(user_id=123)
# アプリ側でマージ

対処法: 1テーブル設計(Single Table Design)でエンティティを同一テーブルに格納するか、非正規化して冗長に持つ。

4. 読み書きキャパシティの設計

RCU(Read Capacity Unit): 1RCU = 最大4KBの強い整合性読み取り1回
WCU(Write Capacity Unit): 1WCU = 最大1KBの書き込み1回

→ アイテムサイズが大きいと消費ユニットが増える
→ バースト時のキャパシティ計算が必要

5. トランザクションの制約

# DynamoDB TransactWrite: 最大100アイテム、同一リージョン内のみ
dynamodb.transact_write(
    TransactItems=[
        {'Put': {'TableName': 'Orders', 'Item': order}},
        {'Update': {'TableName': 'Inventory', 'Key': ...}},
    ]
)
# 異なるリージョンをまたぐトランザクションは不可

なぜ重要か / いつ使うか

  • AWSでスケーラブルなサービスを作るとき(DynamoDBは高スケール・低レイテンシが強み)
  • RDB設計の経験しかないエンジニアがDynamoDBプロジェクトに入るとき
  • GSIの設計、テーブル設計の見直し時
  • 「なぜこんなに遅い/高い?」と感じたらScanしていないか確認する