Часть 2. Какие бывают проблемы с производительностью универсальных коробочных CMS

Многие интернет-магазины, выбирая между популярными коробочными решениями и самописными, часто выбирают первые за их простоту и обилие готовых модулей, а также поддержку и готовые интеграции со сторонними сервисами.

Эти решения отлично работают на старте, пока магазин маленький.

Тут вам и красивые интерфейсы и удобные настройки, а всякие маркетинговые задачи на базовом уровне решаются установкой дополнения из магазина модулей выбранной CMS.

Однако, у всех этих красивостей и удобств есть цена, которую приходится платить, когда магазин развивается и растёт.

Сейчас я не буду говорить о том, что реализация особых хотелок в коробках это особое развлечение и, как правило, не дешевое.

Я не буду устраивать сравнения в стиле «коробки» vs «самописные решения». Это не имеет смысла в рамках темы поста. Всё потому, что сейчас речь не о сравнении фич и стоимости и сложности реализации особых хотелок – тут у всех своё мнение.

Сейчас я затрону другую тему – производительность коробочных CMS при росте объема данных и посещаемости сайта.

Заранее скажу, что аналогичных проблем можно «добиться» и с самописными решениями, если разработчики на проекте неопытные студенты и/или руководство магазина установило перманентный режим разработки, при котором все задачи постоянно ставятся с крайними сроками «вчера» и «надо было сделать уже давно».

Вернемся к коробкам.

Пока на «коробке» работает сравнительно молодой сайт с небольшим количеством данных и посетителей – владелец магазина только и радуется, какой замечательный у них сайт и как удобно в него добавлять новые функции в пару кликов. Единственное что надо делать – своевременно обновлять коробку до новой версии, чтобы злостные хакеры не сделали с сайтом что-то противоестественное.

Со временем сайт растёт, в него добавляют больше товаров, объем истории заказов растёт, а тут еще маркетинговые потуги наконец-то дали свои плоды и на сайт кроме владельца магазина и контент-менеджера начали заходить толпы потенциальных покупателей.

В итоге сайт начинает тормозить или вовсе сыпать ошибками, потому что задыхается от нагрузки.

Или в какой-то момент на контактный email, на который зарегистрирован хостинг приходит письмо о том, что «сайт отключен за превышение допустимой по договору нагрузки». И бизнес встаёт колом, потому что заказов от клиентов больше не будет, пока сайт не вернётся в рабочее состояние.

Почему это происходит?

Как я уже сказал выше – у удобства и универсальности(а большинство CMS создаются именно как универсальные – чтобы всем угодить) есть цена.

И эта цена в производительности.

Приведу пример, чтобы было понятно как универсальность ухудшает производительность.

Допустим, в базовом виде коробка выполняет простую задачу по выводу списка товаров.

Делает она это путем обращения с запросом к базе данных, в котором она говорит, например, «дай мне все товары, доступные к продаже в такой-то категории».

В интерфейсе конечно же пользователю покажут картинки товаров, цену, количество отзывов, а потом еще отсортируют по популярности(количеству покупок).

Чтобы выполнить такую простую задачу, как показать список товаров, коробочные CMS генерируют огромные запросы, которые за собой тащат еще уйму дополнительных данных из других таблиц БД помимо таблицы с товарами.

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

Чем больше данных, тем больше сил у сервера уйдет на его обработку. Логично, правда?

Вероятно, для целей отдельно взятой страницы отдельно взятого сайта все эти данные не нужны, но действие происходит же на коробочной и универсальной CMS! Значит все «лишние» данные всё равно будут запрашиваться. И плевать CMS хотела, что эти данные не пригодятся – запрос на получение списка товаров вот такой, он универсальный и будет все данные тащить не зависимо от того, нужны они в каждом конкретном случае или нет. Всё потому, что этот запрос будет использоваться еще в тысяче других мест, где эти данные могут пригодится.

Разумеется, некоторые CMS в ряде случаев могут делать упрощенные запросы, но они не отличаются настолько, чтобы ситуация коренным образом исправилась.

А теперь вспомним, что у коробочных CMS полно дополнений на все случаи жизни, которые пишут все кому не лень зарабатывать на продаже этих дополнений.

Предположим, администратор такого сайта поставил дополнение, которое выводит дополнительную информацию в списке товаров: всякие красивые значки у товаров, сообщающие о распродаже или какой-то спец серии товаров или еще черт знает о чем. В общем – очень полезный инструмент, который затребовал SEOшник/Маркетолог/Сын уборщицы (нужное подчеркнуть).

Допустим, данные, которыми надо «обогатить» выдачу товаров лежат в еще каких-то таблицах(например тех, которые создал модуль для своей особой функциональности).

Эти данные надо как-то получать при выводе списка товаров.

Для выполнения своей задачи модуль может «пойти» двумя путями:

  • Первый вариант: изменять запрос на получение списка товаров, которая формирует CMS. Для тех, кто знаком с SQL: добавить JOIN нужной таблиц(ы) к основному запросу. Это наилучший вариант, хотя не каждая CMS это позволяет да и каждый JOIN это тоже усложнение запроса, ведь базе данных придется «зачерпнуть» ещё больше данных для обработки такого запроса. Очевидно, что это увеличивает требования к наличию оперативной памяти на сервере и, как ни крути, увеличивает время, требуемое на выполнение запроса. А ещё в код CMS вернётся больше данных от БД и это тоже память и время обработки.
  • Второй вариант, и он к сожалению часто встречается: делать отдельные запросы на каждый товар, найденный основным запросом. Это вообще катастрофа, потому, что к БД вместо одного запроса, полетит 1+N запросов, где N – количество товаров, найденных основным запросом. Если на странице выводится сотня товаров, то вместо одного запроса будет 101 запрос. Даже, если эти запросы каждый в отдельности не занимает много времени – вместе они задолбают БД такой толпой очень быстро. Плюс, если есть такая толпа запросов, то на стороне CMS есть код, который их порождает и потом использует при построении страницы. А это опять занимает время и память.

Первый вариант лучше второго(за использование второго варианта я бы авторов такого кода ставил в угол на горох!).

В первом случае будет запрос с десятком-другим присоединённых таблиц и на такой запрос уйдёт 400-600мс (кошмар!) Если нужно подключить еще какую-то таблицу, то будет например +50-100мс. Плохо, но не смертельно.

Во втором случае на каждый полусекундный запрос будет создано еще пол сотни запросов, по одному на каждый товар. Все полученные данные надо обработать, так что нас уже волнует не только время самого SQL запроса, но и обработка этого на стороне скриптов. Таким образом 500мс может превратиться в 5 секунд, да еще и будет потребляться значительное количество оперативной памяти. Если на хостинге стоит ограничение по максимальной доступной для одного PHP-процесса памяти и оно весьма низкое, то при большом списке товаров можно получить ошибку о достижении этого лимита.

Хотя как я убедился, если есть запросы в цикле, то есть еще куча проблем в коде, которые «позволяют» достичь любого лимита по доступной памяти, какой бы огромный он не был.

На практике таких модулей может оказаться много и все они усложняют основной запрос или увеличивают количество запросов к базе данных. Где один – там и много! И они все очень нужны, потому что решают какую-то очень важную, например маркетинговую, задачу.

Вывод – поставить плагины/модули/дополнения просто, а какой вред они нанесут производительности сайта – узнаете, когда «нагоните» посетителей на сайт.

Теперь про другую универсальность CMS – стандартные возможности и плагины по-умолчанию.

На примере PrestaShop поговорим про встроенный модуль статистики. Внутренняя/встроенная статистика есть во многих CMS, но как правило она ни кому не нужна: ни маркетологам, ни продажникам.

При том, что модуль встроенной статистики никому не нужен, он продолжает трудиться на благо своей цели: собирает в свою табличку в БД все просмотренные страницы, записывает кто с каких IP заходил, каким браузером и еще много чего.

Мало того, что такой модуль делает выборки из БД, так он еще и записывает туда или, что еще хуже – обновляет имеющиеся записи(эта операция на много затранее просто записи).

Если с модулями, которые просто усложняют выборки из БД всё сравнительно просто, то модули, которые что-то без особой надобности постоянно пишут в БД есть еще одна проблема – данные копятся, места на диске занимается всё больше, а базе данных работать с большими таблицами становится всё сложнее.

Казалось бы за год у вас не сильно увеличилось количество товаров в каталоге, база покупателей не то чтобы сильно большая, а слепок БД занимает уже много гигабайт, а база еле ворочается и жрет порядочно оперативки.

Всё потому, что есть лишние фичи в «коробке», которые надо было выключить с самого начала.

К счастью, вооружившись знаниями про такие «паразитные модули», вы можете пойти их и повыключать. Данные, которые они накопили легко найти через тот же phpMyAdmin, отсортировав таблицы в БД по количеству записей, которые в них хранятся. Наверняка таблица с логами/ pageviews’ами или guests будет лидировать по кол-ву записей и объему данных с большим отрывом от остальных, более полезных таблиц.

Только пожалуйста, если вы соберетесь чистить такие таблицы или выключать модули – сделайте сначала полный бекап всего сайта и лучше, если эксперименты вы не будете ставить на продакшене 🙂 (а если по какой-то причине на продакшене, то хоть не днём, когда покупатели пытаются совершить обряд конверсии).

Подведём итог.

Самые большие проблемы универсальности коробочных CMS, которые приводят к проблемам производительности – это:

  • сложные запросы для основных операций, которые начинают тормозить при большом количестве данных(например, отрисовка блока корзины, у интернет-магазина в котором миллионы товаров в сотнях тысяч имеющихся в БД корзин).
  • «легко доступные» модули/дополнения, которые ухудшают производительность, усложняя изначальные запросы к БД или порождают сотни других, а потом тратят время и оперативную память на обработку полученных данных
  • лишние стандартные фичи, которые не несут никакой пользы, но они продолжают работать.

Теперь немного расчётов, чтобы понять какой вред производительности может нанести какая-то маленькая фича.

Возьмем что-то из реального опыта и того, что хоть как-то подходит под один из трех пунктов, описанных выше.

Думаю, что это будет генерация пунктов меню в навигации.

Допустим, на сайте есть блок с со статическими информационными страницами(О нас, Про доставку, Про оплату, Как к нам доехать).

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

Делаем запрос к БД: «покажи мне все ссылки на статичные страницы, которые включены и отсортируй по приоритету».

Просто? Да.

Но мы говорим про универсальную CMS!

Поэтому вместо такого простого запроса, мы получим подключим еще несколько таблиц и усложним запрос:

  • Добавим многоязыковость
  • добавим добавим проверку на право просмотра каждой страницы этим посетителем, этой группой посетителей
  • Если CMS поддерживает многосайтовость, то надо еще проверить к каком сайту принадлежит эта страница
  • проверка на фазу луны? (это конечно шутка, но в каждой CMS могут найтись еще куча всяких проверок).
  • К запросу можно добавить получение картинки-обложки к странице, если такая есть, даже если она нигде выводиться не будет.

Отлично! Вместо одного простого запроса у нас запрос с половиной десятка присоединённых таблиц, много условий в разделе WHERE, а в шаблоне мы тоже проверок напихаем.

Вишенкой на торте будет, когда мы увидим шаблон для вывода этого меню. там для каждого пункта меню будет вставлена функция на получение ссылки на статью по её ID(ведь это нельзя за раз вытащить из запроса – у нас универсальная CMS, а ссылки могут формироваться особым образом, в том числе через плагины).

Поэтому на каждую ссылку в меню у нас будет еще по одному запросу к бд.

Пунктов меню всего пять, но вместе с сложными и многочисленными SQL запросами, а также «отрисовкой» html-кода из шаблона этого блока мы уже потратили 30-100мс.

На каждый просмотр страницы сайта.

При этом эти пункты меню меняются в лучшем случае раз в год, а силы на отрисовку меню CMS-ка тратит при каждом открытии страницы сайта.

Страшно?

Если нет, то давайте посчитаем сколько действительно тратится времени впустую.

Допустим, у нас не один такой бестолковый блок, а скажем пять. Каждый в среднем по 50-100 мс тратит при отрисовке страницы сайта(я специально сгущаю краски, показывая такие числа, но суть от этого не меняется). 5*100ms = 500ms. 0.5 секунды!

Казалось бы терпимо, но за это время можно было всю страницу отрисовать, а у нас только менюшки сгенерились.

Теперь представим реальную ситуацию на сервере, во время наплыва посетителей.

Допустим маркетинг приносит свои плоды, утренняя рассылка оказалось крайне успешной и на сайте у вас(по данным realtime отчета Google Analytics) одновременно 500 человек.

0.5 секунд пустой траты времени происходят у каждого из 500 человек при открытии каждой страницы. 500 * 0.5 = 250 секунд потрачено на генерацию менюшек, которые меняются раз в год. 4 минуты уходит на то, чтобы отрисовывать менюшки каждому из 500 человек.

Если эти 500 человек разом ломанулись на ваш сайт, как только пришла рассылка, то либо они очень долго будут ждать пока загрузится сайт(дождутся не все), либо увидят ошибку, потому что сервер захлебнется от такой нагрузки.

Вот теперь надеюсь, что страшно. 🙂

Цифры все взяты «с потолка»(но коррелируют реальными из опыта) и в каждом случае на каждом сайте и на каждой CMS всё будет по-разному.

То, что я хотел всем этим текстом донести, так это весьма очевидная вещь – там где есть универсальность, там будут различные накладные расходы реализацию этой универсальности.

По другому просто не бывает.

Давайте очень кратко о том, что делать, если надо искать причины тормозов сайта.

  1. Во многих CMS есть решим профилирования/отладки при котором можно заставить её выводить сколько времени ушло и сколько памяти потребовалось на выполнение кода какого-то модуля, а также какие SQL-запросы выполнялись и как долго. Иногда есть информация о повторных SQL запросах, что помогает искать запросы в цикле. Включите этот режим для запросов с определенного IP адреса и/или для посетителей с определенной cookie(если вам понадобилось это делать на боевом сайте). Изучайте и ищите откуда берется нагрузка.
  2. Пройдитесь по списку модулей, активированных в вашей CMS. Наверняка найдется то, что можно отключить. Обязательно замеряйте как изменения настроек скажутся на производительности сайта.
  3. Зайдите в phpMyAdmin и посмотрите какие таблицы очень сильно «распухли» от данных. Возможно там найдется что-то, что не несёт никакой пользы. Подумайте как сократить этот объем.
  4. Подключите внешний сервис, который поможет диагностировать причины тормозов и найти пути оптимизации. Примеры таких сервисов: New Relic, OpBeat
  5. Дождитесь моих следующих статей, где я расскажу о процессе оптимизации в том, числе с помощью упомянутых выше сервисов, а также с написанием своих скриптов, которые понадобились в процессе отладки.

Продолжение следует…

Подпишитесь на новые статьи

Подпишитесь, чтобы получать новые статьи в числе первых!

Я не шлю спам. Отписаться можете в любое время! Powered by ConvertKit

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *