pgbus 0.0.1 → 0.1.2

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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +37 -3
  3. data/Rakefile +98 -1
  4. data/app/controllers/pgbus/application_controller.rb +8 -0
  5. data/app/controllers/pgbus/recurring_tasks_controller.rb +36 -0
  6. data/app/helpers/pgbus/application_helper.rb +41 -0
  7. data/app/models/pgbus/application_record.rb +7 -0
  8. data/app/models/pgbus/batch_entry.rb +31 -0
  9. data/app/models/pgbus/blocked_execution.rb +40 -0
  10. data/app/models/pgbus/process_entry.rb +9 -0
  11. data/app/models/pgbus/processed_event.rb +9 -0
  12. data/app/models/pgbus/recurring_execution.rb +33 -0
  13. data/app/models/pgbus/recurring_task.rb +42 -0
  14. data/app/models/pgbus/semaphore.rb +29 -0
  15. data/app/views/layouts/pgbus/application.html.erb +1 -0
  16. data/app/views/pgbus/dashboard/_stats_cards.html.erb +9 -1
  17. data/app/views/pgbus/dead_letter/_messages_table.html.erb +55 -18
  18. data/app/views/pgbus/jobs/_enqueued_table.html.erb +46 -8
  19. data/app/views/pgbus/recurring_tasks/_tasks_table.html.erb +79 -0
  20. data/app/views/pgbus/recurring_tasks/index.html.erb +6 -0
  21. data/app/views/pgbus/recurring_tasks/show.html.erb +122 -0
  22. data/config/routes.rb +7 -0
  23. data/lib/active_job/queue_adapters/pgbus_adapter.rb +29 -0
  24. data/lib/generators/pgbus/add_recurring_generator.rb +56 -0
  25. data/lib/generators/pgbus/install_generator.rb +76 -2
  26. data/lib/generators/pgbus/templates/add_recurring_tables.rb.erb +31 -0
  27. data/lib/generators/pgbus/templates/migration.rb.erb +72 -4
  28. data/lib/generators/pgbus/templates/recurring.yml.erb +40 -0
  29. data/lib/generators/pgbus/templates/upgrade_pgmq.rb.erb +30 -0
  30. data/lib/generators/pgbus/upgrade_pgmq_generator.rb +60 -0
  31. data/lib/pgbus/active_job/adapter.rb +3 -6
  32. data/lib/pgbus/active_job/executor.rb +26 -12
  33. data/lib/pgbus/batch.rb +65 -72
  34. data/lib/pgbus/cli.rb +11 -16
  35. data/lib/pgbus/client.rb +32 -15
  36. data/lib/pgbus/concurrency/blocked_execution.rb +32 -37
  37. data/lib/pgbus/concurrency/semaphore.rb +11 -39
  38. data/lib/pgbus/concurrency.rb +10 -2
  39. data/lib/pgbus/configuration.rb +48 -0
  40. data/lib/pgbus/engine.rb +19 -1
  41. data/lib/pgbus/event_bus/handler.rb +10 -23
  42. data/lib/pgbus/instrumentation.rb +29 -0
  43. data/lib/pgbus/pgmq_schema/pgmq_v1.11.0.sql +2123 -0
  44. data/lib/pgbus/pgmq_schema.rb +159 -0
  45. data/lib/pgbus/process/consumer.rb +17 -9
  46. data/lib/pgbus/process/dispatcher.rb +33 -41
  47. data/lib/pgbus/process/heartbeat.rb +15 -23
  48. data/lib/pgbus/process/signal_handler.rb +23 -1
  49. data/lib/pgbus/process/supervisor.rb +79 -2
  50. data/lib/pgbus/process/worker.rb +42 -13
  51. data/lib/pgbus/recurring/already_recorded.rb +7 -0
  52. data/lib/pgbus/recurring/command_job.rb +28 -0
  53. data/lib/pgbus/recurring/config_loader.rb +35 -0
  54. data/lib/pgbus/recurring/schedule.rb +102 -0
  55. data/lib/pgbus/recurring/scheduler.rb +102 -0
  56. data/lib/pgbus/recurring/task.rb +111 -0
  57. data/lib/pgbus/serializer.rb +16 -6
  58. data/lib/pgbus/version.rb +1 -1
  59. data/lib/pgbus/web/data_source.rb +217 -36
  60. data/lib/pgbus.rb +8 -0
  61. data/lib/tasks/pgbus_pgmq.rake +62 -0
  62. metadata +51 -24
  63. data/.bun-version +0 -1
  64. data/.claude/commands/architect.md +0 -100
  65. data/.claude/commands/github-review-comments.md +0 -237
  66. data/.claude/commands/lfg.md +0 -271
  67. data/.claude/commands/review-pr.md +0 -69
  68. data/.claude/commands/security.md +0 -122
  69. data/.claude/commands/tdd.md +0 -148
  70. data/.claude/rules/agents.md +0 -49
  71. data/.claude/rules/coding-style.md +0 -91
  72. data/.claude/rules/git-workflow.md +0 -56
  73. data/.claude/rules/performance.md +0 -73
  74. data/.claude/rules/testing.md +0 -67
  75. data/CLAUDE.md +0 -80
  76. data/CODE_OF_CONDUCT.md +0 -10
  77. data/bun.lock +0 -18
  78. data/docs/README.md +0 -28
  79. data/docs/switch_from_good_job.md +0 -279
  80. data/docs/switch_from_sidekiq.md +0 -226
  81. data/docs/switch_from_solid_queue.md +0 -247
  82. data/package.json +0 -9
  83. data/sig/pgbus.rbs +0 -4
@@ -1,279 +0,0 @@
1
- # Switch from GoodJob to Pgbus
2
-
3
- ## Overview
4
-
5
- GoodJob and Pgbus are both PostgreSQL-native job processors with LISTEN/NOTIFY support. The biggest architectural difference: GoodJob uses advisory locks and a `good_jobs` table, while Pgbus uses PGMQ (a dedicated message queue extension) with visibility timeouts. Both are pure ActiveJob adapters.
6
-
7
- **Effort estimate:** Low if you use standard ActiveJob. Medium if you rely on GoodJob's concurrency controls, batches, or cron.
8
-
9
- ## Step 1: Update dependencies
10
-
11
- ```ruby
12
- # Gemfile
13
-
14
- # Remove
15
- gem "good_job"
16
-
17
- # Add
18
- gem "pgbus"
19
- ```
20
-
21
- ```bash
22
- bundle install
23
- rails generate pgbus:install
24
- rails db:migrate
25
- ```
26
-
27
- ## Step 2: Switch the adapter
28
-
29
- ```ruby
30
- # config/application.rb
31
-
32
- # Before
33
- config.active_job.queue_adapter = :good_job
34
-
35
- # After
36
- config.active_job.queue_adapter = :pgbus
37
- ```
38
-
39
- Remove GoodJob configuration:
40
-
41
- ```ruby
42
- # Before: Remove this block
43
- config.good_job = {
44
- execution_mode: :async,
45
- queues: "critical:5;default:3;*:1",
46
- max_threads: 5,
47
- poll_interval: 10,
48
- enable_cron: true,
49
- enable_listen_notify: true,
50
- # ...
51
- }
52
- ```
53
-
54
- ## Step 3: Convert worker configuration
55
-
56
- ```ruby
57
- # Before: GoodJob config (in application.rb or environment files)
58
- config.good_job = {
59
- execution_mode: :external, # or :async
60
- queues: "critical:5;default:3;low:1",
61
- poll_interval: 10,
62
- max_threads: 5,
63
- shutdown_timeout: 30,
64
- }
65
- ```
66
-
67
- ```yaml
68
- # After: config/pgbus.yml
69
- production:
70
- workers:
71
- - queues: [critical]
72
- threads: 5
73
- - queues: [default]
74
- threads: 3
75
- - queues: [low]
76
- threads: 1
77
- max_retries: 5
78
- max_jobs_per_worker: 10000
79
- max_memory_mb: 512
80
- max_worker_lifetime: 3600
81
- ```
82
-
83
- ### Configuration mapping
84
-
85
- | GoodJob | Pgbus | Notes |
86
- |---------|-------|-------|
87
- | `execution_mode: :external` | `bundle exec pgbus start` | Separate process (recommended for production) |
88
- | `execution_mode: :async` | N/A | Pgbus always runs as separate processes |
89
- | `queues: "critical:5;default:3"` | `workers:` array | One entry per worker process |
90
- | `max_threads` | `threads` per worker | Per-worker, not global |
91
- | `poll_interval` | `polling_interval` | Pgbus defaults to 0.1s; LISTEN/NOTIFY is primary |
92
- | `enable_listen_notify` | `listen_notify` | Both support LISTEN/NOTIFY |
93
- | `shutdown_timeout` | Handled by supervisor | Graceful shutdown on SIGTERM |
94
- | `on_thread_error` | Configure via `Pgbus.logger` | Error reporting via logging |
95
- | `preserve_job_records` | Always archived | PGMQ archives completed messages |
96
-
97
- ## Step 4: Remove concurrency controls
98
-
99
- GoodJob's concurrency extensions are GoodJob-specific. Remove them:
100
-
101
- ```ruby
102
- # Before
103
- class ProcessOrderJob < ApplicationJob
104
- include GoodJob::ActiveJobExtensions::Concurrency
105
-
106
- good_job_control_concurrency_with(
107
- total_limit: 1,
108
- enqueue_limit: 2,
109
- perform_limit: 1,
110
- enqueue_throttle: [10, 1.minute],
111
- perform_throttle: [100, 1.hour],
112
- key: -> { "ProcessOrder-#{arguments.first.id}" }
113
- )
114
-
115
- def perform(order)
116
- # ...
117
- end
118
- end
119
- ```
120
-
121
- ```ruby
122
- # After
123
- class ProcessOrderJob < ApplicationJob
124
- def perform(order)
125
- # ...
126
- end
127
- end
128
- ```
129
-
130
- > Pgbus supports concurrency controls via `Pgbus::Concurrency`:
131
- > ```ruby
132
- > class ProcessOrderJob < ApplicationJob
133
- > include Pgbus::Concurrency
134
- > limits_concurrency to: 1,
135
- > key: -> { "ProcessOrder-#{arguments.first.id}" },
136
- > duration: 15.minutes,
137
- > on_conflict: :block
138
- > def perform(order)
139
- > # ...
140
- > end
141
- > end
142
- > ```
143
-
144
- ## Step 5: Migrate batches
145
-
146
- If you use `GoodJob::Batch`:
147
-
148
- ```ruby
149
- # Before: GoodJob batches
150
- GoodJob::Batch.enqueue(
151
- on_finish: BatchCallbackJob,
152
- on_success: SuccessNotifyJob,
153
- description: "Import users"
154
- ) do
155
- users.each { |u| ImportUserJob.perform_later(u) }
156
- end
157
- ```
158
-
159
- Pgbus does not yet have batch support. Workaround using a coordinator job:
160
-
161
- ```ruby
162
- # After: Coordinator pattern
163
- class ImportUsersJob < ApplicationJob
164
- def perform(user_ids)
165
- user_ids.each { |id| ImportUserJob.perform_later(id) }
166
- # Track completion via a counter in the database or Redis
167
- end
168
- end
169
- ```
170
-
171
- ## Step 6: Migrate cron / recurring jobs
172
-
173
- ```ruby
174
- # Before: GoodJob cron
175
- config.good_job.enable_cron = true
176
- config.good_job.cron = {
177
- daily_cleanup: {
178
- cron: "0 2 * * *",
179
- class: "CleanupJob",
180
- set: { priority: -10, queue: "maintenance" },
181
- },
182
- hourly_sync: {
183
- cron: "0 * * * *",
184
- class: "SyncJob",
185
- args: [42],
186
- }
187
- }
188
- ```
189
-
190
- Pgbus does not yet have built-in recurring task support. Options:
191
-
192
- 1. **Use the `whenever` gem**:
193
- ```ruby
194
- # config/schedule.rb
195
- every 1.day, at: "2:00 am" do
196
- runner "CleanupJob.perform_later"
197
- end
198
- every :hour do
199
- runner "SyncJob.perform_later(42)"
200
- end
201
- ```
202
-
203
- 2. **Use system cron** directly:
204
- ```cron
205
- 0 2 * * * cd /app && bin/rails runner "CleanupJob.perform_later"
206
- 0 * * * * cd /app && bin/rails runner "SyncJob.perform_later(42)"
207
- ```
208
-
209
- 3. Wait for Pgbus recurring task support (planned).
210
-
211
- ## Step 7: Replace the dashboard
212
-
213
- ```ruby
214
- # Before: config/routes.rb
215
- mount GoodJob::Engine => "good_job"
216
-
217
- # After:
218
- mount Pgbus::Engine => "/pgbus"
219
- ```
220
-
221
- ## Step 8: Update process management
222
-
223
- ```bash
224
- # Before (external mode)
225
- bundle exec good_job start
226
-
227
- # After
228
- bundle exec pgbus start
229
- ```
230
-
231
- If you ran GoodJob in `async` mode (in-process), note that Pgbus always runs as separate forked processes managed by a supervisor. Update your deployment accordingly -- you need `bundle exec pgbus start` as a separate process.
232
-
233
- ## Step 9: Clean up GoodJob tables
234
-
235
- After verifying Pgbus is processing correctly and GoodJob's tables are drained:
236
-
237
- ```ruby
238
- class RemoveGoodJob < ActiveRecord::Migration[7.1]
239
- def up
240
- drop_table :good_jobs, if_exists: true
241
- drop_table :good_job_batches, if_exists: true
242
- drop_table :good_job_executions, if_exists: true
243
- drop_table :good_job_processes, if_exists: true
244
- drop_table :good_job_settings, if_exists: true
245
- end
246
- end
247
- ```
248
-
249
- ## What you gain
250
-
251
- - **Dead letter queues** -- GoodJob retries in-place and marks jobs as discarded. Pgbus routes failures to dedicated `_dlq` queues for clear operational visibility.
252
- - **Worker recycling** -- GoodJob workers run indefinitely. Pgbus recycles by job count, memory, or lifetime to prevent memory bloat.
253
- - **Event bus** -- AMQP-style pub/sub with topic routing and idempotent handlers.
254
- - **PGMQ** -- purpose-built message queue extension with atomic read/archive/delete, visibility timeouts, and `SKIP LOCKED` under the hood.
255
- - **Supervisor/fork model** -- isolated worker processes. A memory leak or crash in one worker doesn't affect others.
256
-
257
- ## What you lose (for now)
258
-
259
- | GoodJob feature | Status in Pgbus |
260
- |-----------------|-----------------|
261
- | Concurrency controls (`good_job_control_concurrency_with`) | `Pgbus::Concurrency` with `limits_concurrency` DSL |
262
- | Throttling (`enqueue_throttle`, `perform_throttle`) | Planned |
263
- | Batches (`GoodJob::Batch`) | `Pgbus::Batch` with on_finish/on_success/on_discard callbacks |
264
- | Cron / recurring jobs | Planned |
265
- | Async execution mode (in-process) | Not planned (forked processes only) |
266
- | Capsules (isolated thread pools) | Workers are isolated by design (forked processes) |
267
- | Advisory locks | Replaced by PGMQ visibility timeouts |
268
-
269
- ## Gotchas
270
-
271
- 1. **PgBouncer**: Both GoodJob and Pgbus use LISTEN/NOTIFY, which requires session-mode PgBouncer or direct connections. If you already had GoodJob working with PgBouncer, the same configuration applies.
272
-
273
- 2. **No async mode**: GoodJob can run in-process (`:async` mode) alongside your Rails app server. Pgbus requires a separate supervisor process. Make sure your deployment runs `bundle exec pgbus start` alongside your web server.
274
-
275
- 3. **Advisory locks vs. visibility timeouts**: GoodJob uses PostgreSQL advisory locks to claim jobs. Pgbus uses PGMQ's visibility timeout -- a claimed message becomes invisible for `visibility_timeout` seconds. If a worker crashes without archiving the message, it automatically becomes available again after the timeout expires. This is more resilient than advisory locks, which release on disconnect.
276
-
277
- 4. **Job record preservation**: GoodJob has `preserve_job_records` for keeping completed job records. PGMQ automatically archives completed messages to `a_pgbus_*` tables, which serve a similar purpose for debugging and auditing.
278
-
279
- 5. **Queue naming**: GoodJob uses bare queue names. Pgbus prefixes all queues (`pgbus_default`). Your `queue_as` declarations work unchanged -- the prefix is applied automatically.
@@ -1,226 +0,0 @@
1
- # Switch from Sidekiq to Pgbus
2
-
3
- ## Overview
4
-
5
- Sidekiq uses Redis as its message broker. Pgbus uses PostgreSQL via PGMQ. Switching eliminates Redis from your infrastructure and gives you dead letter queues, worker recycling, and an event bus -- all backed by your existing database.
6
-
7
- **Effort estimate:** Low if you use ActiveJob exclusively. Medium-high if you use native Sidekiq workers or Pro/Enterprise features.
8
-
9
- ## Step 1: Update dependencies
10
-
11
- ```ruby
12
- # Gemfile
13
-
14
- # Remove
15
- gem "sidekiq"
16
- gem "sidekiq-cron" # if used
17
- gem "sidekiq-unique-jobs" # if used
18
-
19
- # Add
20
- gem "pgbus"
21
- ```
22
-
23
- ```bash
24
- bundle install
25
- rails generate pgbus:install
26
- rails db:migrate
27
- ```
28
-
29
- ## Step 2: Switch the adapter
30
-
31
- ```ruby
32
- # config/application.rb (or config/environments/production.rb)
33
-
34
- # Before
35
- config.active_job.queue_adapter = :sidekiq
36
-
37
- # After
38
- config.active_job.queue_adapter = :pgbus
39
- ```
40
-
41
- ## Step 3: Convert native Sidekiq workers
42
-
43
- If all your jobs inherit from `ApplicationJob` (ActiveJob), skip this step -- they work unchanged.
44
-
45
- If you have native Sidekiq workers using `include Sidekiq::Job`, convert them:
46
-
47
- ```ruby
48
- # Before: Native Sidekiq worker
49
- class HardWorker
50
- include Sidekiq::Job
51
- sidekiq_options queue: :critical, retry: 5
52
-
53
- def perform(user_id, action)
54
- user = User.find(user_id)
55
- # ...
56
- end
57
- end
58
-
59
- # Enqueue
60
- HardWorker.perform_async(user.id, "activate")
61
- HardWorker.perform_at(5.minutes.from_now, user.id, "activate")
62
- ```
63
-
64
- ```ruby
65
- # After: ActiveJob
66
- class HardWorker < ApplicationJob
67
- queue_as :critical
68
- retry_on StandardError, wait: :polynomially_longer, attempts: 5
69
-
70
- def perform(user_id, action)
71
- user = User.find(user_id)
72
- # ...
73
- end
74
- end
75
-
76
- # Enqueue
77
- HardWorker.perform_later(user.id, "activate")
78
- HardWorker.set(wait: 5.minutes).perform_later(user.id, "activate")
79
- ```
80
-
81
- ### API mapping
82
-
83
- | Sidekiq | ActiveJob / Pgbus |
84
- |---------|-------------------|
85
- | `perform_async(args)` | `perform_later(args)` |
86
- | `perform_at(time, args)` | `.set(wait_until: time).perform_later(args)` |
87
- | `perform_in(duration, args)` | `.set(wait: duration).perform_later(args)` |
88
- | `sidekiq_options queue: :name` | `queue_as :name` |
89
- | `sidekiq_options retry: N` | `retry_on StandardError, attempts: N` |
90
- | `sidekiq_retries_exhausted` | `discard_on` + `after_discard` callback |
91
-
92
- ## Step 4: Replace middleware with ActiveJob callbacks
93
-
94
- Sidekiq middleware wraps job push (client) and execution (server) in a Rack-style chain. ActiveJob provides equivalent hooks.
95
-
96
- ```ruby
97
- # Before: Sidekiq server middleware
98
- class LoggingMiddleware
99
- include Sidekiq::ServerMiddleware
100
- def call(job_instance, job_payload, queue)
101
- Rails.logger.info("Starting #{job_payload['class']}")
102
- yield
103
- Rails.logger.info("Finished #{job_payload['class']}")
104
- end
105
- end
106
- ```
107
-
108
- ```ruby
109
- # After: ActiveJob callback (add to ApplicationJob or per-job)
110
- class ApplicationJob < ActiveJob::Base
111
- around_perform do |job, block|
112
- Rails.logger.info("Starting #{job.class.name}")
113
- block.call
114
- Rails.logger.info("Finished #{job.class.name}")
115
- end
116
- end
117
- ```
118
-
119
- ### Callback mapping
120
-
121
- | Sidekiq middleware | ActiveJob callback |
122
- |--------------------|-------------------|
123
- | Server middleware (before yield) | `before_perform` |
124
- | Server middleware (around yield) | `around_perform` |
125
- | Server middleware (after yield) | `after_perform` |
126
- | Client middleware (before yield) | `before_enqueue` |
127
- | Client middleware (around yield) | `around_enqueue` |
128
- | Client middleware (after yield) | `after_enqueue` |
129
-
130
- ## Step 5: Configure workers
131
-
132
- ```yaml
133
- # Before: config/sidekiq.yml
134
- :concurrency: 10
135
- :queues:
136
- - [critical, 3]
137
- - [default, 2]
138
- - [low, 1]
139
- ```
140
-
141
- ```yaml
142
- # After: config/pgbus.yml
143
- production:
144
- workers:
145
- - queues: [critical]
146
- threads: 5
147
- - queues: [default, low]
148
- threads: 10
149
- max_jobs_per_worker: 10000
150
- max_memory_mb: 512
151
- max_worker_lifetime: 3600
152
- ```
153
-
154
- ## Step 6: Replace the dashboard
155
-
156
- ```ruby
157
- # Before: config/routes.rb
158
- require "sidekiq/web"
159
- mount Sidekiq::Web => "/sidekiq"
160
-
161
- # After:
162
- mount Pgbus::Engine => "/pgbus"
163
- ```
164
-
165
- If you used `Sidekiq::Web`'s authentication:
166
-
167
- ```ruby
168
- Pgbus.configure do |config|
169
- config.web_auth = ->(request) {
170
- request.env["warden"].user&.admin?
171
- }
172
- end
173
- ```
174
-
175
- ## Step 7: Update process management
176
-
177
- ```bash
178
- # Before
179
- bundle exec sidekiq
180
-
181
- # After
182
- bundle exec pgbus start
183
- ```
184
-
185
- Update your `Procfile`, systemd units, or container entrypoints accordingly.
186
-
187
- ## Step 8: Remove Redis
188
-
189
- Once all Sidekiq jobs have drained and you've verified Pgbus is processing correctly, remove Redis from your infrastructure:
190
-
191
- - Remove `REDIS_URL` / Sidekiq Redis config from environment
192
- - Remove `Sidekiq.configure_server` / `Sidekiq.configure_client` blocks
193
- - Remove Redis from `docker-compose.yml`, Terraform, etc.
194
-
195
- ## What you gain
196
-
197
- - **No Redis dependency** -- one fewer service to operate
198
- - **Dead letter queues** -- failed jobs route to `_dlq` queues after `max_retries`, visible in the dashboard
199
- - **Worker recycling** -- memory, job count, and lifetime limits prevent the memory bloat that plagues long-running Sidekiq processes
200
- - **Event bus** -- AMQP-style topic routing for pub/sub, built into the same infrastructure
201
- - **LISTEN/NOTIFY** -- instant job wake-up without polling overhead
202
-
203
- ## What you lose (for now)
204
-
205
- | Sidekiq feature | Status in Pgbus |
206
- |-----------------|-----------------|
207
- | Batches (Pro) | `Pgbus::Batch` with on_finish/on_success/on_discard callbacks |
208
- | Rate limiting (Enterprise) | Planned |
209
- | Concurrency controls (Enterprise) | `Pgbus::Concurrency` with `limits_concurrency` DSL |
210
- | Unique jobs (Enterprise / `sidekiq-unique-jobs`) | Partial -- event bus has idempotency; job-level dedup planned |
211
- | Cron / recurring jobs (`sidekiq-cron`) | Planned |
212
- | Real-time metrics (Sidekiq Web) | Pgbus dashboard covers queue depth, failures, processes |
213
-
214
- ## Gotchas
215
-
216
- 1. **Argument serialization**: Sidekiq passes raw JSON; ActiveJob uses GlobalID for ActiveRecord objects. If you pass raw IDs (`user.id`), both work the same. If you pass AR objects (`user`), ActiveJob serializes via GlobalID automatically.
217
-
218
- 2. **`Sidekiq::Testing.fake!`**: Replace with `ActiveJob::TestHelper`:
219
- ```ruby
220
- include ActiveJob::TestHelper
221
- assert_enqueued_jobs 1 { MyJob.perform_later(args) }
222
- ```
223
-
224
- 3. **`Sidekiq.redis { |conn| ... }`**: If you use Sidekiq's Redis connection for custom caching or distributed locks, you'll need a separate solution (e.g., PostgreSQL advisory locks, `with_advisory_lock` gem).
225
-
226
- 4. **Job priorities**: Sidekiq uses queue weight ordering. Pgbus processes queues in the order listed in the worker config. Put higher-priority queues first.