進階閱讀時間 3 分鐘

從 Dynamo 論文到 DynamoDB

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

DynamoDB 是基於 Dynamo 論文的嗎?

部分是。DynamoDB 從 2007 年的 Amazon Dynamo 論文取用了它的名字與核心目標——可預測的效能與大規模下的高可用性——並幾乎原封不動地保留了 雜湊的想法。但它是一個不同的、受管的系統:論文中的向量時鐘、gossip 成員管理與可調的讀/寫 quorum 都不見了,被 AWS 自有的內部機制取代。

  • 這篇論文解決的是可用性,不是人體工學。 它的工作是在 假日流量尖峰期間絕不拒絕一次寫入,哪怕代價是回傳一次過時的讀取。
  • DynamoDB 保留了形貌,替換了內部機制。 依鍵的 雜湊來分割、跨 AZ 複寫、水平擴展——但衝突解決的 核心(向量時鐘、gossip、read-repair)都不見了。
  • 你不再調那些旋鈕。 論文中的 NRW 變成了一個 選擇:ConsistentRead 為 true 或 false。其餘由 AWS 掌管。
  • 這個心智模型依然管用。 了解這條血脈能解釋為什麼一次 Scan 是昂貴的、以及為什麼一次 GSI 讀取可能落後——這兩者都源自原始的設計。

這篇論文實際上在解決什麼

Amazon 的購物車不能宕機。一個在負載下拒絕 寫入——或在複本失效時阻塞——的關聯式資料庫是不可接受的。2007 年的 Dynamo 論文選擇了可用性優先於一致性:永遠接受寫入, 之後再調解分歧。那個取捨是底下一切的根源。

要在沒有單一主節點的情況下做到這點,Dynamo 必須自行回答兩個問題: 一個鍵住在哪裡,以及在一次讀取或寫入算數之前必須有多少個副本達成一致?

一致性雜湊:一個鍵住在哪裡

這篇論文把每個節點放在一個雜湊環上。一個鍵的位置是它 鍵的雜湊值;它由順時針方向的下一個節點擁有,並複寫到接下來的 N-1 個節點。新增或移除一個節點只會重新洗牌它鄰居的鍵——而非 整個資料集。那就是一致性雜湊,也是 DynamoDB 幾乎原封不動保留的 那一個想法。

DynamoDB 仍會雜湊你的 來決定哪個實體 partition 儲存 該項目。挑一個低基數的 partition key——比如只有兩個值的 STATUS—— 每個帶相同值的項目都會落在同一個 partition。那就是的 自傷武器,也是這個環的直接後果:雜湊會把相同的鍵送到相同的家。

Quorum:必須有多少副本達成一致

論文的第二個旋鈕是一個 quorum。有 N 個複本時,一次寫入在 W 個複本回應後就算成功,而一次讀取會諮詢其中 R 個。設 R + W > N,任何讀取 都會與至少一個持有最新寫入的節點重疊——就是強一致性。設得 更低,你就是在拿新鮮度換取速度與正常運行時間。

Dynamo 執行的是「鬆散的」quorum:如果一個目標節點宕機,寫入會送到一個 替身,稍後再交回(hinted handoff)。衝突的版本會以 向量時鐘標記,並在讀取時由應用程式調解。

DynamoDB 保留了什麼、又改了什麼

DynamoDB 繼承了目標與分割方式,然後刪掉了那些讓 原始系統難以營運的部分。

面向2007 年 Dynamo 論文今日的 DynamoDB
鍵的擺放一致性雜湊環partition key 的雜湊 → 受管 partition
複寫N 個節點,由你選擇跨 AZ 的 3 個副本,由 AWS 固定
一致性旋鈕RW quorum 調校一個旗標:ConsistentRead
衝突解決向量時鐘、讀取時應用端合併Region 內不需要——寫入透過一個領導者複本序列化;僅在 global tables 中跨 Region 時採最後寫入者勝出
成員管理對等節點之間的 gossip 協定完全受管;對你不可見
多鍵操作無——純粹的 key-valueQuery、GSI、交易疊加於其上

論文的 API 是兩個呼叫:get(key)put(key, value)。DynamoDB 在 相同的 key-value 核心之上加了一個 sort key、索引與查詢——這正是為什麼一次 Query 是便宜的(一個 partition)、而一次 Scan 不是(它會走遍這個環 曾經建立過的每一個 partition)。

一次寫入如何遊歷,今昔對照

下圖對照論文的 quorum 寫入與 DynamoDB 的受管寫入。 形貌相似;責任從你的程式碼轉移到了 AWS。

論文:N、R、W 由你調校DynamoDB:固定 3 AZ 副本put(key, value)將索引鍵雜湊到環上寫入 N 個複本收到 W 個確認?讀取時以向量時鐘調和領導者序列化寫入,法定人數隱藏

在論文中,你擁有 quorum 的計算與合併;在 DynamoDB 中,那整個下 半部都是受管的,你只在每次請求時選擇 ConsistentRead

這條血脈在哪裡滲進你的程式碼

最終一致的預設就是這篇論文在透出來。一個全域次要 索引是非同步複寫的,所以一個剛寫入的項目可能會有那麼一瞬間 從索引中缺失——同樣是「稍後再調解」的交易,只是發生在索引 層。關於那個延遲何時重要,參見 GSI 與 LSI

你有兩種方式買回強一致性。在一次基礎表格讀取上使用 ConsistentRead: true (它會路由到領導者副本),或用一個 ConditionExpression 守護一次寫入, 讓它只在項目的目前狀態相符時才落地。在 DynamoDB 運算式建構器 中草擬一個——例如 attribute_not_exists(PK),讓一個 PutItem 成為僅限插入的操作, 也就是這篇論文的衝突偵測的現代替身。

唯一要記住的一件事

這篇論文為了「永不對一次寫入說不」而最佳化。DynamoDB 繼承了那個 偏好,這正是為什麼它的預設偏向可用性、以及為什麼強一致讀取代價更高。為 單一 partition 的 Query 建模你的鍵,就如 單表設計 所述, 並只在你真的別無選擇時才動用 Scan——這個環 讓一次整表走訪跟聽起來一樣昂貴。

試用 DynoTable,瀏覽你的表格與它們的 GSI,然後在 SQL Workbench 中對你自己的資料執行 JOIN 與 GROUP BY。

已更新