以下内容根据演讲视频以及PPT整理而成。
本次演讲视频分享,请戳这里!
本次演讲PPT下载,请戳这里!
关于MaxCompute更多精彩文章,请移步云栖社区MaxCompute公众号!
一、调度优化
在数据仓库建设的过程中,大家都会需要跑一些任务,那么这些任务如何进行配置才会是最优的呢?如果出现了瓶颈点或者业务第二天所需要的数据并没有给到,那么很大一部分的情况需要从调度方面来考虑,是不是有些任务的时间点设置的不合理?或者是不是有些任务的优先级设置的不合理?这些可能是在调度层面,大家需要优先考虑的一个点。
调度优化方式
调度优化的主要方式如下图所示,按照道理前三点应该在设计初期提前想到或者提前规划好的。而目前大部分客户还是用了一段时间的数据仓库的时候,才发现存在一些问题,当第二天需要出报表的时候才想到去优化这些点。
二、模型优化
对于模型优化而言,必须要按照什么方式进行设计以及模型必须是什么样子的,其实没有一个定性的结论。这里也只是给出一些建议和想法。
总之,在MaxCompute上推荐大家使用维度建模,使用星型建模或者雪花型建模的方式,这无论对于后续的运维还是后续对于数据的使用而言,都是比较便利的,并且性能也会好一些。星型模型其实就是中间一个事实表,周边围绕着一堆维度表,其结构会简单一些,使用比较方便,性能也比较好;对于雪花模型而言,维度表可能还会继续关联其他的维度表,这种方式就是雪花模型,它会略微比星型模型复杂一些。其实星型模型也可以理解为较为简单的雪花模型。这里推荐大家使用星型模型,当然如果业务非常复杂,必须要使用雪花型也可以使用。这是因为星型模型虽然有数据冗余,但是其结构比较简单,容易理解,而且使用起来只需要A传给B就可以了,不需要再关联一个C。
除了上述两个较大的关键点之外,还有一些值得注意的小点,比如中间表的利用,在这部分主要是将数仓分为三层,第一层做缓冲,第二层做整合,第三层做应用。但是并不是严格地只能分为三层,中间还是会有一些中间表的,如果能够利用好中间表则会增强数仓的易用性以及整体的性能。其主要是在数仓的第二层里面,因为需要整合一些数据,但是整合之后的数据依旧是明细的,可能有几百亿甚至几千亿的量级,对于这些表而言,数据量往往很大,而且下游任务以及依赖于这个表的报表任务有很多,因此可以做一些轻度的汇总,也就是做一些公共的汇总的中间表,这样应用好了可以节省很多的计算量和成本的。虽然建议大家利用中间表,但是也不建议使用太多的中间表,这还是因为中间表越多,依赖的层级也会越多。
在某些情况下还需要进行拆表,比如某一个大表字段比较多,但是可能其中某两三个字段的产出比较慢,产出很慢可能是因为其加工逻辑很复杂或者数据量比较大导致的,而其他字段产出却是很快的,此时就可以将数据表拆开,将过慢的字段拆出来,并将原来正常的字段留在原来的表,这样就可以避免因为两个过慢的字段影响其他业务,拆表的场景虽然比较常见,但是可能不会在数仓建设初期就出现。
还有一种场景及就是合表,这与拆表是相对的,当大家使用数仓一段时间之后会发现A业务部门出了一些表,B业务部门也出了一些表,而这些表或者数据可能是重叠的,也可能业务含义是一样的,只不过字段不一样。对于这些表而言是可以进行合并的,因为在合并之后可以做整体批量加工的SQL,这样要比多个表批量加工的SQL复杂度要低很多,而且性能要好很多。对于分区的场景而言,也要合理地设置MaxCompute的分区。
此外还有拉链算法,这在传统数仓里面也会用到,大家往往会需要使用拉链算法来记录历史变化情况。而拉链算法会使得计算成本变得比较高,尤其在MaxCompute里面或者离线数仓Hive里面,这是因为其没有Update的操作,因此需要遍历全表,需要对比昨天的全量和今天的增量,甚至是比较昨天的全量和今天的全量,才能得到所想要的拉链算法的结果,这样的计算成本对于MaxCompute而言要高很多。如果数据量不大,每天做全量的拉链算法也是没有问题的,只需要考虑保留多久历史数据的问题。而实际上,有些业务不会关心这些历史数据的变化问题,对于这样的业务其实可以只保留最近多少天的历史数据就可以了。其实是因为MaxCompute这边的数据存储成本很低,如果不使用拉链算法,那么就意味着数据冗余会高很多,所以其实大家可以计算一下每天增量数据的存储成本有多少,再对比一下数据的计算成本,根据自己的业务进行均衡。但是如果每天增量数据达到百亿这种级别,保留全量数据肯定是不现实的,那么就还是去做拉链算法。
模型优化-合理设计分区
MaxCompute分区的功能一定要合理利用,这对于性能会产生很大的影响,一级分区一般都是按照天划分的,建议大家一天一个增量或者一天一个全量来做。二级分区的选择反而会多一些,首先大家可以选择是否建立二级分区,其次大家可以选择二级分区的建立方式。二级分区比较适合于在where语句中经常使用到的字段,而且这个字段应该是可枚举的,比如“男”和“女”这样的。这里还有一个前提,就是如果这个字段的值的分布是非常不均匀的,那么就不太建议做二级分区。
如下图中的例子所示,登录表每天会有9个亿的数据,而其中的一个字段是“是否登录成功”,成功可能有4亿,失败可能有5亿,这就比较适合做二级分区,因为比较均衡。第二个例子是用户访问表,每天新增20亿数据,其中一个字段是“页面访问状态”,成功访问“202”是18亿,而失败“203”只有0.5亿,其他就更少了,这样的字段就不适合做二级分区。在数量级不大的情况下,不建议做二级分区,因为几百万的数据在MaxCompute里面扫描起来也会很快,在数据量大了之后可以再考虑二级分区,因为MaxCompute本身对于分区有一个上限就是6万,也就是一级分区乘以二级分区的个数不能超过6万个。
三、同步任务优化
同步任务优化可以从下图所示的这样几个点进行考虑。正如下面的这张PPT中图所示。数据同步其实就是源库通过网络进入到DataWorks或者自定义的调度资源里,再从DataWorks里面同步到MaxCompute里面,或者反过来从MaxCompute同步到源库,但是无论怎么说同步就是分为这样的几个点:源库、网络1、DataWorks调度资源、网络2以及MaxCompute,出现瓶颈的地方也就在这几部分中,如果同步任务运行缓慢,那么瓶颈点就只能出现在这几个点中。最常见的情况就是从其他数据库向MaxCompute抽取数据,一般情况下的瓶颈点就在源库这部分,出现问题大家可以优先在源库处寻找。在网络层面,从DataWorks到MaxCompute之间的网络2大家一般不用关心,因为这部分是由阿里云负责的,但是从源库到DataWorks调度的网络1这一段需要由用户自己保证,公网、内网和专线,不同的网络环境中同步的速度也是不一样的。
计算任务优化
在计算任务优化部分部分,也只与大家分享在SQL部分开发者应该如何进行优化。大家平时在进行数据处理、数据清洗、数据转换、数据加工等过程中都会使用到SQL。对于SQL的优化而言,主要集中在这样的两个大方面进行:减少数据输入和避免数据倾斜。减少数据输入是最核心的一点,如果数据输入量太大,包括很多无效的数据,那么就会占用很多的计算资源。而数据倾斜是在离线的数仓里面经常会遇到的,几乎每个人都会遇到,数据倾斜也分为好几种,需要对应地进行优化。接下来就为大家展开进行论述。
前面讲述了分区应该如何设计,这里着重讲解分区应该如何使用。如果表存在一级分区,那么将分区的筛选放到了条件里面就是一种错误的写法,有可能导致全表扫描。最好的写法就是像下图右侧所示的一样,把Table1的PT先进行筛选做一个子查询,再把Table2的也做一个分区先筛选了作为t2,之后将它们两个Join在一起再加上一个where条件,这样就能避免全表扫描。对于PT而言,如果使用了系统自带的函数,应该会做分区裁剪,而如果使用了自定义的函数对于PT进行加工,并放到了where条件里就有可能导致全表扫描,而现在DataWorks里面也会有系统提示,也便于大家进行判断。
在MaxCompute里面支持多路输入,可以读取一个表的数据并将其同时写入到两个地方,这样就保证了只做了一次查询,而可以直接生成两个结果表。下图是一个电商的例子,大概就是在销售订单表里面有卖家和买家,分别统计了卖家和买家的数量分别是多少,以前可能需要拆分成两个SQL,而现在可以用一个SQL同时统计两者的数量,只需要读取一次原表就可以了,既能够节省时间,也能够节省成本。
因为MaxCompute里面是列式存储,所以同一列的数据都是存储在一起的,甚至于因为列内容相似都会有一些压缩算法在里面。而SELECT*查询全列与直接查询两个字段的性能差距是非常大的,所以作为数据开发的规范,无论数据量大小都一定不要使用SELECT*就好了。
还有一个减少数据量的办法就是在使用Join、Reduce或者UDF的时候,先做过滤在做具体的计算或者Join。
合表也是减少数据输入的一种方式,它其实是从业务的角度切入考虑的。比如有一个业务分别用到了T1和T2的两列,另外一个业务分别用到了T1和T3的两列,而这两个业务其实是可以合并到一起的,但是却放到了两个表,而这样就可以将这两个表合并到一起,这样只做一次计算就完成了。
在LogView里面有一个Map的时序,可以看到每个Map里面有多少个Instance,里面的哪一个耗时比较长就是发生了数据倾斜。同样的,在LogView里面也能找到Map的平均执行时间以及最大执行时间,如果两者相差很大,那么必然出现了倾斜。对于这样的问题,从业务层面进行解决一般是修改上游数据,让上游按照均衡的KV值进行重新分布。如果业务层面无法规避,那么可以调整Map的个数,也就是加大Map的计算节点,在默认情况下是每256M数据切一个节点,可以将其调小,也就加大了Map处理节点的个数,使得数据分割得更加均匀一些。
Join阶段的倾斜也是比较常见的,这一现象的发现与Map倾斜基本相同,也是可以通过LogView判断。但是其解决方案却需要分为几种情况进行处理:
-情况1:如果为大表与小表(加载到内存不超过512M),则对小表加MAPJOIN HINT
-情况2:两个大表Join,KEY值出现数据倾斜,倾斜值为NULL,则需对NULL进行随时值处理
-情况3:两个大表Join,可以尽量先去重后再Join
-情况4:两个大表Join,业务层面考虑优化,检查业务的必要性
Reduce倾斜现象的查看方式和前面的Map以及Join查看的方式相同,可以从TimeLine看到。可能的情况主要有以下四种:
-情况1:GROUP BY 某个KEY倾斜严重(1. 是否可以过滤 2. 写法改变,见图)
-情况2:DISTINCT引起的倾斜(打标+GROUPBY)
-情况3:动态分区引起的倾斜,尽量避免使用动态分区
-情况4:窗口函数引起的倾斜,尽量避免使用窗口函数,要视具体情况而定
如果是因为DISTINCT造成的数据倾斜,有一种解决方法就是打标+GROUPBY,比如在下图的例子中就是对于求IP段,求美国IP段、中国IP段以及总的IP段一共有多少个,左边这种图简单的写法,当出现IP Key的倾斜就会使得作业比较慢,那么就可以将其打散,先求解这条ID的记录是美国的还是中国的,在子查询里先做这一步,在外面再去求解总的Count或者Sum,从原本Map-Reduce两个阶段的处理改成了Map-Reduce-Reduce三个阶段处理,这种方案也能解决数据倾斜问题。