Skip to content

PostgreSQL 架构与多进程模型

多进程体系、后端工作进程、shared_buffers 25% 内存哲学、work_mem 调优与 PgBouncer 生产连接池实践。

Updated View as Markdown
For humans

PostgreSQL 架构与多进程模型

校招考察频度:★ 高频(架构基石·生产高频考点) 一句话速记:Master 派生独立进程,隔离虽强连接沉重;缓冲双层占两成五,work_mem 乘数防爆内存;高并发首选 PgBouncer 事务池,事务级禁用会话锁。

PostgreSQL 采用了稳固而独特的多进程体系与共享内存架构。在校招面试中,考官常借由此处考察候选人对“操作系统进程与内存”、“高并发连接池设计”以及“生产参数调优”的综合驾驭能力。


一、多进程架构全景(Process Model)

与许多采用多线程模型的存储系统不同,PostgreSQL 坚守多进程架构。每个连接对应一个独立的操作系统级进程,彼此物理隔离,单个进程崩溃绝对不会波及整个数据库实例。

PostgreSQL 多进程协同架构

核心进程职责清单

  1. postmaster(主守护进程):
    • 数据库实例的根节点,监听 5432 端口;
    • 收到客户端连接请求后,通过操作系统 fork() 派生出专属于该连接的 Backend 进程;
    • 监控所有子进程的健康状态,若子进程异常退出,负责安全恢复。
  2. Backend Process(后端连接进程):
    • 每个客户端连接独占一个后端进程;
    • 独立完成从 SQL 词法解析、优化器生成执行计划,到从内存与磁盘提取数据并返回的全生命周期;
    • 连接断开时,该进程正常销毁退出。
  3. 后台核心工作进程组:
    • checkpointer:周期性推进检查点(Checkpoint),将 shared_buffers 中的脏页刷盘,并推进控制文件中的恢复起点 LSN;
    • bgwriter:持续在后台寻找较老的脏页刷盘,确保缓冲池中随时保留充裕的干净页,避免前端查询进程因找不到空页而亲自发生同步 I/O 阻塞;
    • walwriter:负责将 wal_buffers 中的 WAL 日志记录周期性刷写到磁盘;
    • autovacuum launcher & workers:自动监控死元组比例,按需派生 worker 执行垃圾回收与防 20 亿回卷冻结;
    • stats collector:收集表和索引的访问频率、元组数分布,供代价优化器生成最优计划。

二、多进程的工程代价与生产连接池(PgBouncer 必考点)

面试官灵魂拷问:“如果在高并发场景下,应用直连 PostgreSQL 开了 2000 个连接,会发生什么后果?怎么解决?”

1. 直连 2000 个连接的灾难后果

  1. 内存瞬间被打爆:每个 Backend 进程在操作系统中都要分配私有栈、符号表和连接缓冲,平均占用 5MB~10MB 内存。2000 个连接仅基础进程开销就会吃掉 10GB~20GB 物理内存!
  2. CPU 算力被上下文切换耗尽:生产服务器通常是 16~64 核 CPU。让 2000 个活跃进程激烈争抢几十个 CPU 核心,CPU 将 70% 以上的算力浪费在进程上下文切换(Context Switching)与内核信号量争用上,实际业务吞吐呈断崖式下跌。

2. 生产解法:引入外部连接池 PgBouncer

PostgreSQL 内核没有内置线程连接池,生产环境必须部署专用的轻量级连接池中间件(如 PgBouncer)。

PgBouncer 的三种工作模式(必背)

模式工作原理适用场景与优缺点
Session Pooling(会话池化)客户端连上即绑定一个后端进程,客户端断开连接才释放。最保守模式,支持所有会话特性,但无法解决海量突发并发问题。
Transaction Pooling(事务池化)生产主力推荐:客户端仅在开启事务(BEGIN ~ COMMIT)期间才租借后端连接,事务一结束立刻归还池中!极度高效:可以用 50~100 个底层真实数据库连接,轻松承载上万个客户端高并发!
Statement Pooling(语句池化)每执行一条 SQL 就归还一次连接。限制极大:完全不支持多语句事务(BEGIN 报错),极少在业务系统中使用。

事务级连接池(Transaction Mode)的四大踩坑限制(高区分度)

面试官追问:“如果项目接入了 PgBouncer 的事务模式,哪些写法的代码会失效或踩坑?”

  1. 会话级环境变量失效:SET timezone = 'UTC' 在归还连接后会被下一个事务继承或重置;解决:使用事务级临时参数 SET LOCAL;
  2. 禁用会话级咨询锁:不能使用 pg_advisory_lock(可能释放给了别人的连接),必须改用事务托管的 pg_advisory_xact_lock;
  3. 不能使用 LISTEN / NOTIFY:监听机制必须绑定长期会话;
  4. 跨事务预编译语句(Prepared Statements):早期的事务池不支持命名 prepared statement(因为下一条执行可能分发到了另一个 backend),需在应用层开启简单查询模式或升级 PgBouncer 1.21+ 协议级支持。

三、内存架构:共享内存 vs 私有内存

PostgreSQL 内存结构分层

1. shared_buffers 为什么官方推荐只配 25% 物理内存?

这是大厂面试中最考验系统常识的一道题。很多应届生按照传统习惯回答“配置 70%~80%”,当场扣分。

底层原因:PostgreSQL 采用了基于操作系统页缓存(OS Page Cache)的“双缓冲(Double Buffering)”协同模型!

  • PostgreSQL 默认使用标准系统调用(read() / write()),数据首先从磁盘读入操作系统的 OS Page Cache,然后再由内核拷入 PG 的 shared_buffers。
  • Linux 操作系统的内核页缓存算法极其强大,拥有激进的顺序预读(Read-ahead)与干净的后台异步刷新能力。
  • 如果将 shared_buffers 设得太大(如超过 50%),会导致:
    1. 相同的数据在 shared_buffers 和 OS Page Cache 中被双重缓存,严重浪费内存;
    2. 写脏页时,操作系统与 PostgreSQL checkpointer 发生激烈的双重锁竞争与刷盘震荡;
    3. 留给后端进程的私有内存(work_mem)过小,极易引爆 Linux OOM Killer。
  • 最佳实践:通常将其设置为宿主机物理内存的 25%(最大一般不超过 16GB~32GB),剩余大部分内存留给操作系统 Page Cache 与进程排序内存。

2. work_mem 的“乘数爆炸”与 OOM 规避

  • 核心定义:work_mem 控制执行 ORDER BY、DISTINCT、Hash Join、Merge Join 时所允许消耗的最大内部内存。
  • 致命陷阱:work_mem 不是一个连接分配一份,而是每个查询的每个操作节点都会独立分配一次!
    • 例如一条复杂的分析 SQL 包含 3 个 Hash Join 和 2 个 Sort 节点,该单条 SQL 就会同时申请 5 × work_mem 的内存!
    • 如果连接池有 100 个并发连接都在跑类似查询,内存消耗为: 100 × 5 × work_mem
    • 若管理员盲目将全局 work_mem 设为 128MB,瞬间申请内存将达到 64GB,直接把操作系统挤爆宕机!
  • 生产调优法则:
    • 全局默认值保持克制,建议设为 4MB ~ 16MB;
    • 针对个别需要大量排序的报表或批量处理脚本,仅在特定会话内局部调大:
      BEGIN;
      SET LOCAL work_mem = '256MB';
      -- 执行耗费大量内存的复杂报表查询
      SELECT ... ORDER BY ...;
      COMMIT; -- 事务结束自动恢复默认

四、一条 SQL 在 PostgreSQL 中的执行全生命周期

  1. 建立通信:客户端发起 TCP 握手,postmaster 接收并 fork() 出后端进程(或通过 PgBouncer 快速复用现有进程);
  2. 解析阶段(Parser):检查 SQL 语法,将文本转换为抽象语法树(Parse Tree);
  3. 重写阶段(Rewriter):应用系统视图(Views)与规则系统(Rules)将抽象树展开;
  4. 规划与优化(Planner):代价优化器(Cost-based Optimizer) 根据统计信息枚举各种扫描方式(Seq Scan / Index Scan)与连接算法(Nested Loop / Hash / Merge),计算出预估代价(Cost)最小的执行计划树;
  5. 执行器(Executor):按照**火山模型(Volcano Iterator Model)**自顶向下逐层拉取元组,若命中 shared_buffers 则直接在内存读取,未命中则请求 OS 触发磁盘 I/O;
  6. 响应客户端:将结果集打包为 PostgreSQL Wire Protocol 格式,沿 Socket 流回客户端。
Navigation

Type to search…

↑↓ navigate↵ selectEsc close