这件事听起来像个笑话:我自己搭的语义搜索引擎,后端挂了整整两周,我毫不知情,还用得挺开心。直到偶然翻日志才发现,它早就不跑向量了,纯靠最原始的关键词匹配撑了两周。尴尬归尴尬,但搞清楚它为什么没直接挂掉之后,我觉得这部分一点也不尴尬。
我在一个小型MCP服务器上部署了Codicil,专给AI编程助手当文档搜索引擎用。跑Runbook、配置笔记、家庭实验室杂七杂八的记录,总共就我一个用户,没人盯着运维。几个月来它一直对接正经的Ollama实例用nomic-embed-text做嵌入,每次查询都是真正的语义检索。后来因为别的原因我把那台Ollama机器下线了,嵌入服务直接断连。
两周后我才偶然发现这事。具体什么线索让我起了疑心已经记不太清,反正翻日志的时候才意识到,系统已经靠关键词回退方案跑了很久。
它不是没挂,是挂了也不吭声
大多数同类工具有一个统一的失败模式:嵌入服务一挂,万事休矣。没向量就没搜索,干脆报错拉倒。我在写Codicil的时候偏不想走这条路,于是把代码里散落着一堆"要是这东西没了该怎么办"的处理逻辑,没集中放在一起,但每一处都管用。
这套朴素到近乎粗暴的容错机制,事后复盘有四层安全网。第一层:嵌入主机连不上?直接退回到从磁盘grep原始文件。第二层:索引是空的或者压根没建?同样的关键字回退。第三层:重建索引跑到一半崩了?新数据块先写入再删旧块,就算中途崩溃残留的也是过期数据,而不是空白。第四层更实际——一个长期运行的serve进程跟cron定时触发的索引任务抢同一个存储时,真撞上过。后来直接上了排他锁,用fcntl.flock(file_descriptor, LOCK_EX | LOCK_NB)把后来者直接拒掉,连碰数据的机会都不给。
这些技术哪项都不高深,写起来却烦人,也容易被跳过。我第一次就没写全,所以进程竞态的事才真实发生过。
关键词搜索输得明白,但站得稳
说实话,关键词搜索比语义搜索差远了。它不懂同义词,只会数字符重叠。把相同查询放在两边跑,语义模型大部分时候都赢。但前提是嵌入主机得在线——我那个偏偏离线两周,而我毫无察觉。这就引出一个挺荒诞的结论:在你的基础设施偶尔掉线时,一个会降级的差方案,比一个彻底罢工的好方案有用得多。
调试过程中还揪出个更蠢的bug,事后看简直写在脸上。Chroma建集合的时候,第一次写入就锁死向量维度。我最初让不同嵌入模型共用同一个集合,一换模型再次写入就会默默出错。现在每个嵌入模型单独建集合,用模型名的SHA-256前12位做区分,彻底隔离。
没人盯的系统,得自己照顾自己
回看整件事,最值钱的教训不是回退机制多聪明,而是设计假设的根本翻转:不假设依赖的服务永远在线,转而假设它们随时会消失。这种思维下写出来的代码,天然带着怀疑论气质——每个外部调用都要想好"对方不在了我怎么办"。做出来的系统可能不够优雅,但皮实。
写这些容错代码没有技术难度,纯粹是纪律问题。技术栈里每个"先放着回头再加"的防御逻辑,最终都会变成生产环境里的真实故障。进程竞态那次就是活证。
Codicil显然还有一堆可改进的地方,比如埋点监控让系统能主动喊一声"我已经退化到关键词模式了",而不是默不作声。但我依然认为它代表了健壮软件的一种被低估的构建哲学:真正的可靠不是保证所有依赖都万无一失,而是当依赖层层崩塌时,你还能在残垣断壁上继续干活。哪怕干得糙一点。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.