VictoriaMetric 数据文件格式

2020/07/17

VictoriaMetric 的数据文件基本仿照 ClickHouse 的格式。

去除掉允许改表的结构,加一层 TSID 的索引为时序特性特化。

类似于有两个字段的表:timestamp 和 values。

数据由 timestamp.bin / values.bin / index.bin / metaindex.bin 组成。

TSID

即唯一 ID,ID 由 MetricGroup / JobID / InstanceID / MetricID 组成。

即 prometheus 中固定存在的 __name__, job, instance 三个 tag 字符串做 xxhash64 截断拿到 id 上。

id 为定长 24byte。

MetricGroup / JobID / InstanceID 三个 tag 有明显的层级结构。

MetricID 为自增的原子变量,起点为服务启动时的纳秒值。

TSID: 24 byte
  MetricGroup: 8 byte
  JobIB:       4 byte
  InstanceID:  4 byte
  MetricID:    8 byte

Part

一个 part 由 timestamp.bin / values.bin / index.bin / metaindex.bin 四个文件组成。

part 是一个内存结构。一个 part 代表一组文件。

Part Header

part 的 header 写在文件夹名上,各个分段间由 _ 分割。

Part Header:    string
  RowsCount:    string
  BlocksCount:  string
  MinTimestamp: string
  MaxTimestamp: string

当装载一个 Part 时 .metaindex 这个文件需要读入内存。

Meta Index

Meta Index Row: 56 byte
  TSID:              24 byte
  BlockHeadersCount: 4  byte
  MinTimestamp:      8  byte
  MaxTimestamp:      8  byte
  IndexBlcokOffset:  8  byte
  IndexBlcokSize:    4  byte

metaindex.bin 由多个 Meta Index Row 使用 zstd 压缩后组成。

在内存中解压后 meta index 可以进行二分搜索,通过 TSID 定位 index block offset。

Meta Index Row 使用了结构体,为了两个 4 byte 的字段需要做对齐,实际占用 64 byte 保存一个 row。

Index Block

index.bin 中存储连续存放的 Block Headers。

从 Meta Index 拿到 offset 后可以在这里找到对应的 Block Header 数组。

将属于同一个 TSID 的 Block Header 连续存放,外面套上 zstd 常规压缩后在连续存放。

即为 index.bin。

Block Header: 81 byte
  TSID:                 24 byte
  MinTimestamp:         8  byte
  MaxTimestamp:         8  byte
  FirstValue:           8  byte
  TimestampBlockOffset: 8  byte
  ValueBlockOffset:     8  byte
  TimestampBlockSize:   4  byte
  ValueBlockSize:       4  byte
  RowsCount:            4  byte
  Scale:                2  byte
  TimestampMarshalType: 1  byte
  ValueMarshalType:     1  byte
  PrecisionBits:        1  byte

其中 Scale 为 value 的倍数。存储的数据 = (int)真实数据 * Scale

PrecisionBits 是当使用 NearestDelta2 这种编码时的有效位长度。

拿到 Block Header 后即可定位到多组 TimestampBlockSize 和 ValueBlockOffset,分别去 timestamp.binvalues.bin 中寻找数据。

Block

TimestampBlock 与 ValueBlock 文件结构上无区别。文件中只有数据。

比较神奇的是 VictoriaMetric 没有用 XOR 编码。

https://medium.com/faun/victoriametrics-achieving-better-compression-for-time-series-data-than-gorilla-317bc1f95932

首先将所有数据 * Scale 转化为整数。然后数出最长的公共的后缀 0 的个数,再做除法。

注意这都是 10 进制的。

然后有如下 4 种编码方式:

// constantly changed time series with constant delta. 固定间隔的值
MarshalTypeDeltaConst = MarshalType(2)

// time series containing only a single constant. 全一样的值
MarshalTypeConst = MarshalType(3)

// counter timeseries. 即所谓的 double delta
MarshalTypeNearestDelta2 = MarshalType(5)

// gauge timeseries. 只取一次 delta
MarshalTypeNearestDelta = MarshalType(6)

在这 4 中编码方式上再加一层 ZSTD 就有了另外 4 中压缩方式。

in memory part

ClickHouse 没有 in memory part, 要求客户端为输入做排序,并大块插入。

VictoriaMetric 也有类似的设计。在 agent 中处理不连续的数据,转换成 promethrus remote write 协议向存储发送。

在数据发送前是不可查询的。

index

名为 merge set, 结构依旧类似 ClickHouse。

metaindex.bin index.bin items.bin lens.bin 这四种文件组成

merge set 是一个前缀 kv。使用 Seek 实现倒排索引。

其时序部分依旧在 sotrage/index_db 中实现。

索引有 6 个

0 + MetricName(字符串) + TagKV(有序 字符串数组)               -> TSID

1 + MetricName(字符串)                                        -> MetricID
1 + TagK(字符串) + TagV(字符串)                               -> MetricID

2 + MetricID(8 byte)                                          -> TSID(24 byte)

3 + MetricID(8 byte)                                          -> MetricName(字符串) + TagKV(有序 字符串数组)

4 + MetricID(8 byte)                                          -> 空,标识已经删除的 metric

5 + 时间(8byte)                                               -> MetricID(8byte)

6 + 时间(8byte) + MetricName(字符串) + TagKV(有序 字符串数组) -> MetricID