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 60
show 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.c
MemoryContextCreate((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);
}