任务关键型 PostgreSQL 迁移,真正的终点不是“按计划上线”,而是数据完整、系统稳定,并且出了问题仍然能在目标时间内恢复。为了换取一次干净的切换,把上线日期推迟一周,往往比在凌晨面对数据不一致和无法回退更便宜。
一场优秀的迁移结束后,业务人员只会觉得系统正常运行。这种“没人记得迁移发生过”的平静,不是运气,而是明确的验收标准、反复的演练、可执行的运行手册和经过验证的回退路径共同带来的结果。
先定义成功,再决定日期
不要先定一个上线日期,再想办法把迁移塞进去。应该先把成功写成可以测量的数字:
- 丢失行数为
0。 - 目标恢复时间目标(RTO)已经明确,并且经过实际恢复演练验证。
- 业务方签字确认切换窗口。
- 关键业务总额与源库逐项一致。
- 迁移后的监控、告警和运维责任已经交接。
对于金融系统,一小时错误余额造成的损失通常远高于等待一周。因此,日期应该服从风险预算,而不是反过来让风险迁就日期。
三个时点验证数据,而不是只在结束时检查
“目标库里大部分数据都到了”并不是可接受的结果。数据验证至少要覆盖三个时点。
迁移前,在源库建立基线:记录每张表的行数、关键列的聚合结果、业务总额以及一组固定查询的精确结果。典型的业务指标包括所有账户余额之和、活跃账户数量和最新交易时间。
迁移过程中,按阶段检查结果。如果采用逻辑复制,要持续观察复制延迟,确认订阅正在应用变更,而不是因为锁或 DDL 变化停滞。
迁移后,在目标库执行完全相同的检查,并逐项比对。除了行数,还应该使用内容摘要和业务总额,因为行数正确并不代表内容正确。完成一致性检查后,再运行 amcheck 检查索引结构,并执行 ANALYZE,让新系统从新鲜统计信息开始工作。
下面是一组可以改造后直接使用的基线查询。运行前请替换表名和业务列名;pgcrypto 用于计算内容摘要,生产环境中应结合表规模评估执行时间。
-- 需要在数据库中启用一次:CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- 1. 行数
SELECT 'accounts' AS table_name, count(*) AS row_count
FROM public.accounts;
-- 2. 业务总额与关键时间点
SELECT
count(*) AS open_account_count,
coalesce(sum(balance), 0) AS total_balance,
max(updated_at) AS latest_update
FROM public.accounts
WHERE status = 'open';
-- 3. 对稳定排序后的关键字段计算内容摘要
SELECT encode(
digest(
coalesce(
string_agg(
account_id::text || '|' || balance::text || '|' || status,
E'\n' ORDER BY account_id
),
''
),
'sha256'
),
'hex'
) AS accounts_checksum
FROM public.accounts;
对于超大型表,不要在切换窗口内临时对全表执行昂贵查询。可以提前生成分区摘要、按主键范围分段校验,或在生产副本上测量查询耗时。关键是源库和目标库必须使用同一套规则、同一排序方式和同一字段集合。
用生产副本演练,秒表才是真正的窗口
演练不能只是把运行手册从头读一遍。应该建立尽可能接近生产的目标环境,包括:
- 相同的 PostgreSQL 大版本。
- 相同的扩展和配置基线。
- 相近的实例规格、磁盘和网络条件。
- 相同的高可用架构,例如 Patroni 和流复制副本。
- 接近生产规模的数据量与索引。
然后完整执行一次真实迁移,并记录每个阶段的耗时:备份、恢复、追平复制、停止写入、最终校验、提升目标库、切换连接和冒烟测试。规划文档里写下的“预计十分钟”没有证明力;在生产副本上用真实数据量测出的数字,才是切换窗口的依据。
至少重复几轮演练。每轮都要修订运行手册,并记录失败点、锁等待、复制延迟、应用连接池行为和恢复耗时。把问题留在下午的演练里,比把它们留到凌晨的切换窗口里更容易处理。
依据停机预算选择迁移方法
pg_dump/pg_restore 简单、直观,也容易理解,但停机窗口必须容纳完整导出和恢复时间。对于数据量较小、业务可以接受维护窗口的系统,它仍然是稳妥选择。
逻辑复制适合无法承受长时间停机的系统。旧库继续提供服务,变更被持续发送到目标库,最终在复制追平后停止写入并切换流量。代价是方案更复杂,需要单独处理序列、Schema 变更、大对象、权限以及不适合逻辑复制的对象。
一个简化的逻辑复制示例可以作为方案验证的起点。实际执行前应确认版本、网络访问、表的主键或复制标识,以及 DDL 同步策略。
-- 源库:创建发布
CREATE PUBLICATION migration_pub
FOR TABLE public.accounts, public.transactions;
-- 目标库:创建订阅
CREATE SUBSCRIPTION migration_sub
CONNECTION 'host=source-db.example.com port=5432 dbname=app user=repl_user password=REPLACE_ME'
PUBLICATION migration_pub
WITH (
copy_data = true,
create_slot = true,
enabled = true
);
-- 目标库:观察订阅是否持续应用变更
SELECT
subname,
worker_type,
pid,
received_lsn,
latest_end_lsn,
latest_end_time
FROM pg_stat_subscription;
切换前仍然要停止写入、等待最后变更应用完成,并用业务校验确认两端一致。不要把“订阅存在”误认为“数据已经追平”。
让运行手册成为唯一现场事实
切换夜晚不是设计方案的时间。运行手册应该列出每一步的命令、负责人、预计时长、依赖条件、验证结果和回退点,并在不可逆操作前设置明确的 go/no-go 闸门。
一份实用的切换顺序通常包括:
- 确认最新备份已完成并能恢复。
- 确认复制健康、磁盘空间和连接容量充足。
- 冻结 Schema 变更并通知业务方。
- 停止写入,让最后变更排空。
- 执行最终一致性检查。
- 提升目标库,切换连接字符串、服务发现或 DNS。
- 运行登录、读写、交易和关键报表冒烟测试。
- 观察错误率、延迟、锁等待和业务指标。
- 获得业务与运维签字后,才进入旧系统下线流程。
可以把运行手册中的前置检查自动化。例如下面的 Bash 片段会检查目标库可连接、复制延迟和磁盘空间;阈值需要按实际系统调整。
#!/usr/bin/env bash
set -euo pipefail
TARGET_DSN="${TARGET_DSN:?set TARGET_DSN}"
MAX_LAG_SECONDS="${MAX_LAG_SECONDS:-30}"
MIN_FREE_GB="${MIN_FREE_GB:-100}"
psql "$TARGET_DSN" -v ON_ERROR_STOP=1 -c 'SELECT current_database(), now();'
psql "$TARGET_DSN" -v ON_ERROR_STOP=1 -c \
"SELECT subname, latest_end_time
FROM pg_stat_subscription
WHERE latest_end_time IS NULL
OR now() - latest_end_time > make_interval(secs => $MAX_LAG_SECONDS);"
FREE_GB=$(df -BG /var/lib/postgresql | awk 'NR==2 {gsub("G", "", $4); print $4}')
if [ "$FREE_GB" -lt "$MIN_FREE_GB" ]; then
echo "insufficient disk space: ${FREE_GB}GB free" >&2
exit 1
fi
echo "prechecks passed"
现场执行的目标是“按文档运行”,而不是依赖某位专家临时记起一个命令。迁移越重要,现场越应该缺少即兴发挥。
回退路径必须保持可用
在目标库完成验证并获得签字之前,旧系统应该保持运行,并且不再被随意修改。提前定义:
- 什么现象会触发回退,例如业务总额不一致、错误率超阈值或关键写入失败。
- 哪一个步骤是不可逆点。
- 切换最多允许持续多久。
- 如何通过连接字符串、服务发现或 DNS 切回旧库。
- 切换后产生的新写入如何处理,避免回退时出现无法合并的分叉数据。
回退方案不能只写“必要时恢复旧库”。必须具体到命令、负责人和预计耗时,并且在演练中真实执行。还要定期测试备份恢复,确认实际恢复时间确实满足 RTO。
运维团队要在上线前接手
迁移不只是数据搬家,也意味着运维团队接管一套新环境。让负责运行系统的人在上线前使用目标环境:执行查询、观察监控、制造可控故障、验证告警,并确认连接池、备份、故障转移和权限都符合预期。
目标库如果数据完整,却没有可用的监控和明确的值班责任,迁移仍然只完成了一半。上线前应至少确认关键指标已经接入,包括连接数、复制状态、延迟、锁等待、磁盘增长、备份结果、错误率和业务核心总额。
结语:把兴奋留给别的事情
任务关键 PostgreSQL 迁移的质量,体现在没有数据丢失、没有意外停机、没有凌晨临时决策。用数字定义成功,用停机预算选择方法,用生产副本演练并计时,用三阶段校验确认内容,用运行手册执行,用旧系统和回退路径保留选择权。
上线日期可以调整,数据完整性和可恢复性不能靠愿望。真正成熟的迁移,应该让切换窗口像一次经过排练的例行操作,而不是一次不可逆的冒险。