lhm-shopify 3.4.0 → 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/.github/workflows/test.yml +24 -15
- data/.gitignore +1 -6
- data/Appraisals +24 -0
- data/CHANGELOG.md +30 -0
- data/Gemfile.lock +66 -0
- data/README.md +55 -4
- data/Rakefile +11 -0
- data/dev.yml +31 -6
- data/docker-compose.yml +58 -0
- data/gemfiles/activerecord_5.2.gemfile +9 -0
- data/gemfiles/activerecord_5.2.gemfile.lock +65 -0
- data/gemfiles/activerecord_6.0.gemfile +7 -0
- data/gemfiles/activerecord_6.0.gemfile.lock +67 -0
- data/gemfiles/activerecord_6.1.gemfile +7 -0
- data/gemfiles/activerecord_6.1.gemfile.lock +66 -0
- data/gemfiles/activerecord_7.0.0.alpha2.gemfile +7 -0
- data/gemfiles/activerecord_7.0.0.alpha2.gemfile.lock +64 -0
- data/lhm.gemspec +7 -3
- data/lib/lhm/atomic_switcher.rb +5 -11
- data/lib/lhm/chunk_insert.rb +7 -10
- data/lib/lhm/chunker.rb +21 -10
- data/lib/lhm/cleanup/current.rb +9 -12
- data/lib/lhm/connection.rb +108 -0
- data/lib/lhm/entangler.rb +8 -13
- data/lib/lhm/invoker.rb +6 -4
- data/lib/lhm/locked_switcher.rb +2 -0
- data/lib/lhm/migrator.rb +2 -0
- data/lib/lhm/printer.rb +10 -6
- data/lib/lhm/proxysql_helper.rb +10 -0
- data/lib/lhm/sql_retry.rb +129 -10
- data/lib/lhm/throttler/slave_lag.rb +19 -2
- data/lib/lhm/version.rb +1 -1
- data/lib/lhm.rb +41 -16
- data/scripts/helpers/wait-for-dbs.sh +21 -0
- data/scripts/mysql/reader/create_replication.sql +10 -0
- data/scripts/mysql/writer/create_test_db.sql +1 -0
- data/scripts/mysql/writer/create_users.sql +6 -0
- data/scripts/proxysql/proxysql.cnf +117 -0
- data/spec/integration/atomic_switcher_spec.rb +53 -17
- data/spec/integration/chunk_insert_spec.rb +3 -2
- data/spec/integration/chunker_spec.rb +18 -16
- data/spec/integration/cleanup_spec.rb +49 -38
- data/spec/integration/database.yml +25 -0
- data/spec/integration/entangler_spec.rb +7 -5
- data/spec/integration/integration_helper.rb +25 -10
- data/spec/integration/lhm_spec.rb +114 -40
- data/spec/integration/lock_wait_timeout_spec.rb +2 -2
- data/spec/integration/locked_switcher_spec.rb +4 -4
- data/spec/integration/proxysql_spec.rb +34 -0
- data/spec/integration/sql_retry/db_connection_helper.rb +52 -0
- data/spec/integration/sql_retry/lock_wait_spec.rb +8 -6
- data/spec/integration/sql_retry/lock_wait_timeout_test_helper.rb +17 -4
- data/spec/integration/sql_retry/proxysql_helper.rb +22 -0
- data/spec/integration/sql_retry/retry_with_proxysql_spec.rb +109 -0
- data/spec/integration/table_spec.rb +11 -19
- data/spec/integration/toxiproxy_helper.rb +40 -0
- data/spec/test_helper.rb +24 -0
- data/spec/unit/atomic_switcher_spec.rb +4 -6
- data/spec/unit/chunk_insert_spec.rb +7 -2
- data/spec/unit/chunker_spec.rb +47 -42
- data/spec/unit/connection_spec.rb +111 -0
- data/spec/unit/entangler_spec.rb +85 -22
- data/spec/unit/intersection_spec.rb +4 -4
- data/spec/unit/lhm_spec.rb +23 -6
- data/spec/unit/locked_switcher_spec.rb +13 -18
- data/spec/unit/migrator_spec.rb +17 -19
- data/spec/unit/printer_spec.rb +14 -26
- data/spec/unit/sql_helper_spec.rb +8 -12
- data/spec/unit/table_spec.rb +5 -5
- data/spec/unit/throttler/slave_lag_spec.rb +14 -9
- data/spec/unit/throttler_spec.rb +12 -12
- data/spec/unit/unit_helper.rb +13 -0
- metadata +85 -14
- data/bin/.gitkeep +0 -0
- data/dbdeployer/config.json +0 -32
- data/dbdeployer/install.sh +0 -64
- data/gemfiles/ar-2.3_mysql.gemfile +0 -6
- data/gemfiles/ar-3.2_mysql.gemfile +0 -5
- data/gemfiles/ar-3.2_mysql2.gemfile +0 -5
- data/gemfiles/ar-4.0_mysql2.gemfile +0 -5
- data/gemfiles/ar-4.1_mysql2.gemfile +0 -5
- data/gemfiles/ar-4.2_mysql2.gemfile +0 -5
- data/gemfiles/ar-5.0_mysql2.gemfile +0 -5
@@ -0,0 +1,64 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ..
|
3
|
+
specs:
|
4
|
+
lhm-shopify (3.5.5)
|
5
|
+
retriable (>= 3.0.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activemodel (7.0.0.alpha2)
|
11
|
+
activesupport (= 7.0.0.alpha2)
|
12
|
+
activerecord (7.0.0.alpha2)
|
13
|
+
activemodel (= 7.0.0.alpha2)
|
14
|
+
activesupport (= 7.0.0.alpha2)
|
15
|
+
activesupport (7.0.0.alpha2)
|
16
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
17
|
+
i18n (>= 1.6, < 2)
|
18
|
+
minitest (>= 5.1)
|
19
|
+
tzinfo (~> 2.0)
|
20
|
+
after_do (0.4.0)
|
21
|
+
appraisal (2.4.1)
|
22
|
+
bundler
|
23
|
+
rake
|
24
|
+
thor (>= 0.14.0)
|
25
|
+
byebug (11.1.3)
|
26
|
+
concurrent-ruby (1.1.9)
|
27
|
+
docile (1.4.0)
|
28
|
+
i18n (1.8.11)
|
29
|
+
concurrent-ruby (~> 1.0)
|
30
|
+
minitest (5.14.4)
|
31
|
+
mocha (1.13.0)
|
32
|
+
mysql2 (0.5.3)
|
33
|
+
rake (13.0.6)
|
34
|
+
retriable (3.1.2)
|
35
|
+
simplecov (0.21.2)
|
36
|
+
docile (~> 1.1)
|
37
|
+
simplecov-html (~> 0.11)
|
38
|
+
simplecov_json_formatter (~> 0.1)
|
39
|
+
simplecov-html (0.12.3)
|
40
|
+
simplecov_json_formatter (0.1.3)
|
41
|
+
thor (1.1.0)
|
42
|
+
toxiproxy (2.0.0)
|
43
|
+
tzinfo (2.0.4)
|
44
|
+
concurrent-ruby (~> 1.0)
|
45
|
+
|
46
|
+
PLATFORMS
|
47
|
+
x86_64-darwin-20
|
48
|
+
x86_64-linux
|
49
|
+
|
50
|
+
DEPENDENCIES
|
51
|
+
activerecord (= 7.0.0.alpha2)
|
52
|
+
after_do
|
53
|
+
appraisal
|
54
|
+
byebug
|
55
|
+
lhm-shopify!
|
56
|
+
minitest
|
57
|
+
mocha
|
58
|
+
mysql2
|
59
|
+
rake
|
60
|
+
simplecov
|
61
|
+
toxiproxy
|
62
|
+
|
63
|
+
BUNDLED WITH
|
64
|
+
2.2.22
|
data/lhm.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
lib = File.expand_path('
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
4
|
$:.unshift(lib) unless $:.include?(lib)
|
5
5
|
|
6
6
|
require 'lhm/version'
|
@@ -25,10 +25,14 @@ Gem::Specification.new do |s|
|
|
25
25
|
|
26
26
|
s.add_dependency 'retriable', '>= 3.0.0'
|
27
27
|
|
28
|
+
s.add_development_dependency 'activerecord'
|
28
29
|
s.add_development_dependency 'minitest'
|
29
30
|
s.add_development_dependency 'mocha'
|
31
|
+
s.add_development_dependency 'after_do'
|
30
32
|
s.add_development_dependency 'rake'
|
31
|
-
s.add_development_dependency 'activerecord'
|
32
33
|
s.add_development_dependency 'mysql2'
|
33
34
|
s.add_development_dependency 'simplecov'
|
35
|
+
s.add_development_dependency 'toxiproxy'
|
36
|
+
s.add_development_dependency 'appraisal'
|
37
|
+
s.add_development_dependency 'byebug'
|
34
38
|
end
|
data/lib/lhm/atomic_switcher.rb
CHANGED
@@ -16,17 +16,13 @@ module Lhm
|
|
16
16
|
|
17
17
|
attr_reader :connection
|
18
18
|
|
19
|
-
|
19
|
+
LOG_PREFIX = "AtomicSwitcher"
|
20
|
+
|
21
|
+
def initialize(migration, connection = nil)
|
20
22
|
@migration = migration
|
21
23
|
@connection = connection
|
22
24
|
@origin = migration.origin
|
23
25
|
@destination = migration.destination
|
24
|
-
@retry_helper = SqlRetry.new(
|
25
|
-
@connection,
|
26
|
-
{
|
27
|
-
log_prefix: "AtomicSwitcher"
|
28
|
-
}.merge!(options.fetch(:retriable, {}))
|
29
|
-
)
|
30
26
|
end
|
31
27
|
|
32
28
|
def atomic_switch
|
@@ -36,7 +32,7 @@ module Lhm
|
|
36
32
|
|
37
33
|
def validate
|
38
34
|
unless @connection.data_source_exists?(@origin.name) &&
|
39
|
-
|
35
|
+
@connection.data_source_exists?(@destination.name)
|
40
36
|
error "`#{ @origin.name }` and `#{ @destination.name }` must exist"
|
41
37
|
end
|
42
38
|
end
|
@@ -44,9 +40,7 @@ module Lhm
|
|
44
40
|
private
|
45
41
|
|
46
42
|
def execute
|
47
|
-
@
|
48
|
-
retriable_connection.execute atomic_switch
|
49
|
-
end
|
43
|
+
@connection.execute(atomic_switch, should_retry: true, log_prefix: LOG_PREFIX)
|
50
44
|
end
|
51
45
|
end
|
52
46
|
end
|
data/lib/lhm/chunk_insert.rb
CHANGED
@@ -1,24 +1,21 @@
|
|
1
1
|
require 'lhm/sql_retry'
|
2
|
+
require 'lhm/proxysql_helper'
|
2
3
|
|
3
4
|
module Lhm
|
4
5
|
class ChunkInsert
|
5
|
-
|
6
|
+
|
7
|
+
LOG_PREFIX = "ChunkInsert"
|
8
|
+
|
9
|
+
def initialize(migration, connection, lowest, highest, retry_options = {})
|
6
10
|
@migration = migration
|
7
11
|
@connection = connection
|
8
12
|
@lowest = lowest
|
9
13
|
@highest = highest
|
10
|
-
@
|
11
|
-
@connection,
|
12
|
-
{
|
13
|
-
log_prefix: "Chunker Insert"
|
14
|
-
}.merge!(options.fetch(:retriable, {}))
|
15
|
-
)
|
14
|
+
@retry_options = retry_options
|
16
15
|
end
|
17
16
|
|
18
17
|
def insert_and_return_count_of_rows_created
|
19
|
-
@
|
20
|
-
retriable_connection.update sql
|
21
|
-
end
|
18
|
+
@connection.update(sql, should_retry: true, log_prefix: LOG_PREFIX)
|
22
19
|
end
|
23
20
|
|
24
21
|
def sql
|
data/lib/lhm/chunker.rb
CHANGED
@@ -13,6 +13,8 @@ module Lhm
|
|
13
13
|
|
14
14
|
attr_reader :connection
|
15
15
|
|
16
|
+
LOG_PREFIX = "Chunker"
|
17
|
+
|
16
18
|
# Copy from origin to destination in chunks of size `stride`.
|
17
19
|
# Use the `throttler` class to sleep between each stride.
|
18
20
|
def initialize(migration, connection = nil, options = {})
|
@@ -28,15 +30,16 @@ module Lhm
|
|
28
30
|
@start = @chunk_finder.start
|
29
31
|
@limit = @chunk_finder.limit
|
30
32
|
@printer = options[:printer] || Printer::Percentage.new
|
33
|
+
@retry_options = options[:retriable] || {}
|
31
34
|
@retry_helper = SqlRetry.new(
|
32
35
|
@connection,
|
33
|
-
|
34
|
-
log_prefix: "Chunker"
|
35
|
-
}.merge!(options.fetch(:retriable, {}))
|
36
|
+
retry_options: @retry_options
|
36
37
|
)
|
37
38
|
end
|
38
39
|
|
39
40
|
def execute
|
41
|
+
@start_time = Time.now
|
42
|
+
|
40
43
|
return if @chunk_finder.table_empty?
|
41
44
|
@next_to_insert = @start
|
42
45
|
while @next_to_insert <= @limit || (@start == @limit)
|
@@ -44,9 +47,16 @@ module Lhm
|
|
44
47
|
top = upper_id(@next_to_insert, stride)
|
45
48
|
verify_can_run
|
46
49
|
|
47
|
-
affected_rows = ChunkInsert.new(@migration, @connection, bottom, top, @
|
50
|
+
affected_rows = ChunkInsert.new(@migration, @connection, bottom, top, @retry_options).insert_and_return_count_of_rows_created
|
48
51
|
expected_rows = top - bottom + 1
|
49
52
|
|
53
|
+
# Only log the chunker progress every 5 minutes instead of every iteration
|
54
|
+
current_time = Time.now
|
55
|
+
if current_time - @start_time > (5 * 60)
|
56
|
+
Lhm.logger.info("Inserted #{affected_rows} rows into the destination table from #{bottom} to #{top}")
|
57
|
+
@start_time = current_time
|
58
|
+
end
|
59
|
+
|
50
60
|
if affected_rows < expected_rows
|
51
61
|
raise_on_non_pk_duplicate_warning
|
52
62
|
end
|
@@ -54,8 +64,10 @@ module Lhm
|
|
54
64
|
if @throttler && affected_rows > 0
|
55
65
|
@throttler.run
|
56
66
|
end
|
57
|
-
|
67
|
+
|
58
68
|
@next_to_insert = top + 1
|
69
|
+
@printer.notify(bottom, @limit)
|
70
|
+
|
59
71
|
break if @start == @limit
|
60
72
|
end
|
61
73
|
@printer.end
|
@@ -67,7 +79,7 @@ module Lhm
|
|
67
79
|
private
|
68
80
|
|
69
81
|
def raise_on_non_pk_duplicate_warning
|
70
|
-
@connection.
|
82
|
+
@connection.execute("show warnings", should_retry: true, log_prefix: LOG_PREFIX).each do |level, code, message|
|
71
83
|
unless message.match?(/Duplicate entry .+ for key 'PRIMARY'/)
|
72
84
|
m = "Unexpected warning found for inserted row: #{message}"
|
73
85
|
Lhm.logger.warn(m)
|
@@ -82,16 +94,14 @@ module Lhm
|
|
82
94
|
|
83
95
|
def verify_can_run
|
84
96
|
return unless @verifier
|
85
|
-
@retry_helper.with_retries do |retriable_connection|
|
97
|
+
@retry_helper.with_retries(log_prefix: LOG_PREFIX) do |retriable_connection|
|
86
98
|
raise "Verification failed, aborting early" if !@verifier.call(retriable_connection)
|
87
99
|
end
|
88
100
|
end
|
89
101
|
|
90
102
|
def upper_id(next_id, stride)
|
91
103
|
sql = "select id from `#{ @migration.origin_name }` where id >= #{ next_id } order by id limit 1 offset #{ stride - 1}"
|
92
|
-
top = @
|
93
|
-
retriable_connection.select_value(sql)
|
94
|
-
end
|
104
|
+
top = @connection.select_value(sql, should_retry: true, log_prefix: LOG_PREFIX)
|
95
105
|
|
96
106
|
[top ? top.to_i : @limit, @limit].min
|
97
107
|
end
|
@@ -100,5 +110,6 @@ module Lhm
|
|
100
110
|
return if @chunk_finder.table_empty?
|
101
111
|
@chunk_finder.validate
|
102
112
|
end
|
113
|
+
|
103
114
|
end
|
104
115
|
end
|
data/lib/lhm/cleanup/current.rb
CHANGED
@@ -4,17 +4,15 @@ require 'lhm/sql_retry'
|
|
4
4
|
module Lhm
|
5
5
|
module Cleanup
|
6
6
|
class Current
|
7
|
-
|
7
|
+
|
8
|
+
LOG_PREFIX = "Current"
|
9
|
+
|
10
|
+
def initialize(run, origin_table_name, connection, options={})
|
8
11
|
@run = run
|
9
12
|
@table_name = TableName.new(origin_table_name)
|
10
13
|
@connection = connection
|
11
14
|
@ddls = []
|
12
|
-
@
|
13
|
-
@connection,
|
14
|
-
{
|
15
|
-
log_prefix: "Cleanup::Current"
|
16
|
-
}.merge!(options.fetch(:retriable, {}))
|
17
|
-
)
|
15
|
+
@retry_config = options[:retriable] || {}
|
18
16
|
end
|
19
17
|
|
20
18
|
attr_reader :run, :connection, :ddls
|
@@ -59,15 +57,14 @@ module Lhm
|
|
59
57
|
|
60
58
|
def execute_ddls
|
61
59
|
ddls.each do |ddl|
|
62
|
-
|
63
|
-
retriable_connection.execute(ddl)
|
64
|
-
end
|
60
|
+
@connection.execute(ddl, should_retry: true, log_prefix: LOG_PREFIX)
|
65
61
|
end
|
62
|
+
Lhm.logger.info("Dropped triggers on #{@lhm_triggers_for_origin.join(', ')}")
|
63
|
+
Lhm.logger.info("Dropped tables #{@lhm_triggers_for_origin.join(', ')}")
|
66
64
|
end
|
67
65
|
|
68
66
|
def report_ddls
|
69
|
-
|
70
|
-
ddls.each { |ddl| puts ddl }
|
67
|
+
Lhm.logger.info("The following DDLs would be executed: #{ddls}")
|
71
68
|
end
|
72
69
|
end
|
73
70
|
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'forwardable'
|
3
|
+
require 'lhm/sql_retry'
|
4
|
+
|
5
|
+
module Lhm
|
6
|
+
# Lhm::Connection inherits from SingleDelegator. It will forward any unknown method calls to the ActiveRecord
|
7
|
+
# connection.
|
8
|
+
class Connection < SimpleDelegator
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
# Will delegate the following function to @sql_retry object, while leaving them accessible from the Lhm::Connection
|
12
|
+
# object
|
13
|
+
def_delegators :@sql_retry, :reconnect_with_consistent_host, :reconnect_with_consistent_host=, :retry_config=
|
14
|
+
|
15
|
+
alias ar_connection __getobj__
|
16
|
+
|
17
|
+
def initialize(connection:, options: {})
|
18
|
+
@sql_retry = Lhm::SqlRetry.new(
|
19
|
+
connection,
|
20
|
+
retry_options: options[:retriable] || {},
|
21
|
+
reconnect_with_consistent_host: options[:reconnect_with_consistent_host] || false
|
22
|
+
)
|
23
|
+
|
24
|
+
# Creates delegation for the ActiveRecord Connection
|
25
|
+
super(connection)
|
26
|
+
end
|
27
|
+
|
28
|
+
def ar_connection=(connection)
|
29
|
+
raise Lhm::Error.new("Lhm::Connection requires an active record connection to operate") if connection.nil?
|
30
|
+
|
31
|
+
@sql_retry.connection = connection
|
32
|
+
# Sets connection as the delegated object
|
33
|
+
__setobj__(connection)
|
34
|
+
end
|
35
|
+
|
36
|
+
# ActiveRecord::Base overridden methods to incorporate custom retry logic
|
37
|
+
# All other methods will be delegated
|
38
|
+
def execute(query, should_retry: false, log_prefix: nil)
|
39
|
+
if should_retry
|
40
|
+
exec_with_retries(:execute, query, log_prefix)
|
41
|
+
else
|
42
|
+
exec(:execute, query)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def update(query, should_retry: false, log_prefix: nil)
|
47
|
+
if should_retry
|
48
|
+
exec_with_retries(:update, query, log_prefix)
|
49
|
+
else
|
50
|
+
exec(:update, query)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def select_value(query, should_retry: false, log_prefix: nil)
|
55
|
+
if should_retry
|
56
|
+
exec_with_retries(:select_value, query, log_prefix)
|
57
|
+
else
|
58
|
+
exec(:select_value, query)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def select_values(query, should_retry: false, log_prefix: nil)
|
63
|
+
if should_retry
|
64
|
+
exec_with_retries(:select_values, query, log_prefix)
|
65
|
+
else
|
66
|
+
exec(:select_values, query)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def select_one(query, should_retry: false, log_prefix: nil)
|
71
|
+
if should_retry
|
72
|
+
exec_with_retries(:select_one, query, log_prefix)
|
73
|
+
else
|
74
|
+
exec(:select_one, query)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def exec(method, sql)
|
81
|
+
ar_connection.public_send(method, Lhm::ProxySQLHelper.tagged(sql))
|
82
|
+
end
|
83
|
+
|
84
|
+
def exec_with_retries(method, sql, log_prefix=nil)
|
85
|
+
effective_log_prefix = log_prefix || file
|
86
|
+
@sql_retry.with_retries(log_prefix: effective_log_prefix) do |conn|
|
87
|
+
conn.public_send(method, Lhm::ProxySQLHelper.tagged(sql))
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns camelized file name of caller (e.g. chunk_insert.rb -> ChunkInsert)
|
92
|
+
def file
|
93
|
+
# Find calling file and extract name
|
94
|
+
/[\/]*(\w+).rb:\d+:in/.match(relevant_caller)
|
95
|
+
name = $1&.camelize || "Connection"
|
96
|
+
"#{name}"
|
97
|
+
end
|
98
|
+
|
99
|
+
def relevant_caller
|
100
|
+
lhm_stack = caller.select { |x| x.include?("/lhm") }
|
101
|
+
first_candidate_index = lhm_stack.find_index { |line| !line.include?(__FILE__) }
|
102
|
+
|
103
|
+
# Find the file that called the `#execute` (fallbacks to current file)
|
104
|
+
return lhm_stack.first unless first_candidate_index
|
105
|
+
lhm_stack.at(first_candidate_index)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
data/lib/lhm/entangler.rb
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
require 'lhm/command'
|
5
5
|
require 'lhm/sql_helper'
|
6
6
|
require 'lhm/sql_retry'
|
7
|
+
require 'lhm/connection'
|
7
8
|
|
8
9
|
module Lhm
|
9
10
|
class Entangler
|
@@ -12,19 +13,15 @@ module Lhm
|
|
12
13
|
|
13
14
|
attr_reader :connection
|
14
15
|
|
16
|
+
LOG_PREFIX = "Entangler"
|
17
|
+
|
15
18
|
# Creates entanglement between two tables. All creates, updates and deletes
|
16
19
|
# to origin will be repeated on the destination table.
|
17
|
-
def initialize(migration, connection = nil
|
20
|
+
def initialize(migration, connection = nil)
|
18
21
|
@intersection = migration.intersection
|
19
22
|
@origin = migration.origin
|
20
23
|
@destination = migration.destination
|
21
24
|
@connection = connection
|
22
|
-
@retry_helper = SqlRetry.new(
|
23
|
-
@connection,
|
24
|
-
{
|
25
|
-
log_prefix: "Entangler"
|
26
|
-
}.merge!(options.fetch(:retriable, {}))
|
27
|
-
)
|
28
25
|
end
|
29
26
|
|
30
27
|
def entangle
|
@@ -90,18 +87,16 @@ module Lhm
|
|
90
87
|
|
91
88
|
def before
|
92
89
|
entangle.each do |stmt|
|
93
|
-
@
|
94
|
-
retriable_connection.execute(stmt)
|
95
|
-
end
|
90
|
+
@connection.execute(stmt, should_retry: true, log_prefix: LOG_PREFIX)
|
96
91
|
end
|
92
|
+
Lhm.logger.info("Created triggers on #{@origin.name}")
|
97
93
|
end
|
98
94
|
|
99
95
|
def after
|
100
96
|
untangle.each do |stmt|
|
101
|
-
@
|
102
|
-
retriable_connection.execute(stmt)
|
103
|
-
end
|
97
|
+
@connection.execute(stmt, should_retry: true, log_prefix: LOG_PREFIX)
|
104
98
|
end
|
99
|
+
Lhm.logger.info("Dropped triggers on #{@origin.name}")
|
105
100
|
end
|
106
101
|
|
107
102
|
def revert
|
data/lib/lhm/invoker.rb
CHANGED
@@ -16,8 +16,8 @@ module Lhm
|
|
16
16
|
class Invoker
|
17
17
|
include SqlHelper
|
18
18
|
LOCK_WAIT_TIMEOUT_DELTA = 10
|
19
|
-
|
20
|
-
|
19
|
+
INNODB_LOCK_WAIT_TIMEOUT_MAX = 1073741824.freeze # https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_lock_wait_timeout
|
20
|
+
LOCK_WAIT_TIMEOUT_MAX = 31536000.freeze # https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html
|
21
21
|
|
22
22
|
attr_reader :migrator, :connection
|
23
23
|
|
@@ -49,14 +49,14 @@ module Lhm
|
|
49
49
|
normalize_options(options)
|
50
50
|
set_session_lock_wait_timeouts
|
51
51
|
migration = @migrator.run
|
52
|
-
entangler = Entangler.new(migration, @connection
|
52
|
+
entangler = Entangler.new(migration, @connection)
|
53
53
|
|
54
54
|
entangler.run do
|
55
55
|
options[:verifier] ||= Proc.new { |conn| triggers_still_exist?(conn, entangler) }
|
56
56
|
Chunker.new(migration, @connection, options).run
|
57
57
|
raise "Required triggers do not exist" unless triggers_still_exist?(@connection, entangler)
|
58
58
|
if options[:atomic_switch]
|
59
|
-
AtomicSwitcher.new(migration, @connection
|
59
|
+
AtomicSwitcher.new(migration, @connection).run
|
60
60
|
else
|
61
61
|
LockedSwitcher.new(migration, @connection).run
|
62
62
|
end
|
@@ -90,6 +90,8 @@ module Lhm
|
|
90
90
|
options[:throttler] = Lhm.throttler
|
91
91
|
end
|
92
92
|
|
93
|
+
Lhm.connection.retry_config = options[:retriable] || {}
|
94
|
+
|
93
95
|
rescue => e
|
94
96
|
Lhm.logger.error "LHM run failed with exception=#{e.class} message=#{e.message}"
|
95
97
|
raise
|
data/lib/lhm/locked_switcher.rb
CHANGED
data/lib/lhm/migrator.rb
CHANGED
@@ -214,6 +214,8 @@ module Lhm
|
|
214
214
|
replacement = %{CREATE TABLE `#{ @origin.destination_name }`}
|
215
215
|
stmt = @origin.ddl.gsub(original, replacement)
|
216
216
|
@connection.execute(tagged(stmt))
|
217
|
+
|
218
|
+
Lhm.logger.info("Created destination table #{@origin.destination_name}")
|
217
219
|
end
|
218
220
|
|
219
221
|
def destination_read
|
data/lib/lhm/printer.rb
CHANGED
@@ -12,26 +12,30 @@ module Lhm
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
class Percentage
|
15
|
+
class Percentage
|
16
16
|
def initialize
|
17
|
-
super
|
18
17
|
@max_length = 0
|
19
18
|
end
|
20
19
|
|
21
20
|
def notify(lowest, highest)
|
22
21
|
return if !highest || highest == 0
|
22
|
+
|
23
|
+
# The argument lowest represents the next_to_insert row id, and highest represents the
|
24
|
+
# maximum id upto which chunker has to copy the data.
|
25
|
+
# If all the rows are inserted upto highest, then lowest passed here from chunker was
|
26
|
+
# highest + 1, which leads to the printer printing the progress > 100%.
|
27
|
+
return if lowest >= highest
|
28
|
+
|
23
29
|
message = "%.2f%% (#{lowest}/#{highest}) complete" % (lowest.to_f / highest * 100.0)
|
24
30
|
write(message)
|
25
31
|
end
|
26
32
|
|
27
33
|
def end
|
28
34
|
write('100% complete')
|
29
|
-
@output.write "\n"
|
30
35
|
end
|
31
36
|
|
32
37
|
def exception(e)
|
33
|
-
|
34
|
-
@output.write "\n"
|
38
|
+
Lhm.logger.error("failed: #{e}")
|
35
39
|
end
|
36
40
|
|
37
41
|
private
|
@@ -42,7 +46,7 @@ module Lhm
|
|
42
46
|
extra = 0
|
43
47
|
end
|
44
48
|
|
45
|
-
|
49
|
+
Lhm.logger.info(message)
|
46
50
|
end
|
47
51
|
end
|
48
52
|
|