MynaDB项目解析,用Rust、Arrow与Parquet构建时序数据库基础架构

MynaDB 是一个基于 Rust 实现的单机时序数据库原型,通过 HTTP 接口接收 InfluxDB Line Protocol 格式数据,利用 Arrow 内存格式组织数据,以 Parquet ...

互联网/IT 阅读量: 1.4万

时序数据库(Time-Series Database, TSDB)在监控、IoT 和金融等领域承担着高吞吐写入与高效聚合查询的关键任务。近年来,随着 Rust 生态在系统级编程中的成熟,一批开源项目开始尝试用 Rust 重构传统 TSDB 的核心逻辑。MynaDB 即是其中一项代表性实践——它并非商业产品,而是一个教学导向的原型系统,旨在清晰呈现从数据摄入到 SQL 查询的完整链路如何在现代技术栈下协同工作。

MynaDB 的设计目标明确:接收一条标准的 Line Protocol 数据(如 cpu,service=web,host=server01 usage=23.5 1766793600000000000),经由 HTTP 接口写入,最终支持类似 SELECT host, AVG(usage) FROM demo.web.cpu GROUP BY host 的 SQL 查询。为实现这一闭环,第一版系统被划分为四个核心模块:HTTP 接口层、Parser 与 DataCache、ParquetWriter 以及 QueryEngine。

该架构图清晰展示了数据流路径:写入请求经 Axum 框架处理后,由 Parser 解析为结构化数据点(Point),再按逻辑表与日期分区归集至内存缓存;当批次积累完成,ParquetWriter 将其落盘为按日期分区的 Parquet 文件;查询请求则交由 QueryEngine,后者注册表元信息并调用 DataFusion 执行 SQL 计划。值得注意的是,初版仅支持对已落盘数据的查询,内存中未刷出的数据暂不参与查询——这一限制将在后续优化中解除。

数据模型的设计是整个系统的基石。Line Protocol 将一条记录拆解为 measurement(指标名)、tags(维度标签)、fields(数值字段)与 timestamp(时间戳)四部分。MynaDB 将其映射为 Rust 结构体:

关键在于如何将这些原始字段转化为关系型表结构。项目采用一套确定性规则:URL 路径 /write/demo 中的 demo 作为 catalog;tag 中的 service=web 提取为 schema;measurement cpu 对应 table 名;最终形成三段式表名 demo.web.cpu。字段类型也需严格对齐:host 映射为 UTF8 字符串列,usage 为浮点列,timestamp 为纳秒级时间戳列。这种强约定避免了运行时推断带来的歧义,也为后续列式存储打下基础。

写入流程强调批处理与分区意识。HTTP 请求体可能包含多条指标,系统逐条解析后,依据统一时区(如 UTC)将时间戳转换为日期整数(如 2025-12-27 → 20251227),结合 catalog、schema 与 table 构成 PartitionKey。同一分区内的数据被聚合成多个 RecordBatch,存入哈希表缓存。这种设计确保后续落盘时无需二次分类,提升 I/O 效率。

上图直观呈现了从原始写入请求到物理路径的映射过程:POST /write/demo 中的 demo 为 catalog;service=web 定义 schema;cpu 为表名;时间戳决定分区目录 date=2025-12-27;最终生成路径 data/demo/web/cpu/date=2025-12-27/。同时,字段类型也被明确指定:timestamp 为时间列,tags 为字符串列,fields 为对应数值类型列。

Line Protocol 到逻辑表的映射规则

真正体现现代数据系统优势的是 Arrow 与 Parquet 的协同使用。HTTP 接口按行接收数据,但分析型查询通常只需访问少数几列。Arrow 的 RecordBatch 将行式输入转为列式内存布局——例如两条记录会生成 host: ["server01", "server02"] 与 usage: [23.5, 42.0] 两个等长数组,并辅以 Schema 描述列名与类型。这不仅节省内存,更使后续向量化计算成为可能。

如图所示,连续到达的两行指标数据,在按时间、标签分组后,被重组为统一 Schema 下的列数组集合:timestamp、measurement、host、region 等列为字符串或时间类型,usage 为浮点数组。所有列数组长度一致,缺失值以 NULL 填充,且顺序与 Schema 严格对齐——这是构建 RecordBatch 的前提。该结构可直接序列化为 Parquet 文件,亦可被 DataFusion 无缝消费。

文章配图

查询引擎部分依赖 Apache DataFusion,一个用 Rust 编写的嵌入式 SQL 引擎。MynaDB 在启动时扫描 Parquet 分区目录,自动注册表元信息;收到 SQL 请求后,DataFusion 负责词法/语法解析、逻辑计划生成、物理计划优化与执行。由于数据已按列存储且分区有序,GROUP BY host 与 AVG(usage) 等聚合操作可高效完成。当前版本暂未接入内存缓存,因此查询结果仅覆盖已落盘数据;未来可通过将 DataCache 注册为临时表或实现流式查询接口来弥合这一鸿沟。

行数据到 Arrow 列数组的转换过程

从技术选型看,MynaDB 的组合具有典型现代数据系统特征:Rust 提供内存安全与并发性能保障;Tokio + Axum 构建异步 HTTP 服务;Arrow 实现零拷贝列式内存交换;Parquet 提供高压缩比与谓词下推能力;DataFusion 则补足 SQL 层能力。这种“轻量组合”替代了传统 TSDB 中耦合紧密的单体架构,更易于模块替换与功能扩展。

行业层面,此类开源原型的价值在于降低理解门槛。InfluxDB、TimescaleDB 等成熟系统代码庞杂,而 MynaDB 以千行级核心逻辑清晰展示数据流转本质:从协议解析、内存组织、文件持久化到查询执行,各环节职责分明、接口清晰。对于开发者而言,它既是学习时序数据建模的范本,也为定制化轻量 TSDB 提供了可复用的组件参考。

需要指出的是,MynaDB 当前仍处于早期阶段:缺乏索引、压缩策略粗粒度、无副本与容错机制,也不支持实时聚合窗口。但它成功验证了一个关键命题:在 Rust 生态支持下,构建一个具备基本读写与 SQL 能力的时序数据库核心骨架,技术路径已十分清晰。后续迭代若加入 LSM-Tree 写优化、倒排索引加速 tag 查询、或与 DuckDB 集成增强分析能力,有望进一步逼近生产可用水平。