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

相关推荐
难以触及的高度26 分钟前
mysql中between and怎么用
数据库·mysql
Jacky(易小天)40 分钟前
MongoDB比较查询操作符中英对照表及实例详解
数据库·mongodb·typescript·比较操作符
Karoku0661 小时前
【企业级分布式系统】ELK优化
运维·服务器·数据库·elk·elasticsearch
小技与小术3 小时前
数据库表设计范式
数据库·mysql
安迁岚3 小时前
【SQL Server】华中农业大学空间数据库实验报告 实验三 数据操作
运维·服务器·数据库·sql·mysql
安迁岚3 小时前
【SQL Server】华中农业大学空间数据库实验报告 实验九 触发器
数据库·sql·mysql·oracle·实验报告
Loganer3 小时前
MongoDB分片集群搭建
数据库·mongodb
LKID体3 小时前
Python操作neo4j库py2neo使用之创建和查询(二)
数据库·python·neo4j
刘大浪3 小时前
后端数据增删改查基于Springboot+mybatis mysql 时间根据当时时间自动填充,数据库连接查询不一致,mysql数据库连接不好用
数据库·spring boot·mybatis
一只爱撸猫的程序猿3 小时前
简单实现一个系统升级过程中的数据平滑迁移的场景实例
数据库·spring boot·程序员