Разработка интернет-магазина 13000+ товаров на MODX Revolution. Часть 1

в 10:36, , рубрики: modx, modx revolution, интернет-магазин, метки: , ,

Я уже писал про свой компонент shopModx. И хотя мало кто его оценил, так как многие ждут именно готовых решений с одной большой кнопкой «Установить и работать», тем не менее этот компонент разрабатывается с учетом тех минусов, которые есть в MODX, и в которые часто упираются MODX-разработчики, и с учетом тех плюсов, которые в MODX есть, но о которых разработчики не знают, или просто не используют.

Так же хочу сказать, что этот модуль не просто так разрабатывается. Он разрабатывается под два не маленьких магазина (для начала), и на выходе получится обкатанная платформа для реализации крупных интернет-магазинов.

Сегодня я хотел бы начать цикл статей о разработке крупных интернет-магазинов на MODX Revolution, с рассказами о том, с какими сложностями приходится сталкиваться, и какие варианты решения этих проблем используются. А так же о том, что для решения таких проблем shopModx уже будет нести на борту, и какие приемы позволят получить 100% контроль над разработкой своего уникального магазина, не влезая в код shopModx.

Итак, немного о магазине, над которым ведется работа: это интернет-магазин межкомнатных и входных дверей, а так же фурнитуры. Вчера импортнул базу. Получилось 13000+ документов, 43000+ ТВшек и почти 13000 записей в modx_shopmodx_products.

Сразу скажу, что я рассчитываю получать код страницы даже не из кеша и с поиском по параметрам менее чем за 1 секунду, а средняя загрузка не должна превышать 0.3-0.4 сек.

Итак, коротко о первых проблемах и их решениях.

Проблема 1. Большой файл кеша и много используемой памяти

Для начала входные данные для чистой Ревы. Специально скачал чистую 2.3.0 и посмотрел использование памяти. Код воткнул в плагин на событие OnWebPageComplite — это самая крайняя точка выполнения MODX-а уже после exit(), сохранения кеша документа и т.п. Первый заход (удалил вручную все файлы кеша):

Memory: 13.5409 Mb
TotalTime: 0.1880 s

Далее:

Memory: 10.1396 Mb
TotalTime: 0.0640 s

Кстати, на всякий случай код плагина: gist.github.com/Fi1osof/5062419
Можно доработать с проверкой прав доступов и всегда видеть актуальную нагрузку на сервер.

В общем, проверяем результаты на магазине (кстати, сразу хочу уточнить, что документ не пустой, а имеет 8 связанных TV-параметров, один из которых — картинка с кастомным медиамсурсом). Первый заход

Memory: 24.1438 Mb
TotalTime: 0.4360 s

Далее:

Memory: 18.4103 Mb
TotalTime: 0.0960 s

То есть имеем прирост к используемой памяти почти 10 метров сразу. Это потому что у нас кешируется вся карта УРЛ-ов контекста, а у нас там 13000+ документов. Кеш-файл контекста — почти 2 метра.

Очевидное решение — надо сокращать кеш-файл контекста. Я уже писал
подробно про тонкие моменты в кешировании MODX-а и про свой патч cacheOptimizer. Ставим его и отключаем кеширование карты ресурсов для контекста web. Новые результаты:

Memory: 16.1369 Mb
TotalTime: 0.2640 s
Memory: 10.4021 Mb
TotalTime: 0.0720 s

То есть в штатном режиме у нас потребляет почти столько же памяти, как и на голой системе.

Проблема 2. Page not found (404)

Эта проблема вытекает прямиком из предыдущего решения :-) Так как мы отрубили кеширование карты УРЛ-ов, то теперь MODX не сможет по УРЛ-у «понимать», к какой странице у нас происходит обращение при использовании ЧПУ. Сразу уточню, что если у вас не будет использоваться ЧПУ, то это не должно быть для вас проблемой (хотя кто сегодня не использует ЧПУ?), или если у вас не большой магазин (до 1000 товаров), то можно и не отрубать карту страниц, лишний мегабайт оперативки — не проблема.

Итак, для решения этой проблемы я решил использовать собственный роутер. Я просто написал новый класс, расширяющий modRequest, и немного подправил пару методов. Логика следующая: при обращении к странице, MODX пытается найти id ресурса по запрошенному УРЛ-у в кеше. (УРЛ уже очищенный, то есть без всяких параметров и т.п.). Если находит, то возвращает ID и далее все происходит в штатном режиме. Если нет, то пытается найти документ в БД по uri. Находит — записывает id в кеш и далее возвращает id. Если нет, то стандартная процедура OnPageNotFound (так что еще можно заюзать свой плагин для модификации поиска).

Этот дополнительный класс будет поставляться вместе с shopModx, и если кому-то понадобится (если будет большой магазин), то просто включаете его в настройках (ключ modRequest.class).

Так же есть вариант сразу все страницы загонять в кеш, допустим, при обновлении кеша (юзать плагин на событие OnSiteRefresh).

Проблема 3. Много файлов кеша

Представляю, как многие читали предыдущее решение, и думали «ну что за дебил!» :-)

Да, плодить сотни тысяч кеш-файлов — это полный маразм. Но здесь ключевое слово — файлов. Да, именно их состояние (файлы) нам и не дают покоя. Потому в данном случае просто используем другой кеш-провайдер, а не файловый. Я решил использовать memcached, так как с ним уже хоть как-то сталкивался, и устанавливал на сервере, а вы можете использовать и другой, какой пожелаете. В стандартную сборку Рево так же поставляются memcache и APC.

Свой выбор в пользу кеш-механизма на оперативке я аргументировал тем, что упрощается сброс кеша. Попробуйте с жесткого диска удалить 1 000 000 файлов. Это будет происходить оооочень долго. В случае же с memcached сброс кеша делается просто и быстро.

$modx->cacheManager->getCacheProvider()->flush();

Еще один огромный плюс memcached — хранить можно любые типы данных, включая объекты. Исключение только — ресурсы (к примеру соединение с базой данных) и объекты, среди свойств которых есть ресурсы. Такие объекты должны создаваться с методами __sleep() и __wakeup(), чтобы перед сохранением они удаляли все свойства-ресурсы, а при восстановлении из кеша могли эти свойства вновь создать.

Итак, смотри результаты. Первый заход

Memory: 15.0709 Mb
TotalTime: 0.1040 s

Далее

Memory: 10.403 Mb
TotalTime: 0.0640 s

По-моему очень хорошо для некешированного контекста на 13000+документов.

Проблема 4. Массовое обновление документов при изменении системных настроек

Не буду объяснять зачем, но понадобилось мне изменить суффикс контейнеров. Поменял, и Ajax-ответа так и не дождался… Полез смотреть процессор /system/settings/updatefromgrid. Есть в нем такой метод checkForRefreshURIs(). В общем, если изменялись «friendly_urls», «use_alias_path» или «container_suffix», то он сигнализирует, что надо обновить УРЛы. Все правильно. Но проблема в том, что он пытается обновить все документы без разбору, даже не контейнеры. К тому же еще и условие сортировки по menuindex зачем-то добавляет (хотя нас порядок вложенности интересует, а не индекс меню).
В общем этот процесс делал сервер плакать. Добавил условие isfolder=1, и тогда за 6 секунд обновил все контейнеры. Больше менять суффиксы не буду :-)

Резюме

На практике мы получили полную обработку документа на сайте с 13000+ документами (в двух таблицах) и 43000+ TV-шек, менее чем за 0.3 секунды при обновленном кеше. Из кеша — менее чем за 0.1 сек.
Условно можно считать, что на этом этапе разница между большим и маленьким сайтом заканчивается, так как дальнейшие тормоза возможны только на уровне рендеринга страницы, а это уже зависит от того, как мы шаблоны напишем и т.п.
Этот момент я распишу в следующей статье (скорее всего завтра). Но сразу скажу, что я буду делать на Smarty, так как ИМХО делать все это на чистых чанках и сниппетах — куча проблем.

И напоследок результаты локального теста 100 клиентов по 1000 запросов каждый: gist.github.com/Fi1osof/462e1af10ab7b95311df
Time per request: 44.224 [ms] (mean, across all concurrent requests)

P.S. Пакет на modx.com: modx.com/extras/package/shopmodx
Проект на ГитХаб: github.com/Fi1osof/shopModx
Залил последнюю версию с request-классом.

P.P.S. Указывать настройки memcached-провайдера лучше прямо в config.core.php (просто поверьте на слово).

$config_options = array (
  'cache_handler' => 'cache.xPDOMemCached',
  'cache_prefix' => 'shopmodx_', // Надо указывать разный префикс для разных сайтов на одном memcached-сервере
);

Переменная $config_options там уже есть.

Автор: Fi1osof

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js