update
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 10m49s

This commit is contained in:
2026-09-03 09:28:28 +08:00
parent dbfeeee253
commit 370fd3e67f
150 changed files with 64402 additions and 247 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-09-02

View File

@@ -0,0 +1,69 @@
## 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。
回滚时恢复上一版二进制和原有配置;已跳过的正常无变化日志不补写,已积压的归档日期仍按受控任务处理。

View File

@@ -0,0 +1,31 @@
## Why
生产环境异常重启后的轮询积压与日志留存任务同时执行,使 PostgreSQL 出现高 I/O 等待和锁等待。当前每次正常轮询都会持久化审计事件、资源快照与外部交互日志,即使观测结果没有任何业务变化,导致轮询频率直接放大为数据库写入量。
## What Changes
- 正常且无业务变化的轮询不再创建逐次 `tb_audit_event``tb_audit_event_resource``tb_integration_log`;轮询仍更新必要的短期去重、指标和调度状态。
- 网络状态、实名状态、套餐状态、有效流量增量等业务事实变化,以及外部调用失败、未知结果和人工触发,继续持久化业务事实与可调查记录。
- 支付、审批、入站回调等需要可靠幂等、恢复或渠道裁决的外部交互继续使用 PostgreSQL 持久化;不得以 Redis 替代其权威状态。
- 为轮询增加全局硬并发上限与背压语义,阻止积压任务在恢复或重启后同时压垮数据库和渠道。
- **BREAKING** 正常无变化的轮询不再出现在外部交互日志列表或审计调查中;查询仅展示状态变化、异常、人工操作和其他需可靠保留的记录。
- 将 Audit 日归档、Integration Log 日归档和日志日留存改为独立的受控任务:可整体停用、每次执行受日期或工作量预算约束,且不得与核心轮询争抢无上限资源。
## Capabilities
### New Capabilities
- `polling-load-control`: 轮询任务的全局并发上限、背压和正常无变化观测的低写入处理。
### Modified Capabilities
- `operations-audit`: 调整审计事实的保留边界,并使审计归档与日留存任务受控、限量执行。
- `external-integration`: 调整高频正常轮询的外部交互日志保留边界,并使 Integration Log 归档与留存任务受控、限量执行。
## Impact
- 轮询任务、卡观测和 Gateway 调用路径。
- 审计 Writer、Integration Log Repository 与调查查询的记录策略。
- Redis 轮询并发控制、Asynq Worker 队列与定时任务注册。
- Worker 配置、生产运维说明和观测指标。
- 不新增数据库 Schema不修改既有迁移。

View File

@@ -0,0 +1,31 @@
## ADDED Requirements
### Requirement: 高频轮询外部交互日志保留边界
系统 SHALL 不为成功且无业务变化的自动轮询逐次创建 `tb_integration_log`。支付、审批、入站回调及其他依赖外部交互记录实现可靠幂等、结果恢复或渠道裁决的调用 MUST 继续持久化其外部交互记录。轮询外部调用失败、响应无效或结果未知时,系统 MUST 持久化可调查的失败或恢复记录。
#### Scenario: Gateway 轮询成功且状态未变化
- **GIVEN** 一次 Gateway 自动轮询成功,且结果没有引起业务事实变化
- **WHEN** 系统完成该轮询
- **THEN** 系统不创建该次轮询的 Integration Log
#### Scenario: Gateway 轮询调用失败
- **WHEN** 一次 Gateway 自动轮询超时、连接失败或返回无效响应
- **THEN** 系统创建可调查的失败或恢复记录,并按既有策略处理后续轮询
#### Scenario: 入站支付回调
- **GIVEN** 支付渠道发送入站回调
- **WHEN** 系统处理该回调
- **THEN** 系统继续使用可靠持久化的外部交互记录保证既有幂等和恢复语义
### Requirement: 外部交互日志归档与留存受控执行
系统 SHALL 在审计归档与日留存任务总开关关闭时停止 Integration Log 的日归档和日留存处理,并安全跳过已入队的相关任务。总开关开启后,每次相关任务执行 MUST 至多处理一个已结束的上海自然日,且必须继续满足既有归档校验和 pending 记录保留规则。
#### Scenario: Integration Log 归档任务被停用
- **GIVEN** 审计归档与日留存任务总开关关闭
- **WHEN** Integration Log 日归档或日留存任务被调度或消费
- **THEN** 系统不扫描、归档、删除或更新在线 Integration Log
#### Scenario: Integration Log 积压受控推进
- **GIVEN** 任务总开关开启且存在多个满足既有归档条件的日期
- **WHEN** 系统执行一次 Integration Log 归档或留存任务
- **THEN** 系统仅处理一个自然日,并继续保留未处理日期以供后续执行

View File

@@ -0,0 +1,27 @@
## ADDED Requirements
### Requirement: 轮询审计事实保留边界
系统 SHALL 仅为产生业务事实变化、轮询失败、结果未知或人工触发的轮询操作持久化统一审计事件及资源快照。成功且无业务变化的自动轮询 MUST 不创建 `tb_audit_event``tb_audit_event_resource`,审计调查接口仅返回实际已保留的历史事实。
#### Scenario: 自动轮询成功但无业务变化
- **GIVEN** 一次自动轮询成功且没有改变任何业务状态、有效流量或风险结论
- **WHEN** 系统结束该轮询处理
- **THEN** 审计事件和审计资源表不新增该次轮询记录
#### Scenario: 自动轮询改变业务事实
- **GIVEN** 一次自动轮询产生可应用的业务状态或有效流量变化
- **WHEN** 系统提交该变化
- **THEN** 系统保留对应的审计事实与资源快照
### Requirement: 审计归档与日留存受控执行
系统 SHALL 提供独立的审计归档与日留存任务总开关。总开关关闭时,系统 MUST 不调度、不消费且安全跳过已入队的 Audit 日归档、Integration Log 日归档和日志日留存任务,不扫描或修改在线日志表。总开关开启时,每次任务执行 MUST 至多处理一个已结束的上海自然日;维护者可在低峰期重复执行以推进历史积压。
#### Scenario: 归档与留存总开关关闭
- **GIVEN** 审计归档与日留存任务总开关关闭
- **WHEN** 定时器触发或 Worker 取得已入队的归档或留存任务
- **THEN** 系统跳过该任务,不扫描 `tb_audit_event``tb_audit_event_resource``tb_integration_log`,且不创建归档或清理记录
#### Scenario: 总开关开启时处理积压
- **GIVEN** 审计归档与日留存任务总开关开启,且存在多个未处理的已结束自然日
- **WHEN** Worker 执行一次归档或留存任务
- **THEN** 系统至多处理一个自然日,并保留其余日期供后续受控执行

View File

@@ -0,0 +1,47 @@
## Purpose
使高频卡轮询在多 Worker、重启积压和渠道延迟场景下保持受控吞吐避免正常无变化观测将数据库写入量放大为不可接受的 I/O 负载。
## ADDED Requirements
### Requirement: 轮询全局并发与背压
系统 SHALL 同时对全部轮询任务以及每一种轮询任务应用跨 Worker 实例共享的全局并发上限。达到任一上限的任务 MUST 不调用外部渠道、不创建审计或外部交互记录,并按既有调度语义延后执行。配置的上限 MUST 受安全范围约束,不能以零、负数或不受约束的大值绕过背压。
#### Scenario: 多 Worker 达到同类轮询上限
- **GIVEN** 多个 Worker 正在执行同一种轮询,且该任务的全局并发已达到配置上限
- **WHEN** 又一个该类型轮询任务开始处理
- **THEN** 系统不发起外部调用、不写入 PostgreSQL 观测记录,并将任务延后处理
#### Scenario: 达到全部轮询任务总上限
- **GIVEN** 不同种类的轮询任务合计已达到全局总并发上限
- **WHEN** 任意一种新的轮询任务开始处理
- **THEN** 系统不发起外部调用、不写入 PostgreSQL 观测记录,并将任务延后处理
#### Scenario: 轮询上限配置非法
- **WHEN** 维护者提交超出允许范围或非正数的轮询并发上限
- **THEN** 系统拒绝该配置并保留原有有效上限
### Requirement: 正常无变化观测的低写入处理
系统 SHALL 将“渠道调用成功且未产生业务事实变化”的轮询视为短期运行观测,而非持久化审计事实。该结果 MUST 不创建逐次 Audit Event、Audit Event Resource 或 Integration Log系统仍 MUST 维护下一次调度所需状态,并可保留短期去重或聚合指标。
#### Scenario: 网络状态轮询无变化
- **GIVEN** 一张卡的网络状态轮询成功,且规范化后的状态与当前业务事实一致
- **WHEN** 系统应用该轮询结果
- **THEN** 系统不新增审计事件、审计资源或外部交互日志,并按配置继续后续轮询
#### Scenario: 流量读数无有效增量
- **GIVEN** 一张卡的流量轮询成功,且读数未形成有效流量增量或跨周期业务变化
- **WHEN** 系统应用该轮询结果
- **THEN** 系统不新增审计事件、审计资源或外部交互日志,并保留后续调度能力
### Requirement: 业务变化与轮询异常仍可追溯
系统 SHALL 在轮询产生网络、实名、套餐或有效流量等业务事实变化时持久化必要的业务事实与审计记录。外部调用失败、响应无效或结果未知时,系统 MUST 保留可调查的失败或恢复信息,且不得把失败静默降级为正常无变化观测。
#### Scenario: 轮询产生网络状态变化
- **GIVEN** 渠道返回的网络状态与当前业务事实不同
- **WHEN** 系统成功应用该状态变化
- **THEN** 系统持久化业务状态变化及相应审计事实
#### Scenario: 轮询渠道调用失败
- **WHEN** 轮询调用渠道超时、失败或返回无效响应
- **THEN** 系统保留可调查的失败或恢复信息,并按既有策略安排后续处理

View File

@@ -0,0 +1,26 @@
## 1. 轮询背压配置
- [x] 1.1 在 Worker/轮询配置中加入默认保守且受范围校验的全部轮询总并发上限,并保留现有分类轮询上限。
- [x] 1.2 扩展 Redis 轮询令牌获取、释放和 TTL 修复,使其原子地同时限制总量与任务类型;令牌不足时沿用延后入队语义。
- [x] 1.3 收紧轮询并发配置入口的参数校验,拒绝零、负数和超过代码安全上限的配置,并记录令牌不足的结构化指标日志。
## 2. 轮询记录策略
- [x] 2.1 盘点所有 Gateway 自动轮询处理器及其预调用 Integration Log、审计 Writer 调用,区分正常无变化、业务变化、失败和未知结果路径。
- [x] 2.2 为轮询增加按终态记录外部交互的最小持久化路径:失败、无效/未知结果和业务变化保留可调查记录;成功无变化不创建 Integration Log。
- [x] 2.3 修改所有自动轮询处理器,使成功无变化路径不创建 Audit Event、Audit Event Resource 或 Integration Log且不影响下一次调度、缓存失效和领域观测判断。
- [x] 2.4 确保轮询业务变化和失败仍写入必要的业务事实、审计或恢复记录;支付、审批、入站回调及其他非轮询可靠外部交互路径保持不变。
- [x] 2.5 为无变化跳过、变化持久化、失败持久化及按总量/分类限流延后的轮询输出可聚合结构化日志。
## 3. 归档与留存降载
- [x] 3.1 新增默认关闭的审计归档与日留存任务总开关,并同步配置加载、生产运行说明和 Worker 启动日志。
- [x] 3.2 总开关关闭时停止注册相关定时任务,并让 Audit 日归档、Integration 日归档和日留存处理器安全跳过已入队任务。
- [x] 3.3 将留存服务从一次跨全部历史日期的循环改为一次最多处理一个已结束上海自然日保留既有归档校验、pending 保留和物理清理语义。
## 4. 验证与运维交接
- [x] 4.1 在隔离环境核验:成功无变化轮询不新增三张日志表记录;状态变化和失败仍保留可调查记录;入站回调幂等与恢复路径不受影响。
- [x] 4.2 在隔离环境核验:总量或分类轮询令牌耗尽时不调用渠道且任务延后;非法并发配置被拒绝。
- [x] 4.3 在隔离环境核验:归档/留存总开关关闭时已入队任务不扫描在线日志表;开启时单次仅处理一个日期。
- [x] 4.4 执行 `gofmt``go build ./cmd/api ./cmd/worker``go run cmd/gendocs/main.go``openspec validate --all``./scripts/context-health.sh`,并记录生产低峰发布、并发预算逐项调整和回滚步骤。