13 авг. 2024 г.·7 мин чтения

Exemplars OpenTelemetry для поиска медленных API-запросов

Exemplars OpenTelemetry связывают метрики задержки с трассировками, чтобы команда могла перейти от всплеска к точному медленному API-запросу и быстрее найти причину.

Exemplars OpenTelemetry для поиска медленных API-запросов

Почему всплески медленных API так трудно объяснить

Проблема с медленными API чаще всего возникает из-за нескольких плохих запросов, а не потому, что все запросы вдруг стали медленнее. Среднее время ответа 220 мс может выглядеть нормально, хотя один вызов checkout занимает 14 секунд и завершается тайм-аутом. Пользователь чувствует эти 14 секунд. Дашборд часто это скрывает.

Метрики показывают форму проблемы. Вы видите рост p95, дрожание error rate и обычный объём запросов. Это говорит, что что-то изменилось, но не какой именно запрос виноват. График может указать на 10:42:13, но не подскажет, что один клиент отправил слишком большой payload, один запрос к базе ждал блокировку или один downstream-сервис сделал три повтора.

Трассировки, наоборот, решают другую задачу. Одна трасса показывает полный путь запроса в деталях: gateway, app, cache, database, retry, а потом span, который съел 8 секунд. Это отлично, когда у вас уже есть нужная трасса. Самая сложная часть — найти её.

На загруженном API плохая минута может содержать тысячи запросов. Инженеры открывают инструмент трассировки, фильтруют по времени, выбирают несколько трасс и надеются, что одна из них выглядит достаточно плохо. Часто они сначала смотрят несколько нормальных запросов. Потом возвращаются к метрикам, сравнивают время вручную, угадывают, какой pod обслуживал трафик, и пробуют снова.

Метрики показывают тренды. Трассировки показывают историю. Нужны и те и другие, но сами по себе они не указывают друг на друга. Без прямой связи команды тратят время инцидента на поиски нужного запроса вместо проверки причины.

Exemplars OpenTelemetry закрывают этот разрыв. Они прикрепляют trace context к образцу метрики, который действительно повлиял на всплеск. Когда задержка растёт, вы перестаёте гадать и начинаете с реального запроса, который помогает объяснить график.

Что exemplars добавляют к обычным метрикам

Обычные метрики показывают, что что-то изменилось. Они не говорят, какой именно запрос это сделал. График задержки может за минуту прыгнуть с 120 мс до 2,4 с, но по самому графику всё равно приходится гадать.

Exemplar добавляет реальный образец к точке метрики. Представьте это как заметку, прикреплённую к одному bucket в histogram или к одному измерению. Эта заметка несёт trace или span context от настоящего запроса, чаще всего только trace ID и span ID.

Это делает график намного полезнее. Метрика по-прежнему сохраняет общую картину сервиса: трафик, error rate и задержку во времени. Трассировка показывает путь одного медленного запроса через ваш код и зависимости. Exemplar связывает одно с другим.

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

Чаще всего exemplars лучше всего работают в latency histogram. Bucket-и в histogram уже группируют запросы по длительности, поэтому прикрепить sample trace к медленному bucket очень естественно. Если bucket 2–5 секунд вдруг начинает быстро заполняться, exemplar может сразу показать трассировку из этого диапазона, вместо того чтобы заставлять вас искать среди тысяч span-ов.

Без exemplars отладка медленного API часто превращается в угадывание. Инженер открывает случайные трассы из той же минуты и надеется, что одна из них будет достаточно плохой, чтобы объяснить всплеск. Иногда это срабатывает, но часто только отнимает время и пропускает именно тот запрос, который поднял метрику.

С exemplars график становится конкретным. Вы по-прежнему используете метрики, чтобы следить за всем сервисом, но теперь у вас есть и прямой след от реального запроса, который повлиял на число на экране. Это и есть недостающее звено между «что-то стало медленнее» и «этот вызов auth-сервиса добавил 1,8 секунды».

Когда exemplars помогают больше всего

Exemplars особенно важны, когда график выглядит плохо, а причина прячется в небольшом наборе запросов. Если p95 или p99 растёт на три-четыре минуты, а потом возвращается обратно, средние значения размывают проблему. Обычная метрика может показать, что всплеск был. Exemplar может указать на одну трассировку, которая объясняет, что произошло.

Это особенно полезно, когда замедляется один endpoint, а остальной API выглядит нормально. Общий дашборд может показывать, что задержка медленно растёт, но проблема только в /checkout или /search. Без exemplars инженеры часто открывают случайные трассы и надеются на удачу. С exemplars можно перейти от bucket-а, который изменился, к реальному запросу из этого момента.

Редкие сбои — ещё один хороший сценарий. Тайм-ауты, которые случаются раз в несколько сотен запросов, легко тонут в шумных логах. В это же время может быть тысячи безобидных предупреждений и несколько несвязанных повторов. Exemplar убирает шум, потому что связывает плохую точку в метрике с trace ID, а не с догадкой.

Деплои дают похожую картину. Возможно, новый код добавляет лишнюю работу для пользователей с большими payload, с определённым заголовком или включённым feature flag. Большинство запросов по-прежнему быстрые, поэтому среднее почти не меняется. Хвост становится медленнее, и именно это в первую очередь чувствуют клиенты. Exemplars сильно упрощают сравнение трассировок до и после деплоя без разбора лишнего трафика.

Они также помогают, когда одна зависимость тормозит только один путь в коде. Представьте API с двумя способами собрать ответ. Один путь читает данные из cache и отвечает за 80 мс. Второй делает database query и иногда занимает 2,5 секунды. Если этот запрос к базе замедляется, всё приложение не сломано. Страдают только запросы, которые попали в эту ветку. Exemplar может привести прямо к трассе с длинным database span.

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

Как связаны метрика и трассировка

Когда приходит один API-запрос, приложение обычно делает два действия почти одновременно. Оно открывает trace span для этого запроса и измеряет, сколько времени занимает работа. Это разные сигналы, но OpenTelemetry может привязать их к одному и тому же запросу.

Trace span хранит идентичность запроса. В нём есть trace ID и span ID. Если /checkout занимает 2,4 секунды, этот медленный запуск получает свои идентификаторы с момента, когда начинается handler, и до момента, когда уходит ответ.

В то же время код записывает задержку в histogram metric. Histogram не хранит каждый запрос как отдельную строку. Он группирует наблюдения по bucket-ам, например 100 мс, 500 мс, 1 с и выше. Это делает графики быстрыми и недорогими, но при этом скрывает, какой именно запрос вызвал скачок.

Именно здесь помогают exemplars. Когда SDK записывает это наблюдение в 2,4 секунды, он может прикрепить активный trace context к одному sample в метрике. Histogram при этом остаётся обычным histogram, но один его sample теперь содержит указатель обратно на трассу. Это по сути заметка на точке метрики: «этот запрос попал сюда».

Ваша система телеметрии хранит обе части. Она сохраняет histogram data для графика и exemplar metadata рядом с точкой метрики. Она также хранит полную трассировку. Поскольку обе записи содержат один и тот же trace context, UI может потом связать их без догадок.

Что вы видите во время всплеска

Представьте, что p95 latency резко растёт на три минуты сразу после деплоя. На графике вы наводите курсор на всплеск и видите exemplar, прикреплённый к одной из точек с высокой задержкой. Вы нажимаете на него, и система открывает трассу именно этого медленного запроса.

Теперь можно посмотреть на дерево span-ов и увидеть, куда ушло время: в database query, вызов другого сервиса или цикл повторов.

Это и есть вся цепочка. Один запрос создаёт span. Тот же запрос записывает sample задержки. SDK помечает этот sample trace context. Backend сохраняет его. График позволяет перейти от метрики к трассе. Без этой связи отладка медленного API часто сводится к очень умному угадыванию.

Как настроить exemplars шаг за шагом

Безопасно внедрите exemplars
Внедряйте exemplars по одному сервису, не превращая observability в побочный проект.

Exemplars лучше всего работают, когда первый запуск маленький. Выберите один API-сервис, подключите там трассировку и метрики и пока не трогайте остальную систему. Если пытаться настроить всё сразу, обычно приходится бороться с конфигурацией вместо того, чтобы понять, работает ли связь.

Простой план выглядит так:

  1. Добавьте и трассировку, и метрики на один и тот же путь запроса. У запроса должен быть span и метрика длительности в одном сервисе, иначе SDK нечего будет связывать.
  2. Измеряйте задержку с помощью histogram. Counter может показать, сколько запросов было, но не показывает форму времени ответа и не помогает выделить медленный хвост.
  3. Сохраняйте trace context на всём протяжении запроса. Передавайте его через HTTP handlers, фоновые задачи, которые стартовали от этого запроса, и обращения к базе данных. Если контекст теряется на середине, график всё ещё может показать всплеск, но exemplar не приведёт к нужной трассе.
  4. Отправляйте оба сигнала в систему, которая умеет хранить и показывать exemplars. Некоторые инструменты принимают метрики и трассы, но игнорируют связь между ними. Проверьте поддержку в collector, слое хранения и интерфейсе графиков до того, как потратите часы на настройку кода.
  5. Проверьте всё на одном специально медленном запросе. Добавьте короткую задержку в staging или вызовите маршрут, который уже знаете как медленный. Затем откройте график задержки, найдите всплеск и нажмите на marker exemplar. Если открывается подходящая трассировка, настройка работает.

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

Простой пример из реального инцидента

API поиска товаров обычно завершал работу примерно за 180 мс, поэтому команда считала его стабильным. Но в 9:10 p99 latency, то есть самые медленные 1% запросов, выросла выше 3 секунд и держалась так около десяти минут.

Если смотреть только на график задержки, такой всплеск легко вызывает неверные догадки. Люди начинают спрашивать про CPU, сетевые задержки, промахи cache или какой-то странный шум в облаке. На такие догадки легко потратить час.

На этот раз на графике были включены exemplars. В latency histogram bucket от 3 секунд и выше был exemplar, прикреплённый к одному из плохих запросов. Этот маленький маркер изменил всё расследование.

Инженер нажал на exemplar и открыл точную трассу, стоящую за всплеском. Трасса рассказала понятную историю. HTTP span сначала выглядел нормально, но один database span занял почти все 3,2 секунды.

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

Без этой трассировки команда, скорее всего, сначала проверяла бы не то: графики CPU на pod-ах API, сетевое время между сервисами, недавние деплои вне search-сервиса или поведение autoscaling. Вместо этого они исправили запрос, добавили нужный index и изменили логику фильтра, чтобы она больше не создавала такой дорогой шаблон. Задержка почти сразу вернулась к обычному уровню.

В этом и смысл связи метрик с трассировками. Метрика сказала команде, что пользователи испытывают проблему. Exemplar указал на один реальный запрос. Трассировка показала span, который это вызвал. Никаких догадок, никаких поисков виноватого и никакой пустой траты времени на инфраструктуру, которая была в порядке.

Ошибки, которые ломают связь

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

Большинство команд теряют exemplar-данные на этапе инструментирования, а не в дашборде. График задержки может прыгнуть с 180 мс до 2,8 с, но у всплеска не будет прикреплённой трассы, потому что приложение записало метрику после того, как request span уже завершился.

Эту первую ошибку легко пропустить. Запрос создаёт span, делает работу, закрывает span, а уже потом записывает latency metric в коде очистки. Число по-прежнему попадает на график, но exemplars не могут прикрепить трассу, если активного span уже нет. Записывайте длительность запроса, пока span ещё текущий.

Тип метрики тоже может мешать. Summary выглядят просто, но многие системы не могут нормально прикреплять exemplar sample к ним. Histogram работает лучше, потому что хранит задержку по bucket-ам. Когда один bucket внезапно наполняется медленными запросами, можно посмотреть прикреплённые sample traces вместо того, чтобы гадать.

Имена маршрутов создают проблемы чаще, чем кажется. Если один релиз отправляет GET /orders/{id}, а следующий — orders.show, график разбивается на разные series. Сырые пути ещё хуже, потому что /orders/48192 и /orders/48193 становятся отдельными labels. Оставляйте названия маршрутов стабильными и используйте шаблоны, а не путь каждого отдельного запроса.

Trace sampling тоже может стереть следы. Команды часто уменьшают sampling, чтобы сэкономить, а потом удивляются, почему у сильного замедления нет трассы. Если вы оставляете только 1 из 1000 трасс, редкий медленный запрос может вообще не попасть в storage. Оставляйте достаточно sampling для медленных запросов и ошибок или используйте правила, которые сохраняют необычные трассы после завершения.

Labels тоже могут испортить данные. Если добавить user IDs, session IDs или cart IDs в метрики запросов, получится слишком много уникальных series. Стоимость растёт, графики шумят, а exemplar sample расползаются по крошечным кускам трафика. Обычно лучше работает маленький набор labels: имя сервиса, шаблон маршрута, HTTP method и status code или status class.

Когда метрики и трассировки перестают совпадать, сначала проверьте время span, тип метрики, именование маршрутов, sampling и labels. В отладке медленного API именно эти пять проверок решают проблему чаще, чем любая настройка backend.

Быстрые проверки перед тем, как доверять данным

Найдите настоящую причину
Найдите медленные database spans, повторы запросов и неудачные пути обработки до того, как они отнимут ещё больше времени.

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

Начните с самого простого теста. Отправьте один запрос, который, как вы знаете, займёт больше обычного, затем проверьте, что приложение создаёт span для этого запроса и записывает тот же запрос в latency histogram. Оба сигнала должны идти из одного и того же пути выполнения. Если span создаёт middleware, а метрику — другой timer, связь часто ломается.

Потом проверьте labels на метрике. Названия маршрутов должны оставаться стабильными между деплоями. Label вроде /users/123/orders/456 быстро создаёт шум. Label вроде /users/:id/orders/:orderId сохраняет график читаемым и позволяет сравнивать всплески во времени, а не начинать заново после каждого релиза.

Полезен короткий чек-лист. Вызовите один заведомо медленный запрос и убедитесь, что он создаёт и trace span, и одно histogram observation. Проверьте, что label маршрута использует один и тот же шаблон до и после деплоя. Убедитесь, что trace sampling сохраняет медленные запросы достаточно часто, чтобы потом их можно было изучить. Откройте график задержки и проверьте, что exemplar markers появляются на всплеске или рядом с ним, а не в случайных bucket-ах. Потом нажмите marker и убедитесь, что он открывает полную трассировку с child spans для обращений к базе, внешним API, очередям или всему остальному, к чему прикасался запрос.

Sampling вызывает больше проблем, чем ожидают многие команды. Можно правильно записывать exemplars, но потерять медленную трассу, потому что sampler её отбросил. Тогда у вас остаётся marker, который никуда полезно не ведёт. Для поиска всплесков многие команды сохраняют все error traces и большую долю медленных трасс, даже если обычный трафик они семплируют агрессивнее.

Последняя простая проверка очень полезна. Возьмите реальный всплеск на графике, нажмите exemplar и прочитайте child spans. Вы должны увидеть, куда ушло время, простыми словами: например, database query на 2,4 секунды или зависший upstream call. Если вы не можете получить этот ответ меньше чем за минуту, настройку ещё нужно доработать.

Что делать дальше

Выберите один endpoint, который особенно страдает, когда замедляется. Подойдёт login, checkout или search. Начните с него и убедитесь, что метрики, трассировки и exemplars совпадают, прежде чем трогать остальную часть сервиса.

Такой небольшой тест даёт понятный ответ «да» или «нет». Когда график задержки всплескнул, вы должны иметь возможность перейти к реальной трассировке, посмотреть медленный span и назвать причину за несколько минут. Если это не работает на одном endpoint, более широкий запуск только разнесёт путаницу.

Короткий runbook помогает больше, чем ещё один дашборд. Откройте график задержки для endpoint, найдите всплеск, откройте прикреплённую трассу, посмотрите медленный span или downstream call, а затем запишите причину и исправление в одном месте. Команды действительно пользуются runbook, когда он умещается на одной странице. После первого инцидента обновите его скриншотами, названиями полей и точным графиком, которому ваша команда должна доверять.

Перед тем как разворачивать exemplars на большее число сервисов, проверьте части, которые обычно ломают поток. Сначала проверьте trace sampling. Если sampling слишком жёсткий, график может указывать на трассы, которых нет в storage. Потом проверьте retention. Всплеск со вчерашнего дня бесполезен, если трасса исчезла через шесть часов. И наконец проверьте стоимость. Храните достаточно данных для реальной отладки, но не храните каждую трассу вечно только потому, что можете.

Поделитесь с командой одним реальным инцидентом. Короткая заметка вроде «p95 вырос до 2,4 s, exemplar указал на trace 7ab, медленный запрос был из-за отсутствующего index» быстро меняет мнение. Люди доверяют методу, когда видят, что он экономит 30 минут во время живой проблемы.

Если вам нужна помощь с настройкой трассировки, метрик, storage и процесса реагирования на инциденты без превращения observability в огромный побочный проект, Oleg Sotnikov на oleg.is работает со стартапами и небольшими командами как Fractional CTO и advisor.

Часто задаваемые вопросы

Какую проблему решают exemplars?

Они связывают плохой показатель в метриках с реальной трассировкой. Вместо того чтобы смотреть на всплеск p95 и гадать, какой запрос его вызвал, можно перейти с графика к одному медленному запросу и посмотреть span, который занял время.

С чего лучше всего начать добавлять exemplars?

Начните с одного endpoint, который особенно заметен, когда замедляется, например login, checkout или search. Так внедрение останется небольшим, и вы быстро увидите, действительно ли метрики, трассировки и хранилище правильно связаны.

Нужны ли histograms, или достаточно counters?

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

Почему я не вижу ссылку на exemplar при всплеске задержки?

Чаще всего связь пропадает из-за времени записи или потери контекста. Записывайте задержку, пока request span ещё активен, сохраняйте trace context на всём пути запроса и проверьте, что и backend, и интерфейс умеют работать с exemplars.

Сколько trace sampling мне нужно?

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

Какие метки стоит оставить на API-метриках задержки?

Оставьте небольшое и стабильное число меток. Обычно достаточно service name, шаблона маршрута, HTTP method и status code или status class — так графики остаются чистыми, а число series не раздувается.

Могут ли exemplars помочь с короткими всплесками?

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

Как протестировать настройку перед production?

Сделайте один запрос специально медленным в staging, затем откройте график задержки и найдите marker на медленном bucket. Нажмите на него и проверьте, что он открывает подходящую трассировку с child spans для базы данных, cache или downstream calls.

Что должен делать инженер во время инцидента?

Откройте график задержки для затронутого endpoint, найдите всплеск, нажмите exemplar и сначала прочитайте медленный span. Так у вас будет конкретная отправная точка, прежде чем проверять CPU, autoscaling или другие системные сигналы.

Когда небольшой команде стоит обратиться за внешней помощью?

Просите помощи, когда команда снова и снова тратит время на настройку вместо устранения инцидентов. Если нужен человек, который поможет связать трассировки, метрики, storage и простой процесс реагирования, не превращая это в огромный побочный проект, Fractional CTO или advisor сильно ускорит работу.