switchman 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/app/models/switchman/shard.rb +10 -3
- data/lib/switchman/active_record/association.rb +36 -10
- data/lib/switchman/active_record/base.rb +9 -5
- data/lib/switchman/active_record/calculations.rb +12 -11
- data/lib/switchman/active_record/connection_handler.rb +102 -47
- data/lib/switchman/active_record/finder_methods.rb +1 -1
- data/lib/switchman/active_record/query_methods.rb +50 -25
- data/lib/switchman/active_record/relation.rb +4 -4
- data/lib/switchman/active_record/spawn_methods.rb +76 -29
- data/lib/switchman/connection_pool_proxy.rb +2 -1
- data/lib/switchman/database_server.rb +4 -2
- data/lib/switchman/engine.rb +1 -1
- data/lib/switchman/r_spec_helper.rb +14 -6
- data/lib/switchman/rails.rb +7 -9
- data/lib/switchman/test_helper.rb +4 -3
- data/lib/switchman/version.rb +1 -1
- data/spec/dummy/app/models/appendage.rb +2 -4
- data/spec/dummy/app/models/digit.rb +2 -4
- data/spec/dummy/app/models/feature.rb +0 -2
- data/spec/dummy/app/models/user.rb +0 -2
- data/spec/dummy/config/application.rb +0 -6
- data/spec/dummy/config/environments/development.rb +2 -9
- data/spec/dummy/config/environments/production.rb +2 -3
- data/spec/dummy/config/environments/test.rb +2 -11
- data/spec/dummy/config/routes.rb +2 -2
- data/spec/dummy/log/test.log +9947 -0
- data/spec/dummy/tmp/cache/375/530/shard%2F3200 +1 -0
- data/spec/dummy/tmp/cache/37A/590/shard%2F3214 +1 -0
- data/spec/dummy/tmp/cache/37E/620/shard%2F3182 +0 -0
- data/spec/dummy/tmp/cache/381/650/shard%2F3185 +1 -0
- data/spec/dummy/tmp/cache/382/650/shard%2F3177 +1 -0
- data/spec/dummy/tmp/cache/386/6B0/shard%2F3199 +1 -0
- data/spec/dummy/tmp/cache/3A6/F80/shard%2F13200 +1 -0
- data/spec/dummy/tmp/cache/3B0/080/shard%2F13183 +1 -0
- data/spec/dummy/tmp/cache/3B1/070/shard%2F13166 +1 -0
- data/spec/lib/active_record/association_spec.rb +21 -11
- data/spec/lib/active_record/finder_methods_spec.rb +1 -1
- data/spec/lib/active_record/query_cache_spec.rb +17 -17
- data/spec/lib/active_record/query_methods_spec.rb +6 -5
- data/spec/lib/active_record/relation_spec.rb +1 -1
- data/spec/lib/active_record/spawn_methods_spec.rb +5 -4
- data/spec/lib/connection_pool_proxy_spec.rb +2 -0
- data/spec/lib/database_server_spec.rb +12 -4
- data/spec/lib/shackles_spec.rb +8 -6
- metadata +61 -11
@@ -12,36 +12,59 @@ module Switchman
|
|
12
12
|
# for foreign key transposition
|
13
13
|
# :to_a - a special value that Relation#to_a uses when querying multiple shards to
|
14
14
|
# remove primary keys from conditions that aren't applicable to the current shard
|
15
|
-
|
15
|
+
if ::Rails.version < '4'
|
16
|
+
attr_accessor :shard_value, :shard_source_value
|
17
|
+
else
|
18
|
+
def shard_value
|
19
|
+
@values[:shard]
|
20
|
+
end
|
21
|
+
def shard_source_value
|
22
|
+
@values[:shard_source]
|
23
|
+
end
|
24
|
+
def shard_value=(value)
|
25
|
+
raise ImmutableRelation if @loaded
|
26
|
+
@values[:shard] = value
|
27
|
+
end
|
28
|
+
def shard_source_value=(value)
|
29
|
+
raise ImmutableRelation if @loaded
|
30
|
+
@values[:shard_source] = value
|
31
|
+
end
|
32
|
+
end
|
16
33
|
|
17
34
|
def shard(value, source = :explicit)
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
35
|
+
(::Rails.version < '4' ? clone : spawn).shard!(value, source)
|
36
|
+
end
|
37
|
+
|
38
|
+
def shard!(value, source = :explicit)
|
39
|
+
primary_shard = self.primary_shard
|
40
|
+
self.shard_value = value
|
41
|
+
self.shard_source_value = source
|
42
|
+
if (primary_shard != self.primary_shard || source == :to_a)
|
43
|
+
self.where_values = transpose_predicates(where_values, primary_shard, self.primary_shard, source == :to_a) if !where_values.empty?
|
44
|
+
self.having_values = transpose_predicates(having_values, primary_shard, self.primary_shard, source == :to_a) if !having_values.empty?
|
24
45
|
end
|
25
|
-
|
46
|
+
self
|
26
47
|
end
|
27
48
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
49
|
+
if ::Rails.version < '4'
|
50
|
+
# replace these with versions that call build_where on the
|
51
|
+
# result relation, not the source relation (so build_where
|
52
|
+
# is able to implicitly change the shard_value)
|
53
|
+
def where(opts, *rest)
|
54
|
+
return self if opts.blank?
|
33
55
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
56
|
+
relation = clone
|
57
|
+
relation.where_values += relation.build_where(opts, rest)
|
58
|
+
relation
|
59
|
+
end
|
38
60
|
|
39
|
-
|
40
|
-
|
61
|
+
def having(opts, *rest)
|
62
|
+
return self if opts.blank?
|
41
63
|
|
42
|
-
|
43
|
-
|
44
|
-
|
64
|
+
relation = clone
|
65
|
+
relation.having_values += relation.build_where(opts, rest)
|
66
|
+
relation
|
67
|
+
end
|
45
68
|
end
|
46
69
|
|
47
70
|
def build_where(opts, other = [])
|
@@ -147,6 +170,7 @@ module Switchman
|
|
147
170
|
end
|
148
171
|
|
149
172
|
def sharded_primary_key?(relation, column)
|
173
|
+
return column == 'id' if relation.engine == ::ActiveRecord::Base
|
150
174
|
relation.engine.primary_key == column
|
151
175
|
end
|
152
176
|
|
@@ -165,6 +189,7 @@ module Switchman
|
|
165
189
|
attribute = attribute.relation if attribute.relation.is_a?(Arel::Nodes::TableAlias)
|
166
190
|
[attribute.relation, column]
|
167
191
|
end
|
192
|
+
|
168
193
|
# semi-private
|
169
194
|
public
|
170
195
|
def transpose_predicates(predicates, source_shard, target_shard, remove_nonlocal_primary_keys = false)
|
@@ -194,11 +219,11 @@ module Switchman
|
|
194
219
|
local_ids
|
195
220
|
when Arel::Nodes::BindParam
|
196
221
|
# look for a bind param with a matching column name
|
197
|
-
if
|
198
|
-
column, value =
|
222
|
+
if bind_values && idx = bind_values.find_index{|b| b.is_a?(Array) && b.first.try(:name) == predicate.left.name}
|
223
|
+
column, value = bind_values[idx]
|
199
224
|
local_id = Shard.relative_id_for(value, current_source_shard, target_shard)
|
200
225
|
local_id = [] if remove && local_id > Shard::IDS_PER_SHARD
|
201
|
-
|
226
|
+
bind_values[idx] = [column, local_id]
|
202
227
|
end
|
203
228
|
predicate.right
|
204
229
|
else
|
@@ -13,10 +13,10 @@ module Switchman
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
-
def initialize_with_sharding(
|
17
|
-
initialize_without_sharding(
|
18
|
-
self.shard_value = Shard.current(klass.shard_category)
|
19
|
-
self.shard_source_value = :implicit
|
16
|
+
def initialize_with_sharding(*args)
|
17
|
+
initialize_without_sharding(*args)
|
18
|
+
self.shard_value = Shard.current(klass.try(:shard_category) || :default) unless shard_value
|
19
|
+
self.shard_source_value = :implicit unless shard_source_value
|
20
20
|
end
|
21
21
|
|
22
22
|
def merge(*args)
|
@@ -1,17 +1,13 @@
|
|
1
1
|
module Switchman
|
2
2
|
module ActiveRecord
|
3
3
|
module SpawnMethods
|
4
|
-
def
|
5
|
-
return self unless r
|
6
|
-
return to_a & r if r.is_a?(Array)
|
7
|
-
|
8
|
-
# have to figure out shard stuff *before* conditions are merged
|
4
|
+
def shard_values_for_merge(r)
|
9
5
|
if shard_value != r.shard_value
|
10
|
-
if
|
6
|
+
if r.shard_source_value == :implicit
|
11
7
|
final_shard_value = shard_value
|
12
8
|
final_primary_shard = primary_shard
|
13
9
|
final_shard_source_value = shard_source_value
|
14
|
-
elsif
|
10
|
+
elsif shard_source_value == :implicit
|
15
11
|
final_shard_value = r.shard_value
|
16
12
|
final_primary_shard = r.primary_shard
|
17
13
|
final_shard_source_value = r.shard_source_value
|
@@ -30,7 +26,6 @@ module Switchman
|
|
30
26
|
final_primary_shard = Shard.default
|
31
27
|
else
|
32
28
|
final_shard_value = lhs_shard_value.to_a & rhs_shard_value.to_a
|
33
|
-
return none if final_shard_value.length == 0
|
34
29
|
final_primary_shard = final_shard_value.first
|
35
30
|
final_shard_value = final_shard_value.first if final_shard_value.length == 1
|
36
31
|
end
|
@@ -40,34 +35,86 @@ module Switchman
|
|
40
35
|
shard_source_value == source_value || r.shard_source_value == source_value
|
41
36
|
end
|
42
37
|
raise "unknown shard_source_value" unless final_shard_source_value
|
43
|
-
|
44
|
-
result = super
|
45
|
-
result.shard_source_value = final_shard_source_value
|
46
|
-
return result
|
47
38
|
else
|
48
39
|
# nothing fancy
|
49
|
-
return super
|
50
40
|
end
|
51
41
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
42
|
+
[final_shard_value, final_primary_shard, final_shard_source_value]
|
43
|
+
end
|
44
|
+
|
45
|
+
if ::Rails.version < '4'
|
46
|
+
def merge(r)
|
47
|
+
return self unless r
|
48
|
+
return super unless r.is_a?(Relation)
|
49
|
+
|
50
|
+
# have to figure out shard stuff *before* conditions are merged
|
51
|
+
final_shard_value, final_primary_shard, final_shard_source_value = shard_values_for_merge(r)
|
52
|
+
|
53
|
+
return super unless final_shard_source_value
|
54
|
+
|
55
|
+
if !final_shard_value
|
56
|
+
result = super
|
57
|
+
result.shard_source_value = final_shard_source_value
|
58
|
+
return result
|
59
|
+
end
|
60
|
+
|
61
|
+
return none if final_shard_value == []
|
62
|
+
|
63
|
+
# change the primary shard if necessary before merging
|
64
|
+
result = if primary_shard != final_primary_shard && r.primary_shard != final_primary_shard
|
65
|
+
lhs = shard(final_primary_shard)
|
66
|
+
r = r.shard(final_primary_shard)
|
67
|
+
lhs.merge(r)
|
68
|
+
elsif primary_shard != final_primary_shard
|
69
|
+
lhs = shard(final_primary_shard)
|
70
|
+
lhs.merge(r)
|
71
|
+
elsif r.primary_shard != final_primary_shard
|
72
|
+
r = r.shard(final_primary_shard)
|
73
|
+
super(r)
|
74
|
+
else
|
75
|
+
super
|
76
|
+
end
|
77
|
+
|
78
|
+
result.shard_value = final_shard_value
|
79
|
+
result.shard_source_value = final_shard_source_value
|
80
|
+
|
81
|
+
result
|
65
82
|
end
|
83
|
+
else
|
84
|
+
def merge!(r)
|
85
|
+
# have to figure out shard stuff *before* conditions are merged
|
86
|
+
final_shard_value, final_primary_shard, final_shard_source_value = shard_values_for_merge(r)
|
87
|
+
|
88
|
+
return super unless final_shard_source_value
|
89
|
+
|
90
|
+
if !final_shard_value
|
91
|
+
super
|
92
|
+
self.shard_source_value = final_shard_source_value
|
93
|
+
return self
|
94
|
+
end
|
66
95
|
|
67
|
-
|
68
|
-
result.shard_source_value = final_shard_source_value
|
96
|
+
return none! if final_shard_value == []
|
69
97
|
|
70
|
-
|
98
|
+
# change the primary shard if necessary before merging
|
99
|
+
if primary_shard != final_primary_shard && r.primary_shard != final_primary_shard
|
100
|
+
shard!(final_primary_shard)
|
101
|
+
r = r.shard(final_primary_shard)
|
102
|
+
super(r)
|
103
|
+
elsif primary_shard != final_primary_shard
|
104
|
+
shard!(final_primary_shard)
|
105
|
+
super(r)
|
106
|
+
elsif r.primary_shard != final_primary_shard
|
107
|
+
r = r.shard(final_primary_shard)
|
108
|
+
super(r)
|
109
|
+
else
|
110
|
+
super
|
111
|
+
end
|
112
|
+
|
113
|
+
self.shard_value = final_shard_value
|
114
|
+
self.shard_source_value = final_shard_source_value
|
115
|
+
|
116
|
+
self
|
117
|
+
end
|
71
118
|
end
|
72
119
|
end
|
73
120
|
end
|
@@ -91,7 +91,8 @@ module Switchman
|
|
91
91
|
end
|
92
92
|
end
|
93
93
|
end
|
94
|
-
|
94
|
+
klass = ::Rails.version < '4' ? ::ActiveRecord::Base : ::ActiveRecord::ConnectionAdapters
|
95
|
+
spec = klass::ConnectionSpecification.new(config, "#{config[:adapter]}_connection")
|
95
96
|
# unfortunately the AR code that does this require logic can't really be
|
96
97
|
# called in isolation
|
97
98
|
require "active_record/connection_adapters/#{config[:adapter]}_adapter"
|
@@ -125,7 +125,7 @@ module Switchman
|
|
125
125
|
end
|
126
126
|
|
127
127
|
def create_new_shard(options = {})
|
128
|
-
raise
|
128
|
+
raise NotImplementedError.new("Cannot create new shards when sharding isn't initialized") unless Shard.default.is_a?(Shard)
|
129
129
|
|
130
130
|
db_name = options[:db_name]
|
131
131
|
create_schema = options[:schema]
|
@@ -193,13 +193,15 @@ module Switchman
|
|
193
193
|
::ActiveRecord::Base.connection.execute(stmt)
|
194
194
|
end
|
195
195
|
# have to disconnect and reconnect to the correct db
|
196
|
+
shard.name = db_name
|
196
197
|
if self.shareable? && other_shard
|
197
198
|
other_shard.activate { ::ActiveRecord::Base.connection }
|
198
199
|
else
|
199
200
|
::ActiveRecord::Base.connection_pool.current_pool.disconnect!
|
200
201
|
end
|
202
|
+
else
|
203
|
+
shard.name = db_name
|
201
204
|
end
|
202
|
-
shard.name = db_name
|
203
205
|
old_proc = ::ActiveRecord::Base.connection.raw_connection.set_notice_processor {} if config[:adapter] == 'postgresql'
|
204
206
|
old_verbose = ::ActiveRecord::Migration.verbose
|
205
207
|
::ActiveRecord::Migration.verbose = false
|
data/lib/switchman/engine.rb
CHANGED
@@ -75,7 +75,7 @@ module Switchman
|
|
75
75
|
::ActiveRecord::Associations::Association.send(:include, ActiveRecord::Association)
|
76
76
|
::ActiveRecord::Associations::BelongsToAssociation.send(:include, ActiveRecord::BelongsToAssociation)
|
77
77
|
::ActiveRecord::Associations::CollectionProxy.send(:include, ActiveRecord::CollectionProxy)
|
78
|
-
::ActiveRecord::Associations::Builder::
|
78
|
+
::ActiveRecord::Associations::Builder::CollectionAssociation.send(:include, ActiveRecord::Builder::CollectionAssociation)
|
79
79
|
|
80
80
|
::ActiveRecord::Associations::Preloader::Association.send(:include, ActiveRecord::Preloader::Association)
|
81
81
|
::ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:include, ActiveRecord::AbstractAdapter)
|
@@ -96,9 +96,13 @@ module Switchman
|
|
96
96
|
shards.each do |shard|
|
97
97
|
shard.activate do
|
98
98
|
# this is how AR does it in fixtures.rb
|
99
|
-
::
|
100
|
-
|
101
|
-
|
99
|
+
if ::Rails.version < '4'
|
100
|
+
::ActiveRecord::Base.connection.increment_open_transactions
|
101
|
+
::ActiveRecord::Base.connection.transaction_joinable = false
|
102
|
+
::ActiveRecord::Base.connection.begin_db_transaction
|
103
|
+
else
|
104
|
+
::ActiveRecord::Base.connection.begin_transaction joinable: false
|
105
|
+
end
|
102
106
|
end
|
103
107
|
end
|
104
108
|
end
|
@@ -110,9 +114,13 @@ module Switchman
|
|
110
114
|
shards << @shard1 unless @shard1.database_server == Shard.default.database_server
|
111
115
|
shards.each do |shard|
|
112
116
|
shard.activate do
|
113
|
-
if ::
|
114
|
-
::ActiveRecord::Base.connection.
|
115
|
-
|
117
|
+
if ::Rails.version < '4'
|
118
|
+
if ::ActiveRecord::Base.connection.open_transactions != 0
|
119
|
+
::ActiveRecord::Base.connection.rollback_db_transaction
|
120
|
+
::ActiveRecord::Base.connection.decrement_open_transactions
|
121
|
+
end
|
122
|
+
else
|
123
|
+
::ActiveRecord::Base.connection.rollback_transaction if ::ActiveRecord::Base.connection.transaction_open?
|
116
124
|
end
|
117
125
|
end
|
118
126
|
end
|
data/lib/switchman/rails.rb
CHANGED
@@ -1,21 +1,19 @@
|
|
1
1
|
module Switchman::Rails
|
2
2
|
module ClassMethods
|
3
|
-
def
|
3
|
+
def cache
|
4
4
|
Switchman::Shard.current.database_server.cache_store
|
5
5
|
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.included(klass)
|
9
|
+
klass.extend(ClassMethods)
|
10
|
+
klass.singleton_class.send(:remove_method, :cache)
|
6
11
|
|
7
12
|
# in Rails 4+, the Rails.cache= method was used during bootstrap to set
|
8
13
|
# Rails.cache(_without_sharding) to the value from the config file. but now
|
9
14
|
# that that's done (the bootstrap happened before this module is included
|
10
15
|
# into Rails), we want to make sure no one tries to assign to Rails.cache,
|
11
16
|
# because it would be wrong w.r.t. sharding.
|
12
|
-
|
13
|
-
raise NoMethodError
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.included(klass)
|
18
|
-
klass.extend(ClassMethods)
|
19
|
-
klass.singleton_class.alias_method_chain :cache, :sharding
|
17
|
+
klass.singleton_class.send(:remove_method, :cache=) if ::Rails.version >= '4'
|
20
18
|
end
|
21
19
|
end
|
@@ -5,9 +5,9 @@ module Switchman
|
|
5
5
|
# recreate the default shard (it got buhleted)
|
6
6
|
if Shard.default(true).is_a?(DefaultShard)
|
7
7
|
begin
|
8
|
-
Shard.create!(:
|
8
|
+
Shard.create!(default: true)
|
9
9
|
rescue
|
10
|
-
# database doesn't exist yet, presumably
|
10
|
+
# database doesn't exist yet, presumably cause we're creating it right now
|
11
11
|
[nil, nil]
|
12
12
|
end
|
13
13
|
Shard.default(true)
|
@@ -15,7 +15,8 @@ module Switchman
|
|
15
15
|
|
16
16
|
# can't auto-create a new shard on the default shard's db server if the
|
17
17
|
# default shard is split across multiple db servers
|
18
|
-
if ::ActiveRecord::Base.connection_handler.connection_pools.length
|
18
|
+
if (::Rails.version < '4' ? ::ActiveRecord::Base.connection_handler.connection_pools.length :
|
19
|
+
::ActiveRecord::Base.connection_handler.connection_pool_list.length) > 1
|
19
20
|
server1 = DatabaseServer.create(:config => Shard.default.database_server.config)
|
20
21
|
else
|
21
22
|
server1 = Shard.default.database_server
|
data/lib/switchman/version.rb
CHANGED
@@ -1,13 +1,11 @@
|
|
1
1
|
class Appendage < ActiveRecord::Base
|
2
|
-
attr_accessible :user, :user_id, :value
|
3
|
-
|
4
2
|
belongs_to :user
|
5
3
|
has_many :digits
|
6
4
|
|
7
5
|
has_many :features, :as => :owner
|
8
6
|
|
9
|
-
scope :has_no_value, where(:value => nil)
|
10
|
-
scope :has_value, where("appendages.value IS NOT NULL")
|
7
|
+
scope :has_no_value, -> { where(:value => nil) }
|
8
|
+
scope :has_value, -> { where("appendages.value IS NOT NULL") }
|
11
9
|
|
12
10
|
attr_writer :associated_shards
|
13
11
|
class << self
|
@@ -1,9 +1,7 @@
|
|
1
1
|
class Digit < ActiveRecord::Base
|
2
|
-
attr_accessible :appendage, :appendage_id, :value
|
3
|
-
|
4
2
|
belongs_to :appendage
|
5
3
|
has_one :user, :through => :appendage
|
6
4
|
|
7
|
-
scope :has_no_value, where(:value => nil)
|
8
|
-
scope :has_value, where("digits.value IS NOT NULL")
|
5
|
+
scope :has_no_value, -> { where(:value => nil) }
|
6
|
+
scope :has_value, -> { where("digits.value IS NOT NULL") }
|
9
7
|
end
|