N+1 в Laravel: поиск и лечение
Вопрос
Как вы находите и исправляете N+1 в Laravel-приложении?
Попробуйте сами
Ответьте вслух за 2 минуты, как на собесе. Потом сверьтесь с тезисами: что прозвучало, а что нет.
Показать ответ и разбор
Коротко
Что должно прозвучать в ответе
Признак: число запросов растёт с размером списка
Инструменты: Debugbar, Telescope, логирование запросов, preventLazyLoading
with и вложенные связи, load для дозагрузки
withCount, withSum, withExists вместо подсчёта в цикле
Выбор нужных колонок с ключами для сопоставления
API Resources и whenLoaded, чтобы не грузить связи неявно
Что могут спросить следом
- Что будет, если в with указать связь, которая не нужна?
- Как обнаружить N+1 в очередях и консольных командах?
Разбор
N+1 появляется, когда загружаем список моделей и в цикле или в шаблоне обращаемся к связи каждой. Признак: десятки одинаковых запросов, отличающихся только id, и время ответа растёт с длиной списка.
В разработке вижу это в Laravel Debugbar или Telescope, а надёжнее всего включаю Model::preventLazyLoading() для непродакшен-окружений: ленивая загрузка начинает бросать исключение, и тесты сразу падают.
Лечу жадной загрузкой: with(['author', 'comments.user']), а для уже полученной коллекции load. Если нужна только цифра, беру withCount или withSum, тогда связь вообще не грузится. Если ограничиваю колонки, не забываю ключи для сопоставления. В API Resources использую whenLoaded, чтобы ресурс не провоцировал ленивую загрузку. В консольных командах и заданиях то же самое, плюс обработка порциями через chunkById.
Ответьте сами, а не прочитайте
В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.