switchman-inst-jobs 2.0.0 → 3.0.4

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: 9f1525c9c7e0f08abefab65094c1479bed9c042585b627022ef5f3d97f05a798
4
- data.tar.gz: d47f434d7869d9898e04222b232230c5b54b6c9c11dc390b387438d169dcec67
3
+ metadata.gz: f447f30d1f541b4fe85ad0d867276957b5ed583ddd01e70c509834c0441892bc
4
+ data.tar.gz: 677a35494a2b54c609017b335019a98510ca3db04e26e61778ad5a8abc0f9377
5
5
  SHA512:
6
- metadata.gz: 13ea49df7bfd9c7430c6d6261d11510697ce2c91e62e175cb94d12bc03eb0f6174b5153e35ddf1fe8357a5d20e389b13046aae42d9658016f3ecf7174691c45c
7
- data.tar.gz: 7c4ab126b6527b164aa5f278dda90755f15a39d9bc0c2d94cbbabac3d7c3f7c493fcef5009f7d35f4d836c756538d563e00ed3cc96657a7e356ee860c726c7f3
6
+ metadata.gz: 4dead22acfe97a4a5c06ba7ae348916c39e22cef000754c98d7b4e3ecb9a3312fb2fdffaba1e889812bd77efa9ab2d0ecc9ef6bba3d8325fafb18fe99f70278b
7
+ data.tar.gz: 05513257e9b30f44e2cb10ee5eb497a66906c38b8e525fb2373326ec81d6cb84df413f02283d46faf3a85d8e996f7da66cbf4b59335e23a743329ad1c1ef6f7c
@@ -29,7 +29,7 @@ class OptimizeDelayedJobs < ActiveRecord::Migration[4.2]
29
29
  add_index :delayed_jobs, %w[strand id], name: 'index_delayed_jobs_on_strand'
30
30
 
31
31
  # move all failed jobs to the new failed table
32
- Delayed::Backend::ActiveRecord::Job.where('failed_at IS NOT NULL').find_each do |job|
32
+ Delayed::Backend::ActiveRecord::Job.where.not(failed_at: nil).find_each do |job|
33
33
  job.fail! unless job.on_hold?
34
34
  end
35
35
  end
@@ -12,13 +12,13 @@ module SwitchmanInstJobs
12
12
  module Backend
13
13
  module Base
14
14
  module ClassMethods
15
- def enqueue(object, options = {})
15
+ def enqueue(object, **options)
16
16
  ::Switchman::Shard.periodic_clear_shard_cache
17
17
  current_shard = ::Switchman::Shard.current
18
18
  enqueue_options = options.merge(
19
19
  current_shard: current_shard
20
20
  )
21
- enqueue_job = -> { ::GuardRail.activate(:master) { super(object, enqueue_options) } }
21
+ enqueue_job = -> { ::GuardRail.activate(:master) { super(object, **enqueue_options) } }
22
22
 
23
23
  # Another dj shard must be currently manually activated, so just use that
24
24
  # In general this will only happen in unusual circumstances like tests
@@ -43,8 +43,8 @@ module SwitchmanInstJobs
43
43
  end
44
44
 
45
45
  def configured_shard_ids
46
- (::Delayed::Settings.worker_config.try(:[], 'workers') || [])
47
- .map { |w| w['shard'] }.compact.uniq
46
+ (::Delayed::Settings.worker_config.try(:[], 'workers') || []).
47
+ map { |w| w['shard'] }.compact.uniq
48
48
  end
49
49
 
50
50
  def processes_locked_locally
@@ -93,7 +93,7 @@ module SwitchmanInstJobs
93
93
  # likely a missing shard with a stale cache
94
94
  current_shard.send(:clear_cache)
95
95
  ::Switchman::Shard.clear_cache
96
- raise ShardNotFoundError, shard_id unless ::Switchman::Shard.where(id: shard_id).exists?
96
+ raise ShardNotFoundError, shard_id unless ::Switchman::Shard.exists?(id: shard_id)
97
97
 
98
98
  raise
99
99
  end
@@ -1,8 +1,9 @@
1
1
  module SwitchmanInstJobs
2
2
  module Delayed
3
3
  module MessageSending
4
- def send_later_enqueue_args(method, _enqueue_args = {}, *args)
5
- return send(method, *args) if ::Switchman::DatabaseServer.creating_new_shard
4
+ def delay(sender: nil, synchronous: false, **enqueue_args)
5
+ sender ||= __calculate_sender_for_delay
6
+ synchronous ||= ::Switchman::DatabaseServer.creating_new_shard
6
7
 
7
8
  super
8
9
  end
@@ -17,7 +17,7 @@ module SwitchmanInstJobs
17
17
  # We purposely don't .compact to remove nils here, since if any
18
18
  # workers are on the default jobs shard we want to unlock against
19
19
  # that shard too.
20
- shard_ids = @config[:workers].map { |c| c[:shard] }.uniq
20
+ shard_ids = @config[:workers].pluck(:shard).uniq
21
21
  shards = shard_ids.map { |shard_id| ::Delayed::Worker.shard(shard_id) }
22
22
  end
23
23
  ::Switchman::Shard.with_each_shard(shards, [:delayed_jobs]) do
@@ -19,7 +19,7 @@ module SwitchmanInstJobs
19
19
  end
20
20
 
21
21
  def reschedule_abandoned_jobs(call_super: false)
22
- shards = ::Switchman::Shard.delayed_jobs_shards
22
+ shards = ::Switchman::Shard.delayed_jobs_shards.to_a
23
23
  call_super = shards.first if shards.length == 1
24
24
  unless call_super == false
25
25
  call_super.activate(:delayed_jobs) do
@@ -32,9 +32,7 @@ module SwitchmanInstJobs
32
32
  singleton = <<~SINGLETON
33
33
  periodic: Delayed::Worker::HealthCheck.reschedule_abandoned_jobs:#{shard.id}
34
34
  SINGLETON
35
- send_later_enqueue_args(
36
- :reschedule_abandoned_jobs, { singleton: singleton }, call_super: shard
37
- )
35
+ delay(singleton: singleton).reschedule_abandoned_jobs(call_super: shard)
38
36
  end
39
37
  end
40
38
  end
@@ -29,26 +29,41 @@ module SwitchmanInstJobs
29
29
 
30
30
  def migrate_shards(shard_map)
31
31
  source_shards = Set[]
32
+ target_shards = Hash.new([])
32
33
  shard_map.each do |(shard, target_shard)|
33
34
  shard = ::Switchman::Shard.find(shard) unless shard.is_a?(::Switchman::Shard)
34
35
  source_shards << shard.delayed_jobs_shard.id
35
- # If target_shard is an int, it won't have an id, but we can just use it as is
36
- shard.update(delayed_jobs_shard_id: target_shard.try(:id) || target_shard, block_stranded: true)
36
+ target_shard = target_shard.try(:id) || target_shard
37
+ target_shards[target_shard] += [shard.id]
37
38
  end
38
39
 
40
+ # Do the updates in batches and then just clear redis instead of clearing them one at a time
41
+ target_shards.each do |target_shard, shards|
42
+ ::Switchman::Shard.where(id: shards).update_all(delayed_jobs_shard_id: target_shard, block_stranded: true)
43
+ end
44
+ clear_shard_cache
45
+
39
46
  # Wait a little over the 60 second in-process shard cache clearing
40
47
  # threshold to ensure that all new stranded jobs are now being
41
48
  # enqueued with next_in_strand: false
42
- Rails.logger.debug("Waiting for caches to clear (#{source_shard.id} -> #{target_shard.id})")
49
+ Rails.logger.debug('Waiting for caches to clear')
43
50
  sleep(65) unless @skip_cache_wait
44
51
 
45
- # TODO: 4 has been picked completely out of a hat. We should make it configurable or something
46
- Parallel.each(source_shards, in_processes: 4) do |s|
47
- # Ensure the child processes don't share connections with the parent
48
- Delayed::Pool.on_fork.call
49
- ActiveRecord::Base.clear_all_connections!
50
- s.activate(:delayed_jobs) { run }
52
+ ::Switchman::Shard.clear_cache
53
+ # rubocop:disable Style/CombinableLoops
54
+ # We first migrate strands so that we can stop blocking strands before we migrate unstranded jobs
55
+ source_shards.each do |s|
56
+ ::Switchman::Shard.lookup(s).activate(:delayed_jobs) { migrate_strands }
57
+ end
58
+
59
+ source_shards.each do |s|
60
+ ::Switchman::Shard.lookup(s).activate(:delayed_jobs) { migrate_everything }
51
61
  end
62
+ # rubocop:enable Style/CombinableLoops
63
+ end
64
+
65
+ def clear_shard_cache
66
+ ::Switchman.cache.clear
52
67
  end
53
68
 
54
69
  # This method expects that all relevant shards already have block_stranded: true
@@ -71,7 +86,7 @@ module SwitchmanInstJobs
71
86
  # those (= do nothing since it should already be true)
72
87
 
73
88
  source_shard = ::Switchman::Shard.current(:delayed_jobs)
74
- strand_scope = ::Delayed::Job.shard(source_shard).where('strand IS NOT NULL')
89
+ strand_scope = ::Delayed::Job.shard(source_shard).where.not(strand: nil)
75
90
  shard_map = build_shard_map(strand_scope, source_shard)
76
91
  shard_map.each do |(target_shard, source_shard_ids)|
77
92
  shard_scope = strand_scope.where(shard_id: source_shard_ids)
@@ -92,10 +107,10 @@ module SwitchmanInstJobs
92
107
  # We lock it to ensure that the jobs worker can't delete it until we are done moving the strand
93
108
  # Since we only unlock it on the new jobs queue *after* deleting from the original
94
109
  # the lock ensures the blocker always gets unlocked
95
- first = this_strand_scope.where('locked_by IS NOT NULL').next_in_strand_order.lock.first
110
+ first = this_strand_scope.where.not(locked_by: nil).next_in_strand_order.lock.first
96
111
  if first
97
112
  first_job = ::Delayed::Job.create!(strand: strand, next_in_strand: false)
98
- first_job.payload_object = ::Delayed::PerformableMethod.new(Kernel, :sleep, [0])
113
+ first_job.payload_object = ::Delayed::PerformableMethod.new(Kernel, :sleep, args: [0])
99
114
  first_job.queue = first.queue
100
115
  first_job.tag = 'Kernel.sleep'
101
116
  first_job.source = 'JobsMigrator::StrandBlocker'
@@ -106,8 +121,8 @@ module SwitchmanInstJobs
106
121
  first_job.save!
107
122
  # the rest of 3) is taken care of here
108
123
  # make sure that all the jobs moved over are NOT next in strand
109
- ::Delayed::Job.where(next_in_strand: true, strand: strand, locked_by: nil)
110
- .update_all(next_in_strand: false)
124
+ ::Delayed::Job.where(next_in_strand: true, strand: strand, locked_by: nil).
125
+ update_all(next_in_strand: false)
111
126
  end
112
127
 
113
128
  # 4) is taken care of here, by leaveing next_in_strand alone and
@@ -123,26 +138,37 @@ module SwitchmanInstJobs
123
138
  end
124
139
  end
125
140
 
126
- ::Switchman::Shard.find(source_shard_ids).each do |shard|
127
- shard.update(block_stranded: false)
141
+ updated = ::Switchman::Shard.where(id: source_shard_ids, block_stranded: true).
142
+ update_all(block_stranded: false)
143
+ # If this is being manually re-run for some reason to clean something up, don't wait for nothing to happen
144
+ unless updated.zero?
145
+ clear_shard_cache
146
+ # Wait a little over the 60 second in-process shard cache clearing
147
+ # threshold to ensure that all new stranded jobs are now being
148
+ # enqueued with next_in_strand: false
149
+ Rails.logger.debug("Waiting for caches to clear (#{source_shard.id} -> #{target_shard.id})")
150
+ # for spec usage only
151
+ sleep(65) unless @skip_cache_wait
128
152
  end
129
- # Wait a little over the 60 second in-process shard cache clearing
130
- # threshold to ensure that all new stranded jobs are now being
131
- # enqueued with next_in_strand: false
132
- Rails.logger.debug("Waiting for caches to clear (#{source_shard.id} -> #{target_shard.id})")
133
- # for spec usage only
134
- sleep(65) unless @skip_cache_wait
153
+ ::Switchman::Shard.clear_cache
135
154
  # At this time, let's unblock all the strands on the target shard that aren't being held by a blocker
136
155
  # but actually could have run and we just didn't know it because we didn't know if they had jobs
137
156
  # on the source shard
138
- # rubocop:disable Layout/LineLength
139
- strands_to_unblock = shard_scope.where.not(source: 'JobsMigrator::StrandBlocker')
140
- .distinct
141
- .where("NOT EXISTS (SELECT 1 FROM #{::Delayed::Job.quoted_table_name} dj2 WHERE delayed_jobs.strand=dj2.strand AND next_in_strand)")
142
- .pluck(:strand)
143
- # rubocop:enable Layout/LineLength
144
- strands_to_unblock.each do |strand|
145
- Delayed::Job.where(strand: strand).next_in_strand_order.first.update_attribute(:next_in_strand, true)
157
+ target_shard.activate(:delayed_jobs) do
158
+ loop do
159
+ # We only want to unlock stranded jobs where they don't belong to a blocked shard (if they *do* belong)
160
+ # to a blocked shard, they must be part of a concurrent jobs migration from a different source shard to
161
+ # this target shard, so we shouldn't unlock them yet. We only ever unlock one job here to keep the
162
+ # logic cleaner; if the job is n-stranded, after the first one runs, the trigger will unlock larger
163
+ # batches
164
+ break if ::Delayed::Job.where(id: ::Delayed::Job.select('DISTINCT ON (strand) id').
165
+ where.not(strand: nil).
166
+ where.not(shard_id: ::Switchman::Shard.where(block_stranded: true).pluck(:id)).where(
167
+ ::Delayed::Job.select(1).from("#{::Delayed::Job.quoted_table_name} dj2").
168
+ where("dj2.next_in_strand = true OR dj2.source = 'JobsMigrator::StrandBlocker'").
169
+ where('dj2.strand = delayed_jobs.strand').arel.exists.not
170
+ ).order(:strand, :strand_order_override, :id)).limit(500).update_all(next_in_strand: true).zero?
171
+ end
146
172
  end
147
173
  end
148
174
  end
@@ -242,7 +268,7 @@ module SwitchmanInstJobs
242
268
 
243
269
  connection.execute "COPY #{::Delayed::Job.quoted_table_name} (#{quoted_keys}) FROM STDIN"
244
270
  records.map do |record|
245
- connection.raw_connection.put_copy_data(keys.map { |k| quote_text(record[k]) }.join("\t") + "\n")
271
+ connection.raw_connection.put_copy_data("#{keys.map { |k| quote_text(record[k]) }.join("\t")}\n")
246
272
  end
247
273
  connection.clear_query_cache
248
274
  connection.raw_connection.put_copy_end
@@ -11,7 +11,7 @@ module SwitchmanInstJobs
11
11
  # shard's delayed_jobs_shard
12
12
  if shard&.default?
13
13
  # first look for any shard that behaves like a jobs shard
14
- dj_shard ||= ::Switchman::Shard.delayed_jobs_shards.first
14
+ dj_shard ||= ::Switchman::Shard.delayed_jobs_shards.find(&:database_server)
15
15
  # we're really truly out of options, use the default shard itself
16
16
  dj_shard ||= shard
17
17
  end
@@ -28,9 +28,9 @@ module SwitchmanInstJobs
28
28
  return unless wait
29
29
 
30
30
  delayed_jobs_shard.activate(:delayed_jobs) do
31
- while ::Delayed::Job.where(shard_id: id)
32
- .where.not(locked_at: nil)
33
- .where.not(locked_by: ::Delayed::Backend::Base::ON_HOLD_LOCKED_BY).exists?
31
+ while ::Delayed::Job.where(shard_id: id).
32
+ where.not(locked_at: nil).
33
+ where.not(locked_by: ::Delayed::Backend::Base::ON_HOLD_LOCKED_BY).exists?
34
34
  sleep 10
35
35
  lock_jobs_for_hold
36
36
  end
@@ -41,9 +41,9 @@ module SwitchmanInstJobs
41
41
  self.jobs_held = false
42
42
  save! if changed?
43
43
  delayed_jobs_shard.activate(:delayed_jobs) do
44
- ::Delayed::Job.where(locked_by: ::Delayed::Backend::Base::ON_HOLD_LOCKED_BY, shard_id: id)
45
- .in_batches(of: 10_000)
46
- .update_all(
44
+ ::Delayed::Job.where(locked_by: ::Delayed::Backend::Base::ON_HOLD_LOCKED_BY, shard_id: id).
45
+ in_batches(of: 10_000).
46
+ update_all(
47
47
  locked_by: nil,
48
48
  locked_at: nil,
49
49
  attempts: 0,
@@ -111,32 +111,27 @@ module SwitchmanInstJobs
111
111
  end
112
112
 
113
113
  def delayed_jobs_shards
114
- unless instance_variable_defined?(:@delayed_jobs_shards)
115
- # re-entrancy protection
116
- @delayed_jobs_shards = begin
117
- shard_dj_shards = [] unless ::Switchman::Shard.columns_hash.key?('delayed_jobs_shard_id')
118
- shard_dj_shards ||= begin
119
- ::Switchman::Shard
120
- .where.not(delayed_jobs_shard_id: nil)
121
- .distinct
122
- .pluck(:delayed_jobs_shard_id)
123
- .map { |id| ::Switchman::Shard.lookup(id) }
124
- .compact
125
- end
126
- # set it temporarily, to avoid the default shard falling back to itself
127
- # if other shards are usable
128
- @delayed_jobs_shards = shard_dj_shards.uniq.sort
129
-
130
- db_dj_shards = ::Switchman::DatabaseServer.all.map do |db|
131
- next db.shards.to_a if db.config[:delayed_jobs_shard] == 'self'
132
-
133
- db.delayed_jobs_shard
134
- end.compact.flatten.uniq # yes, all three
135
-
136
- (db_dj_shards + shard_dj_shards).uniq.sort
137
- end
114
+ return none unless ::Switchman::Shard.columns_hash.key?('delayed_jobs_shard_id')
115
+
116
+ scope = ::Switchman::Shard.unscoped.
117
+ where(id: ::Switchman::Shard.unscoped.distinct.where.not(delayed_jobs_shard_id: nil).
118
+ select(:delayed_jobs_shard_id))
119
+ db_jobs_shards = ::Switchman::DatabaseServer.all.map { |db| db.config[:delayed_jobs_shard] }.uniq
120
+ db_jobs_shards.delete(nil)
121
+ has_self = db_jobs_shards.delete('self')
122
+ scope = scope.or(::Switchman::Shard.unscoped.where(id: db_jobs_shards)) unless db_jobs_shards.empty?
123
+
124
+ if has_self
125
+ self_dbs = ::Switchman::DatabaseServer.all.
126
+ select { |db| db.config[:delayed_jobs_shard] == 'self' }.map(&:id)
127
+ scope = scope.or(::Switchman::Shard.unscoped.
128
+ where(id: ::Switchman::Shard.unscoped.where(delayed_jobs_shard_id: nil, database_server_id: self_dbs).
129
+ select(:id)))
138
130
  end
139
- @delayed_jobs_shards
131
+ @jobs_scope_empty = !scope.exists? unless instance_variable_defined?(:@jobs_scope_empty)
132
+ return [::Switchman::Shard.default] if @jobs_scope_empty
133
+
134
+ ::Switchman::Shard.merge(scope)
140
135
  end
141
136
  end
142
137
  end
@@ -6,7 +6,7 @@ module SwitchmanInstJobs
6
6
  @cached_at = Time.zone.now
7
7
  end
8
8
 
9
- def clear(force = false)
9
+ def clear(force: false)
10
10
  if force || @cached_at < @timeout.call
11
11
  @block.call
12
12
  @cached_at = Time.zone.now
@@ -1,3 +1,3 @@
1
1
  module SwitchmanInstJobs
2
- VERSION = '2.0.0'.freeze
2
+ VERSION = '3.0.4'.freeze
3
3
  end
metadata CHANGED
@@ -1,35 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: switchman-inst-jobs
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 3.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryan Petty
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-06 00:00:00.000000000 Z
11
+ date: 2020-11-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: inst-jobs
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.16'
20
- - - "<"
19
+ version: '1.0'
20
+ - - ">="
21
21
  - !ruby/object:Gem::Version
22
- version: '0.17'
22
+ version: 1.0.3
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
- - - ">="
27
+ - - "~>"
28
28
  - !ruby/object:Gem::Version
29
- version: '0.16'
30
- - - "<"
29
+ version: '1.0'
30
+ - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: '0.17'
32
+ version: 1.0.3
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: parallel
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -168,14 +168,14 @@ dependencies:
168
168
  requirements:
169
169
  - - "~>"
170
170
  - !ruby/object:Gem::Version
171
- version: '12'
171
+ version: '13'
172
172
  type: :development
173
173
  prerelease: false
174
174
  version_requirements: !ruby/object:Gem::Requirement
175
175
  requirements:
176
176
  - - "~>"
177
177
  - !ruby/object:Gem::Version
178
- version: '12'
178
+ version: '13'
179
179
  - !ruby/object:Gem::Dependency
180
180
  name: rspec
181
181
  requirement: !ruby/object:Gem::Requirement
@@ -210,28 +210,28 @@ dependencies:
210
210
  requirements:
211
211
  - - "~>"
212
212
  - !ruby/object:Gem::Version
213
- version: 0.79.0
213
+ version: 1.3.1
214
214
  type: :development
215
215
  prerelease: false
216
216
  version_requirements: !ruby/object:Gem::Requirement
217
217
  requirements:
218
218
  - - "~>"
219
219
  - !ruby/object:Gem::Version
220
- version: 0.79.0
220
+ version: 1.3.1
221
221
  - !ruby/object:Gem::Dependency
222
222
  name: rubocop-rails
223
223
  requirement: !ruby/object:Gem::Requirement
224
224
  requirements:
225
225
  - - "~>"
226
226
  - !ruby/object:Gem::Version
227
- version: 2.4.2
227
+ version: 2.8.1
228
228
  type: :development
229
229
  prerelease: false
230
230
  version_requirements: !ruby/object:Gem::Requirement
231
231
  requirements:
232
232
  - - "~>"
233
233
  - !ruby/object:Gem::Version
234
- version: 2.4.2
234
+ version: 2.8.1
235
235
  - !ruby/object:Gem::Dependency
236
236
  name: simplecov
237
237
  requirement: !ruby/object:Gem::Requirement
@@ -334,7 +334,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
334
334
  - !ruby/object:Gem::Version
335
335
  version: '0'
336
336
  requirements: []
337
- rubygems_version: 3.1.2
337
+ rubygems_version: 3.1.4
338
338
  signing_key:
339
339
  specification_version: 4
340
340
  summary: Switchman and Instructure Jobs compatibility gem.