Prometheus 监控越跑越胖?别急着加硬盘,TSDB 压缩才是关键
文 / Echo_Wish
很多运维同学都有过这种经历:
刚搭好 Prometheus 的时候,感觉一切都挺美。
CPU、内存、磁盘、网络、容器、Pod……各种指标一股脑采进来。
磁盘?够大。
于是放心大胆地采。
结果几个月以后,服务器磁盘开始报警:
“磁盘空间不足。”
一查,好家伙。
Prometheus 自己就占了几十 GB,甚至上百 GB。
这时候很多人的第一反应是:
加硬盘。
硬盘确实能解决问题,但我觉得这其实是运维里一个非常典型的思维误区:
数据增长太快的时候,第一反应不应该是“存更多”,而应该是“为什么要存这么多”。
而这背后,就涉及 Prometheus 最核心的一个东西:
TSDB——Time Series Database,时序数据库。
今天我们就聊聊,时序数据库到底在监控系统里干什么,以及 Prometheus TSDB 到底是怎么通过压缩,让几十亿个监控数据点不至于把你的硬盘吃光。
一、监控系统为什么非得用时序数据库?
先想一个问题。
普通业务数据库,比如 MySQL,最擅长存什么?
比如:
user_id = 10001
name = '张三'
age = 28
这类数据最大的特点是:
一条数据描述一个实体。
但是监控数据完全不一样。
比如我们监控服务器 CPU:
10:00:01 cpu_usage = 32%
10:00:02 cpu_usage = 35%
10:00:03 cpu_usage = 34%
10:00:04 cpu_usage = 36%
10:00:05 cpu_usage = 33%
你会发现:
时间是绝对核心。
而且监控数据还有一个非常明显的特点:
数据量巨大,但是单条数据的信息量非常小。
一台服务器可能几十个指标。
100 台服务器就是几千个时间序列。
到了 Kubernetes:
1000 Pods
×
几十个指标
×
多个 Label
×
每秒采集
数据量直接开始起飞。
所以监控系统真正需要解决的问题不是:
“怎么把一条数据存下来?”
而是:
“怎么高效地保存海量、连续、按时间增长的数据?”
这就是时序数据库存在的价值。
二、Prometheus 的 TSDB 到底是什么?
很多人使用 Prometheus,只知道:
Exporter
↓
Prometheus
↓
Grafana
但 Prometheus 中间其实还有一个非常重要的角色:
Exporter
↓
Prometheus
↓
TSDB
↓
磁盘
Prometheus 默认会把采集到的时间序列数据写入自己的 TSDB。
所谓 TSDB,可以简单理解成:
专门为“时间 + 数值 + 标签”设计的数据库。
例如:
http_requests_total{
method="GET",
status="200",
service="user-service"
}
然后不断产生:
10:00:01 → 100
10:00:02 → 103
10:00:03 → 105
10:00:04 → 106
Prometheus 并不是简单地把这些数据一行一行塞进一个文件。
它会把数据组织成时间序列,并通过内存缓冲、WAL、Block 等机制,把数据最终持久化到磁盘。
大致可以理解成:
采集数据
↓
Head
↓
WAL
↓
Block
↓
压缩
↓
磁盘
其中最值得我们关注的,就是:
压缩。
因为监控数据有一个天然优势:
它特别适合压缩。
三、为什么监控数据特别适合压缩?
举个最简单的例子。
CPU 数据可能是:
31
32
32
33
33
34
34
34
35
如果你每次完整保存:
31
32
32
33
33
34
34
34
35
其实非常浪费。
因为相邻数据变化很小。
完全可以只保存:
31
+1
0
+1
0
+1
0
0
+1
这就是时序数据压缩的核心思想之一:
不要重复保存“变化不大的东西”。
Prometheus 的 TSDB 对样本值和时间戳都有专门的压缩思路。
尤其是浮点数数据,并不是简单地:
timestamp + value
timestamp + value
timestamp + value
全部原样写入。
而是尽可能利用相邻样本之间的规律。
四、时间戳其实也能压缩
比如 Prometheus 每 15 秒采集一次:
1000
1015
1030
1045
1060
1075
如果每个时间戳都完整保存:
1000
1015
1030
1045
1060
1075
其实没有必要。
因为我们已经知道:
每次 +15
于是可以把它理解成:
1000
+15
+15
+15
+15
+15
再配合进一步的编码方式,实际存储空间就能明显降低。
这就是为什么:
时序数据库和普通数据库的数据压缩逻辑,本身就不太一样。
五、Prometheus 真正容易“爆盘”的,其实不是数值
这里我要说一个很多初学者容易忽略的问题。
很多人认为:
“Prometheus 磁盘越来越大,是因为采集的数据太多。”
这句话没错。
但还不够准确。
真正容易把 Prometheus 搞崩的,往往是:
高基数(High Cardinality)
什么叫高基数?
例如:
http_requests_total{
method="GET",
status="200",
user_id="100001"
}
如果:
user_id
有 100 万个。
那么理论上就可能产生:
100 万个时间序列
再乘:
method
×
status
×
service
×
pod
时间序列数量很快就上去了。
这时候你会发现:
真正吃资源的,不只是数据点,而是时间序列本身。
六、所以 Prometheus 优化,第一步不是压缩
很多人看到 TSDB 优化,第一反应:
“有没有什么参数可以让 Prometheus 压缩得更狠?”
我的观点是:
别急。
真正有效的优化顺序应该是:
降低无意义指标
↓
控制 Label
↓
降低高基数
↓
合理采集频率
↓
设置 Retention
↓
最后再考虑存储和压缩
为什么?
因为:
你压缩一个垃圾数据,不如一开始就别采。
七、第一招:千万别把用户 ID 当 Label
比如你的接口:
/api/order/100001
/api/order/100002
/api/order/100003
有些人为了方便统计,直接做:
http_request_total{
path="/api/order/100001"
}
然后:
/api/order/100002
又变成另外一条时间序列。
这就麻烦了。
正确思路应该是:
/api/order/{id}
统一成:
http_request_total{
path="/api/order/{id}"
}
这一个改动,有时候比你买几块 SSD 都管用。
八、第二招:控制采集频率
假设一个指标:
scrape_interval = 5s
一天的数据点数量:
24 × 60 × 60 ÷ 5
也就是:
17280
如果改成:
15s
一天:
5760
直接变成原来的三分之一。
如果你的业务并不需要秒级监控,那么:
为什么非要 5 秒采一次?
比如磁盘容量:
15s
通常完全够用。
而某些高实时性指标,可以:
5s
甚至:
1s
关键是:
不同指标应该有不同的采集策略,而不是全世界统一 15 秒。
九、第三招:Retention 比压缩更加简单粗暴
Prometheus 可以通过启动参数控制数据保留时间。
例如:
prometheus \
--storage.tsdb.retention.time=15d
意思就是:
数据保留 15 天。
如果你的监控数据只需要保存 15 天,就不要莫名其妙保存半年。
还有一种情况:
Prometheus
↓
只负责最近数据
↓
长期数据
↓
远端存储
比如结合:
Thanos
Cortex
Mimir
VictoriaMetrics
等方案,把 Prometheus 定位成:
实时监控 + 本地短期存储
长期数据交给专门的存储体系。
这其实是非常常见的架构。
十、Prometheus TSDB 的 Block 到底是什么?
Prometheus 的 TSDB 数据并不是永远躺在 Head 里。
随着时间推进,数据会被组织成一个个 Block。
可以简单理解:
Block 1
00:00 ───── 02:00
Block 2
02:00 ───── 04:00
Block 3
04:00 ───── 06:00
不同版本和配置下具体行为会有所差异,但从理解角度来说:
Block 就像把一段时间的数据打包成一个个“小仓库”。
一个 Block 里面包含索引、chunks 等数据结构。
例如:
01JXXXX/
├── chunks/
├── index
├── meta.json
└── tombstones
这样做的好处非常明显。
查询:
最近 1 小时
不需要把整个历史数据全部翻一遍。
只需要定位相关时间范围的数据。
十一、为什么删除数据不等于磁盘马上变小?
这是另一个非常容易踩坑的地方。
比如:
promtool tsdb delete
或者通过其他方式删除数据。
你可能会发现:
“奇怪,我数据都删了,磁盘怎么没明显下降?”
原因之一就是:
删除和物理回收不是一回事。
TSDB 会涉及 Block、tombstone、compaction 等机制。
简单理解:
逻辑删除
↓
标记删除
↓
Compaction
↓
重新整理 Block
↓
旧数据真正释放
所以看到磁盘没有马上下降,不一定代表删除失败。
十二、Compaction 到底是在干什么?
可以把 Compaction 理解成:
把很多零零散散的数据仓库重新整理。
比如:
Block A
Block B
Block C
Block D
里面存在大量重复结构。
Compaction 会重新组织:
Block A+B+C+D
↓
新 Block
这样可以:
- 减少冗余
- 优化存储
- 提高查询效率
- 清理已经删除的数据
所以:
Compaction 是 TSDB 非常核心的一环。
十三、Prometheus 优化到底应该怎么做?
如果让我给一个线上 Prometheus 做优化,我一般会先看这几个东西。
1. 看时间序列数量
prometheus_tsdb_head_series
这个指标非常值得关注。
如果:
100 万
→
200 万
→
500 万
持续增长。
那就要警惕了。
2. 看存储增长速度
例如:
rate(prometheus_tsdb_storage_blocks_bytes[1h])
结合实际版本提供的 TSDB 指标进行观察。
重点不是盯着某一个数字,而是:
看趋势。
如果每天:
+2GB
+2GB
+2GB
那一个月就是:
60GB
三个月:
180GB
这才是运维真正应该关心的东西。
十四、别迷信“压缩率”,指标治理才是核心
我特别想强调一个观点:
Prometheus TSDB 的压缩很重要,但它不是万能药。
如果你的系统:
10 万时间序列
突然变成:
1000 万时间序列
你再好的压缩算法,也救不了你。
这就像:
房间里已经堆满垃圾了,你研究垃圾袋的压缩技术,不如先把垃圾扔掉。
所以 Prometheus 优化最核心的逻辑,其实可以总结成一句话:
先控制数据产生,再优化数据存储。
十五、生产环境我更推荐这套思路
如果是一个 Kubernetes 集群,我比较推荐这样的监控架构:
Kubernetes
│
┌─────────┴─────────┐
↓ ↓
Node Exporter kube-state-metrics
│ │
└─────────┬─────────┘
↓
Prometheus
│
┌─────────┴─────────┐
↓ ↓
本地 TSDB Remote Write
│ │
最近 7~15 天 长期时序存储
│
┌────────┴────────┐
↓ ↓
Thanos Mimir
Prometheus 本地负责:
快。
远端存储负责:
久。
Grafana 负责:
好看。
Alertmanager 负责:
报警。
每个组件各干各的活,整个系统反而更稳。
十六、最后聊点我的真实感受
做运维久了,你会发现一个很有意思的现象:
监控系统最容易犯的错误,就是“什么都想监控”。
CPU 要监控。
内存要监控。
磁盘要监控。
Pod 要监控。
接口要监控。
用户要监控。
订单要监控。
甚至有人恨不得把业务代码里的每个变量都做成 Metric。
最后 Grafana 上几百个 Dashboard,Prometheus 里面几百万甚至上千万条时间序列。
但真正发生故障的时候:
没人知道该看哪个。
所以我越来越觉得:
好的监控,不是采集的数据越多越好,而是关键问题能够被快速发现。
TSDB 压缩解决的是:
“这么多数据怎么存?”
而监控治理解决的是:
“这些数据到底有没有必要存?”
这两个问题看起来很像,其实完全不是一个层面。
写在最后
如果你现在的 Prometheus 已经开始出现:
磁盘不断增长
TSDB 占用越来越高
内存越来越大
查询越来越慢
Kubernetes Pod 数量一多就开始卡
别第一时间想着:
“给 Prometheus 加 CPU、加内存、加硬盘。”
先去看看:
到底有多少 Time Series?
再看看:
哪些 Label 基数特别高?
然后检查:
scrape_interval 是否合理?
Retention 是否合理?
有没有大量无意义指标?
是否应该引入 Remote Storage?
很多时候,真正的优化并不是把 Prometheus 变得更强。
而是:
让它少干一点没意义的活。
这才是时序数据库优化里,我认为最接地气、也最容易被忽略的一条经验。
监控不是“存得越多越专业”,而是“关键的数据,恰好被你留下来了”。
—— Echo_Wish