Prometheus是著名开源监控项目,其监控任务调度给具体的服务器,该服务器到目标上抓取监控数据,而后保存在本地的TSDB中。自定义强大的PromQL语言查询实时和历史时序数据,支持丰富的查询组合。
Prometheus 1.0版本的TSDB(V2存储引擎)基于LevelDB,而且使用了和Facebook Gorilla同样的压缩算法,可以将16个字节的数据点压缩到平均1.37个字节。
Prometheus 2.0版本引入了全新的V3存储引擎,提供了更高的写入和查询性能。本文主要分析该存储引擎设计思路。nginx
Prometheus将Timeseries数据按2小时一个block进行存储。每一个block由一个目录组成,该目录里包含:一个或者多个chunk文件(保存timeseries数据)、一个metadata文件、一个index文件(经过metric name和labels查找timeseries数据在chunk文件的位置)。最新写入的数据保存在内存block中,达到2小时后写入磁盘。为了防止程序崩溃致使数据丢失,实现了WAL(write-ahead-log)机制,将timeseries原始数据追加写入log中进行持久化。删除timeseries时,删除条目会记录在独立的tombstone文件中,而不是当即从chunk文件删除。
这些2小时的block会在后台压缩成更大的block,数据压缩合并成更高level的block文件后删除低level的block文件。这个和leveldb、rocksdb等LSM树的思路一致。
这些设计和Gorilla的设计高度类似,因此Prometheus几乎就是等于一个缓存TSDB。它本地存储的特色决定了它不能用于long-term数据存储,只能用于短时间窗口的timeseries数据保存和查询,而且不具备高可用性(宕机会致使历史数据没法读取)。
Prometheus本地存储的局限性,因此它提供了API接口用于和long-term存储集成,将数据保存到远程TSDB上。该API接口使用自定义的protocol buffer over HTTP而且并不稳定,后续考虑切换为gRPC。算法
内存中的block数据未刷盘时,block目录下面主要保存wal文件。数据库
./data/01BKGV7JBM69T2G1BGBGM6KB12 ./data/01BKGV7JBM69T2G1BGBGM6KB12/meta.json ./data/01BKGV7JBM69T2G1BGBGM6KB12/wal/000002 ./data/01BKGV7JBM69T2G1BGBGM6KB12/wal/000001
持久化的block目录下wal文件被删除,timeseries数据保存在chunk文件里。index用于索引timeseries在wal文件里的位置。json
./data/01BKGV7JC0RY8A6MACW02A2PJD ./data/01BKGV7JC0RY8A6MACW02A2PJD/meta.json ./data/01BKGV7JC0RY8A6MACW02A2PJD/index ./data/01BKGV7JC0RY8A6MACW02A2PJD/chunks ./data/01BKGV7JC0RY8A6MACW02A2PJD/chunks/000001 ./data/01BKGV7JC0RY8A6MACW02A2PJD/tombstones
使用mmap读取压缩合并后的大文件(不占用太多句柄),创建进程虚拟地址和文件偏移的映射关系,只有在查询读取对应的位置时才将数据真正读到物理内存。绕过文件系统page cache,减小了一次数据拷贝。查询结束后,对应内存由Linux系统根据内存压力状况自动进行回收,在回收以前可用于下一次查询命中。所以使用mmap自动管理查询所需的的内存缓存,具备管理简单,处理高效的优点。
从这里也能够看出,它并非彻底基于内存的TSDB,和Gorilla的区别在于查询历史数据须要读取磁盘文件。缓存
Compaction主要操做包括合并block、删除过时数据、重构chunk数据。其中合并多个block成为更大的block,能够有效减小block个数,当查询覆盖的时间范围较长时,避免须要合并不少block的查询结果。
为提升删除效率,删除时序数据时,会记录删除的位置,只有block全部数据都须要删除时,才将block整个目录删除。所以block合并的大小也须要进行限制,避免保留了过多已删除空间(额外的空间占用)。比较好的方法是根据数据保留时长,按百分比(如10%)计算block的最大时长。bash
Inverted Index(倒排索引)基于其内容的子集提供数据项的快速查找。简而言之,我能够查看全部标签为app=“nginx”的数据,而没必要遍历每个timeseries,并检查是否包含该标签。
为此,每一个时间序列key被分配一个惟一的ID,经过它能够在恒定的时间内检索,在这种状况下,ID就是正向索引。
举个栗子:如ID为9,10,29的series包含label app="nginx",则lable "nginx"的倒排索引为[9,10,29]用于快速查询包含该label的series。服务器
在文章Writing a Time Series Database from Scratch里,做者给出了benchmark测试结果为Macbook Pro上写入达到2000万每秒。这个数据比Gorilla论文中的目标7亿次写入每分钟(1000千多万每秒)提供了更高的单机性能。app
Writing a Time Series Database from Scratch
Prometheus官方介绍
时间序列数据的存储和计算 - 开源时序数据库解析(四)性能