PostgreSQL 内核解读系列 - 第4节 存储管理(下)

本文涉及的产品
云原生数据库 PolarDB MySQL 版,通用型 2核4GB 50GB
云原生数据库 PolarDB PostgreSQL 版,标准版 2核4GB 50GB
简介: 本文整理自阿里云数据库开源社区 Maintainer 于巍(花名漠雪),在PostgreSQL数据库内核解读系列的分享。本篇内容主要分为四个部分: 1. 磁盘管理 2. 空闲空间映射表(FSM) 3. 可见性映射表(VM) 4. 内存管理。

本文整理自阿里云数据库开源社区 Maintainer 于巍(花名漠雪),在PostgreSQL数据库内核解读系列的分享。本篇内容主要分为四个部分: 1. 磁盘管理 2. 空闲空间映射表(FSM) 3. 可见性映射表(VM) 4. 内存管理

一、磁盘管理

图片.png

用户访问存储引擎的流程如下:用户发送SQL请求后,首先访问元组(每一行数据即为一个元组),将数据加载至Buffer Pool,再解析出需要的内容,同时还需进行内存管理。然后经过存储管理器、磁盘管理器和虚拟文件管理器的依次调用,最终访问到物理文件。

图片.png

每个Backend 都利用SMgrRelationHash来管理SMgrRelationData RelFileNodeBackend包含了三个 OID 能够唯一定位到某表的文件,表的文件有三种类型,包含正常的数据文件、FSM 文件和 VM 文件。数据文件有容量限制,比如限制为2G,超限后会切换至另一个文件。而针对每一个打开的文件,会由MdfdVec来管理数据,即通过MdfdVec数组来管理RelFileNode的文件。MdfdVecn内存储了 FD 内cache数组的下标以及文件segmnent的blocknumber。


SMgrRelationHash的创建和查询过程如下:首先通过smgropen函数查看hash表是否为空。若为空,则新建哈希表;否则,将rnode的relation与Backend 结合为 key 用于查找。然后设置reln>md_num_open_segs[forknum]=0,并进行segment相关操作。

图片.png

外部访问SMGR,主要通过比如BufferPool利用 smgr相关接口获取数据,然后通过smgrread获取相关数据页。其中ReadBuffer_ommon调用存储来获取数据页,需要通过smgrread函数传入页号等相关参数。


Smgrread调用了虚拟函数表内的smgr_read,smgr_read会从smgrsw[]中查找mdread,相当于smgr利用smgrsw封装了底层MD调用接口,且此处预留了扩展接口,以便后续实现更多访问方式。

图片.png

MD是Berkeyley开源的唯一一个磁盘管理器,可以访问磁盘和SSD,Berkeyley在最初发布的时候就做了注释。Mdread 和 mdcreate 利用虚拟函数表找到对应函数,再往下层调用FileSeek。Mdfd_getseg将根据ForkNumber查找与 SMgrRolation 结构相关的信息,并根据信息找到Mdfd_Vec变量,将Mdfd_Vec变量作为参数传递到下一层的FileSeek和FileRead 等函数。

图片.png

虚拟文件管理层最主要的作用是防止打开文件的数量超过操作系统的限制。多个进程会有多个 backend 同时运行为客户提供服务,每个 bankend 都有对应的VFD记录相关信息。而操作系统对打开文件的数量有要求,因此需要进行对应的设置。利用 LRU 淘汰机制,通过 LRU 的双向链表来管理。如果超过了规定数量,则将最不常用的文件删除。


其中 VFD 内的fd是操作系统里对应的文件句柄fd,nextFree负责串联freelist,lruMoreRecently和lruLessRecently负责管理双向链表LRU结构。需要新访问或打开文件的时候,如果文件不存在,则将其添加到 LRU 最前端;如果LRU列表已满,则将最后(最不常用的)一个元素删除。


之后为 VFDCache申请空间,新空间大小newCacheSize为sizeVdfCache*2。再申请内存,大小为vdf*newCacheSize,并进行freelist初始化,之后数据会逐步从freelist进入LRU。


set_max_safe_fds函数用于计算可以打开的最大文件数量,通过进程数减去已打开的文件数得出。插入新元素至LRU链表时,需从最常用一侧插入。


综上,存储引擎的访问流程可以总结为:通过元组访问,将数据加载到 buffer 里,再通过依次调用SMGR、MD、VFD,其中SMGR层的relationcache和VFD层的 vfdcache分别用于缓存和加速,最终通过read函数获取系统相关的接口来读取文件。


二、空闲空间映射表(FSM)

 图片.png

空闲空间映射表使用最大堆二叉树的结构做存储,每一层记录 4000 个页面,每个节点都用字节来存储。PG规定的表最大size为232,而40003>232,因此三层可覆盖所有数据页。


最底层的叶子用于存储页面对应的空闲空间值,从1开始编号,依次增大。叶子节点用1个字节记录块的大致剩余空间,分为256级,每个级别对应一个空间范围。可以通过pg_freespace查询每个blocknumber对应的空闲空间,空闲空间记录的是范围值,并不需要绝对精确,因为实际使用时往往只需选择比需要的空间略大的页面即可。


符合条件的空闲空间页面的检索逻辑如下:先通过 fsm_readbuf 函数读取页面。然后返回同时符合addr.level=FSM_BOTTOM_LEVEL和max_avail=fsm_get_max_avail(BuffergetPage(buf))以及slot!=-1的blocknumber。


三、可见性映射表

 图片.png

可见性映射表中,每一位数据负责管理某配置的状态是否可见。每个页面使用2个bits来存储heap page。


可以通过create extensiong pg_visibility;select pg_visibility_map(pg_static);语句查询状态可见性。pg_visibility_map()函数的实现原理如下:


首先打开 relation即VM文件,随后执行check_relation_relkind函数,此处只支持RELKIND_RELATION、RELKIND_INDEX、RELKIND_MATVIEW、RELKIND_SEQUENCE、RELKIND_TOASTVALUE几种类型。


再通过pg_visibility_tupdesc组装出tup的描述 。一般情况下,tupdesc能够直接在系统表里获取。而此处由于数据格式固定,因此需要自行生成 。


然后通过visibilitymap_get_status获取页面的可见性信息。函数逻辑为:传入blocknumber并计算其偏移量,然后读取对应的buffer和内容并返回状态信息。


将返回的状态信息与VISIBILITYMAP_ALL_VISIBLE、VISIBILITYMAP_ALL_FROZEN分别进行对比得到两个布尔值,将布尔值组装成DATUM值返回,并解析成(1,t,t,)、(2,f,f,)形式以显示可见状态。


四、内存管理

 图片.png

操作系统有自己的内存管理机制,Java语言也有内存管理和回收机制,而 C语言通常需要自行管理内存。频繁地向操作系统申请和释放内存会产生较大开销,导致效率低,因此,需要相应的内存管理策略——MemoryContex机制来解决问题。


MemoryContex机制与操作系统的管理机制较为相似。相同大小的内存分别用一串 freelist来管理。申请数据时,会一次性申请多个相同容量的空间存放至 freelist,需要时直接从freelist取,用完放回。这样能够大幅减少与操作系统之间的调用次数,能够使性能显著提升。而针对更大的内存比如超过256字节,会有另外的策略,如上图步骤一所示。


MemoryContex一般会定义11个大小不同的freelist,最小内存为23,而后按照24、25依次增大。


AllocSetContext结构用于管理MemoryContext,其中MemoryContextData为树结构,使用完MemoryContext后,可将其对应的内存信息全部清理干净。AllocChunk通过AllocChunkData串联而成。此外,AllocSetContext内还定义了initBlockSize、maxBlockSize、nextBlockSize、allocChunkLimit。

 图片.png

此前,MemoryContext出现问题难以定位,且一旦出现问题往往较为致命。而现在,我们可以利用pg_log_backend_memory_contexts()函数,传入Backend 信息,即可打印日志,查看MemoryContext的使用情况,如上图下方所示,包含了每一层的具体信息以及所有层累计后的汇总信息。

 图片.png

主要MemoryContext的创建通过需要MemoryContextInit函数。首先,创建TopMemoryContext,主要变量的定义如上图左下所示。创建内存都是在CurrentMemoryContext上进行,因此我们需要不停地维护CurrentMemoryContext以保证它总是指向想要创建的MemoryContext,即设置CurrentMemoryContext=TopMemoryContext。


MemoryContext的创建通过AttachPartitionEnsureIndexes函数,其中AllocSetContextCreate函数在CurrentMemoryContext下创建了名为“AttachPartitionEnsureIndexes”的MemoryContext,并得到一个句柄。之后,调用MemoryContextSwitchTo将CurrentMemoryContext指向新创建的MemoryContext。然后调用palloc函数申请内存。


使用完内存以后,再调用MemoryContextSwitchTo将CurrentMemoryContext指向老的MemoryContext,然后执行MemoryContextDelete(cxt),完成清理。

 图片.png

palloc申请内存时,首先通过new_stack=(BTStack)palloc(sizeof(BTStackData))传入对应参数,通过CurrentMemoryContext做内存的申请和管理。底层调用AllocSetAlloc函数来管理内存chunk和freelist等,然后调用虚拟函数表中的alloc方法完成内存申请。

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
存储 关系型数据库 PostgreSQL
Postgresql内核源码分析-heapam分析
Postgresql内核源码分析-heapam分析
161 1
|
存储 缓存 关系型数据库
【PostgreSQL内核】Trigger的一生
前言本文简单介绍 PostgreSQL 数据库的 Trigger 从创建、存储、触发、执行、修改,到删除的过程,贯穿 Trigger 的一生。文中引用的函数、结构体来源于 PG 14 源码,分支为 REL_14_STABLE,对应的 commit id 如下。此外还引用了 PG 14 官方文档。commit be0b0528cb64d49750fcb632faa2cfcd8d920be2 Auth
511 0
|
存储 缓存 NoSQL
[译]解锁TOAST的秘密:如何优化PostgreSQL的大型列存储以最佳性能和可扩展性
[译]解锁TOAST的秘密:如何优化PostgreSQL的大型列存储以最佳性能和可扩展性
189 0
|
存储 关系型数据库 对象存储
PolarDB-PG | PostgreSQL + 阿里云OSS 实现高效低价的海量数据冷热存储分离
数据库里的历史数据越来越多, 占用空间大, 备份慢, 恢复慢, 查询少但是很费钱, 迁移慢 怎么办? 冷热分离方案: - 使用PostgreSQL 或者 PolarDB-PG 存成parquet文件格式, 放到aliyun OSS存储里面. 使用duckdb_fdw对parquet文件进行查询. - duckdb 存储元数据(parquet 映射) 方案特点: - 内网oss不收取网络费用, 只收取存储费用, 非常便宜 - oss分几个档, 可以根据性能需求选择 - parquet为列存储, 一般历史数据的分析需求多,性能不错 - duckdb 支持 parquet下推过滤, 数据过滤性能不错
6845 6
PolarDB-PG | PostgreSQL + 阿里云OSS 实现高效低价的海量数据冷热存储分离
|
存储 对象存储 块存储
|
存储 关系型数据库 API
|
存储 关系型数据库 Linux
|
Cloud Native 关系型数据库 MySQL
直播预告 | PostgreSQL 内核解读系列第六讲:PostgreSQL 索引介绍(下)
本系列课程将面向DBA、高校学生、内核爱好者,分15个章节,系统化介绍PG核心技术原理、用法和代码实现。希望通过课程学习,让没有内核经验的同学和DBA,也可以进行简单的特性开发,更深入理解PG配置和运行原理。本节课将讲述 PostgreSQL 索引基本介绍,涵盖Hash索引、Bitmap索引、GiST索引、GIN索引、brin索引、其他索引等内容。
直播预告 | PostgreSQL 内核解读系列第六讲:PostgreSQL 索引介绍(下)
|
SQL 存储 缓存
23 PostgreSQL 监控4 动态内核跟踪 stap 篇|学习笔记
快速学习23 PostgreSQL 监控4 动态内核跟踪 stap 篇
644 0
23 PostgreSQL 监控4 动态内核跟踪 stap 篇|学习笔记
|
存储 关系型数据库 分布式数据库
直播预告 | PolarDB for PostgreSQL - 共享存储在线扩容
随着业务的发展,当数据库的存储容量不能满足数据规模的增长时,需要对存储空间进行扩容,此过程中会因数据迁移而导致服务中断。本期开源学堂将演示对部署在共享存储上的 PolarDB-PG 集群进行不中断业务的在线扩容,同时简析部署流程背后的技术原理。
 直播预告 | PolarDB for PostgreSQL - 共享存储在线扩容

相关产品

  • 云原生数据库 PolarDB