工业时序数据生命周期:Schema 演进、历史查询与冷归档

数据写入成功只是开始。物模型增加字段后表结构如何变化,历史查询返回的是哪段时间,归档失败时能否删除原数据,才决定了数据是否可以继续使用。我在多存储后端的实现中,把这三个问题分别处理。

更新于 2026-09-09。本文依据项目实现与设计记录,区分现有机制和仍需加强的保证,不提供未经验证的性能结论。

配置表与业务表采用不同的变更方式

配置表结构由程序版本决定,适合用 Flyway 管理。采集业务表则由 Product 定义在运行时派生:属性对应数据列,产品增加字段时,表结构也需要协调。

项目因此把运行时业务表交给 Schema 协调器。这不是说 Flyway 不能处理动态场景,而是避免让两套机制同时决定同一张表应该长什么样。代价是需要自己处理差异检测、变更失败与可追溯性。

协调分成三个职责:

组件 职责
SchemaIntrospector 读取数据库实际结构
SchemaReconciler 比较模型与实际结构,按后端能力执行变更
SchemaDiffService 返回只读差异报告,不执行 DDL

只读 diff 与执行变更分开,让排查问题不必先修改数据库。MISSING_IN_DB 表示模型有字段但库中没有;MISSING_IN_PRODUCT 表示库中仍有历史列;类型差异则需要结合数据库能力判断。

类型归一化不等于变更安全

原实现按类型族归一化,能减少部分方言表达差异导致的误报。但仅比较类型名有盲区:VARCHAR(255) 与 VARCHAR(256) 的容量不同,缩短长度也不能当成无害差异。

我更倾向于分开讨论两件事:检测到什么差异,以及允许执行什么迁移。

变更 应核查的内容
新增列 默认值、可空性、旧记录读取行为
扩大长度或精度 后端支持范围、锁与重建成本
缩短长度或改变类型 存量数据可转换性、截断风险、失败回滚
删除字段 历史查询依赖、备份、保留要求

当前 auto-drop-columns 默认关闭,避免产品删除属性时顺带删除历史列。但它只约束删列,不能保证其他 DDL 无损。差异报告显示 CLEAN,也只代表实现所比较的维度一致,不等于完成了数据质量验收。

查询时聚合:先控制输出,再评估计算成本

一台设备每秒产生一点,七天就是 604,800 个时间点。这是采样频率推导的数量,不是本项目压测结果。让浏览器接收全部原始点通常没有必要,因此查询接口提供 interval、aggregation 和 fill。

当前选择查询时聚合,减少预聚合任务与派生表的维护。代价是每次查询都可能产生聚合成本;高并发和长时间范围下,仍需评估预聚合、缓存或限制查询范围。

TDengine 使用时间窗口能力,关系数据库使用时间桶表达式。以固定五分钟桶为例,概念公式是:

bucketStart = floor(epochSeconds / 300) × 300

只把时间格式化到分钟,不能形成五分钟桶。跨数据库比较时,还要对齐时区、区间端点和窗口起点;“每 24 小时”也不必然等于某个时区的自然日。

聚合方式必须符合字段语义。温度的均值与最大值回答不同问题;状态位不能默认按温度处理;累计计数也不能不考虑复位就求差。当前通用接口提供聚合能力,不代表已实现所有点位的语义约束。

项目 TDengine 查询默认填充策略为 LINEAR,这是需要谨慎使用的实现选择。插值值不是采集值,尤其不能用平滑曲线掩盖断线。更具体的建模与窗口讨论见TDengine 实践。

数据源一致,不只是返回相同 JSON

HistoryQueryService 当前优先查询可用存储;失败时返回内存数据,并使用 memory-fallback 标记,未启用存储时使用 memory-only。

这个标记很重要:内存回退只取最近 N 条,当前调用没有把原来的 start/end 和聚合要求传入缓存。因此它不能被解释为数据库查询的等价结果。

对前端和 AI 调用方,我的建议是:

另一个容易遗漏的问题是排序:ORDER BY time ASC LIMIT 10 获取较早十条,不是最近十条。当前“最新值”的数据库回退路径也应结合底层排序继续验证,不能仅凭方法名判断行为。

冷归档:文件写入与数据库删除不是一个事务

项目以 DataArchiveService 隔离归档介质,当前实现为 CSV,提供归档、状态和回读入口。CSV 易检查、依赖少,但不具备列式分析、跨文件事务或自动 Schema 演进的全部能力。

归档后删除的关键不是“过期就删”,而是确认哪些记录已经可靠保存。设计记录中曾修正过按整个过期截止时间删除的问题:分批归档时,截止时间之前可能仍有未写出的记录。

使用本批最大时间戳收窄删除范围是一项改进,仍需要检查同时间戳多条数据是否跨批、文件是否持久化、进度标记能否恢复,以及重跑是否重复追加。进度标记存在不等于已经提供严格幂等。

对当前方案,归档默认关闭、归档后删除默认关闭。S3 或 Parquet 可以作为后续介质实现,但不能把接口可扩展写成已经支持这些后端。

从什么证据判断生命周期可用

我会分别验证三类结果:

  1. Schema:新增属性后可写可读;不兼容变更被明确识别;失败不会被当成协调成功。
  2. 查询:固定样本能验证时间边界、排序、窗口和缺测;数据库失败时来源与覆盖范围仍准确。
  3. 归档:回读结果与原记录对应;中断、重跑和分批边界不会造成未归档数据被删除。

当前实现提供了这些能力的入口,但文章不把它们概括成“自动治理且不丢数据”。系统架构的价值,在于让失败位置与数据含义能够被检查。

实现依据:storage/schema、storage/archive、HistoryQueryService 及各存储后端。理解它们与采集配置的关系,可先阅读多协议系统架构。