solid_queue 0.9.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +67 -11
- data/UPGRADING.md +5 -8
- data/app/models/solid_queue/process.rb +5 -1
- data/lib/active_job/queue_adapters/solid_queue_adapter.rb +1 -1
- data/lib/generators/solid_queue/install/templates/config/recurring.yml +10 -9
- data/lib/generators/solid_queue/install/templates/db/queue_schema.rb +1 -1
- data/lib/solid_queue/cli.rb +4 -5
- data/lib/solid_queue/configuration.rb +1 -1
- data/lib/solid_queue/processes/registrable.rb +1 -1
- data/lib/solid_queue/version.rb +1 -1
- data/lib/solid_queue.rb +0 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 553dc884dd38c24c16196da9cceb6e7b40d02bd3f5c005bc980065232b4b1682
|
4
|
+
data.tar.gz: dba6309954b326305526417c05c1a5878a0195d759e8cd6fb6c4453ee61cd6c0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dc6ad2d8abd513cd9f357a1190c2173c978b46dd800d1b3f4040364cd70c44b5271da7108901c67a2811586d746607eac4f999c8886a56a1942a228f0f3d1d11
|
7
|
+
data.tar.gz: 999a90c18c09350609670b29bdd3f5e2596815f0a5edb194b225634b9169eca9efaf3e8efda23cfe346e6947e9477b983bbcae1a8a1adf3c6fc2ae9affd4e2ff
|
data/README.md
CHANGED
@@ -15,7 +15,7 @@ Solid Queue is configured by default in new Rails 8 applications. But if you're
|
|
15
15
|
|
16
16
|
This will configure Solid Queue as the production Active Job backend, create the configuration files `config/queue.yml` and `config/recurring.yml`, and create the `db/queue_schema.rb`. It'll also create a `bin/jobs` executable wrapper that you can use to start Solid Queue.
|
17
17
|
|
18
|
-
Once you've done that, you will then have to add the configuration for the queue database in `config/database.yml`. If you're using
|
18
|
+
Once you've done that, you will then have to add the configuration for the queue database in `config/database.yml`. If you're using SQLite, it'll look like this:
|
19
19
|
|
20
20
|
```yaml
|
21
21
|
production:
|
@@ -51,9 +51,12 @@ Now you're ready to start processing jobs by running `bin/jobs` on the server th
|
|
51
51
|
|
52
52
|
For small projects, you can run Solid Queue on the same machine as your webserver. When you're ready to scale, Solid Queue supports horizontal scaling out-of-the-box. You can run Solid Queue on a separate server from your webserver, or even run `bin/jobs` on multiple machines at the same time. Depending on the configuration, you can designate some machines to run only dispatchers or only workers. See the [configuration](#configuration) section for more details on this.
|
53
53
|
|
54
|
+
**Note**: future changes to the schema will come in the form of regular migrations.
|
55
|
+
|
56
|
+
|
54
57
|
### Single database configuration
|
55
58
|
|
56
|
-
|
59
|
+
Running Solid Queue in a separate database is recommended, but it's also possible to use one single database for both the app and the queue. Just follow these steps:
|
57
60
|
|
58
61
|
1. Copy the contents of `db/queue_schema.rb` into a normal migration and delete `db/queue_schema.rb`
|
59
62
|
2. Remove `config.solid_queue.connects_to` from `production.rb`
|
@@ -220,6 +223,7 @@ There are several settings that control how Solid Queue works that you can set a
|
|
220
223
|
```ruby
|
221
224
|
-> (exception) { Rails.error.report(exception, handled: false) }
|
222
225
|
```
|
226
|
+
|
223
227
|
**This is not used for errors raised within a job execution**. Errors happening in jobs are handled by Active Job's `retry_on` or `discard_on`, and ultimately will result in [failed jobs](#failed-jobs-and-retries). This is for errors happening within Solid Queue itself.
|
224
228
|
|
225
229
|
- `use_skip_locked`: whether to use `FOR UPDATE SKIP LOCKED` when performing locking reads. This will be automatically detected in the future, and for now, you'd only need to set this to `false` if your database doesn't support it. For MySQL, that'd be versions < 8, and for PostgreSQL, versions < 9.5. If you use SQLite, this has no effect, as writes are sequential.
|
@@ -231,7 +235,6 @@ There are several settings that control how Solid Queue works that you can set a
|
|
231
235
|
- `preserve_finished_jobs`: whether to keep finished jobs in the `solid_queue_jobs` table—defaults to `true`.
|
232
236
|
- `clear_finished_jobs_after`: period to keep finished jobs around, in case `preserve_finished_jobs` is true—defaults to 1 day. **Note:** Right now, there's no automatic cleanup of finished jobs. You'd need to do this by periodically invoking `SolidQueue::Job.clear_finished_in_batches`, but this will happen automatically in the near future.
|
233
237
|
- `default_concurrency_control_period`: the value to be used as the default for the `duration` parameter in [concurrency controls](#concurrency-controls). It defaults to 3 minutes.
|
234
|
-
- `enqueue_after_transaction_commit`: whether the job queuing is deferred to after the current Active Record transaction is committed. The default is `false`. [Read more](https://github.com/rails/rails/pull/51426).
|
235
238
|
|
236
239
|
## Errors when enqueuing
|
237
240
|
|
@@ -256,7 +259,7 @@ class MyJob < ApplicationJob
|
|
256
259
|
|
257
260
|
When a job includes these controls, we'll ensure that, at most, the number of jobs (indicated as `to`) that yield the same `key` will be performed concurrently, and this guarantee will last for `duration` for each job enqueued. Note that there's no guarantee about _the order of execution_, only about jobs being performed at the same time (overlapping).
|
258
261
|
|
259
|
-
The concurrency limits use the concept of semaphores when enqueuing, and work as follows: when a job is enqueued, we check if it specifies concurrency controls. If it does, we check the semaphore for the computed concurrency key. If the semaphore is open, we claim it and we set the job as _ready_. Ready means it can be picked up by workers for execution. When the job finishes executing (be it successfully or
|
262
|
+
The concurrency limits use the concept of semaphores when enqueuing, and work as follows: when a job is enqueued, we check if it specifies concurrency controls. If it does, we check the semaphore for the computed concurrency key. If the semaphore is open, we claim it and we set the job as _ready_. Ready means it can be picked up by workers for execution. When the job finishes executing (be it successfully or unsuccessfully, resulting in a failed execution), we signal the semaphore and try to unblock the next job with the same key, if any. Unblocking the next job doesn't mean running that job right away, but moving it from _blocked_ to _ready_. Since something can happen that prevents the first job from releasing the semaphore and unblocking the next job (for example, someone pulling a plug in the machine where the worker is running), we have the `duration` as a failsafe. Jobs that have been blocked for more than duration are candidates to be released, but only as many of them as the concurrency rules allow, as each one would need to go through the semaphore dance check. This means that the `duration` is not really about the job that's enqueued or being run, it's about the jobs that are blocked waiting.
|
260
263
|
|
261
264
|
|
262
265
|
For example:
|
@@ -308,6 +311,33 @@ failed_execution.discard # This will delete the job from the system
|
|
308
311
|
|
309
312
|
However, we recommend taking a look at [mission_control-jobs](https://github.com/rails/mission_control-jobs), a dashboard where, among other things, you can examine and retry/discard failed jobs.
|
310
313
|
|
314
|
+
### Error reporting on jobs
|
315
|
+
|
316
|
+
Some error tracking services that integrate with Rails, such as Sentry or Rollbar, hook into [Active Job](https://guides.rubyonrails.org/active_job_basics.html#exceptions) and automatically report not handled errors that happen during job execution. However, if your error tracking system doesn't, or if you need some custom reporting, you can hook into Active Job yourself. A possible way of doing this would be:
|
317
|
+
|
318
|
+
```ruby
|
319
|
+
# application_job.rb
|
320
|
+
class ApplicationJob < ActiveJob::Base
|
321
|
+
rescue_from(Exception) do |exception|
|
322
|
+
Rails.error.report(exception)
|
323
|
+
raise exception
|
324
|
+
end
|
325
|
+
end
|
326
|
+
```
|
327
|
+
|
328
|
+
Note that, you will have to duplicate the above logic on `ActionMailer::MailDeliveryJob` too. That is because `ActionMailer` doesn't inherit from `ApplicationJob` but instead uses `ActionMailer::MailDeliveryJob` which inherits from `ActiveJob::Base`.
|
329
|
+
|
330
|
+
```ruby
|
331
|
+
# application_mailer.rb
|
332
|
+
|
333
|
+
class ApplicationMailer < ActionMailer::Base
|
334
|
+
ActionMailer::MailDeliveryJob.rescue_from(Exception) do |exception|
|
335
|
+
Rails.error.report(exception)
|
336
|
+
raise exception
|
337
|
+
end
|
338
|
+
end
|
339
|
+
```
|
340
|
+
|
311
341
|
## Puma plugin
|
312
342
|
|
313
343
|
We provide a Puma plugin if you want to run the Solid Queue's supervisor together with Puma and have Puma monitor and manage it. You just need to add
|
@@ -316,6 +346,29 @@ plugin :solid_queue
|
|
316
346
|
```
|
317
347
|
to your `puma.rb` configuration.
|
318
348
|
|
349
|
+
|
350
|
+
## Jobs and transactional integrity
|
351
|
+
:warning: Having your jobs in the same ACID-compliant database as your application data enables a powerful yet sharp tool: taking advantage of transactional integrity to ensure some action in your app is not committed unless your job is also committed and viceversa, and ensuring that your job won't be enqueued until the transaction within which you're enqueing it is committed. This can be very powerful and useful, but it can also backfire if you base some of your logic on this behaviour, and in the future, you move to another active job backend, or if you simply move Solid Queue to its own database, and suddenly the behaviour changes under you.
|
352
|
+
|
353
|
+
Because this can be quite tricky and many people shouldn't need to worry about it, by default Solid Queue is configured in a different database as the main app, **job enqueuing is deferred until any ongoing transaction is committed** thanks to Active Job's built-in capability to do this. This means that even if you run Solid Queue in the same DB as your app, you won't be taking advantage of this transactional integrity.
|
354
|
+
|
355
|
+
If you prefer to change this, you can set [`config.active_job.enqueue_after_transaction_commit`](https://edgeguides.rubyonrails.org/configuring.html#config-active-job-enqueue-after-transaction-commit) to `never`. You can also set this on a per-job basis.
|
356
|
+
|
357
|
+
If you set that to `never` but still want to make sure you're not inadvertently on transactional integrity, you can make sure that:
|
358
|
+
- Your jobs relying on specific data are always enqueued on [`after_commit` callbacks](https://guides.rubyonrails.org/active_record_callbacks.html#after-commit-and-after-rollback) or otherwise from a place where you're certain that whatever data the job will use has been committed to the database before the job is enqueued.
|
359
|
+
- Or, you configure a different database for Solid Queue, even if it's the same as your app, ensuring that a different connection on the thread handling requests or running jobs for your app will be used to enqueue jobs. For example:
|
360
|
+
|
361
|
+
```ruby
|
362
|
+
class ApplicationRecord < ActiveRecord::Base
|
363
|
+
self.abstract_class = true
|
364
|
+
|
365
|
+
connects_to database: { writing: :primary, reading: :replica }
|
366
|
+
```
|
367
|
+
|
368
|
+
```ruby
|
369
|
+
config.solid_queue.connects_to = { database: { writing: :primary, reading: :replica } }
|
370
|
+
```
|
371
|
+
|
319
372
|
## Recurring tasks
|
320
373
|
|
321
374
|
Solid Queue supports defining recurring tasks that run at specific times in the future, on a regular basis like cron jobs. These are managed by the scheduler process and are defined in their own configuration file. By default, the file is located in `config/recurring.yml`, but you can set a different path using the environment variable `SOLID_QUEUE_RECURRING_SCHEDULE` or by using the `--recurring_schedule_file` option with `bin/jobs`, like this:
|
@@ -327,13 +380,14 @@ bin/jobs --recurring_schedule_file=config/schedule.yml
|
|
327
380
|
The configuration itself looks like this:
|
328
381
|
|
329
382
|
```yml
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
383
|
+
production:
|
384
|
+
a_periodic_job:
|
385
|
+
class: MyJob
|
386
|
+
args: [ 42, { status: "custom_status" } ]
|
387
|
+
schedule: every second
|
388
|
+
a_cleanup_task:
|
389
|
+
command: "DeletedStuff.clear_all"
|
390
|
+
schedule: every day at 9am
|
337
391
|
```
|
338
392
|
|
339
393
|
Tasks are specified as a hash/dictionary, where the key will be the task's key internally. Each task needs to either have a `class`, which will be the job class to enqueue, or a `command`, which will be eval'ed in the context of a job (`SolidQueue::RecurringJob`) that will be enqueued according to its schedule, in the `solid_queue_recurring` queue.
|
@@ -355,6 +409,8 @@ Tasks are enqueued at their corresponding times by the scheduler, and each task
|
|
355
409
|
|
356
410
|
It's possible to run multiple schedulers with the same `recurring_tasks` configuration, for example, if you have multiple servers for redundancy, and you run the `scheduler` in more than one of them. To avoid enqueuing duplicate tasks at the same time, an entry in a new `solid_queue_recurring_executions` table is created in the same transaction as the job is enqueued. This table has a unique index on `task_key` and `run_at`, ensuring only one entry per task per time will be created. This only works if you have `preserve_finished_jobs` set to `true` (the default), and the guarantee applies as long as you keep the jobs around.
|
357
411
|
|
412
|
+
**Note**: a single recurring schedule is supported, so you can have multiple schedulers using the same schedule, but not multiple schedulers using different configurations.
|
413
|
+
|
358
414
|
Finally, it's possible to configure jobs that aren't handled by Solid Queue. That is, you can have a job like this in your app:
|
359
415
|
```ruby
|
360
416
|
class MyResqueJob < ApplicationJob
|
data/UPGRADING.md
CHANGED
@@ -1,26 +1,23 @@
|
|
1
|
+
# Upgrading to version 1.x
|
2
|
+
The value returned for `enqueue_after_transaction_commit?` has changed to `true`, and it's no longer configurable. If you want to change this, you need to use Active Job's configuration options.
|
3
|
+
|
1
4
|
# Upgrading to version 0.9.x
|
2
5
|
This version has two breaking changes regarding configuration:
|
3
6
|
- The default configuration file has changed from `config/solid_queue.yml` to `config/queue.yml`.
|
4
7
|
- Recurring tasks are now defined in `config/recurring.yml` (by default). Before, they would be defined as part of the _dispatcher_ configuration. Now they've been upgraded to their own configuration file, and a dedicated process (the _scheduler_) to manage them. Check the _Recurring tasks_ section in the `README` to learn how to configure them in detail. They still follow the same format as before when they lived under `dispatchers > recurring_tasks`.
|
5
8
|
|
6
9
|
# Upgrading to version 0.8.x
|
7
|
-
*IMPORTANT*: This version collapsed all migrations into a single `db/queue_schema.rb`, that will use a separate `queue` database. If you're upgrading from a version < 0.6.0, you need to upgrade to 0.6.0 first, ensure all migrations are up-to-date, and then upgrade further.
|
10
|
+
*IMPORTANT*: This version collapsed all migrations into a single `db/queue_schema.rb`, that will use a separate `queue` database on install. If you're upgrading from a version < 0.6.0, you need to upgrade to 0.6.0 first, ensure all migrations are up-to-date, and then upgrade further. You don't have to switch to a separate `queue` database or use the new `db/queue_schema.rb` file, these are for people starting on a version >= 0.8.x. You can continue using your existing database (be it separate or the same as your app) as long as you run all migrations defined up to version 0.6.0.
|
8
11
|
|
9
12
|
# Upgrading to version 0.7.x
|
10
13
|
|
11
14
|
This version removed the new async mode introduced in version 0.4.0 and introduced a new binstub that can be used to start Solid Queue's supervisor.
|
12
15
|
|
13
|
-
To install
|
16
|
+
To install the binstub `bin/jobs`, you can just run:
|
14
17
|
```
|
15
18
|
bin/rails generate solid_queue:install
|
16
19
|
```
|
17
20
|
|
18
|
-
Or, if you're using a different database for Solid Queue:
|
19
|
-
|
20
|
-
```bash
|
21
|
-
$ bin/rails generate solid_queue:install --database <the_name_of_your_solid_queue_db>
|
22
|
-
```
|
23
|
-
|
24
21
|
|
25
22
|
# Upgrading to version 0.6.x
|
26
23
|
|
@@ -20,7 +20,11 @@ class SolidQueue::Process < SolidQueue::Record
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def heartbeat
|
23
|
-
|
23
|
+
# Clear any previous changes before locking, for example, in case a previous heartbeat
|
24
|
+
# failed because of a DB issue (with SQLite depending on configuration, a BusyException
|
25
|
+
# is not rare) and we still have the unpersisted value
|
26
|
+
restore_attributes
|
27
|
+
with_lock { touch(:last_heartbeat_at) }
|
24
28
|
end
|
25
29
|
|
26
30
|
def deregister(pruned: false)
|
@@ -1,9 +1,10 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
1
|
+
# production:
|
2
|
+
# periodic_cleanup:
|
3
|
+
# class: CleanSoftDeletedRecordsJob
|
4
|
+
# queue: background
|
5
|
+
# args: [ 1000, { batch_size: 500 } ]
|
6
|
+
# schedule: every hour
|
7
|
+
# periodic_command:
|
8
|
+
# command: "SoftDeletedRecord.due.delete_all"
|
9
|
+
# priority: 2
|
10
|
+
# schedule: at 5am every day
|
data/lib/solid_queue/cli.rb
CHANGED
@@ -5,16 +5,15 @@ require "thor"
|
|
5
5
|
module SolidQueue
|
6
6
|
class Cli < Thor
|
7
7
|
class_option :config_file, type: :string, aliases: "-c",
|
8
|
-
default: Configuration::DEFAULT_CONFIG_FILE_PATH,
|
9
|
-
desc: "Path to config file",
|
8
|
+
desc: "Path to config file (default: #{Configuration::DEFAULT_CONFIG_FILE_PATH}).",
|
10
9
|
banner: "SOLID_QUEUE_CONFIG"
|
11
10
|
|
12
11
|
class_option :recurring_schedule_file, type: :string,
|
13
|
-
default: Configuration::DEFAULT_RECURRING_SCHEDULE_FILE_PATH,
|
14
|
-
desc: "Path to recurring schedule definition",
|
12
|
+
desc: "Path to recurring schedule definition (default: #{Configuration::DEFAULT_RECURRING_SCHEDULE_FILE_PATH}).",
|
15
13
|
banner: "SOLID_QUEUE_RECURRING_SCHEDULE"
|
16
14
|
|
17
|
-
class_option :skip_recurring, type: :boolean, default: false
|
15
|
+
class_option :skip_recurring, type: :boolean, default: false,
|
16
|
+
desc: "Whether to skip recurring tasks scheduling"
|
18
17
|
|
19
18
|
def self.exit_on_failure?
|
20
19
|
true
|
data/lib/solid_queue/version.rb
CHANGED
data/lib/solid_queue.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: solid_queue
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rosa Gutierrez
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-09-
|
11
|
+
date: 2024-09-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|