写点什么

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

  • 2024-03-15
    北京
  • 本文字数:3207 字

    阅读完需:约 11 分钟

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 buffer 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 不会被重置和删除。

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

发布于: 刚刚阅读数: 5
用户头像

还未添加个人签名 2021-03-10 加入

酷克数据是中国领先的云原生数据仓库软件公司,致力以领先技术降低大数据分析的门槛和成本,我们的产品广泛应用于金融、运营商、能源等领域,帮助企业构筑稳定高效、自主可控的数据底座。

评论

发布
暂无评论
PostgreSQL技术内幕(十四)探索PG的进程与内存管理_酷克数据HashData_InfoQ写作社区