记一次 PostgreSQL WAL 日志撑爆磁盘的排查

20 阅读3分钟

起因

阿里云 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,通过解码插件(pgoutputwal2json 等)把 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 拦住,无法清理。