PostgreSQL 教程: 存储层启用原子写入

五月 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 能为缓存供电数小时,确保所有数据最终写入磁盘

关键配置要求

  1. 必须启用回写模式 (Write-Back)
    • 禁用直写模式 (Write-Through):直写模式下数据直接写入磁盘,不经过缓存,无法提供原子性保证
    • 确认 RAID 卡的缓存策略:MegaCli -LDGetProp -Cache -LAll -aAll
    • 设置回写模式:MegaCli -LDSetProp WB -LAll -aAll
  2. BBU/FBU 必须处于健康状态
    • 定期检查电池状态:MegaCli -AdpBbuCmd -GetBbuStatus -aAll
    • 当电池电量低于阈值或需要校准 (learn cycle) 时,RAID 控制器会自动降级为直写模式,此时原子性保证失效
    • 建议每 3-5 年更换一次 BBU 电池
  3. 禁用磁盘自身的缓存
    • 物理磁盘的写缓存通常不带电池保护,断电会丢失数据
    • 配置命令: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,就能满足要求

关键配置要求

  1. 确认 SSD 的原子写入能力

    # 查看NVMe设备信息
    nvme id-ns /dev/nvme0n1 -H | grep "Atomic Write Unit"
    
    • 输出结果中 “Atomic Write Unit” 的值应≥8KB (即 16 个 512B 扇区)
  2. 启用文件系统的直接 I/O

    • PostgreSQL 默认使用O_DIRECT标志打开数据文件,绕过操作系统页缓存
    • 确保文件系统挂载参数中没有禁用直接 I/O
  3. 禁用 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 关键配置要求

  1. 创建适合 PostgreSQL 的 ZFS 池

    zpool create -o ashift=12 tank /dev/nvme0n1
    # ashift=12表示使用4KB块大小,匹配现代SSD的物理扇区
    
  2. 创建 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:优化大写入操作的性能
  3. 配置 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 基础设施

选型优先级

  1. 核心业务:优先选择带 BBU 的硬件 RAID 或 ZFS 文件系统
  2. 高性能业务:优先选择带 PLP 的企业级 NVMe SSD
  3. 预算有限:优先选择 ZFS 文件系统,利用软件实现高级功能
  4. 已有 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 是一个技术上可行但需要严格条件的方案。它能带来显著的性能提升 (尤其是写密集型业务),但也对存储基础设施和运维能力提出了更高的要求。

核心结论

  1. 带 BBU 的硬件 RAID 和 ZFS 文件系统是最可靠的两种方案,经过了长期的生产验证
  2. 即使存储层提供了原子写入保证,也必须开启数据校验和,并建立完善的备份和高可用体系
  3. 关闭 FPW 是一个高风险操作,必须经过充分的测试和验证,逐步上线

了解更多

PostgreSQL 优化