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.
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