Stop Blaming Laravel: Where Your App Is Actually Losing Time

Stop Blaming Laravel: Where Your App Is Actually Losing Time


There is a widespread misconception among developers, especially beginners who are new to the framework or have experience with lighter PHP frameworks, that Laravel is a bloated framework. This theory has its grounds since applications built with Laravel often become slow, and developers trying to find the reason for this turn out to be wrong — it is not Laravel that is bad, but the incorrect use of the tools that the framework provides.

In this article, we will understand why exactly this misunderstanding happens and what problems really slow down the Laravel application. We will also look at ways of optimizing Laravel and explore possible points of optimization such as Eloquent and Query Builder, model hydration, caches, queues, OPcache, PHP-FPM, database queries, serialization, and API calls. All this will help identify the real reasons that reduce the performance of the application.

Laravel Is Heavy? The Truth About This Popular PHP Framework

Laravel framework, by its nature, provides a feature-rich set of solutions. In some cases, it's easy to get the impression that Laravel is somewhat bulky and not so light. A fresh installation of Laravel may indeed require more memory and take longer to start than some more minimalistic PHP frameworks. However, these differences are usually within the limits of server performance and optimization practices.

Laravel's performance is very good for the majority of cases, as the framework utilizes modern developments in the PHP language. Most of Laravel's core components are optimized and compiled using the OPcache or just-in-time (JIT) compiler. As mentioned above, it's rare to notice the overhead of Laravel's bloat unless you have a particular case when your app struggles to utilize the server's capabilities.

Where Laravel Isn't the Bottleneck

Laravel's Core Framework

The framework’s bootstrapping process is very efficient. By utilizing such helpful features as route caching, configuration caching, and view caching, the production application’s initial load time can be minimized. Modern PHP versions also play an important role in the performance optimization of Laravel.

Eloquent vs. Query Builder

One of the most frequent debates around PHP performance concerns Eloquent ORM. While Query Builder indeed represents a lower level and slightly more performant way of working with the database, Eloquent's overhead is often exaggerated - the ORM layer provides a certain level of convenience and readability, and for 90% of the cases, it's performance difference from the Query Builder would be completely negligible. The problem is in the way Eloquent is used.

// Eloquent: Simpler, more readable for related data
$users = App\Models\User::with('posts')->get();

// Query Builder: Might be marginally faster for very simple queries
$users = DB::table('users')->get();

The performance difference is minimal, probably in microseconds if you are working with minimal data and simple queries. but when it comes to deal with millions of records Query builder is considerably faster than Eloquent.

Model Hydration

Model hydration is the act of turning raw database rows into Eloquent models. When you're getting thousands of rows out of the database, PHP has to create an object for each row and fill them with data and relationships. It can be a memory and CPU intensive operation. It's a database centric problem that gets magnified in PHP due to its object oriented nature.

// This can be slow if there are too many users and posts
$users = App\Models\User::all(); // Each user becomes an object
foreach ($users as $user) {
    echo $user->name;
    foreach ($user->posts as $post) { // N+1 query if not eager loaded
        echo $post->title;
    }
}

The problem is not in eloquent, it's in your understanding how to use it. If you have a huge amount of data that you do not need to load as models, you can use DB::table()->cursor() or ->chunk() methods to minimize memory consumption or even just select needed columns with ->select() method.

Identifying the Real Culprits

Most of the time the performance issue comes from these following reasons: 

1. Database Queries: The N+1 Problem

This is probably the most common performance issue you will run into. The N+1 problem occurs when you have a query that gets you a set of models, and then for every one of these models you perform a query to get their related data. N+1 queries instead of just two (one for the parent, one for all the children)

// N+1 problem example
$users = App\Models\User::all(); // 1 query
foreach ($users as $user) {
    echo $user->posts->count(); // N queries (one for each user's posts)
}

The solution is eager loading:

// Eager loading solution
$users = App\Models\User::with('posts')->get(); // 2 queries
foreach ($users as $user) {
    echo $user->posts->count(); // No extra queries here
}

Always use eager loading (with()) when you know you'll need related data for a collection of models.

2. External API Calls

If your app is calling some external service/Api then the synchronous api call can be expensive and fully dependent on the performance of the external api. If the external API service is slow itself then your app will perform slow. Its out of Laravel's control.

// Example of a slow external API call
public function getUserData($userId)
{
    $user = User::find($userId);
    // This call can take hundreds of milliseconds or even seconds
    $externalData = Http::get('https://some-slow-api.com/users/' . $user->external_id)->json();

    return view('user.profile', compact('user', 'externalData'));
}

One of the solution for this will be caching the API response for a period of time if you don't need to show very latest data to users.

3. Heavy Serialization/Deserialization

When it comes to working with big arrays or any complex data structures, you can encounter an issue when trying to put them into a cache, Redis or a session. The problem appears due to the fact that you need to serialize them to a string for storage and deserialize them back from a string when reading. The overhead of these operations will be significant for large amounts of data or nested structures, and it will consume a lot of CPU time.

// Storing a very large collection in cache
$largeCollection = SomeModel::all(); // Imagine thousands of records
Cache::put('my_large_data', $largeCollection, 3600); // Serialization happens here

// Retrieving it later
$retrievedCollection = Cache::get('my_large_data'); // Deserialization happens here

A quick solution for this will be caching only the required data and use DTO ( Data Transfer Object ) to reduce payload size. 

4. Lack of Caching

If your app often reads same data from database or external API, but you do not cache it, you are wasting a big opportunity to increase the performance. Laravel has a great caching system.

You should use it. The solution is simple, cache this data on application level, cache HTTP responses and use CDN to cache static assets if possible.

// Using Laravel's cache
$users = Cache::remember('all_users', 60*60, function () {
    return App\Models\User::all();
});

5. Inefficient Use of Queues

Any long-running task that doesn't need an immediate response from the user should be offloaded to a queue. Keep the process Asynchronous as much as possible to make the app faster and get the job done in background. This includes 

  • Sending emails
  • processing images
  • Generating reports
  • Hitting external APIs
  • Generating Invoices

// Synchronous email sending (bad)
Mail::to($user)->send(new OrderShipped($order)); // User waits

// Asynchronous email sending (good)
Mail::to($user)->queue(new OrderShipped($order)); // User gets instant response

6. Server Configuration: PHP-FPM, OPcache, and Database

A misconfigured server can make an application run slowly, regardless of what framework it is using.

  • PHP-FPM: Check that the PHP-FPM worker processes are properly configured to handle the incoming requests. Too few workers will result in queued requests, while too many will utilize too much memory.
  • OPcache: This is a must for PHP performance, as it caches the compiled PHP byte code. Make sure it is enabled and configured correctly in your php.ini.
  • Database: A slow database will always give a slow application, so make sure that the DB is optimized, indexed, and has enough resources to handle the incoming queries.

These are infrastructural concerns, not Laravel ones, but they directly impact your Laravel application's perceived speed.

Tools to Identify Bottlenecks

Before you decide to blame Laravel, make sure to utilize some of the following tools to find the real reason behind the slow performance:

  • Laravel Debugbar – it’s a must-have debug extension for any Laravel developer. It gives detailed information about the application, allowing you to find N+1 queries and other performance issues in a matter of seconds.
  • Similar to Debugbar, but with a different set of features. The information about the performance is available in the browser’s developer tools.
  • Blackfire.io – static analysis of the code. This tool allows you to find out which parts of the code take the most time. It’s recommended to use it in the production environment. Another option is Xdebug – it’s much heavier and should be used in a local environment only.
  • Database slow queries log – make sure that your MySQL/PostgreSQL server is set up to log slow queries. It’ll give you a list of the slowest queries executed in the database.
  • Application Performance Monitoring tools – services like New Relic, Datadog or Sentry (Performance Monitoring) can help you track down the issues in the production environment.

Best Practices for a Snappy Laravel App

Now that we've identified the sources of potential issues, let's examine some general best practices for developing a fast Laravel application.
  • Always Eager Load: Make sure to utilize with() on any relationships which you plan to iterate over or which have other relationships.
  • Cache Everything: Caching database queries, computationally-expensive operations, and API calls will give a massive performance boost.
  • Move Everything to Queue: Any process which does not need to happen synchronously should be dispatched to a queue.
  • Optimize Your Queries
    1. Add indexes to columns which are often queried
    2. Use select() to narrow queries to only the columns which are needed, especially in large tables
    3. Use chunk() or cursors when dealing with very large result sets
    4. Make sure to avoid N+1 queries
  • Optimize Your Server
    1. Make sure to enable and configure OPcache
    2. Tune your PHP-FPM settings according to your server's specs and needs
    3. Tune your database server
  • Reduce Dependencies: Only require packages which are necessary, as dependencies add to the bootstrap time and memory consumption of your application
  • Optimize Frontend Assets: Minify CSS and JS, compress images, utilize a CDN, etc. The user will notice slow assets first.

Conclusion

Next time you face a slow Laravel app, do yourself a favor and don't jump to conclusions that the framework itself is to blame. Laravel is a performant framework. The issue almost certainly comes from elsewhere in your app's code, databases, external services, or server setup.

Thank you for reading this article 😊

For any query, do not hesitate to comment 💬


Previous Post Next Post

Contact Form