seamless_database_pool 1.0.5 → 1.0.6
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/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
|