跳至内容
26.5 从测量推导容量与成本

26.5 从测量推导容量与成本

一次压测给出的 TPS 不是容量答案。它只是某个 workload、数据规模、配置和时间窗口下 的一组观测。容量规划要把观测转换为三个可以行动的模型:

resource model
  one business operation consumes how much CPU / IO / WAL / storage

growth model
  data, WAL, backup and maintenance workspace grow how fast

decision model
  at what threshold, considering lead time and failure, should we change capacity

转换过程中最危险的捷径是:

benchmark maximum TPS × server count = future capacity

它忽略了 workload mix、尾延迟、后台工作、故障冗余、增长、复制、备份以及扩容 提前期。可信的容量模型必须保留条件、区间和未知项。

26.5.1 单位业务量的资源消耗

先定义业务单位

“每事务成本”只有在 transaction 的业务语义稳定时才有意义。第 26 章实验的 transaction mix 是:

read-product  50%
read-order    30%
place-order   20%

其中一次 place-order 会写入订单、订单行并扣减库存;一次 read operation 则主要 读取。若把三者统称为“请求”,mixed average 会随权重变化:

$$ D_{\text{mix}}

\sum_{i=1}^{n} w_iD_i $$

其中:

  • $w_i$ 是第 $i$ 类 operation 的比例;
  • $D_i$ 是该 operation 的资源需求;
  • $\sum w_i=1$。

今天写比例 20%,明天促销时写比例 45%,即使每一种 operation 的实现都没变, mixed resource/transaction 也会变化。因此模型应优先保存:

CPU seconds / read-product
CPU seconds / read-order
CPU seconds / place-order
WAL bytes / place-order
durable data bytes / place-order

而不是只保存一个 blended TPS。

CPU service demand

假设测量窗口内:

N_cpu       logical CPU count
U_cpu       non-idle CPU fraction
X           completed transactions / second

粗略的 mixed CPU demand:

$$ D_{\text{cpu,mix}} \approx \frac{N_{\text{cpu}}U_{\text{cpu}}}{X} \quad \text{CPU-seconds / transaction} $$

例如单核 server 上,L-c8 的 server work ratio 约 0.842、吞吐中位数约 2,774 TPS:

$$ D_{\text{cpu,mix}} \approx \frac{1\times0.842}{2774} \approx 0.000304 \text{ CPU-s/tx} $$

即约 0.304 CPU-ms/tx。这个值只能用于同一 workload mix 的粗略推演。它包含 shared overhead,未把 checkpointer、WAL writer、autovacuum 等后台成本可靠地 分摊给 operation,也没有证明 CPU 是唯一限制。

要得到 per-operation demand,应设计独立或正交实验。设三种 operation 的未知 CPU demand 为 $D_1,D_2,D_3$,运行至少三组不同且可识别的 mix,得到:

$$ \begin{bmatrix} w_{11} & w_{12} & w_{13} \ w_{21} & w_{22} & w_{23} \ w_{31} & w_{32} & w_{33} \end{bmatrix} \begin{bmatrix} D_1\D_2\D_3 \end{bmatrix}

\begin{bmatrix} D_{\text{mix},1}\D_{\text{mix},2}\D_{\text{mix},3} \end{bmatrix} $$

更简单的做法是分别运行 read-only、write-only 与代表性 mixed workload,然后用 mixed run 验证模型,而不是直接拿三次结果线性拼接。

从 service demand 估算目标 CPU

若 forecast 中每类业务到达率为 $\lambda_i$:

$$ C_{\text{cpu required}}

\frac{\sum_i \lambda_iD_{\text{cpu},i}} {U_{\text{target}}} $$

目标利用率 $U_{\text{target}}$ 不是物理极限。它还要容纳:

forecast error
traffic burst
autovacuum / checkpoint / backup
failover and degraded topology
software regression
host and storage variance

参考 run 为便于教学,计算了一个 sandbox-only、线性、65% CPU 投影:

scale65% CPU 条件投影 TPS
S2,236.79
M2,222.96
L2,116.98

这只是:

$$ X_{65%} \approx X_{\text{c8}} \times \frac{0.65}{U_{\text{c8}}} $$

它不代表 production sustainable TPS,因为:

  • c8 已高于 65%,这是回算而非直接在 65% 稳态测量;
  • 两个并发点没有找到 knee;
  • workload 是 closed-loop、warm-cache、短窗口;
  • 后台维护、故障、恢复、备份与真实网络未被充分施压;
  • 只有一个虚拟化小节点。

公共结果把 production sustainable TPS 保持为 null,这比填一个看似精确的数字 更诚实。

WAL 与 durable growth 必须按写操作归一

参考实验给出了:

cellmixed WAL bytes/txdurable bytes/place-order
S-c1160.33415.21
S-c8159.53409.12
M-c1175.95412.81
M-c8161.88405.97
L-c1157.56411.50
L-c8196.18407.88

WAL bytes/tx 的分母包含 80% read transaction,因此不能直接说“一笔订单产生 约 160–196 字节 WAL”。若读操作完全不写,按 20% place-order 做第一阶换算, 写操作的 WAL 可约为 mixed value 的五倍;但真实系统还有:

  • hint bit 与 full-page image;
  • checkpoint 后首次页修改;
  • index 数量与 page split;
  • autovacuum、freeze 与 catalog activity;
  • logical decoding;
  • sequence 与 extension 行为。

正确做法是用 pg_stat_wal.wal_bytes 的窗口 delta,除以该窗口实际成功的 write operation 数:

$$ \text{WAL bytes/write op}

\frac{\Delta\text{wal_bytes}} {\text{successful write operations}} $$

同理:

$$ \text{durable growth/write op}

\frac{\Delta\text{relation bytes after stabilization}} {\text{successful write operations}} $$

这里的 “after stabilization” 很重要。heap 与 index 的 allocated size 不是每 写一行都线性增加;页内 free space 会先被消耗,扩页是离散事件。短窗口的 relation size delta / order 容易受页边界影响。应在足够长窗口、多个数据规模 和 vacuum 周期上重复测量。

PostgreSQL 原生统计可以分别回答:

SELECT wal_bytes, wal_records, wal_fpi
FROM pg_stat_wal;

SELECT
    datname,
    xact_commit,
    xact_rollback,
    blks_read,
    blks_hit,
    temp_bytes,
    deadlocks
FROM pg_stat_database
WHERE datname = current_database();

统计视图是累计计数器。必须保存 start/end snapshot、server restart/reset 时间, 用 delta 计算;不能把 dashboard 当前值当作本次 run 的成本。

I/O 成本不能用 cache hit ratio 代替

一个 operation 的 I/O 模型至少分:

logical buffer access
physical data read
data write / writeback
WAL write / sync
temp read/write
backup/archive/network bytes

参考 run 的 S、M cell 没有记录到 PostgreSQL block read,L-c1/L-c8 分别出现 2,500/14,491 次 block read。这只说明 L 数据规模在该 warm-cache 窗口触碰了更多 未驻留 block;它不证明小规模生产 workload “不需要磁盘”。

PostgreSQL 18 的 pg_stat_io 可按 backend type、object 与 context 提供 read/write/extend/fsync 等累计信息。 它仍需和 OS/Pigsty 的 device latency、queue、throughput 对齐,因为:

  • PostgreSQL read 可能由 OS page cache 满足;
  • write() 完成不等于设备持久化完成;
  • shared storage 与 hypervisor 可隐藏或放大延迟;
  • data I/O、WAL I/O 和 backup I/O 可能落在不同设备。

latency 是容量成本的一部分

若只追求最低 unit CPU cost,常会把 server 推到更高 batch/concurrency;throughput 可能提高,但 queueing 令 p95/p99 恶化。容量成本函数应含 SLO penalty:

$$ \text{effective cost}

\text{infrastructure cost} + \text{failure risk} + \text{latency penalty} + \text{operational complexity} $$

因此一份 unit economics 表至少包含:

指标分母条件
CPU-soperation typemix、并发、数据规模
logical/physical I/Ooperation typecache state、plan
WAL bytessuccessful write opcheckpoint/FPI 状态
durable bytesbusiness entityschema/index/vacuum 周期
network bytesAPI or transactionTLS、pool、result set
p50/p95/p99completed opoffered/achieved load
failure/latescheduled optimeout/retry policy

只给 $ / TPS 而没有这些条件,不是成本模型,只是报价除法。

26.5.2 增长、保留、备份与维护空间

数据库大小不是一条线

磁盘容量要同时容纳:

live heap
indexes
TOAST
free space and bloat
temporary files
WAL working set
archive staging / backlog
base backups and incrementals
restore and verification workspace
maintenance rewrite workspace
logs, packages and operating system
failure/rebuild headroom

先用原生 size function 建立对象层 inventory:

SELECT
    pg_size_pretty(pg_database_size(current_database())) AS database_size;

SELECT
    n.nspname,
    c.relname,
    pg_relation_size(c.oid) AS main_fork_bytes,
    pg_indexes_size(c.oid) AS index_bytes,
    pg_total_relation_size(c.oid) AS total_bytes
FROM pg_class AS c
JOIN pg_namespace AS n ON n.oid = c.relnamespace
WHERE c.relkind IN ('r', 'm')
  AND n.nspname NOT IN ('pg_catalog', 'information_schema')
ORDER BY pg_total_relation_size(c.oid) DESC;

这些函数的语义与可选 fork 见官方 Database Object Size Functions

注意:

pg_relation_size(table)
  main fork only by default

pg_table_size(table)
  table forks + TOAST, excluding indexes

pg_total_relation_size(table)
  table + TOAST + indexes

pg_database_size(database)
  database total

不要把其中两项相加造成重复计数。

从业务增长率推导 live data

对业务实体 $i$:

$$ G_{\text{live/day}}

\sum_i N_{i,\text{day}} \times B_{i,\text{durable}}

G_{\text{purged/day}} $$

若保留 $R$ 天,且每日量以 $g$ 增长:

$$ N_R

N_0 \sum_{d=0}^{R-1}(1+g)^d $$

不要先把“当前日增量 × 保留天数”写死,再在运营增长时惊讶。还要单独模拟:

  • 热数据、温数据、冷数据保留;
  • update/delete churn;
  • 索引增长;
  • partition attach/detach;
  • 法规保留与 legal hold;
  • tenant/region/skew;
  • schema 变更增加的列和索引。

容量表应按周或月保存 actual vs forecast:

date
live rows
heap bytes
index bytes
TOAST bytes
dead tuples
WAL bytes/day
backup bytes
archive backlog
forecast P50/P90

forecast 不是只更新未来;它也要用实际误差校准。

bloat 与 free space 不是同义词

PostgreSQL MVCC update/delete 会产生 dead tuple。普通 VACUUM 通常把空间留在 relation 内供复用,并不把大部分文件空间还给文件系统。于是:

relation allocated bytes
  != live tuple bytes
  != irrecoverable waste

容量规划需要同时问:

  • 这部分 free space 能否被相同 relation 的后续写入复用?
  • workload 的 update pattern 会不会跨页,使复用率低?
  • index 是否发生结构性膨胀?
  • autovacuum 能否在增长速度下追上?
  • freeze horizon 和 long transaction 是否阻止清理?
  • 真正回收文件系统空间需要哪种 rewrite,以及多大 workspace?

不要看到 30% “bloat estimate” 就自动采购 30% 磁盘,也不要因为 vacuum 后文件没缩小 就认定 vacuum 无效。

WAL 是速率、窗口与失效状态的乘积

WAL 容量不是固定 “max_wal_size”。要考虑:

steady generation rate
checkpoint behavior
archive throughput and outage
replication slots
slow/offline replicas
backup and restore windows
wal_keep_size / sender retention
logical decoding
promotion and timeline history

若 WAL 生成率为 $W$ bytes/s,允许 archive 中断 $T_a$ 秒,安全因子为 $h$:

$$ S_{\text{archive backlog}} \ge W \times T_a \times h $$

若 replication slot 的 consumer 可离线 $T_s$:

$$ S_{\text{slot retention}} \ge W \times T_s \times h $$

但 slot retention 可能无界,除非使用并验证 max_slot_wal_keep_size 等保护。 PostgreSQL 的 WAL configurationlog-shipping standby 说明了 checkpoint、归档、streaming 与 retention 之间的关系。

容量规划至少演练:

archive destination unavailable
replica offline
logical subscriber stalled
backup exceeds expected duration
write burst crosses checkpoint

正常状态的 WAL 目录大小不能代表这些失效状态。

备份容量用 retention graph,而不是压缩率愿望

备份 inventory 应包括:

full/base backup
incremental/differential data
WAL needed for PITR
retention generations
off-site copies
temporary upload/download
restore verification copy
metadata/catalog
encryption/compression overhead

备份压缩率随 data type、page utilization、encryption 与 compression method 变化。 应直接测量:

$$ \text{compression ratio}

\frac{\text{stored backup bytes}} {\text{logical source bytes}} $$

并分别保存 full 与 WAL 的 ratio。生产采购不能假设日志、JSON、already-compressed payload 与 encrypted data 都能获得同一压缩率。

retention 示例:

7 daily + 4 weekly + 12 monthly

不能简单乘以 23,因为增量链可能共享 block,full schedule 也不同;应从备份系统 catalog 汇总真实 object graph,并验证删除一代不会破坏仍保留的恢复链。

维护和迁移需要临时的第二份数据

以下动作可能需要额外 workspace:

动作可能的额外空间
CREATE INDEX / REINDEX新 index + sort/temp + WAL
table rewrite新 heap + indexes + WAL
VACUUM FULL / CLUSTERrelation rewrite + indexes + WAL
major upgradein-place/link/copy 策略不同
restore rehearsal一份可启动的数据 + WAL + logs
replica rebuildbase backup staging + receive WAL
logical migrationsource 与 target 重叠期

ALTER TABLE 并不总是 rewrite,pg_upgrade --link 也不等于没有风险;模型要按实际 变更计划估算,不应用一个永久的 “2x” 代替 runbook。

一个实用的 local filesystem 约束是:

$$ S_{\text{free}} \ge \max( S_{\text{routine headroom}}, S_{\text{largest planned maintenance}}, S_{\text{failure backlog}} ) $$

若多个动作可重叠,应改为相应的和,而不是最大值。

用 time-to-limit 驱动行动

设当前使用量 $S_0$、目标上限 $S_{\text{limit}}$、净增长速率 $G$:

$$ T_{\text{limit}}

\frac{S_{\text{limit}}-S_0}{G} $$

但 $G$ 不应只有一条平均线。至少计算:

P50 trend
P90/P95 growth or burst scenario
retention policy change
failure backlog scenario
largest scheduled maintenance

如果增长非线性,就用时间序列/业务 forecast,不能继续用直线外推。

26.5.3 扩容触发线、提前期与失效假设

trigger 必须早于 capacity limit

一次扩容从发现到获得容量通常经历:

signal sustained
  -> diagnosis and forecast review
  -> decision / budget / approval
  -> procurement or quota
  -> provision
  -> data copy / rebalance / catch-up
  -> validation
  -> traffic shift
  -> rollback observation window

总提前期:

$$ T_{\text{lead}}

\sum_j T_j $$

若保守预测到达上限的时间为 $T_{\text{limit,P90}}$,行动条件应是:

$$ T_{\text{limit,P90}} \le T_{\text{lead}} + T_{\text{validation}} + T_{\text{safety}} $$

“磁盘 90% 再扩”不是通用策略。一个需要四周采购、两周复制的集群,90% 可能已经 太晚;一个可在数分钟无状态扩出的 read pool,则可使用不同 trigger。

同时定义 utilization、SLO 与 growth trigger

容量触发器不应只看 CPU:

类别示例信号触发语义
demandrequest/write rate forecast业务增长越过已验证 envelope
SLOp95/p99、timeout、pool wait在当前 load 下目标开始失守
CPUbusy/run queue/service demand持续值与 burst/failure headroom
memoryworking set、temp、swap/PSIreclaim 或 spill 开始恶化
storagelatency/queue/throughput设备服务时间接近边界
spacetime-to-limit小于 lead time + safety
WALgeneration/archive/slot backlogconsumer 无法在允许窗口追上
maintenanceautovacuum/checkpoint/backup duration后台工作超过可用窗口
HAN+1/degraded capacity故障后 SLO 无法维持

trigger 应具有:

threshold
duration
forecast horizon
scope
owner
runbook
rollback / stop condition

瞬时 CPU 65% 不应自动购买主机;持续两周的需求趋势、在 N+1 状态下预测将超过已验证 SLO envelope,才是可行动的 signal。

以失效状态写容量预算

先定义状态,而不是笼统说 “预留 30%”:

N0  normal: all primary/replica/pool nodes healthy
N1  one data node unavailable or rebuilding
M1  backup + autovacuum + checkpoint overlap
F1  archive destination unavailable for allowed window
F2  one replica/slot consumer stalled for allowed window

每个状态分别计算:

available CPU / storage / IOPS / connection slots
traffic redistribution
WAL accumulation
replication/rebuild load
latency and error SLO
maximum tolerable duration

例如三台 read replica 各承载 30% 峰值,不能据此宣称 N+1;失去一台后其余两台会 各承载 45%,还要叠加 cache coldness、reconnect 与 recovery traffic。N+1 必须在 failure drill 中测。

扩容不等于只加 CPU

观测到瓶颈后,选择与根因匹配的动作:

限制候选动作必须验证
CPU executionvertical scale、query/index、JIT/parallel policyplan、tail、failure
read throughputreplica/read cache、query optimizationfreshness、一致性、routing
write/WALbatch/schema/index、faster WAL devicedurability、replica/archive
lock/contentionshorten tx、partition key space、admissioncorrectness、公平性
connectionPgBouncer、pool sizing、admissionsession semantics、queue SLO
storage latencystorage class/layout、reduce random I/Ofsync tail、failure behavior
data volumeretention/partition/archive/shardrestore、query、operability

max_connections 变大通常不是 capacity expansion。它可能把受控 queue 从 pool 移入 database,并增加 backend memory、context switch 和 lock competition。

Pigsty 把 topology、HAProxy/PgBouncer、PostgreSQL、监控和运维入口组织在一起, 方便执行变更;它不会替代业务 workload 与 SLO 的容量判断。扩容后仍要在实际 connection path 上重跑 baseline,并用 Pigsty PGSQL dashboards 与 PostgreSQL 原生统计双重验证。

把容量结论写成有期限的决策记录

一条可审计的容量结论应包含:

decision: "当前 topology 是否足以覆盖下一预测窗口"
environment: "硬件、版本、配置、connection path"
workload: "operation catalog、mix、arrival model、data scale"
slo: "latency/error/freshness/durability"
evidence: "run ids、raw artifacts、dashboards、queries"
validated_envelope: "哪些点通过,哪些点失败"
failure_state: "N0/N1/M1/F1/F2 中验证了哪些"
forecast: "P50/P90 demand and growth"
trigger: "threshold + duration + horizon"
lead_time: "provision + copy + validation + shift"
unknowns: "尚未测量的条件"
expires_at: "何时必须复审"
owner: "谁监控、谁决策、谁执行"

任何一个重大条件变化都使 baseline 进入复审:

PostgreSQL/Pigsty or kernel/storage upgrade
schema/index/query change
workload mix or SLO change
data crosses tested scale
topology/routing/pool change
hardware or cloud instance change
backup/replication/durability policy change
material performance incident

容量模型的价值不是预测一个永远正确的 TPS,而是让团队在还有选择的时候,看见:

what is known
under which conditions
how fast the boundary is approaching
how long the response takes
which failure would invalidate the plan

上一节:找到饱和点与瓶颈 · 返回本章目录 · 下一节:实战:pg36_shop 容量基线 · 查看全书目录 · 查看索引中心

最后更新于