maintenance_tasks 2.3.1 → 2.3.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +77 -18
- data/app/controllers/maintenance_tasks/runs_controller.rb +1 -1
- data/app/models/maintenance_tasks/task.rb +15 -3
- data/app/models/maintenance_tasks/task_data_index.rb +1 -1
- data/app/views/maintenance_tasks/tasks/show.html.erb +1 -1
- data/lib/maintenance_tasks/cli.rb +15 -9
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1f292dbc04fbbb71588c0be3af0bc95bfb5de5ca27f9aa7562b681cad39c6241
|
4
|
+
data.tar.gz: d631953126ffbd114721fea778fc8d43f5c86c20461c04943c3127a1b6f9e0d3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 146510c2a63f74b6084b43f9fa939c017e242b79ae0c453008b17b8e5c5e2332bd0d972ab4d8dce4d9d365836bcada8df8a7f1a7d717dc1d733232edd0830a4e
|
7
|
+
data.tar.gz: d1df2c04b6274843b32e4feb44a4bfb1eec3e64717ae67d78bbb440c08c34f24f63882d880dfc602529b2971f9bbfc9ce74345b848ecd3f780d1ab9270a610ec
|
data/README.md
CHANGED
@@ -1,9 +1,63 @@
|
|
1
|
-
#
|
1
|
+
# Maintenance Tasks
|
2
2
|
|
3
3
|
A Rails engine for queuing and managing maintenance tasks.
|
4
4
|
|
5
|
+
By ”maintenance task”, this project means a data migration, i.e. code that
|
6
|
+
changes data in the database, often to support schema migrations. For example,
|
7
|
+
in order to introduce a new `NOT NULL` column, it has to be added as nullable
|
8
|
+
first, backfilled with values, before finally being changed to `NOT NULL`.
|
9
|
+
This engine helps with the second part of this process, backfilling.
|
10
|
+
|
11
|
+
Maintenance tasks are collection-based tasks, usually using Active Record,
|
12
|
+
that update the data in your database. They can be paused or interrupted.
|
13
|
+
Maintenance tasks can operate [in batches](#processing-batch-collections) and
|
14
|
+
use [throttling](#throttling) to control the load on your database.
|
15
|
+
|
16
|
+
Maintenance tasks aren't meant to happen on a regular basis. They're used as
|
17
|
+
needed, or as one-offs. Normally maintenance tasks are ephemeral, so they are
|
18
|
+
used briefly and then deleted.
|
19
|
+
|
20
|
+
The Rails engine has a web-based UI for listing maintenance tasks, seeing
|
21
|
+
their status, and starting, pausing and restarting them.
|
22
|
+
|
5
23
|
[![Link to demo video](static/demo.png)](https://www.youtube.com/watch?v=BTuvTQxlFzs)
|
6
24
|
|
25
|
+
## Should I Use Maintenance Tasks?
|
26
|
+
|
27
|
+
Maintenance tasks have a limited, specific job UI. While the engine can be
|
28
|
+
used to provide a user interface for other data changes, such as data changes
|
29
|
+
for support requests, we recommend you use regular application code for those
|
30
|
+
use cases instead. These inevitably require more flexibility than this engine
|
31
|
+
will be able to provide.
|
32
|
+
|
33
|
+
If your task shouldn't run as an Active Job, it probably isn't a good match
|
34
|
+
for this gem. If your task doesn't need to run in the background,
|
35
|
+
consider a runner script instead. If your task doesn't need to be
|
36
|
+
interruptible, consider a normal Active Job.
|
37
|
+
|
38
|
+
Maintenance tasks can be interrupted between iterations. If your task [isn't
|
39
|
+
collection-based](#tasks-that-dont-need-a-collection) (no CSV file or database
|
40
|
+
table) or has very large batches, it will get limited benefit from throttling
|
41
|
+
(pausing between iterations) or interrupting. This might be fine, or the added
|
42
|
+
complexity of maintenance Tasks over normal Active Jobs may not be worthwhile.
|
43
|
+
|
44
|
+
If your task updates your database schema instead of data, use a migration
|
45
|
+
instead of a maintenance task.
|
46
|
+
|
47
|
+
If your task happens regularly, consider Active Jobs with a scheduler or cron,
|
48
|
+
[job-iteration jobs](https://github.com/shopify/job-iteration) and/or [custom
|
49
|
+
rails_admin UIs][rails-admin-engines] instead of the Maintenance Tasks gem.
|
50
|
+
Maintenance tasks should be ephemeral, to suit their intentionally limited UI.
|
51
|
+
|
52
|
+
To create seed data for a new application, use the provided Rails
|
53
|
+
`db/seeds.rb` file instead.
|
54
|
+
|
55
|
+
If your application can't handle a half-completed migration, maintenance tasks
|
56
|
+
are probably the wrong tool. Remember that maintenance tasks are intentionally
|
57
|
+
pausable and can be cancelled halfway.
|
58
|
+
|
59
|
+
[rails-admin-engines]: https://www.ruby-toolbox.com/categories/rails_admin_interfaces
|
60
|
+
|
7
61
|
## Installation
|
8
62
|
|
9
63
|
To install the gem and run the install generator, execute:
|
@@ -168,6 +222,9 @@ Note that `#count` is calculated automatically based on the number of batches in
|
|
168
222
|
your collection, and your Task’s progress will be displayed in terms of batches
|
169
223
|
(not the total number of rows in your CSV).
|
170
224
|
|
225
|
+
Non-batched CSV tasks will have an effective batch size of 1, which can reduce
|
226
|
+
the efficiency of your database operations.
|
227
|
+
|
171
228
|
### Processing Batch Collections
|
172
229
|
|
173
230
|
The Maintenance Tasks gem supports processing Active Records in batches. This
|
@@ -213,7 +270,7 @@ inside `#process`.
|
|
213
270
|
### Tasks that don’t need a Collection
|
214
271
|
|
215
272
|
Sometimes, you might want to run a Task that performs a single operation, such
|
216
|
-
as enqueuing another background job or
|
273
|
+
as enqueuing another background job or querying an external API. The gem supports
|
217
274
|
collection-less tasks.
|
218
275
|
|
219
276
|
Generate a collection-less Task by running:
|
@@ -242,10 +299,12 @@ end
|
|
242
299
|
|
243
300
|
### Throttling
|
244
301
|
|
245
|
-
Maintenance
|
302
|
+
Maintenance tasks often modify a lot of data and can be taxing on your database.
|
246
303
|
The gem provides a throttling mechanism that can be used to throttle a Task when
|
247
|
-
a given condition is met. If a Task is throttled
|
248
|
-
retried after a backoff period has passed. The
|
304
|
+
a given condition is met. If a Task is throttled (the throttle block returns
|
305
|
+
true), it will be interrupted and retried after a backoff period has passed. The
|
306
|
+
default backoff is 30 seconds.
|
307
|
+
|
249
308
|
Specify the throttle condition as a block:
|
250
309
|
|
251
310
|
```ruby
|
@@ -277,7 +336,7 @@ Tasks can define multiple throttle conditions. Throttle conditions are inherited
|
|
277
336
|
by descendants, and new conditions will be appended without impacting existing
|
278
337
|
conditions.
|
279
338
|
|
280
|
-
The backoff can also be specified as a Proc:
|
339
|
+
The backoff can also be specified as a Proc that receives no arguments:
|
281
340
|
|
282
341
|
```ruby
|
283
342
|
# app/tasks/maintenance/update_posts_throttled_task.rb
|
@@ -362,7 +421,7 @@ module Maintenance
|
|
362
421
|
after_error :dangerous_notify
|
363
422
|
|
364
423
|
def dangerous_notify
|
365
|
-
# This error is rescued in favour of the original error causing the error flow.
|
424
|
+
# This error is rescued and ignored in favour of the original error causing the error flow.
|
366
425
|
raise NotDeliveredError
|
367
426
|
end
|
368
427
|
|
@@ -392,21 +451,21 @@ end
|
|
392
451
|
|
393
452
|
### Considerations when writing Tasks
|
394
453
|
|
395
|
-
|
454
|
+
Maintenance Tasks relies on the queue adapter configured for your application to
|
396
455
|
run the job which is processing your Task. The guidelines for writing Task may
|
397
456
|
depend on the queue adapter but in general, you should follow these rules:
|
398
457
|
|
399
458
|
* Duration of `Task#process`: processing a single element of the collection
|
400
459
|
should take less than 25 seconds, or the duration set as a timeout for Sidekiq
|
401
|
-
or the queue adapter configured in your application.
|
402
|
-
safely interrupted and resumed.
|
460
|
+
or the queue adapter configured in your application. Short batches allow the
|
461
|
+
Task to be safely interrupted and resumed.
|
403
462
|
* Idempotency of `Task#process`: it should be safe to run `process` multiple
|
404
463
|
times for the same element of the collection. Read more in [this Sidekiq best
|
405
464
|
practice][sidekiq-idempotent]. It’s important if the Task errors and you run
|
406
|
-
it again, because the same element that
|
407
|
-
again. It especially matters in the situation described
|
408
|
-
iteration duration exceeds the timeout: if the job is
|
409
|
-
elements may be processed again.
|
465
|
+
it again, because the same element that caused the Task to give an error may
|
466
|
+
well be processed again. It especially matters in the situation described
|
467
|
+
above, when the iteration duration exceeds the timeout: if the job is
|
468
|
+
re-enqueued, multiple elements may be processed again.
|
410
469
|
|
411
470
|
[sidekiq-idempotent]: https://github.com/mperham/sidekiq/wiki/Best-Practices#2-make-your-job-idempotent-and-transactional
|
412
471
|
|
@@ -636,8 +695,8 @@ re-enqueue the jobs that were in progress, the Task may be in a seemingly stuck
|
|
636
695
|
situation where it appears to be running but is not. In that situation, pausing
|
637
696
|
or cancelling it will not result in the Task being paused or cancelled, as the
|
638
697
|
Task will get stuck in a state of `pausing` or `cancelling`. As a work-around,
|
639
|
-
if a Task is `cancelling` for more than 5 minutes, you
|
640
|
-
|
698
|
+
if a Task is `cancelling` for more than 5 minutes, you can cancel it again.
|
699
|
+
It will then be marked as fully cancelled, allowing you to run it again.
|
641
700
|
|
642
701
|
### Configuring the gem
|
643
702
|
|
@@ -840,8 +899,8 @@ or email of the user who performed the maintenance task.
|
|
840
899
|
|
841
900
|
```ruby
|
842
901
|
# config/initializers/maintenance_tasks.rb
|
843
|
-
MaintenanceTasks.metadata = ->(
|
844
|
-
|
902
|
+
MaintenanceTasks.metadata = ->() do
|
903
|
+
{ user_email: current_user.email }
|
845
904
|
end
|
846
905
|
```
|
847
906
|
|
@@ -13,7 +13,7 @@ module MaintenanceTasks
|
|
13
13
|
task = Runner.run(
|
14
14
|
name: params.fetch(:task_id),
|
15
15
|
csv_file: params[:csv_file],
|
16
|
-
arguments: params.fetch(:
|
16
|
+
arguments: params.fetch(:task, {}).permit!.to_h,
|
17
17
|
metadata: instance_exec(&MaintenanceTasks.metadata || -> {}),
|
18
18
|
&block
|
19
19
|
)
|
@@ -41,15 +41,27 @@ module MaintenanceTasks
|
|
41
41
|
task
|
42
42
|
end
|
43
43
|
|
44
|
-
#
|
45
|
-
# superclass.
|
44
|
+
# Loads and returns a list of concrete classes that inherit
|
45
|
+
# from the Task superclass.
|
46
46
|
#
|
47
47
|
# @return [Array<Class>] the list of classes.
|
48
|
-
def
|
48
|
+
def load_all
|
49
49
|
load_constants
|
50
50
|
descendants
|
51
51
|
end
|
52
52
|
|
53
|
+
# Loads and returns a list of concrete classes that inherit
|
54
|
+
# from the Task superclass.
|
55
|
+
#
|
56
|
+
# @return [Array<Class>] the list of classes.
|
57
|
+
def available_tasks
|
58
|
+
warn(<<~MSG.squish, category: :deprecated)
|
59
|
+
MaintenanceTasks::Task.available_tasks is deprecated and will be
|
60
|
+
removed from maintenance-tasks 3.0.0. Use .load_all instead.
|
61
|
+
MSG
|
62
|
+
load_all
|
63
|
+
end
|
64
|
+
|
53
65
|
# Make this Task a task that handles CSV.
|
54
66
|
#
|
55
67
|
# @param in_batches [Integer] optionally, supply a batch size if the CSV
|
@@ -25,7 +25,7 @@ module MaintenanceTasks
|
|
25
25
|
def available_tasks
|
26
26
|
tasks = []
|
27
27
|
|
28
|
-
task_names = Task.
|
28
|
+
task_names = Task.load_all.map(&:name)
|
29
29
|
|
30
30
|
active_runs = Run.with_attached_csv.active.where(task_name: task_names)
|
31
31
|
active_runs.each do |run|
|
@@ -15,7 +15,7 @@
|
|
15
15
|
<% parameter_names = @task.parameter_names %>
|
16
16
|
<% if parameter_names.any? %>
|
17
17
|
<div class="block">
|
18
|
-
<%=
|
18
|
+
<%= fields_for :task, @task.new do |ff| %>
|
19
19
|
<% parameter_names.each do |parameter_name| %>
|
20
20
|
<div class="field">
|
21
21
|
<%= ff.label parameter_name, parameter_name, class: "label is-family-monospace" %>
|
@@ -16,15 +16,6 @@ module MaintenanceTasks
|
|
16
16
|
|
17
17
|
desc "perform [TASK NAME]", "Runs the given Maintenance Task"
|
18
18
|
|
19
|
-
long_desc <<-LONGDESC
|
20
|
-
`maintenance_tasks perform` will run the Maintenance Task specified by the
|
21
|
-
[TASK NAME] argument.
|
22
|
-
|
23
|
-
Available Tasks:
|
24
|
-
|
25
|
-
#{MaintenanceTasks::Task.available_tasks.join("\n\n")}
|
26
|
-
LONGDESC
|
27
|
-
|
28
19
|
# Specify the CSV file to process for CSV Tasks
|
29
20
|
desc = "Supply a CSV file to be processed by a CSV Task, "\
|
30
21
|
"--csv path/to/csv/file.csv"
|
@@ -50,6 +41,21 @@ module MaintenanceTasks
|
|
50
41
|
say_status(:error, error.message, :red)
|
51
42
|
end
|
52
43
|
|
44
|
+
# `long_desc` only allows us to use a static string as "long description".
|
45
|
+
# By redefining the `#long_description` method on the "perform" Command
|
46
|
+
# object instead, we make it dynamic, thus delaying the task loading
|
47
|
+
# process until it's actually required.
|
48
|
+
commands["perform"].define_singleton_method(:long_description) do
|
49
|
+
<<~LONGDESC
|
50
|
+
`maintenance_tasks perform` will run the Maintenance Task specified by
|
51
|
+
the [TASK NAME] argument.
|
52
|
+
|
53
|
+
Available Tasks:
|
54
|
+
|
55
|
+
#{Task.load_all.map(&:name).sort.join("\n\n")}
|
56
|
+
LONGDESC
|
57
|
+
end
|
58
|
+
|
53
59
|
private
|
54
60
|
|
55
61
|
def csv_file
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: maintenance_tasks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.3.
|
4
|
+
version: 2.3.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify Engineering
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|
@@ -56,14 +56,14 @@ dependencies:
|
|
56
56
|
name: job-iteration
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: 1.3.6
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - "
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: 1.3.6
|
69
69
|
- !ruby/object:Gem::Dependency
|
@@ -156,7 +156,7 @@ homepage: https://github.com/Shopify/maintenance_tasks
|
|
156
156
|
licenses:
|
157
157
|
- MIT
|
158
158
|
metadata:
|
159
|
-
source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v2.3.
|
159
|
+
source_code_uri: https://github.com/Shopify/maintenance_tasks/tree/v2.3.3
|
160
160
|
allowed_push_host: https://rubygems.org
|
161
161
|
post_install_message:
|
162
162
|
rdoc_options: []
|
@@ -173,7 +173,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
173
173
|
- !ruby/object:Gem::Version
|
174
174
|
version: '0'
|
175
175
|
requirements: []
|
176
|
-
rubygems_version: 3.4.
|
176
|
+
rubygems_version: 3.4.20
|
177
177
|
signing_key:
|
178
178
|
specification_version: 4
|
179
179
|
summary: A Rails engine for queuing and managing maintenance tasks
|