FAQ:常见疑问
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 亿 seriesSingle-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{jobprometheus,instancelocalhost:9090} 和 metric{jobprometheus,instancelocalhost:9091} 共享 metric{jobprometheus,instancelocalhost: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 有 WALWrite-Ahead Log吗没有这是 VM 的设计选择。Prometheus TSDB 使用 WAL 来保证崩溃恢复但 WAL 会增加写入延迟。VM 通过 InMemoryPart 每 2 秒刷盘的设计pendingRowsFlushInterval在保证数据安全的同时避免了 WAL 的开销。Q10. InMemoryPart 刷盘失败会丢数据吗正常情况下不会丢数据。InMemoryPart 在内存中维护多个副本刷盘时会先写临时文件再原子重命名。如果进程崩溃正在刷盘的数据可能丢失但这是 Prometheus remote_write 协议允许的最多一次语义范围内。Q11. Cardinality 爆炸的根本原因是什么高 cardinality 来自标签值组合过多。例如metric{jobfoo,instanceip-1}、metric{jobfoo,instanceip-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.binidxbCache 缓存索引块index.binibSparseCache 是 ibCache 的稀疏访问优化。三者合计占用 memory.Allowed() 的 40%加上 10% 的 metricNameCache、约 3% 的 tagFiltersCache 等共同瓜分memory.Allowed()。Q14. 如何规划 VictoriaMetrics 的内存容量没有统一的拆分公式VM 在启动时由memory.Allowed()统一核算后分配给多个独立缓存池TSIDCache / blockCache / metricNameCache / tagFiltersCache / dateMetricIDCache 等。日常只需重点关注vm_slow_row_inserts_total、vm_cache_size_bytes、vm_cache_misses_total这几项指标命中率下降或缓存溢出时按需调整对应缓存大小或节点规格。100 万 series 起步建议预留 4–8GB 内存做基线再根据实际命中率与慢插入比例动态扩容。Q15. retention 设置后数据会立即删除吗不会立即删除数据会在后台分多层异步清理。VM 的清理机制分三层分区级默认每 1 分钟左右巡视一次过期的月份分区目录Part 级默认每 7 分钟左右巡视一次过期 Part 文件Block 级则在每次合并时按需裁剪过期样本。磁盘空间释放可能有延迟这是设计如此并非异常。VictoriaMetrics 的 retention 清理采用三层异步清理机制源码实现非常精妙15.1 三层清理机制概览Retention 清理三层架构 │ ├── [Layer 1] 分区级清理 (table.retentionWatcher) │ ├── 触发周期: ~1 分钟 ( 随机抖动) │ ├── 职责: 删除整个过期分区目录 │ └── 源码: lib/storage/table.go:428-467 │ ├── [Layer 2] Part 级清理 (partition.stalePartsRemover) │ ├── 触发周期: ~7 分钟 ( 随机抖动) │ ├── 职责: 标记过期 Part 并在 Merge 时删除 │ └── 源码: lib/storage/partition.go:1742-1754 │ └── [Layer 3] Block 级裁剪 (mergeBlocks) ├── 触发时机: 每次 Merge 时 ├── 职责: Block 内过期样本的精确裁剪 └── 源码: lib/storage/merge.go:85-89, 205-21615.2 分区级清理 (table.retentionWatcher)lib/storage/table.go:428-467分区是按月组织的202306、202307 等当整个分区超过 retention 期限时整个分区目录会被删除func (tb *table) retentionWatcher() { d : timeutil.AddJitterToDuration(time.Minute) ticker : time.NewTicker(d) for { select { case15.3 Part 级清理 (partition.stalePartsRemover)lib/storage/partition.go:1742-1754stalePartsRemover 主体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 { case15.4 Block 内样本级裁剪 (mergeBlocks)lib/storage/merge.go:85-89 与 205-216即使 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-467Part 级 (partition)~7 分钟过期 Part 文件partition.go:1742-1754Block 级 (merge)每次 Merge 时Block 内的过期样本merge.go:85-89, 205-21615.6 为什么不是立即删除清理被分散到多层、多周期、带随机抖动地执行原因是分区级~1 分钟检查一次过期月份目录Part 级~7 分钟检查一次过期 Part