Prometheus TSDB

TSDB 概述:
在这里插入图片描述
Head: 数据库的内存部分
Block: 磁盘上持久块,是不变的
WAL: 预写日志系统
M-map: 磁盘及内存映射

粉红色框是传入的样品,样品先进入Head中存留一会,然后到磁盘、内存映射中(蓝色框)。然后当内存映射块时间长到某点,就会作为持久块存在硬盘上,进一步一个个合并。
超出保留时间就会被删除。


Head的生命周期

(这里讨论的都是基于一个time series,同样适用于其他time series)
在这里插入图片描述
当样品存入时,chunk变活跃,红色块是我们唯一可以主动编写数据的单位。

将样品存入时,我们还会将其记录在WAL中,在机器崩溃时可以从中恢复数据。
在这里插入图片描述
默认chunkRange为120,如果2小时内,chunkRange都是120的满状态情况,就会新建一个chunk,在这篇博客里,我们默认抓取间隔为15s。所以从空chunk到满chunk需要30min.

黄色block是刚刚填充满的满chunk,红色块则是创建的新块。
在这里插入图片描述
在prometheus v2.19.0中,我们不会把所有块存在内存中。当新chunk建立,full chunk被放入磁盘,如果只存内存索引时就将full chunk存入磁盘的memory-mapped中。然后在我们需要时,用索引将chunk动态加载到内存中。
在这里插入图片描述
随着新样品不断进入,新的chunk被分割。
在这里插入图片描述
被放入磁盘及内存映射中
在这里插入图片描述
过了一段时间head如图示,我们认为红色chunk几乎满了,那么我们在head里存在3h的数据。(6个chunk,每个30min满)chunkRange3/2
在这里插入图片描述
当数据存储至chunkRange
3/2时,(2h时)就会压缩成一个恒久chunk。此时WAL被截断,创建一个checkPoint。
以上周而复始及为head的功能。

如果TSDB必须重新启动(优雅退出、突然),他将使用内存映射的chunk和WAL重放数据和事件,重新构建内存索引等。


WAL基础知识

当编写、修改、删除数据库的数据之前,事件首先被记录在WAL中,然后才进行相关操作。

在Prometheus TSDB中写入WAL

records类型

prometheus中存在三种wal类型:

  • Series 当一个新的series到达时记录一次,先将series写入Head,再写wal
  • Samples 先写wal,再写入Head,
  • Tombstones 用于记录删除特定series,soft delete

WAL截断

在上一篇中介绍过,当Head Block阶段时也同时截断wal,在实际实现时,由于写请求可以是随机的,因此要确定WAL段中样本的时间范围而不遍历所有记录比较困难,因此简化为直接删除2/3。

这里有个问题,series只存一次,在wal也一样,直接截断wal可能会丢失series,甚至依然是还在Head中的series,这里引入check point来处理改问题。

CheckPointing

假设截断的时间点是T,要实现保留series,需要遍历所有要删除的wal文件:

  • 删除所有不在Head中的series
  • 删除所有在时间T之前的samples
  • 删除所有在时间T之前的tombstones
  • 保留剩余的series、samples、tombstones到checkpoint.X文件
data
└── wal
    ├── checkpoint.000002
    |   ├── 000000
    |   └── 000001
    ├── 000003

从WAL恢复时,直接从最新的一个checkpoint.X文件开始,扫描X+1对应的WAL文件名;
WAL底层写入磁盘使用32KB page。


当一个chunk满了(120个sample或者2h)以后,会使用m-map(2)将其flush到disk中,其数据不在占用内存,索引依然保留在内存中。

写 chunks
从第1部分重新开始,当一个chunk已满时,我们剪切了一个新chunk,而较旧的chunk变得不可变,只能从中读取(下面的黄色块)。
在这里插入图片描述
而不是将其存储在内存中,我们将其刷新到磁盘并存储引用以供以后访问。
在这里插入图片描述
此刷新的chunk是磁盘中的内存映射chunk。不变性是最重要的因素,否则对于每个sample而言,重写压缩chunk的效率都太低。

File

这些chunks保留在其自己的目录中,称为chunks_head,其文件序列类似于WAL(除了以1开头)。例如:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cDhNZkQZ-1636355505443)(/download/attachments/1195970022/image-1636352695839.png?version=1&modificationDate=1636352693244&api=v2)]
文件由8B的文件头和chunks数据组成:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ac2Cikgz-1636355505445)(/download/attachments/1195970022/image-1636352672901.png?version=1&modificationDate=1636352670327&api=v2)]

Chunks

┌─────────────────────┬───────────────────────┬───────────────────────┬───────────────────┬───────────────┬──────────────┬────────────────┐
| series ref <8 byte> | mint <8 byte, uint64> | maxt <8 byte, uint64> | encoding <1 byte> | len <uvarint> | data <bytes> │ CRC32 <4 byte> │
└─────────────────────┴───────────────────────┴───────────────────────┴───────────────────┴───────────────┴──────────────┴────────────────┘

series ref :是内存中某个的series索引的id,这里是为了方便后续宕机恢复时重建索引数据
mint,maxt:chunk时间戳
encoding :压缩编码方式
len :数据长度
data :压缩编码的数据

如何访问

内存中依然有每个chunks的地址(也叫ref,64位,前32位是文件号,后32位是文件内字节偏移)和mint、maxt。
在代码中看,每个chunks文件都是一个byte slice,当访问该byte slice时,mmap自动映射到磁盘中的文件。

series ref id ->[(ref,mint,maxt),(addr2,mint2,maxt2)…]-> mmap chunks

重启实例时重新生成HeadBlock

因为所有满了的chunk都被mmap到了磁盘目录chunks_head下面,所以在重建HeadBlock时,直接可以使用这些数据重建满chunks,然后再根据wal重建还没有满的chunks。
重建在磁盘中的满的mmap chunks只需要重建index(如上节所示map)即可;
重建wal中的未满的chunks就需要重建index和数据。

Mmap优点

  • 节省约15%~50%内存使用
  • 节省启动时间15%~30%,主要是不需要重建所有wal了

GC

tsdb定期截断HeadBlock为一个2h(default)的block,在截断时,也是内存gc的时刻,对于截断时间T之前的index都可以gc。


Block

磁盘上的Block可以认为是一个小型的db,自带了index和chunks data,其中chunks可以是多个文件。
每个Block用一个Universally Unique Lexicographically Sortable Identifier (ULID)唯一识别。
每个Block是不可变的数据文件,只能标记删除,不同block之间没有引用关系。
默认一个新生成的block是2h的数据,随着时间推移,多个2h block可以compate(merge)为一个更大的block。

block组成

meta.json (file): block元数据,json文件。
chunks (directory): chunks数据文件
index (file): 索引文件
tombstones (file): 删除标记文件

在这里插入图片描述

meta.json

meta记录当前block的ulid,chunks数量,samples数量,时间跨度,comapction等级等信息。

{
    "ulid": "01EM6Q6A1YPX4G9TEB20J22B2R",
    "minTime": 1602237600000,
    "maxTime": 1602244800000,
    "stats": {
        "numSamples": 553673232,
        "numSeries": 1346066,
        "numChunks": 4440437
    },
    "compaction": {
        "level": 1,
        "sources": [
            "01EM65SHSX4VARXBBHBF0M0FDS",
            "01EM6GAJSYWSQQRDY782EA5ZPN"
        ]
    },
    "version": 1
}

chunks

chunks是一个目录,内部存放着多个有序排列的chunks文件,每个文件默认512MB,保存着所有chunks data。
此时的chunk格式发生了变化,具体如下:

┌───────────────┬───────────────────┬──────────────┬────────────────┐
│ len <uvarint> │ encoding <1 byte> │ data <bytes> │ CRC32 <4 byte> │
└───────────────┴───────────────────┴──────────────┴────────────────┘

相比HeadBlock mmap chunk格式,缺少了series ref、mint 和 maxt,这三个信息现在存入了index文件

index

index是一个倒排索引,index的实现比较常规,主要由以下几部分:

一个符号表,每个series中存一个特定符号的addr能节省内存
每个series有一个固定的series ID可以寻址,且每个series内包含在该block内对应的所有chunks地址信息和时间范围信息
基于单个字符的倒排索引,比如有两个series{a=“b1”, x=“y1”} 和 {a=“b2”, x=“y2”},字符a和x将各自都有一个索引指向这两个series,index(a)->[ref(series id1), ref(series id2)]
因为每个字符的索引slice可能很大,所以在实现上,做了一个二级index,字符a -> index(a) -> [ref(series id1), ref(series id2)]

在这里插入图片描述

Postings Offset Table :第一级index,存储每个label对(name,value)对应的Postings N
Postings N: 第二级index,存放一个label对应的所有series id
Series:各个Series信息,包含series ID,按id有序排列,每个series内包含在该block内对应的所有chunks地址信息和时间范围信息可以查找数据
Label Index 和 Label Offset Tabel 废弃了


query分为三种:

  • LabelNames() : 查询所有label names(直接循环遍历Posting Offset Table,获取所有的label name即可。)
  • LabelValues(name) : 查询指定label name对应的所有values(直接循环遍历Posting Offset Table,获取所有的label name与name一致的label value即可。)
  • Select([]matcher) : 返回符合matcher的series
Select([]matcher)

matcher用于选择出符合指定label name和label value的series,有以下4种:

  • labelName="" ,相等匹配
  • labelName!="" ,不相等匹配
  • labelName=~"",正则正匹配
  • labelName!~"" ,正则负匹配

匹配步骤:

  • 获得单个matcher匹配的所有series
  • 取交集

Postings Offset Table存放了所有label name和label value的组合,并且指向Postings N,Postings N包含了所有的series ID。
相等匹配:遍历Postings Offset Table,找到name和value都相等的项就可以获得对应的Postings,然后获得series
正则正匹配:需要遍历所有Postings Offset Table,找到所有符合的name和value项,然后也可以获得series
同理,负向匹配一样,但负匹配可能会匹配到大量数据,所以,不允许单个负匹配查询,必须结合一个相等或者正则正匹配才能使用负匹配。

然后,获取到所有单个matcher的Postings后,进行交集,获取最终的Postings,然后遍历Postings对应的series id,根据series id中的chunk addr和mint、 maxt取得数据返回。

最后,如果查询时间跨越多个block,那么应该单独查每个block,然后将结果merge。

Querying Head block

Head block在内存中建立了map[labelName]map[labelValue]postingsList结构的索引,可以直接查上述过程。

整理 (https://ganeshvernekar.com/blog/prometheus-tsdb-the-head-block/) 学习内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值