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 ...@@ -52,15 +52,22 @@ module Types
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def jobs_for_pipeline(pipeline, stage_ids, include_needs) def jobs_for_pipeline(pipeline, stage_ids, include_needs)
builds_results = pipeline.latest_builds.where(stage_id: stage_ids).preload(:job_artifacts, :project) jobs = pipeline.statuses.latest.where(stage_id: stage_ids)
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)
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 end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
end end
......
...@@ -66,6 +66,7 @@ module Ci ...@@ -66,6 +66,7 @@ module Ci
has_many :processables, class_name: 'Ci::Processable', foreign_key: :commit_id, inverse_of: :pipeline 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 :bridges, class_name: 'Ci::Bridge', foreign_key: :commit_id, inverse_of: :pipeline
has_many :builds, 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 :job_artifacts, through: :builds
has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id # rubocop:disable Cop/ActiveRecordDependent has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id # rubocop:disable Cop/ActiveRecordDependent
has_many :variables, class_name: 'Ci::PipelineVariable' has_many :variables, class_name: 'Ci::PipelineVariable'
......
...@@ -223,6 +223,7 @@ ci_pipelines: ...@@ -223,6 +223,7 @@ ci_pipelines:
- builds - builds
- bridges - bridges
- processables - processables
- generic_commit_statuses
- trigger_requests - trigger_requests
- variables - variables
- auto_canceled_by - auto_canceled_by
......
...@@ -311,6 +311,10 @@ RSpec.describe 'getting pipeline information nested in a project' do ...@@ -311,6 +311,10 @@ RSpec.describe 'getting pipeline information nested in a project' do
end end
it 'does not generate N+1 queries', :request_store, :use_sql_query_cache do 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 # warm up
post_graphql(query, current_user: current_user) post_graphql(query, current_user: current_user)
...@@ -318,9 +322,11 @@ RSpec.describe 'getting pipeline information nested in a project' do ...@@ -318,9 +322,11 @@ RSpec.describe 'getting pipeline information nested in a project' do
post_graphql(query, current_user: current_user) post_graphql(query, current_user: current_user)
end end
create(:ci_build, name: 'test-a', pipeline: pipeline, stage_idx: 1, stage: 'test') create(:generic_commit_status, :pending, name: 'generic-build-b', pipeline: pipeline, stage_idx: 0, stage: 'build')
create(:ci_build, name: 'test-b', pipeline: pipeline, stage_idx: 1, stage: 'test') create(:ci_build, :failed, name: 'test-a', pipeline: pipeline, stage_idx: 1, stage: 'test')
create(:ci_build, name: 'deploy-a', pipeline: pipeline, stage_idx: 2, stage: 'deploy') 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 expect do
post_graphql(query, current_user: current_user) 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