Delta Lake
在說 Delta Lake 之前,不得不說下 Data Lake ,Data Lake 的主要思想是將企業中的所有數據進行統一管理。例如基於 Hadoop 的 Data Lake 方案可以非常低成本的存儲所有類型的數據,但是顯而易見的,它只支持批量插入,用戶讀取時無法獲取當前更新的數據,多用戶寫還可能會發生異常,數據並不是非常可靠。
Delta Lake 不僅能解決上述問題,還能對數據進行各種增強,例如 time travel, index 等。
在閱讀文章之前,你是否在hadoop生態的工作中遇到過這些問題?
- 困難的更新/刪除操作 (需要整塊重寫)
- 無法保證數據一致性 (例如 spark 緩存了 parquet 元數據需要 refresh)
- etc.
Delta Lake 在近期開源 項目地址,發佈了其第一個 release 版本,使用 parquet 文件格式,在其上通過 transaction log
記錄更新操作從而提供了 ACID 事務等能力,通過與 spark 集成,得以處理大量的元數據。
Delta Lake 可以更簡單的理解爲構建在 Data Lake 上的一層數據庫。巧妙的地方在於,它不是一種 file format
而是 table format
,這意味着你不需要修改底層的存儲文件,他解放了用戶花費在數據一致性上的大量時間,分析時也無需關心表中的數據是來自流數據還是歷史數據。此外,通過時間旅行的功能,可能解決很多非常麻煩的事情,例如事件回放。
Delta lake 相關特性在官網上已經說的非常詳細,不再贅述,根據筆者自己的理解簡單描述一下五個比較重要的特性:
- ACID transactions,在數據上的操作變的可靠。
- Schema enforcement, 自動處理表變化,例如 shema 發生變化的數據插入會被拒絕。
- Streaming and batch unification,統一了流和批處理,在以前,通過 spark streaming 可以非常容易的處理當前事件,但是無法處理歷史數據或是進行一些複雜的機器學習分析。這個時候大家很容易就能想到 lambda 架構,在 Delta Lake 中,不論表是流式追加還是靜態,不同的用戶同時對它進行讀寫操作,都是透明的,不會發生異常和併發問題。這意味着你可以直接查詢當前最新的數據或是歷史的數據。
- Scalable metadata handling,隨着數據的增加,元數據也會變得非常大,甚至可能會超過數據本身。顯而易見會帶來元數據恢復的問題(可以想象一下namenode啓動時緩慢的元數據讀取過程),因此把超大的元數據存放在 hive metastore 或是其他的系統中並不現實,同時非常難以管理。因此可以通過 Spark 強大的分佈式處理能力來解決這個問題,例如輕鬆處理pb級表的所有元數據和數十億個文件(類似 parquet 的分區發現)。
- Time travel,時間旅行?這樣說可能有點抽象,它意味着你可以提供時間戳回朔到當時的數據狀態。這在機器學習之類的事件回放場景太有用了(不用擔心由於數據更新導致多次運行時 input 數據不一致)。其他的一些場景例如數據版本回滾(類似 git reset),完整的歷史審計日誌跟蹤等。
Transactional metada 實現
在文件上增加一個日誌結構化存儲(transaction log ),該日誌有序(ordered)且保持原子性(atomic)
增加或者刪除數據時,都會產生一條描述文件,採用樂觀併發控制 (optimistic concurrency control) 保證用戶併發操作時數據的一致性
併發控制
Delta Lake 在讀寫中提供了 ACID 事務保證。這意味着:
- 即使是跨多個集羣的併發寫入,也可以同時修改數據集並查看錶的一致性快照,這些寫入操作將按照串行執行
- 即使在作業執行期間修改了數據,讀取時也能看到一致性快照。
樂觀併發控制(optimistic concurrency control)
Delta Lake 使用 optimistic concurrency control 機制提供寫數據時的事務保證,在這種機制下,寫過程包含三個步驟:
- Read: 讀取表最新可用版本,以確定哪些文件需要被重新定義
- Write: 將所有的修改寫成新的數據文件
- Validate and commit: 在提交變更之前,檢查自讀取該快照以來,和其他併發提交的變更是否有衝突。如果沒有衝突,所有的變更會被提交併生成一個新版本快照,寫操作成功。如果發現了衝突,該寫入操作會因爲併發定義異常失敗,而不會像開源版的 spark 一樣破壞數據。
使用
在 spark 中,爲了提高查詢性能會緩存 parquet 的元數據,如果此時表的 schema 被更新必須要手動刷新元數據才能保證數據的一致性,例如 spark.sql(refresh table xxx)
因爲 delta Lake 的特性,數據更新後也無需調用 refresh
,目前僅支持 HDFS 作爲底層存儲
// 將已有 parquet 數據轉化爲 delta table
val df = spark.read.parquet("/path/to/your/data")
df.write.format("delta").save("/delta/delta_table")
spark.sql("CREATE TABLE delta_table USING DELTA LOCATION '/delta/delta_table'")
delta 表根目錄 _delta_log
下會生成 json 格式的事務文件
hdfs dfs -cat /delta/delta_table/_delta_log/00000000000000000000.json
{"commitInfo":{"timestamp":1555989622032,"operation":"WRITE","operationParameters":{"mode":"ErrorIfExists","partitionBy":"[]"}}}
{"protocol":{"minReaderVersion":1,"minWriterVersion":2}}
{"metaData":{"id":"f33f4754-7c0c-43b8-87a2-ddc21e1311ea","format":{"provider":"parquet","options":{}},"schemaString":"{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{\"comment\":\"\"}}]}","partitionColumns":[],"configuration":{},"createdTime":1555989621467}}