Apache Paimon 在同程旅行的探索實踐

ApacheFlink發表於2023-03-31

摘要:本文主要介紹 Apache Paimon 在同程旅行的生產落地實踐經驗。在同程旅行的業務場景下,透過使用 Paimon 替換 Hudi,實現了讀寫效能的大幅提升(寫入效能 3.3 倍,查詢效能 7.7 倍),接下來將分為如下幾個部分進行詳細介紹:

  1. 湖倉場景現狀和遇到的問題
  2. 遇見 Apache Paimon
  3. Apache Paimon 的應用實踐
  4. 問題發現和解決
  5. 未來規劃

點選檢視更多技術內容

一、湖倉場景現狀和遇到的問題

隨著公司業務發展,實時性業務需求越來越多,2021年開始逐步調研並引入湖倉架構,結合當時資料湖架構,最終我們選擇 hudi 作為湖倉底座。透過內部自研資料整合能力能夠一鍵將內部 base 層的 binglog 資料匯入到湖倉內,逐步替代了基於 hive 實時同步,凌晨合併的方式;另外還結合湖上的流讀能力,透過增量讀的方式將增量結果合併到 DWD 層;以及結合 flink 視窗計算完成了大量實時報表的改造,極大提高了資料時效性,同時也節省了大量批處理合併計算資源。

但是隨著任務和場景的增多,基於 hudi 的湖倉逐漸暴露出了一些問題,讓我們不得不重新思考湖倉架構,以及後續演進方向。

1.1 湖倉應用現狀

目前內部資料湖場景主要應用於以下幾個場景:

  1. 資料庫 base 層入湖,提升 ods 層時效性
  2. 利用湖增量能力,構建下游 dwd 層,節省計算資源
  3. 利用湖上區域性更新能力,構建實時統計檢視和報表
  4. 利用湖近實時更新能力,構建實時監控場景

整體架構如下:

1

利用湖倉的各項能力,我們將 ODS 後置批處理時間提前了近1小時,同時中間過程的計算儲存成本也極大減少。不過同時也遇到了不少問題,在基於 Hudi 湖倉的實踐過程中我們遇到的問題主要集中在寫入效能,查詢效能,資源消耗等方面。

1.2 湖倉寫入效能問題

Apache Hudi 提供了兩種寫入模式 COW 和 MOR,COW 天然存在寫入瓶頸,這裡主要使用 MOR 型別,為了方便管理,同時開啟任務非同步 compact(5個commit/次)。

雖然 Hudi 使用類 LSM 模式進行資料寫入與合併,不過有區別於 LSM 的 SSTable,合併過程全讀全寫,即使只變更了其中一條資料,也需要整個檔案進行讀取合併,這就造成 compact 過程需要比較大的記憶體。尤其當存在熱點資料時,任務需要從一開始便保留足夠的資源來應對突增的大流量資料,從而造成一定的記憶體資源浪費。以下是一個 Hudi 入湖任務的資源配比情況:

2

<center>(上圖為執行容器數)</center>

3

<center>(上圖為容器資源配比)</center>

1.3 湖倉查詢效能問題

我們主要利用基於表主鍵的 bucket 索引,因為湖倉做到了近實時,所以帶來了更多的點查場景,Hudi 利用分割槽和主鍵下推到查詢引擎後能夠剪枝掉大量的分割槽和檔案,不過單 bucket 內仍然需要 scan 整個檔案來定位到具體的主鍵資料,點查效能略顯吃力,結合 MOR 查詢時的合併流程(如寫入流程所描述)點查效能很難提升,以下是基於Hudi的點查,耗時21s。

4

<p><center>(上圖為基於Hudi的點查耗時情況)</center></p>

最後是寫入資源壓力,我們的湖倉主要架設在 HDFS 之上,大量上線湖倉任務之後 HDFS 的 IO 壓力也逐步升高,這與 Hudi 寫入原理有關。

1.4 成本相對較高

實時任務執行資源成本高,Hudi 有較多的調優引數,使用者上手成本高,內部推廣難,早期 Hudi 與 Spark 強繫結,後期解耦後,Flink 整合出現了不少問題,最佳化成本高。

綜上所述,我們在湖倉場景下面臨的問題總結如下:

  • MOR 型別表寫入任務並行度和資源資源配置過高,造成資源浪費
  • 點查效能難以最佳化,不能很好的滿足需求
  • 由於合併帶來的儲存 IO 壓力變大

二、遇見Apache Paimon

彼時還叫 Flink Table Store,如今成功晉升為 Apache 孵化專案 Apache Paimon,官網地址:Apache Paimon,首次接觸在 FLIP-188: Introduce Built-in Dynamic Table Storage - Apache Flink - Apache Software Foundation 中,就被基於原生LSM的寫入設計以及 universal compaction 深深吸引,便持續關注,在0.2版本釋出後我們開始接入測試使用。

2.1 Apache Paimon簡介

Apache Paimon(incubating) is a streaming data lake platform that supports high-speed data ingestion, change data tracking and efficient real-time analytics

Apache Paimon 是一款支援高吞吐資料攝入,變更跟蹤,高效分析的資料湖平臺。以下是官網的架構圖

5

Apache Paimon底層儲存利用LSM結構,支援多分散式儲存系統,且相容當下所有主流的計算引擎(Flink,spark,hive,Trino),檔案結構組織類似 Iceberg,相對 Hudi 來說更加簡單和容易理解:

6

同時涵蓋了湖技術目前我們特別關注的幾大特性:

  • 近實時高效更新
  • 區域性更新
  • 增量流讀
  • 全增量混合流讀
  • 多雲端儲存支援
  • 多查詢引擎支援
  • 特別的Lookup能力
  • CDC攝入(進行中)
  • 結構演進(進行中)

2.2 基於Apache Paimon最佳化效果

寫入效能和資源消耗方面,相同的表(均開啟非同步 Compact)基於 Apache Paimon 的資源使用情況如下:

7

<p><center>(上圖為Apache Paimon寫入容器數)</center></p>

8

<p><center>(上圖為Apache Paimon寫入資源配比)</center></p>

在不降低寫入效能的情況下 Apache Paimon 使用了更少的容器數和更低的資源配比。這得益於 SortRun 和 Universal-Compaction 策略的寫最佳化能力,Upsert 效率相對 Hudi MOR 也有較大提升,如下Flink配置的情況下:

parallelish.default : 2
execution.checkpointing.interval : 2 min
taskmanager.memory.process.size : 6g

Upsert 4 億資料,800 個分割槽(實際效果與叢集效能相關與時間段相關,大概做個參考)的場景下, 使用 Apache Paimon 總共耗時3小時左右,而 Apache Hudi MOR 需要耗時10小時左右。

再來看下點查效能

9

相同的條件下 Apache Paimon 只需要 2.7 秒,對比 Hudi 21秒提升巨大。效能提升的主要原因在於有序的儲存結構能夠在資料檢索時快速定位和減少 Scan 數量。

目前我們上線了部分場景的應用,大批次上線之後再觀察 HDFS IO 壓力情況。

三、Apache Paimon的應用實踐

目前我們在內部資料整合中加入了 Paimon 的支援,同時將多個場景切換到了 Paimon,主要包括 Binglog 整合,Partial Update 準實時寬表,以及 Append Only 場景。

3.1 Paimon的自動化資料整合

我們透過整合平臺遮蔽了使用者對 binglog 的感知,透過一鍵的方式完成底層 Base 表全量+增量的同步功能,大致流程如下:

10

使用者更加關注他們所熟悉的 Mysql 以及我們的最終湖倉表,大致整合介面如下:

11

<center>注:Paimon 原名 Flink Table Store</center>

同時我們為了將 Hudi 表遷移到 Paimon 之中,小資料量的我們直接透過重做的方式,而大資料量會透過 Flink 批次匯入方式進行初始化,經過測試,4 億左右的表只需要不到 20 分鐘即可匯入完成,大致匯入配置如下:

INSERT INTO paimon.ods.order_info
/*+ OPTIONS('sink.parallelism'='100','write-buffer-size'='1024m','sink.partition-shuffle' = 'true') */
SELECT
*
FROM
hudi.ods.order_info/*+ OPTIONS('read.tasks' = '100') */
;

另外我們的整合環境和監控針對 Paimon 也進行了一系列最佳化:

  • 根據表資料量來制定特定引數,使使用者無感知
  • 調整分割槽策略和資源,最佳化大量隨機寫情況
  • 構建監控大盤,時刻關注任務執行情況,時刻維持任務正常執行和資源分配的一個平衡點

3.2 基於 Partial Update 的準實時寬表

準實時是介於離線和實時之間,其中準實時寬表是一個常見的案例,主要用來支援 Ad-Hoc Query。在準實時場景下,主要存在如下特點和挑戰:

  • 透過微批排程(分鐘,小時)進行資料更新,但是延遲相對較高
  • 透過流式引擎構建,則會存在保留大量狀態造成資源嚴重浪費的情況

Paimon 提供了 Partial Update 的功能,可透過 Merge-Engine 引數來指定:

'merge-engine' = 'partial-update'

Partial Update 的特點:

  • 結果表欄位由多個資料來源提供組成,可使用 Union All 的方式進行邏輯拼接
  • 資料在儲存層進行 Join 拼接,與計算引擎無關,不需要保留狀態,節省資源

具體案例如下:

案例實踐:資料寫入

--FlinkSQL引數設定
set `table.dynamic-table-options.enabled`=`true`;
SET `env.state.backend`=`rocksdb`; 
SET `execution.checkpointing.interval`=`60000`;
SET `execution.checkpointing.tolerable-failed-checkpoints`=`3`;
SET `execution.checkpointing.min-pause`=`60000`;

--建立Paimon catalog
CREATE CATALOG paimon WITH (
  'type' = 'paimon',
  'metastore' = 'hive',
  'uri' = 'thrift://localhost:9083',
  'warehouse' = 'hdfs://paimon',
  'table.type' = 'EXTERNAL'
);

--建立Partial update結果表
CREATE TABLE if not EXISTS paimon.dw.order_detail
(
    `order_id` string 
    ,`product_type` string 
    ,`plat_name` string 
    ,`ref_id` bigint 
    ,`start_city_name` string 
    ,`end_city_name` string 
    ,`create_time` timestamp(3)
    ,`update_time` timestamp(3) 
    ,`dispatch_time` timestamp(3) 
    ,`decision_time` timestamp(3) 
    ,`finish_time` timestamp(3) 
    ,`order_status` int 
    ,`binlog_time` bigint
    ,PRIMARY KEY (order_id) NOT ENFORCED
) 
WITH (
  'bucket' = '20', -- 指定20個bucket
  'bucket-key' = 'order_id',
  'sequence.field' = 'binlog_time', -- 記錄排序欄位
  'changelog-producer' = 'full-compaction',  -- 選擇 full-compaction ,在compaction後產生完整的changelog
  'changelog-producer.compaction-interval' = '2 min', -- compaction 間隔時間
  'merge-engine' = 'partial-update',
  'partial-update.ignore-delete' = 'true' -- 忽略DELETE資料,避免執行報錯
);

INSERT INTO paimon.dw.order_detail
-- order_info表提供主要欄位
SELECT
order_id,
product_type,
plat_name,
ref_id,
cast(null as string) as start_city_name,
cast(null as string) as end_city_name,
create_time,
update_time,
dispatch_time,
decision_time,
finish_time,     
order_status,
binlog_time
FROM
paimon.ods.order_info /*+ OPTIONS ('scan.mode'='latest') */

union all 

-- order_address表提供城市欄位
SELECT
order_id,
cast(null as string) as product_type,
cast(null as string) as plat_name,
cast(null as bigint) as ref_id,
start_city_name,
end_city_name,
cast(null as timestamp(3)) as create_time,
cast(null as timestamp(3)) as update_time,
cast(null as timestamp(3)) as dispatch_time,
cast(null as timestamp(3)) as decision_time,
cast(null as timestamp(3)) as finish_time,  
cast(null as int) as order_status,
binlog_time
FROM
paimon.ods.order_address /*+ OPTIONS ('scan.mode'='latest') */
;

3.3 AppendOnly 應用

除了 Binlog 資料來源,還有大量日誌、埋點相關的 AppendOnly 資料來源,這類資料基本都是資料量非常大的存在,一般來說,這類資料都是直接消費落在分散式檔案系統上的。

當我們採用 Paimon 來構建 AppendOnly 表時,資料不僅可以實時寫入,還可以實時讀取,讀寫順序一致,而且實時資源消耗也降低了不少完全可以替換部分訊息佇列的場景,達到解耦和降本增效的效果。SQL 如下:

CREATE TABLE if not exists paimon.ods.event_log(
    .......
) 
PARTITIONED BY (......)
WITH (
  'bucket' = '100',
  'bucket-key' = 'uuid',
  'snapshot.time-retained' = '7 d',
  'write-mode' = 'append-only'
);
INSERT INTO paimon.ods.event_log
SELECT 
    .......
FROM 
    realtime_event_kafka_source
;

寫入效果如下:

12

四、問題發現和解決

4.1 Spark 跨 Warehouse 查詢能力調整

當前 Hive Catalog 主要基於 Warehouse 路徑組裝 Paimon 表路徑,在 Spark 環境內宣告 Warehouse 之後不太容易跨 Warehouse 進行多 Paimon 表的查詢。內部我們過載了HiveCatalog 的 getDataTableLocation 方法,基於 Hive 表構建 Paimon 表路徑

    @Override
    public Path getDataTableLocation(Identifier identifier) {
        try {
            Table table = client.getTable(identifier.getDatabaseName(), identifier.getObjectName());
            return new Path(table.getSd().getLocation());
        } catch (TException e) {
            throw new RuntimeException("Failed to get table location", e);
        }
    }

同時也增加了構建 Hive 外部表的能力,[[FLINK-29922] Support create external table for hive catalog](https://github.com/apache/incubator-paimon/pull/357)

4.2 大量分割槽 + Bucket 場景下 Flink 批讀超過 Akka 訊息限制最佳化

實踐過程中如果發現類似以下錯誤,可以適當調大Flink中的akka.framesize引數,預設10M。

2023-03-21 15:51:08,996 ERROR akka.remote.EndpointWriter                                   [] - Transient association error (association remains live)
akka.remote.OversizedPayloadException: Discarding oversized payload sent to Actor[akka.tcp://flink@hadoop-0xx-xxx:29413/user/rpc/taskmanager_0#1719925448]: max allowed size 10485760 bytes, actual size of encoded class org.apache.flink.runtime.rpc.messages.RemoteRpcInvocation was 1077637236 bytes.

最終透過加入分批次Split方式進行解決,[[flink] Assign splits with fixed batch size in StaticFileStoreSplitEnumerator ](https://github.com/apache/incubator-paimon/pull/687),效果如下:

13

4.3 流讀場景下,並行度分配不合理以及基於時間戳讀取過期時間報錯的問題

目前跟進中,[[Feature] Some problems with stream reading](https://github.com/apache/incubator-paimon/issues/699)

五、未來規劃

  • 完善 Paimon 平臺分析等相關生態
  • 基於 Paimon 的流式數倉構建
  • 推廣 Paimon 在集團內部的應用實踐
  • 替換部分訊息佇列的場景

Reference:

作者簡介:

吳祥平:同程旅行大資料計算組負責人,Apache Hudi & Paimon Contributor,對流計算和資料湖技術充滿熱情

曾思楊:同程旅行公共BI資料開發,熱愛流計算和資料湖技術及其實際應用

點選檢視更多技術內容


更多內容


活動推薦

阿里雲基於 Apache Flink 構建的企業級產品-實時計算Flink版現開啟活動:
99 元試用 實時計算Flink版(包年包月、10CU)即有機會獲得 Flink 獨家定製衛衣;另包 3 個月及以上還有 85 折優惠!
瞭解活動詳情:https://www.aliyun.com/product/bigdata/sc

image.png

相關文章