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-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