Привет! Я Иван, Битрикс-разработчик e-commerce агентства KISLOROD.
Разработчики часто встречаются с непредвиденными сложностями, причем иногда крайне непросто найти причину. На одном из проектов мы столкнулись с серьезной проблемой, которую не смогли сразу решить даже при помощи техподдержки «Битрикса».
Однако после долгих поисков мы нашли неожиданное и быстрое решение, которым хотим поделиться с сообществом, возможно, это сэкономит кому-то время и средства.
Рассказываем о том, откуда взялась проблема при масштабировании каталога, почему не получилось решить ее сразу, какие были варианты и какое более простое решение в итоге оказалось рабочим.
Предыстория
Мы поддерживаем и развиваем крупный интернет-магазин лабораторного оборудования, приборов и расходных материалов для медицины и биотехнологий. В каталоге было несколько десятков тысяч товаров. Поэтому, чтобы клиентам магазина было проще делать заказы, необходимо было его доработать.
Для начала мы провели рефакторинг кода в каталоге, запустив комплексные компоненты. После этого появилась возможность добавить в каталог нормальный фильтр.
Поскольку в каталоге появился фильтр, заказчик решил сделать сайт более удобным для пользователя. На сайте много товаров, они находятся в разных разделах, при этом в каждом собственный набор уникальных свойств, а у каждой группы товаров также могут быть свои особенности. Поэтому клиент начал оформлять свойства товаров, чтобы выводить их в фильтре и сократить путь пользователя.
Проблема
Контент-менеджер со стороны клиента столкнулся с проблемой — при заведении новых свойств товаров, часть товаров становилась неактивной, а админка сайта зависала.
«При работе с фильтром, при заведении новых свойств после нажатия на кнопку «Применить» система зависала дольше, чем если проводить переиндексацию. При этом, если заводим 1 свойство, оно весит меньше чем 2 или 3 свойства. И совсем умирает, если новых свойств 5 и больше.
Сегодня сначала заводили максимум по 3 свойства, но каждое последующее сохранение тормозило сайт все больше и больше, пока на 4 раз все не зависло до ошибки 504».
После обсуждения с клиентом было решено в корне пересмотреть процесс работы фильтров и поиска. Текущая схема была неудобна редакторам сайта и значительно тормозила работу: все параметры фильтра грузились в карточку каждого раздела и товара.
Но по факту часть свойств просто по логике не могла относиться сразу ко всем типам товаров. Кроме того, это порождало неразбериху — редакторам приходилось искать каждое свойство товара через поиск по странице Ctrl+F, либо выводить нужное каждый раз в таблицу списка товаров раздела.
Уточнили у клиента, сколько всего свойств ожидается в каталоге — выяснилось, что около 1000 ± 300.
Начали искать источник проблем и выяснили следующее.
Особенности работы базы данных (БД)
БД реагировала на заведение/удаление новых свойств, после превышения определенного порога свойств крайне странно и нелогично.
После того как общее количество свойств перевалило за 300 единиц, в «Битриксе» начались проблемы.
при добавлении 2 свойств БД увеличивалась на 10 Гб;
при удалении 3 свойств уменьшалась всего на 10 Мб.
Что, очевидно, совершенно несоразмерно одной записи.
До начала работы решили удалить ненужные свойства с помощью специального скрипта. По итогам удаления таблица уменьшилась до 16.23 ГБ. — предполагался больший результат.
Также предполагалось значительное снижение нагрузки от уменьшения количества свойств. Мы планировали сотни свойств со значениями «Да/Нет» превратить в пару свойств с типом «Справочник» и с сотнями значений.
Мы не ожидали решения проблемы с тотальным переходом от свойств с типом «Список» к свойствам с типом «Справочник». Справочник хоть и меньше нагружает БД, но это такое же свойство, и много их быть не должно.
Но при удалении 120 свойств размер таблицы в БД сократился с ~20 Гб до ~16 Гб, что все равно очень много.
Продолжили удалять часть новых свойств и искать ошибки, БД уменьшилась до 12.02 гб. Когда свойств осталось 4, то размер таблицы уменьшился до 320.98 МБ. Дальше стали тестировать новый подход: заводить новое свойство и смотреть, как это повлияет на размер БД.
Заново завели свойства, которые перешли в «Справочник», но размер БД все равно менялся на 5 Мб.
При заведении 2 новых диапазонов БД изменилась до 20 и 49 Мб.
При добавлении всего двух характеристик в список «Тип загрузки» БД выросла на 110 Мб.
Некоторые свойства были важны и поэтому мы стали их добавлять поштучно через код.
Определение источника проблем
До выдвижения гипотез об источниках проблемы, мы обратились в техподдержку «Битрикс». Описали проблематику и получили следующий ответ.
ТП указывала на возможные проблемы с фасетным индексом и ограничения сервера на выполнение скрипта, также рекомендовала разделить основной каталог на несколько частей, где будут сгруппированы товары только с общими для них свойствам.
Предполагалось, что так уменьшится общее количество свойств для каждого инфоблока, снизится нагрузка на сайт и проблема решится.
Решили протестировать работу каталога, а также создание свойств без фасетного индекса, а если это сработает, то создавать свойства, предварительно отключив фасетный индекс, и после пересоздавать его заново. А для ускорения обработки данных попробовать увеличить ресурсы сервера и таймауты.
Еще одна гипотеза — разбить каталог на несколько инфоблоков, предварительно оценив, возможно ли группировать товары по свойствам в разных инфоблоках, чтобы в каждом из них уменьшилось количество свойств. А некоторые свойства, которые не участвуют в фильтрации вынести в отдельные инфоблоки или хайлоад блоки и доработать их вывод в каталоге.
Дополнительно решили проанализировать инфоблоки каталога на наличие ненужных и устаревших свойств и соответственно их удалить.
Параллельно менеджер клиента продолжал заводить по 1–2 свойства в фильтр вручную. Однако довольно быстро мы снова столкнулись с той же проблемой: при сохранении 3 свойств админка «Битрикса» зависла, и сервер выдал 504 ошибку. Так стало понятно, что увеличение мощностей сервера вдвое не помогло.
Свойства мог вносить программист через специальный скрипт, но это отнимало очень много времени и ресурсов — 6–10 минут на каждое свойство. При этом клиентом планировалось заводить еще несколько сотен свойств.
Стало понятно, что нужно искать радикальное решение проблемы, поскольку ручное добавление через скрипт могло обернуться непредсказуемыми проблемами.
Снова пообщались с клиентом, решили сгруппировать свойства по разделам каталога, чтобы не грузить весь сайт. А параллельно стали искать глобальное решение проблемы и снова обратились в ТП «Битрикса».
Промежуточные итоги
Гипотеза с отключением фасетного индекса не дала значимого прироста в обработке свойств. Перенастройка конфигурации сервера после апгрейда мощности также не дала существенных результатов — админка просто стала работать несколько быстрее.
При этом таблица, где хранятся свойства, разрослась до 12 Гб и увеличивалась при добавлении каждого нового значения. Кроме того, при добавлении одного свойства вся таблица заново проходила индексацию, в чем и была особенность InnoDB, и получалось, что увеличить скорость не было возможности.
Итого на тот момент проблема выглядела следующим образом:
в ИБ каталога было более 400 свойств;
добавление нового свойства занимало большой промежуток времени, который выходил за таймауты, а пользователь получал ошибку сервера;
из-за этого были проблемы с перестроением индексов, что приводило к некорректному определению активности товаров.
Поиск решения проблемы дал такие результаты:
Тест каталога без фасетных индексов, а также анализ работы индексов не выдал прироста производительности — получили результаты в рамках погрешности.
Повышение ресурсов в целом свой эффект дало: ускорились выборки данных, но на изменения структуры каталога повлияло не сильно, размер текущей таблицы превышал в несколько раз размеры доступной памяти, перестройка индекса шла также медленно.
Добавление одного свойства существенно влияло на размер итоговой таблицы, при этом зависимость была не линейна, перестраивались индексы, а также влияли особенности работы InnoDB.
При этом вынос свойств в отдельные сущности не дал бы большого результата без группировки и уменьшения количества свойств.
Были проверены различные варианты изменения, также исходя из ответов ТП, выделили 3 основных возможных решения:
Сохранение текущего решения и уменьшение количества необходимых атрибутов до 500, стабилизация работы каталога настройками сервера.
Разделение ИБ каталога на несколько инфоблоков, исходя из группировки атрибутов (до 300 атрибутов, а в идеале до 150), доработка разделов сайта и внешних обменов с учетом работы с несколькими ИБ.
Полный рефакторинг каталога, уход от ИБ, переход на работу напрямую с данными, что потянет за собой реализацию каталога, фильтров, поиска, заказов и т. д.
Пришли к выводу, что остается либо делить все свойства на разные инфоблоки, либо коренным образом менять логику работу со свойствами и их размещением в отдельных сущностях.
При этом вариант с разделением каталога на несколько инфоблоков имел определенные минусы:
Большая часть компонентов «Битрикс» из коробки не могут работать с несколькими ИБ.
То же касается кастомных разработок, которых на сайте было очень много.
Была проблема с выводом аксессуаров, так как они будут находиться в других ИБ.
Кроме того:
нельзя одновременно подключать несколько каталогов к компонентам;
разделение по верхним уровням результата выигрыша не даст;
на более низком уровне большие проблемы будет создавать общий каталог, а из-за перелинковки товаров в каталогах (аксессуары и т. п.), также будет много проблем.
Решение от ТП «Битрикс»
Возникла проблема с множественностью свойств — клиенту оно было необходимо для товаров. Но множество справочников приводило к сильному усложнению запросов к БД, поэтому много создавать их было нельзя, желательно было вынести туда только свойства с большим количеством значений.
Мы пытались выяснить причины странного поведения БД и снова обратились в ТП «Битрикса».
Ответы техподдержки:
Техподдержка «Битрикс», куда мы обращались, предлагала нам сменить на проекте базу данных — перейти с InnoDB на MyISAM, но в ней отсутствует некоторое количество важных особенностей:
нет поддержки транзакций и внешних ключей;
нет самовосстановления по журналу при сбоях;
нельзя блокировать регионы, которые меньше чем целые таблицы;
отсутствуют средства резервного копирования и ряд других недостатков.
И в целом это более устаревший метод для работы с MySQL.
Переход на тип MyISAM мы посчитали опасным и неправильным путем. Данный тип БД уже давно не используется на крупных проектах, в целом устарел и не имеет в себе современных технологий хранения/выборки/изменения данных.
Основные проблемы:
Отсутствие транзакции, в результате чего блокировка идет на уровне таблиц, то есть будут проблемы, блокировки в моменты обмена с «1С» и подобных операциях.
Также в случае сбоя, восстановление будет занимать больше времени и будет большая вероятность потери данных.
По размеру БД, скорее всего, был бы выигрыш, но не настолько большой, чтобы покрыть вышеперечисленные недостатки и решить проблемы со свойствами.
ТП «Битрикса» отписалась, что проблема в БД.
Обобщая ответы ТП «Битрикса», информацию, полученную при тестировании различных вариантов, а также особенности работы различных типов баз данных, получили следующие результаты.
Тип базы MyISAM имеет множество недостатков, и работа на крупном проекте в продуктивной среде чревата серьезными проблемами.
При конвертации базы данных в MyISAM таблица сжалась до 300 мб, но на сайте перестала работать часть функционала.
Обратная конвертация вернула таблицу к прежним размерам.
Сделали вывод, что для работы с большим количеством свойств товара в фильтрах и карточке товара необходимо изменять архитектуру хранения свойств для товаров на проекте.
И никакие изменения и настройки при текущей архитектуре не дадут приемлемого результата, ввиду этого было бы логично проанализировать различные решения для хранения большого количества свойств и выбрать оптимальное для данного проекта, определить перечень компонентов, которые придется дорабатывать, обобщить это всё в ТЗ.
Разработка новой архитектуры
Поскольку предыдущие гипотезы не сработали, а остальные решения имели довольно существенные минусы и ограничения, то решили разрабатывать новую архитектуру хранения данных.
Для работы со свойствами планировали разработать кастомный интерфейс в административном разделе сайта, благодаря чему заказчик смог бы завести не менее 20 000 свойств товаров.
Для этого предполагали сделать следующее:
Создать iblock с описанием типов товаров, где раздел — это название типа, элемент — это свойство, которое можно привязать к нескольким типам.
Каждому товару присвоить свой тип или несколько разных.
Для ввода значений свойств создать hlblock (или для каждого раздела на сайте создать собственный), в котором вертикально будут храниться значения свойств элементов его типа.
В админке у товара добавить новую вкладку, куда будут заноситься данные для того типа или типов, которые указаны. При изменении типа у товара необходимо удалять старые значения, чтобы не загромождать БД мусором (сделать анализатор мусора, чтобы он работал по ночам).
У каждого свойства указывать параметры: «Использовать для поиска», «Использовать для сравнения», «Использовать для фильтрации», единица измерения, редактируемый список разделов, в которых данные свойство необходимо раскрывать по умолчанию.
Использовать символьный код, который будет применяться, как код свойства. Тип свойства, в зависимости от которого будут подбираться нужные данные и нужного формата. По необходимости можно было бы добавлять другие параметры, которые будут заполняться по аналогии со свойствами инфоблока.
Для собственной сортировки свойств в каждом разделе создать hlblock или поле раздела, где будет храниться обозначение раздела каталога и перечисление свойств со значением сортировки в сериализованном виде.
При редактировании раздела, в новой вкладке будут выводиться все свойства из инфоблока и те, что собраны у типов, выставленных у элементов данного раздела.
Все эти свойства будут показываться в столбик по текущему сохраненному значению сортировки, а рядом поле для указания значения сортировки.
Для хранения свойств использовать таблицу:
ID, UF_ITEM, UF_PARAM, UF_VALUE, UF_VALUE_INT, UF_TYPE (текст, да/нет, ползунок, указывается в элементе типа).
Определили порядок внедрения решения:
Разработать архитектуру для заполнения значений параметров у товаров: инфоблок с типами товаров и их свойствами.
Создать таблицы для хранения данных.
Модифицировать стандартный компонент фильтра, чтобы он мог работать как со стандартными свойствами, так и с кастомными, при этом чтобы работал весь его стандартный функционал.
Выполнить миграцию значений списка свойств в новую систему хранения.
После миграции провести нагрузочное тестирование.
Перенести все правки на бой с миграцией данных.
Результатом внедрения должны были стать:
Система хранения свойств, которая не тормозит работу сайта и легко доступна для выборки данных, которые потом можно применять где угодно по необходимости.
Успешно перенесенные данные из стандартной системы хранения в кастомную.
Стандартный фильтр, работающий как со стандартными свойствами, так и с кастомными.
Успешное прохождение нагрузочного тестирования после миграции и внедрения фильтра.
Провели оценку трудозатрат и выяснили, что только на тестирование гипотезы нам понадобится порядка 160 часов разработки, а кастомная архитектура должна будет поддерживать 20 000 свойств товаров.
Вырисовывался довольно трудоемкий и долгий процесс по перестройке архитектуры хранения свойств, для чего пришлось бы переписывать стандартные компоненты.
Неожиданное решение
При реализации другого проекта были замечены особенности хранения БД «Битрикс» и появилась мысль, как можно реализовать возможность ввести большое количество свойств.
Решили попробовать запустить работу на стандартном инфоблоке в общей структуре, и хотя там меньше свойств, чем требуется, но в целом должно было заработать. Возможно, с поднятием ресурсов сервера можно было добиться хранения более 1 000 свойств без проблем с производительностью сайта, а также без работ по изменениям фильтров, каталога и т. п.
На проверку варианта нужно было ~4–8 ч, если отработают штатные механизмы «Битрикс», и 8–16 ч, если придется переносить данные скриптами.
Вариант согласовали с клиентом: в новой архитектуре хранения данных предполагалось порядка 20 000 свойств у товаров, то есть с большим запасом, но решение было затратным по времени и средствам. А в предлагаемом нами решении можно было завести около 1 000 свойств, но его реализация была бы на порядок дешевле, а главное, быстрее.
Поэтому клиент пошел на компромисс: часть ненужных свойств удалили, некоторые вынесли в отдельные инфоблоки, а какие-то сгруппировали.
Решили пойти по пути сначала проверки гипотезы в целом и если не будет особых проблем, то тогда двигаться дальше, наращивать количество свойств и смотреть, будет ли где-то просадка, а далее провести нагрузочное тестирование.
Затраты по времени оценили следующим образом:
Переход на общую структуру.
Увеличение количества свойств и заполнения тестовыми данными.
Общее тестирование проекта.
Простое нагрузочное тестирование (возможно увеличение ресурсов и повторное тестирование).
Создали тестовую площадку. После миграции и предварительных результатов, попробовали увеличивать количество свойств постепенно и до отказа площадки.
ИБ каталога мигрировал, пробно добавили 500 свойств, на тот момент все работало штатно.
В каталог было добавлено порядка 1 500 свойств, все они участвовали в фильтрации, у каждого товара было дополнительно около 20 уникальных свойств.
В целом сайт работал, особых замедлений не было, и на текущей площадке ресурсы занимали в 3 раза меньше места, чем на боевом.
Объем данных вместе с индексом занял порядка 350 Мб.
Проблема была решена.
Для работы с административной панелью сделали отдельную форму для добавления свойств товаров. Стандартные формы работы со свойствами не были рассчитаны на обработку сразу большого количества свойств.
В итоге сделали:
список свойств;
отдельную форму добавления и редактирования свойств, привязки к разделам;
в карточках товаров сделали привязку свойств к разделам для удобства редактирования.
В конце протестировали работу фильтров в различных каталогах сайта — проблем не обнаружили.
Количество свойств увеличили до 6 000, а у каждого товара заполнили порядка 30, в целом публичная часть отрабатывала нормально.
Снова протестировали, увеличили память для PHP с 1024 до 3072. Нашли небольшие ошибки, быстро поправили и финально протестировали — проблем не выявили, перенесли на релиз.
Протестировали релиз, проблем по задаче и по новому функционалу добавления свойств не обнаружили.
В чем была загвоздка
Решение оказалось неожиданным — все проблемы с размерами БД ушли после того, как мы перевели каталог товаров с инфоблоков 2.0 на инфоблоки 1.0. Оказалось, что у версий отличаются способы записи данных.
В инфоблоках 2.0 все значения свойств одного элемента хранятся в одной строке, эта технология позволяет существенно ускорить работу системы и снять ряд ограничений по сравнению с предыдущей версией.
Из-за этой особенности в инфоблоках второй версии идет горизонтальное масштабирование, то есть при добавлении нового свойства в таблицу добавляется новый атрибут. А при добавлении нового атрибута происходит изменение таблицы, и она перестраивается, пересобирается на уровне БД. Соответственно, достигнув предела в 300 свойств, она начинает гигантски раздуваться.
Такой подход идеален для небольших стандартных магазинов, где свойств не более 300, а товаров не более 50 тысяч.
Нашему же проекту технология инфоблоков второй версии не подошла — товаров было значительно больше, а свойств также было больше 1 тысячи. И выяснилось, что в таком случае больше подходит структура инфоблоков 1.0.
В стандартной структуре используется вертикальная система, то есть при добавлении свойств добавляются просто записи в несколько таблиц. Соответственно, выборка усложняется и становится медленнее, но работа продолжается, даже после прохождения порога в 300+ свойств. И по факту получается, что инфоблоки 1.0 работают быстрее, чем инфоблоки 2.0.
При этом на данном проекте, все остальные инфоблоки, которые не относятся к каталогу, мы оставили работать на инфоблоках второй версии, поскольку у них было не более 20 свойств. И при таких вводных, когда нет гигантской атрибутики, новая структура выигрывает у стандартной.
С помощью этого решения мы сэкономили заказчику массу ресурсов, поскольку разработка новой архитектуры хранения данных заняла бы на порядок больше времени, а значит, и средств. На данный момент проект успешно работает.