KAHIBARO
Discord Login Register

17.3. Message Queues

Why Message Queues Matter

In many backend systems you do not want to do everything inside a single HTTP request. Some tasks are slow, unreliable, or involve other services that may fail or be busy. Message queues give you a way to move work out of the request, process it in the background, and build systems that are more reliable and easier to scale.

At a simple level, a message queue is:

This sounds simple, but it is one of the most important building blocks in modern backend systems.


Core Concepts

Producers and Consumers

In a message queue system you will usually see these roles:

RoleAlso calledResponsibility
ProducerPublisher, SenderCreates messages and sends them to a queue
ConsumerWorker, SubscriberReceives messages and processes them
BrokerQueue serverStores messages and delivers them to consumers

The broker is the service that implements queues. Examples are:

Simple Example

Imagine your API has an endpoint:

http
POST /send-welcome-email

Without a queue, the handler might:

  1. Validate the request
  2. Render the email template
  3. Connect to an SMTP server
  4. Send the email
  5. Return 200 OK

If step 3 or 4 is slow, the user waits. With a queue:

  1. Validate the request
  2. Create a message: { "type": "send_welcome_email", "user_id": 123 }
  3. Publish the message to a queue "emails"
  4. Return 202 Accepted quickly
  5. A worker process reads from "emails" and sends the actual email

The API is now faster and more reliable.


What Is a Message?

A message is a unit of data that describes a task or event. It is usually a small JSON object or a binary payload.

Typical fields:

FieldPurpose
idUnique identifier for tracking
typeType of job or event
payloadData needed to do the work
created_atWhen the message was created
retryHow many times it has been retried so far

Example message for sending an email:

json
{
  "id": "79b3a0b8-672c-4e7f-9c11-3b5dc43de4ee",
  "type": "send_email",
  "payload": {
    "to": "user@example.com",
    "subject": "Welcome!",
    "template": "welcome_email",
    "data": {
      "name": "Alice"
    }
  },
  "created_at": "2026-08-27T12:34:56Z",
  "retry": 0
}

The broker does not care what the message means. It just stores and delivers it. Producers and consumers agree on the message format.


How Message Queues Work

Basic Operations

Even though different systems have different APIs, the core operations are usually:

OperationWhat it does
publishSend a message to a queue
consumeWait for a message and receive it
ackAcknowledge that a message was processed successfully
reject/nackTell the broker that processing failed, possibly requeue the message
peekView a message without removing it (not always supported)

In many libraries you will not call ack directly. The library will do it for you when your worker function returns without error.

Conceptual Flow

  1. Producer publishes message M to queue Q
  2. Broker stores M
  3. Consumer subscribes to Q
  4. Broker delivers M to the consumer
  5. Consumer processes M
  6. Consumer acknowledges M
  7. Broker removes M from Q

If the consumer crashes before step 6, the broker can give the same message to another consumer after a timeout.

Important rule: A message should be processed at least once. You must design your consumers to handle the same message more than once without causing incorrect results. This is called idempotency.


Queues vs Topics vs Streams

You will see different patterns:

PatternDescriptionTypical tech
QueueEach message is processed by one consumerRabbitMQ, Redis list
TopicEach message is broadcast to many subscribersPub/Sub, RabbitMQ
StreamMessages are appended to a log, consumers read at their own paceKafka, Redis Streams

For background jobs in a web backend, a simple queue is usually enough.


Common Use Cases

Backend developers use message queues for many scenarios.

Offloading Slow Tasks

You move slow operations away from the request.

Examples:

Example HTTP flow with queue:

  1. Client calls POST /generate-report
  2. API validates parameters
  3. API enqueues job { "type": "generate_report", "user_id": 123 }
  4. API returns 202 Accepted and maybe a report_id
  5. A worker generates the report and updates report status in the database

The client can later call GET /reports/{report_id} to check status.

Decoupling Services

If you have multiple services, a queue can decouple them.

Scenario:

Instead of A calling B and C directly, A puts a message "user_registered" on a queue or topic. B and C subscribe and react independently.

Benefits:

Rate Limiting and Throttling Work

If an external API only allows 100 requests per minute, you can:

The queue absorbs bursts. Your API remains responsive, while the work is processed at a safe speed.


Message Queue Guarantees

Different systems provide different delivery guarantees. You must understand these to design correct systems.

GuaranteeMeaning
At most onceEach message is delivered 0 or 1 times. Messages can be lost, never duplicated.
At least onceEach message is delivered 1 or more times. No loss, but possible duplicates.
Exactly onceEach message is delivered exactly 1 time, from the consumer's view. Very hard and rare.

Most common brokers give at least once guarantee by default.

Important rule: With "at least once" delivery, always design workers to be idempotent. Processing the same message multiple times must not cause wrong data or double billing.

Idempotent Consumer Example

Imagine a job charge_user with payload:

json
{
  "user_id": 10,
  "order_id": 555,
  "amount": 49.99
}

If the worker runs this twice, you do not want to charge the user twice.

A simple idempotent design:

  1. In the payments table, have a unique constraint on order_id.
  2. When processing the message:
    • Start a transaction
    • Try to insert payment with order_id = 555
    • If insert succeeds, call the payment provider
    • If insert fails because order_id exists, do not charge again

So, if the same message is delivered twice, the second time the database insert fails due to the unique constraint, and you skip the external call.


Reliability and Acknowledgments

Acknowledgments (ACKs)

ACKs tell the broker that a message has been processed successfully.

Many systems have a visibility timeout or unacked timeout:

Failure Example

  1. Worker receives message M to send an email.
  2. Worker starts sending but the mail server does not respond.
  3. After some time the worker process crashes.
  4. Broker's timeout ends, M is visible again.
  5. Another worker receives M and tries again.

If the email might be sent twice, the worker must handle this scenario carefully. Often sending an email twice is acceptable, but charging money twice is not.


Dead Letter Queues

Sometimes messages keep failing no matter how many times you retry them. For example:

You do not want these messages to clog the main queue forever.

A dead letter queue (DLQ) is:

Example policy:

Important rule: Always define a clear policy for failed messages, for example retry X times, then move to a dead letter queue. Do not allow infinite retries.


Ordering and Parallelism

Message Ordering

Some brokers guarantee that messages in a queue are delivered in the same order they were published. Others do not guarantee global ordering, especially when you use multiple workers.

You must think about:

Example:

Parallel Consumers

You can scale a queue by adding more consumers.

Example:

The broker distributes messages among consumers. This is a key benefit. You can scale horizontally by starting more worker processes, or more containers.


Using Redis as a Simple Message Queue

Even though there are dedicated message brokers, Redis is often used as a simple queue, especially in Python backends with Celery or RQ.

Redis List as a Queue

Redis has a LIST data structure. You can use:

Together, they form a FIFO queue: First In, First Out.

Example (conceptual):

  1. Producer:
    • Serialize message to JSON string.
    • LPUSH email_queue "<json>".
  2. Consumer:
    • Call BRPOP email_queue to wait for messages.
    • When a message arrives, parse JSON and process.

This is simple but lacks some advanced features like built-in retries or dead letter queues. Libraries like Celery add those features on top.


Designing Message Payloads

What to Put in a Message

You must decide what to include in the payload:

Trade offs:

StrategyProsCons
IdentifiersSmaller messages, always use latest dataIf data changes, job may behave differently
Full dataSnapshot of data at the moment of enqueueLarger messages, harder to change schema

Example trade off:

Important rule: Avoid putting very large payloads or files directly into messages. Store large data in an external storage (database, file store, S3) and put only references (IDs, URLs) in the message.


Idempotent Design Patterns

Because messages may be processed more than once, you need some concrete patterns.

Use Unique Constraints

Use the database to protect against duplicates:

Use a "Processed Messages" Table

You can track processed messages explicitly.

Table: processed_messages

ColumnType
message_idUUID
processed_attimestamp

Process flow:

  1. Start a database transaction.
  2. Check if message_id exists in processed_messages.
  3. If it exists, skip processing.
  4. If not, perform the business logic.
  5. Insert message_id into processed_messages.
  6. Commit transaction.

If the same message is delivered again, step 2 will see that it was already handled.

Use Idempotency Keys

Sometimes the producer creates an idempotency key based on the logical action, for example:

The worker stores the key and result in the database or cache. If the same key is processed again, it returns the stored result without performing the action again.


Handling Retries

Retries are central to message queues, because many failures are temporary.

When to Retry

Good candidates for retry:

Bad candidates:

Exponential Backoff

Instead of retrying immediately many times, you should wait increasingly longer:

General formula: for retry number $n$ (starting from 1),

$$
\text{delay}_n = \text{base\_delay} \times 2^{(n-1)}
$$

For example, base delay 1 second:

Important rule: Use exponential backoff with a maximum retry count to prevent hammering a failing service and to keep your system stable.

Most background job libraries let you configure retry strategies so you do not have to implement this math yourself.


Visibility to Users

Using queues changes how your API behaves for clients. You usually deal with eventual completion, not immediate completion.

Common patterns:

1. Return Accepted, Then Poll

HTTP status code at submit time is often 202 Accepted, which means, "We accepted your request, but the processing is not finished yet."

2. Webhooks or Push Notifications

This is more advanced and requires that the client can receive incoming requests or connections.


Comparing Message Queue Technologies

Since you will often use message queues in Python backends, here is a brief comparison:

TechnologyTypeCommon usage
RedisIn memory storeSimple queues, short lived jobs, Celery broker
RabbitMQBrokerComplex routing, reliable queues, enterprise use
AWS SQSCloud queueServerless, managed queue in AWS
KafkaStream platformHigh volume event streams, analytics

As a beginner:

Practical Design Example

Imagine an e commerce backend, you want to:

You could design your flow with message queues:

  1. User calls POST /orders.
  2. API:
    • Validates input.
    • Stores order with status "pending".
    • Enqueues message {"type": "process_order", "order_id": 777}.
    • Returns 201 Created with the order ID.
  3. Worker order_processor:
    • Receives process_order message.
    • Charges the user.
    • Updates order status to "paid".
    • Enqueues two more messages:
      • {"type": "reserve_inventory", "order_id": 777}
      • {"type": "send_order_confirmation_email", "order_id": 777}
  4. Worker inventory_worker:
    • Reserves stock.
    • Updates order status to "ready_to_ship".
  5. Worker email_worker:
    • Sends email to the customer.

If any step fails:

The main API is fast and responsive. The heavy work happens behind the scenes.


Summary

Message queues let you:

Key ideas to remember:

These foundations prepare you for the later chapters where you will implement actual background jobs using queues and workers in real backend projects.

Views: 9

Comments

Please login to add a comment.

Don't have an account? Register now!