backend / message queues / temporal / 03_temporal_vs_celery.md

Temporal vs Celery vs Other Orchestrators

4 interview angles 5 min read source

Temporal vs Celery vs Other Orchestrators

A frequent interview question: “Why would you pick Temporal over Celery?” The honest answer: they solve different problems, but they overlap enough that teams pick one when they should pick the other.

The one-line distinction

  • Celery / RQ — task queues. Run a function in the background.
  • Temporal / Cadence — workflow engines. Orchestrate a multi-step process durably.
  • AWS Step Functions — managed workflow engine, JSON-defined state machine.
  • Airflow / Prefect / Dagster — DAG-based data pipeline schedulers.
  • Kafka + consumers — event streaming, not workflow orchestration.

Side-by-side

Celery Temporal Airflow Step Functions
Primary use background tasks durable workflows data pipelines (DAGs) cloud workflows
State persistence result backend (optional) always, full history DB (job state only) managed
Retries manual, per-task declarative policy per-task declarative
Long-running (days/weeks) poor fit first-class scheduled DAGs yes (1 year limit)
Code-as-workflow no — DAG of tasks yes (real code) Python DAG definitions JSON/YAML
Survives worker crash mid-step task is re-queued; state lost replay restores state depends on operator yes
Signals / human-in-loop no yes sensors (poll) task tokens
Compensating transactions manual natural (try/except) manual catch states
Operational complexity low (Redis/RabbitMQ) high (Cassandra/ES/server) medium none (managed)

When Celery is the right choice

  • Fire-and-forget tasks: send email, resize image, generate PDF.
  • Short tasks (seconds to a few minutes).
  • No multi-step orchestration with branching/retries between steps.
  • You already run Redis/RabbitMQ.
@celery_app.task(bind=True, max_retries=3)
def send_welcome_email(self, user_id):
    try:
        email_service.send(user_id)
    except SMTPError as exc:
        raise self.retry(exc=exc, countdown=60)

Simple. Fast to set up. No Temporal server to operate.

When Temporal is the right choice

  • Multi-step business workflow where partial failure matters (order fulfillment, KYC, payments).
  • Saga pattern with compensating actions.
  • Long-running workflows (subscription billing every month, 30-day refund window).
  • Need to query running workflows, signal them, inspect state.
  • Need exactly-once execution guarantees with retries.

The classic example where Celery gets painful:

# Celery — you end up writing this manually
@celery_app.task(bind=True)
def order_workflow(self, order_id):
    state = OrderState.objects.get(id=order_id)

    if state.step < 1:
        try:
            charge_id = stripe.charge(...)
            state.charge_id = charge_id
            state.step = 1
            state.save()
        except Exception as e:
            raise self.retry(exc=e, countdown=2 ** self.request.retries)

    if state.step < 2:
        try:
            reserve_inventory(...)
            state.step = 2
            state.save()
        except OutOfStock:
            # compensating: refund the charge
            stripe.refund(state.charge_id)
            state.status = "failed"
            state.save()
            return

    if state.step < 3:
        ...

You’re hand-rolling: durable state, idempotent steps, retry logic, compensating transactions. This is the code Temporal writes for you. In Temporal the equivalent workflow is ~10 lines and the state machine is implicit in normal control flow.

When Airflow / Prefect / Dagster is the right choice

  • Scheduled data pipelines: nightly ETL, hourly aggregation.
  • DAGs where the graph structure is the primary thing.
  • Tasks operate on data assets / datasets.
  • Tight integration with data warehouses / Spark / dbt.

These are pipeline schedulers, not general-purpose orchestrators. Don’t use Airflow for application-level workflows like order processing (you’d be fighting it). Don’t use Temporal for nightly ETL (overkill, awkward scheduling model).

When Step Functions is the right choice

  • Already on AWS.
  • Workflow can be expressed as a state machine (limited control flow).
  • Don’t want to operate any infrastructure.
  • Tight integration with Lambda, SQS, DynamoDB, etc.

Tradeoffs vs Temporal: Step Functions JSON gets unwieldy past ~10 states. Code-as-workflow (Temporal) scales to complex logic better. Step Functions has a 1-year max execution; Temporal is unbounded.

Temporal vs Cadence

Temporal is a fork of Cadence (Uber). The codebases diverged in 2019. Public APIs are similar but not interchangeable. New projects should pick Temporal — it has the larger community, hosted Cloud offering, and more SDK languages.

Migration patterns

Celery → Temporal

Common when teams hit the wall maintaining ad-hoc state in Celery. Migration approach:

  1. Keep existing tasks as Temporal activities unchanged.
  2. Write a workflow that orchestrates them.
  3. Replace Celery chain / chord / group with workflow control flow.
  4. Retire Celery for those use cases; keep it for fire-and-forget.

Many teams run both — Celery for simple jobs, Temporal for complex orchestration.

Kafka → Temporal

Less common. Kafka stays for event streaming; Temporal workflows consume from Kafka via activities. Don’t try to replace Kafka with Temporal — different scaling profiles (Kafka millions/sec, Temporal thousands/sec).

Operational cost

The hidden Temporal cost: the server cluster. Self-hosted Temporal needs:

  • Cassandra (or PostgreSQL/MySQL) for persistence.
  • Elasticsearch for advanced visibility queries (optional).
  • The Temporal server processes (history, matching, frontend, worker services).

This is non-trivial to operate. Temporal Cloud removes this burden at a per-action cost.

Celery is just pip install celery plus Redis. That simplicity is real.

Decision shortcut

Is it a single function call to run in the background?
  → Celery / RQ

Is it a multi-step process where partial failure matters?
  → Temporal

Is it a scheduled data pipeline (DAG over datasets)?
  → Airflow / Prefect / Dagster

Are you on AWS and the workflow fits a state machine?
  → Step Functions

Is it streaming event processing?
  → Kafka + consumers

See 01_what_is_temporal.md for Temporal core concepts and ../celery/01_what_is_celery.md for Celery details. The saga pattern in ../../13_architecture_design/15_event_driven_saga.md covers the orchestration-vs-choreography angle.

Interview angle

  • “Temporal vs Celery — when do you use each?” — Celery for fire-and-forget background tasks; Temporal for multi-step orchestrated processes where state durability and partial-failure recovery matter. Often used together: Celery for simple jobs, Temporal for complex workflows.
  • “Why not just use a state machine in your database?” — that’s what you end up building manually. Temporal generalizes it: history persistence, retry policies, timeouts, signals, queries, versioning — all standardized so you write business logic, not orchestration plumbing.
  • “What’s the cost of using Temporal?” — operational overhead (Cassandra/ES, server cluster) if self-hosted, or per-action billing on Temporal Cloud. Learning curve around determinism rules. Worth it when the alternative is hand-rolled state machines in 10 services.
  • “Could Step Functions replace Temporal?” — for workflows expressible as state machines on AWS, yes. For complex logic requiring branching, loops, signals, and version migration of running workflows, Temporal’s code-as-workflow model scales better. Step Functions has a 1-year execution cap; Temporal is unbounded.