從 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)都不見了。
- 你不再調那些旋鈕。 論文中的
N、R與W變成了一個 選擇: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 固定 |
| 一致性旋鈕 | R、W quorum 調校 | 一個旗標:ConsistentRead |
| 衝突解決 | 向量時鐘、讀取時應用端合併 | Region 內不需要——寫入透過一個領導者複本序列化;僅在 global tables 中跨 Region 時採最後寫入者勝出 |
| 成員管理 | 對等節點之間的 gossip 協定 | 完全受管;對你不可見 |
| 多鍵操作 | 無——純粹的 key-value | Query、GSI、交易疊加於其上 |
論文的 API 是兩個呼叫:get(key) 與 put(key, value)。DynamoDB 在
相同的 key-value 核心之上加了一個 sort key、索引與查詢——這正是為什麼一次
Query 是便宜的(一個 partition)、而一次 Scan 不是(它會走遍這個環
曾經建立過的每一個 partition)。
一次寫入如何遊歷,今昔對照
下圖對照論文的 quorum 寫入與 DynamoDB 的受管寫入。 形貌相似;責任從你的程式碼轉移到了 AWS。
在論文中,你擁有 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。