📊 設計資料密集型應用

DDIA — Martin Kleppmann 資料在多台機器間如何正確流動

🎯 什麼情境該想到我

當你要評估或設計一個資料/後端系統,想有一套「該顧哪些面向」的總框架時。

⚙️ 怎麼用(三個評估軸)

  1. 可靠性(Reliability):即使硬體/軟體/人為出錯,仍能正確運作。→ 設計容錯,主動注入故障測試。
  2. 可擴展性(Scalability):先定義負載參數(QPS、讀寫比、資料量),再看負載成長時效能如何。用百分位(p95/p99)談延遲,別只看平均。
  3. 可維護性(Maintainability):可運維性、簡單性(控制複雜度)、可演進性。

面對任何系統,逐軸問一遍,就不會漏掉非功能面。

🧪 我實際套用的紀錄

  • 2026-07-15:(待填)

⚠️ 注意

  • 「可擴展」不是布林值;一定要問「隨『哪個負載參數』成長」。

🔗 相關工具

  • 工具-資料複製 —— 可靠性那一面的主要手段,也是引入「讀到舊資料」問題的來源
  • 工具-資料分區 —— 可擴展性那一面的主要手段,單機裝不下時的第一個答案
  • 工具-管理複雜度 —— 可維護性那一面的最高準則,設計取捨拿不定主意時回到它

資料模型與查詢語言 關聯 / 文件 / 圖模型;選模型=選「資料之間關係」的表達方式。

儲存與檢索 LSM-Tree(追加寫、寫優化)vs B-Tree(就地更新、讀優化);OLTP vs OLAP 欄式儲存。

🎯 什麼情境該想到我

當你要改資料格式/schema/API,又擔心「新舊版本同時在線」會壞掉時(滾動升級、多服務並存)。

⚙️ 怎麼用

  1. 顧好雙向相容
    • 向後相容:新程式能讀舊資料。
    • 向前相容:舊程式能讀新資料(忽略不認得的欄位)。
      兩者都要,才能新舊版本共存、滾動部署。
  2. 選編碼格式:JSON/XML(人可讀、無 schema 演進保證)vs Protobuf/Avro/Thrift(緊湊、有明確的 schema 演進規則)。
  3. 加欄位用「可選 + 預設值」,別移除或改動既有欄位的標籤/型別。

🧪 我實際套用的紀錄

  • 2026-07-15:(待填)

⚠️ 注意

  • 「改一個必填欄位的意義」是隱形殺手;演進規則要跟著格式走。

🔗 相關工具

🧱 第一部:資料系統基礎

🎯 什麼情境該想到我

當你想讓資料庫高可用、或用多副本分攤讀取,卻遇到「讀到舊資料」的怪象時。

⚙️ 怎麼用(先選架構,再處理延遲)

  1. 選複製架構
    • 單主(Leader-based):寫主、讀從,最常見;簡單但主是寫入瓶頸/單點。
    • 多主:跨資料中心可寫,但要處理寫衝突。
    • 無主(Dynamo 式):靠 quorum 讀寫。
  2. 同步 vs 非同步:同步保證從庫有最新值但慢/易卡;非同步快但有延遲。
  3. 處理複製延遲的讀取異常
    • 讀自己的寫 → 讀己所寫一致性(read-your-writes)
    • 時光倒流 → 單調讀(monotonic reads)

🧪 我實際套用的紀錄

  • 2026-07-15:(待填)

⚠️ 注意

  • 非同步複製下「主故障切換」可能丟資料;切換策略要想清楚。
  • worker 查從庫可能查不到剛寫的資料 → 見 工具-MQ消費端防禦三原則

🔗 相關工具

🎯 什麼情境該想到我

當單一機器裝不下資料/扛不住流量,需要把資料切分到多台機器(水平擴展)時。

⚙️ 怎麼用

  1. 選分區鍵與策略
    • 按範圍分區:範圍查詢方便,但易產生熱點(如全部照時間寫入同一分區)。
    • 按雜湊分區:分佈均勻、避熱點,但失去範圍查詢能力。
  2. 處理熱點:熱鍵可加隨機前綴打散。
  3. 次級索引:本地索引(分區內,查詢要 scatter/gather)vs 全域索引(寫入要跨分區)。
  4. 再平衡(rebalancing):用固定數量分區等策略,避免資料大搬風。

🧪 我實際套用的紀錄

  • 2026-07-15:(待填)

⚠️ 注意

  • 分區鍵選錯(造成熱點或常需跨分區查詢)事後極難改,設計時最關鍵。

🔗 相關工具

🎯 什麼情境該想到我

當多個請求同時讀寫同一批資料、出現「錯亂/覆蓋/看到中間狀態」的併發問題時。

⚙️ 怎麼用(依你要防的異常挑隔離等級)

  • 讀已提交(Read Committed):防髒讀、髒寫。最低堪用等級。
  • 快照隔離 / 可重複讀(Snapshot Isolation):整個交易看同一份快照(MVCC),防不可重複讀。
  • 可序列化(Serializable):最強,行為等同交易一個個依序執行,防所有異常(含寫偏斜 write skew / 幻讀)。實作:真序列化、2PL、或 SSI。

步驟:先想清楚你要防哪種併發異常,再選剛好夠的等級(越強越慢)。

🧪 我實際套用的紀錄

  • 2026-07-15:(待填)

⚠️ 注意

  • 很多預設是「讀已提交」,擋不住 write skew;涉及金額/庫存等不變式要特別確認等級。
  • 別在交易內做外部 I/O(如發 MQ)→ 見 工具-Transactional-Outbox-Pattern

🔗 相關工具

  • 工具-線性一致性與共識 —— 把同一個問題拉到分散式:單機的隔離等級到了多節點就變成一致性與共識的取捨
  • 工具-防禦式編程 —— 補位:隔離等級擋不掉的併發情境(如應用層的檢查再寫入),要靠這張的心態補上

分散式系統的麻煩 不可靠的網路與時鐘、部分失效、拜占庭故障——節點只能靠訊息推測他人狀態。

🎯 什麼情境該想到我

當你在分散式系統裡糾結「要不要強一致、可用性 vs 一致性怎麼取捨、多節點怎麼對同一件事達成一致」時。

⚙️ 怎麼用

  1. 線性一致性(Linearizability):讓多副本「看起來像只有一份資料」,讀到的一定是最新已寫入值。強、直覺,但成本高。
  2. CAP 取捨:網路分區(P)發生時,只能在一致性(C)與可用性(A)間擇一。分區無法避免 → 你在選 CP 還是 AP。
  3. 共識(Consensus):讓一群節點對某個值達成一致(選主、鎖、原子提交)。用 Raft/Paxos 這類演算法,或直接靠 ZooKeeper/etcd。
  4. 能不要強一致就別要:多數場景用因果一致性/最終一致性即可,成本低很多。

🧪 我實際套用的紀錄

  • 2026-07-15:(待填)

⚠️ 注意

  • 線性一致性很貴且在分區時傷可用性;先問「這個場景真的需要嗎」。

🔗 相關工具

🌐 第二部:分散式資料

批次處理 MapReduce 與資料流引擎:對有界資料集做離線轉換,產出衍生資料。

串流處理 事件流、變更資料擷取(CDC)、事件溯源:把批次的邊界拿掉,變成持續處理。

🔀 第三部:衍生資料