系统设计 02:容量估算——QPS、存储、带宽、并发与成本
容量估算不是猜一台服务器能承受多少 QPS。先把业务动作拆成次数、字节、持续时间和保留期,再看哪一项随假设变化最快。算完只能得到待测的负载,不会凭空得到组件容量。
以图片分享的元数据、原图和预览图为教学场景。本篇重点是估算如何改变“上传流量是否穿过应用”这一设计决定,不讨论完整图片社交产品。此前的需求澄清讨论的是分享撤销契约;本篇使用独立的图片负载,不能把两个场景的指标拼成真实业务画像。
先把单位写在每个假设旁边
以下均为教学假设:5 万上传者每天各上传 4 张,得 20 万张/日;每张图片在 90 天保留期内累计产生平均 20 次预览读取,每次读取 256 KiB。按上传速率和各年龄图片的读取分布稳定、系统已进入稳态估算,日均预览读取量为 200000 × 20 = 4000000,读写比 20:1;这不是每张存量图片每天读取 20 次,后者在保留 90 天的稳态下将产生 200000 × 90 × 20 = 360000000 次读取/日。全天峰值按日均速率的 8 倍计算。每张原图 4 MiB,另外生成 2 张各 256 KiB 的预览;原图及预览保留 90 天、两份。每张元数据 800 B、索引 200 B,保留 90 天、三份;写入日志 200 B/条,保留 7 天、一份。
产品约束是:用户收到“上传成功”时,后续读到的预览必须是完整版本,不返回未完成上传。预览读取的服务端 p99 目标暂定 500 ms,上传元数据初始化 p99 目标 300 ms;文件传输完整耗时需要独立目标,不能混入初始化延迟。这里只定衡量对象,没有做性能实测。排除视频转码、AI 审核和原图跨境分发;对象校验、失败重试和清理半成品只讨论与容量相关的确认边界。
从次数推到速率,再推到字节
上传均值 200000 / 86400 = 2.31 upload/s,峰值按 8 倍为 18.52 upload/s;预览均值 4000000 / 86400 = 46.30 read/s,峰值为 370.37 read/s。原图入口在持续峰值下的有效载荷约 18.52 × 4 = 74.07 MiB/s,换成十进制网络速率为 74.07 × 2²⁰ × 8 / 10⁶ ≈ 621.38 Mbit/s。预览出口约 370.37 × 256 KiB = 92.59 MiB/s ≈ 776.72 Mbit/s。这只是有效载荷,未计 TLS、重试、客户端慢连接、跨区域回源和缓存命中;MiB/s 不能直接写成 Mbit/s。
90 天的对象容量包括原图和两张预览:
1 | |
元数据与索引 200000 × (800+200) B × 90 × 3 = 54 GB,日志 200000 × 200 B × 7 = 0.28 GB,合计 54.28 GB,约 50.55 GiB。索引与日志保留期不同,不能把它们偷换成“同一条记录复制三次”。若日志实际按每次预览访问而非每次上传产生,需把它的事件率从 20 万/日改成 400 万/日,再重新计算;本式只计每次上传一条日志。
并发也不等同 QPS。假设预览峰值持续到系统稳定,且实际测得平均请求占用时间是 0.2 秒,平均在途约 370.37/s × 0.2s = 74.07 个。这用的是 Little 的排队关系 L = λW 的平均量前提,不能用 p99 500 ms 代替平均时间去给线程池报一个“p99 并发数”(Little, 1961, A Proof for the Queuing Formula: L = λW)。目前没有实际测得的 0.2 秒,也没有到达分布和慢客户端信息,所以 74 只是条件练习,不是容量规划结果。
让一项假设变动,找先变的瓶颈
只把原图平均大小改成 16 MiB,上传次数、读次数和预览 256 KiB 大小不变。原图入口立刻增至 296.30 MiB/s ≈ 2.49 Gbit/s,90 天两份对象容量增至 622.85 TB ≈ 566.48 TiB;预览出口依旧 92.59 MiB/s,元数据与七天日志依旧 54.28 GB。此时讨论“把 API 实例从两台扩到四台”之前,应先看文件字节是否经过 API:如果经过,它的入站带宽、长连接与临时磁盘先变;如果通过受控直传进入对象层,应用只处理授权与元数据,文件传输瓶颈转到对象层与客户端链路。
这不是宣称对象服务必然无限扩容。以 S3 为一个可选实现例子,AWS 文档允许用预签名 URL 授权上传,并指出相同对象键的再次上传会替换既有对象(Amazon S3: Uploading objects with presigned URLs)。若采用类似机制,给每次上传分配独立对象键,限制目标路径和有效期,完成后验证预期对象版本再把分享状态改为 ready;这些是设计要求,本文没有运行 S3 测试,也没有验证任何存储服务的一致性或原子可见性。
成本也要留在公式里。若练习假设存储价格为 0.02 货币单位/(GB·月),在稳定保留 169869.31 GB 时,仅存储两份对象的账面预算约 3397.39 货币单位/月。这不是任何云厂商的报价,也不含请求、出口、跨区域复制、编码、不同存储层及清理滞后。单凭一个对象存储单价,不足以选定图片分发方案。
用确认路径约束扩容方案
最小方案可以由 HTTP 元数据服务和持有原图与预览的对象层组成。POST /uploads 返回上传会话及限定目标;完成后 POST /uploads/{id}/complete 检查对象存在、大小或摘要及归属,再将 uploads(id, owner_id, object_key, state, version, created_at) 从 pending 改为 ready。GET /images/{id}/preview 仅对 ready 记录发放可读取地址。按 owner_id, created_at, id 列举时才需要相应索引;不为尚无读取模式的字段创建索引。这里的“存在、校验和、原子更新”是待用真实组件检验的契约,Python 容量算术无法证明它们。
flowchart LR
C[客户端] -->|POST 创建 pending 会话| A[元数据服务]
A -->|写会话与对象键| M[(元数据存储)]
A -->|返回限定上传目标| C
C -->|上传原图字节| O[(对象层)]
C -->|POST complete 携带摘要| A
A -->|核对对象版本/摘要| O
A -->|确认后更新 ready| M
C -->|GET ready 预览| A
A -->|校验状态后发放读取位置| C
C -->|读取预览字节| O
在上传前先回“会话已创建”,不等同“图片已发布”。只有完成校验、状态已更新之后才确认发布;缩略图制作如果是异步处理,则发布状态还需区分“原图可用”“预览未就绪”,不能把工作项入队当作预览完成。初版可同步生成小预览,吞吐或尾延迟达到阈值后再引入异步处理,并记录任务幂等和积压上界。
sequenceDiagram
participant C as 客户端
participant A as 元数据服务
participant O as 对象层
participant M as 元数据存储
C->>A: 创建上传会话
A->>M: 保存 pending 与独立对象键
M-->>A: 提交
A-->>C: 会话成功;图片尚未发布
C->>O: 上传原图(可能中断)
Note over C,O: 上传中断:不调用 complete,读路径继续拒绝
C->>A: complete 和摘要
A->>O: 验证对象版本/摘要(可能超时)
Note over A,M: 校验失败或超时:不标 ready,可安全重试核对
O-->>A: 匹配的对象
A->>M: 更新 ready,记录已验证版本
M-->>A: 提交
A-->>C: 发布确认
如果发现 pending 记录持续增长,应先查上传失败率与清理时限,再增加上传入口带宽;如果是预览出口或热点回源,先量命中、回源、预览读取的 p95/p99,再讨论 CDN 或更小尺寸的预览。不能用平均延迟宣称 p99 达标,也不能因上传会话成功就跳过失败后的孤儿对象回收。
固定输入的算术验证
仓库中的 examples/system-design/labs/02/capacity.py 用 Python 3.12.3 标准库 Decimal 核算上述数据;examples/system-design/evidence/02/capacity.md 保留环境、输入、命令、退出码和原始输出。默认 4 MiB 得 74.07 MiB/s 入站及 154.50 TiB 图片;只改为 16 MiB 得 296.30 MiB/s 入站及 566.48 TiB 图片,预览出口和元数据不变;输入 0 MiB 返回退出码 2。没有压测、云账单或真实组件实验,结果只支撑单位和敏感性推导。
面试追问可用“原图翻四倍后哪里先受影响”“为什么 74 个在途不是 p99 线程池大小”“日志若每次读取都产生要改哪一项”。练习可按需求 5 分钟、容量与接口 8 分钟、架构 12 分钟、难点 15 分钟、复核 5 分钟安排,这不是面试行业规范。迁移规则:把次数字节和保留期分别算清,先替换最敏感的假设,再把扩容动作对准被它改变的实际路径。
参考资料
- John D. C. Little, A Proof for the Queuing Formula: L = λW, Operations Research, 1961。仅用于平均在途数量的稳定条件推导。
- Amazon S3, Uploading objects with presigned URLs。仅用于说明一种可选授权上传能力及重复对象键覆盖的边界,不代表本文组件实测。





