指標計算實踐

有了資料開發測試工具及DWD模型,資料開發看起來可以順利往前推進了。下一步是資料開發真正產生業務價值的過程,即指標計算。前面的基礎建設其實都是為了指標計算能高效高質量的完成。本文將嘗試分享一些關於指標計算的實踐經驗。

在前面的文章資料平臺數據管理實踐中,我們提到了基礎資料層(也常被稱為輕度彙總層)。這一層一般以DWB的縮寫來表示,其全稱是Data Warehouse Basis。DWB這樣的資料分層是業界常見的資料倉庫分層實踐,對指標計算有很好的參考意義。

指標計算除了要處理指標邏輯之外,一個核心實踐就是抽象和構建DWB資料層。本文將嘗試分享一般的DWB構建過程,並從開發工具支援上提供一些思路來輔助解決資料應用中的複用問題。

資料應用中的複用

回顧前面文章中計算空調銷量的例子,我們會考慮訂單的狀態,產品的範圍等因素。在實現時,一般需要在第一步就根據這些條件做資料過濾,選出來需要做計算的資料。我們常常將這類資料過濾邏輯稱作取數邏輯。

同樣取數邏輯常常會在很多其他指標計算中使用。比如,當需要統計某一個使用者產生了多少筆訂單以確定高價值使用者時,這裡的指標取數邏輯就可能跟空調銷量指標的取數邏輯相同。這提醒我們需要進行一定的抽象將這部分邏輯在專案中複用起來,以便可以有效的避免bug,並提高交付效率。

在一般的功能性軟體開發中,我們可以透過程式碼複用來解決這個問題(比如抽象一個公共的模組)。在資料開發中,除了程式碼複用,還需要考慮計算複用,因為很多大資料量的計算是比較消耗資源的。

計算複用的一個典型示例還可以從“輕度彙總層”這個名字中引申得到,即某些高階彙總指標可以透過輕度彙總指標計算得到。比如計算空調銷量,在業務上,除了希望能計算每日銷量,還需要計算每月銷量。在計算月銷量時,可能可以根據日銷量彙總得到。如果這樣做,統計月銷量可能只需要計算幾十條資料就可以了,這可以非常快速的完成。

在資料應用開發中,我們需要有一些解決複用問題的方案。

構建DWB層

解決資料應用中的複用問題的一個常用思路就是抽象出DWB資料層。由於DWB的資料常常是由一個獨立的資料任務產生,所以它同時解決了程式碼複用和計算複用的問題。

如何構建一個好的DBW資料層呢?

可以採用程式碼重構的思路。比如,在開發第一個指標的時候,我們將所有的程式碼放在了一起。開發第二個指標的時候,我們發現可以和第一個指標有一定的邏輯複用,於是我們抽象了一個DWB層的資料表,將公共的計算邏輯抽取出來用於構建這個表。構建這個表的程式碼一般還會形成一個獨立的資料計算任務,在資料管道的另一個任務中執行。經過幾輪重構之後,我們將得到一些比較穩定的公共層資料表,DWB資料層也就慢慢豐富起來了。

採用重構的思路構建DWB層資料表存在效率不高的問題。因為計算額外的資料表並不只是需要修改程式碼,我們還常常需要因此多次重跑資料。對於很多指標而言,都需要計算歷史資料指標,這裡的量級通常是很大的,在我們的實踐過程中,重新跑一次全部歷史資料可能需要一天到一週。相比修改程式碼,其實重新跑資料花費的時間更長。

更高效的做法可能是一開始就能有一個好的DWB表設計。這需要對業務和資料有足夠的瞭解,同時有較多的資料開發經驗。從我們的實踐來看,這裡的設計也有不少值得參考的經驗。

從需要設計的資料表來看,一般的資料統計都會基於事實表展開,所以,我們常常可以對常用的事實表設計對應的DBW表。

從特定表的設計來看,首先是要選擇合理的粒度。一般而言,可以選擇輕度彙總粒度,也可以選擇細節粒度。為了能靈活的支援所有上層指標,選擇細節粒度的情況可能是居多的。選好粒度之後,主要有兩種設計思路可以參考。一是建立少量欄位的全量表,二是建立較多欄位的增量表。

輕度彙總粒度

如果選擇了輕度彙總粒度來構建DWB層,我們會發現一些DWB直接就儲存了最終需要的指標資料。比如每日銷量可以作為一個輕度彙總指標,它可以支援高階彙總指標月銷量的計算。

這看起來有點奇怪,不過我們也無需擔心,只需要在DM輸出層建立一個數據對映即可。這一對映可以透過構建一個簡單的物理表來實現。如果不想管理資料任務,也不想產生資料複製,還可以考慮透過資料表檢視來實現。

少量欄位的全量表

接下來,我們來看一下如何建立少量欄位的全量表。為了闡述這一設計思路,我們主要需要回答幾個相關問題。

為什麼需要全量表?答案很簡單,因為很多統計需要提取所有資料進行計算。空調銷量就是一個例子,從邏輯上看,其統計時間範圍是全量資料。有人可能會覺得是不是可以基於每天的銷量資料進行彙總。這不總是能得到正確的值,因為訂單存在取消、退貨等情況。一個正確的銷量統計可能需要根據所有訂單的最新狀態進行計算。

為什麼是少量欄位呢?這裡的設計方式是建立全量表,即每天的資料分割槽都是一份全量的資料。既然如此,如果大量的欄位都需要每天覆制儲存,那將帶來巨大的儲存空間佔用。

欄位少到什麼程度是合適的呢?這個問題並沒有統一的答案。可以參考以下做法:

一般而言,我們需要提取所有的狀態欄位(如訂單的狀態,刪除標記狀態等),然後根據這些狀態欄位計算一些標記欄位供上層使用。比如可以將完成狀態且非內部獎勵且非翻新產品訂單標記為is_new_valid_order,在計算銷量的時候,可以簡單的按這個標記欄位進行資料過濾。

一些常用的關聯維度放到這個全量表通常也是合適的。某些資料量特別大的關聯表的維度,比如使用者的年齡、性別等,尤其可以考慮放到全量表中。由於資料量大(使用者表通常可以到千萬級別),表關聯是很慢的,放在DWB資料層來完成就可以避免上層多次進行資料關聯,從而提高效率。某些資料量特別小的關聯表的維度,比如經銷店的屬性,可以考慮在全量DWB表中僅保留關聯經銷店ID,然後在上層進行表關聯獲取相關維度。是否要應用這個建議可能還需要評估加入這些維度之後會帶來多大的儲存增量,由於資料都是壓縮儲存的,這裡帶來的額外儲存可能沒有想象的那麼大。

較多欄位的增量表

一些指標的計算無需使用全量資料,這樣的指標計算就可以只關心每日增量資料了。可以根據每日增量資料構建DWB表。

對於這樣的增量資料DWB表,其資料量通常不大,因此,我們常常可以在此完成大部分維度的統一關聯。這樣上層的指標計算將能更簡單更快的完成。

除了可以在此類DWB表中儘可能多的儲存關聯維度,計算並存儲上面提到的標記欄位也是合理的。這樣可以推進取數邏輯複用,並有效簡化上層指標計算的程式碼。

增量的新增欄位

DWB表的設計很難一蹴而就,因為指標需求往往不是一開始就確定的,而是隨著業務的發展逐步完善的。因此,DWB表的設計需要具備一定的擴充套件性。這裡的擴充套件性可以透過一定的方法來實現,比如:

在全量表中儘量保留所有的資料,避免出現由於資料不夠用需要全部重新構建資料表的情況

在新加欄位之後,DWB的資料需要重跑,通常耗時很長。此時可以建立一個臨時的DWB表,將資料輸出到這個臨時表中。一旦所有歷史資料準備完畢,再一次性將原表歸檔(可以透過重新命名實現)並將臨時的DWB表重新命名為原表名

採用上述這樣增量的方式完善DWB表,將可以有效減少由設計修改帶來的對生產正在執行的指標的影響。

程式碼複用

構建一個物理的DWB層有一定的副作用,主要是需要管理額外的資料計算任務,並且,由於額外的資料計算任務的出現,DWB層計算邏輯的變更可能需要引入大量的資料重新計算。此時,我們也可以考慮只在程式碼層面進行復用,不構建單獨的物理資料分層。

在剛開始的時候,DWB的設計還不夠穩定,經常需要修改,DWB層的變更會尤其頻繁,在此時選擇只在程式碼層面進行復用就是一個好的時機。

我們的指標計算程式碼一般都是透過SQL編寫而成,如何實現SQL程式碼的複用呢?這可能需要新的技術,或從資料工具上做一些支援。

檢視技術

一個可選的可直接替代物理DWB分層的技術是檢視。特別是對於增量的DWB表,可以考慮用檢視的方式來構建。

透過檢視構建DWB表有一些前提條件,那就是所有分割槽的計算邏輯是一致的,也即每個分割槽的資料可以由DWD層的對應分割槽計算得來。比如活躍使用者數指標,如果計算口徑是最近三天有互動,其計算需要選擇最近三天的資料,這就不適合用檢視來解決問題了。

除了檢視技術,還有一個新的選擇,那就是物化檢視。

Hive 3。0中引入了對Materialized view的支援,這就是物化檢視了。顧名思義,物化檢視就是物理化的檢視,即提前計算好的檢視,相當於有一個物理表。

在進行檢視查詢時,SQL引擎會將檢視對應的SQL擴充套件到待執行的SQL中,所以實際的查詢常常很複雜,速度也很慢。而物化檢視就可以解決這個問題。

使用物化檢視時,我們無需關心這個物理表的資料是什麼時候跑出來的,也無需關心什麼時候應該更新,Hive幫我們管理好了這一切。

由此看來,相比檢視,物化檢視可能是更好的選擇。不過,Hive中的物化檢視需要開啟事務支援,這增加了一些限制。

SQL片段共享

除了可以用檢視技術來實現程式碼的複用,另一個更直接的方式就是共享SQL程式碼片段。這與我們編寫其他語言的程式碼時抽象一個公共模組的思路一樣。

在資料應用開發語言和環境一文中,我們提到了一個新的SQL語法,即模板。一個簡單的包含模板的ETL可以是:

—— target=template。a—— 將下面的sql片段儲存為模板a1 as a, 2 as b—— target=temp。result—— 讀取資料,儲存為一個臨時的表(Spark的TempView)select @{a()}, * from some_table;

模板可以支援在單個ETL檔案中共享程式碼片段。但我們這裡的問題是跨ETL共享程式碼。其實只需要對SQL處理器進行很少的修改就可以支援共享模板了。

簡單來說,我們可以將共享模板定義到一個單獨的檔案裡面,然後在執行時,先執行共享的模板,再執行模板ETL即可。

另一個思路是,繼續增強SQL,增加新的語法,比如可以支援一個稱為include指令的語法。include指令的工作方式類似c語言中的include指令,它可以將指令指定的檔案內容擴充套件到當前位置。有了include指令,我們的ETL寫起來可能是下面這樣:

—— shared_code。snippet。sql—— target=template。a—— 將下面的sql片段儲存為模板a1 as a, 2 as b—— etl。sql—— include=shared_code。snippet。sql—— target=temp。result—— 讀取資料,儲存為一個臨時的表(Spark的TempView)select @{a()}, * from some_table;

為支援這個新語法,我們需要在SQL處理器裡面增加一個預處理的過程,在該預處理階段完成檔案內容擴充套件。

總結

指標計算過程中的一個重要問題是如何進行復用。本文嘗試從DWB的構建及公共SQL片段提取兩個方面分享了我們的一些實踐經驗。

基於在多家企業多個專案的經驗總結,我們沉澱出一套企業資料開發工作臺,可以幫助團隊高效交付資料需求,幫助企業快速構建資料能力,工作臺地址:https://data-workbench。com/

我們開源了其中的核心模組——ETL開發語言,開源專案地址:https://github。com/easysql/easy_sql

指標計算實踐