Add incremental backoff to database health job deferrals
What does this MR do and why?
Jobs deferred by defer_on_database_health_signal are re-enqueued at the worker's fixed delay_by for as long as the stop signal lasts. During long signals (autovacuum on a large table can run for hours), a job with the 5-second delay_by default (WorkerAttributes::DEFAULT_DEFER_DELAY; distinct from SkipJobs::DELAY, the feature-flag deferral branch's flat delay, 5 minutes by default) cycles through Redis thousands of times, re-evaluating the health indicators on every attempt, and all deferred jobs re-check in lockstep and execute together when the signal clears.
This MR makes the delay grow while the stop signal persists, using the deferred_count the job hash already carries:
- First deferral: unchanged, the worker's
delay_by(no behavior change for any of the ~185 opted-in workers unless a job is deferred repeatedly). - Each consecutive deferral:
delay_by * 2^(deferred_count - 1), capped at an absolute 30 minutes, so a job always retries within 30 minutes of the database recovering. - Up to 10% jitter subtracted from backed-off delays, so jobs deferred together do not retry at the same moment (thundering herd on recovery was observed in !205521 (merged)) while the 30-minute cap remains a hard ceiling.
- Only the
:database_health_checkbranch backs off. The:feature_flagdeferral branch (run_sidekiq_jobs_*, an incident-response lever) keeps its flat delay.
Behind the incremental_database_health_defer_delay feature flag (gitlab_com_derisk, disabled by default). No changelog entry per the changelog guidelines for default-off flags.
Notes for reviewers:
- No job payload changes:
deferred_countalready round-trips throughApplicationWorker.deferred, so mixed-version nodes are safe during rolling upgrades (an old-version Sidekiq node simply applies the flat delay). - Backoff reduces re-enqueue churn and the recovery herd. It intentionally does not address the per-execution health-check cost (Prometheus/Mimir 429s); that is complementary work tracked in #413961.
- Cross-reason
deferred_count: a job previously deferred by therun_sidekiq_jobs_*flag enters health-check backoff with its existing count, so its first health-check delay can be deeper thandelay_by. Accepted intentionally (the cap bounds it); pinned by a spec.
References
- Feature issue: Make sidekiq defer_on_database_health_signal to... (#536042)
- Rollout issue: [FF] `incremental_database_health_defer_delay` ... (#608150)
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist.