@Greptime: 我们更新了对GreptimeDB内部存储引擎Mito2的深入解析,涵盖LSM-tree写入路径、列式Parquet SST等……
摘要
GreptimeDB的Mito2存储引擎采用LSM-tree设计,结合列式Parquet SST、三级扫描剪枝以及TWCS压缩。该博客文章对其架构进行了全面解析。
查看缓存全文
缓存时间: 2026/06/22 17:38
我们重新梳理了 GreptimeDB 存储引擎 Mito2 的深度解析。
LSM-tree 写入路径。列式 Parquet SST 文件。三级扫描剪枝(时间范围 → min/max 统计信息 → Puffin 索引)。带 TTL 的时间窗口合并策略。
完整设计解读: https://greptime.com/blogs/2022-12-21-storage-engine-design…
#可观测性 #开源 #数据库工程
GreptimeDB 存储引擎设计 —— 针对时序场景的优化
来源:https://greptime.com/blogs/2022-12-21-storage-engine-design GreptimeDB 通过名为 Mito 的存储引擎存储时序数据。Mito 负责接收写入、保证持久化、组织数据以方便后续读取,并在后台清理旧文件或重叠文件。
时序数据有其独特的特征:写入频繁,大多数行按时间戳顺序追加;查询通常要求指定时间范围、部分序列和少量列;删除和更新虽然存在,但远少于插入。Mito 正是围绕这些特征设计,而非模仿通用的事务型存储引擎。
本文自上而下解析 Mito:为什么采用 LSM-tree 设计、数据如何流经写入路径、数据在存储文件中的布局、扫描如何跳过不必要的数据,以及索引和压缩机制在引擎中的角色。
为什么 LSM 适合时序场景 (https://greptime.com/blogs/2022-12-21-storage-engine-design#why-lsm-fits-time-series)
Mito 采用 LSM-tree 风格设计。在 LSM 树中,新数据首先写入内存和持久化日志,随后被刷入不可变文件。这非常适合时序工作负载,因为它保持写入路径高速,并将许多小写入转化为更大的顺序文件写入。
Mito 按 Region 存储数据。Region 是表数据的物理分片。每个 Region 拥有自己的元数据、内存缓冲区、SST 文件、Manifest 和存储选项。SST 是不可变的存储文件,Manifest 是记录 Region 变更(如新刷入的文件或压缩移除的文件)的元数据日志。
下图展示了 Mito 的主要组件以及数据在内存、持久化日志、SST 文件、元数据和对象存储之间的流动。
存储引擎架构
写入路径包含三个主要步骤:
- Mito 首先将接收的行写入 WAL。WAL(预写日志)在数据被刷入不可变文件之前确保写入持久化。根据部署方式,WAL 可存储在本地磁盘、云块存储(如 EBS)或 Kafka 上。
- Mito 将相同行插入可变 Memtable。Memtable 是内存缓冲区,用于服务最近写入并在持久化前积累数据。
- 当 Memtable 准备持久化时,Mito 将其刷入 Parquet SST 文件,并在 Region Manifest 中记录文件元数据。
这种设计简化了数据摄入:在写入持久化到 WAL 并在内存中可见后确认写入,然后在后台将缓冲数据转化为列式文件。
数据如何组织 (https://greptime.com/blogs/2022-12-21-storage-engine-design#how-data-is-organized)
Mito 针对常见的时序读取模式组织数据:缩小时间范围、选择一组序列,然后仅读取所需列。
在一个 Region 内,行从可变 Memtable 移动到不可变 Memtable,再移动到 SST 文件。新刷入的 SST 文件可能在键和时间范围上重叠。后台压缩随后会将它们重写为更少、更有序的文件。
下图展示了一个 SST 文件的逻辑布局以及读取时使用的元数据。
SST 布局
两个最重要的排序概念是主键和时间索引。
主键通常由标识时间序列的标签列组成,例如 host 和 region。具有相同主键的行属于同一序列。时间索引是时间戳列,它对序列内的点进行排序,并且是时间范围查询的主要剪枝维度。
例如,考虑一个存储主机指标的表:
CREATE TABLE host_metrics (
host STRING,
region STRING,
ts TIMESTAMP TIME INDEX,
cpu DOUBLE,
memory DOUBLE,
PRIMARY KEY (host, region)
);
从概念上讲,行按主键分组,并按时间排序:
| host | region | ts | cpu | memory |
|---|---|---|---|---|
| host-a | us-east | 10:00 | 0.42 | 7.1 |
| host-a | us-east | 10:01 | 0.47 | 7.4 |
| host-a | us-west | 10:00 | 0.31 | 6.8 |
| host-b | us-east | 10:00 | 0.80 | 8.6 |
在存储中,host 和 region 是主键列,ts 是时间索引,cpu 和 memory 是字段列。Mito 还存储内部列,例如编码的主键数据、序列号和操作类型,以便在从多个 Memtable 和 SST 文件读取时正确合并、去重和应用删除。
每个 Parquet SST 被分割成行组(Row Group)。行组是 Parquet 可以高效读取或跳过的主要单元。对于每个行组,Parquet 存储列统计信息,例如最小值、最大值和空值计数。Mito 还记录文件级元数据,包括文件时间范围、行数、文件大小、行组数量、可用索引以及主键范围(如果可用)。
时间窗口是布局中的另一个重要部分。时间窗口是一个时间戳范围,稍后用于压缩和保留策略。
Mito 如何扫描数据 (https://greptime.com/blogs/2022-12-21-storage-engine-design#how-mito-scans-data)
扫描从查询可见的数据开始:当前的 Memtable 和相关的 SST 文件。它还知道谓词和时间范围。Mito 利用这些信息来减少需要打开、解码和过滤的数据量。
下图展示了剪枝流程。关键点是 Mito 不依赖单一的剪枝机制,而是结合了时间范围、行组统计信息和索引。
扫描剪枝流水线
以下查询为例:
SELECT ts, cpu
FROM host_metrics
WHERE host = 'host-a'
AND region = 'us-east'
AND ts >= '2026-05-29 10:00:00'
AND ts < '2026-05-29 11:00:00'
AND cpu > 0.7;
Mito 首先从时间索引的谓词中提取时间范围。存储的时间范围与查询范围不重叠的文件和 Memtable 可以在打开读取器之前跳过。这通常是时序数据中最廉价且最重要的剪枝步骤。
接下来,Mito 使用 min/max 统计信息。如果行组的统计信息证明没有任何行能匹配谓词,则跳过该行组。例如,cpu 最大值 0.6 的行组无法匹配 cpu > 0.7。行组统计信息是粗粒度的,但成本低,并且在数据具有局部性时效果良好。
当统计信息不足以进行更精细的剪枝时,索引提供了更选择性的剪枝。索引是为一个或多个列构建的辅助数据,使得扫描在解码数据列之前能够排除更多行或行组。Mito 将索引数据存储在 Puffin 文件中。Puffin 是一种辅助文件格式,用于将索引数据放在 SST 元数据旁边,而不将其嵌入主 Parquet 数据文件中。
在扫描计划阶段,Mito 检查查询谓词是否可以使用可用索引。索引结果与经过时间范围和 min/max 剪枝后剩余的行组相结合。然后读取器扫描剩余数据并应用精确过滤。
对于可能包含更新或删除的表,Mito 还根据序列信息和操作类型合并和去重行。仅追加表在查询不需要有序序列输出时可以以更少的排序工作进行扫描。
索引与压缩 (https://greptime.com/blogs/2022-12-21-storage-engine-design#indexes-and-compaction)
索引是存储布局的一部分。当 Mito 写入 SST 文件时,它也可以为受益于选择性剪枝的列构建索引文件。SST 元数据记录了存在哪些索引以及它们覆盖哪些列。
这种设计非常适合对象存储。数据文件和索引文件可以一起管理,在有用时一起缓存,并在元数据证明它们与查询无关时一起跳过。
压缩保持 LSM 树的健康。没有压缩,许多小或重叠的 SST 文件会积累,使读取成本更高。在压缩过程中,Mito 读取选定的 SST 文件,合并它们的行,写入新的 SST 文件,必要时更新相关的索引元数据,并通过 Region Manifest 提交结果。
下图展示了 Region 级别的时间窗口。每个窗口是一个时间戳桶,可能包含多个 SST 文件。TTL 可以从较旧的桶中移除过期文件,而压缩可以合并桶内重叠的 SST 文件。
时间窗口压缩与 TTL
Mito 默认使用时间窗口压缩策略(TWCS)作为默认压缩策略。TWCS 按时间戳范围对候选文件进行分组,因此同一窗口内的文件会被一起压缩。当表配置了 TTL 时,压缩还可以移除数据超出保留窗口的过期 SST 文件。
总结 (https://greptime.com/blogs/2022-12-21-storage-engine-design#summary)
Mito 是 GreptimeDB 为时序工作负载设计的存储引擎。它将快速的 LSM 风格写入路径与列式 SST 布局、Region 级元数据、行组统计信息、可选的索引文件以及时间窗口压缩相结合。
该设计专注于避免不必要的工作。时间范围跳过整个文件;min/max 统计信息跳过行组;索引剪枝选择性谓词;压缩将重叠文件重写为更清晰的时间窗口并移除过期数据。
这些机制共同使 GreptimeDB 能够处理高频时序数据摄入,同时保持常见的时间范围和序列导向查询的高效性。
如果你对 GreptimeDB 的存储引擎感兴趣,欢迎加入我们的 Slack (https://greptime.com/slack) 或 GitHub (https://github.com/GreptimeTeam/greptimedb)。
相似文章
@Greptime: GreptimeDB 的扁平格式查询现在可以在任何列上预过滤——标签、字段、时间戳——而不仅仅是主键。而…
GreptimeDB 的扁平格式查询现在支持在任何列(标签、字段、时间戳)上预过滤,而不仅仅是主键,性能提升高达 4.5 倍。此外,mito2 存储引擎移除了其遗留扫描路径,清理了约 1800 行代码。
@Greptime: GreptimeDB 六月的大部分工作归结为一个想法:如果过滤器无法到达数据,那么它就没用。在分布式查询中…
GreptimeDB 通过启用远程动态过滤器在运行时下推到 datanode 扫描,并优化优化器使其在 MergeScan 包装远程计划之前运行,从而确保过滤器到达数据,提升了分布式查询性能。JSON v2 列现在支持类型提示。
@Greptime: DataFusion 在去年九月向上游合并了动态过滤下推功能。GreptimeDB v1.0 将其接入 Mito 扫描层。…
GreptimeDB v1.0 集成了 DataFusion 的动态过滤下推功能,通过将运行时边界推送到扫描层来加速 TopK 查询,在 50 亿行的 trace 表上将查询时间从 29 秒降低到 0.21 秒。
@Greptime: GreptimeDB v1.1.0 发布,PromQL rate/increase 查询速度提升高达97%,整体查询时间降低20-40%,TS… 上速度提升高达4.5倍
GreptimeDB v1.1.0 已发布,提供高达97%的PromQL查询加速,整体查询时间降低20-40%,在TSBS扫描密集型查询上性能提升高达4.5倍,并支持对现有表进行在线重分区。
@Greptime: GreptimeDB v1.1:你不再需要在创建表时锁定分区布局。以前,只有使用 PARTITION ON COLUMNS 创建的表才能…
GreptimeDB v1.1 引入了对现有表的在线重新分区、增量 Flow 读取、面向 LLM 的语义层以及稳定性改进。