跳至内容
25.2 PostgreSQL 核心运行信号

25.2 PostgreSQL 核心运行信号

PostgreSQL 自带两类观察接口:

dynamic current state
  当前 backend、锁、复制、进度

cumulative statistics
  自 reset 以来的 transaction、I/O、WAL、maintenance、statement

查询视图很简单,正确解释并不简单。以下事实可以同时成立:

pg_stat_activity 当前没有 lock wait
过去五分钟 lock wait 曾导致用户超时

pg_stat_archiver.failed_count = 21
当前归档已经恢复并持续成功

pg_stat_io.read_time 增加
物理磁盘没有等量读取,因为 OS page cache 参与

replica WAL distance = 0
应用仍可能因为路由、事务快照或缓存读到旧结果

n_dead_tup = 0
表仍可能存在已分配但未归还给操作系统的空间

本节目标不是记住所有列,而是掌握一套读法:

view
  -> source and update path
      -> current/cumulative/estimate/progress
          -> reset and snapshot
              -> independent corroboration
                  -> safe action

完整视图以当前版本官方文档为准: PostgreSQL 18 Monitoring Stats

25.2.1 会话、事务、等待与锁

先确认统计功能是否开启

关键设置:

SELECT name, setting, unit, source
FROM pg_settings
WHERE name IN (
  'track_activities',
  'track_counts',
  'track_functions',
  'track_io_timing',
  'track_wal_io_timing',
  'stats_fetch_consistency'
)
ORDER BY name;

它们不是同一个开关:

设置作用关闭后的含义
track_activities当前命令与开始时间activity 信息受限
track_counts数据库/表等累计活动autovacuum 也依赖它
track_functions函数调用统计不代表函数没有运行
track_io_timing数据文件 I/O timing时间未测量,不是零成本
track_wal_io_timingWAL I/O timingWAL 时间未测量
stats_fetch_consistency一个事务内统计读取一致性影响缓存/快照行为

统计有开销,timing 尤其依赖平台时钟成本;但关闭后必须把“未测量”保留下来。 绝不能把:

track_wal_io_timing=off
wal_write_time=0

解释为 WAL 写入没有花时间。

本章沙箱:

track_activities       on
track_counts           on
track_functions        all
track_io_timing        on
track_wal_io_timing    off
stats_fetch_consistency cache

pg_stat_activity 是当前 backend 视图

先使用不导出 query text 的聚合:

SELECT
  backend_type,
  state,
  wait_event_type,
  count(*) AS sessions,
  max(clock_timestamp() - xact_start)
    FILTER (WHERE xact_start IS NOT NULL) AS max_xact_age,
  max(clock_timestamp() - query_start)
    FILTER (WHERE query_start IS NOT NULL) AS max_query_age
FROM pg_stat_activity
GROUP BY backend_type, state, wait_event_type
ORDER BY backend_type, state NULLS LAST, wait_event_type NULLS LAST;

为什么带 backend_type?PostgreSQL 18 里不只有 client backend:

autovacuum launcher / worker
background writer
checkpointer
walwriter / walsender / walreceiver
io worker
slotsync worker
logical replication worker

如果把后台进程与 client backend 混在一起,某个长期运行的 background worker 可能被误判为“用户 SQL 运行几小时”。

statewait_event 独立

常见错误:

state = active
  therefore CPU is executing

实际:

state=active, wait_event is null
  -> backend 正在运行,或刚好未被采样到等待

state=active, wait_event is not null
  -> SQL 仍是 active,但正在等待

state=idle, wait_event_type=Client
  -> 等客户端发下一条命令

state=idle in transaction
  -> 事务仍开着,可能保留 snapshot/lock/xmin

所以等待查询写成:

SELECT
  pid,
  backend_type,
  state,
  wait_event_type,
  wait_event,
  clock_timestamp() - xact_start AS xact_age,
  clock_timestamp() - query_start AS query_age
FROM pg_stat_activity
WHERE backend_type = 'client backend'
  AND state = 'active'
  AND wait_event IS NOT NULL
ORDER BY query_start;

这条查询没有读取 query 列。需要 SQL 上下文时,应在受限交互会话中按 queryid、application、database 和 owner 缩小范围,避免把全文复制进工单。

wait event 是“正在等什么”,不是“根因”

wait_event_type 先把等待分大类:

Lock
LWLock
IO
Client
IPC
Activity
Timeout
BufferPin
Extension

同一种等待可能有多种机制:

Lock
  application transaction contention
  DDL conflicting with queries
  idle in transaction retaining locks

IO
  cache miss
  sequential scan
  checkpoint-related work
  WAL read/write
  extension access

Client
  server waits for client
  slow consumer
  application not reading results

因此:

wait type
  -> affected backend/queryid
  -> blocker/resource
  -> user path
  -> corroborating counter/host evidence

才形成诊断。

PostgreSQL 18 的异步 I/O 引入 io worker 等 backend type 和相应等待。升级后 不要假设旧版 wait event 列表仍完整;dashboard 和规则要按当前版本校验。

当前统计在一个事务里可能保持不变

累计统计不是每次访问都无条件读取最新值。PostgreSQL 会把统计写入共享内存, 各进程最迟按一定节奏 flush;访问者又可能在当前事务内缓存读取结果。

这段会造成困惑:

BEGIN;
SELECT xact_commit FROM pg_stat_database WHERE datname = current_database();
-- 等待或在其他连接产生工作
SELECT xact_commit FROM pg_stat_database WHERE datname = current_database();
COMMIT;

stats_fetch_consistency=cache 下,同一事务后续读取可能继续看到缓存值。 诊断时优先:

每次采样使用短事务
不要在长事务里刷新 dashboard 数据
必要时调用 pg_stat_clear_snapshot()
记录采样时间和 stats_fetch_consistency

pg_stat_clear_snapshot() 清的是当前 session 的统计 snapshot,不是重置全局 统计;不要与 pg_stat_reset* 混淆。

统计更新也有时间边界

累计统计通常在 transaction 完成后才反映:

active transaction
  current activity can show it
  cumulative table/database changes may not yet be flushed

因此调查进行中的大事务:

  • activity 看当前 transaction age;
  • locks 看当前持有/等待;
  • progress 看支持的维护动作;
  • WAL/IO counter 看累计变化;
  • 不等待累计表统计“先证明它存在”。

crash、恢复与复制会改变统计历史

PostgreSQL 正常关闭会保存累计统计;非正常关闭、从 base backup 恢复或 PITR 可能导致统计 reset。跨 failover 比较时:

same metric name
  does not imply same counter history

必须同时保存:

  • member/timeline;
  • stats reset;
  • postmaster start;
  • role transition;
  • source instance;
  • sampling window。

long query 与 long transaction 不同

query_age = now - query_start
xact_age  = now - xact_start

场景:

状态query agexact age风险
active long query约等于或短于 xact执行/等待资源
idle in transaction当前 query 已结束lock、xmin、vacuum
active in old transaction当前 query 短很长snapshot/业务批次
idle上条 query 的开始时间不代表在执行无 transaction通常只是连接

因此不要用 query_start 对所有 state 排序然后自动 cancel。

idle in transaction 为什么危险

它可能:

  • 保留 row/table lock;
  • 持有旧 snapshot;
  • 阻碍 dead tuple 回收;
  • 拉长 backend_xmin
  • 占用 connection/pool slot;
  • 让后续应用错误更难定位。

诊断字段:

SELECT
  pid,
  datname,
  usename,
  application_name,
  state,
  clock_timestamp() - xact_start AS xact_age,
  wait_event_type,
  wait_event,
  backend_xid,
  backend_xmin
FROM pg_stat_activity
WHERE backend_type = 'client backend'
  AND state = 'idle in transaction'
ORDER BY xact_start;

取消或终止是变更动作,不属于本章 L0 采集。先确认:

  • owner/application;
  • transaction 是否仍有不可重试副作用;
  • pool mode;
  • unknown commit outcome;
  • cancel 与 terminate 的差异;
  • rollback/重连影响;
  • 用户症状是否关联。

pg_locks 是锁申请,不是完整业务解释

安全聚合:

SELECT locktype, mode, granted, count(*) AS locks
FROM pg_locks
GROUP BY locktype, mode, granted
ORDER BY locktype, mode, granted;

找 blocker 可使用 pg_blocking_pids()

SELECT
  a.pid AS waiting_pid,
  a.datname,
  a.usename,
  a.application_name,
  a.wait_event_type,
  a.wait_event,
  clock_timestamp() - a.query_start AS wait_age,
  pg_blocking_pids(a.pid) AS blocking_pids
FROM pg_stat_activity AS a
WHERE cardinality(pg_blocking_pids(a.pid)) > 0
ORDER BY a.query_start;

这个函数给出 blocker PID,但仍要判断:

direct blocker or blocker behind blocker?
transaction or prepared transaction?
DDL, row lock, advisory lock, relation extension?
which user journey?
is blocker making progress?
what is safe to cancel?

锁图而不是最长列表

事故中更有用的是:

waiting backend
  -> direct blocker
      -> root blocker
          -> owner / transaction age / state

并保存:

  • edge 采样时刻;
  • blocker state;
  • backend_xid/xmin
  • queryid,而不是默认 query text;
  • application/release;
  • lock type/mode;
  • user impact。

一条锁边可能瞬间消失。诊断包应保存有界快照,而不是事后只看当前视图。

deadlock 与普通阻塞

普通 lock wait 可以持续;deadlock 是一个等待环,PostgreSQL 会检测并中止其中 一个 transaction。

观察:

pg_stat_database.deadlocks       cumulative
log_lock_waits                   waits beyond deadlock_timeout
deadlock error log               discrete event
application retry/outcome        user semantics

log_lock_waits=on 只在等待超过 deadlock_timeout 后记录,短等待不会出现。 日志“没有 lock wait”不能证明没有短暂锁竞争。

权限边界

普通用户只能看到其他 session 的有限信息。pg_read_all_stats 能读取全库统计和 其他 session 的更多信息,但这仍然是高敏感可观测权限:

  • query text 可能含业务值;
  • application name 可能带身份;
  • client address 暴露拓扑;
  • activity 能推断业务行为。

建议:

exporter role
  stable, narrow, machine-only

interactive diagnostic role
  time-bounded, reviewed, pg_read_all_stats or narrower

evidence export
  aggregate and redact, no query text/client address

不要因为它不是 superuser 就把它当低风险权限。

查询本身也会进入观察结果

读取 pg_stat_activity 时,你自己的查询也是 active;访问许多系统视图也会拿 AccessShareLock。本章正式快照出现的 relation locks 就包括采集查询本身。

因此:

  • 标识 collector application;
  • 从结果中区分自身;
  • 限制 statement timeout;
  • 不在 tight loop 高频轮询;
  • 避免一次展开所有 query text;
  • 将 observer effect 写进证据。

25.2.2 缓冲、I/O、WAL、检查点与复制

数据路径不是“内存或磁盘”二选一

一个 PostgreSQL page 读取可能经过:

PostgreSQL shared buffers
  -> operating-system page cache
      -> filesystem / block layer
          -> physical or virtual storage

所以:

PostgreSQL read()
  may be satisfied by OS cache

shared buffer hit
  does not require an OS read

device read
  may be caused by another process

pg_stat_io 明确不区分物理磁盘和 OS page cache。必须结合 node/block-device 证据。

pg_stat_database 给出数据库级累计轮廓

常用字段:

SELECT
  datname,
  numbackends,
  xact_commit,
  xact_rollback,
  blks_read,
  blks_hit,
  temp_files,
  temp_bytes,
  deadlocks,
  blk_read_time,
  blk_write_time,
  stats_reset
FROM pg_stat_database
WHERE datname IS NOT NULL
ORDER BY datname;

它适合:

  • database-level rate;
  • hit/read 变化;
  • temp spill 趋势;
  • rollback/deadlock 变化;
  • reset-aware baseline。

不适合:

  • 归因到具体 query;
  • 直接推物理 IOPS;
  • blks_hit / (blks_hit + blks_read) 单独判断内存是否足够;
  • 比较不同 reset 区间的裸总数。

cache hit ratio 不是性能分数

$$ \text{hit ratio}

\frac{\Delta hits} {\Delta hits + \Delta reads} $$

即使正确用窗口增量,也受 workload 影响:

  • 大表顺序扫描天然产生 reads;
  • 小表热点容易高命中;
  • OS cache 命中仍记为 PostgreSQL read;
  • 低 hit 可能是合理批处理;
  • 高 hit 不代表 CPU、lock 或 plan 健康。

把它作为 workload 特征,不要设一个跨服务的“低于 99% 就 page”。

pg_stat_io 按谁、什么对象、什么上下文拆分

PostgreSQL 18 可按:

backend_type
object
context

观察:

reads / read_bytes / read_time
writes / write_bytes / write_time
extends / extend_bytes / extend_time
hits / evictions / reuses
fsyncs / fsync_time

先聚合非零工作:

SELECT
  backend_type,
  object,
  context,
  sum(reads) AS reads,
  sum(read_bytes) AS read_bytes,
  sum(read_time) AS read_ms,
  sum(writes) AS writes,
  sum(write_bytes) AS write_bytes,
  sum(write_time) AS write_ms,
  sum(fsyncs) AS fsyncs,
  sum(fsync_time) AS fsync_ms
FROM pg_stat_io
GROUP BY backend_type, object, context
HAVING
  coalesce(sum(reads), 0)
  + coalesce(sum(writes), 0)
  + coalesce(sum(fsyncs), 0) > 0
ORDER BY backend_type, object, context;

窗口诊断需要两次快照求 delta 或 exporter counter rate。裸累计值只说明 reset 以来总量。

timing 要与 byte/count 一起读

read_time rises, read_bytes rises
  -> more work or slower work

read_time per read rises
  -> average operation cost rises, but distribution unknown

read_bytes rises, device reads stable
  -> OS cache may serve more reads

timing is zero and tracking is off
  -> unknown, not free

平均值会掩盖 tail。需要时结合:

  • block-device latency histogram;
  • request latency histogram;
  • trace sampled tail;
  • queryid-level I/O;
  • workload mix。

buffer、backend 与 checkpoint 写入

dirty buffer 可能由 backend、background writer、checkpointer 等路径写出。 pg_stat_io 帮助按 backend type 区分;pg_stat_checkpointer 给 checkpoint 累计结果。

PostgreSQL 18:

SELECT *
FROM pg_stat_checkpointer;

重要字段包括:

num_timed
num_requested
num_done
restartpoints_*
write_time
sync_time
buffers_written
slru_written
stats_reset

解释:

requested checkpoint rate rises
  -> workload/config/administrative events may force checkpoints

write_time dominates
  -> checkpoint spreads writes

sync_time spikes
  -> fsync phase or storage path needs correlation

buffers_written rises
  -> more dirty data, not automatically a problem

不要只看一次 write_time 总值。计算每窗口的 checkpoint count、buffer、write 和 sync delta,再与用户 latency、WAL rate 和 host I/O 对齐。

checkpoint 不是越少越好

过频可能增加写入压力和 full-page image;过稀可能:

  • 增加 crash recovery 时间;
  • 需要更多 WAL;
  • 在 checkpoint 集中更多工作;
  • 改变恢复和容量特征。

任何调参要结合:

checkpoint completion
WAL generation
dirty buffer path
storage latency
recovery objective
memory and workload

本章只观察,不修改 checkpoint_timeoutmax_wal_size 或 completion target。

pg_stat_wal 是 WAL 生成轮廓

SELECT
  wal_records,
  wal_fpi,
  wal_bytes,
  wal_buffers_full,
  stats_reset
FROM pg_stat_wal;

用途:

  • WAL byte rate;
  • full-page image 比例变化;
  • WAL buffer full;
  • 与 write workload、checkpoint、replication、archive 对齐。

不能直接说明:

  • 哪个 query 产生 WAL;
  • WAL 是否已归档;
  • replica 是否已回放;
  • recovery 能否成功。

query 归因需要 pg_stat_statements.wal_bytes 等;归档与恢复需要另外的证据。

full-page image 的上下文

checkpoint 后 page 首次修改可能记录 full-page image,以支持恢复。wal_fpi 变化与:

  • checkpoint 频率;
  • 工作集;
  • full_page_writes
  • page 修改模式;
  • compression;
  • backup/recovery policy

相关。不要看到 FPI 高就关闭安全机制。

pg_stat_archiver 同时有 counter 和最后事件

SELECT
  archived_count,
  last_archived_wal,
  last_archived_time,
  failed_count,
  last_failed_wal,
  last_failed_time,
  stats_reset
FROM pg_stat_archiver;

三种不同问题:

历史是否失败过?
  failed_count since reset

最近是否出现新失败?
  increase(failed_count[window])

当前是否推进?
  last success age + WAL generation + archive queue

沙箱快照:

failed_count           21
last_failed_time       18:57:58Z
last_archived_time     22:27:54Z

最近成功晚于失败,说明历史 counter 非零不能证明当前仍失败。

候选告警:

pg36:archive_failures:increase15m > 0
and on (cls, ins, ip)
(time() - pg_archiver_finish_time) > 900

这仍只是 recovery risk candidate。下一步要复核:

  • 当前 WAL 是否继续生成;
  • archive command/pgBackRest 状态;
  • repository;
  • latest archive;
  • backup/WAL coverage;
  • 最近 restore drill。

“归档恢复”不等于“恢复就绪”。

replication 有位置、时间、状态三套语义

primary:

SELECT
  application_name,
  state,
  sync_state,
  sent_lsn,
  write_lsn,
  flush_lsn,
  replay_lsn,
  pg_wal_lsn_diff(sent_lsn, replay_lsn) AS sent_replay_gap_bytes,
  write_lag,
  flush_lag,
  replay_lag
FROM pg_stat_replication
ORDER BY application_name;

replica:

SELECT
  status,
  sender_host,
  sender_port,
  written_lsn,
  flushed_lsn,
  latest_end_lsn,
  last_msg_send_time,
  last_msg_receipt_time,
  latest_end_time
FROM pg_stat_wal_receiver;

公开/长期证据不一定应保存 sender/client address;可以保留 member identity、 state、sync_state 和位置差。

位置 gap

sent - write    network/receiver write path
write - flush   replica flush path
flush - replay  replay/apply path

位置差按 WAL byte 计,不是秒:

$$ \text{catch-up time} \ne \frac{\text{WAL gap bytes}}{\text{current generation rate}} $$

因为 replay throughput、workload、conflict、I/O 和 future WAL 都会变化。它可 用于容量与趋势,不应直接承诺 RTO。

时间 lag 可能是 NULL

write_lagflush_lagreplay_lag 是最近同步交互产生的测量,并不保证 持续给出“当前落后秒数”。空闲系统可能为 NULL;这不等于零,也不等于故障。

使用:

  • LSN distance;
  • connection/state;
  • last message;
  • known commit probe;
  • workload/WAL generation

共同解释。

本章正式 SQL 快照中,两条 streaming async replica 的 LSN gap 都为 0,而 lag interval 为 NULL。这正好说明:

NULL time lag
  can coexist with zero position gap in an idle/current sample

sync_state 与 commit durability

sync_state=async 表示它不是当前同步确认的一部分。即使 gap 为零:

  • 后续 commit 仍可能尚未复制;
  • primary 立即丢失会有 write gap 风险;
  • 应用 commit acknowledgment 语义取决于 synchronous_commit 和配置;
  • user freshness 取决于读取路径。

pg_stat_replication 与第 20 章 HA 合同一起读,不要从瞬时 gap 倒推出 durability guarantee。

replication slot 与 WAL retained risk

除了 replica gap,还要观察:

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

slot identity 可能属于 extension/consumer;不要自动删除 inactive slot。风险:

  • WAL retention 填满磁盘;
  • logical consumer 落后;
  • slot 失效;
  • consumer 被误认作废弃。

容量规则默认 ticket,动作需 owner 和 consumer 证据。

复制冲突

hot standby 查询可能与 replay 冲突。观察:

  • pg_stat_database_conflicts
  • replica activity;
  • query cancellation log;
  • feedback/delay 设置;
  • retained xmin/WAL;
  • user read path。

降低冲突的设置可能增加 bloat 或 freshness lag,不能只优化一张图。

25.2.3 vacuum、冻结、膨胀与对象增长

vacuum 有四个主要目的

常规 vacuum 不是“清空表”,而是:

  1. 回收 dead row version,使空间可在表内复用;
  2. 更新 visibility map,支持 index-only scan 等;
  3. 防止 transaction ID wraparound;
  4. 维护统计/冻结等运行状态。

ANALYZE 更新 planner statistics;它与 vacuum 可以一起运行,但目的不同。

autovacuum 依赖统计

track_counts=on 不只是“多收指标”。autovacuum 使用累计活动决定何时处理表。 关闭它会影响维护机制。

每表触发近似由:

vacuum threshold
  base threshold + scale factor * relation tuples

analyze threshold
  base threshold + scale factor * relation tuples

决定,并受 insert threshold、per-table storage parameter、cost limit、worker 数、 全局设置等影响。

不能只看“autovacuum process 存在”。要看:

  • table change pressure;
  • last vacuum/autovacuum;
  • dead tuples;
  • analyze freshness;
  • blockers;
  • progress;
  • freeze age;
  • runtime/duration。

表级统计是 estimate + counter

SELECT
  schemaname,
  relname,
  n_live_tup,
  n_dead_tup,
  n_mod_since_analyze,
  n_ins_since_vacuum,
  last_vacuum,
  last_autovacuum,
  last_analyze,
  last_autoanalyze,
  vacuum_count,
  autovacuum_count,
  analyze_count,
  autoanalyze_count
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC NULLS LAST
LIMIT 50;

n_live_tupn_dead_tup 是估算;count 是自 reset 累计。它们适合找候选,不 适合直接计算“精确 bloat 百分比”。

dead tuple 不等于 bloat

dead tuple
  an old row version no longer visible to current snapshots

reusable free space
  vacuum processed space available for future tuples

table bloat
  allocated pages exceed what current data/layout needs

filesystem size
  file blocks currently allocated

vacuum 后 n_dead_tup 可能下降,但文件通常不缩小,因为普通 vacuum 将空间 留给表内复用。需要归还操作系统的操作通常更重,涉及 rewrite/lock/extra disk; 不能因为文件没缩就说 vacuum 失败。

长事务如何阻止回收

MVCC 需要保留旧版本给仍可能看见它的 snapshot。长 transaction、prepared transaction、replication slot/feedback 等可能拉住 xmin:

updates/deletes create dead versions
  -> vacuum evaluates global visibility horizon
      -> old xmin still needs them
          -> cannot remove
              -> dead tuples/object size grow

观察:

SELECT
  pid,
  datname,
  usename,
  application_name,
  state,
  backend_xid,
  backend_xmin,
  clock_timestamp() - xact_start AS xact_age
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY xact_start;

还要查:

  • prepared transactions;
  • replication slots;
  • replica feedback;
  • vacuum progress;
  • table-level age。

“找到最老 PID 就 terminate”不是安全策略。

freeze age 是剩余空间,不是普通 latency

transaction ID 是有限循环空间。旧 tuple 必须被 freeze,避免 wraparound 后 可见性灾难。

数据库级:

SELECT
  datname,
  age(datfrozenxid) AS xid_age,
  mxid_age(datminmxid) AS multixact_age
FROM pg_database
ORDER BY xid_age DESC;

表级:

SELECT
  n.nspname,
  c.relname,
  age(c.relfrozenxid) AS xid_age,
  mxid_age(c.relminmxid) AS multixact_age,
  pg_total_relation_size(c.oid) AS total_bytes
FROM pg_class AS c
JOIN pg_namespace AS n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r', 'm')
  AND n.nspname NOT IN ('pg_catalog', 'information_schema')
ORDER BY xid_age DESC
LIMIT 50;

不要把阈值写成与配置无关的魔法数字。应比较:

current age
autovacuum_freeze_max_age
table override
consumption rate
vacuum throughput
blocker
remaining time with uncertainty

本章候选 PG36FreezeAgeHorizon 使用固定数字只是 isolated lab 的测试输入, 明确标为 proposed;生产规则应由当前配置和容量政策生成。

anti-wraparound autovacuum 的特殊性

为防 wraparound 启动的 autovacuum 通常不会像普通 autovacuum 那样轻易被冲突 动作自动打断。不要把它当“可以随时 kill 的后台噪声”。

如果已经进入紧急区:

  • 停止增加风险的长事务;
  • 找出不能推进的表与 blocker;
  • 评估 I/O/空间/锁;
  • 按 runbook 控制维护;
  • 不并行执行未经评估的 rewrite;
  • 保留 evidence。

最优策略是在容量 horizon 阶段用 ticket 解决,而不是等 emergency page。

progress view 是当前进度,不是历史

vacuum:

SELECT
  pid,
  datid,
  relid,
  phase,
  heap_blks_total,
  heap_blks_scanned,
  heap_blks_vacuumed,
  index_vacuum_count,
  num_dead_item_ids,
  max_dead_item_ids
FROM pg_stat_progress_vacuum;

PostgreSQL 还为:

  • ANALYZE
  • CREATE INDEX / REINDEX
  • CLUSTER / VACUUM FULL
  • COPY
  • base backup

提供相应 progress view,具体列按版本文档。

没有行只表示当前没有该动作被报告,不表示:

  • 从未运行;
  • 上次成功;
  • 下一次会成功;
  • 没有被瞬间启动后失败。

历史需要日志、事件和累计 count。

progress 百分比可能不单调

不同 phase 使用不同总量;并行、索引清理和 dead item cycle 也会改变解释。 不要把:

heap_blks_scanned / heap_blks_total

当成整个 vacuum 的精确完成百分比。应同时显示 phase 和相关量。

analyze freshness

planner statistics 变旧会导致估算偏差。候选信号:

n_mod_since_analyze
last_analyze / last_autoanalyze
table size
query plan estimate vs actual
statistics target
column distribution

n_mod_since_analyze 是估算/累计线索,不是每行精确 change counter。对热点、 分区和高度偏斜列,需要 workload-aware 策略。

对象增长要拆分

SELECT
  n.nspname,
  c.relname,
  pg_relation_size(c.oid) AS heap_bytes,
  pg_indexes_size(c.oid) AS index_bytes,
  pg_total_relation_size(c.oid) AS total_bytes
FROM pg_class AS c
JOIN pg_namespace AS n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r', 'm')
  AND n.nspname NOT IN ('pg_catalog', 'information_schema')
ORDER BY total_bytes DESC
LIMIT 50;

增长可能来自:

  • 正常业务数据;
  • index 数量;
  • TOAST;
  • dead/reusable space;
  • fillfactor;
  • 分区保留;
  • 临时/中间对象;
  • rewrite;
  • 失控 batch。

不要只按总大小 page。需要:

growth rate
retention expectation
free-space horizon
maintenance/rewrite headroom
business volume
owner plan

容量是预测和计划问题,默认 ticket。

partition 会改变聚合方式

父表和各分区的:

  • size;
  • table stats;
  • autovacuum;
  • analyze;
  • index;
  • freeze age

需要分别观察,再按业务分区策略聚合。只看父表可能近乎空;只列每个分区又会 产生巨大 cardinality。

推荐:

metric
  aggregate by parent / age band / size band

dashboard
  top-N and drill-down

SQL
  on-demand exact partition inventory

不要把每个临时分区名永久做成高基数 alert label。

bloat estimate 的边界

extension 或 SQL 估算 bloat 常依赖:

  • row width;
  • null bitmap;
  • alignment;
  • fillfactor;
  • statistics;
  • page sample;
  • index type。

它是排序候选,不是字节级财务账。要做重操作前:

  1. 复核对象大小与增长趋势;
  2. 判断空间能否复用;
  3. 找出生成机制和 blocker;
  4. 评估 rewrite/lock/replication/WAL/backup 影响;
  5. 准备额外磁盘和回退/前滚;
  6. 在维护窗口验证。

本章不会自动 VACUUM FULLREINDEX

把三类维护信号分开

类别问题典型动作
运行正确性wraparound 是否接近高优先级维护/停止风险来源
性能卫生dead tuple、stats 是否影响 workloadvacuum/analyze 调整与 blocker 修复
容量对象/索引/WAL 是否耗尽空间retention、扩容、结构优化、rewrite 计划

它们的 severity、owner 和时间尺度不同。一个“表大”告警不能同时代表三者。

沙箱快照如何读

正式采集在 pg-test-1 观察到:

user tables             1
estimated dead tuples   0
max table freeze age    1,050

这不是“vacuum 永远健康”的证明:

  • synthetic workload 很小;
  • 只有一次瞬时快照;
  • 没有长期增长率;
  • 没有生产 transaction rate;
  • 没有生产配置与 margin;
  • estimate 可能变化。

可以得出的结论只有:

at captured_at,
the declared sandbox target reported this small current baseline.

PostgreSQL 信号最小关联表

用户症状PG 入口原生证据外部复核
latencyactive/waitactivity、locks、queryidpool、host、trace
errorsrollback/deadlockdatabase counter、logsapp outcome
stale readreplica pathreplication position/statecommit token probe
write latencyWAL/checkpointwal、checkpointer、I/Odevice、app histogram
query spilltemp bytesdatabase、statement、logsplan/work_mem policy
vacuum delayold xminactivity、table stats、progressworkload/change event
freeze riskXID agedatabase/class agerate/horizon/capacity
recovery riskarchivearchiver + WALpgBackRest + restore drill

本节验收

你应当能解释:

  1. active 为什么仍可能在等待;
  2. 同一 transaction 为什么可能读到缓存的累计统计;
  3. track_wal_io_timing=off 为什么不能把时间解释为零;
  4. pg_stat_io 为什么不等于物理磁盘 I/O;
  5. failed_count>0 为什么不等于当前归档失败;
  6. time lag NULL 与 WAL gap 0 为什么可以同时出现;
  7. WAL gap 0 为什么不能证明用户新鲜度;
  8. n_dead_tup 为什么不等于精确 bloat;
  9. 普通 vacuum 为什么通常不缩小文件;
  10. progress view 没有行为什么不证明历史成功;
  11. query、lock、vacuum 和 freeze 信号分别对应哪种动作;
  12. 任何取消、终止、reset、vacuum 或配置变更为什么不属于 L0 观察。

上一节:从问题选择可观测信号 · 返回本章目录 · 下一节:SQL 可观测基线 · 查看全书目录 · 查看索引中心

最后更新于