Batch vulnerability identifier upserts during ingestion

What does this MR do and why?

Security::StoreSecurityReportsByProjectWorker has been intermittently failing on GitLab.com with canceling statement due to statement timeout on the INSERT INTO vulnerability_identifiers ... ON CONFLICT (project_id, fingerprint) DO UPDATE ... RETURNING statement.

The size of this statement varies a lot in production. Security::Ingestion::IngestReportService slices findings into batches of 50 (Security::IngestionConstants::COMPONENTS_BATCH_SIZE), and each finding can carry up to 20 identifiers (Vulnerabilities::Finding::MAX_NUMBER_OF_IDENTIFIERS). That means Security::Ingestion::Tasks::IngestIdentifiers could end up upserting as many as 1000 rows in a single statement. Production logs from the timing out requests show statement sizes ranging anywhere from 11 to 130 rows (correlation IDs a3c2a1b54f1fb68c-DFW and e2c3fb0f54f84e54af7853f94e2dd9b2)

This MR adds an optional batch_size setting to the Gitlab::Ingestion::BulkInsertableTask DSL and passes it through to BulkInsertSafe.bulk_insert! / bulk_upsert!, which already support slicing batches internally (default 500). IngestIdentifiers now sets batch_size = 50, so each INSERT statement it issues carries at most 50 rows. No other ingestion task changes behavior, since they keep the previous effective default of 500.

  • The smaller batches still run inside the same ingestion transaction (SecApplicationRecord.transaction in IngestSliceBaseService, plus the transaction BulkInsertSafe itself opens). Total lock hold time and overall transaction duration are unchanged. What this change does is give each individual statement its own statement timeout window, rather than one large statement that can exceed the timeout on its own.
  • The deadlock-avoidance sort by [project_id, fingerprint] in IngestIdentifiers#attributes was reviewed specifically for this change. It stays valid across batch boundaries because concurrent transactions still acquire row locks in the same global order. Splitting one statement into consecutive slices does not reorder lock acquisition.
  • This is a mitigation, not a guaranteed fix. One of the observed timeouts happened on an 11-row statement, which suggests lock contention or slow ON CONFLICT arbiter index scans could also be contributing factors. We should keep an eye on statement timeout error rates for this worker after this ships.

As a follow-up, Security::Ingestion::Tasks::IngestFindingIdentifiers has the same exposure. It joins up to 50 findings against up to 20 identifiers each, meaning up to 1000 rows can be inserted into vulnerability_occurrence_identifiers in one statement. It's a good candidate for the same batch_size cap, but is left out of this MR to keep the change small and focused.

References

Closes #629542 (closed)

The production symptom being addressed is Security::StoreSecurityReportsByProjectWorker failing with canceling statement due to statement timeout on the vulnerability_identifiers upsert.

Related: the root-cause analysis of the 2026-09-16 incident is at #629530, and the parent epic is &23596.

How to set up and validate locally

New specs added:

  • One asserts bulk_upsert! is called with batch_size: 50.
  • One forces batch_size to 3 and verifies all identifiers are created and identifier_ids are set correctly on finding maps when identifiers span multiple insert batches.

Run the relevant specs:

bundle exec rspec ee/spec/services/security/ingestion/tasks/ingest_identifiers_spec.rb
bundle exec rspec ee/spec/lib/gitlab/ingestion/bulk_insertable_task_spec.rb
bundle exec rspec ee/spec/services/security/ingestion/tasks/ingest_finding_identifiers_spec.rb
bundle exec rspec ee/spec/services/security/ingestion/tasks/ingest_findings_spec.rb

Locally these ran as: ingest_identifiers_spec.rb (6 examples, 0 failures), and bulk_insertable_task_spec.rb plus the sibling task specs ingest_finding_identifiers_spec.rb and ingest_findings_spec.rb (14 examples, 0 failures). RuboCop is clean.

Optionally, you can validate manually in a Rails console with SQL logging enabled: ingest a security report for a pipeline with a large number of findings and confirm the INSERT statements against vulnerability_identifiers are capped at 50 rows each.

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.

Edited by Bala Kumar

Merge request reports

Loading
Loading