IndexLib(6):Segment 合并策略

18 分钟阅读

发布于:

在上一篇文章中,我们深入了解了版本管理和增量更新的机制。本文将继续深入,详细解析 Segment 合并策略的实现,这是理解 IndexLib 如何优化索引结构和提高查询性能的关键。

Segment 合并策略概览:从合并策略到合并执行的完整流程:

flowchart TB
    Start([Segment 合并策略概览<br/>Segment Merge Strategy Overview]) --> StrategyLayer[合并策略层<br/>Merge Strategy Layer]
    
    subgraph StrategyGroup["合并策略 Merge Strategy"]
        direction TB
        S1[MergeStrategy<br/>策略接口<br/>合并策略抽象接口]
        S2[OptimizeMergeStrategy<br/>优化合并策略<br/>优化索引结构]
        S3[RealtimeMergeStrategy<br/>实时合并策略<br/>实时合并小Segment]
        S4[ShardBasedMergeStrategy<br/>分片合并策略<br/>按分片合并]
        S1 --> S2
        S1 --> S3
        S1 --> S4
    end
    
    StrategyLayer --> PlanLayer[合并计划层<br/>Merge Plan Layer]
    
    subgraph PlanGroup["合并计划 Merge Plan"]
        direction TB
        P1[MergePlan<br/>合并计划<br/>合并任务计划]
        P2[SegmentMergePlan<br/>Segment合并计划<br/>Segment合并详情]
        P3[目标版本<br/>Target Version<br/>合并后的目标版本]
        P1 --> P2
        P1 --> P3
    end
    
    PlanLayer --> ExecutionLayer[合并执行层<br/>Merge Execution Layer]
    
    subgraph ExecutionGroup["合并执行 Merge Execution"]
        direction TB
        E1[VersionMerger<br/>版本合并器<br/>执行版本合并]
        E2[IndexMergeOperation<br/>合并操作<br/>索引合并操作]
        E3[读取源Segment<br/>Read Source Segments<br/>读取待合并Segment]
        E4[合并索引数据<br/>Merge Index Data<br/>合并倒排/正排索引]
        E5[写入目标Segment<br/>Write Target Segment<br/>写入合并后的Segment]
        E1 --> E2
        E2 --> E3
        E3 --> E4
        E4 --> E5
    end
    
    ExecutionLayer --> CommitLayer[版本提交层<br/>Version Commit Layer]
    
    subgraph CommitGroup["版本提交 Version Commit"]
        direction TB
        C1[创建新版本<br/>Create New Version<br/>创建包含合并Segment的版本]
        C2[Fence机制<br/>Fence Mechanism<br/>原子性保证]
        C3[提交版本<br/>Commit Version<br/>提交新版本]
        C4[清理旧Segment<br/>Cleanup Old Segments<br/>删除不再需要的Segment]
        C1 --> C2
        C2 --> C3
        C3 --> C4
    end
    
    CommitLayer --> End([合并完成<br/>Merge Complete])
    
    StrategyLayer -.->|包含| StrategyGroup
    PlanLayer -.->|包含| PlanGroup
    ExecutionLayer -.->|包含| ExecutionGroup
    CommitLayer -.->|包含| CommitGroup
    
    S2 -.->|生成| P1
    P2 -.->|执行| E1
    E5 -.->|创建| C1
    
    style Start fill:#c8e6c9,stroke:#388e3c,stroke-width:3px
    style End fill:#c8e6c9,stroke:#388e3c,stroke-width:3px
    style StrategyLayer fill:#e3f2fd,stroke:#1976d2,stroke-width:3px
    style PlanLayer fill:#fff3e0,stroke:#f57c00,stroke-width:3px
    style ExecutionLayer fill:#f3e5f5,stroke:#7b1fa2,stroke-width:3px
    style CommitLayer fill:#e8f5e9,stroke:#2e7d32,stroke-width:3px
    style StrategyGroup fill:#e3f2fd,stroke:#1976d2,stroke-width:3px
    style S1 fill:#90caf9,stroke:#1976d2,stroke-width:2px
    style S2 fill:#90caf9,stroke:#1976d2,stroke-width:2px
    style S3 fill:#90caf9,stroke:#1976d2,stroke-width:2px
    style S4 fill:#90caf9,stroke:#1976d2,stroke-width:2px
    style PlanGroup fill:#fff3e0,stroke:#f57c00,stroke-width:3px
    style P1 fill:#ffcc80,stroke:#f57c00,stroke-width:2px
    style P2 fill:#ffcc80,stroke:#f57c00,stroke-width:2px
    style P3 fill:#ffcc80,stroke:#f57c00,stroke-width:2px
    style ExecutionGroup fill:#f3e5f5,stroke:#7b1fa2,stroke-width:3px
    style E1 fill:#ce93d8,stroke:#7b1fa2,stroke-width:2px
    style E2 fill:#ce93d8,stroke:#7b1fa2,stroke-width:2px
    style E3 fill:#ce93d8,stroke:#7b1fa2,stroke-width:2px
    style E4 fill:#ce93d8,stroke:#7b1fa2,stroke-width:2px
    style E5 fill:#ce93d8,stroke:#7b1fa2,stroke-width:2px
    style CommitGroup fill:#e8f5e9,stroke:#2e7d32,stroke-width:3px
    style C1 fill:#a5d6a7,stroke:#2e7d32,stroke-width:2px
    style C2 fill:#a5d6a7,stroke:#2e7d32,stroke-width:2px
    style C3 fill:#a5d6a7,stroke:#2e7d32,stroke-width:2px
    style C4 fill:#a5d6a7,stroke:#2e7d32,stroke-width:2px

1. Segment 合并概览

1.1 合并的目的

Segment 合并的主要目的包括:

  1. 减少 Segment 数量:合并多个小 Segment 为一个大 Segment,减少查询时需要遍历的 Segment 数量
  2. 优化查询性能:减少 Segment 数量可以降低查询延迟,提高查询吞吐量
  3. 释放存储空间:合并可以删除重复数据,释放存储空间
  4. 优化索引结构:合并可以优化索引结构,提高索引效率

让我们先通过图来理解 Segment 合并的整体流程:

Segment 合并流程:从合并策略到合并执行的完整过程(已在上面详细展示,此处不再重复):

Segment 合并流程图

flowchart TD
    Start([开始合并]) --> GetVersion[获取当前版本<br/>从TabletData获取Version]
    
    GetVersion --> SelectStrategy[选择合并策略<br/>MergeStrategy]
    
    SelectStrategy --> StrategyType{合并策略类型}
    
    StrategyType -->|优化合并| OptimizeStrategy[优化合并策略<br/>选择需要合并的Segment]
    StrategyType -->|实时合并| RealtimeStrategy[实时合并策略<br/>实时合并小Segment]
    StrategyType -->|分片合并| ShardStrategy[分片合并策略<br/>按分片合并]
    
    OptimizeStrategy --> CreatePlan
    RealtimeStrategy --> CreatePlan
    ShardStrategy --> CreatePlan
    
    CreatePlan[创建MergePlan<br/>包含Segment列表和目标版本] --> ValidatePlan[验证MergePlan<br/>检查Segment有效性]
    
    ValidatePlan --> PlanValid{验证通过?}
    
    PlanValid -->|否| AdjustStrategy[调整策略<br/>重新选择Segment]
    AdjustStrategy -.->|重新选择| SelectStrategy
    
    PlanValid -->|是| Merge
    
    subgraph Merge["执行合并"]
        direction LR
        EM1[创建合并操作<br/>IndexMergeOperation]
        EM2[读取源Segment<br/>从多个Segment读取]
        EM3[合并索引数据<br/>倒排/正排/主键索引]
        EM4[写入目标Segment<br/>写入合并后的数据]
        EM1 --> EM2 --> EM3 --> EM4
    end
    
    Merge --> EM1
    EM4 --> CreateVersion[创建新版本<br/>包含合并后的Segment]
    
    CreateVersion --> CommitVersion[提交新版本<br/>使用Fence机制保证原子性]
    
    CommitVersion --> Cleanup[清理旧Segment<br/>删除不再需要的Segment文件]
    
    Cleanup --> End([完成合并])
    
    style Start fill:#e3f2fd,stroke:#1976d2,stroke-width:3px
    style GetVersion fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
    style SelectStrategy fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
    style StrategyType fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
    style OptimizeStrategy fill:#c5e1f5,stroke:#1976d2,stroke-width:1.5px
    style RealtimeStrategy fill:#90caf9,stroke:#1976d2,stroke-width:1.5px
    style ShardStrategy fill:#64b5f6,stroke:#1976d2,stroke-width:1.5px
    style CreatePlan fill:#fff3e0,stroke:#f57c00,stroke-width:2px
    style ValidatePlan fill:#fff3e0,stroke:#f57c00,stroke-width:2px
    style PlanValid fill:#fff3e0,stroke:#f57c00,stroke-width:2px
    style AdjustStrategy fill:#ffcdd2,stroke:#c62828,stroke-width:2px
    style Merge fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
    style EM1 fill:#e1bee7,stroke:#7b1fa2,stroke-width:1.5px
    style EM2 fill:#ce93d8,stroke:#7b1fa2,stroke-width:1.5px
    style EM3 fill:#ba68c8,stroke:#7b1fa2,stroke-width:1.5px
    style EM4 fill:#ab47bc,stroke:#7b1fa2,stroke-width:1.5px
    style CreateVersion fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    style CommitVersion fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    style Cleanup fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    style End fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px

1.2 合并的核心组件

Segment 合并包括以下核心组件,它们协同工作完成合并任务。让我们通过类图来理解各组件的关系:

classDiagram
    class MergeStrategy {
        <<interface>>
        + GetName()
        + CreateMergePlan()
    }
    
    class OptimizeMergeStrategy {
        - OptimizeMergeParams _params
        + CreateMergePlan()
    }
    
    class MergePlan {
        - vector_SegmentMergePlan _mergePlan
        - Version _targetVersion
        + AddMergePlan()
        + GetTargetVersion()
    }
    
    class SegmentMergePlan {
        - vector_segmentid_t _srcSegments
        - segmentid_t _targetSegment
        + AddSrcSegment()
        + SetTargetSegment()
    }
    
    class VersionMerger {
        - ITabletMergeController _controller
        - IIndexTaskPlanCreator _planCreator
        + ExecuteTask()
        + Run()
    }
    
    class IndexMergeOperation {
        - vector_Segment _srcSegments
        - Segment _targetSegment
        + Execute()
        + MergeIndex()
    }
    
    MergeStrategy <|-- OptimizeMergeStrategy : 实现
    MergeStrategy --> MergePlan : 创建
    MergePlan --> SegmentMergePlan : 包含
    VersionMerger --> MergeStrategy : 使用
    VersionMerger --> IndexMergeOperation : 执行
    IndexMergeOperation --> SegmentMergePlan : 使用

核心组件详解

  • MergeStrategy:合并策略,决定哪些 Segment 参与合并
    • 策略模式:通过策略模式支持多种合并策略,便于扩展
    • 策略选择:根据 Segment 特征和配置选择合适的合并策略
    • 计划创建:根据策略创建合并计划,决定合并的 Segment 和目标
  • MergePlan:合并计划,包含合并的 Segment 列表和目标 Segment 信息
    • 计划结构:包含多个 SegmentMergePlan,每个计划合并一组 Segment
    • 目标版本:记录合并后的目标版本,包含合并后的 Segment 列表
    • 计划验证:创建后验证计划的有效性,确保可以执行
  • IndexMergeOperation:合并操作,执行实际的合并工作
    • 数据读取:读取所有源 Segment 的索引数据
    • 数据合并:合并倒排索引、正排索引等索引数据
    • 数据写入:将合并后的数据写入目标 Segment
  • VersionMerger:版本合并器,管理合并流程和版本更新
    • 流程管理:管理合并的完整流程,从计划创建到版本提交
    • 任务调度:调度合并任务的执行,控制合并的并发度
    • 版本更新:合并完成后更新版本,提交新版本

2. MergeStrategy:合并策略

2.1 MergeStrategy 接口

MergeStrategy 是合并策略的抽象接口,定义在 table/index_task/merger/MergeStrategy.h 中:

// table/index_task/merger/MergeStrategy.h
class MergeStrategy
{
public:
    virtual ~MergeStrategy() {}
    
    // 获取策略名称
    virtual std::string GetName() const = 0;
    
    // 创建合并计划:根据 Context 创建合并计划
    virtual std::pair<Status, std::shared_ptr<MergePlan>>
    CreateMergePlan(const framework::IndexTaskContext* context) = 0;
};

MergeStrategy 的关键方法

MergeStrategy 接口:提供合并策略的抽象:

flowchart TD
    subgraph Interface["MergeStrategy 接口"]
        I1[MergeStrategy<br/>策略接口]
        I2[GetName<br/>获取策略名称]
        I3[CreateMergePlan<br/>创建合并计划]
        I1 --> I2
        I1 --> I3
    end
    
    subgraph Context["IndexTaskContext"]
        C1[当前版本<br/>Current Version]
        C2[Segment列表<br/>Segment List]
        C3[合并参数<br/>Merge Parameters]
        C1 --> C2
        C2 --> C3
    end
    
    subgraph Result["返回结果"]
        R1[Status<br/>状态码]
        R2[MergePlan<br/>合并计划]
        R1 --> R2
    end
    
    I3 --> C1
    C3 --> I3
    I3 --> R1
    
    style Interface fill:#e3f2fd
    style Context fill:#fff3e0
    style Result fill:#f3e5f5
  • GetName():获取策略名称,用于标识不同的合并策略
  • CreateMergePlan():根据 IndexTaskContext 创建合并计划,决定哪些 Segment 参与合并

2.2 合并策略类型

IndexLib 支持多种合并策略:

合并策略类型:Optimize、Realtime、ShardBased 等:

flowchart TB
    Start([合并策略体系<br/>Merge Strategy System]) --> BaseLayer[基础接口层<br/>Base Interface Layer]
    
    subgraph BaseGroup["基础接口 Base Interface"]
        direction TB
        B1[MergeStrategy<br/>策略接口<br/>合并策略抽象接口]
    end
    
    BaseLayer --> StrategyLayer[策略实现层<br/>Strategy Implementation Layer]
    
    subgraph StrategyGroup["合并策略实现 Merge Strategy Implementations"]
        direction TB
        S1[OptimizeMergeStrategy<br/>优化合并策略<br/>合并所有符合条件的Segment]
        S2[RealtimeMergeStrategy<br/>实时合并策略<br/>实时合并小Segment]
        S3[ShardBasedMergeStrategy<br/>分片合并策略<br/>按分片合并Segment]
        S4[KeyValueOptimizeMergeStrategy<br/>KV优化合并策略<br/>针对KV表的优化合并]
    end
    
    StrategyLayer --> FeatureLayer[策略特性层<br/>Strategy Features Layer]
    
    subgraph FeatureGroup["策略特性 Strategy Features"]
        direction TB
        F1[全量合并<br/>Full Merge<br/>合并所有Segment]
        F2[实时合并<br/>Realtime Merge<br/>实时合并小Segment]
        F3[分片合并<br/>Shard Merge<br/>按分片合并]
        F4[KV优化<br/>KV Optimize<br/>KV表优化合并]
    end
    
    FeatureLayer --> End([策略体系完成<br/>Strategy System Complete])
    
    BaseLayer -.->|包含| BaseGroup
    StrategyLayer -.->|包含| StrategyGroup
    FeatureLayer -.->|包含| FeatureGroup
    
    B1 -.->|实现| S1
    B1 -.->|实现| S2
    B1 -.->|实现| S3
    B1 -.->|实现| S4
    S1 -.->|提供| F1
    S2 -.->|提供| F2
    S3 -.->|提供| F3
    S4 -.->|提供| F4
    
    style Start fill:#c8e6c9,stroke:#388e3c,stroke-width:3px
    style End fill:#c8e6c9,stroke:#388e3c,stroke-width:3px
    style BaseLayer fill:#e3f2fd,stroke:#1976d2,stroke-width:3px
    style StrategyLayer fill:#fff3e0,stroke:#f57c00,stroke-width:3px
    style FeatureLayer fill:#f3e5f5,stroke:#7b1fa2,stroke-width:3px
    style BaseGroup fill:#e3f2fd,stroke:#1976d2,stroke-width:3px
    style B1 fill:#90caf9,stroke:#1976d2,stroke-width:2px
    style StrategyGroup fill:#fff3e0,stroke:#f57c00,stroke-width:3px
    style S1 fill:#ffcc80,stroke:#f57c00,stroke-width:2px
    style S2 fill:#ffcc80,stroke:#f57c00,stroke-width:2px
    style S3 fill:#ffcc80,stroke:#f57c00,stroke-width:2px
    style S4 fill:#ffcc80,stroke:#f57c00,stroke-width:2px
    style FeatureGroup fill:#f3e5f5,stroke:#7b1fa2,stroke-width:3px
    style F1 fill:#ce93d8,stroke:#7b1fa2,stroke-width:2px
    style F2 fill:#ce93d8,stroke:#7b1fa2,stroke-width:2px
    style F3 fill:#ce93d8,stroke:#7b1fa2,stroke-width:2px
    style F4 fill:#ce93d8,stroke:#7b1fa2,stroke-width:2px

合并策略类型

  • OptimizeMergeStrategy:优化合并策略,合并所有符合条件的 Segment
  • RealtimeMergeStrategy:实时合并策略,实时合并小 Segment
  • ShardBasedMergeStrategy:分片合并策略,按分片合并 Segment
  • KeyValueOptimizeMergeStrategy:KV 优化合并策略,针对 KV 表的优化合并

2.3 OptimizeMergeStrategy:优化合并策略

OptimizeMergeStrategy 是优化合并策略的实现,定义在 table/index_task/merger/OptimizeMergeStrategy.h 中:

// table/index_task/merger/OptimizeMergeStrategy.h
class OptimizeMergeStrategy : public MergeStrategy
{
public:
    std::string GetName() const override { 
        return MergeStrategyDefine::OPTIMIZE_MERGE_STRATEGY_NAME; 
    }
    
    // 创建合并计划
    std::pair<Status, std::shared_ptr<MergePlan>>
    CreateMergePlan(const framework::IndexTaskContext* context) override;

private:
    // 合并参数
    struct OptimizeMergeParams {
        uint32_t maxDocCount;                    // 参与合并的 Segment 的最大文档数
        uint64_t afterMergeMaxDocCount;         // 合并后的最小文档数
        uint32_t afterMergeMaxSegmentCount;     // 合并后的最大 Segment 数
        bool skipSingleMergedSegment;           // 是否跳过单个已合并的 Segment
    };
    
    OptimizeMergeParams _params;
};

OptimizeMergeStrategy 的关键参数

OptimizeMergeStrategy 参数:控制合并行为的关键参数:

flowchart TD
    subgraph Params["OptimizeMergeParams 参数"]
        P1[maxDocCount<br/>参与合并的最大文档数<br/>只有小于等于该值的Segment参与合并]
        P2[afterMergeMaxDocCount<br/>合并后的最小文档数<br/>控制合并后Segment的大小]
        P3[afterMergeMaxSegmentCount<br/>合并后的最大Segment数<br/>控制合并后Segment的数量]
        P4[skipSingleMergedSegment<br/>是否跳过单个已合并的Segment<br/>避免重复合并]
    end
    
    subgraph Impact["参数影响"]
        I1[控制参与合并的Segment<br/>maxDocCount越大包含越多]
        I2[控制合并后Segment大小<br/>afterMergeMaxDocCount越大Segment越大]
        I3[控制合并后Segment数量<br/>afterMergeMaxSegmentCount越小Segment越少]
        I4[控制合并策略<br/>skipSingleMergedSegment避免重复合并]
    end
    
    P1 --> I1
    P2 --> I2
    P3 --> I3
    P4 --> I4
    
    style Params fill:#e3f2fd
    style Impact fill:#fff3e0
  • maxDocCount:参与合并的 Segment 的最大文档数,只有小于等于该值的 Segment 才会参与合并
  • afterMergeMaxDocCount:合并后的最小文档数,控制合并后 Segment 的大小
  • afterMergeMaxSegmentCount:合并后的最大 Segment 数,控制合并后 Segment 的数量
  • skipSingleMergedSegment:是否跳过单个已合并的 Segment,避免重复合并

2.4 合并策略的选择逻辑

合并策略的选择逻辑:

合并策略的选择逻辑:根据 Segment 特征选择合并策略(已在上面详细展示,此处不再重复):

选择逻辑

合并策略的选择逻辑是合并流程的关键。让我们通过流程图来理解详细的选择过程:

flowchart TD
    Start([开始创建合并计划]) --> Collect[收集源Segment<br/>从TabletData获取所有Segment]
    
    Collect --> Filter[过滤Segment<br/>筛选符合条件的Segment]
    
    Filter --> Check{检查maxDocCount<br/>文档数量限制}
    
    Check -->|docCount <= maxDocCount| Keep[保留Segment<br/>符合合并条件]
    Check -->|docCount > maxDocCount| Skip[跳过Segment<br/>超过限制,不合并]
    
    Keep --> Group
    Skip --> Group
    
    Group[分组Segment<br/>将Segment进行分组] --> Calculate[计算目标Segment数<br/>根据合并后大小计算]
    
    Calculate --> GroupBy[根据afterMergeMaxDocCount分组<br/>按目标文档数分组]
    
    GroupBy --> CreatePlan[创建SegmentMergePlan<br/>为每组创建合并计划]
    
    CreatePlan --> SetTarget[设置目标Segment<br/>指定合并后的Segment]
    
    SetTarget --> CreateMergePlan[创建MergePlan<br/>包含所有合并计划]
    
    CreateMergePlan --> SetVersion[设置目标版本<br/>指定合并后的版本号]
    
    SetVersion --> End([完成])
    
    style Start fill:#e3f2fd,stroke:#1976d2,stroke-width:3px
    style Collect fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
    style Filter fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
    style Check fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
    style Keep fill:#c5e1f5,stroke:#1976d2,stroke-width:1.5px
    style Skip fill:#ffcdd2,stroke:#c62828,stroke-width:1.5px
    style Group fill:#fff3e0,stroke:#f57c00,stroke-width:2px
    style Calculate fill:#fff3e0,stroke:#f57c00,stroke-width:2px
    style GroupBy fill:#fff3e0,stroke:#f57c00,stroke-width:2px
    style CreatePlan fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
    style SetTarget fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
    style CreateMergePlan fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    style SetVersion fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    style End fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px

选择逻辑详解

  1. 收集源 Segment:从 TabletData 中收集符合条件的 Segment
    • Segment 筛选:只收集已构建的 Segment(ST_BUILT
    • Segment 排序:按照 SegmentId 排序,保证合并顺序
    • Segment 过滤:可以根据大小、时间等条件过滤 Segment
  2. 过滤 Segment:根据 maxDocCount 过滤 Segment,只保留符合条件的 Segment
    • 文档数检查:只保留文档数小于等于 maxDocCount 的 Segment
    • 跳过已合并:如果 skipSingleMergedSegment 为 true,跳过单个已合并的 Segment
    • 大小限制:可以根据 Segment 大小进一步过滤
  3. 分组 Segment:根据 afterMergeMaxDocCountafterMergeMaxSegmentCount 分组 Segment
    • 目标 Segment 数计算:根据总文档数和 afterMergeMaxDocCount 计算目标 Segment 数
    • Segment 分组:将 Segment 分组,每组的文档数接近 afterMergeMaxDocCount
    • 分组优化:优化分组策略,减少合并次数
  4. 创建合并计划:为每组 Segment 创建合并计划
    • SegmentMergePlan 创建:为每组 Segment 创建 SegmentMergePlan
    • 目标 Segment 设置:为每个 SegmentMergePlan 设置目标 Segment
    • MergePlan 组装:将所有 SegmentMergePlan 添加到 MergePlan

3. MergePlan:合并计划

3.1 MergePlan 的结构

MergePlan 是合并计划,定义在 table/index_task/merger/MergePlan.h 中:

// table/index_task/merger/MergePlan.h
class MergePlan : public framework::IndexTaskResource, 
                  public autil::legacy::Jsonizable
{
public:
    // 添加合并计划
    void AddMergePlan(const SegmentMergePlan& segmentMergePlan);
    
    // 获取合并计划
    const SegmentMergePlan& GetSegmentMergePlan(size_t index);
    
    // 获取目标版本
    const framework::Version& GetTargetVersion() const;
    void SetTargetVersion(framework::Version targetVersion);
    
    // 创建新版本
    static framework::Version CreateNewVersion(
        const std::shared_ptr<MergePlan>& mergePlan,
        const framework::IndexTaskContext* taskContext);

private:
    std::vector<SegmentMergePlan> _mergePlan;  // 合并计划列表
    framework::Version _targetVersion;          // 目标版本
};

MergePlan 的关键字段

MergePlan 的结构:包含 SegmentMergePlan 列表和目标版本:

flowchart TD
    subgraph MergePlan["MergePlan 对象"]
        MP[MergePlan<br/>合并计划]
    end
    
    subgraph Fields["核心字段"]
        F1[SegmentMergePlan列表<br/>vector SegmentMergePlan<br/>每个计划合并一组Segment]
        F2[目标版本<br/>Version targetVersion<br/>合并后的目标版本]
    end
    
    subgraph SegmentMergePlan["SegmentMergePlan 结构"]
        SM1[源Segment列表<br/>vector segmentid_t<br/>要合并的Segment]
        SM2[目标Segment<br/>segmentid_t<br/>合并后的Segment]
        SM1 --> SM2
    end
    
    subgraph TargetVersion["目标版本"]
        TV1[VersionId<br/>版本号]
        TV2[Segment列表<br/>合并后的Segment列表]
        TV3[Locator<br/>位置信息]
        TV1 --> TV2
        TV2 --> TV3
    end
    
    MP --> F1
    MP --> F2
    F1 --> SM1
    F2 --> TV1
    
    style MergePlan fill:#e3f2fd
    style Fields fill:#fff3e0
    style SegmentMergePlan fill:#f3e5f5
    style TargetVersion fill:#e8f5e9
  • SegmentMergePlan 列表:每个 SegmentMergePlan 包含一组要合并的 Segment
  • 目标版本:合并后的目标版本,包含合并后的 Segment 列表

3.2 SegmentMergePlan:Segment 合并计划

SegmentMergePlan 是单个 Segment 合并计划:

// table/index_task/merger/SegmentMergePlan.h
class SegmentMergePlan
{
public:
    // 添加源 Segment
    void AddSrcSegment(segmentid_t segmentId);
    
    // 设置目标 Segment
    void SetTargetSegment(segmentid_t segmentId);
    
    // 获取源 Segment 列表
    const std::vector<segmentid_t>& GetSrcSegments() const;
    
    // 获取目标 Segment
    segmentid_t GetTargetSegment() const;

private:
    std::vector<segmentid_t> _srcSegments;  // 源 Segment 列表
    segmentid_t _targetSegment;            // 目标 Segment
};

SegmentMergePlan 的关键字段

SegmentMergePlan 的结构:包含源 Segment 列表和目标 Segment(已在上面详细展示,此处不再重复):

  • 源 Segment 列表:要合并的 Segment 列表
  • 目标 Segment:合并后的目标 Segment

3.3 合并计划的创建流程

合并计划的创建流程:

合并计划的创建流程:从收集 Segment 到创建合并计划(已在上面详细展示,此处不再重复):

创建流程

  1. 收集源 Segment:从 TabletData 中收集符合条件的 Segment
  2. 过滤 Segment:根据合并参数过滤 Segment
  3. 分组 Segment:根据合并参数分组 Segment
  4. 创建 SegmentMergePlan:为每组 Segment 创建 SegmentMergePlan
  5. 设置目标 Segment:为每个 SegmentMergePlan 设置目标 Segment
  6. 创建 MergePlan:将所有 SegmentMergePlan 添加到 MergePlan
  7. 设置目标版本:设置合并后的目标版本

4. 合并执行流程

4.1 VersionMerger:版本合并器

VersionMerger 是版本合并器,管理合并流程和版本更新,定义在 framework/VersionMerger.h 中:

// framework/VersionMerger.h
class VersionMerger
{
public:
    // 执行合并任务
    future_lite::coro::Lazy<std::pair<Status, versionid_t>>
    ExecuteTask(const Version& sourceVersion, 
                const std::string& taskType,
                const std::string& taskName,
                const std::map<std::string, std::string>& params);
    
    // 运行合并流程
    future_lite::coro::Lazy<std::pair<Status, versionid_t>> Run();
    
    // 获取合并后的版本信息
    std::shared_ptr<MergedVersionInfo> GetMergedVersionInfo();
    
    // 判断是否需要提交
    bool NeedCommit() const;

private:
    std::string _tabletName;
    std::shared_ptr<ITabletMergeController> _controller;
    std::unique_ptr<IIndexTaskPlanCreator> _planCreator;
    Version _currentBaseVersion;
    std::shared_ptr<MergedVersionInfo> _mergedVersionInfo;
};

VersionMerger 的关键组件

VersionMerger 的结构:管理合并流程和版本更新:

flowchart TD
    subgraph VersionMerger["VersionMerger 对象"]
        VM[VersionMerger<br/>版本合并器]
    end
    
    subgraph Components["核心组件"]
        C1[MergeController<br/>ITabletMergeController<br/>管理合并任务执行]
        C2[PlanCreator<br/>IIndexTaskPlanCreator<br/>创建合并计划]
        C3[CurrentBaseVersion<br/>Version<br/>当前基础版本]
        C4[MergedVersionInfo<br/>合并后的版本信息<br/>包含基础版本和目标版本]
    end
    
    subgraph Methods["关键方法"]
        M1[ExecuteTask<br/>执行合并任务]
        M2[Run<br/>运行合并流程]
        M3[GetMergedVersionInfo<br/>获取合并后的版本信息]
        M4[NeedCommit<br/>判断是否需要提交]
    end
    
    VM --> C1
    VM --> C2
    VM --> C3
    VM --> C4
    C1 --> M1
    C2 --> M2
    C4 --> M3
    C3 --> M4
    
    style VersionMerger fill:#e3f2fd
    style Components fill:#fff3e0
    style Methods fill:#f3e5f5
  • MergeController:合并控制器,管理合并任务的执行
  • PlanCreator:计划创建器,创建合并计划
  • MergedVersionInfo:合并后的版本信息,包含基础版本和目标版本

4.2 合并执行流程

合并执行的完整流程:

合并执行流程:从创建合并计划到提交新版本(已在上面详细展示,此处不再重复):

执行流程

合并执行是合并流程的核心,需要高效地处理大量 Segment。让我们通过序列图来理解完整的执行流程:

sequenceDiagram
    participant Controller as MergeController
    participant Strategy as MergeStrategy
    participant Plan as MergePlan
    participant Merger as VersionMerger
    participant Operation as IndexMergeOperation
    participant Seg1 as Segment1
    participant Seg2 as Segment2
    participant Seg3 as Segment3
    participant TargetSeg as TargetSegment
    participant VersionCommitter as VersionCommitter
    
    Controller->>Strategy: CreateMergePlan(Context)
    Strategy->>Strategy: 收集源Segment
    Strategy->>Strategy: 过滤Segment
    Strategy->>Strategy: 分组Segment
    Strategy->>Plan: 创建MergePlan
    Plan-->>Strategy: MergePlan
    Strategy-->>Controller: MergePlan
    
    Controller->>Merger: ExecuteTask(Version, MergePlan)
    Merger->>Operation: CreateIndexMergeOperation(MergePlan)
    Operation-->>Merger: IndexMergeOperation
    
    Merger->>Operation: Execute()
    Operation->>Seg1: ReadIndexData()
    Operation->>Seg2: ReadIndexData()
    Operation->>Seg3: ReadIndexData()
    Seg1-->>Operation: IndexData1
    Seg2-->>Operation: IndexData2
    Seg3-->>Operation: IndexData3
    
    Operation->>Operation: MergeIndexData([Data1, Data2, Data3])
    Operation->>TargetSeg: WriteIndexData(MergedData)
    TargetSeg-->>Operation: Success
    Operation-->>Merger: Success
    
    Merger->>VersionCommitter: Commit(NewVersion)
    VersionCommitter->>VersionCommitter: CreateFence()
    VersionCommitter->>VersionCommitter: WriteVersion()
    VersionCommitter->>VersionCommitter: AtomicSwitch()
    VersionCommitter-->>Merger: VersionMeta
    
    Merger->>Merger: CleanupOldSegments()
    Merger-->>Controller: Success

执行流程详解

  1. 检查合并条件:判断是否需要合并(Segment 数量、大小等)
    • Segment 数量检查:当 Segment 数量超过阈值时触发合并
    • Segment 大小检查:当小 Segment 数量过多时触发合并
    • 查询性能检查:当查询性能下降时触发合并
    • 存储空间检查:当存储空间不足时触发合并
  2. 创建合并计划:调用 MergeStrategy 创建合并计划
    • 策略选择:根据 Segment 特征选择合适的合并策略
    • 计划创建:调用 CreateMergePlan() 创建合并计划
    • 计划验证:验证合并计划的有效性,确保可以执行
  3. 提交合并任务:将合并任务提交到 MergeController
    • 任务调度:MergeController 调度合并任务的执行
    • 资源分配:为合并任务分配 CPU、内存、IO 资源
    • 并发控制:控制同时进行的合并任务数量
  4. 执行合并操作:执行 IndexMergeOperation,合并 Segment
    • 数据读取:并行读取所有源 Segment 的索引数据
    • 数据合并:合并倒排索引、正排索引等索引数据
    • 数据写入:将合并后的数据写入目标 Segment
    • 元数据更新:更新 Segment 的元数据信息
  5. 创建新版本:合并完成后创建新版本
    • 版本信息准备:准备新版本的 Segment 列表和 Locator
    • 版本号递增:递增版本号,保证版本顺序
    • 版本验证:验证新版本的有效性
  6. 提交新版本:提交新版本,更新 TabletData
    • Fence 创建:创建 Fence 目录,保证原子性
    • 版本持久化:将新版本持久化到磁盘
    • 原子切换:原子性地切换版本目录
    • TabletData 更新:更新 TabletData 的版本和 Segment 列表

4.3 IndexMergeOperation:合并操作

IndexMergeOperation 是合并操作,执行实际的合并工作:

IndexMergeOperation:执行实际的合并工作(已在上面详细展示,此处不再重复):

合并操作的关键步骤

IndexMergeOperation 是合并执行的核心,负责实际的合并工作。让我们通过流程图来理解详细的合并过程:

flowchart TD
    Start([开始合并操作]) --> Read[读取源Segment<br/>从多个Segment读取数据]
    
    Read --> MergeInverted[合并倒排索引<br/>InvertedIndex]
    Read --> MergeAttribute[合并正排索引<br/>AttributeIndex]
    Read --> MergePrimaryKey[合并主键索引<br/>PrimaryKeyIndex]
    
    MergeInverted --> MergeDoc[合并文档数据<br/>合并所有索引数据]
    MergeAttribute --> MergeDoc
    MergePrimaryKey --> MergeDoc
    
    MergeDoc --> Dedup[去重处理<br/>去除重复文档]
    
    Dedup --> Sort[排序处理<br/>按文档ID排序]
    
    Sort --> Write[写入目标Segment<br/>写入合并后的数据]
    
    Write --> UpdateMeta[更新元数据<br/>更新Segment元数据]
    
    UpdateMeta --> End([完成合并])
    
    style Start fill:#e3f2fd,stroke:#1976d2,stroke-width:3px
    style Read fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
    style MergeInverted fill:#fff3e0,stroke:#f57c00,stroke-width:2px
    style MergeAttribute fill:#fff3e0,stroke:#f57c00,stroke-width:2px
    style MergePrimaryKey fill:#fff3e0,stroke:#f57c00,stroke-width:2px
    style MergeDoc fill:#fff3e0,stroke:#f57c00,stroke-width:2px
    style Dedup fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
    style Sort fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
    style Write fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
    style UpdateMeta fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    style End fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px

合并操作的关键步骤详解

  1. 读取源 Segment:读取所有源 Segment 的数据
    • 并行读取:多个源 Segment 可以并行读取,提高读取速度
    • 数据缓存:读取的数据可以缓存在内存中,减少重复读取
    • 流式读取:对于大 Segment,可以采用流式读取,减少内存占用
  2. 合并索引:合并倒排索引、正排索引等
    • 倒排索引合并:合并 term 的倒排列表,去重、排序
    • 正排索引合并:合并文档属性,保持属性顺序
    • 主键索引合并:合并主键索引,去重主键
    • 索引优化:合并过程中可以优化索引结构,提高索引效率
  3. 合并文档:合并文档数据,去重、排序等
    • 文档去重:根据主键去重,避免重复文档
    • 文档排序:按照 DocId 排序,保证文档顺序
    • 文档合并:合并文档的各个字段,保持数据完整性
  4. 写入目标 Segment:将合并后的数据写入目标 Segment
    • 索引写入:将合并后的索引数据写入目标 Segment
    • 文档写入:将合并后的文档数据写入目标 Segment
    • 元数据写入:写入 Segment 的元数据信息
  5. 更新元数据:更新 Segment 的元数据信息
    • 文档计数:更新 Segment 的文档数量
    • Locator 更新:更新 Segment 的 Locator 信息
    • 统计信息:更新 Segment 的统计信息(大小、索引数等)

合并操作的性能优化

  1. 并行合并
    • 多个索引可以并行合并,提高合并速度
    • 多个 Segment 可以并行读取,减少读取时间
  2. 增量合并
    • 只合并变更的索引,减少合并工作量
    • 使用增量算法,避免重复处理
  3. 内存优化
    • 使用内存池减少内存分配开销
    • 流式处理减少内存占用
    • 及时释放不再使用的内存

5. 合并策略详解

5.1 OptimizeMergeStrategy 的合并逻辑

OptimizeMergeStrategy 的合并逻辑:

OptimizeMergeStrategy 的合并逻辑:根据参数决定合并行为(已在上面详细展示,此处不再重复):

合并逻辑

  1. 收集源 Segment:从 TabletData 中收集所有符合条件的 Segment
  2. 过滤 Segment:根据 maxDocCount 过滤,只保留文档数小于等于该值的 Segment
  3. 计算目标 Segment 数:根据 afterMergeMaxDocCountafterMergeMaxSegmentCount 计算目标 Segment 数
  4. 分组 Segment:将 Segment 分组,每组合并为一个目标 Segment
  5. 创建合并计划:为每组 Segment 创建 SegmentMergePlan

5.2 合并参数的影响

合并参数对合并行为的影响:

合并参数的影响:maxDocCount、afterMergeMaxDocCount 等参数的作用(已在上面详细展示,此处不再重复):

参数影响

  • maxDocCount:控制哪些 Segment 参与合并,较大的值会包含更多 Segment
  • afterMergeMaxDocCount:控制合并后 Segment 的大小,较大的值会产生更大的 Segment
  • afterMergeMaxSegmentCount:控制合并后 Segment 的数量,较小的值会产生更少的 Segment
  • skipSingleMergedSegment:控制是否跳过单个已合并的 Segment,避免重复合并

5.3 合并策略的选择

不同场景下的合并策略选择:

合并策略的选择:根据场景选择不同的合并策略:

flowchart TB
    Start([合并策略选择<br/>Merge Strategy Selection]) --> ScenarioLayer[应用场景层<br/>Application Scenarios Layer]
    
    subgraph ScenarioGroup["应用场景 Application Scenarios"]
        direction TB
        SC1[全量合并场景<br/>Full Merge Scenario<br/>需要合并所有Segment]
        SC2[实时合并场景<br/>Realtime Merge Scenario<br/>实时合并小Segment]
        SC3[分片合并场景<br/>Shard Merge Scenario<br/>按分片合并Segment]
        SC4[KV表场景<br/>KV Table Scenario<br/>KV表优化合并]
    end
    
    ScenarioLayer --> StrategyLayer[合并策略层<br/>Merge Strategy Layer]
    
    subgraph StrategyGroup["合并策略 Merge Strategies"]
        direction TB
        ST1[OptimizeMergeStrategy<br/>优化合并策略<br/>合并所有符合条件的Segment]
        ST2[RealtimeMergeStrategy<br/>实时合并策略<br/>实时合并小Segment]
        ST3[ShardBasedMergeStrategy<br/>分片合并策略<br/>按分片合并Segment]
        ST4[KeyValueOptimizeMergeStrategy<br/>KV优化合并策略<br/>针对KV表优化]
    end
    
    StrategyLayer --> FeatureLayer[策略特点层<br/>Strategy Features Layer]
    
    subgraph FeatureGroup["策略特点 Strategy Features"]
        direction TB
        F1[合并所有符合条件的Segment<br/>Merge All Eligible Segments<br/>适用于全量合并]
        F2[实时合并小Segment<br/>Realtime Merge Small Segments<br/>适用于实时场景]
        F3[按分片合并Segment<br/>Merge by Shard<br/>适用于分片场景]
        F4[针对KV表优化<br/>KV Table Optimization<br/>适用于KV表]
    end
    
    FeatureLayer --> End([策略选择完成<br/>Strategy Selection Complete])
    
    ScenarioLayer -.->|包含| ScenarioGroup
    StrategyLayer -.->|包含| StrategyGroup
    FeatureLayer -.->|包含| FeatureGroup
    
    SC1 -.->|选择| ST1
    SC2 -.->|选择| ST2
    SC3 -.->|选择| ST3
    SC4 -.->|选择| ST4
    
    ST1 -.->|提供| F1
    ST2 -.->|提供| F2
    ST3 -.->|提供| F3
    ST4 -.->|提供| F4
    
    style Start fill:#c8e6c9,stroke:#388e3c,stroke-width:3px
    style End fill:#c8e6c9,stroke:#388e3c,stroke-width:3px
    style ScenarioLayer fill:#e3f2fd,stroke:#1976d2,stroke-width:3px
    style StrategyLayer fill:#fff3e0,stroke:#f57c00,stroke-width:3px
    style FeatureLayer fill:#f3e5f5,stroke:#7b1fa2,stroke-width:3px
    style ScenarioGroup fill:#e3f2fd,stroke:#1976d2,stroke-width:3px
    style SC1 fill:#90caf9,stroke:#1976d2,stroke-width:2px
    style SC2 fill:#90caf9,stroke:#1976d2,stroke-width:2px
    style SC3 fill:#90caf9,stroke:#1976d2,stroke-width:2px
    style SC4 fill:#90caf9,stroke:#1976d2,stroke-width:2px
    style StrategyGroup fill:#fff3e0,stroke:#f57c00,stroke-width:3px
    style ST1 fill:#ffcc80,stroke:#f57c00,stroke-width:2px
    style ST2 fill:#ffcc80,stroke:#f57c00,stroke-width:2px
    style ST3 fill:#ffcc80,stroke:#f57c00,stroke-width:2px
    style ST4 fill:#ffcc80,stroke:#f57c00,stroke-width:2px
    style FeatureGroup fill:#f3e5f5,stroke:#7b1fa2,stroke-width:3px
    style F1 fill:#ce93d8,stroke:#7b1fa2,stroke-width:2px
    style F2 fill:#ce93d8,stroke:#7b1fa2,stroke-width:2px
    style F3 fill:#ce93d8,stroke:#7b1fa2,stroke-width:2px
    style F4 fill:#ce93d8,stroke:#7b1fa2,stroke-width:2px

策略选择

  • OptimizeMergeStrategy:适用于全量合并,合并所有符合条件的 Segment
  • RealtimeMergeStrategy:适用于实时合并,实时合并小 Segment
  • ShardBasedMergeStrategy:适用于分片场景,按分片合并 Segment
  • KeyValueOptimizeMergeStrategy:适用于 KV 表,针对 KV 表的优化合并

6. 合并的触发条件

6.1 合并触发条件

合并的触发条件:

合并触发条件:Segment 数量、大小等条件:

flowchart TD
    subgraph Conditions["触发条件"]
        C1[Segment数量<br/>Segment Count<br/>超过阈值时触发]
        C2[Segment大小<br/>Segment Size<br/>小Segment数量过多时触发]
        C3[查询性能<br/>Query Performance<br/>查询性能下降时触发]
        C4[存储空间<br/>Storage Space<br/>存储空间不足时触发]
        C5[手动触发<br/>Manual Trigger<br/>支持手动触发]
    end
    
    subgraph Thresholds["阈值设置"]
        T1[Segment数量阈值<br/>如100个Segment]
        T2[小Segment数量阈值<br/>如50个小Segment]
        T3[查询延迟阈值<br/>如P99延迟超过100ms]
        T4[存储空间阈值<br/>如使用率超过80%]
    end
    
    subgraph Actions["触发动作"]
        A1[创建合并计划<br/>Create Merge Plan]
        A2[提交合并任务<br/>Submit Merge Task]
        A3[执行合并<br/>Execute Merge]
    end
    
    C1 --> T1
    C2 --> T2
    C3 --> T3
    C4 --> T4
    C5 --> A1
    T1 --> A1
    T2 --> A1
    T3 --> A1
    T4 --> A1
    A1 --> A2
    A2 --> A3
    
    style Conditions fill:#e3f2fd
    style Thresholds fill:#fff3e0
    style Actions fill:#f3e5f5

触发条件

  • Segment 数量:当 Segment 数量超过阈值时触发合并
  • Segment 大小:当小 Segment 数量过多时触发合并
  • 查询性能:当查询性能下降时触发合并
  • 存储空间:当存储空间不足时触发合并
  • 手动触发:支持手动触发合并

6.2 合并时机的选择

合并时机的选择:

合并时机的选择:在线合并、离线合并等:

flowchart TB
    Start([合并时机选择<br/>Merge Timing Selection]) --> TimingLayer[合并时机层<br/>Merge Timing Layer]
    
    subgraph TimingGroup["合并时机 Merge Timing"]
        direction TB
        T1[在线合并<br/>Online Merge<br/>服务运行期间进行]
        T2[离线合并<br/>Offline Merge<br/>服务停止时进行]
        T3[定时合并<br/>Scheduled Merge<br/>定期触发合并]
        T4[按需合并<br/>On-Demand Merge<br/>根据需求触发]
    end
    
    TimingLayer --> AdvantageLayer[时机优势层<br/>Timing Advantages Layer]
    
    subgraph AdvantageGroup["时机优势 Timing Advantages"]
        direction TB
        A1[不影响服务可用性<br/>No Impact on Availability<br/>在线合并的优势]
        A2[更彻底优化索引<br/>More Thorough Optimization<br/>离线合并的优势]
        A3[保持索引结构优化<br/>Maintain Index Structure<br/>定时合并的优势]
        A4[灵活控制合并<br/>Flexible Control<br/>按需合并的优势]
    end
    
    AdvantageLayer --> TradeoffLayer[权衡层<br/>Tradeoffs Layer]
    
    subgraph TradeoffGroup["权衡 Tradeoffs"]
        direction TB
        TR1[可能影响查询性能<br/>May Impact Query Performance<br/>在线合并的权衡]
        TR2[需要停止服务<br/>Require Service Stop<br/>离线合并的权衡]
        TR3[固定时间触发<br/>Fixed Time Trigger<br/>定时合并的权衡]
        TR4[需要手动干预<br/>Require Manual Intervention<br/>按需合并的权衡]
    end
    
    TradeoffLayer --> End([时机选择完成<br/>Timing Selection Complete])
    
    TimingLayer -.->|包含| TimingGroup
    AdvantageLayer -.->|包含| AdvantageGroup
    TradeoffLayer -.->|包含| TradeoffGroup
    
    T1 -.->|提供| A1
    T2 -.->|提供| A2
    T3 -.->|提供| A3
    T4 -.->|提供| A4
    T1 -.->|权衡| TR1
    T2 -.->|权衡| TR2
    T3 -.->|权衡| TR3
    T4 -.->|权衡| TR4
    
    style Start fill:#c8e6c9,stroke:#388e3c,stroke-width:3px
    style End fill:#c8e6c9,stroke:#388e3c,stroke-width:3px
    style TimingLayer fill:#e3f2fd,stroke:#1976d2,stroke-width:3px
    style AdvantageLayer fill:#fff3e0,stroke:#f57c00,stroke-width:3px
    style TradeoffLayer fill:#f3e5f5,stroke:#7b1fa2,stroke-width:3px
    style TimingGroup fill:#e3f2fd,stroke:#1976d2,stroke-width:3px
    style T1 fill:#90caf9,stroke:#1976d2,stroke-width:2px
    style T2 fill:#90caf9,stroke:#1976d2,stroke-width:2px
    style T3 fill:#90caf9,stroke:#1976d2,stroke-width:2px
    style T4 fill:#90caf9,stroke:#1976d2,stroke-width:2px
    style AdvantageGroup fill:#fff3e0,stroke:#f57c00,stroke-width:3px
    style A1 fill:#ffcc80,stroke:#f57c00,stroke-width:2px
    style A2 fill:#ffcc80,stroke:#f57c00,stroke-width:2px
    style A3 fill:#ffcc80,stroke:#f57c00,stroke-width:2px
    style A4 fill:#ffcc80,stroke:#f57c00,stroke-width:2px
    style TradeoffGroup fill:#f3e5f5,stroke:#7b1fa2,stroke-width:3px
    style TR1 fill:#ce93d8,stroke:#7b1fa2,stroke-width:2px
    style TR2 fill:#ce93d8,stroke:#7b1fa2,stroke-width:2px
    style TR3 fill:#ce93d8,stroke:#7b1fa2,stroke-width:2px
    style TR4 fill:#ce93d8,stroke:#7b1fa2,stroke-width:2px

合并时机

  • 在线合并:在服务运行期间进行合并,不影响服务可用性
  • 离线合并:在服务停止时进行合并,可以更彻底地优化索引
  • 定时合并:定期触发合并,保持索引结构优化
  • 按需合并:根据查询性能或存储空间按需触发合并

7. 合并的性能优化

7.1 合并性能优化策略

合并性能优化的策略:

合并性能优化:并行合并、增量合并等策略:

flowchart TD
    subgraph Optimization["优化策略"]
        O1[并行合并<br/>Parallel Merge<br/>多个Segment并行合并]
        O2[增量合并<br/>Incremental Merge<br/>只合并变更的Segment]
        O3[合并优先级<br/>Merge Priority<br/>根据重要性设置优先级]
        O4[资源控制<br/>Resource Control<br/>控制CPU、内存、IO资源]
    end
    
    subgraph Parallel["并行合并"]
        P1[Segment并行<br/>多个Segment并行合并]
        P2[索引并行<br/>多个索引并行合并]
        P3[IO并行<br/>读取和写入并行进行]
    end
    
    subgraph Incremental["增量合并"]
        I1[变更检测<br/>只检测变更的Segment]
        I2[增量合并<br/>只合并变更的索引]
        I3[增量写入<br/>只写入变更的数据]
    end
    
    O1 --> P1
    O2 --> I1
    P1 --> P2
    P2 --> P3
    I1 --> I2
    I2 --> I3
    
    style Optimization fill:#e3f2fd
    style Parallel fill:#fff3e0
    style Incremental fill:#f3e5f5

优化策略

  • 并行合并:多个 Segment 可以并行合并,提高合并效率
  • 增量合并:只合并变更的 Segment,减少合并工作量
  • 合并优先级:根据 Segment 大小和重要性设置合并优先级
  • 资源控制:控制合并时的 CPU、内存、IO 资源使用

7.2 合并的资源控制

合并时的资源控制:

合并的资源控制:CPU、内存、IO 资源的控制:

flowchart TD
    subgraph Resources["资源类型"]
        R1[CPU控制<br/>CPU Control<br/>限制CPU使用率]
        R2[内存控制<br/>Memory Control<br/>限制内存使用]
        R3[IO控制<br/>IO Control<br/>限制IO带宽]
        R4[并发控制<br/>Concurrency Control<br/>控制并发任务数]
    end
    
    subgraph CPU["CPU控制"]
        C1[限制CPU使用率<br/>如不超过50%]
        C2[避免影响查询性能<br/>Avoid Impact on Query]
        C3[动态调整<br/>Dynamic Adjustment]
    end
    
    subgraph Memory["内存控制"]
        M1[限制内存使用<br/>如不超过总内存的30%]
        M2[避免内存溢出<br/>Avoid Memory Overflow]
        M3[流式处理<br/>Streaming Processing]
    end
    
    subgraph IO["IO控制"]
        IO1[限制IO带宽<br/>如不超过总带宽的40%]
        IO2[避免影响查询IO<br/>Avoid Impact on Query IO]
        IO3[IO优先级<br/>IO Priority]
    end
    
    R1 --> C1
    R2 --> M1
    R3 --> IO1
    C1 --> C2
    M1 --> M2
    IO1 --> IO2
    
    style Resources fill:#e3f2fd
    style CPU fill:#fff3e0
    style Memory fill:#f3e5f5
    style IO fill:#e8f5e9

资源控制

  • CPU 控制:限制合并时的 CPU 使用率,避免影响查询性能
  • 内存控制:限制合并时的内存使用,避免内存溢出
  • IO 控制:限制合并时的 IO 带宽,避免影响查询 IO
  • 并发控制:控制同时进行的合并任务数量

8. 合并的实际应用

8.1 全量合并场景

在全量合并场景中,合并的应用:

全量合并场景:合并所有符合条件的 Segment:

flowchart TD
    Start([全量合并开始]) --> Step1[1. 收集所有Segment<br/>从TabletData获取]
    
    Step1 --> Step2[2. 过滤Segment<br/>筛选符合条件的Segment]
    
    Step2 --> Step3[3. 创建合并计划<br/>MergePlan,合并所有Segment]
    
    Step3 --> Step4[4. 执行合并操作<br/>IndexMergeOperation]
    
    Step4 --> Step5[5. 合并索引数据<br/>合并倒排/正排/主键索引]
    
    Step5 --> Step6[6. 创建目标Segment<br/>生成合并后的Segment]
    
    Step6 --> Step7[7. 提交新版本<br/>使用Fence机制保证原子性]
    
    Step7 --> Step8[8. 更新TabletData<br/>切换到新版本]
    
    Step8 --> Step9[9. 清理旧Segment<br/>删除不再需要的文件]
    
    Step9 --> End([全量合并完成])
    
    style Start fill:#e3f2fd,stroke:#1976d2,stroke-width:3px
    style Step1 fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
    style Step2 fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
    style Step3 fill:#fff3e0,stroke:#f57c00,stroke-width:2px
    style Step4 fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
    style Step5 fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
    style Step6 fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
    style Step7 fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    style Step8 fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    style Step9 fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    style End fill:#c8e6c9,stroke:#2e7d32,stroke-width:3px

全量合并流程

  1. 收集所有 Segment:收集所有符合条件的 Segment
  2. 创建合并计划:创建合并计划,将所有 Segment 合并为少数几个大 Segment
  3. 执行合并:执行合并操作,合并所有 Segment
  4. 提交新版本:提交新版本,更新 TabletData

8.2 增量合并场景

在增量合并场景中,合并的应用:

增量合并场景:只合并新增或变更的 Segment:

flowchart TD
    subgraph Identify["识别变更"]
        I1[识别变更Segment<br/>Identify Changed Segments]
        I2[新增Segment<br/>New Segments]
        I3[变更Segment<br/>Changed Segments]
        I1 --> I2
        I1 --> I3
    end
    
    subgraph Plan["创建合并计划"]
        P1[创建合并计划<br/>Create Merge Plan]
        P2[只合并变更Segment<br/>Merge Only Changed Segments]
        P3[优化合并范围<br/>Optimize Merge Scope]
        P1 --> P2
        P2 --> P3
    end
    
    subgraph Execute["执行合并"]
        E1[执行合并操作<br/>Execute Merge]
        E2[合并变更Segment<br/>Merge Changed Segments]
        E3[创建目标Segment<br/>Create Target Segments]
        E1 --> E2
        E2 --> E3
    end
    
    subgraph Commit["提交版本"]
        CO1[提交新版本<br/>Commit New Version]
        CO2[更新TabletData<br/>Update TabletData]
        CO3[清理旧Segment<br/>Cleanup Old Segments]
        CO1 --> CO2
        CO2 --> CO3
    end
    
    I2 --> P1
    I3 --> P1
    P3 --> E1
    E3 --> CO1
    
    style Identify fill:#e3f2fd
    style Plan fill:#fff3e0
    style Execute fill:#f3e5f5
    style Commit fill:#e8f5e9

增量合并流程

  1. 识别变更 Segment:识别新增或变更的 Segment
  2. 创建合并计划:创建合并计划,只合并变更的 Segment
  3. 执行合并:执行合并操作,合并变更的 Segment
  4. 提交新版本:提交新版本,更新 TabletData

9. 合并的关键设计

9.1 合并的原子性

合并的原子性保证:

合并的原子性:通过 Fence 机制保证合并的原子性:

flowchart LR
    subgraph Fence["Fence 机制"]
        F1[创建Fence目录<br/>Create Fence Directory]
        F2[写入合并结果<br/>Write Merge Result]
        F3[原子重命名<br/>Atomic Rename]
        F1 --> F2
        F2 --> F3
    end
    
    subgraph Atomicity["原子性保证"]
        A1[要么全部成功<br/>All or Nothing]
        A2[要么全部失败<br/>Rollback on Failure]
        A3[避免部分写入<br/>Avoid Partial Write]
        A1 --> A2
        A2 --> A3
    end
    
    subgraph Error["错误处理"]
        E1[合并失败<br/>Merge Failure]
        E2[清理Fence目录<br/>Cleanup Fence]
        E3[不影响已有版本<br/>No Impact on Existing]
        E1 --> E2
        E2 --> E3
    end
    
    subgraph Advantage["设计优势"]
        AD1[原子性<br/>Atomicity]
        AD2[可靠性<br/>Reliability]
        AD3[简单性<br/>Simplicity]
        AD4[性能高<br/>High Performance]
    end
    
    F3 --> A1
    A3 --> E1
    E3 --> AD1
    
    style Fence fill:#e3f2fd
    style Atomicity fill:#fff3e0
    style Error fill:#f3e5f5
    style Advantage fill:#e8f5e9

原子性保证

  • Fence 机制:通过 Fence 目录保证合并的原子性
  • 事务性提交:合并完成后原子性地提交新版本
  • 错误恢复:如果合并失败,可以回滚,不影响已有版本

9.2 合并的一致性

合并的一致性保证:

合并的一致性:保证合并后数据的一致性:

flowchart TD
    subgraph Consistency["一致性保证"]
        C1[数据完整性<br/>Data Integrity<br/>保证不丢失数据]
        C2[索引一致性<br/>Index Consistency<br/>保证索引结构正确]
        C3[版本一致性<br/>Version Consistency<br/>保证版本信息正确]
    end
    
    subgraph Data["数据完整性"]
        D1[文档去重<br/>Document Deduplication]
        D2[数据校验<br/>Data Validation]
        D3[完整性检查<br/>Integrity Check]
        D1 --> D2
        D2 --> D3
    end
    
    subgraph Index["索引一致性"]
        I1[索引结构正确<br/>Correct Index Structure]
        I2[索引数据完整<br/>Complete Index Data]
        I3[索引关系正确<br/>Correct Index Relations]
        I1 --> I2
        I2 --> I3
    end
    
    subgraph Version["版本一致性"]
        V1[版本号递增<br/>Version ID Increment]
        V2[Segment列表正确<br/>Correct Segment List]
        V3[Locator信息正确<br/>Correct Locator Info]
        V1 --> V2
        V2 --> V3
    end
    
    C1 --> D1
    C2 --> I1
    C3 --> V1
    
    style Consistency fill:#e3f2fd
    style Data fill:#fff3e0
    style Index fill:#f3e5f5
    style Version fill:#e8f5e9

一致性保证

  • 数据完整性:保证合并后数据的完整性,不丢失数据
  • 索引一致性:保证合并后索引的一致性,索引结构正确
  • 版本一致性:保证合并后版本的一致性,版本信息正确

9.3 合并的性能优化

合并的性能优化:

合并的性能优化:并行合并、资源控制等优化策略(已在上面详细展示,此处不再重复):

性能优化

  • 并行合并:多个 Segment 可以并行合并,提高合并效率
  • 增量合并:只合并变更的 Segment,减少合并工作量
  • 资源控制:控制合并时的资源使用,避免影响查询性能
  • 合并优先级:根据 Segment 重要性设置合并优先级

10. 性能优化与最佳实践

10.1 合并性能优化

优化策略

  1. 并行合并优化
    • Segment 并行:多个 Segment 可以并行合并,提高合并效率
    • 索引并行:多个索引可以并行合并,充分利用多核 CPU
    • IO 并行:读取和写入可以并行进行,提高 IO 利用率
  2. 增量合并优化
    • 变更检测:只检测变更的 Segment,减少检测开销
    • 增量合并:只合并变更的索引,减少合并工作量
    • 增量写入:只写入变更的数据,减少写入量
  3. 资源控制优化
    • CPU 控制:限制合并时的 CPU 使用率,避免影响查询性能
    • 内存控制:限制合并时的内存使用,避免内存溢出
    • IO 控制:限制合并时的 IO 带宽,避免影响查询 IO
    • 并发控制:控制同时进行的合并任务数量

10.2 合并策略优化

优化策略

  1. 参数调优
    • maxDocCount:根据 Segment 大小分布调整,平衡合并频率和效果
    • afterMergeMaxDocCount:根据查询性能调整,平衡 Segment 大小和查询延迟
    • afterMergeMaxSegmentCount:根据系统负载调整,平衡 Segment 数量和查询性能
  2. 策略选择优化
    • 场景适配:根据场景选择合适的合并策略
    • 动态调整:根据系统负载动态调整合并策略
    • 策略组合:可以组合使用多种合并策略
  3. 触发条件优化
    • 智能触发:根据查询性能和存储空间智能触发合并
    • 定时触发:定期触发合并,保持索引结构优化
    • 按需触发:根据实际需求按需触发合并

10.3 合并监控与调优

监控指标

  1. 合并性能指标
    • 合并耗时:监控合并任务的执行时间
    • 合并吞吐量:监控合并的数据量
    • 资源使用:监控合并时的 CPU、内存、IO 使用
  2. 合并效果指标
    • Segment 数量变化:监控合并前后 Segment 数量的变化
    • 查询性能变化:监控合并前后查询性能的变化
    • 存储空间变化:监控合并前后存储空间的变化
  3. 调优策略
    • 参数调优:根据监控数据调整合并参数
    • 策略调优:根据监控数据调整合并策略
    • 时机调优:根据监控数据调整合并触发时机

11. 小结

Segment 合并策略是 IndexLib 的核心功能,通过 MergeStrategy 和 MergePlan 实现。通过本文的深入解析,我们了解到:

核心机制

  • MergeStrategy:合并策略,决定哪些 Segment 参与合并,支持多种合并策略
    • 策略模式:通过策略模式支持多种合并策略,便于扩展
    • 策略选择:根据 Segment 特征和配置选择合适的合并策略
    • 计划创建:根据策略创建合并计划,决定合并的 Segment 和目标
  • MergePlan:合并计划,包含合并的 Segment 列表和目标 Segment 信息
    • 计划结构:包含多个 SegmentMergePlan,每个计划合并一组 Segment
    • 目标版本:记录合并后的目标版本,包含合并后的 Segment 列表
    • 计划验证:创建后验证计划的有效性,确保可以执行
  • OptimizeMergeStrategy:优化合并策略,根据参数控制合并行为
    • 参数控制:通过 maxDocCountafterMergeMaxDocCount 等参数控制合并行为
    • 分组策略:根据参数将 Segment 分组,每组合并为一个目标 Segment
    • 合并优化:优化合并策略,减少合并次数,提高合并效率
  • 合并执行流程:从创建合并计划到提交新版本的完整流程
    • 计划创建:调用 MergeStrategy 创建合并计划
    • 任务执行:执行 IndexMergeOperation,合并 Segment
    • 版本提交:合并完成后提交新版本,更新 TabletData
  • 合并触发条件:根据 Segment 数量、大小等条件触发合并
    • 数量触发:当 Segment 数量超过阈值时触发合并
    • 大小触发:当小 Segment 数量过多时触发合并
    • 性能触发:当查询性能下降时触发合并
    • 手动触发:支持手动触发合并
  • 合并性能优化:通过并行合并、资源控制等策略优化合并性能
    • 并行合并:多个 Segment 和索引可以并行合并,提高合并效率
    • 资源控制:控制合并时的 CPU、内存、IO 资源使用,避免影响查询
    • 增量合并:只合并变更的 Segment,减少合并工作量
  • 合并的原子性和一致性:通过 Fence 机制保证合并的原子性和一致性
    • 原子性保证:通过 Fence 机制保证合并的原子性,要么全部成功,要么全部失败
    • 一致性保证:保证合并后数据的完整性和索引的一致性
    • 错误恢复:如果合并失败,可以回滚,不影响已有版本

设计亮点

  1. 策略模式:通过策略模式支持多种合并策略,便于扩展和维护
  2. 计划机制:通过 MergePlan 将合并策略和执行分离,提高灵活性
  3. 并行合并:支持并行合并,充分利用多核 CPU,提高合并效率
  4. 资源控制:通过资源控制避免合并影响查询性能,保证系统稳定性
  5. 原子性保证:通过 Fence 机制保证合并的原子性,保证数据一致性

性能优化

  • 合并效率:并行合并显著提高合并效率
  • 查询性能:合并后查询性能显著提升
  • 存储空间:合并后有效减少存储空间
  • 资源使用:资源控制有效降低对查询的影响

理解 Segment 合并策略,是掌握 IndexLib 索引优化机制的关键。在下一篇文章中,我们将深入介绍内存管理与资源控制的实现细节,包括 MemoryQuotaController、TabletMemoryCalculator、IIndexMemoryReclaimer 等各个组件的实现原理和性能优化策略。