lhm-shopify 3.5.5 → 4.1.0
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/.github/workflows/test.yml +20 -18
- data/Appraisals +8 -19
- data/CHANGELOG.md +16 -0
- data/Gemfile.lock +37 -20
- data/README.md +21 -14
- data/dev.yml +12 -8
- data/docker-compose-mysql-5.7.yml +1 -0
- data/docker-compose-mysql-8.0.yml +63 -0
- data/docker-compose.yml +5 -3
- data/gemfiles/activerecord_6.1.gemfile +1 -0
- data/gemfiles/activerecord_6.1.gemfile.lock +23 -13
- data/gemfiles/{activerecord_7.0.0.alpha2.gemfile → activerecord_7.0.gemfile} +2 -1
- data/gemfiles/{activerecord_7.0.0.alpha2.gemfile.lock → activerecord_7.0.gemfile.lock} +29 -19
- data/gemfiles/{activerecord_6.0.gemfile → activerecord_7.1.gemfile} +1 -1
- data/gemfiles/activerecord_7.1.gemfile.lock +83 -0
- data/lhm.gemspec +2 -1
- data/lib/lhm/atomic_switcher.rb +3 -3
- data/lib/lhm/chunker.rb +4 -4
- data/lib/lhm/connection.rb +9 -1
- data/lib/lhm/sql_helper.rb +1 -1
- data/lib/lhm/sql_retry.rb +36 -18
- data/lib/lhm/table.rb +3 -4
- data/lib/lhm/throttler/replica_lag.rb +166 -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/scripts/helpers/wait-for-dbs.sh +3 -3
- data/scripts/mysql/writer/create_users.sql +1 -1
- data/spec/.lhm.example +1 -1
- data/spec/README.md +8 -9
- data/spec/integration/atomic_switcher_spec.rb +6 -10
- data/spec/integration/chunk_insert_spec.rb +2 -2
- data/spec/integration/chunker_spec.rb +54 -44
- data/spec/integration/database.yml +4 -4
- data/spec/integration/entangler_spec.rb +4 -4
- data/spec/integration/integration_helper.rb +23 -15
- data/spec/integration/lhm_spec.rb +70 -44
- data/spec/integration/locked_switcher_spec.rb +2 -2
- data/spec/integration/proxysql_spec.rb +10 -10
- data/spec/integration/sql_retry/db_connection_helper.rb +2 -4
- data/spec/integration/sql_retry/lock_wait_spec.rb +7 -8
- data/spec/integration/sql_retry/lock_wait_timeout_test_helper.rb +18 -10
- data/spec/integration/sql_retry/proxysql_helper.rb +1 -1
- data/spec/integration/sql_retry/retry_with_proxysql_spec.rb +1 -2
- data/spec/integration/table_spec.rb +1 -1
- data/spec/integration/toxiproxy_helper.rb +1 -1
- data/spec/test_helper.rb +27 -3
- data/spec/unit/atomic_switcher_spec.rb +2 -2
- data/spec/unit/chunker_spec.rb +43 -43
- data/spec/unit/connection_spec.rb +2 -2
- data/spec/unit/entangler_spec.rb +14 -24
- 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} +84 -92
- data/spec/unit/throttler/threads_running_spec.rb +18 -0
- data/spec/unit/throttler_spec.rb +8 -8
- metadata +26 -12
- data/.travis.yml +0 -21
- data/gemfiles/activerecord_5.2.gemfile +0 -9
- data/gemfiles/activerecord_5.2.gemfile.lock +0 -65
- data/gemfiles/activerecord_6.0.gemfile.lock +0 -67
@@ -22,7 +22,7 @@ module IntegrationHelper
|
|
22
22
|
def self.included(base)
|
23
23
|
base.after(:each) do
|
24
24
|
cleanup_connection = new_mysql_connection
|
25
|
-
results =
|
25
|
+
results = DATABASE.query(cleanup_connection, "SELECT table_name FROM information_schema.tables WHERE table_schema = '#{$db_name}';")
|
26
26
|
table_names_for_cleanup = results.map { |row| "#{$db_name}." + row.values.first }
|
27
27
|
cleanup_connection.query("DROP TABLE IF EXISTS #{table_names_for_cleanup.join(', ')};") if table_names_for_cleanup.length > 0
|
28
28
|
end
|
@@ -35,6 +35,14 @@ module IntegrationHelper
|
|
35
35
|
@connection
|
36
36
|
end
|
37
37
|
|
38
|
+
def mysql_version
|
39
|
+
@mysql_version ||= begin
|
40
|
+
# This SQL returns a value of shape: X.Y.ZZ-AA-log
|
41
|
+
result = connection.query("SELECT VERSION()")
|
42
|
+
result.dig(0, 0).split("-", 2)[0]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
38
46
|
def connect_proxysql!
|
39
47
|
connect!(
|
40
48
|
'127.0.0.1',
|
@@ -53,12 +61,12 @@ module IntegrationHelper
|
|
53
61
|
)
|
54
62
|
end
|
55
63
|
|
56
|
-
def
|
64
|
+
def connect_replica!
|
57
65
|
connect!(
|
58
66
|
'127.0.0.1',
|
59
|
-
$db_config['
|
60
|
-
$db_config['
|
61
|
-
$db_config['
|
67
|
+
$db_config['replica']['port'],
|
68
|
+
$db_config['replica']['user'],
|
69
|
+
$db_config['replica']['password'],
|
62
70
|
)
|
63
71
|
end
|
64
72
|
|
@@ -81,7 +89,7 @@ module IntegrationHelper
|
|
81
89
|
|
82
90
|
def ar_conn(host, port, user, password)
|
83
91
|
ActiveRecord::Base.establish_connection(
|
84
|
-
:adapter =>
|
92
|
+
:adapter => DATABASE.adapter,
|
85
93
|
:host => host,
|
86
94
|
:username => user,
|
87
95
|
:port => port,
|
@@ -113,12 +121,12 @@ module IntegrationHelper
|
|
113
121
|
end
|
114
122
|
end
|
115
123
|
|
116
|
-
def
|
117
|
-
if
|
118
|
-
|
124
|
+
def replica(&block)
|
125
|
+
if master_replica_mode?
|
126
|
+
connect_replica!
|
119
127
|
|
120
|
-
# need to wait for the
|
121
|
-
# check the master binlog position and wait for the
|
128
|
+
# need to wait for the replica to catch up. a better method would be to
|
129
|
+
# check the master binlog position and wait for the replica to catch up
|
122
130
|
# to that position.
|
123
131
|
sleep 1
|
124
132
|
else
|
@@ -127,7 +135,7 @@ module IntegrationHelper
|
|
127
135
|
|
128
136
|
yield block
|
129
137
|
|
130
|
-
if
|
138
|
+
if master_replica_mode?
|
131
139
|
connect_master!
|
132
140
|
end
|
133
141
|
end
|
@@ -171,7 +179,7 @@ module IntegrationHelper
|
|
171
179
|
end
|
172
180
|
|
173
181
|
def new_mysql_connection(role='master')
|
174
|
-
|
182
|
+
DATABASE.client.new(
|
175
183
|
host: '127.0.0.1',
|
176
184
|
database: $db_name,
|
177
185
|
username: $db_config[role]['user'],
|
@@ -215,8 +223,8 @@ module IntegrationHelper
|
|
215
223
|
# Environment
|
216
224
|
#
|
217
225
|
|
218
|
-
def
|
219
|
-
!!ENV['
|
226
|
+
def master_replica_mode?
|
227
|
+
!!ENV['MASTER_REPLICA']
|
220
228
|
end
|
221
229
|
|
222
230
|
#
|
@@ -9,6 +9,10 @@ describe Lhm do
|
|
9
9
|
|
10
10
|
before(:each) { connect_master!; Lhm.cleanup(true) }
|
11
11
|
|
12
|
+
let(:collation) do
|
13
|
+
mysql_version.start_with?("8.0") ? "utf8mb3_general_ci" : "utf8_general_ci"
|
14
|
+
end
|
15
|
+
|
12
16
|
describe 'id column requirement' do
|
13
17
|
it 'should migrate the table when id is pk' do
|
14
18
|
table_create(:users)
|
@@ -17,9 +21,11 @@ describe Lhm do
|
|
17
21
|
t.add_column(:logins, "int(12) default '0'")
|
18
22
|
end
|
19
23
|
|
20
|
-
|
24
|
+
expected_type = mysql_version.start_with?("8.0") ? "int" : "int(12)"
|
25
|
+
|
26
|
+
replica do
|
21
27
|
value(table_read(:users).columns['logins']).must_equal({
|
22
|
-
:type =>
|
28
|
+
:type => expected_type,
|
23
29
|
:is_nullable => 'YES',
|
24
30
|
:column_default => '0',
|
25
31
|
:comment => '',
|
@@ -35,9 +41,11 @@ describe Lhm do
|
|
35
41
|
t.add_column(:logins, "int(12) default '0'")
|
36
42
|
end
|
37
43
|
|
38
|
-
|
44
|
+
expected_type = mysql_version.start_with?("8.0") ? "int" : "int(12)"
|
45
|
+
|
46
|
+
replica do
|
39
47
|
value(table_read(:custom_primary_key).columns['logins']).must_equal({
|
40
|
-
:type =>
|
48
|
+
:type => expected_type,
|
41
49
|
:is_nullable => 'YES',
|
42
50
|
:column_default => '0',
|
43
51
|
:comment => '',
|
@@ -53,9 +61,11 @@ describe Lhm do
|
|
53
61
|
t.add_column(:logins, "int(12) default '0'")
|
54
62
|
end
|
55
63
|
|
56
|
-
|
64
|
+
expected_type = mysql_version.start_with?("8.0") ? "int" : "int(12)"
|
65
|
+
|
66
|
+
replica do
|
57
67
|
value(table_read(:composite_primary_key).columns['logins']).must_equal({
|
58
|
-
:type =>
|
68
|
+
:type => expected_type,
|
59
69
|
:is_nullable => 'YES',
|
60
70
|
:column_default => '0',
|
61
71
|
:comment => '',
|
@@ -82,7 +92,7 @@ describe Lhm do
|
|
82
92
|
t.ddl("ALTER TABLE #{t.name} CHANGE id id bigint (20) NOT NULL AUTO_INCREMENT")
|
83
93
|
end
|
84
94
|
|
85
|
-
|
95
|
+
replica do
|
86
96
|
value(connection.primary_key('users')).must_equal(['username', 'id'])
|
87
97
|
end
|
88
98
|
end
|
@@ -104,7 +114,7 @@ describe Lhm do
|
|
104
114
|
describe 'when no additional data is inserted into the table' do
|
105
115
|
|
106
116
|
it 'migrates the existing data' do
|
107
|
-
|
117
|
+
replica do
|
108
118
|
value(count_all(:permissions)).must_equal(11)
|
109
119
|
end
|
110
120
|
end
|
@@ -120,7 +130,7 @@ describe Lhm do
|
|
120
130
|
end
|
121
131
|
|
122
132
|
it 'migrates all data' do
|
123
|
-
|
133
|
+
replica do
|
124
134
|
value(count_all(:permissions)).must_equal(13)
|
125
135
|
end
|
126
136
|
end
|
@@ -132,9 +142,11 @@ describe Lhm do
|
|
132
142
|
t.add_column(:logins, "INT(12) DEFAULT '0'")
|
133
143
|
end
|
134
144
|
|
135
|
-
|
145
|
+
expected_type = mysql_version.start_with?("8.0") ? "int" : "int(12)"
|
146
|
+
|
147
|
+
replica do
|
136
148
|
value(table_read(:users).columns['logins']).must_equal({
|
137
|
-
:type =>
|
149
|
+
:type => expected_type,
|
138
150
|
:is_nullable => 'YES',
|
139
151
|
:column_default => '0',
|
140
152
|
:comment => '',
|
@@ -150,7 +162,7 @@ describe Lhm do
|
|
150
162
|
t.add_column(:logins, "INT(12) DEFAULT '0'")
|
151
163
|
end
|
152
164
|
|
153
|
-
|
165
|
+
replica do
|
154
166
|
value(count_all(:users)).must_equal(23)
|
155
167
|
end
|
156
168
|
end
|
@@ -160,7 +172,7 @@ describe Lhm do
|
|
160
172
|
t.remove_column(:comment)
|
161
173
|
end
|
162
174
|
|
163
|
-
|
175
|
+
replica do
|
164
176
|
assert_nil table_read(:users).columns['comment']
|
165
177
|
end
|
166
178
|
end
|
@@ -170,7 +182,7 @@ describe Lhm do
|
|
170
182
|
t.add_index([:comment, :created_at])
|
171
183
|
end
|
172
184
|
|
173
|
-
|
185
|
+
replica do
|
174
186
|
value(index_on_columns?(:users, [:comment, :created_at])).must_equal(true)
|
175
187
|
end
|
176
188
|
end
|
@@ -180,7 +192,7 @@ describe Lhm do
|
|
180
192
|
t.add_index([:comment, :created_at], :my_index_name)
|
181
193
|
end
|
182
194
|
|
183
|
-
|
195
|
+
replica do
|
184
196
|
value(index?(:users, :my_index_name)).must_equal(true)
|
185
197
|
end
|
186
198
|
end
|
@@ -190,7 +202,7 @@ describe Lhm do
|
|
190
202
|
t.add_index(:group)
|
191
203
|
end
|
192
204
|
|
193
|
-
|
205
|
+
replica do
|
194
206
|
value(index_on_columns?(:users, :group)).must_equal(true)
|
195
207
|
end
|
196
208
|
end
|
@@ -200,7 +212,7 @@ describe Lhm do
|
|
200
212
|
t.add_unique_index(:comment)
|
201
213
|
end
|
202
214
|
|
203
|
-
|
215
|
+
replica do
|
204
216
|
value(index_on_columns?(:users, :comment, :unique)).must_equal(true)
|
205
217
|
end
|
206
218
|
end
|
@@ -210,7 +222,7 @@ describe Lhm do
|
|
210
222
|
t.remove_index([:username, :created_at])
|
211
223
|
end
|
212
224
|
|
213
|
-
|
225
|
+
replica do
|
214
226
|
value(index_on_columns?(:users, [:username, :created_at])).must_equal(false)
|
215
227
|
end
|
216
228
|
end
|
@@ -220,7 +232,7 @@ describe Lhm do
|
|
220
232
|
t.remove_index([:username, :group])
|
221
233
|
end
|
222
234
|
|
223
|
-
|
235
|
+
replica do
|
224
236
|
value(index?(:users, :index_with_a_custom_name)).must_equal(false)
|
225
237
|
end
|
226
238
|
end
|
@@ -230,17 +242,27 @@ describe Lhm do
|
|
230
242
|
t.remove_index(:irrelevant_column_name, :index_with_a_custom_name)
|
231
243
|
end
|
232
244
|
|
233
|
-
|
245
|
+
replica do
|
234
246
|
value(index?(:users, :index_with_a_custom_name)).must_equal(false)
|
235
247
|
end
|
236
248
|
end
|
237
249
|
|
250
|
+
it 'should add an index with column sizes' do
|
251
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
252
|
+
t.add_index(["username(6)", "group (10)", "comment (10)"])
|
253
|
+
end
|
254
|
+
|
255
|
+
replica do
|
256
|
+
value(index_on_columns?(:users, [:username, :group, :comment])).must_equal(true)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
238
260
|
it 'should apply a ddl statement' do
|
239
261
|
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
240
262
|
t.ddl('alter table %s add column flag tinyint(1)' % t.name)
|
241
263
|
end
|
242
264
|
|
243
|
-
|
265
|
+
replica do
|
244
266
|
value(table_read(:users).columns['flag']).must_equal({
|
245
267
|
:type => 'tinyint(1)',
|
246
268
|
:is_nullable => 'YES',
|
@@ -256,13 +278,13 @@ describe Lhm do
|
|
256
278
|
t.change_column(:comment, "varchar(20) DEFAULT 'none' NOT NULL")
|
257
279
|
end
|
258
280
|
|
259
|
-
|
281
|
+
replica do
|
260
282
|
value(table_read(:users).columns['comment']).must_equal({
|
261
283
|
:type => 'varchar(20)',
|
262
284
|
:is_nullable => 'NO',
|
263
285
|
:column_default => 'none',
|
264
286
|
:comment => '',
|
265
|
-
:collate =>
|
287
|
+
:collate => collation,
|
266
288
|
})
|
267
289
|
end
|
268
290
|
end
|
@@ -274,9 +296,11 @@ describe Lhm do
|
|
274
296
|
t.change_column(:id, 'int(5)')
|
275
297
|
end
|
276
298
|
|
277
|
-
|
299
|
+
expected_type = mysql_version.start_with?("8.0") ? "int" : "int(5)"
|
300
|
+
|
301
|
+
replica do
|
278
302
|
value(table_read(:small_table).columns['id']).must_equal({
|
279
|
-
:type =>
|
303
|
+
:type => expected_type,
|
280
304
|
:is_nullable => 'NO',
|
281
305
|
:column_default => nil,
|
282
306
|
:comment => '',
|
@@ -293,7 +317,7 @@ describe Lhm do
|
|
293
317
|
t.rename_column(:username, :login)
|
294
318
|
end
|
295
319
|
|
296
|
-
|
320
|
+
replica do
|
297
321
|
table_data = table_read(:users)
|
298
322
|
assert_nil table_data.columns['username']
|
299
323
|
value(table_read(:users).columns['login']).must_equal({
|
@@ -301,7 +325,7 @@ describe Lhm do
|
|
301
325
|
:is_nullable => 'YES',
|
302
326
|
:column_default => nil,
|
303
327
|
:comment => '',
|
304
|
-
:collate =>
|
328
|
+
:collate => collation,
|
305
329
|
})
|
306
330
|
|
307
331
|
result = select_one('SELECT login from users')
|
@@ -318,7 +342,7 @@ describe Lhm do
|
|
318
342
|
t.rename_column(:group, :fnord)
|
319
343
|
end
|
320
344
|
|
321
|
-
|
345
|
+
replica do
|
322
346
|
table_data = table_read(:users)
|
323
347
|
assert_nil table_data.columns['group']
|
324
348
|
value(table_read(:users).columns['fnord']).must_equal({
|
@@ -326,7 +350,7 @@ describe Lhm do
|
|
326
350
|
:is_nullable => 'YES',
|
327
351
|
:column_default => 'Superfriends',
|
328
352
|
:comment => '',
|
329
|
-
:collate =>
|
353
|
+
:collate => collation,
|
330
354
|
})
|
331
355
|
|
332
356
|
result = select_one('SELECT `fnord` from users')
|
@@ -345,7 +369,7 @@ describe Lhm do
|
|
345
369
|
t.rename_column(:username, :user_name)
|
346
370
|
end
|
347
371
|
|
348
|
-
|
372
|
+
replica do
|
349
373
|
table_data = table_read(:users)
|
350
374
|
assert_nil table_data.columns['username']
|
351
375
|
value(table_read(:users).columns['user_name']).must_equal({
|
@@ -373,11 +397,13 @@ describe Lhm do
|
|
373
397
|
t.rename_column(:reference, :ref)
|
374
398
|
end
|
375
399
|
|
376
|
-
|
400
|
+
expected_type = mysql_version.start_with?("8.0") ? "int" : "int(11)"
|
401
|
+
|
402
|
+
replica do
|
377
403
|
table_data = table_read(:users)
|
378
404
|
assert_nil table_data.columns['reference']
|
379
405
|
value(table_read(:users).columns['ref']).must_equal({
|
380
|
-
:type =>
|
406
|
+
:type => expected_type,
|
381
407
|
:is_nullable => 'YES',
|
382
408
|
:column_default => nil,
|
383
409
|
:comment => 'RefComment',
|
@@ -400,7 +426,7 @@ describe Lhm do
|
|
400
426
|
t.rename_column(:group, :fnord)
|
401
427
|
end
|
402
428
|
|
403
|
-
|
429
|
+
replica do
|
404
430
|
table_data = table_read(:users)
|
405
431
|
assert_nil table_data.columns['group']
|
406
432
|
value(table_read(:users).columns['fnord']).must_equal({
|
@@ -408,7 +434,7 @@ describe Lhm do
|
|
408
434
|
:is_nullable => 'YES',
|
409
435
|
:column_default => nil,
|
410
436
|
:comment => '',
|
411
|
-
:collate =>
|
437
|
+
:collate => collation,
|
412
438
|
})
|
413
439
|
|
414
440
|
result = select_one('SELECT `fnord` from users')
|
@@ -425,7 +451,7 @@ describe Lhm do
|
|
425
451
|
t.rename_column(:username, :user_name)
|
426
452
|
end
|
427
453
|
|
428
|
-
|
454
|
+
replica do
|
429
455
|
table_data = table_read(:users)
|
430
456
|
assert_nil table_data.columns['username']
|
431
457
|
value(table_read(:users).columns['user_name']).must_equal({
|
@@ -433,7 +459,7 @@ describe Lhm do
|
|
433
459
|
:is_nullable => 'YES',
|
434
460
|
:column_default => nil,
|
435
461
|
:comment => '',
|
436
|
-
:collate =>
|
462
|
+
:collate => collation,
|
437
463
|
})
|
438
464
|
|
439
465
|
result = select_one('SELECT `user_name` from users')
|
@@ -452,7 +478,7 @@ describe Lhm do
|
|
452
478
|
t.rename_column(:username, :user_name)
|
453
479
|
end
|
454
480
|
|
455
|
-
|
481
|
+
replica do
|
456
482
|
table_data = table_read(:users)
|
457
483
|
assert_nil table_data.columns['username']
|
458
484
|
value(table_read(:users).columns['user_name']).must_equal({
|
@@ -460,7 +486,7 @@ describe Lhm do
|
|
460
486
|
:is_nullable => 'NO',
|
461
487
|
:column_default => nil,
|
462
488
|
:comment => '',
|
463
|
-
:collate =>
|
489
|
+
:collate => collation,
|
464
490
|
})
|
465
491
|
|
466
492
|
result = select_one('SELECT `user_name` from users')
|
@@ -499,7 +525,7 @@ describe Lhm do
|
|
499
525
|
end
|
500
526
|
end
|
501
527
|
|
502
|
-
|
528
|
+
replica do
|
503
529
|
table_data = table_read(:users)
|
504
530
|
assert_nil table_data.columns['fnord']
|
505
531
|
value(table_read(:users).columns['group']).must_equal({
|
@@ -507,7 +533,7 @@ describe Lhm do
|
|
507
533
|
:is_nullable => 'YES',
|
508
534
|
:column_default => 'Superfriends',
|
509
535
|
:comment => '',
|
510
|
-
:collate =>
|
536
|
+
:collate => collation,
|
511
537
|
})
|
512
538
|
end
|
513
539
|
end
|
@@ -525,7 +551,7 @@ describe Lhm do
|
|
525
551
|
t.remove_index('by')
|
526
552
|
end
|
527
553
|
|
528
|
-
|
554
|
+
replica do
|
529
555
|
value(table_read(:lines).columns).must_include 'by'
|
530
556
|
value(table_read(:lines).columns).wont_include 'lines'
|
531
557
|
value(index_on_columns?(:lines, ['between'], :unique)).must_equal true
|
@@ -554,7 +580,7 @@ describe Lhm do
|
|
554
580
|
|
555
581
|
insert.join
|
556
582
|
|
557
|
-
|
583
|
+
replica do
|
558
584
|
value(count_all(:users)).must_equal(60)
|
559
585
|
end
|
560
586
|
end
|
@@ -577,7 +603,7 @@ describe Lhm do
|
|
577
603
|
|
578
604
|
delete.join
|
579
605
|
|
580
|
-
|
606
|
+
replica do
|
581
607
|
value(count_all(:users)).must_equal(40)
|
582
608
|
end
|
583
609
|
end
|
@@ -650,7 +676,7 @@ describe Lhm do
|
|
650
676
|
|
651
677
|
Lhm::ChunkInsert.remove_all_callbacks
|
652
678
|
|
653
|
-
|
679
|
+
replica do
|
654
680
|
value(count_all(:users)).must_equal(100)
|
655
681
|
end
|
656
682
|
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
|
@@ -1,34 +1,34 @@
|
|
1
1
|
describe "ProxySQL integration" do
|
2
2
|
it "Should contact the writer" do
|
3
|
-
conn =
|
3
|
+
conn = DATABASE.client.new(
|
4
4
|
host: '127.0.0.1',
|
5
5
|
username: "writer",
|
6
6
|
password: "password",
|
7
|
-
port: "
|
7
|
+
port: "13005",
|
8
8
|
)
|
9
9
|
|
10
|
-
assert_equal
|
10
|
+
assert_equal DATABASE.query(conn, "SELECT @@global.hostname as host").each.first["host"], "mysql-1"
|
11
11
|
end
|
12
12
|
|
13
13
|
it "Should contact the reader" do
|
14
|
-
conn =
|
14
|
+
conn = DATABASE.client.new(
|
15
15
|
host: '127.0.0.1',
|
16
16
|
username: "reader",
|
17
17
|
password: "password",
|
18
|
-
port: "
|
18
|
+
port: "13005",
|
19
19
|
)
|
20
20
|
|
21
|
-
assert_equal
|
21
|
+
assert_equal DATABASE.query(conn, "SELECT @@global.hostname as host").each.first["host"], "mysql-2"
|
22
22
|
end
|
23
23
|
|
24
24
|
it "Should override default hostgroup from user if rule matches" do
|
25
|
-
conn =
|
25
|
+
conn = DATABASE.client.new(
|
26
26
|
host: '127.0.0.1',
|
27
27
|
username: "reader",
|
28
28
|
password: "password",
|
29
|
-
port: "
|
29
|
+
port: "13005",
|
30
30
|
)
|
31
31
|
|
32
|
-
assert_equal
|
32
|
+
assert_equal DATABASE.query(conn, "SELECT @@global.hostname as host #{Lhm::ProxySQLHelper::ANNOTATION}").each.first["host"], "mysql-1"
|
33
33
|
end
|
34
|
-
end
|
34
|
+
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'yaml'
|
2
|
-
require 'mysql2'
|
3
2
|
|
4
3
|
class DBConnectionHelper
|
5
4
|
|
@@ -11,12 +10,11 @@ class DBConnectionHelper
|
|
11
10
|
end
|
12
11
|
|
13
12
|
def new_mysql_connection(role = :master, with_data = false, toxic = false)
|
14
|
-
|
15
13
|
key = role.to_s + toxic_postfix(toxic)
|
16
14
|
|
17
15
|
conn = ActiveRecord::Base.establish_connection(
|
18
16
|
:host => '127.0.0.1',
|
19
|
-
:adapter =>
|
17
|
+
:adapter => DATABASE.adapter,
|
20
18
|
:username => db_config[key]['user'],
|
21
19
|
:password => db_config[key]['password'],
|
22
20
|
:database => test_db_name,
|
@@ -49,4 +47,4 @@ class DBConnectionHelper
|
|
49
47
|
end
|
50
48
|
end
|
51
49
|
end
|
52
|
-
end
|
50
|
+
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'minitest/autorun'
|
2
|
-
require 'mysql2'
|
3
2
|
require 'integration/sql_retry/lock_wait_timeout_test_helper'
|
4
3
|
require 'lhm'
|
5
4
|
|
@@ -22,7 +21,7 @@ describe Lhm::SqlRetry do
|
|
22
21
|
# Assert our pre-conditions
|
23
22
|
assert_equal 2, @helper.record_count
|
24
23
|
|
25
|
-
|
24
|
+
DATABASE.client.any_instance.stubs(:active?).returns(true)
|
26
25
|
end
|
27
26
|
|
28
27
|
after(:each) do
|
@@ -43,8 +42,8 @@ describe Lhm::SqlRetry do
|
|
43
42
|
|
44
43
|
exception = assert_raises { @helper.trigger_wait_lock }
|
45
44
|
|
46
|
-
assert_match
|
47
|
-
assert_equal
|
45
|
+
assert_match Regexp.new("Lock wait timeout exceeded; try restarting transaction"), exception.message
|
46
|
+
assert_equal DATABASE.timeout_error, exception.class
|
48
47
|
|
49
48
|
assert_equal 2, @helper.record_count # no records inserted
|
50
49
|
puts "*" * 64
|
@@ -82,10 +81,10 @@ describe Lhm::SqlRetry do
|
|
82
81
|
logs = @logger.string.split("\n")
|
83
82
|
assert_equal 2, logs.length
|
84
83
|
|
85
|
-
assert logs.first.include?("
|
84
|
+
assert logs.first.include?("Lock wait timeout exceeded; try restarting transaction' - 1 tries")
|
86
85
|
assert logs.first.include?("0.2 seconds until the next try")
|
87
86
|
|
88
|
-
assert logs.last.include?("
|
87
|
+
assert logs.last.include?("Lock wait timeout exceeded; try restarting transaction' - 2 tries")
|
89
88
|
assert logs.last.include?("0.2 seconds until the next try")
|
90
89
|
end
|
91
90
|
|
@@ -118,8 +117,8 @@ describe Lhm::SqlRetry do
|
|
118
117
|
|
119
118
|
exception = assert_raises { @helper.trigger_wait_lock }
|
120
119
|
|
121
|
-
assert_match
|
122
|
-
assert_equal
|
120
|
+
assert_match Regexp.new("Lock wait timeout exceeded; try restarting transaction"), exception.message
|
121
|
+
assert_equal DATABASE.timeout_error, exception.class
|
123
122
|
|
124
123
|
assert_equal 2, @helper.record_count # no records inserted
|
125
124
|
puts "*" * 64
|
@@ -14,13 +14,21 @@ class LockWaitTimeoutTestHelper
|
|
14
14
|
|
15
15
|
@lock_duration = lock_duration
|
16
16
|
|
17
|
-
# While implementing this, I discovered that MySQL seems to have an off-by-one
|
18
|
-
# bug with the innodb_lock_wait_timeout. If you ask it to wait 2 seconds, it will wait 3.
|
19
|
-
# In order to avoid surprisingly the user, let's account for that here, but also
|
20
|
-
# guard against a case where we go below 1, the minimum value.
|
21
|
-
raise ArgumentError, "innodb_lock_wait_timeout must be greater than or equal to 2" unless innodb_lock_wait_timeout >= 2
|
22
17
|
raise ArgumentError, "innodb_lock_wait_timeout must be an integer" if innodb_lock_wait_timeout.class != Integer
|
23
|
-
|
18
|
+
|
19
|
+
result = DATABASE.query(@main_conn, "SELECT VERSION()")
|
20
|
+
mysql_version = result.to_a.dig(0, "VERSION()").split("-", 2)[0]
|
21
|
+
|
22
|
+
if mysql_version.start_with?("8")
|
23
|
+
@innodb_lock_wait_timeout = innodb_lock_wait_timeout
|
24
|
+
else
|
25
|
+
# While implementing this, I discovered that MySQL seems to have an off-by-one
|
26
|
+
# bug with the innodb_lock_wait_timeout. If you ask it to wait 2 seconds, it will wait 3.
|
27
|
+
# In order to avoid surprisingly the user, let's account for that here, but also
|
28
|
+
# guard against a case where we go below 1, the minimum value.
|
29
|
+
raise ArgumentError, "innodb_lock_wait_timeout must be greater than or equal to 2" unless innodb_lock_wait_timeout >= 2
|
30
|
+
@innodb_lock_wait_timeout = innodb_lock_wait_timeout - 1
|
31
|
+
end
|
24
32
|
|
25
33
|
@threads = []
|
26
34
|
@queue = Queue.new
|
@@ -51,7 +59,7 @@ class LockWaitTimeoutTestHelper
|
|
51
59
|
end
|
52
60
|
|
53
61
|
def record_count(connection = main_conn)
|
54
|
-
response = connection
|
62
|
+
response = mysql_exec(connection, "SELECT COUNT(id) FROM #{test_table_name}")
|
55
63
|
response.first.values.first
|
56
64
|
end
|
57
65
|
|
@@ -79,7 +87,7 @@ class LockWaitTimeoutTestHelper
|
|
79
87
|
attr_reader :main_conn, :lock_duration, :innodb_lock_wait_timeout
|
80
88
|
|
81
89
|
def new_mysql_connection
|
82
|
-
|
90
|
+
DATABASE.client.new(
|
83
91
|
host: '127.0.0.1',
|
84
92
|
username: db_config['master']['user'],
|
85
93
|
password: db_config['master']['password'],
|
@@ -103,8 +111,8 @@ class LockWaitTimeoutTestHelper
|
|
103
111
|
private
|
104
112
|
|
105
113
|
def mysql_exec(connection, statement)
|
106
|
-
if connection.class ==
|
107
|
-
|
114
|
+
if connection.class == DATABASE.client
|
115
|
+
DATABASE.query(connection, statement)
|
108
116
|
elsif connection.class.to_s.include?("ActiveRecord")
|
109
117
|
connection.execute(statement)
|
110
118
|
else
|
@@ -2,7 +2,7 @@ class ProxySQLHelper
|
|
2
2
|
class << self
|
3
3
|
# Flips the destination hostgroup for /maintenance:lhm/ from 0 (i.e. writer) to 1 (i.e. reader)
|
4
4
|
def with_lhm_hostgroup_flip
|
5
|
-
conn =
|
5
|
+
conn = DATABASE.client.new(
|
6
6
|
host: '127.0.0.1',
|
7
7
|
username: "remote-admin",
|
8
8
|
password: "password",
|