Commit 2d302cad authored by Igor's avatar Igor Committed by Nick Thomas

Remove N+1 for fetching commits signatures

It removes whitelisting for the query limiting
from the CommitsController as well since the
specs no longer fail because of the number of queries
parent d8616582
...@@ -23,6 +23,8 @@ class GpgSignature < ApplicationRecord ...@@ -23,6 +23,8 @@ class GpgSignature < ApplicationRecord
validates :project_id, presence: true validates :project_id, presence: true
validates :gpg_key_primary_keyid, presence: true validates :gpg_key_primary_keyid, presence: true
scope :by_commit_sha, ->(shas) { where(commit_sha: shas) }
def self.with_key_and_subkeys(gpg_key) def self.with_key_and_subkeys(gpg_key)
subkey_ids = gpg_key.subkeys.pluck(:id) subkey_ids = gpg_key.subkeys.pluck(:id)
......
---
title: Remove N+1 for fetching commits signatures
merge_request: 18389
author:
type: performance
...@@ -10,6 +10,8 @@ module Gitlab ...@@ -10,6 +10,8 @@ module Gitlab
repo = commit.project.repository.raw_repository repo = commit.project.repository.raw_repository
@signature_data = Gitlab::Git::Commit.extract_signature_lazily(repo, commit.sha || commit.id) @signature_data = Gitlab::Git::Commit.extract_signature_lazily(repo, commit.sha || commit.id)
lazy_signature
end end
def signature_text def signature_text
...@@ -28,18 +30,16 @@ module Gitlab ...@@ -28,18 +30,16 @@ module Gitlab
!!(signature_text && signed_text) !!(signature_text && signed_text)
end end
# rubocop: disable CodeReuse/ActiveRecord
def signature def signature
return unless has_signature? return unless has_signature?
return @signature if @signature return @signature if @signature
cached_signature = GpgSignature.find_by(commit_sha: @commit.sha) cached_signature = lazy_signature&.itself
return @signature = cached_signature if cached_signature.present? return @signature = cached_signature if cached_signature.present?
@signature = create_cached_signature! @signature = create_cached_signature!
end end
# rubocop: enable CodeReuse/ActiveRecord
def update_signature!(cached_signature) def update_signature!(cached_signature)
using_keychain do |gpg_key| using_keychain do |gpg_key|
...@@ -50,6 +50,14 @@ module Gitlab ...@@ -50,6 +50,14 @@ module Gitlab
private private
def lazy_signature
BatchLoader.for(@commit.sha).batch do |shas, loader|
GpgSignature.by_commit_sha(shas).each do |signature|
loader.call(signature.commit_sha, signature)
end
end
end
def using_keychain def using_keychain
Gitlab::Gpg.using_tmp_keychain do Gitlab::Gpg.using_tmp_keychain do
# first we need to get the fingerprint from the signature to query the gpg # first we need to get the fingerprint from the signature to query the gpg
......
...@@ -370,5 +370,33 @@ describe Gitlab::Gpg::Commit do ...@@ -370,5 +370,33 @@ describe Gitlab::Gpg::Commit do
it_behaves_like 'returns the cached signature on second call' it_behaves_like 'returns the cached signature on second call'
end end
context 'multiple commits with signatures' do
let(:first_signature) { create(:gpg_signature) }
let(:gpg_key) { create(:gpg_key, key: GpgHelpers::User2.public_key) }
let(:second_signature) { create(:gpg_signature, gpg_key: gpg_key) }
let!(:first_commit) { create(:commit, project: project, sha: first_signature.commit_sha) }
let!(:second_commit) { create(:commit, project: project, sha: second_signature.commit_sha) }
let(:commits) do
[first_commit, second_commit].map do |commit|
gpg_commit = described_class.new(commit)
allow(gpg_commit).to receive(:has_signature?).and_return(true)
gpg_commit
end
end
it 'does an aggregated sql request instead of 2 separate ones' do
recorder = ActiveRecord::QueryRecorder.new do
commits.each(&:signature)
end
expect(recorder.count).to eq(1)
end
end
end end
end end
...@@ -60,6 +60,18 @@ RSpec.describe GpgSignature do ...@@ -60,6 +60,18 @@ RSpec.describe GpgSignature do
end end
end end
describe '.by_commit_sha scope' do
let(:gpg_key) { create(:gpg_key, key: GpgHelpers::User2.public_key) }
let!(:another_gpg_signature) { create(:gpg_signature, gpg_key: gpg_key) }
it 'returns all gpg signatures by sha' do
expect(described_class.by_commit_sha(commit_sha)).to eq([gpg_signature])
expect(
described_class.by_commit_sha([commit_sha, another_gpg_signature.commit_sha])
).to contain_exactly(gpg_signature, another_gpg_signature)
end
end
describe '#commit' do describe '#commit' do
it 'fetches the commit through the project' do it 'fetches the commit through the project' do
expect_any_instance_of(Project).to receive(:commit).with(commit_sha).and_return(commit) expect_any_instance_of(Project).to receive(:commit).with(commit_sha).and_return(commit)
......
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