Preload artifact jobs when storing security scans

🤖 AI wrote this code from my instructions.

Unreviewed AI output often lands on reviewers and costs them time.
This MR is human verified: I read the full diff and ran the tests myself, because your time is as valuable as mine.

What does this MR do and why?

Security::StoreScansService#grouped_report_artifacts loaded pipeline job artifacts with no preloads:

pipeline.job_artifacts
  .security_reports(file_types: security_report_file_types)
  .group_by(&:file_type)

Ci::Pipeline has_many :job_artifacts, through: :builds. A through association does not populate the source's belongs_to :job. Security::StoreScanService delegates project and job to the artifact, and pipeline to the job. So each artifact resolved its own job, and each job its own Ci::Pipeline instance.

That last part is the expensive one. Ci::Pipeline#security_scans_created_at and #security_findings_partition_number are memoized with @x ||=, per instance. With one pipeline instance per artifact, both re-ran for every artifact.

The fix adds .preload(:project, job: :pipeline). The preloader gives every build the same Ci::Pipeline object, so the two memoized lookups collapse to one each.

Measured with the real downstream services and four report artifacts:

Queries Before After
Total 61 43
p_ci_builds 4 1
p_ci_pipelines 4 1
security_scans 13 7

The saving grows with the number of report artifacts in the pipeline.

CodeReuse/ActiveRecord forbids preload in a service, so the call carries an inline disable with a reason. A scope on the security_reports association was the alternative, but it has about 15 callers, and several only call .exists? or .any?, so preloading there would add queries for them. The sibling store_scan_service.rb already disables the same cop with its own justification.

The spec file stubs Security::StoreGroupedScansService in every other example, so the per-artifact work never runs there. The new example therefore sits in its own context that calls the real service.

References

None.

How to set up and validate locally

bundle exec rspec ee/spec/services/security/store_scans_service_spec.rb

35 examples, all pass. store_grouped_scans_service_spec.rb and store_scan_service_spec.rb together: 59 examples, all pass.

Remove the preload and run the new example again:

Failure/Error: expect(recorder.log.grep(/FROM "p_ci_pipelines"/).size).to eq(1)

  expected: 1
       got: 2

The example uses two artifacts, so without the preload the pipeline loads twice.

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.

Merge request reports

Loading
Loading