为什么 Postgres 需要 NVMe 处理热路径,而 S3 处理其他一切内容

3 阅读6分钟

\n\n文章探讨了 Postgres 的存储策略,指出由于事务提交和随机读取对延迟极度敏感,必须在热路径使用 NVMe,而将 S3 用于备份和冷数据,通过冷热分离平衡性能与成本。

译自:Why Postgres wants NVMe on the hot path, and S3 everywhere else

作者:Alasdair Brown

人们总是试图将两种截然不同的存储任务合二为一。S3 持久、廉价且几乎无穷无尽,因此诱惑显而易见:为什么不把所有东西都放进去?

运行 Postgres 的难点不在于存储大量字节。难点在于如何度过数据库必须停下来等待的时刻。

为了实现持久化提交,Postgres 必须在告知客户端事务完成之前刷新 WAL。这种刷新发生在 XLogFlush() 中,后端会一直阻塞,直到内核确认写入已持久化。在一个带有掉电保护的优秀企业级 NVMe 驱动器上,这可能需要几十微秒。在较慢的网络存储上,这会变成毫秒级。如果在该路径中放入任何类似对象存储的东西,差距会变得更加糟糕。

这种差异至关重要,因为提交延迟不是一个抽象的基准测试数字。对于负载较轻的 OLTP 系统,它为单个会话提交事务的速度设定了真实上限。当多个会话同时提交时,组提交(Group commit)会有所帮助,因为多个事务可以共享一次刷新。但许多生产应用程序并非整天运行在那种理想状态下。在低并发情况下,存储延迟会直接转化为用户可见的响应时间。

在最近关于托管 Postgres 服务的基准测试工作中,你可以看到同样的模式:一旦工作负载溢出内存,拥有更快本地存储的系统通常会表现出更明显的优势。

“对于 Postgres,fsync 是一个承诺,而不仅仅是一次写入。”

这里的 SSD 表现也各不相同。对于 Postgres,fsync 是一个承诺,而不仅仅是一次写入。具有掉电保护的企业级驱动器通常可以更早地确认该承诺,因为写入受到电容支持的缓存保护。消费级 SSD 通常没有足够的空间来安全地做到这一点。这就是为什么两块在纸面上看起来都“很快”的驱动器,在重提交工作负载下的表现可能大相径庭。

同样的问题也出现在读取上。Postgres 将堆数据和索引存储在 8 KB 的页面中。一旦错过缓冲区缓存,后端就会阻塞在小页面读取上。OLTP 工作负载经常这样做:索引查询、堆获取、可见性检查,然后是更多的索引查询。NVMe 擅长处理这些。对象存储则不然。问题不在于带宽。而在于 Postgres 需要大量微小的、对延迟敏感的读取,而 S3 是围绕较大、高延迟的对象请求构建的。

“问题不在于带宽。而在于 Postgres 需要大量微小的、对延迟敏感的读取,而 S3 是围绕较大、高延迟的对象请求构建的。”

一旦工作集不再能舒适地放入内存,这种不匹配就会变得更糟。shared_buffers 和操作系统页面缓存会有很大帮助,但也只能到一定程度。当数据库在热查询上开始出现缓存未命中时,底层存储的延迟就不再是后台细节,而成为了工作负载本身。

MVCC 增加了其特有的 I/O 放大效应。更新操作不会原地覆盖行;它会创建一个新的元组版本并更新受影响的索引。检查点(Checkpoints)将全页写入带入 WAL 流。提示位(Hint bits)可以将读取转变为写入。清理(Vacuum)最终必须清理死元组并控制事务 ID 的增长。这些都不是偶然的。这是 Postgres 实现并发和崩溃安全的一部分。这也意味着 Postgres 严重依赖于能够吸收大量细碎、分散 I/O 而不崩溃的存储。

这就是为什么依赖对象存储的现代托管 Postgres 系统不会将对象存储直接放在热路径上。实现方式各不相同,但模式相当一致:在数据库附近保留一个快速日志、缓存或页面服务层,并将较冷或可重构的状态推送到更持久的远程层。有趣的不是品牌,而是这种趋同性。如果你观察严肃的事务型 PostgreSQL 设计,它们总是在寻找方法来保护提交路径免受对象存储延迟的影响。

最近的上游工作也指向了同一个方向。PostgreSQL 18 增加了异步 I/O 支持,并提高了对并发存储访问的预期。这项工作是为了让 Postgres 更好地驱动快速存储,而不是为了让对象存储表现得像本地 SSD。Postgres 越擅长发布并行低延迟 I/O,就越能从 NVMe 和其他能够快速且可预测响应的存储中获益。

这并不是对 S3 的抨击。S3 在其构建的任务中表现出色:WAL 归档、基础备份、快照、保留以及为下游分析系统提供数据。它也非常适合复制和迁移工作流中较冷的一侧,无论是初始加载、回填,还是大规模切换到另一个系统。操作技巧在于不要让这些任务进入提交路径。使用 CDC 流水线或计划大规模 Postgres 迁移的团队,通常解决的是与事务提交完全不同的问题。

同样的这种分离也有助于分析。Postgres 非常擅长处理事务,但一旦你开始要求它在处理提交、清理和缓存未命中的同一台机器上运行大型扫描和聚合,资源争用就会迅速显现。这就是为什么大量的工程努力都投入到将分析工作移出 OLTP 路径,无论是通过复制还是为 Postgres 和分析建立独立的开源技术栈。目标不是让 Postgres 做得更少,而是让它继续擅长它已经做得很好的事情。

所以答案不是“NVMe 还是 S3”,而是两者兼而有之,并在它们之间划清界限。让快速的本地或块存储处理提交、缓存未命中、检查点和清理。让对象存储处理归档、备份和较冷的历史数据。当热路径处于微秒级,而冷路径用于持久性时,Postgres 表现得非常好。当这两个任务被强行塞进同一层时,它就开始变得挣扎。全 端 工智能