sfsDb 时序数据处理指南

6 阅读7分钟

sfsDb 时序数据处理指南

1. 时序数据概述

时序数据是按时间顺序记录的一系列数据点,具有以下特点:

  • 数据按时间顺序生成
  • 通常具有高写入频率
  • 常用范围查询(如查询最近N小时数据)
  • 数据量随时间线性增长
  • 支持多种精度需求(秒、毫秒、微秒)

2. sfsDb 时序数据支持

2.1 时间类型支持

类型存储格式优点缺点
time.Time字符串(格式:2006-01-02 15:04:05可读性好,易于理解占用空间大(约19字节),比较速度慢
int64(时间戳)8字节二进制存储效率高,比较速度快可读性差,需要转换为时间格式查看

2.2 sfsDb 时间序列化机制

sfsDb 通过 util.AnyToBytes 函数将不同类型转换为字节数组:

// time.Time 序列化
case time.Time:
    return []byte(val.Format(time.DateTime)) // 格式:2006-01-02 15:04:05

// int64 序列化
case int64:
    return Int64ToBytes(val, EndianOrder) // 直接转换为8字节

3. 时间戳 vs time.Time 作为主键

3.1 性能对比

特性时间戳(int64time.Time
存储大小8字节约19字节
比较速度极快(直接数值比较)较慢(字符串字典序比较)
范围查询高效(基于数值范围)低效(基于字符串范围)
精度控制灵活(秒、毫秒、纳秒)固定(sfsDb中为秒级)
写入性能
查询性能

3.2 推荐选择

推荐使用 int64 时间戳作为主键,原因如下:

  • 更高的存储效率
  • 更快的查询性能
  • 更好的扩展性
  • 支持更高精度需求
  • 更适合时序数据特征

4. 时序数据最佳实践

4.1 表设计

// 创建时序数据表
table, _ := TableNew("iot_sensor_data")

// 设置字段,使用时间戳作为主键
fields := map[string]any{
    "timestamp": int64(0), // 时间戳主键(毫秒级)
    "sensor_id": "",      // 传感器ID
    "temperature": 0.0,   // 温度
    "humidity": 0.0,      // 湿度
    "pressure": 0.0,      // 压力
}
table.SetFields(fields)

// 创建时间戳主键索引
table.CreatePrimaryKey("timestamp")

// 可选:为传感器ID创建索引,优化按传感器查询
table.CreateSimpleIndex("idx_sensor_id", "sensor_id")

4.2 数据写入

// 生成毫秒级时间戳
timestamp := time.Now().UnixMilli()

// 准备传感器数据
sensorData := map[string]any{
    "timestamp":   timestamp,
    "sensor_id":   "sensor_001",
    "temperature": 25.5,
    "humidity":    60.2,
    "pressure":    1013.25,
}

// 插入数据
_, err := table.Insert(&sensorData)
if err != nil {
    log.Printf("插入数据失败: %v", err)
}

4.3 数据查询

4.3.1 范围查询
// 查询最近1小时的数据
oneHourAgo := time.Now().Add(-time.Hour).UnixMilli()
query := map[string]any{"timestamp": oneHourAgo}

// 使用 GreaterThan 操作符查询
iter := table.Search(&query, util.GreaterThan)
defer iter.Release()

// 获取结果
records := iter.GetRecords(true)
log.Printf("最近1小时数据量: %d", len(records))
4.3.2 多条件查询
// 查询特定传感器最近1小时的数据
oneHourAgo := time.Now().Add(-time.Hour).UnixMilli()
query := map[string]any{
    "timestamp": oneHourAgo,
    "sensor_id": "sensor_001",
}

// 范围+等值查询
iter := table.Search(&query, util.GreaterThan)
defer iter.Release()
4.3.3 时间窗口查询
// 查询昨天的数据
yesterday := time.Now().AddDate(0, 0, -1)
startOfDay := time.Date(yesterday.Year(), yesterday.Month(), yesterday.Day(), 0, 0, 0, 0, yesterday.Location())
endOfDay := startOfDay.Add(24 * time.Hour)

startTimestamp := startOfDay.UnixMilli()
endTimestamp := endOfDay.UnixMilli()

// 构造查询条件
startQuery := map[string]any{"timestamp": startTimestamp}
endQuery := map[string]any{"timestamp": endTimestamp}

// 先查询大于等于开始时间的数据,然后在应用层过滤
iter := table.Search(&startQuery, util.GreaterThan)
defer iter.Release()

// 在应用层过滤小于结束时间的数据
var filteredRecords []map[string]any
for _, record := range iter.GetRecords(true) {
    if ts, ok := record["timestamp"].(int64); ok && ts < endTimestamp {
        filteredRecords = append(filteredRecords, record)
    }
}

log.Printf("昨天数据量: %d", len(filteredRecords))

4.4 数据更新与删除

// 更新指定时间戳的数据
updateData := map[string]any{
    "timestamp":   targetTimestamp, // 用于定位记录
    "temperature": 26.0,           // 更新温度值
    "humidity":    58.5,           // 更新湿度值
}

err := table.Update(&updateData)
if err != nil {
    log.Printf("更新数据失败: %v", err)
}

// 删除指定时间戳的数据
deleteData := map[string]any{"timestamp": targetTimestamp}
err := table.Delete(&deleteData)
if err != nil {
    log.Printf("删除数据失败: %v", err)
}

4. 时序数据优化建议

4.1 存储优化

  1. 使用适当的时间精度

    • 秒级:time.Now().Unix()(适合低频数据)
    • 毫秒级:time.Now().UnixMilli()(适合大多数IoT场景)
    • 微秒级:time.Now().UnixMicro()(适合高精度传感器)
  2. 数据压缩考虑

    • 合理设计表结构,避免冗余字段
    • 考虑定期归档或删除旧数据
    • 使用数据类型最小化存储(如使用 float32 而非 float64

4.2 查询优化

  1. 创建合适的索引

    • 为主时间字段创建主键索引
    • 为常用查询字段创建辅助索引
    • 避免创建过多索引,影响写入性能
  2. 优化查询范围

    • 尽量缩小查询时间范围
    • 使用 GetRecords(true) 只获取需要的字段
    • 考虑使用分页查询处理大量数据
  3. 批量操作

    • 批量插入数据,减少磁盘IO
    • 批量更新或删除,提高操作效率

5. 时序数据应用场景

5.1 IoT 设备数据

  • 场景:传感器实时采集数据(温度、湿度、压力等)
  • 特点:高写入频率,低查询频率
  • 最佳实践:使用毫秒级时间戳作为主键,定期聚合数据

5.2 系统监控数据

  • 场景:服务器CPU、内存、磁盘使用率监控
  • 特点:中等写入频率,高查询频率
  • 最佳实践:使用秒级时间戳,创建多级索引

5.3 金融交易数据

  • 场景:股票价格、交易量记录
  • 特点:极高写入频率,高精度要求
  • 最佳实践:使用微秒级时间戳,优化查询性能

5.4 日志数据

  • 场景:应用日志、访问日志
  • 特点:高写入频率,复杂查询需求
  • 最佳实践:使用秒级时间戳,结合全文索引

6. 时序数据示例项目

6.1 IoT 传感器数据采集

package main

import (
    "fmt"
    "log"
    "time"
    
    "github.com/liaoran123/sfsDb/engine"
    "github.com/liaoran123/sfsDb/storage"
    "github.com/liaoran123/sfsDb/util"
)

func main() {
    // 初始化数据库
    if _, err := storage.OpenDefaultDb("./iot_data"); err != nil {
        log.Fatalf("初始化数据库失败: %v", err)
    }
    defer storage.CloseDb()

    // 创建传感器数据表
    table, err := engine.TableNew("sensor_data")
    if err != nil {
        log.Fatalf("创建表失败: %v", err)
    }

    // 设置字段
    fields := map[string]any{
        "timestamp":   int64(0), // 时间戳主键
        "sensor_id":   "",      // 传感器ID
        "temperature": 0.0,     // 温度
        "humidity":    0.0,     // 湿度
        "pressure":    0.0,     // 压力
    }
    if err := table.SetFields(fields); err != nil {
        log.Fatalf("设置字段失败: %v", err)
    }

    // 创建主键索引
    if err := table.CreatePrimaryKey("timestamp"); err != nil {
        log.Fatalf("创建索引失败: %v", err)
    }

    // 模拟100条传感器数据
    for i := 0; i < 100; i++ {
        timestamp := time.Now().Add(time.Duration(i) * time.Second).UnixMilli()
        data := map[string]any{
            "timestamp":   timestamp,
            "sensor_id":   fmt.Sprintf("sensor_%03d", i%10),
            "temperature": 20.0 + float64(i%10),
            "humidity":    50.0 + float64(i%20),
            "pressure":    1000.0 + float64(i%50),
        }
        if _, err := table.Insert(&data); err != nil {
            log.Printf("插入数据失败: %v", err)
        }
    }

    // 查询最近30秒的数据
    thirtySecondsAgo := time.Now().Add(-30 * time.Second).UnixMilli()
    query := map[string]any{"timestamp": thirtySecondsAgo}
    iter := table.Search(&query, util.GreaterThan)
    defer iter.Release()

    records := iter.GetRecords(true)
    log.Printf("最近30秒数据量: %d", len(records))
    for _, record := range records {
        log.Printf("时间: %d, 传感器: %s, 温度: %.1f", 
            record["timestamp"], record["sensor_id"], record["temperature"])
    }
}

7. 总结

sfsDb 完全支持时序数据处理,通过以下最佳实践可以获得更好的性能:

  1. 使用 int64 时间戳作为主键,提高存储和查询效率
  2. 选择合适的时间精度,根据场景需求平衡精度和性能
  3. 创建合理的索引,优化查询性能
  4. 优化查询范围,减少返回数据量
  5. 使用批量操作,提高写入效率
  6. 定期维护数据,归档或删除旧数据

通过遵循上述指南,可以充分利用 sfsDb 的轻量级优势,为各种时序数据场景提供高效的存储和查询解决方案。

8. 未来优化方向

sfsDb 在时序数据处理方面还有以下优化空间:

  1. 专门的时序数据压缩算法:针对时序数据的特点优化存储
  2. 自动数据过期机制:支持配置数据保留策略
  3. 时序数据聚合功能:内置常用聚合函数(如平均值、最大值、最小值)
  4. 时间窗口查询优化:原生支持时间窗口查询
  5. 更细粒度的时间精度支持:优化纳秒级时间戳处理
  6. 时序数据可视化工具:集成简单的数据可视化功能

这些优化将使 sfsDb 在时序数据处理方面更具竞争力,能够更好地满足各种时序数据场景的需求。