Задать вопрос

Как ускорить Opencart при большом кол-ве товаров?

Имеется интернет-магазин канцелярии, 17500 позиций номенклатуры. Магазин очень долго работает. Все тормозит.
Улучшение сервера результата не дало.
Отключение подсчета кол-ва товаров, добавление индексов - не принесло ощутимой пользы.
Все страницы очень долго грузятся.
Тормозит все MySQL, поскольку в Opencart очень большие запросы, с множествой вложений и ORDER BY.
Сейчас база MyIsam, где-то читал, что на InnoDB не советуют переводить OpenCart.
В последнее время очень часто стали приходить отчеты по Яндекс.Метрике, что сайт недоступен.
Кеширование Memcache включено - не помогает.
Как можно его оптимизировать?
Или на какой другой движок можно перейти? чтобы быстро и с минимальными затратами.
Часто выполняемый медленный запрос MySQL
Count: 46  Time=6.91s (317s)  Lock=0.05s (2s)  Rows=1.0 (46), 
  SELECT DISTINCT *, pd.name AS name, p.image, m.name AS manufacturer, 
  (SELECT price 
      FROM oc_product_discount pd2 
      WHERE pd2.product_id = p.product_id AND pd2.customer_group_id = 'S' 
            AND pd2.quantity = 'S' AND ((pd2.date_start = 'S' OR pd2.date_start < 'S') 
            AND (pd2.date_end = 'S' OR pd2.date_end > 'S')) 
       ORDER BY pd2.priority ASC, pd2.price ASC LIMIT N) AS discount, 
    (SELECT price 
       FROM oc_product_special ps 
       WHERE ps.product_id = p.product_id AND ps.customer_group_id = 'S' 
                  AND ((ps.date_start = 'S' OR ps.date_start < 'S') AND (ps.date_end = 'S' OR ps.date_end > 'S')) 
        ORDER BY ps.priority ASC, ps.price ASC LIMIT N) AS special, 
     (SELECT points 
        FROM oc_product_reward pr 
        WHERE pr.product_id = p.product_id AND customer_group_id = 'S') AS reward, 
     (SELECT ss.name 
         FROM oc_stock_status ss 
         WHERE ss.stock_status_id = p.stock_status_id AND ss.language_id = 'S') AS stock_status, 
      (SELECT wcd.unit 
          FROM oc_weight_class_description wcd 
          WHERE p.weight_class_id = wcd.weight_class_id AND wcd.language_id = 'S') AS weight_class, 
       (SELECT lcd.unit 
           FROM oc_length_class_description lcd 
           WHERE p.length_class_id = lcd.length_class_id AND lcd.language_id = 'S') AS length_class, 
        (SELECT AVG(rating) AS total 
            FROM oc_review r1 
            WHERE r1.product_id = p.product_id AND r1.status = 'S' 
            GROUP BY r1.product_id) AS rating, 
         (SELECT COUNT(*) AS total 
             FROM oc_review r2 
             WHERE r2.product_id = p.product_id AND r2.status = 'S' 
             GROUP BY r2.product_id) AS reviews, 
        p.sort_order FROM oc_product p 
    LEFT JOIN oc_product_description pd ON (p.product_id = pd.product_id) 
    LEFT JOIN oc_product_to_store p2s ON (p.product_id = p2s.product_id) 
    LEFT JOIN oc_manufacturer m ON (p.manufacturer_id = m.manufacturer_id) 
    WHERE p.product_id = 'S' AND p.status = 'S' AND p.date_available <= 'S' AND p2s.store_id = 'S'
  • Вопрос задан
  • 14807 просмотров
Подписаться 6 Оценить Комментировать
Помогут разобраться в теме Все курсы
  • Нетология
    1C-программист: расширенный курс
    18 месяцев
    Далее
  • Яндекс Практикум
    Python-разработчик
    10 месяцев
    Далее
  • Академия Эдюсон
    Python-разработчик
    9 месяцев
    Далее
Пригласить эксперта
Ответы на вопрос 8
@vodnicear
Лучший вариант самописный движок.
Ответ написан
Комментировать
zoonman
@zoonman
⋆⋆⋆⋆⋆
17500 - это ничтожно малое количество записей.
У Опенкарта основная проблема - говнокод на уровне обращений к базе данных. Много запросов в цикле и т.д.
Решается обычно установкой MySQL на нормальную машину с хорошим объемом памяти и SSD, миграцией на InnoDB, анализом запросов и построением необходимых индексов. Кроме индексов необходимо отследить размеры буферов и т.д. В вашем случае объем памяти должен быть в районе от 8 GB RAM. Из решений рекомендую использовать не MySQL, а MariaDB или PerconaDB. Не используйте виртуальный (shared) хостинг.
Еще поищите, есть хорошая утилита mysqltuner, она подсказывает оптимальные характеристики для СУБД.
Включите лог медленных запросов и запросов неиспользующих индексы.
Проанализируйте сетевой стек вашего соединения с MySQL, работа через сокет значительно ускоряет работу приложения. Некоторые горе-мастера выставляют адрес домена, который система переодически ресолвит и это увеличивает издержки на время соединения.
При высокой посещаемости и некритичности актуальности данных используется подход с переписыванием слоя абстракции БД опенкарта с применением кеширования результатов в Memcached. Актуально для высокопосещаемых ресурсов с разного рода предзаказами или проверкой остатков непосредственно при добавлении заказа.
Ответ написан
@egormmm
Борітеся — поборете!
Если тормозит Mysql - то нужно включить лог медленных запросов, найти где они создаются в движке и оптимизировать их.
Раздел "оптимизация" в руководстве по Mysql также сможет помочь.
Если все индексы правильно расставлены, то скорее всего в запросах используется функция, которая не позволяет Mysql кешировать запрос. Этим грешат разные модули, например Mega filter.

P.S. Надеюсь, что кеш запросов Mysql включен.
Ответ написан
thewind
@thewind
php программист, front / backend developer
Тормозит все MySQL, поскольку в Opencart очень большие запросы, с множествой вложений и ORDER BY.

Почему бы не сесть и планомерно не исправить все нужные запросы? Разделяя их на несколько, оптимизируя и прочее-прочее.
Ответ написан
Комментировать
@amfetamine
исправьте некоторые медленные запросы к БД, повесьте кэш где нужно, еще можете на vds перепрыгнуть или может быть будет достаточно просто хорошего хостинга. был тут недавно один сайт, пока на vds не перекинули его, летать не начал, тоже было достаточное количество товаров и описания к ним
Ответ написан
@devopencart2
Программист, пишу сайты и модули на Opencart
как правило, отключение подсчета товаров не влияет на скорость (хоть об этом все и пишут, я не исключение))

но есть ещё множество манипуляций, которые могут помочь ускорить сайт.

Можете попробовать реализовать несколько или все пункты из этого мануала:
https://in-it24.com/uskorenie-magazina-na-opencart/
Ответ написан
Комментировать
@fbi_agent26
memcached для опенкарта это вещь бесполезная, я включал, скорость загрузки пхп не то чтобы существенно снизилась, она выросла, с 0.17 сек в среднем , до 0.35 сек в среднем, я 10 раз замерял каждый вариант и высчитывал среднее значение.
Индексы нужно не везде делать, а только там где они нужны, у вас при открытии товара идет запрос по айди в несколько таблиц, вот в самые жирные и делайте.
========== если opencart медленно грузится и товаров меньше 100 000 ================
1 - виртуальный хостинг имеет общий сервер баз данных, на который выделяется одно ядро сервера, потому ускорить ее вы не сможете оптимизациями, ниже статистика моей бд.
ø за час: 860 877
ø за минуту: 14 347
ø в секунду: 239
Хостинг позволяет ставить ее на виртуальный аккаунт, но если я туда ставлю ее, то все мелкие сайты начинают лагать, нагрузку на бд никто там не высчитывает.
Кто хочет оптимизировать бд то идите сразу на сервер, или купите модуль кеширования, кеш запросы идут в обход бд, из оперативной памяти, и сразу идет готовый результат, есть такие модули.
================Лишние модули===============
Есть у меня модуль lighting , он показывает запросы, и как то время выполнения запросов к бд выросло с 0.05 секунд до 0.10-0.15, пошел искать, нашел что seo модуль, который я использовал для генерации мета тегов, занимает большую часть этого времени, выключил естественно, если вы не используете модули а они есть - их надо выключать.
===============================================================
Товаров у меня 500 000, время загрузки страницы 0.2 когда нет особого трафика и боты не нападают.
С модулем кеширования ( не буду называть какой , их много , выбирайте любой ) , скорость генерации страницы 0.01, при этом сайт у меня не интернет магазин, он постоянно обновляется ботами на node.js , который и создают нагрузку на бд, у вас скорость будет выше, если все правильно сделать.
Ответ написан
Комментировать
kyrchenkov
@kyrchenkov
UXE | uxengineer.ru
У меня была схожая проблема на OpenCart 3 (MySQL 5.6, PHP 7.1): ~30 000 товаров, медленные страницы, тяжёлые запросы, периодические падения по метрике.
Скриншот консоли Chrome DevTools: TTFB в PWA. Ответ из кэша Service Worker.


698c5c246e2eb276778744.png

Цели были несколько другие, но то что касается "ускорения", вот что сделал, в общих чертах:

Железо

Обязательно NVMe, остальное по потребности, в моем случае - 6 CPU, 12 ГБ RAM

Перевод с MyISAM на InnoDB - можно и нужно

Перевёл все таблицы:
ALTER TABLE oc_product ENGINE=InnoDB
ALTER TABLE oc_product_description ENGINE=InnoDB

Почему это важно:
InnoDB поддерживает блокировку строк, а не целых таблиц
Работает innodb_buffer_pool - кэширует данные в RAM
Лучше масштабируется под нагрузку

Также, обязательно, периодически оптимизировать все таблицы

Настройки в my.cnf:
[mysqld]
# Основной кэш InnoDB должен быть ~70-80% от RAM.
innodb_buffer_pool_size=8G
# Разделяет буферный пул на независимые экземпляры (= CPU) - снижает конкуренцию потоков.
innodb_buffer_pool_instances=6
# Баланс скорости и безопасности: лог пишется в файл каждую секунду, не после каждой транзакции.
innodb_flush_log_at_trx_commit=2
# Прямая запись на диск, минуя кэш ОС - повышает предсказуемость производительности.
innodb_flush_method=O_DIRECT
# Ускоряет доступ к часто используемым данным через хеш-индексы (авто).
innodb_adaptive_hash_index=ON
# Размер файла журнала транзакций. Больше = меньше записи на диск, лучше производительность.
innodb_log_file_size=2G
# Буфер лога транзакций в памяти. Помогает сгладить пиковые нагрузки.
innodb_log_buffer_size=256M
# При старте MySQL загружает в пул ранее сохранённый кэш - быстрый выход на пиковую скорость.
innodb_buffer_pool_load_at_startup=ON
# При остановке MySQL сохраняет "горячий" кэш - ускоряет восстановление после перезапуска.
innodb_buffer_pool_dump_at_shutdown=ON
# Контролирует скорость фоновой записи данных.
innodb_io_capacity=2000
# Увеличивает параллелизм чтения.
innodb_read_io_threads=8
# Увеличивает параллелизм записи.
innodb_write_io_threads=8
# Время ожидания освобождения блокировки перед ошибкой.
innodb_lock_wait_timeout=120
# Максимальный процент "грязных" страниц в буфере.
innodb_max_dirty_pages_pct=70
# Автоматически регулирует скорость записи изменённых страниц - стабилизирует нагрузку.
innodb_adaptive_flushing=ON
# Задержка перед вытеснением "старых" блоков из кэша - защищает от случайного сканирования.
innodb_old_blocks_time=1000
# Отключает сбор статистики при запросах к INFORMATION_SCHEMA - ускоряет метазапросы.
innodb_stats_on_metadata=OFF
# Уменьшает блокировки, подходит для репликации и высокой параллельности.
transaction-isolation=READ-COMMITTED

# Query Cache устарел: при записи блокируется полностью - убивает производительность.
# Обязательно отключить при нагрузке.
query_cache_type=0
query_cache_size=0

# Временные таблицы в памяти - ускоряют сложные JOIN и ORDER BY.
tmp_table_size=512M
max_heap_table_size=512M
# Директория для временных файлов. /tmp - обычно в RAM (если смонтирован как tmpfs).
tmpdir=/tmp


Обязательно настроить веб-сервер и раскомпилировать релевантные модули

Вот пример для Apache 2.4:
httpd.conf

MPM Worker:
StartServers 6            # начальное число серверных процессов
MinSpareThreads 50        # минимальное число свободных потоков
MaxSpareThreads 100       # максимальное число свободных потоков
ThreadsPerChild 25        # потоков на процесс - параллельная обработка
MaxRequestWorkers 300     # общее число одновременных подключений
ServerLimit 12            # макс. число процессов (ограничивает масштабирование)
MaxRequestsPerChild 0     # не перезапускать процессы (риск утечки памяти, но выше скорость)

# Прямая передача файлов ядром ОС - минуя буферизацию, ускоряет отдачу статики
EnableSendfile on

# Время ожидания повторного запроса по соединению - меньше = быстрее освобождение ресурсов
KeepAliveTimeout 1

# Отключение .htaccess - критично для скорости: Apache не проверяет каждый каталог
<Directory "/var/www">
    AllowOverride None    # отключает чтение .htaccess
    Require all granted
</Directory>

# Минимизация записи логов - снижает I/O нагрузку на диск
LogLevel warn           # пишутся только ошибки и предупреждения
CustomLog "logs/access_log" combined  # лог только запросов, без debug

# Модуль сжатия Gzip - уменьшает размер HTML/CSS/JS
LoadModule deflate_module modules/mod_deflate.so

# Модуль сжатия Brotli - лучше Gzip, меньший размер (современные браузеры)
LoadModule brotli_module modules/mod_brotli.so

# Кэширование через заголовки Expires - снижает количество запросов от браузера
LoadModule expires_module modules/mod_expires.so

# Управление HTTP-заголовками - важно для кэширования, CORS, безопасности
LoadModule headers_module modules/mod_headers.so


Корректно настроить глобальный пул PHP-FPM в php-fpm.d/*.conf (тут тоже согласуем с железом)

Вот пример под Apache и PHP 7.1:
# Максимальное число ожидающих соединений к сокету. При высокой нагрузке - увеличивать.
# Значение 4096 предотвращает потерю запросов при всплесках трафика.
listen.backlog = 4096
# Стратегия управления процессами: dynamic - гибко подстраивается под нагрузку.
pm = dynamic
# Максимальное число дочерних процессов PHP. Ограничивает общую нагрузку на CPU и RAM.
# 120 - подходит для сервера с 8–16G RAM (при ~100–150M на процесс).
pm.max_children = 120
# Количество процессов при старте PHP-FPM. Ускоряет обработку первых запросов.
pm.start_servers = 20
# Минимум свободных процессов. Поддерживает "резерв" для быстрого отклика.
pm.min_spare_servers = 10
# Максимум свободных процессов. Превышение - лишние процессы завершаются.
pm.max_spare_servers = 30
# После 500 запросов процесс перезапускается - предотвращает утечки памяти.
pm.max_requests = 500
# Время простоя процесса перед завершением (актуально для ondemand/dynamic).
pm.process_idle_timeout = 60s;
# Лимит открытых файлов на процесс. Если высокая нагрузка и множество соединений.
rlimit_files = 50000
# Разрешить создание core-дампов при аварии - полезно для отладки.
rlimit_core = 'unlimited'
# Убить скрипт, если он выполняется дольше 20 секунд - предотвращает зависания.
request_terminate_timeout = 20s;
# Разрешает обработку только .php файлов - защита от выполнения сторонних скриптов.
security.limit_extensions = .php


OPcache - включить и настроить

Без него каждый запрос перекомпилирует PHP - это убивает CPU

Настройки в php.ini (и тут тоже нужно согласовать с железом):
; Включает кэширование скомпилированного байт-кода PHP. Без этого - нет ускорения.
opcache.enable=1
; Выделяет 2 ГБ RAM для хранения скомпилированных скриптов. 
opcache.memory_consumption=2048
; Максимальное число файлов, которые можно закэшировать. 
opcache.max_accelerated_files=100000
; Частота проверки изменений в файлах (в секундах). 0 - отключает автоматическую проверку.
opcache.revalidate_freq=0
; Полностью отключает проверку временных меток файлов - максимальная скорость.
; Кэш обновляется только вручную (перезагрузка FPM или opcache_reset()).
opcache.validate_timestamps=0
; Использует huge pages (2M вместо 4K) - снижает нагрузку на TLB, ускоряет доступ к памяти.
; Требует настройки ОС: echo 'vm.nr_hugepages = 1280' >> /etc/sysctl.conf
opcache.huge_code_pages=1
После этого PHP существенно снижает нагрузку на CPU

Кэширование HTML на уровне приложения

Memcached не поможет - он кэширует данные, а не готовые страницы

Реализовал кэширование целых HTML-страниц до запуска контроллеров. Решение работает через APCu, кэширует ответы с учётом URL, сессии и устройства

Ключевые особенности:
Кэшируются страницы категорий, товаров, корзины
Умная инвалидация при изменении данных
Сжатие GZIP на этапе кэширования - снижает нагрузку на CPU при отдаче

+ Brotli оставляем только для статики (JS, CSS, JSON) - там выигрыш в сжатии оправдан

Прогрев кэша - через cron + curl

Даже с кэшем первые посетители после простоя будут попадать на "холодные" страницы

Самый простой и достаточно эффективный способ "жарить" кроном (у меня их получилось пять, разные группы страниц, разные тайминги - зависит от вариативности поведения пользователей):
*/15 * * * * /home/scripts/preload_pages.sh

Вот пример скрипта:
#!/bin/bash
PAGES_FILE="./pages_to_warm.txt"

if [ ! -f "$PAGES_FILE" ] || [ ! -s "$PAGES_FILE" ]; then
  echo "❌ Файл $PAGES_FILE не найден или пуст"
  exit 1
fi

readarray -t PAGES < "$PAGES_FILE"

echo " Прогрев десктоп..."
for PAGE in "${PAGES[@]}"; do
  curl -fs -o /dev/null "$PAGE" > /dev/null 2>&1 && sleep 0.5
done

echo " Прогрев мобильных..."
for PAGE in "${PAGES[@]}"; do
  curl -fs -o /dev/null \
    -A "Mozilla/5.0 (iPhone; ... Safari/604.1" \
    "$PAGE" > /dev/null 2>&1 && sleep 0.5
done

echo "✅ Прогрев завершён"
Теперь "горячие" страницы всегда в кэше. Я запускал с двумя User-Agent, смартфон и десктоп (нужно если кэши разные)

На клиенте

Чтобы сделать переходы ещё быстрее:
Добавил Service Worker - кэширует HTML на устройстве. Я у себя разделил на PWA и браузер (смартфоны и ПК) чтобы можно было управлять кэшем более адресно.
Внедрил предиктивную навигацию: анализируем, какие переходы чаще всего делаются - и предзагружаем эти страницы
Часто посещаемые страницы уже закэшированы на устройстве пользователя, до того как он их посетил, что дает очень хорошую скорость особенно в веб-приложении.
Но про это ещё дольше расписывать и возможно для вас это будет избыточным.

Если будет интересно, здесь описал все что делал: https://uxengineer.ru/pe-uxe-sre
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы