Skip to content

Commit 203fbe0

Browse files
authored
Merge pull request #23399 from jrafanie/add-purge-orphan-tool
Add purge orphan tool
2 parents 1b07673 + e0cc94c commit 203fbe0

File tree

3 files changed

+98
-20
lines changed

3 files changed

+98
-20
lines changed

app/models/mixins/purging_mixin.rb

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,10 @@ def purge_by_scope(older_than = nil, window = nil, &block)
116116
total
117117
end
118118

119-
def purge_by_orphaned(fk_name, window = purge_window_size)
119+
def purge_by_orphaned(fk_name, window = purge_window_size, purge_mode = :purge)
120120
_log.info("Purging orphans in #{table_name}...")
121-
total = purge_orphans(fk_name, window)
122-
_log.info("Purging orphans in #{table_name}...Complete - Deleted #{total} records")
121+
total = purge_orphans(fk_name, window, purge_mode)
122+
_log.info("Purging orphans in #{table_name}...Complete - #{purge_mode.to_sym == :count ? 'Would delete' : 'Deleted'} #{total} records")
123123
total
124124
end
125125

@@ -133,23 +133,38 @@ def purge_by_date_and_orphaned(older_than, fk_name, window = purge_window_size)
133133

134134
private
135135

136-
def purge_orphans(fk_name, window)
137-
# For now, this only supports polymorphic references
138-
# We don't currently have a situation where a table with a regular reference needs to be purged
136+
def purge_orphans(fk_name, window, purge_mode = :purge)
137+
reflection = reflect_on_association(fk_name)
138+
scopes = reflection.polymorphic? ? polymorphic_orphan_scopes(fk_name) : non_polymorphic_orphan_scopes(fk_name, reflection.klass)
139+
140+
if purge_mode.to_sym == :count
141+
scopes.sum(&:count)
142+
else
143+
scopes.sum { |s| purge_in_batches(s, window) }
144+
end
145+
end
146+
147+
def polymorphic_orphan_scopes(fk_name)
139148
polymorphic_type_column = "#{fk_name}_type"
140149
polymorphic_id_column = connection.quote_column_name("#{fk_name}_id")
141-
total = 0
142150

143-
polymorphic_classes(polymorphic_type_column).each do |klass|
151+
polymorphic_classes(polymorphic_type_column).collect do |klass|
144152
resource_table = connection.quote_table_name(klass.table_name)
145153
q_table_name = connection.quote_table_name(table_name)
146154

147-
scope = joins("LEFT OUTER JOIN #{resource_table} ON #{q_table_name}.#{polymorphic_id_column} = #{resource_table}.id")
148-
.where(resource_table => {:id => nil})
149-
.where("#{q_table_name}.#{connection.quote_column_name(polymorphic_type_column)} = #{connection.quote(klass.name)}")
150-
total += purge_in_batches(scope, window)
155+
joins("LEFT OUTER JOIN #{resource_table} ON #{q_table_name}.#{polymorphic_id_column} = #{resource_table}.id")
156+
.where(resource_table => {:id => nil})
157+
.where("#{q_table_name}.#{connection.quote_column_name(polymorphic_type_column)} = #{connection.quote(klass.name)}")
151158
end
152-
total
159+
end
160+
161+
def non_polymorphic_orphan_scopes(fk_name, reflection_klass)
162+
resource_table = connection.quote_table_name(reflection_klass.table_name)
163+
assoc_id = connection.quote_column_name("#{fk_name}_id")
164+
q_table_name = connection.quote_table_name(table_name)
165+
166+
[joins("LEFT OUTER JOIN #{resource_table} ON #{q_table_name}.#{assoc_id} = #{resource_table}.id")
167+
.where(resource_table => {:id => nil})]
153168
end
154169

155170
def polymorphic_classes(polymorphic_type_column)

spec/models/vim_performance_state/purging_spec.rb

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,34 @@
11
RSpec.describe VimPerformanceState do
22
context "::Purging" do
33
describe ".purge_by_orphaned" do
4-
it "purges all the orphaned rows for all referenced classes" do
4+
before do
55
# Create an orphaned row referencing VmOrTemplate
66
vm1 = FactoryBot.create(:vm_or_template)
7-
vm_perf1 = FactoryBot.create(:vim_performance_state, :resource => vm1)
7+
@vm_perf1 = FactoryBot.create(:vim_performance_state, :resource => vm1)
88
vm1.delete
99

1010
# Create a non-orphaned row referencing VmOrTemplate
11-
vm_perf2 = FactoryBot.create(:vim_performance_state, :resource => FactoryBot.create(:vm_or_template))
11+
@vm_perf2 = FactoryBot.create(:vim_performance_state, :resource => FactoryBot.create(:vm_or_template))
1212

1313
# Create an orphaned row referencing ExtManagementSystem
1414
ems1 = FactoryBot.create(:ext_management_system)
15-
ems_perf1 = FactoryBot.create(:vim_performance_state, :resource => ems1)
15+
@ems_perf1 = FactoryBot.create(:vim_performance_state, :resource => ems1)
1616
ems1.delete
1717

1818
# Create a non-orphaned row referencing ExtManagementSystem
19-
ems_perf2 = FactoryBot.create(:vim_performance_state, :resource => FactoryBot.create(:ext_management_system))
19+
@ems_perf2 = FactoryBot.create(:vim_performance_state, :resource => FactoryBot.create(:ext_management_system))
20+
expect(described_class.all).to match_array([@vm_perf1, @vm_perf2, @ems_perf1, @ems_perf2])
21+
end
2022

21-
expect(described_class.all).to match_array([vm_perf1, vm_perf2, ems_perf1, ems_perf2])
23+
it "purges all the orphaned rows for all referenced classes" do
2224
count = described_class.purge_by_orphaned("resource")
23-
expect(described_class.all).to match_array([vm_perf2, ems_perf2])
25+
expect(described_class.all).to match_array([@vm_perf2, @ems_perf2])
26+
expect(count).to eq(2)
27+
end
28+
29+
it "count purge_mode returns count of the orphaned rows for all referenced classes without actually purging" do
30+
count = described_class.purge_by_orphaned("resource", 1000, :count)
31+
expect(described_class.all).to match_array([@vm_perf1, @vm_perf2, @ems_perf1, @ems_perf2])
2432
expect(count).to eq(2)
2533
end
2634
end

tools/purge_orphans.rb

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#!/usr/bin/env ruby
2+
require "optimist"
3+
4+
VALID_MODES = %w[count purge].freeze
5+
opts = Optimist.options do
6+
synopsis "Purge orphaned rows from several tables."
7+
usage "[options]"
8+
9+
opt :mode, "Mode", :default => "count", :permitted => VALID_MODES
10+
end
11+
12+
purge_mode = opts[:mode].to_sym
13+
14+
# now load rails
15+
require File.expand_path('../config/environment', __dir__)
16+
17+
def purge_by_orphaned(klass, fk, window, purge_mode)
18+
klass.include PurgingMixin
19+
klass.define_method(:purge_method) { :destroy }
20+
klass.purge_by_orphaned(fk, window, purge_mode)
21+
end
22+
23+
CLASSES_TO_PURGE = [
24+
BinaryBlob, :resource, 100,
25+
ContainerCondition, :container_entity, 1000,
26+
ContainerEnvVar, :container, 1000,
27+
ContainerPortConfig, :container, 1000,
28+
ContainerVolume, :parent, 1000,
29+
CustomAttribute, :resource, 1000,
30+
MiqReportResultDetail, :miq_report_result, 1000,
31+
RequestLog, :resource, 500,
32+
SecurityContext, :resource, 1000
33+
]
34+
35+
puts "\nNote: Count mode was provided. No changes are being made. Use --mode purge to purge the rows.\n" if purge_mode == :count
36+
puts
37+
results = [["Class", purge_mode == :count ? "Orphan Count" : "Purged Rows"]]
38+
_result, bm = Benchmark.realtime_block("TotalTime") do
39+
40+
CLASSES_TO_PURGE.each_slice(3) do |klass, fk, window|
41+
Benchmark.realtime_block(klass.name) do
42+
print "Purging #{klass.name}..."
43+
purge_by_orphaned(klass, fk, window, purge_mode).tap { |result| results << [klass, result] }
44+
puts "Done!"
45+
end
46+
end
47+
nil
48+
end
49+
puts "\nResults:\n\n"
50+
51+
puts results.tableize
52+
puts
53+
54+
puts "Timing by class:\n\n"
55+
puts bm.transform_values { |v| v.round(6) }.to_a.unshift(["Model", "Time (s)"]).tableize

0 commit comments

Comments
 (0)