Skip to content

Job Queue Concepts — Complete Guide

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Job Queue Concepts. We cover key concepts, practical examples, and best practices to help you master this topic.

Learn job queue fundamentals: FIFO vs priority queues, job serialization, queue backends (Redis, RabbitMQ, SQS), and queue lifecycle management.

What You Learn

You will learn how job queues work, different queue types (FIFO, priority, delayed), backend options, queue lifecycle, and how to manage queue backpressure.

Why It Matters

The queue is the heart of any background job system. Choosing the right queue type and backend determines throughput, reliability, and scalability. Understanding queue concepts helps you design better systems.

Real-World Use

DodaTech uses FIFO queues for sequential operations (video transcoding), priority queues for time-sensitive tasks (alerts), and delayed queues for scheduled operations (backup).

Queue Types

flowchart TB
    subgraph "Queue Types"
        Q1[FIFO: First In, First Out]
        Q2[Priority: High first]
        Q3[Delayed: Scheduled time]
        Q4[Dead Letter: Failed jobs]
    end
    Q1 --> |Simple, predictable| U1[Video transcoding]
    Q2 --> |Urgent jobs jump queue| U2[Alert notifications]
    Q3 --> |Execute at specific time| U3[Backup at midnight]
    Q4 --> |Failed jobs for review| U4[Payment failures]

FIFO Queue Implementation

import redis
import json
import time

r = redis.Redis()

class FIFOQueue:
    def __init__(self, name='fifo'):
        self.name = name

    def enqueue(self, job):
        r.lpush(self.name, json.dumps(job))

    def dequeue(self, block=True, timeout=5):
        if block:
            _, data = r.brpop(self.name, timeout=timeout)
        else:
            data = r.lpop(self.name)
        return json.loads(data) if data else None

    def size(self):
        return r.llen(self.name)

q = FIFOQueue()
q.enqueue({'id': 1, 'task': 'email'})
q.enqueue({'id': 2, 'task': 'report'})
q.enqueue({'id': 3, 'task': 'backup'})

print(f"Queue size: {q.size()}")
job = q.dequeue(block=False)
print(f"Dequeued: {job['task']} (id={job['id']})")
print(f"Queue size: {q.size()}")

Expected output:

Queue size: 3
Dequeued: email (id=1)
Queue size: 2

Priority Queue Implementation

import redis
import json
import time

r = redis.Redis()

class PriorityQueue:
    def __init__(self, name='priority', max_priority=10):
        self.name = name
        self.max_priority = max_priority

    def enqueue(self, job, priority=5):
        score = priority
        r.zadd(self.name, {json.dumps(job): score})

    def dequeue(self):
        jobs = r.zpopmin(self.name, 1)
        if jobs:
            return json.loads(jobs[0][0])
        return None

    def dequeue_highest(self):
        jobs = r.zpopmax(self.name, 1)
        if jobs:
            return json.loads(jobs[0][0])
        return None

    def size(self):
        return r.zcard(self.name)

pq = PriorityQueue()
pq.enqueue({'task': 'backup'}, priority=1)
pq.enqueue({'task': 'alert'}, priority=9)
pq.enqueue({'task': 'report'}, priority=5)

job = pq.dequeue_highest()
print(f"Highest priority: {job['task']}")

Expected output:

Highest priority: alert

Delayed Queue

import redis
import json
import time

r = redis.Redis()

class DelayedQueue:
    def __init__(self, name='delayed'):
        self.name = name

    def enqueue(self, job, delay_seconds):
        execute_at = time.time() + delay_seconds
        r.zadd(self.name, {json.dumps(job): execute_at})

    def get_ready(self):
        now = time.time()
        jobs = r.zrangebyscore(self.name, 0, now)
        if jobs:
            r.zremrangebyscore(self.name, 0, now)
        return [json.loads(j) for j in jobs]

    def size(self):
        return r.zcard(self.name)

dq = DelayedQueue()
dq.enqueue({'task': 'backup'}, delay_seconds=10)
dq.enqueue({'task': 'cleanup'}, delay_seconds=5)

time.sleep(6)
ready = dq.get_ready()
print(f"Ready jobs: {[j['task'] for j in ready]}")

Expected output:

Ready jobs: ['cleanup']

Queue Backends Comparison

Feature Redis RabbitMQ Amazon SQS
Type In-memory Message Broker Managed
Persistence Optional (RDB/AOF) Durable queues Built-in
Priority Via sorted sets x-max-priority Not supported
Delayed Via sorted sets TTL + DLX Delay queues
Dead letter Custom implementation Built-in DLX Redrive policy
Throughput Very high (100K+/s) High (10K+/s) High (unlimited)
Operational overhead Low Medium None

Queue Backpressure

import redis
import time

r = redis.Redis()

class BackpressureQueue:
    def __init__(self, name='bp_queue', max_size=100):
        self.name = name
        self.max_size = max_size

    def enqueue(self, job, on_full='block'):
        size = r.llen(self.name)
        if size >= self.max_size:
            if on_full == 'block':
                while r.llen(self.name) >= self.max_size:
                    print(f"Queue full, waiting...")
                    time.sleep(1)
            elif on_full == 'discard':
                print(f"Queue full, discarding job")
                return False
            elif on_full == 'error':
                raise Exception("Queue full")
        r.lpush(self.name, job)
        return True

bp = BackpressureQueue(max_size=3)
for i in range(5):
    bp.enqueue(f"job_{i}", on_full='discard')

Expected output:

Queue full, discarding job
Queue full, discarding job

Common Mistakes

1. Using Only One Queue

One queue for all job types creates head-of-line blocking. A slow email job blocks critical alert jobs. Use separate queues per priority or job type.

2. Not Setting Queue Size Limits

Without max size, queues grow unbounded. A stuck worker causes billions of jobs to accumulate, exhausting broker memory.

3. Ignoring Queue Persistence

Redis without persistence loses all queued jobs on restart. Use Redis AOF or switch to RabbitMQ/SQS for durable queues.

4. Polling Instead of Blocking

Polling with time.sleep wastes resources. Use blocking pop operations (brpop in Redis, basic_consume in RabbitMQ) for efficient queue consumption.

5. Not Monitoring Queue Depth

Queue depth is the most important metric. A growing queue means workers cannot keep up. Set alerts on queue depth thresholds.

Practice Questions

1. What is the difference between FIFO and priority queues?

FIFO processes jobs in submission order. Priority queues Process higher-priority jobs first, regardless of submission order.

2. How do delayed queues work?

Jobs have a scheduled execution time. The queue holds them until that time passes, then makes them available for workers.

3. What is a dead letter queue?

A queue for jobs that failed permanently. Instead of retrying forever, failed jobs go to the dead letter queue for manual inspection.

4. What is queue backpressure?

A mechanism to prevent queue overgrowth. When the queue reaches max size, the producer either blocks, discards, or errors.

Challenge

Design a queue Strategy for a payment processing system: transactions (FIFO, no reordering), fraud alerts (priority 9, immediate), settlement reports (delayed 24h), failed payments (dead letter with 7-day retention). Choose appropriate backends and queue configurations.

FAQ

What is the best queue backend for most applications?

Redis. It is simple, fast, and supports most queue patterns. Use RabbitMQ when you need complex routing. Use SQS when you want zero operations.

Can I have multiple queues with one Redis instance?

Yes. Each queue is a separate Redis key (list or sorted set). One Redis instance can handle hundreds of queues.

How do I handle queue migration?

Run both queues simultaneously. Write to the new queue while still reading from the old. Gradually drain the old queue.

What happens to jobs when the queue is deleted?

All jobs in the queue are lost. Never delete a queue with pending jobs. Drain it first or verify it is empty.

How many jobs can a single queue hold?

Redis: millions (limited by memory). RabbitMQ: limited by disk space. SQS: unlimited. Monitor usage and set limits.

Mini Project: Multi-Queue System

import redis
import json
import time
import threading

r = redis.Redis()

class QueueManager:
    def __init__(self):
        self.queues = {}

    def create_queue(self, name, queue_type='fifo', **opts):
        self.queues[name] = {
            'type': queue_type,
            'opts': opts,
            'key': f"queue:{name}",
        }

    def enqueue(self, queue_name, job, **opts):
        q = self.queues[queue_name]
        job_data = json.dumps(job)

        if q['type'] == 'fifo':
            r.lpush(q['key'], job_data)
        elif q['type'] == 'priority':
            priority = opts.get('priority', 5)
            r.zadd(q['key'], {job_data: priority})
        elif q['type'] == 'delayed':
            delay = opts.get('delay', 0)
            score = time.time() + delay
            r.zadd(q['key'], {job_data: score})

    def dequeue(self, queue_name):
        q = self.queues[queue_name]
        key = q['key']

        if q['type'] == 'fifo':
            data = r.brpop(key, timeout=5)
            return json.loads(data[1]) if data else None
        elif q['type'] == 'priority':
            jobs = r.zpopmax(key, 1)
            return json.loads(jobs[0][0]) if jobs else None
        elif q['type'] == 'delayed':
            jobs = r.zrangebyscore(key, 0, time.time())
            if jobs:
                r.zremrangebyscore(key, 0, time.time())
                return json.loads(jobs[0])
            return None

    def size(self, queue_name):
        key = self.queues[queue_name]['key']
        q = self.queues[queue_name]
        if q['type'] == 'fifo':
            return r.llen(key)
        return r.zcard(key)

qm = QueueManager()
qm.create_queue('critical', 'priority')
qm.create_queue('default', 'fifo')
qm.create_queue('delayed', 'delayed')

qm.enqueue('critical', {'task': 'alert'}, priority=9)
qm.enqueue('default', {'task': 'email'})
qm.enqueue('delayed', {'task': 'backup'}, delay=10)

print(f"Critical size: {qm.size('critical')}")
print(f"Default size: {qm.size('default')}")

Expected output:

Critical size: 1
Default size: 1

What's Next

Now that you understand job queue concepts, explore worker processes for executing jobs, then learn about specific tools like Bull Queue for Node.js.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro