初級読了 4 分

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

アイテムコレクション は、テーブル(またはインデックス)内で同じパーティションキー値を 共有するすべてのアイテムの集合です。オンにする機能ではありません — キースキーマから 創発する性質です。

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

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

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

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

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

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

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

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

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

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

コレクションの実体

DynamoDB はアイテムを パーティション に保存し、パーティションキーをハッシュ化して 各アイテムをパーティションにルーティングします。したがって同じパーティションキー値を持つ すべてのアイテムは同じパーティションに着地し、ソートキー でソートされます。

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

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

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

コレクションを形成するには、複合プライマリキー — パーティションキー ソートキー — が必要です。パーティションキーのみでキーイングしたテーブルは、キー値ごとにちょうど 1アイテムしか持たないため、集める対象がありません。

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

テレメトリストリームを複合キーでモデリングします。パーティションキーが車両を識別し、 ソートキーが計測のタイムスタンプで、コレクション内で計測値を新しい順に保ちます。

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

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

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

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

Partition · VEH#V-7742META 車両プロフィールTS#09:00:02Partition · VEH#V-7741META 車両プロフィールTS#09:00:01TS#09:00:06TS#09:00:11

各破線のボックスが1つのアイテムコレクションです。同じパーティションキー、ソートキーで ソートされたアイテム。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 を超えられません。超えると、コレクションを成長させる書き込みが ItemCollectionSizeLimitExceeded で失敗し始めます。LSI が ない テーブルにはその コレクションあたりの上限がありません。だからこそ、無限に成長し続けるストリーム (止まらないテレメトリ)は LSI に不向きなのです。コレクションは成長するだけだからです。 GSI は自身のパーティションを持つため、この制限を回避します。
  • ホットパーティション。 コレクションはパーティションに住み、単一のパーティションの スループットは有限です。1台の車両(あるいは1つの depotCode)が極端に不釣り合いな トラフィックのシェアを引きつけると、テーブル全体がアンダープロビジョニングされていても そのパーティションをホットスポット化できます。アダプティブキャパシティ — AWS の 「Advanced Design Patterns for DynamoDB」re:Invent ディープダイブで扱われています — はホットキーを自動的に隔離してブーストしますが、まったく広がりのないキーは救えません。 トラフィックが多くのコレクションに分散するよう、カーディナリティの高いパーティション キーを選びましょう。

DynoTable で見る

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

1つのパーティションキーに対する Query で、コレクション内のすべてのアイテムをソートキー順に表示。
1つのパーティションキーに対する Query で、コレクション内のすべてのアイテムをソートキー順に表示。

落とし穴と次のステップ

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

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

更新日