Внезапно, оказалось, что в магазине, в котором предполагалось 20 000+ товаров, на самом деле 150 000+ товаров… Ну чтож, давно хотел сделать магазин покрупнее :-) Очень краткая предыстория: перенос существующего интернет-магазина с самописного движка на MODX Revolution. Проблема №1: документы создаю через процессор resource/create, а обновляю через resource/update. Сама проблема заключается в малой скорости выполнения (из-за большого кеша карты ресурсов). Здесь помогает cacheOptimizer. Устанавливаем его и отключаем полностью кеширование карты кесурсов. Производительность на процессорах сразу поднимается в 3+ раза. Вторая проблема тут же: в источнике очень много документов с повторяющимися названиями, а задача стоит такая, что ЧПУ-алиасы надо сгенерировать. И вот здесь почти каждую секунду цикл выполнения процессоров обрывается из-за дубляжа алиасов. Пошел на небольшую хитрость — создал расширяющий resource/create, resource/update процессор, и перегрузил проверку алиаса. Если если дубликат алиаса, я добавляю к алиасу случайное число, и вызываю родительскую функцию. Там опять выполняется проверка, и если дубляжа нет — едем дальше, а если есть, то тогда уже окончательно останавливаем выполнение ошибкой. Вот код такого update-процессора. Проблема №2: лимит на выполнение скриптов 30 секунд. Я веду работу на modxcloud.com, а там лимит на выполнение скриптов — 30 секунд. За это время MODX успевает прогрузить порядка 300-400 документов. А надо прогрузить более 150 000 документов (скрипт выполняю через Console). Так вот, здесь я использую маленькую хитрость: дело в том, что за 30 секунд — это nginx отбивает, но php-то живет своей жизнью. Так что я ставлю лимит записей гораздо больше (к примеру 5000), а в скрипте пишу это: Теперь даже если nginx отбивает через 30 секунд, сам php-скрипт продолжает выполняться. Каковы лимиты самого php я не знаю (на некоторых хостингах часто обрывают скрипты не только по времени, но и по «очкам нагрузки»), но во всяком случае 5000 записей за раз он прогружает. А выполняется скрипт или нет, я смотрю по остаткам не отработанных записей через phpMyAdmin.
Конечно, особого выигрыша во времени в таком случае нет, но зато пока импортируются 5000 документов, можно чем-то параллельно позаниматься. Проблема №3: большой кеш контекста. При частичном отключении карты ресурсов (фишка Ревы 2.2.7) кеш-файл весит почти 3,5 метра, а при заходе на сайт потребляется от 47Mb и больше.
А если включить полное кеширование карты ресурсов, то объем кеш-файла почти 15 метров (и кеш создается секунд 8), а памяти во фронте кушается… А хз сколько кушается. На modxcloud.com все разваливается при заходе во фронт (просто белый экран без всяких сообщений об ошибках, как я ни старался их включить), а в админке дерево ресурсов вообще ответа от сервера нет.
В общем, карту ресурсов отключаем полностью. Проблема №4: долгие запросы к БД.
У нас получается 150 000+ в таблице документов, 150 000+ в таблице товаров (потом еще будет наверняка не одна сотня тысяч записей в таблице TV-параметров). Так вот, индексы конечно настроены, и при простых джоинах запросы выполняются довольно быстро. Но как только начинается поиск по условиям, вот тут запросы начинают выполняться и пол-секунды, и больше. При чем одно дело, когда мы получаем какое-то ограниченное количество записей (эти запросы выполняются довольно быстро, так как СУБД получает запрашиваемое кол-во записей, и как только лимит получен, выборка прекращается). Но вот когда выполняется подсчет всех результатов, попадающих под условия запроса (а это нам надо знать для постраничности), вот тогда СУБД-шке надо подсчитать вообще все строки, то есть сделать полную выборку из указанных таблиц. Вот тогда время выполнения запроса запросто выходит за 1 секунду. И вот даже при использовании list-процессоров из shopModx выборка 10-ти документов (с подсчетом общего числа найденных записей) занимает 2 и более секунды. Решение довольно не сложно в данном случае. Мы просто пишем общий запрос и делаем выборку всех записей из связанных таблиц без учета поиска. У нас получается одна такая большая таблица (content и т.п. нам не нужны, так как поиск мы по ним не делаем (если не делаем), нам эта таблица нужна только для поиска ID-шников тех документов, которые удовлетворяют условиям поиска, а по этим id-шникам мы уже получим конечные результаты из основных таблиц, так как чисто по id-шникам поиск выполняется достаточно быстро). А вот list-процессор из shopModx-а пришлось серьезно оттюнинговать.
Во-первых, основным объектом для запросов установил объект этой кеш-таблицы.
Во-вторых, заменил стандарный modx−>getCount()навоттакуюхитруюконструкцию(особеннообратитевниманиенаthis->modx->getValue()): Работает она гораздо быстрее стандартного modx−>getCount(),таккактотвыполняетподсчетуникальныхid−шников,анепростоcount(∗).Воттактамзапросстроится:Разницавскоростиподсчетавнесколькораз.UPD:вдальнейшемпришлосьвсе−такизапроспеределатьнаcount(distinctmodelid),таккаквкеш−таблицеPKбылcategoryid,modelid,productid,ипривыборкеуникальныхзаписейименнодляmodelid,простогоподсчетаобщегочисластрокбылонедостаточно.Вкеш−таблицеудалилотдельныеиндексыдляdeleted,hidemenu,publishedисоздалединыйиндексдляэтихтрехколонок.Понаблюдениямприпоискепооднойизэтихтрехколоноксотдельнымииндексамиразницывскоростинезамечено,авоткактолькохотябыподвумколонкамищешь,сразускоростьпадаетвразавдва.Витоге,послевсехэтихманипуляций,ядобилсяполноговыполненияпроцессоранавыборку10−тидокументовсполнымподсчетомобщегоколичестваит.п.менеечемза0.09−0.2сек.Итоговыйпроцессорпослетюнингасталвыглядетьтак:gist.github.com/Fi1osof/d5a4dd427ee31b568e01/365a4ee20661b87c43b369e31683f9cad1c40635Новсежеэтотрезультатнеоченьустраивал,таккакконечнаястраницарендерилась0,5−0,7секунд,аэтовсе−такидолго.ВитогеярешилперевестиMODXнакеш−провайдерAPC,арезультатывыборкиизбазыданныхкешировать,чтобынедергатькаждыйразбазуданных,темболее,чтоосновнуюнагрузкусоздавалименноподсчетобщегочислазаписей,удовлетворяющихусловиямпоиска,такчтохорошегорезультатадостаточнобылопростокешироватьэтотрезультат,аконечнуювыборкусовсемисортировкамиит.п.можнобылобыужеинекешировать,таккакэтотольконесколькозаписей.Сформированиемключапроблемневозникло.НаобщееколичествозаписейвлияеттолькоусловиеWHERE.Sortит.п.вообщенеучитываются.Витогеянаписалвоттак:ТоестьяберуусловиеWHEREпрямизсамогообъектазапроса(итакимобразомизбавляюсьотнеобходимостивклиниватьсявовсеточкиформированияусловий),иформируюкеш−ключ.Адалеееслирезультатестьвкеше,товозвращаюего,аеслинет,тополучаюрезультатотбазыданныхисохраняюеговкеш.Стакоймаленькойхитростьювремянавыполнениепроцессорасократилосьдо0,02−0,05сек,тоестьсразувнесколькораз,арендерстраницысократилсядо0,3—0,5сек(этокешируемаястраницаснекешируемымпостраничнымвыводомкаталогапо32товаранастраницу).Ужебольшепохоженаправду.Измененныйпроцессор:gist.github.com/Fi1osof/d5a4dd427ee31b568e01/53da648ce470ea05db2f3bdffe0795b2e6a9bec0Ноярешилнаэтомнеостанавливаться,аещевключитькешированиеужеконечногомассиваданных.Витоге,переопределилосновнуюфункциюprocess:Вэтойфункциияспециальнонесталсразувозвращатьконечныйрезультат,апрогоняючерезthis->outputArray(), так как в той функции вызывается getPage. А если я этого не буду делать, то постраничность у нас просто пропадет (вывод [[+page.nav]]).
Измененный процессор: gist.github.com/Fi1osof/d5a4dd427ee31b568e01/a0e4a39ddf9e90117204cfad591aed6ad7e98df7 И вот здесь еще один сюрприз меня ждал — getPage. Оказывается, он ппц какой прожорливый. Сравним скорость выполнения процессора без getPage — 0,036 сек., и с getPage — 0,15-0,17 сек. Сразу скорость выполнения падает почти в 5 раз. А ведь это основной процессор на выборку данных каталога. А сравним рендер страницы. Без getPage (страница из кеша, но процессор вызывается) — 0.1152-0.2 сек. А с getPage? 0.28-0.36 сек. В итоге один getPage выжирает в 2-3 раза больше, чем требуется всей странице! Ппц… Короче, getPage — следующий на замену. Надо будет тоже переписать. P.S. Ссылка на магазин будет после того, как я закончу хотя бы основную часть. Продолжение следует… P.S.S. если кому понадобится, ревизия изменения процессора: gist.github.com/Fi1osof/d5a4dd427ee31b568e01/revisions