五月 20, 2026
摘要:在本教程中,您将了解如何在存储层启用原子写入,关闭全页写 full_page_writes,以提升 PostgreSQL 的 OLTP 处理性能。
目录
在传统 PostgreSQL 架构中,存储层保证 8KB 页面原子写入是安全关闭full_page_writes的唯一技术前提。这一方案的核心逻辑是:如果底层存储能保证 PostgreSQL 的 8KB 数据页写入操作是 “要么全部成功,要么全部失败” 的原子操作,那么 FPW 原本要解决的 “撕裂页” 故障模式就从根本上消失了。
原子写入的技术本质与 FPW 的替代关系
撕裂页产生的根本原因
PostgreSQL 默认使用 8KB 数据页,但现代存储栈是分层的:
- 应用层:PostgreSQL 8KB 页面
- 文件系统层:通常 4KB 页面(x86 架构)
- 块设备层:传统 HDD 为 512B 扇区,现代 SSD 为 4KB 物理扇区
当 PostgreSQL 执行一次 8KB 页面写入时,操作系统会将其拆分为两个 4KB 的文件系统写入请求,再进一步拆分为多个磁盘扇区写入。如果在写入过程中发生电源故障或系统崩溃,就可能只有部分数据被持久化到磁盘,形成 “半页” 损坏。
原子写入如何替代 FPW
FPW 的作用是在 WAL 中保存页面的完整副本,作为崩溃恢复时的 “安全起点”。如果存储层能保证:
任何 8KB 页面的写入操作,要么完整地将 8KB 数据全部写入磁盘,要么一点都不写,磁盘上保留原来的完整页面
那么即使发生崩溃,磁盘上的页面要么是旧的完整版本,要么是新的完整版本,永远不会出现中间状态的撕裂页。此时崩溃恢复可以直接重放 WAL 中的增量修改,无需依赖 FPW 提供的完整页面副本。
主流 8KB 原子写入存储方案详解
方案一:带电池备份单元 (BBU) 的硬件 RAID 控制器
这是企业级环境中最成熟、最可靠的方案,也是传统 DBA 最常用的选择。
工作原理
- RAID 控制器配备独立的电池备份单元 (BBU) 或闪存备份单元 (FBU)
- 当操作系统发送 8KB 写入请求时,RAID 控制器先将数据写入其自带的高速缓存
- 只要数据进入了带电池保护的缓存,控制器就会向操作系统返回 “写入完成” 的确认
- 控制器在后台将缓存中的数据批量、原子地写入物理磁盘
- 即使发生电源故障,BBU/FBU 能为缓存供电数小时,确保所有数据最终写入磁盘
关键配置要求
- 必须启用回写模式 (Write-Back)
- 禁用直写模式 (Write-Through):直写模式下数据直接写入磁盘,不经过缓存,无法提供原子性保证
- 确认 RAID 卡的缓存策略:
MegaCli -LDGetProp -Cache -LAll -aAll - 设置回写模式:
MegaCli -LDSetProp WB -LAll -aAll
- BBU/FBU 必须处于健康状态
- 定期检查电池状态:
MegaCli -AdpBbuCmd -GetBbuStatus -aAll - 当电池电量低于阈值或需要校准 (learn cycle) 时,RAID 控制器会自动降级为直写模式,此时原子性保证失效
- 建议每 3-5 年更换一次 BBU 电池
- 定期检查电池状态:
- 禁用磁盘自身的缓存
- 物理磁盘的写缓存通常不带电池保护,断电会丢失数据
- 配置命令:
MegaCli -LDSetProp -DisDskCache -LAll -aAll
验证方法
# 使用dd命令模拟8KB对齐写入,同时反复断电测试
dd if=/dev/zero of=/var/lib/pgsql/16/data/testfile bs=8k count=100000 oflag=direct,sync
# 重启后检查文件完整性
md5sum /var/lib/pgsql/16/data/testfile
优缺点
- 优点:性能优异,兼容性好,支持所有操作系统和文件系统,企业级可靠性
- 缺点:硬件成本高,依赖 BBU 电池寿命,需要定期维护
方案二:NVMe SSD 的原子写入命令
现代 NVMe SSD 原生支持原子写入命令,无需额外的 RAID 控制器即可提供原子性保证。
工作原理
- NVMe 1.1 及以上规范定义了 “原子写入单元”(Atomic Write Unit, AWU) 参数
- 大多数消费级 NVMe SSD 的 AWU 为 16KB,企业级 SSD 通常支持更大的 AWU (如 64KB、128KB)
- 只要写入请求的大小不超过 AWU 且是对齐的,SSD 固件就能保证写入操作的原子性
- 对于 8KB 的 PostgreSQL 页面,只要 SSD 的 AWU≥8KB,就能满足要求
关键配置要求
-
确认 SSD 的原子写入能力
# 查看NVMe设备信息 nvme id-ns /dev/nvme0n1 -H | grep "Atomic Write Unit"- 输出结果中 “Atomic Write Unit” 的值应≥8KB (即 16 个 512B 扇区)
-
启用文件系统的直接 I/O
- PostgreSQL 默认使用
O_DIRECT标志打开数据文件,绕过操作系统页缓存 - 确保文件系统挂载参数中没有禁用直接 I/O
- PostgreSQL 默认使用
-
禁用 SSD 的易失性写缓存
- 大多数企业级 NVMe SSD 默认禁用易失性写缓存
- 检查命令:
nvme get-feature /dev/nvme0n1 -f 0x06 - 如果启用了写缓存,需要确保 SSD 配备了掉电保护电容 (PLP)
验证方法
使用fio工具进行原子写入验证:
fio --name=atomic-write-test --filename=/var/lib/pgsql/testfile --rw=write --bs=8k --size=10G --ioengine=libaio --direct=1 --iodepth=32 --runtime=300 --time_based
在测试过程中反复断电,重启后检查文件系统和数据完整性。
优缺点
- 优点:无需 RAID 控制器,延迟更低,性能更好,功耗更低
- 缺点:依赖 SSD 固件实现,不同厂商的实现质量参差不齐,消费级 SSD 可能不支持 PLP
方案三:写时复制 (CoW) 文件系统 (ZFS、Btrfs)
写时复制文件系统的写入机制天然保证了原子性,是软件层面实现原子写入的最佳方案。
工作原理
- 写时复制文件系统从不原地覆盖数据,而是将新数据写入磁盘的空闲位置
- 当新数据完全写入并持久化后,才更新元数据指针指向新的数据块
- 整个过程是原子的,崩溃后要么看到旧数据,要么看到新数据,不会出现中间状态
- 对于 PostgreSQL 的 8KB 页面写入,ZFS 和 Btrfs 都能保证原子性,无论底层磁盘的扇区大小
ZFS 关键配置要求
-
创建适合 PostgreSQL 的 ZFS 池
zpool create -o ashift=12 tank /dev/nvme0n1 # ashift=12表示使用4KB块大小,匹配现代SSD的物理扇区 -
创建 PostgreSQL 数据集
zfs create -o recordsize=8k -o compression=lz4 -o atime=off -o logbias=throughput tank/pgdata- recordsize=8k:与 PostgreSQL 页面大小一致,避免写入放大
- compression=lz4:启用 LZ4 压缩,几乎不影响性能,反而能提高吞吐量
- atime=off:禁用访问时间更新,减少不必要的写入
- logbias=throughput:优化大写入操作的性能
-
配置 ZIL (ZFS Intent Log)
- 对于写密集型业务,建议使用单独的高速 SSD 作为 ZIL 设备
zfs add tank log /dev/nvme1n1
Btrfs 关键配置要求
# 挂载参数
mount -o noatime,nodiratime,compress=lz4,space_cache=v2 /dev/nvme0n1 /var/lib/pgsql
- nodatacow:不要使用这个参数!它会禁用写时复制,从而失去原子性保证
- compress=lz4:启用 LZ4 压缩
- space_cache=v2:使用更高效的空间缓存机制
优缺点
- 优点:软件实现,无需特殊硬件,自带数据校验和、快照、压缩等高级功能
- 缺点:有一定的性能开销,ZFS 在 Linux 上的兼容性不如 ext4,Btrfs 的稳定性在某些场景下仍有争议
方案四:企业级 SAN 存储
企业级 SAN 存储 (如 EMC、NetApp、HPE) 通常在阵列级别提供原子写入保证。
工作原理
- SAN 存储控制器配备大容量带电池保护的缓存
- 所有写入请求先进入缓存,再由控制器批量写入磁盘
- 控制器保证任何小于等于阵列块大小 (通常为 8KB 或 16KB) 的写入操作都是原子的
关键配置要求
- 确认 SAN 阵列的块大小设置为 8KB 或更大
- 启用控制器的回写缓存模式
- 确保控制器的电池备份单元正常工作
- 使用多路径软件 (如 DM-Multipath) 保证路径冗余
方案对比与选型建议
| 方案 | 原子性保证 | 性能 | 成本 | 运维复杂度 | 适用场景 |
|---|---|---|---|---|---|
| 带 BBU 的硬件 RAID | 极高 | 高 | 高 | 中 | 传统企业级数据中心,对可靠性要求极高 |
| NVMe SSD (带 PLP) | 高 | 极高 | 中 | 低 | 现代服务器,高性能 OLTP 业务 |
| ZFS 文件系统 | 极高 | 中高 | 低 | 中 | 通用场景,需要快照、压缩等高级功能 |
| Btrfs 文件系统 | 中高 | 中高 | 低 | 中 | 测试环境,非核心业务 |
| 企业级 SAN | 极高 | 中 | 极高 | 高 | 大型企业,已有 SAN 基础设施 |
选型优先级
- 核心业务:优先选择带 BBU 的硬件 RAID 或 ZFS 文件系统
- 高性能业务:优先选择带 PLP 的企业级 NVMe SSD
- 预算有限:优先选择 ZFS 文件系统,利用软件实现高级功能
- 已有 SAN 基础设施:直接使用 SAN 存储的原子写入能力
风险评估与局限性
即使存储层提供了 8KB 原子写入保证,关闭 FPW 仍然存在以下风险和局限性:
无法解决的问题
静默数据损坏:原子写入只能保证写入操作的完整性,无法解决磁盘介质损坏、固件 bug 等导致的静默数据损坏
- 必须开启 PostgreSQL 的数据校验和:
initdb --data-checksums - 定期运行
pg_checksums工具检查数据完整性
极端故障场景:如果在写入元数据时发生故障,仍然可能导致文件系统级别的损坏
- 使用带日志的文件系统 (ext4、XFS、ZFS、Btrfs)
- 定期进行文件系统检查
主备同时故障:如果主备数据库同时崩溃,仍然需要进行本地崩溃恢复
- 虽然原子写入保证了没有撕裂页,但其他类型的数据损坏仍然可能发生
- 必须保证备份的可靠性和可恢复性
常见陷阱
BBU 电池失效:当 RAID 控制器的 BBU 电池电量不足时,会自动降级为直写模式,原子性保证失效
- 必须建立 BBU 状态监控告警机制
- 定期进行电池校准和更换
SSD 固件 bug:部分 SSD 厂商的原子写入实现存在 bug,可能导致数据损坏
- 选择经过 PostgreSQL 社区验证的 SSD 型号
- 及时升级 SSD 固件到最新稳定版本
文件系统配置错误:例如在 Btrfs 上使用nodatacow参数,会禁用写时复制,失去原子性保证
- 严格按照推荐配置设置文件系统参数
- 定期检查文件系统挂载参数
完整的实施方案
前期准备
存储选型与测试
- 根据业务需求选择合适的存储方案
- 进行压力测试和故障注入测试,验证原子写入能力
- 测试不同配置下的性能表现
数据库准备
- 开启数据校验和:如果是新建数据库,使用
initdb --data-checksums;如果是已有数据库,使用pg_checksums --enable工具 - 升级 PostgreSQL 到最新稳定版本
- 配置完善的监控系统
上线步骤
在测试环境验证
- 部署与生产环境相同的存储和数据库配置
- 运行业务压力测试,模拟各种故障场景 (断电、重启、磁盘故障等)
- 观察至少 1 个月,确认没有数据损坏问题
逐步上线
- 先在非核心业务系统上线,观察 1-2 周
- 再在核心业务系统的备库上线,观察 1-2 周
- 最后在核心业务系统的主库上线
参数配置
# postgresql.conf
full_page_writes = off
wal_log_hints = on # 即使关闭FPW,也建议开启wal_log_hints,用于数据页修复
运维与监控
存储层监控
- RAID 控制器状态、BBU 电池状态
- SSD 健康状态、磨损程度、温度
- 文件系统使用率、inode 使用率、磁盘错误
数据库层监控
- 数据校验和错误:监控
pg_stat_database中的checksum_failures字段 - WAL 生成速率:关闭 FPW 后 WAL 生成速率应显著下降
- 事务吞吐量、延迟:确认性能提升符合预期
定期维护
- 每周运行一次
pg_checksums全库检查 - 每月进行一次备份恢复演练
- 每季度进行一次故障注入测试
总结
在传统 PostgreSQL 架构中,通过存储层保证 8KB 页面原子写入来关闭 FPW 是一个技术上可行但需要严格条件的方案。它能带来显著的性能提升 (尤其是写密集型业务),但也对存储基础设施和运维能力提出了更高的要求。
核心结论:
- 带 BBU 的硬件 RAID 和 ZFS 文件系统是最可靠的两种方案,经过了长期的生产验证
- 即使存储层提供了原子写入保证,也必须开启数据校验和,并建立完善的备份和高可用体系
- 关闭 FPW 是一个高风险操作,必须经过充分的测试和验证,逐步上线