存储边界:每种失控都有硬上限

更新于 · 在 sijie.xyz 查看原条目 ↗

状态: 已在 v0.1.76 发布(2026-09-27)—— 设计与落地记录见 StandMeet 仓库的 docs/design/event-bus-outbox-webhooks.md。

队列在数据库里,最大的风险是存储失控。原则:每一种失控都有硬上限,并且看得见,不靠“应该会清理”。

每一行什么时候被删掉

stateDiagram-v2
  state events_row {
    [*] --> unfanned : trigger / Record
    unfanned --> fanned : relay claims it and sets fanned_out_at
    unfanned --> poisoned : relay failed on it 5 times
    poisoned --> unfanned : events.requeue (Tasks panel)
    fanned --> [*] : fanned out and older than 7 days → hourly events retention deletes it
  }
  state river_job_row {
    [*] --> in_progress : relay enqueues (index jobs coalesce per subject within a batch)
    in_progress --> completed : success
    in_progress --> discarded : permanent failure or attempts exhausted
    completed --> [*] : River cleaner deletes after 24 h
    discarded --> [*] : River cleaner deletes after 7 days
  }
  state endpoint {
    [*] --> enabled
    enabled --> failing : a delivery fails (failing_since set)
    failing --> enabled : a delivery succeeds
    failing --> disabled : 5 days of continuous failure
    disabled --> enabled : owner re-enables
    note right of disabled : once disabled the fan-out enqueues no new deliveries to it
  }

失控点与控制

失控点 怎么会失控 控制 怎么保证 / 看见
events 只增不减 没人删;或 relay 卡住、未扇出的行堆积 周期任务 events retention 每小时删“已扇出且 ≥ 7 天”的行。未扇出和已隔离的行保留:它们就是积压。 总览显示积压数、隔离数、最老未扇出事件的年龄和表大小。未扇出超过 1000 条或最老超过 5 分钟告警 events_backlog;有隔离行告警 events_poisoned。告警在面板上,不发邮件:邮件本身就可能是失败的那一方。
river_job 完成行堆积 完成的任务行留在表里 River 清理服务,由选出的主节点按默认值跑:completed 24 小时、discarded 7 天 总览显示表大小;存在 discarded 任务时告警 jobs_discarded
死端点重试堆积 对方网站长期挂掉 MaxAttempts 18;failing_since 有值期间新投递排到 5 分钟后;连续失败 5 天自动停用,停用后不再排新投递 端点状态与停用原因在后台可见
批量操作扇出爆炸 Obsidian 一次导入 2000 篇 relay 每轮最多 200 行;corpus.index 在同一批内按主体合并。webhook 不合并也不防抖:每条事件是一件事实,给每个订阅它的端点投一次 e2e events-bulk-import-bound
空更新也触发事件 只刷了 updated_at 触发器加 WHEN:关心的列真变了才写 spec 用“哨兵之前的事件序列”正向断言
一条坏事件卡死流水线 relay 处理某条反复出错 失败的那批逐行重试,一行出错不阻塞其它行;同一行失败 5 次就隔离并告警;投递失败交给各自任务重试 同上,看积压数
死元组膨胀 队列表高频更新,MVCC 旧版本行等 vacuum events 调低了 autovacuum 阈值(scale factor 0.02)。river_job 保持 River 的设置,因为那份 DDL 归 River。量本来就是一天几千行 总览显示表大小

积压数、最老事件年龄、表大小和告警都显示在 tasks-panel 的总览里。

先例

访客流量表原本也会无限增长,现在靠每 24 小时一次的保留期任务(visitor traffic retention)兜住;事件和任务表照这个模式来。

换别的方案也躲不开

  • Redis 队列同样会堆积,且我们的 Redis 满了是静默淘汰。
  • Kafka 自带按时间 / 大小保留,代价是多运维一个服务。

见 why-not-a-broker。

验收

积压 / 表大小在后台可见(e2e tasks-panel、tasks-panel-more);批量导入的上限有自己的 e2e(events-bulk-import-bound);保留期清理和积压告警有 UT。见 events-roadmap · events-test-plan。