Разработчик Magento заменил 300 отдельных вызовов ProductRepositoryInterface::getById() на один запрос к коллекции. Количество обращений к базе данных сократилось с 300 до 1, и страница категории стала загружаться заметно быстрее. Но какова цена? Некоторые товары исчезли с витрины, поставив в тупик покупателей и владельцев.
Проблема N+1 на странице категории Magento
На странице отображалось 300 товаров. Для каждого из них код извлекал два пользовательских атрибута через метод репозитория getById(), создавая по одному запросу на каждый товар — классический паттерн N+1. Замена этих поштучных загрузок на коллекцию, которая подтягивает все нужные строки одним запросом, резко сократила их количество. Задержка на стороне фронтенда уменьшилась, но в списке товаров пропали позиции, которые отображались ранее.
Почему коллекция скрывает товары, которых нет в наличии
Модуль CatalogInventory в Magento автоматически добавляет фильтр «в наличии» (in-stock) к любой коллекции товаров, созданной для фронтенда. Фильтр активируется, когда в настройках магазина отключена опция Display Out of Stock Products — это распространенный выбор для мерчантов, которые не хотят показывать недоступные товары.
Напротив, репозиторий никогда не применяет фильтр по наличию. Он просто проверяет существование товара и возвращает его, независимо от уровня запасов. Когда код переключился с вызовов репозитория на коллекцию, фильтр наличия незаметно отсек все товары с нулевым количеством. Ошибок в логах не было; коллекция просто вернула меньше строк, чем ожидал разработчик.
Что это значит для мерчантов
Витрина, которая незаметно скрывает отсутствующие в наличии SKU, создает ряд проблем:
- Фронтенд-компоненты получают неполные данные, что приводит к пустым меткам или неверным ценам.
- Отладка усложняется, так как симптом — отсутствие товаров — не вызывает никаких предупреждений.
Поскольку большинство автоматизированных тестов используют фикстуры с товарами в наличии, баг часто остается скрытым до тех пор, пока сайт не начнет работать с реальными остатками.
Как сохранить скорость, не скрывая остатки
- Пропускайте стандартный фильтр наличия — Явно удаляйте фильтр из коллекции, если вам нужны все товары независимо от их доступности. Magento предоставляет методы для отключения или замены плагина наличия (stock plugin) для конкретного запроса.
- Используйте репозиторий, когда важна точность — Репозиторий гарантирует, что будет возвращен каждый запрошенный ID товара, даже если его нет в наличии. Кэшируйте результаты или загружайте только необходимые ID, чтобы минимизировать количество лишних запросов.
- Используйте ресурсную модель (resource model) или чистый SQL — Делайте запрос к таблице товаров напрямую для получения нужных атрибутов. Это позволяет обойти все плагины коллекций и дает полный контроль над тем, какие строки будут включены в результат.
Скорость против корректности
Замена «шумного» цикла N+1 на один запрос к коллекции кажется естественным решением; снижение задержки весьма ощутимо. Однако более быстрый запрос, возвращающий неверный набор товаров, — это дефект, а не оптимизация. Разработчики должны взвешивать чистую скорость и риск отображения неполного каталога.
На что обратить внимание в дальнейшем
- Рассинхронизация конфигурации — Убедитесь, что флаг Display Out of Stock Products соответствует ожидаемому поведению любого кастомного кода коллекций.
- Побочные эффекты плагинов — Другие модули могут добавлять свои собственные фильтры к коллекциям. Пересматривайте стек плагинов всякий раз, когда меняете стратегию запросов.
Урок очевиден: исправление проблемы N+1 с помощью коллекции приносит пользу только в том случае, если вы также проверяете стандартные фильтры этой коллекции. Игнорирование встроенного фильтра наличия Magento может незаметно «подрезать» ваш каталог, превращая прирост производительности в потерю выручки.
