1. 为什么我最终放弃了向量数据库一个搜索系统的现实选择做搜索引擎的人这两年大概都被问过一个灵魂问题你上向量数据库了吗说实话最早拿到这个项目需求时我也按“标准答案”走过——接一个嵌入模型把文档全部向量化然后搞个近似最近邻检索看起来又潮又稳。但真正在数据量、实时性和成本三重压力下跑过一段时间后我彻底改变了思路。Sirchmunk这个项目最初的定位是一个面向垂直领域的内容检索系统。这个垂直领域的特点是文档数量不是互联网级别但查询模式极其复杂——用户会用各种口语化的说法去搜索同一类东西而且新内容每天都在进来。用向量数据库一开始确实很爽嵌入、召回、排序流程顺畅。可问题也随之而来千亿级参数模型跑出来的向量不是白来的每个文档都要过一遍嵌入模型每天增量更新还要重新计算商业化向量数据库的按量计费在百万文档级别还勉强能忍但到了千万级、上亿级成本就是一笔让人头疼的开销。更重要的是向量检索是个黑盒用户搜不到东西时你根本没法解释到底哪个环节出了问题。真正让我下决心推倒重来的是一个很简单的场景。系统里存了一批行业报告用户想搜“今年第二季度的市场份额变化”。传统的词法检索可以轻松命中“市场份额”“第二季度”这些关键词但向量检索只按语义相似度找邻居如果不做特别处理匹配到的文档可能语义相近但根本不是用户要的那一篇。调试一个这样的个例就要花掉半天时间因为我得去查向量距离是怎么算的嵌入模型为什么把这几个文档拉到了一起。这太痛苦了。于是我开始思考一个看起来有点“复古”的问题有没有一种方案不需要向量数据库也能实现语义级别的搜索同时还能让系统越用越聪明这个问题的答案就是Sirchmunk今天这版架构的核心——用传统全文索引导出的结构化语义特征加上一套基于用户行为的自反馈循环让系统在运行中持续进化。这篇文章我会把整个架构设计拆开讲清楚包括每个部件为什么存在、数据怎么流转、进化机制怎么落地以及这套方案踩过的坑和已经明确的边界。如果你也在做中小规模的搜索系统或者被向量数据库的成本和黑盒问题困扰这版思路应该能给你一些直接的参考。即便你已经在用向量检索这套架构里的反馈闭环设计也可以单独拿出来借鉴。2. Sirchmunk的架构总览四层模型与两条数据链在详细展开之前我先给出一张全局蓝图——注意这里没有流程图我用文字把关系描述清楚你可以在脑内建模。2.1 四个层次各自的职责我把整个系统拆成四个大的层次每一层只做自己那一摊事层级之间通过明确的数据接口通信。第一层是采集与清洗层。它负责从多个内容源拉取原始文档然后做去重、格式统一、正文抽取、语言检测这些基础工作。输出是一个干净的结构化文档对象字段包括标题、正文、发布时间、来源、标签等。这套系统里的“文档”比你想象的要“瘦”我们不保留网页脚本、导航之类的噪音只要对检索有意义的正文信息。第二层是存储与索引层。原始文档存档在对象存储里同时写入一个核心的全文索引——我用的是Lucene体系。索引在这里不只是用来做关键词匹配的它同时是后面几张“衍生表”的物化基础。除了索引这一层还维护了一张实体关系图和一个标签倒排表。实体关系图记录的是文档中提到的人、机构、产品、概念之间的关联关系标签倒排表则是一个“标签指向哪些文档”的映射表。这两个东西都不是靠外部图数据库而是直接建立在索引文件和排序表之上查询时合并结果。第三层是检索与排序层。所有面向用户的查询都经过这一层处理。它有四个子模块查询解析模块、召回模块、重排序模块、日志记录模块。查询解析模块负责把用户输入的自然语言切割成可检索的词元、短语和标签意图召回模块先跑经典的关键词检索再并行做标签匹配和实体邻域扩展重排序模块把多路召回的结果用一套加权公式揉在一起日志记录模块把每次查询的完整上下文记录下来——包括解析结果、召回集、排序结果、用户最终点击了什么。第四层是进化层。这是Sirchmunk和普通搜索引擎最不一样的地方。它定期扫描行为日志从中挖掘新的标签词、高频查询模式、效果不佳的排序规则然后自动更新标签倒排表、实体关系权重和查询重写规则。这层没有做成在线实时计算而是一个批量进化的离线工作流因为实时计算对基础设施要求太高而且很多反馈信号本身就有噪音等积累到一定数量再做统计更有意义。2.2 两条数据链检索链和进化链整个系统的数据流其实只有两条链。第一条叫检索链是用户查询进来的路径。Query进入查询解析模块产出词元和标签召回模块在索引和标签表里各拉一批候选重排序模块融合多路结果并输出Top列表列表渲染给用户的同时用户操作行为被写成日志。这条链的延迟要求很高核心检索部分必须控制在几十毫秒以内所以我尽量不在实时路径上做重量级计算。第二条叫进化链是反馈驱动的路径。行为日志批量汇入进化层进化层做统计分析生成或更新标签词典、查询重写规则、实体关系权重这些产物被写回索引层供下一次检索链使用。进化链是异步、离线、批量的它不追求实时性只追求“每一次更新都比上一次更准”。为什么要把进化做成离线的这里有一个工程上的现实在线自进化意味着每次检索都要等待一个额外的在线学习动作这在延迟上和稳定性上都是灾难。离线批量更新虽然“慢半拍”但可以充分利用夜间空闲的计算资源而且一次批量更新可以同时解决上千条查询模式的优化性价比远高于在线逐条更新。3. 不依赖向量数据库靠什么做出语义效果这一节回答一个核心质疑你说了半天不用向量数据库那语义匹配怎么办总不能用“精准匹配关键词”糊弄人吧当然不是。Sirchmunk的语义能力来自三个非向量部件标签倒排表、实体关系图、查询重写规则。3.1 标签倒排表把“语义”显式化为可检索的结构向量检索做的本质上是把“语义相似”映射成“向量近邻”。Sirchmunk不搞向量那它把语义映射成了什么映射成了“标签”。每一个文档在入库时会通过一个规则引擎打上若干标签。规则引擎的输入是文档的标题、正文和初始元信息输出是一组标签词及对应的置信度。举例来说一篇标题为“2025年Q2智能手机市场分析报告”的文档规则引擎会打这些标签智能手机、市场分析、季度报告、2025Q2、行业数据。这些标签被写入标签倒排表——这是一个形如“标签词→文档ID列表”的映射表。检索时如果用户的查询解析出一组标签系统直接在表里做交集和并集运算效率比向量近似检索还快。但问题来了规则引擎凭什么知道该打什么标签答案是用一个可维护的标签词典。词典初始版本由业务分析师手工构建——把领域内的核心概念、同义词、上下位词全部列出来。之后进化层会持续往词典里补充新词条这就是自进化的入口之一。这套机制的优势在于可解释。任何一个文档被打上某个标签你都能追溯到规则引擎里的具体条件用户搜不到东西时你可以直接看标签表里有没有对应的标签。向量系统的黑盒问题在这里完全不存在。3.2 实体关系图让“关联”成为可计算的信息标签能解决“这篇文档讲什么”的问题但解决不了“这个实体跟哪些实体有关”的问题。举个例子用户在历史检索里经常把“A产品”和“B技术”放在同一个查询里搜说明这两者在用户认知里是关联的。这条关联信息如果只存在于日志里进不了检索环节那就没法帮助后续查询。Sirchmunk在索引层维护了一张轻量的实体关系图。这张图的节点是实体品牌名、技术名、人物名等边是共现关系边权由共现频次和用户行为共同决定。它不是“存储”在一张图数据库里而是以三列结构存在索引文件里源实体、目标实体、权重值。检索时如果查询命中一个实体节点系统会找到它的邻居实体再用邻居实体去倒查相关文档作为召回路的一个扩展分支。这里我特别想强调一个工程细节实体关系图这张“图”不是给用户看的它是给召回模块看的。用户搜“A产品”召回模块除了直接命中A产品相关文档外还会沿着实体关系图找到与A产品关联度高的“B技术”把B技术的文档也拉回来放在候选集里。重排序环节会判断这些扩展结果到底值不值得放到最终列表里。3.3 查询重写规则让用户问法不再是一个难题用户搜索同一个东西表达方式千差万别。“Q2智能手机出货量”“二季度手机卖了多少台”本质上是一个意思但词法层面完全不同。传统搜索引擎靠同义词扩展词典Sirchmunk的进化层做的是更高级的事情——它不断从行为日志里学习“哪些查询模式是同一个意图该怎么重写成标准查询”。重写规则以“模式→标准查询”的形式存在。模式侧记录的是查询词元的组合、词序、间隔标准查询侧是经过验证的、能召回好结果的规范化格式。规则库可以由人工初始化进化层会在后续运行中自动挖掘新的模式或者修正已有规则的置信度。重写模块作用的位置在查询解析之后、召回之前。如果系统检测到用户查询匹配了某条模式就用标准查询替换原查询继续走检索流程。这里的关键设计是“不替换原始查询只替换检索用的查询”——用户看到的还是自己输入的原话但系统内部检索的是改写后的标准查询。这样做的好处是规则错了也只会影响结果排序而不会让用户觉得系统“乱改了我的话”。这三件套合在一起效果上其实覆盖了向量检索能做的大部分场景但每一步都是可解释、可干预的。更关键的一点是它们的更新机制完全建立在系统自己的运行数据上——这正好接上了下一节讲的自进化。4. 自进化机制系统如何越用越聪明我见过很多号称“智能”的系统所谓的智能其实只是初始化时灌了很多规则上线后就不再更新。Sirchmunk的自进化是指系统的检索能力会随着持续运行显著提升——它不是“假装聪明”而是真正在“变聪明”。4.1 三个进化环路快、中、慢自进化不是在同一个节奏上跑一个巨大的更新任务而是拆成三个节奏完全不同的环路第一个是快环更新周期是分钟级。它只做一件事情实时更新实体关系图的边权。用户点击某个搜索结果系统会在日志里记录“查询实体”和“被点击文档的实体”之间的关联次数。这些次数实时累加直接反映在实体关系图的权重上。这个环路的计算量很小甚至可以放在Web服务进程的内存里定期刷盘。效果是当用户反复通过某条关联路径找到目标文档时下一次这条路径的优先级会立刻上升。第二个是中环更新周期是小时级。它处理的是标签词典的增量更新。系统每小时扫描一次新增的行为日志统计高频查询词。如果一个查询词在日志中反复出现但标签词典里还没有它系统会把它标记为“候选标签”。候选标签要经历一个验证过程看看这个标签能否从日志里找到足够的点击验证如果能就正式加入标签词典并自动回填到历史文档上。这样系统就学到了一个新的“语义锚点”。第三个是慢环更新周期是日级。它处理的是查询重写规则的生成与优化。每天夜间系统跑一遍全量行为日志分析聚类出高频查询模式对每个模式计算当前检索效果。如果某个模式的点击率远低于平均水平说明现有规则处理不了它系统就尝试生成一条新的重写规则如果某条规则改写后的查询点击率比改写前低规则就会被降权或标记待修订。这三条环路并不是从第一天上线就全部有数据可跑快环需要实体关系有初始边中环需要行为日志累积慢环需要足够多的查询样本来做统计判断。所以Sirchmunk的上线过程是“冷启动”的先用人工初始化的词典和规则跑起来然后逐步放开三个环路让它自己变聪明。4.2 反馈信号的质量控制不能把噪音当信号自进化系统的最大风险是“学习到了错误的东西”。用户点了一个结果不代表这个结果就是最优的用户搜了一个词不代表这个词就该进词典。如果系统不加筛选地消化所有行为数据很快会被噪音带偏。我设计了一个简单的信号质量门槛每个反馈事件都要经过三道筛选第一道是行为完整性检查。点击事件必须包含查询、候选列表、耗时、最终点击位置这些信息。少任何一项就不入库——因为少了一个字段后续统计时就有偏差。比如没有记录候选列表的话你没法判断用户点击的是“最优结果”还是“唯一结果”。第二道是行为有效性检查。用户在结果页上停留少于3秒就返回或者查询后立即翻页但不点击这类行为被视为无效信号。它们可能意味着用户没找到想要的东西直接拿去做“这个查询应该召回什么”的学习样本会产生误导。第三道是标签一致性检查。只有当同一个查询模式在行为日志里反复出现并且对应的点击结果具有高度的标签重合度时系统才认为这个模式值得生成一条新的重写规则或标签。单次行为的置信度太低必须靠次数来累积置信度。4.3 人机协作为进化护航纯自动进化可能存在一个盲区——系统没有“品位”它只会统计性地优化点击率但点击率高不代表结果质量高。比如用户想搜“手机”如果首页全是促销广告且用户愿意点系统可能会把广告结果排得更靠前——这对搜索体验是灾难性的。为了解决这个问题我给进化层留了一个“人工确认岗”。所有由中环和慢环生成的新标签、新规则默认状态是“实验性”。它们在系统里可以参与排序但权重打折。每周由运营人员巡检一次候选池确认后的规则转正为“线上规则”权重恢复正常。这个环节看似“不自动化”其实是保留一条安全底线。如果完全自动化而不管内容质量自进化就会走向“过度拟合点击率”的深渊。这个设计我特别想推荐给要做自进化系统的同行永远不要把一个完全无人干预的闭环放上线。你可以把人工确认的周期拉得很长甚至一个月看一次但一定要有。5. 从Query到Top5一次检索的完整旅程讲了这么多部件现在把它们串起来看一次真实查询在Sirchmunk里是怎么走的。这节我按时间顺序逐步拆解每一步都给出对应的数据处理。5.1 第一步查询解析用户输入“今年Q2手机出货量国产”进入系统。查询解析模块先做词法切分得到原始词元今年、Q2、手机、出货量、国产。然后做归一化Q2被映射为标准季度表达“2025Q2”国产被映射为标签“国产手机厂商”。这一步还会调用查询重写规则的匹配看看有没有现成的模式能把这个查询改写成标准格式。如果匹配到一条“时间词产品词指标词属性词”的通用模式系统会把检索查询改写为“2025Q2 智能手机 出货量 国产手机厂商”。注意这里的改写结果不会展示给用户只进入检索阶段。5.2 第二步多路召回改写后的查询并行走三路召回第一路是全文索引召回。用Lucene对核心词元做布尔检索返回按BM25相关性排序的文档集。这一路负责“精准命中”——文档包含尽可能多的查询词。第二路是标签匹配召回。查询中的“智能手机”“出货量”“国产手机厂商”都是标签词系统直接在标签倒排表里取交集得到同时命中这些标签的文档集。这路负责“语义命中”——文档可能没有完整出现所有关键词但标签上完全对得上。第三路是实体扩展召回。查询解析时识别出实体“手机”实体关系图返回它的核心邻居。加入第二季度的出货数据、主要品牌表现等关联文档。这路负责“关联发现”——用户没有明说但大概率相关的文档。三路结果合并成一个候选集每篇候选文档都有一个或多个来源标记对应它被哪条路径召回的。这些标记在重排序时很有用。5.3 第三步融合重排序候选集里可能有几百篇文档但最终只展示5个位置。重排序模块用一套加权公式给每篇文档打分总分 0.5 × BM25得分 0.3 × 标签匹配得分 0.2 × 实体扩展得分 规则加成前两项都是标准计算。标签匹配得分的算法比较直接查询标签数与文档标签数的重合比例重合越多分越高。实体扩展得分看的是文档与查询实体的关联路径强度——如果一篇文档和查询实体之间的边权很高说明它被用户验证过是关联的。规则加成是一个可扩展位。当某个查询模式匹配到一条实验性重写规则时加成值为正或负影响排序结果。这套公式的好处是每个系数都能单独调而且在日志里可以看到每一路的具体得分排查问题非常简单。5.4 第四步日志记录与闭环Top5结果返回给用户后系统记录这次查询的完整日志原始查询、改写后的查询、三路召回各自的候选列表和得分、最终排序列表。用户看到结果后可能点击了第二位或第五位也可能谁都没点直接改搜索词。所有这些行为都会被快环和中环消化成为下一次自进化的原料。有个细节我想强调日志记录一定要做全量记录不要做抽样。搜索系统的行为日志是所有进化分析的唯一数据源。抽样看似省了存储其实丢掉的往往是长尾行为——而长尾行为恰恰是自进化最有价值的输入。这一节整体走下来你会注意到每一路召回的结果都是可复现、可解释的。用户问为什么搜不到你可以把三路召回的候选都拉出来看清楚是哪一路缺席了。这是向量检索方案给不了你的调试体验。6. 每日进化循环系统在夜间如何自我迭代Sirchmunk的进化并不是随时都在发生日常的“自我迭代”集中在一个每天触发的离线工作流里。我给这个流程起的代号是“夜间循环”。整个流程按顺序做四件事。6.1 日志聚合与查询模式挖掘夜间第一步把当天所有的查询日志按查询文本聚合剔除单个用户刷出来的低频查询得到高频查询集合。然后用N-gram重叠算法做模式聚类——把看似不同但实际相似度很高的查询聚成一个模式。比如“手机出货量”“手机销量数据”“手机出货情况分析”很可能被聚在一起因为这些查询共享的核心词元是“手机”加一个出货/销量类指标词。聚类产出的是“查询模式”和“该模式的样本查询列表”。这些模式会拿到著名的“点击率验证”——系统统计每个模式内历史查询的点击率平均值作为这个模式的基准表现。6.2 标签与规则生成拿到查询模式之后系统开始尝试生成新标签和新规则。对于每个模式系统从点击过的文档标题里抽取高频词如果某个词在这个模式的点击文档中反复出现而在非点击文档中很少出现那就把它作为候选标签。候选标签需要过一个“可解释性检查”——简单来说如果一个标签没有对应的中文名词解释或同义词列表就不被认为是可信的。这一步的目的是防止系统学到完全脱离人类认知的“伪标签”。同时规则生成器会对比“模式A的原始查询结果点击率”和“模式A改写后的查询结果点击率”。如果改写的效果显著更好就生成一条以该模式为条件的新规则如果改写效果不佳就把已有规则标记为需要人工复核。Interaction with对话过程中的输出在这里特别值得注意夜间循环永远不要做“大爆炸式更新”。每次只变更一部分标签和规则保留大部分稳定状态。因为如果一夜之间更新了几百条规则第二天搜索结果体验可能会剧烈波动。6.3 实体关系图边权更新第三件事相对简单把白天快环累积的实体关联频次写入长期表重新计算每条边的权重。计算方式不是单纯累加而是带上了衰减因子——早期积累的关联数据权重递减最近的数据权重递增。这样做的好处是实体关系图能及时捕捉趋势变化某个产品在近期的检索热度被持续验证它的关联权重就会持续上升一个半年没人碰过的关联会自动沉底不再出现在扩展召回里。这个衰减机制我强烈建议做自进化的朋友都加上。如果没有衰减早期数据会像“僵尸权重”一样长期霸占系统导致系统无法适应内容或用户习惯的变化。6.4 人工复核机制夜间循环跑完后所有新生成的标签和规则不会直接上线而是进入一个待复核区。我设定的默认规则是标签词典的更新需要人工确认查询重写规则更新需要人工确认实体关系权重更新则完全自动——因为实体权重只是幅度变化不涉及新语义或新逻辑风险可控。人工复核的界面很简单就是一个待审列表每行给出候选标签的关键信息标签词、出现次数、点击率、覆盖文档数、样例查询。运营人员花15分钟扫一遍通过或驳回。这套流程虽然看起来“不够自动化”但保证了系统的语义进化方向不会偏离人类预期。整个夜间循环的执行时间在百万级文档的规模上大约是20到30分钟。我没有把它做得更快因为它并不在实时检索链路上没有必要和时间较劲。稳定性比速度重要。7. 这套架构的真正边界哪些场景不适合把Sirchmunk吹得再好也不合适它有很多明确的局限和边界。我在这篇文章的结尾部分想把这些边界讲清楚免得有人拿它去做不该做的事情。7.1 不适合超大规模或高度动态的搜索场景如果搜索的文档规模达到上亿级别且每天有千万级的新文档流入我建议你还是老老实实用向量数据库或者商业搜索引擎。Sirchmunk的标签倒排表和实体关系图在千亿级关联下会产生很大的存储和计算压力而且离线重建索引的周期会变得越来越长最后无法满足内容时效性。它的舒适区间是百万到千万级的垂直领域内容集行业报告、产品库、知识库、文档中心。这个规模下传统索引的性能几乎可以用“碾压”来形容而且成本优势非常明显。7.2 不适合完全依赖语义且表达高度模糊的场景如果用户的查询几乎不包含任何关键词——比如用户只输入“那个东西”单靠标签和实体关系是做不了语义消歧的。这类场景需要真正的多模态语义理解和上下文推理向量模型仍然是最好的工具。Sirchmunk能在“语义丰富但表达多样”的搜索场景里做到良好的效果但做不到“无中生有”。我到现在还记得第一次给一个同事演示这套系统时他随口问了一句“我把上周看到的那个东西找出来。”系统回车返回的结果当然是乱七八糟。他哈哈一笑说“你看还是得靠人啊”。这句话反而让我放心——说明系统不会在它不理解的查询上硬编一些虚假的“智能感”。7.3 历史经验总结Sirchmunk造出来的最大收获不是证明了“向量数据库不是必须的”而是证明了一个朴素道理搜索系统能力的上限取决于你有没有一套可靠的手段把用户和文档之间的关联知识持续提取出来并反馈到检索链路里。技术选型只是手段反馈闭环才是灵魂。如果你正在设计一个类似的系统我的建议是先不要纠结要不要上向量数据库先回答三个问题。第一我的行为日志是否完整可用了第二我的反馈信号是否有办法做质量控制第三我有没有能力定期人工复核系统的自学结果这三个问题没有答案之前任何架构设计都是空中楼阁。我自己在后续的迭代中已经在尝试把Sirchmunk的标签倒排表和向量召回做混合——保留向量召回作为一路补充信号但主力仍然放在可解释的结构化索引上。这个混合版本的实验数据目前很令人鼓舞等跑得更稳一点我会写深度解析二详细展开。