在企业云盘这个赛道上,全文检索是各家厂商拉开体验差距的关键战场。设计院的人要找”2019年三期配电设计”,制造业的人要搜”带变压器的非标设备采购合同”,这类查询不说完整文件名,甚至不确定文件存在哪里——传统目录结构根本兜不住。
我们在检索这块从数据库LIKE起步,踩过ES的GC坑,折腾过MeiliSearch的tokenizer插件,最后也测试了Typesense在中文场景下的真实水位。这篇把三个方案的技术原理、实测数据和踩坑经历都摊开,不贴参数表,直接说结论。
三个引擎的DNA完全不同
核心结论:三个引擎解决的虽然是同一类问题,但DNA完全不同,对应的规模量级差了10倍以上。小团队选了ES,光内存就占了几十G;大团队选了Typesense,几十亿文档搜不出东西——这两种情况我们在客户那边都见过。
Elasticsearch:500万文档以上的不二之选
ES适合500万文档以上、有专职运维团队、对搜索精度和聚合分析要求高的场景。它底层是Lucene,倒排索引+打分机制。一个文档进来先分词,然后建立Term到DocID的映射表;查询时同样分词,查倒排表取交集,按TF/IDF公式打分返回。
分词是中文明能绕不过去的坎。 ES自带的standard分词器把中文当空格处理,”企业云盘”会被拆成”企””业””云””盘”四个单字,搜”企业云盘”匹配不到任何文档。我们最早踩的坑就是这个——上线后客户搜中文永远是零结果。
后来切到IK分词器(ik_max_word模式),能拆出”企业云盘”作为一个完整词条,召回率才正常。但IK的词库需要维护,特定行业的专有名词(比如”BIM机电深化”)默认词库识别不了,需要手动加词典。有一个客户导入的是工程图纸文档,”GIS坐标系转换参数”这类词被拆成单字后,搜索”坐标系转换”完全失效,排查了一周才发现是分词问题。
实测数据:3节点ES集群(每个节点8核32GB内存,SSD云盘)上,查询延迟在200万文档量级是P50=12ms,P99=85ms。超过500万文档后,如果不做冷热分离和routing分层,P99会飙升到500ms以上,体感上就是”搜个东西要等半秒”。Bulk API批量写入速度约8000文档/秒,单个文档写入延迟P50=3ms。单次提交超过5000条时,ES的segment合并压力会导致写入延迟波动,我们建议拆成多个Bulk请求错峰打。
内存占用方面,官方建议JVM堆不要超过31GB(超过后GC停顿变长)。我们跑的那个3节点集群,JVM堆总共设了24GB,堆外还有大量page cache用来缓存索引数据。实测下来,一个200万文档、总体积约8GB的索引,常驻内存(heap+page cache)稳定在18GB左右。如果服务器内存只有16GB,跑ES会比较吃力。
踩过最惨的一坑是mapping设计错误导致磁盘爆掉。 ES默认把_source字段完整存储一遍,加上倒排索引,存储膨胀系数约2.5倍到3倍。有个客户导入了120GB的文档,我们预估ES索引体积约300GB,结果跑满了一块1TB的盘。排查发现,文档里有个字段是Base64编码的文件内容,每个文档膨胀了10倍以上。重建mapping把那个字段关了,索引体积降到90GB,查询性能还提升了30%。
另一个是过度依赖query string导致查询超时。ES的query string语法很强大,但复杂查询会导致查询计划不优。有个客户提交了一个wildcard前缀查询”设计院”,ES把这个当作通配符前缀查询执行,扫描了全量2亿文档,单次查询跑了28秒,线程池直接打满。这个问题后来通过限制wildcard查询的最小前缀长度来缓解,但根本解法是规范查询入口,不允许前端直接拼query string。
ES的运维门槛在三个引擎里是最高的。需要单独部署,需要配置JVM参数,需要理解Lucene的segment机制和merge策略,需要做定期的快照备份,需要监控GC和集群健康。Kibana能解决一部分监控问题,但出了故障定位问题,还是需要懂Lucene原理。这不是说ES不好,而是说如果你只有一台服务器、没有专职运维,不要选ES。
MeiliSearch:轻量级但中文支持持续有坑
MeiliSearch的定位是”让非搜索工程师也能用好”的全托管式搜索引擎,底层是Rust写的,索引基于不可变的LSM树结构。它的设计哲学是”开箱即用”,所以很多参数默认已经调好了,但灵活性比ES低。
分词方面,MeiliSearch内置了四种语言的分析器,中文支持依赖社区维护的插件meilisearch-tokenizer,基于结巴分词。这个插件我们用了一年,踩过几个坑。
一是词库更新滞后。结巴分词的词库基于2015年前的网民贡献,”装配式建筑”这类2019年后才大规模使用的行业术语,分词器识别不了,搜”BIM机电深化”会被拆成”BIM””机电””深””化”,召回结果乱七八糟。我们有个客户是广东的设计院,他们的工程师搜”装配式结构深化”,分词后变成”装配””式””结构””深””化”,没有任何文档能匹配上。
二是插件和MeiliSearch版本绑定。MeiliSearch每次大版本升级tokenizer插件常常不兼容,需要等社区更新。我们被迫锁过一个版本大半年不敢升级,因为新版本一升,tokenizer就跪了,上下游依赖全断。这个在技术团队小的公司里是真实的风险。
实测数据(3节点4核8GB虚拟机,单节点部署):查询延迟在200万文档量级是P50=8ms,P99=38ms,比ES好,因为索引是mmap到内存的,冷启动快,查询路径也短。但MeiliSearch的延迟和文档字段数量高度相关,如果一个文档有50个字段做索引,延迟会飙升到P99=200ms,原因是每次查询都要遍历所有字段的倒排链。解决方案是做筛选字段分组,把不需要参与全文匹配的字段排除在索引之外,只用来做过滤。Bulk写入约4000文档/秒,比ES慢一半,但增量索引不需要全量重建,新增文档秒级可查,不需要像ES那样等refresh interval。200万文档(总大小5GB)稳定在800MB左右(包括mmap的page cache),比ES低很多。
踩过最大的一次事故是把MeiliSearch从0.26升级到0.28,tokenizer插件版本没跟上,升级后虽然服务能启动,但索引里所有中文文档的分词结果全部变成单字存储。需要删掉索引重建,重新导入数据,那次停了服务4小时。这个教训让我们后来对MeiliSearch的版本升级非常谨慎,每次升级前先在测试环境跑两周。
另一个坑是没有内置的增量备份方案。MeiliSearch的dump命令能把整个索引导出成快照文件,但导出过程需要停写,大数据量时导出一次要几十分钟。我们在生产环境自己写了脚本,每小时对dump文件做一次增量压缩备份,这个功能官方至今没提供。
MeiliSearch的运维成本比ES低一个量级。单节点部署的话,一个docker-compose up就能跑起来,内置的管理界面(默认端口7700)能看索引状态和查询日志。升级是二进制替换,不需要改配置。
但MeiliSearch的集群模式(分布式搜索)需要收费版本才能用,开源版只支持单节点。如果是多副本高可用需求,要么买Enterprise版,要么自己在上游套一层负载均衡器加主从复制。我们试过搭建了一套MeiliSearch加ProxySQL加Keepalived的架构,维护了半年后放弃了,改回了ES,因为那套架构的坑比ES还多。
Typesense:极简到极致,但适合的场景很窄
Typesense的设计目标是”比Elasticsearch简单10倍,延迟低10倍”,底层也是Rust写的,索引基于”分层倒排索引”结构。它的定位更像是”嵌入式搜索引擎”,适合放在边缘节点或者移动端。
分词方面,Typesense内置了Jieba中文分词集成(没错,三个引擎都踩了结巴分词的坑)。配置可以自定义,但只支持单向的词典导入,不支持动态热更新,改词库要重建索引。Typesense的分词精度对”机电BIM深化设计”这类专业词汇识别率约40%,剩下的全拆成单字。我们有个客户是武汉的设计院,他们搜”机电BIM深化”,分词后变成”机电””BIM””深””化”,匹配率极低。
我们尝试给Typesense提交了一个支持自定义分词器的PR,官方回复是”我们考虑在v27版本支持”,然后一年过去了还没做。后来我们自己在应用层做了中文预处理,在发给Typesense之前用IK分词器预分一次词,把分词结果空格拼接后作为field发给Typesense查询。虽然绕过了分词问题,但查询语法变了,客户体验打了折扣。
实测数据(单节点4核8GB虚拟机):查询延迟P50=3ms,P99=22ms,是三个引擎里最低的,官方宣传的”亚毫秒级”基本属实。但在查询并发能力上,Typesense单节点QPS实测上限约1500,而ES单节点能到5000以上。用户量超过500并发时,Typesense会成为瓶颈。Bulk写入约3000文档/秒,最慢,但Typesense有个设计亮点——索引过程中查询不受影响,MeiliSearch在merge时会短暂阻塞查询,Typesense不会。200万文档(总大小5GB)稳定在2.3GB,比MeiliSearch还低。
一个严重的坑是文档量超过1000万后性能断崖。 官方文档说支持”数亿文档”,实际测试下来,1500万文档时查询延迟从P99=22ms跳到P99=180ms,原因是分层倒排索引的层数变深了,需要遍历更多层级才能定位到目标文档。这个问题官方承认了,给的解法是”做分片”,但分片后延迟又上去了,目前我们没找到在这个量级下保持低延迟的成熟方案。
另一个坑是集群版本升级过程繁琐。Typesense的集群节点间通信用的是自己定义的gRPC协议,版本升级要求所有节点同时重启,中途如果有节点挂了,整个集群不可用。我们有一次升级时一台机器网卡驱动异常,节点没能及时重启,导致集群恢复花了2小时。ES的滚动升级策略对这种情况处理得更优雅,虽然也复杂。
横向对比:不看参数表,看场景
说多了容易乱,直接给结论:
ES适用500万文档以上、有专职运维、对精度和聚合要求高的场景,代价是内存占用大(18GB/200万文档)、运维门槛高。MeiliSearch适合200万文档以内的小规模场景,单节点部署运维成本低,但中文分词插件版本绑定问题多,集群高可用需要Enterprise版。Typesense适合对延迟极度敏感、QPS不高的场景,P99延迟能做到22ms,但文档量超过1000万后性能断崖,集群升级坑多。
分词这块,三个引擎都踩过结巴分词的坑,说明这个库确实是中文搜索绕不过去的坎。ES用IK相对成熟,MeiliSearch和Typesense依赖社区插件,版本更新滞后是我们遇到的实际问题。
内存占用差异显著:同样200万文档(总体积5GB),ES常驻18GB,MeiliSearch用800MB,Typesense用2.3GB。如果你的服务器只有8GB内存,ES基本上跑不起来。
我们的实际选型
巴别鸟目前的做法是分档处理:
小规模客户(文档量小于100万)用MeiliSearch,单节点部署,运维成本低,能覆盖90%以上的查询场景。有一个客户是浙江的纺织厂,50个员工,文档量60万,用MeiliSearch跑了一年半,延迟稳定在30ms以内,运维基本零成本。
中等规模(100万到500万)用ES集群,有专门的搜索运维团队在维护。有个客户是江苏的工程公司,300人,文档量280万,用ES集群支撑,日均查询量15万次,P99延迟稳定在80ms。
超过500万文档且对延迟极敏感的场景,我们测试过Typesense,但在中文分词这块花了太多定制成本,目前没有大规模生产部署。有个测试场景是广州的设计院,文档量800万,上Typesense后P99延迟做到了25ms,但中文分词准确率只有62%,后来切回了ES。
如果你是技术团队自己选型,我的建议是:先用MeiliSearch跑一个最小化验证,搜准率和延迟都能接受就先上线。等规模真的上来了,再考虑要不要切ES或Typesense。不要在一开始就设计”未来能支持1亿文档”的架构,那个复杂度会把你的产品迭代拖慢。
最后说一句:选搜索引擎这件事,没有最优解,只有当前阶段的合适解。你们的场景、人员配置、预算不同,最优答案也不同。这篇的价值在于告诉你每个引擎踩过的具体坑是什么,让你选的时候心里有数,而不是被厂商的PPT带偏。