switchman 2.1.6 → 2.2.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 +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
|