把 PostgreSQL 迁移做得足够无聊:任务关键数据库的降风险方法

2026-09-10 28 预计阅读时间: 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.

预计阅读时间:13 分钟

任务关键型 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 闸门。

一份实用的切换顺序通常包括:

  1. 确认最新备份已完成并能恢复。
  2. 确认复制健康、磁盘空间和连接容量充足。
  3. 冻结 Schema 变更并通知业务方。
  4. 停止写入,让最后变更排空。
  5. 执行最终一致性检查。
  6. 提升目标库,切换连接字符串、服务发现或 DNS。
  7. 运行登录、读写、交易和关键报表冒烟测试。
  8. 观察错误率、延迟、锁等待和业务指标。
  9. 获得业务与运维签字后,才进入旧系统下线流程。

可以把运行手册中的前置检查自动化。例如下面的 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 迁移的质量,体现在没有数据丢失、没有意外停机、没有凌晨临时决策。用数字定义成功,用停机预算选择方法,用生产副本演练并计时,用三阶段校验确认内容,用运行手册执行,用旧系统和回退路径保留选择权。

上线日期可以调整,数据完整性和可恢复性不能靠愿望。真正成熟的迁移,应该让切换窗口像一次经过排练的例行操作,而不是一次不可逆的冒险。


相关推荐