← 返回文章列表

Elasticsearch 底层原理系列(四):Segment 合并机制

2020-11-11·4 分钟阅读

前言

在上一章我们了解到,每次 Refresh 都会生成一个新的 Segment。随着写入的进行,Segment 数量会不断增加。过多的 Segment 会影响查询性能,因此 Lucene 会定期执行 Segment 合并(Merge)操作。

本章将深入解析 Segment 合并机制,帮助理解 ES 如何在后台自动优化索引结构。

技术亮点

技术点难度面试价值本文覆盖
Segment 合并原理⭐⭐⭐高频考点
Merge Policy⭐⭐⭐⭐进阶考点
Force Merge⭐⭐⭐常见问题
合并性能优化⭐⭐⭐⭐实战价值

面试考点

  1. 为什么需要 Segment 合并?
  2. Lucene 的 Merge Policy 是怎样的?
  3. Force Merge 什么时候使用?有什么风险?
  4. 如何优化 Segment 合并性能?

为什么需要 Segment 合并

Segment 过多的问题

┌─────────────────────────────────────────────────────────────────────────┐
│                    Segment 过多的问题                                    │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  每个 Refresh 生成一个 Segment:                                         │
│                                                                         │
│  1秒/次 × 3600秒 = 3600 个 Segment/小时                                 │
│                                                                         │
│  问题:                                                                 │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  查询时:                                                       │   │
│  │  • 需要遍历所有 Segment                                         │   │
│  │  • 每个 Segment 需要打开文件句柄                                 │   │
│  │  • 每个 Segment 需要加载 FST 到内存                             │   │
│  │  • 结果合并开销增大                                             │   │
│  │                                                                 │   │
│  │  文件系统:                                                     │   │
│  │  • 文件句柄耗尽                                                 │   │
│  │  • 文件系统性能下降                                             │   │
│  │                                                                 │   │
│  │  内存占用:                                                     │   │
│  │  • 每个 Segment 的元数据占用内存                                │   │
│  │  • FST 索引占用内存                                             │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

合并的好处

┌─────────────────────────────────────────────────────────────────────────┐
│                    Segment 合并的好处                                    │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  Before Merge (100 个小 Segment):                                      │
│  ┌───┐┌───┐┌───┐┌───┐┌───┐     ┌───┐                                 │
│  │ S1││ S2││ S3││ S4││ S5│ ... │S100│                                 │
│  └───┘└───┘└───┘└───┘└───┘     └───┘                                 │
│    │     │     │     │     │       │                                   │
│    ▼     ▼     ▼     ▼     ▼       ▼                                   │
│  每个查询需要访问 100 个 Segment                                         │
│                                                                         │
│                              │                                          │
│                              ▼ Merge                                    │
│                                                                         │
│  After Merge (10 个大 Segment):                                        │
│  ┌─────────┐┌─────────┐┌─────────┐     ┌─────────┐                    │
│  │  Big S1 ││  Big S2 ││  Big S3 │ ... │ Big S10 │                    │
│  └─────────┘└─────────┘└─────────┘     └─────────┘                    │
│       │          │          │              │                           │
│       ▼          ▼          ▼              ▼                           │
│  每个查询只需访问 10 个 Segment                                          │
│                                                                         │
│  合并的好处:                                                           │
│  ✅ 减少文件句柄数量                                                    │
│  ✅ 减少内存占用                                                        │
│  ✅ 提高查询速度                                                        │
│  ✅ 清理已删除文档                                                      │
│  ✅ 提高压缩率                                                          │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Segment 合并流程

合并过程详解

┌─────────────────────────────────────────────────────────────────────────┐
│                    Segment 合并流程                                      │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  Step 1: 选择待合并的 Segment                                           │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  根据 Merge Policy 选择:                                       │   │
│  │  • 相似大小的 Segment                                           │   │
│  │  • 小 Segment 优先合并                                          │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                      │                                                  │
│                      ▼                                                  │
│  Step 2: 创建新的大 Segment                                             │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  合并 Segment 1 + Segment 2 + Segment 3                         │   │
│  │                                                                 │   │
│  │  ┌─────┐   ┌─────┐   ┌─────┐                                   │   │
│  │  │ S1  │ + │ S2  │ + │ S3  │                                   │   │
│  │  │10MB │   │12MB │   │8MB  │                                   │   │
│  │  └─────┘   └─────┘   └─────┘                                   │   │
│  │       │         │         │                                     │   │
│  │       └─────────┼─────────┘                                     │   │
│  │                 ▼                                               │   │
│  │           ┌───────────┐                                         │   │
│  │           │  New S    │  (约 30MB,实际会更小因为压缩)           │   │
│  │           └───────────┘                                         │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                      │                                                  │
│                      ▼                                                  │
│  Step 3: 更新 Commit Point                                              │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  新 Commit Point:                                              │   │
│  │  • 包含新的大 Segment                                           │   │
│  │  • 排除旧的小 Segment                                           │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                      │                                                  │
│                      ▼                                                  │
│  Step 4: 删除旧 Segment                                                 │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  旧的 S1, S2, S3 被删除                                         │   │
│  │  (此时没有 reader 在使用它们)                                  │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

删除文档的处理

┌─────────────────────────────────────────────────────────────────────────┐
│                    合并时清理删除文档                                    │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  Segment 是不可变的,删除只是标记:                                      │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  Segment 1:                                                     │   │
│  │  ┌───────────────────────────────────────────────────────┐     │   │
│  │  │ Doc 1 │ Doc 2 │ Doc 3 │ Doc 4 │ Doc 5                 │     │   │
│  │  │ 活跃  │ 删除   │ 活跃  │ 删除   │ 活跃                 │     │   │
│  │  └───────────────────────────────────────────────────────┘     │   │
│  │                                                                 │   │
│  │  合并时:                                                       │   │
│  │  ┌───────────────────────────────────────────────────────┐     │   │
│  │  │ Doc 1 │ Doc 3 │ Doc 5                                 │     │   │
│  │  │ 活跃  │ 活跃  │ 活跃                                  │     │   │
│  │  └───────────────────────────────────────────────────────┘     │   │
│  │                                                                 │   │
│  │  被标记删除的文档在合并时被真正删除                              │   │
│  │  磁盘空间被回收                                                 │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  重要:这就是为什么删除文档不会立即释放空间!                             │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Merge Policy(合并策略)

TieredMergePolicy

ES 默认使用 TieredMergePolicy,其核心思想是:

┌─────────────────────────────────────────────────────────────────────────┐
│                    TieredMergePolicy 策略                               │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  核心原则:                                                              │
│  1. 合并相似大小的 Segment                                              │
│  2. 小 Segment 优先合并                                                 │
│  3. 限制合并层级数量                                                    │
│                                                                         │
│  Segment 层级示意:                                                      │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                                                                 │   │
│  │  Level 0 (最小):  S1  S2  S3  S4  S5  S6  S7  S8              │   │
│  │                    │   │   │   │                               │   │
│  │                    └───┼───┘                                   │   │
│  │                        ▼                                       │   │
│  │  Level 1:           M1  M2                                     │   │
│  │                      │   │                                     │   │
│  │                      └───┘                                     │   │
│  │                          ▼                                     │   │
│  │  Level 2 (最大):        L1                                     │   │
│  │                                                                 │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  关键配置:                                                             │
│  • index.merge.policy.expunge_deletes_allowed: 10                      │
│    (删除文档占比超过 10% 时触发合并)                                    │
│  • index.merge.policy.floor_segment: 2MB                               │
│    (小于此大小的 Segment 被视为同一层级)                                │
│  • index.merge.policy.max_merge_at_once: 10                            │
│    (一次最多合并 10 个 Segment)                                         │
│  • index.merge.policy.segments_per_tier: 10                            │
│    (每层允许的最大 Segment 数量)                                        │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

合并触发条件

┌─────────────────────────────────────────────────────────────────────────┐
│                    合并触发条件                                         │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  1. 自动触发(后台线程)                                                │
│     • 新 Segment 产生时检查是否需要合并                                 │
│     • 根据策略自动选择待合并的 Segment                                  │
│                                                                         │
│  2. 删除文档占比触发                                                    │
│     • 已删除文档占比超过 expunge_deletes_allowed                        │
│     • 优先合并包含大量删除的 Segment                                    │
│                                                                         │
│  3. 手动触发                                                            │
│     • Force Merge API                                                  │
│     • 强制合并到指定数量的 Segment                                      │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Force Merge

使用场景

┌─────────────────────────────────────────────────────────────────────────┐
│                    Force Merge 使用场景                                  │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  适用场景:                                                              │
│  ✅ 静态索引(不再更新的数据)                                          │
│  ✅ 历史日志数据                                                        │
│  ✅ 归档数据                                                            │
│  ✅ 批量导入完成后的索引                                                │
│                                                                         │
│  不适用场景:                                                            │
│  ❌ 活跃写入的索引                                                      │
│  ❌ 频繁更新的索引                                                      │
│  ❌ 实时数据流                                                          │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Force Merge API

// 合并到 1 个 Segment
POST /my_index/_forcemerge?max_num_segments=1

// 合并到 5 个 Segment
POST /my_index/_forcemerge?max_num_segments=5

// 只合并包含删除文档的 Segment
POST /my_index/_forcemerge?only_expunge_deletes=true

Force Merge 的风险

┌─────────────────────────────────────────────────────────────────────────┐
│                    Force Merge 风险警告                                  │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ⚠️ 重要警告:                                                          │
│                                                                         │
│  1. 重量级操作                                                          │
│     • 消耗大量 CPU 和 I/O                                               │
│     • 可能影响其他操作                                                  │
│                                                                         │
│  2. 不应频繁执行                                                        │
│     • ES 已经有自动合并机制                                             │
│     • 频繁 force merge 会造成浪费                                       │
│                                                                         │
│  3. 合并后 Segment 很大                                                 │
│     • 自动合并可能跳过大 Segment                                        │
│     • 后续更新效率降低                                                  │
│                                                                         │
│  4. 可能导致段合并风暴                                                  │
│     • 多个分片同时合并                                                  │
│     • 系统资源耗尽                                                      │
│                                                                         │
│  最佳实践:                                                             │
│  • 只对静态索引执行 force merge                                         │
│  • 在业务低峰期执行                                                     │
│  • 使用 max_num_segments=1 要谨慎                                       │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

Merge 性能调优

配置参数

PUT /my_index/_settings
{
  "index": {
    "merge": {
      "scheduler": {
        "max_thread_count": 1      // 合并线程数
      },
      "policy": {
        "max_merged_segment": "5gb",    // 合并后最大 Segment 大小
        "segments_per_tier": 10,         // 每层 Segment 数量
        "max_merge_at_once": 10          // 一次合并数量
      }
    }
  }
}

调优建议

┌─────────────────────────────────────────────────────────────────────────┐
│                    Merge 调优建议                                        │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  1. 控制合并线程数                                                      │
│     • 默认:max_thread_count = Math.max(1, cores/2)                     │
│     • SSD:可适当增加                                                   │
│     • HDD:建议设为 1                                                   │
│                                                                         │
│  2. 调整合并策略参数                                                    │
│     • 大索引:增大 max_merged_segment                                   │
│     • 高写入:减小 segments_per_tier                                    │
│                                                                         │
│  3. 监控合并速度                                                        │
│     • GET /_cat/segments?v                                             │
│     • GET /_nodes/stats/indices/merges                                 │
│                                                                         │
│  4. 控制 refresh 频率                                                   │
│     • refresh 越频繁,Segment 越多                                      │
│     • 批量写入时增大 refresh_interval                                   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

监控命令

# 查看 Segment 信息
GET /_cat/segments/my_index?v&h=index,shard,segment,size,size.memory

# 查看合并统计
GET /_nodes/stats/indices/merges

# 查看索引统计
GET /my_index/_stats?filter_path=indices.my_index.total.merges

Segment 状态监控

关键指标

┌─────────────────────────────────────────────────────────────────────────┐
│                    Segment 监控指标                                      │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  通过 _cat/segments API 查看:                                          │
│                                                                         │
│  index   shard segment  size    docs  del  del_count                   │
│  my_idx  0     _0       5mb    1000   0    0                           │
│  my_idx  0     _1       3mb     500   0    0                           │
│  my_idx  0     _2       1mb     200   1    50    ← 有删除文档          │
│                                                                         │
│  关键字段:                                                             │
│  • size: Segment 大小                                                  │
│  • docs: 活跃文档数                                                    │
│  • del: 是否有删除文档                                                  │
│  • del_count: 删除文档数                                                │
│                                                                         │
│  健康指标:                                                             │
│  • Segment 数量 < 每分片 100 个                                         │
│  • 单个 Segment 大小 10MB - 5GB                                        │
│  • 删除文档占比 < 10%                                                   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

总结

本章深入解析了 Segment 合并机制:

要点说明
合并目的减少文件句柄、提高查询效率、清理删除文档
触发时机自动触发(策略决定)、删除占比触发、手动触发
合并策略TieredMergePolicy,合并相似大小的 Segment
Force Merge仅用于静态索引,避免频繁使用
调优关键控制线程数、调整策略参数、监控合并状态

参考资料

下一章预告

下一章将探讨 查询执行原理,包括:

  • Query DSL 解析与执行
  • 评分机制(BM25)
  • 查询优化策略
分享: