From 42b0158b1ce60d69f40a877f1a14ad6437794b71 Mon Sep 17 00:00:00 2001 From: Dmitry Gruzd <dgruzd@gitlab.com> Date: Sat, 18 Jul 2020 00:43:48 +0000 Subject: [PATCH] Resolve "Search API merge_requests scope preload project features" --- app/models/merge_request.rb | 19 +++++++------ ee/app/models/merge_train.rb | 4 +-- .../unreleased/219309-fix-mr-n-plus-1.yml | 5 ++++ spec/services/search_service_spec.rb | 27 +++++++++++++++++++ 4 files changed, 43 insertions(+), 12 deletions(-) create mode 100644 ee/changelogs/unreleased/219309-fix-mr-n-plus-1.yml diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index b788577178180..cf7c40b97d8f8 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -251,17 +251,12 @@ def public_merge_status end scope :join_project, -> { joins(:target_project) } scope :references_project, -> { references(:target_project) } - - PROJECT_ROUTE_AND_NAMESPACE_ROUTE = [ - target_project: [:route, { namespace: :route }], - source_project: [:route, { namespace: :route }] - ].freeze - scope :with_api_entity_associations, -> { - preload(:assignees, :author, :unresolved_notes, :labels, :milestone, - :timelogs, :latest_merge_request_diff, - *PROJECT_ROUTE_AND_NAMESPACE_ROUTE, - metrics: [:latest_closed_by, :merged_by]) + preload_routables + .preload(:assignees, :author, :unresolved_notes, :labels, :milestone, + :timelogs, :latest_merge_request_diff, + target_project: :project_feature, + metrics: [:latest_closed_by, :merged_by]) } scope :by_target_branch_wildcard, ->(wildcard_branch_name) do @@ -269,6 +264,10 @@ def public_merge_status end scope :by_target_branch, ->(branch_name) { where(target_branch: branch_name) } scope :preload_source_project, -> { preload(:source_project) } + scope :preload_routables, -> do + preload(target_project: [:route, { namespace: :route }], + source_project: [:route, { namespace: :route }]) + end scope :with_auto_merge_enabled, -> do with_state(:opened).where(auto_merge_enabled: true) diff --git a/ee/app/models/merge_train.rb b/ee/app/models/merge_train.rb index 8372742bda742..68b8eb139ebe2 100644 --- a/ee/app/models/merge_train.rb +++ b/ee/app/models/merge_train.rb @@ -71,8 +71,8 @@ class MergeTrain < ApplicationRecord scope :by_id, -> (sort = :asc) { order(id: sort) } scope :preload_api_entities, -> do - preload(:user, merge_request: MergeRequest::PROJECT_ROUTE_AND_NAMESPACE_ROUTE, - pipeline: Ci::Pipeline::PROJECT_ROUTE_AND_NAMESPACE_ROUTE) + preload(:user, :merge_request, pipeline: Ci::Pipeline::PROJECT_ROUTE_AND_NAMESPACE_ROUTE) + .merge(MergeRequest.preload_routables) end class << self diff --git a/ee/changelogs/unreleased/219309-fix-mr-n-plus-1.yml b/ee/changelogs/unreleased/219309-fix-mr-n-plus-1.yml new file mode 100644 index 0000000000000..4ecdd46cca905 --- /dev/null +++ b/ee/changelogs/unreleased/219309-fix-mr-n-plus-1.yml @@ -0,0 +1,5 @@ +--- +title: Avoid N+1 of merge requests associations in Search +merge_request: 36712 +author: +type: performance diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb index 52aef73ac77ed..f6bb7acee5728 100644 --- a/spec/services/search_service_spec.rb +++ b/spec/services/search_service_spec.rb @@ -374,6 +374,19 @@ subject(:result) { search_service.search_objects } + shared_examples "redaction limits N+1 queries" do |limit:| + it 'does not exceed the query limit' do + # issuing the query to remove the data loading call + unredacted_results.to_a + + # only the calls from the redaction are left + query = ActiveRecord::QueryRecorder.new { result } + + # these are the project authorization calls, which are not preloaded + expect(query.count).to be <= limit + end + end + def found_blob(project) Gitlab::Search::FoundBlob.new(project: project) end @@ -427,6 +440,12 @@ def kaminari_array(*objects) it 'redacts the inaccessible merge request' do expect(result).to contain_exactly(readable) end + + context 'with :with_api_entity_associations' do + let(:unredacted_results) { ar_relation(MergeRequest.with_api_entity_associations, readable, unreadable) } + + it_behaves_like "redaction limits N+1 queries", limit: 7 + end end context 'project repository blobs' do @@ -460,6 +479,10 @@ def kaminari_array(*objects) it 'redacts the inaccessible snippet' do expect(result).to contain_exactly(readable) end + + context 'with :with_api_entity_associations' do + it_behaves_like "redaction limits N+1 queries", limit: 12 + end end context 'personal snippets' do @@ -471,6 +494,10 @@ def kaminari_array(*objects) it 'redacts the inaccessible snippet' do expect(result).to contain_exactly(readable) end + + context 'with :with_api_entity_associations' do + it_behaves_like "redaction limits N+1 queries", limit: 3 + end end context 'commits' do -- GitLab