【GaussDB】排查应用高可用切换出现数据库整体卡顿及报错自治事务无法创建的问题
背景
某客户在做应用程序的高可用切换测试,在应用程序中,收到了来自数据库的报错,不能创建自治事务
ERROR: autonomous transaction failed to create autonomous session
DETAIL: wait /data/gaussdb506/tmp:7456 timeout expired
数据库是GaussDB 506.0 SPC0100.0.0集中式单机,应用软件是两个节点,同一时刻只有一个应用节点会发生业务,当主应用节点故障时,另外一个应用节点会接管业务。
测试时,先让两个应用都开启,让主应用节点正常开始业务,然后断掉主应用节点的网卡,让备应用节点接管,顿时出现数据库卡顿,新建连接要很长时间,已经建立上的连接执行任何SQL都很卡。并且大约在25秒后,日志中出现了上面提到的自治事务创建报错。
GaussDB的自治事务是服务端自行使用libpq建立了一个本地连接。
分析
该应用开始执行业务时,会根据配置的并发任务数,创建对应个数的数据库连接,本次测试时的并发数配置为200。另外根据测试人员反馈,当并发为100时,就不会出现这个问题。
检查数据库最大连接数和最大自治事务连接数,远超测试并发度:
gaussdb=# show max_connections;
max_connections
-----------------
3000
(1 row)
gaussdb=# show max_concurrent_autonomous_transactions;
max_concurrent_autonomous_transactions
----------------------------------------
1000
(1 row)
该应用执行的每个业务,都会调用一个自治事务的存储过程用于记录日志,因此当业务并发数为200时,也会同时创建200个自治事务的连接,也就是说最大也才400个连接。
尝试编写简单用例进行复现,写一个shell文件,循环后台使用gsql调用自治事务的匿名块,模拟应用行为。
#!/bin/bash
rm -rf lll.log
for i in {1..200}
do
gsql -t <<'SQL_CMD' >>lll.log 2>&1 &
select 1;
declare
PRAGMA AUTONOMOUS_TRANSACTION;
begin
pg_sleep(30);
end;
/
select pg_sleep(200);
SQL_CMD
echo "Launched $i at $(date)" >> lll.log
done
由于是后台异步的,所以这个sh执行很快,top观察此时cpu和内存占用都不高,
ps -ef |grep gsql
也可以观察到还有200个gsql进程。
然后另开一个gsql连接,发现连接时间比之前长了很多。
连接上之后,执行以下SQL观察,此时任意SQL执行都会非常耗时
gaussdb=# select application_name,count(1) from pg_stat_activity where application_name in ('gsql','autonomoustransaction') and pid<>pg_backend_pid() group by application_name;
application_name | count
-----------------------+-------
autonomoustransaction | 54
gsql | 200
(2 rows)
发现gsql的主事务已经有200个了,但是自治事务却还只有54个,说明有大量的自治事务还没有创建,也就是说,并发创建自治事务的确会由于某种原因产生阻塞。
检查lll.log文件,的确也出现了创建自治事务超时的报错。
从官方文档中看有没有什么线索
《自治事务-规格约束》-https://doc.hcs.huawei.com/db/zh-cn/gaussdbqlh/25.1.30/devg-cent/gaussdb-42-0970.html
自治事务执行时,将会在后台启动自治事务session,可以通过max_concurrent_autonomous_transactions设置自治事务执行的最大并行数量,该参数取值范围为0~10000,默认值为10。
当max_concurrent_autonomous_transactions参数设置为0时,自治事务将无法执行。
自治事务新启session后,将使用默认session参数,不共享主session下对象(包括session级别变量,本地临时变量,全局临时表的数据等)。
自治事务理论上限为10000,实际上限为动态值,参考GUC参数max_concurrent_autonomous_transactions描述。
自治事务受通信缓冲区影响,返回给客户端的信息大小受限于通信缓冲区长度,超过通信缓冲区长度时报错。
自治事务的锁不受lock timeout影响,锁超时时间为2147483s,自治事务执行超过此时间会报错锁超时。
自治事务设置建立连接超时时间5s,建立连接尝试5次。建立连接期间不立即响应信号,每次建立连接前检查信号。高并发、高CPU、高内存,以及线程池扩容场景下可能存在超时报错现象。
在PACKAGE SPECIFICATION或PACKAGE BODY SPECIFICATION中声明自治事务PRAGMA AUTONOMOUS_TRANSACTION语法,可成功创建PACKAGE,但自治事务不生效。
其中有一条注意事项和当前现象有点像,超时5秒尝试5次,一共就是25秒。
于是观察故障时间点的CPU/内存/磁盘IO的情况,但是发现故障时间点,这些指标反而比平时更低,可以说服务器几乎没有干活了。那么有可能的就是"线程池扩容场景"了。
为了验证是不是与线程池特性有关,尝试关闭线程池再执行上面的shell测试。
发现自治事务创建非常顺利,也没有出现报错。
gaussdb=# select application_name,count(1) from pg_stat_activity where application_name in ('gsql','autonomoustransaction') and pid<>pg_backend_pid() group by application_name;
application_name | count
-----------------------+-------
autonomoustransaction | 200
gsql | 200
(2 rows)
似乎是与线程池功能有关,但是我在开启线程池后,改成400个并发,不使用自治事务,并没有出现阻塞的情况,400个gsql连接是瞬间就连接成功了。
直觉告诉我这里的设计应该有问题,从线程池里获取线程用于普通连接和用于自治事务连接,似乎走了两个不同的逻辑,自治事务连接会需要阻塞度更大的锁,但是没有源码没法分析。
大概看了一眼openGauss这里的自治事务超时,从3.1版本开始就改成了固定的10min,之前版本是没有额外的超时设置的,和libpq共用,也就是说自治事务的代码在openGauss和GaussDB已经有所区别了。不过我也测了下openGauss,在线程池模式下,的确也会出现并发创建自治事务导致数据库整体卡顿的现象,只是由于超时需要10min,所以没有出现报错,自治事务的连接数会慢慢增加。但并发度如果更高,还是可能会出现自治事务连接超过10min而报错的现象。
这个测试已经充分说明在GaussDB里,线程池开启时,并发创建自治事务会导致数据库出现整体卡顿、新连接建立缓慢、旧连接执行SQL缓慢、还有自治事务执行会出现超时报错的情况。但应用需求摆在这,如何解决这个问题呢?
回到GaussDB文档 ,“线程池扩容场景下可能存在超时报错现象” ,既然扩容就会超时报错,那么不让它扩容,一开始线程数就是够的,是不是就不会超时了?
在GaussDB中有一个这样的参数
thread_pool_attr
参数说明:用于控制线程池功能的详细属性,该参数仅在enable_thread_pool打开后生效,仅sysadmin用户可以访问。
参数类型:字符串
参数单位:无
取值范围:
该参数分为三个部分,‘thread_num, group_num, cpubind_info’,这三个部分的具体含义如下:
- thread_num:线程池中的初始线程总数,可以动态扩充,取值范围是0~4096。其中0的含义是数据库根据系统CPU core的数量来自动配置线程池的线程数,如果参数值大于0,线程池中的线程数等于thread_num。线程池大小建议根据硬件配置进行设置,计算公式如下:thread_num = CPU核数*(3~5),thread_num最大值为4096。
- group_num:线程池中的线程分组个数,取值范围是0~64。其中0的含义是数据库根据系统NUMA组的个数来自动配置线程池的线程分组个数,如果参数值大于0,线程池中的线程组个数等于group_num。
- cpubind_info:线程池是否绑核的配置参数。可选择的配置方式有:1. ‘(nobind)’ ,线程不做绑核;2. ‘(allbind)’,利用当前系统所有能查询到的CPU core做线程绑核;3. ‘(nodebind: 1, 2)’,利用NUMA组1、2中的CPU core进行绑核;4. ‘(cpubind: 0-30)’,利用0-30号CPU core进行绑核;5. ‘(numabind: 0-30)’,在NUMA组内利用0-30号CPU core进行绑核。该参数不区分大小写。当开启资源多租模式时,该参数不生效。
默认值:‘4096,2,(nobind)’(196核CPU/1536G内存,128核CPU/1024G内存,104核CPU/1024G内存,96核CPU/1024G内存);‘2048,2,(nobind)’(96核CPU/768G内存,80核CPU/640G内存);‘1024,2,(nobind)’(64核CPU/512G内存,60核CPU/480G内存,32核CPU/256G内存);‘512,2,(nobind)’(16核CPU/128G内存);‘256,2,(nobind)’(8核CPU/64G内存)
设置方式:该参数属于POSTMASTER类型参数,请参考表1中对应设置方法进行设置。
设置建议:内存充足且CPU性能好的情况,当业务需要更多连接时可以增加该参数值。
设置不当的风险与影响:若thread_pool_attr设置值大于等于max_connections,会导致proc资源全部被线程池占用,出现proc资源不足的问题。默认值即推荐值,不建议修改。修改必须详细确认参数的规格限制,并考虑硬件资源是否足够,否则可能导致数据库异常。
当前环境配置的是 256,2,(nobind)
,也就是说,数据库启动的时候就会同时启动256个worker线程,可以动态扩充意味着并不是只允许256个worker同时干活,它应该会根据实际需要自动增加。但是自治事务这里似乎有些差异,结合上面查询的会话数,主事务200个,自治事务54个,接近256个了,所以理论上把256调大,改成超过400,那么并发200个连接套自治事务,就不会报错了。
实测也的确如此,所以我们调整了安装的初始化参数配置,直接把thread_pool_attr改成了4096,2,(nobind)
,这样启动的时候就会有四千多个空闲的worker线程,在目前应用场景下绝对是够用的。
由于这个主事务加自治事务会话数需要低于启动时的线程池个数的表现,在openGauss中似乎并不存在,因此再看openGauss的源码分析,意义也不大了。
不过,这也意味着,在当前GaussDB数据库版本中,由于默认是开启了线程池的,所以如果业务代码经常使用自治事务,无论机器硬件配置有多高,最大支持的并发业务数其实只有2048个了,超过就可能导致数据库整体变慢,甚至还会出现报错。
结论
在GaussDB中,如果频繁使用自治事务,当thread_pool_attr的第一个值小于应用的并发连接数乘2时,数据库可能会出现严重的卡顿以及报错的现象,因此在硬件资源足够的情况下,建议把thread_pool_attr的第一个值配置成实际上最大客户端连接数的两倍。
- 本文作者: DarkAthena
- 本文链接: https://www.darkathena.top/archives/gaussdb-autonomoustransaction-create-timeout-hang
- 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处