Overview
1.Why data matters?
2.Report-oriented data analysis
3.Optimization: 模块化,暴力扫描,Mapreduce原理及Join优化
Why data matters?
1.显著提高工作效率。数据处理是算法工程师日常工作中时间占比最高的部分,故是工作提效最关键部分之一。
2.严重影响业务决策。错误的数据分析结论甚至会带来致命影响;有效的数据分析结论带来业务的增益的决策,长期的增益分析对于把握业务方向帮助显著。
3.需长期思考锻炼提高。实际的业务数据纷繁复杂、变动频率高、业务强相关,故需要具备一定的广度。例如电商成交数据和足球场上胜负关系数据是两个完全不同的过程,二者不互通.
4.SQL is all you need。到2019年SQL仍然是最通用、最标准、最普适的数据处理语言,故本文以SQL为例进行展开。
Report-oriented data analysis
结论导向:永远不做没有目的的数据分析,必须提前想清楚通过数据要验证什么事实;具体做法,先假设你预期结论成立,心里想象数据画出来图是什么样子,然后统计下来对比画出来的图和前期假设图是否相似,若相似那么很快就能验证一些效果;若不相似,再考虑当前统计层次是否有问题,能否做更细的拆解或者能否精简一下统计层面;
逻辑自恰:数据是流体,总是呈现起伏不定的规律,我们既要抓住一般规律,同时也要尽可能完美解释细节变化产生原因,即在给出业务结论时一定要有自洽的逻辑,对于图中出现的一切现象都要给出非常明确的解释(哪怕解释是片面的、缺乏依据的),为什么上涨,为什么下跌,为什么突然有峰值;
Check & Recheck
Scale: 关注数据规模(量级),不同量级数据切入方式不同(好比菜刀用来切菜/水果刀用于切水果), 海量数据要考虑简化处理
COALESCE(expr1, expr2, ...)
CASE value
WHEN value1 THEN result1
WHEN value2 THEN result2
...
ELSE result
END
-- 日期处理: datetime类型和字符串的灵活转化; datetime类型时间滚动;
ds=TO_CHAR(DATEADD(TO_DATE('${bizdate}', 'yyyymmdd'), -1, 'dd'), 'yyyymmdd')
SPLIT_PART(string str, string separator, bigint start[, bigint end])
SUBSTR(string str, bigint start_position[, bigint length])
CONCAT(string a, string b...)
WM_CONCAT(string separator, string str)
INSTR(string str1, string str2[, bigint start_position[, bigint nth_appearance]])
ROW_NUMBER() OVER(partition by col1[, col2…] order by col1 [asc|desc][, col2[asc|desc]…])
Optimization
大数据性能优化在日常产出的必备能力;
采用增量统计减少暴力扫描
例如,如下代码统计18年6月到10月的所有场景点击率;
SELECT
scene
, SUM(pv)/SUM(click) AS ctr
FROM (
SELECT
scene
, pv
, click
FROM table_name
WHERE ds<=20191000
AND ds>=20191100
AND someflag0=1
) tmp-1
GROUP BY scene
乍看上述代码执行无明显问题,但是计算资源紧张时子查询取日志量级太大会跑不完,Job大概率被无情kill;
优化方法:将子查询写入一个表,天级增量中间表,代码如下所示
采用CTE模块化统计逻辑
Common Table Expression, CTE
-- 人群特征
WITH s_basic AS (
SELECT
s_id
, mele_pay_ratio
, female_pay_ratio
, purchase_pay_ratio
, phd_pay_ratio
, bachelor_pay_ratio
, master_pay_ratio
, f0t17_pay_ratio
, f17t24_pay_ratio
, f24t29_pay_ratio
FROM table_name_a
WHERE ds=${bizdate}
),
-- 场景点击成交uv特征
s_clk_pay_uv_info AS (
SELECT
s_id
, clk_uv_0d
, clk_uv_2d
, pay_uv_0d
, pay_uv_2d
FROM table_name_b
WHERE ds=${bizdate}
),
-- 场景人群属性特征
s_city_info AS (
SELECT
s_id
, city_l0_ratio
, city_l1_ratio
, city_l2_ratio
, vip_ratio,
, normal_ratio
FROM table_name_c
WHERE ds=${bizdate}
)
INSERT OVERWRITE TABLE table_name_d PARTITION (ds=${bizdate})
SELECT
s_basic.*
, s_clk_pay_uv_info.`(s_id)?+.+`
, s_city_info.`(s_id)?+.+`
, s_avg_price_info.`(s_id|ds)?+.+`
FROM s_basic
LEFT OUTER JOIN s_clk_pay_uv_info
ON s_basic.s_id=s_clk_pay_uv_info.s_id
LEFT OUTER JOIN s_city_info
ON s_basic.s_id=s_city_info.s_id
MapReduce原理
数据处理过程分成2个阶段:Map(映射)阶段和Reduce(规约)阶段.
Map前,需要将输入数据进行”分片”。
分片:将输入数据切分为大小相等的数据块,每一块作为单个Map Worker的输入被处理,以便于多个Map Worker同时工作.
分片完毕后,多个Map Worker同时工作。每个Map Worker在读入各自的数据后,进行计算处理,最终输出给Reduce.
Map Worker在输出数据时,需要为每一条输出数据指定一个Key。这个Key值决定了这条数据将会被发送给哪一个Reduce Worker。Key值和Reduce Worker是多对一的关系,具有相同Key的数据会被发送给同一个Reduce Worker,单个Reduce Worker有可能会接收到多个Key值的数据.
进入Reduce之前,MapReduce框架会对数据按照Key值排序,使得具有相同Key的数据彼此相邻。如果用户指定了”合并操作”(Combiner),框架会调用Combiner,将具有相同Key的数据进行聚合。Combiner的逻辑可以由用户自定义实现。与经典的MapReduce框架协议不同,在ODPS中,Combiner的输入、输出的参数必须与Reduce保持一致。这部分的处理通常也叫做”洗牌”(Shuffle).
Reduce阶段。相同的Key的数据会到达同一个Reduce Worker。同一个Reduce Worker会接收来自多个Map Worker的数据。每个Reduce Worker会对Key相同的多个数据进行Reduce操作。最后一个Key的多条数据经过Reduce的作用后,将变成了一个值.
Join长尾优化
问题表现:Join任务上SQL运行时间超长,某个woker上的任务进度一直在99%卡着不动;Join时间过长有很多原因,本质上是数据中参与Join的Key存在存在一定的分布特点(分布不均衡或者长尾);
Mapjoin: 将Join操作提前到Map端执行,避免因为分发Key不均匀导致数据倾斜。但是Mapjoin的使用有限制,必须是Join中的从表比较小才可用。所谓从表,即Left Outer Join中的右表,或者Right Outer Join中的左表。Mapjoin的使用方法非常简单,在代码中select后加上
/*+ mapjoin(a) */
即可,其中b代表小表(或者是子查询)的别名。如:
SELECT
/*+MAPJOIN(b)*/
a.c2
, b.c3
FROM (
SELECT
c1
, c2
FROM t1
) a
LEFT OUTER JOIN (
SELECT
c1
, c3
FROM t2
) b
ON a.c1=b.c1;
调小mapper.split.size
作用:设定一个Map的最大数据输入量,通过设置这个变量达到对Map端输入的控制,单位M,默认256M,在[1, Integer.MAX_VALUE]之间调整。set odps.sql.mapper.split.size=256
场景:当每个Map Instance处理的数据量比较大,时间比较长,并且没有发生长尾时,可以适当调小这个参数。如果有发生长尾,则结合odps.sql.mapper.merge.limit.size这个参数设置每个Map的输入数量.
Reduce端长尾优化
问题表现:Reduce端任务99%进度卡着不动,由Multi COUNT(DISTINCT xx字段) 引起;
SELECT
scene
, COUNT(DISTINCT pv) AS pv
, COUNT(DISTINCT click_id) AS click
FROM table_name_a
GROUP BY scene
解决方法:将Group BY的Key分散开(增加一个层次的Key),以下代码增加了场景的小时统计,最后再累加统计;
SELECT
scene
, SUM(pv) AS pv
, SUM(click_id) AS click
FROM (
SELECT
scene
, hh
, COUNT(DISTINCT pv) AS pv
, COUNT(DISTINCT click_id) AS click
FROM table_name_a
GROUP BY scene, hh
) tmp0
GROUP BY scene;
转载请注明来源, from goldandrabbit.github.io