OpenCart под нагрузкой: где тормозит каталог
Каталог OpenCart может замедляться не из-за одного «тяжёлого» запроса. Разбираем, как отделить очередь PHP-FPM от проблем MySQL, расширений, изображений и поиска, а затем проверить результат замерами.

Каталог OpenCart начинает отдавать страницы по 2–5 секунд задолго до полного отказа магазина: PHP-FPM накапливает очередь, MySQL тратит время на подсчёт товаров, а расширения добавляют запросы к каждой карточке. Разбор ориентирован на OpenCart 3.0.x и типичную связку PHP 8.x, PHP-FPM и MySQL или MariaDB. Универсальной цифры нагрузки здесь нет: сначала нужно выяснить, какой ресурс заканчивается первым.
Сначала определите, что именно тормозит
Одинаковое время ответа не означает одинаковую причину. Если медленно открываются все страницы, включая простую информационную, ищите очередь PHP-FPM, базу данных или внешний вызов. Если зависают только категории, подозревайте выборку товаров, фильтр и подсчёт количества результатов. Если медленная только карточка, проверяйте опции, остатки, рекомендации, отзывы и модули на странице.
Не ускоряйте каталог по ощущению. Сначала зафиксируйте, сколько времени уходит на ожидание PHP, выполнение SQL и отдачу изображения.
Под нагрузкой — это не только большой поток посетителей. Даже несколько десятков параллельных запросов могут положить магазин, если каждый из них запускает длинные SQL-запросы, ждёт свободный процесс PHP-FPM или обращается к медленному внешнему сервису.
Как устроен запрос каталога в OpenCart 3.x
Запрос страницы проходит через маршрутизацию OpenCart, контроллер, модели, языковые файлы, шаблон Twig и подключённые модификации. На странице категории обычно нужны сведения о категории, список товаров, цены, изображения, наличие, сортировка и общее количество результатов для пагинации. Карточка товара дополнительно тянет опции, атрибуты, отзывы, связанные товары и данные модулей.
Цена такого подхода — количество работы на один HTTP-запрос. Если один модуль добавил запрос «для каждого товара», а другой проверяет наличие во внешней системе, страница из 20 позиций может вызвать десятки обращений к базе и сети. На тестовом стенде это незаметно, а при параллельных покупателях очередь растёт.
Категория и карточка тормозят по-разному
- Категория. Чаще всего узкие места — фильтрация, сортировка, подсчёт общего количества товаров и дополнительные данные для каждой позиции.
- Карточка товара. Проверьте опции, скидки, акции, отзывы, рекомендации и синхронизацию остатков.
- Поиск. Запрос по названию, модели и описанию может плохо масштабироваться на большом каталоге, особенно если используется поиск по фрагменту строки.
- Главная страница. Проблему часто создают баннеры, блоки популярных товаров, персональные рекомендации и внешние виджеты, а не сам каталог.
Как снять замеры до изменений
Снимите базовую линию на копии магазина или в согласованное окно на продакшене. Зафиксируйте версию OpenCart, PHP, базы данных, размер каталога, число включённых расширений, тип диска и схему веб-сервера. Без этого сравнение «до и после» легко превращается в впечатление.
- Разделите страницы. Измерьте главную, категорию без фильтра, категорию с сортировкой, поиск и карточку товара. Для каждой страницы запишите медианное время и медленный хвост — например, 95-й перцентиль. Среднее значение может скрыть редкие ответы по 10 секунд.
- Посмотрите серверное время. Отдельно нужны время обработки запроса в Nginx или Apache и время ожидания ответа. Если процесс долго ждёт свободный PHP-FPM, увеличение числа SQL-индексов не устранит очередь.
- Включите журнал медленных запросов на короткий срок. Задайте порог, подходящий вашему окружению, и после теста отключите или верните штатную настройку. Ищите повторяющиеся запросы, а не только один самый длинный.
- Сравните число SQL-запросов. Сделайте это на одной категории с включёнными и отключёнными расширениями на копии магазина. Если число запросов растёт вместе с количеством товаров на странице, вероятна проблема N+1.
- Проверьте браузерную загрузку. Вкладка Network покажет, что именно задерживает отображение: HTML, крупные изображения, JavaScript, шрифты или внешний виджет.
Успешный замер — это не одна цифра. У вас должны остаться URL тестовых страниц, параметры запроса, число параллельных клиентов, результаты до изменений и те же данные после них.
Четыре узких места каталога
| Участок | Как проявляется | Что проверить | Цена исправления |
|---|---|---|---|
| PHP-FPM | Растёт время ожидания, процессы заняты, база при этом не загружена полностью | Очередь, число активных и максимальных процессов, время выполнения PHP | Увеличение процессов требует памяти; слишком большое значение усилит конкуренцию за CPU и базу |
| MySQL или MariaDB | Долгие SELECT, высокий I/O, задержки на категориях и поиске | Медленный журнал, план выполнения, сортировки и фильтры | Индексы ускоряют чтение, но замедляют запись и занимают место; неправильный индекс не помогает |
| Расширения и модификации | После установки модуля медленными стали все или отдельные маршруты | Запросы до и после отключения на тестовой копии, цепочку OCMOD и событий | Удаление модуля может нарушить цену, остатки или оформление заказа; нужен резерв и план отката |
| Изображения и внешние сервисы | HTML готов, но страница визуально появляется поздно; отдельные запросы ждут секунды | Размер файлов, формат, CDN, DNS и тайминги внешних запросов | Оптимизация изображений снижает трафик, но не исправляет медленный SQL или очередь PHP |
PHP-FPM: очередь раньше процессора
PHP-FPM обслуживает запросы ограниченным числом рабочих процессов. Если все они заняты, новый посетитель ждёт, даже когда запросы не используют процессор на 100 процентов. Увеличивать пул вслепую опасно: каждый процесс потребляет память, а параллельная нагрузка затем переходит в MySQL.
Сопоставьте число одновременно занятых процессов с памятью сервера и числом соединений к базе. Если после увеличения пула исчезла очередь, но выросли ошибки базы или swap, вы не устранили причину, а перенесли её.
База данных: список товаров обходится дороже, чем кажется
Категория обычно выполняет не один запрос: отдельно выбираются товары, отдельно считается количество результатов, затем загружаются связанные данные. Фильтр по цене, производителю или атрибутам добавляет условия и сортировку. На больших таблицах запрос может начать читать и сортировать гораздо больше строк, чем попадает на одну страницу.
Начните с реального медленного запроса и его плана выполнения. Проверьте, использует ли база индекс для условий соединения, фильтра и сортировки. Не добавляйте индексы на каждое поле: составной индекс должен соответствовать конкретному запросу, а его польза проверяется повторным замером.
Поиск — отдельный риск. Если покупатель ищет по нескольким текстовым полям с частичным совпадением, обычный индекс может использоваться ограниченно. Для большого каталога стоит сравнить встроенный поиск с отдельным поисковым движком, но это уже отдельная архитектура: понадобится синхронизация товаров, обработка ошибок и понятное поведение при недоступности сервиса.
Расширения: главный кандидат на N+1
N+1-запросы1 возникают, когда код сначала получает список из N товаров, а затем выполняет отдельный запрос для каждого. На странице с 20 товарами 21 обращение выглядит терпимо, но при параллельных запросах нагрузка умножается.
Проверьте модули цены, остатков, брендов, отзывов, рекомендаций, доставки и аналитики. Отключайте их по одному на тестовой копии и сравнивайте не только время ответа, но и количество SQL-запросов. Особенно подозрительны модули, которые обращаются к API поставщика или CRM во время открытия страницы.
Изображения: медленный браузер не всегда означает медленный PHP
Если HTML приходит быстро, а пользователь ждёт появления каталога, смотрите на изображения и клиентский JavaScript. Большие оригиналы, неправильные размеры превью, отсутствие современных форматов и блокирующие скрипты увеличивают время загрузки без единого дополнительного запроса к MySQL.
Проверьте фактический размер файла, а не только его размеры в CSS. Генерируйте превью нужной ширины, включите длительное кеширование неизменяемых файлов и не отдавайте карточкам изображения в несколько мегапикселей, если на экране используется вариант шириной 400 пикселей.
Где оптимизация ломается
Кеш страницы скрывает проблему. Для каталога он опасен устаревшими ценами, остатками, персональными скидками и состоянием корзины. Кешировать можно только после того, как определены данные, допустимые к устареванию, и правила сброса кеша после изменения товара.
Индексы не заменяют анализ запроса. Индекс, который не соответствует фильтру и сортировке, может не использоваться. А лишние индексы увеличат размер базы и стоимость операций записи при импорте товаров.
Отключение расширения может испортить бизнес-логику. Модуль, который замедляет категорию, может одновременно рассчитывать обязательную цену или показывать остаток. Проверяйте результат на копии и заранее сохраните список модификаций.
Внешняя интеграция не должна блокировать каталог. Остатки и цены из ERP лучше обновлять фоновым заданием, если бизнес допускает задержку. Если актуальность в реальном времени обязательна, задайте тайм-аут, обработку ошибки и безопасный режим для недоступного сервиса.
Что сделать дальше
Начните с пяти URL: главная, категория, категория с фильтром, поиск и карточка. Снимите для них 50–100 последовательных запросов в одинаковых условиях, отдельно отметьте 95-й перцентиль, число SQL-запросов и очередь PHP-FPM. Затем выберите только один самый дорогой участок и измените его.
После каждого изменения повторяйте тот же сценарий и проверяйте не только каталог, но и корзину, оформление заказа, импорт товаров и обновление остатков. Для продакшена оставьте мониторинг времени ответа, ошибок 5xx, очереди PHP-FPM, соединений базы и медленных запросов. Именно эти показатели покажут, вернулась ли проблема, когда каталог снова вырастет.
1 N+1 — шаблон работы, при котором один запрос получает список объектов, а затем для каждого объекта выполняется отдельный запрос вместо пакетной загрузки данных.
Редактор изображений: Убрать лишнее, заменить фон, добавить объект, расширить кадр — на вашем изображении.
Спросить за 20 ₽Источники: OpenCart Documentation, OpenCart 3.x на GitHub
Частые вопросы
Почему OpenCart медленно открывает только категории?
Стоит ли сразу увеличивать число процессов PHP-FPM?
Как найти расширение, которое замедляет каталог?
Поможет ли Redis или кеширование страниц?
Что делать, если после индексации база стала работать медленнее?
Как понять, что виноваты изображения, а не сервер?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.