lhm-shopify 3.5.4 → 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 +3 -0
- data/Gemfile.lock +1 -1
- data/dev.yml +3 -0
- 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/sql_retry.rb +37 -47
- data/lib/lhm/version.rb +1 -1
- data/spec/integration/lhm_spec.rb +0 -1
- data/spec/integration/sql_retry/retry_with_proxysql_spec.rb +6 -5
- data/spec/test_helper.rb +3 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c3893b743c675e62933e58a4f56c0ac2dba1b1081afabc779dc1d899406e7c42
|
4
|
+
data.tar.gz: 569175938b8069e0036f881400b9427180283ec61c29ca5d509a7090a7f8a9ba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba3ebf953f97cd793c52cb83fee77dd4436877ca01a51ee6d718c7937916e391a32c659b9f403b62f65cf0a326dc755e6221e3311760738c9c6fb442746e1617
|
7
|
+
data.tar.gz: 2e617329a895d00a2541539526e941fc94dfda8285083d46d4179587a31786cbbb1062bd8dbdfffcc4d046801944ac96aa700c32618a4db9f4346345e9105c7c
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
# 3.5.5 (Jan, 2022)
|
2
|
+
* Fix error where from Config shadowing which would cause LHM to abort on reconnect (https://github.com/Shopify/lhm/pull/128)
|
3
|
+
|
1
4
|
# 3.5.4 (Dec, 2021)
|
2
5
|
* Refactored the way options are handled internally. Code is now much clearer to understand
|
3
6
|
* Removed optional connection_options from `Lhm.setup` and `Lhm.connection`
|
data/Gemfile.lock
CHANGED
data/dev.yml
CHANGED
@@ -40,3 +40,6 @@ commands:
|
|
40
40
|
run: docker-compose logs -f
|
41
41
|
clear:
|
42
42
|
run: docker-compose rm -v -s -f && docker-compose up -d && ./scripts/helpers/wait-for-dbs.sh
|
43
|
+
pre-publish:
|
44
|
+
# Ensures all Gemfile.lock are sync with the new version in `lhm/version.rb` and runs appraisals
|
45
|
+
run: bundle install && bundle exec appraisal install && bundle exec appraisal rake specs
|
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,9 +31,6 @@ module Lhm
|
|
25
31
|
version_comment: "@@version_comment",
|
26
32
|
}
|
27
33
|
|
28
|
-
# This internal error is used to trigger retries from the parent Retriable.retriable in #with_retries
|
29
|
-
class ReconnectToHostSuccessful < Lhm::Error; end
|
30
|
-
|
31
34
|
def initialize(connection, retry_options: {}, reconnect_with_consistent_host: false)
|
32
35
|
@connection = connection
|
33
36
|
self.retry_config = retry_options
|
@@ -38,11 +41,14 @@ module Lhm
|
|
38
41
|
def with_retries(log_prefix: nil)
|
39
42
|
@log_prefix = log_prefix || "" # No prefix. Just logs
|
40
43
|
|
44
|
+
# Amount of time LHM had to reconnect. Aborting if more than RECONNECTION_MAXIMUM
|
45
|
+
reconnection_counter = 0
|
46
|
+
|
41
47
|
Retriable.retriable(@retry_config) do
|
42
48
|
# Using begin -> rescue -> end for Ruby 2.4 compatibility
|
43
49
|
begin
|
44
50
|
if @reconnect_with_consistent_host
|
45
|
-
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?
|
46
52
|
end
|
47
53
|
|
48
54
|
yield(@connection)
|
@@ -51,7 +57,18 @@ module Lhm
|
|
51
57
|
# The error will be raised the connection is still active (i.e. no need to reconnect) or if the connection is
|
52
58
|
# dead (i.e. not active) and @reconnect_with_host is false (i.e. instructed not to reconnect)
|
53
59
|
raise e if @connection.active? || (!@connection.active? && !@reconnect_with_consistent_host)
|
54
|
-
|
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
|
55
72
|
end
|
56
73
|
end
|
57
74
|
end
|
@@ -105,34 +122,32 @@ module Lhm
|
|
105
122
|
|
106
123
|
def reconnect_with_host_check!
|
107
124
|
log_with_prefix("Lost connection to MySQL, will retry to connect to same host")
|
108
|
-
|
109
|
-
|
125
|
+
|
126
|
+
RECONNECT_RETRY_MAX_ITERATION.times do
|
127
|
+
begin
|
128
|
+
sleep(RECONNECT_RETRY_INTERVAL)
|
129
|
+
|
110
130
|
# tries to reconnect. On failure will trigger a retry
|
111
131
|
@connection.reconnect!
|
112
132
|
|
113
133
|
if same_host_as_initial?
|
114
134
|
# This is not an actual error, but controlled way to get the parent `Retriable.retriable` to retry
|
115
135
|
# the statement that failed (since the Retriable gem only retries on errors).
|
116
|
-
|
136
|
+
log_with_prefix("LHM successfully reconnected to initial host: #{@initial_hostname} (server_id: #{@initial_server_id})")
|
137
|
+
return true
|
117
138
|
else
|
118
139
|
# New Master --> abort LHM (reconnecting will not change anything)
|
119
|
-
|
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
|
120
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
|
121
148
|
end
|
122
|
-
rescue StandardError => e
|
123
|
-
# The parent Retriable.retriable is configured to retry if it encounters an error with the success message.
|
124
|
-
# Therefore, if the connection is re-established successfully AND the host is the same, LHM can retry the query
|
125
|
-
# that originally failed.
|
126
|
-
raise e if reconnect_successful?(e)
|
127
|
-
# If the connection was not successful, the parent retriable will raise any error that originated from the
|
128
|
-
# `@connection.reconnect!`
|
129
|
-
# Therefore, this error will cause the LHM to abort
|
130
|
-
raise Lhm::Error.new("LHM tried the reconnection procedure but failed. Latest error: #{e.message}")
|
131
149
|
end
|
132
|
-
|
133
|
-
|
134
|
-
def reconnect_successful?(e)
|
135
|
-
e.is_a?(ReconnectToHostSuccessful)
|
150
|
+
false
|
136
151
|
end
|
137
152
|
|
138
153
|
# For a full list of configuration options see https://github.com/kamui/retriable
|
@@ -149,9 +164,6 @@ module Lhm
|
|
149
164
|
/Unknown MySQL server host/,
|
150
165
|
/connection is locked to hostgroup/,
|
151
166
|
/The MySQL server is running with the --read-only option so it cannot execute this statement/,
|
152
|
-
],
|
153
|
-
ReconnectToHostSuccessful => [
|
154
|
-
/#{RECONNECT_SUCCESSFUL_MESSAGE}/
|
155
167
|
]
|
156
168
|
},
|
157
169
|
multiplier: 1, # each successive interval grows by this factor
|
@@ -159,28 +171,6 @@ module Lhm
|
|
159
171
|
tries: 20, # Number of attempts to make at running your code block (includes initial attempt).
|
160
172
|
rand_factor: 0, # percentage to randomize the next retry interval time
|
161
173
|
max_elapsed_time: Float::INFINITY, # max total time in seconds that code is allowed to keep being retried
|
162
|
-
on_retry: Proc.new do |exception, try_number, total_elapsed_time, next_interval|
|
163
|
-
if reconnect_successful?(exception)
|
164
|
-
log_with_prefix("#{exception.message} -- triggering retry", :info)
|
165
|
-
else
|
166
|
-
log_with_prefix("#{exception.class}: '#{exception.message}' - #{try_number} tries in #{total_elapsed_time} seconds and #{next_interval} seconds until the next try.", :error)
|
167
|
-
end
|
168
|
-
end
|
169
|
-
}.freeze
|
170
|
-
end
|
171
|
-
|
172
|
-
def host_retry_config
|
173
|
-
{
|
174
|
-
on: {
|
175
|
-
StandardError => [
|
176
|
-
/Lost connection to MySQL server at 'reading initial communication packet'/
|
177
|
-
]
|
178
|
-
},
|
179
|
-
multiplier: 1, # each successive interval grows by this factor
|
180
|
-
base_interval: 0.25, # the initial interval in seconds between tries.
|
181
|
-
tries: 20, # Number of attempts to make at running your code block (includes initial attempt).
|
182
|
-
rand_factor: 0, # percentage to randomize the next retry interval time
|
183
|
-
max_elapsed_time: Float::INFINITY, # max total time in seconds that code is allowed to keep being retried
|
184
174
|
on_retry: Proc.new do |exception, try_number, total_elapsed_time, next_interval|
|
185
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)
|
186
176
|
end
|
data/lib/lhm/version.rb
CHANGED
@@ -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
|
|
@@ -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,6 +61,7 @@ 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
67
|
lhm_retry = Lhm::SqlRetry.new(@connection, retry_options: {},
|
@@ -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
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lhm-shopify
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.5.
|
4
|
+
version: 3.5.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- SoundCloud
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date:
|
15
|
+
date: 2022-01-07 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: retriable
|