activerecord-slave 0.0.2
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 +7 -0
- data/.gitignore +19 -0
- data/.rspec +3 -0
- data/.rubocop.yml +56 -0
- data/.ruby-version +1 -0
- data/.travis.yml +31 -0
- data/Gemfile +4 -0
- data/Guardfile +46 -0
- data/LICENSE.txt +22 -0
- data/README.md +141 -0
- data/Rakefile +6 -0
- data/activerecord-slave.gemspec +40 -0
- data/lib/active_record/slave/config.rb +20 -0
- data/lib/active_record/slave/database_tasks.rb +30 -0
- data/lib/active_record/slave/model.rb +44 -0
- data/lib/active_record/slave/railtie.rb +9 -0
- data/lib/active_record/slave/replication_config.rb +28 -0
- data/lib/active_record/slave/replication_router.rb +21 -0
- data/lib/active_record/slave/version.rb +5 -0
- data/lib/activerecord-slave.rb +26 -0
- data/lib/tasks/activerecord-slave.rake +13 -0
- data/spec/active_record/slave/config_spec.rb +17 -0
- data/spec/active_record/slave/database_tasks_spec.rb +21 -0
- data/spec/active_record/slave/model_spec.rb +98 -0
- data/spec/active_record/slave/replication_config_spec.rb +46 -0
- data/spec/active_record/slave/replication_router_spec.rb +41 -0
- data/spec/active_record/slave/version_spec.rb +5 -0
- data/spec/models.rb +49 -0
- data/spec/schema.rb +26 -0
- data/spec/spec_helper.rb +71 -0
- data/spec/tasks/activerecord-slave_spec.rb +48 -0
- metadata +294 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
require "active_support/concern"
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Slave
|
5
|
+
module Model
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
private_class_method :define_slave_class
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def use_slave(replication_name)
|
14
|
+
replication_config = ActiveRecord::Slave.config.fetch_replication_config replication_name
|
15
|
+
@replication_router = ActiveRecord::Slave::ReplicationRouter.new replication_config
|
16
|
+
|
17
|
+
establish_connection replication_config.master_connection_name
|
18
|
+
|
19
|
+
@slave_class_repository = {}
|
20
|
+
base_class = self
|
21
|
+
replication_config.slave_connection_names.keys.each do |connection_name|
|
22
|
+
@slave_class_repository[connection_name] = define_slave_class base_class, connection_name
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def slave_for
|
27
|
+
@slave_class_repository.fetch @replication_router.slave_connection_name
|
28
|
+
end
|
29
|
+
|
30
|
+
def define_slave_class(base_class, connection_name)
|
31
|
+
model = Class.new(base_class) do
|
32
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
33
|
+
def self.name
|
34
|
+
"#{base_class.name}::Slave::#{connection_name}"
|
35
|
+
end
|
36
|
+
RUBY
|
37
|
+
end
|
38
|
+
model.class_eval { establish_connection(connection_name) }
|
39
|
+
model
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Slave
|
3
|
+
class ReplicationConfig
|
4
|
+
attr_reader :name, :master_connection_name
|
5
|
+
|
6
|
+
def initialize(replication_name)
|
7
|
+
@name = replication_name
|
8
|
+
end
|
9
|
+
|
10
|
+
def validate_config!
|
11
|
+
fail "Nothing register master connection." unless @master_connection_name
|
12
|
+
end
|
13
|
+
|
14
|
+
def register_master(connection_name)
|
15
|
+
@master_connection_name = connection_name
|
16
|
+
end
|
17
|
+
|
18
|
+
def register_slave(connection_name, weight)
|
19
|
+
@slave_connection_registory ||= {}
|
20
|
+
@slave_connection_registory.store connection_name, weight
|
21
|
+
end
|
22
|
+
|
23
|
+
def slave_connection_names
|
24
|
+
@slave_connection_registory
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "pickup"
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Slave
|
5
|
+
class ReplicationRouter
|
6
|
+
def initialize(replication_config)
|
7
|
+
fail "Not ActiveRecord::Slave::ReplicationConfig object." unless replication_config.is_a? ActiveRecord::Slave::ReplicationConfig
|
8
|
+
@replication_config = replication_config
|
9
|
+
end
|
10
|
+
|
11
|
+
def master_connection_name
|
12
|
+
@replication_config.master_connection_name
|
13
|
+
end
|
14
|
+
|
15
|
+
def slave_connection_name
|
16
|
+
slaves = Pickup.new(@replication_config.slave_connection_names)
|
17
|
+
slaves.pick(1)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "active_support/lazy_load_hooks"
|
2
|
+
|
3
|
+
require "active_record"
|
4
|
+
|
5
|
+
require "active_record/slave/version"
|
6
|
+
require "active_record/slave/config"
|
7
|
+
require "active_record/slave/replication_config"
|
8
|
+
require "active_record/slave/replication_router"
|
9
|
+
require "active_record/slave/model"
|
10
|
+
require "active_record/slave/database_tasks"
|
11
|
+
|
12
|
+
module ActiveRecord
|
13
|
+
module Slave
|
14
|
+
class << self
|
15
|
+
def config
|
16
|
+
@config ||= Config.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def configure(&block)
|
20
|
+
config.instance_eval(&block)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "active_record/slave/railtie" if defined? Rails
|
@@ -0,0 +1,13 @@
|
|
1
|
+
namespace :active_record do
|
2
|
+
namespace :slave do
|
3
|
+
desc "Create database for replicaition master"
|
4
|
+
task :db_create, %i(replicaition) => %i(environment) do |_, args|
|
5
|
+
ActiveRecord::Slave::DatabaseTasks.create_database args
|
6
|
+
end
|
7
|
+
|
8
|
+
desc "Drop database for replicaition master"
|
9
|
+
task :db_drop, %i(replicaition) => %i(environment) do |_, args|
|
10
|
+
ActiveRecord::Slave::DatabaseTasks.drop_database args
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
describe ActiveRecord::Slave::Config do
|
2
|
+
let!(:config) do
|
3
|
+
config = ActiveRecord::Slave::Config.new
|
4
|
+
config.define_replication(:test) do |replication|
|
5
|
+
replication.register_master :test_master
|
6
|
+
replication.register_slave :test_slave_001, 70
|
7
|
+
replication.register_slave :test_slave_002, 30
|
8
|
+
end
|
9
|
+
config
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#fetch_replication_config" do
|
13
|
+
it "returns :test replication config" do
|
14
|
+
expect(config.fetch_replication_config :test).to be_a ActiveRecord::Slave::ReplicationConfig
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
describe ActiveRecord::Slave::DatabaseTasks do
|
2
|
+
let(:args) do
|
3
|
+
{ replicaition: "task" }
|
4
|
+
end
|
5
|
+
|
6
|
+
after(:each) do
|
7
|
+
ActiveRecord::Base.establish_connection(:test)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "Create database" do
|
11
|
+
ActiveRecord::Slave::DatabaseTasks.create_database args
|
12
|
+
expect(ActiveRecord::Base.connection.execute("SHOW DATABASES").include? ["test_task"]).to be true
|
13
|
+
end
|
14
|
+
|
15
|
+
context "Created task replicaition database" do
|
16
|
+
it "Drop database" do
|
17
|
+
ActiveRecord::Slave::DatabaseTasks.drop_database args
|
18
|
+
expect(ActiveRecord::Base.connection.execute("SHOW DATABASES").include? ["test_task"]).to be false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
describe ActiveRecord::Slave::Model do
|
2
|
+
it "connect to master" do
|
3
|
+
expect(User.connection.pool.spec.config[:port]).to eq 21891
|
4
|
+
end
|
5
|
+
|
6
|
+
it "connect to slaves", retry: 3 do
|
7
|
+
slave_ports = 10.times.map { User.slave_for.connection.pool.spec.config[:port] }.uniq
|
8
|
+
expect(slave_ports.count).to eq 2
|
9
|
+
expect(slave_ports).to include 21892
|
10
|
+
expect(slave_ports).to include 21893
|
11
|
+
end
|
12
|
+
|
13
|
+
it "connect to default" do
|
14
|
+
expect(Item.connection.pool.spec.config[:port]).to eq 3306
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "Write to master, Read from slave" do
|
18
|
+
it "returns user object from slave database" do
|
19
|
+
user_from_master = User.create name: "alice"
|
20
|
+
|
21
|
+
user_from_slave = User.slave_for.find user_from_master.id
|
22
|
+
|
23
|
+
expect(user_from_master.id).to eq user_from_slave.id
|
24
|
+
expect(user_from_master.name).to eq user_from_slave.name
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "Assosiations" do
|
29
|
+
let(:user) { User.create name: "alice" }
|
30
|
+
|
31
|
+
context "has_many object" do
|
32
|
+
context "in default database" do
|
33
|
+
let(:item) { Item.create name: "foo", count: 1, user: user }
|
34
|
+
|
35
|
+
it "returns user object, belongs_to" do
|
36
|
+
expect(item.user).to be_a User
|
37
|
+
expect(item.user.id).to eq user.id
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "model connect to default database" do
|
41
|
+
it "returns has_many objects" do
|
42
|
+
expect { item }.to change { user.items.count }.from(0).to(1)
|
43
|
+
expect { Item.create name: "bar", count: 1, user: user }.to change { user.items.count }.from(1).to(2)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "returns replication model object" do
|
47
|
+
expect(Item.find(item.id).user).to eq user
|
48
|
+
end
|
49
|
+
|
50
|
+
it "returns default database service port" do
|
51
|
+
expect(Item.connection.pool.spec.config[:port]).to eq 3306
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "in replication database" do
|
57
|
+
let(:skill) { Skill.create name: "bar", user: user }
|
58
|
+
|
59
|
+
it "returns user object, belongs_to" do
|
60
|
+
expect(skill.user).to be_a User
|
61
|
+
expect(skill.user.id).to eq user.id
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "model connect to raplication databases" do
|
65
|
+
it "returns has_many objects" do
|
66
|
+
expect { skill }.to change { user.skills.count }.from(0).to(1)
|
67
|
+
expect { Skill.create name: "foobar", user: user }.to change { user.skills.count }.from(1).to(2)
|
68
|
+
expect(user.skills.count).to eq Skill.slave_for.where(user_id: user.id).count
|
69
|
+
end
|
70
|
+
|
71
|
+
it "returns other model object from master" do
|
72
|
+
expect(Skill.find(skill.id).user).to eq user
|
73
|
+
end
|
74
|
+
|
75
|
+
it "returns other model object from slaves" do
|
76
|
+
expect(Skill.slave_for.find(skill.id).user).to eq user
|
77
|
+
end
|
78
|
+
|
79
|
+
it "returns master database service port" do
|
80
|
+
expect(Skill.connection.pool.spec.config[:port]).to eq 21891
|
81
|
+
end
|
82
|
+
|
83
|
+
it "returns slave database service port" do
|
84
|
+
expect(Skill.slave_for.connection.pool.spec.config[:port]).to eq(21892) | eq(21893)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context "Enable DatabaseRewinder, delete records at each after specs" do
|
91
|
+
it "No records in DataBases" do
|
92
|
+
expect(User.all.count).to eq 0
|
93
|
+
expect(Item.all.count).to eq 0
|
94
|
+
expect(Skill.all.count).to eq 0
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
describe ActiveRecord::Slave::ReplicationConfig do
|
2
|
+
let!(:replication_config) do
|
3
|
+
config = ActiveRecord::Slave::ReplicationConfig.new :test_replication
|
4
|
+
|
5
|
+
config.register_master :test_master
|
6
|
+
|
7
|
+
config.register_slave :test_slave_001, 70
|
8
|
+
config.register_slave :test_slave_002, 30
|
9
|
+
|
10
|
+
config
|
11
|
+
end
|
12
|
+
|
13
|
+
context "not setup config for ReplicationConfig object" do
|
14
|
+
context "#validate_config!" do
|
15
|
+
let!(:not_setup_replication_config) { ActiveRecord::Slave::ReplicationConfig.new :test_replication }
|
16
|
+
|
17
|
+
it "returns raise, nothing register master connection" do
|
18
|
+
expect { not_setup_replication_config.validate_config! }.to raise_error "Nothing register master connection."
|
19
|
+
end
|
20
|
+
|
21
|
+
it "returns nil, config is valid" do
|
22
|
+
expect(replication_config.validate_config!).to be nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "#name" do
|
28
|
+
it "returns replication name" do
|
29
|
+
expect(replication_config.name).to eq :test_replication
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "#master_connection_name" do
|
34
|
+
it "returns master database connection name" do
|
35
|
+
expect(replication_config.master_connection_name).to eq :test_master
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "#slave_connection_names" do
|
40
|
+
it "returns slave database connection names" do
|
41
|
+
expect(replication_config.slave_connection_names).to be_a Hash
|
42
|
+
expect(replication_config.slave_connection_names).to include test_slave_001: 70
|
43
|
+
expect(replication_config.slave_connection_names).to include test_slave_002: 30
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
describe ActiveRecord::Slave::ReplicationRouter do
|
2
|
+
let!(:replication_config) do
|
3
|
+
config = ActiveRecord::Slave::Config.new
|
4
|
+
config.define_replication(:test) do |replication|
|
5
|
+
replication.register_master :test_master
|
6
|
+
replication.register_slave :test_slave_001, 70
|
7
|
+
replication.register_slave :test_slave_002, 30
|
8
|
+
end
|
9
|
+
config.fetch_replication_config :test
|
10
|
+
end
|
11
|
+
|
12
|
+
let!(:replication_router) do
|
13
|
+
ActiveRecord::Slave::ReplicationRouter.new replication_config
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#new" do
|
17
|
+
it "Make instance object" do
|
18
|
+
expect(ActiveRecord::Slave::ReplicationRouter.new replication_config).to be_a ActiveRecord::Slave::ReplicationRouter
|
19
|
+
end
|
20
|
+
|
21
|
+
it "Raise error" do
|
22
|
+
expect { ActiveRecord::Slave::ReplicationRouter.new "test" }.to raise_error "Not ActiveRecord::Slave::ReplicationConfig object."
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#master_connection_name" do
|
27
|
+
it "returns master database conneciton name" do
|
28
|
+
expect(replication_router.master_connection_name).to eq :test_master
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#slave_connection_name" do
|
33
|
+
it "returns slave database conneciton name, by wait lottery confidence 99%", retry: 3 do
|
34
|
+
slave_connection_names = 10000.times.map { replication_router.slave_connection_name }
|
35
|
+
expect(slave_connection_names.count(:test_slave_001)).to be_between 7000 - 60, 7000 + 60
|
36
|
+
expect(slave_connection_names.count(:test_slave_002)).to be_between 3000 - 60, 3000 + 60
|
37
|
+
expect(slave_connection_names).to include :test_slave_001
|
38
|
+
expect(slave_connection_names).to include :test_slave_002
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/spec/models.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
base = { "adapter" => "mysql2", "encoding" => "utf8", "pool" => 5, "username" => "root", "password" => "msandbox", "host" => "127.0.0.1" }
|
2
|
+
|
3
|
+
ActiveRecord::Base.configurations = {
|
4
|
+
"test_master" => base.merge("database" => "test_slave", "port" => 21891),
|
5
|
+
"test_slave_001" => base.merge("database" => "test_slave", "port" => 21892),
|
6
|
+
"test_slave_002" => base.merge("database" => "test_slave", "port" => 21893),
|
7
|
+
"test" => base.merge("database" => "test", "port" => 3306, "password" => ""),
|
8
|
+
|
9
|
+
"test_task_master" => base.merge("database" => "test_task", "port" => 21891),
|
10
|
+
"test_task_slave_001" => base.merge("database" => "test_task", "port" => 21892),
|
11
|
+
"test_task_slave_002" => base.merge("database" => "test_task", "port" => 21893)
|
12
|
+
}
|
13
|
+
|
14
|
+
ActiveRecord::Slave.configure do |config|
|
15
|
+
config.define_replication(:user) do |replication|
|
16
|
+
replication.register_master(:test_master)
|
17
|
+
|
18
|
+
replication.register_slave(:test_slave_001, 70)
|
19
|
+
replication.register_slave(:test_slave_002, 30)
|
20
|
+
end
|
21
|
+
|
22
|
+
config.define_replication(:task) do |replication|
|
23
|
+
replication.register_master(:test_task_master)
|
24
|
+
|
25
|
+
replication.register_slave(:test_task_slave_001, 1)
|
26
|
+
replication.register_slave(:test_task_slave_002, 1)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
ActiveRecord::Base.establish_connection(:test)
|
31
|
+
|
32
|
+
class User < ActiveRecord::Base
|
33
|
+
has_many :items
|
34
|
+
has_many :skills
|
35
|
+
|
36
|
+
include ActiveRecord::Slave::Model
|
37
|
+
use_slave :user
|
38
|
+
end
|
39
|
+
|
40
|
+
class Item < ActiveRecord::Base
|
41
|
+
belongs_to :user
|
42
|
+
end
|
43
|
+
|
44
|
+
class Skill < ActiveRecord::Base
|
45
|
+
belongs_to :user
|
46
|
+
|
47
|
+
include ActiveRecord::Slave::Model
|
48
|
+
use_slave :user
|
49
|
+
end
|
data/spec/schema.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
ActiveRecord::Schema.define(version: 0) do
|
2
|
+
create_table "users", force: :cascade do |t|
|
3
|
+
t.string "name", null: false
|
4
|
+
t.datetime "created_at", null: false
|
5
|
+
t.datetime "updated_at", null: false
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
ActiveRecord::Schema.define(version: 1) do
|
10
|
+
create_table "items", force: :cascade do |t|
|
11
|
+
t.integer "user_id", null: false
|
12
|
+
t.string "name", null: false
|
13
|
+
t.integer "count", null: false
|
14
|
+
t.datetime "created_at", null: false
|
15
|
+
t.datetime "updated_at", null: false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
ActiveRecord::Schema.define(version: 2) do
|
20
|
+
create_table "skills", force: :cascade do |t|
|
21
|
+
t.integer "user_id", null: false
|
22
|
+
t.string "name", null: false
|
23
|
+
t.datetime "created_at", null: false
|
24
|
+
t.datetime "updated_at", null: false
|
25
|
+
end
|
26
|
+
end
|