ar-octopus 0.9.0 → 0.9.1
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 +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +10 -2
- data/Appraisals +4 -0
- data/README.mkdn +1 -1
- data/gemfiles/rails51.gemfile +7 -0
- data/lib/octopus.rb +11 -6
- data/lib/octopus/collection_association.rb +9 -3
- data/lib/octopus/finder_methods.rb +1 -1
- data/lib/octopus/load_balancing/round_robin.rb +1 -0
- data/lib/octopus/log_subscriber.rb +6 -2
- data/lib/octopus/migration.rb +25 -9
- data/lib/octopus/model.rb +17 -25
- data/lib/octopus/proxy.rb +78 -279
- data/lib/octopus/proxy_config.rb +252 -0
- data/lib/octopus/relation_proxy.rb +7 -1
- data/lib/octopus/scope_proxy.rb +1 -1
- data/lib/octopus/shard_tracking.rb +2 -1
- data/lib/octopus/version.rb +1 -1
- 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 +15 -15
- data/spec/octopus/load_balancing/round_robin_spec.rb +15 -0
- data/spec/octopus/log_subscriber_spec.rb +1 -1
- data/spec/octopus/model_spec.rb +34 -5
- data/spec/octopus/octopus_spec.rb +1 -1
- data/spec/octopus/proxy_spec.rb +16 -38
- data/spec/octopus/relation_proxy_spec.rb +4 -0
- data/spec/octopus/replication_spec.rb +5 -1
- data/spec/spec_helper.rb +2 -0
- data/spec/support/octopus_helper.rb +1 -2
- data/spec/tasks/octopus.rake_spec.rb +2 -4
- metadata +7 -6
- data/.ruby-version +0 -1
- data/init.rb +0 -1
- data/rails/init.rb +0 -1
@@ -0,0 +1,252 @@
|
|
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
|
+
self.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
|
+
private
|
214
|
+
|
215
|
+
def connection_pool_for(adapter, config)
|
216
|
+
if Octopus.rails4?
|
217
|
+
arg = ActiveRecord::ConnectionAdapters::ConnectionSpecification.new(adapter.dup, config)
|
218
|
+
else
|
219
|
+
name = adapter["octopus_shard"]
|
220
|
+
arg = ActiveRecord::ConnectionAdapters::ConnectionSpecification.new(name, adapter.dup, config)
|
221
|
+
end
|
222
|
+
|
223
|
+
ActiveRecord::ConnectionAdapters::ConnectionPool.new(arg)
|
224
|
+
end
|
225
|
+
|
226
|
+
def resolve_string_connection(spec)
|
227
|
+
if Octopus.rails41? || Octopus.rails50? || Octopus.rails51?
|
228
|
+
resolver = ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new({})
|
229
|
+
HashWithIndifferentAccess.new(resolver.spec(spec).config)
|
230
|
+
else
|
231
|
+
resolver = ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new(spec, {})
|
232
|
+
HashWithIndifferentAccess.new(resolver.spec.config)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def structurally_slave?(config)
|
237
|
+
config.is_a?(Hash) && config.key?('adapter')
|
238
|
+
end
|
239
|
+
|
240
|
+
def structurally_slave_group?(config)
|
241
|
+
config.is_a?(Hash) && config.values.any? { |v| structurally_slave? v }
|
242
|
+
end
|
243
|
+
|
244
|
+
def initialize_adapter(adapter)
|
245
|
+
begin
|
246
|
+
require "active_record/connection_adapters/#{adapter}_adapter"
|
247
|
+
rescue LoadError
|
248
|
+
raise "Please install the #{adapter} adapter: `gem install activerecord-#{adapter}-adapter` (#{$ERROR_INFO})"
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
@@ -24,7 +24,13 @@ module Octopus
|
|
24
24
|
if block
|
25
25
|
@ar_relation.public_send(method, *args, &block)
|
26
26
|
else
|
27
|
-
run_on_shard
|
27
|
+
run_on_shard do
|
28
|
+
if method == :load_records
|
29
|
+
@ar_relation.send(method, *args)
|
30
|
+
else
|
31
|
+
@ar_relation.public_send(method, *args)
|
32
|
+
end
|
33
|
+
end
|
28
34
|
end
|
29
35
|
end
|
30
36
|
|
data/lib/octopus/scope_proxy.rb
CHANGED
@@ -21,7 +21,7 @@ module Octopus
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def using(shard)
|
24
|
-
fail "Nonexistent Shard Name: #{shard}" if @klass.connection.
|
24
|
+
fail "Nonexistent Shard Name: #{shard}" if @klass.connection.shards[shard].nil?
|
25
25
|
@current_shard = shard
|
26
26
|
self
|
27
27
|
end
|
data/lib/octopus/version.rb
CHANGED
@@ -96,7 +96,7 @@ describe Octopus::AssociationShardTracking, :shards => [:brazil, :master, :canad
|
|
96
96
|
expect(@permission_brazil.roles).to eq([new_brazil_role])
|
97
97
|
end
|
98
98
|
|
99
|
-
it 'should
|
99
|
+
it 'should work for build method' do
|
100
100
|
new_brazil_role = Role.using(:brazil).create!(:name => 'Brazil Role')
|
101
101
|
c = new_brazil_role.permissions.create(:name => 'new Permission')
|
102
102
|
c.save
|
@@ -105,7 +105,7 @@ describe Octopus::AssociationShardTracking, :shards => [:brazil, :master, :canad
|
|
105
105
|
expect(new_brazil_role.permissions).to eq([c])
|
106
106
|
end
|
107
107
|
|
108
|
-
describe 'it should
|
108
|
+
describe 'it should work when using' do
|
109
109
|
before(:each) do
|
110
110
|
@permission_brazil_2 = Permission.using(:brazil).create!(:name => 'Brazil Item 2')
|
111
111
|
@role = Role.using(:brazil).create!(:name => 'testes')
|
@@ -198,9 +198,9 @@ describe Octopus::AssociationShardTracking, :shards => [:brazil, :master, :canad
|
|
198
198
|
|
199
199
|
it 'exists?' do
|
200
200
|
role = @permission_brazil_2.roles.create(:name => 'Builded Role')
|
201
|
-
expect(@permission_brazil_2.roles.exists?(role)).to be true
|
201
|
+
expect(@permission_brazil_2.roles.exists?(role.id)).to be true
|
202
202
|
@permission_brazil_2.roles.destroy_all
|
203
|
-
expect(@permission_brazil_2.roles.exists?(role)).to be false
|
203
|
+
expect(@permission_brazil_2.roles.exists?(role.id)).to be false
|
204
204
|
end
|
205
205
|
|
206
206
|
it 'clear' do
|
@@ -250,7 +250,7 @@ describe Octopus::AssociationShardTracking, :shards => [:brazil, :master, :canad
|
|
250
250
|
expect(@project.programmers).to eq([new_brazil_programmer])
|
251
251
|
end
|
252
252
|
|
253
|
-
it 'should
|
253
|
+
it 'should work for create method' do
|
254
254
|
new_brazil_programmer = Programmer.using(:brazil).create!(:name => 'Joao')
|
255
255
|
c = new_brazil_programmer.projects.create(:name => 'new Project')
|
256
256
|
c.save
|
@@ -259,7 +259,7 @@ describe Octopus::AssociationShardTracking, :shards => [:brazil, :master, :canad
|
|
259
259
|
expect(new_brazil_programmer.projects).to eq([c])
|
260
260
|
end
|
261
261
|
|
262
|
-
describe 'it should
|
262
|
+
describe 'it should work when using' do
|
263
263
|
before(:each) do
|
264
264
|
@new_brazil_programmer = Programmer.using(:brazil).create!(:name => 'Jose')
|
265
265
|
@project = Project.using(:brazil).create!(:name => 'VB Application :-(')
|
@@ -352,9 +352,9 @@ describe Octopus::AssociationShardTracking, :shards => [:brazil, :master, :canad
|
|
352
352
|
|
353
353
|
it 'exists?' do
|
354
354
|
role = @new_brazil_programmer.projects.create(:name => 'New VB App :-/')
|
355
|
-
expect(@new_brazil_programmer.projects.exists?(role)).to be true
|
355
|
+
expect(@new_brazil_programmer.projects.exists?(role.id)).to be true
|
356
356
|
@new_brazil_programmer.projects.destroy_all
|
357
|
-
expect(@new_brazil_programmer.projects.exists?(role)).to be false
|
357
|
+
expect(@new_brazil_programmer.projects.exists?(role.id)).to be false
|
358
358
|
end
|
359
359
|
|
360
360
|
it 'clear' do
|
@@ -414,7 +414,7 @@ describe Octopus::AssociationShardTracking, :shards => [:brazil, :master, :canad
|
|
414
414
|
expect(@item_brazil.client).to eq(new_brazil_client)
|
415
415
|
end
|
416
416
|
|
417
|
-
it 'should
|
417
|
+
it 'should work for build method' do
|
418
418
|
item2 = Item.using(:brazil).create!(:name => 'Brazil Item')
|
419
419
|
c = item2.create_client(:name => 'new Client')
|
420
420
|
c.save
|
@@ -442,7 +442,7 @@ describe Octopus::AssociationShardTracking, :shards => [:brazil, :master, :canad
|
|
442
442
|
end
|
443
443
|
end
|
444
444
|
|
445
|
-
describe 'it should
|
445
|
+
describe 'it should work when using' do
|
446
446
|
before(:each) do
|
447
447
|
@item_brazil_2 = Item.using(:brazil).create!(:name => 'Brazil Item 2')
|
448
448
|
expect(@brazil_client.items.to_set).to eq([@item_brazil].to_set)
|
@@ -540,9 +540,9 @@ describe Octopus::AssociationShardTracking, :shards => [:brazil, :master, :canad
|
|
540
540
|
end
|
541
541
|
|
542
542
|
it 'exists?' do
|
543
|
-
expect(@brazil_client.items.exists?(@item_brazil)).to be true
|
543
|
+
expect(@brazil_client.items.exists?(@item_brazil.id)).to be true
|
544
544
|
@brazil_client.items.destroy_all
|
545
|
-
expect(@brazil_client.items.exists?(@item_brazil)).to be false
|
545
|
+
expect(@brazil_client.items.exists?(@item_brazil.id)).to be false
|
546
546
|
end
|
547
547
|
|
548
548
|
it 'uniq' do
|
@@ -586,7 +586,7 @@ describe Octopus::AssociationShardTracking, :shards => [:brazil, :master, :canad
|
|
586
586
|
expect(@comment_brazil.commentable).to eq(new_brazil_client)
|
587
587
|
end
|
588
588
|
|
589
|
-
describe 'it should
|
589
|
+
describe 'it should work when using' do
|
590
590
|
before(:each) do
|
591
591
|
@comment_brazil_2 = Comment.using(:brazil).create!(:name => 'Brazil Comment 2')
|
592
592
|
expect(@brazil_client.comments.to_set).to eq([@comment_brazil].to_set)
|
@@ -690,9 +690,9 @@ describe Octopus::AssociationShardTracking, :shards => [:brazil, :master, :canad
|
|
690
690
|
end
|
691
691
|
|
692
692
|
it 'exists?' do
|
693
|
-
expect(@brazil_client.comments.exists?(@comment_brazil)).to be true
|
693
|
+
expect(@brazil_client.comments.exists?(@comment_brazil.id)).to be true
|
694
694
|
@brazil_client.comments.destroy_all
|
695
|
-
expect(@brazil_client.comments.exists?(@comment_brazil)).to be false
|
695
|
+
expect(@brazil_client.comments.exists?(@comment_brazil.id)).to be false
|
696
696
|
end
|
697
697
|
|
698
698
|
it 'uniq' do
|