- 新增配置项以支持旧/新明细表的独立写入开关及目标表名。 - 重构 DatabaseManager,抽象通用批量 COPY 写入内核,支持不同目标表的复用。 - 新增双明细写入编排器,支持旧/新表独立执行、重试及 fallback。 - 调整 HeartbeatProcessor.processBatch(),确保 room_status 独立执行。 - 错误表仅记录新表写入失败,旧表失败不再写入错误表。 - 重新定义消费暂停策略,基于当前启用的关键 sink 判断。 - 补充按 sink 维度的统计项与启动日志。 新增 G4 热表相关的数据库规范与处理逻辑,确保系统在双写模式下的稳定性与可扩展性。
10 KiB
10 KiB
最终可执行的实施清单
以下清单基于你已经确认的最终约束整理,可直接作为实施顺序使用。
一、实施目标
在不改变数据来源、不改变现有批次处理节奏、不改变 room_status 写法与目标 的前提下,实现:
- 旧明细表
heartbeat.heartbeat_events可独立开关 - 新明细表
heartbeat.heartbeat_events_g4_hot可独立开关 - 新明细表继续使用批量
COPY - 旧/新明细写入完全独立
room_status始终独立执行,不受明细写入开关影响- 错误表
heartbeat.heartbeat_events_errors只保留一份,仅记录新表写入失败 - 支持未来关闭旧表,仅保留新表
二、最终行为规则
1. 明细写入规则
legacyHeartbeatEnabled=true时,写旧表heartbeat.heartbeat_eventsg4HotHeartbeatEnabled=true时,写新表heartbeat.heartbeat_events_g4_hot- 两者可同时开启,也可单独开启,也可同时关闭
2. room_status 规则
- 始终执行现有
upsertRoomStatus() - 目标表不变
- 写法不变
- 不依赖旧明细是否开启
- 不依赖新明细是否开启
- 即使旧/新明细都关闭,仍然写
room_status
3. 错误表规则
- 继续使用现有
heartbeat.heartbeat_events_errors - 字段完全不变
- 只记录新表写入失败
- 旧表写入失败不再写错误表,只记录日志/统计
4. 消费暂停规则
- 不能因为“两路明细都关闭”而暂停消费
- 不能因为“新表表级错误”直接当成全局数据库离线
- 只有在当前启用且关键的写入路径发生连接级不可恢复故障时,才触发暂停消费
三、代码改造清单
Task 1:扩展配置项
目标文件
src/config/config.jssrc/config/config.example.jsREADME.md或部署文档
新增配置建议
DB_LEGACY_HEARTBEAT_ENABLEDDB_G4_HOT_HEARTBEAT_ENABLEDDB_ROOM_STATUS_ENABLEDDB_G4_HOT_TABLEDB_LEGACY_TABLE
建议默认值
DB_LEGACY_HEARTBEAT_ENABLED=trueDB_G4_HOT_HEARTBEAT_ENABLED=falseDB_ROOM_STATUS_ENABLED=trueDB_LEGACY_TABLE=heartbeat.heartbeat_eventsDB_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)
目标参数至少包含
tableNamecolumnslogPrefixenablePartitionEnsure- 缺分区识别规则
实施要求
- 不能复制两份几乎相同的
COPY代码 - 保证旧表和新表复用同一套批量写入能力
- 保证新表也走
COPY
完成标准
- 写旧表和写新表都可通过同一个通用写入内核完成
- 旧逻辑行为不变
- 新表能力复用旧逻辑
Task 3:实现双明细独立编排
目标文件
src/db/databaseManager.js
新增编排方法建议
writeHeartbeatDetails(events)- 或
insertHeartbeatEventsDual(events)
它需要负责
- 判断旧表是否开启
- 判断新表是否开启
- 分别调用:
legacy writerg4Hot writer
- 聚合结果并返回结构化结果
返回结果建议 至少包含:
legacy.enabledlegacy.successlegacy.insertedCountlegacy.failedRecordslegacy.errorg4Hot.enabledg4Hot.successg4Hot.insertedCountg4Hot.failedRecordsg4Hot.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.jssrc/processor/heartbeatProcessor.js
要求
- 保留
insertHeartbeatEventsErrors()表结构和 SQL - 仅调整调用来源:
- 新表失败 → 写错误表
- 旧表失败 → 不写错误表
实施要求 建议在双写结果聚合后,只提取:
g4Hot.failedRecords
转换成当前错误表 payload 后调用 insertHeartbeatEventsErrors()
完成标准
- 错误表字段、表结构、SQL 全不变
- 旧表失败不会污染错误表
- 新表失败可追踪
Task 6:调整连接状态与暂停消费策略
目标文件
src/processor/heartbeatProcessor.jssrc/db/databaseManager.js
当前问题
目前 _isConnectionError() 和 _scheduleDbCheck() 基本围绕单一 DB 写入路径设计。
改造要求 把“是否需要暂停 Kafka 消费”改成基于启用中的关键 sink判断。
建议规则
- 仅旧表开启:旧表连接故障才可能触发暂停
- 仅新表开启:新表连接故障才可能触发暂停
- 双开:如果两路都因连接级错误不可写,可触发暂停
- 双关但
room_status开启:只要room_status还能正常写,就不应因明细关闭而暂停
重要区分 以下不能都算成“数据库离线”:
- 连接失败
- 缺分区
- 表不存在
- 表字段不匹配
- 权限不足
其中真正应触发离线处理的优先是:
080060800108003080040800757P03- 以及明显的网络连接错误
完成标准
- 不会因为新表单独异常而误停整个消费
- 不会因为两路明细关闭而误停消费
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测试文件
必须覆盖的场景
- 仅旧表开启
- 仅新表开启
- 旧新双开
- 旧新双关,但
room_status开启 - 旧成功、新失败
- 旧失败、新成功
- 双失败
- 新表失败时写错误表
- 旧表失败时不写错误表
room_status始终执行COPY失败时新表降级逐条INSERT- 缺分区时自动补分区重试
完成标准
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
六、验收标准
满足以下全部条件即可验收:
- 新表
heartbeat.heartbeat_events_g4_hot接入成功 - 新表仍使用批量
COPY - 旧/新明细可独立开关
room_status始终独立执行- 两路明细都关闭时,系统仍允许继续写
room_status - 错误表仅记录新表失败
- 旧表失败不会写错误表
- 不修改 Kafka 数据来源
- 不修改现有数据转换来源
- 可支持后续关闭旧表,仅保留新表
七、建议的实施顺序编号版
- 扩展
config.js配置项 - 重构
databaseManager.js,抽通用COPYwriter - 在
databaseManager.js增加旧/新明细双写编排 - 调整
heartbeatProcessor.js,让room_status独立执行 - 调整错误表调用,仅接新表失败
- 重构连接异常与暂停消费判定
- 增加启动配置摘要日志
- 增加 stats 指标
- 补测试
- 补 smoke 验证脚本
- 按“旧开新关 → 双开 → 关旧留新”上线