跳至内容

26.4 找到饱和点与瓶颈

资源到 100% 不是 saturation 的唯一定义。

系统可能先在:

latency SLO
error/timeout
lock queue
pool wait
WAL/replica/archive lag
memory pressure
storage latency

上失效,而 dashboard 中 CPU 还没满。

容量曲线必须同时画:

offered load
achieved throughput
latency distribution
failure/late/skipped
queue/wait
resource utilization

瓶颈不是“最高的那条曲线”,而是当前最先限制目标的 service center。

26.4.1 吞吐—延迟曲线与排队拐点

典型曲线有三个区域

throughput
  ^
  |                  __________ maximum / collapse region
  |              ___/
  |          ___/
  |      ___/
  |  ___/
  +--------------------------------> offered load / concurrency
       linear      knee       saturated

对应 latency:

latency
  ^
  |                         /
  |                      __/
  |                   __/
  |__________________/
  +--------------------------------> offered load / concurrency

区域:

区域throughputlatencyqueue
linear近似随 load 增长稳定很小
knee增益变小tail 开始快速上升累积
saturated持平或下降非线性恶化持续/失败

capacity 通常选在 knee 左侧,并留 failure/maintenance headroom,而不是取最高 TPS 点。

两个并发点只能 bracket

参考只有 c1/c8:

scalec1 TPSc8 TPSgainc1 p95c8 p95p95 multiplier
S1,508.52,920.51.936x2.1529.4484.390x
M1,555.82,911.51.871x2.1239.3984.427x
L1,423.62,774.31.949x2.25910.0874.465x

c8 throughput 仍比 c1 高约 1.9 倍,不能说 knee 已经位于 c1–c8;p95 却放大 约 4.4 倍,说明排队/并行争用已明显增加。

公共结果因此写:

{
  "interpretation": "knee-not-bracketed-by-one-and-eight-clients",
  "exact_knee_known": false
}

下一轮应补:

c2 c4 c8 c12 c16 c24 c32

或以 open-loop rate 做更密的 SLO sweep。只有两点画出的“曲线”是连线,不是 找到拐点。

closed-loop 曲线的横轴不是 offered arrival

零 think time、$C$ 个 client:

$$ X \approx \frac{C}{R} $$

当 latency 上升,throughput 自动降低。这会形成自我节流:

server slows
  -> each client waits longer
      -> fewer new transactions arrive

真实应用 request 可能继续到达并在 pool/queue 中堆积。因此 closed-loop c8 回答:

eight persistent clients can complete how much work

不回答:

system can admit N external requests/s while meeting p95 and error SLO

open-loop 要同时看 offered 与 achieved

--rate

pgbench \
  --rate=2200 \
  --latency-limit=250 \
  --time=300 \
  ...

记录:

$$ \text{completion ratio}

\frac{\text{successful}}{\text{scheduled}} $$

以及:

schedule lag
late
skipped
failed
queue depth

若 achieved TPS 看起来稳定,但 skipped 快速增加,系统不是健康保持吞吐,而是 丢弃工作。

用 Little’s Law 检查 queue

对 database active/queued population:

$$ L=\lambda W $$

c8、2,920 TPS、mean latency 粗略约:

$$ W \approx \frac{8}{2920} \approx 2.74 ms $$

这个量接近 mixed mean,不是 p95。若观测到平均 active+wait session 与它严重 不一致,检查:

  • transaction log boundary;
  • pool queue 未计;
  • client think/network;
  • sampler bias;
  • multiple transactions per script;
  • offered/achieved 混用。

knee 是多目标决策

可能的 acceptance:

p95 <= 250 ms
p99 <= 500 ms
failed <= 0.1%
skipped = 0
CPU target <= 65%
disk latency within budget
replica/archive catch up within 5 min
pool wait p95 <= 20 ms

最先违反的条件定义当前可用边界。它可能远早于最大 TPS。

throughput collapse

超过 knee 后可能:

more clients
  -> more context switches
  -> more lock queue
  -> more cache churn
  -> more memory/IO pressure
  -> longer transactions
  -> locks held longer
  -> even more queue

形成正反馈,throughput 反而下降。压测 runner 应有 stop condition:

  • failure/late 超阈值;
  • latency 超 hard limit;
  • replica/archive gap 无界增长;
  • disk/memory safety floor;
  • server health/HA 状态变化;
  • client lost;
  • cleanup 不再可保证。

26.4.2 CPU、内存、I/O、WAL 与锁的证据

每个候选瓶颈需要至少两层证据

假设PostgreSQLOS/Pigsty反证
CPUactive no wait、query exec timeCPU busy/run queueclient/lock wait
data I/Opg_stat_io, blks_read/timedisk bytes/latency/queueOS cache
WALpg_stat_wal, WAL IOdisk write, archive/replaydata write
lockwait event, pg_locks blockerlow CPU possibleclient wait
memorytemp bytes, backend countavailable/PSI/OOMcache reclaim
connectionactivity/pool statesclient queueactive execution

一个指标不是 root cause。

CPU:utilization、run queue 与 service demand

参考 measured-window median:

cellserver workclient work
S-c148.9%17.7%
S-c884.2%29.5%
M-c150.1%17.8%
M-c884.0%29.1%
L-c146.3%16.2%
L-c884.2%27.8%

c8 的 server work 显著更高,client 仍有余量。可以说:

server-side work is a stronger limiting candidate than load-generator CPU

不能说:

CPU is the sole root cause

因为约 16% 时间没有被 work 计入,可能是 idle、virtual scheduling、commit path、network 或其他等待;c8 也存在 lock/LWLock/IO samples。

单位 CPU demand:

$$ D_{cpu}

\frac{U_{cpu}\times T}{N} $$

参考 c8 median run:

scaleCPU s / mixed tx
S0.0002906
M0.0002924
L0.0003070

它包含 server 上所有 CPU work,不只 SQL executor;这是容量视角需要的总需求, 但 shared VM background 会引入噪声。

memory:不要用 available 一张图下结论

PostgreSQL memory:

shared memory
+ backend/session memory
+ per-node work_mem/hash memory
+ parallel workers
+ maintenance/autovacuum
+ extension/JIT
+ OS page cache

风险模型:

$$ M_{\text{peak}} \not\approx shared_buffers + max_connections \times work_mem $$

work_mem 是每个 sort/hash operation 的基础限制,一条 query 可能多个,一台 server 可能多个 query/worker。

观察:

  • MemAvailable minimum;
  • swap/OOM/PSI;
  • backend count 与 active count;
  • temp file/bytes;
  • hash/sort spill;
  • parallel workers;
  • cgroup/VM limit。

参考 cells:

temp_bytes 0
swap       0

只说明这个短 workload 未观测到 database temp spill;它不是 memory capacity 证明。

I/O:先分 data、WAL 与 background

pg_stat_io 按:

backend_type
object
context
reads/writes/extends/fsyncs
bytes/time

聚合。track_io_timing=on 才有部分 timing;参考 track_wal_io_timing=off,所以不能声称有 WAL write/fsync timing。

cell database block evidence:

cellblock readsblock hits
S-c10675,976
S-c801,323,054
M-c10733,198
M-c801,326,143
L-c12,500629,335
L-c814,4911,344,632

L 出现 PostgreSQL-level reads,与 dataset 大于 shared buffers 一致;仍不能把 每个 read 当 physical disk。

Pigsty 全实验 window:

node disk read max   13.99 MB/s
node disk write max  81.22 MB/s

包含 3.2M row 初始化,不用于 cell arithmetic。per-run /proc 与 native delta 才用于解释 measured cell。

WAL:按 transaction 和 operation 分解

mixed WAL/tx:

cellWAL bytes / mixed tx
S-c1160.3
S-c8159.5
M-c1176.0
M-c8161.9
L-c1157.6
L-c8196.2

不要用六个值平均成“PostgreSQL 每事务 168 bytes”。它只适用于当前 80% read、 20% write mix,还受 full-page image、background 和短窗口影响。

更好的生产模型:

WAL/read-product
WAL/read-order
WAL/place-order
WAL/background

通过 operation/queryid-scoped delta 或隔离 workload 校准,再按预测 mix 加权。

WAL rate 还决定:

  • replica network/write/replay;
  • archive bandwidth;
  • retained WAL;
  • PITR repository;
  • recovery/catch-up time。

lock:等待样本不是完整 lock history

c8 cell 观察到少量:

Lock
LWLock
IO
Client
CPU

所有 cell deadlock=0。解释边界:

sampled wait
  catches only state at 250 ms sample points

deadlock counter
  counts detected deadlocks

neither proves no short lock waits

要解释 lock:

  • blocker/waiter graph;
  • lock mode/object;
  • transaction age;
  • wait duration;
  • hot key;
  • statement/queryid;
  • timeout/deadlock log。

Client wait 多也不能自动反证 lock;backend 可能在 statement boundary 等 client,另外 client 可能在等另一个 backend。

checkpoint、archive 与 replication

Pigsty PGSQL Persist dashboard 把 WAL、XID、checkpoint、archive 和 I/O 放在同一观察面。

实验期间:

replica replay gap median 0
replica replay gap max    3,471,848 bytes

这表示 exporter 的 replay-distance metric 曾观测到短时差距。不能推出:

  • commit token 已在特定 replica 可见;
  • RPO=0;
  • failure 时一定无数据丢失;
  • catch-up time 在生产 storage 上相同。

第 20 章的 HA contract 与第 25 章的 freshness signal 仍然生效。

用 hypothesis table 组织诊断

hypothesissupportingcontradictingnext test
client CPU ceilingnone strongclient <30%, server ~84%distributed client
PostgreSQL CPU kneeserver rises to ~84%throughput still gains ~1.9xc12/c16/c24
data I/O for Lblock reads appearc8 iowait median lowlarger-than-RAM/open-loop
hot-row lockLock/LWLock samplesno deadlock, gain still largechange Zipf/uniform
WAL limitL-c8 WAL/tx higherno lag growth provenwrite-heavy mix

结论不是“选一行”,而是设计下一个最能区分假设的实验。

26.4.3 连接数增加为何可能降低吞吐

max_connections 是上限,不是目标

max_connections=500 只说明 PostgreSQL 接受的 backend 数上限。它不证明 500 active query 是健康并发。

每个 backend 带来:

  • process 与 private memory;
  • transaction/snapshot;
  • lock table entries;
  • plan/executor state;
  • network socket;
  • statistics/logging;
  • scheduling/cache footprint。

active concurrency 超过 bottleneck 并行度后,额外 backend 主要排队。

CPU 调度与 cache locality

1 vCPU 上 32 个 CPU-bound backend:

no more execution capacity
more runnable processes
more context switching
instruction/data cache disruption
longer transaction duration

长 transaction 又让 lock 持有更久,形成二阶影响。

观察:

CPU utilization
run queue/load
context switches
active sessions
per-query service demand

lock queue

若多个 place-order 更新同一 hot inventory row:

one holder
many waiters

增加 client 不增加该 key 的 service rate,只增加 queue:

$$ W_q \uparrow,\quad X \approx constant $$

若 transaction 在拿锁前还做很多工作,rollback/retry 成本更高。

措施不是盲目加连接:

  • partition/shard hot key;
  • shorten transaction;
  • fixed lock order;
  • admission per key/tenant;
  • move external calls outside transaction;
  • optimistic/versioned update;
  • reduce retry storm。

memory amplification

更多 active query:

more sort/hash
more parallel workers
more temp spill
less OS cache
more I/O

然后 latency 上升,连接占用更久,再增加在途数。

storage queue

存储有可用并行深度,但不是无限:

low concurrency
  underutilized device

optimal concurrency
  latency acceptable, throughput high

excess queue
  throughput flat, latency rises

同一 device 上 data、WAL、checkpoint、archive staging 和 backup 可能相互影响。

parallel query 与 client concurrency 相乘

若一个 query 最多 4 workers:

8 active queries
may request up to 8 leaders + 32 workers

PostgreSQL resource 文档提醒 parallel worker 是独立 process,CPU/memory impact 类似额外 session,work_mem 也按 worker/operation 作用。

所以容量合同要同时固定:

client concurrency
max_parallel_workers
max_parallel_workers_per_gather
query plan

pool 的正确目标是限制 server concurrency

例如:

10,000 idle application connections
  -> PgBouncer
      -> 32 server connections
          -> 8 active budget

pool queue 是可控 admission,前提是:

  • queue latency 可观测;
  • client timeout 大于/匹配 policy;
  • cancellation 正确;
  • transaction pooling 兼容 session feature;
  • retry 不放大;
  • queue 有上限。

没有上限的 pool 只是把 outage memory 从 PostgreSQL 移到中间层。

找到健康并发的实验

对每个 data/mix:

c1 c2 c4 c8 c12 c16 c24 c32

每点:

  • 固定 offered model;
  • 重复;
  • 画 throughput、p95/p99、failed、late、queue;
  • 同时画 server/client CPU、I/O、WAL、lock;
  • 记录 pool/direct path。

选择:

first point before material tail acceleration
and within headroom/failure policy

不是:

highest observed TPS

本节结论

参考证据支持:

  • c8 比 c1 吞吐高约 1.9x;
  • c8 pooled p95 高约 4.4x;
  • client CPU 不是明显 ceiling;
  • server work、lock/LWLock/IO 与 L block-read 都值得下一轮区分;
  • 两点没有定位 exact knee。

它不支持“最佳连接数=8”。下一轮实验而不是措辞强度,才能减少未知项。


上一节:建立可信实验 · 返回本章目录 · 下一节:从测量推导容量与成本 · 查看全书目录 · 查看索引中心

最后更新于