Postgres 不慢,慢的是你的存储
文章摘要
这是 ClickHouse 博客上的一篇演讲回顾。在 POSETTE 2026 大会上,Sai Srirampur 做了题为《Postgres 不慢,慢的是你的存储》的演讲,讨论存储对大规模 PostgreSQL 性能的影响,介绍了一组基准测试,并提出了在生产环境中把 PostgreSQL 跑在本地 NVMe 上的架构。
五个规模化症状。 当 PostgreSQL 的数据量达到几百 GB 到几 TB、并且有真实的并发和吞吐时,常会出现五种问题:一是以 UPDATE 和 UPSERT 为主的数据导入变慢,原本几秒完成的任务拖到几分钟;二是读延迟不稳定,大部分查询仍然很快,少数却慢得离谱,P95 延迟从毫秒级跳到秒级,仪表盘超时,而且这种不均匀让原因更难定位;三是 autovacuum 跟不上,表膨胀从几 GB 累积到几 TB;四是检查点(checkpoint)变慢,与业务负载争抢 I/O 和 CPU,原本无感的操作变成周期性的延迟尖峰;五是向 Kafka 或 ClickHouse 等系统做逻辑复制时,WAL 解码变慢,复制槽越积越大。这些问题不一定同源,但可能有一个容易被忽视的共同原因:存储性能。
为什么都和存储有关。 当工作集超出 PostgreSQL 和操作系统页缓存能容纳的内存时,越来越多的读取需要物理 I/O,存储越慢,这些读取的开销和排队越严重,平均延迟和尾延迟都会上升。同时,客户端查询和后台维护共用同一份存储预算:堆和索引的写入、WAL 同步、检查点、VACUUM、逻辑解码的溢写都在争抢带宽、IOPS 和队列。一旦存储触及其中某个上限,各种操作就开始互相拖累,这就是慢存储能成为多个看似无关问题根源的原因。本地 NVMe 的意义不在于消除 I/O,而在于大幅降低 I/O 的代价:网络附加存储的操作是毫秒级,本地 NVMe 可能只需几十微秒。
基准测试。 为了把存储隔离成唯一变量,Sai 搭了 8 个完全相同的 PostgreSQL 集群:4 个用实例自带的 NVMe,4 个用基线配置的 gp3 EBS(3000 IOPS)。每个集群都跑在 m6id.4xlarge 实例上(16 个 vCPU、64 GiB 内存、16 GiB shared_buffers)。数据是一张 482 GiB、33 亿行的 pgbench_accounts 表,负载是 64 个客户端、16 个工作线程的高并发随机 UPDATE,持续 5 分钟。结果:NVMe 集群的中位吞吐是 16,030 TPS,EBS 只有 1,734 TPS,相差 9.24 倍;中位事务延迟从 36.9 毫秒降到 4.0 毫秒。在维护操作上,NVMe 清理 10 GB 膨胀的 VACUUM 用了 366 秒,EBS 用了 964 秒;消化 10 GB 逻辑复制积压,NVMe 用 139 秒,EBS 用 238 秒。文章特别说明,这些结果只适用于这个负载和这些存储配置,换一种 EBS 配置结果很可能不同。
差距从哪来。 把 EBS 上 37 毫秒的事务延迟拆开看:页面读取约 19 毫秒(NVMe 上 0.3 毫秒),提交时的 WAL fsync 约 11 毫秒(NVMe 上 1.5 毫秒),锁和调度开销约 5 毫秒(NVMe 上 0.2 毫秒),而 CPU 计算本身两边差不多,都是 2 毫秒左右。也就是说,多出来的延迟绝大部分是在等存储,而不是 PostgreSQL 多干了活。运行中途的 pg_stat_activity 快照显示,64 个后端进程中,EBS 上有 29 个在等待 IO:DataFileRead,NVMe 上只有 9 个。CPU 剖析还给出一个反直觉的观察:在约 240 秒的窗口里,NVMe 主机累计用了 2,253 CPU 秒,相当于 9.4 个核在忙,EBS 主机只用了 251 CPU 秒,大约一个核。EBS 上的进程大部分时间都不在 CPU 上,而是在等存储。
持久性怎么办。 显而易见的反对意见是:实例存储的 NVMe 与机器同生共死,节点没了数据也就没了;它也不能通过 EBS API 做快照,容量还受实例类型限制。演讲的回答是:可以,只要把持久性放在集群层面,而不是依赖单个节点。具体有三条:第一,基于法定人数的同步复制,配置两个备库候选,例如 ANY 1 (standby1, standby2),任意一个备库持久确认 WAL 即可提交,不必等较慢的那个,节点跨可用区部署以抵御单个可用区故障;第二,用 WAL-G 等开源工具做物理基础备份和持续 WAL 归档,存到对象存储,RPO 可以做到秒级并支持时间点恢复,备份应放在独立的故障域,必要时再做一份跨区域副本;第三,备份不只是应急手段,同一份恢复历史还能用于时间点恢复、隔离分支、调整部署规格、初始化只读副本和灾备。备库负责快速故障转移,备份负责防范数据损坏、误操作和整个集群故障,两条恢复路径都需要定期演练。文章还引用了 Instacart(延迟敏感的搜索负载)和 Datadog 公开介绍过的本地 NVMe PostgreSQL 实践。
结论。 当 PostgreSQL 看起来不再能扩展时,应该把数据库和存储放在一起检查。导入慢、读延迟不可预测、VACUUM 压力、检查点干扰和复制延迟,都可能是存储层触顶的信号。文章也承认,真正的结论比标题要窄:本地 NVMe 能大幅降低受存储制约的负载的 I/O 代价,但它意味着另一套运维模式。文末是 ClickHouse 托管 Postgres 服务的推广。
HN 评论精华
本帖 180 分、180 多条评论。讨论围绕几个方向展开:标题是不是 AI 腔、网络存储与本地盘的取舍、「其实是你的 schema 和查询写得烂」,以及云与自建机房的老话题。
标题与 AI 腔。 amitpoonia19xyz 一上来就说,到处都是这种 LLM 生成的标题,让他即使感兴趣也读不下去。dewey 反驳说「X 不慢,慢的是 Y」这种标题格式早在 LLM 出现之前就很常见;arethuza 和 tomnipotent 则表示,现在几乎每篇投稿都有人说是 AI 写的,这种抱怨比被抱怨的内容还烦人。
网络存储还有没有存在的意义。 nasretdinov 认为网络附加存储只在机械硬盘时代才说得通,为了所谓「运维灵活性」在 IOPS 和延迟上付出 10 倍代价很荒唐,直接用本地 NVMe 加复制,架构更简单也更稳健。ngrilly 回应说,那你就得自己处理复制、故障转移和备份,而用带复制的块存储,基本可以把这些问题外包出去。演讲者本人 saisrirampur 也出现在评论区,他承认这正是在本地 NVMe 上做托管 Postgres 最棘手的部分:Postgres 已经提供了流复制、同步复制、WAL 归档和时间点恢复等基础构件,难的是让故障转移、恢复和节点替换变得无缝。CodesInChaos 希望云厂商能提供带原子快照和异步复制等增值功能的 NVMe,saisrirampur 回复「敬请期待,很快会有消息」。dikei 和 vbezhenar 为网络存储辩护:大多数应用的数据库都很小,共享存储更省钱;网络存储还能挂到 Kubernetes 上,数据库实例可以在节点间迁移,只需重新挂载。jdw64 问为什么大家默认选 EBS 而不是更快的 NVMe,nik736 解释说本地盘所在的主机一挂,虚拟机就没了,EBS 则可以在别的主机上重新启动,而且本身跨多台主机复制;rtp4me 补充说 EBS 便于随时扩容和调整性能档位,刚起步时你往往不知道需要多少。
实战数据。 iancarroll 说他们最近把 RDS 实例从 r8g 换成带本地 NVMe 的 r8gd,平均 I/O 延迟从 1.0 毫秒降到 0.1 毫秒,大查询的收益很明显。d1l 说把负载从 gp3 换到 io2 后,延迟更低、持久性更好,甚至不需要配置很高的 IOPS,整个系统都「平静」了下来。youngtaff 推荐了 Discord 那篇讲如何给网络磁盘「增压」以获得极低延迟的文章。bob1029 说 SQLite 加 NVMe 有时感觉像作弊,把 ASP.NET Core 的 Kestrel、SQLite 和 NVMe 放在同一台机器上,一次修改数据库状态的表单提交可以在几十到几百微秒内完成;tilemarch 分享了类似经验:他的多人游戏服务器用 Go 加每个游戏世界一个 SQLite 文件,512 名玩家同时在线的压测中,命令确认的中位延迟只有 4 到 6 毫秒。Havoc 则只回了一个词:「傲腾。」globnomulous 说他从三星 990 换到英特尔 P5800X 后,构建时间缩短了一半以上。
「Postgres 不慢,慢的是你的 schema 和查询」。 traceroute66 改写了标题,说他见过太多人追求「可移植」的 schema 和查询,不针对具体数据库设计,结果性能糟糕,然后跑来抱怨数据库慢,坚持非要加一层 Redis 缓存。他承认存储对某些人确实是问题,但对大多数人来说,存储在可能原因列表里排得很靠后。boxed 反驳说存储离得够远的话,好 schema 和好查询也救不了你,nijave 又反过来说 schema 和查询烂到一定程度,好存储也救不了。几位评论者分享了具体技巧:FreakLegion 推荐「列俄罗斯方块」,通过优化列的排列节省空间,好几次让他得以把 OLAP 负载留在 Postgres 里;kebsup 说他用了很久 Postgres 才搞懂「索引扫描」和「仅索引扫描」的区别,把相关列包含进索引可以避免回表随机读取;gen220 提醒这不是免费午餐,高写入系统中索引会膨胀,需要定期用 pg_repack 重建。s08148692 指出另一个现实:本地和预发环境里完美的查询计划,到了生产环境面对真实统计信息可能完全变样。youngtaff 讲了一个案例:多租户表的主键是全局自增 ID,导致每个页面里混着多个租户的数据,改成以租户为前缀的主键后 I/O 大幅减少。
云与自建机房。 mmnfrdmcx 说云极其昂贵且低效,很多工程师不明白网络存储来回的 I/O 有多慢。dikei 反问:你愿意先花几千美元买一台带 NVMe 的服务器,结果应用连 1% 都用不上就被砍掉吗?立刻有一堆人回应:可以租独立服务器,可以租带本地 NVMe 的 VPS,过去大家都是一台机器起步再按需扩容;tecleandor 说他见过每月数百万美元的云承诺消费合同,一个月的预算就足够买下能用好几年的硬件。linsomniac 则指出硬件价格变了:他们四年前花 1.4 万美元买的数据库服务器,现在要 9.6 万美元。matthorse 认为云让人们忘了计算机实际有多快,硬件规格被简化成「vCPU 个数」,网络存储成了默认选项,我们开始把它的延迟当成 Postgres 的延迟。CharlieDigital 也感叹,过去要精心规划存储阵列才能榨干数据库性能,如今有一代开发者把大内存和 SSD 视为理所当然。
也有人觉得文章没什么新意。CoolCold 说这篇文章基本上就是在教「本地存储的电脑延迟更低」,算是回归基础,但他本以为 ClickHouse 的人会讲些没那么显而易见的东西。glimshe 的总结更简短:「Postgres 不慢,只是你的数据不在缓存里。」mike_hearn 自曝在 Oracle 数据库组兼职、有利益冲突,然后借机推荐了 Oracle 的 ExaData:一整柜预先装配好的数据库硬件,内置 RDMA 背板网络和存储间复制,正是为了解决「很难自己把存储和网络调好」这个问题而生。