∿Метрики и
наблюдаемость
Главы темы 1
1.3

Наблюдаемость и телеметрия

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

Главное в главе

Наблюдаемость оценивают по конкретному вопросу: можно ли ответить на него по данным, которые сервис уже сохранил.

Раздел
Основы наблюдаемости
Уровень
Начальный · Python знаком
Связанные главы
1.2 — мониторинг, 1.5 — логи, 1.6 — трассы
Зачем это знать
Понимать, какие данные создать заранее, чтобы потом найти причину сбоя

§1Вопрос, для которого нет готовой панели

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

Пример

Медленные большие отчёты

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

Чтобы разобраться, системе нужно задать новый вопрос: как длительность формирования зависит от числа строк. Ответить на него можно, только если размер отчёта где-то записан вместе с длительностью. Если такой записи нет, остаётся добавить её в код, выпустить новую версию и ждать, пока проблема повторится.

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

У этой способности есть точное определение. Оно дано в следующем разделе.

§2Наблюдаемость

Сравнение из жизни
Бортовой самописец

Самолёт во время полёта записывает сотни параметров. Когда после рейса возникает вопрос, которого никто не ждал, ответ ищут в уже сделанных записях.

Данные сервиса

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

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

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

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

Уровень наблюдаемости оценивают по конкретному вопросу. Для сервиса отчётов вопрос такой: можно ли по сохранённым данным найти медленные отчёты, узнать их размер и увидеть, на каком этапе ушло время. Если все три ответа есть, для этой проблемы сервис наблюдаем. Если размера нет, наблюдаемости не хватает, и это исправляется добавлением поля.

Первоисточник: OpenTelemetry · Observability primer ↗

Определение похоже на определение мониторинга из главы 1.2. Следующий раздел показывает, чем эти понятия различаются и как они работают вместе.

§3Как связаны мониторинг и наблюдаемость

Часто их противопоставляют так: мониторинг отвечает на вопрос «что сломалось», наблюдаемость — «почему». Для первого знакомства это подходит, но на практике граница размыта. Хорошо подобранные показатели мониторинга часто сразу подсказывают причину, а записи о событиях без нужного контекста причину не объясняют.

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

  1. Получить уведомление

    Доля ошибок /products выросла. Опора — мониторинг, заранее настроенное правило.

  2. Уточнить область

    Какая версия, маршрут, экземпляр. Опора — метрики с разбивкой по признакам.

  3. Найти отдельные неуспешные запросы

    Опора — логи с идентификатором запроса.

  4. Посмотреть, на каком этапе ушло время

    Опора — трасса запроса.

  5. Добавить недостающее измерение

    Если данных не хватило, в код добавляют новое измерение.

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

В каждом шаге разбора инженер опирается на данные. Следующий раздел называет эти данные и их виды.

§4Телеметрия: данные, которые система сообщает о себе

Телеметрия — данные о работе системы, которые она создаёт во время работы и передаёт для хранения и анализа. Слово составлено из греческих «теле» — далеко и «метрон» — мера: измерение на расстоянии.

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

В справочнике используются три основных вида телеметрии. Их называют сигналами.

∑ Метрики

Числа за интервал

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

{ } Логи

Записи о событиях

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

↳ Трассы

Этапы одной операции

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

все часть одна Сколько операций охватывает мало средне много Сколько подробностей об одной операции Метрикичисла за интервал Логисобытие с полями Трассыэтапы операции мало места, хранят долго много места: часть трасс отбрасывают
Рис. 1.3.1 подробность сигнала и охват операций

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

ВопросСигналПочему
Какая доля запросов завершилась ошибкой за час?МетрикаХранит количества, из которых считается доля
Какое исключение возникло у запроса 2797ad2d…?ЛогХранит текст ошибки конкретного события
На каком этапе медленный запрос потратил три секунды?ТрассаХранит начало и длительность каждого этапа

Обычно разбор идёт от метрики к логу и трассе: метрика показывает, что и когда ухудшилось, лог — что случилось с отдельными запросами, трасса — на каком этапе ушло время. Ни один сигнал не заменяет двух других.

Кроме трёх основных сигналов, есть и другие. Например, профили показывают, какие функции программы тратят процессорное время и память. В этой теме они не понадобятся.

Первоисточник: OpenTelemetry · Signals ↗

Сигналы названы. Остаётся вопрос, откуда они берутся в программе.

§5Инструментация: откуда берутся данные

Телеметрия сама не появляется. Если в коде обработчика никто не измерил длительность, никакая система мониторинга её не покажет: измерение, запись о событии или этап трассы должна создать сама программа.

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

Так выглядит инструментация сервиса стенда. Обработчик /products состоит из трёх этапов, и каждый выполняется через вспомогательную функцию stage. Она выполняет работу этапа и записывает, когда этап начался и сколько длился.

Python · фрагмент app.pyФайл целиком ↓
started = time.perf_counter()
spans = []

async def stage(name, seconds):
    offset = time.perf_counter() - started
    begin = time.perf_counter()
    await asyncio.sleep(seconds)
    spans.append({"name": name, "offset_ms": round(offset * 1000, 3),
                  "duration_ms": round((time.perf_counter() - begin) * 1000, 3)})

time.perf_counter() возвращает показания точного таймера в секундах. С часами на стене это число не связано, но разность двух показаний даёт длительность промежутка. Переменная started хранит момент начала всего запроса.

СтрокаЧто делает
offset = …Сколько секунд прошло от начала запроса до начала этапа. По этому значению этап ставится на общую шкалу времени
begin = …Момент начала самого этапа
await asyncio.sleep(seconds)Работа этапа. На стенде это ожидание заданной длительности, в настоящем сервисе — например, запрос к базе данных
spans.append(...)Запись результата: имя этапа, смещение и длительность в миллисекундах с тремя знаками после запятой

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

Автоматическая

Библиотеки для фреймворков и драйверов

Сами измеряют типовые операции: HTTP-запросы, SQL-запросы, вызовы внешних API. Быстро дают общую картину, но смысл программы в них не заложен.

Ручная

Код, который пишет разработчик

Добавляет признаки, важные для задачи: размер отчёта, тариф пользователя, тип операции. Какие из них важны, определяет разработчик, и библиотека за него это не сделает.

Первоисточник: OpenTelemetry · Instrumentation ↗

Инструментация создаёт отдельные записи. Чтобы из них сложилась история одного запроса, записи нужно связать между собой.

§6Время и идентификатор связывают события

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

JSON · поля события products-api
{
  "timestamp": "2026-09-15T13:06:29.890773+00:00",
  "level": "ERROR",
  "service": "products-api",
  "event": "request_completed",
  "status": 500,
  "request_id": "2797ad2d8174461eb96c36e59f8126da"
}

timestamp ставит событие на общую шкалу времени. Окончание +00:00 означает время по UTC — всемирному координированному времени без смещения часового пояса. Если все компоненты пишут время в UTC, записи с серверов в разных часовых поясах сравнивают без пересчёта.

request_id связывает события одного запроса. Сервис создаёт его функцией uuid.uuid4().hex: это 32 шестнадцатеричных символа, случайных для каждого запроса. Вероятность совпадения двух таких значений настолько мала, что на практике её не учитывают. Тот же идентификатор сервис возвращает клиенту в заголовке X-Request-ID. Пользователь пересылает его в поддержку, и разработчик находит по нему все записи.

Когда запрос проходит через несколько сервисов, идентификатор передают дальше, иначе цепочка оборвётся на первом же вызове. Передачу контекста между сервисами называют распространением контекста (context propagation). В распределённой трассировке для этого два идентификатора: trace_id — общий для всей операции и span_id — свой у каждого этапа. Сервис стенда состоит из одного процесса, поэтому ему хватает одного request_id.

Первоисточник: OpenTelemetry · Context propagation ↗

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

§7Почему идентификатор запроса не добавляют в метрику

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

Метрику хранят как набор временных рядов. Признаки, по которым её делят, называют метками (labels). Каждое уникальное сочетание значений меток создаёт отдельный временной ряд. Метрика запросов с метками route и status на стенде хранит два ряда: для статуса 200 и для статуса 500.

Кардинальность — число уникальных значений метки или число временных рядов, которые получаются из всех сочетаний меток. Чем выше кардинальность, тем больше памяти и места нужно хранилищу.

до 20route, status5 маршрутов × 4 статуса
до 60+ instance× 3 экземпляра
до 2 000 000+ user_id× 100 000 пользователей
без предела+ request_idновое значение у каждого запроса

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

В метки метрик

Небольшой известный набор значений

Маршрут, код ответа, экземпляр, версия.

В логи и трассы

Идентификаторы

Запроса, пользователя, заказа. Там каждое событие и так хранится отдельно, лишний ряд не создаётся.

Сервис стенда создаёт телеметрию своим кодом. В рабочих системах для этого есть общий стандарт.

§8OpenTelemetry: общий способ создавать и передавать телеметрию

Раньше каждая система мониторинга предлагала свои библиотеки для инструментации, и при смене хранилища трасс приходилось переписывать код во всех сервисах. В 2019 году проекты OpenTracing и OpenCensus объединились в OpenTelemetry, чтобы у всех был один способ.

OpenTelemetry — открытый стандарт и набор инструментов для создания, сбора и передачи телеметрии: метрик, логов и трасс. Проект развивается в фонде CNCF и не привязан к конкретному хранилищу.

OpenTelemetry состоит из нескольких частей, каждая отвечает за свой участок пути данных.

APIфункции, которыми код создаёт измерения, записи и этапы
SDKобрабатывает данные в процессе и отправляет дальше
Collectorпринимает, обрабатывает и пересылает телеметрию
ХранилищаPrometheus, Tempo, Jaeger, Loki

Между частями данные идут по протоколу OTLP. Библиотеки инструментации подключаются к SDK и измеряют типовые операции фреймворков и драйверов.

Хранилища в этой цепочке — отдельные системы. OpenTelemetry данные не хранит и графики не строит. Метрики сохраняет, например, Prometheus, трассы — Jaeger или Grafana Tempo, логи — Loki или Elasticsearch. OpenTelemetry отвечает за то, чтобы данные были созданы одинаково и доставлены по адресу.

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

Первоисточник: What is OpenTelemetry? ↗

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

§9Где телеметрия искажается и теряется

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

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

Выборку трасс называют сэмплированием (sampling). Сервис с десятью тысячами запросов в секунду не может хранить трассу каждого, поэтому сохраняет, например, один процент трасс или все трассы с ошибками. Это нужно помнить при выводах: сто сохранённых медленных трасс не значат, что все запросы медленные, если медленные сохраняли в первую очередь.

Пример

Совпадение по времени

После выпуска новой версии выросла длительность запросов. В тот же момент выросла загрузка CPU. Инженер записывает в отчёт: «причина — новый алгоритм сортировки».

Два графика изменились одновременно. Это основание для гипотезы, доказательством оно не является: в тот же момент мог вырасти поток пользователей или измениться состав данных. Для проверки сравнивают одинаковые операции до выпуска и после, смотрят этапы медленных запросов в трассах, а если можно — откатывают версию. Если после отката длительность снизилась при том же потоке, гипотеза подтверждена.

§10Частые ошибки

Что делаютЧто получаетсяКак правильно
Считают, что наблюдаемость можно установитьДанные есть, но в них нет подробностей для нужного вопросаОпределить вопросы расследования и добавить в код измерения, которые на них отвечают
Полагаются только на автоматическую инструментациюВидны HTTP и SQL, но не видны размер отчёта, тариф, тип операцииДобавлять вручную признаки, важные для задачи программы
Добавляют request_id или user_id в метки метрикиЧисло рядов растёт без ограничения, хранилище перегруженоИдентификаторы писать в логи и трассы, в метки — признаки с небольшим набором значений
Пишут время в местном часовом поясе каждого сервераСобытия разных серверов нельзя сравнить без пересчётаЗаписывать время в UTC
Не передают идентификатор при вызове другого сервисаЦепочка запроса обрывается на границе сервисаПередавать контекст в заголовках каждого исходящего вызова
Считают отсутствие записи доказательствомПотерянные события принимают за «ничего не случилось»Сначала проверить, собирались ли данные за этот период

§11Проверьте себя

Сервис отправляет данные в Prometheus и Grafana. Можно ли сказать, что он наблюдаем?

Заранее нельзя. Наблюдаемость оценивают по вопросу: можно ли по сохранённым данным разобраться в конкретной проблеме. Если в данных нет нужных подробностей, инструменты не помогут.

Чем метрика, лог и трасса отличаются по количеству сохраняемых подробностей?

Метрика сжимает много событий в числа и теряет подробности отдельных запросов. Лог хранит запись о каждом событии с полями. Трасса хранит этапы одной операции с их началом и длительностью.

Почему метка user_id в метрике запросов опасна для хранилища?

Каждое уникальное сочетание меток создаёт отдельный временной ряд. У сервиса со 100 000 пользователей метрика получит до 100 000 рядов на каждое сочетание остальных меток.

Что делает строка offset = time.perf_counter() - started в функции stage?

Вычисляет, сколько секунд прошло от начала запроса до начала этапа. По этому смещению этап ставится на общую шкалу времени запроса.

Хранит ли OpenTelemetry метрики и трассы?

Нет. OpenTelemetry создаёт и передаёт телеметрию. Хранят данные отдельные системы, например Prometheus для метрик и Tempo или Jaeger для трасс.

§12Шпаргалка

  • Наблюдаемость — насколько по внешним данным можно понять, что происходит внутри системы.
  • Наблюдаемость оценивают по конкретному вопросу расследования.
  • Телеметрия — данные, которые система создаёт о себе во время работы.
  • Метрики показывают масштаб, логи — отдельные события, трассы — этапы операции.
  • Инструментация — код, который создаёт телеметрию; признаки, важные для задачи, добавляет разработчик.
  • Время в UTC ставит события на общую шкалу, идентификатор связывает события одной операции.
  • Метки метрик — признаки с небольшим набором значений; идентификаторы — в логи и трассы.
  • OpenTelemetry создаёт и передаёт телеметрию, но не хранит её.
  • Отсутствие записи не доказывает отсутствие события.

Первоисточники