Back to overview

Detecting N+1 issues in your Laravel project with Ray

10 November 2023

Image of Tim

Tim

N+1 issues are a common pitfall when writing applications backed by a relational database.

These issues happen when you attempt to access data from a relationship by lazy loading it. This might seem innocent, but it can have a significant performance impact as your app keeps growing.

While it's not hard to resolve N+1 issues, they can quickly go under the radar for a long time. This is where the power of Ray comes in to help you avoid them!

Luckily, Laravel gives us some tools to detect and prevent N+1 issues.

Let's add the following code to our AppService provider's boot method:

public function boot(): void

{

    Model::preventLazyLoading(! $this->app->isProduction());

    Model::handleLazyLoadingViolationUsing(function ($model, $relation): void {

        $class = get_class($model);

        ray()->notify("Attempted to lazy load [{$relation}] on model [{$class}].");

    });

}

With the Model::preventLazyLoading() method, we can tell Laravel never to allow lazy loading. Of course, we don't want to break our application in production in case we miss an N+1 issue, so we pass a check and don't turn off lazy loading on production.

The Model::handleLazyLoadingViolationUsing() method will allow us to define custom behavior for when an N+1 issue is detected.

In our case, we will call Ray using the notify() method. Using the notify method, we ensure Ray shows a notification so we don't miss the detected issue.

To make this more practical, let's test this with a simple example: imagine a blogging application with a Post and Comment model. Some simplified code for outputting posts and comments could look like this:


$posts = Post::query()->limit(5)->get();

foreach ($posts as $post) {

    echo "<h1>{$post->title}</h1>";

    foreach ($post->comments as $comment) {

        echo "<p>{$comment->body}</p>";

    }

}

If we run this code, Ray will pop up a notification to tell us about the N+1 issue in our code:

To investigate this more, we can use the showQueries() method from Ray to output all queries that are being executed:


ray()->showQueries();

$posts = Post::query()->limit(5)->get();

foreach ($posts as $post) {

    echo "<h1>{$post->title}</h1>";

    foreach ($post->comments as $comment) {

        echo "<p>{$comment->body}</p>";

    }

}

Now, if we run this code, we can see that queries are being executed for each post that we loaded, which is not optimal:

To resolve this Eloquent allows us to define which relationships we want to eager load:


$posts = Post::query()->with('comments')->limit(5)->get();

When we run this, we no longer get the N+1 notification, and we can see that we optimized the code so that it only has to execute two queries to get all the data. Win!

Stay in the loop with updates & promotions for Ray

We only send a few emails a year.

Debug without breaking your flow

Ray keeps all your debug output neatly organized in a dedicated desktop app.

Licenses are valid for 1 year and managed through Spatie. Licenses purchased before Ray 3 remain valid. VAT is calculated at checkout.