内存数据库

内存结构

memory overview

整个内存结构更像是一个内存时序存储,主要包括如下 2 大块组成:

  1. Metric Meta/Index: 主要存储了 Metric Metadata 和 Tags 的索引;
    • Metric Metadata: 存储该 Metric 下面对应有哪些 Field 和哪些 Tag Keys,同时为每个 Tag Key 生成一个数据库级别的全局唯一的 ID,方便 Tag Index 在底层 KV Store 中的存储;
    • Metric Index: 主要是为每个 Metric Tag Key 下面的 Tag Values 做一个倒排索引,Term 是对应的 Tag Value, Posting List 为该 Metric 下对应的 Series ID ;
    • Hash(Tags) => Series ID: 通过 Tags Hash Code 计算相应的 Series ID 是否已经存在;
  2. Metric Store: 主要存储了 Metric 下面的数据,即该 Metric 下面的所有对应 Series 的每个 Field 的数据;
    • Field List(Field Store): 根据不同的 Field Type 存储该 Field 的实际数据

整个内存结构中维护了上面 2 个数据结构的 2 个映射关系。

Series ID 的唯一性

部分设计考虑了 OpenTSDB String => Int 的思路,考虑到时序数据的特点,Metric Name + Tags 这部分占用存储大头的数据存储在 Meta/Index 中: Filtering/Grouping 等操作将基于这部分数据来进行,实际存储数据时只存储到 Series ID 。

Metric 下的每一条 Series 线的唯一性通过 tags 来确定:对于 tags(ip=1.1.1.1, host=test.vm, zone=nt),先根据 tagKey 作排序,得到 host=test.vm,ip=1.1.1.1,zone=nt 。如果原始存储 tags 文本到 Series ID 的映射关系,会浪费过多的存储空间。权衡效率后,LinDB 通过对 tags 计算 hash 来记录 Series,关于碰撞机率可参考生日碰撞open in new window

一般化的哈希碰撞机率公式如下:

哈希碰撞机率公式

通过以上公式,计算得 64 位 hash 在不同的 tags 组合数下的碰撞概率见下表。其中 d 为取值空间,n 为数据集规模。 在监控领域下,Metric 下 tags 的组合数极少能达到 1M 量级,即使在该量级碰撞机率也极低。

Tags组合数量至少有2条Series出现碰撞的机率
100K0.000000000271
1M0.0000000271
10M0.00000271
100M0.000271
1G0.0271

整个写入流程如下:

  1. 首先通过 Metric Name 查询是否存在对应的 Metric Meta;
    • 存在: 使用 Metric Meta 中的数据,找到对应的 Field ID 和 Series ID,如果存在直接返回相应的 ID,如果没有对应的 Field ID 和 Series ID 的话先生成对应的 ID,同时往 Index 里面写入引索数据;
    • 不存在: 这种情况为一个新的 Metric, 返回生成对应的 ID,同时往 Index 里面写入引索数据;
    • Index 使用 Roaring Bitmap 为存储 Series ID Posting List;
  2. 拿到 Metric ID/Series ID 之后,就可以操作 Metric Store,找到对应的 Field Store 写入即可;

这里需要注意的地方有,如果直接使用 Map 来存储 Metric Store 里面的 Map 的话内存的开销还是比较大的,因为本身已经都转换成了 Int,所以这里可以使用 Roaring Bitmap + Array 的数据结构来构建一个 Map 结构,在 Roaring Bitmap 中存储所有 Keys 的值,Key 在 Roaring Bitmap 中的位置就是 Array 的 Index ,所以读取的效率也可以保证,同时可以与倒排索引里面 Roaring Bitmap 保持一致。

深入 Roaring Bitmap 之后发现 Roaring Bitmap 内部是以 High Container + Low Container 的方式存储的,所以 Roaring Bitmap + Array 也可以使用这个思想来对进一步优化 Map 结构,即使用 Roaring Bitmap + []Array 的结构, Key 的高位使用 Roaring Bitmap 的 High Container 来存储, Key 的低位用 Low Container 来存储, Key 的高位对应一个 Array ,整体就是一个 2 维数组。这样就可以通过 Key 的高位来进行并行查询操作。

Flush

系统会定期检测当前系统中每个 Memory TSDB 是否已经超过了内存大,如果满足下面条件中的任何一种,都需要把内存中的数据 Flush 到磁盘上:

  1. 单个 Memory TSDB 的内存占用超过指定大小;
  2. 该个 Memory TSDB 距离上次 Flush 时间超过了一定的大小;
  3. 该 LinDB 实例总内存占用超过一定大小之后,Flush 哪些占用内存比较大的 Memory TSDB,以防止该实例 OOM;

文件结构请看 Storage SSTable

参考
  1. OpenTSDB UIDopen in new window
  2. Probability of secure hash collisionsopen in new window