replicat 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +135 -0
- data/Rakefile +1 -0
- data/lib/replicat.rb +6 -0
- data/lib/replicat/active_record.rb +1 -0
- data/lib/replicat/model.rb +9 -0
- data/lib/replicat/proxy.rb +100 -0
- data/lib/replicat/replicable.rb +54 -0
- data/lib/replicat/version.rb +3 -0
- data/replicat.gemspec +33 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/app/models/recipe.rb +3 -0
- data/spec/dummy/app/models/user.rb +3 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +51 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +41 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +23 -0
- data/spec/dummy/config/environments/production.rb +71 -0
- data/spec/dummy/config/environments/test.rb +33 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +8 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +2 -0
- data/spec/dummy/db/schema.rb +26 -0
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/replicat/replicable_spec.rb +81 -0
- data/spec/spec_helper.rb +15 -0
- metadata +324 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a4318b9a2e2e1c7e637af213a8ae09f2d292235e
|
4
|
+
data.tar.gz: 5063232ce2dcc9403137e7d4ec2f5ea4d011a5b9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8220a33336d837f1adfe5b848eaf9a301ddbdf9c9487abb4f7c90a6e6558f43907195e517ef435ab18c60b3b75ac08a605202646d5e08ff52b0a5d9225125e1d
|
7
|
+
data.tar.gz: 66bf65c92a44b0935e92473c77a0ecae0f09b72b64668e33ef7c56a58b0fde6068e594fc24613c43fcc24f010d812d202e354993da4475e6acdf4279b39d5986
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
log/*.log
|
13
|
+
pkg
|
14
|
+
rdoc
|
15
|
+
spec/dummy/.sass-cache
|
16
|
+
spec/dummy/db/*.sqlite3
|
17
|
+
spec/dummy/log/*.log
|
18
|
+
spec/dummy/tmp/
|
19
|
+
spec/reports
|
20
|
+
test/tmp
|
21
|
+
test/version_tmp
|
22
|
+
tmp
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Ryo Nakamura
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
# Replicat
|
2
|
+
master-slave replication helper for ActiveRecord.
|
3
|
+
|
4
|
+
## Installation
|
5
|
+
```ruby
|
6
|
+
# Gemfile
|
7
|
+
gem "replicat"
|
8
|
+
```
|
9
|
+
|
10
|
+
## Usage
|
11
|
+
Modify your replicable models & config/database.yml.
|
12
|
+
|
13
|
+
### model
|
14
|
+
```ruby
|
15
|
+
# app/models/user.rb
|
16
|
+
class User < ActiveRecord::Base
|
17
|
+
replicate
|
18
|
+
end
|
19
|
+
|
20
|
+
# config/database.yml
|
21
|
+
production:
|
22
|
+
adapter: mysql2
|
23
|
+
encoding: utf8
|
24
|
+
host: 192.168.24.1
|
25
|
+
port: 3306
|
26
|
+
replications:
|
27
|
+
slave1:
|
28
|
+
adapter: mysql2
|
29
|
+
encoding: utf8
|
30
|
+
host: 192.168.24.2
|
31
|
+
port: 3306
|
32
|
+
slave2:
|
33
|
+
adapter: mysql2
|
34
|
+
encoding: utf8
|
35
|
+
host: 192.168.24.3
|
36
|
+
port: 3306
|
37
|
+
slave3:
|
38
|
+
adapter: mysql2
|
39
|
+
encoding: utf8
|
40
|
+
host: 192.168.24.4
|
41
|
+
port: 3306
|
42
|
+
```
|
43
|
+
|
44
|
+
### replication
|
45
|
+
Now SELECT queries of User model will be sent to slave connections.
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
User.create(name: "replicat")
|
49
|
+
User.first #=> nil
|
50
|
+
```
|
51
|
+
|
52
|
+
### using
|
53
|
+
`using` can help you specify particular connection.
|
54
|
+
`using(:master)` uses master connection.
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
User.create(name: "replicat")
|
58
|
+
User.using(:master) { User.first } #=> #<User id: 2, name: "replicat">
|
59
|
+
```
|
60
|
+
|
61
|
+
### round-robin
|
62
|
+
slave connections are balanced by round-robin way.
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
User.using(:slave1) { User.create(name: "replicat") }
|
66
|
+
User.first #=> #<User id: 2, name: "replicat">
|
67
|
+
User.first #=> nil
|
68
|
+
User.first #=> nil
|
69
|
+
User.first #=> #<User id: 2, name: "replicat">
|
70
|
+
User.first #=> nil
|
71
|
+
User.first #=> nil
|
72
|
+
User.first #=> #<User id: 2, name: "replicat">
|
73
|
+
User.first #=> nil
|
74
|
+
User.first #=> nil
|
75
|
+
```
|
76
|
+
|
77
|
+
### multi master-slave set
|
78
|
+
Pass the master's connection name to `replicate` method.
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
# app/models/user.rb
|
82
|
+
class User < ActiveRecord::Base
|
83
|
+
replicate "production_user"
|
84
|
+
end
|
85
|
+
|
86
|
+
# app/models/recipe.rb
|
87
|
+
class Recipe < ActiveRecord::Base
|
88
|
+
replicate "production_recipe"
|
89
|
+
end
|
90
|
+
|
91
|
+
# config/database.yml
|
92
|
+
production_user:
|
93
|
+
<<: *production
|
94
|
+
host: 192.168.24.1
|
95
|
+
replications:
|
96
|
+
slave1:
|
97
|
+
<<: *slave
|
98
|
+
host: 192.168.24.2
|
99
|
+
slave2:
|
100
|
+
<<: *slave
|
101
|
+
host: 192.168.24.3
|
102
|
+
slave3:
|
103
|
+
<<: *slave
|
104
|
+
host: 192.168.24.4
|
105
|
+
|
106
|
+
production_recipe:
|
107
|
+
<<: *production
|
108
|
+
host: 192.168.24.5
|
109
|
+
replications:
|
110
|
+
slave1:
|
111
|
+
<<: *slave
|
112
|
+
host: 192.168.24.6
|
113
|
+
slave2:
|
114
|
+
<<: *slave
|
115
|
+
host: 192.168.24.7
|
116
|
+
slave3:
|
117
|
+
<<: *slave
|
118
|
+
host: 192.168.24.8
|
119
|
+
```
|
120
|
+
|
121
|
+
|
122
|
+
## For contributors
|
123
|
+
```sh
|
124
|
+
# setup gems
|
125
|
+
bundle install
|
126
|
+
|
127
|
+
# setup database
|
128
|
+
cd spec/dummy
|
129
|
+
rake db:create
|
130
|
+
rake db:schema:load RAILS_ENV=test
|
131
|
+
cd ../../
|
132
|
+
|
133
|
+
# run tests
|
134
|
+
bundle exec rspec
|
135
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/replicat.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ActiveRecord::Base.extend Replicat::Model
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Replicat
|
2
|
+
module Model
|
3
|
+
def replicate(connection_name = nil)
|
4
|
+
raise "You must set `connection_name` of this model class." if !connection_name && !defined?(Rails)
|
5
|
+
include Replicat::Replicable
|
6
|
+
self.connection_name = connection_name || Rails.env.to_s
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require "active_support/core_ext/object/try"
|
2
|
+
|
3
|
+
module Replicat
|
4
|
+
class Proxy
|
5
|
+
REPLICABLE_METHOD_NAMES = %w[select_all schema_cache]
|
6
|
+
REPLICABLE_METHOD_NAMES_REGEXP = /\A#{Regexp.union(REPLICABLE_METHOD_NAMES)}\z/
|
7
|
+
|
8
|
+
attr_accessor :current_connection_name
|
9
|
+
|
10
|
+
attr_writer :index
|
11
|
+
|
12
|
+
def initialize(model_class)
|
13
|
+
@model_class = model_class
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def method_missing(method_name, *args, &block)
|
19
|
+
if current_connection_name
|
20
|
+
current_connection.send(method_name, *args, &block)
|
21
|
+
else
|
22
|
+
connection_by_method_name(method_name).send(method_name, *args, &block)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def connection_by_method_name(method_name)
|
27
|
+
REPLICABLE_METHOD_NAMES_REGEXP === method_name ? slave_connection : master_connection
|
28
|
+
end
|
29
|
+
|
30
|
+
def current_connection
|
31
|
+
if current_connection_name.to_s == "master"
|
32
|
+
master_connection
|
33
|
+
else
|
34
|
+
slave_connection_pool_table[current_connection_name.to_s].try(:connection) or raise_connection_not_found
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def master_connection
|
39
|
+
@model_class.connection_without_proxy
|
40
|
+
end
|
41
|
+
|
42
|
+
def slave_connection
|
43
|
+
slave_connection_pool.connection
|
44
|
+
end
|
45
|
+
|
46
|
+
def slave_connection_pool
|
47
|
+
slave_connection_pool_table.values[slave_connection_index]
|
48
|
+
end
|
49
|
+
|
50
|
+
def slave_connection_pool_table
|
51
|
+
@slave_connection_pools ||= @model_class.replications.inject({}) do |table, (name, configuration)|
|
52
|
+
table.merge(name => ConnectionPoolCreater.create(configuration))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def raise_connection_not_found
|
57
|
+
raise Error, "connection #{current_connection_name} is not found"
|
58
|
+
end
|
59
|
+
|
60
|
+
def slave_connection_index
|
61
|
+
index.tap { increment_slave_connection_index }
|
62
|
+
end
|
63
|
+
|
64
|
+
def increment_slave_connection_index
|
65
|
+
self.index = (index + 1) % slave_connection_pool_table.size
|
66
|
+
end
|
67
|
+
|
68
|
+
def clear_query_cache
|
69
|
+
master_connection.clear_query_cache
|
70
|
+
slave_connection_pool_table.values.map {|pool| pool.connection.clear_query_cache }
|
71
|
+
end
|
72
|
+
|
73
|
+
def index
|
74
|
+
@index ||= rand(slave_connection_pool_table.size)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Creates database connection pool from configuration Hash table.
|
78
|
+
class ConnectionPoolCreater
|
79
|
+
def self.create(*args)
|
80
|
+
new(*args).create
|
81
|
+
end
|
82
|
+
|
83
|
+
def initialize(configuration)
|
84
|
+
@configuration = configuration.dup
|
85
|
+
end
|
86
|
+
|
87
|
+
def create
|
88
|
+
ActiveRecord::ConnectionAdapters::ConnectionPool.new(
|
89
|
+
ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new(
|
90
|
+
@configuration,
|
91
|
+
nil,
|
92
|
+
).spec,
|
93
|
+
)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class Error < StandardError
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require "active_record"
|
2
|
+
require "active_support/concern"
|
3
|
+
require "active_support/core_ext/module/aliasing"
|
4
|
+
|
5
|
+
module Replicat
|
6
|
+
module Replicable
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
class << self
|
11
|
+
def proxy
|
12
|
+
@proxy ||= Proxy.new(self)
|
13
|
+
end
|
14
|
+
|
15
|
+
alias_method_chain :connection, :proxy
|
16
|
+
|
17
|
+
attr_accessor :connection_name
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
def connection_with_proxy
|
23
|
+
if has_any_replication?
|
24
|
+
proxy
|
25
|
+
else
|
26
|
+
connection_without_proxy
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def has_any_replication?
|
31
|
+
has_configuration? && replications.present?
|
32
|
+
end
|
33
|
+
|
34
|
+
def has_configuration?
|
35
|
+
!!configuration
|
36
|
+
end
|
37
|
+
|
38
|
+
def configuration
|
39
|
+
connection_name && configurations[connection_name]
|
40
|
+
end
|
41
|
+
|
42
|
+
def replications
|
43
|
+
configuration["replications"]
|
44
|
+
end
|
45
|
+
|
46
|
+
def using(connection_name)
|
47
|
+
proxy.current_connection_name = connection_name
|
48
|
+
yield
|
49
|
+
ensure
|
50
|
+
proxy.current_connection_name = nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/replicat.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "replicat/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "replicat"
|
7
|
+
spec.version = Replicat::VERSION
|
8
|
+
spec.authors = ["Ryo Nakamura"]
|
9
|
+
spec.email = ["r7kamura@gmail.com"]
|
10
|
+
spec.summary = "master-slave replication helper for ActiveRecord"
|
11
|
+
spec.homepage = "https://github.com/r7kamura/replicat"
|
12
|
+
spec.license = "MIT"
|
13
|
+
|
14
|
+
spec.files = `git ls-files`.split($/)
|
15
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
16
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
17
|
+
spec.require_paths = ["lib"]
|
18
|
+
|
19
|
+
spec.add_dependency "activerecord"
|
20
|
+
spec.add_dependency "activesupport"
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "coffee-rails", ">= 3.0.10"
|
23
|
+
spec.add_development_dependency "jquery-rails"
|
24
|
+
spec.add_development_dependency "pry"
|
25
|
+
spec.add_development_dependency "pry-rails"
|
26
|
+
spec.add_development_dependency "rails", ">= 3.0.10"
|
27
|
+
spec.add_development_dependency "rake"
|
28
|
+
spec.add_development_dependency "rspec", ">= 2.14.1"
|
29
|
+
spec.add_development_dependency "rspec-rails", ">= 2.14.0"
|
30
|
+
spec.add_development_dependency "sass-rails", ">= 3.0.10"
|
31
|
+
spec.add_development_dependency "sqlite3"
|
32
|
+
spec.add_development_dependency "uglifier"
|
33
|
+
end
|
data/spec/dummy/Rakefile
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
3
|
+
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
4
|
+
|
5
|
+
require File.expand_path('../config/application', __FILE__)
|
6
|
+
|
7
|
+
Dummy::Application.load_tasks
|
@@ -0,0 +1,15 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// the compiled file.
|
9
|
+
//
|
10
|
+
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
|
11
|
+
// GO AFTER THE REQUIRES BELOW.
|
12
|
+
//
|
13
|
+
//= require jquery
|
14
|
+
//= require jquery_ujs
|
15
|
+
//= require_tree .
|