Commit ab9cf52e authored by Ash McKenzie's avatar Ash McKenzie

Merge branch 'es_avoid_double_calls' into 'master'

Avoid calling Elasticsearch results twice

See merge request gitlab-org/gitlab-ee!13120
parents e5e5875a 0201b906
---
title: Avoid hitting Elasticsearch more than once on search
merge_request: 13120
author:
type: performance
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
module Gitlab module Gitlab
module Elastic module Elastic
class SearchResults class SearchResults
include Gitlab::Utils::StrongMemoize
attr_reader :current_user, :query, :public_and_internal_projects attr_reader :current_user, :query, :public_and_internal_projects
# Limit search results by passed project ids # Limit search results by passed project ids
...@@ -142,33 +144,41 @@ module Gitlab ...@@ -142,33 +144,41 @@ module Gitlab
end end
def projects def projects
Project.elastic_search(query, options: base_options) strong_memoize(:projects) do
Project.elastic_search(query, options: base_options)
end
end end
def issues def issues
Issue.elastic_search(query, options: base_options) strong_memoize(:issues) do
Issue.elastic_search(query, options: base_options)
end
end end
def milestones def milestones
# Must pass 'issues' and 'merge_requests' to check strong_memoize(:milestones) do
# if any of the features is available for projects in Elastic::ApplicationSearch#project_ids_query # Must pass 'issues' and 'merge_requests' to check
# Otherwise it will ignore project_ids and return milestones # if any of the features is available for projects in Elastic::ApplicationSearch#project_ids_query
# from projects with milestones disabled. # Otherwise it will ignore project_ids and return milestones
options = base_options # from projects with milestones disabled.
options[:features] = [:issues, :merge_requests] options = base_options
options[:features] = [:issues, :merge_requests]
Milestone.elastic_search(query, options: options)
Milestone.elastic_search(query, options: options)
end
end end
def merge_requests def merge_requests
options = base_options.merge(project_ids: non_guest_project_ids) strong_memoize(:merge_requests) do
MergeRequest.elastic_search(query, options: options) options = base_options.merge(project_ids: non_guest_project_ids)
MergeRequest.elastic_search(query, options: options)
end
end end
def blobs def blobs
if query.blank? return Kaminari.paginate_array([]) if query.blank?
Kaminari.paginate_array([])
else strong_memoize(:blobs) do
opt = { opt = {
additional_filter: repository_filter additional_filter: repository_filter
} }
...@@ -182,9 +192,9 @@ module Gitlab ...@@ -182,9 +192,9 @@ module Gitlab
end end
def wiki_blobs def wiki_blobs
if query.blank? return Kaminari.paginate_array([]) if query.blank?
Kaminari.painate_array([])
else strong_memoize(:wiki_blobs) do
opt = { opt = {
additional_filter: wiki_filter additional_filter: wiki_filter
} }
...@@ -197,10 +207,16 @@ module Gitlab ...@@ -197,10 +207,16 @@ module Gitlab
end end
end end
# We're only memoizing once because this object only ever gets used to show a single page of results
# during its lifetime. We _must_ memoize the page we want because `#commits_count` does not have any
# inkling of the current page we're on - if we were to memoize with dynamic parameters we would end up
# hitting ES twice for any page that's not page 1, and that's something we want to avoid.
#
# It is safe to memoize the page we get here because this method is _always_ called before `#commits_count`
def commits(page: 1, per_page: 20) def commits(page: 1, per_page: 20)
if query.blank? return Kaminari.paginate_array([]) if query.blank?
Kaminari.paginate_array([])
else strong_memoize(:commits) do
options = { options = {
additional_filter: repository_filter additional_filter: repository_filter
} }
......
...@@ -119,5 +119,21 @@ describe 'Global elastic search' do ...@@ -119,5 +119,21 @@ describe 'Global elastic search' do
expect(page).to have_selector('.commit-row-description') expect(page).to have_selector('.commit-row-description')
expect(page).to have_selector('.project-namespace') expect(page).to have_selector('.project-namespace')
end end
it 'shows proper page 2 results' do
visit dashboard_projects_path
fill_in "search", with: "add"
click_button "Go"
expected_message = "Add directory structure for tree_helper spec"
select_filter("Commits")
expect(page).not_to have_content(expected_message)
click_link 'Next'
expect(page).to have_content(expected_message)
end
end end
end end
# coding: utf-8 # coding: utf-8
require 'spec_helper' require 'spec_helper'
describe Gitlab::Elastic::SearchResults do describe Gitlab::Elastic::SearchResults, :elastic 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)
Gitlab::Elastic::Helper.create_empty_index
end end
after do after do
Gitlab::Elastic::Helper.delete_index
stub_ee_application_setting(elasticsearch_search: false, elasticsearch_indexing: false) stub_ee_application_setting(elasticsearch_search: false, elasticsearch_indexing: false)
end end
...@@ -17,6 +15,16 @@ describe Gitlab::Elastic::SearchResults do ...@@ -17,6 +15,16 @@ describe Gitlab::Elastic::SearchResults do
let(:project_2) { create(:project, :repository, :wiki_repo) } let(:project_2) { create(:project, :repository, :wiki_repo) }
let(:limit_project_ids) { [project_1.id] } let(:limit_project_ids) { [project_1.id] }
describe 'counts' do
it 'does not hit Elasticsearch twice for result and counts' do
expect(Repository).to receive(:find_commits_by_message_with_elastic).with('hello world', anything).once.and_call_original
results = described_class.new(user, 'hello world', limit_project_ids)
expect(results.objects('commits', 2)).to be_empty
expect(results.commits_count).to eq 0
end
end
describe 'parse_search_result' do describe 'parse_search_result' do
let(:blob) do let(:blob) do
{ {
......
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