Commit 6c597b13 authored by Mayra Cabrera's avatar Mayra Cabrera

Merge branch 'fix-search-issues-n-plus-1' into 'master'

[RUN AS-IF-FOSS] Fix N+1 search scope=issues

See merge request gitlab-org/gitlab!36941
parents a8d7c5d4 a0a49f2f
...@@ -6,7 +6,8 @@ class SearchController < ApplicationController ...@@ -6,7 +6,8 @@ class SearchController < ApplicationController
include RendersCommits include RendersCommits
SCOPE_PRELOAD_METHOD = { SCOPE_PRELOAD_METHOD = {
projects: :with_web_entity_associations projects: :with_web_entity_associations,
issues: :with_web_entity_associations
}.freeze }.freeze
around_action :allow_gitaly_ref_name_caching around_action :allow_gitaly_ref_name_caching
......
...@@ -87,6 +87,8 @@ class Issue < ApplicationRecord ...@@ -87,6 +87,8 @@ class Issue < ApplicationRecord
scope :order_created_at_desc, -> { reorder(created_at: :desc) } scope :order_created_at_desc, -> { reorder(created_at: :desc) }
scope :preload_associated_models, -> { preload(:assignees, :labels, project: :namespace) } scope :preload_associated_models, -> { preload(:assignees, :labels, project: :namespace) }
scope :with_web_entity_associations, -> { preload(:author, :project) }
scope :with_api_entity_associations, -> { preload(:timelogs, :assignees, :author, :notes, :labels, project: [:route, { namespace: :route }] ) }
scope :with_label_attributes, ->(label_attributes) { joins(:labels).where(labels: label_attributes) } scope :with_label_attributes, ->(label_attributes) { joins(:labels).where(labels: label_attributes) }
scope :with_alert_management_alerts, -> { joins(:alert_management_alert) } scope :with_alert_management_alerts, -> { joins(:alert_management_alert) }
scope :with_prometheus_alert_events, -> { joins(:issues_prometheus_alert_events) } scope :with_prometheus_alert_events, -> { joins(:issues_prometheus_alert_events) }
......
---
title: Avoid N+1 of issue associations in Search
merge_request: 36941
author:
type: performance
...@@ -15,58 +15,60 @@ RSpec.describe SearchController, type: :request do ...@@ -15,58 +15,60 @@ RSpec.describe SearchController, type: :request do
get search_path, params: params get search_path, params: params
end end
shared_examples 'an efficient database result' do
it 'avoids N+1 database queries' do
create(object, *creation_traits, creation_args)
ensure_elasticsearch_index!
control = ActiveRecord::QueryRecorder.new(skip_cached: false) { send_search_request(params) }
create_list(object, 3, *creation_traits, creation_args)
ensure_elasticsearch_index!
expect { send_search_request(params) }.not_to exceed_all_query_limit(control).with_threshold(threshold)
end
end
describe 'GET /search' do describe 'GET /search' do
context 'when elasticsearch is enabled', :elastic, :sidekiq_inline do context 'when elasticsearch is enabled', :elastic, :sidekiq_inline 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
let(:creation_traits) { [] }
context 'for issues scope' do
let(:object) { :issue }
let(:creation_args) { { project: project } }
let(:params) { { search: '*', scope: 'issues' } }
let(:threshold) { 0 }
it_behaves_like 'an efficient database result'
end
context 'for merge_request scope' do context 'for merge_request scope' do
before do let(:creation_traits) { [:unique_branches] }
create(:merge_request, target_branch: 'feature_1', source_project: project) let(:object) { :merge_request }
create(:merge_request, target_branch: 'feature_2', source_project: project) let(:creation_args) { { source_project: project } }
create(:merge_request, target_branch: 'feature_3', source_project: project) let(:params) { { search: '*', scope: 'merge_requests' } }
create(:merge_request, target_branch: 'feature_4', source_project: project) let(:threshold) { 0 }
ensure_elasticsearch_index!
end it_behaves_like 'an efficient database result'
it 'avoids N+1 queries' do
control = ActiveRecord::QueryRecorder.new(skip_cached: false) { send_search_request(scope: 'merge_requests', search: '*') }
create(:merge_request, target_branch: 'feature_5', source_project: project)
create(:merge_request, target_branch: 'feature_6', source_project: project)
create(:merge_request, target_branch: 'feature_7', source_project: project)
create(:merge_request, target_branch: 'feature_8', source_project: project)
ensure_elasticsearch_index!
# some N+1 queries still exist
expect { send_search_request(scope: 'merge_requests', search: '*') }
.not_to exceed_all_query_limit(control)
end
end end
context 'for project scope' do context 'for project scope' do
before do let(:creation_traits) { [:public] }
create(:project, :public) let(:object) { :project }
ensure_elasticsearch_index! let(:creation_args) { {} }
end let(:params) { { search: '*', scope: 'projects' } }
# some N+1 queries still exist
it 'avoids N+1 queries' do # each project requires 3 extra queries
control = ActiveRecord::QueryRecorder.new(skip_cached: false) { send_search_request(scope: 'project', search: '*') } # - one count for forks
# - one count for open MRs
create_list(:project, 3, :public) # - one count for open Issues
let(:threshold) { 9 }
ensure_elasticsearch_index!
it_behaves_like 'an efficient database result'
# 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 end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe SearchController, type: :request do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :public, :repository, :wiki_repo, name: 'awesome project', group: group) }
before_all do
login_as(user)
end
def send_search_request(params)
get search_path, params: params
end
shared_examples 'an efficient database result' do
it 'avoids N+1 database queries' do
create(object, *creation_traits, creation_args)
control = ActiveRecord::QueryRecorder.new(skip_cached: false) { send_search_request(params) }
create_list(object, 3, *creation_traits, creation_args)
expect { send_search_request(params) }.not_to exceed_all_query_limit(control).with_threshold(threshold)
end
end
describe 'GET /search' do
let(:creation_traits) { [] }
context 'for issues scope' do
let(:object) { :issue }
let(:creation_args) { { project: project } }
let(:params) { { search: '*', scope: 'issues' } }
let(:threshold) { 0 }
it_behaves_like 'an efficient database result'
end
context 'for merge_request scope' do
let(:creation_traits) { [:unique_branches] }
let(:object) { :merge_request }
let(:creation_args) { { source_project: project } }
let(:params) { { search: '*', scope: 'merge_requests' } }
let(:threshold) { 0 }
it_behaves_like 'an efficient database result'
end
context 'for project scope' do
let(:creation_traits) { [:public] }
let(:object) { :project }
let(:creation_args) { {} }
let(:params) { { search: '*', scope: 'projects' } }
# 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
let(:threshold) { 9 }
it_behaves_like 'an efficient database result'
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