schoefmax-multi_db 0.1.1 → 0.1.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.
- data/README.rdoc +86 -30
- data/lib/multi_db/active_record_extensions.rb +5 -1
- data/lib/multi_db/connection_proxy.rb +17 -22
- data/multi_db.gemspec +2 -2
- data/spec/connection_proxy_spec.rb +24 -3
- metadata +1 -6
data/README.rdoc
CHANGED
|
@@ -10,13 +10,14 @@ master database.
|
|
|
10
10
|
|
|
11
11
|
=== Caveats
|
|
12
12
|
|
|
13
|
-
* works with activerecord 2.1
|
|
13
|
+
* works with activerecord 2.1 and 2.2, but is not threadsafe (yet)
|
|
14
|
+
* when using Passenger or lightspeed you will probably need to introduce a before_filter which checks if the proxy is setup (see the discussion in the masochism readme: http://github.com/technoweenie/masochism/tree/master)
|
|
14
15
|
|
|
15
16
|
=== Install
|
|
16
17
|
|
|
17
18
|
gem install schoefmax-multi_db --source http://gems.github.com
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
When using Rails, add this to your environment.rb:
|
|
20
21
|
|
|
21
22
|
config.gem 'schoefmax-multi_db', :lib => 'multi_db', :source => 'http://gems.github.com'
|
|
22
23
|
|
|
@@ -38,13 +39,18 @@ In your database.yml, add sections for the slaves, e.g.:
|
|
|
38
39
|
password:
|
|
39
40
|
host: 10.0.0.2
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
production_slave_database_2: # another slave
|
|
42
43
|
...
|
|
43
|
-
|
|
44
|
+
production_slave_database_in_india: # yet another one
|
|
44
45
|
...
|
|
45
46
|
|
|
46
|
-
*NOTE*: multi_db identifies slave databases by looking for
|
|
47
|
-
|
|
47
|
+
*NOTE*: multi_db identifies slave databases by looking for entries of the form
|
|
48
|
+
"<tt><environment>_slave_database<_optional_name></tt>". As a (useless) side effect you
|
|
49
|
+
get abstract classes named <tt>MultiDb::SlaveDatabaseInIndia</tt> etc.
|
|
50
|
+
The advantage of specifying the slaves explicitly, instead of the master, is that
|
|
51
|
+
you can use the same configuration file for scripts that don't use multi_db.
|
|
52
|
+
Also, when you decide to disable multi_db for some reason, you don't have to
|
|
53
|
+
swap hosts in your <tt>database.yml</tt> from master to slave (which is easy to forget...).
|
|
48
54
|
|
|
49
55
|
To enable the proxy globally, add this to your environment.rb, or some file in
|
|
50
56
|
config/initializers:
|
|
@@ -58,38 +64,88 @@ the corresponding file in config/environments:
|
|
|
58
64
|
MultiDb::ConnectionProxy.setup!
|
|
59
65
|
end
|
|
60
66
|
|
|
61
|
-
In the development and test environments, you can use
|
|
62
|
-
for
|
|
67
|
+
In the development and test environments, you can use identical configurations
|
|
68
|
+
for master and slave connections. This can help you finding (some of the) issues
|
|
69
|
+
your application might have with a replicated database setup without actually having
|
|
70
|
+
one on your development machine.
|
|
71
|
+
|
|
72
|
+
=== Forcing the master for certain actions
|
|
73
|
+
|
|
74
|
+
Just add this to your controller:
|
|
75
|
+
|
|
76
|
+
around_filter(:only => :foo_action) { |c,a| MyModel.connection.with_master { a.call } }
|
|
77
|
+
|
|
78
|
+
=== Forcing the master for certain models
|
|
79
|
+
|
|
80
|
+
In your environment.rb or an initializer, add:
|
|
81
|
+
|
|
82
|
+
MultiDb::ConnectionProxy.master_models = ['PaymentTransaction', ...]
|
|
83
|
+
|
|
84
|
+
=== Usage outside of Rails
|
|
85
|
+
|
|
86
|
+
You can use multi_db together with other framworks or in standalone scripts.
|
|
87
|
+
Example:
|
|
88
|
+
|
|
89
|
+
require 'rubygems'
|
|
90
|
+
require 'active_record'
|
|
91
|
+
require 'multi_db'
|
|
92
|
+
|
|
93
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
|
94
|
+
ActiveRecord::Base.configurations = {
|
|
95
|
+
'development' => {
|
|
96
|
+
'adapter' => 'mysql',
|
|
97
|
+
'host' => 'localhost',
|
|
98
|
+
'username' => 'root',
|
|
99
|
+
'database' => 'multi_db_test'
|
|
100
|
+
},
|
|
101
|
+
'development_slave_database' => {
|
|
102
|
+
'adapter' => 'mysql',
|
|
103
|
+
'host' => 'localhost',
|
|
104
|
+
'username' => 'root',
|
|
105
|
+
'database' => 'multi_db_test'
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
ActiveRecord::Base.establish_connection :development
|
|
109
|
+
MultiDb::ConnectionProxy.setup!
|
|
110
|
+
|
|
111
|
+
class MyModel < ActiveRecord::Base
|
|
112
|
+
# ...
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# ...
|
|
116
|
+
|
|
117
|
+
Note that the configurations hash should contain strings as keys instead of symbols.
|
|
63
118
|
|
|
64
119
|
=== Differences to "masochism":
|
|
65
120
|
|
|
66
|
-
*
|
|
67
|
-
* It sends
|
|
68
|
-
sending only specific things to the master and anything "else" to the slave
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
121
|
+
* Supports multiple slave databases (round robin)
|
|
122
|
+
* It sends everything except "select ..." queries to the master, instead of
|
|
123
|
+
sending only specific things to the master and anything "else" to the slave.
|
|
124
|
+
This avoids accidential writes to the master when there are API changes in
|
|
125
|
+
ActiveRecord which haven't been picked up by multi_db yet.
|
|
126
|
+
Note that this behaviour will also always send helper methods like "+quote+" or
|
|
127
|
+
"<tt>add_limit!</tt>" to the master connection object, which doesn't add any
|
|
128
|
+
more load on the master, as these methods don't communicate with the db server
|
|
129
|
+
itself.
|
|
130
|
+
* It uses its own query cache as the slave's cache isn't emptied when there are
|
|
131
|
+
changes on the master
|
|
75
132
|
* It supports immediate failover for slave connections
|
|
76
133
|
* It will wait some time before trying to query a failed slave database again
|
|
77
|
-
* It supports
|
|
78
|
-
|
|
79
|
-
* It schedules a reconnect
|
|
80
|
-
with virtual
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
*
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
134
|
+
* It supports nesting "with_master"-blocks, without unexpectedly switching you
|
|
135
|
+
back to the slave again
|
|
136
|
+
* It schedules a reconnect of the master connection if statements fail there.
|
|
137
|
+
This might help with HA setups using virtual IPs (a test setup would be nice
|
|
138
|
+
to verify this)
|
|
139
|
+
* You specify slave databases in the configuration instead of specifying an extra
|
|
140
|
+
master database. This makes disabling or removing multi_db less dangerous.
|
|
141
|
+
* There are no <tt>set_to_master!</tt> and <tt>set_to_slave!</tt> methods, just
|
|
142
|
+
<tt>with_master(&block)</tt>
|
|
143
|
+
* All proxied methods are dynamically generated for better performance
|
|
88
144
|
|
|
89
145
|
=== Contributors
|
|
90
146
|
|
|
91
|
-
|
|
92
|
-
|
|
147
|
+
* Matt Conway http://github.com/wr0ngway
|
|
148
|
+
* Matthias Marshall http://github.com/webops
|
|
93
149
|
|
|
94
150
|
=== Running specs
|
|
95
151
|
|
|
@@ -13,33 +13,28 @@ module MultiDb
|
|
|
13
13
|
attr_accessor :current
|
|
14
14
|
|
|
15
15
|
class << self
|
|
16
|
-
|
|
17
|
-
# Example:
|
|
18
|
-
# MultiDb::ConnectionProxy.configuration = {
|
|
19
|
-
# :production_master_database => {
|
|
20
|
-
# :adapter =>
|
|
21
|
-
# }
|
|
22
|
-
# }
|
|
23
|
-
# defaults to the content of Rails.root/config/database.yml if not set and used with Rails
|
|
24
|
-
attr_accessor :configuration
|
|
16
|
+
|
|
25
17
|
# defaults to RAILS_ENV if multi_db is used with Rails
|
|
18
|
+
# defaults to 'development' when used outside Rails
|
|
26
19
|
attr_accessor :environment
|
|
27
20
|
|
|
21
|
+
# a list of models that should always go directly to the master
|
|
22
|
+
#
|
|
23
|
+
# Example:
|
|
24
|
+
#
|
|
25
|
+
# MultiDb::ConnectionProxy.master_models = %w[PaymentTransactions]
|
|
26
|
+
attr_accessor :master_models
|
|
27
|
+
|
|
28
|
+
# Replaces the connection of ActiveRecord::Base with a proxy and
|
|
29
|
+
# establishes the connections to the slaves.
|
|
28
30
|
def setup!
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
self.configuration = YAML.load(ERB.new(File.read(File.join(RAILS_ROOT, 'config', 'database.yml'))).result)
|
|
33
|
-
end
|
|
34
|
-
unless self.environment
|
|
35
|
-
raise "RAILS_ENV not set. Set MultiDb::ConnectionProxy.environment manually if you use multi_db outside rails" unless defined?(RAILS_ENV)
|
|
36
|
-
self.environment = RAILS_ENV
|
|
37
|
-
end
|
|
31
|
+
self.master_models ||= []
|
|
32
|
+
self.environment ||= (defined?(RAILS_ENV) ? RAILS_ENV : 'development')
|
|
33
|
+
|
|
38
34
|
master = ActiveRecord::Base
|
|
39
|
-
master.send :include, MultiDb::ActiveRecordExtensions
|
|
40
35
|
slaves = init_slaves
|
|
41
|
-
raise "No slaves defined
|
|
42
|
-
|
|
36
|
+
raise "No slaves databases defined for environment: #{self.environment}" if slaves.empty?
|
|
37
|
+
master.send :include, MultiDb::ActiveRecordExtensions
|
|
43
38
|
ActiveRecord::Observer.send :include, MultiDb::ObserverExtensions
|
|
44
39
|
master.connection_proxy = new(master, slaves)
|
|
45
40
|
master.logger.info("** multi_db with master and #{slaves.length} slave#{"s" if slaves.length > 1} loaded.")
|
|
@@ -54,7 +49,7 @@ module MultiDb
|
|
|
54
49
|
# These would be available later as MultiDb::SlaveDatabaseSomeserver
|
|
55
50
|
def init_slaves
|
|
56
51
|
returning([]) do |slaves|
|
|
57
|
-
|
|
52
|
+
ActiveRecord::Base.configurations.keys.each do |name|
|
|
58
53
|
if name.to_s =~ /#{self.environment}_(slave_database.*)/
|
|
59
54
|
MultiDb.module_eval %Q{
|
|
60
55
|
class #{$1.camelize} < ActiveRecord::Base
|
data/multi_db.gemspec
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Gem::Specification.new do |s|
|
|
4
4
|
s.name = %q{multi_db}
|
|
5
|
-
s.version = "0.1.
|
|
5
|
+
s.version = "0.1.2"
|
|
6
6
|
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
|
8
8
|
s.authors = ["Maximilian Sch\303\266fmann"]
|
|
9
9
|
s.date = %q{2009-01-29}
|
|
10
10
|
s.description = "Connection proxy for ActiveRecord for single master / multiple slave database deployments"
|
|
11
11
|
s.email = "max@pragmatic-it.de"
|
|
12
|
-
s.extra_rdoc_files = ["
|
|
12
|
+
s.extra_rdoc_files = ["LICENSE", "README.rdoc"]
|
|
13
13
|
s.files = ["lib/multi_db.rb", "lib/multi_db/active_record_extensions.rb", "lib/multi_db/connection_proxy.rb", "lib/multi_db/observer_extensions.rb", "lib/multi_db/query_cache_compat.rb", "lib/multi_db/scheduler.rb", "LICENSE", "README.rdoc", "spec/config/database.yml", "spec/connection_proxy_spec.rb", "spec/scheduler_spec.rb", "spec/spec_helper.rb", "multi_db.gemspec"]
|
|
14
14
|
s.has_rdoc = true
|
|
15
15
|
s.homepage = "http://github.com/schoefmax/multi_db"
|
|
@@ -8,12 +8,21 @@ require MULTI_DB_SPEC_DIR + '/../lib/multi_db/connection_proxy'
|
|
|
8
8
|
RAILS_ROOT = MULTI_DB_SPEC_DIR
|
|
9
9
|
|
|
10
10
|
describe MultiDb::ConnectionProxy do
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
before(:all) do
|
|
13
|
-
ActiveRecord::Base.
|
|
13
|
+
ActiveRecord::Base.configurations = MULTI_DB_SPEC_CONFIG
|
|
14
|
+
ActiveRecord::Base.establish_connection :test
|
|
15
|
+
class MasterModel < ActiveRecord::Base; end
|
|
16
|
+
@sql = 'SELECT 1 + 1 FROM DUAL'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
before(:each) do
|
|
14
20
|
MultiDb::ConnectionProxy.setup!
|
|
15
21
|
@proxy = ActiveRecord::Base.connection
|
|
16
|
-
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'AR::B#connection should return an instance of MultiDb::ConnectionProxy' do
|
|
25
|
+
ActiveRecord::Base.connection.should be_a(MultiDb::ConnectionProxy)
|
|
17
26
|
end
|
|
18
27
|
|
|
19
28
|
it "should generate classes for each entry in the database.yml" do
|
|
@@ -114,6 +123,18 @@ describe MultiDb::ConnectionProxy do
|
|
|
114
123
|
@proxy.master.retrieve_connection.should_receive(:insert).and_return(1)
|
|
115
124
|
@proxy.insert(@sql)
|
|
116
125
|
end
|
|
126
|
+
|
|
127
|
+
it 'should always use the master database for models configured as master models' do
|
|
128
|
+
MultiDb::SlaveDatabase2.retrieve_connection.should_receive(:select_all).once.and_return([])
|
|
129
|
+
MasterModel.connection.should == @proxy
|
|
130
|
+
MasterModel.first
|
|
131
|
+
|
|
132
|
+
MultiDb::ConnectionProxy.master_models = ['MasterModel']
|
|
133
|
+
MasterModel.connection.should == @proxy.master.retrieve_connection
|
|
134
|
+
MultiDb::SlaveDatabase1.retrieve_connection.should_not_receive(:select_all)
|
|
135
|
+
MasterModel.retrieve_connection.should_receive(:select_all).once.and_return([])
|
|
136
|
+
MasterModel.first
|
|
137
|
+
end
|
|
117
138
|
|
|
118
139
|
end
|
|
119
140
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: schoefmax-multi_db
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- "Maximilian Sch\xC3\xB6fmann"
|
|
@@ -28,11 +28,6 @@ executables: []
|
|
|
28
28
|
extensions: []
|
|
29
29
|
|
|
30
30
|
extra_rdoc_files:
|
|
31
|
-
- lib/multi_db/active_record_extensions.rb
|
|
32
|
-
- lib/multi_db/connection_proxy.rb
|
|
33
|
-
- lib/multi_db/observer_extensions.rb
|
|
34
|
-
- lib/multi_db/query_cache_compat.rb
|
|
35
|
-
- lib/multi_db/scheduler.rb
|
|
36
31
|
- LICENSE
|
|
37
32
|
- README.rdoc
|
|
38
33
|
files:
|