Привет, меня зовут Максим Дьяков, я основатель сообщества Russian Hackers - рассказываем о хакатонам, помогаем разработчикам идти вверх по карьеной лестнице, а стартапам покорять луну. А еще мы пилим свой SaaS - HackeR, платформа для организации хакатонов. В этой статье я расскажу, как для неё реализовывался новый функционал, поделюсь подходами и опытом, которые могут быть полезны при разработке и других продуктов.
"Откуда растут ноги"
Совсем недавно мы поводили хакатон BIGTARGET для компаний Лента и Microsoft, где как раз использовалась наша платформа, но так как для нас это был первый именно полноценный DS хакатон, нам было необходимо доработать нашу платформу - сделать LeaderBoard и расчет Score.
Если тебе интересно больше узнать о Data Science хакатонах, как организовать подобный ивент и как мы работали над BIGTARGET, то рекомендую сначала прочитать нашу статью - "Организация Data Science онлайн-хакатона: личный опыт, на чем сделать акценты и как организовать хакатон именно вам".
При разработке любого функционала мы стараемся соблюсти оптимальный баланс по стоимости, времени и качеству. Так как наш стек это Angular с кастомизированной библиотекой всех необходимых элементов и Node.JS в связке с MongoDB, то мы буквально как из конструктора смогли собрать LeaderBord и форму всего за 1 рабочий день. Главным нашим опасением была непосредственно обработка логики расчета метрики – мы имеем дело с датасетом на 270к+ строк, в хакатоне участвует 20+ команд, а хостимся мы на самом обычном одноядерном сервере-картошке за 400р/месяц.
Оценив ситуацию, мы решили действовать итеративно:
Этап 1 - завести “хоть как-то”
Node.JS позволяет достаточно просто запускать python скрипты и считывать результаты, поэтому в первой реализации мы просто сохраняли полученный файл с результатами на сервер, вызывали скрипт, забирали результат, записывали его в БД и возвращали Score на клиент.
На самом деле этот вариант можно было и оставить, если бы мы реализовали очередь и асинхронную обработку запросов, но у нас в команде на тот момент не было человека, который имел подобный опыт + не хотелось тратить много времени на этот функционал.
Протестировав эту реализацию на нашем сервере, мы увидели, что хотя все обрабатывается в довольно приемлемое время – около 20 секунд на запрос, мы можем «упасть» в последние минуты перед дедлайном: переполнение кэша (файлы с решениями весят по 10мб), непредвиденные ошибки и т.д.
Этап 2 - “лучше одной картошки - ведро картошки”
Следующим этапом было поднятия кластера – одновременно на каждом доступном ядре запускается независимая копия приложения. Мы используем процессный менеджер PM2 от keymetrics.io (максимально рекомендую решение всем старатаперам), который позволяет достаточно просто управлять продакшеном и автоматически развертывать и настраивать кластер.
Это могло бы быть решением, но у нашего облачного провайдера Vscale.io (дочка Selectel) можно было арендовать сервер только с 4 ядрами, а автоматической миграции на сам Selectel не предусмотрено. Поэтому, хоть мы и апскейнули сервер, 100% уверенности у нас не было.
Кстати, мы не немного пропустили момент обновления записей в нашей БД – так как у нас документо-ориентированная база, нужно максимально аккуратно относиться к обновлению документов: одна команда одновременно отправила с нескольких компьютеров свое решение, попала на разные ядра кластера, что привело к сохранению в базу только последнего обработанного результата (результаты хранились как вложенный массив в документе, поэтому для добавления нужно было сначала считать документ, обработать его локально и заново сохранить). Но мы быстро узнали об этом и пофиксили буквально за пару минут, а дальше пристально мониторили ситуацию по логам.
Этап 3 - “python в сделку не входил...”
И так, у нас есть python скрипт с логикой расчета метрики, который запускается локально командой из Node.js. Как нам сделать этот процесс максимально отказоустойчивым и безопасным для продакшена в целом. Идеи, предложения?
Cloud Functions! Это просто идеальное решение для подобных задач. Вы берете и с минимальными доработками заливаете свой скрипт и получаете готовое решение, над которым можно издеваться как угодно.
Конечно, сейчас все еще существуют некоторые сложности с деплоем функций, но в нашем случае у нас в распоряжении оказались менторы из Microsoft, которые помогли все быстро развернуть на Azure, за что им огромный респект – решение отработало без каких-либо нареканий.
Итак, наш итоговый сетап:
- Загрузка решения – фронтенд Angular
- Отправка решения на кластер: на 4 ядра, Node.js (Express), PM2, Nginx
- Проверка пользователя и отправка на решения Azure Cloud Function
- Сохранение результатов в DB и возвращение Score на клиент
По итогу все прошло без каких-либо проблем и только с 1 багом, описанным ранее: система спокойно переварила 281 решения общим объемом 1,9 ГБ. Скорее всего мы где-то слишком сильно перестраховались, где-то архитектуру можно было сделать более оптимальной, и т.д. Но! Вся реализация заняла всего 1 день работы full-stack разработчика и 3 часа специалиста из Microsoft. И я считают, если ты стартап, время - это самый главный ресурс, который нужно утилизировать с максимальной эффективностью.
Если вам интересно узнать больше о нашем решении и как мы работаем над ним, то вот ссылки на наши прошлые материалы:
- Customer Driven Development & история одного стартапа
- Hackathon-as-a-Service: как мы сделали самую популярную платформу по организации онлайн-хакатонов в 2020 году
Я не претендую на то, что мой подход самый правильный. Я делюсь своим опытом решения нетривиальной задачи. Как вы считаете, можно ли использовать такой подход в разработке?