solid_queue 0.9.0 → 1.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b0a582a0c2b6b0025baed737e35b41dbe2dc687b0fbd7a221909b6350321a717
4
- data.tar.gz: 85053c1ab6f8b7d5b66855631a003e789306499eba71d100ae91a32e39757b97
3
+ metadata.gz: '0185a60652945d7afadb3c4c8e646f9445eba35086ab88ddf1b93ff3b271e973'
4
+ data.tar.gz: 363d19a3e07ced689dd4a3ac3ec36c7cdfeecfc61a5588ccb6f67c28b782a5bf
5
5
  SHA512:
6
- metadata.gz: 630b5c980be4b1e152544e46eb38922706b620765ffb1b9c18ff81a9c00a221be2d0ed2f723ecd7d39391a01119785b3be42db408f7e48a6b0afd67ab62088eb
7
- data.tar.gz: 4451b96428dfbaa8213d9fff225dfeaa5826aa10d6ab68151effbba01df866c8c19110e4c6f147a25b70fa34827c6a6c03381b1fb7681934b036791ba4f16faa
6
+ metadata.gz: 1fc522f72cf05273cd6d7fb98cbce44001c28532131952fb1bc139c2c80d808afb2bd7dc8acd337c3fe4834d30e1dc75a908aff7d3984a1e13dfbada8b25261f
7
+ data.tar.gz: 96c3d62e399468a193dc2f915797308b645eaf8c60b109a4344a757b4d91c4accf80192c4ebcd26022d08f876a8b0e95788250dfe27eb58e0b723082c6147b2d
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 sqlite, it'll look like this:
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
- It's also possibile to use one single database for both production data:
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,8 +223,10 @@ 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
 
229
+ - `connects_to`: a custom database configuration that will be used in the abstract `SolidQueue::Record` Active Record model. This is required to use a different database than the main app. For example:
225
230
  - `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.
226
231
  - `process_heartbeat_interval`: the heartbeat interval that all processes will follow—defaults to 60 seconds.
227
232
  - `process_alive_threshold`: how long to wait until a process is considered dead after its last heartbeat—defaults to 5 minutes.
@@ -231,7 +236,6 @@ There are several settings that control how Solid Queue works that you can set a
231
236
  - `preserve_finished_jobs`: whether to keep finished jobs in the `solid_queue_jobs` table—defaults to `true`.
232
237
  - `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
238
  - `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
239
 
236
240
  ## Errors when enqueuing
237
241
 
@@ -256,7 +260,7 @@ class MyJob < ApplicationJob
256
260
 
257
261
  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
262
 
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 unsuccesfully, 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.
263
+ 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
264
 
261
265
 
262
266
  For example:
@@ -308,6 +312,33 @@ failed_execution.discard # This will delete the job from the system
308
312
 
309
313
  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
314
 
315
+ ### Error reporting on jobs
316
+
317
+ 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:
318
+
319
+ ```ruby
320
+ # application_job.rb
321
+ class ApplicationJob < ActiveJob::Base
322
+ rescue_from(Exception) do |exception|
323
+ Rails.error.report(exception)
324
+ raise exception
325
+ end
326
+ end
327
+ ```
328
+
329
+ 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`.
330
+
331
+ ```ruby
332
+ # application_mailer.rb
333
+
334
+ class ApplicationMailer < ActionMailer::Base
335
+ ActionMailer::MailDeliveryJob.rescue_from(Exception) do |exception|
336
+ Rails.error.report(exception)
337
+ raise exception
338
+ end
339
+ end
340
+ ```
341
+
311
342
  ## Puma plugin
312
343
 
313
344
  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 +347,29 @@ plugin :solid_queue
316
347
  ```
317
348
  to your `puma.rb` configuration.
318
349
 
350
+
351
+ ## Jobs and transactional integrity
352
+ :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.
353
+
354
+ 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.
355
+
356
+ 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.
357
+
358
+ If you set that to `never` but still want to make sure you're not inadvertently on transactional integrity, you can make sure that:
359
+ - 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.
360
+ - 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:
361
+
362
+ ```ruby
363
+ class ApplicationRecord < ActiveRecord::Base
364
+ self.abstract_class = true
365
+
366
+ connects_to database: { writing: :primary, reading: :replica }
367
+ ```
368
+
369
+ ```ruby
370
+ config.solid_queue.connects_to = { database: { writing: :primary, reading: :replica } }
371
+ ```
372
+
319
373
  ## Recurring tasks
320
374
 
321
375
  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:
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 both the binstub `bin/jobs` and the migration, you can just run
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
- touch(:last_heartbeat_at)
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)
@@ -9,7 +9,7 @@ module ActiveJob
9
9
  # Rails.application.config.active_job.queue_adapter = :solid_queue
10
10
  class SolidQueueAdapter
11
11
  def enqueue_after_transaction_commit?
12
- SolidQueue.enqueue_after_transaction_commit
12
+ true
13
13
  end
14
14
 
15
15
  def enqueue(active_job) # :nodoc:
@@ -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
@@ -53,7 +53,7 @@ module SolidQueue::Processes
53
53
  end
54
54
 
55
55
  def heartbeat
56
- process.with_lock { process.heartbeat }
56
+ process.heartbeat
57
57
  rescue ActiveRecord::RecordNotFound
58
58
  self.process = nil
59
59
  wake_up
@@ -1,3 +1,3 @@
1
1
  module SolidQueue
2
- VERSION = "0.9.0"
2
+ VERSION = "1.0.0.beta"
3
3
  end
data/lib/solid_queue.rb CHANGED
@@ -32,8 +32,6 @@ module SolidQueue
32
32
 
33
33
  mattr_accessor :shutdown_timeout, default: 5.seconds
34
34
 
35
- mattr_accessor :enqueue_after_transaction_commit, default: false
36
-
37
35
  mattr_accessor :silence_polling, default: true
38
36
 
39
37
  mattr_accessor :supervisor_pidfile
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.9.0
4
+ version: 1.0.0.beta
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 00:00:00.000000000 Z
11
+ date: 2024-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord