lhm-shopify 3.4.0 → 3.5.5
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 +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
|