Postgres 19 让手动 CHECKPOINT 从“立即全刷”变成可控操作

2026-07-17 29 预计阅读时间: 1 分钟
来源: postgr.es AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:9 分钟

在 Postgres 19 之前,手动执行 CHECKPOINT 只有一种含义:立即处理所有待写页面,并在检查点完成后才把控制权交还给客户端。备份、升级和基准测试需要这种确定性,但在繁忙系统上,突然集中的写入也可能把存储延迟推高。

Postgres 19 为这条老命令增加了选项列表,并引入 MODEFLUSH_UNLOGGED。DBA 现在可以选择快速完成、平滑写入,或者明确要求检查点处理 unlogged table 的脏页,同时原有的 CHECKPOINT; 脚本仍可继续工作。

FAST 与 SPREAD:完成同一件事,采用不同节奏

新语法允许在括号中传入选项:

CHECKPOINT (MODE FAST);
CHECKPOINT (MODE SPREAD);
CHECKPOINT (MODE SPREAD, FLUSH_UNLOGGED ON);

MODE FAST 保留传统手动检查点的核心行为:尽快刷新需要处理的数据,并等待操作完成。它适合这些明确需要“现在全部落稳”的场景:

  • 执行基础备份前,让起始状态更容易判断;
  • 运行 pg_upgrade 前,使旧集群进入稳定状态;
  • 两轮基准测试之间,建立一致的测试起点。

MODE SPREAD 则让 checkpointer 有意拉长写入过程,降低瞬时 I/O 峰值。命令仍然是同步的:客户端会等待检查点真正结束,只是等待时间通常更长。这不是减少总写入量,而是用时间换取更平缓的存储负载。

因此,不应把 SPREAD 理解成“后台提交后立即返回”。如果运维脚本有严格超时,切换模式后要同步调整超时和监控阈值。

可以这样实践:测量两种模式的耗时与写入量

下面的实验需要 Postgres 19 测试环境,以及超级用户或拥有预定义角色 pg_checkpoint 的账号。它会创建约数百 MB 的测试数据,请先确认测试库有足够空间,不要直接在生产实例执行。

将连接串替换成自己的测试数据库,然后运行:

export DATABASE_URL='postgresql://postgres@localhost:5432/postgres'

psql "$DATABASE_URL" -v ON_ERROR_STOP=1 <<'SQL'
DROP TABLE IF EXISTS checkpoint_demo;
CREATE TABLE checkpoint_demo (
    id bigint PRIMARY KEY,
    payload text NOT NULL
);

INSERT INTO checkpoint_demo
SELECT i, repeat(md5(i::text), 8)
FROM generate_series(1, 2000000) AS g(i);

CHECKPOINT (MODE FAST);

-- 制造一批脏页。
UPDATE checkpoint_demo
SET payload = repeat(md5((id + 1)::text), 8)
WHERE id % 2 = 0;

SELECT clock_timestamp() AS fast_started \gset
CHECKPOINT (MODE FAST);
SELECT clock_timestamp() - :'fast_started'::timestamptz AS fast_elapsed;

-- 再次修改相同规模的数据。
UPDATE checkpoint_demo
SET payload = repeat(md5((id + 2)::text), 8)
WHERE id % 2 = 0;

SELECT clock_timestamp() AS spread_started \gset
CHECKPOINT (MODE SPREAD);
SELECT clock_timestamp() - :'spread_started'::timestamptz AS spread_elapsed;

SELECT buffers_written, write_time, sync_time
FROM pg_stat_checkpointer;
SQL

结果会受到 checkpoint_timeoutcheckpoint_completion_target、数据是否已被后台写出、共享缓冲区大小和底层存储性能影响。重点不是复现某个固定数字,而是观察两种模式的形态:FAST 往往耗时短、I/O 更集中;SPREAD 往往耗时长、写入更平缓。

若要得到更可信的对比,应重新创建测试表后分别测试两种模式,并同时采集磁盘队列深度、吞吐量和尾延迟。只比较 SQL 命令耗时,无法判断存储是否经历了尖峰。

Unlogged table 为什么默认不参与普通检查点

Unlogged table 不把数据变更写入 WAL,因此写入成本较低,但代价很明确:异常崩溃后内容会被清空,而且这类表不能通过常规物理复制传到备用节点。

既然崩溃恢复无法信任这些数据,普通检查点默认也没有必要把其脏缓冲区写到磁盘。相关页面可能留在内存中,直到干净关闭实例时再处理。Postgres 19 的 FLUSH_UNLOGGED 让 DBA 可以主动改变这一点:

CREATE UNLOGGED TABLE unlogged_stage AS
SELECT i AS id, repeat(md5(i::text), 8) AS payload
FROM generate_series(1, 500000) AS g(i);

UPDATE unlogged_stage
SET payload = repeat(md5((id + 1)::text), 8);

-- 明确要求本次检查点写出 unlogged table 的脏缓冲区。
CHECKPOINT (MODE SPREAD, FLUSH_UNLOGGED ON);

这一选项可能适用于计划内停机:提前承担写入成本,避免所有 unlogged 脏页集中到关闭阶段。但它不会改变 unlogged table 的可靠性语义。即使刚执行过带 FLUSH_UNLOGGED ON 的检查点,崩溃恢复仍会截断该表;它也不会因此获得 WAL 保护或复制能力。

并发请求会合并,选项不是隔离合同

这套接口有一个容易被忽略的边界:多个会话几乎同时请求检查点时,服务器可能把它们合并,而不是连续执行多个检查点。

如果一个会话请求 MODE SPREAD,另一个会话请求 MODE FAST,最终执行的合并检查点会采用更紧急的 FAST 属性。类似地,只要某个请求要求刷新 unlogged 缓冲区,合并后的检查点就可能包含这项工作。

这意味着这些参数表达的是请求偏好,不是当前会话独占的执行合同。你不能仅凭自己发出了 SPREAD,就断言这次 I/O 一定会被平滑展开。生产环境应结合 pg_stat_checkpointer、系统 I/O 指标和操作审计判断实际行为,并限制拥有 pg_checkpoint 权限的账号范围。

上线前的判断清单

Postgres 19 没有改变自动检查点的基本机制,大多数实例也不需要定期手工执行 CHECKPOINT。新选项主要服务于明确的运维动作,而不是替代正常的 checkpoint 调优。

采用时可以按以下原则判断:

  • 备份、升级或可重复基准测试需要尽快稳定状态时,使用 MODE FAST
  • 希望提前推动检查点,又不能接受明显 I/O 冲击时,评估 MODE SPREAD
  • 计划干净停机且 unlogged 数据量很大时,再考虑 FLUSH_UNLOGGED ON
  • SPREAD 操作设置更长的客户端和任务超时;
  • 假设并发检查点请求可能被合并,不把模式选项当作性能保证;
  • 在正式采用前,以 Postgres 19 实际发布版本的文档核对语法,并在接近生产负载的环境中测量。

这项变化不大,却解决了一个长期存在的操作问题:手动检查点不再只能猛烈地一次性冲击存储。DBA 仍要承担判断时机和观测结果的责任,但现在至少拥有了控制写入节奏的明确工具。


相关推荐