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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 32a8870f7b32fb16b98edcd9cd0930c30359df0a71a73ecc63752b256b5ac616
4
- data.tar.gz: 90a3f31b1b8c5951720c7a45a3418c8448a5e08160939de73d487bfd8c9a7dc3
3
+ metadata.gz: cd39304f0dfae8329f95979c1cb2075d09a73cace1b2befa5f62c3100e863ca2
4
+ data.tar.gz: 742dd9cdbd63e3124b9ae4a9e0d73b56a071c43011f3ec0cdf43681f4977c116
5
5
  SHA512:
6
- metadata.gz: 017200b98b0c53d0004c2632322e63141284716609b50400bf5936bf776aa06a68a648e1ff11df0d2d715e1993e051ea0e103c0356d0658ff121283447a376e7
7
- data.tar.gz: 50cb3e155dd07d2d5e9171f788abce42b29b39d1f488d75cb97ae75842eda94876c4a9b0af134f1b0e03094bc14358c4b9ccc0677a83ebff741d948b9b496d5e
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 per database server. Note that parallel
148
- # invocation currently uses forking, so should be used sparingly
149
- # because errors are not raised, and you cannot get results back
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
- 1
175
+ [Environment.cpu_count || 2, 2].min
174
176
  when false, nil
175
- 0
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 > 0
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
- server_scope = server.shards.merge(scope)
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 && parallel == 1
253
- return with_each_shard(scopes.first.last.first, categories, options) { yield }
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, subscopes|
262
- subscopes.each_with_index do |subscope, idx|
263
- if subscopes.length > 1
264
- name = "#{server.id} #{idx + 1}"
265
- else
266
- name = server.id
267
- end
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
- exception_pipe = IO.pipe
270
- exception_pipes << exception_pipe
271
- pid, io_in, io_out, io_err = Open4.pfork4(lambda do
258
+ with_each_shard(subscope, categories, options) { yield }
259
+ exception_pipe.last.close
260
+ rescue Exception => e
272
261
  begin
273
- Switchman.config[:on_fork_proc]&.call
274
-
275
- # set a pretty name for the process title, up to 128 characters
276
- # (we don't actually know the limit, depending on how the process
277
- # was started)
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
- if dumped.nil?
300
- # couldn't dump the exception; create a copy with just
301
- # the message and the backtrace
302
- e2 = e.class.new(e.message)
303
- backtrace = e.backtrace
304
- # truncate excessively long backtraces
305
- if backtrace.length > 50
306
- backtrace = backtrace[0...25] + ['...'] + backtrace[-25..-1]
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
- exception_pipe.last.set_encoding(dumped.encoding)
314
- exception_pipe.last.write(dumped)
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
- end)
320
- exception_pipe.last.close
321
- pids << pid
322
- io_in.close # don't care about writing to stdin
323
- out_fds << io_out
324
- err_fds << io_err
325
- pid_to_name_map[pid] = name
326
- fd_to_name_map[io_out] = name
327
- fd_to_name_map[io_err] = name
328
-
329
- while max_procs && pids.count >= max_procs
330
- while max_procs && out_fds.count >= max_procs
331
- # wait for output if we've hit the max_procs limit
332
- wait_for_output.call(out_fds, err_fds, fd_to_name_map)
333
- end
334
- # we've gotten all the output from one fd so wait for its child process to exit
335
- found_pid, status = Process.wait2
336
- pids.delete(found_pid)
337
- errors << pid_to_name_map[found_pid] if status.exitstatus != 0
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: hash shard id, not database name
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
- shard_name_hash = Zlib.crc32(Shard.current.name)
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'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Switchman
4
- VERSION = "2.1.6"
4
+ VERSION = "2.2.0"
5
5
  end
@@ -46,6 +46,7 @@ module Switchman
46
46
  end
47
47
 
48
48
  def self.options
49
+ # we still pass through both of these options for back-compat purposes
49
50
  { parallel: ENV['PARALLEL'].to_i, max_procs: ENV['MAX_PARALLEL_PROCS'] }
50
51
  end
51
52
 
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.1.6
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-11 00:00:00.000000000 Z
13
+ date: 2021-08-20 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: railties