- 新增配置项以支持旧/新明细表的独立写入开关及目标表名。 - 重构 DatabaseManager,抽象通用批量 COPY 写入内核,支持不同目标表的复用。 - 新增双明细写入编排器,支持旧/新表独立执行、重试及 fallback。 - 调整 HeartbeatProcessor.processBatch(),确保 room_status 独立执行。 - 错误表仅记录新表写入失败,旧表失败不再写入错误表。 - 重新定义消费暂停策略,基于当前启用的关键 sink 判断。 - 补充按 sink 维度的统计项与启动日志。 新增 G4 热表相关的数据库规范与处理逻辑,确保系统在双写模式下的稳定性与可扩展性。
4.0 KiB
4.0 KiB
Context
当前系统只有一个明细写入目标 heartbeat.heartbeat_events。需要新增 heartbeat.heartbeat_events_g4_hot 作为第二个独立写入目标,两路完全解耦,支持平滑迁移。
约束:
- 同一 PostgreSQL 实例/数据库,共享同一连接池
- 数据来源(Kafka)、解包、校验、转换逻辑完全不变
room_status始终独立执行,不依赖任何明细写入- 错误表仅服务新表写入失败
Goals / Non-Goals
-
Goals:
- 旧/新明细表可通过启动配置独立开关
- 新表复用现有批量 COPY 写入内核
- 两路写入互不影响(错误隔离、重试隔离、fallback 隔离)
room_status始终执行,不受明细开关影响- 错误表仅记录新表失败
- 支持"旧开新关 → 双开 → 关旧留新"迁移路径
-
Non-Goals:
- 不引入第二套 PostgreSQL 连接配置(同库新表)
- 不改变 Kafka 消费链路、消息格式、数据转换
- 不做运行时热开关(重启生效即可)
- 不在本次改造中删除旧表相关代码
Decisions
Decision 1: 共享连接池,不新建第二个 Pool
- What: 旧表和新表都使用同一个
pg.Pool - Why: 两个目标表在同一 PostgreSQL 实例/数据库下,共享连接池避免资源浪费
- Alternatives: 为新表创建独立连接池 → 复杂度高,同库下无必要
Decision 2: 抽象通用写入内核而非复制代码
- What: 把现有
insertHeartbeatEvents()中与表名绑定的逻辑抽象为_insertEventsToTarget(events, targetConfig) - Why: 避免维护两份几乎相同的 COPY/INSERT 代码;新表和旧表的列清单不同但写入流程一致
- Alternatives: 复制一份改表名 → 双维护成本高
Decision 3: 编排层聚合结果,Processor 不感知双写细节
- What:
DatabaseManager.insertHeartbeatEventsDual()负责编排两路写入并聚合结果;HeartbeatProcessor只消费聚合结果 - Why: 保持 Processor 职责单一,不引入 DB 路由逻辑
Decision 4: room_status 独立于明细写入
- What:
upsertRoomStatus()始终在processBatch()中独立执行,不依赖旧/新明细写入成功 - Why: 用户明确要求即使两路明细都关闭,仍需写
room_status
Decision 5: 错误表仅服务新表
- What:
insertHeartbeatEventsErrors()仅接收新表 (g4Hot) 写入失败的记录 - Why: 旧表即将下线,错误追踪聚焦新表;旧表失败仅记日志/统计
Decision 6: 消费暂停基于"启用中的关键 sink"
- What: 只有当前启用的写入路径全部发生连接级不可恢复故障时,才暂停 Kafka 消费
- Why: 避免新表结构/权限问题误停全局消费
Risks / Trade-offs
Risk 1: 新表列映射不完整
- 新表字段比旧表更宽,旧数据可能缺少部分新表列(如
svc_*,*_1,*_2,*_residual,guid) - Mitigation: 旧数据确认可完整映射到新表必填字段与唯一键;新增列允许 NULL 或有默认值
Risk 2: 同库双写增加连接池压力
- 单批数据写两次,连接池使用量翻倍
- Mitigation: 两路顺序执行而非并发,复用同一连接池;必要时可调大
maxConnections
Risk 3: 新表分区函数可能与旧表不同
heartbeat.ensure_partitions是否也涵盖新表的分区维护- Mitigation: 实现时需确认分区保障函数对新表的覆盖,或为新表配置独立的分区保障调用
Migration Plan
- 部署代码,保持
DB_LEGACY_HEARTBEAT_ENABLED=true、DB_G4_HOT_HEARTBEAT_ENABLED=false - 验证旧模式下行为不变
- 开启
DB_G4_HOT_HEARTBEAT_ENABLED=true,双写观测 - 确认新表写入稳定后,关闭
DB_LEGACY_HEARTBEAT_ENABLED=false - 过渡观察期后清理旧表相关配置
Open Questions
- 新表
heartbeat.heartbeat_events_g4_hot的分区预创建是否由同一个heartbeat.ensure_partitions函数覆盖?还是需要独立的分区保障函数? - 新表的
guid列是否需要在写入时由应用层生成?还是有数据库默认值?