switchman 1.14.0 → 1.15.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.rb +718 -11
- data/lib/switchman/active_record/association.rb +19 -11
- data/lib/switchman/active_record/attribute_methods.rb +3 -0
- data/lib/switchman/active_record/base.rb +31 -2
- data/lib/switchman/active_record/calculations.rb +1 -2
- data/lib/switchman/active_record/connection_handler.rb +17 -6
- data/lib/switchman/active_record/connection_pool.rb +17 -3
- data/lib/switchman/active_record/log_subscriber.rb +8 -12
- data/lib/switchman/active_record/model_schema.rb +1 -1
- data/lib/switchman/active_record/postgresql_adapter.rb +13 -27
- data/lib/switchman/active_record/query_cache.rb +17 -107
- data/lib/switchman/active_record/statement_cache.rb +1 -9
- data/lib/switchman/active_support/cache.rb +16 -0
- data/lib/switchman/connection_pool_proxy.rb +27 -11
- data/lib/switchman/database_server.rb +8 -1
- data/lib/switchman/default_shard.rb +1 -0
- data/lib/switchman/engine.rb +7 -6
- data/lib/switchman/r_spec_helper.rb +2 -2
- data/lib/switchman/version.rb +1 -1
- data/lib/tasks/switchman.rake +17 -4
- metadata +6 -7
- data/app/models/switchman/shard_internal.rb +0 -714
|
@@ -30,17 +30,14 @@ module Switchman
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
module CollectionAssociation
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Shard.with_each_shard(shards, [klass.shard_category, owner.class.shard_category].uniq) do
|
|
40
|
-
super
|
|
41
|
-
end
|
|
33
|
+
def find_target
|
|
34
|
+
shards = reflection.options[:multishard] && owner.respond_to?(:associated_shards) ? owner.associated_shards : [shard]
|
|
35
|
+
# activate both the owner and the target's shard category, so that Reflection#join_id_for,
|
|
36
|
+
# when called for the owner, will be returned relative to shard the query will execute on
|
|
37
|
+
Shard.with_each_shard(shards, [klass.shard_category, owner.class.shard_category].uniq) do
|
|
38
|
+
super
|
|
42
39
|
end
|
|
43
|
-
|
|
40
|
+
end
|
|
44
41
|
end
|
|
45
42
|
|
|
46
43
|
module BelongsToAssociation
|
|
@@ -88,7 +85,11 @@ module Switchman
|
|
|
88
85
|
# Copypasta from Activerecord but with added global_id_for goodness.
|
|
89
86
|
def records_for(ids)
|
|
90
87
|
scope.where(association_key_name => ids).load do |record|
|
|
91
|
-
global_key =
|
|
88
|
+
global_key = if record.class.shard_category == :unsharded
|
|
89
|
+
convert_key(record[association_key_name])
|
|
90
|
+
else
|
|
91
|
+
Shard.global_id_for(record[association_key_name], record.shard)
|
|
92
|
+
end
|
|
92
93
|
owner = owners_by_key[global_key.to_s].first
|
|
93
94
|
association = owner.association(reflection.name)
|
|
94
95
|
association.set_inverse_instance(record)
|
|
@@ -198,6 +199,13 @@ module Switchman
|
|
|
198
199
|
(record.has_attribute?(reflection.foreign_key) && record.send(reflection.foreign_key) != key) || # have to use send instead of [] because sharding
|
|
199
200
|
record.attribute_changed?(reflection.foreign_key)
|
|
200
201
|
end
|
|
202
|
+
|
|
203
|
+
def save_belongs_to_association(reflection)
|
|
204
|
+
# this seems counter-intuitive, but the autosave code will assign to attribute bypassing switchman,
|
|
205
|
+
# after reading the id attribute _without_ bypassing switchman. So we need Shard.current for the
|
|
206
|
+
# category of the associated record to match Shard.current for the category of self
|
|
207
|
+
shard.activate(shard_category_for_reflection(reflection)) { super }
|
|
208
|
+
end
|
|
201
209
|
end
|
|
202
210
|
end
|
|
203
211
|
end
|
|
@@ -61,6 +61,9 @@ module Switchman
|
|
|
61
61
|
end
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
# see also Base#shard_category_for_reflection
|
|
65
|
+
# the difference being this will output static strings for the common cases, making them
|
|
66
|
+
# more performant
|
|
64
67
|
def shard_category_code_for_reflection(reflection)
|
|
65
68
|
if reflection
|
|
66
69
|
if reflection.options[:polymorphic]
|
|
@@ -68,6 +68,14 @@ module Switchman
|
|
|
68
68
|
result
|
|
69
69
|
end
|
|
70
70
|
end
|
|
71
|
+
|
|
72
|
+
def clear_query_caches_for_current_thread
|
|
73
|
+
::ActiveRecord::Base.connection_handlers.each_value do |handler|
|
|
74
|
+
handler.connection_pool_list.each do |pool|
|
|
75
|
+
pool.connection(switch_shard: false).clear_query_cache if pool.active_connection?
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
71
79
|
end
|
|
72
80
|
|
|
73
81
|
def self.included(klass)
|
|
@@ -99,12 +107,12 @@ module Switchman
|
|
|
99
107
|
|
|
100
108
|
def save(*args)
|
|
101
109
|
@shard_set_in_stone = true
|
|
102
|
-
self.class.shard(shard, :implicit).scoping { super }
|
|
110
|
+
(self.class.current_scope || self.class.default_scoped).shard(shard, :implicit).scoping { super }
|
|
103
111
|
end
|
|
104
112
|
|
|
105
113
|
def save!(*args)
|
|
106
114
|
@shard_set_in_stone = true
|
|
107
|
-
self.class.shard(shard, :implicit).scoping { super }
|
|
115
|
+
(self.class.current_scope || self.class.default_scoped).shard(shard, :implicit).scoping { super }
|
|
108
116
|
end
|
|
109
117
|
|
|
110
118
|
def destroy
|
|
@@ -146,6 +154,27 @@ module Switchman
|
|
|
146
154
|
# do this the Rails 4.2 way, so that if Shard.current != self.shard, the id gets transposed
|
|
147
155
|
self.class.connection.quote(id)
|
|
148
156
|
end
|
|
157
|
+
|
|
158
|
+
protected
|
|
159
|
+
|
|
160
|
+
# see also AttributeMethods#shard_category_code_for_reflection
|
|
161
|
+
def shard_category_for_reflection(reflection)
|
|
162
|
+
if reflection
|
|
163
|
+
if reflection.options[:polymorphic]
|
|
164
|
+
begin
|
|
165
|
+
read_attribute(reflection.foreign_type)&.constantize&.shard_category || :primary
|
|
166
|
+
rescue NameError
|
|
167
|
+
# in case someone is abusing foreign_type to not point to an actual class
|
|
168
|
+
:primary
|
|
169
|
+
end
|
|
170
|
+
else
|
|
171
|
+
# otherwise we can just return a symbol for the statically known type of the association
|
|
172
|
+
reflection.klass.shard_category
|
|
173
|
+
end
|
|
174
|
+
else
|
|
175
|
+
shard_category
|
|
176
|
+
end
|
|
177
|
+
end
|
|
149
178
|
end
|
|
150
179
|
end
|
|
151
180
|
end
|
|
@@ -121,9 +121,8 @@ module Switchman
|
|
|
121
121
|
group_fields = group_attrs
|
|
122
122
|
end
|
|
123
123
|
|
|
124
|
-
# .clone below corrects for what I consider a Rails bug. column_alias_for modifies the string in place.
|
|
125
124
|
# to_s is because Rails 5 returns a string but Rails 6 returns a symbol.
|
|
126
|
-
group_aliases = group_fields.map { |field| column_alias_for(field.
|
|
125
|
+
group_aliases = group_fields.map { |field| column_alias_for(field.downcase.to_s).to_s }
|
|
127
126
|
group_columns = group_aliases.zip(group_fields).map { |aliaz, field|
|
|
128
127
|
[aliaz, type_for(field), column_name_for(field)]
|
|
129
128
|
}
|
|
@@ -24,6 +24,10 @@ module Switchman
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def establish_connection(spec)
|
|
27
|
+
# Just skip establishing a sharded connection if sharding isn't loaded; we'll do it again later
|
|
28
|
+
# This only can happen when loading ActiveRecord::Base; after everything is loaded Shard will
|
|
29
|
+
# be defined and this will actually establish a connection
|
|
30
|
+
return unless defined?(Shard)
|
|
27
31
|
pool = super
|
|
28
32
|
|
|
29
33
|
# this is the first place that the adapter would have been required; but now we
|
|
@@ -39,16 +43,24 @@ module Switchman
|
|
|
39
43
|
# to sharding will recurse onto itself trying to access column information
|
|
40
44
|
Shard.default
|
|
41
45
|
|
|
46
|
+
config = pool.spec.config
|
|
42
47
|
# automatically change config to allow for sharing connections with simple config
|
|
43
|
-
config = ::Rails.version < '5.1' ? spec.config : pool.spec.config
|
|
44
48
|
ConnectionHandler.make_sharing_automagic(config)
|
|
45
49
|
ConnectionHandler.make_sharing_automagic(Shard.default.database_server.config)
|
|
46
50
|
|
|
47
|
-
if ::Rails.version < '
|
|
48
|
-
::ActiveRecord::Base.configurations[::Rails.env] = spec.instance_variable_get(:@config).stringify_keys
|
|
49
|
-
else
|
|
51
|
+
if ::Rails.version < '6.0'
|
|
50
52
|
::ActiveRecord::Base.configurations[::Rails.env] = config.stringify_keys
|
|
53
|
+
else
|
|
54
|
+
# Adopted from the deprecated code that currently lives in rails proper
|
|
55
|
+
remaining_configs = ::ActiveRecord::Base.configurations.configurations.reject { |db_config| db_config.env_name == ::Rails.env }
|
|
56
|
+
new_config = ::ActiveRecord::DatabaseConfigurations.new(::Rails.env => config.stringify_keys).configurations
|
|
57
|
+
new_configs = remaining_configs + new_config
|
|
58
|
+
|
|
59
|
+
::ActiveRecord::Base.configurations = new_configs
|
|
51
60
|
end
|
|
61
|
+
else
|
|
62
|
+
# this is probably wrong now
|
|
63
|
+
Shard.default.remove_instance_variable(:@name) if Shard.default.instance_variable_defined?(:@name)
|
|
52
64
|
end
|
|
53
65
|
|
|
54
66
|
@shard_connection_pools ||= { [:master, Shard.default.database_server.shareable? ? ::Rails.env : Shard.default] => pool}
|
|
@@ -122,8 +134,7 @@ module Switchman
|
|
|
122
134
|
else
|
|
123
135
|
ancestor_pool.spec
|
|
124
136
|
end
|
|
125
|
-
|
|
126
|
-
pool = establish_connection spec
|
|
137
|
+
pool = establish_connection(spec.to_hash)
|
|
127
138
|
pool.instance_variable_set(:@schema_cache, ancestor_pool.schema_cache) if ancestor_pool.schema_cache
|
|
128
139
|
pool
|
|
129
140
|
elsif spec_name != "primary"
|
|
@@ -32,10 +32,10 @@ module Switchman
|
|
|
32
32
|
conn
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
def connection
|
|
36
|
-
conn = super
|
|
35
|
+
def connection(switch_shard: true)
|
|
36
|
+
conn = super()
|
|
37
37
|
raise NonExistentShardError if shard.new_record?
|
|
38
|
-
switch_database(conn) if conn.shard != self.shard
|
|
38
|
+
switch_database(conn) if conn.shard != self.shard && switch_shard
|
|
39
39
|
conn
|
|
40
40
|
end
|
|
41
41
|
|
|
@@ -47,6 +47,20 @@ module Switchman
|
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
def remove_shard!(shard)
|
|
51
|
+
synchronize do
|
|
52
|
+
# The shard might be currently active, so we need to update our own shard
|
|
53
|
+
if self.shard == shard
|
|
54
|
+
self.shard = Shard.default
|
|
55
|
+
end
|
|
56
|
+
# Update out any connections that may be using this shard
|
|
57
|
+
@connections.each do |conn|
|
|
58
|
+
# This will also update the connection's shard to the default shard
|
|
59
|
+
switch_database(conn) if conn.shard == shard
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
50
64
|
def clear_idle_connections!(since_when)
|
|
51
65
|
synchronize do
|
|
52
66
|
@connections.reject! do |conn|
|
|
@@ -18,18 +18,14 @@ module Switchman
|
|
|
18
18
|
shard = " [#{shard[:database_server_id]}:#{shard[:id]} #{shard[:env]}]" if shard
|
|
19
19
|
|
|
20
20
|
unless (payload[:binds] || []).empty?
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
|
|
30
|
-
render_bind(attr, value)
|
|
31
|
-
}.inspect
|
|
32
|
-
end
|
|
21
|
+
use_old_format = (::Rails.version < '5.1.5')
|
|
22
|
+
args = use_old_format ?
|
|
23
|
+
[payload[:binds], payload[:type_casted_binds]] :
|
|
24
|
+
[payload[:type_casted_binds]]
|
|
25
|
+
casted_params = type_casted_binds(*args)
|
|
26
|
+
binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
|
|
27
|
+
render_bind(attr, value)
|
|
28
|
+
}.inspect
|
|
33
29
|
end
|
|
34
30
|
|
|
35
31
|
name = colorize_payload_name(name, payload[:name])
|
|
@@ -4,7 +4,7 @@ module Switchman
|
|
|
4
4
|
module ClassMethods
|
|
5
5
|
def quoted_table_name
|
|
6
6
|
@quoted_table_name ||= {}
|
|
7
|
-
@quoted_table_name[Shard.current.id] ||= connection.quote_table_name(table_name)
|
|
7
|
+
@quoted_table_name[Shard.current(shard_category).id] ||= connection.quote_table_name(table_name)
|
|
8
8
|
end
|
|
9
9
|
end
|
|
10
10
|
end
|
|
@@ -52,31 +52,12 @@ module Switchman
|
|
|
52
52
|
SQL
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
name.instance_variable_set(:@schema, shard.name)
|
|
60
|
-
end
|
|
61
|
-
[name.schema, name.identifier]
|
|
62
|
-
end
|
|
63
|
-
else
|
|
64
|
-
def data_source_exists?(name)
|
|
65
|
-
name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(name.to_s)
|
|
66
|
-
return false unless name.identifier
|
|
67
|
-
if !name.schema && use_qualified_names?
|
|
68
|
-
name.instance_variable_set(:@schema, shard.name)
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
|
|
72
|
-
SELECT COUNT(*)
|
|
73
|
-
FROM pg_class c
|
|
74
|
-
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
75
|
-
WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
|
|
76
|
-
AND c.relname = '#{name.identifier}'
|
|
77
|
-
AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
|
|
78
|
-
SQL
|
|
55
|
+
def extract_schema_qualified_name(string)
|
|
56
|
+
name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(string.to_s)
|
|
57
|
+
if string && !name.schema && use_qualified_names?
|
|
58
|
+
name.instance_variable_set(:@schema, shard.name)
|
|
79
59
|
end
|
|
60
|
+
[name.schema, name.identifier]
|
|
80
61
|
end
|
|
81
62
|
|
|
82
63
|
def view_exists?(name)
|
|
@@ -175,11 +156,12 @@ module Switchman
|
|
|
175
156
|
name.quoted
|
|
176
157
|
end
|
|
177
158
|
|
|
178
|
-
def with_local_table_name
|
|
179
|
-
|
|
159
|
+
def with_local_table_name(enable = true)
|
|
160
|
+
old_value = @use_local_table_name
|
|
161
|
+
@use_local_table_name = enable
|
|
180
162
|
yield
|
|
181
163
|
ensure
|
|
182
|
-
@use_local_table_name =
|
|
164
|
+
@use_local_table_name = old_value
|
|
183
165
|
end
|
|
184
166
|
|
|
185
167
|
def foreign_keys(table_name)
|
|
@@ -248,6 +230,10 @@ module Switchman
|
|
|
248
230
|
|
|
249
231
|
execute "ALTER INDEX #{quote_table_name(old_name)} RENAME TO #{quote_local_table_name(new_name)}"
|
|
250
232
|
end
|
|
233
|
+
|
|
234
|
+
def columns(*)
|
|
235
|
+
with_local_table_name(false) { super }
|
|
236
|
+
end
|
|
251
237
|
end
|
|
252
238
|
end
|
|
253
239
|
end
|
|
@@ -1,122 +1,32 @@
|
|
|
1
1
|
module Switchman
|
|
2
2
|
module ActiveRecord
|
|
3
3
|
module QueryCache
|
|
4
|
-
if ::Rails.version < '5.0.1'
|
|
5
|
-
# thread local accessors to replace @query_cache_enabled
|
|
6
|
-
def query_cache
|
|
7
|
-
thread_cache = Thread.current[:query_cache] ||= {}
|
|
8
|
-
thread_cache[self.object_id] ||= Hash.new { |h,sql| h[sql] = {} }
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def query_cache_enabled
|
|
12
|
-
Thread.current[:query_cache_enabled]
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def query_cache_enabled=(value)
|
|
16
|
-
Thread.current[:query_cache_enabled] = value
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
# basically wholesale repeat of the methods from the original (see
|
|
20
|
-
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb),
|
|
21
|
-
# but with self.query_cache_enabled and self.query_cache_enabled= instead
|
|
22
|
-
# of @query_cache_enabled.
|
|
23
|
-
|
|
24
|
-
def enable_query_cache!
|
|
25
|
-
self.query_cache_enabled = true
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def disable_query_cache!
|
|
29
|
-
self.query_cache_enabled = false
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def cache
|
|
33
|
-
old, self.query_cache_enabled = query_cache_enabled, true
|
|
34
|
-
yield
|
|
35
|
-
ensure
|
|
36
|
-
self.query_cache_enabled = old
|
|
37
|
-
clear_query_cache unless self.query_cache_enabled
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def uncached
|
|
41
|
-
old, self.query_cache_enabled = query_cache_enabled, false
|
|
42
|
-
yield
|
|
43
|
-
ensure
|
|
44
|
-
self.query_cache_enabled = old
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def clear_query_cache
|
|
48
|
-
Thread.current[:query_cache]&.clear
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def select_all(arel, name = nil, binds = [], preparable: nil)
|
|
52
|
-
if self.query_cache_enabled && !locked?(arel)
|
|
53
|
-
arel, binds = binds_from_relation(arel, binds)
|
|
54
|
-
sql = to_sql(arel, binds)
|
|
55
|
-
cache_sql(sql, binds) { super(sql, name, binds, preparable: preparable) }
|
|
56
|
-
else
|
|
57
|
-
super
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# no reason to define these on the including class directly. the super
|
|
62
|
-
# works just as well from a method on the included module
|
|
63
|
-
[:insert, :update, :delete].each do |method_name|
|
|
64
|
-
class_eval <<-end_code, __FILE__, __LINE__ + 1
|
|
65
|
-
def #{method_name}(*args)
|
|
66
|
-
clear_query_cache if self.query_cache_enabled
|
|
67
|
-
super
|
|
68
|
-
end
|
|
69
|
-
end_code
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
4
|
|
|
73
5
|
private
|
|
74
6
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
7
|
+
def cache_sql(sql, name, binds)
|
|
8
|
+
# have to include the shard id in the cache key because of switching dbs on the same connection
|
|
9
|
+
sql = "#{self.shard.id}::#{sql}"
|
|
10
|
+
@lock.synchronize do
|
|
79
11
|
result =
|
|
80
12
|
if query_cache[sql].key?(binds)
|
|
81
|
-
args = {
|
|
82
|
-
|
|
83
|
-
|
|
13
|
+
args = {
|
|
14
|
+
sql: sql,
|
|
15
|
+
binds: binds,
|
|
16
|
+
name: name,
|
|
17
|
+
connection_id: object_id,
|
|
18
|
+
cached: true
|
|
19
|
+
}
|
|
20
|
+
args[:type_casted_binds] = -> { type_casted_binds(binds) } if ::Rails.version >= '5.1.5'
|
|
21
|
+
::ActiveSupport::Notifications.instrument(
|
|
22
|
+
"sql.active_record",
|
|
23
|
+
args
|
|
24
|
+
)
|
|
84
25
|
query_cache[sql][binds]
|
|
85
26
|
else
|
|
86
27
|
query_cache[sql][binds] = yield
|
|
87
28
|
end
|
|
88
|
-
|
|
89
|
-
if ::ActiveRecord::Result === result
|
|
90
|
-
result.dup
|
|
91
|
-
else
|
|
92
|
-
result.collect { |row| row.dup }
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
else
|
|
96
|
-
def cache_sql(sql, name, binds)
|
|
97
|
-
# have to include the shard id in the cache key because of switching dbs on the same connection
|
|
98
|
-
sql = "#{self.shard.id}::#{sql}"
|
|
99
|
-
@lock.synchronize do
|
|
100
|
-
result =
|
|
101
|
-
if query_cache[sql].key?(binds)
|
|
102
|
-
args = {
|
|
103
|
-
sql: sql,
|
|
104
|
-
binds: binds,
|
|
105
|
-
name: name,
|
|
106
|
-
connection_id: object_id,
|
|
107
|
-
cached: true
|
|
108
|
-
}
|
|
109
|
-
args[:type_casted_binds] = -> { type_casted_binds(binds) } if ::Rails.version >= '5.1.5'
|
|
110
|
-
::ActiveSupport::Notifications.instrument(
|
|
111
|
-
"sql.active_record",
|
|
112
|
-
args
|
|
113
|
-
)
|
|
114
|
-
query_cache[sql][binds]
|
|
115
|
-
else
|
|
116
|
-
query_cache[sql][binds] = yield
|
|
117
|
-
end
|
|
118
|
-
result.dup
|
|
119
|
-
end
|
|
29
|
+
result.dup
|
|
120
30
|
end
|
|
121
31
|
end
|
|
122
32
|
end
|
|
@@ -56,15 +56,7 @@ module Switchman
|
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
-
if ::Rails.version < '5.
|
|
60
|
-
def generic_query_builder(connection)
|
|
61
|
-
@query_builder ||= connection.cacheable_query(@arel)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def qualified_query_builder(shard, klass)
|
|
65
|
-
@qualified_query_builders[shard.id] ||= klass.connection.cacheable_query(@arel)
|
|
66
|
-
end
|
|
67
|
-
elsif ::Rails.version < '5.2'
|
|
59
|
+
if ::Rails.version < '5.2'
|
|
68
60
|
def generic_query_builder(connection)
|
|
69
61
|
@query_builder ||= connection.cacheable_query(self.class, @arel)
|
|
70
62
|
end
|
|
@@ -4,10 +4,26 @@ module Switchman
|
|
|
4
4
|
module ClassMethods
|
|
5
5
|
def lookup_store(*store_options)
|
|
6
6
|
store = super
|
|
7
|
+
# can't use defined?, because it's a _ruby_ autoloaded constant,
|
|
8
|
+
# so just checking that will cause it to get required
|
|
9
|
+
if store.class.name == "ActiveSupport::Cache::RedisCacheStore" && !::ActiveSupport::Cache::RedisCacheStore.ancestors.include?(RedisCacheStore)
|
|
10
|
+
::ActiveSupport::Cache::RedisCacheStore.prepend(RedisCacheStore)
|
|
11
|
+
end
|
|
7
12
|
store.options[:namespace] ||= lambda { Shard.current.default? ? nil : "shard_#{Shard.current.id}" }
|
|
8
13
|
store
|
|
9
14
|
end
|
|
10
15
|
end
|
|
16
|
+
|
|
17
|
+
module RedisCacheStore
|
|
18
|
+
def clear(options = {})
|
|
19
|
+
# RedisCacheStore tries to be smart and only clear the cache under your namespace, if you have one set
|
|
20
|
+
# unfortunately, it uses the keys command, which is extraordinarily inefficient in a large redis instance
|
|
21
|
+
# fortunately, we can assume we control the entire instance, because we set up the namespacing, so just
|
|
22
|
+
# always unset it temporarily for clear calls
|
|
23
|
+
options[:namespace] = nil
|
|
24
|
+
super
|
|
25
|
+
end
|
|
26
|
+
end
|
|
11
27
|
end
|
|
12
28
|
end
|
|
13
29
|
end
|
|
@@ -23,7 +23,14 @@ module Switchman
|
|
|
23
23
|
@category = category
|
|
24
24
|
@default_pool = default_pool
|
|
25
25
|
@connection_pools = shard_connection_pools
|
|
26
|
-
@schema_cache =
|
|
26
|
+
@schema_cache = default_pool.get_schema_cache(nil) if ::Rails.version >= '6'
|
|
27
|
+
@schema_cache = SchemaCache.new(self) unless @schema_cache.is_a?(SchemaCache)
|
|
28
|
+
if ::Rails.version >= '6'
|
|
29
|
+
@default_pool.set_schema_cache(@schema_cache)
|
|
30
|
+
@connection_pools.each_value do |pool|
|
|
31
|
+
pool.set_schema_cache(@schema_cache)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
27
34
|
end
|
|
28
35
|
|
|
29
36
|
def active_shard
|
|
@@ -46,11 +53,11 @@ module Switchman
|
|
|
46
53
|
connection_pools.map(&:connections).inject([], &:+)
|
|
47
54
|
end
|
|
48
55
|
|
|
49
|
-
def connection
|
|
56
|
+
def connection(switch_shard: true)
|
|
50
57
|
pool = current_pool
|
|
51
58
|
begin
|
|
52
|
-
connection = pool.connection
|
|
53
|
-
connection.instance_variable_set(:@schema_cache, @schema_cache)
|
|
59
|
+
connection = pool.connection(switch_shard: switch_shard)
|
|
60
|
+
connection.instance_variable_set(:@schema_cache, @schema_cache) unless ::Rails.version >= '6'
|
|
54
61
|
connection
|
|
55
62
|
rescue ConnectionError
|
|
56
63
|
raise if active_shard.database_server == Shard.default.database_server && active_shackles_environment == :master
|
|
@@ -60,7 +67,7 @@ module Switchman
|
|
|
60
67
|
pool = create_pool(config.dup)
|
|
61
68
|
begin
|
|
62
69
|
connection = pool.connection
|
|
63
|
-
connection.instance_variable_set(:@schema_cache, @schema_cache)
|
|
70
|
+
connection.instance_variable_set(:@schema_cache, @schema_cache) unless ::Rails.version >= '6'
|
|
64
71
|
rescue ConnectionError
|
|
65
72
|
raise if idx == configs.length - 1
|
|
66
73
|
next
|
|
@@ -71,6 +78,10 @@ module Switchman
|
|
|
71
78
|
end
|
|
72
79
|
end
|
|
73
80
|
|
|
81
|
+
def get_schema_cache(_connection)
|
|
82
|
+
@schema_cache
|
|
83
|
+
end
|
|
84
|
+
|
|
74
85
|
%w{release_connection
|
|
75
86
|
disconnect!
|
|
76
87
|
flush!
|
|
@@ -98,6 +109,10 @@ module Switchman
|
|
|
98
109
|
connection_pools.each { |pool| pool.clear_idle_connections!(since_when) }
|
|
99
110
|
end
|
|
100
111
|
|
|
112
|
+
def remove_shard!(shard)
|
|
113
|
+
connection_pools.each { |pool| pool.remove_shard!(shard) }
|
|
114
|
+
end
|
|
115
|
+
|
|
101
116
|
protected
|
|
102
117
|
|
|
103
118
|
def connection_pools
|
|
@@ -132,18 +147,19 @@ module Switchman
|
|
|
132
147
|
end
|
|
133
148
|
end
|
|
134
149
|
end
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
150
|
+
spec = ::ActiveRecord::ConnectionAdapters::ConnectionSpecification.new(
|
|
151
|
+
category,
|
|
152
|
+
config,
|
|
153
|
+
"#{config[:adapter]}_connection"
|
|
154
|
+
)
|
|
138
155
|
# unfortunately the AR code that does this require logic can't really be
|
|
139
156
|
# called in isolation
|
|
140
157
|
require "active_record/connection_adapters/#{config[:adapter]}_adapter"
|
|
141
158
|
|
|
142
159
|
::ActiveRecord::ConnectionAdapters::ConnectionPool.new(spec).tap do |pool|
|
|
143
160
|
pool.shard = shard
|
|
144
|
-
if ::Rails.version >= '
|
|
145
|
-
|
|
146
|
-
end
|
|
161
|
+
pool.set_schema_cache(@schema_cache) if ::Rails.version >= '6'
|
|
162
|
+
pool.enable_query_cache! if !@connection_pools.empty? && @connection_pools.first.last.query_cache_enabled
|
|
147
163
|
end
|
|
148
164
|
end
|
|
149
165
|
end
|
|
@@ -185,6 +185,7 @@ module Switchman
|
|
|
185
185
|
shard = Shard.create!(:id => shard_id,
|
|
186
186
|
:name => name,
|
|
187
187
|
:database_server => self)
|
|
188
|
+
schema_already_existed = false
|
|
188
189
|
|
|
189
190
|
begin
|
|
190
191
|
self.class.creating_new_shard = true
|
|
@@ -192,6 +193,10 @@ module Switchman
|
|
|
192
193
|
::Shackles.activate(:deploy) do
|
|
193
194
|
begin
|
|
194
195
|
if create_statement
|
|
196
|
+
if (::ActiveRecord::Base.connection.select_value("SELECT 1 FROM pg_namespace WHERE nspname=#{::ActiveRecord::Base.connection.quote(name)}"))
|
|
197
|
+
schema_already_existed = true
|
|
198
|
+
raise "This schema already exists; cannot overwrite"
|
|
199
|
+
end
|
|
195
200
|
Array(create_statement.call).each do |stmt|
|
|
196
201
|
::ActiveRecord::Base.connection.execute(stmt)
|
|
197
202
|
end
|
|
@@ -229,7 +234,9 @@ module Switchman
|
|
|
229
234
|
shard
|
|
230
235
|
rescue
|
|
231
236
|
shard.destroy
|
|
232
|
-
|
|
237
|
+
unless schema_already_existed
|
|
238
|
+
shard.drop_database rescue nil
|
|
239
|
+
end
|
|
233
240
|
reset_column_information unless create_schema == false rescue nil
|
|
234
241
|
raise
|
|
235
242
|
ensure
|
data/lib/switchman/engine.rb
CHANGED
|
@@ -88,7 +88,7 @@ module Switchman
|
|
|
88
88
|
require "switchman/call_super"
|
|
89
89
|
require "switchman/rails"
|
|
90
90
|
require "switchman/shackles/relation"
|
|
91
|
-
require_dependency "switchman/
|
|
91
|
+
require_dependency "switchman/shard"
|
|
92
92
|
require "switchman/standard_error"
|
|
93
93
|
|
|
94
94
|
::StandardError.include(StandardError)
|
|
@@ -118,11 +118,6 @@ module Switchman
|
|
|
118
118
|
::ActiveRecord::ConnectionAdapters::ConnectionHandler.prepend(ActiveRecord::ConnectionHandler)
|
|
119
119
|
::ActiveRecord::ConnectionAdapters::ConnectionPool.prepend(ActiveRecord::ConnectionPool)
|
|
120
120
|
::ActiveRecord::ConnectionAdapters::AbstractAdapter.prepend(ActiveRecord::QueryCache)
|
|
121
|
-
# when we call super in Switchman::ActiveRecord::QueryCache#select_all,
|
|
122
|
-
# we want it to find the definition from
|
|
123
|
-
# ActiveRecord::ConnectionAdapters::DatabaseStatements, not
|
|
124
|
-
# ActiveRecord::ConnectionAdapters::QueryCache
|
|
125
|
-
::ActiveRecord::ConnectionAdapters::QueryCache.send(:remove_method, :select_all) if ::Rails.version < '5.0.1'
|
|
126
121
|
|
|
127
122
|
::ActiveRecord::LogSubscriber.prepend(ActiveRecord::LogSubscriber)
|
|
128
123
|
::ActiveRecord::Migration.prepend(ActiveRecord::Migration)
|
|
@@ -173,6 +168,12 @@ module Switchman
|
|
|
173
168
|
require "switchman/active_record/postgresql_adapter"
|
|
174
169
|
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(ActiveRecord::PostgreSQLAdapter)
|
|
175
170
|
end
|
|
171
|
+
|
|
172
|
+
# If Switchman::Shard wasn't loaded as of when ActiveRecord::Base initialized
|
|
173
|
+
# establish a connection here instead
|
|
174
|
+
if !Shard.instance_variable_get(:@default)
|
|
175
|
+
::ActiveRecord::Base.establish_connection
|
|
176
|
+
end
|
|
176
177
|
end
|
|
177
178
|
end
|
|
178
179
|
|