Follow-up from PR #770 review (fire-time per-agent rate limit, internal/sendrate).
Observation (cost curve, not a correctness defect): each rate-limit deferral costs ClaimSend + Reserve + ReleaseSend — 3 transactions, 2 of them UPDATEs on messages — and every still-deferred job repeats that each window. Draining a backlog of N costs ~N²/(2·limit) re-drives. A realistic burst (60 accepts/min for an hour, all scheduled for the same instant → N ≈ 3,600) is ~108k wake-ups and ~216k messages row updates over the drain; the 72h-horizon ceiling (259,200/agent) works out to ~560M. Those wake-ups also occupy outbound queue workers, so one agent's backlog adds latency to other tenants' sends.
The per-message jitter added in #770 fixes lockstep but not volume.
Proposal: the worker already holds AcceptedAt/ScheduledAt, so scale the deferral snooze by how long the message has already been deferring (e.g. exponential backoff capped well under the 72h horizon — minutes, not hours), making re-drives logarithmic. No schema change. Alternatively track a per-job deferral count.
Context: PR #770 (worker.go clampRateSnooze/rateJitter, internal/sendrate).
Follow-up from PR #770 review (fire-time per-agent rate limit, internal/sendrate).
Observation (cost curve, not a correctness defect): each rate-limit deferral costs ClaimSend + Reserve + ReleaseSend — 3 transactions, 2 of them UPDATEs on messages — and every still-deferred job repeats that each window. Draining a backlog of N costs ~N²/(2·limit) re-drives. A realistic burst (60 accepts/min for an hour, all scheduled for the same instant → N ≈ 3,600) is ~108k wake-ups and ~216k messages row updates over the drain; the 72h-horizon ceiling (259,200/agent) works out to ~560M. Those wake-ups also occupy outbound queue workers, so one agent's backlog adds latency to other tenants' sends.
The per-message jitter added in #770 fixes lockstep but not volume.
Proposal: the worker already holds AcceptedAt/ScheduledAt, so scale the deferral snooze by how long the message has already been deferring (e.g. exponential backoff capped well under the 72h horizon — minutes, not hours), making re-drives logarithmic. No schema change. Alternatively track a per-job deferral count.
Context: PR #770 (worker.go clampRateSnooze/rateJitter, internal/sendrate).