full_page_writes 看起来像一个直接的性能开关:关闭后 WAL 变小,写入量也可能下降。但它承担的职责并不是普通的日志优化,而是避免 PostgreSQL 在崩溃恢复时把一张“撕裂页”误认为有效数据页。除非你能够严格证明底层存储能保证原子页写入,否则关闭它通常是在用恢复可靠性换取一部分 I/O。
崩溃时真正棘手的问题:torn page
PostgreSQL 的表和索引由固定大小的数据页组成,默认页面大小通常是 8 KiB。一次更新可能同时涉及两类写入:
- WAL:描述这次修改应该如何重做。
- 数据文件页:保存修改后的实际内容。
数据库依赖 WAL 的先写原则:相关 WAL 必须先于脏页落盘。这样即使进程或主机突然终止,恢复流程仍能根据 WAL 重放已提交或已持久化的变更。
问题在于,操作系统或存储设备未必能把一个 PostgreSQL 数据页作为不可分割的整体写入。假设数据库准备把某个 8 KiB 页面从版本 A 写成版本 B,机器恰好在写入中途断电,磁盘上可能留下“前半部分来自 A、后半部分来自 B”的内容。这就是 torn page,或者说部分写入页。
WAL 中通常只记录增量操作,例如“修改第几个元组”“在页内写入哪些字节”。如果恢复时面对的是一个已经损坏的页面,增量重放未必能把它修回正确状态,甚至会在一个逻辑上不成立的页结构上继续操作。
full_page_writes 如何补上恢复所需的基线
启用 full_page_writes 后,PostgreSQL 会在每次 checkpoint 之后,首次修改某个数据页时,向 WAL 写入该页的完整镜像(full-page image,FPI),同时记录正常的增量变更。
恢复期间,如果数据库发现需要重放这条带有完整页镜像的 WAL 记录,就能先用镜像覆盖可能被部分写入的数据页,再继续重放后续记录。它解决的关键不是“每一条修改都备份整页”,而是为 checkpoint 之后第一次触及的页面建立一个可信的恢复起点。
这也解释了它的典型成本特征:
- checkpoint 后的一段时间,首次修改大量不同页面时,WAL 会明显膨胀。
- 热点页被反复更新时,通常不会每次都重复产生完整页镜像。
- 大范围更新、批量导入、索引重建等工作负载更容易产生大量 FPI。
- 更频繁的 checkpoint 会缩短“首次修改”的周期,可能增加完整页镜像的出现频率。
因此,看到 WAL 在 checkpoint 后增长,并不自动代表配置错误。它可能只是数据库在为下一次崩溃恢复保留足够的页级证据。
不要把“有 WAL”误解成“可以关闭完整页写入”
关闭 full_page_writes 后,WAL 仍然存在,事务提交、复制和大多数恢复逻辑也仍然依赖 WAL。失去的是一种重要前提:恢复过程不再有能力可靠覆盖一个被部分写坏的页面。
风险往往不止体现在立刻启动失败。更麻烦的情形是数据库能够启动,但某个表或索引页的内部结构已损坏,问题只会在后续扫描、真空清理、备份或查询特定数据时暴露。
以下情况尤其不适合关闭:
- 不确定块设备是否提供与 PostgreSQL 页面大小匹配的原子写入保证。
- 运行在虚拟化、网络存储、云块存储或多层缓存路径上,且无法验证断电语义。
- 数据库承担生产交易、主从复制、归档恢复或时间点恢复职责。
- 团队没有经过演练的损坏检测、备份和恢复流程。
即使存储厂商声称具有掉电保护或原子写入能力,也应把它当作需要验证的工程假设,而不是根据营销术语直接修改生产参数。
先观察,再决定是否优化 WAL 体积
可以用以下命令检查当前设置和 checkpoint 相关参数。SHOW 不需要重启,也适合在生产环境先做只读确认。
SHOW full_page_writes;
SHOW wal_compression;
SHOW checkpoint_timeout;
SHOW max_wal_size;
SHOW shared_buffers;
查看完整来源和当前生效值:
SELECT
name,
setting,
unit,
source,
pending_restart
FROM pg_settings
WHERE name IN (
'full_page_writes',
'wal_compression',
'checkpoint_timeout',
'max_wal_size'
)
ORDER BY name;
如果问题是归档带宽、复制链路或 WAL 磁盘空间,而不是存储可靠性,优先评估 wal_compression。它可以压缩 WAL 中适合压缩的完整页镜像,通常是比关闭 full_page_writes 更稳妥的选择。
下面的配置示例可作为测试环境的起点。将其写入 postgresql.conf,或通过受控的配置管理系统发布;修改后应按版本和参数要求 reload 或 restart。
# 保留崩溃恢复对完整页镜像的保护
full_page_writes = on
# 降低完整页镜像带来的 WAL 传输和归档体积
wal_compression = on
# 示例值:应依据写入压力、恢复目标和磁盘容量调优
checkpoint_timeout = '15min'
max_wal_size = '8GB'
配置变更前后,可以在相近业务窗口比较 WAL 生成速率、checkpoint 行为、恢复时间和复制延迟。不要只看单一的 WAL 字节数,因为把 checkpoint 拉得过长虽然可能减少某些开销,也会扩大崩溃恢复时需要处理的范围。
采用建议:把它视为数据完整性配置
full_page_writes = on 应是绝大多数 PostgreSQL 部署的默认决策。它的额外 WAL 成本是真实存在的,但它覆盖的是最难处理的一类事故:异常中断后,页面只写入了一部分。
在考虑关闭前,至少完成这份检查:
- 明确存储栈对原子写入和断电场景的保证,并在实际部署形态中验证。
- 在隔离环境做故障注入或崩溃恢复演练,而非只运行正常压测。
- 验证备份、归档 WAL、复制和恢复流程能够发现并处理页损坏。
- 优先尝试
wal_compression、checkpoint 参数和批处理策略等风险更低的优化手段。
对大多数团队而言,完整页写入不是可以随意削减的 WAL 冗余,而是 PostgreSQL 在不可信的故障时刻仍能恢复到一致状态的一层保护。