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.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +170 -2
  3. data/README.md +148 -26
  4. data/app/charts/good_job/base_chart.rb +1 -0
  5. data/app/charts/good_job/latency_metric/base.rb +22 -0
  6. data/app/charts/good_job/latency_metric/execution.rb +27 -0
  7. data/app/charts/good_job/latency_metric/queue.rb +23 -0
  8. data/app/charts/good_job/latency_metric/total.rb +23 -0
  9. data/app/charts/good_job/latency_metric.rb +43 -0
  10. data/app/charts/good_job/performance_index_chart.rb +11 -8
  11. data/app/charts/good_job/performance_range.rb +5 -3
  12. data/app/charts/good_job/performance_show_chart.rb +13 -4
  13. data/app/charts/good_job/scheduled_by_queue_chart.rb +2 -1
  14. data/app/controllers/good_job/application_controller.rb +7 -4
  15. data/app/controllers/good_job/batches_controller.rb +4 -1
  16. data/app/controllers/good_job/cleaner_controller.rb +1 -0
  17. data/app/controllers/good_job/cron_entries_controller.rb +1 -0
  18. data/app/controllers/good_job/executions_controller.rb +12 -0
  19. data/app/controllers/good_job/frontends_controller.rb +9 -4
  20. data/app/controllers/good_job/jobs_controller.rb +2 -0
  21. data/app/controllers/good_job/metrics_controller.rb +4 -1
  22. data/app/controllers/good_job/pauses_controller.rb +1 -0
  23. data/app/controllers/good_job/performance_controller.rb +13 -18
  24. data/app/controllers/good_job/processes_controller.rb +1 -0
  25. data/app/filters/good_job/base_filter.rb +10 -1
  26. data/app/filters/good_job/batches_filter.rb +1 -0
  27. data/app/filters/good_job/jobs_filter.rb +12 -1
  28. data/app/frontend/good_job/icons.svg +12 -0
  29. data/app/frontend/good_job/style.css +16 -0
  30. data/app/frontend/good_job/vendor/bootstrap/bootstrap.bundle.min.js +3 -3
  31. data/app/frontend/good_job/vendor/bootstrap/bootstrap.min.css +3 -3
  32. data/app/frontend/good_job/vendor/chartjs/chart.min.js +10 -15
  33. data/app/frontend/good_job/vendor/es_module_shims.js +1486 -1
  34. data/app/frontend/good_job/vendor/stimulus.js +2314 -2159
  35. data/app/frontend/good_job/vendor/turbo.js +6 -6
  36. data/app/helpers/good_job/application_helper.rb +44 -0
  37. data/app/helpers/good_job/icons_helper.rb +1 -0
  38. data/app/models/concerns/good_job/advisory_lockable.rb +67 -20
  39. data/app/models/concerns/good_job/error_events.rb +1 -0
  40. data/app/models/concerns/good_job/filterable.rb +1 -0
  41. data/app/models/concerns/good_job/filtered_arguments.rb +28 -0
  42. data/app/models/concerns/good_job/reportable.rb +1 -0
  43. data/app/models/good_job/active_record_parent_class.rb +1 -0
  44. data/app/models/good_job/base_record.rb +1 -0
  45. data/app/models/good_job/batch.rb +19 -20
  46. data/app/models/good_job/batch_record.rb +10 -9
  47. data/app/models/good_job/concurrency_claim.rb +183 -0
  48. data/app/models/good_job/cron_entry.rb +37 -21
  49. data/app/models/good_job/discrete_execution.rb +1 -0
  50. data/app/models/good_job/execution.rb +8 -3
  51. data/app/models/good_job/execution_result.rb +1 -0
  52. data/app/models/good_job/i18n_config.rb +1 -0
  53. data/app/models/good_job/job/lockable.rb +68 -42
  54. data/app/models/good_job/job.rb +44 -16
  55. data/app/models/good_job/process.rb +15 -11
  56. data/app/models/good_job/setting.rb +5 -2
  57. data/app/views/good_job/batches/_jobs.erb +9 -10
  58. data/app/views/good_job/cron_entries/index.html.erb +1 -1
  59. data/app/views/good_job/executions/show.html.erb +11 -0
  60. data/app/views/good_job/jobs/_executions.erb +2 -6
  61. data/app/views/good_job/jobs/_table.erb +10 -11
  62. data/app/views/good_job/jobs/show.html.erb +25 -1
  63. data/app/views/good_job/performance/_latency.erb +6 -0
  64. data/app/views/good_job/performance/_metric.erb +8 -0
  65. data/app/views/good_job/performance/_range.erb +2 -2
  66. data/app/views/good_job/performance/index.html.erb +39 -49
  67. data/app/views/good_job/performance/show.html.erb +3 -1
  68. data/app/views/good_job/shared/_chart_container.erb +3 -1
  69. data/app/views/layouts/good_job/application.html.erb +2 -0
  70. data/config/brakeman.ignore +9 -9
  71. data/config/locales/az.yml +26 -4
  72. data/config/locales/de.yml +26 -4
  73. data/config/locales/en.yml +26 -4
  74. data/config/locales/es.yml +26 -4
  75. data/config/locales/fr.yml +26 -4
  76. data/config/locales/it.yml +26 -4
  77. data/config/locales/ja.yml +26 -4
  78. data/config/locales/ko.yml +26 -4
  79. data/config/locales/nl.yml +26 -4
  80. data/config/locales/pt-BR.yml +49 -25
  81. data/config/locales/ru.yml +42 -20
  82. data/config/locales/tr.yml +26 -4
  83. data/config/locales/uk.yml +40 -18
  84. data/config/locales/zh-CN.yml +26 -4
  85. data/config/routes.rb +3 -1
  86. data/lib/active_job/queue_adapters/good_job_adapter.rb +1 -0
  87. data/lib/generators/good_job/install_generator.rb +1 -0
  88. data/lib/generators/good_job/templates/install/migrations/create_good_jobs.rb.erb +15 -0
  89. data/lib/generators/good_job/templates/update/migrations/01_create_good_jobs.rb.erb +1 -0
  90. data/lib/generators/good_job/templates/update/migrations/21_create_good_job_concurrency_claims.rb.erb +27 -0
  91. data/lib/generators/good_job/templates/update/migrations/22_add_index_good_job_executions_on_created_at.rb.erb +12 -0
  92. data/lib/generators/good_job/update_generator.rb +1 -0
  93. data/lib/good_job/active_job_extensions/batches.rb +1 -0
  94. data/lib/good_job/active_job_extensions/concurrency.rb +163 -43
  95. data/lib/good_job/active_job_extensions/interrupt_errors.rb +1 -0
  96. data/lib/good_job/active_job_extensions/labels.rb +28 -7
  97. data/lib/good_job/active_job_extensions/notify_options.rb +1 -0
  98. data/lib/good_job/adapter/inline_buffer.rb +1 -0
  99. data/lib/good_job/adapter.rb +94 -58
  100. data/lib/good_job/bulk.rb +2 -2
  101. data/lib/good_job/callable.rb +1 -0
  102. data/lib/good_job/capsule.rb +12 -8
  103. data/lib/good_job/capsule_tracker.rb +114 -67
  104. data/lib/good_job/cleanup_tracker.rb +1 -0
  105. data/lib/good_job/cli.rb +10 -1
  106. data/lib/good_job/configuration/validator.rb +1 -0
  107. data/lib/good_job/configuration.rb +72 -8
  108. data/lib/good_job/cron_manager.rb +7 -6
  109. data/lib/good_job/current_thread.rb +2 -1
  110. data/lib/good_job/daemon.rb +1 -0
  111. data/lib/good_job/dependencies.rb +1 -0
  112. data/lib/good_job/engine.rb +7 -1
  113. data/lib/good_job/instance_registry.rb +48 -0
  114. data/lib/good_job/interrupt_error.rb +1 -0
  115. data/lib/good_job/interrupted_error.rb +1 -0
  116. data/lib/good_job/job_performer/metrics.rb +1 -0
  117. data/lib/good_job/job_performer.rb +7 -5
  118. data/lib/good_job/lifecycle_hooks.rb +1 -0
  119. data/lib/good_job/log_subscriber.rb +10 -0
  120. data/lib/good_job/multi_scheduler.rb +2 -1
  121. data/lib/good_job/notifier/process_heartbeat.rb +31 -6
  122. data/lib/good_job/notifier.rb +6 -6
  123. data/lib/good_job/poller.rb +5 -6
  124. data/lib/good_job/probe_server/cluster_healthcheck_middleware.rb +1 -0
  125. data/lib/good_job/probe_server/healthcheck_middleware.rb +1 -0
  126. data/lib/good_job/probe_server/not_found_app.rb +1 -0
  127. data/lib/good_job/probe_server/simple_handler.rb +51 -11
  128. data/lib/good_job/probe_server/webrick_handler.rb +3 -0
  129. data/lib/good_job/probe_server.rb +3 -0
  130. data/lib/good_job/ractors.rb +100 -0
  131. data/lib/good_job/safe_state.rb +1 -0
  132. data/lib/good_job/scheduler.rb +6 -8
  133. data/lib/good_job/sd_notify.rb +1 -0
  134. data/lib/good_job/shared_executor.rb +5 -6
  135. data/lib/good_job/subprocess.rb +1 -0
  136. data/lib/good_job/supervisor.rb +22 -15
  137. data/lib/good_job/systemd_service.rb +2 -0
  138. data/lib/good_job/thread_status.rb +1 -0
  139. data/lib/good_job/version.rb +2 -1
  140. data/lib/good_job.rb +129 -41
  141. metadata +31 -3
  142. data/lib/good_job/overridable_connection.rb +0 -38
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 96a4cd91def79d6e98d59849112ee43979d9dd518e252ca693d12c0e71003463
4
- data.tar.gz: 5d82aa51b9f049b7655f0e7243ede43df3029011fd5e1f62637fdf193ba2cec3
3
+ metadata.gz: 6973f64ae5640b5157d72d26438c7bb155072f07a98829461324b548f0092a19
4
+ data.tar.gz: 5b9dbd764aa128a9b54797e1f0abe0912942a391d656070ca86f4302041fed33
5
5
  SHA512:
6
- metadata.gz: 9301c67f9223407df170c9d8b9c93859d274f6affb36afb2fe948e81f1b704d7d8215510ad1491cf86f83fafecd5cd94a2394f8a793cf995b28f41a326d75086
7
- data.tar.gz: 4b6ae502d9030ef467e9c7579ec2da0ccde6ccf35176d7a7a50e1fee13c0463030b9d725c0cd916439cdf58c32c3cf2d0a7bb1bea5c5e790c23110c670c510cf
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) ([codyrobbins](https://github.com/codyrobbins))
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) ([codyrobbins](https://github.com/codyrobbins))
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
- [--max-threads=COUNT] # Default number of threads per pool to use for working jobs. (env var: GOOD_JOB_MAX_THREADS, default: 5)
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] # Destroy records finished more than this many seconds ago (env var: GOOD_JOB_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO, default: 1209600 (14 days))
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.max_threads = 5
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
- max_threads: 5,
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
- - `max_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_MAX_THREADS`.
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. The rule only applies to jobs
565
- # that were enqueued with this label in `good_job_labels`.
566
- label: -> { arguments.first[:user_id] },
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(user_id:)
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: [current_user.id]).perform_later(user_id: current_user.id)
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
- - First-in-first-out job execution order is not preserved when a job is retried with incremental back-off.
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
- #### Legacy: `good_job_control_concurrency_with`
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
- The original concurrency interface uses a single configuration hash and scopes limits to a concurrency _key_ (a string derived from the job) stored on the job record, rather than a label. It remains fully supported.
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
- GOOD_JOB_MAX_THREADS=5 \
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 --max-threads=5
1218
- transactional_worker: bundle exec good_job --queues="transactional_messages" --max-threads=2
1219
- batch_worker: bundle exec good_job --queues="batch_processing" --max-threads=1
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 --max-threads=5 & bundle exec good_job --queues="transactional_messages" --max-threads=2 & bundle exec good_job --queues="batch_processing" --max-threads=1 & wait -n
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 `--max-threads`.
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("GOOD_JOB_MAX_THREADS", 5)` is the number of threads used to perform jobs
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("GOOD_JOB_MAX_THREADS", 5).to_i %>
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("GOOD_JOB_MAX_THREADS", 5).to_i %>
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
- max_threads: 4,
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 GOOD_JOB_MAX_THREADS=4 GOOD_JOB_POLL_INTERVAL=30 bin/rails server
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("GOOD_JOB_MAX_THREADS", 4).to_i %>
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
- All three strategies (`:advisory`, `:skiplocked`, `:hybrid`) can coexist safely during a rolling deploy — each strategy excludes jobs that are already locked by another worker regardless of which strategy that worker uses.
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
 
@@ -1,3 +1,4 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module GoodJob
@@ -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