Команда авторов ГК Luxms, вендора платформы бизнес-аналитики Luxms BI:
Александр Тютюнник — директор по развитию бизнеса
Дмитрий Дорофеев — главный конструктор
Илья Гурешидзе @IlyaGureshidze — тимлид фронтенд-команды
Константин Буров — тимлид команды аналитиков
В крупных федеральных организациях всё активнее используется подход управления на основе данных, который требует активного использования и постоянной переделки, развития, модификации аналитических приложений, отчётов, данных. Тот опыт и наши наработки, которыми мы хотим поделиться в рамках данной статьи, приносят пользу на многих проектах, где речь идёт о сотнях аналитических отчётов и дэшбордов, нескольких тысячах показателей и сотнях и тысячах активных пользователей, где, самое главное, вендорские решения кастомизируются внутренними командами заказчика. Для таких случаев всё, о чём мы расскажем дальше, очень важно, для остальных — надеемся, что будут полезны отдельные мысли и технические решения.
Начнём с простого и наболевшего. Когда создаётся первая версия дэшборда, задача звучит просто: «показать данные хоть как-нибудь и побыстрее». Не до архитектуры, не до производительности — главное, чтобы цифры появились, и руководство смогло принять правильное управленческое решение. Потом уточняется задача, добавляются новые требования, меняются источники, добавляются разрезы данных, растёт нагрузка. И вот тот самый дэшборд, собранный на скорую руку, оказывается в проде — и технически работает не так и не с той скоростью, как нужно. А далее необходимо развивать функционал, обновлять версию. И сложности растут.
В статье расскажем, почему так происходит и почему «оптимизация» — это не про критику, а про работу с реальностью, со сложной реальностью мира IT и мира данных. А еще — почему важно не только чинить, но и уважать чужой код.
Оптимизация BI-приложений — это не магия, а просто другой этап сложного IT-проекта в области данных. Большинство «кривых» решений появляются из-за вполне понятных ограничений. Делать сразу идеально — невозможно.
Подвиги разработки: когда скорость важнее архитектуры
Представьте, в понедельник — срочный показ для руководства. Команда на ушах, все бросаются что-то делать, естественно, переработки, экстренные запросы вендору. Главное — чтобы к моменту демонстрации всё работало. В таком режиме архитектура и долгосрочные последствия отходят на второй план, если хоть какой-то подход срабатывает — его используют. Здесь не до качественного тестирования и работы «как в книжках описано» — дедлайны горят.
Такой код создается «на один раз». Никто не думает о том, что будет через месяц, когда он попадёт в прод и начнёт взаимодействовать с другим кодом, который ещё может даже не написан. После успешного показа — эйфория. Команда молодцы, цель достигнута. Подвиг совершён! Мало кто хочет заниматься рефакторингом, ведь всё работает. Через месяц — новый показ, новый подвиг. Очередные костыли, и снова команда — герои. Но когда таких подвигов становится слишком много, они начинают пересекаться. Один костыль ломает другой, вендор выкатывает новую версию платформы, и вдруг — подвиг невозможен. Почему всё работало, а теперь нет? Они же делают всё «как раньше»!
Мы часто сталкиваемся с тем, что у клиента что-то «тормозит» или работает не так, как задумано. Идём разбираться — а там сложные селекты, тяжёлые джойны, неверная логика. Всё сделано, мягко говоря, неоптимально. Мы оптимизируем, ускоряем, клиент благодарен. Думаете, дело в том, что кто-то не знает, как правильно делать? Нет.
Почти все проекты с данными проходят через это. Даже если работают опытные разработчики. Даже если проект изначально делался «по уму». Тут важно понимать, «оптимизаторы» пришли в момент, когда можно и нужно было всё улучшить. А команда, которая делала проект, работала в условиях дедлайнов и иногда полной неопределённости:
Нет нормального ТЗ. Или есть, но на половину состоит из готовых таблиц в базе — “и так понятно”;
Источники данных плавают. Сегодня Excel, завтра в PostgreSQL, послезавтра что-то ещё;
Бизнес ещё сам не понимает, какие метрики нужны. Через неделю всё поменяется;
Команда в стрессе. Жёсткие дедлайны. Главное — чтобы работало. О каком ревью речь?
И, конечно, вопрос производительности вообще не стоит. Он появится позже.
В этих условиях возникает «компромиссный» код. Да, он неидеален. Да, он тяжёлый, монолитный, местами страшненький. Но он работает. И он появился за два дня, а не за две недели. Вывод: подвигов не избежать, но их последствия можно минимизировать. Главное — вовремя вернуться и навести порядок.
Два аспекта — психологический и технический, — а решение одно
Психологический — команда, которая не раз добивалась успеха, вдруг терпит неудачу, хотя делает всё «как обычно». Это вызывает раздражение и недоумение. Ведь если раньше работало, а теперь нет, — значит, кто-то испортил ситуацию. Возможно, вендор (но это не точно).
Технический — в нормальном процессе есть обязательные этапы — тестирование, код-ревью, архитектурный контроль. Но в режиме подвига эти шаги пропускаются, а результат закономерен.
Однако полностью отказаться от подвигов нельзя — они неизбежны. Хорошо, что платформа позволяет быстро адаптироваться. Например, хэкнуть Windows или Microsoft Office вряд ли получится, а Luxms BI можно, платформа очень гибкая.
Главное — всегда возвращаться и понимать, что единой работающей системы не будет, если каждый раз не проходить все правильные процессы и процедуры IT-производства. После подвига не просто выдохнуть и праздновать успех, а выполнить пропущенные шаги:
Полноценное тестирование;
Код-ревью;
Рефакторинг кода и/или его оптимизация;
Документирование;
Информирование команды о технических изменениях.
Код-ревью: не про красоту, а про совместимость
Код-ревью в данном контексте — это не про обучение стилю, это не про поиск ошибок и не про оценку качества кода с точки зрения «красоты». Это инструмент, который позволяет вендору дать рекомендации, объяснить, какие методы лучше заменить, какие подходы стоит пересмотреть, чтобы код продолжал работать в будущем. Это не тренировка, а практическая необходимость.
Основная цель — проверка кода на совместимость с текущими и запланированными изменениями платформы. У вендора есть своя дорожная карта, стратегия развития продукта, идеи по улучшению кодовой базы. Какие-то части кода планируется развивать, какие-то — наоборот, исключать как устаревшие или неудобные. Эти вещи даже не всегда отражены в документации, но они критически важны для долгосрочной работоспособности системы.
Поэтому наша команда в рамках технического аккаунт-менеджмента проводит аудит кода с фокусом на его совместимость с коробочным решением вендора и его будущими версиями. Параллельно команда заказчика и сама может (и должна!) проводить код-ревью с точки зрения совместимости с уже существующими своими кастомными разработками. Ведь у заказчика могут быть свои внутренние стандарты, стайл-гайды, библиотеки кастомных компонентов, специфические темы оформления. Вендор не знает о них, но они могут существенно повлиять на стабильность и поддержку системы в будущем.
Две команды могут объединить усилия, дополнив пропущенные шаги в разработке, чтобы избежать проблем в перспективе. Систематическое игнорирование таких проверок приведёт к тому, что очередной «подвиг» по доработке системы станет невозможным или крайне болезненным.
В мировой практике давно используются методы совместной разработки, включая конференции, открытые репозитории кода и совместное написание программных решений. Открытые форумы, митапы и другие форматы обсуждения позволяют не просто обмениваться общими идеями, но буквально разбирать код построчно, анализируя и оптимизируя решения.
Лучшие практики совместной разработки применяются уже десятилетиями, как в open-source проектах (PostgreSQL и другие), так и в коммерческом ПО.
Оптимизация и проектная поддержка
Разработка кастомных решений часто связана с необходимостью оптимизации. Однако важно понимать, что подходы к улучшению производительности могут быть разными.
Пример: команда заказчика замечает, что дэшборд медленно загружается, и предполагает, что проблема в JavaScript. Разработчики приходят к специалисту, и он ускоряет JavaScript-код в 2 раза. Вроде бы задача решена.
Но можно рассмотреть эту проблему комплексно. В команде вендора есть специалисты разного профиля — дата инженеры, фронтенд и бекенд-разработчики, техподдержка.
Они могут проанализировать не только код, но и архитектуру решения в целом. Оказывается, что вместо ускорения JavaScript в 2 раза можно оптимизировать обработку данных и ускорить работу в 100 раз, устранив проблему на более глубоком уровне.
Есть и ещё момент: команда вендора уже сделала несколько сотен проектов, и может сравнивать один с другим. Например, если у всех запросы выполняются за полсекунды, а у конкретного заказчика — за 30 секунд, это аномалия. Клиент может этого не замечать, потому что не видел другого примера работы, но вендор-то видел, он может выявить узкие места и предложить решения.
Комплексное сопровождение проекта включает:
поддержку всего кода, в том числе ETL и кастомных решений;
анализ влияния изменений после обновления системы;
обеспечение целостности продукта в процессе его развития.
Таким образом, в рамках вендорского сопровождения проекта заказчик получает не просто локальные исправления кода, а целостный подход к решению проблемы, что позволяет добиться максимальной производительности и устойчивости системы.
Кстати, часто заказчики сами находят новые подходы, которые мы не предусматривали. Если они грамотно вписываются в архитектуру, мы берём их на вооружение и распространяем среди всех пользователей. Это напоминает open-source: сообщество помогает улучшать продукт.
Документация и внутренняя инженерная память
Одно из главных условий устойчивого развития кода — наличие понятной, актуальной документации. Сложные системы редко разваливаются из-за багов — гораздо чаще причиной становится потеря контекста. Кто-то ушёл из команды, кто-то «написал быстро и потом забыл», кто-то добавил особенное поведение без комментариев, полагаясь на то, что «и так понятно». Через полгода становится совсем непонятно. Именно поэтому мы всё чаще говорим не только о коде, но и о внутренней инженерной памяти. Даже сильная архитектура со временем начинает «трещать», если её сопровождают только фрагментарные комментарии в стиле // костыль, не трогать.
Важно фиксировать не только что делает код, но и почему он делает это именно так. Особенно когда речь идёт о нестандартных решениях, работе с кастомным LPE, обходах ограничений, асинхронной логике или взаимодействии между несколькими слоями системы. У каждого такого решения должна быть своя «анатомия»: что было до, зачем понадобилось менять, что пробовали, на чём остановились, и какие риски это в себе несёт.
Чтобы не терять эти знания, необходимо создавать:
Краткие описания к ключевым техническим решениям — фиксируем суть подхода, особенности реализации и важные нюансы, на которые стоит обратить внимание при повторном использовании или доработке;
Методические материалы по развитию внутренних компонентов — описываем принятые практики: как структурировать код, как работать с переменными, как управлять доступами, каких подходов стоит избегать и т.д.;
Сборник кейсов из практики — документируем, как мы решали конкретные задачи, какие трудности возникали, какие подходы оказались эффективными, а какие — нет.
Кроме документации к коду, важны и более широкие методические материалы: гайды по архитектуре, правила написания кастомных компонентов, типовые ошибки при работе с LPE, рекомендации по работе с правами и конфигурациями. Это помогает не только ускорять разработку, но и формирует общее инженерное поле — когда каждый следующий шаг строится на понятной и уже проверенной базе. Это не формальность. Это способ защищать себя в будущем от хаоса. Он позволяет быстрее вводить новых людей, смелее рефакторить, видеть, как работает система.
Разумный рефакторинг: как мы переписали логику для высокой производительности
В одном из наших проектов данные изначально хранились в PostgreSQL. На определённом этапе было принято решение перейти на Greenplum — решение класса MPP, более подходящее для анализа больших объёмов данных.
Этап 1. Перенос на Greenplum: функциональная адаптация
У Greenplum и PostgreSQL есть ряд отличий, особенно в поддержке SQL-синтаксиса и оптимизации запросов. На первом этапе задача была не ускорять, а обеспечить корректную работу на новой СУБД.
Что пришлось изменить:
Переписаны SQL-запросы, использовавшие конструкции, не поддерживаемые Greenplum. Например, в PostgreSQL активно использовался INSERT ... ON CONFLICT ... DO UPDATE — удобный способ делать upsert. Однако Greenplum такую конструкцию не поддерживает из-за своей распределённой архитектуры;
Упрощены участки логики, завязанные на специфичные для PostgreSQL особенности;
Перепроверена корректность агрегаций и расчётов после миграции.
На этом этапе приоритетом была функциональная совместимость — важно было, чтобы дэшборды продолжили работать корректно.
Этап 2. Оптимизация под реальные объёмы
На этом этапе оказалось, что объём данных существенно вырос по сравнению с тестовой средой. Это привело к резкому увеличению времени расчётов.
Основные проблемы:
Одним из источников данных была сверхбольшая таблица, к которой шло множество запросов при расчётах;
Последовательность вычислений была неэффективной;
Объём промежуточных данных значительно вырос, что сильно увеличило требования к ресурсам.
Что сделали:
В расчётах использовали временную таблицу, куда подгружали часть данных из большого источника. Таблица хранилась в оперативной памяти, что значительно ускорило расчёты.
Перестроили последовательность вычислений, оптимизировали алгоритмы обработки: порядок расчётов по сущностям исходя из понимания базы.
Результат:
Скорость выполнения запросов выросла более чем в 10 раз (с часов до нескольких минут);
Повысилась стабильность выполнения запросов даже под высокой нагрузкой.
Развитие системы и управление изменениями
Наша платформа постоянно развивается, компоненты системы эволюционируют. Однако новый функционал также не выходит сразу в идеальном виде — он проходит несколько итераций, получает доработки на основе обратной связи. Это нормальный процесс.
Мы предупреждаем клиентов: если кастомный код использует новые возможности, это дополнительная зона для код-ревью, тестирования и мониторинга. Luxms BI — живой продукт.
Большинство изменений происходит незаметно для пользователей. Однако если клиент использует кастомный код, который напрямую взаимодействует с конфигом, могут возникнуть нюансы. Например, если система начала писать настройки в новом формате, а кастомный код использует старый, это может привести к нестыковкам. Мы стремимся минимизировать влияние таких изменений, но отказ от устаревших решений — естественная часть развития системы: свой код мы также постоянно рефакторим и улучшаем.
Оптимизация кода и архитектурные улучшения в Luxms BI
Один из кейсов по оптимизации кода внутри Luxms BI связан с компонентом VizelTabs. Этот компонент реализует логику визеля-контейнера, в котором дочерние компоненты рендерятся в зависимости от того, какая вкладка открыта.
Компонент очень старый, видны следы работы разных разработчиков (некоторые из них уже даже не работают в команде), и это хорошо видно по структуре кода — фрагменты логики накладывались ситуативно, поверх уже существующего функционала, и код не просто спагеттизировался, но и вился в дреды.
Компонент в какой-то момент начал получать настройки, рассчитанные внутри lpe-выражений. Стал реагировать на смену URL и даже фильтров. Появилась однотипная логика для условно-скрытых вкладок, их заголовков.
Особенность этого компонента ещё и в том, что дочерние элементы — это не реальные дэши, а просто явно указанный перечень JSON-конфигов визелей. Они наследуют права родителя в виде Вкладки и Доски.
В какой-то момент количество различных if-ов достигло критической массы.
Мы всё чаще сталкивались с ситуацией, когда поведение компонентов зависело от логики, рассчитанной через LPE, и быстро меняющегося контекста, который нужно пересчитывать в lpe выражениях. В результате приходилось вручную подписываться на множество различных сервисов и следить за изменениями их моделей — это важно для правильного расчета контекста lpe и корректного поведения компонентов, в частности вкладок. Со временем такая система подписок превратилась в настоящий архитектурный ад.
Было принято решение реализовать иной подход к наблюдению за меняющимся контекстом lpe — так называемые LPE-стримы. Это сложная система на основе промисов, которая хранит все компоненты-подписчики, чей lpe использует сервисы или переменные what-if, от значения которых зависит расчёт соответствующего контекста. Как только модель загружает данные, выходит из состояния loading и завершает обновления, система оповещает: контекст изменился, пора пересчитать значения.
Такой подход стал применяться не только для компонента VizelTabs — он постепенно распространяется на всё большее число коробочных элементов. Это позволяет выстраивать более предсказуемую событийную архитектуру.
Сам компонент VizelTabs был переписан: его размер стал меньше, функции — переиспользуемыми, а логика подписки на смену табов и расчет свойств hidden (видна ли вкладка) и title (название вкладки) реализованы через LPE-стримы. Также в компоненте появилась возможность использовать функции из lpeForReact — вместо строки можно писать результат связки React-нод через встроенные функции, такие как img, svg, wrap и других.
Кроме того, для обратной совместимости сохранена поддержка children, но ранее фиктивные дэши теперь превращаются в настоящие. Это значит, что для них можно задавать собственную ролевую логику, отличную от родителя.
Вместо морали — немного эмпатии
Когда мы приходим на проект «оптимизировать», мы находимся в лучшей позиции:
Уже есть все данные;
Понятны бизнес-требования;
Видны типовые сценарии использования;
Есть профили нагрузки;
Фокус на улучшение, а не на «собери хоть что-нибудь к утру».
Писать книгу с чистого листа — одна задача, а редактировать — другая. Конечно, во втором случае видно ошибки, несогласованности, можно предложить лучшие формулировки. Но это другая фаза работы!
Сказать «здесь всё неправильно» — легко. Гораздо сложнее — уважать работу, сделанную в других условиях, и помочь её улучшить.
Если вы — тот, кто собирает первую версию, — не стыдитесь черновиков. Не бойтесь «некрасивого» кода. Это часть жизни проекта. Просто важно помнить, что рано или поздно придёт следующая стадия. И к этому надо быть готовыми: оставить документацию, подумать о расширяемости, предусмотреть возможные изменения.
Проекты с данными — это не гонка на 100 метров. Это длинная дистанция. С остановками, передышками, переосмыслением и вторым дыханием. И каждый этап здесь важен. И первый, и последний.
Хорошая система — это не та, где сразу всё сделано идеально. Это та, которая способна адаптироваться, улучшаться и справляться с ростом требований. Именно поэтому в BI-проектах важно не просто «сделать красиво», а выстроить процесс развития. Шаг за шагом.