seamless_database_pool 1.0.5 → 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +52 -4
- data/lib/active_record/connection_adapters/seamless_database_pool_adapter.rb +116 -125
- data/lib/seamless_database_pool/arel_compiler.rb +27 -0
- data/lib/seamless_database_pool.rb +80 -77
- data/spec/connection_adapters_spec.rb +212 -0
- data/spec/database.yml +35 -0
- data/spec/seamless_database_pool_adapter_spec.rb +236 -258
- data/spec/spec_helper.rb +4 -3
- data/spec/test_adapter/active_record/connection_adapters/read_only_adapter.rb +51 -0
- data/spec/test_model.rb +46 -0
- metadata +53 -23
- data/MIT-LICENSE +0 -20
- data/VERSION +0 -1
- data/init.rb +0 -2
- data/seamless_database_pool.gemspec +0 -69
- data/spec/test_models.rb +0 -35
data/Rakefile
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'rake'
|
3
3
|
require 'rake/rdoctask'
|
4
|
+
require 'yaml'
|
4
5
|
|
5
6
|
desc 'Default: run unit tests.'
|
6
7
|
task :default => :test
|
@@ -11,17 +12,61 @@ begin
|
|
11
12
|
Spec::Rake::SpecTask.new(:test) do |t|
|
12
13
|
t.spec_files = FileList.new('spec/**/*_spec.rb')
|
13
14
|
end
|
15
|
+
|
16
|
+
namespace :test do
|
17
|
+
desc "Run all tests including for all database adapters"
|
18
|
+
task :all do
|
19
|
+
save_val = ENV['TEST_ADAPTERS']
|
20
|
+
begin
|
21
|
+
ENV['TEST_ADAPTERS'] = YAML.load_file(File.expand_path("../spec/database.yml", __FILE__)).keys.join(' ')
|
22
|
+
Rake::Task["test"].execute
|
23
|
+
ensure
|
24
|
+
ENV['TEST_ADAPTERS'] = save_val
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
desc "Test all database adapters defined in database.yml or just the one specified in TEST_ADAPTERS"
|
29
|
+
task :adapters do
|
30
|
+
save_val = ENV['TEST_ADAPTERS']
|
31
|
+
begin
|
32
|
+
ENV['TEST_ADAPTERS'] ||= YAML.load_file(File.expand_path("../spec/database.yml", __FILE__)).keys.join(' ')
|
33
|
+
Rake::Task["test:adapters:specified"].execute
|
34
|
+
ensure
|
35
|
+
ENV['TEST_ADAPTERS'] = save_val
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
namespace :adapters do
|
40
|
+
desc "Internal task to run database adapter tests"
|
41
|
+
Spec::Rake::SpecTask.new(:specified) do |t|
|
42
|
+
t.spec_files = FileList.new('spec/connection_adapters_spec.rb')
|
43
|
+
end
|
44
|
+
|
45
|
+
YAML.load_file(File.expand_path("../spec/database.yml", __FILE__)).keys.each do |adapter_name|
|
46
|
+
desc "Test the #{adapter_name} database adapter"
|
47
|
+
task adapter_name do
|
48
|
+
save_val = ENV['TEST_ADAPTERS']
|
49
|
+
begin
|
50
|
+
ENV['TEST_ADAPTERS'] = adapter_name
|
51
|
+
Rake::Task["test:adapters:specified"].execute
|
52
|
+
ensure
|
53
|
+
ENV['TEST_ADAPTERS'] = save_val
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
14
59
|
rescue LoadError
|
15
|
-
|
16
|
-
STDERR.puts "You must have rspec >= 1.
|
60
|
+
task :test do
|
61
|
+
STDERR.puts "You must have rspec >= 1.3.0 to run the tests"
|
17
62
|
end
|
18
63
|
end
|
19
64
|
|
20
65
|
desc 'Generate documentation for seamless_database_pool.'
|
21
66
|
Rake::RDocTask.new(:rdoc) do |rdoc|
|
22
67
|
rdoc.rdoc_dir = 'rdoc'
|
23
|
-
rdoc.options << '--title' << 'Seamless Database Pool' << '--line-numbers' << '--inline-source' << '--main' << 'README'
|
24
|
-
rdoc.rdoc_files.include('README')
|
68
|
+
rdoc.options << '--title' << 'Seamless Database Pool' << '--line-numbers' << '--inline-source' << '--main' << 'README.rdoc'
|
69
|
+
rdoc.rdoc_files.include('README.rdoc')
|
25
70
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
26
71
|
end
|
27
72
|
|
@@ -33,6 +78,9 @@ begin
|
|
33
78
|
gem.email = "brian@embellishedvisions.com"
|
34
79
|
gem.homepage = "http://github.com/bdurand/seamless_database_pool"
|
35
80
|
gem.authors = ["Brian Durand"]
|
81
|
+
gem.files = FileList["lib/**/*", "spec/**/*", "README.rdoc", "Rakefile"].to_a
|
82
|
+
gem.has_rdoc = true
|
83
|
+
gem.extra_rdoc_files = ["README.rdoc"]
|
36
84
|
|
37
85
|
gem.add_dependency('activerecord', '>= 2.2.2')
|
38
86
|
gem.add_development_dependency('rspec', '>= 1.2.9')
|
@@ -1,73 +1,83 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
class Base
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
3
|
+
class << self
|
4
|
+
def seamless_database_pool_connection (config)
|
5
|
+
pool_weights = {}
|
6
|
+
|
7
|
+
config = config.with_indifferent_access
|
8
|
+
default_config = {:pool_weight => 1}.merge(config.merge(:adapter => config[:pool_adapter])).with_indifferent_access
|
9
|
+
default_config.delete(:master)
|
10
|
+
default_config.delete(:read_pool)
|
11
|
+
default_config.delete(:pool_adapter)
|
12
|
+
|
13
|
+
master_config = default_config.merge(config[:master]).with_indifferent_access
|
14
|
+
establish_adapter(master_config[:adapter])
|
15
|
+
master_connection = send("#{master_config[:adapter]}_connection".to_sym, master_config)
|
16
|
+
master_connection.class.send(:include, SeamlessDatabasePool::ConnectTimeout) unless master_connection.class.include?(SeamlessDatabasePool::ConnectTimeout)
|
17
|
+
master_connection.connect_timeout = master_config[:connect_timeout]
|
18
|
+
pool_weights[master_connection] = master_config[:pool_weight].to_i if master_config[:pool_weight].to_i > 0
|
19
|
+
|
20
|
+
read_connections = []
|
21
|
+
config[:read_pool].each do |read_config|
|
22
|
+
read_config = default_config.merge(read_config).with_indifferent_access
|
23
|
+
read_config[:pool_weight] = read_config[:pool_weight].to_i
|
24
|
+
if read_config[:pool_weight] > 0
|
25
|
+
establish_adapter(read_config[:adapter])
|
26
|
+
conn = send("#{read_config[:adapter]}_connection".to_sym, read_config)
|
27
|
+
conn.class.send(:include, SeamlessDatabasePool::ConnectTimeout) unless conn.class.include?(SeamlessDatabasePool::ConnectTimeout)
|
28
|
+
conn.connect_timeout = read_config[:connect_timeout]
|
29
|
+
read_connections << conn
|
30
|
+
pool_weights[conn] = read_config[:pool_weight]
|
31
|
+
end
|
32
|
+
end if config[:read_pool]
|
33
|
+
|
34
|
+
@seamless_database_pool_classes ||= {}
|
35
|
+
klass = @seamless_database_pool_classes[master_connection.class]
|
36
|
+
unless klass
|
37
|
+
klass = ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter.adapter_class(master_connection)
|
38
|
+
@seamless_database_pool_classes[master_connection.class] = klass
|
29
39
|
end
|
30
|
-
end if config[:read_pool]
|
31
40
|
|
32
|
-
|
33
|
-
klass = @seamless_database_pool_classes[master_connection.class]
|
34
|
-
unless klass
|
35
|
-
klass = ActiveRecord::ConnectionAdapters::SeamlessDatabasePoolAdapter.adapter_class(master_connection)
|
36
|
-
@seamless_database_pool_classes[master_connection.class] = klass
|
41
|
+
return klass.new(nil, logger, master_connection, read_connections, pool_weights)
|
37
42
|
end
|
38
|
-
|
39
|
-
return klass.new(nil, logger, master_connection, read_connections, pool_weights)
|
40
|
-
end
|
41
43
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
begin
|
47
|
-
require 'rubygems'
|
48
|
-
gem "activerecord-#{adapter}-adapter"
|
49
|
-
require "active_record/connection_adapters/#{adapter}_adapter"
|
50
|
-
rescue LoadError
|
44
|
+
def establish_adapter (adapter)
|
45
|
+
raise AdapterNotSpecified.new("database configuration does not specify adapter") unless adapter
|
46
|
+
raise AdapterNotFound.new("database pool must specify adapters") if adapter == 'seamless_database_pool'
|
47
|
+
|
51
48
|
begin
|
49
|
+
require 'rubygems'
|
50
|
+
gem "activerecord-#{adapter}-adapter"
|
52
51
|
require "active_record/connection_adapters/#{adapter}_adapter"
|
53
52
|
rescue LoadError
|
54
|
-
|
53
|
+
begin
|
54
|
+
require "active_record/connection_adapters/#{adapter}_adapter"
|
55
|
+
rescue LoadError
|
56
|
+
raise "Please install the #{adapter} adapter: `gem install activerecord-#{adapter}-adapter` (#{$!})"
|
57
|
+
end
|
55
58
|
end
|
56
|
-
end
|
57
59
|
|
58
|
-
|
59
|
-
|
60
|
-
|
60
|
+
adapter_method = "#{adapter}_connection"
|
61
|
+
if !respond_to?(adapter_method)
|
62
|
+
raise AdapterNotFound, "database configuration specifies nonexistent #{adapter} adapter"
|
63
|
+
end
|
61
64
|
end
|
62
65
|
end
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
66
|
+
|
67
|
+
module SeamlessDatabasePoolBehavior
|
68
|
+
def self.included (base)
|
69
|
+
base.alias_method_chain(:reload, :seamless_database_pool)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Force reload to use the master connection since it's probably being called for a reason.
|
73
|
+
def reload_with_seamless_database_pool (*args)
|
74
|
+
SeamlessDatabasePool.use_master_connection do
|
75
|
+
reload_without_seamless_database_pool(*args)
|
76
|
+
end
|
68
77
|
end
|
69
78
|
end
|
70
|
-
|
79
|
+
|
80
|
+
include(SeamlessDatabasePoolBehavior) unless include?(SeamlessDatabasePoolBehavior)
|
71
81
|
end
|
72
82
|
|
73
83
|
module ConnectionAdapters
|
@@ -78,25 +88,45 @@ module ActiveRecord
|
|
78
88
|
# Create an anonymous class that extends this one and proxies methods to the pool connections.
|
79
89
|
def self.adapter_class (master_connection)
|
80
90
|
# Define methods to proxy to the appropriate pool
|
81
|
-
read_only_methods = [:select_one, :select_all, :select_value, :select_values, :select_rows]
|
82
|
-
master_methods =
|
91
|
+
read_only_methods = [:select_one, :select_all, :select_value, :select_values, :select, :select_rows, :execute, :tables, :columns]
|
92
|
+
master_methods = []
|
93
|
+
master_connection_classes = [AbstractAdapter, Quoting, DatabaseStatements, SchemaStatements]
|
94
|
+
master_connection_classes << DatabaseLimits if const_defined?(:DatabaseLimits)
|
95
|
+
master_connection_class = master_connection.class
|
96
|
+
while ![Object, AbstractAdapter].include?(master_connection_class) do
|
97
|
+
master_connection_classes << master_connection_class
|
98
|
+
master_connection_class = master_connection_class.superclass
|
99
|
+
end
|
100
|
+
master_connection_classes.each do |connection_class|
|
101
|
+
master_methods.concat(connection_class.public_instance_methods(false))
|
102
|
+
master_methods.concat(connection_class.protected_instance_methods(false))
|
103
|
+
#master_methods.concat(connection_class.private_instance_methods(false))
|
104
|
+
end
|
105
|
+
master_methods.uniq!
|
83
106
|
master_methods -= public_instance_methods(false) + protected_instance_methods(false) + private_instance_methods(false)
|
84
107
|
master_methods = master_methods.collect{|m| m.to_sym}
|
85
108
|
master_methods -= read_only_methods
|
86
|
-
master_methods.delete(:transaction)
|
87
109
|
|
88
110
|
klass = Class.new(self)
|
89
111
|
master_methods.each do |method_name|
|
90
|
-
klass.class_eval
|
112
|
+
klass.class_eval %Q(
|
91
113
|
def #{method_name}(*args, &block)
|
92
|
-
|
93
|
-
proxy_connection_method(master_connection, :#{method_name}, :master, *args, &block)
|
94
|
-
rescue DatabaseConnectionError => e
|
95
|
-
raise e.wrapped_exception
|
114
|
+
use_master_connection do
|
115
|
+
return proxy_connection_method(master_connection, :#{method_name}, :master, *args, &block)
|
96
116
|
end
|
97
117
|
end
|
98
|
-
)
|
118
|
+
)
|
119
|
+
end
|
120
|
+
|
121
|
+
read_only_methods.each do |method_name|
|
122
|
+
klass.class_eval %Q(
|
123
|
+
def #{method_name}(*args, &block)
|
124
|
+
connection = @use_master ? master_connection : current_read_connection
|
125
|
+
proxy_connection_method(connection, :#{method_name}, :read, *args, &block)
|
126
|
+
end
|
127
|
+
)
|
99
128
|
end
|
129
|
+
klass.send :protected, :select
|
100
130
|
|
101
131
|
return klass
|
102
132
|
end
|
@@ -115,7 +145,7 @@ module ActiveRecord
|
|
115
145
|
end
|
116
146
|
|
117
147
|
def adapter_name #:nodoc:
|
118
|
-
'
|
148
|
+
'Seamless_Database_Pool'
|
119
149
|
end
|
120
150
|
|
121
151
|
# Returns an array of the master connection and the read pool connections
|
@@ -174,37 +204,6 @@ module ActiveRecord
|
|
174
204
|
return SeamlessDatabasePool.read_only_connection(self)
|
175
205
|
end
|
176
206
|
|
177
|
-
def transaction(start_db_transaction = true)
|
178
|
-
use_master_connection do
|
179
|
-
master_connection.transaction(start_db_transaction) do
|
180
|
-
yield if block_given?
|
181
|
-
end
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
# Returns the last auto-generated ID from the affected table.
|
186
|
-
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
187
|
-
master_connection.insert(sql, name, pk, id_value, sequence_name)
|
188
|
-
end
|
189
|
-
|
190
|
-
# Executes the update statement and returns the number of rows affected.
|
191
|
-
def update(sql, name = nil)
|
192
|
-
master_connection.update(sql, name)
|
193
|
-
end
|
194
|
-
|
195
|
-
# Executes the delete statement and returns the number of rows affected.
|
196
|
-
def delete(sql, name = nil)
|
197
|
-
master_connection.delete(sql, name)
|
198
|
-
end
|
199
|
-
|
200
|
-
def execute (*args)
|
201
|
-
proxy_connection_method(current_read_connection, :execute, :read, *args)
|
202
|
-
end
|
203
|
-
|
204
|
-
def select_rows(*args)
|
205
|
-
proxy_connection_method(current_read_connection, :select_rows, :read, *args)
|
206
|
-
end
|
207
|
-
|
208
207
|
def using_master_connection?
|
209
208
|
!!@use_master
|
210
209
|
end
|
@@ -221,7 +220,6 @@ module ActiveRecord
|
|
221
220
|
end
|
222
221
|
|
223
222
|
class DatabaseConnectionError < StandardError
|
224
|
-
attr_accessor :wrapped_exception
|
225
223
|
end
|
226
224
|
|
227
225
|
# This simple class puts an expire time on an array of connections. It is used so the a connection
|
@@ -237,7 +235,7 @@ module ActiveRecord
|
|
237
235
|
end
|
238
236
|
|
239
237
|
def expired?
|
240
|
-
@expires
|
238
|
+
@expires <= Time.now if @expires
|
241
239
|
end
|
242
240
|
|
243
241
|
def reconnect!
|
@@ -253,7 +251,7 @@ module ActiveRecord
|
|
253
251
|
available = @available_read_connections.last
|
254
252
|
if available.expired?
|
255
253
|
begin
|
256
|
-
available.reconnect
|
254
|
+
available.reconnect!# unless available.active?
|
257
255
|
rescue
|
258
256
|
# Couldn't reconnect so try again in a little bit
|
259
257
|
available.expires = 30.seconds.from_now
|
@@ -266,6 +264,15 @@ module ActiveRecord
|
|
266
264
|
end
|
267
265
|
end
|
268
266
|
|
267
|
+
def reset_available_read_connections
|
268
|
+
@available_read_connections.slice!(1, @available_read_connections.length)
|
269
|
+
@available_read_connections.first.connections.each do |connection|
|
270
|
+
unless connection.active?
|
271
|
+
connection.reconnect! rescue nil
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
269
276
|
# Temporarily remove a connection from the read pool.
|
270
277
|
def suppress_read_connection (conn, expire)
|
271
278
|
available = available_read_connections
|
@@ -276,47 +283,31 @@ module ActiveRecord
|
|
276
283
|
|
277
284
|
if connections.empty?
|
278
285
|
# No connections available so we might as well try them all again
|
279
|
-
|
286
|
+
reset_available_read_connections
|
280
287
|
else
|
281
288
|
# Available connections will now not include the suppressed connection for a while
|
282
289
|
@available_read_connections.push(AvailableConnections.new(connections, conn, expire.seconds.from_now))
|
283
290
|
end
|
284
291
|
end
|
285
292
|
|
286
|
-
|
293
|
+
private
|
287
294
|
|
288
|
-
def
|
289
|
-
connection = current_read_connection
|
295
|
+
def proxy_connection_method (connection, method, proxy_type, *args, &block)
|
290
296
|
begin
|
291
|
-
|
292
|
-
rescue
|
293
|
-
|
294
|
-
|
297
|
+
connection.send(method, *args, &block)
|
298
|
+
rescue => e
|
299
|
+
# If the statement was a read statement and it wasn't forced against the master connection
|
300
|
+
# try to reconnect if the connection is dead and then re-run the statement.
|
301
|
+
if proxy_type == :read and !using_master_connection?
|
295
302
|
unless connection.active?
|
296
303
|
suppress_read_connection(connection, 30)
|
297
304
|
connection = current_read_connection
|
298
305
|
SeamlessDatabasePool.set_persistent_read_connection(self, connection)
|
299
306
|
end
|
300
|
-
proxy_connection_method(connection,
|
307
|
+
proxy_connection_method(connection, method, :retry, *args, &block)
|
301
308
|
else
|
302
|
-
raise e
|
303
|
-
end
|
304
|
-
end
|
305
|
-
end
|
306
|
-
|
307
|
-
private
|
308
|
-
|
309
|
-
def proxy_connection_method (connection, method, proxy_type, *args, &block)
|
310
|
-
begin
|
311
|
-
connection.send(method, *args, &block)
|
312
|
-
rescue => e
|
313
|
-
unless proxy_type == :retry or connection.active?
|
314
|
-
connection.reconnect! rescue nil
|
315
|
-
connection_error = DatabaseConnectionError.new
|
316
|
-
connection_error.wrapped_exception = e
|
317
|
-
raise connection_error
|
309
|
+
raise e
|
318
310
|
end
|
319
|
-
raise e
|
320
311
|
end
|
321
312
|
end
|
322
313
|
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Arel
|
2
|
+
module SqlCompiler
|
3
|
+
# Hook into arel to use the compiler used by the master connection.
|
4
|
+
class Seamless_Database_PoolCompiler < GenericCompiler
|
5
|
+
def self.new (relation)
|
6
|
+
@compiler_classes ||= {}
|
7
|
+
master_adapter = relation.engine.connection.master_connection.adapter_name
|
8
|
+
compiler_class = @compiler_classes[master_adapter]
|
9
|
+
unless compiler_class
|
10
|
+
begin
|
11
|
+
require "arel/engines/sql/compilers/#{master_adapter.downcase}_compiler"
|
12
|
+
rescue LoadError
|
13
|
+
begin
|
14
|
+
# try to load an externally defined compiler, in case this adapter has defined the compiler on its own.
|
15
|
+
require "#{master_adapter.downcase}/arel_compiler"
|
16
|
+
rescue LoadError
|
17
|
+
raise "#{master_adapter} is not supported by Arel."
|
18
|
+
end
|
19
|
+
end
|
20
|
+
compiler_class = Arel::SqlCompiler.const_get("#{master_adapter}Compiler")
|
21
|
+
@compiler_classes[master_adapter] = compiler_class
|
22
|
+
end
|
23
|
+
compiler_class.new(relation)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -2,6 +2,7 @@ require File.join(File.dirname(__FILE__), 'seamless_database_pool', 'connect_tim
|
|
2
2
|
require File.join(File.dirname(__FILE__), 'seamless_database_pool', 'connection_statistics')
|
3
3
|
require File.join(File.dirname(__FILE__), 'seamless_database_pool', 'controller_filter')
|
4
4
|
require File.join(File.dirname(__FILE__), 'active_record', 'connection_adapters', 'seamless_database_pool_adapter')
|
5
|
+
$LOAD_PATH << File.dirname(__FILE__) unless $LOAD_PATH.include?(File.dirname(__FILE__))
|
5
6
|
|
6
7
|
# This module allows setting the read pool connection type. Generally you will use one of
|
7
8
|
#
|
@@ -17,97 +18,99 @@ module SeamlessDatabasePool
|
|
17
18
|
|
18
19
|
READ_CONNECTION_METHODS = [:master, :persistent, :random]
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
21
|
+
class << self
|
22
|
+
# Call this method to use a random connection from the read pool for every select statement.
|
23
|
+
# This method is good if your replication is very fast. Otherwise there is a chance you could
|
24
|
+
# get inconsistent results from one request to the next. This can result in mysterious failures
|
25
|
+
# if your code selects a value in one statement and then uses in another statement. You can wind
|
26
|
+
# up trying to use a value from one server that hasn't been replicated to another one yet.
|
27
|
+
# This method is best if you have few processes which generate a lot of queries and you have
|
28
|
+
# fast replication.
|
29
|
+
def use_random_read_connection
|
30
|
+
if block_given?
|
31
|
+
set_read_only_connection_type(:random){yield}
|
32
|
+
else
|
33
|
+
Thread.current[:read_only_connection] = :random
|
34
|
+
end
|
32
35
|
end
|
33
|
-
end
|
34
36
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
37
|
+
# Call this method to pick a random connection from the read pool and use it for all subsequent
|
38
|
+
# select statements. This provides consistency from one select statement to the next. This
|
39
|
+
# method should always be called with a block otherwise you can end up with an imbalanced read
|
40
|
+
# pool. This method is best if you have lots of processes which have a relatively few select
|
41
|
+
# statements or a slow replication mechanism. Generally this is the best method to use for web
|
42
|
+
# applications.
|
43
|
+
def use_persistent_read_connection
|
44
|
+
if block_given?
|
45
|
+
set_read_only_connection_type(:persistent){yield}
|
46
|
+
else
|
47
|
+
Thread.current[:read_only_connection] = {}
|
48
|
+
end
|
46
49
|
end
|
47
|
-
end
|
48
50
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
51
|
+
# Call this method to use the master connection for all subsequent select statements. This
|
52
|
+
# method is most useful when you are doing lots of updates since it guarantees consistency
|
53
|
+
# if you do a select immediately after an update or insert.
|
54
|
+
#
|
55
|
+
# The master connection will also be used for selects inside any transaction blocks. It will
|
56
|
+
# also be used if you pass :readonly => false to any ActiveRecord.find method.
|
57
|
+
def use_master_connection
|
58
|
+
if block_given?
|
59
|
+
set_read_only_connection_type(:master){yield}
|
60
|
+
else
|
61
|
+
Thread.current[:read_only_connection] = :master
|
62
|
+
end
|
60
63
|
end
|
61
|
-
end
|
62
64
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
65
|
+
# Set the read only connection type to either :master, :random, or :persistent.
|
66
|
+
def set_read_only_connection_type (connection_type)
|
67
|
+
saved_connection = Thread.current[:read_only_connection]
|
68
|
+
retval = nil
|
69
|
+
begin
|
70
|
+
connection_type = {} if connection_type == :persistent
|
71
|
+
Thread.current[:read_only_connection] = connection_type
|
72
|
+
retval = yield if block_given?
|
73
|
+
ensure
|
74
|
+
Thread.current[:read_only_connection] = saved_connection
|
75
|
+
end
|
76
|
+
return retval
|
73
77
|
end
|
74
|
-
return retval
|
75
|
-
end
|
76
78
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
79
|
+
# Get the read only connection type currently in use. Will be one of :master, :random, or :persistent.
|
80
|
+
def read_only_connection_type (default = :master)
|
81
|
+
connection_type = Thread.current[:read_only_connection] || default
|
82
|
+
connection_type = :persistent if connection_type.kind_of?(Hash)
|
83
|
+
return connection_type
|
84
|
+
end
|
83
85
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
86
|
+
# Get a read only connection from a connection pool.
|
87
|
+
def read_only_connection (pool_connection)
|
88
|
+
return pool_connection.master_connection if pool_connection.using_master_connection?
|
89
|
+
connection_type = Thread.current[:read_only_connection]
|
88
90
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
91
|
+
if connection_type.kind_of?(Hash)
|
92
|
+
connection = connection_type[pool_connection]
|
93
|
+
unless connection
|
94
|
+
connection = pool_connection.random_read_connection
|
95
|
+
connection_type[pool_connection] = connection
|
96
|
+
end
|
97
|
+
return connection
|
98
|
+
elsif connection_type == :random
|
99
|
+
return pool_connection.random_read_connection
|
100
|
+
else
|
101
|
+
return pool_connection.master_connection
|
94
102
|
end
|
95
|
-
return connection
|
96
|
-
elsif connection_type == :random
|
97
|
-
return pool_connection.random_read_connection
|
98
|
-
else
|
99
|
-
return pool_connection.master_connection
|
100
103
|
end
|
101
|
-
end
|
102
104
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
105
|
+
# This method is provided as a way to change the persistent connection when it fails and a new one is substituted.
|
106
|
+
def set_persistent_read_connection (pool_connection, read_connection)
|
107
|
+
connection_type = Thread.current[:read_only_connection]
|
108
|
+
connection_type[pool_connection] = read_connection if connection_type.kind_of?(Hash)
|
109
|
+
end
|
108
110
|
|
109
|
-
|
110
|
-
|
111
|
+
def clear_read_only_connection
|
112
|
+
Thread.current[:read_only_connection] = nil
|
113
|
+
end
|
111
114
|
end
|
112
115
|
|
113
116
|
end
|