Files
junhong_cmp_fiber/openspec/changes/add-polling-async-ingest/proposal.md
邹进 1214181e78
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 16m2s
feat: 轮询观测入库异步化,调网关与入库分离,采集记录经消息攒批写入
- 轮询步骤异步模式下只调网关并发布 card.ingest 入库消息,入库消费者固定并发 30、按采集 ID 去重、可重试错误不限次数退避重投
- 采集记录经 card.collection 攒批写入,失败整批重投,新增 (observation_id, observed_at) 唯一索引
- 网络、实名观测增加乱序保护;轮询读查询走缓存,套餐状态查询各自计时且失败不缓存
- 入库积压告警与反压;系统配置 polling.ingest.mode 默认 sync 可回退;测试环境 nsqd 消息全部落盘

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 16:33:29 +09:00

4.1 KiB
Raw Blame History

Why

测试环境 worker 每小时约 8,000 次 SQL context deadline exceeded、13.8 万条慢查询告警,采集记录因 3 秒写超时持续丢失。根因是按运营商账户调度的 NSQ 消费者只有"每账户上限"(41 个账户 × 20 = 820),没有总量上限,而每个轮询步骤在同一个任务里先调网关再同步写库,三百多个任务争抢 60 个数据库连接。依据 docs/2026-11-06-轮询入库异步化技术方案.md,把"调上游"与"入库"拆开:调网关继续按账户并发,入库单独发消息、由固定并发的消费者处理。

What Changes

  • 轮询的卡状态、流量、实名步骤只做"读缓存 → 调网关 → 发入库消息",不在卡任务内写库;步骤结果只反映网关调用是否成功。
  • 新增 NSQ topic card.ingest 与入库消费者,并发固定(默认 30),与账户数量无关;入库复用现有 ApplyTrafficObservation、ApplyNetworkObservation、ApplyCardObservation。
  • 入库可重试错误按退避不限次数重试、不丢弃;业务性错误记 WARN 后丢弃;本期不做死信。
  • 入库幂等与顺序:按 observation_id 去重;采集队列按采集时间去重;网络、实名观测"更新的才生效";采集记录唯一索引。
  • 新增 NSQ topic card.collection 与批量消费者,攒批写采集成功表、失败表;删除采集记录 3 秒写超时与"写失败只记告警"。
  • 优先轮询项完成、对接日志"业务事实发生变化"、网络观测导致的停止轮询、套餐步骤跳过标记移到入库段。
  • 轮询步骤调网关前的读查询走缓存(卡信息、优先项、运营商开关、绑定关系);配置匹配查套餐状态每次查询单独计时,失败不缓存"未知"。
  • 入库积压监控:积压超过 20 万条告警,超过 50 万条调度器暂停派发新卡任务,降到一半以下恢复。
  • 系统配置 polling.ingest.mode(async / sync)一键回退;发消息失败时降级为同步入库。
  • 测试环境 nsqd 改为 --mem-queue-size=0,消息全部落盘。
  • 后台人工刷新、运营商回调、激活前结算、观测序列保持同步入库。
  • 不在本 Change:死信表与重放、生产环境发布、按账户调度参数调整。

Capabilities

New Capabilities

  • polling-async-ingest:轮询观测入库消息、入库消费者并发与重试、幂等与顺序、采集记录批量写、积压告警与反压、同步回退与降级。

Modified Capabilities

  • carrier-polling-scheduler:卡任务的检查步骤改为只调网关并发入库消息;套餐步骤跳过依据改为入库段写的流量评估标记。
  • card-traffic-metering:采集记录写入改为经消息批量写、失败重试不丢,替代"写入失败只记告警"。

Impact

  • 前置:add-carrier-polling-scheduler、add-card-traffic-queue-and-observed-deduction、refactor-audit-log-persistence 须先归档(本 Change 修改它们新增或修改的要求)。
  • 代码:internal/task/polling_carddata_handler.go、polling_cardstatus_handler.go、polling_realname_handler.go、carrier_card_executor.go、polling_priority_claim.go;internal/application/cardobservation/(traffic.go、traffic_collection.go、network.go、apply.go);internal/polling/config_manager.go、tier.go;采集队列 Lua;新增入库消费者、采集记录批量消费者与积压监控;cmd/worker/carrier_scheduler.go、internal/polling/carrierqueue/dispatcher.go(反压)。
  • 数据库:采集成功表、失败表新增 (observation_id, observed_at) 唯一索引(成对迁移)。
  • 配置:系统配置 polling.ingest.mode、polling.ingest.alert_depth、polling.ingest.pause_depth;启动配置 polling.ingest.concurrency、polling.ingest.timeout、polling.collection.batch_size。
  • Redis:ingest:done:<observation_id>(TTL 48h)、flow_evaluated:<card_id>、有活动优先项的卡集合。
  • 部署:docker-compose.prod.yml 中 nsqd 改 --mem-queue-size=0。
  • 范围:基于 Iteration/8-11,只部署测试环境。