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
@@ -0,0 +1,251 @@
|
|
1
|
+
module Octopus
|
2
|
+
class ProxyConfig
|
3
|
+
CURRENT_MODEL_KEY = 'octopus.current_model'.freeze
|
4
|
+
CURRENT_SHARD_KEY = 'octopus.current_shard'.freeze
|
5
|
+
CURRENT_GROUP_KEY = 'octopus.current_group'.freeze
|
6
|
+
CURRENT_SLAVE_GROUP_KEY = 'octopus.current_slave_group'.freeze
|
7
|
+
CURRENT_LOAD_BALANCE_OPTIONS_KEY = 'octopus.current_load_balance_options'.freeze
|
8
|
+
BLOCK_KEY = 'octopus.block'.freeze
|
9
|
+
FULLY_REPLICATED_KEY = 'octopus.fully_replicated'.freeze
|
10
|
+
|
11
|
+
attr_accessor :config, :sharded, :shards, :shards_slave_groups, :slave_groups,
|
12
|
+
:adapters, :replicated, :slaves_load_balancer, :slaves_list, :shards_slave_groups,
|
13
|
+
:slave_groups, :groups, :entire_sharded, :shards_config
|
14
|
+
|
15
|
+
def initialize(config)
|
16
|
+
initialize_shards(config)
|
17
|
+
initialize_replication(config) if !config.nil? && config['replicated']
|
18
|
+
end
|
19
|
+
|
20
|
+
def current_model
|
21
|
+
Thread.current[CURRENT_MODEL_KEY]
|
22
|
+
end
|
23
|
+
|
24
|
+
def current_model=(model)
|
25
|
+
Thread.current[CURRENT_MODEL_KEY] = model.is_a?(ActiveRecord::Base) ? model.class : model
|
26
|
+
end
|
27
|
+
|
28
|
+
def current_shard
|
29
|
+
Thread.current[CURRENT_SHARD_KEY] ||= Octopus.master_shard
|
30
|
+
end
|
31
|
+
|
32
|
+
def current_shard=(shard_symbol)
|
33
|
+
if shard_symbol.is_a?(Array)
|
34
|
+
self.current_slave_group = nil
|
35
|
+
shard_symbol.each { |symbol| fail "Nonexistent Shard Name: #{symbol}" if shards[symbol].nil? }
|
36
|
+
elsif shard_symbol.is_a?(Hash)
|
37
|
+
hash = shard_symbol
|
38
|
+
shard_symbol = hash[:shard]
|
39
|
+
slave_group_symbol = hash[:slave_group]
|
40
|
+
load_balance_options = hash[:load_balance_options]
|
41
|
+
|
42
|
+
if shard_symbol.nil? && slave_group_symbol.nil?
|
43
|
+
fail 'Neither shard or slave group must be specified'
|
44
|
+
end
|
45
|
+
|
46
|
+
if shard_symbol.present?
|
47
|
+
fail "Nonexistent Shard Name: #{shard_symbol}" if shards[shard_symbol].nil?
|
48
|
+
end
|
49
|
+
|
50
|
+
if slave_group_symbol.present?
|
51
|
+
if (shards_slave_groups.try(:[], shard_symbol).present? && shards_slave_groups[shard_symbol][slave_group_symbol].nil?) ||
|
52
|
+
(shards_slave_groups.try(:[], shard_symbol).nil? && @slave_groups[slave_group_symbol].nil?)
|
53
|
+
fail "Nonexistent Slave Group Name: #{slave_group_symbol} in shards config: #{shards_config.inspect}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
self.current_slave_group = slave_group_symbol
|
57
|
+
self.current_load_balance_options = load_balance_options
|
58
|
+
else
|
59
|
+
fail "Nonexistent Shard Name: #{shard_symbol}" if shards[shard_symbol].nil?
|
60
|
+
end
|
61
|
+
|
62
|
+
Thread.current[CURRENT_SHARD_KEY] = shard_symbol
|
63
|
+
end
|
64
|
+
|
65
|
+
def current_group
|
66
|
+
Thread.current[CURRENT_GROUP_KEY]
|
67
|
+
end
|
68
|
+
|
69
|
+
def current_group=(group_symbol)
|
70
|
+
# TODO: Error message should include all groups if given more than one bad name.
|
71
|
+
[group_symbol].flatten.compact.each do |group|
|
72
|
+
fail "Nonexistent Group Name: #{group}" unless has_group?(group)
|
73
|
+
end
|
74
|
+
|
75
|
+
Thread.current[CURRENT_GROUP_KEY] = group_symbol
|
76
|
+
end
|
77
|
+
|
78
|
+
def current_slave_group
|
79
|
+
Thread.current[CURRENT_SLAVE_GROUP_KEY]
|
80
|
+
end
|
81
|
+
|
82
|
+
def current_slave_group=(slave_group_symbol)
|
83
|
+
Thread.current[CURRENT_SLAVE_GROUP_KEY] = slave_group_symbol
|
84
|
+
Thread.current[CURRENT_LOAD_BALANCE_OPTIONS_KEY] = nil if slave_group_symbol.nil?
|
85
|
+
end
|
86
|
+
|
87
|
+
def current_load_balance_options
|
88
|
+
Thread.current[CURRENT_LOAD_BALANCE_OPTIONS_KEY]
|
89
|
+
end
|
90
|
+
|
91
|
+
def current_load_balance_options=(options)
|
92
|
+
Thread.current[CURRENT_LOAD_BALANCE_OPTIONS_KEY] = options
|
93
|
+
end
|
94
|
+
|
95
|
+
def block
|
96
|
+
Thread.current[BLOCK_KEY]
|
97
|
+
end
|
98
|
+
|
99
|
+
def block=(block)
|
100
|
+
Thread.current[BLOCK_KEY] = block
|
101
|
+
end
|
102
|
+
|
103
|
+
def fully_replicated?
|
104
|
+
@fully_replicated || Thread.current[FULLY_REPLICATED_KEY]
|
105
|
+
end
|
106
|
+
|
107
|
+
# Public: Whether or not a group exists with the given name converted to a
|
108
|
+
# string.
|
109
|
+
#
|
110
|
+
# Returns a boolean.
|
111
|
+
def has_group?(group)
|
112
|
+
@groups.key?(group.to_s)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Public: Retrieves names of all loaded shards.
|
116
|
+
#
|
117
|
+
# Returns an array of shard names as symbols
|
118
|
+
def shard_names
|
119
|
+
shards.keys
|
120
|
+
end
|
121
|
+
|
122
|
+
def shard_name
|
123
|
+
current_shard.is_a?(Array) ? current_shard.first : current_shard
|
124
|
+
end
|
125
|
+
|
126
|
+
# Public: Retrieves the defined shards for a given group.
|
127
|
+
#
|
128
|
+
# Returns an array of shard names as symbols or nil if the group is not
|
129
|
+
# defined.
|
130
|
+
def shards_for_group(group)
|
131
|
+
@groups.fetch(group.to_s, nil)
|
132
|
+
end
|
133
|
+
|
134
|
+
def initialize_shards(config)
|
135
|
+
@original_config = config
|
136
|
+
|
137
|
+
self.shards = HashWithIndifferentAccess.new
|
138
|
+
self.shards_slave_groups = HashWithIndifferentAccess.new
|
139
|
+
self.slave_groups = HashWithIndifferentAccess.new
|
140
|
+
self.groups = {}
|
141
|
+
self.config = ActiveRecord::Base.connection_pool_without_octopus.spec.config
|
142
|
+
|
143
|
+
unless config.nil?
|
144
|
+
self.entire_sharded = config['entire_sharded']
|
145
|
+
self.shards_config = config[Octopus.rails_env]
|
146
|
+
end
|
147
|
+
|
148
|
+
self.shards_config ||= []
|
149
|
+
|
150
|
+
shards_config.each do |key, value|
|
151
|
+
if value.is_a?(String)
|
152
|
+
value = resolve_string_connection(value).merge(:octopus_shard => key)
|
153
|
+
initialize_adapter(value['adapter'])
|
154
|
+
shards[key.to_sym] = connection_pool_for(value, "#{value['adapter']}_connection")
|
155
|
+
elsif value.is_a?(Hash) && value.key?('adapter')
|
156
|
+
value.merge!(:octopus_shard => key)
|
157
|
+
initialize_adapter(value['adapter'])
|
158
|
+
shards[key.to_sym] = connection_pool_for(value, "#{value['adapter']}_connection")
|
159
|
+
|
160
|
+
slave_group_configs = value.select do |_k, v|
|
161
|
+
structurally_slave_group? v
|
162
|
+
end
|
163
|
+
|
164
|
+
if slave_group_configs.present?
|
165
|
+
slave_groups = HashWithIndifferentAccess.new
|
166
|
+
slave_group_configs.each do |slave_group_name, slave_configs|
|
167
|
+
slaves = HashWithIndifferentAccess.new
|
168
|
+
slave_configs.each do |slave_name, slave_config|
|
169
|
+
shards[slave_name.to_sym] = connection_pool_for(slave_config, "#{value['adapter']}_connection")
|
170
|
+
slaves[slave_name.to_sym] = slave_name.to_sym
|
171
|
+
end
|
172
|
+
slave_groups[slave_group_name.to_sym] = Octopus::SlaveGroup.new(slaves)
|
173
|
+
end
|
174
|
+
@shards_slave_groups[key.to_sym] = slave_groups
|
175
|
+
@sharded = true
|
176
|
+
end
|
177
|
+
elsif value.is_a?(Hash)
|
178
|
+
@groups[key.to_s] = []
|
179
|
+
|
180
|
+
value.each do |k, v|
|
181
|
+
fail 'You have duplicated shard names!' if shards.key?(k.to_sym)
|
182
|
+
|
183
|
+
initialize_adapter(v['adapter'])
|
184
|
+
config_with_octopus_shard = v.merge(:octopus_shard => k)
|
185
|
+
|
186
|
+
shards[k.to_sym] = connection_pool_for(config_with_octopus_shard, "#{v['adapter']}_connection")
|
187
|
+
@groups[key.to_s] << k.to_sym
|
188
|
+
end
|
189
|
+
|
190
|
+
if structurally_slave_group? value
|
191
|
+
slaves = Hash[@groups[key.to_s].map { |v| [v, v] }]
|
192
|
+
@slave_groups[key.to_sym] = Octopus::SlaveGroup.new(slaves)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
shards[:master] ||= ActiveRecord::Base.connection_pool_without_octopus if Octopus.master_shard == :master
|
198
|
+
end
|
199
|
+
|
200
|
+
def initialize_replication(config)
|
201
|
+
@replicated = true
|
202
|
+
if config.key?('fully_replicated')
|
203
|
+
@fully_replicated = config['fully_replicated']
|
204
|
+
else
|
205
|
+
@fully_replicated = true
|
206
|
+
end
|
207
|
+
|
208
|
+
@slaves_list = shards.keys.map(&:to_s).sort
|
209
|
+
@slaves_list.delete('master')
|
210
|
+
@slaves_load_balancer = Octopus.load_balancer.new(@slaves_list)
|
211
|
+
end
|
212
|
+
|
213
|
+
def reinitialize_shards
|
214
|
+
initialize_shards(@original_config)
|
215
|
+
end
|
216
|
+
|
217
|
+
private
|
218
|
+
|
219
|
+
def connection_pool_for(config, adapter)
|
220
|
+
if Octopus.rails4?
|
221
|
+
spec = ActiveRecord::ConnectionAdapters::ConnectionSpecification.new(config.dup, adapter )
|
222
|
+
else
|
223
|
+
name = adapter["octopus_shard"]
|
224
|
+
spec = ActiveRecord::ConnectionAdapters::ConnectionSpecification.new(name, config.dup, adapter)
|
225
|
+
end
|
226
|
+
|
227
|
+
ActiveRecord::ConnectionAdapters::ConnectionPool.new(spec)
|
228
|
+
end
|
229
|
+
|
230
|
+
def resolve_string_connection(spec)
|
231
|
+
resolver = ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new({})
|
232
|
+
HashWithIndifferentAccess.new(resolver.spec(spec).config)
|
233
|
+
end
|
234
|
+
|
235
|
+
def structurally_slave?(config)
|
236
|
+
config.is_a?(Hash) && config.key?('adapter')
|
237
|
+
end
|
238
|
+
|
239
|
+
def structurally_slave_group?(config)
|
240
|
+
config.is_a?(Hash) && config.values.any? { |v| structurally_slave? v }
|
241
|
+
end
|
242
|
+
|
243
|
+
def initialize_adapter(adapter)
|
244
|
+
begin
|
245
|
+
require "active_record/connection_adapters/#{adapter}_adapter"
|
246
|
+
rescue LoadError
|
247
|
+
raise "Please install the #{adapter} adapter: `gem install activerecord-#{adapter}-adapter` (#{$ERROR_INFO})"
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# query cache methods are moved to ConnectionPool for Rails >= 5.0
|
2
|
+
module Octopus
|
3
|
+
module ConnectionPool
|
4
|
+
module QueryCacheForShards
|
5
|
+
%i(enable_query_cache! disable_query_cache!).each do |method|
|
6
|
+
define_method(method) do
|
7
|
+
if(Octopus.enabled? && (shards = ActiveRecord::Base.connection.shards)['master'] == self)
|
8
|
+
shards.each do |shard_name, v|
|
9
|
+
if shard_name == 'master'
|
10
|
+
super()
|
11
|
+
else
|
12
|
+
v.public_send(method)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
else
|
16
|
+
super()
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
ActiveRecord::ConnectionAdapters::ConnectionPool.send(:prepend, Octopus::ConnectionPool::QueryCacheForShards)
|
data/lib/octopus/railtie.rb
CHANGED
@@ -16,8 +16,43 @@ module Octopus
|
|
16
16
|
@ar_relation = ar_relation
|
17
17
|
end
|
18
18
|
|
19
|
+
def respond_to?(*args)
|
20
|
+
method_missing(:respond_to?, *args)
|
21
|
+
end
|
22
|
+
|
23
|
+
# methods redefined in ActiveRecord that should run_on_shard
|
24
|
+
ENUM_METHODS = (::Enumerable.instance_methods - ::Object.instance_methods).reject do |m|
|
25
|
+
::ActiveRecord::Relation.instance_method(m).source_location rescue nil
|
26
|
+
end + [:each, :map, :index_by]
|
27
|
+
# `find { ... }` etc. should run_on_shard, `find(id)` should be sent to relation
|
28
|
+
ENUM_WITH_BLOCK_METHODS = [:find, :select, :none?, :any?, :one?, :many?, :sum]
|
29
|
+
BATCH_METHODS = [:find_each, :find_in_batches, :in_batches]
|
30
|
+
WHERE_CHAIN_METHODS = [:not]
|
31
|
+
|
19
32
|
def method_missing(method, *args, &block)
|
20
|
-
|
33
|
+
if !block && BATCH_METHODS.include?(method)
|
34
|
+
::Enumerator.new do |yielder|
|
35
|
+
run_on_shard do
|
36
|
+
@ar_relation.public_send(method, *args) do |batch_item|
|
37
|
+
yielder << batch_item
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
elsif ENUM_METHODS.include?(method) || block && ENUM_WITH_BLOCK_METHODS.include?(method)
|
42
|
+
run_on_shard { @ar_relation.to_a }.public_send(method, *args, &block)
|
43
|
+
elsif WHERE_CHAIN_METHODS.include?(method)
|
44
|
+
::Octopus::ScopeProxy.new(@current_shard, run_on_shard { @ar_relation.public_send(method, *args) } )
|
45
|
+
elsif block
|
46
|
+
@ar_relation.public_send(method, *args, &block)
|
47
|
+
else
|
48
|
+
run_on_shard do
|
49
|
+
if method == :load_records
|
50
|
+
@ar_relation.send(method, *args)
|
51
|
+
else
|
52
|
+
@ar_relation.public_send(method, *args)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
21
56
|
end
|
22
57
|
|
23
58
|
def ==(other)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Octopus::ResultPatch
|
2
|
+
attr_accessor :current_shard
|
3
|
+
|
4
|
+
private
|
5
|
+
|
6
|
+
def hash_rows
|
7
|
+
if current_shard.blank?
|
8
|
+
super
|
9
|
+
else
|
10
|
+
foo = super
|
11
|
+
foo.each { |f| f.merge!('current_shard' => current_shard) }
|
12
|
+
foo
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class ActiveRecord::Result
|
18
|
+
prepend Octopus::ResultPatch
|
19
|
+
end
|
data/lib/octopus/scope_proxy.rb
CHANGED
@@ -11,20 +11,24 @@ module Octopus
|
|
11
11
|
|
12
12
|
attr_accessor :klass
|
13
13
|
|
14
|
+
# Dup and clone should be delegated to the class.
|
15
|
+
# We want to dup the query, not the scope proxy.
|
16
|
+
delegate :dup, :clone, to: :klass
|
17
|
+
|
14
18
|
def initialize(shard, klass)
|
15
19
|
@current_shard = shard
|
16
20
|
@klass = klass
|
17
21
|
end
|
18
22
|
|
19
23
|
def using(shard)
|
20
|
-
fail "Nonexistent Shard Name: #{shard}" if @klass.connection.
|
24
|
+
fail "Nonexistent Shard Name: #{shard}" if @klass.connection.shards[shard].nil?
|
21
25
|
@current_shard = shard
|
22
26
|
self
|
23
27
|
end
|
24
28
|
|
25
29
|
# Transaction Method send all queries to a specified shard.
|
26
30
|
def transaction(options = {}, &block)
|
27
|
-
run_on_shard {
|
31
|
+
run_on_shard { klass.transaction(options, &block) }
|
28
32
|
end
|
29
33
|
|
30
34
|
def connection
|
@@ -40,10 +44,13 @@ module Octopus
|
|
40
44
|
end
|
41
45
|
|
42
46
|
def method_missing(method, *args, &block)
|
43
|
-
result = run_on_shard { @klass.
|
47
|
+
result = run_on_shard { @klass.__send__(method, *args, &block) }
|
44
48
|
if result.respond_to?(:all)
|
45
|
-
|
46
|
-
|
49
|
+
return ::Octopus::ScopeProxy.new(current_shard, result)
|
50
|
+
end
|
51
|
+
|
52
|
+
if result.respond_to?(:current_shard)
|
53
|
+
result.current_shard = current_shard
|
47
54
|
end
|
48
55
|
|
49
56
|
result
|
@@ -20,7 +20,8 @@ module Octopus
|
|
20
20
|
define_method with do |*args, &block|
|
21
21
|
run_on_shard { send(without, *args, &block) }
|
22
22
|
end
|
23
|
-
|
23
|
+
alias_method without.to_sym, name.to_sym
|
24
|
+
alias_method name.to_sym, with.to_sym
|
24
25
|
end
|
25
26
|
end
|
26
27
|
|
@@ -31,8 +32,12 @@ module Octopus
|
|
31
32
|
# Use a case statement to avoid any path through ActiveRecord::Delegation's
|
32
33
|
# respond_to? code. We want to avoid the respond_to? code because it can have
|
33
34
|
# the side effect of causing a call to load_target
|
34
|
-
|
35
|
-
r
|
35
|
+
|
36
|
+
if (ActiveRecord::Relation === r || ActiveRecord::QueryMethods::WhereChain === r) && !(Octopus::RelationProxy === r)
|
37
|
+
Octopus::RelationProxy.new(cs, r)
|
38
|
+
else
|
39
|
+
r
|
40
|
+
end
|
36
41
|
else
|
37
42
|
yield
|
38
43
|
end
|
data/lib/octopus/slave_group.rb
CHANGED
@@ -3,11 +3,11 @@ module Octopus
|
|
3
3
|
def initialize(slaves)
|
4
4
|
slaves = HashWithIndifferentAccess.new(slaves)
|
5
5
|
slaves_list = slaves.values
|
6
|
-
@load_balancer = Octopus
|
6
|
+
@load_balancer = Octopus.load_balancer.new(slaves_list)
|
7
7
|
end
|
8
8
|
|
9
|
-
def next
|
10
|
-
@load_balancer.next
|
9
|
+
def next(options)
|
10
|
+
@load_balancer.next options
|
11
11
|
end
|
12
12
|
end
|
13
13
|
end
|
data/lib/octopus/version.rb
CHANGED
data/lib/octopus.rb
CHANGED
@@ -11,12 +11,12 @@ module Octopus
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def self.rails_env
|
14
|
-
@rails_env ||=
|
14
|
+
@rails_env ||= defined?(::Rails.env) ? Rails.env.to_s : 'shards'
|
15
15
|
end
|
16
16
|
|
17
17
|
def self.config
|
18
18
|
@config ||= begin
|
19
|
-
file_name = Octopus.directory
|
19
|
+
file_name = File.join(Octopus.directory, 'config/shards.yml').to_s
|
20
20
|
|
21
21
|
if File.exist?(file_name) || File.symlink?(file_name)
|
22
22
|
config ||= HashWithIndifferentAccess.new(YAML.load(ERB.new(File.read(file_name)).result))[Octopus.env]
|
@@ -28,13 +28,25 @@ module Octopus
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
+
def self.load_balancer=(balancer)
|
32
|
+
@load_balancer = balancer
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.load_balancer
|
36
|
+
@load_balancer ||= Octopus::LoadBalancing::RoundRobin
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.master_shard
|
40
|
+
((config && config[:master_shard]) || :master).to_sym
|
41
|
+
end
|
42
|
+
|
31
43
|
# Public: Whether or not Octopus is configured and should hook into the
|
32
44
|
# current environment. Checks the environments config option for the Rails
|
33
45
|
# environment by default.
|
34
46
|
#
|
35
47
|
# Returns a boolean
|
36
48
|
def self.enabled?
|
37
|
-
if defined?(::Rails)
|
49
|
+
if defined?(::Rails.env)
|
38
50
|
Octopus.environments.include?(Rails.env.to_s)
|
39
51
|
else
|
40
52
|
# TODO: This doens't feel right but !Octopus.config.blank? is breaking a
|
@@ -46,7 +58,7 @@ module Octopus
|
|
46
58
|
# Returns the Rails.root_to_s when you are using rails
|
47
59
|
# Running the current directory in a generic Ruby process
|
48
60
|
def self.directory
|
49
|
-
@directory ||= defined?(Rails) ?
|
61
|
+
@directory ||= defined?(::Rails.root) ? Rails.root.to_s : Dir.pwd
|
50
62
|
end
|
51
63
|
|
52
64
|
# This is the default way to do Octopus Setup
|
@@ -78,26 +90,42 @@ module Octopus
|
|
78
90
|
robust_environments.include? rails_env
|
79
91
|
end
|
80
92
|
|
81
|
-
def self.
|
82
|
-
ActiveRecord::VERSION::MAJOR
|
93
|
+
def self.rails4?
|
94
|
+
ActiveRecord::VERSION::MAJOR == 4
|
83
95
|
end
|
84
96
|
|
85
|
-
def self.
|
86
|
-
ActiveRecord::VERSION::
|
97
|
+
def self.rails42?
|
98
|
+
rails4? && ActiveRecord::VERSION::MINOR == 2
|
87
99
|
end
|
88
100
|
|
89
|
-
def self.
|
90
|
-
|
101
|
+
def self.rails50?
|
102
|
+
ActiveRecord::VERSION::MAJOR == 5 && ActiveRecord::VERSION::MINOR == 0
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.atleast_rails50?
|
106
|
+
ActiveRecord::VERSION::MAJOR >= 5
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.rails51?
|
110
|
+
ActiveRecord::VERSION::MAJOR == 5 && ActiveRecord::VERSION::MINOR == 1
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.rails52?
|
114
|
+
ActiveRecord::VERSION::MAJOR == 5 && ActiveRecord::VERSION::MINOR == 2
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.atleast_rails51?
|
118
|
+
ActiveRecord::VERSION::MAJOR > 5 || (ActiveRecord::VERSION::MAJOR == 5 && ActiveRecord::VERSION::MINOR >= 1)
|
91
119
|
end
|
92
120
|
|
93
|
-
def self.
|
94
|
-
|
121
|
+
def self.atleast_rails52?
|
122
|
+
ActiveRecord::VERSION::MAJOR > 5 || (ActiveRecord::VERSION::MAJOR == 5 && ActiveRecord::VERSION::MINOR > 1)
|
95
123
|
end
|
96
124
|
|
97
125
|
attr_writer :logger
|
98
126
|
|
99
127
|
def self.logger
|
100
|
-
if defined?(Rails)
|
128
|
+
if defined?(Rails.logger)
|
101
129
|
@logger ||= Rails.logger
|
102
130
|
else
|
103
131
|
@logger ||= Logger.new($stderr)
|
@@ -119,32 +147,57 @@ module Octopus
|
|
119
147
|
end
|
120
148
|
end
|
121
149
|
|
150
|
+
def self.using_group(group, &block)
|
151
|
+
conn = ActiveRecord::Base.connection
|
152
|
+
|
153
|
+
if conn.is_a?(Octopus::Proxy)
|
154
|
+
conn.send_queries_to_group(group, &block)
|
155
|
+
else
|
156
|
+
yield
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def self.using_all(&block)
|
161
|
+
conn = ActiveRecord::Base.connection
|
162
|
+
|
163
|
+
if conn.is_a?(Octopus::Proxy)
|
164
|
+
conn.send_queries_to_all_shards(&block)
|
165
|
+
else
|
166
|
+
yield
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
122
170
|
def self.fully_replicated(&_block)
|
123
|
-
old_fully_replicated = Thread.current[
|
124
|
-
Thread.current[
|
171
|
+
old_fully_replicated = Thread.current[Octopus::ProxyConfig::FULLY_REPLICATED_KEY]
|
172
|
+
Thread.current[Octopus::ProxyConfig::FULLY_REPLICATED_KEY] = true
|
125
173
|
yield
|
126
174
|
ensure
|
127
|
-
Thread.current[
|
175
|
+
Thread.current[Octopus::ProxyConfig::FULLY_REPLICATED_KEY] = old_fully_replicated
|
128
176
|
end
|
129
177
|
end
|
130
178
|
|
179
|
+
require 'octopus/exception'
|
180
|
+
|
131
181
|
require 'octopus/shard_tracking'
|
132
182
|
require 'octopus/shard_tracking/attribute'
|
133
183
|
require 'octopus/shard_tracking/dynamic'
|
134
184
|
|
135
185
|
require 'octopus/model'
|
186
|
+
require 'octopus/result_patch'
|
136
187
|
require 'octopus/migration'
|
137
188
|
require 'octopus/association'
|
138
189
|
require 'octopus/collection_association'
|
139
|
-
require 'octopus/has_and_belongs_to_many_association' unless Octopus.rails41?
|
140
190
|
require 'octopus/association_shard_tracking'
|
141
191
|
require 'octopus/persistence'
|
142
192
|
require 'octopus/log_subscriber'
|
143
193
|
require 'octopus/abstract_adapter'
|
144
194
|
require 'octopus/singular_association'
|
195
|
+
require 'octopus/finder_methods'
|
196
|
+
require 'octopus/query_cache_for_shards' unless Octopus.rails4?
|
145
197
|
|
146
|
-
require 'octopus/railtie' if defined?(::Rails)
|
198
|
+
require 'octopus/railtie' if defined?(::Rails::Railtie)
|
147
199
|
|
200
|
+
require 'octopus/proxy_config'
|
148
201
|
require 'octopus/proxy'
|
149
202
|
require 'octopus/collection_proxy'
|
150
203
|
require 'octopus/relation_proxy'
|
data/spec/config/shards.yml
CHANGED
@@ -86,6 +86,18 @@ production_replicated:
|
|
86
86
|
<<: *mysql
|
87
87
|
|
88
88
|
|
89
|
+
production_fully_replicated:
|
90
|
+
replicated: true
|
91
|
+
fully_replicated: true
|
92
|
+
|
93
|
+
shards:
|
94
|
+
slave1:
|
95
|
+
database: octopus_shard_2
|
96
|
+
<<: *mysql
|
97
|
+
slave2:
|
98
|
+
database: octopus_shard_3
|
99
|
+
<<: *mysql
|
100
|
+
|
89
101
|
replicated_with_one_slave:
|
90
102
|
replicated: true
|
91
103
|
shards:
|