Skip to content

service-messaging: NotificationDispatcher issues 32 statements per tick on an EMPTY outbox (reap runs per partition, twice) — idle cost scales with partitions × table size and never backs off #17610

Description

@hotlong

现象

outbox 完全为空(零 pending)时,NotificationDispatcher 每一跳仍然:

  • runTick 遍历 partitionCount 个分区(默认 8dispatcher.ts 构造函数 partitionCount ?? 8
  • 每个分区 runPartition 依次调用 outbox.claim()outbox.claimDigest()
  • sql-outbox.ts 里这两个方法各自先跑一次 reap(UPDATE … WHERE status='in_flight' AND claimed_at < now - claimTtlMs,代码注释自称 environment-wide),再跑一次候选 find,没有候选即返回

⇒ 空闲时每跳 8 × (2 + 2) = 32 条语句。其中 reap 逻辑上每跳至多需要 1 次,现在跑 16 次

intervalMs 默认 500。inflightTick 守卫会跳过重叠的跳,所以实际速率被单跳耗时封顶——远程 Turso 下每条语句一次 HTTP 往返,生产实测稳态约每秒 28 条,与 32 条 ÷ 约 1.1 秒/跳吻合。

后果(生产实测;租户信息已匿名)

在热表缺索引的租户库上(驱动侧另卡),这 32 条每条都是全表扫描。一个生产租户库在 sys_notification_delivery 约 5,700 行时,稳态约 15 万行读/秒,9 天 1,011.6 亿次读,占其 Turso 组织 99.9%。

即使驱动侧索引修好,空闲成本仍是每跳 32 次索引查找、永不停歇、每个温热 kernel 一份。

验收

  1. reap 与领取解耦:reap 每跳至多一次(或更慢的独立节奏,例如 claimTtlMs 量级),不再随 分区数 × 2 放大
  2. 空闲退避:连续若干跳零领取时逐步拉长间隔(上限可配);有投递入队时立即唤醒。若退避会让新通知的首次投递延迟变长,写明上界
  3. 计数测试防回归:对空 outbox 跑 N 跳,断言 engine 语句数的上限
  4. 语义不变:分区锁、至少一次投递、digest 折叠、claim TTL 回收——现有测试全绿

同形状,请评估(不强求同卡)

service-queueDbQueueAdapter.claimBatch(默认 1s 轮询)按 priority, scheduled_for 排序,而声明索引是 ['queue','status','scheduled_for']——排序首键 priority 不在索引里;空闲时同样恒定轮询。你若判断应该另立卡,说出来。

驱动缺索引、未配置渠道写死信两张关联卡的编号见本卡评论。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions