Files
Web_BLS_Heartbeat_Server/docs/db-heartbeat-schema.md
XuJiacheng 41301f9ce5 feat: 实现GUID主键与service_mask索引改造
- 将主键从自增id改为GUID格式并添加格式校验
- 为service_mask添加表达式索引优化首位查询性能
- 更新相关文档说明改造方案与验证步骤
- 添加统计模块记录数据库写入与Kafka消费量
- 重构Redis心跳协议改用LIST类型存储项目状态
- 修复部署脚本中的服务名称不一致问题
2026-01-17 18:37:44 +08:00

5.6 KiB
Raw Blame History

Heartbeat 数据库与表结构v2 草案)

本文档描述 PostgreSQL 中 heartbeat 数据库的心跳明细表设计,用于高吞吐写入与按酒店/时间范围检索。

1. 数据库与命名空间

  • 数据库:使用既有业务库(默认 log_platform,以 src/config/config.js 为准)
  • Schemaheartbeat
  • 编码:数据库需为 UTF-8执行器会输出并提示
  • 排序规则/字符类型:若数据库不是中文 locale可通过 ICU collation 在列级/表达式级实现中文排序(如确有严格要求)。

2. 主表

  • 表名:heartbeat.heartbeat_events
  • 分区:按 ts_msepoch 毫秒)按天 RANGE 分区

对应脚本:

  • scripts/db/010_heartbeat_schema.sql
  • scripts/db/020_partitioning_auto_daily.sql

2.1 字段列表

字段 类型 必填 说明
guid varchar(32) 是(自动生成) GUID32 位无连字符 HEX小写自动生成
ts_ms bigint 毫秒级时间戳epoch ms
hotel_id int2 酒店编号
room_id varchar(50) 房间编号(或房间唯一标识,按字符串存储)
device_id varchar(64) 设备 ID序列号/MAC/混合编码);如明确为纯数字可改 bigint
ip varchar(21) IP:PORTIPv4或纯 IP 字符串
power_state int2 取电状态(枚举值待标准化)
guest_type int2 住客身份(住客/空房/保洁/维修等,枚举值待标准化)
cardless_state int2 无卡取电/无卡策略状态(枚举待定)
service_mask bigint 服务位图/场景位图(需求指定 BRIN 索引)
pms_state int2 PMS 状态(枚举待定)
carbon_state int2 碳控状态(枚举待定)
device_count int2 设备数量/上报设备数量(语义待确认)
comm_seq int4 通讯序号(语义待确认)
elec_address text[] 电力设备地址数组(与 voltage[] 等按下标对齐)
voltage double precision[] 电压数组
ampere double precision[] 电流数组
power double precision[] 功率数组
phase text[] 相位数组
energy double precision[] 能耗数组
sum_energy double precision[] 总能耗数组
air_address text[] 空调设备地址数组(与 state[] 等按下标对齐)
state int2[] 开关状态数组
model int2[] 运行模式数组
speed int2[] 风速设置数组
set_temp int2[] 设定温度数组
now_temp int2[] 当前温度数组
solenoid_valve int2[] 电磁阀门状态数组
extra jsonb 可扩展字段:电参/空调状态/版本/来源等

2.2 约束

  • 所有必填字段:NOT NULL
  • ip:使用 varchar(21),用于存储 IP:PORTIPv4
  • int2/int4:当前脚本采用“非负 + 上界”CHECK避免枚举未来扩展造成写入失败
    • 如需更强的枚举约束,建议在确认枚举标准后改为 IN (...)BETWEEN 更小范围。

2.3 主键(重要说明)

需求写“主键id(bigserial)”,但 PostgreSQL 分区表的主键/唯一约束通常必须包含分区键

脚本采用:

  • PRIMARY KEY (ts_ms, guid)

原因:保证分区表可创建、约束可落地。

3. 分区策略与自动分区

  • 分区键:ts_ms
  • 粒度按天Asia/Shanghai自然日
  • 自动分区:通过“预创建分区”的方式实现(安装时预建昨天~未来 7 天),并提供函数供服务启动/定时任务调用

调用方式:

  • SQLSELECT heartbeat.ensure_partitions(current_date, current_date + 30);
  • Node执行 npm run db:apply(会应用脚本并预创建分区)

风险与建议:

  • PostgreSQL 在单条 INSERT 执行过程中对父分区表执行 CREATE TABLE .. PARTITION OF 会触发锁/使用中限制,导致写入失败;因此不建议“插入时动态建分区”。
  • 推荐每日提前创建未来 N 天分区(例如外部调度/运维脚本或服务启动时调用 heartbeat.ensure_partitions)。

4. 索引设计

需求指定:

  • B-treehotel_id, power_state, guest_type, device_id
  • BRINservice_mask

新增(数组元素查询):

  • GINelec_address, air_address, state, model

常用查询示例:

  • SELECT * FROM heartbeat.heartbeat_events WHERE elec_address @> ARRAY['add11'];
  • SELECT * FROM heartbeat.heartbeat_events WHERE air_address @> ARRAY['ac1'];
  • SELECT * FROM heartbeat.heartbeat_events WHERE state @> ARRAY[1::int2];
  • SELECT * FROM heartbeat.heartbeat_events WHERE model @> ARRAY[2::int2];

额外建议(脚本默认包含,可按需移除):

  • btree (hotel_id, ts_ms):覆盖最常见过滤(酒店 + 时间范围),显著提升检索与分区内扫描效率。

5. 查询性能影响分析(分区)

  • 优点:
    • 时间范围查询触发分区裁剪(只扫命中的日分区)
    • 冷热数据按分区自然分层,便于归档/清理
  • 代价:
    • 跨大量分区的查询会增加计划时间与元数据开销
    • 需要运维策略预建分区、定期维护索引、vacuum/analyze

6. 性能优化建议(高吞吐)

  • 写入使用批量写入COPY 或 multi-row INSERT并控制批大小例如 500~5000按网络与锁争用调优
  • 分区:建议预创建未来 7~30 天分区;触发器只做兜底
  • 统计:对 Grafana 读取的 1m/5m/1h 聚合建议做物化视图或汇总表(避免每次扫明细)
  • 维护:
    • 定期 VACUUM (ANALYZE) 各分区
    • 监控 bloat 与 autovacuum 参数