近期用AI解读的2个补丁出了点小插曲, 给哼哼打脸了, 可能是AI未读取代码, 而是直接从comment推演的. 还好传成老师和zhijie帮忙点出了错误.
今天想综合这几天的信息, 纠正一下错误同时指出PG 逻辑复制的一个巨坑.
首先来回顾一下什么是同步复制槽.
同步复制槽目的就是保证使用物理流复制从库时, 如果发生failover(激活从库成为新主库), 这个同步复制槽的消费者漂移到新激活的主库上时, 不会丢数据.
基于这个前提条件, 以某个同步复制槽为例, 消费者在老的主库上消费的最后的WAL位点, 此前的所有WAL必须已经同步给从库.
PG 19 推出了一个函数 pg_sync_replication_slots() . 类似设置了一个栅栏, 在从库执行它时, 这个函数返回true之前, 必须等待此函数调用前就已经在老主库上存在的所有同步复制槽, 这些槽的已消费的WAL(如果有多个同步复制槽, 则取最小的那个wal), 此位置之前的所有WAL都已经同步到当前从库.
但是这个逻辑还是有个小问题, 因为这个操作是异步的, 你在从库看到的永远是主库过去的slot状态, 所以消费者在主库不断消费, 你看到的wal永远比实际情况更早(旧).
因此补丁不是保证在从库激活成新的主库后不丢wal, 而是保证它不删消费者需要的wal. 大不了多消费(消费层面要做好幂等), 但绝对不能少.
不丢WAL是另一个逻辑保证的, 也就是PG 很早的版本就已经存在的同步流复制(quarum based sync replication). 不过这个也有漏洞, 当同步出现堵塞时, 它只是阻止commit返回给客户端, 不阻止本地wal持久化, 所以结果是客户端感觉还没提交(因为一直没有收到返回), 实际上这个事务的wal已经在本地持久化. 在这个情况下, 主库上的消费者是可以消费这个wal的.
这种情况下, 逻辑消费者可能比物理的从库同步了更多的信息. 所以 pg_sync_replication_slots() 在这个场景下也有作用, 它可以将slot标记为未同步.
PG 的巨坑来自堵塞, 而不是丢数据, 请往下看
同步复制槽的功能开启有点复杂:
参考文档 https://www.postgresql.org/docs/current/logicaldecoding-explanation.html
For the synchronization to work, it is mandatory to have a physical replication slot between the primary and the standby (i.e., primary_slot_name should be configured on the standby), and hot_standby_feedback must be enabled on the standby. It is also necessary to specify a valid dbname in the primary_conninfo. It's highly recommended that the said physical replication slot is named in synchronized_standby_slots list on the primary, to prevent the subscriber from consuming changes faster than the hot standby. Even when correctly configured, some latency is expected when sending changes to logical subscribers due to the waiting on slots named in synchronized_standby_slots. When synchronized_standby_slots is utilized, the primary server will not completely shut down until the corresponding standbys, associated with the physical replication slots specified in synchronized_standby_slots, have confirmed receiving the WAL up to the latest flushed position on the primary server.
当然, 这是PG 18的情况, 用可用性换取切换后逻辑订阅同步复制槽的可靠性. 19也许会有改观. 例如 是不是不用配置单一的物理standby来保障逻辑同步复制槽的可靠性, 而是直接和quorum base sync standby结合起来呢?
下面是详细的同步逻辑复制槽配置详解, 以及摆在那里的坑
以下依旧是AI生成, 不过这次AI读了代码, 不是只看comment哦.
PostgreSQL的逻辑复制槽同步功能允许在主备切换后继续逻辑复制。以下是详细的配置说明:
synchronized_standby_slots - 指定逻辑WAL发送进程需要等待的物理复制槽列表 :
-- 在postgresql.conf中设置
synchronized_standby_slots = 'sb1_slot,sb2_slot'
这确保逻辑复制不会比物理备机更快地消费变更,防止数据丢失。
sync_replication_slots - 启用物理备机从主服务器同步逻辑故障转移复制槽 :
-- 在postgresql.conf中设置
sync_replication_slots = on
根据ValidateSlotSyncParams()函数,同步复制槽需要满足以下条件:
启用sync_replication_slots后,备机会启动槽同步工作进程(ReplSlotSyncWorkerMain),定期从主服务器获取故障转移槽信息并同步到本地 。
可以使用pg_sync_replication_slots()函数手动触发同步 :
SELECT pg_sync_replication_slots();
同步的槽可能处于两种状态 :
-- 创建物理复制槽
SELECT pg_create_physical_replication_slot('sb1_slot');
-- 创建逻辑故障转移槽
SELECT pg_create_logical_replication_slot('logical_slot', 'pgoutput',
false, false, true);
-- 配置同步槽列表
ALTER SYSTEM SET synchronized_standby_slots = 'sb1_slot';
SELECT pg_reload_conf();
-- postgres.conf
primary_conninfo = 'host=primary port=5432 dbname=postgres'
primary_slot_name = 'sb1_slot'
hot_standby_feedback = on
sync_replication_slots = on
检查备机上的同步槽状态 :
SELECT slot_name, synced, temporary, invalidation_reason
FROM pg_replication_slots
WHERE slot_name IN ('logical_slot');
当 synchronized_standby_slots 配置了多个slot时,同步复制槽需要等待所有slot都到达同步位置,而不是任意一个。
在 StandbySlotsHaveCaughtup() 函数中,系统会遍历 synchronized_standby_slots 中配置的所有物理复制槽:
name = synchronized_standby_slots_config->slot_names;
for (int i = 0; i < synchronized_standby_slots_config->nslotnames; i++)
对于每个slot,系统检查其 restart_lsn 是否已经达到等待的位置:
只有当所有配置的slot都满足条件时,函数才返回 true:
if (caught_up_slot_num != synchronized_standby_slots_config->nslotnames)
return false;
这意味着:
caught_up_slot_num 记录已赶上进度的slot数量这种设计确保了逻辑复制不会比任何一个物理备机更快地消费变更,从而防止在主备切换时发生数据丢失。
为什么要牺牲可用性换取可靠性呢?
为什么同步槽等待列表支持配置多个slot, 但确是等待全部节点, 而不是使用quorum base sync standby那套逻辑呢?
你怎么看? 欢迎留言.