G-FORCE
0.0G
разгон
← все разборы

90 SQL-запросов на одной странице каталога


! Проблема

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

Решение

Поставил django-debug-toolbar и увидел реальную картину: 90+ запросов к базе на одну страницу. Классический N+1 — шаблон дёргал категорию и бренд у каждого товара отдельно. Добавил select_related для внешних ключей и prefetch_related для связей многие-ко-многим, прочитал EXPLAIN и завёл составные индексы ровно под те фильтры, которыми реально пользуются. Выдачу закэшировал в Redis с инвалидацией по сигналу сохранения товара.

Как я к этому пришёл

Моя первая гипотеза была «много картинок». Я сжал изображения, перевёл их в WebP, настроил ленивую загрузку — и выиграл около ста миллисекунд из тысячи восьмисот. Работа полезная, но проблема была не там.

Переломный момент наступил, когда я перестал гадать и поставил профайлер. Оказалось, что база отвечает быстро, но её спрашивают девяносто раз подряд — по одному запросу на товар. В шаблоне это выглядело безобидно: {{ product.category.name }}. Каждое такое обращение — отдельный поход в базу.

Дальше было скучно и эффективно. select_related подтянул категории одним JOIN. prefetch_related забрал теги вторым запросом вместо сотни. EXPLAIN показал, что фильтр по цене и категории идёт последовательным сканированием — составной индекс закрыл этот случай.

Отдельная история — кэш. Наивная реализация «кэшировать на пять минут» означала, что менеджер меняет цену и не видит её ещё пять минут. Пришлось вешать инвалидацию на сигнал post_save товара.

Итог: 180 миллисекунд и 6 запросов вместо 1.8 секунды и 90.

Вывод

Сначала измеряй, потом оптимизируй. Час с профайлером экономит день догадок — узкое место почти никогда не там, где кажется.

Другие разборы