Commit 3ab026b7 authored by Zeger-Jan van de Weg's avatar Zeger-Jan van de Weg

Use memoization for commits on diffs

The Gitaly CommitService is being hammered by n + 1 calls, mostly when
finding commits. This leads to this gRPC being turned of on production:
https://gitlab.com/gitlab-org/gitaly/issues/514#note_48991378

Hunting down where it came from, most of them were due to
MergeRequest#show. To prove this, I set a script to request the
MergeRequest#show page 50 times. The GDK was being scraped by
Prometheus, where we have metrics on controller#action and their Gitaly
calls performed. On both occations I've restarted the full GDK so all
caches had to be rebuild.

Current master, 806a68a8, needed 435 requests
After this commit, 154 requests
parent 806a68a8
...@@ -8,6 +8,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -8,6 +8,7 @@ class MergeRequest < ActiveRecord::Base
include ManualInverseAssociation include ManualInverseAssociation
include EachBatch include EachBatch
include ThrottledTouch include ThrottledTouch
include Gitlab::Utils::StrongMemoize
ignore_column :locked_at, ignore_column :locked_at,
:ref_fetched :ref_fetched
...@@ -53,6 +54,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -53,6 +54,7 @@ class MergeRequest < ActiveRecord::Base
after_create :ensure_merge_request_diff, unless: :importing? after_create :ensure_merge_request_diff, unless: :importing?
after_update :reload_diff_if_branch_changed after_update :reload_diff_if_branch_changed
after_update :clear_memoized_shas
# When this attribute is true some MR validation is ignored # When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests # It allows us to close or modify broken merge requests
...@@ -387,14 +389,18 @@ class MergeRequest < ActiveRecord::Base ...@@ -387,14 +389,18 @@ class MergeRequest < ActiveRecord::Base
end end
def source_branch_head def source_branch_head
return unless source_project strong_memoize(:source_branch_head) do
if source_project && source_branch_ref
source_project.repository.commit(source_branch_ref) if source_branch_ref source_project.repository.commit(source_branch_ref)
end
end
end end
def target_branch_head def target_branch_head
strong_memoize(:target_branch_head) do
target_project.repository.commit(target_branch_ref) target_project.repository.commit(target_branch_ref)
end end
end
def branch_merge_base_commit def branch_merge_base_commit
start_sha = target_branch_sha start_sha = target_branch_sha
...@@ -525,6 +531,13 @@ class MergeRequest < ActiveRecord::Base ...@@ -525,6 +531,13 @@ class MergeRequest < ActiveRecord::Base
end end
end end
def clear_memoized_shas
@target_branch_sha = @source_branch_sha = nil
clear_memoization(:source_branch_head)
clear_memoization(:target_branch_head)
end
def reload_diff_if_branch_changed def reload_diff_if_branch_changed
if (source_branch_changed? || target_branch_changed?) && if (source_branch_changed? || target_branch_changed?) &&
(source_branch_head && target_branch_head) (source_branch_head && target_branch_head)
......
...@@ -104,19 +104,19 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -104,19 +104,19 @@ class MergeRequestDiff < ActiveRecord::Base
def base_commit def base_commit
return unless base_commit_sha return unless base_commit_sha
project.commit(base_commit_sha) project.commit_by(oid: base_commit_sha)
end end
def start_commit def start_commit
return unless start_commit_sha return unless start_commit_sha
project.commit(start_commit_sha) project.commit_by(oid: start_commit_sha)
end end
def head_commit def head_commit
return unless head_commit_sha return unless head_commit_sha
project.commit(head_commit_sha) project.commit_by(oid: head_commit_sha)
end end
def commit_shas def commit_shas
......
---
title: Cache commits for MergeRequest diffs
merge_request:
author:
type: performance
...@@ -19,6 +19,8 @@ module Gitlab ...@@ -19,6 +19,8 @@ module Gitlab
commit_message: commit_message || default_commit_message commit_message: commit_message || default_commit_message
} }
resolver.resolve_conflicts(user, files, args) resolver.resolve_conflicts(user, files, args)
ensure
@merge_request.clear_memoized_shas
end end
def files def files
......
...@@ -11,6 +11,8 @@ module Gitlab ...@@ -11,6 +11,8 @@ module Gitlab
# #
# We could write it like: # We could write it like:
# #
# include Gitlab::Utils::StrongMemoize
#
# def trigger_from_token # def trigger_from_token
# strong_memoize(:trigger) do # strong_memoize(:trigger) do
# Ci::Trigger.find_by_token(params[:token].to_s) # Ci::Trigger.find_by_token(params[:token].to_s)
...@@ -18,14 +20,22 @@ module Gitlab ...@@ -18,14 +20,22 @@ module Gitlab
# end # end
# #
def strong_memoize(name) def strong_memoize(name)
ivar_name = "@#{name}" if instance_variable_defined?(ivar(name))
instance_variable_get(ivar(name))
if instance_variable_defined?(ivar_name)
instance_variable_get(ivar_name)
else else
instance_variable_set(ivar_name, yield) instance_variable_set(ivar(name), yield)
end end
end end
def clear_memoization(name)
remove_instance_variable(ivar(name)) if instance_variable_defined?(ivar(name))
end
private
def ivar(name)
"@#{name}"
end
end end
end end
end end
...@@ -49,4 +49,17 @@ describe Gitlab::Utils::StrongMemoize do ...@@ -49,4 +49,17 @@ describe Gitlab::Utils::StrongMemoize do
end end
end end
end end
describe '#clear_memoization' do
let(:value) { 'mepmep' }
it 'removes the instance variable' do
object.method_name
object.clear_memoization(:method_name)
expect(object.instance_variable_defined?(:@method_name)).to be(false)
end
end
end end
...@@ -124,6 +124,7 @@ describe MergeRequest do ...@@ -124,6 +124,7 @@ describe MergeRequest do
context 'when the target branch does not exist' do context 'when the target branch does not exist' do
before do before do
project.repository.rm_branch(subject.author, subject.target_branch) project.repository.rm_branch(subject.author, subject.target_branch)
subject.clear_memoized_shas
end end
it 'returns nil' do it 'returns nil' do
...@@ -733,7 +734,7 @@ describe MergeRequest do ...@@ -733,7 +734,7 @@ describe MergeRequest do
before do before do
project.repository.raw_repository.delete_branch(subject.target_branch) project.repository.raw_repository.delete_branch(subject.target_branch)
subject.reload subject.clear_memoized_shas
end end
it 'does not crash' do it 'does not crash' do
...@@ -1468,6 +1469,7 @@ describe MergeRequest do ...@@ -1468,6 +1469,7 @@ describe MergeRequest do
context 'when the target branch does not exist' do context 'when the target branch does not exist' do
before do before do
subject.project.repository.rm_branch(subject.author, subject.target_branch) subject.project.repository.rm_branch(subject.author, subject.target_branch)
subject.clear_memoized_shas
end end
it 'returns nil' do it 'returns nil' 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