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 +4 -4
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +1 -1
- data/README.md +30 -1
- data/gemfiles/activerecord_6.1.gemfile.lock +1 -1
- data/gemfiles/activerecord_7.0.gemfile.lock +1 -1
- data/gemfiles/activerecord_7.1.gemfile.lock +1 -1
- data/gemfiles/activerecord_head.gemfile.lock +1 -1
- data/lib/lhm/chunker.rb +2 -6
- data/lib/lhm/migrator.rb +13 -8
- data/lib/lhm/throttler/backoff_reduction.rb +42 -0
- data/lib/lhm/throttler/replica_lag.rb +3 -0
- data/lib/lhm/throttler/threads_running.rb +3 -0
- data/lib/lhm/throttler/time.rb +2 -30
- data/lib/lhm/throttler.rb +1 -0
- data/lib/lhm/version.rb +1 -1
- data/spec/unit/migrator_spec.rb +30 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 938d28fc99ea36454247de34a03ff3a202f0892e11f590b0c41556de4e041ee4
|
4
|
+
data.tar.gz: e28790317276015c4ffaeb9150c7e2a5eeb3a3fad1713604bda670225c6e181b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
56
|
-
|
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
|
-
|
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
|
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
|
-
|
111
|
-
|
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
|
data/lib/lhm/throttler/time.rb
CHANGED
@@ -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
|
-
|
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
data/lib/lhm/version.rb
CHANGED
data/spec/unit/migrator_spec.rb
CHANGED
@@ -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.
|
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-
|
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.
|
330
|
+
rubygems_version: 3.5.18
|
330
331
|
signing_key:
|
331
332
|
specification_version: 4
|
332
333
|
summary: online schema changer for mysql
|