複製 (Replication)

★★★★★Draft

FromDDIASystem Design InterviewGrokking Advanced

📚 From the Books

複製 (Replication)ConceptData Distribution

把同一份資料放到多台機器上,目的有三個:降低延遲(資料靠近使用者)、提高可用性(節點掛了還能服務)、擴展讀取吞吐。難的從來不是複製一次,而是處理資料的變更

🧠 Core Ideas

TIP

三種複製模型,差別在「誰能接受寫入」:單主=只有 leader 收寫、follower 收變更日誌;多主=多個節點都能收寫、彼此互相複製;無主=客戶端直接寫多個副本、靠 quorum 收斂。愈往下走,容錯愈強、一致性保證愈弱。

⚖️ 同步 vs 非同步複製

同步 — 安全但會被慢節點卡住
  • Follower 保證有與 leader 一致的最新副本,leader 掛了資料還在。
  • 但同步 follower 沒回應時,寫入就得阻塞等待——任一節點故障拖垮整體。
  • 實務上幾乎都是 semi-synchronous:一個 follower 同步、其餘非同步;同步的那個變慢就換人。
非同步 — 最常見,但故障會丟資料
  • 即使所有 follower 落後,leader 仍可繼續寫。
  • 代價:leader 故障且無法恢復時,尚未複製出去的寫入永久遺失——即使客戶端已收到成功確認。
  • 讀取擴展(read-scaling)架構在非同步下效果最好,但 follower 讀到的可能是舊資料。

⚖️ 複製延遲的三種一致性問題

非同步 follower 落後時,會冒出三類具體異常,各對應一個一致性保證:

Read-after-write — 讀到自己剛寫的
  • 使用者寫入後立刻讀,卻打到還沒收到該寫入的 follower,看起來像資料消失了。
  • 保證:使用者一定看得到自己提交的資料。
  • 做法:自己可能改過的資料(如個人檔案)從 leader 讀;或記住寫入時間戳,只讀「至少含該時間戳」的副本。跨裝置更難,需集中化的更新時間戳。
Monotonic reads — 不會時間倒流
  • 連續兩次讀取先打到新 follower、再打到舊 follower,導致「先看到新的、又看到舊的」。
  • 保證:看過某時間點的資料後,之後不會讀到更早的。比 strong 弱、比 eventual 強。
  • 做法:讓同一使用者的讀取固定打同一個副本(如依 user ID 雜湊選副本)。
Consistent prefix reads — 因果順序不被打亂
  • 觀察者看到「回答出現在問題之前」——因為問題與回答落在不同分片、複製延遲不同。
  • 保證:一連串有順序的寫入,任何讀取者都以相同順序看到。
  • 做法:讓有因果關係的寫入落在同一個分片。分片資料庫尤其棘手,因為各分片獨立、沒有全域寫入順序。

⚖️ 無主複製:Quorum 與衝突

w + r > n — quorum 的核心不等式
  • n 個副本,寫入要 w 個確認、讀取查 r 個節點。只要 w + r > n,讀取集合必與最新寫入集合重疊,至少讀到一個新值。
  • 常見 n=3, w=2, r=2。寫少讀多可設 w=n, r=1(讀更快,但一個節點掛就寫不了)。
  • 邊界:sloppy quorum、並行寫、寫入部分成功等情況,即使 w+r>n 仍可能讀到舊值——所以 Dynamo 風格多半只求最終一致。
偵測並行寫入 — happens-before 與 version vector
  • 「並行」不看物理時間,看是否互相知道對方:都不知道對方 → 並行。
  • 單主用版本號追因果依賴;多副本無主則要每副本每 key 一組版本號version vector(Riak 稱 causal context)。
  • 合併並行值(siblings)的責任落在應用層;LWW 最簡單但會丟資料,購物車這種場景該取聯集,刪除要用 tombstone。CRDT 可自動合併。
Multi-leader 的寫入衝突
  • 兩個 leader 同時改同一筆,非同步複製時才發現衝突。
  • 策略:避免衝突(同一 key 固定路由到同一 leader);或收斂解決(LWW / 應用層合併 / 保留全部由使用者處理)。
  • 拓撲(環形 / 星形 / 全連接)影響傳播路徑;全連接容錯較好但寫入可能亂序到達,需 version vector 排序。

🔑 Takeaways

✍️ My Notes

No notes yet — jot your takeaways or Q&A here.

📖 Further Reading

🔗 Dive Deeper