進階閱讀時間 3 分鐘

DynamoDB 儲存內部原理如何運作

DynamoDB 是一個雜湊表,它的值是排序樹,散布在三個可用區的 多台機器上。兩個資料結構幾乎做了所有的工作:一個對分區鍵的 雜湊挑選一台機器,一個對排序鍵的 B-tree 在那台機器裡 排序項目。

DynamoDB 如何儲存資料?

DynamoDB 將你的資料儲存為一個巨大的分散式雜湊表,其值為已排序的 B-tree。對分區鍵的雜湊決定儲存節點;B-tree 依排序鍵在節點內排列項目。每次寫入在回應前會同步複製到三個可用區的三個節點。

  • 分區鍵是被雜湊的,不是被搜尋的。 DynamoDB 對你的 PK 跑一個雜湊 函式,找到那唯一一個持有該分區的儲存節點——一次 O(1) 的 跳躍,無論資料表多大。
  • 排序鍵住在一棵 B-tree 裡。 在一個分區之內,項目存在一棵 依 SK 字典序排序的 B-tree 裡,這就是為什麼範圍讀取(begins_withbetween)很便宜,而 Scan 不便宜。
  • 每次寫入在確認之前都打到三個節點。 一次寫入去到一個 leader,由它 同步複製到其他可用區的兩個對等節點——耐久性在 你的 PutItem 回傳之前就買好了。
  • 這就是為什麼存取模式規則存在。 雜湊再走樹只有在你依鍵 讀取時才快。沒有鍵、沒有快路徑——你退回到掃描那棵樹。

從資料結構開始,不是從 API

從 SQL 過來,你把一張資料表想成磁碟上的列,加上一個挑選 索引的查詢規劃器。DynamoDB 沒有規劃器。儲存布局 就是 那份合約—— 什麼快、什麼是地雷,兩者都直接從兩個結構掉出來。

想像一個巨大的分散式 map。鍵是你分區鍵的雜湊。 值不是單一個項目——它是一整棵共用那個 分區鍵、依排序鍵排序的項目 B-tree

其餘一切——查詢語意、那些 10 GB 警告、為什麼一個缺失的鍵會強迫 一次 Scan——都是那一句話的後果。

對分區鍵做雜湊以找到節點

當一個請求抵達,DynamoDB 對分區鍵值套用一個內部雜湊 函式。雜湊確定地對應到一個儲存節點—— 那個擁有這些項目的實體分區。AWS 把這個記載為 無論資料表多大都能常數時間查鍵的背後機制。

那是 O(1) 的那一步。一張 10 TB 的資料表和一張 10 KB 的資料表,定位 的成本 一樣:雜湊、跳躍、完成。沒有索引掃描去找節點、沒有統計、沒有計畫。

陷阱在另一面。如果你不提供分區鍵,DynamoDB 就沒有 節點可跳——它得走遍每個分區。那就是一次 Scan,而它是 O(1) 和讀整張資料表之間的差別。

PutItem / GetItemPK=DEVICE#a91Hash(PK)儲存節點(一個分區)SK 上的 B-treeREADING#... 已排序項目

請求雜湊到剛好一個分區,然後沿那個分區的 排序鍵 B-tree 下降到項目——兩個便宜的步驟,而不是走遍資料表。

在每分區的 B-tree 裡排序項目

在單一個分區之內,項目不是一個 heap。它們被持在一棵依 排序鍵作鍵、依字典序排序的 B-tree 裡。一次 B-tree 搜尋是 O(log n),而 關鍵的是那個 n一個 分區裡的項目,不是整張資料表的。

這就是排序鍵範圍讀取便宜的全部理由。拿一張車隊遙測 資料表,每台裝置的讀數都住在一個分區鍵底下:

PKSK
PK = DEVICE#a91SK = READING#2026-06-23T08:00Z
PK = DEVICE#a91SK = READING#2026-06-23T08:05Z
PK = DEVICE#a91SK = READING#2026-06-23T08:10Z

因為 B-tree 是排序的,「08:00 到 09:00 之間的所有讀數」是一次 到起始值的樹下降,加上一段循序走訪——而不是對那台裝置 曾送出的每筆讀數做篩選。你只讀那段比中的範圍。

那個排序也是為什麼一個 begins_with(SK, "READING#2026-06-23") 查詢很快、 而在非鍵屬性上篩選不快。樹能依 SK seek;它 不能依別的東西 seek。要安全地組合那些鍵條件,在 DynamoDB 運算式建構器裡建立它們,而不是 手動串接字串:

KeyConditionExpression  PK = :pk AND begins_with(SK, :day)

把每次寫入複製到三個可用區

一個分區不是一台機器。每個都跨三個可用區的三個 節點複製——這個設計血統可追溯回 2007 年 Amazon Dynamo 論文的複製模型。

其中一個節點是這個分區的 leader。一次寫入去到 leader,由它 在本機寫入並複製給它的兩個對等節點。leader 在一個 耐久的法定數的節點都拿到它之後才確認那次寫入——所以跨可用區的 耐久性是在你的 PutItem 回傳 之前 就付清的。

讀取有得選。一次強一致讀取去到 leader,看見 最新已提交的寫入。一次最終一致讀取可以由 三個節點中的任一個服務,其中一個可能落後幾毫秒——那就是 你為更便宜、更可用的讀取所換來的延遲。

最終一致強一致
由誰服務三個節點中任一個只有 leader 節點
看見最新寫入也許(小延遲)永遠
RCU 成本一半全額
可用性較高較低(單一節點)

同樣的非同步傳播概念,也是為什麼一個 GSI 讀取可能是過期的——參見 GSI 是最終一致

從結構把規則讀出來

幾乎每一條 DynamoDB「規則」都只是儲存物理學:

  • 永遠提供分區鍵。 沒有鍵、沒有雜湊目標——你就在掃描 整個 map。這是 Query 與 Scan 的核心。
  • 把你一起讀的東西共置 在一個分區鍵底下,這樣單一次 雜湊 + 樹走訪就回傳整個項目集合。那是 單一資料表設計的基礎。
  • 讓分區保持有界。 一個分區是一組有限節點上的一棵 B-tree; 一個失控的熱鍵或一個 LSI 的 10 GB 上限,都是那個實體 分區的限制。

一旦你看見這個雜湊再走 B-tree 的形狀,存取模式的紀律就不再 感覺武斷——你只是讓每次讀取都待在快路徑上。

後續步驟

排序鍵策略單一資料表設計把你的鍵建模成符合這個結構,然後在 DynamoDB 運算式建構器裡組裝出實際的運算式。 試用 DynoTable對著你自己的資料表看著這些讀取執行, 並確切看見一個鍵條件拉回哪些項目。

已更新