good_job 1.1.4 → 1.2.4
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/CHANGELOG.md +101 -9
- data/README.md +275 -150
- data/lib/good_job.rb +29 -3
- data/lib/good_job/adapter.rb +6 -4
- data/lib/good_job/cli.rb +41 -11
- data/lib/good_job/configuration.rb +8 -0
- data/lib/good_job/current_execution.rb +2 -0
- data/lib/good_job/job.rb +2 -1
- data/lib/good_job/lockable.rb +10 -12
- data/lib/good_job/notifier.rb +25 -14
- data/lib/good_job/scheduler.rb +1 -0
- data/lib/good_job/version.rb +1 -1
- metadata +73 -4
- data/lib/good_job/pg_locks.rb +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1f3fa6d323c18dfb49aaf3b87f0ca71492a935752e7ca140880b68daa72a60f9
|
4
|
+
data.tar.gz: 999b400b7e1dd22206898fb1cdc35f6255088ce94c3562fa48fe97fc20a23672
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1b9236562e20d4da1e5738d9b143fabe9377d854b392b6b0eb2d77c0009d9f8a0c82ef94b414446a7b5db0b2a43e477ad4b8a6c7bff737b69c9d8a744ee29151
|
7
|
+
data.tar.gz: cfcc994ea2f6e9a26c3b1115ccb2df6383e2897c3db891415745b86769dbdc411a62b7d60c89aa7ddb6880009c6124c795cef33efff55daa860ed7fd90e1b81d
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,95 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v1.2.4](https://github.com/bensheldon/good_job/tree/v1.2.4) (2020-09-01)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.2.3...v1.2.4)
|
6
|
+
|
7
|
+
**Implemented enhancements:**
|
8
|
+
|
9
|
+
- Add environment variable to mirror `cleanup\_preserved\_jobs --before-seconds-ago=SECONDS` [\#110](https://github.com/bensheldon/good_job/issues/110)
|
10
|
+
|
11
|
+
**Closed issues:**
|
12
|
+
|
13
|
+
- Remove unused PgLocks class [\#121](https://github.com/bensheldon/good_job/issues/121)
|
14
|
+
- Fix minor issue with CommandLine option links in README.md [\#116](https://github.com/bensheldon/good_job/issues/116)
|
15
|
+
- Unused .advisory\_lock\_details in PgLocks [\#105](https://github.com/bensheldon/good_job/issues/105)
|
16
|
+
|
17
|
+
**Merged pull requests:**
|
18
|
+
|
19
|
+
- Remove unused PgLocks class [\#120](https://github.com/bensheldon/good_job/pull/120) ([gadimbaylisahil](https://github.com/gadimbaylisahil))
|
20
|
+
- Better table name detection for Job queries [\#119](https://github.com/bensheldon/good_job/pull/119) ([gadimbaylisahil](https://github.com/gadimbaylisahil))
|
21
|
+
- Fix readme CommandLine option links [\#115](https://github.com/bensheldon/good_job/pull/115) ([gadimbaylisahil](https://github.com/gadimbaylisahil))
|
22
|
+
- Allow env variable config for cleanups [\#114](https://github.com/bensheldon/good_job/pull/114) ([gadimbaylisahil](https://github.com/gadimbaylisahil))
|
23
|
+
- Have YARD render markdown files with GFM \(Github Flavored Markdown\) [\#113](https://github.com/bensheldon/good_job/pull/113) ([bensheldon](https://github.com/bensheldon))
|
24
|
+
- Add markdownlint to lint readme [\#109](https://github.com/bensheldon/good_job/pull/109) ([bensheldon](https://github.com/bensheldon))
|
25
|
+
- Remove unused method in PgLocks [\#107](https://github.com/bensheldon/good_job/pull/107) ([gadimbaylisahil](https://github.com/gadimbaylisahil))
|
26
|
+
- Re-organize Readme: frontload configuration, add Table of Contents [\#106](https://github.com/bensheldon/good_job/pull/106) ([bensheldon](https://github.com/bensheldon))
|
27
|
+
|
28
|
+
## [v1.2.3](https://github.com/bensheldon/good_job/tree/v1.2.3) (2020-08-27)
|
29
|
+
|
30
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.2.2...v1.2.3)
|
31
|
+
|
32
|
+
**Closed issues:**
|
33
|
+
|
34
|
+
- requiring more dependencies in then needed [\#103](https://github.com/bensheldon/good_job/issues/103)
|
35
|
+
|
36
|
+
**Merged pull requests:**
|
37
|
+
|
38
|
+
- stop depending on all rails libs [\#104](https://github.com/bensheldon/good_job/pull/104) ([thilo](https://github.com/thilo))
|
39
|
+
|
40
|
+
## [v1.2.2](https://github.com/bensheldon/good_job/tree/v1.2.2) (2020-08-27)
|
41
|
+
|
42
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.2.1...v1.2.2)
|
43
|
+
|
44
|
+
**Implemented enhancements:**
|
45
|
+
|
46
|
+
- Run Github Action tests against Ruby 2.5, 2.6, 2.7 [\#100](https://github.com/bensheldon/good_job/issues/100)
|
47
|
+
|
48
|
+
**Fixed bugs:**
|
49
|
+
|
50
|
+
- Freezes puma on code change [\#95](https://github.com/bensheldon/good_job/issues/95)
|
51
|
+
- Ruby 2.7 keyword arguments warning [\#93](https://github.com/bensheldon/good_job/issues/93)
|
52
|
+
|
53
|
+
**Closed issues:**
|
54
|
+
|
55
|
+
- Add test for `rails g good\_job:install` [\#57](https://github.com/bensheldon/good_job/issues/57)
|
56
|
+
|
57
|
+
**Merged pull requests:**
|
58
|
+
|
59
|
+
- Use more ActiveRecord in Lockable and not connection.execute [\#102](https://github.com/bensheldon/good_job/pull/102) ([bensheldon](https://github.com/bensheldon))
|
60
|
+
- Run CI tests on Ruby 2.5, 2.6, and 2.7 [\#101](https://github.com/bensheldon/good_job/pull/101) ([arku](https://github.com/arku))
|
61
|
+
- Return to using executor.wrap around Scheduler execution task [\#99](https://github.com/bensheldon/good_job/pull/99) ([bensheldon](https://github.com/bensheldon))
|
62
|
+
- Fix Ruby 2.7 keyword arguments warning [\#98](https://github.com/bensheldon/good_job/pull/98) ([arku](https://github.com/arku))
|
63
|
+
- Remove executor/reloader for less interlocking [\#97](https://github.com/bensheldon/good_job/pull/97) ([sj26](https://github.com/sj26))
|
64
|
+
- Name the thread pools [\#96](https://github.com/bensheldon/good_job/pull/96) ([sj26](https://github.com/sj26))
|
65
|
+
- Add test for `rails g good\_job:install` [\#94](https://github.com/bensheldon/good_job/pull/94) ([arku](https://github.com/arku))
|
66
|
+
|
67
|
+
## [v1.2.1](https://github.com/bensheldon/good_job/tree/v1.2.1) (2020-08-21)
|
68
|
+
|
69
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.2.0...v1.2.1)
|
70
|
+
|
71
|
+
**Fixed bugs:**
|
72
|
+
|
73
|
+
- undefined method `thread\_mattr\_accessor' when not requiring the Sprockets Railstie [\#85](https://github.com/bensheldon/good_job/issues/85)
|
74
|
+
|
75
|
+
**Closed issues:**
|
76
|
+
|
77
|
+
- Document comparison of GoodJob with other backends [\#51](https://github.com/bensheldon/good_job/issues/51)
|
78
|
+
|
79
|
+
**Merged pull requests:**
|
80
|
+
|
81
|
+
- Explicitly require thread\_mattr\_accessor from ActiveSupport [\#86](https://github.com/bensheldon/good_job/pull/86) ([bensheldon](https://github.com/bensheldon))
|
82
|
+
- Add comparison of other backends to Readme [\#84](https://github.com/bensheldon/good_job/pull/84) ([bensheldon](https://github.com/bensheldon))
|
83
|
+
|
84
|
+
## [v1.2.0](https://github.com/bensheldon/good_job/tree/v1.2.0) (2020-08-20)
|
85
|
+
|
86
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.1.4...v1.2.0)
|
87
|
+
|
88
|
+
**Merged pull requests:**
|
89
|
+
|
90
|
+
- Document GoodJob module [\#83](https://github.com/bensheldon/good_job/pull/83) ([bensheldon](https://github.com/bensheldon))
|
91
|
+
- Add Postgres LISTEN/NOTIFY support [\#82](https://github.com/bensheldon/good_job/pull/82) ([bensheldon](https://github.com/bensheldon))
|
92
|
+
|
3
93
|
## [v1.1.4](https://github.com/bensheldon/good_job/tree/v1.1.4) (2020-08-19)
|
4
94
|
|
5
95
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.1.3...v1.1.4)
|
@@ -11,7 +101,6 @@
|
|
11
101
|
|
12
102
|
**Merged pull requests:**
|
13
103
|
|
14
|
-
- Add Postgres LISTEN/NOTIFY support [\#82](https://github.com/bensheldon/good_job/pull/82) ([bensheldon](https://github.com/bensheldon))
|
15
104
|
- Allow Schedulers to filter \#create\_thread to avoid flood of queries when running async with multiple schedulers [\#81](https://github.com/bensheldon/good_job/pull/81) ([bensheldon](https://github.com/bensheldon))
|
16
105
|
- Fully name scheduler threadpools and thread names; refactor CLI STDOUT [\#80](https://github.com/bensheldon/good_job/pull/80) ([bensheldon](https://github.com/bensheldon))
|
17
106
|
|
@@ -51,6 +140,7 @@
|
|
51
140
|
**Merged pull requests:**
|
52
141
|
|
53
142
|
- Allow instantiation of multiple schedulers via --queues [\#76](https://github.com/bensheldon/good_job/pull/76) ([bensheldon](https://github.com/bensheldon))
|
143
|
+
- Extract options parsing to Configuration object [\#74](https://github.com/bensheldon/good_job/pull/74) ([bensheldon](https://github.com/bensheldon))
|
54
144
|
|
55
145
|
## [v1.1.0](https://github.com/bensheldon/good_job/tree/v1.1.0) (2020-08-10)
|
56
146
|
|
@@ -85,7 +175,6 @@
|
|
85
175
|
|
86
176
|
**Merged pull requests:**
|
87
177
|
|
88
|
-
- Extract options parsing to Configuration object [\#74](https://github.com/bensheldon/good_job/pull/74) ([bensheldon](https://github.com/bensheldon))
|
89
178
|
- Re-perform a job if a StandardError bubbles up; better document job reliability [\#62](https://github.com/bensheldon/good_job/pull/62) ([bensheldon](https://github.com/bensheldon))
|
90
179
|
- Update the setup documentation to use correct bin setup command [\#61](https://github.com/bensheldon/good_job/pull/61) ([jm96441n](https://github.com/jm96441n))
|
91
180
|
|
@@ -93,10 +182,6 @@
|
|
93
182
|
|
94
183
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.0.1...v1.0.2)
|
95
184
|
|
96
|
-
**Fixed bugs:**
|
97
|
-
|
98
|
-
- Fix counting of available execution threads [\#58](https://github.com/bensheldon/good_job/pull/58) ([bensheldon](https://github.com/bensheldon))
|
99
|
-
|
100
185
|
**Merged pull requests:**
|
101
186
|
|
102
187
|
- Add migration generator [\#56](https://github.com/bensheldon/good_job/pull/56) ([thedanbob](https://github.com/thedanbob))
|
@@ -108,7 +193,7 @@
|
|
108
193
|
|
109
194
|
**Merged pull requests:**
|
110
195
|
|
111
|
-
-
|
196
|
+
- Change threadpool idletime default to 60 seconds from 0 [\#49](https://github.com/bensheldon/good_job/pull/49) ([bensheldon](https://github.com/bensheldon))
|
112
197
|
|
113
198
|
## [v1.0.0](https://github.com/bensheldon/good_job/tree/v1.0.0) (2020-07-20)
|
114
199
|
|
@@ -118,6 +203,14 @@
|
|
118
203
|
|
119
204
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v0.8.2...v0.9.0)
|
120
205
|
|
206
|
+
**Fixed bugs:**
|
207
|
+
|
208
|
+
- Fix counting of available execution threads [\#58](https://github.com/bensheldon/good_job/pull/58) ([bensheldon](https://github.com/bensheldon))
|
209
|
+
|
210
|
+
**Merged pull requests:**
|
211
|
+
|
212
|
+
- Allow preservation of finished job records [\#46](https://github.com/bensheldon/good_job/pull/46) ([bensheldon](https://github.com/bensheldon))
|
213
|
+
|
121
214
|
## [v0.8.2](https://github.com/bensheldon/good_job/tree/v0.8.2) (2020-07-18)
|
122
215
|
|
123
216
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v0.8.1...v0.8.2)
|
@@ -145,7 +238,6 @@
|
|
145
238
|
|
146
239
|
**Merged pull requests:**
|
147
240
|
|
148
|
-
- Change threadpool idletime default to 60 seconds from 0 [\#49](https://github.com/bensheldon/good_job/pull/49) ([bensheldon](https://github.com/bensheldon))
|
149
241
|
- Replace Adapter inline boolean kwarg with execution\_mode instead [\#41](https://github.com/bensheldon/good_job/pull/41) ([bensheldon](https://github.com/bensheldon))
|
150
242
|
|
151
243
|
## [v0.7.0](https://github.com/bensheldon/good_job/tree/v0.7.0) (2020-07-16)
|
@@ -195,6 +287,7 @@
|
|
195
287
|
|
196
288
|
- Improve ActiveRecord usage for advisory locking [\#24](https://github.com/bensheldon/good_job/pull/24) ([bensheldon](https://github.com/bensheldon))
|
197
289
|
- Remove support for Rails 5.1 [\#23](https://github.com/bensheldon/good_job/pull/23) ([bensheldon](https://github.com/bensheldon))
|
290
|
+
- Clean up Gemspec [\#15](https://github.com/bensheldon/good_job/pull/15) ([bensheldon](https://github.com/bensheldon))
|
198
291
|
|
199
292
|
## [v0.3.0](https://github.com/bensheldon/good_job/tree/v0.3.0) (2020-03-22)
|
200
293
|
|
@@ -222,7 +315,6 @@
|
|
222
315
|
|
223
316
|
**Merged pull requests:**
|
224
317
|
|
225
|
-
- Clean up Gemspec [\#15](https://github.com/bensheldon/good_job/pull/15) ([bensheldon](https://github.com/bensheldon))
|
226
318
|
- Set up Rubocop [\#14](https://github.com/bensheldon/good_job/pull/14) ([bensheldon](https://github.com/bensheldon))
|
227
319
|
- Add pg gem as explicit dependency [\#13](https://github.com/bensheldon/good_job/pull/13) ([bensheldon](https://github.com/bensheldon))
|
228
320
|
- Bump nokogiri from 1.10.7 to 1.10.9 [\#12](https://github.com/bensheldon/good_job/pull/12) ([dependabot[bot]](https://github.com/apps/dependabot))
|
data/README.md
CHANGED
@@ -4,197 +4,281 @@ GoodJob is a multithreaded, Postgres-based, ActiveJob backend for Ruby on Rails.
|
|
4
4
|
|
5
5
|
**Inspired by [Delayed::Job](https://github.com/collectiveidea/delayed_job) and [Que](https://github.com/que-rb/que), GoodJob is designed for maximum compatibility with Ruby on Rails, ActiveJob, and Postgres to be simple and performant for most workloads.**
|
6
6
|
|
7
|
-
- **Designed for ActiveJob.** Complete support for [async, queues, delays, priorities, timeouts, and retries](https://edgeguides.rubyonrails.org/active_job_basics.html) with near-zero configuration.
|
8
|
-
- **Built for Rails.** Fully adopts Ruby on Rails [threading and code execution guidelines](https://guides.rubyonrails.org/threading_and_code_execution.html) with [Concurrent::Ruby](https://github.com/ruby-concurrency/concurrent-ruby).
|
7
|
+
- **Designed for ActiveJob.** Complete support for [async, queues, delays, priorities, timeouts, and retries](https://edgeguides.rubyonrails.org/active_job_basics.html) with near-zero configuration.
|
8
|
+
- **Built for Rails.** Fully adopts Ruby on Rails [threading and code execution guidelines](https://guides.rubyonrails.org/threading_and_code_execution.html) with [Concurrent::Ruby](https://github.com/ruby-concurrency/concurrent-ruby).
|
9
9
|
- **Backed by Postgres.** Relies upon Postgres integrity, session-level Advisory Locks to provide run-once safety and stay within the limits of `schema.rb`, and LISTEN/NOTIFY to reduce queuing latency.
|
10
10
|
- **For most workloads.** Targets full-stack teams, economy-minded solo developers, and applications that enqueue less than 1-million jobs/day.
|
11
11
|
|
12
12
|
For more of the story of GoodJob, read the [introductory blog post](https://island94.org/2020/07/introducing-goodjob-1-0).
|
13
13
|
|
14
|
-
|
14
|
+
<details markdown="1">
|
15
|
+
<summary><strong>📊 Comparison of GoodJob with other job queue backends (click to expand)</strong></summary>
|
16
|
+
|
17
|
+
| | Queues, priority, retries | Database | Concurrency | Reliability/Integrity | Latency |
|
18
|
+
|-----------------|---------------------------|---------------------------------------|-------------------|------------------------|--------------------------|
|
19
|
+
| **GoodJob** | ✅ Yes | ✅ Postgres | ✅ Multithreaded | ✅ ACID, Advisory Locks | ✅ Postgres LISTEN/NOTIFY |
|
20
|
+
| **Que** | ✅ Yes | 🔶️ Postgres, requires `structure.sql` | ✅ Multithreaded | ✅ ACID, Advisory Locks | ✅ Postgres LISTEN/NOTIFY |
|
21
|
+
| **Delayed Job** | ✅ Yes | ✅ Postgres | 🔴 Single-threaded | ✅ ACID, record-based | 🔶 Polling |
|
22
|
+
| **Sidekiq** | ✅ Yes | 🔴 Redis | ✅ Multithreaded | 🔴 Crashes lose jobs | ✅ Redis BRPOP |
|
23
|
+
| **Sidekiq Pro** | ✅ Yes | 🔴 Redis | ✅ Multithreaded | ✅ Redis RPOPLPUSH | ✅ Redis RPOPLPUSH |
|
24
|
+
|
25
|
+
</details>
|
26
|
+
|
27
|
+
## Table of contents
|
28
|
+
|
29
|
+
- [Set up](#set-up)
|
30
|
+
- [Configuration](#configuration)
|
31
|
+
- [Command-line options](#command-line-options)
|
32
|
+
- [`good_job start`](#good_job-start)
|
33
|
+
- [`good_job cleanup_preserved_jobs`](#good_job-cleanup_preserved_jobs)
|
34
|
+
- [Adapter options](#adapter-options)
|
35
|
+
- [Global options](#global-options)
|
36
|
+
- [Going deeper](#going-deeper)
|
37
|
+
- [Exceptions, retries, and reliability](#exceptions-retries-and-reliability)
|
38
|
+
- [Exceptions](#exceptions)
|
39
|
+
- [Retries](#retries)
|
40
|
+
- [ActionMailer retries](#actionmailer-retries)
|
41
|
+
- [Timeouts](#timeouts)
|
42
|
+
- [Optimize queues, threads, and processes](#optimize-queues-threads-and-processes)
|
43
|
+
- [Database connections](#database-connections)
|
44
|
+
- [Executing jobs async / in-process](#executing-jobs-async--in-process)
|
45
|
+
- [Migrating to GoodJob from a different ActiveJob backend](#migrating-to-goodjob-from-a-different-activejob-backend)
|
46
|
+
- [Monitoring and preserving worked jobs](#monitoring-and-preserving-worked-jobs)
|
47
|
+
- [Contributing](#contributing)
|
48
|
+
- [Gem development](#gem-development)
|
49
|
+
- [Releasing](#releasing)
|
50
|
+
- [License](#license)
|
51
|
+
|
52
|
+
## Set up
|
53
|
+
|
54
|
+
1. Add `good_job` to your application's Gemfile:
|
15
55
|
|
16
|
-
|
56
|
+
```ruby
|
57
|
+
gem 'good_job'
|
58
|
+
```
|
17
59
|
|
18
|
-
|
19
|
-
gem 'good_job'
|
20
|
-
```
|
60
|
+
1. Install the gem:
|
21
61
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
```
|
62
|
+
```bash
|
63
|
+
$ bundle install
|
64
|
+
```
|
26
65
|
|
27
|
-
|
66
|
+
1. Run the GoodJob install generator. This will generate a database migration to create a table for GoodJob's job records:
|
28
67
|
|
29
|
-
|
30
|
-
|
31
|
-
```bash
|
68
|
+
```bash
|
32
69
|
$ bin/rails g good_job:install
|
33
70
|
```
|
34
71
|
|
35
72
|
Run the migration:
|
36
|
-
|
73
|
+
|
37
74
|
```bash
|
38
75
|
$ bin/rails db:migrate
|
39
76
|
```
|
40
|
-
|
77
|
+
|
41
78
|
1. Configure the ActiveJob adapter:
|
42
|
-
|
43
|
-
|
79
|
+
|
80
|
+
```ruby
|
44
81
|
# config/application.rb
|
45
82
|
config.active_job.queue_adapter = :good_job
|
46
83
|
```
|
47
|
-
|
48
|
-
|
49
|
-
|
84
|
+
|
85
|
+
1. Inside of your application, queue your job 🎉:
|
86
|
+
|
50
87
|
```ruby
|
51
|
-
|
52
|
-
config.active_job.queue_adapter = GoodJob::Adapter.new(execution_mode: :inline)
|
53
|
-
|
54
|
-
# config/environments/test.rb
|
55
|
-
config.active_job.queue_adapter = GoodJob::Adapter.new(execution_mode: :inline)
|
56
|
-
|
57
|
-
# config/environments/production.rb
|
58
|
-
config.active_job.queue_adapter = GoodJob::Adapter.new(execution_mode: :external)
|
88
|
+
YourJob.perform_later
|
59
89
|
```
|
60
90
|
|
61
|
-
|
62
|
-
|
91
|
+
GoodJob supports all ActiveJob features:
|
92
|
+
|
63
93
|
```ruby
|
64
94
|
YourJob.set(queue: :some_queue, wait: 5.minutes, priority: 10).perform_later
|
65
95
|
```
|
66
96
|
|
67
|
-
1. In
|
68
|
-
|
69
|
-
```bash
|
70
|
-
$ bundle exec good_job
|
71
|
-
```
|
72
|
-
|
73
|
-
Configuration options available with `help`:
|
97
|
+
1. In development, GoodJob executes jobs immediately. In production, GoodJob provides different options:
|
74
98
|
|
75
|
-
|
76
|
-
$ bundle exec good_job help start
|
77
|
-
|
78
|
-
Usage:
|
79
|
-
good_job start
|
80
|
-
|
81
|
-
Options:
|
82
|
-
[--max-threads=N] # Maximum number of threads to use for working jobs (default: ActiveRecord::Base.connection_pool.size)
|
83
|
-
[--queues=queue1,queue2(;queue3,queue4:5;-queue1,queue2)] # Queues to work from. Separate multiple queues with commas; exclude queues with a leading minus; separate isolated execution pools with semicolons and threads with colons (default: *)
|
84
|
-
[--poll-interval=N] # Interval between polls for available jobs in seconds (default: 1)
|
85
|
-
|
86
|
-
Start job worker
|
87
|
-
```
|
99
|
+
- By default, GoodJob separates job enqueuing from job execution so that jobs can be scaled independently of the web server. Use the GoodJob command-line tool to execute jobs:
|
88
100
|
|
89
|
-
|
101
|
+
```bash
|
102
|
+
$ bundle exec good_job start
|
103
|
+
```
|
90
104
|
|
91
|
-
|
105
|
+
Ideally the command-line tool should be run on a separate machine or container from the web process. For example, on Heroku:
|
92
106
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
$ bundle exec good_job --queues=transactional_messages:2;batch_processing:1;-transactional_messages,batch_processing:2;* --max-threads=5
|
107
|
+
```Procfile
|
108
|
+
web: rails server
|
109
|
+
worker: bundle exec good_job start
|
97
110
|
```
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
- `-transactional_messages,batch_processing`: execute jobs enqueued on _any_ queue _excluding_ `transactional_messages` or `batch_processing` with up to 2 threads.
|
104
|
-
- `*`: execute jobs on any queue on up to 5 threads, as configured by `--max-threads=5`
|
105
|
-
|
106
|
-
For moderate workloads, multiple isolated thread execution pools offers a good balance between congestion management and economy.
|
107
|
-
|
108
|
-
Configuration can be injected by environment variables too:
|
109
|
-
|
110
|
-
```bash
|
111
|
-
$ GOOD_JOB_QUEUES="transactional_messages:2;batch_processing:1;-transactional_messages,batch_processing:2;*" GOOD_JOB_MAX_THREADS=5 bundle exec good_job
|
111
|
+
|
112
|
+
The command-line tool supports a variety of options, see the reference below for command-line configuration.
|
113
|
+
|
114
|
+
- GoodJob can also be configured to execute jobs within the web server process to save on resources. This is useful for low-workloads when economy is paramount.
|
115
|
+
|
112
116
|
```
|
113
|
-
|
114
|
-
- Multiple processes; for example, on Heroku:
|
115
|
-
|
116
|
-
```procfile
|
117
|
-
# Procfile
|
118
|
-
|
119
|
-
# Separate dyno types
|
120
|
-
worker: bundle exec good_job --max-threads=5
|
121
|
-
transactional_worker: bundle exec good_job --queues=transactional_messages --max-threads=2
|
122
|
-
batch_worker: bundle exec good_job --queues=batch_processing --max-threads=1
|
123
|
-
|
124
|
-
# Combined multi-process dyno
|
125
|
-
combined_worker: bundle exec good_job --max-threads=5 & bundle exec good_job --queues=transactional_messages --max-threads=2 & bundle exec good_job --queues=batch_processing --max-threads=1 & wait -n
|
117
|
+
$ GOOD_JOB_EXECUTION_MODE=async rails server
|
126
118
|
```
|
127
|
-
|
128
|
-
Running multiple processes can optimize for CPU performance at the expense of greater memory and system resource usage.
|
129
119
|
|
130
|
-
|
120
|
+
Additional configuration is likely necessary, see the reference below for async configuration.
|
131
121
|
|
132
|
-
|
122
|
+
## Configuration
|
133
123
|
|
134
|
-
|
124
|
+
### Command-line options
|
135
125
|
|
136
|
-
|
126
|
+
There several top-level commands available through the `good_job` command-line tool.
|
137
127
|
|
138
|
-
|
128
|
+
Configuration options are available with `help`.
|
139
129
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
130
|
+
#### `good_job start`
|
131
|
+
|
132
|
+
`good_job start` executes queued jobs.
|
133
|
+
|
134
|
+
```bash
|
135
|
+
$ bundle exec good_job help start
|
136
|
+
|
137
|
+
Usage:
|
138
|
+
good_job start
|
139
|
+
|
140
|
+
Options:
|
141
|
+
[--max-threads=COUNT] # Maximum number of threads to use for working jobs. (env var: GOOD_JOB_MAX_THREADS, default: 5)
|
142
|
+
[--queues=QUEUE_LIST] # Queues to work from. (env var: GOOD_JOB_QUEUES, default: *)
|
143
|
+
[--poll-interval=SECONDS] # Interval between polls for available jobs in seconds (env var: GOOD_JOB_POLL_INTERVAL, default: 1)
|
144
|
+
|
145
|
+
Executes queued jobs.
|
146
|
+
|
147
|
+
All options can be configured with environment variables.
|
148
|
+
See option descriptions for the matching environment variable name.
|
149
|
+
|
150
|
+
== Configuring queues
|
151
|
+
Separate multiple queues with commas; exclude queues with a leading minus;
|
152
|
+
separate isolated execution pools with semicolons and threads with colons.
|
153
|
+
```
|
154
|
+
|
155
|
+
#### `good_job cleanup_preserved_jobs`
|
156
|
+
|
157
|
+
`good_job cleanup_preserved_jobs` deletes preserved job records. See [`GoodJob.preserve_job_records` for when this command is useful.
|
158
|
+
|
159
|
+
```bash
|
160
|
+
$ bundle exec good_job help cleanup_preserved_jobs
|
161
|
+
|
162
|
+
Usage:
|
163
|
+
good_job cleanup_preserved_jobs
|
164
|
+
|
165
|
+
Options:
|
166
|
+
[--before-seconds-ago=SECONDS] # Delete records finished more than this many seconds ago (env var: GOOD_JOB_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO, default: 86400)
|
167
|
+
|
168
|
+
Deletes preserved job records.
|
169
|
+
|
170
|
+
By default, GoodJob deletes job records when the job is performed and this
|
171
|
+
command is not necessary.
|
172
|
+
|
173
|
+
However, when `GoodJob.preserve_job_records = true`, the jobs will be
|
174
|
+
preserved in the database. This is useful when wanting to analyze or
|
175
|
+
inspect job performance.
|
176
|
+
|
177
|
+
If you are preserving job records this way, use this command regularly
|
178
|
+
to delete old records and preserve space in your database.
|
179
|
+
```
|
180
|
+
|
181
|
+
### Adapter options
|
182
|
+
|
183
|
+
To use GoodJob, you can set `config.active_job.queue_adapter` to a `:good_job` or to an instance of `GoodJob::Adapter`, which you can configure with several options:
|
184
|
+
|
185
|
+
- `execution_mode` (symbol) specifies how and where jobs should be executed. You can also set this with the environment variable `GOOD_JOB_EXECUTION_MODE`. It can be any one of:
|
186
|
+
- `:inline` executes jobs immediately in whatever process queued them (usually the web server process). This should only be used in test and development environments.
|
187
|
+
- `:external` causes the adapter to equeue jobs, but not execute them. When using this option (the default for production environments), you’ll need to use the command-line tool to actually execute your jobs.
|
188
|
+
- `:async` causes the adapter to execute you jobs in separate threads in whatever process queued them (usually the web process). This is akin to running the command-line tool’s code inside your web server. It can be more economical for small workloads (you don’t need a separate machine or environment for running your jobs), but if your web server is under heavy load or your jobs require a lot of resources, you should choose `:external` instead.
|
189
|
+
- `max_threads` (integer) sets the maximum number of threads to use when `execution_mode` is set to `:async`. You can also set this with the environment variable `GOOD_JOB_MAX_THREADS`.
|
190
|
+
- `queues` (string) determines which queues to execute jobs from when `execution_mode` is set to `:async`. See the description of `good_job start` for more details on the format of this string. You can also set this with the environment variable `GOOD_JOB_QUEUES`.
|
191
|
+
- `poll_interval` (integer) sets the number of seconds between polls for jobs when `execution_mode` is set to `:async`. You can also set this with the environment variable `GOOD_JOB_POLL_INTERVAL`.
|
192
|
+
|
193
|
+
Using the symbol instead of explicitly configuring the options above (i.e. setting `config.active_job.queue_adapter = :good_job`) is equivalent to:
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
# config/environments/development.rb
|
197
|
+
config.active_job.queue_adapter = GoodJob::Adapter.new(execution_mode: :inline)
|
198
|
+
|
199
|
+
# config/environments/test.rb
|
200
|
+
config.active_job.queue_adapter = GoodJob::Adapter.new(execution_mode: :inline)
|
201
|
+
|
202
|
+
# config/environments/production.rb
|
203
|
+
config.active_job.queue_adapter = GoodJob::Adapter.new(execution_mode: :external)
|
204
|
+
```
|
205
|
+
|
206
|
+
### Global options
|
207
|
+
|
208
|
+
Good Job’s general behavior can also be configured via several attributes directly on the `GoodJob` module:
|
209
|
+
|
210
|
+
- **`GoodJob.logger`** ([Rails Logger](https://api.rubyonrails.org/classes/ActiveSupport/Logger.html)) lets you set a custom logger for GoodJob. It should be an instance of a Rails `Logger`.
|
211
|
+
- **`GoodJob.preserve_job_records`** (boolean) keeps job records in your database even after jobs are completed. (Default: `false`)
|
212
|
+
- **`GoodJob.reperform_jobs_on_standard_error`** (boolean) causes jobs to be re-queued and retried if they raise an instance of `StandardError`. Instances of `Exception`, like SIGINT, will *always* be retried, regardless of this attribute’s value. (Default: `true`)
|
213
|
+
- **`GoodJob.on_thread_error`** (proc, lambda, or callable) will be called when a job raises an error. It can be useful for logging errors to bug tracking services, like Sentry or Airbrake.
|
147
214
|
|
148
|
-
|
215
|
+
You’ll generally want to configure these in `config/initializers/good_job.rb`, like so:
|
149
216
|
|
150
217
|
```ruby
|
151
218
|
# config/initializers/good_job.rb
|
219
|
+
GoodJob.preserve_job_records = true
|
220
|
+
GoodJob.reperform_jobs_on_standard_error = false
|
221
|
+
GoodJob.on_thread_error = -> (exception) { Raven.capture_exception(exception) }
|
222
|
+
```
|
152
223
|
|
153
|
-
|
224
|
+
## Going deeper
|
225
|
+
|
226
|
+
### Exceptions, retries, and reliability
|
227
|
+
|
228
|
+
GoodJob guarantees that a completely-performed job will run once and only once. GoodJob fully supports ActiveJob's built-in functionality for error handling, retries and timeouts.
|
229
|
+
|
230
|
+
#### Exceptions
|
231
|
+
|
232
|
+
ActiveJob provides [tools for rescuing and retrying exceptions](https://guides.rubyonrails.org/active_job_basics.html#exceptions), including `retry_on`, `discard_on`, `rescue_from` that will rescue exceptions before they get to GoodJob.
|
233
|
+
|
234
|
+
If errors do reach GoodJob, you can assign a callable to `GoodJob.on_thread_error` to be notified. For example, to log errors to an exception monitoring service like Sentry (or Bugsnag, Airbrake, Honeybadger, etc.):
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
# config/initializers/good_job.rb
|
154
238
|
GoodJob.on_thread_error = -> (exception) { Raven.capture_exception(exception) }
|
155
239
|
```
|
156
240
|
|
157
|
-
|
241
|
+
#### Retries
|
158
242
|
|
159
|
-
|
243
|
+
By default, GoodJob will automatically and immediately retry a job when an exception is raised to GoodJob.
|
244
|
+
|
245
|
+
However, ActiveJob can be configured to retry an infinite number of times, with an exponential backoff. Using ActiveJob's `retry_on` prevents exceptions from reaching GoodJob:
|
160
246
|
|
161
247
|
```ruby
|
162
|
-
class ApplicationJob < ActiveJob::Base
|
248
|
+
class ApplicationJob < ActiveJob::Base
|
163
249
|
retry_on StandardError, wait: :exponentially_longer, attempts: Float::INFINITY
|
164
250
|
# ...
|
165
251
|
end
|
166
252
|
```
|
167
253
|
|
168
|
-
When
|
254
|
+
When using `retry_on` with _a limited number of retries_, the final exception will not be rescued and will raise to GoodJob. GoodJob can be configured to discard un-handled exceptions instead of retrying them:
|
169
255
|
|
170
256
|
```ruby
|
171
|
-
|
257
|
+
# config/initializers/good_job.rb
|
258
|
+
GoodJob.reperform_jobs_on_standard_error = false
|
259
|
+
```
|
260
|
+
|
261
|
+
Alternatively, pass a block to `retry_on` to handle the final exception instead of raising it to GoodJob:
|
262
|
+
|
263
|
+
```ruby
|
264
|
+
class ApplicationJob < ActiveJob::Base
|
172
265
|
retry_on StandardError, attempts: 5 do |_job, _exception|
|
173
|
-
# Log error, etc.
|
174
|
-
# You must implement this block, otherwise,
|
175
|
-
# Active Job will re-raise the error.
|
176
|
-
# Do not re-raise the error, otherwise
|
177
|
-
# GoodJob will immediately re-perform the job.
|
266
|
+
# Log error, do nothing, etc.
|
178
267
|
end
|
179
268
|
# ...
|
180
269
|
end
|
181
270
|
```
|
182
271
|
|
183
|
-
|
272
|
+
When using `retry_on` with an infinite number of retries, exceptions will never be raised to GoodJob, which means `GoodJob.on_thread_error` will never be called. To report log or report exceptions to an exception monitoring service (e.g. Sentry, Bugsnag, Airbrake, Honeybadger, etc), create an explicit exception wrapper. For example:
|
184
273
|
|
185
274
|
```ruby
|
186
|
-
|
187
|
-
|
188
|
-
# Do NOT re-perform a job if a StandardError bubbles up to the GoodJob backend
|
189
|
-
GoodJob.reperform_jobs_on_standard_error = false
|
190
|
-
```
|
275
|
+
class ApplicationJob < ActiveJob::Base
|
276
|
+
retry_on StandardError, wait: :exponentially_longer, attempts: Float::INFINITY
|
191
277
|
|
192
|
-
|
278
|
+
retry_on SpecialError, attempts: 5 do |_job, exception|
|
279
|
+
Raven.capture_exception(exception)
|
280
|
+
end
|
193
281
|
|
194
|
-
```ruby
|
195
|
-
class ApplicationJob < ActiveJob::Base
|
196
|
-
retry_on StandardError, wait: :exponentially_longer, attempts: Float::INFINITY
|
197
|
-
|
198
282
|
around_perform do |_job, block|
|
199
283
|
block.call
|
200
284
|
rescue StandardError => e
|
@@ -205,12 +289,11 @@ class ApplicationJob < ActiveJob::Base
|
|
205
289
|
end
|
206
290
|
```
|
207
291
|
|
208
|
-
|
209
|
-
ActiveJob's `discard_on` functionality is supported too.
|
210
|
-
|
211
292
|
#### ActionMailer retries
|
212
293
|
|
213
|
-
|
294
|
+
Any configuration in `ApplicationJob` will have to be duplicated on `ActionMailer::DeliveryJob` because ActionMailer uses a custom class, `ActionMailer::DeliveryJob`, which inherits from `ActiveJob::Base`, rather than your applications `ApplicationJob`.
|
295
|
+
|
296
|
+
You can use an initializer to configure `ActionMailer::DeliveryJob`, for example:
|
214
297
|
|
215
298
|
```ruby
|
216
299
|
# config/initializers/good_job.rb
|
@@ -225,14 +308,14 @@ rescue StandardError => e
|
|
225
308
|
end
|
226
309
|
```
|
227
310
|
|
228
|
-
|
311
|
+
### Timeouts
|
229
312
|
|
230
313
|
Job timeouts can be configured with an `around_perform`:
|
231
314
|
|
232
315
|
```ruby
|
233
|
-
class ApplicationJob < ActiveJob::Base
|
316
|
+
class ApplicationJob < ActiveJob::Base
|
234
317
|
JobTimeoutError = Class.new(StandardError)
|
235
|
-
|
318
|
+
|
236
319
|
around_perform do |_job, block|
|
237
320
|
# Timeout jobs after 10 minutes
|
238
321
|
Timeout.timeout(10.minutes, JobTimeoutError) do
|
@@ -242,20 +325,61 @@ class ApplicationJob < ActiveJob::Base
|
|
242
325
|
end
|
243
326
|
```
|
244
327
|
|
245
|
-
###
|
246
|
-
|
247
|
-
GoodJob
|
328
|
+
### Optimize queues, threads, and processes
|
329
|
+
|
330
|
+
By default, GoodJob creates a single thread execution pool that will execute jobs from any queue. Depending on your application's workload, job types, and service level objectives, you may wish to optimize execution resources. For example, providing dedicated execution resources for transactional emails so they are not delayed by long-running batch jobs. Some options:
|
331
|
+
|
332
|
+
- Multiple execution pools within a single process:
|
248
333
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
334
|
+
```bash
|
335
|
+
$ bundle exec good_job --queues=transactional_messages:2;batch_processing:1;-transactional_messages,batch_processing:2;* --max-threads=5
|
336
|
+
```
|
337
|
+
|
338
|
+
This configuration will result in a single process with 4 isolated thread execution pools. Isolated execution pools are separated with a semicolon (`;`) and queue names and thread counts with a colon (`:`)
|
339
|
+
|
340
|
+
- `transactional_messages:2`: execute jobs enqueued on `transactional_messages` with up to 2 threads.
|
341
|
+
- `batch_processing:1` execute jobs enqueued on `batch_processing` with a single thread.
|
342
|
+
- `-transactional_messages,batch_processing`: execute jobs enqueued on _any_ queue _excluding_ `transactional_messages` or `batch_processing` with up to 2 threads.
|
343
|
+
- `*`: execute jobs on any queue on up to 5 threads, as configured by `--max-threads=5`
|
344
|
+
|
345
|
+
For moderate workloads, multiple isolated thread execution pools offers a good balance between congestion management and economy.
|
346
|
+
|
347
|
+
Configuration can be injected by environment variables too:
|
348
|
+
|
349
|
+
```bash
|
350
|
+
$ GOOD_JOB_QUEUES="transactional_messages:2;batch_processing:1;-transactional_messages,batch_processing:2;*" GOOD_JOB_MAX_THREADS=5 bundle exec good_job
|
351
|
+
```
|
352
|
+
|
353
|
+
- Multiple processes; for example, on Heroku:
|
354
|
+
|
355
|
+
```procfile
|
356
|
+
# Procfile
|
357
|
+
|
358
|
+
# Separate dyno types
|
359
|
+
worker: bundle exec good_job --max-threads=5
|
360
|
+
transactional_worker: bundle exec good_job --queues=transactional_messages --max-threads=2
|
361
|
+
batch_worker: bundle exec good_job --queues=batch_processing --max-threads=1
|
362
|
+
|
363
|
+
# Combined multi-process dyno
|
364
|
+
combined_worker: bundle exec good_job --max-threads=5 & bundle exec good_job --queues=transactional_messages --max-threads=2 & bundle exec good_job --queues=batch_processing --max-threads=1 & wait -n
|
365
|
+
```
|
366
|
+
|
367
|
+
Running multiple processes can optimize for CPU performance at the expense of greater memory and system resource usage.
|
368
|
+
|
369
|
+
Keep in mind, queue operations and management is an advanced discipline. This stuff is complex, especially for heavy workloads and unique processing requirements. Good job 👍
|
370
|
+
|
371
|
+
### Database connections
|
372
|
+
|
373
|
+
Each GoodJob execution thread requires its own database connection that is automatically checked out from Rails’s connection pool. _Allowing GoodJob to create more threads than available database connections can lead to timeouts and is not recommended._ For example:
|
374
|
+
|
375
|
+
```yaml
|
376
|
+
# config/database.yml
|
377
|
+
pool: <%= [ENV.fetch("RAILS_MAX_THREADS", 5).to_i, ENV.fetch("GOOD_JOB_MAX_THREADS", 4).to_i].max %>
|
378
|
+
```
|
255
379
|
|
256
380
|
### Executing jobs async / in-process
|
257
381
|
|
258
|
-
GoodJob
|
382
|
+
GoodJob can execute jobs "async" in the same process as the webserver (e.g. `bin/rail s`). GoodJob's async execution mode offers benefits of economy by not requiring a separate job worker process, but with the tradeoff of increased complexity. Async mode can be configured in two ways:
|
259
383
|
|
260
384
|
- Directly configure the ActiveJob adapter:
|
261
385
|
|
@@ -263,12 +387,13 @@ GoodJob is able to run "async" in the same process as the webserver (e.g. `bin/r
|
|
263
387
|
# config/environments/production.rb
|
264
388
|
config.active_job.queue_adapter = GoodJob::Adapter.new(execution_mode: :async, max_threads: 4, poll_interval: 30)
|
265
389
|
```
|
390
|
+
|
266
391
|
- Or, when using `...queue_adapter = :good_job`, via environment variables:
|
267
392
|
|
268
393
|
```bash
|
269
394
|
$ GOOD_JOB_EXECUTION_MODE=async GOOD_JOB_MAX_THREADS=4 GOOD_JOB_POLL_INTERVAL=30 bin/rails server
|
270
395
|
```
|
271
|
-
|
396
|
+
|
272
397
|
Depending on your application configuration, you may need to take additional steps:
|
273
398
|
|
274
399
|
- Ensure that you have enough database connections for both web and job execution threads:
|
@@ -282,27 +407,27 @@ Depending on your application configuration, you may need to take additional ste
|
|
282
407
|
|
283
408
|
```ruby
|
284
409
|
# config/puma.rb
|
285
|
-
|
410
|
+
|
286
411
|
before_fork do
|
287
412
|
GoodJob.shutdown
|
288
413
|
end
|
289
|
-
|
414
|
+
|
290
415
|
on_worker_boot do
|
291
416
|
GoodJob.restart
|
292
417
|
end
|
293
|
-
|
418
|
+
|
294
419
|
on_worker_shutdown do
|
295
420
|
GoodJob.shutdown
|
296
421
|
end
|
297
|
-
|
422
|
+
|
298
423
|
MAIN_PID = Process.pid
|
299
424
|
at_exit do
|
300
425
|
GoodJob.shutdown if Process.pid == MAIN_PID
|
301
426
|
end
|
302
427
|
```
|
303
|
-
|
428
|
+
|
304
429
|
GoodJob is compatible with Puma's `preload_app!` method.
|
305
|
-
|
430
|
+
|
306
431
|
### Migrating to GoodJob from a different ActiveJob backend
|
307
432
|
|
308
433
|
If your application is already using an ActiveJob backend, you will need to install GoodJob to enqueue and perform newly created jobs _and_ finish performing pre-existing jobs on the previous backend.
|
@@ -318,7 +443,7 @@ If your application is already using an ActiveJob backend, you will need to inst
|
|
318
443
|
```
|
319
444
|
|
320
445
|
1. Continue running executors for both backends. For example, on Heroku it's possible to run [two processes](https://help.heroku.com/CTFS2TJK/how-do-i-run-multiple-processes-on-a-dyno) within the same dyno:
|
321
|
-
|
446
|
+
|
322
447
|
```procfile
|
323
448
|
# Procfile
|
324
449
|
# ...
|
@@ -331,7 +456,7 @@ If your application is already using an ActiveJob backend, you will need to inst
|
|
331
456
|
|
332
457
|
GoodJob is fully instrumented with [`ActiveSupport::Notifications`](https://edgeguides.rubyonrails.org/active_support_instrumentation.html#introduction-to-instrumentation).
|
333
458
|
|
334
|
-
By default, GoodJob will delete job records after they are run, regardless of whether they succeed or not (raising a kind of `StandardError`), unless they are interrupted (raising a kind of `Exception`).
|
459
|
+
By default, GoodJob will delete job records after they are run, regardless of whether they succeed or not (raising a kind of `StandardError`), unless they are interrupted (raising a kind of `Exception`).
|
335
460
|
|
336
461
|
To preserve job records for later inspection, set an initializer:
|
337
462
|
|
@@ -343,7 +468,7 @@ GoodJob.preserve_job_records = true
|
|
343
468
|
It is also necessary to delete these preserved jobs from the database after a certain time period:
|
344
469
|
|
345
470
|
- For example, in a Rake task:
|
346
|
-
|
471
|
+
|
347
472
|
```ruby
|
348
473
|
GoodJob::Job.finished(1.day.ago).delete_all
|
349
474
|
```
|
@@ -413,7 +538,7 @@ $ gem signin
|
|
413
538
|
# Update version number, changelog, and create git commit:
|
414
539
|
$ bundle exec rake release[minor] # major,minor,patch
|
415
540
|
|
416
|
-
# ..and follow subsequent directions.
|
541
|
+
# ..and follow subsequent directions.
|
417
542
|
```
|
418
543
|
|
419
544
|
## License
|
data/lib/good_job.rb
CHANGED
@@ -8,20 +8,44 @@ require 'good_job/job'
|
|
8
8
|
require 'good_job/scheduler'
|
9
9
|
require 'good_job/multi_scheduler'
|
10
10
|
require 'good_job/adapter'
|
11
|
-
require 'good_job/pg_locks'
|
12
11
|
require 'good_job/performer'
|
13
12
|
require 'good_job/current_execution'
|
14
13
|
require 'good_job/notifier'
|
15
14
|
|
16
15
|
require 'active_job/queue_adapters/good_job_adapter'
|
17
16
|
|
17
|
+
# GoodJob is a multithreaded, Postgres-based, ActiveJob backend for Ruby on Rails.
|
18
|
+
#
|
19
|
+
# +GoodJob+ is the top-level namespace and exposes configuration attributes.
|
18
20
|
module GoodJob
|
21
|
+
# @!attribute [rw] logger
|
22
|
+
# @!scope class
|
23
|
+
# The logger used by GoodJob
|
24
|
+
# @return [Logger]
|
19
25
|
mattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
|
26
|
+
|
27
|
+
# @!attribute [rw] preserve_job_records
|
28
|
+
# @!scope class
|
29
|
+
# Whether to preserve job records in the database after they have finished for inspection
|
30
|
+
# @return [Boolean]
|
20
31
|
mattr_accessor :preserve_job_records, default: false
|
32
|
+
|
33
|
+
# @!attribute [rw] reperform_jobs_on_standard_error
|
34
|
+
# @!scope class
|
35
|
+
# Whether to re-perform a job when a type of +StandardError+ is raised and bubbles up to the GoodJob backend
|
36
|
+
# @return [Boolean]
|
21
37
|
mattr_accessor :reperform_jobs_on_standard_error, default: true
|
22
|
-
mattr_accessor :on_thread_error, default: nil
|
23
38
|
|
24
|
-
|
39
|
+
# @!attribute [rw] on_thread_error
|
40
|
+
# @!scope class
|
41
|
+
# Called when a thread raises an error
|
42
|
+
# @example Send errors to Sentry
|
43
|
+
# # config/initializers/good_job.rb
|
44
|
+
#
|
45
|
+
# # With Sentry (or Bugsnag, Airbrake, Honeybadger, etc.)
|
46
|
+
# GoodJob.on_thread_error = -> (exception) { Raven.capture_exception(exception) }
|
47
|
+
# @return [#call, nil]
|
48
|
+
mattr_accessor :on_thread_error, default: nil
|
25
49
|
|
26
50
|
# Shuts down all execution pools
|
27
51
|
# @param wait [Boolean] whether to wait for shutdown
|
@@ -43,4 +67,6 @@ module GoodJob
|
|
43
67
|
Notifier.instances.each(&:restart)
|
44
68
|
Scheduler.instances.each(&:restart)
|
45
69
|
end
|
70
|
+
|
71
|
+
ActiveSupport.run_load_hooks(:good_job, self)
|
46
72
|
end
|
data/lib/good_job/adapter.rb
CHANGED
@@ -9,10 +9,12 @@ module GoodJob
|
|
9
9
|
end
|
10
10
|
|
11
11
|
configuration = GoodJob::Configuration.new(
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
{
|
13
|
+
execution_mode: execution_mode,
|
14
|
+
queues: queues,
|
15
|
+
max_threads: max_threads,
|
16
|
+
poll_interval: poll_interval,
|
17
|
+
}
|
16
18
|
)
|
17
19
|
|
18
20
|
@execution_mode = configuration.execution_mode
|
data/lib/good_job/cli.rb
CHANGED
@@ -4,17 +4,29 @@ module GoodJob
|
|
4
4
|
class CLI < Thor
|
5
5
|
RAILS_ENVIRONMENT_RB = File.expand_path("config/environment.rb")
|
6
6
|
|
7
|
-
desc :start,
|
7
|
+
desc :start, <<~DESCRIPTION
|
8
|
+
Executes queued jobs.
|
9
|
+
|
10
|
+
All options can be configured with environment variables.
|
11
|
+
See option descriptions for the matching environment variable name.
|
12
|
+
|
13
|
+
== Configuring queues
|
14
|
+
Separate multiple queues with commas; exclude queues with a leading minus;
|
15
|
+
separate isolated execution pools with semicolons and threads with colons.
|
16
|
+
|
17
|
+
DESCRIPTION
|
8
18
|
method_option :max_threads,
|
9
19
|
type: :numeric,
|
10
|
-
|
20
|
+
banner: 'COUNT',
|
21
|
+
desc: "Maximum number of threads to use for working jobs. (env var: GOOD_JOB_MAX_THREADS, default: 5)"
|
11
22
|
method_option :queues,
|
12
23
|
type: :string,
|
13
|
-
banner: "
|
14
|
-
desc: "Queues to work from.
|
24
|
+
banner: "QUEUE_LIST",
|
25
|
+
desc: "Queues to work from. (env var: GOOD_JOB_QUEUES, default: *)"
|
15
26
|
method_option :poll_interval,
|
16
27
|
type: :numeric,
|
17
|
-
|
28
|
+
banner: 'SECONDS',
|
29
|
+
desc: "Interval between polls for available jobs in seconds (env var: GOOD_JOB_POLL_INTERVAL, default: 1)"
|
18
30
|
def start
|
19
31
|
set_up_application!
|
20
32
|
|
@@ -39,17 +51,35 @@ module GoodJob
|
|
39
51
|
|
40
52
|
default_task :start
|
41
53
|
|
42
|
-
desc :cleanup_preserved_jobs,
|
54
|
+
desc :cleanup_preserved_jobs, <<~DESCRIPTION
|
55
|
+
Deletes preserved job records.
|
56
|
+
|
57
|
+
By default, GoodJob deletes job records when the job is performed and this
|
58
|
+
command is not necessary.
|
59
|
+
|
60
|
+
However, when `GoodJob.preserve_job_records = true`, the jobs will be
|
61
|
+
preserved in the database. This is useful when wanting to analyze or
|
62
|
+
inspect job performance.
|
63
|
+
|
64
|
+
If you are preserving job records this way, use this command regularly
|
65
|
+
to delete old records and preserve space in your database.
|
66
|
+
|
67
|
+
DESCRIPTION
|
43
68
|
method_option :before_seconds_ago,
|
44
69
|
type: :numeric,
|
45
|
-
|
46
|
-
desc: "Delete records finished more than this many seconds ago"
|
47
|
-
|
70
|
+
banner: 'SECONDS',
|
71
|
+
desc: "Delete records finished more than this many seconds ago (env var: GOOD_JOB_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO, default: 86400)"
|
48
72
|
def cleanup_preserved_jobs
|
49
73
|
set_up_application!
|
50
74
|
|
51
|
-
|
52
|
-
|
75
|
+
configuration = GoodJob::Configuration.new(options)
|
76
|
+
|
77
|
+
timestamp = Time.current - configuration.cleanup_preserved_jobs_before_seconds_ago
|
78
|
+
|
79
|
+
ActiveSupport::Notifications.instrument(
|
80
|
+
"cleanup_preserved_jobs.good_job",
|
81
|
+
{ before_seconds_ago: configuration.cleanup_preserved_jobs_before_seconds_ago, timestamp: timestamp }
|
82
|
+
) do |payload|
|
53
83
|
deleted_records_count = GoodJob::Job.finished(timestamp).delete_all
|
54
84
|
|
55
85
|
payload[:deleted_records_count] = deleted_records_count
|
data/lib/good_job/job.rb
CHANGED
@@ -58,7 +58,8 @@ module GoodJob
|
|
58
58
|
|
59
59
|
unfinished.priority_ordered.only_scheduled.limit(1).with_advisory_lock do |good_jobs|
|
60
60
|
good_job = good_jobs.first
|
61
|
-
|
61
|
+
# TODO: Determine why some records are fetched without an advisory lock at all
|
62
|
+
break unless good_job&.owns_advisory_lock?
|
62
63
|
|
63
64
|
result, error = good_job.perform
|
64
65
|
end
|
data/lib/good_job/lockable.rb
CHANGED
@@ -25,8 +25,8 @@ module GoodJob
|
|
25
25
|
join_sql = <<~SQL
|
26
26
|
LEFT JOIN pg_locks ON pg_locks.locktype = 'advisory'
|
27
27
|
AND pg_locks.objsubid = 1
|
28
|
-
AND pg_locks.classid = ('x'||substr(md5(:table_name ||
|
29
|
-
AND pg_locks.objid = (('x'||substr(md5(:table_name ||
|
28
|
+
AND pg_locks.classid = ('x'||substr(md5(:table_name || #{quoted_table_name}.#{quoted_primary_key}::text), 1, 16))::bit(32)::int
|
29
|
+
AND pg_locks.objid = (('x'||substr(md5(:table_name || #{quoted_table_name}.#{quoted_primary_key}::text), 1, 16))::bit(64) << 32)::bit(32)::int
|
30
30
|
SQL
|
31
31
|
|
32
32
|
joins(sanitize_sql_for_conditions([join_sql, { table_name: table_name }]))
|
@@ -55,19 +55,17 @@ module GoodJob
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def advisory_lock
|
58
|
-
|
59
|
-
|
60
|
-
WHERE pg_try_advisory_lock(('x'||substr(md5(:table_name || :id::text), 1, 16))::bit(64)::bigint)
|
58
|
+
where_sql = <<~SQL
|
59
|
+
pg_try_advisory_lock(('x'||substr(md5(:table_name || :id::text), 1, 16))::bit(64)::bigint)
|
61
60
|
SQL
|
62
|
-
self.class.
|
61
|
+
self.class.unscoped.where(where_sql, { table_name: self.class.table_name, id: send(self.class.primary_key) }).exists?
|
63
62
|
end
|
64
63
|
|
65
64
|
def advisory_unlock
|
66
|
-
|
67
|
-
|
68
|
-
WHERE pg_advisory_unlock(('x'||substr(md5(:table_name || :id::text), 1, 16))::bit(64)::bigint)
|
65
|
+
where_sql = <<~SQL
|
66
|
+
pg_advisory_unlock(('x'||substr(md5(:table_name || :id::text), 1, 16))::bit(64)::bigint)
|
69
67
|
SQL
|
70
|
-
self.class.
|
68
|
+
self.class.unscoped.where(where_sql, { table_name: self.class.table_name, id: send(self.class.primary_key) }).exists?
|
71
69
|
end
|
72
70
|
|
73
71
|
def advisory_lock!
|
@@ -85,11 +83,11 @@ module GoodJob
|
|
85
83
|
end
|
86
84
|
|
87
85
|
def advisory_locked?
|
88
|
-
self.class.advisory_locked.where(id: send(self.class.primary_key)).
|
86
|
+
self.class.unscoped.advisory_locked.where(id: send(self.class.primary_key)).exists?
|
89
87
|
end
|
90
88
|
|
91
89
|
def owns_advisory_lock?
|
92
|
-
self.class.owns_advisory_locked.where(id: send(self.class.primary_key)).
|
90
|
+
self.class.unscoped.owns_advisory_locked.where(id: send(self.class.primary_key)).exists?
|
93
91
|
end
|
94
92
|
|
95
93
|
def advisory_unlock!
|
data/lib/good_job/notifier.rb
CHANGED
@@ -7,6 +7,7 @@ module GoodJob # :nodoc:
|
|
7
7
|
class Notifier
|
8
8
|
CHANNEL = 'good_job'.freeze
|
9
9
|
POOL_OPTIONS = {
|
10
|
+
name: name,
|
10
11
|
min_threads: 0,
|
11
12
|
max_threads: 1,
|
12
13
|
auto_terminate: true,
|
@@ -69,13 +70,12 @@ module GoodJob # :nodoc:
|
|
69
70
|
|
70
71
|
def listen
|
71
72
|
future = Concurrent::Future.new(args: [@recipients, @pool, @listening], executor: @pool) do |recipients, pool, listening|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
73
|
+
begin
|
74
|
+
with_listen_connection do |conn|
|
75
|
+
ActiveSupport::Notifications.instrument("notifier_listen.good_job") do
|
76
|
+
conn.async_exec "LISTEN #{CHANNEL}"
|
77
|
+
end
|
77
78
|
|
78
|
-
begin
|
79
79
|
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
80
80
|
while pool.running?
|
81
81
|
listening.make_true
|
@@ -93,14 +93,14 @@ module GoodJob # :nodoc:
|
|
93
93
|
listening.make_false
|
94
94
|
end
|
95
95
|
end
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
96
|
+
end
|
97
|
+
rescue StandardError => e
|
98
|
+
ActiveSupport::Notifications.instrument("notifier_notify_error.good_job", { error: e })
|
99
|
+
raise
|
100
|
+
ensure
|
101
|
+
@listening.make_false
|
102
|
+
ActiveSupport::Notifications.instrument("notifier_unlisten.good_job") do
|
103
|
+
conn.async_exec "UNLISTEN *"
|
104
104
|
end
|
105
105
|
end
|
106
106
|
end
|
@@ -112,5 +112,16 @@ module GoodJob # :nodoc:
|
|
112
112
|
def listen_observer(_time, _result, _thread_error)
|
113
113
|
listen unless shutdown?
|
114
114
|
end
|
115
|
+
|
116
|
+
def with_listen_connection
|
117
|
+
ar_conn = ActiveRecord::Base.connection_pool.checkout.tap do |conn|
|
118
|
+
ActiveRecord::Base.connection_pool.remove(conn)
|
119
|
+
end
|
120
|
+
pg_conn = ar_conn.raw_connection
|
121
|
+
pg_conn.exec("SET application_name = #{pg_conn.escape_identifier(self.class.name)}")
|
122
|
+
yield pg_conn
|
123
|
+
ensure
|
124
|
+
ar_conn.disconnect!
|
125
|
+
end
|
115
126
|
end
|
116
127
|
end
|
data/lib/good_job/scheduler.rb
CHANGED
data/lib/good_job/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: good_job
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Sheldon
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-09-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activejob
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 5.1.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 5.1.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activerecord
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 5.1.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 5.1.0
|
13
41
|
- !ruby/object:Gem::Dependency
|
14
42
|
name: concurrent-ruby
|
15
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -39,7 +67,7 @@ dependencies:
|
|
39
67
|
- !ruby/object:Gem::Version
|
40
68
|
version: 1.0.0
|
41
69
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
70
|
+
name: railties
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
44
72
|
requirements:
|
45
73
|
- - ">="
|
@@ -150,6 +178,48 @@ dependencies:
|
|
150
178
|
- - ">="
|
151
179
|
- !ruby/object:Gem::Version
|
152
180
|
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: kramdown
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: kramdown-parser-gfm
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - ">="
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0'
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ">="
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: mdl
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - ">="
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: '0'
|
216
|
+
type: :development
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - ">="
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '0'
|
153
223
|
- !ruby/object:Gem::Dependency
|
154
224
|
name: pry-rails
|
155
225
|
requirement: !ruby/object:Gem::Requirement
|
@@ -319,7 +389,6 @@ files:
|
|
319
389
|
- lib/good_job/multi_scheduler.rb
|
320
390
|
- lib/good_job/notifier.rb
|
321
391
|
- lib/good_job/performer.rb
|
322
|
-
- lib/good_job/pg_locks.rb
|
323
392
|
- lib/good_job/railtie.rb
|
324
393
|
- lib/good_job/scheduler.rb
|
325
394
|
- lib/good_job/version.rb
|
data/lib/good_job/pg_locks.rb
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
module GoodJob
|
2
|
-
class PgLocks < ActiveRecord::Base
|
3
|
-
self.table_name = 'pg_locks'.freeze
|
4
|
-
|
5
|
-
# https://www.postgresql.org/docs/9.6/view-pg-locks.html
|
6
|
-
# Advisory locks can be acquired on keys consisting of either a single bigint value or two integer values.
|
7
|
-
# A bigint key is displayed with its high-order half in the classid column, its low-order half in the objid column, and objsubid equal to 1.
|
8
|
-
# The original bigint value can be reassembled with the expression (classid::bigint << 32) | objid::bigint.
|
9
|
-
# Integer keys are displayed with the first key in the classid column, the second key in the objid column, and objsubid equal to 2.
|
10
|
-
# The actual meaning of the keys is up to the user. Advisory locks are local to each database, so the database column is meaningful for an advisory lock.
|
11
|
-
def self.advisory_lock_details
|
12
|
-
connection.select <<~SQL
|
13
|
-
SELECT *
|
14
|
-
FROM pg_locks
|
15
|
-
WHERE
|
16
|
-
locktype = 'advisory' AND
|
17
|
-
objsubid = 1
|
18
|
-
SQL
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|