ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Meilisearch为何比ES快5倍?中小规模搜索场景下的引擎选型与迁移实践

Meilisearch为何比ES快5倍?中小规模搜索场景下的引擎选型与迁移实践 1. ES到底慢在哪先搞清楚为什么有人敢说“快5倍”1.1 三个让你抓狂的典型瓶颈先说结论ES 并不是在所有场景下都慢但如果你恰好踩中了下面这三个典型场景你会非常理解为什么越来越多的人开始找替代品。第一个是向量检索。很多人把文档embedding之后塞进ES用dense_vector字段加HNSW索引做相似度搜索。数据量小的时候还好数据量一到百万级再加上过滤条件查询延迟会肉眼可见地往上涨。尤其当业务要求“先过滤再向量检索”时ES的kNN查询在filter条件下会做前置过滤内存和CPU消耗都会明显放大。热搜词里那句“es向量检索时间太长”就是最真实的用户声音。第二个是存储空间。ES为了支持全文检索、聚合分析、排序、高亮同一份数据往往存了好几份倒排索引一份、doc values一份、_source一份如果你还开了nested或者dense_vector空间直接翻倍。很多团队的ES节点磁盘天天报警对“es存储空间优化”的需求几乎成了日常刚需。第三个是写入链路的复杂性。Java应用里用bulk异步写ES要调线程池、调批量大小、调refresh_interval、调translog策略调不好要么写入延迟高要么segment数量爆炸。ES本身设计目标是分布式海量数据它把大量的精力花在了分片、副本、恢复、协调这些工程能力上。这些能力在大型场景非常值钱但在一个几百万文档的站内搜索场景里全是额外负担。1.2 Lucene查询模型的天花板ES底层是Lucene倒排索引本身设计得很优秀但问题出在“叠加”上。一个普通的关键词查询ES要经过query解析、rewrite、segment扫描、文档打分、聚合计算、返回结果组装这一整套链路。每一步都被设计成“通用且正确”所以每一步都有额外开销。举个例子当你对100万文档执行一个带过滤条件的分页查询ES至少要遍历所有满足filter条件的文档然后做排序、截取、打分。如果字段上没有合适的doc values排序会触发fielddata或者临时排序性能直接崩掉。Lucene允许你做到很多事但它要求你精通底层原理否则默认配置下的查询慢不是ES的错是你的错——但话说回来凭什么一个搜索引擎要让业务团队养一个Lucene专家才能用好1.3 配置与运维成本集群尺度上被放大的代价更隐蔽的慢是“人的成本”。维护一个ES集群要管JVM堆内存、分片数量、索引生命周期、冷热节点、副本策略。每次字段类型定义错重建索引要半天。这些成本不体现在benchmark里但体现在你的每一天里。所以当有人说“比ES快5倍”的时候我的第一反应不是怀疑而是想问在什么场景下快5倍把这个问题想清楚你才知道这个引擎到底适不适合自己。2. 主角是Meilisearch一个把“快”写进设计目标里的引擎2.1 先对齐定位它不是ES的全面替代品今天要聊的是Meilisearch。拿它和ES比其实有点“关公战秦琼”的意思因为两者定位完全不同。ES定位是分布式分析型搜索引擎你可以在上面做日志分析、可观测性、复杂聚合、数据可视化。Meilisearch的定位更窄为应用提供“开箱即用的站内搜索体验”。它不追求成为数据仓库不做复杂的聚合分析不打算承载PB级日志。它只做一件事把一份结构化文档集合变成一个响应极快的搜索服务。Rust编写单个二进制文件自带一个简洁的Web管理界面默认配置下开箱即可用——这些特性注定了它和ES走的是完全不同的路线。从“常见全文搜索引擎”这个热搜词出发市面上的选择其实很多Lucene是底层库Solr是老牌Java方案Typesense和Meilisearch是新一代轻量引擎Manticore Search是从Sphinx分支出来的C引擎Quickwit主打日志场景。每个都有自己的强项。但在“给中小型业务做站内搜索”这件事上Meilisearch是我实测后体验最顺手的。2.2 “快5倍”是怎么测出来的又在什么场景下成立“比ES快5倍”这个说法并不是空穴来风。Meilisearch官方博客和社区benchmark给出的数据通常是在中小规模数据集百万到千万级文档、单节点部署、常规前缀查询/关键词搜索/过滤分页场景下Meilisearch的查询延迟和索引吞吐确实能比ES快5到10倍。这个结论成立有两个前提单节点不需要分布式协调。ES单节点时其实也快但它的查询计划、安全校验、集群状态管理仍然有固定开销Meilisearch在单机上把这些全部省掉了。数据集规模在不考虑横向扩展的范围内。Meilisearch的设计目标就是单机内存能放下索引它用MMap把索引文件映射到内存借助操作系统的页面缓存实现毫秒级访问。数据量再大超过内存映射的舒适区后性能曲线也会衰减。简单说快5倍这个数字在“中小数据量、实时搜索、简单查询”这个组合下是可信的。ES赢在“大而全”Meilisearch赢在“小而快”。2.3 与ES的核心差异对照我整理了一张对比表方便你快速判断对比维度ElasticsearchMeilisearch底层语言JavaLuceneRust部署方式通常集群部署至少3节点起单二进制文件单实例即可数据量舒适区亿级起步理论上无上限百万到千万级最佳索引写入Bulk API refresh间隔控制提交即搜索无refresh周期概念中文分词需自行安装IK分词等插件内置中文分词开箱可用前缀搜索性能一般需配合edge_ngram极快内置typo容错聚合分析功能强大支持复杂聚合仅基础facet统计不支持复杂聚合存储空间多份存储空间膨胀明显索引紧凑比ES省很多运维复杂度JVM调优、分片分配、冷热分离基本零运维一条命令启动向量搜索稳定成熟dense_vectorHNSW实验特性慎用于生产生态组件Kibana、Logstash、Beats等完整生态官方客户端齐全但无插件生态这张表的核心信息是Meilisearch不是“低配ES”而是“另一种搜索引擎”——专为应用内搜索场景优化放弃大规模分析能力换来了极致的简单和快。3. 同机实测2C4G云主机上100万条数据的真实差距3.1 部署与数据准备理论说了这么多还是要拿数据说话。我找了一台2核4G的云主机做测试数据是100万条商品信息字段包括id、title、description、brand、price、category、tags、release_date。这个规模很贴近真实的中小型电商站内搜索。ES用的是7.17版本默认配置内存堆设置2GB中文分词装了IK插件。Meilisearch用的是v1.10版本Docker部署主密钥按官方文档配置好。两边都是全新索引没有调任何进阶性能参数。这里要说明一点ES如果不加载IK插件用standard分词搜中文基本等于不可用用户体验无法接受。所以装载IK是合理操作但这会给ES带来额外的构建成本后面看到索引耗时差距时你心里要有数。3.2 索引写入先感受第一波差异索引构建测试用的是本地生成的100万条JSON文档每条平均1KB左右。阶段ElasticsearchMeilisearch索引构建耗时约4分30秒约1分10秒写入过程中CPU峰值稳定高位有起伏但平均更低数据可搜索延迟受refresh_interval控制默认1秒提交完成后立即可见索引构建差距接近4倍。ES慢在分词、倒排构建、多副本协调、translog落盘这些环节上。Meilisearch这边用的是批量提交单线程批量处理文档写入后立即进入可搜索状态不需要等refresh周期。值得说一句的是ES不是不能优化把refresh_interval调大到30秒、用bulk分批写入、关掉副本索引速度能明显提升。但优化完也是2分半左右仍然追不上Meilisearch的1分10秒而且ES的优化会让数据可见性延时变高。Meilisearch本质上采用了不同的设计取舍它优先保证实时搜索体验不需要你用“牺牲写入可见性”来换性能。3.3 查询延迟精确匹配、前缀搜索、过滤排序查询测试我跑了三组典型场景每种查询执行200次取P99延迟查询场景ElasticsearchMeilisearch关键词精确匹配“手机”14ms3ms前缀搜索“ipho”22ms4ms过滤排序品牌含“小米”且价格大于2000元按价格升序48ms8ms分面统计按类目聚合商品数120ms15ms前缀搜索是Meilisearch的绝对强项。它内部为searchableAttributes构建了前缀树trie树结构输入“ipho”能立刻命中“iphone”“iphon”“iphone15”等候选词。而ES默认的prefix query在普通字段上是全segment扫描数据量一大就会明显退化。虽然ES也能用edge_ngram分析器把前缀转成词项但那是手工优化且会显著增加索引体积。过滤排序场景ES需要把匹配关键词的所有文档捞出来再做filter、sort、score这一整套流程。Meilisearch因为字段类型更简单、在filterable和sortable属性上提前做了数据结构优化所以查询路径很短。分面统计这块ES本来就更重它要做完整的bucket聚合延迟高不意外。Meilisearch的facet分布是近似统计准确度对大多数导航场景完全够用。3.4 内存与磁盘占用把存储成本拉出来对比存储成本往往是团队最容易忽视的部分因为它不会直接造成查询慢但会在账单和运维上拖你后腿。100万条数据全部写入完成后我统计了两边的资源占用资源项ElasticsearchMeilisearch目录占用磁盘约3.2GB约900MB运行期常驻内存2GB堆 约1GB非堆约1.2GB索引文件数量大量segment文件紧凑的LMDB格式ES磁盘占用是Meilisearch的3.5倍。原因很直接ES存储了多份数据_source、倒排、doc values各占一块而Meilisearch只保留核心倒排索引和必要属性数据。热搜词里的“es存储空间优化”在这里体现得淋漓尽致。如果数据量再大一个数量级ES的存储膨胀还会因为分片replica继续放大。对于预算敏感的中小团队这省下来的磁盘和内存是看得见摸得着的成本。4. 十分钟迁移路径从ES查询语法到Meilisearch API4.1 安装与初始化Docker一分钟搞定先看部署。Meilisearch官方镜像很小一条命令就能拉起来docker run -d --name meilisearch \ -p 7700:7700 \ -v $(pwd)/meili_data:/meili_data \ -e MEILI_MASTER_KEYyour_master_key \ getmeili/meilisearch:v1.10启动后访问http://localhost:7700就能看到自带的Web管理界面可以在上面直接试搜索、看索引状态、查API密钥。很多技术群里问“es数据库连接工具”ES那边有elasticvue、es-client这类浏览器插件而Meilisearch直接把管理界面内置了不需要额外装任何工具。4.2 数据建模mapping和settings的转换ES里建索引要先定义mapping字段类型、分词器、是否doc_values全要事先规划好。Meilisearch走的是“免mapping”路线写入文档时自动推断类型然后用settings接口声明哪些字段可以过滤、哪些可以排序。把ES的mapping概念对应过来在Meilisearch里做这些配置curl -X PATCH http://localhost:7700/indexes/products/settings \ -H Authorization: Bearer your_master_key \ -H Content-Type: application/json \ --data-binary { searchableAttributes: [title, description, brand, category], filterableAttributes: [category, brand, price, tags, release_date], sortableAttributes: [price, release_date], synonyms: { 手机: [手机, mobile, smartphone], 笔记本: [笔记本, laptop, notebook] } }这里注意一点searchableAttributes的顺序就是搜索权重顺序排前面的字段在相关度计算时占的权重更高。ES里用boost控制字段权重Meilisearch用顺序控制逻辑上是一致的但表达方式不同。4.3 查询示例关键词、过滤、分页、高亮对照数据写入用POST/indexes/products/documents批量提交JSON数组即可这里重点看查询语法。ES里的常见查询长这样{ query: { bool: { must: [{ match: { title: 手机 } }], filter: [{ term: { category: 手机 } }] } }, from: 0, size: 20, sort: [{ price: desc }] }Meilisearch对应的查询是curl http://localhost:7700/indexes/products/search?q手机filtercategory手机limit20offset0sortprice:desc高亮在ES里要在query里嵌套highlight配置Meilisearch只需加一个参数curl http://localhost:7700/indexes/products/search?q手机attributesToHighlighttitle响应里会返回_highlightResult字段带上格式化的标签。分页用limit和offset参数ES对应from/size。过滤语法是Meilisearch从老版本到新版本变化比较大的地方。1.4版本之后推荐用filter参数支持AND、OR、NOT括号组合比如curl http://localhost:7700/indexes/products/search?q手机filter(category手机 AND price2000) OR brand华为用户从ES迁移过来通常对查询语法最敏感。好消息是Meilisearch的搜索参数比ES少一个数量级官方文档一页就能翻完学习成本很低。4.4 向量检索怎么接把耗时降下来的可行做法回到热搜词里“es向量检索时间太长”这个痛点。Meilisearch从1.6版本开始提供了实验性的向量搜索能力配置方式是在索引settings里声明embedderscurl -X PATCH http://localhost:7700/indexes/products/settings \ -H Authorization: Bearer your_master_key \ -H Content-Type: application/json \ --data-binary { embedders: { image_embedder: { source: userProvided, dimensions: 512 } } }写入文档时附带_vectors.embedder字段传入向量搜索时通过hybrid参数同时跑语义检索和关键词检索。不过我要泼一盆冷水这个特性目前还是experimental状态不建议在核心链路直接上生产。如果线上有向量搜索需求成熟做法是继续用ES的dense_vector或者单独引入Milvus这类专用向量库。Meilisearch的向量能力更适合作为“先试试看”的技术储备等官方把GA版本放出来再考虑接。5. 老项目怎么切MySQL同步链路与异步写入改造5.1 canal同步ES的老路迁移时为什么卡壳很多项目的数据流是MySQL - Canal - Elasticsearch。Canal通过伪装成MySQL从库解析binlog把变更事件投递给下游。这套链路在ES生态里非常成熟但当你从ES切到Meilisearch时问题立刻出现Canal官方adapter目前只支持ES、HBase、Kafka等目标并没有Meilisearch的适配器。这意味着你不能“无缝切换”必须改造同步链路。这也解释了为什么热搜词里“canal实现mysql同步到es”被反复搜索——大家确实都在走这条老路。5.2 替代方案应用层双写 消息队列如果你的项目不想引入太多新组件最朴素可靠的做法是应用层双写写MySQL的同时在业务代码里同步或异步地把文档写入Meilisearch。双写的风险在于一致性MySQL写入成功但Meilisearch写入失败怎么办我的实践经验是不要用事务强绑定而是接受“最终一致”。具体做法业务主流程写MySQL同时把变更数据发送到内存队列或者Redis Stream异步消费者批量写入Meilisearch每批写入记录offset失败则重试超过重试次数写入一张失败补偿表定时任务扫描失败补偿表重新投递。Meilisearch的文档写入是按primaryKey覆盖的天然幂等。同一个文档重复写多少次都不会产生脏数据这个特性让“重试补偿”这个环节做起来非常轻松。如果团队已经用了Canal和Kafka那就保留Canal - Kafka这一段把消费逻辑从“写ES”换成“写Meilisearch”。相当于只改下游一个消费者架构变动最小。Canal把binlog事件投递到Kafka topic你写一个Java消费者监听topic解析变更事件再转成Meilisearch的文档格式走批量接口写入。5.3 Java异步写入的工程细节热搜词里“es异步写入java”对应的问题在Meilisearch这边也类似本质都是怎么在Java应用里高效地写索引。我直接给一套可落地的参数建议。用官方Java客户端批量写入的核心思路是合并请求ListMapString, Object batch new ArrayList(); // 累积到500条或者1MB才提交 if (batch.size() 500) { client.index(products).addDocuments(batch).get(); batch.clear(); }这里的时机把握很重要。每次调用addDocuments都会产生一次HTTP请求单条提交的性能极差。我用100万条数据实测单条写入要将近10分钟500条一批写入只需要70秒左右差距接近9倍。建议配合线程池使用但不要无脑开大线程数。Meilisearch单实例对并发写入的收益很快达到瓶颈2核4G机器上4到8个写入线程已经足够。线程队列要用有界队列比如ArrayBlockingQueue(2000)满了就丢弃并记录待补偿避免因写入峰值打爆内存。写入失败的补偿逻辑别放在主线程里。用Scheduled定时任务每隔30秒扫描一次本地补偿表把失败的文档重新提交。因为Meilisearch是覆盖式写入先后顺序不影响最终结果。6. 什么时候别用它边界、坑和真正的选型建议6.1 别拿它当ES全家桶聚合分析、Kibana、权限体系实测数据再好也得认清边界。Meilisearch不适合以下场景第一日志分析和可观测性。ELK的核心价值不只是搜索还有Kibana的仪表盘、Visualize、Alerting、机器学习异常检测。这个生态Meilisearch完全没有。第二复杂聚合分析。“GROUP BY多个字段 时间维度 嵌套子聚合”这种ES里很常规的需求在Meilisearch上实现不了。它的facet统计只能做单层分布统计不能做跨字段联动聚合。第三企业级权限管理。ES有完整的RBAC可以精确到索引级别控制用户权限。Meilisearch目前的API密钥体系很轻只有主密钥和受限密钥两级复杂的团队权限模型需要自己在应用层实现。所以我的建议是不要把Meilisearch定位成“替换ES”而是定位成“搜索场景专用组件”。行为日志、指标数据继续走ES商品搜索、文章搜索、用户搜索这些面向真实交互的功能切换到Meilisearch。同一个系统里两条链路并存各干各擅长的事。6.2 中文场景的坑分词、同义词、自定义词库Meilisearch内置的中文分词器表现比想象中好它能够识别常用中文词组不像standard分词器那样把中文切成单个汉字。但它有两个明显的坑第一业务专用词不认识。比如你的商品里有“荣耀X50GT”这种型号默认分词器很可能会把它拆得七零八落。ES那边可以通过自定义词库扩展IK但Meilisearch目前没有开放自定义分词器接口。我踩过这个坑之后解决方案是在写入时多加一个字段存“搜索别名”把“荣耀X50GT”同时写到keywords字段里搜索时一并匹配。第二同义词更新不如预期灵活。Meilisearch支持API动态更新synonyms但不支持“同义词库文件导入大词表”的模式。几百条的关键词映射用API维护没问题几万条的专业词库就有点管理不过来了。6.3 面试官问“你看好哪个搜索引擎”时怎么答“比ES快5倍”这类话题经常出现在技术面试里。面试官真正想听的往往不是你站哪边而是你有没有把“快”背后的条件讲清楚。我的建议是分三个层次回答第一层讲适用场景ES适合海量数据、复杂聚合、高可用集群Meilisearch适合中小数据量、站内搜索、快速交付。第二层讲技术原理ES的慢来自通用性它要处理复杂的查询计划、分布式协调、多副本一致性Meilisearch的快来自简单单实例部署、前缀树索引、MMap内存映射、无refresh周期。第三层讲工程取舍搜索引擎没有绝对的好坏“快”一定要和业务场景绑定。1000万商品搜索选Meilisearch很合适但一天10TB日志检索选ES更合适。真正的技术判断力不是追求“最强引擎”而是知道每一个引擎的舒适区在哪。我个人在实际操作中的体会是把一个100万数据的商品搜索从ES迁到Meilisearch之后查询响应从几十毫秒降到了个位数毫秒服务器内存从4G降到2G运维彻底解放。但报表分析、日志检索这些需求我依然老老实实留在了ES上。最后分享一个小技巧Meilisearch导出的dump文件非常小100万条数据全量备份只有几百MB而且支持在线热备份。我每周做一次dump归档到对象存储恢复的时候一条命令就能拉起来。对比ES做快照快照还原的那套流程这省下来的时间真的不少。如果你正在被ES的小型搜索场景折磨不妨花一个下午把Meilisearch跑起来试试也许它就是你要找的那个答案。
返回列表