最近,我在研究一些产生大量 I/O 和 CPU 争用的高写入代码路径的工作负载时,发现了 synchronous_commit(PostgreSQL 文档链接)。由于在 I/O、CPU 和每秒事务数(TPS)方面的性能提升非常明显,因此非常诱人去全局禁用它。我注意到 I/O 完全消失了,CPU 在峰值时降低了 20%,TPS 提高了 30%。然而,这也伴随着一些重要的权衡,值得我们牢记。
什么是同步提交?
默认情况下,PostgreSQL 使用同步提交,这意味着当你的应用程序提交一个事务时,PostgreSQL 会:
- 将事务的更改写入预写日志(WAL)
- 将这些 WAL 记录刷新到永久存储中
- 然后才向客户端确认成功
这确保了事务的持久性——即使数据库在提交后立即崩溃,你的事务仍然安全。然而,这种磁盘 I/O 操作通常是事务吞吐量的瓶颈,尤其是对于频繁的小事务。
异步提交登场
启用异步提交后,PostgreSQL 会在事务逻辑完成时立即确认事务成功,而不是等到 WAL 记录刷新到磁盘之后。
-- 启用异步提交 SET synchronous_commit = off; -- 恢复默认(同步) SET synchronous_commit = on;
复制
结果如何?事务吞吐量显著提高,I/O 压力也降低了。在我的测试中,这个简单的更改使每秒事务数增加了 30%,尤其是在 I/O 受限的系统上。
权衡:了解风险窗口
性能提升是有代价的:在事务被报告为已提交和实际写入磁盘之间存在一个短暂的“风险窗口”。如果数据库服务器在这个窗口期间崩溃,最近的事务可能会丢失。这里的风险是数据丢失,而不是数据损坏。PostgreSQL 文档用非常通俗易懂的语言解释了这一点:PostgreSQL 文档链接。
灵活的实现方式
即使测试了 synchronous_commit 的其他设置后,我发现这个功能的美妙之处在于,你不必做出全局性的全有或全无的选择。你可以:
- 按会话切换
- 按事务切换
- 为特定操作切换
这允许一种细致入微的方法,即关键事务保持完全持久性,而不太关键的操作则获得性能提升。
在 Ruby on Rails 应用程序中,这可以像下面这样简单:
def with_synchronous_commit_off(&block) ActiveRecord::Base.connection.exec_query("SET synchronous_commit = off") yield ensure ActiveRecord::Base.connection.exec_query("SET synchronous_commit = on") end with_synchronous_commit_off do # 在这里执行非关键的批量操作 # 例如,分析数据、日志或后台处理 end
复制
中间设置
PostgreSQL 为 synchronous_commit 提供的不仅仅是开/关两种选项。还有中间设置,可以在性能和持久性之间提供不同的平衡:
-- 从最强保证到最高性能的选项: SET synchronous_commit = 'remote_apply'; -- 最强保证(适用于副本) SET synchronous_commit = 'remote_write'; -- 较强但更快 SET synchronous_commit = 'local'; -- 仅本地持久性 SET synchronous_commit = 'off'; -- 最高性能
复制
然而,在 Aurora PostgreSQL 上,我发现将其设置为 OFF 时收益最大。我认为,由于 Aurora 的工作方式以及其要求 6 个节点中有 4 个需要确认提交(AWS 博客链接),其他设置可能没有太多作用,或者它们的收益被稀释了。
总结
我意识到,对于经验丰富的 PostgreSQL 用户来说,这可能是一个众所周知的设置。尽管如此,我希望这篇文章对你有所帮助,我很想听听你在生产环境中使用 synchronous_commit 的经验。
原文地址:https://www.shayon.dev/post/2025/75/selective-asynchronous-commits-in-postgresql-balancing-durability-and-performance/
原文作者: Shayon Mukherjee