惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

B
Blog RSS Feed
V2EX - 技术
V2EX - 技术
P
Privacy & Cybersecurity Law Blog
T
The Exploit Database - CXSecurity.com
美团技术团队
WordPress大学
WordPress大学
博客园 - 司徒正美
S
Securelist
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - Franky
Attack and Defense Labs
Attack and Defense Labs
Security Latest
Security Latest
L
LINUX DO - 最新话题
NISL@THU
NISL@THU
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
腾讯CDC
Y
Y Combinator Blog
The Hacker News
The Hacker News
Security Archives - TechRepublic
Security Archives - TechRepublic
IT之家
IT之家
T
Threatpost
Hugging Face - Blog
Hugging Face - Blog
Scott Helme
Scott Helme
S
SegmentFault 最新的问题
Cyberwarzone
Cyberwarzone
C
Cisco Blogs
阮一峰的网络日志
阮一峰的网络日志
U
Unit 42
B
Blog
Microsoft Azure Blog
Microsoft Azure Blog
P
Proofpoint News Feed
小众软件
小众软件
V
Vulnerabilities – Threatpost
J
Java Code Geeks
V
Visual Studio Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
A
Arctic Wolf
博客园 - 【当耐特】
Microsoft Security Blog
Microsoft Security Blog
S
Security @ Cisco Blogs
雷峰网
雷峰网
Help Net Security
Help Net Security
The Last Watchdog
The Last Watchdog
Recent Announcements
Recent Announcements
G
Google Developers Blog
C
CERT Recently Published Vulnerability Notes
T
Troy Hunt's Blog
MyScale Blog
MyScale Blog

博客园_首页

Plist 二进制格式 Milvus 和 PGVector,哪个更好? OpenClaw 已过时?在 VS Code 中运行 Hermes Agent! 第30篇文章:一个大三计科生的自白 Manim如何在数学公式中完美显示中文? Docker 部署 RocketMQ 5 并发编程核心概念辨析 C#事务处理最佳实践:别再让“主表存了、明细丢了”的破事发生 CLI 是什么?为什么大厂突然集体卷命令行? 【从0到1构建一个ClaudeAgent】协作-自主Agent UIImageView 设置图片不生效的原因排查 最小二乘问题详解20:无先验约束下的增量式SFM自由网平差 痞子衡嵌入式:大话双核i.MXRT1180之XIP应用里借助MU实现可靠Flash IAP的方法 AI Chat 封装, SemanticKerne.AiProvider.Unified 已发布 Windows下右键编辑js文件无法打开记事本——在注册表中使用环境变量 在后台服务中使用 Scoped 服务,为什么总是报错? H200 安装驱动并使用sglang启动模型 wireshark 抓包Trap上报告警内容 我用 AI 辅助开发了一系列小工具(2):图片压缩工具 [A Primer On MC and CC] 2.1 Memory Consistency 1 - 指令重排序和 SC 模型 Oracle数据库SCN推进技术详解与实践指南 玩转控件:封装个带图片的Label控件 Claude Code 4.7 真正该升级的不是模型,而是你的工作流 前端小白一句话,AI 帮我做了个颜值拉满的桌面媒体播放器。当代码不再是门槛,一句话编程就是现实。 5. WorkBuddy: 小龙虾的灵魂三件套,让你的小龙虾不只是工具 SQLite 分片方案实战:三种分片策略的深度对比 告别简陋 UI!一款基于 Fluent Design 和基于 WinUI 的开源免费、现代化的 Avalonia UI 控件库 关于二进制排列组合枚举的总结 AI开发-python-LangGraph框架(3-27-LangGraph从零实现大模型智能决策工作流) ElasticSearch主分片和副本分片概念详解 【002】HTTPS 粗解:证书、TLS 握手与对后端配置的影响 Hermes Agent 一周暴涨五万 Star,但我劝你别急着追 明明连接的是Redis的DB0,为什么能查到DB3的数据? 【从0到1构建一个ClaudeAgent】协作-Agent团队 熟悉电子元器件之后,电子小白下一步该怎么走? MAF快速入门(23)通过C#类定义Skills .NET 高级开发 | 手写一个对象映射框架 FastAPI数据库ORM怎么选?我肝了三个Demo后,终于不再纠结了 mysqldump 参数拾遗:在遗忘与铭记之间 C# .NET 周刊|2026年3月5期 Claude code入门 - 陈彦斌 一文学习入门 ThingsBoard 开源物联网平台 GitHub 热门项目 | 2026年04月16日 如何为GIT设置全局勾子,为每次提交追加信息 Number.isFinite和isFinite与isNaN()和Number.isNaN的区别 PortSwigger SQL注入LAB2 推荐一个测试人必备的Skills,从功能到性能全搞定(附详细实操和安装下载方式) 筑基期:掌握Odoo基础核心知识点02(Odoo XML 开发方式详解) GLM模型这么火,咱们用vllm也咧一个呗! 深入理解 AbortController:从底层原理到跨语言设计哲学 字符串学习笔记 多租户系统框架的基础模块设计和分析设计 Apache SeaTunnel Zeta 为什么能做到“又快又稳”? AI开发-python-LangGraph框架(3-26-LangGraph基本概念及第一个简单样例) Vue 3 组件通信,别只会用 Props 和 Emits 了,这几个狠活儿你得看看 ElasticSearch7.X版本配置密码 用Manim实现动态交点计算--从一个动点问题说起 团结引擎+Addressable+Instant Game打包抖音小游戏 function call 实战:让 LLM 自动判断 pod 异常、调用日志工具并完成故障分析 bubseek —— 让 Agent 的足迹,变成团队的洞察 通过 C# 读取并导出 PDF 书签 如何用 GitHub Actions 实现 Steam 自动化发布 【从0到1构建一个ClaudeAgent】并发-后台任务 .NET 高级开发 | 定制 ASP.NET Core 框架 电子小白:什么是运算放大器(运放) zero2Agent:面向大厂面试的 Agent 工程教程,从概念到生产的完整学习路线 堆上的ORW HC32F460 USB CDC通信异常:非对齐访问异常排查 20260413-Hyperbridge 攻击事件:发生在默克尔山上的验证绕过 那些喊着AI 要淘汰你的人,正在靠你的焦虑赚大钱! 深度学习进阶(八)Swin Transformer 最小二乘问题详解19:带先验约束的增量式SFM优化与实现 SnapTranslate 3.0 正式发布:全局划词翻译 + 完整英语学习闭环,一站式搞定查词、记词、复习 工作的意义、工作的困难认知再思考 .NET + AI 进阶实战:基于类的技能开发 - 打造可治理的 Agent 能力模块 【从0到1构建一个ClaudeAgent】规划与协调-技能 上周热点回顾(4.6-4.12) 电子小白的工具三件套:面包板、杜邦线、万能板 单表五亿数据的查询优化 | Mysql、StarRocks 2. WorkBuddy:从“我是谁”到“帮我干活” C# 如何减少代码运行时间:7 个实战技巧 基于HelixToolkit.SharpDX 渲染3D模型 - 笺上知微 从零开始的双臂具身VLA起源及现阶段发展综述 - SkyXZ 记对 xonsh shell 的使用, 脚本编写, 迁移及调优 - pluvium27 受够了Vibe Coding的失控?换个起点,让AI事半功倍 从开始配置漏洞环境到漏洞复现流程 - 難しい 关于10年工作经验的程序员对OpenClaw的实战经验分享以及看法 - 虚无境 Any metadata 的内存布局 C# .NET 周刊|2026年3月2期 - InCerry 我帮你测过了,测试圈排名第二的 Skill 依然很牛逼 Skill Discovery | 无监督技能发现的经典工作总结 - MoonOut 上下文工程是什么?过时了么?一文讲明白! - 一枫说码 开了 TUN 模式还是直连?90% 的人都踩过这个坑 AScript扩展多种脚本语言 - rockey627 AI 学习笔记:Agent 的记忆机制 你能被装进一个文件里吗?——7 万人把同事"蒸馏"成了 AI - 我没有三颗心脏 Claude Code 通关手册(七):给 AI 装上技能包——Skills 完全指南 - 暮色之狐 在浏览器中快速编辑代码:VSCode Web 集成实践 - Newbe36524 蒸馏自己 skill?基于 Deepseek 的蒸馏器,丐版蒸馏方式,简单便捷 - To_Carpe_Diem Spring AI Aliababa和AgentScope,哪个更好? - 苏三说技术
VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 架构演进:从 TSDB 到 MergeSet 的设计取舍
左扬 · 2026-06-29 · via 博客园_首页

VictoriaMetrics 1.146.0 源码专题【左扬精讲】—— 架构演进:从 TSDB 到 MergeSet 的设计取舍

在时序数据库(TSDB)的世界里,存储引擎的设计直接决定了写入性能、查询效率和资源利用率。VictoriaMetrics 采用了独特的 MergeSet 存储架构,与传统 TSDB(如 Prometheus、InfluxDB)的 LSM Tree 或 B-Tree 有着本质区别。本篇文章将从源码层面深入剖析 MergeSet 的设计哲学,以及它相比传统 TSDB 的优势与取舍。

一、TSDB 存储引擎演进史

思考记忆提示理解 TSDB 存储引擎的演进,才能理解 MergeSet 为什么会这样设计

  • 第一代 TSDB:基于 B-Tree(如 InfluxDB 1.x)
  • 第二代 TSDB:基于 LSM Tree(如 Prometheus 2.x、Cassandra)
  • 第三代 TSDB:MergeSet(VictoriaMetrics独创)
  • 面试高频提问:MergeSet 和 LSM Tree 的核心区别是什么?

1.1 传统 TSDB 的存储架构

在讨论 MergeSet 之前,我们需要了解传统 TSDB 的存储架构。主流的 TSDB(如 Prometheus 2.x)采用 LSM Tree(Log-Structured Merge Tree)作为底层存储引擎。

LSM Tree 的核心思想是:

  1. 写入时:数据先写入内存中的 MemTable(类似 WAL),达到阈值后刷盘生成 SSTable
  2. 合并时:多个 SSTable 按层次合并,小表合并成大表(这就是"分层"的概念)
  3. 查询时:需要读取多个层次的 SSTable,可能影响查询性能
  LSM Tree 架构
┌─────────────────────────────────────────────────────────────────────────────┐
│                                                                             │
│  Level 0 (L0)                                                              │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐                                     │
│  │ SSTable │ │ SSTable │ │ SSTable │ ← 新刷出的文件,小而多               │
│  └────┬────┘ └────┬────┘ └────┬────┘                                     │
│       │           │           │                                            │
│       └───────────┴───────────┘                                            │
│                     │                                                        │
│                     ▼                                                        │
│  Level 1 (L1)                                                              │
│  ┌───────────────────────────┐                                              │
│  │        SSTable            │ ← 合并后的文件,较大                         │
│  └─────────────┬─────────────┘                                              │
│                │                                                             │
│                ▼                                                             │
│  Level 2 (L2)                                                              │
│  ┌───────────────────────────┐                                              │
│  │        SSTable            │ ← 更大                                      │
│  └─────────────┬─────────────┘                                              │
│                │                                                             │
│                ▼                                                             │
│           ...                                                                │
│                                                                             │
│  问题:查询需要遍历所有层级,Level 越多,查询越慢                             │
└─────────────────────────────────────────────────────────────────────────────┘

1.2 Prometheus TSDB 的局限性

Prometheus 2.x 的 TSDB 基于 LSM Tree 设计,虽然相比 1.x 版本有了巨大提升,但在超大规模场景下仍面临挑战:

问题描述影响
分层合并开销 LSM Tree 需要多层合并,Level 越多 IO 越重 写入放大、写放大问题严重
查询延迟不稳定 查询需要遍历多个 Level,数据分散 P99 延迟难以控制
内存占用高 多层索引、BloomFilter 需要维护 RAM 消耗大

注意

Prometheus 的 LSM Tree 实现与 Cassandra/RocksDB 有一定区别,但核心问题类似。对于超大规模场景(如 100 万+ series),LSM Tree 的分层合并策略会成为性能瓶颈。

二、MergeSet 核心设计:只合并不分层

思考记忆提示MergeSet 的精髓在于"只合并不分层"——这是它与 LSM Tree 的本质区别

  • MergeSet 不分层,所有 Part 文件在同一层级
  • 合并策略:小型 Part 合并成大型 Part,永远变大的单向合并
  • 设计优势:查询只需扫描少量大文件,IO 更高效

2.1 MergeSet 的核心概念

MergeSet 是 VictoriaMetrics 独创的存储架构,其核心设计哲学可以用一句话概括:"只合并不分层"。这与 LSM Tree 的"分层合并"形成鲜明对比。

lib/mergeset/table.go 中,MergeSet 的设计理念被清晰定义:

 // lib/mergeset/table.go
// MergeSet 核心设计:只合并不分层

// MergeSet 与 LSM Tree 的本质区别:
// - LSM Tree: 分层合并,Level N 合并到 Level N+1
// - MergeSet: 不分层,所有 Part 文件在同一目录,按大小合并

// Part 文件的生命周期:
// InMemoryPart (新建)
//     ↓ (1秒后刷盘)
// Small Part (小文件,KB级别)
//     ↓ (合并)
// Big Part (大文件,MB级别)
//     ↓ (合并)
// 更大的 Part
//     ↓
// 最终的超大 Part

// 关键设计点:
// 1. Part 文件永不删除,只合并成更大的文件
// 2. 查询时扫描所有 Part,但利用 BloomFilter 快速跳过无关 Part
// 3. 后台任务持续合并小 Part 成大 Part,保持 Part 数量可控

2.2 MergeSet vs LSM Tree 对比

  MergeSet 架构(VictoriaMetrics)
┌─────────────────────────────────────────────────────────────────────────────┐
│                                                                             │
│  /data/                                                                     │
│  ├── 2024_01/                                                               │
│  │   ├── small_001.tar / small_002.tar / small_003.tar  ← 小文件,合并中     │
│  │   ├── big_001.tar                                ← 大文件,已稳定         │
│  │   ├── big_002.tar                                                              │
│  │   └── super_001.tar / super_002.tar              ← 更大文件             │
│  │                                                                     │
│  ├── 2024_02/    ...                                                        │
│  └── 2024_03/    ...                                                        │
│                                                                             │
│  特点:                                                                     │
│  - 所有 Part 文件在同一目录层级                                               │
│  - 小文件持续合并成大文件(单向合并)                                         │
│  - 查询扫描所有 Part,但用 BloomFilter 过滤                                  │
│  - IO 模式:顺序读大文件,而非随机读多层小文件                                 │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
维度LSM Tree (Prometheus)MergeSet (VictoriaMetrics)
文件层级 多层(L0, L1, L2...) 单层(所有 Part 在同级目录)
合并方向 逐层向上合并 小 Part → 大 Part(单向)
查询方式 遍历所有层级 扫描所有 Part + BloomFilter
IO 模式 大量小文件随机读 少量大文件顺序读
写放大 严重(多层重复写) 轻量(只写一次)
查询延迟 不稳定(P99 难控制) 稳定(可预测)

源码视角:MergeSet 合并调度

MergeSet 的合并调度逻辑在 lib/mergeset/table.goscheduleMerges() 函数中实现:

  • 默认配置defaultPartsToMerge=15,每次合并最多 15 个小 Part
  • 合并策略:优先合并"最老"的小 Part,避免大量小文件堆积
  • 并行合并:通过 rawItemsShards 实现 CPU 级别的并行合并
  • ZSTD 压缩:合并时自动选择压缩级别,getCompressLevel() 根据数据量动态选择

三、源码解析:MergeSet vs LSM Tree

思考记忆提示源码是理解 MergeSet 设计取舍的最佳途径

  • lib/mergeset/ 是 MergeSet 的核心实现
  • lib/storage/ 中的 Table/Partition 对接 MergeSet
  • 面试高频提问:MergeSet 为什么不需要 WAL?

3.1 InmemoryPart:1秒刷盘的原子性保证

MergeSet 不使用 WAL(Write-Ahead Log),而是通过 InmemoryPart 的原子性刷盘实现数据可靠性。这在 lib/mergeset/inmemory_part.go 中实现:

 // lib/mergeset/inmemory_part.go
// InmemoryPart 核心设计:原子性刷盘

// 刷盘流程:
// 1. 内存中构建完整的 Part 数据(4 个 buffer 并行写入)
// 2. 调用 MustStoreToDisk() 原子性刷盘
// 3. 刷盘成功后才更新目录索引

// MustStoreToDisk 的关键点:
// - 先写临时文件(如 small_001.tar.tmp)
// - 刷盘成功后,原子性 rename 到正式文件名
// - 如果进程崩溃,临时文件会被忽略,不会污染数据

// 这就是为什么 MergeSet 不需要 WAL:
// - InmemoryPart 每秒刷盘,数据最多丢失 1 秒
// - 刷盘后的数据已经是完整可用的 Part 文件
// - 重启时扫描目录即可恢复所有 Part

3.2 Part 文件结构:四文件合一

MergeSet 的 Part 文件采用独特的四文件结构,这在 lib/mergeset/part.go 中定义:

  MergeSet Part 文件结构
┌─────────────────────────────────────────────────────────────────────────────┐
│                                                                             │
│  Part.tar 文件内部结构:                                                     │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │  metaindex.bin                                                        │    │
│  │  ├── [MetaIndexRow 1] ← Block 1 的元信息(offset, size, min/max)    │    │
│  │  ├── [MetaIndexRow 2] ← Block 2 的元信息                              │    │
│  │  └── [MetaIndexRow N] ← Block N 的元信息                              │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │  index.bin                                                            │    │
│  │  ├── [IndexRow 1] ← MetricName → BlockID 映射                        │    │
│  │  ├── [IndexRow 2]                                                      │    │
│  │  └── [IndexRow N]                                                      │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │  items.bin                                                            │    │
│  │  ├── [Item 1] ← 时序数据点(Timestamp + Value)                       │    │
│  │  ├── [Item 2]                                                          │    │
│  │  └── [Item N]                                                          │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │  lens.bin                                                             │    │
│  │  └── 每行的长度信息(用于快速随机访问)                                  │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                                                             │
│  关键设计点:                                                               │
│  - metaindex.bin:Block 的索引,用于快速定位数据                            │
│  - index.bin:MetricName 倒排索引,用于标签查询                            │
│  - items.bin:实际数据,commonPrefix 压缩                                  │
│  - lens.bin:行长度,用于随机访问                                          │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

小贴士为什么 Part 文件是 .tar 格式?

.tar 格式最初用于将多个文件打包成一个便于传输。在 MergeSet 中,.tar 格式用于将 metaindex、index、items、lens 四个文件打包成一个 Part。.tar 本身不压缩,压缩发生在 items.bin 内部的 ZSTD 压缩。

3.3 commonPrefix 压缩:存储空间减少 30-50%

MergeSet 的另一大优化是 commonPrefix 压缩,在 lib/mergeset/block_header.go 中实现:

 // lib/mergeset/block_header.go
// commonPrefix 压缩原理

// BlockHeader 结构:
type BlockHeader struct {
    // commonPrefix 长度:当前 Block 与前一个 Block 的公共前缀长度
    CommonPrefixLen uint64
    
    // 第一个 Item 的元信息
    FirstItemMeta uint64
    
    // 最后一个 Item 的元信息
    LastItemMeta uint64
    
    // Items 数量
    ItemsCount uint64
    
    // 压缩类型(NearestDelta / ZSTD / None)
    CompressionType uint64
}

// 压缩示例:
// 未压缩:[2024-01-01 10:00:00] cpu_usage{job="prometheus",instance="localhost:9090"} 95.5
// 压缩后:[2024-01-01 10:00:00] cpu_usage{job="prometheus",instance="localhost:9090"} 95.5
//           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 全部存储
//           ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
//           只存一次,后面的 Block 只存差异

// 实际效果:
// - 时序数据通常有很长的共同前缀(标签名+标签值模式固定)
// - commonPrefix 压缩可以将存储空间减少 30-50%
// - 同时保持解码速度(不需要解压,只需提取差异部分)

四、设计取舍与适用场景

设计精髓

MergeSet 的设计哲学是"用空间换时间,用简单换性能"。放弃 WAL 换来的是写入的极致简单;只合并不分层换来的是查询的可预测性。

4.1 MergeSet 的优势

优势原因实际效果
写入简单 不需要 WAL,不需要复杂的两阶段写入 写入延迟极低
查询稳定 扫描大文件而非多层小文件 P99 延迟可控
资源高效 commonPrefix + ZSTD 双重压缩 存储空间减少 50%+
运维简单 无分层,无复杂合并策略 调参少,易理解

4.2 MergeSet 的取舍

取舍描述影响
无 WAL 进程崩溃可能丢失最多 1 秒数据 不适用于数据零丢失的金融场景
Part 数量膨胀 高写入场景下,小 Part 产生速度快于合并 需要足够的 CPU 进行后台合并
查询全扫描 查询需要遍历所有 Part(虽然有 BloomFilter) 超多 Part 时查询变慢

4.3 适用场景对比

  VictoriaMetrics MergeSet vs Prometheus LSM Tree vs InfluxDB TSM
┌─────────────────────────────────────────────────────────────────────────────┐
│                                                                             │
│  场景                              │  Prometheus  │  InfluxDB  │    VM       │
│  ─────────────────────────────────┼─────────────┼────────────┼────────────│
│  超大规模 series (1000万+)        │      ⚠️     │     ⚠️     │     ✅      │
│  高写入吞吐 (100万 samples/s)     │      ⚠️     │     ⚠️     │     ✅      │
│  稳定 P99 查询延迟                │      ⚠️     │     ⚠️     │     ✅      │
│  低内存占用                       │      ⚠️     │     ⚠️     │     ✅      │
│  数据零丢失要求                   │      ✅     │     ✅     │     ⚠️      │
│  运维简单优先                     │      ⚠️     │     ⚠️     │     ✅      │
│  开源生态成熟                     │      ✅     │     ⚠️     │     ⚠️      │
│                                                                             │
│  ✅ 强烈推荐  ⚠️ 可用但非最优  ❌ 不推荐                                 │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

五、面试高频提问

面试问答本节精选面试高频问题,直接命中面试官想听到的答案

Q1:MergeSet 和 LSM Tree 的核心区别是什么?

核心区别在于"是否分层"。LSM Tree 将数据分成多层(L0, L1, L2...),每层大小递增,合并时需要逐层向上合并。MergeSet 不分层,所有 Part 文件在同一目录,小 Part 持续合并成大 Part,合并方向是单向的(Small → Big → Bigger)。这种设计让 MergeSet 的查询更稳定(只需扫描少量大文件),而 LSM Tree 的查询需要遍历多个层级。

Q2:MergeSet 为什么不需要 WAL?

因为 MergeSet 使用 InmemoryPart 的原子性刷盘代替 WAL。lib/mergeset/inmemory_part.go 中,MustStoreToDisk() 函数先写临时文件,刷盘成功后原子性 rename 到正式文件名。进程崩溃时,临时文件会被忽略,不会污染数据。代价是最多丢失 1 秒数据。

Q3:MergeSet 的 Part 文件为什么是 .tar 格式?

.tar 用于打包 metaindex、index、items、lens 四个文件。每个 Part.tar 内部包含 4 个二进制文件:metaindex.bin(Block 索引)、index.bin(标签倒排索引)、items.bin(实际时序数据)、lens.bin(行长度)。.tar 本身不压缩,压缩发生在 items.bin 内部,通过 commonPrefix + ZSTD 实现 30-50% 的存储节省。

Q4:MergeSet 如何保证查询性能?

通过 BloomFilter + 顺序读大文件 + commonPrefix 压缩。查询时先通过 BloomFilter 快速判断某个 Part 是否可能包含目标数据,跳过不相关的 Part。对于可能包含数据的 Part,顺序读取大文件比 LSM Tree 的随机读多层小文件更高效。commonPrefix 压缩减少了解码数据量,进一步提升查询速度。

全篇必记口诀

MergeSet 的精髓是"只合并不分层":InmemoryPart 原子性刷盘替代 WAL,BloomFilter 过滤减少 IO,commonPrefix + ZSTD 双重压缩节省空间。记住这个口诀:"合并不分层,刷盘无 WAL,大文件顺序读"