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

原创
2023/10/24 11:38
阅读数 92

前言

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 和每个 regionmemtable。为了突出重点,我们在图里省略了写入 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 内部,我们将数据按列存储,并分为一个 activefrozen 两种 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-cpudouble-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
分享
返回顶部
顶部