起因
阿里云 RDS PostgreSQL 磁盘满了。看了一下,WAL 日志从某个时间点开始一直在涨,没有回落过。业务数据量没什么变化,问题出在 WAL 本身。
排查
先查了 replication slot:
SELECT slot_name, database, active, restart_lsn, confirmed_flush_lsn
FROM pg_replication_slots;
发现有一个叫 test 的 slot,active = false,没人在用。
再查日志,找到了创建它的 SQL:
SELECT pg_create_logical_replication_slot('test', 'pgoutput');
时间点和 WAL 开始堆积的时间完全对得上。应该是有人测试逻辑复制,创建了 slot 之后忘了删。
原因
PostgreSQL 的 replication slot 会保证:在消费者确认读取之前,不删除对应的 WAL。这个机制本身是为了防止从库或 CDC 工具断连后丢数据。
但如果 slot 没人用了、也没人删,PostgreSQL 就会一直保留 WAL,永远不清理。磁盘自然就满了。
修复
删掉这个 slot:
SELECT pg_drop_replication_slot('test');
删完之后 PostgreSQL 自动清理了多余的 WAL,空间就释放了。
另外加了一个参数防止以后再出这种事:
max_slot_wal_keep_size = 10240 -- 10GB,大概是磁盘的 10%~20%
这个参数的意思是,如果某个 slot 保留的 WAL 超过这个值,PostgreSQL 会强制清理,slot 会失效。消费者需要重新全量同步,但至少不会把磁盘撑爆。
注意这个参数只管以后,已经堆积的 WAL 还是得靠删 slot 来释放。
顺便整理一下 Replication Slot 相关的知识
WAL 是什么
PostgreSQL 的所有数据变更都先写 WAL,再写数据文件。WAL 有三个用途:
- 崩溃恢复:重启后重放 WAL 恢复数据一致性
- 复制:把 WAL 发给从库,做主从同步
- PITR(时间点恢复):配合基础备份回到任意时间点
正常情况下 WAL 被消费完就会清理,磁盘占用是稳定的。
Replication Slot 是什么
简单说就是一个书签,告诉 PostgreSQL:"从这个位置往后的 WAL 还有人要读,别删。"
没有 slot 的时候,从库断连后主库可能已经把 WAL 清理了,从库重连就只能全量重做。有了 slot,主库会记住每个消费者读到哪了,断连后再接上就行。
两种类型
- 物理复制槽:用于流复制(主从同步),直接传 WAL 二进制,从库和主库版本要一致
- 逻辑复制槽:用于逻辑复制和 CDC,通过解码插件(
pgoutput、wal2json等)把 WAL 解析成 INSERT/UPDATE/DELETE,消费者可以是不同版本的 PG 甚至其他系统
什么场景会用到
逻辑复制:
PostgreSQL 自带的功能。比如跨版本迁移(PG 14 → PG 16)、只同步部分表到另一个库、跨地域同步。
-- 发布端
CREATE PUBLICATION my_pub FOR TABLE users, orders;
-- 订阅端(会自动创建 replication slot)
CREATE SUBSCRIPTION my_sub
CONNECTION 'host=... dbname=...'
PUBLICATION my_pub;
CDC(Change Data Capture):
把数据库变更实时同步到外部系统。比如用 Debezium 把变更推到 Kafka,再分发到 ES、数据湖之类的。
PostgreSQL → Replication Slot → Debezium → Kafka → 下游系统
其他:
实时计算(Flink)、搜索索引更新、缓存失效通知,只要需要监听数据库变更的场景都可能用到。
WAL 是实例级别的,不是数据库级别的。
PostgreSQL 整个实例(所有数据库)共用一条 WAL 流,所有库的所有变更都顺序写入同一份 WAL 文件。Replication slot 记录的是一个 WAL 位置(restart_lsn),它告诉 PostgreSQL:"这个位置之后的 WAL 文件,一个都不能删。"
所以不管是哪个 database、哪个 schema 产生的写入,只要产生了新的 WAL 段,就会堆积在那个位置之后,全部被 slot 拦住,无法清理。