lhm-shopify 4.3.0 → 4.4.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
  SHA256:
3
- metadata.gz: f0c79dc83baf664e8cd4ad2e11f8e72b3927edbb2af0beb5626961339e7f75c7
4
- data.tar.gz: 1a58b01ab3ab768c37f497e24329d0e4e3d85dc64af7b1a338c5447b742f4a4b
3
+ metadata.gz: 9b9ff81249ed6289557c131a6b6183dd77b494af67b71cb4845e69628092801e
4
+ data.tar.gz: ca4a71a59985f62685056833ad288a63ff33b0ca233a7a41571cb7f0963b096e
5
5
  SHA512:
6
- metadata.gz: e349310577754cebfe7fa4d067727946c19fb0b291f1f2305e87d26a7d30371fd37c80bcdb58039936c90c75618157904f3141722e5f3f83c9b0e05afb76e447
7
- data.tar.gz: 63fe5f4a016b32ab3d299e5cd9368f226a575bd725040931b8dd28db7e8a35cef7fbd78f0ec2713108dd508a974c7b783734a02e4d724a3b40e00afb7502693e
6
+ metadata.gz: ae9eedad45c7425785150927537a604084c60215c1bdfa796e7bc018fdddbaff2e431af97f5611d15ad5064ad9640f4af38c24f44712eb3567a66bba9f128038
7
+ data.tar.gz: 67135252c34441946ff069a1c6283545656a60b77306f8e6c019d319f0a164fd17dd898a21fcbfeba5490a52598812a804de52ad5fa498b6eee77a7731bc49ae
data/Appraisals CHANGED
@@ -11,3 +11,7 @@ end
11
11
  appraise "activerecord-7.1" do
12
12
  gem "activerecord", "7.1.1"
13
13
  end
14
+
15
+ appraise "activerecord-head" do
16
+ gem "activerecord", git: "https://github.com/rails/rails.git", branch: "main"
17
+ end
data/CHANGELOG.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Unreleased
2
2
 
3
+ # 4.4.0 (Aug, 2024)
4
+ * Add support for retrying chunks when running into max_binlog_cache_size exceeded error
5
+
3
6
  # 4.3.0 (Aug, 2024)
4
7
  * Drop support for Ruby 3.0, as it reached its EOL
5
8
  * Add support for next Rails version
data/Gemfile.lock CHANGED
@@ -1,28 +1,29 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- lhm-shopify (4.3.0)
4
+ lhm-shopify (4.4.0)
5
5
  retriable (>= 3.0.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- activemodel (7.1.3.4)
11
- activesupport (= 7.1.3.4)
12
- activerecord (7.1.3.4)
13
- activemodel (= 7.1.3.4)
14
- activesupport (= 7.1.3.4)
10
+ activemodel (7.2.0)
11
+ activesupport (= 7.2.0)
12
+ activerecord (7.2.0)
13
+ activemodel (= 7.2.0)
14
+ activesupport (= 7.2.0)
15
15
  timeout (>= 0.4.0)
16
- activesupport (7.1.3.4)
16
+ activesupport (7.2.0)
17
17
  base64
18
18
  bigdecimal
19
- concurrent-ruby (~> 1.0, >= 1.0.2)
19
+ concurrent-ruby (~> 1.0, >= 1.3.1)
20
20
  connection_pool (>= 2.2.5)
21
21
  drb
22
22
  i18n (>= 1.6, < 2)
23
+ logger (>= 1.4.2)
23
24
  minitest (>= 5.1)
24
- mutex_m
25
- tzinfo (~> 2.0)
25
+ securerandom (>= 0.3)
26
+ tzinfo (~> 2.0, >= 2.0.5)
26
27
  after_do (0.4.0)
27
28
  appraisal (2.5.0)
28
29
  bundler
@@ -31,20 +32,21 @@ GEM
31
32
  base64 (0.2.0)
32
33
  bigdecimal (3.1.8)
33
34
  byebug (11.1.3)
34
- concurrent-ruby (1.3.3)
35
+ concurrent-ruby (1.3.4)
35
36
  connection_pool (2.4.1)
36
37
  docile (1.4.1)
37
38
  drb (2.2.1)
38
39
  i18n (1.14.5)
39
40
  concurrent-ruby (~> 1.0)
40
- minitest (5.24.1)
41
+ logger (1.6.0)
42
+ minitest (5.25.1)
41
43
  mocha (2.4.5)
42
44
  ruby2_keywords (>= 0.0.5)
43
- mutex_m (0.2.0)
44
45
  mysql2 (0.5.6)
45
46
  rake (13.2.1)
46
47
  retriable (3.1.2)
47
48
  ruby2_keywords (0.0.5)
49
+ securerandom (0.3.1)
48
50
  simplecov (0.22.0)
49
51
  docile (~> 1.1)
50
52
  simplecov-html (~> 0.11)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- lhm-shopify (4.3.0)
4
+ lhm-shopify (4.4.0)
5
5
  retriable (>= 3.0.0)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- lhm-shopify (4.3.0)
4
+ lhm-shopify (4.4.0)
5
5
  retriable (>= 3.0.0)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- lhm-shopify (4.3.0)
4
+ lhm-shopify (4.4.0)
5
5
  retriable (>= 3.0.0)
6
6
 
7
7
  GEM
@@ -0,0 +1,88 @@
1
+ GIT
2
+ remote: https://github.com/rails/rails.git
3
+ revision: f4a9b7618fc32f0d3b2c0ff03a3f34f4964cc553
4
+ branch: main
5
+ specs:
6
+ activemodel (8.0.0.alpha)
7
+ activesupport (= 8.0.0.alpha)
8
+ activerecord (8.0.0.alpha)
9
+ activemodel (= 8.0.0.alpha)
10
+ activesupport (= 8.0.0.alpha)
11
+ timeout (>= 0.4.0)
12
+ activesupport (8.0.0.alpha)
13
+ base64
14
+ bigdecimal
15
+ concurrent-ruby (~> 1.0, >= 1.3.1)
16
+ connection_pool (>= 2.2.5)
17
+ drb
18
+ i18n (>= 1.6, < 2)
19
+ logger (>= 1.4.2)
20
+ minitest (>= 5.1)
21
+ securerandom (>= 0.3)
22
+ tzinfo (~> 2.0, >= 2.0.5)
23
+
24
+ PATH
25
+ remote: ..
26
+ specs:
27
+ lhm-shopify (4.4.0)
28
+ retriable (>= 3.0.0)
29
+
30
+ GEM
31
+ remote: https://rubygems.org/
32
+ specs:
33
+ after_do (0.4.0)
34
+ appraisal (2.5.0)
35
+ bundler
36
+ rake
37
+ thor (>= 0.14.0)
38
+ base64 (0.2.0)
39
+ bigdecimal (3.1.8)
40
+ byebug (11.1.3)
41
+ concurrent-ruby (1.3.3)
42
+ connection_pool (2.4.1)
43
+ docile (1.4.1)
44
+ drb (2.2.1)
45
+ i18n (1.14.5)
46
+ concurrent-ruby (~> 1.0)
47
+ logger (1.6.0)
48
+ minitest (5.24.1)
49
+ mocha (2.4.5)
50
+ ruby2_keywords (>= 0.0.5)
51
+ mysql2 (0.5.6)
52
+ rake (13.2.1)
53
+ retriable (3.1.2)
54
+ ruby2_keywords (0.0.5)
55
+ securerandom (0.3.1)
56
+ simplecov (0.22.0)
57
+ docile (~> 1.1)
58
+ simplecov-html (~> 0.11)
59
+ simplecov_json_formatter (~> 0.1)
60
+ simplecov-html (0.12.3)
61
+ simplecov_json_formatter (0.1.4)
62
+ thor (1.3.1)
63
+ timeout (0.4.1)
64
+ toxiproxy (2.0.2)
65
+ trilogy (2.8.1)
66
+ tzinfo (2.0.6)
67
+ concurrent-ruby (~> 1.0)
68
+
69
+ PLATFORMS
70
+ arm64-darwin-22
71
+ x86_64-linux
72
+
73
+ DEPENDENCIES
74
+ activerecord!
75
+ after_do
76
+ appraisal
77
+ byebug
78
+ lhm-shopify!
79
+ minitest
80
+ mocha
81
+ mysql2
82
+ rake
83
+ simplecov
84
+ toxiproxy
85
+ trilogy
86
+
87
+ BUNDLED WITH
88
+ 2.2.22
data/lib/lhm/chunker.rb CHANGED
@@ -37,6 +37,10 @@ module Lhm
37
37
  )
38
38
  end
39
39
 
40
+ def handle_max_binlog_exceeded_error
41
+ @throttler.backoff_stride
42
+ end
43
+
40
44
  def execute
41
45
  @start_time = Time.now
42
46
 
@@ -47,7 +51,18 @@ module Lhm
47
51
  top = upper_id(@next_to_insert, stride)
48
52
  verify_can_run
49
53
 
50
- affected_rows = ChunkInsert.new(@migration, @connection, bottom, top, @retry_options).insert_and_return_count_of_rows_created
54
+ begin
55
+ affected_rows = ChunkInsert.new(@migration, @connection, bottom, top, @retry_options).insert_and_return_count_of_rows_created
56
+ rescue ActiveRecord::StatementInvalid => e
57
+ if e.message.downcase.include?("transaction required more than 'max_binlog_cache_size' bytes of storage")
58
+ Lhm.logger.info("Encountered max_binlog_cache_size error, attempting to reduce stride size")
59
+ handle_max_binlog_exceeded_error
60
+ next
61
+ else
62
+ raise e
63
+ end
64
+ end
65
+
51
66
  expected_rows = top - bottom + 1
52
67
 
53
68
  # Only log the chunker progress every 5 minutes instead of every iteration
@@ -110,6 +125,5 @@ module Lhm
110
125
  return if @chunk_finder.table_empty?
111
126
  @chunk_finder.validate
112
127
  end
113
-
114
128
  end
115
129
  end
@@ -5,6 +5,8 @@ module Lhm
5
5
 
6
6
  DEFAULT_TIMEOUT = 0.1
7
7
  DEFAULT_STRIDE = 2_000
8
+ DEFAULT_BACKOFF_REDUCTION_FACTOR = 0.2 # 20%
9
+ MIN_STRIDE_SIZE = 1
8
10
 
9
11
  attr_accessor :timeout_seconds
10
12
  attr_accessor :stride
@@ -12,6 +14,37 @@ module Lhm
12
14
  def initialize(options = {})
13
15
  @timeout_seconds = options[:delay] || DEFAULT_TIMEOUT
14
16
  @stride = options[:stride] || DEFAULT_STRIDE
17
+ @backoff_reduction_factor = options[:backoff_reduction_factor] || DEFAULT_BACKOFF_REDUCTION_FACTOR
18
+ @min_stride_size = options[:min_stride_size] || MIN_STRIDE_SIZE
19
+
20
+ if @backoff_reduction_factor >= 1 || @backoff_reduction_factor <= 0
21
+ raise ArgumentError, 'backoff_reduction_factor must be between greater than 0, and less than 1'
22
+ end
23
+
24
+ if @min_stride_size < 1
25
+ raise ArgumentError, 'min_stride_size must be an integer greater than 0'
26
+ end
27
+
28
+ if !@min_stride_size.is_a?(Integer)
29
+ raise ArgumentError, 'min_stride_size must be an integer'
30
+ end
31
+
32
+ if @min_stride_size > @stride
33
+ raise ArgumentError, 'min_stride_size must be less than or equal to stride'
34
+ end
35
+ end
36
+
37
+ def backoff_stride
38
+ new_stride = (@stride * (1 - @backoff_reduction_factor)).to_i
39
+
40
+ if new_stride == @stride
41
+ raise RuntimeError, "Cannot backoff any further"
42
+ end
43
+
44
+ if new_stride < @min_stride_size
45
+ raise RuntimeError, "Cannot reduce stride below #{@min_stride_size}"
46
+ end
47
+ @stride = new_stride
15
48
  end
16
49
 
17
50
  def execute
data/lib/lhm/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # Schmidt
3
3
 
4
4
  module Lhm
5
- VERSION = '4.3.0'
5
+ VERSION = '4.4.0'
6
6
  end
@@ -5,6 +5,7 @@ require File.expand_path(File.dirname(__FILE__)) + '/integration_helper'
5
5
  require 'lhm/table'
6
6
  require 'lhm/migration'
7
7
 
8
+
8
9
  describe Lhm::Chunker do
9
10
  include IntegrationHelper
10
11
 
@@ -17,6 +18,7 @@ describe Lhm::Chunker do
17
18
  @migration = Lhm::Migration.new(@origin, @destination)
18
19
  @logs = StringIO.new
19
20
  Lhm.logger = Logger.new(@logs)
21
+ set_max_binlog_size(1024 * 1024 * 1024) # necessary since some tests reduce binlog size (1gb default)
20
22
  end
21
23
 
22
24
  def log_messages
@@ -306,6 +308,70 @@ describe Lhm::Chunker do
306
308
  value(count_all(@destination.name)).must_equal(0)
307
309
  end
308
310
  end
311
+
312
+ it 'should reduce stride size if chunker runs into max_binlog_cache_size error' do
313
+ init_stride = 1000
314
+
315
+ # Create a bunch of users
316
+ n = 0
317
+ 25.times do |i|
318
+ execute "BEGIN"
319
+ init_stride.times do # each batch is 10 * 1000 * i bytes, so each batch of 1000 will range from 10kb - 250kb
320
+ n += 1
321
+ id = n
322
+ username_data = "a" * 10 * i
323
+ execute "insert into origin (id, common) values (#{id}, '#{username_data}')"
324
+ end
325
+ execute "COMMIT"
326
+ end
327
+
328
+ # reduce binlog size to 8kb
329
+ set_max_binlog_size(1024 * 8)
330
+
331
+ throttler = Lhm::Throttler::Time.new(stride: init_stride )
332
+ chunker = Lhm::Chunker.new(
333
+ @migration, connection, { throttler: throttler }
334
+ )
335
+
336
+ # start chunking
337
+ chunker.run
338
+ assert init_stride > throttler.stride
339
+ end
340
+
341
+ it 'should throw an error when stride cannot be reduced beyond min stride size' do
342
+ init_stride = 100
343
+ min_stride_size = 50
344
+
345
+ # Create a bunch of users
346
+ n = 0
347
+ 25.times do |i|
348
+ execute "BEGIN"
349
+ init_stride.times do # each batch is init_stride * 250 bytes, so even at min_stride of 20,
350
+ # batch_size will be greater than 4kb (50 * 250kb = 12.5kb)
351
+ n += 1
352
+ id = n
353
+ username_data = "a" * 250
354
+ execute "insert into origin (id, common) values (#{id}, '#{username_data}')"
355
+ end
356
+ execute "COMMIT"
357
+ end
358
+
359
+ # reduce binlog size to 4kb
360
+ set_max_binlog_size(1024 * 4)
361
+ throttler = Lhm::Throttler::Time.new(stride: init_stride, min_stride_size: min_stride_size, backoff_reduction_factor: 0.9)
362
+
363
+ chunker = Lhm::Chunker.new(
364
+ @migration, connection, { throttler: throttler }
365
+ )
366
+
367
+ # start chunking
368
+ exception = assert_raises do
369
+ chunker.run
370
+ end
371
+
372
+ assert RuntimeError = exception.class
373
+ assert "Cannot reduce stride below #{min_stride_size}" == exception.message
374
+ end
309
375
  end
310
376
 
311
377
  def index_key(table_name, index_name)
@@ -315,4 +381,13 @@ describe Lhm::Chunker do
315
381
  index_name
316
382
  end
317
383
  end
384
+
385
+ def set_global_variable(name, value)
386
+ execute("set global #{name} = #{value}")
387
+ connection.reconnect!
388
+ end
389
+
390
+ def set_max_binlog_size(value)
391
+ set_global_variable('max_binlog_cache_size', value)
392
+ end
318
393
  end
@@ -111,6 +111,66 @@ describe Lhm::Throttler do
111
111
  end
112
112
  end
113
113
 
114
+ describe 'when using backoff functionality' do
115
+ it 'should backoff by default amount' do
116
+ @mock.setup_throttler(:time_throttler, stride: 100)
117
+ @mock.throttler.backoff_stride
118
+ value(@mock.throttler.stride).must_equal 80
119
+ end
120
+
121
+ it 'should backoff by specified amount' do
122
+ @mock.setup_throttler(:time_throttler, backoff_reduction_factor: 0.5, stride: 100)
123
+ @mock.throttler.backoff_stride
124
+ value(@mock.throttler.stride).must_equal 50
125
+ end
126
+
127
+ it 'should throw an error when backoff exceeds limit' do
128
+ @mock.setup_throttler(:time_throttler, backoff_reduction_factor: 0.2, stride: 1000, min_stride_size: 900)
129
+ proc { @mock.throttler.backoff_stride }.must_raise RuntimeError
130
+ end
131
+
132
+ it 'should throw an error when backoff cannot be done anymore' do
133
+ @mock.setup_throttler(:time_throttler, backoff_reduction_factor: 0.2, stride: 1, min_stride_size: 1)
134
+ proc { @mock.throttler.backoff_stride }.must_raise RuntimeError
135
+ end
136
+
137
+ it 'should throw an error when backoff reduction factor is not less than one' do
138
+ assert_raises ArgumentError do
139
+ @mock.setup_throttler(:time_throttler, backoff_reduction_factor: 1)
140
+ end
141
+ end
142
+
143
+ it 'should throw an error when backoff reduction factor is not greater than zero' do
144
+ assert_raises ArgumentError do
145
+ @mock.setup_throttler(:time_throttler, backoff_reduction_factor: 0)
146
+ end
147
+ end
148
+
149
+ it 'should throw an error when backoff reduction factor is negative' do
150
+ assert_raises ArgumentError do
151
+ @mock.setup_throttler(:time_throttler, backoff_reduction_factor: -0.5)
152
+ end
153
+ end
154
+
155
+ it 'should throw an error when min_stride_size is not an integer' do
156
+ assert_raises ArgumentError do
157
+ @mock.setup_throttler(:time_throttler, min_stride_size: 0.5)
158
+ end
159
+ end
160
+
161
+ it 'should throw an error when min_stride_size is not greater than 1' do
162
+ assert_raises ArgumentError do
163
+ @mock.setup_throttler(:time_throttler, min_stride_size: -12)
164
+ end
165
+ end
166
+
167
+ it 'should throw an error when min_stride_size is greater than inital stride size' do
168
+ assert_raises ArgumentError do
169
+ @mock.setup_throttler(:time_throttler, min_stride_size: 1000, stride: 500)
170
+ end
171
+ end
172
+ end
173
+
114
174
  describe '#throttler' do
115
175
 
116
176
  it 'returns the default Time based' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lhm-shopify
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.3.0
4
+ version: 4.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - SoundCloud
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2024-08-05 00:00:00.000000000 Z
15
+ date: 2024-08-20 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: retriable
@@ -213,6 +213,7 @@ files:
213
213
  - gemfiles/activerecord_7.1.gemfile
214
214
  - gemfiles/activerecord_7.1.gemfile.lock
215
215
  - gemfiles/activerecord_head.gemfile
216
+ - gemfiles/activerecord_head.gemfile.lock
216
217
  - lhm.gemspec
217
218
  - lib/lhm-shopify.rb
218
219
  - lib/lhm.rb
@@ -325,7 +326,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
325
326
  - !ruby/object:Gem::Version
326
327
  version: '0'
327
328
  requirements: []
328
- rubygems_version: 3.5.16
329
+ rubygems_version: 3.5.17
329
330
  signing_key:
330
331
  specification_version: 4
331
332
  summary: online schema changer for mysql