InfluxDB 2 存储、查询与优化

在工业MES等应用中,时序数据库是会被频繁使用的数据存储方案,由于工业环境中设备数量多,需要采集的数据点位数量庞大,因此在于数据存储和查询、统计过程中需要有一些基本方法。

1. 数据存储

关于InfluxDB的数据存储,以下是一些需要遵循的基本原则:

1.1 数据存储时长

不同类型数据的重要性是不同的,因此有些类型的数据需要保留的时间要求更长一些(例如趋势和统计数据,往往需要存储一年以上的时间),以方便管理者了解宏观信息与趋势,而明细类数据存储时间则往往要求上会更低一些(3个月~半年时间)。

根据 InfluxDB 的存储方案,数据清理是可以通过 Bucket 的存储时长设置自动完成的,不需要人工主动干预,因此统计类数据往往需要存储到独立的 Bucket 中。实际操作层面,可以考虑将分钟、小时、日、月维度的数据存储到不同的Bucket中,以方便独立控制数据存储的时长。周维度的存储数据由于比较容易从日维度数据进行聚合,可以考虑不进行单独存储。

1.2 存储空间与查询性能

InfluxDB 的数据存储与查询性能与 Series 存储的所用的 tag 数量及 tag 值数量的笛卡尔积相关,因此当tag组合数量笛卡尔积值比较大的时候(e.g. > 50万),就需要非常谨慎地设计 TAG 的存储了。特别是数据查询的内存使用也与此相关。以下是一些常用的应用措施:

  1. 擅用 Measurement

    Series 组合笛卡尔积的计算通常是按照 Measurement 进行划分的,因此首先考虑将不同业务类型的数据存储到隔离的 Measurement 当中。

  2. 结合其他存储辅助方案

    由于TAG值的组合空间可能巨大,但是在实际应用中可能出现的情况又是非常稀疏的,因此某些属性的值并不适合作为TAG值进行存储。

    举一个实际的例子,假设某工厂有近百台设备,每台设备有数千个需要进行状态采集的子节点,则 设备ID编号 x 子节点编号 的组合数量就接近100万了。特别是单个子节点的故障概率更不高,实际产生故障的节点数据是非常稀疏的,但是由于数据存储的时长足够长,因此就比较难以预估实际的数据组合情况。对于这一类数据,就适用于其他存储方案相结合的方式,因为某一时段内,实际要查询的故障子节点数据结果并不多。

2. 数据查询与性能优化

以实际场景中遇到的应用案例:为某10台同型号设备实时记录异常状态,每天可能产生2万条异常记录,假设每条记录查询数据结果长度为200字节,则一次全量查询所返回的数据长度就会达到 3~4 MB 的长度,耗时700ms左右,在用户交互过程中体验不太能接受。这个时候需要结合实际场景考虑以下方案:

  1. 查询结果属性精简

    InfluxDB API 的数据查询由于考虑到通用性,返回结果往往是比较冗余的,例如查询某时段内的数据明细每条记录都会返回对应的 start, stop 时间,_field 通常也是在查询指令中已经指定,对于业务并没有实际价值,网络传输开销也会更高,因此首先在结果集中使用 keep 仅保留所需的字段(或是通过 drop 去掉不需要的数据字段)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from(bucket: "energy")
    |> range(start:-1h)
    |> filter(fn: (r) =>
        r._measurement == "device_data" and 
        r.group_name == "device_xxx" and 
        r._field == "energy"
    )
    |> stateDuration(fn: (r) => r._value < low_power, column: "abnormal_duration_s", unit: 1s)
    |> filter(fn: (r) => r.abnormal_duration_s >= 0)
    |> map(fn: (r) => ({
        r with
        period_group: if r.abnormal_duration_s == 0 then 1 else 0
    }))
    |> cumulativeSum(columns: ["period_group"])
    |> group(columns: ["device_name", "period_group"])
    |> last()
    |> filter(fn: (r) => float(v: r.abnormal_duration_s) > 60)
    |> map(fn: (r) => ({
        start_time: time(v: uint(v: r._time) - uint(v: r.abnormal_duration_s) * uint(v: 1000000000)),
        end_time: r._time,
        device_name: r.device_name,
        duration_minutes: uint(v:float(v: r.abnormal_duration_s) / 60.0 + 0.5)
    }))
    |> keep(columns: ["device_name", "duration_minutes"])
  1. 根据时间窗口进行聚合

    InfluxDB 非常擅长时间维度的数据聚合,因此数据查询并不需要返回时间段内每条数据的明细,可以按照固定的时长间隔汇总该时间段内的数据均值、加总等。这在于曲线图展示中是比较常用的场景,例如查询小时、3小时、24小时、周维度数据的数据曲线,可以通过提前计算出数据时间间隔作为聚合时间窗口。

    由于曲线图实际能够展示的数据有限,一个比较实用的小习惯是,根据目前查询的时长除以预设的数据点数量(例如 500个点)。24小时曲线的聚合窗口可以设置为 24小时 / 500 ,限定数据长度 limit 1000。

  2. 定时任务 + 预计算

    如果只需要查询统计数据,而不需要查看每条记录的具体明细,通过定时任务预计计算出用户高频查询的数据是一个很好的习惯。

3. 数据统计

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus