跳至内容
26.1 从需求建立容量模型

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/sdatabase TPS
在途量$L$300 个尚未完成的 requestconnection count
响应时间$W$p95 120 msSQL execution time
database transaction rate$X$2,400 commit/srequest rate
statement rate$Q$18,000 statement/stransaction 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:

operationrequest/sDB tx/requeststatements/txdata shapeconsistency
read-product4,0001.01–2Zipf point readcurrent primary
read-order1,5001.01customer recent-N5 s freshness
place-order8001.04–8hot inventory + appenddurable commit
reconcile201.0range + aggregatetime windowcorrectness control
expire-order1001.0scan + updatebatcheventual

同为“读”:

indexed point lookup
index-only recent-N
wide range scan
hash aggregate spilling to temp
JSON path evaluation
vector nearest-neighbor

CPU、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 COPY

WAL、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 clientpool queue
server connection idlebackend memory,少量管理成本
active on CPUCPU
active waiting lock/I/Oqueue + held resources
idle in transactionsnapshot/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:

scalehistory rowsschema bytes
S100,00029,704,192
M800,000235,175,936
L3,200,000942,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、admissionscrape 太慢完全看不见
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:

jobscheduledurationread/writetempWALlockretry
settlement00:0518 minheavy read/updatepossiblehighrowyes
expire-orderevery 5 min40 sscan/updatelowmediumrowyes
analytics extract01:0045 minrange readspill risklowAccessSharerestart
backup02:0060 minstorage/networkn/aarchive couplingn/aresume

“平均业务低谷”不代表有余量。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覆盖
statisticalworkload 与测量波动
forecast增长和 mix 误差
burst短时 arrival
maintenancevacuum、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 只会更快地产生无意义数字。


返回本章目录 · 下一节:设计代表性工作负载 · 查看全书目录 · 查看索引中心

最后更新于