跳到正文
Databricks Blog·· 3 小时前AI 评分22

Databricks Apps 扩展指南:从设计到运维的四阶段实践

A builder’s guide to scaling Databricks Apps

AI 导读

Databricks 发布《Databricks Apps 扩展指南》,按设计、构建、测试部署、运维四个阶段讲解如何让应用在用户从数百增至数千时保持低延迟与高可用。指南建议先确定 p95 端到端延迟和峰值用户目标,再把长任务交给后台 Job、把不可控代码放进 Databricks Sandbox,并推荐新应用默认使用 TypeScript SDK AppKit。

正文 · AI 翻译

恭喜,你已经在 Databricks Apps 上构建了一个应用,它的用户从几个人增长到了组织内的数百甚至数千人。你的用户希望它响应迅速并保持在线。而你的老板可能并不乐意为一堆实例上的 XLarge 计算资源付费。

如何让应用运行得快、保持在线并应对不断增长的需求?直觉上的答案是增加计算资源,但只有在你弄清楚请求把时间花在哪里,以及应用是否具备扩展能力之后,这才能起作用:这包括它的框架、异步代码以及运行在多个实例上的能力,这样平台就能轻松地将其纵向或横向扩展。

在本指南中,我们将分四个阶段介绍如何扩展 Databricks Apps:设计;构建;测试与部署;以及运维(图 1)。 

本指南的重点主要是大规模下的应用性能和韧性;它不涵盖部署自动化,也不涵盖可能与应用程序交互的其他 Databricks 服务的性能优化和扩展。

image1.png

图 1:扩展 Databricks App 的四个阶段。

良好的扩展决策在编写任何代码之前就已开始:先就用户需要应用提供什么达成一致,然后决定工作负载在哪里运行以及数据存放在哪里。

从目标开始:峰值用户数和延迟

在选择架构之前,先明确应用需要处理多少负载以及什么样的体验才算良好,这会很有帮助。我们建议就两个主要目标达成一致:

  • 性能:关键操作必须多快响应?我们建议基于百分位数(例如 p95)设定端到端延迟目标,这样可以捕捉到平均值无法体现的较慢体验。要将 UI 渲染包含在内,并将冷启动单独处理。对于流式 AI 响应,首个 token 的时间也很重要。
  • 可扩展性:峰值时有多少活跃用户,他们使用应用的频率如何,应用应支持多大的增长?月末等繁忙时段也应纳入这一估算,因为应用必须在其延迟和错误率目标内处理这些负载。

除此之外,还要决定哪些工作必须在重启后仍然保留,并检查关键依赖项能否支持这一点。这决定了你如何拆分状态以及运行多少个实例。

接下来,勾勒几个用户旅程,例如打开仪表板或提交问题。对于每个操作,记录它调用的服务、大致调用次数以及哪些调用相互依赖、它应多快完成以及缓存数据可以有多陈旧。这些假设决定了架构和最初的测试目标。一旦原型运行起来,你就可以测量真实的调用并进行修正。

假设一家零售商为其 2,000 名门店经理提供了一个由 Lakebase 支持的库存查询应用。在黑色星期五最繁忙的一小时里,有 200 人同时使用它。如果每人平均每分钟进行两次库存查询,那就是每分钟 400 次查询。如果每次查询对 Lakebase 执行三条 SQL 语句,预计每分钟 1,200 条语句,即那一小时内平均每秒 20 条。

这估算的是查询速率,而不是并发数。要测试突发流量和增长情况,用实际使用情况来验证,并单独规划连接数预算(参见“规划 Lakebase 的连接数预算”)。

设计:规划架构

将应用计算资源保留用于应用服务

应用计算的存在是为了服务 UI 并协调调用,而不是承担繁重的工作。我们建议将每类工作发送到为其构建的服务(图 2)。对于长时间运行的调用,请在后台运行工作,例如作为作业,并让 UI 轮询状态或接收流式更新,而不是保持一个请求打开直到完成。

如果应用需要运行它无法控制的代码,例如用户编写或由代理生成的代码,请不要在应用中运行它。请在 Databricks Sandbox 中运行它,这是一个可以从自定义镜像启动的隔离环境,因此其 CPU 和内存使用不会占用服务用户的实例。

image4.png

图 2:每种工作在何处运行以及状态位于何处。

为规模做规划并选择框架

当更多人使用你的应用时,你可以通过两种方式增加容量:纵向扩展(垂直扩展)通过迁移到更大的计算规格,为每个实例提供更多 CPU 和内存,或横向扩展(水平扩展)通过添加实例来分担请求负载。由于繁重的工作在应用外部运行,通常限制它的是它能同时处理多少请求。

例如,在黑色星期五案例中,每次库存检查都很轻量,但来自 200 名店长的检查可能会突发地同时到达。更多实例可以分散这些请求,而更大的实例在应用能够利用额外核心时(通常通过运行更多工作进程)会有所帮助。一个在内存中保留大型参考数据集或模型的应用会遇到不同的限制。更大的实例提供它所需的内存;添加实例没有帮助,因为每个实例都会加载自己的副本。你可以从 Medium 开始,让本指南后面的负载测试显示你需要什么。

框架以后更难更改,所以现在值得选择。Databricks Apps 运行基于 Python 或 Node.js 构建的应用,你并不局限于下面的框架。对于大多数新应用,我们推荐 AppKit,这是 databricks apps init 和 Databricks 代理技能默认使用的 TypeScript SDK。缓存、遥测、重试逻辑和错误处理都是内置的,为你提供了本指南中若干实践的起点。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 代理:在循环运行时将对话上下文、工具输出和计划保留在软状态中,并在每个有意义的步骤后保存检查点。
  • 工作流应用:将进行中的状态流式传输或轮询到 UI,并保存每个完成的步骤。
  • 仪表板:将筛选值、参考列表和小型数据集保留在内存中,并将用户偏好和保存的筛选器保存为硬状态。

image3.png

图 3:当会话迁移时,另一个实例可以从硬状态重建它。

Databricks Apps 会尽力将每个用户保持在同一个实例上(会话亲和性),并在需要时重新平衡会话,因此你的应用在本地保留的任何每用户缓存通常仍然可用。重启、故障和扩缩容变化仍可能迁移会话,因此另一个实例必须能够从硬状态重建它(图 3)。

构建:编写可扩展并能承受重启的代码

有了目标和架构,下一步是编写在负载下经得起考验的代码,包括会话迁移或实例停止时。

在需要之前对应用进行插桩

我们建议从一开始就开启遥测,这样数据在你需要之前就已存在。应用遥测通过 OpenTelemetry 将日志、追踪和指标写入 Unity Catalog 表。一旦为应用启用,日志会自动到达,但追踪和指标需要插桩。

你应该围绕 SQL、数据库、模型和 API 调用创建自定义 span,以显示时间花在哪里。同时将捕获的异常记录在 span 上,以便它们出现在追踪中。追踪和请求 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。该头由客户端设置,因此只能用于路由,绝不能用来决定用户是谁。
  • 也要预料到数据库连接会断开。当数据库重启或故障转移时,连接会断开。使用有界退避和抖动进行重连,以免所有实例同时重连并引发连接风暴。在重试写入之前,检查数据库以确认该写入尚未完成。
  • 保持启动轻量。每次重启、部署和新实例都要付出加载模型或数据集、预热缓存和打开连接池的代价,之后才能处理流量。避免大型安装和启动时的阻塞性外部调用,只在需要时加载重型资源。

优雅关闭

当你重新部署应用时,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 插件在关闭时会关闭其连接池;任何自定义检查点仍必须适应同一时间窗口。

测试与部署:针对峰值进行负载测试和容量规划

image2.png

图 4:阶梯式负载测试期间的系统指标:在最后一步延迟跃升,而 CPU 和内存仍低于其限制。

在发布之前,负载测试将帮助你把估算转化为更贴近现实的决策:实例大小、每个实例的 worker 数量以及实例数量。

如何对 Databricks Apps 进行负载测试

我们建议在生产环境之前进行负载测试,使用真实用户旅程模拟预期峰值。像 Locust 这样的工具会报告每秒请求数、延迟百分位数以及每一步的失败率,测试会根据你为关键操作约定的延迟和错误率目标判定通过或失败。测试运行期间,应用内置的系统指标会显示每分钟请求数、延迟以及 CPU 和内存相对于其限制的情况,这样你就能看到应用先达到哪个限制(图 4)。

逐步增加并发用户,直到吞吐量不再增加,或者延迟和错误率突破你的目标。值得尝试多种实例大小和 worker 数量,因为更大的配置并不总是更快。我们还建议在测试环境中测试恢复能力:停止并启动应用,而不仅仅是在运行期间重新部署或更改实例数量,然后检查用户能否继续使用、恢复需要多长时间以及是否有任何工作丢失。对于 agent 应用,负载测试指南 介绍了先用模拟 LLM 测试一次以测量应用自身的吞吐量,然后用真实模型测试端到端延迟。

负载生成器本身也可能成为瓶颈:它可能先耗尽 CPU 或连接,而身份验证错误或速率限制响应可能看起来像性能下降。重要的是在延迟目标内持续成功的请求,而不是短暂的峰值。

负载测试还应显示应用背后的服务是否能跟上。如果数据仓库开始排队或模型端点限流,应扩展该服务,而不是应用。

选择实例大小

Databricks Apps 提供三种大小:Medium(默认,最多 2 个 vCPU 和 6 GB)、Large(最多 4 个 vCPU,12 GB)和 XLarge(最多 12 个 vCPU,48 GB)。正确的选择取决于你的应用先达到哪个限制,因此从 Medium 开始,让负载测试告诉你:

负载测试显示的情况可能的原因应采取的措施
请求缓慢且应用 CPU 较低应用在等待其背后的某些东西,或者阻塞调用占用了 worker保持大小不变。先修复查询、数据仓库、连接池或阻塞调用,并检查每个进程的 CPU:一个核心满载可能看起来像整体 CPU 较低
CPU 接近限制,服务器运行多个 worker(例如,使用 Uvicorn 的 FastAPI)应用可以使用更多核心迁移到 Large
CPU 接近限制,单进程应用(Streamlit,或默认的 AppKit)性能分析显示工作绑定在一个核心上增加实例。更大的实例大小只有在应用能够使用额外核心时才有帮助,例如通过 worker 线程或原生库
内存接近限制大型数据集或模型保存在内存中为内存而升级:Large 是 Medium 的两倍,XLarge 在单个实例中提供 48 GB
恢复测试:当应用唯一的实例重启时,应用会宕机没有备用实例接管增加实例,而不是更大的实例大小

每个实例应运行多少个 worker?

Worker 是每个实例内的服务器进程。对于支持此模式的 Python 服务器,你在启动时设置数量(例如 uvicorn --workers 2),该数量按实例生效。

单个 worker 可能让 CPU 空闲,但额外的 worker 也会争抢 CPU 和内存。在我们负载测试指南的基准测试中,Medium 使用两个 worker 比使用四个表现更好,而 Large 的吞吐量约为 Medium 的 2.2 倍。请将文档中的起点(Medium 上两个 worker 或 Large 上八个)作为初步猜测:该基准测试模拟了 LLM 且受 CPU 限制,而主要等待模型调用的真实应用可能从额外 worker 中获得更多收益。请根据使用真实模型进行的自有负载测试来选择数量。

应用应运行多少个实例?

通过水平扩展,你可以自行设置实例数量。默认限制为每个应用五个实例,如果负载测试表明你需要更多,你的 Databricks 代表可以提高该限制。请根据预期峰值和增长来设定数量。实例数量变化时应用会继续提供服务,计算成本随实例数量线性增长。如果你要转换现有应用,请先在副本上试用。

为实现更高可用性,我们建议至少使用两个实例,并具备在失去一个实例后仍能处理峰值负载的足够容量:如果峰值需要两个实例,请运行三个。额外实例可防止实例故障,但不能防止应用背后服务中的问题。

为 Lakebase 规划连接预算

对于使用 OAuth 连接 Lakebase 的 Databricks App,我们建议从数据库标准(非 PgBouncer)连接字符串上的应用侧连接池开始。它会复用连接,并在打开新连接时获取新的 OAuth 凭据,正如Apps-to-Lakebase 教程中针对 Python 所示。请设置连接池超时,以便在请求截止时间之前为查询本身留出足够时间。

这些连接会累积,因此在添加实例或 worker 之前请先规划预算。如果每个 worker 一个连接池,上限为实例数 × 每实例 worker 数 × 连接池最大大小:五个实例、八个 worker、连接池最大值为十,最多可打开 400 个连接。加上其他客户端以及应用代表用户打开的任何连接,然后将总数与数据库文档中的限制进行比较,并为部署(新旧版本并行运行时)留出余量。

代表用户(OBO)查询需要身份容量。这意味着每个以自身身份查询 Lakebase 的用户都需要自己的 Postgres 角色,而 Lakebase 允许每个分支 500 个角色。对于更大的用户群,请尽早确定哪些查询真正需要按用户访问。添加实例不会改变此限制。在组级权限足够的情况下,为 Databricks 组分配一个 Postgres 角色可让其成员使用自己的 OAuth 令牌连接,因此一个角色即可覆盖整个组。

实现零停机部署和安全回滚

对于水平扩展的应用,Databricks 会先更新一个实例,成功后再更新其余实例,因此应用会继续提供服务。每次部署后仍值得检查应用是否健康以及会话是否恢复。将代码保存在 Git 中意味着你可以通过重新部署已知良好的标签或提交来回滚。在部署期间,新旧版本会并行运行,因此请保持数据库和检查点格式对两者都可读,并且在确定不会回滚之前,不要删除上一版本需要的字段。

运维:基于数据进行监控、诊断和修复

上线之后,工作就变成了一个循环:找出最慢的步骤,修复它,然后再次测量。这就是我们在生产环境中持续开启遥测的原因。在横向扩展的应用中,Logs 标签页的 Live 视图一次只显示一个实例;启用遥测后,App Telemetry Logs 会显示所有实例。

当某个环节变慢时,对请求进行端到端追踪(图 5)。

image5.png

图 5:请求的时间花在哪里以及主要瓶颈。

从症状入手

以下是我们最常见的症状,以及它们通常指向的问题:

症状排查位置可能原因应对措施
空闲期或部署后的首次请求缓慢Insights 标签页的 System metrics 以及应用遥测中的 traces冷启动:应用启动,或服务从空闲状态唤醒检查应用启动时间,以及 Lakebase 或 SQL warehouse 在空闲时是否会停止,并保持启动过程轻量
请求缓慢且应用 CPU 较低Insights 标签页的 System metrics 以及应用遥测中的 traces应用在等待其背后的某个环节,或阻塞调用占住了工作线程

在增加应用计算资源之前,先检查 traces 中是否存在查询、warehouse 或连接池延迟

对于单进程应用,请注意整体 CPU 较低仍可能意味着某个核心已满载

应用 CPU 或内存接近上限Insights 标签页的 System metrics应用本身是瓶颈,或它在内存中保留了过多数据对 CPU 密集型工作进行性能分析,将大型状态移出内存,然后参阅“选择实例大小”中的指导
等待数据库连接时超时应用日志和 Lakebase 指标连接池争用、长事务或数据库连接数限制检查连接使用情况和事务持续时间,然后在连接预算内调整连接池或数据库容量
用户在部署或扩缩容期间丢失工作或看到错误Insights 标签页的 System metrics无法重建的软状态,或未能在 15 秒内完成的关闭过程对重要工作设置检查点,使会话可在任意实例上重建,并用真实请求和流测试关闭过程

设置健康告警

我们建议将告警规划为设置工作的一部分:确定应用宕机时通知谁,并将 Apps API 中的 app_status.state 和 compute_status.state 接入你的监控工具。仅凭应用状态并不能证明用户可以正常工作,因此还要检查一个有代表性的用户旅程,包括其延迟和错误率。设置阈值时,要为部署期间的短暂状态变化留出余量。

总结

扩展 Databricks Apps 意味着要对整个请求路径进行容量规划,而不仅仅是应用服务器:设定延迟和峰值负载目标,将繁重工作交给专为此构建的服务,保持状态可恢复,并让负载测试和遥测来决定容量投向何处。在测量结果表明需要时再增加计算资源,而不是作为第一反应。

如需进一步了解,阅读如何使用 Databricks Apps 和 Lakebase 构建生产就绪的数据和 AI 应用。更多详情,请参阅文档中的 Databricks Apps 最佳实践 和 Databricks Apps 的日志记录与监控。关于按应用类型划分的架构模式,例如内部工具、AI copilot 和 agentic 工作流,请观看 Data + AI Summit 2026 上的 Building Better Apps 和 Stateful Apps for Thousands of Users。

常见问题

如何扩展 Databricks App?

分四个阶段扩展:设计、构建、测试与部署、运维。先设定延迟和峰值负载目标,保留应用计算资源用于服务 UI,并编写具有可恢复状态的异步代码。然后让负载测试和遥测数据决定是纵向扩展到更大的实例,还是横向扩展到更多实例。

我应该纵向扩展还是横向扩展我的 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 的横向扩展。

“探索 Databricks Apps” →

来源:Databricks Blog · databricks.com