← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

Message Queue

Messaging Intermediate
debt(d7/e5/b7/t7)
d7 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'only careful code review or runtime testing' (d7). The detection_hints.tools list is empty. Non-idempotent consumers, missing dead-letter queues, or synchronous processing inside the HTTP cycle are not caught by compilers or standard linters — they require careful code review, integration testing, or are only discovered in production when duplicate messages cause double-charges, duplicate emails, or silent data corruption. No automated tool reliably catches these structural misuses.

e5 Effort Remediation debt — work required to fix once spotted

Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix mentions starting with the database driver and switching to Redis, plus implementing idempotency checks in job handlers. Retrofitting idempotency across existing job handlers, adding dead-letter queue configuration, and reorganising work currently done inside HTTP request cycles touches multiple files and components — it is not a single-line fix but not a full architectural rework either.

b7 Burden Structural debt — long-term weight of choosing wrong

Closest to 'strong gravitational pull' (d7). Once a message queue is adopted, every background job, every retry strategy, every deployment (worker processes, supervisor config), and every feature that offloads async work is shaped by the choice of queue backend and consumer design. The choice of driver (database vs. Redis vs. RabbitMQ) and whether consumers are idempotent affects every future async feature. It does not quite define the entire system shape but exerts strong gravitational pull across many work streams.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap' (t7). The misconception field states explicitly that developers assume exactly-once delivery, but almost all queues guarantee at-least-once. This contradicts how most developers reason about function calls and HTTP requests (which happen once), leading to serious real-world bugs — duplicate emails sent, double charges processed, duplicate records created — that are hard to reproduce in development and only surface in production under failure conditions.

About DEBT scoring →

Also Known As

job queue task queue work queue queue async queue background queue

TL;DR

A durable buffer that decouples the component producing work (producer) from the component processing it (consumer), enabling async processing, load levelling, and retry logic without direct coupling.

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

A message queue guarantees exactly-once delivery. Almost all message queues guarantee at-least-once delivery — a message may be delivered more than once if the consumer crashes after processing but before acknowledging. Consumers must be idempotent: processing the same message twice must produce the same result as processing it once. Exactly-once delivery is significantly harder and comes with performance trade-offs; most systems are designed around at-least-once with idempotent consumers.

Why It Matters

Message queues are the standard solution for every PHP task that should not block an HTTP response: sending emails, generating PDFs, processing image uploads, making third-party API calls, and running reports. Without a queue, these operations either block the user waiting for them to complete, or fail silently when a downstream service is slow. With a queue, the HTTP response returns in milliseconds, the work happens asynchronously, failed jobs retry automatically, and traffic spikes absorb into the queue rather than crashing the application.

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

✗ Vulnerable
// 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);
    }
}
✓ Fixed
// 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()]);
    }
}

Added 23 Mar 2026
Views 55
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings W 1 ping T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 1 ping W 0 pings T 0 pings F 2 pings S 2 pings S 3 pings M 1 ping T 0 pings W 0 pings T 0 pings F 1 ping S 0 pings S 1 ping M 0 pings T 0 pings W 0 pings T 0 pings F 1 ping S 0 pings S 0 pings M 0 pings T 0 pings W
No pings yet today
No pings yesterday
Amazonbot 9 Perplexity 7 Scrapy 7 Google 6 ChatGPT 4 Ahrefs 4 Claude 2 Bing 2 Meta AI 1 PetalBot 1
crawler 37 crawler_json 6
DEV INTEL Tools & Severity
🔵 Info ⚙ Fix effort: Medium
⚡ Quick Fix
Use Laravel Queue with the database driver to start (zero new infrastructure), switch to Redis for production performance, always implement idempotency checks in job handlers


✓ schema.org compliant