26.1 从需求建立容量模型
容量规划的第一份输入不是 CPU 核数,而是业务在什么时间、以什么方式要求系统 完成什么工作。
一句“峰值 5,000 QPS,读写比 8:2”至少缺少:
- QPS 是 HTTP request、SQL statement 还是 transaction;
- 读是 point lookup、范围扫描、聚合还是 cache miss 后回源;
- 写是单行更新、订单事务、批量导入还是索引构建;
- 一次 request 打开几个 transaction、执行几条 SQL;
- key 是均匀分布还是集中在少数 tenant/product;
- peak 持续 10 秒、10 分钟还是 10 小时;
- 失败后谁重试、最多几次、是否产生重复写;
- batch、backup、vacuum、DDL 和 failover 是否同时发生;
- 延迟、正确性、恢复和成本目标是什么。
所以容量模型从一份 workload contract 开始:
operation
arrival process
transaction boundary
SQL and data shape
consistency and durability
retry and timeout
latency/error/correctness objective
growth and retention
overlap and failure state第 24 章把服务承诺写成 SLO;第 25 章把承诺落实为观察合同;本节把同一套语义 变成容量输入。三章必须使用同一个 operation name 和 eligible event 定义。
26.1.1 事务类型、读写比、并发和数据增长
先分清五个容易混用的量
| 量 | 符号 | 例子 | 不能替代 |
|---|---|---|---|
| 到达率 | $\lambda$ | 2,000 eligible order request/s | database TPS |
| 在途量 | $L$ | 300 个尚未完成的 request | connection count |
| 响应时间 | $W$ | p95 120 ms | SQL execution time |
| database transaction rate | $X$ | 2,400 commit/s | request rate |
| statement rate | $Q$ | 18,000 statement/s | transaction rate |
一次 place-order 可能:
HTTP request
-> auth/cache reads
-> BEGIN
SELECT product
UPDATE inventory
INSERT order
INSERT order_item
COMMIT
-> publish outbox later于是:
1 request
!= 1 SQL
may equal 1 or more database transactions
may cause background transactions after response如果把 5,000 HTTP RPS 直接填成 pgbench --rate=5000,脚本却只执行一条
SELECT 1,得到的不是业务容量,只是另一种 workload 的数字。
用 operation class,而不是“读/写”两个桶
一个最小 operation catalog:
| operation | request/s | DB tx/request | statements/tx | data shape | consistency |
|---|---|---|---|---|---|
| read-product | 4,000 | 1.0 | 1–2 | Zipf point read | current primary |
| read-order | 1,500 | 1.0 | 1 | customer recent-N | 5 s freshness |
| place-order | 800 | 1.0 | 4–8 | hot inventory + append | durable commit |
| reconcile | 20 | 1.0 | range + aggregate | time window | correctness control |
| expire-order | 100 | 1.0 | scan + update | batch | eventual |
同为“读”:
indexed point lookup
index-only recent-N
wide range scan
hash aggregate spilling to temp
JSON path evaluation
vector nearest-neighborCPU、buffer、I/O、work_mem、parallel worker 和 lock footprint 完全不同。
同为“写”:
HOT-eligible update
indexed-column update
append-only insert
upsert on a hot unique key
multi-table transaction with foreign keys
bulk COPYWAL、index maintenance、dead tuple、checkpoint、replication 和 vacuum 成本也不 一样。
给每种 operation 建资源需求向量
对 operation $i$,定义:
$$ \mathbf{D_i}
(D_{cpu}, D_{read}, D_{write}, D_{wal}, D_{lock}, D_{temp}, D_{net}) $$
混合 workload 的单位需求不是简单“平均 SQL”:
$$ \mathbf{D_{mix}}
\sum_i w_i \mathbf{D_i} $$
其中 $w_i$ 是 transaction mix 权重。若流量增长只发生在
place-order,不能继续使用旧的全局 $D_{mix}$:
$$ \text{resource rate}
\sum_i \lambda_i \mathbf{D_i} $$
因此容量报告至少同时保留:
- operation-class rate;
- mix;
- 每类 service demand;
- mixed demand;
- 预测期内 mix 是否变化。
读写比为什么经常误导
“80% 读、20% 写”没有说明计数单位:
by request?
by transaction?
by statement?
by tuple?
by byte?
by CPU second?
by WAL byte?参考实验按 pgbench transaction 选择脚本:
read-product 50
read-order 30
place-order 20这是 transaction selection weight。它不保证:
- 80% CPU 消耗来自读;
- 20% statement 是写;
- 20% WAL-producing operation;
- 20% wall time 在写事务;
- 每次运行精确出现 50/30/20。
raw transaction log 必须核对实际 script count。
用 Little’s Law 做第一轮自洽检查
稳定系统中,平均在途量满足:
$$ L = \lambda W $$
若:
arrival rate 2,000 request/s
mean latency 80 ms = 0.08 s则平均在途 request:
$$ L = 2000 \times 0.08 = 160 $$
这不是说“必须给 PostgreSQL 160 个连接”。应用中的在途量可能分布在:
edge queue
application worker
pool wait
database execution
external call
response serialization如果 transaction 只占 15 ms:
$$ L_{db} = 2000 \times 0.015 = 30 $$
一个 30 左右的 active DB concurrency 可能足够;160 个 backend 反而增加上下文 切换和内存风险。
Little’s Law 是平均量关系,不描述 tail,也不自动证明系统稳定。若 offered load 超过 service capacity,queue 持续增长,就不存在可长期使用的稳态平均。
并发不是连接数
把连接状态拆开:
| 状态 | 是否占 client connection | 是否占 server backend | 是否消耗执行资源 |
|---|---|---|---|
| application queue | 否/可能 | 否 | 应用资源 |
| PgBouncer waiting client | 是 | 否 | pool queue |
| server connection idle | 是 | 是 | backend memory,少量管理成本 |
| active on CPU | 是 | 是 | CPU |
| active waiting lock/I/O | 是 | 是 | queue + held resources |
| idle in transaction | 是 | 是 | snapshot/lock/vacuum horizon 风险 |
容量模型关心 active concurrency、queue、transaction duration 和 backend
footprint,而不是只看 max_connections。
数据增长必须进入 workload
数据量改变:
- B-tree 高度和 cache working set;
- statistics 与 selectivity;
- index/table correlation;
- vacuum 和 analyze 时间;
- checkpoint、base backup、restore、upgrade 时间;
- retained WAL、replication catch-up 和 archive volume;
- partition 数量、catalog 开销与 planning time;
- maintenance workspace。
至少分开:
logical rows/day
logical bytes/day
table bytes/day
index bytes/day
TOAST bytes/day
WAL bytes/day
archive bytes/day
backup repository growth/day参考实验三档 shopbench schema:
| scale | history rows | schema bytes |
|---|---|---|
| S | 100,000 | 29,704,192 |
| M | 800,000 | 235,175,936 |
| L | 3,200,000 | 942,268,416 |
L 已大于 512,753,664-byte shared_buffers,但小于 server RAM。它能观察
PostgreSQL buffer miss,却不能区分 page 是否真正来自物理磁盘还是 OS page
cache。PostgreSQL 的
pg_stat_io 文档
也明确指出,数据库 I/O 统计无法区分内核调用最终命中 page cache 还是访问
storage,必须结合 OS 证据。
本目产物:业务容量输入表
forecast_window: 12 months
operations:
place-order:
eligible_peak_tps: 800
peak_duration: 20m
db_transactions_per_request: 1
retry_amplification_p95: 1.04
data_growth:
orders_per_success: 1
line_items_per_order_p95: 6
objective:
p95_ms: 250
error_ratio: 0.001
correctness: no unexplained duplicate不要在这个阶段填“8 vCPU”。先把问题写完整。
26.1.2 平均值、峰值、突发与批处理叠加
日平均掩盖容量风险
一天 86,400 秒。日订单 8,640,000:
$$ \text{daily average} = 100/s $$
但若 40% 发生在两小时促销窗:
$$ \text{promotion average}
\frac{8{,}640{,}000 \times 0.4}{7200} = 480/s $$
再叠加一分钟抢购峰值、支付回调、重试和 batch,瞬时 offered load 可能超过 1,000/s。用 100/s 采购必然低估。
至少保留四种时间尺度
| 时间尺度 | 回答问题 | 常见误用 |
|---|---|---|
| 1–10 s | 突发、queue、admission | scrape 太慢完全看不见 |
| 1–5 min | 用户影响、autoscaling 响应 | 被小时均值摊平 |
| 1 h | 班次、batch、业务时段 | 当成 peak |
| day/week/season | 容量增长、节日、结算 | 无法解释短时 saturation |
一个 forecast 需要:
baseline
peak factor
burst factor and duration
seasonality
growth trend
event calendar
retry amplification
batch overlap
maintenance overlap区分 arrival burst 与 backlog drain
两种看起来都像“TPS 突然上升”:
new user demand
arrival rises
backlog drain
arrival may have fallen
workers consume queued work faster后者常在依赖恢复后发生:
payment provider recovers
-> retries released
-> queue drain
-> database write burst
-> replicas/archive/backup lag如果模型只用前台 request,遗漏 backlog,恢复本身就可能触发第二次事故。
重试是负载放大器
每个原始 operation 平均尝试次数:
$$ A
1 + r_1 + r_2 + \cdots $$
更实用地:
$$ \lambda_{\text{database}}
\lambda_{\text{eligible}} \times \text{attempts per eligible operation} $$
例如:
eligible arrival 1,000/s
5% requests retry once
1% requests retry twice平均尝试:
$$ 1 + 0.05 + 2 \times 0.01 = 1.07 $$
数据库看到约 1,070 attempt/s。若超时发生在 commit outcome unknown 区域,盲目 重试还可能制造 duplicate 与 reconciliation workload;不能只算 TPS,不算正确性。
batch 不能用“夜间”一笔带过
列出每个 batch:
| job | schedule | duration | read/write | temp | WAL | lock | retry |
|---|---|---|---|---|---|---|---|
| settlement | 00:05 | 18 min | heavy read/update | possible | high | row | yes |
| expire-order | every 5 min | 40 s | scan/update | low | medium | row | yes |
| analytics extract | 01:00 | 45 min | range read | spill risk | low | AccessShare | restart |
| backup | 02:00 | 60 min | storage/network | n/a | archive coupling | n/a | resume |
“平均业务低谷”不代表有余量。batch、autovacuum、checkpoint、archive、 replication catch-up 和 backup 可能恰好在低谷争用 I/O。
把 overlap 写成场景
N0 normal peak
interactive peak + routine background
N1 campaign peak
interactive 2.5x + retries 1.1x
M1 maintenance overlap
normal peak + vacuum/index build/backup
F1 one replica unavailable
normal peak + catch-up/rebuild or read traffic reroute
F2 primary failover
reconnect storm + retry burst + cold-ish cache + reduced topology容量批准必须说明要满足哪个场景。只在 N0 通过,不等于生产通过。
平均、quantile 与最大值各有用途
mean
resource accounting, Little's Law, long-run throughput
p50
typical transaction
p95/p99
user tail and queue onset
max
evidence lead, but sample-size sensitive不能把每分钟 p99 再做平均并称为“全天 p99”。若需要跨 window 或 instance 聚合,保留原始 transaction sample 或可聚合 histogram。Prometheus 的 histogram 指南 解释了为什么直接平均预计算 quantile 在统计上没有意义。
26.1.3 延迟目标、错误预算与安全余量
容量是带约束的可行域
把 capacity 定义为:
$$ \max \lambda \quad \text{subject to} \quad \begin{cases} P(W \le T) \ge S \ \text{error ratio} \le E \ \text{correctness invariant holds} \ \text{resource and recovery limits hold} \end{cases} $$
例如:
place-order p95 <= 250 ms
availability >= 99.9%
no unexplained duplicate order
replica/archive remain within declared recovery bounds
primary disk < 70%
one declared failure still has headroom没有约束的“最大 TPS”只是最大努力点。
延迟目标要定义 measurement boundary
这些延迟不相等:
client schedule -> response
application admission -> response
pool checkout -> release
transaction BEGIN -> COMMIT response
one SQL execute
server execution excluding network参考实验的 latency:
origin pgbench client
boundary one chosen script
protocol prepared
connection persistent
path pg-meta-1 -> pg-test-1:5432它不包含 application edge、HAProxy、PgBouncer 和 WAN,所以不能与第 24 章
place-order 用户 SLO 直接画等号。
错误不能从分母消失
报告至少包含:
attempted
processed successful
failed
retried transactions
total retries
late
skipped
client aborted
server disconnect若只用:
$$ \text{TPS}
\frac{\text{successful}}{\text{elapsed}} $$
系统可以通过拒绝慢请求让成功样本看起来更快。failed、skipped 和 admission rejection 必须与 latency 并列。
参考实验固定 --max-tries=1,不让 pgbench 把 serialization/deadlock retry
隐藏到成功事务里;250 ms 只计 late。正式运行:
failed 0
skipped 0
late 0
deadlock 0这只适用于 511,709 个合成事务。零次观察不是“真实概率为零”的证明。
错误预算不是容量余量
SLO 允许 0.1% bad event,不代表正常运行可以把资源推到 99.9%:
resource headroom
absorbs burst, forecast error, maintenance and failure
error budget
governs reliability trade-offs and release policy若平时已经在 knee 右侧,任何轻微 burst 都会使 queue 与 tail 非线性上升,错误 预算会被快速消耗。
拆分安全余量
不要只写“预留 30%”。说明它覆盖什么:
| headroom | 覆盖 |
|---|---|
| statistical | workload 与测量波动 |
| forecast | 增长和 mix 误差 |
| burst | 短时 arrival |
| maintenance | vacuum、backup、DDL、reindex |
| failure | 节点/副本/路径损失 |
| operational | 扩容、验证、回滚提前期 |
这些余量不能总是简单相加,也不能互相冒充。failure headroom 可能要求一整台 node,而不是 10% CPU。
target utilization 是政策,不是自然常数
参考模型使用 65% CPU 作教学投影点:
$$ \lambda_{65} \approx \frac{0.65}{D_{cpu}} $$
得到 S/M/L 约 2,237 / 2,223 / 2,117 TPS。但这是假设:
- mix 不变;
- CPU demand 近似线性;
- cache、I/O、lock、client 和 background 不先成为瓶颈;
- 仍是同一个 sandbox。
所以公共结果将字段命名为 tps_at_65_percent_cpu_if_linear,并把
production_sustainable_tps 保持为 null。
生产 target utilization 应由:
failure model
burst duration
autoscaling/provisioning time
workload convexity
cost objective
operational experience共同决定,而不是复制 65%。
本节验收:一页容量问题陈述
在开始压测前,评审以下问题:
- operation catalog 有稳定名称与 owner;
- request/transaction/statement 的换算明确;
- arrival、peak、burst、seasonality 和 retry 已量化;
- data size、growth、retention 与 access skew 已量化;
- batch、maintenance 和 failure overlap 已列场景;
- latency measurement boundary 与 percentile 明确;
- error、late、skip、retry 和 correctness 都在验收条件;
- headroom 分解,而不是一个来历不明的百分比;
- production gate 的未知项仍然可见。
如果这页写不出来,更多 pgbench client 只会更快地产生无意义数字。
返回本章目录 · 下一节:设计代表性工作负载 · 查看全书目录 · 查看索引中心