资料越存越多,AI材料柜怎么保证检索不会越来越慢?
资料越存越多,AI材料柜怎么保证检索不会越来越慢?
这是一篇关于"AI材料柜"这类本地知识库系统的工程实践文章。核心问题只有一个:当你的资料从几十篇涨到几千篇、从几 MB 涨到几百 MB,检索为什么不能越来越慢?
一、问题:知识库的"规模诅咒"
几乎每个知识管理工具都会遇到同一个尴尬:刚建库的时候检索飞快,输入几个关键词,结果秒出。可随着资料越存越多,一切开始不对劲——搜索开始转圈、结果变得迟钝、甚至并发用的时候整个应用都卡。
这不是某个产品做得不够好,而是知识库天然面对的三个硬约束:
第一,检索响应时间。 海量文档下,实时检索要求极高。如果每次查询都要把几万份文档"翻一遍",那响应时间必然随资料量线性恶化。
第二,内存占用控制。 现代检索依赖向量化表示——把文档内容变成高维向量。向量化表示需要大量内存空间,文档越多,这部分开销越失控。
第三,并发处理能力。 多人同时使用、多会话同时写入时,系统要能扛住。而单库单闸门的架构,恰恰会在并发时暴露问题。
所以,"越存越多"确实会拖慢检索——如果不做任何设计上的应对。AI 材料柜的做法,是把这个问题拆成三层分别解决:查得准、建得省、写不堵。下面逐一展开。
二、检索本体:混合检索 + 分层缓存
1. 全文索引(FTS):让精确匹配"按图索骥"
传统的暴力扫描是"逐字比对",而全文索引(FTS5)的思路是倒排:先对文档切片建索引,把"词 → 出现在哪些 chunk"的关系预先记下来。查询时直接在索引上查找,而不是扫描全文。
这就像给一本书编目录而不是每次从头翻。无论书多厚,查一个词的开销都差不多——索引把"读全文"变成了"查目录"。
2. 向量检索:让语义相近的内容也能被找到
关键词匹配有一个致命短板:它不认识同义词。用户搜"人工智能应用",如果文档里只写了"机器学习实践",字面匹配是找不到的。
向量检索解决的就是这个问题。文档内容被 embedding 成高维向量,查询时计算语义相似度(如余弦相似度),于是"语义相关"的文档也能被召回——哪怕一个关键词都没对上。
3. 混合检索:精度与召回两手抓
单用全文索引,召回不足;单用向量检索,可能召回一堆"意思相近但不够准确"的结果。所以实际系统采用混合检索策略:关键词匹配保精度、向量匹配保召回,再综合语义相似度、关键词匹配度、文档质量等多个因素做结果排序。
这套组合让系统同时满足两类查询:精确的技术术语能命中,模糊的概念描述也能找到相关材料。
4. 分层缓存:高频查询不再重复计算
检索再快,也快不过"不检索"。系统采用分层缓存机制,把高频查询的结果缓存起来,重复查询直接命中缓存。配合增量索引更新,避免每次文档变化都全量重建索引的开销。
这一层回答的是:检索本身怎么做得又快又准。 但还有一个更隐蔽的性能杀手——索引的维护成本。
三、索引写入:文档规模自适应重索引
1. 固定 debounce 的代价
文档写盘后,系统需要重新切块(chunk)、重新 embedding,把新内容"消化"进检索库。早期实现是固定的 2 秒 debounce:停手 2 秒就全量重建一次索引。
对几十 KB 的小文件,这没问题。但当一份几百 KB 的大文档在连续编辑时,问题就来了:
- 每次停手 2 秒就触发一次全量 import + embed;
- 大文件的向量化非常耗时,CPU 占用高、耗时长;
- 而大多数情况下,检索新鲜度收益和这个成本完全不成比例。
一句话:固定 debounce 是"以不变应万变",而资料规模是万变的。
2. 按字节分段:小文件勤快,大文件偷懒
解决办法是让重建策略跟着文档规模走。系统按 content.md 的字节数分段,查表决定 debounce 延迟:
| 文档大小 | 重建延迟(base_delay) | 最短重建间隔 | 最长容忍陈旧(staleness) |
|---|---|---|---|
| ≤ 50 KB | 2 秒 | 0 | 30 秒 |
| ≤ 200 KB | 10 秒 | 0 | 30 秒 |
| ≤ 1 MB | 45 秒 | 30 秒 | 120 秒 |
| 更大 | 90 秒 | 60 秒 | 300 秒 |
小文件保持"改完很快可搜"的体验;大文件不再频繁全量重建,把宝贵的 embedding 成本花在刀刃上。
3. soft / hard 脏检测:小改动不值得付全量成本
光按大小调延迟还不够。一次只改了 3 个字,和一次重写了一半,显然不该用同一个策略。系统引入脏程度判定:
- hard dirty(脏得够):无上次索引记录,或内容变化量超过门槛(如 ≥ 512 字节且 ≥ 0.2%)→ 走经典 trailing debounce,停手满
base_delay再重建; - soft dirty(小改动):变化很小 → 不按
base_delay重建,只挂一个"最晚重建时间"(staleness 截止),一直小改就一直拖到上限才建。
这个设计有个反直觉但正确的细节:soft 不会重置重建倒计时。大文件碎改时,每次小修改都不推倒重来,而是统一等到 staleness 保底截止点。只有中途变成 hard 修改,才从那一刻重新按 base_delay 计时。
4. 冷却与让路:别在忙的时候添乱
- min_interval 冷却:两次成功重建之间设最短间隔,防止"刚建完又立刻要建";
- flush 让路:用户显式要求落盘/立即索引时,无视延迟、立即重建;
- 对话让路:聊天进行中,本文件的全量 embed 推迟到对话结束后再调度,避免抢 CPU。
这套"规模自适应重索引"的直觉可以概括成一句话:小文件勤快、大文件懒、关键时候立刻建、忙的时候让路。 它把"索引写入成本"从失控变成了可控。
四、架构解耦:检索库与对话库拆分
1. 单库混表 + 整库写闸门:对话被索引"卡脖子"
前两层的优化方向是"减少重建次数",但还有一类问题不是次数多,而是写锁竞争。
在早期架构里,所有表都在同一个 SQLite 库中:检索表(documents、chunks、chunk_embeddings、chunks_fts)和对话表(chat_history、会话状态、设置)混在一起。为了防止 database is locked,系统加了一把整库写闸门:
- 索引重建时:长时间持有闸门(import + 全量 embed);
- 每条对话历史落库时:也要抢同一把闸门。
于是出现了一个魔幻场景:你在跟 AI 对话,AI 正在后台重建某个大文件的索引,你的每条消息落库都得排队等它释放。 即使开了 WAL,进程级互斥依然让对话被索引"卡脖子"。
更糟的是开流阶段的硬等待:对话开始时,如果本文件正在 reindex,系统会阻塞开流、发送"正在等待文件索引…",最长等 120 秒,超时报 INDEXING_TIMEOUT。这个等待的动机其实是防落库撞索引写,而不是"检索必须最新"。
2. 双库拆分:各写各的,互不干扰
解决方案很朴素也很彻底:把库拆成两个文件。
{CABINET_ROOT}/db/
cabinet.db # 检索库:documents / chunks / chunk_embeddings / chunks_fts
app.db # 应用库:chat_history / 会话状态 / 设置 / 草稿状态
- 检索侧(import / embed / search)只写
cabinet.db; - 对话/UI/设置/草稿等短写只写
app.db; - 两者通过
obj_id字符串关联,无跨库外键。
拆库之后,chat 写入 app.db,与检索库的 reindex 不再有写锁竞争:
| 场景 | 拆库前 | 拆库后 |
|---|---|---|
| reindex 进行中发消息 | 可能卡住至闸门释放 | chat 写入 app.db,不排队 |
| 多会话写 history | 与 reindex 争闸门 | 仅 app 库内短事务 |
| 开聊时本文件正在索引 | 阻塞开流,可能 INDEXING_TIMEOUT | 立即进入对话 |
于是,开流时的 indexing_wait 硬等待也退役了——它和闸门同源,都是为了防落库撞索引写,既然写路径已经物理隔离,等待就没有存在的理由了。对话期间的 defer reindex(聊着时推迟本文件 embed)依然保留,但那属于 CPU 调度层面的取舍,不再是落库互斥。
3. 迁移:一次性的平滑拆分
拆分不是重建系统。旧单库通过启动时的一次性迁移完成:ATTACH 旧库 → 拷贝应用相关表到新库 → 校验 → 删除已迁表 → 备份。两个库各自维护自己的迁移账本(migrations_rag / migrations_app),后续演进互不牵连。
这一层回答的是:写入路径怎么彻底不堵。 检索和对话从"抢一把锁"变成"各写各的",性能问题在架构层面被直接消灭。
五、总结:让"慢"在设计层面不发生
回头看,AI 材料柜对抗"规模诅咒"的手段可以归纳为三层:
- 查得准:全文索引(FTS)+ 向量检索 + 混合排序 + 分层缓存,让单次查询的开销不随资料量线性增长;
- 建得省:按文档规模自适应 debounce、soft/hard 脏检测、staleness 保底、冷却期与让路机制,让索引维护成本可控;
- 写不堵:检索库与对话库物理拆分,去掉整库写闸门和开流硬等待,让写入路径彻底并行。
这三点其实是一套完整的思路:检索性能的问题,不能只在"查询"这一个环节解决,而要在存储、索引、写入、架构的每一层同时设防。 当资料量增长时,系统不是靠"扛",而是靠设计让慢根本不发生。
如果你也在做自己的知识库、RAG 系统或者任何"越存越多"的检索应用,这三层手段——尤其是"按规模自适应"和"读写物理隔离"这两点——非常值得抄作业。
资料越多,检索不该越慢;真正优秀的检索系统,应该让"快"成为一种可以随着规模扩展而维持的能力。
浙公网安备 33010602011771号