que 2.0.0.beta1 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +58 -1
- data/README.md +7 -3
- data/bin/que +1 -1
- data/docs/README.md +145 -51
- data/{bin → lib/que}/command_line_interface.rb +0 -0
- data/lib/que/connection.rb +1 -1
- data/lib/que/job.rb +122 -12
- data/lib/que/migrations/7/down.sql +5 -0
- data/lib/que/migrations/7/up.sql +13 -0
- data/lib/que/migrations.rb +17 -4
- data/lib/que/version.rb +1 -1
- data/lib/que.rb +1 -1
- data/scripts/test +1 -0
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fd8e2e7b77de787218a646c550cf38fddd52091c91422fdb5813b12fe1af775c
|
4
|
+
data.tar.gz: e9abc8a607b82db9b0ed925163ed4b21d61f7f2588b1dfd91bf8b558af3fa14c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 55cea89090a9685c5001aba97794e0fa2246362da2b530b02808966e3a4487d07a908ec980d141ad76d59bd04065863fbc097af2ea78acefda110dfdb37cea48
|
7
|
+
data.tar.gz: 114317905799b63fcc9b4c5180a15f24da03e7937790e6b330d0d67c5a0d7df062c470dd2b8a98426f5ced795b5da6d435a647e9504b66ba669fb72953872f66
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,10 @@
|
|
2
2
|
|
3
3
|
<!-- MarkdownTOC autolink=true -->
|
4
4
|
|
5
|
+
- [2.2.0 \(2022-08-29\)](#220-2022-08-29)
|
6
|
+
- [2.1.0 \(2022-08-25\)](#210-2022-08-25)
|
7
|
+
- [2.0.0 \(2022-08-25\)](#200-2022-08-25)
|
8
|
+
- [1.4.1 \(2022-07-24\)](#141-2022-07-24)
|
5
9
|
- [2.0.0.beta1 \(2022-03-24\)](#200beta1-2022-03-24)
|
6
10
|
- [1.4.0 \(2022-03-23\)](#140-2022-03-23)
|
7
11
|
- [1.3.1 \(2022-02-25\)](#131-2022-02-25)
|
@@ -52,6 +56,57 @@
|
|
52
56
|
|
53
57
|
<!-- /MarkdownTOC -->
|
54
58
|
|
59
|
+
## 2.2.0 (2022-08-29)
|
60
|
+
|
61
|
+
- **Changed**:
|
62
|
+
+ When migrating, now raises an exception when the Que DB schema version is missing from the database. The migrations system records this version in a comment on the `que_jobs` table. [#379](https://github.com/que-rb/que/pull/379)
|
63
|
+
* > Que::Error: Cannot determine Que DB schema version.
|
64
|
+
>
|
65
|
+
> The que_jobs table is missing its comment recording the Que DB schema version. This is likely due to a bug in Rails schema dump in Rails 7 versions prior to 7.0.3, omitting comments - see https://github.com/que-rb/que/issues/363. Please determine the appropriate schema version from your migrations and record it manually by running the following SQL (replacing version as appropriate):
|
66
|
+
>
|
67
|
+
> COMMENT ON TABLE que_jobs IS 'version';
|
68
|
+
- **Removed**:
|
69
|
+
+ Removed support for upgrading directly from a version of Que prior to v0.5.0 (released on 2014-01-14), which introduced the migrations system. It's too difficult to handle the different DB schemas from prior to this.
|
70
|
+
- **Internal**:
|
71
|
+
+ Moved `command_line_interface.rb` from `bin/` to `lib/`. [#378](https://github.com/que-rb/que/pull/378)
|
72
|
+
|
73
|
+
## 2.1.0 (2022-08-25)
|
74
|
+
|
75
|
+
- **Added**:
|
76
|
+
+ Added bulk enqueue interface for performance when enqueuing a large number of jobs at once - [docs](docs#enqueueing-jobs-in-bulk).
|
77
|
+
- **Deprecated**:
|
78
|
+
+ Deprecated `que_state_notify` trigger (`que_state` notification channel / `job_change` notification message). See [#372](https://github.com/que-rb/que/issues/372). We plan to remove this in a future release - let us know on the issue if you desire otherwise.
|
79
|
+
|
80
|
+
This release contains a database migration. You will need to migrate Que to the latest database schema version (7). For example, on ActiveRecord and Rails 6:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
class UpdateQueTablesToVersion7 < ActiveRecord::Migration[6.0]
|
84
|
+
def up
|
85
|
+
Que.migrate!(version: 7)
|
86
|
+
end
|
87
|
+
|
88
|
+
def down
|
89
|
+
Que.migrate!(version: 6)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
## 2.0.0 (2022-08-25)
|
95
|
+
|
96
|
+
**Important: Do not upgrade straight to Que 2.** You will need to first update to the latest 1.x version, apply the Que database schema migration, and deploy, before you can safely begin the process of upgrading to Que 2. See the [2.0.0.beta1 changelog entry](#200beta1-2022-03-24) for details.
|
97
|
+
|
98
|
+
See beta 2.0.0.beta1, plus:
|
99
|
+
|
100
|
+
- **Fixed**:
|
101
|
+
+ Updated to use non-deprecated method from PG when params are passed (`#async_exec_params`). [#374](https://github.com/que-rb/que/pull/374)
|
102
|
+
|
103
|
+
Note that @dtcristo submitted a PR proposing an easier upgrade path to Que 2 and Ruby 3 - [#365](https://github.com/que-rb/que/pull/365). We are yet to properly consider this, but a later release which includes this feature would mean you don't need to simultaneously deploy Que 1.x and 2.x workers during the upgrade.
|
104
|
+
|
105
|
+
## 1.4.1 (2022-07-24)
|
106
|
+
|
107
|
+
- **Added**
|
108
|
+
+ Added Ruby version requirement of < 3. For Ruby 3 compatibility, upgrade to Que 2 - [upgrade process](https://github.com/que-rb/que/blob/master/CHANGELOG.md#200beta1-2022-03-24)
|
109
|
+
|
55
110
|
## 2.0.0.beta1 (2022-03-24)
|
56
111
|
|
57
112
|
**Preliminary release of Ruby 3 support**
|
@@ -105,6 +160,8 @@ To run workers with two different versions of Que, you'll probably need to tempo
|
|
105
160
|
- Update the bundle at `que-1-gemfile/Gemfile.lock` using `BUNDLE_GEMFILE=que-1-gemfile/Gemfile bundle`
|
106
161
|
- Create a second deployment of Que, but with your `que` command prefixed with `BUNDLE_GEMFILE=que-1-gemfile/Gemfile`
|
107
162
|
|
163
|
+
We'd appreciate feedback on your experience upgrading to and running Que 2. Feel free to post on our Discord, or if you run into trouble, open an issue on GitHub.
|
164
|
+
|
108
165
|
## 1.4.0 (2022-03-23)
|
109
166
|
|
110
167
|
- **Fixed**
|
@@ -115,7 +172,7 @@ To run workers with two different versions of Que, you'll probably need to tempo
|
|
115
172
|
* It became used in 1.0.0.beta4, and that changelog entry has been updated to reflect this.
|
116
173
|
- **Documentation**:
|
117
174
|
+ Reformatted the changelog to be more consistent, including adding links to all issue/PR numbers. [#347](https://github.com/que-rb/que/pull/347)
|
118
|
-
|
175
|
+
|
119
176
|
## 1.3.1 (2022-02-25)
|
120
177
|
|
121
178
|
Unfortunately, v1.3.0 was broken. Follow its upgrade instructions, but use this version instead.
|
data/README.md
CHANGED
@@ -23,7 +23,7 @@ Que's secondary goal is performance. The worker process is multithreaded, so tha
|
|
23
23
|
|
24
24
|
Compatibility:
|
25
25
|
|
26
|
-
- MRI Ruby 2.7+
|
26
|
+
- MRI Ruby 2.7+ (for Ruby 3, Que 2+ is required)
|
27
27
|
- PostgreSQL 9.5+
|
28
28
|
- Rails 6.0+ (optional)
|
29
29
|
|
@@ -119,7 +119,11 @@ You can also add options to run the job after a specific time, or with a specifi
|
|
119
119
|
```ruby
|
120
120
|
ChargeCreditCard.enqueue(card.id, user_id: current_user.id, job_options: { run_at: 1.day.from_now, priority: 5 })
|
121
121
|
```
|
122
|
+
|
123
|
+
[Learn more about job options](docs/README.md#job-options).
|
124
|
+
|
122
125
|
## Running the Que Worker
|
126
|
+
|
123
127
|
In order to process jobs, you must start a separate worker process outside of your main server.
|
124
128
|
|
125
129
|
```bash
|
@@ -192,11 +196,11 @@ If you have a project that uses or relates to Que, feel free to submit a PR addi
|
|
192
196
|
|
193
197
|
## Community and Contributing
|
194
198
|
|
195
|
-
- For bugs in the library, please feel free to [open an issue](https://github.com/que-rb/que/issues/new).
|
199
|
+
- For feature suggestions or bugs in the library, please feel free to [open an issue](https://github.com/que-rb/que/issues/new).
|
196
200
|
- For general discussion and questions/concerns that don't relate to obvious bugs, join our [Discord Server](https://discord.gg/B3EW32H).
|
197
201
|
- For contributions, pull requests submitted via Github are welcome.
|
198
202
|
|
199
|
-
Regarding contributions, one of the project's priorities is to keep Que as simple, lightweight and dependency-free as possible, and pull requests that change too much or wouldn't be useful to the majority of Que's users have a good chance of being rejected. If you're thinking of submitting a pull request that adds a new feature, consider starting a discussion in
|
203
|
+
Regarding contributions, one of the project's priorities is to keep Que as simple, lightweight and dependency-free as possible, and pull requests that change too much or wouldn't be useful to the majority of Que's users have a good chance of being rejected. If you're thinking of submitting a pull request that adds a new feature, consider starting a discussion in an issue first about what it would do and how it would be implemented. If it's a sufficiently large feature, or if most of Que's users wouldn't find it useful, it may be best implemented as a standalone gem, like some of the related projects above.
|
200
204
|
|
201
205
|
### Specs
|
202
206
|
|
data/bin/que
CHANGED
data/docs/README.md
CHANGED
@@ -1,52 +1,63 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# Que documentation
|
2
|
+
|
3
|
+
<!-- MarkdownTOC autolink=true -->
|
3
4
|
|
4
5
|
- [Command Line Interface](#command-line-interface)
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
- [`worker-priorities` and `worker-count`](#worker-priorities-and-worker-count)
|
7
|
+
- [`poll-interval`](#poll-interval)
|
8
|
+
- [`maximum-buffer-size`](#maximum-buffer-size)
|
9
|
+
- [`connection-url`](#connection-url)
|
10
|
+
- [`wait-period`](#wait-period)
|
11
|
+
- [`log-internals`](#log-internals)
|
11
12
|
- [Advanced Setup](#advanced-setup)
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
- [Using ActiveRecord Without Rails](#using-activerecord-without-rails)
|
14
|
+
- [Managing the Jobs Table](#managing-the-jobs-table)
|
15
|
+
- [Other Setup](#other-setup)
|
15
16
|
- [Error Handling](#error-handling)
|
16
|
-
|
17
|
-
|
17
|
+
- [Error Notifications](#error-notifications)
|
18
|
+
- [Error-Specific Handling](#error-specific-handling)
|
18
19
|
- [Inspecting the Queue](#inspecting-the-queue)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
- [Job Stats](#job-stats)
|
21
|
+
- [Custom Queries](#custom-queries)
|
22
|
+
- [ActiveRecord Example](#activerecord-example)
|
23
|
+
- [Sequel Example](#sequel-example)
|
23
24
|
- [Managing Workers](#managing-workers)
|
24
|
-
|
25
|
-
|
25
|
+
- [Working Jobs Via Executable](#working-jobs-via-executable)
|
26
|
+
- [Thread-Unsafe Application Code](#thread-unsafe-application-code)
|
26
27
|
- [Logging](#logging)
|
27
|
-
|
28
|
+
- [Logging Job Completion](#logging-job-completion)
|
28
29
|
- [Migrating](#migrating)
|
29
30
|
- [Multiple Queues](#multiple-queues)
|
30
31
|
- [Shutting Down Safely](#shutting-down-safely)
|
31
32
|
- [Using Plain Postgres Connections](#using-plain-postgres-connections)
|
32
|
-
|
33
|
-
|
33
|
+
- [Using ConnectionPool or Pond](#using-connectionpool-or-pond)
|
34
|
+
- [Using Any Other Connection Pool](#using-any-other-connection-pool)
|
34
35
|
- [Using Sequel](#using-sequel)
|
35
36
|
- [Using Que With ActiveJob](#using-que-with-activejob)
|
36
37
|
- [Job Helper Methods](#job-helper-methods)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
38
|
+
- [`destroy`](#destroy)
|
39
|
+
- [`finish`](#finish)
|
40
|
+
- [`expire`](#expire)
|
41
|
+
- [`retry_in`](#retry_in)
|
42
|
+
- [`error_count`](#error_count)
|
43
|
+
- [`default_resolve_action`](#default_resolve_action)
|
43
44
|
- [Writing Reliable Jobs](#writing-reliable-jobs)
|
44
|
-
|
45
|
+
- [Timeouts](#timeouts)
|
46
|
+
- [Job Options](#job-options)
|
47
|
+
- [`queue`](#queue)
|
48
|
+
- [`priority`](#priority)
|
49
|
+
- [`run_at`](#run_at)
|
50
|
+
- [`job_class`](#job_class)
|
51
|
+
- [`tags`](#tags)
|
45
52
|
- [Middleware](#middleware)
|
46
|
-
|
47
|
-
|
53
|
+
- [Defining Middleware For Jobs](#defining-middleware-for-jobs)
|
54
|
+
- [Defining Middleware For SQL statements](#defining-middleware-for-sql-statements)
|
48
55
|
- [Vacuuming](#vacuuming)
|
56
|
+
- [Enqueueing jobs in bulk](#enqueueing-jobs-in-bulk)
|
57
|
+
- [Expired jobs](#expired-jobs)
|
58
|
+
- [Finished jobs](#finished-jobs)
|
49
59
|
|
60
|
+
<!-- /MarkdownTOC -->
|
50
61
|
|
51
62
|
## Command Line Interface
|
52
63
|
|
@@ -67,7 +78,7 @@ usage: que [options] [file/to/require] ...
|
|
67
78
|
|
68
79
|
Some explanation of the more unusual options:
|
69
80
|
|
70
|
-
### worker-priorities and worker-count
|
81
|
+
### `worker-priorities` and `worker-count`
|
71
82
|
|
72
83
|
These options dictate the size and priority distribution of the worker pool. The default worker-priorities is `10,30,50,any,any,any`. This means that the default worker pool will reserve one worker to only works jobs with priorities under 10, one for priorities under 30, and one for priorities under 50. Three more workers will work any job.
|
73
84
|
|
@@ -77,23 +88,23 @@ Instead of passing worker-priorities, you can pass a `worker-count` - this is a
|
|
77
88
|
|
78
89
|
If you pass both worker-count and worker-priorities, the count will trim or pad the priorities list with `any` workers. So, `--worker-priorities=20,30,40 --worker-count=6` would be the same as passing `--worker-priorities=20,30,40,any,any,any`.
|
79
90
|
|
80
|
-
### poll-interval
|
91
|
+
### `poll-interval`
|
81
92
|
|
82
93
|
This option sets the number of seconds the process will wait between polls of the job queue. Jobs that are ready to be worked immediately will be broadcast via the LISTEN/NOTIFY system, so polling is unnecessary for them - polling is only necessary for jobs that are scheduled in the future or which are being delayed due to errors. The default is 5 seconds.
|
83
94
|
|
84
|
-
### maximum-buffer-size
|
95
|
+
### `maximum-buffer-size`
|
85
96
|
|
86
97
|
This option sets the size of the internal buffer that Que uses to hold jobs until they're ready for workers. The default maximum is 8, meaning that the process won't buffer more than 8 jobs that aren't yet ready to be worked. If you don't want jobs to be buffered at all, you can set this value to zero.
|
87
98
|
|
88
|
-
### connection-url
|
99
|
+
### `connection-url`
|
89
100
|
|
90
101
|
This option sets the URL to be used to open a connection to the database for locking purposes. By default, Que will simply use a connection from the connection pool for locking - this option is only useful if your application connections can't use advisory locks - for example, if they're passed through an external connection pool like PgBouncer. In that case, you'll need to use this option to specify your actual database URL so that Que can establish a direct connection.
|
91
102
|
|
92
|
-
### wait-period
|
103
|
+
### `wait-period`
|
93
104
|
|
94
105
|
This option specifies (in milliseconds) how often the locking thread wakes up to check whether the workers have finished jobs, whether it's time to poll, etc. You shouldn't generally need to tweak this, but it may come in handy for some workloads. The default is 50 milliseconds.
|
95
106
|
|
96
|
-
### log-internals
|
107
|
+
### `log-internals`
|
97
108
|
|
98
109
|
This option instructs Que to output a lot of information about its internal state to the logger. It should only be used if it becomes necessary to debug issues.
|
99
110
|
|
@@ -147,7 +158,6 @@ Be sure to read the docs on [managing workers](#managing-workers) for more infor
|
|
147
158
|
|
148
159
|
You'll also want to set up [logging](#logging) and an [error handler](#error-handling) to track errors raised by jobs.
|
149
160
|
|
150
|
-
|
151
161
|
## Error Handling
|
152
162
|
|
153
163
|
If an error is raised and left uncaught by your job, Que will save the error message and backtrace to the database and schedule the job to be retried later.
|
@@ -425,7 +435,6 @@ Que.db_version #=> 3
|
|
425
435
|
|
426
436
|
Note that you can remove Que from your database completely by migrating to version 0.
|
427
437
|
|
428
|
-
|
429
438
|
## Multiple Queues
|
430
439
|
|
431
440
|
Que supports the use of multiple queues in a single job table. Please note that this feature is intended to support the case where multiple codebases are sharing the same job queue - if you want to support jobs of differing priorities, the numeric priority system offers better flexibility and performance.
|
@@ -452,11 +461,7 @@ class ProcessCreditCard < Que::Job
|
|
452
461
|
end
|
453
462
|
```
|
454
463
|
|
455
|
-
In some cases, the ProcessCreditCard class may not be defined in the application that is enqueueing the job. In that case, you can specify the job class as a string
|
456
|
-
|
457
|
-
```ruby
|
458
|
-
Que.enqueue(current_user.id, job_options: { job_class: 'ProcessCreditCard', queue: 'credit_cards' })
|
459
|
-
```
|
464
|
+
In some cases, the `ProcessCreditCard` class may not be defined in the application that is enqueueing the job. In that case, you can [specify the job class as a string](#job_class).
|
460
465
|
|
461
466
|
## Shutting Down Safely
|
462
467
|
|
@@ -466,7 +471,6 @@ To prevent this, Que will block the worker process from exiting until all jobs i
|
|
466
471
|
|
467
472
|
So, be prepared to use SIGKILL on your Ruby processes if they run for too long. For example, Heroku takes a good approach to this - when Heroku's platform is shutting down a process, it sends SIGTERM, waits ten seconds, then sends SIGKILL if the process still hasn't exited. This is a nice compromise - it will give each of your currently running jobs ten seconds to complete, and any jobs that haven't finished by then will be interrupted and retried later.
|
468
473
|
|
469
|
-
|
470
474
|
## Using Plain Postgres Connections
|
471
475
|
|
472
476
|
If you're not using an ORM like ActiveRecord or Sequel, you can use a distinct connection pool to manage your Postgres connections. Please be aware that if you **are** using ActiveRecord or Sequel, there's no reason for you to be using any of these methods - it's less efficient (unnecessary connections will waste memory on your database server) and you lose the reliability benefits of wrapping jobs in the same transactions as the rest of your data.
|
@@ -594,27 +598,29 @@ Additionally, including `Que::ActiveJob::JobExtensions` lets you define a run()
|
|
594
598
|
|
595
599
|
There are a number of instance methods on Que::Job that you can use in your jobs, preferably in transactions. See [Writing Reliable Jobs](#writing-reliable-jobs) for more information on where to use these methods.
|
596
600
|
|
597
|
-
### destroy
|
601
|
+
### `destroy`
|
598
602
|
|
599
603
|
This method deletes the job from the queue table, ensuring that it won't be worked a second time.
|
600
604
|
|
601
|
-
### finish
|
605
|
+
### `finish`
|
602
606
|
|
603
607
|
This method marks the current job as finished, ensuring that it won't be worked a second time. This is like destroy, in that it finalizes a job, but this method leaves the job in the table, in case you want to query it later.
|
604
608
|
|
605
|
-
### expire
|
609
|
+
### `expire`
|
606
610
|
|
607
611
|
This method marks the current job as expired. It will be left in the table and won't be retried, but it will be easy to query for expired jobs. This method is called if the job exceeds its maximum_retry_count.
|
608
612
|
|
609
|
-
### retry_in
|
613
|
+
### `retry_in`
|
610
614
|
|
611
615
|
This method marks the current job to be retried later. You can pass a numeric to this method, in which case that is the number of seconds after which it can be retried (`retry_in(10)`, `retry_in(0.5)`), or, if you're using ActiveSupport, you can pass in a duration object (`retry_in(10.minutes)`). This automatically happens, with an exponentially-increasing interval, when the job encounters an error.
|
612
616
|
|
613
|
-
|
617
|
+
Note that `retry_in` increments the job's `error_count`.
|
618
|
+
|
619
|
+
### `error_count`
|
614
620
|
|
615
621
|
This method returns the total number of times the job has errored, in case you want to modify the job's behavior after it has failed a given number of times.
|
616
622
|
|
617
|
-
### default_resolve_action
|
623
|
+
### `default_resolve_action`
|
618
624
|
|
619
625
|
If you don't perform a resolve action (destroy, finish, expire, retry_in) while the job is worked, Que will call this method for you. By default it simply calls `destroy`, but you can override it in your Job subclasses if you wish - for example, to call `finish`, or to invoke some more complicated logic.
|
620
626
|
|
@@ -727,6 +733,51 @@ end
|
|
727
733
|
|
728
734
|
Now, if the request takes more than five seconds, an error will be raised (probably - check your library's documentation) and Que will just retry the job later.
|
729
735
|
|
736
|
+
## Job Options
|
737
|
+
|
738
|
+
When enqueueing a job, you can specify particular options for it in a `job_options` hash, e.g.:
|
739
|
+
|
740
|
+
```ruby
|
741
|
+
ChargeCreditCard.enqueue(card.id, user_id: current_user.id, job_options: { run_at: 1.day.from_now, priority: 5 })
|
742
|
+
```
|
743
|
+
|
744
|
+
### `queue`
|
745
|
+
|
746
|
+
See [Multiple Queues](#multiple-queues).
|
747
|
+
|
748
|
+
### `priority`
|
749
|
+
|
750
|
+
Provide an integer to customise the priority level of the job.
|
751
|
+
|
752
|
+
We use the Linux priority scale - a lower number is more important.
|
753
|
+
|
754
|
+
### `run_at`
|
755
|
+
|
756
|
+
Provide a `Time` as the `run_at` to make a job run at a later time (well, at some point after it, depending on how busy the workers are).
|
757
|
+
|
758
|
+
It's best not to use `Time.now` here, as the current time in the Ruby process and the database won't be perfectly aligned. When the database considers the `run_at` to be in the past, the job will not be broadcast via the LISTEN/NOTIFY system, and it will need to wait for a poll. This introduces an unnecessary delay of probably a few seconds (depending on your configured [poll interval](#poll-interval)). So if you want the job to run ASAP, just omit the `run_at` option.
|
759
|
+
|
760
|
+
### `job_class`
|
761
|
+
|
762
|
+
Specifying `job_class` allows you to enqueue a job using `Que.enqueue`:
|
763
|
+
|
764
|
+
```ruby
|
765
|
+
Que.enqueue(current_user.id, job_options: { job_class: 'ProcessCreditCard' })
|
766
|
+
```
|
767
|
+
|
768
|
+
Rather than needing to use the job class (nor even have it defined in the enqueueing process):
|
769
|
+
|
770
|
+
```ruby
|
771
|
+
ProcessCreditCard.enqueue(current_user.id)
|
772
|
+
```
|
773
|
+
|
774
|
+
### `tags`
|
775
|
+
|
776
|
+
You can provide an array of strings to give a job some tags. These are not used by Que and are completely custom.
|
777
|
+
|
778
|
+
A job can have up to five tags, each one up to 100 characters long.
|
779
|
+
|
780
|
+
Note that unlike the other job options, tags are stored within the `que_jobs.data` column, rather than a correspondingly-named column.
|
730
781
|
|
731
782
|
## Middleware
|
732
783
|
|
@@ -785,3 +836,46 @@ class ManualVacuumJob < CronJob
|
|
785
836
|
end
|
786
837
|
end
|
787
838
|
```
|
839
|
+
|
840
|
+
## Enqueueing jobs in bulk
|
841
|
+
|
842
|
+
If you need to enqueue a large number of jobs at once, enqueueing each one separately (and running the notify trigger for each) can become a performance bottleneck. To mitigate this, there is a bulk enqueue interface:
|
843
|
+
|
844
|
+
```ruby
|
845
|
+
Que.bulk_enqueue do
|
846
|
+
MyJob.enqueue(user_id: 1)
|
847
|
+
MyJob.enqueue(user_id: 2)
|
848
|
+
# ...
|
849
|
+
end
|
850
|
+
```
|
851
|
+
|
852
|
+
The jobs are only actually enqueued at the end of the block, at which point they are inserted into the database in one big query.
|
853
|
+
|
854
|
+
Limitations:
|
855
|
+
|
856
|
+
- ActiveJob is not supported
|
857
|
+
- All jobs must use the same job class
|
858
|
+
- All jobs must use the same `job_options` (`job_options` must be provided to `.bulk_enqueue` instead of `.enqueue`)
|
859
|
+
- The `que_attrs` of a job instance returned from `.enqueue` is empty (`{}`)
|
860
|
+
- The notify trigger is not run by default, so jobs will only be picked up by a worker upon its next poll
|
861
|
+
|
862
|
+
If you still want the notify trigger to run for each job, use `Que.bulk_enqueue(notify: true) { ... }`.
|
863
|
+
|
864
|
+
## Expired jobs
|
865
|
+
|
866
|
+
Expired jobs hang around in the `que_jobs` table. If necessary, you can get an expired job to run again by clearing the `error_count` and `expired_at` columns, e.g.:
|
867
|
+
|
868
|
+
```sql
|
869
|
+
UPDATE que_jobs SET error_count = 0, expired_at = NULL WHERE id = 172340879;
|
870
|
+
```
|
871
|
+
|
872
|
+
## Finished jobs
|
873
|
+
|
874
|
+
If you prefer to leave finished jobs in the database for a while, to performantly remove them periodically, you can use something like:
|
875
|
+
|
876
|
+
```sql
|
877
|
+
BEGIN;
|
878
|
+
SET LOCAL que.skip_notify TO true;
|
879
|
+
DELETE FROM que_jobs WHERE finished_at < (select now() - interval '7 days');
|
880
|
+
COMMIT;
|
881
|
+
```
|
File without changes
|
data/lib/que/connection.rb
CHANGED
data/lib/que/job.rb
CHANGED
@@ -27,6 +27,26 @@ module Que
|
|
27
27
|
RETURNING *
|
28
28
|
}
|
29
29
|
|
30
|
+
SQL[:bulk_insert_jobs] =
|
31
|
+
%{
|
32
|
+
WITH args_and_kwargs as (
|
33
|
+
SELECT * from json_to_recordset(coalesce($5, '[{args:{},kwargs:{}}]')::json) as x(args jsonb, kwargs jsonb)
|
34
|
+
)
|
35
|
+
INSERT INTO public.que_jobs
|
36
|
+
(queue, priority, run_at, job_class, args, kwargs, data, job_schema_version)
|
37
|
+
SELECT
|
38
|
+
coalesce($1, 'default')::text,
|
39
|
+
coalesce($2, 100)::smallint,
|
40
|
+
coalesce($3, now())::timestamptz,
|
41
|
+
$4::text,
|
42
|
+
args_and_kwargs.args,
|
43
|
+
args_and_kwargs.kwargs,
|
44
|
+
coalesce($6, '{}')::jsonb,
|
45
|
+
#{Que.job_schema_version}
|
46
|
+
FROM args_and_kwargs
|
47
|
+
RETURNING *
|
48
|
+
}
|
49
|
+
|
30
50
|
attr_reader :que_attrs
|
31
51
|
attr_accessor :que_error, :que_resolved
|
32
52
|
|
@@ -78,30 +98,120 @@ module Que
|
|
78
98
|
queue: job_options[:queue] || resolve_que_setting(:queue) || Que.default_queue,
|
79
99
|
priority: job_options[:priority] || resolve_que_setting(:priority),
|
80
100
|
run_at: job_options[:run_at] || resolve_que_setting(:run_at),
|
81
|
-
args:
|
82
|
-
kwargs:
|
83
|
-
data: job_options[:tags] ?
|
101
|
+
args: args,
|
102
|
+
kwargs: kwargs,
|
103
|
+
data: job_options[:tags] ? { tags: job_options[:tags] } : {},
|
84
104
|
job_class: \
|
85
105
|
job_options[:job_class] || name ||
|
86
106
|
raise(Error, "Can't enqueue an anonymous subclass of Que::Job"),
|
87
107
|
}
|
88
108
|
|
89
|
-
if
|
90
|
-
|
91
|
-
|
92
|
-
|
109
|
+
if Thread.current[:que_jobs_to_bulk_insert]
|
110
|
+
if self.name == 'ActiveJob::QueueAdapters::QueAdapter::JobWrapper'
|
111
|
+
raise Que::Error, "Que.bulk_enqueue does not support ActiveJob."
|
112
|
+
end
|
113
|
+
|
114
|
+
raise Que::Error, "When using .bulk_enqueue, job_options must be passed to that method rather than .enqueue" unless job_options == {}
|
115
|
+
|
116
|
+
Thread.current[:que_jobs_to_bulk_insert][:jobs_attrs] << attrs
|
117
|
+
new({})
|
118
|
+
elsif attrs[:run_at].nil? && resolve_que_setting(:run_synchronously)
|
119
|
+
attrs.merge!(
|
120
|
+
args: Que.deserialize_json(Que.serialize_json(attrs[:args])),
|
121
|
+
kwargs: Que.deserialize_json(Que.serialize_json(attrs[:kwargs])),
|
122
|
+
data: Que.deserialize_json(Que.serialize_json(attrs[:data])),
|
123
|
+
)
|
93
124
|
_run_attrs(attrs)
|
94
125
|
else
|
95
|
-
|
96
|
-
Que.
|
97
|
-
|
98
|
-
|
99
|
-
|
126
|
+
attrs.merge!(
|
127
|
+
args: Que.serialize_json(attrs[:args]),
|
128
|
+
kwargs: Que.serialize_json(attrs[:kwargs]),
|
129
|
+
data: Que.serialize_json(attrs[:data]),
|
130
|
+
)
|
131
|
+
values = Que.execute(
|
132
|
+
:insert_job,
|
133
|
+
attrs.values_at(:queue, :priority, :run_at, :job_class, :args, :kwargs, :data),
|
134
|
+
).first
|
100
135
|
new(values)
|
101
136
|
end
|
102
137
|
end
|
103
138
|
ruby2_keywords(:enqueue) if respond_to?(:ruby2_keywords, true)
|
104
139
|
|
140
|
+
def bulk_enqueue(job_options: {}, notify: false)
|
141
|
+
raise Que::Error, "Can't nest .bulk_enqueue" unless Thread.current[:que_jobs_to_bulk_insert].nil?
|
142
|
+
Thread.current[:que_jobs_to_bulk_insert] = { jobs_attrs: [], job_options: job_options }
|
143
|
+
yield
|
144
|
+
jobs_attrs = Thread.current[:que_jobs_to_bulk_insert][:jobs_attrs]
|
145
|
+
job_options = Thread.current[:que_jobs_to_bulk_insert][:job_options]
|
146
|
+
return [] if jobs_attrs.empty?
|
147
|
+
raise Que::Error, "When using .bulk_enqueue, all jobs enqueued must be of the same job class" unless jobs_attrs.map { |attrs| attrs[:job_class] }.uniq.one?
|
148
|
+
args_and_kwargs_array = jobs_attrs.map { |attrs| attrs.slice(:args, :kwargs) }
|
149
|
+
klass = job_options[:job_class] ? Que::Job : Que.constantize(jobs_attrs.first[:job_class])
|
150
|
+
klass._bulk_enqueue_insert(args_and_kwargs_array, job_options: job_options, notify: notify)
|
151
|
+
ensure
|
152
|
+
Thread.current[:que_jobs_to_bulk_insert] = nil
|
153
|
+
end
|
154
|
+
|
155
|
+
def _bulk_enqueue_insert(args_and_kwargs_array, job_options: {}, notify:)
|
156
|
+
raise 'Unexpected bulk args format' if !args_and_kwargs_array.is_a?(Array) || !args_and_kwargs_array.all? { |a| a.is_a?(Hash) }
|
157
|
+
|
158
|
+
if job_options[:tags]
|
159
|
+
if job_options[:tags].length > MAXIMUM_TAGS_COUNT
|
160
|
+
raise Que::Error, "Can't enqueue a job with more than #{MAXIMUM_TAGS_COUNT} tags! (passed #{job_options[:tags].length})"
|
161
|
+
end
|
162
|
+
|
163
|
+
job_options[:tags].each do |tag|
|
164
|
+
if tag.length > MAXIMUM_TAG_LENGTH
|
165
|
+
raise Que::Error, "Can't enqueue a job with a tag longer than 100 characters! (\"#{tag}\")"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
args_and_kwargs_array = args_and_kwargs_array.map do |args_and_kwargs|
|
171
|
+
args_and_kwargs.merge(
|
172
|
+
args: args_and_kwargs.fetch(:args, []),
|
173
|
+
kwargs: args_and_kwargs.fetch(:kwargs, {}),
|
174
|
+
)
|
175
|
+
end
|
176
|
+
|
177
|
+
attrs = {
|
178
|
+
queue: job_options[:queue] || resolve_que_setting(:queue) || Que.default_queue,
|
179
|
+
priority: job_options[:priority] || resolve_que_setting(:priority),
|
180
|
+
run_at: job_options[:run_at] || resolve_que_setting(:run_at),
|
181
|
+
args_and_kwargs_array: args_and_kwargs_array,
|
182
|
+
data: job_options[:tags] ? { tags: job_options[:tags] } : {},
|
183
|
+
job_class: \
|
184
|
+
job_options[:job_class] || name ||
|
185
|
+
raise(Error, "Can't enqueue an anonymous subclass of Que::Job"),
|
186
|
+
}
|
187
|
+
|
188
|
+
if attrs[:run_at].nil? && resolve_que_setting(:run_synchronously)
|
189
|
+
args_and_kwargs_array = Que.deserialize_json(Que.serialize_json(attrs.delete(:args_and_kwargs_array)))
|
190
|
+
args_and_kwargs_array.map do |args_and_kwargs|
|
191
|
+
_run_attrs(
|
192
|
+
attrs.merge(
|
193
|
+
args: args_and_kwargs.fetch(:args),
|
194
|
+
kwargs: args_and_kwargs.fetch(:kwargs),
|
195
|
+
),
|
196
|
+
)
|
197
|
+
end
|
198
|
+
else
|
199
|
+
attrs.merge!(
|
200
|
+
args_and_kwargs_array: Que.serialize_json(attrs[:args_and_kwargs_array]),
|
201
|
+
data: Que.serialize_json(attrs[:data]),
|
202
|
+
)
|
203
|
+
values_array =
|
204
|
+
Que.transaction do
|
205
|
+
Que.execute('SET LOCAL que.skip_notify TO true') unless notify
|
206
|
+
Que.execute(
|
207
|
+
:bulk_insert_jobs,
|
208
|
+
attrs.values_at(:queue, :priority, :run_at, :job_class, :args_and_kwargs_array, :data),
|
209
|
+
)
|
210
|
+
end
|
211
|
+
values_array.map(&method(:new))
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
105
215
|
def run(*args)
|
106
216
|
# Make sure things behave the same as they would have with a round-trip
|
107
217
|
# to the DB.
|
@@ -0,0 +1,13 @@
|
|
1
|
+
DROP TRIGGER que_job_notify ON que_jobs;
|
2
|
+
CREATE TRIGGER que_job_notify
|
3
|
+
AFTER INSERT ON que_jobs
|
4
|
+
FOR EACH ROW
|
5
|
+
WHEN (NOT coalesce(current_setting('que.skip_notify', true), '') = 'true')
|
6
|
+
EXECUTE PROCEDURE public.que_job_notify();
|
7
|
+
|
8
|
+
DROP TRIGGER que_state_notify ON que_jobs;
|
9
|
+
CREATE TRIGGER que_state_notify
|
10
|
+
AFTER INSERT OR UPDATE OR DELETE ON que_jobs
|
11
|
+
FOR EACH ROW
|
12
|
+
WHEN (NOT coalesce(current_setting('que.skip_notify', true), '') = 'true')
|
13
|
+
EXECUTE PROCEDURE public.que_state_notify();
|
data/lib/que/migrations.rb
CHANGED
@@ -4,7 +4,7 @@ module Que
|
|
4
4
|
module Migrations
|
5
5
|
# In order to ship a schema change, add the relevant up and down sql files
|
6
6
|
# to the migrations directory, and bump the version here.
|
7
|
-
CURRENT_VERSION =
|
7
|
+
CURRENT_VERSION = 7
|
8
8
|
|
9
9
|
class << self
|
10
10
|
def migrate!(version:)
|
@@ -48,14 +48,27 @@ module Que
|
|
48
48
|
# No table in the database at all.
|
49
49
|
0
|
50
50
|
elsif (d = result.first[:description]).nil?
|
51
|
-
#
|
52
|
-
|
53
|
-
1
|
51
|
+
# The table exists but the version comment is missing
|
52
|
+
_raise_db_version_comment_missing_error
|
54
53
|
else
|
55
54
|
d.to_i
|
56
55
|
end
|
57
56
|
end
|
58
57
|
|
58
|
+
# The que_jobs table could be missing the schema version comment either due to:
|
59
|
+
# - Being created before the migration system existed; or
|
60
|
+
# - A bug in Rails schema dump in some versions of Rails
|
61
|
+
# The former is the case on Que versions prior to v0.5.0 (2014-01-14). Upgrading directly from there is unsupported, so we just raise in all cases of the comment being missing
|
62
|
+
def _raise_db_version_comment_missing_error
|
63
|
+
raise Error, <<~ERROR
|
64
|
+
Cannot determine Que DB schema version.
|
65
|
+
|
66
|
+
The que_jobs table is missing its comment recording the Que DB schema version. This is likely due to a bug in Rails schema dump in Rails 7 versions prior to 7.0.3, omitting comments - see https://github.com/que-rb/que/issues/363. Please determine the appropriate schema version from your migrations and record it manually by running the following SQL (replacing version as appropriate):
|
67
|
+
|
68
|
+
COMMENT ON TABLE que_jobs IS 'version';
|
69
|
+
ERROR
|
70
|
+
end
|
71
|
+
|
59
72
|
def set_db_version(version)
|
60
73
|
i = version.to_i
|
61
74
|
Que.execute "COMMENT ON TABLE que_jobs IS '#{i}'" unless i.zero?
|
data/lib/que/version.rb
CHANGED
data/lib/que.rb
CHANGED
@@ -69,7 +69,7 @@ module Que
|
|
69
69
|
|
70
70
|
# Copy some commonly-used methods here, for convenience.
|
71
71
|
def_delegators :pool, :execute, :checkout, :in_transaction?
|
72
|
-
def_delegators Job, :enqueue, :run_synchronously, :run_synchronously=
|
72
|
+
def_delegators Job, :enqueue, :bulk_enqueue, :run_synchronously, :run_synchronously=
|
73
73
|
def_delegators Migrations, :db_version, :migrate!
|
74
74
|
|
75
75
|
# Global configuration logic.
|
data/scripts/test
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: que
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Hanks
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-08-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -45,7 +45,6 @@ files:
|
|
45
45
|
- auto/psql
|
46
46
|
- auto/test
|
47
47
|
- auto/test-postgres-14
|
48
|
-
- bin/command_line_interface.rb
|
49
48
|
- bin/que
|
50
49
|
- docker-compose.yml
|
51
50
|
- docs/README.md
|
@@ -53,6 +52,7 @@ files:
|
|
53
52
|
- lib/que/active_job/extensions.rb
|
54
53
|
- lib/que/active_record/connection.rb
|
55
54
|
- lib/que/active_record/model.rb
|
55
|
+
- lib/que/command_line_interface.rb
|
56
56
|
- lib/que/connection.rb
|
57
57
|
- lib/que/connection_pool.rb
|
58
58
|
- lib/que/job.rb
|
@@ -74,6 +74,8 @@ files:
|
|
74
74
|
- lib/que/migrations/5/up.sql
|
75
75
|
- lib/que/migrations/6/down.sql
|
76
76
|
- lib/que/migrations/6/up.sql
|
77
|
+
- lib/que/migrations/7/down.sql
|
78
|
+
- lib/que/migrations/7/up.sql
|
77
79
|
- lib/que/poller.rb
|
78
80
|
- lib/que/rails/railtie.rb
|
79
81
|
- lib/que/result_queue.rb
|
@@ -109,9 +111,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
109
111
|
version: 2.7.0
|
110
112
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
113
|
requirements:
|
112
|
-
- - "
|
114
|
+
- - ">="
|
113
115
|
- !ruby/object:Gem::Version
|
114
|
-
version:
|
116
|
+
version: '0'
|
115
117
|
requirements: []
|
116
118
|
rubygems_version: 3.3.7
|
117
119
|
signing_key:
|