diff --git "a/app/zh/blogs/chunyangxu/2024-05-20-\344\273\216\350\277\220\347\273\264\350\247\206\350\247\222\346\235\245\350\247\243\346\236\220vacuum\346\234\272\345\210\266\344\270\216\347\233\270\345\205\263\345\217\202\346\225\260\357\274\2101\357\274\211.md" "b/app/zh/blogs/chunyangxu/2024-05-20-\344\273\216\350\277\220\347\273\264\350\247\206\350\247\222\346\235\245\350\247\243\346\236\220vacuum\346\234\272\345\210\266\344\270\216\347\233\270\345\205\263\345\217\202\346\225\260\357\274\2101\357\274\211.md"
new file mode 100644
index 0000000000000000000000000000000000000000..ae398e6e3e8538f20ef6d6de21ed056279aa1340
--- /dev/null
+++ "b/app/zh/blogs/chunyangxu/2024-05-20-\344\273\216\350\277\220\347\273\264\350\247\206\350\247\222\346\235\245\350\247\243\346\236\220vacuum\346\234\272\345\210\266\344\270\216\347\233\270\345\205\263\345\217\202\346\225\260\357\274\2101\357\274\211.md"
@@ -0,0 +1,297 @@
+---
+title: "从运维视角来解析vacuum机制跟相关参数(1)"
+date: '2024-05-20'
+category: 'blog'
+tags: ['openGauss']
+archives: '2024-05'
+author:'xuchunyang'
+summary: "从运维视角来解析vacuum机制跟相关参数"
+---
+
+ 这两天想仔细了解一下vacuum机制,因为该机制会影响数据库的性能以及表的占用空间。通过网上了解一些资料,有些是从纯代码角度来解析的,有些是用纯文字来描述的,看了之后,似懂非懂,心中还是没有完全理清楚vacuum的机制。于是,准备按照自己的思路,来撸撸opengauss的代码,以便解答自己的疑惑。下面将描述个人的学习思路以及学习所得。
+
+ 想了解vacuum的机制,打算从参数入手,vacuum相关的参数如下:
+
+```
+xcytest=# select name,setting from pg_settings where name like '%vacuum%';
+ name | setting
+---------------------------------+------------
+ autovacuum | on
+ autovacuum_analyze_scale_factor | 0.1
+ autovacuum_analyze_threshold | 50
+ autovacuum_freeze_max_age | 4000000000
+ autovacuum_io_limits | -1
+ autovacuum_max_workers | 3
+ autovacuum_mode | mix
+ autovacuum_naptime | 600
+ autovacuum_vacuum_cost_delay | 20
+ autovacuum_vacuum_cost_limit | 20
+ autovacuum_vacuum_scale_factor | 0.001
+ autovacuum_vacuum_threshold | 50
+ enable_debug_vacuum | on
+ log_autovacuum_min_duration | 1
+ vacuum_cost_delay | 0
+ vacuum_cost_limit | 200
+ vacuum_cost_page_dirty | 20
+ vacuum_cost_page_hit | 1
+ vacuum_cost_page_miss | 10
+ vacuum_defer_cleanup_age | 0
+ vacuum_freeze_min_age | 80
+ vacuum_freeze_table_age | 100
+ vacuum_gtt_defer_check_age | 10000
+(23 rows)
+
+```
+
+通过参数的名字,在网上搜索一通,对参数有大概的了解。但是还是以下疑惑:
+
+ 1.vacuum_freeze_min_age,vacuum_free_table_age,autovacuum_freeze_max_age 这三个参数,都跟freeze有关,但一个表到底什么时候做freeze/vacuum,最终是由哪个参数决定的?为什么需要这么多类似的参数?(这个问题提出来了,但写完这篇文章后,但还是没有解析到freeze这一步,后续继续解析)。
+
+ 2. autovacuum_vacuum_threshold ,autovacuum_vacuum_scale_factor这两跟vacuum有关,第一问提到的参数也大概跟vacuum有关,这两个参数的准确含义又是什么?
+
+ 3. .autovacuum_vacuum_cost_limit 跟vacuum_cost_limit这两个参数,都跟vacuum cost有关,具体各自限制什么?其他cost相关的参数有类似疑惑。
+
+ 4. autovacuum_max_workers 这个参数应该跟vacuum 线程有关系,vacuum线程之间是如何协同工作的?
+
+作者本来的解析路线是,先通过跟踪vacuum 函数,找到哪个表在做vacuum ,然后进一步去找为什么这个表触发了vacuum ,而其他表没有触发vacuum的原因,找到表触发vacuum的原因之后,进一步挖掘vacuum线程是怎么调度这个表来做vacuum的,vacuum线程是如何调度起来的,何时调度起来的。这个解析路径回头看,特别合理跟清晰,但当时处于完全未知的状态时,一顿瞎摸索。
+
+但本次总结vacuum ,不打算按照我摸索的路线来,准备从源头开始,从vacuum线程是如何调度的开始:
+
+opengauss 后台线程中,有个叫AVClauncher常驻线程,该线程负责调度vacuum 线程,在不做autovacuum的时候,是没有vacuum线程在执行的。我们来看看autovacuum线程是如何被调度起来的。线程vacuum调度命令跟堆栈如下:
+```
+(gdb) bt
+#0 do_start_worker () at autovacuum.cpp:880
+#1 0x000000000173382e in launch_worker (now=769162991223266) at autovacuum.cpp:1096
+#2 0x000000000173273c in AutoVacLauncherMain () at autovacuum.cpp:577
+#3 0x0000000001790494 in GaussDbThreadMain<(knl_thread_role)7> (arg=0x7feb2253e170) at postmaster.cpp:12974
+#4 0x000000000178a6ba in InternalThreadFunc (args=0x7feb2253e170) at postmaster.cpp:13517
+#5 0x000000000232811f in ThreadStarterFunc (arg=0x7feb2253e160) at gs_thread.cpp:382
+#6 0x00007febbe357ea5 in start_thread () from /lib64/libpthread.so.0
+#7 0x00007febbe0808dd in clone () from /lib64/libc.so.6
+```
+我们来解析do_start_worker函数,其中有下面一段:
+```
+ foreach (cell, dblist) {
+ avw_dbase* tmp = (avw_dbase*)lfirst(cell);
+ Dlelem* elem = NULL;
+
+ /* Check to see if this one is need freeze */
+ if (TransactionIdPrecedes(tmp->adw_frozenxid, xidForceLimit)) {
+ if (avdb == NULL || TransactionIdPrecedes(tmp->adw_frozenxid, avdb->adw_frozenxid))
+ avdb = tmp;
+ for_xid_wrap = true;
+ continue;
+```
+上面这段代码的作用为遍历所有的数据库,在frozenxid小于xidForceLimit的情况下,找到frozenxid最小的数据库优先调度,同时,将参数for_xid_wrap设置为true. 单纯从变量名称的字面意思来理解,在这种情况下,做vacuum是为了防止xid回卷。但大部分情况下,这个条件不容易满足,做vacuum还有另外一个目的是为了删除死亡元组,而不仅仅是为了防止xid回卷。 这个xidForceLimit变量的值跟参数autovacuum_freeze_max_age有关系(如下)。当recentXid大于autovacuum_freeze_max_age的时候,取值为t_thrd.autovacuum_cxt.recentXid 减去autovacuum_freeze_max_age;否则就为3.
+```
+ t_thrd.autovacuum_cxt.recentXid = ReadNewTransactionId();
+ if (t_thrd.autovacuum_cxt.recentXid >
+ FirstNormalTransactionId + (uint64)g_instance.attr.attr_storage.autovacuum_freeze_max_age)
+ xidForceLimit = t_thrd.autovacuum_cxt.recentXid -
+ (uint64)g_instance.attr.attr_storage.autovacuum_freeze_max_age;
+ else
+ xidForceLimit = FirstNormalTransactionId;
+```
+
+在没有数据库满足frozenxid小于xidForceLimit的情况下,依然会选择数据库进行调度,原因如下:avdb变量最终总会赋值。
+```
+ avw_dbase* tmp = (avw_dbase*)lfirst(cell);
+ ...................
+ if (avdb == NULL || tmp->adw_entry->last_autovac_time < avdb->adw_entry->last_autovac_time)
+ avdb = tmp;
+```
+
+给avdb赋值后,下面就是具体的调度vaccuum worker线程的代码,通过共享内存AutoVacuumShmem控制运行中的work线程的数量,该数量的大小由参数autovacuum_max_workers控制。如果没有空闲的worker线程,则停止调度。用变量worker->wi_dboid指定此次被调度执行vacuum的数据库的oid. 然后给postmaster线程发送PMSIGNAL_START_AUTOVAC_WORKER信号,postmaster线程收到信号后,就会调起vacuum worker线程。
+```
+ worker = t_thrd.autovacuum_cxt.AutoVacuumShmem->av_freeWorkers;
+ if (worker == NULL)
+ ereport(FATAL, (errmsg("no free worker found")));
+
+ t_thrd.autovacuum_cxt.AutoVacuumShmem->av_freeWorkers = (WorkerInfo)worker->wi_links.next;
+
+ worker->wi_dboid = avdb->adw_datid;
+ worker->wi_proc = NULL;
+ worker->wi_launchtime = GetCurrentTimestamp();
+
+ t_thrd.autovacuum_cxt.AutoVacuumShmem->av_startingWorker = worker;
+
+ LWLockRelease(AutovacuumLock);
+
+ SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_WORKER);
+
+ retval = avdb->adw_datid
+```
+
+ 后面就进入autovacuum worker的线程的处理流程。autovacuum worker线程对指定的数据库(由上面的处理流程指定)进行vacuum. 堆栈如下:
+
+
+下面我们来看看do_autovacuum函数是如何选择需要vacuum的表来进行vacuum的。
+
+```
+ t_thrd.autovacuum_cxt.default_freeze_min_age = u_sess->attr.attr_storage.vacuum_freeze_min_age;
+ t_thrd.autovacuum_cxt.default_freeze_table_age = u_sess->attr.attr_storage.vacuum_freeze_table_age;
+```
+
+首先对上面两个变量赋值,看起来跟参数free_min_age,free_table_age有关。对比上面查询pg_settings表出来的结果,赋值还真的是来源参数vacuum_freeze_min_age与vacuum_freeze_table_age
+
+```
+(gdb) p u_sess->attr.attr_storage.vacuum_freeze_min_age
+$1 = 80
+(gdb) p u_sess->attr.attr_storage.vacuum_freeze_table_age
+$2 = 100
+```
+
+然后开始扫描表pg_class,代码如下,1259为pg_class表的oid. 然后找出需要vacuum的表。
+
+```
+#define RelationRelationId 1259
+ classRel = heap_open(RelationRelationId, AccessShareLock);
+ relScan = tableam_scan_begin(classRel, SnapshotNow, 0, NULL);
+ while ((tuple = (HeapTuple) tableam_scan_getnexttuple(relScan, ForwardScanDirection)) != NULL) {
+ /*如果表的类型不是RELKIND_RELATION或者RELKIND_MATVIEW,则
+ 直接跳过*/
+ if (classForm->relkind != RELKIND_RELATION &&
+ classForm->relkind != RELKIND_MATVIEW)
+ continue;
+ Oid relid = HeapTupleGetOid(tuple);
+ /* Fetch reloptions for this table */
+ relopts = extract_autovac_opts(tuple, pg_class_desc)
+ /*检查表类型是否支持vacuum以及analyze*/
+ relation_support_autoavac(tuple, &enable_analyze, &enable_vacuum, &is_internal_relation);
+ /*检查表是否需要vacuum以及analyze*,这个函数决定表是否需要做vacuum*/
+ relation_needs_vacanalyze(relid, relopts, rawRelopts, classForm, tuple, tabentry, enable_analyze, enable_vacuum,
+ false, &dovacuum, &doanalyze, &need_freeze)
+
+ /* relations that need work are added to table_oids */
+ if (dovacuum || doanalyze || isUstorePartitionTable) {
+ vacObj = (vacuum_object*)palloc(sizeof(vacuum_object));
+ vacObj->tab_oid = relid;
+ vacObj->parent_oid = InvalidOid;
+ vacObj->dovacuum = isUstorePartitionTable ? true : dovacuum;
+ vacObj->dovacuum_toast = false;
+ vacObj->doanalyze = doanalyze;
+ vacObj->need_freeze = isUstorePartitionTable ? false : need_freeze;
+ vacObj->is_internal_relation = isUstorePartitionTable ? false : is_internal_relation;
+ vacObj->gpi_vacuumed = false;
+ vacObj->flags = (isPartitionedRelation(classForm) ? VACFLG_MAIN_PARTITION : VACFLG_SIMPLE_HEAP);
+ table_oids = lappend(table_oids, vacObj);
+ }
+
+
+ }
+```
+
+下面我们来重点看一下relation_needs_vacanalyze 这个函数,这个函数决定了表是否需要vacuum 或者analyze .
+
+```
+ /*获取跟vacuum 相关的参数,这些值来自于数据库参数,具体后面再详细解析*/
+ determine_vacuum_params(vac_scale_factor, vac_base_thresh, anl_scale_factor, anl_base_thresh, freeze_max_age,
+ av_enabled, xidForceLimit, multiForceLimit, relopts);
+ /*获取该表的relforzenxid,用于后面的判断*/
+ Datum xid64datum = heap_getattr(tuple, Anum_pg_class_relfrozenxid64, RelationGetDescr(rel), &isNull);
+
+ if (isNull) {
+ relfrozenxid = classForm->relfrozenxid;
+
+ if (TransactionIdPrecedes(t_thrd.xact_cxt.ShmemVariableCache->nextXid, relfrozenxid) ||
+ !TransactionIdIsNormal(relfrozenxid)) {
+ relfrozenxid = FirstNormalTransactionId;
+ }
+ } else {
+ relfrozenxid = DatumGetTransactionId(xid64datum);
+ }
+ /* 根据relfrozenxid的值,判断是否需要force_vacuum*/
+ force_vacuum = (TransactionIdIsNormal(relfrozenxid) && TransactionIdPrecedes(relfrozenxid, xidForceLimit));
+#ifndef ENABLE_MULTIPLE_NODES
+ if (!force_vacuum) {
+ Datum mxidDatum = heap_getattr(tuple, Anum_pg_class_relminmxid, RelationGetDescr(rel), &isNull);
+ MultiXactId relminmxid = isNull ? FirstMultiXactId : DatumGetTransactionId(mxidDatum);
+ force_vacuum = (MultiXactIdIsValid(relminmxid) && MultiXactIdPrecedes(relminmxid, multiForceLimit));
+ }
+ #endif
+ *need_freeze = force_vacuum;
+ /*判断用户是否对该表禁用了vacuum,貌似可以通过参数设置,
+ 除了force_vacuum场景需要外,禁止对表进行vacuum ?*/
+
+ /* User disabled it in pg_class.reloptions? (But ignore if at risk) */
+ if (!force_vacuum && (!av_enabled || !u_sess->attr.attr_storage.autovacuum_start_daemon)) {
+ userEnabled = false;
+ }
+
+ /*下面是根据死亡元组的数量来判断是否需要做vacuum,
+ 根据变化的元组的数量来决定是否需要做analyze
+ ,有数据库的四个参数在此段代码被使用 */
+ reltuples = classForm->reltuples;
+ vacthresh = (float4)vac_base_thresh + vac_scale_factor * reltuples;
+ anlthresh = (float4)anl_base_thresh + anl_scale_factor * reltuples;
+
+ if (tabentry && (tabentry->changes_since_analyze || tabentry->n_dead_tuples)) {
+ anltuples = tabentry->changes_since_analyze;
+ vactuples = tabentry->n_dead_tuples;
+ AUTOVAC_LOG(DEBUG2, "fetch local stat info: vac \"%s\" changes_since_analyze = %ld n_dead_tuples = %ld ",
+ NameStr(classForm->relname), tabentry->changes_since_analyze, tabentry->n_dead_tuples);
+ }
+
+ if (avwentry && (avwentry->changes_since_analyze || avwentry->n_dead_tuples)) {
+ anltuples = avwentry->changes_since_analyze;
+ vactuples = avwentry->n_dead_tuples;
+ AUTOVAC_LOG(DEBUG2, "fetch global stat info: vac \"%s\" changes_since_analyze = %ld n_dead_tuples = %ld ",
+ NameStr(classForm->relname), avwentry->changes_since_analyze, avwentry->n_dead_tuples);
+ }
+
+ /* Determine if this table needs vacuum. */
+ *dovacuum = force_vacuum || delta_vacuum;
+ *doanalyze = false;
+
+ if (false == *dovacuum && allowVacuum)
+ *dovacuum = ((float4)vactuples > vacthresh);
+ /*上面决定是否因为死亡元组数量达到数据库参数设置的阈值
+ 而需要做vacuum*/
+
+ /* Determine if this table needs analyze. */
+ if (allowAnalyze)
+ *doanalyze = ((float4)anltuples > anlthresh);
+ /*上面决定是否因为变化的元组的数量达到阈值而进行analyze*/
+```
+
+ 以上逻辑决定了表是否需要做vacuum或者analyze,最后,将需要vacuum的表,放入列表table_oids.然后逐个进行vacuum. 在解析过程中,看到代码中涉及到了相关数据库参数。
+
+ 通过上面的解析,我们了解到, auto vacuum 是按照库为单位进行调度的,函数do_autovacuum 是对一个库下面的表逐个进行处理,如果该表不需要vacuum 或者analyze的,则跳过。对表进行vacuum 的原因有两种:1.为了避免事务回卷,而进行force_vacuum ,这个触发条件跟参数autovacuum_freeze_max_age 有关系,具体条件见上面的解析。2.另外,就是表的死亡元组达到一定数量,具体的触发条件跟参数autovacuum_vacuum_scale_factor与autovacuum_vacuum_threshold 参数有关系。 同时,需要提一下,就是参数vacuum_freeze_min_age,vacuum_freeze_table_age虽然在do_autovacuum函数中有利用,但上面解析到的代码中,目前还没发现该参数的作用,这应该跟freeze 有关系,目前还没有解析到该步骤。
+
+ 接着往下看,当对表执行vacuum的时候,当表太大或者需要删除的死元组比较多的时候,可能会消耗的大量数据库资源,为了不影响数据库的稳定性,数据库做了对vacuum 线程消耗资源限制的功能,当vacuum线程消耗资源到一定程度的时候,会进行sleep 操作,sleep的时间由参数autovacuum_vacuum_cost_delay决定,单位是ms. 具体实现资源限制的代码如下:
+
+```
+/*
+ * vacuum_delay_point --- check for interrupts and cost-based delay.
+ *
+ * This should be called in each major loop of VACUUM processing,
+ * typically once per page processed.
+ */
+void vacuum_delay_point(void)
+{
+ /* Always check for interrupts */
+ CHECK_FOR_INTERRUPTS();
+
+ /* Nap if appropriate */
+ if (t_thrd.vacuum_cxt.VacuumCostActive && !InterruptPending &&
+ t_thrd.vacuum_cxt.VacuumCostBalance >= u_sess->attr.attr_storage.VacuumCostLimit) {
+ int msec;
+
+ msec = u_sess->attr.attr_storage.VacuumCostDelay * t_thrd.vacuum_cxt.VacuumCostBalance /
+ u_sess->attr.attr_storage.VacuumCostLimit;
+ if (msec > u_sess->attr.attr_storage.VacuumCostDelay * 4)
+ msec = u_sess->attr.attr_storage.VacuumCostDelay * 4;
+
+ pg_usleep(msec * 1000L);
+
+ t_thrd.vacuum_cxt.VacuumCostBalance = 0;
+
+ /* update balance values for workers */
+ AutoVacuumUpdateDelay();
+
+ /* Might have gotten an interrupt while sleeping */
+ CHECK_FOR_INTERRUPTS();
+ }
+```
+
+当t_thrd.vacuum_cxt.VacuumCostBalance 大于u_sess->attr.attr_storage.VacuumCostLimit时,会进行sleep 操作。VacuumCostLimit 计算方式跟参数autovacuum_vacuum_cost_limit 有关系,但好像不完全等同于该值,大概是因为可以同时运行多个autovacuum 线程的原因,但参数autovacuum_vacuum_cost_limit 应该是限制了总的消耗量,具体代码尚未解析,后面有时间再分享。
\ No newline at end of file
diff --git "a/app/zh/blogs/chunyangxu/2024-05-20-\344\273\216\350\277\220\347\273\264\350\247\206\350\247\222\346\235\245\350\247\243\346\236\220vacuum\346\234\272\345\210\266\350\267\237\345\217\202\346\225\260-00.png" "b/app/zh/blogs/chunyangxu/2024-05-20-\344\273\216\350\277\220\347\273\264\350\247\206\350\247\222\346\235\245\350\247\243\346\236\220vacuum\346\234\272\345\210\266\350\267\237\345\217\202\346\225\260-00.png"
new file mode 100644
index 0000000000000000000000000000000000000000..6fac0d84ad0c2c73f5d575903c4467b2bdfbf376
Binary files /dev/null and "b/app/zh/blogs/chunyangxu/2024-05-20-\344\273\216\350\277\220\347\273\264\350\247\206\350\247\222\346\235\245\350\247\243\346\236\220vacuum\346\234\272\345\210\266\350\267\237\345\217\202\346\225\260-00.png" differ
diff --git "a/app/zh/blogs/chunyangxu/2024-05-23-\344\273\216\350\277\220\347\273\264\350\247\206\350\247\222\346\235\245\350\247\243\346\236\220vacuum\346\234\272\345\210\266\350\267\237\345\217\202\346\225\260(2)-00.png" "b/app/zh/blogs/chunyangxu/2024-05-23-\344\273\216\350\277\220\347\273\264\350\247\206\350\247\222\346\235\245\350\247\243\346\236\220vacuum\346\234\272\345\210\266\350\267\237\345\217\202\346\225\260(2)-00.png"
new file mode 100644
index 0000000000000000000000000000000000000000..b34d37e746d5130dcf31217ff80a5d37fd0e5c9a
Binary files /dev/null and "b/app/zh/blogs/chunyangxu/2024-05-23-\344\273\216\350\277\220\347\273\264\350\247\206\350\247\222\346\235\245\350\247\243\346\236\220vacuum\346\234\272\345\210\266\350\267\237\345\217\202\346\225\260(2)-00.png" differ
diff --git "a/app/zh/blogs/chunyangxu/2024-05-23-\344\273\216\350\277\220\347\273\264\350\247\206\350\247\222\346\235\245\350\247\243\346\236\220vacuum\346\234\272\345\210\266\350\267\237\345\217\202\346\225\260(2)-01.png" "b/app/zh/blogs/chunyangxu/2024-05-23-\344\273\216\350\277\220\347\273\264\350\247\206\350\247\222\346\235\245\350\247\243\346\236\220vacuum\346\234\272\345\210\266\350\267\237\345\217\202\346\225\260(2)-01.png"
new file mode 100644
index 0000000000000000000000000000000000000000..d0daf826a4fd7c383713cddfdb61cb555679e28b
Binary files /dev/null and "b/app/zh/blogs/chunyangxu/2024-05-23-\344\273\216\350\277\220\347\273\264\350\247\206\350\247\222\346\235\245\350\247\243\346\236\220vacuum\346\234\272\345\210\266\350\267\237\345\217\202\346\225\260(2)-01.png" differ
diff --git "a/app/zh/blogs/chunyangxu/2024-05-23-\344\273\216\350\277\220\347\273\264\350\247\206\350\247\222\346\235\245\350\247\243\346\236\220vacuum\346\234\272\345\210\266\350\267\237\347\233\270\345\205\263\345\217\202\346\225\260(2).md" "b/app/zh/blogs/chunyangxu/2024-05-23-\344\273\216\350\277\220\347\273\264\350\247\206\350\247\222\346\235\245\350\247\243\346\236\220vacuum\346\234\272\345\210\266\350\267\237\347\233\270\345\205\263\345\217\202\346\225\260(2).md"
new file mode 100644
index 0000000000000000000000000000000000000000..5241e28cf8720a0cc3e4e30846a9590063f09a1b
--- /dev/null
+++ "b/app/zh/blogs/chunyangxu/2024-05-23-\344\273\216\350\277\220\347\273\264\350\247\206\350\247\222\346\235\245\350\247\243\346\236\220vacuum\346\234\272\345\210\266\350\267\237\347\233\270\345\205\263\345\217\202\346\225\260(2).md"
@@ -0,0 +1,202 @@
+---
+title: "从运维视角来解析vacuum机制跟相关参数(2)"
+date: '2024-05-21'
+category: 'blog'
+tags: ['openGauss']
+archives: '2024-05'
+author:'xuchunyang'
+summary: "从运维视角来解析vacuum机制跟相关参数"
+---
+
+ 上一篇从运维视角解析了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的时候被使用到。
+
+
+
+ 我们来看看这个heap_tuple_needs_freeze函数,函数非常简单,如下,最简单的场景,就是xmin小于u_sess->cmd_cxt.FreezeLimit时,该行就会被freeze.
+
+
+
+ 到这里,我们大概知道了vacuum_freeze_min_age参数的意义,该参数决定了表里面的行在什么时候做freeze。如果对行过早做freeze,freeze之后,该行又被更新了,之前的freeze就白做了。另外,freeze 会修改行的infomask ,自然会产生脏页,如果一个页面中不同行,在不同的批次做freeze,自然带来额外的刷脏负担。
+所以,设置非常合理的vacuum相关的参数,对数据库的性能的稳定性非常重要,有时候,统一的数据库参数可能无法适用所有的表的各自使用场景,为个别表单独设置特定的参数,我想是非常有必要的。
\ No newline at end of file
diff --git "a/app/zh/blogs/chunyangxu/2024-06-19-opengauss\345\206\205\345\255\230\345\210\206\351\205\215\350\267\237\350\270\252-01.png" "b/app/zh/blogs/chunyangxu/2024-06-19-opengauss\345\206\205\345\255\230\345\210\206\351\205\215\350\267\237\350\270\252-01.png"
new file mode 100644
index 0000000000000000000000000000000000000000..17b6aed518a4960926b32b5c058106aa0c8615de
Binary files /dev/null and "b/app/zh/blogs/chunyangxu/2024-06-19-opengauss\345\206\205\345\255\230\345\210\206\351\205\215\350\267\237\350\270\252-01.png" differ
diff --git "a/app/zh/blogs/chunyangxu/2024-06-19-opengauss\345\206\205\345\255\230\345\210\206\351\205\215\350\267\237\350\270\252-02.png" "b/app/zh/blogs/chunyangxu/2024-06-19-opengauss\345\206\205\345\255\230\345\210\206\351\205\215\350\267\237\350\270\252-02.png"
new file mode 100644
index 0000000000000000000000000000000000000000..e0cab3e8c2ace2d431aa0e6a2c5af08e6bb9b11a
Binary files /dev/null and "b/app/zh/blogs/chunyangxu/2024-06-19-opengauss\345\206\205\345\255\230\345\210\206\351\205\215\350\267\237\350\270\252-02.png" differ
diff --git "a/app/zh/blogs/chunyangxu/2024-06-19-opengauss\345\206\205\345\255\230\345\210\206\351\205\215\350\267\237\350\270\252.md" "b/app/zh/blogs/chunyangxu/2024-06-19-opengauss\345\206\205\345\255\230\345\210\206\351\205\215\350\267\237\350\270\252.md"
new file mode 100644
index 0000000000000000000000000000000000000000..3101b0d7b5fa23f326e1262f13011bd749fe300a
--- /dev/null
+++ "b/app/zh/blogs/chunyangxu/2024-06-19-opengauss\345\206\205\345\255\230\345\210\206\351\205\215\350\267\237\350\270\252.md"
@@ -0,0 +1,81 @@
+---
+title: "opengauss内存分配跟踪"
+date: '2024-06-19'
+category: 'blog'
+tags: ['openGauss']
+archives: '2024-06'
+author:'xuchunyang'
+summary: "如何使用dbe_perf.track_memory_context以及pv_session_memctx_detail追踪内存分配"
+---
+
+ 近日,我们线上系统遇到动态内存高的报警(通过查询视图gs_total_memory_detail 获取的监控数值),经过定位,发现是绑定变量在不应该使用的场景使用了,导致会话线程在缓存执行计划上消耗了大量的内存,也就是CachedPlan 内存上下文占用内存多(通过查询gs_session_memory_detail可以获得某个会话线程各个上下文占用的内存)。虽然该问题已经定位,但还是想对opengauss的内存知识以及问题定位有更多的了解,然后查找一些资料以及学习了一小段代码,在这里做一下笔记。
+
+ 通过查询下面的视图,获取一个会话的内存消耗情况
+```
+xcytest=# select * from gs_session_memory_detail where sessid like '%140445449905920%' and contextname like 'CachedPlan';
+ sessid | sesstype | contextname | level | parent | totalsize | freesize | usedsize
+----------------------------+----------+-------------+-------+---------------------------+-----------+----------+----------
+ 1716794872.140445449905920 | postgres | CachedPlan | 2 | SessionCacheMemoryContext | 15360 | 5760 | 9600
+ 1716794872.140445449905920 | postgres | CachedPlan | 2 | SessionCacheMemoryContext | 15360 | 5760 | 9600
+ 1716794872.140445449905920 | postgres | CachedPlan | 2 | SessionCacheMemoryContext | 7168 | 888 | 6280
+```
+ 除上述说到的两个视图外,还可以查询到更详细的内存分配信息,就是利用opengauss提供的context track 函数。 例如,想知道CachedPlan内存上下文在哪里被消耗的,可以使用select * from dbe_perf.track_memory_context('CachedPlan'); 命令进行查看, 执行完命令后,再查询dbe_perf.track_memory_context_detail 视图,就能看到详细信息,样式如下:
+```
+xcytest=# select * from dbe_perf.track_memory_context_detail();
+ context_name | file | line | size
+--------------+---------------+------+------
+ CachedPlan | copyfuncs.cpp | 2351 | 168
+ CachedPlan | copyfuncs.cpp | 2262 | 6
+ CachedPlan | copyfuncs.cpp | 6382 | 8
+ CachedPlan | list.cpp | 104 | 16
+ CachedPlan | plancache.cpp | 1301 | 88
+ CachedPlan | bitmapset.cpp | 94 | 8
+ CachedPlan | copyfuncs.cpp | 6371 | 32
+```
+当不需要track详细信息时,可以使用下面的语句进行关闭,关闭后再查询上述视图,视图将没有数据。
+```
+select * from dbe_perf.track_memory_context('');
+```
+除上述方法可以track内存上下文外,还有pv_session_memctx_detail 函数,它的用法如下:
+
+打印一个会话的内存使用情况
+```
+xcytest=# select * from pv_session_memctx_detail(140445646583552,'');
+-[ RECORD 1 ]------------+--
+pv_session_memctx_detail | t
+```
+打印某个会话的某个内存上下文的使用情况
+select pv_session_memctx_detail(140444974577408,'OptimizerTopMemoryContext');
+
+执行上述命令后,会在数据库的pg_log目录下生成一个memdump目录,生成如下文件
+```
+[omm@nd1 memdump]$ ls -lrt
+total 36
+-rw-------. 1 omm omm 6625 May 27 16:06 140445449905920_1716797214.log
+-rw-------. 1 omm omm 7254 May 27 16:07 140445159192320_1716797266.log
+-rw-------. 1 omm omm 6256 May 27 16:18 140445646583552_1716797900.log
+-rw-------. 1 omm omm 27 May 27 16:36 SessionSelfMemoryContext_140444974577408_1716798975.log
+-rw-------. 1 omm omm 225 May 27 16:37 OptimizerTopMemoryContext_140444974577408_1716799037.log
+```
+文件内容的样式如下:
+```
+[omm@nd1 memdump]$ cat OptimizerTopMemoryContext_140444974577408_1716799191.log
+variable.cpp:801, 32, 20
+variable.cpp:459, 32, 24
+variable.cpp:371, 32, 32
+variable.cpp:961, 32, 24
+variable.cpp:667, 32, 20
+variable.cpp:600, 32, 20
+variable.cpp:205, 32, 24
+variable.cpp:865, 32, 24
+variable.cpp:161, 64, 48
+```
+对文件里面的内容的含义非常模糊,没有找到对其解析的相关资料,于是自行解析。通过从函数pv_session_memctx_detail入手,找到函数DumpMemoryCtxOnBackend, 该函数会给master线程发送PROCSIG_MEMORYCONTEXT_DUMP 信号,master线程收到信号后,调用DumpMemoryContext函数,然后调用recursiveMemoryContextForDump 函数,最后调用dumpAllocBlock函数,该函数的代码如下:
+
+
+
+上述代码,就是通过函数dumpAllocChunk逐个处理该内存上下文的所有的block,如果这个block没有被使用,则跳过。通过上述代码,我们可以更直观地了解到内存上下文的基本结构:它里面可以包含很多block, block里面又包含chunk.我们来看看dumpAllocChunk这个函数。
+
+
+
+查看上面代码,可以知道memdump文件中的内容就是上面的代码输出的,可以知道文件中第一列表示chunk是由哪行代码申请的,第二列就是chunk的大小,第三列就是chunk的实际使用大小。通过阅读上述代码,可以更进一步直观地知道,一个block里面,可以包含很多chunk.
\ No newline at end of file