進階閱讀時間 3 分鐘

DynamoDB 的儲存內部機制是如何工作的

DynamoDB 是一張雜湊表,其值是一棵棵已排序的樹,散佈在三個 可用區中的多臺機器上。兩個資料結構承擔了幾乎全部的工作:一次對 的雜湊挑出一臺機器,一棵建立在上的 B 樹在其 內部為各項排序。

DynamoDB 是如何儲存資料的?

DynamoDB 把你的資料儲存為一張巨大的分散式雜湊表,其值是一棵棵已排序的 B 樹。一次對的雜湊挑出一個儲存節點;一棵 B 樹在其內部按排序索引鍵為各項排序。每一次寫入都交給一個領導者,由它複製到跨三個可用區的兩個對等節點,並在法定多數(三個副本中的兩個)拿到它之後確認。

  • 分割區索引鍵是被雜湊的,不是被搜尋的。 DynamoDB 對你的 PK 執行一個雜湊 函式,以找到持有該分割區的儲存節點(或多個節點,一旦一個大集合發生 拆分)—— 一次 O(1) 的跳轉,與表大小無關。
  • 排序索引鍵存在一棵 B 樹裡。 在一個分割區內,各項儲存在一棵按 排序索引鍵排序的 B 樹裡 —— 字串按 UTF-8 位元組順序,Number 鍵按數值 順序 —— 這正是為什麼範圍讀取(begins_withbetween)廉價而 Scan 不然。
  • 每一次寫入在法定多數確認後才應答。 一次寫入交給一個領導者,由它 複製到其他可用區的兩個對等節點,並在法定多數(三個 副本中的兩個)拿到它之後確認 —— 永續性在你的 PutItem 返回之前就已買單。
  • 這正是訪問模式規則存在的原因。 先雜湊後走樹,只有在你 按鍵讀取時才快。沒有鍵,就沒有快速路徑 —— 你會退化到掃描那棵樹。

從資料結構說起,而不是從 API

從 SQL 過來,你把一張表想象成磁碟上的行,配一個挑選 索引的查詢規劃器。DynamoDB 沒有規劃器。儲存佈局_就是_那份契約 —— 什麼 快、什麼是陷阱,都直接從兩個結構裡掉出來。

設想一張巨大的分散式對映。鍵是你分割區索引鍵的一個雜湊。 值不是單個項 —— 而是一整棵共享那個分割區索引鍵的項的 B 樹,按排序索引鍵排序。

其他的一切 —— 查詢語義、那些 10 GB 警告、為什麼一個缺失的鍵會逼出 一次 Scan —— 都是那一句話的推論。

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

請求到達時,DynamoDB 對分割區索引鍵的值應用一個內部雜湊 函式。這個雜湊確定性地對映到一個儲存節點 —— 擁有那些項的物理分割區。AWS 把這記錄為 無論表大小如何都能實現常數時間鍵查詢背後的機制。

那就是 O(1) 那一步。一張 10 TB 的表和一張 10 KB 的表,_定位_成本相同: 雜湊、跳轉、搞定。沒有用來找節點的索引掃描,沒有統計資訊,沒有計劃。

難處在其反面。如果你不提供分割區索引鍵,DynamoDB 就 沒有節點可跳 —— 它只好走遍每一個分割區。那是一次 Scan,也正是 O(1) 與讀取整張表之間的差別。

PutItem / GetItemPK=DEVICE#a91Hash(PK)儲存節點(單一分割區)SK 上的 B READING#... 排序

請求雜湊到恰好一個分割區,然後沿著該分割區的 排序索引鍵 B 樹下降到那個項 —— 兩個廉價步驟,而非一次全表遍歷。

在每分割區一棵 B 樹裡為各項排序

在單個分割區內,各項不是一個堆。它們被儲存在一棵以排序 鍵為鍵的 B 樹裡,按字典序排序。一次 B 樹搜尋是 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 樹是有序的,"08:00 到 09:00 之間的所有讀數"就是一次 沿樹下降到起始值再加一次順序遍歷 —— 而不是對該裝置曾傳送過的 每一條讀數做一次過濾。你唯讀取匹配的那個範圍。

那份排序也是為什麼一個 begins_with(SK, "READING#2026-06-23") 查詢快, 而對一個非鍵屬性做過濾則不然。這棵樹能按 SK 定位;它 無法按其他任何東西定位。要安全地組合那些鍵條件,請在 DynamoDB 運算式構建器裡構建它們,而不是 手工拼接字串:

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

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

一個分割區不是一臺機器。每一個都跨三個可用區裡的三個 節點複製 —— 即 2022 年 USENIX ATC DynamoDB 論文裡詳述的那種基於領導者的 法定多數設計(2007 年 Amazon Dynamo 論文是命名與 理念上的祖先,而不是這個複製模型)。

一個節點是該分割區的領導者。一次寫入交給領導者,由它 在本地寫入並複製到它的兩個對等節點。領導者在一個 持久的法定多數節點拿到它之後確認寫入 —— 所以跨可用區的永續性在 你的 PutItem 返回_之前_就已付清。

讀取有得選。一次讀取交給領導者,能看到 最新提交的寫入。一次讀取可以由 三個節點中的任意一個來提供,其中之一可能落後幾毫秒 —— 那正是 你為更廉價、更高可用的讀取所交換的滯後。

最終一致強一致
由誰提供3 個節點中的任意一個僅領導者節點
看到最新寫入也許(小滯後)總是
RCU 成本一半us-east-1 按需模式下每 4 KB 0.5 個 RCU)全額(每 4 KB 1 個 RCU)
可用性更高更低(單個節點)

在按需計費下,強一致讀取一個 2 KB 的項要花 1 個 RCU;同一次讀取走最終一致則是 0.5 個 RCU。在定價計算器裡給你的熱路徑估個價。

同樣的非同步傳播思路,也是為什麼一次 GSI 讀取可能讀到舊值 —— 參見 GSI 是最終一致的

從結構裡讀出規則

幾乎每一條 DynamoDB "規則"都無非是儲存物理學:

  • 總是提供分割區索引鍵。 沒有鍵,就沒有雜湊目標 —— 你是在掃描 整張對映。這是 Query 與 Scan 對比的核心。
  • 把你一起讀取的東西共置在一個分割區索引鍵之下,這樣一次 雜湊加樹遍歷就返回整個項集合。那是 單表設計的基石。
  • 讓分割區保持有界。 一個分割區就是有限一組節點上的一棵 B 樹; 一個失控的熱鍵或一個 LSI 的 10 GB 上限,都是那個物理 分割區的限制。

一旦你看清了先雜湊後 B 樹的形態,那份訪問模式的紀律就不再 顯得任意 —— 你無非是在讓每一次讀取都保持在快速路徑上。

後續步驟

排序索引鍵策略單表設計來對齊結構地建模你的鍵,然後在 DynamoDB 運算式構建器裡組裝實際的運算式。 試用 DynoTable,看著這些讀取針對你自己的表執行, 看清一個鍵條件究竟拉回哪些項。

已更新