solid_queue 1.0.0 → 1.0.2

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: 553dc884dd38c24c16196da9cceb6e7b40d02bd3f5c005bc980065232b4b1682
4
- data.tar.gz: dba6309954b326305526417c05c1a5878a0195d759e8cd6fb6c4453ee61cd6c0
3
+ metadata.gz: 2af6f7c63701d0cdc2c017e4dde0ab4d73e00140402bb09df68705321c122483
4
+ data.tar.gz: 82732122367e5161471f1a76a80b9d11878241fce2a9d095b71e33a2dc6dd5d8
5
5
  SHA512:
6
- metadata.gz: dc6ad2d8abd513cd9f357a1190c2173c978b46dd800d1b3f4040364cd70c44b5271da7108901c67a2811586d746607eac4f999c8886a56a1942a228f0f3d1d11
7
- data.tar.gz: 999a90c18c09350609670b29bdd3f5e2596815f0a5edb194b225634b9169eca9efaf3e8efda23cfe346e6947e9477b983bbcae1a8a1adf3c6fc2ae9affd4e2ff
6
+ metadata.gz: 255deba66b8fddc1df0a050642d9ef640f609a58a51020feb3aeaede5c04fe64acd64e5cc2088969a688f2ff94b423f06b955d6ad70f5a10d2b7d00dea54ce75
7
+ data.tar.gz: 7d41051ef3361b1bb55d6a484e0faa5eb979bafc3d0ef99f6759c6ec1375c4587aba159880be4b2a0534d4821b68227ea4645650822243306b9bbc18947e38d8
data/README.md CHANGED
@@ -149,6 +149,9 @@ Here's an overview of the different options:
149
149
  This will create a worker fetching jobs from all queues starting with `staging`. The wildcard `*` is only allowed on its own or at the end of a queue name; you can't specify queue names such as `*_some_queue`. These will be ignored.
150
150
 
151
151
  Finally, you can combine prefixes with exact names, like `[ staging*, background ]`, and the behaviour with respect to order will be the same as with only exact names.
152
+
153
+ Check the sections below on [how queue order behaves combined with priorities](#queue-order-and-priorities), and [how the way you specify the queues per worker might affect performance](#queues-specification-and-performance).
154
+
152
155
  - `threads`: this is the max size of the thread pool that each worker will have to run jobs. Each worker will fetch this number of jobs from their queue(s), at most and will post them to the thread pool to be run. By default, this is `3`. Only workers have this setting.
153
156
  - `processes`: this is the number of worker processes that will be forked by the supervisor with the settings given. By default, this is `1`, just a single process. This setting is useful if you want to dedicate more than one CPU core to a queue or queues with the same configuration. Only workers have this setting.
154
157
  - `concurrency_maintenance`: whether the dispatcher will perform the concurrency maintenance work. This is `true` by default, and it's useful if you don't use any [concurrency controls](#concurrency-controls) and want to disable it or if you run multiple dispatchers and want some of them to just dispatch jobs without doing anything else.
@@ -164,6 +167,67 @@ This is useful when you run jobs with different importance or urgency in the sam
164
167
 
165
168
  We recommend not mixing queue order with priorities but either choosing one or the other, as that will make job execution order more straightforward for you.
166
169
 
170
+ ### Queues specification and performance
171
+
172
+ To keep polling performant and ensure a covering index is always used, Solid Queue only does two types of polling queries:
173
+ ```sql
174
+ -- No filtering by queue
175
+ SELECT job_id
176
+ FROM solid_queue_ready_executions
177
+ ORDER BY priority ASC, job_id ASC
178
+ LIMIT ?
179
+ FOR UPDATE SKIP LOCKED;
180
+
181
+ -- Filtering by a single queue
182
+ SELECT job_id
183
+ FROM solid_queue_ready_executions
184
+ WHERE queue_name = ?
185
+ ORDER BY priority ASC, job_id ASC
186
+ LIMIT ?
187
+ FOR UPDATE SKIP LOCKED;
188
+ ```
189
+
190
+ The first one (no filtering by queue) is used when you specify
191
+ ```yml
192
+ queues: *
193
+ ```
194
+ and there aren't any queues paused, as we want to target all queues.
195
+
196
+ In other cases, we need to have a list of queues to filter by, in order, because we can only filter by a single queue at a time to ensure we use an index to sort. This means that if you specify your queues as:
197
+ ```yml
198
+ queues: beta*
199
+ ```
200
+
201
+ we'll need to get a list of all existing queues matching that prefix first, with a query that would look like this:
202
+ ```sql
203
+ SELECT DISTINCT(queue_name)
204
+ FROM solid_queue_ready_executions
205
+ WHERE queue_name LIKE 'beta%';
206
+ ```
207
+
208
+ This type of `DISTINCT` query on a column that's the leftmost column in an index can be performed very fast in MySQL thanks to a technique called [Loose Index Scan](https://dev.mysql.com/doc/refman/8.0/en/group-by-optimization.html#loose-index-scan). PostgreSQL and SQLite, however, don't implement this technique, which means that if your `solid_queue_ready_executions` table is very big because your queues get very deep, this query will get slow. Normally your `solid_queue_ready_executions` table will be small, but it can happen.
209
+
210
+ Similarly to using prefixes, the same will happen if you have paused queues, because we need to get a list of all queues with a query like
211
+ ```sql
212
+ SELECT DISTINCT(queue_name)
213
+ FROM solid_queue_ready_executions
214
+ ```
215
+
216
+ and then remove the paused ones. Pausing in general should be something rare, used in special circumstances, and for a short period of time. If you don't want to process jobs from a queue anymore, the best way to do that is to remove it from your list of queues.
217
+
218
+ 💡 To sum up, **if you want to ensure optimal performance on polling**, the best way to do that is to always specify exact names for them, and not have any queues paused.
219
+
220
+ Do this:
221
+
222
+ ```yml
223
+ queues: background, backend
224
+ ```
225
+
226
+ instead of this:
227
+ ```yml
228
+ queues: back*
229
+ ```
230
+
167
231
 
168
232
  ### Threads, processes and signals
169
233
 
@@ -348,7 +412,7 @@ to your `puma.rb` configuration.
348
412
 
349
413
 
350
414
  ## 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.
415
+ :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 enqueuing 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
416
 
353
417
  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
418
 
@@ -64,6 +64,7 @@ class SolidQueue::ClaimedExecution < SolidQueue::Execution
64
64
  finished
65
65
  else
66
66
  failed_with(result.error)
67
+ raise result.error
67
68
  end
68
69
  ensure
69
70
  job.unblock_next_blocked_job
@@ -34,7 +34,7 @@ module SolidQueue
34
34
  def eligible_queues
35
35
  if include_all_queues? then all_queues
36
36
  else
37
- exact_names + prefixed_names
37
+ in_raw_order(exact_names + prefixed_names)
38
38
  end
39
39
  end
40
40
 
@@ -42,8 +42,12 @@ module SolidQueue
42
42
  "*".in? raw_queues
43
43
  end
44
44
 
45
+ def all_queues
46
+ relation.distinct(:queue_name).pluck(:queue_name)
47
+ end
48
+
45
49
  def exact_names
46
- raw_queues.select { |queue| !queue.include?("*") }
50
+ raw_queues.select { |queue| exact_name?(queue) }
47
51
  end
48
52
 
49
53
  def prefixed_names
@@ -54,15 +58,41 @@ module SolidQueue
54
58
  end
55
59
 
56
60
  def prefixes
57
- @prefixes ||= raw_queues.select { |queue| queue.ends_with?("*") }.map { |queue| queue.tr("*", "%") }
61
+ @prefixes ||= raw_queues.select { |queue| prefixed_name?(queue) }.map { |queue| queue.tr("*", "%") }
58
62
  end
59
63
 
60
- def all_queues
61
- relation.distinct(:queue_name).pluck(:queue_name)
64
+ def exact_name?(queue)
65
+ !queue.include?("*")
66
+ end
67
+
68
+ def prefixed_name?(queue)
69
+ queue.ends_with?("*")
62
70
  end
63
71
 
64
72
  def paused_queues
65
73
  @paused_queues ||= Pause.all.pluck(:queue_name)
66
74
  end
75
+
76
+ def in_raw_order(queues)
77
+ # Only need to sort if we have prefixes and more than one queue name.
78
+ # Exact names are selected in the same order as they're found
79
+ if queues.one? || prefixes.empty?
80
+ queues
81
+ else
82
+ queues = queues.dup
83
+ raw_queues.flat_map { |raw_queue| delete_in_order(raw_queue, queues) }.compact
84
+ end
85
+ end
86
+
87
+ def delete_in_order(raw_queue, queues)
88
+ if exact_name?(raw_queue)
89
+ queues.delete(raw_queue)
90
+ elsif prefixed_name?(raw_queue)
91
+ prefix = raw_queue.tr("*", "")
92
+ queues.select { |queue| queue.start_with?(prefix) }.tap do |matches|
93
+ queues -= matches
94
+ end
95
+ end
96
+ end
67
97
  end
68
98
  end
@@ -4,7 +4,7 @@ module SolidQueue
4
4
  class Record < ActiveRecord::Base
5
5
  self.abstract_class = true
6
6
 
7
- connects_to **SolidQueue.connects_to if SolidQueue.connects_to
7
+ connects_to(**SolidQueue.connects_to) if SolidQueue.connects_to
8
8
 
9
9
  def self.non_blocking_lock
10
10
  if SolidQueue.use_skip_locked
@@ -67,11 +67,15 @@ module SolidQueue
67
67
  end
68
68
  end
69
69
 
70
- payload[:active_job_id] = active_job.job_id if active_job
70
+ active_job.tap do |enqueued_job|
71
+ payload[:active_job_id] = enqueued_job.job_id
72
+ end
71
73
  rescue RecurringExecution::AlreadyRecorded
72
74
  payload[:skipped] = true
75
+ false
73
76
  rescue Job::EnqueueError => error
74
77
  payload[:enqueue_error] = error.message
78
+ false
75
79
  end
76
80
  end
77
81
 
@@ -41,10 +41,6 @@ module SolidQueue::Processes
41
41
  end
42
42
  end
43
43
 
44
- def run
45
- raise NotImplementedError
46
- end
47
-
48
44
  def shutting_down?
49
45
  stopped? || (running_as_fork? && supervisor_went_away?) || finished? || !registered?
50
46
  end
@@ -29,7 +29,7 @@ module SolidQueue
29
29
  else
30
30
  FileUtils.mkdir_p File.dirname(path)
31
31
  end
32
- rescue Errno::ESRCH => e
32
+ rescue Errno::ESRCH
33
33
  # Process is dead, ignore, just delete the file
34
34
  delete
35
35
  rescue Errno::EPERM
@@ -1,3 +1,3 @@
1
1
  module SolidQueue
2
- VERSION = "1.0.0"
2
+ VERSION = "1.0.2"
3
3
  end
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: 1.0.0
4
+ version: 1.0.2
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-26 00:00:00.000000000 Z
11
+ date: 2024-11-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -302,7 +302,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
302
302
  - !ruby/object:Gem::Version
303
303
  version: '0'
304
304
  requirements: []
305
- rubygems_version: 3.5.9
305
+ rubygems_version: 3.5.16
306
306
  signing_key:
307
307
  specification_version: 4
308
308
  summary: Database-backed Active Job backend.