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