PostgreSQL 内部有一套低调但关键的共享内存结构叫 SLRU(Simple LRU),负责管理事务状态、提交时间戳、子事务等核心元数据。多年来这些缓冲池的大小全部硬编码,遇到高并发或长事务场景只能靠改源码重新编译。PG 17 打破了这一限制——首次把 SLRU 缓冲池大小暴露为 GUC 参数,commit_timestamp_buffers 就是其中之一。
SLRU 是什么,为什么它容易成为瓶颈
SLRU 是 PostgreSQL 实现的一组轻量 LRU 缓存,每个用途独立一套缓冲池,底层用 8KB 页面为单位管理。常见的 SLRU 用途包括:
| SLRU 用途 | 对应磁盘文件 | 旧版硬编码页面数 |
|---|---|---|
| 事务状态(clog) | pg_xact |
128 |
| 提交时间戳 | pg_commit_ts |
1024(启用时) |
| 子事务 | pg_subtrans |
128 |
| MultiXact 成员 | pg_multixact/members |
128 |
| MultiXact 偏移 | pg_multixact/offsets |
128 |
每个页面 8KB,128 页就是 1MB。看起来不大,但问题在于:当缓冲池不够,SLRU 必须频繁把脏页刷盘再换入新页,产生大量 I/O。而这些元数据的访问路径处在事务提交的关键环节——缓冲池不足直接拖慢提交延迟,监控里表现为 slru_read / slru_write 指标飙升。
典型触发场景:
- 大量短事务并发提交——clog 和 commit timestamp 页面快速轮转。
- 长事务产生大量子事务——subtrans 缓冲池压力骤增。
- 大量
SHARE ROW EXCLUSIVE锁导致 MultiXact 热点——members/offsets 缓冲池吃紧。
过去你只能观察指标、确认瓶颈,然后……等上游改硬编码值。PG 17 终于给了运维直接调参的能力。
PG 17 新增的 SLRU GUC 参数一览
PG 17 把以下 SLRU 缓冲池大小全部开放为 GUC:
commit_timestamp_buffers
multixact_member_buffers
multixact_offset_buffers
subtrans_buffers
transaction_buffers # 即 clog
async_buffers # 异步提交通知
这些参数都是 8KB × 值 的总缓冲大小。默认值保持与旧版硬编码一致,确保升级无行为变化。参数类型为 postmaster——修改需要重启 PostgreSQL。
以 commit_timestamp_buffers 为例,默认 1024 页(8MB)。如果你启用了 track_commit_timestamp 并且集群写入量很大,8MB 可能不够,导致提交时间戳页面频繁换入换出。
实操:监控 SLRU 呋声并调参
第一步:确认当前 SLRU 活动
PG 17 在 pg_stat_slru 视图中暴露了每个 SLRU 的读写统计。先看现状:
SELECT name,
blks_read,
blks_written,
blks_read / (blks_read + blks_hit + 0.001) AS read_ratio
FROM pg_stat_slru
ORDER BY blks_written DESC;
read_ratio 接近 1 说明几乎每次访问都要从磁盘读——缓冲池严重不足。blks_written 高则说明频繁刷脏页。
第二步:确认启用了提交时间戳追踪
SHOW track_commit_timestamp;
如果返回 off,commit_timestamp_buffers 基本无意义——该 SLRU 不会被使用。启用它:
-- 需要重启生效
ALTER SYSTEM SET track_commit_timestamp = on;
-- 同时考虑调大缓冲池
ALTER SYSTEM SET commit_timestamp_buffers = 2048; -- 16MB
SELECT pg_reload_conf(); -- track_commit_timestamp 需要 restart,此步仅做标记
然后重启 PostgreSQL:
# 根据你的部署方式选择
pg_ctl restart -D /var/lib/postgresql/17/main
# 或 systemd
sudo systemctl restart postgresql-17
第三步:验证新配置生效
SHOW commit_timestamp_buffers;
-- 应返回 2048
-- 重启后重新观察 SLRU 统计
SELECT pg_stat_reset_slru(); -- 清零旧统计,便于对比
-- 运行一段业务后再次查询
SELECT name, blks_read, blks_written
FROM pg_stat_slru
WHERE name = 'commit_timestamp';
如果 blks_read 显著下降,说明缓冲池扩容有效。
第四步:一个完整调参脚本示例
下面是一个可以直接在 psql 中运行的诊断+调参脚本:
-- slru_tune.sql —— SLRU 缓冲池诊断与调参建议
\pset format wrapped
-- 1. 查看当前 SLRU 命中情况
SELECT name,
blks_hit,
blks_read,
blks_written,
ROUND(blks_read::numeric /
NULLIF(blks_hit + blks_read, 0) * 100, 2) AS miss_pct
FROM pg_stat_slru
ORDER BY miss_pct DESC NULLS LAST;
-- 2. 查看当前各 SLRU GUC 值
SELECT name,
setting,
unit,
source
FROM pg_settings
WHERE name IN (
'commit_timestamp_buffers',
'multixact_member_buffers',
'multixact_offset_buffers',
'subtrans_buffers',
'transaction_buffers',
'async_buffers'
);
-- 3. 对 miss_pct > 5% 的 SLRU 给出翻倍建议
-- (仅输出建议,不自动执行)
SELECT 'ALTER SYSTEM SET ' || s.name || ' = ' ||
(g.setting::int * 2) || '; -- 当前 ' || g.setting ||
' 页,建议翻倍至 ' || (g.setting::int * 2) || ' 页'
FROM pg_stat_slru s
JOIN pg_settings g
ON g.name = s.name || '_buffers'
WHERE ROUND(s.blks_read::numeric /
NULLIF(s.blks_hit + s.blks_read, 0) * 100, 2) > 5;
运行方式:
psql -h localhost -U postgres -d mydb -f slru_tune.sql
根据输出建议,挑选需要调整的参数写入 postgresql.conf 或通过 ALTER SYSTEM 设置,然后重启。
调参的边界与风险
缓冲池调大不是无成本的:
- 共享内存争用:SLRU 缓冲池从
shared_buffers之外单独分配,占用的是 PostgreSQL 进程的共享内存段总空间。调大多个 SLRU 缓冲池可能需要同步增大shared_memory_size(通过内核参数shmmax等),否则启动时报内存不足。 - 冷启动延迟:缓冲池更大意味着崩溃恢复后需要预读更多 SLRU 页面,恢复时间可能略增。
- 过度配置浪费:如果
pg_stat_slru显示 miss_pct 始终 < 1%,扩容纯属浪费内存。先量测,再调参。
一个务实的策略:
- 升级到 PG 17 后先保持默认值运行一周。
- 收集
pg_stat_slru的 miss_pct 和blks_written。 - 只对 miss_pct > 5% 的 SLRU 翻倍缓冲池。
- 重启后再观察一周,miss_pct 降到 < 1% 即可收手。
检查清单
上线 PG 17 SLRU 调参前,确认以下事项:
- [ ] PG 17 已部署且
pg_stat_slru视图可访问 - [ ] 运行至少 3 天业务负载后收集 SLRU 统计
- [ ] 确认
track_commit_timestamp状态——关闭时commit_timestamp_buffers无效 - [ ] 计算新增 SLRU 缓冲总内存,确认共享内存段限额足够
- [ ] 只对 miss_pct 明确偏高的 SLRU 调参,避免一刀切翻倍
- [ ] 修改后必须重启,安排低峰期操作
- [ ] 重启后重置
pg_stat_slru统计,持续观察至少 3 天验证效果
SLRU 缓冲池可配置是 PG 17 一个看似小但实际影响深远的改动。它把过去只能靠源码修改的内部调优,变成了运维可直接操作的参数。从 commit_timestamp_buffers 入手,量测先行、按需扩容,你就能在高并发提交场景下拿到更稳定的延迟表现。