跳至内容
28.7 实战:建立维护节奏

28.7 实战:建立维护节奏

前六节分别讨论了旧版本、autovacuum、冻结、膨胀、分区生命周期和物理完整性。 这些知识如果只停在若干 SQL 和参数上,仍然很容易变成“告警来了就跑一次 VACUUM”的被动运维。本节把它们收束成一条可重复的维护闭环:

发现信号
  -> 确认对象与保留者
      -> 判断正确性、服务和效率优先级
          -> 选择最小动作并预算锁 / 空间 / WAL
              -> 执行、旁路观测、验收
                  -> 留档、复盘、进入下一周期

本节同时提供一套可复现实验。它不是把固定阈值塞给读者,而是让读者亲眼验证四件事:

  1. 旧快照怎样改变普通 VACUUM 的清理结果;
  2. “空间已经可复用”和“文件已经缩小”为什么是两个结论;
  3. 索引检查、并发重建与分区退役怎样分别验收;
  4. 哪些异常仍属于日常维护,哪些必须立即转入资源事故或数据救援。

28.7.1 制造膨胀、长事务与分区到期

先读实验合同

本章实验只允许在已经确认的 Pigsty pg-test 开发沙箱执行,正式参考环境为:

target        pg36-l2-vagrant/pg-test
server        PostgreSQL 18.4
database      pg36_maintenance
role          dbuser_pg36maint
data          synthetic only
capture       L0 read-only
exercise      L2 bounded disposable fixture
production    forbidden

完整合同在 static/labs/ch28/lab-contract.md,机器可校验的要求与动作白名单 分别在 requirements.jsonmaintenance-contract.json

实验只创建带随机 run_id 注释的一次性数据库和角色。为避免 exporter 在建库与授权之间 抢先连接,runner 依次执行:

CREATE DATABASE ... ALLOW_CONNECTIONS false
  -> REVOKE CONNECT FROM PUBLIC
      -> GRANT CONNECT TO exact fixture role
          -> ALTER DATABASE ... ALLOW_CONNECTIONS true

所有表都位于夹具数据库。只有 maint.churn 的表级 autovacuum 被临时关闭,以便让手工 VACUUM 的因果关系可复现;这不是生产建议。实验明确禁止:

ALTER SYSTEM
修改 Pigsty inventory 或 Patroni DCS
reload / restart PostgreSQL
全局关闭 autovacuum
VACUUM FULL
DROP DATABASE ... WITH (FORCE)
终止不相关会话
未验证归档就删除分区
接触生产数据或流量

换言之,这里制造的是可丢弃夹具上的现象,不是在真实库中“先破坏再学习”。

先锁定输入与上游证据

capture 在任何写入前验证:

  • 第 19 章部署证据仍指向同一个非生产沙箱;
  • 第 25 章确认目标为主库,并保留观测基线;
  • 第 27 章没有把试验参数持久化;
  • 数据库与角色在起点均不存在;
  • amcheckpg_freespacemappg_visibilitypgstattuple 可安装;
  • PostgreSQL 设置、复制槽、prepared transaction、文件系统空间和校验和状态可读;
  • 11 个实验源文件的 SHA-256 与随后执行的版本一致。

这一步解决一个经常被忽略的问题:如果运行期间脚本、目标或前置状态发生变化,最终数字 即使“看起来正确”,也不能归到当前实验设计上。runner 因此在 capture 之后再次计算源文件 散列,不一致就失败关闭。

创建三组现象

夹具包含两类表。

第一类是一个 fillfactor = 70 的 heap,共写入 60,000 行,并建立主键和 (status, id) B-tree:

CREATE TABLE maint.churn (
  id      bigint PRIMARY KEY,
  status  text NOT NULL,
  payload text NOT NULL
) WITH (
  fillfactor = 70,
  autovacuum_enabled = false
);

INSERT INTO maint.churn (id, status, payload)
SELECT i, 'new', repeat(md5(i::text), 20)
FROM generate_series(1, 60000) AS g(i);

CREATE INDEX churn_status_idx
  ON maint.churn (status, id);

VACUUM (FREEZE, ANALYZE) maint.churn;

第二类是按日期范围分区的事件表:

events_2024    10,000 rows   expired
events_2025     5,000 rows   retained
parent total   15,000 rows

数据内容、行数和边界都是确定的,因此归档前后可以比较:

row_count
min(id) / max(id)
min(event_date) / max(event_date)
sum(amount)
ordered row digest

仅比较文件大小或只执行一次 count(*) 都不够:前者不能证明逻辑内容,后者无法发现 同样行数下的值篡改。

建立一个可识别的旧快照

实验另开一个连接:

BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY;
SELECT count(*) FROM maint.churn;

连接的 application_name 固定为 pg36-ch28-old-snapshot。runner 必须从 pg_stat_activity 同时看到:

datname          = pg36_maintenance
usename          = dbuser_pg36maint
application_name = pg36-ch28-old-snapshot
pid              = recorded holder pid
backend_xmin     IS NOT NULL

只有这五个条件全匹配,后续才允许释放该会话。脚本不使用模糊的 query 文本、不按用户名 批量杀连接,也不把所有 idle in transaction 一锅端。

接下来在另一个事务中制造 churn:

WITH updated AS (
  UPDATE maint.churn
     SET status = 'changed',
         payload = payload || '-u'
   WHERE id <= 40000
   RETURNING 1
),
deleted AS (
  DELETE FROM maint.churn
   WHERE id > 40000
     AND id <= 50000
   RETURNING 1
)
SELECT
  (SELECT count(*) FROM updated) AS updated_rows,
  (SELECT count(*) FROM deleted) AS deleted_rows;

结果应为:

updated_rows    40,000
deleted_rows    10,000
remaining_rows  50,000

remaining_rows 必须在下一条 SQL 命令中读取。PostgreSQL 的 data-modifying CTE 共享同一个命令级快照;若在同一条语句里再次 count(*),读到的仍可能是修改前的 60,000 行。这不是数据库“少提交了一次”,而是命令快照语义。类似地,不能让 UPDATEDELETE 命中同一批行,再假设两个子语句会按书写顺序串行处理。

为什么这组夹具有教学价值

这组实验同时保留了四条互不替代的证据线:

现象主要证据回答的问题
旧快照backend_xmin、精确 holder identity谁还需要旧版本
heap churnpgstattuple、FSM、VM、关系大小旧版本是否清掉、空间去哪里
索引维护amcheck、catalog、relfilenode结构是否通过检查、重建是否完成
分区到期分区拓扑、CSV、manifest、回灌数据是否先可恢复、再退出热表

任何一列都不能替代其他列。n_dead_tup 是估计值;文件大小不是可见性;amcheck 不是备份;CSV 存在也不等于可恢复。

28.7.2 从指标与原生视图判定维护优先级

先按风险排序,不按表大小排序

维护队列应该先回答“拖延会造成什么”,而不是“哪个数字最大”。一个实用的三层优先级是:

P0 correctness
  XID / MXID headroom、校验和或 amcheck 错误、无法推进的冻结

P1 service continuity
  replication slot / prepared xact / 长事务保留、磁盘逼近红线、
  autovacuum backlog、锁等待、持续延迟与 WAL 压力

P2 efficiency
  可复用空间不足、索引低效、统计陈旧、计划可控的重组与分区退役

因此,一个 relfrozenxid 年龄危险但只有 2 GB 的表,可能比一个 2 TB、膨胀 20%、 仍有充足空间且正常被 vacuum 的表更紧急。维护分数可以帮助排序,但不能把 P0 平均进 一个漂亮的加权总分:

$$ priority = \operatorname{lexicographic} \left( correctness,\ service,\ efficiency \right) $$

同层内再用 headroom、增长速率、业务关键度、预计锁时间和维护成本排序。

第一屏:全库安全边界

先看数据库年龄、活动快照、prepared transaction 和复制槽:

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

SELECT pid, datname, usename, application_name,
       state, xact_start, backend_xid, backend_xmin,
       wait_event_type, wait_event
FROM pg_stat_activity
WHERE backend_xmin IS NOT NULL
   OR state = 'idle in transaction'
ORDER BY xact_start NULLS LAST;

SELECT transaction, gid, prepared, owner, database
FROM pg_prepared_xacts
ORDER BY prepared;

SELECT slot_name, slot_type, active,
       xmin, catalog_xmin,
       restart_lsn, confirmed_flush_lsn,
       wal_status, safe_wal_size
FROM pg_replication_slots
ORDER BY slot_name;

不要把“最老连接”自动等同于“清理阻塞者”。真正相关的是它是否持有旧 backend_xmin、prepared XID 或 slot xmin/catalog_xmin,以及时间线是否与问题吻合。 也不要看到 age() 大就立即运行一条万能命令;先按 28.3 的版本化流程判断处于常规态、 迫近 failsafe,还是已经进入事务 ID 硬停机状态。

第二屏:对象触发与进度

候选表至少需要这些原生信息:

SELECT relid::regclass AS relation,
       n_live_tup,
       n_dead_tup,
       n_tup_ins,
       n_tup_upd,
       n_tup_hot_upd,
       n_tup_del,
       last_autovacuum,
       autovacuum_count,
       last_autoanalyze,
       autoanalyze_count
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC;

SELECT c.oid::regclass AS relation,
       c.reltuples::bigint,
       c.relpages,
       age(c.relfrozenxid)    AS xid_age,
       mxid_age(c.relminmxid) AS mxid_age,
       pg_relation_size(c.oid)       AS heap_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 age(c.relfrozenxid) DESC;

SELECT pid, datname, relid::regclass AS relation,
       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;

其中:

  • pg_stat_user_tables 是累计统计和估计,适合发现趋势,不是物理真值;
  • pg_class 给出 catalog 估计、年龄和大小,仍不能直接证明 dead tuple 百分比;
  • pg_stat_progress_vacuum 只能描述正在运行的进度,phase 切换不是线性 ETA;
  • 需要高成本物理确认时,再在已选对象上使用 pgstattuple,不要全库高频扫描;
  • pg_visibility_map_summarypg_freespace 分别观察 VM 与 FSM,但不要把它们 解释成业务行正确性。

例如对单个已获批候选:

SELECT * FROM pgstattuple('app.orders'::regclass);

SELECT *
FROM pg_visibility_map_summary('app.orders'::regclass);

SELECT sum(avail) AS fsm_available_bytes
FROM pg_freespace('app.orders'::regclass);

将 Pigsty 看板与 SQL 对齐

Pigsty 的 PostgreSQL 监控提供数据库、实例、表、查询、复制、WAL、磁盘和 autovacuum 等多层视图。看板负责快速发现关联:

dead tuple ratio rises
  + autovacuum duration / backlog rises
  + disk free falls
  + latency or I/O pressure rises

原生视图负责复核对象、持有者、年龄、命令 phase 和 catalog 状态。正确的工作方式是:

dashboard finds pattern
  -> SQL proves identity and mechanism
      -> maintenance ticket freezes target and budget
          -> dashboard + SQL watch the operation

不要从一张图直接跳到 VACUUM FULLREINDEX 或终止会话。图表的采样、标签聚合和 保留周期都可能掩盖瞬态事实;反过来,单次 SQL 也无法替代时间序列。

本次实验如何判定

正式 run 的基线与 churn 后快照同时记录:

logical row count
pgstattuple live/dead tuple counts
pg_stat_user_tables counters
heap and total relation bytes
FSM available bytes
VM all-visible / all-frozen page counts
relfrozenxid age
holder backend_xmin
VACUUM progress samples and phases

优先级判断如下:

  1. 没有 XID/MXID 或完整性 P0 信号;
  2. 人工旧快照明确保留 50,000 个物理 dead tuples,先解除精确保留者;
  3. 普通 vacuum 之后确认空间回收语义,再决定是否需要文件重写;
  4. 索引检查、并发重建和分区到期作为独立、可验收的维护动作执行。

这里的重要判断不是“dead tuple 多,所以 vacuum”,而是“先证明谁让 vacuum 不能完成, 再移除那个精确原因”。

28.7.3 执行清理、检查和分区退役并验证副作用

运行完整闭环

先做不接触数据库的合同检查:

static/labs/ch28/task.sh lint

然后为每次正式实验使用一个不存在或为空的私密绝对目录:

export PG36_EVIDENCE_DIR=/absolute/private/new-empty/ch28-run
static/labs/ch28/task.sh all

all 的顺序固定为:

capture L0 read-only
  -> exercise L2 bounded fixture
      -> verify L0
          -> review L0

也可以逐步执行:

static/labs/ch28/task.sh capture
static/labs/ch28/task.sh exercise
static/labs/ch28/task.sh verify
static/labs/ch28/task.sh review

captureall 拒绝覆盖非空证据目录。私有目录包含原始 SQL 输出、进度采样、 归档 CSV、清理记录和 source hashes;公开仓库只保存字段白名单后的 maintenance-run.json,不保存口令、连接串、SSH 材料或原始业务数据。

第一步:旧快照存在时只运行普通 VACUUM

在 holder 仍有非空 backend_xmin 时,runner 对夹具表运行普通 VACUUM,并以较低的 session-local cost 设置放慢它,以便旁路采样:

old snapshot remains
  -> VACUUM maint.churn
      -> sample pg_stat_progress_vacuum
          -> wait for completion
              -> take physical/statistical snapshot

正式 run 结果:

项目结果
初始行数60,000
更新行数40,000
删除行数10,000
当前行数50,000
观察到 holder backend_xmin
vacuum 后物理 dead tuples50,000
进度采样128
观察到 phaseinitializingscanning heap

这不是“VACUUM 失效”。它遵守 MVCC,不能移除那个旧快照仍可能读取的版本。若此时反复 提高 cost limit、增加 worker 或改用 VACUUM FULL,都没有解决保留边界,反而会把 问题扩大成资源或锁事故。

第二步:只释放精确 holder,再完成冻结与统计

runner 只允许终止同时匹配数据库、角色、application、记录 PID 和非空 backend_xmin 的那一个夹具会话。任一属性改变都拒绝动作。释放后执行:

VACUUM (FREEZE, ANALYZE) maint.churn;

这里的 FREEZE 是为了在可控夹具上展示冻结与 VM 变化,不是声称所有日常 vacuum 都 必须加 FREEZE,也不是 PostgreSQL 18 已进入 XID 硬停机后的万能命令。

正式结果:

证据基线 / 旧快照阶段释放后
物理 dead tuples50,000(旧快照下 vacuum 后)0
heap bytes61,440,000(初始)87,040,000
FSM 可用字节51,920,000
all-visible pages10,625
all-frozen pages10,625
age(relfrozenxid)142
进度采样128171
释放后 phasescan heap、vacuum indexes、vacuum heap

结论需要精确措辞:

dead tuples became removable
free space became reusable inside the relation
visibility/freeze metadata advanced
heap file did not shrink

初始 heap 为 61,440,000 字节,churn 后最终仍为 87,040,000 字节。普通 VACUUM 成功清理,并不承诺把中间空洞归还操作系统。它在适当条件下可能截断文件末尾的空页, 但这次证据不能推导出“普通 vacuum 永不缩文件”,也不能推导出“文件没缩,所以 vacuum 没用”。

第三步:检查索引,再验证并发重建

runner 先对主键执行两层 amcheck

SELECT bt_index_check(
  'maint.churn_pkey'::regclass,
  heapallindexed => true,
  checkunique    => true
);

SELECT bt_index_parent_check(
  'maint.churn_pkey'::regclass,
  heapallindexed => true,
  rootdescend    => true,
  checkunique    => true
);

正式 run:

bt_index_check             pass   0.0597 s
bt_index_parent_check      pass   0.0789 s

第二种检查更强,但会取得 ShareLock,阻塞并发 DML 与 VACUUM;不能因为本次只需 0.08 秒就假设大表也能随时运行。生产中应根据表大小、缓存状态、业务窗口和副本角色 安排,必要时先用较轻的 bt_index_checkpg_amcheck 分批巡检。

然后对次级索引执行:

REINDEX INDEX CONCURRENTLY maint.churn_status_idx;

验收不止是“命令返回 0”:

条件正式结果
relfilenode 改变
原大小3,227,648 bytes
新大小1,589,248 bytes
同名有效索引1
indisvalid / indisready / indislive全部为真
无效夹具索引0
_ccnew / _ccold 残留0

尺寸变小是这次数据分布的结果,不是并发重建的固定收益。真正的最低验收是新索引有效、 唯一目标明确、没有异常残留,并且写放大、临时空间、WAL、复制延迟和锁等待仍在预算内。

第四步:先分离、归档与回灌,再删除

过期分区执行:

ALTER TABLE maint.events
  DETACH PARTITION maint.events_2024 CONCURRENTLY;

DETACH ... CONCURRENTLY 不能放在显式事务块中;它分两次事务完成,存在 default partition 时也不能直接使用。若中途留下 pending detach,应该检查 catalog 与作业状态, 按实际版本使用 FINALIZE,而不是盲目再次执行或直接 drop。

正式 run 的顺序和结果:

parent before             15,000 rows
expired partition         10,000 rows
DETACH CONCURRENTLY       passed
parent after               5,000 rows
standalone relation       10,000 rows
CSV bytes                 547,894
CSV SHA-256               cd7d54e4781db3e18deb3fd6d49d5e23...
independent restore       matched
detached partition drop   after validation only

SHA-256 完整值在公开结果文件中为:

cd7d54e4781db3e18deb3fd6d49d5e23daf54496da14e41e5daed009a9a41d0d

独立回灌表的行数、日期范围、金额合计和有序 digest 必须与 detach 前 manifest 一致。 只有 round_trip_validated = true 后,runner 才允许删除原分区。这条门槛把“对象已经 离开热路径”和“数据已经可以销毁”明确分开。

第五步:证明副作用已经收束

正式 run 最终证明:

fixture database absent         true
fixture role absent             true
ordinary drop                   true
DROP ... FORCE used             false
unrelated sessions terminated   0
remote temporary path absent    true
persistent config changed       false
production data/traffic touched false

验证器拒绝 28 个预声明反例,并对 14 个现场证据 mutant 验证失败关闭。review 校验私有 证据文件、归档、源散列和公开摘要。这样的负向验证很重要:只证明“正确文件能通过” 无法证明校验器真的会拦住数据库未清理、索引无效、digest 不一致或误用 VACUUM FULL 等失败状态。

一次沙箱成功只说明合同内流程可执行。它没有证明:

  • 同样动作在生产表上的锁时间和 I/O 成本;
  • 当前备份真的可恢复;
  • amcheck 能替代 checksums、pg_verifybackup 或恢复演练;
  • 生产窗口已经获批;
  • 任何固定 dead tuple 比例适合所有表。

28.7.4 输出维护清单及 ch34/ch35 的安全路由

把“维护”拆成不同频率

健康维护不是每月一次“大扫除”,而是多种频率的闭环。

频率观察与动作必须留下的证据
连续XID/MXID headroom、磁盘、autovacuum backlog、复制槽、长事务、错误日志告警身份、开始时间、当前 headroom
每日top dead/churn 表、长事务、slot/prepared xact、失败 vacuum、分区边界排序快照、owner、处置状态
每周autovacuum 覆盖、统计陈旧、HOT 比例、FSM/VM 抽查、索引增长趋势、候选对象、是否需要实验
每月表级 override 审计、冻结策略、空间与 WAL 预算、归档回灌抽检参数来源、容量预算、恢复证据
每季度checksums/pg_amcheck 策略、备份 manifest、整库恢复演练完整性报告、恢复 RTO/RPO
事件驱动大批量导入/更新/删除、版本升级、分区切换、schema change 后前后统计、对象状态、回退结果

频率不是硬编码。写入速率、表大小、SLO、冻结 headroom 和恢复要求决定实际周期。 关键是每项都有 owner、阈值来源、验收和升级路线。

一张可执行的生产工单

任何主动维护动作至少填写:

identity:
  cluster: ...
  instance_role: primary
  database: ...
  relation: schema.table
  relation_oid: ...
  owner: ...

reason:
  signal: ...
  first_seen: ...
  trend_window: ...
  mechanism_evidence: ...
  priority: P0 | P1 | P2

action:
  exact_command: ...
  expected_effect: ...
  alternatives_rejected: ...
  version_authority: PostgreSQL <major.minor> official docs

budget:
  lock_timeout: ...
  statement_timeout: ...
  estimated_runtime: ...
  free_space_required: ...
  wal_budget: ...
  replica_lag_budget: ...
  io_cpu_budget: ...

observation:
  dashboard: ...
  sql_views: ...
  sample_interval: ...
  success_conditions: ...
  abort_conditions: ...

recovery:
  cancel_or_rollback: ...
  invalid_index_cleanup: ...
  pending_detach_finalize: ...
  backup_restore_evidence: ...

approval:
  application_owner: ...
  database_owner: ...
  platform_owner: ...
  window: ...

没有 target OID、精确命令、预算和停手条件的工单,不应进入生产。

Pigsty 命令是入口,不是审批

在目标节点上,Pigsty 提供本地 PostgreSQL 维护封装:

pig pg vacuum  mydb -t app.orders
pig pg analyze mydb -t app.orders
pig pg freeze  mydb -t app.orders
pig pg repack  mydb --plan

它们分别封装 vacuumdbpg_repack,降低日常操作摩擦,但不改变 PostgreSQL 本身的锁、WAL、磁盘、MVCC 和版本语义。特别注意:

  • 不要把 pig pg vacuum mydb --full 当作常规清理;VACUUM FULL 需要 ACCESS EXCLUSIVE 并重写表;
  • pig pg freeze 适用于明确的冻结任务,不是 PostgreSQL 18 事务 ID 硬停机状态下 可以无条件执行的急救口诀;
  • pig pg repack --plan 先列出计划对象,但正式执行仍要预算额外空间、写放大、 WAL、复制延迟和最终锁;
  • pig pg kill 默认 dry-run 是好习惯;即使加 -x,也必须先用 PID、数据库、角色、 application、事务时间和保留证据锁定目标。

平台让命令一致,证据链才决定命令是否应该执行。

明确停手条件

以下任何条件出现,都应停止扩大动作,保留现场并重新判断:

target OID / role / primary identity changed
lock wait exceeds budget
free space falls below action + rollback reserve
WAL rate or replica lag exceeds budget
autovacuum / user workload begins mutual starvation
dead tuple or XID result contradicts hypothesis
REINDEX leaves invalid or _ccnew/_ccold artifacts
DETACH remains pending or archive manifest mismatches
checksum / amcheck / page read reports structural error
unrelated sessions would need termination
only force-drop could clean the fixture

“命令还在跑”不是继续等待的充分理由;“已经跑了很久”也不是取消的充分理由。应根据 预先声明的预算、progress phase、阻塞图和副作用趋势决定。

分流到第 34 章:资源与过载事故

如果数据结构没有明确损坏,但维护动作或 backlog 正在威胁服务,应转入 第 34 章:过载保护与资源故障判型

disk free / inode rapidly falling
I/O queue and latency rise together
VACUUM workers or maintenance I/O crowd out foreground traffic
WAL burst causes archive or replica lag
lock queue expands
connection / worker / memory pressure
multiple maintenance jobs overlap without budget

第 34 章回答的是“如何止血、保护前台、恢复资源 headroom,并在容量与并发边界内重排 维护”,不是在压力中继续加大 vacuum 或 reindex 并发。

分流到第 35 章:数据救援与取证

出现以下证据时,应停止把问题称为“普通膨胀”,转入 第 35 章:数据抢救与工程取证

checksum failure
amcheck reports structural inconsistency
heap / index / VM relation produces impossible invariant
page read or WAL replay exposes corruption
REINDEX cannot establish a trustworthy valid index
backup manifest or restore validation fails
primary and replica disagree in ways normal MVCC cannot explain

进入救援路线后,优先保护证据和可恢复性:

freeze the incident timeline
identify exact relation / fork / block
preserve logs, checksums, WAL and backup manifests
avoid destructive repair on the only copy
restore or clone into an isolated environment
compare independent evidence
then decide rebuild, logical extraction or failover

不要在唯一生产副本上反复尝试 zero_damaged_pages、手工删文件或未经验证的 catalog 修改。这些动作可能把可调查的局部损坏变成不可逆的数据丢失。

本章最终验收清单

完成第 28 章后,读者应该能逐项回答:

  • 我能用触发公式、表级 override 与版本参数解释 autovacuum 为什么启动;
  • 我能区分统计估计、物理抽查、VM、FSM 和关系大小各自证明什么;
  • 我能找到实际保留旧版本的事务、prepared xact 或 replication slot;
  • 我能同时检查 XID 与 MXID headroom,而不是只背一个 wraparound 数字;
  • 我知道普通 VACUUMVACUUM FULLREINDEX CONCURRENTLYpg_repack 的锁、空间和 WAL 边界;
  • 我能把分区 detach、归档、回灌验证和最终删除拆成独立门槛;
  • 我能为 amcheck 选择检查层级与窗口,并知道它不能替代备份恢复;
  • 我能写出含目标、假设、预算、停手、验收和恢复的生产维护工单;
  • 我能判断事件应留在日常维护,还是升级到第 34 或第 35 章;
  • 我不会把一次沙箱成功描述成生产安全证明。

本章参考实验的最终判定为:

maintenance loop demonstrated in sandbox
exact cleanup verified
production_ch28_gate = pending

这三个结论缺一不可:流程已经跑通,副作用已经收束,但生产变更仍未获授权。

进一步阅读:


上一节:amcheck 与例行完整性检查 · 返回本章目录 · 下一章:移花接木:逻辑复制、迁移与异构同步 · 查看全书目录 · 查看索引中心

最后更新于