Трассировка и золотые сигналы
Трассировка - это запись пути одного запроса через все сервисы, с отметками, что и когда происходило. Замерил одну и ту же работу двумя способами. Три вызова подряд: сумма длительностей 251 миллисекунда, запрос занял 251. Те же три вызова параллельно: сумма длительностей 252, а запрос занял 121. Сумма одинаковая, время пользователя разное вдвое. Из одних только длительностей это не видно - нужна картинка, кто кого ждал.
Стержень: трассировка показывает не сколько заняли шаги, а как они расположены во времени; сэмплирование определяет, увидишь ли ты вообще нужный случай.
// Формулировки: «зачем трассировка, если есть логи?», «как выбрать долю сэмплирования?», «что такое золотые сигналы?»
Спаны и картинка запроса
Спан - это отрезок работы с началом, концом и именем: «запрос к базе», «вызов сервиса цен». У спанов есть родитель, поэтому из них складывается дерево, а дерево разворачивается в картинку по времени. Именно картинка отвечает на вопросы, на которые не отвечают ни метрики, ни логи.
Мой замер это показывает буквально. Сумма спанов 251 миллисекунда в обоих случаях, но в первом шаги идут друг за другом, а во втором - одновременно, и запрос занимает 121. Если смотреть только на сумму, две ситуации неразличимы. На картинке видно сразу: лесенка означает последовательные вызовы, которые можно распараллелить, а длинный спан, начавшийся сразу и закончившийся последним, - это тот, кто держит весь запрос.
// Отсюда типичные находки в трассировках: три последовательных вызова, которые могли идти параллельно; повторяющийся одинаковый запрос к базе двадцать раз подряд; ожидание блокировки, которого не видно ни в одной метрике; чужой сервис, который отвечает за 800 миллисекунд при собственной работе в 30.
- спан
- отрезок работы с началом, концом, именем и родителем
- дерево спанов
- структура вызовов одного запроса; разворачивается в картинку по времени
Сэмплирование: сколько запросов оставлять
Хранить трассировку каждого запроса дорого, поэтому оставляют долю. Вопрос в том, какую, и он считается арифметикой. При 2000 запросах в секунду и доле 1% в трассировки попадает 20 запросов в секунду. Если сбоит 5% запросов, это 60 сбойных трассировок в минуту - разбирать есть что. А если сбоит 0,1%, то всего 1,2 в минуту, и редкий сбой можно ждать часами.
Ужесточим: при доле 0,1% и той же редкой поломке в трассировки попадает 0,1 случая в минуту, то есть один раз в десять минут. Именно поэтому «мы включили трассировку, но нужного случая там нет» - это не мистика, а арифметика.
// Решение известно: сэмплировать не случайно, а по результату. Сначала собрать спаны запроса, а решение о сохранении принять в конце: сохранить все запросы с ошибкой и все медленные, а из успешных и быстрых оставить небольшой процент. Тогда редкие сбои сохраняются целиком, а объём остаётся приемлемым. Плата - буфер, где спаны ждут решения, и сложность в самом сборщике.
- сэмплирование
- сохранение только доли трассировок
- решение по результату
- сохранять запрос или нет, решают в конце: ошибки и медленные - всегда
Три сигнала, которые меряют у всего
Есть короткий список того, что меряют у любого сервиса, чтобы понять его состояние. Нагрузка: сколько запросов в секунду приходит. Ошибки: какая доля запросов завершается неуспешно. Задержка: сколько занимает ответ, причём в квантилях (значениях, ниже которых лежит заданная доля запросов), а не в среднем - я это показывал числами, среднее 79 миллисекунд при квантиле 0,99 в 2318.
К этим трём часто добавляют четвёртый - насыщенность: насколько близко к пределу самый узкий ресурс. Очередь, запас заранее открытых соединений к базе, место на диске, свободная память. Смысл в том, что первые три показывают, что уже плохо, а насыщенность предупреждает, что скоро станет.
// Практическая ценность списка в том, что он одинаков для всего: для сервиса, для базы, для очереди. Не надо придумывать метрики заново для каждого компонента - берёшь эти четыре, и у тебя уже есть с чем сравнивать и на что вешать алерты. А дальше добавляешь то, что специфично именно для этого компонента.
- нагрузка
- сколько запросов приходит в секунду
- задержка
- время ответа; смотрят в квантилях, а не в среднем
- насыщенность
- насколько близко к пределу самый узкий ресурс
Как отвечать: «Зачем трассировка, если уже есть метрики и логи?»
Метрики отвечают на вопрос «что и насколько плохо», логи - «что случилось в конкретном месте», а трассировка отвечает на «где именно ушло время в этом запросе». Я это специально мерил: три вызова подряд дают сумму длительностей 251 миллисекунда и время запроса 251, а те же три вызова параллельно - сумму 252 и время запроса 121. По суммам ситуации неразличимы, а пользователь ждёт вдвое дольше. На картинке видно сразу: лесенка последовательных вызовов, повторяющийся одинаковый запрос к базе, ожидание блокировки, чужой сервис, который отвечает за 800 миллисекунд. И сразу оговорю сэмплирование, потому что на нём чаще всего разочаровываются: при одном проценте и редкой поломке в трассировки попадает около одного случая в минуту, поэтому решение о сохранении лучше принимать по результату - все ошибки и все медленные сохранять целиком.
Ответ разводит три инструмента по вопросам, на которые они отвечают, и подкрепляет измерением. Замечание про сэмплирование снимает главное разочарование от трассировки.
На чём валятся
- −Смотрят на сумму длительностей и не видят разницы между последовательными и параллельными вызовами.
- −Ставят случайное сэмплирование в один процент и не находят редкие сбои.
- −Не пробрасывают идентификатор трассировки в одном сервисе и получают обрубленное дерево.
- −Меряют задержку средним вместо квантилей.
- −Придумывают свой набор метрик для каждого компонента вместо общего короткого списка.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 6, остальные разбираются в тренажёре.
- Трассы собирают с сэмплированием 1%. Что теряется и чем это компенсируют?A)Ничего: редкие запросы всё равно повторяются и попадут в выборкуB)Теряется точность метрик, поэтому их считают из трасс зановоC)Теряются редкие ошибки и хвост задержекD)Теряется связь с логами: идентификаторы перестают совпадать
показать ответ и разбор
+C)Теряются редкие ошибки и хвост задержек// разбор: Решение на входе (head-based) отбрасывает 99% запросов вслепую — вместе с редкими ошибками и медленными хвостами, ради которых трассировку и заводят. Сэмплирование по итогу (tail-based) держит трассу в буфере до завершения и сохраняет её, если запрос упал или превысил порог задержки, а из успешных быстрых берёт долю. Платить приходится памятью коллектора и сложностью настройки.
- Мониторинг сервиса состоит из графиков CPU, памяти и диска. Что предлагает подход золотых сигналов?A)Добавить к ним сетевые метрики и число процессовB)Мерить трафик, ошибки, задержку и насыщенность ресурсовC)Перейти на проверки доступности раз в минуту из внешней точкиD)Считать только доступность: остальное вторично
показать ответ и разбор
+B)Мерить трафик, ошибки, задержку и насыщенность ресурсов// разбор: Золотые сигналы описывают сервис глазами пользователя: сколько запросов приходит, какая доля падает, сколько ждут ответа (обязательно в квантилях, а не в среднем) и насколько близко ресурсы к пределу. Для очередей и пулов последний сигнал — самый ранний предвестник аварии. Метрики железа при этом никуда не деваются, но они объясняют причину, а не сигналят о проблеме.
- Что технически представляет собой одна распределённая трасса и как она собирается через сервисы?A)Один большой лог-файл, склеенный со всех сервисовB)Копия запроса, продублированная в каждый сервисC)Метрика длительности запроса без разбивки по шагамD)Дерево спанов с общим trace-id
показать ответ и разбор
+D)Дерево спанов с общим trace-id// разбор: Трасса — дерево спанов: каждый спан описывает операцию (вызов сервиса, запрос к БД) с временем начала/длительностью, у всех общий trace-id, а parent-id задаёт родство. Чтобы спаны связались в одну трассу, между сервисами прокидывают trace-context — обычно в HTTP-заголовке (traceparent по W3C). Каждый сервис создаёт свой спан и передаёт контекст дальше. Так виден путь и вклад каждого шага в общую задержку.
- Голова-сэмплинг оставляет случайный 1% трасс — и редкие ошибки/медленные хвосты как раз теряются. Что это лечит?A)Tail-based сэмплинг: решать после трассыB)Поднять head-сэмплинг до 100% и хранить всёC)Снизить head-сэмплинг ещё сильнее ради экономииD)Сэмплировать по имени сервиса, а не по проценту
показать ответ и разбор
+A)Tail-based сэмплинг: решать после трассы// разбор: Head-based сэмплинг решает судьбу трассы в самом начале, вслепую, поэтому случайный 1% почти не содержит редких ошибок и хвостовых задержек. Tail-based сэмплинг откладывает решение до конца трассы: увидев исход (ошибка, высокая latency), он сохраняет все «интересные» трассы и прореживает рутинные. Цена — надо буферизовать спаны до решения (сложнее инфраструктурно), зато в хранилище попадает именно то, что нужно для разбора.
- Трасса аккуратно проходит по синхронным вызовам, но обрывается, как только запрос уходит в очередь и обрабатывается воркером. Почему?A)Трассировка вообще не работает с очередямиB)Воркер обязан читать trace-id из общей базыC)Trace-context не прокинут через сообщение — воркер начинает новую трассуD)Очередь сбрасывает trace-id по соображениям безопасности
показать ответ и разбор
+C)Trace-context не прокинут через сообщение — воркер начинает новую трассу// разбор: Через синхронный HTTP trace-context прокидывается заголовком, а вот на границе очереди он теряется, если его не положить в метаданные/заголовки самого сообщения. Тогда воркер, не получив контекст, стартует новую трассу — и путь рвётся. Лечится инструментацией продюсера (записать trace-context в сообщение) и консьюмера (извлечь и продолжить трассу). Это типичная слепая зона на асинхронных границах.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.