lhm-shopify 3.4.0 → 3.5.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|