PostgreSQL技术内幕4:PostgreSQL存储引擎内存管理

news/2024/9/14 22:32:17/ 标签: postgresql, 数据库

目录

    • 0.简介
    • 1.原理介绍
    • 2.代码分析
      • 2.0 整体结构
      • 2.1 相关宏定义
      • 2.2 MemoryContextData,内存管理使用的结构定义
      • 2.3 AllocSetContext,负责真正的内存分配和管理
      • 2.4 使用分析+层次分析
      • 2.5 使用过程
    • 3.共享内存区域介绍
    • 4. 查看方法

0.简介

了解完PG的物理存储,本篇介绍PG的内存管理,主要包含内存管理方式,分配流程,各部分层级关系,以及如何通过函数指针的方式动态的进行分配方式选择,主要分析进程本地的内存管理,同时在最后第三节介绍多进程共享内存管理。本篇介绍完内存管理后,下一篇介绍物理存储到内存的转换过程。

1.原理介绍

对于操作系统来说,对于小块内存的管理,是利用不同大小的bucket链表串连相同大小的buf,如下图:

在这里插入图片描述
对于PG来说,利用和操作系统类似的原理,定义了11个大小不同的freelist,管理不同大小的内存。
在这里插入图片描述

2.代码分析

2.0 整体结构

其整体设计思路为两级结构MemoryContextData负责记录和管理内存树的信息,AllocSetContext负责真正的分配和管理内存,其中有空闲内存列表和MemoryContextData树的头节点。
在这里插入图片描述

2.1 相关宏定义

#define ALLOC_MINBITS       3   /* smallest chunk size is 8 bytes,2的3次方 */
#define ALLOCSET_NUM_FREELISTS  11 /*freelist的数量,如上图的freelist0到freelists10共11个*/
#define ALLOC_CHUNK_LIMIT   (1 << (ALLOCSET_NUM_FREELISTS-1+ALLOC_MINBITS)) /* Size of largest chunk that we use a fixed size for */

2.2 MemoryContextData,内存管理使用的结构定义

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 */const char *name;           /* context name (just for debugging) */const char *ident;          /* context ID if any (just for debugging) */MemoryContextCallback *reset_cbs;   /* list of reset/delete callbacks */
} MemoryContextData;

可以看到,内存分配方法methods使用函数指针表的方式定义,是c语言动态调用函数的一种方式,其结构如下。该结构还有父节点以及子节点,可以理解MemoryContextData构造了一个内存管理信息的树,某个节点清理可以清理它的子节点,方便了管理。


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_CHECKINGvoid    (*check) (MemoryContext context);
#endif
} MemoryContextMethods;

2.3 AllocSetContext,负责真正的内存分配和管理


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;

MemoryContextData是内存管理的基本结构,其只会记录内存管理的信息,而不直接管理内存分配,内存分配的管理实际采用AllocSetContext,其中header记录分配的Context信息,freelist就是上文介绍的内存切块的管理,数量为ALLOCSET_NUM_FREELISTS,和 AllocBlock分别对应内存的两个层次:内存块(Block)和内存片(Chunk)。通常,一个内存块会包含多个内存片,内存片则是PG分配内存的最小单元。

在向PG申请内存的时候,不会直接调用malloc分配内存,而是由PG先向系统通过malloc申请一块较大的内存块,然后由PG从该较大的内存块中切割出一块合适大小的内存片返回给申请者;申请者释放内存的时候也不会直接返还给操作系统,而是交由PG通过freelist保留不同大小的空闲碎片,在下次申请内存的时候,可以直接从freelist中寻找合适的内存片进行内存分配。这样做的目的主要是为了减少系统调用的次数和内存碎片的产生,提高内存分配和回收的效率。
allocChunkLimit为申请内存大小的最大值,如果需要分配的内存超过该值,显然无法从freelist中分配内存,将通过 malloc直接申请一整块内存块(内存对齐处理),并整体作为一个内存片返回给申请者。如果申请内存大小未超 allocChunkLimit 且freelist 中有合适空闲碎片,可直接通过计算,得到 freelistindex,并从freelist中的链表中返回合适的空闲碎片。
在内存释放时,如果ChunkSize > allocChunkLimit,直接调用 free() 进行释放;ChunkSize <= allocChunkLimit,将 Chunk直接添加至freelist空闲链表中。

2.4 使用分析+层次分析

在程序启动时会调用MemoryContextInit,其会初始化TopMemoryContext ,即顶层的MemoryContext,预先定义的MemoryContext如下所示:


MemoryContext CurrentMemoryContext = NULL;/** Standard top-level contexts. For a description of the purpose of each* of these contexts, refer to src/backend/utils/mmgr/README*/
MemoryContext TopMemoryContext = NULL;
MemoryContext ErrorContext = NULL;
MemoryContext PostmasterContext = NULL;
MemoryContext CacheMemoryContext = NULL;
MemoryContext MessageContext = NULL;
MemoryContext TopTransactionContext = NULL;
MemoryContext CurTransactionContext = NULL;

层次如下:
在这里插入图片描述
1)TopMemoryContext:位于MemoryContext树状管理结构的最上层,其它所有MemoryContext都是其直接或间接子节点。
2)CacheMemoryContext:RelCache、CatCache以及相关模块的持久存储,无法重置或删除。
3)MessageContext:此Context保存有传递过来的当前命令和当前命令创建出的和当前命令生存周期一致的存储空间。
4)TopTransactionContext:此内存环境一直持续到最高层事务结束的时候。在每一次最高层事务结束的时候,这个内存环境都会被重设,其所有的子内存环境都会被删除。注意:此内存环境不会在出错时立即清除,而是直到事务块通过调用COMMIT/ROLLBACK时清除。
5)CurTransactionContext:此内存环境持有当前事务的数据,直到当前事务结束,特别是在最高层事务提交时需要此内存环境。当处于一个最高层事务中时,此内存环境与TopTransactionContext一致,但是在子事务中,CurTransactionContext则指向一个子内存环境。
6)ErrorContext:这是一个持久性的内存环境,会在错误恢复过程中切换,在恢复结束时重设。这里安排了8K的空间,保证在其他所有内存用尽之后,也可以顺利地把错误恢复。

2.5 使用过程

创建:

cxt = AllocSetContextCreate(CurrentMemoryContext,"AttachPartitionEnsureIndexes",ALLOCSET_DEFAULT_SIZES);

删除:


out:/* Clean up. */for (i = 0; i < list_length(attachRelIdxs); i++)index_close(attachrelIdxRels[i], AccessShareLock);MemoryContextSwitchTo(oldcxt);MemoryContextDelete(cxt);

创建删除的调用代码如上,下面对其中调用palloc的部分的分配进行分析,
主要分为以下两个步骤:
1)调用palloc申请内存。
2)palloc中调用虚拟表(即函数指针的列表)中的alloc方法。
其中底层使用的为AllocSetAlloc:

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

3.共享内存区域介绍

分为以下几个部分:

1)Shared buffer pool:存储PG表和索引的页面。
2)WAL buffer:WAL(事务日志)写入前的存储区域。
3)Commit log buffer:提交日志为并发控制(CC)机制保存了所需的所有事务状态(例如进行中、已提交、已中止等)。

4. 查看方法

在PG14提供了pg_log_backend_memory_contexts函数,查看MemoryContext使用情况,可以使用select pg_log_backend_memory_contexts(进程id),然后可以在log中查看信息。


http://www.ppmy.cn/news/1519955.html

相关文章

计算机视觉之 SE 注意力模块

计算机视觉之 SE 注意力模块 一、简介 SEBlock 是一个自定义的神经网络模块&#xff0c;主要用于实现 Squeeze-and-Excitation&#xff08;SE&#xff09;注意力机制。SE 注意力机制通过全局平均池化和全连接层来重新校准通道的权重&#xff0c;从而增强模型的表达能力。 原论…

Python对音频进行频谱分析

文章目录 一、储备函数1.1 计时装饰器、查找最接近的值、音高频率创建与读取1.2 440音高频率表 二、Pyaudio 一、储备函数 1.1 计时装饰器、查找最接近的值、音高频率创建与读取 import time import math import numpy as np import pandas as pd# 定义时间装饰器 def timeit…

Linux驱动(三):字符设备驱动之杂项

目录 一、Linux设备分类二、设备号与字符设备的编码方式1.设备号2.字符设备的编码方式 三、杂项字符设备驱动的初级编写 一、Linux设备分类 Linux下一切皆文件&#xff0c;所有的硬件设备在Linux应用层中都会被抽象成文件&#xff0c;所有对硬件设备的操作到应用层中&#xff0…

1004.最大连续1的个数Ⅲ

1.题目描述 给定一个二进制数组 nums 和一个整数 k&#xff0c;如果可以翻转最多 k 个 0 &#xff0c;则返回 数组中连续 1 的最大个数 。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,1,0,0,0,1,1,1,1,0], K 2 输出&#xff1a;6 解释&#xff1a;[1,1,1,0,0,1,1,1,1,1,1…

我不是非酋之-天空概率

最近入坑了dnf手游&#xff0c;染上了合天空&#xff0c;大黑蛋子一个&#xff0c;突发奇想&#xff0c;模拟下合天空概率&#xff0c;看看是否真的有20%。 梳理代码逻辑如下&#xff1a; 不考虑礼包和其他东西 条件&#xff1a; 合成概率20%&#xff0c;每次需要2个装扮和一个…

改进YOLO的群养猪行为识别算法研究及部署(小程序-网站平台-pyqt)

概述 群养猪的运动信息和行为信息与其健康状况息息相关&#xff0c;但人工巡视费时费力&#xff0c;本实验提出采用行为识别算法于群养猪的养殖管理中&#xff0c;识别群养猪drink&#xff08;饮水&#xff09;、stand&#xff08;站立&#xff09;和lie&#xff08;躺卧&#…

论tomcat线程池和spring封装的线程池

Tomcat 中的线程池是什么&#xff1f; 内部线程池&#xff1a;Tomcat 确实有一个内部的线程池&#xff0c;用于处理 HTTP 请求&#xff0c;通常是org.apache.tomcat.util.threads.ThreadPoolExecutor 类的实例。这个线程池专门用于处理进入的 HTTP 请求和发送响应。可以通过 T…

【C++】手动实现String类的封装(分文件编译)

实现了String类的大部分封装&#xff0c;采用分文件编译 //mystring.h #ifndef MYSTRING_H #define MYSTRING_H#include <iostream> #include <cstring> using namespace std;class myString { private:char *str; //定义一个字符串int size; //记录字符串…

线性代数 第五讲:线性方程组_齐次线性方程组_非齐次线性方程组_公共解同解方程组_详解

线性方程组 文章目录 线性方程组1.齐次线性方程组的求解1.1 核心要义1.2 基础解系与线性无关的解向量的个数1.3 计算使用举例 2. 非齐次线性方程的求解2.1 非齐次线性方程解的判定2.2 非齐次线性方程解的结构2.3 计算使用举例 3.公共解与同解3.1 两个方程组的公共解3.2 同解方程…

鸿蒙界面开发(五):相对布局

相对布局RelativeContainer 相对布局的容器&#xff0c;支持容器内部的子元素设置相对位置关系&#xff0c;适用于界面复杂场景的情况&#xff0c;对多个子组件进行对齐和排列。子元素支持指定兄弟或父元素作为锚点&#xff0c;基于锚点做相对位置布局。 锚点&#xff1a;通过…

RK3568 Android 11 蓝牙BluetoothA2dpSink 获取用于生成频谱的PCM

Android 中的 A2DP Sink A2DP Sink 在 Android 系统中主要用于 接收 其他蓝牙设备&#xff08;如手机、平板、电脑等&#xff09;发送过来的 高质量的立体声音频。简单来说&#xff0c;它让你的 Android 设备可以充当一个 蓝牙音箱 或 耳机 的角色。 核心功能&#xff1a; 接…

vue3 监听

watch &#xff08;1&#xff09;监听ref 状态的变化 const num ref(1) watch(num,(newValue,oldValue)>{console.log(newValue,oldValue);//2,1 }) setTimeout(()>{num.value },500)&#xff08;2&#xff09;computed const num ref(1); const double computed((…

惠中科技光伏清洗剂:绿色清洁,引领光伏行业新潮流

在当今全球能源转型的大潮中&#xff0c;光伏产业作为绿色能源的重要组成部分&#xff0c;正以前所未有的速度蓬勃发展。然而&#xff0c;随着光伏板在户外环境的长时间暴露&#xff0c;其表面不可避免地会积累灰尘、鸟粪、油污等污染物&#xff0c;严重影响光伏板的透光率和发…

Gorm--Scan

在 Gorm 中&#xff0c;Scan 是一个用于将查询结果映射到自定义结构体或变量的函数。与 Find 或 First 不同&#xff0c;Scan 允许你将查询结果存储到与数据库模型不完全匹配的结构体中。它特别适合用于自定义查询结果或联合查询的场景。 type Result struct {Name stringEma…

#单片机基础 笔记二

SPI中断 1.SPI总线协议 1.1协议介绍 SPI接口是Motorola &#xff08;motorola | Smartphones, Accessories & Smart Home Devices&#xff09;首先提出的全双工三线/四线同步串行外围接口采用主从模式&#xff08;Master Slave&#xff09;架构。 时钟由Master控制&#xf…

ELK学习笔记——如何给Kibana新增用户和角色

Kibana新增用户和角色 首先用超管账号登录上Kibana&#xff0c;按照下面步骤操作 1、创建角色 按图操作 2、创建用户 按图操作 3、给用户分配角色 至此&#xff0c;角色和用户绑定成功&#xff1b; 最后&#xff0c;可以退出管理员账号&#xff0c;登录这个新…

github私有仓库通过action部署hexo到公开仓库

github私有仓库通过action部署hexo到公开仓库 有一段时间一直将博客md文件直接放到公开仓库然后通过工作流action创建一个gh-page分支&#xff0c;来实现部署 但是这样做有一个问题&#xff0c;如果你的源文件&#xff0c;或者配置文件中有涉及变量&#xff0c;或者密钥key&a…

Linux-vim

文章目录 vi和vimvim的基本概念vim的基本操作vim正常模式命令集插入模式从插入模式切换为命令模式移动光标删除文字复制替换撤销上一次操作更改跳至指定的行 vim末行模式命令集列出行号跳到文件中的某一行查找字符保存文件推出vim vi和vim vi/vim的区别简单点来说&#xff0c;…

Linux C 内核编程 /proc 编程例子

直接上代码 proc_demo.c内核版本&#xff1a; 5.4.0-150-generic #include <linux/module.h> #include <linux/sched.h> #include <linux/proc_fs.h> #include <linux/seq_file.h> #include <linux/uaccess.h> #include <linux/slab.h>s…

threading.local的使用

python中的threading.local对象 在Python中&#xff0c;使用threading.local对象的意义在于为每个线程提供了一种安全地存储和访问线程局部变量的方式。这种机制对于多线程编程特别有用&#xff0c;因为它可以帮助开发者避免一些常见的多线程编程问题&#xff0c;如数据竞争、…