/ Shayon Mukherjee / blog

PostgreSQL 中选择性异步提交:平衡持久性和性能

2025 年 3 月 16 日 ~ 3 分钟

最近,我研究了一些在高写入代码路径上产生大量 I/O 和 CPU 竞争的工作负载,并发现了 synchronous_commit (https://www.postgresql.org/docs/current/wal-async-commit.html)。 全局关闭此设置可能非常诱人,因为在 I/O、CPU 和 TPS(每秒事务数)方面的性能提升非常显著。我注意到 I/O 完全消失,CPU 下降 20%(在峰值时),TPS 增加 30%。但是,这带来了一些重要的权衡,值得记住。

什么是同步提交?

默认情况下,PostgreSQL 使用同步提交,这意味着当您的应用程序提交事务时,PostgreSQL 会:

  1. 将事务的更改写入预写日志 (WAL)
  2. 将这些 WAL 记录刷新到永久存储
  3. 仅在此时 向客户端确认成功

这确保了持久性 - 如果数据库在提交后立即崩溃,您的事务仍然安全。但是,此磁盘 I/O 操作通常是事务吞吐量的瓶颈,尤其是对于小型、频繁的事务。

进入异步提交

异步提交改变了这种行为。启用后,PostgreSQL 在事务逻辑上完成后立即确认事务成功, WAL 记录刷新到磁盘之前

-- 启用异步提交
SET synchronous_commit = off;
-- 恢复默认(同步)
SET synchronous_commit = on;

结果是?事务吞吐量显着提高,I/O 压力降低。在我的测试中,这个简单的更改使每秒事务数增加了 30%,尤其是在 I/O 受限的系统上。

权衡:理解风险窗口

性能提升伴随着权衡:在事务报告为已提交和实际写入磁盘之间存在一个小的“风险窗口”。如果数据库服务器在此窗口期间崩溃,则最近的事务可能会丢失,这就是全局启用此设置感觉不合适的原因。 这里的风险是数据丢失,而不是数据损坏。PostgreSQL 文档以非常简单的术语很好地解释了这一点:https://www.postgresql.org/docs/current/wal-async-commit.html

选择性实施

即使在测试了 synchronous_commit 的其他设置之后,我发现此功能的优点在于您不必做出全局的“全有或全无”的选择。 您可以切换它:

这允许采用细致入微的方法,其中关键事务保持完全持久,而不太关键的操作获得性能提升。

在 Ruby on Rails 应用程序中,可以像这样简单地操作:

defwith_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 个需要确认提交 (https://aws.amazon.com/blogs/database/amazon-aurora-postgresql-parameters-part-2-replication-security-and-logging/),其余设置可能没有做太多,或者它们的优点可能会被摊销。

总结

我意识到这对于经验丰富的 PostgreSQL 用户来说可能是一个非常众所周知的设置。 也就是说,我希望您发现这篇文章有用,并且我很乐意听到您在生产中使用 synchronous_commit 的经验。