深入doris查询计划以及io调度(四)存储引擎架构

1. StorageEngine 架构概述

1.1 核心架构设计

Doris 的存储引擎采用 LSM-Tree(Log-Structured Merge-Tree) 架构,专为写多读少场景优化。StorageEngine 是 BE 节点存储层的核心管理组件,负责:

  • Tablet 管理:管理所有 Tablet 的生命周期
  • 数据目录管理:管理多个数据目录(DataDir)
  • MemTable 刷盘:协调内存数据持久化
  • Compaction 调度:管理 Base 和 Cumulative Compaction
  • 事务管理:协调数据加载的事务流程

1.2 存储架构类型

cpp 复制代码
// be/src/olap/storage_engine.h
class BaseStorageEngine {
public:
    enum StorageType {
        LOCAL = 0,   // 存算一体架构
        CLOUD = 1    // 存算分离架构
    };
};

LOCAL 模式(存算一体)

  • 数据和计算节点紧密耦合
  • 本地存储提供高性能读写
  • 适合低延迟查询场景

CLOUD 模式(存算分离)

  • 数据存储在远程对象存储(S3/HDFS)
  • 计算节点无状态,可弹性扩缩容
  • 适合云原生部署场景

1.3 LSM-Tree 核心概念

scss 复制代码
版本链示例:
[0-0] Base Rowset (Base Compaction输出)
  ↓
[1-1] Delta Rowset (数据导入)
[2-2] Delta Rowset
[3-3] Delta Rowset
  ↓
[1-3] Cumulative Rowset (Cumulative Compaction输出)
  ↓ cumulative_layer_point = 4
[4-4] Delta Rowset
[5-5] Delta Rowset

关键字段

  • cumulative_layer_point:区分 Base 和 Cumulative 层的分界点
  • _rs_version_map:活跃的 Rowset 版本映射
  • _stale_rs_version_map:过期的 Rowset(Compaction 输入)

2. StorageEngine 初始化流程

2.1 初始化入口(_open)

cpp 复制代码
// be/src/olap/storage_engine.cpp
Status StorageEngine::_open() {
    // 1. 初始化 store_map(数据目录映射)
    RETURN_NOT_OK_STATUS_WITH_WARN(_init_store_map(), "_init_store_map failed");
    
    // 2. 设置集群 ID
    _effective_cluster_id = config::cluster_id;
    RETURN_NOT_OK_STATUS_WITH_WARN(_check_all_root_path_cluster_id(), 
                                   "fail to check cluster id");
    
    // 3. 更新存储介质类型计数
    _update_storage_medium_type_count();
    
    // 4. 检查文件描述符数量
    RETURN_NOT_OK_STATUS_WITH_WARN(_check_file_descriptor_number(), 
                                   "check fd number failed");
    
    // 5. 加载所有 DataDir 的数据
    auto dirs = get_stores();
    RETURN_IF_ERROR(load_data_dirs(dirs));
    
    // 6. 初始化 MemTable 刷盘执行器
    _disk_num = cast_set<int>(dirs.size());
    _memtable_flush_executor = std::make_unique<MemTableFlushExecutor>();
    _memtable_flush_executor->init(_disk_num);
    
    // 7. 初始化 DeleteBitmap 计算执行器(MOW表)
    _calc_delete_bitmap_executor = std::make_unique<CalcDeleteBitmapExecutor>();
    _calc_delete_bitmap_executor->init(config::calc_delete_bitmap_max_thread);
    
    // 8. 解析默认 Rowset 类型
    _parse_default_rowset_type();
    
    return Status::OK();
}

2.2 核心组件初始化

MemTableFlushExecutor

cpp 复制代码
// be/src/olap/memtable_flush_executor.h
class MemTableFlushExecutor {
public:
    void init(int num_disk);
    
    Status create_flush_token(std::shared_ptr<FlushToken>& flush_token,
                              std::shared_ptr<RowsetWriter> rowset_writer,
                              bool is_high_priority,
                              std::shared_ptr<WorkloadGroup> wg_sptr);
                              
private:
    std::unique_ptr<ThreadPool> _flush_pool;           // 普通优先级线程池
    std::unique_ptr<ThreadPool> _high_prio_flush_pool; // 高优先级线程池
    std::atomic<int> _flushing_task_count = 0;
};

CalcDeleteBitmapExecutor

  • 用于 Unique Key Merge-on-Write 模型
  • 计算 DeleteBitmap 标记重复键为删除状态
  • 避免读取阶段的 Merge 开销

3. 数据目录管理(DataDir)

3.1 DataDir 结构

cpp 复制代码
// be/src/olap/data_dir.h
class DataDir {
public:
    DataDir(StorageEngine& engine, const std::string& path, 
            int64_t capacity_bytes = -1,
            TStorageMedium::type storage_medium = TStorageMedium::HDD);
    
    Status init(bool init_meta = true);
    
    const std::string& path() const { return _path; }
    size_t available_bytes() const { return _available_bytes; }
    size_t disk_capacity_bytes() const { return _disk_capacity_bytes; }
    TStorageMedium::type storage_medium() const { return _storage_medium; }
    
private:
    StorageEngine& _engine;
    std::string _path;              // 数据目录路径
    size_t _path_hash;              // 路径哈希值
    size_t _available_bytes;        // 可用空间
    size_t _disk_capacity_bytes;    // 磁盘总容量
    TStorageMedium::type _storage_medium; // 存储介质类型(HDD/SSD)
};

3.2 多目录管理

Doris 支持配置多个数据目录,提供以下能力:

负载均衡

  • 创建 Tablet 时选择负载最低的目录
  • 均衡分布数据,避免单盘热点

存储介质分层

  • HDD:存储冷数据
  • SSD:存储热数据
  • 支持自动冷热数据迁移(Cooldown)

容错能力

  • 单盘故障不影响其他盘的数据
  • 支持数据在线迁移

4. TabletManager 详解

4.1 TabletManager 架构

cpp 复制代码
// be/src/olap/tablet_manager.h
class TabletManager {
public:
    TabletManager(StorageEngine& engine, int32_t tablet_map_lock_shard_size);
    
    // Tablet 生命周期管理
    Status create_tablet(const TCreateTabletReq& request, 
                        std::vector<DataDir*> stores, 
                        RuntimeProfile* profile);
    Status drop_tablet(TTabletId tablet_id, TReplicaId replica_id, 
                      bool is_drop_table_or_partition);
    
    TabletSharedPtr get_tablet(TTabletId tablet_id, 
                               bool include_deleted = false,
                               std::string* err = nullptr);
    
private:
    using tablet_map_t = std::unordered_map<int64_t, TabletSharedPtr>;
    
    struct tablets_shard {
        mutable std::shared_mutex lock;           // 分片锁
        tablet_map_t tablet_map;                  // Tablet 映射
        std::mutex lock_for_transition;           // 状态转换锁
        std::map<int64_t, std::tuple<std::string, std::thread::id, int64_t>> 
            tablets_under_transition;             // 正在转换状态的 Tablet
    };
    
    StorageEngine& _engine;
    const int32_t _tablets_shards_size;           // 分片数量
    const int32_t _tablets_shards_mask;           // 分片掩码
    std::vector<tablets_shard> _tablets_shards;   // Tablet 分片
    
    std::shared_mutex _partitions_lock;
    std::map<int64_t, Partition> _partitions;     // Partition 信息
    
    std::shared_mutex _shutdown_tablets_lock;
    std::list<TabletSharedPtr> _shutdown_tablets; // 待关闭的 Tablet
};

4.2 分片锁设计

TabletManager 使用 分片锁(Shard Lock) 降低锁竞争:

ini 复制代码
Tablet ID: 123456789
Shard Index = 123456789 & (shard_size - 1)

例如 shard_size = 1024:
Shard Index = 123456789 & 1023 = 853

访问 _tablets_shards[853] 时,只需锁住该分片

优势

  • 减少全局锁争用
  • 提高并发访问性能
  • 默认分片数:128 或 256

4.3 Tablet 创建流程

cpp 复制代码
Status TabletManager::create_tablet(const TCreateTabletReq& request,
                                   std::vector<DataDir*> stores,
                                   RuntimeProfile* profile) {
    // 1. 选择数据目录(负载最低的目录)
    DataDir* store = _select_data_dir(stores);
    
    // 2. 创建 TabletMeta
    TabletMetaSharedPtr tablet_meta = TabletMeta::create(
        request, tablet_uid, shard_id, next_unique_id, col_ordinal_to_unique_id);
    
    // 3. 创建 Tablet 对象
    TabletSharedPtr tablet = Tablet::create_tablet_from_meta(
        _engine, tablet_meta, store);
    
    // 4. 初始化 Tablet
    RETURN_IF_ERROR(tablet->init());
    
    // 5. 注册到 TabletManager
    RETURN_IF_ERROR(_add_tablet_unlocked(tablet_id, tablet, true, false));
    
    // 6. 创建初始 Rowset(版本 [0-1])
    RETURN_IF_ERROR(tablet->create_initial_rowset(1));
    
    return Status::OK();
}

5. Tablet 生命周期管理

5.1 Tablet 核心结构

cpp 复制代码
// be/src/olap/tablet.h
class Tablet final : public BaseTablet {
public:
    Tablet(StorageEngine& engine, TabletMetaSharedPtr tablet_meta, 
           DataDir* data_dir, const std::string_view& cumulative_compaction_type = "");
    
    // 版本管理
    int64_t cumulative_layer_point() const;
    void set_cumulative_layer_point(int64_t new_point);
    
    // Rowset 管理
    Status add_rowset(RowsetSharedPtr rowset);
    Status modify_rowsets(std::vector<RowsetSharedPtr>& to_add,
                         std::vector<RowsetSharedPtr>& to_delete,
                         bool check_delete = false);
    
    void delete_expired_stale_rowset();
    void max_continuous_version_from_beginning(Version* version, 
                                              Version* max_version = nullptr);
    
private:
    StorageEngine& _engine;
    DataDir* _data_dir = nullptr;
    
    std::atomic<int64_t> _cumulative_point;        // Cumulative 分界点
    
    // Rowset 版本映射(继承自 BaseTablet)
    std::unordered_map<Version, RowsetSharedPtr, HashOfVersion> _rs_version_map;
    std::unordered_map<Version, RowsetSharedPtr, HashOfVersion> _stale_rs_version_map;
};

5.2 Rowset 添加流程

cpp 复制代码
Status Tablet::add_rowset(RowsetSharedPtr rowset) {
    std::unique_lock wlock(_meta_lock);
    
    // 1. 检查版本是否已存在
    RETURN_IF_ERROR(_contains_version(rowset->version()));
    
    // 2. 添加到活跃 Rowset 映射
    _rs_version_map[rowset->version()] = rowset;
    
    // 3. 更新 TabletMeta
    _tablet_meta->add_rs_meta(rowset->rowset_meta());
    
    // 4. 更新累积点(如果需要)
    if (rowset->end_version() >= cumulative_layer_point()) {
        // 新 Rowset 超过累积点,可能需要调整
    }
    
    return Status::OK();
}

5.3 过期 Rowset 清理

cpp 复制代码
void Tablet::delete_expired_stale_rowset() {
    int64_t now = UnixMillis();
    std::vector<RowsetSharedPtr> expired_rowsets;
    
    {
        std::shared_lock rlock(_meta_lock);
        for (auto& [version, rowset] : _stale_rs_version_map) {
            int64_t rowset_stale_time = rowset->rowset_meta()->stale_at();
            int64_t interval = now - rowset_stale_time;
            
            // 超过过期时间阈值
            if (interval >= config::tablet_rowset_expired_stale_sweep_time_sec * 1000) {
                expired_rowsets.push_back(rowset);
            }
        }
    }
    
    // 删除过期 Rowset
    for (auto& rowset : expired_rowsets) {
        std::unique_lock wlock(_meta_lock);
        _stale_rs_version_map.erase(rowset->version());
        _tablet_meta->delete_stale_rs_meta_by_version(rowset->version());
    }
}

6. LSM-Tree 结构详解

6.1 cumulative_layer_point 机制

cpp 复制代码
// be/src/olap/tablet_meta.h
class TabletMeta {
private:
    int64_t _cumulative_layer_point = 0;  // Cumulative 分界点
    
    RowsetMetaMapContainer _rs_metas;       // 活跃的 Rowset
    RowsetMetaMapContainer _stale_rs_metas; // 过期的 Rowset
};

cumulative_layer_point 的作用

ini 复制代码
示例 1:cumulative_layer_point = 10
[0-0]   Base Rowset
[1-9]   Base Rowset (Base Compaction 输出)
[10-10] Delta Rowset  ← Cumulative Layer 开始
[11-11] Delta Rowset
[12-12] Delta Rowset

Base Compaction:   合并 [0-0] 和 [1-9]
Cumulative Compaction: 合并 [10-10], [11-11], [12-12]

更新时机

  1. Cumulative Compaction 完成后:point 前移到新 Rowset 的 end_version + 1
  2. 遇到 Delete 版本:跳过 Delete 版本,point = delete_version + 1
  3. 长时间无新数据:自动前移 point,让 Base Compaction 继续

6.2 Rowset 版本管理

cpp 复制代码
// be/src/olap/rowset/rowset_meta.h
class RowsetMeta {
public:
    Version version() const { 
        return {_rowset_meta_pb.start_version(), _rowset_meta_pb.end_version()}; 
    }
    
    int64_t start_version() const;
    int64_t end_version() const;
    
    bool is_singleton_delta() const {
        return has_version() && 
               _rowset_meta_pb.start_version() == _rowset_meta_pb.end_version();
    }
    
    bool produced_by_compaction() const {
        return has_version() &&
               (start_version() < end_version() ||
                (start_version() == end_version() && 
                 segments_overlap() == NONOVERLAPPING));
    }
    
    uint32_t get_compaction_score() const {
        if (!is_segments_overlapping()) {
            return 1;
        } else {
            return cast_set<uint32_t>(num_segments());
        }
    }
};

版本类型

  • Singleton Delta:[N-N],单版本 Rowset(数据导入产生)
  • Range Rowset:[M-N],版本范围 Rowset(Compaction 产生)

Compaction Score 计算

  • 非重叠 Rowset:Score = 1
  • 重叠 Rowset:Score = Segment 数量

6.3 版本跟踪器(TimestampedVersionTracker)

cpp 复制代码
// be/src/olap/base_tablet.h
class BaseTablet {
protected:
    TimestampedVersionTracker _timestamped_version_tracker;
    
    // 活跃 Rowset 映射
    std::unordered_map<Version, RowsetSharedPtr, HashOfVersion> _rs_version_map;
    
    // 过期 Rowset 映射
    std::unordered_map<Version, RowsetSharedPtr, HashOfVersion> _stale_rs_version_map;
};

TimestampedVersionTracker 功能

  • 跟踪每个版本的创建时间
  • 判断 Rowset 是否过期
  • 计算连续版本范围

7. 数据写入流程

7.1 MemTable → Rowset 流程

markdown 复制代码
数据导入流程:
1. DeltaWriter 写入 MemTable
2. MemTable 达到阈值触发 Flush
3. FlushToken 提交 Flush 任务
4. MemTableFlushExecutor 执行刷盘
5. RowsetWriter 生成 Segment 文件
6. Publish 使 Rowset 可见

7.2 FlushToken 机制

cpp 复制代码
// be/src/olap/memtable_flush_executor.h
class FlushToken : public std::enable_shared_from_this<FlushToken> {
public:
    FlushToken(ThreadPool* thread_pool, std::shared_ptr<WorkloadGroup> wg_sptr);
    
    Status submit(std::shared_ptr<MemTable> mem_table);
    Status wait();
    void cancel();
    
    void set_rowset_writer(std::shared_ptr<RowsetWriter> rowset_writer);
    
private:
    void _flush_memtable(std::shared_ptr<MemTable> memtable_ptr, 
                        int32_t segment_id, int64_t submit_task_time);
    
    Status _do_flush_memtable(MemTable* memtable, 
                             int32_t segment_id, int64_t* flush_size);
    
    std::shared_mutex _flush_status_lock;
    Status _flush_status;                          // Flush 状态
    FlushStatistic _stats;                         // 统计信息
    std::shared_ptr<RowsetWriter> _rowset_writer;  // Rowset Writer
    ThreadPool* _thread_pool;                      // 线程池
};

7.3 MemTable Flush 执行

cpp 复制代码
void FlushToken::_flush_memtable(std::shared_ptr<MemTable> memtable_ptr, 
                                 int32_t segment_id, int64_t submit_task_time) {
    // 1. 更新等待时间统计
    uint64_t flush_wait_time_ns = MonotonicNanos() - submit_task_time;
    _stats.flush_wait_time_ns += flush_wait_time_ns;
    
    // 2. 检查之前的 Flush 是否失败
    {
        std::shared_lock rdlk(_flush_status_lock);
        if (!_flush_status.ok()) {
            return;  // 之前已失败,直接返回
        }
    }
    
    // 3. 执行 Flush
    MonotonicStopWatch timer;
    timer.start();
    size_t memory_usage = memtable_ptr->memory_usage();
    int64_t flush_size;
    Status s = _do_flush_memtable(memtable_ptr.get(), segment_id, &flush_size);
    
    // 4. 处理 Flush 结果
    if (!s.ok()) {
        std::lock_guard wrlk(_flush_status_lock);
        LOG(WARNING) << "Flush memtable failed with res = " << s;
        _flush_status = s;
        return;
    }
    
    // 5. 更新统计信息
    _stats.flush_time_ns += timer.elapsed_time();
    _stats.flush_finish_count++;
    _stats.flush_size_bytes += memory_usage;
    _stats.flush_disk_size_bytes += flush_size;
}

7.4 RowsetWriter 写入 Segment

cpp 复制代码
// be/src/olap/rowset/segment_v2/column_writer.h
class ScalarColumnWriter : public ColumnWriter {
public:
    Status append_data(const uint8_t** ptr, size_t num_rows) override;
    Status finish_current_page() override;
    Status finish() override;
    Status write_data() override;
    Status write_ordinal_index() override;
    Status write_zone_map() override;
    Status write_bitmap_index() override;
    Status write_inverted_index() override;
    
private:
    std::unique_ptr<PageBuilder> _page_builder;              // Page 构建器
    std::unique_ptr<NullBitmapBuilder> _null_bitmap_builder; // NULL Bitmap
    std::vector<std::unique_ptr<Page>> _pages;               // 缓存的 Page
    
    std::unique_ptr<OrdinalIndexWriter> _ordinal_index_builder;      // 序号索引
    std::unique_ptr<ZoneMapIndexWriter> _zone_map_index_builder;     // Zone Map 索引
    std::unique_ptr<BitmapIndexWriter> _bitmap_index_builder;        // Bitmap 索引
    std::vector<std::unique_ptr<IndexColumnWriter>> _inverted_index_builders; // 倒排索引
    std::unique_ptr<BloomFilterIndexWriter> _bloom_filter_index_builder;      // BloomFilter
};

8. Compaction 机制

8.1 Base Compaction

触发条件(满足任一):

cpp 复制代码
// be/src/olap/base_compaction.cpp
Status BaseCompaction::pick_rowsets_to_compact() {
    _input_rowsets = tablet()->pick_candidate_rowsets_to_base_compaction();
    
    // 条件 1:Cumulative Rowset 数量达到阈值
    if (_input_rowsets.size() > config::base_compaction_min_rowset_num) {
        return Status::OK();
    }
    
    // 条件 2:Cumulative 数据量与 Base 数据量比值达到阈值
    int64_t base_size = _input_rowsets.front()->data_disk_size();
    int64_t cumulative_total_size = 0;
    for (auto it = _input_rowsets.begin() + 1; it != _input_rowsets.end(); ++it) {
        cumulative_total_size += (*it)->data_disk_size();
    }
    double cumulative_base_ratio = 
        cast_set<double>(cumulative_total_size) / cast_set<double>(base_size);
    
    if (cumulative_base_ratio > config::base_compaction_min_data_ratio) {
        return Status::OK();
    }
    
    // 条件 3:距离上次 Base Compaction 时间达到阈值
    int64_t interval_since_last = time(nullptr) - _input_rowsets[0]->creation_time();
    if (interval_since_last > config::base_compaction_interval_seconds_since_last_operation) {
        return Status::OK();
    }
    
    return Status::Error<BE_NO_SUITABLE_VERSION>("don't satisfy the policy");
}

Compaction 执行流程

cpp 复制代码
Status Compaction::merge_input_rowsets() {
    // 1. 创建 RowsetReader
    std::vector<RowsetReaderSharedPtr> input_rs_readers;
    for (auto& rowset : _input_rowsets) {
        RowsetReaderSharedPtr rs_reader;
        RETURN_IF_ERROR(rowset->create_reader(&rs_reader));
        input_rs_readers.push_back(std::move(rs_reader));
    }
    
    // 2. 创建输出 RowsetWriter
    RowsetWriterContext ctx;
    RETURN_IF_ERROR(construct_output_rowset_writer(ctx));
    
    // 3. 垂直 Compaction(按列组合并)
    if (_is_vertical) {
        res = Merger::vertical_merge_rowsets(
            _tablet, compaction_type(), *_cur_tablet_schema,
            input_rs_readers, _output_rs_writer.get(), way_num, _stats);
    } 
    // 4. 水平 Compaction(按行合并)
    else {
        res = Merger::merge_rowsets(
            _tablet, compaction_type(), *_cur_tablet_schema,
            input_rs_readers, _output_rs_writer.get(), &_stats);
    }
    
    // 5. 倒排索引 Compaction(如果有)
    RETURN_IF_ERROR(do_inverted_index_compaction());
    
    return res;
}

8.2 Cumulative Compaction

选择策略

cpp 复制代码
// be/src/olap/cumulative_compaction_policy.cpp
size_t SizeBasedCumulativeCompactionPolicy::pick_input_rowsets(
        Tablet* tablet,
        const std::vector<RowsetSharedPtr>& candidate_rowsets,
        const int64_t max_compaction_score,
        const int64_t min_compaction_score,
        std::vector<RowsetSharedPtr>* input_rowsets,
        Version* last_delete_version,
        size_t* compaction_score,
        bool allow_delete) {
    
    size_t promotion_size = tablet->cumulative_promotion_size();
    int64_t total_size = 0;
    *compaction_score = 0;
    
    for (auto& rowset : candidate_rowsets) {
        // 1. 遇到 Delete 版本
        if (!allow_delete && rowset->rowset_meta()->has_delete_predicate()) {
            *last_delete_version = rowset->version();
            if (!input_rowsets->empty()) {
                break;  // 有其他版本,先合并
            } else {
                input_rowsets->clear();
                *compaction_score = 0;
                continue;  // 跳过 Delete 版本
            }
        }
        
        // 2. 累积 Compaction Score
        *compaction_score += rowset->rowset_meta()->get_compaction_score();
        total_size += rowset->rowset_meta()->total_disk_size();
        input_rowsets->push_back(rowset);
        
        // 3. 达到最大 Score,停止选择
        if (*compaction_score >= max_compaction_score) {
            break;
        }
    }
    
    // 4. 数据量达到 Promotion Size,提升到 Base 层
    if (total_size >= promotion_size) {
        return *compaction_score;
    }
    
    // 5. 选择同一 Level 的 Rowset
    auto rs_begin = input_rowsets->begin();
    while (rs_begin != input_rowsets->end()) {
        auto& rs_meta = (*rs_begin)->rowset_meta();
        int64_t current_level = _level_size(rs_meta->total_disk_size());
        int64_t remain_level = _level_size(total_size - rs_meta->total_disk_size());
        
        if (current_level == remain_level) {
            break;  // 找到同级 Rowset
        }
        
        // 移除不同级的 Rowset
        total_size -= rs_meta->total_disk_size();
        *compaction_score -= rs_meta->get_compaction_score();
        rs_begin++;
    }
    
    input_rowsets->erase(input_rowsets->begin(), rs_begin);
    return *compaction_score;
}

Level 计算

cpp 复制代码
int64_t SizeBasedCumulativeCompactionPolicy::_level_size(int64_t size) {
    // Level 0: 0 ~ 64MB
    // Level 1: 64MB ~ 128MB
    // Level 2: 128MB ~ 256MB
    // ...
    if (size < 64 * 1024 * 1024) return 0;
    
    int64_t level = 0;
    int64_t threshold = 64 * 1024 * 1024;
    while (size >= threshold) {
        level++;
        threshold *= 2;
    }
    return level;
}

8.3 Compaction 优化

垂直 Compaction

scss 复制代码
传统 Compaction(水平):
[Rowset1] Row1 Row2 Row3
[Rowset2] Row4 Row5
[Rowset3] Row6 Row7
     ↓ 合并所有列
[Output] Row1~Row7 (所有列)

垂直 Compaction:
[Rowset1] Key列 | 列组1 | 列组2
[Rowset2] Key列 | 列组1 | 列组2
     ↓ 分列组合并
[Output] Key列 → 列组1 → 列组2

优势

  • 降低内存峰值(只加载部分列)
  • 提高 I/O 效率(按列组顺序写入)
  • 适合宽表场景

9. 版本发布机制(Publish)

9.1 事务两阶段提交

cpp 复制代码
// be/src/olap/txn_manager.cpp
Status TxnManager::publish_txn(OlapMeta* meta, 
                               TPartitionId partition_id,
                               TTransactionId transaction_id, 
                               TTabletId tablet_id,
                               TabletUid tablet_uid, 
                               const Version& version,
                               TabletPublishStatistics* stats,
                               std::shared_ptr<TabletTxnInfo>& extend_tablet_txn_info) {
    auto tablet = _engine.tablet_manager()->get_tablet(tablet_id);
    if (tablet == nullptr) {
        return Status::Error<TABLE_NOT_FOUND>("tablet not found");
    }
    
    // 1. 获取已提交的 Rowset
    TabletTxnInfo txn_info;
    {
        std::shared_lock txn_rlock(_get_txn_map_lock(transaction_id));
        auto it = _get_txn_tablet_map(transaction_id).find(tablet_info);
        if (it == _get_txn_tablet_map(transaction_id).end()) {
            return Status::Error<TRANSACTION_NOT_EXIST>("txn not found");
        }
        txn_info = it->second;
    }
    
    // 2. 设置版本号
    RowsetSharedPtr rowset = txn_info.rowset;
    rowset->make_visible(version);
    
    // 3. 添加到 Tablet
    Status res = tablet->add_inc_rowset(rowset);
    if (!res.ok()) {
        return res;
    }
    
    // 4. 更新 TabletMeta
    {
        std::unique_lock tablet_wlock(tablet->get_header_lock());
        tablet->save_meta();
    }
    
    // 5. 删除事务信息
    {
        std::unique_lock txn_wlock(_get_txn_map_lock(transaction_id));
        _get_txn_tablet_map(transaction_id).erase(tablet_info);
    }
    
    return Status::OK();
}

9.2 Rowset 可见性控制

cpp 复制代码
// be/src/olap/rowset/rowset.h
class Rowset {
public:
    void make_visible(Version version) {
        _rowset_meta->set_version(version);
        _rowset_meta->set_rowset_state(VISIBLE);
    }
    
    bool is_pending() const { return _is_pending; }
    
    Version version() const { return _rowset_meta->version(); }
    int64_t start_version() const { return _rowset_meta->version().first; }
    int64_t end_version() const { return _rowset_meta->version().second; }
};

Rowset 状态

  • COMMITTED:事务已提交,等待 Publish
  • VISIBLE:已 Publish,查询可见

10 冷热数据分层(Cooldown)

10.1 Cooldown 架构

cpp 复制代码
// be/src/olap/tablet.h
struct CooldownConf {
    int64_t term = -1;
    int64_t cooldown_replica_id = -1;
};

class Tablet {
public:
    Status cooldown(RowsetSharedPtr rowset = nullptr);
    RowsetSharedPtr pick_cooldown_rowset();
    RowsetSharedPtr need_cooldown(int64_t* cooldown_timestamp, size_t* file_size);
    
private:
    CooldownConf _cooldown_conf;
    mutable std::shared_mutex _cooldown_conf_lock;
    std::mutex _cold_compaction_lock;
};

10.2 Cooldown 执行流程

cpp 复制代码
Status Tablet::_cooldown_data(RowsetSharedPtr rowset) {
    // 1. 检查是否为 Cooldown 副本
    DCHECK(_cooldown_conf.cooldown_replica_id == replica_id());
    
    // 2. 获取远程存储资源
    auto storage_resource = DORIS_TRY(get_resource_by_storage_policy_id(storage_policy_id()));
    
    // 3. 选择待 Cooldown 的 Rowset
    if (!rowset) {
        rowset = pick_cooldown_rowset();
    }
    if (!rowset) {
        return Status::OK();
    }
    
    // 4. 上传 Rowset 到远程存储
    RowsetId new_rowset_id = _engine.next_rowset_id();
    auto start = std::chrono::steady_clock::now();
    RETURN_IF_ERROR(rowset->upload_to(storage_resource, new_rowset_id));
    
    auto duration = std::chrono::duration<double>(std::chrono::steady_clock::now() - start);
    LOG(INFO) << "Upload rowset " << rowset->version() 
              << " to " << storage_resource.fs->root_path().native()
              << ", tablet_id=" << tablet_id()
              << ", duration=" << duration.count()
              << ", capacity=" << rowset->total_disk_size();
    
    // 5. 生成新的 RowsetMeta(指向远程)
    auto new_rowset_meta = std::make_shared<RowsetMeta>();
    new_rowset_meta->init(rowset->rowset_meta().get());
    new_rowset_meta->set_rowset_id(new_rowset_id);
    new_rowset_meta->set_remote_storage_resource(std::move(storage_resource));
    
    // 6. 创建新 Rowset
    RowsetSharedPtr new_rowset;
    RETURN_IF_ERROR(RowsetFactory::create_rowset(
        _tablet_meta->tablet_schema(), "", new_rowset_meta, &new_rowset));
    
    // 7. 替换本地 Rowset
    {
        std::unique_lock wlock(_meta_lock);
        RETURN_IF_ERROR(delete_rowsets({std::move(rowset)}, false));
        add_rowsets({std::move(new_rowset)});
        _tablet_meta->set_cooldown_meta_id(UniqueId::gen_uid());
    }
    
    // 8. 保存元数据
    {
        std::shared_lock rlock(_meta_lock);
        save_meta();
    }
    
    // 9. 上传 Cooldown Meta 到远程
    async_write_cooldown_meta(shared_from_this());
    
    return Status::OK();
}

10.3 Cooldown Rowset 选择

cpp 复制代码
RowsetSharedPtr Tablet::pick_cooldown_rowset() {
    if (!_has_data_to_cooldown()) {
        return nullptr;
    }
    
    int64_t cooldowned_version = -1;
    int64_t min_local_version = std::numeric_limits<int64_t>::max();
    RowsetSharedPtr rowset;
    
    {
        std::shared_lock rlock(_meta_lock);
        for (auto& [v, rs] : _rs_version_map) {
            if (!rs->is_local()) {
                cooldowned_version = std::max(cooldowned_version, v.second);
            } else if (v.first < min_local_version) {
                min_local_version = v.first;
                rowset = rs;
            }
        }
    }
    
    // 确保版本连续性
    if (min_local_version != cooldowned_version + 1) {
        if (UNLIKELY(cooldowned_version != -1)) {
            LOG(WARNING) << "version not continuous. tablet_id=" << tablet_id()
                         << " cooldowned_version=" << cooldowned_version
                         << " min_local_version=" << min_local_version;
        }
        return nullptr;
    }
    
    return rowset;
}

Cooldown 策略

  • 按版本顺序 Cooldown(保证连续性)
  • 优先 Cooldown 最老的 Rowset
  • 支持按时间或大小触发

11. 性能优化建议

  1. 调整 cumulative_layer_point

    • 根据写入频率调整累积点前移策略
    • 避免过多小 Rowset 积累
  2. Compaction 参数调优

    • base_compaction_min_rowset_num:Base 触发阈值
    • cumulative_compaction_max_deltas:Cumulative 最大 Score
    • vertical_compaction_num_columns_per_group:垂直 Compaction 列组大小
  3. MemTable Flush 优化

    • 调整 MemTable 大小(write_buffer_size)
    • 增加 Flush 线程池大小(num_threads_per_disk)
  4. Cooldown 策略

    • 设置合理的 Cooldown 时间阈值
    • 监控远程存储访问延迟
    • 使用 SSD 作为本地缓存
相关推荐
@淡 定3 小时前
Redis热点Key独立集群实现方案
数据库·redis·缓存
laocooon5238578863 小时前
mysql,100个题目。
数据库·sql·mysql
Web极客码4 小时前
如何在Ubuntu服务器上安装和配置BIND9
服务器·数据库·ubuntu
W001hhh4 小时前
数据库实训Day004上午
数据库
funfan05174 小时前
【运维】MySQL数据库全量备份与恢复实战指南:从入门到精通
运维·数据库·mysql
+VX:Fegn08954 小时前
计算机毕业设计|基于springboot + vue在线音乐播放系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
tq10865 小时前
通用数据引用表示法:基于协议-URI-JSONPath的简洁数据定位规范
数据库
+VX:Fegn08955 小时前
计算机毕业设计|基于springboot + vue律师咨询系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计
manuel_897575 小时前
六 系统安全
网络·数据库·系统安全
液态不合群6 小时前
【面试题】MySQL 三层 B+ 树能存多少数据?
java·数据库·mysql