基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

如何优化 Elasticsearch 的查询性能(Search Performance)?

知识点图片

优化 Elasticsearch (ES) 的查询性能是一个系统工程,通常需要从查询语句编写索引设计集群配置以及硬件资源四个维度入手。

以下是详细的优化指南:


一、 查询语句层面的优化 (Query DSL)

这是成本最低、见效最快的方法。

  1. 多用 Filter Context,少用 Query Context

    • 原理filter 子句不计算相关性评分(Score),且结果会被节点缓存(Node Query Cache)。query 子句需要计算评分,无法缓存。
    • 做法:对于不需要评分的过滤条件(如状态、时间范围、ID),务必放入 bool 查询的 filter 中。
    • 示例
      json
      GET /_search
      {
        "query": {
          "bool": {
            "must": { "match": { "title": "search" }},
            "filter": { "term": { "status": "active" }}  <-- 放在这里
          }
        }
      }
  2. 避免使用高昂的查询操作

    • 避免前缀通配符:如 *keyword。这会导致扫描所有倒排索引。尽量使用 keyword*(前缀匹配)。
    • 慎用 Regexp 和 Wildcard:正则和通配符查询非常消耗 CPU。
    • 少用 Script:脚本查询(Painless)通常不能利用索引结构,需要遍历文档。如果必须用,优先使用 stored script 并传入参数,或者在写入时预处理数据。
  3. 限制返回字段 (_source filtering)

    • 如果不需要文档的全部字段,使用 _source 参数指定返回字段,减少网络传输和反序列化开销。
  4. 日期舍入 (Date Rounding)

    • 在使用时间范围查询时,使用 now/m 而不是 now
    • 原因now 是毫秒级变化的,会导致缓存失效。now/m (按分钟取整) 可以让缓存生效一分钟。
  5. 避免深度分页 (Deep Paging)

    • from + size 超过 10,000 条数据时性能急剧下降(因为需要排序和截断)。
    • 优化:使用 search_after API 进行滚动查询,或者使用 Scroll API(仅用于导出数据,不用于实时查询)。

二、 索引与建模层面的优化 (Index & Mapping)

在数据写入前设计好 Schema 是性能的关键。

  1. 选择合适的数据类型

    • Keyword vs Text:不需要分词搜索的字段(如 ID、枚举、标签),务必设为 keywordtext 字段会分词,占用更多空间且聚合慢。
    • 数值类型:够用就好。能用 integer 别用 long,能用 float 别用 double
    • 禁用不需要的特性
      • 如果不搜索该字段:index: false
      • 如果不聚合/排序该字段:doc_values: false
      • 如果不算分:norms: false (对 text 字段节省大量内存)
  2. 使用 copy_to 聚合字段

    • 如果经常需要跨多个字段搜索(如同时搜 first_namelast_name),在 Mapping 中使用 copy_to 将它们复制到一个 full_name 字段,然后只搜这一个字段,比 multi_match 更快。
  3. 预索引数据 (Pre-indexing)

    • 以空间换时间。不要在查询时通过脚本计算(如 doc['price'].value * doc['quantity'].value)。
    • 在写入数据时,直接算好存入一个新字段 total_amount
  4. 分片 (Shards) 与 副本 (Replicas) 策略

    • 分片大小:单个分片建议在 10GB - 50GB 之间。分片过多会导致元数据管理开销大,查询时这就变成了 Map-Reduce 的负担。
    • 副本数量:增加 number_of_replicas 可以直接提升读取吞吐量(Read Throughput),因为查询可以在主分片或副本上并行执行。
  5. 强制合并 (Force Merge)

    • 对于只读索引(如按天生成的日志索引),执行 _forcemerge 将 Segment 合并为 1 个。这能极大减少文件句柄打开数和搜索时的 Segment 扫描数。
  6. 自定义路由 (Custom Routing)

    • 如果你的查询总是基于某个 ID(如 UserID),写入时指定 routing=UserID
    • 查询时也带上 routing=UserID,ES 就只需要去这一个分片查,而不是广播到所有分片,性能提升数倍。

三、 聚合查询优化 (Aggregations)

  1. 使用 keyword 字段聚合:永远不要对 text 字段开启 fielddata: true 进行聚合,这会撑爆堆内存。
  2. Breadth_first 模式:对于基数(Cardinality)很大但只需要返回 Top N 的聚合(如嵌套聚合),设置 "collect_mode": "breadth_first"
  3. Sampler Aggregation:如果数据量极大,可以使用 sampler 聚合先取样,再在样本上做聚合,牺牲少量精度换取极高的速度。
  4. Composite Aggregation:如果需要导出全部聚合结果,使用 composite 聚合代替 terms 聚合的分页。

四、 集群与硬件配置 (Infrastructure)

  1. 文件系统缓存 (Filesystem Cache) 是核心

    • Elasticsearch 严重依赖操作系统的文件系统缓存(Lucene 索引文件就在这里)。
    • 黄金法则:不要把所有内存都分给 JVM Heap。建议配置 Heap Size 为物理内存的 50%,且不超过 32GB(为了利用 Compressed OOPs)。剩下的 50% 留给操作系统做缓存。
  2. 使用 SSD

    • 机械硬盘(HDD)是 ES 性能的杀手。必须使用 NVMe 或高性能 SSD。
    • 如果是云盘,注意 IOPS 和吞吐量限制。
  3. Swap 必须关闭

    • 内存交换会导致 GC 停顿时间剧增,甚至导致节点无响应。在 elasticsearch.yml 中配置 bootstrap.memory_lock: true
  4. Refresh Interval

    • 默认 refresh_interval 是 1s。这意味着写入 1 秒后才能被搜到。
    • 如果业务允许,将其调大(如 30s)或在批量导入时临时设为 -1。这能减少 Segment 的生成,降低系统负载,间接提升搜索性能。

五、 排查与诊断工具

如果不知道慢在哪里,优化就是盲人摸象。

  1. Profile API

    • 在查询中添加 "profile": true,ES 会返回详细的执行计划,告诉你哪个分片、哪个查询子句、哪个阶段(Lucene 层面)耗时最长。
  2. Slow Log (慢查询日志)

    • 配置 index.search.slowlog.threshold。例如记录超过 500ms 的查询,分析日志中的查询语句。
  3. Hot Threads

    • 使用 GET /_nodes/hot_threads 查看当前节点 CPU 都在干什么(是在做正则匹配?还是在做 JSON 解析?)。

总结 Checklist

  1. 查询:用了 Filter 吗?去掉了前缀通配符吗?
  2. Schema:字段类型对了吗?不需要的 doc_values 关了吗?
  3. 内存:Heap 设为 50% 且 < 32GB 了吗?Swap 关了吗?
  4. 分片:分片大小在 30GB 左右吗?副本数够吗?
  5. 缓存:利用好 OS Cache 了吗?
00:00
00:00