深入解析 GreptimeDB 全新时序存储引擎 Mito | 周三直播预约中

前言


GreptimeDB 在 0.4 版本中进行了一系列重构和改进,其中就包括底层时序数据存储引擎 `Mito` 的重大升级。在这次升级中,我们几乎重写了整个存储引擎,包括:对引擎的架构进行重构、重新实现了部分核心组件、调整了数据存储格式和引入更多针对时序场景的优化方案。在替换掉原有的存储引擎后,GreptimeDB 在时序场景下的整体读写性能都有了相当大的进步,部分查询相比 0.3 版本更是有10 倍以上的提升

本文将分几个方面简要介绍 0.4 版本中对 `Mito` 引擎的重构和改进,帮助用户进一步了解 GreptimeDB 的存储引擎。

直播预告


预告‼️ 就在本周三 10 月 25 日 19:00,Greptime 团队首个直播强势来袭,我们将介绍 GreptimeDB v0.4 的新功能并深入解析专为时序数据而生的全新存储引擎 Mito!
微信视频号搜索 Greptime 预约本周三晚七点的直播,听 Greptime 技术 VP 和核心工程师讲解 v0.4 存储引擎的高效性能 👀 (参与直播的观众有机会参与 Greptime 限定版礼品抽奖哦🎁~)

架构重构


我们存储引擎最大的重构就是对引擎写入架构的调整。下图为 0.3 版本时存储引擎的写入架构:

图中 `region` 可以看作是 GreptimeDB 中的一个数据分区,类似 HBase 中的 `region`。我们的存储引擎使用的是 LSM-Tree 存储数据,每次写入都需要依次将数据写入 WAL 和每个 `region` 的 `memtable`。为了突出重点,我们在图里省略了写入 `memtable` 的部分,有兴趣的读者可以查阅 LSM-Tree 相关的资料做进一步了解。

在老的架构中,每个 `region` 分别有一个叫 `RegionWriter` 的组件负责写入数据。这个组件通过不同的锁来保护不同的内部状态,同时为了避免查询阻塞写入,部分状态通过原子变量维护,更新时才需要加锁。该方案虽然实现较为简单,但存在着以下问题:

  • 不易于攒批;
  • 需要时刻小心不同的锁所保护的状态,编码负担较大;
  • 如果 `region` 数量较多,对 WAL 的写入请求也会被分散掉。

在 0.4 版本中,我们对引擎的写入架构进行了重构。重构后的架构如下图所示:

在新的架构中,存储引擎预先分配了若干个写入工作线程 `RegionWorker` 来负责处理写入请求,每个 `region` 由固定的工作线程管理:

  • `RegionWorker` 支持攒批,可以批量处理多个请求,提高写入吞吐;
  • 写入行为只在 `RegionWorker` 线程内执行,无需考虑并发修改的问题,因此可以去掉部分锁,简化并发处理;
  • `RegionWorker` 能够合并不同 `region` 的 WAL 写入请求。

Memtable 优化


旧存储引擎中的 `memtable` 实现一直没有针对时序场景进行优化,只是单纯地将数据按行写入到 BTree 中。这个实现存在着内存放大较为严重,读写性能一般等问题。其中内存放大严重带来了不少问题:

  • `memtable` 很快就会被写满然后触发 flush;
  • 由于数据量太少, flush 后的文件太小,降低后续查询和 compaction 的效率。

在 0.4 中,我们重新实现了引擎的 `memtable`,重点提高了空间利用率。

新的 `memtable` 中具有以下特点:

  • 对同一个时间序列,我们只存储一次它的 tags (labels);
  • 一个时间序列的数据存储在一个 `Series` 结构体中,同时提供 `Series` 粒度的锁;
  • 在 `Series` 内部,我们将数据按列存储,并分为一个 `active` 和 `frozen` 两种 buffer(缓冲区);
  • 数据会先写入 `active` buffer 中,在查询时再转为不可变的 `fronzen` 的 buffer 以提供读取。在读取不可变的 buffer 时也不需要持续持有锁,减少读写的锁竞争。

在我们的测试场景下,导入 4000 个时间序列共计 500000 行数据到 `memtable` 后,新的 `memtable` 实现可以比老的 `memtable` 节省 10 倍以上的内存。

存储格式调整


针对时序场景的特点,我们在 0.4 中对数据的存储格式也做了调整。每次查询时,我们都需要拿到所有的 tags 列,后续也需要通过比较所有的 tags 列来确定数据是否属于同一个时间序列。为了提高查询效率,我们将每行数据的 tags 列编码得到完整的 `__primary_key`,并直接把 `__primary_key` 作为一列单独存储,如下图中的 `__primary_key` 列所示。我们对 `__primary_key` 列做字典编码以减少存储空间。

这个方案的优点包括:
- 在查询时,我们读取 `__primary_key` 这一列就可以拿到所有 tag 列的数据,减少需要读取的列的数量;

  • 时序场景下,直接字典化存储 `__primary_key` 仍然有不错压缩效果;
  • `__primary_key` 可以直接比较;
  • 文件扫描速度有 2~4 倍的提升。

Benchmark


0.4 版本在 TSBS 的读写性能测试中均有提升。写入方面,在引入 `RegionWorker` 进行攒批后, TSBS 场景的写入性能提升了约 30%。

查询方面,我们在对存储格式和数据扫描路径进行了优化,提升了数据的扫描性能。对于 TSBS 中需要扫描大量数据的场景,0.4 的查询速度要比 0.3 快数倍。例如在 `high-cpu` 和 `double-group-by` 场景中,存储引擎可能需要扫描一段时间范围内的所有数据。在这部分查询中, 0.4 比 0.3 快 3 ~ 10 倍。

在 `single-group-by` 系列查询中, `single-groupby-1-1-12` 需要查询 12 个小时的数据。这个场景下 0.4 比 0.3 快 14 倍。

小结


在 GreptimeDB 0.4 中,我们重构并改进了时序存储引擎 `Mito`,取得了不错的性能提升。本文简要介绍了其中的一部分优化。目前,我们对 `Mito` 引擎的优化工作尚未结束,引擎的性能也仍有不少提升空间,欢迎感兴趣的读者持续关注我们的代码仓库。


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值