Commit b64f01ea authored by Igor's avatar Igor Committed by Heinrich Lee Yu

Load commits in batches for MR RefreshService

This commit will allow us to avoid the case when
database is loaded with a query which consists
~30k+ shas as an argument
parent 41f8d551
...@@ -420,15 +420,6 @@ class MergeRequest < ApplicationRecord ...@@ -420,15 +420,6 @@ class MergeRequest < ApplicationRecord
limit ? shas.take(limit) : shas limit ? shas.take(limit) : shas
end end
# Returns true if there are commits that match at least one commit SHA.
def includes_any_commits?(shas)
if persisted?
merge_request_diff.commits_by_shas(shas).exists?
else
(commit_shas & shas).present?
end
end
def supports_suggestion? def supports_suggestion?
true true
end end
......
...@@ -10,6 +10,7 @@ class MergeRequestDiff < ApplicationRecord ...@@ -10,6 +10,7 @@ class MergeRequestDiff < ApplicationRecord
# Don't display more than 100 commits at once # Don't display more than 100 commits at once
COMMITS_SAFE_SIZE = 100 COMMITS_SAFE_SIZE = 100
BATCH_SIZE = 1000
# Applies to closed or merged MRs when determining whether to migrate their # Applies to closed or merged MRs when determining whether to migrate their
# diffs to external storage # diffs to external storage
...@@ -254,10 +255,14 @@ class MergeRequestDiff < ApplicationRecord ...@@ -254,10 +255,14 @@ class MergeRequestDiff < ApplicationRecord
merge_request_diff_commits.limit(limit).pluck(:sha) merge_request_diff_commits.limit(limit).pluck(:sha)
end end
def commits_by_shas(shas) def includes_any_commits?(shas)
return MergeRequestDiffCommit.none unless shas.present? return false if shas.blank?
merge_request_diff_commits.where(sha: shas) # when the number of shas is huge (1000+) we don't want
# to pass them all as an SQL param, let's pass them in batches
shas.each_slice(BATCH_SIZE).any? do |batched_shas|
merge_request_diff_commits.where(sha: batched_shas).exists?
end
end end
def diff_refs=(new_diff_refs) def diff_refs=(new_diff_refs)
......
...@@ -106,7 +106,7 @@ module MergeRequests ...@@ -106,7 +106,7 @@ module MergeRequests
filter_merge_requests(merge_requests).each do |merge_request| filter_merge_requests(merge_requests).each do |merge_request|
if branch_and_project_match?(merge_request) || @push.force_push? if branch_and_project_match?(merge_request) || @push.force_push?
merge_request.reload_diff(current_user) merge_request.reload_diff(current_user)
elsif merge_request.includes_any_commits?(push_commit_ids) elsif merge_request.merge_request_diff.includes_any_commits?(push_commit_ids)
merge_request.reload_diff(current_user) merge_request.reload_diff(current_user)
end end
......
...@@ -426,24 +426,38 @@ describe MergeRequestDiff do ...@@ -426,24 +426,38 @@ describe MergeRequestDiff do
end end
end end
describe '#commits_by_shas' do describe '#includes_any_commits?' do
let(:commit_shas) { diff_with_commits.commit_shas } let(:non_existent_shas) do
Array.new(30) { Digest::SHA1.hexdigest(SecureRandom.hex) }
it 'returns empty if no SHAs were provided' do
expect(diff_with_commits.commits_by_shas([])).to be_empty
end end
it 'returns one SHA' do subject { diff_with_commits }
commits = diff_with_commits.commits_by_shas([commit_shas.first, Gitlab::Git::BLANK_SHA])
context 'processes the passed shas in batches' do
context 'number of existing commits is greater than batch size' do
it 'performs a separate request for each batch' do
stub_const('MergeRequestDiff::BATCH_SIZE', 5)
commit_shas = subject.commit_shas
query_count = ActiveRecord::QueryRecorder.new do
subject.includes_any_commits?(non_existent_shas + commit_shas)
end.count
expect(query_count).to eq(7)
end
end
end
expect(commits.count).to eq(1) it 'returns false if passed commits do not exist' do
expect(subject.includes_any_commits?([])).to eq(false)
expect(subject.includes_any_commits?([Gitlab::Git::BLANK_SHA])).to eq(false)
end end
it 'returns all matching SHAs' do it 'returns true if passed commits exists' do
commits = diff_with_commits.commits_by_shas(commit_shas) args_with_existing_commits = non_existent_shas << subject.head_commit_sha
expect(commits.count).to eq(commit_shas.count) expect(subject.includes_any_commits?(args_with_existing_commits)).to eq(true)
expect(commits.map(&:sha)).to match_array(commit_shas)
end end
end end
......
...@@ -3134,36 +3134,6 @@ describe MergeRequest do ...@@ -3134,36 +3134,6 @@ describe MergeRequest do
end end
end end
describe '#includes_any_commits?' do
it 'returns false' do
expect(subject.includes_any_commits?([])).to be_falsey
end
it 'returns false' do
expect(subject.includes_any_commits?([Gitlab::Git::BLANK_SHA])).to be_falsey
end
it 'returns true' do
expect(subject.includes_any_commits?([subject.merge_request_diff.head_commit_sha])).to be_truthy
end
it 'returns true even when there is a non-existent comit' do
expect(subject.includes_any_commits?([Gitlab::Git::BLANK_SHA, subject.merge_request_diff.head_commit_sha])).to be_truthy
end
context 'unpersisted merge request' do
let(:new_mr) { build(:merge_request) }
it 'returns false' do
expect(new_mr.includes_any_commits?([Gitlab::Git::BLANK_SHA])).to be_falsey
end
it 'returns true' do
expect(new_mr.includes_any_commits?([subject.merge_request_diff.head_commit_sha])).to be_truthy
end
end
end
describe '#can_allow_collaboration?' do describe '#can_allow_collaboration?' do
let(:target_project) { create(:project, :public) } let(:target_project) { create(:project, :public) }
let(:source_project) { fork_project(target_project) } let(:source_project) { fork_project(target_project) }
......
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