論文解讀 | 如何在CDC過程中執行增量快照!

語言: CN / TW / HK

最近在網上看到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架構:

1

架構比較簡單,DBLog服務基於ZK做了個簡單的HA,並利用ZK來存儲處理過程中的State。從上游攝取change log,然後從表裏查到特點的記錄行並與其interleave(交織),最後再output。

兩個核心的設計點

  • • chunk

chunk 將要捕獲的表切分為一個個小的“塊”。它會對錶進行查詢,然後對“數值型”的主鍵做個“升序”排序。在這個“即席查詢得到的視圖”上,才會將其切割為記錄數相等的“塊”。形如下圖:

2

備註這個視圖,不管刪除機制是怎樣,它本質上都可以看成是串行、有序的“記錄表”。只要是對現存的記錄,重排若干次其“順序性”不會發生改變。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流裏。

以下是這段算法的“偽代碼”:

3

算法不是很複雜,如果不太好理解,下文會有圖示。

接下來,拆解一下這段算法,看它是如何做到的。

第一步到第四步的圖解如下:

4

首先,源數據庫,一直在不斷地產生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。

那麼,接下來要做什麼:

5

在chunk中,踢掉兩個數據集中overlapping的數據,剩下的不重複的數據留在chunk中。我們來解釋下,為什麼要踢掉重疊的數據。在同一時間範圍內(L~H),我們看到了chunk中存在的,未被處理的數據“記錄”(靜態狀態)。又看到了change log窗口。而根據“流表二象性”,流通過回放事件,可以形成表某時刻的快照。所以,此時K1、K3就沒必要保存在chunk裏的,因為最終如果整個change log流被回放的話,會將K1、K3體現在表中。

最後:我們將chunk裏快照的剩餘數據安插在窗口最後。

6

也即將表裏的靜態數據(狀態)增補進去。看到的就是一個相對更完整的: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的設計,依賴一些前提假設:數值型的自增主鍵,來確保可排序,使得其應用場景收到了一定的限制。