feat: 升级心跳数据库为高吞吐日分区模型(v2)
- 新增 heartbeat 数据库与表结构文档,描述心跳明细表设计及字段约束。 - 新增 OpenSpec 符合性说明文档,指出与规范的一致点及偏差。 - 新增 Kafka 心跳数据推送说明文档,定义消息格式与推送方式。 - 更新数据库创建脚本,支持 UTF-8 编码与中文排序规则。 - 更新心跳表结构脚本,定义主表及索引,采用 ts_ms 日分区。 - 实现自动分区机制,确保按天创建分区以支持高吞吐写入。 - 添加数据库应用脚本,自动执行 SQL 文件并验证表结构。 - 添加运行时烟雾测试脚本,验证数据库连接与基本操作。 - 添加完整的烟雾测试脚本,验证数据插入与分区创建。
This commit is contained in:
87
docs/db-heartbeat-schema.md
Normal file
87
docs/db-heartbeat-schema.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# 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.sql`
|
||||
- `scripts/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 参数
|
||||
53
docs/db-openspec-compliance.md
Normal file
53
docs/db-openspec-compliance.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# DB 设计与 OpenSpec 符合性/偏差说明(v2)
|
||||
|
||||
本文件用于对照当前 OpenSpec 规范与本次 v2 数据库设计的符合性,并指出偏差与风险点。
|
||||
|
||||
## 1. 与 OpenSpec(当前主规格)的一致点
|
||||
- 满足 `openspec/specs/db/spec.md` 对“表结构初始化、批量写入、约束错误捕获、查询支持”等方向性要求。
|
||||
- 本次变更已在提案增量规范中补充了“分区表/自动分区/高吞吐”相关要求:
|
||||
- `openspec/changes/update-heartbeat-db-v2/specs/db/spec.md`
|
||||
- `openspec/changes/update-heartbeat-db-v2/specs/kafka/spec.md`
|
||||
- `openspec/changes/update-heartbeat-db-v2/specs/processor/spec.md`
|
||||
|
||||
## 2. 偏差与风险(需要评估)
|
||||
### 2.1 “主键仅 id” 与 PostgreSQL 分区约束冲突
|
||||
- 需求写:主键为 `id (bigserial)`。
|
||||
- 现实现:`PRIMARY KEY (ts_ms, id)`。
|
||||
|
||||
原因:PostgreSQL 分区表的主键/唯一约束通常需要包含分区键,否则无法在父表创建全局约束。
|
||||
|
||||
影响:
|
||||
- 业务若强依赖“仅 id 即主键”的语义,需要额外约定(例如只把 id 当作全局唯一序列号使用,主键组合用于物理约束)。
|
||||
|
||||
### 2.2 “自动分区”实现方式
|
||||
- 需求写:新分区可自动创建。
|
||||
- 现实现:通过 `heartbeat.ensure_partitions(start_day, end_day)` 预创建分区(安装时默认创建昨天~未来 7 天)。
|
||||
|
||||
原因:在单条 INSERT 语句执行过程中对分区父表执行 `CREATE TABLE .. PARTITION OF` 会触发 PostgreSQL 的“对象正在被当前查询使用”的限制(已在冒烟测试中复现)。
|
||||
|
||||
建议:
|
||||
- 运维/服务启动时每天调用一次 `heartbeat.ensure_partitions(current_date, current_date + N)` 预建未来分区。
|
||||
- 如环境允许,可用 `pg_cron` 等机制定时执行。
|
||||
|
||||
### 2.3 数据库“中文排序规则”约束的实现方式
|
||||
- 需求写:数据库排序规则使用适合中文环境的配置。
|
||||
- 现状:我们不新建数据库,而是在既有库(默认 log_platform)建表;该库的数据库级 collation 可能不是中文。
|
||||
|
||||
可行方案:
|
||||
- 若仅少数字段需要中文排序(通常是 text/varchar):可创建 ICU collation 并在列级/查询级使用(不要求重建数据库)。
|
||||
- 若要求“整个数据库默认 collation 为中文”:只能通过重建数据库实现(你明确不希望这么做,则建议采用 ICU/列级 collation 的折中方案)。
|
||||
|
||||
### 2.4 枚举 CHECK 约束偏保守
|
||||
- 需求写:各 int 字段添加合理 CHECK,限制取值范围。
|
||||
- 现实现:采用“非负 + 类型上界”的保守范围,避免未来枚举扩展导致写入失败。
|
||||
|
||||
建议:
|
||||
- 在枚举标准明确后,将约束收紧为 `IN (...)` 或更小区间。
|
||||
|
||||
## 3. 结论
|
||||
- 结构、分区、索引均已按 v2 脚本落地并通过冒烟测试。
|
||||
- 两个主要“需求严格字面”不完全满足点:
|
||||
1) 中文 collation(当前 DB 为 en_US.utf8)
|
||||
2) 主键仅 id(分区表限制导致采用 ts_ms + id)
|
||||
|
||||
若你希望我下一步把这些偏差也强制满足(例如重建 DB + 调整主键策略/使用其他分区方案),我可以继续改脚本并提供迁移/回滚方案。
|
||||
74
docs/kafka-heartbeat-producer.md
Normal file
74
docs/kafka-heartbeat-producer.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Kafka 心跳数据推送说明(给数据产生者)
|
||||
|
||||
本文档说明数据产生者需要往 Kafka 队列推送的数据结构与推送方式。
|
||||
|
||||
## 1. Topic 与编码
|
||||
- Topic:默认 `bls-heartbeat`(以服务端配置为准,见 `src/config/config.js`)
|
||||
- 编码:UTF-8
|
||||
- 建议消息格式:JSON(便于跨语言对接与灰度演进)
|
||||
|
||||
> 注意:当前服务端代码的“二进制解包”尚未实现,若你们已经有既定二进制协议,需要在 Processor 中落地对应解包逻辑,并在本文档补充协议细节。
|
||||
|
||||
## 2. 消息 Key(强烈建议)
|
||||
为了保证同设备消息更有序、便于消费端批量聚合:
|
||||
- Kafka message key:`"{hotel_id}:{device_id}"`
|
||||
|
||||
## 3. 消息 Value(JSON)
|
||||
### 3.1 必填字段
|
||||
下面字段必须提供(否则会被判定为无效数据并丢弃/记录错误):
|
||||
|
||||
| 字段 | 类型 | 示例 | 说明 |
|
||||
|---|---|---|---|
|
||||
| ts_ms | number/int64 | 1700000000123 | 毫秒级 epoch 时间戳 |
|
||||
| hotel_id | number/int | 12 | 酒店编号(int2 范围内) |
|
||||
| room_id | number/int | 1203 | 房间编号/房间标识(int4) |
|
||||
| device_id | string | "A1B2C3D4" | 设备唯一 ID(序列号/MAC/自定义编码) |
|
||||
| ip | string | "192.168.1.10" | IPv4/IPv6 字符串(落库为 inet) |
|
||||
| power_state | number/int | 1 | 取电状态(枚举值需统一标准) |
|
||||
| guest_type | number/int | 0 | 住客身份(住客/空房/保洁/维修等,枚举值需统一标准) |
|
||||
| cardless_state | number/int | 0 | 无卡取电/无卡策略状态(枚举) |
|
||||
| service_mask | number/int64 | 5 | 服务/场景位图(bigint) |
|
||||
| pms_state | number/int | 1 | PMS 状态(枚举) |
|
||||
| carbon_state | number/int | 0 | 碳控状态(枚举) |
|
||||
| device_count | number/int | 1 | 设备数量/上报设备数(语义需统一) |
|
||||
| comm_seq | number/int | 7 | 通讯序号(语义需统一) |
|
||||
|
||||
### 3.2 可选字段
|
||||
| 字段 | 类型 | 示例 | 说明 |
|
||||
|---|---|---|---|
|
||||
| extra | object | {"source":"gw","ver":"1.2.3"} | 扩展字段:电参、空调状态、版本、上报来源等 |
|
||||
|
||||
## 4. JSON 示例
|
||||
```json
|
||||
{
|
||||
"ts_ms": 1700000000123,
|
||||
"hotel_id": 12,
|
||||
"room_id": 1203,
|
||||
"device_id": "A1B2C3D4",
|
||||
"ip": "192.168.1.10",
|
||||
"power_state": 1,
|
||||
"guest_type": 0,
|
||||
"cardless_state": 0,
|
||||
"service_mask": 5,
|
||||
"pms_state": 1,
|
||||
"carbon_state": 0,
|
||||
"device_count": 1,
|
||||
"comm_seq": 7,
|
||||
"extra": {
|
||||
"source": "gw",
|
||||
"ver": "1.2.3",
|
||||
"ac": {"mode": 1, "set_temp": 26},
|
||||
"meter": {"p": 123.4, "e_wh": 5678}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 推送方式(实现建议)
|
||||
- Producer:建议开启压缩(lz4/zstd)、合理的 `batch.size` 与 `linger.ms`,以降低单条发送开销
|
||||
- 分区:按 key 分区(同设备落同分区)
|
||||
- 语义:至少一次(at-least-once)或恰好一次(exactly-once)取决于你们链路要求;服务端需要配合幂等/去重策略(如后续引入唯一键)
|
||||
|
||||
## 6. 与数据库字段的映射
|
||||
服务端落库目标表:`heartbeat.heartbeat_events`(位于既有数据库中,默认 log_platform)
|
||||
- 必填字段:与表字段同名
|
||||
- 弹性字段:写入 `extra`(jsonb)
|
||||
Reference in New Issue
Block a user