→ From Coder to Shipper
From Coder to Shipper · Extras

Message Queues

The layer that decouples who SENDS work from who DOES it — a line of messages where a producer drops tasks and consumers pick them up when ready.

← / → to navigate · EN/VI switch below

The basics

What is a message queue?

A first-in-first-out line of small messages, standing between the part of the app that creates work and the part that does it.

A line of messages

Messages (usually small JSON strings) line up: first in, first out. The queue is just storage — a waiting line.

Producer → queue → consumer

A producer PUBLISHES a message; a consumer CONSUMES it later. They never call each other directly.

Decoupling

The sender doesn't know who handles the work, when it happens, or how many handlers there are.

Buffering

Work can pile up in the queue while consumers catch up — a shock absorber for your system.

The contrast

One request, two shapes

The same 'user places an order' moment, with and without a queue in between.

Without a queue (synchronous)

User clicks 'order' → your app sends the email, calls the payment API, renders the invoice — all inside the same request. The user stares at a spinner for 8 seconds.

Slow — and if the payment API hiccups, the whole request fails.

With a queue (asynchronous)

User clicks 'order' → the app drops ONE message on the queue, replies in 50 ms, and is done. Workers handle the email/payment/invoice in the background.

Fast response — and a slow worker doesn't block anyone.

What it does

A message queue does four jobs

Decouples components

The web app, the workers and the third-party APIs all talk through the queue instead of calling each other.

Buffers spikes

1,000 orders in one minute? They line up; the workers chew through them at their own pace.

Makes work async

Emails, reports, video renders — anything that doesn't need to finish before the user sees 'done'.

Adds reliability

Messages persist in the queue: if a worker crashes mid-job, the message is still there to be redelivered.

The why

Why you'd reach for a queue

Three reasons, all about not waiting and not losing.

Don't block the request

Response time stops depending on the slowest side effect. Eight seconds of work becomes 50 ms of enqueuing.

Reliability

A failed worker is not a lost message — retry it, or park it in a dead-letter queue and investigate later.

Scale independently

Traffic doubled? Add another consumer. The queue is the rubber coupling between demand and capacity.

01
Part 1

How it works (the light version)

publish → store → route → consume → ack. Five words, one loop.

The flow

The life of one message

Follow one order from click to done.

  1. 1

    The producer publishes

    Your app calls publish() with the message — e.g. an order JSON with id 42. That is the entire sending side.

  2. 2

    The broker stores it

    The queue server (the broker) takes the message and keeps it — in memory and/or on disk, depending on the system.

  3. 3

    Routing decides where

    Which queue does it go to? Simple systems: one queue per job type. Bigger systems route by name or topic.

  4. 4

    A consumer consumes

    A worker pulls the next message and does the work: send the email, call the API, render the invoice.

  5. 5

    Ack — or retry

    When done, the consumer acknowledges (ack) it. Crash mid-way? The message is redelivered — 'at-least-once' delivery.

The picture

Three boxes, one line between

Nobody calls anyone. Everything is a message moving down the line.

📤 Producer (your web app)

Flask · FastAPI · any app

Creates the work and drops it on the line: publish(order). Doesn't wait, doesn't know who picks it up.

⟶

publish()

📥 Broker — the queue

RabbitMQ · Redis · Kafka

Holds the line: stores messages, routes them to the right queue, redelivers if a consumer dies. The shock absorber.

⟶

consume() + ack

🔧 Consumers (workers)

worker-1 · worker-2 · worker-3 …

Loop forever: take the next message, do the job, ack. Too busy? Add another worker — the queue doesn't care.

The producer and the workers only ever meet the queue — never each other.

Two flavors

Queue vs topic

One message — one consumer, or many? That's the fork in the road.

Queue — point to point

One message, one consumer. A line of work: whichever worker is free takes the next task. Load gets spread automatically.

Use it for TASKS: 'process this order'.

Topic — broadcast

One message, many consumers. Every subscriber gets its own copy — pub/sub style.

Use it for EVENTS: 'an order was placed'.

Vocabulary

Six words, one breath

Message

The payload — usually a JSON string plus metadata (id, type, timestamp). Small and self-contained.

Broker

The queue server that stores and routes messages: RabbitMQ, Kafka — or Redis standing in.

Producer / consumer

The two roles: who puts messages on the line, and who takes them off. The same app can be both.

Ack

The consumer's 'done, drop it'. Until the ack arrives, the queue keeps the message safe.

Retry / DLQ

Failed messages get retried, then parked in a dead-letter queue — a parking lot for broken work.

Backpressure

The queue fills up when producers outpace consumers — a visible, honest signal to add capacity.

Example 1 · producer

Publish: drop one message on the queue

The whole 'async' trick from the web app's side: one line, and the request is free.

producer.py
import redis

r = redis.Redis(host="localhost", port=6379)

def publish(order):
    # put one message onto the queue
    r.lpush("orders", order)
    print("queued:", order)

publish({"id": 42, "item": "keyboard", "qty": 1})

lpush = list push: the message lands at the head of the 'orders' list — a queue in one Redis command.

Example 2 · consumer

Consume: a loop that works until the queue is quiet

The worker side: take the next message, do the job, loop. Nothing is lost.

worker.py
import redis, json

r = redis.Redis(host="localhost", port=6379)

while True:
    item = r.brpop("orders", timeout=5)
    if item is None:
        continue            # queue empty — keep waiting
    order = json.loads(item[1])
    print("processing:", order)
    # work: send email, call API, render PDF…
    # then loop — the next message is up

brpop = blocking pop: the worker sleeps until a message arrives (timeout = how long it naps).

In the terminal

Watch the queue from the terminal

LLEN = how many are waiting; LRANGE = peek inside; LPOP = take one off — that's a consume.

ops@vps — zsh
$ redis-cli LLEN orders
3
$ redis-cli LRANGE orders 0 -1
1) {"id": 42, "item": "keyboard", "qty": 1}
2) {"id": 43, "item": "mouse", "qty": 2}
$ redis-cli LPOP orders
{"id": 42, "item": "keyboard", "qty": 1}
← LPOP = consume: read one message and remove it
The landscape

Names you'll actually meet

Three of these cover nearly every small-to-mid system you'll ship.

RabbitMQ

The classic general-purpose broker: queues, routing, acknowledgments, retries. When someone says 'message queue', this is usually what they mean.

Redis

You already met it as a cache — its lists double as a simple queue (LPUSH/BRPOP). Perfect for small systems; not built for hard guarantees.

Kafka

The event-log giant: append-only, partitioned, replayable. Powers high-volume event streaming — a different (bigger) animal.

Side by side

RabbitMQ vs Redis vs Kafka

RabbitMQRedisKafka
What it really isA message broker (queues + routing)A cache that doubles as a simple queueA distributed event log
Guaranteesack + retry, solidbasic (in-memory by default)durable log, replayable
Best forGeneral app work: jobs, decouplingSmall systems, quick asyncHigh-volume event streaming
Out of scope

What we're deliberately skipping

Queues go very deep. These are real — but not your next step.

Exchanges & routing keys

RabbitMQ's fine-grained routing (direct, fanout, topic). Overkill until you have many queues and rules.

Partitions & consumer groups

Kafka's internal structure: partitions, offsets, consumer groups. Powerful — a whole course of its own.

Stream processing

Transforming streams on the fly (Kafka Streams, Flink). The frontier, not your next step.

A message queue is a rubber coupling in your system: the sender drops work on the line and keeps going; the workers take it at their own pace; and nothing is lost when someone hiccups.
the big picture

Decouple. Buffer. Retry.

Next: when one box isn't enough, the queue is the first thing you reach for.