實時物化視圖:加速大規模時間序列數據查詢的利器

一、為什么需要物化視圖
日常生活中,我們每天都會產生大量的數據。根據統計,僅在2020年,人類每天就產生了約2.5EB(即2.5 x 10^18字節)的數據。而預計到2025年,這個數字將會達到463EB(即463 x 10^18字節),增長速度非常可觀。隨著數據規模的不斷擴大,數據分析查詢變得更加復雜和耗時,加速查詢成為分析的關鍵任務。

常用的分析查詢加速手段主要包括以下幾種:
- 緩存:通過將數據從慢存儲介質緩存到快存儲介質,例如內存中,可以在分析數據過程中獲得更快的數據讀取響應,從而實現加速效果。
- 并行計算和分布式計算:將計算任務分解為多個子任務并行處理,充分利用計算資源,提高分析查詢的速度和效率。
- 數據分區和索引:減少查詢時需要掃描和分析的數據量,從而達到加速的效果。
- 預計算:提前對數據進行計算和聚合,并將結果物化存儲,以便在查詢時直接使用計算好的結果來進行加速。物化視圖是預計算的一種重要實現方式。

二、什么是實時物化視圖
物化視圖是預先計算出常用耗時或復雜查詢的結果集,使得在查詢時能夠快速訪問和使用這些預先計算的結果。如下圖左表是一張商品訂單信息表,包含簡單的商品售賣信息。傳統方式下,每次需要計算每個商品的總銷售額時,都要從左邊原始數據表中讀取數據并進行計算,這既耗時又消耗系統資源。通過物化視圖,我們可以事先對數據進行預計算,并將計算結果存儲為右側的結果表。下一次查詢時,只需直接從右表物化視圖中讀取數據,無需再進行復雜的計算操作,節省了大量時間和資源開銷,大幅提升查詢的效率和響應速度。

然而在實際場景中,隨著時間的增長,數據量也會持續增加。不同時間查詢商品總銷售額時,結果也會相應地發生變化,存儲下來的物化數據也需要隨著時間的推移進行更新。從時間序列數據中進行全量數據更新十分耗時,且物化結果會隨著新數據的產生而失效。但時間序列數據通常具有Append-only特性,即已經存在的數據幾乎不會發生變化。在物化數據和計算過程中,可以重復使用歷史的物化計算結果,當新數據到來時,只需對原有的物化預計算數據進行增量更新。
實時物化視圖就是一種適用于時間序列數據的預計算加速過程,其核心思想是將歷史物化數據與計算結果保留下來,并在新數據到來時進行增量更新。在高效地管理和維護物化操作的同時,減少計算的開銷。

在時間序列上實現實時物化視圖的關鍵點主要有三個:
- 存儲:在實時物化視圖中,需要定義好物化表的結構,將物化數據存儲下來。存儲可以采用適當的存儲介質和數據結構,以滿足查詢性能和存儲需求。通過合理的存儲設計,加上合適的索引壓縮方式,可以確保物化數據的高效訪問和使用。
- 更新:實時物化視圖需要進行定期更新或事件驅動的增量更新。定期更新意味著根據一定的時間間隔,例如每小時、每天或每周對物化數據進行更新。事件驅動更新則是根據數據的變化事件觸發更新操作。通過定期更新或事件驅動更新,可以保持物化數據與原始數據的一致性,并確保查詢結果的準確性。
- 預計算:在實時物化視圖中,預計算是一個重要環節,需要提供增量更新下的預計算方式。

三、怎樣實現實時物化視圖
炎凰產品本身是一個面向可觀測性數據的分析平臺,可觀測數據天然帶有時間戳,分析時會自然而然給查詢帶上查詢時間窗。炎凰產品在設計實時物化視圖時,期望物化視圖的查詢能適應任意的時間窗口,并得到準確的查詢結果。基于這樣的目標進行了如下實現。
對隨著時間增長的原始時間數據,通常會拆分成小的數據塊分片存儲。分片規則通常基于導入時間范圍、累積數據量、其他標簽信息等進行分片。針對每個原始時間數據分片分別進行預計算得到對應的物化數據分片,當有新數據來臨時,只需要對新數據進行物化預計算操作。

得到每個原始數據分片對應的預計算物化數據分片后,查詢時通常要把這些物化分片合并再聚合。該過程類似于常見的Map-reduce計算框架:Map過程將數據拆分成小數據塊,分發到不同計算節點進行計算,得到部分數據的部分聚合結果。Reduce階段將部分結果合并再聚合得到最終結果返回給請求端。實時物化視圖的實現中,對每個原始數據分片進行物化預計算操作類似于Map操作。這意味著每個分片都會獨立進行預計算,生成部分聚合結果。而在查詢結果時,需要將這些物化數據分片進行合并聚合,類似于Reduce操作。
需要注意的是,實時物化視圖的實現過程中,每個數據分片進行預計算的時機是靈活的。它可以根據需要隨著時間的增長而分別進行一些預計算操作。這意味著不同的分片可能會在不同的時間點進行預計算,根據數據的變化和更新情況來動態地進行預計算操作。


以價格的平均值為例,如上圖所示,基于原始數據表①求價格的平均值,通常會先求和再計數,將中間預計算結果②存儲下來。依此類推,對每個原始時間序列數據分片計算對應的部分數據的部分聚合計算操作。查詢結果時只需要從表②中讀取部分聚合結果,對其合并得到如表③的平均值。
在查詢時,查詢時間范圍通常不會覆蓋完整的物化分片所對應的時間范圍,可能只包含某些物化分片的部分時間范圍,也可能包含正在導入的數據或沒有物化數據的時間范圍。為了適應不同時間范圍的查詢,我們期望預計算物化數據保留一定的時間性。上述商品訂單信息①中,會天然帶有時間戳信息,得到的部分聚合結果②,是十分單薄的數據表總和和數量值,完全喪失了時間信息。在此基礎上對預計算數據進行時間分桶,例如將②中結果進行一小時的分桶,得到右下方綠色表的中間結果。該預計算中間結果帶有時間信息,包含每一小時時間桶下部分數據的部分聚合結果。

將上述計算價格平均值AVG的過程抽象成計算表達式:在預計算過程中會保留SUM和COUNT值,在最后請求查詢結果的時候通常會對SUM求和之后再除以SUM數量值,這樣就得到了最后查詢請求結果SUM(SUM)/SUM(COUNT)。在這個基礎上想要對預先物化數據進行時間分桶,相當于在預計算步驟中加上GROUP BY操作。具體見下圖。


至此,我們得到了每個原始數據分片對應的預計算物化結果分片,每個物化分片包含了時間桶信息。查詢物化視圖時,查詢時間窗口可能包含完整的物化分片范圍,也可能包含正在導入的或者沒有物化的數據分片的時間范圍。如上圖所示,將時間窗口按照是否已預計算物化進行劃分。在物化分片4-7對應的時間范圍上,可以采用預計算好的物化分片中的存儲結果參與計算,這作為第一部分P1。對于右側正在導入的數據,還在內存中未進行數據分片落盤,需要直接從原始數據中讀取數據參與計算,這作為第二部分P2。對于左側剩余時間范圍,包含了物化分片3的部分時間范圍,由于物化分片3上的數據已經進行了部分聚合操作,喪失了完整的原始時間信息,因此沒有辦法完全利用物化分片完整的部分聚合結果參與計算。將這部分數據按照時間分桶進行拆分,落在完整時間桶中的數據可以從物化分片結果中讀取數據參與計算,作為第三部分P3。沒有落在完全時間桶范圍中的時間范圍,只能從原始數據分片中讀取數據參與計算,作為第四部分P4。通常P1加P3占查詢時間范圍比例比較高,當我們在查詢物化視圖時,對這部分數據進行了預計算加速,從而加速整個查詢。

P3、P4部分的劃分略顯抽象,假設查詢起始時間為11:22,給定時間分桶大小是1小時(1h),物化分片3占用的時間分桶為10:00到14:00。那么以12:00為分界限,則12:00-14:00部分時間已經落入完整時間桶范圍即是P3部分。而11:22-12:00沒有完整落入時間桶范圍,必須從原始數據中讀取時間參與計算,即為P4部分。

以上,我們將查詢時間范圍劃分成了四個部分。第一部分是包含完整物化分片所在時間范圍的數據。第二部分是沒有物化的數據的時間范圍。這里可以是正在導入的數據時間范圍,也可能是創建完物化視圖之后還沒有來得及進行物化預計算的數據時間范圍。第三部分是已經物化的數據但只取了部分時間桶里的數據結果。第四部分雖然已經進行過物化但由于丟失部分時間信息,沒辦法從時間桶的部分聚合結果中直接使用參與計算?;谶@四部分時間窗口的劃分,對P2和P4的時間窗口的數據,需要在查詢時進行預計算相關的操作,例如AVG需要進行SUM/COUNT的計算。對于P1和P3數據已經做了聚合操作,直接從物化分片中讀取聚合結果就可以了。得到這兩部分數據再將所有SUM和COUNT進行聚合就得到了最終的查詢結果。這樣就完成了查詢物化視圖適應任何時間窗口的目標,并保證結果一致性。

炎凰產品提供SQL接口進行查詢分析,針對上述實現定義了物化視圖創建的DDL。如上圖所示,在DDL中給定WITH語句,提供了創建物化視圖的兩個參數。前文中四個時間窗口中未物化數據部分,是需要從原始數據中去讀取數據的,當物化程度比較高時,這部分數據所占比例會非常小,當請求端不需要非常準確的查詢結果時,往往可以忽略這部分數據不參與計算,DDL建模時可以通過指定MATERIALIZED_ONLY=true在查詢中忽略這部分數據,進一步加速查詢分析。TIME_BUCKET即是前述時間桶的配置,該參數需要根據常用查詢的時間范圍和數據的時間分布來自定義。另外,在創建物化視圖時,如果不需要關注過去數據的情況,就可以指定WITH NO DATA,不對過去數據進行后臺的預計算操作。同時,炎凰產品提供了SHOW MATERIALIZED VIEW語句來展示物化視圖的基本信息和狀態。

上圖在炎凰產品中定義了一個簡單的數據集,查詢本身帶有時間窗口。點開每條數據可以看到數據上的每個字段。在這個數據集上創建一個時間桶大小為1小時的物化視圖,計算平均值,如下所示。

通過SHOW語句可以看到物化視圖的基本信息,并提供了對應物化進度的狀態展示,物化進度是通過已經物化的數據分片占所有需要物化數據分片的比例來表達的。

在炎凰產品中,預計算結果存成了Parquet數據格式。Parquet是一種網格存儲,使用了高效的壓縮算法和數據編碼方式實現數據精簡,能夠減少數據IO,同時提高性能。實時物化視圖更新是系統自動維護的,是數據變化事件驅動更新。當有新數據到達或滿足某些特定條件時候系統會自動觸發相應預計算和更新操作,更新操作范圍是以數據分片為單位的,這種事件驅動方式使物化視圖能夠靈活響應數據變化和查詢請求,保持物化視圖的實時性。前文中描述的例子多以聚合為主,物化視圖的定義也可以使用非聚合的查詢,當我們只需要某部分字段或者某些過濾條件下的特定數據時,也可以創建物化視圖來加速查詢分析。
這種實現下物化視圖的性能,與數據的時間分布、查詢的時間范圍,以及分片大小、時間分桶的大小等因素息息相關。這里我們用產品進行一個簡單的性能測試,在炎凰系統中打開調試debug日志,每天大約能收集到1億條數據,占用1000個數據分片。在這樣的數據上查詢每個數據集被使用的次數時,需要40s。

分別在該數據集上創建分桶大小為1小時和1天的實時物化視圖。如下圖可以看到查詢性能有明顯改善,時間分桶大小為1天的物化視圖查詢效率優于1小時,這是由于查詢的時間窗口大小為1天和時間桶大小一致。原始數據分片存儲大小為15M/Slice,不同時間分桶下的物化分片在存儲上的優勢相當,這得益于parquet的高性能存儲。

四、展望和總結
前文介紹了產品中對實時物化視圖的實現。我們還會對物化視圖進行進一步的探索:
- 智能路由:當查詢原始數據時,利用了物化視圖中的一些聚合邏輯或過濾邏輯時,系統可以自動將基于原始數據的查詢改寫成基于物化視圖的查詢,進行智能路由,自動為查詢加速。
- 分層物化:文中提到的物化存儲主要是按時間范圍劃分的,但實際上,物化結果物理表的存儲,可以按特定條件進行進一步分區,從而進一步加速。
- 另一種ETL:物化視圖的維護過程是從原始數據中讀取數據,進行預計算操作,再將預計算結果存儲下來,這一過程是非常類似于ETL的。在系統中針對數據的所有計算操作都可以用DLL來表達,包括索引過程、查詢過程等。

本次主要分享了實時物化視圖的實現。實時物化視圖通過預先物化和存儲物化數據,使得查詢操作可以從物化視圖中直接獲取數據,而不需要重新在原始時間序列數據上執行復雜操作,從而大大提高了查詢性能。實現中將數據進行時間分桶,以數據事件觸發自動維護更新,保持了物化視圖查詢與原始數據查詢的實時一致性。但實時物化視圖會帶來額外的存儲空間以及維護成本,使用物化視圖需要結合實際場景,充分考慮查詢的時間窗口范圍、數據時間分布,來定制分析加速。

在物化視圖方面還有很多值得探索的方面,期待以后有機會和大家交流分享。
五、Q&A
Q1:物化視圖能替換現有報表模型嗎?
A:會在報表中利用物化視圖加速。很多時候會去頻繁查看報表,報表背后的查詢通常會使用物化視圖查詢,加速報表展示。
Q2:這里提到具體物化視圖的實現,具體是用哪個框架實現的?
A:這是炎凰產品自己的物化視圖實現,沒有依賴于其他框架。
Q3:這里涉及的聚合函數有什么要求嗎?
A:在整個分享過程中,講述了實時物化視圖的基本實現思路。預計算過程和系統底層的計算引擎相關。當計算引擎能支持聚合計算預計算(pre-aggregation)和后處理(post-aggregation)的操作分解時,在實時物化視圖中都可以使用。通常而言,物化視圖不支持非確定聚合計算、非線性聚合計算、依賴于動態參數的聚合計算等。

























