Dataworks实践——脚本优化

2024-08-10 22:07:54 浏览数 (1)

1.0 引言

Dataworks从零到一的过程,是希望任务越多越好,命名可以不标准,脚本可以重复迭沓,但能够覆盖掉业务数据的100%即可;而从一到一百的过程,是为前一阶段“买单”的时候,如何分散调度时间的集中度,如何设置告警基线,如何识别并下线无效调度,如何对脚本调优降低费用等等。

任务时间线任务时间线

本文从费用的点来切入,因为想要优化调度任务的主要目的就是为了节省费用。

2.0 Maxcompute计费规则

2.1 Maxcompute计费规则小结

对于任何的优化措施来说,我们首先需要了解其背后计费的规则,对此网页上有比较详细的介绍。我们就总结其中的主要规则来说。

总体的规则如下脑图:

2.2 Maxcompute计费之SQL作业按量计费

根据实际业务的不同,具体的费用项也有差异,而我们目前只涉及到图中标红部分的SQL作业按量计费项。其中主要包括三个因变量:输入数据量SQL复杂度单价

输入数据量,指的是一个SQL作业实际扫描的数据量,而实际扫描的数据量的定义中则涵盖两个可以优化的点:1、列剪切,即在我们具体建模过程中,实际的输入数据量仅统计我们写入的列,SQL中未列出的列不参与统计。2、分区过滤,如果表中有分区,那么如果在SQL中我们限制了分区,即仅有限制的分区的数据量才统计,而限制以外的分区不统计在内。而这两点也给予了我们脚本优化的两个灵感,后续我们展开讲。

SQL复杂度,指的是一个任务中关键字的数量,按其数量的多少,来划分为四个不同的SQL复杂度,分别为1,1.5,2,4四档。所以关键字的多少是直接决定了SQL复杂度的大小,尽管这种相关性并不是线性相关的,因此实际操作中如何减少关键字的个数是脚本优化的关键

单价,主要是由我们每日的计算量,即$$sum{i=1}^{infty}{输入数据量{i}*SQL复杂度_{i}}$$ 决定的。对于我们来说,按照0.3的标准来统计即可。

综上所述,其实可以看到我们对于脚本的优化主要来自于两方面,第一,减少输入数据量;第二,减少关键字,进而降低SQL复杂度。

3.0 脚本优化——降低模型SQL复杂度

我们来看一下具体案例,这段脚本是从任务ads_ilp_pdd_dailyspy_inf中截取的部分

代码语言:sql复制
insert overwrite ...
...
...
...

from (select * from yht_dwd.dwd_ilp_trail_detail WHERE pt='${bizdate}' ) x1
left join (select * from yht_dwd.dwd_uexpress_u_express_order_df WHERE pt='${bizdate}' ) x2 
on x1.trail_ref1 = x2.number
LEFT JOIN (select * from yht_dws.dws_ilp_ar_billing_detail where pt='${bizdate}' ) x3 
ON x1.trail_ref1 = x3.waybill_no
) y1
left join (
--# 获取该揽收时间、该航班号、该批次号下的,最早的 【交航司时效/起飞时间】、【到港时间(中国时间)】、【清关--交USPS时间 / 派送时间】 
select b1.* 
    ,b2.flight_arrived_time   --# 到港时间(中国时间)
    ,b3.delivery_time               --# 清关--交USPS时间 / 派送时间
from(
select substr(inhouse_time,1,10) as inhouse_time --# 揽收时间
    ,hawb_no                   --# 航班号
    ,bill_no as bill_no_full
    ,min(flight_time) as flight_time                   --# 交航司时效/起飞时间 
FROM yht_dwd.dwd_ilp_trail_detail y1 WHERE pt='${bizdate}'  
and substr(flight_time,1,10) <> '1970-01-01'
group by  
    substr(inhouse_time,1,10)  --# 揽收时间
    ,hawb_no                   --# 航班号
    ,bill_no ) b1

left join (
select substr(inhouse_time,1,10) as inhouse_time --# 揽收时间
    ,hawb_no                   --# 航班号
    ,bill_no as bill_no_full
    ,min(flight_arrived_time) as flight_arrived_time   --# 到港时间(中国时间)
FROM yht_dwd.dwd_ilp_trail_detail y1 WHERE pt='${bizdate}'  
and substr(flight_arrived_time,1,10) <> '1970-01-01'
group by  
    substr(inhouse_time,1,10)  --# 揽收时间
    ,hawb_no                   --# 航班号
    ,bill_no ) b2 
on b1.inhouse_time = b2.inhouse_time 
and b1.hawb_no = b2.hawb_no 
and b1.bill_no_full = b2.bill_no_full

left join (
select substr(inhouse_time,1,10) as inhouse_time --# 揽收时间
    ,hawb_no                   --# 航班号
    ,bill_no as bill_no_full
    ,min(delivery_time) as delivery_time               --# 清关--交USPS时间 / 派送时间
FROM yht_dwd.dwd_ilp_trail_detail y1 WHERE pt='${bizdate}'  
and substr(delivery_time,1,10) <> '1970-01-01'
group by  
    substr(inhouse_time,1,10)  --# 揽收时间
    ,hawb_no                   --# 航班号
    ,bill_no ) b3 
on b1.inhouse_time = b3.inhouse_time 
and b1.hawb_no = b3.hawb_no 
and b1.bill_no_full = b3.bill_no_full
) a 
on y1.inhouse_time = a.inhouse_time 
and y1.hawb_no = a.hawb_no 
and y1.bill_no_full = a.bill_no_full 

...
...
...

首先我们来统计一下SQL关键字:

join: #6,#8,#11,#28和#43 ,合计5个

groupby:#23,#35和#50,合计3个

加上一个 insert ,合计9个关键字,那么SQL复杂度是2。

然后,我们再来看一下脚本:其中的b1,b2,b3在我看来其实逻辑都是一致的,改写成如下:

代码语言:sql复制
select 
    substr(inhouse_time,1,10) as inhouse_time --# 揽收时间
    ,hawb_no                   --# 航班号
    ,bill_no as bill_no_full
    ,min(flight_time) as flight_time                   --# 交航司时效/起飞时间 
    ,min(flight_arrived_time) as flight_arrived_time   --# 到港时间(中国时间)
    ,min(delivery_time) as delivery_time               --# 清关--交USPS时间 / 派送时间
FROM yht_dwd.dwd_ilp_trail_detail y1 WHERE pt='${bizdate}'  
and to_char(flight_time,'yyyy-mm-dd') <> '1970-01-01'
-- and to_char(flight_arrived_time,'yyyy-mm-dd') <> '1970-01-01'
-- and to_char(delivery_time,'yyyy-mm-dd') <> '1970-01-01')
group by  
    substr(inhouse_time,1,10)  --# 揽收时间
    ,hawb_no                   --# 航班号
    ,bill_no

这样就减少了2个 groupby,减少2个join,而关键字个数会降低到5个,SQL复杂度降低到1.5。

最后再来看,案例中这样的写法:

代码语言:sql复制
...
LEFT JOIN (select * from yht_dws.dws_ilp_ar_billing_detail where pt='${bizdate}' and type=1 ) x3 
ON x1.trail_ref1 = x3.waybill_no
...

按照官方文档上的介绍,这样的写法完全不会降低扫描数据量,也不会提升sql运行效率,有兴趣的可以去官方文档了解 Hive SQL语句的正确执行顺序 ,看下hive sql的底层运行原理,可能会有所领悟。

所以,上述脚本修改成如下:

代码语言:sql复制
...
left join yht_dws.dws_ilp_ar_billing_detail x3 
on x1.trail_ref1=x3.waybill_no
and x3.pt='${bizdate}'
and x3.type=1
...

那么上述上述脚本即可改写为如下:

代码语言:sql复制
insert overwrite ...
...
...
from  yht_dwd.dwd_ilp_trail_detail  x1
-- left join yht_dwd.dwd_uexpress_u_express_order_df  x2 on x1.trail_ref1 = x2.number
-- and x2.pt='${bizdate}'
LEFT JOIN yht_dws.dws_ilp_ar_billing_detail  x3 ON x1.trail_ref1 = x3.waybill_no
and x3.pt='${bizdate}'
where x1.status_id not in ('TERMINATED','RETURNED_TO_CHINA')
and x3.bussiness_type = 'QLL'
and nvl(x1.carrier,'-') like '%拼多多%'
and substr(x1.inhouse_time,1,10) >= '2022-10-15'
and x1.pt='${bizdate}'
) y1
left join (
--# 获取该揽收时间、该航班号、该批次号下的,最早的 【交航司时效/起飞时间】、【到港时间(中国时间)】、【清关--交USPS时间 / 派送时间】 
select substr(inhouse_time,1,10) as inhouse_time --# 揽收时间
    ,hawb_no                   --# 航班号
    ,bill_no as bill_no_full
    ,min(flight_time) as flight_time                   --# 交航司时效/起飞时间 
    ,min(flight_arrived_time) as flight_arrived_time   --# 到港时间(中国时间)
    ,min(delivery_time) as delivery_time               --# 清关--交USPS时间 / 派送时间
FROM yht_dwd.dwd_ilp_trail_detail y1 WHERE pt='${bizdate}'  
and to_char(flight_time,'yyyy-mm-dd') <> '1970-01-01'
group by  
    substr(inhouse_time,1,10)  --# 揽收时间
    ,hawb_no                   --# 航班号
    ,bill_no
) a 
on y1.inhouse_time = a.inhouse_time 
and y1.hawb_no = a.hawb_no 
and y1.bill_no_full = a.bill_no_full 

4.0 脚本优化——避免重复造轮子

先来看几个脚本的片段:

来自ads_ilp_pdd_packagecost_inf模型中片段:

来自ads_ilp_product_timely模型的片段:

再来看下ads_ilp_pdd_undeliver_inf模型的片段:

再来看下,ads_ilp_pdd_deliverdelay_inf的脚本片段:

我们可以看到四段脚本中同样用到了几乎相同的逻辑,依赖的表相同:yht_dwd.dwd_ilp_trail_detail,yht_dwd.dwd_uexpress_u_express_order_df,yht_dws.dws_ilp_ar_billing_detail和yht_dwd.dwd_uexpress_order_contact_df,字段也是几乎相同,唯一的区别在于限制条件是有区别的,比如说,对于入库时间的限定,对于渠道的限定等等。那么问题就来了,为什么如此相似的脚本还会重复四次的上线呢?

这不仅是对调度资源的浪费,也同时拖慢了任务的执行时间,如果考虑将这段复用的逻辑物化成一张表,而下游的任务即来依赖物化的表即可,此时对于各个任务的SQL关键字都减少了3个,基本上相当于将SQL复杂度由1.5降低到1,任务的运行效率集中加强。

0 人点赞