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 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