根据索引查询文档es的段机制是怎么查的

小豆丁 25天前 ⋅ 20 阅读

假如es我要根据name条件查询某个索引的文档,es的段机制是怎么查的?一个索引最多有多少段?


好的,这是一个非常核心的问题。我们来详细拆解一下 Elasticsearch 如何根据 name 字段进行查询,以及段(Segment)在其中扮演的角色。同时,我也会解答关于段数量限制的问题。

第一部分:根据 name 条件查询的段机制流程

假设你有一个索引,里面是用户数据,你执行以下查询来查找名字为 "Alice" 的人:

JSON
GET /my_index/_search
{
  "query": {
    "term": {
      "name": "Alice"
    }
  }
}

Elasticsearch (Lucene) 会执行以下精妙的、分层的查找过程:

第1步:查询分发与确定分片

  1. 请求到达一个协调节点。
  2. 协调节点根据路由规则确定这个索引的哪些分片(Shard)需要参与查询(对于简单查询,通常是所有分片)。
  3. 将查询请求并行地转发给这些分片所在的各个数据节点。

第2步:在每个分片内,并行搜索所有段

这是最核心的部分。每个数据节点收到请求后,会在目标分片上执行以下操作:

一个分片 = 一个完整的 Lucene 索引 = 由多个段(Segment)组成

对于 name 字段,Lucene 为其建立了一个倒排索引(Inverted Index)。每个段都拥有自己独立的、完整的倒排索引。

查询流程如下:

  1. 遍历段列表:Lucene 会获取该分片下所有最新的、有效的段(Segments)列表。
  2. 按段搜索(Per-Segment Search)
    • 对于每一个段,Lucene 都会执行相同的查找动作:
    • 检查布隆过滤器(Bloom Filter,如果存在):快速判断 "Alice" 这个词是否绝对不存在于该段。如果不存在,立即跳过整个段,极大提升性能。
    • 查找词项字典(Term Dictionary):在段的 .tim 文件中查找 "Alice" 这个词项。词项字典是排序的,可以使用二分查找等算法快速定位。
    • 定位倒排表(Postings List):一旦找到 "Alice" 这个词项,就从 .doc 文件中获取其对应的倒排表。这个列表记录了包含 "Alice" 的所有文档的 ID(Doc ID)以及其它信息(如词频)。
  3. 收集结果:每个段返回自己内部匹配到的文档 ID 列表。
  4. 合并段结果:将来自该分片内所有段的文档 ID 结果合并起来。同时,如果某文档在较新的段中被标记为删除(但在旧的段中存在),它会被过滤掉。
  5. 相关性打分:如果需要打分(如 match 查询),会根据词频、逆文档频率等因素计算每个文档的相关性得分。
  6. 返回:将该分片的最终结果(文档ID和得分)返回给协调节点。

第3步:全局归并

  1. 协调节点收集来自所有相关分片的结果。
  2. 将所有结果进行全局排序(如果需要)、过滤、分页等处理。
  3. 最后,根据排序后的文档 ID,去各个分片拉取完整的文档内容(_source)并返回给客户端。

过程中的缓存加速:

  • OS Cache:最重要的缓存。.tip(词项索引)和 .tim(词项字典)文件如果已被缓存,查找速度极快。
  • Node Query Cache:如果完全相同的查询刚刚在该分片上执行过,且数据未变更,可能会直接返回缓存的结果。
  • Request Cache:如果整个搜索请求被缓存,可能会直接返回完整的 JSON 结果。

第二部分:一个索引最多有多少段?

这是一个非常实践性的问题。答案是:Elasticsearch 本身没有硬编码的段数量上限。理论上,一个分片可以有成千上万个段。

但是,段的数量越多,性能越差。 这就是为什么 ES 要不断进行段合并(Merge)。

为什么段多不好?

  1. 查询性能下降:如第一部分所述,查询需要遍历每一个段。段越多,需要进行的“按段搜索”次数就越多,CPU 和 I/O 开销就越大,查询速度就越慢。
  2. 资源消耗大:每个段都会消耗文件句柄、内存(用于缓存段元数据)。大量段可能导致文件描述符耗尽或内存压力过大。
  3. 删除效率:删除操作只是标记,需要等到段合并时才能真正释放磁盘空间。段越多,未回收的空间可能越多。

如何管理段的数量?—— 段合并(Merge)

Lucene 的后台有一个“合并策略”(默认为 TieredMergePolicy),它会持续地将小段合并成大段,最终目标是减少段的总数量。

  • 过程:选择一批大小相近的小段,将它们合并成一个更大的新段。合并完成后,删除旧的小段。
  • 影响:合并过程非常消耗 I/O 和 CPU,是 ES 写入时的主要开销来源。在合并期间,查询性能可能会受到一定影响。

最佳实践与段数量建议

虽然没有绝对上限,但通常建议一个分片中的段数量保持在几十到一百以内。一个健康的分片,其段数量通常是个位数或两位数。

如果你通过 GET /_cat/segments?v 命令发现某个分片的段数量高达数百甚至数千,这通常意味着:

  1. 索引正在承受巨大的写入压力,合并速度跟不上写入速度。
  2. 可能发生了“合并风暴”,需要调整合并策略(但通常不建议)。
  3. 索引是只读的,并且从未进行过强制合并。

对于只读索引(如历史数据),你可以执行强制合并来优化它:

BASH
POST /my_index/_forcemerge?max_num_segments=1
  • max_num_segments=1:表示最终合并成 1 个段。警告:此操作对资源消耗极大,且不可逆,只能在只读索引上执行。
  • 通常,对于仍在写入的索引,不建议强制合并,或者可以设置为一个合理的值,如 max_num_segments=10

总结

  1. 查询机制:ES 的查询是 “分片 -> 段” 的两级并行过程。在每个分片内,查询需要依次遍历每一个段,查找其内部的倒排索引,最后合并结果。OS Cache 是加速此过程的关键。
  2. 段数量没有硬性上限,但越少越好。段过多是性能的“毒药”。Lucene 会通过后台合并来努力减少段的数量。对于不再写入的索引,可以使用 _forcemerge 来手动优化段数量,提升查询性能。一个健康的分片,其段数量通常维持在几十个以内。

全部评论: 0

    我有话说: