- PVSM.RU - https://www.pvsm.ru -
Это небольшая история страданий, боли, взлетов и падений в попытках ускорить работу RaphaelJS на больших и сложных SVG. Если вы страдаете от подобных проблем, то не стоит ждать в конце этой статьи серебряной пули, но, надеюсь, что про наш путь поиска решения будет интересно прочитать всем.
Начиналось все с того, что в ResumUP [1] мы рисуем svg, рисуем его много и красиво.
Исторически сложилось так, что все это рисуется с помощью прекрасной библиотеки RaphaelJS [3] (тут нет иронии, спасибо Дмитрию Барановскому за нее). Как только началась работа над проектом, то Raphael был единственным решением, которое позволило во вменяемые сроки реализовать все то, что нарисовали дизайнеры.
Но радость наша была преждевременной: отрисовка резюме занимала в среднем 5-7 секунд на нормальных машинах в нормальных браузерах (при переходе одного из условий в состояние «ненормальный» — до 30! секунд). Понятно, что в долгосрочной перспективе это никого не устраивало и нужно было искать способы оптимизации.
Итак, что мы имеем?
Первое, что всем приходит на ум, — посмотреть, почему рафаэль рисует все так долго. Небольшое погружение в код библиотеки открыло интересные особенности относительно того, как браузеры работают c svg, деление вышло всего на два лагеря: те, кто понимают inline svg и те, что нет.
Так вот, для совместимости со старыми браузерами рафаэль работает напрямую с DOM-дерево документа (это все очень грубо, но фактически так), понятно, что когда элементов много, то даже нормальным браузерам приходится тяжко (а если вспомнить, что при отрисовке еще и логика есть, то совсем плохо становится)
Вооружившись знаниями и осознав, что быстро мы рафаэль поправить не сможем, решили пойти в обход и кешировать результат отрисовки на стороне сервера. Придумали — сделали, схема получилась такой:
Это позволило сократить загрузку страницы до секунды, что все равно было довольно много, но казалось неимоверно быстрым после 5-7. Единственная проблема — браузеры без поддержки inline svg, они рисовали все и всегда. (тестировали мы возможности браузера с помощью modernizr [4])
Итак, а теперь что мы имеем?
Так как страсть к оптимизации бесконечна, то мы решили не останавливаться на текущей системе кеширования и решить проблему как-то более кардинально. И тут перед нами встает задача от начальства изучить возможность рисования наших резюме и вакансий на сервере.
Первым шагом я полез смотреть, что в мире руби есть для этого (бекэнд у нас на рельсах), но не нашел ничего вразумительного. Да даже если бы и нашел, то не было совершенно никакого желания переписывать всю ту кучу кода, что отвечает у нас за отрисовку блоков.
Т.е. появилось первое обязательное требование — оставить текущий js-код, рисующий блоки.
Самое первое решение, что мы решили попробовать — запустить все под rubyracer, обертка вокруг v8, позволяющая в руби запускать js-код.
Но проблема в том, что v8 — машина для интерпретации js, но не браузера, и, соответственно, DOM-дерева в ней нет, поэтому рафаэль упирался, но рисовать ничего не хотел. Но мы далеко не первые, кто столкнулся с задачей имитации браузера, поэтому быстро нагуглиги решение — jsdom [5]
Схема взлетела, но при попытке отрисовать всю базу резюме мы свалились уже на 10-м с ошибкой could not allocate memory. Посмотрели статистику, замерили расход памяти и получили около 50-70 мегабайт утечки и 8 секунд на исполнение. Но сам факт: использовать существующий код для отрисовки SVG на сервере можно, так что мы остались довольны.
Поэтому решили упростить немного схему и выкинуть руби. Вспомнив о главном тренде и объекте насмешек 2011 года — nodejs, тем более, что вся схема и так работала под v8.
Воткнув этого франкештейна в expressjs, мы, хоть и со скрипом, но получили нашу картинку. Скорость увеличилась до 4-5 секунд, утечка упала до 40-50.
После долгого профилирования пришли к выводу, что единственный способ избавиться от утечки — избавиться от jsdom. Но для этого требовался совершенно иной подход.
После первых странных экспериментов, мы решили что полумеры нам не помогут и придётся идти на более глобальные изменения.
Основная идея состояла в том, что мы хотели разделить процесс отрисовки SVG на два этапа:
Этот подход имел несколько преимуществ:
Сначала появилась идея распотрошить raphael и вытащить из него всю логику, которая не завязана на создание DOM-элементов, а остальное попытаться изменить. Однако, после нескольких часов внимательного изучения исходников версии 2.0, стало ясно, что это работа на неделю, дедлайн же стремительно приближался.
Подумав, мы решили написать свою реализацию, скопировав интерфейс raphael. Дальше все просто: каждый блок на резюме представлял собой отдельный класс, в который, в зависимости от окружения, прокидывали либо raphael, либо инстанс нашего велосипеда.
Так родился baileys.
В итоге получилось:
Пользователь делает запрос к rails, rails стучится к nodejs, nodejs вызывает код блоков резюме и отсылает рельсам json с данными об SVG, а рельсы, в свою очередь, должны преобразовывать json в SVG, так и родился absinthe.
Схема заработала, но до production-решения было еще далеко: отрисовка занимала в среднем 0,5 секунды и возникало много вопросов с масштабированием.
Прототип был, но хотелось довести его до ума, поэтому мы решили составить список требований, который бы удовлетворил нас на текущий момент:
В итоге получилось, что из основного репозитория мы выделили еще 4 проекта:
В итоге мы получили возможность использовать tetris как на стороне ноды в виде npm-пакета, так и на стороне рельс, подключив в asset pipeline (для этого просто собрали простой gem, который несет с собой tetris+baileys+absinthe в минимизированном виде).
Текущие бенчмарки показывают, что мы рисуем резюме в среднем за 70мс, что уже вполне неплохой результат, если вспомнить про первоначальные 7 секунд.
В дальнейших наших планах стоит перенос всей системы отрисовки на связку baileys’а и absinthe’а и полный отказ от raphael. Для тех же браузеров, что не поддерживают inline svg отдавать img с указанием на SVG, что отрисовано на сервере.
В случае если nodejs по каким-то причинам не отвечает, всегда можно переключиться в режим отрисовки SVG на клиенте.
Так же мы вынашиваем светлые мысли открыть код baileys и absinthe и поделиться наработками со всеми желающими, вдруг кому понадобится.
Если вы дошли до конца, то вам бонус — талисман нашей команды:
Спасибо за внимание, с вами были somebody32 [6] и Terminal [7]
Автор: Somebody32
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/4870
Ссылки в тексте:
[1] ResumUP: http://resumup.com
[2] Image: http://resumup.com/me/1
[3] RaphaelJS: http://raphaeljs.com/
[4] modernizr: http://modernizr.com/
[5] jsdom: https://github.com/tmpvar/jsdom
[6] somebody32: http://habrahabr.ru/users/somebody32/
[7] Terminal: http://habrahabr.ru/users/terminal/
Нажмите здесь для печати.