ChatGPT 存储扩容:读取提速十二倍
OpenAI 介绍 Habitat 存储系统的扩容实践,通过多租户隔离、全球缓存和 Azure Cosmos DB 支撑超十亿用户规模,并实现读取速度提升至原来的十二倍。
AI 深度解读
支撑超过 10 亿用户、覆盖 40 个地理区域之后,数据库的问题不再只是「容量够不够」。共享资源会相互干扰,拆开资源又会把容量规划压给每个产品团队;数据放得下,也未必离用户足够近。
OpenAI 这篇 Habitat 系列第二篇,讲的是他们如何调整 Azure Cosmos DB 的隔离、扩容和缓存机制。最有价值的结论是:存储平台的职责,不只是接住请求,还要减少产品团队为数据库操心的事情。
为什么拆成独立容器,最后又改回共享
Habitat 已从 Python 客户端演变为全球存储平台,承载超过 500 PB 数据。本篇聚焦它底下的 Azure Cosmos DB,经历了一次看似绕路的调整:共享容器、独立容器,再回到默认共享。
先分清三个概念。Azure Cosmos DB 的 account 管理容器和复制策略;container 按 RU/s 配置吞吐量,RU 是数据库操作消耗资源的计量单位;数据先按逻辑分区键归组,再由数据库分配到物理分区。
最初,他们每个 account 只用一个默认容器,依赖每个逻辑分区键 10K RU/s 的限制提供背压,也就是让数据库限制超额请求。但客户端可以直接选择分区键,不同产品使用相同的 user_id、workspace_id 时,可能挤进同一个逻辑分区,共用额度。
更深一层的问题在物理分区。多个逻辑分区共用物理分区的吞吐量,一个热点键就可能挤占其他键的资源。作者报告,边缘功能的负载曾影响核心登录和注册流程。
容量还会随着数据增长「变弱」:容器总 RU/s 大致均分给物理分区,数据涨到需要拆分时,如果总吞吐量不变,每个分区分到的额度就会下降。原文举例是 10K RU/s 拆成两个各 5K RU/s 的分区。即使请求量没涨,只增加存储,也可能让原有请求开始被限流。
于是,他们把关键业务拆到独立容器,再用脚本根据物理分区数量调整总吞吐量。这缩小了故障范围,却引出了另一种问题:每个团队都得为上线准备容量。
作者描述过新业务从 0 QPS 涨到 100 万 QPS,相比内部试用基线达到 1,000 倍负载的情况。另一个容量示意更直观:测试需要 1K RU/s,上线需要 1M RU/s;放在容量 100M RU/s、已使用 80% 的共享容器里,分别只占剩余额度的 0.005% 和 5%;放进只有 10K RU/s 的独立容器,上线需求就是容量的 10,000%。
共享池能消化的变化,独立容器可能扛不住。团队忘记预扩容,基础设施人员就得在上线当天处理事故。隔离改善了,组织协作成本却持续上升。
共享能不能稳定,取决于怎样争抢资源
他们最终回到默认共享,前提是与 Azure 一起补上了资源分配机制。
首先,物理分区承压时优先限制最热的键,避免单个热点吞掉整块资源。其次,引入服务质量分级(QoS),把批量写入、数据导出等离线请求设为低优先级,资源不足时先限制它们,保护面向用户的交互请求。
第三项改动针对扩容。原有 serverless 模式能解决预配置吞吐量下的弱分区问题,但只根据存储量扩展,每个物理分区最多 5K RU/s,不适合 RU 消耗高的业务。Azure 为他们构建的改进模式,把单分区上限提高到 10K RU/s,并增加按 RU 使用量触发分裂的能力。
共享容器能成立,靠的是热点控制、请求优先级和随负载扩展共同工作。
作者称,调整后数月内没有发生容量配置或吵闹邻居事故。但这不是一组有对照的实验,也不能据此推断任意共享数据库都能获得相同结果。
在这个规模上,「托管服务」也需要双方共同运营。他们最大的 Azure Cosmos DB account 存储数十 PB 数据,处理超过 20M QPS。
2024 年末,一次主区域断电暴露出,大型 account 切换到备用区域需要数小时。约一个月后,切回原区域又发生连接风暴:gateway-mode 客户端立即向原区域建立连接并发送请求,虽然当时只有部分物理分区迁回,网关仍被压垮。
此后双方共同维护监控,在生产规模的非生产 account 上测试,并联合迭代 SDK、控制平面和网关。他们还通过连接池、连接复用、元数据缓存与负载保护减轻压力,用不同 VPC 隔离关键业务和离线业务。
这些做法解决的是普通压测未必覆盖的问题:正常请求能跑通,不代表整区切换和集体重连也能扛住。
全球读取从 120 ms 降到 10 ms,靠的不只是 Rust
原文把工作顺序概括为:先可靠,再快,最后优化成本。系统在 2025 年趋于稳定后,全球访问延迟成为新瓶颈;2026 年的重点是读取优化,因为读取占 Habitat 请求的 90% 以上。
他们没有把所有数据复制到所有地方,而是分层处理。
第一层是尽量让数据靠近用户。但数百 PB 级数据受机房、电力和冷却容量约束。有些区域必须在增加推理等服务计算容量、放置更多本地存储之间取舍,位置选择并非单纯的路由问题。
第二层是广泛复制少量关键数据。每次请求都需要的认证元数据,对延迟尤其敏感;部分 account 复制到全部 40 个服务区域,另一些至少覆盖每个大洲的一个区域。Habitat 为此提前建设了透明的数据迁移和路由层,让数据集能在不同复制拓扑的 account 之间移动。
第三层覆盖其余大量读取:在请求处理位置附近部署 Valkey,作为集成的旁路缓存,保存最近访问的对象和关系。
这里真正费功夫的是失效机制。短 TTL 能减少旧数据,却降低命中率;长 TTL 提高命中率,又可能让旧数据停留太久。应用写入时主动删缓存也有局限,部分失败、跨区域网络和存储复制的先后顺序,都可能留下旧值。
Habitat 借鉴 Meta TAO,通过 Azure Cosmos DB change feed 做变更数据捕获(CDC),在数秒内向全球传播缓存失效。消费者收到变更后删除对应缓存,下一次读取未命中,再从存储填入最新数据。
最后,他们用 Rust 重写原有 Python 服务,降低代理层开销。上述变化共同让全球读取 p50 从约 120 ms 降到约 10 ms,改善 12 倍。原文没有拆分各项贡献,不能把全部收益归给 Rust。
我的判断
我认可的重点是把数据库复杂度收回平台。独立容器、应用缓存、批处理都能解决局部问题,但如果每个产品团队都得理解扩容、失效和跨区域访问,平台只是转交了复杂度。
不过,默认共享有很强的前提。OpenAI 得到了 Azure 定制的扩容能力,并能联合修改和测试底层系统。普通团队是否能获得同样的 serverless 行为,原文没有交代。已有明确隔离要求、负载又容易预测的业务,独立容器仍然可能更合适。
CDC 缓存也不是零成本替代 TTL。数秒内失效仍有旧数据窗口,文章没有给出失效积压、消费者故障或回填竞争的完整处理方案。对权限等敏感数据,必须先确定允许的陈旧时间。
这篇提供了可信的故障经过和优化结果,但没有成本对照、缓存命中率、写入延迟及 p95、p99 数据。它能证明这套组合改善了他们的系统,尚不足以证明它是普遍最优方案。
能拿来做什么
- 给 Node 或全栈服务设计分区键时,同时检查业务之间是否共享键空间,以及热点键所在物理资源的限制;不要把单键额度当成独享吞吐量。
- 把批量导入、数据导出与在线接口区分优先级,并验证资源不足时谁先被限流,避免后台任务拖慢登录等关键请求。
- 为 Valkey 缓存先定义可接受的旧数据窗口,再选择 TTL、主动失效或 CDC;采用 CDC 时,把变更消费延迟纳入监控。
- 做数据库容灾演练时,覆盖切换、切回和客户端集中重连,并观察网关及连接池,不能只验证备用区域能否接收请求。