good_job 4.19.4 → 4.20.0
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 +40 -0
- data/README.md +1 -1
- data/app/filters/good_job/base_filter.rb +12 -10
- data/app/filters/good_job/batches_filter.rb +12 -2
- data/app/frontend/good_job/modules/async_values_controller.js +7 -0
- data/app/frontend/good_job/modules/live_poll_controller.js +16 -1
- data/app/helpers/good_job/application_helper.rb +18 -0
- data/app/models/concerns/good_job/filterable.rb +19 -6
- data/app/models/good_job/cron_entry.rb +3 -1
- data/app/models/good_job/job.rb +26 -7
- data/app/views/good_job/batches/_jobs.erb +1 -1
- data/app/views/good_job/batches/_table.erb +1 -1
- data/app/views/good_job/jobs/_executions.erb +1 -1
- data/app/views/good_job/jobs/_table.erb +1 -1
- data/app/views/good_job/jobs/show.html.erb +2 -2
- data/config/brakeman.ignore +38 -32
- data/lib/generators/good_job/templates/install/migrations/create_good_jobs.rb.erb +20 -6
- data/lib/generators/good_job/templates/update/migrations/01_create_good_jobs.rb.erb +11 -6
- data/lib/generators/good_job/templates/update/migrations/16_add_index_good_jobs_dequeue_by_queue.rb.erb +21 -0
- data/lib/generators/good_job/templates/update/migrations/17_add_index_good_job_executions_on_scheduled_at.rb.erb +12 -0
- data/lib/generators/good_job/templates/update/migrations/18_add_batch_query_indexes.rb.erb +28 -0
- data/lib/generators/good_job/templates/update/migrations/19_remove_redundant_good_jobs_indexes.rb.erb +55 -0
- data/lib/generators/good_job/templates/update/migrations/20_reorder_good_jobs_cron_index.rb.erb +51 -0
- data/lib/good_job/capsule.rb +1 -1
- data/lib/good_job/cli.rb +32 -1
- data/lib/good_job/configuration.rb +92 -3
- data/lib/good_job/cron_manager.rb +30 -7
- data/lib/good_job/lifecycle_hooks.rb +83 -0
- data/lib/good_job/log_subscriber.rb +78 -0
- data/lib/good_job/multi_scheduler.rb +2 -1
- data/lib/good_job/notifier.rb +5 -3
- data/lib/good_job/probe_server/cluster_healthcheck_middleware.rb +32 -0
- data/lib/good_job/probe_server/simple_handler.rb +14 -9
- data/lib/good_job/probe_server/webrick_handler.rb +12 -0
- data/lib/good_job/probe_server.rb +18 -0
- data/lib/good_job/shared_executor.rb +3 -1
- data/lib/good_job/subprocess.rb +135 -0
- data/lib/good_job/supervisor.rb +536 -0
- data/lib/good_job/systemd_service.rb +12 -1
- data/lib/good_job/version.rb +1 -1
- data/lib/good_job.rb +23 -1
- metadata +10 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 96a4cd91def79d6e98d59849112ee43979d9dd518e252ca693d12c0e71003463
|
|
4
|
+
data.tar.gz: 5d82aa51b9f049b7655f0e7243ede43df3029011fd5e1f62637fdf193ba2cec3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9301c67f9223407df170c9d8b9c93859d274f6affb36afb2fe948e81f1b704d7d8215510ad1491cf86f83fafecd5cd94a2394f8a793cf995b28f41a326d75086
|
|
7
|
+
data.tar.gz: 4b6ae502d9030ef467e9c7579ec2da0ccde6ccf35176d7a7a50e1fee13c0463030b9d725c0cd916439cdf58c32c3cf2d0a7bb1bea5c5e790c23110c670c510cf
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [v4.20.0](https://github.com/bensheldon/good_job/tree/v4.20.0) (2026-10-03)
|
|
4
|
+
|
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v4.19.4...v4.20.0)
|
|
6
|
+
|
|
7
|
+
**Implemented enhancements:**
|
|
8
|
+
|
|
9
|
+
- Match identifier-like strings in dashboard search [\#1831](https://github.com/bensheldon/good_job/pull/1831) ([shiaho777](https://github.com/shiaho777))
|
|
10
|
+
- Add a queue-scoped index for the dequeue candidate query [\#1810](https://github.com/bensheldon/good_job/pull/1810) ([a-abdellatif98](https://github.com/a-abdellatif98))
|
|
11
|
+
- Add cluster mode [\#1803](https://github.com/bensheldon/good_job/pull/1803) ([bensheldon](https://github.com/bensheldon))
|
|
12
|
+
|
|
13
|
+
**Fixed bugs:**
|
|
14
|
+
|
|
15
|
+
- \[dashboard\] Mismatch between page header vs graph headers [\#1601](https://github.com/bensheldon/good_job/issues/1601)
|
|
16
|
+
- Add dashboard and batch indexes, remove redundant indexes, and reorder the cron index [\#1840](https://github.com/bensheldon/good_job/pull/1840) ([bensheldon](https://github.com/bensheldon))
|
|
17
|
+
- Detect Puma worker boot hooks and Puma 8 requests in `in_webserver?` [\#1836](https://github.com/bensheldon/good_job/pull/1836) ([amanfredi](https://github.com/amanfredi))
|
|
18
|
+
- Keep both SharedExecutor threads available on JRuby [\#1835](https://github.com/bensheldon/good_job/pull/1835) ([amanfredi](https://github.com/amanfredi))
|
|
19
|
+
- Pass `cron_graceful_restart_period` to the CronManager so it takes effect [\#1833](https://github.com/bensheldon/good_job/pull/1833) ([amanfredi](https://github.com/amanfredi))
|
|
20
|
+
- Wrap Notifier's NOTIFY recipient dispatch in the Rails executor [\#1830](https://github.com/bensheldon/good_job/pull/1830) ([shiaho777](https://github.com/shiaho777))
|
|
21
|
+
- Preserve active backtrace tabs across live poll refreshes [\#1824](https://github.com/bensheldon/good_job/pull/1824) ([shiaho777](https://github.com/shiaho777))
|
|
22
|
+
- Refetch async navbar values after a live poll refresh [\#1823](https://github.com/bensheldon/good_job/pull/1823) ([shiaho777](https://github.com/shiaho777))
|
|
23
|
+
- Truncate long strings when displaying job params on the dashboard [\#1822](https://github.com/bensheldon/good_job/pull/1822) ([shiaho777](https://github.com/shiaho777))
|
|
24
|
+
- Bound all search\_text documents under the tsvector size limit [\#1820](https://github.com/bensheldon/good_job/pull/1820) ([shiaho777](https://github.com/shiaho777))
|
|
25
|
+
- Finish a job's unfinished executions when it is discarded [\#1819](https://github.com/bensheldon/good_job/pull/1819) ([shiaho777](https://github.com/shiaho777))
|
|
26
|
+
|
|
27
|
+
**Closed issues:**
|
|
28
|
+
|
|
29
|
+
- Dequeue candidate indexes don't cover queue-filtered workers — one queue's backlog degrades every other queue's claim query [\#1790](https://github.com/bensheldon/good_job/issues/1790)
|
|
30
|
+
- PG: ProgramLimitExceeded during search when a job has a huge error message [\#1785](https://github.com/bensheldon/good_job/issues/1785)
|
|
31
|
+
- Very large `serialized_params` caused dashboard timeout on Heroku [\#1629](https://github.com/bensheldon/good_job/issues/1629)
|
|
32
|
+
- \[dashboard\] Viewing a job & live poll resets the Full Trace vs Application Trace [\#1606](https://github.com/bensheldon/good_job/issues/1606)
|
|
33
|
+
- \[GoodJob\] Notifier errored: NameError: uninitialized constant GoodJob::Job in develpment [\#1571](https://github.com/bensheldon/good_job/issues/1571)
|
|
34
|
+
- Bug: calling GoodJob.restart in Puma on\_worker\_boot still starts GoodJob regardless of it being in async mode or not [\#1531](https://github.com/bensheldon/good_job/issues/1531)
|
|
35
|
+
- Discarding a job via the dashboard should also update the discrete\_execution [\#1093](https://github.com/bensheldon/good_job/issues/1093)
|
|
36
|
+
- A copy of GoodJob::Execution has been removed from the module tree but is still active! [\#954](https://github.com/bensheldon/good_job/issues/954)
|
|
37
|
+
|
|
38
|
+
**Merged pull requests:**
|
|
39
|
+
|
|
40
|
+
- Wait for Turbo navigation before reading the URL in performance specs [\#1838](https://github.com/bensheldon/good_job/pull/1838) ([amanfredi](https://github.com/amanfredi))
|
|
41
|
+
- Bump github/codeql-action from 4.37.9 to 4.38.2 [\#1837](https://github.com/bensheldon/good_job/pull/1837) ([dependabot[bot]](https://github.com/apps/dependabot))
|
|
42
|
+
|
|
3
43
|
## [v4.19.4](https://github.com/bensheldon/good_job/tree/v4.19.4) (2026-10-02)
|
|
4
44
|
|
|
5
45
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v4.19.3...v4.19.4)
|
data/README.md
CHANGED
|
@@ -308,7 +308,7 @@ Available configuration options are:
|
|
|
308
308
|
- `max_cache` (integer) sets the maximum number of scheduled jobs that will be stored in memory to reduce execution latency when also polling for scheduled jobs. Caching 10,000 scheduled jobs uses approximately 20MB of memory. You can also set this with the environment variable `GOOD_JOB_MAX_CACHE`.
|
|
309
309
|
- `shutdown_timeout` (integer) number of seconds to wait for jobs to finish when shutting down before stopping the thread. Defaults to forever: `-1`. You can also set this with the environment variable `GOOD_JOB_SHUTDOWN_TIMEOUT`.
|
|
310
310
|
- `enable_cron` (boolean) whether to run cron process. Defaults to `false`. You can also set this with the environment variable `GOOD_JOB_ENABLE_CRON`.
|
|
311
|
-
- `cron_graceful_restart_period` (integer) when
|
|
311
|
+
- `cron_graceful_restart_period` (integer seconds or `ActiveSupport::Duration`) when cron starts, attempt to re-enqueue jobs that would have been enqueued by cron within this time period (e.g. `1.minute`). This should match the expected downtime during deploys. Like enabling cron on multiple processes, this relies on cron-created job records being preserved to avoid re-enqueuing jobs that already ran, so it is ignored when `preserve_job_records` is `false` or `:on_unhandled_error`. It does not apply to a `cron` proc that returns a time, which already enqueues the runs it missed since its last job. Defaults to `nil` (disabled). You can also set this with the environment variable `GOOD_JOB_CRON_GRACEFUL_RESTART_PERIOD`.
|
|
312
312
|
- `enable_listen_notify` (boolean) whether to enqueue and read jobs with Postgres LISTEN/NOTIFY. Defaults to `true`. You can also set this with the environment variable `GOOD_JOB_ENABLE_LISTEN_NOTIFY`.
|
|
313
313
|
- `cron` (hash) cron configuration. Defaults to `{}`. You can also set this as a JSON string with the environment variable `GOOD_JOB_CRON`
|
|
314
314
|
- `cleanup_discarded_jobs` (boolean) whether to destroy discarded jobs when cleaning up preserved jobs using the `$ good_job cleanup_preserved_jobs` CLI command or calling `GoodJob.cleanup_preserved_jobs`. Defaults to `true`. Can also be set with the environment variable `GOOD_JOB_CLEANUP_DISCARDED_JOBS`.
|
|
@@ -13,15 +13,17 @@ module GoodJob
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def records
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
@_records ||= begin
|
|
17
|
+
after_id = params[:after_id]
|
|
18
|
+
after_at = params[:after_at].present? ? Time.zone.parse(params[:after_at]) : nil
|
|
19
|
+
limit = params.fetch(:limit, DEFAULT_LIMIT)
|
|
20
|
+
|
|
21
|
+
query_for_records.display_all(
|
|
22
|
+
ordered_by: ordered_by,
|
|
23
|
+
after_at: after_at,
|
|
24
|
+
after_id: after_id
|
|
25
|
+
).limit(limit)
|
|
26
|
+
end
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
def last
|
|
@@ -76,7 +78,7 @@ module GoodJob
|
|
|
76
78
|
|
|
77
79
|
def next_page_params
|
|
78
80
|
order_column = ordered_by.first
|
|
79
|
-
last_record =
|
|
81
|
+
last_record = last
|
|
80
82
|
|
|
81
83
|
{
|
|
82
84
|
after_at: last_record&.send(order_column)&.iso8601(6),
|
|
@@ -2,16 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
module GoodJob
|
|
4
4
|
class BatchesFilter < BaseFilter
|
|
5
|
+
def job_count(batch)
|
|
6
|
+
job_counts.fetch(batch.id, 0)
|
|
7
|
+
end
|
|
8
|
+
|
|
5
9
|
def filtered_query(_filtered_params = params)
|
|
6
10
|
base_query
|
|
7
11
|
end
|
|
8
12
|
|
|
9
13
|
def query_for_records
|
|
10
|
-
|
|
14
|
+
base_query
|
|
11
15
|
end
|
|
12
16
|
|
|
13
17
|
def default_base_query
|
|
14
|
-
GoodJob::BatchRecord.
|
|
18
|
+
GoodJob::BatchRecord.all
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def job_counts
|
|
24
|
+
@_job_counts ||= GoodJob::Job.where(batch_id: records.map(&:id)).group(:batch_id).count
|
|
15
25
|
end
|
|
16
26
|
end
|
|
17
27
|
end
|
|
@@ -7,8 +7,15 @@ export default class extends Controller {
|
|
|
7
7
|
}
|
|
8
8
|
static targets = ["value"]
|
|
9
9
|
|
|
10
|
+
#onLivePollRefresh = () => this.#fetch();
|
|
11
|
+
|
|
10
12
|
connect() {
|
|
11
13
|
this.#fetch();
|
|
14
|
+
document.addEventListener("good_job:live-poll-refreshed", this.#onLivePollRefresh);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
disconnect() {
|
|
18
|
+
document.removeEventListener("good_job:live-poll-refreshed", this.#onLivePollRefresh);
|
|
12
19
|
}
|
|
13
20
|
|
|
14
21
|
async #fetch() {
|
|
@@ -56,8 +56,23 @@ export default class extends Controller {
|
|
|
56
56
|
const regionName = newElement.getAttribute('data-live-poll-region')
|
|
57
57
|
const originalElement = document.querySelector(`[data-live-poll-region="${regionName}"]`)
|
|
58
58
|
if (originalElement) {
|
|
59
|
-
originalElement
|
|
59
|
+
this.#restoreTabs(originalElement, newElement)
|
|
60
60
|
}
|
|
61
61
|
})
|
|
62
62
|
}
|
|
63
|
+
|
|
64
|
+
// Re-select any Bootstrap tabs that were active before a region is replaced
|
|
65
|
+
#restoreTabs(originalElement, newElement) {
|
|
66
|
+
const activeTargets = [...originalElement.querySelectorAll('.nav-link.active[data-bs-toggle]')]
|
|
67
|
+
.map((tab) => tab.getAttribute('data-bs-target') || tab.getAttribute('href'))
|
|
68
|
+
originalElement.replaceWith(newElement)
|
|
69
|
+
activeTargets.forEach((target) => {
|
|
70
|
+
const newTab = newElement.querySelector(`[data-bs-target="${target}"], [href="${target}"]`)
|
|
71
|
+
if (newTab) {
|
|
72
|
+
bootstrap.Tab.getOrCreateInstance(newTab).show()
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
document.dispatchEvent(new CustomEvent("good_job:live-poll-refreshed"))
|
|
77
|
+
}
|
|
63
78
|
}
|
|
@@ -7,6 +7,10 @@ module GoodJob
|
|
|
7
7
|
# We can't rely on +config.action_controller.include_all_helpers = true+ in the host app.
|
|
8
8
|
include IconsHelper
|
|
9
9
|
|
|
10
|
+
# Maximum length of an individual string rendered inside serialized params
|
|
11
|
+
# and arguments on the dashboard, so very large payloads cannot stall the page.
|
|
12
|
+
MAX_DISPLAY_STRING_LENGTH = 1_000
|
|
13
|
+
|
|
10
14
|
def job_action_states
|
|
11
15
|
{
|
|
12
16
|
reschedule: %w[scheduled retried queued],
|
|
@@ -17,6 +21,20 @@ module GoodJob
|
|
|
17
21
|
}
|
|
18
22
|
end
|
|
19
23
|
|
|
24
|
+
# Truncates long strings within a JSON-like structure for dashboard display.
|
|
25
|
+
def truncate_display_value(value, limit: MAX_DISPLAY_STRING_LENGTH)
|
|
26
|
+
case value
|
|
27
|
+
when String
|
|
28
|
+
value.length > limit ? "#{value.first(limit)}… [#{value.length} characters total]" : value
|
|
29
|
+
when Array
|
|
30
|
+
value.map { |element| truncate_display_value(element, limit: limit) }
|
|
31
|
+
when Hash
|
|
32
|
+
value.transform_values { |element| truncate_display_value(element, limit: limit) }
|
|
33
|
+
else
|
|
34
|
+
value
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
20
38
|
def format_duration(sec)
|
|
21
39
|
return unless sec
|
|
22
40
|
return "" if sec.is_a?(String) # pg interval support added in Rails 6.1
|
|
@@ -5,9 +5,11 @@ module GoodJob
|
|
|
5
5
|
module Filterable
|
|
6
6
|
extend ActiveSupport::Concern
|
|
7
7
|
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
8
|
+
# Bounds every document fed to to_tsvector() so the combined tsvector stays
|
|
9
|
+
# under PG's ~1 MB limit (PG::ProgramLimitExceeded: "string is too long for
|
|
10
|
+
# tsvector"). Free-form columns (arguments, error, labels) are truncated, and
|
|
11
|
+
# arguments are excluded from the full serialized_params document since they
|
|
12
|
+
# are covered by their own truncated term.
|
|
11
13
|
MAX_SEARCH_COLUMN_CHARS = 262_144
|
|
12
14
|
|
|
13
15
|
included do
|
|
@@ -74,14 +76,25 @@ module GoodJob
|
|
|
74
76
|
(
|
|
75
77
|
to_tsvector('english', id::text) ||
|
|
76
78
|
to_tsvector('english', COALESCE(active_job_id::text, '')) ||
|
|
77
|
-
to_tsvector('english', serialized_params) ||
|
|
79
|
+
to_tsvector('english', serialized_params - 'arguments') ||
|
|
78
80
|
to_tsvector('english', COALESCE(LEFT(serialized_params->>'arguments', #{MAX_SEARCH_COLUMN_CHARS}), '')) ||
|
|
79
81
|
to_tsvector('english', COALESCE(LEFT(error, #{MAX_SEARCH_COLUMN_CHARS}), '')) ||
|
|
80
|
-
to_tsvector('english', COALESCE(array_to_string(labels, ' '), ''))
|
|
82
|
+
to_tsvector('english', COALESCE(LEFT(array_to_string(labels, ' '), #{MAX_SEARCH_COLUMN_CHARS}), ''))
|
|
81
83
|
)
|
|
82
84
|
SQL
|
|
83
85
|
to_tsquery_function = database_supports_websearch_to_tsquery? ? 'websearch_to_tsquery' : 'plainto_tsquery'
|
|
84
|
-
|
|
86
|
+
# Full-text tokenization does not preserve identifier-like strings (URLs, globs of
|
|
87
|
+
# numbers and letters), so also match the raw text columns as a substring.
|
|
88
|
+
text_match = <<~SQL.squish
|
|
89
|
+
(
|
|
90
|
+
id::text ILIKE :like_pattern OR
|
|
91
|
+
COALESCE(active_job_id::text, '') ILIKE :like_pattern OR
|
|
92
|
+
serialized_params::text ILIKE :like_pattern OR
|
|
93
|
+
COALESCE(error, '') ILIKE :like_pattern OR
|
|
94
|
+
COALESCE(array_to_string(labels, ' '), '') ILIKE :like_pattern
|
|
95
|
+
)
|
|
96
|
+
SQL
|
|
97
|
+
where("#{tsvector} @@ #{to_tsquery_function}('english', CAST(:query AS text)) OR #{text_match}", query: query, like_pattern: "%#{sanitize_sql_like(query)}%")
|
|
85
98
|
.order(sanitize_sql_for_order([Arel.sql("ts_rank(#{tsvector}, #{to_tsquery_function}('english', CAST(? AS text)))"), query]) => 'DESC')
|
|
86
99
|
end)
|
|
87
100
|
end
|
|
@@ -110,7 +110,9 @@ module GoodJob # :nodoc:
|
|
|
110
110
|
if result.is_a?(String)
|
|
111
111
|
Fugit.parse(result).within(period).map(&:to_t)
|
|
112
112
|
else
|
|
113
|
-
|
|
113
|
+
# A proc that returns a Time is called with the previous run's time, so when cron starts,
|
|
114
|
+
# CronManager#create_task already enqueues every run missed since the last job.
|
|
115
|
+
[]
|
|
114
116
|
end
|
|
115
117
|
else
|
|
116
118
|
fugit.within(period).map(&:to_t)
|
data/app/models/good_job/job.rb
CHANGED
|
@@ -287,14 +287,20 @@ module GoodJob
|
|
|
287
287
|
end
|
|
288
288
|
|
|
289
289
|
def historic_finished_at_index_migrated?
|
|
290
|
-
|
|
290
|
+
index_exists = connection_pool.with_connection do |connection|
|
|
291
|
+
connection.index_name_exists?(table_name, "index_good_jobs_on_queue_name_priority_scheduled_at_unfinished")
|
|
292
|
+
end
|
|
293
|
+
return true if index_exists
|
|
291
294
|
|
|
292
295
|
migration_pending_warning!
|
|
293
296
|
false
|
|
294
297
|
end
|
|
295
298
|
|
|
296
299
|
def lock_type_migrated?
|
|
297
|
-
|
|
300
|
+
index_exists = connection_pool.with_connection do |connection|
|
|
301
|
+
connection.index_name_exists?(table_name, :index_good_jobs_for_candidate_dequeue_unlocked)
|
|
302
|
+
end
|
|
303
|
+
return true if index_exists
|
|
298
304
|
|
|
299
305
|
migration_pending_warning!
|
|
300
306
|
false
|
|
@@ -957,11 +963,24 @@ module GoodJob
|
|
|
957
963
|
job_error = GoodJob::Job::DiscardJobError.new(message)
|
|
958
964
|
|
|
959
965
|
update_record = proc do
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
966
|
+
now = Time.current
|
|
967
|
+
error = self.class.format_error(job_error)
|
|
968
|
+
|
|
969
|
+
transaction do
|
|
970
|
+
update(
|
|
971
|
+
finished_at: now,
|
|
972
|
+
error: error,
|
|
973
|
+
error_event: :discarded
|
|
974
|
+
)
|
|
975
|
+
executions.where(finished_at: nil).find_each do |execution|
|
|
976
|
+
execution.update!(
|
|
977
|
+
finished_at: now,
|
|
978
|
+
error: error,
|
|
979
|
+
error_event: :discarded,
|
|
980
|
+
duration: (now - execution.created_at).seconds
|
|
981
|
+
)
|
|
982
|
+
end
|
|
983
|
+
end
|
|
965
984
|
end
|
|
966
985
|
|
|
967
986
|
if active_job.respond_to?(:instrument)
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
</div>
|
|
110
110
|
</div>
|
|
111
111
|
<%= tag.div id: dom_id(job, "params"), class: "job-params list-group-item collapse small bg-dark text-light" do %>
|
|
112
|
-
<%= tag.pre JSON.pretty_generate(job.display_serialized_params) %>
|
|
112
|
+
<%= tag.pre JSON.pretty_generate(truncate_display_value(job.display_serialized_params)) %>
|
|
113
113
|
<% end %>
|
|
114
114
|
<% end %>
|
|
115
115
|
<% else %>
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
</div>
|
|
57
57
|
<div class="col-6 col-lg-1">
|
|
58
58
|
<div class="d-lg-none small text-muted mt-1"><%= t "good_job.models.batch.jobs" %></div>
|
|
59
|
-
<%= batch
|
|
59
|
+
<%= filter.job_count(batch) %>
|
|
60
60
|
</div>
|
|
61
61
|
<div class="col text-end d-flex flex-row justify-content-end">
|
|
62
62
|
<% if batch.discarded? %>
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
<% end %>
|
|
71
71
|
<%= render 'good_job/custom_execution_details', execution: execution, job: @job %>
|
|
72
72
|
<%= tag.div id: dom_id(execution, "params"), class: "list-group-item collapse small bg-dark text-light" do %>
|
|
73
|
-
<%= tag.pre JSON.pretty_generate(execution.display_serialized_params) %>
|
|
73
|
+
<%= tag.pre JSON.pretty_generate(truncate_display_value(execution.display_serialized_params)) %>
|
|
74
74
|
<% end %>
|
|
75
75
|
<% end %>
|
|
76
76
|
</div>
|
|
@@ -190,7 +190,7 @@
|
|
|
190
190
|
</div>
|
|
191
191
|
<% end %>
|
|
192
192
|
<%= tag.div id: dom_id(job, "params"), class: "job-params list-group-item collapse small bg-dark text-light" do %>
|
|
193
|
-
<%= tag.pre JSON.pretty_generate(job.display_serialized_params) %>
|
|
193
|
+
<%= tag.pre JSON.pretty_generate(truncate_display_value(job.display_serialized_params)) %>
|
|
194
194
|
<% end %>
|
|
195
195
|
<% end %>
|
|
196
196
|
<% else %>
|
|
@@ -78,10 +78,10 @@
|
|
|
78
78
|
<% end %>
|
|
79
79
|
</h5>
|
|
80
80
|
</div>
|
|
81
|
-
<%= tag.pre @job.serialized_params["arguments"].map(
|
|
81
|
+
<%= tag.pre @job.serialized_params["arguments"].map { |argument| truncate_display_value(argument).inspect }.join(', '), class: 'text-wrap text-break' %>
|
|
82
82
|
|
|
83
83
|
<%= tag.div id: dom_id(@job, "params"), class: "list-group-item collapse small bg-dark text-light" do %>
|
|
84
|
-
<%= tag.pre JSON.pretty_generate(@job.display_serialized_params) %>
|
|
84
|
+
<%= tag.pre JSON.pretty_generate(truncate_display_value(@job.display_serialized_params)) %>
|
|
85
85
|
<% end %>
|
|
86
86
|
|
|
87
87
|
<%= render 'executions', executions: @job.executions.sort_by(&:number).reverse %>
|
data/config/brakeman.ignore
CHANGED
|
@@ -1,26 +1,5 @@
|
|
|
1
1
|
{
|
|
2
2
|
"ignored_warnings": [
|
|
3
|
-
{
|
|
4
|
-
"warning_type": "SQL Injection",
|
|
5
|
-
"warning_code": 0,
|
|
6
|
-
"fingerprint": "e3cc5489d3e3e81f35347b7530d5743fbac8491ab612ceec0d034e22656269ed",
|
|
7
|
-
"check_name": "SQL",
|
|
8
|
-
"message": "Possible SQL injection",
|
|
9
|
-
"file": "app/models/good_job/job/lockable.rb",
|
|
10
|
-
"line": 103,
|
|
11
|
-
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
|
|
12
|
-
"code": "unscoped.find_by_sql([\"WITH candidate AS #{(\"MATERIALIZED \" or \"\")}(#{select(:id).lock(\"FOR NO KEY UPDATE SKIP LOCKED\").to_sql})\\nUPDATE #{quoted_table_name}\\nSET locked_by_id = ?,\\n locked_at = ?,\\n lock_type = ?\\nFROM (\\n SELECT id FROM candidate\\n WHERE pg_try_advisory_lock(#{\"('x' || substr(md5(#{_quoted_table_name_string} || '-' || id::text), 1, 16))::bit(64)::bigint\"})\\n) AS locked\\nWHERE #{quoted_table_name}.id = locked.id\\nRETURNING #{quoted_table_name}.*\\n\".squish, locked_by_id, locked_at, lock_types[lock_type.to_s]])",
|
|
13
|
-
"render_path": null,
|
|
14
|
-
"location": {
|
|
15
|
-
"type": "method",
|
|
16
|
-
"class": "GoodJob::Job",
|
|
17
|
-
"method": "with_hybrid_lock_claim"
|
|
18
|
-
},
|
|
19
|
-
"user_input": "_quoted_table_name_string",
|
|
20
|
-
"confidence": "Weak",
|
|
21
|
-
"cwe_id": [89],
|
|
22
|
-
"note": "Table name is quoted via quoted_table_name (Rails); advisory lock expression uses _quoted_table_name_string (escaped); candidate SQL is Rails-generated; values are parameterized bindings."
|
|
23
|
-
},
|
|
24
3
|
{
|
|
25
4
|
"warning_type": "SQL Injection",
|
|
26
5
|
"warning_code": 0,
|
|
@@ -28,18 +7,20 @@
|
|
|
28
7
|
"check_name": "SQL",
|
|
29
8
|
"message": "Possible SQL injection",
|
|
30
9
|
"file": "app/models/concerns/good_job/advisory_lockable.rb",
|
|
31
|
-
"line":
|
|
10
|
+
"line": 254,
|
|
32
11
|
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
|
|
33
|
-
"code": "joins(\"LEFT JOIN pg_locks ON pg_locks.locktype = 'advisory'\\n AND pg_locks.objsubid = 1\\n AND pg_locks.classid = #{advisory_lock_classid_sql(
|
|
12
|
+
"code": "joins(\"LEFT JOIN pg_locks ON pg_locks.locktype = 'advisory'\\n AND pg_locks.objsubid = 1\\n AND pg_locks.classid = #{advisory_lock_classid_sql(\"#{_quoted_table_name_string} || '-' || #{quoted_table_name}.#{adapter_class.quote_column_name(_advisory_lockable_column)}::text\")}\\n AND pg_locks.objid = #{advisory_lock_objid_sql(\"#{_quoted_table_name_string} || '-' || #{quoted_table_name}.#{adapter_class.quote_column_name(_advisory_lockable_column)}::text\")}\\n\".squish)",
|
|
34
13
|
"render_path": null,
|
|
35
14
|
"location": {
|
|
36
15
|
"type": "method",
|
|
37
16
|
"class": "GoodJob",
|
|
38
17
|
"method": null
|
|
39
18
|
},
|
|
40
|
-
"user_input": "advisory_lock_classid_sql(
|
|
19
|
+
"user_input": "advisory_lock_classid_sql(\"#{_quoted_table_name_string} || '-' || #{quoted_table_name}.#{adapter_class.quote_column_name(_advisory_lockable_column)}::text\")",
|
|
41
20
|
"confidence": "Medium",
|
|
42
|
-
"cwe_id": [
|
|
21
|
+
"cwe_id": [
|
|
22
|
+
89
|
|
23
|
+
],
|
|
43
24
|
"note": "Values are quoted identifiers and an escaped table name string; not user input."
|
|
44
25
|
},
|
|
45
26
|
{
|
|
@@ -49,30 +30,32 @@
|
|
|
49
30
|
"check_name": "SQL",
|
|
50
31
|
"message": "Possible SQL injection",
|
|
51
32
|
"file": "app/models/concerns/good_job/advisory_lockable.rb",
|
|
52
|
-
"line":
|
|
33
|
+
"line": 254,
|
|
53
34
|
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
|
|
54
|
-
"code": "joins(\"LEFT JOIN pg_locks ON pg_locks.locktype = 'advisory'\\n AND pg_locks.objsubid = 1\\n AND pg_locks.classid = #{advisory_lock_classid_sql(
|
|
35
|
+
"code": "joins(\"LEFT JOIN pg_locks ON pg_locks.locktype = 'advisory'\\n AND pg_locks.objsubid = 1\\n AND pg_locks.classid = #{advisory_lock_classid_sql(\"#{_quoted_table_name_string} || '-' || #{quoted_table_name}.#{adapter_class.quote_column_name(_advisory_lockable_column)}::text\")}\\n AND pg_locks.objid = #{advisory_lock_objid_sql(\"#{_quoted_table_name_string} || '-' || #{quoted_table_name}.#{adapter_class.quote_column_name(_advisory_lockable_column)}::text\")}\\n\".squish)",
|
|
55
36
|
"render_path": null,
|
|
56
37
|
"location": {
|
|
57
38
|
"type": "method",
|
|
58
39
|
"class": "GoodJob::AdvisoryLockable",
|
|
59
40
|
"method": null
|
|
60
41
|
},
|
|
61
|
-
"user_input": "advisory_lock_classid_sql(
|
|
42
|
+
"user_input": "advisory_lock_classid_sql(\"#{_quoted_table_name_string} || '-' || #{quoted_table_name}.#{adapter_class.quote_column_name(_advisory_lockable_column)}::text\")",
|
|
62
43
|
"confidence": "Medium",
|
|
63
|
-
"cwe_id": [
|
|
44
|
+
"cwe_id": [
|
|
45
|
+
89
|
|
46
|
+
],
|
|
64
47
|
"note": "Values are quoted identifiers and an escaped table name string; not user input."
|
|
65
48
|
},
|
|
66
49
|
{
|
|
67
50
|
"warning_type": "Dangerous Eval",
|
|
68
51
|
"warning_code": 13,
|
|
69
|
-
"fingerprint": "
|
|
52
|
+
"fingerprint": "54b7ac445eea2aa1735ae97fab1d841e15405a747ba0fdb577cf779286647534",
|
|
70
53
|
"check_name": "Evaluation",
|
|
71
54
|
"message": "Dynamic string evaluated as code",
|
|
72
55
|
"file": "lib/good_job/log_subscriber.rb",
|
|
73
|
-
"line":
|
|
56
|
+
"line": 356,
|
|
74
57
|
"link": "https://brakemanscanner.org/docs/warning_types/dangerous_eval/",
|
|
75
|
-
"code": "class_eval(\" def #{level}(progname = nil, tags: [], &block) # def info(progname = nil, tags: [], &block)\\n return unless logger # return unless logger\\n #\\n tag_logger(*tags) do # tag_logger(*tags) do\\n logger.#{level}(progname, &block) # logger.info(progname, &block)\\n end # end\\n end #\\n\", \"lib/good_job/log_subscriber.rb\", (
|
|
58
|
+
"code": "class_eval(\" def #{level}(progname = nil, tags: [], &block) # def info(progname = nil, tags: [], &block)\\n return unless logger # return unless logger\\n #\\n tag_logger(*tags) do # tag_logger(*tags) do\\n logger.#{level}(progname, &block) # logger.info(progname, &block)\\n end # end\\n end #\\n\", \"lib/good_job/log_subscriber.rb\", (356 + 1))",
|
|
76
59
|
"render_path": null,
|
|
77
60
|
"location": {
|
|
78
61
|
"type": "method",
|
|
@@ -132,6 +115,29 @@
|
|
|
132
115
|
77
|
|
133
116
|
],
|
|
134
117
|
"note": ""
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"warning_type": "SQL Injection",
|
|
121
|
+
"warning_code": 0,
|
|
122
|
+
"fingerprint": "e3cc5489d3e3e81f35347b7530d5743fbac8491ab612ceec0d034e22656269ed",
|
|
123
|
+
"check_name": "SQL",
|
|
124
|
+
"message": "Possible SQL injection",
|
|
125
|
+
"file": "app/models/good_job/job/lockable.rb",
|
|
126
|
+
"line": 102,
|
|
127
|
+
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
|
|
128
|
+
"code": "unscoped.find_by_sql([\"WITH candidate AS #{(\"MATERIALIZED \" or \"\")}(#{select(:id).lock(\"FOR NO KEY UPDATE SKIP LOCKED\").to_sql})\\nUPDATE #{quoted_table_name}\\nSET locked_by_id = ?,\\n locked_at = ?,\\n lock_type = ?\\nFROM (\\n SELECT id FROM candidate\\n WHERE pg_try_advisory_lock(#{\"('x' || substr(md5(#{_quoted_table_name_string} || '-' || id::text), 1, 16))::bit(64)::bigint\"})\\n) AS locked\\nWHERE #{quoted_table_name}.id = locked.id\\nRETURNING #{quoted_table_name}.*\\n\".squish, locked_by_id, locked_at, lock_types[lock_type.to_s]])",
|
|
129
|
+
"render_path": null,
|
|
130
|
+
"location": {
|
|
131
|
+
"type": "method",
|
|
132
|
+
"class": "GoodJob::Job",
|
|
133
|
+
"method": "with_hybrid_lock_claim"
|
|
134
|
+
},
|
|
135
|
+
"user_input": "_quoted_table_name_string",
|
|
136
|
+
"confidence": "Weak",
|
|
137
|
+
"cwe_id": [
|
|
138
|
+
89
|
|
139
|
+
],
|
|
140
|
+
"note": "Table name is quoted via quoted_table_name (Rails); advisory lock expression uses _quoted_table_name_string (escaped); candidate SQL is Rails-generated; values are parameterized bindings."
|
|
135
141
|
}
|
|
136
142
|
],
|
|
137
143
|
"brakeman_version": "8.0.4"
|
|
@@ -80,25 +80,26 @@ class CreateGoodJobs < ActiveRecord::Migration<%= migration_version %>
|
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
add_index :good_jobs, :scheduled_at, where: "(finished_at IS NULL)", name: :index_good_jobs_on_scheduled_at
|
|
83
|
-
add_index :good_jobs, [:queue_name, :scheduled_at], where: "(finished_at IS NULL)", name: :index_good_jobs_on_queue_name_and_scheduled_at
|
|
84
83
|
add_index :good_jobs, [:active_job_id, :created_at], name: :index_good_jobs_on_active_job_id_and_created_at
|
|
85
84
|
add_index :good_jobs, :concurrency_key, where: "(finished_at IS NULL)", name: :index_good_jobs_on_concurrency_key_when_unfinished
|
|
86
85
|
add_index :good_jobs, [:concurrency_key, :created_at], name: :index_good_jobs_on_concurrency_key_and_created_at
|
|
87
86
|
add_index :good_jobs, [:cron_key, :created_at], where: "(cron_key IS NOT NULL)", name: :index_good_jobs_on_cron_key_and_created_at_cond
|
|
88
|
-
add_index :good_jobs, [:cron_key, :cron_at],
|
|
87
|
+
add_index :good_jobs, [:cron_key, :cron_at], order: { cron_at: "DESC NULLS LAST" },
|
|
88
|
+
where: "(cron_key IS NOT NULL)", unique: true, name: :index_good_jobs_on_cron_key_and_cron_at_cond
|
|
89
89
|
add_index :good_jobs, [:finished_at], where: "finished_at IS NOT NULL", name: :index_good_jobs_jobs_on_finished_at_only
|
|
90
|
-
add_index :good_jobs, [:priority, :created_at], order: { priority: "DESC NULLS LAST", created_at: :asc },
|
|
91
|
-
where: "finished_at IS NULL", name: :index_good_jobs_jobs_on_priority_created_at_when_unfinished
|
|
92
90
|
add_index :good_jobs, [:priority, :created_at], order: { priority: "ASC NULLS LAST", created_at: :asc },
|
|
93
91
|
where: "finished_at IS NULL", name: :index_good_job_jobs_for_candidate_lookup
|
|
94
92
|
add_index :good_jobs, [:batch_id], where: "batch_id IS NOT NULL"
|
|
95
93
|
add_index :good_jobs, [:batch_callback_id], where: "batch_callback_id IS NOT NULL"
|
|
94
|
+
add_index :good_jobs, :batch_id,
|
|
95
|
+
where: "batch_id IS NOT NULL AND finished_at IS NULL", name: :index_good_jobs_on_batch_id_unfinished
|
|
96
|
+
add_index :good_jobs, :batch_callback_id,
|
|
97
|
+
where: "batch_callback_id IS NOT NULL AND finished_at IS NULL", name: :index_good_jobs_on_batch_callback_id_unfinished
|
|
96
98
|
add_index :good_jobs, :job_class, name: :index_good_jobs_on_job_class
|
|
97
99
|
add_index :good_jobs, :labels, using: :gin, where: "(labels IS NOT NULL)", name: :index_good_jobs_on_labels
|
|
98
100
|
|
|
99
101
|
add_index :good_job_executions, [:active_job_id, :created_at], name: :index_good_job_executions_on_active_job_id_and_created_at
|
|
100
|
-
add_index :
|
|
101
|
-
where: "finished_at IS NULL AND locked_by_id IS NULL", name: :index_good_jobs_on_priority_scheduled_at_unfinished_unlocked
|
|
102
|
+
add_index :good_job_executions, :scheduled_at, name: :index_good_job_executions_on_scheduled_at
|
|
102
103
|
add_index :good_jobs, [:priority, :scheduled_at, :id],
|
|
103
104
|
where: "finished_at IS NULL", name: "index_good_jobs_on_priority_scheduled_at_unfinished"
|
|
104
105
|
add_index :good_jobs, [:queue_name, :scheduled_at, :id],
|
|
@@ -107,11 +108,24 @@ class CreateGoodJobs < ActiveRecord::Migration<%= migration_version %>
|
|
|
107
108
|
add_index :good_jobs, :locked_by_id,
|
|
108
109
|
where: "locked_by_id IS NOT NULL", name: "index_good_jobs_on_locked_by_id"
|
|
109
110
|
add_index :good_job_executions, [:process_id, :created_at], name: :index_good_job_executions_on_process_id_and_created_at
|
|
111
|
+
add_index :good_job_batches, :finished_at,
|
|
112
|
+
where: "finished_at IS NOT NULL", name: :index_good_job_batches_on_finished_at
|
|
113
|
+
add_index :good_job_batches, [:created_at, :id],
|
|
114
|
+
order: { created_at: :desc, id: :desc }, name: :index_good_job_batches_on_created_at_and_id
|
|
110
115
|
add_index :good_jobs, [:priority, :scheduled_at, :id],
|
|
111
116
|
name: :index_good_jobs_for_candidate_dequeue_unlocked,
|
|
112
117
|
order: { priority: "ASC NULLS LAST", scheduled_at: :asc, id: :asc },
|
|
113
118
|
where: "finished_at IS NULL AND locked_by_id IS NULL"
|
|
114
119
|
|
|
120
|
+
add_index :good_jobs, [:queue_name, :priority, :created_at],
|
|
121
|
+
name: :index_good_jobs_dequeue_by_queue,
|
|
122
|
+
order: { priority: "ASC NULLS LAST", created_at: :asc },
|
|
123
|
+
where: "finished_at IS NULL AND locked_by_id IS NULL"
|
|
124
|
+
add_index :good_jobs, [:queue_name, :priority, :scheduled_at],
|
|
125
|
+
name: :index_good_jobs_dequeue_by_queue_scheduled_at,
|
|
126
|
+
order: { priority: "ASC NULLS LAST", scheduled_at: :asc },
|
|
127
|
+
where: "finished_at IS NULL AND locked_by_id IS NULL"
|
|
128
|
+
|
|
115
129
|
add_index :good_jobs, :queue_name, name: :index_good_jobs_on_queue_name
|
|
116
130
|
add_index :good_jobs, :created_at, name: :index_good_jobs_on_created_at
|
|
117
131
|
add_index :good_jobs, :finished_at,
|
|
@@ -79,23 +79,24 @@ class CreateGoodJobs < ActiveRecord::Migration<%= migration_version %>
|
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
add_index :good_jobs, :scheduled_at, where: "(finished_at IS NULL)", name: :index_good_jobs_on_scheduled_at
|
|
82
|
-
add_index :good_jobs, [:queue_name, :scheduled_at], where: "(finished_at IS NULL)", name: :index_good_jobs_on_queue_name_and_scheduled_at
|
|
83
82
|
add_index :good_jobs, [:active_job_id, :created_at], name: :index_good_jobs_on_active_job_id_and_created_at
|
|
84
83
|
add_index :good_jobs, :concurrency_key, where: "(finished_at IS NULL)", name: :index_good_jobs_on_concurrency_key_when_unfinished
|
|
85
84
|
add_index :good_jobs, [:cron_key, :created_at], where: "(cron_key IS NOT NULL)", name: :index_good_jobs_on_cron_key_and_created_at_cond
|
|
86
|
-
add_index :good_jobs, [:cron_key, :cron_at],
|
|
85
|
+
add_index :good_jobs, [:cron_key, :cron_at], order: { cron_at: "DESC NULLS LAST" },
|
|
86
|
+
where: "(cron_key IS NOT NULL)", unique: true, name: :index_good_jobs_on_cron_key_and_cron_at_cond
|
|
87
87
|
add_index :good_jobs, [:finished_at], where: "retried_good_job_id IS NULL AND finished_at IS NOT NULL", name: :index_good_jobs_jobs_on_finished_at
|
|
88
|
-
add_index :good_jobs, [:priority, :created_at], order: { priority: "DESC NULLS LAST", created_at: :asc },
|
|
89
|
-
where: "finished_at IS NULL", name: :index_good_jobs_jobs_on_priority_created_at_when_unfinished
|
|
90
88
|
add_index :good_jobs, [:priority, :created_at], order: { priority: "ASC NULLS LAST", created_at: :asc },
|
|
91
89
|
where: "finished_at IS NULL", name: :index_good_job_jobs_for_candidate_lookup
|
|
92
90
|
add_index :good_jobs, [:batch_id], where: "batch_id IS NOT NULL"
|
|
93
91
|
add_index :good_jobs, [:batch_callback_id], where: "batch_callback_id IS NOT NULL"
|
|
92
|
+
add_index :good_jobs, :batch_id,
|
|
93
|
+
where: "batch_id IS NOT NULL AND finished_at IS NULL", name: :index_good_jobs_on_batch_id_unfinished
|
|
94
|
+
add_index :good_jobs, :batch_callback_id,
|
|
95
|
+
where: "batch_callback_id IS NOT NULL AND finished_at IS NULL", name: :index_good_jobs_on_batch_callback_id_unfinished
|
|
94
96
|
add_index :good_jobs, :labels, using: :gin, where: "(labels IS NOT NULL)", name: :index_good_jobs_on_labels
|
|
95
97
|
|
|
96
98
|
add_index :good_job_executions, [:active_job_id, :created_at], name: :index_good_job_executions_on_active_job_id_and_created_at
|
|
97
|
-
add_index :
|
|
98
|
-
where: "finished_at IS NULL AND locked_by_id IS NULL", name: :index_good_jobs_on_priority_scheduled_at_unfinished_unlocked
|
|
99
|
+
add_index :good_job_executions, :scheduled_at, name: :index_good_job_executions_on_scheduled_at
|
|
99
100
|
add_index :good_jobs, :locked_by_id,
|
|
100
101
|
where: "locked_by_id IS NOT NULL", name: "index_good_jobs_on_locked_by_id"
|
|
101
102
|
add_index :good_jobs, [:priority, :scheduled_at, :id],
|
|
@@ -103,5 +104,9 @@ class CreateGoodJobs < ActiveRecord::Migration<%= migration_version %>
|
|
|
103
104
|
order: { priority: "ASC NULLS LAST", scheduled_at: :asc, id: :asc },
|
|
104
105
|
where: "finished_at IS NULL AND locked_by_id IS NULL"
|
|
105
106
|
add_index :good_job_executions, [:process_id, :created_at], name: :index_good_job_executions_on_process_id_and_created_at
|
|
107
|
+
add_index :good_job_batches, :finished_at,
|
|
108
|
+
where: "finished_at IS NOT NULL", name: :index_good_job_batches_on_finished_at
|
|
109
|
+
add_index :good_job_batches, [:created_at, :id],
|
|
110
|
+
order: { created_at: :desc, id: :desc }, name: :index_good_job_batches_on_created_at_and_id
|
|
106
111
|
end
|
|
107
112
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddIndexGoodJobsDequeueByQueue < ActiveRecord::Migration<%= migration_version %>
|
|
4
|
+
disable_ddl_transaction!
|
|
5
|
+
|
|
6
|
+
def change
|
|
7
|
+
add_index :good_jobs, [:queue_name, :priority, :created_at],
|
|
8
|
+
name: :index_good_jobs_dequeue_by_queue,
|
|
9
|
+
order: { priority: "ASC NULLS LAST", created_at: :asc },
|
|
10
|
+
where: "finished_at IS NULL AND locked_by_id IS NULL",
|
|
11
|
+
algorithm: :concurrently,
|
|
12
|
+
if_not_exists: true
|
|
13
|
+
|
|
14
|
+
add_index :good_jobs, [:queue_name, :priority, :scheduled_at],
|
|
15
|
+
name: :index_good_jobs_dequeue_by_queue_scheduled_at,
|
|
16
|
+
order: { priority: "ASC NULLS LAST", scheduled_at: :asc },
|
|
17
|
+
where: "finished_at IS NULL AND locked_by_id IS NULL",
|
|
18
|
+
algorithm: :concurrently,
|
|
19
|
+
if_not_exists: true
|
|
20
|
+
end
|
|
21
|
+
end
|