其他分享
首页 > 其他分享> > Prometheus TSDB (Part 2): WAL and Checkpoint

Prometheus TSDB (Part 2): WAL and Checkpoint

作者:互联网

Prometheus TSDB (Part 2): WAL and Checkpoint

本文译自Ganesh Vernekar 的 prometheus-tsdb-wal-and-checkpoint

文章目录

Introduction

在TSDB系列博客的第一部分,我提到了为了持久化的考虑,我们会将抓取的样本数据先写入WAL,并且当WAL被清理时,会创建一个checkpoint。在本篇博客,我们会简要的讨论WAL的基本概念,然后深入谈下WAL和checkpoint在Prometheus的TSDB中是如何设计的。

由于这是我关于Prometheus TSDB系列博客的一部分,所以建议你在阅读时先阅读 第一部分

WAL Basics

WAL是一个顺序存储的日志,包含database中发生的事件。当数据库中发生 writing/modifying/deleting 数据的事件时,会先写入到WAL中,然后才会在database中执行必要的操作。

当机器或程序突然崩溃时,我们可以通过顺序的重播WAL中存储的事件来恢复数据。这对于内存型数据库而言非常有效,因为如果没有WAL,那当数据库崩溃时,所有内存中的数据都会丢失。

在关系型数据库中,这是一个广泛使用的用于实现持久化的特性。类似的,Prometheus也是通过这种机制来为Head Block提供持久化能力。同时,Prometheus在重启时能优雅的恢复内存中的数据状态,这也是通过WAL来实现的。

在Prometheus的体系中,WAL只是用来记录事件,或在启动时恢复内存中数据。它并没有用于其他读写相关的用途。

Writing to WAL in Prometheus TSDB

Types of records

TSDB的写请求中,包含了时序相关的所有Label values以及相关的Sample,分别对应两种需记录的数据类型,Series和Samples。

对Seires记录而言,它包含写请求中所有的时序的Label values,在创建Series时,会生成唯一的引用,该引用用于查找(关联)该Seires。相应的,Samples记录中会包含Series的唯一引用(用于关联Seires记录,指明该Sample的归属)以及该Seires的所有样本数据。

最后一种Record类型是Tombstones,用于删除请求。它包含待删除的Seires的唯一引用以及时间跨度。

这些Record的格式可以从 这里 找到,此处就不再展开描述。

Writing them

如果写请求中包含Sample数据,那每次写请求时都会往Samples记录中写入数据,相反的,Series记录是公用的,仅在第一接收到这个新的Seires时才会写入。

如果一个写请求包含一个新的Seires,那Series的写入一定会先于Sample,因为显而易见的是,如果Sample的写入在Seires之前,则Sample中的对Seires的唯一引用将会找不到任何Seires的记录。

Seires是在写入Head之后再写入WAL的,因为需要先获取到唯一的引用。而Samples则是在写入Head之前写入WAL的。

通过将所有不同的Time Seires都分组到相同的Record中,每个写请求只会写入一个Seires和Smaples Record。如果写请求中的所有Sample对应的Seires都已经存在于Head中,则只有Samples会被写入WAL。

当我们接收到删除请求时,我们并不会直接从内存中删除。我们会通过存储Tombstones(包含待删除的时序以及时间跨度)来表明这个删除操作。在实际执行这个删除请求前,会先将Timebstones写入WAL。

How it looks on disk

WAL是通过顺序的数字命名的文件来存储的,每个文件默认为128MiB,这些文件被称为“segment”。

data
└── wal
    ├── 000000
    ├── 000001
    └── 000002

文件的大小是有限制的,以使得对旧文件的垃圾回收更简单。显而易见,文件序号是不断增加的。

WAL truncation and Checkpoingting

我们需要定时的删除老的WAL的segment,否则磁盘早晚会满,并且TSDB的启动周期也会变长,因为它需要重播非常多的事件到内存中。一般来讲,任何你不再需要的数据,你都会想要清空它。

WAL tuncation

当Head Block被清空时,WAL也会同时被清空(第一部分有简单提及)。对WAL中的文件的删除并不是随机发生的,它总是删除前N个文件,并且不会在递增的文件序列中产生间隙。

因为写请求是随机发生的,所以想在segment中高效的找到时间跨度中的所有样本并不容易,所以我们默认删除前2/3rd的segment。

data
└── wal
    ├── 000000
    ├── 000001
    ├── 000002
    ├── 000003
    ├── 000004
    └── 000005

在如上的例子中,000000 000001 000002 000003会被删除。

补充一点:因为Series只会被写入一次,所以如果我们盲目的删除WAL segment,会导致我们丢失这些Seires,从而导致启动时无法恢复数据,同时,我们也可能会丢失前2/3rd segment中所包含的并未在Head Block中清除的Sample数据。这也是为什么我们需要checkpointing的原因。

Checkpointing

在清空WAL之前,我们会为待删除的segment创建checkpoint,你可以想象checkpoint是一个过滤后的WAL。考虑当Head Block的清空会删除掉时间T之前的数据,如上例所示,checkpointing会在000000 000001 000002 000003中发生:

  1. 删除Head中不再存在的Series;
  2. 删除所有在时间T之前的Sample数据;
  3. 删除所有在时间T之前的Tombstones;
  4. 保留所有需要的Seires、Samples和Tombstones(顺序同之前保持一致);

当从Record中移除不需要的项时,删除操作也可以看做是一个重写操作(因为一个Record中可能包含多个Seires、Sample和Tombstone)。

通过这种方式,你不会丢失在Head中仍然存在的Seires、Sample和Tombstone。checkpoint的名字叫checkpoint.XX代表创建这个checkpoint的segment的数字文件名(此处为000003,下一章节中你会知道我们为什么这么做)。

在WAL清理并创建checkpoint之后,磁盘上的文件会看起来类似如下示例:

data
└── wal
    ├── checkpoint.000003
    |   ├── 000000
    |   └── 000001
    ├── 000004
    └── 000005

如果在这之前有其它老的checkpoints,它们也会在此时被删除。

译者注:checkpoint.00003中仍包含000000 000001两个segment,可能是其中存在需要保留的Seires

Replaying the WAL

我们一开始会从最后一个checkpoint开始遍历(编号最大的checkpoint即是最后一个)。对于checkpoint.XX就是用于告诉我们需要从哪一个segment开始进行重播,这个segment就是X+1。所以在上面的例子中,在对checkpoint.000003进行重播后,我们会开始对segment 000004进行重播。

你可能会考虑为什么我们在checkpoint中需要跟踪segment的段号,因为最终我们都会删除掉在checkpoint之前的segment。原因是,创建checkpoint和删除segment并非原子性的,在这两个动作之间任何事情的发生都可能阻止segment的删除,所以我们有时不得不重播额外的2/3rd的segment,即便它们已经在Head中被删除,这使得重播的过程变得更缓慢。

对于单个Record,它们会发生如下动作:

  1. Series:以相同的引用(ID)创建该Seires,对于相同的Series可能存在多条记录,Prometheus会处理这种情况;
  2. Samples:Sample数据会被添加到Head中,Sample记录中的引用关系会指明该Sample被添加到哪个Seires,如果该引用指向空,则该Sample会被忽略;
  3. Tomstones:Tomstones会被添加的Head中,其保存的引用会指明其所属的Seires;

Low level details of writing to and reading from WAL

当写请求的量较大时,你希望能避免随机写入造成的 写入放大 ,同时,当你进行读操作时,你希望确保它没有损坏(突然关机或磁盘故障时很容易重现)。

Promethues包含WAL的一般的实现,其记录的是字节流,调用方必需负责对Record进行编码。为解决上述的问题,WAL包做了如下工作:

  1. 数据是一次一页的写入,每页的长度是32KiB,如果Record的大小超过32KiB,它会被拆解为更小的片段,每个片段都会有一个WAL头,以便知道该片段是头部、中部还是尾部;
  2. 通过Record尾部的checksum来校验其没有被损坏;

WAL包负责无缝的连接Record的片段,并且在迭代时检查checksum,以便后续进行重播。

WAL中的Record并没有被严格压缩(或者说根本没有压缩),所以WAL包提供了一个选项,可以通过Snappy来压缩记录(现在默认是启用的)。这个信息被保存在WAL头,所以你打算启用或禁用压缩,这些压缩和未压缩的记录可以保存在一起。

Code reference

tsdb/wal/wal.go中有将Record进行字节化处理并提供低级磁盘交互的实现。该文件有写入字节流和对Record进行迭代的实现。

tsdb/record/record.go包含各种Record的编解码的逻辑。

tsdb/wal/checkpoint.go中包含checkpoint的逻辑。

tsdb/head中包含:

  1. 创建Record,对Record进行编码并调用WAL的写入;
  2. 调用checkpoint并清空WAL;
  3. 重播WAL,对其进行解码并存储为内存状态;

标签:checkpoint,WAL,Seires,写入,Checkpoint,Record,Prometheus,Part,segment
来源: https://blog.csdn.net/m0_51450908/article/details/121982967