lhm-shopify 3.4.0 → 3.5.5
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 +24 -15
- data/.gitignore +1 -6
- data/Appraisals +24 -0
- data/CHANGELOG.md +30 -0
- data/Gemfile.lock +66 -0
- data/README.md +55 -4
- data/Rakefile +11 -0
- data/dev.yml +31 -6
- data/docker-compose.yml +58 -0
- data/gemfiles/activerecord_5.2.gemfile +9 -0
- data/gemfiles/activerecord_5.2.gemfile.lock +65 -0
- data/gemfiles/activerecord_6.0.gemfile +7 -0
- data/gemfiles/activerecord_6.0.gemfile.lock +67 -0
- data/gemfiles/activerecord_6.1.gemfile +7 -0
- data/gemfiles/activerecord_6.1.gemfile.lock +66 -0
- data/gemfiles/activerecord_7.0.0.alpha2.gemfile +7 -0
- data/gemfiles/activerecord_7.0.0.alpha2.gemfile.lock +64 -0
- data/lhm.gemspec +7 -3
- data/lib/lhm/atomic_switcher.rb +5 -11
- data/lib/lhm/chunk_insert.rb +7 -10
- data/lib/lhm/chunker.rb +21 -10
- data/lib/lhm/cleanup/current.rb +9 -12
- data/lib/lhm/connection.rb +108 -0
- data/lib/lhm/entangler.rb +8 -13
- data/lib/lhm/invoker.rb +6 -4
- data/lib/lhm/locked_switcher.rb +2 -0
- data/lib/lhm/migrator.rb +2 -0
- data/lib/lhm/printer.rb +10 -6
- data/lib/lhm/proxysql_helper.rb +10 -0
- data/lib/lhm/sql_retry.rb +129 -10
- data/lib/lhm/throttler/slave_lag.rb +19 -2
- data/lib/lhm/version.rb +1 -1
- data/lib/lhm.rb +41 -16
- data/scripts/helpers/wait-for-dbs.sh +21 -0
- data/scripts/mysql/reader/create_replication.sql +10 -0
- data/scripts/mysql/writer/create_test_db.sql +1 -0
- data/scripts/mysql/writer/create_users.sql +6 -0
- data/scripts/proxysql/proxysql.cnf +117 -0
- data/spec/integration/atomic_switcher_spec.rb +53 -17
- data/spec/integration/chunk_insert_spec.rb +3 -2
- data/spec/integration/chunker_spec.rb +18 -16
- data/spec/integration/cleanup_spec.rb +49 -38
- data/spec/integration/database.yml +25 -0
- data/spec/integration/entangler_spec.rb +7 -5
- data/spec/integration/integration_helper.rb +25 -10
- data/spec/integration/lhm_spec.rb +114 -40
- data/spec/integration/lock_wait_timeout_spec.rb +2 -2
- data/spec/integration/locked_switcher_spec.rb +4 -4
- data/spec/integration/proxysql_spec.rb +34 -0
- data/spec/integration/sql_retry/db_connection_helper.rb +52 -0
- data/spec/integration/sql_retry/lock_wait_spec.rb +8 -6
- data/spec/integration/sql_retry/lock_wait_timeout_test_helper.rb +17 -4
- data/spec/integration/sql_retry/proxysql_helper.rb +22 -0
- data/spec/integration/sql_retry/retry_with_proxysql_spec.rb +109 -0
- data/spec/integration/table_spec.rb +11 -19
- data/spec/integration/toxiproxy_helper.rb +40 -0
- data/spec/test_helper.rb +24 -0
- data/spec/unit/atomic_switcher_spec.rb +4 -6
- data/spec/unit/chunk_insert_spec.rb +7 -2
- data/spec/unit/chunker_spec.rb +47 -42
- data/spec/unit/connection_spec.rb +111 -0
- data/spec/unit/entangler_spec.rb +85 -22
- data/spec/unit/intersection_spec.rb +4 -4
- data/spec/unit/lhm_spec.rb +23 -6
- data/spec/unit/locked_switcher_spec.rb +13 -18
- data/spec/unit/migrator_spec.rb +17 -19
- data/spec/unit/printer_spec.rb +14 -26
- data/spec/unit/sql_helper_spec.rb +8 -12
- data/spec/unit/table_spec.rb +5 -5
- data/spec/unit/throttler/slave_lag_spec.rb +14 -9
- data/spec/unit/throttler_spec.rb +12 -12
- data/spec/unit/unit_helper.rb +13 -0
- metadata +85 -14
- data/bin/.gitkeep +0 -0
- data/dbdeployer/config.json +0 -32
- data/dbdeployer/install.sh +0 -64
- data/gemfiles/ar-2.3_mysql.gemfile +0 -6
- data/gemfiles/ar-3.2_mysql.gemfile +0 -5
- data/gemfiles/ar-3.2_mysql2.gemfile +0 -5
- data/gemfiles/ar-4.0_mysql2.gemfile +0 -5
- data/gemfiles/ar-4.1_mysql2.gemfile +0 -5
- data/gemfiles/ar-4.2_mysql2.gemfile +0 -5
- data/gemfiles/ar-5.0_mysql2.gemfile +0 -5
@@ -2,6 +2,7 @@
|
|
2
2
|
# Schmidt
|
3
3
|
|
4
4
|
require File.expand_path(File.dirname(__FILE__)) + '/integration_helper'
|
5
|
+
require 'integration/toxiproxy_helper'
|
5
6
|
|
6
7
|
describe Lhm do
|
7
8
|
include IntegrationHelper
|
@@ -17,7 +18,7 @@ describe Lhm do
|
|
17
18
|
end
|
18
19
|
|
19
20
|
slave do
|
20
|
-
table_read(:users).columns['logins'].must_equal({
|
21
|
+
value(table_read(:users).columns['logins']).must_equal({
|
21
22
|
:type => 'int(12)',
|
22
23
|
:is_nullable => 'YES',
|
23
24
|
:column_default => '0',
|
@@ -35,7 +36,7 @@ describe Lhm do
|
|
35
36
|
end
|
36
37
|
|
37
38
|
slave do
|
38
|
-
table_read(:custom_primary_key).columns['logins'].must_equal({
|
39
|
+
value(table_read(:custom_primary_key).columns['logins']).must_equal({
|
39
40
|
:type => 'int(12)',
|
40
41
|
:is_nullable => 'YES',
|
41
42
|
:column_default => '0',
|
@@ -53,7 +54,7 @@ describe Lhm do
|
|
53
54
|
end
|
54
55
|
|
55
56
|
slave do
|
56
|
-
table_read(:composite_primary_key).columns['logins'].must_equal({
|
57
|
+
value(table_read(:composite_primary_key).columns['logins']).must_equal({
|
57
58
|
:type => 'int(12)',
|
58
59
|
:is_nullable => 'YES',
|
59
60
|
:column_default => '0',
|
@@ -82,7 +83,7 @@ describe Lhm do
|
|
82
83
|
end
|
83
84
|
|
84
85
|
slave do
|
85
|
-
connection.primary_key('users').must_equal(['username', 'id'])
|
86
|
+
value(connection.primary_key('users')).must_equal(['username', 'id'])
|
86
87
|
end
|
87
88
|
end
|
88
89
|
|
@@ -104,7 +105,7 @@ describe Lhm do
|
|
104
105
|
|
105
106
|
it 'migrates the existing data' do
|
106
107
|
slave do
|
107
|
-
count_all(:permissions).must_equal(11)
|
108
|
+
value(count_all(:permissions)).must_equal(11)
|
108
109
|
end
|
109
110
|
end
|
110
111
|
end
|
@@ -120,7 +121,7 @@ describe Lhm do
|
|
120
121
|
|
121
122
|
it 'migrates all data' do
|
122
123
|
slave do
|
123
|
-
count_all(:permissions).must_equal(13)
|
124
|
+
value(count_all(:permissions)).must_equal(13)
|
124
125
|
end
|
125
126
|
end
|
126
127
|
end
|
@@ -132,7 +133,7 @@ describe Lhm do
|
|
132
133
|
end
|
133
134
|
|
134
135
|
slave do
|
135
|
-
table_read(:users).columns['logins'].must_equal({
|
136
|
+
value(table_read(:users).columns['logins']).must_equal({
|
136
137
|
:type => 'int(12)',
|
137
138
|
:is_nullable => 'YES',
|
138
139
|
:column_default => '0',
|
@@ -150,7 +151,7 @@ describe Lhm do
|
|
150
151
|
end
|
151
152
|
|
152
153
|
slave do
|
153
|
-
count_all(:users).must_equal(23)
|
154
|
+
value(count_all(:users)).must_equal(23)
|
154
155
|
end
|
155
156
|
end
|
156
157
|
|
@@ -170,7 +171,7 @@ describe Lhm do
|
|
170
171
|
end
|
171
172
|
|
172
173
|
slave do
|
173
|
-
index_on_columns?(:users, [:comment, :created_at]).must_equal(true)
|
174
|
+
value(index_on_columns?(:users, [:comment, :created_at])).must_equal(true)
|
174
175
|
end
|
175
176
|
end
|
176
177
|
|
@@ -180,7 +181,7 @@ describe Lhm do
|
|
180
181
|
end
|
181
182
|
|
182
183
|
slave do
|
183
|
-
index?(:users, :my_index_name).must_equal(true)
|
184
|
+
value(index?(:users, :my_index_name)).must_equal(true)
|
184
185
|
end
|
185
186
|
end
|
186
187
|
|
@@ -190,7 +191,7 @@ describe Lhm do
|
|
190
191
|
end
|
191
192
|
|
192
193
|
slave do
|
193
|
-
index_on_columns?(:users, :group).must_equal(true)
|
194
|
+
value(index_on_columns?(:users, :group)).must_equal(true)
|
194
195
|
end
|
195
196
|
end
|
196
197
|
|
@@ -200,7 +201,7 @@ describe Lhm do
|
|
200
201
|
end
|
201
202
|
|
202
203
|
slave do
|
203
|
-
index_on_columns?(:users, :comment, :unique).must_equal(true)
|
204
|
+
value(index_on_columns?(:users, :comment, :unique)).must_equal(true)
|
204
205
|
end
|
205
206
|
end
|
206
207
|
|
@@ -210,7 +211,7 @@ describe Lhm do
|
|
210
211
|
end
|
211
212
|
|
212
213
|
slave do
|
213
|
-
index_on_columns?(:users, [:username, :created_at]).must_equal(false)
|
214
|
+
value(index_on_columns?(:users, [:username, :created_at])).must_equal(false)
|
214
215
|
end
|
215
216
|
end
|
216
217
|
|
@@ -220,7 +221,7 @@ describe Lhm do
|
|
220
221
|
end
|
221
222
|
|
222
223
|
slave do
|
223
|
-
index?(:users, :index_with_a_custom_name).must_equal(false)
|
224
|
+
value(index?(:users, :index_with_a_custom_name)).must_equal(false)
|
224
225
|
end
|
225
226
|
end
|
226
227
|
|
@@ -230,7 +231,7 @@ describe Lhm do
|
|
230
231
|
end
|
231
232
|
|
232
233
|
slave do
|
233
|
-
index?(:users, :index_with_a_custom_name).must_equal(false)
|
234
|
+
value(index?(:users, :index_with_a_custom_name)).must_equal(false)
|
234
235
|
end
|
235
236
|
end
|
236
237
|
|
@@ -240,7 +241,7 @@ describe Lhm do
|
|
240
241
|
end
|
241
242
|
|
242
243
|
slave do
|
243
|
-
table_read(:users).columns['flag'].must_equal({
|
244
|
+
value(table_read(:users).columns['flag']).must_equal({
|
244
245
|
:type => 'tinyint(1)',
|
245
246
|
:is_nullable => 'YES',
|
246
247
|
:column_default => nil,
|
@@ -256,7 +257,7 @@ describe Lhm do
|
|
256
257
|
end
|
257
258
|
|
258
259
|
slave do
|
259
|
-
table_read(:users).columns['comment'].must_equal({
|
260
|
+
value(table_read(:users).columns['comment']).must_equal({
|
260
261
|
:type => 'varchar(20)',
|
261
262
|
:is_nullable => 'NO',
|
262
263
|
:column_default => 'none',
|
@@ -274,7 +275,7 @@ describe Lhm do
|
|
274
275
|
end
|
275
276
|
|
276
277
|
slave do
|
277
|
-
table_read(:small_table).columns['id'].must_equal({
|
278
|
+
value(table_read(:small_table).columns['id']).must_equal({
|
278
279
|
:type => 'int(5)',
|
279
280
|
:is_nullable => 'NO',
|
280
281
|
:column_default => nil,
|
@@ -295,7 +296,7 @@ describe Lhm do
|
|
295
296
|
slave do
|
296
297
|
table_data = table_read(:users)
|
297
298
|
assert_nil table_data.columns['username']
|
298
|
-
table_read(:users).columns['login'].must_equal({
|
299
|
+
value(table_read(:users).columns['login']).must_equal({
|
299
300
|
:type => 'varchar(255)',
|
300
301
|
:is_nullable => 'YES',
|
301
302
|
:column_default => nil,
|
@@ -305,7 +306,7 @@ describe Lhm do
|
|
305
306
|
|
306
307
|
result = select_one('SELECT login from users')
|
307
308
|
result = result['login'] if result.respond_to?(:has_key?)
|
308
|
-
result.must_equal('a user')
|
309
|
+
value(result).must_equal('a user')
|
309
310
|
end
|
310
311
|
end
|
311
312
|
|
@@ -320,7 +321,7 @@ describe Lhm do
|
|
320
321
|
slave do
|
321
322
|
table_data = table_read(:users)
|
322
323
|
assert_nil table_data.columns['group']
|
323
|
-
table_read(:users).columns['fnord'].must_equal({
|
324
|
+
value(table_read(:users).columns['fnord']).must_equal({
|
324
325
|
:type => 'varchar(255)',
|
325
326
|
:is_nullable => 'YES',
|
326
327
|
:column_default => 'Superfriends',
|
@@ -330,7 +331,7 @@ describe Lhm do
|
|
330
331
|
|
331
332
|
result = select_one('SELECT `fnord` from users')
|
332
333
|
result = result['fnord'] if result.respond_to?(:has_key?)
|
333
|
-
result.must_equal('Superfriends')
|
334
|
+
value(result).must_equal('Superfriends')
|
334
335
|
end
|
335
336
|
end
|
336
337
|
|
@@ -347,7 +348,7 @@ describe Lhm do
|
|
347
348
|
slave do
|
348
349
|
table_data = table_read(:users)
|
349
350
|
assert_nil table_data.columns['username']
|
350
|
-
table_read(:users).columns['user_name'].must_equal({
|
351
|
+
value(table_read(:users).columns['user_name']).must_equal({
|
351
352
|
:type => 'varchar(255)',
|
352
353
|
:is_nullable => 'YES',
|
353
354
|
:column_default => nil,
|
@@ -357,7 +358,7 @@ describe Lhm do
|
|
357
358
|
|
358
359
|
result = select_one('SELECT `user_name` from users')
|
359
360
|
result = result['user_name'] if result.respond_to?(:has_key?)
|
360
|
-
result.must_equal('a user')
|
361
|
+
value(result).must_equal('a user')
|
361
362
|
end
|
362
363
|
end
|
363
364
|
|
@@ -375,7 +376,7 @@ describe Lhm do
|
|
375
376
|
slave do
|
376
377
|
table_data = table_read(:users)
|
377
378
|
assert_nil table_data.columns['reference']
|
378
|
-
table_read(:users).columns['ref'].must_equal({
|
379
|
+
value(table_read(:users).columns['ref']).must_equal({
|
379
380
|
:type => 'int(11)',
|
380
381
|
:is_nullable => 'YES',
|
381
382
|
:column_default => nil,
|
@@ -385,7 +386,7 @@ describe Lhm do
|
|
385
386
|
|
386
387
|
result = select_one('SELECT `ref` from users')
|
387
388
|
result = result['ref'] if result.respond_to?(:has_key?)
|
388
|
-
result.must_equal(10)
|
389
|
+
value(result).must_equal(10)
|
389
390
|
end
|
390
391
|
end
|
391
392
|
|
@@ -402,7 +403,7 @@ describe Lhm do
|
|
402
403
|
slave do
|
403
404
|
table_data = table_read(:users)
|
404
405
|
assert_nil table_data.columns['group']
|
405
|
-
table_read(:users).columns['fnord'].must_equal({
|
406
|
+
value(table_read(:users).columns['fnord']).must_equal({
|
406
407
|
:type => 'varchar(255)',
|
407
408
|
:is_nullable => 'YES',
|
408
409
|
:column_default => nil,
|
@@ -412,7 +413,7 @@ describe Lhm do
|
|
412
413
|
|
413
414
|
result = select_one('SELECT `fnord` from users')
|
414
415
|
result = result['fnord'] if result.respond_to?(:has_key?)
|
415
|
-
result
|
416
|
+
assert_nil(result)
|
416
417
|
end
|
417
418
|
end
|
418
419
|
|
@@ -427,7 +428,7 @@ describe Lhm do
|
|
427
428
|
slave do
|
428
429
|
table_data = table_read(:users)
|
429
430
|
assert_nil table_data.columns['username']
|
430
|
-
table_read(:users).columns['user_name'].must_equal({
|
431
|
+
value(table_read(:users).columns['user_name']).must_equal({
|
431
432
|
:type => 'varchar(255)',
|
432
433
|
:is_nullable => 'YES',
|
433
434
|
:column_default => nil,
|
@@ -437,7 +438,7 @@ describe Lhm do
|
|
437
438
|
|
438
439
|
result = select_one('SELECT `user_name` from users')
|
439
440
|
result = result['user_name'] if result.respond_to?(:has_key?)
|
440
|
-
result.must_equal('a user')
|
441
|
+
value(result).must_equal('a user')
|
441
442
|
end
|
442
443
|
end
|
443
444
|
|
@@ -454,7 +455,7 @@ describe Lhm do
|
|
454
455
|
slave do
|
455
456
|
table_data = table_read(:users)
|
456
457
|
assert_nil table_data.columns['username']
|
457
|
-
table_read(:users).columns['user_name'].must_equal({
|
458
|
+
value(table_read(:users).columns['user_name']).must_equal({
|
458
459
|
:type => 'varchar(255)',
|
459
460
|
:is_nullable => 'NO',
|
460
461
|
:column_default => nil,
|
@@ -464,7 +465,7 @@ describe Lhm do
|
|
464
465
|
|
465
466
|
result = select_one('SELECT `user_name` from users')
|
466
467
|
result = result['user_name'] if result.respond_to?(:has_key?)
|
467
|
-
result.must_equal('a user')
|
468
|
+
value(result).must_equal('a user')
|
468
469
|
end
|
469
470
|
end
|
470
471
|
|
@@ -501,7 +502,7 @@ describe Lhm do
|
|
501
502
|
slave do
|
502
503
|
table_data = table_read(:users)
|
503
504
|
assert_nil table_data.columns['fnord']
|
504
|
-
table_read(:users).columns['group'].must_equal({
|
505
|
+
value(table_read(:users).columns['group']).must_equal({
|
505
506
|
:type => 'varchar(255)',
|
506
507
|
:is_nullable => 'YES',
|
507
508
|
:column_default => 'Superfriends',
|
@@ -525,11 +526,11 @@ describe Lhm do
|
|
525
526
|
end
|
526
527
|
|
527
528
|
slave do
|
528
|
-
table_read(:lines).columns.must_include 'by'
|
529
|
-
table_read(:lines).columns.wont_include 'lines'
|
530
|
-
index_on_columns?(:lines, ['between'], :unique).must_equal true
|
531
|
-
index_on_columns?(:lines, ['by']).must_equal false
|
532
|
-
count_all(:lines).must_equal(2)
|
529
|
+
value(table_read(:lines).columns).must_include 'by'
|
530
|
+
value(table_read(:lines).columns).wont_include 'lines'
|
531
|
+
value(index_on_columns?(:lines, ['between'], :unique)).must_equal true
|
532
|
+
value(index_on_columns?(:lines, ['by'])).must_equal false
|
533
|
+
value(count_all(:lines)).must_equal(2)
|
533
534
|
end
|
534
535
|
end
|
535
536
|
|
@@ -554,7 +555,7 @@ describe Lhm do
|
|
554
555
|
insert.join
|
555
556
|
|
556
557
|
slave do
|
557
|
-
count_all(:users).must_equal(60)
|
558
|
+
value(count_all(:users)).must_equal(60)
|
558
559
|
end
|
559
560
|
end
|
560
561
|
|
@@ -577,7 +578,80 @@ describe Lhm do
|
|
577
578
|
delete.join
|
578
579
|
|
579
580
|
slave do
|
580
|
-
count_all(:users).must_equal(40)
|
581
|
+
value(count_all(:users)).must_equal(40)
|
582
|
+
end
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
586
|
+
describe 'connection' do
|
587
|
+
include ToxiproxyHelper
|
588
|
+
|
589
|
+
before(:each) do
|
590
|
+
@logs = StringIO.new
|
591
|
+
Lhm.logger = Logger.new(@logs)
|
592
|
+
end
|
593
|
+
|
594
|
+
it " should not try to reconnect if reconnect_with_consistent_host is not provided" do
|
595
|
+
connect_master_with_toxiproxy!
|
596
|
+
|
597
|
+
table_create(:users)
|
598
|
+
100.times { |n| execute("insert into users set reference = '#{ n }'") }
|
599
|
+
|
600
|
+
assert_raises ActiveRecord::StatementInvalid do
|
601
|
+
Toxiproxy[:mysql_master].down do
|
602
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
603
|
+
t.ddl("ALTER TABLE #{t.name} CHANGE id id bigint (20) NOT NULL")
|
604
|
+
t.ddl("ALTER TABLE #{t.name} DROP PRIMARY KEY, ADD PRIMARY KEY (username, id)")
|
605
|
+
t.ddl("ALTER TABLE #{t.name} ADD INDEX (id)")
|
606
|
+
t.ddl("ALTER TABLE #{t.name} CHANGE id id bigint (20) NOT NULL AUTO_INCREMENT")
|
607
|
+
end
|
608
|
+
end
|
609
|
+
end
|
610
|
+
end
|
611
|
+
|
612
|
+
it "should reconnect if reconnect_with_consistent_host is true" do
|
613
|
+
connect_master_with_toxiproxy!
|
614
|
+
mysql_disabled = false
|
615
|
+
|
616
|
+
table_create(:users)
|
617
|
+
100.times { |n| execute("insert into users set reference = '#{ n }'") }
|
618
|
+
|
619
|
+
# Redeclare Lhm::ChunkInsert to use Hook to disable MySQL writer for 3 seconds before first insert
|
620
|
+
Lhm::ChunkInsert.class_eval do
|
621
|
+
extend AfterDo
|
622
|
+
|
623
|
+
before(:insert_and_return_count_of_rows_created) do
|
624
|
+
unless mysql_disabled
|
625
|
+
mysql_disabled = true
|
626
|
+
Thread.new do
|
627
|
+
Toxiproxy[:mysql_master].down do
|
628
|
+
sleep 3
|
629
|
+
end
|
630
|
+
end
|
631
|
+
end
|
632
|
+
end
|
633
|
+
|
634
|
+
# Need to call `#method_added` manually to have the hooks take into effect
|
635
|
+
method_added(:insert_and_return_count_of_rows_created)
|
636
|
+
end
|
637
|
+
|
638
|
+
Lhm.change_table(:users, atomic_switch: false, reconnect_with_consistent_host: true) do |t|
|
639
|
+
t.ddl("ALTER TABLE #{t.name} CHANGE id id bigint (20) NOT NULL")
|
640
|
+
t.ddl("ALTER TABLE #{t.name} DROP PRIMARY KEY, ADD PRIMARY KEY (username, id)")
|
641
|
+
t.ddl("ALTER TABLE #{t.name} ADD INDEX (id)")
|
642
|
+
t.ddl("ALTER TABLE #{t.name} CHANGE id id bigint (20) NOT NULL AUTO_INCREMENT")
|
643
|
+
end
|
644
|
+
|
645
|
+
log_lines = @logs.string.split("\n")
|
646
|
+
|
647
|
+
assert log_lines.one?{ |line| line.include?("Lost connection to MySQL, will retry to connect to same host")}
|
648
|
+
assert log_lines.one?{ |line| line.include?("LHM successfully reconnected to initial host")}
|
649
|
+
assert log_lines.one?{ |line| line.include?("100% complete")}
|
650
|
+
|
651
|
+
Lhm::ChunkInsert.remove_all_callbacks
|
652
|
+
|
653
|
+
slave do
|
654
|
+
value(count_all(:users)).must_equal(100)
|
581
655
|
end
|
582
656
|
end
|
583
657
|
end
|
@@ -24,7 +24,7 @@ describe Lhm do
|
|
24
24
|
session_innodb_lock_wait_timeout = connection.select_one("SHOW SESSION VARIABLES LIKE 'innodb_lock_wait_timeout'")['Value'].to_i
|
25
25
|
session_lock_wait_timeout = connection.select_one("SHOW SESSION VARIABLES LIKE 'lock_wait_timeout'")['Value'].to_i
|
26
26
|
|
27
|
-
session_lock_wait_timeout.must_equal global_lock_wait_timeout + Lhm::Invoker::LOCK_WAIT_TIMEOUT_DELTA
|
28
|
-
session_innodb_lock_wait_timeout.must_equal global_innodb_lock_wait_timeout + Lhm::Invoker::LOCK_WAIT_TIMEOUT_DELTA
|
27
|
+
value(session_lock_wait_timeout).must_equal global_lock_wait_timeout + Lhm::Invoker::LOCK_WAIT_TIMEOUT_DELTA
|
28
|
+
value(session_innodb_lock_wait_timeout).must_equal global_innodb_lock_wait_timeout + Lhm::Invoker::LOCK_WAIT_TIMEOUT_DELTA
|
29
29
|
end
|
30
30
|
end
|
@@ -32,8 +32,8 @@ describe Lhm::LockedSwitcher do
|
|
32
32
|
switcher.run
|
33
33
|
|
34
34
|
slave do
|
35
|
-
data_source_exists?(@origin).must_equal true
|
36
|
-
table_read(@migration.archive_name).columns.keys.must_include 'origin'
|
35
|
+
value(data_source_exists?(@origin)).must_equal true
|
36
|
+
value(table_read(@migration.archive_name).columns.keys).must_include 'origin'
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
@@ -42,8 +42,8 @@ describe Lhm::LockedSwitcher do
|
|
42
42
|
switcher.run
|
43
43
|
|
44
44
|
slave do
|
45
|
-
data_source_exists?(@destination).must_equal false
|
46
|
-
table_read(@origin.name).columns.keys.must_include 'destination'
|
45
|
+
value(data_source_exists?(@destination)).must_equal false
|
46
|
+
value(table_read(@origin.name).columns.keys).must_include 'destination'
|
47
47
|
end
|
48
48
|
end
|
49
49
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
describe "ProxySQL integration" do
|
2
|
+
it "Should contact the writer" do
|
3
|
+
conn = Mysql2::Client.new(
|
4
|
+
host: '127.0.0.1',
|
5
|
+
username: "writer",
|
6
|
+
password: "password",
|
7
|
+
port: "33005",
|
8
|
+
)
|
9
|
+
|
10
|
+
assert_equal conn.query("SELECT @@global.hostname as host").each.first["host"], "mysql-1"
|
11
|
+
end
|
12
|
+
|
13
|
+
it "Should contact the reader" do
|
14
|
+
conn = Mysql2::Client.new(
|
15
|
+
host: '127.0.0.1',
|
16
|
+
username: "reader",
|
17
|
+
password: "password",
|
18
|
+
port: "33005",
|
19
|
+
)
|
20
|
+
|
21
|
+
assert_equal conn.query("SELECT @@global.hostname as host").each.first["host"], "mysql-2"
|
22
|
+
end
|
23
|
+
|
24
|
+
it "Should override default hostgroup from user if rule matches" do
|
25
|
+
conn = Mysql2::Client.new(
|
26
|
+
host: '127.0.0.1',
|
27
|
+
username: "reader",
|
28
|
+
password: "password",
|
29
|
+
port: "33005",
|
30
|
+
)
|
31
|
+
|
32
|
+
assert_equal conn.query("SELECT @@global.hostname as host #{Lhm::ProxySQLHelper::ANNOTATION}").each.first["host"], "mysql-1"
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'mysql2'
|
3
|
+
|
4
|
+
class DBConnectionHelper
|
5
|
+
|
6
|
+
DATABASE_CONFIG_FILE = "database.yml"
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def db_config
|
10
|
+
@db_config ||= YAML.load_file(File.expand_path(File.dirname(__FILE__)) + "/../#{DATABASE_CONFIG_FILE}")
|
11
|
+
end
|
12
|
+
|
13
|
+
def new_mysql_connection(role = :master, with_data = false, toxic = false)
|
14
|
+
|
15
|
+
key = role.to_s + toxic_postfix(toxic)
|
16
|
+
|
17
|
+
conn = ActiveRecord::Base.establish_connection(
|
18
|
+
:host => '127.0.0.1',
|
19
|
+
:adapter => "mysql2",
|
20
|
+
:username => db_config[key]['user'],
|
21
|
+
:password => db_config[key]['password'],
|
22
|
+
:database => test_db_name,
|
23
|
+
:port => db_config[key]['port']
|
24
|
+
)
|
25
|
+
conn = conn.connection
|
26
|
+
init_with_dummy_data(conn) if with_data
|
27
|
+
conn
|
28
|
+
end
|
29
|
+
|
30
|
+
def toxic_postfix(toxic)
|
31
|
+
toxic ? "_toxic" : ""
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_db_name
|
35
|
+
@test_db_name ||= "test"
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_table_name
|
39
|
+
@test_table_name ||= "test"
|
40
|
+
end
|
41
|
+
|
42
|
+
def init_with_dummy_data(conn)
|
43
|
+
conn.execute("DROP TABLE IF EXISTS #{test_table_name} ")
|
44
|
+
conn.execute("CREATE TABLE #{test_table_name} (id int)")
|
45
|
+
|
46
|
+
1.upto(9) do |i|
|
47
|
+
query = "INSERT INTO #{test_table_name} (id) VALUE (#{i})"
|
48
|
+
conn.execute(query)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -21,6 +21,8 @@ describe Lhm::SqlRetry do
|
|
21
21
|
|
22
22
|
# Assert our pre-conditions
|
23
23
|
assert_equal 2, @helper.record_count
|
24
|
+
|
25
|
+
Mysql2::Client.any_instance.stubs(:active?).returns(true)
|
24
26
|
end
|
25
27
|
|
26
28
|
after(:each) do
|
@@ -41,7 +43,7 @@ describe Lhm::SqlRetry do
|
|
41
43
|
|
42
44
|
exception = assert_raises { @helper.trigger_wait_lock }
|
43
45
|
|
44
|
-
|
46
|
+
assert_match /Lock wait timeout exceeded; try restarting transaction/, exception.message
|
45
47
|
assert_equal Mysql2::Error::TimeoutError, exception.class
|
46
48
|
|
47
49
|
assert_equal 2, @helper.record_count # no records inserted
|
@@ -53,12 +55,12 @@ describe Lhm::SqlRetry do
|
|
53
55
|
it "successfully executes the SQL despite the errors encountered" do
|
54
56
|
# Start a thread to retry, once the lock is held, execute the block
|
55
57
|
@helper.with_waiting_lock do |waiting_connection|
|
56
|
-
sql_retry = Lhm::SqlRetry.new(waiting_connection, {
|
58
|
+
sql_retry = Lhm::SqlRetry.new(waiting_connection, retry_options: {
|
57
59
|
base_interval: 0.2, # first retry after 200ms
|
58
60
|
multiplier: 1, # subsequent retries wait 1x longer than first retry (no change)
|
59
61
|
tries: 3, # we only need 3 tries (including the first) for the scenario described below
|
60
62
|
rand_factor: 0 # do not introduce randomness to wait timer
|
61
|
-
})
|
63
|
+
}, reconnect_with_consistent_host: false)
|
62
64
|
|
63
65
|
# RetryTestHelper is configured to hold lock for 5 seconds and timeout after 2 seconds.
|
64
66
|
# Therefore the sequence of events will be:
|
@@ -96,12 +98,12 @@ describe Lhm::SqlRetry do
|
|
96
98
|
puts "*" * 64
|
97
99
|
# Start a thread to retry, once the lock is held, execute the block
|
98
100
|
@helper.with_waiting_lock do |waiting_connection|
|
99
|
-
sql_retry = Lhm::SqlRetry.new(waiting_connection, {
|
101
|
+
sql_retry = Lhm::SqlRetry.new(waiting_connection, retry_options: {
|
100
102
|
base_interval: 0.2, # first retry after 200ms
|
101
103
|
multiplier: 1, # subsequent retries wait 1x longer than first retry (no change)
|
102
104
|
tries: 2, # we need 3 tries (including the first) for the scenario described below, but we only get two...we will fail
|
103
105
|
rand_factor: 0 # do not introduce randomness to wait timer
|
104
|
-
})
|
106
|
+
}, reconnect_with_consistent_host: false)
|
105
107
|
|
106
108
|
# RetryTestHelper is configured to hold lock for 5 seconds and timeout after 2 seconds.
|
107
109
|
# Therefore the sequence of events will be:
|
@@ -116,7 +118,7 @@ describe Lhm::SqlRetry do
|
|
116
118
|
|
117
119
|
exception = assert_raises { @helper.trigger_wait_lock }
|
118
120
|
|
119
|
-
|
121
|
+
assert_match /Lock wait timeout exceeded; try restarting transaction/, exception.message
|
120
122
|
assert_equal Mysql2::Error::TimeoutError, exception.class
|
121
123
|
|
122
124
|
assert_equal 2, @helper.record_count # no records inserted
|
@@ -1,5 +1,7 @@
|
|
1
|
-
require '
|
1
|
+
require 'integration/integration_helper'
|
2
|
+
|
2
3
|
class LockWaitTimeoutTestHelper
|
4
|
+
|
3
5
|
def initialize(lock_duration:, innodb_lock_wait_timeout:)
|
4
6
|
# This connection will be used exclusively to setup the test,
|
5
7
|
# assert pre-conditions and assert post-conditions.
|
@@ -68,7 +70,7 @@ class LockWaitTimeoutTestHelper
|
|
68
70
|
|
69
71
|
def insert_records_at_ids(connection, ids)
|
70
72
|
ids.each do |id|
|
71
|
-
connection
|
73
|
+
mysql_exec(connection, "INSERT INTO #{test_table_name} (id) VALUES (#{id})")
|
72
74
|
end
|
73
75
|
end
|
74
76
|
|
@@ -79,11 +81,10 @@ class LockWaitTimeoutTestHelper
|
|
79
81
|
def new_mysql_connection
|
80
82
|
Mysql2::Client.new(
|
81
83
|
host: '127.0.0.1',
|
82
|
-
database: test_db_name,
|
83
84
|
username: db_config['master']['user'],
|
84
85
|
password: db_config['master']['password'],
|
85
86
|
port: db_config['master']['port'],
|
86
|
-
|
87
|
+
database: test_db_name
|
87
88
|
)
|
88
89
|
end
|
89
90
|
|
@@ -98,4 +99,16 @@ class LockWaitTimeoutTestHelper
|
|
98
99
|
def test_table_name
|
99
100
|
@test_table_name ||= "lock_wait"
|
100
101
|
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def mysql_exec(connection, statement)
|
106
|
+
if connection.class == Mysql2::Client
|
107
|
+
connection.query(statement)
|
108
|
+
elsif connection.class.to_s.include?("ActiveRecord")
|
109
|
+
connection.execute(statement)
|
110
|
+
else
|
111
|
+
raise StandardError.new("Unrecognized MySQL client")
|
112
|
+
end
|
113
|
+
end
|
101
114
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class ProxySQLHelper
|
2
|
+
class << self
|
3
|
+
# Flips the destination hostgroup for /maintenance:lhm/ from 0 (i.e. writer) to 1 (i.e. reader)
|
4
|
+
def with_lhm_hostgroup_flip
|
5
|
+
conn = Mysql2::Client.new(
|
6
|
+
host: '127.0.0.1',
|
7
|
+
username: "remote-admin",
|
8
|
+
password: "password",
|
9
|
+
port: "6032",
|
10
|
+
)
|
11
|
+
|
12
|
+
begin
|
13
|
+
conn.query("UPDATE mysql_query_rules SET destination_hostgroup=1 WHERE match_pattern=\"maintenance:lhm\"")
|
14
|
+
conn.query("LOAD MYSQL QUERY RULES TO RUNTIME;")
|
15
|
+
yield
|
16
|
+
ensure
|
17
|
+
conn.query("UPDATE mysql_query_rules SET destination_hostgroup=0 WHERE match_pattern=\"maintenance:lhm\"")
|
18
|
+
conn.query("LOAD MYSQL QUERY RULES TO RUNTIME;")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|