暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

PolarDB PostgreSQL版图解MemoryContext

PolarDB 2025-04-01
338

PolarDB PostgreSQL版图解MemoryContext

PostgreSQL(以下简称PG)遇到OOM,相信是大家非常头疼的问题。当PG发生OOM时,会造成:

  • 所有session processes被系统kill,引起应用连接不可用;

  • postmaster被被系统kill,数据库需要重启,引起数据库系统不可用。

这都涉及到了PG的内存管理系统。PG的内存管理分为共享内存管理以及本地内存管理,本文主要对本地内存管理进行介绍。

PostgreSQL的MemoryContext机制实现,本质上是对malloc,free,realloc的封装。但是glibc的malloc为了多线程架构的实现,加入了一些锁来保证并发的一致性,而PostgreSQL本身是多进程单线程的架构,所以完全使用glibc的内存接口,性能是有损耗的。

其次在PostgreSQL这样工程较为庞大,函数调用较为复杂的数据库管理系统内,对于内存的管理,如果使用malloc和free进行内存的申请和释放,是很难做到尽善尽美。在7.0之前的版本中,PG以指针传递来处理大量查询,并且在查询结束后回收内存,因此存在着内存泄漏的风险。尤其是处理TOAST数据时,需要使用大量的内存,使得这个问题尤其明显。

因此,在7.1版本之后,PG实现了新的内存管理机制--MemoryContext。整个MemoryContext是以树形结构来组织管理的,这能够很容易的对MemoryContext进行管理。

本文将会以下面几个部分进行阐述:

  • MemoryContext的基础结构

  • MemoryContext的操作函数

术语解释:

  • MemoryContext,PostgreSQL实现的内存上下文管理机制;

  • block,内存块

  • chunk,内存片

MemoryContext基础结构:MemoryContextData,AllocSetContextData,AllocBlockData,AllocChunkData,AllocSetFreeList

在搞清楚MemoryContext之前,我们首先要清晰地认识MemoryContextData,AllocSetContextData,AllocBlockData,AllocChunkData。

MemoryContext(这里指的是指针名称),AllocSetContext,AllocBlock,AllocChunk是上面四者的指针类型,下文阐述时会以带有Data的名称表示这四种结构。

首先看下这四者的整体关系结构图:

image.png

AllocSetContext is our standard implementation of MemoryContext.^[1]^

AllocSetContextData是MemoryContext的标准实现。类似于简单的操作系统内存管理,要组织已使用内存和已空闲内存的管理。在进程中对MemoryContext的操作实际是对AllocSetContextData的操作。

MemoryContextData只是用来表示进程操作期间的不同MemoryContext之间的关系(父子,前后),不关心内存本身的分配和释放,是一个抽象类型,可以有多种实现。同时MemoryContextData的methods定义了一种特定实现方式。而作为MemoryContext的实际实现,每个AllocSetContextData的起始位置必须是MemoryContextData,这类似于面向对象中的继承概念。MemoryContextData是父类,AllocSetContextData是子类,继承了父类的变量,并增加了一些成员变量以及操作接口。

我们可以使用MemoryContextData将整个MemoryContext抽象表示一个树形结构:

image.png

AllocBlockData是进程用来分配、释放的内存单元,通过malloc申请。AllocChunkData是用户存放数据的内存单元,通过palloc申请,其中已经使用的AllocChunkData的aset将会指向所属的AllocSetContextData;如果是空闲未使用的将会指向freelist中的下一个成员变量(没有则为空)。一个AllocBlockData具有一个或多个AllocChunkData。^[2]^

四者的数据结构

MemoryContext的数据结构为:

typedef struct MemoryContextData
{

 NodeTag  type;   /* identifies exact kind of context */
/* these two fields are placed here to minimize alignment wastage: */
bool  isReset;  /* T = no space alloced since last reset */
bool  allowInCritSection; /* allow palloc in critical section */
const MemoryContextMethods *methods; /* virtual function table */
 MemoryContext parent;  /* NULL if no parent (toplevel context) */
 MemoryContext firstchild; /* head of linked list of children */
 MemoryContext prevchild; /* previous child of same parent */
 MemoryContext nextchild; /* next child of same parent */
constchar *name;   /* context name (just for debugging) */
constchar *ident;   /* context ID if any (just for debugging) */
 MemoryContextCallback *reset_cbs; /* list of reset/delete callbacks */
} MemoryContextData;

  • type 标示内存上下文类型,默认只有一种类型,T_AllocSetContext

  • isReset:True表示上次重置后没有内存申请动作发生,False表示已经发生内存申请

  • allowInCritSection: 是否允许在临界区中分配内存。通常来说是不允许进行这样分配的,分配失败会导致PANIC,但这可以用于调试代码,方便开发调试,这些代码不建议进入实际生成环境中

  • parent,prevchild,nextchild组成内存上下文的树形结构,方便在重置或删除内存上下文时查找子节点,分别表示父节点,同一个父节点下的prev节点,同一个父节点下的next节点

  • name: MemoryContext的名称,不同的MemoryContext具有不同的MemoryContext。方便调试

  • ident: MemoryContext的ID,方便调试

  • reset_cbs: Postgres 9.5中引入的一项功能允许将内存上下文用于管理更多资源,而不仅仅是普通的palloc分配的内存。 这是通过为内存上下文注册“重置回调函数”来完成的。 在下一次重置或删除上下文之前,将调用一次此类函数。 它可以用来放弃在某种意义上与上下文中分配的对象相关联的资源。

  • methods: 记录了内存上下文使用的函数指针,MemoryContextMethods的数据结构:

typedef struct MemoryContextMethods
{
 void   *(*alloc) (MemoryContext context, Size size);
 /* call this free_p in case someone #define's free() */
 void (*free_p) (MemoryContext context, void *pointer);
 void   *(*realloc) (MemoryContext context, void *pointer, Size size);
 void (*reset) (MemoryContext context);
 void (*delete_context) (MemoryContext context);
 Size (*get_chunk_space) (MemoryContext context, void *pointer);
 bool (*is_empty) (MemoryContext context);
 void (*stats) (MemoryContext context,
  MemoryStatsPrintFunc printfunc, void *passthru,
  MemoryContextCounters *totals);
#ifdef MEMORY_CONTEXT_CHECKING
 void (*check) (MemoryContext context);
#endif
} MemoryContextMethods;

在本文第二章基本操作会详细介绍各个函数操作。

AllocSetContextData的数据结构:

typedef struct AllocSetContext
{

 MemoryContextData header; /* Standard memory-context fields */
/* Info about storage allocated in this context: */
 AllocBlock blocks;   /* head of list of blocks in this set */
 AllocChunk freelist[ALLOCSET_NUM_FREELISTS]; /* free chunk lists */
/* Allocation parameters for this context: */
 Size  initBlockSize; /* initial block size */
 Size  maxBlockSize; /* maximum block size */
 Size  nextBlockSize; /* next block size to allocate */
 Size  allocChunkLimit; /* effective chunk size limit */
 AllocBlock keeper;   /* keep this block over resets */
/* freelist this context could be put in, or -1 if not a candidate: */
int   freeListIndex; /* index in context_freelists[], or -1 */
} AllocSetContext;

  • header: 标准的内存上下文区域,类型为MemoryContextData

  • blocks: 内存块链表,记录内存上下文向操作系统申请的连续大块内存

  • initBlockSize,maxBlockSize,nextBlockSize: 内存上下文创建时指定的初始block大小,最大block大小和下一次分配的block大小。一般来说,下一次申请的block会是上一次的2被,但不会超过maxBlockSize

  • allocChunkLimit: 内存片的阈值,申请的内存超过此阀值直接分配新的block

  • keeper: 为防止一些需要频繁重置的小的内存上下文重复的进行malloc,重置时保留第一次申请的内存块

  • freeListIndex,在context_freelists^[3]^顺序,0表示默认freeList,1表示小内存freeList,-1表示不需要进入freeList(例如超过allocChunkLimit的block)

  • freelist: 组织该内存上下文里所有内存块中已释放的内存片的链表结构,这是一个具有ALLOCSET_NUM_FREELISTS成员的AllocChunkData数组,成员分别表示不同大小的AllocChunkData,每个相同大小的AllocChunkData由aset指针进行链接。每个一个成员是上一个的2倍,因此当前freelist可以存储的AllocChunkData大小有8bytes,16bytes,32bytes,64bytes,128bytes,256bytes,512bytes,1024bytes,2048bytes,4096bytes,8192bytes,freelist可以表示为:

image.png

AllocBlockData的数据结构:

typedef struct AllocBlockData
{
 AllocSet aset; * aset that owns this block */
 AllocBlock prev; * prev block in aset's blocks list, if any */
 AllocBlock next; * next block in aset's blocks list, if any */
 char   *freeptr; * start of free space in this block */
 char   *endptr; * end of space in this block */
} AllocBlockData;

  • aset: 指向AllocBlockData所属的AllocSetContext

  • prev: 在AllocSetContext中的blocks prev block

  • next: 在AllocSetContext中的blocks next block

  • freeptr: 可用空闲区域的起始地址

  • endptr: 可用空闲区域的结束地址

AllocChunkData的数据结构:

typedef struct AllocChunkData
{
 /* size is always the size of the usable space in the chunk */
 Size size;
#ifdef MEMORY_CONTEXT_CHECKING
 /* when debugging memory usage, also store actual requested size */
 /* this is zero in a free chunk */
 Size requested_size;

#define ALLOCCHUNK_RAWSIZE  (SIZEOF_SIZE_T * 2 + SIZEOF_VOID_P)
#else
#define ALLOCCHUNK_RAWSIZE  (SIZEOF_SIZE_T + SIZEOF_VOID_P)
#endif * MEMORY_CONTEXT_CHECKING */

 /* ensure proper alignment by adding padding if needed */
#if (ALLOCCHUNK_RAWSIZE % MAXIMUM_ALIGNOF) != 0
 char padding[MAXIMUM_ALIGNOF - ALLOCCHUNK_RAWSIZE % MAXIMUM_ALIGNOF];
#endif

 /* aset is the owning aset if allocated, or the freelist link if free */
 void   *aset;
 /* there must not be any padding to reach a MAXALIGN boundary here! */
} AllocChunkData;

  • size: 当前AllocChunkData的长度

  • requested_size: 在debugging模式下会记录实际请求的长度

  • padding:对齐时使用

  • aset:当此AllocChunkData正在使用时,指向所属的AllocSetContext;未使用则指向下一个相同大小的空闲AllocChunkData(没有则为空)

还有一个比较重要的数据结构是AllocSetFreeList,和上面AllocSetContextData的freelist不同,这里指的是空闲的AllocSetContextData,而非AllocChunkData。当发生Delete操作时,会将删除后的AllocSetContextData放入AllocSetFreeList。

AllocSetFreeList在每个进程使用了数组进行存储:

static AllocSetFreeList context_freelists[2] =
{
 {
 0, NULL
 },
 {
 0, NULL
 }
};

  • 第一个成员用来存放普通大小的AllocSetContextData

  • 第二个成员用来存放较小尺寸的AllocSetContextData

image.png

AllocSetFreeList的数据结构:

#define MAX_FREE_CONTEXTS 100 * arbitrary limit on freelist length */

typedef struct AllocSetFreeList
{
 int num_free; * current list length */
 AllocSetContext *first_free; * list header */
} AllocSetFreeList;

  • num_free,空闲AllocSetContextData的数量,超过MAX_FREE_CONTEXTS,即100后将会对此freelist进行清理,避免内存过于松散

  • first_free指向第一个AllocSetContextData,相同类别的AllocSetContextData使用nextchild进行指向

AllocSetContextData的函数接口

PostgreSQL的MemoryContext应该是借鉴了很多Linux对于内存的管理。同时提供了palloc,repalloc,pfree等函数调用接口。由于对PG对内存的管理其实是对AllocSetContextData的管理,并且由MemoryContextData的methods定义了一种特定实现方式。那么首先看下这些函数实现:

static const MemoryContextMethods AllocSetMethods = {
 AllocSetAlloc,
 AllocSetFree,
 AllocSetRealloc,
 AllocSetReset,
 AllocSetDelete,
 AllocSetGetChunkSpace,
 AllocSetIsEmpty,
 AllocSetStats
#ifdef MEMORY_CONTEXT_CHECKING
 ,AllocSetCheck
#endif
};

在阐述以上函数时我们首先单独介绍一个函数AllocSetContextCreateExtended

Create a new AllocSet context.

先看下一个AllocSetContextData的初始状态:

image.png

AllocSetContextCreateExtended

/* 查看是否有空闲的freelist */
if (freelist->first_free != NULL)
{
/* 有空闲的AllocSetContextData,则从freelist移除 */
set = freelist->first_free;
/* 将first_free指向原有的AllocSetContextData下一个节点 */
 freelist->first_free = (AllocSet) set->header.nextchild;
 freelist->num_free--; /* 空闲AllocSetContextData数量减一 */

/* 设置maxBlockSize,同一个freelist具有相同的initBlockSize */
set->maxBlockSize = maxBlockSize;

/* 使用MemoryContextCreate将此AllocSetContextData加入到MemoryContext中 */
 MemoryContextCreate((MemoryContext) set,
      T_AllocSetContext,
      &AllocSetMethods,
      parent,
      name);
return (MemoryContext) set;
}

/* 没有freelist,则新建AllocSetContextData */
/* 这里使用malloc来申请、分配内存 */
set = (AllocSet) malloc(firstBlockSize);
/* 找到block的位置 */
block = (AllocBlock) (((char *) set) + MAXALIGN(sizeof(AllocSetContext)));
/* 当前block指向所属的AllocSetContextData */
block->aset = set;
/* 初始化的AllocBlockData,不设置AllocChunkData,因此freeptr指向AllocBlockData之后的内存地址 */
block->freeptr = ((char *) block) + ALLOC_BLOCKHDRSZ;
/* endptr指向block最后地址 */
block->endptr = ((char *) set) + firstBlockSize;
block->prev = NULL;
block->next = NULL;
set->blocks = block;
set->keeper = block;
set->initBlockSize = initBlockSize;
set->maxBlockSize = maxBlockSize;
set->nextBlockSize = initBlockSize;
set->freeListIndex = freeListIndex;
/* 使用MemoryContextCreate将此AllocSetContextData加入到MemoryContext中 */
MemoryContextCreate((MemoryContext) set,
     T_AllocSetContext,
     &AllocSetMethods,
     parent,
     name);

return (MemoryContext) set;

AllocSetAlloc

Returns pointer to allocated memory of given size or NULL if request could not be completed; memory is added to the set.

实施内存分配的函数,内部调用了malloc函数。具体分配方式可以分为两种:

  • 申请的内存size大于allocChunkLimit

  • 申请的内存size小于等于allocChunkLimit

size > allocChunkLimit

image.png
/* 当申请的内存空间size大于内存片阀值,则直接申请新的AllocBlockData */
if (size > set->allocChunkLimit)
{
        /* 对齐字节 */
 chunk_size = MAXALIGN(size);
        /* 预留AllocBlockData,AllocChunkData空间 */
 blksize = chunk_size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ;
 block = (AllocBlock) malloc(blksize);
        /* malloc申请失败直接返回null */
if (block == NULL)
returnNULL;
/* 当前block指向所属的AllocSetContextData */
 block->aset = set;
        /* 由于是为内存申请单独申请的block,所以不需要有多个chunk,所以freeptr和endptr都指向最后的地址,表示没有空闲空间 */
 block->freeptr = block->endptr = ((char *) block) + blksize;

        /* 建立内存片chunk */
 chunk = (AllocChunk) (((char *) block) + ALLOC_BLOCKHDRSZ);
        /* 当前chunk指向所属的AllocSetContextData */
 chunk->aset = set;
 chunk->size = chunk_size;

/* 将当前block加入到blocks中 */
if (set->blocks != NULL)
 {
/* 如果存在blocks,则将此block加入到blocks的第二个位置 */
  block->prev = set->blocks;
  block->next = set->blocks->next;
if (block->next)
   block->next->prev = block;
set->blocks->next = block;
 }
else
 {
/* 如果不存在blocks,则将blocks直接指向此block */
  block->prev = NULL;
  block->next = NULL;
set->blocks = block;
 }

return AllocChunkGetPointer(chunk);
}

size <= allocChunkLimit

image.png
/* 根据size计算log2(size)+1 */
fidx = AllocSetFreeIndex(size);
/* 选取空闲的chunk */
chunk = set->freelist[fidx];
if (chunk != NULL)
{
    * 如果不为null,将此chunk从freelist中移除,并返回 */
 set->freelist[fidx] = (AllocChunk) chunk->aset;
 chunk->aset = (void *) set;

 return AllocChunkGetPointer(chunk);
}

/* 如果不存在空闲的chunk,就需要申请新的chunk,需要计算其size */
chunk_size = (1 << ALLOC_MINBITS) << fidx;

/* 查看是否有合适的block可以申请chunk */
if ((block = set->blocks) != NULL)
{
    * 计算是否有足够的空闲空间 */
 Size availspace = block->endptr - block->freeptr;

    * 
     * 如果空闲空间不足,则将已有的空闲空间划分为chunk。
     * 这么做的原因是blocks的第一个block作为活跃内存块,
     * 并且当前设计只对block做一次检查,避免不必要的消耗。
     */
 if (availspace < (chunk_size + ALLOC_CHUNKHDRSZ))
 {
        * 对空闲空间进行申请chunk,空闲chuck最小为(1 << ALLOC_MINBITS) + ALLOC_CHUNKHDRSZ */
    while (availspace >= ((1 << ALLOC_MINBITS) + ALLOC_CHUNKHDRSZ))
    {
     Size availchunk = availspace - ALLOC_CHUNKHDRSZ;
     int a_fidx = AllocSetFreeIndex(availchunk);

            * 确定合适的chunk size,因为block所有的内容都是经过对齐的,因此能够找到合适的大小 */
    if (availchunk != ((Size) 1 << (a_fidx + ALLOC_MINBITS)))
    {
 a_fidx--;
 Assert(a_fidx >= 0);
 availchunk = ((Size) 1 << (a_fidx + ALLOC_MINBITS));
 }

            * 找到后直接使用此chunk */
 chunk = (AllocChunk) (block->freeptr);

 block->freeptr += (availchunk + ALLOC_CHUNKHDRSZ);
 availspace -= (availchunk + ALLOC_CHUNKHDRSZ);

 chunk->size = availchunk;

            * 将chunk加入到freelist内 */
 chunk->aset = (void *) set->freelist[a_fidx];
 set->freelist[a_fidx] = chunk;
 }

        * 设置block为null,设置创建新block的标记 */
 block = NULL;
 }
}

/* 如果block是null,创建新的block */
if (block == NULL)
{
 Size required_size;

 blksize = set->nextBlockSize;
 required_size = chunk_size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ;
    * 如果下一次应申请的block的大小小于当前申请的内存大小,则左移一位,知道大于等于申请的大小 */
 while (blksize < required_size)
 blksize <<= 1;

 block = (AllocBlock) malloc(blksize);

    * 
     * 如果malloc失败,则继续申请,失败是因为可能blksize远远大于required_size,
     * 也超出当前剩余内存。当申请的内存小于1MB或blksize < required_size则放弃申请。
     */
 while (block == NULL && blksize > 1024 * 1024)
 {
 blksize >>= 1;
 if (blksize < required_size)
 break;
 block = (AllocBlock) malloc(blksize);
 }

 if (block == NULL)
 return NULL;

 block->aset = set;
 block->freeptr = ((char *) block) + ALLOC_BLOCKHDRSZ;
 block->endptr = ((char *) block) + blksize;

 block->prev = NULL;
 block->next = set->blocks;
 if (block->next)
    block->next->prev = block;
 set->blocks = block;
}

chunk = (AllocChunk) (block->freeptr);

block->freeptr += (chunk_size + ALLOC_CHUNKHDRSZ);
chunk->aset = (void *) set;
chunk->size = chunk_size;

return AllocChunkGetPointer(chunk);


AllocSetFree

image.png
static void
AllocSetFree(MemoryContext context, void *pointer)
{
 AllocSet set = (AllocSet) context;
 AllocChunk chunk = AllocPointerGetChunk(pointer);

if (chunk->size > set->allocChunkLimit)
 {
        /* chunk大小超过chunk阀值,则直接释放此chunk所属的block */
  AllocBlock block = (AllocBlock) (((char *) chunk) - ALLOC_BLOCKHDRSZ);

/* 从所属的blocks中移除 */
if (block->prev)
   block->prev->next = block->next;
else
   set->blocks = block->next;
if (block->next)
   block->next->prev = block->prev;

free(block);
 }
else
 {
/* 一般大小,则加入到freelist中,并 */
int   fidx = AllocSetFreeIndex(chunk->size);

        /* chunk的aset指向freelist */
  chunk->aset = (void *) set->freelist[fidx];
set->freelist[fidx] = chunk;
 }
}

AllocSetRealloc

static void *
AllocSetRealloc(MemoryContext context, void *pointer, Size size)
{
 AllocSet set = (AllocSet) context;
 AllocChunk chunk = AllocPointerGetChunk(pointer);
 Size  oldsize;

 oldsize = chunk->size;

    /* 如果此chunk的size大于内存片的阀值,则表示这是单独的block,直接对block进行处理 */
if (oldsize > set->allocChunkLimit)
 {

  AllocBlock block = (AllocBlock) (((char *) chunk) - ALLOC_BLOCKHDRSZ);
  Size  chksize;
  Size  blksize;

  chksize = Max(size, set->allocChunkLimit + 1);
  chksize = MAXALIGN(chksize);

/* 使用realloc对此block进行处理 */
  blksize = chksize + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ;
  block = (AllocBlock) realloc(block, blksize);
if (block == NULL)
  {
   returnNULL;
  }
  block->freeptr = block->endptr = ((char *) block) + blksize;

/* 在realloc后的block申请chunk */
  chunk = (AllocChunk) (((char *) block) + ALLOC_BLOCKHDRSZ);
  pointer = AllocChunkGetPointer(chunk);
if (block->prev)
   block->prev->next = block;
else
   set->blocks = block;
if (block->next)
   block->next->prev = block;
  chunk->size = chksize;

return pointer;
 }

elseif (oldsize >= size)
 {
        /* 如果realloc的大小,小于chunk大小,直接返回 */
return pointer;
 }
else
 {
/* 如果realloc的大小,大于chunk大小 */
  AllocPointer newPointer;

/* 申请新的chunk */
  newPointer = AllocSetAlloc((MemoryContext) set, size);

/* 将原有内容复制到新的chunk内 */
memcpy(newPointer, pointer, oldsize);

/* 释放原来的chunk */
  AllocSetFree((MemoryContext) set, pointer);

return newPointer;
 }
}

AllocSetReset

image.png
/* 设置blocks指向keeper,reset不对keeper进行释放,只进行重置 */
set->blocks = set->keeper;

while (block != NULL)
{
 AllocBlock next = block->next;

if (block == set->keeper)
 {
char    *datastart = ((char *) block) + ALLOC_BLOCKHDRSZ;
  block->freeptr = datastart;
  block->prev = NULL;
  block->next = NULL;
 }
else
 {
/* 非keeper,直接释放 */
free(block);
 }
 block = next;
}

AllocSetDelete

static void
AllocSetDelete(MemoryContext context)
{
 AllocSet set = (AllocSet) context;
 AllocBlock block = set->blocks;

/* 查看此AllocSetContextData是否是freelist的候选者 */
if (set->freeListIndex >= 0)
 {
  AllocSetFreeList *freelist = &context_freelists[set->freeListIndex];

/* 当前MemoryContext没有被重置,则重置 */
if (!context->isReset)
   MemoryContextResetOnly(context);

/* 如果freelist的空闲AllocSetContextData数量超过MAX_FREE_CONTEXTS后,删除所有的空闲AllocSetContextData */
if (freelist->num_free >= MAX_FREE_CONTEXTS)
  {
   while (freelist->first_free != NULL)
   {
    AllocSetContext *oldset = freelist->first_free;

    freelist->first_free = (AllocSetContext *) oldset->header.nextchild;
    freelist->num_free--;

    free(oldset);
   }
  }

/* 将此AllocSetContextData加入到freelist中 */
set->header.nextchild = (MemoryContext) freelist->first_free;
  freelist->first_free = set;
  freelist->num_free++;

return;
 }

/* 如果不是freelist的候选MemoryContext,则直接释放 */
while (block != NULL)
 {
  AllocBlock next = block->next;

if (block != set->keeper)
   free(block);

  block = next;
 }

free(set);
}

AllocSetGetChunkSpace

result = chunk->size + ALLOC_CHUNKHDRSZ;
return result;

AllocSetIsEmpty

返回此AllocSetContextData是否为空,这里的空指的是新的或者reset,delete(delete)^[4]^后的AllocSetContextData。

if (context->isReset)
 return true;
return false;

AllocSetStats

遍历当前AllocSetContextData的blocks,统计内存申请总量,block数,未使用的chunk数,以及使用的内存大小。

/* 遍历blocks,确定使用了多少block */
for (block = set->blocks; block != NULL; block = block->next)
{
 nblocks++;
 totalspace += block->endptr - ((char *) block);
 freespace += block->endptr - block->freeptr;
}
/* 查找空闲chunk链表,计算空闲chunk数量,以及空闲空间大小 */
for (fidx = 0; fidx < ALLOCSET_NUM_FREELISTS; fidx++)
{
 AllocChunk chunk;

for (chunk = set->freelist[fidx]; chunk != NULL;
   chunk = (AllocChunk) chunk->aset)
 {
  freechunks++;
  freespace += chunk->size + ALLOC_CHUNKHDRSZ;
 }
}

MemoryContext的操作函数

对于MemoryContextData的操作本质上就是在调用AllocSetMethods,但是比之要多了一些更高层次的修改。PostgreSQL进程实际调用的内存管理函数就是这里的函数操作接口。

MemoryContextCreate

image.png
if (parent)
{
    /* 
     * 如果父节点不为空,将当前MemoryContext的nextchild指向父节点的第一个子节点
     * 则将新的MemoryContext作为父节点的第一个子节点
     */

 node->nextchild = parent->firstchild;
if (parent->firstchild != NULL)
  parent->firstchild->prevchild = node;
 parent->firstchild = node;
/* inherit allowInCritSection flag from parent */
 node->allowInCritSection = parent->allowInCritSection;
}
else
{
    /* 没有父节点,则作为初始节点 */
 node->nextchild = NULL;
 node->allowInCritSection = false;
}

MemoryContextDelete

image.png
/* 如果存在子节点,则删除所有子节点 */
if (context->firstchild != NULL)
 MemoryContextDeleteChildren(context);
/* 回调函数 */
MemoryContextCallResetCallbacks(context);
/* 设置父节点为空 */
MemoryContextSetParent(context, NULL);
context->ident = NULL;
/* 删除此MemoryContext */
context->methods->delete_context(context);

MemoryContextDeleteChildren

image.png
/* 如果存在子节点,则删除所有子节点 */
while (context->firstchild != NULL)
 MemoryContextDelete(context->firstchild);

MemoryContextReset

image.png
/* 如果存在子节点,则直接删除所有子节点 */
if (context->firstchild != NULL)
 MemoryContextDeleteChildren(context);

/* 如果没有reset过,或者reset后未再申请内存,则对当前MemoryContext进行reset */
if (!context->isReset)
 MemoryContextResetOnly(context);

MemoryContextResetOnly

image.png
if (!context->isReset)
{
    /* 回掉函数 */
 MemoryContextCallResetCallbacks(context);
    /* 利用methods的reset函数处理MemoryContext */
 context->methods->reset(context);
 context->isReset = true;
}

MemoryContextResetChildren

image.png
/* 递归遍历所有的子节点,进行reset */
for (child = context->firstchild; child != NULL; child = child->nextchild)
{
 MemoryContextResetChildren(child);
 MemoryContextResetOnly(child);
}

MemoryContextAlloc,MemoryContextAllocZero,MemoryContextAllocZeroAligned,MemoryContextAllocExtended,palloc,palloc0,palloc_extended^[5]^

/* 申请内存,设置isReset为false */
context->isReset = false;

/* 使用methods的alloc函数申请、分配内存,失败则报错 */
ret = context->methods->alloc(context, size);
if (unlikely(ret == NULL))
{
 MemoryContextStats(TopMemoryContext);
 ereport(ERROR,
   (errcode(ERRCODE_OUT_OF_MEMORY),
    errmsg("out of memory"),
    errdetail("Failed on request of size %zu in memory context \"%s\".",
        size, context->name)));
}

return ret;

MemoryContextInit

/* 申请TopMemoryContext和ErrorContext */
TopMemoryContext = AllocSetContextCreate((MemoryContext) NULL,
       "TopMemoryContext",
      ALLOCSET_DEFAULT_SIZES);
ErrorContext = AllocSetContextCreate(TopMemoryContext,
       "ErrorContext",
        8 * 1024,
        8 * 1024,
        8 * 1024);
/* 保证ErrorContext能够一直返回信息 */
MemoryContextAllowInCriticalSection(ErrorContext, true);

pfree

/* 使用methods的free函数释放内存 */
context->methods->free_p(context, pointer);

repalloc

/* 使用methods的realloc函数realloc内存,失败则报错 */
ret = context->methods->realloc(context, pointer, size);
if (unlikely(ret == NULL))
{
 MemoryContextStats(TopMemoryContext);
 ereport(ERROR,
 (errcode(ERRCODE_OUT_OF_MEMORY),
 errmsg("out of memory"),
 errdetail("Failed on request of size %zu in memory context \"%s\".",
   size, context->name)));
}

return ret;

总结

相比较使用glibc的malloc等接口,PostgreSQL实现的MemoryContext:

  1. 使用特定的内存管理方式,以及特定的内存管理函数,减少了malloc和free的使用,降低了开发复杂度,并减少了内存泄漏的风险;

  2. 由于malloc是会针对多线程进行一定设计,所以加了一定的锁,当内存进行频繁申请和释放时,会有一定的性能损耗,PG释放MemoryContext首先会将其加入到freelist中,减少malloc的频率,提高内存申请的效率。


文章转载自PolarDB,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论