Ускорение загрузки нагруженного сайта на Битриксе или зачем программисту знать как работает mySQL

Сегодняшняя статья – об ускорении работы нагруженного интернет-магазина.

Этот интернет-магазин – mybox.ru. Внутри – 1C-Битрикс.

Оглавление статьи:

  1. Кто пациент? Что умеет сайт mybox.ru?
  2. Скорость работы после старта
  3. Оптимизация скорости работы сайта, снижение нагрузки на сервер
  4. Ошибки программистов
  5. Неоптимальная работа стандартных компонентов и страниц Битрикса
  6. Результаты оптимизации
  7. Выводы

Сначала несколько слов о Битриксе

Есть два лагеря, между которыми – пламя войны.

Один – студии, работающие на Битриксе. Их директора продают Битрикс всем, потому что это мейнстрим, потому что рейтинги, потому что “Битрикс позволяет рубить бабло”. Программисты внутри таких компаний иногда пищат тоненькими голосами, но их никто не слышит. Даже их директора.

Второй – люди снаружи “мира Битрикса”. Они пишут на “правильных платформах”. Они боятся, не знают и не любят битриксоидов. Они оставляют у нас на сайте ядовитые комментарии. Они испускают вопли ненависти. Их никто не слышит. Даже их клиенты. Их клиенты хотят перейти на Битрикс. Потому что мейнстрим, потому что оттуда нет воплей ненависти. Потому что там как-то все работает. Потому что найти профессиональную и организованную команду разработчиков под Битрикс проще чем под “правильную платформу”.

Лишь малая доля веб-разработчиков думает не штампами, а головой. Например – мы.

Профессионалы понимают, что тиражируемый софт и промышленная разработка это не то же самое что “команда одного проекта”. Мы знаем для чего подходит Битрикс и почему он такой.

При этом мы не идеализируем платформу. Мы знаем о стольких странностях Битрикса, что людям “снаружи” и не снилось.

В каждом крупном проекте – Евраз, MYBOX, Альфа, многочисленные “Уберы маркетплейсов” – мы думаем “черт возьми, причем тут Битрикс!? Тут же все нетиповое!”. Каждый раз мы начинаем проект и осознанно делаем его на Битриксе.

Я понимаю что люди по ту стороны пламени войны сейчас скажут “мыши плакали, кололись, но продолжали жрать кактус”. Но – это неправда.

В команде ИНТЕРВОЛГИ есть такие люди как Алексей Шкарупа и Сергей Горелов, руководители двух продакшн-отделов. Первый пришел “укушенный питоном”, второй первые пару лет “пописывал на Zend’e”. Я сам так вообще на голом php с 2005 по 2009 столько странного написал, что самому удивительно.

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

В статье — реальный кейс ускорения работы сайта на Битрикс, объемом в 3000+ часов и пример оптимизации его работы.

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

Кто пациент? Что умеет сайт mybox.ru?

Mybox.ru – сайт, highload проект на Битрикс, где можно заказать суши и другие блюда азиатской кухни в 100+ городах.

Сайт показывает доступность блюд в 250+ точках выдачи. Статусы заказов обновляются через веб-сервисы 2 раза в минуту. Обновление наличия товаров и меню идет почти непрерывно. На сайте делается ежедневно более 1000 заказов, в месяц – несколько десятков тысяч.

Ну хорошо, а нагрузка?

Утром (когда заказов почти нет) в минуту бывает около 35 хитов. Мало.

Днем в 14-00 по Москве (восточные регионы начинают активно работать) в минуту сайт обрабатывает 220 запросов. Уже не так мало.

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

Вечером в праздник (например 8 марта) обычный показатель перекрывается в 2-2.5 раза.

Итого до 1300 хитов в минуту. Это 22 хита в секунду.

Нагрузка эта – от покупателей, без признаков хакерских DDOS-атак, неумелого парсинга, буйных ботов Яндекса и всего такого.

Со временем сайт стал работать заметно медленнее. Особенно на некоторых страницах. В данных мусора нет, посещаемость здоровая, конверсия – обзавидуешься.

Ясно, что причины “тормозов” надо искать в коде и в обработке данных.

Начнем с истории.

Скорость работы после старта

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

Что такое норма?

Лирическое отступление: в частных беседах сотрудники 1С-Битрикс говорят что сайт должен “без кеша” отдавать любую страницу за 0.1-0.5 секунды и это они считают нормой.

При этом сайт Битрикса контентные страницы отдает за 0.2-0.3 с, а страницу отфильтрованного списка партнеров — сначала за 0.8 с, потом за 0.25 с.

Штатная “мерилка” Битрикса (серверное + в браузере) помечает любое время свыше 1 с – “не быстро”.

Разумно считать около 0.5 секунды  серверного времени – допустимой нормой.

Иногда прямо в договоре на запуск сайта мы записываем требования к скорости работы и “на выходе” проводим нагрузочное тестирование: Яндекс.Танк + Munin, Nagios + штатные инструменты Битрикса.

На момент старта сайт был “бодрячком”: среднее время генерации страниц составляло около 0.3 секунды.

...и через год расширения сети и развития сайта

За год посещаемость, число точек и городов выросли процентов на 30.

База данных имеет стандартную для Битрикса структуру – инфоблоки (товары, SKU, маркетинговый контент), немного хайлоадблоков.
Данных немало: например, в инфоблоке SKU (“наличие по точкам”) хранится более 400 тысяч записей.

Кроме того, сайт хранит более 6 миллионов записей – позиций ранее сделанных заказов. Каждый раз когда сайт обращается к истории заказов, выполняет операции с корзинами – этот объем записей затрагивается.

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

За год на сайт было добавлено много новых функций. Вот неполный список:

  • Добавлена паназиатская кухня с конструктором заказов, что сделало весь код работы с корзиной “сильно нестандартным”

  • Создана адаптивная версия сайта . На мобильных доступна вся функциональность.

  • Сделали “гео-доставку”. Это значит, что в зависимости от местоположения покупателя сайт показывает ему точный состав доступных к заказу блюд и условия доставки. Помимо координат клиента сайт учитывает нюансы работы сети MYBOX во всех городах (цена доставки, время, наличие блюд в ближайшей точке).

  • Разработаны и запущены 2 мобильных приложения (приложения берут данные из того же сайта и добавляют ему нагрузки; мы разработали стыковочное API для мобильных приложение, это API реализует логику работы с товарами, заказами и доставкой)

  • Реализован и запущен интранет-портал обучения и проверки уровня сотрудников (в платформе есть модуль Обучение, неплохо покрывающий задачи HR-департамента, мы использовали тот же сайт).

Выросла и нагрузка. Сайт перестал быть “бодрячком”. Перед нами встал вопрос - как увеличить скорость загрузки сайта.


По самым продающим и самым нагруженным страницам время за год сильно выросло (на графике показано серверное время).

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

Оптимизация скорости работы сайта, снижение нагрузки на сервер

Расскажем что мы нашли, как исправили и к чему привела оптимизация скорости работы сайта.

Внимание:

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

Симптомы

Хозяйке на заметку:
Даже если вы не специалист в веб-разработке, для определения медленно работающих частей сайта прекрасно подходит штатный монитор производительности Битрикса и его инструменты “Страницы”, “Хиты”,  “SQL-запросы”. Запустите мониторинг на 20-60 минут и посмотрите статистику.
Все как на ладони.

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

Вы спросите: как так вышло? Ответ прост:

  • непрерывный рост базы и замедление запросов;

  • постоянное изменение логики и “наслаивание кода”;

  • весь год мы работали над проектом в таком темпе, что не проводили регулярное нагрузочное тестирование, перестали применять модульное и интеграционное виды тестирования.
    Мы оставили только “смок-тесты” и мониторинг работоспособности основного бизнес-процесса через Selenium.
    Скорость разработки была на первом месте, и накопился технический долг.
    Это плохо, но это неизбежное следствие режима “давай-давай”.

Важно время от времени наводить порядок.

Еще вопрос: как при таких показателях вообще удавалось работать?

Нужно похвалить кеширование Битрикса: большинство медленных запросов работали редко.

Парадокс, но жалоб от пользователей до определенного момента было крайне мало. Только тогда, когда долгий запрос оказался на пути конверсии при тестировании геозависимости доставки, вопрос стал ребром: надо оптимизировать.

А может сервер посильней?

Очевидная мысль — для ускорение работы сайта мало ресурсов, давайте добавим.

Но мы решили этого не делать до наведения порядка в коде. Добавлять мощности — значит консервировать проблему. Да и Антон Гойхман, ИТ-директор сети, совершенно разумно отказался “тушить пожар бензином”.

Сайт работает на обычном виртуальном сервере на SSD.

Сервер соответствует требованиям Битрикса, поэтому начинать надо не с него.

Напоминание:

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

Мы нашли и устранили кучу мест “потери скорости”. Первая группа проблем называется

Ошибки программистов

Логически верные, но неоптимально написанные вызовы API Битрикса

API Битрикса – инструмент. Инструмент непростой. С его помощью очень легко написать такой код, который будет логически верен, но крайне непроизводителен. И требуется квалификация “сильно выше средней”  чтобы это найти и исправить быстро и изящно.

Пример: Многие сущности на сайте связаны между собой и с внешней системой учета по XML_ID.
В коде огромное число getlist-запросов с отбором XML_ID. При этом иногда программисты не указывали фильтрацию по iblock_id.

Логически это допустимо: если XML_ID уникален, то проблем нет.

Но в Битриксе для поиска по iblock_id, XML_ID есть составной индекс. А при запросе без указания инфоблока он не используется.

В огромном учебном курсе Битрикса “разработчик на bitrix framework” этот момент четко не описан.

Эта не бросающаяся в глаза особенности стиля кодирования здорово тормозила многие запросы.

Нашли, исправили.

Update: После нашего уточняющего запроса руководитель отдела документирования Роберт Басыров скорректировал текст документации Битрикс по этому поводу: http://dev.1c-bitrix.ru/api_help/iblock/classes/ciblockelement/getlist.php

SQL в цикле вместо более сложной, но быстрой методики получения данных

Пример: Больное место — result_modifier компонента catalog.section.

На сайте есть особый вид товаров — наборы в виде “коробочек паназии”. У них специфический жизненный цикл от синхронизации с учетной системой до ценообразования, доступности по городам, расчету геоданных.

В коде страницы каталога каждой коробки-вока в коде узнавали ее состав с помощью метода getBoxProductItems (в самом методе еще 2 раза вызывается Getlist по компонентам коробочек).
Затем опять делали запрос для получения значения свойства. Все это для того чтобы узнать с чем коробка.

Работает, но неоптимально. Код избыточен.

Что сделали:

Добавили интересующее нас свойство в параметр DISPLAY_PROPERTIES, собрали ID-записей и разом получили необходимые нам значения.

После:

Результат: Ускорение этого логического блока в сотни раз.

Запросы с пустой фильтрацией

Фильтры при отборах иногда формируются по сложной логике “в пару экранов кода”.

Завершается все это getlist’ом.

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

Совет: при больших (или даже при любых) объемах данных проверяйте, не пустой ли у вас фильтр.

Повторный запрос тех же данных

В проект вложено примерно 3400 часов работы, участвовало много людей, и порой следующий программист писал в result_modifier примерно то же, что уже делалось в компоненте, с чуть измененной под новую логику структурой данных.

В идеальном мире каждый раз проводится code review, а по пятницам еще и комплексное нагрузочное тестирование. В реальном мире всегда есть срочные задачи, добавление новых фич.

Результат – накопилось вот такое: если на странице выводилось 50 товаров, то для каждого товара выполнялся запрос, определяющий текущий тип цены, одинаковый у всех.

Отсутствие нужных индексов на нагружающие запросы select

Пример: при работе с товарами наиболее часто производятся выборки по городу.

Город в нашей структуре данных — свойство инфоблока товарных предложений. Получается “запрос через 2 ступени”: товар-предложение-город. Запросы сложные, данных много, все тормозит.

Для ускорения сайта на Битрикс, сконвертировали в Инфоблоки 2.0 и добавили индекс на поле “город”.

Еще пример: довольно много данных хранится в highload-блоках, например расширенные профили покупателей — сотни тысяч записей, по которым часто ведется поиск и отбор.

Проанализировав запросы, мы добавили индексы почти на все highload-блоки.

Следующая группа проблем интереснее.

Напоминание:

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

Неоптимальная работа стандартных компонентов и страниц Битрикса

Метод  CIBlockPriceTools::GetOffersArray используемый в компоненте Catalog.Section

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

Делает он это довольно странным образом.

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

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

Работа со свойствами элементов инфоблока в компонентах

Стандартные компоненты каталога делают “лишние” для нас операции при извлечении свойств товаров.

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

Пример: Список полей которые мы получаем вместе с элементами

Стандартные компоненты получают свойства отдельно, с помощью метода CIBlockElement::GetPropertyValuesArray.

Чем больше свойств нам надо получить, тем медленнее метод работает.

В каталоге нам требуется всего 12 свойств, для интернет-магазина это не много.

На скриншоте профилировщика XHprof видно, что вызовы методов получения свойств (внешние) занимают очень много времени.

Следите за руками.

В методе GetPropertyValuesArray мы (в смысле код Битрикса) получаем значения всех активных свойств, причем двумя отдельными запросами: сначала список существующих свойств,

затем сами значения для выбранных элементов каталога

Далее если свойство имеет тип свойства “привязка” и свойство указано в параметре PROPERTY_CODE идет получение привязанного элемента, причем запросом в цикле (метод GetDisplayValue).

Время затраченное на исполнение функции GetDisplayValue

Можно порекомендовать разработчикам сайтов на Битриксе, если данных много и этот метод тупит, как у нас, не передавать в параметр PROPERTY_CODE компонента это свойство.

Тогда этот связанный элемент не будет извлекаться из базы без толку.

Напоминание:

Тем, кто “поделится” статьей в социальных сетях, мы пришлем инструкцию по оптимизации SQL-запросов Битрикса.

Удивительная страница sale_buyers.php

Эта страница доступна в админке вашего Битрикса по ссылке /bitrix/admin/sale_buyers.php?lang=ru. Она показывает покупателей и много данных по каждому: сумма заказов, число, время последнего логина.

На малых данных все отлично, а на больших — нет.

Она генерирует такой вот запрос:

Любители пописать SQL-запросы спросят — почему бы сумму и число заказов не получить одним подзапросом, а не двумя одинаковыми? Причина известна: так работает автоматический построитель запросов на основе настраиваемого визуального представления.

И черт с этим, проблема не в двух запросах вместо одного.

Проблема в том, что на нашей боевой базе этот запрос выполняется 20+ минут, неторопливо пережевывая данные и используя все больше ресурсов сервера.

Пришлось сообщить в техподдержку и временно заблокировать эту страницу.

Update : Трудами сотрудников компании Битрикс Дениса Шаромова и Николая Рыжонина пример 20+ - минутного запроса был стремительно исследован, в продукт внесены изменения, которые придут с обновлениями. Ждем.

Результаты оптимизации

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

Сравните результаты “до и после”. Среднее время нагружающих запросов:

Даже самые тяжелые страницы теперь отдаются менее чем за 0.4 секунды (напомню, при 10+ хитах в секунду).

Сайт снова стал бодрячком.


Осталось еще одно “место потери времени” – проблемы клиентская оптимизация, работы в браузере.

Ей мы займемся в ближайшее время.

На диаграмме внизу зеленое — серверное время, синее – время на клиенте, в браузере.

Напоминание:

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

Выводы

Универсальность Битрикса, гибкость инфоблоков и буйство фантазии заказчика позволяет многим веб-разработчикам забыть, как платформа устроена внутри. Забыть про уровень протокола, сессий, индексов, таймаутов.

А забывать не надо.

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

И если в простых случаях можно программировать “спинным мозгом”, то нагруженный проект не позволит этого.

Затраты времени на рефакторинг составили около 60 часов.

Мы написали эту довольно откровенную статью и убедили руководство заказчика в необходимости публикации, потому что знаем — проблемы организации и качества есть во всех проектах.

Важно как компания работает над ними.
Я "поделился" статьей, прошу прислать файл