論文解讀 | 如何在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的設計,依賴一些前提假設:數值型的自增主鍵,來確保可排序,使得其應用場景收到了一定的限制。