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.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +24 -15
  3. data/.gitignore +1 -6
  4. data/Appraisals +24 -0
  5. data/CHANGELOG.md +30 -0
  6. data/Gemfile.lock +66 -0
  7. data/README.md +55 -4
  8. data/Rakefile +11 -0
  9. data/dev.yml +31 -6
  10. data/docker-compose.yml +58 -0
  11. data/gemfiles/activerecord_5.2.gemfile +9 -0
  12. data/gemfiles/activerecord_5.2.gemfile.lock +65 -0
  13. data/gemfiles/activerecord_6.0.gemfile +7 -0
  14. data/gemfiles/activerecord_6.0.gemfile.lock +67 -0
  15. data/gemfiles/activerecord_6.1.gemfile +7 -0
  16. data/gemfiles/activerecord_6.1.gemfile.lock +66 -0
  17. data/gemfiles/activerecord_7.0.0.alpha2.gemfile +7 -0
  18. data/gemfiles/activerecord_7.0.0.alpha2.gemfile.lock +64 -0
  19. data/lhm.gemspec +7 -3
  20. data/lib/lhm/atomic_switcher.rb +5 -11
  21. data/lib/lhm/chunk_insert.rb +7 -10
  22. data/lib/lhm/chunker.rb +21 -10
  23. data/lib/lhm/cleanup/current.rb +9 -12
  24. data/lib/lhm/connection.rb +108 -0
  25. data/lib/lhm/entangler.rb +8 -13
  26. data/lib/lhm/invoker.rb +6 -4
  27. data/lib/lhm/locked_switcher.rb +2 -0
  28. data/lib/lhm/migrator.rb +2 -0
  29. data/lib/lhm/printer.rb +10 -6
  30. data/lib/lhm/proxysql_helper.rb +10 -0
  31. data/lib/lhm/sql_retry.rb +129 -10
  32. data/lib/lhm/throttler/slave_lag.rb +19 -2
  33. data/lib/lhm/version.rb +1 -1
  34. data/lib/lhm.rb +41 -16
  35. data/scripts/helpers/wait-for-dbs.sh +21 -0
  36. data/scripts/mysql/reader/create_replication.sql +10 -0
  37. data/scripts/mysql/writer/create_test_db.sql +1 -0
  38. data/scripts/mysql/writer/create_users.sql +6 -0
  39. data/scripts/proxysql/proxysql.cnf +117 -0
  40. data/spec/integration/atomic_switcher_spec.rb +53 -17
  41. data/spec/integration/chunk_insert_spec.rb +3 -2
  42. data/spec/integration/chunker_spec.rb +18 -16
  43. data/spec/integration/cleanup_spec.rb +49 -38
  44. data/spec/integration/database.yml +25 -0
  45. data/spec/integration/entangler_spec.rb +7 -5
  46. data/spec/integration/integration_helper.rb +25 -10
  47. data/spec/integration/lhm_spec.rb +114 -40
  48. data/spec/integration/lock_wait_timeout_spec.rb +2 -2
  49. data/spec/integration/locked_switcher_spec.rb +4 -4
  50. data/spec/integration/proxysql_spec.rb +34 -0
  51. data/spec/integration/sql_retry/db_connection_helper.rb +52 -0
  52. data/spec/integration/sql_retry/lock_wait_spec.rb +8 -6
  53. data/spec/integration/sql_retry/lock_wait_timeout_test_helper.rb +17 -4
  54. data/spec/integration/sql_retry/proxysql_helper.rb +22 -0
  55. data/spec/integration/sql_retry/retry_with_proxysql_spec.rb +109 -0
  56. data/spec/integration/table_spec.rb +11 -19
  57. data/spec/integration/toxiproxy_helper.rb +40 -0
  58. data/spec/test_helper.rb +24 -0
  59. data/spec/unit/atomic_switcher_spec.rb +4 -6
  60. data/spec/unit/chunk_insert_spec.rb +7 -2
  61. data/spec/unit/chunker_spec.rb +47 -42
  62. data/spec/unit/connection_spec.rb +111 -0
  63. data/spec/unit/entangler_spec.rb +85 -22
  64. data/spec/unit/intersection_spec.rb +4 -4
  65. data/spec/unit/lhm_spec.rb +23 -6
  66. data/spec/unit/locked_switcher_spec.rb +13 -18
  67. data/spec/unit/migrator_spec.rb +17 -19
  68. data/spec/unit/printer_spec.rb +14 -26
  69. data/spec/unit/sql_helper_spec.rb +8 -12
  70. data/spec/unit/table_spec.rb +5 -5
  71. data/spec/unit/throttler/slave_lag_spec.rb +14 -9
  72. data/spec/unit/throttler_spec.rb +12 -12
  73. data/spec/unit/unit_helper.rb +13 -0
  74. metadata +85 -14
  75. data/bin/.gitkeep +0 -0
  76. data/dbdeployer/config.json +0 -32
  77. data/dbdeployer/install.sh +0 -64
  78. data/gemfiles/ar-2.3_mysql.gemfile +0 -6
  79. data/gemfiles/ar-3.2_mysql.gemfile +0 -5
  80. data/gemfiles/ar-3.2_mysql2.gemfile +0 -5
  81. data/gemfiles/ar-4.0_mysql2.gemfile +0 -5
  82. data/gemfiles/ar-4.1_mysql2.gemfile +0 -5
  83. data/gemfiles/ar-4.2_mysql2.gemfile +0 -5
  84. data/gemfiles/ar-5.0_mysql2.gemfile +0 -5
@@ -29,7 +29,7 @@ describe Lhm::Chunker do
29
29
  Lhm::Chunker.new(@migration, connection, {throttler: throttler, printer: printer} ).run
30
30
 
31
31
  slave do
32
- count_all(@destination.name).must_equal(1)
32
+ value(count_all(@destination.name)).must_equal(1)
33
33
  end
34
34
 
35
35
  end
@@ -42,7 +42,7 @@ describe Lhm::Chunker do
42
42
  Lhm::Chunker.new(@migration, connection, {throttler: throttler, printer: printer} ).run
43
43
 
44
44
  slave do
45
- count_all(@destination.name).must_equal(2)
45
+ value(count_all(@destination.name)).must_equal(2)
46
46
  end
47
47
  end
48
48
 
@@ -58,7 +58,7 @@ describe Lhm::Chunker do
58
58
  Lhm::Chunker.new(migration, connection, {throttler: throttler, printer: printer} ).run
59
59
 
60
60
  slave do
61
- count_all(destination.name).must_equal(2)
61
+ value(count_all(destination.name)).must_equal(2)
62
62
  end
63
63
  end
64
64
 
@@ -128,7 +128,7 @@ describe Lhm::Chunker do
128
128
  Lhm::Chunker.new(@migration, connection, {throttler: throttler, printer: printer} ).run
129
129
 
130
130
  slave do
131
- count_all(@destination.name).must_equal(0)
131
+ value(count_all(@destination.name)).must_equal(0)
132
132
  end
133
133
 
134
134
  end
@@ -145,7 +145,7 @@ describe Lhm::Chunker do
145
145
  ).run
146
146
 
147
147
  slave do
148
- count_all(@destination.name).must_equal(23)
148
+ value(count_all(@destination.name)).must_equal(23)
149
149
  end
150
150
 
151
151
  printer.verify
@@ -161,7 +161,7 @@ describe Lhm::Chunker do
161
161
  ).run
162
162
 
163
163
  slave do
164
- count_all(@destination.name).must_equal(11)
164
+ value(count_all(@destination.name)).must_equal(11)
165
165
  end
166
166
 
167
167
  end
@@ -173,12 +173,15 @@ describe Lhm::Chunker do
173
173
  printer.expect(:notify, :return_value, [Integer, Integer])
174
174
  printer.expect(:end, :return_value, [])
175
175
 
176
+ Lhm::Throttler::Slave.any_instance.stubs(:slave_hosts).returns(['127.0.0.1'])
177
+ Lhm::Throttler::SlaveLag.any_instance.stubs(:master_slave_hosts).returns(['127.0.0.1'])
178
+
176
179
  Lhm::Chunker.new(
177
180
  @migration, connection, { throttler: Lhm::Throttler::SlaveLag.new(stride: 100), printer: printer }
178
181
  ).run
179
182
 
180
183
  slave do
181
- count_all(@destination.name).must_equal(23)
184
+ value(count_all(@destination.name)).must_equal(23)
182
185
  end
183
186
 
184
187
  printer.verify
@@ -203,7 +206,7 @@ describe Lhm::Chunker do
203
206
  assert_equal(Lhm::Throttler::SlaveLag::INITIAL_TIMEOUT * 2 * 2, throttler.timeout_seconds)
204
207
 
205
208
  slave do
206
- count_all(@destination.name).must_equal(15)
209
+ value(count_all(@destination.name)).must_equal(15)
207
210
  end
208
211
  end
209
212
 
@@ -215,17 +218,16 @@ describe Lhm::Chunker do
215
218
  printer.expects(:verify)
216
219
  printer.expects(:end)
217
220
 
218
- throttler = Lhm::Throttler::SlaveLag.new(stride: 10, allowed_lag: 0)
221
+ Lhm::Throttler::Slave.any_instance.stubs(:slave_hosts).returns(['127.0.0.1'])
222
+ Lhm::Throttler::SlaveLag.any_instance.stubs(:master_slave_hosts).returns(['127.0.0.1'])
219
223
 
220
- def throttler.slave_hosts
221
- ['127.0.0.1']
222
- end
224
+ throttler = Lhm::Throttler::SlaveLag.new(stride: 10, allowed_lag: 0)
223
225
 
224
226
  if master_slave_mode?
225
227
  def throttler.slave_connection(slave)
226
- config = ActiveRecord::Base.connection_pool.spec.config.dup
228
+ config = ActiveRecord::Base.connection_pool.db_config.configuration_hash.dup
227
229
  config[:host] = slave
228
- config[:port] = 3307
230
+ config[:port] = 33007
229
231
  ActiveRecord::Base.send('mysql2_connection', config)
230
232
  end
231
233
  end
@@ -238,7 +240,7 @@ describe Lhm::Chunker do
238
240
  assert_equal(0, throttler.send(:max_current_slave_lag))
239
241
 
240
242
  slave do
241
- count_all(@destination.name).must_equal(15)
243
+ value(count_all(@destination.name)).must_equal(15)
242
244
  end
243
245
 
244
246
  printer.verify
@@ -260,7 +262,7 @@ describe Lhm::Chunker do
260
262
  assert_match "Verification failed, aborting early", exception.message
261
263
 
262
264
  slave do
263
- count_all(@destination.name).must_equal(0)
265
+ value(count_all(@destination.name)).must_equal(0)
264
266
  end
265
267
  end
266
268
  end
@@ -31,12 +31,13 @@ describe Lhm, 'cleanup' do
31
31
 
32
32
  describe 'cleanup' do
33
33
  it 'should show temporary tables' do
34
- output = capture_stdout do
34
+ output = capture_stdout do |logger|
35
+ Lhm.logger = logger
35
36
  Lhm.cleanup
36
37
  end
37
- output.must_include('Would drop LHM backup tables')
38
- output.must_match(/lhma_[0-9_]*_users/)
39
- output.must_match(/lhma_[0-9_]*_permissions/)
38
+ value(output).must_include('Would drop LHM backup tables')
39
+ value(output).must_match(/lhma_[0-9_]*_users/)
40
+ value(output).must_match(/lhma_[0-9_]*_permissions/)
40
41
  end
41
42
 
42
43
  it 'should show temporary tables within range' do
@@ -48,12 +49,13 @@ describe Lhm, 'cleanup' do
48
49
  table_name2 = Lhm::Migration.new(table2, nil, nil, {}, Time.now - 172800).archive_name
49
50
  table_rename(:permissions, table_name2)
50
51
 
51
- output = capture_stdout do
52
+ output = capture_stdout do |logger|
53
+ Lhm.logger = logger
52
54
  Lhm.cleanup false, { :until => Time.now - 86400 }
53
55
  end
54
- output.must_include('Would drop LHM backup tables')
55
- output.must_match(/lhma_[0-9_]*_users/)
56
- output.must_match(/lhma_[0-9_]*_permissions/)
56
+ value(output).must_include('Would drop LHM backup tables')
57
+ value(output).must_match(/lhma_[0-9_]*_users/)
58
+ value(output).must_match(/lhma_[0-9_]*_permissions/)
57
59
  end
58
60
 
59
61
  it 'should exclude temporary tables outside range' do
@@ -65,30 +67,40 @@ describe Lhm, 'cleanup' do
65
67
  table_name2 = Lhm::Migration.new(table2, nil, nil, {}, Time.now).archive_name
66
68
  table_rename(:permissions, table_name2)
67
69
 
68
- output = capture_stdout do
70
+ output = capture_stdout do |logger|
71
+ Lhm.logger = logger
69
72
  Lhm.cleanup false, { :until => Time.now - 172800 }
70
73
  end
71
- output.must_include('Would drop LHM backup tables')
72
- output.wont_match(/lhma_[0-9_]*_users/)
73
- output.wont_match(/lhma_[0-9_]*_permissions/)
74
+ value(output).must_include('Would drop LHM backup tables')
75
+ value(output).wont_match(/lhma_[0-9_]*_users/)
76
+ value(output).wont_match(/lhma_[0-9_]*_permissions/)
74
77
  end
75
78
 
76
79
  it 'should show temporary triggers' do
77
- output = capture_stdout do
80
+ output = capture_stdout do |logger|
81
+ Lhm.logger = logger
78
82
  Lhm.cleanup
79
83
  end
80
- output.must_include('Would drop LHM triggers')
81
- output.must_include('lhmt_ins_users')
82
- output.must_include('lhmt_del_users')
83
- output.must_include('lhmt_upd_users')
84
- output.must_include('lhmt_ins_permissions')
85
- output.must_include('lhmt_del_permissions')
86
- output.must_include('lhmt_upd_permissions')
84
+ value(output).must_include('Would drop LHM triggers')
85
+ value(output).must_include('lhmt_ins_users')
86
+ value(output).must_include('lhmt_del_users')
87
+ value(output).must_include('lhmt_upd_users')
88
+ value(output).must_include('lhmt_ins_permissions')
89
+ value(output).must_include('lhmt_del_permissions')
90
+ value(output).must_include('lhmt_upd_permissions')
87
91
  end
88
92
 
89
93
  it 'should delete temporary tables' do
90
- Lhm.cleanup(true).must_equal(true)
91
- Lhm.cleanup.must_equal(true)
94
+ value(Lhm.cleanup(true)).must_equal(true)
95
+ value(Lhm.cleanup).must_equal(true)
96
+ end
97
+
98
+ it 'outputs deleted tables and triggers' do
99
+ output = capture_stdout do |logger|
100
+ Lhm.logger = logger
101
+ Lhm.cleanup(true)
102
+ end
103
+ value(output).must_include('Dropped triggers lhmt_ins_users, lhmt_upd_users, lhmt_del_users, lhmt_ins_permissions, lhmt_upd_permissions, lhmt_del_permissions')
92
104
  end
93
105
  end
94
106
 
@@ -96,27 +108,26 @@ describe Lhm, 'cleanup' do
96
108
  it 'should show lhmn table for the specified table only' do
97
109
  table_create(:permissions)
98
110
  table_rename(:permissions, 'lhmn_permissions')
99
- output = capture_stdout do
111
+ output = capture_stdout do |logger|
112
+ Lhm.logger = logger
100
113
  Lhm.cleanup_current_run(false, 'permissions')
101
- end.split("\n")
102
-
103
- assert_equal "The following DDLs would be executed:", output[0]
104
- assert_equal "drop trigger if exists lhmt_ins_permissions", output[1]
105
- assert_equal "drop trigger if exists lhmt_upd_permissions", output[2]
106
- assert_equal "drop trigger if exists lhmt_del_permissions", output[3]
107
- assert_match(/rename table lhmn_permissions to lhma_[0-9_]*_permissions_failed/, output[4])
108
- assert_equal 5, output.length
114
+ end
115
+
116
+ value(output).must_include("The following DDLs would be executed:")
117
+ value(output).must_include("drop trigger if exists lhmt_ins_permissions")
118
+ value(output).must_include("drop trigger if exists lhmt_upd_permissions")
119
+ value(output).must_include("drop trigger if exists lhmt_del_permissions")
109
120
  end
110
121
 
111
122
  it 'should show temporary triggers for the specified table only' do
112
- output = capture_stdout do
123
+ output = capture_stdout do |logger|
124
+ Lhm.logger = logger
113
125
  Lhm.cleanup_current_run(false, 'permissions')
114
- end.split("\n")
115
- assert_equal "The following DDLs would be executed:", output[0]
116
- assert_equal "drop trigger if exists lhmt_ins_permissions", output[1]
117
- assert_equal "drop trigger if exists lhmt_upd_permissions", output[2]
118
- assert_equal "drop trigger if exists lhmt_del_permissions", output[3]
119
- assert_equal 4, output.length
126
+ end
127
+ value(output).must_include("The following DDLs would be executed:")
128
+ value(output).must_include("drop trigger if exists lhmt_ins_permissions")
129
+ value(output).must_include("drop trigger if exists lhmt_upd_permissions")
130
+ value(output).must_include("drop trigger if exists lhmt_del_permissions")
120
131
  end
121
132
 
122
133
  it 'should delete temporary tables and triggers for the specified table only' do
@@ -0,0 +1,25 @@
1
+ master:
2
+ host: mysql-1
3
+ user: root
4
+ password: password
5
+ port: 33006
6
+ slave:
7
+ host: mysql-2
8
+ user: root
9
+ password: password
10
+ port: 33007
11
+ proxysql:
12
+ host: proxysql
13
+ user: root
14
+ password: password
15
+ port: 33005
16
+ master_toxic:
17
+ host: toxiproxy
18
+ user: root
19
+ password: password
20
+ port: 22220
21
+ proxysql_toxic:
22
+ host: toxiproxy
23
+ user: root
24
+ password: password
25
+ port: 22222
@@ -6,6 +6,7 @@ require File.expand_path(File.dirname(__FILE__)) + '/integration_helper'
6
6
  require 'lhm/table'
7
7
  require 'lhm/migration'
8
8
  require 'lhm/entangler'
9
+ require 'lhm/connection'
9
10
 
10
11
  describe Lhm::Entangler do
11
12
  include IntegrationHelper
@@ -17,7 +18,8 @@ describe Lhm::Entangler do
17
18
  @origin = table_create('origin')
18
19
  @destination = table_create('destination')
19
20
  @migration = Lhm::Migration.new(@origin, @destination)
20
- @entangler = Lhm::Entangler.new(@migration, connection)
21
+ @connection = Lhm::Connection.new(connection: connection)
22
+ @entangler = Lhm::Entangler.new(@migration, @connection)
21
23
  end
22
24
 
23
25
  it 'should replay inserts from origin into destination' do
@@ -26,7 +28,7 @@ describe Lhm::Entangler do
26
28
  end
27
29
 
28
30
  slave do
29
- count(:destination, 'common', 'inserted').must_equal(1)
31
+ value(count(:destination, 'common', 'inserted')).must_equal(1)
30
32
  end
31
33
  end
32
34
 
@@ -38,7 +40,7 @@ describe Lhm::Entangler do
38
40
  end
39
41
 
40
42
  slave do
41
- count(:destination, 'common', 'inserted').must_equal(0)
43
+ value(count(:destination, 'common', 'inserted')).must_equal(0)
42
44
  end
43
45
  end
44
46
 
@@ -49,7 +51,7 @@ describe Lhm::Entangler do
49
51
  end
50
52
 
51
53
  slave do
52
- count(:destination, 'common', 'updated').must_equal(1)
54
+ value(count(:destination, 'common', 'updated')).must_equal(1)
53
55
  end
54
56
  end
55
57
 
@@ -59,7 +61,7 @@ describe Lhm::Entangler do
59
61
  execute("insert into origin (common) values ('inserted')")
60
62
 
61
63
  slave do
62
- count(:destination, 'common', 'inserted').must_equal(0)
64
+ value(count(:destination, 'common', 'inserted')).must_equal(0)
63
65
  end
64
66
  end
65
67
  end
@@ -3,6 +3,7 @@
3
3
  require 'test_helper'
4
4
  require 'yaml'
5
5
  require 'active_support'
6
+ require 'logger'
6
7
 
7
8
  begin
8
9
  $db_config = YAML.load_file(File.expand_path(File.dirname(__FILE__)) + '/database.yml')
@@ -34,13 +35,21 @@ module IntegrationHelper
34
35
  @connection
35
36
  end
36
37
 
38
+ def connect_proxysql!
39
+ connect!(
40
+ '127.0.0.1',
41
+ $db_config['proxysql']['port'],
42
+ $db_config['proxysql']['user'],
43
+ $db_config['proxysql']['password'],
44
+ )
45
+ end
46
+
37
47
  def connect_master!
38
48
  connect!(
39
49
  '127.0.0.1',
40
50
  $db_config['master']['port'],
41
51
  $db_config['master']['user'],
42
52
  $db_config['master']['password'],
43
- $db_config['master']['socket']
44
53
  )
45
54
  end
46
55
 
@@ -50,28 +59,33 @@ module IntegrationHelper
50
59
  $db_config['slave']['port'],
51
60
  $db_config['slave']['user'],
52
61
  $db_config['slave']['password'],
53
- $db_config['slave']['socket']
54
62
  )
55
63
  end
56
64
 
57
- def connect!(hostname, port, user, password, socket)
58
- adapter = ar_conn(hostname, port, user, password, socket)
59
- Lhm.setup(adapter)
65
+ def connect_master_with_toxiproxy!
66
+ connect!(
67
+ '127.0.0.1',
68
+ $db_config['master_toxic']['port'],
69
+ $db_config['master_toxic']['user'],
70
+ $db_config['master_toxic']['password'])
71
+ end
72
+
73
+ def connect!(hostname, port, user, password)
74
+ Lhm.setup(ar_conn(hostname, port, user, password))
60
75
  unless defined?(@@cleaned_up)
61
76
  Lhm.cleanup(true)
62
77
  @@cleaned_up = true
63
78
  end
64
- @connection = adapter
79
+ @connection = Lhm.connection
65
80
  end
66
81
 
67
- def ar_conn(host, port, user, password, socket)
82
+ def ar_conn(host, port, user, password)
68
83
  ActiveRecord::Base.establish_connection(
69
84
  :adapter => 'mysql2',
70
85
  :host => host,
71
86
  :username => user,
72
87
  :port => port,
73
88
  :password => password,
74
- :socket => socket,
75
89
  :database => $db_name
76
90
  )
77
91
  ActiveRecord::Base.connection
@@ -121,7 +135,7 @@ module IntegrationHelper
121
135
  # Helps testing behaviour when another client locks the db
122
136
  def start_locking_thread(lock_for, queue, locking_query)
123
137
  Thread.new do
124
- conn = Mysql2::Client.new(host: '127.0.0.1', database: $db_name, user: 'root', port: 3306)
138
+ conn = new_mysql_connection
125
139
  conn.query('BEGIN')
126
140
  conn.query(locking_query)
127
141
  queue.push(true)
@@ -212,7 +226,8 @@ module IntegrationHelper
212
226
  def capture_stdout
213
227
  out = StringIO.new
214
228
  $stdout = out
215
- yield
229
+ logger = Logger.new($stdout)
230
+ yield logger
216
231
  return out.string
217
232
  ensure
218
233
  $stdout = ::STDOUT