Commit 92778fcf authored by Igor's avatar Igor Committed by Sean McGivern

Execute limited request for diff commits instead of preloading

The number of diff commits can be overwhelming sometimes and
preloading them and iterating over them using map
can be very expensive

We'd better make multiple limited requests (even if it's N+1)
then running into a subtle case when thousands of elements
are loaded into memory and iterated
parent 6a7e8e3f
...@@ -148,7 +148,7 @@ module IssuableCollections ...@@ -148,7 +148,7 @@ module IssuableCollections
when 'Issue' when 'Issue'
common_attributes + [:project, project: :namespace] common_attributes + [:project, project: :namespace]
when 'MergeRequest' when 'MergeRequest'
common_attributes + [:target_project, source_project: :route, head_pipeline: :project, target_project: :namespace, latest_merge_request_diff: :merge_request_diff_commits] common_attributes + [:target_project, :latest_merge_request_diff, source_project: :route, head_pipeline: :project, target_project: :namespace]
end end
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables # rubocop:enable Gitlab/ModuleWithInstanceVariables
......
...@@ -212,8 +212,8 @@ class MergeRequest < ApplicationRecord ...@@ -212,8 +212,8 @@ class MergeRequest < ApplicationRecord
scope :join_project, -> { joins(:target_project) } scope :join_project, -> { joins(:target_project) }
scope :references_project, -> { references(:target_project) } scope :references_project, -> { references(:target_project) }
scope :with_api_entity_associations, -> { scope :with_api_entity_associations, -> {
preload(:assignees, :author, :unresolved_notes, :labels, :milestone, :timelogs, preload(:assignees, :author, :unresolved_notes, :labels, :milestone,
latest_merge_request_diff: [:merge_request_diff_commits], :timelogs, :latest_merge_request_diff,
metrics: [:latest_closed_by, :merged_by], metrics: [:latest_closed_by, :merged_by],
target_project: [:route, { namespace: :route }], target_project: [:route, { namespace: :route }],
source_project: [:route, { namespace: :route }]) source_project: [:route, { namespace: :route }])
...@@ -396,14 +396,17 @@ class MergeRequest < ApplicationRecord ...@@ -396,14 +396,17 @@ class MergeRequest < ApplicationRecord
end end
end end
def commit_shas def commit_shas(limit: nil)
if persisted? return merge_request_diff.commit_shas(limit: limit) if persisted?
merge_request_diff.commit_shas
elsif compare_commits shas =
if compare_commits
compare_commits.to_a.reverse.map(&:sha) compare_commits.to_a.reverse.map(&:sha)
else else
Array(diff_head_sha) Array(diff_head_sha)
end end
limit ? shas.take(limit) : shas
end end
# Returns true if there are commits that match at least one commit SHA. # Returns true if there are commits that match at least one commit SHA.
...@@ -913,7 +916,7 @@ class MergeRequest < ApplicationRecord ...@@ -913,7 +916,7 @@ class MergeRequest < ApplicationRecord
def commit_notes def commit_notes
# Fetch comments only from last 100 commits # Fetch comments only from last 100 commits
commit_ids = commit_shas.take(100) commit_ids = commit_shas(limit: 100)
Note Note
.user .user
......
...@@ -218,7 +218,7 @@ class MergeRequestDiff < ApplicationRecord ...@@ -218,7 +218,7 @@ class MergeRequestDiff < ApplicationRecord
end end
def last_commit_sha def last_commit_sha
commit_shas.first commit_shas(limit: 1).first
end end
def first_commit def first_commit
...@@ -247,8 +247,8 @@ class MergeRequestDiff < ApplicationRecord ...@@ -247,8 +247,8 @@ class MergeRequestDiff < ApplicationRecord
project.commit_by(oid: head_commit_sha) project.commit_by(oid: head_commit_sha)
end end
def commit_shas def commit_shas(limit: nil)
merge_request_diff_commits.map(&:sha) merge_request_diff_commits.limit(limit).pluck(:sha)
end end
def commits_by_shas(shas) def commits_by_shas(shas)
......
---
title: Execute limited request for diff commits instead of preloading
merge_request: 19485
author:
type: performance
...@@ -378,6 +378,14 @@ describe MergeRequestDiff do ...@@ -378,6 +378,14 @@ describe MergeRequestDiff do
expect(diff_with_commits.commit_shas).not_to be_empty expect(diff_with_commits.commit_shas).not_to be_empty
expect(diff_with_commits.commit_shas).to all(match(/\h{40}/)) expect(diff_with_commits.commit_shas).to all(match(/\h{40}/))
end end
context 'with limit attribute' do
it 'returns limited number of shas' do
expect(diff_with_commits.commit_shas(limit: 2).size).to eq(2)
expect(diff_with_commits.commit_shas(limit: 100).size).to eq(29)
expect(diff_with_commits.commit_shas.size).to eq(29)
end
end
end end
describe '#compare_with' do describe '#compare_with' do
......
...@@ -1261,13 +1261,49 @@ describe MergeRequest do ...@@ -1261,13 +1261,49 @@ describe MergeRequest do
end end
describe '#commit_shas' do describe '#commit_shas' do
context 'persisted merge request' do
context 'with a limit' do
it 'returns a limited number of commit shas' do
expect(subject.commit_shas(limit: 2)).to eq(%w[
b83d6e391c22777fca1ed3012fce84f633d7fed0 498214de67004b1da3d820901307bed2a68a8ef6
])
end
end
context 'without a limit' do
it 'returns all commit shas of the merge request diff' do
expect(subject.commit_shas.size).to eq(29)
end
end
end
context 'new merge request' do
subject { build(:merge_request) }
context 'compare commits' do
before do before do
allow(subject.merge_request_diff).to receive(:commit_shas) subject.compare_commits = [
.and_return(['sha1']) double(sha: 'sha1'), double(sha: 'sha2')
]
end end
it 'delegates to merge request diff' do context 'without a limit' do
expect(subject.commit_shas).to eq ['sha1'] it 'returns all shas of compare commits' do
expect(subject.commit_shas).to eq(%w[sha2 sha1])
end
end
context 'with a limit' do
it 'returns a limited number of shas' do
expect(subject.commit_shas(limit: 1)).to eq(['sha2'])
end
end
end
it 'returns diff_head_sha as an array' do
expect(subject.commit_shas).to eq([subject.diff_head_sha])
expect(subject.commit_shas(limit: 2)).to eq([subject.diff_head_sha])
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