switchman 3.4.2 → 3.6.7
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 -16
- data/lib/switchman/active_record/attribute_methods.rb +67 -22
- data/lib/switchman/active_record/base.rb +112 -22
- data/lib/switchman/active_record/calculations.rb +93 -37
- data/lib/switchman/active_record/connection_handler.rb +18 -0
- data/lib/switchman/active_record/connection_pool.rb +18 -14
- 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 +28 -9
- data/lib/switchman/active_record/pending_migration_connection.rb +17 -0
- data/lib/switchman/active_record/persistence.rb +22 -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 +22 -11
- 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 +68 -21
- data/lib/switchman/default_shard.rb +14 -3
- data/lib/switchman/engine.rb +39 -19
- data/lib/switchman/environment.rb +2 -2
- data/lib/switchman/errors.rb +4 -1
- data/lib/switchman/guard_rail/relation.rb +1 -2
- data/lib/switchman/parallel.rb +5 -5
- data/lib/switchman/r_spec_helper.rb +11 -11
- data/lib/switchman/shard.rb +166 -64
- data/lib/switchman/sharded_instrumenter.rb +7 -3
- 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 +117 -51
- metadata +19 -44
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "etc"
|
4
4
|
|
5
5
|
module Switchman
|
6
6
|
class Environment
|
7
|
-
def self.cpu_count(nproc_bin =
|
7
|
+
def self.cpu_count(nproc_bin = "nproc")
|
8
8
|
return Etc.nprocessors if Etc.respond_to?(:nprocessors)
|
9
9
|
|
10
10
|
`#{nproc_bin}`.to_i
|
data/lib/switchman/errors.rb
CHANGED
@@ -3,7 +3,10 @@
|
|
3
3
|
module Switchman
|
4
4
|
module Errors
|
5
5
|
class ManuallyCreatedShadowRecordError < RuntimeError
|
6
|
-
|
6
|
+
DEFAULT_MSG = "It looks like you're trying to manually create a shadow record. " \
|
7
|
+
"Please use Switchman::ActiveRecord::Base#save_shadow_record instead."
|
8
|
+
|
9
|
+
def initialize(msg = DEFAULT_MSG)
|
7
10
|
super
|
8
11
|
end
|
9
12
|
end
|
@@ -13,9 +13,8 @@ module Switchman
|
|
13
13
|
end
|
14
14
|
|
15
15
|
%w[update_all delete_all].each do |method|
|
16
|
-
arg_params = RUBY_VERSION <= '2.8' ? '*args' : '*args, **kwargs'
|
17
16
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
18
|
-
def #{method}(
|
17
|
+
def #{method}(*args, **kwargs)
|
19
18
|
db = Shard.current(connection_class_for_self).database_server
|
20
19
|
db.unguard { super }
|
21
20
|
end
|
data/lib/switchman/parallel.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "parallel"
|
4
4
|
|
5
5
|
module Switchman
|
6
6
|
module Parallel
|
@@ -50,16 +50,16 @@ module Switchman
|
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
|
-
class
|
53
|
+
class TransformingIO
|
54
54
|
delegate_missing_to :@original_io
|
55
55
|
|
56
|
-
def initialize(
|
57
|
-
@
|
56
|
+
def initialize(transformer, original_io)
|
57
|
+
@transformer = transformer
|
58
58
|
@original_io = original_io
|
59
59
|
end
|
60
60
|
|
61
61
|
def puts(*args)
|
62
|
-
args.flatten.each { |arg| @original_io.puts
|
62
|
+
args.flatten.each { |arg| @original_io.puts @transformer.call(arg) }
|
63
63
|
end
|
64
64
|
end
|
65
65
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "switchman/test_helper"
|
4
4
|
|
5
5
|
module Switchman
|
6
6
|
# including this module in your specs will give you several shards to
|
@@ -34,9 +34,9 @@ module Switchman
|
|
34
34
|
groups = group.class.descendant_filtered_examples.map(&:example_group).uniq
|
35
35
|
next unless groups.any? { |descendant_group| RSpecHelper.included_in?(descendant_group) }
|
36
36
|
|
37
|
-
puts
|
37
|
+
puts "Setting up sharding for all specs..."
|
38
38
|
Shard.delete_all
|
39
|
-
Switchman.cache.delete(
|
39
|
+
Switchman.cache.delete("default_shard")
|
40
40
|
|
41
41
|
@@shard1, @@shard2 = TestHelper.recreate_persistent_test_shards
|
42
42
|
@@default_shard = Shard.default
|
@@ -48,7 +48,7 @@ module Switchman
|
|
48
48
|
@@shard1 = @@shard1.create_new_shard
|
49
49
|
@@shard2 = @@shard2.create_new_shard
|
50
50
|
rescue => e
|
51
|
-
warn
|
51
|
+
warn "Sharding setup FAILED!:"
|
52
52
|
while e
|
53
53
|
warn "\n#{e}\n"
|
54
54
|
warn e.backtrace
|
@@ -66,9 +66,9 @@ module Switchman
|
|
66
66
|
# we'll re-persist in the group's `before :all`; we don't want them to exist
|
67
67
|
# in the db before then
|
68
68
|
Shard.delete_all
|
69
|
-
Switchman.cache.delete(
|
69
|
+
Switchman.cache.delete("default_shard")
|
70
70
|
Shard.default(reload: true)
|
71
|
-
puts
|
71
|
+
puts "Done!"
|
72
72
|
|
73
73
|
main_pid = Process.pid
|
74
74
|
at_exit do
|
@@ -76,7 +76,7 @@ module Switchman
|
|
76
76
|
|
77
77
|
# preserve rspec's exit status
|
78
78
|
status = $!.is_a?(::SystemExit) ? $!.status : nil
|
79
|
-
puts
|
79
|
+
puts "Tearing down sharding for all specs"
|
80
80
|
@@shard1.database_server.destroy unless @@shard1.database_server == Shard.default.database_server
|
81
81
|
unless @@keep_the_shards
|
82
82
|
@@shard1.drop_database
|
@@ -95,7 +95,7 @@ module Switchman
|
|
95
95
|
dup = @@default_shard.dup
|
96
96
|
dup.id = @@default_shard.id
|
97
97
|
dup.save!
|
98
|
-
Switchman.cache.delete(
|
98
|
+
Switchman.cache.delete("default_shard")
|
99
99
|
Shard.default(reload: true)
|
100
100
|
dup = @@shard1.dup
|
101
101
|
dup.id = @@shard1.id
|
@@ -107,7 +107,7 @@ module Switchman
|
|
107
107
|
end
|
108
108
|
|
109
109
|
klass.before do
|
110
|
-
raise
|
110
|
+
raise "Sharding did not set up correctly" if @@sharding_failed
|
111
111
|
|
112
112
|
Shard.clear_cache
|
113
113
|
if use_transactional_tests
|
@@ -121,7 +121,7 @@ module Switchman
|
|
121
121
|
next if @@sharding_failed
|
122
122
|
|
123
123
|
# clean up after specs
|
124
|
-
DatabaseServer.
|
124
|
+
DatabaseServer.each do |ds|
|
125
125
|
if ds.fake? && ds != @shard2.database_server
|
126
126
|
ds.shards.delete_all unless use_transactional_tests
|
127
127
|
ds.destroy
|
@@ -132,7 +132,7 @@ module Switchman
|
|
132
132
|
klass.after(:all) do
|
133
133
|
# Don't truncate because that can create some fun cross-connection lock contention
|
134
134
|
Shard.delete_all
|
135
|
-
Switchman.cache.delete(
|
135
|
+
Switchman.cache.delete("default_shard")
|
136
136
|
Shard.default(reload: true)
|
137
137
|
end
|
138
138
|
end
|
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,7 @@ 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
|
-
# output: - :simple, :decorated (with database_server_id:shard_name)
|
168
|
+
# output: - :simple, :decorated (with database_server_id:shard_name), custom lambda transformer
|
124
169
|
def with_each_shard(*args, parallel: false, exception: :raise, output: :simple)
|
125
170
|
raise ArgumentError, "wrong number of arguments (#{args.length} for 0...2)" if args.length > 2
|
126
171
|
|
@@ -136,17 +181,26 @@ module Switchman
|
|
136
181
|
scope, classes = args
|
137
182
|
end
|
138
183
|
|
184
|
+
output = if output == :decorated
|
185
|
+
->(arg) { "#{Shard.current.description}: #{arg}" }
|
186
|
+
elsif output == :simple
|
187
|
+
nil
|
188
|
+
else
|
189
|
+
output
|
190
|
+
end
|
139
191
|
parallel = [Environment.cpu_count || 2, 2].min if parallel == true
|
140
192
|
parallel = 0 if parallel == false || parallel.nil?
|
141
193
|
|
142
194
|
scope ||= Shard.all
|
143
|
-
|
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
|
144
198
|
|
145
199
|
if parallel > 1
|
146
200
|
if ::ActiveRecord::Relation === scope
|
147
201
|
# still need a post-uniq, cause the default database server could be NULL or Rails.env in the db
|
148
|
-
database_servers = scope.reorder(
|
149
|
-
|
202
|
+
database_servers = scope.reorder("database_server_id").select(:database_server_id).distinct
|
203
|
+
.filter_map(&:database_server).uniq
|
150
204
|
# nothing to do
|
151
205
|
return if database_servers.count.zero?
|
152
206
|
|
@@ -160,20 +214,27 @@ module Switchman
|
|
160
214
|
# clear connections prior to forking (no more queries will be executed in the parent,
|
161
215
|
# and we want them gone so that we don't accidentally use them post-fork doing something
|
162
216
|
# silly like dealloc'ing prepared statements)
|
163
|
-
::
|
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
|
164
222
|
|
165
|
-
parent_process_name =
|
166
|
-
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|
|
167
225
|
name = server.id
|
168
226
|
last_description = name
|
169
227
|
|
170
228
|
begin
|
171
229
|
max_length = 128 - name.length - 3
|
172
230
|
short_parent_name = parent_process_name[0..max_length] if max_length >= 0
|
173
|
-
new_title = [short_parent_name, name].join(
|
231
|
+
new_title = [short_parent_name, name].join(" ")
|
174
232
|
Process.setproctitle(new_title)
|
175
233
|
Switchman.config[:on_fork_proc]&.call
|
176
|
-
with_each_shard(subscope,
|
234
|
+
with_each_shard(subscope,
|
235
|
+
classes,
|
236
|
+
exception: exception,
|
237
|
+
output: output || :decorated) do
|
177
238
|
last_description = Shard.current.description
|
178
239
|
Parallel::ResultWrapper.new(yield)
|
179
240
|
end
|
@@ -187,8 +248,9 @@ module Switchman
|
|
187
248
|
unless errors.empty?
|
188
249
|
raise errors.first.exception if errors.length == 1
|
189
250
|
|
251
|
+
errors_desc = errors.map(&:name).sort.join(", ")
|
190
252
|
raise Errors::ParallelShardExecError,
|
191
|
-
"The following database server(s) did not finish processing cleanly: #{
|
253
|
+
"The following database server(s) did not finish processing cleanly: #{errors_desc}",
|
192
254
|
cause: errors.first.exception
|
193
255
|
end
|
194
256
|
|
@@ -206,9 +268,9 @@ module Switchman
|
|
206
268
|
next unless shard.database_server
|
207
269
|
|
208
270
|
shard.activate(*classes) do
|
209
|
-
if output
|
210
|
-
$stdout = Parallel::
|
211
|
-
$stderr = Parallel::
|
271
|
+
if output
|
272
|
+
$stdout = Parallel::TransformingIO.new(output, $stdout)
|
273
|
+
$stderr = Parallel::TransformingIO.new(output, $stderr)
|
212
274
|
end
|
213
275
|
|
214
276
|
result.concat Array.wrap(yield)
|
@@ -251,7 +313,7 @@ module Switchman
|
|
251
313
|
else
|
252
314
|
shard = partition_object.shard
|
253
315
|
end
|
254
|
-
when Integer,
|
316
|
+
when Integer, /\A\d+\Z/, /\A(\d+)~(\d+)\Z/
|
255
317
|
local_id, shard = Shard.local_id_for(partition_object)
|
256
318
|
local_id ||= partition_object
|
257
319
|
object = local_id unless partition_proc
|
@@ -293,14 +355,14 @@ module Switchman
|
|
293
355
|
case any_id
|
294
356
|
when ::ActiveRecord::Base
|
295
357
|
any_id.id
|
296
|
-
when
|
358
|
+
when /\A(\d+)~(-?\d+)\Z/
|
297
359
|
local_id = $2.to_i
|
298
360
|
signed_id_operation(local_id) do |id|
|
299
361
|
return nil if id > IDS_PER_SHARD
|
300
362
|
|
301
363
|
($1.to_i * IDS_PER_SHARD) + id
|
302
364
|
end
|
303
|
-
when Integer,
|
365
|
+
when Integer, /\A-?\d+\Z/
|
304
366
|
any_id.to_i
|
305
367
|
end
|
306
368
|
end
|
@@ -384,7 +446,7 @@ module Switchman
|
|
384
446
|
end
|
385
447
|
|
386
448
|
def configure_connects_to
|
387
|
-
full_connects_to_hash = DatabaseServer.
|
449
|
+
full_connects_to_hash = DatabaseServer.to_h { |db| [db.id.to_sym, db.connects_to_hash] }
|
388
450
|
sharded_models.each do |klass|
|
389
451
|
connects_to_hash = full_connects_to_hash.deep_dup
|
390
452
|
if klass == UnshardedRecord
|
@@ -403,7 +465,10 @@ module Switchman
|
|
403
465
|
end
|
404
466
|
end
|
405
467
|
|
468
|
+
# this resets the default shard on rails 7.1+, but we want to preserve it
|
469
|
+
shard_was = klass.default_shard
|
406
470
|
klass.connects_to shards: connects_to_hash
|
471
|
+
klass.default_shard = shard_was
|
407
472
|
end
|
408
473
|
end
|
409
474
|
|
@@ -437,8 +502,50 @@ module Switchman
|
|
437
502
|
shard = nil unless shard.database_server
|
438
503
|
shard
|
439
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
|
440
545
|
end
|
441
546
|
|
547
|
+
delegate :region, :in_region?, :in_current_region?, to: :database_server
|
548
|
+
|
442
549
|
def name
|
443
550
|
unless instance_variable_defined?(:@name)
|
444
551
|
# protect against re-entrancy
|
@@ -467,7 +574,7 @@ module Switchman
|
|
467
574
|
end
|
468
575
|
|
469
576
|
def description
|
470
|
-
[database_server.id, name].compact.join(
|
577
|
+
[database_server.id, name].compact.join(":")
|
471
578
|
end
|
472
579
|
|
473
580
|
# Shards are always on the default shard
|
@@ -479,6 +586,10 @@ module Switchman
|
|
479
586
|
id
|
480
587
|
end
|
481
588
|
|
589
|
+
def original_id_value
|
590
|
+
id
|
591
|
+
end
|
592
|
+
|
482
593
|
def activate(*classes, &block)
|
483
594
|
shards = hashify_classes(classes)
|
484
595
|
Shard.activate(shards, &block)
|
@@ -501,48 +612,39 @@ module Switchman
|
|
501
612
|
end
|
502
613
|
|
503
614
|
def drop_database
|
504
|
-
raise
|
615
|
+
raise "Cannot drop the database of the default shard" if default?
|
505
616
|
return unless read_attribute(:name)
|
506
617
|
|
507
618
|
begin
|
508
|
-
|
509
|
-
|
510
|
-
drop_statement = sharding_config[adapter]&.[](:drop_statement)
|
511
|
-
drop_statement ||= sharding_config[:drop_statement]
|
512
|
-
if drop_statement
|
513
|
-
drop_statement = Array(drop_statement).dup.
|
514
|
-
map { |statement| statement.gsub('%{name}', name) }
|
619
|
+
activate do
|
620
|
+
self.class.drop_database(name)
|
515
621
|
end
|
622
|
+
rescue ::ActiveRecord::StatementInvalid => e
|
623
|
+
logger.error "Drop failed: #{e}"
|
624
|
+
end
|
625
|
+
end
|
516
626
|
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
drop_statement ||= "DROP SCHEMA #{name} CASCADE"
|
535
|
-
Array(drop_statement).each do |stmt|
|
536
|
-
::ActiveRecord::Base.connection.execute(stmt)
|
537
|
-
end
|
538
|
-
ensure
|
539
|
-
conn.raw_connection.set_notice_processor(&old_proc) if old_proc
|
540
|
-
end
|
541
|
-
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)
|
542
644
|
end
|
645
|
+
ensure
|
646
|
+
conn.raw_connection.set_notice_processor(&old_proc) if old_proc
|
543
647
|
end
|
544
|
-
rescue
|
545
|
-
logger.info "Drop failed: #{$!}"
|
546
648
|
end
|
547
649
|
end
|
548
650
|
|
@@ -561,7 +663,7 @@ module Switchman
|
|
561
663
|
end
|
562
664
|
|
563
665
|
def destroy
|
564
|
-
raise(
|
666
|
+
raise("Cannot destroy the default shard") if default?
|
565
667
|
|
566
668
|
super
|
567
669
|
end
|
@@ -570,8 +672,8 @@ module Switchman
|
|
570
672
|
|
571
673
|
def clear_cache
|
572
674
|
Shard.default.activate do
|
573
|
-
Switchman.cache.delete([
|
574
|
-
Switchman.cache.delete(
|
675
|
+
Switchman.cache.delete(["shard", id].join("/"))
|
676
|
+
Switchman.cache.delete("default_shard") if default?
|
575
677
|
end
|
576
678
|
self.class.clear_cache
|
577
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,14 @@ module Switchman
|
|
16
16
|
payload[:shard] = {
|
17
17
|
database_server_id: shard.database_server.id,
|
18
18
|
id: shard.id,
|
19
|
-
env: ::Rails.version <
|
19
|
+
env: if ::Rails.version < "7.0"
|
20
|
+
@shard_host.pool.connection_klass&.current_role
|
21
|
+
else
|
22
|
+
@shard_host.pool.connection_class&.current_role
|
23
|
+
end
|
20
24
|
}
|
21
25
|
end
|
22
|
-
super
|
26
|
+
super(name, payload)
|
23
27
|
end
|
24
28
|
end
|
25
29
|
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