## 最终可执行的实施清单 以下清单基于你已经确认的最终约束整理,可直接作为实施顺序使用。 --- ## 一、实施目标 在**不改变数据来源**、**不改变现有批次处理节奏**、**不改变 `room_status` 写法与目标** 的前提下,实现: - 旧明细表 `heartbeat.heartbeat_events` 可独立开关 - 新明细表 `heartbeat.heartbeat_events_g4_hot` 可独立开关 - 新明细表继续使用**批量 `COPY`** - 旧/新明细写入**完全独立** - `room_status` 始终独立执行,不受明细写入开关影响 - 错误表 `heartbeat.heartbeat_events_errors` 只保留一份,**仅记录新表写入失败** - 支持未来**关闭旧表,仅保留新表** --- ## 二、最终行为规则 ### 1. 明细写入规则 - `legacyHeartbeatEnabled=true` 时,写旧表 `heartbeat.heartbeat_events` - `g4HotHeartbeatEnabled=true` 时,写新表 `heartbeat.heartbeat_events_g4_hot` - 两者可同时开启,也可单独开启,也可同时关闭 ### 2. `room_status` 规则 - 始终执行现有 `upsertRoomStatus()` - 目标表不变 - 写法不变 - 不依赖旧明细是否开启 - 不依赖新明细是否开启 - 即使旧/新明细都关闭,仍然写 `room_status` ### 3. 错误表规则 - 继续使用现有 `heartbeat.heartbeat_events_errors` - 字段完全不变 - 只记录**新表写入失败** - 旧表写入失败**不再写错误表**,只记录日志/统计 ### 4. 消费暂停规则 - 不能因为“两路明细都关闭”而暂停消费 - 不能因为“新表表级错误”直接当成全局数据库离线 - 只有在**当前启用且关键的写入路径**发生连接级不可恢复故障时,才触发暂停消费 --- ## 三、代码改造清单 ### Task 1:扩展配置项 **目标文件** - `src/config/config.js` - `src/config/config.example.js` - `README.md` 或部署文档 **新增配置建议** - `DB_LEGACY_HEARTBEAT_ENABLED` - `DB_G4_HOT_HEARTBEAT_ENABLED` - `DB_ROOM_STATUS_ENABLED` - `DB_G4_HOT_TABLE` - `DB_LEGACY_TABLE` **建议默认值** - `DB_LEGACY_HEARTBEAT_ENABLED=true` - `DB_G4_HOT_HEARTBEAT_ENABLED=false` - `DB_ROOM_STATUS_ENABLED=true` - `DB_LEGACY_TABLE=heartbeat.heartbeat_events` - `DB_G4_HOT_TABLE=heartbeat.heartbeat_events_g4_hot` **实施要求** - 保持与当前 `config.js` 风格一致 - 布尔值走现有 `parseBoolean` - 表名配置单独放在 `db` 配置节点下 **完成标准** - 应用启动时可从环境变量读取旧/新明细开关 - `room_status` 开关独立可控 - 新旧目标表名可配置 --- ### Task 2:抽象通用明细写入内核 **目标文件** - `src/db/databaseManager.js` **当前基础** - 现有 `insertHeartbeatEvents()` 已实现: - 分区预创建 - `COPY ... FROM STDIN` - 缺分区补建重试 - fallback 逐条 `INSERT` **要做的事** 把现有写旧表的强绑定逻辑抽象为通用方法,例如: - `_insertHeartbeatEventsToTarget(events, targetConfig)` - `_buildHeartbeatCopySql(targetTable, columns)` - `_buildHeartbeatInsertSql(targetTable, columns)` **目标参数至少包含** - `tableName` - `columns` - `logPrefix` - `enablePartitionEnsure` - 缺分区识别规则 **实施要求** - 不能复制两份几乎相同的 `COPY` 代码 - 保证旧表和新表复用同一套批量写入能力 - 保证新表也走 `COPY` **完成标准** - 写旧表和写新表都可通过同一个通用写入内核完成 - 旧逻辑行为不变 - 新表能力复用旧逻辑 --- ### Task 3:实现双明细独立编排 **目标文件** - `src/db/databaseManager.js` **新增编排方法建议** - `writeHeartbeatDetails(events)` - 或 `insertHeartbeatEventsDual(events)` **它需要负责** - 判断旧表是否开启 - 判断新表是否开启 - 分别调用: - `legacy writer` - `g4Hot writer` - 聚合结果并返回结构化结果 **返回结果建议** 至少包含: - `legacy.enabled` - `legacy.success` - `legacy.insertedCount` - `legacy.failedRecords` - `legacy.error` - `g4Hot.enabled` - `g4Hot.success` - `g4Hot.insertedCount` - `g4Hot.failedRecords` - `g4Hot.error` **实施要求** - 两路执行逻辑互不影响 - 一路失败不能吞掉另一路结果 - 两路都关闭时返回“跳过写入”的明确结果,不报错 **完成标准** - 系统能正确识别四种模式: - 仅旧 - 仅新 - 双写 - 双关 --- ### Task 4:调整 `HeartbeatProcessor.processBatch()` 的主流程 **目标文件** - `src/processor/heartbeatProcessor.js` **当前逻辑问题** 当前流程中: - 先写 `insertHeartbeatEvents(batchData)` - 只有成功后才 best-effort 写 `room_status` 这与当前最终要求不一致。 **改造目标** 把流程改成三段独立逻辑: #### A. 明细写入 调用新的双写编排方法,获取旧/新明细写入结果 #### B. `room_status` 写入 无论旧/新明细写入开关状态如何,都执行: - `upsertRoomStatus(batchData)` 注意: - 只要 `DB_ROOM_STATUS_ENABLED=true` - 就执行当前逻辑 - 不依赖明细成功与否 #### C. 错误表写入 仅把**新表失败记录**送入 `insertHeartbeatEventsErrors()` **完成标准** - `room_status` 从“依赖旧表成功”变成“独立执行” - 错误表只接新表失败 - 旧表失败不进错误表 --- ### Task 5:重新定义错误表调用策略 **目标文件** - `src/db/databaseManager.js` - `src/processor/heartbeatProcessor.js` **要求** - 保留 `insertHeartbeatEventsErrors()` 表结构和 SQL - 仅调整调用来源: - 新表失败 → 写错误表 - 旧表失败 → 不写错误表 **实施要求** 建议在双写结果聚合后,只提取: - `g4Hot.failedRecords` 转换成当前错误表 payload 后调用 `insertHeartbeatEventsErrors()` **完成标准** - 错误表字段、表结构、SQL 全不变 - 旧表失败不会污染错误表 - 新表失败可追踪 --- ### Task 6:调整连接状态与暂停消费策略 **目标文件** - `src/processor/heartbeatProcessor.js` - `src/db/databaseManager.js` **当前问题** 目前 `_isConnectionError()` 和 `_scheduleDbCheck()` 基本围绕单一 DB 写入路径设计。 **改造要求** 把“是否需要暂停 Kafka 消费”改成基于**启用中的关键 sink**判断。 **建议规则** - 仅旧表开启:旧表连接故障才可能触发暂停 - 仅新表开启:新表连接故障才可能触发暂停 - 双开:如果两路都因连接级错误不可写,可触发暂停 - 双关但 `room_status` 开启:只要 `room_status` 还能正常写,就不应因明细关闭而暂停 **重要区分** 以下不能都算成“数据库离线”: - 连接失败 - 缺分区 - 表不存在 - 表字段不匹配 - 权限不足 其中真正应触发离线处理的优先是: - `08006` - `08001` - `08003` - `08004` - `08007` - `57P03` - 以及明显的网络连接错误 **完成标准** - 不会因为新表单独异常而误停整个消费 - 不会因为两路明细关闭而误停消费 --- ### Task 7:补充启动日志与配置摘要 **目标文件** - `src/index.js` **新增日志建议** 启动时输出: - 旧明细写入是否开启 - 新明细写入是否开启 - `room_status` 是否开启 - 旧表目标 - 新表目标 **完成标准** - 运行日志中能直接看出当前处于哪种模式 - 运维排障时不用翻配置文件 --- ### Task 8:补充统计项 **目标文件** - `src/stats/statsManager.js` - 如有 Redis 控制台输出,也同步补充 **新增统计建议** - legacy detail success count - legacy detail failed count - g4Hot detail success count - g4Hot detail failed count - room_status success count - room_status failed count - g4Hot error-table inserted count **完成标准** - 双写观察期可以快速对比旧表/新表健康状态 - 能单独看出新表失败是否升高 --- ## 四、测试清单 ### Task 9:补充单元测试 **目标文件** - `test/smoke.test.js` - 如有必要新增专门的 `databaseManager` 测试文件 **必须覆盖的场景** 1. 仅旧表开启 2. 仅新表开启 3. 旧新双开 4. 旧新双关,但 `room_status` 开启 5. 旧成功、新失败 6. 旧失败、新成功 7. 双失败 8. 新表失败时写错误表 9. 旧表失败时不写错误表 10. `room_status` 始终执行 11. `COPY` 失败时新表降级逐条 `INSERT` 12. 缺分区时自动补分区重试 **完成标准** - `npm test` 可覆盖新旧双写与独立逻辑 - `room_status` 独立性有明确断言 --- ### Task 10:补充数据库 smoke 验证 **目标文件** - `scripts/db/smokeTest.js` - 或新增 `scripts/db/smokeG4Hot.js` **验证项** - 新表 `heartbeat.heartbeat_events_g4_hot` 能写入 - 新表分区能正确附着 - 新表唯一键/索引存在 - 新表可按主查询维度检索 - 旧表关闭、新表开启时仍正常 - 旧新都关但 `room_status` 开启时系统不报配置错误 **完成标准** - 有一套上线前可重复执行的 smoke 脚本 - 可快速确认新表接入正确 --- ## 五、上线实施顺序 ### Step 1:代码部署,保持现状 配置: - 旧表开启 - 新表关闭 - `room_status` 开启 目标: - 确认改造后代码在“旧模式”下行为不变 ### Step 2:开启新表双写 配置: - 旧表开启 - 新表开启 - `room_status` 开启 目标: - 观察新表写入是否稳定 - 检查新表失败是否进入错误表 - 比对旧/新明细数据量 ### Step 3:观察稳定性 重点观察: - 新表 `COPY` 成功率 - 新表 fallback 频率 - 错误表记录量 - `room_status` 是否持续正常 ### Step 4:关闭旧表 配置: - 旧表关闭 - 新表开启 - `room_status` 开启 目标: - 验证“仅保留新表”时系统稳定运行 ### Step 5:保留过渡观察期 目标: - 持续观察新表、错误表、`room_status` --- ## 六、验收标准 满足以下全部条件即可验收: 1. 新表 `heartbeat.heartbeat_events_g4_hot` 接入成功 2. 新表仍使用批量 `COPY` 3. 旧/新明细可独立开关 4. `room_status` 始终独立执行 5. 两路明细都关闭时,系统仍允许继续写 `room_status` 6. 错误表仅记录新表失败 7. 旧表失败不会写错误表 8. 不修改 Kafka 数据来源 9. 不修改现有数据转换来源 10. 可支持后续关闭旧表,仅保留新表 --- ## 七、建议的实施顺序编号版 1. 扩展 `config.js` 配置项 2. 重构 `databaseManager.js`,抽通用 `COPY` writer 3. 在 `databaseManager.js` 增加旧/新明细双写编排 4. 调整 `heartbeatProcessor.js`,让 `room_status` 独立执行 5. 调整错误表调用,仅接新表失败 6. 重构连接异常与暂停消费判定 7. 增加启动配置摘要日志 8. 增加 stats 指标 9. 补测试 10. 补 smoke 验证脚本 11. 按“旧开新关 → 双开 → 关旧留新”上线