在企业云盘场景里,全文检索是继同步和权限之后第三个被客户追问的功能。设计院的人要搜”2019年三期的配电设计”,制造业的人要搜”带变压器的非标设备采购合同”,这类模糊的、不记得完整文件名的查询,传统的文件夹目录结构根本兜不住。
做企业云盘这几年,我们在检索这块踩过不少坑,从一开始的数据库LIKE查询,到后来引入Elasticsearch,再到后来调研MeiliSearch和Typesense,走了不少弯路。这篇把三个方案的技术原理、适用场景、实际踩过的坑都摊开讲,避免后续入坑的企业重复我们的教训。
先说结论:三个引擎不是替代关系
Elasticsearch、MeiliSearch、Typesense解决的是同一类问题,但各自的 DNA 完全不同,适用的规模量级也差了10倍以上。
选错引擎最常见的后果是:小团队上了ES,光内存就占了几十G,运维成本爆炸;大团队上了Typesense,几十亿文档搜不出东西,回过头来重做迁移。
以下是我们在巴别鸟实测后拿到的数据,不是官网上抄的参数。
Elasticsearch:大而全,但运维复杂度也是真的高
适用场景
文档量在500万以上、有专职运维团队、对搜索精度和聚合分析要求高的场景。ES本质是一套分布式文档数据库,搜索只是它能力的一部分。
核心原理
ES底层是Lucene,倒排索引+打分机制。一个文档进来,先分词(Analyzer),然后建立Term→DocID的映射表。查询时,ES把查询语句也做同样的分词,然后查倒排表取交集,按TF/IDF公式打分返回。
分词是中文明能绕不过去的坎。ES自带的standard分词器把中文当空格分隔的字符处理,”企业云盘”会被拆成”企””业””云””盘”四个单字,搜”企业云盘”匹配不到任何文档。我们最早踩的坑就是这个——上线后客户搜中文永远是零结果。
后来切到了IK分词器(ik_max_word模式),能拆出”企业云盘”作为一个完整词条,召回率才正常。但IK的词库需要维护,特定行业的专有名词(比如”GIS坐标系””BIM机电深化”)默认词库识别不了,需要手动加词典,这个是持续的运维成本。
性能数据(我们的实测)
以下数据是3节点ES集群(每个节点8核32GB内存,SSD云盘)上跑出来的,不代表ES的极限,只代表在企业云盘这个场景里我们摸到的实际水位。
查询延迟:单次查询P50=12ms,P99=85ms。这个数字在100万文档量级是稳的,超过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会比较吃力。
踩坑记录
内存设置不当导致频繁FGC。 我们的ES集群上线第一个月,GC日志显示平均每45分钟一次Full GC,每次停顿200-400ms。原因是初始JVM堆设了8GB,但Lucene的segment缓存依赖page cache,heap太小导致大量小segment无法缓存,查询时不断触发load,数据节点负载飙升。最后把堆调到了31GB的合理水位,同时关闭了swap,问题才缓解。
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的运维门槛在三个引擎里是最高的。需要单独部署(官方推荐用tar包而不是系统包管理器),需要配置JVM参数,需要理解Lucene的segment机制和merge策略,需要做定期的快照备份,需要监控GC和集群健康。Kibana能解决一部分监控问题,但出了故障定位问题,还是需要懂Lucene原理。
这不是说ES不好,而是说如果你只有一台服务器、没有专职运维,不要选ES。我们的做法是把ES做成独立的搜索服务集群,不和业务数据库混部,用Graylog做日志收集,Kibana看集群状态。
MeiliSearch:轻量级但中文支持持续有坑
适用场景
文档量在1000万以内、没有专职运维、需要快速上线、用户体验优先的场景。MeiliSearch的定位是”让非搜索工程师也能用好”的全托管式搜索引擎。
核心原理
MeiliSearch的核心是Rust写的,底层索引基于不可变的LSM树结构。它的设计哲学是”开箱即用”,所以很多参数默认已经调好了,但也意味着灵活性比ES低。
分词方面,MeiliSearch内置了四种语言的分析器,默认的面向欧洲语言(空格分隔),中文支持依赖一个社区维护的中文分词插件 meilisearch-tokenizer,基于结巴分词。这个插件我们用了一年,踩过几个坑:
一是词库更新滞后。结巴分词的词库基于2015年前的网民贡献,”BIM正向设计””装配式建筑”这类2019年后才大规模使用的行业术语,分词器识别不了,搜”BIM机电深化”会被拆成”BIM””机电””深””化”,召回结果乱七八糟。
二是插件和MeiliSearch版本绑定。MeiliSearch每次大版本升级(比如0.27→0.28),tokenizer插件常常不兼容,需要等社区更新。我们被迫锁过一个版本大半年不敢升级,因为新版本一升,tokenizer就跪了,上下游依赖全断。这个在技术团队小的公司里是真实的风险。
性能数据(3节点4核8GB虚拟机,单节点部署)
查询延迟:单次查询P50=8ms,P99=38ms。这个数字在200万文档量级是稳的,MeiliSearch的延迟表现比ES好,因为它的索引是mmap到内存的,冷启动快,查询路径也短。
但MeiliSearch的延迟和文档字段数量高度相关。如果一个文档有50个字段做索引,延迟会飙升到P99=200ms,原因是每次查询都要遍历所有字段的倒排链。解决方案是做筛选字段分组,把不需要参与全文匹配的字段排除在索引之外,只用来做过滤。
索引速度:Bulk写入约4000文档/秒,比ES慢一半。但MeiliSearch的增量索引不需要全量重建,新增文档秒级可查,不需要像ES那样等refresh interval。
内存占用:官方建议至少512MB,但我们的实测,200万文档(总大小5GB)稳定在800MB左右(包括mmap的page cache)。这个占用比ES低很多,小规模部署时可以直接在业务服务器上混部。
踩坑记录
中文分词插件导致索引损坏。 一次线上事故,我们把MeiliSearch从0.26升级到0.28,tokenizer插件版本没跟上,升级后虽然服务能启动,但索引里所有中文文档的分词结果全部变成单字存储。需要删掉索引重建,重新导入数据,那次停了服务4小时。这个教训让我们后来对MeiliSearch的版本升级非常谨慎,每次升级前先在测试环境跑两周。
没有内置的增量备份方案。 MeiliSearch的dump命令能把整个索引导出成快照文件,但导出过程需要停写,大数据量时导出一次要几十分钟。我们在生产环境自己写了脚本,每小时对dump文件做一次增量压缩备份,这个功能官方至今没提供。
对中文搜索结果的排序不如预期。 MeiliSearch的Ranking规则默认优先考虑词频(TF),但在企业云盘场景里,用户搜”配电设计”,文档的标题含”配电设计”和正文含”配电设计”的权重应该不同。MeiliSearch不支持像ES那样给不同字段配置不同的boost权重,虽然可以通过自定义Ranking规则调整,但要写到代码里,不够灵活。
运维成本
MeiliSearch的运维成本比ES低一个量级。单节点部署的话,一个docker-compose up就能跑起来,内置的管理界面(默认端口7700)能看索引状态和查询日志。升级是二进制替换,不需要改配置。
但MeiliSearch的集群模式(分布式搜索)需要收费版本才能用,开源版只支持单节点。如果是多副本高可用需求,要么买Enterprise版,要么自己在上游套一层负载均衡器加主从复制。后者我们试过,搭建了一套MeiliSearch + ProxySQL + Keepalived的架构,维护了半年后放弃了,改回了ES,因为那套架构的坑比ES还多。
Typesense:极简到极致,但适合的场景很窄
适用场景
文档量在1000万以内、追求极低延迟、硬件资源极其有限、对搜索精度要求不高的场景。Typesense的定位更像是”嵌入式搜索引擎”,适合放在移动端或者边缘节点。
核心原理
Typesense也是Rust写的,设计目标是”比Elasticsearch简单10倍,延迟低10倍”。它的索引基于一种叫做”分层倒排索引”(Hierarchical Inverted Index)的结构,官方声称能保证P99<50ms的查询延迟,代价是索引体积比ES大。
分词方面,Typesense内置了一个叫”Jieba”的中文分词集成(对,就是结巴)。没错,三个引擎都踩了结巴分词的坑,说明这个库确实是中文搜索绕不过去的坎。Typesense的分词配置可以自定义,但只支持单向的词典导入,不支持动态热更新,改词库要重建索引。
性能数据(单节点4核8GB虚拟机)
查询延迟:P50=3ms,P99=22ms。这是三个引擎里最低的,官方宣传的”亚毫秒级”基本属实。在我们的测试场景里,Typesense的延迟比MeiliSearch低40%,比ES低70%。
但这个延迟优势是有代价的。Typesense的查询并发能力比ES低很多,单节点QPS实测上限约1500,而ES单节点能到5000以上。如果你的用户量超过500并发,Typesense会成为瓶颈。
索引速度:Bulk写入约3000文档/秒,最慢。但Typesense有个设计亮点——索引过程中查询不受影响,MeiliSearch在merge时会短暂阻塞查询,Typesense不会。
内存占用:200万文档(总大小5GB)稳定在2.3GB,比MeiliSearch还低。Typesense的内存管理比较激进,大部分索引数据在内存里,磁盘只是持久化用。如果内存不够(比如只有4GB),Typesense会因为不断在内存和磁盘之间换页导致性能急剧下降。
踩坑记录
中文分词精度差但官方不作为。 Typesense用的结巴分词版本比较老(v0.39),对”机电BIM深化设计”这类专业词汇识别率约40%,剩下的全拆成单字。我们尝试给Typesense提交了一个支持自定义分词器的PR,官方回复是”我们考虑在v27版本支持”,然后一年过去了还没做。后来我们自己在应用层做了中文预处理,在发给Typesense之前用IK分词器预分一次词,把分词结果空格拼接后作为field发给Typesense查询。虽然绕过了分词问题,但查询语法变了,客户的搜索体验打了折扣。
集群版本升级过程繁琐。 Typesense的集群节点间通信用的是自己定义的gRPC协议,版本升级要求所有节点同时重启,中途如果有节点挂了,整个集群不可用。我们有一次升级时一台机器网卡驱动异常,节点没能及时重启,导致集群恢复花了2小时。ES的滚动升级策略对这种情况处理得更优雅,虽然也复杂。
文档量超过1000万后性能断崖。 官方文档说支持”数亿文档”,实际测试下来,1500万文档时查询延迟从P99=22ms跳到P99=180ms,原因是分层倒排索引的层数变深了,需要遍历更多层级才能定位到目标文档。这个问题官方承认了,给的解法是”做分片”,但分片后延迟又上去了。目前我们没找到在这个量级下保持低延迟的成熟方案。
运维成本
Typesense的运维成本介乎ES和MeiliSearch之间。安装简单(一个二进制文件),配置项少(主要就是端口和内存限制),但集群部署的坑比较多,遇到问题查资料也不好找——毕竟ES有大量社区沉淀,Typesense的社区还小。
横向对比:选型决策矩阵
说多了容易乱,直接给结论:
| 维度 | Elasticsearch | MeiliSearch | Typesense |
|---|---|---|---|
| 适合文档量级 | 500万+ | 200万以内 | 1000万以内 |
| 中文分词 | IK分词(较成熟) | 结巴插件(版本绑定问题多) | 结巴(版本老,更新慢) |
| P99查询延迟 | 85ms(500万文档) | 38ms(200万文档) | 22ms(200万文档) |
| 内存占用 | 高(18GB/200万文档) | 中(800MB/200万文档) | 低(2.3GB/200万文档) |
| 索引速度 | 快(8000/s) | 中(4000/s) | 慢(3000/s) |
| 运维复杂度 | 高 | 低 | 中 |
| 开源免费 | 是(Apache 2.0) | 是(MIT) | 是(GPL 3.0) |
| 集群高可用 | 原生支持 | 需Enterprise | 原生支持 |
| 中文搜索精度 | 优(IK分词) | 良(结巴+插件) | 中(结巴老版本) |
我们的实际选型
巴别鸟目前的做法是分档处理:
小规模客户(文档量<100万)用MeiliSearch,单节点部署,运维成本低,能覆盖90%以上的查询场景。
中等规模(100万-500万)用ES集群,有专门的搜索运维团队在维护,效果稳定。
超过500万文档且对延迟极敏感的场景,我们测试过Typesense,但在中文分词这块花了太多定制成本,目前没有大规模生产部署。
如果你是技术团队自己选型,我的建议是:先用MeiliSearch跑一个最小化验证,搜准率和延迟都能接受就先上线。等规模真的上来了,再考虑要不要切ES或Typesense。不要在一开始就设计”未来能支持1亿文档”的架构,那个复杂度会把你的产品迭代拖慢。
最后说一句:选搜索引擎这件事,没有最优解,只有当前阶段的合适解。你们的场景、人员配置、预算不同,最优答案也不同。这篇的价值在于告诉你,每个引擎踩过的具体坑是什么,让你选的时候心里有数,而不是被厂商的PPT带偏。