The interface is localized. Skill descriptions and documents are machine-translated by AI into your language; code, commands, paths and technical terms stay in the original, and the author's document governs.
name: backend-message-queue
description: >
Use this skill when the user says 'message queue', 'Kafka', 'RabbitMQ', 'SQS', 'pub-sub', 'event bus', 'consumer group', 'topic', 'queue', 'at-least-once', 'exactly-once', 'idempotent consumer', 'event sourcing', 'dead letter queue', 'DLQ', 'message broker', 'producer', 'consumer', 'event-driven', 'async processing', or when designing asynchronous messaging. This skill enforces consistent messaging patterns: broker selection, topic/queue topology, message schema, consumer groups, retry, DLQ, and idempotency. Applies to any backend stack. Do NOT use for: gRPC streaming, WebSocket real-time, REST API design, or database CDC.
version: "2.0.0"
author: "j4flmao"
license: "MIT"
compatibility:
claude-code: true
cursor: true
codex: true
windsurf: true
tags: [backend, messaging, phase-2, universal]
Backend Message Queue
Purpose
Design consistent, production-grade message-driven systems. Every message flow must follow the same conventions for broker selection, topic/queue topology, message schema, delivery guarantees, consumer idempotency, retry, and dead-letter handling.
No preamble. No postamble. No explanations. No filler/hedging/transitions. Compress output — why use many token when few do trick.
Completion Criteria
Broker is selected with justification.
Every topic/queue has: name, type, partitions, retention, consumers, DLQ.
Every message has: schema with version, key, payload, delivery guarantee, idempotency key.
Consumer failure handling (retry policy, DLQ routing) is defined.
Ordering requirements are documented (key-based partitioning).
Schema evolution strategy is defined.
Max Response Length
Per topic/queue: 8 lines. Per message type: 6 lines.
Decision Tree
Which Broker?
What are your requirements?
├── High throughput (>100K msg/s), replay, log compaction, analytics
│ └── Apache Kafka — partitioning, consumer groups, long retention
├── Flexible routing (topic/direct/fanout), per-message ack, task queues
│ └── RabbitMQ — exchanges, bindings, TTL, dead-lettering
├── Fully managed, no ops, Lambda triggers, simple
│ └── AWS SQS — auto-scale, limited features, 256KB max
├── High throughput, low latency, JVM-free
│ └── Pulsar — geo-replication, multi-tenancy, segment-based storage
└── Pub/sub with push delivery, mobile/web integration
└── Google Pub/Sub — managed, exactly-once, push subscriptions
Which Delivery Guarantee?
What happens if a message is lost?
├── Data loss is acceptable (metrics, non-critical logs)
│ └── At-most-once — fire and forget, lowest overhead
├── Duplicates are OK, data loss is not
│ └── At-least-once — retry on failure, idempotent consumer required
├── Neither loss nor duplicates allowed (financial, inventory)
│ └── Exactly-once — transactional producer + dedup consumer + idempotent processing
└── I don't know
└── Default to at-least-once — safest choice for most use cases
Workflow
Step 1: Select Broker
Kafka: high throughput (100k+ msg/s), replay, log compaction, multi-consumer
Best for: event sourcing, analytics pipelines, audit logs, CDC
RabbitMQ: flexible routing (exchanges, bindings), low latency, per-message ack
Best for: task queues, RPC, complex routing, lower throughput
SQS: fully managed, no ops, auto-scaled, simple
Best for: AWS-native, simple queues, Lambda triggers, small teams
At-most-once: fire and forget. Lowest overhead, possible data loss.
Use: metrics, non-critical logs.
At-least-once: retry on failure. Lower overhead, no data loss, possible duplicates.
Use: order processing, notifications, data sync.
Required: idempotent consumer.
Exactly-once: transactional producers + idempotent consumers + dedup.
High overhead. Only when mandated by compliance.
Use: financial transactions, inventory deduction.
Step 5: Idempotent Consumer Pattern
On receive message:
1. Check if idempotency_key exists in processed set (Redis / DB).
2. If exists → ack and skip (duplicate).
3. If not exists → process, store idempotency_key, commit offset / ack.
Idempotency key = message.id or business_key + event_type
Processed set TTL: match broker retention period
class IdempotentConsumer {
private processed = new Set<string>();
async process<T>(message: Message<T>, handler: (data: T) => Promise<void>): Promise<void> {
if (this.processed.has(message.id)) {
logger.info('Duplicate message skipped', { id: message.id });
return;
}
await handler(message.data);
this.processed.add(message.id);
// For persistence: store in Redis with TTL
}
}
Step 6: Retry and Dead-Letter Queue
Kafka:
- Retry topic: user.events.v1.retry (consumers reprocess after delay)
- DLQ topic: user.events.v1.dlq (after 3 retries)
- Alert on DLQ message production
RabbitMQ:
- Retry policy: reject → DLX → retry queue (with TTL) → requeue
- Max retries: 3 with exponential backoff (10s, 30s, 60s)
- After max: route to DLQ permanently
SQS:
- redrive policy: maxReceiveCount = 3
- DLQ for failed messages
- Lambda DLQ destinations for async invocation failures
No artifact produced unless requested.
Next skill: backend-caching — if the event-driven system needs to cache materialized views or read models.
Carry forward: topic/queue topology, message schemas, consumer group configs, retry/DLQ policies.
Implementation Patterns
Kafka Producer/Consumer
from typing import Dict, Callable, Any, Optional
import json
import asyncio
from confluent_kafka import Producer, Consumer, KafkaError
class KafkaMessageProducer:
def __init__(self, bootstrap_servers: str, client_id: str = "producer-1"):
self.producer = Producer({
"bootstrap.servers": bootstrap_servers,
"client.id": client_id,
"acks": "all",
"enable.idempotence": True,
"compression.type": "snappy",
})
def produce(self, topic: str, key: str, value: Dict, headers: Optional[Dict] = None):
headers_list = [("content-type", "application/json")]
if headers:
headers_list.extend((k, v.encode()) for k, v in headers.items())
self.producer.produce(
topic=topic,
key=key.encode(),
value=json.dumps(value).encode(),
headers=headers_list,
callback=self._delivery_report,
)
self.producer.poll(0)
def flush(self):
self.producer.flush()
def _delivery_report(self, err, msg):
if err:
print(f"Delivery failed: {err}")
else:
print(f"Delivered to {msg.topic()}[{msg.partition()}] @ {msg.offset()}")
class KafkaMessageConsumer:
def __init__(self, bootstrap_servers: str, group_id: str, topics: list[str]):
self.consumer = Consumer({
"bootstrap.servers": bootstrap_servers,
"group.id": group_id,
"auto.offset.reset": "earliest",
"enable.auto.commit": False,
"max.poll.interval.ms": 300000,
})
self.consumer.subscribe(topics)
def process_messages(self, handler: Callable, timeout: float = 1.0):
try:
while True:
msg = self.consumer.poll(timeout)
if msg is None:
continue
if msg.error():
if msg.error().code() == KafkaError._PARTITION_EOF:
continue
print(f"Consumer error: {msg.error()}")
continue
try:
value = json.loads(msg.value().decode())
handler(value, msg.key().decode() if msg.key() else None, msg.headers() or [])
self.consumer.commit(msg)
except Exception as e:
print(f"Processing error: {e}")
# Send to DLQ
self._send_to_dlq(msg)
except KeyboardInterrupt:
pass
finally:
self.consumer.close()
def _send_to_dlq(self, msg):
dlq_topic = f"{msg.topic()}.dlq"
self.consumer.produce(dlq_topic, key=msg.key(), value=msg.value())
self.consumer.commit(msg)
Architecture Decision Trees
Broker Selection
What are the requirements?
├── High throughput (>100K msg/s), replay, long retention
│ └── Apache Kafka
│ ├── Persistent, replayable, ordered within partition
│ ├── Consumer group scaling
│ └── Schema Registry required
│
├── Simple messaging, lower throughput
│ └── RabbitMQ
│ ├── Direct, topic, fanout exchanges
│ ├── Priority queues, TTL, DLX
│ └── Good for RPC and work queues
│
├── Serverless, fully managed, no ops
│ └── AWS SQS / SNS or GCP Pub/Sub
│ ├── Automatic scaling
│ ├── No broker management
│ └── Pay per request, not per throughput
│
└── Exactly once, ordered delivery
└── Kafka with idempotent producer + transactional API
Anti-Patterns
Anti-Pattern
Why It Fails
Correct Approach
Infinite retention
Storage explosion, slow rebalances
Set retention by time (7d default) and size
No DLQ monitoring
Silent data loss
Alert on DLQ message production
Committing offset before processing
Lost messages on crash
Commit after processing (at-least-once)
Too many partitions
Rebalance overhead, connection overhead
Partitions = consumers x 2-3 max
Performance Optimization
Batched production: Use produce() with flush() at intervals or count. Reduces network round-trips by 10x compared to per-message flush.
Compression: Use Snappy or Zstd compression at the producer. Reduces storage and network by 60-70% for text-based messages.
Partition-aware consumers: Match consumer count to partition count. Avoid idle partitions and rebalance overhead. Use cooperative rebalancing for sticky partition assignment.