Spock 6 已进入 Beta,支持 PostgreSQL 16、17、18 和 19。与其说这是一次功能堆叠,不如说它重新整理了多主复制最危险的几条路径:复制进度不再依赖高频目录表写入,超大异常事务可以落盘,节点灾难后能够定位数据分歧,冲突与延迟也有了更直接的统计入口。
对于已经运行 Spock 5 的团队,升级价值主要不在“能否复制”,而在高负载、故障恢复和日常运维时,复制系统是否仍然可解释、可控制。
把复制进度移出事务热路径
多主集群中的订阅端需要记录每个对等节点有哪些事务已经接收、应用。Spock 5 将这类状态写入 spock.progress 目录表,因此每个复制事务都会带来额外的目录写入。流量较小时问题不明显,但在繁忙的多节点拓扑中,这会增加 I/O 竞争,并让进度表持续膨胀。
Spock 6 将运行时进度迁移到共享内存,正常复制不再为每个事务写一次进度表。干净关闭时,状态会快照到 $PGDATA/spock/resource.dat;发生崩溃后,则结合 PostgreSQL 的 replication origin 信息重建进度。自定义 WAL resource manager 还会在关闭和节点操作期间将进度快照写入 WAL,因此可以用 pg_waldump 等 PostgreSQL 工具检查这部分恢复信息。
原来的 spock.progress 没有从 SQL 接口中直接消失,而是变成由 C 语言集合返回函数支持的视图。已有的只读查询通常可以继续运行,但任何直接写入该表的内部脚本都必须删除或重构。
可以这样检查升级后的对象类型和进度数据:
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 <<'SQL'
SELECT c.relname, c.relkind
FROM pg_class AS c
JOIN pg_namespace AS n ON n.oid = c.relnamespace
WHERE n.nspname = 'spock'
AND c.relname = 'progress';
SELECT * FROM spock.progress;
SELECT * FROM pg_replication_origin_status;
SQL
这里应重点确认两件事:progress 已经是视图,以及 Spock 进度与 PostgreSQL replication origin 状态在重启后仍然合理。不要尝试通过 INSERT、UPDATE 或 DELETE 修改 spock.progress。
超大异常事务不再挤爆内存
约束冲突、目标表缺失、模式不一致等问题会让事务进入异常处理路径。Spock 在决定如何处理事务时,需要暂存 replay queue。Spock 5.x 使用 spock.exception_replay_queue_size 限制队列内存,超出限制的事务可能需要从远端重新获取。
Spock 6 保留这个 GUC,但单位改为 MB,默认值为 4。队列达到内存上限后,后续内容会自动写入临时文件,并在需要时读回。内存到磁盘的切换可以发生在同一个事务中,不需要操作员干预。这让多 GB 事务进入异常处理时,不必依靠无限增大内存,也不必重新拉取整笔事务。
配置迁移时要特别检查单位。旧配置中的 4194304 表示字节,不能原样带入新版本,否则会被解释成 4194304 MB。可以先搜索所有节点:
rg -n "spock\.(exception_replay_queue_size|use_spi|batch_inserts|feedback_frequency)" \
/etc/postgresql /var/lib/pgsql 2>/dev/null
按 Spock 6 的含义,一个保守的配置片段可以这样写:
# PostgreSQL configuration for Spock 6
spock.exception_replay_queue_size = 16
spock.resolutions_retention_days = 100
spock.apply_change_logging = 'none'
spock.apply_idle_timeout = 300
具体内存值应根据并发 apply worker 数量、异常事务大小和临时磁盘容量压测。落盘避免了单笔事务占满内存,但会把压力转移到临时文件所在磁盘;磁盘空间和延迟仍然需要告警。
从“复制落后”拆到三个 LSN
Spock 6 的 spock.lag_tracker 使用三个值描述一条订阅链路:
commit_lsn:对端已经提交到哪里。remote_insert_lsn:对端报告已经发送到哪里。received_lsn:本订阅端实际接收到哪里。
这种拆分可以区分“发布端尚未发送”和“发布端已发送但订阅端尚未收到”。旧的 last_received_lsn 已被替换,依赖旧列名的 Dashboard 和告警规则需要修改。
下面的查询可以直接作为巡检起点;如果视图还包含订阅名称等标识列,可按实际表结构补入:
SELECT
commit_lsn,
remote_insert_lsn,
received_lsn,
pg_size_pretty(
abs(pg_wal_lsn_diff(commit_lsn, remote_insert_lsn))::bigint
) AS peer_send_gap,
pg_size_pretty(
abs(pg_wal_lsn_diff(remote_insert_lsn, received_lsn))::bigint
) AS network_receive_gap
FROM spock.lag_tracker;
在 PostgreSQL 18 及以上版本中,Spock 6 还通过 pgstat 基础设施维护每个订阅的冲突计数。运维脚本不必只靠解析日志或事后扫描 spock.resolutions:
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 <<'SQL'
SELECT * FROM spock.get_subscription_stats();
-- 确认已经采集并导出当前基线后,再按需重置统计。
-- SELECT * FROM spock.reset_subscription_stats();
-- 手工保留最近 30 天的 resolution 记录。
SELECT spock.cleanup_resolutions(30);
SQL
冲突命名也开始对齐 PostgreSQL 18,例如 INSERT_EXISTS、UPDATE_ORIGIN_DIFFERS、UPDATE_MISSING、DELETE_ORIGIN_DIFFERS 和 DELETE_MISSING。Spock 额外保留了多主场景需要的 DELETE_EXISTS。升级脚本会迁移已有数据,但使用旧名称过滤 spock.resolutions 的自定义 SQL 仍需更新。
节点彻底损坏后,检测和修复要分开
三节点集群中,如果 A 向 B、C 发送事务时突然永久损坏,B 可能收到了完整批次,C 只收到一半。两个存活节点都可能继续提供服务,却已经产生静默漂移。
Spock 6 通过 WAL 记录的进度数据保存每个对端的 remote_commit_lsn、remote_insert_lsn 和 received_lsn,用于判断每个存活节点停在复制流的什么位置。这解决的是检测问题,而不是自动证明两张业务表内容完全一致。
修复阶段需要使用 ACE 的 table-diff 找出差异,再用 table-repair 补齐记录。执行修复时,--preserve-origin 是关键参数:它保留原始 replication origin ID 和提交时间,避免修复数据被识别成新的本地写入,进而制造二次冲突。
由于摘要没有给出 ACE 命令的完整参数格式,生产操作可以按下面的伪流程编排,并以当前安装版本的帮助信息为准:
# 假设:ace 是部署环境中提供的 ACE CLI;参数名需按实际版本调整。
ace table-diff --source node-b --target node-c --table public.orders
ace table-repair --source node-b --target node-c \
--table public.orders --preserve-origin
不要把 table-repair 当成全库自动裁决器。修复前应冻结或隔离相关写入范围,确认哪一个节点拥有权威记录,并保存 diff 结果以便审计。
滚动升级前要处理的兼容变化
Spock 6 提供从 5.0.10 到 6.0.0 的直接迁移脚本,并通过协议版本协商支持 5.x 与 6.0 节点在升级窗口内共存。实际升级仍应逐节点执行:确认节点追平、停止该节点上的业务写入或摘流、升级扩展、验证复制,再进入下一台。
安装好 Spock 6 二进制和迁移文件后,单节点上的扩展升级可以这样执行:
psql "$DATABASE_URL" -v ON_ERROR_STOP=1 <<'SQL'
SELECT extname, extversion
FROM pg_extension
WHERE extname = 'spock';
ALTER EXTENSION spock UPDATE TO '6.0.0';
SELECT extname, extversion
FROM pg_extension
WHERE extname = 'spock';
SELECT * FROM spock.sub_show_status();
SQL
这段命令只负责数据库内扩展迁移,不会替你安装共享库,也不能代替节点级备份和回滚方案。Beta 阶段应先在与生产拓扑相同的测试集群中验证 DML、DDL、TRUNCATE、异常事务和节点重启。
升级清单至少应包含:
- 删除
spock.use_spi、spock.batch_inserts和spock.feedback_frequency。 - 将
spock.exception_replay_queue_size从字节值换算为 MB。 - 把
last_received_lsn监控改为三个新 LSN。 - 更新旧冲突类型名称和相关告警规则。
- 检查是否存在写入
spock.progress的脚本。 - 为 replay queue 临时文件准备磁盘容量与延迟告警。
- 在 PostgreSQL 17 及以上验证原生 logical slot failover;PG18 及以上会优先使用 PostgreSQL 自身的 slotsync。
- 测试只读订阅、级联复制和 ZODAN 加节点流程,而不只测试普通双向 DML。
Spock 6 的方向很明确:尽量使用 PostgreSQL 已有的 WAL、replication origin、事务只读机制、统计基础设施和原生 slot failover,把扩展自己的状态和特殊路径压缩到更小范围。代价是监控字段、GUC 单位和部分运维接口发生变化。它目前仍是 Beta,更适合作为严格故障演练和容量测试的对象;在完成混合版本、崩溃恢复、磁盘耗尽和节点永久丢失测试之前,不应只凭正常复制测试就推进生产升级。