2026-09-27·by Sijie Wang#standmeet#architecture#design#events

storage-bounds

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

上级:events

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

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

每一行什么时候被删掉

失控点与控制

失控点怎么会失控控制怎么保证 / 看见
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 旧版本行等 vacuumevents 调低了 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。

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →