Message Queue
Also Known As
TL;DR
Explanation
A message queue stores messages durably until a consumer processes and acknowledges them. Producers write messages and continue without waiting for processing to complete. Consumers pull messages at their own pace, process them, and acknowledge success — or reject and requeue on failure. This decoupling provides three key properties: temporal decoupling (producer and consumer do not need to be running simultaneously), rate decoupling (a traffic spike fills the queue rather than overwhelming the consumer), and failure isolation (a crashed consumer does not lose work; messages wait until it recovers). Common implementations used with PHP: Redis (via Predis or phpredis, using LPUSH/BRPOP or Streams), database-backed queues (Laravel Queue with MySQL/PostgreSQL), RabbitMQ (via php-amqplib), and Amazon SQS (via AWS SDK). The choice depends on durability requirements, throughput, and operational complexity tolerance.
Common Misconception
Why It Matters
Common Mistakes
- Not making consumers idempotent — at-least-once delivery means duplicate messages are inevitable; always design for it.
- Not setting a dead letter queue — messages that fail repeatedly should move to a DLQ for inspection, not be silently dropped or loop forever.
- Processing messages inside the HTTP request cycle — the whole point of a queue is to move work out of the request; processing synchronously negates the benefit.
- Using a queue for real-time communication — queues add latency; use WebSockets or Server-Sent Events for real-time features.
Code Examples
// Blocking the HTTP response — user waits for email
class OrderController {
public function store(Request $request) {
$order = Order::create($request->all());
// User waits 2-3 seconds for this
Mail::to($order->user)->send(new OrderConfirmation($order));
return response()->json($order, 201);
}
}
// Async — response returns immediately
class OrderController {
public function store(Request $request) {
$order = Order::create($request->all());
// Dispatched to queue — returns in <10ms
SendOrderConfirmation::dispatch($order);
return response()->json($order, 201);
}
}
// Idempotent job handler
class SendOrderConfirmation implements ShouldQueue {
public function handle(): void {
// Check if already sent — safe to run twice
if ($this->order->confirmation_sent_at) return;
Mail::to($this->order->user)->send(new OrderConfirmation($this->order));
$this->order->update(['confirmation_sent_at' => now()]);
}
}