АрхитектураНадёжность и производительностьИнженер по надёжности платформы

В распределённой трассировке один запрос содержит параллельные вызовы: как определить, какой из них огранич...

В распределённой трассировке один запрос содержит параллельные вызовы: как определить, какой из них ограничивает сквозную задержку?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

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

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

Исторический контекст

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

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

Постановка проблемы

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

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

Подробное решение

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

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

Практический анализ обычно выглядит так:

  • сравнить трассы нормального и медленного запросов;
  • найти участок, который добавляет задержку именно на критическом пути;
  • проверить его p95 или p99, а не только среднее значение;
  • сопоставить результат с метриками загрузки, очередей, ошибок и насыщения ресурса;
  • проверить, не вызвана ли задержка повторными попытками или отсутствующими участками трассировки.

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

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

Ситуация из практики

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

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

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

Что кандидаты часто упускают

  1. Может ли самый долгий span не быть узким местом?

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

  1. Как анализировать критический путь при неполной трассировке?

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

  1. Почему средняя длительность span может скрыть проблему сквозной задержки?

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