All three open decisions were settled as proposed: a dedicated SourceNotConfiguredError so a misconfigured source reaches the dead letter queue instead of retrying forever, a 5 second retry_after_s so a backlog does not stampede a backend that is already down, and public export so callers can alarm on assembly defects specifically.
4.4 KiB
type, node_id, title, date
| type | node_id | title | date |
|---|---|---|---|
| design | design:governance-backend-error | 治理后端故障归位为 scope 级不可用(Issue #7) | 2026-08-06 |
治理后端故障归位为 scope 级不可用(Issue #7)
全文见 2026-08-06-governance-backend-error-design.md。来源: Gitea Issue #7(下游 CHSAnalyzer3 按异常类型分流失败)。状态: 已批准(2026-08-06,人类逐条拍板 Q1/Q2/Q3),待 writing-plans。
问题: 限流/熔断状态后端故障时库 fail-closed,一个请求都发不出去——语义上就是 scope 级不可用,但 GovernanceBackendError 是 PolyGatewayError 的直接子类,只写 except GatewayUnavailableError 的调用方接不住,于是 Redis 抖一下,积压任务一批批消耗业务失败预算进死信,而那是运维重启就好的故障。
选定方案
| 决策 | 选定 | 关键理由 |
|---|---|---|
| A 类型树 | GovernanceBackendError 改继承 GatewayUnavailableError,SCOPE_REASONS 增 governance_backend_down,reason 恒为该值 |
加父类是扩大不是破坏(既有 except GovernanceBackendError 照旧命中);库内仅 telemetry.py:210 一处捕父类且已并列写两者,零回归 |
B retry_after_s |
模块常量 GOVERNANCE_BACKEND_RETRY_AFTER_S = 5.0,非环境配置项 |
后端恢复时间物理上不可知(不同于熔断冷却有确定到期时刻);取 0 会让积压任务零延迟批量重投,把一次故障放大成风暴 |
| C scope 来源 | 后端层用 self._scope;QuotaGate/BreakerGate 构造函数注入,三处装配(retry.py/ocr.py/embedding.py)各传一行 |
两个包装器是后端异常的唯一入口,注入点收敛;三处装配本就持有 self._scope |
| D 未知源拆分 | _cfg() 的 2 处改抛新增的 SourceNotConfiguredError,有意不放在 GatewayUnavailableError 之下 |
那是装配缺陷不是后端故障;随整类归入"可重投"会让配置写错的任务永远重投、永不进死信——本 issue 要修的 bug 的镜像 |
| E message 保全 | super().__init__() 后覆写 self.args = (message,) |
父类会把 message 覆盖为 f"{scope} 网关暂时不可用: {reason}",而 22 处构造点的诊断串是排障主线索。机制已实跑验证 |
被否决的备选
| 备选 | 否决原因 |
|---|---|
| B(issue 原议): 只补文档,类型树不动 | 正确性依赖每个下游都读到那句话;本 issue 本身就是"文档读不出来"引发的,同一失效模式不能用同一种药治 |
C: 在 RetryMW 边界包成 AllSourcesExhausted |
比选定方案更具破坏性——下游现有 except GovernanceBackendError 直接失效 |
| D: 后端层不再构造该异常,原始异常穿透由包装器统一翻译 | 初评时倾向。redis/limiter.py:133,151 的 RedisPermit.release/settle 依赖 except GovernanceBackendError 实现释放侧降级,穿透后接不住会破坏该既有行为;改 except Exception 则违反 P5 |
retry_after_s 复用 BackpressureConfig.poll_interval_s |
该值只有三个装配点持有,为此给后端加构造参数等于让状态存储层持有重投策略,违反 P7 |
新增配置项 PGW_GOVERNANCE_BACKEND_RETRY_AFTER_S |
YAGNI;无下游表达过需要,真需要时下游可忽略该字段用自有退避 |
对 issue 前提的四处修正
泄漏路径是三条不是两条(retry.py:216 的 progress_age_s() 同样在 catch 之外);构造点 22 处;其中 2 处语义完全不同(未知源);retry_after_s=0 语义通但工程不通。
根因记录: ARCHITECTURE.md §6.1 错误分类表里 GovernanceBackendError 一次都没出现——它是 M2 引入分布式后端时新增的,当时未回补架构表,于是它在"调用方视角的分类学"中从来没有位置,README 的遗漏是这个遗漏的下游后果。
独立审查修正(2026-08-06, Codex)
4 条意见逐条核验: 两条"架构文档未同步"实质成立但性质是执行顺序 → 新增 §8.1 钉死"ARCHITECTURE.md §6.1 修订先于/同批于实现";"新错误类违反四分类铁律"部分成立——铁律论域被误读(GatewayUnavailableError 族本就合法处在四分类之外),但原表述确会引起疑虑 → §7 补写三论域划分论证,并把"复用 RequestRejectedError"增列为待人类权衡的备选;两条建议性意见(常量非配置项的说明、决策编号 D→Q 防与架构 D1–D14 混淆)已采纳。