Files
junhong_cmp_fiber/openspec/changes/archive/2026-09-02-reduce-polling-observability-load/design.md
break 370fd3e67f
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 10m49s
update
2026-09-03 09:28:28 +08:00

70 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Context
见 proposal.md。当前 `PollingCarddataHandler``PollingCardStatusHandler` 在调用 Gateway 前创建 pending Integration Log成功后再终结该记录Integration Log 创建还会创建统一审计关联。因此一次无变化成功轮询至少造成审计事件、审计资源、Integration Log 创建和终结等多次 PostgreSQL 写入。生产轮询并发配置的数值远高于单机磁盘可承受范围,现有按任务类型的 Redis 信号量不能限制不同任务类型的合计压力。`AuditRetentionCleanupEnabled=false` 仅关闭物理删除,不阻止归档和留存扫描。
## Goals / Non-Goals
**Goals:**
- 让无业务变化的成功轮询不产生 PostgreSQL 可调查记录。
- 保留支付、审批、入站回调和失败/未知外部调用的可靠持久化语义。
- 用跨 Worker 的总量与分类限流阻止轮询积压同时执行。
- 让归档和留存默认不参与日常生产负载,并能低峰限量推进。
**Non-Goals:**
- 不把支付、回调或审批的可靠幂等状态迁移到 Redis。
- 不删除历史审计或 Integration Log不修改表结构或既有迁移。
- 不实现复杂自适应限流;先使用可验证的固定安全上限。
- 不改变轮询产生业务状态变化时的领域规则、Outbox 语义或渠道调用协议。
## Decisions
### 1. 轮询完成后按结果决定是否记录
轮询路径先在内存中生成稳定的关联标识并调用渠道,再应用观测结果。只有失败、无效/未知响应或观测应用产生业务变化时,才持久化一个终态的轮询外部交互记录;成功且无变化直接进入下一次调度。
审计 Writer 只在观测业务变化或失败路径被调用。轮询专用的终态记录路径不得复用“调用前先写 pending”的通用可靠调用路径以免重新引入无变化成功的两次写入和审计关联。
备选方案是在写入后异步删除无变化日志;它仍消耗 WAL 和索引 I/O不能解决问题故不采用。
### 2. Redis 仅承担协调与聚合
继续使用 Redis 保存轮询并发计数、短期去重和可选成功计数。原子获取脚本同时获取“全部轮询”令牌与“任务类型”令牌;任一令牌不足时不调用 Gateway按现有重入队流程延后。释放和 TTL 修复必须同时覆盖两个计数,避免重启或 Worker 异常后令牌永久泄漏。
Redis 丢失后只会丢失短期协调或统计,轮询可以保守重试;可靠业务状态、支付/回调幂等和外部恢复继续在 PostgreSQL 中维持。
备选方案是让 Redis 保存所有 Integration Log 或回调幂等状态Redis 不是权威持久化存储,故不采用。
### 3. 采用固定且受校验的并发预算
为全部轮询设置一个 Worker 配置总上限,并为每个任务类型保留现有动态上限。动态上限和总上限都必须校验为有限正整数;代码层硬上限防止生产误设为数千或数万。初始生产值由维护者在低峰期设置为保守值,并根据数据库 I/O、队列积压和渠道延迟逐项增加。
备选方案是只依赖 Asynq Worker `Concurrency`;它不能区分轮询和其他任务,也不能限制多实例或不同任务类型合计压力,故不采用。
### 4. 归档与留存采用默认关闭的总开关和单日预算
新增 Worker 配置总开关,默认关闭。关闭时不注册定时调度;处理器仍以安全 no-op 方式接住已入队任务,避免旧任务重试或扫描。开启时,各归档/留存执行只处理一个已结束的上海自然日;留存服务用“下一待处理日”替换跨历史日期循环。物理清理开关继续只决定是否删除,不改变新的任务总开关。
备选方案是只降低任务并发;现有跨历史日期循环单任务即可长时间占用数据库,故不采用。
### 5. 以可操作指标验证降载
轮询处理记录结构化计数:无变化跳过持久化、状态变化持久化、失败持久化、因总量/分类令牌延后。归档任务记录开关状态、处理日期和耗时。维护者据此结合 PostgreSQL I/O wait、活跃会话与 Asynq 队列深度决定是否提高预算。
## Risks / Trade-offs
- [正常轮询不再可逐次调查] → 调查接口明确只展示变化、失败和人工事实;运行次数依赖聚合指标和应用日志。
- [失败后才建立轮询外部交互记录] → 使用同一内存关联标识写入终态失败记录,保留资源、场景、请求关联和恢复摘要。
- [Redis 令牌异常泄漏或丢失] → 原子双令牌脚本配合 TTL丢失时按保守重试处理不承担权威状态。
- [过低限流造成队列延迟] → 先保护数据库监控积压、I/O 和渠道延迟后逐项提高,不允许绕过代码硬上限。
- [关闭归档导致历史在线数据继续增长] → 低峰期按单日预算受控推进,不能通过重新开启无界补偿来解决。
## Migration Plan
1. 维护者先使用现有轮询并发控制将所有任务类型降至保守值,并保持归档/留存 Worker 停止,记录 PostgreSQL 基线。
2. 发布新配置与代码时,新归档/留存总开关保持关闭,轮询总并发使用保守值。
3. 验证无变化轮询不新增三张日志表记录,失败和状态变化仍可调查,支付/回调幂等行为不变。
4. 观察稳定窗口内 I/O wait、WAL 等待、队列积压和渠道延迟;每次只调整一类轮询预算。
5. 低峰期手动开启归档/留存总开关,以单日预算推进;异常时关闭开关并停止相关 Worker。
回滚时恢复上一版二进制和原有配置;已跳过的正常无变化日志不补写,已积压的归档日期仍按受控任务处理。