中級読了 2 分

DynamoDB の複合ソートキー

とは、パーティションキーとソートキーの組み合わせです。 これを強力にする鍵は、ソートキーの 中身 に何を入れるかにあります。階層構造を1本の 区切り付き文字列としてエンコードすれば、単一の Query がソート順でサブツリー全体を 読み取れます — 結合も、再帰も、2 回目の往復もありません。

DynamoDB の複合ソートキーはどう機能するのか?

複合ソートキーは、階層構造を1本の区切り付き文字列 — root/photos/2026/ — に詰め込み、DynamoDB はそれを UTF-8 のバイト順で保存します。レイアウトがすでにツリーと一致しているため、begins_with(SK, "root/photos/") を使った単一の Query がサブツリー全体をパス順で読み取ります。結合も、再帰も、2 回目の往復もなく、連続した へのプレフィックススキャンだけです。

  • ソートキーは単なる ID ではなく、ソート可能な文字列。 パスを詰め込めば — root/photos/2026/ — DynamoDB はそのパーティションのアイテムを自動的に UTF-8 の バイト順で保存します。
  • 区切り文字がプレフィックス一致をサブツリー読み取りに変える。 begins_with(SK, "root/photos/") は、そのフォルダのすべての子孫を 1 回のクエリで返します。
  • ソートキーが対応するのは範囲条件であって、任意のフィルタではない。 使えるのは begins_withbetween>< — 必要な読み取りが Scan ではなくプレフィックスや 範囲になるようにキーを設計しましょう。
  • 区切り文字は要となる存在。 パスセグメント内に現れ得ないものを選ばないと、 無関係な 2 つのブランチが衝突します。

なぜソートキーがすべての勝負どころなのか

SQL から来ると、フォルダツリーは parent_id の自己結合でモデル化し、それを再帰的に たどるでしょう — 階層ごとに 1 クエリです。DynamoDB では、それは結合を持たない キーバリューストアに対する N+1 の地雷になります。

DynamoDB はすべてのアイテムをパーティションキーの下に、そのソートキー順で、文字列なら UTF-8 のバイト順で保存します(AWS: Query key conditions)。 つまりソートキーが パスそのもの であれば、物理レイアウトはすでにツリーと一致します。 読み取りはグラフ走査ではなく、連続したスライスへのプレフィックススキャンになるのです。

これがその転換点です。ソートキーは完全一致で照合する識別子ではありません。ソート可能な アドレスなのです。それを設計すれば、クエリはおのずと決まります。

ファイルシステムのツリーをモデル化する

アカウントごとのファイルツリーを保存するとしましょう。アカウントごとに 1 つのドライブが 自然なパーティションで、その中のパスがソートキーです。

PKSKnode_typebytes
DRIVE#a91root/folder-
DRIVE#a91root/docs/folder-
DRIVE#a91root/docs/taxes.pdffile88210
DRIVE#a91root/photos/folder-
DRIVE#a91root/photos/2026/folder-
DRIVE#a91root/photos/2026/beach.jpgfile284910
DRIVE#a91root/photos/2026/sunset.jpgfile512004

ここで働いている、オリジナルの取り決めが 2 つあります。

  • PK = DRIVE#<account> は、1 つのアカウントのツリー全体を単一の に収めるため、どんなサブツリー読み取りも単一パーティションの Query になります。
  • SK は完全なパスで、フォルダには末尾に / を付けます。この末尾スラッシュは 意図的なものです — フォルダを自身の子より にソートさせ、root/photos/root/photos という名前の兄弟ファイルと区別できるようにします。

サブツリーを 1 クエリで読み取る

root/photos/ 配下のすべて — フォルダ、サブフォルダ、ファイルを再帰的に — 一覧します。

Query
KeyConditionExpression = PK = :drive AND begins_with(SK, :prefix)
:drive   = "DRIVE#a91"
:prefix  = "root/photos/"

これは root/photos/root/photos/2026/beach.jpgsunset.jpg を — パス順で、 1 回の課金読み取りで返します。ドライブ全体ではなく、そのスライス内のアイテム分だけを 支払います。

DynoTable では、まさにこの begins_with クエリをパスのソートキーに対して実行すると、 フォルダとその子孫がパス順で返ってきます — 手書きすべきプレースホルダー構文はありません。

自分のコード用に生の KeyConditionExpression(名前、値、begins_with)が必要ですか? DynamoDB 式ビルダー で組み立ててコピーできます。

DynoTable でパスのソートキーに begins_with クエリを実行し、フォルダとその子孫をパス順で返している様子。
DynoTable でパスのソートキーに begins_with クエリを実行し、フォルダとその子孫をパス順で返している様子。

サブツリー全体ではなく 1 階層だけを一覧する

begins_with再帰的 な読み取りを与えます。非再帰的なディレクトリ一覧 — root/photos/ の直下の子だけで、それより深いものは含めない — が欲しい場合は、 depth(深さ)属性を保存してソートキーの範囲とフィルタを加えるか、パスを parent GSI に分割します。最もシンプルな形は、parent 属性(root/photos/)を 保持し、それをキーにした GSI を用意することです。

要点はこうです。ソートキーは プレフィックス範囲 の問いに安く答えます。 「直下の子だけ」は別の問いです — FilterExpression が効率化してくれると期待するのでは なく、明示的にモデル化しましょう。フィルタは読み取りの に走り、捨てられるアイテムの 分も支払うことになります。

区切り文字を慎重に選ぶ

区切り文字はデータ契約の一部です。ルールは 2 つ。

  • パスセグメント内に決して現れてはいけない。 ファイル名に / が含まれ得るなら、 / は誤った区切り文字です — a/b という名前のファイルは、b を保持する a という フォルダと区別できません。予約バイト(# や制御文字を使うチームもあります)を選び、 セグメント内では禁止しましょう。
  • 境界でのソート順に注意する。 /(0x2F)は数字や文字より前にソートされ、 これは通常ツリー順として望ましいものです。区切り文字を変えれば順序も変わります — 実データに対して検証しましょう。

複合ソートキー vs. 独立したソート属性

複合ソートキー(root/photos/2026/xプレーンな ID ソートキー + parent 属性
サブツリー読み取り1 回の begins_with クエリ再帰クエリ(N+1)または GSI 走査
順序パス順、無料明示的なソート属性を追加する必要あり
移動 / リネームすべての子孫を書き換えparent ポインタを 1 つ更新
直下の子の一覧depth 属性か GSI が必要自然(parent = x

複合キーが勝つのは、読み取りが サブツリー型で順序が重要 な場合です。フラットな ID モデルが勝つのは、ツリーが絶えず変化する場合です。読み取り中心の階層のほとんど — ファイルツリー、カテゴリツリー、組織図 — は複合キー寄りです。

落とし穴と次のステップ

  • キーに詰め込みすぎない。 エンコードするものはすべて不変で、プレフィックスでのみ インデックスされます。等価で照合したい属性は、それ専用のフィールドか GSI に置くべきで、 ソートキーに押し込むものではありません。
  • ソートキーは任意の WHERE を扱えない。 使えるのは begins_withbetween、 比較だけです。FilterExpression に手を伸ばしていることに気づいたら、おそらくキーの モデル化を誤っています — Query と Scan を参照。
  • キー設計をさらに深掘りする なら シングルテーブル設計 にあります。サブツリー読み取りにベーステーブルではなくインデックスが必要な場合は GSI と LSI を参照してください。

式ビルダーbegins_with キー条件を組み立て、 DynoTable をダウンロード して、これらのプレフィックスクエリを自分の テーブルに対して実行し、サブツリーがパス順で返ってくる様子を見てみましょう。 (そして SQL に置いてきた自己 JOIN は? 必要になれば DynoTable の SQL Workbench が今でも実行してくれます。)

更新日

この設計をインタラクティブに試す

無料の DynamoDB シングルテーブル設計ツールでエンティティとアクセスパターンをスケッチしてみてください — PK/SK キーテンプレートを提案し、アイテムコレクションをプレビューし、どのパターンに GSI が必要かを示します。

シングルテーブル設計ツールを開く