lhm-shopify 4.0.0 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +20 -18
  3. data/Appraisals +5 -11
  4. data/CHANGELOG.md +6 -0
  5. data/Gemfile.lock +22 -7
  6. data/README.md +7 -7
  7. data/dev.yml +4 -1
  8. data/docker-compose-mysql-5.7.yml +1 -0
  9. data/docker-compose-mysql-8.0.yml +63 -0
  10. data/docker-compose.yml +3 -3
  11. data/gemfiles/activerecord_6.1.gemfile +1 -0
  12. data/gemfiles/activerecord_6.1.gemfile.lock +8 -2
  13. data/gemfiles/activerecord_7.0.gemfile +1 -0
  14. data/gemfiles/activerecord_7.0.gemfile.lock +7 -1
  15. data/gemfiles/{activerecord_6.0.gemfile → activerecord_7.1.gemfile} +1 -1
  16. data/gemfiles/{activerecord_7.1.0.beta1.gemfile.lock → activerecord_7.1.gemfile.lock} +10 -8
  17. data/lhm.gemspec +1 -0
  18. data/lib/lhm/atomic_switcher.rb +3 -3
  19. data/lib/lhm/chunker.rb +4 -4
  20. data/lib/lhm/connection.rb +9 -1
  21. data/lib/lhm/sql_retry.rb +36 -18
  22. data/lib/lhm/table.rb +3 -4
  23. data/lib/lhm/throttler/replica_lag.rb +17 -13
  24. data/lib/lhm/version.rb +1 -1
  25. data/scripts/helpers/wait-for-dbs.sh +3 -3
  26. data/scripts/mysql/writer/create_users.sql +1 -1
  27. data/spec/integration/atomic_switcher_spec.rb +4 -8
  28. data/spec/integration/chunker_spec.rb +21 -6
  29. data/spec/integration/database.yml +3 -3
  30. data/spec/integration/integration_helper.rb +11 -3
  31. data/spec/integration/lhm_spec.rb +29 -13
  32. data/spec/integration/proxysql_spec.rb +10 -10
  33. data/spec/integration/sql_retry/db_connection_helper.rb +2 -4
  34. data/spec/integration/sql_retry/lock_wait_spec.rb +7 -8
  35. data/spec/integration/sql_retry/lock_wait_timeout_test_helper.rb +18 -10
  36. data/spec/integration/sql_retry/proxysql_helper.rb +1 -1
  37. data/spec/integration/sql_retry/retry_with_proxysql_spec.rb +1 -2
  38. data/spec/integration/table_spec.rb +1 -1
  39. data/spec/test_helper.rb +27 -3
  40. data/spec/unit/atomic_switcher_spec.rb +2 -2
  41. data/spec/unit/chunker_spec.rb +43 -43
  42. data/spec/unit/connection_spec.rb +2 -2
  43. data/spec/unit/entangler_spec.rb +14 -24
  44. data/spec/unit/throttler/replica_lag_spec.rb +6 -14
  45. metadata +21 -8
  46. data/.travis.yml +0 -21
  47. data/gemfiles/activerecord_6.0.gemfile.lock +0 -71
  48. data/gemfiles/activerecord_7.1.0.beta1.gemfile +0 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34c64cb93bda1fe53758ea181451438890d8460e167784dfff3255aba7ccf662
4
- data.tar.gz: 14e18340ed78985984b93c8d36328ccb9a29d1318639e190ad98f5ceecdf828c
3
+ metadata.gz: 850cda6d2f610db2985c780d2a90d176e8520b373e21dafbe67df47e9a07ba0f
4
+ data.tar.gz: 982ab91c6319f5f7803d73d9f3734a57d28a13e76184500a665a5ab5b91d434f
5
5
  SHA512:
6
- metadata.gz: 4f83e952632a11d868a75da5d9b2e64b7326090ed0b37f74d221f8b86a1345bef783c5d56ca82691d13067b410cf0926d2e88be0939c9cb925b66f418e4fcb9e
7
- data.tar.gz: 6aefea0808ed1ba537d550464492784f9b2cc504858a2524cf7c74bde16123c4be0eb0965c206f8f3cb0c2cceca48bc17651d111e8d9401a7f3c762efbcef7a3
6
+ metadata.gz: 8bdb254a7cd2091f7d1d125080bc843b43fc9039a7b2a99cba62752bee84726971e8018fb77738fdd0b15867a7f959ff419802270de75c08c2a72cd44d8b004b
7
+ data.tar.gz: b7d3a989384483ba9f43dd000f1e46c62805e15a0e15c4fcba0fedc964787d4fb1eafe294a2b23deae2d5b035057739551385eca08d24529d8c8e951ba44b163
@@ -14,30 +14,32 @@ jobs:
14
14
  strategy:
15
15
  fail-fast: false
16
16
  matrix:
17
- include:
18
- - ruby-version: "3.0"
19
- activerecord-version: "6.0"
20
- - ruby-version: "3.1"
21
- activerecord-version: "6.1"
22
- - ruby-version: "3.2"
23
- activerecord-version: "7.0"
24
- - ruby-version: "3.2"
25
- activerecord-version: "7.1.0.beta1"
17
+ activerecord: ["6.1", "7.0", "7.1"]
18
+ ruby: ["3.0", "3.1", "3.2", "head"]
19
+ mysql: ["5.7", "8.0"]
20
+ adapter: ["mysql2", "trilogy"]
21
+
22
+ env:
23
+ BUNDLE_GEMFILE: "${{ github.workspace }}/gemfiles/activerecord_${{ matrix.activerecord }}.gemfile"
24
+ DATABASE_ADAPTER: "${{ matrix.adapter }}"
25
+
26
26
  steps:
27
- - uses: actions/checkout@v2
28
- - name: Set up Ruby
29
- uses: ruby/setup-ruby@v1
27
+ - uses: actions/checkout@v4
28
+ - uses: ruby/setup-ruby@v1
30
29
  with:
31
- ruby-version: ${{matrix.ruby-version}}
32
- bundler-cache: false
33
- - name: Install Ruby packages
34
- run: BUNDLE_GEMFILE="${GITHUB_WORKSPACE}/gemfiles/activerecord_${{ matrix.activerecord-version }}.gemfile" bundle install
30
+ ruby-version: ${{matrix.ruby}}
31
+ bundler-cache: true
32
+
35
33
  - name: Install Ubuntu packages
36
34
  run: sudo apt-get update && sudo apt-get install numactl libaio-dev libmysqlclient-dev
35
+
37
36
  - name: Setup MySQL and ProxySQL (docker-compose)
38
- run: docker-compose up -d # Might have to change to docker compose up -d (i.e. Compose V2) when the Ubuntu image changes the docker-compose version
37
+ # Might have to change to docker compose up -d (i.e. Compose V2) when the Ubuntu image changes the docker-compose version
38
+ run: docker-compose -f docker-compose-mysql-${{ matrix.mysql }}.yml up -d
39
+
39
40
  - name: Wait until DBs are alive
40
41
  run: ./scripts/helpers/wait-for-dbs.sh
41
42
  timeout-minutes: 2
43
+
42
44
  - name: Run tests
43
- run: BUNDLE_GEMFILE="${GITHUB_WORKSPACE}/gemfiles/activerecord_${{ matrix.activerecord-version }}.gemfile" bundle exec rake specs
45
+ run: bundle exec rake specs
data/Appraisals CHANGED
@@ -1,19 +1,13 @@
1
- # First conflicted version
2
- appraise "activerecord-6.0" do
3
- gem "activerecord", "6.0.3"
4
- end
5
-
6
- # Second conflicted version
7
1
  appraise "activerecord-6.1" do
8
2
  gem "activerecord", "6.1.0"
3
+ gem "activerecord-trilogy-adapter"
9
4
  end
10
5
 
11
- # Latest version at the moment
12
6
  appraise "activerecord-7.0" do
13
7
  gem "activerecord", "7.0.8"
8
+ gem "activerecord-trilogy-adapter"
14
9
  end
15
10
 
16
- # Next release
17
- appraise "activerecord-7.1.0.beta1" do
18
- gem "activerecord", "7.1.0.beta1"
19
- end
11
+ appraise "activerecord-7.1" do
12
+ gem "activerecord", "7.1.1"
13
+ end
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Unreleased
2
2
 
3
+ # 4.1.0 (Oct, 2023)
4
+ * Test against MySQL 8.0.
5
+ * Test against Ruby Head.
6
+ * Drop support for ActiveRecord below version 6.1, as it has reached EOL.
7
+ * Add support for Trilogy MySQL client. It's works with built in ActiveRecord adapter from Rails 7.1 on, as well as dedicated one in older Rails versions.
8
+
3
9
  # 4.0.0 (Sep, 2023)
4
10
  * Deprecate `SlaveLag` throttler class name. Use `ReplicaLag` instead (https://github.com/Shopify/lhm/pull/144)
5
11
  * Deprecate `slave_lag_throttler` throttler config value. Use `replica_lag_throttler` instead (https://github.com/Shopify/lhm/pull/144)
data/Gemfile.lock CHANGED
@@ -1,35 +1,47 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- lhm-shopify (4.0.0)
4
+ lhm-shopify (4.1.0)
5
5
  retriable (>= 3.0.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- activemodel (7.0.8)
11
- activesupport (= 7.0.8)
12
- activerecord (7.0.8)
13
- activemodel (= 7.0.8)
14
- activesupport (= 7.0.8)
15
- activesupport (7.0.8)
10
+ activemodel (7.1.1)
11
+ activesupport (= 7.1.1)
12
+ activerecord (7.1.1)
13
+ activemodel (= 7.1.1)
14
+ activesupport (= 7.1.1)
15
+ timeout (>= 0.4.0)
16
+ activesupport (7.1.1)
17
+ base64
18
+ bigdecimal
16
19
  concurrent-ruby (~> 1.0, >= 1.0.2)
20
+ connection_pool (>= 2.2.5)
21
+ drb
17
22
  i18n (>= 1.6, < 2)
18
23
  minitest (>= 5.1)
24
+ mutex_m
19
25
  tzinfo (~> 2.0)
20
26
  after_do (0.4.0)
21
27
  appraisal (2.5.0)
22
28
  bundler
23
29
  rake
24
30
  thor (>= 0.14.0)
31
+ base64 (0.1.1)
32
+ bigdecimal (3.1.4)
25
33
  byebug (11.1.3)
26
34
  concurrent-ruby (1.2.2)
35
+ connection_pool (2.4.1)
27
36
  docile (1.4.0)
37
+ drb (2.1.1)
38
+ ruby2_keywords
28
39
  i18n (1.14.1)
29
40
  concurrent-ruby (~> 1.0)
30
41
  minitest (5.20.0)
31
42
  mocha (2.1.0)
32
43
  ruby2_keywords (>= 0.0.5)
44
+ mutex_m (0.1.2)
33
45
  mysql2 (0.5.5)
34
46
  rake (13.0.6)
35
47
  retriable (3.1.2)
@@ -41,7 +53,9 @@ GEM
41
53
  simplecov-html (0.12.3)
42
54
  simplecov_json_formatter (0.1.4)
43
55
  thor (1.2.2)
56
+ timeout (0.4.0)
44
57
  toxiproxy (2.0.2)
58
+ trilogy (2.6.0)
45
59
  tzinfo (2.0.6)
46
60
  concurrent-ruby (~> 1.0)
47
61
 
@@ -63,6 +77,7 @@ DEPENDENCIES
63
77
  rake
64
78
  simplecov
65
79
  toxiproxy
80
+ trilogy
66
81
 
67
82
  BUNDLED WITH
68
83
  2.2.22
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Tests](https://github.com/Shopify/lhm/actions/workflows/test.yml/badge.svg)](https://github.com/Shopify/lhm/actions/workflows/test.yml)
4
4
 
5
- This is the Shopify fork of [SoundCloud's LHM](https://github.com/soundcloud/lhm). The
5
+ This is the Shopify fork of [SoundCloud's LHM](https://github.com/soundcloud/lhm). The
6
6
  following description, originally from SoundCloud (with minor updates by Shopify),
7
7
  gives some of the flavor around its original creation, and its choice of name...
8
8
 
@@ -112,7 +112,7 @@ tables must be cleaned up.
112
112
  ### Usage with ProxySQL
113
113
  LHM can recover from connection loss. However, when used in conjunction with ProxySQL, there are multiple ways that
114
114
  connection loss could induce data loss (if triggered by a failover). Therefore it will perform additional checks to
115
- ensure that the MySQL host stays consistent across the schema migrations if the feature is enabled.
115
+ ensure that the MySQL host stays consistent across the schema migrations if the feature is enabled.
116
116
  This is done by tagging every query with `/*maintenance:lhm*/`, which will be recognized by ProxySQL.
117
117
  However, to get this feature working, a new ProxySQL query rule must be added.
118
118
  ```cnf
@@ -127,7 +127,7 @@ However, to get this feature working, a new ProxySQL query rule must be added.
127
127
  This will ensure that all relevant queries are forwarded to the current writer.
128
128
 
129
129
  Also, ProxySQL disables [multiplexing](https://proxysql.com/documentation/multiplexing/) for `select` on `@@` variables.
130
- Therefore, the following rules must be added to ensure that queries (even if tagged with `/*maintenance:lhm*/`) get
130
+ Therefore, the following rules must be added to ensure that queries (even if tagged with `/*maintenance:lhm*/`) get
131
131
  forwarded to the right target.
132
132
  ```cnf
133
133
  {
@@ -144,7 +144,7 @@ forwarded to the right target.
144
144
  }
145
145
  ```
146
146
 
147
- Once these changes are added to the ProxySQL configuration (either through `.cnf` or dynamically through the admin interface),
147
+ Once these changes are added to the ProxySQL configuration (either through `.cnf` or dynamically through the admin interface),
148
148
  the feature can be enabled. This is done by adding this flag when providing options to the migration:
149
149
  ```ruby
150
150
  Lhm.change_table(..., options: {reconnect_with_consistent_host: true}) do |t|
@@ -285,7 +285,7 @@ To run the tests:
285
285
  ```bash
286
286
  bundle exec rake unit # unit tests
287
287
  bundle exec rake integration # integration tests
288
- bundle exec rake unit # all tests
288
+ bundle exec rake specs # all tests
289
289
  ```
290
290
 
291
291
  You can run an individual test as follows:
@@ -311,12 +311,12 @@ open coverage/index.html
311
311
  ```
312
312
 
313
313
  ### Merging for a new version
314
- When creating a PR for a new version, make sure that th version has been bumped in `lib/lhm/version.rb`. Then run the following code snippet to ensure the everything is consistent, otherwise
314
+ When creating a PR for a new version, make sure that th version has been bumped in `lib/lhm/version.rb`. Then run the following code snippet to ensure the everything is consistent, otherwise
315
315
  the gem will not publish.
316
316
  ```bash
317
317
  bundle install
318
318
  bundle update
319
- bundle exec appraisals install
319
+ bundle exec appraisal install
320
320
  ```
321
321
 
322
322
  ### Podman Compose
data/dev.yml CHANGED
@@ -14,7 +14,7 @@ up:
14
14
  meet: ":"
15
15
  - custom:
16
16
  name: Podman compose
17
- met?: podman-compose ls | grep -ioE -q "lhm.*running\(4\)"
17
+ met?: podman-compose ps | grep -ioE -q "lhm.*running\(4\)"
18
18
  meet: podman-compose up -d
19
19
  - custom:
20
20
  name: Waiting for DBs to be operational
@@ -41,6 +41,9 @@ commands:
41
41
  run: podman-compose logs -f
42
42
  clear:
43
43
  run: podman-compose down -v && podman-compose up -d && ./scripts/helpers/wait-for-dbs.sh
44
+ subcommands:
45
+ mysql-5.7: podman-compose down -v && podman-compose -f docker-compose-mysql-5.7.yml up -d && ./scripts/helpers/wait-for-dbs.sh
46
+ mysql-8.0: podman-compose down -v && podman-compose -f docker-compose-mysql-8.0.yml up -d && ./scripts/helpers/wait-for-dbs.sh
44
47
  pre-publish:
45
48
  # Ensures all Gemfile.lock are sync with the new version in `lhm/version.rb` and runs appraisals
46
49
  run: bundle install && bundle exec appraisal install && bundle exec appraisal rake specs
@@ -0,0 +1 @@
1
+ docker-compose.yml
@@ -0,0 +1,63 @@
1
+ services:
2
+ # Writer
3
+ mysql-1:
4
+ container_name: mysql-1
5
+ image: percona:8.0
6
+ platform: linux/amd64
7
+ command:
8
+ --server-id=1
9
+ --log-bin
10
+ --log-slave-updates=ON
11
+ --gtid-mode=ON
12
+ --enforce-gtid-consistency=ON
13
+ --read-only=OFF
14
+ --max-connections=1000
15
+ --default-authentication-plugin=mysql_native_password
16
+ hostname: 'mysql-1'
17
+ volumes:
18
+ - ./scripts/mysql/writer:/docker-entrypoint-initdb.d
19
+ environment:
20
+ MYSQL_ROOT_PASSWORD: password
21
+ MYSQL_HOST: mysql-1
22
+ ports:
23
+ - "13006:3306"
24
+ # Reader
25
+ mysql-2:
26
+ container_name: mysql-2
27
+ image: percona:8.0
28
+ platform: linux/amd64
29
+ command:
30
+ --server-id=2
31
+ --log-bin
32
+ --log-slave-updates=ON
33
+ --gtid-mode=ON
34
+ --enforce-gtid-consistency=ON
35
+ --read-only=ON
36
+ --max-connections=1000
37
+ --default-authentication-plugin=mysql_native_password
38
+ hostname: 'mysql-2'
39
+ volumes:
40
+ - ./scripts/mysql/reader:/docker-entrypoint-initdb.d
41
+ environment:
42
+ MYSQL_ROOT_PASSWORD: password
43
+ MYSQL_HOST: mysql-2
44
+ ports:
45
+ - "13007:3306"
46
+ # Proxysql
47
+ proxysql:
48
+ container_name: proxysql
49
+ image: proxysql/proxysql:2.0.11
50
+ platform: linux/amd64
51
+ volumes:
52
+ - ./scripts/proxysql/proxysql.cnf:/etc/proxysql.cnf
53
+ command: "proxysql -c /etc/proxysql.cnf -f --idle-threads"
54
+ ports:
55
+ - "13005:3306"
56
+ - "6032:6032"
57
+ toxiproxy:
58
+ container_name: toxiproxy
59
+ image: "ghcr.io/shopify/toxiproxy"
60
+ ports:
61
+ - "8474:8474"
62
+ - "22220:22220"
63
+ - "22222:22222"
data/docker-compose.yml CHANGED
@@ -19,7 +19,7 @@ services:
19
19
  MYSQL_ROOT_PASSWORD: password
20
20
  MYSQL_HOST: mysql-1
21
21
  ports:
22
- - "33006:3306"
22
+ - "13006:3306"
23
23
  # Reader
24
24
  mysql-2:
25
25
  container_name: mysql-2
@@ -40,7 +40,7 @@ services:
40
40
  MYSQL_ROOT_PASSWORD: password
41
41
  MYSQL_HOST: mysql-2
42
42
  ports:
43
- - "33007:3306"
43
+ - "13007:3306"
44
44
  # Proxysql
45
45
  proxysql:
46
46
  container_name: proxysql
@@ -49,7 +49,7 @@ services:
49
49
  - ./scripts/proxysql/proxysql.cnf:/etc/proxysql.cnf
50
50
  command: "proxysql -c /etc/proxysql.cnf -f --idle-threads"
51
51
  ports:
52
- - "33005:3306"
52
+ - "13005:3306"
53
53
  - "6032:6032"
54
54
  toxiproxy:
55
55
  container_name: toxiproxy
@@ -3,5 +3,6 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "activerecord", "6.1.0"
6
+ gem "activerecord-trilogy-adapter"
6
7
 
7
8
  gemspec path: "../"
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- lhm-shopify (4.0.0)
4
+ lhm-shopify (4.1.0)
5
5
  retriable (>= 3.0.0)
6
6
 
7
7
  GEM
@@ -12,6 +12,9 @@ GEM
12
12
  activerecord (6.1.0)
13
13
  activemodel (= 6.1.0)
14
14
  activesupport (= 6.1.0)
15
+ activerecord-trilogy-adapter (3.1.2)
16
+ activerecord (>= 6.0.a, < 7.1.a)
17
+ trilogy (>= 2.4.0)
15
18
  activesupport (6.1.0)
16
19
  concurrent-ruby (~> 1.0, >= 1.0.2)
17
20
  i18n (>= 1.6, < 2)
@@ -43,9 +46,10 @@ GEM
43
46
  simplecov_json_formatter (0.1.4)
44
47
  thor (1.2.2)
45
48
  toxiproxy (2.0.2)
49
+ trilogy (2.6.0)
46
50
  tzinfo (2.0.6)
47
51
  concurrent-ruby (~> 1.0)
48
- zeitwerk (2.6.11)
52
+ zeitwerk (2.6.12)
49
53
 
50
54
  PLATFORMS
51
55
  arm64-darwin-21
@@ -55,6 +59,7 @@ PLATFORMS
55
59
 
56
60
  DEPENDENCIES
57
61
  activerecord (= 6.1.0)
62
+ activerecord-trilogy-adapter
58
63
  after_do
59
64
  appraisal
60
65
  byebug
@@ -65,6 +70,7 @@ DEPENDENCIES
65
70
  rake
66
71
  simplecov
67
72
  toxiproxy
73
+ trilogy
68
74
 
69
75
  BUNDLED WITH
70
76
  2.2.22
@@ -3,5 +3,6 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "activerecord", "7.0.8"
6
+ gem "activerecord-trilogy-adapter"
6
7
 
7
8
  gemspec path: "../"
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- lhm-shopify (4.0.0)
4
+ lhm-shopify (4.1.0)
5
5
  retriable (>= 3.0.0)
6
6
 
7
7
  GEM
@@ -12,6 +12,9 @@ GEM
12
12
  activerecord (7.0.8)
13
13
  activemodel (= 7.0.8)
14
14
  activesupport (= 7.0.8)
15
+ activerecord-trilogy-adapter (3.1.2)
16
+ activerecord (>= 6.0.a, < 7.1.a)
17
+ trilogy (>= 2.4.0)
15
18
  activesupport (7.0.8)
16
19
  concurrent-ruby (~> 1.0, >= 1.0.2)
17
20
  i18n (>= 1.6, < 2)
@@ -42,6 +45,7 @@ GEM
42
45
  simplecov_json_formatter (0.1.4)
43
46
  thor (1.2.2)
44
47
  toxiproxy (2.0.2)
48
+ trilogy (2.6.0)
45
49
  tzinfo (2.0.6)
46
50
  concurrent-ruby (~> 1.0)
47
51
 
@@ -53,6 +57,7 @@ PLATFORMS
53
57
 
54
58
  DEPENDENCIES
55
59
  activerecord (= 7.0.8)
60
+ activerecord-trilogy-adapter
56
61
  after_do
57
62
  appraisal
58
63
  byebug
@@ -63,6 +68,7 @@ DEPENDENCIES
63
68
  rake
64
69
  simplecov
65
70
  toxiproxy
71
+ trilogy
66
72
 
67
73
  BUNDLED WITH
68
74
  2.2.22
@@ -2,6 +2,6 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "activerecord", "6.0.3"
5
+ gem "activerecord", "7.1.1"
6
6
 
7
7
  gemspec path: "../"
@@ -1,19 +1,19 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- lhm-shopify (4.0.0)
4
+ lhm-shopify (4.1.0)
5
5
  retriable (>= 3.0.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- activemodel (7.1.0.beta1)
11
- activesupport (= 7.1.0.beta1)
12
- activerecord (7.1.0.beta1)
13
- activemodel (= 7.1.0.beta1)
14
- activesupport (= 7.1.0.beta1)
10
+ activemodel (7.1.1)
11
+ activesupport (= 7.1.1)
12
+ activerecord (7.1.1)
13
+ activemodel (= 7.1.1)
14
+ activesupport (= 7.1.1)
15
15
  timeout (>= 0.4.0)
16
- activesupport (7.1.0.beta1)
16
+ activesupport (7.1.1)
17
17
  base64
18
18
  bigdecimal
19
19
  concurrent-ruby (~> 1.0, >= 1.0.2)
@@ -55,6 +55,7 @@ GEM
55
55
  thor (1.2.2)
56
56
  timeout (0.4.0)
57
57
  toxiproxy (2.0.2)
58
+ trilogy (2.6.0)
58
59
  tzinfo (2.0.6)
59
60
  concurrent-ruby (~> 1.0)
60
61
 
@@ -65,7 +66,7 @@ PLATFORMS
65
66
  x86_64-linux
66
67
 
67
68
  DEPENDENCIES
68
- activerecord (= 7.1.0.beta1)
69
+ activerecord (= 7.1.1)
69
70
  after_do
70
71
  appraisal
71
72
  byebug
@@ -76,6 +77,7 @@ DEPENDENCIES
76
77
  rake
77
78
  simplecov
78
79
  toxiproxy
80
+ trilogy
79
81
 
80
82
  BUNDLED WITH
81
83
  2.2.22
data/lhm.gemspec CHANGED
@@ -31,6 +31,7 @@ Gem::Specification.new do |s|
31
31
  s.add_development_dependency 'after_do'
32
32
  s.add_development_dependency 'rake'
33
33
  s.add_development_dependency 'mysql2'
34
+ s.add_development_dependency 'trilogy'
34
35
  s.add_development_dependency 'simplecov'
35
36
  s.add_development_dependency 'toxiproxy'
36
37
  s.add_development_dependency 'appraisal'
@@ -26,14 +26,14 @@ module Lhm
26
26
  end
27
27
 
28
28
  def atomic_switch
29
- "rename table `#{ @origin.name }` to `#{ @migration.archive_name }`, " \
30
- "`#{ @destination.name }` to `#{ @origin.name }`"
29
+ "RENAME TABLE `#{ @origin.name }` TO `#{ @migration.archive_name }`, " \
30
+ "`#{ @destination.name }` TO `#{ @origin.name }`"
31
31
  end
32
32
 
33
33
  def validate
34
34
  unless @connection.data_source_exists?(@origin.name) &&
35
35
  @connection.data_source_exists?(@destination.name)
36
- error "`#{ @origin.name }` and `#{ @destination.name }` must exist"
36
+ error "`#{ @origin.name }` AND `#{ @destination.name }` MUST EXIST"
37
37
  end
38
38
  end
39
39
 
data/lib/lhm/chunker.rb CHANGED
@@ -79,9 +79,9 @@ module Lhm
79
79
  private
80
80
 
81
81
  def raise_on_non_pk_duplicate_warning
82
- @connection.execute("show warnings", should_retry: true, log_prefix: LOG_PREFIX).each do |level, code, message|
83
- unless message.match?(/Duplicate entry .+ for key 'PRIMARY'/)
84
- m = "Unexpected warning found for inserted row: #{message}"
82
+ @connection.select_all("SHOW WARNINGS", should_retry: true, log_prefix: LOG_PREFIX).each do |row|
83
+ unless row["Message"].match?(/Duplicate entry .+ for key 'PRIMARY'/)
84
+ m = "Unexpected warning found for inserted row: #{row["Message"]}"
85
85
  Lhm.logger.warn(m)
86
86
  raise Error.new(m) if @raise_on_warnings
87
87
  end
@@ -100,7 +100,7 @@ module Lhm
100
100
  end
101
101
 
102
102
  def upper_id(next_id, stride)
103
- sql = "select id from `#{ @migration.origin_name }` where id >= #{ next_id } order by id limit 1 offset #{ stride - 1}"
103
+ sql = "SELECT id FROM `#{ @migration.origin_name }` WHERE id >= #{ next_id } ORDER BY id LIMIT 1 OFFSET #{ stride - 1}"
104
104
  top = @connection.select_value(sql, should_retry: true, log_prefix: LOG_PREFIX)
105
105
 
106
106
  [top ? top.to_i : @limit, @limit].min
@@ -75,6 +75,14 @@ module Lhm
75
75
  end
76
76
  end
77
77
 
78
+ def select_all(query, should_retry: false, log_prefix: nil)
79
+ if should_retry
80
+ exec_with_retries(:select_all, query, log_prefix)
81
+ else
82
+ exec(:select_all, query)
83
+ end
84
+ end
85
+
78
86
  private
79
87
 
80
88
  def exec(method, sql)
@@ -105,4 +113,4 @@ module Lhm
105
113
  lhm_stack.at(first_candidate_index)
106
114
  end
107
115
  end
108
- end
116
+ end
data/lib/lhm/sql_retry.rb CHANGED
@@ -105,9 +105,7 @@ module Lhm
105
105
  def mysql_single_value(name)
106
106
  query = Lhm::ProxySQLHelper.tagged("SELECT #{name} LIMIT 1")
107
107
 
108
- @connection.execute(query).to_a.first.tap do |record|
109
- return record&.first
110
- end
108
+ @connection.select_value(query)
111
109
  end
112
110
 
113
111
  def same_host_as_initial?
@@ -140,32 +138,22 @@ module Lhm
140
138
  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
139
  return false
142
140
  end
143
- rescue StandardError => e
141
+ rescue ActiveRecord::ConnectionNotEstablished
144
142
  # Retry if ActiveRecord cannot reach host
145
- next if /Lost connection to MySQL server at 'reading initial communication packet'/ === e.message
143
+ next
144
+ rescue StandardError => e
146
145
  log_with_prefix("Encountered error: [#{e.class}] #{e.message}. Will stop reconnection procedure.", :info)
147
146
  return false
148
147
  end
149
148
  end
149
+
150
150
  false
151
151
  end
152
152
 
153
153
  # For a full list of configuration options see https://github.com/kamui/retriable
154
154
  def default_retry_config
155
155
  {
156
- on: {
157
- StandardError => [
158
- /Lock wait timeout exceeded/,
159
- /Timeout waiting for a response from the last query/,
160
- /Deadlock found when trying to get lock/,
161
- /Query execution was interrupted/,
162
- /Lost connection to MySQL server during query/,
163
- /Max connect timeout reached/,
164
- /Unknown MySQL server host/,
165
- /connection is locked to hostgroup/,
166
- /The MySQL server is running with the --read-only option so it cannot execute this statement/,
167
- ]
168
- },
156
+ on: retriable_mysql2_errors || retriable_trilogy_errors,
169
157
  multiplier: 1, # each successive interval grows by this factor
170
158
  base_interval: 1, # the initial interval in seconds between tries.
171
159
  tries: 20, # Number of attempts to make at running your code block (includes initial attempt).
@@ -176,5 +164,35 @@ module Lhm
176
164
  end
177
165
  }.freeze
178
166
  end
167
+
168
+ def retriable_mysql2_errors
169
+ return unless defined?(Mysql2::Error)
170
+
171
+ {
172
+ StandardError => [
173
+ /Lock wait timeout exceeded/,
174
+ /Timeout waiting for a response from the last query/,
175
+ /Deadlock found when trying to get lock/,
176
+ /Query execution was interrupted/,
177
+ /Lost connection to MySQL server during query/,
178
+ /Max connect timeout reached/,
179
+ /Unknown MySQL server host/,
180
+ /connection is locked to hostgroup/,
181
+ /The MySQL server is running with the --read-only option so it cannot execute this statement/,
182
+ ],
183
+ }
184
+ end
185
+
186
+ def retriable_trilogy_errors
187
+ return unless defined?(Trilogy::BaseError)
188
+
189
+ {
190
+ ActiveRecord::StatementInvalid => [
191
+ # Those sometimes appear as Trilogy::TimeoutError, and sometimes as ActiveRecord::StatementInvalid
192
+ /Lock wait timeout exceeded/,
193
+ ],
194
+ Trilogy::ConnectionError => nil,
195
+ }
196
+ end
179
197
  end
180
198
  end