← Back to list

The “Performance Bible” — Laravel Queue Mastery

Volume VI: The Queue Serialization Trap — Why sending full models to queues kills your Redis memory

Ez Eldeen A. Mushtaha · 2026-06-02 21:54 · 0 claps · 2.1 min read
#programming #php #laravel #software-development #software-engineering
Open on Medium ↗
Wiki topics: 💻 · Programming 🕊️ · Religion

The “Performance Bible” — Laravel Queue Mastery

Volume VI: The Queue Serialization Trap — Why sending full models to queues kills your Redis memory

You dispatch a job with $user. Laravel serializes the entire model — all columns, all relations, and all hidden attributes. Your 2KB payload becomes 2MB. Your Redis memory fills up. Your workers hang. And you have no idea why.

The Silent Killer

This is one of the most common production failures in Laravel. Developers dispatch jobs with full models; everything works in development (small data), but once 100,000 jobs hit the queue in production, the memory explodes, and the entire queue system collapses.

The Anatomy of the Serialization Problem

When you dispatch a job with a Model, you are serializing its entire state:

  • Database Attributes: All columns, including those you don’t need.
  • Hidden Attributes: Laravel serializes these even if they are excluded from API responses.
  • Loaded Relationships: If you called $user->load('posts'), the entire collection of posts is serialized recursively.
  • Model Metadata: Table names, connection details, and internal state for dirty checking.

The Payload Size Reality (Per 100,000 Jobs)

The impact on your Redis infrastructure is immediate and severe:

  • Sending Full Model (No relations): ~5 KB per job = 500 MB total. This pushes many Redis instances to their memory limit.
  • Sending Full Model (+ Relations): ~500 KB per job = 50 GB total. This results in an inevitable system crash.
  • Sending ID Only: ~8 bytes per job = 0.8 MB total. This is the standard for high-scale, production-grade systems.

The Golden Rule: Dispatch the ID, not the Model

Never dispatch a model. Always dispatch the ID, and fetch the model fresh inside the job.

BAD (Dispatch Full Model):

// Controller
$user = User::find(123);
ProcessUserJob::dispatch($user); // ❌ Serializes everything, including sensitive data and relations.

GOOD (Dispatch ID Only):

// Controller
$user = User::find(123);
ProcessUserJob::dispatch($user->id); // ✅ Serializes only the integer.
// Job
class ProcessUserJob implements ShouldQueue
{
    public function __construct(public int $userId) {}
    public function handle()
    {
        // Fetch fresh data from the database
        $user = User::find($this->userId); 
        $user->update(['processed' => true]);
    }
}

Frequently Asked Questions

“What if the user changes between dispatch and execution?” That is actually a feature! The job should always use the current state of the user. Fetching the model inside the handle() method ensures you are working with the most up-to-date data, rather than a stale snapshot captured at dispatch time.

“What about SQS?” AWS SQS has a hard limit of 256KB per message. A User model with relations can easily exceed this limit, causing the job to fail silently with no error message. Dispatching the ID prevents this entirely.

The Bottom Line

When in doubt, dispatch the ID. One extra database query inside the job is infinitely cheaper than a crashed queue system.

Dig Deeper

This is Volume VI of the Performance Bible. If you want to master queue efficiency and prevent production infrastructure crashes, check out the full technical deep dive:

The “Performance Bible” — Volume VI: The Queue Serialization Trap


메타데이터
post_id
cdfb40d12e9c
slug
the-performance-bible-laravel-queue-mastery-cdfb40d12e9c
url
https://medium.com/@3z.eldeen/the-performance-bible-laravel-queue-mastery-cdfb40d12e9c
canonical_url
https://medium.com/@3z.eldeen/the-performance-bible-laravel-queue-mastery-cdfb40d12e9c
author_url
https://medium.com/@3z.eldeen
status
ok
fetched_at
2026-07-13 06:23:13