
Тишина может означать что угодно
Разбор этой проблемы для Node.js-сервиса с запланированными импортами формулирует её прямо: подсчёт завершений не говорит, была ли работа вообще нужна, остановилась ли очередь, оказался ли источник пустым или отклонил всё на этапе проверки — все эти состояния схлопываются в один и тот же тихий график. Соблазн здесь понятен: один числовой ряд дешевле читать, чем парсить свободный текст логов, поэтому метрики кажутся самодостаточным решением. Но у метрик есть и обратная сторона — они агрегируют, то есть намеренно стирают детали отдельного запуска. Если дашборду нужно быстро отвечать на регулярный вопрос «всё ли в порядке сейчас», агрегация — это достоинство. Если нужно понять, что произошло с конкретным прогоном в три часа ночи, та же агрегация становится препятствием.
Отсюда рождается ложная дилемма «логи или метрики», которая на самом деле плохо сформулирована. Вопрос не в том, какой бэкенд лучше рисует линию на графике, а в том, может ли команда различить три принципиально разных состояния одного и того же запланированного задания: оно не было запущено планировщиком, оно было запущено, но не завершилось, или оно завершилось, но не дало полезного результата. Это и есть рабочий критерий выбора — не абстрактное «логи против метрик», а проверка на объяснимость конкретного отказа.
Три состояния вместо одной цифры
Практическая схема, которую стоит разобрать, использует три сигнала на ограниченный набор измерений: число запланированных запусков, число стартовавших запусков и число завершённых с результатом. Четвёртый счётчик — отказы — может быть полезен операционно, но не годится как доказательство живости системы, потому что процесс способен зависнуть, так и не выбросив ошибку. Разница между «тихо, потому что нечего обрабатывать» и «тихо, потому что всё сломалось» видна именно на стыке этих трёх счётчиков, а не внутри одного из них.
Но счётчики — это только половина картины. Чтобы восстановить, на каком именно шаге остановился конкретный прогон, нужен не агрегат, а последовательность событий с привязкой к идентификатору запуска: запланирован, стартовал, завершился или упал, с таймстампом и контекстом каждого перехода. Здесь и проходит разумная граница между двумя инструментами телеметрии.
| Слой | Что измеряет | Сильные стороны | Ограничения и риски | Когда нужен |
|---|---|---|---|---|
| Метрики (счётчики/гистограммы) | Агрегированные количества по ограниченным категориям | Быстрые графики, дешёвые алерты, стабильная кардинальность | Не объясняет конкретный прогон; рост меток ломает агрегат | Дашборд дежурного, пороговые алерты |
| Структурированные события | Последовательность стадий одного запуска с идентификатором | Восстановление хода инцидента, корреляция между сервисами | Дороже хранить подробно; требует отдельной политики доступа и удаления | Расследование конкретного сбоя |
| Свободные логи | Произвольный текст сообщений | Гибкость при отладке, не требует заранее продуманной схемы | Формулировки меняются при рефакторинге, парсинг становится частью контракта дашборда | Точечная отладка, не основа для регулярных графиков |
Ключевая деталь здесь — не «логи плохие», а то, что превращение свободного текста в постоянный источник данных для дашборда скрыто перекладывает бремя стабильности на формулировки сообщений, которые никто не обязан держать неизменными. Структурированные события решают эту проблему частично, потому что у них есть схема, но они всё равно не заменяют агрегированный счётчик там, где нужен быстрый ответ на повторяющийся вопрос вроде «сколько успешных импортов за последний час».
Почему идентификатор запуска убивает метрику, но необходим событию
Здесь стоит отдельно остановиться на том, почему руками добавленный runId в метку метрики — это не мелкая небрежность, а архитектурная ошибка. В модели Prometheus каждая уникальная комбинация имени метрики и значений её меток образует отдельную временную серию. Если runId уникален для каждого запуска, а запусков тысячи, метрика перестаёт быть ограниченным агрегатом и превращается в постоянно растущий список рядов — именно так на практике возникает проблема кардинальности, когда память и скорость запросов начинают деградировать без видимого роста нагрузки. Разбор этой темы прямо называет источники такого роста: неограниченные значения вроде ID пользователя, ID запроса или необработанного URL.
В событийном следе та же самая метка — абсолютное благо. Чтобы восстановить цепочку «запланирован → стартовал → не завершился», нужен стабильный логический идентификатор запуска, который связывает все три записи между собой. Поэтому в предложенной схеме runId пишется только в событие, а метрика получает лишь низкокардинальные категории — класс фида, стадия, исход. Это не произвольное решение дизайна, а прямое следствие того, для какой задачи создаётся каждый из двух сигналов.
flowchart TD A[Запланирован] --> B[Стартовал] B --> C[Завершился / упал] A --> D[Счётчик: низкая кардинальность] B --> D C --> D C --> E[Событие: runId, стадия, время] D --> F[Дашборд дежурного] E --> G[След для расследования]
Один и тот же прогон задания фиксируется дважды, но с разной целью: агрегированная сторона идёт на график и в алерт, детализированная — в хранилище событий, к которому обращаются только при расследовании. Разделение по кардинальности здесь совпадает с разделением по назначению, и это не совпадение, а закономерность.
Два календаря для удаления данных
Третья ось, которую легко упустить, — это срок хранения. Метрики и события обслуживают разные задачи, а значит, и политика их хранения и удаления обязана быть разной: агрегированная история может быть полезна для анализа трендов намного дольше, чем детальные события, которые несут больше контекста о конкретных людях и объектах. Здесь стоит сделать оговорку: GDPR определяет персональные данные широко и устанавливает принципы минимизации и ограничения хранения, но конкретные выводы о соответствии конкретного решения этим требованиям — вопрос юридической проверки, а не инженерного самодиагноза. Псевдонимизация идентификатора снижает риск, но не отменяет режим персональных данных, если связать запись с человеком всё ещё можно при наличии дополнительной информации. Практический вывод для дизайна телеметрии: решение о том, что храним и на сколько, нужно принимать до выбора хранилища, а не после.
Как проверить схему, прежде чем на неё полагаться
Прежде чем закладывать такую схему в продакшн, разумно прогнать её через намеренный тест: создать четыре состояния в тестовой среде — нормальный результат, пустой результат, отклонённый результат и прогон, превысивший дедлайн — и проверить, показывает ли график провал, а след событий — последний завершённый шаг. То же самое справедливо и для расследования уже случившегося инцидента: полезная хронология строится не как пересказ чата дежурных, а как ряд фактов с явно помеченной неопределённостью, где сообщение «ошибки начались минут десять назад» — это наблюдение, а не доказанная граница влияния. Разделение факта и интерпретации, знакомое по практике восстановления таймлайнов, работает и на уровне телеметрии: график — это факт агрегата, событие — факт последовательности, а причина сбоя — это уже интерпретация, которую строит человек поверх обоих источников.
Короткий вывод
Ни logs-only, ни metrics-only подход не является универсально правильным — каждый решает свою часть задачи и плохо справляется с другой. Рабочая схема для небольшого продукта получается не из поиска «идеального» хранилища, а из намеренного разведения ролей: немногочисленные стабильные метрики — для быстрых вопросов дежурного, отдельный структурированный след событий — для восстановления хода конкретного сбоя, и у каждого из них — собственная политика кардинальности и собственный срок жизни данных. Если дашборд должен оставаться быстрым и предсказуемым, а расследование — полным и воспроизводимым, пытаться заставить один источник данных делать и то, и другое — это не экономия, а отложенная цена, которую команда заплатит в момент, когда тишина на графике окажется не пустым источником, а сломанной системой.


