背景與問題
Pinterest 的下一代資料擷取框架負責把線上資料庫的異動搬進分析用的資料表。這條管線的組成是 MySQL、TiDB 與 KVStore 三類來源資料庫,透過 Kafka 傳送變更資料擷取(Change Data Capture,簡稱 CDC,意思是持續捕捉資料庫裡每一筆新增、修改、刪除並把它們當成事件流出,讓下游不必再全表掃描)事件,再由 Apache Flink 處理、Apache Iceberg 落成資料湖表格,Spark 則負責後續的 upsert 合併。依 Pinterest 公布的數字,平台支撐 petabyte 級資料與數千條管線,線上異動通常在 15 分鐘到一小時內出現在分析表。
真正的困難在於「一個分區到底好了沒」。過去用批次作業時答案很單純:作業跑完,這個分區就可以給人讀。改成串流擷取之後,變更是持續不斷 commit 進去的,並沒有等價的完成點。偏偏 CDC 的事件時間(event time,指事件實際發生在來源系統的時刻,而非它被管線處理的時刻)可能晚於該分區對應的牆上時鐘區間才抵達,牆上時鐘過了就切分區、就開放讀取,很容易讀到還沒到齊的資料。
核心內容
新機制運作在 Flink 到 Iceberg 的 sink(負責把串流結果寫進 Iceberg 表的元件)裡。每一筆 CDC 記錄進來時,系統先抽出設定好的事件時間欄位;在 Flink checkpoint(把串流狀態做快照以確保可回復的固定時點)期間,收集事件時間的最小值、最大值與百分位數統計。這些統計會寫進 Iceberg 的 snapshot summary(表格每一次提交後留下的中繼資料摘要),其中百分位數用 t-digest 表示分佈,只佔幾百位元組。
Iceberg commit 之後,系統再從最近幾次 checkpoint 的統計算出分區完成水位線(finalization watermark,一個代表「事件時間在這個時刻之前的資料都已到齊」的邊界值)。預設策略是取最近若干次提交的最小事件時間,再向下取整到小時。這個值保證單調遞增(monotonic),晚到的記錄不會把已經發布的水位線往回推,因此下游可以放心把它當成安全可讀的界線。
水位線本身存成 Iceberg 表屬性(table property),下游的 sensor(監控資料是否到位、決定何時啟動後續工作的元件)直接讀表即可,不需要另外架設一組協調服務。Spark 在它的 CDC upsert commit 過程中也會把這份中繼資料帶到基底表。Pinterest 強調:完成與否不再只是作業結束的副產品。
技術脈絡解讀
這套設計等於把兩項既有能力拼起來用:Flink 的 watermark 本來就是用來追蹤亂序串流中事件時間的推進,Iceberg 的 snapshot 則提供持久化的表格中繼資料。Pinterest 把兩者結合,推導出一個表格專屬的完整性訊號,並把它和資料存在一起。Google 架構師 Nitesh Singhal 在 LinkedIn 上點評時特別指出,最精巧之處正是「不回退的安全可讀水位線」。
實作上依賴 Flink-to-Iceberg sink 的擴充點:accumulators(累加器)收集每筆記錄的統計,commit processors(提交處理器)在 Iceberg 提交前後執行完成度邏輯。另有一則 Apache Iceberg issue 在討論擴充 IcebergSink 以支援下游整合,包含自訂可提交的中繼資料,例如水位線資訊。
Salesforce 工程師 Tharun Kumar Reddy Polu 則點出串流與批次的取捨才是關鍵:在不犧牲批次原本提供的保證之下拿到新鮮度,正是核心難題。百分位數策略就是這個取捨的具體形態——允許犧牲一部分完整換取更即時的資料,而預設的最小值策略偏向保守。
影響與限制
對下游而言,最大的改變是判定邏輯變得明確且可查詢:一個表屬性就說清楚「讀到哪裡是安全的」,省掉額外的協調元件,也讓跨管線的下游有統一依據。以 petabyte 級、數千條管線的規模來看,把完成訊號放進資料本身,比維護外部狀態更可控。
限制同樣清楚。事件時間欄位必須事先設定,抽錯欄位水位線就失去意義。取最小值的預設策略偏保守,資料新鮮度會受限於最慢的來源;改用百分位數雖然更快,但換來的是完整性的缺口,需要依場景衡量。此外,這套機制建立在 Iceberg snapshot 中繼資料之上,相關擴充(例如 IcebergSink 支援自訂水位線)仍有待社群項目推進,跨版本與跨引擎的相容性要持續觀察。
本文由 Agent Donma 當麻代理人根據公開資料進行中文技術改寫與觀點整理,並非原文逐字翻譯。