Skip to content

pg_stat_replication 监控 ​

复制配好之后,「有没有在跑」「跑得快不快」「会不会断」这三个问题就全部落到监控上。PostgreSQL 提供了一组专用视图来描述复制状态,其中最重要的两个是主库上的 pg_stat_replication(每个 walsender 一行,描述备库的进度)与备库上的 pg_stat_wal_receiver(单行,描述接收进程的状态)。前者回答「主库眼里备库怎么样」,后者回答「备库自己收到了多少」。只监控其中之一,很容易得出错误结论。

理解这组视图的关键,是把「LSN 位置」和「延迟度量」两类字段分开看。位置字段(sent_lsn / write_lsn / flush_lsn / replay_lsn)是绝对坐标,它们之间的差值就是字节级的落后量;延迟字段(write_lag / flush_lag / replay_lag)是相对时间度量,描述「最近一批 WAL 在被处理上花了多久」,不是「当前落后多久」。把这两类混为一谈,是复制监控里最常见的误判来源。

本页会先把 pg_stat_replication 的字段逐项讲清,再给出监控 SQL、告警阈值与常见异常状态的判读方法,最后指向 Prometheus 等外部监控体系的对接方式。所有示例都基于 PostgreSQL 18。

前置知识

建议先阅读:备库配置(primary_conninfo)

基础概念 ​

复制监控涉及的视图有四个,职责各不相同。

视图所在端行数回答的问题
pg_stat_replication主库每个 walsender 一行主库看到的备库进度与状态
pg_stat_wal_receiver备库单行(或空)备库接收进程的状态与位置
pg_stat_database_conflicts备库每个数据库一行重放与查询冲突的累计次数
pg_stat_replication_slots主库每个逻辑槽一行逻辑复制槽的统计(逻辑复制用)

外加两个辅助视图:pg_replication_slots 描述槽的存在与 WAL 可用性,pg_stat_activity 可用于查看 walsender 会话本身的等待事件。

pg_stat_replication 的完整字段 ​

下表列出 PostgreSQL 18 中该视图的全部字段。字段较多,但可以分成四组:身份、位置、时间、状态。

字段类型分组含义
pidinteger身份walsender 进程的 PID
usesysidoid身份发起连接的角色 OID
usenamename身份发起连接的角色名
application_nametext身份备库上报的应用名
client_addrinet身份备库的 IP 地址
client_hostnametext身份备库主机名(需开启反向解析)
client_portinteger身份备库的 TCP 端口
backend_starttimestamptz时间walsender 启动时间
backend_xminxid位置备库反馈的 xmin 快照(hot_standby_feedback 开启时有值)
statetext状态复制连接的当前状态
sent_lsnpg_lsn位置主库已发送的最后一个 WAL 位置
write_lsnpg_lsn位置备库已写入磁盘的最后一个位置
flush_lsnpg_lsn位置备库已刷入磁盘的最后一个位置
replay_lsnpg_lsn位置备库已重放的最后一个位置
write_laginterval时间最近一次写入所花的时间
flush_laginterval时间最近一次刷盘所花的时间
replay_laginterval时间最近一次重放所花的时间
sync_priorityinteger状态作为同步备库的优先级
sync_statetext状态同步状态
reply_timetimestamptz时间主库收到备库最后一次回复的时间(PostgreSQL 14 起)

其中 reply_time 是 PostgreSQL 14 新增字段,用它和 now() 相减可以快速判断「备库最近还活着吗」,比推断 write_lag 更直接。

四个 *_lsn 字段的语义与顺序 ​

sent_lsn / write_lsn / flush_lsn / replay_lsn 是一条流水线上的四个检查点,正常情况下它们满足单调递减关系:

text
sent_lsn  ≥  write_lsn  ≥  flush_lsn  ≥  replay_lsn
(主库已发)  (备库已写盘)(备库已刷盘)(备库已重放)

每个字段的官方含义:

  • sent_lsn:这条连接上已发送的最后一个 WAL 位置;
  • write_lsn:备库已写入磁盘的最后一个 WAL 位置;
  • flush_lsn:备库已刷入磁盘的最后一个 WAL 位置;
  • replay_lsn:备库已重放的最后一个 WAL 位置。

理解它们之间的差距,就能定位瓶颈:

观察到的现象含义
sent_lsn 落后 pg_current_wal_lsn()主库发送端有积压(walsender CPU、网络拥塞)
write_lsn 落后 sent_lsn网络传输中或备库接收慢
flush_lsn 落后 write_lsn备库刷盘慢(磁盘 I/O 压力)
replay_lsn 落后 flush_lsn备库重放慢(重放 I/O、查询冲突)

flush_lsn 与 replay_lsn 的差别在同步复制中有实际意义:synchronous_commit = remote_write 等不同等级分别对应「已写」「已刷」「已重放」。

state 的取值 ​

state 描述复制连接当前处于哪个阶段。理解这些取值的含义,才能判断「这个状态是正常的吗」。

取值含义
startup备库的 startup 进程正在启动,walsender 尚未进入正常发送
catchup备库正在追赶落后的 WAL(常见于刚建立复制时)
streaming正常流式复制中,备库已跟上主库
backup正在为这个连接发送基础备份(pg_basebackup)
stoppingwalsender 正在停止

catchup 是「新备库刚上线」或「备库落后很多正在追」的正常状态,追平后会自动转为 streaming。backup 通常只在做基础备份时短暂出现。如果长时间停留在 catchup 或 startup,说明备库跟不上或启动流程卡住,需要进一步排查。

sync_state 的取值 ​

sync_state 描述该备库在同步复制机制中的角色。即使没有开启同步复制,这个字段也有值(为 async)。

取值含义
async此备库是异步的,不参与同步确认
potential当前是异步的,但如果当前的同步备库失败,它有可能成为同步
sync此备库是当前被用作同步确认的备库
quorum此备库在 quorum(ANY 模式)下达到了法定确认数

在没有配置 synchronous_standby_names 时,所有备库都是 async。配置后,按 FIRST 或 ANY 的规则,部分备库变为 sync / quorum,其余候补为 potential。同步复制的选择规则参见同步复制配置。

pg_stat_wal_receiver 的完整字段 ​

备库侧的这个视图只有一行(walreceiver 是单进程),且该视图可能返回零行——零行表示 walreceiver 进程没有运行,通常意味着这台机器不是备库、或备库正在重启、或复制还没建立起来。

字段类型含义
pidintegerwalreceiver 进程 PID
statustext接收状态(streaming / waiting 等)
receive_start_lsnpg_lsnwalreceiver 启动时的第一个 WAL 位置
receive_start_tliintegerwalreceiver 启动时使用的第一个时间线编号
written_lsnpg_lsn已接收并写入磁盘、但尚未刷盘的位置
flushed_lsnpg_lsn已接收并刷入磁盘的位置
received_tliinteger最近收到消息所在的时间线编号
last_msg_send_timetimestamptz从发送端收到的最后一条消息的发送时间
last_msg_receipt_timetimestamptz收到最后一条消息的本地时间
latest_end_lsnpg_lsn最后一次向发送端报告的位置
latest_end_timetimestamptz最后一次向发送端报告的时间
slot_nametext使用的复制槽名
sender_hosttext发送端(主库)主机
sender_portinteger发送端端口
conninfotext使用的连接串(含主机、端口、用户等)

关于字段有两个必须准确的要点:

  • written_lsn 不能用于数据完整性判断。 官方描述明确写着它表示「已写入磁盘但尚未刷盘」的位置,并提示不应用于完整性检查("This should not be used for data integrity checks")。
  • 不存在 received_lsn 这个字段。 历史版本曾使用该名字,但从 PostgreSQL 13 起它被改名为 flushed_lsn,同时新增了 written_lsn。因此在新版本文档里引用 received_lsn 是错误的。

要判断「备库已经安全落盘到哪里」,应当使用 flushed_lsn。

pg_stat_database_conflicts 的字段 ​

在热备(hot standby)场景下,重放需要清理的行版本可能正被只读查询使用,这会产生冲突。pg_stat_database_conflicts 按数据库统计冲突次数(每库一行)。

字段含义
datid数据库 OID
datname数据库名
confl_tablespace因表空间被删除而取消的查询数
confl_lock因锁超时被取消的查询数
confl_snapshot因「快照过旧」(需要清理的行版本已不可用)被取消的查询数
confl_bufferpin因缓冲区被固定而取消的查询数
confl_deadlock因死锁被取消的查询数
confl_active_logicalslot因逻辑槽使用而取消的查询数

confl_snapshot 通常增长最快,它直接对应「长查询导致清理无法推迟」的情况。这个计数持续增长说明备库上的查询与重放冲突严重,应考虑开启 hot_standby_feedback、缩短查询时长,或调整 max_standby_streaming_delay。

pg_wal_lsn_diff 与延迟计算 ​

pg_wal_lsn_diff(lsn1, lsn2) 返回两个 LSN 之间的字节差(numeric)。它是所有「延迟字节数」计算的基石,主备两侧都能用:

  • 主库侧:pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) —— 备库整体落后多少字节;
  • 备库侧:pg_wal_lsn_diff(pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn()) —— 收到但还没重放多少字节。

注意这个函数只反映「WAL 字节量」,不反映「时间」。当一个系统的 WAL 生成速率变化很大时,字节延迟与时间延迟不成比例,因此监控上通常两个口径都要采集。

延迟度量的正确理解 ​

write_lag / flush_lag / replay_lag 是 PostgreSQL 用来描述「提交延迟」的度量。官方对它们的说明是:这些时间表示最近一批 WAL 被写、被刷、被重放所需的时间,反映的是「提交延迟」(commit delay),也就是同步复制中提交需要等待多久。

它们有几个容易误解的性质:

  • 它们是最近样本的度量,不是累积值,也不是绝对滞后时间。 备库长时间没有新 WAL 时,这些值可能保持为旧值或为 NULL。
  • 它们是区间值,不是时间点。 不要把它当成「备库落后 3 秒」来读。
  • 在某些情况下可能为 NULL。 例如没有足够样本时。

如果要回答「备库现在落后多久」,更可靠的方式是用 reply_time(主库视角)或 pg_last_xact_replay_timestamp()(备库视角)配合当前时间计算,并明确它的语义边界。

application_name 的作用 ​

application_name 在监控里是「身份标识」,它决定了你能不能在 pg_stat_replication 里分清哪一行是哪台备库。它的来源按优先级是:

  1. 备库 primary_conninfo 中显式设置的 application_name;
  2. 备库的 cluster_name;
  3. 默认值 walreceiver。

如果所有备库都没有设置,pg_stat_replication 里会出现多行同名 walreceiver,你只能靠 client_addr 区分——这在多网卡、NAT 环境下会非常混乱。因此「每台备库唯一命名」是复制监控的第一条基础实践。

语法与用法 ​

监控 SQL 通常按「主库看进度」「备库看接收」「看冲突」三类组织。

主库上查看所有备库的进度(推荐的基础查询):

sql
SELECT application_name,
       client_addr,
       state,
       sync_state,
       pg_wal_lsn_diff(pg_current_wal_lsn(), sent_lsn)   AS sent_lag_bytes,
       pg_wal_lsn_diff(sent_lsn, flush_lsn)             AS flush_lag_bytes,
       pg_wal_lsn_diff(flush_lsn, replay_lsn)           AS replay_lag_bytes,
       pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS total_lag_bytes,
       now() - reply_time                               AS since_last_reply
FROM pg_stat_replication
ORDER BY application_name;

备库上查看接收状态:

sql
SELECT status, slot_name, sender_host, sender_port,
       receive_start_lsn, written_lsn, flushed_lsn, latest_end_lsn,
       now() - last_msg_receipt_time AS since_last_msg
FROM pg_stat_wal_receiver;

备库上查看重放进度与延迟:

sql
SELECT pg_is_in_recovery() AS is_standby,
       pg_last_wal_receive_lsn() AS receive_lsn,
       pg_last_wal_replay_lsn()  AS replay_lsn,
       pg_wal_lsn_diff(pg_last_wal_receive_lsn(),
                       pg_last_wal_replay_lsn()) AS apply_lag_bytes,
       now() - pg_last_xact_replay_timestamp()    AS replay_age;

查看复制槽的 WAL 可用性:

sql
SELECT slot_name, slot_type, active, restart_lsn, wal_status,
       pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS slot_lag_bytes
FROM pg_replication_slots
ORDER BY slot_name;

详细说明 ​

主库视角与备库视角为什么必须都看 ​

主库的 pg_stat_replication 是基于备库回报的。备库把进度回报给主库,主库才能更新这个视图。这意味着它有一定的滞后(取决于回报频率),而且它无法反映「备库重放侧的细粒度问题」。

备库的 pg_stat_wal_receiver 与 pg_last_wal_replay_lsn() 是本地事实,更及时、更准确,但它无法说明「主库是否还在发送」。

两类信息结合才能给出完整体检结论:

主库视图现象备库视图现象结论
有对应行,state = streamingpg_stat_wal_receiver.status = streaming正常
无对应行status = waiting 或视图为空复制断了,需查网络/认证/槽
有对应行但 reply_time 很旧日志有重连记录连接假死或即将超时断开
replay_lsn 不前进flushed_lsn 前进但 pg_last_wal_replay_lsn() 不动重放侧卡住(I/O 或冲突)

只看主库,会漏掉「备库其实在疯狂重连」;只看备库,会漏掉「主库根本没发新 WAL」。

用 reply_time 判断连接活性 ​

reply_time 是判断「备库最近还理会主库吗」最直接的字段。它的语义是「主库收到备库最后一条回复消息的时间」。正常情况下备库会定期回报(默认约每 wal_receiver_status_interval 一次),因此 now() - reply_time 应当稳定在一个很小的值。

sql
SELECT application_name,
       now() - reply_time AS since_last_reply,
       wal_sender_timeout
FROM pg_stat_replication, pg_settings
WHERE pg_settings.name = 'wal_sender_timeout';
text
 application_name |   since_last_reply    | wal_sender_timeout
------------------+-----------------------+--------------------
 standby1         | 00:00:00.102345       | 60000
(1 row)

判断标准:since_last_reply 明显小于 wal_sender_timeout 即为健康;接近或超过超时值意味着连接即将被主库主动终止。这是一个非常实用的「活性心跳」指标,比推断 lag 字段更可靠。

state 异常状态的排查路径 ​

不同 state 对应的排查方向完全不同:

state可能原因排查方向
startup 长时间备库 startup 卡住备库日志、磁盘 I/O、是否有恢复错误
catchup 长时间备库落后太多,追赶慢备库 I/O、网络带宽、WAL 生成速率
backup 长时间基础备份正在进行属于正常,确认备份进度即可
stoppingwalsender 正在退出通常是备库断开或超时,看日志
无 streaming 行复制未建立认证、网络、槽、max_wal_senders 名额

其中 catchup 卡住最常见的原因是 WAL 生成速率持续大于备库重放速率——这属于容量问题,需要提升备库 I/O 或减少主库写入峰值,而不是「改个参数就好」。

复制槽的 WAL 可用性监控 ​

pg_replication_slots.wal_status 是判断「这个槽的备库还能不能救」的关键字段。取值语义:

wal_status含义
reserved槽需要的 WAL 都在保留范围内
extendedWAL 超出 max_wal_size 但仍在保留范围内
unreserved槽需要的 WAL 不在 max_wal_size 范围内,但仍未被删除
lost槽需要的部分 WAL 已被删除,该槽不可用

一旦出现 lost,使用该槽的备库必须重建(重新做基础备份)。因此监控上应当把 wal_status = 'lost' 与 active = false 的槽列为告警项。同时用 pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) 量化槽的落后字节数,为容量规划提供依据。

监控 SQL 汇总表 ​

把日常巡检需要看的指标整理成一张表,便于做成定时采集脚本。

指标查询位置SQL 要点
备库数量与状态主库SELECT application_name, state FROM pg_stat_replication
总落后字节主库pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn)
发送端积压主库pg_wal_lsn_diff(pg_current_wal_lsn(), sent_lsn)
重放积压主库pg_wal_lsn_diff(flush_lsn, replay_lsn)
连接活性主库now() - reply_time
同步状态主库SELECT application_name, sync_state, sync_priority
接收状态备库SELECT status FROM pg_stat_wal_receiver
本地重放延迟备库pg_wal_lsn_diff(pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn())
重放时间戳备库pg_last_xact_replay_timestamp()
冲突计数备库SELECT * FROM pg_stat_database_conflicts
槽可用性主库SELECT slot_name, wal_status FROM pg_replication_slots

告警阈值的设定思路 ​

阈值没有放之四海皆准的数值,但有几条设定原则:

  1. 区分「必须立即处理」与「观察趋势」两类。 复制连接中断、wal_status = lost 属于前者,必须立即告警;延迟缓慢上升属于后者,可以设趋势告警。
  2. 字节延迟结合写入速率判断。 单看「落后 100MB」没有意义——如果主库每秒写 50MB,100MB 就是 2 秒;如果每秒写 1MB,那是 100 秒。建议同时采集主库的 WAL 生成速率。
  3. 时间延迟要排除空闲干扰。 业务低峰期 pg_last_xact_replay_timestamp 会显得很旧,不应触发告警。可结合「主库是否有新 WAL」共同判断。
  4. reply_time 阈值直接取 wal_sender_timeout 的一定比例。 例如超过超时值的 50% 就预警,接近 100% 就告警,这样能在连接被断开之前介入。
  5. 冲突计数用「增长率」而非绝对值。 累计值总会增长,关注单位时间内的增量才有意义。

常见的复制延迟案例 ​

几种典型场景下的表现与处理方式:

场景主要表现处理方向
网络带宽不足sent_lsn 落后,write_lsn 也落后扩容链路、启用压缩(如适用)或用级联复制分流
备库磁盘慢flush_lsn 落后 write_lsn提升备库存储性能
重放大事务flush_lsn 前进而 replay_lsn 长时间不动属于正常;关注重放完成后的追赶
长查询阻塞重放replay_lsn 停滞,日志有取消查询消息调整 hot_standby_feedback / 缩短查询
主库检查点导致 WAL 突增短时间内大量 WAL,备库追赶不及调整检查点参数平滑 WAL 生成
备库频繁重连pg_stat_replication 行反复出现又消失查网络稳定性、超时参数、认证

「重放大事务」这一项值得特别说明:一个巨大的事务(例如一次导入上千万行)在重放时只有在整个事务的 WAL 全部重放完成后才会更新 replay_lsn 的可见推进,期间看起来「卡住」其实是正常现象。此时应看 flush_lsn 是否在前进,以及备库日志中的重放进度。

逻辑复制槽监控的边界 ​

pg_stat_replication_slots 是给逻辑复制用的(每个逻辑槽一行,含 spill_txns、spill_count、spill_bytes、stream_txns 等统计)。如果集群里没有逻辑复制,这个视图会是空的,这不代表异常。

同理,pg_stat_replication 也会包含逻辑复制连接的 walsender(如果使用了逻辑复制)。要区分物理与逻辑连接,可以看 pg_stat_replication 与 pg_stat_activity 的组合,或检查对应的槽类型(pg_replication_slots.slot_type)。本页聚焦物理流复制,逻辑复制的监控指标在逻辑复制章节展开。

采样与趋势分析 ​

瞬时值只能回答「现在怎么样」,而复制问题往往是渐进式的。建议在采集时保留一段时间序列,用于回答:

  • 延迟是否在持续上升(容量问题的早期信号);
  • 是否在特定时间段(如夜间批量任务)出现延迟峰值;
  • 复制断开是否与网络事件、备份任务相关;
  • pg_wal 目录大小与槽的落后字节数是否同步增长。

有了趋势数据,很多运维决策(是否扩容、是否需要级联、是否需要调整检查点)就有了依据,而不是靠感觉。

复制监控与备份任务的相互影响 ​

基础备份(pg_basebackup)与流复制共享同一套机制,也会相互影响,因此监控时要把两者放在一起看。

pg_basebackup -X stream 会占用一个 walsender 名额,并在 pg_stat_replication 中出现一行 state = backup。它的存在在几个方面会干扰判读:

  • max_wal_senders 名额被占用,备库断线重连时可能被拒;
  • 备份期间的 I/O 消耗会加剧备库或主库的负载,进而推高 write_lag;
  • 备份会读取大量数据页,与备库上的只读查询竞争 I/O 与缓冲区。

因此建议:把基础备份安排在业务低峰期;为备份单独预留 max_wal_senders 名额;在监控看板上能看到「当前是否有备份在跑」,避免把备份造成的延迟误判为复制故障。

复位与统计口径的坑 ​

PostgreSQL 的统计信息(累计计数器)可以通过 pg_stat_reset() 系列函数重置,这会直接影响监控数据的连续性。

相关函数包括 pg_stat_reset()(重置大部分统计)、pg_stat_reset_replication_slot()、pg_stat_reset_shared() 等。需要注意两点:

  1. 重置后累计值归零,基于「增量」的告警逻辑会瞬间误判(例如 confl_snapshot 从 128 变成 0,可能被解读为「冲突清零」或触发「计数器回退」告警)。
  2. 重启实例也会让部分统计归零。 例如 pg_stat_wal_receiver 的内容在备库重启后重新开始,receive_start_lsn / receive_start_tli 会更新为新的启动值。

监控系统应当对「计数器回退」做特殊处理:如果当前采集值小于上一次,说明发生了重置或重启,应当把基线重置而不是报错或按负增量计算。

与外部监控体系的对接 ​

生产环境通常不会靠人手工跑 SQL,而是把上述指标接入监控系统。对接思路有三种:

  1. 自定义 exporter:用 postgres_exporter 之类的工具把 SQL 结果转成 metrics,再配置 Prometheus 抓取与 Grafana 展示。
  2. SQL 轮询脚本:定时执行监控 SQL,把结果推到已有的监控通道。
  3. 复用内置统计:把 pg_stat_replication 的关键字段做周期采样,计算变化量而不是只看瞬时值。

无论哪种方式,采集频率都建议不高于 15 秒(更短的周期对复制状态的判读帮助有限,反而增加负载),并且要注意采集本身也会产生连接开销。关于把指标接入 Prometheus 的完整做法,参见Prometheus 监控。

备库侧等待事件与 pg_stat_activity 观察 ​

主库上的 pg_stat_replication 只能看到「主库视角」的复制状态。当延迟上升时,还需要到备库侧看它在忙什么,才能区分是网络问题还是备库自身卡住。

备库侧最直接的入口是 pg_stat_activity:

sql
-- 在备库上执行:观察 walreceiver 与 startup 进程的状态
SELECT pid,
       backend_type,
       state,
       wait_event_type,
       wait_event,
       backend_start,
       state_change
FROM pg_stat_activity
WHERE backend_type IN ('walreceiver', 'startup', 'checkpointer')
ORDER BY backend_type;

判读要点:

进程正常表现异常信号
walreceiverstate = active,wait_event_type 多为 Client 或 IPC长时间 wait_event = WalReceiverWaitStart 表示在等上游发数据
startup持续重放,wait_event_type = IO 的 WalSync / DataFileRead 属正常wait_event = RecoveryConflictSnapshot 等冲突等待表示重放被查询阻塞
checkpointer周期出现频繁出现且伴随 WAL 增长,考虑检查点过于频繁

startup 进程的 wait_event 是最有价值的线索:如果它长期停在 I/O 类等待上,说明备库存储跟不上重放速度;如果停在复制冲突相关的等待上,则说明是备库上的只读查询在阻止重放推进,属于 max_standby_streaming_delay / hot_standby_feedback 的配置权衡问题,而不是复制链路的问题。

此外,备库同样可以使用 pg_stat_wal_receiver 的 status 字段做快速判断:streaming 表示正在接收,stopping / starting 属于短暂过渡态,若长期停留在非 streaming 状态则应当检查 primary_conninfo、网络连通性与主库上的 walsender 是否还在。

把「主库视角」与「备库视角」的指标放在同一张看板上,是快速定位延迟根因的最有效方式。

实战示例 ​

示例一:主库上查看所有备库的延迟 ​

sql
SELECT application_name,
       client_addr,
       state,
       sync_state,
       pg_wal_lsn_diff(pg_current_wal_lsn(), sent_lsn)   AS sent_lag,
       pg_wal_lsn_diff(sent_lsn, flush_lsn)              AS net_lag,
       pg_wal_lsn_diff(flush_lsn, replay_lsn)            AS replay_lag,
       pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS total_lag,
       now() - reply_time                                AS last_reply_age
FROM pg_stat_replication
ORDER BY application_name;
text
 application_name |  client_addr  |   state   | sync_state | sent_lag | net_lag | replay_lag | total_lag | last_reply_age
------------------+---------------+-----------+------------+----------+---------+------------+-----------+----------------
 standby1         | 192.168.1.20  | streaming | sync       |        0 |       0 |       4096 |      4096 | 00:00:00.102
 standby2         | 192.168.1.21  | streaming | potential  |        0 |    8192 |      16384 |     24576 | 00:00:00.131
 standby3         | 192.168.1.22  | streaming | async      |        0 |    1024 |       1024 |      2048 | 00:00:00.098
(3 rows)

读法:standby1 是同步备库(sync_state = sync)且延迟极小;三台的 sent_lag 都为 0,说明主库发送端没有积压;standby2 的 replay_lag 相对大一些,说明它的重放稍慢。

示例二:备库上区分接收与重放瓶颈 ​

sql
SELECT status,
       flushed_lsn,
       latest_end_lsn,
       slot_name,
       now() - last_msg_receipt_time AS since_last_msg
FROM pg_stat_wal_receiver;
text
  status   | flushed_lsn | latest_end_lsn |   slot_name   |    since_last_msg
-----------+-------------+----------------+---------------+-----------------------
 streaming | 0/301B2C0   | 0/301B2C0      | standby1_slot | 00:00:00.043
(1 row)
sql
SELECT pg_last_wal_receive_lsn() AS receive_lsn,
       pg_last_wal_replay_lsn()  AS replay_lsn,
       pg_wal_lsn_diff(pg_last_wal_receive_lsn(),
                       pg_last_wal_replay_lsn()) AS not_yet_replayed;
text
 receive_lsn | replay_lsn | not_yet_replayed
-------------+------------+------------------
 0/301B2C0   | 0/3016F40  |            17344
(1 row)

status = streaming 且 since_last_msg 很小,说明接收侧健康;not_yet_replayed = 17344 说明重放侧落后 17KB。两句话合起来就是「接收没问题,重放略微落后」,处理方向是检查备库磁盘与是否有长查询。

示例三:根据 sent/flush/replay 定位瓶颈 ​

用一个缓慢写入的测试场景,可以直观看到四个 LSN 的推进差异。

sql
SELECT sent_lsn, write_lsn, flush_lsn, replay_lsn,
       pg_wal_lsn_diff(sent_lsn, write_lsn)  AS in_network,
       pg_wal_lsn_diff(write_lsn, flush_lsn) AS in_pagecache,
       pg_wal_lsn_diff(flush_lsn, replay_lsn) AS in_replay
FROM pg_stat_replication;
text
 sent_lsn  | write_lsn | flush_lsn | replay_lsn | in_network | in_pagecache | in_replay
-----------+-----------+-----------+------------+------------+--------------+-----------
 0/3020000 | 0/301F000 | 0/301E000 | 0/301D000  |       4096 |         4096 |      4096
(1 row)

当 in_network 大而其他两项小时,瓶颈在网络;当 in_pagecache 大时,瓶颈在备库刷盘;当 in_replay 大时,瓶颈在重放。这是一个非常清晰的「分层归因」方法。

示例四:监控复制槽是否濒临失效 ​

sql
SELECT slot_name,
       active,
       wal_status,
       restart_lsn,
       pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS lag_bytes,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS lag_pretty
FROM pg_replication_slots
ORDER BY slot_name;
text
   slot_name   | active | wal_status | restart_lsn | lag_bytes | lag_pretty
---------------+--------+------------+-------------+-----------+------------
 standby1_slot | t      | reserved   | 0/2FF8D18   |   1048576 | 1024 kB
 standby2_slot | f      | unreserved | 0/1A00000   | 419430400 | 400 MB
(2 rows)

standby2_slot 处于 active = f(备库掉线)且 wal_status = unreserved(保留即将不再受 max_wal_size 保护),落后 400MB。这是一个明确的预警:如果它继续掉线,wal_status 会变成 lost,届时必须重建该备库。处理方式是尽快恢复备库连接,或根据 max_slot_wal_keep_size 的设定评估风险。

示例五:检查重放冲突 ​

sql
SELECT datname,
       confl_snapshot,
       confl_lock,
       confl_bufferpin,
       confl_deadlock
FROM pg_stat_database_conflicts
WHERE confl_snapshot + confl_lock + confl_bufferpin + confl_deadlock > 0
ORDER BY confl_snapshot DESC;
text
 datname  | confl_snapshot | confl_lock | confl_bufferpin | confl_deadlock
----------+----------------+------------+-----------------+----------------
 appdb    |            128 |          4 |               0 |              0
(1 row)

confl_snapshot = 128 说明有 128 个查询因为「所需行版本已被清理」而被取消。如果这个数字在快速上升,说明备库上的长查询与主库的清理正在激烈竞争。解决思路见「最佳实践」中关于 hot_standby_feedback 的部分。

注意事项 ​

pg_stat_replication 只存在于主库(发送方)。 在备库上查询它通常返回零行,这不代表异常——备库要看的是 pg_stat_wal_receiver。在级联复制中,上游备库也会有自己的 pg_stat_replication(因为它在向下游发送)。

pg_stat_wal_receiver 返回零行说明 walreceiver 没在运行。 常见的三种情形:这台机器不是备库、备库刚重启还没进入接收、复制尚未建立。零行不等于「延迟为零」,不要把两者混淆。

write_lsn 与 written_lsn 不可用于完整性判断。 pg_stat_replication.write_lsn 与 pg_stat_wal_receiver.written_lsn 都表示「已写入但未刷盘」,官方明确提示不应用于数据完整性检查。要判断安全进度,用 flush_lsn / flushed_lsn。

不存在 received_lsn 字段。 该字段在 PostgreSQL 13 被改名为 flushed_lsn,并新增了 written_lsn。引用旧名字会导致 SQL 报错,也说明文档过时。

reply_time 是 PostgreSQL 14 才有的字段。 在更早的版本上查询它会报错。如果需要在旧版本上判断连接活性,只能间接推断。

lag 字段不是「当前落后多久」。 write_lag / flush_lag / replay_lag 描述的是最近样本的处理耗时,不是绝对滞后。把它当作「备库落后 X 秒」会产生误判。

字节延迟与时间延迟必须一起看。 空闲时段字节延迟为 0 但时间延迟在增长,这是正常的;写入高峰期两者同时增长才是真正的延迟问题。

pg_stat_database_conflicts 是累计值。 它只增不减(除非重置统计),所以绝对值的意义有限,要看单位时间的增量。

监控查询本身要注意权限。 普通用户通常能读这些视图,但 pg_stat_wal_receiver.conninfo 可能包含连接信息,应当只授予可信的监控角色。这也意味着不要在连接串里写明文密码。

采样频率不宜过高。 过密的采集会给主库带来额外连接与查询开销,反过来影响复制性能。15 秒到 1 分钟是比较常见的区间。

最佳实践 ​

  1. 备库必须唯一命名。 在 primary_conninfo 中设置唯一的 application_name,否则 pg_stat_replication 里会出现多行同名记录,故障时无法快速定位。

  2. 主备两侧的视图都要采集。 主库采 pg_stat_replication + pg_replication_slots,备库采 pg_stat_wal_receiver + pg_stat_database_conflicts + pg_last_wal_replay_lsn()。单侧采集必然存在盲区。

  3. 用四段 LSN 差做分层归因。 把 pg_current_wal_lsn → sent_lsn → write_lsn → flush_lsn → replay_lsn 各段差值都采集下来,延迟问题就能自动定位到「发送 / 网络 / 刷盘 / 重放」中的一段。

  4. 把 reply_time 作为连接活性心跳。 用 now() - reply_time 与 wal_sender_timeout 的比例做阈值,能在主库主动断开之前提前发现「假死连接」。

  5. 对 wal_status = lost 立即告警。 这个状态意味着槽已不可用,对应的备库必须重建。越早发现,越有时间在业务低峰期处理。

  6. 对不活跃的复制槽单独告警。 active = false 且持续较长时间的槽会不断累积 WAL,是磁盘写满的常见根因。PostgreSQL 18 可以用 idle_replication_slot_timeout 自动失效这类槽。

  7. 延迟告警结合写入速率。 同时采集主库的 WAL 生成速率,把字节延迟换算成「秒」来设阈值,比直接用字节数更贴近业务感知。

  8. 记录并分析延迟的时间分布,而不只看平均值。 平均值会掩盖夜间批量任务造成的尖峰,而这些尖峰恰恰是容量规划的依据。

  9. 把重放冲突纳入常规监控。 备库查询被取消会直接影响下游业务,confl_snapshot 的增长率上升应当触发排查。

  10. 监控备库的磁盘与 pg_wal 目录。 备库磁盘写满会直接导致实例崩溃,而 pg_wal 异常增长通常指向接收积压或重放停滞。

  11. 让监控能回答「复制断了多久」。 除了「是否断」,还要记录断开与恢复的时刻,用于事后复盘网络与主机的稳定性。

  12. 把告警接入值班流程并演练。 一个只会亮灯、没人响应的告警等于没有告警。定期演练「收到复制告警后的处置流程」,验证 Runbook 是否可用。

下一节

继续学习:级联复制

参考链接 ​