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