feat: 添加 G5 独立写入功能
- 新增 G5 数据库连接配置与可关闭的写入开关 - 在现有 legacy/G4 写入成功路径后,追加独立的 G5 写入流程 - G5 使用与 G4 相同的数据结构映射,但不写入 guid,由数据库自生成 int4 guid - room_status 新增 G5 独立 upsert 写入路径,并保留旧表与 G5 表的独立开关 - 新增 G5 写入统计与启动摘要输出 - 更新 StatsCounters 和 StatsReporter 以支持 G5 统计 - 增加测试覆盖,确保 G5 写入逻辑与 room_status 的独立执行 - 新增 G5 相关数据库表结构 SQL 文件
This commit is contained in:
15
openspec/changes/add-g5-independent-write/proposal.md
Normal file
15
openspec/changes/add-g5-independent-write/proposal.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Change: add g5 independent write
|
||||
|
||||
## Why
|
||||
当前服务已经支持 legacy 与 G4 热表双写,但需要在不影响现有主链路的前提下,再额外向一个临时 G5 库独立写入同构数据,便于过渡期联调与验证。
|
||||
|
||||
## What Changes
|
||||
- 新增 G5 独立数据库连接配置与可关闭的写入开关
|
||||
- 在现有 legacy/G4 写入成功路径后,追加独立的 G5 写入流程
|
||||
- G5 使用与 G4 相同的数据结构映射,但不写入 guid,由数据库自生成 int4 guid
|
||||
- room_status 新增 G5 独立 upsert 写入路径,并保留旧表与 G5 表的独立开关
|
||||
- 新增 G5 写入统计与启动摘要输出
|
||||
|
||||
## Impact
|
||||
- Affected specs: db, processor
|
||||
- Affected code: src/config/config.js, src/index.js, src/db/databaseManager.js, src/processor/heartbeatProcessor.js, src/stats/statsManager.js, test/*.test.js
|
||||
63
openspec/changes/add-g5-independent-write/specs/db/spec.md
Normal file
63
openspec/changes/add-g5-independent-write/specs/db/spec.md
Normal file
@@ -0,0 +1,63 @@
|
||||
## MODIFIED Requirements
|
||||
### Requirement: 双明细独立编排
|
||||
系统 MUST 提供多目标明细写入编排能力,按启动配置分别控制旧表、G4 热表和临时 G5 热表的写入,各路写入结果相互独立。
|
||||
|
||||
#### Scenario: 旧表、G4 与 G5 可独立组合
|
||||
- **WHEN** `legacyHeartbeatEnabled`、`g4HotHeartbeatEnabled`、`g5HeartbeatEnabled` 任意组合启停
|
||||
- **THEN** 系统应只对开启的目标执行写入
|
||||
- **AND** 任一路写入失败都不应阻塞其他已开启目标的写入
|
||||
|
||||
#### Scenario: G5 写入不影响主链路暂停判定
|
||||
- **WHEN** G5 临时库写入失败或连接异常
|
||||
- **THEN** 系统不应因此暂停 legacy/G4 主链路消费
|
||||
- **AND** 应记录日志与统计以便排查
|
||||
|
||||
### Requirement: 写入目标启动配置
|
||||
系统 MUST 通过启动配置(环境变量)分别控制旧表、G4 热表、临时 G5 热表和 room_status 的写入开关与目标表名。
|
||||
|
||||
#### Scenario: 读取 G5 独立连接与表配置
|
||||
- **WHEN** 系统启动时
|
||||
- **THEN** 应读取 `DB_G5_HEARTBEAT_ENABLED` 作为 G5 写入开关
|
||||
- **AND** 应读取 `DB_G5_TABLE` 作为 G5 目标表名,默认 `heartbeat.heartbeat_events_g5`
|
||||
- **AND** 应读取 `POSTGRES_HOST_G5`、`POSTGRES_PORT_G5`、`POSTGRES_DATABASE_G5`、`POSTGRES_USER_G5`、`POSTGRES_PASSWORD_G5`、`POSTGRES_IDLE_TIMEOUT_MS_G5` 作为 G5 独立连接参数
|
||||
|
||||
### Requirement: G5 热表独立写入能力
|
||||
系统 MUST 支持向 `heartbeat.heartbeat_events_g5` 执行批量 COPY 写入,并与 G4 热表共享同一套字段展开逻辑。
|
||||
|
||||
#### Scenario: G5 写入复用 G4 字段映射
|
||||
- **WHEN** `g5HeartbeatEnabled=true` 且有一批心跳数据待写入
|
||||
- **THEN** 系统应使用与 G4 热表一致的字段展开规则写入 `heartbeat.heartbeat_events_g5`
|
||||
- **AND** `service_mask` 应展开为 `svc_01` 至 `svc_64`
|
||||
- **AND** 电力与空调数组应展开为 `_1`、`_2`、`_residual` 列
|
||||
|
||||
#### Scenario: G5 guid 由数据库生成
|
||||
- **WHEN** 系统写入 `heartbeat.heartbeat_events_g5`
|
||||
- **THEN** 系统不应为 `guid` 字段赋值
|
||||
- **AND** 应依赖数据库默认值生成 `int4` 类型 guid
|
||||
|
||||
#### Scenario: G5 原始数组列固定写空
|
||||
- **WHEN** 系统写入 `heartbeat.heartbeat_events_g5`
|
||||
- **THEN** `service_mask`、`elec_address`、`air_address`、`voltage`、`ampere`、`power`、`phase`、`energy`、`sum_energy`、`state`、`model`、`speed`、`set_temp`、`now_temp`、`solenoid_valve`、`extra` 应统一写入 `null`
|
||||
- **AND** `_1`、`_2`、`_residual` 展开列仍应按来源数据正常写入
|
||||
|
||||
### Requirement: room_status 新旧双表独立 upsert
|
||||
系统 MUST 支持将 room_status 同时写入旧表与 G5 表,并允许分别开关。
|
||||
|
||||
#### Scenario: 旧表与 G5 表可独立开关
|
||||
- **WHEN** `DB_ROOM_STATUS_ENABLED` 与 `DB_G5_ROOM_STATUS_ENABLED` 任意组合启停
|
||||
- **THEN** 系统应仅对开启的 room_status 目标表执行 upsert
|
||||
- **AND** 任一 room_status 目标失败不应阻塞另一目标
|
||||
|
||||
#### Scenario: room_status 全盘使用 ON CONFLICT DO UPDATE
|
||||
- **WHEN** 系统写入旧 room_status 表或 G5 room_status 表
|
||||
- **THEN** 系统应统一使用 `INSERT ... ON CONFLICT DO UPDATE`
|
||||
- **AND** 不应退回为单独的 `INSERT` 或 `UPDATE` 路径
|
||||
|
||||
#### Scenario: room_status SQL 不更新 ts_ms
|
||||
- **WHEN** 系统执行旧 room_status 或 G5 room_status 的 upsert SQL
|
||||
- **THEN** `DO UPDATE SET` 子句中不应包含 `ts_ms = EXCLUDED.ts_ms`
|
||||
|
||||
#### Scenario: G5 room_status 使用 hotel_id 与 room_id 冲突键
|
||||
- **WHEN** 系统写入 `room_status.room_status_moment_g5`
|
||||
- **THEN** 应使用 `(hotel_id, room_id)` 作为 `ON CONFLICT` 的冲突键
|
||||
- **AND** 应将 `device_id` 作为普通可更新列写入
|
||||
@@ -0,0 +1,19 @@
|
||||
## MODIFIED Requirements
|
||||
### Requirement: 按 sink 维度的统计与监控
|
||||
系统 MUST 按写入目标维度分别统计成功数、失败数与降级事件。
|
||||
|
||||
#### Scenario: legacy、g4Hot 与 g5 分别统计
|
||||
- **WHEN** 系统完成一批次的多目标写入编排
|
||||
- **THEN** 应分别统计 legacy 写入成功数、失败数
|
||||
- **AND** 应分别统计 g4Hot 写入成功数、失败数
|
||||
- **AND** 应分别统计 g5 写入成功数、失败数
|
||||
- **AND** 统计项应在 Redis 控制台输出或 stats 汇总中可见
|
||||
|
||||
### Requirement: room_status 新旧双表独立执行
|
||||
系统 MUST 在批次处理中独立执行旧 room_status 与 G5 room_status 的 upsert,不依赖任意明细写入结果。
|
||||
|
||||
#### Scenario: 两个 room_status 目标独立执行
|
||||
- **WHEN** 一批心跳数据完成验证与转换
|
||||
- **THEN** 只要 `DB_ROOM_STATUS_ENABLED=true` 就应执行旧 room_status upsert
|
||||
- **AND** 只要 `DB_G5_ROOM_STATUS_ENABLED=true` 就应执行 G5 room_status upsert
|
||||
- **AND** 任一路 room_status 写入失败都不应阻塞另一条 room_status 路径或主处理流程
|
||||
6
openspec/changes/add-g5-independent-write/tasks.md
Normal file
6
openspec/changes/add-g5-independent-write/tasks.md
Normal file
@@ -0,0 +1,6 @@
|
||||
## 1. Implementation
|
||||
- [x] 1.1 增加 G5 独立连接与开关配置读取
|
||||
- [x] 1.2 增加 G5 独立写库实现,复用 G4 热表映射逻辑并省略 guid 写入
|
||||
- [x] 1.3 将 G5 写入接入现有批处理流程,保持与 legacy/G4/room_status 相互独立
|
||||
- [x] 1.4 增加 G5 统计、启动摘要与测试覆盖
|
||||
- [x] 1.5 增加旧 room_status 与 G5 room_status 的独立 ON CONFLICT DO UPDATE 写入路径
|
||||
Reference in New Issue
Block a user