cleansweep 1.0.6 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|