容量估算不是猜一台服务器能承受多少 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
2
(4 MiB + 2 × 256 KiB) / 张 × 200000 张/日 × 90 日 × 2 份
= 169869312000000 B ≈ 169.87 TB ≈ 154.50 TiB

元数据与索引 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 分钟安排,这不是面试行业规范。迁移规则:把次数字节和保留期分别算清,先替换最敏感的假设,再把扩容动作对准被它改变的实际路径。

参考资料