百万连接洪峰,PostgreSQL 的 postmaster 扛不住了
多数人选择接受 Postgres 的局限,用中间件和解耦架构去消化问题;少数人认为应该刨根问底,推动内核演进。Recall.ai 的调查之所以引发共鸣,是因为它把「连接池是标配」这个共识背后的技术债翻出来晒了晒。有评论总结得最好:「有时候智慧比努力更重要。」与其花几个月调试 postmaster,不如一开始就设计成分布式架构,或者干脆用云原生托管服务(RDS Proxy)把问题外包出去。但另一方面
你的会议机器人一到整点就集体卡壳,像被按了暂停键。更诡异的是,TCP 握手毫发无伤,数据包在毫秒间完成往返,但 PostgreSQL 偏偏要等上十秒才肯回应你的连接请求。这不是网络故障,也不是磁盘慢了,而是藏在数据库内核深处的一个单线程循环,在连接洪峰来临时被活活累到虚脱。
这是一家叫 Recall.ai 的公司最近踩到的坑。他们每周录制数百万场会议,服务特性决定了负载像悬崖峭壁——会议大都整点或半点开始,数千台 EC2 实例必须在同一秒内就位,否则实时媒体流就永远丢失了。他们的排查过程最终指向 PostgreSQL 那个叫「postmaster」的「总管」进程,以及它那个仿佛活在九十年代的单线程主循环。
当连接洪峰撞上单线程接待员
Postmaster 的工作听起来简单:新连接来了,fork 一个后端进程去处理;进程死了,回收资源。问题出在它的实现方式——整个逻辑跑在一个 CPU 核里,事件处理是同步的。Recall.ai 的压测显示,在 r8g.8xlarge 实例上,每秒 1400 次连接就能把 postmaster 的主循环塞满,CPU 占用直接冲顶。
搞笑的是,fork 本身并不是元凶。Linux 内核早就用写时复制(Copy-on-Write)优化了内存页拷贝,但父进程的页表项(PTE)还是得逐一复制。Postgres 社区的老生常谈是「fork 慢」,所以开几百个连接就要上连接池。但 Recall.ai 通过启用大页面(Huge Pages)把 PTE 数量压下去后,连接吞吐量直接提升了 20%。这证明瓶颈不在 fork 系统调用,而在于 postmaster 那个忙不过来的单核大脑——它要在同一时间处理连接请求、回收僵尸进程、启动并行查询工人,所有任务挤在一个队列里。
评论区里有句吐槽很扎心:「折腾几个月,最后绕回『加 jitter』和『消除并行查询』这两句废话。」确实,Recall.ai 的解决方案看起来平淡无奇:给 EC2 启动加随机延迟,分散连接尖峰;再干掉几个会触发并行计划的 SQL。但问题是,为什么在看似完美的监控体系下,这个瓶颈能藏得这么深?
被扭曲的生态与被忽视的内核
PostgreSQL 的文档和最佳实践里,连接池(pgbouncer、RDS Proxy)几乎是标配。但 Recall.ai 的文章指出,这形成了一种「人造限制」——开发者被迫在中间加一层代理,不是因为架构优雅,而是因为 postmaster 无法充分利用现代多核硬件。有评论说得好:「这不正是 pgbouncer 要解决的问题吗?」没错,但作者想说的是:我们集体接受了这个 workaround,却没人去质问 why。
更讽刺的是,Recall.ai 在 RDS 上复现问题时,发现云厂商根本没提供 postmaster 争用的监控指标。你能在仪表盘里看到每秒查询数、缓存命中率,但看不到那个单线程主循环是不是 CPU 100% 了。一位评论者调侃:「说实话,居然没有任何 DBaaS 或监控工具提供 postmaster 争用的可观测性,这有点荒谬。」
生态就这样被扭曲了。ProxySQL 最初为 MySQL 设计,2024 年才加入 Postgres 支持,结果成了救命稻草。PgCat、PgDog 这些新项目也都在试图用中间件层绕开原生限制。就像作者说的,这是「一个令人造约束扭曲了开发者生态形状」的经典案例。
抖动、并行查询与那些没说出口的细节
Recall.ai 的「抖动」方案在评论区引发了不少追问:到底是让 EC2 实例随机延迟多久?几十毫秒还是几秒?文章没细说。同样含糊的是「消除并行查询」——是改 SQL Hint,还是直接关掉 max_parallel_workers?有读者尖锐指出:「他们花了大量篇幅讲底层原理,最后给出两个模糊的建议,像是从草稿箱里直接发布的。」
一位资深工程师用 Chesterton’s Fence 定律反驳:「当初他们把 max_connections 调到几千的时候,怎么不想想默认值为啥那么低?」确实,如果早点理解 Postgres 进程模型的局限,可能根本不会走到「每秒几千新连接」这一步。
但也有人为作者辩护:不是所有人都能控制上游负载。会议开始时间由客户决定,你不能强制把 10:00 的会议改到 10:07。抖动只能在实例启动阶段做,真正的业务峰值无法避免。这种「可控范围外的突发」,恰恰是 PostgreSQL 架构最不擅长的场景。
更深层的架构拷问
当讨论陷入「要不要用连接池」的循环时,有评论者把话题引向更根基的层面:「既然会议数据是写一次读多次,为啥不直接存 S3,非要实时写数据库?」的确,JSON 结构化日志、WAL 归档这类场景,用对象存储加批量导入可能更契合。但现实中,能稳定实现「先写本地再批量上传」的系统设计,比直接用数据库复杂得多。尤其当你有 50 个不同团队的后端服务在同时写入时,用 S3 当主存储的协调成本会飙升。
另一个被反复提及的类比是 Python GIL(全局解释器锁):单线程事件循环可以很快,但一旦被某个重操作卡住,整个系统都受影响。Postmaster 就是 Postgres 的 GIL。好的一面是,社区已经在推进多线程化改造(PostgreSQL wiki 上有详细路线图),异步 I/O 在 v18 的进展也证明 Postgres 能进化。坏的一面是,作为 30 年历史的老项目,这种底层重构注定缓慢,可能要等 2-3 年才能见到实质性成果。
OrioleDB 这个插件也被点名——它用自定义存储引擎绕过了不少 Postgres 原生限制,但一直没被官方收编。评论区有人感叹:「要是 OrioleDB 的改进能合并进主分支就好了。」
那些散落在讨论里的技术彩蛋
-
权限乌龙:原文里
sudo echo $NUM_PAGES > /proc/sys/vm/nr_hugepages被指出根本行不通——重定向由 shell 执行,sudo 管不了它。正确写法是echo $NUM_PAGES | sudo tee /proc/sys/vm/nr_hugepages。这个小细节引来十几条关于tee、dd(磁盘毁灭者)和pee(moreutils 工具)的玩梗。 -
vCPU 的陷阱:Recall.ai 用的是 32 vCPU 的 r8g.8xlarge 实例。但有评论提醒,vCPU 只是物理核的时间片,云厂商恨不得超售到飞起。实际物理机可能根本不受限。这也解释了为何压测结果在不同环境下差异巨大。
-
调度玄学:有人分享 Jenkins 的 “H” cron 语法,用哈希函数把定时任务随机化,避免「惊群效应」。systemd 的
RandomizedDelaySec=同理。但会议业务天然反随机化——客户就是要整点开始。 -
连接池悖论:PgBouncer 这类工具确实能缓冲洪峰,但它自己也得监控、扩缩容,还增加了延迟。更麻烦的是,会话级变量、预备语句(prepared statements)这些 Postgres 特性在连接池里可能行为异常。就像一位评论者说的:「人们不会某天醒来就随机给基础设施加齿轮。」
结语:接受现实还是推动变革?
这场讨论的底色其实很现实:多数人选择接受 Postgres 的局限,用中间件和解耦架构去消化问题;少数人认为应该刨根问底,推动内核演进。Recall.ai 的调查之所以引发共鸣,是因为它把「连接池是标配」这个共识背后的技术债翻出来晒了晒。
有评论总结得最好:「有时候智慧比努力更重要。」与其花几个月调试 postmaster,不如一开始就设计成分布式架构,或者干脆用云原生托管服务(RDS Proxy)把问题外包出去。但另一方面,如果所有开发者都放弃理解底层,Postgres 就永远只能「将就着用」,无法释放现代硬件的全部潜力。
Postgres 的进程模型像一辆手动挡老爷车,在自动驾驶时代依然能跑,但你需要知道怎么换挡、怎么听发动机的声音。Recall.ai 的经历提醒我们:当你的业务出现「整点尖峰」这种违背云原生假设的负载特征时,那些藏在内核深处的单线程循环,可能就是你下一个十万火急的故障根因。

更多推荐
所有评论(0)