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
         |