Deweloper Magento zamienił 300 oddzielnych wywołań ProductRepositoryInterface::getById() na pojedyncze zapytanie do kolekcji. Liczba zapytań do bazy danych spadła z 300 do 1, a strona kategorii ładowała się zauważalnie szybciej. Cena za to? Niektóre produkty zniknęły ze sklepu, wprawiając klientów i właścicieli w osłupienie.
Problem N+1 na stronie kategorii Magento
Strona wyświetlała 300 produktów. Dla każdego z nich kod pobierał dwa niestandardowe atrybuty za pomocą metody getById() repozytorium, generując jedno zapytanie na produkt — klasyczny wzorzec N+1. Zastąpienie tych pojedynczych ładowów kolekcją, która pobiera wszystkie potrzebne wiersze w jednym zapytaniu, drastycznie zmniejszyło liczbę zapytań. Opóźnienie front-endu uległo poprawie, ale lista produktów nie zawierała już elementów, które wcześniej były widoczne.
Dlaczego kolekcja ukrywa produkty niedostępne w magazynie
Moduł CatalogInventory w Magento automatycznie dodaje filtr „in-stock” do każdej kolekcji produktów tworzonej dla front-endu. Filtr ten aktywuje się, gdy ustawienie sklepu Display Out of Stock Products jest wyłączone, co jest częstym wyborem wśród sprzedawców, którzy nie chcą pokazywać niedostępnych produktów.
W przeciwieństwie do tego, repozytorium nigdy nie stosuje filtra stanów magazynowych. Po prostu sprawdza, czy produkt istnieje, i zwraca go, niezależnie od poziomu zapasów. Gdy kod został zmieniony z wywołań repozytorium na kolekcję, filtr stanów magazynowych po cichu usunął każdy produkt, którego ilość wynosiła zero. Nie odnotowano żadnego błędu; kolekcja po prostu zwróciła mniej wierszy, niż deweloper się spodziewał.
Co to oznacza dla sprzedawców
Sklep internetowy, który po cichu usuwa niedostępne jednostki SKU, generuje kilka problemów:
- Komponenty front-endu otrzymują brakujące wpisy, co skutkuje pustymi etykietami lub błędnymi cenami.
- Debugowanie staje się trudniejsze, ponieważ objaw — brakujące produkty — nie generuje żadnych ostrzeżeń.
Ponieważ większość testów automatycznych korzysta z danych testowych (fixtures) produktów dostępnych w magazynie, błąd często pozostaje ukryty, dopóki strona nie zacznie pracować na rzeczywistych stanach magazynowych.
Jak zachować szybkość bez ukrywania stanów magazynowych
- Pomiń domyślny filtr stanów magazynowych – Jawnie usuń filtr z kolekcji, jeśli potrzebujesz każdego produktu, niezależnie od jego dostępności. Magento oferuje metody pozwalające wyłączyć lub zastąpić wtyczkę (plugin) stanów magazynowych dla konkretnego zapytania.
- Używaj repozytorium, gdy liczy się dokładność – Repozytorium gwarantuje, że każdy żądany identyfikator produktu zostanie zwrócony, nawet jeśli jest on niedostępny. Przechowuj wyniki w pamięci podręcznej (cache) lub ładuj tylko niezbędne identyfikatory, aby ograniczyć liczbę dodatkowych zapytań.
- Zastosuj model zasobów (resource model) lub surowe zapytania SQL – Zapytaj bezpośrednio tabelę produktów o wymagane atrybuty. Pozwala to ominąć wszystkie wtyczki kolekcji i daje pełną kontrolę nad tym, które wiersze zostaną uwzględnione.
Szybkość kontra poprawność
Zamiana „głośnej” pętli N+1 na pojedyncze zapytanie do kolekcji wydaje się naturalna; spadek opóźnień jest odczuwalny. Jednak szybsze zapytanie, które zwraca niewłaściwy zestaw produktów, jest błędem, a nie optymalizacją. Deweloperzy muszą rozważyć czystą szybkość w stosunku do ryzyka wyświetlania niepełnego katalogu.
Na co zwrócić uwagę w przyszłości
- Rozbieżność konfiguracji (Configuration drift) – Sprawdź, czy flaga Display Out of Stock Products jest zgodna z zamierzonym działaniem dowolnego niestandardowego kodu kolekcji.
- Skutki uboczne wtyczek (Plugin side effects) – Inne moduły mogą dodawać własne filtry do kolekcji. Przejrzyj stos wtyczek (plugin stack) za każdym razem, gdy zmieniasz strategię zapytań.
Lekcja jest jasna: naprawienie problemu N+1 za pomocą kolekcji przynosi korzyści tylko wtedy, gdy przeprowadzi się również audyt domyślnych filtrów kolekcji. Ignorowanie wbudowanego w Magento filtra stanów magazynowych może po cichu „przyciąć” Twój katalog, zamieniając zysk wydajnościowy w stratę przychodów.
