Как мониторить Java-приложения: метрики, алерты и правило 80/20
Хороший мониторинг помогает быстро понять, что происходит с приложением и куда смотреть в первую очередь. Для этого не нужно пытаться измерить всё: базовый набор технических метрик покрывает большинство типовых проблем, а бизнес-метрики, SLO и анализ аномалий помогают заранее замечать нетипичные отклонения. В Календаре мы называем этот подход правилом 80/20.
Всем привет! Меня зовут Настя, я бэкенд-разработчик в Яндекс 360 и отвечаю за надёжность Календаря. В этой статье я покажу, какие метрики стоит взять за основу, как выбирать полезные алерты и чем дополнять базовый набор для оставшихся 20%.
Подписывайся на наши соц сети:
Почему перебор метрик убивает мониторинг
Если собрать разработчиков и спросить у них, хорошо ли, когда метрик много, то подавляющее большинство с этим тезисом согласится. К слову, я тоже. Нам хочется понимать, хорошо ли у нас всё работает, и вовремя узнавать, если где-то что-то идёт не так. Мы хотим кучу метрик, хотим оперативного подсвечивания инцидентов, вызова дежурных и прочего.
Это одна сторона медали. Вторая — не такая блестящая. В какой-то момент (особенно это характерно для зрелых продуктов) мы понимаем, что чем больше новых метрик мы добавляем, тем больше появляется информационного шума. Дашборды превращаются в нечто бездонное, куда не хочется даже заглядывать. А вот когда реально что-то случается, то никто не знает наверняка, на какой именно график и на каком дашборде надо смотреть, чтобы увидеть нужную метрику и её состояние. Например, когда у вас 1 000+ алертов, которые безудержно флапают.
Что делать? Как и везде в жизни, тут хорошо бы достичь золотой середины — добавить ровно столько метрик, сколько надо для получения пользы, но не столько, чтобы утонуть в них.
Два главных принципа здорового мониторинга
Принцип № 1. Всегда имейте в виду, что метрики — это маяки, которые сигнализируют о наличии проблемы, а не микроскопы, которые детально показывают вам, какая именно из шестерёнок огромного механизма барахлит. Или, если хотите другую аналогию, градусники. Градусник просто показывает, есть температура или нет, это бинарка. А вот уже на основании показаний градусника мы идём к врачу, который помогает поставить диагноз и назначить план лечения.
С алертами та же история: они просто показывают, есть проблема или нет, без указания на конкретную строку в коде, которую стоит починить
Принцип № 2. Нужно стремиться к балансу. Само собой, нам хочется замониторить все наши сервисы так, чтобы нигде и никогда не пропустить ни одну проблему. Однако очень наивно полагать, что такой дашборд на 1 000+ метрик будет полезен и что на него вообще будут смотреть. Так что тут нужен баланс, без этого никуда.
Базовые метрики
Почти любой бэкенд можно представить вот такой схемой. У нас тут есть синхронный API (у вас может не быть, но обычно есть). Бэк ходит в другие системы и другие бэки, может что-то складывать в очередь, что-то оттуда читать, что-то класть в БД или читать оттуда — в общем, взаимодействует.
RED-метрики бэкенда — HTTP/gRPC, клиентов, очередей и баз данных
Вернёмся к принципу 80/20, о котором я говорила в самом начале. Что это за цифра, 80%, и откуда она взялась?
Как показывает опыт, большинство инцидентов можно поймать базовым набором метрик. И лишь для по-настоящему сложных ситуаций вам пригодятся бизнес-специфичные метрики — те, для которых в целом прописана определённая бизнес-логика.
Вернёмся к схеме для наглядности.
Начну с HTTP/gRPC-сервера в нашем синхронном API. Тут золотые метрики — request, error и duration. Они могут быть вам знакомы как RED-метрики. Что тут важно — их можно применить почти для любой системы, которая так или иначе завязана на запросы извне.
Мы замеряем три вещи:
- Количество запросов. Тут мы можем увидеть, если кто-то решил спустить на нас DDoS, или, наоборот, перестал это делать, а также если у нас куда-то утекает трафик.
- Ошибки. Причём именно те ошибки, которые наш сервер отдаёт клиенту. В случае HTTP — это 4xx и 5xx, в случае gRPC — unavailable и прочее.
- Длительность запросов. Это помогает узнать, что сервер где-то начал подтормаживать или же, наоборот, очень быстро отдаёт что-то странное.
Если завести себе три таких графика, то можно будет понять практически любую проблему на стороне сервера.
Но кроме сервера есть ещё и клиент, для которого тоже работают эти метрики.
Когда наш бэк идёт в любую другую систему (да хоть в соседний бэк), мы делаем то же самое. Берём те же золотые RED-метрики и те же самые ошибки, смотрим на длительность запросов. Так точно можно заметить, если какой-то сервис, в который мы ходим, начал влиять на другой сервис, что повлекло проблемы.
Метрики очереди: producer, consumer и критическая важность лагов
Проблемы с очередями появляются не только когда мы пишем в них слишком много, но и когда пишем слишком мало.
И смотреть тут надо на такие же метрики — сколько сообщений пишем в секунду, стало их слишком много или слишком мало (запросы), по какой причине отвалилась сеть и мы перестали писать в очередь (ошибки). Ну и тайминги.
Но именно для очередей существует одна особенная метрика — лаг. Например, мы записали в очередь ряд данных, из-за этого в ней скопилось определённое количество сообщений, и по какой-то причине читатели их не читают (могли развалиться, или в целом случился некий баг).
Так что это очень важная метрика для очередей — количество записанных сообщений минус количество прочитанных. Получаем разницу позиций в очереди.
Метрики баз данных
Тот же набор RED-метрик пригодится и для мониторинга состояния баз данных. Отдельно стоит следить за connection pool: успешные запросы к БД ещё не гарантируют, что с пулом соединений всё в порядке.
При этом важно охватывать всю цепочку. На стороне приложения есть клиентский пул, а между приложением и БД может работать отдельный серверный пул, например Odyssey. Если наблюдать только за одним из них, отклонение на другом уровне останется незаметным.
Метрики клиентского пула обычно можно подключить через Spring Boot и Micrometer. Если в архитектуре есть отдельный серверный пул, его нужно мониторить на стороне компонента, который этим пулом управляет. Такой контур помогает заметить риск заранее — до того, как он повлияет на запросы.
JVM-метрики — что Spring Boot даёт сразу, а что вам придётся писать руками
Итак, это было про бэкенд. Теперь давайте про его внутрянку — про Java.
Тут тоже есть свои золотые метрики, но их триада немного иная — это ресурсы: процессор, память и диск.
Вы, возможно, спросите, где же сеть, и будете правы: это тоже важная штука. Но увы, из Java-приложений очень сложно собирать сетевые метрики — их лучше собирать из Kubernetes. Так что остановимся на этих трёх. Их можно без проблем собирать с вашего контейнера и через Spring Boot.
Процессор — как вовремя распознать заблокированные потоки
Мы же не просто используем процессор, а запускаем на нём определённые потоки, у которых могут быть разные состояния. Например, поток может чего-то ждать, поток может что-то делать, а ещё поток может быть чем-то заблокирован.
В плане метрик это значит, что нам хорошо бы видеть и понимать: какие-то потоки внезапно заблокировались, зависли или вообще закончились. В Spring Boot можно смело смотреть на состояние всех потоков в сумме.
Но ещё у нас есть тред-пулы, которые тоже могут (причём независимо от системы) заканчиваться. Поэтому если мы хотим мониторить происходящие на пулах операции — например, в ForkJoinPool или каком-то другом пуле, то просто так этого сделать уже не получится. Их надо будет замониторить самостоятельно, включая и ForkJoinPool — он хоть и создаётся штатными средствами, но мониторить его надо самим.
Вот простая команда, после вызова которой в нашем регистре появятся метрики по пулам.
ExecutorServiceMetrics.monitor(
meterRegistry,
ForkJoinPool.commonPool() ,
"fork.join.pool.common"
);
ExecutorService myPool =
Executors.newFixedThreadPool (8);
ExecutorServiceMetrics-monitor(
meterRegistry,
myPool,
"my.executor.service"
) ;
Таким же образом можно обернуть каждый нужный вам thread pool, получив красивые графики.
Память — что и как может утечь здесь
Тут тоже просто не будет — мы же живём в JVM, значит память у нас managed, да ещё и делится на Heap и Non-Heap. Спасибо, что все области памяти и её регионы точно так же репортятся. Можно из коробки сразу пойти и посмотреть, сколько у нас памяти на Direct Buffers, к примеру.
Здесь важно помнить, что радостно утекать у нас может не только Heap, но и всё остальное, хотя все привыкли прежде всего следить за Heap. Те же Direct Buffers могут вполне себе утекать, пару лет назад была утечка в gRPC-java, так что те, кто обновил эту библиотеку до новой версии, могли внезапно обнаружить утечки памяти и постоянное падение приложений. Просто из-за того, что обновили библиотеку. Чтобы такое заметить, надо мониторить именно разные регионы памяти.
К слову о Heap: у нас есть целый garbage collector, который генерирует нам разные задержки. В Spring Boot мы можем уже привычным образом посмотреть, сколько пауз у нас было и сколько по времени они длились.
Задержки могут генерировать ещё и блокировки, safepoint’ы и многое другое. И вот их уже из коробки не помониторишь. Например, для тех же safepoint’ов надо писать свой собственный код — нужна JVM, которая использует Java Flight Recorder, и надо подписаться на события, сообщающие о завершении safepoint’ов.
Если вам нужен пример такого кода для измерения safepoint’ов — держите.
Имейте в виду, что JVM по умолчанию не даст вам понять, что именно это был за safepoint, нельзя отделить один тип от другого и непонятно, что тут было блокировкой, а что — тем же самым GC. Можно лишь увидеть, что что-то где-то задерживается. Почему — секрет.
Задержек же может быть великое множество, кроме safepoint’ов. Мы живём не просто в JVM, а на большой железке, на которой запущена и ОСь, и, скорее всего, развёрнут кубер со своим набором подов, а в подах — контейнеры, где и обитает наша JVM. И ещё повезёт, если всё это и правда работает просто на железке. А если в облаке, где таких слоёв и переменных становится ещё больше?
У разных слоёв — приложения, JVM, контейнера, оркестратора, ОС и инфраструктуры — свои наборы метрик и зоны ответственности. Поэтому мониторинг стоит строить в двух уровнях: подробная картина каждого слоя плюс общий обзор всей цепочки. Так сигналы можно сопоставлять между собой, не превращая дашборд в бесконечное полотно.
Так вот, если мы и завели такой большой дашборд, мы можем увидеть там какие-то миллисекундные задержки, даже наносекундные. При этом можно не заметить, как в сумме они складываются в уже довольно значимый лаг, из-за которого приложение будет отдавать данные клиентам куда дольше задуманного.
Скажем, если ваше приложение latency sensitive, вы увидите, что ваш P99 улетает куда-то в космос. А потом из этого космоса возвращается. Причина, скорее всего, будет как раз в одном из этих слоёв пирога.
Но решение есть. Чтобы смотреть на все такие задержки в сумме, создали такую прекрасную штуку, как hiccups. Идею изложил Gil Tene в далёком 2015-м, и он же написал библиотеку для их измерения.
В чём тут суть? Сколько времени будет выполняться код, который не делает ровным счётом ничего?
В идеальном мире ответом было бы «Нисколько». Но мы, увы, не в идеальном мире, и на этой метрике будут видны все возможные задержки на уровнях ниже этого кода. Причиной задержки мог стать шумный сосед в кубере, планировщик, который не выдал CPU, или что-то ещё. По этой метрике можно увидеть все слои сразу, даже не зная, какие слои вообще есть, и не имея доступа к конкретному. Вы сможете понять главное: или в вашем коде ошибка, или же что-то пошло не так слоем ниже. А потом сходить к ответственным за этот слой.
Увы, исходная библиотека jHiccup была рассчитана на то, чтобы писать логи на диск, а потом с этого диска красиво выводить куда-то данные. Но в 2026-м код принято писать с помощью Micrometer и выгружать в современные системы мониторинга, так что этот код надо адаптировать под реалии. Вот ссылки на исходный код и на переписанный для Micrometer — берите тот, что вам удобнее.
С базовыми метриками сложно перебрать — их в целом-то фиксированное количество. Почти все они есть из коробки, но Spring Boot не идеален, так что немного кода руками написать придётся.
Бизнес-метрики
Мы обсудили, как отлавливать 80% метрик. Теперь давайте посмотрим, как поймать остальные 20%, специфичных для бизнес-задач.
Самое главное — понять, чем занимается ваш сервис. У вас может быть множество важных бизнес-метрик, например:
- число успешных заказов;
- конверсии любого рода;
- качество поисковой выдачи (если вы поисковый движок);
- процент ошибок на HTTP/gRPC.
Как только вы поймёте, что в вашем сервисе самое важное и олицетворяет его успех, в ту сторону и надо копать.
Как выбрать бизнес-метрики
Способ 1 — отталкиваться от инцидентов. Например, вы определили, чем занимается ваш сервис, но тут случился какой-то инцидент, который вы не заметили на метриках. Недовольные из-за инцидента клиенты — это не только техническая проблема, но ещё и бизнесовая. Так что такие проблемы хочется видеть заранее.
Тут полезно будет посмотреть на ваши инциденты за последний месяц и отметить, когда они случились и что именно вы не заметили. Заведите метрику, алерт и график, чтобы такое не пропускать.
Базовый пример — клиенты жалуются, что у них не срабатывают скидочные купоны. Можно начать смотреть на процент ошибок при применении таких купонов. А ещё можно немного генерализовать имеющиеся метрики, за которыми вы уже следите, — брать что-то шире, чем конкретный инцидент с купоном. Скажем, замерить не только случай несрабатывания купона, но и любые проблемы с промокодами, скидками и вот такими смежными штуками.
ОК, вы выбрали метрики, дело за малым — завести алерты. Кажется, что тут вообще ничего сложного, просто прописать пороговые значения — и всё. Но нет.
Вот пример из Календаря. После одного из изменений сценарий создания встречи стал отправлять в смежный сервис заметно больше запросов, чем обычно. При этом технически всё работало: события создавались, запросы завершались успешно, ошибок не было. Если смотреть только на error rate, такое изменение останется незаметным. А вот график межсервисного трафика сразу показывал аномалию. Этот кейс хорошо иллюстрирует ограничение базовых метрик: успешный запрос ещё не означает, что система ведёт себя ожидаемо. Поэтому важно следить не только за ошибками, но и за отклонениями от нормального профиля нагрузки.
Поэтому мы мониторим не только пороговые значения, но и возможные аномалии. У Prometheus, к счастью для всех нас, есть готовое решение.
В чём соль: для мониторинга аномалий нужна математика, позволяющая понять, что мы правда имеем дело именно с аномалией. Например, надо учитывать сезонность. Если ваш основной трафик приходится на дневное время, а ночью он почти нулевой, то тут хочется, чтобы по ночам не зажигалось ничего лишнего на дашборде. То же самое с недельной сезонностью: если ваш трафик активен только по будням, а в выходные вашим сервисом не пользуются, это тоже нужно учитывать.
Это уже готовое решение, вам просто надо на метрику повесить лейбл и указать, что ваша аномалия будет называться, скажем, demo request. Тип этой аномалии — именно реквесты, и это не какая-то gauge-метрика, которая может расти от 0 до 100 в процентах, а реквесты, которых может быть любое количество, вплоть до бесконечности.
- record: anomaly:request:rate5m
expr: sum(rate(duration_milliseconds_count[5m])) by (job)
labels:
anomaly_name: "otel_demo_requests"
anomaly_type: "requests
В самом же алерте указываем, что хотим его зажигать, если метрика находится ниже или выше границы аномалии (то есть выбивается из допустимого диапазона).
- alert: AnomalyDetected
for: 5m
expr: |
metric < lower_band
or
metric > upper_band
Визуально на графике это будет выглядеть примерно так:
Что мы видим: у нас была метрика, которая всегда была где-то в районе нуля, а затем начала резко куда-то расти. Видно, что аномалия зажигается именно в момент роста и немного после него. А затем уже считается, что аномалии (роста) нет и метрика снова в какой-то константе (просто новой). Это даёт вам возможность увидеть, что где-то что-то произошло, но при этом не получить значительных флапов, потому что сегодня это была аномалия, а завтра её просто не будет в то же время.
Способ 2 — определить SLO (Service Level Objective). То, что ваш сервис должен выполнять с точки зрения бизнеса. В контексте бизнес-метрик Календаря это договорённости вида «95% запросов на создание встречи завершаются за 200 мс или меньше».
Почему это полезно: так мы получаем фиксированный порог того, что можно считать проблемой. Само собой, как программисты, мы хотим, чтобы всё обрабатывалось за 0 мс и было 100% запросов без ошибок и задержек, но реальный мир вносит коррективы. Так что договаривайтесь с бизнесом о таких цифрах и делайте это с умом.
Onepager и drilldown для ускорения диагностики
Итак, нужные метрики выбраны. Теперь разберёмся, как разместить их на дашборде и не перегрузить его.
Есть две техники.
Первая — onepager: обзорный дашборд, который помещается на одном экране. Он помогает быстро оценить состояние системы и понять, куда перейти за деталями.
На onepager каждый компонент можно показать отдельным цветовым индикатором. Красный означает, что нужна быстрая реакция, жёлтый — что есть отклонение, которое стоит проверить, зелёный — что всё работает штатно
Вторая — drill-down: переход от общего индикатора к подробным метрикам конкретного компонента
Красная клетка на дашборде намекает на проблему, можно посмотреть, что за ней скрыто: дриллдаун говорит нам, что мы хотим перейти из верхнеуровневой картины на картину поменьше. Де-факто тут получается такой иерархический дашборд.
Демонстративный пример нагляднее:
На дашборде выбрано пять разных функциональностей, я вижу тайминги и проценты ошибок. Красные клетки — я хочу посмотреть на read scheduler, кликаю на неё и перехожу на дашборд моего сервиса, который именно за это и отвечает.
На верхнем дашборде показаны пять функций, их время ответа и доля ошибок. Индикатор read scheduler показывает отклонение — открываем дашборд связанного сервиса. Там видно, что выросло время ответа БД и сервера; дальше можно перейти к графикам конкретных баз данных.
Делаем алерты понятными с помощью ранбуков
Наверняка вам не хочется оказаться в ситуации, когда алерты флапают, зажигаются, дежурный не спит ночами и не может уже смотреть на всё это.
Что делать? Использовать ранбуки!
Если алерт загорелся, должно быть сразу понятно, что с ним делать. Ситуации, когда алерт загорелся, но никто не знает, что делать, — это ситуации про плохой алерт, их в идеале и быть-то не должно.
Ранбук подразумевает, что у каждого алерта должно быть подробное описание, инструкция, которой сможет воспользоваться любой разработчик, независимо от его опыта и степени погружённости в продукт.
Вот пример. Алерт про то, что мой сервис стал потреблять более 85% CPU. Я расписываю шаги.
- Прежде всего я хочу проверить релизы и запуски, вдруг там что-то недавно менялось. Если да — скорее всего, захочу откатиться на версию без проблем, чтобы убрать фактор релиза.
- Проверю RPS. Если сервис под DDoS, то рост CPU — это норма, можно найти виновника, пожаловаться на него или включить rate limiter. А может, это вообще garbage collector.
- Проверю использование памяти. Если там что-то подозрительное, сниму heap dump.
И так с каждым алертом. А если придумать ранбук не получается — подумайте, нужен ли вам вообще этот алерт.
Небольшой итоговый чек-лист
- Подключайте к вашему приложению Spring Boot Actuator: там очень много полезного.
- Следите за hiccups — просто чтобы замечать все возможные задержки на всех уровнях ниже приложения и понимать, что у вас, например, заканчиваются ресурсы.
- Используйте анализ аномалий — отличная вещь для того, чтобы увидеть, что что-то идёт не так, но недостаточно «не так», чтобы затриггерить какой-то из ваших текущих алертов.
- Пересмотрите свои дашборды. Скорее всего, вам тоже захочется сделать их иерархическими, чтобы в них было проще ориентироваться.
- Пишите ранбуки. Они сделают жизнь проще. Гораздо проще.
