Commit 20ffd9b4 authored by James Fargher's avatar James Fargher

Merge branch '215592-avoid-n-1-queries-on-project-search' into 'master'

Resolve "Avoid N+1 queries on project search"

See merge request gitlab-org/gitlab!30346
parents 896857eb d4a9126c
...@@ -5,6 +5,10 @@ class SearchController < ApplicationController ...@@ -5,6 +5,10 @@ class SearchController < ApplicationController
include SearchHelper include SearchHelper
include RendersCommits include RendersCommits
SCOPE_PRELOAD_METHOD = {
projects: :with_web_entity_associations
}.freeze
around_action :allow_gitaly_ref_name_caching around_action :allow_gitaly_ref_name_caching
skip_before_action :authenticate_user! skip_before_action :authenticate_user!
...@@ -28,7 +32,7 @@ class SearchController < ApplicationController ...@@ -28,7 +32,7 @@ class SearchController < ApplicationController
@scope = search_service.scope @scope = search_service.scope
@show_snippets = search_service.show_snippets? @show_snippets = search_service.show_snippets?
@search_results = search_service.search_results @search_results = search_service.search_results
@search_objects = search_service.search_objects @search_objects = search_service.search_objects(preload_method)
render_commits if @scope == 'commits' render_commits if @scope == 'commits'
eager_load_user_status if @scope == 'users' eager_load_user_status if @scope == 'users'
...@@ -64,6 +68,10 @@ class SearchController < ApplicationController ...@@ -64,6 +68,10 @@ class SearchController < ApplicationController
private private
def preload_method
SCOPE_PRELOAD_METHOD[@scope.to_sym]
end
def search_term_valid? def search_term_valid?
unless search_service.valid_query_length? unless search_service.valid_query_length?
flash[:alert] = t('errors.messages.search_chars_too_long', count: SearchService::SEARCH_CHAR_LIMIT) flash[:alert] = t('errors.messages.search_chars_too_long', count: SearchService::SEARCH_CHAR_LIMIT)
......
...@@ -509,6 +509,7 @@ class Project < ApplicationRecord ...@@ -509,6 +509,7 @@ class Project < ApplicationRecord
left_outer_joins(:pages_metadatum) left_outer_joins(:pages_metadatum)
.where(project_pages_metadata: { project_id: nil }) .where(project_pages_metadata: { project_id: nil })
end end
scope :with_api_entity_associations, -> { scope :with_api_entity_associations, -> {
preload(:project_feature, :route, :tags, preload(:project_feature, :route, :tags,
group: :ip_restrictions, namespace: [:route, :owner]) group: :ip_restrictions, namespace: [:route, :owner])
...@@ -528,6 +529,10 @@ class Project < ApplicationRecord ...@@ -528,6 +529,10 @@ class Project < ApplicationRecord
# Used by Projects::CleanupService to hold a map of rewritten object IDs # Used by Projects::CleanupService to hold a map of rewritten object IDs
mount_uploader :bfg_object_map, AttachmentUploader mount_uploader :bfg_object_map, AttachmentUploader
def self.with_web_entity_associations
preload(:project_feature, :route, :creator, :group, namespace: [:route, :owner])
end
def self.eager_load_namespace_and_owner def self.eager_load_namespace_and_owner
includes(namespace: :owner) includes(namespace: :owner)
end end
......
...@@ -191,6 +191,8 @@ module EE ...@@ -191,6 +191,8 @@ module EE
end end
class_methods do class_methods do
extend ::Gitlab::Utils::Override
def search_by_visibility(level) def search_by_visibility(level)
where(visibility_level: ::Gitlab::VisibilityLevel.string_options[level]) where(visibility_level: ::Gitlab::VisibilityLevel.string_options[level])
end end
...@@ -205,6 +207,11 @@ module EE ...@@ -205,6 +207,11 @@ module EE
# see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24063#note_282435524 for details # see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24063#note_282435524 for details
joins(:service_desk_setting).find_by('service_desk_settings.project_key' => key) joins(:service_desk_setting).find_by('service_desk_settings.project_key' => key)
end end
override :with_web_entity_associations
def with_web_entity_associations
super.preload(:compliance_framework_setting)
end
end end
def can_store_security_reports? def can_store_security_reports?
......
---
title: Fix N+1 queries for Elastic Web Search projects scope
merge_request: 30346
author:
type: performance
...@@ -29,7 +29,7 @@ module Gitlab ...@@ -29,7 +29,7 @@ module Gitlab
case scope case scope
when 'projects' when 'projects'
eager_load(projects, page, per_page, preload_method, [:route, :namespace, :compliance_framework_setting]) eager_load(projects, page, per_page, preload_method, [:route, :namespace])
when 'issues' when 'issues'
eager_load(issues, page, per_page, preload_method, project: [:route, :namespace], labels: [], timelogs: [], assignees: []) eager_load(issues, page, per_page, preload_method, project: [:route, :namespace], labels: [], timelogs: [], assignees: [])
when 'merge_requests' when 'merge_requests'
......
...@@ -5,7 +5,7 @@ require 'spec_helper' ...@@ -5,7 +5,7 @@ require 'spec_helper'
describe SearchController, type: :request do describe SearchController, type: :request do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) } let_it_be(:group) { create(:group) }
let(:project) { create(:project, :public, :repository, :wiki_repo, name: 'awesome project', group: group) } let_it_be(:project) { create(:project, :public, :repository, :wiki_repo, name: 'awesome project', group: group) }
before_all do before_all do
login_as(user) login_as(user)
...@@ -20,6 +20,7 @@ describe SearchController, type: :request do ...@@ -20,6 +20,7 @@ describe SearchController, type: :request do
before do before do
stub_ee_application_setting(elasticsearch_search: true, elasticsearch_indexing: true) stub_ee_application_setting(elasticsearch_search: true, elasticsearch_indexing: true)
end end
context 'for merge_request scope' do context 'for merge_request scope' do
before do before do
create(:merge_request, target_branch: 'feature_1', source_project: project) create(:merge_request, target_branch: 'feature_1', source_project: project)
...@@ -28,6 +29,7 @@ describe SearchController, type: :request do ...@@ -28,6 +29,7 @@ describe SearchController, type: :request do
create(:merge_request, target_branch: 'feature_4', source_project: project) create(:merge_request, target_branch: 'feature_4', source_project: project)
ensure_elasticsearch_index! ensure_elasticsearch_index!
end end
it 'avoids N+1 queries' do it 'avoids N+1 queries' do
control = ActiveRecord::QueryRecorder.new(skip_cached: false) { send_search_request(scope: 'merge_requests', search: '*') } control = ActiveRecord::QueryRecorder.new(skip_cached: false) { send_search_request(scope: 'merge_requests', search: '*') }
...@@ -40,7 +42,30 @@ describe SearchController, type: :request do ...@@ -40,7 +42,30 @@ describe SearchController, type: :request do
# some N+1 queries still exist # some N+1 queries still exist
expect { send_search_request(scope: 'merge_requests', search: '*') } expect { send_search_request(scope: 'merge_requests', search: '*') }
.not_to exceed_all_query_limit(control.count + 2) .not_to exceed_all_query_limit(control)
end
end
context 'for project scope' do
before do
create(:project, :public)
ensure_elasticsearch_index!
end
it 'avoids N+1 queries' do
control = ActiveRecord::QueryRecorder.new(skip_cached: false) { send_search_request(scope: 'project', search: '*') }
create_list(:project, 3, :public)
ensure_elasticsearch_index!
# some N+1 queries still exist
# each project requires 3 extra queries
# - one count for forks
# - one count for open MRs
# - one count for open Issues
expect { send_search_request(scope: 'project', search: '*') }
.not_to exceed_all_query_limit(control).with_threshold(9)
end end
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