DynamoDB のリクエストルーティングの仕組み
あなたが送るすべての読み取りや書き込みは、まずステートレスなリクエストルーターの一群に到達します。 ルーターは をハッシュ化し、そのハッシュを、そのキーのデータを所有する ストレージノードにマッピングして、リクエストをそこへ転送します。この1ホップこそが、キー参照が テーブルに1000件のアイテムがあっても10億件あっても同じコストで済む理由です。
DynamoDB のリクエストルーティングはどのように機能するのか?
DynamoDB はすべてのリクエストを、 をハッシュ化するステートレスなリクエストルーターの一群を通して振り分けます。ルーターはそのハッシュを、当該パーティションを所有する単一のストレージノードにマッピングし、読み取りや書き込みをそこへ転送します。ルーティングはキーのハッシュの純粋な関数なので、テーブルに1000件のアイテムがあっても10億件あっても、1回の参照コストは同じです。
- リクエストルーターは玄関口。 リクエストを受け取り、パーティションキーをハッシュ化して、そのパーティションを保持する ストレージノードへ振り分ける、ステートレスな一群です — スキャンも、テーブル全体の知識も必要ありません。
- すべてを決めるのはパーティションキー。 ルーティングはパーティションキーのハッシュの純粋な関数です —
同じキーは常に、それを所有するパーティションにルーティングされるため、
GetItemは O(テーブルサイズ) ではなく O(1) です。 - 1つのプライマリ、2つのセカンダリ。 書き込みはパーティションのプライマリノードに到達し、 クォーラム(3つのレプリカのうち2つ)が永続化した時点で確認応答されます。
- 不適切なキーは設計を台無しにする。 カーディナリティの低いキーや キーは トラフィックを1つのノードに集中させます — ルーティングは正常で、問題はあなたのキーです。
ルーティングが解決する問題から始める
SQL から来ると、統計情報を読み、インデックスを選び、時にはスキャンするクエリプランナーを思い浮かべるでしょう。 そのコストは触れるデータ量に応じて増えます。しかしそのモデルは、どんなサイズでも1桁ミリ秒で応答しなければならない キーバリューストアには合いません。
DynamoDB の答えは、単一アイテムの参照を検索ではなく直接アドレス指定にすることです。 パーティションキーはフィルターの対象となる列ではありません — それは、データが物理的にどこに存在するか を 計算するハッシュ関数への入力です。統計情報もプランナーもありません。
これがリレーショナル思考から離れるときに受け入れるトレードオフです。アドホックなクエリの柔軟性を手放す代わりに、 定数時間のアドレス指定を得るのです。
リクエストルーターに出会う
リクエストが到着すると、それは直接ストレージへは向かいません。まずリクエストルーター — サービス全体の前面に立つ、ステートレスで水平方向にスケールする一群 — に到達します。 (USENIX ATC '22 の DynamoDB 論文がこのリクエストルーターの一群を説明しています。)
ルーターは3つのことを行い、自身のデータは一切保持しません。
- IAM に対してリクエストを認証・認可する。
- パーティションキーをハッシュ化して、それを所有するパーティションを見つける。
- そのパーティションのストレージノードへリクエストを転送する。
ルーターはステートレスなので、負荷がかかるとサービスはルーターを追加します。どのルーターもボトルネックにならず、 単一障害点にもなりません — これは 2007年の Amazon Dynamo 論文 が元のシステムを構築する際に据えたのと同じ特性です。
1つの読み取りをルーター越しに追う
ドローン群のテレメトリテーブルを取り上げます。アイテムは DroneId(パーティションキー)と
ReadingTs(ソートキー)でキー付けされ、BatteryPct や AltitudeM といった属性を持ちます。
あなたは1機のドローンの6月23日の測定値を求めます。
PK = "DRONE#A19F"
SK begins_with "2026-06-23"
ルーターがそれをどう扱うかを示します。以下のリード文はリクエストを上から下へ辿ります — 1つの下向きの流れとして読んでください。
ルーターは DRONE#A19F をハッシュ化し、それをそのキーを所有するパーティションにマッピングして、
読み取りをそのパーティションのプライマリストレージノードへ転送し、そのノードがアイテムを返します。
重要な洞察はこうです。ハッシュはテーブルが持ついくつものパーティションのうち 1つ を指します。 ルーターは他のパーティションを一切見ないので、ドローン — そしてパーティション — を追加しても、 この参照は遅くなりません。
パーティションが実際に何であるかを知る
パーティションはストレージとスループットの単位です。それぞれに上限があり
(およそ 10 GB と、固定された読み取り/書き込みキャパシティの一部)、DynamoDB はパーティションが
いずれかの上限を超えると分割します。あるパーティションキーを持つすべてのアイテムは1つのパーティション上で始まります。
熱による分割(split-for-heat)は後でそのコレクションをソートキーの範囲で切り分けることができ
(LSI や単調増加するソートキーがそれを固定していない限り)、これが1つのパーティションキーに対する Query を
今でも安価にしている理由です。
各パーティションは、アベイラビリティーゾーンにまたがる3つのストレージノードに複製されます。 1つのプライマリと2つのセカンダリです。
| ノードの役割 | 処理する内容 | 提供できる整合性 |
|---|---|---|
| プライマリ | すべての書き込み、強い整合性のある読み取り | 強い(自身の最新の書き込みを見る) |
| セカンダリ | 結果整合性のある読み取り、フェイルオーバー | 結果整合性(プライマリに遅れる場合あり) |
書き込みはプライマリへ向かい、プライマリはクォーラム(3つのレプリカのうち2つ)が永続化した時点で 書き込みを確認応答します。 読み取りはプライマリにルーティングされ、 最新の書き込みを反映します。 読み取りは、まだ追いついていない セカンダリによって提供されることがあります — コストは半分、ただし古い可能性があります。
落とし穴を名指しする: ホットパーティションキー
ルーティングの良し悪しはパーティションキー次第です。ハッシュはキーを均等に分散させるので、キーが 高いカーディナリティを持ち、トラフィックが均一であれば、負荷はすべてのノードに分散します。 どちらかの特性を壊すと、ホットパーティションが生まれます。
そのテレメトリを DroneId の代わりに Region でキー付けするとしましょう。すると us-east-1 の
すべてのドローンが1つのパーティションキーを共有します — その結果、それらの読み取りと書き込みは同じ
キースペースのスロットにハッシュ化され、1つのアイテムコレクションに積み上がります。ルーターは完璧に仕事を
こなしています。あなたが群全体を単一パーティションのキャパシティに集中させてしまっただけです。
ルーターがノードを選ぶ様子を見ることはできませんが、うまくルーティングされるキーを設計することは できます。
式ビルダー でキー条件を組み立てるとき、PK = … の左側に置く
パーティションキーが、まさにルーターがハッシュ化する値です — その値を高いカーディナリティに保つことが、
読み取りを別々のノードに乗せ続ける方法です。
これがアクセスパターンとどう結びつくか
リクエストルーティングは、シングルテーブル設計
のルールを譲れないものにしている仕組みです。パーティションキーを中心にモデリングするのは、
パーティションキー こそが アドレスだからです。また、これは Query が Scan に勝る
理由でもあります — Query はルーターを通して1つのパーティションに到達しますが、Scan はすべての
パーティションを順に歩き回ります。
セカンダリインデックスは自身のパーティションと自身のルーティングを持ちます。 GSI は自身のパーティションキーでルーティングされ、ベーステーブルとは独立しています。 これが、テーブルがホットでなくても GSI がホットになりうる理由です。
次のステップ
1つではなく多数のノードにルーティングされるキーを設計しましょう。
式ビルダー で PK = … 条件をスケッチして、どの値が
ハッシュ化されるかを正確に確かめ、それから DynoTable をダウンロード して、自分自身の
テーブルに対してそれらのクエリを実行し、各キー条件が何を返すかを正確に見てみましょう。