Commit a47de446 authored by Patrick Bajao's avatar Patrick Bajao

Reduce Gitaly calls for keeping around refs of published notes

We are currently keeping around commits of each note being
published even though they are all the same commits. That results
to duplicate `ref_exists` calls.

For example, if there are 6 draft notes set to be published on a
single MR version, there will be around 36 calls for `ref_exists`.

This fix reduces those calls by getting the unique shas to be kept.
Based on local testing, this reduced the `ref_exists` calls from
36 to 4.

Changelog: performance
parent e24dec57
...@@ -22,7 +22,7 @@ class DiffNote < Note ...@@ -22,7 +22,7 @@ class DiffNote < Note
validate :verify_supported, unless: :importing? validate :verify_supported, unless: :importing?
before_validation :set_line_code, if: :on_text?, unless: :importing? before_validation :set_line_code, if: :on_text?, unless: :importing?
after_save :keep_around_commits, unless: :importing? after_save :keep_around_commits, unless: -> { importing? || skip_keep_around_commits }
NoteDiffFileCreationError = Class.new(StandardError) NoteDiffFileCreationError = Class.new(StandardError)
...@@ -115,6 +115,20 @@ class DiffNote < Note ...@@ -115,6 +115,20 @@ class DiffNote < Note
position&.multiline? position&.multiline?
end end
def shas
[
self.original_position.base_sha,
self.original_position.start_sha,
self.original_position.head_sha
].tap do |a|
if self.position != self.original_position
a << self.position.base_sha
a << self.position.start_sha
a << self.position.head_sha
end
end
end
private private
def enqueue_diff_file_creation_job def enqueue_diff_file_creation_job
...@@ -173,18 +187,6 @@ class DiffNote < Note ...@@ -173,18 +187,6 @@ class DiffNote < Note
end end
def keep_around_commits def keep_around_commits
shas = [
self.original_position.base_sha,
self.original_position.start_sha,
self.original_position.head_sha
]
if self.position != self.original_position
shas << self.position.base_sha
shas << self.position.start_sha
shas << self.position.head_sha
end
repository.keep_around(*shas) repository.keep_around(*shas)
end end
......
...@@ -48,6 +48,9 @@ class Note < ApplicationRecord ...@@ -48,6 +48,9 @@ class Note < ApplicationRecord
# Attribute used to store the attributes that have been changed by quick actions. # Attribute used to store the attributes that have been changed by quick actions.
attr_accessor :commands_changes attr_accessor :commands_changes
# Attribute used to determine whether keep_around_commits will be skipped for diff notes.
attr_accessor :skip_keep_around_commits
default_value_for :system, false default_value_for :system, false
attr_mentionable :note, pipeline: :note attr_mentionable :note, pipeline: :note
......
...@@ -34,22 +34,24 @@ module DraftNotes ...@@ -34,22 +34,24 @@ module DraftNotes
created_notes = draft_notes.map do |draft_note| created_notes = draft_notes.map do |draft_note|
draft_note.review = review draft_note.review = review
create_note_from_draft(draft_note, skip_capture_diff_note_position: true) create_note_from_draft(draft_note, skip_capture_diff_note_position: true, skip_keep_around_commits: true)
end end
capture_diff_note_positions(created_notes) capture_diff_note_positions(created_notes)
keep_around_commits(created_notes)
draft_notes.delete_all draft_notes.delete_all
set_reviewed set_reviewed
notification_service.async.new_review(review) notification_service.async.new_review(review)
MergeRequests::ResolvedDiscussionNotificationService.new(project: project, current_user: current_user).execute(merge_request) MergeRequests::ResolvedDiscussionNotificationService.new(project: project, current_user: current_user).execute(merge_request)
end end
def create_note_from_draft(draft, skip_capture_diff_note_position: false) def create_note_from_draft(draft, skip_capture_diff_note_position: false, skip_keep_around_commits: false)
# Make sure the diff file is unfolded in order to find the correct line # Make sure the diff file is unfolded in order to find the correct line
# codes. # codes.
draft.diff_file&.unfold_diff_lines(draft.original_position) draft.diff_file&.unfold_diff_lines(draft.original_position)
note = Notes::CreateService.new(draft.project, draft.author, draft.publish_params).execute( note_params = draft.publish_params.merge(skip_keep_around_commits: skip_keep_around_commits)
note = Notes::CreateService.new(draft.project, draft.author, note_params).execute(
skip_capture_diff_note_position: skip_capture_diff_note_position skip_capture_diff_note_position: skip_capture_diff_note_position
) )
...@@ -86,5 +88,17 @@ module DraftNotes ...@@ -86,5 +88,17 @@ module DraftNotes
capture_service.execute(note.discussion) if note.diff_note? && note.start_of_discussion? capture_service.execute(note.discussion) if note.diff_note? && note.start_of_discussion?
end end
end end
def keep_around_commits(notes)
shas = notes.flat_map do |note|
note.shas if note.diff_note?
end.uniq
# We are allowing this since gitaly call will be created for each sha and
# even though they're unique, there will still be multiple Gitaly calls.
Gitlab::GitalyClient.allow_n_plus_1_calls do
project.repository.keep_around(*shas)
end
end
end end
end end
...@@ -558,4 +558,31 @@ RSpec.describe DiffNote do ...@@ -558,4 +558,31 @@ RSpec.describe DiffNote do
it { is_expected.to eq('note') } it { is_expected.to eq('note') }
end end
describe '#shas' do
it 'returns list of SHAs based on original_position' do
expect(subject.shas).to match_array([
position.base_sha,
position.start_sha,
position.head_sha
])
end
context 'when position changes' do
before do
subject.position = new_position
end
it 'includes the new position SHAs' do
expect(subject.shas).to match_array([
position.base_sha,
position.start_sha,
position.head_sha,
new_position.base_sha,
new_position.start_sha,
new_position.head_sha
])
end
end
end
end end
...@@ -33,7 +33,8 @@ RSpec.describe DraftNotes::PublishService do ...@@ -33,7 +33,8 @@ RSpec.describe DraftNotes::PublishService do
end end
it 'does not skip notification', :sidekiq_might_not_need_inline do it 'does not skip notification', :sidekiq_might_not_need_inline do
expect(Notes::CreateService).to receive(:new).with(project, user, drafts.first.publish_params).and_call_original note_params = drafts.first.publish_params.merge(skip_keep_around_commits: false)
expect(Notes::CreateService).to receive(:new).with(project, user, note_params).and_call_original
expect_next_instance_of(NotificationService) do |notification_service| expect_next_instance_of(NotificationService) do |notification_service|
expect(notification_service).to receive(:new_note) expect(notification_service).to receive(:new_note)
end end
...@@ -127,12 +128,17 @@ RSpec.describe DraftNotes::PublishService do ...@@ -127,12 +128,17 @@ RSpec.describe DraftNotes::PublishService do
publish publish
end end
context 'capturing diff notes positions' do context 'capturing diff notes positions and keeping around commits' do
before do before do
# Need to execute this to ensure that we'll be able to test creation of # Need to execute this to ensure that we'll be able to test creation of
# DiffNotePosition records as that only happens when the `MergeRequest#merge_ref_head` # DiffNotePosition records as that only happens when the `MergeRequest#merge_ref_head`
# is present. This service creates that for the specified merge request. # is present. This service creates that for the specified merge request.
MergeRequests::MergeToRefService.new(project: project, current_user: user).execute(merge_request) MergeRequests::MergeToRefService.new(project: project, current_user: user).execute(merge_request)
# Need to re-stub this and call original as we are stubbing
# `Gitlab::Git::KeepAround#execute` in spec_helper for performance reason.
# Enabling it here so we can test the Gitaly calls it makes.
allow(Gitlab::Git::KeepAround).to receive(:execute).and_call_original
end end
it 'creates diff_note_positions for diff notes' do it 'creates diff_note_positions for diff notes' do
...@@ -143,11 +149,26 @@ RSpec.describe DraftNotes::PublishService do ...@@ -143,11 +149,26 @@ RSpec.describe DraftNotes::PublishService do
expect(notes.last.diff_note_positions).to be_any expect(notes.last.diff_note_positions).to be_any
end end
it 'keeps around the commits of each published note' do
publish
repository = project.repository
notes = merge_request.notes.order(id: :asc)
notes.first.shas.each do |sha|
expect(repository.ref_exists?("refs/keep-around/#{sha}")).to be_truthy
end
notes.last.shas.each do |sha|
expect(repository.ref_exists?("refs/keep-around/#{sha}")).to be_truthy
end
end
it 'does not requests a lot from Gitaly', :request_store do it 'does not requests a lot from Gitaly', :request_store do
# NOTE: This should be reduced as we work on reducing Gitaly calls. # NOTE: This should be reduced as we work on reducing Gitaly calls.
# Gitaly requests shouldn't go above this threshold as much as possible # Gitaly requests shouldn't go above this threshold as much as possible
# as it may add more to the Gitaly N+1 issue we are experiencing. # as it may add more to the Gitaly N+1 issue we are experiencing.
expect { publish }.to change { Gitlab::GitalyClient.get_request_count }.by(11) expect { publish }.to change { Gitlab::GitalyClient.get_request_count }.by(21)
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