PostgreSQL 逻辑复制遇到大事务时,订阅端的回放很容易成为瓶颈。max_parallel_apply_workers_per_subscription 提供了并行 apply worker,但仅仅打开并行模式还不够:如果 worker 上限太低,或者上层 worker 池已经耗尽,大事务仍然只能排队。
它加速的是并发大事务,而不是拆分单个事务
订阅使用并行流式应用时,可以把正在流式传输的大事务交给 parallel apply worker,让主 apply 进程继续接收和调度其他事务。要启用这一行为,订阅需要配置:
ALTER SUBSCRIPTION orders_sub SET (streaming = parallel);
如果是新建订阅,可以直接指定:
CREATE SUBSCRIPTION orders_sub
CONNECTION 'host=publisher.example.com port=5432 dbname=app user=replicator password=change_me'
PUBLICATION orders_pub
WITH (streaming = parallel);
这里有一个重要边界:parallel apply 通常不会把同一个事务里的 SQL 拆给多个 worker 执行。一个大事务仍主要由一个 worker 应用;收益来自多个事务可以同时推进,而不是单个 INSERT 突然获得多核并行能力。
因此,下列场景通常更容易获益:
- 同时存在多个正在流式传输的大事务;
- 一个大事务回放时,其他事务不应全部被它阻塞;
- 订阅端 CPU、存储 IOPS 仍有余量;
- 复制负载以独立事务为主,没有严重的锁竞争。
如果工作负载始终只有一个超大事务,提高 worker 数量未必能缩短该事务本身的执行时间。此时更值得考虑缩小事务批次、优化订阅端索引,或者提高存储吞吐量。
Worker 上限是三层预算
max_parallel_apply_workers_per_subscription 不是独立的资源池,而是每个订阅可使用的 parallel apply worker 上限。真正能启动多少 worker,还会受到另外两个参数约束:
max_parallel_apply_workers_per_subscription:单个订阅的并行 apply 上限;max_logical_replication_workers:整个实例可用于逻辑复制的 worker 池,apply、表同步及并行 apply 都要从这里取得名额;max_worker_processes:实例级后台 worker 总上限,逻辑复制还要与其他后台 worker 和扩展共享它。
规划时可以采用下面的容量思路,而不要只修改一个参数:
逻辑复制 worker 预算
≈ 启用中的订阅主 apply worker
+ 高峰期 parallel apply worker
+ 初始表同步 worker
+ 故障恢复和突发流量余量
例如,实例有 3 个活跃订阅,希望每个订阅高峰期最多使用 4 个并行 worker,那么仅主 apply 与并行 apply 的理论需求就可能达到 15。初始化新订阅时还会出现表同步 worker,因此把 max_logical_replication_workers 恰好设置成 15 通常过于紧张。
同时需要注意:每订阅上限不是预留配额。多个订阅会竞争共享的逻辑复制 worker 池,某个高流量订阅可能在其他订阅需要 worker 之前占用大量容量。
一套可以改造的配置与检查命令
下面的示例在订阅端执行。数值只是一个起点,应根据订阅数量、CPU 核数和表同步需求调整:
psql -X -v ON_ERROR_STOP=1 postgres <<'SQL'
ALTER SYSTEM SET max_worker_processes = '32';
ALTER SYSTEM SET max_logical_replication_workers = '16';
ALTER SYSTEM SET max_parallel_apply_workers_per_subscription = '4';
SELECT pg_reload_conf();
SELECT name, setting, context, pending_restart
FROM pg_settings
WHERE name IN (
'max_worker_processes',
'max_logical_replication_workers',
'max_parallel_apply_workers_per_subscription'
)
ORDER BY name;
SQL
max_worker_processes、max_logical_replication_workers 等参数在许多版本中需要重启才能生效。不要仅凭 pg_reload_conf() 判断成功,应检查 pending_restart;如果为 true,通过当前环境的服务管理器重启 PostgreSQL。例如在使用标准 systemd 服务名的主机上:
sudo systemctl restart postgresql
重启或重载后,再确认实际值:
SHOW max_worker_processes;
SHOW max_logical_replication_workers;
SHOW max_parallel_apply_workers_per_subscription;
观察逻辑复制 worker 时,可以使用兼容性相对较好的 pg_stat_activity 查询:
SELECT pid,
backend_type,
application_name,
state,
wait_event_type,
wait_event
FROM pg_stat_activity
WHERE backend_type LIKE 'logical replication%'
ORDER BY backend_type, pid;
同时检查订阅接收位置和最近结束时间:
SELECT subname,
received_lsn,
latest_end_lsn,
latest_end_time
FROM pg_stat_subscription
ORDER BY subname;
如果要在测试环境制造一个较大的事务,可以在发布端对已经纳入 publication 的测试表执行:
CREATE TABLE IF NOT EXISTS replication_probe (
id bigint PRIMARY KEY,
payload text NOT NULL
);
BEGIN;
INSERT INTO replication_probe (id, payload)
SELECT g, repeat(md5(g::text), 8)
FROM generate_series(1, 200000) AS g
ON CONFLICT (id) DO NOTHING;
COMMIT;
运行测试时同时观察订阅端 worker、CPU、磁盘延迟和复制滞后。若 worker 数量增加但吞吐量没有改善,瓶颈很可能已经转移到磁盘、索引维护、锁等待或订阅端约束检查。
上线时不要只看 worker 数量
调高该参数前,可以按以下清单逐项确认:
- 订阅已经设置为
streaming = parallel; max_logical_replication_workers能容纳主 apply、并行 apply 和表同步 worker;max_worker_processes为逻辑复制及其他扩展留下足够空间;- 订阅端有可用 CPU 和 I/O,而不是已经处于饱和状态;
- 测试负载包含多个并发大事务,而不只是单个超大事务;
- 调整后持续监控复制延迟、锁等待、磁盘延迟和 WAL 保留量。
更稳妥的做法是从每订阅 2~4 个 parallel apply worker 开始,使用真实事务分布压测,再逐步增加。这个参数的目标不是让 worker 越多越好,而是确保大事务到来时有 worker 可用,同时避免把共享资源池和订阅端存储压垮。