2. ES查询基础回顾

在深入慢查询优化之前,必须牢固掌握 Elasticsearch 查询的核心基础。本章将系统回顾查询 DSL 的结构化组成、query/filter 上下文的本质区别、相关性评分的基本原理,以及分片与副本数量对查询性能与准确性的具体影响。这些概念是后续分析慢查询根因和设计压测方案的基石。

1. 查询 DSL 的结构化组成

Elasticsearch 使用基于 JSON 的领域特定语言(DSL)来构建查询。一个完整的查询请求通常包含以下层级结构:

层级 JSON 键 说明 示例值
根查询 query 查询入口,必须包含 {}
查询子句 bool, match, term, range 等 定义具体的匹配逻辑 {"match": {"title": "ES"}}
过滤子句 filter 在 bool 查询中用于精确过滤 {"term": {"status": "published"}}
排序 sort 控制返回文档的顺序 [{"timestamp": "desc"}]
分页 from, size 控制返回结果的偏移量与数量 0, 20
高亮 highlight 标记匹配的片段 {"fields": {"content": {}}}
// 一个典型的复合查询 DSL 示例
GET /my_index/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "title": "Elasticsearch 优化" } }
      ],
      "filter": [
        { "term": { "status": "active" } },
        { "range": { "publish_date": { "gte": "2024-01-01" } } }
      ]
    }
  },
  "sort": [
    { "publish_date": "desc" }
  ],
  "from": 0,
  "size": 20
}
优化提示: 在构建 DSL 时,尽量将不需要参与评分的条件放入 filter 上下文。这不仅能利用缓存,还能减少评分计算的开销,是慢查询优化的第一道防线。

2. query 上下文与 filter 上下文

理解 query 和 filter 上下文的区别,是掌握 ES 查询性能的关键。两者在计算逻辑、缓存行为和性能表现上存在本质差异。

对比维度 query 上下文 filter 上下文
核心任务 计算相关性评分 _score 判断文档是否匹配(是/否)
评分计算 必须计算,影响排序 不计算,_score 恒为 0
缓存机制 默认不缓存结果 自动缓存 bitset,可复用
适用场景 全文搜索、模糊匹配、需要按相关性排序 精确值匹配、范围过滤、状态筛选
性能特征 相对较慢,CPU 密集 非常快,内存友好
实践原则: 能用 filter 解决的问题,绝不用 query。例如,筛选“状态为已发布”或“日期在最近 7 天”这类条件,应始终放在 filter 中。这能显著降低查询延迟,尤其是在高并发场景下。

3. 相关性评分原理

相关性评分是 ES 全文搜索的核心。默认使用 BM25 算法,它基于词频(TF)和逆文档频率(IDF)计算每个匹配词项对文档得分的贡献。

  • 词频(TF):词项在文档中出现的次数越多,该文档的相关性越高。但 BM25 对 TF 进行了饱和处理,防止高频词过度主导。
  • 逆文档频率(IDF):词项在整个索引中出现的文档数越少(即越罕见),该词项的权重越高。
  • 字段长度归一化:字段内容越短,匹配时获得的权重越高。短标题匹配比长正文匹配更相关。
// BM25 评分公式简化示意
_score = IDF * (TF * (k1 + 1)) / (TF + k1 * (1 - b + b * (fieldLength / avgFieldLength)))

// 其中:
// k1 控制 TF 饱和曲线(默认 1.2)
// b 控制字段长度归一化程度(默认 0.75)
慢查询关联: 当查询涉及大量文本字段(如 match 在多个 text 字段上)或使用复杂的 function_score 时,评分计算会成为 CPU 瓶颈。优化方向包括:减少参与评分的字段、使用 keyword 类型替代 text(当不需要分词时)、或调整 BM25 参数。

4. 分片与副本对查询的影响

分片(primary shard)和副本(replica shard)是 ES 分布式架构的基础,它们直接影响查询的吞吐量、延迟和准确性。

分片与副本对查询的影响示意 分片过多(过度拆分) • 查询需要扇出到更多分片 • 协调节点合并开销增大 • 全局 agg 精度下降(基数估算) • 尾延迟显著升高 分片过少(单分片瓶颈) • 无法充分利用集群并行能力 • 单分片数据量过大,查询变慢 • 写入成为瓶颈 • 无法水平扩展查询吞吐 副本对查询的正面影响 • 增加查询吞吐量(负载均衡) • 提供高可用性(节点故障容错) • 减少查询热点(分散请求) • 不影响评分准确性(同一分片副本数据一致) 副本的代价与注意事项 • 增加磁盘存储开销 • 写入延迟略有增加(同步复制) • 副本过多可能导致集群资源浪费 • 建议副本数 ≤ 节点数 - 1
分片与副本优化建议:
  • 分片数量:建议每个分片的数据量在 20GB ~ 50GB 之间。对于日志类时序数据,可以按时间跨度规划分片。
  • 副本数量:生产环境至少 1 个副本。对于读多写少的场景,可以适当增加副本数(如 2~3)以提升查询吞吐。
  • 查询路由:如果查询条件明确(如按用户 ID 或时间范围),可以使用 routing 将查询定向到特定分片,减少扇出。
  • 准确性考量:分片数量会影响聚合(aggregation)的精度,尤其是基数估算(cardinality)和百分位(percentile)计算。分片越多,误差可能越大。

5. 查询执行流程与分片交互

一个查询请求在 ES 集群中的执行流程,清晰地展示了分片与副本的角色:

  1. 协调节点接收请求:接收客户端请求,解析 DSL,确定目标索引。
  2. 分片选择:协调节点根据索引的分片配置,决定将查询发送到哪些分片(主分片或副本分片均可)。
  3. 分片级查询:每个分片独立执行查询,返回匹配文档的 ID 和评分(query 上下文)或仅返回 ID(filter 上下文)。
  4. 结果合并与排序:协调节点收集所有分片返回的结果,进行全局排序、分页、聚合等操作。
  5. 取回阶段(fetch phase):协调节点根据排序后的文档 ID,向对应分片请求完整的文档内容,最终返回给客户端。
ES 查询执行流程(分片交互) 客户端 协调节点 分片 1(主/副本) 分片 2(主/副本) 分片 N(主/副本)
性能洞察: 协调节点的合并操作是查询延迟的重要组成部分。当分片数量过多或返回结果集过大时,协调节点会成为瓶颈。使用 track_total_hits 控制精确命中数统计,或使用 search_after 替代深度分页,都能有效减轻协调节点的压力。