switchman 2.1.5 → 2.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/models/switchman/shard.rb +83 -128
- data/lib/switchman/active_record/connection_pool.rb +3 -1
- data/lib/switchman/active_record/migration.rb +6 -3
- data/lib/switchman/active_record/persistence.rb +10 -1
- data/lib/switchman/active_record/postgresql_adapter.rb +1 -1
- data/lib/switchman/version.rb +1 -1
- data/lib/tasks/switchman.rake +3 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 901b8608f750cdaacfabbb90b2894acb1e73adeb27438ccff1907376f41bbadd
|
4
|
+
data.tar.gz: 5dc547e878ef50027f013370010372dcb90fdda3ada531b5d6dc85cb902db633
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5b16b9e6ea387ec65f117e29d9b12b02287b4403474342b593fcefed333cc816137a596af01fa63eb78e5f37a44381177071e95453c8ea2d0bbb0366a3ce8992
|
7
|
+
data.tar.gz: af01a3a853f9c54151da689ba408e8443ee40ba97402175ae0e0cdf961af6f6c9396c57dee668d2a2c885ad22d313fbe4de9804a063317628500ff1636487ef7
|
@@ -129,6 +129,11 @@ module Switchman
|
|
129
129
|
cached_shards[id]
|
130
130
|
end
|
131
131
|
|
132
|
+
def preload_cache
|
133
|
+
cached_shards.reverse_merge!(active_shards.values.index_by(&:id))
|
134
|
+
cached_shards.reverse_merge!(all.index_by(&:id))
|
135
|
+
end
|
136
|
+
|
132
137
|
def clear_cache
|
133
138
|
cached_shards.clear
|
134
139
|
end
|
@@ -139,10 +144,9 @@ module Switchman
|
|
139
144
|
# * +categories+ - an array of categories to activate
|
140
145
|
# * +options+ -
|
141
146
|
# :parallel - true/false to execute in parallel, or a integer of how many
|
142
|
-
# sub-processes
|
143
|
-
#
|
144
|
-
#
|
145
|
-
# :max_procs - only run this many parallel processes at a time
|
147
|
+
# sub-processes. Note that parallel invocation currently uses
|
148
|
+
# forking, so should be used sparingly because you cannot get
|
149
|
+
# results back
|
146
150
|
# :exception - :ignore, :raise, :defer (wait until the end and raise the first
|
147
151
|
# error), or a proc
|
148
152
|
def with_each_shard(*args)
|
@@ -163,11 +167,14 @@ module Switchman
|
|
163
167
|
scope, categories = args
|
164
168
|
end
|
165
169
|
|
170
|
+
# back-compat
|
171
|
+
options[:parallel] = options.delete(:max_procs) if options.key?(:max_procs)
|
172
|
+
|
166
173
|
parallel = case options[:parallel]
|
167
174
|
when true
|
168
|
-
|
175
|
+
[Environment.cpu_count || 2, 2].min
|
169
176
|
when false, nil
|
170
|
-
|
177
|
+
1
|
171
178
|
else
|
172
179
|
options[:parallel]
|
173
180
|
end
|
@@ -178,47 +185,19 @@ module Switchman
|
|
178
185
|
scope = scope.order(::Arel.sql("database_server_id IS NOT NULL, database_server_id, id"))
|
179
186
|
end
|
180
187
|
|
181
|
-
if parallel >
|
182
|
-
max_procs = determine_max_procs(options.delete(:max_procs), parallel)
|
188
|
+
if parallel > 1
|
183
189
|
if ::ActiveRecord::Relation === scope
|
184
190
|
# still need a post-uniq, cause the default database server could be NULL or Rails.env in the db
|
185
191
|
database_servers = scope.reorder('database_server_id').select(:database_server_id).distinct.
|
186
192
|
map(&:database_server).compact.uniq
|
187
193
|
# nothing to do
|
188
194
|
return if database_servers.count == 0
|
189
|
-
parallel = [(max_procs.to_f / database_servers.count).ceil, parallel].min if max_procs
|
190
195
|
|
191
196
|
scopes = Hash[database_servers.map do |server|
|
192
|
-
|
193
|
-
if parallel == 1
|
194
|
-
subscopes = [server_scope]
|
195
|
-
else
|
196
|
-
subscopes = []
|
197
|
-
total = server_scope.count
|
198
|
-
ranges = []
|
199
|
-
server_scope.find_ids_in_ranges(:batch_size => (total.to_f / parallel).ceil) do |min, max|
|
200
|
-
ranges << [min, max]
|
201
|
-
end
|
202
|
-
# create a half-open range on the last one
|
203
|
-
ranges.last[1] = nil
|
204
|
-
ranges.each do |min, max|
|
205
|
-
subscope = server_scope.where("id>=?", min)
|
206
|
-
subscope = subscope.where("id<=?", max) if max
|
207
|
-
subscopes << subscope
|
208
|
-
end
|
209
|
-
end
|
210
|
-
[server, subscopes]
|
197
|
+
[server, server.shards.merge(scope)]
|
211
198
|
end]
|
212
199
|
else
|
213
200
|
scopes = scope.group_by(&:database_server)
|
214
|
-
if parallel > 1
|
215
|
-
parallel = [(max_procs.to_f / scopes.count).ceil, parallel].min if max_procs
|
216
|
-
scopes = Hash[scopes.map do |(server, shards)|
|
217
|
-
[server, shards.in_groups(parallel, false).compact]
|
218
|
-
end]
|
219
|
-
else
|
220
|
-
scopes = Hash[scopes.map { |(server, shards)| [server, [shards]] }]
|
221
|
-
end
|
222
201
|
end
|
223
202
|
|
224
203
|
exception_pipes = []
|
@@ -244,8 +223,8 @@ module Switchman
|
|
244
223
|
end
|
245
224
|
|
246
225
|
# only one process; don't bother forking
|
247
|
-
if scopes.length == 1
|
248
|
-
return with_each_shard(scopes.first.last
|
226
|
+
if scopes.length == 1
|
227
|
+
return with_each_shard(scopes.first.last, categories, options) { yield }
|
249
228
|
end
|
250
229
|
|
251
230
|
# clear connections prior to forking (no more queries will be executed in the parent,
|
@@ -253,84 +232,78 @@ module Switchman
|
|
253
232
|
# silly like dealloc'ing prepared statements)
|
254
233
|
::ActiveRecord::Base.clear_all_connections!
|
255
234
|
|
256
|
-
scopes.each do |server,
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
235
|
+
scopes.each do |server, subscope|
|
236
|
+
name = server.id
|
237
|
+
|
238
|
+
exception_pipe = IO.pipe
|
239
|
+
exception_pipes << exception_pipe
|
240
|
+
pid, io_in, io_out, io_err = Open4.pfork4(lambda do
|
241
|
+
begin
|
242
|
+
Switchman.config[:on_fork_proc]&.call
|
243
|
+
|
244
|
+
# set a pretty name for the process title, up to 128 characters
|
245
|
+
# (we don't actually know the limit, depending on how the process
|
246
|
+
# was started)
|
247
|
+
# first, simplify the binary name by stripping directories,
|
248
|
+
# then truncate arguments as necessary
|
249
|
+
bin = File.basename($0) # Process.argv0 doesn't work on Ruby 2.5 (https://bugs.ruby-lang.org/issues/15887)
|
250
|
+
max_length = 128 - bin.length - name.length - 3
|
251
|
+
args = ARGV.join(" ")
|
252
|
+
if max_length >= 0
|
253
|
+
args = args[0..max_length]
|
254
|
+
end
|
255
|
+
new_title = [bin, args, name].join(" ")
|
256
|
+
Process.setproctitle(new_title)
|
263
257
|
|
264
|
-
|
265
|
-
|
266
|
-
|
258
|
+
with_each_shard(subscope, categories, options) { yield }
|
259
|
+
exception_pipe.last.close
|
260
|
+
rescue Exception => e
|
267
261
|
begin
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
# first, simplify the binary name by stripping directories,
|
274
|
-
# then truncate arguments as necessary
|
275
|
-
bin = File.basename($0) # Process.argv0 doesn't work on Ruby 2.5 (https://bugs.ruby-lang.org/issues/15887)
|
276
|
-
max_length = 128 - bin.length - name.length - 3
|
277
|
-
args = ARGV.join(" ")
|
278
|
-
if max_length >= 0
|
279
|
-
args = args[0..max_length]
|
280
|
-
end
|
281
|
-
new_title = [bin, args, name].join(" ")
|
282
|
-
Process.setproctitle(new_title)
|
283
|
-
|
284
|
-
with_each_shard(subscope, categories, options) { yield }
|
285
|
-
exception_pipe.last.close
|
286
|
-
rescue Exception => e
|
287
|
-
begin
|
288
|
-
dumped = Marshal.dump(e)
|
289
|
-
dumped = nil if dumped.length > 64 * 1024
|
290
|
-
rescue
|
291
|
-
dumped = nil
|
292
|
-
end
|
262
|
+
dumped = Marshal.dump(e)
|
263
|
+
dumped = nil if dumped.length > 64 * 1024
|
264
|
+
rescue
|
265
|
+
dumped = nil
|
266
|
+
end
|
293
267
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
end
|
303
|
-
e2.set_backtrace(backtrace)
|
304
|
-
e2.instance_variable_set(:@active_shards, e.instance_variable_get(:@active_shards))
|
305
|
-
dumped = Marshal.dump(e2)
|
268
|
+
if dumped.nil?
|
269
|
+
# couldn't dump the exception; create a copy with just
|
270
|
+
# the message and the backtrace
|
271
|
+
e2 = e.class.new(e.message)
|
272
|
+
backtrace = e.backtrace
|
273
|
+
# truncate excessively long backtraces
|
274
|
+
if backtrace.length > 50
|
275
|
+
backtrace = backtrace[0...25] + ['...'] + backtrace[-25..-1]
|
306
276
|
end
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
exception_pipe.last.flush
|
311
|
-
exception_pipe.last.close
|
312
|
-
exit! 1
|
277
|
+
e2.set_backtrace(backtrace)
|
278
|
+
e2.instance_variable_set(:@active_shards, e.instance_variable_get(:@active_shards))
|
279
|
+
dumped = Marshal.dump(e2)
|
313
280
|
end
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
281
|
+
|
282
|
+
exception_pipe.last.set_encoding(dumped.encoding)
|
283
|
+
exception_pipe.last.write(dumped)
|
284
|
+
exception_pipe.last.flush
|
285
|
+
exception_pipe.last.close
|
286
|
+
exit! 1
|
287
|
+
end
|
288
|
+
end)
|
289
|
+
exception_pipe.last.close
|
290
|
+
pids << pid
|
291
|
+
io_in.close # don't care about writing to stdin
|
292
|
+
out_fds << io_out
|
293
|
+
err_fds << io_err
|
294
|
+
pid_to_name_map[pid] = name
|
295
|
+
fd_to_name_map[io_out] = name
|
296
|
+
fd_to_name_map[io_err] = name
|
297
|
+
|
298
|
+
while pids.count >= parallel
|
299
|
+
while out_fds.count >= parallel
|
300
|
+
# wait for output if we've hit the parallel limit
|
301
|
+
wait_for_output.call(out_fds, err_fds, fd_to_name_map)
|
333
302
|
end
|
303
|
+
# we've gotten all the output from one fd so wait for its child process to exit
|
304
|
+
found_pid, status = Process.wait2
|
305
|
+
pids.delete(found_pid)
|
306
|
+
errors << pid_to_name_map[found_pid] if status.exitstatus != 0
|
334
307
|
end
|
335
308
|
end
|
336
309
|
|
@@ -544,24 +517,6 @@ module Switchman
|
|
544
517
|
shard || source_shard || Shard.current
|
545
518
|
end
|
546
519
|
|
547
|
-
# given the provided option, determines whether we need to (and whether
|
548
|
-
# it's possible) to determine a reasonable default.
|
549
|
-
def determine_max_procs(max_procs_input, parallel_input=2)
|
550
|
-
max_procs = nil
|
551
|
-
if max_procs_input
|
552
|
-
max_procs = max_procs_input.to_i
|
553
|
-
max_procs = nil if max_procs == 0
|
554
|
-
else
|
555
|
-
return 1 if parallel_input.nil? || parallel_input < 1
|
556
|
-
cpus = Environment.cpu_count
|
557
|
-
if cpus && cpus > 0
|
558
|
-
max_procs = cpus * parallel_input
|
559
|
-
end
|
560
|
-
end
|
561
|
-
|
562
|
-
return max_procs
|
563
|
-
end
|
564
|
-
|
565
520
|
private
|
566
521
|
# in-process caching
|
567
522
|
def cached_shards
|
@@ -30,10 +30,13 @@ module Switchman
|
|
30
30
|
end
|
31
31
|
|
32
32
|
module Migrator
|
33
|
-
# significant change:
|
33
|
+
# significant change: just return MIGRATOR_SALT directly
|
34
|
+
# especially if you're going through pgbouncer, the database
|
35
|
+
# name you're accessing may not be consistent. it is NOT allowed
|
36
|
+
# to run migrations against multiple shards in the same database
|
37
|
+
# concurrently
|
34
38
|
def generate_migrator_advisory_lock_id
|
35
|
-
|
36
|
-
::ActiveRecord::Migrator::MIGRATOR_SALT * shard_name_hash
|
39
|
+
::ActiveRecord::Migrator::MIGRATOR_SALT
|
37
40
|
end
|
38
41
|
|
39
42
|
if ::Rails.version >= '6.0'
|
@@ -13,6 +13,15 @@ module Switchman
|
|
13
13
|
shard.activate(self.class.shard_category) { super }
|
14
14
|
end
|
15
15
|
end
|
16
|
+
|
17
|
+
def delete
|
18
|
+
db = shard.database_server
|
19
|
+
if ::GuardRail.environment != db.guard_rail_environment
|
20
|
+
return db.unguard { super }
|
21
|
+
else
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
16
25
|
end
|
17
26
|
end
|
18
|
-
end
|
27
|
+
end
|
@@ -102,7 +102,7 @@ module Switchman
|
|
102
102
|
WHERE i.relkind = 'i'
|
103
103
|
AND d.indisprimary = 'f'
|
104
104
|
AND t.relname = '#{table_name}'
|
105
|
-
AND
|
105
|
+
AND t.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = '#{shard.name}' )
|
106
106
|
ORDER BY i.relname
|
107
107
|
SQL
|
108
108
|
|
data/lib/switchman/version.rb
CHANGED
data/lib/tasks/switchman.rake
CHANGED
@@ -46,7 +46,8 @@ module Switchman
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def self.options
|
49
|
-
|
49
|
+
# we still pass through both of these options for back-compat purposes
|
50
|
+
{ parallel: ENV['PARALLEL']&.to_i, max_procs: ENV['MAX_PARALLEL_PROCS']&.to_i }
|
50
51
|
end
|
51
52
|
|
52
53
|
# categories - an array or proc, to activate as the current shard during the
|
@@ -89,7 +90,7 @@ module Switchman
|
|
89
90
|
nil
|
90
91
|
end
|
91
92
|
rescue => e
|
92
|
-
puts "Exception from #{e.current_shard.id}: #{e.current_shard.description}" if options[:parallel] != 0
|
93
|
+
puts "Exception from #{e.current_shard.id}: #{e.current_shard.description}" if options[:parallel].to_i != 0
|
93
94
|
raise
|
94
95
|
end
|
95
96
|
end
|
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: 2.
|
4
|
+
version: 2.2.2
|
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: 2021-
|
13
|
+
date: 2021-10-19 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: railties
|