项目地址

https://github.com/mababaNiubi/qv-lite.git
https://gitee.com/QUIWEI/qv-lite.git

目录

特性

  • 嵌入轻量 — 专为边缘网关、工业控制器、IoT 设备等 CPU/内存/磁盘受限场景设计。
  • 代码精简 — 紧凑、可审计的核心代码,极少三方依赖。直观的目录布局, 不依赖重型数据库引擎,便于排查、备份和迁移。
  • 高性能写入 — 单线程同步设计。单点写入性能 800 万+ 点/秒
  • 自适应类型编码 — 运行时自动识别输入数据的结构,根据数据类型选择合适的压缩编码器,达到最优存储效率。
  • 高压缩率 — 紧凑的数据编码加二次块压缩,实现极致的存储效率。
  • 多表支持 — K/V列式数据库,管理多个独立表,每个表可拥有独立的 Schema 定义和列集合。
  • 降采样查询 — 长时间范围查询支持滑动窗口聚合(avg / min / max)。
  • 数据过期 — 可配置的基于时间的数据自动清理。
  • 去重与最小间隔 — 可配置的去重窗口和最小写入间隔,防止重复数据。
  • 块级索引 — 基于二分搜索的块索引,快速过滤时间范围,无需扫描无关数据。

安装

go get github.com/mababaNiubi/qv-lite/tsdb

使用方法

打开与关闭

import (
    "context"
    "github.com/mababaNiubi/qv-lite/tsdb"
)

db, err := tsdb.Open(tsdb.Config{
    Path: "./qvLite-data",
}, context.Background())
if err != nil {
    panic(err)
}
defer db.Close()

Open 在给定路径创建或打开数据库。default 表在首次使用时自动创建。Close() 会刷新所有缓冲数据并释放资源。

写入

import (
    "time"
    "github.com/mababaNiubi/variant"
)

// 写入 default 表(tableName = "" 或 "default")
written, err := db.Write("", "sensor_temp", time.Now().UnixNano(), variant.New(25.6))

// 写入指定的表
written, err := db.Write("metrics", "cpu_usage", time.Now().UnixNano(), variant.New(42.5))
  • tableName — 空字符串或 "default" 写入自动创建的默认表。
  • tag — 时序标识(如传感器名称、指标 key)。
  • timestamp — Unix 时间戳,单位为纳秒
  • value — 使用 variant.New(v) 包装任意支持的 Go 值。
  • 返回 (true, nil) 表示写入成功;(false, nil) 规则跳过。

查询

// 范围查询 + 降采样 — 适用于长时间范围
points, err := db.Query("default", "sensor_temp",
    time.Now().Add(-1*time.Hour).UnixNano(), // startTime (ns)
    time.Now().UnixNano(),                   // endTime (ns)
    1000,                                    // maxNumber 返回最大点数
    tsdb.AvgFusion,                          // 聚合模式
    nil,                                     // 条件过滤(nil = 无过滤)
)

// 获取全部原始数据(不降采样)
points, err := db.QueryAll("default", "sensor_temp",
    time.Now().Add(-30*time.Minute).UnixNano(),
    time.Now().UnixNano(),
    nil,
)

// 获取 tag 的最新值
point, err := db.QueryLatest("default", "sensor_temp")
if point != nil {
    fmt.Printf("latest: time=%d, value=%v\n", point.Tms, point.V)
}
方法说明
Query(tableName, tag, startTime, endTime, maxNumber, polymerization, cond)范围查询。时间跨度 > 1 小时时返回最多 maxNumber 个降采样点;≤ 1 小时时直接返回全部原始数据。
QueryAll(tableName, tag, startTime, endTime, cond)返回范围内的全部原始数据点,不做数量限制。
QueryLatest(tableName, tag)返回指定 tag 的最新一条数据。

所有时间戳单位为纳秒(UnixNano)。maxNumber 设为 0 时默认 10000。

表管理

// 创建简化指标表,拥有最高的写入性能
err := db.CreateTable(tsdb.TableInfo{
    ColumnAttribute: tsdb.ColumnAttribute{
        Name: "device",
        Desc: "attributes",
        Type: tsdb.ColumnTypeFloat,
        FloatPrecision: 2,
    },
})


// 创建多列表
err = db.CreateTable(tsdb.TableInfo{
    ColumnAttribute: tsdb.ColumnAttribute{
        Name: "metrics",
        Desc: "Dev01.CPU",
        Type: tsdb.ColumnTypeStructure,
        Structure: []tsdb.ColumnAttribute{
            {Name: "value",   Type: tsdb.ColumnTypeFloat, FloatPrecision: 2},
            {Name: "quality", Type: tsdb.ColumnTypeInt},
            {Name: "status",  Type: tsdb.ColumnTypeString},
        },
    },
})

// 创建自适应 Schema 的表(运行时自动发现字段,比固定结构慢)
err = db.CreateTable(tsdb.TableInfo{
    ColumnAttribute: tsdb.ColumnAttribute{
        Name: "events",
        Desc: "动态事件日志",
        Type: tsdb.ColumnTypeUnknown, // 自适应 — 运行时发现字段格式
    },
})

数据类型

常量类型说明
ColumnTypeUnknown (0)自适应运行时自动检测嵌套结构
ColumnTypeInt (1)整数有符号 64 位整数
ColumnTypeFloat (2)浮点数64 位浮点数,可配置小数精度
ColumnTypeString (3)字符串UTF-8 字符串
ColumnTypeBool (4)布尔值true / false
ColumnTypeJson (5)JSON任意嵌套 variant
ColumnTypeStructure (6)固定结构体预定义列 Schema

聚合

常量说明
AvgFusion0滑动窗口取平均值
MinFusion1滑动窗口取最小值
MaxFusion2滑动窗口取最大值

条件过滤

查询方法的 cond 参数支持按值过滤数据点:

// 等值过滤 — 仅返回 value == "ok" 的点
cond := tsdb.Condition{
    Operator: tsdb.OpEqual,
    Value:    variant.New("ok"),
}
points, err := db.QueryAll("default", "status", startTs, endTs, cond)

// 逻辑与 — 组合多个条件
logicalCond := tsdb.LogicalCondition{
    Op:   tsdb.LogicalAnd,
    Cond: []any{
        tsdb.Condition{Operator: tsdb.OpGreaterThan, Value: variant.New(80)},
        tsdb.Condition{Operator: tsdb.OpLessThan,    Value: variant.New(100)},
    },
}
points, err := db.QueryAll("default", "cpu", startTs, endTs, logicalCond)

配置项

Config

字段类型默认值说明
Pathstring"./qvLite-data"数据库数据存储路径
WalConfigWalConfigWAL 配置(见下)
MaxSegmentSizeint6467108864 (64MB)单个段文件最大大小(字节)
MaxSegmentTimeIntervalint640(不限制)单个段文件最大时间跨度(秒)
MaxStorageTimeint643600(1 小时)拒绝远大于当前时间的数据写入
ExpirationMinuteTimeint640(禁用)数据过期时间(分钟),写入时清理
DedupWindowMsint640(禁用)去重窗口(毫秒),两次写入窗口内相同的值跳过写入
MinIntervalMsint640(禁用)两次写入之间的最小间隔(毫秒),小于这个值跳过写入
SecondaryCompressionNamestring"zstd"块压缩算法:"zstd""lz4""snappy""gzip""none"

WalConfig

字段类型默认值说明
MaxCacheSizeint6467108864 (64MB)WAL 内存缓存最大大小(字节)
MaxFileNumberint最大 WAL 文件数量
CloseBufferboolfalse是否关闭内存 WAL 缓冲
MaxBufferBatchSizeint10000排序刷盘前的最大缓冲条目数

CloseBuffer 行为说明:

场景CloseBuffer = trueCloseBuffer = false
乱序写入同 key 下禁止乱序时间写入MaxBufferBatchSize 批处理范围内允许乱序写入
写入/查询性能较低较高
异常中断数据安全可能丢失部分缓冲数据
内存占用通常在 MaxCacheSize 范围内MaxCacheSize 的 3~4 倍

可通过限制 MaxCacheSize 大小来控制数据库内存使用。

压缩算法

数据类型策略
时间戳delta-of-delta + 缩放 + simple8b / RLE
整数delta + zigzag + simple8b / RLE
浮点数XOR-delta + 尾数截断(基于小数精度)
布尔值位打包(每字节 8 个)或全 True/False RLE
字符串uvarint 长度前缀拼接 + Snappy
JSONvariant 二进制序列化 + LZ4
固定结构体按列独立编码,每列使用对应类型的子编码器
自适应结构体自动发现 Map 中的列,递归嵌套,自描述格式

数据编码

磁盘布局

{db_path}/
  table.json              # 表元数据
  default/                # 默认表目录
    meta.json             # 标签字典
    data/                 # 段数据文件
      1234567890.tsb      # 段文件(BlockFile 格式)
      1234567890.tsb.idx  # 段块索引
    wal/                  # WAL 目录
      1234567890.wal      # WAL 日志文件

段文件 (.tsb) — BlockFile

每个 .tsb 文件是一个 BlockFile,包含若干压缩数据块,末尾附有块级索引:

┌──────────────────────────────────────────────────────┐
│                    BlockFile (.tsb)                    │
├───────────────┬──────────────────────────────────────┤
│ FileHeader    │ Magic、版本、事务状态、压缩算法等       │
├───────────────┼──────────────────────────────────────┤
│ 物理块 0       │ ┌ LenPrefix ───────────────────────┐ │
│               │ │ CompressedPayload                │ │
│               │ │  = 多个逻辑块拼接后一起压缩         │ │
├───────────────┤ └──────────────────────────────────┘ │
│ 物理块 1       │ ┌ LenPrefix ───────────────────────┐ │
│               │ │ CompressedPayload                │ │
│               │ │  ...                              │ │
├───────────────┤ └──────────────────────────────────┘ │
│     ...       │                                       │
├───────────────┼──────────────────────────────────────┤
│ 块索引         │ Count + IndexEntry × M               │
│               │  · RawOff  → 解压后逻辑偏移            │
│               │  · CompOff → 压缩后物理偏移            │
│               │  · CompLen → 压缩后大小               │
│               │  · Crc32   → 校验码                   │
└───────────────┴──────────────────────────────────────┘

块索引位于文件末尾,记录每个物理块的物理位置与解压后逻辑偏移的映射关系,支持按偏移直接定位到任意块。

段索引文件 (.tsb.idx)

.tsb 段文件配套存在,用于时间范围和标签维度的快速过滤:

┌──────────────────────────────────────────────────┐
│              段索引 (.tsb.idx)                      │
├──────────────┬───────────────────────────────────┤
│ Header       │ Magic、BlockCount、MinTime、MaxTime │
├──────────────┼───────────────────────────────────┤
│ Entry [0]    │ TagCode、MinTime、MaxTime、         │
│              │ Offset、DataSize                   │
├──────────────┼───────────────────────────────────┤
│ Entry [1]    │ ...                                │
├──────────────┼───────────────────────────────────┤
│     ...      │  (每个逻辑块一条记录)               │
└──────────────┴───────────────────────────────────┘

每条记录描述一个逻辑块属于哪个标签列、覆盖的时间范围、以及在 .tsb 文件中的位置。查询时通过二分搜索快速定位相关块并跳过无关数据。

逻辑块

列编码器写入的基本单元,多个逻辑块拼接后压缩为一个物理块:

┌──────────────────────────────────────────────────────────┐
│                     一个逻辑块                              │
├────────────────┬─────────────────────────────────────────┤
│ SegmentHeader  │           Payload(压缩后的数据)          │
│                │  ┌─────────────────┬──────────────────┐ │
│  · TagCode     │  │ CompressedValue │ CompressedTime   │ │
│  · MinTime     │  │ (类型编码器)    │ (delta-of-delta)│ │
│  · MaxTime     │  └─────────────────┴──────────────────┘ │
│  · DataSize    │                                          │
│  · CRC32       │                                          │
└────────────────┴──────────────────────────────────────────┘

负载格式

标量类型(Int、Float、String、Bool、Json):

┌────────────────────┬─────────────────────┬──────────────────┐
│ ValueByteLength    │ CompressedValueData │ CompressedTime   │
│  (长度前缀)        │ (类型编码器压缩)    │ (TimeEncoder)   │
└────────────────────┴─────────────────────┴──────────────────┘

结构体类型(固定 / 自适应结构体):

┌────────┬──────────┬──────────┬─────┬──────────────────┬──────────────────┐
│ Marker │ Field0Len│ Field1Len│ ... │ FieldData Concat │ CompressedTime   │
│ (1B)   │  (各 8B)           │     │ (各字段独立压缩)  │ (TimeEncoder)   │
└────────┴──────────┴──────────┴─────┴──────────────────┴──────────────────┘

结构体的每个字段使用各自类型对应的压缩器独立编码后拼接。Marker 标记区分固定结构体(预注册列)和自适应结构体(运行时自动发现 Map 中的列)。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐