從 Dynamo 論文到 DynamoDB
2007 年的「Dynamo: Amazon's Highly Available Key-value Store」論文,與你 今天呼叫的 DynamoDB,共用一個名字和一個目標——在任何 規模下都有可預測的效能——但它們不是同一個系統。論文描述的是一個內部的、 最終一致的、你自己跑的儲存。DynamoDB 是一個受管服務,保留了 那些教訓,卻丟掉了大部分機械。
DynamoDB 是以 Dynamo 論文為基礎打造的嗎?
算是一部分。DynamoDB 的名稱與核心目標——大規模下可預測的效能與高可用性——都來自 2007 年的 Amazon Dynamo 論文,而且它幾乎逐字保留了分區鍵雜湊的概念。但它是一個不同的受管系統:論文中的向量時鐘、gossip 成員協定,以及可調的讀取/寫入法定數都已不復存在,取而代之的是 AWS 自有的內部機制。
- 論文解決的是可用性,不是人因好用度。 它的工作是在 假日流量尖峰時永不拒絕一次寫入,哪怕代價是回傳一個過期的讀取。
- DynamoDB 保留了外形,替換了內部。 依鍵的雜湊分區、跨可用區 複製、水平擴展——但那些衝突解決的內臟 (向量時鐘、gossip、讀取修復)都不見了。
- 你不再調那些旋鈕。 論文裡的
N、R和W變成了一個 選擇:ConsistentRead為真或假。其餘 AWS 全包。 - 這個心智模型仍然划得來。 知道這個血統能解釋為什麼一個
Scan昂貴、以及為什麼一個 GSI 讀取會落後——兩者都從原始設計掉出來。
論文實際上在解決什麼
Amazon 的購物車不能掛掉。一個在負載下拒絕寫入——或在 一個失敗的副本上阻塞——的關聯式資料庫,是不可接受的。2007 年的 Dynamo 論文選擇了可用性勝過一致性:永遠接受寫入, 之後再調和分歧。那個交易是底下一切的根。
要在沒有單一主節點的情況下做到那點,Dynamo 得自己回答兩個問題: 一個鍵住在哪裡,以及在一次讀取或寫入算數之前,多少份副本必須一致?
一致性雜湊:一個鍵住在哪裡
論文把每個節點放在一個雜湊環上。一個鍵的位置是它鍵的
雜湊;它由順時針方向的下一個節點擁有,並複製到接下來的 N-1
個節點。增加或移除一個節點只重洗它鄰居的鍵——而不是
整份資料集。那就是一致性雜湊,而它是 DynamoDB
幾乎逐字保留的那一個概念。
DynamoDB 仍然對你的分區鍵做雜湊來決定哪個實體分區儲存
那個項目。挑一個低基數的分區鍵——比方說有兩個值的 STATUS——
那麼每個有相同值的項目都落在同一個分區裡。那就是熱
分區地雷,而它是環的直接後果:雜湊把
相同的鍵送到相同的家。
法定數:多少份副本必須一致
論文的第二個旋鈕是一個法定數。有 N 份副本時,一次寫入在
W 份確認後就成功,而一次讀取會諮詢 R 份。設 R + W > N,那麼任何讀取
就和至少一個持有最新寫入的節點重疊——強一致。把它們
設更低,你就拿新鮮度換速度和正常運行時間。
Dynamo 跑「鬆散」法定數:如果一個目標節點掛了,寫入就去到一個 替身,之後再交回(hinted handoff)。衝突的版本被 用向量時鐘標記,並在讀取時由應用程式調和。
DynamoDB 保留了什麼、又改了什麼
DynamoDB 繼承了那些目標和分區方式,然後刪掉了那些 讓原始系統難以營運的部分。
| 關注點 | 2007 Dynamo 論文 | 今天的 DynamoDB |
|---|---|---|
| 鍵的擺放 | 一致性雜湊環 | 分區鍵的雜湊 → 受管分區 |
| 複製 | N 個節點,你選 | 跨可用區的 3 份副本,由 AWS 固定 |
| 一致性旋鈕 | R、W 法定數調整 | 一個旗標:ConsistentRead |
| 衝突解決 | 向量時鐘、讀取時應用端合併 | 後寫者勝;你選擇加入條件式寫入 |
| 成員資格 | 對等節點間的 gossip 協定 | 完全受管;對你隱形 |
| 多鍵操作 | 沒有——純鍵值 | Query、GSI、交易疊在上面 |
論文的 API 是兩個呼叫:get(key) 和 put(key, value)。DynamoDB 在同一個
鍵值核心上加了一個排序鍵、索引和查詢——這就是為什麼一個
Query 便宜(一個分區)、而一個 Scan 不便宜(它走遍環
曾建立過的每個分區)。
一次寫入如何旅行,那時與現在
下方的流對照論文的法定數寫入和 DynamoDB 的受管寫入。 形狀押韻;責任從你的程式碼挪到了 AWS。
在論文裡你掌管法定數算式和合併;在 DynamoDB 裡整個下半部
是受管的,而你每個請求只選 ConsistentRead。
這個血統在哪裡滲進你的程式碼
最終一致的預設就是論文在透出來。一個 global secondary index 是非同步複製的,所以一個剛寫入的項目可能會有一瞬間從 索引裡缺席——同樣的「之後再調和」交易,只是在索引 層。參見 GSI 與 LSI了解那個落後何時重要。
你有兩種方法買回強一致。在一次基底資料表讀取上用 ConsistentRead: true
(它路由到 leader 副本),或用一個 ConditionExpression 護衛一次寫入,
讓它只在項目當前狀態相符時才落地。在
DynamoDB 運算式建構器裡勾勒一個——例如
attribute_not_exists(PK),讓一個 PutItem 成為一個僅插入的操作,
也就是論文衝突偵測的現代替身。
要記住的那一件事
論文為「永不對一次寫入說不」最佳化。DynamoDB 繼承了那個偏向,
這就是為什麼它的預設偏好可用性、以及為什麼強讀取花更多錢。把
你的鍵建模成單一分區的 Query,就像單一資料表設計裡那樣,
並只在你真的非用不可時才伸手去拿 Scan——環
讓一次整張資料表的走訪如它聽起來那般昂貴。
試用 DynoTable來檢查你的分區、隨需跑一致讀取, 並在你自己的資料表上看著一個 GSI 追上來。