Migrate

# From Sidekiq

Drop Redis, switch the adapter, and map Sidekiq's Pro/Enterprise features onto pgbus.

## What changes

Sidekiq brokers through Redis; pgbus brokers through PostgreSQL (PGMQ). The migration removes Redis and gives you dead-letter queues, worker recycling, and an event bus on your existing database.

**Effort:** low if you use ActiveJob exclusively; medium-high if you have native Sidekiq workers or lean on Pro/Enterprise features.

## Swap the gem and adapter

```ruby
# Remove
gem "sidekiq"
gem "sidekiq-cron"        # if used
gem "sidekiq-unique-jobs" # if used

# Add
gem "pgbus"
```

```shell
bundle install && rails generate pgbus:install && rails db:migrate
```

```ruby
config.active_job.queue_adapter = :pgbus # was :sidekiq
```

## Convert native workers

ActiveJob jobs work unchanged; native workers need a rewrite.

If every job already inherits `ApplicationJob`, you're done — they run unchanged. A native `Sidekiq::Job` becomes a standard ActiveJob:

```ruby
# Before — native Sidekiq worker
class HardWorker
  include Sidekiq::Job
  sidekiq_options queue: :critical, retry: 5
  def perform(user_id, action) = ...
end

# After — ActiveJob
class HardWorker < ApplicationJob
  queue_as :critical
  retry_on StandardError, wait: :polynomially_longer, attempts: 5
  def perform(user_id, action) = ...
end
```

## API mapping

| Sidekiq | ActiveJob / pgbus |
| --- | --- |
| `perform_async(args)` | `perform_later(args)` |
| `perform_at(time, args)` | `.set(wait_until: time).perform_later(args)` |
| `perform_in(duration, args)` | `.set(wait: duration).perform_later(args)` |
| `sidekiq_options queue:` | `queue_as` |
| `sidekiq_options retry: N` | `retry_on StandardError, attempts: N` |
| `sidekiq_retries_exhausted` | `discard_on` + `after_discard` |

## Middleware → ActiveJob callbacks

Sidekiq middleware becomes ActiveJob callbacks. Server middleware maps to `before_perform` / `around_perform` / `after_perform`; client middleware to the `*_enqueue` callbacks:

```ruby
class ApplicationJob < ActiveJob::Base
  around_perform do |job, block|
    Rails.logger.info("Starting #{job.class.name}")
    block.call
    Rails.logger.info("Finished #{job.class.name}")
  end
end
```

## What you gain

And where the Pro/Enterprise features land.

| Sidekiq feature | pgbus equivalent |
| --- | --- |
| Batches (Pro) | [`Pgbus::Batch`](https://pgbus.zoolutions.llc/docs/batches) |
| Concurrency (Enterprise) | [`limits_concurrency`](https://pgbus.zoolutions.llc/docs/concurrency-uniqueness) |
| Unique jobs (Enterprise) | [`ensures_uniqueness`](https://pgbus.zoolutions.llc/docs/concurrency-uniqueness) |
| Cron (sidekiq-cron) | [Recurring tasks](https://pgbus.zoolutions.llc/docs/recurring-tasks) (fugit) |
| Sidekiq Web | [Dashboard](https://pgbus.zoolutions.llc/docs/dashboard) |

Plus what Sidekiq never had: dead-letter queues, worker recycling, an event bus, and no Redis.

## Gotchas

- **Argument serialization** — Sidekiq passes raw JSON; ActiveJob uses GlobalID for Active Record objects. Passing ids (`user.id`) behaves the same; passing records (`user`) serializes via GlobalID automatically.
- **`Sidekiq.redis { ... }`** — if you used Sidekiq's Redis for custom locks or caching, move to PostgreSQL advisory locks or another store.
- **Queue priority** — Sidekiq uses queue weights; pgbus processes queues in the order listed, so put higher-priority queues first (or use [priority queues](https://pgbus.zoolutions.llc/docs/routing-ordering)).