switchman-inst-jobs 1.4.3 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/db/migrate/20200818130101_add_on_hold_to_switchman_shards.rb +5 -0
- data/db/migrate/20200822014259_add_block_stranded_to_switchman_shards.rb +5 -0
- data/lib/switchman_inst_jobs.rb +2 -0
- data/lib/switchman_inst_jobs/delayed/backend/base.rb +29 -6
- data/lib/switchman_inst_jobs/engine.rb +16 -0
- data/lib/switchman_inst_jobs/jobs_migrator.rb +271 -0
- data/lib/switchman_inst_jobs/switchman/default_shard.rb +4 -0
- data/lib/switchman_inst_jobs/switchman/shard.rb +53 -0
- data/lib/switchman_inst_jobs/timed_cache.rb +19 -0
- data/lib/switchman_inst_jobs/version.rb +1 -1
- metadata +25 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d279a1bceff0465d419fb835766fda41b6f682ba91ee8b76241238c632d08813
|
4
|
+
data.tar.gz: fb7c5eb1b6a9ab934e951021d489cb1292e4f944abce9163b48f2b2ef16bf976
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 20c649d58b0975d99e41b13c5fd39eeea73b59763f8075d36301cbdb88b1dd866e4fdaec8c8fa4a656ba9a0ceb166cd099c6b7a80727ce4ed709fbd0da3c0431
|
7
|
+
data.tar.gz: 39a3d57f786c8e02e922e5eaead0a415e3f1337fa4907490f9bc16ab00f153a363db599d5422a52a048b5621ee18e07e130912cbb5a4d07643712f15a3fabac1
|
data/lib/switchman_inst_jobs.rb
CHANGED
@@ -44,9 +44,11 @@ require 'switchman_inst_jobs/delayed/pool'
|
|
44
44
|
require 'switchman_inst_jobs/delayed/worker'
|
45
45
|
require 'switchman_inst_jobs/delayed/worker/health_check'
|
46
46
|
require 'switchman_inst_jobs/engine'
|
47
|
+
require 'switchman_inst_jobs/jobs_migrator'
|
47
48
|
require 'switchman_inst_jobs/new_relic'
|
48
49
|
require 'switchman_inst_jobs/shackles'
|
49
50
|
require 'switchman_inst_jobs/switchman/database_server'
|
50
51
|
require 'switchman_inst_jobs/switchman/default_shard'
|
51
52
|
require 'switchman_inst_jobs/switchman/shard'
|
53
|
+
require 'switchman_inst_jobs/timed_cache'
|
52
54
|
require 'switchman_inst_jobs/yaml_extensions'
|
@@ -13,16 +13,32 @@ module SwitchmanInstJobs
|
|
13
13
|
module Base
|
14
14
|
module ClassMethods
|
15
15
|
def enqueue(object, options = {})
|
16
|
+
::Switchman::Shard.periodic_clear_shard_cache
|
17
|
+
current_shard = ::Switchman::Shard.current
|
16
18
|
enqueue_options = options.merge(
|
17
|
-
current_shard:
|
19
|
+
current_shard: current_shard
|
18
20
|
)
|
21
|
+
enqueue_job = -> { ::Shackles.activate(:master) { super(object, enqueue_options) } }
|
19
22
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
23
|
+
# Another dj shard must be currently manually activated, so just use that
|
24
|
+
# In general this will only happen in unusual circumstances like tests
|
25
|
+
# also if migrations are running, always use the current shard's job shard
|
26
|
+
if ::ActiveRecord::Migration.open_migrations.zero? &&
|
27
|
+
current_shard.delayed_jobs_shard != ::Switchman::Shard.current(:delayed_jobs)
|
28
|
+
enqueue_job.call
|
24
29
|
else
|
25
|
-
::
|
30
|
+
::Switchman::Shard.default.activate do
|
31
|
+
current_shard = ::Switchman::Shard.lookup(current_shard.id)
|
32
|
+
current_job_shard = current_shard.delayed_jobs_shard
|
33
|
+
|
34
|
+
if (options[:singleton] || options[:strand]) && current_shard.block_stranded
|
35
|
+
enqueue_options[:next_in_strand] = false
|
36
|
+
end
|
37
|
+
|
38
|
+
current_job_shard.activate(:delayed_jobs) do
|
39
|
+
enqueue_job.call
|
40
|
+
end
|
41
|
+
end
|
26
42
|
end
|
27
43
|
end
|
28
44
|
|
@@ -54,8 +70,15 @@ module SwitchmanInstJobs
|
|
54
70
|
end
|
55
71
|
|
56
72
|
def current_shard=(shard)
|
73
|
+
@current_shard = nil
|
57
74
|
self.shard_id = shard.id
|
58
75
|
self.shard_id = nil if shard.is_a?(::Switchman::DefaultShard)
|
76
|
+
# If jobs are held for a shard, enqueue new ones as held as well
|
77
|
+
return unless shard.jobs_held
|
78
|
+
|
79
|
+
self.locked_by = ::Delayed::Backend::Base::ON_HOLD_LOCKED_BY
|
80
|
+
self.locked_at = ::Delayed::Job.db_time_now
|
81
|
+
self.attempts = ::Delayed::Backend::Base::ON_HOLD_COUNT
|
59
82
|
end
|
60
83
|
|
61
84
|
def invoke_job
|
@@ -16,6 +16,22 @@ module SwitchmanInstJobs
|
|
16
16
|
block.call(worker, config)
|
17
17
|
end
|
18
18
|
end
|
19
|
+
|
20
|
+
# Ensure jobs get unblocked on the new shard if they exist
|
21
|
+
::Delayed::Worker.lifecycle.after(:perform) do |_worker, job|
|
22
|
+
if job.strand
|
23
|
+
::Switchman::Shard.clear_cache
|
24
|
+
::Switchman::Shard.default.activate do
|
25
|
+
current_job_shard = ::Switchman::Shard.lookup(job.shard_id).delayed_jobs_shard
|
26
|
+
if current_job_shard != ::Switchman::Shard.current(:delayed_jobs)
|
27
|
+
current_job_shard.activate(:delayed_jobs) do
|
28
|
+
j = ::Delayed::Job.where(strand: job.strand).next_in_strand_order.first
|
29
|
+
j.update_column(:next_in_strand, true) if j && !j.next_in_strand
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
19
35
|
end
|
20
36
|
|
21
37
|
initializer 'sharding.shackles', after: 'switchman.extend_shackles' do
|
@@ -0,0 +1,271 @@
|
|
1
|
+
# Just disabling all the rubocop metrics for this file for now,
|
2
|
+
# as it is a direct port-in of existing code
|
3
|
+
|
4
|
+
# rubocop:disable Metrics/BlockLength, Metrics/MethodLength, Metrics/AbcSize, Metrics/ClassLength
|
5
|
+
require 'set'
|
6
|
+
require 'parallel'
|
7
|
+
|
8
|
+
module SwitchmanInstJobs
|
9
|
+
class JobsMigrator
|
10
|
+
class << self
|
11
|
+
def add_before_move_callback(proc)
|
12
|
+
@before_move_callbacks ||= []
|
13
|
+
@before_move_callbacks << proc
|
14
|
+
end
|
15
|
+
|
16
|
+
def transaction_on(shards, &block)
|
17
|
+
return yield if shards.empty?
|
18
|
+
|
19
|
+
shard = shards.pop
|
20
|
+
current_shard = ::Switchman::Shard.current(:delayed_jobs)
|
21
|
+
shard.activate(:delayed_jobs) do
|
22
|
+
::Delayed::Job.transaction do
|
23
|
+
current_shard.activate(:delayed_jobs) do
|
24
|
+
transaction_on(shards, &block)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def migrate_shards(shard_map)
|
31
|
+
source_shards = Set[]
|
32
|
+
shard_map.each do |(shard, target_shard)|
|
33
|
+
shard = ::Switchman::Shard.find(shard) unless shard.is_a?(::Switchman::Shard)
|
34
|
+
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)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Wait a little over the 60 second in-process shard cache clearing
|
40
|
+
# threshold to ensure that all new stranded jobs are now being
|
41
|
+
# enqueued with next_in_strand: false
|
42
|
+
Rails.logger.debug("Waiting for caches to clear (#{source_shard.id} -> #{target_shard.id})")
|
43
|
+
sleep(65) unless @skip_cache_wait
|
44
|
+
|
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 }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# This method expects that all relevant shards already have block_stranded: true
|
55
|
+
# but otherwise jobs can be running normally
|
56
|
+
def run
|
57
|
+
# Ensure this is never run with a dirty in-memory shard cache
|
58
|
+
::Switchman::Shard.clear_cache
|
59
|
+
migrate_strands
|
60
|
+
migrate_everything
|
61
|
+
end
|
62
|
+
|
63
|
+
def migrate_strands
|
64
|
+
# there are 4 scenarios to deal with here
|
65
|
+
# 1) no running job, no jobs moved: do nothing
|
66
|
+
# 2) running job, no jobs moved; create blocker with next_in_strand=false
|
67
|
+
# to prevent new jobs from immediately executing
|
68
|
+
# 3) running job, jobs moved; set next_in_strand=false on the first of
|
69
|
+
# those (= do nothing since it should already be false)
|
70
|
+
# 4) no running job, jobs moved: set next_in_strand=true on the first of
|
71
|
+
# those (= do nothing since it should already be true)
|
72
|
+
|
73
|
+
source_shard = ::Switchman::Shard.current(:delayed_jobs)
|
74
|
+
strand_scope = ::Delayed::Job.shard(source_shard).where('strand IS NOT NULL')
|
75
|
+
shard_map = build_shard_map(strand_scope, source_shard)
|
76
|
+
shard_map.each do |(target_shard, source_shard_ids)|
|
77
|
+
shard_scope = strand_scope.where(shard_id: source_shard_ids)
|
78
|
+
|
79
|
+
# 1) is taken care of because it should not show up here in strands
|
80
|
+
strands = shard_scope.distinct.order(:strand).pluck(:strand)
|
81
|
+
|
82
|
+
target_shard.activate(:delayed_jobs) do
|
83
|
+
strands.each do |strand|
|
84
|
+
transaction_on([source_shard, target_shard]) do
|
85
|
+
this_strand_scope = shard_scope.where(strand: strand)
|
86
|
+
# we want to copy all the jobs except the one that is still running.
|
87
|
+
jobs_scope = this_strand_scope.where(locked_by: nil)
|
88
|
+
|
89
|
+
# 2) and part of 3) are taken care of here by creating a blocker
|
90
|
+
# job with next_in_strand = false. as soon as the current
|
91
|
+
# running job is finished it should set next_in_strand
|
92
|
+
# We lock it to ensure that the jobs worker can't delete it until we are done moving the strand
|
93
|
+
# Since we only unlock it on the new jobs queue *after* deleting from the original
|
94
|
+
# 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
|
96
|
+
if first
|
97
|
+
first_job = ::Delayed::Job.create!(strand: strand, next_in_strand: false)
|
98
|
+
first_job.payload_object = ::Delayed::PerformableMethod.new(Kernel, :sleep, [0])
|
99
|
+
first_job.queue = first.queue
|
100
|
+
first_job.tag = 'Kernel.sleep'
|
101
|
+
first_job.source = 'JobsMigrator::StrandBlocker'
|
102
|
+
first_job.max_attempts = 1
|
103
|
+
# If we ever have jobs left over from 9999 jobs moves of a single shard,
|
104
|
+
# something has gone terribly wrong
|
105
|
+
first_job.strand_order_override = -9999
|
106
|
+
first_job.save!
|
107
|
+
# the rest of 3) is taken care of here
|
108
|
+
# 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)
|
111
|
+
end
|
112
|
+
|
113
|
+
# 4) is taken care of here, by leaveing next_in_strand alone and
|
114
|
+
# it should execute on the new shard
|
115
|
+
batch_move_jobs(
|
116
|
+
target_shard: target_shard,
|
117
|
+
source_shard: source_shard,
|
118
|
+
scope: jobs_scope
|
119
|
+
) do |job, new_job|
|
120
|
+
# This ensures jobs enqueued on the old jobs shard run before jobs on the new jobs queue
|
121
|
+
new_job.strand_order_override = job.strand_order_override - 1
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
::Switchman::Shard.find(source_shard_ids).each do |shard|
|
127
|
+
shard.update(block_stranded: false)
|
128
|
+
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
|
135
|
+
# At this time, let's unblock all the strands on the target shard that aren't being held by a blocker
|
136
|
+
# but actually could have run and we just didn't know it because we didn't know if they had jobs
|
137
|
+
# 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)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def migrate_everything
|
152
|
+
source_shard = ::Switchman::Shard.current(:delayed_jobs)
|
153
|
+
scope = ::Delayed::Job.shard(source_shard).where('strand IS NULL')
|
154
|
+
|
155
|
+
shard_map = build_shard_map(scope, source_shard)
|
156
|
+
shard_map.each do |(target_shard, source_shard_ids)|
|
157
|
+
batch_move_jobs(
|
158
|
+
target_shard: target_shard,
|
159
|
+
source_shard: source_shard,
|
160
|
+
scope: scope.where(shard_id: source_shard_ids).where(locked_by: nil)
|
161
|
+
)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
|
167
|
+
def build_shard_map(scope, source_shard)
|
168
|
+
shard_ids = scope.distinct.pluck(:shard_id)
|
169
|
+
|
170
|
+
shard_map = {}
|
171
|
+
::Switchman::Shard.find(shard_ids).each do |shard|
|
172
|
+
next if shard.delayed_jobs_shard == source_shard
|
173
|
+
|
174
|
+
shard_map[shard.delayed_jobs_shard] ||= []
|
175
|
+
shard_map[shard.delayed_jobs_shard] << shard.id
|
176
|
+
end
|
177
|
+
|
178
|
+
shard_map
|
179
|
+
end
|
180
|
+
|
181
|
+
def batch_move_jobs(target_shard:, source_shard:, scope:)
|
182
|
+
while scope.exists?
|
183
|
+
# Adapted from get_and_lock_next_available in delayed/backend/active_record.rb
|
184
|
+
target_jobs = scope.limit(1000).lock('FOR UPDATE SKIP LOCKED')
|
185
|
+
|
186
|
+
query = "WITH limited_jobs AS (#{target_jobs.to_sql}) " \
|
187
|
+
"UPDATE #{::Delayed::Job.quoted_table_name} " \
|
188
|
+
"SET locked_by = #{::Delayed::Job.connection.quote(::Delayed::Backend::Base::ON_HOLD_LOCKED_BY)}, " \
|
189
|
+
"locked_at = #{::Delayed::Job.connection.quote(::Delayed::Job.db_time_now)} "\
|
190
|
+
"FROM limited_jobs WHERE limited_jobs.id=#{::Delayed::Job.quoted_table_name}.id " \
|
191
|
+
"RETURNING #{::Delayed::Job.quoted_table_name}.*"
|
192
|
+
|
193
|
+
jobs = source_shard.activate(:delayed_jobs) { ::Delayed::Job.find_by_sql(query) }
|
194
|
+
new_jobs = jobs.map do |job|
|
195
|
+
new_job = job.dup
|
196
|
+
new_job.shard = target_shard
|
197
|
+
new_job.created_at = job.created_at
|
198
|
+
new_job.updated_at = job.updated_at
|
199
|
+
new_job.locked_at = nil
|
200
|
+
new_job.locked_by = nil
|
201
|
+
yield(job, new_job) if block_given?
|
202
|
+
@before_move_callbacks&.each do |proc|
|
203
|
+
proc.call(
|
204
|
+
old_job: job,
|
205
|
+
new_job: new_job
|
206
|
+
)
|
207
|
+
end
|
208
|
+
new_job
|
209
|
+
end
|
210
|
+
transaction_on([source_shard, target_shard]) do
|
211
|
+
target_shard.activate(:delayed_jobs) do
|
212
|
+
bulk_insert_jobs(new_jobs)
|
213
|
+
end
|
214
|
+
source_shard.activate(:delayed_jobs) do
|
215
|
+
::Delayed::Job.delete(jobs)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# This is adapted from the postgreql adapter in canvas-lms
|
222
|
+
# Once we stop supporting rails 5.2 we can just use insert_all from activerecord
|
223
|
+
def bulk_insert_jobs(objects)
|
224
|
+
records = objects.map do |object|
|
225
|
+
object.attributes.map do |(name, value)|
|
226
|
+
next if name == ::Delayed::Job.primary_key
|
227
|
+
|
228
|
+
if (type = ::Delayed::Job.attribute_types[name]).is_a?(::ActiveRecord::Type::Serialized)
|
229
|
+
value = type.serialize(value)
|
230
|
+
end
|
231
|
+
[name, value]
|
232
|
+
end.compact.to_h
|
233
|
+
end
|
234
|
+
return if records.length.zero?
|
235
|
+
|
236
|
+
keys = records.first.keys
|
237
|
+
|
238
|
+
connection = ::Delayed::Job.connection
|
239
|
+
quoted_keys = keys.map { |k| connection.quote_column_name(k) }.join(', ')
|
240
|
+
|
241
|
+
connection.execute "COPY #{::Delayed::Job.quoted_table_name} (#{quoted_keys}) FROM STDIN"
|
242
|
+
records.map do |record|
|
243
|
+
connection.raw_connection.put_copy_data(keys.map { |k| quote_text(record[k]) }.join("\t") + "\n")
|
244
|
+
end
|
245
|
+
connection.clear_query_cache
|
246
|
+
connection.raw_connection.put_copy_end
|
247
|
+
result = connection.raw_connection.get_result
|
248
|
+
begin
|
249
|
+
result.check
|
250
|
+
rescue StandardError => e
|
251
|
+
raise connection.send(:translate_exception, e, 'COPY FROM STDIN')
|
252
|
+
end
|
253
|
+
result.cmd_tuples
|
254
|
+
end
|
255
|
+
|
256
|
+
# See above comment...
|
257
|
+
def quote_text(value)
|
258
|
+
if value.nil?
|
259
|
+
'\\N'
|
260
|
+
elsif value.is_a?(::ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array::Data)
|
261
|
+
quote_text(encode_array(value))
|
262
|
+
else
|
263
|
+
hash = { "\n" => '\\n', "\r" => '\\r', "\t" => '\\t', '\\' => '\\\\' }
|
264
|
+
value.to_s.gsub(/[\n\r\t\\]/) { |c| hash[c] }
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
# rubocop:enable Metrics/BlockLength, Metrics/MethodLength, Metrics/AbcSize, Metrics/ClassLength
|
@@ -17,6 +17,51 @@ module SwitchmanInstJobs
|
|
17
17
|
database_server&.delayed_jobs_shard(self)
|
18
18
|
end
|
19
19
|
|
20
|
+
# Adapted from hold/unhold methods in base delayed jobs base
|
21
|
+
# Wait is required to be able to safely move jobs
|
22
|
+
def hold_jobs!(wait: false)
|
23
|
+
self.jobs_held = true
|
24
|
+
save! if changed?
|
25
|
+
delayed_jobs_shard.activate(:delayed_jobs) do
|
26
|
+
lock_jobs_for_hold
|
27
|
+
end
|
28
|
+
return unless wait
|
29
|
+
|
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?
|
34
|
+
sleep 10
|
35
|
+
lock_jobs_for_hold
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def unhold_jobs!
|
41
|
+
self.jobs_held = false
|
42
|
+
save! if changed?
|
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(
|
47
|
+
locked_by: nil,
|
48
|
+
locked_at: nil,
|
49
|
+
attempts: 0,
|
50
|
+
failed_at: nil
|
51
|
+
)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def lock_jobs_for_hold
|
58
|
+
::Delayed::Job.where(locked_at: nil, shard_id: id).in_batches(of: 10_000).update_all(
|
59
|
+
locked_by: ::Delayed::Backend::Base::ON_HOLD_LOCKED_BY,
|
60
|
+
locked_at: ::Delayed::Job.db_time_now,
|
61
|
+
attempts: ::Delayed::Backend::Base::ON_HOLD_COUNT
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
20
65
|
module ClassMethods
|
21
66
|
def clear_cache
|
22
67
|
super
|
@@ -57,6 +102,14 @@ module SwitchmanInstJobs
|
|
57
102
|
db.create_new_shard
|
58
103
|
end
|
59
104
|
|
105
|
+
def periodic_clear_shard_cache
|
106
|
+
# TODO: make this configurable
|
107
|
+
@timed_cache ||= TimedCache.new(-> { 60.to_i.seconds.ago }) do
|
108
|
+
::Switchman::Shard.clear_cache
|
109
|
+
end
|
110
|
+
@timed_cache.clear
|
111
|
+
end
|
112
|
+
|
60
113
|
def delayed_jobs_shards
|
61
114
|
unless instance_variable_defined?(:@delayed_jobs_shards)
|
62
115
|
# re-entrancy protection
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module SwitchmanInstJobs
|
2
|
+
class TimedCache
|
3
|
+
def initialize(timeout, &block)
|
4
|
+
@timeout = timeout
|
5
|
+
@block = block
|
6
|
+
@cached_at = Time.zone.now
|
7
|
+
end
|
8
|
+
|
9
|
+
def clear(force = false)
|
10
|
+
if force || @cached_at < @timeout.call
|
11
|
+
@block.call
|
12
|
+
@cached_at = Time.zone.now
|
13
|
+
true
|
14
|
+
else
|
15
|
+
false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: switchman-inst-jobs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
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-08-
|
11
|
+
date: 2020-08-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: inst-jobs
|
@@ -16,20 +16,34 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0.
|
19
|
+
version: '0.16'
|
20
20
|
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: '0.
|
22
|
+
version: '0.17'
|
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.
|
29
|
+
version: '0.16'
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: '0.
|
32
|
+
version: '0.17'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: parallel
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.19'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '1.19'
|
33
47
|
- !ruby/object:Gem::Dependency
|
34
48
|
name: railties
|
35
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -261,6 +275,8 @@ extra_rdoc_files: []
|
|
261
275
|
files:
|
262
276
|
- db/migrate/20170308045400_add_shard_id_to_delayed_jobs.rb
|
263
277
|
- db/migrate/20180628153808_set_search_paths_on_functions.rb
|
278
|
+
- db/migrate/20200818130101_add_on_hold_to_switchman_shards.rb
|
279
|
+
- db/migrate/20200822014259_add_block_stranded_to_switchman_shards.rb
|
264
280
|
- lib/switchman-inst-jobs.rb
|
265
281
|
- lib/switchman_inst_jobs.rb
|
266
282
|
- lib/switchman_inst_jobs/active_record/connection_adapters/postgresql_adapter.rb
|
@@ -271,11 +287,13 @@ files:
|
|
271
287
|
- lib/switchman_inst_jobs/delayed/worker.rb
|
272
288
|
- lib/switchman_inst_jobs/delayed/worker/health_check.rb
|
273
289
|
- lib/switchman_inst_jobs/engine.rb
|
290
|
+
- lib/switchman_inst_jobs/jobs_migrator.rb
|
274
291
|
- lib/switchman_inst_jobs/new_relic.rb
|
275
292
|
- lib/switchman_inst_jobs/shackles.rb
|
276
293
|
- lib/switchman_inst_jobs/switchman/database_server.rb
|
277
294
|
- lib/switchman_inst_jobs/switchman/default_shard.rb
|
278
295
|
- lib/switchman_inst_jobs/switchman/shard.rb
|
296
|
+
- lib/switchman_inst_jobs/timed_cache.rb
|
279
297
|
- lib/switchman_inst_jobs/version.rb
|
280
298
|
- lib/switchman_inst_jobs/yaml_extensions.rb
|
281
299
|
homepage: https://github.com/instructure/switchman-inst-jobs
|
@@ -297,7 +315,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
297
315
|
- !ruby/object:Gem::Version
|
298
316
|
version: '0'
|
299
317
|
requirements: []
|
300
|
-
rubygems_version: 3.
|
318
|
+
rubygems_version: 3.0.3
|
301
319
|
signing_key:
|
302
320
|
specification_version: 4
|
303
321
|
summary: Switchman and Instructure Jobs compatibility gem.
|