switchman 3.3.1 → 4.0.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/Rakefile +15 -14
- data/db/migrate/20180828183945_add_default_shard_index.rb +1 -1
- data/db/migrate/20190114212900_add_unique_name_indexes.rb +10 -4
- data/lib/switchman/active_record/abstract_adapter.rb +4 -2
- data/lib/switchman/active_record/associations.rb +89 -49
- data/lib/switchman/active_record/attribute_methods.rb +72 -34
- data/lib/switchman/active_record/base.rb +145 -27
- data/lib/switchman/active_record/calculations.rb +96 -49
- data/lib/switchman/active_record/connection_handler.rb +18 -0
- data/lib/switchman/active_record/connection_pool.rb +24 -3
- data/lib/switchman/active_record/database_configurations.rb +37 -15
- data/lib/switchman/active_record/finder_methods.rb +44 -14
- data/lib/switchman/active_record/log_subscriber.rb +11 -5
- data/lib/switchman/active_record/migration.rb +45 -3
- data/lib/switchman/active_record/pending_migration_connection.rb +17 -0
- data/lib/switchman/active_record/persistence.rb +30 -0
- data/lib/switchman/active_record/postgresql_adapter.rb +11 -10
- data/lib/switchman/active_record/predicate_builder.rb +2 -2
- data/lib/switchman/active_record/query_cache.rb +49 -20
- data/lib/switchman/active_record/query_methods.rb +93 -30
- data/lib/switchman/active_record/relation.rb +23 -12
- data/lib/switchman/active_record/spawn_methods.rb +2 -2
- data/lib/switchman/active_record/statement_cache.rb +2 -2
- data/lib/switchman/active_record/tasks/database_tasks.rb +6 -1
- data/lib/switchman/active_record/test_fixtures.rb +26 -16
- data/lib/switchman/active_support/cache.rb +9 -4
- data/lib/switchman/arel.rb +34 -18
- data/lib/switchman/call_super.rb +2 -8
- data/lib/switchman/database_server.rb +69 -31
- data/lib/switchman/default_shard.rb +14 -3
- data/lib/switchman/engine.rb +29 -22
- data/lib/switchman/environment.rb +2 -2
- data/lib/switchman/errors.rb +13 -0
- data/lib/switchman/guard_rail/relation.rb +1 -2
- data/lib/switchman/parallel.rb +6 -6
- data/lib/switchman/r_spec_helper.rb +12 -11
- data/lib/switchman/shard.rb +180 -68
- data/lib/switchman/sharded_instrumenter.rb +3 -3
- data/lib/switchman/shared_schema_cache.rb +11 -0
- data/lib/switchman/standard_error.rb +4 -0
- data/lib/switchman/test_helper.rb +2 -2
- data/lib/switchman/version.rb +1 -1
- data/lib/switchman.rb +27 -15
- data/lib/tasks/switchman.rake +96 -60
- metadata +38 -48
data/lib/switchman/shard.rb
CHANGED
|
@@ -13,6 +13,47 @@ module Switchman
|
|
|
13
13
|
|
|
14
14
|
scope :primary, -> { where(name: nil).order(:database_server_id, :id).distinct_on(:database_server_id) }
|
|
15
15
|
|
|
16
|
+
scope :in_region, (lambda do |region, include_regionless: true|
|
|
17
|
+
next in_current_region if region.nil?
|
|
18
|
+
|
|
19
|
+
dbs_by_region = DatabaseServer.group_by(&:region)
|
|
20
|
+
db_count_in_this_region = dbs_by_region[region]&.length.to_i
|
|
21
|
+
db_count_in_this_region += dbs_by_region[nil]&.length.to_i if include_regionless
|
|
22
|
+
non_existent_database_servers = Shard.send(:non_existent_database_servers)
|
|
23
|
+
db_count_in_other_regions = DatabaseServer.all.length -
|
|
24
|
+
db_count_in_this_region +
|
|
25
|
+
non_existent_database_servers.length
|
|
26
|
+
|
|
27
|
+
dbs_in_this_region = dbs_by_region[region]&.map(&:id) || []
|
|
28
|
+
dbs_in_this_region += dbs_by_region[nil]&.map(&:id) || [] if include_regionless
|
|
29
|
+
|
|
30
|
+
if db_count_in_this_region <= db_count_in_other_regions
|
|
31
|
+
if dbs_in_this_region.include?(Shard.default.database_server.id)
|
|
32
|
+
where("database_server_id IN (?) OR database_server_id IS NULL", dbs_in_this_region)
|
|
33
|
+
else
|
|
34
|
+
where(database_server_id: dbs_in_this_region)
|
|
35
|
+
end
|
|
36
|
+
elsif db_count_in_other_regions.zero?
|
|
37
|
+
all
|
|
38
|
+
else
|
|
39
|
+
dbs_not_in_this_region = DatabaseServer.map(&:id) - dbs_in_this_region + non_existent_database_servers
|
|
40
|
+
if dbs_in_this_region.include?(Shard.default.database_server.id)
|
|
41
|
+
where("database_server_id NOT IN (?) OR database_server_id IS NULL", dbs_not_in_this_region)
|
|
42
|
+
else
|
|
43
|
+
where.not(database_server_id: dbs_not_in_this_region)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end)
|
|
47
|
+
|
|
48
|
+
scope :in_current_region, (lambda do |include_regionless: true|
|
|
49
|
+
# sharding isn't set up? maybe we're in tests, or a somehow degraded environment
|
|
50
|
+
# either way there's only one shard, and we always want to see it
|
|
51
|
+
return [default] unless default.is_a?(Switchman::Shard)
|
|
52
|
+
return all if !Switchman.region || DatabaseServer.none?(&:region)
|
|
53
|
+
|
|
54
|
+
in_region(Switchman.region, include_regionless: include_regionless)
|
|
55
|
+
end)
|
|
56
|
+
|
|
16
57
|
class << self
|
|
17
58
|
def sharded_models
|
|
18
59
|
@sharded_models ||= [::ActiveRecord::Base, UnshardedRecord].freeze
|
|
@@ -36,13 +77,15 @@ module Switchman
|
|
|
36
77
|
|
|
37
78
|
# Now find the actual record, if it exists
|
|
38
79
|
@default = begin
|
|
39
|
-
find_cached(
|
|
80
|
+
find_cached("default_shard") { Shard.where(default: true).take } || default
|
|
40
81
|
rescue
|
|
41
82
|
default
|
|
42
83
|
end
|
|
43
84
|
|
|
44
85
|
# make sure this is not erroneously cached
|
|
45
|
-
|
|
86
|
+
if @default.database_server.instance_variable_defined?(:@primary_shard)
|
|
87
|
+
@default.database_server.remove_instance_variable(:@primary_shard)
|
|
88
|
+
end
|
|
46
89
|
|
|
47
90
|
# and finally, check for cached references to the default shard on the existing connection
|
|
48
91
|
sharded_models.each do |klass|
|
|
@@ -75,28 +118,30 @@ module Switchman
|
|
|
75
118
|
klass.current_switchman_shard != shard
|
|
76
119
|
|
|
77
120
|
(activated_classes ||= []) << klass
|
|
78
|
-
klass.connected_to_stack << { shard: shard.database_server.id.to_sym,
|
|
121
|
+
klass.connected_to_stack << { shard: shard.database_server.id.to_sym,
|
|
122
|
+
klasses: [klass],
|
|
123
|
+
switchman_shard: shard }
|
|
79
124
|
end
|
|
80
125
|
activated_classes
|
|
81
126
|
end
|
|
82
127
|
|
|
83
128
|
def active_shards
|
|
84
|
-
sharded_models.
|
|
129
|
+
sharded_models.filter_map do |klass|
|
|
85
130
|
[klass, current(klass)]
|
|
86
|
-
end.
|
|
131
|
+
end.to_h
|
|
87
132
|
end
|
|
88
133
|
|
|
89
134
|
def lookup(id)
|
|
90
135
|
id_i = id.to_i
|
|
91
|
-
return current if id_i == current.id || id ==
|
|
92
|
-
return default if id_i == default.id || id.nil? || id ==
|
|
136
|
+
return current if id_i == current.id || id == "self"
|
|
137
|
+
return default if id_i == default.id || id.nil? || id == "default"
|
|
93
138
|
|
|
94
139
|
id = id_i
|
|
95
140
|
raise ArgumentError if id.zero?
|
|
96
141
|
|
|
97
142
|
unless cached_shards.key?(id)
|
|
98
143
|
cached_shards[id] = Shard.default.activate do
|
|
99
|
-
find_cached([
|
|
144
|
+
find_cached(["shard", id]) { find_by(id: id) }
|
|
100
145
|
end
|
|
101
146
|
end
|
|
102
147
|
cached_shards[id]
|
|
@@ -120,7 +165,8 @@ module Switchman
|
|
|
120
165
|
# forking.
|
|
121
166
|
# exception: - :ignore, :raise, :defer (wait until the end and raise the first
|
|
122
167
|
# error), or a proc
|
|
123
|
-
|
|
168
|
+
# output: - :simple, :decorated (with database_server_id:shard_name), custom lambda transformer
|
|
169
|
+
def with_each_shard(*args, parallel: false, exception: :raise, output: :simple)
|
|
124
170
|
raise ArgumentError, "wrong number of arguments (#{args.length} for 0...2)" if args.length > 2
|
|
125
171
|
|
|
126
172
|
return Array.wrap(yield) unless default.is_a?(Shard)
|
|
@@ -135,17 +181,26 @@ module Switchman
|
|
|
135
181
|
scope, classes = args
|
|
136
182
|
end
|
|
137
183
|
|
|
184
|
+
output = if output == :decorated
|
|
185
|
+
->(arg) { "#{Shard.current.description}: #{arg}" }
|
|
186
|
+
elsif output == :simple
|
|
187
|
+
nil
|
|
188
|
+
else
|
|
189
|
+
output
|
|
190
|
+
end
|
|
138
191
|
parallel = [Environment.cpu_count || 2, 2].min if parallel == true
|
|
139
192
|
parallel = 0 if parallel == false || parallel.nil?
|
|
140
193
|
|
|
141
194
|
scope ||= Shard.all
|
|
142
|
-
|
|
195
|
+
if ::ActiveRecord::Relation === scope && scope.order_values.empty?
|
|
196
|
+
scope = scope.order(::Arel.sql("database_server_id IS NOT NULL, database_server_id, id"))
|
|
197
|
+
end
|
|
143
198
|
|
|
144
199
|
if parallel > 1
|
|
145
200
|
if ::ActiveRecord::Relation === scope
|
|
146
201
|
# still need a post-uniq, cause the default database server could be NULL or Rails.env in the db
|
|
147
|
-
database_servers = scope.reorder(
|
|
148
|
-
|
|
202
|
+
database_servers = scope.reorder("database_server_id").select(:database_server_id).distinct
|
|
203
|
+
.filter_map(&:database_server).uniq
|
|
149
204
|
# nothing to do
|
|
150
205
|
return if database_servers.count.zero?
|
|
151
206
|
|
|
@@ -159,25 +214,33 @@ module Switchman
|
|
|
159
214
|
# clear connections prior to forking (no more queries will be executed in the parent,
|
|
160
215
|
# and we want them gone so that we don't accidentally use them post-fork doing something
|
|
161
216
|
# silly like dealloc'ing prepared statements)
|
|
162
|
-
::
|
|
217
|
+
if ::Rails.version < "7.1"
|
|
218
|
+
::ActiveRecord::Base.clear_all_connections!(nil)
|
|
219
|
+
else
|
|
220
|
+
::ActiveRecord::Base.connection_handler.clear_all_connections!(:all)
|
|
221
|
+
end
|
|
163
222
|
|
|
164
|
-
parent_process_name =
|
|
165
|
-
ret = ::Parallel.map(scopes, in_processes: scopes.length > 1 ? parallel : 0) do |server, subscope|
|
|
223
|
+
parent_process_name = sanitized_process_title
|
|
224
|
+
ret = ::Parallel.map(scopes, in_processes: (scopes.length > 1) ? parallel : 0) do |server, subscope|
|
|
166
225
|
name = server.id
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
$stderr = Parallel::PrefixingIO.new(name, STDERR)
|
|
170
|
-
# rubocop:enable Style/GlobalStdStream
|
|
226
|
+
last_description = name
|
|
227
|
+
|
|
171
228
|
begin
|
|
172
229
|
max_length = 128 - name.length - 3
|
|
173
230
|
short_parent_name = parent_process_name[0..max_length] if max_length >= 0
|
|
174
|
-
new_title = [short_parent_name, name].join(
|
|
231
|
+
new_title = [short_parent_name, name].join(" ")
|
|
175
232
|
Process.setproctitle(new_title)
|
|
176
233
|
Switchman.config[:on_fork_proc]&.call
|
|
177
|
-
with_each_shard(subscope,
|
|
234
|
+
with_each_shard(subscope,
|
|
235
|
+
classes,
|
|
236
|
+
exception: exception,
|
|
237
|
+
output: output || :decorated) do
|
|
238
|
+
last_description = Shard.current.description
|
|
239
|
+
Parallel::ResultWrapper.new(yield)
|
|
240
|
+
end
|
|
178
241
|
rescue => e
|
|
179
242
|
logger.error e.full_message
|
|
180
|
-
Parallel::QuietExceptionWrapper.new(
|
|
243
|
+
Parallel::QuietExceptionWrapper.new(last_description, ::Parallel::ExceptionWrapper.new(e))
|
|
181
244
|
end
|
|
182
245
|
end.flatten
|
|
183
246
|
|
|
@@ -185,8 +248,9 @@ module Switchman
|
|
|
185
248
|
unless errors.empty?
|
|
186
249
|
raise errors.first.exception if errors.length == 1
|
|
187
250
|
|
|
251
|
+
errors_desc = errors.map(&:name).sort.join(", ")
|
|
188
252
|
raise Errors::ParallelShardExecError,
|
|
189
|
-
"The following database server(s) did not finish processing cleanly: #{
|
|
253
|
+
"The following database server(s) did not finish processing cleanly: #{errors_desc}",
|
|
190
254
|
cause: errors.first.exception
|
|
191
255
|
end
|
|
192
256
|
|
|
@@ -195,14 +259,20 @@ module Switchman
|
|
|
195
259
|
|
|
196
260
|
classes ||= []
|
|
197
261
|
|
|
198
|
-
previous_shard = nil
|
|
199
262
|
result = []
|
|
200
263
|
ex = nil
|
|
264
|
+
old_stdout = $stdout
|
|
265
|
+
old_stderr = $stderr
|
|
201
266
|
scope.each do |shard|
|
|
202
267
|
# shard references a database server that isn't configured in this environment
|
|
203
268
|
next unless shard.database_server
|
|
204
269
|
|
|
205
270
|
shard.activate(*classes) do
|
|
271
|
+
if output
|
|
272
|
+
$stdout = Parallel::TransformingIO.new(output, $stdout)
|
|
273
|
+
$stderr = Parallel::TransformingIO.new(output, $stderr)
|
|
274
|
+
end
|
|
275
|
+
|
|
206
276
|
result.concat Array.wrap(yield)
|
|
207
277
|
rescue
|
|
208
278
|
case exception
|
|
@@ -216,8 +286,10 @@ module Switchman
|
|
|
216
286
|
else
|
|
217
287
|
raise
|
|
218
288
|
end
|
|
289
|
+
ensure
|
|
290
|
+
$stdout = old_stdout
|
|
291
|
+
$stderr = old_stderr
|
|
219
292
|
end
|
|
220
|
-
previous_shard = shard
|
|
221
293
|
end
|
|
222
294
|
raise ex if ex
|
|
223
295
|
|
|
@@ -241,7 +313,7 @@ module Switchman
|
|
|
241
313
|
else
|
|
242
314
|
shard = partition_object.shard
|
|
243
315
|
end
|
|
244
|
-
when Integer,
|
|
316
|
+
when Integer, /\A\d+\Z/, /\A(\d+)~(\d+)\Z/
|
|
245
317
|
local_id, shard = Shard.local_id_for(partition_object)
|
|
246
318
|
local_id ||= partition_object
|
|
247
319
|
object = local_id unless partition_proc
|
|
@@ -283,14 +355,14 @@ module Switchman
|
|
|
283
355
|
case any_id
|
|
284
356
|
when ::ActiveRecord::Base
|
|
285
357
|
any_id.id
|
|
286
|
-
when
|
|
358
|
+
when /\A(\d+)~(-?\d+)\Z/
|
|
287
359
|
local_id = $2.to_i
|
|
288
360
|
signed_id_operation(local_id) do |id|
|
|
289
361
|
return nil if id > IDS_PER_SHARD
|
|
290
362
|
|
|
291
363
|
($1.to_i * IDS_PER_SHARD) + id
|
|
292
364
|
end
|
|
293
|
-
when Integer,
|
|
365
|
+
when Integer, /\A-?\d+\Z/
|
|
294
366
|
any_id.to_i
|
|
295
367
|
end
|
|
296
368
|
end
|
|
@@ -374,7 +446,7 @@ module Switchman
|
|
|
374
446
|
end
|
|
375
447
|
|
|
376
448
|
def configure_connects_to
|
|
377
|
-
full_connects_to_hash = DatabaseServer.
|
|
449
|
+
full_connects_to_hash = DatabaseServer.to_h { |db| [db.id.to_sym, db.connects_to_hash] }
|
|
378
450
|
sharded_models.each do |klass|
|
|
379
451
|
connects_to_hash = full_connects_to_hash.deep_dup
|
|
380
452
|
if klass == UnshardedRecord
|
|
@@ -393,7 +465,10 @@ module Switchman
|
|
|
393
465
|
end
|
|
394
466
|
end
|
|
395
467
|
|
|
468
|
+
# this resets the default shard on rails 7.1+, but we want to preserve it
|
|
469
|
+
shard_was = klass.default_shard
|
|
396
470
|
klass.connects_to shards: connects_to_hash
|
|
471
|
+
klass.default_shard = shard_was
|
|
397
472
|
end
|
|
398
473
|
end
|
|
399
474
|
|
|
@@ -427,8 +502,50 @@ module Switchman
|
|
|
427
502
|
shard = nil unless shard.database_server
|
|
428
503
|
shard
|
|
429
504
|
end
|
|
505
|
+
|
|
506
|
+
# Determines the name of the current process, including arguments, but stripping
|
|
507
|
+
# any shebang from the invoked script, and any additional path info from the
|
|
508
|
+
# executable.
|
|
509
|
+
#
|
|
510
|
+
# @return [String]
|
|
511
|
+
def sanitized_process_title
|
|
512
|
+
# get the effective process name from `ps`; this will include any changes
|
|
513
|
+
# from Process.setproctitle _or_ assigning to $0.
|
|
514
|
+
parent_process_name = `ps -ocommand= -p#{Process.pid}`.strip
|
|
515
|
+
# Effective process titles may be shorter than the actual
|
|
516
|
+
# command; truncate our ARGV[0] so that they are comparable
|
|
517
|
+
# for the next step
|
|
518
|
+
argv0 = if parent_process_name.length < Process.argv0.length
|
|
519
|
+
Process.argv0[0..parent_process_name.length]
|
|
520
|
+
else
|
|
521
|
+
Process.argv0
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
# when running via a shebang, the `ps` output will include the shebang
|
|
525
|
+
# (i.e. it will be "ruby bin/rails c"); attempt to strip it off.
|
|
526
|
+
# Note that argv0 in this case will _only_ be `bin/rails` (no shebang,
|
|
527
|
+
# no arguments). We want to preserve the arguments we got from `ps`
|
|
528
|
+
if (index = parent_process_name.index(argv0))
|
|
529
|
+
parent_process_name.slice!(0...index)
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
# remove directories from the main executable to make more room
|
|
533
|
+
# for additional info
|
|
534
|
+
argv = parent_process_name.shellsplit
|
|
535
|
+
argv[0] = File.basename(argv[0])
|
|
536
|
+
argv.shelljoin
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
# @return [Array<String>] the list of database servers that are in the
|
|
540
|
+
# config, but don't have any shards on them
|
|
541
|
+
def non_existent_database_servers
|
|
542
|
+
@non_existent_database_servers ||=
|
|
543
|
+
Shard.distinct.pluck(:database_server_id).compact - DatabaseServer.all.map(&:id)
|
|
544
|
+
end
|
|
430
545
|
end
|
|
431
546
|
|
|
547
|
+
delegate :region, :in_region?, :in_current_region?, to: :database_server
|
|
548
|
+
|
|
432
549
|
def name
|
|
433
550
|
unless instance_variable_defined?(:@name)
|
|
434
551
|
# protect against re-entrancy
|
|
@@ -457,7 +574,7 @@ module Switchman
|
|
|
457
574
|
end
|
|
458
575
|
|
|
459
576
|
def description
|
|
460
|
-
[database_server.id, name].compact.join(
|
|
577
|
+
[database_server.id, name].compact.join(":")
|
|
461
578
|
end
|
|
462
579
|
|
|
463
580
|
# Shards are always on the default shard
|
|
@@ -469,6 +586,10 @@ module Switchman
|
|
|
469
586
|
id
|
|
470
587
|
end
|
|
471
588
|
|
|
589
|
+
def original_id_value
|
|
590
|
+
id
|
|
591
|
+
end
|
|
592
|
+
|
|
472
593
|
def activate(*classes, &block)
|
|
473
594
|
shards = hashify_classes(classes)
|
|
474
595
|
Shard.activate(shards, &block)
|
|
@@ -491,48 +612,39 @@ module Switchman
|
|
|
491
612
|
end
|
|
492
613
|
|
|
493
614
|
def drop_database
|
|
494
|
-
raise
|
|
615
|
+
raise "Cannot drop the database of the default shard" if default?
|
|
495
616
|
return unless read_attribute(:name)
|
|
496
617
|
|
|
497
618
|
begin
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
drop_statement = sharding_config[adapter]&.[](:drop_statement)
|
|
501
|
-
drop_statement ||= sharding_config[:drop_statement]
|
|
502
|
-
if drop_statement
|
|
503
|
-
drop_statement = Array(drop_statement).dup.
|
|
504
|
-
map { |statement| statement.gsub('%{name}', name) }
|
|
619
|
+
activate do
|
|
620
|
+
self.class.drop_database(name)
|
|
505
621
|
end
|
|
622
|
+
rescue ::ActiveRecord::StatementInvalid => e
|
|
623
|
+
logger.error "Drop failed: #{e}"
|
|
624
|
+
end
|
|
625
|
+
end
|
|
506
626
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
drop_statement ||= "DROP SCHEMA #{name} CASCADE"
|
|
525
|
-
Array(drop_statement).each do |stmt|
|
|
526
|
-
::ActiveRecord::Base.connection.execute(stmt)
|
|
527
|
-
end
|
|
528
|
-
ensure
|
|
529
|
-
conn.raw_connection.set_notice_processor(&old_proc) if old_proc
|
|
530
|
-
end
|
|
531
|
-
end
|
|
627
|
+
#
|
|
628
|
+
# Drops a specific database/schema from the currently active connection
|
|
629
|
+
#
|
|
630
|
+
def self.drop_database(name)
|
|
631
|
+
sharding_config = Switchman.config || {}
|
|
632
|
+
drop_statement = sharding_config["postgresql"]&.[](:drop_statement)
|
|
633
|
+
drop_statement ||= sharding_config[:drop_statement]
|
|
634
|
+
drop_statement = Array(drop_statement).map { |statement| statement.gsub("%{name}", name) } if drop_statement
|
|
635
|
+
|
|
636
|
+
::GuardRail.activate(:deploy) do
|
|
637
|
+
# Shut up, Postgres!
|
|
638
|
+
conn = ::ActiveRecord::Base.connection
|
|
639
|
+
old_proc = conn.raw_connection.set_notice_processor {}
|
|
640
|
+
begin
|
|
641
|
+
drop_statement ||= "DROP SCHEMA #{name} CASCADE"
|
|
642
|
+
Array(drop_statement).each do |stmt|
|
|
643
|
+
::ActiveRecord::Base.connection.execute(stmt)
|
|
532
644
|
end
|
|
645
|
+
ensure
|
|
646
|
+
conn.raw_connection.set_notice_processor(&old_proc) if old_proc
|
|
533
647
|
end
|
|
534
|
-
rescue
|
|
535
|
-
logger.info "Drop failed: #{$!}"
|
|
536
648
|
end
|
|
537
649
|
end
|
|
538
650
|
|
|
@@ -551,7 +663,7 @@ module Switchman
|
|
|
551
663
|
end
|
|
552
664
|
|
|
553
665
|
def destroy
|
|
554
|
-
raise(
|
|
666
|
+
raise("Cannot destroy the default shard") if default?
|
|
555
667
|
|
|
556
668
|
super
|
|
557
669
|
end
|
|
@@ -560,8 +672,8 @@ module Switchman
|
|
|
560
672
|
|
|
561
673
|
def clear_cache
|
|
562
674
|
Shard.default.activate do
|
|
563
|
-
Switchman.cache.delete([
|
|
564
|
-
Switchman.cache.delete(
|
|
675
|
+
Switchman.cache.delete(["shard", id].join("/"))
|
|
676
|
+
Switchman.cache.delete("default_shard") if default?
|
|
565
677
|
end
|
|
566
678
|
self.class.clear_cache
|
|
567
679
|
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Switchman
|
|
4
4
|
class ShardedInstrumenter < ::SimpleDelegator
|
|
5
5
|
def initialize(instrumenter, shard_host)
|
|
6
|
-
super
|
|
6
|
+
super(instrumenter)
|
|
7
7
|
@shard_host = shard_host
|
|
8
8
|
end
|
|
9
9
|
|
|
@@ -16,10 +16,10 @@ module Switchman
|
|
|
16
16
|
payload[:shard] = {
|
|
17
17
|
database_server_id: shard.database_server.id,
|
|
18
18
|
id: shard.id,
|
|
19
|
-
env:
|
|
19
|
+
env: @shard_host.pool.connection_class&.current_role
|
|
20
20
|
}
|
|
21
21
|
end
|
|
22
|
-
super
|
|
22
|
+
super(name, payload)
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Switchman
|
|
4
|
+
class SharedSchemaCache
|
|
5
|
+
def self.get_schema_cache(connection)
|
|
6
|
+
@schema_cache ||= ::ActiveRecord::ConnectionAdapters::SchemaCache.new(connection)
|
|
7
|
+
@schema_cache.connection = connection
|
|
8
|
+
@schema_cache
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -13,6 +13,10 @@ module Switchman
|
|
|
13
13
|
Thread.current[:switchman_error_handler] = true
|
|
14
14
|
|
|
15
15
|
@active_shards ||= Shard.active_shards
|
|
16
|
+
rescue
|
|
17
|
+
# intentionally empty - don't allow calculating the active_shards to prevent
|
|
18
|
+
# creating the StandardError for any reason. this prevents various random issues
|
|
19
|
+
# when a StandardError is created within a finalizer
|
|
16
20
|
ensure
|
|
17
21
|
Thread.current[:switchman_error_handler] = nil
|
|
18
22
|
end
|
|
@@ -19,7 +19,7 @@ module Switchman
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
server1 = Shard.default.database_server
|
|
22
|
-
server2 = DatabaseServer.create(Shard.default.database_server.config.merge(server2: true))
|
|
22
|
+
server2 = DatabaseServer.create(Shard.default.database_server.config.merge(server2: true, schema_dump: false))
|
|
23
23
|
|
|
24
24
|
if server1 == Shard.default.database_server && server1.config[:shard1] && server1.config[:shard2]
|
|
25
25
|
# look for the shards in the db already
|
|
@@ -65,7 +65,7 @@ module Switchman
|
|
|
65
65
|
if server == Shard.default.database_server
|
|
66
66
|
server.shards.where(name: name).first
|
|
67
67
|
else
|
|
68
|
-
shard = Shard.where(
|
|
68
|
+
shard = Shard.where("database_server_id IS NOT NULL AND name=?", name).first
|
|
69
69
|
# if somehow databases got created in a different order, change the shard to match
|
|
70
70
|
shard.database_server = server if shard
|
|
71
71
|
shard
|
data/lib/switchman/version.rb
CHANGED
data/lib/switchman.rb
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "guard_rail"
|
|
4
|
+
require "zeitwerk"
|
|
5
5
|
|
|
6
6
|
class SwitchmanInflector < Zeitwerk::GemInflector
|
|
7
7
|
def camelize(basename, abspath)
|
|
8
8
|
if basename =~ /\Apostgresql_(.*)/
|
|
9
|
-
|
|
9
|
+
"PostgreSQL" + super($1, abspath)
|
|
10
10
|
else
|
|
11
11
|
super
|
|
12
12
|
end
|
|
@@ -18,21 +18,33 @@ loader.inflector = SwitchmanInflector.new(__FILE__)
|
|
|
18
18
|
loader.setup
|
|
19
19
|
|
|
20
20
|
module Switchman
|
|
21
|
-
|
|
22
|
-
# TODO: load from yaml
|
|
23
|
-
@config ||= {}
|
|
24
|
-
end
|
|
21
|
+
Deprecation = ::ActiveSupport::Deprecation.new("4.0", "Switchman")
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
end
|
|
23
|
+
class << self
|
|
24
|
+
attr_writer :cache
|
|
29
25
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
def config
|
|
27
|
+
# TODO: load from yaml
|
|
28
|
+
@config ||= {}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def cache
|
|
32
|
+
(@cache.respond_to?(:call) ? @cache.call : @cache) || ::Rails.cache
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def region
|
|
36
|
+
config[:region]
|
|
37
|
+
end
|
|
33
38
|
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
def foreign_key_check(name, type, limit: nil)
|
|
40
|
+
return unless name.to_s.end_with?("_id") && type.to_s == "integer" && limit.to_i < 8
|
|
41
|
+
|
|
42
|
+
puts <<~TEXT.squish
|
|
43
|
+
WARNING: All foreign keys need to be 8-byte integers.
|
|
44
|
+
#{name} looks like a foreign key.
|
|
45
|
+
If so, please add the option: `:limit => 8`
|
|
46
|
+
TEXT
|
|
47
|
+
end
|
|
36
48
|
end
|
|
37
49
|
|
|
38
50
|
class OrderOnMultiShardQuery < RuntimeError; end
|