- 扩展 Databricks Apps 的工作在编写任何代码之前就已开始:先就应用应多快响应、在高峰时段必须支持多少用户达成一致,然后把繁重的工作交给合适的服务。应用计算负责提供 UI 并协调调用。
- 可扩展的代码在等待服务时保持应用响应,使用按用户缓存并保持状态可恢复。软状态可以重建;硬状态存放在 Lakebase 或 Unity Catalog 表中,因此任何实例都能重建会话。
- 使用负载测试和遥测来选择实例规格、工作进程数和实例数。为 Lakebase 连接做预算,并增加计算资源以满足实测需求和可用性目标。
恭喜,你已经在 Databricks Apps 上构建了一个应用,而且它已经从几个用户增长到整个组织内的数百或数千个用户。你的用户希望它响应迅速并保持在线。而你的老板可能不太乐意为一堆实例上的 XLarge 计算付费。
如何让应用运行快速、保持在线并应对不断增长的需求?直觉上的答案是增加计算资源,但只有在你了解请求把时间花在哪里,以及应用是否具备扩展能力(其框架、异步代码以及运行在多个实例上的能力,使平台能够轻松地纵向或横向扩展)之后,这才会有所帮助。
在本指南中,我们将介绍如何分四个阶段扩展 Databricks Apps:设计;构建;测试与部署;以及运维(图 1)。
本指南的重点主要是大规模下的应用性能和韧性;它不涵盖部署自动化,也不涵盖可能与应用交互的其他 Databricks 服务的性能优化和扩展。

图 1:扩展 Databricks App 的四个阶段。
良好的扩展决策在编写任何代码之前就已开始:先就用户需要应用提供什么达成一致,然后决定工作在哪里运行、数据存放在哪里。
从目标开始:峰值用户和延迟
在选择架构之前,先明确应用必须处理多少负载以及什么样的体验算好,这会很有帮助。我们建议就两个主要目标达成一致:
- 性能: 关键操作必须多快响应?我们建议基于百分位数(例如 p95)设定端到端延迟目标,它能捕捉到平均值无法体现的较慢体验。要包含 UI 渲染,并将冷启动单独处理。对于流式 AI 响应,首个 token 的时间也很重要。
- 可扩展性: 峰值时有多少活跃用户、他们使用频率如何,以及应用应支持多少增长?月末等繁忙时段也应纳入这一估算,因为应用必须在其延迟和错误率目标内处理这些负载。
除此之外,还要决定哪些工作必须在重启后存活,并检查关键依赖能否支持这一点。这决定了你如何拆分状态以及运行多少个实例。
接下来,勾勒几个用户旅程,例如打开仪表板或提交问题。对于每个操作,记录它调用的服务、大致调用次数以及哪些调用相互依赖、它应多快完成,以及缓存数据可以有多陈旧。这些假设决定了架构和最初的测试目标。一旦原型运行起来,你就可以测量真实的调用并进行修正。
假设一家零售商为其 2,000 名门店经理提供了一个由 Lakebase 支持的库存查询应用。在黑色星期五最繁忙的一小时里,有 200 人同时使用它。如果每人平均每分钟进行两次库存查询,那就是每分钟 400 次查询。如果每次查询对 Lakebase 执行三条 SQL 语句,那么预计每分钟 1,200 条语句,即该小时内平均每秒 20 条。
这估算的是查询速率,而不是并发数。要测试突发和增长,对照实际使用情况进行验证,并单独为连接做预算(见“为 Lakebase 连接做预算”)。
设计:规划架构
将应用计算保留用于服务应用
应用计算的存在是为了提供 UI 并协调调用,而不是承担繁重工作。我们建议将每种工作发送到为其构建的服务(图 2)。对于长时间运行的调用,在后台运行工作,例如作为作业运行,并让 UI 轮询状态或接收流式更新,而不是让一个请求一直保持打开直到完成。
如果应用需要运行它无法控制的代码,例如用户编写的代码或由代理生成的代码,不要在应用中运行它。请在 Databricks Sandbox, 一个可以从自定义镜像启动的隔离环境中运行它,这样它的 CPU 和内存使用就不会占用服务用户的实例。

图 2:每种工作在哪里运行以及状态存放在哪里。
为规模做规划并选择框架
当更多人使用你的应用时,你可以通过两种方式增加容量:纵向扩展(垂直扩展),即迁移到更大的计算规格,为每个实例提供更多 CPU 和内存;或横向扩展(水平扩展),即增加实例来分担请求负载。由于繁重工作运行在应用之外,通常限制应用的是它一次能处理多少请求。
例如,在黑色星期五的案例中,每次库存查询都很轻量,但来自 200 名门店经理的查询可能会成批同时到达。更多实例可以分散这些请求,而如果应用能够利用额外的核心(通常通过运行更多工作进程),更大的实例会有所帮助。将大型参考数据集或模型保留在内存中的应用会遇到不同的限制。更大的实例提供它所需的内存;增加实例没有帮助,因为每个实例都会加载自己的副本。你可以从 Medium 开始,让本指南后面的负载测试显示你需要哪一种。
框架以后更难更改,所以现在值得做出选择。Databricks Apps 运行基于 Python 或 Node.js 构建的应用,你并不局限于下面的框架。对于大多数新应用,我们推荐 AppKit, 这个 TypeScript SDK 是 databricks apps init 以及默认使用的 Databricks agent skills。缓存、遥测、重试逻辑和错误处理都是内置的,为你提供了本指南中若干实践的起点。AppKit 默认每个实例运行一个 Node.js 进程,因此最简单的扩展方式就是增加实例。
如果你已经有一个应用,或者你的后端需要 Python:
- JavaScript 前端加 API 后端(FastAPI、Flask 或 Express):将 UI 状态保留在浏览器中,并让服务端状态可在各工作进程和实例之间恢复。Python 后端可以在每个实例中运行多个工作进程,例如使用 Uvicorn 或 Gunicorn。Node.js 后端默认每个实例运行一个进程,因此通过增加实例来扩展它们。
- Streamlit 或 Gradio 框架:适合用 Python 快速构建的数据和 AI 工具。Streamlit 以单进程运行,而且两者都依赖实例本地的会话处理,因此要在重启和扩展期间测试会话连续性。对于必须跨实例扩展并能无缝恢复的应用,请优先选择上述方法之一,因为不推荐使用这些框架。
无论你选择哪种方式,如果它将会话状态保存在服务器内存中,那么在横向扩展之前,值得测试一下会话迁移到另一个实例时会发生什么。
为应用状态做好规划,使其能在重启和重新扩展后存活
大多数 Databricks Apps 会在内存中保留一些状态,只要你知道哪些部分可以承受丢失,这没问题。一个有用的第一步是列出每个会话持有什么,以及哪些必须在重启后存活,然后将其分为软状态和硬状态:
| 它是什么 | 软状态 | 硬状态 |
|---|---|---|
| 它是什么 | 为速度或上下文而保留的临时工作数据 | 事实来源,持久保存 |
| 示例 | 内存中的上下文、缓存、进行中的工作 | 业务记录、检查点、已保存的偏好设置 |
| 存放位置 | 通常是实例内存或本地磁盘,与单个实例绑定 | Lakebase 或 Unity Catalog 表,每个实例都可访问 |
| 如果丢失 | 尽可能从硬状态重建 | 必须在重启和故障后存活,满足你的恢复需求 |
这种划分如何运作取决于应用类型:
- AI agent:在循环运行期间,将会话上下文、工具输出和计划保留在软状态中,并在每个有意义的步骤之后保存检查点。
- 工作流应用:将进行中的状态流式传输或轮询到 UI,并保存每个已完成的步骤。
- 仪表板:将筛选值、参考列表和小型数据集保留在内存中,并将用户偏好设置和已保存的筛选器保存为硬状态。

图 3:当会话迁移时,另一个实例可以从硬状态重建它。
Databricks Apps 会尽力将每个用户保持在同一个实例上(会话亲和性),并在需要时重新平衡会话,因此你的应用在本地保留的任何按用户缓存通常仍然可用。重启、故障和扩展变化仍可能迁移会话,因此另一个实例必须能够从硬状态重建它(图 3)。
构建:编写可扩展且能在重启后存活的代码
有了目标和架构之后,下一步是编写在负载下也能撑住的代码,包括在会话迁移或实例停止时。
在你需要之前就对应用进行监测
我们建议从一开始就开启遥测,这样在你需要之前数据就已经在那里了。 应用遥测 通过 OpenTelemetry 将日志、追踪和指标写入 Unity Catalog 表。一旦为应用启用,日志会自动到达,但追踪和指标需要插桩。
你应该围绕 SQL、数据库、模型和 API 调用创建自定义 span,以显示时间花在哪里。还要在 span 上记录捕获的异常,这样它们就会出现在你的追踪中。然后,追踪 ID 和请求 ID 让你可以跨实例跟踪一个用户的请求。例如,围绕 SQL 调用的一个 span:
使用异步代码并减少浪费的调用
大多数应用把时间花在等待另一个服务上:SQL 查询、模型调用、外部 API。对于阻塞式调用,每次等待都会在整个调用期间占用一个执行槽。少数几个慢调用就可能耗尽该容量。这就是为什么异步处理程序对 I/O 密集型工作很重要。当处理程序等待模型和数据库调用时,只要这些调用是非阻塞的,一个工作进程就可以在等待期间继续服务其他请求。你的追踪和系统指标会显示何时适用这一点:如果一个请求的大部分时间花在 SQL、模型或 API span 上,并且延迟随并发用户增加而上升,而 CPU 保持低位,那么请求很可能在阻塞式调用后面排队。
即使使用异步,如果一个请求一个接一个地运行查询,它仍然可能很慢。将独立的查询一起启动(例如,使用 asyncio.gather 或 Promise.all),这样请求的耗时接近其最慢查询的耗时。还值得限制并行运行的数量,这样单个请求就不会淹没仓库或数据库。通常,合并查询比运行 N+1 查询更好,后者是指应用获取一个列表,然后为每个条目再运行一个查询:一个 join 或一个 IN 子句可以在一到两次查询中完成同样的工作。
默认按用户缓存数据
缓存可以加速应用,但也可能泄露数据:应用级缓存不强制执行 Unity Catalog 权限,因此共享缓存可能向一个用户显示另一个用户的数据。按用户经过验证的身份和查询参数来限定缓存键,并在需要时按会话限定;绝不要以客户端发送的未经核实的身份作为键。只有当缓存数据对所有人都相同时才共享它。对于许多用户同时读取的共享条目,让一个请求刷新过期条目,而其他请求等待,这样它们就不会一起冲击仓库。 例如,AppKit 的缓存会在每个实例内对并发的缓存未命中进行去重。
关于应用容器重启或会话迁移
将软状态和硬状态设计付诸实践归结为几条建议:
- 持久化任何必须在重启或部署后存活的内容。仅靠内存对于一次性缓存没问题,但对于用户不能承受丢失的工作则不行。
- 在你不希望重复工作的地方设置检查点,并使步骤具有幂等性,这样重试就是安全的。
- 让会话可移植,使任何实例都能从硬状态重建会话(图 3)。只重新加载你需要的内容,并对其进行缓存以避免重复读取。
- 浏览器应用会自动获得会话亲和性。对于有状态 API 客户端,发送一个稳定的
X-Routing-Key请求头,并以拥有软状态的任何对象作为键。选择一个足够具体、能分散负载的键,例如对话 ID 或用户 ID,而不是租户 ID。该请求头由客户端设置,因此只能用于路由,绝不能用来判断用户是谁。 - 数据库连接也会断开。数据库重启或故障转移时连接就会断开。使用有界退避和抖动进行重连,这样实例不会同时全部重连,从而引发连接风暴。在重试写入之前,先检查数据库以确认该写入尚未成功执行。
- 保持启动轻量。每次重启、部署和新实例都要付出加载模型或数据集、预热缓存和打开连接池的代价,之后才能开始处理流量。避免大型安装和启动时的阻塞式外部调用,只在需要时加载重型资源。
优雅关闭
当你重新部署应用时,Databricks 会发送 SIGTERM,如果应用在 15 秒内没有退出,则发送 SIGKILL。这个时间窗口用于执行最终的检查点。重要的工作应该在正常运行期间就已经保存。
例如,使用 FastAPI 时,Uvicorn 会先完成未完成的请求,再运行 lifespan 清理。 save_in_progress_sessions() 代表你自己用于保存未完成工作的函数:
这假设使用异步连接池(例如 psycopg 的 AsyncConnectionPool)。 Apps-to-Lakebase 教程 使用的是同步连接池,关闭时无需 await.
将 Uvicorn 的 --timeout-graceful-shutdown 设置为低于 15 秒(例如 10),这样长时间运行的请求就不会占用检查点时间,并用你自己的负载测试关闭过程。AppKit 的 Lakebase 插件会在关闭时关闭其连接池;任何自定义检查点仍必须适应同一时间窗口。
测试与部署:进行负载测试并按峰值确定规模

图 4:阶梯式负载测试期间的系统指标:延迟在最后一步骤升,而 CPU 和内存仍低于其上限。
在上线之前,负载测试将帮助你把估算转化为更接近现实的决策:实例规格、每个实例的工作进程数以及实例数量。
如何对 Databricks Apps 进行负载测试
我们建议在生产环境之前进行负载测试,使用真实用户旅程并按照你预期的峰值进行。Locust 之类的工具会报告每秒请求数、延迟百分位数以及每一步的失败率,测试会根据你为关键操作约定的延迟和错误率目标判定通过或失败。测试运行期间,应用内置的系统指标会显示每分钟请求数、延迟以及 CPU 和内存相对于其上限的情况,因此你可以看到应用首先触及哪个限制(图 4)。
逐步增加并发用户,直到吞吐量不再增长,或延迟和错误率突破你的目标。值得尝试多种实例规格和工作进程数,因为更大的配置并不总是更快。我们还建议在测试环境中测试恢复能力:停止并启动应用,而不仅仅是在运行期间重新部署或更改实例数量,然后检查用户能否继续使用、恢复需要多长时间以及是否有任何工作丢失。对于智能体应用, 负载测试指南 介绍了先用模拟 LLM 测试一次,以测量应用自身的吞吐量,然后用真实模型测试端到端延迟。
负载生成器本身也可能成为瓶颈:它可能先耗尽 CPU 或连接,而身份验证错误或速率限制响应可能看起来像性能下降。真正重要的是在你的延迟目标内持续成功的请求,而不是短暂的峰值。
负载测试还应显示应用背后的服务能否跟上。如果仓库开始排队或模型端点受到限流,应扩展该服务,而不是扩展应用。
选择实例规格
Databricks Apps 提供三种规格:Medium(默认,最多 2 个 vCPU 和 6 GB)、Large(最多 4 个 vCPU、12 GB)和 XLarge(最多 12 个 vCPU、48 GB)。正确的选择取决于你的应用首先触及哪个限制,因此从 Medium 开始,让负载测试告诉你:
| 负载测试显示的情况 | 可能的原因 | 应采取的措施 |
|---|---|---|
| 请求缓慢且应用 CPU 较低 | 应用在等待其背后的某些东西,或者阻塞式调用占用了工作进程 | 保持规格不变。先修复查询、仓库、连接池或阻塞式调用,并检查每个进程的 CPU:一个核心满载可能看起来像整体 CPU 较低 |
| CPU 接近上限,服务器运行多个工作进程(例如 FastAPI 配合 Uvicorn) | 应用可以使用更多核心 | 迁移到 Large |
| CPU 接近上限,单进程应用(Streamlit,或默认情况下的 AppKit) | 性能分析显示工作受限于单个核心 | 增加实例。只有当应用能够使用额外核心时(例如通过工作线程或原生库),更大的规格才有帮助 |
| 内存接近上限 | 大型数据集或模型保存在内存中 | 为内存而升级:Large 是 Medium 的两倍,XLarge 在单个实例中提供 48 GB |
| 恢复测试:当应用唯一的实例重启时,应用会宕机 | 没有备用实例可以接管 | 增加实例,而不是增大规格 |
每个实例应运行多少个工作进程?
工作进程是每个实例内的服务器进程。对于支持此模型的 Python 服务器,你在启动时设置数量(例如 uvicorn --workers 2),并且它按实例生效。
单个工作进程可能让 CPU 空闲,但额外的工作进程也会竞争 CPU 和内存。在我们负载测试指南的基准测试中,Medium 使用两个工作进程比使用四个表现更好,而 Large 的吞吐量约为 Medium 的 2.2 倍。将文档中的起点(Medium 上两个工作进程或 Large 上八个)视为初步猜测:该基准测试模拟了 LLM 且受 CPU 限制,而一个主要等待模型调用的真实应用可能从额外工作进程中获益更多。根据你自己的负载测试(使用真实模型)来选择数量。
应用应运行多少个实例?
使用 水平扩展,实例数量由你自行设置。默认限制为每个应用五个实例,如果负载测试表明你需要更多,你的 Databricks 代表可以提高该限制。请按预期峰值和增长来设定数量。实例数量变化期间应用会持续提供服务,计算成本随实例数量线性增长。如果你要转换现有应用,请先在副本上试用。
为实现更高可用性,我们建议至少使用两个实例,并留出足够容量以在失去一个实例后仍能处理峰值负载:如果峰值需要两个实例,就运行三个。额外实例防范的是实例故障,而不是应用背后服务中的问题。
为 Lakebase 规划连接预算
对于通过 OAuth 连接 Lakebase 的 Databricks App,我们建议先在数据库的标准(非 PgBouncer)连接字符串上使用应用侧连接池。它在打开新连接时会复用连接并获取新的 OAuth 凭据,正如 Apps-to-Lakebase 教程 中针对 Python 所展示的那样。设置连接池超时,以便在请求截止时间之前为查询本身留出足够时间。
这些连接会累积起来,因此在增加实例或工作进程之前要先做好预算。每个工作进程一个连接池时,上限为实例数 × 每实例工作进程数 × 连接池最大大小:五个实例、每个实例八个工作进程、连接池最大值为十,最多可打开 400 个连接。再加上其他客户端以及应用代表用户打开的任何连接,然后将总数与数据库文档中记录的限值进行比较,并为部署(新旧版本并行运行)留出余量。
代表用户(OBO)查询需要身份容量。这意味着每个以自己身份查询 Lakebase 的用户都需要自己的 Postgres 角色,而 Lakebase 允许 每个分支 500 个角色. 对于更大的用户群,要尽早决定哪些查询真正需要按用户访问。增加实例不会改变这一限制。在组级权限足够的情况下,为 Databricks 组创建一个 Postgres 角色,可让其成员使用自己的 OAuth 令牌连接,这样一个角色就能覆盖整个组。
无停机上线并安全回滚
对于水平扩展的应用,Databricks 会先更新一个实例,成功后再更新其余实例,因此应用会持续提供服务。但每次部署后仍值得检查应用是否健康、会话是否恢复。将代码保存在 Git 中意味着你可以通过重新部署已知良好的标签或提交来回滚。在滚动发布期间,新旧版本会并行运行,因此要确保数据库和检查点格式对两者都可读,并且在确定不会回滚之前,不要删除上一版本需要的字段。
运维:基于数据进行监控、诊断和修复
上线之后,工作就变成了一个循环:找到最慢的步骤,修复它,然后再次测量。这就是我们在生产环境中保持遥测开启的原因。在水平扩展的应用上,日志选项卡中的实时视图一次只显示一个实例;启用遥测后,应用遥测日志会显示所有实例。
当某个环节变慢时,端到端地追踪请求(图 5)。

图 5:请求的时间花在哪里以及主要瓶颈。
从症状入手
以下是我们最常见的症状,以及它们通常指向的位置:
| 症状 | 查看位置 | 可能原因 | 应对措施 |
|---|---|---|---|
| 空闲期或部署后的首次请求缓慢 | Insights 选项卡的系统指标和应用遥测中的追踪 | 冷启动:应用启动,或某个服务从空闲状态唤醒 | 检查应用启动时间,以及 Lakebase 或 SQL 仓库在空闲时是否会停止,并保持启动过程轻量 |
| 请求缓慢且应用 CPU 较低 | Insights 选项卡的系统指标和应用遥测中的追踪 | 应用在等待其背后的某些东西,或者阻塞式调用占用了工作进程 | 在增加应用计算资源之前,先检查追踪中是否存在查询、仓库或连接池延迟 对于单进程应用,请考虑整体 CPU 较低仍可能意味着某个核心已满载 |
| 应用 CPU 或内存接近上限 | Insights 选项卡的系统指标 | 应用本身是瓶颈,或者它在内存中保留了太多内容 | 对 CPU 密集型工作进行性能分析,将大型状态移出内存,然后参阅“选择实例规格”中的指导 |
| 等待数据库连接时超时 | 应用日志和 Lakebase 指标 | 连接池争用、长事务或数据库连接限制 | 检查连接使用情况和事务持续时间,然后在连接预算内调整连接池或数据库容量 |
| 用户在部署或扩缩容期间丢失工作或看到错误 | Insights 选项卡的系统指标 | 无法重建的软状态,或未能在 15 秒内完成的关闭 | 对重要工作设置检查点,使会话可在任意实例上重建,并用真实请求和流测试关闭过程 |
设置健康告警
我们建议将告警规划作为设置工作的一部分:确定应用宕机时通知谁,并将 Apps API 中的 app_status.state 和 compute_status.state 接入你的监控工具。仅凭应用状态并不能证明用户可以正常工作,因此还要检查一个有代表性的用户旅程,包括其延迟和错误率。设置阈值时,要为部署期间的短暂状态变化留出余地。
总结
扩展 Databricks Apps 意味着要对整个请求路径进行规格规划,而不仅仅是应用服务器:设定延迟和峰值负载目标,将繁重工作交给为此构建的服务,保持状态可恢复,并让负载测试和遥测来决定容量投向何处。当测量结果表明需要时再增加计算资源,而不是作为第一反应。
要进一步了解,请 阅读如何使用 Databricks Apps 和 Lakebase 构建生产就绪的数据和 AI 应用. 更多详情,请参阅文档中的 Databricks Apps 最佳实践 和 Databricks Apps 的日志记录与监控 . 有关按应用类型划分的架构模式,例如内部工具、AI 副驾驶和智能体工作流,请观看 Data + AI Summit 2026 的 构建更好的应用 和 面向数千用户的有状态应用 .
常见问题
如何扩展 Databricks App?
分四个阶段进行扩展:设计、构建、测试与部署、运维。先设定延迟和峰值负载目标,将应用计算保留用于服务 UI,并编写具有可恢复状态的异步代码。然后让负载测试和遥测来决定是纵向扩展(scale up)到更大的实例,还是横向扩展(scale out)到更多实例。
我应该对 Databricks App 进行纵向扩展还是横向扩展?
这取决于应用先触及哪个限制。当请求以突发方式到达,或单进程应用受限于单个核心时,可通过增加实例进行横向扩展。当应用需要更多内存,或运行多个可利用额外核心的 worker 时,可纵向扩展至 Large 或 XLarge。
Databricks Apps 提供哪些实例规格?
Databricks Apps 提供 Medium(默认,最多 2 个 vCPU 和 6 GB)、Large(最多 4 个 vCPU 和 12 GB)和 XLarge(最多 12 个 vCPU 和 48 GB)。从 Medium 开始,让负载测试显示你的应用需要哪种规格。
应用重启或扩缩容时,如何保持用户会话正常工作?
将会话状态拆分为软状态(可重建)和硬状态(持久存储在 Lakebase 或 Unity Catalog 表中)。Databricks Apps 会尽力将每个用户保持在同一个实例上,但重启和扩缩容仍可能迁移会话,因此任何实例都必须能够从硬状态重建会话。
扩展后的应用可以打开多少个 Lakebase 连接?
每个 worker 一个连接池时,上限为实例数 × 每实例 worker 数 × 连接池最大大小。例如,五个实例、每个实例八个 worker、连接池最大值为十,最多可打开 400 个连接。将该总数与数据库文档中说明的限制进行比较,并为部署留出余量。
准备好让你的应用从少数用户扩展到数千用户了吗?请参阅文档中的 Databricks Apps 最佳实践 和 Databricks Apps 的横向扩展 。
相关标签:在收件箱中获取最新文章
订阅我们的博客,将最新文章直接发送到你的收件箱。