愛奇藝數據湖實戰 - Hive數倉平滑入湖

愛奇藝基於 Hive 構建了傳統的離線數據倉庫,支持了公司運營決策、用戶增長、視頻推薦、會員、廣告等業務需求。近幾年,隨着業務對數據實時性的更高要求。我們引入了基於 Iceberg 的數據湖技術,大幅提升數據查詢性能及整體流通效率。從性能和成本角度考慮,將現有的Hive表遷移到數據湖是必要的。然而多年來,大數據平臺上已經積累了數百 PB 的 Hive 數據,如何將 Hive 遷移到數據湖,成爲我們面臨的一大挑戰。本文介紹了愛奇藝從 Hive 平滑遷移到 Iceberg 數據湖的技術方案,幫助業務加速數據流程,提效增收。

01

   Hive VS Iceberg

Hive 是一個基於 Hadoop 的數據倉庫和分析平臺,提供了類似 SQL 的語言,支持複雜的數據處理和分析。

Iceberg 是一個開源的數據表格式,旨在提供可擴展、穩定和高效的表格存儲,以支持分析性工作負載。Iceberg 提供了類似傳統數據庫的事務性保證和數據一致性,並支持複雜的數據操作,如更新、刪除等。

表 1-1 分別列出了 Hive 和 Iceberg 在時效性、查詢性能等方面的比較情況:

表 1-1 Hive 和 Iceberg 的對比

切換到 Iceberg 可以提高數據處理的效率和可靠性,爲複雜的數據操作提供更好的支持,目前已接入廣告、會員、Venus 日誌、審覈等十幾個業務。關於愛奇藝 Iceberg 實踐的更多細節,可閱讀之前的系列文章(見文末引用)。

02

   Hive 存量數據平滑切換 Iceberg

Iceberg 相比 Hive 有諸多優勢,可是業務的數據已經運行在 Hive 環境中,業務不希望投入大量的人力修改存量的任務。我們調研了業界常用切換方法 [1],在數據湖平臺上提供了自助式 Hive 平滑切換 Iceberg 的能力,本節將闡述具體實現方案。

1. 查詢兼容性

在實際切換前,我們驗證了 Spark 對 Hive 和 Iceberg 的兼容性。

Spark 對 Hive 和 Iceberg 表的查詢、寫入語法基本是相同的,對 Hive 表的查詢 SQL 語句無需修改即可查詢 Iceberg 表。

但 Iceberg 與 Hive 在 DDL 方面存在較大差異,主要表現在對錶結構進行修改的處理方式上,詳細信息如表 2-1 所述。實際 schema 與數據文件的 schema 需要一一對應,否則會影響數據的查詢,因此對於 DDL 語句的處理應該更爲謹慎,不建議將這類 DDL 語句與任務綁定在一起。

表 2-1 Hive 和 Iceberg 語法兼容性對比

2. 業界切換方案

2.1 業務雙寫切換

業務複製現有的pipeline,實現Hive、Iceberg雙寫。待新舊通路對數一致後,切換到 Iceberg 通路,並下線原有的通路。該方案需要業務投入人力進行開發、對數,耗時耗力。

2.2 原地切換,客戶端停寫

如果業務允許停寫一段時間進行切換,則可以用如下一些方式:

  • Spark migrate procedure 是 Iceberg 官方提供的函數,可將一個 Hive 表原地切換爲 Iceberg,示例如下:

CALL catalog_name.system.migrate('db.sample');

該程序不會修改原始數據,僅會掃描原表的數據然後構建 Iceberg 元信息,引用原始的文件。因而 migrate 程序執行速度非常快,但存量數據無法利用文件索引等特性加速查詢。如希望存量數據也加速,可以使用 Spark 的rewrite_data_files 方法重寫歷史數據。

migrate 程序並不會將 Hive 表刪除,而是將其重命名爲 sample__BACKUP__,此處 __BACKUP__ 後綴是硬編碼的,如果需要回滾可將新建的 Iceberg 表 Drop 掉,將 Hive 表 rename 回去。

  • 使用 CTAS 語句,Spark 示例如下:

CREATE TABLE db.sample_iceberg
(id bigint, ..., dt string) 

USING Iceberg

PARTITIONED BY dt

LOCATION 'qbfs://....'  

TBLPROPERTIES('write.target-file-size-bytes' = '512m', ...)

AS SELECT * FROM db.sample;

在寫入完成後進行對數,符合要求後,通過重命名完成切換。

ALTER TABLE db.sample RENAME TO db.sample_backup;

ALTER TABLE db.sample_iceberg RENAME TO db.sample;

CTAS 相比於 migrate 優勢是存量數據重新寫入,因而可以優化分區、列排序、文件格式和小文件等。缺點是如果存量數據較多,重寫耗時耗資源。

以上兩個方案,具有如下特性:

優點:

  • 方案簡單,執行已有的 SQL 即可

  • 可回滾,原 Hive 表還在


缺點:

  • 寫入/讀取程序未驗證:切換到 Iceberg 表後,可能出現寫入或查詢異常

  • 要求切換過程停寫,對一些業務是不能接受的


3. 愛奇藝平滑遷移方案

考慮到上述方案的缺點,我們設計了原地雙寫 + 透明切換的方案,實現平滑遷移,如圖 2-1 所示:

  • 建表創建與 Hive 相同 schema 的 Iceberg 表,同步 Hive 表的 TTL、權限等元信息到 Iceberg 表。
  • 歷史數據遷移到 Iceberg Hive 歷史數據通過 add_file procedure 添加到 Iceberg 中,該操作會根據 Hive 數據構建出 Iceberg 的元數據,實際上 Iceberg 的元數據中指向的是 Hive 的數據文件,減少了數據冗餘及歷史數據同步時間。
  • 增量數據雙寫 通過愛奇藝自研的 Pilot SQL 網關探測 Hive 表的寫入任務,自動複製寫入 SQL,並將輸出替換成 Iceberg 表,實現雙寫。
  • 數據一致性 校驗: 當歷史數據同步完成且增量雙寫到一定次數之後,後臺會自動發起對數,校驗 Hive 和 Iceberg 中的數據是否一致。對於歷史數據與增量數據會選取一部分數據進行 count 以及字段 CRC 數值校驗。
  • 切換 數據一致性校驗完成後,進行 Hive 和 Iceberg 的切換,用戶不需要修改任務,直接使用原來的表名進行訪問即可。正常切換過程耗時在幾分鐘之內。

圖 2-1 Hive 切換到 Iceberg 大致流程
圖 2-2 展示了 Hive to Iceberg 相關操作界面,點擊創建轉化任務即可開始進行切換流程,當任務創建成功會在下方展示任務的狀態以及運行階段等信息。

圖 2-2 Hive to Iceberg 相關操作界面

03

   核心收益 - 加速查詢

1. Iceberg 查詢加速技術

Iceberg 自身提供了三層數據過濾策略,分別是 [2]:
分區剪裁:和 Hive 表類似,對於分區表,引擎端可以自動從 where 條件中根據分區鍵直接提取出需要訪問的分區,從而避免掃描所有的分區。分區剪裁可以細分爲靜態分區剪裁和動態分區剪裁,其中靜態分區剪裁發生在 SQL 語句編譯階段,而動態分區剪裁則發生在 SQL 語句執行階段。
文件過濾:Iceberg 提供了文件級別的統計信息,例如 Min/Max 等,可以快速過濾無關數據和文件,可以用 where 語句中的過濾條件去判斷目標數據是否存在於文件中。例如 SELECT * FROM table WHERE dt='2023-01-01' AND channel_id = '20',dt 是分區,channel_id 是字段,對於 channel_id = '20' 這樣的過濾條件,元信息中存儲了每個文件 channel_id 的 upper_bounds 和 lower_bounds,可以通過判斷列值是否在範圍內決定是否需要掃描當前文件。
但實際使用中,這種過濾發揮的作用比較小。因爲數據寫入是隨機且無序的,導致 upper_bounds 和 lower_bounds 範圍重合度非常高,這種情況下目標數據可能會分佈在大部分文件甚至所有文件,掃描數據文件的範圍也大大增加。因此在切換爲 Iceberg 後,我們可以基於過濾條件中的高頻列進行排序,降低文件級別的 upper_bounds 和 lower_bounds 的範圍重合度。
除了 MinMax 外,Iceberg 還可以支持更多類型的索引進行文件級過濾,例如字典、布隆過濾器等。
文件內 RowGroup 過濾:對於 Parquet、ORC 這類列式存儲文件格式,在文件內部也存在相應的統計信息,例如Min、Max、BloomFiter 等等,利用這些信息可以快速跳過無關的 RowGroup 或者 Stripe,減少文件內數據掃描的量。

2. Iceberg 加速技巧

基於 Iceberg 查詢更快的基本原理,我們可以總結出如下技巧:
  • 配置分區:使用分區剪裁的方式使查詢只針對特定分區的數據執行,而不需要掃描整個數據集。
  • 指定排序列:通過對數據分佈進行合理的組織,最大限度的發揮文件級別的過濾效果,使得查詢只集中在特定的文件。例如通過下面的方式使得寫入 sample 表的數據按照 category, id 降序寫入,注意由於多了一個排序的環節,這種方式會比非排序的寫入耗時長。
ALTER TABLE db.sample WRITE ORDERED BY category, id DESC
  • 高基數列應用布隆過濾器:在查詢數據時,會自動應用布隆過濾器來快速驗證查詢數據是否存在於某個數據塊,避免不必要的磁盤訪問。
write.parquet.bloom-filter-enabled.column.test = true -- parquet 文件給 test 列增加 bloom-filter
write.orc.bloom.filter.columns = test -- orc 文件給 test 列增加 bloom-filter
  • 使用 Trino 代替 Spark:由於 Trino 自身 MPP 的架構,在查詢上相較於 Spark 更有優勢,並且 Trino 自身對 Iceberg 也有相應的優化,因此如果有秒級查詢的需求,可將引擎由 Spark 切換到 Trino。
  • Alluxio 緩存:使用 Alluxio 作爲數據緩存層,將數據緩存在內存中。在查詢時可以直接從內存中獲取數據,避免從磁盤讀取數據的開銷,可大大提高查詢速度,也可防止 HDFS 抖動對任務的影響。
  • ORC 代替 Parquet:由於 Trino 對 ORC 格式有特定的優化,使得 ORC 的讀取性能要優於 Parquet,可以將文件格式設置爲 ORC 加速查詢。
  • 配置合併:寫 Iceberg 的任務往往會出現寫入文件較小但數量較多的情況,通過將小文件合併成一個或少量更大的文件,有利於減少讀取的文件數,降低磁盤 I/O。


3. 性能評測

3.1 文件內過濾性能提升

背景:數據集市是從 Hive 表切換爲 Iceberg 表的場景之一,在切換到 Iceberg 後查詢速度明顯地變快。經過實驗對比,確認性能是由文件內 RowGroup 過濾帶來的。


圖 3-1 Hive 和 Iceberg 查詢對比

3.2 列排序對文件內過濾性能提升

我們在另一個場景進一步探索排序對性能的影響。由於分區下僅一個文件,因而文件級過濾不起作用。我們分別比較了 Parquet 和 ORC 這兩種文件格式,在排序和未排序下的查詢性能,最終結論如下:
  • 同樣的文件格式,排序後文件內過濾效果更好,大致能快 40%;
  • ORC 查詢性能優於 Parquet;
  • 使用 Trino 查詢,我們推薦 Iceberg 表 + ORC 文件格式 + 列排序;

圖 3-2 Iceberg 分別在 Parquet、ORC 格式上文件內過濾性能對比

3.3 列排序對文件級過濾性能提升

業務表特定的列可能會頻繁用做過濾條件,默認情況下數據是亂序組織,此時列 MinMax 值過濾也難以發揮作用,因特定值在每個文件都被包含。如果在數據寫入時,按照該列進行排序組織,則 MinMax 值就能過濾掉大部分無關文件,大幅減少讀取的數據量,加速查詢。
下面以 CDN 的一個表爲例,它的查詢頻繁用到 isp 和 prov 兩個列,一個典型查詢如下:
SELECT 
  "date" / 300 * 300 as "date",
  isp, 
  ip_type, 
  sum("traffic") as "traffic"
FROM
  table
WHERE "date" >= 1698986100  AND "date" < 1698986400 
  AND isp IN ('TV', 'Mobile', 'Phone') 
  AND prov IN ('BeiJing') 
 GROUP BY  
  "date" / 300 * 300, isp, ip_type;
我們分別測試對應表,默認不排序,按照 isp 排序,按照 prov 排序 3 種情況,最終性能如下:
  • 按照 prov 排序查詢讀取數據量是不排序的 25%,耗時是 66%;
  • 按照 isp 排序提升不明顯,這是因爲 isp 數據量有明顯的傾斜,條件中 isp 值佔比高達 90%;

圖 3-3 Iceberg 文件級過濾性能提升對比

3.4 布隆過濾器的性能提升

在會員訂單場景,業務既有基於訂單 ID 檢索的需求,又有查詢某個用戶 UserId 歷史訂單的需求。這兩個列基數都非常大,無論用哪個列排序,另一個列的查詢都會退化爲全表檢索。此類場景可以通過布隆過濾器滿足。下圖演示了開啓布隆過濾器後,訂單表的性能和 Impala + Kudu 接近,而未開啓的情況下查詢要接近 1000 秒。

圖 3-4 Iceberg 使用布隆過濾器和 Impala + Kudu 的性能對比

3.5 Spark Trino 性能比較

Trino 社區早期版本僅支持 Iceberg 表 V1 的查詢,而對 V2 表格式的支持有問題(查詢結果不正確)。愛奇藝的方案是在 Pilot SQL 網關中基於 Iceberg 表格式進行路由,V1 表路由到 Trino 引擎,V2 表路由到 Spark 引擎。
我們在 Trino 434 版本,重新驗證了 Trino 對 Iceberg V2 表的查詢。實驗過程如下,對於 TPC-DS 測試集,我們每次變更表 0.1% 的數據,累計變更 20 輪,表使用 Merge On Read 模式,通過 Spark 執行變更生成 Position Delete 文件。changeN 代表 N 次變更後,rewrite_position 代表執行了 Spark rewrite_position_delete_files 後,rewrite_data 代表rewrite_data_files 後。

圖 3-5 Spark 和 Trino 對於 Iceberg 的查詢性能對比
可以看到:
  • Trino 對於 V2 表查詢結果與 Spark 一致,且在相同核數性能優於 Spark,耗時是 Spark 的 1/3 左右;
  • 隨着變更輪次的增加(Data File 和 Postition Delete File 數量增加),Trino 查詢性能也會逐漸變慢,需要定期進行合併。


04

   核心收益 - 支持變更

1. 變更在業務使用場景

傳統上大數據表對變更的支持較差,然而業務上有很多的變更需求:
  • ETL 計算:如廣告計費,通過接入 Iceberg 實現變更,簡化業務邏輯,實現了更長時間範圍的轉化回收;
  • 數據修正:批量修正,如對某個數據的狀態進行修改、批量刪除等;
  • 隱私相關:如播放記錄、搜索記錄,用戶需要刪除歷史條目等;
  • CDC 同步:如訂單業務,需要將 MySQL 中的數據進行大數據分析,通過 Flink CDC 技術很方便地將 MySQL 數據入湖,實時性可達到分鐘級。


2. Hive 如何實現變更

在 Hive 中實現變更,主要有如下兩種方式:
  • 分區覆寫 例如修改某個 id 的相關內容,先篩選出要修改的目標行,更新後與歷史數據進行合併,最後覆蓋原表。這種方式對不需要修改的數據進行了重寫,浪費計算資源;且覆寫的粒度最小是分區級別,數據無法進一步細分,任務耗時相對較長。
  • 標記刪除 通常的做法是添加標誌位,數據初始寫入時標誌位置 0,需要刪除時,插入相同的數據,且標誌位置 1,查詢時過濾掉標誌位爲 1 的數據即可。這種方式在語義上未實現真正的刪除,歷史數據仍然保存在 Hive 中,浪費空間,而且查詢語句較爲複雜。

3. Iceberg 支持的變更類型

Iceberg 目前支持的變更類型如下:
  • Delete:刪除符合指定條件的數據,例如
DELETE FROM table_name WHERE channel_id= '2'
  • Update:更新指定範圍的數據,例如
UPDATE table_name SET category='c2' WHERE id='2'
  • MERGE:若數據已存在 UPDATE,不存在執行 INSERT,例如
MERGE INTO db.target t   -- a target table
USING (SELECT ...) s   -- the source updates
ON t.id = s.id   -- condition to find updates for target rows
WHEN MATCHED AND t.count IS NULL AND s.op = 'increment' THEN UPDATE SET t.count = 0 
WHEN NOT MATCHED THEN INSERT *


4. Iceberg 變更策略

Iceberg 支持多種變更策略,每個策略有各自的優劣和適用場景,下面簡單介紹一下每種策略的原理 [3]。
  • Copy on Write(寫時合併):當進行刪除或更新特定行時,包含這些行的數據文件將被重寫。寫入耗時取決於重寫的數據文件數量,頻繁變更會面臨寫放大問題。如果更新數據分佈在大量不同的文件,那麼更新的執行速度比較慢。這種方式由於結果文件數較少,讀取的速度會比較快,適合頻繁讀取、低頻批次更新的場景。
  • Merge on Read(讀時合併):文件不會被重寫,而是將更改寫入新文件,當讀取數據時,將新文件合併到原始數據文件得到最終結果。這使得寫入速度更快,但讀取數據時必須完成更多工作。寫入新文件有兩種方式,分別是記錄刪除某個文件對應的行(position delete)、記錄刪除的數據(equality detete)。
    • Position Delete:當前 Spark 的實現方式,記錄變更對應的文件及行位置。這種方式不需要重寫整個數據文件,只需找到對應數據的文件位置並記錄,減少了寫入的延遲,讀取時合併的代價較小。
    • Equality Delete:當前 Flink 的實現方式,記錄了刪除數據行的主鍵。這種方式要求表必須有唯一的主鍵,寫入過程無需查詢數據文件,延遲最低;然而它的讀取代價最大,這是由於讀取時需要將 equality delete 記錄和所有的原始文件進行 JOIN。
表 4-1 總結了不同變更策略的特點及適用場景:

4-1 Iceberg 不同變更策略對比

Iceberg 配置變更策略:Iceberg 中可以通過 write.delete.mode、write.merge.mode、write.update.mode 屬性分別設置刪除、合併、更新等寫入模式,默認值均是 copy-on-write。當前只有 V2 表支持 Merge-on-read 模式。

表 4-2 Iceberg 變更屬性配置方式

5. 業務接入

本節通過一些例子,說明 Iceberg 支持變更給業務帶來的價值。

5.1 廣告計費轉換

如圖 4-1 所示,在效果廣告場景中,客戶有查詢計費轉化數和深度轉化數據的需求(基於計費時間)。比如某垂直領域客戶,希望把用戶行爲統一起來,1 號發生的 100 萬曝光,產生了 40 萬點擊(僅爲示例,非真實數據),進而在後續的第 N 天內發生了 5000 的用戶付費行爲,需將第 N 天的付費歸因到  1  號的曝光。廣告報表都是基於用戶行爲時間,即日誌時間聚合而成,爲支持將深度轉換歸因到廣告計費的當日,由於 Hive 不支持變更做了如下複雜的設計:
  • 每天觸發一次計算,從行爲表聚合出過去 7 天的“計費時間”數據。此處用 rt 字段代表計費時間
  • 提供統一視圖合併行爲數據和計費時間數據,計費歸因表 rt as dt 作爲分區過濾查詢條件,滿足同時檢索曝光和計費轉化的需求

圖 4-1 廣告計費轉換場景
而在 Iceberg 場景下,其支持變更因而無需使用多個不同的表,直接在原表通過如下 SQL 即可完成:
MERGE INTO iceberg_taget_table t 
USING (   
  SELECT * FROM changes_table   
  WHERE dt='2023-12-12' 
) s   
ON t.id = s.id 
... 
AND t.dt = s.dt 
WHEN MATCHED THEN 
 UPDATE SET 
  count = count + s.cnt,  
  deep_count = deep_count + s.deep_cnt, 
  ...
通過 Iceberg 表 merge 可簡化整個處理流程:
  • 時效性提升:從天級縮短到小時級,客戶更實時觀察成本,有利於預算引入;
  • 計算更長週期數據:原先爲計算效率僅提供 7 日內轉換,而真實場景轉換週期可能超過 1 個月;
  • 表語義清晰:多表聯合變爲單表查詢。

5.2 數據修正

舉個例子,業務發現線上 ETL 任務邏輯有 BUG,導致某個列的值不準確。雖然線上 ETL 任務已經修復,但是錯誤的數據已經寫入到下游的 Iceberg 表裏。如果是 Hive 場景,需要重跑 ETL 任務,全量覆蓋天分區進行修正。而在 Iceberg 表我們可以通過如下 SQL 進行修正:
UPDATE your_iceberg_table 
SET strategy_code = 'correct_value' 
WHERE dt = '2023-12-01' and strategy_code = 'wrong_value'


05

   總結

Iceberg 不僅提供了數據刪除、更新等功能,有效滿足數據保留政策和合規性要求,而且查詢加速措施更爲多樣,可以利用列式存儲、索引和元數據統計信息來優化查詢計劃,提高查詢性能,可以幫助我們簡化業務邏輯,提升時效性,加快數據產出。
通過以上平滑切換方案,從 Hive 到 Iceberg 不需要重新構建數據湖,在儘量保持語義兼容性的情況下,可以進行無縫遷移,減少了遷移的複雜性和風險。
後續我們將繼續推進 Hive 到 Iceberg 的遷移,提升數據流通效率,促進業務提效增收。

06

   引用

  1. From Hive Tables to Iceberg Tables: Hassle-Free
  2. 通過數據組織優化加速基於Apache Iceberg的大規模數據分析
  3. Row-Level Changes on the Lakehouse: Copy-On-Write vs. Merge-On-Read in Apache Iceberg
  4. 《愛奇藝數據湖實戰 - 綜述》
  5. 《愛奇藝數據湖實戰 - 廣告》
  6. 《愛奇藝數據湖實戰 - 基於數據湖的日誌平臺架構演進》
  7. 《愛奇藝數據湖實戰 - 數據湖技術在愛奇藝BI場景的應用》
  8. 《愛奇藝在Iceberg落地相關性能優化與實踐》

數據湖在愛奇藝數據中臺的應用

愛奇藝數據湖實戰 - 基於數據湖的日誌平臺架構演進


本文分享自微信公衆號 - 愛奇藝技術產品團隊(iQIYI-TP)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章