ar-octopus 0.8.5 → 0.10.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.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.travis.yml +6 -9
- data/Appraisals +8 -8
- data/README.mkdn +47 -15
- data/Rakefile +1 -0
- data/ar-octopus.gemspec +9 -12
- data/gemfiles/rails42.gemfile +2 -2
- data/gemfiles/{rails32.gemfile → rails5.gemfile} +2 -2
- data/gemfiles/{rails4.gemfile → rails51.gemfile} +2 -2
- data/gemfiles/{rails41.gemfile → rails52.gemfile} +2 -2
- data/lib/octopus/abstract_adapter.rb +4 -10
- data/lib/octopus/association.rb +1 -0
- data/lib/octopus/association_shard_tracking.rb +41 -71
- data/lib/octopus/collection_association.rb +9 -3
- data/lib/octopus/exception.rb +4 -0
- data/lib/octopus/finder_methods.rb +8 -0
- data/lib/octopus/load_balancing/round_robin.rb +2 -1
- data/lib/octopus/log_subscriber.rb +6 -2
- data/lib/octopus/migration.rb +123 -55
- data/lib/octopus/model.rb +42 -27
- data/lib/octopus/persistence.rb +33 -27
- data/lib/octopus/proxy.rb +147 -272
- data/lib/octopus/proxy_config.rb +251 -0
- data/lib/octopus/query_cache_for_shards.rb +24 -0
- data/lib/octopus/railtie.rb +0 -2
- data/lib/octopus/relation_proxy.rb +36 -1
- data/lib/octopus/result_patch.rb +19 -0
- data/lib/octopus/scope_proxy.rb +12 -5
- data/lib/octopus/shard_tracking.rb +8 -3
- data/lib/octopus/slave_group.rb +3 -3
- data/lib/octopus/version.rb +1 -1
- data/lib/octopus.rb +71 -18
- data/spec/config/shards.yml +12 -0
- data/spec/migrations/10_create_users_using_replication.rb +1 -1
- data/spec/migrations/11_add_field_in_all_slaves.rb +1 -1
- data/spec/migrations/12_create_users_using_block.rb +1 -1
- data/spec/migrations/13_create_users_using_block_and_using.rb +1 -1
- data/spec/migrations/14_create_users_on_shards_of_a_group_with_versions.rb +1 -1
- data/spec/migrations/15_create_user_on_shards_of_default_group_with_versions.rb +1 -1
- data/spec/migrations/1_create_users_on_master.rb +1 -1
- data/spec/migrations/2_create_users_on_canada.rb +1 -1
- data/spec/migrations/3_create_users_on_both_shards.rb +1 -1
- data/spec/migrations/4_create_users_on_shards_of_a_group.rb +1 -1
- data/spec/migrations/5_create_users_on_multiples_groups.rb +1 -1
- data/spec/migrations/6_raise_exception_with_invalid_shard_name.rb +1 -1
- data/spec/migrations/7_raise_exception_with_invalid_multiple_shard_names.rb +1 -1
- data/spec/migrations/8_raise_exception_with_invalid_group_name.rb +1 -1
- data/spec/migrations/9_raise_exception_with_multiple_invalid_group_names.rb +1 -1
- data/spec/octopus/association_shard_tracking_spec.rb +344 -16
- data/spec/octopus/load_balancing/round_robin_spec.rb +15 -0
- data/spec/octopus/log_subscriber_spec.rb +1 -1
- data/spec/octopus/migration_spec.rb +45 -11
- data/spec/octopus/model_spec.rb +204 -16
- data/spec/octopus/octopus_spec.rb +2 -2
- data/spec/octopus/proxy_spec.rb +44 -40
- data/spec/octopus/query_cache_for_shards_spec.rb +40 -0
- data/spec/octopus/relation_proxy_spec.rb +71 -23
- data/spec/octopus/replicated_slave_grouped_spec.rb +27 -0
- data/spec/octopus/replication_spec.rb +72 -2
- data/spec/octopus/scope_proxy_spec.rb +41 -7
- data/spec/spec_helper.rb +2 -0
- data/spec/support/database_connection.rb +1 -1
- data/spec/support/database_models.rb +1 -1
- data/spec/support/octopus_helper.rb +14 -6
- data/spec/tasks/octopus.rake_spec.rb +1 -1
- metadata +40 -30
- data/.ruby-version +0 -1
- data/init.rb +0 -1
- data/lib/octopus/has_and_belongs_to_many_association.rb +0 -9
- data/rails/init.rb +0 -1
data/lib/octopus/proxy.rb
CHANGED
@@ -4,196 +4,64 @@ require 'octopus/load_balancing/round_robin'
|
|
4
4
|
|
5
5
|
module Octopus
|
6
6
|
class Proxy
|
7
|
-
attr_accessor :
|
7
|
+
attr_accessor :proxy_config
|
8
|
+
|
9
|
+
delegate :current_model, :current_model=,
|
10
|
+
:current_shard, :current_shard=,
|
11
|
+
:current_group, :current_group=,
|
12
|
+
:current_slave_group, :current_slave_group=,
|
13
|
+
:current_load_balance_options, :current_load_balance_options=,
|
14
|
+
:block, :block=, :fully_replicated?, :has_group?,
|
15
|
+
:shard_names, :shards_for_group, :shards, :sharded, :slaves_list,
|
16
|
+
:shards_slave_groups, :slave_groups, :replicated, :slaves_load_balancer,
|
17
|
+
:config, :initialize_shards, :shard_name, to: :proxy_config, prefix: false
|
8
18
|
|
9
19
|
def initialize(config = Octopus.config)
|
10
|
-
|
11
|
-
initialize_replication(config) if !config.nil? && config['replicated']
|
20
|
+
self.proxy_config = Octopus::ProxyConfig.new(config)
|
12
21
|
end
|
13
22
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
23
|
+
# Rails Connection Methods - Those methods are overriden to add custom behavior that helps
|
24
|
+
# Octopus introduce Sharding / Replication.
|
25
|
+
delegate :adapter_name, :add_transaction_record, :case_sensitive_modifier,
|
26
|
+
:type_cast, :to_sql, :quote, :quote_column_name, :quote_table_name,
|
27
|
+
:quote_table_name_for_assignment, :supports_migrations?, :table_alias_for,
|
28
|
+
:table_exists?, :in_clause_length, :supports_ddl_transactions?,
|
29
|
+
:sanitize_limit, :prefetch_primary_key?, :current_database,
|
30
|
+
:combine_bind_parameters, :empty_insert_statement_value, :assume_migrated_upto_version,
|
31
|
+
:schema_cache, :substitute_at, :internal_string_options_for_primary_key, :lookup_cast_type_from_column,
|
32
|
+
:supports_advisory_locks?, :get_advisory_lock, :initialize_internal_metadata_table,
|
33
|
+
:release_advisory_lock, :prepare_binds_for_database, :cacheable_query, :column_name_for_operation,
|
34
|
+
:prepared_statements, :transaction_state, :create_table, to: :select_connection
|
21
35
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
@shards_config ||= []
|
28
|
-
|
29
|
-
@shards_config.each do |key, value|
|
30
|
-
if value.is_a?(String)
|
31
|
-
value = resolve_string_connection(value).merge(:octopus_shard => key)
|
32
|
-
initialize_adapter(value['adapter'])
|
33
|
-
@shards[key.to_sym] = connection_pool_for(value, "#{value['adapter']}_connection")
|
34
|
-
elsif value.is_a?(Hash) && value.key?('adapter')
|
35
|
-
value.merge!(:octopus_shard => key)
|
36
|
-
initialize_adapter(value['adapter'])
|
37
|
-
@shards[key.to_sym] = connection_pool_for(value, "#{value['adapter']}_connection")
|
38
|
-
|
39
|
-
slave_group_configs = value.select do |_k, v|
|
40
|
-
structurally_slave_group? v
|
41
|
-
end
|
42
|
-
|
43
|
-
if slave_group_configs.present?
|
44
|
-
slave_groups = HashWithIndifferentAccess.new
|
45
|
-
slave_group_configs.each do |slave_group_name, slave_configs|
|
46
|
-
slaves = HashWithIndifferentAccess.new
|
47
|
-
slave_configs.each do |slave_name, slave_config|
|
48
|
-
@shards[slave_name.to_sym] = connection_pool_for(slave_config, "#{value['adapter']}_connection")
|
49
|
-
slaves[slave_name.to_sym] = slave_name.to_sym
|
50
|
-
end
|
51
|
-
slave_groups[slave_group_name.to_sym] = Octopus::SlaveGroup.new(slaves)
|
52
|
-
end
|
53
|
-
@shards_slave_groups[key.to_sym] = slave_groups
|
54
|
-
@sharded = true
|
55
|
-
end
|
56
|
-
elsif value.is_a?(Hash)
|
57
|
-
@groups[key.to_s] = []
|
58
|
-
|
59
|
-
value.each do |k, v|
|
60
|
-
fail 'You have duplicated shard names!' if @shards.key?(k.to_sym)
|
61
|
-
|
62
|
-
initialize_adapter(v['adapter'])
|
63
|
-
config_with_octopus_shard = v.merge(:octopus_shard => k)
|
64
|
-
|
65
|
-
@shards[k.to_sym] = connection_pool_for(config_with_octopus_shard, "#{v['adapter']}_connection")
|
66
|
-
@groups[key.to_s] << k.to_sym
|
67
|
-
end
|
68
|
-
|
69
|
-
if structurally_slave_group? value
|
70
|
-
slaves = Hash[@groups[key.to_s].map { |v| [v, v] }]
|
71
|
-
@slave_groups[key.to_sym] = Octopus::SlaveGroup.new(slaves)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
@shards[:master] ||= ActiveRecord::Base.connection_pool_without_octopus
|
77
|
-
end
|
78
|
-
|
79
|
-
def initialize_replication(config)
|
80
|
-
@replicated = true
|
81
|
-
if config.key?('fully_replicated')
|
82
|
-
@fully_replicated = config['fully_replicated']
|
83
|
-
else
|
84
|
-
@fully_replicated = true
|
85
|
-
end
|
86
|
-
|
87
|
-
@slaves_list = @shards.keys.map(&:to_s).sort
|
88
|
-
@slaves_list.delete('master')
|
89
|
-
@slaves_load_balancer = Octopus::LoadBalancing::RoundRobin.new(@slaves_list)
|
90
|
-
end
|
91
|
-
|
92
|
-
def current_model
|
93
|
-
Thread.current['octopus.current_model']
|
94
|
-
end
|
95
|
-
|
96
|
-
def current_model=(model)
|
97
|
-
Thread.current['octopus.current_model'] = model.is_a?(ActiveRecord::Base) ? model.class : model
|
98
|
-
end
|
99
|
-
|
100
|
-
def current_shard
|
101
|
-
Thread.current['octopus.current_shard'] ||= :master
|
102
|
-
end
|
103
|
-
|
104
|
-
def current_shard=(shard_symbol)
|
105
|
-
self.current_slave_group = nil
|
106
|
-
if shard_symbol.is_a?(Array)
|
107
|
-
shard_symbol.each { |symbol| fail "Nonexistent Shard Name: #{symbol}" if @shards[symbol].nil? }
|
108
|
-
elsif shard_symbol.is_a?(Hash)
|
109
|
-
hash = shard_symbol
|
110
|
-
shard_symbol = hash[:shard]
|
111
|
-
slave_group_symbol = hash[:slave_group]
|
112
|
-
|
113
|
-
if shard_symbol.nil? && slave_group_symbol.nil?
|
114
|
-
fail 'Neither shard or slave group must be specified'
|
115
|
-
end
|
116
|
-
|
117
|
-
if shard_symbol.present?
|
118
|
-
fail "Nonexistent Shard Name: #{shard_symbol}" if @shards[shard_symbol].nil?
|
119
|
-
end
|
120
|
-
|
121
|
-
if slave_group_symbol.present?
|
122
|
-
if (@shards_slave_groups.try(:[], shard_symbol).present? && @shards_slave_groups[shard_symbol][slave_group_symbol].nil?) ||
|
123
|
-
(@shards_slave_groups.try(:[], shard_symbol).nil? && @slave_groups[slave_group_symbol].nil?)
|
124
|
-
fail "Nonexistent Slave Group Name: #{slave_group_symbol} in shards config: #{@shards_config.inspect}"
|
125
|
-
end
|
126
|
-
self.current_slave_group = slave_group_symbol
|
127
|
-
end
|
128
|
-
else
|
129
|
-
fail "Nonexistent Shard Name: #{shard_symbol}" if @shards[shard_symbol].nil?
|
130
|
-
end
|
131
|
-
|
132
|
-
Thread.current['octopus.current_shard'] = shard_symbol
|
36
|
+
def execute(sql, name = nil)
|
37
|
+
conn = select_connection
|
38
|
+
clean_connection_proxy if should_clean_connection_proxy?('execute')
|
39
|
+
conn.execute(sql, name)
|
133
40
|
end
|
134
41
|
|
135
|
-
def
|
136
|
-
|
42
|
+
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
|
43
|
+
conn = select_connection
|
44
|
+
clean_connection_proxy if should_clean_connection_proxy?('insert')
|
45
|
+
conn.insert(arel, name, pk, id_value, sequence_name, binds)
|
137
46
|
end
|
138
47
|
|
139
|
-
def
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
Thread.current['octopus.current_group'] = group_symbol
|
48
|
+
def update(arel, name = nil, binds = [])
|
49
|
+
conn = select_connection
|
50
|
+
# Call the legacy should_clean_connection_proxy? method here, emulating an insert.
|
51
|
+
clean_connection_proxy if should_clean_connection_proxy?('insert')
|
52
|
+
conn.update(arel, name, binds)
|
146
53
|
end
|
147
54
|
|
148
|
-
def
|
149
|
-
|
55
|
+
def delete(*args, &block)
|
56
|
+
legacy_method_missing_logic('delete', *args, &block)
|
150
57
|
end
|
151
58
|
|
152
|
-
def
|
153
|
-
|
59
|
+
def select_all(*args, &block)
|
60
|
+
legacy_method_missing_logic('select_all', *args, &block)
|
154
61
|
end
|
155
62
|
|
156
|
-
def block
|
157
|
-
|
158
|
-
end
|
159
|
-
|
160
|
-
def block=(block)
|
161
|
-
Thread.current['octopus.block'] = block
|
162
|
-
end
|
163
|
-
|
164
|
-
def last_current_shard
|
165
|
-
Thread.current['octopus.last_current_shard']
|
166
|
-
end
|
167
|
-
|
168
|
-
def last_current_shard=(last_current_shard)
|
169
|
-
Thread.current['octopus.last_current_shard'] = last_current_shard
|
170
|
-
end
|
171
|
-
|
172
|
-
def fully_replicated?
|
173
|
-
@fully_replicated || Thread.current['octopus.fully_replicated']
|
174
|
-
end
|
175
|
-
|
176
|
-
# Public: Whether or not a group exists with the given name converted to a
|
177
|
-
# string.
|
178
|
-
#
|
179
|
-
# Returns a boolean.
|
180
|
-
def has_group?(group)
|
181
|
-
@groups.key?(group.to_s)
|
182
|
-
end
|
183
|
-
|
184
|
-
# Public: Retrieves names of all loaded shards.
|
185
|
-
#
|
186
|
-
# Returns an array of shard names as symbols
|
187
|
-
def shard_names
|
188
|
-
@shards.keys
|
189
|
-
end
|
190
|
-
|
191
|
-
# Public: Retrieves the defined shards for a given group.
|
192
|
-
#
|
193
|
-
# Returns an array of shard names as symbols or nil if the group is not
|
194
|
-
# defined.
|
195
|
-
def shards_for_group(group)
|
196
|
-
@groups.fetch(group.to_s, nil)
|
63
|
+
def select_value(*args, &block)
|
64
|
+
legacy_method_missing_logic('select_value', *args, &block)
|
197
65
|
end
|
198
66
|
|
199
67
|
# Rails 3.1 sets automatic_reconnect to false when it removes
|
@@ -202,22 +70,14 @@ module Octopus
|
|
202
70
|
# reconnect, but in Rails 3.1 the flag prevents this.
|
203
71
|
def safe_connection(connection_pool)
|
204
72
|
connection_pool.automatic_reconnect ||= true
|
205
|
-
if !connection_pool.connected? &&
|
73
|
+
if !connection_pool.connected? && shards[Octopus.master_shard].connection.query_cache_enabled
|
206
74
|
connection_pool.connection.enable_query_cache!
|
207
75
|
end
|
208
76
|
connection_pool.connection
|
209
77
|
end
|
210
78
|
|
211
79
|
def select_connection
|
212
|
-
safe_connection(
|
213
|
-
end
|
214
|
-
|
215
|
-
def shard_name
|
216
|
-
current_shard.is_a?(Array) ? current_shard.first : current_shard
|
217
|
-
end
|
218
|
-
|
219
|
-
def should_clean_table_name?
|
220
|
-
@adapters.size > 1
|
80
|
+
safe_connection(shards[shard_name])
|
221
81
|
end
|
222
82
|
|
223
83
|
def run_queries_on_shard(shard, &_block)
|
@@ -229,13 +89,23 @@ module Octopus
|
|
229
89
|
end
|
230
90
|
|
231
91
|
def send_queries_to_multiple_shards(shards, &block)
|
232
|
-
shards.
|
92
|
+
shards.map do |shard|
|
233
93
|
run_queries_on_shard(shard, &block)
|
234
94
|
end
|
235
95
|
end
|
236
96
|
|
97
|
+
def send_queries_to_group(group, &block)
|
98
|
+
using_group(group) do
|
99
|
+
send_queries_to_multiple_shards(shards_for_group(group), &block)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def send_queries_to_all_shards(&block)
|
104
|
+
send_queries_to_multiple_shards(shard_names.uniq { |shard_name| shards[shard_name] }, &block)
|
105
|
+
end
|
106
|
+
|
237
107
|
def clean_connection_proxy
|
238
|
-
self.current_shard =
|
108
|
+
self.current_shard = Octopus.master_shard
|
239
109
|
self.current_model = nil
|
240
110
|
self.current_group = nil
|
241
111
|
self.block = nil
|
@@ -249,7 +119,7 @@ module Octopus
|
|
249
119
|
|
250
120
|
def transaction(options = {}, &block)
|
251
121
|
if !sharded && current_model_replicated?
|
252
|
-
run_queries_on_shard(
|
122
|
+
run_queries_on_shard(Octopus.master_shard) do
|
253
123
|
select_connection.transaction(options, &block)
|
254
124
|
end
|
255
125
|
else
|
@@ -258,20 +128,7 @@ module Octopus
|
|
258
128
|
end
|
259
129
|
|
260
130
|
def method_missing(method, *args, &block)
|
261
|
-
|
262
|
-
conn = select_connection
|
263
|
-
self.last_current_shard = current_shard
|
264
|
-
clean_connection_proxy
|
265
|
-
conn.send(method, *args, &block)
|
266
|
-
elsif should_send_queries_to_shard_slave_group?(method)
|
267
|
-
send_queries_to_shard_slave_group(method, *args, &block)
|
268
|
-
elsif should_send_queries_to_slave_group?(method)
|
269
|
-
send_queries_to_slave_group(method, *args, &block)
|
270
|
-
elsif should_send_queries_to_replicated_databases?(method)
|
271
|
-
send_queries_to_selected_slave(method, *args, &block)
|
272
|
-
else
|
273
|
-
select_connection.send(method, *args, &block)
|
274
|
-
end
|
131
|
+
legacy_method_missing_logic(method, *args, &block)
|
275
132
|
end
|
276
133
|
|
277
134
|
def respond_to?(method, include_private = false)
|
@@ -279,16 +136,18 @@ module Octopus
|
|
279
136
|
end
|
280
137
|
|
281
138
|
def connection_pool
|
282
|
-
|
139
|
+
shards[current_shard]
|
283
140
|
end
|
284
141
|
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
142
|
+
if Octopus.rails4?
|
143
|
+
def enable_query_cache!
|
144
|
+
clear_query_cache
|
145
|
+
with_each_healthy_shard { |v| v.connected? && safe_connection(v).enable_query_cache! }
|
146
|
+
end
|
289
147
|
|
290
|
-
|
291
|
-
|
148
|
+
def disable_query_cache!
|
149
|
+
with_each_healthy_shard { |v| v.connected? && safe_connection(v).disable_query_cache! }
|
150
|
+
end
|
292
151
|
end
|
293
152
|
|
294
153
|
def clear_query_cache
|
@@ -301,33 +160,81 @@ module Octopus
|
|
301
160
|
|
302
161
|
def clear_all_connections!
|
303
162
|
with_each_healthy_shard(&:disconnect!)
|
163
|
+
|
164
|
+
if Octopus.atleast_rails52?
|
165
|
+
# On Rails 5.2 it is no longer safe to re-use connection pools after they have been discarded
|
166
|
+
# This happens on webservers with forking, for example Phusion Passenger.
|
167
|
+
# Therefor after we clear all connections we reinitialize the shards to get fresh and not discarded ConnectionPool objects
|
168
|
+
proxy_config.reinitialize_shards
|
169
|
+
end
|
304
170
|
end
|
305
171
|
|
306
172
|
def connected?
|
307
|
-
|
173
|
+
shards.any? { |_k, v| v.connected? }
|
308
174
|
end
|
309
175
|
|
310
176
|
def should_send_queries_to_shard_slave_group?(method)
|
311
|
-
should_use_slaves_for_method?(method) &&
|
177
|
+
should_use_slaves_for_method?(method) && shards_slave_groups.try(:[], current_shard).try(:[], current_slave_group).present?
|
312
178
|
end
|
313
179
|
|
314
180
|
def send_queries_to_shard_slave_group(method, *args, &block)
|
315
|
-
send_queries_to_balancer(
|
181
|
+
send_queries_to_balancer(shards_slave_groups[current_shard][current_slave_group], method, *args, &block)
|
316
182
|
end
|
317
183
|
|
318
184
|
def should_send_queries_to_slave_group?(method)
|
319
|
-
should_use_slaves_for_method?(method) &&
|
185
|
+
should_use_slaves_for_method?(method) && slave_groups.try(:[], current_slave_group).present?
|
320
186
|
end
|
321
187
|
|
322
188
|
def send_queries_to_slave_group(method, *args, &block)
|
323
|
-
send_queries_to_balancer(
|
189
|
+
send_queries_to_balancer(slave_groups[current_slave_group], method, *args, &block)
|
190
|
+
end
|
191
|
+
|
192
|
+
def current_model_replicated?
|
193
|
+
replicated && (current_model.try(:replicated) || fully_replicated?)
|
194
|
+
end
|
195
|
+
|
196
|
+
def initialize_schema_migrations_table
|
197
|
+
if Octopus.atleast_rails52?
|
198
|
+
select_connection.transaction { ActiveRecord::SchemaMigration.create_table }
|
199
|
+
else
|
200
|
+
select_connection.initialize_schema_migrations_table
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def initialize_metadata_table
|
205
|
+
select_connection.transaction { ActiveRecord::InternalMetadata.create_table }
|
324
206
|
end
|
325
207
|
|
326
208
|
protected
|
327
209
|
|
210
|
+
# @thiagopradi - This legacy method missing logic will be keep for a while for compatibility
|
211
|
+
# and will be removed when Octopus 1.0 will be released.
|
212
|
+
# We are planning to migrate to a much stable logic for the Proxy that doesn't require method missing.
|
213
|
+
def legacy_method_missing_logic(method, *args, &block)
|
214
|
+
if should_clean_connection_proxy?(method)
|
215
|
+
conn = select_connection
|
216
|
+
clean_connection_proxy
|
217
|
+
conn.send(method, *args, &block)
|
218
|
+
elsif should_send_queries_to_shard_slave_group?(method)
|
219
|
+
send_queries_to_shard_slave_group(method, *args, &block)
|
220
|
+
elsif should_send_queries_to_slave_group?(method)
|
221
|
+
send_queries_to_slave_group(method, *args, &block)
|
222
|
+
elsif should_send_queries_to_replicated_databases?(method)
|
223
|
+
send_queries_to_selected_slave(method, *args, &block)
|
224
|
+
else
|
225
|
+
val = select_connection.send(method, *args, &block)
|
226
|
+
|
227
|
+
if val.instance_of? ActiveRecord::Result
|
228
|
+
val.current_shard = shard_name
|
229
|
+
end
|
230
|
+
|
231
|
+
val
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
328
235
|
# Ensure that a single failing slave doesn't take down the entire application
|
329
236
|
def with_each_healthy_shard
|
330
|
-
|
237
|
+
shards.each do |shard_name, v|
|
331
238
|
begin
|
332
239
|
yield(v)
|
333
240
|
rescue => e
|
@@ -339,17 +246,10 @@ module Octopus
|
|
339
246
|
end
|
340
247
|
end
|
341
248
|
|
342
|
-
|
343
|
-
if conn_handler.respond_to?(:connection_pool_list)
|
344
|
-
# Rails 4+
|
345
|
-
ar_pools = conn_handler.connection_pool_list
|
346
|
-
else
|
347
|
-
# Rails 3.2
|
348
|
-
ar_pools = conn_handler.connection_pools.values
|
349
|
-
end
|
249
|
+
ar_pools = ActiveRecord::Base.connection_handler.connection_pool_list
|
350
250
|
|
351
251
|
ar_pools.each do |pool|
|
352
|
-
next if pool ==
|
252
|
+
next if pool == shards[:master] # Already handled this
|
353
253
|
|
354
254
|
begin
|
355
255
|
yield(pool)
|
@@ -363,57 +263,20 @@ module Octopus
|
|
363
263
|
end
|
364
264
|
end
|
365
265
|
|
366
|
-
def connection_pool_for(adapter, config)
|
367
|
-
if Octopus.rails4?
|
368
|
-
arg = ActiveRecord::ConnectionAdapters::ConnectionSpecification.new(adapter.dup, config)
|
369
|
-
else
|
370
|
-
arg = ActiveRecord::Base::ConnectionSpecification.new(adapter.dup, config)
|
371
|
-
end
|
372
|
-
|
373
|
-
ActiveRecord::ConnectionAdapters::ConnectionPool.new(arg)
|
374
|
-
end
|
375
|
-
|
376
|
-
def initialize_adapter(adapter)
|
377
|
-
@adapters << adapter
|
378
|
-
begin
|
379
|
-
require "active_record/connection_adapters/#{adapter}_adapter"
|
380
|
-
rescue LoadError
|
381
|
-
raise "Please install the #{adapter} adapter: `gem install activerecord-#{adapter}-adapter` (#{$ERROR_INFO})"
|
382
|
-
end
|
383
|
-
end
|
384
|
-
|
385
|
-
def resolve_string_connection(spec)
|
386
|
-
if Octopus.rails41?
|
387
|
-
resolver = ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new({})
|
388
|
-
HashWithIndifferentAccess.new(resolver.spec(spec).config)
|
389
|
-
else
|
390
|
-
if Octopus.rails4?
|
391
|
-
resolver = ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new(spec, {})
|
392
|
-
else
|
393
|
-
resolver = ActiveRecord::Base::ConnectionSpecification::Resolver.new(spec, {})
|
394
|
-
end
|
395
|
-
HashWithIndifferentAccess.new(resolver.spec.config)
|
396
|
-
end
|
397
|
-
end
|
398
|
-
|
399
266
|
def should_clean_connection_proxy?(method)
|
400
267
|
method.to_s =~ /insert|select|execute/ && !current_model_replicated? && (!block || block != current_shard)
|
401
268
|
end
|
402
269
|
|
403
270
|
# Try to use slaves if and only if `replicated: true` is specified in `shards.yml` and no slaves groups are defined
|
404
271
|
def should_send_queries_to_replicated_databases?(method)
|
405
|
-
|
406
|
-
end
|
407
|
-
|
408
|
-
def current_model_replicated?
|
409
|
-
@replicated && (current_model.try(:replicated) || fully_replicated?)
|
272
|
+
replicated && method.to_s =~ /select/ && !block && !slaves_grouped?
|
410
273
|
end
|
411
274
|
|
412
275
|
def send_queries_to_selected_slave(method, *args, &block)
|
413
276
|
if current_model.replicated || fully_replicated?
|
414
|
-
selected_slave =
|
277
|
+
selected_slave = slaves_load_balancer.next current_load_balance_options
|
415
278
|
else
|
416
|
-
selected_slave =
|
279
|
+
selected_slave = Octopus.master_shard
|
417
280
|
end
|
418
281
|
|
419
282
|
send_queries_to_slave(selected_slave, method, *args, &block)
|
@@ -433,20 +296,24 @@ module Octopus
|
|
433
296
|
end
|
434
297
|
|
435
298
|
def slaves_grouped?
|
436
|
-
|
299
|
+
slave_groups.present?
|
437
300
|
end
|
438
301
|
|
439
302
|
# Temporarily switch `current_shard` to the next slave in a slave group and send queries to it
|
440
303
|
# while preserving `current_shard`
|
441
304
|
def send_queries_to_balancer(balancer, method, *args, &block)
|
442
|
-
send_queries_to_slave(balancer.next, method, *args, &block)
|
305
|
+
send_queries_to_slave(balancer.next(current_load_balance_options), method, *args, &block)
|
443
306
|
end
|
444
307
|
|
445
308
|
# Temporarily switch `current_shard` to the specified slave and send queries to it
|
446
309
|
# while preserving `current_shard`
|
447
310
|
def send_queries_to_slave(slave, method, *args, &block)
|
448
311
|
using_shard(slave) do
|
449
|
-
select_connection.send(method, *args, &block)
|
312
|
+
val = select_connection.send(method, *args, &block)
|
313
|
+
if val.instance_of? ActiveRecord::Result
|
314
|
+
val.current_shard = slave
|
315
|
+
end
|
316
|
+
val
|
450
317
|
end
|
451
318
|
end
|
452
319
|
|
@@ -468,6 +335,8 @@ module Octopus
|
|
468
335
|
# Temporarily switch `current_shard` and run the block
|
469
336
|
def using_shard(shard, &_block)
|
470
337
|
older_shard = current_shard
|
338
|
+
older_slave_group = current_slave_group
|
339
|
+
older_load_balance_options = current_load_balance_options
|
471
340
|
|
472
341
|
begin
|
473
342
|
unless current_model && !current_model.allowed_shard?(shard)
|
@@ -476,15 +345,21 @@ module Octopus
|
|
476
345
|
yield
|
477
346
|
ensure
|
478
347
|
self.current_shard = older_shard
|
348
|
+
self.current_slave_group = older_slave_group
|
349
|
+
self.current_load_balance_options = older_load_balance_options
|
479
350
|
end
|
480
351
|
end
|
481
352
|
|
482
|
-
|
483
|
-
|
484
|
-
|
353
|
+
# Temporarily switch `current_group` and run the block
|
354
|
+
def using_group(group, &_block)
|
355
|
+
older_group = current_group
|
485
356
|
|
486
|
-
|
487
|
-
|
357
|
+
begin
|
358
|
+
self.current_group = group
|
359
|
+
yield
|
360
|
+
ensure
|
361
|
+
self.current_group = older_group
|
362
|
+
end
|
488
363
|
end
|
489
364
|
end
|
490
365
|
end
|