一、WAL机制详解
PostgreSQL 的 WAL (Write-Ahead Logging) 是确保数据持久化和主备同步的核心机制。
我们可以从发送机制、失效场景、堆积原因以及删除策略四个维度来深度解析。
1. WAL 是怎么发送的?
在 PostgreSQL 的主从架构(Streaming Replication)中,WAL 的发送主要通过 walsender 和 walreceiver 两个进程协作完成。
核心步骤:
- 产生 WAL: 当主库(Primary)发生数据修改时,后端进程将变更记录写入
pg_wal目录下的日志文件中。 - 建立连接: 备库(Standby)启动
walreceiver进程,连接到主库。 - 握手与请求: 备库告知主库自己当前已接收到的最大 LSN(Log Sequence Number)。
- 持续推送: 主库启动
walsender进程,从请求的 LSN 位置开始读取 WAL 日志,并通过网络发送给备库。 - 落盘与反馈: 备库接收后写入本地文件,并根据配置(同步或异步)向主库发送确认信息。
2. WAL 什么时候会"无效"?
这里的"无效"通常指备库无法利用现有的 WAL 进行恢复。主要场景包括:
- 日志被覆盖/删除: 备库由于网络断开太久,主库已经将备库需要的 WAL 文件循环利用或删除了。此时备库会报错
requested WAL segment has already been removed。 - Timeline(时间线)不匹配: 如果主库进行了 Failover(故障切换)或不完全恢复,时间线会增加。如果备库没有跟随切换,其 WAL 链条就会失效。
- 文件损坏: 磁盘故障或硬件问题导致 WAL 文件校验失败。
3. WAL 为什么会堆积?
正常情况下,WAL会循环使用。堆积意味着pg_wal目录不断膨胀,甚至撑爆磁盘。主要原因有:
- 复制槽(Replication Slots)积压: 这是最常见的原因。如果创建了复制槽但备库断开连接,主库为了保证备库回来后能同步,会永久保留从断开点开始的所有 WAL。
- 归档失败: 如果开启了
archive_mode,但archive_command执行失败(如备份服务器挂了、权限不足),主库会保留所有未成功归档的 WAL。 - 大事务/高负载: 瞬间产生大量数据写入,速度超过了清理速度。
- 参数配置不当:
max_wal_size设置过大,允许系统在触发检查点(Checkpoint)前累积更多日志。
4. WAL 什么时候删除?
PostgreSQL 不会随时删除 WAL,而是在特定时机进行回收(Rename/Reuse)或彻底删除。
- 检查点(Checkpoint)完成后: 这是触发清理的主要时机。当 Checkpoint 完成,意味着该点之前的所有数据已安全落盘。
- 满足删除条件:
- 该 WAL 段不再被当前数据库恢复需要。
- 该 WAL 段已经成功归档(如果开启了归档)。
- 该 WAL 段已经发送给所有的复制槽(如果使用了 Replication Slots)。
- 清理逻辑:
- 系统会计算
min_wal_size和max_wal_size之间的平衡。 - 多余的文件会被重命名(供未来循环使用)或直接 unlink(删除)。
- 系统会计算
建议: 如果你遇到了磁盘空间报警,请先检查 select * from pg_replication_slots;。如果存在 active 为 f 的复制槽,那通常就是堆积的元凶。
二、WAL日志堆积的预防与监控
在 PostgreSQL 13 之前,只要备库断开或处理变慢,主库的复制槽(Replication Slot)会无限制地保留WAL文件,往往导致主库磁盘空间爆满而崩溃。
max_slot_wal_keep_size 的引入就是为了给这个行为加上一个"安全阀"。以下是具体配置与防堆积策略:
1. 核心参数:max_slot_wal_keep_size
这个参数决定了复制槽能"强行保留"的最大 WAL 容量。
- 配置方法: 修改
postgresql.conf或通过ALTER SYSTEM设置。
-- 限制复制槽最多保留 10GB 的 WAL
ALTER SYSTEM SET max_slot_wal_keep_size ='10GB';
-- 重载配置生效
SELECT pg_reload_conf();
- 生效逻辑:
- 如果备库落后产生的 WAL 超过了
10GB,主库会强制删除最旧的 WAL。 - 此时,该复制槽的状态会变为
lost(在pg_replication_slots视图中可见)。 - 后果: 备库会报错并停止同步,因为需要的日志已被删除。你必须重新制作备库(重做基础备份),但主库保住了。
- 如果备库落后产生的 WAL 超过了
2. 辅助参数:形成"防御体系"
单靠一个参数是不够的,通常需要配合以下参数来全面防止堆积:
A. 防止"正常"波动堆积 (max_wal_size)
- 作用: 控制检查点(Checkpoint)之间的 WAL 总量。
- 建议: 设置为你磁盘空间的 20%~30% 。如果磁盘很大,设为
32GB或64GB可以减少检查点频率,提升性能。
B. 防止"无复制槽"时的断连 (wal_keep_size)
- 作用: 在不使用复制槽的情况下,主库强行保留的 WAL 大小。
- 建议: 如果用了复制槽,此值建议设小(如
1GB),让复制槽去管理。
C. 归档超时预防 (archive_timeout)
- 作用: 即使事务很少,也强制每隔一段时间切换并归档一个 WAL。
- 建议: 设置为
60s或300s。这能防止因单个 WAL 文件长期不写满而导致无法归档的问题。
3. 如何监控和排查?
如果你发现 pg_wal 还在增加,请运行以下 SQL 诊断:
select slot_name,active,wal_status, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS lag_size, safe_wal_size FROM pg_replication_slots;
wal_status: 如果显示 'extended' 表示已超出常规大小,'lost' 表示已失效。
safe_wal_size :剩余多少字节后该槽会失效(若为负数则已失效)
4. 配置建议
为了彻底防止磁盘爆满,建议在生产环境采用以下组合拳:
- 设置
max_slot_wal_keep_size:给磁盘留出至少 20% 的余量 。例如 100GB 磁盘,设为70GB。 - 监控报警 :监控
pg_wal目录大小,超过阈值立即通过pg_drop_replication_slot('slot_name')手动清理。 - 归档检查 :确保
archive_command返回 0(成功)。如果归档命令一直报错,上述max_slot_wal_keep_size对归档文件是不生效的。
特别注意 :max_slot_wal_keep_size 是一剂"弃卒保帅"的药。它优先保护主库不挂,但代价是牺牲掉落后太多的从库。
三、复制槽的配置
在 PostgreSQL 中配置基于复制槽(Replication Slots)的主从同步,可以有效防止备库断连后主库过早删除 WAL 日志。
以下是标准的配置步骤,分为主库设置 和备库设置两部分:
第一步:主库(Primary)配置
1. 修改配置文件 postgresql.conf
确保以下参数已设置(修改后需重启服务):
允许作为主库进行流复制
wal_level = logical # 如果是物理流复制,replica 即可;逻辑流复制需 logical
max_wal_senders = 10 # 允许的 walsender 进程上限
max_replication_slots = 10 # 允许创建的最大复制槽数量
2. 配置权限 pg_hba.conf
允许备库 IP 访问主库的 replication 数据库:
#允许来自 192.168.1.100(备库)的连接
host replication replica_user 192.168.1.100/32 scram-sha-256
3. 创建复制槽
登录主库,手动创建一个物理复制槽:
-- 创建一个名为 'slave_slot' 的物理复制槽
SELECT * FROM pg_create_physical_replication_slot('slave_slot');
注:逻辑复制需要指定插件,此处以最常用的物理流复制为例。
第二步:备库(Standby)配置
1. 基础备份 (pg_basebackup)
在备库机器上,使用 pg_basebackup 从主库拉取初始数据:
-S 指定复制槽名称,-R 会自动生成信令文件 (standby.signal)
pg_basebackup -h <主库IP> -U replica_user -D /var/lib/postgresql/data -Fp -Xs -P -R -S slave_slot
2. 检查或手动配置复制槽参数
如果不是通过 pg_basebackup -S 自动生成的,需要在备库的 postgresql.conf (或 postgresql.auto.conf) 中手动指定:
告诉备库连接主库的信息
primary_conninfo = 'host=<主库IP> port=5432 user=replica_user password=your_password'
关键:指定备库连接时使用的复制槽名称
primary_slot_name = 'slave_slot'
3. 启动备库
启动 PostgreSQL 服务,备库会读取 standby.signal 进入只读恢复模式,并寻找 primary_slot_name 进行同步。
第三步:验证状态
1. 在主库查看复制槽
SELECT slot_name, slot_type, active, restart_lsn FROM pg_replication_slots;
- active = t: 表示备库已成功连接并正在使用该槽。
- active = f: 表示备库断开了,此时主库会开始为该槽堆积 WAL。
2. 在主库查看流复制详情
SELECT application_name, state, sync_state FROM pg_stat_replication;
注意事项
- 插槽名称 :
primary_slot_name必须与主库创建的名称完全一致。 - 清理旧槽 :如果备库以后弃用了,一定要手动在主库删除该槽,否则主库磁盘会被 WAL 撑爆:
SELECT pg_drop_replication_slot('slave_slot');
- 安全保护 :如前所述,建议主库务必配置
max_slot_wal_keep_size,防止备库挂掉导致主库宕机。
四、流复制监控
在 PostgreSQL 中,监控流复制(Streaming Replication)主要通过主库和备库上的几个关键系统视图。
以下是根据你的需求分类整理的视图及常用查询 SQL:
1. 在主库(Primary)上查看
主库通过 pg_stat_replication 视图提供备库的实时连接和同步进度信息。
核心视图:pg_stat_replication
这是最常用的视图,可以看到每个备库的 IP、同步状态和延迟。
SELECT
application_name, -- 备库的名字
client_addr, -- 备库的 IP 地址
state, -- 复制状态:startup(启动), catchup(追赶), streaming(流复制中), backup(备份中), stopping(停止)
sync_state, -- 同步模式:async(异步), sync(同步), potential(潜在同步), quorum(法定人数同步)
sent_lsn, -- 主库发送的最新的 LSN
write_lsn, -- 备库已写入磁盘的 LSN
flush_lsn, -- 备库已刷新到磁盘的 LSN
replay_lsn, -- 备库已应用(重放)的 LSN
-- 计算延迟量(字节)
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn)) AS replication_lag
FROM pg_stat_replication;
核心视图:pg_replication_slots
如果你使用了复制槽,需要查看此视图确保槽是激活的,并监控它保留了多少 WAL。
SELECT
slot_name,
slot_type, -- physical(物理) 或 logical(逻辑)
active, -- 是否有备库正在使用此槽 (t/f)
restart_lsn, -- 备库需要的旧 LSN,决定了 WAL 堆积的起点
wal_status, -- WAL 状态:reserved(正常), extended(超出范围), lost(已丢失)
safe_wal_size -- 还能再产生多少 WAL 才会导致此槽失效
FROM pg_replication_slots;
2. 在备库(Standby)上查看
备库主要关注自己接收和重放(Replay)的情况。
核心视图:pg_stat_wal_receiver
查看备库当前的接收状态,包括连接到的主库信息。
SELECT
status, -- 接收器状态:streaming(运行中)
receive_start_lsn, -- 开始接收时的 LSN
received_lsn, -- 当前已接收到的最大 LSN
latest_end_lsn, -- 最近一次接收到的 LSN
last_msg_receipt_time, -- 最近一次收到主库消息的时间
conninfo -- 主库的连接字符串信息
FROM pg_stat_wal_receiver;
常用函数(用于备库)
直接调用函数判断备库的状态:
-- 1. 判断当前是否处于恢复模式(t 表示备库,f 表示主库)
SELECT pg_is_in_recovery();
-- 2. 查看最后一次接收到的 WAL 时间
SELECT pg_last_xact_replay_timestamp();
-- 3. 查看接收和应用的 LSN
SELECT pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn();
注意: 所有的 lsn_diff 计算出的字节数越大,说明主从延迟越高。如果 pg_replication_slots 中的 active 为 f 且 wal_status 为 extended,说明该槽正在导致主库磁盘空间迅速减少。