switchman 2.1.6 → 2.2.0
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 +78 -128
- data/lib/switchman/active_record/migration.rb +6 -3
- data/lib/switchman/version.rb +1 -1
- data/lib/tasks/switchman.rake +1 -0
- 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: cd39304f0dfae8329f95979c1cb2075d09a73cace1b2befa5f62c3100e863ca2
|
4
|
+
data.tar.gz: 742dd9cdbd63e3124b9ae4a9e0d73b56a071c43011f3ec0cdf43681f4977c116
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5355bcac300f60b1c2d626998ebd97a52871c4beff4bc597d2acc1f0439b7365b1a7478f91ec5a05bd8faa2df545e5444341ac296c826f1ab701372180a0282c
|
7
|
+
data.tar.gz: 54a69a012b6d48b91efd5417a71a536a9da8b02283284c5e9327121dcce0c458a1f065d6357012b9cbeba1dbba0603357a64505e69a16de886b9d34add5d8976
|
@@ -144,10 +144,9 @@ module Switchman
|
|
144
144
|
# * +categories+ - an array of categories to activate
|
145
145
|
# * +options+ -
|
146
146
|
# :parallel - true/false to execute in parallel, or a integer of how many
|
147
|
-
# sub-processes
|
148
|
-
#
|
149
|
-
#
|
150
|
-
# :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
|
151
150
|
# :exception - :ignore, :raise, :defer (wait until the end and raise the first
|
152
151
|
# error), or a proc
|
153
152
|
def with_each_shard(*args)
|
@@ -168,11 +167,14 @@ module Switchman
|
|
168
167
|
scope, categories = args
|
169
168
|
end
|
170
169
|
|
170
|
+
# back-compat
|
171
|
+
options[:parallel] = options.delete(:max_procs) if options.key?(:max_procs)
|
172
|
+
|
171
173
|
parallel = case options[:parallel]
|
172
174
|
when true
|
173
|
-
|
175
|
+
[Environment.cpu_count || 2, 2].min
|
174
176
|
when false, nil
|
175
|
-
|
177
|
+
1
|
176
178
|
else
|
177
179
|
options[:parallel]
|
178
180
|
end
|
@@ -183,47 +185,19 @@ module Switchman
|
|
183
185
|
scope = scope.order(::Arel.sql("database_server_id IS NOT NULL, database_server_id, id"))
|
184
186
|
end
|
185
187
|
|
186
|
-
if parallel >
|
187
|
-
max_procs = determine_max_procs(options.delete(:max_procs), parallel)
|
188
|
+
if parallel > 1
|
188
189
|
if ::ActiveRecord::Relation === scope
|
189
190
|
# still need a post-uniq, cause the default database server could be NULL or Rails.env in the db
|
190
191
|
database_servers = scope.reorder('database_server_id').select(:database_server_id).distinct.
|
191
192
|
map(&:database_server).compact.uniq
|
192
193
|
# nothing to do
|
193
194
|
return if database_servers.count == 0
|
194
|
-
parallel = [(max_procs.to_f / database_servers.count).ceil, parallel].min if max_procs
|
195
195
|
|
196
196
|
scopes = Hash[database_servers.map do |server|
|
197
|
-
|
198
|
-
if parallel == 1
|
199
|
-
subscopes = [server_scope]
|
200
|
-
else
|
201
|
-
subscopes = []
|
202
|
-
total = server_scope.count
|
203
|
-
ranges = []
|
204
|
-
server_scope.find_ids_in_ranges(:batch_size => (total.to_f / parallel).ceil) do |min, max|
|
205
|
-
ranges << [min, max]
|
206
|
-
end
|
207
|
-
# create a half-open range on the last one
|
208
|
-
ranges.last[1] = nil
|
209
|
-
ranges.each do |min, max|
|
210
|
-
subscope = server_scope.where("id>=?", min)
|
211
|
-
subscope = subscope.where("id<=?", max) if max
|
212
|
-
subscopes << subscope
|
213
|
-
end
|
214
|
-
end
|
215
|
-
[server, subscopes]
|
197
|
+
[server, server.shards.merge(scope)]
|
216
198
|
end]
|
217
199
|
else
|
218
200
|
scopes = scope.group_by(&:database_server)
|
219
|
-
if parallel > 1
|
220
|
-
parallel = [(max_procs.to_f / scopes.count).ceil, parallel].min if max_procs
|
221
|
-
scopes = Hash[scopes.map do |(server, shards)|
|
222
|
-
[server, shards.in_groups(parallel, false).compact]
|
223
|
-
end]
|
224
|
-
else
|
225
|
-
scopes = Hash[scopes.map { |(server, shards)| [server, [shards]] }]
|
226
|
-
end
|
227
201
|
end
|
228
202
|
|
229
203
|
exception_pipes = []
|
@@ -249,8 +223,8 @@ module Switchman
|
|
249
223
|
end
|
250
224
|
|
251
225
|
# only one process; don't bother forking
|
252
|
-
if scopes.length == 1
|
253
|
-
return with_each_shard(scopes.first.last
|
226
|
+
if scopes.length == 1
|
227
|
+
return with_each_shard(scopes.first.last, categories, options) { yield }
|
254
228
|
end
|
255
229
|
|
256
230
|
# clear connections prior to forking (no more queries will be executed in the parent,
|
@@ -258,84 +232,78 @@ module Switchman
|
|
258
232
|
# silly like dealloc'ing prepared statements)
|
259
233
|
::ActiveRecord::Base.clear_all_connections!
|
260
234
|
|
261
|
-
scopes.each do |server,
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
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)
|
268
257
|
|
269
|
-
|
270
|
-
|
271
|
-
|
258
|
+
with_each_shard(subscope, categories, options) { yield }
|
259
|
+
exception_pipe.last.close
|
260
|
+
rescue Exception => e
|
272
261
|
begin
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
# first, simplify the binary name by stripping directories,
|
279
|
-
# then truncate arguments as necessary
|
280
|
-
bin = File.basename($0) # Process.argv0 doesn't work on Ruby 2.5 (https://bugs.ruby-lang.org/issues/15887)
|
281
|
-
max_length = 128 - bin.length - name.length - 3
|
282
|
-
args = ARGV.join(" ")
|
283
|
-
if max_length >= 0
|
284
|
-
args = args[0..max_length]
|
285
|
-
end
|
286
|
-
new_title = [bin, args, name].join(" ")
|
287
|
-
Process.setproctitle(new_title)
|
288
|
-
|
289
|
-
with_each_shard(subscope, categories, options) { yield }
|
290
|
-
exception_pipe.last.close
|
291
|
-
rescue Exception => e
|
292
|
-
begin
|
293
|
-
dumped = Marshal.dump(e)
|
294
|
-
dumped = nil if dumped.length > 64 * 1024
|
295
|
-
rescue
|
296
|
-
dumped = nil
|
297
|
-
end
|
262
|
+
dumped = Marshal.dump(e)
|
263
|
+
dumped = nil if dumped.length > 64 * 1024
|
264
|
+
rescue
|
265
|
+
dumped = nil
|
266
|
+
end
|
298
267
|
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
end
|
308
|
-
e2.set_backtrace(backtrace)
|
309
|
-
e2.instance_variable_set(:@active_shards, e.instance_variable_get(:@active_shards))
|
310
|
-
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]
|
311
276
|
end
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
exception_pipe.last.flush
|
316
|
-
exception_pipe.last.close
|
317
|
-
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)
|
318
280
|
end
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
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)
|
338
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
|
339
307
|
end
|
340
308
|
end
|
341
309
|
|
@@ -549,24 +517,6 @@ module Switchman
|
|
549
517
|
shard || source_shard || Shard.current
|
550
518
|
end
|
551
519
|
|
552
|
-
# given the provided option, determines whether we need to (and whether
|
553
|
-
# it's possible) to determine a reasonable default.
|
554
|
-
def determine_max_procs(max_procs_input, parallel_input=2)
|
555
|
-
max_procs = nil
|
556
|
-
if max_procs_input
|
557
|
-
max_procs = max_procs_input.to_i
|
558
|
-
max_procs = nil if max_procs == 0
|
559
|
-
else
|
560
|
-
return 1 if parallel_input.nil? || parallel_input < 1
|
561
|
-
cpus = Environment.cpu_count
|
562
|
-
if cpus && cpus > 0
|
563
|
-
max_procs = cpus * parallel_input
|
564
|
-
end
|
565
|
-
end
|
566
|
-
|
567
|
-
return max_procs
|
568
|
-
end
|
569
|
-
|
570
520
|
private
|
571
521
|
# in-process caching
|
572
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'
|
data/lib/switchman/version.rb
CHANGED
data/lib/tasks/switchman.rake
CHANGED
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.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: 2021-08-
|
13
|
+
date: 2021-08-20 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: railties
|