深入专题:时序、告警与容量¶
这篇从一个具体规模开始,推导数据模型、写入、查询、聚合、告警和 OTA 为什么需要不同的处理路径。
场景与假设¶
平均心跳¶
平均遥测¶
每天遥测消息¶
如果把 20 个指标拆成 20 行:
这说明数据模型选择会让行数产生数量级差异。
不要只用原始 JSON 大小估算¶
存储占用还包括:
- 行头和对齐;
- 时间、tenant_id、device_id;
- 索引;
- WAL;
- 副本;
- 备份;
- 临时排序;
- 聚合表。
600 字节消息不等于数据库只增加 600 字节。
粗估时可以使用放大系数,之后必须用真实样本导入验证。
宽表、窄表与 JSON¶
宽表¶
优点:
- 一条上报一行;
- 常见指标查询简单;
- 行数较少。
缺点:
- 不同型号字段差异大;
- Schema 变化;
- 稀疏列。
窄表¶
优点:
- 指标灵活;
- 通用查询。
缺点:
- 行数极大;
- 单次上报拆多行;
- 多指标组合查询更复杂。
JSON/JSONB¶
优点:
- 灵活;
- 保留原始结构。
缺点:
- 高频聚合和索引代价;
- 类型和单位治理困难;
- 随意查询可能很慢。
常见折中:
- 原始消息归档到对象存储;
- 关键稳定指标进入宽表;
- 少量扩展字段进入 JSON;
- 最新状态单独保存;
- 根据业务聚合。
写入路径为什么要批量¶
每条消息单独事务:
事务提交、WAL 刷盘和网络往返成本很高。
可以:
取舍:
- 批次越大,吞吐更高;
- 等待越久,写入可见延迟更高;
- Consumer 崩溃时未提交批次会重投;
- 批量仍需 message_id/约束处理重复。
分区和 Chunk¶
按时间分区使查询:
只扫描相关时间范围。
Chunk 太小:
- 表和元数据太多;
- 规划成本高。
Chunk 太大:
- 热索引难以留在内存;
- 压缩、删除和维护粒度太粗。
选择需要结合:
- 每天写入量;
- 活跃查询时间范围;
- 内存;
- 保留策略;
- 压缩周期。
索引从查询反推¶
常见查询:
SELECT time, temperature
FROM telemetry
WHERE tenant_id = ?
AND device_id = ?
AND time >= ?
AND time < ?
ORDER BY time;
候选索引围绕:
如果系统保证 device_id 全局唯一,tenant_id 可能不必重复进入所有索引;但权限查询仍必须验证租户。
不要为 20 个指标分别无脑加索引。写入会被索引维护拖慢。
最新值与历史值分离¶
页面看当前温度:
错误:
即使有索引,也会给高频列表查询增加压力。
维护最新状态:
更新需要版本/时间判断,避免晚到旧数据覆盖新值。
如果设备时钟不可信,使用 server_received_time 或同时保存两者。
聚合怎样保持可合并¶
5 分钟聚合:
小时平均不能简单平均十二个“5 分钟平均”,因为每个窗口样本数可能不同:
这就是为什么聚合表应保留可继续合并的中间量。
迟到数据¶
设备断网后补发一小时前数据:
- 原始表按事件时间写入旧 Chunk;
- 已生成的 5 分钟聚合需要刷新;
- 实时看板是否显示补发值要明确;
- 告警是否补触发要有业务规则。
“历史数据正确”和“实时告警不扰民”可能需要不同策略。
告警如何避免风暴¶
单设备抖动¶
使用:
- 持续时间;
- 连续次数;
- 迟滞;
- 冷却时间。
大规模共同故障¶
一个网关离线,500 台子设备离线。
如果每台都:
会形成通知风暴。
需要拓扑和根因抑制:
告警去重键¶
持续异常更新同一活动告警的次数和 last_seen,恢复后关闭。下次重新异常创建新的 epoch。
OTA 对容量的冲击¶
10,000 台设备下载 50MB 固件:
如果 10 分钟内同时下载:
因此 OTA 需要:
- CDN/对象存储;
- 分批和速率限制;
- 设备随机抖动;
- 地区和网络分组;
- 下载断点续传;
- 不通过业务 API 服务器转发整个固件;
- 监控下载、校验、安装、重启各阶段。
容量不是只有“能扛住”¶
还要定义降级:
- 遥测积压时先保证命令和心跳;
- 报表变慢时不影响实时状态;
- AI 分析拥堵时回到规则告警;
- OTA 高峰限制新批次;
- 非关键 WebSocket 推送可以抽样或合并。
这要求不同流量使用独立 Topic、队列、消费者和资源配额,避免互相拖垮。
一份可信容量报告¶
至少包含:
- 假设;
- 平均与峰值;
- 每层放大;
- 单机实测;
- 瓶颈;
- 扩展方式;
- 降级;
- 监控;
- 未验证风险。
不要只写“Redis 单机十万 QPS,所以可以支持十万设备”。设备数、消息率、查询率和状态变化率不是同一个量。
深度自测¶
- 一条 600 字节 JSON 为什么不等于数据库增加 600 字节?
- 宽表、窄表、JSON 各自最主要的取舍是什么?
- 批量写入为什么提高吞吐,又引入什么风险?
- 为什么小时平均不能直接平均 5 分钟平均?
- 晚到数据对历史聚合和实时告警有什么不同影响?
- 网关离线怎样避免 500 条通知?
- 10,000 台设备 OTA 为什么不能同时开始?
- “十万设备”为什么不是一个足够的容量指标?
参考答案要点
- 还有行元数据、索引、WAL、副本、备份和临时空间。
- 宽表查询简单但 Schema 固定;窄表灵活但行数大;JSON 灵活但治理和聚合成本高。
- 减少事务和网络往返,但增加可见延迟、内存批次和崩溃重投。
- 每个窗口样本数可能不同,要用总 sum/总 count。
- 历史应修正聚合;实时告警是否补触发要防止迟到风暴。
- 识别拓扑根因,主告警通知,子设备事件聚合抑制。
- 会形成带宽和服务洪峰,应灰度、限速和随机抖动。
- 还需连接率、消息率、消息大小、查询率、变化率、峰值和下游放大。
完成标准¶
- 能完成消息、行数、存储和带宽估算
- 能根据查询选择宽表、窄表或混合模型
- 能解释批量写、分区、索引和聚合
- 能处理迟到数据与最新值
- 能设计告警抑制和 OTA 灰度
- 能写一份包含降级的容量报告