事務日誌(Transaction log)是理解 Delta Lake 的一個關鍵點,不少 Delta Lake 的重要特性都是基於事務日誌實現的,包括 ACID 事務性、可擴展元數據處理、時間回溯等等。本文將探討什麼是事務日誌,如何在文件層面實現,以及怎樣優雅地解決併發讀寫的問題。數據庫
Delta Lake 的事務日誌(簡稱 DeltaLog)是一種有序記錄集,按序記錄了 Delta Lake 表從生成伊始的全部事務操做。json
單一信息源緩存
Delta Lake 基於 Apache Spark 構建,用來支持多用戶同時讀寫同一數據表。事務日誌做爲單一信息源——跟蹤記錄了用戶全部的表操做,從而爲用戶提供了在任意時刻準確的數據視圖。併發
當用戶首次訪問 Delta Lake 的表,或者對一張已打開的表提交新的查詢但表中的數據在上一次訪問以後已發生變化時,Spark 將會檢查事務日誌來肯定該表經歷了哪些事務操做,並將更新結果反饋給用戶。這樣的流程保證了用戶所看到的數據版本永遠保持與主分支一致,不會對同一個表產生有衝突的修改。大數據
Delta Lake 原子性實現spa
原子性,做爲 ACID 四個特性之一,保證了對數據湖的操做(例如 INSERT 或者 UPDATE)或者所有完成,或者所有不完成。若是沒有原子性保證,那麼很容易由於硬件或軟件的錯誤致使表中的數據被部分修改,從而致使數據錯亂。版本控制
事務日誌提供了一種機制來保證 Delta Lake 的原子性。任何操做只要沒有記錄在事務日誌中,都會被認爲沒有發生過。事務操做只有在徹底執行成功後纔會被記錄到事務日誌中,而且將事務日誌做爲單一信息源,這二者保證了數據的可靠性,保證用戶能夠安心處理 PB 級的數據。調試
將事務分解爲原子提交日誌
每當用戶提交一個修改表的操做時(例如 INSERT, UPDATE 或 DELETE),Delta Lake 將該操做分解爲包括以下所示的一系列離散的步驟:blog
這些操做都會被記錄在事務日誌中,造成一系列原子的單元,稱做提交。
例如,假設用戶建立了一個事務,往表中新增一列,而且添加一些數據。Delta Lake 會將該事務分解成離散步驟,當事務完成後,將如下提交添加到事務日誌中:
事務日誌在文件層面的實現
當用戶建立一個 Delta Lake 的表時,會在 _delta_log 子目錄下自動建立該表的事務日誌。後續對錶的修改操做都將被記錄爲有序的原子提交,寫入事務日誌中。每一個提交都是一個 JSON 文件,序號從 000000.json 開始。以後的修改操做都將生成遞增的文件序號,例如 000001.json、000002.json,以此類推。
舉個例子,假如咱們須要往數據文件 1.parquet 和 2.parquet 中添加新的記錄,該事務會被自動寫入到事務日誌中,保存成 000000.json 文件。而後,咱們又決定刪除這些文件而且添加一個新的文件(3.parquet),這些操做將被記錄成事務日誌中的下一個新的提交 000001.json,以下圖所示:
即便如今 1.parquet 和 2.parquet 已經再也不是 Delta Lake 表中的數據,對它們的添加刪除操做仍會記錄在事務日誌中,由於即使增刪操做的做用最後相互抵消,可是這些操做是確實發生過的。Delta Lake 仍會保留這些原子提交,來保證當咱們須要對事件進行審計,或者進行時間回溯查詢表在某個歷史時間點的視圖時,咱們均可以得到精確的結果。
另外,Spark 也不會從磁盤上刪除這些文件,即便咱們執行了刪除了底層的數據文件的操做。用戶能夠經過 VACUMM 命令顯示地刪除再也不須要的文件。
從 Checkpoint 文件快速重構狀態
每隔 10 個提交,Delta Lake 會在 _delta_log 子目錄下自動生成一個 Parquet 格式的 checkpoint 文件。
這些 checkpoint 文件保存了表在該時間點上的全部狀態,而原生 Parquet 格式對 Spark 讀取也比較友好和高效。換句話說,checkpoint 文件給 Spark 提供了一種捷徑來重構表狀態,避免低效地處理可能上千條的 JSON 格式的小文件。
爲了同步提交進度,Spark 能夠執行 listFrom 操做查看全部事務日誌的文件,快速跳轉到最新的 checkpoint 文件,這樣只需處理該 checkpoint 以後的 JSON 提交便可。
下面詳細闡述一下該工做流程,假設咱們的提交一直建立到 000007.json,以下圖所示,Spark 同步到該提交,也就是說已經將表的最新版本緩存在內存中。同時,其餘提交者添加了新的提交一直建立到 000012.json。
爲了包含這些新的事務而且更新咱們的表狀態,Spark 會運行 listFrom verion 7 操做來查看新的修改。
Spark 將會直接跳轉到最新的 checkpoint 文件,而不是逐條處理全部的 JSON 文件,由於 checkpoint 文件包含了 commit #10 以前的全部表狀態。如今,Spark 只需增量執行 0000011.json 和 0000012.json,來構建表的當前狀態,而後將版本12緩存在內存中。經過這樣的流程,Delta Lake 可以利用 Spark 來高效地維護任意時刻的表狀態。
咱們已經闡述了事務日誌的大體工做原理,接下來咱們討論一下如何處理併發。以上咱們的示例基本覆蓋了用戶順序提交事務的場景,或者說是沒有衝突的場景。但若是 Delta Lake 處理併發讀寫會發生什麼?
這個問題很是簡單,因爲 Delta Lake 是基於 Apache Spark 實現的,多個用戶同時修改一個表徹底是一種很是常見的場景,Delta Lake 使用了樂觀鎖來解決這個問題。
什麼是樂觀鎖
樂觀併發控制(又名「樂觀鎖」,Optimistic Concurrency Control,縮寫「OCC」)是一種併發控制的方法,它假設多用戶併發的事務在處理時不會彼此互相影響。樂觀鎖很是高效,由於在處理 PB 級大數據時,有很大機率不一樣用戶處理的是數據的不一樣部分,樂觀鎖使得各事務可以在不產生鎖的狀況下處理各自影響的那部分數據。
舉個例子,假設你和我在一塊兒合做玩拼圖遊戲,只要咱們負責拼不一樣的部分,好比你負責拼角,我負責拼邊,那麼咱們徹底能夠同時處理咱們各自負責的部分,最終可以以翻倍的速度完成拼圖。只有當咱們同時須要拿同一塊拼圖時纔會發生衝突。這就是樂觀鎖的原理。
相對而言,有一些數據庫使用了悲觀鎖,悲觀鎖假設了最壞的狀況,就是說即便咱們有 10,000 塊拼圖,也假設咱們會同時拿同一塊拼圖,從而會形成大量的衝突狀況。悲觀鎖規定同時只能有一我的對拼圖進行操做,其餘人不能同時操做拼圖,這並非一個完成拼圖遊戲的高效方式。
固然,即便使用樂觀鎖,仍是會存在不一樣用戶同時修改數據同一部分的場景,幸運的是,Delta Lake 有一套本身的協議來處理這個問題。
樂觀處理衝突
爲了提供 ACID 事務性,Delta Lake 有一套協議來規定 commit 如何排序(也就是數據庫領域串行性 serializability 的概念),協議規定了如何處理同一時間點的有多個 commit。Delta Lake 經過互斥準則來處理這種場景,而且試圖樂觀處理衝突。協議容許 Delta Lake 實現 ACID 的隔離性準則(isolation),保證了通過多個併發提交後表的最終狀態和單獨順序提交的結果是一致的。
一般來講,整個流程以下所示:
下圖具體闡述了 Delta Lake 如何處理衝突,假設兩個用戶從同一個表中讀取數據,而後同時去嘗試添加數據。
在大部分狀況下,這種重試可以在後臺無感知的完成,但也存在一些狀況 Delta Lake 沒法經過這種重試成功完成(例如 User 1 和 2 都同時刪除同一個文件),在這種狀況下將會返回異常給用戶。
最後提一點,因爲 Delta Lake 表的全部事務都直接保存在磁盤上,所以整個過程知足 ACID 的持久性(durability)特性,也就是說可以容忍諸如系統崩潰等錯誤狀況。
時間回溯
每張表的狀態都是由記錄在事務日誌中中的全部 commit 所決定的,事務日誌至關於提供了每一步操做的歷史記錄,詳細記錄了表從初始狀態到當前狀態的全部操做步驟。所以,咱們能夠經過遍歷表從初始狀態到某個時間點的全部 commit ,來重構出表在任意時間點的狀態。這個強大的功能就是時間回溯,或者叫作數據版本控制。更多關於時間回溯的說明,能夠參考 Introducing Delta Time Travel for Large Scale Data Lakes。
數據血緣和調試
事務日誌確切地記錄了 Delta Lake 表的全部修改,所以它能提供可信的數據血緣,這對治理、審計和合規目的頗有用處。它也能夠用來跟蹤一些無心或者有錯誤的修改,從而可以回退到指望的版本。用戶能夠執行 DESCRIBE HISTORY 來查看指定修改附近的元數據。
本文詳細探討了 Delta Lake 的事務日誌,主要包含了如下幾點:
本文爲雲棲社區原創內容,未經容許不得轉載。