📝 摘要 :读者问:Prometheus 的 TSDB 能独立部署吗?存更多数据有限制吗?答案是不能------TSDB 是嵌入式库,
data目录一个lock文件就挡住了双进程,官方明确「不集群、不副本、不支持 NFS」。但存储能换。本文含 TSDB 写入路径、官方公式算出「50 万 series 存 90 天要 518 GB」、remote_write 三个必踩的坑(远端挂 2 小时就永久丢数据),以及加盘 / VictoriaMetrics / Thanos / Mimir 四条路怎么选。
有人问了我两个问题,问得很准:
- Prometheus 的存储 TSDB,可以独立部署、独立维护吗?
- 如果要存更多的数据,有没有限制?
这两个问题背后是同一个真实困境:监控刚搭起来时谁都不管存储,
--storage.tsdb.path指个目录就上线了。跑上半年,盘满了、Grafana 拉一个月的曲线要转十几秒、想看去年同期的容量趋势发现数据早没了------这时候才回头问「存储这块能不能单独拎出来搞」。先把结论摆出来:第一个问题的答案是「不能」,而且拦住你的是一个具体的文件;第二个问题的答案是「有,而且是三条硬限制」。但「TSDB 拆不出来」不等于「存储换不掉」------这两件事被混为一谈,是绝大多数人卡住的地方。

📚 前置阅读 :本文只讲存储 这一层,不重复搭建过程。Prometheus + Grafana + AlertManager 怎么装、Exporter 怎么配,见 《Prometheus + Grafana + AlertManager 监控体系搭建:Docker 一把梭》------那篇里
--storage.tsdb.path=/prometheus/data只是一行启动参数,本文补上它背后的全部内容。
📖 先约定五个词
后文反复出现,先花一分钟对齐。这五个词分不清,容量就一定算错(尤其前两个,混用是最常见的错源)。
| 词 | 是什么 | 一句话理解 |
|---|---|---|
| series(时间序列) | 一个指标名 + 一组 label 的唯一组合 | http_requests_total{method="GET",code="200"} 和 {method="POST",code="200"} 是两条 series。它决定内存 |
| sample(样本) | 一条 series 在某个时刻的一个数据点(时间戳 + 值) | 每抓取一次,每条 series 产生 1 个 sample。它决定磁盘 |
| 基数(cardinality) | 一个 label 可能取值的个数 | method 基数约 5;userId 基数是百万级------后者放进 label 就是灾难,见 §3.5 |
| block(数据块) | 一段时间范围的数据打包成的只读目录 | 默认 2 小时一个,后台会合并成更大的。「只读」这条性质是后面所有方案的地基 |
| WAL(预写日志) | 落盘前先顺序追加的日志,用于崩溃恢复 | 跟 MySQL redo log、PostgreSQL WAL 是同一思路。但 Prometheus 的 WAL 还兼了第二个身份,见 §4.1 |
💡 一句话记住两者的分工 :series 数量压内存,sample 数量压磁盘。 缩短保留时间只省磁盘、一点内存都省不了;治基数才省内存。这条搞反的话,后面所有优化都会做在错的地方。
一、先把两个问题正面回答了
不绕弯子,这一节直接给答案,原理和方案放后面展开。
1.1 能不能独立部署 TSDB?不能,拦你的是一个 lock 文件
很多人默认「TSDB」是个像 MySQL 那样的独立数据库服务,只是被 Prometheus 用着。不是。
Prometheus 的 TSDB 是一个编译进 Prometheus 二进制里的 Go 库 (prometheus/prometheus/tsdb 包),它:
- ❌ 没有独立的可执行程序 ------没有
tsdb-server这种东西可以单独起 - ❌ 没有网络协议------不监听端口,不接受连接,只有 Go 的函数调用
- ❌ 没有认证、没有多客户端概念------它压根不是设计给「外部访问」的
也就是说,「把 TSDB 单独部署到一台存储机、让 Prometheus 远程连过去」这个想法,在架构上不存在实现路径。
那能不能退一步:Prometheus 还在原地,只把 data 目录挂到网络存储(NFS / EFS)上,让存储独立? 也不行,而且这条路踩下去是数据损坏级别的后果。官方文档写得非常直白:
Non-POSIX compliant filesystems are not supported for Prometheus' local storage as unrecoverable corruptions may happen. NFS filesystems (including AWS's EFS) are not supported.
------ Prometheus 官方文档 · Storage
「unrecoverable corruptions」(不可恢复的损坏) ,用词已经很重了。原因就在于 TSDB 大量依赖 POSIX 文件锁和 mmap 语义,而 NFS 这类网络文件系统对这两样的实现是打折的。
再退一步:两个 Prometheus 共享一个 data 目录做高可用? 这是最直接撞墙的一条------TSDB 会在 data 目录里放一个 lock 文件独占,第二个进程起不来,直接报错退出:
level=error caller=main.go:894 err="opening storage failed: lock DB directory: resource temporarily unavailable"
这个报错在社区里出现频率很高,通常是两种场景撞上的:Kubernetes 里 Prometheus 副本数写成了 2 且共享同一个 PVC,或者旧进程没退干净就拉了新的。
⚠️ 别去找
--storage.tsdb.no-lockfile这个「解法」。 这个参数确实存在,源码里的说明只有一句大白话------Do not create lockfile in data directory.(cmd/prometheus/main.go)。它是给某些部署环境本身已经保证了单实例 的场景用的逃生舱(比如编排层已经锁死副本数、或数据目录挂载方式导致锁文件创建不了),不代表 Prometheus 支持多实例共享目录。拿它去让两个正常 Prometheus 共写一个目录,等于亲手把上面那句
unrecoverable corruptions兑现------锁没了,但两个进程同时写 WAL、同时做 compaction 的问题一个都没解决,锁只是原本在替你挡着而已。📌 顺带澄清一个容易混的点:源码里其实有两个同名参数,分属两种模式、互不通用------
参数 作用域 何时可用 --storage.tsdb.no-lockfileserverOnlyFlag只在普通 server 模式下可用 --storage.agent.no-lockfileagentOnlyFlag只在 Agent 模式下可用(§4.4) 所以别把前者理解成「给 Agent 模式准备的」------它俩是平行的两个开关,用错模式直接不认这个参数。
顺着往下,官方还有一句更根本的定性:
A limitation of local storage is that it is not clustered or replicated.
------ Prometheus 官方文档 · Storage
不做集群、不做副本。 这不是「暂时还没做」,而是 Prometheus 明确划出去的边界------它把自己定位成一个单机、自包含、可靠性靠简单换来的采集与告警引擎,长期存储和水平扩展交给生态里的其他项目。
所以「独立部署存储」的正确形态不是拆 TSDB,而是换掉存储的去向 :让 Prometheus 通过 remote_write 把数据吐给一个真正独立部署的时序数据库,TSDB 退化成一个短期缓冲。这是 §四、§五的主题。
1.2 能存多少?三条硬限制,先看清哪条会先撞
「存更多数据有没有限制」------有,而且是三条不同性质的限制,触发条件也不一样。多数人只盯着磁盘,结果被内存先干掉。

| # | 限制 | 由什么决定 | 撞上的表现 | 能不能靠加机器解决 |
|---|---|---|---|---|
| ① | 磁盘容量 | 单机本地盘大小 × 保留时间 | 盘满,Prometheus 停止写入 | ❌ 不能横向扩,只能换更大的盘 |
| ② | 内存 | 活跃 series 数量(不是磁盘量!) | OOM 被杀,重启后重放 WAL 又慢 | ❌ 只能垂直加内存 |
| ③ | 查询能力 | 原生没有降采样 | 查一年的图要扫全部原始点,慢甚至打爆内存 | ❌ 加机器也没用,是设计缺失 |
三条里最容易被忽视的是 ②③:
- ② 内存跟着 series 走,不跟磁盘走。 你把保留时间从 90 天砍到 15 天,磁盘立刻降到 1/6,内存一点都不会降 ------因为内存里装的是「当前活跃的 series」,跟你留多久历史无关。反过来,某个应用上线时在 label 里塞了
traceId,磁盘还没什么动静,内存已经先炸了。 - ③ 没有降采样是原生 Prometheus 的结构性缺失。 存了一年的数据,查「过去一年的 CPU 趋势」时它会老老实实去扫一年里每 15 秒一个的原始点。Thanos / Mimir 会把老数据预聚合成 5 分钟、1 小时粒度(一年的图本来也看不出 15 秒的细节),原生 TSDB 没这个能力------这也是「存得下」和「查得动」是两个问题的原因。
💡 一句话 :磁盘不够是成本问题 (加盘就行),内存不够和查不动是架构问题 (必须换方案)。先判断自己撞的是哪一条,别拿加盘去治基数爆炸。
二、TSDB 长什么样:搞清结构,才明白为什么拆不开、又为什么能搬走
上一节给了结论,这一节讲清「为什么」。看懂目录结构和写入路径,后面 Thanos 为什么能把数据搬到 S3、remote_write 为什么会丢数据,就都是顺理成章的了。
2.1 目录结构:每个文件是干什么的
官方给出的 data 目录结构(Storage 文档),加上实际会看到的 lock:
./data
├── 01BKGV7JBM69T2G1BGBGM6KB12/ # 一个 block(目录名是 ULID,含时间信息、可排序)
│ └── meta.json
├── 01BKGTZQ1SYQJTR4PB43C8PD98/ # 另一个 block
│ ├── chunks/
│ │ └── 000001 # 压缩后的样本数据,每段最大 512MB
│ ├── index # 倒排索引:label 键值 → series → chunk 位置
│ ├── meta.json # 时间范围、series 数、压缩层级
│ └── tombstones # 删除标记(删数据不立即物理删,见下方说明)
├── chunks_head/ # head block 已满 chunk 的落盘镜像(mmap 读取)
│ └── 000001
├── wal/ # 预写日志,每段 128MB
│ ├── 000000002
│ └── checkpoint.00000001/ # WAL 检查点,压缩掉已落 block 的部分
│ └── 00000000
└── lock # ⭐ 独占锁,就是它让 TSDB「一个目录只能一个进程」
三个值得单独说的:
meta.json里的compaction.level:block 是分层合并的。2 小时的小 block 会被后台 compactor 合并成更大的 block(level 1 → 2 → 3......),减少查询时要打开的文件数。合并只发生在本地,且是重写而非追加。tombstones(墓碑) :调 delete API 删数据时,Prometheus 不立即物理删除,只写一个墓碑标记,查询时跳过。真正的空间回收要等下一次 compaction。所以「删了数据盘没变小」是正常现象,不是 bug。lock:社区反馈它是基于 PID 而非flock()的,所以有个边缘坑------进程被强杀后留下陈旧 lock,若那个 PID 恰好被别的程序占用了,Prometheus 会误判「数据库被锁」而拒绝启动。遇到起不来先确认没有活着的 Prometheus,再清理。
2.2 一条指标从抓取到落盘,走了什么路

按图上的编号:
- scrape :按
scrape_interval去 Exporter 拉一次/metrics。 - 同时写两处 ------这是关键:
- 进内存里的 head block(当前正在写的那个 2 小时窗口),查询走这里,最快;
- 追加进 WAL ,纯顺序写,只为一件事:进程崩了能恢复。
- chunks_head 落盘 :head 里写满的 chunk 会刷到
chunks_head/,之后通过mmap访问。这一步让 head 不必把全部数据都常驻堆内存,是内存优化的关键机制,也是为什么「内存占用 < 活跃数据总量」。 - 切 block :head 攒满 2 小时,整体持久化成一个
01BKG.../目录(chunks + index + meta.json),从此只读,永不修改。对应的 WAL 段被 checkpoint 掉。 - compact:后台把相邻小 block 合并成大 block,同时按 retention 策略删掉过期的整个 block 目录。
🔑 第 4 步的「从此只读」是全文最重要的一句话。 正因为 block 一旦生成就不可变,它才能被安全地整目录复制走 ------Thanos 的 sidecar 干的就是这件事:盯着
data目录,发现新 block 就上传到对象存储。如果 block 是可变的,这种「旁路搬运」根本不成立。这也解释了 §1.1 的另一半:block 可以搬走,但 head + WAL + lock 这套「正在写的状态」搬不走------所以能外接存储,不能外接 TSDB。
2.3 跟 MySQL / PostgreSQL 的 WAL 是一回事吗
思路一致,用途有个重要差别。
| MySQL redo log / PostgreSQL WAL | Prometheus WAL | |
|---|---|---|
| 崩溃恢复 | ✅ 主要用途 | ✅ 主要用途 |
| 主从复制的数据源 | ✅ binlog / WAL 流式发给备库 | ⚠️ 不做复制 ,但 remote_write 从 WAL 读数据往外发(§4.1) |
| 能不能靠它做时间点恢复(PITR) | ✅ 可以 | ❌ 不能,Prometheus 没有 PITR 概念 |
| 保留多久 | 可配置,可以很长 | 约 2 小时,之后被 checkpoint 压缩掉 |
最后一行是个会咬人的差别 :Prometheus 的 WAL 只留约 2 小时。而 remote_write 恰好是从 WAL 读数据的------所以远端存储挂超过 2 小时,那部分数据就永久没了,不是延迟送达,是丢。这条坑在 §4.2 详细展开。
💡 对 WAL、复制、故障切换这一整套机制想系统看的,可以参考【待发布后补充引用关系:《PostgreSQL 高可用理论篇:一主一从不等于高可用,从流复制到 Patroni 自动切换》】------那篇把 WAL 的三段记账(write / flush / replay)、同步档位、复制槽讲透了,很多概念跟这里是通的。
三、先别急着换方案:把原生 TSDB 用到极限
很多人一听「存不下」立刻要上 Thanos,结果引入 5 个组件、还得配对象存储,最后发现真实问题是某个应用往 label 里塞了动态值。换存储治不了基数爆炸,顺序别搞反。
3.1 两个 retention 阀门:谁先到谁生效
bash
prometheus \
--storage.tsdb.path=/prometheus/data \
--storage.tsdb.retention.time=30d \ # 按时间:留 30 天
--storage.tsdb.retention.size=200GB # 按大小:最多占 200GB
| 参数 | 默认值 | 行为 |
|---|---|---|
--storage.tsdb.retention.time |
15d(两个都不设时生效) |
超过时长的 block 整个删掉 |
--storage.tsdb.retention.size |
0(关闭) |
超过容量就从最老的 block 开始删 |
两个同时配,谁先触发谁生效(逻辑上取更严格的那个)。
⭐ 生产建议:两个都配。 只配
time的风险是------某天业务上线一批新指标,series 翻倍,磁盘在 30 天没到之前就满了,Prometheus 直接停止写入,监控系统自己先瞎了 。加一条retention.size当保险,宁可少留几天历史,也不能让监控自己挂掉。设置时留出余量:
retention.size给到盘容量的 70~80%,别顶到 100%------compaction 过程中会临时占用额外空间(要同时存在旧 block 和正在写的新 block)。
3.2 容量测算:官方公式 + 一张能直接查的表
官方给的公式很简洁:
needed_disk_space = retention_time_seconds × ingested_samples_per_second × bytes_per_sample
其中 bytes_per_sample,官方的说法是:
Prometheus stores an average of only 1-2 bytes per sample.
这个「1~2 字节」低到反直觉------一个时间戳加一个 float64 明明是 16 字节。能压到这个量级靠的是 Gorilla 压缩算法 (Facebook 的时序压缩论文):时间戳存差值的差值(等间隔抓取时几乎为 0),数值存与前值的 XOR(监控指标相邻值通常很接近,XOR 后高位全是 0)。指标越平稳,压缩比越高------这也意味着剧烈抖动的指标会比预估占更多空间。
而 ingested_samples_per_second 可以从 series 推出来:
samples_per_second = 活跃 series 数 ÷ scrape_interval(秒)
按 scrape_interval=15s、bytes_per_sample=2(取保守上限)算出的对照表:
| 活跃 series | samples/s | 留 15 天 | 留 30 天 | 留 90 天 | 留 180 天 | 留 365 天 |
|---|---|---|---|---|---|---|
| 10 万 | 6.7k | 17 GB | 35 GB | 104 GB | 207 GB | 420 GB |
| 50 万 | 33k | 86 GB | 173 GB | 518 GB | 1.04 TB | 2.1 TB |
| 200 万 | 133k | 346 GB | 691 GB | 2.07 TB | 4.15 TB | 8.4 TB |
⚠️ 实际还要再乘 1.2 左右,给 index、meta.json、tombstones 这些非样本数据留量。上表是纯样本估算,规划时按 ×1.2 报预算。
这张表把「存不下」量化了。最该被看见的是中间那行:
50 万 series 是个中型系统很容易到的量级 (几十个服务 + 十几种 Exporter 就够了)。它留 15 天只要 86 GB,一块盘轻松装下;但想留 90 天就要 518 GB,留一年要 2.1 TB ------而且这 2.1 TB 全压在一块本地盘上,还没算查询时的内存开销。
这就是原生 TSDB 的天花板具体长什么样:不是「不能存」,是「存到一年这个量级,单机方案在成本和风险上都不再合理」。

3.3 别猜 bytes_per_sample,量一下你自己的
上面用的是 2 字节保守值,但你的实际值可能是 1.1 也可能是 3------取决于指标平稳程度。直接在 Prometheus 里查真实值,比套公式准得多:
promql
# 你的实际 bytes per sample
rate(prometheus_tsdb_compaction_chunk_size_bytes_sum[1h])
/
rate(prometheus_tsdb_compaction_chunk_samples_sum[1h])
promql
# 你的实际样本摄入速率(samples/s)
rate(prometheus_tsdb_head_samples_appended_total[1h])
promql
# 当前活跃 series 数------决定内存的那个数字
prometheus_tsdb_head_series
拿这三个实测值代回 §3.2 的公式,就能算出贴合自己系统的 磁盘需求。规划容量前先跑这三条查询,别拿别人博客里的数字当自己的预算依据(包括本文这张表)。
⚠️ 查不到这几个指标是正常的------
prometheus_tsdb_*这组自监控指标跟版本、部署方式都有关。 别急着以为公式错了,先确认你这套里到底有哪些:
promql{__name__=~"prometheus_tsdb_compaction.*"} # 这组实际存在的名字,一次列全或者直接看原始暴露:
curl -s http://<你的prometheus>:9090/metrics | grep tsdb_compaction。
按查出来的结果对号入座:
- 一条都没有 → 多半是 Prometheus 没抓自己 。
prometheus.yml里要有一个指向自己:9090的 job(见 Prometheus + Grafana + AlertManager 监控体系搭建 里的job_name: 'prometheus')。没这一条的话,所有prometheus_开头的指标都查不到 ,不只是这几个。另外 Agent 模式(§4.4)本身就没有本地 TSDB,自然也没有 compaction 这组。- 名字能列出来,但查
_sum是空的 → compaction 还没跑过 。head block 默认攒够约 2 小时才落盘压缩一次,刚起的实例一次观测都没有;此时rate(...) / rate(...)会算出NaN被 PromQL 丢掉,表现就是Empty query result。等它跑满几个压缩周期再查。- 名字和本文对不上 → 以你查出来的为准。不同版本这组指标增删改过,本文写的是当前官方源码里的名字。
实在拿不到,用最笨但最准的办法 :隔 24 小时量两次数据目录大小(du -sb <storage.tsdb.path>),差值除以这段时间的样本数increase(prometheus_tsdb_head_samples_appended_total[24h]),得到的就是你自己的bytes_per_sample。这个办法不依赖任何自监控指标,任何版本都能用。
3.4 内存怎么估:跟着 series 走
内存的经验公式(社区常引用的量级,不是官方承诺值):
head 内存 ≈ 活跃 series 数 × 约 7.5 KiB
| 活跃 series | head 内存估算 | 整机建议内存 |
|---|---|---|
| 10 万 | ≈ 0.7 GiB | 4 GB |
| 50 万 | ≈ 3.6 GiB | 8~16 GB |
| 200 万 | ≈ 14 GiB | 32 GB+ |
「整机建议」按 head 的 2 倍以上 给,因为还要装下:查询的临时开销 (大范围查询会把数据加载进内存,是最容易触发 OOM 的动作)、remote_write 的发送队列 (§4.1,队列内存 ∝ 分片数 × 队列容量)、compaction 的工作内存、以及给 OS page cache 留的余量(block 靠 mmap 读,page cache 直接决定查询快慢)。
同样,别用估算值,量自己的:
promql
prometheus_tsdb_head_series # 活跃 series
process_resident_memory_bytes{job="prometheus"} # 实际内存占用
💡 第二条里的
job="prometheus"要换成你自己prometheus.yml里那个 job 的名字 ,写错了同样是空结果。查不到就先去掉标签选择器:process_resident_memory_bytes,看看实际有哪些job值。上面 §3.3 那段「查不到怎么办」对这里同样适用。
⚠️ 重启慢也是内存问题的连带后果 :Prometheus 启动时要重放 WAL 才能重建 head。series 越多、WAL 越大,重启就越慢------大实例重启几分钟起步,这段时间没有采集、没有告警评估。所以给 Prometheus 留足内存不只是防 OOM,也是在压缩这段监控盲区。
3.5 ⚠️ 换存储治不了的病:基数爆炸
这是「存储不够」最常见的假象 。真正的病根往往是某个 label 里塞了不该塞的动态值:
promql
# ❌ 灾难写法:每个用户产生一条独立 series
http_requests_total{userId="10086", path="/api/order"}
# ❌ 更狠的:traceId 基数无上限,每个请求都是一条新 series
db_query_duration{traceId="a1b2c3d4...", sql="SELECT..."}
# ✅ 正确:label 只放低基数的枚举值
http_requests_total{method="GET", path="/api/order", code="200"}
为什么它比「数据太多」严重得多 :series 数量是乘法关系 。一个指标带 5 个 label,各自基数是 10、5、20、3、2,就是 10×5×20×3×2 = 6000 条 series。其中任何一个 label 换成 userId(百万基数),这个指标单独就能产生百万级 series ------内存直接爆,而且换成 VictoriaMetrics 或 Thanos 一样爆,只是爆得晚一点。
怎么定位真凶:
promql
# 哪些指标名 series 最多(先看这个)
topk(10, count by (__name__)({__name__=~".+"}))
# 某个可疑指标里,哪个 label 是基数元凶
count(count by (userId) (http_requests_total))
更省事的办法是直接开 Web UI 的 /tsdb-status 页面(Status → TSDB Status),Prometheus 自带一组统计:series 最多的指标名、基数最高的 label、占空间最多的 label 值。比自己写 PromQL 快,排查基数问题第一站就该看这里。

治法只有一条:从源头改埋点 ,把高基数值从 label 挪走------真要按 userId 排查就该去日志和链路追踪系统查(那才是它们的战场),指标系统只该存可聚合的低基数维度。
💡 这个错误的形状,和【待发布后补充引用关系:《ClickHouse 大表 67 GiB 清到 57 MiB:真凶不是重复行,是被重存 N 遍的数组》】里的病根是同一类:都不是「数据本来就这么多」,而是把不该存进来的东西存进来了。 遇到存储暴涨,先怀疑「存了什么」,再怀疑「存了多久」------顺序反了就会拿加盘去治设计问题。
3.6 什么信号说明真该换方案了
把上面的手段(调 retention、治基数、加盘)都用过之后,出现下面任意一条,就是原生 TSDB 到顶了:
| 信号 | 具体表现 | 说明 |
|---|---|---|
| 合规 / 业务要长期数据 | 要看季度环比、年度容量趋势、要留一年以上 | ⭐ 最硬的一条,加盘也解决不了「查得动」 |
| 活跃 series 稳定超百万 | 且已确认不是基数问题、是真实业务规模 | 内存进入危险区,垂直扩容边际成本陡增 |
| 查长周期直接把 Prometheus 打挂 | 拉 30 天的图 OOM 或超时 | 缺降采样,架构缺陷 |
| 多个 Prometheus 要统一查询 | 分机房 / 分集群各一套,想在一个看板里看全局 | 单机 TSDB 天生没有全局视图 |
| 监控自己成了单点 | Prometheus 挂了这段时间数据永久空洞 | 官方明确「不集群不副本」,本机存储无解 |
反过来------活跃 series 在百万以内、只需要留 15~30 天、单套 Prometheus ,那么加盘 + 配好两个 retention 阀门就够了,别上 Thanos。多引入的组件本身就是新的故障源,而监控系统挂掉的代价是「所有故障你都看不见」。
四、外部存储:remote_write 是唯一正道
确认要换了,接口只有一个:remote_write。它就是官方留出的「存储换出去」的口子。
4.1 remote_write 干的其实是「读 WAL 再转发」
配置本身很简单:
yaml
# prometheus.yml
remote_write:
- url: "http://victoriametrics:8428/api/v1/write"
queue_config:
capacity: 10000 # 单个分片的队列容量
max_shards: 50 # 最大并发分片数(默认 200)
min_shards: 1
max_samples_per_send: 2000 # 每个请求带多少样本
batch_send_deadline: 5s # 攒不满也最多等这么久
min_backoff: 30ms
max_backoff: 5s
# 可选:只把需要长存的指标发出去,减少远端压力和成本
write_relabel_configs:
- source_labels: [__name__]
regex: 'go_.*|process_.*' # 过滤掉进程自身的指标
action: drop
关键在于它的工作机制 ,官方 Remote write tuning 描述得很清楚:
Each remote write destination starts a queue which reads from the write-ahead log (WAL), writes the samples into an in memory queue owned by a shard, which then sends a request to the configured endpoint.
remote_write 是一个 WAL 的读者。 它不是在采集时旁路分流,而是数据先正常落进 WAL,再由独立的队列去读 WAL、分片、批量发往远端。

这个机制直接解释了下一节三个坑的全部成因。
4.2 ⚠️ 三个必须知道的坑
坑 1:remote_write 不省本地盘
最常见的误解 :以为配了 remote_write,数据就「转移」到外部了,本地不占空间。
不是。 从上图看得很清楚:本地的 head → block → compact 这条路照常走 ,remote_write 只是额外 多了一条发送路径。配完之后本地磁盘占用一点没变,反而因为多了发送队列,内存还涨了。
想省本地盘必须自己动手 ------配了 remote_write 之后,把本地 retention 主动调短:
bash
--storage.tsdb.retention.time=6h # 历史数据在远端,本地只留短期
但短到多少有个下限,就是坑 3。
坑 2:远端挂超过 2 小时 = 数据永久丢失
这条最危险,因为它平时完全看不出来,出事时已经晚了。官方原话:
Failures will be retried without loss of data unless the remote endpoint remains down for more than 2 hours. After 2 hours, the WAL will be compacted and data that has not been sent will be lost.
因果链是这样的:remote_write 从 WAL 读数据 → WAL 只保留约 2 小时就被 checkpoint 压缩 → 远端宕机超过 2 小时,还没发出去的数据随 WAL 一起被清掉。
这不是延迟送达,是永久空洞。 而且本地 block 里那份也会按你配的 retention 到期删除------如果按坑 1 把本地调到了 6h,那么远端挂一个周末,这两天的数据就两边都没有了。
必配的告警(这条必须有,没有等于在裸奔):
yaml
groups:
- name: remote-write
rules:
# ⭐ 最关键:样本被丢弃,说明队列满了、正在丢数据
- alert: RemoteWriteSamplesDropped
expr: rate(prometheus_remote_storage_samples_dropped_total[5m]) > 0
for: 5m
labels: { severity: critical }
annotations:
summary: "remote_write 正在丢样本,远端可能已挂或太慢"
# 落后越来越多,是丢数据的前兆------留出的处置时间比上面那条多
- alert: RemoteWriteBehind
expr: |
(prometheus_remote_storage_highest_timestamp_in_seconds
- ignoring(remote_name, url) prometheus_remote_storage_queue_highest_sent_timestamp_seconds)
> 300
for: 10m
labels: { severity: warning }
annotations:
summary: "remote_write 落后远端超过 5 分钟"
# 分片打到上限,说明吞吐不够,该调 max_shards 或扩远端
- alert: RemoteWriteShardsMaxed
expr: |
prometheus_remote_storage_shards
>= prometheus_remote_storage_shards_max
for: 15m
labels: { severity: warning }
💡
prometheus_remote_storage_samples_dropped_total一旦有增长就是在丢数据,这条按 critical 配,别按 warning------它响的时候,你损失的是永远补不回来的监控数据。
坑 3:告警规则还在本地评估,本地 retention 有下限
容易被漏掉、后果很隐蔽的一条。
remote_write 只管把数据往外送,Prometheus 的告警规则和 recording rules 仍然在本地评估、读的是本地 TSDB。所以:
yaml
# 假设你有这么一条规则
- alert: HighErrorRate
expr: rate(http_errors_total[1h]) / rate(http_requests_total[1h]) > 0.05
for: 30m
这条规则需要本地至少有 1 小时的数据 ([1h] 窗口),加上 for: 30m 的持续判断,实际需要更多。如果你按坑 1 把本地 retention 压到 30m,这条告警会静默失效------不报错、不告警、看起来一切正常,直到某天真出事了它没响。
规则:
本地 retention ≥ 所有规则里最长的时间窗口 × 2,再往上取整
排查方法------把所有规则里的时间窗口捞出来看最长的:
bash
grep -rhoE '\[[0-9]+[smhd]\]' /etc/prometheus/rules/ | sort -u
保险起见本地留 24h 以上是个稳妥的默认值:既覆盖了绝大多数规则窗口,也给远端故障留出了远超 2 小时的缓冲。别为了省几十 GB 盘把告警搞瘸了。
⚠️ 想真正把规则也移出去,得用 Thanos Ruler 或 VictoriaMetrics 的 vmalert------它们在外部存储上评估规则。那才是「彻底把 Prometheus 变成纯采集器」的完整形态。
4.3 remote_read 为什么基本不用
有 remote_write 就有 remote_read,但它很少是正确答案。
remote_read 的工作方式是:查询打到 Prometheus,Prometheus 把原始样本 从远端拉回本地,再在本地跑 PromQL 计算。问题一目了然------大范围查询要把海量原始点通过网络搬回来,慢,而且容易把 Prometheus 自己打爆。
正确做法:让 Grafana 直连外部存储。
┌── Prometheus(采集 + 告警,短期数据)
Grafana ─┤
└── VictoriaMetrics / Thanos Query(历史数据,直连查询)
VictoriaMetrics、Thanos Query、Mimir 都提供 Prometheus 兼容的查询 API,在 Grafana 里当成一个普通 Prometheus 数据源加上就行。看近期数据选前者,看历史趋势选后者;也可以直接把外部存储设为默认数据源(它通常也能查到近期数据)。
4.4 Agent 模式:最接近「把存储拆出去」的形态
如果你的诉求是「Prometheus 只负责采集,存储完全在别处」,官方给了正式支持------Agent 模式:
bash
prometheus --agent --config.file=/etc/prometheus/prometheus.yml
它的行为(官方文档):
- ✅ 保留:scrape、服务发现、
remote_write------配置文件写法完全不变 - ❌ 去掉:本地查询("You can not query the local Prometheus instance")
- ❌ 去掉:告警("All alerting must be done by the remote system")
- ❌ 去掉:recording rules
- ❌ 不生成本地 block,只用一个专门优化过的 WAL :写成功即删除数据;远端不可达时临时落盘,同样限于 2 小时缓冲
- Web UI 在 9095 端口,能看 targets 和配置,但不能查询
这才是「TSDB 独立出去」这个诉求的官方答案 :不是把 TSDB 搬走,而是把 Prometheus 砍成一个纯转发器,存储、查询、告警全部交给远端系统。
适合 :边缘节点、每个 K8s 集群放一个采集器往中心汇聚、多机房架构。
不适合 :单套监控的主力实例------告警没了,你得先把告警体系整个搬到远端(vmalert / Thanos Ruler / Mimir Ruler)才能用它。
五、四条路线怎么选
5.1 对照表
| 维度 | ① 原生 TSDB + 加盘 | ② VictoriaMetrics | ③ Thanos | ④ Grafana Mimir |
|---|---|---|---|---|
| 形态 | 嵌在 Prometheus 里 | 独立时序库,vmsingle 单二进制起步 |
Prometheus + 对象存储 + 一组组件 | 集群,微服务化 |
| 怎么接 | 什么都不用改 | remote_write |
sidecar 传 block(不走 remote_write) | remote_write |
| 历史数据落在 | 本地盘 | 本地盘(集群版可分片) | 对象存储(S3 / MinIO / OSS) | 对象存储 |
| 组件数 | 1 | 1(vmsingle) | 4~5(sidecar / store / query / compact / ruler) | 10+ |
| 降采样 | ❌ 没有 | ⚠️ 开源版无,企业版有(以官网为准) | ✅ 5m / 1h 两档 | ✅ 有 |
| 查询语言 | PromQL | MetricsQL(PromQL 超集,绝大多数直接兼容) | 原生 PromQL | 原生 PromQL |
| 压缩 | 基准 | 官方宣称可达 10x | 约 2~4x + 降采样省量 | 与 Thanos 同量级 |
| 多租户 | ❌ | 集群版支持 | 弱 | ✅ 强项 |
| 运维成本 | 最低 | 低 | 中高 | 高 |
| 适合 | <100 万 series、留 15~30 天 | ⭐ 中小团队要长期存储 | 已有多套 Prometheus + 已有对象存储 | 多租户 / 超大规模 / 已在 Grafana 生态 |
⚠️ 压缩比和降采样的授权归属都请以官网当时的口径为准 ------这两项是选型的关键变量,而开源项目的功能划分和许可会变。上表里 VictoriaMetrics 的 10x 是官方宣称值(不是我实测的),实际压缩比取决于你的指标平稳程度(原理见 §3.2)。
5.2 决策路径
#mermaid-svg-FV7NDzjKLfJpcAwW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-FV7NDzjKLfJpcAwW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FV7NDzjKLfJpcAwW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FV7NDzjKLfJpcAwW .error-icon{fill:#552222;}#mermaid-svg-FV7NDzjKLfJpcAwW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FV7NDzjKLfJpcAwW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FV7NDzjKLfJpcAwW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FV7NDzjKLfJpcAwW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FV7NDzjKLfJpcAwW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FV7NDzjKLfJpcAwW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FV7NDzjKLfJpcAwW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FV7NDzjKLfJpcAwW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FV7NDzjKLfJpcAwW .marker.cross{stroke:#333333;}#mermaid-svg-FV7NDzjKLfJpcAwW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FV7NDzjKLfJpcAwW p{margin:0;}#mermaid-svg-FV7NDzjKLfJpcAwW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-FV7NDzjKLfJpcAwW .cluster-label text{fill:#333;}#mermaid-svg-FV7NDzjKLfJpcAwW .cluster-label span{color:#333;}#mermaid-svg-FV7NDzjKLfJpcAwW .cluster-label span p{background-color:transparent;}#mermaid-svg-FV7NDzjKLfJpcAwW .label text,#mermaid-svg-FV7NDzjKLfJpcAwW span{fill:#333;color:#333;}#mermaid-svg-FV7NDzjKLfJpcAwW .node rect,#mermaid-svg-FV7NDzjKLfJpcAwW .node circle,#mermaid-svg-FV7NDzjKLfJpcAwW .node ellipse,#mermaid-svg-FV7NDzjKLfJpcAwW .node polygon,#mermaid-svg-FV7NDzjKLfJpcAwW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FV7NDzjKLfJpcAwW .rough-node .label text,#mermaid-svg-FV7NDzjKLfJpcAwW .node .label text,#mermaid-svg-FV7NDzjKLfJpcAwW .image-shape .label,#mermaid-svg-FV7NDzjKLfJpcAwW .icon-shape .label{text-anchor:middle;}#mermaid-svg-FV7NDzjKLfJpcAwW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-FV7NDzjKLfJpcAwW .rough-node .label,#mermaid-svg-FV7NDzjKLfJpcAwW .node .label,#mermaid-svg-FV7NDzjKLfJpcAwW .image-shape .label,#mermaid-svg-FV7NDzjKLfJpcAwW .icon-shape .label{text-align:center;}#mermaid-svg-FV7NDzjKLfJpcAwW .node.clickable{cursor:pointer;}#mermaid-svg-FV7NDzjKLfJpcAwW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-FV7NDzjKLfJpcAwW .arrowheadPath{fill:#333333;}#mermaid-svg-FV7NDzjKLfJpcAwW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FV7NDzjKLfJpcAwW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FV7NDzjKLfJpcAwW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FV7NDzjKLfJpcAwW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-FV7NDzjKLfJpcAwW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FV7NDzjKLfJpcAwW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-FV7NDzjKLfJpcAwW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FV7NDzjKLfJpcAwW .cluster text{fill:#333;}#mermaid-svg-FV7NDzjKLfJpcAwW .cluster span{color:#333;}#mermaid-svg-FV7NDzjKLfJpcAwW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FV7NDzjKLfJpcAwW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-FV7NDzjKLfJpcAwW rect.text{fill:none;stroke-width:0;}#mermaid-svg-FV7NDzjKLfJpcAwW .icon-shape,#mermaid-svg-FV7NDzjKLfJpcAwW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FV7NDzjKLfJpcAwW .icon-shape p,#mermaid-svg-FV7NDzjKLfJpcAwW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-FV7NDzjKLfJpcAwW .icon-shape .label rect,#mermaid-svg-FV7NDzjKLfJpcAwW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FV7NDzjKLfJpcAwW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-FV7NDzjKLfJpcAwW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-FV7NDzjKLfJpcAwW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是:某指标 series 畸高
不是,是真实规模
≤30 天
>30 天 / 要看年度趋势
单套就够
≤3 人
有专职 SRE
多集群 / 多机房
有
没有
还要多租户隔离
Prometheus 存储扛不住了
先查 /tsdb-status
是基数爆炸吗?
❌ 别换存储
回去改埋点,把高基数 label 挪走
(§3.5)
要留多久?
✅ 原生 TSDB
加盘 + retention.time/size 双阀
(§3.1)
已经有多套 Prometheus
需要统一查询吗?
团队有几个人
能扛运维?
✅ VictoriaMetrics
vmsingle 单进程 + vmalert
运维成本最接近原生
VictoriaMetrics 或 Thanos
看有没有现成对象存储
有对象存储
(S3/MinIO/OSS)吗?
✅ Thanos
sidecar 上传 block
历史进对象存储、成本最低
✅ VictoriaMetrics 集群版
不依赖对象存储
✅ Grafana Mimir
⚠️ 运维最重,先确认扛得住
5.3 落到具体建议
Thanos 和 VictoriaMetrics 的分水岭不是性能,是「你已经有什么」:
-
已经有一批 Prometheus 在跑、且有对象存储 → Thanos 。它的 sidecar 模式不改变你的采集模型:Prometheus 照旧跑,旁边挂个 sidecar 把只读 block 上传到 S3(这正是 §2.2 那句「block 从此只读」的价值兑现)。历史数据在对象存储上,成本比块存储低一个量级,还有降采样。代价是组件多、查询链路长(Query → Store Gateway → 对象存储),历史查询延迟明显高于本地盘。
-
就一套 Prometheus、团队没有专职 SRE → VictoriaMetrics 。
vmsingle是单个二进制、一个进程 就能上生产,运维心智负担和原生 Prometheus 几乎一个量级,配上vmalert就能把告警也接过去。代价是 MetricsQL 虽然是 PromQL 超集,但社区看板偶尔会有个别函数行为差异,导入现成 Dashboard 时留意验证。 -
Mimir :横向扩展和多租户是它的强项,但 10+ 个组件的运维成本是实打实的。没有专职团队别碰------为了存监控数据引入一套比业务系统还复杂的东西,本末倒置了。
-
InfluxDB / TimescaleDB / GreptimeDB 这类通用时序库 :能接,但不在上面四条推荐里------原因值得单独说清,见下面 §5.4。
💡 最后一条,也是最容易被忽略的:换存储之前,先确认 Prometheus 自己被监控着。 Prometheus 挂了通常没人告诉你------因为告警就是它发的。至少配上「Prometheus 自身 down」「TSDB head series 突增」「remote_write 丢样本」这三条,交叉监控(两套 Prometheus 互相抓)或用 Alertmanager 的 Dead man's switch 兜底。监控系统的盲区就是它自己。
这类「组件自己出问题反而没人知道」的坑,在 《监控指标自己把进程打到 CPU 100%:一次 safepoint 风暴的深度复盘》 里有个更极端的版本------监控埋点本身成了故障源。
5.4 为什么不用 InfluxDB?
这是被问最多的一个问题------InfluxDB 名气比 VictoriaMetrics 大得多,很多人第一反应就是它。它不是坏数据库,但它不是「Prometheus 长期存储」这个岗位的合适人选。
先说清判断标准:选长期存储时,你真正在买的不是「数据库好不好」,而是「PromQL 还能不能用」。因为 PromQL 决定了三样已经存在的资产还认不认:
| 已有资产 | 用 VictoriaMetrics / Thanos / Mimir | 用 InfluxDB |
|---|---|---|
| Grafana 看板(含所有社区看板 ID) | ✅ 直接用 | ❌ 全部重写查询 |
告警规则 (.rules 文件) |
✅ 原样交给 vmalert / Thanos Ruler | ❌ 重写成它自己的告警体系 |
| 团队会写的查询语言 | ✅ 还是 PromQL | ❌ 学 InfluxQL / SQL |
这三行就是全部理由。展开说四点:
① 没有原生 PromQL
Prometheus 生态里几乎所有现成资产都是 PromQL 写的。你在 Grafana 里按 ID 导入的那些看板(node_exporter、MinIO、Redis、Kafka......),查询全是 PromQL。换到 InfluxDB,这些一张都不能直接用------要么逐条重写,要么放弃社区看板自己画。
社区里早有人提过「希望原生支持 PromQL,这样现有看板不用改」这个需求,理由跟这里说的完全一样。截至写这篇时没有看到官方原生 PromQL 支持 ;如果你正在评估,这一条务必去官网确认当前状态------它一旦支持,下面大半结论都要重估。
② 数据模型对不上,翻译是有损的
| Prometheus | InfluxDB | |
|---|---|---|
| 结构 | 指标名 + labels(全是字符串)+ float64 | measurement + tags (索引)+ fields(有类型)+ 时间戳 |
写入方向还行(指标名→measurement、labels→tags、值→一个叫 value 的 field),但读的方向就别扭了 :本来一句 rate(http_requests_total[5m]) 的事,要用它的语言重新表达一遍聚合逻辑。而且这个映射是 Prometheus 模型的降级投射------它的 fields 支持多类型、多字段,Prometheus 用不上;反过来 Prometheus 要的那套 PromQL 语义,它没有。
③ 版本反复带来的真实迁移成本(我认为这条最该看)
InfluxDB 的查询语言在几年里换了两轮方向:
1.x → InfluxQL(类 SQL)
2.x → Flux(全新函数式语言,被推为旗舰)
3.x → 回到 SQL + InfluxQL,Flux 被弃用
3.x 是 Rust 重写、基于 Apache Arrow / DataFusion,技术上是次大升级;但对在 2.x 时代照官方指引把查询和告警都写成 Flux 的团队来说,等于主力查询语言被废弃了一次------官方甚至提供了「Flux 转 SQL」的转换工具,这本身就说明迁移量是真实存在的。
对比一下:PromQL 从 Prometheus 1.x 到 3.x 一直是 PromQL。 长期存储这种东西一旦上线就要活好几年,语言层的稳定性不是锦上添花。
④ 开源版是单机的,横向扩展要商业授权
- InfluxDB 3 Core :开源(MIT / Apache 2),但单节点,官方自己的定位是「非生产、边缘、单节点部署」
- 横向扩展 / 集群 :走 Enterprise,按 CPU 核数授权
如果你换外部存储的动机里包含「单机扛不住了」,那开源版正好不解决这个问题。而 VictoriaMetrics 的集群版本身就是开源的(Apache 2.0,企业版另加降采样等增强),Thanos / Mimir 也都是 Apache 2.0 的完整分布式方案。
⑤ 顺带一个实操摩擦 :1.x 时代有原生的 Prometheus remote write / read 端点,是最干净的接法;到了新版本,常见路径变成 Prometheus → Telegraf(prometheus_remote_write 输入)→ InfluxDB,多一跳组件。这削掉了「用它更简单」的那部分理由。(具体接法各版本不同,动手前查当版文档。)
那什么时候 InfluxDB 才是对的?
当「Prometheus 长期存储」不是你的主要需求时------这时它的优势才用得上:
- ✅ 存的不只是指标:IoT / 传感器数据、事件流、带字符串和高精度整型的业务时序(Prometheus 模型只有 float64,装不了这些)
- ✅ 团队要用 SQL(3.x 的 SQL + Arrow 生态是真优势,接 BI 工具比 PromQL 顺)
- ✅ 公司已经在跑 InfluxDB、有现成运维经验和授权------这时多一套 VictoriaMetrics 的运维成本,可能反而大于看板重写的成本
- ✅ 业务侧时序分析和监控指标想合到一个库里
💡 一句话:InfluxDB 是个通用时序数据库,VictoriaMetrics / Thanos / Mimir 是 Prometheus 生态的原生扩展。 干「给 Prometheus 当长期存储」这份活,生态契合度比数据库本身的素质更决定成败------因为决定你迁移成本的,是那几百个已经写好的 PromQL 查询,不是写入吞吐的 benchmark 数字。
同理适用于 TimescaleDB(PostgreSQL 扩展,SQL 生态强,但同样不是 PromQL)和 GreptimeDB(较新,兼容性和成熟度请自行验证)。
六、总结

回到开头那两个问题:
1. TSDB 能独立部署维护吗?------不能。
- 它是编译进 Prometheus 的 Go 库,没有独立进程、没有网络协议
data目录有lock文件独占,两个进程共享直接起不来- 官方明确 不集群、不副本、不支持 NFS/EFS(会不可恢复损坏)
- 但存储的去向能换 :
remote_write接外部时序库;要彻底只留采集,用 Agent 模式 (--agent)
2. 存更多数据有限制吗?------有三条,性质不同。
- 磁盘 :受单机盘限制。
磁盘 ≈ 保留秒数 × samples/s × 1~2 字节,再 ×1.2 - 内存 :跟活跃 series 线性相关,跟保留时长无关。缩 retention 省不了内存
- 查询 :原生没有降采样,长周期查询必然慢------这条加机器也治不了
排查与决策顺序(照这个顺序走,能省掉大部分无谓的架构升级):
| 步骤 | 动作 | 命令 / 查询 |
|---|---|---|
| 1️⃣ | 先排除基数爆炸 | Web UI → Status → /tsdb-status |
| 2️⃣ | 量自己的真实数字,别套别人的 | prometheus_tsdb_head_series、rate(prometheus_tsdb_head_samples_appended_total[1h])。⚠️ 查出来是空的先看 §3.3 那段「查不到怎么办」------这组自监控指标跟版本、有没有抓自己都有关 |
| 3️⃣ | 配好两个 retention 阀门 | --storage.tsdb.retention.time + .size(给盘的 70~80%) |
| 4️⃣ | ≤30 天 → 到此为止,加盘就行 | 别上 Thanos |
| 5️⃣ | 要长存 → remote_write 接外部库 |
中小团队优先 VictoriaMetrics;有对象存储 + 多套 Prometheus 用 Thanos |
| 6️⃣ | 接完必须做三件事 | 本地 retention ≥ 最长规则窗口×2(别让告警静默失效 );配 samples_dropped_total 告警;Grafana 直连外部库、别用 remote_read |
三条最容易踩的(重复一遍,因为代价都很大):
remote_write不省本地盘------本地照常写,要省得自己调 retention- 远端挂超 2 小时的数据永久丢失 ------WAL 只留 2 小时,
samples_dropped_total必须配 critical 告警 - 告警仍在本地评估 ------本地 retention 压太短会让告警静默失效,不报错、不告警,出事那天才发现
一句话收尾:Prometheus 从来没打算做你的长期数据仓库,它只想做一个可靠的采集器和告警引擎。 认清这个定位,就不会指望把 TSDB 拆出来单独维护,而是让它做好短期缓冲,把「存一年」这件事交给专门干这个的系统。
延伸阅读
本站相关
- 《Prometheus + Grafana + AlertManager 监控体系搭建:Docker 一把梭》 ------ 本文的前置篇,监控底座怎么搭起来
- 《MinIO 监控一条龙:看板全是「无数据」?Prometheus + Grafana 从搭建到告警》 ------ 顺带一提:MinIO 也可以直接当 Thanos 的对象存储后端
- 《监控指标自己把进程打到 CPU 100%:一次 safepoint 风暴的深度复盘》 ------ 监控自己成为故障源的极端案例
- 【待发布后补充引用关系:《PostgreSQL 高可用理论篇:一主一从不等于高可用,从流复制到 Patroni 自动切换》】------ WAL、复制、故障切换的系统讲法
- 【待发布后补充引用关系:《ClickHouse 大表 67 GiB 清到 57 MiB:真凶不是重复行,是被重存 N 遍的数组》】------ 同一类病根:先怀疑「存了什么」,再怀疑「存了多久」
官方文档
-
Prometheus · Storage ------ 目录结构、容量公式、NFS 限制、retention 参数
-
Prometheus · Remote write tuning ------
queue_config调参与「2 小时后丢数据」的出处 -
Prometheus · Agent Mode ------
--agent的能力与限制 -
Introducing Prometheus Agent Mode ------ Agent 模式的设计动机
-
Which InfluxDB 3 should I use? ------ §5.4 提到的 Core(开源单机)vs Enterprise(集群、按核授权)的官方口径
-
InfluxDB v1 · Prometheus endpoints support ------ 1.x 时代原生 remote write / read 端点的文档
-
Telegraf · prometheus_remote_write 输入格式 ------ 新版本经 Telegraf 接入的那一跳
-
Prometheus 源码 ·
cmd/prometheus/main.go------ §1.1 那处订正的一手依据:storage.tsdb.no-lockfile注册为serverOnlyFlag、storage.agent.no-lockfile注册为agentOnlyFlag,两者平行且互不通用
📅 最后更新 :2026-08-26。订正 §1.1 对
--storage.tsdb.no-lockfile的归因 :原文说「它的用途是配合 Agent 模式」------查cmd/prometheus/main.go后确认并非如此。源码里其实是两个平行的参数 :--storage.tsdb.no-lockfile注册为serverOnlyFlag(只在普通 server 模式可用) ,而 Agent 模式用的是另一个--storage.agent.no-lockfile(agentOnlyFlag),用错模式直接不认 。核心警告不变(别拿它去绕开单写限制------锁没了,但两个进程同时写 WAL、同时 compaction 的问题一个没解决),但理由改成了准确的:它是给「部署环境本身已保证单实例」的场景用的逃生舱。已补两者的对照表。
🏷️ 标签 :Prometheus 时序数据库 remote_write VictoriaMetrics Thanos 监控