1

I have been trying to learn more about ways to better optimize my eloquent queries. I understand that it involves having more robust database designs and asking data from the server the "right" way.

The Application

I have a sample application that lets you add task lists, remove task lists, and edit them (just the title mostly). With each task list, you can add tasks, delete tasks, and edit tasks. A typical CRUD app.

enter image description here

The Code

I wont post my Vue components, unless requested, because I am not sure it's relevant. I am more interested in the initial query optimization. I have my relationships setup like this:

Model: User.php

<?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

use App\Models\TaskList;
use App\Task;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'first', 'last', 'email', 'phone', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

    public function lists() {
        return $this->hasMany(TaskList::class,'user_id');
    }
}

Model: TaskList.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

use User;
use App\Models\Task;

class TaskList extends Model
{
    protected $fillable = ['user_id','status','title'];

    public function user() {
        return $this->belongsTo(User::class);
    }

    public function tasks() {
        return $this->hasMany(Task::class,'list_id');
    }

    public function scopeLists() {
        return $this->where('user_id',auth()->user()->id);
    }
}

Model: Task.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

use App\Models\TaskList;
use App\User;

class Task extends Model
{
    public function list() {
        return $this->belongsTo(TaskList::class);
    }
}

Controller: TaskListController.php @index

public function index()
{
    $user = auth()->user()->load('lists.tasks');

    $data = [
        'lists' => $user->lists,
    ];        

    return view('user.tasklist',$data);
}

In my view I am not doing anything special. Just sticking my data into my Vues array member lists:

let app = new Vue({
        el: '#app',
        data: {
            lists: []
        }
    });

    @if(isset($lists))
        @foreach($lists as $list)
            app.lists.push({
                id: '{{ $list->id }}',
                title: '{!! addslashes($list->title) !!}',
                status: '{{ $list->status }}',
                tasks: JSON.parse('{!! $list->tasks !!}')
            });
        @endforeach
    @endif

The Problem

The problem is simply that My load time is at about 1 second. I have read mixed information online that Laravel is a slow frameworks, others claim they can get sub 20ms load time and so on. Granted, I understand that I am in dev environment and I am going to see different results from production. I also understand that I have dev packages that wouldn't necessarily be loaded in production and that would improve booting. But I think these numbers below are pretty slow and there's something else going on here.

enter image description here

enter image description here

The Question

Simply, how can I improve performance and optimize my application in this situation?

I want to know what steps I can take in this specific scenario to improve my applications performance. In my dev box, fully loaded with all my dev packages, I can achieve sub 500ms load times. Even 500ms seems slow. Can Sub 200ms or sub 100ms be achieved? Is it just a hardware thing? Or am I totally wrong about my relationships and loading?

"require": {
    "php": ">=7.1.3",
    "fideloper/proxy": "~4.0",
    "laravel/framework": "5.6.*",
    "laravel/tinker": "~1.0"
},
"require-dev": {
    "barryvdh/laravel-debugbar": "^3.1",
    "filp/whoops": "~2.0",
    "fzaninotto/faker": "~1.4",
    "mockery/mockery": "~1.0",
    "phpunit/phpunit": "~7.0",
    "symfony/thanks": "^1.0"
},

Any insight into optimizing this is greatly appreciated!

Kevin Pimentel
  • 2,056
  • 3
  • 22
  • 50
  • 1
    The first rule of performance optimisation: measure. Profile your application and find what exactly takes most of the time. There is no generic performance optimisation advice that would make an arbitrary application faster automagically. – zerkms Feb 15 '18 at 00:48
  • I don't actually know how to do that but it sounds like it's time to learn. Thanks for the tip. – Kevin Pimentel Feb 15 '18 at 01:05

1 Answers1

2

About your code

In my knowledge, you are querying your database in the right way, you are using lazy eager loading, that do exactly the same queries that the eager loading do.

You may have some problem on your database. You did not posted the migrations. You must have foreign keys on task_lists.user_id and tasks.list_id referencing to user.id and task_lists.id respectively.

Other possible causes

  • You are saying that your entire request takes 1s, and are focusing on the queries, but you didn't show the actual query times on Laravel Debugbar.
  • Note that: the booting time is ~600ms and the execution time ~300ms. Your queries are within the execution time (with many other things).
  • The booting time is mostly affected by slowness on filesystem access, caused by the common bad guy, the HDD. Replace by a SSD and you'll notice a high improvement.
  • Laravel on debug mode with Laravel Debugbar running is significantly slower then on production mode. This is because the Database Query Logger is enabled and for other reasons.
  • If you have the php xdebug installed, it will also slow down your execution time.
  • Remove from Laravel on production everything that you do not use. Don't use cookies? Why load CookieServiceProvider? Don't use filesystem? Why load FilesystemServiceProvider? Review your app.providers config. This will reduce the booting time since you'll load less files.

Okay, fixed all of this, how to improve performance after?

Assuming that your environment is optimized, your code is optimized, and you still have slow response times, it's time to Profile your code. With the right profiler tool you will see exactly what piece of code is slowing down.

If the Database Query itself is slow and there's nothing that you can do to improve, you can use the Laravel's Cache with memcached or redis driver, and cache out your slow queries, so instead of querying the database for the results, they will be on memory and available in microseconds.

Elias Soares
  • 9,884
  • 4
  • 29
  • 59
  • 1
    Excellent that's exactly the type of insight I was hoping for. I am going to tinker around and see if I can improve my results by following the points you laid out. I am going to leave the question open though because I want to see if others have more to add! thanks again! – Kevin Pimentel Feb 15 '18 at 01:28
  • Good luck! Optimizing is a hard job that most developers don't do, then tell to others that need more infrastructure. :-( – Elias Soares Feb 15 '18 at 01:30