Skip to main content

Command Palette

Search for a command to run...

Eloquent Performance

Optimizing Queries Before You Need Cache

Published
•4 min read•View as Markdown
Eloquent Performance
A

Seasoned IT professional with 7+ years of Program & Project Management, Enterprise and Information Architecture, Technical Delivery (IT Manager), IT Service Management, Team Building, Vendor Management, and Technology experience. Solid track record of leading and managing teams and delivering solutions of business value, use of diverse technologies in the fields of Business Intelligence and Enterprise Data Warehouse, Applications, Databases, Web Portals, Infrastructure, Business Products, and various IT solutions.

Look, I've been there. Your Laravel app is humming along nicely in development, everything feels snappy, and then you push to production. Suddenly, pages that loaded in 200ms are taking 2 seconds. Your first instinct? "I need caching!"

But here's the thing, caching is a band-aid. A useful band-aid, sure, but still a band-aid. If your queries are garbage, caching just means you're serving cached garbage faster.

Let me show you what I mean.

The N+1 Problem That Everyone Keeps Making

You know this one. We all do. But we keep doing it anyway.

// Your code looks innocent enough
$users = User::all();

foreach ($users as $user) {
    echo $user->posts->count() . " posts";
}

Looks fine, right? Wrong. If you've got 100 users, that's 101 queries. One to get the users, then one for each user's posts. Your database is crying.

The fix is simple:

$users = User::withCount('posts')->get();

foreach ($users as $user) {
    echo $user->posts_count . " posts";
}

Two queries total. Done. This single change once dropped a page load from 1.8 seconds to 180ms for me. No cache needed.


Stop Loading Everything When You Need Almost Nothing

Here's another one I see constantly. You need a user's name and email, but you're loading their entire profile, preferences, settings, notification history and everything.

// Why are you doing this?
$user = User::find(1);
return $user->name;

Eloquent just grabbed 30 columns when you needed one. Use select():

$user = User::select('id', 'name', 'email')->find(1);

"But it's just a few KB!" Sure. Now multiply that by 10,000 users on a dashboard. Those KBs add up, and your database has to do more work reading and transferring data you're literally throwing away.


The Chunk Method Is Your Friend

Processing 50,000 records? Don't load them all into memory at once. I learned this the hard way when a background job crashed the server because it tried to load 200,000 rows.

// This will eat your RAM alive
$orders = Order::where('status', 'pending')->get();

foreach ($orders as $order) {
    $order->process();
}

Instead:

Order::where('status', 'pending')->chunk(200, function ($orders) {
    foreach ($orders as $order) {
        $order->process();
    }
});

Same result, fraction of the memory. Your server will thank you.


Eager Loading: Load Smart, Not Hard

You've got a blog. Each post has comments. Each comment has an author. You want to display all of this.

// The nightmare scenario
$posts = Post::all();

foreach ($posts as $post) {
    foreach ($post->comments as $comment) {
        echo $comment->author->name;
    }
}

If you've got 10 posts with 20 comments each, that's 1 query for posts, 10 queries for comments, and 200 queries for authors. 211 queries for one page. Unnecessary stress for DB.

$posts = Post::with('comments.author')->get();

foreach ($posts as $post) {
    foreach ($post->comments as $comment) {
        echo $comment->author->name;
    }
}

Three queries. Same data. That's the difference between a page that times out and one that loads instantly.


Index Your Database (Seriously)

This isn't technically Eloquent, but it matters. You can write perfect queries, but if your database doesn't have indexes, you're still screwed.

// This query looks fine
$user = User::where('email', $email)->first();

But if there's no index on the email column, MySQL is scanning every single row. Add an index:

// In your migration
$table->string('email')->index();
// Or even better
$table->string('email')->unique();

I once added a single index to a created_at column that was being used for date-range queries. Query time went from 8 seconds to 40ms.


When Relationships Get Complicated

Sometimes you need specific data from a relationship, not the whole thing. Use has() or whereHas():

// Get users who have published at least one post
$users = User::has('posts')->get();

// Get users who have posts published this year
$users = User::whereHas('posts', function ($query) {
    $query->where('published_at', '>=', now()->startOfYear());
})->get();

This keeps the query efficient and doesn't load unnecessary relationship data.

The Real Talk About Caching

After you've done all this? Sure, add caching. Cache query results that don't change often. Cache computed values. Cache aggregate data.

But optimize first. A well-optimized query with caching is blazing fast. A terrible query with caching is still terrible. It just fails less often.

I've seen developers slap Redis on everything and call it optimized. Then the cache expires, and suddenly their app grinds to a halt because the underlying queries are still doing 500 database calls per page.

The Bottom Line

Your queries are probably slower than they need to be. Check your query count with Laravel Debugger or Telescope. If you're seeing double-digit queries on a simple page, you've got work to do.

Fix your queries. Make them lean. Make them smart. Then, when you actually need it, add caching as the cherry on top.

Your users and your server bill will thank you.

The Art of Laravel

Part 3 of 12

Laravel isn’t just a framework — it’s a craft. The Art of Laravel captures that spirit, blending technical depth with developer wisdom to help you master queues, authentication, Octane, and more — building apps as elegant as the framework itself.

Up next

Testing for Humans: Laravel Pest

If you’ve been around Laravel long enough, you know the pain of opening a test file and feeling like you just walked into a courtroom, too many words, too much formality, and definitely not enough joy. PHPUnit works… but let’s be honest, it’s not the...