Hashlogics
Tool review

Celery

Reliable once you stop trusting the defaults

Celery runs the background work behind a voice agent that calls suppliers for parts quotes while the phone line stays free. It does the job well. Getting there took explicit acks, a memory ceiling on every worker, and a rule against ever letting a task assume it ran once.

The short answer

Celery is the right default for background jobs in a Python backend, provided you configure acknowledgements, retries and worker memory limits yourself rather than trusting what ships out of the box.

The library is mature and the documentation is thorough. But several defaults exist for backward compatibility, not safety. A job can be marked done before it actually finishes. A worker can leak memory for weeks before anyone notices. A task can silently run twice. None of these are bugs. They are settings, and the fix is knowing which ones to change before load finds them for you.

What decides this

4 things that decide this

  1. 01Celery is a task queue for Python: a web process hands off slow work, and one or more workers pick it up and run it separately.
  2. 02The default acknowledgement setting marks a task done as soon as a worker receives it, not when it finishes, so a worker crash mid-task can lose the job silently.
  3. 03Long-running workers accumulate memory over weeks unless you cap tasks per worker, because Python's allocator does not always return freed memory to the OS.
  4. 04Celery gives you retries and scheduling out of the box. It does not give you exactly-once execution. That is on you to build.
What it is

A task queue, not a message broker

Celery is the standard way a Python backend moves work off the request path. A web process publishes a task, a broker holds it, and a separate worker process picks it up and runs it. Redis or RabbitMQ usually plays the broker. Celery itself is the layer above that: retries, scheduling, rate limits and task routing.

That separation is the whole reason to reach for it. Anything slow, or dependent on a third party, belongs in a worker rather than blocking a response the user is waiting on. The trade is operational. You now run two kinds of process, and the queue between them can hide problems the request-response cycle used to surface right away.

Where it holds up and where it does not

Strengths

  • Retries with backoff are a few lines of configuration, not something you hand-roll, and they cover the common case of a flaky downstream call cleanly.
  • Celery Beat schedules recurring jobs from the same codebase as the tasks themselves, so a cron job and its logic never drift apart in two different places.
  • Routing tasks to named queues lets you give slow, low-priority work its own workers, so a batch of supplier calls never starves the queue handling something time-sensitive.
  • The ecosystem is old enough that most failure modes are documented somewhere, which matters more than it sounds once you are debugging a stuck queue at night.

Trade-offs

  • The default acknowledgement setting confirms receipt, not completion. A worker that dies mid-task can lose that task with no error anywhere, and this bit us before we switched to late acknowledgement.
  • Worker memory grows over days of continuous operation unless you set a task limit per worker process and let it recycle. Left unset, a long-running worker eventually gets killed by the host and takes its in-flight jobs with it.
  • Celery assumes at-least-once delivery, which means at-least-once execution unless the task itself is written to be safe to run twice. A supplier call that is not built idempotent can go out twice under a retry.
  • Observability is thin without extra tooling. Flower helps for a dashboard view, but knowing why a specific task is stuck usually means reading logs across two or three processes by hand.
From our build

What we use it for

ZhoopZhoop is a voice-agent platform for a multi-branch auto repair business. Inbound agents answer customer calls and book appointments. A separate outbound flow works through a queue of parts requests. It calls suppliers one at a time, collects availability and pricing, then hands back a comparison. That outbound work runs on Celery, with Redis as the broker, behind a FastAPI backend.

Putting supplier calls in a queue rather than a request handler was the point from day one. A supplier call can take a minute, or run into a busy line. None of that should hold up a customer on the phone or block the front desk dashboard. It also gave us a natural place to enforce order. One call to a given supplier finishes before the next one starts, instead of racing several requests against the same phone line.

A single deploy taught us the most. A worker restart mid-deploy silently dropped two in-flight supplier calls, because the default acknowledgement had already marked them done. We moved to late acknowledgement and added a visibility timeout on the Redis broker. Every outbound task now checks whether a quote already exists before placing the call again.

Where a Celery queue loses a jobLive
  1. Task dispatchedWeb process hands off, does not wait
  2. Worker acks earlyDefault setting confirms receipt, not completion
  3. Worker dies mid-taskDeploy, OOM kill, or a crash
  4. Task is goneBroker already marked it delivered
  5. No retry firesNothing knows the job failed

Switching to late acknowledgement closes this path, at the cost of a task running twice if the worker dies after finishing but before confirming. That is the trade you are actually choosing, not a bug you can avoid.

The JS-side contrast

Celery against BullMQ

TrialTriage, a clinical trial matching platform on a Node backend, runs the equivalent background work on BullMQ instead. Both sit on Redis and solve the same problem: keep slow work off the request path and survive a worker restart. The difference is less about capability and more about which ecosystem the rest of the backend already lives in.

BullMQ's job states and events are easier to inspect from the same process that enqueued them, since everything stays in JavaScript. Celery's strength is scheduling and retry policy proven over a decade. It also fits naturally when the rest of the backend, like FastAPI, is already Python. Pick based on the language your team already owns, not a feature checklist. Both queues fail the same way if you skip idempotency.

Questions, answered

What teams ask before committing

01Is Celery still the right choice in 2026?

For a Python backend, yes. It remains the default task queue, with the widest documentation and the most mature retry and scheduling tooling. The decision that matters more than picking Celery is configuring acknowledgement, worker memory limits and idempotency correctly, since the defaults favour compatibility over safety.

02Celery or BullMQ?

Match the queue to the backend language rather than treating this as a feature comparison. Celery fits a Python service. BullMQ fits Node. Both run on Redis and both need the same discipline around retries and idempotent tasks, so switching one for the other does not remove the work.

03How do you stop a Celery worker from leaking memory?

Set a per-worker task limit so the process recycles after a fixed number of tasks, rather than running indefinitely. Python does not always return freed memory to the OS, so a long-lived worker under continuous load grows until something kills it. Recycling on a schedule you control is safer than finding out from an outage.

04Does Celery guarantee a task runs only once?

No. Celery's delivery model is at-least-once, which means a task can run twice under certain failure conditions, particularly after a worker crash. The task itself has to check whether the work already happened, such as confirming a quote does not already exist before placing another supplier call.

Written by Abdul Basit, CEO, HashlogicsVerified
Start

Let’s build the one that runs after.

We build AI agents and automation, then stay on under an agreed service level. A senior engineer reads every brief, and your call gets scheduled within 24 hours.

What happens next

  1. 01

    You send a brief or book a call

    Two minutes, whichever you prefer.

  2. 02

    A senior engineer replies within 24 hours

    Not a sales rep.

  3. 03

    Honest scoping, in writing

    And if we’re not the right fit, we say so.

Abdul Basit, CEO of Hashlogics

“I started Hashlogics because too many teams ship a demo, get paid, and disappear. We build to a standard we’d run ourselves — and we stay to keep it running.”

Abdul Basit · CEO · a direct line

Not ready to talk? Take the checklist.

12 questions to ask any AI agency before you sign. They separate a demo shop from a team that ships to production.

Get the checklist

Free · no newsletter