DynamoDB 強一致讀取與最終一致讀取
你更新一個項,立刻把它讀回來,卻拿到了舊值。寫入是成功的——片刻之後同一次讀取就返回了新值。沒有任何東西壞掉:你撞上了 DynamoDB 預設的最終一致讀取,而你可以按請求選擇退出它。
這是 DynamoDB 直接交到你手裡的少數幾個正確性旋鈕之一,而它帶著一個實實在在的代價。用對它意味著要懂每種模式保證什麼、它花費多少,以及在哪裡根本沒有強一致讀取可用。
DynamoDB 中的強一致讀取與最終一致讀取有什麼區別?
最終一致讀取(預設)由任意一個副本提供服務,因此在一次寫入之後它可能短暫返回陳舊資料,但成本只有一半。強一致讀取用 ConsistentRead=true 按請求選擇啟用,會被路由到分割區的主導節點,並且總是反映每一次已提交的寫入——代價是 2 倍的讀取容量。
- 最終一致(預設)——在一次寫入之後可能短暫返回陳舊資料。最便宜的讀取模式。
- 強一致——總是反映讀取之前已提交的每一次寫入。用
ConsistentRead=true按請求選擇啟用。 - 強一致讀取的成本是最終一致的 2 倍。 對於相同的資料,一次強一致讀取消耗的讀取容量是一次最終一致讀取的兩倍。
- 並非處處可用。 你能在基礎表和本地次要索引上拿到強一致讀取。全域次要索引只支援最終一致——無法選擇啟用。
- 預設用最終一致。 只有在讀取你自己剛剛寫入的資料、且差一瞬間的陳舊就會出錯時,才去用強一致。
問題:一次看不到最新寫入的讀取
假設你營運使用者帳戶。一個使用者修改了他的通知郵箱,你的應用寫入了這次更新,確認頁面立刻重新讀取資料以顯示新地址。在預設讀取模式下,那次重新讀取可能落在一個還沒收到變更的副本上——於是使用者看到的是他的舊郵箱,並以為儲存失敗了。
這個視窗很小(通常遠不到一秒)而且會自行關閉。但對於一次寫後立即讀的確認來說,"通常正確"還不夠好。這正是強一致存在的理由。
為什麼會出現最終一致
DynamoDB 把每個分割區儲存在三個儲存節點上——一個主節點和兩個副本——分佈在不同的可用區。一次寫入在落到主節點和一個副本上時就被確認;隨後它非同步地傳播到第三個節點。
為了分攤負載,讀取可以由這三個節點中的任意一個來服務。一次最終一致讀取可能命中一個還沒收到你最近寫入的節點——於是它返回一個略微陳舊的值。一次強一致讀取會被路由到該分割區的主導節點,那裡總是持有最新已提交的資料,因此它絕不會返回陳舊結果。
那個複製延遲就是全部差別所在。它也解釋了 2 倍成本:強一致讀取無法像最終一致讀取那樣在副本間做負載均衡,所以 DynamoDB 按兩倍的容量給它定價。
把成本說具體
讀取以讀取容量單元(RCU)計量,每個 RCU 覆蓋最大 4 KB。對於一個 4 KB 的項,一個 RCU 可買一次強一致讀取或兩次最終一致讀取。所以在一個熱讀取路徑上翻開 ConsistentRead=true 會讓它的讀取成本翻倍——在一個高流量端點上,那是一筆你會注意到的開銷條目。
在把強一致讀取設為預設之前,用 DynamoDB 定價計算器針對你自己的項大小和請求速率把差別建模出來——全面付雙倍代價很少值得。
強一致讀取在哪裡可用(在哪裡不可用)
| 讀取物件 | 是否強一致? |
|---|---|
| 基礎表 | 是——用 ConsistentRead=true 選擇啟用 |
| 本地次要索引(LSI) | 是——與基礎表相同的選擇啟用方式 |
| 全域次要索引(GSI) | 否——僅最終一致,無法覆蓋 |
一個 GSI 維護它自己的一份資料副本,從基礎表非同步複製而來,所以它永遠無法提供強一致讀取。如果某個訪問模式確實需要寫後立即讀,而你原本打算用一個 GSI 來服務它,那就是一個訊號——應該改用基礎表或一個 LSI 來服務它。
陷阱與後續步驟
- 別把強一致讀取設為預設。 大多數讀取能容忍一個亞秒級的陳舊視窗;處處付 2 倍是浪費的開銷。
- 別指望從 GSI 做寫後立即讀。 它在設計上就是最終一致的——參見為什麼 GSI 是最終一致的。
- 事務是強一致讀取。
TransactGetItems始終是強一致的——參見 DynamoDB 事務。 - 一致性與容量相互影響。 那個 2 倍乘數直接關聯到按需與預置的成本規劃。
想在不寫 API 呼叫的情況下探索你的 DynamoDB 表和索引?下載 DynoTable 直接檢視你的資料。
工作 RCU 比較
對基表上同一個 6 KB 項目的兩次讀取:
| 模式 | 4 KB 塊 | RCU 消耗 | 何時使用 |
|---|---|---|---|
| 強烈一致 | 2(6 KB 向上舍入) | 2 RCU | 您自己編寫後的確認螢幕 |
| 最終一致 | 2 | 1 RCU | 儀表板、列表、分析 |
如果每秒讀取 1,000 次,強模式的成本大約是按需模式的兩倍讀取最終模式的支出 — 對增量進行建模
pricing calculator 翻熱之前全域性到 ConsistentRead=true 的路徑。
BatchGetItem consistency
BatchGetItem中的每個表項可以獨立設定ConsistentRead。一個載入使用者設定檔案(強)和相關設定(最終)的儀表板可以在一次批次呼叫中混合標誌——仍然受到每表強讀的影響可用性規則(GSI 不強)。
應用程式程式碼中的先讀後寫
設定檔案更新確認模式:
UpdateItem新電子郵件。- 直接
GetItem與ConsistentRead: true位於基表上。步驟 2 的成本是最終讀取的 RCU 的 2 倍,但保證了確認螢幕與寫入相符。跳過對後臺聚合的強讀取容忍亞秒級的延遲。
DynoTable defaults
DynoTable 中的探索性讀取使用最終一致的基表查詢除非您在高階設定中選擇更強的語義 - 匹配大多數儀表板用例。暫存寫入後,項目編輯器重新整理顯示來自成功響應的承諾值,無需單獨的一致性切換公共路徑。使用 query builder 發出樣本讀取
ConsistentRead 在將 SDK 程式碼複製到需要的服務時顯式設定先寫後讀保證。
全域性表註釋
全域性表跨區域非同步複製。強一致適用
在一個區域的副本內,而不是全球範圍內。寫入us-east-1不是在 eu-west-1 中立即具有很強的可讀性 - 相應地規劃跨區域使用者體驗。有關複製延遲預期,請參閱global tables。