長周期指標的計算優化方案
1 背景
在電子商務公司(如淘寶),對用戶的數據分析的角度和思路可謂是應有盡有、層出不窮。所以在電商數據倉庫和商業分析場景裏,經常需要計算最近N天訪客數、最近N天的購買用戶數、老客數等等類似的指標。這些指標有一個共同點:都需要根據用戶在電商平台上(或網上店鋪)一段時間積累的數據進行計算(這裏討論的前提是數據都存儲在MaxCompute上)。
一般情況下,這些指標的計算方式就是從日誌明細表中計算就行了,如下代碼計算商品的最近30天訪客數:
select item_id --商品id
,count(distinct visitor_id) as ipv_uv_1d_001
from 用戶訪問商品日誌明細表
where ds <= ${bdp.system.bizdate}
and ds >=to_char(dateadd(to_date(${bdp.system.bizdate},'yyyymmdd'),-29,'dd'),'yyyymmdd')
group by item_id
但是當每天的日誌量很大時,上麵代碼存在一個嚴重的問題,需要的**Map Instance**個數太多,甚至會超過**99999**個Instance個數的限製,Map Task就沒有辦法順利執行,更別說後續的操作了。為什麼Instance個數需要那麼多呢?原因:每天的日誌數據很大,30天的數據量更是驚人。這時候Select 操作需要大量的Map Instance,結果查過了Instance的上限,代碼無法運行
2 目的
如何計算長周期的指標,又不影響性能?
1. 多天匯總的問題根源是數據量的問題,如果把數據量給降低了,就可以解決這個問題了。
2. 減少數據量最直接的辦法是把每天的數據量都給減少,因此需要構建臨時表,對1d的數據進行輕度匯總,這樣就能去掉很多重複數據,減少數據量。
3 方案
1. 構建中間表,每天匯總一次,比如對於上麵的例子,構建一個item_id+visitor_id粒度的中間表
2. 計算多天的數據,依賴中間表進行匯總
例子如下:
step1:構建item_id+visitior_id粒度的日匯總表,記作A
insert overwrite table mds_itm_vsr_xx(ds='${bizdate}')
select item_id,visitor_id,count(1) as pv
from
(
select item_id,visitor_id
from 用戶訪問商品日誌明細表
where ds =${bdp.system.bizdate}
group by item_id,visitor_id
)a
**setp2:對A進行30天匯總**
select item_id
,count(distinct visitor_id) as uv
,sum(pv) as pv
from mds_itm_vsr_xx
where ds <= '{bizdate}'
and ds >= to_char(dateadd(to_date('${bizdate}','yyyymmdd'),-29,'dd'),'yyyymmdd')
group by item_id
4 影響及思考
上麵講述的方法,對每天的訪問日誌明細數據進行單天去重,從而減少了數據量,提高了性能。缺點是每次計算多天的數據的時候,都要N個分區的數據,那麼能不能有一種方式,不需要讀取N個分區的數據,而是把N個分區的數據壓縮合並成一個分區的數據,讓一個分區的數據包含曆史數據的信息。業務上是有類似場景的,有如下方式:
1. 增量累計方式計算長周期指標
例子:求最近1天店鋪商品的老買家數,老買家數的算法定義為:過去一段時間有購買的買家(比如過去30天)。
一般情況下,老買家數計算方式:
select item_id --商品id
,buyer_id as old_buyer_id
from 用戶購買商品明細表
where ds < ${bdp.system.bizdate}
and ds >=to_char(dateadd(to_date(${bdp.system.bizdate},'yyyymmdd'),-29,'dd'),'yyyymmdd')
group by item_id
,buyer_id
改進思路:
1. 維護一張店鋪商品和買家購買關係的維表記作表A,記錄買家和店鋪的購買關係,以及第一次購買時間,最近一次購買時間,累計購買件數,累計購買金額等等信息
2. 每天使用最近1天的支付明細日誌更新表A的相關數據
3. 計算老買家時,最需要判斷最近一次購買時間是否是30天之內就行了,從而做到最大程度上的數據關係對去重,減少了計算輸入數據量
最後更新:2017-07-18 07:33:01