REAMDE

PTSDB

PTSDB的核心包括:倒排索引+窗口存储Block。数据的写入按照两个小时为一个时间窗口,将两小时内产生的数据存储在一个Head Block中,每一个块中包含该时间窗口内的所有样本数据(chunks),元数据文件(meta.json)以及索引文件(index)

最新写入数据保存在内存block中,2小时后写入磁盘。后台线程把2小时的数据最终合并成更大的数据块,一般的数据库在固定一个内存大小后,系统的写入和读取性能会受限于这个配置的内存大小。而PTSDB的内存大小是由最小时间周期,采集周期以及时间线数量来决定的。为防止内存数据丢失,实现wal机制。删除记录在独立的tombstone文件中。

存储引擎

PTSDB的核心数据结构是HeadAppenderAppender commitwal日志编码落盘,同时写入head block中。

Head Appender

PTSDB本地存储使用自定义的文件结构。主要包含:WAL,元数据文件,索引,chunks,tombstones。

本地文件结构

乱序处理

PTSDB对于乱序的处理采用了最小时间窗口的方式,指定合法的最小时间戳,小于这一时间戳的数据会丢弃不再处理。 合法最小时间戳取决于当前head block里面最早的时间戳和可存储的chunk范围。 这种对于数据行为的限定极大的简化了设计的灵活性,对于compaction的高效处理以及数据完整性提供了基础。

内存的管理

使用mmap读取压缩合并后的大文件(不占用太多句柄, 建立进程虚拟地址和文件偏移的映射关系,只有在查询读取对应的位置时才将数据真正读到物理内存。 绕过文件系统page cache,减少了一次数据拷贝。 查询结束后,对应内存由Linux系统根据内存压力情况自动进行回收,在回收之前可用于下一次查询命中。 因此使用mmap自动管理查询所需的的内存缓存,具有管理简单,处理高效的优势。

Compaction

Compaction主要操作包括合并block、删除过期数据、重构chunk数据。

合并多个block成为更大的block,可以有效减少block个,当查询覆盖的时间范围较长时,避免需要合并很多block的查询结果。 为提高删除效率,删除时序数据时,会记录删除的位置,只有block所有数据都需要删除时,才将block整个目录删除。 block合并的大小也需要进行限制,避免保留了过多已删除空间(额外的空间占用)。 比较好的方法是根据数据保留时长,按百分比(如10%)计算block的最大时长,block的最小和最大时长超过2/3blok范围时,执行compaction

快照

PTSDB提供了快照备份数据的功能,用户通过admin/snapshot协议可以生成快照,快照数据存储于data/snapshots/-目录。

存储格式

Write Ahead Log

WAL3种编码格式:时间线,数据点,以及删除点。总体策略是基于文件大小滚动,并且根据最小内存时间执行清除。

  • 当日志写入时,以segment为单位存储,每个segment默认128M,记录数大小达到32KB页时刷新一次。当剩余空间小于新的记录数大小时,创建新的Segment

  • compationWAL基于时间执行清除策略,小于内存中block的最小时间的wal日志会被删除。

  • 重启时,首先打开最新的Segment,从日志中恢复加载数据到内存。

WAL 文件结构

元数据文件

meta.json文件记录了Chunks的具体信息,比如新的compactin chunk来自哪几个小的chunk。这个chunk的统计信息,比如:最小最大时间范围,时间线,数据点个数等等。

compaction线程根据统计信息判断该blocks是否可以做compact(maxTime-minTime)占整体压缩时间范围的50%,删除的时间线数量占总体数量的5%

元数据文件

索引

索引一部分先写入Head Block中,随着compaction的触发落盘。 索引采用的是倒排的方式,posting list里面的id是局部自增的,作为reference id表示时间线。索引compact时分为6步完成索引的落盘:Symbols->Series->LabelIndex->Posting->OffsetTable->TOC

  • Symbols存储的是tagk, tagv按照字母序递增的字符串表。比如name,go_gc_duration_seconds, instance, localhost:9090等等。字符串按照utf8统一编码。
  • Series存储了两部分信息,一部分是标签键值对的符号表引用;另外一部分是时间线到数据文件的索引,按照时间窗口切割存储数据块记录的具体位置信息,因此在查询时可以快速跳过大量非查询窗口的记录数据, 为了节省空间,时间戳范围和数据块的位置信息的存储采用差值编码。
  • LabelIndex存储标签键以及每一个标签键对应的所有标签值,当然具体存储的数据也是符号表里面的引用值。
  • Posting存储倒排的每个label对所对应的posting refid
  • OffsetTable加速查找做的一层映射,将这部分数据加载到内存。OffsetTable主要关联了LabelIndexPosting数据块。TOC是各个数据块部分的位置偏移量,如果没有数据就可以跳过查找。

索引文件格式

Chunks

数据点存放在chunks目录下,每个data默认512M,数据的编码方式支持XORchunk按照refid来索引,refidsegmentid和文件内部偏移量两个部分组成。

Chunks 结构

Tombstones

记录删除通过mark的方式,数据的物理清除发生在compactionreload的时候。以时间窗口为单位存储被删除记录的信息。

Tombstones 结构

最佳实践

在一般情况下,Prometheus中存储的每一个样本大概占用1-2字节大小。如果需要对Prometheus Server的本地磁盘空间做容量规划时,可以通过以下公式计算: neededdisk_space = retention_time_seconds * ingestedsamples_per_second * bytes_per_sample 保留时间(retention_time_seconds)和样本大小(bytes_per_sample)不变的情况下,如果想减少本地磁盘的容量需求, 只能通过减少每秒获取样本数(ingested_samples_per_second)的方式。

因此有两种手段,一是减少时间序列的数量,二是增加采集样本的时间间隔。 考虑到Prometheus会对时间序列进行压缩,因此减少时间序列的数量效果更明显。 PTSDB的限制在于集群和复制。因此当一个node宕机时,会导致一定窗口的数据丢失。当然,如果业务要求的数据可靠性不是特别苛刻,本地盘也可以存储几年的持久化数据。当PTSDB Corruption时,可以通过移除磁盘目录或者某个时间窗口的目录恢复。 PTSDB的高可用,集群和历史数据的保存可以借助于外部解决方案,不在本文讨论范围。

历史方案的局限性,PTSDB在早期采用的是单条时间线一个文件的存储方式。这中方案有非常多的弊端,比如: Snapshot的刷盘压力:定期清理文件的负担;低基数和长周期查询查询,需要打开大量文件;时间线膨胀可能导致inode耗尽。

下一页