如何优化 ElasticSearch 搜索性能?

#中间件#面试八股

如何优化 ElasticSearch 搜索性能?

结论先行:优化分四层——索引设计(字段类型、分片数、副本)、查询写法(filter 优先、避免前缀通配、控制返回字段)、写入策略(批量、refresh 间隔、translog)、运维(冷热分离、段合并、慢日志)。多数“ES 慢”不是集群不行,而是映射错了或查询写错了。

一、四层优化清单

层次手段收益
映射精确值用 keyword,全文用 text,不需要检索的字段 index:false减小倒排、加快写入
映射doc_values:false 关闭不需要排序聚合的字段省磁盘与内存
映射只保留必要字段的 _source,或用 includes减少回表 IO
分片单分片 10~50GB,分片数 ≈ 数据量 / 30GB避免过多协调开销
查询filter 代替 must,不算分可缓存常见 2~10 倍提升
查询避免 wildcard 前缀 *abc、regexp、脚本查询避免全词典扫描
查询只取需要的字段 _source: ["id","title"]减少网络与反序列化
查询用 search_after 替代深分页内存恒定
写入批量 bulk(每批 5~15MB),调大 refresh_interval提升吞吐数倍
运维冷热分离、force merge 只读索引、慢日志稳定长尾

二、索引设计示例

PUT /article
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1,
    "refresh_interval": "5s",
    "translog": { "durability": "async", "sync_interval": "30s" }
  },
  "mappings": {
    "dynamic": "strict",
    "properties": {
      "title":   { "type": "text", "analyzer": "ik_max_word" },
      "tags":    { "type": "keyword" },
      "content": { "type": "text", "index": true, "norms": false },
      "view_cnt":{ "type": "integer", "doc_values": true },
      "raw_html":{ "type": "keyword", "index": false, "doc_values": false }
    }
  }
}

refresh_interval 从 1s 调到 5s,写入吞吐通常能提升 2~5 倍;代价是数据可见延迟变长,导出类索引可以调到 30s 甚至 -1。

三、查询写法对比

// 慢:must 参与打分,且前缀通配无法走词典
{ "query": { "bool": {
    "must": [
      { "match": { "title": "分布式" } },
      { "wildcard": { "tags": "*缓存*" } }
    ]
} } }

// 快:过滤条件放 filter,精确匹配用 term
{ "query": { "bool": {
    "filter": [
      { "term": { "tags": "cache" } },
      { "range": { "publish_time": { "gte": "now-30d/d" } } }
    ],
    "must": [ { "match": { "title": "分布式" } } ]
}, "_source": ["id", "title", "publish_time"] }
反模式问题替代
wildcard: "*x*"遍历全部 term分词后用 match,或用 ngram
script 排序每文档执行脚本写入时算好冗余字段
terms 传上万个值查询解析开销大拆批或改用 bitmap 方案
from+size 翻 1000 页内存随页码增长search_after
聚合无 size 限制返回海量桶terms.size 限定 + composite 分页

四、定位慢查询

// 1. 慢日志
PUT /article/_settings
{ "index.search.slowlog.threshold.query.warn": "1s",
  "index.search.slowlog.threshold.fetch.warn": "500ms" }

// 2. profile 看时间花在哪
GET /article/_search
{ "profile": true, "query": { "match": { "title": "分布式" } } }

// 3. 看分片是否倾斜
GET /_cat/shards/article?v&h=index,shard,prirep,docs,store,node
现象可能原因
只有个别分片慢分片倾斜、热点文档
fetch 阶段慢_source 太大、返回字段太多
query 阶段慢通配符、脚本、range 范围过大
写入慢且 CPU 高段太多、merge 跟不上

五、容量与运维

分片规划:
  单分片 10~50GB,日增 100GB 的索引按月建(article-2026.11)
  副本数 ≥1 保证高可用,读多写少可加副本分摊查询
冷热分离:
  热节点 SSD 存近 7 天,温节点 HDD 存历史,用 ILM 自动迁移
只读索引:
  force merge 到 1 个段,减少段数量与内存开销

常见追问

  • 追问:分片是不是越多越好?不是。每个分片都是一个 Lucene 实例,有固定开销(文件句柄、堆内存、集群状态),分片过多会让协调与恢复变慢。
  • 追问:refresh_interval 调大影响什么?只影响“写入后多久可被搜索到”,不影响持久化(持久化由 translog 保证)。
  • 追问:副本能提升写入性能吗?不能,写入要同步到副本,副本越多写入越慢;副本提升的是读吞吐与可用性。

小结

  • 先看映射与查询写法,再看分片与运维,最后才考虑加机器。
  • filter 代替 must、精确值用 keyword 是性价比最高的两条。
  • 写入侧靠批量与 refresh_interval 换吞吐。
  • 慢日志 + profile + 分片分布,是定位问题的三件套。
笔记加载中…