Elasticsearch 搜索与相关性调优:映射、查询、聚合与性能
Elasticsearch 为什么值得认真调优
Elasticsearch 的「开箱即用」往往只适合 demo。一旦数据量上亿、QPS 上千,未调优的 mapping 和查询会迅速拖垮集群:写入阻塞、查询毛刺、堆内存溢出。调优的本质是在写入时把该算的算完,在查询时只做必要的工作。
| 维度 | 未调优 | 调优后 |
|---|---|---|
| 字段类型 | 全 text 或 keyword |
按检索/聚合需求精确拆分 |
| 过滤条件 | 写在 must 里 |
写在 filter 上下文 |
| 分片数 | 随手 5/10 个 | 按数据量与节点反推 |
| 批量写入 | 单条 _doc |
bulk + 合理批次 |
| 相关性 | 默认 BM25 | 结合 boost / 业务权重 |
分词与 Analyzer:检索质量的根基
analyzer 决定「用户搜的词」和「文档里的词」能否对上。中文尤其要吃对分词器。
{
"settings": {
"analysis": {
"analyzer": {
"my_cn": {
"tokenizer": "ik_max_word",
"filter": ["lowercase"]
}
}
}
},
"mappings": {
"properties": {
"title": { "type": "text", "analyzer": "my_cn" },
"status": { "type": "keyword" }
}
}
}
排查 mapping 与查询 DSL 时,可先把请求体用 JSON 格式化 工具整理,避免嵌套大对象看花眼。
text vs keyword:检索与聚合的取舍
text:会被分词,适合「全文检索」,但不适合精确匹配与聚合。keyword:不分词,适合「精确匹配、排序、terms 聚合」,但无法做全文检索。
常见做法是用 multi-fields 同时保留两者:
{
"mappings": {
"properties": {
"city": {
"type": "text",
"fields": {
"raw": { "type": "keyword" }
}
}
}
}
}
检索用 city(分词),聚合/排序用 city.raw(精确)。
bool 查询:filter 与 query 的天壤之别
这是最常被用错的一点。filter 上下文不计算相关性分数、结果可缓存;query 上下文要计算分数。凡是「是否」类条件(状态、时间范围、类目)都应放进 filter。
{
"query": {
"bool": {
"must": [
{ "match": { "title": "手机" } }
],
"filter": [
{ "term": { "status": "on" } },
{ "range": { "price": { "gte": 100, "lte": 5000 } } }
]
}
}
}
must 里的 match 参与算分,而 filter 里的 term/range 既快又可被节点级缓存复用。
聚合:把分析下推到引擎
与其把数据拉回应用层再统计,不如直接用 ES 聚合:
{
"size": 0,
"aggs": {
"by_category": {
"terms": { "field": "category.raw", "size": 10 }
},
"price_stats": {
"stats": { "field": "price" }
}
}
}
size: 0 表示不返回命中文档,只返回聚合结果,能显著省带宽。
索引设置:分片与副本
分片不是越多越好。经验公式:
- 单分片大小控制在 10GB ~ 50GB 之间。
- 分片数 ≈ 数据总量 / 单分片目标大小。
- 副本数 = 读吞吐需求 / 单节点能力,并至少为 1 以保证高可用。
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"refresh_interval": "30s"
}
}
把 refresh_interval 调大到 30s 能显著降低近实时刷新带来的写入放大(适合日志类场景)。
批量写入与缓存
bulk 而非单条
curl -XPOST "localhost:9200/orders/_bulk" -H 'Content-Type: application/json' --data-binary @bulk.jsonl
每批次建议 5MB ~ 15MB,过大反而触发队列堆积。
善用缓存
- Node query cache:
filter结果按节点缓存。 - Shard request cache:聚合/无
size的请求结果缓存。 - Fielddata / doc_values:排序聚合依赖列存,避免对
text直接聚合。
相关性调优(BM25)
默认 BM25 已不错,但可叠加业务权重:
{
"query": {
"bool": {
"should": [
{ "match": { "title": { "query": "手机", "boost": 3 } } },
{ "match": { "description": { "query": "手机", "boost": 1 } } }
]
}
}
}
标题命中权重是描述的 3 倍。function_score 还能引入时间衰减、销量等外部因子。
接口鉴权(如写入 ES 的 Service Token)可用 JWT 解码 工具快速查看 payload 中的过期时间与权限声明。
常见问题 FAQ
Q1:为什么 terms 聚合对 text 字段报错?
text 字段被分词,无法做精确聚合;改用 .raw(keyword)子字段。
Q2:filter 和 must 哪个更快?
filter 更快且不参与算分、可缓存,凡是「硬性条件」优先放 filter。
Q3:分片数设多少合适?
按总数据量反推,单分片 10~50GB,避免过多导致集群元数据开销飙升。
Q4:写入慢怎么排查?
先看是否单条写入(应改 bulk)、refresh_interval 是否过小、副本是否过多。
Q5:中文搜索效果差怎么办?
换用 IK / pinyin 等中文分词器,并在 mapping 里为检索字段指定对应 analyzer。
工具推荐
在 Elasticsearch 开发中,以下 工具库 能帮上忙:
- JSON 格式化 — 整理 mapping 与 DSL 请求体
- SQL 格式化 — 对照 SQL 思路设计 ES 查询
- Base64 编码 — 处理文档 ID / 二进制载荷
- JWT 解码 — 查看写入鉴权 Token 的声明
Elasticsearch 的性能,七分在写入前的 mapping 与 analyzer 设计,三分在查询时的 filter/聚合取舍。把「该算的提前算、该缓存的缓存住」,集群才能在亿级数据下依然毫秒响应。
本站提供浏览器本地工具,免注册即可试用 →