lhm 1.0.0.rc2 → 1.0.0.rc3
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.
- data/.config +3 -0
- data/.gitignore +1 -0
- data/.travis.yml +9 -3
- data/CHANGELOG.md +10 -0
- data/README.md +28 -24
- data/Rakefile +2 -1
- data/gemfiles/ar-2.3.gemfile +4 -0
- data/gemfiles/ar-3.1.gemfile +4 -0
- data/lhm.gemspec +1 -1
- data/lib/lhm.rb +30 -8
- data/lib/lhm/chunker.rb +53 -34
- data/lib/lhm/command.rb +15 -42
- data/lib/lhm/entangler.rb +13 -20
- data/lib/lhm/intersection.rb +3 -7
- data/lib/lhm/invoker.rb +9 -14
- data/lib/lhm/locked_switcher.rb +22 -26
- data/lib/lhm/migration.rb +2 -8
- data/lib/lhm/migrator.rb +76 -56
- data/lib/lhm/sql_helper.rb +45 -0
- data/lib/lhm/table.rb +2 -10
- data/lib/lhm/version.rb +6 -0
- data/spec/README.md +26 -0
- data/spec/bootstrap.rb +2 -5
- data/spec/config/.config +3 -0
- data/spec/config/clobber +36 -0
- data/spec/config/grants +25 -0
- data/spec/config/setup-cluster +61 -0
- data/spec/fixtures/destination.ddl +0 -1
- data/spec/fixtures/origin.ddl +0 -1
- data/spec/fixtures/users.ddl +1 -1
- data/spec/integration/chunker_spec.rb +10 -9
- data/spec/integration/entangler_spec.rb +16 -10
- data/spec/integration/integration_helper.rb +43 -12
- data/spec/integration/lhm_spec.rb +66 -42
- data/spec/integration/locked_switcher_spec.rb +11 -10
- data/spec/unit/chunker_spec.rb +50 -18
- data/spec/unit/entangler_spec.rb +2 -5
- data/spec/unit/intersection_spec.rb +2 -5
- data/spec/unit/locked_switcher_spec.rb +2 -5
- data/spec/unit/migration_spec.rb +2 -5
- data/spec/unit/migrator_spec.rb +6 -9
- data/spec/unit/sql_helper_spec.rb +32 -0
- data/spec/unit/table_spec.rb +2 -29
- data/spec/unit/unit_helper.rb +2 -5
- metadata +52 -7
- data/Gemfile +0 -3
data/spec/bootstrap.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
# Schmidt
|
4
|
-
#
|
1
|
+
# Copyright (c) 2011, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
2
|
+
# Schmidt
|
5
3
|
|
6
4
|
require 'minitest/spec'
|
7
5
|
require 'minitest/autorun'
|
@@ -13,4 +11,3 @@ $spec = $project.join("spec")
|
|
13
11
|
$fixtures = $spec.join("fixtures")
|
14
12
|
|
15
13
|
$: << $project.join("lib").to_s
|
16
|
-
|
data/spec/config/.config
ADDED
data/spec/config/clobber
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
set -e
|
4
|
+
set -u
|
5
|
+
|
6
|
+
source .config
|
7
|
+
|
8
|
+
lhmkill() {
|
9
|
+
ps -ef | gsed -n "/[m]ysqld.*lhm-cluster/p" | awk '{ print $2 }' | xargs kill
|
10
|
+
sleep 5
|
11
|
+
}
|
12
|
+
|
13
|
+
echo stopping other running mysql instance
|
14
|
+
launchctl remove com.mysql.mysqld || { echo launchctl did not remove mysqld; }
|
15
|
+
mysqladmin shutdown || { echo mysqladmin did not shut down anything; }
|
16
|
+
|
17
|
+
echo killing lhm-cluster
|
18
|
+
lhmkill
|
19
|
+
|
20
|
+
echo removing $basedir
|
21
|
+
rm -rf "$basedir"
|
22
|
+
|
23
|
+
echo setting up cluster
|
24
|
+
spec/config/setup-cluster
|
25
|
+
|
26
|
+
echo staring instances
|
27
|
+
mysqld --defaults-file="$basedir/master/my.cnf" 2>&1 >$basedir/master/lhm.log &
|
28
|
+
mysqld --defaults-file="$basedir/slave/my.cnf" 2>&1 >$basedir/slave/lhm.log &
|
29
|
+
sleep 5
|
30
|
+
|
31
|
+
echo running grants
|
32
|
+
spec/config/grants
|
33
|
+
|
34
|
+
trap lhmkill SIGTERM SIGINT
|
35
|
+
|
36
|
+
wait
|
data/spec/config/grants
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
source .config
|
4
|
+
|
5
|
+
master() { mysql --protocol=TCP -P $master_port -uroot; }
|
6
|
+
slave() { mysql --protocol=TCP -P $slave_port -uroot; }
|
7
|
+
|
8
|
+
# set up master
|
9
|
+
|
10
|
+
echo "create user 'slave'@'localhost' identified by 'slave'" | master
|
11
|
+
echo "grant replication slave on *.* to 'slave'@'localhost'" | master
|
12
|
+
|
13
|
+
# set up slave
|
14
|
+
|
15
|
+
echo "change master to master_user = 'slave', master_password = 'slave', master_port = 3306, master_host = 'localhost'" | slave
|
16
|
+
echo "start slave" | slave
|
17
|
+
echo "show slave status \G" | slave
|
18
|
+
|
19
|
+
# setup for test
|
20
|
+
|
21
|
+
echo "grant all privileges on *.* to ''@'localhost'" | master
|
22
|
+
echo "grant all privileges on *.* to ''@'localhost'" | slave
|
23
|
+
|
24
|
+
echo "create database lhm" | master
|
25
|
+
echo "create database lhm" | slave
|
@@ -0,0 +1,61 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
#
|
4
|
+
# Set up master slave cluster for lhm specs
|
5
|
+
#
|
6
|
+
|
7
|
+
set -e
|
8
|
+
set -u
|
9
|
+
|
10
|
+
source .config
|
11
|
+
|
12
|
+
#
|
13
|
+
# Main
|
14
|
+
#
|
15
|
+
|
16
|
+
mkdir -p "$basedir/master/data" "$basedir/slave/data"
|
17
|
+
|
18
|
+
cat <<-CNF > $basedir/master/my.cnf
|
19
|
+
[mysqld]
|
20
|
+
pid-file = $basedir/master/mysqld.pid
|
21
|
+
socket = $basedir/master/mysqld.sock
|
22
|
+
port = $master_port
|
23
|
+
log_output = FILE
|
24
|
+
log-error = $basedir/master/error.log
|
25
|
+
datadir = $basedir/master/data
|
26
|
+
log-bin = master-bin
|
27
|
+
log-bin-index = master-bin.index
|
28
|
+
server-id = 1
|
29
|
+
CNF
|
30
|
+
|
31
|
+
cat <<-CNF > $basedir/slave/my.cnf
|
32
|
+
[mysqld]
|
33
|
+
pid-file = $basedir/slave/mysqld.pid
|
34
|
+
socket = $basedir/slave/mysqld.sock
|
35
|
+
port = $slave_port
|
36
|
+
log_output = FILE
|
37
|
+
log-error = $basedir/slave/error.log
|
38
|
+
datadir = $basedir/slave/data
|
39
|
+
relay-log = slave-relay-bin
|
40
|
+
relay-log-index = slave-relay-bin.index
|
41
|
+
server-id = 2
|
42
|
+
|
43
|
+
# replication (optional filters)
|
44
|
+
|
45
|
+
replicate-do-table = lhm.users
|
46
|
+
replicate-do-table = lhm.lhmn_users
|
47
|
+
replicate-wild-do = lhm.lhma_%_users
|
48
|
+
|
49
|
+
replicate-do-table = lhm.origin
|
50
|
+
replicate-do-table = lhm.lhmn_origin
|
51
|
+
replicate-wild-do = lhm.lhma_%_origin
|
52
|
+
|
53
|
+
replicate-do-table = lhm.destination
|
54
|
+
replicate-do-table = lhm.lhmn_destination
|
55
|
+
replicate-wild-do = lhm.lhma_%_destination
|
56
|
+
CNF
|
57
|
+
|
58
|
+
# build system tables
|
59
|
+
|
60
|
+
mysql_install_db --datadir="$basedir/master/data"
|
61
|
+
mysql_install_db --datadir="$basedir/slave/data"
|
data/spec/fixtures/origin.ddl
CHANGED
data/spec/fixtures/users.ddl
CHANGED
@@ -2,10 +2,10 @@ CREATE TABLE `users` (
|
|
2
2
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
3
3
|
`reference` int(11) DEFAULT NULL,
|
4
4
|
`username` varchar(255) DEFAULT NULL,
|
5
|
+
`group` varchar(255) DEFAULT NULL,
|
5
6
|
`created_at` datetime DEFAULT NULL,
|
6
7
|
`comment` varchar(20) DEFAULT NULL,
|
7
8
|
PRIMARY KEY (`id`),
|
8
9
|
UNIQUE KEY `index_users_on_reference` (`reference`),
|
9
10
|
KEY `index_users_on_username_and_created_at` (`username`,`created_at`)
|
10
11
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8
|
11
|
-
|
@@ -1,7 +1,5 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
# Schmidt
|
4
|
-
#
|
1
|
+
# Copyright (c) 2011, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
2
|
+
# Schmidt
|
5
3
|
|
6
4
|
require File.expand_path(File.dirname(__FILE__)) + '/integration_helper'
|
7
5
|
|
@@ -12,7 +10,7 @@ require 'lhm/migration'
|
|
12
10
|
describe Lhm::Chunker do
|
13
11
|
include IntegrationHelper
|
14
12
|
|
15
|
-
before(:each) {
|
13
|
+
before(:each) { connect_master! }
|
16
14
|
|
17
15
|
describe "copying" do
|
18
16
|
before(:each) do
|
@@ -22,10 +20,13 @@ describe Lhm::Chunker do
|
|
22
20
|
end
|
23
21
|
|
24
22
|
it "should copy 23 rows from origin to destination" do
|
25
|
-
23.times { |n| execute("insert into origin set
|
26
|
-
|
27
|
-
|
23
|
+
23.times { |n| execute("insert into origin set id = '#{ n * n + 23 }'") }
|
24
|
+
|
25
|
+
Lhm::Chunker.new(@migration, connection, { :stride => 100 }).run
|
26
|
+
|
27
|
+
slave do
|
28
|
+
count_all(@destination.name).must_equal(23)
|
29
|
+
end
|
28
30
|
end
|
29
31
|
end
|
30
32
|
end
|
31
|
-
|
@@ -1,7 +1,5 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
# Schmidt
|
4
|
-
#
|
1
|
+
# Copyright (c) 2011, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
2
|
+
# Schmidt
|
5
3
|
|
6
4
|
require File.expand_path(File.dirname(__FILE__)) + '/integration_helper'
|
7
5
|
|
@@ -12,7 +10,7 @@ require 'lhm/entangler'
|
|
12
10
|
describe Lhm::Entangler do
|
13
11
|
include IntegrationHelper
|
14
12
|
|
15
|
-
before(:each) {
|
13
|
+
before(:each) { connect_master! }
|
16
14
|
|
17
15
|
describe "entanglement" do
|
18
16
|
before(:each) do
|
@@ -27,7 +25,9 @@ describe Lhm::Entangler do
|
|
27
25
|
execute("insert into origin (common) values ('inserted')")
|
28
26
|
end
|
29
27
|
|
30
|
-
|
28
|
+
slave do
|
29
|
+
count(:destination, "common", "inserted").must_equal(1)
|
30
|
+
end
|
31
31
|
end
|
32
32
|
|
33
33
|
it "should replay deletes from origin into destination" do
|
@@ -37,7 +37,9 @@ describe Lhm::Entangler do
|
|
37
37
|
execute("delete from origin where common = 'inserted'")
|
38
38
|
end
|
39
39
|
|
40
|
-
|
40
|
+
slave do
|
41
|
+
count(:destination, "common", "inserted").must_equal(0)
|
42
|
+
end
|
41
43
|
end
|
42
44
|
|
43
45
|
it "should replay updates from origin into destination" do
|
@@ -46,15 +48,19 @@ describe Lhm::Entangler do
|
|
46
48
|
execute("update origin set common = 'updated'")
|
47
49
|
end
|
48
50
|
|
49
|
-
|
51
|
+
slave do
|
52
|
+
count(:destination, "common", "updated").must_equal(1)
|
53
|
+
end
|
50
54
|
end
|
51
55
|
|
52
56
|
it "should remove entanglement" do
|
53
57
|
@entangler.run {}
|
54
58
|
|
55
59
|
execute("insert into origin (common) values ('inserted')")
|
56
|
-
|
60
|
+
|
61
|
+
slave do
|
62
|
+
count(:destination, "common", "inserted").must_equal(0)
|
63
|
+
end
|
57
64
|
end
|
58
65
|
end
|
59
66
|
end
|
60
|
-
|
@@ -1,31 +1,36 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
# Schmidt
|
4
|
-
#
|
1
|
+
# Copyright (c) 2011, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
2
|
+
# Schmidt
|
5
3
|
|
6
4
|
require File.expand_path(File.dirname(__FILE__)) + "/../bootstrap"
|
7
5
|
|
8
6
|
require 'active_record'
|
9
7
|
require 'lhm/table'
|
8
|
+
require 'lhm/sql_helper'
|
10
9
|
|
11
10
|
module IntegrationHelper
|
11
|
+
attr_accessor :connection
|
12
12
|
|
13
13
|
#
|
14
14
|
# Connectivity
|
15
15
|
#
|
16
16
|
|
17
|
-
def
|
17
|
+
def connect_master!
|
18
|
+
@connection = connect!(3306)
|
19
|
+
end
|
20
|
+
|
21
|
+
def connect_slave!
|
22
|
+
@connection = connect!(3307)
|
23
|
+
end
|
24
|
+
|
25
|
+
def connect!(port)
|
18
26
|
ActiveRecord::Base.establish_connection(
|
19
27
|
:adapter => 'mysql',
|
28
|
+
:host => '127.0.0.1',
|
20
29
|
:database => 'lhm',
|
21
30
|
:username => '',
|
22
|
-
:
|
31
|
+
:port => port
|
23
32
|
)
|
24
33
|
|
25
|
-
ActiveRecord::Migration.verbose = !!ENV["VERBOSE"]
|
26
|
-
end
|
27
|
-
|
28
|
-
def connection
|
29
34
|
ActiveRecord::Base.connection
|
30
35
|
end
|
31
36
|
|
@@ -41,6 +46,23 @@ module IntegrationHelper
|
|
41
46
|
connection.execute(*args)
|
42
47
|
end
|
43
48
|
|
49
|
+
def slave(&block)
|
50
|
+
if master_slave_mode?
|
51
|
+
connect_slave!
|
52
|
+
|
53
|
+
# need to wait for the slave to catch up. a better method would be to
|
54
|
+
# check the master binlog position and wait for the slave to catch up
|
55
|
+
# to that position.
|
56
|
+
sleep 1
|
57
|
+
end
|
58
|
+
|
59
|
+
yield block
|
60
|
+
|
61
|
+
if master_slave_mode?
|
62
|
+
connect_master!
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
44
66
|
#
|
45
67
|
# Test Data
|
46
68
|
#
|
@@ -77,9 +99,18 @@ module IntegrationHelper
|
|
77
99
|
select_value(query).to_i
|
78
100
|
end
|
79
101
|
|
80
|
-
def key?(
|
102
|
+
def key?(table_name, cols, type = :non_unique)
|
81
103
|
non_unique = type == :non_unique ? 1 : 0
|
82
|
-
|
104
|
+
key_name = Lhm::SqlHelper.idx_name(table_name, cols)
|
105
|
+
query = "show indexes in #{ table_name } where key_name = '#{ key_name }' and non_unique = #{ non_unique }"
|
83
106
|
!!select_value(query)
|
84
107
|
end
|
108
|
+
|
109
|
+
#
|
110
|
+
# Environment
|
111
|
+
#
|
112
|
+
|
113
|
+
def master_slave_mode?
|
114
|
+
!!ENV["MASTER_SLAVE"]
|
115
|
+
end
|
85
116
|
end
|
@@ -1,7 +1,5 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
# Schmidt
|
4
|
-
#
|
1
|
+
# Copyright (c) 2011, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
2
|
+
# Schmidt
|
5
3
|
|
6
4
|
require File.expand_path(File.dirname(__FILE__)) + '/integration_helper'
|
7
5
|
|
@@ -9,9 +7,8 @@ require 'lhm'
|
|
9
7
|
|
10
8
|
describe Lhm do
|
11
9
|
include IntegrationHelper
|
12
|
-
include Lhm
|
13
10
|
|
14
|
-
before(:each) {
|
11
|
+
before(:each) { connect_master! }
|
15
12
|
|
16
13
|
describe "changes" do
|
17
14
|
before(:each) do
|
@@ -19,67 +16,91 @@ describe Lhm do
|
|
19
16
|
end
|
20
17
|
|
21
18
|
it "should add a column" do
|
22
|
-
|
19
|
+
Lhm.change_table(:users) do |t|
|
23
20
|
t.add_column(:logins, "INT(12) DEFAULT '0'")
|
24
21
|
end
|
25
22
|
|
26
|
-
|
27
|
-
:
|
28
|
-
|
29
|
-
|
23
|
+
slave do
|
24
|
+
table_read(:users).columns["logins"].must_equal({
|
25
|
+
:type => "int(12)",
|
26
|
+
:metadata => "DEFAULT '0'"
|
27
|
+
})
|
28
|
+
end
|
30
29
|
end
|
31
30
|
|
32
31
|
it "should copy all rows" do
|
33
32
|
23.times { |n| execute("insert into users set reference = '#{ n }'") }
|
34
33
|
|
35
|
-
|
34
|
+
Lhm.change_table(:users) do |t|
|
36
35
|
t.add_column(:logins, "INT(12) DEFAULT '0'")
|
37
36
|
end
|
38
37
|
|
39
|
-
|
38
|
+
slave do
|
39
|
+
count_all(:users).must_equal(23)
|
40
|
+
end
|
40
41
|
end
|
41
42
|
|
42
43
|
it "should remove a column" do
|
43
|
-
|
44
|
+
Lhm.change_table(:users) do |t|
|
44
45
|
t.remove_column(:comment)
|
45
46
|
end
|
46
47
|
|
47
|
-
|
48
|
+
slave do
|
49
|
+
table_read(:users).columns["comment"].must_equal nil
|
50
|
+
end
|
48
51
|
end
|
49
52
|
|
50
53
|
it "should add an index" do
|
51
|
-
|
54
|
+
Lhm.change_table(:users) do |t|
|
52
55
|
t.add_index([:comment, :created_at])
|
53
56
|
end
|
54
57
|
|
55
|
-
|
58
|
+
slave do
|
59
|
+
key?(:users, [:comment, :created_at]).must_equal(true)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should add an index on a column with a reserved name" do
|
64
|
+
Lhm.change_table(:users) do |t|
|
65
|
+
t.add_index(:group)
|
66
|
+
end
|
67
|
+
|
68
|
+
slave do
|
69
|
+
key?(:users, :group).must_equal(true)
|
70
|
+
end
|
56
71
|
end
|
57
72
|
|
58
73
|
it "should add a unqiue index" do
|
59
|
-
|
74
|
+
Lhm.change_table(:users) do |t|
|
60
75
|
t.add_unique_index(:comment)
|
61
76
|
end
|
62
77
|
|
63
|
-
|
78
|
+
slave do
|
79
|
+
key?(:users, :comment, :unique).must_equal(true)
|
80
|
+
end
|
64
81
|
end
|
65
82
|
|
66
83
|
it "should remove an index" do
|
67
|
-
|
84
|
+
Lhm.change_table(:users) do |t|
|
68
85
|
t.remove_index([:username, :created_at])
|
69
86
|
end
|
70
87
|
|
71
|
-
|
88
|
+
slave do
|
89
|
+
key?(:users, [:username, :created_at]).must_equal(false)
|
90
|
+
end
|
72
91
|
end
|
73
92
|
|
74
93
|
it "should apply a ddl statement" do
|
75
|
-
|
94
|
+
Lhm.change_table(:users) do |t|
|
76
95
|
t.ddl("alter table %s add column flag tinyint(1)" % t.name)
|
77
96
|
end
|
78
97
|
|
79
|
-
|
80
|
-
:
|
81
|
-
|
82
|
-
|
98
|
+
slave do
|
99
|
+
table_read(:users).columns["flag"].must_equal({
|
100
|
+
:type => "tinyint(1)",
|
101
|
+
:metadata => "DEFAULT NULL"
|
102
|
+
})
|
103
|
+
end
|
83
104
|
end
|
84
105
|
|
85
106
|
describe "parallel" do
|
@@ -93,34 +114,37 @@ describe Lhm do
|
|
93
114
|
end
|
94
115
|
end
|
95
116
|
|
96
|
-
|
117
|
+
Lhm.change_table(:users, :stride => 10, :throttle => 97) do |t|
|
97
118
|
t.add_column(:parallel, "INT(10) DEFAULT '0'")
|
98
119
|
end
|
99
120
|
|
100
121
|
insert.join
|
101
122
|
|
102
|
-
|
123
|
+
slave do
|
124
|
+
count_all(:users).must_equal(60)
|
125
|
+
end
|
103
126
|
end
|
104
|
-
end
|
105
127
|
|
106
|
-
|
107
|
-
|
128
|
+
it "should perserve deletes during migration" do
|
129
|
+
50.times { |n| execute("insert into users set reference = '#{ n }'") }
|
108
130
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
131
|
+
delete = Thread.new do
|
132
|
+
10.times do |n|
|
133
|
+
execute("delete from users where id = '#{ n + 1 }'")
|
134
|
+
sleep(0.17)
|
135
|
+
end
|
113
136
|
end
|
114
|
-
end
|
115
137
|
|
116
|
-
|
117
|
-
|
118
|
-
|
138
|
+
Lhm.change_table(:users, :stride => 10, :throttle => 97) do |t|
|
139
|
+
t.add_column(:parallel, "INT(10) DEFAULT '0'")
|
140
|
+
end
|
119
141
|
|
120
|
-
|
142
|
+
delete.join
|
121
143
|
|
122
|
-
|
144
|
+
slave do
|
145
|
+
count_all(:users).must_equal(40)
|
146
|
+
end
|
147
|
+
end
|
123
148
|
end
|
124
149
|
end
|
125
150
|
end
|
126
|
-
|