在企業數據架構逐步走向實時化與一體化的過程中,如何高效處理“大量歷史 + 少量新增”的業務數據,已成為建設統一數倉與實時數倉時繞不開的關鍵挑戰。
傳統全量刷新方式在面對億級歷史數據時,往往面臨刷新延遲高、計算成本大、鏈路複雜等問題。為了解決這些痛點,業界逐漸形成了一種新的數據處理範式——Dynamic Table(動態表),它通過聲明式語法自動維護物化結果,並支持高效的增量刷新能力。
阿里雲 Hologres 作為高性能實時數倉引擎,原生提供了 Dynamic Table,並基於有狀態增量計算模型,在多表關聯、聚合等複雜場景下展現出顯著性能優勢。本文將深入解析 Hologres Dynamic Table 的技術原理與實踐價值。
為什麼需要增量刷新能力?
典型業務場景
在電商、互聯網、金融等行業,以下場景極為常見:
實時運營分析
- 訂單、支付、退款等多源數據,按用户、商品、活動等維度進行關聯與聚合,形成實時運營看板;
- 運營與業務團隊希望以分鐘級、甚至更高頻率刷新核心指標。
用户與商品特徵構建
- 將用户信息、行為數據、訂單數據、商品信息、支付信息等多張表進行 Join,生成統一的特徵寬表;
- 這些寬表通常是推薦、風控、畫像等應用的輸入,需要穩定、快速地更新。
資金與交易監控
- 交易流水、賬户信息、風控結果等數據源不斷產生新的記錄;需要以較短的刷新間隔計算聚合指標或風控特徵。
這些場景的共同特點是:
歷史數據規模龐大(億級),但每次新增或變更的數據量很小(通常 <1%)。
傳統數據加工的侷限
在缺少增量引擎的情況下,常見做法是:
- 使用一條或多條 SQL 定義目標寬表或彙總表;
- 定時執行全量計算(如 INSERT OVERWRITE、CTAS),每次從基表完整掃描數據、執行多表 Join、再進行聚合;
- 通過調度系統編排上游任務和下游任務之間的依賴。
這種模式存在若干明顯不足:
- 刷新時延受限:數據規模增大後,每次全量掃描和計算耗時較長。當業務希望從“每小時刷新”提升到“每 5 分鐘刷新”時,往往需要成倍增加計算資源,也可能遇到物理資源上限。
- 計算成本較高:即使每次只有 1% 左右的數據發生變化,全量模式仍需對 100% 數據進行掃描和計算,CPU、IO 和網絡資源利用效率不高。
- 鏈路複雜,維護成本高:為降低單任務壓力,工程實踐中常將複雜邏輯拆解為多層中間表,形成較長的任務鏈路。鏈路越長,依賴越多,維護難度和變更風險也隨之上升。
因此,在“歷史數據量大、實時數據實時產生”的場景下,引入真正高效的增量刷新機制,是提升數據時效與降低資源利用率的關鍵。
二、什麼是 Dynamic Table?Hologres 的增量刷新如何工作?
Dynamic Table是當前一種主流的聲明式數據處理架構,該架構可以自動處理並存儲一個或者多個基表(Base Table)對象的數據關聯、聚合結果,內置不同的數據刷新策略,業務可以根據需求設置不同的數據刷新策略,實現數據從基表對象到Dynamic Table的自動流轉,滿足業務統一開發、數據自動流轉、處理時效性等訴求。
增量(incremental)、全量(full)是Dynamic Table的兩種不同刷新方式,底層實現原理具有顯著的差異:
- 全量刷新是指每次執行刷新時,都以全量的方式進行數據處理,並將基表的關聯、聚合結果物化寫入Dynamic Table,其技術原理類似於INSERT OVERWRITE。
- 增量刷新模式下,每次刷新時只會讀取基表中新增的數據,根據中間聚合狀態和增量數據計算最終結果並更新到Dynamic Table中。相比全量刷新,增量刷新每次處理的數據量更少,效率更高,從而可以非常有效地提升刷新任務的時效性,同時降低計算資源的使用。
增量Dynamic Table的計算遵循如下計算模式:
- 增全量刷新階段:Dynamic Table的第一次刷新會把已有的所有歷史數據都進行計算,即相當於把所有歷史數據都當作增量來進行計算。這一階段一般耗時比較長。
- 增量刷新階段:在增全量刷新完成後,以後的每次Dynamic Table刷新僅針對增量數據進行計算。理論上這些刷新應該耗時較短。
在增量 Dynamic Table 的實現上,業界存在不同的技術路徑。一種常見的做法是採用無狀態增量計算模型:每次刷新時,系統僅基於源表的變更數據,重新推導整個查詢邏輯,而不持久化任何中間計算狀態。這種方式雖然節省存儲,但在面對複雜查詢(如多表關聯、去重聚合等)時,往往需要反覆掃描大量歷史數據,導致刷新效率低下,甚至在某些場景下增量計算的開銷反而超過全量。
相比之下,Hologres 採用了有狀態增量計算模型:在首次全量構建 Dynamic Table 時,同步生成並持久化關鍵的中間狀態(例如聚合結果、多表 Join 的中間產物等)。後續的增量刷新只需將新增或變更的數據與這些狀態表進行高效合併,無需重複處理歷史數據。這種設計以有限且可控的額外存儲開銷為代價,顯著提升了複雜場景下的刷新性能和資源利用率,尤其適合“海量歷史 + 少量增量”的典型業務負載。
三、Hologres 增量刷新的實戰優勢
我們通過三個典型場景驗證 Hologres 的性能表現(測試環境:Hologres 15CU Serverless,競品採用相似規格)。
對於增量數據,本實驗模擬兩種場景:
- Append only:即源表的增量數據只有新增(Insert),沒有修改(update和delete)。這在日誌、埋點數據表中非常常見。
- Retraction(回撤):源表的增量數據包含Insert、Update和Delete的數據。這適用於數據庫類的表。
對於Append only的源表,很多增量計算算子都可以大幅簡化,狀態表也可以大幅縮小。所以在性能環節,可以看到Append only源表的增量計算性能會更好。
場景 1:單表聚合(COUNT DISTINCT + SUM)
該場景使用的工作負載如下所示:
Hologres建表SQL如下:
CREATE DYNAMIC TABLE DT_ORDER_DETAIL_AGG with (
auto_refresh_mode = 'incremental',
auto_refresh_enable = 'false',
freshness = '1 minutes'
)AS
SELECT
PRODUCT_ID,
COUNT(DISTINCT USER_ID) AS UV,
SUM(LINE_AMOUNT) AS SUM_LINE_AMOUNT,
MAX(QUANTITY) AS MAX_QUANTITY
FROM ORDER_DETAIL
GROUP BY PRODUCT_ID;
測試結果如下表所示(完整測試SQL見附錄):
| 刷新 | 源錶行數 | 某國際知名產品無狀態增量計算刷新耗時(s) | Hologres刷新耗時(s) | |||
|---|---|---|---|---|---|---|
| Retraction | Appendonly | Retraction | Appendonly | Retraction | Appendonly | |
| 增全量刷新 | ORDER\_DETAIL: 10M | 1.5 | 1.3 | 4.6 | 3.9 | |
| 增量第一次 | 每張表
UPDATE 0.5% INSERT 0.5% |
每張表
INSERT 1.0% |
7.8 | 2.4 | 0.59 | 0.48 |
| 增量第二次 | 8.1 | 2.7 | 0.49 | 0.41 | ||
| 增量第三次 | 7.8 | 2.9 | 0.45 | 0.3 |
可以看到Hologres增全量刷新階段較慢,但後面的每次增量刷新都很快,符合預期。
無狀態增量刷新性能較差的主要原因是無狀態增量執行計劃變得更加複雜,包含36個計算節點,且變更數據觸及了大量分區,需要從源表重新掃描計算所有的數據。在有回撤數據時,處理數據變更前後因果關係會導致計算邏輯會變得更加複雜,進一步變慢,增量計算顯得沒有意義。
Hologres增量刷新快的核心原因是每次增量計算都是基於上次計算生成的狀態表(State),這極大的簡化了增量計算邏輯。此例中Hologres中各表存儲大小如下所示(源表是Retraction的情況),結果表很小,而因為min/max/count distinct這兩種聚合函數與sum/count這類不同,狀態表需要存儲對應列的所有原始數據,所以相比結果表要大很多。
| 源表 | 結果表 | 狀態表 | |
|---|---|---|---|
| 存儲 | 252 MB | 1 MB | 93 MB |
場景 2:兩表 Join(訂單 + 明細)
兩表Join是大數據處理中一種較為簡單的基本場景,測試使用的具體工作負載如下圖所示
Hologres建表SQL如下:
CREATE DYNAMIC TABLE DT_ORDER_DETAIL
WITH (
auto_refresh_mode = 'incremental',
auto_refresh_enable = 'false',
freshness = '1 minutes'
) AS
SELECT
o.ORDER_ID,
o.ORDER_DATE,
o.ORDER_STATUS,
oi.ORDER_ITEM_ID,
oi.PRODUCT_ID,
oi.QUANTITY,
oi.UNIT_PRICE,
oi.LINE_AMOUNT
FROM ORDERS o
JOIN ORDER_ITEMS oi
ON o.ORDER_ID = oi.ORDER_ID;
測試結果如下表所示(完整測試SQL見附錄),無狀態增量刷新引擎在數據含有回撤的時候執行計劃包含50個節點,源表大部分分區的數據被反覆讀取參與聚合,導致性能不佳。而數據不包含回撤(Appendonly)時,無狀態增量刷新執行計劃相對簡單,含有35個節點,且新增數據不會觸及太多分區,表現良好,體現出了增量計算的意義。
| 源錶行數 | 某國際知名產品無狀態增量計算(s) | Hologres刷新耗時(s) | ||||
|---|---|---|---|---|---|---|
| Retraction | Appendonly | Retraction | Appendonly | Retraction | Appendonly | |
| 增全量刷新 | ORDERS: 5M
ORDER\_ITEMS: 10M |
10 | 8.7 | 9.2 | 6.29 | |
| 增量第一次 | 每張表
UPDATE 0.5% INSERT 0.5% |
每張表
INSERT 1.0% |
22 | 1.1 | 2.2 | 0.68 |
| 增量第二次 | 19 | 2.2 | 1.1 | 0.50 | ||
| 增量第三次 | 22 | 1.7 | 1.9 | 0.51 |
Hologres中各表存儲大小如下所示,在這種場景下狀態表存了源表的部分列數據,實際存儲小於源表。
| 源表 | 結果表 | 狀態表 | |
|---|---|---|---|
| 存儲 | 370 MB | 350 MB | 228 MB |
場景 3:五表複雜 Join(訂單 + 用户 + 商品 + 支付等)
多表Join是一種相對更常見、真實的場景,測試具體使用的工作負載如下:
Hologres的測試腳本如下:
CREATE DYNAMIC TABLE DT_ORDER_DETAIL
WITH (
auto_refresh_mode = 'incremental',
auto_refresh_enable = 'false',
freshness = '1 minutes'
) AS
SELECT
o.ORDER_ID,
o.ORDER_DATE,
o.ORDER_STATUS,
u.USER_ID,
u.USER_NAME,
u.EMAIL,
u.STATUS AS USER_STATUS,
oi.ORDER_ITEM_ID,
oi.PRODUCT_ID,
p.PRODUCT_NAME,
p.CATEGORY,
p.PRICE AS PRODUCT_PRICE,
oi.QUANTITY,
oi.UNIT_PRICE,
oi.LINE_AMOUNT,
pay.PAYMENT_ID,
pay.PAY_AMOUNT,
pay.PAY_METHOD,
pay.PAY_TIME
FROM ORDERS o
JOIN USERS u
ON o.USER_ID = u.USER_ID
JOIN ORDER_ITEMS oi
ON o.ORDER_ID = oi.ORDER_ID
JOIN PRODUCTS p
ON oi.PRODUCT_ID = p.PRODUCT_ID
LEFT JOIN PAYMENTS pay
ON o.ORDER_ID = pay.ORDER_ID;
測試結果如下表所示(完整測試SQL見附錄),無狀態增量計算引擎無論是隻包含插入還是有回撤,性能都相對較差,原因也是類似的。五表Join的場景下,增量計算的執行節點超過140個,會大量讀取源表數據。
| 源錶行數 | 某國際知名產品無狀態增量計算(s) | Hologres刷新耗時(s) | ||||
|---|---|---|---|---|---|---|
| Retraction | Appendonly | Retraction | Appendonly | Retraction | Appendonly | |
| 增全量刷新 | ORDER\_ITEMS: 10M
PAYMENTS: 4.9M ORDERS: 5M PRODUCTS: 50K USERS: 500K |
23 | 23 | 30 | 22 | |
| 增量第一次 | 每張表
UPDATE 0.5% INSERT 0.5% |
每張表
INSERT 1.0% |
55 | 26 | 3.9 | 2.8 |
| 增量第二次 | 58 | 27 | 3.2 | 1.8 | ||
| 增量第三次 | 60 | 26 | 2.9 | 1.8 |
Hologres中各表存儲大小如下所示,狀態表會存儲每一次Join的中間結果
| 源表 | 結果表 | 狀態表 | |
|---|---|---|---|
| 存儲 | 594 MB | 1218 MB | 1094 MB |
四、有狀態增量計算:為何更高效?
基於前面的實驗結果,不難看出,無狀態增量計算方案適用條件其實比較苛刻,在很多場景中基於少量數據的增量計算開銷甚至會超過第一次的全量計算,喪失了增量計算的意義。
而相比較之下,Hologres的有狀態增量計算方案可以適用於大多數的場景,通常只需要滿足增量數據較少這一個條件即可。下圖以單表聚合場景(sum(value) group by key)為例,展示了有狀態方案的基本原理,增量計算過程中可以從狀態表中直接獲取歷史數據的聚合結果,而不需要基於歷史表的原始數據重新計算。此外在讀取狀態表數據時,也進一步引入了OLAP查詢中常用的runtime filter優化,在增量數據較少的場景中,大幅減少狀態表數據讀取量,使刷新性能得到了顯著的提升。
有狀態方案一個顯著的缺點是與無狀態方案相比會需要佔用額外的存儲空間用於狀態表的存儲。如上述實驗數據所示,實際額外存儲大小通常與涉及到的源表、結果表的大小相關。
這一缺點通常是可以接受的,因為在業務實踐操作中,這部分存儲一般是可控的,不會無限增長。這是因為:
- 分區表的場景中,只有活躍分區需要狀態表,歷史分區轉全量刷新後不再需要狀態表(這個過程是自動的,分區不再活躍後會自動清理狀態表)。因此只有最近的一兩個分區才需要狀態表,這極大地減少了狀態表的存儲空間。因此Hologres Dynamic Table增量計算狀態表沒有Flink常見的狀態膨脹問題。
- 非分區表場景中,可以為狀態表配置合適的TTL,丟棄一些不再需要的歷史狀態減少存儲空間
總結:Hologres Dynamic Table 的核心價值
面對企業日益增長的實時分析需求,Hologres 的 Dynamic Table 通過有狀態增量計算引擎,從根本上解決了傳統方案在複雜查詢下“增量不增效”的痛點。它不僅大幅縮短了數據刷新延遲,還顯著降低了計算資源消耗和運維複雜度,真正實現了“寫一次 SQL,自動高效更新”的體驗。無論你是構建實時看板、用户畫像寬表,還是風控特徵管道,Hologres 都能以穩定、高性能、低成本的方式支撐你的核心數據鏈路。
附錄
無狀態 & 有狀態增量計算底層原理對比分析
從上面的實驗結果來看,Hologres的有狀態實現方案在大多數的場景中是要優於無狀態增量計算引擎的,本小節將對兩者的底層計算原理進行對比分析,説明造成這種性能差異的根本原因。
符號説明
多表Join場景
無狀態實現Hologres Dynamic Table:高效增量刷新,構建實時統一數倉的核心利器
無狀態多表Inner Join的增量計算公式如下(原理可參考論文),根據實際執行計劃推測某無狀態增量計算引擎的多表Join增量計算應該也是基於該公式實現的
該方案主要會有如下弊端:
有狀態實現
Hologres的有狀態多表Join計算公式原理大致如下,因為所有的中間計算結果狀態會通過狀態表(State)持久化保存下來,因此可以按照兩表Join的公式做簡單展開
有狀態方案以額外的存儲開銷為代價換來了:
單表聚合場景
單表聚合場景相對較為簡單,無狀態、有狀態兩種方案的計算公式原理分別如下:
公式大體是比較相似的,主要區別是Hologres的有狀態方案持久化存儲了歷史數據的聚合結果,因此有以下優勢:
- 不需要重新計算曆史數據的聚合結果
- State表數據量極小,Join 操作可以顯著減少讀取數據量