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 亿 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 每 2 秒刷盘的设计(pendingRowsFlushInterval),在保证数据安全的同时避免了 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.Allowed() 的 40%,加上 10% 的 metricNameCache、约 3% 的 tagFiltersCache 等共同瓜分 memory.Allowed()

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

没有统一的拆分公式,VM 在启动时由 memory.Allowed() 统一核算后分配给多个独立缓存池(TSIDCache / blockCache / metricNameCache / tagFiltersCache / dateMetricIDCache 等)。日常只需重点关注 vm_slow_row_inserts_totalvm_cache_size_bytesvm_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-216
15.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 {
        case 
15.3 Part 级清理 (partition.stalePartsRemover)

lib/storage/partition.go:1742-1754(stalePartsRemover 主体)

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-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-467
Part 级 (partition) ~7 分钟 过期 Part 文件 partition.go:1742-1754
Block 级 (merge) 每次 Merge 时 Block 内的过期样本 merge.go:85-89, 205-216
15.6 为什么不是"立即删除"?

清理被分散到多层、多周期、带随机抖动地执行,原因是:

  • 分区级:~1 分钟检查一次过期月份目录
  • Part 级:~7 分钟检查一次过期 Part
相关推荐
_lucas18 分钟前
给知识库网站接入AI问答
前端·ai编程·全栈
头发还在的女程序员43 分钟前
医院陪诊管理系统怎么选择?——2026 年选型避坑与架构参考
java·开发语言·陪诊系统·陪诊app·医院陪诊陪护
前端糕手1 小时前
前端面试题大全:JavaScript + Vue3 + React + TypeScript + 工程化 + 性能优化
前端
CodeStats1 小时前
【Spring事务】Spring事务注解 @Transactional 完整体系:从 MySQL 隔离级别到 MyBatis 原理详解
java·spring·mybatis·事务·transactional
我命由我123452 小时前
Android 开发问题:为 PDFView 设置一个带有黑色边框的背景 drawable,但边框没有生效
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
我不叫武2 小时前
一个用 Rust 写的离线编码查询桌面工具
前端
拳里剑气3 小时前
C++算法:BFS解决FloodFill算法
c++·算法·bfs·宽度优先
Web4Browser3 小时前
指纹浏览器 API 自动化怎么接:启动 Profile、获取 CDP 端点并连接自动化框架
前端·网络·typescript·自动化
Patrick_Wilson3 小时前
从 React 到 Flutter:写给前端的一张跨端知识地图
前端·flutter·react.js