lhm-shopify 4.4.0 → 4.4.2

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