issue: chat content search on PostgreSQL scans TOASTed chat.chat blobs, 5-15 s per query

当 Open WebUI 使用 PostgreSQL 后端且 chat.chat 的 JSON 数据被 TOAST 存储后,侧边栏聊天内容搜索( /api/v1/chats/search )会做逐元素的 JSON EXISTS 匹配,导致每次查询触发大量 TOAST detoast,单次 5–15

快速结论:当 Open WebUI 使用 PostgreSQL 后端且 chat.chat 的 JSON 数据被 TOAST 存储后,侧边栏聊天内容搜索(/api/v1/chats/search)会做逐元素的 JSON EXISTS 匹配,导致每次查询触发大量 TOAST detoast,单次 5–15 秒。优先排查 chat.chat 表体积与查询计划(是否顺序扫描 + 逐行 detoast)。

适用环境:Open WebUI v0.11.4(dev @ c9d3ae999 同样复现);Docker 安装;Linux;PostgreSQL 17 后端;Ollama 版本 n/a(与问题无关)。

最快修复方案:暂无确认的一步修复方案。Issue 中给出的两阶段查询 + chat_search_content 纯文本列 + GIN trigram 索引方案属于作者提出的修复方向(对应 PR #32268),但 Issue 本身未确认已合并或已在发行版中验证,可优先关注该 PR 的合并状态。

注意事项:该方案依赖 pg_trgm 扩展与索引创建权限;不允许创建索引的环境会自动回退为旧行为,性能问题不会改善。SQLite 部署不受影响。基准数据来自作者的生产库副本(2164 条 chat、含 TOAST 约 490 MB),你的数据分布不同,耗时与收益可能有差异。

问题场景

在 Docker + PostgreSQL 17 后端运行 Open WebUI v0.11.4(或当前 dev 分支),聊天记录积累到数千条、其中包含较长会话后,在侧边栏用关键词搜索聊天内容。数据量达到 chat.chat 表约 490 MB(含 TOAST)、3.3 万条 chat_message 时,每次搜索耗时 5–16 秒,侧边栏搜索基本不可用。问题出在后端查询路径(backend/open_webui/models/chats.py 中的 chat_search_message_content_match_sql、get_chats_by_user_id_and_search_text),与浏览器无关。

报错原文

issue: chat content search on PostgreSQL scans TOASTed chat.chat blobs, 5-15 s per query

the content predicate evaluates correlated per-element EXISTS JSON conditions directly against Chat.chat
the PostgreSQL planner cannot price TOAST detoasting
the chat heap is ~1 MB while its TOAST table holds hundreds of MB
it chooses plans that detoast hundreds of rows per query
chat::text NOT LIKE '%%' null-byte safety filter alone costs ~1.7 s per query

EXPLAIN ANALYZE shows sequential scans with per-row detoast of the JSON blob

原因分析

最可能的原因是 PostgreSQL 查询规划器对 TOAST detoast 成本“无感知”。内容匹配条件(history + legacy 分支)以逐元素关联 EXISTS 的 JSON 条件直接作用于 Chat.chat。由于 chat 主堆只有约 1 MB,而对应 TOAST 表存有数百 MB,规划器在比较 user_id 位图/过滤(成本约 125)与基于 blob 的 trigram 索引扫描(成本约 520)时,无法估算实际 detoast 成本,最终选择每次 detoast 数百行的执行计划。基表上任何索引都无法解决这一步,因为被错误定价的是 detoast 本身。仅 chat::text NOT LIKE '%%' 这个空字节安全过滤在我的数据上就约占 1.7 秒/查询。

环境排查

  • 确认 Open WebUI 版本:v0.11.4 或 dev @ c9d3ae999(Issue 确认两条路径字节级一致)。
  • 确认数据库:PostgreSQL 17,且为 Docker 部署;SQLite 部署不涉及该路径。
  • 确认数据规模:chat 表含 TOAST 体积、chat 条数、chat_message 行数(Issue 数据为 490 MB / 2164 条 / 33k 行)。
  • 确认数据库参数:Issue 基准为 stock PostgreSQL 17 设置(random_page_cost=4、shared_buffers=128MB)。
  • 确认是否具备 pg_trgm 扩展与创建索引的权限(影响修复方案是否能落地)。
  • Ollama 版本与前端浏览器不影响该问题。

解决步骤

  1. 先用 EXPLAIN ANALYZE 跑一遍实际搜索查询,确认是否出现顺序扫描以及对 chat.chat 的逐行 detoast,作为基线证据。
  2. 如果只是想在当前版本缓解,先缩小数据集或归档长会话,减少 TOAST 总量——这只降低单次查询扫描量,并非结构性修复。
  3. 关注/试用 Issue 作者提出的两阶段方案(对应 PR #32268):阶段 A 只匹配标题与 chat_message 表(不 detoast),通常已填满分页;阶段 B 仅在阶段 A 结果不足时执行,通过新增的触发器维护、已回填的纯文本列 chat_search_content 搭配 GIN trigram 索引做预筛选,再用原始逐元素 EXISTS 条件精确确认。
  4. 对 trigram 索引无法服务的短 CJK 模式,方案改为在该纯文本列上做内联预筛选。
  5. 确认你的 PostgreSQL 允许安装 pg_trgm 并创建索引;若不允许,安装逻辑会自动保留旧行为。

验证方法

用相同的搜索词对修改前后跑对比,确认结果集与旧查询完全一致,并观察耗时变化。作者在完整生产数据副本、stock PostgreSQL 17 设置下测得:proxy 4.5 s → 1.9 s、proxy server 14.1 s → 3.4 s、uptime 8.9 s → 3.0 s、opnsense 5.0 s → 0.09 s、netbird 4.6 s → 0.10 s、电池 5.6 s → 1.0 s、电池 传感器 7.5 s → 0.7 s。请以你自己数据上的实测结果为准。

参考来源

open-webui/open-webui #32266

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 28997

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注