cleansweep 1.0.6 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +2 -1
- data/CHANGES.md +5 -0
- data/cleansweep.gemspec +1 -1
- data/lib/clean_sweep/purge_runner.rb +45 -11
- data/lib/clean_sweep/version.rb +1 -1
- data/spec/purge_runner_spec.rb +86 -4
- data/spec/spec_helper.rb +3 -2
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9720deef8bdeca62cd0b483a7d0dc27587667d29
|
4
|
+
data.tar.gz: 6655283cf1ae8df51bb054643ba2f0e78dd1a871
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e4c81408490d26080503edcadf0451f6600e55ad404eea339b4d363a05289958a7f1f42983a5dd0f5974cd240d7b8be1caa7916ba6a8ea9b320a8a6feacb51a6
|
7
|
+
data.tar.gz: f0c5777dd4b362b9b6c790c74c38022e97dbce1faea43897f17d8d9348b4e2852bf3cad658f2fbb8e7a2344b9837ee77eea7202bebd37cd3e2b55f8b16c34bea
|
data/.travis.yml
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
3
|
- 2.1.4
|
4
|
-
- 1.9.3
|
5
4
|
gemfile:
|
6
5
|
- gemfiles/Gemfile.rails3
|
7
6
|
- gemfiles/Gemfile.rails4
|
8
7
|
addons:
|
9
8
|
code_climate:
|
10
9
|
repo_token: 7ec6fd701b7d2b206cdd233c2202b6e11c8ba6af01f8a93f5e24595008ac20a0
|
10
|
+
after_success:
|
11
|
+
- bundle exec codeclimate-test-reporter
|
data/CHANGES.md
CHANGED
@@ -1,4 +1,9 @@
|
|
1
1
|
See the [documentation](http://bkayser.github.io/cleansweep) for details
|
2
|
+
### Version 1.1.0
|
3
|
+
|
4
|
+
* Support automatic DB reconnection during a purge run.
|
5
|
+
The max number of reconnections can be controlled with the max_reconnects
|
6
|
+
option to PurgeRunner.
|
2
7
|
|
3
8
|
### Version 1.0.6
|
4
9
|
|
data/cleansweep.gemspec
CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
|
|
25
25
|
spec.test_files = spec.files.grep(%r{^spec/})
|
26
26
|
spec.require_paths = ["lib"]
|
27
27
|
|
28
|
-
spec.add_runtime_dependency 'activerecord', '>= 3.0'
|
28
|
+
spec.add_runtime_dependency 'activerecord', '>= 3.0', '< 5.0.0'
|
29
29
|
spec.add_runtime_dependency 'newrelic_rpm'
|
30
30
|
spec.add_runtime_dependency 'mysql2', '~> 0.3'
|
31
31
|
|
@@ -74,6 +74,10 @@ require 'stringio'
|
|
74
74
|
# [:max_repl_lag]
|
75
75
|
# The maximum length of the replication lag. Checked every 5 minutes and if exceeded the purge
|
76
76
|
# pauses until the replication lag is below 90% of this value.
|
77
|
+
# [:max_reconnects]
|
78
|
+
# The maximum number of times to automatically attempt to reconnect to the database
|
79
|
+
# in the event of a dropped connection during a query. Set this to zero if you want
|
80
|
+
# to opt-out of the automatic reconnect behavior. Default: 3.
|
77
81
|
|
78
82
|
class CleanSweep::PurgeRunner
|
79
83
|
|
@@ -99,6 +103,8 @@ class CleanSweep::PurgeRunner
|
|
99
103
|
|
100
104
|
@max_history = options[:max_history]
|
101
105
|
@max_repl_lag = options[:max_repl_lag]
|
106
|
+
@max_reconnects = options[:max_reconnects] || 3
|
107
|
+
@reconnects_remaining = @max_reconnects
|
102
108
|
|
103
109
|
@copy_mode = @target_model && options[:copy_only]
|
104
110
|
|
@@ -136,6 +142,27 @@ class CleanSweep::PurgeRunner
|
|
136
142
|
@copy_mode
|
137
143
|
end
|
138
144
|
|
145
|
+
def with_reconnect_handling
|
146
|
+
begin
|
147
|
+
yield
|
148
|
+
rescue ActiveRecord::StatementInvalid => e
|
149
|
+
if e.message =~ /Lost connection to MySQL server during query/
|
150
|
+
if @reconnects_remaining > 0
|
151
|
+
@reconnects_remaining -= 1
|
152
|
+
wait_before_reconnect = rand * 10.0
|
153
|
+
log :warn, "Lost connection to DB during query, reconnecting and trying again in #{wait_before_reconnect} seconds. #{@reconnects_remaining} re-connection attempts remaining after this. Original error: #{e}"
|
154
|
+
sleep(wait_before_reconnect)
|
155
|
+
@model.connection.reconnect!
|
156
|
+
else
|
157
|
+
log :error, "Lost connection to MySQL during query, and have already reconnected #{@max_reconnects} times. Giving up. Original error: #{e}"
|
158
|
+
raise
|
159
|
+
end
|
160
|
+
else
|
161
|
+
raise
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
139
166
|
# Execute the purge in chunks according to the parameters given on instance creation.
|
140
167
|
# Will raise <tt>CleanSweep::PurgeStopped</tt> if a <tt>stop_after</tt> option was provided and
|
141
168
|
# that limit is hit.
|
@@ -165,6 +192,7 @@ class CleanSweep::PurgeRunner
|
|
165
192
|
rows = NewRelic::Agent.with_database_metric_name(@model.name, 'SELECT') do
|
166
193
|
@model.connection.select_rows @query.to_sql
|
167
194
|
end
|
195
|
+
|
168
196
|
while rows.any? && (!@stop_after || @total_deleted < @stop_after) do
|
169
197
|
# index_entrypoint_args = Hash[*@source_keys.zip(rows.last).flatten]
|
170
198
|
log :debug, "#{verb} #{rows.size} records between #{rows.first.inspect} and #{rows.last.inspect}" if @logger.level == Logger::DEBUG
|
@@ -180,24 +208,30 @@ class CleanSweep::PurgeRunner
|
|
180
208
|
statement = @table_schema.delete_statement(rows)
|
181
209
|
end
|
182
210
|
log :debug, statement if @logger.level == Logger::DEBUG
|
183
|
-
chunk_deleted = NewRelic::Agent.with_database_metric_name((@target_model||@model), metric_op_name) do
|
184
|
-
(@target_model||@model).connection.update statement
|
185
|
-
end
|
186
211
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
212
|
+
with_reconnect_handling do
|
213
|
+
chunk_deleted = NewRelic::Agent.with_database_metric_name((@target_model||@model), metric_op_name) do
|
214
|
+
(@target_model||@model).connection.update statement
|
215
|
+
end
|
216
|
+
|
217
|
+
@total_deleted += chunk_deleted
|
218
|
+
raise CleanSweep::PurgeStopped.new("stopped after #{verb} #{@total_deleted} #{@model} records", @total_deleted) if stopped
|
219
|
+
q = @table_schema.scope_to_next_chunk(@query, last_row).to_sql
|
220
|
+
log :debug, "find rows: #{q}" if @logger.level == Logger::DEBUG
|
191
221
|
|
192
|
-
|
193
|
-
|
222
|
+
sleep @sleep if @sleep && !copy_mode?
|
223
|
+
@mysql_status.check! if @mysql_status
|
194
224
|
|
195
|
-
|
196
|
-
|
225
|
+
rows = NewRelic::Agent.with_database_metric_name(@model, 'SELECT') do
|
226
|
+
@model.connection.select_rows(q)
|
227
|
+
end
|
197
228
|
end
|
229
|
+
|
198
230
|
report
|
199
231
|
end
|
232
|
+
|
200
233
|
report(true)
|
234
|
+
|
201
235
|
if copy_mode?
|
202
236
|
log :info, "completed after #{verb} #{@total_deleted} #{@table_schema.name} records to #{@target_model.table_name}"
|
203
237
|
else
|
data/lib/clean_sweep/version.rb
CHANGED
data/spec/purge_runner_spec.rb
CHANGED
@@ -7,6 +7,7 @@ describe CleanSweep::PurgeRunner do
|
|
7
7
|
before do
|
8
8
|
Timecop.freeze Time.parse("2014-12-02 13:47:43.000000 -0800")
|
9
9
|
end
|
10
|
+
|
10
11
|
after do
|
11
12
|
Timecop.return
|
12
13
|
end
|
@@ -135,6 +136,82 @@ EOF
|
|
135
136
|
Book.delete_all
|
136
137
|
end
|
137
138
|
|
139
|
+
it 'reconnects after a lost connection' do
|
140
|
+
purger = CleanSweep::PurgeRunner.new model: Book,
|
141
|
+
chunk_size: 10
|
142
|
+
|
143
|
+
original_update = Book.connection.method(:update)
|
144
|
+
update_number = 0
|
145
|
+
|
146
|
+
allow(Book.connection).to receive(:update) do |*args|
|
147
|
+
update_number += 1
|
148
|
+
|
149
|
+
if update_number == 2
|
150
|
+
raise ActiveRecord::StatementInvalid.new("Lost connection to MySQL server during query: blah blah")
|
151
|
+
else
|
152
|
+
original_update.call(*args)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
expect(purger).to receive(:sleep).once
|
157
|
+
expect(Book.connection).to receive(:reconnect!).once
|
158
|
+
|
159
|
+
purger.execute_in_batches
|
160
|
+
|
161
|
+
expect(Book.count).to eq(0)
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'reconnects after a lost connection during select' do
|
165
|
+
purger = CleanSweep::PurgeRunner.new model: Book,
|
166
|
+
chunk_size: 10
|
167
|
+
|
168
|
+
original_select_rows = Book.connection.method(:select_rows)
|
169
|
+
iteration = 0
|
170
|
+
|
171
|
+
allow(Book.connection).to receive(:select_rows) do |*args|
|
172
|
+
iteration += 1
|
173
|
+
|
174
|
+
if iteration == 2
|
175
|
+
raise ActiveRecord::StatementInvalid.new("Lost connection to MySQL server during query: blah blah")
|
176
|
+
else
|
177
|
+
original_select_rows.call(*args)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
expect(purger).to receive(:sleep).once
|
182
|
+
expect(Book.connection).to receive(:reconnect!).once
|
183
|
+
|
184
|
+
purger.execute_in_batches
|
185
|
+
|
186
|
+
expect(Book.count).to eq(0)
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'stops trying to reconnect after max_reconnects' do
|
190
|
+
purger = CleanSweep::PurgeRunner.new model: Book,
|
191
|
+
chunk_size: 10,
|
192
|
+
max_reconnects: 4
|
193
|
+
|
194
|
+
original_update = Book.connection.method(:update)
|
195
|
+
update_number = 0
|
196
|
+
|
197
|
+
allow(Book.connection).to receive(:update) do |*args|
|
198
|
+
update_number += 1
|
199
|
+
if update_number > 1
|
200
|
+
raise ActiveRecord::StatementInvalid.new("Lost connection to MySQL server during query: blah blah")
|
201
|
+
else
|
202
|
+
original_update.call(*args)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
expect(purger).to receive(:sleep).exactly(4).times
|
207
|
+
expect(Book.connection).to receive(:reconnect!).exactly(4).times
|
208
|
+
|
209
|
+
expect { purger.execute_in_batches }.to raise_error(ActiveRecord::StatementInvalid)
|
210
|
+
|
211
|
+
# we only got through the first batch of 10, and then gave up
|
212
|
+
expect(Book.count).to eq(40)
|
213
|
+
end
|
214
|
+
|
138
215
|
it 'waits for history' do
|
139
216
|
purger = CleanSweep::PurgeRunner.new model: Book,
|
140
217
|
max_history: 100,
|
@@ -209,10 +286,11 @@ EOF
|
|
209
286
|
count = purger.execute_in_batches
|
210
287
|
expect(count).to be(@total_book_size)
|
211
288
|
expect(BookTemp.count).to eq(@total_book_size)
|
212
|
-
last_book =
|
213
|
-
|
214
|
-
expect(
|
215
|
-
expect(
|
289
|
+
last_book = Book.last
|
290
|
+
last_book_copy = BookTemp.last
|
291
|
+
expect(last_book_copy.book_id).to eq(last_book.id)
|
292
|
+
expect(last_book_copy.bin).to eq(last_book.bin)
|
293
|
+
expect(last_book_copy.published_by).to eq(last_book.publisher)
|
216
294
|
end
|
217
295
|
|
218
296
|
end
|
@@ -234,12 +312,16 @@ describe CleanSweep::PurgeRunner::MysqlStatus do
|
|
234
312
|
it "fetches innodb status" do
|
235
313
|
mysql_status.get_replication_lag
|
236
314
|
end
|
315
|
+
|
237
316
|
it "checks history and pauses" do
|
238
317
|
allow(mysql_status).to receive(:get_history_length).and_return(101, 95, 89)
|
318
|
+
allow(mysql_status).to receive(:get_replication_lag).and_return(50)
|
239
319
|
expect(mysql_status).to receive(:pause).twice
|
240
320
|
mysql_status.check!
|
241
321
|
end
|
322
|
+
|
242
323
|
it "checks replication and pauses" do
|
324
|
+
allow(mysql_status).to receive(:get_history_length).and_return(50)
|
243
325
|
allow(mysql_status).to receive(:get_replication_lag).and_return(101, 95, 89)
|
244
326
|
expect(mysql_status).to receive(:pause).twice
|
245
327
|
mysql_status.check!
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cleansweep
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bill Kayser
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-01-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -17,6 +17,9 @@ dependencies:
|
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '3.0'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 5.0.0
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -24,6 +27,9 @@ dependencies:
|
|
24
27
|
- - ">="
|
25
28
|
- !ruby/object:Gem::Version
|
26
29
|
version: '3.0'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 5.0.0
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: newrelic_rpm
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -205,7 +211,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
205
211
|
version: '0'
|
206
212
|
requirements: []
|
207
213
|
rubyforge_project:
|
208
|
-
rubygems_version: 2.
|
214
|
+
rubygems_version: 2.5.1
|
209
215
|
signing_key:
|
210
216
|
specification_version: 4
|
211
217
|
summary: Utility to purge or archive rows in mysql tables
|