初級読了 3 分

DynamoDB のアイテムコレクション

アイテムコレクション とは、テーブル(またはインデックス)内で同じ の値を共有するすべてのアイテムの集合です。これは 有効にする機能ではなく — キースキーマから生じる創発的な性質です。

2つのアイテムが同じパーティションキーを持った瞬間、それらはコレクションを形成し、その コレクションが、DynamoDB が単一の Query でまとめて読み取れる単位になります。

これを正しく設計すれば、読み取りは1回のラウンドトリップで返ってきます。間違えると、 Scan から抜け出せなくなります。

DynamoDB のアイテムコレクションとは?

DynamoDB のアイテムコレクションとは、同じ の値を 共有するすべてのアイテムの集合で、まとめて格納され、ソートキーで並べられます。これは 有効にする機能ではなく、キースキーマから生じるものです。コレクションは単一の Query が 効率的に読み取る単位であり、一方 Scan はすべてのパーティションを歩きます。

  • コレクションとは単に「同じパーティションキー」です。 同じパーティションキーの値を 持つ2つ以上のアイテムがまとめて格納され、 で並べられます。
  • これは効率的な Query の単位です。 Query は1つのコレクションを読み、Scan は すべてのパーティションを歩きます。パフォーマンスの話はそれがすべてです。
  • ソートキーがなければ、コレクションもありません。 パーティションキーのみのテーブルは キーごとに1アイテムしか保持しません — 集めるものがないのです。
  • 2つの制限が噛みます: が存在する場合のコレクションあたり 10 GB の上限と、 カーディナリティの低いキーによるホットパーティションです。

問題: 関連するアイテムをまとめて読む

各車両が数秒ごとにテレメトリ — 速度、冷却水温度、燃料残量 — をストリーミングする車両群を 運用しているとします。支配的な読み取りは「車両 V-7741 の最近の測定値をくれ」です。

SQL から来ると、vehicle_id 列にインデックスを張り、プランナに仕事をさせるでしょう。 素朴なキーバリューストアには、そんな贅沢はありません。

それは各測定値を孤立したレコードとして扱うので、その問いはテーブル全体をスキャンして フィルタすることを意味します。遅く、高価で、車両群が増えるほど悪化します。

DynamoDB の答えは、「1台の車両のすべての測定値」を 物理的に グループ化された、直接 アドレス指定可能なものにすることです。そのグループ化がアイテムコレクションです。

コレクションが実際に何であるか

DynamoDB はアイテムを パーティション に格納し、各アイテムをそのパーティションキーの ハッシュによってパーティションへルーティングします。同じパーティションキーの値を持つ アイテムはすべてまとめて格納され、ソートキー で並べられます。それらは1つの パーティションで始まりますが、LSI がなければ DynamoDB は大きな、またはホットな コレクションをソートキーの境界で複数のパーティションに分割できます。コレクション全体を 単一のパーティションに固定するのは LSI だけです(だからこそ、以下の 10 GB 上限は LSI 限定なのです)。

AWS デベロッパーガイドはこれを正確にこう名付けています。パーティションキーの値を共有する アイテムは アイテムコレクション であり、まとめて格納され、ソートキーで順序付けられる、と。

これは 2007年の Amazon Dynamo 論文が導入したのと同じ発想 — キーをノードに割り当てる コンシステントハッシュ法 — を、関連するアイテムがディスク上で隣接するようソート次元で 拡張したものです。

隣接して順序付けられているため、DynamoDB は1回のシークで、それらの連続した並びを返します。 これが Query は安く Scan は安くない理由です。Query は単一のコレクションを読み、 Scan はすべてのパーティションを歩くのです。

コレクションを形成するには、 — パーティションキー ソートキー — が必要です。パーティションキーだけでキー付けしたテーブルは、キー値 ごとにちょうど1つのアイテムを持つので、集めるものがありません。

具体例: 車両 → テレメトリ測定値

テレメトリストリームを複合キーでモデル化します。パーティションキーが車両を識別し、 ソートキーは測定値のタイムスタンプで、測定値をタイムスタンプ順に保ちます(既定では昇順。 最新を先頭にするには ScanIndexForward=false を渡します)。

PK (vehicleId)SK (recordedAt)attributes
VEH#V-7741METAplate, model, depotCode
VEH#V-7741TS#2026-06-23T09:00:01ZspeedKph, coolantC, fuelPct
VEH#V-7741TS#2026-06-23T09:00:06ZspeedKph, coolantC, fuelPct
VEH#V-7741TS#2026-06-23T09:00:11ZspeedKph, coolantC, fuelPct
VEH#V-7742METAplate, model, depotCode
VEH#V-7742TS#2026-06-23T09:00:02ZspeedKph, coolantC, fuelPct

ここには2つのコレクションが存在します — 車両ごとに1つです。META アイテム(車両の メタデータ)と V-7741 のすべての測定値が1つのコレクションを形成し、V-7742 のアイテムが 別のコレクションを形成します。

トリックに注目してください。メタデータに、どの TS#... の値よりも前にソートされる ソートキー(META)を与えると、PK = "VEH#V-7741" に対する単一の Query が、車両の プロフィール その測定値をまとめて返します。

これが シングルテーブル設計 の核心にある、親子パターンです。

パーティション · VEH#V-7742META 車両プロファイルTS#09:00:02パーティション · VEH#V-7741META 車両プロファイルTS#09:00:01TS#09:00:06TS#09:00:11

各破線ボックスが1つのアイテムコレクションです。同じパーティションキーで、アイテムは ソートキーで並んでいます。Query はちょうど1つのボックスを読みます。

コレクションにクエリする

コレクションはソートキーで並んでいるので、範囲読み取りが無料で手に入ります。1台の車両の ある10分間に記録された測定値を取り出すには、ソートキーを範囲で区切ります。

# Query
KeyConditionExpression   vehicleId = :v AND recordedAt BETWEEN :from AND :to
ScanIndexForward         false        # newest first

キー条件は、あなたを1つのコレクション(vehicleId = :v)に、次いでその連続したスライス (recordedAt BETWEEN ...)に制限します。DynamoDB はそれらのアイテムだけを読み、それら だけを課金します。メタデータだけ欲しいですか? recordedAt = "META" が単一の META アイテムを取得します。

これらのキー条件や射影式を手で組み立てるのは面倒です。 DynamoDB Expression BuilderKeyConditionExpressionExpressionAttributeNamesExpressionAttributeValues を 代わりに生成するので、予約語やプレースホルダの細かい点に噛まれません。

インデックス上のコレクション

セカンダリインデックスは 独自の キースキーマを持つので、独自の アイテムコレクションを 形成します。

depotCode(パーティション)と recordedAt(ソート)でキー付けしたグローバル セカンダリインデックスを追加すると、「デポ DEP-LON-3 のすべての測定値を新しい順に」が、 そのインデックスのコレクションに対する単一の Query になります — ベーステーブルが さばけない読み取りです。

だからインデックスのタイプが重要なのです。それが、どんなコレクションを形成できるか、 それらがどう振る舞うかを決めます。トレードオフは GSI と LSI を 参照してください。

はっきりした違いが1つあります。ローカルセカンダリインデックス(LSI) はベーステーブルの パーティションキーを共有するので、そのコレクションはベースのアイテムコレクションに 物理的に結び付いています — そしてその結合が、以下のハードな制限を生みます。

噛んでくる制限

アイテムコレクションは強力ですが、キーの形を決める制約が2つあります。

  • 10 GB の LSI 制限。 テーブルに1つ以上の ローカル セカンダリインデックスがあると、 単一のアイテムコレクション — 1つのパーティションキーに対するベースアイテムとその LSI 射影 — は 10 GB を超えられません。超えると、コレクションを成長させる書き込みが ItemCollectionSizeLimitExceededException で失敗し始めます。LSI が ない テーブルには、 そのようなコレクションあたりの上限はありません。まさにこれが、際限なく増え続けるストリーム (止まらないテレメトリ)が LSI に不向きな理由です。コレクションは増える一方だからです。 GSI は独自のパーティションを持つので、この制限を回避します。
  • コレクションは1つのパーティションに存在し、 単一のパーティションは有限のスループットしか持ちません。ある車両(あるいは1つの depotCode)がトラフィックの極端に不均衡な割合を引き寄せると、テーブル全体としては プロビジョンドスループットを十分下回っていても、そのパーティションをホットスポット化 できてしまいます。アダプティブキャパシティ — AWS の「Advanced Design Patterns for DynamoDB」re:Invent ディープダイブで取り上げられています — はホットキーを自動的に隔離 して増強しますが、まったく分散のないキーを救うことはできません。トラフィックが多くの コレクションにファンアウトするよう、カーディナリティの高いパーティションキーを選びましょう。

DynoTable で見る

コレクションの直感を養う最速の方法は、実物を見ることです。DynoTable では、パーティション キーにクエリすると、コレクション全体が連続した、ソートキー順のリストとして描画されます — META アイテムがタイムスタンプ付きの測定値のすぐ前に、画面上に並び、頭の中で再構成する 必要はありません。

DynoTable で 1 つのパーティションキーに対して実行した DynamoDB Query。コレクション内の全アイテムがソートキー順に並びます。
DynoTable で 1 つのパーティションキーに対して実行した DynamoDB Query。コレクション内の全アイテムがソートキー順に並びます。

落とし穴と次のステップ

  • ソートキーがなければ、コレクションもありません。 パーティションキーのみのテーブルは 関連するアイテムをグループ化できません。アイテムをまとめて読む必要があるなら、複合キーが 必要です。
  • LSI コレクションを際限なく成長させないこと。 追記のみのストリームは、10 GB の上限が あるため、LSI ではなく GSI(または時刻でバケット化したパーティションキー)に属します。
  • パーティションキーを分散させること。 コレクションのスケーラビリティは、それが存在 するパーティションと同じだけです。カーディナリティの低いパーティションキーはホット スポットを生みます。
  • Scan ではなく Query に手を伸ばすこと。 コレクションは、関連するアイテムを1回の 的を絞った Query で読めるように存在します。Scan に頼ると、その利点を捨てることに なります — Query と Scan を参照してください。

自分のキースキーマをスケッチし、実際のパーティションキーに対して Query を実行して、 コレクションが順序どおりに返ってくるのを見てください。DynoTable をダウンロード して、テーブルのコレクションを直接探索しましょう。

更新日