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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8ec1d586978900f85eff3d161db3a7228829272d
4
- data.tar.gz: c93186b039b2c9bdf3b08d69d73e237effdde631
3
+ metadata.gz: 9720deef8bdeca62cd0b483a7d0dc27587667d29
4
+ data.tar.gz: 6655283cf1ae8df51bb054643ba2f0e78dd1a871
5
5
  SHA512:
6
- metadata.gz: b8089e971691d784346b1572ac0235bc2aa25866415ac91bada9db10bdf1c1fd2682e724148decbbf72d946ee20f4e4a05f42f90a699da11d037b8a236059cd8
7
- data.tar.gz: 67cdf88125ece554602df476975702ef0898e0d47de70ed05bbc56296a53d506d42b505b028ef7ea3340bb217d928b6c3c6bcbd188d4792f0ce5ae9593cfbba2
6
+ metadata.gz: e4c81408490d26080503edcadf0451f6600e55ad404eea339b4d363a05289958a7f1f42983a5dd0f5974cd240d7b8be1caa7916ba6a8ea9b320a8a6feacb51a6
7
+ data.tar.gz: f0c5777dd4b362b9b6c790c74c38022e97dbce1faea43897f17d8d9348b4e2852bf3cad658f2fbb8e7a2344b9837ee77eea7202bebd37cd3e2b55f8b16c34bea
@@ -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
 
@@ -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
- @total_deleted += chunk_deleted
188
- raise CleanSweep::PurgeStopped.new("stopped after #{verb} #{@total_deleted} #{@model} records", @total_deleted) if stopped
189
- q = @table_schema.scope_to_next_chunk(@query, last_row).to_sql
190
- log :debug, "find rows: #{q}" if @logger.level == Logger::DEBUG
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
- sleep @sleep if @sleep && !copy_mode?
193
- @mysql_status.check! if @mysql_status
222
+ sleep @sleep if @sleep && !copy_mode?
223
+ @mysql_status.check! if @mysql_status
194
224
 
195
- rows = NewRelic::Agent.with_database_metric_name(@model, 'SELECT') do
196
- @model.connection.select_rows(q)
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
@@ -1,3 +1,3 @@
1
1
  module CleanSweep
2
- VERSION = "1.0.6"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -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 = BookTemp.last
213
- expect(last_book.book_id).to be 200
214
- expect(last_book.bin).to be 2000
215
- expect(last_book.published_by).to eq 'Random House'
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!
@@ -1,7 +1,8 @@
1
1
  ENV['RACK_ENV'] = 'test'
2
2
 
3
- require "codeclimate-test-reporter"
4
- CodeClimate::TestReporter.start
3
+ require 'simplecov'
4
+ SimpleCov.start
5
+
5
6
  require 'clean_sweep'
6
7
  require 'factory_girl'
7
8
  require 'fileutils'
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.6
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: 2016-05-18 00:00:00.000000000 Z
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.2.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