この記事は何?
開発でDynamoDBを使用する機会があり、設計の注意点について調べた内容のメモ。
誰向けの記事?
RDS(MySQL, Postgresなど)の経験はあるが、DynamoDBは初めて/初心者の人
はじめに: DynamoDBは「先にアクセスパターンを決めてからスキーマを設計する」データベース
RDSではまずデータを正規化し、あとからSQLで柔軟にクエリを組み立てる。DynamoDBでは逆に、アプリケーションが必要とするクエリを先に洗い出し、そのクエリに最適化した形でデータを格納する。この発想の転換が最も重要であり、RDS経験者が最もつまずきやすいポイントでもある。
背景
RDSでは正規化によりデータ重複を排除し、JOINで柔軟にデータを再構成できる。
しかしDynamoDBにはJOINがなく、クエリの柔軟性が限定されている。RDSの設計手法をそのまま持ち込むと、パフォーマンスやコストの問題に直面する。
DynamoDBの基本概念 — RDSとの用語対応
RDSに慣れている人がまず押さえるべき、DynamoDB固有の概念を整理する。
RDSとの用語マッピング
| RDS(RDBMS) | DynamoDB | 備考 |
|---|---|---|
| テーブル | テーブル | 同じ |
| 行(row) | アイテム(item) | 1件のレコードに相当 |
| 列(column) | 属性(attribute) | ただしスキーマレス。アイテムごとに属性が異なってよい |
| 主キー | パーティションキー(+ ソートキー) | 後述 |
| セカンダリインデックス | GSI / LSI | 後述 |
| SQL | Query / Scan / GetItem / PutItem 等のAPI | SQLは使えない(PartiQLという簡易SQLもあるが基本はAPI) |
テーブル・アイテム・属性
DynamoDBのテーブルはアイテム(RDSでいう行)の集合で、各アイテムは属性(RDSでいう列)の集合。RDSと異なり、主キー以外の属性は事前定義不要で、アイテムごとに異なる属性を持てる。
// PersonIDだけが必須(主キー)。他の属性はアイテムごとに自由
{ "PersonID": 101, "Name": "田中", "Phone": "090-xxxx" }
{ "PersonID": 102, "Name": "佐藤", "Address": {"City": "東京"}, "FavoriteColor": "Blue" }
プライマリキー:パーティションキーとソートキー
DynamoDBのプライマリキーは2種類ある。
1. パーティションキーのみ(単純主キー)
RDSの単一カラム主キーに近い。パーティションキーの値でアイテムの物理的な格納先(パーティション)が決まる。
2. パーティションキー+ソートキー(複合主キー)
RDSの複合主キーに近いが、役割が異なる。同じパーティションキーを持つアイテムは物理的に近くに格納され、ソートキーの昇順で並ぶ。この「同じパーティションキーを持つアイテムの集合」をアイテムコレクションと呼ぶ。
// Musicテーブル: Artist(PK) + SongTitle(SK)
{ "Artist": "YUI", "SongTitle": "CHE.R.RY", "Album": "..." }
{ "Artist": "YUI", "SongTitle": "TOKYO", "Album": "..." } // 同じPKなので近くに格納
{ "Artist": "aiko", "SongTitle": "花火", "Album": "..." } // 別パーティション
この構造により、Artist = "YUI" でQueryすれば、YUIの全曲がソートキー順で効率的に取得できる。RDSの SELECT * FROM Music WHERE Artist = 'YUI' ORDER BY SongTitle に相当するが、JOINなしで高速に動作する。
セカンダリインデックス(GSI / LSI)
テーブルの主キー以外の切り口でデータを検索したい場合に使う。RDSの CREATE INDEX に相当するが、仕組みが異なる。
- GSI(グローバルセカンダリインデックス): テーブルとは異なるパーティションキー・ソートキーを指定できる。実体としてはデータのコピーが別途管理される(RDSのマテリアライズドビューに近い感覚)。テーブルあたり最大20個。
- LSI(ローカルセカンダリインデックス): パーティションキーはテーブルと同じで、ソートキーだけ変える。テーブル作成時にしか定義できない。テーブルあたり最大5個。
データの読み書きオペレーション
| 操作 | RDS相当 | 説明 |
|---|---|---|
GetItem |
SELECT ... WHERE pk = ? |
主キー指定で1件取得 |
Query |
SELECT ... WHERE pk = ? AND sk BETWEEN ... |
パーティションキー指定+ソートキー条件で複数件取得 |
Scan |
SELECT * FROM table |
全件走査。高コストなので基本的に避ける |
PutItem |
INSERT / REPLACE |
1件書き込み(存在すれば上書き) |
UpdateItem |
UPDATE ... WHERE pk = ? |
属性単位で部分更新 |
DeleteItem |
DELETE ... WHERE pk = ? |
1件削除 |
BatchGetItem |
複数SELECT | 最大100件を一括取得 |
TransactWriteItems |
BEGIN; ... COMMIT; |
最大100項目のACIDトランザクション |
キャパシティモード
RDSはインスタンスサイズで性能が決まるが、DynamoDBはテーブル単位でスループットを制御する。
- オンデマンドモード: リクエストに応じて自動スケール。予測しにくいワークロード向け
- プロビジョンドモード: 読み取り/書き込みキャパシティユニット(RCU/WCU)を事前に指定。Auto Scalingと組み合わせ可能
参考: Core components of Amazon DynamoDB / Partitions and data distribution
スキーマ設計の進め方が根本的に異なる
| RDS | DynamoDB | |
|---|---|---|
| 設計の起点 | データの構造(ER図) | アクセスパターン(どんなクエリが必要か) |
| 正規化 | 推奨(3NF等) | 非推奨。積極的にデノーマライズする |
| テーブル数 | エンティティごとにテーブルを作る | できるだけ少なく。シングルテーブル設計が基本 |
| スキーマ変更 | ALTER TABLEで列追加 | スキーマレス。項目ごとに属性が異なってよい |
JOINの代わりにデータの持ち方で解決する
RDSでは正規化してJOINで組み立てるが、DynamoDBではJOINがないため、1回のクエリで必要なデータが全て取れるようにデータを配置する。
| RDSの手法 | DynamoDBでの代替 |
|---|---|
| JOINで複数テーブルを結合 | 関連データを同じパーティションキーの下にまとめる(アイテムコレクション) |
| 外部キーによるリレーション | 複合キー(パーティションキー+ソートキー)で1対多を表現 |
| 多対多の中間テーブル | 隣接リストパターン(adjacency list)+GSI(逆引きインデックス) |
| VIEWや集約クエリ | GSI(グローバルセカンダリインデックス)で別の切り口のクエリを実現 |
キー設計がパフォーマンスの全てを決める
RDSでは主キーは一意性の保証が主目的で、パフォーマンスはインデックスやクエリオプティマイザが担う。DynamoDBではキー設計がデータの物理配置とクエリ性能を直接決定する。
- パーティションキー: カーディナリティの高い属性を選ぶ。偏ると特定パーティションにホットスポットが発生する(RDSのAUTO_INCREMENTのような連番IDは不向き)
-
ソートキー: 範囲検索(
begins_with,between)に使える。階層構造を複合ソートキー(例:COUNTRY#JP#CITY#TOKYO)で表現する - GSI(グローバルセカンダリインデックス): RDSのセカンダリインデックスに近いが、別テーブルのように独自のキースキーマを持つ。追加のクエリパターンに対応する手段
RDS経験者が特に注意すべき点
- 「あとからクエリを追加」が難しい: RDSならSQLを書けば済むが、DynamoDBでは新しいアクセスパターンにはGSI追加やデータ構造の変更が必要になることがある
-
Scanは避ける: RDSの
SELECT * FROM tableに相当するScanは全件読み取りで高コスト。Queryで取れる設計にする - トランザクションは限定的: DynamoDBにもTransactWriteItems/TransactGetItemsがあるが、1回のトランザクションで操作できるのは100項目まで。RDSのような長時間トランザクションは不可
- アイテムサイズ上限は400KB: 大きなデータはS3に置いて参照を保持するパターンが推奨
- テーブル数の上限はアカウントあたり10,000: RDSのように無制限にテーブルを作る設計は合わない