Observability-капстоун: чтение сигналов и запросов
Суть Чтение PromQL burn-rate-выражения, заголовка traceparent, flame graph и коррелированной строки лога — предсказать поведение и выбрать senior-прочтение.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min
Артефакты трека — это запросы и wire-форматы: строка burn-rate PromQL, строка traceparent, flame graph, запись лога с trace-id. Прочитайте каждый так, как читали бы в 2 ночи, потом выберите вывод senior-инженера.
Цель
Потренировать чтение четырёх конкретных артефактов главы — SLO-алерт-запроса, заголовка propagation, профиля и коррелированного лога — и превращение каждого в следующий шаг воронки.
Чего добиваются множитель 14.4 и two-window AND-клауза в этом алерте?
Heads-up 14.4 — это множитель burn-rate (во сколько раз быстрее бюджет-нейтрального), а не SLO. Окна объединяются по AND для подтверждения, а не усредняются.
Heads-up Окно 5m само по себе дёрганое и срабатывает на мгновенных всплесках. Окно 1h — это gate подтверждения; убрать его — обменять MTTD на поток ложных пейджей.
Heads-up Burn-rate-алертинг срабатывает, пока бюджет тратится слишком быстро — burn 14.4x исчерпал бы 30 дней бюджета за ~2 дня. Он предиктивный, а не пост-фактум.
Downstream-сервис получает этот заголовок. Читая четыре поля через дефис, что он должен сделать, чтобы корректно продолжить trace?
Heads-up Новый trace-id ломает trace на два несвязанных дерева — весь смысл propagation в том, что trace-id постоянен на каждом hop. Меняется только span-id.
Heads-up Переиспользование входящего span-id как своего сливает ваш span с вызывающим, разрушая parent/child причинный граф. Вы наследуете trace-id и parent-id, но чеканите новый span-id.
Heads-up 01 в trace-flags — это бит sampled (держать этот trace), а не флаг завершения. Сброс заголовка рвёт propagation и осиротит каждый downstream-span.
Этот профиль отфильтрован по медленному trace-id из trace view. Какое прочтение верное и какой следующий шаг воронки?
Heads-up inventory.Lookup — это фрейм-предок: его 87% в основном унаследованы от ребёнка json.Marshal. Оптимизируют широчайший ЛИСТ (self-time), а не родителя, который лишь вызывает его.
Heads-up 9% — это мало; 73% в json.Marshal затмевают его. Это проблема CPU-сериализации, а не сетевая.
Heads-up Один профиль уже называет широчайший лист и его долю self-time. Differential-профилирование помогает при регрессиях, но здесь один отфильтрованный профиль указывает прямо на json.Marshal.
Сниппет 4 — коррелированная строка лога
{"level":"error","ts":"2026-05-29T02:14:07Z","service":"inventory", "msg":"marshal failed: schema v3 field overflow","trace_id":"4bf92f3577b34da6a3ce929d0e0e4736", "span_id":"00f067aa0ba902b7","http.route":"/inventory/lookup"}
Викторина
Completed
Как эта одна строка лога привязывается к trace и профилю из предыдущих сниппетов и почему это важно?
Heads-up Поиск по timestamp возвращает каждый лог в эту секунду по всем запросам. trace_id — это то, что прицельно привязывает к одному падающему запросу; ровно поэтому структурированные логи его несут.
Heads-up http.route матчит каждый запрос к этому эндпоинту, а не один медленный запрос. Только trace_id изолирует этот конкретный падающий запрос из тысяч здоровых.
Heads-up С trace_id, проброшенным в структурированные логи, логи и трейсы явно сджойнены. Это и есть four-signal-модель: один trace-id на логах, exemplar'ах метрик, трейсах и профилях.
Итог
Четыре артефакта — одна цепочка. Multi-window burn-rate-запрос даёт алерт (короткое окно для скорости, длинное для подтверждения, 14.4x = burn бюджета за 2 дня). Заголовок traceparent держит trace-id постоянным, чеканя новый span-id на каждом hop. Flame graph называет широчайший лист как self-time-hotspot — оптимизируй лист, а не предка. А структурированный лог несёт тот же trace_id, поэтому джойнится точно к тому запросу. Прочитанные последовательно, это воронка, сделанная конкретной.