问题背景
在社区论坛问答区里,总是能看到不断有同学发帖询问类似于 “执行 DDL 超时,为何调大超时时间不生效?” 之类的问题。其中有很多帖子里的问题,因为各种原因没有完全解决,最终都不了了之了,很是可惜。
OceanBase 里的超时时间确实比较多,自己始终也没把这些超时时间搞明白,上周看用户发的问答帖里,又有人在问一样的问题。所以趁热打铁,学习了一下这个最让用户困扰的 DDL 超时时间,在这里和大家分享一下。
帖子里对这类问题的描述都很简单:就是执行了一条 DDL,然后超时了,再然后把 ob_query_timeout 从默认的 10000000 微秒调整到了一个很大的时间,最后执行这条 DDL,结果又是超时。
DDL 超时时间
就当前 OceanBase 的产品设计而言,个人理解的超时机制主要包括两个纬度:控制语句超时的 ob_query_timeout 和控制事务超时的 ob_trx_timeout,看上去用户只需要关注这俩超时时间就够了。不过 ob_query_timeout 这个超时时间的名字起的过于通俗,很容易让人误解为会影响所有 query 的超时时间。
实际还有两个超时时间可能也需要部分用户了解下,一个是系统变量 ob_pl_block_timeout,因为 PL 内部可能会包含多条 query,例如:
create procedure test(x bigint, y bigint)
begin
insert /*+trans_param('enable_early_lock_release', 'true')*/ into elr_b (c1, c2) values (x, y);
update elr_a set c2 = c2 + 1 where c1 = 1;
commit;
end
在 PL 的执行过程中,任何超时的判断,均会以该系统变量为准。这个时间的默认值是十年左右,一般情况下用户对这个超时时间都只会有调小的需求。
obclient [test]> show variables like 'ob_pl_block_timeout';
+---------------------+------------------+
| Variable_name | Value |
+---------------------+------------------+
| ob_pl_block_timeout | 3216672000000000 |
+---------------------+------------------+
1 row in set (0.069 sec)
另一个超时时间就是经常被用户反复问到的 DDL 超时时间,这个超时时间不受 ob_query_timeout 的影响,而是受一个叫 _ob_ddl_timeout 的隐藏系统变量的控制,变量名开头有一个下划线,表明它是一个隐藏的配置项,一般也不需要进行修改。
因为是隐藏配置项,所以没找到特别详细的官方文档。在代码里搜索关键字,可以看到对这个 _ob_ddl_timeout 的描述:影响范围是集群级别,默认值是 1000 秒,修改完成后动态生效。
// src/share/parameter/ob_parameter_seed.ipp
// ddl 超时时间
DEF_TIME(_ob_ddl_timeout, OB_CLUSTER_PARAMETER, "1000s", "[1s,)",
"the config parameter of ddl timeout"
"Range: [1s, +∞)",
ObParameterAttr(Section::OBSERVER, Source::DEFAULT, EditLevel::DYNAMIC_EFFECTIVE));
对整个集群生效的变量,一般都是需要登录 sys 租户进行配置的,咱们登录 sys 租户看一眼。
obclient [oceanbase]> select name, data_type, value from oceanbase.__all_sys_parameter where name = '_ob_ddl_timeout';
Empty set (0.059 sec)
发现信息是空的。猜测大概率是因为这个集群里从来没有修改过这个 _ob_ddl_timeout 值,这张表只记录修改过默认值的变量,咱们修改下试试看。
obclient [oceanbase]> alter system set _ob_ddl_timeout = '1234s';
Query OK, 0 rows affected (0.046 sec)
obclient [oceanbase]> select name,data_type,value from oceanbase.__all_sys_parameter where name = '_ob_ddl_timeout';
+-----------------+-----------+-------+
| name | data_type | value |
+-----------------+-----------+-------+
| _ob_ddl_timeout | varchar | 1234s |
+-----------------+-----------+-------+
1 row in set (0.002 sec)
obclient [oceanbase]> alter system set _ob_ddl_timeout = '1h';
Query OK, 0 rows affected (0.035 sec)
obclient [oceanbase]> select name,data_type,value from oceanbase.__all_sys_parameter where name = '_ob_ddl_timeout';
+-----------------+-----------+-------+
| name | data_type | value |
+-----------------+-----------+-------+
| _ob_ddl_timeout | varchar | 1h |
+-----------------+-----------+-------+
1 row in set (0.007 sec)
obclient [oceanbase]> alter system set _ob_ddl_timeout = '1d';
Query OK, 0 rows affected (0.019 sec)
obclient [oceanbase]> select name,data_type,value from oceanbase.__all_sys_parameter where name = '_ob_ddl_timeout';
+-----------------+-----------+-------+
| name | data_type | value |
+-----------------+-----------+-------+
| _ob_ddl_timeout | varchar | 1d |
+-----------------+-----------+-------+
1 row in set (0.002 sec)
obclient [oceanbase]> alter system set _ob_ddl_timeout = '1m';
Query OK, 0 rows affected (0.019 sec)
obclient [oceanbase]> select name,data_type,value from oceanbase.__all_sys_parameter where name = '_ob_ddl_timeout';
+-----------------+-----------+-------+
| name | data_type | value |
+-----------------+-----------+-------+
| _ob_ddl_timeout | varchar | 1m |
+-----------------+-----------+-------+
1 row in set (0.001 sec)
好了,看来和猜想一致,调整成非默认值就能查到了。
1000 秒的时间已经足够长,一般不需要进行调整。如果执行的 DDL 超时了,大多都是因为用户并发执行了一大批 DDL,大部分类型的 DDL 在 OceanBase 里暂时都还是串行处理的,所以排队时间可能会超过这个 _ob_ddl_timeout(这种情况在用户压测时偶尔会遇到)。还有一种情况就是用户执行的 DDL 真的超级复杂,1000 秒就是处理不完(这种情况还真没遇到过)。
问答贴里“执行 DDL 超时,为何调大超时时间不生效?” 的问题,如果执行时间真的是到了 1000 秒的话,到这里基本就可以告一段落了,结论是调大 _ob_ddl_timeout 就好了。
特殊的 DDL 超时时间
有一些用户看到这里可能会有疑问,就是对于一些会受表中数据量影响的 DDL,例如创建索引、创建外键约束、创建 check 约束等,是不是需要有特殊的超时逻辑?
因为这些 DDL 会对表中已有的存量数据进行检查,或者需要把这些存量数据写入到一张新表中,所以执行时间显然不可控。这个默认的 _ob_ddl_timeout 只有 1000 秒,肯定不够用。是不是对于这类特殊的 DDL,应该把超时时间的默认值改成 INT64_MAX 之类的超长超时时间?
OceanBase 看上去也确实是这样做的,只不过它把这类特殊 DDL 的超时时间写成了 102 年。看代码里的注释说原因是为了防止对 INT64_MAX 做加法导致计算结果溢出。
// deps/oblib/src/lib/ob_define.h
// The maximum time set by the user through hint/set session.ob_query_timeout/set session.ob_tx_timeout is 102 years
// The purpose of this is to avoid that when the user enters a value that is too large, adding the current timestamp causes the MAX_INT64 to overflow
const int64_t OB_MAX_USER_SPECIFIED_TIMEOUT = 102L * 365L * 24L * 60L * 60L * 1000L * 1000L;
至于为什么是 102 年,猜测是因为马云很久以前曾提出过一个阿里巴巴要活 102 年的愿望,从 1999 年开始横跨三个世纪,所以 OceanBase 的研发同学特意在代码里埋下了一个彩蛋。
对超时时间日志易用性的期待
看用户发的问答帖,发现大家在遇到超时问题之后,基本都是尝试性地逐个调大自己知道的几个超时时间,但是 OceanBase 里的超时时间很多,下面列出冰山一角。
obclient [test]> show global variables like '%timeout%';
+---------------------+------------------+
| Variable_name | Value |
+---------------------+------------------+
| connect_timeout | 10 |
| interactive_timeout | 28800 |
| lock_wait_timeout | 31536000 |
| net_read_timeout | 30 |
| net_write_timeout | 60 |
| ob_pl_block_timeout | 3216672000000000 |
| ob_query_timeout | 10000000 |
| ob_trx_idle_timeout | 86400000000 |
| ob_trx_lock_timeout | -1 |
| ob_trx_timeout | 86400000000 |
| wait_timeout | 28800 |
+---------------------+------------------+
11 rows in set (0.064 sec)
obclient [test]> show parameters like '%timeout%';
+-------+----------+--------------+----------+-----------------------------------------------+-----------+-------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------+---------+---------+-------------------+---------------+-----------+
| zone | svr_type | svr_ip | svr_port | name | data_type | value | info | section | scope | source | edit_level | default_value | isdefault |
+-------+----------+--------------+----------+-----------------------------------------------+-----------+-------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------+---------+---------+-------------------+---------------+-----------+
| zone1 | observer | 11.158.31.20 | 22602 | sys_bkgd_migration_change_member_list_timeout | NULL | 20s | the timeout for migration change member list retry. The default value is 20s. Range: [0s,24h] | OBSERVER | CLUSTER | DEFAULT | DYNAMIC_EFFECTIVE | 20s | 1 |
| zone1 | observer | 11.158.31.20 | 22602 | location_cache_refresh_sql_timeout | NULL | 1s | The timeout used for refreshing location cache by SQL. Range: [1ms, +∞) | LOCATION_CACHE | CLUSTER | DEFAULT | DYNAMIC_EFFECTIVE | 1s | 1 |
| zone1 | observer | 11.158.31.20 | 22602 | location_cache_refresh_rpc_timeout | NULL | 500ms | The timeout used for refreshing location cache by RPC. Range: [1ms, +∞) | LOCATION_CACHE | CLUSTER | DEFAULT | DYNAMIC_EFFECTIVE | 500ms | 1 |
| zone1 | observer | 11.158.31.20 | 22602 | rpc_timeout | NULL | 2s | the time during which a RPC request is permitted to execute before it is terminated | RPC | CLUSTER | DEFAULT | DYNAMIC_EFFECTIVE | 2s | 1 |
| zone1 | observer | 11.158.31.20 | 22602 | balancer_task_timeout | NULL | 20m | the time to execute the load-balancing task before it is terminated. Range: [1s, +∞) | LOAD_BALANCE | CLUSTER | DEFAULT | DYNAMIC_EFFECTIVE | 20m | 1 |
| zone1 | observer | 11.158.31.20 | 22602 | dead_socket_detection_timeout | NULL | 3s | specify a tcp_user_timeout for RFC5482. A zero value makes the option disabled, Range: [0, 2h] | OBSERVER | CLUSTER | DEFAULT | DYNAMIC_EFFECTIVE | 3s | 1 |
| zone1 | observer | 11.158.31.20 | 22602 | debug_sync_timeout | NULL | 0 | Enable the debug sync facility and optionally specify a default wait timeout in micro seconds. A zero value keeps the facility disabled, Range: [0, +∞] | OBSERVER | CLUSTER | DEFAULT | DYNAMIC_EFFECTIVE | 0 | 1 |
| zone1 | observer | 11.158.31.20 | 22602 | internal_sql_execute_timeout | NULL | 30s | the number of microseconds an internal DML request is permitted to execute before it is terminated. Range: [1000us, 1h] | OBSERVER | CLUSTER | DEFAULT | DYNAMIC_EFFECTIVE | 30s | 1 |
| zone1 | observer | 11.158.31.20 | 22602 | arbitration_timeout | NULL | 5s | The timeout before automatically degrading when arbitration member exists. Range: [3s,+∞] | TRANS | TENANT | DEFAULT | DYNAMIC_EFFECTIVE | 5s | 1 |
| zone1 | observer | 11.158.31.20 | 22602 | ob_query_switch_leader_retry_timeout | NULL | 0ms | max time spend on retry caused by leader swith or network disconnectionRange: [0ms, +∞) | OBSERVER | TENANT | DEFAULT | DYNAMIC_EFFECTIVE | 0ms | 1 |
| zone1 | observer | 11.158.31.20 | 22602 | standby_db_fetch_log_rpc_timeout | NULL | 15s | The threshold for detecting the RPC timeout for the standby tenant to fetch log from the log restore source tenant. When the rpc timeout, the log transport service switches to another server of the log restore source tenant to fetch logs. Range: [2s, +∞) | LOGSERVICE | TENANT | DEFAULT | DYNAMIC_EFFECTIVE | 15s | 1 |
+-------+----------+--------------+----------+-----------------------------------------------+-----------+-------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------+---------+---------+-------------------+---------------+-----------+
11 rows in set (0.104 sec)
如果遇到了 SQL 超时,想知道这条 SQL 到底受到了哪个超时时间的限制,看报错日志好像也看不出来,因为无论由于什么超时报错,错误码好像都是 4012。
希望未来 OceanBase 能够把由于不同超时时间导致的报错,匹配上不同的错误码,提升一些日志的易用性。例如 ob_query_timeout 超时报错 40120,ob_trx_timeout 超时报错 40121,_ob_ddl_timeout 超时报错 40122。这样大家看到错误码后,马上就可以知道要调整哪个超时时间了。
其他
前两天发现 4.3.0 及以上版本的 OCP 和 OBserver 支持了一个叫参数模板的功能。这个参数模板会预先将租户的参数设置好,在需要创建配置相似的一系列租户的场景下,创建各租户时可直接应用参数模板,如此可不必反复配置租户参数。在这里推荐给大家。
例如在实时分析的场景下,选择 OLAP 这个参数模板,系统就会自动调整 ob_query_timeout 这种超时时间参数,让这个租户更适合进行复杂的查询。
除了租户级的参数模板,还有集群级的参数模板,大家感兴趣的话,自己去 OceanBase 官网搜一下 “参数模板” 关键字吧~