Must-Know Tips for Efficient Laravel Apps (Not just selecting only needed columns, eager loading instead of lazy loading, caching queries, using queues, indexes, and blah blah blah)

Suman Shrestha
3 min readAug 31, 2024

--

Forget the usual advice like selecting only needed columns, sub-queries, eager loading, caching queries, using queues, and indexes. This article dives deeper into actionable strategies that can truly level up your Laravel applications.

Use toBase() Function

The toBase() function is a hidden gem in Laravel's Eloquent ORM. When you don't need all the functionality of an Eloquent model, using toBase() can significantly improve performance. It returns a plain Object instance instead of hydrating a full Eloquent model.

$users = User::query()->toBase()->get();

This approach is particularly useful when you’re dealing with large datasets and don’t need model events or relationships. It can reduce the memory usage by up to three-quarters.

Utilise chunkById for Large Datasets

When working with large datasets, using `chunkById()` can prevent memory issues and improve performance. This method processes a large number of database records in smaller chunks.

User::chunkById(100, function ($users) {
foreach ($users as $user) {
// Process each user
}
});

This approach is more efficient than retrieving all records at once, especially for tasks like data exports or batch processing.

Learn about caveats of the chunk method in Laravel here.

Implement HTTP Pool for Concurrent Requests

When your application needs to make multiple HTTP requests, using an HTTP pool can significantly reduce execution time. Laravel’s HTTP client supports concurrent requests out of the box.

use Illuminate\Support\Facades\Http;

$responses = Http::pool(fn ($pool) => [
$pool->get('http://example.com/api/users?page=1&size=200'),
$pool->get('http://example.com/api/users?page=2&size=200'),
$pool->get('http://example.com/api/users?page=3&size=200'),
$pool->get('http://example.com/api/users?page=4&size=200'),
]);

This method sends all requests concurrently, improving overall performance.

Handle Laravel Array Validation in Chunks

For large arrays that need validation, processing them in chunks can prevent timeouts and reduce memory usage. Here’s an example of how to implement this:


$data = collect($request->input('items', []));
$data->chunk(100)->each(function ($chunk) use ($validator) {
$validator = Validator::make([], []);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
});

This approach allows you to validate large arrays without overwhelming your server’s resources.

Note: Bail early if possible, otherwise return only partial error and append message like (and 600 more errors;).

Use Cache Lock to Prevent Duplicate Actions

To prevent race conditions or duplicate actions, implement Cache locks with Redis. This is particularly useful for scenarios like preventing double-clicks on submission buttons or ensuring only one instance of a scheduled task runs at a time.

use Illuminate\Support\Facades\Cache;

$lock = Cache::lock('processing-podcast', 120);

if (!$lock->get()) {
throw new Exception('Something similar is already in progress');
}

// The lock has been acquired...
// Do your transaction
$lock->release();

This ensures that only one process can execute the critical section at a time.

Prefer Database Operations Over In-Memory Processing

When possible, perform operations in the database rather than retrieving data and processing it in PHP. Database engines are optimized for these operations and can handle them more efficiently.

Instead of:

// Instead of this
$activeUsers = User::all()->filter(function ($user) {
return $user->is_active;
});

// Do this
$activeUsers = User::where('is_active', true)->get();

This approach reduces memory usage and takes advantage of database optimisations.

Use Lookup Instead of Queries in Loops

Running queries inside loops can lead to the N+1 problem, severely impacting performance. Instead, use lookups or eager loading.

// Instead of this
foreach ($users as $user) {
$todayOrder= Order::where('user_id', $user->id)->where('created_at',today())->first();
// Process
}

// Do this
$types = Order::where('created_at', today())->pluck('id','user_id');

foreach ($users as $user) {
$userType = $types->get($user->id) ?? null;
// Process order
}

Remember, optimisation is an ongoing process, and it’s important to profile your application to identify bottlenecks and areas for improvement.

If you have access to laracasts.com, learn all this in the course: Eloquent Performance Patterns.

Follow me on twitter: https://twitter.com/sumfreelancer

--

--

Suman Shrestha

#fullstack #tailwindcss #php #Laravel #Vue #React #Meroshare