lhm-shopify 3.5.4 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +6 -6
- data/Appraisals +8 -13
- data/CHANGELOG.md +13 -0
- data/Gemfile.lock +22 -20
- data/README.md +14 -7
- data/dev.yml +12 -8
- data/docker-compose.yml +2 -0
- data/gemfiles/activerecord_6.0.gemfile +1 -1
- data/gemfiles/activerecord_6.0.gemfile.lock +25 -21
- data/gemfiles/activerecord_6.1.gemfile.lock +17 -13
- data/gemfiles/{activerecord_7.0.0.alpha2.gemfile → activerecord_7.0.gemfile} +1 -1
- data/gemfiles/{activerecord_7.0.0.alpha2.gemfile.lock → activerecord_7.0.gemfile.lock} +23 -19
- data/gemfiles/{activerecord_5.2.gemfile → activerecord_7.1.0.beta1.gemfile} +1 -3
- data/gemfiles/activerecord_7.1.0.beta1.gemfile.lock +81 -0
- data/lhm.gemspec +1 -1
- data/lib/lhm/sql_helper.rb +1 -1
- data/lib/lhm/sql_retry.rb +37 -47
- data/lib/lhm/throttler/replica_lag.rb +162 -0
- data/lib/lhm/throttler/slave_lag.rb +5 -155
- data/lib/lhm/throttler/threads_running.rb +3 -1
- data/lib/lhm/throttler.rb +7 -3
- data/lib/lhm/version.rb +1 -1
- data/spec/.lhm.example +1 -1
- data/spec/README.md +8 -9
- data/spec/integration/atomic_switcher_spec.rb +2 -2
- data/spec/integration/chunk_insert_spec.rb +2 -2
- data/spec/integration/chunker_spec.rb +33 -38
- data/spec/integration/database.yml +1 -1
- data/spec/integration/entangler_spec.rb +4 -4
- data/spec/integration/integration_helper.rb +12 -12
- data/spec/integration/lhm_spec.rb +41 -32
- data/spec/integration/locked_switcher_spec.rb +2 -2
- data/spec/integration/sql_retry/retry_with_proxysql_spec.rb +6 -5
- data/spec/integration/toxiproxy_helper.rb +1 -1
- data/spec/test_helper.rb +3 -0
- data/spec/unit/printer_spec.rb +2 -6
- data/spec/unit/sql_helper_spec.rb +2 -2
- data/spec/unit/throttler/{slave_lag_spec.rb → replica_lag_spec.rb} +79 -79
- data/spec/unit/throttler/threads_running_spec.rb +18 -0
- data/spec/unit/throttler_spec.rb +8 -8
- metadata +10 -9
- data/gemfiles/activerecord_5.2.gemfile.lock +0 -65
@@ -28,7 +28,7 @@ describe Lhm::Chunker do
|
|
28
28
|
|
29
29
|
Lhm::Chunker.new(@migration, connection, {throttler: throttler, printer: printer} ).run
|
30
30
|
|
31
|
-
|
31
|
+
replica do
|
32
32
|
value(count_all(@destination.name)).must_equal(1)
|
33
33
|
end
|
34
34
|
|
@@ -41,7 +41,7 @@ describe Lhm::Chunker do
|
|
41
41
|
|
42
42
|
Lhm::Chunker.new(@migration, connection, {throttler: throttler, printer: printer} ).run
|
43
43
|
|
44
|
-
|
44
|
+
replica do
|
45
45
|
value(count_all(@destination.name)).must_equal(2)
|
46
46
|
end
|
47
47
|
end
|
@@ -57,7 +57,7 @@ describe Lhm::Chunker do
|
|
57
57
|
|
58
58
|
Lhm::Chunker.new(migration, connection, {throttler: throttler, printer: printer} ).run
|
59
59
|
|
60
|
-
|
60
|
+
replica do
|
61
61
|
value(count_all(destination.name)).must_equal(2)
|
62
62
|
end
|
63
63
|
end
|
@@ -127,7 +127,7 @@ describe Lhm::Chunker do
|
|
127
127
|
|
128
128
|
Lhm::Chunker.new(@migration, connection, {throttler: throttler, printer: printer} ).run
|
129
129
|
|
130
|
-
|
130
|
+
replica do
|
131
131
|
value(count_all(@destination.name)).must_equal(0)
|
132
132
|
end
|
133
133
|
|
@@ -136,20 +136,17 @@ describe Lhm::Chunker do
|
|
136
136
|
it 'should copy 23 rows from origin to destination in one shot, regardless of the value of the id' do
|
137
137
|
23.times { |n| execute("insert into origin set id = '#{ n * n + 23 }'") }
|
138
138
|
|
139
|
-
printer =
|
140
|
-
printer.
|
141
|
-
printer.
|
139
|
+
printer = mock("printer")
|
140
|
+
printer.expects(:notify).with(kind_of(Integer), kind_of(Integer))
|
141
|
+
printer.expects(:end)
|
142
142
|
|
143
143
|
Lhm::Chunker.new(
|
144
144
|
@migration, connection, { throttler: throttler, printer: printer }
|
145
145
|
).run
|
146
146
|
|
147
|
-
|
147
|
+
replica do
|
148
148
|
value(count_all(@destination.name)).must_equal(23)
|
149
149
|
end
|
150
|
-
|
151
|
-
printer.verify
|
152
|
-
|
153
150
|
end
|
154
151
|
|
155
152
|
it 'should copy all the records of a table, even if the last chunk starts with the last record of it.' do
|
@@ -160,42 +157,40 @@ describe Lhm::Chunker do
|
|
160
157
|
@migration, connection, { throttler: Lhm::Throttler::Time.new(stride: 10), printer: printer }
|
161
158
|
).run
|
162
159
|
|
163
|
-
|
160
|
+
replica do
|
164
161
|
value(count_all(@destination.name)).must_equal(11)
|
165
162
|
end
|
166
163
|
|
167
164
|
end
|
168
165
|
|
169
|
-
it 'should copy 23 rows from origin to destination in one shot with
|
166
|
+
it 'should copy 23 rows from origin to destination in one shot with replica lag based throttler, regardless of the value of the id' do
|
170
167
|
23.times { |n| execute("insert into origin set id = '#{ 100000 + n * n + 23 }'") }
|
171
168
|
|
172
|
-
printer =
|
173
|
-
printer.
|
174
|
-
printer.
|
169
|
+
printer = mock("printer")
|
170
|
+
printer.expects(:notify).with(kind_of(Integer), kind_of(Integer))
|
171
|
+
printer.expects(:end)
|
175
172
|
|
176
|
-
Lhm::Throttler::
|
177
|
-
Lhm::Throttler::
|
173
|
+
Lhm::Throttler::Replica.any_instance.stubs(:replica_hosts).returns(['127.0.0.1'])
|
174
|
+
Lhm::Throttler::ReplicaLag.any_instance.stubs(:master_replica_hosts).returns(['127.0.0.1'])
|
178
175
|
|
179
176
|
Lhm::Chunker.new(
|
180
|
-
@migration, connection, { throttler: Lhm::Throttler::
|
177
|
+
@migration, connection, { throttler: Lhm::Throttler::ReplicaLag.new(stride: 100), printer: printer }
|
181
178
|
).run
|
182
179
|
|
183
|
-
|
180
|
+
replica do
|
184
181
|
value(count_all(@destination.name)).must_equal(23)
|
185
182
|
end
|
186
|
-
|
187
|
-
printer.verify
|
188
183
|
end
|
189
184
|
|
190
|
-
it 'should throttle work stride based on
|
185
|
+
it 'should throttle work stride based on replica lag' do
|
191
186
|
15.times { |n| execute("insert into origin set id = '#{ (n * n) + 1 }'") }
|
192
187
|
|
193
188
|
printer = mock()
|
194
189
|
printer.expects(:notify).with(instance_of(Integer), instance_of(Integer)).twice
|
195
190
|
printer.expects(:end)
|
196
191
|
|
197
|
-
throttler = Lhm::Throttler::
|
198
|
-
def throttler.
|
192
|
+
throttler = Lhm::Throttler::ReplicaLag.new(stride: 10, allowed_lag: 0)
|
193
|
+
def throttler.max_current_replica_lag
|
199
194
|
1
|
200
195
|
end
|
201
196
|
|
@@ -203,14 +198,14 @@ describe Lhm::Chunker do
|
|
203
198
|
@migration, connection, { throttler: throttler, printer: printer }
|
204
199
|
).run
|
205
200
|
|
206
|
-
assert_equal(Lhm::Throttler::
|
201
|
+
assert_equal(Lhm::Throttler::ReplicaLag::INITIAL_TIMEOUT * 2 * 2, throttler.timeout_seconds)
|
207
202
|
|
208
|
-
|
203
|
+
replica do
|
209
204
|
value(count_all(@destination.name)).must_equal(15)
|
210
205
|
end
|
211
206
|
end
|
212
207
|
|
213
|
-
it 'should detect a single
|
208
|
+
it 'should detect a single replica with no lag in the default configuration' do
|
214
209
|
15.times { |n| execute("insert into origin set id = '#{ (n * n) + 1 }'") }
|
215
210
|
|
216
211
|
printer = mock()
|
@@ -218,15 +213,15 @@ describe Lhm::Chunker do
|
|
218
213
|
printer.expects(:verify)
|
219
214
|
printer.expects(:end)
|
220
215
|
|
221
|
-
Lhm::Throttler::
|
222
|
-
Lhm::Throttler::
|
216
|
+
Lhm::Throttler::Replica.any_instance.stubs(:replica_hosts).returns(['127.0.0.1'])
|
217
|
+
Lhm::Throttler::ReplicaLag.any_instance.stubs(:master_replica_hosts).returns(['127.0.0.1'])
|
223
218
|
|
224
|
-
throttler = Lhm::Throttler::
|
219
|
+
throttler = Lhm::Throttler::ReplicaLag.new(stride: 10, allowed_lag: 0)
|
225
220
|
|
226
|
-
if
|
227
|
-
def throttler.
|
221
|
+
if master_replica_mode?
|
222
|
+
def throttler.replica_connection(replica)
|
228
223
|
config = ActiveRecord::Base.connection_pool.db_config.configuration_hash.dup
|
229
|
-
config[:host] =
|
224
|
+
config[:host] = replica
|
230
225
|
config[:port] = 33007
|
231
226
|
ActiveRecord::Base.send('mysql2_connection', config)
|
232
227
|
end
|
@@ -236,10 +231,10 @@ describe Lhm::Chunker do
|
|
236
231
|
@migration, connection, { throttler: throttler, printer: printer }
|
237
232
|
).run
|
238
233
|
|
239
|
-
assert_equal(Lhm::Throttler::
|
240
|
-
assert_equal(0, throttler.send(:
|
234
|
+
assert_equal(Lhm::Throttler::ReplicaLag::INITIAL_TIMEOUT, throttler.timeout_seconds)
|
235
|
+
assert_equal(0, throttler.send(:max_current_replica_lag))
|
241
236
|
|
242
|
-
|
237
|
+
replica do
|
243
238
|
value(count_all(@destination.name)).must_equal(15)
|
244
239
|
end
|
245
240
|
|
@@ -261,7 +256,7 @@ describe Lhm::Chunker do
|
|
261
256
|
|
262
257
|
assert_match "Verification failed, aborting early", exception.message
|
263
258
|
|
264
|
-
|
259
|
+
replica do
|
265
260
|
value(count_all(@destination.name)).must_equal(0)
|
266
261
|
end
|
267
262
|
end
|
@@ -27,7 +27,7 @@ describe Lhm::Entangler do
|
|
27
27
|
execute("insert into origin (common) values ('inserted')")
|
28
28
|
end
|
29
29
|
|
30
|
-
|
30
|
+
replica do
|
31
31
|
value(count(:destination, 'common', 'inserted')).must_equal(1)
|
32
32
|
end
|
33
33
|
end
|
@@ -39,7 +39,7 @@ describe Lhm::Entangler do
|
|
39
39
|
execute("delete from origin where common = 'inserted'")
|
40
40
|
end
|
41
41
|
|
42
|
-
|
42
|
+
replica do
|
43
43
|
value(count(:destination, 'common', 'inserted')).must_equal(0)
|
44
44
|
end
|
45
45
|
end
|
@@ -50,7 +50,7 @@ describe Lhm::Entangler do
|
|
50
50
|
execute("update origin set common = 'updated'")
|
51
51
|
end
|
52
52
|
|
53
|
-
|
53
|
+
replica do
|
54
54
|
value(count(:destination, 'common', 'updated')).must_equal(1)
|
55
55
|
end
|
56
56
|
end
|
@@ -60,7 +60,7 @@ describe Lhm::Entangler do
|
|
60
60
|
|
61
61
|
execute("insert into origin (common) values ('inserted')")
|
62
62
|
|
63
|
-
|
63
|
+
replica do
|
64
64
|
value(count(:destination, 'common', 'inserted')).must_equal(0)
|
65
65
|
end
|
66
66
|
end
|
@@ -53,12 +53,12 @@ module IntegrationHelper
|
|
53
53
|
)
|
54
54
|
end
|
55
55
|
|
56
|
-
def
|
56
|
+
def connect_replica!
|
57
57
|
connect!(
|
58
58
|
'127.0.0.1',
|
59
|
-
$db_config['
|
60
|
-
$db_config['
|
61
|
-
$db_config['
|
59
|
+
$db_config['replica']['port'],
|
60
|
+
$db_config['replica']['user'],
|
61
|
+
$db_config['replica']['password'],
|
62
62
|
)
|
63
63
|
end
|
64
64
|
|
@@ -113,12 +113,12 @@ module IntegrationHelper
|
|
113
113
|
end
|
114
114
|
end
|
115
115
|
|
116
|
-
def
|
117
|
-
if
|
118
|
-
|
116
|
+
def replica(&block)
|
117
|
+
if master_replica_mode?
|
118
|
+
connect_replica!
|
119
119
|
|
120
|
-
# need to wait for the
|
121
|
-
# check the master binlog position and wait for the
|
120
|
+
# need to wait for the replica to catch up. a better method would be to
|
121
|
+
# check the master binlog position and wait for the replica to catch up
|
122
122
|
# to that position.
|
123
123
|
sleep 1
|
124
124
|
else
|
@@ -127,7 +127,7 @@ module IntegrationHelper
|
|
127
127
|
|
128
128
|
yield block
|
129
129
|
|
130
|
-
if
|
130
|
+
if master_replica_mode?
|
131
131
|
connect_master!
|
132
132
|
end
|
133
133
|
end
|
@@ -215,8 +215,8 @@ module IntegrationHelper
|
|
215
215
|
# Environment
|
216
216
|
#
|
217
217
|
|
218
|
-
def
|
219
|
-
!!ENV['
|
218
|
+
def master_replica_mode?
|
219
|
+
!!ENV['MASTER_REPLICA']
|
220
220
|
end
|
221
221
|
|
222
222
|
#
|
@@ -17,7 +17,7 @@ describe Lhm do
|
|
17
17
|
t.add_column(:logins, "int(12) default '0'")
|
18
18
|
end
|
19
19
|
|
20
|
-
|
20
|
+
replica do
|
21
21
|
value(table_read(:users).columns['logins']).must_equal({
|
22
22
|
:type => 'int(12)',
|
23
23
|
:is_nullable => 'YES',
|
@@ -35,7 +35,7 @@ describe Lhm do
|
|
35
35
|
t.add_column(:logins, "int(12) default '0'")
|
36
36
|
end
|
37
37
|
|
38
|
-
|
38
|
+
replica do
|
39
39
|
value(table_read(:custom_primary_key).columns['logins']).must_equal({
|
40
40
|
:type => 'int(12)',
|
41
41
|
:is_nullable => 'YES',
|
@@ -53,7 +53,7 @@ describe Lhm do
|
|
53
53
|
t.add_column(:logins, "int(12) default '0'")
|
54
54
|
end
|
55
55
|
|
56
|
-
|
56
|
+
replica do
|
57
57
|
value(table_read(:composite_primary_key).columns['logins']).must_equal({
|
58
58
|
:type => 'int(12)',
|
59
59
|
:is_nullable => 'YES',
|
@@ -82,7 +82,7 @@ describe Lhm do
|
|
82
82
|
t.ddl("ALTER TABLE #{t.name} CHANGE id id bigint (20) NOT NULL AUTO_INCREMENT")
|
83
83
|
end
|
84
84
|
|
85
|
-
|
85
|
+
replica do
|
86
86
|
value(connection.primary_key('users')).must_equal(['username', 'id'])
|
87
87
|
end
|
88
88
|
end
|
@@ -104,7 +104,7 @@ describe Lhm do
|
|
104
104
|
describe 'when no additional data is inserted into the table' do
|
105
105
|
|
106
106
|
it 'migrates the existing data' do
|
107
|
-
|
107
|
+
replica do
|
108
108
|
value(count_all(:permissions)).must_equal(11)
|
109
109
|
end
|
110
110
|
end
|
@@ -120,7 +120,7 @@ describe Lhm do
|
|
120
120
|
end
|
121
121
|
|
122
122
|
it 'migrates all data' do
|
123
|
-
|
123
|
+
replica do
|
124
124
|
value(count_all(:permissions)).must_equal(13)
|
125
125
|
end
|
126
126
|
end
|
@@ -132,7 +132,7 @@ describe Lhm do
|
|
132
132
|
t.add_column(:logins, "INT(12) DEFAULT '0'")
|
133
133
|
end
|
134
134
|
|
135
|
-
|
135
|
+
replica do
|
136
136
|
value(table_read(:users).columns['logins']).must_equal({
|
137
137
|
:type => 'int(12)',
|
138
138
|
:is_nullable => 'YES',
|
@@ -150,7 +150,7 @@ describe Lhm do
|
|
150
150
|
t.add_column(:logins, "INT(12) DEFAULT '0'")
|
151
151
|
end
|
152
152
|
|
153
|
-
|
153
|
+
replica do
|
154
154
|
value(count_all(:users)).must_equal(23)
|
155
155
|
end
|
156
156
|
end
|
@@ -160,7 +160,7 @@ describe Lhm do
|
|
160
160
|
t.remove_column(:comment)
|
161
161
|
end
|
162
162
|
|
163
|
-
|
163
|
+
replica do
|
164
164
|
assert_nil table_read(:users).columns['comment']
|
165
165
|
end
|
166
166
|
end
|
@@ -170,7 +170,7 @@ describe Lhm do
|
|
170
170
|
t.add_index([:comment, :created_at])
|
171
171
|
end
|
172
172
|
|
173
|
-
|
173
|
+
replica do
|
174
174
|
value(index_on_columns?(:users, [:comment, :created_at])).must_equal(true)
|
175
175
|
end
|
176
176
|
end
|
@@ -180,7 +180,7 @@ describe Lhm do
|
|
180
180
|
t.add_index([:comment, :created_at], :my_index_name)
|
181
181
|
end
|
182
182
|
|
183
|
-
|
183
|
+
replica do
|
184
184
|
value(index?(:users, :my_index_name)).must_equal(true)
|
185
185
|
end
|
186
186
|
end
|
@@ -190,7 +190,7 @@ describe Lhm do
|
|
190
190
|
t.add_index(:group)
|
191
191
|
end
|
192
192
|
|
193
|
-
|
193
|
+
replica do
|
194
194
|
value(index_on_columns?(:users, :group)).must_equal(true)
|
195
195
|
end
|
196
196
|
end
|
@@ -200,7 +200,7 @@ describe Lhm do
|
|
200
200
|
t.add_unique_index(:comment)
|
201
201
|
end
|
202
202
|
|
203
|
-
|
203
|
+
replica do
|
204
204
|
value(index_on_columns?(:users, :comment, :unique)).must_equal(true)
|
205
205
|
end
|
206
206
|
end
|
@@ -210,7 +210,7 @@ describe Lhm do
|
|
210
210
|
t.remove_index([:username, :created_at])
|
211
211
|
end
|
212
212
|
|
213
|
-
|
213
|
+
replica do
|
214
214
|
value(index_on_columns?(:users, [:username, :created_at])).must_equal(false)
|
215
215
|
end
|
216
216
|
end
|
@@ -220,7 +220,7 @@ describe Lhm do
|
|
220
220
|
t.remove_index([:username, :group])
|
221
221
|
end
|
222
222
|
|
223
|
-
|
223
|
+
replica do
|
224
224
|
value(index?(:users, :index_with_a_custom_name)).must_equal(false)
|
225
225
|
end
|
226
226
|
end
|
@@ -230,17 +230,27 @@ describe Lhm do
|
|
230
230
|
t.remove_index(:irrelevant_column_name, :index_with_a_custom_name)
|
231
231
|
end
|
232
232
|
|
233
|
-
|
233
|
+
replica do
|
234
234
|
value(index?(:users, :index_with_a_custom_name)).must_equal(false)
|
235
235
|
end
|
236
236
|
end
|
237
237
|
|
238
|
+
it 'should add an index with column sizes' do
|
239
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
240
|
+
t.add_index(["username(6)", "group (10)", "comment (10)"])
|
241
|
+
end
|
242
|
+
|
243
|
+
replica do
|
244
|
+
value(index_on_columns?(:users, [:username, :group, :comment])).must_equal(true)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
238
248
|
it 'should apply a ddl statement' do
|
239
249
|
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
240
250
|
t.ddl('alter table %s add column flag tinyint(1)' % t.name)
|
241
251
|
end
|
242
252
|
|
243
|
-
|
253
|
+
replica do
|
244
254
|
value(table_read(:users).columns['flag']).must_equal({
|
245
255
|
:type => 'tinyint(1)',
|
246
256
|
:is_nullable => 'YES',
|
@@ -256,7 +266,7 @@ describe Lhm do
|
|
256
266
|
t.change_column(:comment, "varchar(20) DEFAULT 'none' NOT NULL")
|
257
267
|
end
|
258
268
|
|
259
|
-
|
269
|
+
replica do
|
260
270
|
value(table_read(:users).columns['comment']).must_equal({
|
261
271
|
:type => 'varchar(20)',
|
262
272
|
:is_nullable => 'NO',
|
@@ -274,7 +284,7 @@ describe Lhm do
|
|
274
284
|
t.change_column(:id, 'int(5)')
|
275
285
|
end
|
276
286
|
|
277
|
-
|
287
|
+
replica do
|
278
288
|
value(table_read(:small_table).columns['id']).must_equal({
|
279
289
|
:type => 'int(5)',
|
280
290
|
:is_nullable => 'NO',
|
@@ -293,7 +303,7 @@ describe Lhm do
|
|
293
303
|
t.rename_column(:username, :login)
|
294
304
|
end
|
295
305
|
|
296
|
-
|
306
|
+
replica do
|
297
307
|
table_data = table_read(:users)
|
298
308
|
assert_nil table_data.columns['username']
|
299
309
|
value(table_read(:users).columns['login']).must_equal({
|
@@ -318,7 +328,7 @@ describe Lhm do
|
|
318
328
|
t.rename_column(:group, :fnord)
|
319
329
|
end
|
320
330
|
|
321
|
-
|
331
|
+
replica do
|
322
332
|
table_data = table_read(:users)
|
323
333
|
assert_nil table_data.columns['group']
|
324
334
|
value(table_read(:users).columns['fnord']).must_equal({
|
@@ -345,7 +355,7 @@ describe Lhm do
|
|
345
355
|
t.rename_column(:username, :user_name)
|
346
356
|
end
|
347
357
|
|
348
|
-
|
358
|
+
replica do
|
349
359
|
table_data = table_read(:users)
|
350
360
|
assert_nil table_data.columns['username']
|
351
361
|
value(table_read(:users).columns['user_name']).must_equal({
|
@@ -373,7 +383,7 @@ describe Lhm do
|
|
373
383
|
t.rename_column(:reference, :ref)
|
374
384
|
end
|
375
385
|
|
376
|
-
|
386
|
+
replica do
|
377
387
|
table_data = table_read(:users)
|
378
388
|
assert_nil table_data.columns['reference']
|
379
389
|
value(table_read(:users).columns['ref']).must_equal({
|
@@ -400,7 +410,7 @@ describe Lhm do
|
|
400
410
|
t.rename_column(:group, :fnord)
|
401
411
|
end
|
402
412
|
|
403
|
-
|
413
|
+
replica do
|
404
414
|
table_data = table_read(:users)
|
405
415
|
assert_nil table_data.columns['group']
|
406
416
|
value(table_read(:users).columns['fnord']).must_equal({
|
@@ -425,7 +435,7 @@ describe Lhm do
|
|
425
435
|
t.rename_column(:username, :user_name)
|
426
436
|
end
|
427
437
|
|
428
|
-
|
438
|
+
replica do
|
429
439
|
table_data = table_read(:users)
|
430
440
|
assert_nil table_data.columns['username']
|
431
441
|
value(table_read(:users).columns['user_name']).must_equal({
|
@@ -452,7 +462,7 @@ describe Lhm do
|
|
452
462
|
t.rename_column(:username, :user_name)
|
453
463
|
end
|
454
464
|
|
455
|
-
|
465
|
+
replica do
|
456
466
|
table_data = table_read(:users)
|
457
467
|
assert_nil table_data.columns['username']
|
458
468
|
value(table_read(:users).columns['user_name']).must_equal({
|
@@ -499,7 +509,7 @@ describe Lhm do
|
|
499
509
|
end
|
500
510
|
end
|
501
511
|
|
502
|
-
|
512
|
+
replica do
|
503
513
|
table_data = table_read(:users)
|
504
514
|
assert_nil table_data.columns['fnord']
|
505
515
|
value(table_read(:users).columns['group']).must_equal({
|
@@ -525,7 +535,7 @@ describe Lhm do
|
|
525
535
|
t.remove_index('by')
|
526
536
|
end
|
527
537
|
|
528
|
-
|
538
|
+
replica do
|
529
539
|
value(table_read(:lines).columns).must_include 'by'
|
530
540
|
value(table_read(:lines).columns).wont_include 'lines'
|
531
541
|
value(index_on_columns?(:lines, ['between'], :unique)).must_equal true
|
@@ -554,7 +564,7 @@ describe Lhm do
|
|
554
564
|
|
555
565
|
insert.join
|
556
566
|
|
557
|
-
|
567
|
+
replica do
|
558
568
|
value(count_all(:users)).must_equal(60)
|
559
569
|
end
|
560
570
|
end
|
@@ -577,7 +587,7 @@ describe Lhm do
|
|
577
587
|
|
578
588
|
delete.join
|
579
589
|
|
580
|
-
|
590
|
+
replica do
|
581
591
|
value(count_all(:users)).must_equal(40)
|
582
592
|
end
|
583
593
|
end
|
@@ -645,13 +655,12 @@ describe Lhm do
|
|
645
655
|
log_lines = @logs.string.split("\n")
|
646
656
|
|
647
657
|
assert log_lines.one?{ |line| line.include?("Lost connection to MySQL, will retry to connect to same host")}
|
648
|
-
assert log_lines.any?{ |line| line.include?("Lost connection to MySQL server at 'reading initial communication packet'")}
|
649
658
|
assert log_lines.one?{ |line| line.include?("LHM successfully reconnected to initial host")}
|
650
659
|
assert log_lines.one?{ |line| line.include?("100% complete")}
|
651
660
|
|
652
661
|
Lhm::ChunkInsert.remove_all_callbacks
|
653
662
|
|
654
|
-
|
663
|
+
replica do
|
655
664
|
value(count_all(:users)).must_equal(100)
|
656
665
|
end
|
657
666
|
end
|
@@ -31,7 +31,7 @@ describe Lhm::LockedSwitcher do
|
|
31
31
|
switcher = Lhm::LockedSwitcher.new(@migration, connection)
|
32
32
|
switcher.run
|
33
33
|
|
34
|
-
|
34
|
+
replica do
|
35
35
|
value(data_source_exists?(@origin)).must_equal true
|
36
36
|
value(table_read(@migration.archive_name).columns.keys).must_include 'origin'
|
37
37
|
end
|
@@ -41,7 +41,7 @@ describe Lhm::LockedSwitcher do
|
|
41
41
|
switcher = Lhm::LockedSwitcher.new(@migration, connection)
|
42
42
|
switcher.run
|
43
43
|
|
44
|
-
|
44
|
+
replica do
|
45
45
|
value(data_source_exists?(@destination)).must_equal false
|
46
46
|
value(table_read(@origin.name).columns.keys).must_include 'destination'
|
47
47
|
end
|
@@ -38,7 +38,7 @@ describe Lhm::SqlRetry, "ProxiSQL tests for LHM retry" do
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
assert_equal Lhm::Error, e.class
|
41
|
-
assert_match(/LHM tried the reconnection procedure but failed.
|
41
|
+
assert_match(/LHM tried the reconnection procedure but failed. Aborting/, e.message)
|
42
42
|
end
|
43
43
|
|
44
44
|
it "Will retry until connection is achieved" do
|
@@ -61,6 +61,7 @@ describe Lhm::SqlRetry, "ProxiSQL tests for LHM retry" do
|
|
61
61
|
it "Will abort if new writer is not same host" do
|
62
62
|
# The hostname will be constant before the blip
|
63
63
|
Lhm::SqlRetry.any_instance.stubs(:hostname).returns("mysql-1").then.returns("mysql-2")
|
64
|
+
Lhm::SqlRetry.any_instance.stubs(:server_id).returns(1).then.returns(2)
|
64
65
|
|
65
66
|
# Need new instance for stub to take into effect
|
66
67
|
lhm_retry = Lhm::SqlRetry.new(@connection, retry_options: {},
|
@@ -76,12 +77,12 @@ describe Lhm::SqlRetry, "ProxiSQL tests for LHM retry" do
|
|
76
77
|
end
|
77
78
|
|
78
79
|
assert_equal e.class, Lhm::Error
|
79
|
-
assert_match(/LHM tried the reconnection procedure but failed.
|
80
|
+
assert_match(/LHM tried the reconnection procedure but failed. Aborting/, e.message)
|
80
81
|
|
81
82
|
logs = @logger.string.split("\n")
|
82
83
|
|
83
84
|
assert logs.first.include?("Lost connection to MySQL, will retry to connect to same host")
|
84
|
-
assert logs.last.include?("
|
85
|
+
assert logs.last.include?("Reconnected to wrong host. Started migration on: mysql-1 (server_id: 1), but reconnected to: mysql-2 (server_id: 2).")
|
85
86
|
end
|
86
87
|
|
87
88
|
it "Will abort if failover happens (mimicked with proxySQL)" do
|
@@ -98,11 +99,11 @@ describe Lhm::SqlRetry, "ProxiSQL tests for LHM retry" do
|
|
98
99
|
end
|
99
100
|
|
100
101
|
assert_equal e.class, Lhm::Error
|
101
|
-
assert_match(/LHM tried the reconnection procedure but failed.
|
102
|
+
assert_match(/LHM tried the reconnection procedure but failed. Aborting/, e.message)
|
102
103
|
|
103
104
|
logs = @logger.string.split("\n")
|
104
105
|
|
105
106
|
assert logs.first.include?("Lost connection to MySQL, will retry to connect to same host")
|
106
|
-
assert logs.last.include?("
|
107
|
+
assert logs.last.include?("Reconnected to wrong host. Started migration on: mysql-1 (server_id: 1), but reconnected to: mysql-2 (server_id: 2).")
|
107
108
|
end
|
108
109
|
end
|
@@ -7,7 +7,7 @@ module ToxiproxyHelper
|
|
7
7
|
def included(base)
|
8
8
|
Toxiproxy.reset
|
9
9
|
|
10
|
-
# listen on localhost, but toxiproxy is in a container itself, thus the upstream uses the
|
10
|
+
# listen on localhost, but toxiproxy is in a container itself, thus the upstream uses the Podman-Compose DNS
|
11
11
|
Toxiproxy.populate(
|
12
12
|
[
|
13
13
|
{
|
data/spec/test_helper.rb
CHANGED
@@ -28,6 +28,9 @@ logger = Logger.new STDOUT
|
|
28
28
|
logger.level = Logger::WARN
|
29
29
|
Lhm.logger = logger
|
30
30
|
|
31
|
+
# Want test to be efficient without having to wait the normal value of 120s
|
32
|
+
Lhm::SqlRetry::RECONNECT_RETRY_MAX_ITERATION = 4
|
33
|
+
|
31
34
|
def without_verbose(&block)
|
32
35
|
old_verbose, $VERBOSE = $VERBOSE, nil
|
33
36
|
yield
|
data/spec/unit/printer_spec.rb
CHANGED
@@ -70,15 +70,11 @@ describe Lhm::Printer do
|
|
70
70
|
end
|
71
71
|
|
72
72
|
it 'prints the dots' do
|
73
|
-
mock =
|
74
|
-
|
75
|
-
mock.expect(:write, :return_value, ['.'])
|
76
|
-
end
|
73
|
+
mock = mock("output")
|
74
|
+
mock.expects(:write).with('.').times(10)
|
77
75
|
|
78
76
|
@printer.instance_variable_set(:@output, mock)
|
79
77
|
10.times { @printer.notify }
|
80
|
-
|
81
|
-
mock.verify
|
82
78
|
end
|
83
79
|
|
84
80
|
end
|
@@ -22,7 +22,7 @@ describe Lhm::SqlHelper do
|
|
22
22
|
end
|
23
23
|
|
24
24
|
it 'should quote column names in index specification' do
|
25
|
-
value(Lhm::SqlHelper.idx_spec(['title(10)', 'album']))
|
26
|
-
.must_equal('`title`(10), `album`')
|
25
|
+
value(Lhm::SqlHelper.idx_spec(['title(10)', 'name (6)', 'album']))
|
26
|
+
.must_equal('`title`(10), `name`(6), `album`')
|
27
27
|
end
|
28
28
|
end
|