Laravel – Eloquent Performance at Scale

2 min read

Khi Database có 1 Triệu Rows, Eloquent sẽ thế nào?

Eloquent rất tiện khi project nhỏ.
Nhưng khi table đạt:

  • 500k rows
  • 1M rows
  • 10M rows

Bạn sẽ bắt đầu thấy:

  • Pagination chậm dần
  • Query filter mất nhiều thời gian
  • Memory tăng đột biến
  • CPU spike khi traffic cao

Bài này tập trung vào cách tối ưu Eloquent trong môi trường production thực tế.

Tài liệu chính thức:
https://laravel.com/docs/eloquent


1️⃣ Offset Pagination là “kẻ giết hiệu năng”

Mặc định:

User::paginate(20);

Laravel sẽ generate:

SELECT * FROM users LIMIT 20 OFFSET 20000;

Khi OFFSET lớn:

  • Database vẫn phải scan qua hàng chục nghìn row
  • Query chậm theo cấp số nhân

Giải pháp: Cursor Pagination

User::orderBy('id')->cursorPaginate(20);

Cursor pagination:

  • Không dùng OFFSET
  • Dùng last seen value
  • Nhanh hơn rất nhiều khi page sâu

Docs:
https://laravel.com/docs/pagination#cursor-pagination


2️⃣ Chunk vs Cursor vs Lazy

Chunk

User::chunk(100, function ($users) {
    foreach ($users as $user) {
        // xử lý
    }
});

Phù hợp:

  • Batch processing
  • Cron job

Cursor

foreach (User::cursor() as $user) {
    // xử lý
}
  • Dùng generator
  • Giữ memory cực thấp
  • Phù hợp dataset lớn

Lazy

User::lazy();
Tương tự cursor nhưng linh hoạt hơn trong pipeline.

3️⃣ Composite Index – Thứ Tự Rất Quan Trọng

Sai:

$table->index(['created_at', 'tenant_id']);

Nếu query chính là:

WHERE tenant_id = ? ORDER BY created_at DESC

Thì index nên là:

$table->index(['tenant_id', 'created_at']);

Thứ tự index ảnh hưởng trực tiếp đến query plan.

Đọc thêm:
https://use-the-index-luke.com/


4️⃣ whereDate Làm Mất Index

Sai:

User::whereDate('created_at', now())->get();

Điều này khiến database không dùng index vì có function bọc cột.

Tốt hơn:

User::whereBetween('created_at', [
    now()->startOfDay(),
    now()->endOfDay()
])->get();

Giữ được index.


5️⃣ EXPLAIN – Công Cụ Không Thể Thiếu

Nếu query chậm, chạy:

EXPLAIN SELECT ...

Quan tâm:

  • type (ALL là xấu)
  • key (index nào đang dùng)
  • rows (bao nhiêu row bị scan)

Nếu thấy:

type = ALL
→ full table scan → cần index.


6️⃣ Bulk Insert & Update

Sai:

foreach ($users as $data) {
    User::create($data);
}

Đúng:

User::insert($users);

Bulk insert giảm số lượng query đáng kể.


7️⃣ N+1 Ở Scale Lớn Càng Nguy Hiểm

Ở 100 record → có thể chậm nhẹ.
Ở 10k record → có thể làm server sập.

Luôn dùng:

User::with('posts')->get();

Và bật:

Model::preventLazyLoading();

8️⃣ Cache Chiến Lược

Nếu query:

  • Tính toán nhiều
  • Ít thay đổi

Dùng:

Cache::remember('dashboard_stats', 300, function () {
    return ReportService::generate();
});

Redis giúp giảm load database đáng kể.

Docs cache:
https://laravel.com/docs/cache


9️⃣ Khi Nào Phải Bỏ Eloquent?

Eloquent không tối ưu cho:

  • Complex reporting
  • Heavy aggregation
  • Data analytics

Trong trường hợp đó:

DB::select("SELECT ... GROUP BY ...");

Hoặc dùng read replica riêng cho reporting.

Kết luận

Khi làm việc với dataset lớn:

  • Tránh offset pagination
  • Dùng cursor hoặc chunk
  • Tối ưu index đúng thứ tự
  • Tránh function làm mất index
  • Phân tích query bằng EXPLAIN
  • Cache hợp lý
  • Không lạm dụng ORM

Avatar photo

Leave a Reply

Your email address will not be published. Required fields are marked *