Привет! Меня зовут Асхат Нурыев, я ведущий инженер по автоматизации в компании DINS.

Я работаю в Dino Systems последние 7 лет. За это время пришлось заниматься разными задачами: от написания автоматических функциональных тестов до тестирования производительности и высокой доступности. Постепенно я стал больше заниматься организацией тестирования и оптимизацией процессов в целом.

В этой статье я расскажу:

  • Что делать, если баги уже просочились на продакшн?
  • Как побороться за качество системы, если ошибок уже руками не счесть и глазами не пересмотреть?
  • Какие подводные камни встречаются при автоматической обработке ошибок?
  • Какие бонусы можно получить от анализа статистики запросов?

DINS — это центр разработки компании RingСentral, лидера рынка среди облачных провайдеров Unified Communications. RingСentral предоставляет все для бизнес-коммуникаций начиная с классической телефонии, смс, митингов и заканчивая функциональностью контакт-центров и продуктов для сложного командного взаимодействия (а-ля Slack). Это облачное решение расположено в собственных дата-центрах, и клиенту нужно только оформить подписку на сайте.

Система, в разработке которой мы участвуем, обслуживает 2 млн активных пользователей и обрабатывает более 275 млн запросов в сутки. Команда, в которой я работаю разрабатывает API.
У системы довольно сложный API. С его помощью можно отправлять смс, совершать звонки, собирать видеоконференции, настраивать аккаунты и даже отправлять факсы (привет, 2019 год). В упрощенном виде схема взаимодействия сервисов выглядит как-то так. Я не шучу.



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

Сперва мы пытались решать проблему классическим образом.
Мы собирались, запрашивали логи с продакшена, что-то исправляли, что-то забывали, создавали дашборды в Kibana и Sumologic. Но в целом это не помогало. Баги все равно просачивались, пользователи жаловались. Стало ясно, что-то идет не так.

Автоматизация


Конечно, мы стали разбираться и увидели, что 90% времени, которое уходит на исправление ошибки, тратится на сбор информации по ней. Вот на что конкретно:

  • Получить недостающую информацию от других департаментов.
  • Изучить журналы серверов.
  • Исследовать поведение нашей системы.
  • Понять, ошибочно ли то или иное поведение системы.

И только оставшиеся 10% мы тратили непосредственно на разработку.

Мы подумали — а что, если сделать систему, которая сама найдет ошибки, проставит им приоритет и покажет все нужные для исправления данные?

Надо сказать, что сама идея такого сервиса вызывала некоторые опасения.
Кто-то говорил: «Если мы найдем все баги сами, то зачем нам QA?».
Другие говорили обратное: «Вы утонете в этой куче багов!».
Одним словом, сделать сервис стоило хотя бы для того, чтобы понять, кто из них прав.
спойлер
(ошиблись обе группы скептиков)


Готовые решения


В первую очередь мы решили посмотреть, какие из аналогичных систем уже есть на рынке. Оказалось, что их немало. Можно выделить Raygun, Sentry, Airbrake, есть и другие сервисы.
Но ни один из них нас не устроил, и вот почему:

  • Одни сервисы требовали от нас слишком больших изменений существующей инфраструктуры, в том числе изменений на сервере. Для Airbrake.io пришлось бы дорабатывать десятки, сотни компонентов системы.
  • Другие собирали данные о наших собственных ошибках и отправляли их куда-то на сторону. Наша политика безопасности не позволяет такого — данные о пользователях и ошибках должны оставаться у нас.
  • Ну а еще они стоят довольно дорого.


Делаем свое


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

  • Все сервисы уже писали логи в единое хранилище — Elastic. В логах были прокинуты единые идентификаторы запросов через все сервисы.
  • Статистика исполнения дополнительно записывалась в Hadoop. Мы работали с логами с помощью Impala и Metabase.

Из всех ошибок сервера (по классификации HTTP статус кодов), код 500 — наиболее перспективен с точки зрения анализа ошибок. В ответ на ошибки 502, 503 и 504 можно в некоторых случаях просто повторить запрос через какое-то время даже не показывая ответ пользователю. А согласно рекомендациям к RC Platform API, пользователи должны обращаться в службу поддержки, если получают статус код 500 в ответ на вызов.

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

Сразу после тестового запуска мы заметили, что система создает значительное количество ошибок-дубликатов. Однако среди этих дубликатов многие имели почти одинаковые стек-трейсы.

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

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

В итоге мы выпустили новую версию, в ней ошибки идентифицировались по этим уникальным шаблонам, если стек-трейсы были доступны. А если не были доступны — то по-старому, по http-методу и группе API.

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

Следующий шаг — понять, как приоритизировать ошибки, какие из них нужно исправлять раньше. Мы приоритизировали по:

  • Частоте появления ошибки.
  • Числу пользователей, которых она касается.

На основе собранной статистики мы стали публиковать недельные отчеты. Выглядят они вот так:



Или вот, к примеру, топ-10 ошибок за неделю. Занятно, что на эти 10 багов в jira приходилось 90% ошибок сервиса:



Такие отчеты мы рассылали разработчикам и тимлидам.
Через пару месяцев после того, как мы запустили систему, количество проблем стало ощутимо меньше. Даже наше небольшое MVP (минимально жизнеспособный продукт) помогло лучше разгребать ошибки.

Проблема


Возможно, н�� этом мы бы и остановились, если бы не одна случайность.
Однажды я пришел на работу и заметил, что система клепает баги как горячие пирожки: один за другим. После короткого расследования стало ясно, что десятки этих ошибок — с одного сервиса. Чтобы узнать в чем дело я пошел в чатик команды деплоя. В нем сидели ребята, которые занимались установкой новых версий сервисов на продакшн и следили за тем, чтобы они работали как полагается.
Я спросил: «Ребята, что такое, что случилось с этим сервисом?».
А они отвечают: «Час назад мы установили туда новую версию».
Шаг за шагом мы выявили проблему и нашли временное решение, проще говоря, перезапустили сервер.

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

И мы решили сделать еще одну итерацию разработки.

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



А вот так — ошибки в отдельных сервисах.



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



Ошибки клиентов


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

Троттлинг — это способ защиты системы от перегрузок. Если приложение или пользователь превышают свою квоту запросов, система во��вращает код ошибки 429 и заголовок Retry-After, в значении заголовка указывается время, через которое запрос нужно повторить для успешного выполнения.

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


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

Вот так выглядит мониторинг ошибок троттлинга. Пики ошибок соответствуют будним дням, а в выходные — наоборот, ошибок нет:



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

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

Для каждого такого приложения мы:

  • Создаем задачку в JIRA.
  • Записываем статистику в Influx.
  • Готовим триггеры для оперативного вмешательства в случае резкого увеличения числа ошибок.

Система работы с клиентскими ошибками выглядит так:



Раз в неделю мы собираем отчеты с топ-10 худших приложений по числу ошибок.

Не ловить, а предупреждать


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

Но по сути, мы реагируем слишком поздно — баги уже влияют на работу пользователей!

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

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

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

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

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

Таким образом, мы построили прообраз системы, заведения и отслеживания ошибок. Уже в текущем виде она позволяет улучшить качество системы, замечать и исправлять ошибки раньше, чем пользователи о них узнают. Если раньше мы обрабатывали десятки тысяч ошибочных запросов в неделю, то сейчас — всего 2-3 тысячи. И исправляем мы их значительно быстрее.

Что дальше


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

  • Анализ большего числа ошибок API.
  • Интеграция с функциональными тестами.
  • Дополнительные возможности для расследования инцидентов в нашей системе.

Но об этом — в следующий раз.