Commit 650739fa authored by Mayra Cabrera's avatar Mayra Cabrera

Merge branch '330707-pipeline-show-jobs-nplusone' into 'master'

Eliminate N+1 queries for pipeline show jobs

See merge request gitlab-org/gitlab!68743
parents 106e63e8 13a9bb47
...@@ -12,11 +12,11 @@ module Ci ...@@ -12,11 +12,11 @@ module Ci
erased_by.name if erased_by_user? erased_by.name if erased_by_user?
end end
def status_title def status_title(status = detailed_status)
if auto_canceled? if auto_canceled?
"Job is redundant and is auto-canceled by Pipeline ##{auto_canceled_by_id}" "Job is redundant and is auto-canceled by Pipeline ##{auto_canceled_by_id}"
else else
tooltip_for_badge tooltip_for_badge(status)
end end
end end
...@@ -41,8 +41,8 @@ module Ci ...@@ -41,8 +41,8 @@ module Ci
private private
def tooltip_for_badge def tooltip_for_badge(status)
detailed_status.badge_tooltip.capitalize status.badge_tooltip.capitalize
end end
def detailed_status def detailed_status
......
...@@ -15,18 +15,23 @@ module Ci ...@@ -15,18 +15,23 @@ module Ci
private private
def preload_statuses(statuses) def preload_statuses(statuses)
loaded_statuses = statuses.load common_relations = [:pipeline]
statuses.tap do |statuses|
preloaders = {
::Ci::Build => [:metadata, :tags, :job_artifacts_archive],
::Ci::Bridge => [:metadata, :downstream_pipeline],
::GenericCommitStatus => []
}
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
ActiveRecord::Associations::Preloader.new.preload(preloadable_statuses(loaded_statuses), %w[pipeline tags job_artifacts_archive metadata]) preloaders.each do |klass, relations|
# rubocop: enable CodeReuse/ActiveRecord ActiveRecord::Associations::Preloader
end .new
.preload(statuses.select { |job| job.is_a?(klass) }, relations + common_relations)
end end
# rubocop: enable CodeReuse/ActiveRecord
def preloadable_statuses(statuses) statuses
statuses.reject do |status|
status.instance_of?(::GenericCommitStatus) || status.instance_of?(::Ci::Bridge)
end
end end
end end
end end
...@@ -7,10 +7,14 @@ ...@@ -7,10 +7,14 @@
- pipeline_link = local_assigns.fetch(:pipeline_link, false) - pipeline_link = local_assigns.fetch(:pipeline_link, false)
- stage = local_assigns.fetch(:stage, false) - stage = local_assigns.fetch(:stage, false)
- allow_retry = local_assigns.fetch(:allow_retry, false) - allow_retry = local_assigns.fetch(:allow_retry, false)
-# This prevents initializing another Ci::Status object where 'status' is used
- status = job.detailed_status(current_user)
%tr.build.commit{ class: ('retried' if retried) } %tr.build.commit{ class: ('retried' if retried) }
%td.status %td.status
= render "ci/status/badge", status: job.detailed_status(current_user), title: job.status_title -# Sending 'status' prevents calling the user relation inside the presenter, generating N+1,
-# see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68743
= render "ci/status/badge", status: status, title: job.status_title(status)
%td %td
- if can?(current_user, :read_build, job) - if can?(current_user, :read_build, job)
......
...@@ -311,23 +311,42 @@ RSpec.describe Projects::PipelinesController do ...@@ -311,23 +311,42 @@ RSpec.describe Projects::PipelinesController do
let_it_be(:pipeline) { create(:ci_pipeline, project: project) } let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
def create_build_with_artifacts(stage, stage_idx, name) def create_build_with_artifacts(stage, stage_idx, name, status)
create(:ci_build, :artifacts, :tags, pipeline: pipeline, stage: stage, stage_idx: stage_idx, name: name) create(:ci_build, :artifacts, :tags, status, user: user, pipeline: pipeline, stage: stage, stage_idx: stage_idx, name: name)
end
def create_bridge(stage, stage_idx, name, status)
create(:ci_bridge, status, pipeline: pipeline, stage: stage, stage_idx: stage_idx, name: name)
end end
before do before do
create_build_with_artifacts('build', 0, 'job1') create_build_with_artifacts('build', 0, 'job1', :failed)
create_build_with_artifacts('build', 0, 'job2') create_build_with_artifacts('build', 0, 'job2', :running)
create_build_with_artifacts('build', 0, 'job3', :pending)
create_bridge('deploy', 1, 'deploy-a', :failed)
create_bridge('deploy', 1, 'deploy-b', :created)
end end
it 'avoids N+1 database queries', :request_store do it 'avoids N+1 database queries', :request_store, :use_sql_query_cache do
control_count = ActiveRecord::QueryRecorder.new { get_pipeline_html }.count # warm up
get_pipeline_html
expect(response).to have_gitlab_http_status(:ok)
control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
get_pipeline_html
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end
create_build_with_artifacts('build', 0, 'job3') create_build_with_artifacts('build', 0, 'job4', :failed)
create_build_with_artifacts('build', 0, 'job5', :running)
create_build_with_artifacts('build', 0, 'job6', :pending)
create_bridge('deploy', 1, 'deploy-c', :failed)
create_bridge('deploy', 1, 'deploy-d', :created)
expect { get_pipeline_html }.not_to exceed_query_limit(control_count) expect do
get_pipeline_html
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end.not_to exceed_all_query_limit(control)
end end
end end
......
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