Средний

Wide Events вместо метрик, логов и трейсов

Алексей Кузнецов
Алексей Кузнецов
Системный администратор26 октября 2026 г.17 мин чтения

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

Wide Events вместо метрик, логов и трейсов

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

Проблема фрагментации данных

Проблема фрагментации данных

Когда я только начинал администрировать серверы, мониторинг был линейным: если сервис падал, я шёл в логи, если росла нагрузка — смотрел на графики CPU. Сейчас, когда в домашней лаборатории или в промышленном кластере у вас сотни микросервисов, этот подход превращается в бесконечный бег по кругу между разными вкладками браузера. Проблема не в нехватке данных, а в их фрагментации. Мы получаем огромный объём информации, но она разбросана по разным «силосам».

Разрыв между метриками и логами

Традиционный подход разделяет данные на два принципиально разных типа. Метрики — это агрегаты. Они говорят нам, что происходит: «задержка ответа выросла до 500 мс» или «занятость памяти составляет 90 %». Это отличный инструмент для алертинга, но у метрик есть фундаментальное ограничение — они не содержат контекста.

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

Проблема в том, что связь между ними часто держится «на честном слове». Например, вы можете попытаться добавить service_name в оба источника, но если в логах нет уникального идентификатора запроса, который совпадает с метрикой, вы так и будете гадать, какой именно запрос вызвал деградацию производительности.

Представьте типичную ситуацию:

  1. Prometheus сигнализирует о росте 5xx ошибок.
  2. Вы открываете панель логов.
  3. Вы видите тысячи строк ERROR: connection timeout.
  4. Вы не знаете, это вызвано нехваткой ресурсов (метрика) или специфическим багом в коде (лог).

С точки зрения предсказуемости системы, такой разрыв — это слепая зона. Мы видим симптом, но не видим пациента.

Сложность корреляции в распределенных системах

В распределённых системах ситуация усложняется в разы. Если ваш сервис «Пятачок» обращается к базе данных на «Винни-Пухе», а тот, в свою очередь, запрашивает данные у «Совы», то стандартных метрик станет недостаточно.

Традиционные трейсы (Distributed Tracing) призваны решить эту проблему, добавляя trace_id. Однако и здесь возникает подвох. Трейс показывает путь запроса, но он часто слишком «худой». В нём есть информация о времени выполнения сегментов, но в нём нет деталей того, что происходило внутри конкретного узла в момент выполнения операции.

Если мы используем классическую связку, процесс расследования инцидента выглядит так:

  1. Находим аномалию в метриках (Prometheus).
  2. Находим проблемный запрос через трейсы (Jaeger).
  3. Пытаемся найти логи именно этого запроса по trace_id (Loki).

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

С практической точки зрения, такая фрагментация делает невозможным автоматизированный анализ. Сложно написать скрипт или настроить алерт, который скажет: «Ошибка выросла, потому что конкретный SQL-запрос в этом контексте стал занимать больше времени из-за блокировки таблицы». Мы можем настроить алерт только на следствие, но не на причину. Концепция Wide Events как раз предлагает уйти от этого разделения, объединяя контекст лога, агрегаты метрик и структуру трейса в одно событие.

Концепция Wide Events

Что такое широкие события

Когда мы говорим об обсервабилити (observability), стандартный подход диктует разделение на три столпа: метрики, логи и трейсы. Метрики говорят нам, что происходит (растёт нагрузка на CPU), логи — что именно случилось (ошибка доступа к БД), а трейсы показывают, где застрял запрос. Проблема в том, что эти данные живут в разных мирах. Чтобы связать ошибку из лога с конкретным запросом в трейсе, инженеру приходится вручную сопоставлять временные метки или искать общий trace_id в разных интерфейсах.

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

Если классическая метрика — это просто число http_requests_total{method="POST", status="500"}, то широкое событие — это полноценный объект. Представьте, что вместо записи в базе данных о том, что «произошла ошибка 500», мы отправляем структурированный JSON, в котором указаны:

  • ID пользователя и его роль;
  • Версия приложения и ID контейнера;
  • Время выполнения запроса;
  • Идентификаторы всех связанных сервисов;
  • Даже специфические параметры окружения.

С практической точки зрения это превращает мониторинг из разрозненного набора графиков в поток структурированных данных. По сути, мы перестаём смотреть на «пульс» системы и начинаем видеть её «биографию».

Преимущества единого контекста

Главная ценность Wide Events — предсказуемость при расследовании инцидентов. Когда у вас есть единый контекст, вам не нужно переключаться между вкладками Grafana и Kibana, пытаясь угадать, какой именно запрос вызвал скачок задержки на графике.

Во-первых, это радикально упрощает корреляцию. Рассмотрим пример. Допустим, в вашем кластере на Proxmox начал тормозить сервис, отвечающий за бэкапы. В традиционной схеме вы увидите рост latency в Prometheus, а затем пойдёте в логи, чтобы понять, почему. В схеме с Wide Events вы просто фильтруете события по параметру latency > 500ms и сразу видите, что все эти события имеют общий тег node="Vinni-Puh" и disk_utilization="95%".

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

Для реализации такого подхода в современных стеках часто используют OpenTelemetry. Вместо того чтобы писать кастомные логгеры, вы настраиваете экспорт атрибутов. Пример того, как может выглядеть структура такого события в формате JSON, который уходит в хранилище (например, в ClickHouse):

json
{
  "timestamp": "2023-10-27T14:15:01.123Z",
  "event_type": "http_request",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "span_id": "00f067aa0ba902b7",
  "service_name": "api-gateway",
  "status_code": 500,
  "duration_ms": 1250,
  "customer_id": "user_8821",
  "region": "eu-central-1",
  "container_id": "docker://a1b2c3d4e5f6",
  "error_message": "connection timeout to database",
  "db_host": "sql-node-01",
  "cpu_usage_percent": 82.5
}

Сравните это с обычным логом. Здесь нет «пустоты» между данными. Если вы видите ошибку, у вас уже есть customer_id и container_id прямо в этой же строке. Вам не нужно делать grep по логам, чтобы найти, на каком именно контейнере это произошло.

Подход Wide Events во многом опирается на идеи, описанные в документации OpenTelemetry, где акцент смещается с разрозненных сигналов на единую модель данных. Это позволяет сократить время поиска причин сбоев (MTTR) за счёт того, что контекст всегда находится «под рукой» в момент возникновения аномалии.

Переход к новой модели наблюдаемости

Сравнение подходов: классика против Wide Events

Когда мы говорим о классической наблюдаемости (observability), мы обычно подразумеваем три столпа: метрики, логи и трейсы. В моей практике, когда я настраивал мониторинг для кластера на Proxmox, я привык работать именно так. Если в Grafana я видел всплеск задержек (метрика), я шёл в Loki, чтобы найти ошибки в логах, а затем пытался сопоставить это с распределёнными трейсами в Tempo.

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

Концепция Wide Events предлагает сменить парадигму. Вместо того чтобы разбрасывать данные по разным хранилищам, мы создаём одно событие, которое содержит в себе всё: и метрики (длительность, количество байт), и контекст (ID пользователя, версию приложения, ID контейнера), и даже часть лога.

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

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

Практические сложности внедрения

Переход на такую модель — это не просто смена софта, это изменение способа генерации данных. В классической схеме мы привыкли, что приложение просто пишет logger.info("User logged in"). В модели Wide Events приложение должно собирать контекст.

Во-первых, это нагрузка на инфраструктуру. Если раньше мы отправляли в хранилище короткую строку лога, то теперь мы отправляем структурированный JSON с десятками полей. Это требует пересмотра пропускной способности сети и объёмов дискового пространства на ваших серверах хранения, например, на TrueNAS.

Во-вторых, это сложность формирования события внутри кода. Чтобы получить качественный Wide Event, разработчик должен прокидывать контекст через все слои приложения.

Рассмотрим пример того, как может выглядеть структура такого события в формате JSON, которое мы отправляем в систему сбора данных (например, через OpenTelemetry):

json
{
  "timestamp": "2023-10-27T10:15:30.452Z",
  "event_type": "http_request",
  "trace_id": "a1b2c3d4e5f6g7h8",
  "service_name": "api-gateway",
  "duration_ms": 145,
  "status_code": 500,
  "customer_id": "user_9921",
  "container_id": "docker_container_77a1",
  "host_name": "vinni-pukh",
  "error_message": "Connection timeout to database",
  "db_query_time_ms": 120,
  "cpu_usage_percent": 45.2
}

Как видите, здесь в одном объекте объединились и метрики (duration, cpu_usage), и логи (error_message), и метаданные инфраструктуры (host_name, container_id).

Чтобы начать внедрение, не обязательно переписывать всё приложение. Можно начать с использования OpenTelemetry, который позволяет стандартизировать сбор этих данных. На уровне инфраструктуры это потребует настройки коллекторов, которые будут принимать эти раздутые события и эффективно их индексировать.

Если вы используете Docker, важно убедиться, что коллектор (например, OpenTelemetry Collector) имеет достаточно ресурсов, чтобы не стать узким местом. Настройка конфигурации коллектора для обработки таких объёмов выглядит примерно так:

yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    # Группируем события, чтобы снизить количество сетевых запросов
    send_batch_size: 1000
    timeout: 10s

exporters:
  # Отправляем данные в хранилище, поддерживающее структурированные события
  clickhouse:
    endpoint: "clickhouse-server:9000"
    database: "observability"

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [clickhouse]

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

Инструменты и реализация

Инструменты и реализация

Переход от классической триады «метрики — логи — трейсы» к концепции Wide Events требует смены парадигмы в инструментарии. Если раньше мы настраивали три разных пайплайна, то теперь нам нужен один мощный поток структурированных данных. С практической точки зрения, ключевым требованием становится поддержка глубокой вложенности и высокая пропускная способность.

Для реализации такой схемы я обычно выбираю стек на базе OpenTelemetry, так как он стал стандартом де-факто для сбора телеметрии. Он позволяет собирать контекст (те самые атрибуты события) и передавать их в едином формате.

Подготовка инфраструктуры для сбора событий

Чтобы Wide Events не превратились в хаос, инфраструктура должна уметь обрабатывать огромные объёмы структурированных JSON-объектов. Обычный текстовый логгер здесь не справится. Вам понадобится коллектор, который умеет обогащать события на лету.

В качестве агента я рекомендую использовать OpenTelemetry Collector. Он работает как промежуточное звено: принимает данные, добавляет к ним метаданные (например, имя узла, версию приложения или ID контейнера) и отправляет в хранилище.

Пример конфигурации otel-collector-config.yaml, который принимает данные и добавляет к ним атрибуты окружения:

yaml
receivers:
  otlp:
    protocols:
      grpc:
      http:

processors:
  # Добавляем метаданные, чтобы каждое событие было предсказуемым
  resourcedetection:
    detectors: [env, system]
  
  # Группируем атрибуты для удобства поиска
  attributes:
    actions:
      - key: deployment.environment
        value: "production"
        action: insert

exporters:
  # Отправляем всё в ClickHouse — он идеально подходит для Wide Events
  clickhouse:
    endpoint: "tcp://clickhouse-server:9000"
    database: "telemetry"
    table: "wide_events"

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [resourcedetection, attributes]
      exporters: [clickhouse]

Важно понимать: когда вы объединяете данные, объем одного «события» растет. Если раньше лог весил 200 байт, то Wide Event с метаданными может весить несколько килобайт. Это нужно учитывать при расчёте пропускной способности сети между вашими нодами и хранилищем.

Оптимизация стоимости хранения данных

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

Для решения этой проблемы я использую два подхода:

  1. Выбор правильного движка. Вместо Elasticsearch или Loki для таких задач лучше подходят колоночные базы данных, такие как ClickHouse. Они сжимают данные в разы эффективнее, потому что в Wide Event многие поля (например, service.name или region) дублируются. Колоночное хранение позволяет не читать лишнее при запросе.
  2. Политика жизненного цикла (TTL). Не все данные одинаково полезны через неделю. Я разделяю хранение на уровни.

Для управления этим процессом в ClickHouse можно использовать механизм TTL. Например, мы можем хранить детальные события с полным набором атрибутов 7 дней, а затем автоматически переводить их в агрегированное состояние или удалять.

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

sql
CREATE TABLE telemetry.wide_events (
    event_time DateTime64(3),
    service_name String,
    trace_id String,
    span_id String,
    -- Все дополнительные атрибуты храним в Map для гибкости
    attributes Map(String, String),
    duration_ms Float64
) 
ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_time)
ORDER BY (service_name, event_time)
-- Удаляем данные старше 30 дней автоматически
TTL event_time + INTERVAL 30 DAY;

С точки зрения экономики, использование Map(String, String) вместо создания сотен отдельных колонок под каждый новый атрибут позволяет сохранять предсказуемость схемы данных и упрощает управление ресурсами. Если вы видите, что какой-то атрибут стал использоваться постоянно (например, customer_id), его стоит вынести в отдельную колонку для ускорения поиска.

Заключение

Когда Wide Events становятся необходимостью

Переход от классической триады «метрики, логи, трейсы» к концепции Wide Events — это не просто смена формата хранения данных, а смена парадигмы. В традиционном подходе мы часто тратим лишние десять минут на то, чтобы сопоставить ID запроса из лога с графиком в Grafana и найти соответствующий спан в Jaeger. Wide Events убирают этот разрыв. Когда всё контекстное окружение — от версии приложения до ID пользователя и состояния базы данных — уже находится внутри одного события, поиск причины сбоя превращается из детективного расследования в обычную фильтрацию.

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

Как начать внедрение

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

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

json
{
  "timestamp": "2023-10-27T14:20:01.452Z",
  "level": "error",
  "service": "order-processor",
  "trace_id": "a1b2c3d4e5f6",
  "user_id": "user_987",
  "duration_ms": 450,
  "db_connection_pool_usage": 0.85,
  "cpu_usage_percent": 12.5,
  "message": "Failed to process payment"
}

В этом примере мы объединили лог, метрику задержки (duration_ms), состояние пула соединений (db_connection_pool_usage) и системную метрику (cpu_usage_percent). Теперь, если в Grafana вы увидите аномалию, вам не нужно переключаться между вкладками — вы просто берёте этот trace_id и видите полную картину.

Если вы используете стек Prometheus и Loki, стоит изучить возможности OpenTelemetry, который сейчас является стандартом де-факто для сбора таких данных. Он позволяет нативно формировать события, объединяющие разные типы сигналов.

Итоги внедрения

Wide Events работают эффективно, пока объем данных остается управляемым. Основные нюансы, к которым нужно быть готовым:

  1. Стоимость хранения. Объём данных вырастет, так как каждое событие становится «толще». Решение — агрегация старых событий и переход на более дешёвые типы хранилищ для долгосрочного архива.
  2. Кардинальность (Cardinality). Добавление слишком большого количества уникальных тегов (например, user_id) в классические метрики Prometheus «убьёт» его. Но в концепции Wide Events, где данные хранятся в колоночных БД или специализированных системах вроде ClickHouse, высокая кардинальность — это не проблема, а преимущество.
  3. Сложность генерации. Приложению нужно больше ресурсов, чтобы собрать весь контекст перед отправкой события.

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

FAQ

FAQ

Нужно ли полностью отказываться от метрик?

Нет, отказываться от классических метрик не стоит. Wide Events не заменяют их, а дополняют, создавая контекст. Метрики (например, счётчики запросов или гистограммы задержек в Prometheus) идеально подходят для построения графиков в Grafana и настройки алертов на уровне инфраструктуры. Они отвечают на вопрос «что происходит?».

Wide Events отвечают на вопрос «почему это происходит?». Если ваш алерт в Alertmanager сработал на всплеск 500-х ошибок, вам всё равно нужно быстро перейти от графика к конкретному событию, где в одном объекте собраны и ID запроса, и параметры пользователя, и состояние системы. Это обеспечивает предсказуемость расследования инцидентов.

Как Wide Events влияют на объем передаваемых данных?

С точки зрения объёма передаваемых данных, Wide Events могут быть «тяжёлыми». Вместо того чтобы отправлять три разных пакета (метрику, лог и спан трейса), вы отправляете один, но насыщенный атрибутами. Если в каждом событии передавать десятки неиндексируемых полей, нагрузка на сеть и хранилище вырастет.

Чтобы контролировать этот процесс, я рекомендую использовать стратегии сэмплирования. Например, в OpenTelemetry можно настроить правила, чтобы отправлялись полные события только для ошибок или для определённого процента успешных запросов.

Пример конфигурации процессора в OpenTelemetry Collector, который помогает фильтровать данные:

yaml
processors:
  filter:
    error-only:
      traces:
        span:
          # Оставляем все спаны, где есть статус ошибки
          status.code: SpanStatusCodeError

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [filter/error-only, batch]
      exporters: [otlp]

Какие инструменты лучше всего подходят для работы с широкими событиями?

Для работы с таким подходом нужны системы, которые умеют эффективно индексировать и хранить структурированные данные (JSON или Protobuf), а не просто текстовые строки.

  1. ClickHouse. Это мой фаворит для хранения Wide Events. Благодаря колоночному хранению он позволяет быстро выполнять аналитические запросы по огромным массивам структурированных событий.
  2. Grafana Tempo / Loki. Если вы уже используете стек Grafana, эти инструменты позволяют связать трейсы и логи через общие лейблы.
  3. Honeycomb. Один из первопроходцев концепции, который изначально строился вокруг анализа событий, а не просто метрик.

Поможет ли это при отладке микросервисов?

Это, пожалуй, главный профит. В классической схеме при отладке микросервисов вы часто сталкиваетесь с «разрывом контекста»: в логах одного сервиса есть request_id, в трейсах другого — trace_id, а метрики вообще не показывают, к какому конкретному пользователю относился сбой.

Wide Events объединяют всё это в единую структуру. Когда вы смотрите на событие в распределённой системе, вы сразу видите всю цепочку:

  • Какой сервис инициировал запрос.
  • Какие параметры были в заголовках.
  • Какая версия кода (commit hash) сейчас развёрнута.
  • Какая задержка возникла на конкретном узле.

Это превращает поиск ошибки из процесса сопоставления разрозненных данных в линейное чтение истории одного конкретного запроса.

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

Чем Wide Events отличаются от обычных трейсов?

Трейсинг фокусируется на пути прохождения запроса через систему, связывая вызовы между сервисами. Wide Events идут дальше: они упаковывают в одно событие не только путь запроса, но и все сопутствующие контекстные данные — состояние системы, параметры конфигурации и метрики в момент выполнения. Если трейс — это маршрут на карте, то Wide Event — это подробный отчет о поездке с указанием погоды, давления в шинах и расхода топлива.

Не вырастет ли объем передаваемых данных слишком сильно?

С точки зрения объема данных, переход на Wide Events действительно требует пересмотра стратегии хранения. Однако вместо того, чтобы дублировать одни и те же данные в разных форматах (метрики, логи, трейсы), вы храните одну структурированную запись. С практической точки зрения, это позволяет снизить нагрузку на поиск и агрегацию, так как вам больше не нужно выполнять сложные Join-операции между разными базами данных, чтобы сопоставить лог с метрикой.

Можно ли внедрить этот подход в уже существующую инфраструктуру?

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

Как Wide Events помогают сократить время поиска причин сбоя (MTTR)?

В традиционном подходе инженер тратит значительную часть времени на корреляцию данных: сопоставление временных меток в Grafana с записями в Elasticsearch. Wide Events устраняют этот этап, так как контекст уже находится внутри события. Когда вы видите аномалию, вам не нужно переключаться между вкладками — все необходимые детали, от версии приложения до загрузки CPU в момент запроса, доступны в одной строке.

Поделиться:TelegramX / TwitterVK