Iceberg 資料治理及查詢加速實踐

meicanhong發表於2023-02-25

資料治理

在實時資料來源源不斷經過 Flink 寫入的 Iceberg 的過程中,Flink 透過定時的 Checkpoint 提交 snapshot commit 操作到 Iceberg,將已寫入到 Iceberg 的資料檔案透過 Snapshot 組織暴露出來。如果不對流實時寫入 Iceberg 的檔案進行治理,久而久之 Iceberg 下的小檔案會越來越多,Snapshot 版本也越來越多,查詢速度大打折扣。

資料治理方案

基於上述問題,我們需要對 Iceberg 的後設資料和資料檔案定期進行治理。治理方向主要有倆點:

  • 清理快照
  • 合併小檔案

因為我們查詢引擎用 Trino,於是我們選用 Trino 對 Iceberg 進行最佳化。
Trino-Iceberg Connetor 提供了最佳化方法:

-- 清理快照
ALTER TABLE test_table EXECUTE remove_orphan_files(retention_threshold => '7d')

-- 合併小檔案
ALTER TABLE test_table EXECUTE optimize(file_size_threshold => '10MB')

使用 Trino SQL 便可以對 Iceberg 表進行最佳化,很方便。我們基於 Trino SQL 上,做了一個自動自助的 Iceberg 表最佳化工具,實現了定時對某個 Catalog 下的表進行最佳化,省去了人工運維最佳化的成本。
除了快照清理和合並小檔案外,Trino 提供了清理無效資料的方法,可以刪掉一些已經不被 Iceberg 管理的無用的資料檔案。我們是每週對 Iceberg 執行一次無效資料清理。

-- 清理無效檔案
ALTER TABLE test_table EXECUTE remove_orphan_files(retention_threshold => '7d')

查詢加速

我們都知道對 Iceberg Partition 列進行查詢速度都很快,因為其過濾掉很多檔案,只讀取符合查詢分割槽的資料檔案。單讀到底層的 ORC 資料檔案時,Iceberg 提供了 min/max 等資料元資訊,透過元資訊可以快速得知所找的資料是否在此檔案內。

Bloom Filter

在最新的 Iceberg 1.1.0 版本中,Iceberg 支援在 ORC 資料檔案內設定 bloom filters。
而新版 Trino 也跟上 Iceberg 適配 bloom filter,我們需要在 trino-iceberg 的配置檔案裡配置,來開啟 Trino 查詢時使用 bloom filter 查詢

hive.orc.bloom-filters.enabled = true

除此之外,我們還需要設定 Iceberg 表屬性,對列配置上 bloom filter

CREATE TABLE iceberg_table (
   token_address varchar,
   from_address varchar,
   to_address varchar,
   block_timestamp timestamp(6) with time zone,
)
WITH (
   orc_bloom_filter_columns = ARRAY['token_address','from_address','to_address'],
   orc_bloom_filter_fpp = 0.05,
   partitioning = ARRAY['day(block_timestamp)']
)

因為 bloom filter 是生效於 ORC 檔案中,如果想要應用在舊錶上,需要將舊錶資料重寫到新表上,這樣底層的資料檔案才帶有 bloom filter。

舉例:

假如我們有一張 token_transfer 表,表內大概有四個欄位

  • from_address 買方地址
  • to_address 賣家地址
  • token_address 交易代幣
  • block_timestamp 日期

我們對該表 from_address、to_address、token_address 應用 bloom filter,對 timestamp 進行分割槽。該表每天的資料量假設有 100w 條資料。
此時有倆類查詢過來:

  • 查詢熱門 token 今天發生的交易
select * from token_transfer 
where token_address = '熱門token' and block_timestamp > today
  • 查詢冷門 token 今天發生的交易
select * from token_transfer 
where token_address = '冷門token' and block_timestamp > today

此時倆類查詢的 bloom filter 產生的效果是不一樣的,因為熱門的 token 會存在大部分資料檔案裡,冷門的 token 大機率只存在於少部分資料檔案內。對於熱門 token,bloom filter 的加速效果不佳,但對於冷門 token,bloom filter 幫助其快速過濾掉了很多資料檔案,快速找到有冷門 token 的資料檔案,加速效果極佳。
所以得到的結論是,bloom filter 對一些 不重複,特徵值很高的資料有比較好的加速效果。

Order & Z-Order

上文提到,ORC資料檔案內有 min/max 值,查詢引擎可以根據 min/max 值判斷資料是否在此檔案內。
可是日常在寫入 Iceberg 的資料一般都是無序寫入的,無序寫入會導致每個資料檔案也是無序的,不能發揮 min/max 過濾的效果。

Order

Spark 提供了一個壓縮檔案並排序的方法,可以將無序的檔案按指定列排好序。排序策略不僅可以最佳化檔案大小,還可以對資料進行排序以對資料進行聚類以獲得更好的效能。將相似資料聚集在一起的好處是更少的檔案可能具有與查詢相關的資料,這意味著 min/max 的好處會更大(掃描的檔案越少,速度越快)。

CALL catalog.system.rewrite_data_files(
  table => 'db.teams', 
  strategy => 'sort', 
  sort_order => 'team ASC NULLS LAST, name DESC NULLS FIRST'
)

Z-Order

雖然 Order 排序可以同時對多列進行排序,但其列與列之間的排序是有先後順序之分的,像是 MySQL 裡的聯合索引,先對 欄位A 排序再對 欄位B 排序。如果只是的查詢的謂詞只包含 欄位B,則上述索引失效(先對 欄位A 排序再對 欄位B 排序)。
而 Z-Order 能解決上面的問題,使用 Z-Order 對多列排序,列與列之間的排序權重相同。所以使用 Z-Order 對多欄位進行排序,查詢中只要謂詞命中了 Z-Order 中其中任何一欄位,都能加速查詢。
Spark 提供了使用 Z-Order 的方法

CALL catalog.system.rewrite_data_files(
  table => 'db.people', 
  strategy => 'sort', 
  sort_order => 'zorder(height_in_cm, age)'
)

差異

我們測試過對 100G 的表分別進行 Order 和 Z-Order,命中 Order 最高能帶來 10 倍的效能提升,命中 Z-Order 能帶來 2 倍的效能提升。粗步得到的結論是,Order 比 Z-Order 大致快 2 倍。
所以在實踐應用上不能盲目選擇 Z-Order,得根據這張表的熱門查詢SQL、欄位特徵、數量來做:

  • 查詢欄位是資料連續且範圍小的,選 Order
  • 查詢欄位具有高基數特徵,選 Z-Order
  • 頻繁查詢此表多個欄位的,選 Z-Order,否則 Order 的效能會更好

小結

Iceberg 做了很多功夫去加速查詢,本文中提到的小檔案合併、快照清理、Bloom Filter、Order、Z-Order 都是為了在查詢時跳過無用的檔案,透過減少磁碟 IO 操作來加速查詢。Trino 和 Spark 提供許多便利的方法給開發者維護治理 Iceberg;資料治理這塊成本比較低,可以寫好自動化指令碼每天執行資料治理;查詢加速這裡的維護成本比較高,都是需要重寫後設資料和資料檔案的操作,一般每月做一次重寫操作。

參考文章:

相關文章