lhm-shopify 3.5.4 → 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/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
|