3. 历史库数据模型与存储结构

历史库不是“把实时值写进数据库”这么简单。电力监控系统每秒可能产生数万到数十万个测点值,历史库要在多年跨度上保存这些数据,同时支持按测点、按时间范围的快速查询。因此,历史库的核心是时序数据模型:以“测点标识 + 时间戳 + 值 + 质量码”为最小记录单元,围绕时间维度组织存储与索引。理解这一点,才能理解不同历史库在索引、精度和压缩上的差异。

时序数据模型:一条记录包含什么

在电力监控场景中,一条历史记录通常包含四个基本字段:测点标识(Tag/Point ID)、时间戳(Timestamp)、数值(Value)、质量码(Quality)。测点标识不是测点名称字符串,而是历史库内部的整型句柄或哈希键,用于快速定位存储位置。质量码用于标记该值是否有效、是否被替代、是否处于抖动或人工置数状态。例如某遥测点“110kV 甲线有功功率”在 10:00:00.000 的值为 42.35 MW,质量码为 Good,这就是一条完整记录。

时序模型还决定了“一个测点一条时间线”的组织方式。每个测点独立维护按时间递增的数据块,写入时追加,查询时按时间范围扫描。这种模型天然适合电力监控中“单点高频、多点并发”的写入模式,也决定了索引和压缩必须围绕测点与时间两个维度设计。

测点索引:如何快速找到一条时间线

历史库面对的第一个工程问题是:给定测点名,如何快速找到它的存储位置。常见做法是两级索引:第一级用哈希表或 B+ 树将测点名字符串映射为整型 Tag ID;第二级用 Tag ID 直接定位到该测点的数据块链表或分区目录。PI 和 eDNA 这类传统实时库通常采用“测点句柄 + 时间索引”的结构,测点句柄在系统启动或测点创建时分配,写入和查询都直接使用句柄,避免字符串比较开销。

InfluxDB 和 TDengine 则更偏向“标签 + 时间”的索引模型。InfluxDB 用 measurement + tag set 构成 series key,再对 series key 建倒排索引;TDengine 用“超级表 + 子表”模型,每个子表对应一个测点,子表名即测点标识,底层按时间分区。两种模型都能支持按测点快速定位,但传统实时库更强调单点写入延迟,现代时序库更强调标签过滤和聚合查询。

时间戳精度:毫秒、微秒与对齐

时间戳精度直接影响存储开销和查询语义。电力监控中,SOE(事件顺序记录)通常要求毫秒级甚至微秒级精度,而普通遥测历史可能只需秒级或毫秒级。若历史库统一使用 64 位微秒时间戳,每个点额外增加 8 字节;若使用 32 位秒级时间戳,则只能覆盖约 68 年,且无法表达毫秒事件。

实际选型时要注意三点:一是精度定义,是存储精度还是显示精度;二是时间对齐,例如按 1 分钟聚合时,时间戳应对齐到整分钟边界;三是时区与夏令时,历史库内部通常统一用 UTC 存储,查询时再转换为本地时间。若忽略这些,趋势曲线会出现“同一时刻两个值”或“跨天查询少一小时”的问题。

压缩策略:旋转门、死区与增量

历史库的压缩不是通用文件压缩,而是有损/无损结合的时序压缩。常见策略有三类:

  • 旋转门压缩(Swinging Door):用前后两点连线的斜率变化判断是否保留中间点。若新点落在上一点形成的“门”内,则不存储;超出则保留并重新开门。它适合模拟量趋势,能大幅减少存储量,但会丢失局部细节。
  • 死区压缩(Deadband):设定一个阈值,例如 0.5% 或 0.1 MW,只有变化超过阈值才记录。死区可以是绝对死区或百分比死区,常用于变化缓慢的温度、液位等测点。
  • 增量压缩(Delta / XOR):对时间戳和数值做差分或异或编码,再配合变长整数存储。Gorilla、TDengine 的列式压缩都采用类似思路,适合高频、数值连续变化的测点。

三种策略可以组合使用。例如先做死区过滤,再对保留点做旋转门,最后对时间戳做增量编码。配置时要明确:死区阈值、旋转门压缩偏差、是否保留原始值、是否允许查询时插值。若死区设得过大,趋势曲线会“台阶化”;若旋转门偏差过小,压缩率下降。工程上通常先用 0.1%~0.5% 死区试运行,再根据存储增长和曲线形态调整。

不同历史库的存储结构对比与选型

PI、eDNA、InfluxDB、TDengine 是电力监控中常见的历史库。PI 和 eDNA 属于传统实时历史库,强调高可靠写入、测点句柄索引和旋转门压缩,适合与 SCADA 紧密耦合、测点规模在十万到百万级的场景。InfluxDB 属于现代时序数据库,采用 LSM Tree + TSM 文件,支持标签索引和类 SQL 查询,适合云边协同、需要灵活聚合和可视化的场景。TDengine 采用“一设备一子表”的列式存储,按时间分区,压缩率高,适合测点数量大、写入吞吐高的电力物联网场景。

选型时不要只看压缩率,要综合评估:写入吞吐、查询延迟、测点规模、压缩率、运维复杂度、与现有 SCADA 的接口方式、是否支持乱序写入、是否支持数据保留策略。例如,若系统已有 PI 且测点规模稳定,继续用 PI 更稳妥;若新建系统需要弹性扩展和标准 SQL,TDengine 或 InfluxDB 更合适;若需要与 Kafka、Flink 等流处理组件集成,现代时序库的生态更友好。

历史库数据模型与存储结构对比 时序记录模型 Tag ID | Timestamp | Value | Quality 测点句柄 + 时间递增数据块 单点高频、多点并发写入 质量码标记有效/替代/抖动 示例:10:00:00.000 42.35 MW Good 测点索引与时间戳精度 一级:名称→Tag ID 二级:Tag ID→数据块 PI/eDNA:句柄 + 时间索引 InfluxDB:series key 倒排索引 TDengine:超级表 + 子表分区 精度:秒 / 毫秒 / 微秒,UTC 存储 压缩策略 旋转门:斜率变化保留关键点 死区:变化超阈值才记录 增量:时间戳/数值差分编码 可组合:死区→旋转门→增量 PI / eDNA / InfluxDB / TDengine 存储结构对比 历史库 存储结构 索引方式 压缩特点 适用场景 PI 测点句柄 + 时间块 句柄 + 时间索引 旋转门 + 死区 SCADA 紧耦合 eDNA 实时库 + 历史归档 测点 ID + 时间 死区 + 旋转门 电力实时监控 InfluxDB LSM Tree + TSM 文件 series key 倒排索引 增量 + 块压缩 云边协同、聚合查询 TDengine 一设备一子表 + 列式 子表名 + 时间分区 列式 + 增量压缩 海量测点、高吞吐

选型检查表:把决策落到参数上

下面给出一个可直接用于选型讨论的检查表,字段均围绕历史库实际运行指标,不引入无关管理项。使用时逐项填写,再综合判断。

检查项 需要确认的内容 示例/判断依据
测点规模 总测点数、每秒新增点数 10 万点,峰值 5 万点/秒
时间戳精度 秒级、毫秒级还是微秒级 SOE 需毫秒级,遥测可秒级
压缩策略 死区阈值、旋转门偏差、是否增量 死区 0.2%,旋转门偏差 0.5%
查询模式 单点趋势、多点聚合、标签过滤 按线路聚合 15 分钟均值
写入可靠性 是否允许乱序、断点续传、副本数 支持乱序写入,副本数 2
保留策略 原始数据保留多久,降采样保留多久 原始 1 年,1 分钟聚合 5 年
接口与生态 是否支持 OPC UA、Kafka、SQL 需与现有 SCADA 和流处理集成
实践建议:在正式选型前,用真实测点做一次小规模写入测试。记录 24 小时内的原始点数、压缩后点数、磁盘占用和典型查询响应时间。若压缩率低于 5:1,检查死区是否过小;若查询延迟超过 2 秒,检查索引是否命中、分区是否合理。测试数据应明确标注为示例环境结果,不能直接当作生产实测值。

回到本节的核心:历史库的数据模型决定了“怎么存”,索引决定了“怎么找”,时间戳精度决定了“多准”,压缩策略决定了“多省”,而选型决定了“在给定约束下哪一项最优先”。把这五个问题分开评估,再组合判断,比单纯比较“哪个库更好”更接近工程实际。