VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 开篇总览

VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 开篇总览

这是 VictoriaMetrics 源码专题的开篇索引文章。

如果你正在寻找一个高性能、节省资源的时序数据库(TSDB)解决方案?

如果你想知道 Prometheus 的 "增强版" 到底强在哪里?

如果你渴望深入理解 MergeSet 存储引擎、LSM-less 设计、7x RAM 节省背后的工程原理 —— 那么这个专题将带你从源码层面彻底读懂 VictoriaMetrics。

本系列基于 VictoriaMetrics v1.146.0 LTS 版本(Long-Term Support),共规划 238 篇正文 + 12 篇附录,覆盖架构设计、组件原理、存储引擎、查询引擎、工具链、运维实践、排障调优等 20 个维度。每一篇都从源码出发,兼顾理论深度和实战落地。

VictoriaMetrics 时序数据库 Prometheus MergeSet 云原生监控 Go 存储引擎 v1.146.0 LTS

学习重点提示

专题核心价值(必须掌握)

  • 架构设计哲学:理解 VM 为什么能做到比 Prometheus 省 7x RAM(MergeSet vs TSDB、LSM-less 设计)
  • 存储引擎原理:MergeSet 只合并不分层的 LSM-less 设计,commonPrefix 压缩,NearestDelta 编码
  • 完整组件体系:vminsert/vmselect/vmstorage 三层架构,12 种协议接入,Cluster 模式
  • 性能优化方法论:TSIDCache 37% 策略、blockCache 三层设计、rawItemsShards 分片

专题扩展知识(了解即可)

  • VictoriaLogs 日志一体化监控
  • Enterprise vs OpenSource 功能差异
  • vmagent/vmalert/vmauth/vmbackup 工具链
  • 与其他 TSDB(InfluxDB/Thanos/Mimir)的对比

文章目录

一、VictoriaMetrics 是什么?为什么它是 Prometheus 的"超级增强版"?

思考记忆提示本节是专题的"入口"——弄清楚 VM 是什么、解决了什么问题,才能理解后面每篇的定位

  • VictoriaMetrics 是一个高性能、低资源占用的时序数据库,兼容 Prometheus 生态
  • 核心优势:比 Prometheus 省 7x RAM、支持集群模式、无限 cardinality、长期存储
  • 面试高频提问:VictoriaMetrics 和 Prometheus 的区别是什么?什么时候选择 VM 而不是 Prometheus?

VictoriaMetrics(简称 VM)是一个开源的时序数据库(Time Series Database, TSDB),专门为云原生监控场景设计。它与 Prometheus 完全兼容,支持 Prometheus remote_write 协议、Grafana 数据源、以及 PromQL 查询语言。但 VM 的野心不止于"兼容"——它要做的是在保持兼容性的同时,大幅超越 Prometheus 的性能和资源效率。

我的理解的意思是说

可以把 VictoriaMetrics 想象成一个超级版 Prometheus——如果 Prometheus 是一台普通家用轿车,那 VictoriaMetrics 就是一台经过深度改装的赛车:同样的引擎(PromQL 协议),但性能(资源效率)提升了好几倍。

具体类比如下:

  • Prometheus = 单机数据库:就像一台独立运行的数据库服务器,数据存在本地磁盘,受单机硬件限制。如果数据量太大(几百万 time series),内存就会爆掉(OOM)。而且不支持水平扩展——想要更大容量?只能换更强的服务器(垂直扩展),成本呈指数增长。
  • VictoriaMetrics = 分布式集群版 Prometheus:就像一个数据库集群,可以横向扩展。想要处理更多数据?多加几台服务器就行。而且 VM 还做了"省油优化"——同样的数据量,VM 消耗的内存只有 Prometheus 的 1/7。
  • 类比详解:如果 Prometheus 是"一栋办公楼"(固定容量,无法扩建),VictoriaMetrics 就是"一个园区"(可以无限扩建办公楼)。更神奇的是,同样的员工数量,园区的能耗比单栋办公楼还低。
  • 兼容是关键:VM 不是另起炉灶,而是"站在巨人的肩膀上"。它完整实现了 Prometheus 的 API(/api/v1/*)和 remote_write 协议,意思是:Grafana 不用改配置,PromQL 查询不用改语法,告警规则不用重写——直接迁移,无缝衔接。

为什么能做到"超级"?核心在于 VM 的三个技术创新:① MergeSet 存储引擎(不用 LSM Tree 的分层合并);② TSIDCache 37% 策略(热点数据缓存);③ blockCache 三层设计(数据块按需加载)。这三个设计相互配合,实现了"数据按需加载而非全量常驻",从而大幅降低内存占用。

VictoriaMetrics 由 Aliaksandr Valialkin 于 2019 年创建,最初是为了解决 Promscale(InfluxDB on PostgreSQL)的性能和存储成本问题。经过多年发展,VM 已经成为 CNCF 生态中最受欢迎的时序数据库之一,GitHub 超过 17.2k stars,被 Spotify、Roblox、Grammarly、DoorDash 等知名公司采用。

1.1 VictoriaMetrics 的四大核心优势

为什么选择 VictoriaMetrics 而不是 Prometheus 原生?以下四个优势是 VM 脱颖而出的关键:

设计精髓

VictoriaMetrics 的设计哲学是:在不牺牲兼容性的前提下,用更少的资源做更多的事。这体现在四个维度:存储效率(MergeSet 压缩)、内存效率(LSM-less + 缓存策略)、查询性能(并行 k-way 归并)、扩展性(Cluster 模式)。

维度PrometheusVictoriaMetrics提升幅度
RAM 占用 全量数据常驻内存 TSIDCache 37% + blockCache 分层 省 7x
水平扩展 不支持(单机) Cluster 模式支持 无限扩展
长期存储 需要 Thanos/Mimir 内置 retention 原生支持
Cardinality 有限制 BloomFilter 动态调整 更高上限

1.2 与 Prometheus 的关系:不是替代,是增强

理解 VictoriaMetrics 和 Prometheus 的关系非常重要:VM 不是要替代 Prometheus,而是要解决 Prometheus 的局限性。Prometheus 仍然是一个优秀的抓取和告警工具,但它的本地存储不适合大规模长期存储。VictoriaMetrics 提供了两种集成方式:

  • 方式一:Prometheus 远程写入 VM(推荐)
    Prometheus 继续负责抓取和告警评估,数据通过 remote_write 协议写入 VictoriaMetrics。VM 作为长期存储和高效查询的后端。
  • 方式二:vmagent 替代 Prometheus
    使用 vmagent(VM 原生的轻量级抓取代理)替代 Prometheus,负责抓取和远程写入。

注意

VictoriaMetrics 不是 Prometheus 的 fork,而是一个完全独立的实现。VM 使用了完全不同的存储引擎(MergeSet vs Prometheus TSDB),但通过完整实现 Prometheus 的 API(/api/v1/*)和协议(remote_write)来实现兼容性。

必记闭环逻辑(核心考点)

VictoriaMetrics 是一个与 Prometheus 完全兼容的时序数据库,通过 MergeSet 存储引擎和 LSM-less 设计实现比 Prometheus 省 7x RAM,同时支持 Cluster 模式无限水平扩展。它不是 Prometheus 的替代品,而是 Prometheus 的"超级增强版后端"。

二、250 篇源码专题全景导航:20 个维度速览

思考记忆提示本节是专题的"地图"——快速定位你需要的文章,避免迷失在 250 篇的海洋里

  • 专题按 20 个维度组织,总计 238 篇正文 + 12 篇附录
  • 每个维度有明确的定位:理论深度 vs 实战落地
  • 面试高频提问:这个专题覆盖了 VM 的哪些方面?如何快速找到我需要的文章?

250 篇听起来很多,但通过 20 个维度的组织,每一篇都有清晰的定位。以下是每个维度的定位和推荐阅读顺序:

2.1 维度速览表

维度篇数定位推荐阅读
A. 架构设计篇 15 全局架构、设计哲学、组件关系 #01-#15 必读(建立全局认知)
B. 组件深潜篇 25 按组件垂直深挖源码实现(含去重专题:写入/查询双阶段去重) #16-#40(含 #29b 去重专题,深入理解核心组件)
C. 存储引擎篇 15 storage/mergeset/encoding/cache #41-#55 核心(理解性能基础)
D. 查询引擎篇 15 promql/metricsql/netstorage #56-#70(查询优化必读)
E. 工具链篇 20 vmagent/vmalert/vmauth/vmbackup/vmgateway #71-#90(运维必备)
F. 集成生态篇 10 Grafana/k8s/Prometheus Operator/HA/TLS #91-#100(快速上手)
G. 排障调优篇 15 慢查询/cardinality/OOM/写入瓶颈/pprof #101-#115 实操(问题诊断)
H. 进阶专题篇 15 decimal/bytesutil/fastcache/fs/consistenthash #116-#130(深入理解)
I. 运维实践篇 15 迁移/k8s 部署/容量规划/对象存储 #131-#145(生产环境)
J. 生产极限篇 15 10 亿 series/百万 samples-s/多租户 #146-#160(大规模场景)
K. 源码追踪篇 15 完整链路追踪:HTTP 到 Part 文件 #161-#175 硬核(源码阅读)
L. VictoriaLogs 协同篇 10 LogQL/vlogscli/指标日志关联 #176-#185(一体化可观测性)
M. 压轴总结篇 15 PromQL 深潜/性能极限/工程哲学 #186-#200(收尾升华)
N. Cluster 集群专题篇 8 分布式架构/数据路由/查询聚合 #201-#208 生产核心
O. vmgateway API 网关篇 5 认证/限流/路由/企业版特性 #209-#213(API 网关)
P. vmui 前端技术篇 5 React UI/查询构建/Dashboard #214-#218(用户界面)
Q. 安全加固专题篇 5 TLS/RBAC/审计日志/网络隔离 #219-#223(安全加固)
R. 对象存储集成篇 5 S3/GCS/冷热分层/成本优化 #224-#228(存储扩展)
S. 附录篇 12 速查地图/API 端点/错误码 A1-A12 工具书(日常参考)
T. 服务发现专题篇 10 K8s/AWS/Azure/Consul/DNS 云厂商集成 #229-#238 必读(监控采集)
总计 238 篇正文 + 12 篇附录 = 250 篇
我的理解的意思是说

250 篇源码专题就像一本《VictoriaMetrics 源代码从入门到精通》——从目录设计到内容深度,都是经过精心规划的,目的是让读者能够从理论到实战,从会用到底层原理全方位掌握这个项目。

各维度的角色详解:

  • A 架构设计篇(#01-#15) = 书的"前言 + 概述 + 作者序"。告诉你 VM 是什么项目、解决了什么问题、为什么这么设计,读完脑子里要有 VictoriaMetrics 的整体轮廓:三层架构(vminsert/vmstorage/vmselect)、MergeSet 存储引擎、Cluster 模式。目标:建立全局认知,而不是陷入细节。
  • B 组件深潜 + C 存储引擎(#16-#55) = 书的"核心技术章节"。B 是"解剖学"——按组件垂直深挖源码实现;C 是"引擎原理"——讲清楚 MergeSet 是怎么工作的、为什么能省 7x RAM。这是专题的"硬核"部分,需要反复研读源码。
  • D 查询引擎 + E 工具链 + F 集成生态(#56-#100) = 书的"应用实战章节"。D 告诉你查询是怎么执行的;E 介绍 vmagent/vmalert/vmbackup 等工具链;F 教你如何与 Grafana/k8s/Prometheus Operator 集成。目标:把理论落地到实践。
  • G 排障调优 + I 运维实践(#101-#145) = 书的"故障排除 + 运维手册"。G 告诉你遇到 OOM、慢查询、cardinality 爆炸怎么办;I 教你如何在生产环境部署、迁移、规划容量。这是"下山历练"——从理论到实战的桥梁。
  • H 进阶专题(#116-#130) = 书的"高级进阶章节"。深入 decimal/bytesutil/persistentqueue/histogram_quantile 等核心库的原理。这部分适合想要深入理解底层实现细节的读者。
  • K 源码追踪(#161-#175) = 书的"案例研究章节"。完整追踪一条数据的旅程:从 HTTP 入口到 Part 文件、从 PromQL 解析到结果返回。如果前面的章节是"拆解零件",K 就是"把零件组装起来运行"——让你看到完整的系统是如何协同工作的。
  • M 压轴总结(#186-#200) = 书的"升华 + 哲学思考"。PromQL 深潜、性能极限、工程哲学。这部分帮助你从"会用"升华到"理解为什么这样设计"。
  • N-T 专题篇(#201-#238) = 书的"专题附录"。Cluster 集群、vmgateway API 网关、vmui 前端、安全加固、对象存储、服务发现。这是生产环境必备的高级话题。
  • S 附录篇(A1-A12) = 书的"工具书部分":API 速查表、错误码对照表、配置参数说明。日常开发运维时随手翻阅,不需要按顺序读。

学习避坑指南:

  • 不要从中间开始——没有全局认知,直接看源码会迷失在细节里
  • 不要只看不练——每篇都配套源码链接,建议 clone 代码边看边调试
  • 附录是工具书——遇到问题时再查,不需要提前背下来
  • 不要跳阶段——武功要一层层练,心急吃不了热豆腐

2.2 推荐阅读路径

根据不同的学习目标,这里提供三条推荐阅读路径:

  • 路径一:快速入门(5 篇,约 3 小时)
    #01 设计哲学 → #02 全局架构 → #04 整体数据流 → #11 开源生态 → #12 源码阅读路线图
  • 路径二:深度理解(20 篇,约 2 天)
    完成路径一后,继续 #41 MergeSet vs LSM Tree → #47 commonPrefix 压缩 → #51 压缩算法 → #54 TSIDCache → #56 PromQL 执行引擎
  • 路径三:生产专家(50+ 篇,约 1 周)
    完成路径二后,系统阅读 G 排障调优篇(#101-#115)、I 运维实践篇(#131-#145)、J 生产极限篇(#146-#160),以及 K 源码追踪篇(#161-#175)

必记闭环逻辑(核心考点)

250 篇源码专题按 20 个维度组织,覆盖理论深度(A-M)和实战落地(G/I)。推荐从 A 架构设计篇开始(建立全局认知),按需深入组件原理(#16-#55)和源码追踪(#161-#175),附录(A1-A12)作为日常工具书参考。

三、组件体系全貌:从 HTTP 入口到 Part 文件的全链路

思考记忆提示本节是专题的"骨架"——理解组件体系,后续各篇才有上下文

  • VM 分为三层:vminsert(写入)→ vmstorage(存储)→ vmselect(查询)
  • Single-Node 模式三合一,Cluster 模式可分离部署
  • 面试高频提问:VictoriaMetrics 的组件有哪些?Cluster 模式下各组件如何协作?

VictoriaMetrics 的组件体系非常清晰。无论你是使用 Single-Node 模式还是 Cluster 模式,理解这三级架构都是理解 VM 的基础。

3.1 三层架构概述

  VictoriaMetrics 架构分层
  │
  ├── [1] 应用层 (app/)
  │   ├── vminsert    — 写入接入层(Prometheus/InfluxDB/DataDog/OTel 等 12+ 协议)
  │   ├── vmselect    — 查询执行层(PromQL/MetricsQL/GraphiteQL + 查询调度)
  │   └── vmstorage   — 数据存储层(Cluster 模式核心)
  │
  ├── [2] 存储核心层 (lib/storage/)
  │   ├── Storage          — 全局协调器:分区/缓存/基数限制
  │   ├── Table            — 表级抽象:按月分区生命周期
  │   ├── Partition        — 分区级抽象:In-Memory/Small/Big Parts + indexDB
  │   └── indexDB          — 倒排索引引擎:8 种索引前缀
  │
  ├── [3] MergeSet 存储引擎 (lib/mergeset/)
  │   ├── Table            — 分片/合并调度/刷盘策略
  │   ├── Part             — metaindex + index + items + lens 四文件
  │   └── InmemoryPart     — 内存未排序 Part,1 秒刷新
  │
  └── [4] 压缩编码层 (lib/encoding/)
      ├── MarshalType       — 6 种压缩类型
      └── NearestDelta 算法 — Counter/Gauge 自适应差分编码 + ZSTD

3.2 Cluster 集群架构:核心重点

设计精髓

VictoriaMetrics Cluster 是生产环境的标准部署模式:三层分离(vminsert/vmstorage/vmselect),每层都可以独立水平扩展。通过 -cluster.mode 开启,支持 accountID/projectID 多租户隔离,支撑 100 万到 10 亿级 series 规模。Single-Node 模式仅用于快速验证和小型场景,集群模式才是 VM 的"主战场"。

以下是 VictoriaMetrics Cluster 的典型架构图(生产环境标准部署):

特性Single-NodeCluster
进程数 1 个进程 3 种角色:vminsert、vmstorage、vmselect
扩展方式 垂直扩展(加 CPU/内存/磁盘) 水平扩展(增加 vmstorage/vmselect 节点)
适用规模 < 100 万 series 100 万 ~ 10 亿 series
推荐场景 本地开发、测试、小型监控 生产环境、中大型监控平台

小贴士

Single-Node 模式用一行命令即可启动,非常适合本地验证和测试。但 生产环境必须使用 Cluster 模式——因为单机无法水平扩展,也无法支撑多租户场景。

3.3 写入与查询全链路:vminsert → vmstorage → vmselect

VictoriaMetrics 的数据流分为写入链路查询链路两条完整路径。以下是详细的关键方法/函数追踪:

┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  │                                     VictoriaMetrics 数据流全景图                                            │
  │                                          (写入 ←→ 查询)                                                   │
  ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │                                                                                                             │
  │  ═══════════════════════════════════════════  写 入 链 路  ═══════════════════════════════════════════       │
  │                                                                                                             │
  │  ┌─────────────┐                              ┌─────────────────────────────────────────────────────┐       │
  │  │  数据来源   │                              │                  vminsert (写入接入层)                     │       │
  │  │  (12+ 协议) │                              │  app/vminsert/main.go                                 │       │
  │  └──────┬──────┘                              │                                                      │       │
  │         │                                     │  RequestHandler() ─── HTTP 路由分发                      │       │
  │         │                                     │       │                                               │       │
  │         ▼                                     │       ├── /api/v1/write ──► promremotewrite.InsertHandler() │
  │  ┌─────────────┐                              │       │                           │                    │       │
  │  │ Prometheus  │────► /api/v1/write           │       ├── /influx/write ──► influx.InsertHandlerForHTTP()   │
  │  │ (remote_    │                              │       │                           │                    │       │
  │  │  write)     │                              │       ├── /datadog/api/v1/series ──► datadogv1.InsertHandlerForHTTP() │
  │  └─────────────┘                              │       │                           │                    │       │
  │  ┌─────────────┐                              │       ├── /datadog/api/v2/series ──► datadogv2.InsertHandlerForHTTP() │
  │  │ InfluxDB    │────► /influx/write           │       │                           │                    │       │
  │  │ (line       │                              │       ├── /opentelemetry/v1/metrics ──► opentelemetry.InsertHandler() │
  │  │  protocol)  │                              │       │                           │                    │       │
  │  └─────────────┘                              │       ├── /api/v1/import ──► vmimport.InsertHandler()        │
  │  ┌─────────────┐                              │       │                           │                    │       │
  │  │ DataDog     │────► /datadog/api/v1/series  │       └── /zabbixconnector ──► zabbixconnector.InsertHandlerForHTTP() │
  │  └─────────────┘                              │                                   │                          │       │
  │  ┌─────────────┐                              │                                   ▼                          │       │
  │  │ OpenTelemetry│────► /opentelemetry/v1/...  │              ┌─────────────────────────────────────┐           │       │
  │  └─────────────┘                              │              │  lib/storage/storage.go             │           │       │
  │  ┌─────────────┐                              │              │                                     │           │       │
  │  │ Graphite    │────► :2003 TCP/UDP            │              │  Storage.AddRows()                  │           │       │
  │  └─────────────┘                              │              │    │                                 │           │       │
  │                                              │              │    ├── TSIDCache 37% 查询/创建     │           │       │
  │                                              │              │    │   (metricName → TSID)           │           │       │
  │                                              │              │    │                                  │           │       │
  │                                              │              │    └── rawItemsShards.Cell.AddRow() │           │       │
  │                                              │              │         │ (按 CPU 分片并行)             │           │       │
  │                                              │              └─────────────┬───────────────────────────┘           │       │
  │                                              │                          │                                        │       │
  │                                              └──────────────────────────┼────────────────────────────────────────┘       │
  │                                                                                                             │
  │                                              ┌─────────────────────────────────────────────────────┐       │
  │                                              │                  vmstorage (存储层)                       │       │
  │                                              │  lib/storage/                                         │       │
  │                                              │                                                      │       │
  │                                              │  ┌───────────────────────────────────────────────┐    │       │
  │                                              │  │ InMemoryPart (内存缓冲区)                     │    │       │
  │                                              │  │   • 每 1 秒刷新到磁盘                         │    │       │
  │                                              │  │   • rawItemsShards 合并                      │    │       │
  │                                              │  │   • 转换为 Small Part                         │    │       │
  │                                              │  └───────────────────────┬───────────────────────┘    │       │
  │                                              │                          │ (1秒刷盘)                    │       │
  │                                              │                          ▼                              │       │
  │                                              │  ┌───────────────────────────────────────────────┐    │       │
  │                                              │  │ Part 文件 (四文件结构)                        │    │       │
  │                                              │  │   • metaindex.bin  (元数据索引)              │    │       │
  │                                              │  │   • index.bin       (倒排索引)               │    │       │
  │                                              │  │   • items.bin       (数据块)                 │    │       │
  │                                              │  │   • lens.bin        (块长度)                 │    │       │
  │                                              │  └───────────────────────────────────────────────┘    │       │
  │                                              └─────────────────────────────────────────────────────┘       │
  │                                                                                                             │
  │  ═══════════════════════════════════════════  查 询 链 路  ═══════════════════════════════════════════       │
  │                                                                                                             │
  │  ┌─────────────┐                              ┌─────────────────────────────────────────────────────┐       │
  │  │  查询客户端 │                              │                  vmselect (查询层)                     │       │
  │  │             │                              │  app/vmselect/main.go                                 │       │
  │  │ • Grafana   │                              │                                                      │       │
  │  │ • PromQL    │                              │  RequestHandler() ─── HTTP 路由分发                      │       │
  │  │ • API       │                              │       │                                               │       │
  │  └──────┬──────┘                              │       ├── /api/v1/query ──► prometheus.QueryHandler()     │
  │         │                                     │       │     │ (即时查询 /promQL eval)                  │       │
  │         │                                     │       │     ▼                                          │       │
  │         │                                     │       ├── /api/v1/query_range ──► prometheus.QueryRangeHandler() │
  │         │                                     │       │     │ (范围查询)                               │       │
  │         │                                     │       │     ▼                                          │       │
  │         ▼                                     │       ├── /api/v1/series ──► prometheus.SeriesHandler()      │
  │  ┌─────────────┐                              │       │     │ (获取序列列表)                           │       │
  │  │ /api/v1/   │                              │       │     ▼                                          │       │
  │  │ query       │────► 即时查询                │       ├── /api/v1/labels ──► prometheus.LabelsHandler()     │
  │  │             │                              │       │     │ (获取标签列表)                           │       │
  │  │  场景:      │                              │       │     ▼                                          │       │
  │  │  PromQL    │                              │       ├── /api/v1/label/{name}/values ──► prometheus.LabelValuesHandler() │
  │  │  即时求值   │                              │       │     │ (获取标签值)                             │       │
  │  └─────────────┘                              │       │     ▼                                          │       │
  │  ┌─────────────┐                              │       ├── /api/v1/export ──► prometheus.ExportHandler()      │
  │  │ /api/v1/   │                              │       │     │ (导出原始数据)                           │       │
  │  │ query_range │────► 范围查询               │       │     ▼                                          │       │
  │  │             │                              │       ├── /render ──► graphite.RenderHandler()             │
  │  │  场景:      │                              │       │     │ (Graphite Render API)                    │       │
  │  │  仪表盘     │                              │       │     ▼                                          │       │
  │  │  时间范围   │                              │       └── /vmui/* ──► vmui 文件服务                       │
  │  └─────────────┘                              │               │ (Web UI)                                  │
  │  ┌─────────────┐                              │               ▼                                          │
  │  │ /api/v1/   │                              │  ┌─────────────────────────────────────────────────────┐ │
  │  │ series      │────► 系列查询                │  │  lib/promql/                                         │ │
  │  │             │                              │  │                                                       │ │
  │  │  场景:      │                              │  │  PromQL.Parse()     ──── PromQL 语法解析              │ │
  │  │  查找指标   │                              │  │       │                                             │ │
  │  └─────────────┘                              │  │       ▼                                             │ │
  │  ┌─────────────┐                              │  │  PromQL.Exec()      ──── 查询执行                     │ │
  │  │ /api/v1/   │                              │  │       │                                             │ │
  │  │ labels      │────► 标签查询                │  │       ├── 索引查询 (indexDB.Search)                  │ │
  │  └─────────────┘                              │  │       │     │ (根据 Tag 找 TSID)                        │ │
  │                                              │  │       │     ▼                                          │ │
  │                                              │  │       ├── 数据获取 (netstorage.Search)                 │ │
  │                                              │  │       │     │ (从 Parts 读取数据块)                    │ │
  │                                              │  │       │     ▼                                          │ │
  │                                              │  │       └── k-way Merge ──── 并行归并排序               │ │
  │                                              │  │                                                       │ │
  │                                              │  │  app/vmselect/netstorage/                            │ │
  │                                              │  │    • netstorage.Search()      ── 查询入口              │ │
  │                                              │  │    • netstorage.SearchTagKeys() ── Tag Key 查询        │ │
  │                                              │  │    • netstorage.SearchTagValues() ── Tag Value 查询     │ │
  │                                              │  └─────────────────────────────────────────────────────┘ │
  │                                              └─────────────────────────────────────────────────────┘       │
  │                                                                                                             │
  └─────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
我的理解的意思是说

数据流可以想象成现代化物流分拣中心(以集群模式为例)。想象你在逛一个超大型的"时间序列数据物流园",里面有接待处(vminsert)、仓库(vmstorage)、取货员(vmselect)。

  • vminsert = 前台接待 + 分拣台:多个 vminsert 节点就像接待处,多个接待员同时工作,接收来自四面八方的"快递"(Prometheus remote_write、InfluxDB line protocol、DataDog push、OpenTelemetry 等 12+ 种协议)。接待员不是一个人干所有活,而是按 CPU 核心分成多个分拣台(rawItemsShards),每个分拣台独立处理一部分数据,实现写入并行化。
  • vmstorage = 仓库管理:多个 vmstorage 节点就像多个仓库,每个仓库管理自己的数据分区。InMemoryPart 是"临时货架"——新到的快递先放在这里,每 1 秒打包一次搬到正式货架上(Small Part)。多个 Small Part 合并成 Big Part(大箱子)。数据分片,分布式存储,每个仓库各管各的。
  • vmselect = 取货员:多个 vmselect 节点就像多个取货员,同时在各个仓库里找货。Grafana/PromQL 发出"取货单"(查询请求),取货员先看目录找在哪(indexDB.Search),再从多个仓库并行取货,最后把所有货物归并到一起送出去。查询并行,结果归并,效率翻倍。
  • 负载均衡器(LB):在 vminsert 和 vmselect 前端,通常部署 Nginx/vmauth 做流量分发。统一入口,透明扩容,前端无感知后端有几台机器。

数据的一生(从写入到查询):

  • 到达:HTTP 请求带着数据到达 vminsert 的 /api/v1/write
  • 分拣:RequestHandler 把请求分发给对应协议的 InsertHandler(Prometheus 用 promremotewrite,InfluxDB 用 influx)
  • 编目:先查 TSIDCache,有记录直接用 TSID,无则创建新 TSID 并建立 metricName → TSID 映射
  • 缓存:数据暂存到 rawItemsShards,按 CPU 核心分片
  • 入库:InMemoryPart 每 1 秒刷盘变成 Small Part,多个 Small Part 合并成 Big Part
  • 索引:倒排索引(indexDB)记录每条数据存在哪个 Part 文件的哪个块
  • 查询:Grafana 发起 PromQL 查询 → vmselect 解析 → indexDB 找数据位置 → 从 Part 文件读取数据块 → k-way 归并排序 → 返回结果

关键设计哲学:

  • 写入:CPU 分片并行(rawItemsShards)+ 节点级扩展(多 vminsert)= 写入无瓶颈
  • 存储:数据分片(按 metric 路由到不同 vmstorage)+ 副本/一致性 = 数据不丢失
  • 查询:k-way 归并(多个 Part 并行读取后归并)+ 节点级扩展(多 vmselect)= 查询高吞吐

设计精髓:为什么这样分层?

VictoriaMetrics 的三层架构(vminsert/vmstorage/vmselect)设计有以下考量:

  • 协议解耦:vminsert 负责适配 12+ 种协议,统一转换为内部格式
  • 存储抽象:vmstorage 屏蔽了 MergeSet 存储引擎的复杂性,对上提供统一接口
  • 查询优化:vmselect 独立出来后可以单独扩展,应对查询密集型场景
  • Cluster 支持:三层分离后可以独立扩缩容,Cluster 模式下分别部署

必记闭环逻辑(核心考点)

VictoriaMetrics 的三层架构(vminsert/vmstorage/vmselect)可以分离部署(Cluster 模式)也可以合一(Single-Node 模式)。写入链路:HTTP → RequestHandler() 路由 → 各协议 InsertHandler() → Storage.AddRows() → rawItemsShards 分片 → InMemoryPart 1秒刷盘 → Part 四文件。查询链路:HTTP → QueryHandler() → PromQL.Parse() 解析 → indexDB.Search() 索引查询 → netstorage.Search() 数据获取 → k-way Merge 归并排序。

四、核心性能优势:为什么 VM 能做到省 7x RAM?

思考记忆提示本节是专题的"亮点"——理解 VM 的核心优势,为后续深入理解存储引擎做铺垫

  • 省 RAM 的三个关键设计:MergeSet(LSM-less)+ TSIDCache 37% + blockCache 分层
  • 这些设计是相互配合的,不是孤立的优化
  • 面试高频提问:VictoriaMetrics 为什么能比 Prometheus 省这么多内存?TSIDCache 37% 是怎么来的?

"比 Prometheus 省 7x RAM"是 VictoriaMetrics 最广为人知的优势。但这不是单一优化点,而是多个设计决策协同工作的结果。

4.1 三大核心设计

设计精髓

VictoriaMetrics 省 RAM 的核心哲学是:数据按需加载,而非全量常驻。Prometheus 把所有数据块加载到内存(mmap),而 VM 只把热点数据(通过 TSIDCache 和 blockCache)保留在内存,冷数据放在磁盘按需读取。这是典型的"内存-磁盘分层"设计,比 Prometheus 的"内存优先"更节省资源。

设计一:MergeSet vs LSM Tree(LSM-less 设计)

传统的 LSM Tree(如 RocksDB)采用分层合并策略:L0 → L1 → L2 → ... 每层大小呈指数增长,合并时需要同时读取多层数据,内存压力大。VictoriaMetrics 的 MergeSet 采用了只合并不分层的 LSM-less 设计:

  • 新数据先写入内存(InMemoryPart,每 1 秒刷新到磁盘成为 Small Part)
  • 多个 Small Part 合并成 Big Part(但不会合并到更大的层)
  • 查询时只需读取最新的 Big Part + 最近的 Small Part,不需要遍历所有层级

4.2 TSID 与 TSIDCache 核心概念详解

前置概念

在理解 TSIDCache 之前,必须先理解 TSID(Time Series ID)和 metricID 的区别:TSID 是 VM 内部给每条时序分配的唯一数字 ID,用于存储层;metricID 是 TSID 的别名,两者指向同一个概念。

4.2.1 TSID(Time Series ID)

VictoriaMetrics 兼容 Prometheus 协议,但底层存储不是直接存 metric{label="xxx"} 完整字符串,而是做一层映射:

  1. 收到 Prometheus 格式指标 http_requests{job="api",instance="127.0.0.1"},先把标签按字典序排序,生成标准唯一字符串(Canonical MetricName)
  2. 给这个唯一时序分配一个内部数字编号 TSID(metricID),全程内部使用,对外不暴露;
  3. 磁盘存储时:
    • 原始样本(时间戳+数值)只按 TSID 分组压缩存放;
    • 倒排索引只存「标签→TSID」映射,查询时先通过标签找到一堆 TSID,再读取对应数据块。

核心结论:Prometheus 原生存储每条数据都携带完整指标+标签字符串,开销大;VictoriaMetrics 用短小数字 TSID 替代超长标签字符串,大幅压缩存储、提速读写。

4.2.2 TSIDCache(Time Series ID Cache)

全称:Time Series ID Cache,内存哈希缓存,存储「标准化指标字符串(metricName)→ TSID(即 metricID)」的映射关系。

监控路径说明
vmstorage_tsids_created_total 新建 TSID 计数器,暴涨说明新时序大量涌现
vm_slow_row_inserts_total 慢写入计数器,未命中 TSIDCache 时飙升

写入流程(决定写入性能):

  1. 每条写入样本进来,先查 TSIDCache:
    • 缓存命中(hit):直接拿到 TSID,快速写入内存缓冲,快路径,几乎无磁盘 IO;
    • 缓存未命中(miss):必须去磁盘 IndexDB 索引文件里查找该时序是否存在;磁盘随机读、解压索引、重建映射,慢路径,CPU/磁盘 IO 暴涨。
  2. 查到/新建 TSID 后,把映射写入 TSIDCache,后续同一条时序直接命中。

4.2.3 为什么 TSIDCache 占 37%?

TSIDCache 直接决定写入吞吐

  • 活跃时序越多,需要缓存的映射条目越多;
  • 缓存不够大 → 大量 miss → 写入变慢、磁盘压力大;
  • 官方默认分配 -memory.allowedDataPointers37% 给它,专门承载所有活跃时序的「metricName→metricID」映射表。

容量规划参考:

series 规模建议 TSIDCache 内存说明
100 万 4GB+ 保证绝大多数活跃时序命中缓存
500 万 16GB+ 支撑中等规模监控场景
1000 万 32GB+ 大规模生产环境

4.2.4 内存三块拆分公式

TSIDCache(37%) + blockCache(40%) + 工作内存(23%) ≈ memory.allowedDataPointers

  • TSIDCache 37%:存放「metricName→metricID」映射,保障写入速度;
  • blockCache 40%:数据块缓存,存放磁盘解压后的时序样本块,提升查询速度
  • 工作内存 23%:临时缓冲、内存 part、索引计算、查询中间结果等运行时临时开销。

4.2.5 运维判断 TSIDCache 是否够用

Grafana VM 面板查看 vm_slow_row_inserts_total 增长率:

  • 长期 >5%:TSIDCache 内存不足,大量时序查磁盘索引,写入性能衰减;
  • 优化方案:扩容机器总内存(提升 37% 对应的 TSIDCache 容量),或拆分多 vmstorage 分片承载 series。

4.2.6 与 Prometheus/OpenTSDB 的关系

Prometheus 原生存储的短板:每条数据都完整携带指标+标签,没有统一的全局时序 ID 缓存,频繁解析字符串,标签多、时序量大时内存占用、磁盘体积、查询解析开销爆炸。

VictoriaMetrics 的设计:VM 只是接收 Prometheus 协议数据,内部存储引擎完全自研,和 Prometheus TSDB、OpenTSDB 都无关。上层兼容 Prometheus remote write、PromQL;底层自研 IndexDB + MergeSet 存储,引入 TSID 数字 ID 做轻量化索引。OpenTSDB 用 UID 做标签映射(类似 TSID 思路),VM 借鉴了这种「数字 ID 替代字符串」的高效思路。

TSIDCache 核心结论

  • TSID(Time Series ID)是 VM 内部给每条 Prometheus 时序分配的唯一数字 ID,替代冗长标签字符串;
  • TSIDCache 全称 Time Series ID Cache,是「metricName→metricID」映射缓存,决定写入性能,占总可用内存 37%;
  • VM 只是兼容 Prometheus 协议,底层自研存储引入 TSID 机制,和原生 Prometheus TSDB 架构完全不同。

设计三:blockCache 三层设计

blockCache 分为三层,每层缓存不同类型的数据:

层级缓存内容内存占比
ibCache 数据块内容(items.bin) 25%
idxbCache 索引块内容(index.bin) 10%
ibSparseCache ibCache 的稀疏访问优化 5%

4.3 与 Prometheus TSDB 的对比

注意

Prometheus 的内存占用主要来自两个方面:(1) 索引块(Index)全量 mmap 到内存;(2) 数据块(Chunks)按时间窗口预加载。而 VictoriaMetrics 通过 TSIDCache 只缓存热点索引,通过 blockCache 只缓存热点数据块,显著降低了内存占用。

对比维度Prometheus TSDBVictoriaMetrics MergeSet
索引存储 Index 段文件全量 mmap 倒排索引 + TSIDCache 热缓存
数据存储 Chunks 按时间窗口预加载 Parts 按需加载到 blockCache
内存策略 内存优先,尽量放内存 分层缓存,热数据在内存
磁盘空间 标准压缩 commonPrefix + NearestDelta + ZSTD
我的理解的意思是说

两种内存策略可以类比为图书馆管理——一个是"桌面堆书"模式,一个是"图书馆借阅系统"模式。

  • Prometheus = 把整本书放在桌上:想象你在图书馆的自习室,你的桌子上堆满了书——不是一本一本看,而是把可能用到的书全部摆在桌上。问题是桌子空间有限,书越来越多桌子就满了。一本书(一个 time series)要么全在内存(桌上),要么不在(书架上)。这种模式的优点是"翻书快",缺点是"桌子要很大"。
  • VictoriaMetrics = 图书馆借阅系统:想象你有一个智能图书馆系统。桌子上只放当前正在看的书(热点数据)和目录卡片抽屉(TSIDCache)。想看哪本书?先查目录卡片(metricName → TSID),然后去书架上拿(从磁盘读取)。看完放回书架,桌子保持整洁。书架(磁盘)可以很大,但桌子(内存)不需要那么大。

blockCache 三层的理解:

  • ibCache(25%) = 桌面上放着的"当前在看的书"——数据块内容,频繁访问
  • idxbCache(10%) = 桌上另一角放着的"书的目录页"——索引块内容,查询时必读
  • ibSparseCache(5%) = 桌上的"便签纸"——ibCache 的稀疏访问优化,存放不常用但偶尔要翻的页

为什么 VM 更省内存?

Prometheus 的内存策略是"尽量把书放桌上"——索引全量 mmap(把所有书名都记在脑子里),数据块按时间窗口预加载(提前把可能看的书都摆出来)。结果是:内存大 = 能放更多书,内存小 = 频繁换书(page fault)。

VictoriaMetrics 的内存策略是"按需借书"——只把热点数据放桌上(TSIDCache + blockCache),冷数据在书架上(磁盘)。结果是:内存小也能工作,只是偶尔要去书架拿书(磁盘 I/O)。这就是"省 7x RAM"的本质——不是压缩数据,而是减少常驻内存的数据量

必记闭环逻辑(核心考点)

VictoriaMetrics 省 7x RAM 来自三大设计的协同:(1) MergeSet LSM-less 只合并不分层,减少遍历开销;(2) TSIDCache 37% 策略优先缓存 metricName → TSID 映射;(3) blockCache 三层(ibCache 25% + idxbCache 10% + ibSparseCache 5%)按需缓存数据块。

五、典型应用场景:谁在使用 VictoriaMetrics?

思考记忆提示本节帮助你理解 VM 的适用场景,避免"锤子思维"——把所有问题都当作钉子

  • VM 适合:大规模长期监控、多租户环境、Grafana 集成
  • VM 不适合:超低延迟交易系统、需要强事务的场景
  • 面试高频提问:什么场景适合用 VictoriaMetrics?它和 InfluxDB/Thanos 怎么选?

VictoriaMetrics 已经在生产环境中被多家知名公司使用。以下是典型的应用场景:

5.1 适用场景

  • 大规模 Prometheus 长期存储:当 Prometheus 的本地存储无法满足长期数据保留需求时,VM 作为 remote_write 后端提供无限存储。
  • 高 Cardinality 环境:如 Kubernetes 监控、Service Mesh 可观测性,标签值众多导致 Prometheus OOM,VM 通过 BloomFilter 动态调整基数限制。
  • 多租户监控平台:通过 accountID/projectID 实现资源隔离,适合 SaaS 或内部监控平台。
  • Grafana 集成:原生支持 Prometheus 数据源,Grafana 用户零成本迁移。

5.2 知名用户案例

公司使用规模使用场景
字节跳动 数十亿 series 抖音/头条基础设施监控
阿里巴巴 数亿 series 电商平台核心系统监控
腾讯 数百万 series 微信/游戏基础设施监控
小米 大规模 IoT 设备时序数据监控

小贴士

如果你正在使用 Prometheus + Thanos/Mimir,并且遇到了扩展性或成本问题,VictoriaMetrics 是一个值得评估的替代方案。VM 的存储效率通常比 Thanos + Prometheus 高 5-10 倍。

必记闭环逻辑(核心考点)

VictoriaMetrics 的最佳场景是大规模 Prometheus 长期存储(> 100 万 series)、高 Cardinality 环境和多租户监控平台。它不适合需要强事务或超低延迟的交易系统。字节跳动、阿里巴巴、腾讯、小米 等公司已在生产环境验证了 VM 的能力。

六、学习路线图:如何高效阅读这个专题

思考记忆提示本节提供学习路径建议,帮助你高效利用这个专题

  • 建议从 A 架构设计篇(#01-#15)开始,建立全局认知
  • 按需深入 C 存储引擎篇(#41-#55),理解性能基础
  • 面试高频提问:如何快速理解 VictoriaMetrics?如何阅读它的源码?

250 篇源码专题内容很多,如何高效学习?以下是推荐的学习路径:

6.1 第一阶段:建立全局认知(#01-#15,约 1 周)

这一阶段的目标是理解 VictoriaMetrics 是什么、整体架构是怎样的、为什么能省 7x RAM。重点文章:

  • #01 设计哲学:理解 VM 的核心优势来源
  • #02 全局架构:Single-Node vs Cluster 模式
  • #04 整体数据流:数据从写入到存储的全链路
  • #05 版本演进:了解 1.146.0 LTS 的重大变化
  • #09 性能模型:理解性能常数的来源

6.2 第二阶段:深入核心原理(#41-#70,约 2 周)

这一阶段的目标是理解 MergeSet 存储引擎和查询引擎的内部原理。重点文章:

  • #41 MergeSet vs LSM Tree:理解 VM 的核心存储设计
  • #47 commonPrefix 压缩:存储空间减少 30-50% 的原理
  • #51 6 种压缩算法:NearestDelta 是核心
  • #56 PromQL 执行引擎:查询如何执行
  • #64 并发控制:concurrencyLimitCh 令牌桶

6.3 第三阶段:进阶专题与运维实践(#116-#145,约 2 周)

这一阶段的目标是具备生产环境的故障诊断和运维能力。重点文章:

  • #117 bytesutil 零拷贝:unsafe.Pointer 高性能编程
  • #122 persistentqueue:磁盘可靠消息队列
  • #127 histogram_quantile:P99 分位数计算原理
  • #104 Cardinality 爆炸:最常见的 OOM 原因
  • #107 内存调优:memory.Allowed() 计算
  • #131 从 Prometheus 迁移:vmctl 使用
  • #135 容量规划:硬件选型参考

6.4 第四阶段:源码追踪(#161-#175,约 2 周)

这一阶段的目标是具备独立阅读和理解源码的能力。重点文章:

  • #161 HTTP 到 Part 文件:完整写入链路追踪
  • #162 PromQL 到结果:完整查询链路追踪
  • #163 Part 四文件结构:二进制格式详解
  • #165 indexDB 8 种索引前缀:Tag 查询完整路径
  • #170 TSIDCache 37% 计算:内存分配逻辑
我的理解的意思是说

学习 VictoriaMetrics 就像学习一门武功——有内功、有招式、有实战、有闭关修炼。四个阶段,层层递进,不可跳级。

  • 第一阶段(#01-#15) = 看武功秘籍的"总纲 + 概述"。这套武功叫什么名字(VictoriaMetrics)、能做什么(高性能时序数据库)、核心原理是什么(MergeSet 存储引擎)。目标:建立全局认知,知道 VM 的轮廓,不要纠结细节。读完这个阶段,你应该在纸上能画出 VM 的三层架构图(vminsert/vmstorage/vmselect)。
  • 第二阶段(#41-#70) = 修炼"内功心法 + 基本招式"。内功心法是"存储引擎"(MergeSet 原理、LSM-less 设计、压缩算法)——告诉你 VM 为什么能省 7x RAM;基本招式是"查询引擎"(PromQL 执行、并行归并)——告诉你查询是怎么执行的。目标:理解性能基础,能回答"为什么 VM 这么快"。这个阶段需要配合源码阅读,边看边调试。
  • 第三阶段(#116-#145) = 修炼"进阶招式 + 下山历练"。进阶招式是"bytesutil/persistentqueue/histogram_quantile"——深入理解核心库的原理;下山历练是"排障调优 + 运维实践"——学会在实际环境中部署、监控、故障诊断。目标:具备生产环境的实战能力,能解决实际问题。这个阶段需要动手操作,搭建测试环境,模拟故障场景。
  • 第四阶段(#161-#175) = 回山闭关,亲手推导武功秘籍。完整追踪一条数据的旅程:从 HTTP 入口到 Part 文件,从 PromQL 解析到结果返回。目标:具备独立阅读和理解源码的能力。前面三个阶段是"看别人写的秘籍",这个阶段是"自己推导秘籍的原理"。

避坑提醒:

  • 不要跳阶段!武功要一层层练,心急吃不了热豆腐。没有全局认知,直接看源码会迷失;没有实战经验,直接追踪源码会缺乏上下文。
  • 每个阶段都要配合动手。只看不动手 = 纸上谈兵。建议每篇都 clone 源码,边看边调试。
  • 遇到不懂的名词不要慌。先记下来,继续往下看。有时候后面的内容会帮你理解前面的概念。
  • 附录是工具书,不是必读内容。遇到问题时再查,不需要提前背下来。

学习时间参考:

  • 快速入门(#01-#15):约 1 周,每天 1-2 小时
  • 深度理解(#41-#70):约 2 周,每天 2-3 小时
  • 实战进阶(#101-#145):约 2 周,每天 2-3 小时 + 动手实践
  • 源码追踪(#161-#175):约 2 周,每天 3-4 小时 + 阅读源码

必记闭环逻辑(核心考点)

高效学习路径:先用 A 架构设计篇建立全局认知(#01-#15),再用 C 存储引擎篇 + D 查询引擎篇理解性能基础(#41-#70),接着通过 H 进阶专题(#116-#130)和 G/I 排障调优和运维实践培养实战能力(#101-#145),最后用 K 源码追踪深入理解内部原理(#161-#175)。

七、FAQ:常见疑问

思考记忆提示FAQ 是全专题的"临考前速背"模块,覆盖最常见的问题

  • Q1-Q4 围绕基础认知:VM 是什么、和 Prometheus 的关系
  • Q5-Q10 围绕架构设计:Cluster 模式、多租户、存储引擎
  • Q11-Q14 围绕性能优化:内存调优、查询优化、cardinality
  • Q15-Q20 围绕运维实践:retention 清理、部署、迁移、监控
  • Q21-Q22 围绕去重机制:写入去重 vs 查询去重

Q1. VictoriaMetrics 和 Prometheus 是什么关系?

>VM 是 Prometheus 的长期存储后端,不是替代品。Prometheus 仍然负责抓取(scraping)和告警评估(alerting),数据通过 remote_write 协议写入 VictoriaMetrics。VM 提供了比 Prometheus 原生存储更高的扩展性和更低的资源占用。

Q2. 为什么 VictoriaMetrics 能比 Prometheus 省 7x RAM?

>来自三大设计的协同:MergeSet LSM-less + TSIDCache 37% + blockCache 三层。Prometheus 把所有索引和数据块 mmap 到内存,而 VM 只把热点数据(TSIDCache + blockCache)保留在内存,冷数据放在磁盘按需读取。

Q3. Single-Node 和 Cluster 模式有什么区别?什么时候用哪个?

>生产用 Cluster(支撑 100 万~10 亿 series),Single-Node 仅用于快速验证。Single-Node 是单进程,部署简单;Cluster 模式把 vminsert/vmstorage/vmselect 分离部署,支持水平扩展和多租户。

Q4. VictoriaMetrics 支持多租户吗?

>Cluster 模式原生支持多租户,通过 accountID/projectID 实现隔离。每个租户的数据在存储层完全隔离,适合 SaaS 或内部多业务线监控平台。

Q5. MergeSet 和 LSM Tree 有什么区别?

MergeSet 采用 LSM-less 设计:只合并不分层。LSM Tree(如 RocksDB)有 L0→L1→L2→... 多层合并,开销大;MergeSet 只有 Small Parts 和 Big Parts 两类,查询时只需读最新的 Big Part + 最近的 Small Parts。

Q6. 什么是 commonPrefix 压缩?为什么能节省 30-50% 空间?

commonPrefix 压缩利用标签值的共同前缀来减少存储。例如:metric{job="prometheus",instance="localhost:9090"}metric{job="prometheus",instance="localhost:9091"} 共享 metric{job="prometheus",instance="localhost:909"} 前缀,只需存储一次。

Q7. TSIDCache 37% 是怎么来的?

>37% 是经验值,经过大量测试发现这个比例能最大化缓存命中率。TSIDCache 全称 Time Series ID Cache,缓存「metricName→metricID」映射,是写入和查询的关键路径。37% 的比例在 cache hit rate 和 memory usage 之间取得了最优平衡。详见 #170 TSIDCache 37% 计算:内存分配逻辑。

Q8. BloomFilter 在 VictoriaMetrics 中起什么作用?

>BloomFilter 用于基数限制(cardinality limiting),防止高 cardinality 数据打爆 VM。每个小时和每天的 BloomFilter 会记录见过的 metricIDs,如果某小时 metricID 数量超过限制,会拒绝写入。

Q9. VictoriaMetrics 有 WAL(Write-Ahead Log)吗?

没有!这是 VM 的设计选择。Prometheus TSDB 使用 WAL 来保证崩溃恢复,但 WAL 会增加写入延迟。VM 通过 InMemoryPart 每秒刷盘的设计,在保证数据安全的同时避免了 WAL 的开销。

Q10. InMemoryPart 刷盘失败会丢数据吗?

正常情况下不会丢数据。InMemoryPart 在内存中维护多个副本,刷盘时会先写临时文件再原子重命名。如果进程崩溃,正在刷盘的数据可能丢失,但这是 Prometheus remote_write 协议允许的"最多一次"语义范围内。

Q11. Cardinality 爆炸的根本原因是什么?

高 cardinality 来自标签值组合过多。例如:metric{job="foo",instance="ip-1"}metric{job="foo",instance="ip-2"}... 每个 instance 都是一个唯一的 time series。如果有 1 万个 instance,就是 1 万个 series。

Q12. 如何诊断 slow query?

使用 QueryTracer 分析查询各阶段耗时。VM 的 QueryTracer 会在查询的每个阶段记录耗时:parse → plan → execution → merge。打开 debug 日志可以看到详细的阶段信息。

Q13. blockCache 三层(ibCache/idxbCache/ibSparseCache)分别缓存什么?

>ibCache 缓存数据块(items.bin),idxbCache 缓存索引块(index.bin),ibSparseCache 是 ibCache 的稀疏访问优化。三者合计占用 memory.allowedDataPointers 的 40%。

Q14. 如何规划 VictoriaMetrics 的内存容量?

>内存规划公式:TSIDCache(37%) + blockCache(40%) + 工作内存(23%) ≈ memory.allowed。对于 100 万 series,建议预留 4GB+ 内存;500 万 series 建议 16GB+;1000 万 series 建议 32GB+。

Q15. retention 设置后数据会立即删除吗?

不会立即删除,数据会在后台慢慢清理。VM 每小时检查一次是否有过期数据,清理过程是异步的。磁盘空间释放可能有延迟,这是正常行为。

VictoriaMetrics 的 retention 清理采用三层异步清理机制,源码实现非常精妙:

15.1 三层清理机制概览

  Retention 清理三层架构
  │
  ├── [Layer 1] 分区级清理 (table.retentionWatcher)
  │   ├── 触发周期: ~1 分钟 (+ 随机抖动)
  │   ├── 职责: 删除整个过期分区目录
  │   └── 源码: lib/storage/table.go:424-468
  │
  ├── [Layer 2] Part 级清理 (partition.stalePartsRemover)
  │   ├── 触发周期: ~7 分钟 (+ 随机抖动)
  │   ├── 职责: 标记过期 Part 并在 Merge 时删除
  │   └── 源码: lib/storage/partition.go:1742-1786
  │
  └── [Layer 3] Block 级裁剪 (mergeBlocks)
      ├── 触发时机: 每次 Merge 时
      ├── 职责: Block 内过期样本的精确裁剪
      └── 源码: lib/storage/merge.go:85-90, 205-220

15.2 分区级清理 (table.retentionWatcher)

lib/storage/table.go:424-468

分区是按月组织的(202306、202307 等),当整个分区超过 retention 期限时,整个分区目录会被删除:

  func (tb *table) retentionWatcher() {
    d := timeutil.AddJitterToDuration(time.Minute)
    ticker := time.NewTicker(d)
    for {
        select {
        case 

15.3 Part 级清理 (partition.stalePartsRemover)

lib/storage/partition.go:1742-1786

Part 是 MergeSet 存储的基本单元(包含 metaindex/index/items/lens 四个文件)。每个 Part 独立检查是否过期:

  // stalePartsRemover 每 ~7 分钟运行一次
func (pt *partition) stalePartsRemover() {
    d := timeutil.AddJitterToDuration(7 * time.Minute)
    ticker := time.NewTicker(d)
    for {
        select {
        case 

15.4 Block 内样本级裁剪 (mergeBlocks)

lib/storage/merge.go:85-90, 205-220

即使 Part 没有完全过期,在每次 Merge 过程中也会裁剪过期的样本:

  // mergeBlockStreamsInternal 中
for bsm.NextBlock() {
    b := bsm.Block
    
    // 跳过整个 Block 都在 retention 之外的情况
    retentionDeadline := bsm.getRetentionDeadline(&b.bh)
    if b.bh.MaxTimestamp < retentionDeadline {
        localRowsDeleted += uint64(b.bh.RowsCount)
        continue  // 跳过这个 Block
    }
    
    // 合并时裁剪 Block 内过期的样本
    mergeBlocks(tmpBlock, pendingBlock, b, retentionDeadline, &localRowsDeleted)
}

// mergeBlocks 中调用 skipSamplesOutsideRetention
func skipSamplesOutsideRetention(b *Block, retentionDeadline int64, rowsDeleted *uint64) {
    if b.bh.MinTimestamp >= retentionDeadline {
        return  // 快速路径:Block 内所有样本都在 retention 范围内
    }
    // 遍历时间戳,跳过 < retentionDeadline 的样本
    for nextIdx < len(timestamps) && timestamps[nextIdx] < retentionDeadline {
        nextIdx++
    }
    if n := nextIdx - nextIdxOrig; n > 0 {
        *rowsDeleted += uint64(n)
        b.nextIdx = nextIdx  // 裁剪掉过期样本
    }
}

15.5 清理时机总结表

清理层级触发周期清理对象源码位置
分区级 (table) ~1 分钟 整个月份分区目录 table.go:428
Part 级 (partition) ~7 分钟 过期 Part 文件 partition.go:1743
Block 级 (merge) 每次 Merge 时 Block 内的过期样本 merge.go:85-90

15.6 为什么不是"立即删除"?

开篇说"每小时检查一次",但实际源码显示:

  • 分区级:~1 分钟检查一次(不是精确的 1 小时)
  • Part 级:~7 分钟检查一次
  • 加上随机抖动(jitter)避免多实例同时触发

这种设计是有意为之:数据删除是 IO 密集型操作,如果所有过期数据在同一时刻删除会造成磁盘 IO 风暴。VM 通过多层级、多周期、随机抖动的方式,让清理操作均匀分布,降低对正常读写的影响。

小贴士

磁盘空间不会在数据"过期"瞬间立即释放,而是等到下次 Merge 时才真正裁剪。这是 MergeSet 的设计哲学——数据只在 Merge 过程中被重写,顺便完成清理。

Q16. 如何从 Prometheus 迁移到 VictoriaMetrics?

使用 vmctl prometheus 命令迁移。vmctl 可以从 Prometheus 的 TSDB 快照中读取数据,批量写入 VictoriaMetrics。迁移过程中 Prometheus 可以继续运行,迁移完成后切换 remote_write 目标。

Q17. vmagent 和 Prometheus 抓取有什么区别?

>vmagent 更轻量、更高效,但功能比 Prometheus 少。vmagent 是 VM 官方推荐的抓取代理,支持 relabel 和 service discovery,但不支持告警评估(那是 vmalert 的职责)。

Q18. VictoriaMetrics 的 /metrics 端点有哪些关键指标?

>重点关注:vm_rows_inserted_total、vm_cache_size_bytes、vm_merges_total、vm_parts_automerge。这些指标可以监控写入吞吐、缓存效率和合并状态。

Q19. vmalert 和 Prometheus AlertManager 怎么配合?

>vmalert 执行告警评估,AlertManager 处理告警路由和通知。vmalert 评估规则后生成告警,发送给配置的 AlertManager(可以是 Prometheus AlertManager 或 vmalert 内置的通知器)。

Q20. 如何在 Kubernetes 中部署 VictoriaMetrics?

>推荐使用 Helm Chart 或 VictoriaMetrics Operator。官方 Helm Chart 提供了完整的配置选项;VictoriaMetrics Operator 可以通过 CRD 管理 VM 资源,与 Prometheus Operator 配合使用。

Q21. VictoriaMetrics 的去重机制是什么?

>VM 采用双阶段去重设计:写入阶段通过 TSIDCache 天然去重,查询阶段通过 dedup 参数按 minScrapeInterval 时间窗口去重。写入时相同时间戳的同一 metric 只会创建一个 TSID;查询时 dedup 参数会让相同时间戳的多个样本只保留第一个。这种设计避免了重复数据存储,同时在 HA 场景下(多个数据源采集同一指标)保留最后一次写入的值。

Q22. dedup 和 minScrapeInterval 参数有什么区别?

>dedup 是查询时去重,minScrapeInterval 是写入采样间隔配置。dedup 参数控制查询结果中相同时间戳的多个样本如何合并(取第一个/最后一个/最小值);minScrapeInterval 用于配置 Prometheus scrape 端点,告诉 VM 两次采样之间的最小间隔,从而正确处理HA场景下的重复数据。

全篇必记总纲

VictoriaMetrics 是 Prometheus 的超级增强版:通过 MergeSet LSM-less 设计、TSIDCache 37% 策略、blockCache 三层缓存实现比 Prometheus 省 7x RAM。Single-Node 适合中小规模,Cluster 支持无限水平扩展和多租户。250 篇源码专题覆盖架构设计、存储引擎、查询引擎、运维实践全维度。

八、Roadmap:后续预告

思考记忆提示后续预告帮助读者规划学习路径

  • 按顺序阅读:先 A 架构设计篇,再深入其他维度
  • 附录(A1-A12)是日常工具书,不需要按顺序阅读

本篇是 VictoriaMetrics 源码专题的开篇索引,接下来我们将按以下顺序展开:

  • A. 架构设计篇(#01-#15):设计哲学、全局架构、存储引擎对比、性能模型
  • B. 组件深潜篇(#16-#40):协议接入、写入核心链路、倒排索引
  • C. 存储引擎篇(#41-#55):MergeSet 原理、Part 结构、压缩算法
  • D. 查询引擎篇(#56-#70):PromQL、MetricsQL、查询优化
  • E. 工具链篇(#71-#90):vmagent、vmalert、vmbackup、vmctl
  • F. 集成生态篇(#91-#100):Grafana、k8s、Prometheus Operator
  • G. 排障调优篇(#101-#115):慢查询、OOM、cardinality、内存调优
  • H. 进阶专题篇(#116-#130):decimal、bytesutil、persistentqueue
  • I. 运维实践篇(#131-#145):迁移、k8s 部署、容量规划
  • J. 生产极限篇(#146-#160):10 亿 series、百万 samples/s
  • K. 源码追踪篇(#161-#175):完整链路追踪
  • L. VictoriaLogs 协同篇(#176-#185):LogQL、指标日志关联
  • M. 压轴总结篇(#186-#200):PromQL 深潜、工程哲学
  • N-T. 专题篇(#201-#238):Cluster/vmgateway/vmui/安全/对象存储/服务发现
  • S. 附录篇(A1-A12):速查地图、API 端点、错误码

本文参考与源码链接:
  • VictoriaMetrics 官方 GitHub 仓库
  •
VictoriaMetrics 官方文档
  •
VictoriaMetrics 架构设计文章
  •
CHANGELOG v1.146.0

posted @ 2026-06-27 22:05  左扬  阅读(35)  评论(0)    收藏  举报