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,109 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'mysql2'
|
3
|
+
require 'lhm'
|
4
|
+
require 'toxiproxy'
|
5
|
+
|
6
|
+
require 'integration/sql_retry/lock_wait_timeout_test_helper'
|
7
|
+
require 'integration/sql_retry/db_connection_helper'
|
8
|
+
require 'integration/sql_retry/proxysql_helper'
|
9
|
+
require 'integration/toxiproxy_helper'
|
10
|
+
|
11
|
+
describe Lhm::SqlRetry, "ProxiSQL tests for LHM retry" do
|
12
|
+
include ToxiproxyHelper
|
13
|
+
|
14
|
+
before(:each) do
|
15
|
+
@old_logger = Lhm.logger
|
16
|
+
@logger = StringIO.new
|
17
|
+
Lhm.logger = Logger.new(@logger)
|
18
|
+
|
19
|
+
@connection = DBConnectionHelper::new_mysql_connection(:proxysql, true, true)
|
20
|
+
|
21
|
+
@lhm_retry = Lhm::SqlRetry.new(@connection, retry_options: {},
|
22
|
+
reconnect_with_consistent_host: true)
|
23
|
+
end
|
24
|
+
|
25
|
+
after(:each) do
|
26
|
+
# Restore default logger
|
27
|
+
Lhm.logger = @old_logger
|
28
|
+
end
|
29
|
+
|
30
|
+
it "Will abort if service is down" do
|
31
|
+
|
32
|
+
e = assert_raises Lhm::Error do
|
33
|
+
#Service down
|
34
|
+
Toxiproxy[:mysql_proxysql].down do
|
35
|
+
@lhm_retry.with_retries do |retriable_connection|
|
36
|
+
retriable_connection.execute("INSERT INTO #{DBConnectionHelper.test_table_name} (id) VALUES (2000)")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
assert_equal Lhm::Error, e.class
|
41
|
+
assert_match(/LHM tried the reconnection procedure but failed. Aborting/, e.message)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "Will retry until connection is achieved" do
|
45
|
+
|
46
|
+
#Creating a network blip
|
47
|
+
ToxiproxyHelper.with_kill_and_restart(:mysql_proxysql, 2.seconds) do
|
48
|
+
@lhm_retry.with_retries do |retriable_connection|
|
49
|
+
retriable_connection.execute("INSERT INTO #{DBConnectionHelper.test_table_name} (id) VALUES (2000)")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
assert_equal @connection.execute("Select * from #{DBConnectionHelper.test_table_name} WHERE id=2000").to_a.first.first, 2000
|
54
|
+
|
55
|
+
logs = @logger.string.split("\n")
|
56
|
+
|
57
|
+
assert logs.first.include?("Lost connection to MySQL, will retry to connect to same host")
|
58
|
+
assert logs.last.include?("LHM successfully reconnected to initial host")
|
59
|
+
end
|
60
|
+
|
61
|
+
it "Will abort if new writer is not same host" do
|
62
|
+
# The hostname will be constant before the blip
|
63
|
+
Lhm::SqlRetry.any_instance.stubs(:hostname).returns("mysql-1").then.returns("mysql-2")
|
64
|
+
Lhm::SqlRetry.any_instance.stubs(:server_id).returns(1).then.returns(2)
|
65
|
+
|
66
|
+
# Need new instance for stub to take into effect
|
67
|
+
lhm_retry = Lhm::SqlRetry.new(@connection, retry_options: {},
|
68
|
+
reconnect_with_consistent_host: true)
|
69
|
+
|
70
|
+
e = assert_raises Lhm::Error do
|
71
|
+
#Creating a network blip
|
72
|
+
ToxiproxyHelper.with_kill_and_restart(:mysql_proxysql, 2.seconds) do
|
73
|
+
lhm_retry.with_retries do |retriable_connection|
|
74
|
+
retriable_connection.execute("INSERT INTO #{DBConnectionHelper.test_table_name} (id) VALUES (2000)")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
assert_equal e.class, Lhm::Error
|
80
|
+
assert_match(/LHM tried the reconnection procedure but failed. Aborting/, e.message)
|
81
|
+
|
82
|
+
logs = @logger.string.split("\n")
|
83
|
+
|
84
|
+
assert logs.first.include?("Lost connection to MySQL, will retry to connect to same host")
|
85
|
+
assert logs.last.include?("Reconnected to wrong host. Started migration on: mysql-1 (server_id: 1), but reconnected to: mysql-2 (server_id: 2).")
|
86
|
+
end
|
87
|
+
|
88
|
+
it "Will abort if failover happens (mimicked with proxySQL)" do
|
89
|
+
e = assert_raises Lhm::Error do
|
90
|
+
#Creates a failover by switching the target hostgroup for the #hostname
|
91
|
+
ProxySQLHelper.with_lhm_hostgroup_flip do
|
92
|
+
#Creating a network blip
|
93
|
+
ToxiproxyHelper.with_kill_and_restart(:mysql_proxysql, 2.seconds) do
|
94
|
+
@lhm_retry.with_retries do |retriable_connection|
|
95
|
+
retriable_connection.execute("INSERT INTO #{DBConnectionHelper.test_table_name} (id) VALUES (2000)")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
assert_equal e.class, Lhm::Error
|
102
|
+
assert_match(/LHM tried the reconnection procedure but failed. Aborting/, e.message)
|
103
|
+
|
104
|
+
logs = @logger.string.split("\n")
|
105
|
+
|
106
|
+
assert logs.first.include?("Lost connection to MySQL, will retry to connect to same host")
|
107
|
+
assert logs.last.include?("Reconnected to wrong host. Started migration on: mysql-1 (server_id: 1), but reconnected to: mysql-2 (server_id: 2).")
|
108
|
+
end
|
109
|
+
end
|
@@ -15,28 +15,24 @@ describe Lhm::Table do
|
|
15
15
|
end
|
16
16
|
|
17
17
|
it 'should parse primary key' do
|
18
|
-
@table.pk.must_equal('pk')
|
18
|
+
value(@table.pk).must_equal('pk')
|
19
19
|
end
|
20
20
|
|
21
21
|
it 'should parse indices' do
|
22
|
-
@table.
|
23
|
-
indices['index_custom_primary_key_on_id'].
|
24
|
-
must_equal(['id'])
|
22
|
+
value(@table.indices['index_custom_primary_key_on_id']).must_equal(['id'])
|
25
23
|
end
|
26
24
|
|
27
25
|
it 'should parse columns' do
|
28
|
-
@table.
|
29
|
-
columns['id'][:type].
|
30
|
-
must_match(/(bigint|int)\(\d+\)/)
|
26
|
+
value(@table.columns['id'][:type]).must_match(/(bigint|int)\(\d+\)/)
|
31
27
|
end
|
32
28
|
|
33
29
|
it 'should return true for method that should be renamed' do
|
34
|
-
@table.satisfies_id_column_requirement
|
30
|
+
value(@table.satisfies_id_column_requirement?).must_equal true
|
35
31
|
end
|
36
32
|
|
37
33
|
it 'should support bigint tables' do
|
38
34
|
@table = table_create(:bigint_table)
|
39
|
-
@table.satisfies_id_column_requirement
|
35
|
+
value(@table.satisfies_id_column_requirement?).must_equal true
|
40
36
|
end
|
41
37
|
end
|
42
38
|
|
@@ -47,7 +43,7 @@ describe Lhm::Table do
|
|
47
43
|
|
48
44
|
it 'should return false for a non-int id column' do
|
49
45
|
@table = table_create(:wo_id_int_column)
|
50
|
-
@table.satisfies_id_column_requirement
|
46
|
+
value(@table.satisfies_id_column_requirement?).must_equal false
|
51
47
|
end
|
52
48
|
end
|
53
49
|
end
|
@@ -60,15 +56,15 @@ describe Lhm::Table do
|
|
60
56
|
end
|
61
57
|
|
62
58
|
it 'should parse table name in show create table' do
|
63
|
-
@table.name.must_equal('users')
|
59
|
+
value(@table.name).must_equal('users')
|
64
60
|
end
|
65
61
|
|
66
62
|
it 'should parse primary key' do
|
67
|
-
@table.pk.must_equal('id')
|
63
|
+
value(@table.pk).must_equal('id')
|
68
64
|
end
|
69
65
|
|
70
66
|
it 'should parse column type in show create table' do
|
71
|
-
@table.columns['username'][:type].must_equal('varchar(255)')
|
67
|
+
value(@table.columns['username'][:type]).must_equal('varchar(255)')
|
72
68
|
end
|
73
69
|
|
74
70
|
it 'should parse column metadata' do
|
@@ -76,15 +72,11 @@ describe Lhm::Table do
|
|
76
72
|
end
|
77
73
|
|
78
74
|
it 'should parse indices' do
|
79
|
-
@table.
|
80
|
-
indices['index_users_on_username_and_created_at'].
|
81
|
-
must_equal(['username', 'created_at'])
|
75
|
+
value(@table.indices['index_users_on_username_and_created_at']).must_equal(['username', 'created_at'])
|
82
76
|
end
|
83
77
|
|
84
78
|
it 'should parse index' do
|
85
|
-
@table.
|
86
|
-
indices['index_users_on_reference'].
|
87
|
-
must_equal(['reference'])
|
79
|
+
value(@table.indices['index_users_on_reference']).must_equal(['reference'])
|
88
80
|
end
|
89
81
|
end
|
90
82
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'toxiproxy'
|
3
|
+
|
4
|
+
module ToxiproxyHelper
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def included(base)
|
8
|
+
Toxiproxy.reset
|
9
|
+
|
10
|
+
# listen on localhost, but toxiproxy is in a container itself, thus the upstream uses the Docker-Compose DNS
|
11
|
+
Toxiproxy.populate(
|
12
|
+
[
|
13
|
+
{
|
14
|
+
name: 'mysql_master',
|
15
|
+
listen: '0.0.0.0:22220',
|
16
|
+
upstream: 'mysql-1:3306'
|
17
|
+
},
|
18
|
+
{
|
19
|
+
name: 'mysql_proxysql',
|
20
|
+
listen: '0.0.0.0:22222',
|
21
|
+
upstream: 'proxysql:3306'
|
22
|
+
}
|
23
|
+
])
|
24
|
+
end
|
25
|
+
|
26
|
+
def with_kill_and_restart(target, restart_after)
|
27
|
+
thread = Thread.new do
|
28
|
+
sleep(restart_after) unless restart_after.nil?
|
29
|
+
Toxiproxy[target].enable
|
30
|
+
end
|
31
|
+
|
32
|
+
Toxiproxy[target].disable
|
33
|
+
|
34
|
+
yield
|
35
|
+
|
36
|
+
ensure
|
37
|
+
thread.join
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/spec/test_helper.rb
CHANGED
@@ -10,6 +10,8 @@ require 'minitest/autorun'
|
|
10
10
|
require 'minitest/spec'
|
11
11
|
require 'minitest/mock'
|
12
12
|
require 'mocha/minitest'
|
13
|
+
require 'after_do'
|
14
|
+
require 'byebug'
|
13
15
|
require 'pathname'
|
14
16
|
require 'lhm'
|
15
17
|
|
@@ -17,6 +19,8 @@ $project = Pathname.new(File.dirname(__FILE__) + '/..').cleanpath
|
|
17
19
|
$spec = $project.join('spec')
|
18
20
|
$fixtures = $spec.join('fixtures')
|
19
21
|
|
22
|
+
$db_name = 'test'
|
23
|
+
|
20
24
|
require 'active_record'
|
21
25
|
require 'mysql2'
|
22
26
|
|
@@ -24,6 +28,9 @@ logger = Logger.new STDOUT
|
|
24
28
|
logger.level = Logger::WARN
|
25
29
|
Lhm.logger = logger
|
26
30
|
|
31
|
+
# Want test to be efficient without having to wait the normal value of 120s
|
32
|
+
Lhm::SqlRetry::RECONNECT_RETRY_MAX_ITERATION = 4
|
33
|
+
|
27
34
|
def without_verbose(&block)
|
28
35
|
old_verbose, $VERBOSE = $VERBOSE, nil
|
29
36
|
yield
|
@@ -43,3 +50,20 @@ end
|
|
43
50
|
def throttler
|
44
51
|
Lhm::Throttler::Time.new(:stride => 100)
|
45
52
|
end
|
53
|
+
|
54
|
+
def init_test_db
|
55
|
+
db_config = YAML.load_file(File.expand_path(File.dirname(__FILE__)) + '/integration/database.yml')
|
56
|
+
conn = Mysql2::Client.new(
|
57
|
+
:host => '127.0.0.1',
|
58
|
+
:username => db_config['master']['user'],
|
59
|
+
:password => db_config['master']['password'],
|
60
|
+
:port => db_config['master']['port']
|
61
|
+
)
|
62
|
+
|
63
|
+
conn.query("DROP DATABASE IF EXISTS #{$db_name}")
|
64
|
+
conn.query("CREATE DATABASE #{$db_name}")
|
65
|
+
end
|
66
|
+
|
67
|
+
init_test_db
|
68
|
+
|
69
|
+
|
@@ -20,12 +20,10 @@ describe Lhm::AtomicSwitcher do
|
|
20
20
|
|
21
21
|
describe 'atomic switch' do
|
22
22
|
it 'should perform a single atomic rename' do
|
23
|
-
@switcher.
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
'`destination` to `origin`'
|
28
|
-
)
|
23
|
+
value(@switcher.atomic_switch).must_equal(
|
24
|
+
"rename table `origin` to `#{ @migration.archive_name }`, " \
|
25
|
+
'`destination` to `origin`'
|
26
|
+
)
|
29
27
|
end
|
30
28
|
end
|
31
29
|
end
|
@@ -4,17 +4,22 @@
|
|
4
4
|
require File.expand_path(File.dirname(__FILE__)) + '/unit_helper'
|
5
5
|
|
6
6
|
require 'lhm/chunk_insert'
|
7
|
+
require 'lhm/connection'
|
7
8
|
|
8
9
|
describe Lhm::ChunkInsert do
|
9
10
|
before(:each) do
|
10
|
-
|
11
|
+
ar_connection = mock()
|
12
|
+
ar_connection.stubs(:execute).returns([["dummy"]])
|
13
|
+
@connection = Lhm::Connection.new(connection: ar_connection, options: {reconnect_with_consistent_host: false})
|
11
14
|
@origin = Lhm::Table.new('foo')
|
12
15
|
@destination = Lhm::Table.new('bar')
|
13
16
|
end
|
14
17
|
|
15
18
|
describe "#sql" do
|
16
19
|
describe "when migration has no conditions" do
|
17
|
-
before
|
20
|
+
before do
|
21
|
+
@migration = Lhm::Migration.new(@origin, @destination)
|
22
|
+
end
|
18
23
|
|
19
24
|
it "uses a simple where clause" do
|
20
25
|
assert_equal(
|
data/spec/unit/chunker_spec.rb
CHANGED
@@ -7,15 +7,20 @@ require 'lhm/table'
|
|
7
7
|
require 'lhm/migration'
|
8
8
|
require 'lhm/chunker'
|
9
9
|
require 'lhm/throttler'
|
10
|
+
require 'lhm/connection'
|
10
11
|
|
11
12
|
describe Lhm::Chunker do
|
12
13
|
include UnitHelper
|
13
14
|
|
15
|
+
EXPECTED_RETRY_FLAGS_CHUNKER = {:should_retry => true, :log_prefix => "Chunker"}
|
16
|
+
EXPECTED_RETRY_FLAGS_CHUNK_INSERT = {:should_retry => true, :log_prefix => "ChunkInsert"}
|
17
|
+
|
14
18
|
before(:each) do
|
15
19
|
@origin = Lhm::Table.new('foo')
|
16
20
|
@destination = Lhm::Table.new('bar')
|
17
21
|
@migration = Lhm::Migration.new(@origin, @destination)
|
18
22
|
@connection = mock()
|
23
|
+
@connection.stubs(:execute).returns([["dummy"]])
|
19
24
|
# This is a poor man's stub
|
20
25
|
@throttler = Object.new
|
21
26
|
def @throttler.run
|
@@ -37,11 +42,11 @@ describe Lhm::Chunker do
|
|
37
42
|
5
|
38
43
|
end
|
39
44
|
|
40
|
-
@connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 4/)).returns(7)
|
41
|
-
@connection.expects(:select_value).with(regexp_matches(/where id >= 8 order by id limit 1 offset 4/)).returns(21)
|
42
|
-
@connection.expects(:update).with(regexp_matches(/between 1 and 7/)).returns(2)
|
43
|
-
@connection.expects(:update).with(regexp_matches(/between 8 and 10/)).returns(2)
|
44
|
-
@connection.expects(:
|
45
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 4/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(7)
|
46
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 8 order by id limit 1 offset 4/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(21)
|
47
|
+
@connection.expects(:update).with(regexp_matches(/between 1 and 7/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
48
|
+
@connection.expects(:update).with(regexp_matches(/between 8 and 10/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
49
|
+
@connection.expects(:execute).twice.with(regexp_matches(/show warnings/),EXPECTED_RETRY_FLAGS_CHUNKER).returns([])
|
45
50
|
|
46
51
|
@chunker.run
|
47
52
|
end
|
@@ -52,17 +57,17 @@ describe Lhm::Chunker do
|
|
52
57
|
2
|
53
58
|
end
|
54
59
|
|
55
|
-
@connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 1/)).returns(2)
|
56
|
-
@connection.expects(:select_value).with(regexp_matches(/where id >= 3 order by id limit 1 offset 1/)).returns(4)
|
57
|
-
@connection.expects(:select_value).with(regexp_matches(/where id >= 5 order by id limit 1 offset 1/)).returns(6)
|
58
|
-
@connection.expects(:select_value).with(regexp_matches(/where id >= 7 order by id limit 1 offset 1/)).returns(8)
|
59
|
-
@connection.expects(:select_value).with(regexp_matches(/where id >= 9 order by id limit 1 offset 1/)).returns(10)
|
60
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(2)
|
61
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 3 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(4)
|
62
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 5 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(6)
|
63
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 7 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(8)
|
64
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 9 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(10)
|
60
65
|
|
61
|
-
@connection.expects(:update).with(regexp_matches(/between 1 and 2/)).returns(2)
|
62
|
-
@connection.expects(:update).with(regexp_matches(/between 3 and 4/)).returns(2)
|
63
|
-
@connection.expects(:update).with(regexp_matches(/between 5 and 6/)).returns(2)
|
64
|
-
@connection.expects(:update).with(regexp_matches(/between 7 and 8/)).returns(2)
|
65
|
-
@connection.expects(:update).with(regexp_matches(/between 9 and 10/)).returns(2)
|
66
|
+
@connection.expects(:update).with(regexp_matches(/between 1 and 2/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
67
|
+
@connection.expects(:update).with(regexp_matches(/between 3 and 4/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
68
|
+
@connection.expects(:update).with(regexp_matches(/between 5 and 6/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
69
|
+
@connection.expects(:update).with(regexp_matches(/between 7 and 8/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
70
|
+
@connection.expects(:update).with(regexp_matches(/between 9 and 10/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
66
71
|
|
67
72
|
@chunker.run
|
68
73
|
end
|
@@ -79,17 +84,17 @@ describe Lhm::Chunker do
|
|
79
84
|
end
|
80
85
|
end
|
81
86
|
|
82
|
-
@connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 1/)).returns(2)
|
83
|
-
@connection.expects(:select_value).with(regexp_matches(/where id >= 3 order by id limit 1 offset 2/)).returns(5)
|
84
|
-
@connection.expects(:select_value).with(regexp_matches(/where id >= 6 order by id limit 1 offset 2/)).returns(8)
|
85
|
-
@connection.expects(:select_value).with(regexp_matches(/where id >= 9 order by id limit 1 offset 2/)).returns(nil)
|
87
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(2)
|
88
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 3 order by id limit 1 offset 2/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(5)
|
89
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 6 order by id limit 1 offset 2/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(8)
|
90
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 9 order by id limit 1 offset 2/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(nil)
|
86
91
|
|
87
|
-
@connection.expects(:update).with(regexp_matches(/between 1 and 2/)).returns(2)
|
88
|
-
@connection.expects(:update).with(regexp_matches(/between 3 and 5/)).returns(2)
|
89
|
-
@connection.expects(:update).with(regexp_matches(/between 6 and 8/)).returns(2)
|
90
|
-
@connection.expects(:update).with(regexp_matches(/between 9 and 10/)).returns(2)
|
92
|
+
@connection.expects(:update).with(regexp_matches(/between 1 and 2/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
93
|
+
@connection.expects(:update).with(regexp_matches(/between 3 and 5/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
94
|
+
@connection.expects(:update).with(regexp_matches(/between 6 and 8/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
95
|
+
@connection.expects(:update).with(regexp_matches(/between 9 and 10/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
91
96
|
|
92
|
-
@connection.expects(:
|
97
|
+
@connection.expects(:execute).twice.with(regexp_matches(/show warnings/),EXPECTED_RETRY_FLAGS_CHUNKER).returns([])
|
93
98
|
|
94
99
|
@chunker.run
|
95
100
|
end
|
@@ -99,8 +104,8 @@ describe Lhm::Chunker do
|
|
99
104
|
:start => 1,
|
100
105
|
:limit => 1)
|
101
106
|
|
102
|
-
@connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 0/)).returns(nil)
|
103
|
-
@connection.expects(:update).with(regexp_matches(/between 1 and 1/)).returns(1)
|
107
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 0/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(nil)
|
108
|
+
@connection.expects(:update).with(regexp_matches(/between 1 and 1/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(1)
|
104
109
|
|
105
110
|
@chunker.run
|
106
111
|
end
|
@@ -113,17 +118,17 @@ describe Lhm::Chunker do
|
|
113
118
|
2
|
114
119
|
end
|
115
120
|
|
116
|
-
@connection.expects(:select_value).with(regexp_matches(/where id >= 2 order by id limit 1 offset 1/)).returns(3)
|
117
|
-
@connection.expects(:select_value).with(regexp_matches(/where id >= 4 order by id limit 1 offset 1/)).returns(5)
|
118
|
-
@connection.expects(:select_value).with(regexp_matches(/where id >= 6 order by id limit 1 offset 1/)).returns(7)
|
119
|
-
@connection.expects(:select_value).with(regexp_matches(/where id >= 8 order by id limit 1 offset 1/)).returns(9)
|
120
|
-
@connection.expects(:select_value).with(regexp_matches(/where id >= 10 order by id limit 1 offset 1/)).returns(nil)
|
121
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 2 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(3)
|
122
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 4 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(5)
|
123
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 6 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(7)
|
124
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 8 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(9)
|
125
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 10 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(nil)
|
121
126
|
|
122
|
-
@connection.expects(:update).with(regexp_matches(/between 2 and 3/)).returns(2)
|
123
|
-
@connection.expects(:update).with(regexp_matches(/between 4 and 5/)).returns(2)
|
124
|
-
@connection.expects(:update).with(regexp_matches(/between 6 and 7/)).returns(2)
|
125
|
-
@connection.expects(:update).with(regexp_matches(/between 8 and 9/)).returns(2)
|
126
|
-
@connection.expects(:update).with(regexp_matches(/between 10 and 10/)).returns(1)
|
127
|
+
@connection.expects(:update).with(regexp_matches(/between 2 and 3/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
128
|
+
@connection.expects(:update).with(regexp_matches(/between 4 and 5/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
129
|
+
@connection.expects(:update).with(regexp_matches(/between 6 and 7/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
130
|
+
@connection.expects(:update).with(regexp_matches(/between 8 and 9/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
131
|
+
@connection.expects(:update).with(regexp_matches(/between 10 and 10/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(1)
|
127
132
|
|
128
133
|
@chunker.run
|
129
134
|
end
|
@@ -137,9 +142,9 @@ describe Lhm::Chunker do
|
|
137
142
|
2
|
138
143
|
end
|
139
144
|
|
140
|
-
@connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 1/)).returns(2)
|
141
|
-
@connection.expects(:update).with(regexp_matches(/where \(foo.created_at > '2013-07-10' or foo.baz = 'quux'\) and `foo`/)).returns(1)
|
142
|
-
@connection.expects(:
|
145
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(2)
|
146
|
+
@connection.expects(:update).with(regexp_matches(/where \(foo.created_at > '2013-07-10' or foo.baz = 'quux'\) and `foo`/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(1)
|
147
|
+
@connection.expects(:execute).with(regexp_matches(/show warnings/),EXPECTED_RETRY_FLAGS_CHUNKER).returns([])
|
143
148
|
|
144
149
|
def @migration.conditions
|
145
150
|
"where foo.created_at > '2013-07-10' or foo.baz = 'quux'"
|
@@ -157,9 +162,9 @@ describe Lhm::Chunker do
|
|
157
162
|
2
|
158
163
|
end
|
159
164
|
|
160
|
-
@connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 1/)).returns(2)
|
161
|
-
@connection.expects(:update).with(regexp_matches(/inner join bar on foo.id = bar.foo_id and/)).returns(1)
|
162
|
-
@connection.expects(:
|
165
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(2)
|
166
|
+
@connection.expects(:update).with(regexp_matches(/inner join bar on foo.id = bar.foo_id and/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(1)
|
167
|
+
@connection.expects(:execute).with(regexp_matches(/show warnings/),EXPECTED_RETRY_FLAGS_CHUNKER).returns([])
|
163
168
|
|
164
169
|
def @migration.conditions
|
165
170
|
'inner join bar on foo.id = bar.foo_id'
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'lhm/connection'
|
2
|
+
require 'lhm/proxysql_helper'
|
3
|
+
|
4
|
+
describe Lhm::Connection do
|
5
|
+
|
6
|
+
LOCK_WAIT = ActiveRecord::StatementInvalid.new('Lock wait timeout exceeded; try restarting transaction.')
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
@logs = StringIO.new
|
10
|
+
Lhm.logger = Logger.new(@logs)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "Should find use calling file as prefix" do
|
14
|
+
ar_connection = mock()
|
15
|
+
ar_connection.stubs(:execute).raises(LOCK_WAIT).then.returns(true)
|
16
|
+
ar_connection.stubs(:active?).returns(true)
|
17
|
+
|
18
|
+
connection = Lhm::Connection.new(connection: ar_connection, options: {
|
19
|
+
retriable: {
|
20
|
+
base_interval: 0
|
21
|
+
}
|
22
|
+
})
|
23
|
+
|
24
|
+
connection.execute("SHOW TABLES", should_retry: true)
|
25
|
+
|
26
|
+
log_messages = @logs.string.split("\n")
|
27
|
+
assert_equal(1, log_messages.length)
|
28
|
+
assert log_messages.first.include?("[ConnectionSpec]")
|
29
|
+
end
|
30
|
+
|
31
|
+
it "#execute should be retried" do
|
32
|
+
ar_connection = mock()
|
33
|
+
ar_connection.stubs(:execute).raises(LOCK_WAIT)
|
34
|
+
.then.raises(LOCK_WAIT)
|
35
|
+
.then.returns(true)
|
36
|
+
ar_connection.stubs(:active?).returns(true)
|
37
|
+
|
38
|
+
connection = Lhm::Connection.new(connection: ar_connection, options: {
|
39
|
+
retriable: {
|
40
|
+
base_interval: 0,
|
41
|
+
tries: 3
|
42
|
+
}
|
43
|
+
})
|
44
|
+
|
45
|
+
connection.execute("SHOW TABLES", should_retry: true)
|
46
|
+
|
47
|
+
log_messages = @logs.string.split("\n")
|
48
|
+
assert_equal(2, log_messages.length)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "#update should be retried" do
|
52
|
+
ar_connection = mock()
|
53
|
+
ar_connection.stubs(:update).raises(LOCK_WAIT)
|
54
|
+
.then.raises(LOCK_WAIT)
|
55
|
+
.then.returns(1)
|
56
|
+
ar_connection.stubs(:active?).returns(true)
|
57
|
+
|
58
|
+
connection = Lhm::Connection.new(connection: ar_connection, options: {
|
59
|
+
retriable: {
|
60
|
+
base_interval: 0,
|
61
|
+
tries: 3
|
62
|
+
}
|
63
|
+
})
|
64
|
+
|
65
|
+
val = connection.update("SHOW TABLES", should_retry: true)
|
66
|
+
|
67
|
+
log_messages = @logs.string.split("\n")
|
68
|
+
assert_equal val, 1
|
69
|
+
assert_equal(2, log_messages.length)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "#select_value should be retried" do
|
73
|
+
ar_connection = mock()
|
74
|
+
ar_connection.stubs(:select_value).raises(LOCK_WAIT)
|
75
|
+
.then.raises(LOCK_WAIT)
|
76
|
+
.then.returns("dummy")
|
77
|
+
ar_connection.stubs(:active?).returns(true)
|
78
|
+
|
79
|
+
connection = Lhm::Connection.new(connection: ar_connection, options: {
|
80
|
+
retriable: {
|
81
|
+
base_interval: 0,
|
82
|
+
tries: 3
|
83
|
+
}
|
84
|
+
})
|
85
|
+
|
86
|
+
val = connection.select_value("SHOW TABLES", should_retry: true)
|
87
|
+
|
88
|
+
log_messages = @logs.string.split("\n")
|
89
|
+
assert_equal val, "dummy"
|
90
|
+
assert_equal(2, log_messages.length)
|
91
|
+
end
|
92
|
+
|
93
|
+
it "Queries should be tagged with ProxySQL tag if reconnect_with_consistent_host is enabled" do
|
94
|
+
ar_connection = mock()
|
95
|
+
ar_connection.expects(:public_send).with(:select_value, "SHOW TABLES #{Lhm::ProxySQLHelper::ANNOTATION}").returns("dummy")
|
96
|
+
ar_connection.stubs(:execute).times(4).returns([["dummy"]])
|
97
|
+
ar_connection.stubs(:active?).returns(true)
|
98
|
+
|
99
|
+
connection = Lhm::Connection.new(connection: ar_connection, options: {
|
100
|
+
reconnect_with_consistent_host: true,
|
101
|
+
retriable: {
|
102
|
+
base_interval: 0,
|
103
|
+
tries: 3
|
104
|
+
}
|
105
|
+
})
|
106
|
+
|
107
|
+
val = connection.select_value("SHOW TABLES", should_retry: true)
|
108
|
+
|
109
|
+
assert_equal val, "dummy"
|
110
|
+
end
|
111
|
+
end
|