Upgrading to 0.2
0.2.0 changed the broker topology. Code changes are small; the topology needs a decision.
x-max-priority is a breaking topology change
Section titled “x-max-priority is a breaking topology change”Queues are now declared with x-max-priority = 10 by default, because that is
what lets a deferred job come back ahead of the
backlog.
x-max-priority is a declaration argument, and RabbitMQ refuses to change the
arguments of a queue that already exists. A queue declared by 0.1 has no
priority argument, so redeclaring it with one closes the channel with
PRECONDITION_FAILED.
Two ways forward:
Recreate the queues. Drain each queue first, delete it, and let the next
declare recreate it with priorities. You get the full behaviour.
Or turn priorities off on the existing queues.
#[queue(max_priority = 0)]Emails,Deferral still works — the job waits exactly as long as it should — but held jobs come back FIFO instead of ahead of the backlog. This is the no-downtime option, and it can be reversed later whenever recreating the queue is convenient.
q.retry is gone
Section titled “q.retry is gone”0.1 held retries in a single q.retry queue with per-message TTLs. That queue
is no longer declared or used, and RabbitMqOptions::retry_suffix has been
removed with it.
Retries now go into the same per-delay hold queues as deferrals,
q.deferred.{ttl_ms}, for a concrete reason: RabbitMQ expires messages from
the head of a queue, so in a shared retry queue a message with a long TTL
blocks every shorter one behind it. Exponential backoff produces a wide spread
of delays, which is the case that suffers most.
Replace retry_suffix with retry_granularity, which controls how delays are
rounded and therefore how many hold queues can exist at once:
RabbitMqOptions::default().retry_suffix(".retry")
// 0.2RabbitMqOptions::default().retry_granularity(Duration::from_secs(1))After upgrading, q.retry is left over. Drain it before deleting it: anything
still in there is a retry that has not fired yet. Messages in it are ordinary
envelopes, so they can also be shovelled onto q to run immediately.
deferred_queue_grace is gone
Section titled “deferred_queue_grace is gone”RabbitMqOptions::deferred_queue_grace no longer exists. x-expires is now
fixed at 2 * ttl_ms, computed from the name alone.
That is deliberate rather than an oversight. Hold-queue arguments have to be a
pure function of the queue name, so that every process declares the same queue
identically and no two processes can deadlock each other with
PRECONDITION_FAILED. A tunable grace period would break that.
In-flight messages are compatible
Section titled “In-flight messages are compatible”Envelopes written by 0.1 decode fine: deferrals and priority are
#[serde(default)], so an older envelope reads as zero deferrals at normal
priority. You do not need to drain your work queues to upgrade.
Suggested order
Section titled “Suggested order”- Decide the priority question above, and set
max_priority = 0on existing queues if you are not recreating them. - Replace
retry_suffixwithretry_granularity, and delete any use ofdeferred_queue_grace. - Deploy the workers.
- Deploy the producers.
- Drain and delete the leftover
q.retryqueues.
Workers before producers, as always: a producer that enqueues a job type no worker recognises gets it dead-lettered. See Dead letters.
New in 0.2 and worth adopting
Section titled “New in 0.2 and worth adopting”- Deferral, both handler-side
(
JobError::deferred_msg) and producer-side (Producer::defer). #[queue(max_priority = ...)].JobContext::deferralsandJobContext::priority.MemoryBackend::deferred()for asserting on held jobs in tests.