ThingsBoard 核心优化:通过聚合表提升物联网遥测数据统计性能

本文针对物联网海量遥测数据的查询性能瓶颈,系统阐述通用聚合表的设计与实现路径,为企业级自定义报表统计提供一套高性能、可扩展的优化解决方案。

问题描述

前期基于遥测数据开发统计报表时,即发现查询性能存在严重短板;究其原因,设备以 1 分钟频次上报心跳数据,导致ks_tv表年数据量高达 5 亿条。当查询一个支局的整体功率因素时,由于设备数量多,常规查询已无法完成计算。而电量尖峰平谷的时段化统计,又必须依赖全量明细数据,无法通过粗粒度聚合规避性能问题。

  1. 当前设备数:实际物理设备数170个。
设备 类型 个数
网关 逻辑 2
用电主机 物理 17
传感器 逻辑 441
用电精灵 物理 153
用电精灵通道 逻辑 306
合计 逻辑 919
  1. 当前遥测表数据量统计(12月第二批设备接入)
月份 SQL 记录条数
2025.10 select count(*) from ts_kv_2025_10 3259,6974
2025.11 select count(*) from ts_kv_2025_11 3040,1911
2025.12 select count(*) from ts_kv_2025_12 8784,3354
  1. 数据量推算

当前按1000个设备计算,每个设备只上传一种遥测数据,1次/分钟,年数据量5亿。

bash 复制代码
日:1000 * 60 * 24 = 1,440,000 (144万)
月:1,440,000 * 30 = 43,200,000 (4千万)
年:43,200,000 * 12 = 518,400,000 (5亿)

问题分析

  1. 该类统计需求在业务场景中具备高度普遍性,且凸显三大核心特征:
  • 指标计算逻辑多元,有功电量需统计累计值、功率因素需计算平均值,各类指标均需按需适配对应的统计方法;
  • 统计维度灵活,可按资产目录或组织维度开展核算,因此需以设备作为基础统计单元,借助资产与设备的关联关系,实现设备数据的层级聚合;
  • 报表需支撑历史数据回溯统计,需要纵向缩短时间维度的数据,规则链更倾向于资产间汇总,故此方案不可采用。
    基于上述特征,本需求的核心优化目标明确为:通过对遥测数据实施按日汇总预处理,实现报表统计性能的质效提升
  1. 数据汇总结构
  • 既有遥测数据ts_tv的数据结构

    字段:entity_id,key,ts,bool_v,str_v,long_v,dbl_v,json_v

  • 新增通用遥测汇总数据ts_kv_daily的结构为

    字段:entity_id,key,date, sum_value,count_value,avg_value,min_value,max_value

  • 只有数值型才涉及到聚合,因而只针对数值型字段,进行记录。在ts_tv增加时,使用触发器同步添加到本表。哪些指标要汇总,直接在触发器中定义,不定义参数表。

问题解决

  1. 建表语句
sql 复制代码
CREATE TABLE ts_kv_daily (
    entity_id UUID NOT NULL,
    key INT NOT NULL,
    ts BIGINT NOT NULL,
    sum_value DOUBLE PRECISION DEFAULT 0,
    count_value BIGINT DEFAULT 0,
    avg_value DOUBLE PRECISION DEFAULT 0,
    min_value DOUBLE PRECISION DEFAULT NULL,
    max_value DOUBLE PRECISION DEFAULT NULL,
    UNIQUE (entity_id, key, ts)
);
  1. 创建触发器
sql 复制代码
CREATE OR REPLACE FUNCTION update_ts_kv_daily()
RETURNS TRIGGER AS $$
DECLARE
    v_date DATE;
    v_ts BIGINT;  -- 改为 BIGINT
    v_value DOUBLE PRECISION;
BEGIN
    -- 根据key决定是否处理,按需增加
    IF NEW.key NOT IN (96, 98) THEN
        RETURN NEW;
    END IF;
    
    -- 只处理数值型数据
    IF (NEW.dbl_v IS NOT NULL OR NEW.long_v IS NOT NULL) THEN
        v_value := COALESCE(NEW.dbl_v, NEW.long_v::DOUBLE PRECISION);
        
        -- 计算日期和时间戳
        v_date := DATE(
            (TO_TIMESTAMP(NEW.ts/1000) AT TIME ZONE 'UTC') 
            AT TIME ZONE 'Asia/Shanghai'
        );
        v_ts := (EXTRACT(EPOCH FROM 
            (v_date || ' 00:00:00 Asia/Shanghai')::TIMESTAMPTZ
        ) * 1000)::BIGINT;
		
        -- 插入或更新 ts_kv_daily 表
        INSERT INTO ts_kv_daily 
            (entity_id, key, ts, sum_value, count_value, avg_value, min_value, max_value)
        VALUES 
            (NEW.entity_id, NEW.key, v_ts, v_value, 1, v_value, v_value, v_value)
        ON CONFLICT (entity_id, key, ts) 
        DO UPDATE SET
            sum_value = ts_kv_daily.sum_value + EXCLUDED.sum_value,
            count_value = ts_kv_daily.count_value + 1,
            avg_value = (ts_kv_daily.sum_value + EXCLUDED.sum_value) / (ts_kv_daily.count_value + 1),
            min_value = LEAST(ts_kv_daily.min_value, EXCLUDED.min_value),
            max_value = GREATEST(ts_kv_daily.max_value, EXCLUDED.max_value);
    END IF;
    
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;


-- 删除存在的触发器
DROP TRIGGER IF EXISTS trg_update_daily_stats ON ts_kv;

-- 创建正确的触发器
CREATE TRIGGER trg_update_daily_stats
AFTER INSERT ON ts_kv
FOR EACH ROW
EXECUTE FUNCTION update_ts_kv_daily();
  1. 可以随时重新计算的存储过程,按天和指标计算,避免数据量太大造成耗时太长
sql 复制代码
CREATE OR REPLACE FUNCTION put_ts_kv_daily(
    p_date DATE,
    p_key INT
)
RETURNS VOID AS $$
DECLARE
    v_start_ts BIGINT;
    v_end_ts BIGINT;
    v_daily_ts BIGINT;  -- 每日的时间戳
BEGIN
    -- 计算查询的时间范围(北京时区)
    v_start_ts := (EXTRACT(EPOCH FROM 
        (p_date::text || ' 00:00:00')::timestamp AT TIME ZONE 'Asia/Shanghai'
    ) * 1000)::BIGINT;
    
    v_end_ts := (EXTRACT(EPOCH FROM 
        (p_date::text || ' 23:59:59.999')::timestamp AT TIME ZONE 'Asia/Shanghai'
    ) * 1000)::BIGINT;
    
    -- 计算每日聚合的时间戳(北京时区当天的零点)
    v_daily_ts := (EXTRACT(EPOCH FROM 
        (p_date::text || ' 00:00:00 Asia/Shanghai')::timestamptz
    ) * 1000)::BIGINT;
    
    -- 删除旧数据(使用 ts 字段)
    DELETE FROM ts_kv_daily 
    WHERE ts = v_daily_ts 
      AND key = p_key;
    
    -- 聚合插入(使用 ts 字段)
    INSERT INTO ts_kv_daily (
        entity_id, key, ts, sum_value, count_value, avg_value, min_value, max_value
    )
    SELECT 
        entity_id,
        p_key,
        v_daily_ts,  -- 使用计算出的每日时间戳
        SUM(COALESCE(dbl_v, long_v::DOUBLE PRECISION)),
        COUNT(*),
        AVG(COALESCE(dbl_v, long_v::DOUBLE PRECISION)),
        MIN(COALESCE(dbl_v, long_v::DOUBLE PRECISION)),
        MAX(COALESCE(dbl_v, long_v::DOUBLE PRECISION))
    FROM ts_kv
    WHERE key = p_key
      AND ts >= v_start_ts
      AND ts <= v_end_ts
      AND (dbl_v IS NOT NULL OR long_v IS NOT NULL)
    GROUP BY entity_id;
    
    RAISE NOTICE '刷新完成: 日期=%, key=%, 时间戳=%', p_date, p_key, v_daily_ts;
END;
$$ LANGUAGE plpgsql;
  1. 调用一次过程刷新数据
sql 复制代码
-- 创建索引(包含查询所需的所有字段),否则执行put_ts_kv_daily会很慢
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ts_kv_2025_12_key_ts_covering 
ON ts_kv_2025_12(key, ts, entity_id, dbl_v, long_v);

//若是要刷新多天,或者多个指标,则调用多次
SELECT put_ts_kv_daily('2025-12-31', 96);
SELECT put_ts_kv_daily('2025-12-30', 96);
SELECT put_ts_kv_daily('2025-12-29', 96);
SELECT put_ts_kv_daily('2025-12-28', 96);
SELECT put_ts_kv_daily('2025-12-27', 96);
SELECT put_ts_kv_daily('2025-12-26', 96);
SELECT put_ts_kv_daily('2025-12-25', 96);
SELECT put_ts_kv_daily('2025-12-24', 96);
SELECT put_ts_kv_daily('2025-12-23', 96);
SELECT put_ts_kv_daily('2025-12-22', 96);
SELECT put_ts_kv_daily('2025-12-21', 96);
SELECT put_ts_kv_daily('2025-12-20', 96);
SELECT put_ts_kv_daily('2025-12-19', 96);
SELECT put_ts_kv_daily('2025-12-18', 96);
SELECT put_ts_kv_daily('2025-12-17', 96);
SELECT put_ts_kv_daily('2025-12-16', 96);
SELECT put_ts_kv_daily('2025-12-15', 96);
SELECT put_ts_kv_daily('2025-12-14', 96);
SELECT put_ts_kv_daily('2025-12-13', 96);
SELECT put_ts_kv_daily('2025-12-12', 96);
SELECT put_ts_kv_daily('2025-12-11', 96);
SELECT put_ts_kv_daily('2025-12-10', 96);
SELECT put_ts_kv_daily('2025-12-09', 96);
SELECT put_ts_kv_daily('2025-12-08', 96);
SELECT put_ts_kv_daily('2025-12-07', 96);
SELECT put_ts_kv_daily('2025-12-06', 96);
SELECT put_ts_kv_daily('2025-12-05', 96);
SELECT put_ts_kv_daily('2025-12-04', 96);
SELECT put_ts_kv_daily('2025-12-03', 96);
SELECT put_ts_kv_daily('2025-12-02', 96);
SELECT put_ts_kv_daily('2025-12-01', 96);

-- 聚合完成后删除这个索引
DROP INDEX CONCURRENTLY IF EXISTS idx_ts_kv_2025_12_key_ts_covering;
相关推荐
华奥系科技1 天前
智慧经济新格局:解码社区、园区与城市一体化建设逻辑
大数据·人工智能·科技·物联网·安全
TDengine (老段)1 天前
TDengine IDMP 组态面板 —— 画布
大数据·数据库·物联网·时序数据库·tdengine·涛思数据
蓝奥声科技1 天前
扩展式智能插座,破解多国标准与定制需求的新思路
物联网·智能用电计量插座·lpiot 低功耗物联网·外贸插座
Zevalin爱灰灰1 天前
零基础入门学用物联网(ESP8266) 第一部分 基础知识篇(三)
单片机·物联网·嵌入式·esp8266
我爱我家8821 天前
亚洲艺术电影节携澳门文化亮相深圳
人工智能·物联网·算法·区块链·爬山算法
物联通信量讯说1 天前
从5G迈向未来通信时代,量讯物联深耕连接基础能力
物联网·5g·信息与通信·iot·通信·6g·量讯物联
搜佛说1 天前
RocksDB, SQLite, TDengine Edge, LiteDB与sfsDb选型
物联网·edge·sqlite·边缘计算·时序数据库·iot·tdengine
沐欣工作室_lvyiyi1 天前
基于物联网的体温心率监测系统(论文+源码)
stm32·单片机·嵌入式硬件·物联网·体温心率
QYR_112 天前
香叶醇行业深度解析:香精香料领域核心原料的发展潜力与挑战
大数据·人工智能·物联网
taxunjishu2 天前
塔讯总线协议转换信捷 PLC 对接 TCP/IP 设备实战方案
网络·物联网·自动化