NanoTDB – Golang 仅追加时间序列数据库

2026-05-15 1 阅读 aymanhs72
NanoTDB 一种小型嵌入式时间序列数据库,专为资源受限的主机(Raspberry Pi、边缘节点、物联网网关)而设计。运行时没有外部依赖。所有数据都位于单个根目录下的纯文件中。架构概述 引擎 ├── “prod” 数据库 → WAL (prod.wal) + 目录 (catalog.json) + 分区 .dat 文件 ├── “传感器” 数据库 → WAL + 目录 + 分区 .dat 文件 └── “内部” → 引擎自身指标(相同的布局,从不暴露给用户) 引擎是单一入口点。它拥有一组命名数据库,并根据线路协议前缀将摄取的样本路由到正确的数据库。每个数据库具有三个存储层: 层 文件 用途 WAL .wal 崩溃安全:在进入页面之前记录每个样本 Catalog Catalog.json 映射指标名称 ↔ 紧凑 MetricID + 值类型 数据文件 data-.dat 从内存中刷新的不可变压缩页面 数据流 Ingest ( AddLine ) AddLine("prod/room.temp 21.5 1715000000000000000") │ ├─ 解析行协议 → dbName="prod" metric="room.temp" ts=… value=21.5 ├─ getOrCreateDB → 打开或重用 prod 数据库 ├─ WAL 追加 → 将紧凑记录写入 prod.wal (崩溃安全) ├─ addToOpenDay → 追加到今天存储桶的内存页面└─ 如果页面已满 → 压缩 + 将页面帧写入 data-.dat 重置 WAL(不再需要重放) 整个写入流中每个指标的时间戳必须单调非递减。无序或过时的样品将被拒绝。重播(引擎打开时) 当数据库打开时,如果数据文件落后,则 WAL 会重播到内存页面中。该目录用于解析省略它们的指标的 ValueType(紧凑格式优化)。完整重播后,引擎准备好接受新的写入。 Query ( QueryRange ) QueryRange("prod", "room.temp", fromTS, toTS, stride,callback) │ ├─ 在 [fromTS, toTS] 中迭代 UTC 天 │ ├─ 打开 data-.dat → 扫描页帧头 │ │ 跳过时间窗口外的帧(不解压) │ │ 解压缩 + 扫描匹配帧 │ └─ 检查内存中今天数据的页面 └─ 对每个样本调用回调(如果步长 > 1,则每 N 个) 行协议 DB/metric.name value [ts] DB — 数据库名称(在第一次写入时自动创建) metric.name — 任意指标标识符(斜杠分隔的命名空间效果很好) value — 整数( 42 、 -7 )或浮点数( 3.14 、 1e-3 )。整数文字总是创建 int32 指标; float 文字创建 float32 指标。类型在第一次写入时固定;同一指标的混合类型是错误的。 ts — Unix 纳秒时间戳(可选;默认为 time.Now() )示例: prod/room.temp 21.5 1715000000000000000sensors/Pressure.hpa 1013internal/batch.size 256i i 后缀强制对看起来像浮点数的值进行整数解释。 WAL 格式(紧凑 v2) 每条记录都是 uvarint 长度前缀,后跟固定布局有效负载: [uvarint: Payload_len] [payload] 有效负载布局:偏移大小字段 0 2 MetricID uint16 LE 2 3 TS delta uint24 LE 距基线的纳秒 5 1 CompactTL 标志位 7 = 新基线,位 6 = 新指标 6 8 基线 TS int64 LE (仅当设置位 7 时) — var name_len+name+vtype (仅当设置位 6 时) — 4 值 int32 或 float32 LE,始终存在 热路径(已知度量,相同基线): 2+3+1+4 = 10 字节 + 1 varint = 11 字节。每当时间戳间隙超过 ~16.7 ms (2²⁴ ns) 时,就会在每个 WAL 的第一个记录上发出新基线。典型的传感器流在基线重置之间需要数百秒的时间。已知指标(之前在会话中看到)省略名称和值类型;这些字段在重播期间从目录中恢复。磁盘布局 /engine.toml — 引擎配置(首次启动时自动创建) /catalog.json — 指标注册表:名称 → id + 类型 manifest.toml — 每个数据库设置(保留、WAL、页限制) .wal — 预写日志(单个可重用文件) data-.dat — 已完成分区的压缩页框 数据文件是页框的仅附加序列: Frame = PageHeader(18 字节)+compressed_len(uvarint)+S2 压缩有效负载+CRC32(4 字节)有效负载是交错(MetricID、时间戳、值)三元组的平面数组,按时间戳排序。 S2 压缩通常可实现实际传感器数据的 3-4 倍。配置 (engine.toml) 首次启动时在 /engine.toml 处自动创建。关键设置:关键默认效果engine.listen:8428 HTTP服务器地址wal.max_segment_size 67108864(64 MiB)页面刷新后重置之前的WAL大小wal.fsync_policy段segment = fsync on WAL重置; Always = fsync everyappendurationdurability.profile strict strict/balanced/throughput(见下文) stats.enabled true 将引擎自身指标发送到内部数据库 stats.interval 30s 刷新统计数据的频率 持久性配置文件:配置文件 页面文件 fsync 目录 fsync strict yes yes 平衡 yes no 吞吐量 no no 每个数据库设置(保留、分区、WAL 跳过窗口、页面刷新阈值)