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.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +24 -15
  3. data/.gitignore +1 -6
  4. data/Appraisals +24 -0
  5. data/CHANGELOG.md +30 -0
  6. data/Gemfile.lock +66 -0
  7. data/README.md +55 -4
  8. data/Rakefile +11 -0
  9. data/dev.yml +31 -6
  10. data/docker-compose.yml +58 -0
  11. data/gemfiles/activerecord_5.2.gemfile +9 -0
  12. data/gemfiles/activerecord_5.2.gemfile.lock +65 -0
  13. data/gemfiles/activerecord_6.0.gemfile +7 -0
  14. data/gemfiles/activerecord_6.0.gemfile.lock +67 -0
  15. data/gemfiles/activerecord_6.1.gemfile +7 -0
  16. data/gemfiles/activerecord_6.1.gemfile.lock +66 -0
  17. data/gemfiles/activerecord_7.0.0.alpha2.gemfile +7 -0
  18. data/gemfiles/activerecord_7.0.0.alpha2.gemfile.lock +64 -0
  19. data/lhm.gemspec +7 -3
  20. data/lib/lhm/atomic_switcher.rb +5 -11
  21. data/lib/lhm/chunk_insert.rb +7 -10
  22. data/lib/lhm/chunker.rb +21 -10
  23. data/lib/lhm/cleanup/current.rb +9 -12
  24. data/lib/lhm/connection.rb +108 -0
  25. data/lib/lhm/entangler.rb +8 -13
  26. data/lib/lhm/invoker.rb +6 -4
  27. data/lib/lhm/locked_switcher.rb +2 -0
  28. data/lib/lhm/migrator.rb +2 -0
  29. data/lib/lhm/printer.rb +10 -6
  30. data/lib/lhm/proxysql_helper.rb +10 -0
  31. data/lib/lhm/sql_retry.rb +129 -10
  32. data/lib/lhm/throttler/slave_lag.rb +19 -2
  33. data/lib/lhm/version.rb +1 -1
  34. data/lib/lhm.rb +41 -16
  35. data/scripts/helpers/wait-for-dbs.sh +21 -0
  36. data/scripts/mysql/reader/create_replication.sql +10 -0
  37. data/scripts/mysql/writer/create_test_db.sql +1 -0
  38. data/scripts/mysql/writer/create_users.sql +6 -0
  39. data/scripts/proxysql/proxysql.cnf +117 -0
  40. data/spec/integration/atomic_switcher_spec.rb +53 -17
  41. data/spec/integration/chunk_insert_spec.rb +3 -2
  42. data/spec/integration/chunker_spec.rb +18 -16
  43. data/spec/integration/cleanup_spec.rb +49 -38
  44. data/spec/integration/database.yml +25 -0
  45. data/spec/integration/entangler_spec.rb +7 -5
  46. data/spec/integration/integration_helper.rb +25 -10
  47. data/spec/integration/lhm_spec.rb +114 -40
  48. data/spec/integration/lock_wait_timeout_spec.rb +2 -2
  49. data/spec/integration/locked_switcher_spec.rb +4 -4
  50. data/spec/integration/proxysql_spec.rb +34 -0
  51. data/spec/integration/sql_retry/db_connection_helper.rb +52 -0
  52. data/spec/integration/sql_retry/lock_wait_spec.rb +8 -6
  53. data/spec/integration/sql_retry/lock_wait_timeout_test_helper.rb +17 -4
  54. data/spec/integration/sql_retry/proxysql_helper.rb +22 -0
  55. data/spec/integration/sql_retry/retry_with_proxysql_spec.rb +109 -0
  56. data/spec/integration/table_spec.rb +11 -19
  57. data/spec/integration/toxiproxy_helper.rb +40 -0
  58. data/spec/test_helper.rb +24 -0
  59. data/spec/unit/atomic_switcher_spec.rb +4 -6
  60. data/spec/unit/chunk_insert_spec.rb +7 -2
  61. data/spec/unit/chunker_spec.rb +47 -42
  62. data/spec/unit/connection_spec.rb +111 -0
  63. data/spec/unit/entangler_spec.rb +85 -22
  64. data/spec/unit/intersection_spec.rb +4 -4
  65. data/spec/unit/lhm_spec.rb +23 -6
  66. data/spec/unit/locked_switcher_spec.rb +13 -18
  67. data/spec/unit/migrator_spec.rb +17 -19
  68. data/spec/unit/printer_spec.rb +14 -26
  69. data/spec/unit/sql_helper_spec.rb +8 -12
  70. data/spec/unit/table_spec.rb +5 -5
  71. data/spec/unit/throttler/slave_lag_spec.rb +14 -9
  72. data/spec/unit/throttler_spec.rb +12 -12
  73. data/spec/unit/unit_helper.rb +13 -0
  74. metadata +85 -14
  75. data/bin/.gitkeep +0 -0
  76. data/dbdeployer/config.json +0 -32
  77. data/dbdeployer/install.sh +0 -64
  78. data/gemfiles/ar-2.3_mysql.gemfile +0 -6
  79. data/gemfiles/ar-3.2_mysql.gemfile +0 -5
  80. data/gemfiles/ar-3.2_mysql2.gemfile +0 -5
  81. data/gemfiles/ar-4.0_mysql2.gemfile +0 -5
  82. data/gemfiles/ar-4.1_mysql2.gemfile +0 -5
  83. data/gemfiles/ar-4.2_mysql2.gemfile +0 -5
  84. 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
- # -*- encoding: utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
- lib = File.expand_path('../lib', __FILE__)
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
@@ -16,17 +16,13 @@ module Lhm
16
16
 
17
17
  attr_reader :connection
18
18
 
19
- def initialize(migration, connection = nil, options = {})
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
- @connection.data_source_exists?(@destination.name)
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
- @retry_helper.with_retries do |retriable_connection|
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
@@ -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
- def initialize(migration, connection, lowest, highest, options = {})
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
- @retry_helper = SqlRetry.new(
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
- @retry_helper.with_retries do |retriable_connection|
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, @options).insert_and_return_count_of_rows_created
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
- @printer.notify(bottom, @limit)
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.query("show warnings").each do |level, code, message|
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 = @retry_helper.with_retries do |retriable_connection|
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
@@ -4,17 +4,15 @@ require 'lhm/sql_retry'
4
4
  module Lhm
5
5
  module Cleanup
6
6
  class Current
7
- def initialize(run, origin_table_name, connection, options = {})
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
- @retry_helper = SqlRetry.new(
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
- @retry_helper.with_retries do |retriable_connection|
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
- puts "The following DDLs would be executed:"
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, options = {})
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
- @retry_helper.with_retries do |retriable_connection|
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
- @retry_helper.with_retries do |retriable_connection|
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
- 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
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, options)
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, options).run
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
@@ -22,6 +22,8 @@ module Lhm
22
22
 
23
23
  attr_reader :connection
24
24
 
25
+ LOG_PREFIX = "LockedSwitcher"
26
+
25
27
  def initialize(migration, connection = nil)
26
28
  @migration = migration
27
29
  @connection = connection
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 < Base
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
- write("failed: #{e}")
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
- @output.write "\r#{message}" + (' ' * extra)
49
+ Lhm.logger.info(message)
46
50
  end
47
51
  end
48
52
 
@@ -0,0 +1,10 @@
1
+ module Lhm
2
+ module ProxySQLHelper
3
+ extend self
4
+ ANNOTATION = "/*maintenance:lhm*/"
5
+
6
+ def tagged(sql)
7
+ "#{sql} #{ANNOTATION}"
8
+ end
9
+ end
10
+ end