计算倾斜排查与解决
云原生数据仓库AnalyticDB PostgreSQL版中,计算倾斜是指由于数据分布不均或查询逻辑设计不合理,导致实例中部分计算节点负载显著高于其他节点的现象,这种不均衡会造成资源利用率降低、查询性能下降,以及高负载节点容易出现内存溢出或磁盘IO瓶颈等问题。本文为您介绍如何识别并缓解MPP数据库中的计算倾斜问题,从而提升集群整体性能和稳定性。
常见现象
通过监控或报错可观察到以下现象:
CPU:执行SQL时,单个或部分节点的CPU明显高于其他节点。
内存:执行SQL时,单个或部分节点的内存水位明显高于其他节点,可能伴随报错
ERROR: Canceling query because of high VMEM usage.磁盘:执行SQL时,单个或部分节点的落盘文件大小明显高于其他节点,可能伴随报错
ERROR: workfile per segment size limit exceeded.
诊断方法
根据SQL的执行状态,采用不同的诊断方法。
SQL正常执行
当SQL能够正常执行时,可以通过EXPLAIN ANALYZE来查看该SQL的具体信息。
在该SQL前添加以下语句,用于打印每个计算节点(Segment)的统计信息。
SET gp_enable_explain_allstat TO ON;例如:
SET gp_enable_explain_allstat TO ON; EXPLAIN ANALYZE <sql>;在输出中定位
allstat字段。格式为
allstat: seg_firststart_total_ntuples,每个Segment输出三项指标,包含firststart(毫秒)、total(毫秒)和ntuples(处理的行数)。不同Segment用斜杠分隔,各指标以下划线分隔,末尾的//end表示该统计块结束。如果某Segment处理行数远高于其他节点,即存在计算倾斜。例如:
allstat: .../seg1_0.618ms_3.569ms_100000/...//end
案例
EXPLAIN ANALYZE输出结果如下:
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
Aggregate (cost=0.00..862.00 rows=1 width=8) (actual time=54.648..54.649 rows=1 loops=1)
allstat: seg_firststart_total_ntuples/seg-1_0.433 ms_55 ms_1//end
-> Gather Motion 3:1 (slice1; segments: 3) (cost=0.00..862.00 rows=2 width=1) (actual time=40.810..54.268 rows=599999 loops=1)
allstat: seg_firststart_total_ntuples/seg-1_0.435 ms_54 ms_599999//end
-> Hash Left Join (cost=0.00..862.00 rows=1 width=1) (actual time=20.366..54.055 rows=266546 loops=1)
Hash Cond: (skew_hashjoin1.a = skew_hashjoin2.c)
Extra Text: (seg1) Hash chain length 100000.0 avg, 100000 max, using 1 of 131072 buckets.
allstat: seg_firststart_total_ntuples/seg0_0.611 ms_41 ms_166579/seg1_0.618 ms_54 ms_266546/seg2_0.658 ms_40 ms_166874//end
-> Redistribute Motion 3:3 (slice2; segments: 3) (cost=0.00..431.00 rows=1 width=8) (actual time=0.023..36.498 rows=166874 loops=1)
Hash Key: skew_hashjoin1.a
allstat: seg_firststart_total_ntuples/seg0_4.405 ms_37 ms_166579/seg1_7.684 ms_5.770 ms_166547/seg2_4.415 ms_36 ms_166874//end
-> Seq Scan on skew_hashjoin1 (cost=0.00..431.00 rows=1 width=8) (actual time=0.055..4.708 rows=166874 loops=1)
allstat: seg_firststart_total_ntuples/seg0_1.946 ms_4.398 ms_166579/seg1_1.941 ms_4.789 ms_166547/seg2_1.943 ms_4.708 ms_166874//end
-> Hash (cost=431.00..431.00 rows=1 width=8) (actual time=7.064..7.064 rows=100000 loops=1)
Buckets: 1 Batches: 1 Memory Usage: 5305kB
allstat: seg_firststart_total_ntuples/seg0_0.612 ms_3.792 ms_0/seg1_0.618 ms_7.064 ms_100000/seg2_0.659 ms_3.755 ms_0//end
-> Redistribute Motion 3:3 (slice3; segments: 3) (cost=0.00..431.00 rows=1 width=8) (actual time=2.895..3.569 rows=100000 loops=1)
Hash Key: skew_hashjoin2.c
allstat: seg_firststart_total_ntuples/seg0_0.612 ms_3.790 ms_0/seg1_0.618 ms_3.569 ms_100000/seg2_0.659 ms_3.752 ms_0//end
-> Seq Scan on skew_hashjoin2 (cost=0.00..431.00 rows=1 width=8) (actual time=0.050..0.915 rows=33462 loops=1)
allstat: seg_firststart_total_ntuples/seg0_1.946 ms_0.915 ms_33462/seg1_1.925 ms_1.013 ms_33327/seg2_1.923 ms_0.989 ms_33211//end
Optimizer: GPORCA
Planning Time: 4.612 ms
(slice0) Executor memory: 134K bytes.
(slice1) Executor memory: 2691K bytes avg x 3 workers, 7300K bytes max (seg1). Work_mem: 5305K bytes max.
(slice2) Executor memory: 233K bytes avg x 3 workers, 233K bytes max (seg0).
(slice3) Executor memory: 233K bytes avg x 3 workers, 233K bytes max (seg0).
Memory used: 4194304kB
Execution Time: 55.433 ms
(29 rows)定位到allstat字段,统计信息如下:
allstat: seg_firststart_total_ntuples/seg0_0.612 ms_3.790 ms_0/seg1_0.618 ms_3.569 ms_100000/seg2_0.659 ms_3.752 ms_0//end含义为:
seg0: firststart=0.612 ms,total=3.790 ms,ntuples=0
seg1: firststart=0.618 ms,total=3.569 ms,ntuples=100000
seg2: firststart=0.659 ms,total=3.752 ms,ntuples=0
数据重分布(Redistribute Motion)将实际数据集中在seg1(处理了 100000 行),其他Segment没有处理数据。可以判断出现了计算倾斜。
SQL执行报错
若SQL执行出现如下报错,且监控中发现部分节点内存明显高于其他节点,可通过以下步骤诊断。
ERROR: Canceling query because of high VMEM usage. Used: 6975MB, available 25MB, red zone: 6300MB (seg0 slice1 xxx.xxx.xxx.xxx:xxxx pid=xxx)计算倾斜需要结合监控具体判断,并非出现该报错一定为计算倾斜,同时计算倾斜并非必定出现上述报错信息。本节以上述报错为例,介绍诊断步骤。
使用
EXPLAIN查看执行计划。EXPLAIN statement;上述报错的SQL计划如下:
QUERY PLAN ------------------------------------------------------------------------------------------------------------ Aggregate (cost=0.00..862.00 rows=1 width=8) -> Gather Motion 3:1 (slice1; segments: 3) (cost=0.00..862.00 rows=2 width=1) -> Hash Left Join (cost=0.00..862.00 rows=1 width=1) Hash Cond: (skew_hashjoin1.a = skew_hashjoin2.c) -> Redistribute Motion 3:3 (slice2; segments: 3) (cost=0.00..431.00 rows=1 width=8) Hash Key: skew_hashjoin1.a -> Seq Scan on skew_hashjoin1 (cost=0.00..431.00 rows=1 width=8) -> Hash (cost=431.00..431.00 rows=1 width=8) -> Redistribute Motion 3:3 (slice3; segments: 3) (cost=0.00..431.00 rows=1 width=8) Hash Key: skew_hashjoin2.c -> Seq Scan on skew_hashjoin2 (cost=0.00..431.00 rows=1 width=8) Optimizer: GPORCA (12 rows)使用EXPLAIN阅读查询计划:上述计划表明查询执行分为三个Slice。其中,slice1执行
Hash Left Join操作,左表所在slice2按照分布键skew_hashjoin1.a重分布,右表所在slice3按照skew_hashjoin2.c键进行重分布。验证数据分布。
分析关联键的数据分布是否存在热点值,如果查询结果显示某个或某几个值的
cnt远高于其他值,即可确认存在数据倾斜,从而导致了计算倾斜。SELECT count(*) AS cnt, distribute_key_1, distribute_key_2, ..., distribute_key_n FROM [ table | sub_select ] GROUP BY distribute_key_1, distribute_key_2, ... distribute_key_n ORDER BY cnt DESC LIMIT N;例如:
-- 验证slice1下左表经过shuffle后的数据分布 SELECT count(*) AS cnt, skew_hashjoin1.a FROM skew_hashjoin1 GROUP BY skew_hashjoin1.a ORDER BY cnt DESC LIMIT 10; -- 验证右表slice2经过shuffle后的数据分布 SELECT count(*) AS cnt, skew_hashjoin2.c FROM skew_hashjoin2 GROUP BY skew_hashjoin2.c ORDER BY cnt DESC LIMIT 10;返回示例如下,从该结果可以发现
skew_hashjoin2中的c列值倾斜。由此断定该SQL发生计算倾斜。-- 验证slice1下左表经过shuffle后的数据分布 cnt | a -----+---- 1 | 10 1 | 11 1 | 13 1 | 14 1 | 17 1 | 21 1 | 52 1 | 56 1 | 58 1 | 9 (10 rows) -- 验证右表slice2经过shuffle后的数据分布 cnt | c -----------+--- 100000000 | 1 (1 row)
解决方案
计算倾斜在分布式场景下多数是由于计算发生重分布时,分布键发生值倾斜。因此,解决方案分为解决值倾斜和重分布两类。
解决值倾斜问题
业务优化
重分布的意义在于
JOIN时将相同的连接键(Join key)分布在同一节点上,因此当连接键发生倾斜的值有匹配的数据时,建议从业务角度优化数据,从数据源避免键值集中。NULL值倾斜处理
当发生倾斜的连接键不存在匹配的数据时,例如
Inner Join中的NULL值,此时可以在运行时将NULL值映射到一片不会被匹配上的值域中。例如,当连接键全为正数时,可以将NULL值映射到随机负值,使其可以在进行重分布时均匀分布至各个节点,同时也不会影响JOIN的结果,最后再将所有的负值重新转换为NULL值。在
JOIN发生NULL值倾斜时, 建议排查对应业务SQL。例如,推荐使用EXISTS,尽量规避使用IN表达式。
解决重分布问题
如果表不存在分布倾斜,建议建表时将小表创建为复制表,避免运行时发生连接键的重分布。