- Опубликовано: 8 дек 2024
- 165
7 уровней оптимизации Laravel: от новичка до мастера-фокусника
Как сэкономить до 98% ресурсов, или как превратить простой Laravel-код в магию, которую мало кто осмелится трогать?
Готовьтесь: мы отправимся в увлекательное путешествие по ступеням оптимизации в Laravel. От наивного «лишь бы всё работало» до уверенного «коллеги либо назовут вас волшебником, либо обвинят в шаманстве». Поехали!
Уровень 1: Первый шаг — жадная загрузка (Eager Loading)
Всё начинается с классики. Вы только-только узнали, что такое with()
, и чувствуете себя героем: никакого N+1, только два запроса — и все данные под рукой.
use App\Models\Post;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
$posts = Post::with('user')->get();
return view('posts', ['posts' => $posts]);
});
Результат: Для 100k постов затрачиваем 191 МБ и примерно 0,63 сек. Итоговый «коэффициент ресурсоёмкости» — 120 единиц (условно). Неплохо, но не идеально.
Уровень 2: Первые серьёзные шаги — «А точно ли нужны все эти поля?»
Далее вы задумались: зачем вытаскивать всё подряд? Ограничьте выборку до нужных столбцов, и уже почувствуете разницу.
use App\Models\Post;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
$posts = Post::with('user:id,name')
->select('id','user_id','title')
->get();
return view('posts', ['posts' => $posts]);
});
Результат: Минус 20% ресурсоёмкости. Конечно, вы ещё не спасли мир, но уже выглядите более профессионально.
Уровень 3: Переход на ручное управление — разделяем запросы
Теперь вы идёте дальше. Вместо жадной загрузки — два отдельных запроса: один для постов, другой для пользователей. В итоге вы создаёте своего рода таблицу соответствий.
use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
$posts = Post::select('id','user_id','title')->get();
$users = User::whereIn('id', $posts->pluck('user_id')->unique())
->pluck('name', 'id');
return view('posts', ['posts' => $posts, 'users' => $users]);
});
Результат: Ещё +15% эффективности. Вы уже пьёте кофе с лёгкой улыбкой превосходства.
Уровень 4: Попытка хитрить с distinct
Вы пытаетесь доверить часть оптимизации базе данных: пусть она сама выберет уникальные значения user_id
. Результаты? Есть прирост по времени, но с памятью особого выигрыша нет.
use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
$query = Post::query();
$posts = (clone $query)->select('id', 'user_id', 'title')->get();
$users = User::whereIn('id', (clone $query)->select('user_id')->distinct())
->get();
return view('posts', ['posts' => $posts, 'users' => $users]);
});
Результат: Быстрее, но ощутимого облегчения памяти нет. Что ж, оптимизация — это путь проб и ошибок.
Уровень 5: Скрытая магия — toBase()
Вы обнаружили toBase()
, который пропускает всю «магию» Eloquent, оставляя только «чистые» результаты. И вот тут начинается серьёзный прорыв.
use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
$query = Post::query();
$posts = (clone $query)->select('id','user_id','title')
->toBase()
->get();
$users = User::whereIn('id', (clone $query)->select('user_id')->distinct())
->toBase()
->get();
return view('posts', ['posts' => $posts, 'users' => $users]);
});
Результат: Минус 85% ресурсоёмкости. Теперь вы чувствуете себя настоящим гуру Laravel.
Уровень 6: Ещё шаг — chunkById
Время применить «чанкование». Вы загружаете данные порционно и аккуратно складываете их в коллекцию. К примеру, по 15 000 записей.
use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
$query = Post::query();
$posts = collect();
(clone $query)->select('id', 'user_id', 'title')
->toBase()
->orderBy('posts.id')
->chunkById(15000, function ($collection) use (&$posts) {
$posts->push(...$collection);
}, 'id');
$users = User::whereIn('id', (clone $query)->select('user_id')->distinct())
->toBase()
->get();
return view('posts', ['posts' => $posts, 'users' => $users]);
});
Результат: Ещё плюс 14% к оптимизации. Кажется, вы уже приблизились к пределу.
Уровень 7: Высший пилотаж — Lazy Collections
Финальный козырь в рукаве — lazyById()
. Представьте себе chunkById, но более «ленивый» и гибкий. Вы загружаете данные партиями (например, по 10 000), удерживая их в памяти по мере необходимости.
use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
$query = Post::query();
$users = User::whereIn('id', (clone $query)->select('user_id')->distinct())
->pluck('name', 'id');
$posts = (clone $query)
->select('id', 'user_id', 'title')
->toBase()
->lazyById(10000, 'id');
return view('welcome', [
'posts' => $posts,
'users' => $users,
]);
});
Результат: Память используется ещё более экономно. Вы стоите на вершине горы оптимизации, смотрите свысока на затраты ресурсов и понимаете, что сократили их примерно на 98% (с 120 до 2,4). Ваши коллеги в шоке — либо восхищены, либо озадачены тем, что вы сотворили.
Итог:
Вы прошли путь от новичка, довольствующегося eager loading, до мастера, выжимающего из Laravel максимум. Но не забывайте, с чего вы начинали. И не удивляйтесь, если через секунду ваш начальник влетит с вопросом: «А как там прогресс по остальным задачам?» Вы сэкономили серверные ресурсы — но кто сэкономит ваше время теперь?
Была статья полезной: