A queue migration needs an owner for every pending job
Moving delayed jobs between backends needs explicit ownership and reconciliation. Assess draining, duplication and unreadable payloads before retiring a queue.
A queue migration is incomplete while scheduled work has no clear owner. Switching the adapter for new jobs does not decide who will execute items already waiting in the old backend. The migration needs a reconciliation rule for every pending item, including jobs copied twice, jobs that cannot be deserialized and jobs close enough to execution that the old scheduler may already be handling them.
This is a November 2026 retrospective. The On Rails episode announced on 24 June 2025 features Rosa Gutiérrez discussing Solid Queue and migration from Resque at 37signals. Her scheduled-job migration gist was created on 30 June, after the historical week. It is later supporting evidence, not a script shown to have been published on the interview date.
New work and old promises
For new jobs, an adapter setting establishes a route. Existing scheduled payloads remain a separate population. They may be due far in the future, depend on old job classes or refer to records that no longer exist. The product’s promise concerns what those jobs are meant to accomplish, rather than which backend currently contains their bytes.
Before moving that population, the team should identify the source of truth for scheduled work and establish which producers are still writing to it. Otherwise a copy can finish successfully while new old-backend entries keep appearing. Stopping or redirecting producers and transferring existing items are separate parts of the cutover.
The same distinction applies to recurring work. A recurring definition can generate future jobs; an already scheduled item is one concrete promise. Moving both without a plan can generate overlapping work. Reconciliation should distinguish the definition from its pending instances, rather than assuming that the new scheduler’s presence accounts for every old item.
Read the script within its original boundary
The gist’s initial 30 June revision is deliberately specific. It skips timestamps less than ten minutes ahead, leaving those jobs for Resque. It decodes Active Job arguments and handles ActiveJob::DeserializationError without producing a decoded job. Its transfer branch selects ActiveRecord::DestroyJob, enqueues through that class and then removes the old delayed payload.
The revision’s comment says Solid Queue had already been selected as that class’s adapter. That condition matters: enqueueing through a job class uses its configured route. A reader cannot copy the scheduling call and assume the destination is established independently of application configuration. The initial revision also says its authors’ future jobs were of that particular class; it is not a general converter for arbitrary payload formats.
The gist has later revisions, so a present-day display should not be silently treated as the original script. The pinned version establishes what this discussion examines. The ten-minute boundary is the author’s chosen margin, not a universal safe interval or a guarantee that a scheduler cannot race a transfer.
Ownership survives an interrupted copy
The order “enqueue there, remove here” exposes a possible interruption between the two operations. If the destination accepts the job and the process stops before source removal, another migration attempt or the old scheduler may submit it again. Reversing the order creates a different problem: removing first can lose work if the destination never accepts it. A simple order change cannot manufacture an atomic transfer across independent backends.
The application therefore needs a stable way to identify the intended business operation and reconcile uncertain outcomes. A migration ledger can record the source identity, scheduled time, destination acknowledgement and final disposition. This is a proposed design, not a capability supplied automatically by the gist. The details depend on the backend APIs and the consequences of repeating the job.
Job execution should tolerate duplicates where practical. That can involve checking whether the business operation has already completed before applying its effect. Such a check needs a sound concurrency design for the application; merely describing a job as idempotent does not prove it. No exactly-once execution or delivery guarantee is implied here.
Unreadable payloads require their own disposition. A deserialization failure might reflect a missing record or an incompatible application change. Silently excluding that item from the transfer total does not establish that its obligation disappeared. Record enough diagnostic context for an owner to decide whether to repair, cancel or otherwise reconcile it, without unnecessarily copying sensitive arguments into logs.
Draining can be the better migration
The strongest counterargument to copying is to keep the old workers and scheduler running until their pending population drains. Where schedules are short and both backends remain operable, this avoids writing a transfer procedure for every payload. New work can use the new backend while the old system completes a bounded remainder.
Draining has an operating cost. The old runtime, credentials and job classes must remain usable for the remaining period. Long schedules can extend that period substantially. It also requires evidence that old producers have stopped and the remainder is actually decreasing. An apparently empty ready queue says little about delayed items stored elsewhere.
A mixed strategy can be reasonable: leave imminent work with the old scheduler and explicitly transfer later items. The gist illustrates that kind of boundary without proving it is sufficient for every application. Document what happens if a job crosses the time boundary during migration, and how a second run recognizes work already transferred.
For custom software development, make retirement conditional on reconciliation. Account for transferred, drained, failed and deliberately cancelled items, with an owner for uncertainty. The June interview and later script provide a concrete migration discussion. The application’s completion criterion remains the fate of its pending promises, not the arrival of its new workers.
Paul