switchman 1.6.1 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/models/switchman/shard_internal.rb +4 -4
- data/lib/switchman/active_record/attribute_methods.rb +2 -2
- data/lib/switchman/active_record/base.rb +15 -5
- data/lib/switchman/active_record/calculations.rb +1 -1
- data/lib/switchman/active_record/connection_handler.rb +86 -55
- data/lib/switchman/active_record/relation.rb +3 -3
- data/lib/switchman/connection_pool_proxy.rb +7 -1
- data/lib/switchman/shackles.rb +1 -1
- data/lib/switchman/standard_error.rb +1 -1
- data/lib/switchman/version.rb +1 -1
- data/lib/tasks/switchman.rake +1 -1
- metadata +38 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8a8e56791eabefc33f16db7b5f9917c547758b19
|
4
|
+
data.tar.gz: 9c5746a17f3b2c08f0a3a5e3d30823966e15017e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f90666ec8cdaea74595ebcdb75c84ae69aae97c4e48997fc7c1779ad3340e3fb0350b24b87e888b0365a27098e64fe0e28ea6895932e62b3f275d18a43abab99
|
7
|
+
data.tar.gz: 6d4f656621629771412e95f1facca1ce1b30023f20247b70cf383a995e636763408dd62db9312ac2356b2c633ddca72502079bef05158c3926133411d01405ec
|
@@ -11,12 +11,12 @@ module Switchman
|
|
11
11
|
CATEGORIES =
|
12
12
|
{
|
13
13
|
# special cased to mean all other models
|
14
|
-
:
|
14
|
+
:primary => nil,
|
15
15
|
# special cased to not allow activating a shard other than the default
|
16
16
|
:unsharded => [Shard]
|
17
17
|
}
|
18
18
|
private_constant :CATEGORIES
|
19
|
-
@shard_category = :unsharded
|
19
|
+
@connection_specification_name = @shard_category = :unsharded
|
20
20
|
|
21
21
|
if defined?(::ProtectedAttributes)
|
22
22
|
attr_accessible :default, :name, :database_server
|
@@ -71,7 +71,7 @@ module Switchman
|
|
71
71
|
@default
|
72
72
|
end
|
73
73
|
|
74
|
-
def current(category = :
|
74
|
+
def current(category = :primary)
|
75
75
|
active_shards[category] || Shard.default
|
76
76
|
end
|
77
77
|
|
@@ -682,7 +682,7 @@ module Switchman
|
|
682
682
|
|
683
683
|
def hashify_categories(categories)
|
684
684
|
if categories.empty?
|
685
|
-
{ :
|
685
|
+
{ :primary => self }
|
686
686
|
else
|
687
687
|
categories.inject({}) { |h, category| h[category] = self; h }
|
688
688
|
end
|
@@ -65,8 +65,8 @@ module Switchman
|
|
65
65
|
if reflection
|
66
66
|
if reflection.options[:polymorphic]
|
67
67
|
# a polymorphic association has to be discovered at runtime. This code ends up being something like
|
68
|
-
# context_type.try(:constantize).try(:shard_category) || :
|
69
|
-
"read_attribute(:#{reflection.foreign_type}).try(:constantize).try(:shard_category) || :
|
68
|
+
# context_type.try(:constantize).try(:shard_category) || :primary
|
69
|
+
"read_attribute(:#{reflection.foreign_type}).try(:constantize).try(:shard_category) || :primary"
|
70
70
|
else
|
71
71
|
# otherwise we can just return a symbol for the statically known type of the association
|
72
72
|
reflection.klass.shard_category.inspect
|
@@ -4,8 +4,14 @@ module Switchman
|
|
4
4
|
module ClassMethods
|
5
5
|
delegate :shard, to: :all
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
if ::Rails.version < '5'
|
8
|
+
def shard_category
|
9
|
+
@shard_category || (self.superclass < ::ActiveRecord::Base && self.superclass.shard_category) || :primary
|
10
|
+
end
|
11
|
+
else
|
12
|
+
def shard_category
|
13
|
+
connection_specification_name.to_sym
|
14
|
+
end
|
9
15
|
end
|
10
16
|
|
11
17
|
def shard_category=(category)
|
@@ -14,11 +20,15 @@ module Switchman
|
|
14
20
|
categories[shard_category].delete(self)
|
15
21
|
categories.delete(shard_category) if categories[shard_category].empty?
|
16
22
|
end
|
17
|
-
connection_handler.uninitialize_ar(self)
|
18
23
|
categories[category] ||= []
|
19
24
|
categories[category] << self
|
20
|
-
|
21
|
-
|
25
|
+
if ::Rails.version < '5'
|
26
|
+
connection_handler.uninitialize_ar(self)
|
27
|
+
@shard_category = category
|
28
|
+
connection_handler.initialize_categories(superclass)
|
29
|
+
else
|
30
|
+
self.connection_specification_name = category.to_s
|
31
|
+
end
|
22
32
|
end
|
23
33
|
|
24
34
|
def integral_id?
|
@@ -76,7 +76,7 @@ module Switchman
|
|
76
76
|
opts = grouped_calculation_options(operation.to_s.downcase, column_name, distinct)
|
77
77
|
|
78
78
|
relation = build_grouped_calculation_relation(opts)
|
79
|
-
target_shard = Shard.current(:
|
79
|
+
target_shard = Shard.current(:primary)
|
80
80
|
|
81
81
|
rows = relation.activate do |rel, shard|
|
82
82
|
calculated_data = klass.connection.select_all(rel)
|
@@ -22,8 +22,9 @@ module Switchman
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
def establish_connection(
|
26
|
-
super
|
25
|
+
def establish_connection(*args)
|
26
|
+
pool = super
|
27
|
+
owner, spec = ::Rails.version < '5' ? args : [nil, args.first]
|
27
28
|
|
28
29
|
# this is the first place that the adapter would have been required; but now we
|
29
30
|
# need this addition ASAP since it will be called when loading the default shard below
|
@@ -32,17 +33,6 @@ module Switchman
|
|
32
33
|
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(ActiveRecord::PostgreSQLAdapter)
|
33
34
|
end
|
34
35
|
|
35
|
-
# AR3 uses the name, AR4 uses the model
|
36
|
-
model = case owner
|
37
|
-
when String
|
38
|
-
owner.constantize
|
39
|
-
when Class
|
40
|
-
owner
|
41
|
-
else
|
42
|
-
raise "unknown owner #{owner}"
|
43
|
-
end
|
44
|
-
pool = owner_to_pool[owner.name]
|
45
|
-
|
46
36
|
first_time = !Shard.instance_variable_get(:@default)
|
47
37
|
if first_time
|
48
38
|
# Have to cache the default shard before we insert sharding, otherwise the first access
|
@@ -55,13 +45,19 @@ module Switchman
|
|
55
45
|
|
56
46
|
::ActiveRecord::Base.configurations[::Rails.env] = spec.instance_variable_get(:@config).stringify_keys
|
57
47
|
end
|
48
|
+
|
58
49
|
@shard_connection_pools ||= { [:master, Shard.default.database_server.shareable? ? ::Rails.env : Shard.default] => pool}
|
59
50
|
|
60
|
-
|
51
|
+
category = ::Rails.version < '5' ? owner.shard_category : pool.spec.name.to_sym
|
52
|
+
proxy = ConnectionPoolProxy.new(category,
|
61
53
|
pool,
|
62
54
|
@shard_connection_pools)
|
63
|
-
|
64
|
-
|
55
|
+
if ::Rails.version < '5'
|
56
|
+
owner_to_pool[owner.name] = proxy
|
57
|
+
class_to_pool.clear
|
58
|
+
else
|
59
|
+
owner_to_pool[pool.spec.name] = proxy
|
60
|
+
end
|
65
61
|
|
66
62
|
if first_time
|
67
63
|
if Shard.default.database_server.config[:prefer_slave]
|
@@ -79,7 +75,7 @@ module Switchman
|
|
79
75
|
# DON'T do it if we're not the current connection handler - that means
|
80
76
|
# we're in the middle of switching environments, and we don't want to
|
81
77
|
# establish a connection with incorrect settings
|
82
|
-
if (
|
78
|
+
if [:primary, :unsharded].include?(category) && self == ::ActiveRecord::Base.connection_handler && !first_time
|
83
79
|
Shard.default(reload: true, with_fallback: true)
|
84
80
|
proxy.disconnect!
|
85
81
|
end
|
@@ -106,50 +102,85 @@ module Switchman
|
|
106
102
|
proxy
|
107
103
|
end
|
108
104
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
def pool_for(owner)
|
117
|
-
# copypasted from AR#ConnectionHandler other than proxy handling
|
105
|
+
if ::Rails.version < '5'
|
106
|
+
def remove_connection(model)
|
107
|
+
uninitialize_ar(model) if owner_to_pool[model.name].is_a?(ConnectionPoolProxy)
|
108
|
+
result = super
|
109
|
+
initialize_categories
|
110
|
+
result
|
111
|
+
end
|
118
112
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
113
|
+
def pool_for(owner)
|
114
|
+
# copypasted from AR#ConnectionHandler other than proxy handling
|
115
|
+
|
116
|
+
owner_to_pool.fetch(owner.name) {
|
117
|
+
if ancestor_pool = pool_from_any_process_for(owner)
|
118
|
+
# A connection was established in an ancestor process that must have
|
119
|
+
# subsequently forked. We can't reuse the connection, but we can copy
|
120
|
+
# the specification and establish a new connection with it.
|
121
|
+
if ancestor_pool.is_a?(ConnectionPoolProxy)
|
122
|
+
establish_connection owner, ancestor_pool.default_pool.spec
|
123
|
+
else
|
124
|
+
establish_connection owner, ancestor_pool.spec
|
125
|
+
end
|
126
126
|
else
|
127
|
-
|
127
|
+
owner_to_pool[owner.name] = nil
|
128
128
|
end
|
129
|
-
|
130
|
-
|
131
|
-
end
|
132
|
-
}
|
133
|
-
end
|
129
|
+
}
|
130
|
+
end
|
134
131
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
132
|
+
def retrieve_connection_pool(klass)
|
133
|
+
class_to_pool[klass.name] ||= begin
|
134
|
+
original_klass = klass
|
135
|
+
until pool = pool_for(klass)
|
136
|
+
klass = klass.superclass
|
137
|
+
break unless klass <= Base
|
138
|
+
end
|
139
|
+
|
140
|
+
if pool.is_a?(ConnectionPoolProxy) && pool.category != original_klass.shard_category
|
141
|
+
default_pool = pool.default_pool
|
142
|
+
pool = nil
|
143
|
+
class_to_pool.each_value { |p| pool = p if p.is_a?(ConnectionPoolProxy) &&
|
144
|
+
p.category == original_klass.shard_category &&
|
145
|
+
p.default_pool == default_pool }
|
146
|
+
pool ||= ConnectionPoolProxy.new(original_klass.shard_category, default_pool, @shard_connection_pools)
|
147
|
+
end
|
142
148
|
|
143
|
-
|
144
|
-
default_pool = pool.default_pool
|
145
|
-
pool = nil
|
146
|
-
class_to_pool.each_value { |p| pool = p if p.is_a?(ConnectionPoolProxy) &&
|
147
|
-
p.category == original_klass.shard_category &&
|
148
|
-
p.default_pool == default_pool }
|
149
|
-
pool ||= ConnectionPoolProxy.new(original_klass.shard_category, default_pool, @shard_connection_pools)
|
149
|
+
class_to_pool[original_klass.name] = pool
|
150
150
|
end
|
151
|
+
end
|
152
|
+
else
|
153
|
+
def remove_connection(spec_name)
|
154
|
+
pool = owner_to_pool[spec_name]
|
155
|
+
owner_to_pool[spec_name] = pool.default_pool if pool.is_a?(ConnectionPoolProxy)
|
156
|
+
super
|
157
|
+
end
|
151
158
|
|
152
|
-
|
159
|
+
def retrieve_connection_pool(spec_name)
|
160
|
+
owner_to_pool.fetch(spec_name) do
|
161
|
+
if ancestor_pool = pool_from_any_process_for(spec_name)
|
162
|
+
# A connection was established in an ancestor process that must have
|
163
|
+
# subsequently forked. We can't reuse the connection, but we can copy
|
164
|
+
# the specification and establish a new connection with it.
|
165
|
+
pool = nil
|
166
|
+
if ancestor_pool.is_a?(ConnectionPoolProxy)
|
167
|
+
pool = establish_connection ancestor_pool.default_pool.spec
|
168
|
+
else
|
169
|
+
pool = establish_connection ancestor_pool.spec
|
170
|
+
end
|
171
|
+
pool.instance_variable_set(:@schema_cache, ancestor_pool.schema_cache) if ancestor_pool.schema_cache
|
172
|
+
pool
|
173
|
+
elsif spec_name != "primary"
|
174
|
+
primary_pool = retrieve_connection_pool("primary")
|
175
|
+
if primary_pool.is_a?(ConnectionPoolProxy)
|
176
|
+
ConnectionPoolProxy.new(spec_name.to_sym, primary_pool.default_pool, @shard_connection_pools)
|
177
|
+
else
|
178
|
+
primary_pool
|
179
|
+
end
|
180
|
+
else
|
181
|
+
owner_to_pool[spec_name] = nil
|
182
|
+
end
|
183
|
+
end
|
153
184
|
end
|
154
185
|
end
|
155
186
|
|
@@ -159,7 +190,7 @@ module Switchman
|
|
159
190
|
end
|
160
191
|
|
161
192
|
def switchman_connection_pool_proxies
|
162
|
-
class_to_pool.values.uniq
|
193
|
+
(::Rails.version < '5' ? class_to_pool : owner_to_pool).values.uniq.select{|p| p.is_a?(ConnectionPoolProxy)}
|
163
194
|
end
|
164
195
|
|
165
196
|
private
|
@@ -7,13 +7,13 @@ module Switchman
|
|
7
7
|
|
8
8
|
def initialize(*args)
|
9
9
|
super
|
10
|
-
self.shard_value = Shard.current(klass ? klass.shard_category : :
|
10
|
+
self.shard_value = Shard.current(klass ? klass.shard_category : :primary) unless shard_value
|
11
11
|
self.shard_source_value = :implicit unless shard_source_value
|
12
12
|
end
|
13
13
|
|
14
14
|
def clone
|
15
15
|
result = super
|
16
|
-
result.shard_value = Shard.current(klass ? klass.shard_category : :
|
16
|
+
result.shard_value = Shard.current(klass ? klass.shard_category : :primary) unless shard_value
|
17
17
|
result
|
18
18
|
end
|
19
19
|
|
@@ -47,7 +47,7 @@ module Switchman
|
|
47
47
|
self.activate { |relation| relation.explain(super_method: true) }
|
48
48
|
end
|
49
49
|
|
50
|
-
to_a_method = ::Rails.version
|
50
|
+
to_a_method = ::Rails.version >= '5' ? :records : :to_a
|
51
51
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
52
52
|
def #{to_a_method}(super_method: false)
|
53
53
|
return super() if super_method
|
@@ -79,6 +79,10 @@ module Switchman
|
|
79
79
|
EOS
|
80
80
|
end
|
81
81
|
|
82
|
+
def automatic_reconnect=(value)
|
83
|
+
@connection_pools.values.each { |pool| pool.automatic_reconnect = value }
|
84
|
+
end
|
85
|
+
|
82
86
|
def clear_idle_connections!(since_when)
|
83
87
|
@connection_pools.values.each { |pool| pool.clear_idle_connections!(since_when) }
|
84
88
|
end
|
@@ -113,7 +117,9 @@ module Switchman
|
|
113
117
|
end
|
114
118
|
end
|
115
119
|
end
|
116
|
-
|
120
|
+
args = [config, "#{config[:adapter]}_connection"]
|
121
|
+
args.unshift(pool_key.join("/")) if ::Rails.version >= '5' # seems as good a name as any
|
122
|
+
spec = ::ActiveRecord::ConnectionAdapters::ConnectionSpecification.new(*args)
|
117
123
|
# unfortunately the AR code that does this require logic can't really be
|
118
124
|
# called in isolation
|
119
125
|
require "active_record/connection_adapters/#{config[:adapter]}_adapter"
|
data/lib/switchman/shackles.rb
CHANGED
data/lib/switchman/version.rb
CHANGED
data/lib/tasks/switchman.rake
CHANGED
@@ -48,7 +48,7 @@ module Switchman
|
|
48
48
|
# task. tasks which modify the schema may want to pass all categories in
|
49
49
|
# so that schema updates for non-default tables happen against all shards.
|
50
50
|
# this is handled automatically for the default migration tasks, below.
|
51
|
-
def self.shardify_task(task_name, categories: [:
|
51
|
+
def self.shardify_task(task_name, categories: [:primary])
|
52
52
|
old_task = ::Rake::Task[task_name]
|
53
53
|
old_actions = old_task.actions.dup
|
54
54
|
old_task.actions.clear
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: switchman
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cody Cutrer
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2016-
|
13
|
+
date: 2016-10-10 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: railties
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
version: '4.0'
|
22
22
|
- - "<="
|
23
23
|
- !ruby/object:Gem::Version
|
24
|
-
version: 5.
|
24
|
+
version: '5.1'
|
25
25
|
type: :runtime
|
26
26
|
prerelease: false
|
27
27
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -31,7 +31,7 @@ dependencies:
|
|
31
31
|
version: '4.0'
|
32
32
|
- - "<="
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: 5.
|
34
|
+
version: '5.1'
|
35
35
|
- !ruby/object:Gem::Dependency
|
36
36
|
name: activerecord
|
37
37
|
requirement: !ruby/object:Gem::Requirement
|
@@ -41,7 +41,7 @@ dependencies:
|
|
41
41
|
version: '4.0'
|
42
42
|
- - "<="
|
43
43
|
- !ruby/object:Gem::Version
|
44
|
-
version: 5.
|
44
|
+
version: '5.1'
|
45
45
|
type: :runtime
|
46
46
|
prerelease: false
|
47
47
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -51,21 +51,21 @@ dependencies:
|
|
51
51
|
version: '4.0'
|
52
52
|
- - "<="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 5.
|
54
|
+
version: '5.1'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: shackles
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 1.
|
61
|
+
version: 1.3.0
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: 1.
|
68
|
+
version: 1.3.0
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: open4
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +80,34 @@ dependencies:
|
|
80
80
|
- - '='
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: 1.3.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: appraisal
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 2.1.0
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 2.1.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: byebug
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
83
111
|
- !ruby/object:Gem::Dependency
|
84
112
|
name: mysql2
|
85
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -142,14 +170,14 @@ dependencies:
|
|
142
170
|
requirements:
|
143
171
|
- - "~>"
|
144
172
|
- !ruby/object:Gem::Version
|
145
|
-
version: '
|
173
|
+
version: '11.3'
|
146
174
|
type: :development
|
147
175
|
prerelease: false
|
148
176
|
version_requirements: !ruby/object:Gem::Requirement
|
149
177
|
requirements:
|
150
178
|
- - "~>"
|
151
179
|
- !ruby/object:Gem::Version
|
152
|
-
version: '
|
180
|
+
version: '11.3'
|
153
181
|
description: Sharding
|
154
182
|
email:
|
155
183
|
- cody@instructure.com
|