问题背景
流复制的"连接仍在"并不等于灾备链路健康。主库可以看到备库连接,但备库可能长期未回放 WAL;复制延迟的字节数可能很大,却未必立刻影响既定恢复目标;同步复制场景中,一台备库的异常还可能扩大为业务提交延迟。
常见的失误,是把 pg_stat_replication 的一行输出直接交给模型,让模型判断"是否正常"。这会带来两个问题:第一,模型不应决定阈值、切换或写库等控制动作;第二,原始系统视图包含角色名、地址、应用名乃至业务库名,直接外发会扩大数据暴露面。
更稳妥的结构是三层分工:采集器只读取并规范化事实,规则引擎只执行预先批准的判断,模型只根据已经脱敏且结构化的结果生成面向人的摘要和排查建议。模型不可用时,告警与巡检结论仍应成立。
先明确复制语义
在流复制中,主库为每个直连备库维护 WAL 位点。常见字段包括:
sent_lsn:主库已发送给备库的 WAL 位置。write_lsn:备库已写入但不一定已刷盘的位置。flush_lsn:备库已刷盘的位置。replay_lsn:备库已回放的位置。state:如streaming、catchup等连接状态。
在支持 pg_wal_lsn_diff 的 PostgreSQL 版本中,可以用主库当前 WAL 位置减去 replay_lsn,得到近似的回放积压字节数。它是位点差,不是时间延迟:WAL 产生速率变化时,相同字节数对应的恢复时间并不相同。因此,告警策略通常需要同时考虑"是否正在流式复制""位点积压"和"最近收到回放进度的时间"。
主库的 pg_stat_replication 只能反映主库观察到的直连下游;备库自身还应检查 pg_stat_wal_receiver 和 pg_last_wal_replay_lsn()。级联复制、逻辑复制以及不同 PostgreSQL 大版本的字段与语义可能不同,投产前应以目标版本官方文档和拓扑为准。
架构与权限边界
建议让巡检任务运行在受限的运维环境中,并使用独立只读角色。角色不应拥有建表、写数据、复制槽管理或切换权限。
sql
CREATE ROLE replication_observer LOGIN PASSWORD '由密钥系统注入';
GRANT pg_monitor TO replication_observer;
pg_monitor 可读取多项统计视图,是否符合组织的最小权限要求,需要由数据库管理员审查。若不能授予该预定义角色,可针对实际查询涉及的对象逐项授权,并在升级 PostgreSQL 后重新验证。
采集链路可按以下顺序实现:
- 在主库以只读账号查询复制视图。
- 仅保留状态、位点差、时间戳和匿名节点标识等必要字段。
- 在本地执行阈值规则,形成
ok、warning或critical。 - 把结构化结论发送给监控系统;只有需要值班摘要时,才调用模型接口。
- 将原始采集结果、规则版本、输入摘要和模型输出分别留档,便于复盘。
最小化采集 SQL
以下查询应在主库执行。application_name 可能包含环境或业务信息,示例中仅在本地做匿名化映射,不将其作为模型输入。对于尚未回复回放位点的连接,结果会保留为 NULL,规则必须显式处理,不能默认为零延迟。
sql
SELECT
application_name,
state,
sync_state,
pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS replay_lag_bytes,
EXTRACT(EPOCH FROM now() - reply_time) AS reply_age_seconds
FROM pg_stat_replication
ORDER BY application_name;
阈值不宜直接照搬。先根据恢复目标、可用磁盘、链路带宽和可接受追赶时间确定内部标准。例如,streaming 以外的状态可被标记为需要关注;reply_age_seconds 为空或超过内部上限时应升级;积压字节数则应结合 WAL 产生速率解释。
可执行巡检脚本
下面的 Python 脚本把数据库连接信息全部从环境变量读取,查询结果只在本进程中使用。它输出可被日志系统采集的一行 JSON,并通过退出码区分严重程度。运行前安装 psycopg:pip install psycopg[binary]。
python
import json
import os
import sys
from hashlib import sha256
import psycopg
MAX_LAG_BYTES = int(os.environ.get("MAX_REPLAY_LAG_BYTES", "1073741824"))
MAX_REPLY_AGE = int(os.environ.get("MAX_REPLY_AGE_SECONDS", "120"))
SQL = """
SELECT application_name, state, sync_state,
pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS replay_lag_bytes,
EXTRACT(EPOCH FROM now() - reply_time) AS reply_age_seconds
FROM pg_stat_replication
ORDER BY application_name
"""
def node_id(name):
return sha256((name or "unknown").encode()).hexdigest()[:12]
def assess(row):
lag = row[3]
age = row[4]
if row[1] != "streaming" or lag is None or age is None:
return "critical"
if lag > MAX_LAG_BYTES or age > MAX_REPLY_AGE:
return "warning"
return "ok"
with psycopg.connect(os.environ["PG_DSN"]) as conn:
with conn.cursor() as cur:
cur.execute(SQL)
rows = cur.fetchall()
replicas = []
for row in rows:
replicas.append({
"node": node_id(row[0]),
"state": row[1],
"sync_state": row[2],
"replay_lag_bytes": row[3],
"reply_age_seconds": row[4],
"severity": assess(row),
})
rank = {"ok": 0, "warning": 1, "critical": 2}
overall = max((item["severity"] for item in replicas), key=rank.get, default="critical")
print(json.dumps({"check": "postgres_replication", "severity": overall, "replicas": replicas}))
sys.exit({"ok": 0, "warning": 1, "critical": 2}[overall])
例如,以 systemd timer 或 Kubernetes CronJob 定时执行时,应由部署平台注入 PG_DSN、MAX_REPLAY_LAG_BYTES 和 MAX_REPLY_AGE_SECONDS。不要把 DSN、密码或 API 密钥写进镜像、仓库或日志。脚本返回非零退出码不一定适合所有调度器的重试策略:若平台会把任何非零退出反复重试,建议同时保留结构化日志,并由监控规则对 severity 告警。
模型归纳应是可选支路
模型适合把一组已经判定的结果压缩成值班摘要,例如指出"哪个匿名节点积压最高、规则命中了什么条件、下一步先查什么"。它不应执行 pg_promote()、修改 synchronous_standby_names、清理复制槽,或替代故障切换审批。
可以定义稳定的输入契约,只传递巡检脚本输出、拓扑类别和内部运行手册中允许外发的条目:
json
{
"role": "replication-incident-summarizer",
"input": {
"severity": "warning",
"topology": "primary-standby",
"replicas": [
{"node": "a91d7f03e2c1", "state": "streaming", "replay_lag_bytes": 2147483648, "reply_age_seconds": 8, "severity": "warning"}
]
},
"required_output": ["observations", "possible_checks", "escalation_condition"],
"constraints": ["不得建议自动故障切换", "不得臆测未提供的原因", "无法判断时明确说明"]
}
调用端应为模型设置超时、有限重试、响应长度上限和降级文案,并校验输出是否为预期 JSON。若供应商文档明确提供与所用 SDK 兼容的接口,可把其基地址和密钥作为部署变量接入;例如,HaerAPI(https://www.haerapi.com)可以作为需要评估的模型接入候选之一,但具体认证方式、模型名称、限额和数据处理范围必须以其当前文档为准。
模型调用前还应完成三项检查:确认输入是否含个人信息、主机地址、库名或业务标识;确认供应商、区域和保留策略满足组织要求;确认审计日志只记录必要的请求摘要,且不会反向泄露密钥或敏感上下文。
常见问题
只有一台备库时,pg_stat_replication 返回空行怎么办?
先确认查询是否在主库执行,再检查备库的连接与认证配置。空行不能被解释为"无延迟";对要求具备备库的环境,应将其作为拓扑不符合预期的信号。对于故意不配置物理备库的实例,则应在资产清单中明确排除该检查。
积压很大,为什么业务没有报错?
异步复制下,主库提交通常不等待备库回放,因此应用写入可以正常完成。风险主要体现为故障时可能丢失尚未复制或尚未回放的数据,以及备库无法按恢复目标接管。是否影响业务取决于复制模式和恢复承诺,不能只根据一项指标下结论。
能否用模型直接判断是否切换主备?
不建议。切换涉及法定拓扑、客户端连接、数据一致性、隔离操作和回滚预案。应由经过演练的自动化流程和人工审批共同控制;模型最多提供证据摘要与检查清单。
规则阈值怎样避免频繁告警?
使用持续时间窗口和恢复阈值。例如连续多个采样周期超过上限才触发,恢复到更低阈值后才关闭。窗口长度必须与采样周期、恢复目标及值班响应时间一起设定,并在故障演练或历史事件复盘后调整。
总结
可靠的复制巡检不是让模型"看懂数据库",而是先用数据库自身可验证的状态建立事实,再用明确规则完成分级,最后把模型限制在解释和归纳的位置。这样既保留了模型在值班沟通中的价值,也确保模型服务不可用、输出错误或合规审查收紧时,核心监控和处置链路仍然可运行。