switchman 1.6.1 → 1.7.0
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/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
|