slave_pools 0.1.2 → 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +196 -0
- data/lib/slave_pools/active_record_extensions.rb +7 -36
- data/lib/slave_pools/config.rb +22 -0
- data/lib/slave_pools/connection_proxy.rb +86 -225
- data/lib/slave_pools/engine.rb +24 -0
- data/lib/slave_pools/hijack.rb +24 -0
- data/lib/slave_pools/observer_extensions.rb +5 -5
- data/lib/slave_pools/pool.rb +21 -0
- data/lib/slave_pools/pools.rb +60 -0
- data/lib/slave_pools/query_cache.rb +35 -0
- data/lib/slave_pools/version.rb +3 -0
- data/lib/slave_pools.rb +55 -27
- data/spec/config/test_model.rb +19 -0
- data/spec/connection_proxy_spec.rb +182 -257
- data/spec/observer_extensions_spec.rb +21 -0
- data/spec/pool_spec.rb +35 -0
- data/spec/query_cache_spec.rb +83 -0
- data/spec/slave_pools_spec.rb +18 -56
- data/spec/spec_helper.rb +34 -6
- metadata +105 -108
- data/README.rdoc +0 -259
- data/lib/slave_pools/query_cache_compat.rb +0 -45
- data/lib/slave_pools/slave_pool.rb +0 -29
- data/slave_pools.gemspec +0 -25
- data/spec/config/database.yml +0 -43
- data/spec/slave_pool_spec.rb +0 -43
data/spec/slave_pools_spec.rb
CHANGED
@@ -2,67 +2,29 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
|
2
2
|
|
3
3
|
describe SlavePools do
|
4
4
|
|
5
|
-
before(:
|
6
|
-
|
7
|
-
|
8
|
-
@sql = 'SELECT NOW()'
|
5
|
+
before(:each) do
|
6
|
+
SlavePools.pools.each{|_, pool| pool.reset }
|
7
|
+
@proxy = SlavePools.proxy
|
9
8
|
end
|
10
|
-
|
11
|
-
context "with no setup" do
|
12
|
-
it "should not error out if next slave is called and SlavePools is not set up" do
|
13
|
-
SlavePools.should_receive(:active?).and_return(false)
|
14
|
-
SlavePools.next_slave!.should be_nil
|
15
|
-
end
|
16
|
-
|
17
|
-
it "should not error out if current is called and SlavePools is not set up" do
|
18
|
-
SlavePools.should_receive(:active?).and_return(false)
|
19
|
-
SlavePools.current.should be_nil
|
20
|
-
end
|
21
|
-
|
22
|
-
it "should yield on a with_pool call if slave_pools is not active" do
|
23
|
-
SlavePools.should_receive(:active?).and_return(false)
|
24
|
-
ActiveRecord::Base.connection.should_receive(:execute)
|
25
|
-
SlavePools.with_pool('admin') {ActiveRecord::Base.connection.execute(@sql)}
|
26
|
-
end
|
27
9
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
SlavePools.with_master {ActiveRecord::Base.connection.execute(@sql)}
|
32
|
-
end
|
10
|
+
it 'should delegate next_slave! call to connection proxy' do
|
11
|
+
@proxy.should_receive(:next_slave!).exactly(1)
|
12
|
+
SlavePools.next_slave!
|
33
13
|
end
|
34
|
-
|
35
|
-
describe "Slave Pool Wrapper calls" do
|
36
|
-
before(:each) do
|
37
|
-
SlavePools.setup!
|
38
|
-
@proxy = ActiveRecord::Base.connection_proxy
|
39
|
-
end
|
40
|
-
|
41
|
-
it 'should send next_slave! call to connection proxy' do
|
42
|
-
ActiveRecord::Base.should_receive(:respond_to?).exactly(1)
|
43
|
-
SlavePools.active?
|
44
|
-
end
|
45
|
-
|
46
|
-
it 'should send next_slave! call to connection proxy' do
|
47
|
-
@proxy.should_receive(:next_slave!).exactly(1)
|
48
|
-
SlavePools.next_slave!
|
49
|
-
end
|
50
|
-
|
51
|
-
it 'should send with_pool call to connection proxy' do
|
52
|
-
@proxy.should_receive(:with_pool).exactly(1)
|
53
|
-
SlavePools.with_pool('test')
|
54
|
-
end
|
55
|
-
|
56
|
-
it 'should send with_master call to connection proxy' do
|
57
|
-
@proxy.should_receive(:with_master).exactly(1)
|
58
|
-
SlavePools.with_master
|
59
|
-
end
|
60
14
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
15
|
+
it 'should delegate with_pool call to connection proxy' do
|
16
|
+
@proxy.should_receive(:with_pool).exactly(1)
|
17
|
+
SlavePools.with_pool('test')
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should delegate with_master call to connection proxy' do
|
21
|
+
@proxy.should_receive(:with_master).exactly(1)
|
22
|
+
SlavePools.with_master
|
23
|
+
end
|
65
24
|
|
25
|
+
it 'should delegate current call to connection proxy' do
|
26
|
+
@proxy.should_receive(:current).exactly(1)
|
27
|
+
SlavePools.current
|
66
28
|
end
|
67
29
|
|
68
30
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -2,16 +2,44 @@ require 'rubygems'
|
|
2
2
|
require 'bundler/setup'
|
3
3
|
require 'logger'
|
4
4
|
|
5
|
-
require 'slave_pools'
|
6
|
-
|
7
5
|
module Rails
|
8
6
|
def self.env
|
9
7
|
ActiveSupport::StringInquirer.new("test")
|
10
8
|
end
|
11
9
|
end
|
12
10
|
|
13
|
-
|
14
|
-
|
11
|
+
require 'active_record'
|
12
|
+
spec_dir = File.dirname(__FILE__)
|
13
|
+
ActiveRecord::Base.logger = Logger.new(spec_dir + "/debug.log")
|
14
|
+
ActiveRecord::Base.configurations = YAML::load(File.open(spec_dir + '/config/database.yml'))
|
15
|
+
|
16
|
+
ActiveRecord::Base.establish_connection :test
|
17
|
+
ActiveRecord::Migration.verbose = false
|
18
|
+
ActiveRecord::Migration.create_table(:test_models, :force => true) {}
|
19
|
+
ActiveRecord::Migration.create_table(:test_subs, :force => true) {|t| t.integer :test_model_id}
|
15
20
|
|
16
|
-
|
17
|
-
|
21
|
+
require 'slave_pools'
|
22
|
+
SlavePools::Engine.initializers.each(&:run)
|
23
|
+
ActiveSupport.run_load_hooks(:after_initialize, SlavePools::Engine)
|
24
|
+
|
25
|
+
module SlavePools::Testing
|
26
|
+
# Creates aliases for the slave connections in each pool
|
27
|
+
# for easy reference in tests.
|
28
|
+
def create_slave_aliases(proxy)
|
29
|
+
proxy.slave_pools.each do |name, pool|
|
30
|
+
pool.slaves.each.with_index do |slave, i|
|
31
|
+
instance_variable_set("@#{name}_slave#{i + 1}", slave.retrieve_connection)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def reset_proxy(proxy)
|
37
|
+
proxy.slave_pools.each{|_, pool| pool.reset }
|
38
|
+
proxy.current_pool = proxy.slave_pools[:default]
|
39
|
+
proxy.current = proxy.current_slave
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
RSpec.configure do |c|
|
44
|
+
c.include SlavePools::Testing
|
45
|
+
end
|
metadata
CHANGED
@@ -1,144 +1,141 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: slave_pools
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 1
|
9
|
-
- 2
|
10
|
-
version: 0.1.2
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0.rc1
|
11
5
|
platform: ruby
|
12
|
-
authors:
|
6
|
+
authors:
|
13
7
|
- Dan Drabik
|
8
|
+
- Lance Ivy
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2013-12-31 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
21
15
|
name: activerecord
|
22
|
-
|
23
|
-
|
24
|
-
none: false
|
25
|
-
requirements:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
26
18
|
- - ~>
|
27
|
-
- !ruby/object:Gem::Version
|
28
|
-
hash: 23
|
29
|
-
segments:
|
30
|
-
- 3
|
31
|
-
- 2
|
32
|
-
- 12
|
19
|
+
- !ruby/object:Gem::Version
|
33
20
|
version: 3.2.12
|
34
21
|
type: :runtime
|
35
|
-
version_requirements: *id001
|
36
|
-
- !ruby/object:Gem::Dependency
|
37
|
-
name: mysql2
|
38
22
|
prerelease: false
|
39
|
-
|
40
|
-
|
41
|
-
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ~>
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 3.2.12
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: mysql2
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
42
32
|
- - ~>
|
43
|
-
- !ruby/object:Gem::Version
|
44
|
-
hash: 5
|
45
|
-
segments:
|
46
|
-
- 0
|
47
|
-
- 3
|
48
|
-
- 11
|
33
|
+
- !ruby/object:Gem::Version
|
49
34
|
version: 0.3.11
|
50
35
|
type: :development
|
51
|
-
version_requirements: *id002
|
52
|
-
- !ruby/object:Gem::Dependency
|
53
|
-
name: rspec
|
54
36
|
prerelease: false
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ~>
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: 0.3.11
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rspec
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
64
49
|
type: :development
|
65
|
-
|
66
|
-
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
67
57
|
name: rake
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
type: :development
|
68
64
|
prerelease: false
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: rails
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
78
77
|
type: :development
|
79
|
-
|
80
|
-
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
description: Connection proxy for ActiveRecord for master / replica setups.
|
81
85
|
email: dan@kickstarter.com
|
82
86
|
executables: []
|
83
|
-
|
84
87
|
extensions: []
|
85
|
-
|
86
|
-
|
87
|
-
- LICENSE
|
88
|
-
- README.rdoc
|
89
|
-
files:
|
90
|
-
- lib/slave_pools.rb
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
91
90
|
- lib/slave_pools/active_record_extensions.rb
|
91
|
+
- lib/slave_pools/config.rb
|
92
92
|
- lib/slave_pools/connection_proxy.rb
|
93
|
+
- lib/slave_pools/engine.rb
|
94
|
+
- lib/slave_pools/hijack.rb
|
93
95
|
- lib/slave_pools/observer_extensions.rb
|
94
|
-
- lib/slave_pools/
|
95
|
-
- lib/slave_pools/
|
96
|
+
- lib/slave_pools/pool.rb
|
97
|
+
- lib/slave_pools/pools.rb
|
98
|
+
- lib/slave_pools/query_cache.rb
|
99
|
+
- lib/slave_pools/version.rb
|
100
|
+
- lib/slave_pools.rb
|
96
101
|
- LICENSE
|
97
|
-
- README.
|
98
|
-
- spec/config/
|
102
|
+
- README.md
|
103
|
+
- spec/config/test_model.rb
|
99
104
|
- spec/connection_proxy_spec.rb
|
100
|
-
- spec/
|
105
|
+
- spec/observer_extensions_spec.rb
|
106
|
+
- spec/pool_spec.rb
|
107
|
+
- spec/query_cache_spec.rb
|
101
108
|
- spec/slave_pools_spec.rb
|
102
109
|
- spec/spec_helper.rb
|
103
|
-
- slave_pools.gemspec
|
104
110
|
homepage: https://github.com/kickstarter/slave_pools
|
105
|
-
licenses:
|
111
|
+
licenses:
|
106
112
|
- MIT
|
113
|
+
metadata: {}
|
107
114
|
post_install_message:
|
108
|
-
rdoc_options:
|
109
|
-
|
110
|
-
- --inline-source
|
111
|
-
- --title
|
112
|
-
- slave_pools
|
113
|
-
- --main
|
114
|
-
- README.rdoc
|
115
|
-
require_paths:
|
115
|
+
rdoc_options: []
|
116
|
+
require_paths:
|
116
117
|
- lib
|
117
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
none: false
|
128
|
-
requirements:
|
129
|
-
- - ">="
|
130
|
-
- !ruby/object:Gem::Version
|
131
|
-
hash: 11
|
132
|
-
segments:
|
133
|
-
- 1
|
134
|
-
- 2
|
135
|
-
version: "1.2"
|
118
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - '>='
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - '>='
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '1.2'
|
136
128
|
requirements: []
|
137
|
-
|
138
129
|
rubyforge_project:
|
139
|
-
rubygems_version: 1.
|
130
|
+
rubygems_version: 2.1.11
|
140
131
|
signing_key:
|
141
|
-
specification_version:
|
142
|
-
summary: Connection proxy for ActiveRecord for
|
143
|
-
test_files:
|
144
|
-
|
132
|
+
specification_version: 4
|
133
|
+
summary: Connection proxy for ActiveRecord for master / replica setups.
|
134
|
+
test_files:
|
135
|
+
- spec/config/test_model.rb
|
136
|
+
- spec/connection_proxy_spec.rb
|
137
|
+
- spec/observer_extensions_spec.rb
|
138
|
+
- spec/pool_spec.rb
|
139
|
+
- spec/query_cache_spec.rb
|
140
|
+
- spec/slave_pools_spec.rb
|
141
|
+
- spec/spec_helper.rb
|
data/README.rdoc
DELETED
@@ -1,259 +0,0 @@
|
|
1
|
-
= SlavePools
|
2
|
-
|
3
|
-
== Easy Single Master/ Multiple Slave Setup for use in Ruby/Rails projects
|
4
|
-
|
5
|
-
SlavePools builds a base layer of master/slave query splitting, by overwriting ActiveRecord's connection (with connection_proxy). With this in place, you can easily add a second layer of traffic splitting, by wrapping requests in the provided helper methods (examples below), and have a manageable master/slave solution for a standard rails application
|
6
|
-
|
7
|
-
Overview
|
8
|
-
* Sends only whitelisted SELECT-type queries to the Slaves
|
9
|
-
* Sends all other queries to the Master
|
10
|
-
* Works with query caching and transactions
|
11
|
-
* Easy to separate types of read traffic into different collections of slaves (e.g. separating admin and user traffic)
|
12
|
-
* Minimalist approach
|
13
|
-
* doesn't include sharding
|
14
|
-
* doesn't create a new ActiveRecord adapter
|
15
|
-
* doesn't weight slave db's
|
16
|
-
* Builds onto a standard database.yml file (gem doesn't initialize if no slaves are specified)
|
17
|
-
* doesn't switch slaves on its own (the user specifies when to switch in their code)
|
18
|
-
|
19
|
-
The SlavePools GEM started as a fork of Maximilian Sch\303\266fmann's https://github.com/schoefmax/multi_db
|
20
|
-
The MultiDB gem was inspired by Rick Olson's "masochism"-Plugin
|
21
|
-
|
22
|
-
== Usage
|
23
|
-
|
24
|
-
Toggle to next slave:
|
25
|
-
SlavePools.next_slave!
|
26
|
-
|
27
|
-
Specify a different slave pool than the default:
|
28
|
-
SlavePools.with_pool('other_pool') { #do stuff }
|
29
|
-
|
30
|
-
Specifically use the master for a call:
|
31
|
-
SlavePools.with_master { #do stuff }
|
32
|
-
|
33
|
-
Determine if there are slaves:
|
34
|
-
SlavePools.active?
|
35
|
-
|
36
|
-
The gem, by default, sends writes and reads to the master and slave databases, respectfully. But in your app, if you write to the master during a request, you will probably want to read from the master in that request as well, in case there is replication. You will also probably want to read from the master on the next request (after a write to the master) to cover redirects.
|
37
|
-
|
38
|
-
Using a standard rails application setup, you can achieve this by adding these example methods to your application controller (some of these may be folded in the gem, but leaving out for now):
|
39
|
-
|
40
|
-
class ApplicationController < ActionController::Base
|
41
|
-
|
42
|
-
around_filter :stick_to_master_for_updates
|
43
|
-
around_filter :use_master_for_redirect #goes with above
|
44
|
-
after_filter :switch_to_next_slave
|
45
|
-
|
46
|
-
def switch_to_next_slave
|
47
|
-
SlavePools.next_slave! if slaves?
|
48
|
-
end
|
49
|
-
|
50
|
-
def use_admin_slave_pool
|
51
|
-
SlavePools.with_pool('admin') { yield } if slaves?
|
52
|
-
end
|
53
|
-
|
54
|
-
def stick_to_master_for_updates
|
55
|
-
if slaves? && (request.post? || request.put? || request.delete?)
|
56
|
-
SlavePools.with_master { yield }
|
57
|
-
session[:stick_to_master] = 1
|
58
|
-
else
|
59
|
-
yield
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def use_master_for_redirect
|
64
|
-
if slaves? && session[:stick_to_master]
|
65
|
-
session[:stick_to_master] = nil
|
66
|
-
SlavePools.with_master { yield }
|
67
|
-
else
|
68
|
-
yield
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def use_master
|
73
|
-
if slaves?
|
74
|
-
SlavePools.with_master { yield }
|
75
|
-
session[:stick_to_master] = 1
|
76
|
-
else
|
77
|
-
yield
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def slaves?
|
82
|
-
SlavePools.active?
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
For other cases where you use the master for writes, you should wrap the request in a 'use_master' block
|
87
|
-
|
88
|
-
class PostsController < ApplicationController
|
89
|
-
around_filter :use_master, :only=>:index
|
90
|
-
|
91
|
-
def index
|
92
|
-
Activity.create()
|
93
|
-
# index is a GET call, but we've decided to record something, so we want to wrap it in a use_master block
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
* works with activerecord 3.2.12 (not tested with Rails 2)
|
98
|
-
|
99
|
-
=== Install
|
100
|
-
|
101
|
-
Add to your Gemfile
|
102
|
-
|
103
|
-
gem 'slave_pools'
|
104
|
-
|
105
|
-
=== Setup
|
106
|
-
|
107
|
-
slave_pools identifies slave databases by looking for entries of the form
|
108
|
-
"<tt><environment>_pool_<pool_name>_name_<db_name></tt>".
|
109
|
-
|
110
|
-
In your database.yml, add sections for the slaves, e.g.:
|
111
|
-
|
112
|
-
development: # that would be the master
|
113
|
-
adapter: mysql
|
114
|
-
database: myapp_production
|
115
|
-
username: root
|
116
|
-
password:
|
117
|
-
host: localhost
|
118
|
-
|
119
|
-
development_pool_default_name_slave1: # that would be a slave named 'slave1' in the 'default' pool
|
120
|
-
adapter: mysql
|
121
|
-
database: slave_db1
|
122
|
-
username: root
|
123
|
-
password:
|
124
|
-
host: 10.0.0.2
|
125
|
-
|
126
|
-
development_pool_default_name_slave2: # that would be a slave named 'slave2' in the 'default' pool
|
127
|
-
...
|
128
|
-
development_pool_admin_name_slave1: # that would be a slave named 'slave1' in the 'admin' pool (db names can be reused across pools)
|
129
|
-
...
|
130
|
-
development_pool_admin_name_another_slave: # that would be a slave named 'another_slave' in the 'admin' pool
|
131
|
-
|
132
|
-
This also creates an abstract classes named <tt>SlavePools::DefaultDb1</tt> for each db of the form <tt>SlavePools::<PoolName><DbName></tt>etc. If no slaves are specified, the SlavePools setup does not run, and the development DB would be used as normal.
|
133
|
-
|
134
|
-
For development testing, I recommend creating a read-only mysql user and just point all of your slave DB's to the your development DB using the read-only user.
|
135
|
-
|
136
|
-
The Default SlavePool will be used for all requests, so you should name on of the pools 'default' (if there isn't a 'default' slave_pool, the first slave_pool specified becomes the default)
|
137
|
-
|
138
|
-
To enable the proxy globally, add this to a config/initializers:
|
139
|
-
|
140
|
-
SlavePools.setup!
|
141
|
-
|
142
|
-
If you only want to enable it for specific environments, add this to
|
143
|
-
the corresponding file in config/environments:
|
144
|
-
|
145
|
-
config.after_initialize do
|
146
|
-
SlavePools.setup!
|
147
|
-
end
|
148
|
-
|
149
|
-
|
150
|
-
=== Using with Phusion Passenger
|
151
|
-
|
152
|
-
(this is a note from MultiDB gem and has not been verified)
|
153
|
-
|
154
|
-
With Passengers smart spawning method, child processes forked by the ApplicationSpawner
|
155
|
-
won't have the connection proxy set up properly (this is a note from ).
|
156
|
-
|
157
|
-
To make it work, add this to your <tt>environment.rb</tt> or an initializer script
|
158
|
-
(e.g. <tt>config/initializers/connection_proxy.rb</tt>):
|
159
|
-
|
160
|
-
if defined?(PhusionPassenger)
|
161
|
-
PhusionPassenger.on_event(:starting_worker_process) do |forked|
|
162
|
-
if forked
|
163
|
-
# ... set configuration options, if any ...
|
164
|
-
SlavePools::ConnectionProxy.setup!
|
165
|
-
end
|
166
|
-
end
|
167
|
-
else # not using passenger (e.g. development/testing)
|
168
|
-
# ... set configuration options, if any ...
|
169
|
-
SlavePools::ConnectionProxy.setup!
|
170
|
-
end
|
171
|
-
|
172
|
-
=== Using with ThinkingSphinx
|
173
|
-
|
174
|
-
ThinkingSphinx looks for an adapter type and
|
175
|
-
SlavePools::ConnectionProxy.setup!
|
176
|
-
|
177
|
-
if ActiveRecord::Base.respond_to?('connection_proxy')
|
178
|
-
ThinkingSphinx::AbstractAdapter.class_eval do
|
179
|
-
def self.standard_adapter_for_model(model)
|
180
|
-
:mysql
|
181
|
-
end
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
=== Forcing the master for certain actions
|
186
|
-
|
187
|
-
Just add this to your controller:
|
188
|
-
|
189
|
-
around_filter(:only => :foo_action) { |c,a| ActiveRecord::Base.connection_proxy.with_master { a.call } }
|
190
|
-
|
191
|
-
=== Forcing the master for certain models
|
192
|
-
|
193
|
-
In your environment.rb or an initializer, add this *before* the call to <tt>setup!</tt>:
|
194
|
-
|
195
|
-
SlavePoolsModule::ConnectionProxy.master_models = ['CGI::Session::ActiveRecordStore::Session', 'PaymentTransaction', ...]
|
196
|
-
SlavePoolsModule::ConnectionProxy.setup!
|
197
|
-
|
198
|
-
*NOTE*: You cannot safely add more master_models after calling <tt>setup!</tt>.
|
199
|
-
=== Features
|
200
|
-
* Minimalist implementation - does include sharding, doesn't creation a new adapter (so if you don't specify slaves for
|
201
|
-
an environment, the connection is not overwritten, and the DB works as normal), doesn't blacklist/remove slaves,
|
202
|
-
* It sends everything except "select ..." queries to the master, instead of
|
203
|
-
sending only specific things to the master and anything "else" to the slave.
|
204
|
-
This avoids accidental writes to the master when there are API changes in
|
205
|
-
ActiveRecord which haven't been picked up by multi_db yet.
|
206
|
-
Note that this behavior will also always send helper methods like "+quote+" or
|
207
|
-
"<tt>add_limit!</tt>" to the master connection object, which doesn't add any
|
208
|
-
more load on the master, as these methods don't communicate with the db server
|
209
|
-
itself.
|
210
|
-
|
211
|
-
|
212
|
-
=== Differences to "multi_db":
|
213
|
-
|
214
|
-
* Supports multiple separate pools of slave databases
|
215
|
-
* query caching is fixed
|
216
|
-
* tries a slave once and immediately reverts to the master afterwards (does not cycle through slaves)
|
217
|
-
* stays with the same slave DB until explicitly told to change. In practical usage, it didn't make sense to us
|
218
|
-
to have it cycle through slaves in the same web request, so I made the 'sticky slave' feature permanent
|
219
|
-
* removed weighted slave rotation for now (didn't need it)
|
220
|
-
* Currently not using Threaded variables (left this commented out in the code for now, may revisit)
|
221
|
-
* Added with_pool method
|
222
|
-
* does not blacklist slaves for timing out (we want other more robust monitoring software to take care of this)
|
223
|
-
* better default case handling - if no slave DB's are specified, the regular Environment database is used, and the gem is
|
224
|
-
not initialized
|
225
|
-
* added a wrapper class for shorter calls
|
226
|
-
|
227
|
-
=== See also
|
228
|
-
|
229
|
-
===== Masochism
|
230
|
-
|
231
|
-
The original master/slave plugin:
|
232
|
-
|
233
|
-
* http://github.com/technoweenie/masochism
|
234
|
-
|
235
|
-
===== MultiDb
|
236
|
-
|
237
|
-
The project is based on:
|
238
|
-
|
239
|
-
* https://github.com/schoefmax/multi_db
|
240
|
-
|
241
|
-
=== Running specs
|
242
|
-
|
243
|
-
If you haven't already, install the rspec gem, then set up your database
|
244
|
-
with a test database and a read_only user.
|
245
|
-
|
246
|
-
To match spec/config/database.yml, you can:
|
247
|
-
|
248
|
-
mysql>
|
249
|
-
create database test_db;
|
250
|
-
create user 'read_only'@'localhost' identified by 'readme';
|
251
|
-
grant select on db_test.* to 'read_only'@'localhost';
|
252
|
-
|
253
|
-
From the plugin directory, run:
|
254
|
-
|
255
|
-
rspec spec
|
256
|
-
|
257
|
-
Author: Dan Drabik
|
258
|
-
Copyright (c) 2012, Kickstarter
|
259
|
-
Released under the MIT license
|
@@ -1,45 +0,0 @@
|
|
1
|
-
module SlavePoolsModule
|
2
|
-
# Implements the methods expected by the QueryCache module
|
3
|
-
module QueryCacheCompat
|
4
|
-
|
5
|
-
def select_all(*a, &b)
|
6
|
-
arel, name, binds = a
|
7
|
-
if query_cache_enabled && !locked?(arel)
|
8
|
-
# FIXME this still hits the +select_all+ method in AR connection's
|
9
|
-
# query_cache.rb. It'd be nice if we could avoid it somehow so
|
10
|
-
# +select_all+ and then +to_sql+ aren't called redundantly.
|
11
|
-
sql = to_sql(arel, binds)
|
12
|
-
@master.connection.send(:cache_sql, sql, binds) {send_to_current(:select_all, *[sql, name, binds], &b)}
|
13
|
-
else
|
14
|
-
send_to_current(:select_all, *a, &b)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def insert(*a, &b)
|
19
|
-
@master.connection.clear_query_cache if query_cache_enabled
|
20
|
-
send_to_master(:insert, *a, &b)
|
21
|
-
end
|
22
|
-
|
23
|
-
def update(*a, &b)
|
24
|
-
@master.connection.clear_query_cache if query_cache_enabled
|
25
|
-
send_to_master(:update, *a, &b)
|
26
|
-
end
|
27
|
-
|
28
|
-
def delete(*a, &b)
|
29
|
-
@master.connection.clear_query_cache if query_cache_enabled
|
30
|
-
send_to_master(:delete, *a, &b)
|
31
|
-
end
|
32
|
-
|
33
|
-
# Rails 3.2 changed query cacheing a little and affected slave_pools like this:
|
34
|
-
#
|
35
|
-
# * ActiveRecord::Base.cache sets @query_cache_enabled for current connection
|
36
|
-
# * ActiveRecord::QueryCache middleware (in call()) that Rails uses sets
|
37
|
-
# @query_cache_enabled directly on ActiveRecord::Base.connection
|
38
|
-
# (which could be master at that point)
|
39
|
-
#
|
40
|
-
# :`( So, let's just use the master connection for all query cacheing.
|
41
|
-
def query_cache_enabled
|
42
|
-
@master.connection.query_cache_enabled
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|