跳转到主要内容

这是本节的多页打印视图。 .

返回本页常规视图.

设计归档

PG Exporter 的架构决策、被否决方案与实现契约

设计记录解释 PG Exporter 的架构、备选方案与取舍。每篇注明状态、源码边界和最后核验日期。

快照直方图已随 v1.4.0 发布。复制采集器修正与目标状态约定已随 v1.5.0 发布。Composite 模式、缓存 pgBackRest 指标和 PostgreSQL 日志指标已在开发分支实现,尚未进入稳定版 v1.6.0。独立可观测性产品提案已被取代。

稳定版操作方法见使用手册,已发布变更见发布归档。

1 - SQL 采集的总时限与客户端取消

状态: 已实现,纳入 v1.6.0。
决策/最后核验: 2026-11-09。
范围: main 上的 v1.6.0,源码 6bee809,解决 #116,未随 v1.5.0 发布。
交付: 源码已提交并推送为 6bee809,本地验证与 Test Release 通过。带版本号的压缩包、RPM/DEB 与校验和已为草稿构建;公开标签、镜像发布、部署与生产验证仍是独立关卡。

决策

此前查询从 context.Background() 派生 context,指标客户端断开后工作仍可能继续,后续 HTTP 请求也可能无限等待 exporter 锁。本次限制这些工作,同时保留失败隔离与缓存复用。

最多接纳两个指标请求,涵盖采集、等待及响应写入。一次只执行一轮采集,第二个抓取者可以在剩余请求预算内等待同一把采集锁,更多请求返回 HTTP 503。等待可以取消,不启动额外 goroutine,也不执行 SQL。这样支持双 Prometheus 抓取,且不引入无界队列或共享任务的取消归属问题。

请求预算沿用现有 30 秒 HTTP 写入时限,合法、有限且为正数的 X-Prometheus-Scrape-Timeout-Seconds 请求头可以缩短它;排队也消耗同一预算。预检继承请求 context;采集在每个数据库和采集器开始前检查 context,过期后不再启动新的采集器。已开始执行且设有 timeout 的采集器,其谓词与主查询共用同一时限,即使请求结束也会执行完毕,结果照常缓存供后续抓取使用。负数 timeout 取消单查询期限,因此只有这类查询会随请求一起取消。

沿用原有失败缓存策略:刷新失败(包括被取消的无上限查询)时,清空该采集器结果,并按正常 TTL 记录此次尝试,下个请求不会立即重跑。客户端离开不会把健康的有界刷新变成失败:fatal 采集器不会向其他抓取者报告 pg_up 0,TTL 为 60 或 300 秒的容量与膨胀采集器也不会在整个 TTL 内缺失。例外是 timeout 为负数的 fatal 采集器:取消它会在 TTL 内缓存 fatal 失败,因此 fatal 采集器应保留 timeout。因客户端离开或请求预算耗尽而中断的采集轮次的汇总日志为 INFO,不计入 pg_exporter_scrape_error_count 与 pg_exporter_server_scrape_error_count,这两个计数继续只统计连接错误与 fatal 采集器错误。其他采集器的缓存保持有效。不发布中断采集器的部分结果,也不把过期值悄悄当作新数据返回。TTL 为零或 --disable-cache 仍表示每次抓取都刷新。因此较长 TTL 也会延迟失败后的恢复,这是已有权衡,不增加重试调度器。

采集形成响应快照后,在编码和网络写入前释放采集锁。请求直到响应结束仍占用接纳名额,从而同时限制等待和慢响应。保留 Prometheus 编码、压缩、Go/进程指标及 HTTP 自监控;标准采集器接口继续可用,不在 exporter 上保存可变请求 context。

备选方案与边界

一律立即拒绝重叠请求会给双抓取者制造不必要的缺口;取消后清零重试时间会反复运行昂贵查询并重建连接,评审后放弃这两个方案。同样放弃随请求取消有界查询:一个客户端断开会变成所有抓取者共享的失败缓存,而且 lib/pq 每次取消都会关闭连接。让正在执行的采集器执行完毕,代价通常是预算过期后最多再占用采集锁该采集器自身的 timeout(默认配置最长 2 秒)。也放弃在同一采集器的谓词与主查询之间中止:那需要单独的“未执行”状态和回滚,省下的工作仍受同一时限约束。无限返回旧结果会掩盖数据陈旧,需要单独的新鲜度契约。本次不增加后台刷新、共享 gather、并行调度、新参数,也不迁移驱动。

预算过期会停止采集并取消无上限 SQL。正在执行的有界采集器可能让响应超出预算,通常最多为其自身 timeout。网络阻塞时驱动可能占用更久,v1.5.0 亦然,因此预算与查询 timeout 都不是硬性的墙钟上限,也不预留固定编码余量;HTTP 写入期限仍是传输层限制。尤其是 lib/pq 建连握手需要合适的 DSN connect_timeout,仅有查询 context 无法约束每个网络阶段。独立健康探测见目标状态记录。

错误计数边界

中断轮次不增加 pg_exporter_scrape_error_count 和 pg_exporter_server_scrape_error_count,轮次汇总日志为 INFO interrupted;查询错误计数与 fatal 查询的 ERROR 日志仍然保留。因此,恰好与请求取消重叠的真实失败可能不计入这两个轮次计数。请求没有取消时,连接/预检失败和 fatal 查询失败(包括自身超时)仍按错误计数;非 fatal 失败仍体现在查询级计数中。

验收

验证连续取消不会绕过 TTL,健康采集器及有效缓存仍可用,并在 TTL 过期后恢复尝试。覆盖双抓取重叠、取消等待者、接纳名额已满、取消无上限 SQL、有界 SQL 执行中客户端离开后结果仍须缓存、中断的采集轮次不得增加错误计数,以及慢响应不能占用采集锁。真实 PostgreSQL 须确认取消、SQL 尝试次数受限及后续抓取成功,另行验证 PgBouncer。本地验收不等于发布或生产负载保证。

本地验证

完整 race 测试与重点并发用例连续十轮通过。在 PostgreSQL 18.6 上,被取消的刷新在 TTL 内只执行一次;后续六次请求返回健康指标,没有重复失败 SQL,TTL 过期后可以成功重试。五轮双抓取均返回 HTTP 200,每轮只刷新一次 SQL。测试还覆盖取消等待者、接纳名额耗尽、部分行、谓词/预检取消,以及慢响应结束前释放采集锁。本次未增加后台刷新或旧值回退。

上述取消实测早于一项评审调整,该调整只影响 timeout 为正数的查询。调整后在 PostgreSQL 18.6 上,0.8 秒的有界查询执行期间客户端离开或 0.3 秒预算到期,查询都会执行完毕;预算到期的请求在 0.8 秒后返回 503,下一次抓取直接复用结果,不再执行。3 秒的无上限查询在客户端于 1.5 秒离开时被 PostgreSQL 取消(SQLSTATE 57014),没有残留活跃后端,并在 TTL 内保持缺失。调整前,同一有界查询会被取消,并在整个 TTL 内缺失。回归测试确认,fatal 采集器执行中客户端离开,下一个抓取者不再看到 pg_up 0。

第二轮评审发现,取消后正在执行的采集器还会继续执行其余谓词与主查询,并且中断的采集轮次会使两个错误计数增加、记录 error 日志。上文契约已改为按采集器描述这一行为,中断轮次也不再计数。在 PostgreSQL 18.6 上,0.8 秒的有界 fatal 查询执行中客户端离开,此前两个计数均为 1 并有 error 日志;修正后均保持 0,只记 info 日志,下一次抓取返回 pg_up 1 与缓存指标。回归测试改用显式同步而非 sleep,并确认真实连接失败仍会计数;完整测试在 race 检测下连续 20 轮通过。

2 - 保留集群进度与准确的关系标识

状态: 已实现,纳入 v1.6.0。
决策/最后核验: 2026-11-09。
范围: main 上的 v1.6.0,源码 6bee809,解决 #115,涵盖 PostgreSQL 12+ 的 vacuum、建索引与 cluster 进度,未随 v1.5.0 发布。
交付: 源码已提交并推送为 6bee809,本地验证与 Test Release 通过。带版本号的压缩包、RPM/DEB 与校验和已为草稿构建;公开标签、镜像发布、部署与生产验证仍是独立关卡。

决策

进度视图报告整个实例的任务,但 relid::regclass 只能在当前连接数据库中解析名称。其他数据库的 OID 可能被解析成不相关的本地关系。

保留 pg_vacuuming、pg_indexing 与 pg_clustering 的 cluster, primary 标签。记录属于当前连接数据库,或关系是系统目录时,用 relid::regclass::text 解析 relname:小于 16384(FirstNormalObjectId)的 OID 由 initdb 分配,在每个数据库中都指向同一系统目录。其他数据库的其余关系直接使用数字 OID 文本,并保留 datname 与 pid 标识任务。不滤除跨库记录。

保留指标名称、标签、类型、版本范围、TTL,以及每次刷新三条集群级查询。关闭自动发现、排除数据库或监控角色无法连接某库时,仍按进度视图原有的可见性规则覆盖跨库任务,不新增连接或名称查询。

权衡与未采用方案

数字 OID 可读性不如关系名,但准确表达了主连接能够获取的标识。本次解决错误表名,且不丢失系统目录名称(例如其他库中 pg_attribute 的 VACUUM),明确不承诺用户关系的跨库名称解析。

放弃逐库采集:它把每次刷新的查询数从三条增加为每库三条,即使没有进度任务也要执行,同时会丢失被排除或无法连接数据库的进度。额外为活跃任务创建名称查询连接会重复自动发现及权限处理;重命名标签则需要单独的指标迁移。基础备份进度保持不变。

验收与升级

执行 PostgreSQL 12–16、17、18+ 的 SQL 分支,用相同关系 OID 构造本地与外库记录,验证本地名称、外库数字 OID、外库系统目录名称及每个任务唯一的样本。检查主/备角色计划、发现连接不重复执行集群采集器,并确认关闭自动发现或排除数据库时仍有跨库进度。

同时更新分片 YAML 和生成配置。现有安装需要合并或替换这些定义,仅升级二进制不会应用 SQL 修正。本地 SQL 测试不代表生产负载验收。

本地验证

PostgreSQL 12、17、18 以监控角色通过 SQL 集成测试,覆盖三组查询版本分支。临时进度视图中,本地与外库任务使用相同关系 OID,本地任务输出名称,外库任务输出数字 OID。PostgreSQL 18 的真实建索引任务在关闭发现、数据库被排除,以及监控角色缺少该库 CONNECT 权限时均保持可见,每个指标只出现一次。计划测试覆盖 PostgreSQL 12–19,防止发现连接重复执行集群采集器。

评审跟进在测试数据中加入外库 pg_catalog.pg_attribute 记录。PostgreSQL 18.6 上输出 pg_attribute,此前的条件输出 1249。对其他库中 pg_attribute 的限速 VACUUM 实测结果相同,而该库用户表仍输出 OID。此次跟进未重跑 PostgreSQL 12 与 17。

3 - 容器信任根与发布检查

状态: 已实现,纳入 v1.6.0。
决策日期/最后核验: 2026-11-08/2026-11-09。
范围: 基于 v1.5.0 源码 1891254 的容器与发布验证改动,吸收 68c95ba 的打包修正。不修改 exporter 运行时代码或数据库驱动。
交付: 源码 370b9fa 已纳入 v1.6.0 的 6bee809 并推送。两条 Docker 构建路径、amd64/arm64 GoReleaser 镜像、三个 DEB 架构、Go 测试、静态检查和文档构建均已验证。CI 与草稿制品以 v1.6.0 发布关卡为准;公开标签、镜像发布、部署和生产验证仍属独立步骤。

问题

v1.5.0 的两条 Docker 构建路径都以 scratch 为最终镜像,未携带系统 CA 证书包。普通 Dockerfile 还漏了 LICENSE;GoReleaser Dockerfile 已包含该文件。OCI 许可证标签只能标识许可证,不能携带许可证正文。

当前 lib/pq 驱动在省略 sslrootcert 时使用 Go 的系统信任根。容器没有这些根证书,便无法在 verify-ca 或 verify-full 模式下验证由公共 CA 签发的服务器证书。v1.5.0 目标状态设计记录 已如实说明此前需要挂载 CA。

决策与不变量

最终镜像继续使用 scratch。将发行版 CA 证书包复制到 Go 标准信任库路径 /etc/ssl/certs/ca-certificates.crt,并让两条构建路径都包含 /LICENSE。源码构建从 Go 构建阶段取得证书包;发布构建在独立 Alpine 阶段安装 ca-certificates,只将证书包复制进最终镜像。Docker 构建上下文的白名单须允许 LICENSE。

补入公共信任根不会自动启用 TLS。省略 sslmode 时仍设为 disable;验证证书链与主机名应使用 sslmode=verify-full。私有 CA 仍需挂载,并通过 sslrootcert 指定。发布镜像不得包含私钥或组织私有信任根。

根证书在镜像构建时固定,更新 CA 需要重新构建和分发镜像。运行时安装软件包、加入 Shell、迁移驱动、调整双向 TLS 和改变默认 TLS 模式,均不在本次范围内。

备选方案

要求每个部署都挂载公共根证书会保留打包缺口,并增加普通证书验证连接的配置负担。将最终镜像改为完整 Linux 发行版,又会为静态二进制引入不必要的运行时文件。复制 CA 证书包可以沿用现有镜像结构,同时补齐缺失的运行时数据。

验收与发布边界

CI 实际构建普通 Dockerfile。发布测试构建真实的 GoReleaser snapshot,包括 amd64 与 arm64 镜像,但不发布。两条路径共用以下镜像检查:

  • 将镜像内的许可证和默认配置与源码文件逐字比较,解析 CA 证书包,并运行二进制。
  • 用 verify-ca 和 verify-full 连接临时 PostgreSQL TLS 实例:未知私有 CA 必须失败,显式指定可信 CA 必须成功。
  • 通过临时挂载将测试 CA 追加到镜像证书包,不指定 sslrootcert,验证系统信任库查找路径;verify-full 必须拒绝主机名不匹配。

TLS 用例使用本地服务与临时生成的证书,不依赖公共 TLS 服务。它们验证证书包解析和信任库查找路径,不声称覆盖每个公共 CA 或完整的双向 TLS 组合。

发布产物检查保留 RPM 兼容性断言,补充 DEB 元数据、文件内容、配置权限、conffiles 和安装检查。检查所有 DEB 架构的内容,在容器原生架构上安装软件包,并以 prometheus 身份运行二进制。校验和必须验证成功。

这些检查只验收构建候选。v1.6.0 手册与发布正文描述本次修正,在正式公开版本与制品可用时一同上线。

4 - 目标身份与一致的健康检查响应

状态: 原始契约已随 v1.5.0 发布;2026-11-09 健康修订已纳入 v1.6.0。
决策/最后核验日期: 原始决策为 2026-10-23;健康修订与本地验证为 2026-11-09。
范围: v1.5.0,源码 1891254(修复基线 9b378cb),涵盖连接串与数据库身份、由元数据驱动的采集器选择、HTTP 健康状态快照、YAML 时间配置及指标路由校验。
交付: 源码已提交并推送,v1.5.0 标签指向 18912540fe5edce0ed0ee81fac0d989738ecaa87;CI 与正式发布工作流通过,GitHub Release、RPM/DEB/压缩包和 Docker 镜像已发布。未据此声称生产部署或生产负载验收完成。

目标身份

URL 语法或查询参数转义错误必须阻止启动,且不能打印原始连接串或含有连接串的解析错误。共享 URL 解析器还须拒绝解码后为空、包含 Unicode 空白或 = 的查询参数名,防止这些名称进入驱动解析或绕过密码遮盖。诊断使用固定文本,不包含连接串或错误参数名。静默丢弃错误参数可能连显式 TLS 设置一起丢掉。省略 sslmode 时仍默认使用 disable;有效的显式设置保持不变。

所支持 URL 形式中的数据库名交给现有 lib/pq 解析器确定,涵盖路径、显式查询参数和数据库别名。Exporter 自行模拟它们的优先级容易把 PgBouncer 当成 PostgreSQL,或错误识别自动发现的数据库。发现新库后,替换连接串时移除 dbname、database 两个查询参数,将库名写入唯一且正确转义的路径,保留其他参数及库名的原始字符。Service 文件中的目标解析,以及仅依赖连接默认值推导的 PgBouncer 身份,不在本次修复范围内。

选用 PG_EXPORTER_URL_FILE 时,文件内容为空或仅含空白必须阻止启动。此处检查的是保存连接 URL 的文件,不是 TLS 私钥文件。

采集器选择

元数据预检成功后,用户名、Schema 集合或扩展集合变化也应使查询计划失效,沿用版本、数据库名和恢复状态变化时的处理方式。比较集合而非返回数组顺序。下一轮采集通过既有计划流程重新选择采集器、重建缓存并重置查询统计;元数据不变则保留现有计划和缓存。安装扩展后不再需要手工重载,也不增加后台线程或额外元数据查询。

健康状态快照

只发布一个原子状态枚举:unknown、down、starting、primary、replica。Up() 和 Recovery() 从这个值推导。每个健康检查 HTTP 请求只读取一次,并同时推导状态码和响应正文。三个分别原子更新的字段并不是一份原子快照;重复读取也可能混合不同探测轮次的状态。

v1.5.0 沿用既有路由、HTTP 状态码、后台探测间隔和采集结果更新健康状态的方式。此决定保证单次响应内部一致,并不把缓存状态变成数据库实时可用性的同步证明。完整状态可存入一个原子整数,无需增加互斥锁。

健康探测独立性(v1.6.0,2026-11-09)

本次修订基于 main 的 9d9804f,解决 #112 与 #113,纳入 v1.6.0 的 6bee809,不属于 v1.5.0。源码已提交并推送;CI 与草稿制品是独立发布关卡,草稿不等于公开发布或生产部署。

v1.6.0 之前,主目标的采集锁会阻塞后台探测;子库采集完成后,旧的主目标观测又可能覆盖较新的探测结果。数据库句柄初始化改用独立的短锁,探测沿用现有连接池,不再等待采集锁。PostgreSQL 主目标池保持三个连接,发现连接保持一个;PgBouncer 主目标池由一个增为两个,容纳串行采集与串行探测各一条连接,避免仅因 exporter 自身占用连接而报告 down。代价是最多增加一个 PgBouncer 连接。探测仍传递连接超时 context,但握手等网络阶段需要合适的 DSN connect_timeout,不承诺硬终止时限。

构造阶段的初始状态之后,HTTP 健康状态只由串行的健康探测更新。采集继续更新 pg_up 与 pg_in_recovery,但不再写入 HTTP 健康快照。路由、状态码与一秒探测间隔不变。健康状态表示最近一次完成的连通性与角色探测;fatal 采集器失败通过采集指标反映,不再成为另一处健康状态写入来源。

这个方案避免引入两个写入来源之间的时间戳仲裁和第二个连接池,不新增健康参数、工作线程,也不在 HTTP 健康处理器中同步执行 SQL。回归测试须覆盖阻塞采集器期间的探测、新角色或失败状态不被子库完成事件覆盖,以及初始化、显式与后台探测并发、停止过程的 race 检查。

指标路由

启动校验必须确认对配置路径的请求选中了指标处理器本身。仅比较匹配的路由字符串并不充分:mux 的重定向也可能返回相同字符串,却没有进入指标处理器。校验时比较返回的处理器与注册的指标处理器,拒绝落入重定向或其他端点的路径,保留在当前 mux 下确实可达的转义路径,无须一概禁止百分号转义。

验证与边界

回归测试覆盖显式数据库身份、特殊字符和冲突参数、错误 URL、Schema/扩展/用户资格变化、元数据顺序不变、并发健康状态切换,以及两种 HTTP mux 下的可达转义路径和重定向。新增用例在修复前失败、修复后通过。

v1.5.0 发布前验收补充了以下验证:

  • 错误的 TLS 转义在连接前被拒绝;verify-full 对不提供 TLS 的服务器连接失败。日志不包含连接密码,空 URL 文件被拒绝。
  • 含冲突参数和特殊字符的自动发现连接返回正确数据库指标;增加与删除数据库、Schema、hstore 后,采集计划按元数据更新。稳定集合保留缓存。
  • 非法单文件重载保留旧定义和缓存;断连后健康检查恢复;物理备库晋升后角色与标签选择更新;并发抓取、健康与重载请求的状态码和正文保持一致。
  • Go race 测试、两种 HTTP mux、go vet、Staticcheck、模块与生成配置一致性、六平台构建、RPM/DEB 安装和 Docker 构建通过。针对有效 UTF-8 URL 身份及实际 HTTP URL 构造的短时模糊测试完成约 60.4 万次输入。
  • 以非超级用户重新验证 PostgreSQL 9.1–9.6、10–18、19 RC1 的默认主库采集,以及 PgBouncer 1.26.1。PG10–13 使用已记录的订阅 OID 授权。此范围不代表穷尽扩展、瞬态故障和生产负载。

证书链/主机名/双向 TLS 的全部组合、SQLSTATE 57P03 的真实启动瞬态和生产负载不在本轮完整验证范围内。Docker 的证书验证仍需按手册挂载 CA。目录加载可跳过错误文件;空 YAML 是合法的零采集器配置;SIGTERM 不等待活跃 HTTP 请求。上述既有行为没有改变。

配套配置校验在转换为 time.Duration 前拒绝 YAML 主查询 TTL、查询 timeout 和谓词 TTL 中非有限或正向溢出的秒数,保留零值、默认值及有限负超时的既有语义。本次不迁移驱动,不整合可选组件,不修改指标名称,也不做通用重构。

本次修订通过了确定性的 race 测试,覆盖阻塞采集器、较新的 primary/replica/down 观测、连接池耗尽、数据库初始化并发,以及采集期间停止健康循环。这些测试不代表真实主备切换或生产负载验收。

PgBouncer 1.26.1 的统计响应被人为延迟时,版本探测仍成功,未误报 down;服务停止与重启也被正确识别。这不代表 PostgreSQL 故障切换或生产负载验收。

5 - 复制采集器的身份、状态与权限修正

订阅身份、接收器状态、复制源权限和关系计数的最小修正

状态: 已随 v1.5.0 发布。
决策日期 / 最近核验: 2026-10-22 / 2026-10-23。
范围: pg_exporter main 提交 d97ba38fc8390984df994b2f9ea58b43ecfc8b89 中的 Collector 修正,基于 72f00a4fa4e6eb7524174f2122b3fe1e1d608db4;PostgreSQL REL_19_RC1 对应 0b8bff042a997fe50fcd1c4c83b9ef1cc9ed8a89,另作 PG9.6–18 的针对性检查。这些修正从 v1.5.0 开始发布,v1.4.1 不包含。
交付: 源码已提交并推送,v1.5.0 标签指向 18912540fe5edce0ed0ee81fac0d989738ecaa87;CI 与正式发布工作流通过,GitHub Release、RPM/DEB/压缩包和 Docker 镜像已发布。未据此声称生产部署或生产负载验收完成。

已选定的修正

问题 决策 保留行为
不同数据库的同名订阅产生重复指标样本 给全部 pg_sub 版本分支添加 datname 原指标族,包括 pg_sub_id;cluster 范围、worker 过滤和数值
Receiver CASE 混入 sender 状态名称 补足真实 receiver 字典并修正 HELP 已有效的 0、4、5 编码;未知与 NULL 仍为 -1
可选 origin 采集器的权限说明不完整 保持 skip: true,说明用户自行授权及版本限制 原 SQL、指标和默认关闭行为
发布与订阅的关系数量统计集合不同 校正 HELP 与手册 SQL、标签、数值和缺失样本行为;不新增序列指标

订阅身份

(datname, subname) 可以唯一标识同一 PostgreSQL 集群中的订阅。上游对 (subdbid, subname) 有唯一约束,数据库名在同一集群内也唯一。查询必须通过订阅 OID 找到真实归属数据库:

JOIN pg_catalog.pg_subscription s ON s.oid = subscription_id
JOIN pg_catalog.pg_database d ON d.oid = s.subdbid

这里的 subscription_id 指现有来源别名:PG19 分支用 s2.subid,更早分支用统计视图中的 subid。把 d.datname 加入结果并声明为 LABEL。不能使用 CURRENT_DATABASE(),不能仅按订阅名关联,也不能把连接库名作为常量标签代替实际归属。

查询仍必须保持每个订阅一行。保留 apply leader 过滤和 PG19 对 sequence synchronization worker 的排除。数据库身份区分不同订阅;worker 过滤避免同一订阅重复出行,两者分别必要。

PG10–13 的额外权限前提

PG10–13 遗漏了 pg_subscription.oid 的默认公开读取权限。上游明确只为 PG14 修正,没有修改旧分支。因此,数据库标签方案在这些旧版本上有一项升级前提:

-- 由管理员在 exporter 主连接所使用的数据库中执行。
GRANT SELECT (oid) ON pg_catalog.pg_subscription TO monitor;

应替换为实际监控角色。授权应在每个可能作为 exporter 主连接的数据库中执行,不能只在订阅所在库执行;它不会授予 subconninfo 权限。PG14+ 在标准目录 ACL 下无需为此关联增加授权;用户自行收紧的 ACL 仍由用户管理。

这是一项部署成本,不是自动权限修复。PG10–13 必须先授权,再部署修改后的配置。缺权限时查询失败,订阅指标缺失,但 HTTP 仍可能是 200;因此验收必须同时检查预期样本与查询错误,不能只检查 HTTP 状态。

替代方案与迁移代价

额外增加 subid 标签可以省掉两个关联及旧版本授权。此次选定数据库身份,是为了便于按数据库阅读、筛选,并与其他 Collector 的标签习惯一致。在此需求下,两个目录关联和一项明确的旧版列授权,比新建权限包装函数或运行时 fallback 更小。不同时添加两种身份标签。

保留 id GAUGE,不把它改成 LABEL 而删除 pg_sub_id。指标族名称与样本数量保持不变,但新标签会产生新时序;应检查对标签敏感的 PromQL 联接与聚合。数据库或订阅改名也会改变身份。跨集群仍由目标标签区分。

Receiver 状态字典

已逐个对照 PG9.6 至 PG19 RC1 的发布边界源码。PG9.6–18 有六个 receiver 状态,PG19 增加 connecting。

Receiver 字符串 编码 适用范围
streaming 0 PG9.6–19
stopping 4 PG9.6–19
connecting 5 PG19;旧服务器不会产生此值
starting 6 PG9.6–19
waiting 7 PG9.6–19
restarting 8 PG9.6–19
stopped 9 PG9.6–19
其他或 NULL -1 全部分支

从 receiver CASE 与 HELP 中移除 startup、catchup、backup。旧编码 1–3 留空,不重新赋义。对当前三个分支及 legacy 的 pg_recv_96 使用一致字典,无需新版本分支或 Go 状态逻辑。

内部枚举存在不代表统计视图一定有可见行。RC1 在 PID 为零或统计未准备好时不返回 receiver 行;无行时不能制造 stopped 或 streaming 样本。保留连接中 sender host / port 为 NULL 的处理。

对抗性测试还发现,legacy receiver 查询在 PG9.6 上调用了 PG10 才引入的 regexp_match()。将两处取首个捕获组的表达式改为标量 substring(... FROM ...),即可让查询在 PG9.6 执行,不增加版本分支、集合返回函数或 conninfo 解析器。正则表达式与得到的 host / port 标签保持一致。这一直接前提随字典修正一起处理。

PG9.6 自身只向非超级用户展示 receiver PID,其余统计字段为 NULL,因此该角色下状态为 -1。修复 SQL 兼容性不会绕过这种可见性规则,也不意味着应向监控账号授予超级用户权限。

最终 legacy 查询已在 PG9.6–12 执行。真实 PG9.6.24 备库向管理员返回 streaming 状态 0,向普通 monitor 返回状态 -1;该监控角色的 HTTP 抓取返回 200 与 11 个 receiver 样本。host / port fixture 覆盖缺失字段、NULL、Unix 路径、IPv6、重复键与带引号输入;PG10–12 的新旧提取表达式结果一致。这里保留原有正则语义,包括其对含空格引号值的既有限制。

可选复制源采集

当前与 legacy 配置已经设置 skip: true,应原样保留。文档明确由用户先准备权限,再显式启用。

采用标准目录 ACL 的 PG14+ 可由管理员给实际监控角色授权:

GRANT SELECT ON pg_catalog.pg_replication_origin_status TO monitor;
GRANT EXECUTE ON FUNCTION
  pg_catalog.pg_show_replication_origin_status() TO monitor;

授权在 exporter 主连接使用的数据库中执行。PG13 及以前保留函数内部的超级用户检查,仅 GRANT 不能让普通监控角色直接使用;除非管理员另行选择并维护访问方案,否则保持可选入口关闭。本次不添加 SECURITY DEFINER 包装函数、自动 GRANT、切换身份重试或伪造零值。

标准 ACL 已允许公开读取 pg_replication_origin 目录,因此不需要重复授权。若部署收紧过目录 ACL,管理员还需自行准备查询所需的目录读取权限。

发布与订阅计数

文档描述当前真实行为:

  • pg_pubrel_count 统计 pg_get_publication_tables() 返回的表,不包括序列。现有 CROSS LATERAL 查询对空发布、纯序列发布不输出行;缺失不是显式的零。
  • PG19 的 pg_subrel_count 按同步状态统计 pg_subscription_rel 中的表与序列。PG19 表状态为 i/d/f/s/r,序列状态为 i/r。
  • 两端计数不构成复制健康的等式。多 publication、对象重叠、分区、refresh 和异步同步都会影响解释。

只修改说明与手册。保留订阅关系查询的 DISTINCT subid, subname,不在此补丁增加 pg_pubseq_count、relkind 标签、补零或新的对象计数定义。

验证证据与剩余关口

最终配置和从上述 main 基线构建的二进制,已由非超级用户监控角色在 PG10.21、11.16、12.22、13.23、14.23、15.18、16.14、17.10、18.4 和 19 RC1 上验证。两个数据库中的同名订阅得到不同标签,重复样本键为零。PG10–13 在列授权前失败,仅在订阅库授权后仍失败,在主连接库授权后通过;始终不能读取 subconninfo。PG10–14 分支输出 14 个样本,PG15–18 输出 18 个,PG19 输出 42 个,差异来自原有指标族数量。

RC1 完整配置验证覆盖 disabled、active、自动发现开关及物理备库。针对性 HTTP 验证在抓取前后均捕获 table-sync / sequence-sync 与 apply worker 并存,订阅样本仍为 42 个。完整默认计划未选择 pg_origin。Receiver 枚举 fixture 覆盖 PG9.6 和 PG10–19 各大版本;另实际观察 RC1 streaming 与 connecting,后者 sender 字段为 NULL 时 HTTP 仍正确输出状态 5。枚举 fixture 不等于实际观察过所有瞬态。

使用真实 origin 条目的测试确认:仅授权视图不足;PG14+ 补充文档中的函数授权后成功,PG13 及更早版本仍拒绝非超级用户。空 origin 目录不足以证明权限正确,因为 LEFT JOIN 可能不调用受限函数。RC1 中,混合发布输出一个表,订阅端统计一个表加一个序列;空发布和纯序列发布不输出行。

本地 Go race 测试、兼容 HTTP-mux 模式、go vet、依赖一致性,以及已有的 schema、版本覆盖、生成文件一致性、常量标签冲突检查均通过。无需修改 Go 实现或依赖。源码验证与双语文档构建独立于发布和生产验收。

实施边界

源码范围限于七个拆分 Collector 文件及两个生成配置包;companion 范围为成对的 Collector 手册和本 Design 记录。手册区分已有权限 / 计数语义与 v1.5.0 的身份、状态字典、PG9.6 SQL 修正。源码审阅、提交、推送、标签与正式制品发布均已完成;生产部署仍是独立动作。

不包含驱动迁移、Gatherer 重构、运行时权限管理、新 Collector 分支、新指标族、订阅关系聚合改写或无关 legacy SQL 修复。

上游依据

6 - 把 PostgreSQL CSV 日志转成持久指标

读取完整 CSV 记录,持久化游标与聚合值,发布有界 PostgreSQL 日志指标

PostgreSQL 日志会记录 SQL 快照可能错过的事件:死锁、认证失败、自动清理取消、检查点、临时文件删除和客户端断连。根据服务端日志策略,还可以记录语句耗时。

状态: 已实现,未发布。
决策日期: 2026-08-24。
最后核验: 2026-10-13。
范围: 源码提交 c0ba58a 的 PostgreSQL 14+ CSV 日志采集器,默认关闭;稳定版 v1.4.1 尚未包含。
取代: 独立可观测性产品提案。
交付进度: 本地测试通过;未合入 main 或发布标签;软件包、部署与生产运行未核验。

本采集器读取一个本地 CSV 日志目录,保存有限的聚合状态,并在 /metrics 暴露 Counter 和 Histogram。原始日志保留在 PostgreSQL 日志文件中。

范围与启用方式

采集器运行在现有 PG Exporter 进程中。指定非空日志目录即可启用:

pg_exporter \
  --pg-log-dir=/var/log/postgresql \
  --pg-log-state-file=/var/lib/pg_exporter/pglog-state.json \
  --pg-log-poll-interval=1s

对应环境变量为 PG_EXPORTER_PG_LOG_DIR、PG_EXPORTER_PG_LOG_STATE_FILE 和 PG_EXPORTER_PG_LOG_POLL_INTERVAL。默认目录为空,状态路径如上,轮询间隔为一秒。

关闭时不创建后台任务、状态锁、状态文件或日志指标,也不增加文件扫描或抓取协调。--dry-run 与 --explain 不启动后台任务。

范围限于本地 PostgreSQL CSV 日志和聚合指标,不包括其他格式、日志发送、查询界面、会话时间线、任意正则表达式或自动根因分析。日志设置仍由 PostgreSQL 配置管理,采集器不提供 pg_log_setting_* 指标。

读取完整 CSV 记录

PostgreSQL 14 到 18 文档采用相同的 26 列 CSV 布局,包含时间戳、进程和会话标识、严重级别、SQLSTATE、消息、应用、后端类型、并行主进程 PID 与查询 ID。

字段可以包含逗号、引号、回车和换行,因此读取器先识别完整 CSV 记录,再解码。逐物理行扫描无法正确处理该格式。

文件末尾尚未写完的记录保持待处理,完整后才提交其偏移量。单条记录上限为 16 MiB。畸形或超大输入触发有界重新同步,并增加解析错误和输入缺口计数。连续结构不匹配会使组件保持不可用,直到恢复有效输入。

文件身份与轮转

后台任务轮询并核对目录。仅靠文件通知无法恢复遗漏事件,也无法在重启后重建状态。

每个游标保存文件身份、代次、路径哈希、字节偏移、大小、内容指纹和读取状态。状态文件不保存明文日志路径。

重命名轮转按文件身份跟踪:读取器继续读完旧文件,为新文件建立游标。文件大小低于已提交偏移时,视为截断,从零开始新代次,并记录轮转和输入缺口。复制后截断可能在轮询发现前丢弃未读字节,采集器无法恢复这些字节。

未读完的文件消失或内容指纹变化,也会记录缺口。已读完且消失的游标可以移除。状态最多跟踪 256 个文件身份,并保留仍有未读内容的游标。

无状态文件的首次启动,从各现有文件的当前末尾开始读取,同时增加状态重置和输入缺口计数,标明新的计数起点。本设计不回填历史日志。

先提交状态,再发布指标

日志 Counter 与 Histogram 必须在 Exporter 重启后保留。游标偏移和聚合值共同提交;只推进其中一个,会导致观测丢失或重复。

一轮后台周期准备候选状态,然后执行:

读取完整记录,更新候选状态
    -> 验证计数器、直方图、标签和上限
    -> 构造并验证指标族
    -> 写入 0600 临时状态文件
    -> fsync 文件
    -> 原子重命名到状态路径
    -> 在支持的平台 fsync 父目录
    -> 发布对应的不可变指标快照

状态文件上限为 4 MiB。在 Unix 上,必须是权限 0600 的普通文件;符号链接和不安全权限会被拒绝。非阻塞状态锁防止两个进程共用同一状态,保存的目录身份必须与配置来源一致。

重命名前崩溃会留下上一份已提交状态。重命名后崩溃,重启可以恢复新状态并重建对应指标。/metrics 只读取已发布快照,不扫描文件、解析 CSV 或写状态。

每轮最多处理 100,000 条记录或 64 MiB。仍有输入时,立即开始下一轮。

指标与序列上限

核心指标族为:

pg_log_records_total{severity}
pg_log_errors_total{severity,sqlstate_class}
pg_log_query_duration_seconds{kind}
pg_log_events_total{category,event}

其他指标覆盖具体 SQLSTATE 代码、检查点与重启点、自动清理、锁等待、临时文件、连接和会话时长。

日志 Histogram 是与游标一同持久化的累计观测,可使用 rate() 等普通 Counter Histogram 查询。显式状态重置会开始新的计数周期。这与描述当前总体、使用 Gauge 序列的 SQL 快照直方图不同。

标签采用固定取值。查询时长的 kind 只有 statement、execute、parse、bind 和 other。具体 SQLSTATE 指标仅接受官方代码,最多保存 128 个已观察代码序列,自定义和未知代码归入 other。总输出上限为 1,200 条序列。

原始 SQL、消息、数据库名、用户名、应用名、关系名、查询 ID、地址、进程与会话 ID、事务 ID 和文件路径都不进入标签或持久状态。一条记录可以更新多个聚合值,但最多产生一个主要分类事件。例如,指标可以显示死锁增加,而不保留相关 SQL 文本。

读取器健康

读取器暴露以下指标:

pg_exporter_log_bytes_read_total
pg_exporter_log_parse_errors_total{reason}
pg_exporter_log_files_watched
pg_exporter_log_last_record_timestamp_seconds
pg_exporter_log_state_persist_timestamp_seconds
pg_exporter_log_rotations_total{type}
pg_exporter_log_input_gaps_total{reason}
pg_exporter_log_state_resets_total

它还使用 pg_exporter_component_*{component="postgres_log"}。输入、权限、解析、状态、上限或指标族冲突错误只将日志组件标为不可用,最后提交的快照继续提供。结合健康、最后成功时间和缺口计数判断数据的新鲜度与完整性。

--disable-intro 移除 Exporter、组件和读取器自监控,保留 pg_log_* 业务指标。

日志策略决定覆盖范围

指标只覆盖 PostgreSQL 实际输出的记录。查询时长分布取决于 log_duration、log_min_duration_statement、采样和协议行为。例如,慢语句阈值产生的是已记录慢语句的分布,而非全部查询的分布。

检查点、自动清理、连接、断连、锁等待和临时文件指标也需要对应服务端日志设置。基于消息文本的事件解析依赖支持的语言和格式;基于 SQLSTATE 的错误分类不依赖本地化措辞。

为服务用户授予指定日志的读取权限和状态目录的写入权限。源日志可能包含 SQL 与客户端数据,应限制访问范围。

备选方案与非目标

  • 在 /metrics 中读取日志,会给数据库抓取增加解析和持久化耗时。后台任务将这些工作分开。
  • 文件通知可以加快发现,但不能替代保证正确性所需的轮询与核对。
  • 首次启动重放全部历史文件,会让初始计数取决于日志保留策略。从文件末尾开始可明确建立基线。
  • 持久化前发布指标可能导致崩溃后重复计数,因此先提交状态。
  • 动态标签与用户匹配规则会使序列数量和保存结构取决于输入,因此采用固定指标目录。
  • 独立端点或可执行文件会为同一有限数据源增加部署单元,因此复用现有进程和端点。

核验与剩余工作

实现位于 exporter/postgres_log*.go。测试覆盖 PostgreSQL 14、16、18 样例;引号与多行字段;未完成记录;重命名与截断;重启、锁和权限;崩溃位置;事件分类;序列上限;冲突;重叠抓取与关闭。

外部 CSV 集成测试待核验。设置 PG_EXPORTER_TEST_PG_LOG_DIR,使用预定服务用户验证真实日志的轮转与重启恢复。

协调规则见 Composite Exporter。CSV 定义和日志设置见 PostgreSQL 14 与 PostgreSQL 18 官方文档。

7 - 把 pgBackRest 指标移出抓取路径

在有界后台任务中执行 pgBackRest,并提供最后一份有效指标快照

pgbackrest info --output=json 提供备份状态。命令可能访问远程仓库并等待存储响应。若在 Prometheus 请求中执行,数据库监控也要承担这段延迟。

状态: 已实现,未发布。
决策日期: 2026-08-23。
最后核验: 2026-10-13。
范围: 源码提交 c0ba58a 的可选 --pgbackrest 组件;稳定版 v1.4.1 尚未包含。
交付进度: 本地测试通过;未合入 main 或发布标签;软件包、部署与生产运行未核验。

因此,PG Exporter 在后台执行 pgBackRest,验证结果后发布不可变快照。/metrics 读取最新快照,无需等待命令。

后台任务
    -> 检测 pgBackRest 版本
    -> 执行 info --output=json
    -> 验证并构造指标
    -> 发布快照

/metrics 请求
    -> 读取快照
    -> 与其他来源合并

执行上限

后台任务直接执行配置的程序,不经过 shell。

控制项 默认值 规则
命令 pgbackrest 使用固定的版本与 info 参数
刷新间隔 2 分钟 最小 10 秒
刷新超时 30 秒 版本检测与 info 共用一个截止时间
标准输出 16 MiB 超限取消命令
标准错误 64 KiB 超限取消命令

后台先执行 version --output=num,旧版本回退到 version,随后执行 info --output=json。取消操作终止直接子进程;一秒的等待上限约束输出管道清理时间。

发布前验证

命令正常退出后,还要检查 JSON 结构、数值字段,以及 stanza 和仓库引用关系。输入最多包含 256 个 stanza 和 4,096 个备份;输出最多包含 20,000 个样本,Prometheus 文本不超过 4 MiB。

只有完整合法的结果才能替换快照。指标族在发布前完成验证,抓取路径只需检查元数据和所有权。

按 Composite 所有权顺序,pgBackRest 排在 PostgreSQL、进程自监控、Patroni 和 PgBouncer 之后。冲突指标族会被省略,组件标记为不可用;其他合法指标族仍可保留。

保留最后一次成功结果

首次成功刷新后,命令、超时、解析或资源上限错误会保留上一份业务指标。组件报告 up=0,增加错误计数,并保留最后成功时间。

刷新失败时,后台仍会根据保留的 JSON 重算备份年龄,使其按刷新间隔反映经过的时间。/metrics 不解析 JSON,也不执行 pgBackRest。

首次成功前没有备份业务指标。PostgreSQL 与其他有效来源继续工作。

健康与并发请求

组件健康使用 component="pgbackrest"。up 表示最近一次采集结果,最后成功时间说明备份数据有多旧。命令失败使用 exec;标准输出超限使用 limit;解析和合并错误使用各自原因。标准错误超限归类为执行错误。

重叠抓取可以读取同一份快照,不会启动第二条命令,也不会更新已保存的组件健康历史。指标冲突仍按 Composite 所有权规则处理。

备选方案与非目标

每次抓取都执行命令,会把 Prometheus 超时和并发请求绑定到仓库操作。后台任务使执行超时和刷新间隔独立于抓取。

独立 Exporter 提供进程隔离,但增加一个服务、端口、软件包和目标。集成组件提供另一种部署选择,不自动迁移现有部署。

通用 shell 命令插件不适合此处。pgBackRest 需要专用 JSON 校验、版本处理、指标定义与恢复规则。本组件接受 pgBackRest 可执行程序,不接受任意命令模板。

核验与运维

exporter/pgbackrest.go 负责命令执行与快照发布,exporter/pgbackrest_metrics.go 负责 JSON 校验与指标构造。测试覆盖版本回退、畸形与过量输出、超时、保留最后成功结果、指标族冲突和关闭。

服务用户需要执行 pgBackRest、读取其配置和仓库凭据的权限。PG Exporter 软件包不捆绑 pgBackRest,使用主机上的备份软件安装。

对组件健康和最后成功时间设置告警。采集失败后,旧备份指标仍可能可见。替换独立 Exporter 前,使用预定服务用户,对同一个仓库比较两者的输出。

8 - 我们最终没有做的 PostgreSQL 可观测性产品

为什么独立事故工作台最终变为 PG Exporter 内的 CSV 日志采集器

我们最初考虑做一个本地 PostgreSQL 事故工作台:读取结构化日志,重建会话时间线,提供交互过滤与终端界面,后续再作为 Agent 运行。

状态: 已于 2026-08-24 被 PostgreSQL 日志指标方案取代。
决策日期: 2026-08-13 开始研究;2026-08-24 调整范围。
最后核验: 2026-10-13。
范围: 独立 PostgreSQL 事故工作台与 Agent 提案。
交付进度: 止于提案,未实现、发布或部署。

眼前需求更小:从 PostgreSQL 日志中提取可靠的运维指标。最终选择是在 PG Exporter 中增加后台采集器。本文记录保留的研究结论,以及放弃独立产品的原因。

最初提案

事故发生时,DBA 可能只有本机日志,没有集中日志服务。理解 PostgreSQL CSV、SQLSTATE、会话与轮转的工具,可以在不上传 SQL 文本的情况下生成时间线。

提案包括:

  • PostgreSQL 14+ CSV 解析,包括多行字段;
  • 按时间、严重级别、SQLSTATE、数据库、用户、应用和会话过滤;
  • 跨重命名日志文件保留上下文;
  • 交互式终端界面;
  • 本地处理与低基数 Prometheus 指标;
  • 后续增加持久游标和多个输出目标的守护进程。

保留的结论

PostgreSQL 14 到 18 文档给出了相同的 26 列 CSV 布局。示例导入表以 (session_id, session_line_num) 为主键。CSV 字段可包含逗号、引号和换行,因此解析器必须读取完整记录。

SQLSTATE 比本地化消息文本稳定,是错误分类的主要依据。检查点、自动清理、锁等待、临时文件和连接消息,需要针对支持版本和语言测试匹配规则。

原始 SQL、用户名、数据库名、客户端地址和会话标识不适合作为默认指标标签。它们会暴露源数据,并使序列数量随工作负载变化。固定事件类别和有限分布更适合 Prometheus。

轮转、部分写入、重启恢复和游标持久化决定计数是否可靠,必须从一开始就纳入功能契约。

调整范围的原因

工作台需要独立的命令、输出结构、界面、软件包、文档和发布周期。Agent 还需要队列、重试、保留策略和服务管理。这些职责超过了指标导出器的范围。

PG Exporter 已有抓取目标、/metrics、打包和 Pigsty 集成;开发中的协调层也提供组件健康指标。有限范围的日志采集器可以复用这些能力,满足眼前需求。

因此,我们停止独立产品、仓库和品牌工作,将交互调查与日志发送移出范围。

替代方案

2026-08-24 的决策是:

为 PG Exporter 增加默认关闭的 PostgreSQL CSV 日志后台采集器,在现有 /metrics 端点发布有限的运维指标。

替代方案保留完整 CSV 解析、文件身份、轮转与截断处理、持久游标与聚合值、原子持久化、不可变快照、固定标签和明确的输入缺口指标。

日志发送、交互查询、会话时间线、任意正则表达式和自动根因分析仍不在范围内。实现契约见 PostgreSQL 日志指标。

何时重新评估

独立工作台需要一个现有日志工具无法满足的本地调查场景,也需要用户验证事件模型,以及维护者承担发布和支持周期。在这些需求明确之前,PG Exporter 继续提供聚合指标。

来源

CSV 要求来自 PostgreSQL 14 与 PostgreSQL 18 官方日志文档。替代方案的源码边界和测试证据记录在链接的设计文章中。

9 - 一个端点,多种来源:Composite Exporter 契约

通过固定所有权和独立故障处理,合并 PostgreSQL 与可选来源的指标

Composite 模式把 PostgreSQL、PgBouncer、Patroni、pgBackRest 和 PostgreSQL 日志指标合并到同一个 /metrics 端点,减少数据库节点上的进程和抓取目标。PostgreSQL 是主数据源;各可选来源独立报告故障和健康状态。

状态: 已实现,未发布。
决策日期: 2026-08-11;2026-08-23 与 2026-08-24 增加缓存组件。
最后核验: 2026-10-13。
范围: 源码提交 c0ba58a 的 Composite 协调层;稳定版 v1.4.1 尚未包含。
交付进度: 本地测试通过;未合入 main 或发布标签;软件包、部署与生产运行未核验。

PostgreSQL 保持主导

只有 PostgreSQL 的指标汇总错误会使 Composite 汇总失败。可选组件故障只影响自身指标和健康状态。只要 PostgreSQL 汇总成功,端点仍返回 HTTP 200。

所有可选组件默认关闭。全部关闭时,启动过程不创建可选后台任务、客户端或文件读取器,抓取路径也不增加 Composite 协调锁。

实时采集与缓存快照

Prometheus /metrics
        |
        v
Composite 协调层
  |-- PostgreSQL SQL       实时、主数据源
  |-- PgBouncer SQL        实时、可选
  |-- Patroni HTTP         实时、可选
  |-- pgBackRest 快照      缓存、可选
  `-- PostgreSQL 日志      缓存、可选

PostgreSQL 与 PgBouncer 使用现有 SQL 采集器及查询缓存。Patroni 的角色和主节点状态变化较快,因此在每次完整抓取中请求其指标端点。

pgBackRest 与 PostgreSQL 日志由后台任务采集,并发布不可变快照。HTTP 请求只读取快照,命令执行、日志解析和状态持久化不会拖慢数据库采集。刷新与恢复规则见缓存 pgBackRest 指标和持久 PostgreSQL 日志指标。

超时与并发请求

PgBouncer 与 Patroni 在 --scrape-timeout 限制下并发执行,默认 10 秒。PostgreSQL 同时采集,沿用自身查询超时;可选组件的超时不会取消 PostgreSQL 采集。

若已有一轮完整 Composite 抓取正在运行,重叠请求只采集 PostgreSQL、进程指标和缓存快照。它跳过 PgBouncer 与 Patroni 的实时采集,并在本次响应中将这两个组件的 up 设为零,不更新已保存的错误计数和最后成功时间。这样可选采集不会形成请求队列。

指标族所有权

业务指标的优先级如下:

PostgreSQL > Patroni > PgBouncer > pgBackRest > PostgreSQL Log

进程与 HTTP 自监控在 PostgreSQL 之后、可选来源之前合并。组件健康指标最后合并。

先占用名称的来源拥有整个指标族。即使类型、说明和标签一致,低优先级来源也不能追加样本。Histogram 与 Summary 的派生名称同样保留:高优先级 foo Histogram 会阻止低优先级 foo_count 指标族加入。

发生冲突时,省略冲突指标族,并将可选组件标记为 gather 错误。该组件的其他合法指标族仍可保留。

Patroni 指标接入

PG Exporter 抓取、解析、验证并重新编码 Patroni 的经典 Prometheus 文本,保留指标族名称、说明、类型、标签和值,不为业务指标添加 component 标签。

响应上限为 1 MiB、10,000 行样本和 256 个指标族。HTTPS 使用系统信任根及可选的 --http-ca-file。证书错误会将 Patroni 标记为不可用;没有跳过证书验证的参数。

组件健康

除非设置 --disable-intro,Composite 模式会暴露以下自监控指标:

pg_exporter_component_enabled{component="..."}
pg_exporter_component_up{component="..."}
pg_exporter_component_scrape_duration_seconds{component="..."}
pg_exporter_component_scrape_errors_total{component="...",reason="..."}
pg_exporter_component_last_success_timestamp_seconds{component="..."}

enabled 表示组件是否启用,关闭的组件为零。up 表示采集结果。耗时和最后成功时间对实时来源表示抓取,对缓存来源表示后台周期。错误原因使用固定枚举,例如 connect、timeout、tls、parse 和 gather。

既有 /up、/health、/primary 和 /replica 端点继续描述 PostgreSQL。这些端点用于数据库健康检查与路由,可选组件不改变其含义。--disable-intro 移除自监控指标,保留业务指标。

备选方案与非目标

  • 独立 Exporter 提供进程隔离,但需要独立的目标、软件包和服务配置。Composite 是一种可选部署方式。
  • 串行采集使总耗时依赖来源顺序。并发实时采集共用一个可选组件超时。
  • 原样转发 Patroni 文本无法检查指标所有权和响应上限。解析后可以同时检查两者。
  • 通用插件注册机制会在集成尚未稳定时暴露生命周期和优先级规则。本设计采用具名组件和固定优先级。
  • 要求所有来源成功,会让数据库监控依赖可选服务。协调层将故障限制在所属组件内。

本设计不改变指标命名空间,也不负责迁移 Pigsty 目标、仪表盘、告警或现有独立 Exporter。

核验与运维影响

实现位于 exporter/composite.go、exporter/patroni.go 及启动、重载路径。测试覆盖关闭行为、超时、TLS 与格式错误、指标族冲突、并发请求、重载和关闭。

同一个进程需要所有已启用来源的权限,只授予实际所需权限。替换独立 Exporter 前,在目标环境验证正式软件包、目标标签、仪表盘和告警。

10 - 快照直方图是 Gauge,不是 Counter

为什么 SQL 快照分布将桶、计数与总和输出为 Gauge 序列

一个标量指标无法描述变化中的总体分布。一个 30 分钟事务与一百个 18 秒事务的总年龄相同,但数量、最大年龄和运维影响不同。

状态: 已随 v1.4.0 发布。
决策日期: 2026-07-11;2026-07-17 修订。
最后核验: 2026-10-13。
范围: 稳定版 v1.4.1 的 HISTOGRAM 列与 pg_xact_age 采集器,并对照开发提交 c0ba58a 核验。
交付进度: 已实现、合并、打标签并发布软件包。针对性本地测试已通过;部署与生产运行未核验。

PG Exporter 快照直方图对一次 SQL 执行返回的观测值分组。每次执行都重建分布。由于值可能下降,桶、计数和总和都以 Gauge 序列输出。

时间语义

事务开始时进入总体,随时间增长移入更大的年龄桶,结束时离开总体。这与持续累计事件的 Histogram 不同。

输出沿用常见的桶结构:

pg_xact_age_seconds_bucket{datname="app",le="10"} 5
pg_xact_age_seconds_bucket{datname="app",le="30"} 9
pg_xact_age_seconds_bucket{datname="app",le="+Inf"} 11
pg_xact_age_seconds_count{datname="app"} 11
pg_xact_age_seconds_sum{datname="app"} 214

这三个指标族都是 Gauge。桶内计数按当前快照的上界累计,不跨抓取累计。直接使用 histogram_quantile():

histogram_quantile(
  0.95,
  sum by (datname, le) (pg_xact_age_seconds_bucket)
)

当前平均年龄由总和除以计数得到:

sum by (datname) (pg_xact_age_seconds_sum)
/
sum by (datname) (pg_xact_age_seconds_count)

不要对这些快照序列使用 rate()、irate() 或 increase()。这些函数会把数值下降解释为计数器重置。

配置与 SQL

用 usage: HISTOGRAM 声明观测列,并指定桶上界:

- seconds:
    usage: HISTOGRAM
    bucket: [1, 3, 10, 30, 100, 300, 1000, 3000]
    description: Open transaction age snapshot in seconds

每个非 NULL 值是一次观测。完整标签元组相同的行归入同一分布;多个 Histogram 列独立聚合。

桶包含上界,边界必须有限且严格递增。配置加载时拒绝空列表、重复值、NaN 和无穷值。PG Exporter 自动追加 +Inf。le 标签及生成的 _bucket、_count、_sum 名称为保留名称,冲突配置会被拒绝。

scale 在分桶和求和前生效。时间戳与布尔值沿用标量转换规则,不进行缩放。显式 default 将 NULL 转为观测值;未指定时忽略 NULL。

查询、缓存与失败行为

每次 SQL 执行都从空累加器开始。PG Exporter 对观测分组、计算累计桶,并在读完全部结果后发布指标。

查询出错、缺少必需列或出现非有限观测值时,整份查询结果都被拒绝,包括同一查询的标量指标。刷新失败会将缓存替换为空结果,不保留上一份分布。

缓存命中直接返回上一份结果,不追加观测。下一次 SQL 执行从零开始。重载丢弃旧采集器和缓存,包括旧桶布局。

备选方案与非目标

  • prometheus.NewConstHistogram 将数据标为 Counter Histogram,其时间语义不适合变化中的 SQL 总体。
  • OpenMetrics GaugeHistogram 能直接表达该语义,但需要为此功能改变输出协议。显式 Gauge 可沿用经典文本端点。
  • Native Histogram 解决存储和分辨率问题,不改变观测是事件还是当前总体这一事实。
  • 在 SQL 中预聚合桶,会让每个采集器重复分组逻辑。
  • 跨抓取保留观测需要事件身份、重启处理和去重。持久事件采集不在本设计范围内;日志 Histogram 使用独立契约。

参考采集器与验证

pg_xact_age 按数据库统计未结束事务的年龄和事务内空闲时长,只选择客户端后端,TTL 为 10 秒。其桶边界支持计算当前分位数、超过阈值的数量和平均年龄。

以下 pgbench 脚本产生多档查询时长和事务内空闲时长。可在测试数据库中运行,观察事务进入和离开各桶:

\set query_band random(1, 4)
\if :query_band = 1
  \set query_ms 300
\elif :query_band = 2
  \set query_ms 3000
\elif :query_band = 3
  \set query_ms 10000
\else
  \set query_ms 30000
\endif

\set hold_band random(1, 4)
\if :hold_band = 1
  \set hold_ms 300
\elif :hold_band = 2
  \set hold_ms 3000
\elif :hold_band = 3
  \set hold_ms 10000
\else
  \set hold_ms 30000
\endif

BEGIN;
SELECT pg_current_xact_id(), pg_sleep(:query_ms / 1000.0);
\sleep :hold_ms ms
COMMIT;

实现与测试覆盖多个 Histogram 列独立聚合、NULL、缩放、桶边界、查询失败、缓存复用、确定性输出、并发采集与重载。

运维影响

Exporter 无需保存事件历史,就能提供可聚合的当前分布。虽然序列使用常见 Histogram 后缀,仪表盘查询仍须遵循快照语义。

完整语法见采集器配置,发布内容见 v1.4.0。普通累计 Histogram 的查询方法见 Prometheus Histograms and summaries。