From a2f3675dc7d8faaa33b2465df95a00c08b15eea1 Mon Sep 17 00:00:00 2001 From: Marc Shaw <mshaw@gitlab.com> Date: Fri, 3 Jul 2020 13:42:31 +0000 Subject: [PATCH] Memoize the source_branch_exists method for a MR Each time this will make a redis call, which is expensive if called enough times. This was an issue when serializing large diffs for a merge request. Clear the memoized source_branch_exists on MR update Clear memoized variables before proceeding with checks Issue: gitlab.com/gitlab-org/gitlab/-/issues/209786 Merge Request: gitlab.com/gitlab-org/gitlab/-/merge_requests/34516 --- app/models/merge_request.rb | 17 +++++- ..._batch-diffs-metadata-source-branch-ex.yml | 5 ++ spec/models/merge_request_spec.rb | 53 +++++++++++++++++++ .../conflicts/list_service_spec.rb | 1 + 4 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/209786-improve-performance-of-diffs_batch-diffs-metadata-source-branch-ex.yml diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index d4a56709edb5f..6a3fba93aea87 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -107,6 +107,7 @@ def merge_request_diff after_create :ensure_merge_request_diff after_update :clear_memoized_shas + after_update :clear_memoized_source_branch_exists after_update :reload_diff_if_branch_changed after_commit :ensure_metrics, on: [:create, :update], unless: :importing? after_commit :expire_etag_cache, unless: :importing? @@ -862,6 +863,10 @@ def clear_memoized_shas clear_memoization(:target_branch_head) end + def clear_memoized_source_branch_exists + clear_memoization(:source_branch_exists) + end + def reload_diff_if_branch_changed if (saved_change_to_source_branch? || saved_change_to_target_branch?) && (source_branch_head && target_branch_head) @@ -1109,9 +1114,17 @@ def target_project_namespace end def source_branch_exists? - return false unless self.source_project + if Feature.enabled?(:memoize_source_branch_merge_request, project) + strong_memoize(:source_branch_exists) do + next false unless self.source_project + + self.source_project.repository.branch_exists?(self.source_branch) + end + else + return false unless self.source_project - self.source_project.repository.branch_exists?(self.source_branch) + self.source_project.repository.branch_exists?(self.source_branch) + end end def target_branch_exists? diff --git a/changelogs/unreleased/209786-improve-performance-of-diffs_batch-diffs-metadata-source-branch-ex.yml b/changelogs/unreleased/209786-improve-performance-of-diffs_batch-diffs-metadata-source-branch-ex.yml new file mode 100644 index 0000000000000..215d64bbacbcd --- /dev/null +++ b/changelogs/unreleased/209786-improve-performance-of-diffs_batch-diffs-metadata-source-branch-ex.yml @@ -0,0 +1,5 @@ +--- +title: Further improve the performance for loading large diffs on a Merge request +merge_request: 34516 +author: +type: performance diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 332f5d62c62b7..4853fc2438e43 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1217,6 +1217,59 @@ def set_compare(merge_request) end end + describe "#source_branch_exists?" do + let(:merge_request) { subject } + let(:repository) { merge_request.source_project.repository } + + context 'when memoize_source_branch_merge_request feature is enabled' do + before do + stub_feature_flags(memoize_source_branch_merge_request: true) + end + + context 'when the source project is set' do + it 'memoizes the value and returns the result' do + expect(repository).to receive(:branch_exists?).once.with(merge_request.source_branch).and_return(true) + + 2.times { expect(merge_request.source_branch_exists?).to eq(true) } + end + end + + context 'when the source project is not set' do + before do + merge_request.source_project = nil + end + + it 'returns false' do + expect(merge_request.source_branch_exists?).to eq(false) + end + end + end + + context 'when memoize_source_branch_merge_request feature is disabled' do + before do + stub_feature_flags(memoize_source_branch_merge_request: false) + end + + context 'when the source project is set' do + it 'does not memoize the value and returns the result' do + expect(repository).to receive(:branch_exists?).twice.with(merge_request.source_branch).and_return(true) + + 2.times { expect(merge_request.source_branch_exists?).to eq(true) } + end + end + + context 'when the source project is not set' do + before do + merge_request.source_project = nil + end + + it 'returns false' do + expect(merge_request.source_branch_exists?).to eq(false) + end + end + end + end + describe '#default_merge_commit_message' do it 'includes merge information as the title' do request = build(:merge_request, source_branch: 'source', target_branch: 'target') diff --git a/spec/services/merge_requests/conflicts/list_service_spec.rb b/spec/services/merge_requests/conflicts/list_service_spec.rb index cd9eae2f2eed9..14133731e3795 100644 --- a/spec/services/merge_requests/conflicts/list_service_spec.rb +++ b/spec/services/merge_requests/conflicts/list_service_spec.rb @@ -30,6 +30,7 @@ def conflicts_service(merge_request) it 'returns a falsey value when one of the MR branches is missing' do merge_request = create_merge_request('conflict-resolvable') merge_request.project.repository.rm_branch(merge_request.author, 'conflict-resolvable') + merge_request.clear_memoized_source_branch_exists expect(conflicts_service(merge_request).can_be_resolved_in_ui?).to be_falsey end -- GitLab