概述

本文对openGauss数据库内存管理模型和内存问题处理思路进行整理,对于从事openGauss数据库的DBA和使用者提供参考。

1、内存管理模型

openGauss数据库内存管理采用动态内存与静态内存相结合的管理方式,由参数 max_process_memory控制数据库可用的最大内存,数据库使用的内存不会超过这个值,合理设置该值可以防止数据库占用内存过大导致操作系统OOM,通常情况下建议设置服务器最大内存的75%。右边为数据库的共享缓冲区,主要有数据库的buffer pool用于缓存数据页面,用于提升行存表的查询和写入性能。wal buffer为数据库wal日志缓存,clog buffer为clog日志缓存,以及其它一些缓存,比如cstore buffer因为列存表属于非商用特性,在这里暂未列出。左边为动态内存区域,由数据库根据需要进行动态分配,主要包括元数据的缓存、执行计划的缓存、用户的会话以及内部线程的内存消耗等。如下图所示:
image.png

GLC:用于缓存元数据,数据库的元数据会在全局和会话中各自缓存一份,用于提升数据库的性能。
enable_global_plancache参数设置为on,打开全局数据库全局缓存功能,会对SQL的执行计划进行全局缓存,开启该功能可以节省高并发下数据库节点的内存使用。如果没有打开全局缓存,SQL的执行计划会在各自的会话中进行缓存。

相关参数

1、enable_memory_limit:启用逻辑内存管理模块,如果改参数设置为off,数据库不会对内存使用进行管理,可能导致操作系统oom。
2、max_process_memory:设置一个数据库节点可用的最大物理内存,建议可以设置为服务器物理内存的75%。
3、shared_buffers:设置数据库共享缓冲区的大小。
4、temp_buffers:设置每个数据库会话使用的LOCAL临时缓冲区的大小。在每个会话的第一次使用临时表之前可以改变temp_buffers的值,之后的设置将是无效的。如果你的工作负载中频繁使用临时表或涉及复杂排序、分组等操作(可能隐式使用临时表),可以适当增加 temp_buffers 的值来提升性能。
5、bulk_write_ring_size:大批量数据写入触发时(例如copy动作),该操作使用的环形缓冲区大小。建议导入压力大的场景中增加数据库节点中此参数配置。
6、work_mem:设置内部排序操作和Hash表在开始写入临时磁盘文件之前使用的内存大小。ORDER BY、DISTINCT和merge joins都要用到排序操作。Hash表在散列连接、散列为基础的聚集、散列为基础的IN子查询处理中都要用到。

对于复杂的查询,可能会同时并发运行好几个排序或者散列操作,每个都可以使用此参数所声明的内存量,不足时会使用临时文件。同样,好几个正在运行的会话可能会同时进行排序操作。因此使用的总内存可能是work_mem的好几倍。
7、maintenance_work_mem:设置在维护性操作(比如VACUUM、CREATE INDEX、ALTER TABLE ADD FOREIGN KEY等)中可使用的最大的内存。该参数的设置会影响VACUUM、VACUUM FULL、CLUSTER、CREATE INDEX的执行效率。
8、cstore_buffers:设置列存所使用的共享缓冲区的大小,应用系统未使用列存表,建议设置为最小值(16MB)。
9、bulk_read_ring_size:大批量数据查询时(例如大表扫描),该操作使用的环形缓冲区大小。
10、local_syscache_threshold:系统表cache在单个session缓存的大小,根据当前系统表的数据量合理进行设置,如果表的数量非常多,可以考虑增加该参数。
11、global_syscache_threshold:全局系统缓存内存最大占用大小。如果设置的值过小,会导致内存频繁淘汰,内存存在大量碎片无法回收,导致内存控制失效。需要打开enable_global_syscache参数,该参数才生效。
推荐计算公式:热点DB个数和线程个数的最小值乘以每个DB分配的内存大小,即global_syscache_threshold = min(count(hot dbs),count(threads)) * memofdb。
hot dbs:热点DB数,即访问较为频繁的数据库。
threads:线程数,在线程池模式下取线程池线程个数和后台线程个数之和,非线程池模式不需要计算这个值,直接使用热点DB数。
memofdb:平均每个db应该分配的内存,每个DB的底噪内存是2M,平均每增加一个表或者索引,增加11k内存。
12、resilience_memory_reject_percent:openGauss内核开发的内存防过载功能,避免内存过载导致系统无法正常工作,在内存使用率超过一定阈值后,数据库会阻止新的连接接入,并kill堆积的会话。
比如:resilience_memory_reject_percent = ‘70,90’,上述设置表示内存使用超过最大内存上限的90%后禁止新连接接入并kill堆积的会话,kill会话过程中内存恢复到最大内存的70%以下时停止kill会话并允许新连接接入。该功能默认关闭,可以根据系统需要进行设置。

常见内存问题处理

数据库内存问题通常表现为内存不足导致的性能下降,以及动态内存不足导致应用执行操作报错。

1、缓存命中率低

数据库的共享缓冲区不足导致数据库的buffer命中率低,会影响系统的整体性能。通常情况下,TP数据库的buffer命中率一般都在97%以上。如果数据库的buffer命中率较低,考虑适当调大share_bufer参数配置。可以借助如下方式查看数据库的buffer命中率。
1)可以借助数据库的WDR报告,如下图所示,数据库整体的buffer命中率为76.7%,处于较低的水平。
dc3af43ee6285a261a503a06a4cc744f.png

2)想要查看单个SQL语句的buffer命中率,可以通过如下语句:

select unique_sql_id,substr(query,1,50) as query,n_calls,n_blocks_hit/n_blocks_fetched as buff_rate from dbe_perf.statement where query like '%hdg_table_rule_task%' and n_blocks_fetched>0;

查询结果如下图所示:
image.png

2、work_mem不足导致排序落盘

当业务SQL需要对较大的结果集进行排序或者hashjoin等操作,一旦work_mem限定的物理内存不够,算子运算数据将写入临时表空间,带来5-10倍的性能下降,查询响应时间从秒级下降到分钟级。如下图所示,执行计划中的sort算子有Disk关键子,说明算子存在落盘情况,考虑适当增加work_mem参数值提升查询性能。
image.png

3、动态内存不足

数据库可用动态内存不足,表现为业务执行会报错:memory is temporarily unavailable。如果出现集群内存不足或者长时间内存占用处于较高状态的情况,一般是由以下几种原因造成的。
1.内存堆积
当SQL语句执行过程中临时内存未及时释放时,会导致内存堆积。
2.并发过高导致内存占用过高
客户端向server端建立的连接数过多,导致server端创建了大量的session,占用大量内存,这种场景下会出现内存出现占用较高的情况。
3.单条SQL语句内存占用较高
对于部分SQL语句,执行过程中会使用大量的内存,导致内存出现短暂性上涨,如复杂的存储过程语句,执行时内存占用可能达到几十GB大小。
4.内存缓存过多
GaussDB引入了jemalloc开源库来管理内存,但是jemalloc在面对大量的内存碎片时会存在内存持续缓存不释放的问题,导致数据库进程使用的内存远远超过了实际使用的内存,具体表现为使用内存视图gs_total_memory_detail查询时,other_used_memory占用过大。如果存在该问题,可以考虑通过主备切换或者重启数据库进程解决。
通过如下方式查看数据库内存使用情况。

select * from gs_total_memory_detail;

image.png

1、数据库可用的最大动态内存可以粗略进行计算,=max_process_memory-shared_buffers-cstore_buffers,首先排查相关内存参数设置是否合理。
2、结合上面节点内存使用的查询结果,如果dynamic_used_memory较大,dynamic_used_shrctx使用较小,则可以确认是线程和session上内存占用较多,按如下思路排查:
1)查看当前数据库的会话情况,活跃会话数,空闲会话数等信息。

select 
sum(case when state='active' then  1 else 0 end) as active,
sum(case when state='idle' then  1 else 0 end) as idle,
sum(case when state='idle in transaction' then  1 else 0 end) as idle_in_transaction 
from  pg_stat_activity;

image.png

如果idle或者idle in transaction的会话很多,需要跟应用沟通是合理,通常情况下数据库的空闲会话也会占用数十MB的内存,包括连接自身占用的内存,会话中缓存的执行计划,元数据等等。如果数据库存在非常多的空闲会话,会占用比较多的动态内存。这在数据库的内存比较吃紧的情况下尤其需要关注,如果存在该问题,可以先kill掉空闲会话,并和应用沟通保持合理的长连接数。
2)通过如下方式查看session内存堆积的上下文,排查内存堆积的根因。

select sesstype,contextname,pg_size_pretty(sum(totalsize)) as totalsize,pg_size_pretty(sum(usedsize)) as usedsize from gs_session_memory_detail group by sesstype,contextname order by totalsize desc limit 10;

image.png

开启内存追踪,根据contextname检查其内存分配,和调用的cpp文件

select * from dbe_perf.track_memory_context('global_ckpt_status');

过几分钟查询:

select * from dbe_perf.track_memory_context_detail();

image.png

关闭内存追踪
select * from dbe_perf.track_memory_context(‘’);
在数据库线程池打开的情况下,可以通过如下方式获取context内存堆积的详细代码位置。

SELECT * FROM gs_get_session_memctx_detail('CachedPlanQuery');

3)通过如下方式查看数据库正在执行中的SQL语句的内存占用,排查是否存在异常占用内存的业务语句。

with t1 as (select sessid, sum(totalsize) totalsize, sum(usedsize) usedsize from gs_session_memory_detail group by 1) 
select a.sessionid, a.datname, a.usename, a.application_name, a.client_addr, 
pg_size_pretty(totalsize) total_size, pg_size_pretty(usedsize) used_size, a.query  from pg_stat_activity a, t1  
where a.sessionid = split_part(sessid,'.',2) order by usedsize desc; 

image.png

找出占用内存高的会话及对应的语句分析内存占用的原因,比如结果集大,SQL语句复杂,执行计划不优等导致的内存占用高,对业务语句进行分析优化。如果单个session内存占用正常,会话数据太高导致内存不足,跟应用沟通合理减少业务的并发数。

2、如果dynamic_used_shrctx占用大,超过40%,说明数据库的共享内存占用比较大,通过下面语句查看共享内存的context占用情况。

select contextname,pg_size_pretty(sum(totalsize)) as totalsize,pg_size_pretty(sum(usedsize)) as usedsize from gs_shared_memory_detail group by contextname order by totalsize desc limit 10;

image.png

通过如下方式,可以跟踪context具体在代码的哪个位置产生了内存堆积。

SELECT * FROM gs_get_shared_memctx_detail('cda_template_config');

image.png

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐