Migrate

# Upgrading to v9

v9 is a ground-up rewrite that migrates your existing v8 locks automatically on first startup — no scripts to run, no downtime to plan.

## The short version

Bump the gem, re-check your initializer, boot.

Most of v9 lives below the surface. The public API you write in your workers — `sidekiq_options lock: :until_executed`, conflict strategies, `lock_ttl`, `lock_args_method` — is unchanged. What changed is the machinery underneath: the Redis data model, the fetch loop, and the minimum versions of Ruby and Sidekiq.

Bump the gem and boot your app:

```ruby
# Gemfile
gem "sidekiq-unique-jobs", "~> 9.0"
```

On the first boot, v9 upgrades any locks written by v8 into the new format for you. There is nothing else to run.

> **Note:** There is no data migration script to run. v9 migrates your v8 lock data on the first startup, in place. Deploy it like any other gem bump.

## Breaking changes

The things that can stop your app from booting.

- **Sidekiq 8.0 or newer only.** v9 drops support for every earlier Sidekiq release. If you are on Sidekiq 7 or below, upgrade Sidekiq first, then upgrade this gem.
- **Ruby 3.2 or newer only.** Older Rubies are no longer supported.
- **The changelog history feature is gone.** v8 recorded a per-lock event log; v9 removes it in favor of the reflection system. See the "Changelog history is gone" section below.
- **The Redis data model changed.** A lock is now two keys instead of up to thirteen. The upgrade rewrites your existing locks into the new shape automatically — you do not touch Redis yourself.

None of these require code changes in your workers. The two that can block a boot are the version floors: pin Ruby to 3.2+ and Sidekiq to 8.0+ before you deploy.

## What happens on the first boot

When a v9 process starts, it runs a one-time upgrade over the locks left behind by v8. It reads the old multi-key lock structures out of Redis and rewrites each one into the v9 two-key format, preserving which jobs hold which locks. Jobs that are already enqueued or in flight stay unique across the transition — you do not lose locks and you do not get a burst of duplicates.

Because the migration is automatic and in place, the rollout is a normal deploy: replace the gem, restart your Sidekiq processes, done. If you run a fleet, the first process to come up on v9 performs the upgrade and the rest join the already-migrated data.

## Two Redis keys per lock

Down from as many as thirteen.

v8 spread a single lock across several Redis keys — separate structures for the lock, its queue, its metadata, its changelog, and a distinct sorted set for expiring locks. v9 collapses all of that into two keys:

```
<digest>:LOCKED       # Hash  — the job IDs holding this lock, with metadata
uniquejobs:digests    # ZSet  — a global index of every active lock
```

There is no longer a separate `expiring_digests` sorted set. TTL locks — the ones created by `:until_expired` — now live in the same `uniquejobs:digests` index as every other lock, with their expiry time stored as the score. One index, one code path, whether or not a lock has a TTL. Fewer keys means less Redis memory per lock and simpler, cheaper reaping of orphans.

## Changelog history is gone

Reflections replace it.

v8 kept a rolling changelog of lock events in Redis. v9 removes it. If you relied on that history to see when locks were acquired, released, or contended, move to the reflection system — it hands you the same events as they happen, and you send them wherever you already send metrics and logs instead of storing them in Redis.

```ruby
SidekiqUniqueJobs.reflect do |on|
  on.locked        { |job_hash| StatsD.increment("uniquejobs.locked") }
  on.lock_failed   { |job_hash| StatsD.increment("uniquejobs.lock_failed") }
  on.unlocked      { |job_hash| StatsD.increment("uniquejobs.unlocked") }
end
```

Reflections cover fourteen lock-lifecycle events — locked, unlocked, lock_failed, timeout, duplicate, execution_failed, and more. See [Observability](https://sidekiq-unique-jobs.zoolutions.llc/docs/observability) for the full picture and [Reflections](https://sidekiq-unique-jobs.zoolutions.llc/docs/reflections) for the complete event list.

## Upgrade checklist

1. **Raise your version floors.** Ruby 3.2+ and Sidekiq 8.0+ are required. Upgrade those first if you are behind.
2. **Bump the gem** to `~> 9.0` and run `bundle install`.
3. **Re-check your initializer.** The middleware is still not loaded automatically — confirm your Sidekiq configuration matches the current [Installation](https://sidekiq-unique-jobs.zoolutions.llc/docs/installation) guide.
4. **Replace any changelog-history usage** with the reflection system — see [Reflections](https://sidekiq-unique-jobs.zoolutions.llc/docs/reflections).
5. **Deploy and boot.** The first v9 process migrates your v8 locks in place. No migration script, no manual Redis surgery.

> **Tip:** Consider enabling ReliableFetch while you are here — v9 makes it opt-in with a single config line so jobs survive a crashed worker. See [ReliableFetch](https://sidekiq-unique-jobs.zoolutions.llc/docs/reliablefetch).