Привет! Меня зовут Асхат Нурыев, я ведущий инженер по автоматизации в компании 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.
  • Интеграция с функциональными тестами.
  • Дополнительные возможности для расследования инцидентов в нашей системе.

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

Комментарии (8)


  1. Vahman
    29.10.2019 22:58

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


  1. askhaan Автор
    30.10.2019 16:21

    Можете, пожалуйста, подробнее описать как групировали схожие stacktrace? Это вручную делалось, или есть какая-то эвристика помимо регулярок?

    Практически все стек трейсы, которые мы обрабатываем выброшены java сервисами. Как следствие — имеют похожий формат.
    Алгоритм обработки примерно такой:

    1. Все полученные стек трейсы разбиваем построчно.
    2. В каждой строке находим токен содержащий java package и унифицируем его формат. То есть из java.lang.NullPointerException получается j.l.NullPointerException.
    3. Далее для каждой строки применяем регулярки, чтобы избавиться от номеров строк и прочего шума.
    4. Стек трейсы сравниваем построчно на первые N строк.

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

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

    Насколько меньше багов стало долетать до прода, в процентах, за более или менее продолжительное время?

    Если говорить об ошибках, которые отслеживает наш сервис, то за последние полгода статистика такая:

    • половина всех исправленных багов найдена на интеграционных окружениях
    • но 60% созданных там багов не связанны с функциональностью и закрываются без модификации кода

    То есть шума все же достаточно много, но возможность исправить ошибку раньше того стоит.


    1. Vahman
      30.10.2019 17:21

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

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


      1. askhaan Автор
        31.10.2019 11:05

        Согласен с вами, с номерами строк эта логика работает, но на практике вычищать приходится намного больше всего — адреса ссылок, идентификаторы потоков, чисто пользовательские данные. А иногда летят эксепшены от базы данных, завернутые в java.

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


  1. ghost404
    31.10.2019 10:00

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


    1. askhaan Автор
      31.10.2019 12:08

      Sentry — интересная альтернатива, мы ее рассматривали, но у нее есть свои недостатки.
      Некоторые из них вызваны особенностями продакшн системы — в ней очень много разных сервисов. Чтобы безопасно использовать облачную версию Sentry нужно убедиться, что все сервисы вычищают пользовательские данные, и часть из них поправить. Даже для того, чтобы просто посылать логи в Sentry тоже требуется небольшая разработка.
      А self-hosted версия по функциональности очень сильно уступает платной облачной.

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

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

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

      В итоге получается более удобный и адаптированный под нужды разработки сервис.


      1. ghost404
        31.10.2019 15:34

        Даже для того, чтобы просто посылать логи в Sentry тоже требуется небольшая разработка.

        У нас Symfony и для нее есть готовый бандл, так что у нас с подключение Sentry вообще никаких проблем. Так же есть интеграция GitLab <=> Sentry. И нам вполне хватает стектрейса собранного Sentry для идентификации и исправления проблемы. Но в вашем случае конечно все гораздо сложнее с учетом вашей архитектуры и вам нужно значительно больше данных чем может собрать Sentry. Тут я не спорю.
        Я лишь хочу сказать, что Sentry очень хорошо подходит для не очень больших проектов. Так сказать, инструмент для старта.


        1. askhaan Автор
          31.10.2019 16:28

          Полностью согласен, для маленького проекта Sentry — то что нужно )