上一篇《从 Habitat 看存储平台的整合与分工》,主要讨论 Habitat 如何从 Python SDK 演进成独立服务,以及这种位于业务和基础存储之间的平台,能接过多少重复建设。2026 年 10 月 23 日发布的 Part II 继续向下,展开 Azure Cosmos DB 的使用和运营。1
这次围绕原文的四个话题来看:多租户经历了哪些阶段,全球读取怎样优化,OpenAI 与 Azure 怎样共同运营,以及全球化以后 CDC 平台要接过什么工作。前两个话题展开一些,最后再接上我们自己的 RU 经验,以及 Habitat 在研存储的成本动机。
1. 大规模多租户的可靠性与三个演进阶段
1.1 Cosmos DB 的容器、RU 与分区
先明确几个对象。Account 管理一组容器及其区域复制配置;Container 是组织数据和配置吞吐的单位。RU(Request Unit)把数据库操作消耗的 CPU、I/O、内存等资源归一化,RU/s 表示单位时间的吞吐预算。文档大小、索引和操作类型都会影响 RU 消耗,所以同样的 QPS,资源需求未必相同。2
容器里的数据按逻辑分区键值归组,多个逻辑分区落到同一个物理分区。以这里讨论的普通分区键模型为例,一个逻辑分区完整落在一个物理分区上;物理分区管理实际的容量和吞吐资源,最高支持 10,000 RU/s。预置模式下,容器总吞吐默认均分到各物理分区。3
业务看到的是逻辑键,实际竞争却发生在物理分区。理解这层区别,后面的多租户问题就比较直接了。
1.2 第一阶段:依赖 Azure Cosmos DB 原语
Habitat 最初在每个 account 中使用一个默认共享容器,允许客户端流量直接进入,依靠 Cosmos DB 的分区限制提供背压。1 业务不必为新用例单独准备资源,大家共享容量;平台也尽量复用云数据库已有的能力。
这个阶段暴露的问题,分别来自逻辑键、物理资源和容量分配。
1.2.1 不同业务共享逻辑分区键
早期 API 将分区键选择暴露给客户端。如果多个业务都按用户或 workspace 组织数据,又采用相同的键值,它们会进入同一个逻辑分区,共享吞吐限制。一个用户在多个功能里的请求,由此被叠加到同一份预算上。1
例如两个业务都使用 user-123 作为分区键值,逻辑上不同的数据也可能竞争同一份额度。这里要检查键值的编码和命名空间;字段都叫 user_id,本身还不足以说明发生了碰撞。

图 1:OpenAI 原文 Figure 02A 的交互演示录制。1 不同客户端的请求汇入同一逻辑分区键,图中流量用于解释竞争关系。
1.2.2 物理分区内的 noisy neighbors
即使逻辑键不同,仍可能落在同一个物理分区,共享它的吞吐预算。某个热点键先耗掉大量资源,其他无关键就可能一起被限流。因此,逻辑分区的 10K RU/s 上限不能理解为每个键都独占 10K RU/s。3
这里有两个治理粒度:为逻辑键增加业务命名空间,可以减少无意的混用;限制热点对所在物理分区的影响,还需要资源调度和限流。只解决前一个问题,邻居仍然可能很吵。

图 2:OpenAI 原文 Figure 02B 的交互演示录制。1 分区键不同,实际使用的物理分区资源仍然可能相同。
1.2.3 数据增长引起的 weak partitions
物理分区数量还会随数据量增长而增加。如果总 RU/s 不变,分区越多,每个物理分区能分到的额度就越少。3
用一个简化算例看:假设容器当前只有一个物理分区,预算是 10K RU/s,一个热逻辑分区需要 6K RU/s。冷数据增长推动它拆成两个物理分区,总预算仍为 10K RU/s,每个子分区只有 5K RU/s。原来的热逻辑分区不会因此自动拆散,它仍然需要 6K,超过预算的部分就会被限流。

图 3:OpenAI 原文 Figure 02C。1 单分区预算从 10K 降到 5K RU/s,需求仍为 6K RU/s。图中数值是机制示意,200 与 429 分别表示成功和被限流的请求。
请求没有变多,历史数据长大也能引发前台限流。这个现象依赖访问分布:如果负载也随分裂均匀散开,未必出现问题;冷数据推动分裂、热键仍集中时,存储量和吞吐需求就脱节了。单看容器的总利用率,也可能看不出哪个分区已经不够用。
1.3 第二阶段:尝试工作负载隔离和单租户
拆成独立容器,可以让不同工作负载拥有自己的物理分区和吞吐预算,减少分区键混用和邻居干扰。这里的单租户指服务或用例的资源隔离,粒度远大于单个终端用户。
Habitat 在这一阶段为用例分配容器,并按物理分区数量提高总吞吐,以维持每分区可用预算。1 这能减轻第一阶段的问题,容量规划却从一个共享池变成许多独立对象。

图 4:OpenAI 原文 Figure 03 的容量示意。1 左侧分母是共享池剩余的 20M RU/s,右侧分母是新独立容器的全部 10K RU/s;两边都在比较新负载与可用余量的比例。
共享池的一个好处,是已有业务提供了比较稳定的总量和可复用的余量。图中同一个功能从内测的 1K RU/s 增长到上线后的 1M RU/s,在共享池里只占剩余容量的 5%;放进内测阶段准备的 10K RU/s 专用容器,则达到整个容量的 100 倍。独立以后,其他容器空闲,也不会自动变成这个功能的可用容量。
于是,上线前扩容、流量增长预测和持续监控,都需要理解具体产品的发布节奏。平台可以按过去的趋势写扩容脚本,但新入口开放、全量发布、活动放量,很难只从历史曲线推出来。每个业务团队都得参与容量规划,而且认真程度未必相同。
这个阶段获得了更小的故障影响范围,也增加了需要单独管理的资源。旧数据仍留在默认共享容器时,拆出新用例还不能立即解决旧容器的问题;只有具备迁移能力,才能继续调整已经落盘的数据。1
1.4 第三阶段:默认使用多租户模式,并具备数据迁移能力
最终方案回到默认共享,同时补齐隔离和弹性能力。前两个阶段已经说明:共享池能降低容量规划成本,但需要控制相互干扰;独立容器提供隔离,但把流量预测和资源准备分散到了各业务。
原文的改进可以分别对应到这些问题:1
-
热点键限流。 优先限制最热的键,避免它持续吃掉所在物理分区的吞吐。容器还有总容量,不代表每个物理分区都能承受任意一个热点。
-
请求 QoS。 批量导入、数据导出等离线请求使用较低优先级。资源紧张时先让这些流量退下来,保护用户正在等待的在线请求。共享资源需要明确过载时的服务顺序。
-
增强的 serverless 模式。 原有模式主要随存储量扩展,每物理分区上限为 5K RU/s;Azure 为 OpenAI 改进后,物理分区可到 10K RU/s,并能依据 RU 使用量触发拆分,降低持续手工调节容器容量的需要。
这里的增强模式来自双方共同改进。截至本文核查时,Azure 公开 serverless 性能文档仍按每物理分区 5K RU/s 描述,普通账号的可用配置需要另行确认。4
默认共享还需要保留调整数据位置的能力。一个业务变热、数据变大,或者需要不同的区域复制范围时,初始布局未必继续合适。Habitat 的透明迁移与路由层支持在复制拓扑不同的 account 之间移动数据。1 上层继续使用统一接口,平台才能根据运行情况调整底层安排。原文没有展开迁移期间的切流和回滚协议,这部分先不替它补实现。
2. 全球范围的读取性能优化
Habitat 的性能问题包含一段实际的地理距离:处理请求的计算资源,与保存数据的存储资源,可能位于不同 Azure 区域。数据库本地访问很快,跨区 RTT 仍然会进入用户等待时间。

图 5:OpenAI 原文 Figure 04 的交互演示录制。1 图中地理位置和路线用于说明跨区域访问,不能作为生产部署地图。
业务当然可以自己缓存、合批、减少读写,或者把部分工作移出关键路径。但每个产品都处理一遍这些问题,接口虽然统一了,性能适配仍然散落在业务里。原文给出的优化覆盖数据位置、复制范围、缓存和服务本身。
2.1 容量允许时,将数据放在用户附近
就近放置可以减少网络往返,也能满足特定的数据驻留要求。困难在于,某个区域承接了多少用户流量,不代表它就能同时容纳对应的数据量。存储、供电、制冷和计算资源都有容量约束。1
因此,就近存储需要与请求路由配合。把用户状态放到一个近端集群以后,上层也得把相关请求送到能高效访问它的计算资源;用户位置、服务入口和存储位置各自独立变化,最终仍可能绕远。
数据特别大时,区域资源还存在机会成本:同一份资源是用于增加服务计算,还是放更多本地数据以降低读取延迟,需要按产品和用户群体权衡。全局布局无法只按最短 RTT 一项指标决定。
2.2 广泛复制小而且时延敏感的数据
认证元数据这类每次请求都可能需要的数据,适合承担更广的复制范围。Habitat 为这类数据使用带有多个辅助区域的高复制 account,减少计算资源与存储之间的 RTT。1
这类策略首先看工作集的体积与访问频率。对象很小、访问很频繁,增加副本有机会让大量请求获益;大对象低频访问,则可能为很少的请求长期保存许多副本。复制成本还会随区域数量、变更频率和后台维护放大,不能把所有数据都按认证请求的要求处理。
复制拓扑不同,也解释了前文为什么需要跨 account 迁移。同一种逻辑接口下,数据可以按不同的访问与驻留要求,采用不同的区域布局。
2.3 区域缓存与 CDC 驱动的失效
其余数据通过靠近请求处理位置的 Valkey lookaside cache 缓解远端读取。1 缓存命中时,读取在区域内结束;未命中才需要回源。对反复访问同一批状态和属性的负载,缓存能够减少跨区往返。
TTL 直接决定了命中率与陈旧程度的取舍。过短会频繁回源,过长又让旧状态停留更久。由业务在写入时主动删除缓存,可以加快更新,但部分失败、跨区网络和数据库复制进度之间的竞争,都会让这条链路变复杂。
Habitat 使用 Cosmos change feed 消费数据变更,再向各区域传播缓存失效,原文给出的传播口径是秒级(这和我们的需求不一致,因为agentloop依赖于底层做BuildContext,这里接口需要强一致)。1 缓存项失效后,下一次读取再回源填充。这样,缓存更新也进入平台的统一链路,业务不必为每一类数据重新接一套跨区失效机制。
秒级传播仍然允许短暂陈旧,不能直接等同强一致;原文也没有公开完整的并发回填协议。这里重点看它解决了什么:在较高命中率下,减少完全依赖 TTL 等待过期所带来的旧数据驻留。
2.4 Rust 改写与各项优化的延迟变化
最后是 Habitat 服务本身。上一篇已经讨论 Python 的 Event Loop、连接复用和代理开销;Rust 改写进一步降低了这一层的处理成本。它与数据复制、区域缓存作用在请求路径的不同位置。

图 6:OpenAI 原文 Figure 05。1 官方整体口径为全局读 p50 约 120→10 ms。横轴未给具体日期;下表按图中台阶粗读,数值为约值。
| 图中标注的优化上线 | 上线前读 p50 | 上线后读 p50 | 台阶约下降 |
|---|---|---|---|
| 关键数据复制到近端区域 | 110 ms | 50 ms | 60 ms |
| 区域内缓存 | 50 ms | 25 ms | 25 ms |
| Rust 重写 | 25 ms | 10 ms | 15 ms |
图上最大的一个台阶出现在关键数据就近复制上线前后,随后两个台阶分别对应区域缓存和 Rust 改写上线。跨区域访问存在时,减少远端请求,通常比只压缩本地处理开销有更大的优化空间。
这些差值描述连续上线前后的观测变化,不能当作独立消融实验,也不能简单相加后替代官方的 120→10 ms 口径。缓存未命中时仍要访问底层存储,所以中位数改善以后,冷读和尾延迟还要继续依靠数据放置与容量建设处理。
3. 共同管理服务的可靠性
OpenAI 这样的规模,会把托管服务中平时不显眼的路径一起放大。原文提到区域故障时的大账号切换,以及回切时客户端集中重连压垮 gateway 的问题。1 副本已经存在,切换仍然需要协调路由、连接和元数据;稳态请求吞吐无法覆盖这些恢复路径。
双方后来把合作延伸到生产规模的非生产账号测试、SDK 迭代、共享监控与直接工程联系。1 测试环境需要有相近的数据、分区和客户端规模,才能看出一次全量操作究竟要花多久,以及恢复流量会先撞上哪个组件。
对平台团队而言,上层看到一次存储调用变慢,底层可能涉及客户端连接池、网关、控制面与物理分区。两边各守自己的监控,很难拼出整条路径。共同运营的实际作用,是让这些组件的行为能够在同一个故障和容量模型里被验证。
4. 全球化 CDC 平台的扩展
缓存失效只是 CDC 的一个消费者。变更还会被用于二级索引、数据迁移和分析,因此 CDC 需要独立承担数据从源存储到下游的持续传播。
在一个区域、少数容器上,注册一个变更回调看起来很简单。全球部署后,业务首先要知道自己的数据分布在哪些 account 和 container,哪些区域运行着对应采集服务;然后还要配置采集 worker、Kafka 和下游消费者的容量。数据发生迁移,采集位置也得跟着更新。
Habitat 把这些工作收到平台里。产品开发者注册对某类数据变更的回调,控制面负责发现待采集容器、选择读取区域,并把采集任务分配给不同地域的 worker。1 业务依赖逻辑数据类型,采集服务理解实际存储拓扑,这样数据位置变化就不用每个消费者重新适配。
资源隔离也要跟进去。CDC 可以从副本区域读取,并使用低优先级请求,减少对延迟敏感的前台读写的影响。1 这里的副本区域是 account 的辅助地域,和一个物理分区内部的主从副本是不同层次。
故障恢复则需要区分错误范围。一个 worker 退出,可以重新分配任务;一个区域不可用,需要换区域继续读取;某条记录永久投递失败,则要进入死信队列,避免一直挡住同一条流。配置和发布出错,也不能让所有流一起停下来。
原文还提到对 SDK checkpoint 的改造:先在内存中批处理,再周期持久化。1 这减少了频繁写进度的开销,恢复时则需要容忍相应的重放。采集平台要维护的状态,已经包括任务归属、读取进度、失败隔离和区域切换。业务看到的回调越简单,这些事情越要由平台接稳。
5. 结束语
5.1 SSDKV 的 RU 预算、burst 与流量优先级
RU 这部分,我们的 SSDKV 很早就做了。一般每个 pod 有一个预设的 RU 上限,pod 内各副本均分共享。为了避免某个副本先变热、撞到均分后的额度,pod 层面会预留一定百分比的 burst,给局部突发留一些空间。
这和前面的 weak partitions 有相近之处:总资源够用,不代表分配到每个副本后的预算恰好符合访问热度。burst 可以缓冲局部偏斜,冷的副本也可以合并,减少预算被切得过碎的问题。当然,合并以后,副本数量、容量和热度都在变化,均衡就没那么好做了。
所以很多时候限流需要设置优先级。离线流量、低优流量和后台流量,要给前台请求让开。完全追求每类请求拿到同样比例的资源,业务未必满意;用户正在等的请求先完成,后台工作晚一点追赶,通常更符合产品需要。前提是后台积压也要能被观察和消化,不能无限期饿着。
5.2 OpenAI 与 WorkBuddy 的全球部署需求
性能这部分,OpenAI 的需求和我们现在做的 WorkBuddy 不太一样。OpenAI 面对的是真正的全球化:它在不同 Azure 区域里有多个 Cosmos DB account,计算资源、用户和存储都分布在全球。就近放置以及跨区域复制,已经直接影响产品体验。
对于通常仅自己可见的 Agent 状态,数据放得离用户近一些就可以。上层需要知道用户的数据属于哪个集群,把请求路由过去;没有共享需求的数据,不必仅仅因为平台是全球化的,就在所有地域都保留完整副本。用户跨区域移动、团队共享和数据驻留要求会增加例外,但可以从这个相对简单的默认模型开始。
认证元数据则不同,每次请求都需要访问,对延迟也更敏感。用带有多个辅助区域的高复制 account 缩短计算与 Habitat 之间的 RTT,是很直接的做法。其余热数据再放一层 Redis 这一类的区域缓存------原文实际使用的是 Valkey。数据按用户就近放置,少量全局元数据广泛复制,缓存负责减少剩余的跨区读取,这几件事各有对应的负载。
5.3 大客户与云厂商的合作方式
Cosmos DB 碰上 OpenAI 这种超级金主,贴身服务才是正常情况。我估计这很可能就在相关团队 OKR 的第一项。做云的就是要当好乙方。客户的规模已经能暴露产品的新限制,还愿意持续买单,双方一起改 SDK、网关和资源调度,是正常的商业和工程合作。中小客户就别指望拿到同样深度的服务了,做选型时得分清普遍可获得的产品能力,和大客户合作里共同做出来的能力。
5.4 在研存储:文档接口保持稳定,成本推动架构变化
Habitat 正在开发面向大体积数据的 EB 级在线存储,结合 Rockset 的 RocksDB Cloud 核心与 Raft 复制,沿用 Cosmos DB 的文档模型,通过存算分离和物理复制降低 flash 容量需求,原文同时将较低的峰值 QPS 列为代价。1
从副本区域读取 CDC,我关心的是强一致在线读能利用多少副本。Cosmos DB 的 follower 可以参与读 quorum,5 不能直接套用只有主副本服务的假设;但每个副本仍有独立引擎,要分别应用写入、维护数据。如果 Compaction 也独立执行,又是全 SSD 保存、没有在线降冷,大量低频数据的成本就很高。
这让其实类似前面写过的《TiDB X------经典架构的扩展问题与改造方法》:持久数据放进共享对象存储,副本复用 SST 和 Compaction 结果,本地 SSD 主要承接日志与热数据缓存,这也是业界各类系统典型做法。6 Habitat 新系统的具体实现还没展开,但减少全量 SSD 副本和重复维护的成本动机很相近。
继续保留文档模型,说明上层数据表达可以延续,现有访问性能也暂时可以接受。原文的 lower peak QPS 没有说明是读、写还是混合负载,也没交代单节点还是整集群、资源配置和延迟要求。1 我暂时不知道这个下降针对什么。我们的读写都是纯在线链路,不能接受业务峰值能力下降。
按 TiDB X 这类做法,写入以本地日志和 Raft 提交,持久数据异步上传;读服务也能继续横向扩展。6 从这个架构方向本身,我推不出峰值 QPS 一定会下降。如果这里指缩小 SSD 缓存后,在某种命中率、固定资源配置下的性能损失,就需要给出对应条件。这一点还得等更详细的设计。
5.5 跨产品线推广与 API 抽象
我认为,Habitat 这样的产品要在公司内部真正做成,最终一定需要自上而下的推动,甚至要有雷霆手段,让各条产品线统一使用。类比我司,Agent 产品本身已经多如牛毛,按理说更容易孵化出类似的平台,但实际不会自然发生。各个团队都有自己的 OKR,核心存储逻辑层都想自己做,毕竟适配别人不是自己的 OKR。每个团队单独看都有理由,放到公司层面,就很难把这些重复建设收敛起来。
另一个难点是 API 抽象。我们开始做的时候,第一个难点就是 API 设计。接口要能容纳多个业务的共性,又不能把每个业务的特殊逻辑都装进来,这需要经验,也需要真实业务反复打磨。我想这也是 Habitat 招聘把"拥有设计供多个内部团队使用的平台 API 的经验"单独列出来的原因。7
6. 参考资料
1 OpenAI:Rapidly scaling online storage to serve over 1 billion ChatGPT users (Part II),2026-10-23
2 Microsoft Learn:Request units (RUs) in Cosmos DB
3 Microsoft Learn:Partitioning and horizontal scaling in Azure Cosmos DB
4 Microsoft Learn:Azure Cosmos DB serverless account performance
5 Microsoft Learn:Global data distribution with Azure Cosmos DB --- under the hood
7 OpenAI:Software Engineer, Habitat (Online Data) --- Seattle