進階閱讀時間 3 分鐘

從 Dynamo 論文到 DynamoDB

2007 年的「Dynamo: Amazon's Highly Available Key-value Store」論文,與你 今天呼叫的 DynamoDB,共用一個名字和一個目標——在任何 規模下都有可預測的效能——但它們不是同一個系統。論文描述的是一個內部的、 最終一致的、你自己跑的儲存。DynamoDB 是一個受管服務,保留了 那些教訓,卻丟掉了大部分機械。

DynamoDB 是以 Dynamo 論文為基礎打造的嗎?

算是一部分。DynamoDB 的名稱與核心目標——大規模下可預測的效能與高可用性——都來自 2007 年的 Amazon Dynamo 論文,而且它幾乎逐字保留了分區鍵雜湊的概念。但它是一個不同的受管系統:論文中的向量時鐘、gossip 成員協定,以及可調的讀取/寫入法定數都已不復存在,取而代之的是 AWS 自有的內部機制。

  • 論文解決的是可用性,不是人因好用度。 它的工作是在 假日流量尖峰時永不拒絕一次寫入,哪怕代價是回傳一個過期的讀取。
  • DynamoDB 保留了外形,替換了內部。 依鍵的雜湊分區、跨可用區 複製、水平擴展——但那些衝突解決的內臟 (向量時鐘、gossip、讀取修復)都不見了。
  • 你不再調那些旋鈕。 論文裡的 NRW 變成了一個 選擇: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 固定
一致性旋鈕RW 法定數調整一個旗標:ConsistentRead
衝突解決向量時鐘、讀取時應用端合併後寫者勝;你選擇加入條件式寫入
成員資格對等節點間的 gossip 協定完全受管;對你隱形
多鍵操作沒有——純鍵值Query、GSI、交易疊在上面

論文的 API 是兩個呼叫:get(key)put(key, value)。DynamoDB 在同一個 鍵值核心上加了一個排序鍵、索引和查詢——這就是為什麼一個 Query 便宜(一個分區)、而一個 Scan 不便宜(它走遍環 曾建立過的每個分區)。

一次寫入如何旅行,那時與現在

下方的流對照論文的法定數寫入和 DynamoDB 的受管寫入。 形狀押韻;責任從你的程式碼挪到了 AWS。

論文:你調 N、R、WDynamoDB:固定 3份可用區副本put(key, value)把鍵雜湊到環上寫入 N 份副本收到 W 份確認?讀取時用向量時鐘調和後寫者勝,法定數隱藏

在論文裡你掌管法定數算式和合併;在 DynamoDB 裡整個下半部 是受管的,而你每個請求只選 ConsistentRead

這個血統在哪裡滲進你的程式碼

最終一致的預設就是論文在透出來。一個 global secondary index 是非同步複製的,所以一個剛寫入的項目可能會有一瞬間從 索引裡缺席——同樣的「之後再調和」交易,只是在索引 層。參見 GSI 與 LSI了解那個落後何時重要。

你有兩種方法買回強一致。在一次基底資料表讀取上用 ConsistentRead: true (它路由到 leader 副本),或用一個 ConditionExpression 護衛一次寫入, 讓它只在項目當前狀態相符時才落地。在 DynamoDB 運算式建構器裡勾勒一個——例如 attribute_not_exists(PK),讓一個 PutItem 成為一個僅插入的操作, 也就是論文衝突偵測的現代替身。

要記住的那一件事

論文為「永不對一次寫入說不」最佳化。DynamoDB 繼承了那個偏向, 這就是為什麼它的預設偏好可用性、以及為什麼強讀取花更多錢。把 你的鍵建模成單一分區的 Query,就像單一資料表設計裡那樣, 並只在你真的非用不可時才伸手去拿 Scan——環 讓一次整張資料表的走訪如它聽起來那般昂貴。

試用 DynoTable來檢查你的分區、隨需跑一致讀取, 並在你自己的資料表上看著一個 GSI 追上來。

已更新