在几秒钟内扩展到100万个并发沙盒
反向工程 2026年7月16日 • 10分钟阅读 在Modal,我们构建沙盒,除了其他东西。代理在沙盒中运行,而代理正在消耗软件。今天,Modal每天运行数百万个沙盒,支持每个客户最多五万个并发沙盒,并能够在规模上支持多种用例,从增强学习到后台代理。随着我们的用户要求越来越多的沙盒,以越来越高的速度创建。增强学习可能需要同时运行数百万个沙盒,并在推出初期创建成百上千个沙盒的高峰。同样,代理越来越需要大规模和高并发创建速率以应对流量峰值。我们现有的沙盒平台非常优秀,但并没有针对这些规模进行设计;也没有其他现有解决方案。我们对规模和性能非常执着,我们希望我们的基础设施能够加速代理的增长,而不是增加摩擦。因此,我们重新回到了绘图板上。在过去几个月里,我们从零开始重建了我们的核心沙盒平台,以支持规模和可靠性。在我们的新系统上,用户可以并发运行数百万个沙盒,并且每秒可以创建数万个沙盒。我们已经消除了控制平面中的所有中央瓶颈,因此没有实际的扩展限制,并且我们优化了容器调度和启动的每个部分,简化了调度路径,使用一层负载均衡器,该负载均衡器直接在我们的工作节点上创建容器。作为我们平台能力的证明,我们已经成功并发运行了100万个沙盒,在一分钟内创建了所有100万个沙盒。这证明我们可以运行大量沙盒。 为什么大多数解决方案无法扩展 运行100万个沙盒达到了任何容器平台的极限,不仅因为容器的数量庞大,而且因为运行这么多沙盒需要数万个计算节点。将会有许多操作的复杂度是O(沙盒)或者O(节点)或者两者兼有,这将导致传统容器平台面临扩展限制。以Kubernetes为例:调度算法在最坏情况下是O(n x p),其中n为节点数,p为pods,并且调度默认是串行的。每个pod在其生命周期内都会多次写入etcd(中央Kubernetes持久存储),在创建速率高或pod更替频繁的情况下,这会导致严重问题,且etcd在keyspace内并不是本地分片的。 每个节点必须在每个心跳间隔内至少向etcd写入一次以发出生存信号,因此基础的etcd写入负载是O(节点),与pod创建完全无关。 Kubernetes调度流的近似。新pods由API服务器写入etcd(一个强一致性的持久存储)。Kubernetes调度器监控新的未分配pod,通过调用API服务器将它们分配给节点,这再次写入etcd;在这个写入完成之后,节点才能启动pod。Kubernetes可以被扩展,但需要认真工作。为了运行大量的节点,etcd通常必须重写或替换。支持高调度吞吐量需要构建复杂的散布-聚集系统,以并行化调度算法,同时仍然保持pod状态的单一可信来源。分片和并行化并不是默认情况下简单的,因为Kubernetes将强一致性作为其设计的支柱。Modal的原始沙盒架构存在类似问题。像Kubernetes一样,我们在整个后端依赖强一致性,因此创建和调度沙盒需要全球协调,并且需要对Postgres进行O(沙盒)写入,而这些写入我们不能简单地分片。Modal的原始沙盒控制平面架构。当沙盒被创建时,它们会放入队列并写入Postgres。调度是乐观的并且是并行运行的,需要中央协调以避免冲突。将沙盒分配给工作节点(计算节点)需要额外的写入到Postgres。由于我们并未建立在Kubernetes之上,因此我们能够扩展系统的许多部分。例如,调度是默认并行化的,这使我们能够实现非常高的沙盒创建速率。但随着我们扩大到越来越多的节点和沙盒,我们不断遇到新的瓶颈,这些瓶颈的操作是O(沙盒)或O(节点),但不容易扩展。例如,我们为每个完成的沙盒运行一个持久工作流,因此高沙盒更替率会造成巨大的事件积压。我们反复遭遇以O(沙盒)速率调用的RPC,这造成了系统中的意外负载问题。而且,运行大量沙盒所需的节点的数量造成了节点管理和自动扩展的多个下游问题。最后,尽管我们能够找到解决方法,但在关键路径上留下一个未分片的Postgres实例仍然是个问题。
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡