Postgres 19 让老集群在线补上数据校验和

2026-07-03 41 预计阅读时间: 1 分钟
来源: postgr.es AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:10 分钟

数据校验和不是那种每天刷存在感的功能。它安静地躺在每个 Postgres 数据页的页头里,等硬件、存储控制器、内存或某次诡异的位翻转露出破绽。过去很多老集群没有开启它,因为开启成本太高:要么初始化集群时就决定,要么后来停机全盘转换。Postgres 19 的变化很直接:校验和终于可以在运行中的集群上打开或关闭。

校验和防的不是 SQL Bug,而是硬件撒谎

Postgres 已经有一整套机制保护自己写出来的数据:WAL、崩溃恢复、full page writes,这些都是为了避免宕机、半页写入、事务中断造成的数据不一致。

但这些机制有一个边界:如果硬件返回了错误内容,而且操作系统也照单全收,数据库本身在普通读取路径上未必知道这页已经坏了。

数据校验和补的是这个洞。开启后,Postgres 会为每个写入磁盘的数据页计算一个 16 位 checksum,并放在页头里。之后页面被读回 shared buffers 时,Postgres 重新计算并比较。如果两者不一致,就说明页面内容在数据库不知情的情况下变了,Postgres 会报错,而不是把错误数据继续交给查询、复制或备份流程。

这类错误不一定常见,但影响很坏。没有校验和时,腐坏可能沉默很久,直到它已经进入备库、备份,甚至被业务逻辑消费。校验和的价值不是修复坏页,而是把“静默腐坏”变成“可观测故障”。

pg_rewind 也是一个现实理由

高可用集群里,故障切换之后,旧主库常常要重新作为备库加入。pg_rewind 的价值就在这里:它比较新旧主库分叉后的差异,把旧主库“倒回”到可以继续跟随新主库的状态。

pg_rewind 有前提:目标服务器需要满足 wal_log_hints=on,或者集群在初始化时启用了数据校验和。

原因和 hint bits 有关。hint bits 是 Postgres 为优化可见性判断写入页面的小标记,它可能在读页面时被更新,默认不一定写 WAL。但校验和依赖页面字节内容,只要 hint bit 变了,checksum 也要变。于是启用校验和后,Postgres 必须确保相关页面变化能通过 WAL 正确恢复和复制。

所以,如果你只是为了 pg_rewindwal_log_hints=on 是一条路;但开启校验和同时给你数据页腐坏检测能力。对生产集群来说,这通常是更完整的安全网。

老办法为什么让 DBA 犹豫

数据校验和从 Postgres 9.3 就有了,但很长时间只能在 initdb 时通过参数决定。可以这样创建带校验和的新集群:

initdb --data-checksums -D /var/lib/postgresql/pgdata

问题是:如果当年没有加这个参数,集群基本就被锁在“无校验和”的状态里。

Postgres 11 提供过离线检查工具,Postgres 12 之后 pg_checksums 可以离线启用或禁用校验和。典型流程类似这样:

# 示例:仅适合维护窗口。运行前必须干净停止数据库。
pg_ctl -D /var/lib/postgresql/pgdata stop -m fast

pg_checksums --check --pgdata=/var/lib/postgresql/pgdata
pg_checksums --enable --pgdata=/var/lib/postgresql/pgdata

pg_ctl -D /var/lib/postgresql/pgdata start

这能解决问题,但代价很硬:主库必须完全停机,工具要扫过并重写每个关系的每个页面。多 TB 数据库可能意味着数小时甚至数天维护窗口。转换过程中如果中断,也不能从断点续跑,只能重新开始。转换完成后,复制拓扑也要认真处理,因为可写主库才是事实来源。

另一个绕法是新建启用校验和的集群,再用逻辑复制迁移数据。这也能顺手处理大版本升级、排序规则变化等问题,但它本质上是一次迁移项目,不是一个轻量开关。

Postgres 19:一个函数启动后台转换

Postgres 19 的关键变化是在线转换。开启校验和不再要求整个集群停机,而是通过函数调用触发后台任务。

可以这样实践,具体函数名和参数以你正在测试的 Postgres 19 文档为准:

-- 在测试环境或恢复出来的生产备份上先验证
SELECT pg_enable_data_checksums();

这个调用会在后台启动转换流程。Postgres 会为集群中的每个数据库启动 worker,遍历数据页,标记 buffer 为 dirty,让页面在后续刷盘时写入有效 checksum。相关工作会写 WAL,并复制到 standby,所以备库也会随 WAL 隐式完成转换。

在线不等于免费。扫完整个集群仍然会消耗 I/O,并产生 WAL。Postgres 19 因此提供了类似 cost-based vacuum 的节流参数,用来在转换速度和生产负载之间取舍。可以这样实践:

-- 假设参数遵循 cost delay / cost limit 风格;请按实际 beta 文档调整
SELECT pg_enable_data_checksums(
  cost_delay => '10ms',
  cost_limit => 1000
);

如果存储压力已经很高,应该从保守参数开始;如果是在低峰期或隔离环境,可以少节流,让它尽快跑完。重点是:现在你付出的成本是可调的后台 I/O,而不是完整停机窗口。

怎么观察转换状态

Postgres 19 还改变了 data_checksums 参数的表达方式。过去它基本是布尔值:onoff。现在它会暴露转换中的状态,包含:

  • off
  • inprogress-on
  • on
  • inprogress-off

可以用 SQL 或 psql 查看:

SHOW data_checksums;

也可以写一个简单轮询脚本,方便在演练时记录耗时:

#!/usr/bin/env bash
set -euo pipefail

DB_URL="${DB_URL:-postgresql://postgres@localhost/postgres}"

while true; do
  ts=$(date -Is)
  state=$(psql "$DB_URL" -Atc "SHOW data_checksums;")
  printf "%s data_checksums=%s\n" "$ts" "$state"

  if [ "$state" = "on" ] || [ "$state" = "off" ]; then
    break
  fi

  sleep 30
done

运行前把 DB_URL 改成你的连接串,例如:

export DB_URL="postgresql://postgres@127.0.0.1:5432/postgres"
bash watch-checksums.sh

禁用校验和也会走状态变化,例如从 oninprogress-off 再到 off。不过关闭通常不需要重写页面,速度会快得多。现实问题是:除非你正在做特殊测试或排障,否则生产上很少有理由主动关掉它。

上生产前的检查清单

Postgres 19 把老集群启用校验和的门槛从“安排长时间停机”降到了“安排可控后台转换”,这是非常大的差别。但它仍然不是无成本操作。

建议按这个顺序推进:

  1. 在生产备份恢复出的沙箱环境中测试 Postgres 19 beta 或正式版。
  2. 记录无节流和不同节流参数下的转换耗时、I/O、WAL 增量。
  3. 检查备库复制延迟和归档容量,避免 WAL 峰值把链路或磁盘打满。
  4. 在低峰期启动转换,并用 SHOW data_checksums 持续观察状态。
  5. 把 checksum 错误纳入监控告警,因为开启后腐坏会更早暴露。

对新集群,Postgres 18 已经把数据校验和变成更安全的默认方向。对大量历史集群,Postgres 19 的在线转换才是真正补齐短板的版本。它不能消灭硬件故障,但能让数据库在读到坏页时及时喊停。这一点,足够值得安排一次认真演练。


相关推荐