PostgreSQL WAL 原理剖析、日志堆积治理与流复制监控

一、WAL机制详解

PostgreSQL 的 WAL (Write-Ahead Logging) 是确保数据持久化和主备同步的核心机制。

我们可以从发送机制、失效场景、堆积原因以及删除策略四个维度来深度解析。

1. WAL 是怎么发送的?

在 PostgreSQL 的主从架构(Streaming Replication)中,WAL 的发送主要通过 walsenderwalreceiver 两个进程协作完成。

核心步骤:

  1. 产生 WAL: 当主库(Primary)发生数据修改时,后端进程将变更记录写入 pg_wal 目录下的日志文件中。
  2. 建立连接: 备库(Standby)启动 walreceiver 进程,连接到主库。
  3. 握手与请求: 备库告知主库自己当前已接收到的最大 LSN(Log Sequence Number)。
  4. 持续推送: 主库启动 walsender 进程,从请求的 LSN 位置开始读取 WAL 日志,并通过网络发送给备库。
  5. 落盘与反馈: 备库接收后写入本地文件,并根据配置(同步或异步)向主库发送确认信息。

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 完成,意味着该点之前的所有数据已安全落盘。
  • 满足删除条件:
    1. 该 WAL 段不再被当前数据库恢复需要。
    2. 该 WAL 段已经成功归档(如果开启了归档)。
    3. 该 WAL 段已经发送给所有的复制槽(如果使用了 Replication Slots)。
  • 清理逻辑:
    • 系统会计算 min_wal_sizemax_wal_size 之间的平衡。
    • 多余的文件会被重命名(供未来循环使用)或直接 unlink(删除)。

建议: 如果你遇到了磁盘空间报警,请先检查 select * from pg_replication_slots;。如果存在 activef 的复制槽,那通常就是堆积的元凶。

二、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 视图中可见)。
    • 后果: 备库会报错并停止同步,因为需要的日志已被删除。你必须重新制作备库(重做基础备份),但主库保住了。

2. 辅助参数:形成"防御体系"

单靠一个参数是不够的,通常需要配合以下参数来全面防止堆积:

A. 防止"正常"波动堆积 (max_wal_size)

  • 作用: 控制检查点(Checkpoint)之间的 WAL 总量。
  • 建议: 设置为你磁盘空间的 20%~30% 。如果磁盘很大,设为 32GB64GB 可以减少检查点频率,提升性能。

B. 防止"无复制槽"时的断连 (wal_keep_size)

  • 作用: 在不使用复制槽的情况下,主库强行保留的 WAL 大小。
  • 建议: 如果用了复制槽,此值建议设小(如 1GB),让复制槽去管理。

C. 归档超时预防 (archive_timeout)

  • 作用: 即使事务很少,也强制每隔一段时间切换并归档一个 WAL。
  • 建议: 设置为 60s300s。这能防止因单个 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. 配置建议

为了彻底防止磁盘爆满,建议在生产环境采用以下组合拳:

  1. 设置 max_slot_wal_keep_size :给磁盘留出至少 20% 的余量 。例如 100GB 磁盘,设为 70GB
  2. 监控报警 :监控 pg_wal 目录大小,超过阈值立即通过 pg_drop_replication_slot('slot_name') 手动清理。
  3. 归档检查 :确保 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;

注意事项

  1. 插槽名称primary_slot_name 必须与主库创建的名称完全一致。
  2. 清理旧槽 :如果备库以后弃用了,一定要手动在主库删除该槽,否则主库磁盘会被 WAL 撑爆:

SELECT pg_drop_replication_slot('slave_slot');

  1. 安全保护 :如前所述,建议主库务必配置 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,说明该槽正在导致主库磁盘空间迅速减少。

相关推荐
默默前行的虫虫2 小时前
nicegui网页多用户数据隔离总结
数据库·sql
特立独行的猫a2 小时前
PostgreSQL客户端工具介绍:从性能测试到跨平台管理
数据库·docker·postgresql·客户端·pgadmin4
微爱帮监所写信寄信2 小时前
微爱帮监狱写信寄信小程序:MySQL核心日志与备份恢复安全架构
数据库·mysql·小程序·邮局·监狱寄信·挂号信·邮政
isNotNullX3 小时前
数据迁移怎么做?有什么好用的数据库迁移工具推荐吗?
数据库·数字化·数据迁移·企业管理
云老大TG:@yunlaoda3603 小时前
华为云国际站代理商DAS的跨境合规适配的应用场景有哪些?
网络·数据库·华为云
3824278273 小时前
python3网络爬虫开发实战 第二版:绑定回调
开发语言·数据库·python
wniuniu_4 小时前
ceph的参数
java·数据库·ceph
一只专注api接口开发的技术猿4 小时前
智能决策数据源:利用 1688 商品详情 API 构建实时比价与供应链分析系统
大数据·前端·数据库
山峰哥4 小时前
SQL查询优化秘籍:从Explain分析到性能飞跃
开发语言·数据库·sql·oracle·性能优化·系统优化