| Primary model |
Small queue runtime with Postgres as source of truth plus pluggable broker adapters |
Mature distributed task queue with broad ecosystem/tooling |
Async-first distributed task runner with modular brokers/extensions |
| Async support |
Yes, async-first. Worker, handlers, DB access, and adapters are designed around asyncio |
Not primarily async-first in overall design |
Yes, async-first |
| Built-in broker/backend support |
Redis, SQS, Azure Service Bus, PGMQ, none (in-process) |
Commonly used with Redis/RabbitMQ and other broker/result-backend combinations |
Broker-based, typically via pluggable integrations |
| Redis support |
Yes |
Yes |
Yes, depending on broker plugin/config |
| SQS support |
Yes |
Yes |
Possible via broker integration/plugin, but not the default mental model |
| Azure Service Bus support |
Yes |
Not a standard first-class default choice in the same way as Redis/RabbitMQ |
Possible via ecosystem extension/custom broker, but not a core default pairing |
| No-broker / in-process mode |
Yes via queue_backend="none" |
Not the usual model |
Not the main model |
| Source of truth for job state |
Postgres |
Broker/backend-centric task system, optionally paired with result backends |
Broker/message-centric |
| Pydantic support |
Yes — enqueue() accepts BaseModel, dispatch() validates annotated payload models via model_validate() |
Available via explicit task-side support |
Works naturally with typed task signatures and model parsing patterns |
| Pydantic behavior |
Strict: model serialised to dict on enqueue, model_validate() on dispatch, ValidationError => TerminalError (no retry) |
Usually task-side validation/conversion |
More ergonomic/best-effort style than strict contract-first behavior |
| Result persistence |
Postgres jobs.result JSONB — same DB already tracking job state, no extra infrastructure |
Requires a separate result backend (Redis, DB, RPC, etc.) configured explicitly; off by default |
Requires a separate result backend plugin (e.g. taskiq-redis); pluggable but extra infrastructure |
| Multi-worker distribution |
Yes. Workers compete on claim; one worker owns the in-flight delivery |
Yes |
Yes |
| Worker identity tracking |
Yes — polyqueue_workers registry: worker_id, hostname, pid, current job, heartbeat, status (running/stopped/dead); stale workers reaped by maintenance loop |
Stronger built-in worker identity/inspection model |
Workers exist, but less built-in cluster observability than Celery |
| In-flight ownership model |
Redis lease / SQS receipt handle + visibility timeout / Azure SB lock token + lock renewal |
Broker/worker-managed |
Broker/worker-managed |
| Recovery when worker crashes |
Yes, via lease expiry / visibility timeout / lock expiry and redelivery/reclaim |
Yes |
Yes |
| Detecting "alive but wedged" jobs |
Basic — progress_heartbeat_at + timeout_at in DB; maintenance backstop reconciles timed-out jobs |
Better operational story; mature controls exist |
Typically needs app/broker-level design |
| Time limits |
Yes — per-job max_run_seconds, cooperative timeout via asyncio.wait_for, DB backstop, three strategies (retry/fail/ignore) |
More mature built-in support |
Less central than in Celery's ops model |
| Operational maturity |
Focused, understandable, smaller surface area |
Most mature |
Lighter-weight, async-friendly |
| Best fit |
Internal service jobs where you want simplicity, explicit semantics, and control |
Large-scale general background job platform |
Async Python apps that want typed/ergonomic task execution |