27.4 规划器、并行与连接参数
规划器参数决定 PostgreSQL 如何比较候选 plan;并行参数决定 plan 可请求多少 worker; 连接参数决定多少 backend 能同时竞争资源。
三者形成一条链:
cost/statistics
-> chosen plan and requested workers
-> active backend/worker population
-> CPU/memory/I/O/lock demand
-> pool queue and SLO把它们分开调,容易得到一个单 query 很快、cluster 却更慢的系统。
27.4.1 成本参数只能用硬件和计划证据校准
cost 是相对单位
常见:
seq_page_cost
random_page_cost
cpu_tuple_cost
cpu_index_tuple_cost
cpu_operator_cost
parallel_setup_cost
parallel_tuple_cost它们不是毫秒,默认以 sequential page cost 为相对基准。把所有 cost 同乘 10,plan 通常不变;重要的是相对值。
random_page_cost
降低它会让 random/index access 相对便宜,可能使 planner 更偏向 index scan。正确 证据:
actual storage random vs sequential latency
cache residency
concurrent workload
tablespace/storage difference
representative plans and actual time/buffersPostgreSQL 官方指出,默认 4.0 已隐含一部分 random access 会命中 cache;完全 cached 时较低值可合理,random 物理 I/O 昂贵时则可能需要较高值。
不能这样校准:
query uses seq scan
-> random_page_cost = 1.0seq scan 可能就是正确 plan,或根因是 statistics/index/predicate。
tablespace 可有不同 cost
若 hot index 在 NVMe、archive table 在 HDD,可在 tablespace scope 设置 page cost, 不必用一个 cluster-global average 强迫两种 storage:
ALTER TABLESPACE fast_ssd
SET (
random_page_cost = 1.1,
seq_page_cost = 1.0
);仍需把 filesystem/cache 与 production I/O 测量纳入。
effective_io_concurrency
它表达 PostgreSQL 可以向 storage 发起的并发 I/O 提示能力;合理值取决于 device/ filesystem/RAID/cloud volume 和 PostgreSQL I/O implementation。大值不是免费吞吐:
- device queue 可能更深;
- tail latency 可能变差;
- shared storage 邻居受影响;
- query/scan type 可能不使用;
- OS/backend 的实现随 major version 演进。
参考沙箱为 200、source 是 configuration file;这不能直接复制到 HDD 或网络块存储。
effective_cache_size
只影响 planner estimate,不分配 memory。校准时考虑:
shared_buffers
+ PostgreSQL data 可实际使用的 kernel cache
- concurrent queries sharing cache
- same blocks duplicated in two caches不要写成 host RAM 总量,也不要当作 cache guarantee。
statistics 优先于 cost hack
如果 estimated rows 错数个数量级:
EXPLAIN (ANALYZE, BUFFERS, SETTINGS)
SELECT ...;先查:
ANALYZE recency/sample
default_statistics_target
ALTER COLUMN SET STATISTICS
extended statistics: dependencies/ndistinct/mcv
expression statistics
partition statistics
parameter/cast/collation成本模型在错误 cardinality 上做得再精细,也会比较错误规模的 plan。
enable_* 是诊断探针
SET LOCAL enable_hashjoin = off;
SET LOCAL enable_nestloop = off;
SET LOCAL enable_seqscan = off;可以回答:
planner 若被迫使用另一路径,实际是否更好?
不应默认成为永久 global 配置。某种 plan type 对当前一条 query 不好,不代表对全库 无用。
plan cache
prepared statement 有:
custom plan
parameter value known
planning repeated
can adapt to skew
generic plan
reusable
lower planning cost
cannot adapt to specific valueplan_cache_mode=auto 通常先生成若干 custom plan,再比较 generic cost。可以用:
SELECT
name,
generic_plans,
custom_plans,
parameter_types
FROM pg_prepared_statements;第 27 章实验中,两个 probe 在 auto 下均为:
custom_plans 5
generic_plans 5说明自动策略已经切换。强制 generic 的长期 TPS 益处没有达到 2% 置信下界。
对于 tenant skew:
tenant small -> index/nested loop
tenant huge -> bitmap/hash/seq alternative强制 generic 可能让其中一类严重退化。probe 必须覆盖高低/MCV/边界,而不是只用 id=1。
JIT
JIT 有 compilation/setup cost,也可能加快长 CPU-intensive execution。阈值由 plan estimated cost 决定:
jit_above_cost
jit_inline_above_cost
jit_optimize_above_costOLTP point query 常低于阈值;把 jit=off 后看到无差异不证明 JIT 没用,只说明当前
query 没触发或收益不足。分析 query 要把:
planning/JIT generation time
execution time
repetition/cache
CPU分开。
calibration report
| query class | estimated rows | actual rows | plan | buffers/I/O | execution | alternative | result |
|---|---|---|---|---|---|---|---|
| point | |||||||
| range | |||||||
| tenant-small | |||||||
| tenant-large | |||||||
| analytic |
只从两条 query 推导 cluster cost constants 很危险。官方文档也指出,没有定义良好的 “理想 cost”求法,应把它视为 workload average。
27.4.2 并行 worker 的全局预算与退化条件
三层上限
max_worker_processes
hard pool for background workers
max_parallel_workers
cluster parallel operation subset
max_parallel_workers_per_gather
one Gather/Gather Merge request还包括:
max_parallel_maintenance_workers
extension background workers
logical replication workers提高 per-gather 而不提高上层 pool,可能没有效果;提高上层 pool 又会增加 cluster CPU/ memory contention。
plan request 不等于实际 worker
Workers Planned: 4
Workers Launched: 2worker unavailable 时,query 通常以更少 worker 运行;某些 parallel plan 在 worker 不足时效率很差。要记录:
planned
launched
launch wait/starvation
leader participation
other concurrent parallel queriesPostgreSQL 18 的 database statistics 还提供 parallel worker launch 相关累计信息; system view 随版本变化,先查当前列。
leader 也可能工作
parallel_leader_participation=on 时 leader 可以执行 parallel plan,也要负责读取 worker
tuple。若 worker 输出大量 tuple,leader 可能主要消耗在汇总/传输;parallel speedup
不会等于 worker 数。
Amdahl:
$$ S(N)
\frac{1} {(1-P)+P/N+\text{parallel overhead}} $$
serial fraction、launch、tuple transfer 与 skew 限制 speedup。
memory 按 process 放大
PostgreSQL 官方给出的关键边界:parallel query 的 resource limit 通常按 worker process 应用。4 个 worker 加 leader,某些 node 的内存/CPU/I/O footprint 可接近串行的 5 倍。
因此:
max_parallel_workers_per_gather=4
work_mem=256MB不能解释成“一条 query 最多 256MB”。
全局 CPU budget
若 host 有 $C$ cores、需要为 OLTP 保留 $R$ cores:
$$ C_{\text{parallel budget}} \le C-R-\text{background/failure headroom} $$
并行分析的 admission:
$$ \sum_q \text{active}_q \times (1+\text{workers}_q) \le \text{process/CPU budget} $$
不是“每条报表允许 8 worker,所以十条报表都允许 8”。
什么时候 parallel 反而慢
- query 太短,launch/setup 占比高;
- output 太大,leader/tuple transfer 成为瓶颈;
- worker skew,一人做绝大多数工作;
- storage 已饱和;
- memory spill 按 worker 放大;
- concurrent OLTP 被抢 CPU;
- worker pool 不足;
- parallel-unsafe/restricted function;
- serialization/ordering overhead。
接受时比较:
single-query duration
cluster throughput
OLTP tail
CPU/I/O/memory
worker availability
N+1 state维护 worker
parallel CREATE INDEX/VACUUM 与 parallel query 的 memory limit 语义不完全相同,
但仍争抢 CPU/I/O/worker。DDL window 中提高 maintenance worker,可能缩短单项任务,
却把 replica/archive 与 OLTP 推过 SLO。
角色级治理
分析角色:
ALTER ROLE dbuser_analytics
SET max_parallel_workers_per_gather = 4;OLTP 角色:
ALTER ROLE dbuser_app
SET max_parallel_workers_per_gather = 0;只是示意,不是默认建议。要验证新连接、pool lifecycle 和 query class。role scope 比 global 更贴近 ownership,但一个 role 内也可能混合 workload。
27.4.3 连接上限、超时和锁等待边界
connection slot 不是并发目标
max_connections 是 server 能接纳的 backend 上限。提高它:
- 预留更多 shared structures;
- 允许更多 private backend;
- 扩大 active query/memory/lock population;
- 增加 context switch;
- 可能降低每条 query cache locality;
- 使 overload 更深。
它不增加 CPU、memory bandwidth、IOPS 或 lock throughput。
参考沙箱:
max_connections = 500
context = postmaster
source = command line
RAM ~1.91 GiB这是 platform/template fact,不表示 500 个 64MB-work-mem query 可同时 active。
连接预算
$$ \text{max connections} \ge \text{app server connections} + \text{replication} + \text{maintenance} + \text{monitoring} + \text{reserved/emergency} + \text{migration overlap} $$
同时:
$$ \text{active app connections} \le \text{resource/SLO envelope} $$
两条都要满足。通常:
logical client population
> pool client connections
> active server connectionsreserved slots
PostgreSQL 18:
reserved_connections
superuser_reserved_connections前者供拥有 pg_use_reserved_connections 的角色,后者是 superuser 最后保留。设计:
- emergency role 最小权限;
- pool 不耗尽 reserved;
- monitoring/replication 配额;
- incident 时实际演练能连接;
- standby 的
max_connections不低于 primary。
把 emergency slot 留给日常应用,相当于没有 reserve。
pool queue 比 database overload 更可控
在 pool:
queue depth
wait time
timeout
admission/fairness可以观察和限制。把 server connection 上限提高,会让更多 request 同时进入 executor/ lock/memory,queue 仍然存在,只是搬到了更危险的位置。
pool sizing 要配合第 22 章 transaction/session semantics:
- session state;
- prepared statement;
- temp table;
- advisory lock;
- LISTEN/NOTIFY;
- transaction pooling reset。
statement_timeout
从 command 到达 server 开始计,extended protocol 对 Parse/Bind/Execute/Sync 有具体 边界。它终止 statement,不等于 HTTP request deadline。
不要设置一个过于激进的 global value杀掉:
DDL
backup catalog
maintenance
replica diagnostic
legitimate report优先按 role/database:
ALTER ROLE dbuser_app
IN DATABASE app
SET statement_timeout = '2s';新 session 生效。
lock_timeout
只在等待 lock 时计时,并且每次 lock acquisition 单独应用。若它等于或大于
statement_timeout,往往 statement timeout 先触发。
常见关系:
lock_timeout < statement_timeout <= request deadline但 transaction 内多条 statement、client network 和 retry 仍需 budget。
DDL migration 常用短 lock_timeout 来避免排队阻塞业务:
BEGIN;
SET LOCAL lock_timeout = '500ms';
SET LOCAL statement_timeout = '5min';
ALTER TABLE ...;
COMMIT;失败要退出/重试,不应无限 loop。
idle 与 transaction timeout
idle_in_transaction_session_timeout
session 在 open transaction 中 idle
防止长期持锁/阻碍 vacuum
idle_session_timeout
无 transaction 的 idle session
pool 中要谨慎,middleware 未必处理意外关闭
transaction_timeout
整个 transaction 存活时间
prepared transaction 不受其约束PostgreSQL 18 官方指出,不建议把某些 timeout 粗暴写成影响所有 session 的
postgresql.conf default。scope 应跟 workload。
timeout 不是 cancel 后就结束
应用必须:
observe SQLSTATE
rollback failed transaction
release/replace connection
respect outer deadline
limit retries
preserve idempotency否则 database cancel 后,client 立即重试可能制造 retry storm。
lock diagnosis
不要为了更快报 deadlock,把 deadlock_timeout 全局调到 1ms。deadlock check 有成本,
普通 lock wait 不是 deadlock。
调查:
SELECT
pid,
pg_blocking_pids(pid) AS blockers,
wait_event_type,
wait_event,
xact_start,
query_start
FROM pg_stat_activity
WHERE wait_event_type = 'Lock';配合 log_lock_waits 与适当 deadlock_timeout,在诊断窗口使用。最终修复通常是:
- transaction order;
- shorten critical section;
- index/access path;
- hot-key design;
- admission;
- application retry;
而不是无限提高 timeout。
一份连接/超时合同
application_deadline: 2500ms
pool_wait_timeout: 200ms
connect_timeout: 300ms
lock_timeout: 400ms
statement_timeout: 1800ms
transaction_timeout: 2200ms
idle_in_transaction_timeout: 30s
retry:
max_attempts: 2
budget_included_in_deadline: true
server_connections:
active_cap: 48
reserved_platform: 12
emergency: 5数值只是示意。关键是所有 timeout 和 slot 在同一个 end-to-end budget 中,不互相 矛盾。
上一节:WAL、检查点与写入平滑 · 返回本章目录 · 下一节:参数作用域与变更方式 · 查看全书目录 · 查看索引中心