从一到无穷大 #105:OpenAI Habitat II——多租户演进、全球读取优化与 CDC 平台

上一篇《从 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

  1. 热点键限流。 优先限制最热的键,避免它持续吃掉所在物理分区的吞吐。容器还有总容量,不代表每个物理分区都能承受任意一个热点。

  2. 请求 QoS。 批量导入、数据导出等离线请求使用较低优先级。资源紧张时先让这些流量退下来,保护用户正在等待的在线请求。共享资源需要明确过载时的服务顺序。

  3. 增强的 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

6 PingCAP:TiDB X Architecture

7 OpenAI:Software Engineer, Habitat (Online Data) --- Seattle

相关推荐
李兆龙的博客2 小时前
从一到无穷大 #101:SSD 的工作原理——从 NAND 到数据库读写
数据库·ssd·存储
yyuuuzz14 小时前
跨境站点日志时区混乱的排查记录
运维·服务器·网络·云计算·aws·1024程序员节
Maynor9962 天前
免费 Cloudflare 只有 10 个 D1?满了之后的「穷鬼打法」:表前缀复用同一个库
数据库·后端·云计算·cloudflare
A-刘晨阳2 天前
【云原生】nerdctl 部署及使用指南
运维·docker·kubernetes·云计算·1024程序员节·nerdctl
我是高手高手高高手3 天前
安装宝塔,阿里云(有系统和数据两个盘)
阿里云·云计算
FY@-@4 天前
VMware ESXi 7.0 实验全记录:嵌套安装 → 网络配置 → 存储 → Windows 10 虚拟机部署
运维·网络·windows·云计算
DolitD4 天前
国产信创+实时云渲染:数字孪生智慧矿山UE5流畅推流
3d·信息可视化·云计算·图形渲染·数据可视化
做运维的阿瑞4 天前
Terraform 模块版本管理:约束语法、源类型与升级策略
云原生·云计算·terraform
做运维的阿瑞4 天前
Terraform 模块组合:VPC、安全组、EC2 的输出传递与依赖编排
云原生·云计算·terraform