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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4f676427b10052113b08c4dda8f648bc11234024deab2f1e96de332e0906a110
4
- data.tar.gz: dbd91d1b72d713e9d6e18edb46900971ee89c20563d5e93db9eec746887a12d7
3
+ metadata.gz: c3893b743c675e62933e58a4f56c0ac2dba1b1081afabc779dc1d899406e7c42
4
+ data.tar.gz: 569175938b8069e0036f881400b9427180283ec61c29ca5d509a7090a7f8a9ba
5
5
  SHA512:
6
- metadata.gz: be5dc9f5d6d2a3ae62b476fa19455bd0f864b989d6ffbff35a93a0afea4e8d9be4492a4b3bfc2bec1029193114187eaf4eda5bb6dfde9d0be34884bde317c1c1
7
- data.tar.gz: aa4e866f74da3255586c1597ae63a3792606bd092e46c3a90335d22b9f6c9b243bc133d99e50233c884b03064dfaeee75983597f84733484e05ec456833f3395
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- lhm-shopify (3.5.4)
4
+ lhm-shopify (3.5.5)
5
5
  retriable (>= 3.0.0)
6
6
 
7
7
  GEM
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- lhm-shopify (3.5.4)
4
+ lhm-shopify (3.5.5)
5
5
  retriable (>= 3.0.0)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- lhm-shopify (3.5.4)
4
+ lhm-shopify (3.5.5)
5
5
  retriable (>= 3.0.0)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- lhm-shopify (3.5.4)
4
+ lhm-shopify (3.5.5)
5
5
  retriable (>= 3.0.0)
6
6
 
7
7
  GEM
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- lhm-shopify (3.5.4)
4
+ lhm-shopify (3.5.5)
5
5
  retriable (>= 3.0.0)
6
6
 
7
7
  GEM
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("Could not reconnected to initial MySQL host. Aborting to avoid data-loss") unless same_host_as_initial?
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
- reconnect_with_host_check! if @reconnect_with_consistent_host
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
- begin
109
- Retriable.retriable(host_retry_config) do
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
- raise ReconnectToHostSuccessful.new("LHM successfully reconnected to initial host: #{@initial_hostname} (server_id: #{@initial_server_id})")
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
- raise Lhm::Error.new("Reconnected to wrong host. Started migration on: #{@initial_hostname} (server_id: #{@initial_server_id}), but reconnected to: #{hostname} (server_id: #{@initial_server_id}).")
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
- end
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
@@ -2,5 +2,5 @@
2
2
  # Schmidt
3
3
 
4
4
  module Lhm
5
- VERSION = '3.5.4'
5
+ VERSION = '3.5.5'
6
6
  end
@@ -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. Latest error:/, e.message)
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. Latest error: Reconnected to wrong host/, e.message)
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?("Lost connection to MySQL server at 'reading initial communication packet")
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. Latest error: Reconnected to wrong host/, e.message)
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?("Lost connection to MySQL server at 'reading initial communication packet")
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
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: 2021-12-14 00:00:00.000000000 Z
15
+ date: 2022-01-07 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: retriable