Инструменты надежности Яндекс Такси

  • Александр Фишер
    Александр Фишер

    Александр Фишер

    Руководитель службы надежности

Всем привет! Меня зовут Саша Фишер, я руководитель службы надежности Яндекс Такси. В статье расскажу про инструменты, которыми мы обеспечиваем бесперебойную работу сервиса, в том числе при повышенных нагрузках и в сезонные пики.

Высокий сезон — время, когда нагрузка может повышаться в несколько раз. Конечно, самый топ — Новый год. Это период с 22:00 31 декабря до 3:00 1 января. Чтобы пережить эти пять часов, мы готовимся весь год. Кстати, в период с 1 по 6 января — наоборот, всё гладко и тихо. Но кроме Нового года, в декабре есть и другие часы повышенной нагрузки — так называемые корпоративные пятницы. Это последние три пятницы декабря с 17:00.

Как устроено Такси

Прежде всего, Яндекс Такси — это инфраструктура городов, в первую очередь миллионников. Такси — такая же часть транспортной доступности в городе, как троллейбусы и метро.

У нас микросервисная архитектура: больше 800 микросервисов и 500 баз данных. 90–99% нашей инфраструктуры размещено в облаках. На точках входящего трафика RPS примерно 600 тысяч.

Наши языки и фреймворки:

  • userver (С++) — фреймворк сердца Такси, внутри него большой запас надежности и отказоустойчивости;
  • FastCGI2 (C++) и py2 (Python) — legacy-фреймворки, которые не развиваются, мы туда ничего не пишем;
  • backend-py3 (Python 3) — живой фреймворк;
  • Goliath (Golang) — будущий фреймворк на Golang, который мы планируем массово внедрять;
  • MJ Kotlin — потенциально возможный фреймворк на Kotlin.

Метрики надежности

Чтобы что-то улучшить — это нужно измерить. А чтобы что-то измерить — нужно понимать, что и как мы измеряем. У нас 4 основных метрики:

1. Availability SLO (Service Level Objective)

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

2. MTTR (Mean Time To Recovery)

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

В инцидент-менеджменте все случаи сбоев прокрашиваются статистикой, мы делим их на мажорные и минорные. Лежали полчаса и больше — мажор. Лежали всего пять минут — минор. К мажорам и минорам разное отношение, у них разные процессы инцидент-менеджмента и приоритизация. Если клиент не может заказать такси в течение пяти минут, он перезакажет его через шесть минут и уедет — столько можно подождать. Если же такси везде не работает час — это серьезная проблема, причем на уровне города. А так как Яндекс Такси — провайдер сервисов для Яндекс Доставки, Яндекс Лавки и так далее, негативные последствия мультиплицируются.

Фокус на метрике MTTR мотивирует нас быстро подниматься. Fail fast: упали — отжались — поднялись.

3. MTTRC (Mean Time To Root Cause)

Среднее время нахождения корневой причины. Во-первых, по этой метрике мы понимаем, насколько хорошо у нас работает observability. Если корневую причину находим быстро — observability хорошее. Если не так быстро — плохое.

В пятидесятом перцентиле метрика равна 7 минутам. То есть 7 минут — медиана нахождения корневой причины по мажорным инцидентам примерно с июля 2023 года. Но в девяностом перцентиле — это примерно тридцать минут! Наша цель — сократить нахождение корневой причины в девяностом перцентиле до трех минут.

4. ROCOF (Rate of Occurrence of Failures)

Последняя метрика — количество инцидентов. Она показывает, насколько хорошо мы сделали свою домашку. Если мы проактивно поработали над причинами инцидентов — они не произойдут и метрика улучшится.

Не поверите, но метрики сложно улучшать... из-за сложности. Когда у тебя 860 микросервисов, а тут внезапно происходит инцидент — нахождение причины и починка выглядят примерно так:

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

Инструмент 1: хаос

У нас в команде надежности есть специальные люди, которых мы называем «хаоситы». Рекомендую посмотреть их доклад с Яндекс Go Infra Meetup #2: «Сломай меня полностью. Chaos Engineering». Его авторы Максим Ивашковский и Андрей Матвеев — те программисты, которые этот хаос и писали.

Чего мы достигаем, создавая хаос:

  • находим критичные сервисы;
  • находим неявные зависимости между некритичными и критичными сервисами;
  • тестируем согласованность тайм-аутов в цепочке сервисов;
  • увеличиваем знание о поведении системы при ошибках.

Наш хаос — не классический вариант Netflix, который выключает pod или сервер. Он делает fault и error injection. У нас больше 800 сервисов, и мы хотим знать, какие из них действительно критичны.

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

На текущий момент у нас три базовых сценария хаоса:

  1. Управляемое замедление сервиса — такое, чтобы он укладывался в deadline propagation timeout.
  2. Имитация метастабильного состояния. Метастабильное состояние — это когда ваша система была так неустойчива, что для ее падения хватило одной соломинки, и после падения она не поднимается. Трафик приходит, но система не восстанавливается, уже не выдерживает.

    Смотрите доклад, чтобы узнать больше о метастабильных состояниях

    Мы смотрим, как вся система себя поведет. Есть, например, микросервис, который отдает кандидатов на заказ: пассажир заказал такси, и сервис под него находит двадцать водителей. Что будет при отказе этого сервиса? Система не будет работать.
  3. Отдача от имени сервиса 500. При этом мы умеем управлять процентом отдаваемых 500-x ошибок, например пишем в конфиге хаоса: пусть 1% всех запросов отдает пятисотки.

Это то, как хаос реализован технически. Есть авторизационный прокси, через который мы прокидываем контекст с помощью HTTP-заголовка baggage (это стандарт W3С) и включаем хаос.

Дальше работает наш плагин на Lua к nginx. Через него мы всё и делаем.

У нас есть подобие control plan, сейчас не оформленное никакой админкой: ни GUI, ничего подобного. Это система, которая кладет JSON-файл в каждый сервис в pod’ы в нашем облаке. В этих файликах написаны сценарии того, как и что делать. На схеме эта часть расположена сверху справа.

А справа снизу — observability. Мы посылаем статистику, чтобы знать, как у нас включен хаос, какие там метрики. Всё сделано просто.

Как мы планируем развивать хаос в 2024 году:

  • создать сервис control-plane для настройки расписания и процентовки инъекций;
  • создать админку для управления этими настройками и вручить ее командам сервисов, чтобы за учения каждого отвечала команда, которая его пишет;
  • проводить регулярные учения с инъекциями по расписанию;
  • делать иррегулярные инъекции по усмотрению владельцев;
  • добавлять новые сценарии.

Инструмент 2: срезание нагрузки

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

Degradation mode

Первый способ срезания нагрузки — деградация сервиса, так называемая graceful degradation, когда есть потребность именно снизить нагрузку.

Мы реализуем его так:

  • отключаем анимацию поиска;
  • отключаем мультизаказы (мультизаказ — возможность сделать сразу несколько заказов для разных точек и разных клиентов: ваших друзей, родных);
  • отключаем в базе mongo write concern (MongoDB — наша главная БД);
  • повышаем интервалы поллинга в разных частях;
  • срезаем ретраи на критичных ручках;
  • отключаем постановку части задач в очереди.

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

Деградация может идти в двух режимах: мягком и тяжелом. На практике почти всегда при серьезном инциденте мы сразу включаем тяжелую деградацию и увеличиваем емкость примерно на 20%. Наша цель — +50%.

Retries, RPS limits

Второй вариант срезания нагрузки — управление ретраями — родился из амплификации. Когда система легла и вошла в метастабильное состояние, клиенты начинают ретраить запросы постоянно. Этим они создают амплификацию X10, при которой подняться уже невозможно: нет железа, чтобы выдержать такую нагрузку.

Ретраями нужно управлять, и управляем мы ими с помощью HTTP-заголовков. Причем они поддерживаются как на клиентах — то есть в Яндекс Go и в водительском продукте Яндекс Про, так и в ручках на бэкенде.

Есть четыре стратегии:

  1. После N-ного ретрая используется последний тайм-аут из списка.
  2. Exponential back-off, когда мы ретраимся с постоянным замедлением: 500 мс, 1000 мс, 2000 мс...
  3. Остановка после третьего ретрая (Retry-Action:stop).
  4. Полный запрет на ретраи (при жесткой деградации).

Есть и глобальная стратегия ретраев, и отдельные настройки в каждой критической ручке сервисов. Все наши 860 микросервисов разделены на тиры: A, B и C.

  • Tier A — сервисы, без которых Такси не работает. Если лежит любой из этих сервисов — всё, такси конец, приехали.
  • Tier B — сервисы, без которых мы можем поработать 15–20 минут. При инциденте они приведут к деградации бизнес-показателей и заказов не сразу, а минут через 20–30.
  • Tier C — сервисы типа «Цвет машины». Если мы его отключим, то продолжим работать дальше.

Инструмент 3: виртуальные заказы

Виртуальные заказы — это не классическое нагрузочное тестирование. В классическом исполнении мы генерируем тысячу RPS в какой-то эндпойнт с каким-то HTTP::payload. Запускаем Apache Bench или Yandex Tank — и вуаля: мы создали нагрузку X10.

Но если система не монолит, а микросервисная — такая нагрузка не показывает ее емкость, не показывает взаимодействий между ее частями. И если ты нагрузил одну ручку — это совершенно не означает, что другая ручка выдержит у тебя этот X10.

Емкость в Такси — количество заказов в системе, причем количество заказов в разной стадии: предзаказы, поиск водителя, пассажир едет, пассажир завершает заказ. Все эти заказы живут внутри, и есть четкое количество, которое мы можем переварить. И виртуальные заказы тестируют именно емкость, нагрузку на всю систему.

При подготовке к высокому сезону у нас было всего 70 учений, из них 22 — учения виртуальных заказов. Мы нашли все слабые места: тут нет RPS-лимитов, здесь не хватает процессора, здесь сервис начинает пятисотить по неизвестной причине при нагрузке X2... Расставили таски, всё пофиксили. Снова запустили, снова дали нагрузку. И так двадцать два раза, чтобы Новый год прошел без сучка и задоринки.

Выше схема реализации виртуальных заказов. Наша платформа соединяет людей, которые хотят уехать («Пользователи»), и людей, которые готовы их подвезти («Водители»).

Поэтому у нас есть симулятор водителей, где живут сгенерированные водители с именем и фамилией, например «Робот Давидович», и есть таксопарки, к которым эти водители привязаны. Есть виртуальные пассажиры и диспетчер, VirtualOrdersDispatcher, который соединяет их с водителями.

В схеме учитывается тариф. Разные тарифы дают разную нагрузку, потому что поиски Maybach и Kia Rio разные.

Итак, мы говорим системе: сгенерируй нам, пожалуйста, тысячу заказов в секунду. Сервис создает виртуальных водителей, которые едут по реальным историческим маршрутам, предзагруженным раньше. Они едут с реальной скоростью, например, по тарифу «Эконом», — скажем, 60 километров в час. А мы смотрим, как это работает.

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

Инструмент 4: eventboard/observability

Eventboard — часть observability, это класс софта, который показывает все изменения на продакшне.

Видеть изменения важно, потому что до 50% инцидентов — последствия наших релизов. Мы выкатили какой-то конфиг или релиз, и стало хуже. Эмпирически определилось, что у нас имеет место rate of change: надежность обратно пропорциональна количеству изменений, которые мы внесли в систему. Если мы не видим эти изменения в удобном простом виде, их можно искать очень долго.

Посмотрим подробнее на observability. Это наш главный дашборд, классическая Grafana. У него простая цель — за десять секунд понять, где проблема. Все картинки иллюстрируют воронку заказов: созданные заказы, найденные водители, завершенные заказы... Главная метрика (по центру) — отношение назначенных заказов к созданным. 100% она достигает редко, но в норме всегда выше 90%.

На картинке выше так называемый иконостас. Те самые 36 сервисов Tier A, без которых Такси не может работать, и их SLO. Если эти кубики желтенькие или красненькие, значит, что-то не так, пора чинить.

Eventboard — лента событий на продакшне. Кроме того, что мы видим события, у нас есть фильтры. Мы можем выбрать конкретные события и прямо отсюда включить режим деградации.

Справа от молнии есть кнопочка массового отката. Если мы считаем, что к инциденту могла привести любая выкатка, то откатываем всё в течение пяти минут во время инцидента, не рефлексируя. Это сильно сокращает нам время и улучшает метрику MTTR.

Инструмент 5: симуляции инцидентов

Есть и нетехнические средства обеспечения надежности. Инцидент-менеджмент работает, но у нас не так много происшествий, и это, что удивительно, нам мешает. Из-за нехватки инцидентов мы не всегда понимаем, что надо загодя чинить, где стоит проактивно копать, что нужно превентивно улучшить. Приходится создавать инциденты своими руками. Этим заняты две группы людей.

  • Координаторы — люди, которые во время инцидентов в Такси распределяют задачи и управляют процессом. В основном это тимлиды разработки, которые отвечают за какой-то компонент: водителей, клиентов, эффективность. Это опытные разработчики, которые неплохо знают, как работает Такси, и у них есть представление о системе в целом.
  • Группа SRE-эксплуатации. Люди, отвечающие, в числе прочего, за инфраструктуру. Есть интерфейс, где рандомный алгоритм выбирает, кто сегодня ломает Такси, а кому придется чинить. Симуляции происходят каждую неделю. Тот, кто чинил на этой неделе, ломает на следующей. Мы случайным образом выбираем одного координатора и одного SRE от эксплуатации, они всегда работают в паре.

Когда человек что-то ломает, мы смотрим на метрики. График выше — динамика MTTR и MTTRC. Не очень хороший график, сейчас там среднее — примерно 30–40 минут, потому что в симуляции не прекращается гонка вооружений. Чем лучше наши инструменты observability, тем больше те, кто ломает, пытаются эти инструменты обойти или выключить.

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

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

Инструмент 5½: Moonshots

Moonshots — венчурные проекты, в которые мы инвестируем и на которые делаем ставку, но мы не уверены, что они сработают. Потому что пока еще мы нигде не видели их в действии.

Autorecovery

В какой-то момент стало понятно, что есть предел скорости починки Такси человеком — примерно 10–15 минут с момента поломки. Человеку о поломке сообщил робот, сотрудник дошел до ноутбука, залогинился, посмотрел графики, нашел поломку, починил. 10–15 минут — число для топовых координаторов. В разгар рабочего дня можно управиться и быстрее, но в среднем это так. Получаем, что оптимизировать метрику MTTR, среднее время починки, невозможно, если мы делаем ставку на людей. Мы сделали ставку на робота, который принимает решение сам, — Autorecovery.

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

SreGPT

Второй инструмент — SreGPT. Он нужен для улучшения метрики MTTRC, среднего времени нахождения причины. Всё просто: мы хотим скармливать YandexGPT все наши метрики и логи, чтобы дальше нейросеть сказала: «С вероятностью 80% корневая причина проблемы — в таких-то базе, сервисе и конфиге».

Здесь мы на стадии очень-очень раннего RnD. Пока не могу сказать, сработает это у нас или нет, но глубоко убежден, что через пару лет это станет стандартом индустрии.

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