“FATAL: could not receive data from WAL stream: ERROR: requested WAL segment has already been removed 怎么办、同步复制备库挂了主库会怎样” 是流复制运维里最常见的两个问题。这篇在本机自己起一主一备两个 PostgreSQL 16.15 实例,逐个复现并给出复制槽的做法与代价。
pg_basebackup 退出码 0 ;standby.signal 存在: True ;postgresql.auto.conf 里的 primary_conninfo: host=127.0.0.1 port=9261(conninfo 里的其余字段略) 备库启动退出码 0 备库 pg_is_in_recovery() = True 主库 pg_stat_replication:state = streaming ,sync_state = async 在备库上建表: ReadOnlySqlTransaction: cannot execute CREATE TABLE in a read-only transaction
pg_basebackup -R 写好 standby.signal 和 primary_conninfo;备库只读。
30 次单行提交:备库可见延迟 中位数 0.15 ms,最大 1.10 ms 主库一次性插入 50 万行后:备库回放落后的字节数最大 31800 KB,约 0.06 s 追平;pg_stat_replication 最后一次的 write_lag / flush_lag / replay_lag = 5.6 ms / 5.6 ms / 32.3 ms
write、flush、replay 三段 lag 分别看;大批量写入时落后的主要是回放。
异步复制:单行提交延迟中位数 0.106 ms
设了 synchronous_standby_names = '*' 之后 sync_state = sync
同步复制(remote_write 之上的默认 on):单行提交延迟中位数 0.186 ms
remote_apply(等备库回放完成):单行提交延迟中位数 0.130 ms
备库停掉 2 秒后,主库上这条 insert 的提交仍然没有返回: is alive
pg_stat_activity 里它的状态: ('active', 'IPC', 'SyncRep')
给它发 cancel 之后:2.0 s 时返回 → returned
WARNING: canceling wait for synchronous replication due to user request | DETAIL: The transaction has already committed locally, but might not have been replicated to the standby.
这行数据在主库上已经提交了: 1 行(但没有确认到达备库)备库停掉后主库的提交会一直卡在 SyncRep;cancel 之后事务在主库已提交但没有备库确认。
备库停机期间主库又写了 264 MB WAL,做了 8 次检查点,主库 pg_wal 里现在只剩 4 个段文件(min_wal_size = 32MB,max_wal_size = 64MB)
再启动备库,退出码 0
LOG: started streaming WAL from primary at 0/<lsn> on timeline 1
FATAL: could not receive data from WAL stream: ERROR: requested WAL segment <segment> has already been removed
LOG: started streaming WAL from primary at 0/<lsn> on timeline 1
FATAL: could not receive data from WAL stream: ERROR: requested WAL segment <segment> has already been removed备库停机太久,主库回收了它需要的 WAL,备库启动报 has already been removed。
备库停机、主库又写入 60 万行并做了 6 次检查点之后:slot s1,active = False,wal_status = extended,为它保留的 WAL = 198 MB 主库 pg_wal 里现在有 13 个段文件(不设复制槽时只有 4 个) 再启动备库,退出码 0 备库追平后 t 的行数: 1900631 ,主库: 1900631 追平后 slot s1:active = True,保留的 WAL = 0 bytes
复制槽让主库为备库保留 WAL,备库能追平;备库不回来 WAL 会占满磁盘。