論文解讀 | 如何在CDC過程中執行增量快照!
最近在網上看到Netflix發的一篇Paper:《DBLog: A Watermark Based Change-Data-Capture Framework》 3 ,可以在不中斷CDC過程,不鎖表的情況下,在任意時刻捕獲表的full state。且能實現隨時終止、隨時恢復的能力。So,這裡對該論文做一個剖析、解讀。
我們在建立一個OLTP資料庫的表到大資料鏈路的過程,通常分為兩步:
-
• bootstrap:歷史存量資料的初始“匯入”;
-
• incremental ingestion:增量攝取,也即我們常說的CDC的過程;
在老的方案中,尤其是我司數千個表的入湖,都是拆分為這兩步序列來做的。我們稱之為:全量+增量以及全量增量銜接。這種方案簡單粗暴,且兩步必須是序列完成,如果bootstrap沒做完,增量的流資料是沒法先攝取到資料湖的。
接下來,我們通過解讀這篇論文,看看Netflix的破解之道。
我們先來看下DBLog的High Level架構:
架構比較簡單,DBLog服務基於ZK做了個簡單的HA,並利用ZK來儲存處理過程中的State。從上游攝取change log,然後從表裡查到特點的記錄行並與其interleave(交織),最後再output。
兩個核心的設計點
-
• chunk
chunk 將要捕獲的表切分為一個個小的“塊”。它會對錶進行查詢,然後對“數值型”的主鍵做個“升序”排序。在這個“即席查詢得到的檢視”上,才會將其切割為記錄數相等的“塊”。形如下圖:
備註這個檢視,不管刪除機制是怎樣,它本質上都可以看成是序列、有序的“記錄表”。只要是對現存的記錄,重排若干次其“順序性”不會發生改變。chunk裡的資料其實是即席查詢的“快照檢視”的切片。
下文會講到,DBLog 所記錄的處理狀態就是新chunk的位置。
要求主鍵是數值型別,也是本設計的一點”cons”,非數值型的主鍵在各種遺留系統裡仍然不少見。
-
• watermark
因為change log本質上是個“流”,流是unbounded,但業界通常都會選擇安插一些特殊的“標記”來表示“流”向前演進的“進展”。而自Google在其流計算系統MillWheel中降這種標記稱之為“Watermark”之後 1 ,它便在各種系統裡出現,並在Flink流行後而被進一步發揚光大 2 。
DBLog在設計中也引入了“Watermark”。它通過在源庫中,新建一個稱之為“Watermark”的表,然後通過向這個表裡upsert一個UUID來表示對watermark的更新(這裡只是使用了watermark在流裡進行切片的作用,並沒有利用它來度量流的演進狀態)。由於change log的scope在資料庫例項級別,因此,對watermark表的操作所產生的“watermark” 的change log會自動interleave到change log stream裡,而且這些都是由資料庫自行完成的,毫無侵入性。
假設我們在某一時刻,生成了一個watermark叫:L,在未來的某一時刻,又生成了一個watermark叫:H。當然,在這之間會有很多事務性的insert/update所產生的正常change log。那麼,L~H框出了一個bounded dataset,也即“window”,window裡是正常DML操作所產生的change log。
接下來,我們來看這兩個核心設計在DBLog的應用。
Watermark-based chunk selection
DBLog會基於watermark來進行chunk的選擇,然後踢掉重複的記錄,將chunk裡的表記錄interleave到change log流裡。
以下是這段演算法的“虛擬碼”:
演算法不是很複雜,如果不太好理解,下文會有圖示。
接下來,拆解一下這段演算法,看它是如何做到的。
第一步到第四步的圖解如下:
首先,源資料庫,一直在不斷地產生change log,這一點是RDBMS的行為且不會也無需中斷。DBLog程式會順序讀取並解析,這個是DBLog可以控制的。
流程開始時,DBLog會暫停處理(1),然後它會先生成一個低水位的“watermark” L(2)。接著,它會進行一個chunk的選擇(3),這裡補充幾個點:
-
• chunk 是大小是固定的且可配的;
-
• chunk 的偏移狀態儲存在ZK裡;
-
• chunk裡的資料不用被預先存起來,而是可以每次通過ad-hoc拿到;
-
• chunk體現的是什麼?體現的是查詢時的快照。
chunk裡資料集拿到後,再生成一個高水位的“watermark” H(4)。拿到chunk資料集之後,再生成新的watermark,意味著在新的watermark生成之前,這個資料集對它是“可見”的。此時L~H也形成了一個change log視窗,也即另一個dataset。因此,現在我們就有了兩個dataset。
那麼,接下來要做什麼:
在chunk中,踢掉兩個資料集中overlapping的資料,剩下的不重複的資料留在chunk中。我們來解釋下,為什麼要踢掉重疊的資料。在同一時間範圍內(L~H),我們看到了chunk中存在的,未被處理的資料“記錄”(靜態狀態)。又看到了change log視窗。而根據“流表二象性”,流通過回放事件,可以形成表某時刻的快照。所以,此時K1、K3就沒必要儲存在chunk裡的,因為最終如果整個change log流被回放的話,會將K1、K3體現在表中。
最後:我們將chunk裡快照的剩餘資料安插在視窗最後。
也即將表裡的靜態資料(狀態)增補進去。看到的就是一個相對更完整的:change log流與static data state交織的完整流。當然再之後,如果K2~K6 仍然發生了改變,也不影響正確性。因為,插入的位置在H之前,而其他的變更必然在H之後,所以不會影響流表二象性的語義。
隨著chunk被一輪接一輪地處理,更多的static data state被interleave到一個super change log裡去。我們就可以在不中斷事務日誌或業務過程的情況下,漸進式地捕獲所有的“靜態資料”狀態(也即我們之前所提到的bootstrap的目的)。最後,我們將這個super change log回放到下游的湖倉裡,如Hudi表,那麼就能完全反應上游表的全貌。
My two cents
毫無疑問,論文在試圖解決一個實際的工程問題,邏輯並不複雜,它巧妙地應用了流計算裡的一些設計,本質上還是“流表二象性”的一個應用。且這個設計已經著名的CDC框架debezium所借鑑並擴充套件實現 4 ,其設計文件在這裡 5 .
pros:
將獲取表的full state的過程,從傳統的序列化、“鎖表”等代價很大的方式,替換成現在的並行、低成本的、change log和表記錄“均衡”交織的方式,可以說是一大進步。論文裡所有的這些解法,都沒有涉及到市面上常見RDBMS獨有的設計(如,yelp在其部落格中介紹了一種依賴於Mysql Blackhole引擎的bootstrap設計 6 ,其通用性上有些劣勢),從而使得其使用場景收到侷限。使用ZK儲存了chunk處理的狀態,可以暫停或恢復。
cons:
由於chunk的設計,依賴一些前提假設:數值型的自增主鍵,來確保可排序,使得其應用場景收到了一定的限制。