good_job 4.20.0 → 4.24.1
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 +170 -2
- data/README.md +148 -26
- data/app/charts/good_job/base_chart.rb +1 -0
- data/app/charts/good_job/latency_metric/base.rb +22 -0
- data/app/charts/good_job/latency_metric/execution.rb +27 -0
- data/app/charts/good_job/latency_metric/queue.rb +23 -0
- data/app/charts/good_job/latency_metric/total.rb +23 -0
- data/app/charts/good_job/latency_metric.rb +43 -0
- data/app/charts/good_job/performance_index_chart.rb +11 -8
- data/app/charts/good_job/performance_range.rb +5 -3
- data/app/charts/good_job/performance_show_chart.rb +13 -4
- data/app/charts/good_job/scheduled_by_queue_chart.rb +2 -1
- data/app/controllers/good_job/application_controller.rb +7 -4
- data/app/controllers/good_job/batches_controller.rb +4 -1
- data/app/controllers/good_job/cleaner_controller.rb +1 -0
- data/app/controllers/good_job/cron_entries_controller.rb +1 -0
- data/app/controllers/good_job/executions_controller.rb +12 -0
- data/app/controllers/good_job/frontends_controller.rb +9 -4
- data/app/controllers/good_job/jobs_controller.rb +2 -0
- data/app/controllers/good_job/metrics_controller.rb +4 -1
- data/app/controllers/good_job/pauses_controller.rb +1 -0
- data/app/controllers/good_job/performance_controller.rb +13 -18
- data/app/controllers/good_job/processes_controller.rb +1 -0
- data/app/filters/good_job/base_filter.rb +10 -1
- data/app/filters/good_job/batches_filter.rb +1 -0
- data/app/filters/good_job/jobs_filter.rb +12 -1
- data/app/frontend/good_job/icons.svg +12 -0
- data/app/frontend/good_job/style.css +16 -0
- data/app/frontend/good_job/vendor/bootstrap/bootstrap.bundle.min.js +3 -3
- data/app/frontend/good_job/vendor/bootstrap/bootstrap.min.css +3 -3
- data/app/frontend/good_job/vendor/chartjs/chart.min.js +10 -15
- data/app/frontend/good_job/vendor/es_module_shims.js +1486 -1
- data/app/frontend/good_job/vendor/stimulus.js +2314 -2159
- data/app/frontend/good_job/vendor/turbo.js +6 -6
- data/app/helpers/good_job/application_helper.rb +44 -0
- data/app/helpers/good_job/icons_helper.rb +1 -0
- data/app/models/concerns/good_job/advisory_lockable.rb +67 -20
- data/app/models/concerns/good_job/error_events.rb +1 -0
- data/app/models/concerns/good_job/filterable.rb +1 -0
- data/app/models/concerns/good_job/filtered_arguments.rb +28 -0
- data/app/models/concerns/good_job/reportable.rb +1 -0
- data/app/models/good_job/active_record_parent_class.rb +1 -0
- data/app/models/good_job/base_record.rb +1 -0
- data/app/models/good_job/batch.rb +19 -20
- data/app/models/good_job/batch_record.rb +10 -9
- data/app/models/good_job/concurrency_claim.rb +183 -0
- data/app/models/good_job/cron_entry.rb +37 -21
- data/app/models/good_job/discrete_execution.rb +1 -0
- data/app/models/good_job/execution.rb +8 -3
- data/app/models/good_job/execution_result.rb +1 -0
- data/app/models/good_job/i18n_config.rb +1 -0
- data/app/models/good_job/job/lockable.rb +68 -42
- data/app/models/good_job/job.rb +44 -16
- data/app/models/good_job/process.rb +15 -11
- data/app/models/good_job/setting.rb +5 -2
- data/app/views/good_job/batches/_jobs.erb +9 -10
- data/app/views/good_job/cron_entries/index.html.erb +1 -1
- data/app/views/good_job/executions/show.html.erb +11 -0
- data/app/views/good_job/jobs/_executions.erb +2 -6
- data/app/views/good_job/jobs/_table.erb +10 -11
- data/app/views/good_job/jobs/show.html.erb +25 -1
- data/app/views/good_job/performance/_latency.erb +6 -0
- data/app/views/good_job/performance/_metric.erb +8 -0
- data/app/views/good_job/performance/_range.erb +2 -2
- data/app/views/good_job/performance/index.html.erb +39 -49
- data/app/views/good_job/performance/show.html.erb +3 -1
- data/app/views/good_job/shared/_chart_container.erb +3 -1
- data/app/views/layouts/good_job/application.html.erb +2 -0
- data/config/brakeman.ignore +9 -9
- data/config/locales/az.yml +26 -4
- data/config/locales/de.yml +26 -4
- data/config/locales/en.yml +26 -4
- data/config/locales/es.yml +26 -4
- data/config/locales/fr.yml +26 -4
- data/config/locales/it.yml +26 -4
- data/config/locales/ja.yml +26 -4
- data/config/locales/ko.yml +26 -4
- data/config/locales/nl.yml +26 -4
- data/config/locales/pt-BR.yml +49 -25
- data/config/locales/ru.yml +42 -20
- data/config/locales/tr.yml +26 -4
- data/config/locales/uk.yml +40 -18
- data/config/locales/zh-CN.yml +26 -4
- data/config/routes.rb +3 -1
- data/lib/active_job/queue_adapters/good_job_adapter.rb +1 -0
- data/lib/generators/good_job/install_generator.rb +1 -0
- data/lib/generators/good_job/templates/install/migrations/create_good_jobs.rb.erb +15 -0
- data/lib/generators/good_job/templates/update/migrations/01_create_good_jobs.rb.erb +1 -0
- data/lib/generators/good_job/templates/update/migrations/21_create_good_job_concurrency_claims.rb.erb +27 -0
- data/lib/generators/good_job/templates/update/migrations/22_add_index_good_job_executions_on_created_at.rb.erb +12 -0
- data/lib/generators/good_job/update_generator.rb +1 -0
- data/lib/good_job/active_job_extensions/batches.rb +1 -0
- data/lib/good_job/active_job_extensions/concurrency.rb +163 -43
- data/lib/good_job/active_job_extensions/interrupt_errors.rb +1 -0
- data/lib/good_job/active_job_extensions/labels.rb +28 -7
- data/lib/good_job/active_job_extensions/notify_options.rb +1 -0
- data/lib/good_job/adapter/inline_buffer.rb +1 -0
- data/lib/good_job/adapter.rb +94 -58
- data/lib/good_job/bulk.rb +2 -2
- data/lib/good_job/callable.rb +1 -0
- data/lib/good_job/capsule.rb +12 -8
- data/lib/good_job/capsule_tracker.rb +114 -67
- data/lib/good_job/cleanup_tracker.rb +1 -0
- data/lib/good_job/cli.rb +10 -1
- data/lib/good_job/configuration/validator.rb +1 -0
- data/lib/good_job/configuration.rb +72 -8
- data/lib/good_job/cron_manager.rb +7 -6
- data/lib/good_job/current_thread.rb +2 -1
- data/lib/good_job/daemon.rb +1 -0
- data/lib/good_job/dependencies.rb +1 -0
- data/lib/good_job/engine.rb +7 -1
- data/lib/good_job/instance_registry.rb +48 -0
- data/lib/good_job/interrupt_error.rb +1 -0
- data/lib/good_job/interrupted_error.rb +1 -0
- data/lib/good_job/job_performer/metrics.rb +1 -0
- data/lib/good_job/job_performer.rb +7 -5
- data/lib/good_job/lifecycle_hooks.rb +1 -0
- data/lib/good_job/log_subscriber.rb +10 -0
- data/lib/good_job/multi_scheduler.rb +2 -1
- data/lib/good_job/notifier/process_heartbeat.rb +31 -6
- data/lib/good_job/notifier.rb +6 -6
- data/lib/good_job/poller.rb +5 -6
- data/lib/good_job/probe_server/cluster_healthcheck_middleware.rb +1 -0
- data/lib/good_job/probe_server/healthcheck_middleware.rb +1 -0
- data/lib/good_job/probe_server/not_found_app.rb +1 -0
- data/lib/good_job/probe_server/simple_handler.rb +51 -11
- data/lib/good_job/probe_server/webrick_handler.rb +3 -0
- data/lib/good_job/probe_server.rb +3 -0
- data/lib/good_job/ractors.rb +100 -0
- data/lib/good_job/safe_state.rb +1 -0
- data/lib/good_job/scheduler.rb +6 -8
- data/lib/good_job/sd_notify.rb +1 -0
- data/lib/good_job/shared_executor.rb +5 -6
- data/lib/good_job/subprocess.rb +1 -0
- data/lib/good_job/supervisor.rb +22 -15
- data/lib/good_job/systemd_service.rb +2 -0
- data/lib/good_job/thread_status.rb +1 -0
- data/lib/good_job/version.rb +2 -1
- data/lib/good_job.rb +129 -41
- metadata +31 -3
- data/lib/good_job/overridable_connection.rb +0 -38
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6973f64ae5640b5157d72d26438c7bb155072f07a98829461324b548f0092a19
|
|
4
|
+
data.tar.gz: 5b9dbd764aa128a9b54797e1f0abe0912942a391d656070ca86f4302041fed33
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e9a5a767e63afdf414c61d9437486d2670d14bc37f006963a5f4c2f13a6b23805fb9fb70e51068d67ee6f7f3133463ba201798a0464260fc0cba09fa319cbeb3
|
|
7
|
+
data.tar.gz: 49c3e706bc73a2715993d38a0713ec736864c7fe9a434461db87092b0ba20c0900043e324887d17c5d40e14d0dd3df0c304caae0c77f912e952a8b66c65beff8
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,173 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [v4.24.1](https://github.com/bensheldon/good_job/tree/v4.24.1) (2026-11-03)
|
|
4
|
+
|
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v4.24.0...v4.24.1)
|
|
6
|
+
|
|
7
|
+
**Fixed bugs:**
|
|
8
|
+
|
|
9
|
+
- Look up cron settings once, not per entry, in the primary nav \(36 ms → 0.75 ms at 100 entries\) [\#1906](https://github.com/bensheldon/good_job/pull/1906) ([czj](https://github.com/czj))
|
|
10
|
+
- Sort dashboard chart legends alphabetically [\#1903](https://github.com/bensheldon/good_job/pull/1903) ([6temes](https://github.com/6temes))
|
|
11
|
+
|
|
12
|
+
**Closed issues:**
|
|
13
|
+
|
|
14
|
+
- Unique constraint violation messages in postgres error logs [\#1536](https://github.com/bensheldon/good_job/issues/1536)
|
|
15
|
+
|
|
16
|
+
**Merged pull requests:**
|
|
17
|
+
|
|
18
|
+
- Document expected duplicate key errors from cron in Postgres logs [\#1908](https://github.com/bensheldon/good_job/pull/1908) ([rafayqayyum](https://github.com/rafayqayyum))
|
|
19
|
+
- Bump github/codeql-action from 4.38.2 to 4.39.1 [\#1907](https://github.com/bensheldon/good_job/pull/1907) ([dependabot[bot]](https://github.com/apps/dependabot))
|
|
20
|
+
|
|
21
|
+
## [v4.24.0](https://github.com/bensheldon/good_job/tree/v4.24.0) (2026-10-29)
|
|
22
|
+
|
|
23
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v4.23.1...v4.24.0)
|
|
24
|
+
|
|
25
|
+
**Implemented enhancements:**
|
|
26
|
+
|
|
27
|
+
- Add a separate retention period for discarded jobs [\#1897](https://github.com/bensheldon/good_job/pull/1897) ([IslamElsayed](https://github.com/IslamElsayed))
|
|
28
|
+
|
|
29
|
+
**Fixed bugs:**
|
|
30
|
+
|
|
31
|
+
- Prevent idle or malformed connections from stalling or stopping the probe server [\#1902](https://github.com/bensheldon/good_job/pull/1902) ([bensheldon](https://github.com/bensheldon))
|
|
32
|
+
- Validate advisory lock SQL identifiers and harden dashboard filter params [\#1901](https://github.com/bensheldon/good_job/pull/1901) ([bensheldon](https://github.com/bensheldon))
|
|
33
|
+
- Don't retain or report schedulers replaced by a capsule restart [\#1894](https://github.com/bensheldon/good_job/pull/1894) ([bensheldon](https://github.com/bensheldon))
|
|
34
|
+
|
|
35
|
+
**Closed issues:**
|
|
36
|
+
|
|
37
|
+
- \[feature request\] Cleanup older discarded jobs [\#1597](https://github.com/bensheldon/good_job/issues/1597)
|
|
38
|
+
|
|
39
|
+
**Merged pull requests:**
|
|
40
|
+
|
|
41
|
+
- Restructure code so it type-checks under Sorbet without runtime assertions [\#1899](https://github.com/bensheldon/good_job/pull/1899) ([bensheldon](https://github.com/bensheldon))
|
|
42
|
+
- Upgrade development/demo Ruby to 4.0.9 and update dependencies [\#1898](https://github.com/bensheldon/good_job/pull/1898) ([bensheldon](https://github.com/bensheldon))
|
|
43
|
+
- Fix flaky JRuby FrozenError by preloading record schemas in specs [\#1896](https://github.com/bensheldon/good_job/pull/1896) ([bensheldon](https://github.com/bensheldon))
|
|
44
|
+
- Demo: shorten database connection lifetimes [\#1895](https://github.com/bensheldon/good_job/pull/1895) ([bensheldon](https://github.com/bensheldon))
|
|
45
|
+
|
|
46
|
+
## [v4.23.1](https://github.com/bensheldon/good_job/tree/v4.23.1) (2026-10-27)
|
|
47
|
+
|
|
48
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v4.23.0...v4.23.1)
|
|
49
|
+
|
|
50
|
+
**Fixed bugs:**
|
|
51
|
+
|
|
52
|
+
- Release abandoned job claims after database failures [\#1887](https://github.com/bensheldon/good_job/pull/1887) ([ollym](https://github.com/ollym))
|
|
53
|
+
|
|
54
|
+
## [v4.23.0](https://github.com/bensheldon/good_job/tree/v4.23.0) (2026-10-27)
|
|
55
|
+
|
|
56
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v4.22.1...v4.23.0)
|
|
57
|
+
|
|
58
|
+
**Implemented enhancements:**
|
|
59
|
+
|
|
60
|
+
- Filter sensitive job arguments in the Dashboard [\#1881](https://github.com/bensheldon/good_job/pull/1881) ([bogdan](https://github.com/bogdan))
|
|
61
|
+
|
|
62
|
+
**Fixed bugs:**
|
|
63
|
+
|
|
64
|
+
- Respect enable\_listen\_notify when promoting concurrency claims [\#1892](https://github.com/bensheldon/good_job/pull/1892) ([eidam](https://github.com/eidam))
|
|
65
|
+
- Downgrade lock\_type before releasing advisory lock [\#1883](https://github.com/bensheldon/good_job/pull/1883) ([jmsanders](https://github.com/jmsanders))
|
|
66
|
+
|
|
67
|
+
**Closed issues:**
|
|
68
|
+
|
|
69
|
+
- ConcurrencyClaim sends NOTIFY even when enable\_listen\_notify is false [\#1891](https://github.com/bensheldon/good_job/issues/1891)
|
|
70
|
+
|
|
71
|
+
**Merged pull requests:**
|
|
72
|
+
|
|
73
|
+
- Keep JRuby platform in Gemfile.lock when Bundler re-resolves [\#1893](https://github.com/bensheldon/good_job/pull/1893) ([bensheldon](https://github.com/bensheldon))
|
|
74
|
+
- Make integration specs robust under database load; fix mixed lock strategy specs [\#1890](https://github.com/bensheldon/good_job/pull/1890) ([bensheldon](https://github.com/bensheldon))
|
|
75
|
+
- Pin CI test runners to ubuntu-24.04 [\#1888](https://github.com/bensheldon/good_job/pull/1888) ([bensheldon](https://github.com/bensheldon))
|
|
76
|
+
- Require the concurrent-ruby classes GoodJob uses [\#1885](https://github.com/bensheldon/good_job/pull/1885) ([czj](https://github.com/czj))
|
|
77
|
+
- Add index\_good\_job\_executions\_on\_created\_at [\#1884](https://github.com/bensheldon/good_job/pull/1884) ([clinejj](https://github.com/clinejj))
|
|
78
|
+
- Demo: cap YJIT memory and shrink the Rack::Attack store [\#1880](https://github.com/bensheldon/good_job/pull/1880) ([bensheldon](https://github.com/bensheldon))
|
|
79
|
+
- Demo: enable rbtrace in Puma workers for heap dumps; add sheap [\#1879](https://github.com/bensheldon/good_job/pull/1879) ([bensheldon](https://github.com/bensheldon))
|
|
80
|
+
- Demo: warm up Puma before forking, add Thruster, raise DB pool [\#1878](https://github.com/bensheldon/good_job/pull/1878) ([bensheldon](https://github.com/bensheldon))
|
|
81
|
+
- Return 404 for GET /jobs/mass\_update instead of redirecting [\#1877](https://github.com/bensheldon/good_job/pull/1877) ([bensheldon](https://github.com/bensheldon))
|
|
82
|
+
- Load execution full traces and serialized params on demand [\#1876](https://github.com/bensheldon/good_job/pull/1876) ([bensheldon](https://github.com/bensheldon))
|
|
83
|
+
- Add specs for the demo's Rack::Attack rules [\#1875](https://github.com/bensheldon/good_job/pull/1875) ([bensheldon](https://github.com/bensheldon))
|
|
84
|
+
- Fix demo boot: don't require rails/all in bin/rails [\#1874](https://github.com/bensheldon/good_job/pull/1874) ([bensheldon](https://github.com/bensheldon))
|
|
85
|
+
- Reduce demo app memory and crawler load [\#1873](https://github.com/bensheldon/good_job/pull/1873) ([bensheldon](https://github.com/bensheldon))
|
|
86
|
+
- Fix JRuby flake from inspect in wait\_until predicate expectations [\#1872](https://github.com/bensheldon/good_job/pull/1872) ([bensheldon](https://github.com/bensheldon))
|
|
87
|
+
- Remove Adapter::RUNTIMES so adapters can be garbage collected [\#1870](https://github.com/bensheldon/good_job/pull/1870) ([bensheldon](https://github.com/bensheldon))
|
|
88
|
+
- Add GoodJob.prune to release shut down capsules, schedulers, and notifiers [\#1869](https://github.com/bensheldon/good_job/pull/1869) ([bensheldon](https://github.com/bensheldon))
|
|
89
|
+
- Use resolve\_\[property\] naming for CronEntry configuration resolvers [\#1868](https://github.com/bensheldon/good_job/pull/1868) ([bensheldon](https://github.com/bensheldon))
|
|
90
|
+
- Fix inconsistencies in the Brazilian Portuguese locale [\#1867](https://github.com/bensheldon/good_job/pull/1867) ([tiagoamaro](https://github.com/tiagoamaro))
|
|
91
|
+
|
|
92
|
+
## [v4.22.1](https://github.com/bensheldon/good_job/tree/v4.22.1) (2026-10-13)
|
|
93
|
+
|
|
94
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v4.22.0...v4.22.1)
|
|
95
|
+
|
|
96
|
+
**Implemented enhancements:**
|
|
97
|
+
|
|
98
|
+
- Show full job labels under the job name in dashboard tables [\#1862](https://github.com/bensheldon/good_job/pull/1862) ([olivier-thatch](https://github.com/olivier-thatch))
|
|
99
|
+
|
|
100
|
+
**Fixed bugs:**
|
|
101
|
+
|
|
102
|
+
- Keep the process id when refresh recreates a deleted process record [\#1866](https://github.com/bensheldon/good_job/pull/1866) ([wivern-co-uk](https://github.com/wivern-co-uk))
|
|
103
|
+
- Don't apply a concurrency lock when a rule's label resolves to blank [\#1864](https://github.com/bensheldon/good_job/pull/1864) ([bensheldon](https://github.com/bensheldon))
|
|
104
|
+
|
|
105
|
+
**Merged pull requests:**
|
|
106
|
+
|
|
107
|
+
- Support enqueuing jobs from non-main Ractors [\#1863](https://github.com/bensheldon/good_job/pull/1863) ([bensheldon](https://github.com/bensheldon))
|
|
108
|
+
|
|
109
|
+
## [v4.22.0](https://github.com/bensheldon/good_job/tree/v4.22.0) (2026-10-09)
|
|
110
|
+
|
|
111
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v4.21.1...v4.22.0)
|
|
112
|
+
|
|
113
|
+
**Implemented enhancements:**
|
|
114
|
+
|
|
115
|
+
- Rename max\_threads config to threads and deprecate max\_threads [\#1846](https://github.com/bensheldon/good_job/pull/1846) ([bensheldon](https://github.com/bensheldon))
|
|
116
|
+
|
|
117
|
+
**Closed issues:**
|
|
118
|
+
|
|
119
|
+
- CI: json 3.0.2 removed `quirks_mode`, breaking ActiveSupport's JSON encoder on every test job [\#1816](https://github.com/bensheldon/good_job/issues/1816)
|
|
120
|
+
|
|
121
|
+
**Merged pull requests:**
|
|
122
|
+
|
|
123
|
+
- Restore JRuby gems to Gemfile.lock and add a spec guarding it [\#1861](https://github.com/bensheldon/good_job/pull/1861) ([bensheldon](https://github.com/bensheldon))
|
|
124
|
+
- Upgrade vendored frontend libraries \(Turbo, Stimulus, es-module-shims, Bootstrap, Chart.js\) [\#1860](https://github.com/bensheldon/good_job/pull/1860) ([bensheldon](https://github.com/bensheldon))
|
|
125
|
+
- Upgrade dev environment: Ruby 4.0.7, Bundler 4.0.22, and gems [\#1859](https://github.com/bensheldon/good_job/pull/1859) ([bensheldon](https://github.com/bensheldon))
|
|
126
|
+
- Don't crash demo when git is unavailable \(e.g. Heroku\) [\#1858](https://github.com/bensheldon/good_job/pull/1858) ([bensheldon](https://github.com/bensheldon))
|
|
127
|
+
- Add apply\_label: option to good\_job\_concurrency\_rule [\#1857](https://github.com/bensheldon/good_job/pull/1857) ([bensheldon](https://github.com/bensheldon))
|
|
128
|
+
- Deprecate good\_job\_control\_concurrency\_with and key-only concurrency rules in favor of labels [\#1856](https://github.com/bensheldon/good_job/pull/1856) ([bensheldon](https://github.com/bensheldon))
|
|
129
|
+
- Show concurrency claims on the dashboard [\#1855](https://github.com/bensheldon/good_job/pull/1855) ([bensheldon](https://github.com/bensheldon))
|
|
130
|
+
- Allow json 3 for Rails versions that support it; pin json \< 3 only for older Rails [\#1854](https://github.com/bensheldon/good_job/pull/1854) ([bensheldon](https://github.com/bensheldon))
|
|
131
|
+
|
|
132
|
+
## [v4.21.1](https://github.com/bensheldon/good_job/tree/v4.21.1) (2026-10-07)
|
|
133
|
+
|
|
134
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v4.21.0...v4.21.1)
|
|
135
|
+
|
|
136
|
+
**Fixed bugs:**
|
|
137
|
+
|
|
138
|
+
- Fix crash on natural language cron schedules \(v4.21.0 regression\) [\#1853](https://github.com/bensheldon/good_job/pull/1853) ([bensheldon](https://github.com/bensheldon))
|
|
139
|
+
|
|
140
|
+
**Closed issues:**
|
|
141
|
+
|
|
142
|
+
- v4.21.0 regression: undefined method 'next\_time' for an instance of EtOrbi::EoTime [\#1852](https://github.com/bensheldon/good_job/issues/1852)
|
|
143
|
+
|
|
144
|
+
## [v4.21.0](https://github.com/bensheldon/good_job/tree/v4.21.0) (2026-10-06)
|
|
145
|
+
|
|
146
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v4.20.0...v4.21.0)
|
|
147
|
+
|
|
148
|
+
**Implemented enhancements:**
|
|
149
|
+
|
|
150
|
+
- Enqueue jobs with concurrency rules individually in bulk/batch, and support Lambda class-level labels [\#1851](https://github.com/bensheldon/good_job/pull/1851) ([bensheldon](https://github.com/bensheldon))
|
|
151
|
+
|
|
152
|
+
**Closed issues:**
|
|
153
|
+
|
|
154
|
+
- perform\_limit: 1 permits two same-label jobs to execute concurrently [\#1843](https://github.com/bensheldon/good_job/issues/1843)
|
|
155
|
+
|
|
156
|
+
**Merged pull requests:**
|
|
157
|
+
|
|
158
|
+
- Fix: Run the enqueue concurrency check always after the job's other enqueue callbacks [\#1850](https://github.com/bensheldon/good_job/pull/1850) ([bensheldon](https://github.com/bensheldon))
|
|
159
|
+
- Add updated\_at to good\_job\_concurrency\_claims [\#1849](https://github.com/bensheldon/good_job/pull/1849) ([bensheldon](https://github.com/bensheldon))
|
|
160
|
+
- Fix Russian/Ukrainian "one" plurals showing "1" for 21, 31, … and Russian apply\_to\_all text [\#1848](https://github.com/bensheldon/good_job/pull/1848) ([owgreen-dev](https://github.com/owgreen-dev))
|
|
161
|
+
- Deprecate combining label: and key: in concurrency rules [\#1847](https://github.com/bensheldon/good_job/pull/1847) ([bensheldon](https://github.com/bensheldon))
|
|
162
|
+
- Run specs in parallel with parallel\_tests [\#1845](https://github.com/bensheldon/good_job/pull/1845) ([bensheldon](https://github.com/bensheldon))
|
|
163
|
+
- Fix perform\_limit race and promote waiting jobs when a concurrency claim is released [\#1844](https://github.com/bensheldon/good_job/pull/1844) ([bensheldon](https://github.com/bensheldon))
|
|
164
|
+
- Fix Ukrainian x\_minutes "few" form showing "1" for every count [\#1842](https://github.com/bensheldon/good_job/pull/1842) ([owgreen-dev](https://github.com/owgreen-dev))
|
|
165
|
+
- Fix flaky CronManager graceful restart spec at minute boundary [\#1841](https://github.com/bensheldon/good_job/pull/1841) ([bensheldon](https://github.com/bensheldon))
|
|
166
|
+
- Validate cron entry keys and schedules instead of raising [\#1832](https://github.com/bensheldon/good_job/pull/1832) ([shiaho777](https://github.com/shiaho777))
|
|
167
|
+
- Fix concurrency label matching and document dynamic labels [\#1827](https://github.com/bensheldon/good_job/pull/1827) ([shiaho777](https://github.com/shiaho777))
|
|
168
|
+
- Add queue and total latency to the Performance dashboard [\#1812](https://github.com/bensheldon/good_job/pull/1812) ([dennispaagman](https://github.com/dennispaagman))
|
|
169
|
+
- Remove connection overrides from process heartbeats [\#1763](https://github.com/bensheldon/good_job/pull/1763) ([bensheldon](https://github.com/bensheldon))
|
|
170
|
+
|
|
3
171
|
## [v4.20.0](https://github.com/bensheldon/good_job/tree/v4.20.0) (2026-10-03)
|
|
4
172
|
|
|
5
173
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v4.19.4...v4.20.0)
|
|
@@ -3702,7 +3870,7 @@
|
|
|
3702
3870
|
|
|
3703
3871
|
**Fixed bugs:**
|
|
3704
3872
|
|
|
3705
|
-
- Don’t attempt to enforce concurrency limits with other queue adapters [\#333](https://github.com/bensheldon/good_job/pull/333) ([
|
|
3873
|
+
- Don’t attempt to enforce concurrency limits with other queue adapters [\#333](https://github.com/bensheldon/good_job/pull/333) ([CodyRobbins](https://github.com/CodyRobbins))
|
|
3706
3874
|
|
|
3707
3875
|
## [v1.13.0](https://github.com/bensheldon/good_job/tree/v1.13.0) (2021-08-18)
|
|
3708
3876
|
|
|
@@ -3729,7 +3897,7 @@
|
|
|
3729
3897
|
|
|
3730
3898
|
**Fixed bugs:**
|
|
3731
3899
|
|
|
3732
|
-
- Fixes for race conditions in ActiveJob concurrency extension [\#326](https://github.com/bensheldon/good_job/pull/326) ([
|
|
3900
|
+
- Fixes for race conditions in ActiveJob concurrency extension [\#326](https://github.com/bensheldon/good_job/pull/326) ([CodyRobbins](https://github.com/CodyRobbins))
|
|
3733
3901
|
|
|
3734
3902
|
**Merged pull requests:**
|
|
3735
3903
|
|
data/README.md
CHANGED
|
@@ -44,10 +44,13 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla
|
|
|
44
44
|
- [Dashboard](#dashboard)
|
|
45
45
|
- [API-only Rails applications](#api-only-rails-applications)
|
|
46
46
|
- [Live polling](#live-polling)
|
|
47
|
+
- [Filtering sensitive arguments](#filtering-sensitive-arguments)
|
|
47
48
|
- [Extending dashboard views](#extending-dashboard-views)
|
|
48
49
|
- [Job priority](#job-priority)
|
|
49
50
|
- [Concurrency controls](#concurrency-controls)
|
|
51
|
+
- [Dynamic labels](#dynamic-labels)
|
|
50
52
|
- [How concurrency controls work](#how-concurrency-controls-work)
|
|
53
|
+
- [Migrating from concurrency keys to labels](#migrating-from-concurrency-keys-to-labels)
|
|
51
54
|
- [Cron-style repeating/recurring jobs](#cron-style-repeatingrecurring-jobs)
|
|
52
55
|
- [Bulk enqueue](#bulk-enqueue)
|
|
53
56
|
- [Batches](#batches)
|
|
@@ -197,7 +200,8 @@ Usage:
|
|
|
197
200
|
|
|
198
201
|
Options:
|
|
199
202
|
[--queues=QUEUE_LIST] # Queues or pools to work from. (env var: GOOD_JOB_QUEUES, default: *)
|
|
200
|
-
[--
|
|
203
|
+
[--threads=COUNT] # Default number of threads per pool to use for working jobs. (env var: GOOD_JOB_THREADS, default: 5)
|
|
204
|
+
[--max-threads=COUNT] # (DEPRECATED: use --threads) (env var: GOOD_JOB_MAX_THREADS, default: 5)
|
|
201
205
|
[--poll-interval=SECONDS] # Interval between polls for available jobs in seconds (env var: GOOD_JOB_POLL_INTERVAL, default: 10)
|
|
202
206
|
[--max-cache=COUNT] # Maximum number of scheduled jobs to cache in memory (env var: GOOD_JOB_MAX_CACHE, default: 10000)
|
|
203
207
|
[--shutdown-timeout=SECONDS] # Number of seconds to wait for jobs to finish when shutting down before stopping the thread. (env var: GOOD_JOB_SHUTDOWN_TIMEOUT, default: -1 (forever))
|
|
@@ -232,7 +236,8 @@ Usage:
|
|
|
232
236
|
good_job cleanup_preserved_jobs
|
|
233
237
|
|
|
234
238
|
Options:
|
|
235
|
-
[--before-seconds-ago=SECONDS]
|
|
239
|
+
[--before-seconds-ago=SECONDS] # Destroy records finished more than this many seconds ago (env var: GOOD_JOB_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO, default: 1209600 (14 days))
|
|
240
|
+
[--discarded-before-seconds-ago=SECONDS] # Destroy discarded records finished more than this many seconds ago (env var: GOOD_JOB_CLEANUP_DISCARDED_JOBS_BEFORE_SECONDS_AGO, default: same as --before-seconds-ago)
|
|
236
241
|
|
|
237
242
|
Manually destroys preserved job records.
|
|
238
243
|
|
|
@@ -261,7 +266,7 @@ Rails.application.configure do
|
|
|
261
266
|
config.good_job.on_thread_error = -> (exception) { Rails.error.report(exception) }
|
|
262
267
|
config.good_job.execution_mode = :async
|
|
263
268
|
config.good_job.queues = '*'
|
|
264
|
-
config.good_job.
|
|
269
|
+
config.good_job.threads = 5
|
|
265
270
|
config.good_job.poll_interval = 30 # seconds
|
|
266
271
|
config.good_job.shutdown_timeout = 25 # seconds
|
|
267
272
|
config.good_job.enable_cron = true
|
|
@@ -276,7 +281,7 @@ Rails.application.configure do
|
|
|
276
281
|
on_thread_error: -> (exception) { Rails.error.report(exception) },
|
|
277
282
|
execution_mode: :async,
|
|
278
283
|
queues: '*',
|
|
279
|
-
|
|
284
|
+
threads: 5,
|
|
280
285
|
poll_interval: 30,
|
|
281
286
|
shutdown_timeout: 25,
|
|
282
287
|
enable_cron: true,
|
|
@@ -299,7 +304,7 @@ Available configuration options are:
|
|
|
299
304
|
- `:async` (or `:async_server`) executes jobs in separate threads within the Rails web server process (`bundle exec rails server`). It can be more economical for small workloads because you don’t need a separate machine or environment for running your jobs, but if your web server is under heavy load or your jobs require a lot of resources, you should choose `:external` instead. When not in the Rails web server, jobs will execute in `:external` mode to ensure jobs are not executed within `rails console`, `rails db:migrate`, `rails assets:prepare`, etc.
|
|
300
305
|
- `:async_all` executes jobs in separate threads in _any_ Rails process.
|
|
301
306
|
- `queues` (string) sets queues or pools to execute jobs. You can also set this with the environment variable `GOOD_JOB_QUEUES`.
|
|
302
|
-
- `
|
|
307
|
+
- `threads` (integer) sets the default number of threads per pool to use for working jobs. You can also set this with the environment variable `GOOD_JOB_THREADS`. (Formerly `max_threads` / `GOOD_JOB_MAX_THREADS`, which are deprecated but still honored.)
|
|
303
308
|
- `poll_interval` (integer) sets the number of seconds between polls for jobs when `execution_mode` is set to `:async`. You can also set this with the environment variable `GOOD_JOB_POLL_INTERVAL`. A poll interval of `-1` disables polling completely.
|
|
304
309
|
- production default: 10 seconds (in case of a LISTEN/NOTIFY blip)
|
|
305
310
|
- development default: -1, disabled (because the application is likely being restarted often and won't be running unobserved). You can enable it by setting a `poll_interval`.
|
|
@@ -313,6 +318,7 @@ Available configuration options are:
|
|
|
313
318
|
- `cron` (hash) cron configuration. Defaults to `{}`. You can also set this as a JSON string with the environment variable `GOOD_JOB_CRON`
|
|
314
319
|
- `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`.
|
|
315
320
|
- `cleanup_preserved_jobs_before_seconds_ago` (integer) number of seconds to preserve jobs when using the `$ good_job cleanup_preserved_jobs` CLI command or calling `GoodJob.cleanup_preserved_jobs`. Defaults to `1209600` (14 days). Can also be set with the environment variable `GOOD_JOB_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO`.
|
|
321
|
+
- `cleanup_discarded_jobs_before_seconds_ago` (integer) number of seconds to preserve discarded jobs, when they should be kept longer (or shorter) than other jobs. Has no effect when `cleanup_discarded_jobs` is `false`. Defaults to `nil`, which uses `cleanup_preserved_jobs_before_seconds_ago`. Can also be set with the environment variable `GOOD_JOB_CLEANUP_DISCARDED_JOBS_BEFORE_SECONDS_AGO`.
|
|
316
322
|
- `cleanup_interval_jobs` (integer) Number of jobs a Scheduler will execute before cleaning up preserved jobs. Defaults to `1000`. Disable with `false`. Can also be set with the environment variable `GOOD_JOB_CLEANUP_INTERVAL_JOBS` and disabled with `0`).
|
|
317
323
|
- `cleanup_interval_seconds` (integer) Number of seconds a Scheduler will wait before cleaning up preserved jobs. Defaults to `600` (10 minutes). Disable with `false`. Can also be set with the environment variable `GOOD_JOB_CLEANUP_INTERVAL_SECONDS` and disabled with `0`).
|
|
318
324
|
- `logger` ([Rails Logger](https://api.rubyonrails.org/classes/ActiveSupport/Logger.html)) lets you set a custom logger for GoodJob. It should be an instance of a Rails `Logger` (Default: `Rails.logger`).
|
|
@@ -476,6 +482,15 @@ end
|
|
|
476
482
|
|
|
477
483
|
The Dashboard can be set to automatically refresh by checking "Live Poll" in the Dashboard header, or by setting `?poll=10` with the interval in seconds (default 30 seconds).
|
|
478
484
|
|
|
485
|
+
#### Filtering sensitive arguments
|
|
486
|
+
|
|
487
|
+
The Dashboard hides job arguments the same way Rails hides them from logs:
|
|
488
|
+
|
|
489
|
+
- Hash values whose keys match `Rails.application.config.filter_parameters` (e.g. `password:`) are displayed as `[FILTERED]`.
|
|
490
|
+
- All arguments are displayed as `[FILTERED]` for jobs with `log_arguments` disabled, either for every job with `config.active_job.log_arguments = false` or per job class with `self.log_arguments = false`.
|
|
491
|
+
|
|
492
|
+
Filtering only applies to how arguments are displayed; arguments are still stored unfiltered in the database and are used when the job is performed or retried.
|
|
493
|
+
|
|
479
494
|
#### Extending dashboard views
|
|
480
495
|
|
|
481
496
|
GoodJob exposes some views that are intended to be overridden by placing views in your application:
|
|
@@ -561,9 +576,15 @@ class MyJob < ApplicationJob
|
|
|
561
576
|
# exceeded rule short-circuits the rest.
|
|
562
577
|
good_job_concurrency_rule(
|
|
563
578
|
# A label that scopes this rule. Can be a static String or a Lambda/Proc
|
|
564
|
-
# invoked in the context of the job instance
|
|
565
|
-
# that were enqueued with this label in
|
|
566
|
-
|
|
579
|
+
# invoked in the context of the job instance (see "Dynamic labels" below).
|
|
580
|
+
# The rule only applies to jobs that were enqueued with this label in
|
|
581
|
+
# `good_job_labels`.
|
|
582
|
+
label: "email",
|
|
583
|
+
|
|
584
|
+
# Apply the rule's label to every job of this class (and its subclasses),
|
|
585
|
+
# so the jobs don't need to be enqueued with it. Defaults to false.
|
|
586
|
+
# A Lambda/Proc label that returns nil is not applied.
|
|
587
|
+
apply_label: true,
|
|
567
588
|
|
|
568
589
|
# Maximum number of unfinished jobs with this label to allow.
|
|
569
590
|
# Can be an Integer or Lambda/Proc invoked in the context of the job.
|
|
@@ -595,7 +616,7 @@ class MyJob < ApplicationJob
|
|
|
595
616
|
good_job_concurrency_rule(...)
|
|
596
617
|
good_job_concurrency_rule(...)
|
|
597
618
|
|
|
598
|
-
def perform
|
|
619
|
+
def perform
|
|
599
620
|
# do work
|
|
600
621
|
end
|
|
601
622
|
end
|
|
@@ -604,21 +625,116 @@ end
|
|
|
604
625
|
Jobs must be enqueued with the matching label for rules to take effect:
|
|
605
626
|
|
|
606
627
|
```ruby
|
|
607
|
-
MyJob.set(good_job_labels: [
|
|
628
|
+
MyJob.set(good_job_labels: ["email"]).perform_later
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
Supplying both `label:` and `key:` to `good_job_concurrency_rule` is deprecated. For labelled rules, `key:` is ignored: jobs sharing the label use the same advisory lock for concurrency checks. Remove `key:` from these rules. Rules with `key:` and no `label:` are also deprecated; see [Migrating from concurrency keys to labels](#migrating-from-concurrency-keys-to-labels).
|
|
632
|
+
|
|
633
|
+
#### Dynamic labels
|
|
634
|
+
|
|
635
|
+
A rule's `label:` can also be a Lambda/Proc that is invoked in the context of the job instance, for example to derive the label from job arguments. The lambda resolves the label to check against; the job's `good_job_labels` must still contain it for the rule to apply. Rule labels and job labels are converted to strings and stripped of surrounding whitespace when matched, consistent with how labels are stored.
|
|
636
|
+
|
|
637
|
+
To apply a rule's label to every job of a class, use `apply_label: true`. The label is resolved in the context of the job when the job is initialized, so it is present for checks at both enqueue and perform time, including for jobs enqueued in bulk. Labels passed with `MyJob.set(good_job_labels: [...])` are added to it rather than replacing it:
|
|
638
|
+
|
|
639
|
+
```ruby
|
|
640
|
+
class MyJob < ApplicationJob
|
|
641
|
+
include GoodJob::ActiveJobExtensions::Concurrency
|
|
642
|
+
|
|
643
|
+
good_job_concurrency_rule(
|
|
644
|
+
label: -> { "user-#{arguments.first[:user_id]}" },
|
|
645
|
+
apply_label: true,
|
|
646
|
+
total_limit: 1
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
def perform(user_id:)
|
|
650
|
+
# do work
|
|
651
|
+
end
|
|
652
|
+
end
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
Class-level `good_job_labels` can also be Lambdas/Procs, invoked in the context of the job when it is initialized:
|
|
656
|
+
|
|
657
|
+
```ruby
|
|
658
|
+
class MyJob < ApplicationJob
|
|
659
|
+
include GoodJob::ActiveJobExtensions::Concurrency
|
|
660
|
+
|
|
661
|
+
self.good_job_labels = [-> { "user-#{arguments.first[:user_id]}" }]
|
|
662
|
+
|
|
663
|
+
good_job_concurrency_rule(
|
|
664
|
+
label: -> { "user-#{arguments.first[:user_id]}" },
|
|
665
|
+
total_limit: 1
|
|
666
|
+
)
|
|
667
|
+
|
|
668
|
+
def perform(user_id:)
|
|
669
|
+
# do work
|
|
670
|
+
end
|
|
671
|
+
end
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
Labels can also be applied in a `before_enqueue` callback. They are stored on the job record and checked by the rules:
|
|
675
|
+
|
|
676
|
+
```ruby
|
|
677
|
+
class MyJob < ApplicationJob
|
|
678
|
+
include GoodJob::ActiveJobExtensions::Concurrency
|
|
679
|
+
|
|
680
|
+
before_enqueue do |job|
|
|
681
|
+
job.good_job_labels |= [job.arguments.first[:user_id].to_s]
|
|
682
|
+
end
|
|
683
|
+
|
|
684
|
+
good_job_concurrency_rule(
|
|
685
|
+
label: -> { arguments.first[:user_id].to_s },
|
|
686
|
+
perform_limit: 1
|
|
687
|
+
)
|
|
688
|
+
|
|
689
|
+
def perform(user_id:)
|
|
690
|
+
# do work
|
|
691
|
+
end
|
|
692
|
+
end
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
Rules are checked when a job is enqueued and again when it is performed. The enqueue-time check runs after the job's other `before_enqueue` and `around_enqueue` callbacks, regardless of where they are defined, so labels assigned in those callbacks are present for both checks. Labels can also be passed when enqueuing:
|
|
696
|
+
|
|
697
|
+
```ruby
|
|
698
|
+
MyJob.set(good_job_labels: [user_id.to_s]).perform_later(user_id: user_id)
|
|
608
699
|
```
|
|
609
700
|
|
|
701
|
+
Rules apply across job classes to jobs carrying the resolved label. `total_limit` counts unfinished jobs, `enqueue_limit` excludes claimed/performing jobs, and `perform_limit` counts running jobs. Throttles count enqueued jobs or executions within their time window, including finished ones.
|
|
702
|
+
|
|
610
703
|
#### How concurrency controls work
|
|
611
704
|
|
|
612
705
|
GoodJob's concurrency control strategy for `perform_limit` is "optimistic retry with an incremental backoff". The [code is readable](https://github.com/bensheldon/good_job/blob/main/lib/good_job/active_job_extensions/concurrency.rb).
|
|
613
706
|
|
|
614
707
|
- "Optimistic" meaning that the implementation's performance trade-off assumes that collisions are atypical (e.g. two users enqueue the same job at the same time) rather than regular (e.g. the system enqueues thousands of colliding jobs at the same time). Depending on your concurrency requirements, you may also want to manage concurrency through the number of GoodJob threads and processes that are performing a given queue.
|
|
615
708
|
- "Retry with an incremental backoff" means that when `perform_limit` is exceeded, the job will raise a `GoodJob::ActiveJobExtensions::Concurrency::ConcurrencyExceededError` which is caught by a `retry_on` handler which re-schedules the job to execute in the near future with an incremental backoff.
|
|
616
|
-
-
|
|
709
|
+
- When the `good_job_concurrency_claims` table has been migrated, a job that exceeded `perform_limit` waits on the limit's key, and when a job holding that key finishes, the longest-waiting job is re-scheduled to run immediately rather than waiting for its backoff. The incremental backoff remains as a fallback.
|
|
710
|
+
- First-in-first-out job execution order is not strictly preserved: a waiting job that is re-scheduled can be overtaken by a newly enqueued job, and jobs retried with incremental back-off run in backoff order.
|
|
617
711
|
- For pessimistic usecases that collisions are expected, use number of threads/processes (e.g., `good_job --queues "serial:1;-serial:5"`) to control concurrency. It is also a good idea to use `perform_limit` as backstop.
|
|
618
712
|
|
|
619
|
-
####
|
|
713
|
+
#### Migrating from concurrency keys to labels
|
|
714
|
+
|
|
715
|
+
`good_job_control_concurrency_with`, and `good_job_concurrency_rule` with `key:` and no `label:`, are deprecated and will raise in GoodJob v5, which will also remove the `good_jobs.concurrency_key` column. They scope limits to a concurrency _key_ stored in that column. Replace them with a labelled rule and apply the same value to the job as a label:
|
|
716
|
+
|
|
717
|
+
```ruby
|
|
718
|
+
class MyJob < ApplicationJob
|
|
719
|
+
include GoodJob::ActiveJobExtensions::Concurrency
|
|
720
|
+
|
|
721
|
+
# Before
|
|
722
|
+
good_job_control_concurrency_with(total_limit: 1, key: -> { "MyJob-#{arguments.first}" })
|
|
620
723
|
|
|
621
|
-
|
|
724
|
+
# After
|
|
725
|
+
self.good_job_labels = [-> { "MyJob-#{arguments.first}" }]
|
|
726
|
+
good_job_concurrency_rule(total_limit: 1, label: -> { "MyJob-#{arguments.first}" })
|
|
727
|
+
end
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
- A static key becomes a static label: `self.good_job_labels = ["my-key"]` and `good_job_concurrency_rule(label: "my-key", ...)`.
|
|
731
|
+
- Labels can also be applied in a `before_enqueue` callback; see [Dynamic labels](#dynamic-labels).
|
|
732
|
+
- If you did not supply a `key:`, the job class name was used; use it as the label.
|
|
733
|
+
- If you supplied `key: nil` to disable concurrency control in a subclass: rules are inherited, but a labelled rule only applies to jobs carrying its label, so don't apply the label in that subclass (e.g. with a Lambda label that returns `nil`).
|
|
734
|
+
- `total_limit:`, `enqueue_limit:`, `perform_limit:`, `enqueue_throttle:` and `perform_throttle:` are unchanged.
|
|
735
|
+
- Jobs enqueued before the change do not have the label and are not counted by the new rule, so limits may be exceeded until those jobs finish.
|
|
736
|
+
|
|
737
|
+
The deprecated interface is documented below for reference:
|
|
622
738
|
|
|
623
739
|
```ruby
|
|
624
740
|
class MyJob < ApplicationJob
|
|
@@ -689,6 +805,8 @@ GoodJob can enqueue Active Job jobs on a recurring basis that can be used as a r
|
|
|
689
805
|
|
|
690
806
|
Cron-style jobs can be enequeued by any GoodJob process (e.g., CLI or `:async` execution mode) that has `config.good_job.enable_cron` set to `true`. Enabling cron on multiple processes will not enqueue duplicate jobs; GoodJob's cron uses unique indexes to ensure that only a single job is enqueued for a given time interval. In order for this to work, GoodJob must preserve cron-created job records; these records will be automatically deleted like any other preserved record.
|
|
691
807
|
|
|
808
|
+
When cron is enabled on multiple processes, each process attempts to enqueue every scheduled job and all but one of the inserts fail on the unique index. GoodJob rescues these failures, but PostgreSQL still writes them to its own log as `duplicate key value violates unique constraint "index_good_jobs_on_cron_key_and_cron_at_cond"`. These errors are expected and harmless. If they are too noisy, enable cron on a single process instead, at the cost of no redundancy if that process stops.
|
|
809
|
+
|
|
692
810
|
Cron-format is parsed by the [`fugit`](https://github.com/floraison/fugit) gem, which has support for seconds-level resolution (e.g. `* * * * * *`) and natural language parsing (e.g. `every second`).
|
|
693
811
|
|
|
694
812
|
If you use the [Dashboard](#dashboard) the scheduled tasks can be viewed in the 'cron' menu. In this view you can also disable a task or run/enqueue a task immediately.
|
|
@@ -1111,6 +1229,8 @@ Note that when running jobs in `:inline` execution mode, `GoodJob.current_thread
|
|
|
1111
1229
|
|
|
1112
1230
|
Jobs will be automatically retried if the process is interrupted while performing a job and the job is unable to finish before the timeout or as the result of a `SIGKILL` or power failure. The interrupted execution's error record will show `GoodJob::InterruptedError` to distinguish it from the rescuable `GoodJob::InterruptError` that is raised when the job is retried.
|
|
1113
1231
|
|
|
1232
|
+
With `:skiplocked` or `:hybrid` locking, a worker releases its claim even if recording the job's completion fails. If the database connection is unavailable during cleanup, the worker waits and retries every five seconds until it can release the claim, GoodJob begins shutting down, or the worker is forcibly terminated. If the worker stops retrying, the claim is released when the exited process's record is cleaned up. The job can then be recovered as an interrupted execution, which may run it again; external effects should be idempotent.
|
|
1233
|
+
|
|
1114
1234
|
If you need more control over interrupt-caused retries, include the `GoodJob::ActiveJobExtensions::InterruptErrors` extension in your job class. When an interrupted job is retried, the extension will raise a `GoodJob::InterruptError` exception within the job, which allows you to use Active Job's `retry_on` and `discard_on` to control the behavior of the job.
|
|
1115
1235
|
|
|
1116
1236
|
```ruby
|
|
@@ -1200,7 +1320,7 @@ By default, GoodJob creates a single thread execution pool that will execute job
|
|
|
1200
1320
|
|
|
1201
1321
|
```bash
|
|
1202
1322
|
$ GOOD_JOB_QUEUES="transactional_messages:2;batch_processing:1;-transactional_messages,batch_processing:2;*" \
|
|
1203
|
-
|
|
1323
|
+
GOOD_JOB_THREADS=5 \
|
|
1204
1324
|
bundle exec good_job
|
|
1205
1325
|
```
|
|
1206
1326
|
|
|
@@ -1214,9 +1334,9 @@ By default, GoodJob creates a single thread execution pool that will execute job
|
|
|
1214
1334
|
# Procfile
|
|
1215
1335
|
|
|
1216
1336
|
# Separate process types
|
|
1217
|
-
worker: bundle exec good_job --
|
|
1218
|
-
transactional_worker: bundle exec good_job --queues="transactional_messages" --
|
|
1219
|
-
batch_worker: bundle exec good_job --queues="batch_processing" --
|
|
1337
|
+
worker: bundle exec good_job --threads=5
|
|
1338
|
+
transactional_worker: bundle exec good_job --queues="transactional_messages" --threads=2
|
|
1339
|
+
batch_worker: bundle exec good_job --queues="batch_processing" --threads=1
|
|
1220
1340
|
```
|
|
1221
1341
|
|
|
1222
1342
|
To optimize for CPU performance at the expense of greater memory and system resource usage, while keeping a single process type (and thus a single dyno), combine several processes and wait for them:
|
|
@@ -1225,7 +1345,7 @@ By default, GoodJob creates a single thread execution pool that will execute job
|
|
|
1225
1345
|
# Procfile
|
|
1226
1346
|
|
|
1227
1347
|
# Combined multi-process
|
|
1228
|
-
combined_worker: bundle exec good_job --
|
|
1348
|
+
combined_worker: bundle exec good_job --threads=5 & bundle exec good_job --queues="transactional_messages" --threads=2 & bundle exec good_job --queues="batch_processing" --threads=1 & wait -n
|
|
1229
1349
|
```
|
|
1230
1350
|
|
|
1231
1351
|
Keep in mind, queue operations and management is an advanced discipline. This stuff is complex, especially for heavy workloads and unique processing requirements. Good job 👍
|
|
@@ -1234,7 +1354,7 @@ Keep in mind, queue operations and management is an advanced discipline. This st
|
|
|
1234
1354
|
|
|
1235
1355
|
GoodJob job executor processes require the following database connections:
|
|
1236
1356
|
|
|
1237
|
-
- 1 connection per execution pool thread. E.g., `--queues=mice:2;elephants:1` is 3 threads and thus 3 connections. Pool size defaults to `--
|
|
1357
|
+
- 1 connection per execution pool thread. E.g., `--queues=mice:2;elephants:1` is 3 threads and thus 3 connections. Pool size defaults to `--threads`.
|
|
1238
1358
|
- 2 additional connections that GoodJob uses for utility functionality (e.g. LISTEN/NOTIFY, cron, etc.)
|
|
1239
1359
|
- 1 connection per subthread, if your application makes multithreaded database queries (e.g. `load_async`) within a job.
|
|
1240
1360
|
|
|
@@ -1253,12 +1373,12 @@ When GoodJob runs in `:async` mode (in Rails's development environment, by defau
|
|
|
1253
1373
|
- `ENV.fetch("RAILS_MAX_THREADS", 5)` is the number of threads used by the web server
|
|
1254
1374
|
- `1` is the number of connections used by the job listener
|
|
1255
1375
|
- `2` is the number of connections used by the cron scheduler and executor
|
|
1256
|
-
- `ENV.fetch("
|
|
1376
|
+
- `ENV.fetch("GOOD_JOB_THREADS", 5)` is the number of threads used to perform jobs
|
|
1257
1377
|
|
|
1258
1378
|
```yaml
|
|
1259
1379
|
# config/database.yml
|
|
1260
1380
|
|
|
1261
|
-
pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5).to_i + 1 + 2 + ENV.fetch("
|
|
1381
|
+
pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5).to_i + 1 + 2 + ENV.fetch("GOOD_JOB_THREADS", 5).to_i %>
|
|
1262
1382
|
```
|
|
1263
1383
|
|
|
1264
1384
|
When GoodJob runs in `:external` mode (in Rails' production environment, by default), the following database pool configurations work for web servers and worker processes, respectively.
|
|
@@ -1272,7 +1392,7 @@ pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %>
|
|
|
1272
1392
|
```yaml
|
|
1273
1393
|
# config/database.yml
|
|
1274
1394
|
|
|
1275
|
-
pool: <%= 1 + 2 + ENV.fetch("
|
|
1395
|
+
pool: <%= 1 + 2 + ENV.fetch("GOOD_JOB_THREADS", 5).to_i %>
|
|
1276
1396
|
```
|
|
1277
1397
|
|
|
1278
1398
|
### Production setup
|
|
@@ -1360,7 +1480,7 @@ GoodJob can execute jobs "async" in the same process as the web server (e.g. `bi
|
|
|
1360
1480
|
# Or with more configuration
|
|
1361
1481
|
config.good_job = {
|
|
1362
1482
|
execution_mode: :async,
|
|
1363
|
-
|
|
1483
|
+
threads: 4,
|
|
1364
1484
|
poll_interval: 30
|
|
1365
1485
|
}
|
|
1366
1486
|
```
|
|
@@ -1368,7 +1488,7 @@ GoodJob can execute jobs "async" in the same process as the web server (e.g. `bi
|
|
|
1368
1488
|
- Or, with environment variables:
|
|
1369
1489
|
|
|
1370
1490
|
```bash
|
|
1371
|
-
GOOD_JOB_EXECUTION_MODE=async
|
|
1491
|
+
GOOD_JOB_EXECUTION_MODE=async GOOD_JOB_THREADS=4 GOOD_JOB_POLL_INTERVAL=30 bin/rails server
|
|
1372
1492
|
```
|
|
1373
1493
|
|
|
1374
1494
|
Depending on your application configuration, you may need to take additional steps:
|
|
@@ -1377,7 +1497,7 @@ Depending on your application configuration, you may need to take additional ste
|
|
|
1377
1497
|
|
|
1378
1498
|
```yaml
|
|
1379
1499
|
# config/database.yml
|
|
1380
|
-
pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5).to_i + ENV.fetch("
|
|
1500
|
+
pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5).to_i + ENV.fetch("GOOD_JOB_THREADS", 4).to_i %>
|
|
1381
1501
|
```
|
|
1382
1502
|
|
|
1383
1503
|
- When running Puma with workers (`WEB_CONCURRENCY > 0`) or another process-forking web server, GoodJob's threadpool schedulers should be stopped before forking, restarted after fork, and cleanly shut down on exit. Stopping GoodJob's scheduler pre-fork is recommended to ensure that GoodJob does not continue executing jobs in the parent/controller process. For example, with Puma:
|
|
@@ -1484,6 +1604,7 @@ GoodJob will automatically delete preserved job records after 14 days. The reten
|
|
|
1484
1604
|
|
|
1485
1605
|
```ruby
|
|
1486
1606
|
config.good_job.cleanup_preserved_jobs_before_seconds_ago = 14.days
|
|
1607
|
+
config.good_job.cleanup_discarded_jobs_before_seconds_ago = 90.days # Optional: keep discarded jobs longer than the others.
|
|
1487
1608
|
config.good_job.cleanup_interval_jobs = 1_000 # Number of executed jobs between deletion sweeps.
|
|
1488
1609
|
config.good_job.cleanup_interval_seconds = 10.minutes # Number of seconds between deletion sweeps.
|
|
1489
1610
|
```
|
|
@@ -1495,6 +1616,7 @@ It is also possible to manually trigger a cleanup of preserved job records:
|
|
|
1495
1616
|
```ruby
|
|
1496
1617
|
GoodJob.cleanup_preserved_jobs # Will use default retention period
|
|
1497
1618
|
GoodJob.cleanup_preserved_jobs(older_than: 7.days) # custom retention period
|
|
1619
|
+
GoodJob.cleanup_preserved_jobs(older_than: 7.days, discarded_older_than: 90.days) # keep discarded jobs longer
|
|
1498
1620
|
```
|
|
1499
1621
|
|
|
1500
1622
|
- For example, using the `good_job` command-line utility:
|
|
@@ -1540,7 +1662,7 @@ end
|
|
|
1540
1662
|
GOOD_JOB_LOCK_STRATEGY=skiplocked
|
|
1541
1663
|
```
|
|
1542
1664
|
|
|
1543
|
-
|
|
1665
|
+
During a rolling deploy, `:hybrid` workers can safely run alongside either `:advisory` or `:skiplocked` workers. But `:advisory` and `:skiplocked` workers must not run at the same time, because neither sees the other's locks and the same job may be performed twice. To move from `:advisory` to `:skiplocked`, deploy `:hybrid` first, wait until no `:advisory` workers remain, and then deploy `:skiplocked`.
|
|
1544
1666
|
|
|
1545
1667
|
#### PgBouncer configuration
|
|
1546
1668
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module GoodJob
|
|
5
|
+
module LatencyMetric
|
|
6
|
+
# A measurement the Performance charts can plot. Subclasses supply a SQL
|
|
7
|
+
# expression over +good_job_executions+, the aggregate that summarizes it per
|
|
8
|
+
# time bucket, and their titles.
|
|
9
|
+
class Base
|
|
10
|
+
def expression = self.class.const_get(:EXPRESSION)
|
|
11
|
+
|
|
12
|
+
def to_arel = Arel.sql(expression)
|
|
13
|
+
|
|
14
|
+
# nil plots an empty bucket as a gap; a summed metric overrides this with a true zero.
|
|
15
|
+
def empty_value = nil
|
|
16
|
+
|
|
17
|
+
def to_params = { PARAMETER_KEY => key.to_s }
|
|
18
|
+
|
|
19
|
+
def span_gaps? = empty_value.nil?
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module GoodJob
|
|
5
|
+
module LatencyMetric
|
|
6
|
+
# How long jobs took to run. Summed across a bucket because total execution time
|
|
7
|
+
# is a measure of load.
|
|
8
|
+
class Execution < Base
|
|
9
|
+
EXPRESSION = "duration"
|
|
10
|
+
|
|
11
|
+
def key = :execution
|
|
12
|
+
|
|
13
|
+
def aggregate = "SUM"
|
|
14
|
+
|
|
15
|
+
def presence_sql = "duration IS NOT NULL"
|
|
16
|
+
|
|
17
|
+
def empty_value = 0
|
|
18
|
+
|
|
19
|
+
# The default metric stays out of navigation URLs.
|
|
20
|
+
def to_params = {}
|
|
21
|
+
|
|
22
|
+
def chart_title = I18n.t("good_job.performance.index.chart_title")
|
|
23
|
+
|
|
24
|
+
def histogram_title = I18n.t("good_job.performance.show.execution_chart_title")
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|