Разработчик Magento заменил 300 отдельных вызовов ProductRepositoryInterface::getById() на один запрос к коллекции. Количество обращений к базе данных сократилось с 300 до 1, и страница категории стала загружаться заметно быстрее. Но какова цена? Некоторые товары исчезли с витрины, поставив в тупик покупателей и владельцев.

Проблема N+1 на странице категории Magento

На странице отображалось 300 товаров. Для каждого из них код извлекал два пользовательских атрибута через метод репозитория getById(), создавая по одному запросу на каждый товар — классический паттерн N+1. Замена этих поштучных загрузок на коллекцию, которая подтягивает все нужные строки одним запросом, резко сократила их количество. Задержка на стороне фронтенда уменьшилась, но в списке товаров пропали позиции, которые отображались ранее.

Почему коллекция скрывает товары, которых нет в наличии

Модуль CatalogInventory в Magento автоматически добавляет фильтр «в наличии» (in-stock) к любой коллекции товаров, созданной для фронтенда. Фильтр активируется, когда в настройках магазина отключена опция Display Out of Stock Products — это распространенный выбор для мерчантов, которые не хотят показывать недоступные товары.

Напротив, репозиторий никогда не применяет фильтр по наличию. Он просто проверяет существование товара и возвращает его, независимо от уровня запасов. Когда код переключился с вызовов репозитория на коллекцию, фильтр наличия незаметно отсек все товары с нулевым количеством. Ошибок в логах не было; коллекция просто вернула меньше строк, чем ожидал разработчик.

Что это значит для мерчантов

Витрина, которая незаметно скрывает отсутствующие в наличии SKU, создает ряд проблем:

  • Фронтенд-компоненты получают неполные данные, что приводит к пустым меткам или неверным ценам.
  • Отладка усложняется, так как симптом — отсутствие товаров — не вызывает никаких предупреждений.

Поскольку большинство автоматизированных тестов используют фикстуры с товарами в наличии, баг часто остается скрытым до тех пор, пока сайт не начнет работать с реальными остатками.

Как сохранить скорость, не скрывая остатки

  1. Пропускайте стандартный фильтр наличия — Явно удаляйте фильтр из коллекции, если вам нужны все товары независимо от их доступности. Magento предоставляет методы для отключения или замены плагина наличия (stock plugin) для конкретного запроса.
  2. Используйте репозиторий, когда важна точность — Репозиторий гарантирует, что будет возвращен каждый запрошенный ID товара, даже если его нет в наличии. Кэшируйте результаты или загружайте только необходимые ID, чтобы минимизировать количество лишних запросов.
  3. Используйте ресурсную модель (resource model) или чистый SQL — Делайте запрос к таблице товаров напрямую для получения нужных атрибутов. Это позволяет обойти все плагины коллекций и дает полный контроль над тем, какие строки будут включены в результат.

Скорость против корректности

Замена «шумного» цикла N+1 на один запрос к коллекции кажется естественным решением; снижение задержки весьма ощутимо. Однако более быстрый запрос, возвращающий неверный набор товаров, — это дефект, а не оптимизация. Разработчики должны взвешивать чистую скорость и риск отображения неполного каталога.

На что обратить внимание в дальнейшем

  • Рассинхронизация конфигурации — Убедитесь, что флаг Display Out of Stock Products соответствует ожидаемому поведению любого кастомного кода коллекций.
  • Побочные эффекты плагинов — Другие модули могут добавлять свои собственные фильтры к коллекциям. Пересматривайте стек плагинов всякий раз, когда меняете стратегию запросов.

Урок очевиден: исправление проблемы N+1 с помощью коллекции приносит пользу только в том случае, если вы также проверяете стандартные фильтры этой коллекции. Игнорирование встроенного фильтра наличия Magento может незаметно «подрезать» ваш каталог, превращая прирост производительности в потерю выручки.