DynamoDB 儲存內部原理如何運作
DynamoDB 是一個雜湊表,它的值是排序樹,散布在三個可用區的 多台機器上。兩個資料結構幾乎做了所有的工作:一個對分區鍵的 雜湊挑選一台機器,一個對排序鍵的 B-tree 在那台機器裡 排序項目。
DynamoDB 如何儲存資料?
DynamoDB 將你的資料儲存為一個巨大的分散式雜湊表,其值為已排序的 B-tree。對分區鍵的雜湊決定儲存節點;B-tree 依排序鍵在節點內排列項目。每次寫入在回應前會同步複製到三個可用區的三個節點。
- 分區鍵是被雜湊的,不是被搜尋的。 DynamoDB 對你的
PK跑一個雜湊 函式,找到那唯一一個持有該分區的儲存節點——一次 O(1) 的 跳躍,無論資料表多大。 - 排序鍵住在一棵 B-tree 裡。 在一個分區之內,項目存在一棵
依
SK字典序排序的 B-tree 裡,這就是為什麼範圍讀取(begins_with、between)很便宜,而Scan不便宜。 - 每次寫入在確認之前都打到三個節點。 一次寫入去到一個 leader,由它
同步複製到其他可用區的兩個對等節點——耐久性在
你的
PutItem回傳之前就買好了。 - 這就是為什麼存取模式規則存在。 雜湊再走樹只有在你依鍵 讀取時才快。沒有鍵、沒有快路徑——你退回到掃描那棵樹。
從資料結構開始,不是從 API
從 SQL 過來,你把一張資料表想成磁碟上的列,加上一個挑選 索引的查詢規劃器。DynamoDB 沒有規劃器。儲存布局 就是 那份合約—— 什麼快、什麼是地雷,兩者都直接從兩個結構掉出來。
想像一個巨大的分散式 map。鍵是你分區鍵的雜湊。 值不是單一個項目——它是一整棵共用那個 分區鍵、依排序鍵排序的項目 B-tree。
其餘一切——查詢語意、那些 10 GB 警告、為什麼一個缺失的鍵會強迫
一次 Scan——都是那一句話的後果。
對分區鍵做雜湊以找到節點
當一個請求抵達,DynamoDB 對分區鍵值套用一個內部雜湊 函式。雜湊確定地對應到一個儲存節點—— 那個擁有這些項目的實體分區。AWS 把這個記載為 無論資料表多大都能常數時間查鍵的背後機制。
那是 O(1) 的那一步。一張 10 TB 的資料表和一張 10 KB 的資料表,定位 的成本 一樣:雜湊、跳躍、完成。沒有索引掃描去找節點、沒有統計、沒有計畫。
陷阱在另一面。如果你不提供分區鍵,DynamoDB 就沒有
節點可跳——它得走遍每個分區。那就是一次 Scan,而它是
O(1) 和讀整張資料表之間的差別。
請求雜湊到剛好一個分區,然後沿那個分區的 排序鍵 B-tree 下降到項目——兩個便宜的步驟,而不是走遍資料表。
在每分區的 B-tree 裡排序項目
在單一個分區之內,項目不是一個 heap。它們被持在一棵依
排序鍵作鍵、依字典序排序的 B-tree 裡。一次 B-tree 搜尋是 O(log n),而
關鍵的是那個 n 是 一個 分區裡的項目,不是整張資料表的。
這就是排序鍵範圍讀取便宜的全部理由。拿一張車隊遙測 資料表,每台裝置的讀數都住在一個分區鍵底下:
| PK | SK |
|---|---|
| PK = DEVICE#a91 | SK = READING#2026-06-23T08:00Z |
| PK = DEVICE#a91 | SK = READING#2026-06-23T08:05Z |
| PK = DEVICE#a91 | SK = 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對著你自己的資料表看著這些讀取執行, 並確切看見一個鍵條件拉回哪些項目。