Eager Loading laravel

Eager Loading laravel

Laravel là một trong những framework của php phổ biến hiện nay, được cộng đồng php developer yêu thích và sử dụng.
Query Performance là chủ đề mà nhiều bạn khi mới bắt đầu tiếp cận laravel có thể chưa quan tâm đến, nhưng nó lại là một vấn đề hết sức quan trọng trong hệ thống.
Chúng ta hãy cùng tìm hiểu về nó nhé :D

Giả sử ta có quan hệ như sau

class User extends Authenticatable
{
    use Notifiable;

    protected $fillable = [
        'name', 'email', 'password',
    ];

    protected $hidden = [
        'password', 'remember_token',
    ];

    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    public function todos()
    {
        return $this->hasMany(Todo::class);
    }
}
class Todo extends Model
{
    protected $fillable = [
        'title',
        'content',
        'user_id',
    ];

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

I.Tìm hiểu về Eager Loading Relationships

1.Bài toán

Giả sử bạn sẽ thực hiện đoạn code sau để hiện thị công việc của tất cả user
Trong controller ta lấy tất cả users

$users = User::all();
@foreach($users as $user)
    @foreach($user->todos as $todo)
        <h1>{{ $todo->title }}</h1>
        <p>{{ $todo->content }}</p>
    @endforeach
@endforeach

2.Vấn đề

Đoạn code trên nếu run tất nhiên nó sẽ work và hiện thị thông tin công việc của user :)).
Vấn đề ở đây là ở lệnh $user->todos, what happended khi nó được thực hiện: laravel sẽ execute một câu query cho mỗi user:

select * from todos where user_id = ? 

Kết quả là nếu hệ thống có 1000 user thì ta phải run 10001 query để hiện thị thông tin công việc

3.Giải pháp

Eager Loading được dùng để giải quết vấn đề đó
Lệnh lấy danh sách users trong controller ta cần phải eager thêm todos

User::with('todos')->get()

Bản chất của câu lệnh trên sẽ thực hiện 2 câu query:
query 1: lấy ra danh sách users:

select * from users;

query 2: dùng để lấy ra tất cả các todos của tất cả user đã lấy ở trên

select * from todos where user_id in (?,?,?)

Và khi đó kết quả là khi hiển thị danh sách todos user sẽ không cần phải thực hiện query nữa và như vậy tổng số query phải thực hiện ở đây chỉ là 2 query.

II.Vấn đề của Eager Loading

1. Bài toán

Giả sử hệ thống có 3 user và mỗi user có 1000000 task để làm
Yêu cầu vẫn là hiển thị tất cả công việc của 3 user trên: gồm cả công việc chưa làm hoặc đã hoàn thành.

2.Vấn đề

Khi dùng eager loading số lượng bản ghi phải load ra là 30000003, điều này có thể dẫn đến việc out of memory(đối với server khỏe có thể với số lượng bản ghi lớn hơn thì mới bị), hoặc có thể bị timeout do data load ra vượt quá dung lượng cho phép của server hoặc do thời gian load dữ liệu quá lâu.

3.Giải pháp

Giải pháp mình áp dụng ở đây là phân trang và infinite load scroll(tức là khi xem đến user nào thì thì mình sẽ goi api để todos của user đấy)

Todo::where('user_id', $userId)->paginate($numberPerPage);

Bên cạnh đấy chúng ta có thể app dụng thêm các phương pháp khác để optimal query như: cache lại những expensive query, đánh index ...

III.Kết luận

Qua bài viết mình chỉ muốn mọi người chú ý hơn trong việc dùng eager loading đối với những table có dữ liệu lớn. Và cuối cùng bài viết dựa trên kiến thức hạn hẹp của mình nên mọng mọi người góp ý để mình có thể cải thiện trong tương lai.