Use `Ci::Preloaders::PipelinePreloader` for bulk pipeline preloading on non-CI-DB models
Problem
!236832 (merged) introduced a partition-aware bulk preloader for Ci::Pipeline associations, deferring caller conversion: "No callers are converted yet — subsequent MRs will opt in list endpoints (REST, GraphQL, Sidekiq) one at a time."
ActiveRecord's standard preload / includes for belongs_to :pipeline associations on non-CI-DB models issues WHERE id IN (...) against p_ci_pipelines with no partition_id filter, causing a full cross-partition scan across every partition. As the number of CI partitions grows, this becomes progressively more expensive.
#599815 covers the single-record path via Ci::Partitionable::AssociationFinder#partitionable_belongs_to_loader. This issue covers the bulk path: making list endpoints (REST, GraphQL, Sidekiq) preload the pipeline association in a partition-aware way for the non-CI-DB models listed below.
Proposal
Note (approach changed): The standalone
Ci::Preloaders::PipelinePreloader.new(...).preload_allAPI originally proposed here was removed in !240414 (merged). The bulk path is now a concern-level relation hook inCi::Partitionable::AssociationFinder, gated behind thepartition_aware_pipeline_preloadfeature flag (see #603166).
For each model, register the pipeline association in the loader and convert its list scope to chain with_partition_aware_preload:
include Ci::Partitionable::AssociationFinder
belongs_to :pipeline, class_name: 'Ci::Pipeline'
partitionable_belongs_to_loader :pipeline
scope :preload_pipeline, -> { with_partition_aware_preload.preload(:pipeline) }with_partition_aware_preload extends the relation with PipelineRelationPreload, which overrides AR's preload_associations: it collects the registered foreign-key values, resolves them through Gitlab::Ci::Pipeline::BulkByIdLookup (partition-pruned), and feeds the results back via ActiveRecord::Associations::Preloader#available_records:. Nested child preloads (e.g. pipeline: { project: :namespace }) are preserved by the trailing super.
Behavior:
- FF on (
partition_aware_pipeline_preload): partition-pruned bulk pipeline lookup, no full cross-partition scan. - FF off: falls back to vanilla AR bulk preload (still not N+1), by design.
Scope review
After auditing all 18 models for an actual bulk pipeline-load path (a real collection/list endpoint that preloads the pipeline association), 8 models have no bulk path and need no change here. They already declare partitionable_belongs_to_loader (covered by #599815 for the single-record reader), but no list endpoint loads the pipeline association in bulk:
| Model | Why no bulk fix |
|---|---|
Environments::Job |
pipeline association never read; only bulk inserts and reverse FK queries |
Vulnerabilities::Statistic |
latest_pipeline_id is write-only; no endpoint exposes the pipeline |
Vulnerabilities::PartialScan |
only EXISTS/FK-copy/pluck usage; association never read |
Security::Scan |
single-record access only; GraphQL ScanType exposes no pipeline field |
Sbom::OccurrenceRef |
pipeline_id write-only (ingestion); association never read |
Dependencies::DependencyListExport |
single-record only (exportable); entity exposes no pipeline |
Dast::ProfilesPipeline |
create-only join model; ci_pipeline never read in a list path |
Dast::PreScanVerification |
single has_one on DastProfileType; type exposes no pipeline field |
Progress
| # | Model | Association | FK column | MR | Status |
|---|---|---|---|---|---|
| — | Framework (PipelineRelationPreload + FF) |
— | — | !240414 (merged) | |
| 1 | MergeRequest::Metrics |
pipeline |
pipeline_id |
— | |
| 2 | Packages::BuildInfo |
pipeline |
pipeline_id |
— | Package#pipelines through-assoc) |
| 3 | Packages::PackageFileBuildInfo |
pipeline |
pipeline_id |
— | PackageFile#pipelines through-assoc) |
| 4 | Environments::Job |
pipeline |
ci_pipeline_id |
— | |
| 5 | MergeTrains::Car |
pipeline |
pipeline_id |
!240901 (merged) | |
| 6 | Security::PolicySchedulePipeline |
pipeline |
pipeline_id |
!240414 (merged) | |
| 7 | Security::ScheduledPipelineExecutionPolicyTestRun |
pipeline |
pipeline_id |
!240433 | |
| 8 | Vulnerabilities::Statistic |
pipeline |
latest_pipeline_id |
— | |
| 9 | Vulnerabilities::Finding |
initial_finding_pipeline |
initial_pipeline_id |
— | VulnerabilitiesResolver lookahead) |
| 10 | Vulnerabilities::Finding |
latest_finding_pipeline |
latest_pipeline_id |
— | VulnerabilitiesResolver lookahead) |
| 11 | Vulnerabilities::PartialScan |
pipeline |
pipeline_id |
— | |
| 12 | Vulnerabilities::Feedback |
pipeline |
pipeline_id |
!241111 (merged) | |
| 13 | Security::Scan |
pipeline |
pipeline_id |
— | |
| 14 | Sbom::Occurrence |
pipeline |
pipeline_id |
!241099 (merged) | |
| 15 | Sbom::OccurrenceRef |
pipeline |
pipeline_id |
— | |
| 16 | Dependencies::DependencyListExport |
pipeline |
pipeline_id |
— | |
| 17 | Dast::ProfilesPipeline |
ci_pipeline |
ci_pipeline_id |
— | |
| 18 | Dast::PreScanVerification |
ci_pipeline |
ci_pipeline_id |
— |
Remaining actionable: MergeRequest::Metrics, Packages::BuildInfo, Packages::PackageFileBuildInfo, Vulnerabilities::Finding (both associations).
References
- Bulk preloader framework: !236832 (merged), !240414 (merged)
- Single-record pruning: #599815
- FF rollout: #603166
- Origin issue: #593701 (closed)