深入解析 GreptimeDB 全新时序存储引擎 Mito
前言
GreptimeDB 在 0.4 版本中进行了一系列重构和改进,其中就包括底层时序数据存储引擎 Mito
的重大升级。在这次升级中,我们几乎重写了整个存储引擎,包括:对引擎的架构进行重构、重新实现了部分核心组件、调整了数据存储格式和引入更多针对时序场景的优化方案。在替换掉原有的存储引擎后,GreptimeDB 在时序场景下的整体读写性能都有了相当大的进步,部分查询相比 0.3 版本更是有 10 倍以上的提升。
本文将分几个方面简要介绍 0.4 版本中对 Mito
引擎的重构和改进,帮助用户进一步了解 GreptimeDB 的存储引擎。
架构重构
我们存储引擎最大的重构就是对引擎写入架构的调整。下图为 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
引擎的优化工作尚未结束,引擎的性能也仍有不少提升空间,欢迎感兴趣的读者持续关注我们的代码仓库。
评论