Commit cec7261f authored by Furkan Ayhan's avatar Furkan Ayhan

Eliminate N+1 queries for pipeline GraphQL endpoint

The 'metadata' relation is the most common N+1 query.
The 'downstream_pipeline' relation is for bridge jobs, not so common.

In this commit, the structure of preloading is also changed.

Changelog: performance
parent 20ced226
......@@ -52,15 +52,22 @@ module Types
# rubocop: disable CodeReuse/ActiveRecord
def jobs_for_pipeline(pipeline, stage_ids, include_needs)
builds_results = pipeline.latest_builds.where(stage_id: stage_ids).preload(:job_artifacts, :project)
bridges_results = pipeline.bridges.where(stage_id: stage_ids).preload(:project)
builds_results = builds_results.preload(:needs) if include_needs
bridges_results = bridges_results.preload(:needs) if include_needs
commit_status_results = pipeline.latest_statuses.where(stage_id: stage_ids)
jobs = pipeline.statuses.latest.where(stage_id: stage_ids)
results = builds_results | bridges_results | commit_status_results
common_relations = [:project]
common_relations << :needs if include_needs
results.group_by(&:stage_id)
preloaders = {
::Ci::Build => [:metadata, :job_artifacts],
::Ci::Bridge => [:metadata, :downstream_pipeline],
::GenericCommitStatus => []
}
preloaders.each do |klass, relations|
ActiveRecord::Associations::Preloader.new.preload(jobs.select { |job| job.is_a?(klass) }, relations + common_relations)
end
jobs.group_by(&:stage_id)
end
# rubocop: enable CodeReuse/ActiveRecord
end
......
......@@ -66,6 +66,7 @@ module Ci
has_many :processables, class_name: 'Ci::Processable', foreign_key: :commit_id, inverse_of: :pipeline
has_many :bridges, class_name: 'Ci::Bridge', foreign_key: :commit_id, inverse_of: :pipeline
has_many :builds, foreign_key: :commit_id, inverse_of: :pipeline
has_many :generic_commit_statuses, foreign_key: :commit_id, inverse_of: :pipeline, class_name: 'GenericCommitStatus'
has_many :job_artifacts, through: :builds
has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id # rubocop:disable Cop/ActiveRecordDependent
has_many :variables, class_name: 'Ci::PipelineVariable'
......
......@@ -223,6 +223,7 @@ ci_pipelines:
- builds
- bridges
- processables
- generic_commit_statuses
- trigger_requests
- variables
- auto_canceled_by
......
......@@ -311,6 +311,10 @@ RSpec.describe 'getting pipeline information nested in a project' do
end
it 'does not generate N+1 queries', :request_store, :use_sql_query_cache do
# create extra statuses
create(:generic_commit_status, :pending, name: 'generic-build-a', pipeline: pipeline, stage_idx: 0, stage: 'build')
create(:ci_bridge, :failed, name: 'deploy-a', pipeline: pipeline, stage_idx: 2, stage: 'deploy')
# warm up
post_graphql(query, current_user: current_user)
......@@ -318,9 +322,11 @@ RSpec.describe 'getting pipeline information nested in a project' do
post_graphql(query, current_user: current_user)
end
create(:ci_build, name: 'test-a', pipeline: pipeline, stage_idx: 1, stage: 'test')
create(:ci_build, name: 'test-b', pipeline: pipeline, stage_idx: 1, stage: 'test')
create(:ci_build, name: 'deploy-a', pipeline: pipeline, stage_idx: 2, stage: 'deploy')
create(:generic_commit_status, :pending, name: 'generic-build-b', pipeline: pipeline, stage_idx: 0, stage: 'build')
create(:ci_build, :failed, name: 'test-a', pipeline: pipeline, stage_idx: 1, stage: 'test')
create(:ci_build, :running, name: 'test-b', pipeline: pipeline, stage_idx: 1, stage: 'test')
create(:ci_build, :pending, name: 'deploy-b', pipeline: pipeline, stage_idx: 2, stage: 'deploy')
create(:ci_bridge, :failed, name: 'deploy-c', pipeline: pipeline, stage_idx: 2, stage: 'deploy')
expect do
post_graphql(query, current_user: current_user)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment