Prometheus TSDB文件格式-index
存储目录
如下所示为Prometheus/TSDB存储目录结构:
drwxrwxr-x 3 service service 4096 Nov 9 12:08 01EPNJJA12FXV9YDYM3MBNE3HP
drwxrwxr-x 3 service service 4096 Nov 10 10:10 01EPQY3Z6AJ700B9DVFWSG0FSD
drwxrwxr-x 3 service service 4096 Nov 11 05:26 01EPSZZJ29EQZ9EQ1Z15EGGRJM
drwxrwxr-x 3 service service 4096 Nov 11 15:30 01EPV3C56BA53YZ4H28PQHBWQV
drwxrwxr-x 3 service service 4096 Nov 11 16:30 01EPV6T1RWCFQ6T4RVGAN2G7BG
drwxrwxr-x 3 service service 4096 Nov 11 17:11 01EPV8V50SMAJ617XQKWZ344HD
drwxrwxr-x 3 service service 4096 Nov 11 17:30 01EPVA7WJ5DXTV6FR06VJ0CT40
-rw------- 1 service service 7 Dec 13 2019 lock
-rw-rw-r-- 1 service service 200 Sep 26 2019 schema
drwxrwxr-x 2 service service 4096 Nov 11 17:42 wal
其中类似'01EPVA7WJ5DXTV6FR06VJ0CT40'的目录为其中一个block。如下所示为block的目录结构:
drwxrwxr-x 2 service service 4096 Nov 11 17:30 chunks
-rw-rw-r-- 1 service service 104684896 Nov 11 17:30 index
-rw-rw-r-- 1 service service 302 Nov 11 17:30 meta.json
-rw-rw-r-- 1 service service 9 Nov 11 17:30 tombstones
其中meta.json
文件描述概要信息,一看就懂,不必多言:
{
"ulid": "01EPVA7WJ5DXTV6FR06VJ0CT40",
"minTime": 1605081600,
"maxTime": 1605085200,
"stats": {
"numSamples": 1359295562,
"numSeries": 441979,
"numChunks": 11207472
},
"compaction": {
"level": 1,
"sources": [
"01EPVA7WJ5DXTV6FR06VJ0CT40"
]
},
"version": 2,
"numChunkFile": 3
}
其中index
文件用于定位和查询指标数据,它是本文的主角。
index磁盘格式
首先,声明一下index磁盘格式不等于index到内存里的格式,当然也差不太多。
如下所示为每个block目录下面必有的index文件磁盘格式,index以TOC(table of contents)目录表作为结尾。
TOC也作为index文件的入口:总是从文件尾部倒数N个字节读取TOC开始干活。
┌────────────────────────────┬─────────────────────┐
│ magic(0xBAAAD700) <4b> │ version(1) <1 byte> │
├────────────────────────────┴─────────────────────┤
│ ┌──────────────────────────────────────────────┐ │
│ │ Symbol Table │ │
│ ├──────────────────────────────────────────────┤ │
│ │ Series │ │
│ ├──────────────────────────────────────────────┤ │
│ │ Label Index 1 │ │
│ ├──────────────────────────────────────────────┤ │
│ │ ... │ │
│ ├──────────────────────────────────────────────┤ │
│ │ Label Index N │ │
│ ├──────────────────────────────────────────────┤ │
│ │ Postings 1 │ │
│ ├──────────────────────────────────────────────┤ │
│ │ ... │ │
│ ├──────────────────────────────────────────────┤ │
│ │ Postings N │ │
│ ├──────────────────────────────────────────────┤ │
│ │ Label Offset Table │ │
│ ├──────────────────────────────────────────────┤ │
│ │ Postings Offset Table │ │
│ ├──────────────────────────────────────────────┤ │
│ │ TOC │ │
│ └──────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────┘
上述主要部分(section)之间,在写入index时会追加一些padding字节用于对齐,在读取时,需要跳过那些不在len里的值为0的padding字节。
大部分section都以len字段开始,len指定了该section有效的字节数,在这些有效数据之后,追加了一个checksum,它基于len的有效数据进行CRC32计算。
Symbol Table 字符串符号表
Symbol Table保存了所有sereis的label用到的字符串,并做了排序和去重。在后续的section中,将通过引用字符串的序号,达到降低index空间的目的。
Symbol Table包含一个序列的字符串条目,每个条目包括一个字符串原始字节的长度(len)和字符串实际字节两个部分。字符串的格式是UTF-8,并且按字典序升序排序,通过条目序列的索引号对其进行引用。
┌────────────────────┬─────────────────────┐
│ len <4b> │ #symbols <4b> │
├────────────────────┴─────────────────────┤
│ ┌──────────────────────┬───────────────┐ │
│ │ len(str_1) <uvarint> │ str_1 <bytes> │ │
│ ├──────────────────────┴───────────────┤ │
│ │ . . . │ │
│ ├──────────────────────┬───────────────┤ │
│ │ len(str_n) <uvarint> │ str_n <bytes> │ │
│ └──────────────────────┴───────────────┘ │
├──────────────────────────────────────────┤
│ CRC32 <4b> │
└──────────────────────────────────────────┘
Series 时序数据索引表
该section包含了所有的series时序数据,包括sereis的label sets和其block内的chunks信息,series按label sets的字典序排序。
每个series按16字节对齐,series的ID=offset/16
,后面section通过ID对该series进行引用。因此series的ID列表和sereis按字典序排序后的列表一一对应。
┌───────────────────────────────────────┐
│ ┌───────────────────────────────────┐ │
│ │ series_1 │ │
│ ├───────────────────────────────────┤ │
│ │ . . . │ │
│ ├───────────────────────────────────┤ │
│ │ series_n │ │
│ └───────────────────────────────────┘ │
└───────────────────────────────────────┘
每个sereis section以len
开始,以CRC32
结尾。数据区首先是一个8字节数值标识labels的个数,然后是所有的labels,每个label包括name和value的ID号(对Symbol Table的引用)。该series的labels对按字典序排序。
紧随label表格之后是对chunk的索引表,首先是一个8字节数值标识chunk的个数,然后是所有的chunks,每个chunk条目包括:chunk的最小时间mint
和最大时间maxt
,以及一个8字节数值指向chunk文件的位置position。mint
即该series在该chunk里第一个sample的时间戳,maxt
即该series在该chunk里最后一个sample的时间戳。
只有第一个mint
保存的是原始时间戳值,后续的maxt
以及后续chunk条目里的mint
和maxt
都是通过与前一个计算的差值进行保存。同样的差值编码也用于chunk条目的positon数值存储。
┌──────────────────────────────────────────────────────────────────────────┐
│ len <uvarint> │
├──────────────────────────────────────────────────────────────────────────┤
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ labels count <uvarint64> │ │
│ ├──────────────────────────────────────────────────────────────────────┤ │
│ │ ┌────────────────────────────────────────────┐ │ │
│ │ │ ref(l_i.name) <uvarint32> │ │ │
│ │ ├────────────────────────────────────────────┤ │ │
│ │ │ ref(l_i.value) <uvarint32> │ │ │
│ │ └────────────────────────────────────────────┘ │ │
│ │ ... │ │
│ ├──────────────────────────────────────────────────────────────────────┤ │
│ │ chunks count <uvarint64> │ │
│ ├──────────────────────────────────────────────────────────────────────┤ │
│ │ ┌────────────────────────────────────────────┐ │ │
│ │ │ c_0.mint <varint64> │ │ │
│ │ ├────────────────────────────────────────────┤ │ │
│ │ │ c_0.maxt - c_0.mint <uvarint64> │ │ │
│ │ ├────────────────────────────────────────────┤ │ │
│ │ │ ref(c_0.data) <uvarint64> │ │ │
│ │ └────────────────────────────────────────────┘ │ │
│ │ ┌────────────────────────────────────────────┐ │ │
│ │ │ c_i.mint - c_i-1.maxt <uvarint64> │ │ │
│ │ ├────────────────────────────────────────────┤ │ │
│ │ │ c_i.maxt - c_i.mint <uvarint64> │ │ │
│ │ ├────────────────────────────────────────────┤ │ │
│ │ │ ref(c_i.data) - ref(c_i-1.data) <varint64> │ │ │
│ │ └────────────────────────────────────────────┘ │ │
│ │ ... │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────────────────┤
│ CRC32 <4b> │
└──────────────────────────────────────────────────────────────────────────┘
注:每个block里会有多个chunk,每个series条目针对每个chunk都有且只有一个位置索引,由此也可以知道单个series在同一个chunk里的数据是连续存储的。
Label index 标签索引表
Label索引表保存了每个label name对应的values列表,可用于/api/v1/label/{name}/values
接口快速查询。
每个Label索引section同样以len
开始,以CRC32
结束。数据区首先是字段name
标识label name,然后是字段entries
标识label下面的value个数,他们都是4字节大小。
随后是所有的label value,每个value
都是4字节,和name一样,都是对Symbol Table的字符串的引用ID。value
列表按字典序升序排序。
┌───────────────┬────────────────┬────────────────┐
│ len <4b> │ #names <4b> │ #entries <4b> │
├───────────────┴────────────────┴────────────────┤
│ ┌─────────────────────────────────────────────┐ │
│ │ ref(value_0) <4b> │ │
│ ├─────────────────────────────────────────────┤ │
│ │ ... │ │
│ ├─────────────────────────────────────────────┤ │
│ │ ref(value_n) <4b> │ │
│ └─────────────────────────────────────────────┘ │
│ . . . │
├─────────────────────────────────────────────────┤
│ CRC32 <4b> │
└─────────────────────────────────────────────────┘
如下例所示,为一个简单的label name对应4个value:
┌────┬───┬───┬──────────────┬──────────────┬──────────────┬──────────────┬───────┐
│ 24 │ 1 │ 4 │ ref(value_0) | ref(value_1) | ref(value_2) | ref(value_3) | CRC32 |
└────┴───┴───┴──────────────┴──────────────┴──────────────┴──────────────┴───────┘
Label索引表每个name的长度不同,无法根据name快速定位到values在索引表的位置。该工作由Label offset table标签偏移表完成,该表包含了每个label name对应在Label索引表中的位置。
Postings 倒排索引表
Postings表保存了每个label pair对应的series列表,这些series里都包含改label pair。series列表按ID递增排序。
每个Postings section包含字段len
,entries
,series
列表和CRC32
。
┌────────────────────┬────────────────────┐
│ len <4b> │ #entries <4b> │
├────────────────────┴────────────────────┤
│ ┌─────────────────────────────────────┐ │
│ │ ref(series_1) <4b> │ │
│ ├─────────────────────────────────────┤ │
│ │ ... │ │
│ ├─────────────────────────────────────┤ │
│ │ ref(series_n) <4b> │ │
│ └─────────────────────────────────────┘ │
├─────────────────────────────────────────┤
│ CRC32 <4b> │
└─────────────────────────────────────────┘
同样,通过label pair快速索引到倒排索引表中位置的工作由Postings offset table倒排索引偏移表完成。
Label offset table 标签偏移表
该表保存了一系列label偏移条目,每个条目包含label name和指向Label索引表的位置信息。
┌─────────────────────┬──────────────────────┐
│ len <4b> │ #entries <4b> │
├─────────────────────┴──────────────────────┤
│ ┌────────────────────────────────────────┐ │
│ │ n = 1 <1b> │ │
│ ├──────────────────────┬─────────────────┤ │
│ │ len(name) <uvarint> │ name <bytes> │ │
│ ├──────────────────────┴─────────────────┤ │
│ │ offset <uvarint64> │ │
│ └────────────────────────────────────────┘ │
│ . . . │
├────────────────────────────────────────────┤
│ CRC32 <4b> │
└────────────────────────────────────────────┘
注:该表里的label name是直接保存了原始字符串,而不是对Symbol table的引用。
Postings offset table 倒排索引偏移表
该表保存了一系列label pair条目,每个条目包含label name/value pair和指向Postings表的位置信息。该表在index读取时会部分导入到内存中。
┌─────────────────────┬──────────────────────┐
│ len <4b> │ #entries <4b> │
├─────────────────────┴──────────────────────┤
│ ┌────────────────────────────────────────┐ │
│ │ n = 2 <1b> │ │
│ ├──────────────────────┬─────────────────┤ │
│ │ len(name) <uvarint> │ name <bytes> │ │
│ ├──────────────────────┼─────────────────┤ │
│ │ len(value) <uvarint> │ value <bytes> │ │
│ ├──────────────────────┴─────────────────┤ │
│ │ offset <uvarint64> │ │
│ └────────────────────────────────────────┘ │
│ . . . │
├────────────────────────────────────────────┤
│ CRC32 <4b> │
└────────────────────────────────────────────┘
注:该表里的label name/value也是直接存储了原始字符串,而不是对Symbol table的引用。
TOC 目录表
TOC(table of contents)目录表保存了指向各个表的位置信息,如果值为0,则表示对应的表不存在,对应的数据查询返回为空。
┌─────────────────────────────────────────┐
│ ref(symbols) <8b> │
├─────────────────────────────────────────┤
│ ref(series) <8b> │
├─────────────────────────────────────────┤
│ ref(label indices start) <8b> │
├─────────────────────────────────────────┤
│ ref(label offset table) <8b> │
├─────────────────────────────────────────┤
│ ref(postings start) <8b> │
├─────────────────────────────────────────┤
│ ref(postings offset table) <8b> │
├─────────────────────────────────────────┤
│ CRC32 <4b> │
└─────────────────────────────────────────┘
参考
https://github.com/prometheus/prometheus/blob/master/tsdb/docs/format/index.md