6 декабря в московском офисе Яндекса прошёл митап Под капотом Яндекс.Такси: Python. В рамках этого митапа было прочитано три доклада про работу бэкенда “Яндекс.Такси”, причём все они были довольно общие и могли быть полезны даже тем, кто не работает с Python.

В этом обзоре я расскажу про основные моменты всех трёх докладов.

“От пул-реквеста до релиза”, Сергей Помазанов

Речь пойдёт обо всех видах тестирования, которые используются у нас в автоматическом и ручном режимах, а также о том, как мы автоматизируем весь процесс разработки.

В этом докладе Сергей рассказал про путь задачи от момента “сделано на машине разработчика” до момента “используется реальными пользователями”.

Код “Яндекс.Такси” хранится на Github (по всей видимости Enterprise версии), там же происходит ревью pull request-ов, а вся автоматизация тестирования и сборки работает через TeamCity. При работе с репозиторием используют модель gitflow, но уходят от него в сторону каких-то своих вариаций.

Основные стадии, которые проходит задача:

  1. Локальные проверки: прогон модульных и интеграционных тестов в окружении разработчика (собирается на его машине через docker compose), проверка на соответствие стилю кодирования (для этого используется 5 разных инструментов)
  2. PR в Github: прогон модульных и интеграционных тестов в отдельном тестовом окружении, возможно нагрузочные тесты (инструмент - “Лунапарк”)
  3. Code review (про него будет чуть позже)
  4. Сборка единого релизного тикета с уведомлением всех разработчиков, чьи задачи будут выезжать
  5. Накатывание миграций для изменений схемы баз данных
  6. Выкатка на предпродакшен: изменения раскладываются на несколько машин и функционал становится доступен небольшой части пользователей.
  7. Выкатка на продакшен: изменения раскладываются на все машины.

На стадии code review проверяют обратную совместимость кода, наличия простой возможности включать/выключать его (кажется весь новый функционал выкладывается в выключенном состоянии), наличие проверки входных данных, корректность работы при retry запроса (aka идемпотентность), объём данных, наличие нужных индексов в таблицах, наличие логов, которые помогут исследовать проблемы, отсутствие предварительных оптимизаций.

Интересно, что на стадии code review не тратят время на поиск ошибок в реализации: вся ответственность на за корректность работы кода лежит на разработчике, который его написал.

Новый функционал обычно выкатывается в выключенном виде, а потом включается через специальную админку. Если с ним возникают проблемы, то сервис выключается через ту же самую админку, а по собранным в процессе его работы логам выявляются причины возникновения проблем.

Для работы с логами сейчас используют ELK-стэк (ElasticSearch для поиска + LogStash для сбора логов + Kibana для визуализации). LogStash плохо работает на их объёме логов, поэтому они планируют заменить его каким-то своим решением.

Переходят от разрозненных репозиториев сервисов к monorepo ради упрощения переиспользования общего кода в разных сервисах (“пакеты и сабмодули - это менее удобное решение”). Переход идёт тяжело из-за конфликтующих между собой версий одних и тех же пакетов в разных сервисах.

Видео и текстовая расшифровка доклада на Хабре

“Graceful degradation в Яндекс.Такси”, Илья Сидоров

Расскажу о том, как мы даём пользователю заказать машину, даже когда какие-то части сервиса не работают.

Илья сильно волновался и не очень уверенно выступал, но несмотря на это его доклад понравился мне больше всех.

Основная идея: проблемы случаются, но даже с ними основная функциональность должна работать, пусть и не идеальным образом.

Если какой-то сервис испытывает проблемы, то он или выключается (если это возможно), или заменяется на упрощённую версию (“тыкву” в их терминологии).

Некоторые сервисы нельзя просто выключить, т.к. или он сам может быть очень важным для работы приложения (aka mission critical), или он требуется для работы других более критичных сервисов. В таком случае для этого сервиса включается failover режим и вместо него начинает использоваться сервис-“тыква”. Критерием включения является превышение определённого лимита некорректных ответов от сервиса.

Интересно, что с проблемного сервиса не снимают нагрузку при включении “тыквы”. Запросы отправляются в оба сервиса и при корректном ответе оригинального сервиса используется его ответ, при некорректном - ответ от “тыквы”. Это позволяет продолжить измерять процент ошибок в ответах и при уменьшении ниже определённого лимита выключить failover режим и перестать использовать “тыкву”.

При использовании “тыквы” важно уметь обеспечивать консистентность использования “тыквы” для взаимодействующих друг с другом пользователей (водитель-пассажир).

Также нужно иметь возможность включить тыкву только для части пользователей, которые испытывают проблемы (а не для всех). Связано с тем, что может быть несколько инстансов сервиса или датацентров.

Видео и текстовая расшифровка доклада на Хабре

“Как запустить ML-прототип за один день?”, Роман Халкечев

В докладе речь пойдёт о написанном на Python сервисе, который позволил нам быстро создавать прототипы, использующие machine learning, а также быстро экспериментировать с разными ML-моделями.

Доклад был не столько рассказом про быстрое создание прототипов моделей машинного обучения, сколько обзором того, как в продакшене “Такси” используется машинное обучение. Само прототипирование было лишь частью этого рассказа. По содержанию доклад был довольно похож на предыдущее выступление Романа (без рассказа о прототипах и с другой задачей в качестве примера), запись которого уже можно посмотреть.

Весь доклад был построен на разборе реализации задачи по рекомендации пассажиру мест посадки при вызове такси. Вначале Роман описал “offline” часть задачи: её исходные данные, возможные метрики для оценки результата, пропустил этап подготовки модели и более подробно рассказал, что происходит после этого в “online”.

Готовые модели, обученные на подготовленных заранее данных, передаются в сервис MLaaS (Machine Learning as a Service), который представляет собой FastCGI демон, написанный на C++. Для каждой модели там есть свой собственный хэндлер (т.е. при добавлении новой модели нужно модифицировать демон, что достаточно трудоёмко).

Для упрощения этой задачи они написали еще один демон на Python + Flask (PyMLaaS). Упрощение достигается за счёт того, что при построении моделей в offline они также используют Python и это позволяет модифицировать демон гораздо быстрее. Нагрузку этот демон держит гораздо хуже, поэтому его используют для экспериментов на части трафика или в местах без большой нагрузки (кажется, он обрабатывает около 5% запросов к ML). Снаружи оба этих сервиса выглядят одинаково (нагрузка переключается на уровне nginx, за которыми стоят оба этих сервиса).

PyMLaaS используется в основном для прототипирования, т.к. результаты модели на онлайн данных могут оказаться гораздо хуже, нежели на заранее подготовленных. Модели раскатываются под A/B-тестами (причём их может быть и больше двух), что позволяет выкатить под одним тестом сразу много моделей и потом выбрать лучшую из них.

Для уже работающих моделей собираются новые данные с продакшена, они дообучаются на этих данных и периодически заменяются (кажется, раз в сутки). Есть мониторинг, который отслеживает, как долго модель не обновлялась.