VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 写入吞吐/查询延迟/内存占用的数学模型
VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 写入吞吐/查询延迟/内存占用的数学模型
当你需要评估 VictoriaMetrics 是否能满足业务需求时,
-
-
- 如何量化其性能?
- 写入吞吐量能达到多少?
- 查询延迟的 P99 是多少?
- 内存占用和时间序列数量是什么关系?
-
理解了性能模型,你才能做出正确的容量规划。
读完本篇,你应该能回答:VictoriaMetrics 的写入吞吐模型是什么?查询延迟如何计算?内存占用和时间序列数量的关系是什么?硬件配置如何影响性能?
VictoriaMetrics 性能模型 容量规划 写入吞吐 查询延迟 内存模型 v1.146.0
学习重点提示 — 建议先通读全文,再重点回顾标注内容
重点掌握(必须)
- 写入吞吐模型:CPU 核数与写入速率的关系
- 查询延迟模型:Part 数量与查询时间的关系
- 内存占用模型:TSIDCache 37% 策略与内存需求
- 容量规划公式:实际部署的计算方法
次重点(了解即可)
- 压缩率的影响因素
- 网络带宽的影响
- 磁盘 I/O 的影响
文章目录
一、问题的起点:为什么需要性能模型?
思考记忆提示 — 性能模型是容量规划的基础——没有模型就像盲人摸象
- 性能模型帮助 预测系统行为
- 性能模型帮助 容量规划
- 性能模型帮助 瓶颈定位
- 面试高频提问:如何评估一个系统的性能上限?
在生产环境中,容量规划是至关重要的工作。如果配置不足,会导致性能下降甚至 OOM;如果配置过剩,会造成资源浪费。
VictoriaMetrics 官方提供了基准测试数据,但理解这些数据背后的原理,才能更好地应用它们。
VM 性能模型的核心参数,可以直接从源码里读出来。我们不看任何类比,直接看关键文件中的常量定义,就能理解写入吞吐、查询延迟、内存占用的数学关系:
源码视角一:写入吞吐取决于 CPU 核数和 rawRowsShards 分片数
读 lib/storage/partition.go 第 46 行和第 484-500 行,会发现:
- rawRowsShardsPerPartition = cgroup.AvailableCPUs() — 分片数默认等于 CPU 核数,实现并行写入
- 每个 rawRowsShard 有独立的锁,减少锁竞争
- 写入吞吐 ≈ CPU核数 × 每核处理能力
源码视角二:Part 合并由 defaultPartsToMerge = 15 控制
读 lib/storage/partition.go 第 41 行:
const defaultPartsToMerge = 15
if len(rrss.rowssToFlush) >= defaultPartsToMerge {
// 触发合并
}
当一个分区的 Part 数量达到 15 时,自动触发合并。这就是"Part 数量 10-50 为正常"的底层原因。
源码视角三:缓存大小由 memory.Allowed() 按比例分配
读 lib/storage/storage.go 第 338-368 行:
func getTSIDCacheSize() int {
// TSID 缓存 = memory.Allowed() × 37%
return int(float64(memory.Allowed()) * 0.37)
}
func getMetricNamesStatsCacheSize() int {
// MetricNamesStats 缓存 = memory.Allowed() × 1%
return memory.Allowed() / 100
}
func getMetricNameCacheSize() int {
// MetricName 缓存 = memory.Allowed() × 10%
return memory.Allowed() / 10
}
三种缓存的比例不同:TSID 37%、MetricName 10%、MetricNamesStats 1%。这就是内存占用模型的底层实现。
源码视角总结:3 个关键常数
- 1 个分片参数:rawRowsShardsPerPartition = cgroup.AvailableCPUs()
- 1 个合并参数:defaultPartsToMerge = 15
- 1 个缓存参数:getTSIDCacheSize() = memory.Allowed() × 37%
避坑提醒(源码视角):
- 不要以为有单一的 workingsetcacheSize 参数:实际上有多个独立缓存,各自大小由不同函数控制
- 不要以为 37% 是所有缓存的总比例:37% 只是 TSID 缓存一个,MetricName 是 10%,MetricNamesStats 是 1%
- 不要以为 -defaultPartsToMerge 存在:实测是 Go 代码中的常量,不是命令行参数
1.1 VictoriaMetrics 官方基准测试
以下是 VictoriaMetrics 官方公布的基准测试数据(Single-Node 模式):
| 指标 | 基准测试结果 | 测试条件 |
|---|---|---|
| 写入吞吐 | ~500K samples/s | 8 核 CPU,SSD |
| 查询 P99 延迟 | <50ms | 100 万 series,随机查询 |
| 内存占用 | Prometheus 的 1/7 | 相同数据量,相同查询模式 |
| 压缩率 | 10x-50x | 取决于数据特征 |
注意
基准测试数据是在特定硬件和数据集条件下得出的,实际生产环境可能有所不同。以下公式是基于基准测试数据推导的经验公式,用于容量规划参考。
二、写入吞吐模型:CPU 核数与写入速率
思考记忆提示 — 写入吞吐主要由 CPU 决定——Goroutine 池设计让写入近乎线性扩展
- 写入吞吐与 CPU 核数 近似线性关系
- rawRowsShards 分片实现了并行写入
- 面试高频提问:VM 的写入性能如何随 CPU 核数扩展?
2.1 写入吞吐公式
VictoriaMetrics 的写入吞吐可以表示为:
写入吞吐公式:
T_write = N_cpu × C_write × E_parallel × F_protocol
其中:
T_write - 写入吞吐(samples/s)
N_cpu - CPU 核数
C_write - 每核每秒处理样本数(常数,~50K samples/s per core)
E_parallel - 并行效率因子(0.7-0.9,经验值)
F_protocol - 协议解析效率(0.8-0.95,取决于协议复杂度)
简化公式:
T_write ≈ N_cpu × 50K × 0.8
≈ N_cpu × 40K samples/s
示例:
4 核 → ~160K samples/s
8 核 → ~320K samples/s
16 核 → ~640K samples/s
32 核 → ~1.28M samples/s
2.2 影响写入吞吐的因素
以下是影响写入吞吐的关键因素:
| 因素 | 影响 | 说明 |
|---|---|---|
| CPU 核数 | 正相关 | 更多核数 = 更高吞吐(近乎线性) |
| 磁盘 I/O | 瓶颈 | SSD 建议;HDD 会限制吞吐 |
| 数据复杂度 | 负相关 | 标签越多、metric name 越长,解析越慢 |
| 网络带宽 | 瓶颈 | 10Gbps 网络下,1Gbit 约等于 125MB/s |
| 压缩开销 | 负相关 | NearestDelta + ZSTD 需要额外 CPU |
2.3 写入路径的性能分析
写入流程中各步骤的性能开销取决于数据特征和硬件配置,源码在 lib/storage/partition.go 中实现。关键性能参数:
- rawRowsShards 分片数:等于 CPU 核数(lib/storage/partition.go 第 46 行),减少锁竞争
- 合并阈值:Part 数量达到 15 时触发合并(第 41 行 defaultPartsToMerge = 15)
- 内存分配:每条写入记录涉及 TSID 查找、索引更新、数据块写入
小贴士 — 性能优化方向
- 使用 NVMe SSD(加速 indexDB 写入)
- 增加 CPU 核数(提升并行处理能力)
- 避免高基数标签(减少索引写入开销)
三、查询延迟模型:Part 数量与扫描时间
思考记忆提示 — 查询延迟主要由 Part 数量决定——合并策略是控制延迟的关键
- 查询延迟与 Part 数量 近似线性关系
- blockCache 命中率显著影响缓存命中延迟
- 面试高频提问:为什么 VM 的查询延迟比 Prometheus 低?
3.1 查询延迟公式
查询延迟公式:
T_query = T_index + T_scan + T_merge
其中:
T_index - 索引查询时间
= f(series_count) × (1 - cache_hit_rate)
≈ N_series × 100ns × (1 - cache_hit_rate)
T_scan - 数据扫描时间
= N_parts × T_part_scan
= N_parts × (data_size_per_part / disk_throughput)
T_merge - 结果归并时间
= O(N_parts × log(N_parts))
简化公式(无缓存命中):
T_query ≈ N_parts × 1ms + N_series × 100ns
示例:
10 个 Part → ~10-20ms
50 个 Part → ~50-100ms
100 个 Part → ~100-200ms
3.2 影响查询延迟的因素
| 因素 | 影响 | 说明 |
|---|---|---|
| Part 数量 | 正相关 | 更多 Part = 更高扫描开销 |
| blockCache 命中率 | 负相关 | 命中缓存 → 避免磁盘 I/O |
| 查询范围 | 正相关 | 更大时间范围 → 更多 Part |
| 标签过滤器 | 正相关 | 正则匹配 → 额外计算 |
| 磁盘类型 | 瓶颈 | NVMe SSD >> SATA SSD >> HDD |
3.3 Part 数量对查询的影响
┌─────────────────────────────────────────────────────────────────────────┐
│ Part 数量与查询延迟的关系 │
│ │
│ Part 数量 │ 查询延迟(P99) │ 状态 │
│ ────────────────────────────────────────────────────────────────────── │
│ 1-10 │ < 50ms │ 理想状态 │
│ 10-50 │ 50-200ms │ 可接受 │
│ 50-100 │ 200ms-1s │ 警告,需要合并 │
│ > 100 │ > 1s │ 严重,需要立即合并 │
│ │
│ 控制 Part 数量的方法: │
│ 1. defaultPartsToMerge = 15 是 Go 代码中的常量,不是命令行参数 │
│ 2. 监控 vm_parts{type="..."} 指标(app/vmstorage/main.go 第 427-431 行) │
│ 3. /internal/force_merge 手动触发合并 │
│ 4. 调整 retentionPeriod 清理旧数据 │
└─────────────────────────────────────────────────────────────────────────┘
设计精髓
VM 查询性能优于 Prometheus 的核心原因:
- blockCache 按需缓存:只缓存命中的数据块,避免无关数据
- MergeSet 无分层:查询只需扫描相关 Part,不需要跨层合并
- 倒排索引:标签查询直接定位 series,不需要扫描全量数据
- PromQL 优化:并行执行、rollup 缓存、streaming 算子
四、内存占用模型:时间序列数量与 RAM
思考记忆提示 — 内存占用是 VM 的核心优势——按需缓存策略减少内存占用
- 内存占用与 series 数量 近似线性关系
- 缓存按比例分配:TSID 缓存 37%、MetricName 缓存 10%、MetricNamesStats 缓存 1%
- 面试高频提问:VM 的内存占用模型是怎样的?
4.1 内存占用公式
内存占用公式(源码实测):
memory.Allowed() = 系统内存 × -memory.allowedPercent(默认 60%)
源码:lib/memory/memory.go 第 14 行(default 60)
VM 缓存分配(lib/storage/storage.go 第 338-370 行):
TSID 缓存 = memory.Allowed() × 37%
MetricID 缓存 = memory.Allowed() / 16 ≈ 6.25%
MetricName 缓存 = memory.Allowed() × 10%
MetricNamesStats 缓存 = memory.Allowed() × 1%
─────────────────────────────────────────────────
合计 workingsetcache ≈ memory.Allowed() × 54%
注意:这些是缓存最大分配上限,实际占用取决于数据量和访问模式。
公式中的系数基于源码常量推导,不是生产环境的精确测量值。
4.2 影响内存占用的因素
| 因素 | 影响 | 说明 |
|---|---|---|
| series 数量 | 正相关 | series 越多,TSID 缓存需求越大。实际内存取决于 memory.Allowed() 配置 |
| 标签数量 | 正相关 | 更多标签 = 更大 metric name = 更大索引 |
| 查询模式 | 影响 | 热点查询 → 高缓存命中率 → 低内存需求 |
| memory.Allowed() | 控制 | 限制 VM 可用内存 |
4.3 按需缓存的原理
┌─────────────────────────────────────────────────────────────────────────┐
│ VM 内存模型:按需缓存 │
│ │
│ memory.Allowed() 配置(lib/memory/memory.go 第 14 行): │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ -memory.allowedPercent = 60%(默认) │ │
│ │ -memory.allowedBytes 可覆盖此配置 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ VM 缓存分配(lib/storage/storage.go 第 338-370 行): │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ TSID 缓存 = memory.Allowed() × 37% │ │
│ │ MetricID 缓存 = memory.Allowed() / 16 ≈ 6.25% │ │
│ │ MetricName 缓存 = memory.Allowed() × 10% │ │
│ │ MetricNamesStats 缓存 = memory.Allowed() × 1% │ │
│ │ ───────────────────────────────────────────────────── │ │
│ │ 合计 ≈ memory.Allowed() × 54% │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 核心优势:冷数据从磁盘读取,按需缓存,无需全量 mmap │
└─────────────────────────────────────────────────────────────────────────┘
五、压缩与存储:数据压缩对容量的影响
思考记忆提示 — 压缩是 VM 高效存储的关键——NearestDelta + ZSTD 组合拳
- 压缩率与数据特征强相关
- NearestDelta 对时序数据特别有效
- 面试高频提问:VM 的压缩率是多少?
5.1 压缩率公式
压缩率公式:
CR = CR_timestamp × CR_value × CR_index
其中:
CR - 总压缩率
CR_timestamp - 时间戳压缩率(NearestDelta 算法)
≈ 10x-30x(取决于采样间隔稳定性)
CR_value - 值压缩率(NearestDelta + ZSTD)
≈ 2x-10x(取决于值的变化幅度)
CR_index - 索引压缩率(倒排索引 + commonPrefix)
≈ 2x-5x(取决于标签基数)
典型压缩率:
- Counter 类型(平滑变化):20x-50x
- Gauge 类型(波动较大):10x-20x
- Histogram 类型(稀疏):5x-10x
原始数据 vs 压缩后:
1 个样本(16 bytes)→ 压缩后 ~0.5-2 bytes
1 天数据(86400 秒采样)→ 约 40KB - 170KB
5.2 存储空间计算
存储空间:使用官方推荐的实测法(docs.victoriametrics.com):
1. 运行 1 天测试,测量 storageDataPath 目录大小
2. 按 retention 天数外推:预计总空间 = 单日增长 × retention 天数
示例:测试 1 天后占用 10GB → retention=100 天 → 预计 10GB × 100 = 1TB
注意:以下系数为估算,未在源码中精确验证:
- 样本存储:取决于压缩率(NearestDelta + ZSTD)
- 索引存储:取决于标签数量和基数
- 建议通过实测获取精确数字
小贴士 — 磁盘空间规划建议
生产环境磁盘空间规划:
- 运行 1 天测试,测量实际存储增长
- 按 retention 天数外推(见官方公式)
- 预留 20% 空余空间(减少磁盘碎片)
六、容量规划:实际部署的计算方法
思考记忆提示 — 容量规划是性能和成本之间的权衡——理解模型才能做出正确决策
- 容量规划需要考虑三个维度:吞吐、延迟、存储
- 瓶颈往往是木桶效应:最弱的环节决定整体
- 面试高频提问:如何为 1000 万 series 规划 VM 集群?
6.1 容量规划模板
┌─────────────────────────────────────────────────────────────────────────┐
│ 容量规划检查清单 │
│ │
│ 1. 确定业务需求 │
│ □ series 数量:当前 N,当前增长速率 │
│ □ 写入吞吐:samples/s 或 samples/day │
│ □ retention:90 天 / 180 天 / 1 年 │
│ □ 查询 QPS:查询频率和复杂度 │
│ │
│ 2. 计算资源需求 │
│ □ 内存 ≈ memory.Allowed() × 60%(默认值,可通过 -memory.allowedPercent 调整) │
│ □ CPU = 吞吐 / 40K samples per core │
│ □ 磁盘 = 存储需求 × 2.0 安全系数 │
│ │
│ 3. 选择部署模式 │
│ □ Single-Node:< 500 万 series │
│ □ Cluster 模式:> 500 万 series 或高可用需求 │
│ │
│ 4. 验证配置 │
│ □ 吞吐测试:能否满足写入需求 │
│ □ 延迟测试:P99 延迟是否可接受 │
│ □ 内存测试:OOM 是否发生 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
6.2 实际案例计算
案例:1000 万 series,90 天 retention,500K samples/s 写入
1. 内存计算:
memory.Allowed() = 机器总内存 × 60%(默认 -memory.allowedPercent = 60)
缓存总大小 ≈ memory.Allowed() × 54%(TSID 37% + MetricID 6.25% + MetricName 10% + MetricNamesStats 1%)
10M series 时,建议 memory.Allowed() ≥ 16GB
2. CPU 计算:
N_cpu = 500K / 40K
= 12.5 cores
建议:16 核 CPU
3. 磁盘计算:
样本数 = 500K/s × 86400s × 90 days
= 3.89 trillion samples
存储(压缩后)= 3.89T × 1 byte
= 3.89 TB
安全系数 = 3.89TB × 2.0 = 7.78TB
建议:8TB - 10TB NVMe SSD
4. 部署模式:
1000 万 series > 500 万 series → Cluster 模式
Cluster 配置建议:
- 3 个 vmstorage 节点,每个 32GB RAM
- 2 个 vminsert 节点
- 2 个 vmselect 节点
注意
以上计算是简化模型,实际需求可能因数据特征、查询模式、硬件配置等因素而有所不同。建议在生产部署前进行实际压测验证。
七、FAQ:常见疑问
思考记忆提示 — FAQ 是全篇的"临考前速背"模块,20 组覆盖全链路
- Q1-Q5 围绕写入吞吐:CPU 核数与写入速率的关系
- Q6-Q10 围绕查询延迟:Part 数量与延迟的关系
- Q11-Q15 围绕内存占用:series 数量与 RAM 的关系
- Q16-Q20 围绕容量规划:实际部署的计算方法
Q1. VM 的写入吞吐与 CPU 核数是线性关系吗?
近似线性,但不是完美的线性关系。由于锁竞争、内存带宽、缓存一致性等开销,实际吞吐约为线性值的 70-90%。
Q2. 为什么我的 VM 写入吞吐达不到官方基准?
可能的原因:磁盘 I/O 瓶颈、网络瓶颈、数据复杂度高、配置不当。建议使用 pprof 分析 CPU 和内存使用,找出瓶颈。
Q3. Part 数量多少算正常?
正常情况下,每个分区应有 10-50 个 Part。超过 100 个 Part 会显著影响查询延迟,需要检查合并是否正常。源码在 lib/storage/partition.go 第 41 行,defaultPartsToMerge = 15 是触发合并的阈值。
Q4. 如何监控 Part 数量?
通过 /metrics 端点的 vm_parts{type="..."} 指标监控。源码在 app/vmstorage/main.go 第 427-431 行,可看到按类型分的 Part 数量:storage/inmemory、storage/small、storage/big、indexdb/inmemory、indexdb/file。
Q5. 什么是 active_merges?
active_merges 表示当前正在进行的合并任务数量。源码在 app/vmstorage/main.go 第 381-385 行,指标名为 vm_active_merges{type="..."},按 storage 和 indexdb 分别统计。
Q6. TSIDCache 命中率多少算正常?
正常情况下命中率应高于 90%。如果命中率低于 80%,可能需要增加内存或调整缓存大小。
Q7. blockCache 命中率多少算正常?
正常情况下命中率应高于 80%。命中率受查询模式和数据集大小影响。
Q8. VM 的压缩率是多少?
典型压缩率 10x-50x,取决于数据特征。Counter 类型(平滑变化)压缩率高,Histogram 类型压缩率低。
Q9. 磁盘空间如何计算?
公式:存储 = 样本数 × 1 byte × 2.0(安全系数)。示例:90 天 retention,500K samples/s ≈ 6.5TB 原始数据 → 约 8TB 磁盘空间。
Q10. retention 对存储空间的影响是什么?
存储空间与 retention 成正比。90 天 = 30 天 × 3,存储空间也约为 3 倍。
Q11. memory.Allowed() 如何设置?
通过 -memory.allowedPercent 或 -memory.allowedBytes 参数设置(源码在 lib/memory/memory.go 第 14 行,默认 60%)。
Q12. 内存不足会发生什么?
内存不足会导致 OOM 或性能急剧下降。建议监控 process_resident_memory_bytes 指标(来自 vendor/github.com/VictoriaMetrics/metrics/process_metrics_linux.go),以及 vm_parts{...} 指标观察存储状态。
Q13. 如何调整缓存大小?
VictoriaMetrics 使用多个独立缓存,各自有不同的命令行参数。源码在 app/vmstorage/main.go 第 73-85 行,包括:-storage.cacheSizeStorageTSID(TSID 缓存)、-storage.cacheSizeStorageMetricName(MetricName 缓存)等。默认大小由 lib/storage/storage.go 中的 getTSIDCacheSize() 等函数按 memory.Allowed() 比例计算。
Q14. 为什么 VM 比 Prometheus 省内存?
因为 VM 使用按需缓存策略,不是全量 mmap。源码在 lib/storage/storage.go 第 338-370 行,四种缓存按 memory.Allowed() 分配:TSID 缓存 37%、MetricID 缓存 6.25%、MetricName 缓存 10%、MetricNamesStats 缓存 1%。冷数据从磁盘读取,按需缓存,避免了 Prometheus 全量 mmap 带来的内存浪费。
Q15. 什么场景下 VM 内存占用会很高?
高基数标签、全量查询、缓存未命中等场景。建议避免高基数标签,控制查询范围。具体缓存配置参数见 app/vmstorage/main.go 第 73-85 行。
Q16. Single-Node 模式支持多少 series?
官方建议 < 500 万 series,理论上限取决于可用内存。每百万 series 约需 500MB 内存。
Q17. Cluster 模式的优势是什么?
支持水平扩展、高可用、租户隔离。适合 > 500 万 series 或需要高可用的场景。
Q18. 如何选择 SSD 还是 HDD?
生产环境强烈建议使用 NVMe SSD。HDD 的随机 I/O 性能严重影响查询延迟。
Q19. 网络带宽会影响性能吗?
会,特别是 Cluster 模式下。建议使用 10Gbps 网络,避免网络成为瓶颈。
Q20. 如何进行容量规划?
内存需求取决于缓存大小(由 memory.Allowed() 控制),CPU 与写入吞吐线性相关,磁盘与存储数据量成正比。具体参考:内存由 lib/storage/storage.go 第 338-368 行的缓存分配策略决定;CPU 参考写入吞吐公式(每核约 40K samples/s);磁盘建议预留 2 倍存储空间。
全篇必记总纲
VictoriaMetrics 性能模型的三个核心来源(全部可从源码实测):rawRowsShardsPerPartition = cgroup.AvailableCPUs()(并行写入)、defaultPartsToMerge = 15(合并阈值)、TSIDCache = memory.Allowed() × 37%(缓存分配)。理解这三个源码常量,才能做好容量规划。
八、Roadmap:后续预告
本篇覆盖了 VictoriaMetrics 的性能模型,但还有很多细节尚未展开:
- #10 与其他 TSDB 对比:Prometheus/InfluxDB/Thanos/VM——理解 VM 在竞品中的定位
- #11 开源生态:VM 在 CNCF 生态中的位置——理解 VM 的生态位
- #147 写入吞吐极限:100 万 samples/s 写入调优——性能优化专题
- #148 查询延迟 P99:50ms 内返回的查询优化策略——性能优化专题
- #107 内存调优:memory.Allowed() 与 cache 大小规划——排障调优专题
本文参考与源码链接:
• VictoriaMetrics 官方 FAQ
• VictoriaMetrics Cluster 文档
• lib/storage/ · 存储核心
• lib/mergeset/ · MergeSet 存储

浙公网安备 33010602011771号