RabbitMQ¶
app = Potatoq(
"proj",
broker="amqp://user:pass@rabbit:5672//",
backend="redis://localhost:6379/0", # RabbitMQ can't store results
)
Requirements
- Server: RabbitMQ 4.0 or newer; 4.3 recommended (per-task consumer timeouts, and tasks given back don't count as deliveries). CI tests 4.0 and 4.3.
- Install:
uv add "potatoq[rabbitmq]"(pika).
Why it's good¶
- Quorum queues: replicated, with a delivery limit (poison-message guard) and an
at-least-once dead-letter queue per queue (
<queue>.dlq). - Publisher confirms on every publish:
delay()returns only once the broker has the task. - Delays without plugins (the delayed-message plugin is archived and doesn't work on 4.3): a 28-level TTL cascade carries countdowns and retries for up to ~8.5 years at 1-second precision.
- One message per idle process, and a consumer timeout set above your longest task time limit, so long tasks aren't interrupted by RabbitMQ's 30-minute default (RabbitMQ 4.3+, see below).
Things to know¶
- Queue names can't contain
.(they become routing-key words for delays). - Queues are created on first use as quorum queues. If a queue of the same name already exists with different arguments (for example a classic queue created by Celery), potatoq refuses to use it and tells you so. Pick a new queue name.
- Results, revocation, chords and
potatoq statusneed aresult_backend(Redis, Postgres or SQLite). - Without a result backend, a task killed for exceeding its hard time limit is redelivered until the delivery limit. The worker warns about this at startup.
- Before RabbitMQ 4.3, a task given back to the queue counts towards its delivery limit
(
task_max_deliveries, 5 by default). A task still running when a warm shutdown runs out of time is given back, so a task cut short by five restarts in a row is dead-lettered. From 4.3, giving a task back doesn't count. - Periodic tasks are sent by one elected worker. If it dies, a new one takes over within about 1 s, or about 60 s if its host or network went down. Runs due in that window can be skipped, or occasionally sent twice (details).
Long tasks and the consumer timeout¶
RabbitMQ gives up on a delivery that stays unacknowledged longer than its consumer
timeout (30 minutes by default) and hands the message to another worker, while the
first one is still running it. potatoq asks for a timeout 5 minutes above the longest
time limit of the tasks the worker has registered, but RabbitMQ only honours that from
4.3. On older servers, the server-wide consumer_timeout applies: raise it in
rabbitmq.conf, or set a policy on potatoq's queues (merge it with any policy they
already have, only one applies per queue):
$ rabbitmqctl set_policy potatoq-timeout "^(?!potatoq\.)" '{"consumer-timeout": 18300000}' --apply-to quorum_queues
A time limit passed per call (apply_async(time_limit=...)) can't be known in advance:
keep those below the longest limit set on a task. If RabbitMQ does cancel a worker's
consumer, the worker logs it and reconnects.
One app per vhost¶
The scheduler's leader token (potatoq.scheduler.leader), the delay exchanges
(potatoq.delay.*) and the dead-letter exchange are shared by every potatoq app on a
vhost. Two apps on one vhost means only one of them sends its periodic tasks, so give
each app (and each environment, like staging and production) its own vhost:
amqp://user:pass@rabbit:5672/myapp-production.