Часто задаваемый нами вопрос: почему мы выбрали Clojure в качестве основного серверного языка в Metabase. Выбор языка программирования для проекта обычно определяется сочетанием того, с чем команда наиболее комфортно себя чувствует, горячих споров о динамической и статической типизации, критериев минимальной релевантности и этого неуловимого поиска очков Dev-Hipster. В нашем случае мы попали на наш основной серверный язык довольно окольным путем.
Начало
Еще в темные века Metabase начинала свою жизнь как встроенная аналитическая система для одного из первых продуктов Expa, который был написан на Python с использованием веб-фреймворка Django. Увидев потребность в подобной системе для каждой другой компании, начатой в Expa, мы вывели ее из этого проекта в отдельную систему аналитики и расширили ее. В итоге он включал в себя BI-сервер, конвейер сбора и обогащения событий, сторонние импортеры данных и управление хранилищем данных. Чтобы не переписывать его больше, чем необходимо, он остался проектом Django. С начала 2014 года он использовался Expa и его портфельными компаниями.
BI-сервер (который в конечном итоге стал Metabase) на тот момент представлял собой большой шар Python с бэкэндом Django и фронтэндом Angular. Помимо множества операций CRUD с данными приложения и нашей семантической модели хранилища данных, обслуживающей интерфейсное приложение Angular, у приложения были два основных аспекта, которые были особенно сложными. Во-первых, основной рабочей лошадкой нашего приложения была конечная точка API, которая принимала запрос и выполняла его в удаленном хранилище данных. Этот вызов обычно имел очень широкий диапазон задержек, так как некоторые запросы возвращались почти мгновенно, в то время как другие выполнялись за несколько минут (или, что еще хуже, с некоторыми особенно сложными запросами). Второй аспект заключался в том, что все такие запросы были выражены на нашем языке запросов. Они были сериализованы как словарь JSON. Когда запрос был выполнен, он будет скомпилирован в запрос SQL или MongoDB.
В то время как мы работали внутри компании, у нас не было особой мотивации что-то менять в архитектуре. Как только мы решили открыть исходный код проекта, на первый план вышли вопросы стабильности и простоты развертывания. У нас было несколько очевидных проблем с нашей кодовой базой Python:
Модель запроса WSGI не соответствует нашему основному вызову API при выполнении запроса к хранилищу данных. Хотя мы смогли обойти это, активно используя асинхронные рабочие очереди, это значительно усложнило как внешний, так и внутренний код.
Требуется компиляция драйверов баз данных Python Mysql и Postgres. Это сделало как настройку машины разработчика, так и развертывание более сложными, чем это должно было быть на самом деле.
Было много подвижных частей между Docker + nginx + uWSGI + Django + скомпилированными драйверами + процессами поисковой индексации (свист) + Celeryd + Celery worker + Redis. Это приводило к значительным трудностям при настройке новых машин для разработчиков. Для развертывания новых производственных установок у нас был набор довольно сложных рецептов Chef, которые было бы очень трудно доказать идиотам.
У нас также был ряд повторяющихся заданий, которые мы выполняли с помощью Celery. Это сработало, но было непросто настроить или отладить. В целом, для поддержания работоспособности всей системы требовалась значительная нагрузка на поддержку.
Каждые пару недель что-то в настройке нашей машины разработчика, которая добавляла Vagrant и VirtualBox в приведенный выше список движущихся частей, выходило из строя и съедало пару часов.
Критерии для нового языка
Когда мы начали процесс рассмотрения порта, мы определили наши критерии выбора нового языка. Это были:
Твердая модель для асинхронных веб-запросов
Быстро + легко развертывается другими
Продуктивно развиваться в
Широкий выбор зрелых драйверов баз данных
Сильные примитивы функционального программирования, упрощающие компиляцию нашего языка запросов.
Претенденты
Джава
Scala
Clojure
Javascript / Узел
Go
Python (скрученный)
Питон (торнадо)
Учитывая сложности, связанные с настройкой машины разработки с помощью python, мы на раннем этапе вычеркнули Twisted и Tornado.
Go казался многообещающим, но слишком низким для нас, чтобы продуктивно выразить наш язык запросов. Вдобавок у него были относительно незрелые драйверы для чего-либо, кроме MySQL и PostgreSQL.
Команда Javascript получала значительную поддержку, но мы чувствовали, что драйверы базы данных в целом недостаточно развиты.
Шорт-лист действительно сводился к «чему-то на JVM» из-за его зрелой и предсказуемой модели потоковой передачи, надежных соединителей баз данных и способности поставлять одну банку со встроенным веб-сервером (Jetty) + встроенной базой данных (H2). Однако у нас было сильное отвращение к использованию самой Java, поэтому реальным коротким списком были Scala или Clojure.
Фальстарт
После некоторых раздумий по поводу найма в нашей сети (много людей Scala в SF), желания обеспечить безопасность типов (не один из наших критериев, а серьезное дополнительное соображение) и хорошего взаимодействия со Spark и драйверами базы данных JVM в целом, мы остановился на Scala. Вся команда проглядела несколько руководств по Scala и начала создавать прототипы в Play и различных DSL баз данных (в первую очередь Slick, Squeryl и Anorm).
Примерно через неделю экспериментов мы натолкнулись на камень преткновения. Хотя у нас было большое количество «обычных» операций CRUD, все наши запросы от имени конечного пользователя генерировались динамически. Большая часть экосистемы баз данных Scala ориентирована на расширение системы типов Scala с целью включения проверки типов запросов к базе данных. В целом наш энтузиазм по поводу Scala не пережил первую попытку перенести нашу систему языка запросов.
Clojure для победы
Именно в этот момент мы переоценили Clojure и, в частности, Korma. Быстрый прототип приложения был создан на Clojure, и самый сложный аспект нашего приложения было очень легко выразить. В первый день мы добились большего прогресса, чем в Scala за неделю, и в целом почувствовали, что это было намного более естественным. Clojure это было!
Переход от асинхронной рабочей очереди к облегченным потокам значительно снизил сложность нашей кодовой базы. Эта простота уменьшила количество возможных состояний ошибки и, как правило, упростила и улучшила взаимодействие с пользователем. Вдобавок, между потоками и улучшенным профилем задержки Clojure в целом, скорость, по мнению пользователей, значительно улучшилась.
Повышение производительности труда разработчиков было драматическим. JDK в основном прост в установке, а с Leiningen установка машины разработчика была неизменно безболезненной. Изо дня в день возникает гораздо меньше проблем, и в целом язык стало намного легче научить новых разработчиков, чем ожидалось.
В результате перехода на Clojure появился ряд неожиданных преимуществ. Стабильность JVM, простота использования одного jar-файла в качестве артефакта развертывания, а также довольно дисциплинированный процесс тестирования и проверки кода позволили нам управлять относительно большим количеством экземпляров (~ 15 по последним подсчетам) без каких-либо реальных усилий на наша часть. Комбинация Elastic Beanstalk и uberjar сделала нашу общую поддержку незначительной. Примерно за год работы в производственной среде у нас не было серьезных сбоев сервера, которые требовали бы ручного вмешательства.
Когда пришло время создавать приложение для Mac OS X, простота развертывания сделала объединение JRE + в наш uberjar чрезвычайно простым. Попытка сделать то же самое с нашей старой комбинацией Django + Postgres + Celery + Redis было бы значительно более рискованным и хрупким при запуске.
Итак, в итоге мы выбрали Clojure для потоков JVM + драйверы баз данных, возможность отправлять uberjar и простоту выражения манипуляций с деревом, которые были основной частью сложности в нашем бэкэнде.
Общий вердикт: 10/10 будет перенесено снова.
Если вы зашли так далеко, вы можете получить дополнительную информацию о Metabase, сервере бизнес-аналитики с открытым исходным кодом, который настраивается за 5 минут на сайте www.metabase.com. Мы также высказываем свое мнение и разглагольствуем в твиттере @ Metabase.