Background Jobs & Queues

BullMQ vs Sidekiq vs Celery — Picking a Job Queue in 2026

By Domain India Team · DomainIndia EngineeringPublished 10 min read
Knowledge base article
Contents (17 sections)

Almost every production app needs a background job queue: sending email, generating PDFs, calling webhooks, running scheduled tasks. This guide compares the main options by language, features, tooling and how much work they are to run, and explains where each one can run on Domain India.

Key takeaways

Pick the queue that matches your language: BullMQ for Node.js, Sidekiq for Ruby, Celery (or RQ) for Python, Laravel Queues with Horizon for PHP, and Temporal when you need durable multi-step workflows. All of them need a long-running worker process, and most need Redis. On Domain India, run workers and Redis on a VPS: long-running processes are stopped on shared hosting, and neither shared hosting nor the App Platform offers Redis.

1. The six choices

QueueLanguageBackendMonitoring UIBest for
BullMQNode.jsRedisBull BoardNode and TypeScript apps
SidekiqRubyRedisBuilt inRails apps
CeleryPythonRedis or RabbitMQFlowerDjango, Flask, FastAPI
Laravel Queues / HorizonPHPRedis, database, SQSHorizon (Redis only)Laravel apps
TemporalAny (SDKs)PostgreSQL, MySQL or CassandraTemporal Web UIDurable multi-step workflows
AWS SQS + LambdaAnyAWS-managedCloudWatchServerless apps

Raw throughput is rarely the deciding factor. On a small server, most of these handle far more jobs than a typical business app produces; your database and the external APIs your jobs call usually become the bottleneck first. Developer experience and operational maturity matter more. If throughput does matter, benchmark your own job on your own hardware.

2. Decision tree

1. What is your primary language?

  • Node.js → BullMQ
  • Ruby/Rails → Sidekiq, or Solid Queue (the database-backed default in Rails 8)
  • Python → Celery (most common) or RQ (simpler)
  • PHP → Laravel Queues, with Horizon for monitoring
  • Several languages → Temporal or SQS

2. Do you need durable workflows (multi-step, long waits, human approval)?

  • Yes → Temporal
  • No → a standard queue is enough

3. Where will the workers run?

  • Every option here needs a worker process that stays running. That rules out shared hosting, where processes running outside the web server are stopped.
  • VPS → any of them, with Redis installed alongside.
  • Serverless → SQS + Lambda, Cloudflare Queues, or an HTTP-based queue such as Upstash QStash that calls your endpoint.

3. BullMQ: the Node.js choice

The successor to the bull package, written in TypeScript.

Strengths:

  • Good TypeScript types
  • Rich features: rate limiting, priorities, backoff, repeatable jobs, flows (parent and child jobs)
  • Bull Board for monitoring

Weaknesses:

  • Redis only (or a Redis-compatible server)
  • Some advanced features (groups) are in the paid BullMQ Pro

Example:

typescript
import { Queue, Worker } from 'bullmq';
import IORedis from 'ioredis';

const connection = new IORedis({ maxRetriesPerRequest: null });

const emailQueue = new Queue('email', { connection });

await emailQueue.add('welcome', { userId: 42 }, {
  attempts: 3,
  backoff: { type: 'exponential', delay: 1000 },
});

// Worker process (runs separately and stays running)
new Worker('email', async (job) => {
  console.log(`Sending welcome to user ${job.data.userId}`);
  await sendEmail(job.data.userId);
}, { connection, concurrency: 10 });

4. Sidekiq: the Ruby default

Around since 2012 and still the standard for Rails. See our Sidekiq article.

Strengths:

  • Very reliable
  • Rich ecosystem (sidekiq-cron, sidekiq-unique-jobs)
  • Commercial Pro and Enterprise tiers for batches, rate limiting and more
  • Good web UI built in

Weaknesses:

  • Ruby only
  • Some powerful features are in the paid tiers

Rails 8 also ships Solid Queue, which stores jobs in your database instead of Redis. It is a good choice when you don't want to run Redis, though it still needs a worker process.

5. Celery: the Python standard

python
from celery import Celery

app = Celery('myapp', broker='redis://localhost:6379/0')

@app.task(bind=True, max_retries=3)
def send_email(self, user_id):
    try:
        do_send(user_id)
    except Exception as exc:
        raise self.retry(exc=exc, countdown=60)

# Call
send_email.delay(42)

Strengths:

  • Several brokers (Redis, RabbitMQ, Amazon SQS)
  • Beat scheduler built in
  • Large ecosystem
  • Works with Django, Flask and FastAPI

Weaknesses:

  • Many configuration options to understand
  • Debugging can be tricky
  • Behaviour changes between major versions

Lighter alternative: RQ

python
from rq import Queue
from redis import Redis

q = Queue(connection=Redis())
q.enqueue(send_email, 42)

RQ is simpler when you don't need Celery's features.

6. Laravel Queues and Horizon (PHP)

php
class SendWelcomeEmail implements ShouldQueue
{
    use Queueable;

    public function __construct(public User $user) {}

    public function handle(): void {
        Mail::to($this->user)->send(new WelcomeMail());
    }
}

SendWelcomeEmail::dispatch($user)->onQueue('emails');

Strengths:

  • Built into Laravel; little configuration for new apps
  • Horizon dashboard for monitoring and tagging (Redis queues)
  • Drivers for Redis, database, SQS and Beanstalkd

Weaknesses:

  • PHP only
  • queue:work must run as a long-lived process under a supervisor

7. Temporal: workflows, not queues

A different model. You write workflows as code, and Temporal persists their state so they survive crashes and can wait for days.

typescript
// Simplified sketch of a workflow; the real SDK uses proxyActivities() and condition()
export async function processOrder(orderId: string) {
  await validatePayment(orderId);      // each step is retried on its own
  await reserveInventory(orderId);
  await waitForSignal('payment-confirmed', '30 days');  // pause for an external event
  await shipOrder(orderId);
}

Strengths:

  • Multi-step workflows with pause and resume
  • State is persisted automatically
  • Workflow history can be replayed for debugging
  • SDKs for Go, Java, TypeScript, Python, PHP and .NET

Weaknesses:

  • You run a Temporal server and its database (or pay for Temporal Cloud)
  • Overkill for simple "send an email" jobs
  • A real learning curve

Best for: multi-step business processes such as order fulfilment and onboarding flows.

8. Feature comparison

FeatureBullMQSidekiqCeleryLaravel
Rate limitingYesEnterprisePer-task rate_limitJob middleware
Scheduled jobsJob schedulerssidekiq-cron or EnterpriseBeatScheduler
Retries with backoffYesYesYesYes
Priority queuesYesYesYesYes
Unique jobsJob ID / deduplicationEnterprise or pluginPluginShouldBeUnique
Batches (callback when N done)FlowsProCanvas (chords)Bus batches
Monitoring UIBull BoardBuilt inFlowerHorizon
Failed jobs keptYesYes (dead set)Result backendfailed_jobs table

9. Choose by pain point

"I just need to send emails in the background":

  • Laravel: built-in queues
  • Rails: deliver_later with Solid Queue or Sidekiq
  • Node: BullMQ
  • Django: Celery with Redis

"I need scheduled, cron-like tasks":

  • BullMQ: queue.upsertJobScheduler()
  • Sidekiq: the sidekiq-cron gem
  • Celery: Beat
  • Laravel: Schedule::job(...) in routes/console.php

"I need to orchestrate multi-step business flows": Temporal.

"I'm on serverless (AWS Lambda, Vercel)": SQS + Lambda, or Upstash QStash (an HTTP-based queue).

"I want as little operations work as possible": a managed Redis service with BullMQ, Cloudflare Queues with Workers, or AWS SQS with Lambda.

10. Patterns that apply everywhere

Idempotency

Jobs can run twice (for example, a retry after a timeout). Make them safe to repeat:

python
def send_welcome(user_id):
    # Check if already sent
    if db.email_log.exists(user_id=user_id, type='welcome'):
        return
    do_send()
    db.email_log.create(user_id=user_id, type='welcome')

Timeouts

Every job needs a maximum run time; hangs and infinite loops happen. BullMQ has no per-job timeout option, so enforce one inside the processor:

typescript
// BullMQ: abort the work after 30 seconds
new Worker('email', async (job) => {
  const signal = AbortSignal.timeout(30_000);
  await sendEmail(job.data.userId, { signal });
}, { connection });
python
# Celery: hard and soft time limits per task
@app.task(time_limit=60, soft_time_limit=50)
def generate_report(report_id):
    ...

Retry with exponential backoff

python
# Celery
@app.task(bind=True, autoretry_for=(Exception,), retry_backoff=True, max_retries=5)
def process(self, data):
    ...

Dead-letter handling

When a job fails all its retries, keep it somewhere you can inspect (failed set, dead set or a failed_jobs table) instead of letting it vanish, and review it by hand.

11. Monitoring essentials

Whatever queue you pick, watch:

  • Queue depth: backlog size over time
  • Processing rate: jobs per second
  • Failure rate: failed against succeeded
  • Age of the oldest waiting job: are you keeping up?
  • Dead-letter count: something is wrong

See our observability article for a monitoring stack.

12. Common pitfalls

Large payloads in the job
10 MB of JSON in Redis is slow. Pass an ID and fetch the data inside the job.
No idempotency
Retried jobs cause duplicate effects: two emails, two charges.
No job timeout
A hung job blocks a worker indefinitely.
Workers overloading the database
50 concurrent workers each running queries can exhaust the database. Tune concurrency against your connection pool.
Enabling pickle in Celery
Celery uses JSON by default. Don't switch to pickle for data you don't fully trust; it allows code execution.
Long-running jobs block deploys
A 10-minute job holds up a graceful restart. Split work into smaller jobs.

13. Running job queues on Domain India

Workers do not run on shared hosting

On Domain India shared hosting (cPanel, DirectAdmin and Webuzo), long-running processes such as queue workers, BullMQ, Sidekiq, Celery and WebSocket servers are stopped, because processes running outside the web server are reaped. Redis and Memcached are not available on any shared plan. Shared hosting is for request/response apps.

  • VPS: the right home for workers and Redis. Domain India VPS plans are self-managed with full root access on KVM, so you install Redis, your workers and a process manager (systemd) yourself. VPS plans start from ₹553 a month, excluding 18% GST (Domain India list price on 30 September 2026). No backups or snapshots are included, so plan your own backups, including Redis persistence if your jobs matter.
  • App Platform: deploys web apps from GitHub or with a deploy token (Node.js is detected automatically; other languages need a Dockerfile). It includes PostgreSQL but has no Redis add-on, so Redis-backed queues need Redis somewhere else.
  • Shared hosting: cron jobs run at most every 4 minutes, so cron is not a substitute for a real-time queue. If you need something light, do the work inside the request, or push it to an external HTTP-based queue that calls a URL on your site.
Can I run a job queue on Domain India shared hosting?

No. Queue workers such as BullMQ, Sidekiq and Celery are long-running processes, and those are stopped on Domain India shared hosting. Redis is also not available on shared plans. Run workers and Redis on a VPS.

How many workers can I run on a 2 GB VPS?

It depends on the job. CPU-heavy jobs: start with about as many workers as CPU cores. I/O-heavy jobs (API calls, email): 10 to 20 concurrent jobs is a common starting point. Start small, watch CPU, RAM and database connections, then scale.

Do I need a separate queue server?

Not for small apps; Redis can run on the same VPS as your app. For fault isolation at larger scale, move Redis to its own server.

What happens if Redis crashes with jobs in flight?

With AOF persistence enabled, Redis restores its data after a restart; with the common everysec setting you can lose up to about a second of writes. For critical jobs, also keep a database record that you mark complete only after the job succeeds.

What is the difference between a workflow engine and a queue?

A queue runs short, isolated tasks. A workflow engine such as Temporal runs a multi-step process whose state survives crashes and long waits. You can chain queue jobs to imitate a workflow, but workflow engines are built for it.

Does the Domain India App Platform include Redis?

No. The App Platform includes PostgreSQL but has no Redis add-on. For Redis-backed queues, use a VPS.

Ready to run production queues? Set up Redis and your workers on a Domain India VPS, or ask us through a support ticket which option fits your app.

Run your workers on a VPS

Full root access to run Redis, queue workers and schedulers the way your app needs.

See VPS plans

Was this article helpful?

Your answer helps us decide what to improve next.

Still need help? Open a support ticket and our team will reply.

Prefer an app? Add this site to your home screen.Get the app