fsync 默认开启,看起来只是 postgresql.conf 中一个普通的布尔参数,实际却守着 PostgreSQL 的持久性底线。查询规划参数配错,常见后果是 SQL 变慢;fsync 配错,在断电、操作系统崩溃或宿主机故障后,可能留下无法恢复的数据损坏。
fsync 究竟保证了什么
PostgreSQL 修改数据时,不会让每个事务直接重写完整的数据文件。它先更新共享缓冲区,并把恢复所需的信息写入 WAL。事务提交、检查点和后台写入过程再按照既定顺序,把 WAL 与数据页交给操作系统。
问题在于,操作系统报告一次普通的 write() 成功,通常只代表数据进入了内核页缓存,并不等于数据已经落到非易失存储介质。fsync=on 时,PostgreSQL会在需要建立持久性边界的位置要求操作系统刷新数据,并维持恢复依赖的写入顺序。
如果关闭它,数据库仍然可能正常启动、接受写入并通过日常查询。风险通常要等到突然掉电或系统崩溃才显现:
- WAL 可能看起来已经提交,但对应数据页的写入状态不符合恢复假设。
- 数据页、索引页与事务状态可能处于彼此不一致的持久化时点。
- 重放 WAL 不一定能修复这种写入顺序被破坏后的状态。
- 备库和备份不能自动挽救尚未可靠落盘、却被当作已提交的数据。
因此,这不是“愿意牺牲一点可靠性换速度”那么简单。关闭 fsync 会改变数据库能够承诺的故障恢复边界。
先检查,不要凭印象判断
可以这样实践:先从正在运行的实例读取实际值、配置来源和参数上下文。将连接串替换为自己的管理连接。
export DATABASE_URL='postgresql://postgres@127.0.0.1:5432/postgres'
psql "$DATABASE_URL" -X -v ON_ERROR_STOP=1 <<'SQL'
SELECT name,
setting,
source,
sourcefile,
sourceline,
context,
pending_restart
FROM pg_settings
WHERE name IN (
'fsync',
'synchronous_commit',
'full_page_writes',
'wal_sync_method'
)
ORDER BY name;
SHOW data_directory;
SHOW config_file;
SQL
这里不能只看某一份模板中的 postgresql.conf。参数可能来自启动命令、发行版生成的配置、ALTER SYSTEM 写入的 postgresql.auto.conf,或者运维平台注入的设置。source、sourcefile 和 sourceline 能帮助定位真正生效的来源。
同时不要混淆几个名字相近但边界不同的参数:
fsync控制 PostgreSQL 是否要求内核可靠刷新,并关系到整个实例的崩溃恢复安全。synchronous_commit影响事务向客户端报告提交成功前等待到什么程度,可按事务调整。full_page_writes用于降低崩溃期间页面部分写入带来的风险,不是fsync的替代品。wal_sync_method选择具体的 WAL 同步方式,需要结合操作系统和存储测试。
发现 fsync 关闭后,怎样恢复到安全状态
不要只在高流量实例上改成 on,看到配置值变化就宣布完成。关闭期间,操作系统可能仍保存着尚未可靠刷新的脏页;恢复持久性保证时,需要把维护窗口和底层存储一起纳入操作。
可以采用下面的保守流程。具体服务名和 PostgreSQL 二进制目录需按环境修改:
# 1. 确认当前值与配置来源
psql "$DATABASE_URL" -X -Atc \
"SELECT setting || ' source=' || source || ' file=' || coalesce(sourcefile, '<none>') FROM pg_settings WHERE name='fsync';"
# 2. 写入受 PostgreSQL 管理的配置覆盖
psql "$DATABASE_URL" -X -v ON_ERROR_STOP=1 -c \
"ALTER SYSTEM SET fsync = 'on';"
# 3. 安排维护窗口,停止数据库,避免继续产生写入
sudo systemctl stop postgresql
# 4. 要求操作系统刷新仍在缓存中的数据
sync
# 5. 启动并复核实际值;服务名可能包含版本或集群名
sudo systemctl start postgresql
psql "$DATABASE_URL" -X -v ON_ERROR_STOP=1 -c \
"SELECT name, setting, source, pending_restart FROM pg_settings WHERE name='fsync';"
这是一份可改造的通用示例,不是所有部署方式的固定操作手册。Kubernetes、云托管数据库、BSD、macOS 和不同 Linux 发行版的停止方式不同;某些环境也不允许执行 ALTER SYSTEM。应先查看当前 PostgreSQL 版本中 pg_settings.context 的结果,并遵守平台的重载或重启流程。
更重要的是,fsync=on 只能要求操作系统完成持久化,不能让不诚实的存储变诚实。虚拟磁盘、RAID 控制器、消费级 SSD、网络块存储或宿主机缓存如果错误地确认 flush 请求,数据库仍可能丢失数据。应确认写缓存是否有电池或断电保护,并检查云盘对 flush、写入顺序和故障域的承诺。
性能问题不要靠关闭 fsync 解决
如果提交延迟确实集中在同步写入上,可以这样实践:在与生产相同的文件系统和存储路径上使用 PostgreSQL 自带的 pg_test_fsync 比较同步方法。请在独立测试目录中运行,不要把测试文件放进生产数据目录。
mkdir -p /var/tmp/pg-fsync-test
cd /var/tmp/pg-fsync-test
pg_test_fsync
这个工具生成的是低层同步测试结果,不等同于真实业务吞吐量。后续仍应使用代表性事务、连接数和检查点压力做基准测试。更稳妥的优化方向通常包括:
- 检查 WAL 所在卷的延迟、队列深度和抖动。
- 合并短事务,避免一次业务操作拆成大量独立提交。
- 对明确允许丢失最近事务的任务,评估会话级或事务级
synchronous_commit=off,而不是关闭实例级fsync。 - 调整检查点和 WAL 相关配置前,先采集
pg_stat_bgwriter、pg_stat_wal与存储指标。 - 通过故障注入或断电测试验证整条存储链路,而不只相信顺序写带宽。
例如,只对可重建的批处理事务放宽提交等待,可以明确限定风险范围:
BEGIN;
SET LOCAL synchronous_commit = off;
-- 仅放置能够从上游重新生成的数据写入
INSERT INTO rebuildable_daily_summary(day, total)
VALUES (CURRENT_DATE, 0)
ON CONFLICT (day) DO UPDATE
SET total = EXCLUDED.total;
COMMIT;
这并不等于事务失去原子性,也不代表它一定会丢失;它表示服务器可能在 WAL 真正持久化前向客户端报告提交成功。系统崩溃时,最近一小段已确认事务可能消失,所以不能用于账务、订单状态或其他无法重建的数据。
上线前的判断清单
把 fsync=on 视为生产实例的硬性默认值,并在配置审计中持续检查,而不是只在建库时看一次。上线或迁移前至少确认:
- 运行实例报告
fsync=on,并且配置来源符合预期。 - 底层存储正确实现 flush 和写入顺序语义。
- RAID 或设备写缓存具备可靠的断电保护。
- 性能测试覆盖事务提交延迟、检查点压力和高峰并发。
- 备份完成过恢复演练,副本也接受过故障切换验证。
- 任何持久性降级都有明确的数据范围、审批记录和恢复方案。
fsync 的危险之处在于,错误配置往往不会立即报警。数据库可能平稳运行数月,直到一次基础设施故障把隐藏的持久性问题一次性暴露出来。对生产 PostgreSQL,正确做法是保留 fsync=on,再从事务设计、WAL 存储和底层设备中寻找可测量、可控制的性能改进。