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
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
YzRjZGFmYzVmY2EwYjYyNGQ3Nzc0NzYxNjdhOTQ2M2U5ZDU1YmE0Zg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
ZTY2ZDgwNDU2YjFmOWYwMDMyYzgyYzJmOGU5MWIzZTJlNGIzNDVhNg==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
NTVmOWYyY2Y2YWUzMmQ4MWY3NjUzZTFiYThmMmM0OGM3ZDA1YTczNDdmZjFl
|
10
|
+
MjEzYmJhNzBjODc4OGZmYzgwZmEwNDI3NWQ2YWU3Yzg3ODBkNTBmNTYyNThh
|
11
|
+
YTJlOGRjMDI5NzhjYmNhZmQyYTMwNWQ4MGIwOWQ2MDM4ODYwZDU=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
MzY2ODJiM2E3YWZkNjJhYjllODIyNDBkMzQ5NTkyNzhjNjY4Yzc5MDdiY2Iy
|
14
|
+
OGE3ZTA2N2U4YTdlZWFmMWJhZjQzNTU5NzNmYWYyMGIxZTNmOWVhZTc3Mzgw
|
15
|
+
MWE3YjYwNTlkODIzZThkNTlmMDI2NTVhNWNlNTA4Nzc5MTdlNjI=
|
@@ -16,8 +16,6 @@ module Switchman
|
|
16
16
|
private_constant :CATEGORIES
|
17
17
|
@shard_category = :unsharded
|
18
18
|
|
19
|
-
attr_accessible :name, :database_server, :default
|
20
|
-
|
21
19
|
# only allow one default
|
22
20
|
validates_uniqueness_of :default, :if => lambda { |s| s.default? }
|
23
21
|
|
@@ -87,7 +85,11 @@ module Switchman
|
|
87
85
|
nil
|
88
86
|
else
|
89
87
|
shard = Shard.new
|
90
|
-
|
88
|
+
if ::Rails.version < '4'
|
89
|
+
shard.assign_attributes(attributes, :without_protection => true)
|
90
|
+
else
|
91
|
+
shard.assign_attributes(attributes)
|
92
|
+
end
|
91
93
|
shard.instance_variable_set(:@new_record, false)
|
92
94
|
# connection info doesn't exist in database.yml;
|
93
95
|
# pretend the shard doesn't exist either
|
@@ -520,6 +522,11 @@ module Switchman
|
|
520
522
|
super
|
521
523
|
end
|
522
524
|
|
525
|
+
# skip global_id.hash
|
526
|
+
def hash
|
527
|
+
id.hash
|
528
|
+
end
|
529
|
+
|
523
530
|
private
|
524
531
|
|
525
532
|
def clear_cache
|
@@ -2,7 +2,8 @@ module Switchman
|
|
2
2
|
module ActiveRecord
|
3
3
|
module Association
|
4
4
|
def self.included(klass)
|
5
|
-
%w{build_record creation_attributes load_target
|
5
|
+
%w{build_record creation_attributes load_target scope}.each do |method|
|
6
|
+
method = 'scoped' if method == 'scope' && ::Rails.version < '4'
|
6
7
|
klass.alias_method_chain(method, :sharding)
|
7
8
|
end
|
8
9
|
end
|
@@ -24,10 +25,14 @@ module Switchman
|
|
24
25
|
self.shard.activate { load_target_without_sharding }
|
25
26
|
end
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
# scoped is renamed to scope in Rails 4
|
29
|
+
method = ::Rails.version < '4' ? 'scoped' : 'scope'
|
30
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
31
|
+
def #{method}_with_sharding
|
32
|
+
shard_value = @reflection.options[:multishard] ? @owner : self.shard
|
33
|
+
@owner.shard.activate { #{method}_without_sharding.shard(shard_value, :association) }
|
34
|
+
end
|
35
|
+
RUBY
|
31
36
|
|
32
37
|
def creation_attributes_with_sharding
|
33
38
|
attributes = creation_attributes_without_sharding
|
@@ -65,9 +70,17 @@ module Switchman
|
|
65
70
|
end
|
66
71
|
|
67
72
|
module Builder
|
68
|
-
module
|
73
|
+
module CollectionAssociation
|
69
74
|
def self.included(klass)
|
70
|
-
|
75
|
+
if ::Rails.version < '4'
|
76
|
+
[klass] + klass.descendants.each do |k|
|
77
|
+
k.valid_options << :multishard
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def valid_options
|
83
|
+
super + [:multishard]
|
71
84
|
end
|
72
85
|
end
|
73
86
|
end
|
@@ -77,7 +90,11 @@ module Switchman
|
|
77
90
|
def self.included(klass)
|
78
91
|
klass.send(:remove_method, :associated_records_by_owner)
|
79
92
|
klass.send(:remove_method, :owners_by_key)
|
80
|
-
|
93
|
+
if ::Rails.version < '4'
|
94
|
+
klass.send(:remove_method, :scoped)
|
95
|
+
else
|
96
|
+
klass.send(:remove_method, :scope)
|
97
|
+
end
|
81
98
|
end
|
82
99
|
|
83
100
|
def associated_records_by_owner
|
@@ -126,15 +143,24 @@ module Switchman
|
|
126
143
|
end
|
127
144
|
end
|
128
145
|
|
129
|
-
def
|
146
|
+
def scope
|
130
147
|
build_scope
|
131
148
|
end
|
149
|
+
# renamed to just scope in Rails 4
|
150
|
+
if ::Rails.version < '4'
|
151
|
+
alias_method :scoped, :scope
|
152
|
+
remove_method(:scope)
|
153
|
+
end
|
132
154
|
end
|
133
155
|
end
|
134
156
|
|
135
157
|
module CollectionProxy
|
136
158
|
def shard(*args)
|
137
|
-
|
159
|
+
if ::Rails.version < '4'
|
160
|
+
scoped.shard(*args)
|
161
|
+
else
|
162
|
+
scope.shard(*args)
|
163
|
+
end
|
138
164
|
end
|
139
165
|
end
|
140
166
|
end
|
@@ -2,7 +2,11 @@ module Switchman
|
|
2
2
|
module ActiveRecord
|
3
3
|
module Base
|
4
4
|
module ClassMethods
|
5
|
-
|
5
|
+
if ::Rails.version < '4'
|
6
|
+
delegate :shard, to: :scoped
|
7
|
+
else
|
8
|
+
delegate :shard, to: :all
|
9
|
+
end
|
6
10
|
|
7
11
|
def shard_category
|
8
12
|
@shard_category || :default
|
@@ -29,7 +33,7 @@ module Switchman
|
|
29
33
|
end
|
30
34
|
|
31
35
|
def transaction(*args)
|
32
|
-
if current_scope
|
36
|
+
if self != ::ActiveRecord::Base && current_scope
|
33
37
|
current_scope.activate do
|
34
38
|
db = Shard.current(shard_category).database_server
|
35
39
|
if ::Shackles.environment != db.shackles_environment
|
@@ -78,16 +82,16 @@ module Switchman
|
|
78
82
|
|
79
83
|
def save(*args)
|
80
84
|
@shard_set_in_stone = true
|
81
|
-
self.class.
|
85
|
+
self.class.shard(shard).scoping { super }
|
82
86
|
end
|
83
87
|
|
84
88
|
def save!(*args)
|
85
89
|
@shard_set_in_stone = true
|
86
|
-
self.class.
|
90
|
+
self.class.shard(shard).scoping { super }
|
87
91
|
end
|
88
92
|
|
89
93
|
def destroy
|
90
|
-
self.class.
|
94
|
+
self.class.shard(shard).scoping { super }
|
91
95
|
end
|
92
96
|
|
93
97
|
def clone
|
@@ -45,15 +45,15 @@ module Switchman
|
|
45
45
|
relation.select_values = [operation_over_aggregate_column(column, "average", distinct).as("average"),
|
46
46
|
operation_over_aggregate_column(column, "count", distinct).as("count")]
|
47
47
|
|
48
|
-
initial_results = relation.activate{ |rel|
|
48
|
+
initial_results = relation.activate{ |rel| klass.connection.select_all(rel) }
|
49
49
|
if initial_results.is_a?(Array)
|
50
50
|
initial_results.each do |r|
|
51
|
-
r["average"] = type_cast_calculated_value(r["average"],
|
52
|
-
r["count"] = type_cast_calculated_value(r["count"],
|
51
|
+
r["average"] = type_cast_calculated_value(r["average"], column_for(column_name), "average")
|
52
|
+
r["count"] = type_cast_calculated_value(r["count"], column_for(column_name), "count")
|
53
53
|
end
|
54
54
|
result = initial_results.map{|r| r["average"] * r["count"]}.sum / initial_results.map{|r| r["count"]}.sum
|
55
55
|
else
|
56
|
-
result = type_cast_calculated_value(initial_results["average"],
|
56
|
+
result = type_cast_calculated_value(initial_results.first["average"], column_for(column_name), "average")
|
57
57
|
end
|
58
58
|
result
|
59
59
|
end
|
@@ -66,7 +66,7 @@ module Switchman
|
|
66
66
|
target_shard = Shard.current(:default)
|
67
67
|
|
68
68
|
rows = relation.activate do |rel, shard|
|
69
|
-
calculated_data =
|
69
|
+
calculated_data = klass.connection.select_all(rel)
|
70
70
|
|
71
71
|
if opts[:association]
|
72
72
|
key_ids = calculated_data.collect { |row| row[opts[:group_aliases].first] }
|
@@ -98,9 +98,9 @@ module Switchman
|
|
98
98
|
opts = {:operation => operation, :column_name => column_name, :distinct => distinct}
|
99
99
|
|
100
100
|
opts[:aggregate_alias] = aggregate_alias_for(operation, column_name)
|
101
|
-
group_attrs =
|
101
|
+
group_attrs = group_values
|
102
102
|
if group_attrs.first.respond_to?(:to_sym)
|
103
|
-
association =
|
103
|
+
association = klass.reflect_on_association(group_attrs.first.to_sym)
|
104
104
|
associated = group_attrs.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
|
105
105
|
group_fields = Array(associated ? association.foreign_key : group_attrs)
|
106
106
|
else
|
@@ -125,12 +125,12 @@ module Switchman
|
|
125
125
|
elsif operation == 'average'
|
126
126
|
'average'
|
127
127
|
else
|
128
|
-
column_alias_for(operation
|
128
|
+
column_alias_for("#{operation} #{column_name}")
|
129
129
|
end
|
130
130
|
end
|
131
131
|
|
132
132
|
def build_grouped_calculation_relation(opts)
|
133
|
-
group =
|
133
|
+
group = opts[:group_fields]
|
134
134
|
|
135
135
|
select_values = [
|
136
136
|
operation_over_aggregate_column(
|
@@ -146,7 +146,7 @@ module Switchman
|
|
146
146
|
'count', opts[:distinct]).as('count')
|
147
147
|
end
|
148
148
|
|
149
|
-
select_values +=
|
149
|
+
select_values += select_values unless having_values.empty?
|
150
150
|
select_values.concat opts[:group_fields].zip(opts[:group_aliases]).map { |field,aliaz|
|
151
151
|
if field.respond_to?(:as)
|
152
152
|
field.as(aliaz)
|
@@ -155,7 +155,8 @@ module Switchman
|
|
155
155
|
end
|
156
156
|
}
|
157
157
|
|
158
|
-
relation = except(:group)
|
158
|
+
relation = except(:group)
|
159
|
+
relation.group_values = group
|
159
160
|
relation.select_values = select_values
|
160
161
|
relation
|
161
162
|
end
|
@@ -22,10 +22,11 @@ module Switchman
|
|
22
22
|
def self.included(klass)
|
23
23
|
klass.alias_method_chain(:establish_connection, :sharding)
|
24
24
|
klass.alias_method_chain(:remove_connection, :sharding)
|
25
|
+
klass.send(:remove_method, :retrieve_connection_pool) if ::Rails.version >= '4'
|
25
26
|
end
|
26
27
|
|
27
|
-
def establish_connection_with_sharding(
|
28
|
-
establish_connection_without_sharding(
|
28
|
+
def establish_connection_with_sharding(owner, spec)
|
29
|
+
establish_connection_without_sharding(owner, spec)
|
29
30
|
|
30
31
|
# this is the first place that the adapter would have been required; but now we
|
31
32
|
# need this addition ASAP since it will be called when loading the default shard below
|
@@ -34,8 +35,16 @@ module Switchman
|
|
34
35
|
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:include, ActiveRecord::PostgreSQLAdapter)
|
35
36
|
end
|
36
37
|
|
37
|
-
|
38
|
-
|
38
|
+
# AR3 uses the name, AR4 uses the model
|
39
|
+
model = case owner
|
40
|
+
when String
|
41
|
+
owner.constantize
|
42
|
+
when Class
|
43
|
+
owner
|
44
|
+
else
|
45
|
+
raise "unknown owner #{owner}"
|
46
|
+
end
|
47
|
+
pool = ::Rails.version < '4' ? connection_pools[spec] : owner_to_pool[owner.name]
|
39
48
|
|
40
49
|
first_time = !Shard.instance_variable_get(:@default)
|
41
50
|
if first_time
|
@@ -52,7 +61,12 @@ module Switchman
|
|
52
61
|
proxy = ConnectionPoolProxy.new(model.shard_category,
|
53
62
|
pool,
|
54
63
|
@shard_connection_pools)
|
55
|
-
|
64
|
+
if ::Rails.version < '4'
|
65
|
+
connection_pools[spec] = proxy
|
66
|
+
else
|
67
|
+
owner_to_pool[owner.name] = proxy
|
68
|
+
class_to_pool.clear
|
69
|
+
end
|
56
70
|
|
57
71
|
if first_time
|
58
72
|
if Shard.default.database_server.config[:prefer_slave]
|
@@ -65,8 +79,10 @@ module Switchman
|
|
65
79
|
end
|
66
80
|
end
|
67
81
|
|
68
|
-
|
69
|
-
|
82
|
+
if ::Rails.version < '4'
|
83
|
+
initialize_categories(model)
|
84
|
+
class_to_pool[model.name] = proxy
|
85
|
+
end
|
70
86
|
|
71
87
|
# reload the default shard if we just got a new connection
|
72
88
|
# to where the Shards table is
|
@@ -96,61 +112,100 @@ module Switchman
|
|
96
112
|
end
|
97
113
|
|
98
114
|
def remove_connection_with_sharding(model)
|
99
|
-
uninitialize_ar(model) if
|
100
|
-
remove_connection_without_sharding(model)
|
115
|
+
uninitialize_ar(model) if (::Rails.version < '4' ? class_to_pool : owner_to_pool)[model.name].is_a?(ConnectionPoolProxy)
|
116
|
+
result = remove_connection_without_sharding(model)
|
117
|
+
initialize_categories if ::Rails.version >= '4'
|
118
|
+
result
|
119
|
+
end
|
120
|
+
|
121
|
+
if ::Rails.version >= '4'
|
122
|
+
def retrieve_connection_pool(klass)
|
123
|
+
class_to_pool[klass.name] ||= begin
|
124
|
+
original_klass = klass
|
125
|
+
until pool = pool_for(klass)
|
126
|
+
klass = klass.superclass
|
127
|
+
break unless klass <= Base
|
128
|
+
end
|
129
|
+
|
130
|
+
if pool.is_a?(ConnectionPoolProxy) && pool.category != original_klass.shard_category
|
131
|
+
default_pool = pool.default_pool
|
132
|
+
pool = nil
|
133
|
+
class_to_pool.each_value { |p| pool = p if p.is_a?(ConnectionPoolProxy) &&
|
134
|
+
p.category == original_klass.shard_category &&
|
135
|
+
p.default_pool == default_pool }
|
136
|
+
pool ||= ConnectionPoolProxy.new(original_klass.shard_category, default_pool, @shard_connection_pools)
|
137
|
+
end
|
138
|
+
|
139
|
+
class_to_pool[original_klass.name] = pool
|
140
|
+
end
|
141
|
+
end
|
101
142
|
end
|
102
143
|
|
103
144
|
private
|
104
145
|
|
146
|
+
# AR3 only; AR4 defines it, and hides this version,
|
147
|
+
# and it's a slightly different data structure
|
148
|
+
def class_to_pool
|
149
|
+
@class_to_pool
|
150
|
+
end
|
151
|
+
|
105
152
|
def uninitialize_ar(model = ::ActiveRecord::Base)
|
106
153
|
# take the proxies out
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
154
|
+
if ::Rails.version >= '4'
|
155
|
+
owner_to_pool[model.name] = owner_to_pool[model.name].default_pool
|
156
|
+
else
|
157
|
+
class_to_pool.each_key do |model_name|
|
158
|
+
pool_model = model_name.constantize
|
159
|
+
# only de-proxify models that inherit from what we're uninitializing
|
160
|
+
next unless pool_model == model || pool_model < model
|
161
|
+
proxy = class_to_pool[model_name]
|
162
|
+
next unless proxy.is_a?(ConnectionPoolProxy)
|
163
|
+
|
164
|
+
# make sure we're switched back to the default shard for the
|
165
|
+
# connection that will remain
|
166
|
+
if proxy.connected?
|
167
|
+
Shard.default.activate(proxy.category) { proxy.connection }
|
168
|
+
end
|
169
|
+
connection_pools[proxy.spec] = proxy.default_pool
|
170
|
+
class_to_pool[model_name] = proxy.default_pool
|
118
171
|
end
|
119
|
-
connection_pools[proxy.spec] = proxy.default_pool
|
120
|
-
@class_to_pool[model_name] = proxy.default_pool
|
121
|
-
end
|
122
172
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
173
|
+
# prune dups that were created for implementing shard categories
|
174
|
+
class_to_pool.each_key do |model_name|
|
175
|
+
next if model_name == ::ActiveRecord::Base.name
|
176
|
+
pool_model = model_name.constantize
|
177
|
+
class_to_pool.delete(model_name) if retrieve_connection_pool(pool_model.superclass) == class_to_pool[model_name]
|
178
|
+
end
|
128
179
|
end
|
129
180
|
end
|
130
181
|
|
131
182
|
# semi-private
|
132
183
|
public
|
133
184
|
def initialize_categories(model = ::ActiveRecord::Base)
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
185
|
+
if ::Rails.version < '4'
|
186
|
+
# now set up pools for models that inherit from this model, but with a different
|
187
|
+
# sharding category
|
188
|
+
Shard.const_get(:CATEGORIES).each do |category, models|
|
189
|
+
next if category == :default
|
190
|
+
next if category == model.shard_category
|
191
|
+
|
192
|
+
this_proxy = nil
|
193
|
+
Array(models).each do |category_model|
|
194
|
+
category_model = category_model.constantize if category_model.is_a? String
|
195
|
+
next unless category_model < model
|
196
|
+
|
197
|
+
# don't replace existing connections
|
198
|
+
next if class_to_pool[category_model.name]
|
199
|
+
|
200
|
+
default_pool = retrieve_connection_pool(model)
|
201
|
+
default_pool = default_pool.default_pool if default_pool.is_a?(ConnectionPoolProxy)
|
202
|
+
# look for an existing compatible proxy for this category
|
203
|
+
this_proxy ||= ConnectionPoolProxy.new(category_model.shard_category, default_pool, @shard_connection_pools)
|
204
|
+
class_to_pool[category_model.name] = this_proxy
|
205
|
+
end
|
153
206
|
end
|
207
|
+
else
|
208
|
+
class_to_pool.clear
|
154
209
|
end
|
155
210
|
end
|
156
211
|
end
|