PostgreSQL 内存配置 与 MemoryContext 的生命周期
PostgreSQL 内存配置与MemoryContext的生命周期
PG/GP 内存配置
数据库可用的内存 gp_vmem
整个 GP 数据库可用的内存 gp_vmem:
>>> RAM = 128 * GB
>>> gp_vmem = ((SWAP + RAM) - (7.5*GB + 0.05 * RAM)) / 1.7
>>> print(gp_vmem / GB)
67.11764705882352>>> RAM = 256 * GB
>>> gp_vmem = ((SWAP + RAM) - (7.5*GB + 0.05 * RAM)) / 1.7
>>> print(gp_vmem / GB)
138.64705882352942
内存上限 gp_vmem_protect_limit
每个 Segment 可用的内存上限 gp_vmem_protect_limit:
max_acting_primary_segments = num_segments + 3
gp_vmem_protect_limit = gp_vmem / max_acting_primary_segments
内存配额 gp_statement_mem
每个 Segment 可用的内存配额 gp_statement_mem:
gp_statement_mem = (gp_vmem_protect_limit * 0.9) / max_expected_concurrent_queries
gp_statement_mem = (8192 MB * .9) / 40 = 184MB
注意:实际上的配置名称为statement_mem,并且其值不能突破max_statement_mem的限制。
并发量 max_parallel_workers_per_gather
并发量与内存息息相关,并发越大启动的进程越多,消耗的内存则越大。
show max_parallel_workers; -- default 60show max_parallel_workers_per_gather; -- default 2
set max_parallel_workers_per_gather = 5;
官方参考文档:
- https://gp-docs-cn.github.io/docs/best_practices/sysconfig.html#topic_dt3_fkv_r4__segment_mem_config
- https://gp-docs-cn.github.io/docs/best_practices/workloads.html
PG MemoryContext 生命周期
MemoryContext 生命周期概览
PG 的 MemoryContext 是一个树形结构,每个Query可以对应一个MemoryContext(我们可以称为query_context),query_context会创建Child行级别的MemoryContext,比如下面是Aggregate的2个典型行级别MemoryContext,分别为tuple_context、expr_context:
auto aggstate = reinterpret_cast<::AggState *>((::PlanState *) shadow_ps);
auto tuple_context = aggstate->curaggcontext->ecxt_per_tuple_memory;
auto expr_context = aggstate->tmpcontext->ecxt_per_tuple_memory;
tuple_context 生命周期
一般来说,tuple_context的生命周期比query_context的短,当火山模型中的一行数据完成吐出,或者向量化模型中的一批数据完成吐出,则tuple_context中的内存就可以释放了,tuple_context的生命周期是贯穿每一行的如下步骤:
- 从最底层读取数据到数据。
- 中间的一连串计算算子。
- 结尾的将最终结果吐出给用户。
expr_context 生命周期
但是expr_context的生命周期,一般来说,比tuple_context的更短,expr_context在任何一个算子中,或两个上下依赖的算子中,均可完成其全部生命周期(从创建到销毁内存)。本质上来说,expr_context定位为临时性的内存需求,用完即可释放。
MemoryContext 典型操作
MemoryContext 上下文创建
/** Create working memory for expression evaluation in this context.*/
// src/backend/executor/execUtils.c
econtext->ecxt_per_tuple_memory =AllocSetContextCreate(estate->es_query_cxt,"ExprContext",minContextSize,initBlockSize,maxBlockSize);// src/backend/utils/mmgr/aset.c
MemoryContext
AllocSetContextCreateInternal(MemoryContext parent,const char *name,Size minContextSize,Size initBlockSize,Size maxBlockSize)
{AllocSet set;...// src/backend/utils/mmgr/mcxt.cMemoryContextCreate((MemoryContext) set,T_AllocSetContext,&AllocSetMethods,parent,name);...return set;
}
MemoryContext 上下文切换
auto expr_context = aggstate->tmpcontext->ecxt_per_tuple_memory;
auto oldcxt = ::MemoryContextSwitchTo(expr_context);
MemoryContext 内存释放(整体)
MemoryContextReset() 函数用于释放一个 MemoryContext 中分配的所有内存:
// the tmpcontext is short-live
auto expr_context = aggstate->tmpcontext->ecxt_per_tuple_memory;
::MemoryContextReset(expr_context);
Memory 内存典型操作
内存分配 palloc
void* palloc(Size size)
{/* duplicates MemoryContextAlloc to avoid increased overhead */void *ret;MemoryContext context = CurrentMemoryContext;AssertArg(MemoryContextIsValid(context));AssertNotInCriticalSection(context);if (!AllocSizeIsValid(size))elog(ERROR, "invalid memory alloc request size %zu", size);context->isReset = false;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)));}VALGRIND_MEMPOOL_ALLOC(context, ret, size);return ret;
}
内存分配并填充0 – palloc0
void* palloc0(Size size)
{/* duplicates MemoryContextAllocZero to avoid increased overhead */void *ret;MemoryContext context = CurrentMemoryContext;AssertArg(MemoryContextIsValid(context));AssertNotInCriticalSection(context);if (!AllocSizeIsValid(size))elog(ERROR, "invalid memory alloc request size %zu", size);context->isReset = false;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)));}VALGRIND_MEMPOOL_ALLOC(context, ret, size);MemSetAligned(ret, 0, size);return ret;
}
内存重分配 repalloc
/** repalloc* Adjust the size of a previously allocated chunk.*/
void* repalloc(void *pointer, Size size)
{MemoryContext context = GetMemoryChunkContext(pointer);void *ret;if (!AllocSizeIsValid(size))elog(ERROR, "invalid memory alloc request size %zu", size);AssertNotInCriticalSection(context);/* isReset must be false already */Assert(!context->isReset);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)));}VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size);return ret;
}
内存释放 pfree
/** pfree* Release an allocated chunk.*/
void pfree(void *pointer)
{MemoryContext context = GetMemoryChunkContext(pointer);context->methods->free_p(context, pointer);VALGRIND_MEMPOOL_FREE(context, pointer);
}