跳到主要内容

多副本、高可用与并发故障排除

本指南旨在解决在多副本环境(如 Kubernetes、Docker Swarm)中部署 Open WebUI,或通过使用多 Worker (UVICORN_WORKERS > 1) 来提升并发处理能力时遇到的常见问题。

如果您是首次搭建扩容部署方案,建议从扩容 Open WebUI 指南开始,获取逐步的操作演示。

核心要求核对清单

在对具体错误进行排障之前,请确保您的部署满足以下针对多副本设置的绝对要求。遗漏其中任何一项都会导致系统不稳定、登录循环或数据丢失。

  1. 共享 Secret Key: 所有副本上的 WEBUI_SECRET_KEY 必须完全一致。
  2. 外部数据库:必须使用外部 PostgreSQL 数据库(参见 DATABASE_URL)。SQLite 不支持多实例部署。
  3. 针对 WebSocket 使用 Redis: 必须配置 ENABLE_WEBSOCKET_SUPPORT=TrueWEBSOCKET_MANAGER=redis,并配以有效的 WEBSOCKET_REDIS_URL
  4. 共享存储: 共享的持久化卷(如果可能的话,推荐使用 RWX / ReadWriteMany,或者确保所有副本都映射到同一个底层的 data/ 存储上)对于 RAG(上传文件/向量)以及生成的图像至关重要。
  5. 外部向量数据库(必需): 默认的 ChromaDB 使用本地基于 SQLite 的 PersistentClient,这在多 Worker 或多副本部署中是不安全的。SQLite 连接并不是 fork 安全的,多个进程并发写入会立即使 Worker 进程崩溃。您必须通过 VECTOR_DB 选用专用的外部向量数据库(例如 PGVector, MariaDB Vector, Milvus, Qdrant),或者将 ChromaDB 作为独立的 HTTP 服务器运行。
  6. 数据库会话共享(可选): 对于资源充足的 PostgreSQL 部署,可以考虑启用 DATABASE_ENABLE_SESSION_SHARING=True 以提升高并发下的性能。

常见问题

1. 登录循环 / 401 Unauthorized(未授权)错误

症状:

  • 您成功登录了系统,但点击下一步操作时便被登出。
  • 登录后,浏览器控制台中立即显示 "Unauthorized" 或 "401" 错误。
  • 日志中出现 "Error decrypting tokens"。

原因: 各个副本正在使用不同的 WEBUI_SECRET_KEY。当副本 A 签发了会话 Token (JWT) 时,副本 B 会将其拒绝,因为它无法使用自己不同的密钥去验证该 Token 的签名。

解决方案: 在所有后端副本上,将 WEBUI_SECRET_KEY 环境变量设置为相同的强随机字符串。

# Kubernetes/Compose 中的配置示例
env:
  - name: WEBUI_SECRET_KEY
    value: "your-super-secure-static-key-here"

2. WebSocket 403 错误 / 连接失败

症状:

  • 对话停止响应或无限挂起。
  • 浏览器控制台显示 WebSocket connection failed: 403 ForbiddenConnection closed
  • 日志显示 engineio.server: https://your-domain.com is not an accepted origin

原因:

  • CORS:负载均衡器或 Ingress 域名与允许的 Origin 域不匹配。
  • 缺失 Redis:WebSocket 退回到了内存(in-memory)模式,因此副本 A 上的事件(例如 LLM 生成完毕)无法广播到连接在副本 B 上的用户端。

解决方案:

  1. 配置 CORS:确保 CORS_ALLOW_ORIGIN 包含了您的公开域名以及 http/https 协议变体。

    如果您看到类似于 engineio.base_server:_log_error_once:354 - https://yourdomain.com is not an accepted origin 的日志,说明您必须更新该变量。它接受以英文分号分隔的允许 Origin 列表。

    示例:

    CORS_ALLOW_ORIGIN="https://chat.yourdomain.com;http://chat.yourdomain.com;https://yourhostname;http://localhost:3000"

    添加用户可能用于访问您 Open WebUI 实例的所有有效 IP、域名和主机名。

  2. 为 WebSocket 启用 Redis: 确保在所有副本上均设置了以下变量:

    ENABLE_WEBSOCKET_SUPPORT=True
    WEBSOCKET_MANAGER=redis
    WEBSOCKET_REDIS_URL=redis://your-redis-host:6379/0

3. "Model Not Found"(模型未找到)或配置不匹配

症状:

  • 您在管理员界面中启用了某个模型或更改了某项设置,但其他用户(或您在刷新页面后)看不到该更改。
  • 对话交互间歇性失败并报错 "Model not found"。

原因:

  • 配置同步问题:副本之间未能同步。Open WebUI 使用 Redis Pub/Sub(发布/订阅)机制将配置更改(如切换模型启用状态)广播至所有其他实例。
  • 缺失 Redis:如果没有配置 REDIS_URL,配置更改仅会停留在执行更改的单台实例本地。

解决方案: 设置 REDIS_URL 指向您共享的 Redis 实例。这会启用 Pub/Sub 机制以进行实时的配置同步。

REDIS_URL=redis://your-redis-host:6379/0

4. 数据库损坏 / "Locked"(锁定)错误

症状:

  • 日志中显示 database is locked 或严重的 SQL 错误。
  • 在一个实例上保存的数据在另一个实例上消失了。
  • 在经过简短的热身运行后,每个请求都会触发 sqlalchemy.exc.TimeoutError: QueuePool limit of size N overflow M reached, connection timed out, timeout 30.00 报错,而这不仅仅发生在并发峰值期。
  • /api/config/api/v1/chats/?page=1、OIDC 回调全部停滞,耗时 10 秒至数分钟不等。
  • 记录了 PRAGMA journal_mode=WAL 开始运行但永远无法完成。
  • 在 0.8.x → 0.9.x 升级后,在部署没有做任何其他更改的情况下,问题突然出现。

原因: 在多副本部署中使用了 SQLite——或者单个副本的 webui.db 存放在网络文件系统上。SQLite 是一个基于文件的数据库,它的文件锁在 NFS / CIFS / CephFS / Azure Files / 任何网络 PVC 上都无法可靠地工作(这是 SQLite 官方自身的立场,而非 Open WebUI 的政策)。伴随着 0.9.0 引入的异步后端,由于缓慢的网络 fsync 操作会长时间占用每个连接池中的连接,导致每个请求都使连接池饱和,这直接从“运行慢和偶尔锁定”演变成了彻底的故障模式。

欲了解完整的底层原理(fsync 延迟 + 异步并发 + 连接池饱和 + NFS 上的 WAL mmap),请参阅 性能 → 磁盘 I/O 延迟

解决方案 — 按正确程度从高到低排列:

  1. 迁移至 PostgreSQL(强烈推荐,多副本部署的必需项):

    DATABASE_URL=postgresql+asyncpg://user:password@postgres-host:5432/openwebui

    对于 Kubernetes / Docker Swarm 部署而言,这实际上是强制性的。PostgreSQL 会针对其自身的本地存储管理自身的 I/O,因此网络 fsync 引发的病态问题会彻底消失。详见 扩容 → 步骤 1 了解完整的迁移步骤。

  2. 如果您是单实例且能移动数据库文件:请将 webui.db 放在本地挂载的 SSD/NVMe(主机绑定挂载 host bind mount、节点本地卷 node-local volume、临时磁盘 ephemeral disk)上——而不要放在您用于上传和 RAG 文件的 NFS/Ceph/EFS 挂载点上。您将共享存储用于 /app/backend/data 是没问题的,唯独 SQLite 必须被移出来。

  3. 请勿单纯地增加 DATABASE_POOL_SIZE 更大的连接池无法解决缓慢的 fsync;它只会让更多并发的缓慢 fsync 作用于同一个缓慢的存储上,从而把崩溃点往后推迟几秒而已。这只是治标不治本。

  4. 仅作为临时的损害控制(针对您即将进行迁移的部署):

    DATABASE_POOL_SIZE=1
    DATABASE_SQLITE_PRAGMA_BUSY_TIMEOUT=30000

    这会将连接序列化为单个异步连接,牺牲并发性以换取稳定性。长期来看并不支持此做法。 如果后台调度器的周期性轮询是把系统推向崩溃边缘的最后一根稻草,您还可以考虑设置 ENABLE_AUTOMATIONS=false

5. 上传的文件或 RAG 知识库无法访问

症状:

  • 您在其中一台实例上上传了文件(用于 RAG),但模型随后无法找到它。
  • 生成的图像显示为损坏的链接。

原因: /app/backend/data 目录在各个副本之间并未共享或数据不一致。如果用户 A 向副本 1 上传了文件,而接下来的请求路由到了副本 2,则副本 2 的磁盘上将物理性地缺失该文件。

解决方案:

  • Kubernetes:如果您的存储提供商支持,请使用 ReadWriteMany (RWX) 访问模式的 PersistentVolumeClaim(例如 NFS、CephFS、AWS EFS)。
  • Docker Swarm/Compose:在所有容器上将共享卷(如 NFS 挂载点)挂载到 /app/backend/data 路径下。

6. 文档上传期间 Worker 崩溃(ChromaDB + 多 Worker)

症状:

  • 日志显示以下序列,且全部发生在同一秒内:
    save_docs_to_vector_db:1619 - adding to collection file-id
    INFO: Waiting for child process [pid]
    INFO: Child process [pid] died
  • 在 RAG 文档摄取(ingestion)过程中,Worker 进程立即死掉。
  • 崩溃是瞬间发生的(非超时)。

原因: 默认的 ChromaDB 配置使用的是由 SQLite 支持的本地 PersistentClient。当 uvicorn fork 了多个 Worker (UVICORN_WORKERS > 1) 时,每个 Worker 进程都会继承同一个 SQLite 数据库连接的副本——且全部指向磁盘上的同一个文件(data/vector_db/)。

当两个 Worker 尝试同时写入集合时(例如在文档上传过程中),SQLite 的文件级锁在 fork 的进程之间会失效。这会导致数据库锁定错误,或者因继承自 fork() 调用的损坏内部状态而引发段错误(segfault),从而立即使 Worker 进程崩溃死掉。

这是一个众所周知的 SQLite 限制:打开的数据库连接绝不能跨 fork() 传递。

解决方案: 在多 Worker 部署中,您必须停止使用默认的本地 ChromaDB。请选择以下选项之一:

选项对应更改折中考量
仅保持 1 个 Worker设置 UVICORN_WORKERS=1(默认值)最简单,但会限制并发能力
使用 ChromaDB HTTP 模式设置 CHROMA_HTTP_HOST / CHROMA_HTTP_PORT 指向独立的 Chroma 服务器每个 Worker 通过 HTTP 连接而非 SQLite —— 完全符合 fork 安全
切换向量数据库VECTOR_DB 设置为 pgvectormariadb-vectormilvusqdrant这些属于客户端-服务器型数据库,天然支持多进程安全

推荐的修复方案 —— 将 ChromaDB 作为独立服务器运行:

# 独立运行 chroma 服务器
chroma run --host 0.0.0.0 --port 8000 --path /data/vector_db

# 随后为 Open WebUI 配置以下环境变量
CHROMA_HTTP_HOST=localhost
CHROMA_HTTP_PORT=8000
UVICORN_WORKERS=4

7. 云端与本地 Kubernetes 相比性能缓慢

症状:

  • Open WebUI 在本地运行良好,但在部署到云服务商(AKS、EKS、GKE)时会遇到严重的性能下降或超时。
  • 尽管分配了充足的资源,但在并发负载下性能依然急剧下降。

原因: 这通常是由云环境固有的基础设施延迟(连接数据库的网络延迟,或针对 SQLite 的磁盘 I/O 延迟)引起的,这比本地的 NVMe/SSD 存储和本地网络延迟要高得多。

解决方案: 请参阅性能指南中的 云端基础设施延迟 章节,了解详细的诊断与缓解策略。

如果您需要更多关于性能提升的技巧,请查阅完整的 优化与性能指南

8. 优化数据库性能

对于分配了充足资源的 PostgreSQL 部署,可以考虑进行以下优化:

数据库会话共享

启用会话共享可以提升高并发场景下的性能:

DATABASE_ENABLE_SESSION_SHARING=true

详情请参见 DATABASE_ENABLE_SESSION_SHARING

连接池大小调整

如果您在高并发下遇到 QueuePool limit reached 错误或连接超时,请调高连接池大小:

DATABASE_POOL_SIZE=15 (或更高)
DATABASE_POOL_MAX_OVERFLOW=20 (or更高)

重要提示: 两者之和 (DATABASE_POOL_SIZE + DATABASE_POOL_MAX_OVERFLOW) 应当远低于您数据库的 max_connections 限制。PostgreSQL 默认的最大连接数为 100,因此每个 Open WebUI 实例的池大小之和应当保持在 50-80 以下,以便为其他客户端和维护操作预留空间。

连接池大小会随着并发数成倍增长

每个 Open WebUI 进程都维护着自己独立的连接池。 这不仅适用于多个副本(Kubernetes pods, Docker Swarm replicas),也适用于每个副本内的多个 Uvicorn Worker。

实际可能建立的最大数据库连接数为:

最大连接数 = (DATABASE_POOL_SIZE + DATABASE_POOL_MAX_OVERFLOW) × 总进程数

其中 总进程数 = 副本数量 × 每个副本的 UVICORN_WORKERS 数量

例如,当设置 DATABASE_POOL_SIZE=15DATABASE_POOL_MAX_OVERFLOW=20,部署 3 个副本且每个副本运行 2 个 Worker 时,您可能最多打开 210 个连接 (35 × 6 个进程)。

详情请参见 DATABASE_POOL_SIZE。有关包含缓存、会话共享和内容提取微调在内的全面数据库优化,请参见 优化与性能 指南。

9. Function/工具依赖安装崩溃

症状:

  • Worker 在启动或首次加载某个 Function/工具时因 AssertionError 崩溃。
  • 日志显示 pip 锁定错误,或有多个 pip 进程在同时竞争。

原因: 当 Function 或工具在其 Frontmatter 中指定了 requirements 时,Open WebUI 会在运行时执行 pip install。在多 Worker 或多副本部署中,每个进程都会尝试独立安装,导致 pip 的内部锁检测到冲突并崩溃。

解决方案: 设置 ENABLE_PIP_INSTALL_FRONTMATTER_REQUIREMENTS=False 以完全禁用运行时的 pip 安装。然后,请在镜像构建阶段预先安装所有必需的包:

FROM ghcr.io/open-webui/open-webui:main

RUN pip install --no-cache-dir python-docx requests beautifulsoup4

运行时的 requirements 依赖安装仅适用于单 Worker 的开发环境或家庭实验室(homelab)环境。

有关更多细节,请参见工具文档中的外部依赖包章节。


部署最佳实践

升级与迁移

关键:避免并发迁移

在升级 Open WebUI 版本时,务必确保仅有单个进程在执行数据库迁移。

数据库迁移会在启动时自动运行。如果在升级新版本时,多个副本(或单个容器内的多个 Worker)同时启动,它们可能会尝试并发运行迁移,从而可能导致竞态条件或数据库 Schema 结构损坏。

安全升级流程:

在多副本环境中,有两种安全处理迁移的方法:

  1. 指定其中一个 Pod/副本作为迁移的“主(master)”节点。
  2. 在该主 Pod 上设置 ENABLE_DB_MIGRATIONS=True(默认值)。
  3. 在所有其他 Pod 上设置 ENABLE_DB_MIGRATIONS=False
  4. 在升级更新时,主 Pod 将负责处理数据库 Schema 的更新,而其他 Pod 则会跳过迁移步骤。

选项 2:在更新期间缩减实例数量

  1. 缩减实例:将副本数设为 1(并确保 UVICORN_WORKERS=1)。
  2. 升级镜像:更新镜像或版本。
  3. 等待就绪检查:等待这单个实例完全启动并完成迁移。
  4. 扩容:将副本数量重新调回到您期望的值。

会话亲和性(粘性会话)

尽管通过配置 Redis 后 Open WebUI 被设计为无状态的,但在您的负载均衡器 / Ingress 级别启用会话亲和性(Session Affinity / 粘性会话)可以提升性能,并减少 WebSocket 连接偶尔出现的抖动。

  • Nginx Ingressnginx.ingress.kubernetes.io/affinity: "cookie"
  • AWS ALB:启用目标组粘性(Target Group Stickiness)。

This content is for informational purposes only and does not constitute a warranty, guarantee, or contractual commitment. Open WebUI is provided "as is." See your license for applicable terms.