lhm-shopify 3.5.1 → 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/CHANGELOG.md +26 -0
- data/Gemfile.lock +1 -1
- data/README.md +14 -3
- data/Rakefile +6 -6
- data/dev.yml +5 -2
- data/gemfiles/activerecord_5.2.gemfile.lock +1 -1
- data/gemfiles/activerecord_6.0.gemfile.lock +1 -1
- data/gemfiles/activerecord_6.1.gemfile.lock +1 -1
- data/gemfiles/activerecord_7.0.0.alpha2.gemfile.lock +1 -1
- data/lib/lhm/atomic_switcher.rb +4 -3
- data/lib/lhm/chunk_insert.rb +6 -3
- data/lib/lhm/chunker.rb +6 -6
- data/lib/lhm/cleanup/current.rb +4 -1
- data/lib/lhm/connection.rb +33 -25
- data/lib/lhm/entangler.rb +5 -4
- data/lib/lhm/invoker.rb +5 -3
- data/lib/lhm/locked_switcher.rb +2 -0
- data/lib/lhm/proxysql_helper.rb +1 -1
- data/lib/lhm/sql_retry.rb +56 -60
- data/lib/lhm/version.rb +1 -1
- data/lib/lhm.rb +30 -24
- data/spec/integration/atomic_switcher_spec.rb +28 -17
- data/spec/integration/chunker_spec.rb +7 -5
- data/spec/integration/integration_helper.rb +4 -6
- data/spec/integration/lhm_spec.rb +3 -4
- data/spec/integration/proxysql_spec.rb +1 -1
- data/spec/integration/sql_retry/lock_wait_spec.rb +2 -2
- data/spec/integration/sql_retry/retry_with_proxysql_spec.rb +8 -7
- data/spec/test_helper.rb +3 -0
- data/spec/unit/chunker_spec.rb +44 -43
- data/spec/unit/connection_spec.rb +37 -12
- data/spec/unit/entangler_spec.rb +31 -9
- data/spec/unit/lhm_spec.rb +17 -0
- data/spec/unit/throttler/slave_lag_spec.rb +1 -1
- metadata +2 -2
data/lib/lhm/sql_retry.rb
CHANGED
@@ -18,6 +18,12 @@ module Lhm
|
|
18
18
|
class SqlRetry
|
19
19
|
RECONNECT_SUCCESSFUL_MESSAGE = "LHM successfully reconnected to initial host:"
|
20
20
|
CLOUDSQL_VERSION_COMMENT = "(Google)"
|
21
|
+
# Will retry for 120 seconds (approximately, since connecting takes time).
|
22
|
+
RECONNECT_RETRY_MAX_ITERATION = 120
|
23
|
+
RECONNECT_RETRY_INTERVAL = 1
|
24
|
+
# Will abort the LHM if it had to reconnect more than 25 times in a single run (indicator that there might be
|
25
|
+
# something wrong with the network and would be better to run the LHM at a later time).
|
26
|
+
RECONNECTION_MAXIMUM = 25
|
21
27
|
|
22
28
|
MYSQL_VAR_NAMES = {
|
23
29
|
hostname: "@@global.hostname",
|
@@ -25,30 +31,24 @@ module Lhm
|
|
25
31
|
version_comment: "@@version_comment",
|
26
32
|
}
|
27
33
|
|
28
|
-
|
29
|
-
class ReconnectToHostSuccessful < Lhm::Error; end
|
30
|
-
|
31
|
-
def initialize(connection, options: {}, reconnect_with_consistent_host: true)
|
34
|
+
def initialize(connection, retry_options: {}, reconnect_with_consistent_host: false)
|
32
35
|
@connection = connection
|
33
|
-
|
34
|
-
|
35
|
-
if (@reconnect_with_consistent_host = reconnect_with_consistent_host)
|
36
|
-
@initial_hostname = hostname
|
37
|
-
@initial_server_id = server_id
|
38
|
-
end
|
36
|
+
self.retry_config = retry_options
|
37
|
+
self.reconnect_with_consistent_host = reconnect_with_consistent_host
|
39
38
|
end
|
40
39
|
|
41
40
|
# Complete explanation of algorithm: https://github.com/Shopify/lhm/pull/112
|
42
|
-
def with_retries(
|
43
|
-
@log_prefix =
|
41
|
+
def with_retries(log_prefix: nil)
|
42
|
+
@log_prefix = log_prefix || "" # No prefix. Just logs
|
44
43
|
|
45
|
-
|
44
|
+
# Amount of time LHM had to reconnect. Aborting if more than RECONNECTION_MAXIMUM
|
45
|
+
reconnection_counter = 0
|
46
46
|
|
47
|
-
Retriable.retriable(retry_config) do
|
47
|
+
Retriable.retriable(@retry_config) do
|
48
48
|
# Using begin -> rescue -> end for Ruby 2.4 compatibility
|
49
49
|
begin
|
50
50
|
if @reconnect_with_consistent_host
|
51
|
-
raise Lhm::Error.new("
|
51
|
+
raise Lhm::Error.new("MySQL host has changed since the start of the LHM. Aborting to avoid data-loss") unless same_host_as_initial?
|
52
52
|
end
|
53
53
|
|
54
54
|
yield(@connection)
|
@@ -57,13 +57,36 @@ module Lhm
|
|
57
57
|
# The error will be raised the connection is still active (i.e. no need to reconnect) or if the connection is
|
58
58
|
# dead (i.e. not active) and @reconnect_with_host is false (i.e. instructed not to reconnect)
|
59
59
|
raise e if @connection.active? || (!@connection.active? && !@reconnect_with_consistent_host)
|
60
|
-
|
60
|
+
|
61
|
+
# Lhm could be stuck in a weird state where it loses connection, reconnects and re looses-connection instantly
|
62
|
+
# after, creating an infinite loop (because of the usage of `retry`). Hence, abort after 25 reconnections
|
63
|
+
raise Lhm::Error.new("LHM reached host reconnection max of #{RECONNECTION_MAXIMUM} times. " \
|
64
|
+
"Please try again later.") if reconnection_counter > RECONNECTION_MAXIMUM
|
65
|
+
|
66
|
+
reconnection_counter += 1
|
67
|
+
if reconnect_with_host_check!
|
68
|
+
retry
|
69
|
+
else
|
70
|
+
raise Lhm::Error.new("LHM tried the reconnection procedure but failed. Aborting")
|
71
|
+
end
|
61
72
|
end
|
62
73
|
end
|
63
74
|
end
|
64
75
|
|
65
|
-
|
66
|
-
|
76
|
+
# Both attributes will have defined setters
|
77
|
+
attr_reader :retry_config, :reconnect_with_consistent_host
|
78
|
+
attr_accessor :connection
|
79
|
+
|
80
|
+
def retry_config=(retry_options)
|
81
|
+
@retry_config = default_retry_config.dup.merge!(retry_options)
|
82
|
+
end
|
83
|
+
|
84
|
+
def reconnect_with_consistent_host=(reconnect)
|
85
|
+
if (@reconnect_with_consistent_host = reconnect)
|
86
|
+
@initial_hostname = hostname
|
87
|
+
@initial_server_id = server_id
|
88
|
+
end
|
89
|
+
end
|
67
90
|
|
68
91
|
private
|
69
92
|
|
@@ -99,34 +122,32 @@ module Lhm
|
|
99
122
|
|
100
123
|
def reconnect_with_host_check!
|
101
124
|
log_with_prefix("Lost connection to MySQL, will retry to connect to same host")
|
102
|
-
|
103
|
-
|
125
|
+
|
126
|
+
RECONNECT_RETRY_MAX_ITERATION.times do
|
127
|
+
begin
|
128
|
+
sleep(RECONNECT_RETRY_INTERVAL)
|
129
|
+
|
104
130
|
# tries to reconnect. On failure will trigger a retry
|
105
131
|
@connection.reconnect!
|
106
132
|
|
107
133
|
if same_host_as_initial?
|
108
134
|
# This is not an actual error, but controlled way to get the parent `Retriable.retriable` to retry
|
109
135
|
# the statement that failed (since the Retriable gem only retries on errors).
|
110
|
-
|
136
|
+
log_with_prefix("LHM successfully reconnected to initial host: #{@initial_hostname} (server_id: #{@initial_server_id})")
|
137
|
+
return true
|
111
138
|
else
|
112
139
|
# New Master --> abort LHM (reconnecting will not change anything)
|
113
|
-
|
140
|
+
log_with_prefix("Reconnected to wrong host. Started migration on: #{@initial_hostname} (server_id: #{@initial_server_id}), but reconnected to: #{hostname} (server_id: #{server_id}).", :error)
|
141
|
+
return false
|
114
142
|
end
|
143
|
+
rescue StandardError => e
|
144
|
+
# Retry if ActiveRecord cannot reach host
|
145
|
+
next if /Lost connection to MySQL server at 'reading initial communication packet'/ === e.message
|
146
|
+
log_with_prefix("Encountered error: [#{e.class}] #{e.message}. Will stop reconnection procedure.", :info)
|
147
|
+
return false
|
115
148
|
end
|
116
|
-
rescue StandardError => e
|
117
|
-
# The parent Retriable.retriable is configured to retry if it encounters an error with the success message.
|
118
|
-
# Therefore, if the connection is re-established successfully AND the host is the same, LHM can retry the query
|
119
|
-
# that originally failed.
|
120
|
-
raise e if reconnect_successful?(e)
|
121
|
-
# If the connection was not successful, the parent retriable will raise any error that originated from the
|
122
|
-
# `@connection.reconnect!`
|
123
|
-
# Therefore, this error will cause the LHM to abort
|
124
|
-
raise Lhm::Error.new("LHM tried the reconnection procedure but failed. Latest error: #{e.message}")
|
125
149
|
end
|
126
|
-
|
127
|
-
|
128
|
-
def reconnect_successful?(e)
|
129
|
-
e.is_a?(ReconnectToHostSuccessful)
|
150
|
+
false
|
130
151
|
end
|
131
152
|
|
132
153
|
# For a full list of configuration options see https://github.com/kamui/retriable
|
@@ -143,9 +164,6 @@ module Lhm
|
|
143
164
|
/Unknown MySQL server host/,
|
144
165
|
/connection is locked to hostgroup/,
|
145
166
|
/The MySQL server is running with the --read-only option so it cannot execute this statement/,
|
146
|
-
],
|
147
|
-
ReconnectToHostSuccessful => [
|
148
|
-
/#{RECONNECT_SUCCESSFUL_MESSAGE}/
|
149
167
|
]
|
150
168
|
},
|
151
169
|
multiplier: 1, # each successive interval grows by this factor
|
@@ -153,28 +171,6 @@ module Lhm
|
|
153
171
|
tries: 20, # Number of attempts to make at running your code block (includes initial attempt).
|
154
172
|
rand_factor: 0, # percentage to randomize the next retry interval time
|
155
173
|
max_elapsed_time: Float::INFINITY, # max total time in seconds that code is allowed to keep being retried
|
156
|
-
on_retry: Proc.new do |exception, try_number, total_elapsed_time, next_interval|
|
157
|
-
if reconnect_successful?(exception)
|
158
|
-
log_with_prefix("#{exception.message} -- triggering retry", :info)
|
159
|
-
else
|
160
|
-
log_with_prefix("#{exception.class}: '#{exception.message}' - #{try_number} tries in #{total_elapsed_time} seconds and #{next_interval} seconds until the next try.", :error)
|
161
|
-
end
|
162
|
-
end
|
163
|
-
}.freeze
|
164
|
-
end
|
165
|
-
|
166
|
-
def host_retry_config
|
167
|
-
{
|
168
|
-
on: {
|
169
|
-
StandardError => [
|
170
|
-
/Lost connection to MySQL server at 'reading initial communication packet'/
|
171
|
-
]
|
172
|
-
},
|
173
|
-
multiplier: 1, # each successive interval grows by this factor
|
174
|
-
base_interval: 0.25, # the initial interval in seconds between tries.
|
175
|
-
tries: 20, # Number of attempts to make at running your code block (includes initial attempt).
|
176
|
-
rand_factor: 0, # percentage to randomize the next retry interval time
|
177
|
-
max_elapsed_time: Float::INFINITY, # max total time in seconds that code is allowed to keep being retried
|
178
174
|
on_retry: Proc.new do |exception, try_number, total_elapsed_time, next_interval|
|
179
175
|
log_with_prefix("#{exception.class}: '#{exception.message}' - #{try_number} tries in #{total_elapsed_time} seconds and #{next_interval} seconds until the next try.", :error)
|
180
176
|
end
|
data/lib/lhm/version.rb
CHANGED
data/lib/lhm.rb
CHANGED
@@ -28,7 +28,7 @@ module Lhm
|
|
28
28
|
extend Throttler
|
29
29
|
extend self
|
30
30
|
|
31
|
-
DEFAULT_LOGGER_OPTIONS =
|
31
|
+
DEFAULT_LOGGER_OPTIONS = { level: Logger::INFO, file: STDOUT }
|
32
32
|
|
33
33
|
# Alters a table with the changes described in the block
|
34
34
|
#
|
@@ -46,15 +46,19 @@ module Lhm
|
|
46
46
|
# Use atomic switch to rename tables (defaults to: true)
|
47
47
|
# If using a version of mysql affected by atomic switch bug, LHM forces user
|
48
48
|
# to set this option (see SqlHelper#supports_atomic_switch?)
|
49
|
+
# @option options [Boolean] :reconnect_with_consistent_host
|
50
|
+
# Active / Deactivate ProxySQL-aware reconnection procedure (default to: false)
|
49
51
|
# @yield [Migrator] Yielded Migrator object records the changes
|
50
52
|
# @return [Boolean] Returns true if the migration finishes
|
51
53
|
# @raise [Error] Raises Lhm::Error in case of a error and aborts the migration
|
52
54
|
def change_table(table_name, options = {}, &block)
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
55
|
+
with_flags(options) do
|
56
|
+
origin = Table.parse(table_name, connection)
|
57
|
+
invoker = Invoker.new(origin, connection)
|
58
|
+
block.call(invoker.migrator)
|
59
|
+
invoker.run(options)
|
60
|
+
true
|
61
|
+
end
|
58
62
|
end
|
59
63
|
|
60
64
|
# Cleanup tables and triggers
|
@@ -86,26 +90,16 @@ module Lhm
|
|
86
90
|
# Setups DB connection
|
87
91
|
#
|
88
92
|
# @param [ActiveRecord::Base] connection ActiveRecord Connection
|
89
|
-
|
90
|
-
|
91
|
-
# Active / Deactivate ProxySQL-aware reconnection procedure (default to: false)
|
92
|
-
def setup(connection, connection_options = {})
|
93
|
-
@@connection = Connection.new(connection: connection, options: connection_options)
|
93
|
+
def setup(connection)
|
94
|
+
@@connection = Connection.new(connection: connection)
|
94
95
|
end
|
95
96
|
|
96
|
-
#
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
if @@connection.nil?
|
103
|
-
raise 'Please call Lhm.setup' unless defined?(ActiveRecord)
|
104
|
-
@@connection = Connection.new(connection: ActiveRecord::Base.connection, options: connection_options || {})
|
105
|
-
else
|
106
|
-
@@connection.options = connection_options unless connection_options.nil?
|
107
|
-
end
|
108
|
-
@@connection
|
97
|
+
# Returns DB connection (or initializes it if not created yet)
|
98
|
+
def connection
|
99
|
+
@@connection ||= begin
|
100
|
+
raise 'Please call Lhm.setup' unless defined?(ActiveRecord)
|
101
|
+
@@connection = Connection.new(connection: ActiveRecord::Base.connection)
|
102
|
+
end
|
109
103
|
end
|
110
104
|
|
111
105
|
def self.logger=(new_logger)
|
@@ -147,4 +141,16 @@ module Lhm
|
|
147
141
|
false
|
148
142
|
end
|
149
143
|
end
|
144
|
+
|
145
|
+
def with_flags(options)
|
146
|
+
old_flags = {
|
147
|
+
reconnect_with_consistent_host: Lhm.connection.reconnect_with_consistent_host,
|
148
|
+
}
|
149
|
+
|
150
|
+
Lhm.connection.reconnect_with_consistent_host = options[:reconnect_with_consistent_host] || false
|
151
|
+
|
152
|
+
yield
|
153
|
+
ensure
|
154
|
+
Lhm.connection.reconnect_with_consistent_host = old_flags[:reconnect_with_consistent_host]
|
155
|
+
end
|
150
156
|
end
|
@@ -39,10 +39,15 @@ describe Lhm::AtomicSwitcher do
|
|
39
39
|
.then
|
40
40
|
.returns([["dummy"]]) # Matches initial host -> triggers retry
|
41
41
|
|
42
|
-
connection = Lhm::Connection.new(connection: ar_connection, options: {
|
42
|
+
connection = Lhm::Connection.new(connection: ar_connection, options: {
|
43
|
+
reconnect_with_consistent_host: true,
|
44
|
+
retriable: {
|
45
|
+
tries: 3,
|
46
|
+
base_interval: 0
|
47
|
+
}
|
48
|
+
})
|
43
49
|
|
44
|
-
|
45
|
-
switcher = Lhm::AtomicSwitcher.new(@migration, connection, retriable: { tries: 3, base_interval: 0 })
|
50
|
+
switcher = Lhm::AtomicSwitcher.new(@migration, connection)
|
46
51
|
|
47
52
|
assert switcher.run
|
48
53
|
|
@@ -58,20 +63,26 @@ describe Lhm::AtomicSwitcher do
|
|
58
63
|
ar_connection.stubs(:data_source_exists?).returns(true)
|
59
64
|
ar_connection.stubs(:active?).returns(true)
|
60
65
|
ar_connection.stubs(:execute).returns([["dummy"]], [["dummy"]], [["dummy"]])
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
connection = Lhm::Connection.new(connection: ar_connection, options: {
|
73
|
-
|
74
|
-
|
66
|
+
.then
|
67
|
+
.raises(ActiveRecord::StatementInvalid, 'Lock wait timeout exceeded; try restarting transaction.')
|
68
|
+
.then
|
69
|
+
.returns([["dummy"]]) # triggers retry 1
|
70
|
+
.then
|
71
|
+
.raises(ActiveRecord::StatementInvalid, 'Lock wait timeout exceeded; try restarting transaction.')
|
72
|
+
.then
|
73
|
+
.returns([["dummy"]]) # triggers retry 2
|
74
|
+
.then
|
75
|
+
.raises(ActiveRecord::StatementInvalid, 'Lock wait timeout exceeded; try restarting transaction.') # triggers retry 2
|
76
|
+
|
77
|
+
connection = Lhm::Connection.new(connection: ar_connection, options: {
|
78
|
+
reconnect_with_consistent_host: true,
|
79
|
+
retriable: {
|
80
|
+
tries: 2,
|
81
|
+
base_interval: 0
|
82
|
+
}
|
83
|
+
})
|
84
|
+
|
85
|
+
switcher = Lhm::AtomicSwitcher.new(@migration, connection)
|
75
86
|
|
76
87
|
assert_raises(ActiveRecord::StatementInvalid) { switcher.run }
|
77
88
|
end
|
@@ -173,6 +173,9 @@ 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
|
@@ -215,17 +218,16 @@ describe Lhm::Chunker do
|
|
215
218
|
printer.expects(:verify)
|
216
219
|
printer.expects(:end)
|
217
220
|
|
218
|
-
|
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
|
-
|
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
228
|
config = ActiveRecord::Base.connection_pool.db_config.configuration_hash.dup
|
227
229
|
config[:host] = slave
|
228
|
-
config[:port] =
|
230
|
+
config[:port] = 33007
|
229
231
|
ActiveRecord::Base.send('mysql2_connection', config)
|
230
232
|
end
|
231
233
|
end
|
@@ -62,18 +62,16 @@ module IntegrationHelper
|
|
62
62
|
)
|
63
63
|
end
|
64
64
|
|
65
|
-
def connect_master_with_toxiproxy!
|
65
|
+
def connect_master_with_toxiproxy!
|
66
66
|
connect!(
|
67
67
|
'127.0.0.1',
|
68
68
|
$db_config['master_toxic']['port'],
|
69
69
|
$db_config['master_toxic']['user'],
|
70
|
-
$db_config['master_toxic']['password']
|
71
|
-
with_retry)
|
70
|
+
$db_config['master_toxic']['password'])
|
72
71
|
end
|
73
72
|
|
74
|
-
def connect!(hostname, port, user, password
|
75
|
-
|
76
|
-
Lhm.setup(adapter,{reconnect_with_consistent_host: with_retry})
|
73
|
+
def connect!(hostname, port, user, password)
|
74
|
+
Lhm.setup(ar_conn(hostname, port, user, password))
|
77
75
|
unless defined?(@@cleaned_up)
|
78
76
|
Lhm.cleanup(true)
|
79
77
|
@@cleaned_up = true
|
@@ -592,7 +592,7 @@ describe Lhm do
|
|
592
592
|
end
|
593
593
|
|
594
594
|
it " should not try to reconnect if reconnect_with_consistent_host is not provided" do
|
595
|
-
connect_master_with_toxiproxy!
|
595
|
+
connect_master_with_toxiproxy!
|
596
596
|
|
597
597
|
table_create(:users)
|
598
598
|
100.times { |n| execute("insert into users set reference = '#{ n }'") }
|
@@ -610,7 +610,7 @@ describe Lhm do
|
|
610
610
|
end
|
611
611
|
|
612
612
|
it "should reconnect if reconnect_with_consistent_host is true" do
|
613
|
-
connect_master_with_toxiproxy!
|
613
|
+
connect_master_with_toxiproxy!
|
614
614
|
mysql_disabled = false
|
615
615
|
|
616
616
|
table_create(:users)
|
@@ -635,7 +635,7 @@ describe Lhm do
|
|
635
635
|
method_added(:insert_and_return_count_of_rows_created)
|
636
636
|
end
|
637
637
|
|
638
|
-
Lhm.change_table(:users, :
|
638
|
+
Lhm.change_table(:users, atomic_switch: false, reconnect_with_consistent_host: true) do |t|
|
639
639
|
t.ddl("ALTER TABLE #{t.name} CHANGE id id bigint (20) NOT NULL")
|
640
640
|
t.ddl("ALTER TABLE #{t.name} DROP PRIMARY KEY, ADD PRIMARY KEY (username, id)")
|
641
641
|
t.ddl("ALTER TABLE #{t.name} ADD INDEX (id)")
|
@@ -645,7 +645,6 @@ describe Lhm do
|
|
645
645
|
log_lines = @logs.string.split("\n")
|
646
646
|
|
647
647
|
assert log_lines.one?{ |line| line.include?("Lost connection to MySQL, will retry to connect to same host")}
|
648
|
-
assert log_lines.any?{ |line| line.include?("Lost connection to MySQL server at 'reading initial communication packet'")}
|
649
648
|
assert log_lines.one?{ |line| line.include?("LHM successfully reconnected to initial host")}
|
650
649
|
assert log_lines.one?{ |line| line.include?("100% complete")}
|
651
650
|
|
@@ -29,6 +29,6 @@ describe "ProxySQL integration" do
|
|
29
29
|
port: "33005",
|
30
30
|
)
|
31
31
|
|
32
|
-
assert_equal conn.query("
|
32
|
+
assert_equal conn.query("SELECT @@global.hostname as host #{Lhm::ProxySQLHelper::ANNOTATION}").each.first["host"], "mysql-1"
|
33
33
|
end
|
34
34
|
end
|
@@ -55,7 +55,7 @@ describe Lhm::SqlRetry do
|
|
55
55
|
it "successfully executes the SQL despite the errors encountered" do
|
56
56
|
# Start a thread to retry, once the lock is held, execute the block
|
57
57
|
@helper.with_waiting_lock do |waiting_connection|
|
58
|
-
sql_retry = Lhm::SqlRetry.new(waiting_connection,
|
58
|
+
sql_retry = Lhm::SqlRetry.new(waiting_connection, retry_options: {
|
59
59
|
base_interval: 0.2, # first retry after 200ms
|
60
60
|
multiplier: 1, # subsequent retries wait 1x longer than first retry (no change)
|
61
61
|
tries: 3, # we only need 3 tries (including the first) for the scenario described below
|
@@ -98,7 +98,7 @@ describe Lhm::SqlRetry do
|
|
98
98
|
puts "*" * 64
|
99
99
|
# Start a thread to retry, once the lock is held, execute the block
|
100
100
|
@helper.with_waiting_lock do |waiting_connection|
|
101
|
-
sql_retry = Lhm::SqlRetry.new(waiting_connection,
|
101
|
+
sql_retry = Lhm::SqlRetry.new(waiting_connection, retry_options: {
|
102
102
|
base_interval: 0.2, # first retry after 200ms
|
103
103
|
multiplier: 1, # subsequent retries wait 1x longer than first retry (no change)
|
104
104
|
tries: 2, # we need 3 tries (including the first) for the scenario described below, but we only get two...we will fail
|
@@ -18,7 +18,7 @@ describe Lhm::SqlRetry, "ProxiSQL tests for LHM retry" do
|
|
18
18
|
|
19
19
|
@connection = DBConnectionHelper::new_mysql_connection(:proxysql, true, true)
|
20
20
|
|
21
|
-
@lhm_retry = Lhm::SqlRetry.new(@connection,
|
21
|
+
@lhm_retry = Lhm::SqlRetry.new(@connection, retry_options: {},
|
22
22
|
reconnect_with_consistent_host: true)
|
23
23
|
end
|
24
24
|
|
@@ -38,7 +38,7 @@ describe Lhm::SqlRetry, "ProxiSQL tests for LHM retry" do
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
assert_equal Lhm::Error, e.class
|
41
|
-
assert_match(/LHM tried the reconnection procedure but failed.
|
41
|
+
assert_match(/LHM tried the reconnection procedure but failed. Aborting/, e.message)
|
42
42
|
end
|
43
43
|
|
44
44
|
it "Will retry until connection is achieved" do
|
@@ -61,9 +61,10 @@ describe Lhm::SqlRetry, "ProxiSQL tests for LHM retry" do
|
|
61
61
|
it "Will abort if new writer is not same host" do
|
62
62
|
# The hostname will be constant before the blip
|
63
63
|
Lhm::SqlRetry.any_instance.stubs(:hostname).returns("mysql-1").then.returns("mysql-2")
|
64
|
+
Lhm::SqlRetry.any_instance.stubs(:server_id).returns(1).then.returns(2)
|
64
65
|
|
65
66
|
# Need new instance for stub to take into effect
|
66
|
-
lhm_retry = Lhm::SqlRetry.new(@connection,
|
67
|
+
lhm_retry = Lhm::SqlRetry.new(@connection, retry_options: {},
|
67
68
|
reconnect_with_consistent_host: true)
|
68
69
|
|
69
70
|
e = assert_raises Lhm::Error do
|
@@ -76,12 +77,12 @@ describe Lhm::SqlRetry, "ProxiSQL tests for LHM retry" do
|
|
76
77
|
end
|
77
78
|
|
78
79
|
assert_equal e.class, Lhm::Error
|
79
|
-
assert_match(/LHM tried the reconnection procedure but failed.
|
80
|
+
assert_match(/LHM tried the reconnection procedure but failed. Aborting/, e.message)
|
80
81
|
|
81
82
|
logs = @logger.string.split("\n")
|
82
83
|
|
83
84
|
assert logs.first.include?("Lost connection to MySQL, will retry to connect to same host")
|
84
|
-
assert logs.last.include?("
|
85
|
+
assert logs.last.include?("Reconnected to wrong host. Started migration on: mysql-1 (server_id: 1), but reconnected to: mysql-2 (server_id: 2).")
|
85
86
|
end
|
86
87
|
|
87
88
|
it "Will abort if failover happens (mimicked with proxySQL)" do
|
@@ -98,11 +99,11 @@ describe Lhm::SqlRetry, "ProxiSQL tests for LHM retry" do
|
|
98
99
|
end
|
99
100
|
|
100
101
|
assert_equal e.class, Lhm::Error
|
101
|
-
assert_match(/LHM tried the reconnection procedure but failed.
|
102
|
+
assert_match(/LHM tried the reconnection procedure but failed. Aborting/, e.message)
|
102
103
|
|
103
104
|
logs = @logger.string.split("\n")
|
104
105
|
|
105
106
|
assert logs.first.include?("Lost connection to MySQL, will retry to connect to same host")
|
106
|
-
assert logs.last.include?("
|
107
|
+
assert logs.last.include?("Reconnected to wrong host. Started migration on: mysql-1 (server_id: 1), but reconnected to: mysql-2 (server_id: 2).")
|
107
108
|
end
|
108
109
|
end
|
data/spec/test_helper.rb
CHANGED
@@ -28,6 +28,9 @@ logger = Logger.new STDOUT
|
|
28
28
|
logger.level = Logger::WARN
|
29
29
|
Lhm.logger = logger
|
30
30
|
|
31
|
+
# Want test to be efficient without having to wait the normal value of 120s
|
32
|
+
Lhm::SqlRetry::RECONNECT_RETRY_MAX_ITERATION = 4
|
33
|
+
|
31
34
|
def without_verbose(&block)
|
32
35
|
old_verbose, $VERBOSE = $VERBOSE, nil
|
33
36
|
yield
|