ETL调优的一些分享

  • Post author:
  • Post category:其他



ETL是构建数据仓库的重要一环。通过该过程用户将所需数据提取出来,并按照已定义的模型导入数据仓库。由于ETL是建立数据仓库的必经过程,它的效率将影响整个数据仓库的构建,因此它的有效调优具有很高的重要性。在实际应用中我们通常建议把ETL业务的调优分为若干思路,从而保证调优充分有序进行,避免遗漏,最大化提升ETL的执行效率。



检查资源是否有效配置




关于资源,一般原则是根据需求场景,尽量最大化资源的有效配置。POC可考虑采取最大化的配置方式。实施项目中标准可适当保守,确保系统的稳定性。


假如这个集群主要用作批处理,一般建议Inceptor使用50%左右的CPU资源,另外单个executor不要超过16 core。如果硬件配置比较不错,如服务器有40个以上CPU core,建议采用多executor的方式来部署,譬如采用2 executor 每个10 core的方式,会比1 executor每个20 core的效果更好。


如果集群同时部署了其他服务,请保证其他的服务的资源前提下,给Inceptor部署尽量多的计算资源。计算资源的提高一般都会带来接近线性的分析性能提升。



收集数据特征,确定分区分桶


请注意,

合理的DDL设计是批处理项目的一个非常关键的过程

,需要综合分析业务和数据特点来确定。我们建议开发人员投入足够多的时间和经历来设计DDL,并充分论证设计的有效性。






1) 分区



  • 分区字段选择


一般原则为根据系统的业务类型来分则分区字段。通常来讲事实表是数据都包含时间属性,而报表业务也多在一定的时间范围内做统计分析,那么根据时间字段进行分区是常用的选择。而如果业务更多按照部门做统计分析,那么更适合按照部门代码,地域代码进行分区。所以贴近业务的特点选择分区是第一要素。


另外数据的分布特点也是分区字段选择要考量的重要因素。假如按照用户期望按照某个字段A做分区,但是这个字段A的分布绝对倾斜(譬如字段A一共有1000个不同的数值,但是50%的值都是0,假如按照这个字段分区,那么对应的分区就占了全表50%的数据,这样会导致SQL业务的低效),这个时候选择字段A就不是一个合理的选择。


不过只要数据分布不是绝对倾斜,我们是可以通过Range分区指定不同大小的方式来有效的规避倾斜问题的,因此还是可以选用该字段用于数据分区。譬如企业用户数据,假如2016年用户数是2015年的3倍,我们在选择分区的时候2016年按照1个月为一个分区,而2015年每三个月为一个分区


,而不是简单的数值等值切分Range。另外一个常见的情况是按照


地域进行分区,我们建议把业务量较多的地域单独放在一个区,其余业务量不多的地域进行整合,从而保证各个区之间数据量大致均衡,避免出现大部分区没有数据或者数据非常少的情况。如果发现某些区数据特别倾斜,则需要考虑进一步切分或者更换分区字段。



  • 分区个数


分区个数的选择需要综合考量数据的特性,在选择好分区字段后,我们需要根据数据的特点确定分区个数的可选择区间。


分区个数不宜太少,需要根据业务特点来确定,并保证分区里面不会太多的冷热数据混合。譬如SQL业务大部分都是操作2~3个月的数据,那么我们就尽量按照3个月做一个分区,如果我们选择1年做一个分区的话,每个SQL业务实际执行的时候就会多读取前面9个月的数据,这这部分的资源和IO开销都是没有必要的。


实际情况中我们看到的更多的DDL设计都是分区数量过多,譬如

单个分区的数据量不超过1

GB,或者按照天来分区,这些都是不合适的设计。分区过多的坏处是会导致过多的

系统资源占用(譬如全表扫表类业务,Inceptor会启动 “

分区数×分桶数




的任务去完成这个SQL任务,分区数量或分桶数量过多都会导致集群资源在比较长的周期内被这个任务占据)。一般我们建议分区数量在几十以内。



Reiterate:



不要按天分区!



2)

分桶




  • 分桶字段选择


一般遵循的原则为,选择离散度高的字段进行分桶。可以通过收集的数据特征,如Distinct Value来做参考,值越大的可以优先作为考虑对象。分桶字段选择时,注意尽量使记录分布均匀,避免数据倾斜。



  • 分桶个数


针对不同的存储类型,分桶个数的标准稍微有所差别。以ORC表举例,对于普通ORC表,单分桶大小可以在200M以内;对于ORC事务表,标准适当降低,文件大小限制在100M以内,记录条数限制在几百万条左右。


另外,在考虑分桶个数的时候,同时要考虑是否已经分过区。对于已经分过区的表,要按照单区的大小和条数进行桶数的估计,而不是依照原始表。例如某运行商项目中,我们发现某些表进行分区分桶后,分桶数量过大,导致每个桶的文件大小仅仅为几十K,这样就会使单个Task的执行效率很低,同时总体任务数过大也对系统资源造成极大浪费,并发度上不去。调整方案是根据实际情况,将原桶数降低数量级。



在进行分区分桶时,还应注意以下因素:


因为事务性业务大部分情况下建的都是Range分区分桶表。此种情况下,Map Task的数量=分区数×分桶数,所以,在考虑分区数和分桶数时要综合考虑,Map Task的数量不能过大,比如几千上万,这样可能使单个Task的执行效率太低,并且占用了系统资源,降低了系统并发度。

Task运行情况收集和监控




主要在4040界面上从以下三个方面进行监控:



1) Task数量


Task数量存在过多和过少两种情况,可以通过在4040界面上对Task数量排序进行检查。


对于过多的情况,例如发现某Stage有超过几千上万的Task,应查看是否合理。如上所述的分区分桶导致的任务数量过大就是一个例子。 另外,如果SQL中有针对分区字段的过滤,但是在4040上观察到Task数量的数量仍然很多,需要检查分区剪枝是否成功。


然而,任务数量过少会导致单Task的任务量过大,执行时间过长,也需要进行调整。



2) Task倾斜导致的拖尾。


一般情况的主要成因是某些Key导致的倾斜,如NULL值或者热点地域导致倾斜;还有其他个别情况例如窗口函数导致的单Task任务量过大,引发拖尾。例如我们在某客户业务中遇到,因row_number窗口函数不加partition by,导致单个Task全排序的情况。定位问题后,可采取相应的措施解决。



Case Study





/* 以邮件收寄为主表,以邮件号码关联生成临时表数据 */


CREATE TABLE


PIMS_SDATA.TMP_MAIL_33_20170106_0123


AS SELECT * FROM


( SELECT


A.PostId PostId ,


……


FROM (


SELECT


row_number() over(order by x.PostNbr,  x.CollCpOrgCd, x.CollDt, x.CollTm, x.PostBusnTypCd, x.SeatNbr, x.prikey) +  y.PostId PostId ,



……


FROM


PIMS_SDATA.PEO_TXNPCL_20170106_33  x,


(SELECT  ROW_MAXID PostId FROM tmp_maxid123) y ) A


LEFT  OUTER JOIN


PIMS_SDATA.PEO_TXNPCF_20170106_33 B


ON A.PostNbr = B.PostNbr


AND A.CollCpOrgCd = B.CollCpOrgCd


……


……)


由4040界面观察出, task 0的单任务执行时间超过10mins。排除由NULL值导致的经典task 0倾斜问题,最后定位是窗口函数row_number不带partition by而导致的全局排序所致。




3) 同时并发执行的stage数量








一般情况下给定CPU资源以及表的DDL设计后,我们是可以大概确定4040页面的并发任务数总是处在一个合理的范围内。譬如说当前Inceptor配置了400 core,而一般的SQL都操作一个分区的数据,假如这个分区有20个bucket,那么总体上我们应该能够看到同时并发的stage数量应该是 “资源总量 % SQL任务平均任务数”,在这个例子中就是400%20=20个。如果我们观察到并发任务数比预期的要低,这个时候就需要做一些检查。在4040界面上可以观察每个Stage提交到执行结束的时间点,可以监控此类信息,确保执行的合理性。



Case Study:


下图所示为某个测试场景,上一个Stage结束到下一个Stage提交之间的时间间隔超过3mins。分析hive-server2.log发现异常显示有等锁超时的现象,通过有效的规避锁竞争等问题来避免了这个问题,同时也发现系统性能和并发度有较大提升。

减少不必要的事务表的使用


由于ORC事务表读取和操作较慢,为确保执行效率,对于业务中不涉及事务操作的表,建议使用普通ORC表,而非ORC事务表。另外,必要时建议手动进行Major compact,可以减少因Delta文件过多导致的查询速度慢的问题。




Case Study:


CREATE TEMPORARY TABLE test_transaction_tmp(


id string, name string)


CLUSTERED BY (id) INTO 3 BUCKETS


STORED AS orc_transaction;


!set plsqlUseSlash true


SET transaction.type=inceptor;


SET plsql.catch.hive.exception=true;


begin


begin transaction;


insert into test_transaction_tmp


select * from default.test_transaction_use;


dbms_output.put_line(“step1 : “||sql%rowcount);


delete from default.test_transaction_target where 1=1;


dbms_output.put_line(“step2 : “||sql%rowcount);


insert into default.test_transaction_target


select * from test_transaction_tmp;


dbms_output.put_line(“step3 : “||sql%rowcount);


commit;


Exception


when others then


rollback


dbms_output.put_line(“step4 : “||sql%rowcount);


dbms_output.put_line(‘Exception caught, error code = ‘ || sqlcode());


dbms_output.put_line(‘error message = ‘ || sqlerrm());


end;


/


该案例中的业务人员将此语句中临时表test_transaction_tmp建成了事务表。但是分析业务发现,test_transaction_tmp表仅仅进行了insert into和查询操作,并未进行update/delete/merge等事务性操作,完全可以用普通ORC表替代。替代后的读取速度会比事务ORC表快,并会减少因事务性操作带来的干扰。





减少事务性操作的窗口时间



Inceptor的事务性实现是基于表级锁,而非行级锁。所以在高并发业务场景下,减少事务性操作的互斥区域的执行时间就很重要。优化业务性能时应该优先考虑这部分的时间。



Case Study



:SQL同上节。


由上述SQL可见,对于事务表test_transaction_target的事务操作,从语句“delete from default.test_transaction_target where 1=1;”开始,拿到表test_transaction_target的锁,直至commit语句执行完才会释放。所以高并发场景下就会遇到类似这种互斥区域带来的串行问题。为了减少因事务性功能的互斥实现导致的串行问题影响,需要重点优化从delete from语句开始,到commit语句结束,此窗口的SQL执行总时间,减少因串行带来的影响。


从最影响总体性能的Case开始分析



处理了前五步(包括上文的三步)的影响因素后,接下来对执行依然很慢的业务,需要从耗时的业务开始进行case by case的迭代分析。



首先通过Explain检查执行计划的合理性。如查看过滤下推是否成功,Join顺序是否合理等。对于过滤下推的检查,若处于未被Inceptor优化覆盖的场景,可通过适当手工修改SQL解决。Join顺序的问题,在之前收集的数据特征的基础上,判断Join顺序是否合理,如大表和大表先Join即为不合理的情况。对于此类情况,可以考虑enable CBO来统一解决Join顺序问题。另外,如果在多表Join的案例中发现Join过程较慢,并发度不高,需要考虑是否应该disable MapJoin。一般来说,大表很大时,为节省Shuffle effort,优先默认使用MapJoin。对于中小表同多个小表Join,为提升并发度,减少MapJoin的串行执行影响,可以考虑关掉autoconvert开关。


其次,确定执行计划无误之后,可以进一步通过jstack观察执行热点,进一步定位性能瓶颈。


迭代步骤,直至最优



不断迭代上步,参考语句的性能分析(例如观察Inceptor的4040界面),发现性能瓶颈并解决。直至总体性能满足要求。