OpenTSDB:
Whisper (Graphite):
InfluxDB:
作者认为以上方案的通用问题:从磁盘查不够快。
时间戳压缩:
数值(double)压缩:
内存中的数据结构
磁盘上的数据结构
为了保证Gorilla 能从 crash 中恢复
{__name__="requests_total", path="/status", method="GET", instance=”10.0.0.1:80”}{__name__="requests_total", path="/status", method="POST", instance=”10.0.0.3:80”}{__name__="requests_total", path="/", method="GET", instance=”10.0.0.2:80”} |
---|
写一般是对比如1000个series 同时进行采集,所以他是纵向的。磁盘随机写效率低、SSD 写放大问题 → 顺序写 和 批量写 是必然选择 (sequential and batched writes are the ideal write pattern for spinning disks and SSDs alike. A simple rule to stick)
读的问题更加复杂:可能是读一段时间一个 series 的数据,也可能是 读一个时间点 10000 个 series 的数据,所以读 是在 two-dimensional plane, 读既不是完全横向也不是纵向的,而是一个两者组合的矩形.
为什么一个 series 一个文件不行:在微服务环境中 series 会非常多,和存在多时间可能很短 (作者命名为 series churn )
series churn 带来另一个问题:由于 series 数量总是在不停膨胀的,所以从千万个 series 查询出满足条件的 series 的查询效率需要优化
series churn increases the usage of memory, CPU, and disk IO every time we scale an application or do a rolling update. 如果 单个文件存储一个 series 这个消耗非常高。
总结
总结一下,主要要解决的几个问题:
./data ├── 01DYXN0KX8B50SX1CAPCXXWJFN │ ├── chunks │ │ ├── 000001 │ │ ├── 000002 │ │ └── 000003 │ ├── index │ ├── tombstones │ └── meta.json ├── 01DYXVWB57MYNTD4XHCSC0A2JX │ ├── chunks │ │ ├── 000001 │ │ └── 000002 │ ├── index │ ├── tombstones │ └── meta.json └── wal ├── 000001 ├── 001227 └── 001228 |
---|
# Compaction t0 t1 t2 t3 t4 now ┌────────────┐ ┌──────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ 1 │ │ 2 │ │ 3 │ │ 4 │ │ 5 mutable │ before └────────────┘ └──────────┘ └───────────┘ └───────────┘ └───────────┘ ┌─────────────────────────────────────────┐ ┌───────────┐ ┌───────────┐ │ 1 compacted │ │ 4 │ │ 5 mutable │ after (option A) └─────────────────────────────────────────┘ └───────────┘ └───────────┘ ┌──────────────────────────┐ ┌──────────────────────────┐ ┌───────────┐ │ 1 compacted │ │ 3 compacted │ │ 5 mutable │ after (option B) └──────────────────────────┘ └──────────────────────────┘ └───────────┘ # Retention ┌────────────┐ ┌────┼─────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ 1 │ │ 2 | │ │ 3 │ │ 4 │ │ 5 │ . . . └────────────┘ └────┼─────┘ └───────────┘ └───────────┘ └───────────┘ | | retention boundary |
---|
使用倒序索引,series ID 是 forward index. 对每种 label name/value 对都建立倒序索引,比如 app=nginx → ID 1,4,5,ID保持有序,这样多个 label 组合的时候查询效率也是 O(n)
__name__="requests_total" -> [ 9999, 1000, 1001, 2000000, 2000001, 2000002, 2000003 ] app="foo" -> [ 1, 3, 10, 11, 12, 100, 311, 320, 1000, 1001, 10002 ] intersection => [ 1000, 1001 ] |
---|
总结
基于 2.10
meta.json 例子
在下面这个例子里面:
【索引这块看上去有不小的优化空间,观察一个 100 M 左右的 block (kubernetes 环境的 prometheus),index 文件24 M左右,占比很高】
【另:按照时间分片的文件结构也带来不少问题,一个问题是构建 cluster 变得很困难(同时因为prometheus 是主动 scrape ),如何在多节点上进行分片是一个问题,参考 Gorilla 的做一个 shard manager (按照 series id/name 进行分片 )是一个办法,前端抓取之后,后端分片存储到多个prometheus instance,查询的时候再做 merge】
label 中用的strings,集中存储能减少 index 大小series 数据,按照 label set 排序,同时 id = offset/16一个 series 数据的索引信息,包括 labels, chunks的开始结束时间,以及真实chuck 索引一个 label对 的所有倒序索引,即这个 label 对匹配的series idsPosting 的索引,对一个 label 对,其对应的posting 位置,这里比较奇怪的是 name 和 value 直接存储并没有索引到 Symbol table整体布局的索引
chunks/ 文件夹下面的内容,最大单文件大小512 M
┌──────────────────────────────┐
│ magic(0x85BD40DD) <4 byte> │
├──────────────────────────────┤
│ version(1) <1 byte> │
├──────────────────────────────┤
│ padding(0) <3 byte> │
├──────────────────────────────┤
│ ┌──────────────────────────┐ │
│ │ Chunk 1 │ │
│ ├──────────────────────────┤ │
│ │ ... │ │
│ ├──────────────────────────┤ │
│ │ Chunk N │ │
│ └──────────────────────────┘ │
└──────────────────────────────┘
chuck
┌───────────────┬───────────────────┬──────────────┬────────────────┐
│ len <uvarint> │ encoding <1 byte> │ data <bytes> │ CRC32 <4 byte> │
└───────────────┴───────────────────┴──────────────┴────────────────┘
被删除的 数据
┌────────────────────────────┬─────────────────────┐
│ magic(0x0130BA30) <4b> │ version(1) <1 byte> │
├────────────────────────────┴─────────────────────┤
│ ┌──────────────────────────────────────────────┐ │
│ │ Tombstone 1 │ │
│ ├──────────────────────────────────────────────┤ │
│ │ ... │ │
│ ├──────────────────────────────────────────────┤ │
│ │ Tombstone N │ │
│ ├──────────────────────────────────────────────┤ │
│ │ CRC<4b> │ │
│ └──────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────┘
chuck
┌────────────────┬─────────────────┬────────────────┐
│ref <uvarint64> │ mint <varint64> │ maxt <varint64>│
└────────────────┴─────────────────┴────────────────┘
默认单文件大小限制为 128 M
Series records encode the labels that identifies a series and its unique ID.
┌────────────────────────────────────────────┐
│ type = 1 <1b> │
├────────────────────────────────────────────┤
│ ┌─────────┬──────────────────────────────┐ │
│ │ id <8b> │ n = len(labels) <uvarint> │ │
│ ├─────────┴────────────┬─────────────────┤ │
│ │ len(str_1) <uvarint> │ str_1 <bytes> │ │
│ ├──────────────────────┴─────────────────┤ │
│ │ ... │ │
│ ├───────────────────────┬────────────────┤ │
│ │ len(str_2n) <uvarint> │ str_2n <bytes> │ │
│ └───────────────────────┴────────────────┘ │
│ . . . │
└────────────────────────────────────────────┘
Sample records encode samples as a list of triples (series_id, timestamp, value).
┌──────────────────────────────────────────────────────────────────┐
│ type = 2 <1b> │
├──────────────────────────────────────────────────────────────────┤
│ ┌────────────────────┬───────────────────────────┐ │
│ │ id <8b> │ timestamp <8b> │ │
│ └────────────────────┴───────────────────────────┘ │
│ ┌────────────────────┬───────────────────────────┬─────────────┐ │
│ │ id_delta <uvarint> │ timestamp_delta <uvarint> │ value <8b> │ │
│ └────────────────────┴───────────────────────────┴─────────────┘ │
│ . . . │
└──────────────────────────────────────────────────────────────────┘
compact 流程
query 流程
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。