本章摘要: 統合全書概念,探討如何透過事件日誌整合多個專用資料系統、以端到端操作識別碼和冪等性保障正確性,並反思資料倫理——演算法偏見、隱私監控與工程師的社會責任。

本章是全書的收束,將前面所有章節的概念統合起來,展望資料系統的未來方向。作者首先探討如何整合多種資料系統,接著討論「拆分資料庫」的設計思路,然後深入端到端正確性的保障機制,最後提出資料倫理的反思——身為工程師,我們有責任思考技術對人類社會的影響。

資料整合#

在現實世界中,沒有一種工具能同時滿足所有使用場景。應用程式不可避免地需要組合多個專門化的軟體來提供完整功能,例如同時使用 OLTP 資料庫與全文搜尋索引。資料整合的挑戰在於:如何在多個資料系統之間保持資料一致。

結合專用工具來衍生資料#

當同一份資料需要存在於多個儲存系統中(資料庫、搜尋索引、快取、數據倉儲、機器學習系統等),你必須清楚定義輸入與輸出的流向:資料首先寫入哪裡?哪些表示是從哪個來源衍生的?

最可靠的做法是透過 Change Data Capture (CDC) 或事件日誌,讓所有更新都經過一個決定順序的系統:

  • 寫入 系統記錄資料庫 (system of record),作為單一真相來源
  • 捕獲該資料庫的變更,按相同順序套用到搜尋索引等衍生系統
  • 由於衍生系統完全來自系統記錄,可以確保兩者一致

若允許應用程式直接同時寫入多個儲存系統(dual writes),會產生競爭條件:兩個客戶端的併發寫入可能被不同系統以不同順序處理,導致永久不一致。

衍生資料 vs. 分散式交易#

衍生資料系統與分散式交易從不同角度解決同一個問題:

  • 分散式交易透過鎖和原子提交確保一致性,提供線性化 (linearizability) 保證
  • 衍生資料系統透過事件日誌決定順序,依賴確定性重試和冪等性 (idempotence)

兩者最大差異在於:分散式交易通常提供線性化保證(如 read-your-own-writes),但效能差、容錯性低(XA 協定的缺陷在前面章節已討論過)。事件日誌式衍生資料則是非同步的,不預設提供即時一致性,但更健壯、更具擴展性。

全序的限制#

在小型系統中,建構全序事件日誌完全可行(單一 leader 複製就是這麼做的)。但隨著規模擴大,限制開始浮現:

  • 單一節點的吞吐量上限:分區後不同分區之間的事件順序變得模糊
  • 跨地理區域的資料中心:每個區域各有 leader,跨區域同步代價太高
  • 微服務架構:各服務獨立擁有狀態,跨服務事件沒有定義好的順序
  • 離線優先的客戶端應用:客戶端和伺服器看到的事件順序很可能不同

在形式化理論中,決定事件全序等價於共識 (consensus),而目前的共識演算法尚無法擴展超過單一節點的吞吐量。

捕獲因果關係#

當事件之間沒有因果關聯時,缺乏全序不是問題——併發事件可以任意排列。但因果依賴有時會以微妙的方式出現。例如在社群網路中,使用者先解除好友關係再發送抱怨訊息,若這兩個事件分別存在不同系統中,順序可能丟失,導致通知被錯誤地發送給前好友。

可能的應對策略包括:

  • 邏輯時間戳提供不需協調的全序,但接收端需處理亂序事件
  • 記錄使用者決策時看到的系統狀態,為後續事件建立因果依賴的錨點
  • 衝突解決演算法可處理亂序送達的事件,但無法回收已送出通知等外部副作用

批處理與串流處理的統一#

批處理串流處理本質上共享許多原則,主要差別在於串流處理的資料集是無界的。兩者正在趨向融合:Spark 透過微批次在批處理引擎上實作串流,Flink 則在串流引擎上實作批處理。

統一兩者所需的關鍵特性:

  • 能將歷史事件重播到與處理即時事件相同的引擎
  • 串流處理器的 exactly-once 語義——確保輸出與無故障情況相同
  • 事件時間而非處理時間做視窗化,因為重播歷史資料時處理時間毫無意義

Lambda 架構提出同時運行批處理(精確但慢)和串流處理(快速但近似),再合併結果。這個想法推廣了「衍生不可變事件的視圖」原則,但維護兩套邏輯的成本太高。現代框架已能在單一系統中實現批串流統一,無需承受 Lambda 架構的額外複雜度。

拆分資料庫#

在最抽象的層級,資料庫、Hadoop 和作業系統都在做同一件事:儲存資料並允許查詢處理。Unix 和關聯式資料庫分別代表了兩種截然不同的哲學:Unix 提供接近硬體的低階抽象,關聯式資料庫則提供高階宣告式抽象。

組合資料儲存技術#

資料庫內建了許多功能:二級索引、物化視圖、複製日誌、全文搜尋索引。在第十和十一章中,我們看到了類似的主題:用批處理和串流處理建構搜尋索引、維護物化視圖、複製變更到衍生系統。

建構索引的過程與設定新的 follower 副本驚人地相似:掃描一致性快照 → 挑出要索引的值 → 排序 → 寫出索引 → 處理快照後的寫入積壓 → 持續維護。這就是 CREATE INDEX 的本質——重新處理現有資料集,衍生出新的視圖

兩種組合不同儲存系統的途徑:

  • 聯邦式資料庫 (federated database):統一讀取。提供統一的查詢介面橫跨多種底層儲存引擎,例如 PostgreSQL 的 foreign data wrapper
  • 拆分式資料庫 (unbundled database):統一寫入。透過 CDC 和事件日誌確保所有資料變更流入正確的地方,就像把資料庫的索引維護功能拆開來

拆分的目標不是在特定工作負載上與單一資料庫競爭效能,而是透過組合多個資料庫來達成更廣泛的工作負載覆蓋。如果單一工具能滿足所有需求,直接使用它通常是最佳選擇。

基於日誌的整合帶來鬆耦合的優勢:

  • 系統層面:非同步事件串流讓系統整體更能承受個別元件的故障。若消費者變慢或當機,事件日誌可以緩衝訊息,不影響生產者和其他消費者
  • 人員層面:不同團隊可以獨立開發、部署、維護各自的元件,事件日誌提供足夠強的介面(順序性和持久性),同時又足夠通用

圍繞資料流設計應用#

這種拆分資料庫的做法也被稱為 “database inside-out” 模式。試想試算表的運作方式:當某個儲存格的值改變,所有依賴它的公式會自動重新計算。這正是我們在資料系統層級想要的效果——VisiCalc 在 1979 年就有的功能,至今多數資料系統仍未完全實現。

應用程式碼作為衍生函式:二級索引的衍生函式很標準(CREATE INDEX),但全文搜尋索引需要語言處理、機器學習模型需要特徵工程、快取需要了解 UI 結構。這些應用特定的衍生邏輯必須用自訂程式碼處理,而傳統資料庫對此支援不佳。

狀態與應用程式碼的分離:現代 Web 應用通常將無狀態的應用邏輯和有狀態的資料管理分開。資料庫作為可變共用變數被同步存取——但不像試算表,讀取者不會被主動通知變更。作者認為應該翻轉這個關係,從被動輪詢轉向主動訂閱資料流。

寫入路徑與讀取路徑#

Figure 12-1: 在搜尋索引中,寫入(文件更新)與讀取(查詢)的交匯

衍生資料集是寫入路徑 (write path)讀取路徑 (read path) 交會的地方:

  • 寫入路徑:資料一寫入就立刻經過批處理和串流處理的多個階段,更新所有衍生資料集。這是預先計算 (eager evaluation)
  • 讀取路徑:只在有人查詢時才發生。這是延遲計算 (lazy evaluation)

索引、快取和物化視圖的本質就是在寫入路徑和讀取路徑之間移動邊界——把更多工作推到寫入時完成,以節省讀取時的開銷。全文搜尋索引就是一個很好的例子:沒有索引意味著寫入路徑輕鬆(不用維護索引),但讀取路徑昂貴(需要逐一掃描所有文件)。

觀察衍生狀態#

離線優先客戶端:行動裝置上的狀態可以視為伺服器端狀態的快取。螢幕上的像素是客戶端模型物件的物化視圖,而模型物件是遠端資料中心狀態的本地副本。

推送狀態變更到客戶端:傳統 Web 頁面只在使用者重新載入時才獲取新資料。透過 Server-Sent Events 和 WebSockets,伺服器可以主動將狀態變更推送到客戶端。這意味著將寫入路徑一路延伸到終端使用者裝置。當裝置離線時,就像日誌消費者重新連線後追補遺漏的訊息一樣。

讀取也是事件:讀取請求也可以表示為事件串流,與寫入事件一起送入串流處理器——這本質上是在進行串流-表連接 (stream-table join)。記錄讀取事件還有助於追蹤因果依賴:我們可以重建使用者在做出決定之前看到了什麼。

端到端正確性#

有狀態的系統(如資料庫)會「永遠記住」東西,因此如果出了錯,影響也可能是永久的。ACID 交易(原子性、隔離性、持久性)已經是四十年來建構正確應用程式的工具,但這些基礎比看起來更脆弱——弱隔離層級造成的混亂就是明證。

Exactly-once 的真正意義#

Exactly-once(或 effectively-once)語義意味著安排計算使最終效果與沒有故障發生時相同,即使操作因為故障而實際重試了。重複處理是一種資料損壞:對客戶收費兩次或將計數器遞增兩次都是不可接受的。

最有效的方法是讓操作具有冪等性 (idempotence):無論執行一次或多次,效果相同。但將非天然冪等的操作變為冪等需要額外工作——維護操作 ID 集合等中繼資料,並在故障轉移時做好隔離。

重複抑制與端到端論證#

TCP 在連線層級有去重機制(序號),但這只在單一 TCP 連線範圍內有效。考慮以下場景:客戶端送出 COMMIT 後在收到回應前斷線,不知道交易是否已提交,若重試可能導致轉帳金額翻倍($22 而非 $11)。

進一步延伸:即使資料庫客戶端與伺服器之間能抑制重複,終端使用者裝置與應用伺服器之間的網路仍然會產生重複。使用者在行動網路上送出 POST 請求後信號消失,瀏覽器會提示「確定要再次提交嗎?」——使用者會選擇是。

解決方案是端到端的操作識別碼 (operation identifier)

  • 由客戶端產生唯一 ID(如 UUID),隨表單一起提交
  • 將此 ID 傳遞到資料庫,利用唯一性約束確保同一 ID 只執行一次
  • 即使在弱隔離層級下,關聯式資料庫通常也能正確維護唯一性約束

這就是 端到端論證 (end-to-end argument) 的體現:低層級的可靠性機制(TCP 去重、Ethernet 校驗碼、WiFi 加密)本身無法提供端到端的正確性保證,但它們仍然有用,因為減少了高層級問題發生的機率。真正的正確性必須在端點(應用程式層級)實現。

強制約束#

在拆分式資料庫的脈絡下,唯一性約束如何實施?

唯一性約束需要共識:在分散式環境中,對於多個併發的相同值請求,系統必須決定接受哪一個、拒絕哪些。最常見的做法是單一 leader 負責所有決定,並可透過按需唯一的值做分區來水平擴展。非同步多主複製被排除,因為不同 master 可能同時接受衝突的寫入。

基於日誌的唯一性保證:串流處理器依序消費日誌分區中的所有訊息。若日誌依據需要唯一的值做分區,處理器可以確定性地決定哪個操作先到。步驟為:

  1. 將使用者名稱請求編碼為訊息,追加到依使用者名稱雜湊分區的日誌
  2. 串流處理器依序讀取,用本地資料庫追蹤已被佔用的名稱
  3. 客戶端監聽輸出串流,等待成功或拒絕訊息

跨分區請求處理#

當操作涉及多個分區(例如轉帳涉及請求 ID 分區、付款方帳戶分區、收款方帳戶分區),傳統做法需要跨分區的原子提交。但透過分區日誌可以達到等效的正確性而不需要原子提交:

  1. 轉帳請求由客戶端給予唯一 ID,追加到依請求 ID 分區的日誌
  2. 串流處理器讀取請求,發出兩條訊息:對付款方的扣款指令和對收款方的入帳指令
  3. 後續處理器消費這些指令,依請求 ID 去重後套用

即使步驟 2 的處理器崩潰重啟並產生重複指令,由於具有確定性,步驟 3 的處理器可以輕鬆用端到端請求 ID 去重。

及時性與完整性#

作者提出將一致性 (consistency) 這個被過度使用的詞拆分為兩個獨立的需求:

  • 及時性 (Timeliness):使用者觀察到的系統狀態是最新的。違反及時性 = 最終一致性——暫時的,等一下就好
  • 完整性 (Integrity):沒有資料損壞——沒有資料遺失、沒有矛盾或虛假的資料。違反完整性 = 永久不一致——不會自行修復

對大多數應用而言,完整性遠比及時性重要。信用卡帳單延遲一天出現(及時性)可以接受,但餘額計算錯誤或款項憑空消失(完整性)則是災難。

事件式資料流系統的有趣特性在於它們解耦了及時性和完整性。透過以下機制的組合,可以在不需要分散式交易的情況下保證完整性:

  • 將寫入操作的內容表示為單一訊息(原子寫入)
  • 從該單一訊息透過確定性衍生函式推導出所有其他狀態更新
  • 將客戶端產生的請求 ID 貫穿所有處理層級,實現端到端去重和冪等性
  • 讓訊息不可變,並允許衍生資料被重新處理,便於從錯誤中恢復

寬鬆約束#

許多實際業務場景其實可以接受暫時違反約束,事後補救即可:

  • 兩人同時註冊相同使用者名稱?通知其中一人選別的名字(補償交易
  • 訂購量超過庫存?追加訂貨、向客戶道歉並提供折扣
  • 航空公司和旅館本來就在超賣——補償流程(退款、升等、安排鄰近旅館)是正常營運的一部分
  • 提款超過餘額?收取透支手續費

這些應用需要完整性(預訂不能消失、帳目不能不平),但不需要即時強制約束。道歉工作流 (apology workflow) 本來就是業務流程的一部分。

避免協調的資料系統#

結合以上觀察,資料流系統可以在不需要協調的情況下提供大部分應用所需的資料管理服務,同時維持強完整性保證。這種避免協調的資料系統 (coordination-avoiding data systems) 可以跨多個資料中心以多主配置運行、非同步複製,具有弱及時性但強完整性。同步協調只在真正需要的地方引入(例如無法恢復的操作前強制嚴格約束)。

信任但驗證#

我們的系統模型假設某些事情可能出錯而某些不會,但現實中更像是機率問題。磁碟上的資料可能靜默損壞、網路校驗碼可能被繞過、硬體記憶體可能因 rowhammer 效應翻轉位元。再加上軟體 bug(作者親眼見過 MySQL 無法正確維護唯一性約束、PostgreSQL 的可序列化隔離出現 write skew)。

稽核 (auditing) 是發現資料損壞的關鍵。成熟的系統會考慮不太可能發生的問題並管理風險——例如 HDFS 和 S3 會在背景持續讀回檔案、與其他副本比對、在磁碟之間搬移以降低靜默損壞的風險。

事件式系統的可稽核性優勢:使用者輸入被表示為單一不可變事件,所有狀態更新都從該事件衍生,衍生過程具有確定性和可重複性。我們可以對事件日誌使用雜湊來檢查儲存完整性,對衍生狀態則重跑批處理和串流處理器來驗證結果是否一致。

加密稽核工具如 Merkle trees(用於 certificate transparency 和分散式帳本)可以高效地證明記錄存在於某個資料集中。這類技術未來有望更廣泛地應用於一般資料系統的完整性檢查。

做正確的事——資料倫理#

在本書的最後一節,作者從純技術層面退後一步,探討構建資料密集型應用的倫理責任。每個系統都為某個目的而建構,每個行動都有預期和非預期的後果。工程師有責任謹慎思考這些後果。

預測性分析的風險#

用資料分析來預測天氣或疾病傳播是一回事,用來預測犯人是否會再犯、貸款申請人是否會違約,則直接影響個人的人生。

被演算法(準確或錯誤地)標記為高風險的人,可能遭受系統性排斥——被拒於工作、航空旅行、保險、租屋、金融服務之外。這種由演算法造成的系統性排斥,被稱為**「演算法監獄」(algorithmic prison)**。在尊重人權的國家,刑事司法系統推定無罪直到證實有罪;但自動化系統可以在無需證據的情況下系統性地排斥一個人。

偏見與歧視#

機器學習模型從資料中推斷規則,但如果輸入資料中存在系統性偏見,系統會學習並放大這些偏見。在種族隔離的社區中,郵遞區號甚至 IP 位址就是種族的強預測因子。以為演算法能從偏見資料中產生公平結果,被諷刺為:

  • 「機器學習就像是偏見的洗錢」——將歷史歧視以數學嚴謹性的偽裝合法化

如果我們希望未來比過去更好,需要的是道德想像力 (moral imagination),而這是只有人類才能提供的。資料和模型應該是我們的工具,不是我們的主人。

責任與問責#

  • 人類犯錯可以被追究責任,受影響者可以上訴
  • 演算法也會犯錯,但誰來負責?自駕車造成事故、信用評分演算法系統性歧視——如果決策受到司法審查,你能向法官解釋演算法如何做出決定嗎?

傳統信用評分基於「你過去的行為如何?」,而預測性分析基於「與你相似的人表現如何?」——這本質上是刻板印象化,基於居住地(種族和社經階層的近似代理變數)來推斷個人。

回饋循環#

推薦系統擅長預測使用者想看的內容,卻可能導致只展示使用者已有的觀點——造成同溫層 (echo chambers),助長刻板印象、假訊息和極端化。

更惡性的是自我強化循環:雇主用信用評分篩選求職者 → 因不幸而信用下降 → 更難找到工作 → 經濟困境加劇 → 信用更差。這是由有毒假設驅動的惡性螺旋,藏在數學嚴謹性和資料的偽裝之後。

隱私與監控#

作者提出一個發人深省的思想實驗:將所有出現 “data” 的地方替換成 “surveillance”

  • 「在我們的監控驅動組織中,我們收集即時監控串流,儲存在監控倉儲。我們的監控科學家使用進階分析和監控處理來衍生新的洞察。」

當使用者的行為被追蹤並記錄為其他活動的副產品,服務不再只是為使用者做事,而是有了自己的利益——這些利益可能與使用者的利益衝突。當服務由廣告收入支撐,使用者的利益退居次位,真正的客戶是廣告主。

同意與選擇自由#

使用者對自己的資料如何被收集和處理所知甚少,大多數隱私政策是在混淆而非闡明。更根本的問題是:

  • 資料擷取是單向過程,不是真正的互惠關係
  • 條款由服務方設定,使用者沒有談判空間
  • 當服務普及到成為「基本社交參與的必要條件」時,不使用不再是真正的選擇——監控變得無法逃避

資料作為資產與權力#

資料被稱為「資料廢氣 (data exhaust)」暗示它是無價值的副產品,但更準確的說法是:對以廣告為商業模式的服務而言,行為資料就是核心資產。批評者更進一步稱資料是「有毒資產 (toxic asset)」:

  • 每次收集資料都必須權衡效益與落入壞人之手的風險
  • 系統可能被犯罪者或敵對情報機構入侵
  • 資料可能被內部人員洩露
  • 公司可能落入不道德的管理層手中
  • 國家可能被不尊重人權的政權接管

「安裝可能有朝一日促成警察國家的技術,是糟糕的公民衛生。」收集資料時不只要考慮今天的政治環境,還要考慮所有可能的未來政府。

工業革命的教訓#

資料之於資訊時代,正如污染之於工業時代。工業革命帶來了經濟成長和生活水準的提升,但也伴隨著空氣污染、水污染、惡劣的工作條件和童工。建立環境保護、工作安全、食品衛生等法規花了很長時間,無疑增加了營商成本——但整個社會從中受益巨大。

引用 Bruce Schneier 的話:「資料是資訊時代的污染問題,保護隱私是環境挑戰。正如我們今天回顧工業時代初期,驚訝於祖先如何在追求工業化的過程中忽視污染,我們的後代也會回顧這個資訊時代的初期,評判我們如何應對資料收集與濫用的挑戰。」

自律與立法#

歐洲資料保護指令要求個人資料只能「為特定、明確和合法的目的收集」,不得以與這些目的不相容的方式進一步處理。但這與 Big Data 最大化收集、組合、探索的哲學直接衝突。

作者認為,技術產業需要一個文化轉變:

  • 停止將使用者視為待最佳化的指標,記住他們是值得尊重、尊嚴和自主權的人
  • 自律我們的資料收集和處理實踐
  • 主動教育終端使用者他們的資料如何被使用
  • 不再永久保留資料,在不再需要時清除
  • 透過密碼學協議而非僅靠政策來強制存取控制

本章小結#

本章統合了全書的核心概念,從資料整合到系統正確性再到倫理責任:

  • 資料整合:透過批處理和事件串流讓資料變更在不同系統之間流動。指定系統記錄,其他資料從中衍生。非同步和鬆耦合提升了系統整體的健壯性
  • 拆分資料庫:將資料庫的元件拆開,用鬆耦合的元件組合成應用。衍生狀態可以被觀察,資料流可以一路延伸到終端使用者裝置
  • 端到端正確性:強完整性保證可以透過非同步事件處理、端到端操作識別碼、冪等性和非同步約束檢查來實現,比分散式交易更具擴展性和健壯性
  • 倫理責任:資料既能行善也能造成重大傷害。身為工程師,我們有責任朝著善待人類、尊重人類的世界努力