關於Delta Lake的ACID事務機制簡介

banq發表於2021-12-25

近年來,隨著大資料利用用例的多樣化,需要為分散式儲存新增更多功能。這幾年誕生了幾款OSS儲存層SW,可以原樣使用HDFS等分散式儲存和Apache Spark等分散式處理框架,為分散式儲存新增新功能。其中,Delta Lake通過將ACID事務的功能賦予分散式儲存來提高資料集的可靠性,本文將進行介紹。我們將探索 Delta Lake 的 ACID 事務,從 Delta Lake 的概述到深入的內容,例如在原始碼級別檢查實現。

 

什麼是Delta Lake?

Delta Lake 是 Databricks 在 2019 年宣佈為 OSS 的儲存層軟體。Delta Lake 允許您通過在分散式儲存(如 HDFS 和 Amazon S3)上啟用 ACID 事務來保持資料集的可靠性。

部署 Delta Lake 很簡單,只需在實現 Spark 應用程式時使用 Maven 或 SBT 安裝包。(以下以Maven為例)

<dependency>
  <groupId>io.delta</groupId>
  <artifactId>delta-core_2.12</artifactId>
  <version>1.1.0</version>
</dependency>

如果您只想讀寫簡單的資料,只需對您的 Spark SQL 應用程式進行一些更改。

// Write
val data = spark.range(0, 5)
data.write.format("delta").save("/tmp/delta-table")

// Read
val df = spark.read.format("delta").load("/tmp/delta-table")

它與 Spark SQL Read/Write 相同,只是格式已更改為"delta"。僅此一項就可以確保您仍然可以以 Parquet 的資料格式讀取和寫入資料,以及讀取和寫入 ACID 事務。

此外,Delta Lake 可以更輕鬆地為以前難以用分散式儲存處理的資料實施 Upsert(在 SQL 術語中也稱為 Merge)。

對於HDFS等儲存,通過規範劃分,實現了高擴充套件性。結果,如果我想更新資料,我必須做這樣的事情:1. 讀取所有要更新的資料集2. 向讀取的資料集新增/更新/刪除資料3. 寫入新資料集4.刪除舊資料集

使用 Delta Lake,這些現在可以通過一次操作完成。

 

分散式儲存的 ACID 事務

首先,讓我們看看每個 ACID 事務是什麼。

  • 原子性:對於事務中包含的操作,要麼保證“所有結果都反映”,要麼“什麼都不反映”。
  • 一致性:保證事務前後的資料一致性。
  • 隔離:每個事務的處理不影響其他事務。
  • 永續性:已完成事務的資料不應在系統故障時丟失。

分散式儲存配置了大量機器,其中一臺機器經常壞掉,所以有些機器是用 Dorability 作為標準實現的。例如,HDFS 通過預設建立三個副本並跨不同節點儲存它們來實現 Durability。

其餘三個通常不受分散式儲存的保護。

關於原子性,我們會在更早的Upsert過程中考慮。(轉貼)

  1. 讀取所有要更新的資料集
  2. 對讀取的資料集新增/更新/刪除資料
  3. 寫一個新的資料集
  4. 擦除舊資料集

現在假設應用程式在 3 完成時崩潰了。在這種情況下,舊資料集和新資料集都將保留在 HDFS 中。如果你立即注意到它,你可能沒有任何問題,但如果它無人看管,其他應用程式將使用舊資料集,如果你注意到它,資料集很多,你不知道哪個是最新的。這個可能會導致問題。

關於一致性,可能是資料集的模式有問題。在HDFS中,寫入的資料的內容是沒有經過審查的,所以有可能,比如寫Parquet有更多的列,json有更多的層次等等。如果模式不一致的地方很多,資料集可能無法建立,資料處理和分析也可能無法進行。

讓我們在與原子性相同的情況下考慮隔離。(轉貼)

  1. 讀取所有要更新的資料集
  2. 對讀取的資料集新增/更新/刪除資料
  3. 寫一個新的資料集
  4. 擦除舊資料集

在 HDFS 中,Isolation Level 是最低的 Read Uncommitted,即沒有事務併發控制。在這種情況下,例如,1 和 3 之間的另一個事務可能會更新資料集,從而導致資料不一致。

  

關於 Delta Lake 事務

Delta Lake 假設您將使用 HDFS 和其他 Durability 分散式儲存作為資料儲存。因此,Durability 是由分散式儲存來保證的。此外,通過使用 Delta Lake 來確保原子性、一致性和隔離性。

讓我更詳細地解釋一下這個故事。通過將格式更改為"delta",Write 將按如下方式執行

  1. 將資料的實體Parquet檔案寫入分散式儲存
  2. 將即1個資料的後設資料寫入分散式儲存“Delta Log”(=提交)

讀取執行如下。

  1. 通過閱讀之前的“Delta Log”確定所需的 Parquet 檔案
  2. 讀取指定的 Parquet 檔案

以下是 Delta Log 的示例:

{"protocol":{"minReaderVersion":1,"minWriterVersion":2}}
{"metaData":{"id":"ac72d2ec-7bdc-42cd-88cc-72da634f0873","format":{"provider":"parquet","options":{}},"schemaString":"{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"long\",\"nullable\":true,\"metadata\":{}}]}","partitionColumns":[],"configuration":{},"createdTime":1640262316180}}
{"add":{"path":"part-00000-1c9604e7-6b21-4392-acf1-1fb45e31744b-c000.snappy.parquet","partitionValues":{},"size":296,"modificationTime":1640262319000,"dataChange":true}}

這個Delta Log的存在實際上保證了原子性、一致性和隔離性。

 

  • 首先是原子性,

這很簡單。由於編寫 Delta Log 本身就是一次提交,因此您可以建立一種情況,如果有 Delta Log,則反映所有更新,如果沒有 Delta Log,則不反映任何更新。沒有在任何 Delta Log 中列出的 Parquet 檔案永遠不會被 Read 讀取,所以如果只將一些過渡處理作為 Parqeut 檔案寫入分散式儲存,那也沒關係。

 

  • 接下來是一致性:

說到前面提到的schema問題,這可以通過檢查Delta Log上掛起的Metadata是否存在不一致來實現。如果它與後設資料中儲存的架構不匹配,則更新將被拒絕(= 架構驗證),並且維護資料集完整性的機制將起作用。只要不衝突,也可以增加模式中的列數(=模式演化)和分割槽中的列數。(Schema Evolution 的具體行為是automatic-schema-evolution>https://docs.delta.io/latest/delta-update.htmlautomatic-schema-evolution)

 

  • 最後,隔離性:

Delta Lake 利用多版本併發控制 (MVCC) 和樂觀獨佔控制。Delta Log 有版本編號,這意味著資料集的版本。通過在讀取資料時在 Spark 中保留版本號,即使發生另一個事務的更新(從應用程式端讀取),您也可以繼續讀取相同版本的資料集(=快照)。您也可以指定特定版本)。對於write,記下事務開始時資料集的最新版本是什麼,如果在commit時已經有(latest version + 1)的Delta Log,就會和其他事務發生資料衝突。

 

相關文章