Defer storing security reports on sec database health
What does this MR do and why?
Adds database-health deferral to Security::StoreSecurityReportsByProjectWorker, mirroring the pattern already merged for Sbom::IngestReportsWorker.
- Adds
defer_on_database_health_signal :gitlab_sec, [::Vulnerabilities::Finding.table_name], 1.minuteand aself.defer_on_database_health_signal?override gated on a new feature flag. - The worker calls
Security::Ingestion::IngestReportsService, the same vulnerability ingestion path used bySecurity::Scans::IngestReportsWorker, so it declares the same table.Vulnerabilities::Finding.table_nameresolves tovulnerability_occurrences, derived from the model rather than hardcoded since that exact name has previously been mistyped. - No actor is available: the middleware evaluates the predicate at job-fetch time with no project or user in scope, and deferral is an all-or-nothing throttle. Hence the inline
rubocop:disable Gitlab/FeatureFlagWithoutActor. - New flag
defer_store_security_reports_on_database_health(gitlab_com_derisk, default disabled,group::security foundations, milestone 19.5). Default-off, so no behaviour change until enabled.
Evidence
The Kibana panel "Sec DB writes per job by worker class (gprd)", over a 12-hour window ending 2026-09-12 08:30 UTC, shows Security::StoreSecurityReportsByProjectWorker peaking at 2,807 sec-database writes in a single job — the second-highest of any worker, behind Sbom::IngestReportsWorker at 16,896. Every other worker is two orders of magnitude lower: Security::StoreScansWorker at 346, Sbom::RemoveOldDependencyGraphsWorker at 152, Sbom::BuildDependencyGraphWorker at 86.
Note: the panel is titled "p99" but its legend series are prefixed (100), so these are the maximum per-job write count, not a 99th percentile — treat them as peak per-job writes.
Sbom::IngestReportsWorker has already been gated for exactly this reason, in !254955 (merged).
Behaviour when the flag is on
- If a database health indicator returns a stop signal,
Gitlab::SidekiqMiddleware::SkipJobsreschedules the job instead of running it. - First deferral waits 1 minute; consecutive deferrals back off exponentially to a 30 minute cap. That escalation is itself behind the pre-existing
incremental_database_health_defer_delayflag. - Deferred jobs appear in logs as
job_status: deferredwithjob_deferred_by: database_health_check.
Known limitation: the AutovacuumActiveOnTable indicator does not currently observe the sec database (#628693), so today the effective gating comes from cluster-wide indicators, chiefly WalRate. The table list is declared for correctness and for when that gap closes.
References
- Related merge request that gated the SBOM worker: !254955 (merged)
- Autovacuum indicator connection gap: #628693
- WAL runbook: https://runbooks.gitlab.com/patroni/primary_db_node_wal_generation_saturation/
Screenshots or screen recordings
Backend-only change, no UI. The evidence image above covers the production data behind this change.
How to set up and validate locally
bundle exec rspec ee/spec/workers/security/store_security_reports_by_project_worker_spec.rb— coversdatabase_health_check_attrsand both feature flag states.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
