Обратно в блог
  • backend
  • management

Будьте в курсе всех событий

Как добавить вертолёты в приложение такси и не сойти с ума от дедлайнов

Всё началось внутри команды Яндекс Армении, когда пришёл продукт и сказал: «Ребят, всё, делаем вертолётные заказы».

Тогда это звучало одновременно как детская мечта — кто из нас в детстве не хотел вертолётик на пульте управления? — и как серьёзная инженерная задача. Потому что эти вертолёты уже не на пульте управления, а вполне себе настоящие, да ещё и впридачу со сложнейшей инфраструктурой Яндекс Go.

Обычно запуск нового продукта в бигтехе — это долгая история: согласования, выделение ресурсов, разработка с нуля, интеграции. Но у нас был жёсткий дедлайн, продиктованный маркетингом: полететь нужно было через несколько месяцев. Поэтому вариант написать новый бэкенд под авиацию отпал сразу. И нам предстояло выяснить, способна ли наша текущая архитектура, заточенная под автомобили, дороги и пробки, переварить принципиально новый вид транспорта.

01.png

Эта статья не про романтику полётов, а про гибкость инфраструктурных систем и искусство переиспользования ресурсов. Мы расскажем, как удалось адаптировать нашу систему, чтобы превратить такси в авиадиспетчерскую, и сделать это не за год, а всего за несколько месяцев.

Чем вертолёт отличается от такси?

Так как мы работаем на рынке Армении, то все требования формировали с местным партнёром — владельцем вертолётного парка, учитывая все локальные особенности. И довольно быстро стало очевидно, что те паттерны, к которым мы привыкли в такси, мы можем применить и к вертолётам, хоть и с существенными изменениями.

В такси всё линейно и понятно: пассажир нажал кнопку, приехала ближайшая машина, отвезла из точки А в точку Б, поездка завершилась. С авиацией всё оказалось куда сложнее.

Вот как выглядит список условий, которые нам выставил бизнес и сама реальность полётов:

Два принципиально разных продукта Первый — классический трансфер: нужно быстро добраться из Еревана, скажем, на озеро Севан. Это полёт из точки А в точку Б. Второй — обзорные экскурсии. Пассажир выбирает красивый маршрут, взлетает, любуется видами и приземляется там же, где взлетал.

Жёсткая привязка к площадкам Мы не можем подать вертолёт к подъезду или высадить пассажира у метро. Взлёт и посадка возможны только на сертифицированных вертолётных площадках. В поле сесть нельзя, на крышу произвольного здания — тоже.

Обязательный предзаказ Вертолёт — не такси, он не дежурит в соседнем дворе. Пилоту и технике нужно время на подготовку, поэтому спонтанные заказы здесь и сейчас тут не годятся. Всё должно планироваться заранее.

Гибридная тарификация Для обзорных экскурсий нужна фиксированная цена за услугу. А для трансферов — поминутная тарификация, так как время в пути может зависеть от погодных условий и воздушных коридоров.

Паспортные данные Это требование регулятора для обеспечения авиационной безопасности. Мы обязаны собирать данные всех пассажиров до вылета.

Прехолд и платные отмены В такси, если клиент не вышел, водитель теряет время и немного бензина. В авиации, если вертолёт забронирован, подготовлен, а клиент не приехал — парк несёт ощутимые убытки. Поэтому нужны были гарантии: деньги на карте холдируются заранее, а отмена прямо перед вылетом стоит дорого.

Ручное управление диспетчером Автоматическое назначение ближайшего свободного борта не подходит. Распределением заказов должен управлять диспетчер парка, который знает графики пилотов и техническое состояние каждого борта.

02.png

Собрав весь этот список, мы внимательно посмотрели на наши существующие сервисы такси. У нас есть предзаказы, есть биллинг, есть формы для данных. Но как объединить поминутную тарификацию с фиксированной в одном флоу? Как заставить систему, привыкшую к миллионам адресов, работать только с десятком конкретных точек? И главное — как реализовать всё это в срок, когда на календаре уже горят дедлайны?

Backend и приложение пользователя

После того как мы прикинули и оценили требования бизнеса, первой мыслью было пойти самым прямым путём: взять существующую инфраструктуру Яндекс Go и просто настроить её. У нас есть микросервисы для всего на свете — неужели не найдётся какой-то их комбинации для вертолётов?

Мы решили действовать по стратегии в лоб: добавляем новый тариф и смотрим, что заработает из коробки, а где придётся искать обходные пути.

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

Вся страна — один сплошной марафон

Первая проблема: вертолёт не может приземлиться где угодно. Нам нужно было жёстко ограничить точки посадки и высадки конкретными координатами вертолётных площадок.

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

03.png

Мы сделали инверсию этой логики. Мы сказали системе, что вся страна — это одна большая зона перекрытия, эдакий бесконечный марафон. И оставили белыми пятнами только маленькие зоны вокруг вертолётных площадок. В итоге пользователь физически не мог поставить пин в поле — приложение разрешало выбор только разрешённых точек. Решение стоило нам примерно ноль часов разработки бэкенда.

Два тарифа и хитрость с детским креслом

С деньгами всё оказалось чуть сложнее. Платформа тарификации не умела работать в гибридном режиме внутри одного тарифа: либо мы считаем таксометром, либо фиксированной ценой. А требования, напомню, были разные для трансфера и для экскурсий.

Решение разделилось на две части:

  • Трансфер (точка А — точка Б). Здесь мы запустили отдельный тариф «Вертолёт». Система считает только предварительную цену, а итоговая стоимость формируется поминутно таксометром — ровно так же, как в обычном такси.

  • SkyTour (облёт по маршруту). Для этого мы создали второй тариф, где цена должна быть фиксированной. Но как дать пользователю выбрать конкретный маршрут пролёта?

Мы использовали уже имеющийся у нас механизм Требований. В обычном такси это опции вроде «Детское кресло», «Перевозка животного» или «Лыжи». С точки зрения архитектуры, это просто конфигурируемые параметры, влияющие на цену. Мы немного докрутили админку на основе конфигов и превратили «Требования» в селектор туров.

04.png

Вместо выбора размера детского кресла пользователь видит красивые карточки с турами: «Полёт над ущельем», «Закат в горах» и так далее. Мы подгружаем картинку, описание, выставляем ценник — и всё это через конфиги. Главный плюс такого подхода — маленький Time-to-Market. Если партнёр звонит и говорит: «Хотим запустить тур „Пикник“», мы добавляем его за 10 минут без единого деплоя.

Проблема прехолда

Бизнесу было критично удерживать стоимость полёта заранее. Но в нашей стейт-машине заказа холд денег обычно происходит только после назначения водителя. Стандартный флоу выглядит так: Created (заказ создан) → Driving (водитель едет к вам) → Waiting (водитель приехал и ждёт) → Transporting (началась поездка).

05.png

Мы физически не можем захолдить деньги на этапе Created. Поэтому нам пришлось искусственно переводить заказ в статус Driving за 24 часа до вылета. Как только система видит этот статус, она считает, что водитель выехал, даже если по факту это ещё не так, и блокирует сумму на карте. Для этого потребовалось немного заморочиться с тонкой настройкой, но в результате без переписывания ядра биллинга.

Админка для диспетчера и 37 миллионов записей

Ещё один челлендж на стороне бэкенда — инструмент для работы диспетчера. Ему нужно видеть список всех актуальных заказов на вертолёты, чтобы назначать пилотов.

У нас есть огромная MongoDB, где хранятся заказы за последние двое суток — в среднем это около 37 миллионов записей. Фильтровать этот поток по тарифу «Вертолёт» прямым запросом в базу — безумие, потому что это просто убьёт продакшн. Строить отдельный индекс в Mongo только для вертолётных заказов — слишком дорого и нерационально, учитывая, что база под постоянной нагрузкой на запись и удаление.

В результате мы нашли третий путь через DWH, это — система для хранения и управления большими объёмами данных, которые объединяются из разных источников, чтобы анализировать их и строить отчёты. У нас есть сервис репликации, который переливает данные из Mongo в DWH для аналитиков. Да, там есть лаг репликации порядка 10–15 минут. Но для вертолётов, которые заказывают минимум за пару часов (а чаще всего вообще за сутки), задержка в 15 минут не играет никакой роли.

06.png

Мы сделали самописный индекс поверх DWH. Диспетчер получает данные не из горячей базы, а из хранилища. А чтобы диспетчеру не приходилось скучать и обновлять страницу в ожидании заказов, прикрутили простую нотификацию в Telegram.

Кстати, с уведомлениями возник забавный нюанс. Сразу после релиза к нам буквально потоком пошли любопытные пользователи: они открывали тариф, делали заказ, смотрели, как это выглядит, и сразу отменяли. Диспетчеры чуть не сошли с ума от такого спама. Пришлось добавить таймер: уведомление уходит только через несколько минут после создания заказа. Если за это время его не отменили — значит, намерения серьёзные.

Приложение пилота

Хорошо, с пассажирским приложением разобрались, но оставался второй, не менее важный вопрос: а что видит пилот? Тут мы снова столкнулись с дилеммой.

Словосочетание «новый вид транспорта» звучало как однозначная необходимость писать всё с нуля. Потому что ну где вообще такси и где вертолёты. Но если посмотреть на процесс абстрактно, то выясняется, что задачи пилота вертолёта и водителя такси в целом очень похожи и все они так или иначе сводятся к тому, что нужно доставить пассажира из точки А в точку Б. Разница лишь в переменных среды: вместо дорог — воздушные коридоры, вместо пробок — погодные условия, вместо парковок — вертолётные площадки.

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

Межгородской шаттл

Обычный предзаказ такси мы взять не могли, потому что там совершенно неподходящая для наших целей механика. Предзаказ работает как отложенный поиск: за 30 минут до подачи система начинает разыгрывать заказ среди всех доступных исполнителей поблизости. Нам нужно было, чтобы рейс был закреплён за конкретным бортом и пилотом за 24 часа, а не за 30 минут.

Но тут очень кстати оказалось то, что у нас есть тариф «Межгород» и механика «Шаттл» — микроавтобусы между городами. Этот тариф позволяет пассажирам создавать заказы в маршрутных микроавтобусах, которые совершают длинные межрегиональные поездки. Пассажиры создают предзаказ, водитель его получает и может одновременно везти до 7 пассажиров, в зависимости от количества свободных мест в микроавтобусе.

07.png

Всё, что оставалось сделать, — просто взять и переиспользовать эту механику. Пилот получает список заказов, они выстраиваются в хронологическую последовательность, и система понимает, что это не случайные поездки, а спланированный маршрут.

В механике Межгорода есть важная архитектурная особенность: там нет привычного для такси автоматического назначения на ближайшую машину. Заказы попадают в общий список, доступный всем исполнителям, и водитель должен сам выбрать конкретный рейс. Чтобы обойти это ограничение, мы реализовали в админке эндпоинт, который эмулирует выбор водителя. Для бэкенда событие выглядит так, будто пилот сам нажал кнопку в приложении, хотя фактически назначение сделал за него диспетчер.

Адаптация флоу заказа

Взяв механику шаттла, мы доработали поведение самого заказа под реалии полёта:

  • Автопринятие. Пилоту некогда тыкать в экран, когда он занят предполётной подготовкой или находится в воздухе. Заказы принимаются автоматически и затем прорастают в приложение с пуш-уведомлением.
  • Отключение гео-валидации. В такси есть защита: нельзя поменять статус заказа, если GPS показывает, что ты далеко от точки посадки или высадки пассажира. Вертолёт же, из-за более жёстких ограничений, может приземлиться на выбранный аэродром, но не на конкретную точку на этом аэродроме. Поэтому мы отключили эти проверки, доверившись пилоту.
  • Бесплатное ожидание. В отличие от такси, где ожидание на промежуточной точке почти всегда платное, в обзорных экскурсиях мы заложили 2 часа бесплатного ожидания. Это время для пассажира, чтобы выйти, погулять где ему нужно и вернуться.

Вертолёт — это просто очень быстрая машина

Создание нового типа исполнителя и нового типа транспортного средства в нашей системе — это тоже небыстрый процесс, который обычно занимает месяцы работ, со всеми проверками и настройками. Здесь, как вы понимаете, нам тоже пришлось пойти на хитрость.

В нашей системе вертолёт — это просто машина. Новая марка и модель автомобиля. А пилот — это водитель такси. Да, звучит как костыль, но в рамках MVP это отличное решение. Единственное, чем пришлось пожертвовать — это автоматическим фотоконтролем. Наши ML-модели умеют находить царапины на бампере Hyundai Solaris, но они пока что понятия не имеют, как должен выглядеть исправный Robinson R66. Поэтому проверку документов и состояния борта мы полностью делегировали партнёру.

Убираем лишнее в интерфейсе

Приложение для пилотов сейчас — это то же самое Яндекс Про, которым пользуются таксисты, но изрядно переработанное. Всё потому, что пилоту не нужны 90% функций, доступных водителю. Мы очень плотно прошлись по конфигам и отключили всё лишнее:

  • Навигация. Мы умеем строить маршруты по дорогам, учитывать пробки и ремонты. Но мы не умеем строить маршруты по рельефу местности и бесполётным зонам. Пилоты летают визуально или по своим приборам, поэтому навигатор в приложении им только мешает.
  • Слои на карте. Пробки, ДТП, камеры, парковки — всё это отключили. Оставили чистую карту.
  • Виджеты и сервисы. Заправки, мойки, зарядки, маркетплейс, цели по заработку, кнопка «По делам» — всё это неактуально на высоте 300 метров.
  • Анимация. С технической точки зрения карте всё равно, что именно движется — машина, велосипед или вертолёт, для движка это просто курсор с координатами. Но в жизни разница огромная: средняя скорость потока в городе 40–60 км/ч, а вертолёт летит под 300 км/ч. Стандартные алгоритмы сглаживания настроены на наземный трафик и поэтому не справлялись с такими быстрыми изменениями координат, из-за чего иконка на карте двигалась рывками. Нам пришлось перенастроить параметры локации, чтобы научить курсор плавно плыть по экрану даже на авиационных скоростях.
08.png

Получилось ли у нас?

Конечно получилось! В итоге команда получила не просто работающий MVP, а полноценный продукт. Пилот видит в своём приложении чистую карту и понятную очередь заказов, диспетчер управляет флотом через админку, а пассажир бронирует полёт над горами так же привычно, как поездку до офиса. И всё это — без года разработки и переписывания ядра платформы.

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

На первый взгляд может показаться, что мы построили решение из компромиссов. Но на самом деле это история именно про гибкость архитектуры. Потому что с технической точки зрения мы не создали костылей или критичного техдолга: большая часть логики живёт в конфигурационных файлах, а не зашита в код хардкодом. Сторонний разработчик, заглянув в наш репозиторий, может даже не понять, что система настроена под авиацию — для кода это просто очередной набор параметров.

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

  • backend
  • management

Будьте в курсе всех событий