lhm-shopify 4.4.0 → 4.4.2

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: 9b9ff81249ed6289557c131a6b6183dd77b494af67b71cb4845e69628092801e
4
- data.tar.gz: ca4a71a59985f62685056833ad288a63ff33b0ca233a7a41571cb7f0963b096e
3
+ metadata.gz: 938d28fc99ea36454247de34a03ff3a202f0892e11f590b0c41556de4e041ee4
4
+ data.tar.gz: e28790317276015c4ffaeb9150c7e2a5eeb3a3fad1713604bda670225c6e181b
5
5
  SHA512:
6
- metadata.gz: ae9eedad45c7425785150927537a604084c60215c1bdfa796e7bc018fdddbaff2e431af97f5611d15ad5064ad9640f4af38c24f44712eb3567a66bba9f128038
7
- data.tar.gz: 67135252c34441946ff069a1c6283545656a60b77306f8e6c019d319f0a164fd17dd898a21fcbfeba5490a52598812a804de52ad5fa498b6eee77a7731bc49ae
6
+ metadata.gz: 6eeca296f8626d383bccc0978440f26dd05c7a4da002648101db578e553d4a407da4238e35cf7ee18a21e821d7c1f66a30c55bd665dff9863bdad80e3f6c60b9
7
+ data.tar.gz: 78b1799fd87375c9042b3340b40db4439702b007977632afe104800bd01b12aad34e44f9694b3d58e9a277a3a42a8785794d87c31d2b56c3a9b59f448b60eda4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Unreleased
2
2
 
3
+ # 4.4.2 (Sep, 2024)
4
+ * Allow caller to set the algorithm that will be used for DDL ALTER TABLE operations
5
+
6
+ # 4.4.1 (Aug, 2024)
7
+ * Extend max_binlog_cache_size exceeded error handling to all throttlers
8
+
3
9
  # 4.4.0 (Aug, 2024)
4
10
  * Add support for retrying chunks when running into max_binlog_cache_size exceeded error
5
11
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- lhm-shopify (4.4.0)
4
+ lhm-shopify (4.4.2)
5
5
  retriable (>= 3.0.0)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -207,6 +207,35 @@ Or to set that as default throttler, use the following (for instance in a Rails
207
207
  Lhm.setup_throttler(:threads_running_throttler)
208
208
  ```
209
209
 
210
+ ### Retrying chunks using Throttler
211
+
212
+ If chunks fail due to the MySQL `max_binlog_cache_size` being exceeded, the Chunker class will call the `backoff_stride` method on the throttler to reduce the stride size and retry the chunk. The default backoff factor is 0.2 (reducing the chunk size by 20% with each failure), and the minimum stride size is 1 (the stride can be reduced until each chunk is 1 row). Default values can be overridden (see below). To disable backoff, set `min_stride_size` equal to `stride_size`; this will throw an exception when `max_binlog_cache_size` is exceeded. Configuration options should be passed into the time throttler constructor or in the `throttler_options` of change_table when using a time throttler.
213
+
214
+ ```ruby
215
+ ## Configure on throttler instance
216
+ my_throttler = Lhm::Throttler::ThreadsRunning.new(stride: 2000, delay: 1, backoff_reduction_factor: 0.1, min_stride_size: 10)
217
+
218
+ Lhm.change_table :users, throttler: my_throttler do |m|
219
+ ...
220
+ end
221
+
222
+ ## Or pass as throttler options
223
+ Lhm.change_table :users, {
224
+ throttler: :time_throttler,
225
+ throttler_options: {
226
+ stride: 2000,
227
+ delay: 1,
228
+ backoff_reduction_factor: 0.1,
229
+ min_stride_size: 10
230
+ }
231
+ } do |t|
232
+ ...
233
+ end
234
+ ```
235
+
236
+ ### Custom Throttlers with retry functionality
237
+ If using your own throttler, ensure that the class definition includes the `Lhm::Throttler::BackoffReduction` to retry failed chunks.
238
+
210
239
  ## Table rename strategies
211
240
 
212
241
  There are two different table rename strategies available: `LockedSwitcher` and
@@ -311,7 +340,7 @@ open coverage/index.html
311
340
  ```
312
341
 
313
342
  ### Merging for a new version
314
- When creating a PR for a new version, make sure that th version has been bumped in `lib/lhm/version.rb`. Then run the following code snippet to ensure the everything is consistent, otherwise
343
+ When creating a PR for a new version, make sure that the version has been bumped in `lib/lhm/version.rb`. Then run the following code snippet to ensure the everything is consistent, otherwise
315
344
  the gem will not publish.
316
345
  ```bash
317
346
  bundle install
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- lhm-shopify (4.4.0)
4
+ lhm-shopify (4.4.2)
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.4.0)
4
+ lhm-shopify (4.4.2)
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.4.0)
4
+ lhm-shopify (4.4.2)
5
5
  retriable (>= 3.0.0)
6
6
 
7
7
  GEM
@@ -24,7 +24,7 @@ GIT
24
24
  PATH
25
25
  remote: ..
26
26
  specs:
27
- lhm-shopify (4.4.0)
27
+ lhm-shopify (4.4.2)
28
28
  retriable (>= 3.0.0)
29
29
 
30
30
  GEM
data/lib/lhm/chunker.rb CHANGED
@@ -37,10 +37,6 @@ module Lhm
37
37
  )
38
38
  end
39
39
 
40
- def handle_max_binlog_exceeded_error
41
- @throttler.backoff_stride
42
- end
43
-
44
40
  def execute
45
41
  @start_time = Time.now
46
42
 
@@ -54,9 +50,9 @@ module Lhm
54
50
  begin
55
51
  affected_rows = ChunkInsert.new(@migration, @connection, bottom, top, @retry_options).insert_and_return_count_of_rows_created
56
52
  rescue ActiveRecord::StatementInvalid => e
57
- if e.message.downcase.include?("transaction required more than 'max_binlog_cache_size' bytes of storage")
53
+ if e.message.downcase.include?("transaction required more than 'max_binlog_cache_size' bytes of storage") && @throttler.respond_to?(:backoff_stride)
58
54
  Lhm.logger.info("Encountered max_binlog_cache_size error, attempting to reduce stride size")
59
- handle_max_binlog_exceeded_error
55
+ @throttler.backoff_stride
60
56
  next
61
57
  else
62
58
  raise e
data/lib/lhm/migrator.rb CHANGED
@@ -32,14 +32,16 @@ module Lhm
32
32
  # end
33
33
  #
34
34
  # @param [String] statement SQL alter statement
35
+ # @param [String] algorithm Algorithm that will be used in the DDL operation
35
36
  # @note
36
37
  #
37
38
  # Don't write the table name directly into the statement. Use the #name
38
39
  # getter instead, because the alter statement will be executed against a
39
40
  # temporary table.
40
41
  #
41
- def ddl(statement)
42
- statements << statement
42
+ def ddl(statement, algorithm: nil)
43
+ full_statement = algorithm ? "#{statement}, ALGORITHM=#{algorithm}" : statement
44
+ statements << full_statement
43
45
  end
44
46
 
45
47
  # Add a column to a table
@@ -52,8 +54,9 @@ module Lhm
52
54
  #
53
55
  # @param [String] name Name of the column to add
54
56
  # @param [String] definition Valid SQL column definition
55
- def add_column(name, definition)
56
- ddl('alter table `%s` add column `%s` %s, ALGORITHM=INPLACE' % [@name, name, definition])
57
+ # @param [String] algorithm Algorithm that will be used in the DDL operation
58
+ def add_column(name, definition, algorithm: 'INPLACE')
59
+ ddl('alter table `%s` add column `%s` %s' % [@name, name, definition], algorithm:)
57
60
  end
58
61
 
59
62
  # Change an existing column to a new definition
@@ -84,7 +87,8 @@ module Lhm
84
87
  #
85
88
  # @param [String] old Name of the column to change
86
89
  # @param [String] nu New name to use for the column
87
- def rename_column(old, nu)
90
+ # @param [String] algorithm Algorithm that will be used in the DDL operation
91
+ def rename_column(old, nu, algorithm: 'INPLACE')
88
92
  col = @origin.columns[old.to_s]
89
93
 
90
94
  definition = col[:type]
@@ -94,7 +98,7 @@ module Lhm
94
98
  definition += " COMMENT #{@connection.quote(col[:comment])}" if col[:comment]
95
99
  definition += " COLLATE #{@connection.quote(col[:collate])}" if col[:collate]
96
100
 
97
- ddl('alter table `%s` change column `%s` `%s` %s, ALGORITHM=INPLACE' % [@name, old, nu, definition])
101
+ ddl('alter table `%s` change column `%s` `%s` %s' % [@name, old, nu, definition], algorithm:)
98
102
  @renames[old.to_s] = nu.to_s
99
103
  end
100
104
 
@@ -107,8 +111,9 @@ module Lhm
107
111
  # end
108
112
  #
109
113
  # @param [String] name Name of the column to delete
110
- def remove_column(name)
111
- ddl('alter table `%s` drop `%s`, ALGORITHM=INPLACE' % [@name, name])
114
+ # @param [String] algorithm Algorithm that will be used in the DDL operation
115
+ def remove_column(name, algorithm: 'INPLACE')
116
+ ddl('alter table `%s` drop `%s`' % [@name, name], algorithm:)
112
117
  end
113
118
 
114
119
  # Add an index to a table
@@ -0,0 +1,42 @@
1
+ module Lhm
2
+ module Throttler
3
+ module BackoffReduction
4
+ DEFAULT_BACKOFF_REDUCTION_FACTOR = 0.2
5
+ MIN_STRIDE_SIZE = 1
6
+
7
+ def initialize(options = {})
8
+ @backoff_reduction_factor = options[:backoff_reduction_factor] || DEFAULT_BACKOFF_REDUCTION_FACTOR
9
+ @min_stride_size = options[:min_stride_size] || MIN_STRIDE_SIZE
10
+
11
+ if @backoff_reduction_factor >= 1 || @backoff_reduction_factor <= 0
12
+ raise ArgumentError, 'backoff_reduction_factor must be between greater than 0, and less than 1'
13
+ end
14
+
15
+ if @min_stride_size < 1
16
+ raise ArgumentError, 'min_stride_size must be an integer greater than 0'
17
+ end
18
+
19
+ if !@min_stride_size.is_a?(Integer)
20
+ raise ArgumentError, 'min_stride_size must be an integer'
21
+ end
22
+
23
+ if @min_stride_size > @stride
24
+ raise ArgumentError, 'min_stride_size must be less than or equal to stride'
25
+ end
26
+ end
27
+
28
+ def backoff_stride
29
+ new_stride = (@stride * (1 - @backoff_reduction_factor)).to_i
30
+
31
+ if new_stride == @stride
32
+ raise RuntimeError, "Cannot backoff any further"
33
+ end
34
+
35
+ if new_stride < @min_stride_size
36
+ raise RuntimeError, "Cannot reduce stride below #{@min_stride_size}"
37
+ end
38
+ @stride = new_stride
39
+ end
40
+ end
41
+ end
42
+ end
@@ -13,6 +13,7 @@ module Lhm
13
13
 
14
14
  class ReplicaLag
15
15
  include Command
16
+ include BackoffReduction
16
17
 
17
18
  INITIAL_TIMEOUT = 0.1
18
19
  DEFAULT_STRIDE = 2_000
@@ -29,6 +30,8 @@ module Lhm
29
30
  @replicas = {}
30
31
  @get_config = options[:current_config]
31
32
  @check_only = options[:check_only]
33
+
34
+ super
32
35
  end
33
36
 
34
37
  def execute
@@ -2,6 +2,7 @@ module Lhm
2
2
  module Throttler
3
3
  class ThreadsRunning
4
4
  include Command
5
+ include BackoffReduction
5
6
 
6
7
  DEFAULT_STRIDE = 2_000
7
8
  DEFAULT_INITIAL_TIMEOUT = 0.1
@@ -17,6 +18,8 @@ module Lhm
17
18
  @timeout_seconds = @initial_timeout_seconds
18
19
  @healthy_range = options[:healthy_range] || DEFAULT_HEALTHY_RANGE
19
20
  @connection = options[:connection]
21
+
22
+ super
20
23
  end
21
24
 
22
25
  def threads_running
@@ -2,6 +2,7 @@ module Lhm
2
2
  module Throttler
3
3
  class Time
4
4
  include Command
5
+ include BackoffReduction
5
6
 
6
7
  DEFAULT_TIMEOUT = 0.1
7
8
  DEFAULT_STRIDE = 2_000
@@ -14,37 +15,8 @@ module Lhm
14
15
  def initialize(options = {})
15
16
  @timeout_seconds = options[:delay] || DEFAULT_TIMEOUT
16
17
  @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
18
 
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
19
+ super
48
20
  end
49
21
 
50
22
  def execute
data/lib/lhm/throttler.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'lhm/throttler/backoff_reduction'
1
2
  require 'lhm/throttler/time'
2
3
  require 'lhm/throttler/replica_lag'
3
4
  require 'lhm/throttler/slave_lag'
data/lib/lhm/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # Schmidt
3
3
 
4
4
  module Lhm
5
- VERSION = '4.4.0'
5
+ VERSION = '4.4.2'
6
6
  end
@@ -101,6 +101,14 @@ describe Lhm::Migrator do
101
101
  ])
102
102
  end
103
103
 
104
+ it 'should add a column using the passed algorithm' do
105
+ @creator.add_column('logins', 'INT(12)', algorithm: 'COPY')
106
+
107
+ value(@creator.statements).must_equal([
108
+ 'alter table `lhmn_alt` add column `logins` INT(12), ALGORITHM=COPY'
109
+ ])
110
+ end
111
+
104
112
  it 'should remove a column' do
105
113
  @creator.remove_column('logins')
106
114
 
@@ -109,6 +117,14 @@ describe Lhm::Migrator do
109
117
  ])
110
118
  end
111
119
 
120
+ it 'should remove a column using the passed algorithm' do
121
+ @creator.remove_column('logins', algorithm: 'COPY')
122
+
123
+ value(@creator.statements).must_equal([
124
+ 'alter table `lhmn_alt` drop `logins`, ALGORITHM=COPY'
125
+ ])
126
+ end
127
+
112
128
  it 'should change a column' do
113
129
  @creator.change_column('logins', 'INT(11)')
114
130
 
@@ -157,4 +173,18 @@ describe Lhm::Migrator do
157
173
  .must_equal('alter table `lhmn_alt` add column `last` VARCHAR(64), ALGORITHM=INPLACE')
158
174
  end
159
175
  end
176
+
177
+ describe 'multiple changes using the passed algorithm' do
178
+ it 'should add two columns' do
179
+ @creator.add_column('first', 'VARCHAR(64)', algorithm: 'COPY')
180
+ @creator.add_column('last', 'VARCHAR(64)', algorithm: 'COPY')
181
+ value(@creator.statements.length).must_equal(2)
182
+
183
+ value(@creator.statements[0])
184
+ .must_equal('alter table `lhmn_alt` add column `first` VARCHAR(64), ALGORITHM=COPY')
185
+
186
+ value(@creator.statements[1])
187
+ .must_equal('alter table `lhmn_alt` add column `last` VARCHAR(64), ALGORITHM=COPY')
188
+ end
189
+ end
160
190
  end
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.4.0
4
+ version: 4.4.2
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-20 00:00:00.000000000 Z
15
+ date: 2024-09-09 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: retriable
@@ -239,6 +239,7 @@ files:
239
239
  - lib/lhm/table_name.rb
240
240
  - lib/lhm/test_support.rb
241
241
  - lib/lhm/throttler.rb
242
+ - lib/lhm/throttler/backoff_reduction.rb
242
243
  - lib/lhm/throttler/replica_lag.rb
243
244
  - lib/lhm/throttler/slave_lag.rb
244
245
  - lib/lhm/throttler/threads_running.rb
@@ -326,7 +327,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
326
327
  - !ruby/object:Gem::Version
327
328
  version: '0'
328
329
  requirements: []
329
- rubygems_version: 3.5.17
330
+ rubygems_version: 3.5.18
330
331
  signing_key:
331
332
  specification_version: 4
332
333
  summary: online schema changer for mysql