Commit b870a652 authored by Patrick Bajao's avatar Patrick Bajao

Reduce N+1 Gitaly queries when publishing multiple draft notes

We are currently calling
`Discussions::CaptureDiffNotePositionService` for each draft note
being published and results to N+1 Gitaly queries. This is because
we create an instance of that service in `Notes::CreateService`
for each draft note being published and that service is being
  called in that service.

In this fix, we are adding the capability to skip that call so we
can call it within the `DraftNotes::PublishService` instead and
only use a single instance of that service.

Changelog: performance
parent bb2352cc
...@@ -32,26 +32,28 @@ module DraftNotes ...@@ -32,26 +32,28 @@ module DraftNotes
review = Review.create!(author: current_user, merge_request: merge_request, project: project) review = Review.create!(author: current_user, merge_request: merge_request, project: project)
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) create_note_from_draft(draft_note, skip_capture_diff_note_position: true)
end end
draft_notes.delete_all
capture_diff_note_positions(created_notes)
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) def create_note_from_draft(draft, skip_capture_diff_note_position: 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 = Notes::CreateService.new(draft.project, draft.author, draft.publish_params).execute(
set_discussion_resolve_status(note, draft) skip_capture_diff_note_position: skip_capture_diff_note_position
)
set_discussion_resolve_status(note, draft)
note note
end end
...@@ -70,5 +72,19 @@ module DraftNotes ...@@ -70,5 +72,19 @@ module DraftNotes
def set_reviewed def set_reviewed
::MergeRequests::MarkReviewerReviewedService.new(project: project, current_user: current_user).execute(merge_request) ::MergeRequests::MarkReviewerReviewedService.new(project: project, current_user: current_user).execute(merge_request)
end end
def capture_diff_note_positions(notes)
paths = notes.flat_map do |note|
note.diff_file&.paths if note.diff_note?
end
return if paths.empty?
capture_service = Discussions::CaptureDiffNotePositionService.new(merge_request, paths.compact)
notes.each do |note|
capture_service.execute(note.discussion) if note.diff_note? && note.start_of_discussion?
end
end
end end
end end
...@@ -4,7 +4,7 @@ module Notes ...@@ -4,7 +4,7 @@ module Notes
class CreateService < ::Notes::BaseService class CreateService < ::Notes::BaseService
include IncidentManagement::UsageData include IncidentManagement::UsageData
def execute def execute(skip_capture_diff_note_position: false)
note = Notes::BuildService.new(project, current_user, params.except(:merge_request_diff_head_sha)).execute note = Notes::BuildService.new(project, current_user, params.except(:merge_request_diff_head_sha)).execute
# n+1: https://gitlab.com/gitlab-org/gitlab-foss/issues/37440 # n+1: https://gitlab.com/gitlab-org/gitlab-foss/issues/37440
...@@ -34,7 +34,7 @@ module Notes ...@@ -34,7 +34,7 @@ module Notes
end end
end end
when_saved(note) if note_saved when_saved(note, skip_capture_diff_note_position: skip_capture_diff_note_position) if note_saved
end end
note note
...@@ -68,14 +68,14 @@ module Notes ...@@ -68,14 +68,14 @@ module Notes
end end
end end
def when_saved(note) def when_saved(note, skip_capture_diff_note_position: false)
todo_service.new_note(note, current_user) todo_service.new_note(note, current_user)
clear_noteable_diffs_cache(note) clear_noteable_diffs_cache(note)
Suggestions::CreateService.new(note).execute Suggestions::CreateService.new(note).execute
increment_usage_counter(note) increment_usage_counter(note)
track_event(note, current_user) track_event(note, current_user)
if note.for_merge_request? && note.diff_note? && note.start_of_discussion? if !skip_capture_diff_note_position && note.for_merge_request? && note.diff_note? && note.start_of_discussion?
Discussions::CaptureDiffNotePositionService.new(note.noteable, note.diff_file&.paths).execute(note.discussion) Discussions::CaptureDiffNotePositionService.new(note.noteable, note.diff_file&.paths).execute(note.discussion)
end end
end end
......
...@@ -66,8 +66,8 @@ RSpec.describe DraftNotes::PublishService do ...@@ -66,8 +66,8 @@ RSpec.describe DraftNotes::PublishService do
let(:commit_id) { nil } let(:commit_id) { nil }
before do before do
create(:draft_note, merge_request: merge_request, author: user, note: 'first note', commit_id: commit_id, position: position) create(:draft_note_on_text_diff, merge_request: merge_request, author: user, note: 'first note', commit_id: commit_id, position: position)
create(:draft_note, merge_request: merge_request, author: user, note: 'second note', commit_id: commit_id, position: position) create(:draft_note_on_text_diff, merge_request: merge_request, author: user, note: 'second note', commit_id: commit_id, position: position)
end end
context 'when review fails to create' do context 'when review fails to create' do
...@@ -127,6 +127,30 @@ RSpec.describe DraftNotes::PublishService do ...@@ -127,6 +127,30 @@ RSpec.describe DraftNotes::PublishService do
publish publish
end end
context 'capturing diff notes positions' do
before do
# 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`
# is present. This service creates that for the specified merge request.
MergeRequests::MergeToRefService.new(project: project, current_user: user).execute(merge_request)
end
it 'creates diff_note_positions for diff notes' do
publish
notes = merge_request.notes.order(id: :asc)
expect(notes.first.diff_note_positions).to be_any
expect(notes.last.diff_note_positions).to be_any
end
it 'does not requests a lot from Gitaly', :request_store do
# NOTE: This should be reduced as we work on reducing Gitaly calls.
# 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.
expect { publish }.to change { Gitlab::GitalyClient.get_request_count }.by(11)
end
end
context 'commit_id is set' do context 'commit_id is set' do
let(:commit_id) { commit.id } let(:commit_id) { commit.id }
......
...@@ -185,6 +185,14 @@ RSpec.describe Notes::CreateService do ...@@ -185,6 +185,14 @@ RSpec.describe Notes::CreateService do
expect(note.note_diff_file).to be_present expect(note.note_diff_file).to be_present
expect(note.diff_note_positions).to be_present expect(note.diff_note_positions).to be_present
end end
context 'when skip_capture_diff_note_position execute option is set to true' do
it 'does not execute Discussions::CaptureDiffNotePositionService' do
expect(Discussions::CaptureDiffNotePositionService).not_to receive(:new)
described_class.new(project_with_repo, user, new_opts).execute(skip_capture_diff_note_position: true)
end
end
end end
context 'when DiffNote is a reply' do context 'when DiffNote is a reply' 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