Skip to content

Elasticsearch 性能优化与排查

ES 线上问题通常不是一句“加机器”就能解决。查询慢、写入慢、搜不到、结果不准、集群 yellow/red、磁盘只读、JVM 抖动、分片未分配,都要从数据模型、查询 DSL、同步链路、分片规划和集群资源一起看。

学习目标

目标需要掌握什么
会排查搜不到主库、同步、ES 文档、Mapping、分词、查询 DSL、权限过滤
会排查搜不准analyzer、同义词、字段权重、filter、_explain
会排查查询慢slowlog、profile、深分页、大聚合、wildcard、脚本排序
会排查写入慢bulk、refresh、副本、merge、thread pool、磁盘 IO
会排查集群异常health、cat shards、allocation explain、磁盘水位
会做优化Mapping、分片、缓存、分页、聚合、冷热数据、限流

总体排查入口

mermaid
flowchart TD
    A["ES 线上问题"] --> B{"问题类型"}
    B -- "搜不到" --> C["查同步和查询条件"]
    B -- "搜不准" --> D["查分词和评分"]
    B -- "查询慢" --> E["查 DSL 和资源"]
    B -- "写入慢" --> F["查 bulk、refresh、merge"]
    B -- "集群异常" --> G["查 health、shards、磁盘"]

第一步先分类,不要一上来重启 ES。

常用排查命令

json
GET /_cluster/health?pretty
GET /_cat/nodes?v
GET /_cat/indices?v&s=store.size:desc
GET /_cat/shards?v
GET /_cluster/allocation/explain
GET /_nodes/stats/jvm,fs,os,thread_pool
GET /_nodes/hot_threads
GET /product_search/_settings
GET /product_search/_mapping
命令看什么
_cluster/health集群 green/yellow/red
_cat/nodes节点 CPU、内存、角色
_cat/indices索引大小、文档量、状态
_cat/shards分片分布和未分配原因
allocation/explain为什么分片不能分配
_nodes/hot_threads当前热点线程
_mapping字段类型是否正确
_settings分片、副本、refresh 等配置

搜不到怎么排查

搜不到通常不是 ES 坏了,而是某一层条件不匹配。

mermaid
flowchart TD
    A["用户说搜不到"] --> B["确认主库是否有数据"]
    B --> C["按 ID 查 ES 文档"]
    C --> D{"ES 是否有文档"}
    D -- "没有" --> E["查同步链路、MQ、CDC、补偿"]
    D -- "有" --> F["查查询 DSL"]
    F --> G["查 Mapping 字段类型"]
    G --> H["用 _analyze 看分词"]
    H --> I["查 filter、权限、状态是否误伤"]

按 ID 查文档:

json
GET /product_search/_doc/10001

看分词:

json
GET /product_search/_analyze
{
  "field": "productName",
  "text": "无线蓝牙降噪耳机 Pro"
}

常见原因:

现象原因处理
主库有,ES 没有同步失败、消息堆积、CDC 延迟查同步日志、MQ Lag、死信、补偿
ES 有,按关键词搜不到分词不匹配、term 查了 text_analyze,改 match 或 Mapping
ES 有,某些用户搜不到权限、租户、状态过滤误伤打印最终 DSL,检查 filter
新写入短时间搜不到refresh 未发生接受近实时或必要时手动 refresh
改了 analyzer 仍搜不到历史倒排未重建重建索引

搜不准怎么排查

搜不准包括:不相关结果排前面、相关结果排后面、召回太少、召回太多。

mermaid
flowchart TD
    A["搜索结果不准"] --> B["查看最终 DSL"]
    B --> C["用 _analyze 看索引和查询分词"]
    C --> D["用 _explain 看评分"]
    D --> E["检查字段 boost"]
    E --> F["检查 should、filter、排序规则"]
    F --> G["调整同义词、权重、业务排序"]

定位单条评分:

json
GET /product_search/_explain/10001
{
  "query": {
    "multi_match": {
      "query": "无线降噪耳机",
      "fields": ["productName^5", "subTitle^2", "tags"]
    }
  }
}

常见原因:

问题原因处理
长详情命中文档排前字段权重不合理标题字段提高 boost
品牌词搜不到分词词典缺品牌词加自定义词典或 keyword 召回
型号词搜不到中英文数字混合分词不合理自定义 analyzer 或额外 keyword 字段
结果太泛should 太多、minimum_should_match 不合理收紧查询条件
业务排序压过相关性销量、新品、运营权重过强调整 function_score 或排序策略

查询慢怎么排查

查询慢先看是不是某类查询慢,而不是所有查询都慢。

mermaid
flowchart TD
    A["查询慢"] --> B["打开 slowlog 或查接口日志"]
    B --> C["找到慢 DSL"]
    C --> D["profile 分析耗时"]
    D --> E{"慢在哪里"}
    E -- "深分页" --> F["限制页数或 search_after"]
    E -- "聚合" --> G["缩小范围和 bucket"]
    E -- "wildcard" --> H["改 ngram 或 search_as_you_type"]
    E -- "脚本/排序" --> I["改字段排序或预计算"]
    E -- "资源" --> J["查 CPU、GC、磁盘、线程池"]

开启慢日志示例:

json
PUT /product_search/_settings
{
  "index.search.slowlog.threshold.query.warn": "2s",
  "index.search.slowlog.threshold.fetch.warn": "1s"
}

profile 示例:

json
GET /product_search/_search
{
  "profile": true,
  "query": {
    "bool": {
      "must": [
        { "match": { "productName": "无线降噪耳机" } }
      ],
      "filter": [
        { "term": { "stockStatus": "IN_STOCK" } }
      ]
    }
  }
}

查询慢全过程:慢在 Query 还是 Fetch

很多人排查 ES 慢查询时只看 DSL,然后直接改索引或加机器。更稳的方式是先把一次查询拆成 Query Phase 和 Fetch Phase,再判断慢在哪一段。

mermaid
flowchart TD
    A["搜索请求慢"] --> B["协调节点接收请求"]
    B --> C["Query Phase"]
    C --> D["分片本地召回、过滤、评分、排序"]
    D --> E["协调节点合并 TopN"]
    E --> F["Fetch Phase"]
    F --> G["按 docId 拉取 _source"]
    G --> H["高亮、字段过滤、返回"]

两段慢的表现不一样:

慢的位置常见原因证据
Query Phase 慢倒排候选多、filter 范围大、评分复杂、排序聚合重、分片多profile 中 query collector、rewrite、score、aggregation 耗时高
Fetch Phase 慢_source 大、高亮字段大、返回字段太多、inner_hits、磁盘缓存未命中slowlog fetch 慢、响应体大、profile query 不慢但接口慢
协调节点合并慢深分页、分片过多、每个分片返回候选多from + size 大、协调节点 CPU 高
集群资源慢CPU、GC、磁盘 IO、线程池排队hot_threads、nodes stats、search rejected

排查时不要只说“ES 慢”。要尽量落到这句话:

这条查询慢主要慢在 Query Phase 的候选召回和排序,还是 Fetch Phase 的取 _source 和高亮,还是协调节点深分页合并。

profile 结果怎么看

profile: true 会告诉你查询在每个分片上的执行细节。它适合低风险环境、测试环境或线上小流量排查,不建议对高频大查询长期打开。

排查思路:

mermaid
flowchart TD
    A["拿到慢 DSL"] --> B["开启 profile"]
    B --> C["看每个 shard 耗时"]
    C --> D{"是否单个 shard 特别慢"}
    D -- "是" --> E["查热点分片、分片大小、节点资源"]
    D -- "否" --> F["看 query / fetch / aggregation 哪段慢"]
    F --> G["定位 match、filter、sort、agg、highlight"]

常见字段可以这样理解:

profile 信息说明常见判断
rewrite_time查询重写耗时wildcard、prefix、复杂 bool 可能高
collector收集 TopN 或聚合结果排序、深分页、聚合常体现在这里
score评分耗时must、BM25、function_score、script_score
next_doc / advance遍历候选文档候选集太大、filter 范围太宽
shard 耗时差异不同分片耗时热点分片、数据倾斜、节点资源不均

如果发现某个分片明显慢,要继续看:

json
GET /_cat/shards/product_search?v
GET /_cat/nodes?v
GET /_nodes/hot_threads

这能判断是不是某个分片太大、某个节点 CPU 高、磁盘 IO 慢,或者热点查询集中在某个 routing。

慢查询证据链

成熟排查不能只靠一句“感觉是深分页”。建议按证据链走:

步骤要拿到的证据目的
1. 找慢接口traceId、接口耗时、最终 DSL确认是不是 ES 查询慢
2. 看 slowlogquery 慢还是 fetch 慢初步定位阶段
3. 看 profile哪个 query、filter、agg、sort 慢定位 DSL 结构问题
4. 看索引信息mapping、settings、分片数、文档量判断模型是否合适
5. 看集群资源CPU、GC、磁盘、线程池 rejected判断是不是资源瓶颈
6. 看业务参数页码、时间范围、聚合维度、权限过滤判断是不是接口没保护
mermaid
flowchart TD
    A["接口慢"] --> B["打印最终 DSL 和 traceId"]
    B --> C["查 ES slowlog"]
    C --> D{"query 慢还是 fetch 慢"}
    D -- "query 慢" --> E["profile 看倒排、filter、sort、agg"]
    D -- "fetch 慢" --> F["看 _source、高亮、返回字段"]
    E --> G["看 mapping、分片、资源"]
    F --> G
    G --> H["按证据优化并压测"]

Query 慢的常见根因和改法

根因为什么慢改法
wildcard: "*abc*"很难通过 term dictionary 快速定位,可能扫描大量 termngram、search_as_you_type、业务限制前缀
script_score每个候选都执行脚本缩小候选集、预计算排序字段
function_score 候选太大加权计算文档太多先 filter 收窄,再打分
terms 参数过大大量 term 匹配和内存开销拆批、改权限模型、预计算可见范围
大范围时间查询候选文档太多限制时间范围、按时间索引、冷热分层
深分页每分片返回 from + sizesearch_after、PIT、限制页数
分片过多协调和查询 fan-out 成本高合理分片、合并小索引

示例:把危险 wildcard 改成受控前缀或 ngram。

危险查询:

json
{
  "query": {
    "wildcard": {
      "productName.keyword": "*耳机*"
    }
  }
}

更稳的方向:

  1. 商品名用合适 analyzer 做全文检索。
  2. 型号、编码、拼音前缀用 edge_ngramsearch_as_you_type
  3. 服务端限制关键词长度和特殊字符。
  4. 对低价值模糊查询限流或降级。

Fetch 慢的常见根因和改法

Fetch 慢经常被忽略,因为大家更容易盯着倒排索引。但列表页返回大 _source、高亮长字段、嵌套文档 inner_hits,都可能让 Fetch 很慢。

根因为什么慢改法
_source 很大读取、解压、网络传输都重列表页只返回必要字段
高亮大字段需要分析文本和生成片段只高亮短字段,限制片段数
返回大数组响应体大,客户端解析慢拆详情接口,不在列表返回
inner_hits 多嵌套文档拉取成本高控制 size,或调整文档模型
跨很多分片 fetch网络 fan-out 多合理分片和 routing

字段过滤示例:

json
GET /product_search/_search
{
  "_source": ["id", "productName", "brandName", "price", "saleStatus"],
  "query": {
    "match": {
      "productName": "无线耳机"
    }
  }
}

列表页不要返回商品详情、评论、富文本、规格大数组。详情页按 ID 再查,或者从 MySQL/业务服务回源校验。

为什么加机器不一定解决慢查询

加机器只能增加资源,不会自动修复坏 DSL、坏 Mapping 和坏分页。

问题加机器效果真正要改
深分页协调节点仍要合并并丢弃大量候选search_after、限制页数
wildcard 前缀星号仍可能扫描大量 term改 analyzer 或 ngram
_source 太大网络和客户端解析仍重控制返回字段
Mapping 错误text/keyword 错用仍然错重建索引
单 routing 热点热点仍可能集中在单分片调整 routing 或拆索引
聚合 bucket 爆炸更多节点也可能被内存打爆控制 bucket、预聚合

面试回答要有边界感:ES 优化不是“调参数和加节点”,而是从业务查询入口、Mapping、DSL、分片、资源和同步链路一起治理。

深分页为什么慢,怎么处理

from + size 深分页的成本会放大。

text
from = 10000
size = 20
分片数 = 5

每个分片可能都要取出前 10020 条候选,协调节点合并后再丢掉前 10000 条。

mermaid
flowchart TD
    A["每个分片返回大量候选"] --> B["协调节点合并排序"]
    B --> C["丢弃 from 之前的数据"]
    C --> D["只返回 size 条"]

处理方式:

方案适合场景
限制最大页数普通商品搜索、后台列表
search_after下一页、无限滚动
PIT + search_after翻页期间保持一致视图
Scroll大批量导出或重建,不适合用户实时翻页
业务游标用时间、ID、排序字段做游标

search_after 示例:

json
GET /product_search/_search
{
  "size": 20,
  "query": {
    "match": {
      "productName": "无线耳机"
    }
  },
  "sort": [
    { "saleCount": "desc" },
    { "id": "desc" }
  ],
  "search_after": [5821, 10001]
}

排序字段必须稳定,最后通常加唯一 ID 兜底。

聚合慢怎么处理

聚合慢常见于:范围太大、bucket 太多、字段类型不合适、分片太多。

问题处理
text 聚合改用 keyword 子字段
时间范围太大限制时间范围或用预聚合
bucket 太多控制 size,避免超大 terms
嵌套聚合太深拆查询或预计算
每次都实时统计缓存热门条件或离线统计

商品筛选项常见聚合:

json
GET /product_search/_search
{
  "size": 0,
  "query": {
    "match": {
      "productName": "耳机"
    }
  },
  "aggs": {
    "brands": {
      "terms": {
        "field": "brandName",
        "size": 20
      }
    }
  }
}

size: 0 表示只要聚合结果,不返回文档列表。

写入慢怎么排查

mermaid
flowchart TD
    A["写入慢"] --> B["看 bulk 大小和失败率"]
    B --> C["看 refresh_interval"]
    C --> D["看副本数"]
    D --> E["看 merge 压力"]
    E --> F["看 thread_pool rejected"]
    F --> G["看磁盘 IO 和 JVM GC"]

常见原因:

原因现象处理
单条写太多网络往返多、吞吐低使用 Bulk
Bulk 太大内存高、失败重试代价大压测合适批次
refresh 太频繁小 segment 多、写入抖动批量导入时调大 refresh
副本多每次写要复制更多副本导入时可临时降低副本
文档太大网络和解析成本高控制 _source 大小,拆字段
merge 压力大IO 高、查询也变慢控制写入速率,观察 merge
磁盘慢或水位高写入、查询都慢清理、扩容、冷热分层

Bulk 怎么用才合理

Bulk 不是越大越好。

json
POST /_bulk
{ "index": { "_index": "product_search", "_id": "10001" } }
{ "id": 10001, "productName": "无线耳机" }
{ "index": { "_index": "product_search", "_id": "10002" } }
{ "id": 10002, "productName": "机械键盘" }

建议:

  1. 按文档大小压测,不只按条数。
  2. 控制并发,避免 thread pool rejected。
  3. 失败要按单条 item 处理,不要只看 HTTP 状态。
  4. 大批量导入时调大 refresh,必要时临时降低副本。
  5. 写入服务要有限流和重试退避。

集群 yellow / red 怎么排查

mermaid
flowchart TD
    A["集群状态异常"] --> B["GET _cluster/health"]
    B --> C{"状态"}
    C -- "yellow" --> D["副本未分配"]
    C -- "red" --> E["主分片不可用"]
    D --> F["GET _cat/shards"]
    E --> F
    F --> G["GET _cluster/allocation/explain"]
    G --> H["处理节点、磁盘、水位、分配规则"]

常见原因:

状态常见原因处理
yellow单节点设置了副本、副本无处放开发环境副本设 0,生产增加节点
yellow磁盘水位高,副本不能分配清理索引、扩容磁盘
red主分片所在节点异常恢复节点,检查快照
red磁盘或索引损坏评估恢复、快照、重建
只读flood stage 水位触发清理磁盘后解除只读

解除只读示例:

json
PUT /*/_settings
{
  "index.blocks.read_only_allow_delete": null
}

前提是磁盘水位已经降下来,否则很快又会触发。

JVM 和内存问题

ES 很依赖 JVM 堆内存,但不是堆越大越好。堆过大可能影响压缩指针和 GC,文件系统缓存也会减少。

常见原则:

  1. 给 ES 足够文件系统缓存,因为 Lucene 查询依赖 OS cache。
  2. 堆内存不要把机器内存吃满。
  3. 关注 old GC、heap used、circuit breaker。
  4. 大聚合、脚本、字段数据加载可能触发内存压力。

排查命令:

json
GET /_nodes/stats/jvm,breaker,thread_pool
GET /_nodes/hot_threads

热点分片

热点分片是指某些分片承担了远高于其他分片的写入或查询压力。

常见原因:

  1. routing 设计不合理,例如所有大客户数据落到一个 routing。
  2. 时间索引中当天索引写入太集中。
  3. 某个租户、店铺、商品特别热门。
  4. 分片大小差距过大。

排查:

json
GET /_cat/shards/product_search?v
GET /_cat/thread_pool/search?v
GET /_nodes/hot_threads

处理:

  1. 调整 routing 策略。
  2. 拆分热点业务索引。
  3. 增加分片或重建索引,但要评估长期成本。
  4. 对热点查询做缓存或预计算。

冷热数据和生命周期

日志、审计、采集明细这类数据通常按时间增长,不应该永久占用热节点。

mermaid
flowchart TD
    A["热数据:最近 7 天"] --> B["热节点,查询和写入频繁"]
    B --> C["温数据:最近 30 天"]
    C --> D["冷数据:历史归档"]
    D --> E["过期删除或快照归档"]

商业建议:

  1. 日志索引用 ILM 管理生命周期。
  2. 热数据保证查询性能。
  3. 冷数据降低副本和存储成本。
  4. 过期数据及时删除或快照。
  5. 不要让日志无限增长挤爆商品搜索集群。

搜索接口保护

不要把 ES DSL 直接暴露给前端或外部系统。

风险:

  1. 用户可以构造深分页。
  2. 用户可以发起大聚合。
  3. 用户可以使用 wildcard 或脚本拖垮集群。
  4. 用户可能绕过权限过滤。

正确做法:

mermaid
flowchart TD
    A["前端业务参数"] --> B["搜索服务校验"]
    B --> C["权限和租户过滤"]
    C --> D["翻译成受控 DSL"]
    D --> E["限制分页、聚合、排序"]
    E --> F["查询 ES"]

生产排查清单

问题优先看什么
搜不到主库、ES 文档、同步日志、Mapping、分词、filter
搜不准_analyze_explain、boost、同义词、业务排序
查询慢slowlog、profile、深分页、大聚合、wildcard、脚本
写入慢bulk、refresh、副本、merge、thread pool、磁盘
集群 yellow副本、节点数、磁盘水位、allocation explain
集群 red主分片、节点故障、磁盘、快照恢复
磁盘只读flood stage、水位、过期索引、ILM
JVM 抖动heap、GC、breaker、大聚合、fielddata
同步延迟MQ Lag、CDC 延迟、消费者耗时、ES 写入失败

关联知识点

知识点继续学习什么
底层原理refresh、segment、query/fetch、BM25
同步与重建索引MQ/CDC 同步、别名切换、补偿
Mapping 与查询字段类型、Query DSL、分页、聚合
集群节点、分片、副本、磁盘水位
消息堆积与背压ES 同步链路堆积排查

小结

ES 排障要分层:先确认是结果问题、性能问题还是集群问题;结果问题看同步、Mapping、分词和 DSL;性能问题看慢日志、profile、深分页、聚合、写入和资源;集群问题看 health、shards、allocation explain、磁盘水位和 JVM。成熟的搜索系统还要用服务端保护 DSL、限制深分页、设计补偿任务、监控同步延迟和集群健康。