从Greenplum一个WARN的排查浅析PostgreSQL MemoryContext内存管理

Greenplum(GP)是一款开源的MPP数据库,兼容PostgreSQL生态。我们尝试基于开源GP支持多个副本,改造让集群从初始的最多只支持一个standby Master,到支持多个standby。

相关实现并不复杂,内核和工具中没有太多对于standby个数的限制。经过多次的修改后,遗留的问题只剩下了一个:由一行代码引起的、但是找到这行代码花费了很久的WARN。本文就从这个WARN排查的角度,浅度分析下PostgreSQL 基于MemoryContext的内存管理。


到处出现的WARN


我们拉起一个多副本的集群,通过gpinitstandby来向集群添加standby,当我们添加1个standby,集群工作的很正常。而当我们添加的standby个数超过1个之后,集群就会打出一条WARN:

WARNING: problem in alloc set cdb components Context: detected write past chunk end in block 0x2e23c30, chunk 0x2e23d00 (aset.c:2027)

当时看到这个WARN之后第一反应是这应该是一个并不棘手的问题,毕竟连产生WARN的代码文件和行数都给了,大不了直接捋调用链就好了,总能找到原因,而且就算找不到,仅仅是一条WARN而已。

但是随着使用发现,这个WARN在集群正常使用的过程中会不停的打印在日志中,打开pg_log,满屏的WARN;另外,在用户使用过程中,它会多次出现在psql的输出中,甚至会干扰正常命令输入。甚至我们在代码中埋点,打印出会print这条WARN的进程id之后发现,包括bgworker、fts、postgres、postmaster在内的多个进程都会不停打印这条WARN。这就成了一个必须解决的问题。

从Greenplum一个WARN的排查浅析PostgreSQL MemoryContext内存管理_第1张图片

怎么解决呢,首先的思路当然还是查看报错信息、整理调用堆栈,去代码里看看到底是哪段代码搞出了WARN,又或者是gpinitstandby的哪段逻辑调用产生WARN的代码。直接找到打印WARN的代码并不难,aset.c 2027行。

/*
* AllocSetCheck
* Walk through chunks and check consistency of memory.
*
* NOTE: report errors as WARNING, *not* ERROR or FATAL. Otherwise you'll
* find yourself in an infinite loop when trouble occurs, because this
* routine will be entered again when elog cleanup tries to release memory!
*/
static void
AllocSetCheck(MemoryContext context)
{
... many code before ...

if (dsize > 0 && dsize < chsize &&
!sentinel_ok(chunk, ALLOC_CHUNKHDRSZ + dsize))
elog(WARNING, "problem in alloc set %s: detected write past chunk end in block %p, chunk %p (%s:%d)",
name, block, chunk, CDB_MCXT_WHERE(&set->header));

... many code after ...
}

原来,这是一个为MemoryContext做memory check的函数,出WARN那段代码的逻辑就是检查传入的MemoryContext,当如果发现了某段不应该被访问到的内存被修改了之后,就会打印出这条WARN,这是一条内存访问越界的WARN。

Ok,问题清晰了一点,增加副本超过1个之后,导致某段内存的访问超过初始所申请的内存段。所以现在核心的问题变成,定位导致WARN的内存访问越界发生的位置,以及思考两个问题:1)这个WARN为啥在不同的时间、不同的进程内到处出现;2)以及既然发生了内存访问越界,为啥没core。

访问基于MemoryContext的内存越界与普通的内存越界不太相同,它并不会在发生内存越界的位置直接停止、报错;在PostgreSQL中内存的申请、使用时没有输出ÿ

你可能感兴趣的:(分布式,数据库,后端,postgresql,数据库,内存管理)