- 新增 heartbeat 数据库与表结构文档,描述心跳明细表设计及字段约束。 - 新增 OpenSpec 符合性说明文档,指出与规范的一致点及偏差。 - 新增 Kafka 心跳数据推送说明文档,定义消息格式与推送方式。 - 更新数据库创建脚本,支持 UTF-8 编码与中文排序规则。 - 更新心跳表结构脚本,定义主表及索引,采用 ts_ms 日分区。 - 实现自动分区机制,确保按天创建分区以支持高吞吐写入。 - 添加数据库应用脚本,自动执行 SQL 文件并验证表结构。 - 添加运行时烟雾测试脚本,验证数据库连接与基本操作。 - 添加完整的烟雾测试脚本,验证数据插入与分区创建。
4.4 KiB
4.4 KiB
Heartbeat 数据库与表结构(v2 草案)
本文档描述 PostgreSQL 中 heartbeat 数据库的心跳明细表设计,用于高吞吐写入与按酒店/时间范围检索。
1. 数据库与命名空间
- 数据库:使用既有业务库(默认
log_platform,以src/config/config.js为准) - Schema:
heartbeat - 编码:数据库需为 UTF-8(执行器会输出并提示)
- 排序规则/字符类型:若数据库不是中文 locale,可通过 ICU collation 在列级/表达式级实现中文排序(如确有严格要求)。
2. 主表
- 表名:
heartbeat.heartbeat_events - 分区:按
ts_ms(epoch 毫秒)按天 RANGE 分区
对应脚本:
scripts/db/010_heartbeat_schema.sqlscripts/db/020_partitioning_auto_daily.sql
2.1 字段列表
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | bigserial | 否(自动生成) | 自增序列号(写入时可不提供) |
| ts_ms | bigint | 是 | 毫秒级时间戳(epoch ms) |
| hotel_id | int2 | 是 | 酒店编号 |
| room_id | int4 | 是 | 房间编号(或房间唯一标识) |
| device_id | varchar(64) | 是 | 设备 ID(序列号/MAC/混合编码);如明确为纯数字可改 bigint |
| ip | inet | 是 | 设备/上报方 IP(PostgreSQL inet 类型自带格式校验) |
| 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 | int2 | 是 | 通讯序号(语义待确认) |
| extra | jsonb | 否 | 可扩展字段:电参/空调状态/版本/来源等 |
2.2 约束
- 所有必填字段:
NOT NULL ip:使用inet类型(天然校验 IPv4/IPv6 格式)- 各
int2/int4:当前脚本采用“非负 + 上界”CHECK(避免枚举未来扩展造成写入失败)- 如需更强的枚举约束,建议在确认枚举标准后改为
IN (...)或BETWEEN更小范围。
- 如需更强的枚举约束,建议在确认枚举标准后改为
2.3 主键(重要说明)
需求写“主键:id(bigserial)”,但 PostgreSQL 分区表的主键/唯一约束通常必须包含分区键。
脚本采用:
PRIMARY KEY (ts_ms, id)
原因:保证分区表可创建、约束可落地。
3. 分区策略与自动分区
- 分区键:
ts_ms - 粒度:按天(Asia/Shanghai,自然日)
- 自动分区:通过“预创建分区”的方式实现(安装时预建昨天~未来 7 天),并提供函数供服务启动/定时任务调用
调用方式:
- SQL:
SELECT 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-tree:
hotel_id,power_state,guest_type,device_id - BRIN:
service_mask
额外建议(脚本默认包含,可按需移除):
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 参数
- 定期