ddia-systems

ddia-systems

熱門

透過理解儲存引擎、複寫、分割、交易與一致性模型來設計資料系統。當使用者提到「資料庫選擇」、「該用哪個資料庫」、「SQL 或 NoSQL」、「複寫延遲」、「分割策略」、「一致性 vs 可用性」、「串流處理」、「ACID 交易」、「最終一致性」、「我的查詢在規模化時變慢」或「跨副本資料不一致」時使用。也適用於選擇資料儲存、設計資料管線或除錯分散式系統一致性問題。涵蓋資料模型、批次/串流處理與分散式共識。如需系統設計,請參閱 system-design。如需韌性,請參閱 release-it。

1717星標
174分支
更新於 2026/7/22
SKILL.md
readonlyread-only
name
ddia-systems
description

透過理解儲存引擎、複寫、分割、交易與一致性模型來設計資料系統。當使用者提到「資料庫選擇」、「該用哪個資料庫」、「SQL 或 NoSQL」、「複寫延遲」、「分割策略」、「一致性 vs 可用性」、「串流處理」、「ACID 交易」、「最終一致性」、「我的查詢在規模化時變慢」或「跨副本資料不一致」時使用。也適用於選擇資料儲存、設計資料管線或除錯分散式系統一致性問題。涵蓋資料模型、批次/串流處理與分散式共識。如需系統設計,請參閱 system-design。如需韌性,請參閱 release-it。

Designing Data-Intensive Applications 框架

一套有原則的方法,用於建構可靠、可擴展且可維護的資料系統。在選擇資料庫、設計綱要、架構分散式系統或推理一致性與容錯時,應用這些原則。

核心原則

資料比程式碼長壽。 應用程式會被重寫,框架來來去去,但資料會持續數十年——優先考慮資料層的長期正確性、持久性與可演化性。大多數應用程式是資料密集型的,而非計算密集型:困難的問題在於資料量、複雜度與變更速率,而明確的一致性/可用性/延遲取捨,是區分穩健系統與脆弱系統的關鍵。

評分

目標:10/10。 根據下方七個快速診斷項目為資料架構評分:每個項目若回答「是」且有證據(經過深思熟慮、有文件記錄的取捨)則給約 1.4 分,若回答「否」或未知則給 0 分。

  • 9-10: 每個領域的選擇——資料模型、儲存引擎、複寫、分割、隔離、衍生資料、故障處理——都是經過深思熟慮、有文件記錄,且符合實際的讀取/寫入/一致性需求;已測試過容錯移轉。
  • 5-6: 核心選擇已做出,但兩到三個診斷項目失敗——例如預設隔離層級未知、熱鍵風險未處理,或容錯移轉未測試。
  • <=3: 選擇基於熟悉度而非需求;忽略了故障模式(複寫延遲、寫入偏斜、熱分割區)且意外複雜度佔主導。

報告當前分數、哪些診斷項目失敗,以及達到 10/10 所需的改進。

DDIA 框架

推理資料密集型系統的七個領域:

1. 資料模型與查詢語言

核心概念: 資料模型塑造了你對問題的思考方式。關聯式、文件式與圖形模型各自施加不同的約束,並啟用不同的查詢模式。

為何有效: 選擇錯誤的資料模型會迫使應用程式碼補償表示上的不匹配,隨著時間增加意外複雜度。

關鍵見解:

  • 關聯式模型擅長多對多關係與臨時查詢;文件模型擅長一對多關係與局部性;圖形模型擅長對互連資料的遞迴遍歷
  • 寫入時綱要(關聯式)能及早捕獲錯誤;讀取時綱要(文件)提供靈活性
  • 多語言持久化——針對不同存取模式使用不同儲存——通常是正確答案
  • 物件-關聯阻抗不匹配是真實成本;文件模型對自包含聚合體可減少此問題

程式碼應用:

情境 模式 範例
具有巢狀資料的使用者個人資料 自包含聚合體的文件模型 個人資料、地址與偏好設定放在一個 MongoDB 文件中
社交網路連線 關係遍歷的圖形模型 Neo4j Cypher:MATCH (a)-[:FOLLOWS*2]->(b) 用於朋友的朋友
需要 JOIN 的財務帳本 參照完整性的關聯式模型 PostgreSQL 在帳戶、交易、分錄之間的外鍵

在選擇關聯式 vs 文件式 vs 圖形模型或評估讀取時綱要時,請參閱 references/data-models.md——包含完整的取捨矩陣與查詢語言比較。

2. 儲存引擎

核心概念: 儲存引擎在讀取效能與寫入效能之間取捨。日誌結構引擎(LSM 樹)最佳化寫入;頁面導向引擎(B 樹)平衡讀取與寫入。

關鍵見解:

  • LSM 樹:僅附加寫入、定期壓縮、出色的寫入吞吐量、較高的讀取放大
  • B 樹:就地更新、可預測的讀取延遲、頁面分裂導致的寫入放大
  • 寫入放大(一次邏輯寫入導致多次實體寫入)對有限寫入週期的 SSD 很重要
  • 列式儲存透過壓縮與向量化處理大幅改善分析查詢
  • 記憶體資料庫之所以快,是因為避免了編碼開銷,而非因為避免磁碟

程式碼應用:

情境 模式 範例
高寫入吞吐量 LSM 樹引擎 Cassandra 或 RocksDB 用於每秒 10 萬次以上寫入的時序資料擷取
混合讀寫 OLTP B 樹引擎 PostgreSQL B 樹索引用於交易性點查詢
分析查詢 列式儲存 ClickHouse 或 Parquet 用於掃描數十億行、少數幾欄

當工作負載受讀取或寫入限制,或必須選擇索引時,請參閱 references/storage-engines.md——包含寫入/讀取路徑圖、壓縮策略、列式儲存,以及基於基準測試的決策程序。

3. 複寫

核心概念: 複寫將資料副本保存在多台機器上,以實現容錯、可擴展性與降低延遲。核心挑戰在於一致地處理變更。

為何有效: 每種複寫策略都在一致性、可用性與延遲之間取捨。明確做出取捨可防止僅在負載或故障下才出現的微妙異常。

關鍵見解:

  • 單領導者:簡單、可能達到強一致性,但領導者是瓶頸與單點故障
  • 多領導者:跨資料中心更好的寫入可用性,但複雜的衝突解決
  • 無領導者:透過法定人數讀寫達到最高可用性,但需要謹慎的衝突處理
  • 複寫延遲導致讀取自己寫入、單調讀取與因果違反
  • 同步複寫保證持久性但增加延遲;非同步複寫在容錯移轉時有資料遺失風險
  • CRDT 與最後寫入勝出以非常不同的正確性保證解決衝突

程式碼應用:

情境 模式 範例
讀取密集的網頁應用 單領導者搭配唯讀副本 PostgreSQL 主節點 + 唯讀副本,後端使用 pgBouncer
多區域寫入 多領導者複寫 CockroachDB 或 Spanner 搭配有限過時性
購物車可用性 無領導者搭配合併 DynamoDB 搭配最後寫入勝出或應用層級的購物車合併

在選擇單/多/無領導者或除錯過時讀取時,請參閱 references/replication.md——包含延遲異常、法定人數計算、衝突解決與 CRDT。

4. 分割

核心概念: 分割(分片)將資料分散到多個節點,每個節點處理一部分,從而實現超越單一機器的水平擴展。

關鍵見解:

  • 鍵範圍分割支援高效的範圍掃描,但可能因順序鍵而產生熱點
  • 雜湊分割均勻分散負載,但破壞排序順序,使範圍查詢成本高昂
  • 本地次要索引需要分散-收集查詢;全域次要索引需要跨分割更新
  • 即使使用雜湊,當單一鍵極受歡迎時仍會發生熱點(名人問題)
  • 重新平衡策略:固定分割數、動態分裂或與節點數成比例

程式碼應用:

情境 模式 範例
時序資料 按時間+來源的鍵範圍分割 (sensor_id, date) 分割以避免當日寫入熱點
大規模使用者資料 基於使用者 ID 的雜湊分割 Cassandra 對 user_id 進行一致性雜湊以均勻分佈
名人/熱鍵問題 鍵分割搭配隨機後綴 為熱鍵附加隨機數字,將讀取分散到 10 個子分割

在進行分片或處理熱鍵時,請參閱 references/partitioning.md——包含重新平衡策略、請求路由,以及本地 vs 全域次要索引的取捨。

5. 交易與一致性

核心概念: 交易提供安全保證(ACID),簡化應用程式碼,讓你在交易範圍內假設故障與並發不存在。

為何有效: 沒有交易,每段應用程式碼都必須處理部分失敗、競爭與並發修改。交易將該複雜度移入資料庫,一次正確處理。

關鍵見解:

  • 隔離層級是一個光譜:讀取未提交、讀取已提交、快照隔離、可序列化
  • 大多數資料庫預設為讀取已提交或快照隔離——而非可序列化——因此你必須了解這允許哪些異常
  • 寫入偏斜:兩個交易讀取相同資料、做出決定並寫入不同記錄——沒有列鎖可以防止
  • 可序列化快照隔離(SSI)樂觀地提供完整可序列化性:無阻塞,但衝突時中止;兩階段鎖定在競爭下會阻塞與死結
  • 分散式交易(兩階段提交)昂貴且脆弱;改為設計單分割操作

程式碼應用:

情境 模式 範例
帳戶餘額轉帳 可序列化交易 BEGIN; UPDATE accounts ... -100 WHERE id=1; UPDATE accounts ... +100 WHERE id=2; COMMIT;
庫存預留 SELECT FOR UPDATE 以防止寫入偏斜 在遞減前執行 SELECT stock FROM items WHERE id = X FOR UPDATE
跨服務操作 Saga 而非分散式交易 扣款、預留庫存;失敗時執行補償退款

在設定隔離層級或追蹤並發錯誤時,請參閱 references/transactions.md——包含各隔離層級的異常表、寫入偏斜範例、2PL vs SSI,以及分散式交易陷阱。

6. 批次與串流處理

核心概念: 批次處理大量轉換有界資料集;串流處理持續轉換無界事件串流。兩者都計算衍生資料。

為何有效: 將記錄系統與衍生資料(快取、索引、具體化視圖)分離,讓各自能獨立最佳化,並在需求變更時從來源重建。

關鍵見解:

  • MapReduce 概念簡單但操作麻煩;資料流引擎(Spark、Flink)以任意 DAG 將其泛化
  • 變更資料捕獲(CDC)將資料庫寫入轉換為下游系統可消費的串流
  • 串流-表格二元性:串流是表格的變更日誌;表格是串流的具體化狀態
  • 恰好一次語義需要冪等操作或交易性輸出
  • 時間視窗(翻轉、跳躍、會話)對於聚合無界串流至關重要

程式碼應用:

情境 模式 範例
每日分析管線 使用 Spark 的批次處理 從 S3 讀取當日事件、聚合、寫入倉儲
即時詐欺偵測 使用 Flink 的串流處理 Kafka 支付事件、基於 5 秒翻轉視窗的規則
同步搜尋索引 變更資料捕獲 Debezium 捕獲 PostgreSQL WAL,Kafka 饋送 Elasticsearch
稽核軌跡/事件重播 事件溯源 儲存 OrderPlacedOrderShipped 事件;透過重播重建狀態

在設計管線或從記錄系統衍生資料時,請參閱 references/batch-stream.md——包含資料流引擎、CDC 接線、視窗化與恰好一次技術。

7. 可靠性與容錯

核心概念: 故障無可避免;失效則非必然。可靠的系統即使個別元件故障,仍能持續正確運作。為故障設計,而非對抗故障。

關鍵見解:

  • 故障是單一元件偏離規格;失效是整個系統停止——容錯防止前者變成後者
  • 硬體故障隨機且獨立;軟體故障相關且系統性(更危險)
  • 人為錯誤是停機的主要原因——最小化犯錯機會,最大化復原能力
  • 超時是基本的故障偵測器,但調整困難:太短導致誤判,太長延遲復原
  • 安全性質(壞事永不發生)必須始終成立;活躍性(好事最終發生)可能暫時違反
  • 拜占庭容錯在區塊鏈之外很少需要;假設崩潰-停止或崩潰-復原

程式碼應用:

情境 模式 範例
服務通訊 超時 + 重試與退避 retry(max=3, backoff=exponential(base=1s, max=30s)) 搭配抖動
領導者選舉 共識演算法(Raft/Paxos) etcd 或 ZooKeeper 用於分散式鎖與領導者選舉
優雅降級 斷路器 Resilience4j:在 10 秒視窗內失敗率達 50% 時開啟電路

在調整超時/重試或加入共識時,請參閱 references/fault-tolerance.md——包含故障分類、超時調整數學、Raft/Paxos 機制,以及安全性/活躍性保證。

常見錯誤

錯誤 為何失敗 修正
根據流行度選擇資料庫 引擎有根本不同的取捨 將儲存引擎匹配實際讀寫模式
忽略複寫延遲 過時讀取、幻讀、遺失更新 實作讀取自己寫入與單調讀取保證
到處使用分散式交易 2PC 慢且脆弱;協調者是單點故障 設計單分割操作;跨服務使用 saga
對所有東西使用雜湊分割 破壞範圍查詢能力 時序資料使用鍵範圍分割;複合鍵實現局部性
假設可序列化隔離 預設較弱;寫入偏斜出現在生產環境 檢查實際預設值;在需要處使用顯式鎖定
混淆批次與串流 錯誤工具增加延遲或浪費複雜度 將處理模型匹配資料有界性與延遲需求
將所有故障視為可復原 損壞與拜占庭故障需要不同處理 分類故障;為每類設計復原策略

快速診斷

問題 若否 行動
你能解釋為何選擇這個資料庫而非其他嗎? 選擇基於熟悉度而非需求 評估資料模型契合度、讀寫比、一致性需求、擴展路徑
你知道資料庫的預設隔離層級嗎? 潛在的並發錯誤 查閱文件;測試寫入偏斜與幻讀
你的複寫策略是否明確選擇? 隱含的一致性/持久性假設 記錄同步 vs 非同步、容錯移轉行為、延遲容忍度
你的系統能否處理熱分割鍵? 一個熱門實體可能拖垮叢集 為熱鍵加入鍵分割或負載捨棄
你是否將記錄系統與衍生資料分離? 每次變更需要遷移所有東西 引入 CDC 或事件溯源以解耦
超時與重試是否經過調整而非使用預設值? 串聯故障或不必要的延遲 測量 p99;將超時設在 p99 以上、串聯閾值以下
你是否在生產條件下測試過容錯移轉? 復原計畫僅是理論 執行混亂實驗:殺死領導者、分割網路、填滿磁碟

延伸閱讀

如需包含詳細圖表與研究參考的完整論述:

關於作者

Martin Kleppmann 是劍橋大學的分散式系統研究員,曾任職於 LinkedIn 與 Rapportive,以 CRDT 與本地優先軟體的研究聞名。他的著作《Designing Data-Intensive Applications》(2017 年)是建構資料系統工程師的權威參考,因使分散式系統概念易於理解且實用而備受讚譽。