Files
Web_BLS_Heartbeat_Server/openspec/changes/add-g4-hot-dual-write/design.md
XuJiacheng 43fa7505e5 feat: 新增 G4 热表独立双写能力
- 新增配置项以支持旧/新明细表的独立写入开关及目标表名。
- 重构 DatabaseManager,抽象通用批量 COPY 写入内核,支持不同目标表的复用。
- 新增双明细写入编排器,支持旧/新表独立执行、重试及 fallback。
- 调整 HeartbeatProcessor.processBatch(),确保 room_status 独立执行。
- 错误表仅记录新表写入失败,旧表失败不再写入错误表。
- 重新定义消费暂停策略,基于当前启用的关键 sink 判断。
- 补充按 sink 维度的统计项与启动日志。

新增 G4 热表相关的数据库规范与处理逻辑,确保系统在双写模式下的稳定性与可扩展性。
2026-03-09 15:49:12 +08:00

4.0 KiB
Raw Blame History

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

  1. 部署代码,保持 DB_LEGACY_HEARTBEAT_ENABLED=trueDB_G4_HOT_HEARTBEAT_ENABLED=false
  2. 验证旧模式下行为不变
  3. 开启 DB_G4_HOT_HEARTBEAT_ENABLED=true,双写观测
  4. 确认新表写入稳定后,关闭 DB_LEGACY_HEARTBEAT_ENABLED=false
  5. 过渡观察期后清理旧表相关配置

Open Questions

  • 新表 heartbeat.heartbeat_events_g4_hot 的分区预创建是否由同一个 heartbeat.ensure_partitions 函数覆盖?还是需要独立的分区保障函数?
  • 新表的 guid 列是否需要在写入时由应用层生成?还是有数据库默认值?