OceanBase v4.2 特性解析:Auto DOP

发布于:2024-04-25 ⋅ 阅读:(25) ⋅ 点赞:(0)

我们常会使用并行执行来缩短查询时间,以满足业务对加速查询的需求。那么,如何确定合适的并行资源量呢?在优化器中,并行资源量可以通过并行度(DOP:Degree of Parallelism)这来衡量。在实际业务场景中,是否启用并行执行以及设置多大,往往需要根据查询的实际执行情况、业务需求,结合经验来做出决策。

在人为指定并行度时,可以通过系统变量对当前会话中的所有执行查询开启并行,并指定并行度。然而,这对会话中本不需要并行加速或无需使用较高 DOP 的查询带来额外的并行执行开销,从而导致性能下降。另一方面,可以通过 hint 的方式来指定特定查询的并行度,但这需要对每一条业务查询进行单独考量,对于存在大量业务查询的情况,是不可行的。

为了解决人为指定并行度的不便和限制,查询优化器可以通过Auto DOP功能在生成查询计划时,评估查询需要执行的时间,自动确定是否开启并行和开启适量的并行度。这样可以避免由于人工指定并行度不当而导致的性能下降。

开启 Auto DOP

优化器的 DOP 选择策略目前分为 AUTO 模式和 MANUAL 模式。AUTO 模式下优化器可以根据查询实际情况,自动选择开启并行,并确定并行度。MANUAL 模式仅能通过人为干预开启并行。

优化器的 DOP 选择策略有两种控制方式,可以通过系统变量 parallel_degree_policy 进行全局或 session 级别的 DOP 选择策略配置,也通过更高优先级的 hint 进行查询级别的 DOP 选择策略配置。具体配置方法如下:

-- 在全局级别启用 Auto DOP
 set global parallel_degree_policy = AUTO;
 -- 在 session 级别启用 Auto DOP
 set session parallel_degree_policy = AUTO;
 set parallel_degree_policy = AUTO;
 
 -- 在全局级别禁止 Auto DOP
 set global parallel_degree_policy = MANUAL;
 -- 在 session 级别禁止 Auto DOP
 set session parallel_degree_policy = MANUAL;
 set parallel_degree_policy = MANUAL;
 
 -- 使用 hint 在查询级别启用 Auto DOP
 select /*+parallel(auto)*/ * from t1;
 -- 使用 hint 在查询级别禁止 Auto DOP
 select /*+parallel(manual)*/ * from t1;
 select /*+parallel(8)*/ * from t1;  

在 OceanBase 4.2 版本中,parallel_degree_policy 默认设置为 MANUAL。

Auto DOP 相关设置

在 Auto DOP 策略下,有两个相关的变量会影响获取的 DOP 大小。

变量 parallel_degree_limit 限制 Auto DOP 策略时允许使用的最大 DOP,parallel_degree_limit 缺省值为 0,使用缺省值时,使用下面两种方法得到的较小值限制最大 DOP:

1.     变量 parallel_servers_target 的设置值。

2.     利用租户 unit 的 min cpu 与读取表的 server 数量相乘得到限制值。

如 parallel_degree_limit = 0,parallel_servers_target = 10, min_cpu = 2, 下方查询读取的两个分区分布在两台 observer 上,则最大可用 DOP 为 min(10, 2*2) = 4
select * from t1 partition (p0, p1);
min_cpu 可以通过 select min_cpu from oceanbase.V$OB_UNITS; 查询

变量 parallel_min_scan_time_threshold 是在 Auto DOP 策略中用于计算并行度的参数,表示对基表扫描开启并行的参考执行时间。当基表扫描评估执行时间超过这个设定值时,会对基表扫描开启并行,并利用这个设定值计算一个合适的并行度。变量默认值设置为 1000,单位毫秒。通过调小 parallel_min_scan_time_threshold 的值,可以降低对基表开启并行的门限制,允许对评估执行时间更小基表扫描开启并行,对已经开启并行且数据量固定的表,也会使用更大的并行度进行扫描。

-- 设置 global/session 级别 Auto DOP 策略最大 DOP 限制
 set global parallel_degree_limit = 64;
 set session parallel_degree_limit = 64;
 set parallel_degree_limit = 64;
 
 -- 设置 global/session 级别 Auto DOP 策略 DOP
 set global parallel_min_scan_time_threshold = 100;
 set session parallel_min_scan_time_threshold = 100;
 set parallel_min_scan_time_threshold = 100;  

Auto DOP 与其它并行开启方式关系

对于开启并行的多种方式,优先级关系详见官网文档并行开启方式及优先级

开启 Auto DOP 后,优化器可以对 select/delete/update/insert 等多种操作开启并行,对于 delete/update/insert 等数据维护操作,开启并行后会自动使用 PDML,不需要再对 _enable_parallel_dml 进行配置打开 PDML。

Auto DOP 使用场景

Auto DOP 可以对查询主动开启并行,但使用并行度的大小受到一些配置项的影响,在不同场景下使用 Auto DOP 需要对相关参数进行一些调整。

场景1:查询性能极致优化

当数据库没有其它查询负载时,想要利用租户下所有并行执行资源获得极致查询性能时,可以将 Auto DOP 开启并行的参考执行时间设到最低,同时不主动对 Auto DOP 使用的最大 DOP 进行限制。此时将相关参数进行以下设置:
set parallel_degree_policy = AUTO;
 set parallel_degree_limit = 0;
 set parallel_min_scan_time_threshold = 10;  

场景2:系统性能优化

当数据库大量并发查询负载时,大量查询使用较高 DOP 会导致系统并行资源不足,导致查询队列排队等问题。为了避免大量大并行度查询对系统资源的占用,可以对查询机制性能优化中的参数进行调整:

a. 将 parallel_degree_limit 设置为小于系统 CPU 数的某个数值,限制 Auto DOP 使用的最大 DOP;

b. 将 parallel_min_scan_time_threshold 调大,整体降低大量查询使用的并行度。

如限制最大使用 DOP = 32 、对基表扫描评估时间超过20ms的查询开启并行的参数设置如下:

set parallel_degree_policy = AUTO;
 set parallel_degree_limit = 32;
 set parallel_min_scan_time_threshold = 20;  

Auto DOP 策略下 DOP 的获取

Auto DOP 策略下首先通过一定算法确定基表扫描算子使用的 DOP(通过对不同并行度下基表扫描执行时间进行估计,综合 parallel_min_scan_time_threshold 和最大允许 DOP 得到,其它算子的 DOP 在计划生成的过程中,通过继承孩子算子的 DOP 获取。

如下方查询,表 tp1、tp2 插入一定规模数据后,使用 Auto DOP 策略,同时指定两表的连接顺序、分布式连接方法。对于表 A、B 使用 Auto DOP 策略,获取并行度为 DOP = 4、DOP = 8。两表使用 HASH HASH 的分布式连接方法后,HASH JOIN 算子的并行度通过继承得到,使用了左右两个孩子算子中较大并行度 DOP = 8。

create table tp1(c1 int, c2 int, c3 int) partition by hash(c1) partitions 4;
 create table tp2(c1 int, c2 int, c3 int) partition by hash(c1) partitions 8;
 explain basic
 select /*+leading(a b) use_hash(a b) pq_distribute(b hash hash) */
 * from tp1 a, tp2 b where a.c1 = b.c1;
 +------------------------------------------------------------------------------------------------+
 | Query Plan                                                                                     |
 +------------------------------------------------------------------------------------------------+
 | ===========================================                                                    |
 | |ID|OPERATOR                     |NAME    |                                                    |
 | -------------------------------------------                                                    |
 | |0 |PX COORDINATOR               |        |                                                    |
 | |1 | EXCHANGE OUT DISTR          |:EX10002|                                                    |
 | |2 |  HASH JOIN                  |        |                                                    |
 | |3 |   EXCHANGE IN DISTR         |        |                                                    |
 | |4 |    EXCHANGE OUT DISTR (HASH)|:EX10000|                                                    |
 | |5 |     PX BLOCK ITERATOR       |        |                                                    |
 | |6 |      TABLE SCAN             |A       |                                                    |
 | |7 |   EXCHANGE IN DISTR         |        |                                                    |
 | |8 |    EXCHANGE OUT DISTR (HASH)|:EX10001|                                                    |
 | |9 |     PX BLOCK ITERATOR       |        |                                                    |
 | |10|      TABLE SCAN             |B       |                                                    |
 | ===========================================                                                    |
 | Outputs & filters:                                                                             |
 | -------------------------------------                                                          |
 |   0 - output([INTERNAL_FUNCTION(A.C1, A.C2, A.C3, B.C1, B.C2, B.C3)]), filter(nil), rowset=256 |
 |   1 - output([INTERNAL_FUNCTION(A.C1, A.C2, A.C3, B.C1, B.C2, B.C3)]), filter(nil), rowset=256 |
 |       dop=8                                                                                    |
 |   2 - output([A.C1], [B.C1], [A.C2], [A.C3], [B.C2], [B.C3]), filter(nil), rowset=256          |
 |       equal_conds([A.C1 = B.C1]), other_conds(nil)                                             |
 |   3 - output([A.C1], [A.C2], [A.C3]), filter(nil), rowset=256                                  |
 |   4 - output([A.C1], [A.C2], [A.C3]), filter(nil), rowset=256                                  |
 |       (#keys=1, [A.C1]), dop=4                                                                 |
 |   5 - output([A.C1], [A.C2], [A.C3]), filter(nil), rowset=256                                  |
 |   6 - output([A.C1], [A.C2], [A.C3]), filter(nil), rowset=256                                  |
 |       access([A.C1], [A.C2], [A.C3]), partitions(p[0-3])                                       |
 |       is_index_back=false, is_global_index=false,                                              |
 |       range_key([A.__pk_increment]), range(MIN ; MAX)always true                               |
 |   7 - output([B.C1], [B.C2], [B.C3]), filter(nil), rowset=256                                  |
 |   8 - output([B.C1], [B.C2], [B.C3]), filter(nil), rowset=256                                  |
 |       (#keys=1, [B.C1]), dop=8                                                                 |
 |   9 - output([B.C1], [B.C2], [B.C3]), filter(nil), rowset=256                                  |
 |  10 - output([B.C1], [B.C2], [B.C3]), filter(nil), rowset=256                                  |
 |       access([B.C1], [B.C2], [B.C3]), partitions(p[0-7])                                       |
 |       is_index_back=false, is_global_index=false,                                              |
 |       range_key([B.__pk_increment]), range(MIN ; MAX)always true                               |
 +------------------------------------------------------------------------------------------------+  

小结

Auto DOP 提供了对各类 DML 操作自动开启并行的能力,一定程度上解决了依赖手动调整 DOP 的局限性。

Auto DOP 以计算基表最佳并行度为基础,在计划生成过程中根据计划形态不同产生各算子使用的并行度。使用这种策略能够得到相对稳定可靠的并行度,但对于一些特定场景开启的并行度可能偏小,如中间结果集相对基表扫描的数据量急剧膨胀、存在特定性能瓶颈等。

目前 Auto DOP 不具备系统负载感知、动态调整或反馈调整 DOP 的能力,因此使用 Auto DOP 时需要关注数据库的业务场景,针对查询性能极致优化或系统级性能优化对 Auto DOP 几个相关配置项进行调整。