PostgreSQL技术内幕(十四)探索PG的进程与内存管理

PostgreSQL因为性能卓越、运行稳定的特点而广受欢迎,高效和精细的进程与内存管理机制是性能和稳定背后重要的支撑。它采用多进程协同配合架构,进程间通过共享内存进行通信。

在本次直播中,我们与大家分享了PostgreSQL多进程架构和内存管理机制。以下内容根据直播文字整理而成。

PostgreSQL进程架构与内存管理********

PostgreSQL的多进程处理架构,为数据库系统的稳定性、并发性、跨平台能力和安全性提供了支撑。虽然与基于多线程的数据库系统相比,多进程模型可能在上下文切换和资源占用上有所不足,但在众多常用场景下,这种设计选择为PostgreSQL带来了显著的优势。

在PostgreSQL多进程架构体系中,最重要的两个进程是守护进程(Postmaster)与服务进程(Postgres)。其中服务进程可以接收并执行客户端发送的命令,并调用底层存储、事务管理、索引等功能模块完成客户端的各种操作,并返回执行结果。

图1:PostgreSQL多进程架构和内存模型流程示意图

如上图所示,当客户端发起连接时,守护进程会fork单独的服务进程为客户端提供服务,此后由服务进程为客户端执行各种命令,客户端直接和服务进程通信,不再需要守护进程中转,直到客户端断开连接。

守护进程是所有进程的父进程,负责整个数据库系统的启动、关闭、监听、接受新的客户端连接、处理配置变更和恢复和故障处理;服务进程主要职责在于客户端连接认证,并负责处理客户端发出的查询和语句。

除了守护进程的和服务进程外,PG在运行期间还需要一些辅助进程,包括:

  • Background writer:负责将共享缓冲池中的脏页逐渐刷入持久化存储中。
  • Checkpointer:在PG9.2及其后版本中,该进程负责处理检查点。
  • Autovacuum launcher:周期性地启动自动清理工作进程。
  • WAL writer:本进程周期性地将WAL缓冲区中的WAL数据刷入持久存储中。
  • Statistics Collector:负责收集统计信息,用于诸如pg_stat_activity, pg_stat_database等系统视图。
  • Logging collector (logger):负责将错误消息写入日志文件。
  • Archiver:负责将日志归档。

在内存模型方面,PostgreSQL的内存体系结构可以分为两大类:本地内存区域(Local memory area)和共享内存区域(Shared memory area)

本地内存由每个后端服务进程分配供自己使用,当后端服务进程被fork时,每个后端服务进程为查询分配一个本地内存区域,由以下三部分组成:

  • work_mem:执行器在执行ORDER BY和DISTINCT时使用该区域对元组做排序,以及存储归并连接和散列连接中的连接表。
  • maintenance_work_mem:某些类型的维护操作使用该区域(例如VACUUM、REINDEX)。
  • temp_buffers:临时表相关操作使用这部分内存。

共享内存区域由PostgreSQL服务器在启动时分配,由所有后端进程共同使用。这个区域也被划分为几个固定大小的子区域,如下所示:

  • Shared buff er pool:PostgreSQL将表和索引中的页面从持久存储加载至此,并直接操作。
  • WAL buffer:WAL数据是PostgreSQL中的事务日志;WAL缓冲区是WAL数据在写入持久存储之前的缓冲区。
  • Commit log buffer:提交日志为并发控制(CC)机制保存了所需的所有事务状态(例如进行中、已提交、已中止等)。

内存上下文的实现

内存管理是数据库设计的重要环节。在PostgreSQL 7.1之前,大量以指针传值的查询可能会造成严重、不易排查的内存泄漏。从7.1版本开始,PostgreSQL使用内存上下文(MemoryContext)机制来管理内存,解决了内存泄漏的问题,同时可以提高内存分配的效率,并避免内存碎片的产生。对用户来说,可通过palloc和pfree函数在内存上下文中申请、释放内存片。

内存上下文的本质是对SQL执行所需的内存进行阶段性的划分,每个阶段的内存由对应的内存上下文进行管理,内存上下文之间则构成树状的结构,其根节点为TopMemoryContext,在整个进程的生命周期里,TopMemoryContext 都将常驻于内存。

这样,在数据库运行的过程中,可以不断地根据需要创建和释放内存上下文。在释放内存上下文的过程中,所有内存上下文(及其子上下文)中分配的内存也都得以释放,而不必去关心每一块内存的释放。

图2:PostgreSQL内存上下文结构示意图

在上图中,各模块功能如下:

  • TopMemoryContext **:**位于内存上下文树型管理结构的顶层,所有其它的内存上下文都是其直接或间接子节点。TopMemoryContext上的内存分配与malloc完全相同,因此TopMemoryContext不会被重置和删除。
  • CacheMemoryContext**:**RelCache、CatCache以及相关模块的持久存储,无法重置或删除。
  • MessageContext**:**此内存环境持有前台进程传递过来的当前命令消息,以及当前消息衍生出来的并且与当前消息生命周期相同的存储空间。
  • TopTransactionContext**:**此内存环境一直持续到最高层事务结束的时候。在每一次最高层事务结束的时候,这个内存环境都会被重设,其所有的子内存环境都会被删除。在大多数情况下,无须在这里分配内存,而应该在CurTransactionContext中分配。注意:此内存环境不会在出错时立即清除,而是直到事务块通过调用COMMIT/ROLLBACK时清除。
  • CurTransactionContext**:**此内存环境持有当前事务的数据,直到当前事务结束,特别是在最高层事务提交时需要此内存环境。当处于一个最高层事务中时,此内存环境与TopTransactionContext一致,但是在子事务中,CurTransactionContext则指向一个子内存环境。
  • ErrorContext**:**这是一个持久性的内存环境,会在错误恢复过程中切换,在恢复结束时重设。这里安排了8K的空间,保证在其他所有内存用尽之后,也可以顺利地把错误恢复。

图3:PostgreSQL内存上下文数据结构示意图

在PostgreSQL中,MemoryContextData是内存上下文的核心数据结构,通过指针描述了内存上下文之间的关系,并通过虚函数表提供了对内存进行基本操作的接口,包括对内存的分配和释放。上下文之间的关系通过parent、firstchild、prevchild、nextchild等指针进行描述,在释放内存上下文的时候也会根据这些指针,遍历释放当前上下文的所有子上下文。

事实上,内存上下文并不管理实际上的内存分配,仅仅是用作对MemoryContext树的控制。管理内存上下文中的内存块是通过AllocSet结构来完成的,而MemoryContext仅作为AllocSet的头部信息存在,AllocSet是一个指向AllocSetContext结构的类型指针。

AllocSetContext是内存上下文的核心控制结构,本质上是一个高效的内存分配器。在AllocSetContext中,内存分成两个层次:内存块(Block)和内存片(Chunk)。通常,一个内存块会包含多个内存片,内存片则是PG分配内存的最小单元。

在整个内存分配的过程中有一个非常关键的数据结构:freelist,它是减少系统调用的关键所在。

freelist数组的大小默认为11,能够保存11种不同大小的空闲内存片,freelist 数组中最小的内存片大小为8Bytes,最大的内存片为8192bytes。

在向PG申请内存的时候,不会直接调用malloc分配内存,而是由PG先向系统通过malloc申请一块较大的内存块,然后由PG从该较大的内存块中切割出一块合适大小的内存片返回给申请者;申请者释放内存的时候也不会直接返还给操作系统,而是交由PG通过freelist保留不同大小的空闲碎片,在下次申请内存的时候,可以直接从freelist中寻找合适的内存片进行内存分配。这样做的目的主要是为了减少系统调用的次数和内存碎片的产生,提高内存分配和回收的效率。

申请内存大小的上限为allocChunkLimit,如果需要分配的内存超过该值,显然无法从freelist中分配内存,将通过 malloc直接申请一整块内存块(内存对齐处理),并整体作为一个内存片返回给申请者。如果申请内存大小未超 allocChunkLimit 且freelist 中有合适空闲碎片,可直接通过计算,得到 freelistindex,并从freelist中的链表中返回合适的空闲碎片。

在内存释放的时候,会有两种情况:

  • ChunkSize > allocChunkLimit,直接调用 free() 进行释放。
  • ChunkSize <= allocChunkLimit,将 Chunk直接添加至freelist空闲链表中即可。

结语

PostgreSQL通过使用多进程架构实现了系统的可靠性和健壮性,同时采用内存上下文管理机制,避免了内存泄漏问题的发生,减少内存碎片,提高内存的分配效率。

相关推荐
PyAIGCMaster20 分钟前
文本模式下成功。ubuntu P104成功。
服务器·数据库·ubuntu
drebander33 分钟前
MySQL 查询优化案例分享
数据库·mysql
初晴~1 小时前
【Redis分布式锁】高并发场景下秒杀业务的实现思路(集群模式)
java·数据库·redis·分布式·后端·spring·
盖世英雄酱581361 小时前
InnoDB 的页分裂和页合并
数据库·后端
YashanDB3 小时前
【YashanDB知识库】XMLAGG方法的兼容
数据库·yashandb·崖山数据库
独行soc3 小时前
#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍11基于XML的SQL注入(XML-Based SQL Injection)
数据库·安全·web安全·漏洞挖掘·sql注入·hw·xml注入
风间琉璃""4 小时前
bugkctf 渗透测试1超详细版
数据库·web安全·网络安全·渗透测试·内网·安全工具
drebander4 小时前
SQL 实战-巧用 CASE WHEN 实现条件分组与统计
大数据·数据库·sql
IvorySQL4 小时前
IvorySQL 4.0 发布:全面支持 PostgreSQL 17
数据库·postgresql·开源数据库·国产数据库·ivorysql
梦想画家4 小时前
DuckDB:pg_duckdb集成DuckDB和PostgreSQL实现高效数据分析
postgresql·数据分析·duckdb·pg_duckdb