openGauss
开源数据库
openGauss社区官网
开源社区
从运维视角来解析vacuum机制跟相关参数(2)
上一篇从运维视角解析了vacuum的机制,但还没讲解完,有几个跟freeze相关的参数还没有涉及到,今天接着往下继续挖,看能否解析到相关参数。
上一篇讲到了vacuum线程如何调起,如何选择db进行vacuum ,以及选择哪些表进行vacuum , 但还没有讲到vacuum是如何做的,做完后影响了(更新了)哪些视图数据。
我们先来看一下test1表的跟vacuum相关的视图的当前数据。
xcytest=# select oid,relname,relfrozenxid,relfrozenxid64 from pg_class where relname='test1';
oid | relname | relfrozenxid | relfrozenxid64
-------+---------+--------------+----------------
16408 | test1 | 0 | 2270175
当前的fronzenxid为2270175
xcytest=# delete from test1 limit 10000;
DELETE 10000
xcytest=# delete from test1 limit 30000;
DELETE 30000
xcytest=# select * from pg_stat_all_tables where relname='test1';
-[ RECORD 1 ]-----+------------------------------
relid | 16408
schemaname | public
relname | test1
seq_scan | 4
seq_tup_read | 430000
idx_scan |
idx_tup_fetch |
n_tup_ins | 0
n_tup_upd | 0
n_tup_del | 40000
n_tup_hot_upd | 0
n_live_tup | 0
n_dead_tup | 40000
last_vacuum | 2024-05-23 11:33:59.193913+08
last_autovacuum | 2024-05-23 11:33:59.193913+08
last_analyze |
last_autoanalyze |
vacuum_count | 2
autovacuum_count | 2
analyze_count | 0
autoanalyze_count | 0
last_data_changed | 2024-05-23 11:34:19.086808+08
手工删除了40000条记录了,在这里提一个问题,为什么手工删除了40000条记录了,表还没有被选中自动做vacuum? 表的定义如下:
xcytest=# select pg_get_tabledef(16408);
-[ RECORD 1 ]---+----------------------------------------------------------------------------------------------------------
pg_get_tabledef | SET search_path = public;
| CREATE TABLE test1 (
| id integer,
| name character varying(2000)
| )
| WITH (orientation=row, compression=no, autovacuum_freeze_max_age=100000, autovacuum_vacuum_threshold=10);
同时,为了更容易触发,还特意表将autovacuum_vacuum_scale_factor的参数修改成了0.001 , 按照前面介绍到的,死亡元组达到设定的阈值之后,就会执行vacuum。已经删除了40000行,但为何迟迟没有执行vacuum ? 一度怀疑之前的解析有差错。再次跟踪do_autovacuum函数,最后发现它确实会做,调试跟跟踪如下:
(gdb) l autovacuum.cpp:3284
3279
3280 reltuples = classForm->reltuples;
3281 vacthresh = (float4)vac_base_thresh + vac_scale_factor * reltuples;
3282 anlthresh = (float4)anl_base_thresh + anl_scale_factor * reltuples;
3283
3284 if ((avwentry == NULL) && (tabentry == NULL)) {
3285 *dovacuum = force_vacuum;
3286 *doanalyze = false;
3287 } else {
3288 if (tabentry && (tabentry->changes_since_analyze || tabentry->n_dead_tuples)) {
(gdb) p reltuples
$33 = 160000 #表的当前行数为160000, 200000-40000(删除了40000)
(gdb) p reltuples
$34 = 160000
(gdb) p vac_scale_factor 数据库参数定义,vac的比例
$35 = 0.00100000005
(gdb) p anl_scale_factor
$36 = 0.100000001 数据库参数定义,anlayze 的比例
(gdb) p vacthresh 根据上述代码的计算方式,计算出死亡元组达到该值后进行vacuum.
$37 = 170
(gdb) p anlthresh 根据上述代码的计算方式,计算出行变化的数量达到该值后进行anlayze.
$38 = 16050
(gdb) p vac_base_thresh
$39 = 10
(gdb) p anl_base_thresh
$40 = 50
至于迟迟没有做vacuum的原因,是因为调度周期还没有到,参数autovacuum_naptime 的默认值为600秒,也就是10分钟一个调度周期。 上面算是回顾了上一篇所学习的内容,加深了对上一篇所得的理解,咱们接着往下解析。上一篇提到,关于freeze的参数还没有解析到,继续往下学习,看是否能够得到答案。 继续往下找,看到了vacuum_set_xid_limits函数,该函数的调用堆栈如下,在下面的堆栈中,我们看到了函数的入参freeze_min_age 以及freeze_table_age的值,刚好是数据库参数vacuum_freeze_min_age与vacuum_freeze_table_age,解析完这部分,大概可以了解到该参数的具体作用。
(gdb) bt
#0 vacuum_set_xid_limits (rel=0x7f460121cea8, freeze_min_age=80, freeze_table_age=100, oldestXmin=0x7f461410ce40,
freezeLimit=0x7f461410ce48, freezeTableLimit=0x7f4609cca7b8, multiXactFrzLimit=0x7f461410ce50) at vacuum.cpp:1092
#1 0x00000000016dd4f2 in lazy_vacuum_rel (onerel=0x7f460121cea8, vacstmt=0x7f4609ccae60, bstrategy=0x7f4601356088) at vacuumlazy.cpp:346
#2 0x00000000016d297a in TableRelationVacuum (rel=0x7f460121cea8, vacstmt=0x7f4609ccae60, lockmode=4, vacStrategy=0x7f4601356088)
at vacuum.cpp:1867
#3 0x00000000016d6610 in vacuum_rel (relid=16408, vacstmt=0x7f4609ccae60, do_toast=false) at vacuum.cpp:2785
#4 0x00000000016cdc62 in vacuum (vacstmt=0x7f4609ccae60, relid=16408, do_toast=false, bstrategy=0x7f4601356088, isTopLevel=true)
at vacuum.cpp:352
#5 0x000000000173d1d6 in autovacuum_local_vac_analyze (tab=0x7f46013568b0, bstrategy=0x7f4601356088) at autovacuum.cpp:3448
#6 0x0000000001739e71 in do_autovacuum () at autovacuum.cpp:2762
#7 0x0000000001734363 in AutoVacWorkerMain () at autovacuum.cpp:1410
#8 0x0000000001790910 in GaussDbThreadMain<(knl_thread_role)8> (arg=0x7f464c53e448) at postmaster.cpp:12980
#9 0x000000000178a6ba in InternalThreadFunc (args=0x7f464c53e448) at postmaster.cpp:13517
#10 0x000000000232811f in ThreadStarterFunc (arg=0x7f464c53e438) at gs_thread.cpp:382
#11 0x00007f46e848dea5 in start_thread () from /lib64/libpthread.so.0
#12 0x00007f46e81b68dd in clone () from /lib64/libc.so.6
我们来解析这个函数,目的是通过计算,得出freezelimit与freetablelimit的值,然后给调用函数lazy_vacuum_rel使用。
void vacuum_set_xid_limits(Relation rel, int64 freeze_min_age, int64 freeze_table_age, TransactionId* oldestXmin,
TransactionId* freezeLimit, TransactionId* freezeTableLimit, MultiXactId* multiXactFrzLimit)
{
int64 freezemin;
TransactionId limit;
TransactionId safeLimit;
TransactionId nextXid;
。。。。。。。。
获取当前所有会话中最老的事务id.
*oldestXmin = GetOldestXmin(rel);
将入参赋值给临时变量。
freezemin = freeze_min_age;
下面是计算freezeLimit的值,可以简单理解为当前活跃的最老的事务id,
减去参数vacuum_freeze_min_age的值所得。
如果有从库,从库的当前最老的活跃事务id也在考虑之内.
计算出来的值,会给调用函数使用。
freezemin = Min(freezemin, g_instance.attr.attr_storage.autovacuum_freeze_max_age / 2);
limit = *oldestXmin;
if (limit > FirstNormalTransactionId + (uint64)freezemin)
limit -= (uint64)freezemin;
else
limit = FirstNormalTransactionId;
*freezeLimit = limit;
接下来计算freezeTableLimit的值,可以简单解析为当前最新的xid ,
减去参数vacuum_freeze_table_age的值所得。
计算出来的值,会给调用函数使用。
freezetable = freeze_table_age;
freezetable = Min(freezetable, g_instance.attr.attr_storage.autovacuum_freeze_max_age * 0.95);
limit = ReadNewTransactionId();
if (limit > FirstNormalTransactionId + (uint64)freezetable)
limit -= (uint64)freezetable;
else
limit = FirstNormalTransactionId;
*freezeTableLimit = limit;
。。。。。。。。。。
}
接下来,我们看lazy_vacuum_rel函数是怎么使用这两个变量的。该函数是通过下面的调用语句来获得这两个变量的值,然后接着就使用。
通过下面函数计算出u_sess->cmd_cxt.FreezeLimit与freezeTableLimit
的值。
vacuum_set_xid_limits(onerel,
vacstmt->freeze_min_age,
vacstmt->freeze_table_age,
&u_sess->cmd_cxt.OldestXmin,
&u_sess->cmd_cxt.FreezeLimit,
&freezeTableLimit,
&u_sess->cmd_cxt.MultiXactFrzLimit);
然后将表的relfrozenxid(可以通过pg_class查询到)跟freezeTableLimit
比较,如果小,则scan_all等于true, 做vacuum的时候,
会对表的所有页面进行scan. 从这里,我们可以得到参数vacuum_freeze_table_age
的真正意义,同时也应该明白了该参数应该怎样合理设置:设置太小,将
导致表做vacuum的时候,没有修改的页面也被无意义的重复扫描,白白加重
数据库的负担。 如果scan_all为false,只会对不是完全可见的页面进行扫描,
也就是进行过删除数据,或者修改数据的页面进行扫描,页面是否完全可见,
来自页面可见性位图数据的判断,这里不再展开。
scan_all = TransactionIdPrecedesOrEquals(relfrozenxid, freezeTableLimit);
上面讲到了freezeTableLimit的意义,还没有讲到u_sess->cmd_cxt.FreezeLimit,该变量在什么时候会被用到? 如下,在判断表里面的行是否需要做freeze的时候被使用到。
-00.D0y-ogfS.png)
我们来看看这个heap_tuple_needs_freeze函数,函数非常简单,如下,最简单的场景,就是xmin小于u_sess->cmd_cxt.FreezeLimit时,该行就会被freeze.
-01.DMHIDUzr.png)
到这里,我们大概知道了vacuum_freeze_min_age参数的意义,该参数决定了表里面的行在什么时候做freeze。如果对行过早做freeze,freeze之后,该行又被更新了,之前的freeze就白做了。另外,freeze 会修改行的infomask ,自然会产生脏页,如果一个页面中不同行,在不同的批次做freeze,自然带来额外的刷脏负担。 所以,设置非常合理的vacuum相关的参数,对数据库的性能的稳定性非常重要,有时候,统一的数据库参数可能无法适用所有的表的各自使用场景,为个别表单独设置特定的参数,我想是非常有必要的。