Elasticsearch 搜索与相关性调优:映射、查询、聚合与性能

数据库

Elasticsearch 为什么值得认真调优

Elasticsearch 的「开箱即用」往往只适合 demo。一旦数据量上亿、QPS 上千,未调优的 mapping 和查询会迅速拖垮集群:写入阻塞、查询毛刺、堆内存溢出。调优的本质是在写入时把该算的算完,在查询时只做必要的工作

维度 未调优 调优后
字段类型 textkeyword 按检索/聚合需求精确拆分
过滤条件 写在 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 cachefilter 结果按节点缓存。
  • 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 开发中,以下 工具库 能帮上忙:


Elasticsearch 的性能,七分在写入前的 mapping 与 analyzer 设计,三分在查询时的 filter/聚合取舍。把「该算的提前算、该缓存的缓存住」,集群才能在亿级数据下依然毫秒响应。

本站提供浏览器本地工具,免注册即可试用 →

#Elasticsearch#搜索#相关性#索引#聚合#全文检索#性能调优