Bài 33 — Tối ưu hiệu năng Laravel: Bắt đầu bằng một request cụ thể, dữ liệu xác định và chỉ số có thể lặp lại. Trong TaskFlow, trang project phải giữ số query ổn định khi tăng số task; điều đó không đồng nghĩa thời gian phản hồi hoặc bộ nhớ luôn cố định.
1. Chọn câu hỏi đo được
ProjectQueryBudgetTest tạo một project, task có assignee và mỗi task có một attachment. Test gọi HTTP GET trang project với per_page=20, ghi query chỉ trong lúc xử lý request. Sau đó thêm task để tăng từ 1 lên 20 và đo lại. Fixture/migration nằm ngoài vùng đo. Session dùng array và user được thiết lập bằng actingAs; đây không phải toàn bộ chi phí request người dùng thật.
Kết quả kiểm chứng: cả hai request đều có 5 query — project theo route binding, count phân trang, danh sách task, eager-load assignee và attachments. Test kiểm tra số query không tăng theo dòng, đồng thời có giới hạn review 6 query. Assertion đúng 5 ghi nhận checkpoint; khi thêm tính năng hợp lệ cần review lại phép đo, không tăng ngưỡng chỉ để test xanh.
2. Tại sao trang không phát sinh N+1?
// ProjectController::show: authorize first, paginate, then eager-load.
Gate::authorize('view', $project);
$tasks = $query->paginate($project, $request->query());
$tasks->getCollection()->load('attachments');
foreach ($tasks as $task) {
$task->setRelation('project', $project);
}
return view('projects.show', ['project' => $project, 'tasks' => $tasks]);
// TaskListQuery keeps assignee keys needed by the relationship:
$query = $project->tasks()->with('assignee:id,name');TaskListQuery eager-load assignee, controller tải attachments theo batch. Policy trong vòng lặp view đọc project của task; setRelation gắn project đã được authorize thay vì truy vấn lại từng task. Chỉ làm vậy vì query tasks đã được giới hạn bởi chính project đó. Không gắn một project bất kỳ để vượt qua kiểm tra quyền.
Test bài 12 đã chứng minh ví dụ đọc project của 5 task giảm từ 6 xuống 2 query bằng eager loading. Bài này kiểm tra trang HTTP thật có Blade, policy và attachment, tránh suy luận từ một query demo sang toàn bộ giao diện. Đọc code policy/component khi tìm N+1, không chỉ nhìn controller.
3. Ít query chưa chắc ít chi phí
Danh sách task có per_page tối đa 50 nhưng số attachment trên mỗi task hiện chưa có quota hoặc phân trang riêng. Một query attachments vẫn có thể trả rất nhiều dòng. Test dùng một attachment/task không chứng minh trường hợp hàng nghìn file vẫn nhẹ. Khi nhu cầu tăng, chọn withCount cho màn hình tổng quan và endpoint phân trang attachment, thay vì tải toàn bộ metadata chỉ để hiện số lượng.
paginate chạy count và offset. Trang sâu, tìm kiếm LIKE có wildcard đầu, hoặc sort title có thể tốn nhiều tài nguyên dù số query không đổi. Dùng EXPLAIN trên engine triển khai và dữ liệu có phân bố thực tế trước khi thêm index. Cursor pagination cần thứ tự ổn định và thay đổi contract điều hướng; không đổi chỉ vì tên gọi nghe nhanh hơn. Không thêm index cho mọi cột vì sẽ tăng chi phí ghi và lưu trữ.
4. Đo latency và bộ nhớ riêng
Ở staging, ghi p50/p95, tỷ lệ lỗi, rows trả về, query time, memory peak, queue lag và kích thước response. Giữ cùng dataset, runtime, cache warm/cold và mức concurrency khi so sánh trước/sau. Không dùng tổng thời gian artisan test làm latency HTTP. Không chạy load test lên production khi chưa có kế hoạch tải và quyền phù hợp.
DB::enableQueryLog giữ query/bindings trong bộ nhớ, nên test dùng try/finally để disable và flush. Không bật query log vô hạn trên server thật hoặc ghi binding chứa email/token/nội dung riêng tư. Observability production cần sampling, retention và lọc dữ liệu nhạy cảm.
5. Cache đúng tầng
ProjectTaskCount ở bài 19 dùng key theo project và TTL 30 giây; queue maintenance xóa key sau tạo task ở bài 26. Đây là số đếm có thể cũ, không phải nguồn quyết định quyền hoặc transaction. Reader đang tính vẫn có thể ghi lại dữ liệu cũ sau invalidation; process chết trước enqueue cũng làm mất tín hiệu. Không cache chung một trang có dữ liệu owner mà bỏ qua scope người dùng.
Phân biệt application cache với cache config/routes/views của framework. Trong release staging đã cấu hình đúng môi trường, có thể kiểm tra riêng các bước:
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
Sau config cache, đọc cấu hình qua config thay vì gọi env trong business code. Khi thay đổi cần tái tạo cache và restart process sống lâu theo kế hoạch deploy. Không coi optimize:clear là thao tác không ảnh hưởng dữ liệu: lệnh còn xóa key của default cache driver. Bài này không chạy các lệnh cache trên production hoặc thay cấu hình FPM/OPcache.
6. Bảo vệ cải tiến bằng regression
php artisan test --filter=ProjectQueryBudgetTest
php artisan test --filter=TaskFlowLoadingTest
php artisan test
vendor/bin/pint --test
Checkpoint đạt 98 tests/442 assertions và Pint pass. Không cần sửa query production trong bước này vì eager loading đã có; phần bổ sung là regression theo trang thật. Chưa đo tải đồng thời, p95 trên server, engine MySQL/PostgreSQL, attachment cardinality lớn hoặc hiệu quả OPcache.
Bài tập: tăng attachment trên mỗi task trong fixture rồi so số query với số model hydrate; thử bỏ eager-load trong nhánh thử nghiệm để thấy test bắt hồi quy, sau đó phục hồi. Ghi rõ giới hạn của chỉ số trước khi công bố “nhanh hơn”. Tiếp theo là quy trình deploy và quản lý worker/scheduler.
Tham khảo: Eloquent relationships, Laravel deployment. Điều hướng: Bài 32: CI · Lộ trình.




Chưa có bình luận. Hãy là người đầu tiên chia sẻ ý kiến.