リレーションシップ
UI がワークスペースをそのプロジェクトの隣に表示するので JOIN に手を伸ばすが、
DynamoDB にはサーバー側の join 演算子がない。高くつく間違いは、各側を別々の
GetItem 呼び出しで取ることだ。画面上の行が増えるたびに膨らむ N+1 読み取りになる。
DynamoDB のリレーションシップはキー設計に宿る。関連アイテムを同じアイテムコレクションに置いて 1 回の Query で返すか、インデックスが反対側から読むエッジアイテムを書く。
複合ソートキーを使えば必要な部分だけを読め、参照カウントがあればコレクションを数えるために全体を読む必要がなくなる。
このセクションで身につくこと
- 親のパーティションキーの下に子を同居させて一対多をモデル化できる。
- エッジアイテムと反転 GSI で多対多をモデル化し、どちらの側も
Query1 回ずつで相手側を一覧できる。 - 階層をソートキーにエンコードし、1 回の
begins_withクエリで部分木を読める。 - 子の書き込みと同じトランザクションで親にカウントを保持し、それを削除のゲートに使える。
読む順番
- 一対多:1 つのパーティションキーの下の子。他のすべてのリレーションシップが拡張する基本パターン。
- 隣接リスト:フォローグラフをエッジアイテムとして持ち、両方向に 1 ホップずつ読む。書き込みコストとホットなフォロー先も扱う。
- 多対多:学生と講座をエッジアイテムとして持ち、講座の受講者名簿には反転 GSI を使う。
- 複合ソートキー:ソートキーにファイルパスを入れ、1 回の
begins_withクエリで部分木全体を読む。 - 参照カウント:1 つの条件付きトランザクションで正しく保ついいね数と、同じカウントによる削除のゲート。
EntityRef = WS#acme のワークスペースと Detail = PROJ#2026-0042 のプロジェクトがあれば、
1 回の Query でワークスペース画面を描ける。
フォローグラフは各フォローを 1 回だけ保存し、「Bob をフォローしているのは誰か」を GSI から読む。各読み取りは 1 ホップだ。ミリ秒単位のレイテンシーで複数ホップを走査するなら、AWS は Amazon Neptune を勧めている。
学生と講座には受講登録ごとにエッジアイテムが要る。それを省くと、片方向のために Scan
することになる。
ソートキーに root/photos/2026/ のようなパスを入れると、フォルダの部分木が 1 回の
begins_with クエリになる。
親のカウントは削除のゲートにできる。
attribute_not_exists(projectCount) OR projectCount = :zero は、まだプロジェクトが残っているワークスペースの削除を拒否する。
どのパターンも書き込みの手間と引き換えに読み取りを安くする。一覧する頻度とグラフを組み替える頻度のどちらに合うかで選ぶ。
DynoTable で手を動かす
DynoTable をダウンロードし、親/子キーのあるテーブルを開く。親パーティションで Query を実行すると、グリッドがソート順でコレクション全体を返す。
行を右クリックし、キー接頭辞の … で絞り込む を選ぶと、1 つのエンティティ型の行だけを表示できる。
SQL Workbench は、各 join が単純キーのテーブルのパーティションキーか GSI のパーティションキーに当たるとき、別々のテーブルをまたいで JOIN を実行する。自己結合も同じルールに従うため、1 つのシングルテーブル設計の内部では GSI のパーティションキーに当たる。
1 つのテーブル内でも GROUP BY や集計は実行でき、保存したカウントを子と比べるといったことができる。キー設計の検証に使い、欠けたアクセスパターンを取り繕うのには使わない。
アドホックな親/子レイアウトには、シングルテーブル設計プランナー が述べた読み取りから PK/SK の配置を提案する。