Commit 88c71e08 authored by Gregory Havenga's avatar Gregory Havenga Committed by Adam Hegyi

Optimise Security::Finding cleanup by clearing build_id scope

Unscoping the where: :build_id scope from the cleanup_service
scan_batch relation resolves a strange issue where this query was being
carried through to the Security::Finding queries.

`unscope` is not permitted for use outside of models, so this action was
moved to a scope on the Security::Scan model.

Changelog: performance
MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84283
EE: true
parent 98284cbb
...@@ -4,7 +4,8 @@ module Security ...@@ -4,7 +4,8 @@ module Security
module Findings module Findings
class CleanupService class CleanupService
MAX_STALE_SCANS_SIZE = 50_000 MAX_STALE_SCANS_SIZE = 50_000
BATCH_DELETE_SIZE = 10_000 SCAN_BATCH_SIZE = 100
BATCH_DELETE_SIZE = 100
class << self class << self
def delete_stale_records def delete_stale_records
...@@ -25,7 +26,7 @@ module Security ...@@ -25,7 +26,7 @@ module Security
end end
def execute def execute
security_scans.in_batches(&method(:purge)) # rubocop:disable Cop/InBatches (`each_batch` does not let us set a global limit of records to be batched) security_scans.in_batches(of: SCAN_BATCH_SIZE, &method(:purge)) # rubocop:disable Cop/InBatches (`each_batch` does not let us set a global limit of records to be batched)
end end
private private
...@@ -33,14 +34,16 @@ module Security ...@@ -33,14 +34,16 @@ module Security
attr_reader :security_scans attr_reader :security_scans
def purge(scan_batch) def purge(scan_batch)
delete_findings_for(scan_batch) delete_findings_for(scan_batch.where_values_hash['id'])
scan_batch.update_all(status: :purged) scan_batch.unscope(where: :build_id).update_all(status: :purged) # rubocop:disable CodeReuse/ActiveRecord unlikely that a `unscope where build_id` scope would be used elsewhere
end end
def delete_findings_for(scan_batch) def delete_findings_for(scan_batch)
Security::Finding.by_scan(scan_batch).each_batch(of: BATCH_DELETE_SIZE) do |finding_batch| findings_relation = Security::Finding.by_scan(scan_batch).limit(BATCH_DELETE_SIZE)
finding_batch.delete_all
loop do
break if findings_relation.delete_all < BATCH_DELETE_SIZE
end end
end end
end end
......
...@@ -47,19 +47,39 @@ RSpec.describe Security::Findings::CleanupService do ...@@ -47,19 +47,39 @@ RSpec.describe Security::Findings::CleanupService do
end end
describe '#execute' do describe '#execute' do
let(:security_scan) { create(:security_scan) } let(:security_scans) { create_list(:security_scan, 2) }
let(:relation) { Security::Scan.where(id: security_scan.id) } let(:relation) { Security::Scan.where(id: security_scans.map(&:id)) }
let(:service_object) { described_class.new(relation) } let(:service_object) { described_class.new(relation) }
subject(:cleanup_findings) { service_object.execute } subject(:cleanup_findings) { service_object.execute }
before do before do
create_list(:security_finding, 2, scan: security_scan) expect(relation).to receive(:unscope).with(where: :build_id).and_call_original
create_list(:security_finding, 2, scan: security_scans.first)
create_list(:security_finding, 2, scan: security_scans.last)
end end
it 'deletes the findings of the given security scan object and marks the scan as purged' do it 'deletes the findings of the given security scan object and marks the scan as purged' do
expect { cleanup_findings }.to change { security_scan.findings.count }.from(2).to(0) expect { cleanup_findings }.to change { security_scans.map(&:reload).sum(&:findings).count }.from(4).to(0)
.and change { security_scan.reload.status }.to('purged') .and change { Security::Scan.purged.count }.from(0).to(2)
end
context 'when iterating through security findings' do
let(:finding_scope) { instance_double(ActiveRecord::Relation) }
before do
allow(Security::Finding).to receive(:by_scan).and_return(finding_scope)
allow(finding_scope).to receive(:limit).with(100).and_return(finding_scope)
allow(finding_scope).to receive(:delete_all).and_return(2, 0)
cleanup_findings
end
it 'deletes findings in batches of 100' do
expect(Security::Finding).to have_received(:by_scan).with(relation.map(&:id))
expect(finding_scope).to have_received(:limit).with(100)
expect(finding_scope).to have_received(:delete_all)
end
end 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