Содержание урока · 10 разделов
- Типы метрик Prometheus: карта из четырёх сущностей
- Counter и Gauge: счётчик против датчика
- Типичная ошибка: сырой счётчик на графике
- Четыре типа метрик: сравнение по конструкции
- Histogram: латентность по бакетам
- Summary: квантили на стороне клиента
- Спорное мнение: summary почти всегда лишний
- Песочница урока
- Коротко: что запомнить
- Вопросы и ответы
Владелец магазина хочет знать две вещи: сколько всего заказов создано и сколько прямо сейчас висит в очереди на обработку. Числа похожи, но живут по разным законам: первое только растёт, второе ходит вверх и вниз. Если мерить оба одной метрикой, одна из двух историй будет врать. В Prometheus за эти два закона отвечают разные типы, и всего их четыре: Counter, Gauge, Histogram и Summary. Тип выбирается один раз: он вшивается в формат экспозиции — строку TYPE и суффиксы имён, — поэтому «сменить тип» на живой метрике нельзя; по факту вы заведёте новую метрику, а старые дашборды и алерты замолчат. Поэтому этот урок — про выбор, а код с типами Counter и Gauge пойдёт следом: в следующих двух уроках каждый из них получит свой разбор со всеми методами и ловушками.
Типы метрик Prometheus: карта из четырёх сущностей#
Counter — счётчик, который может только расти и сбрасывается в ноль при рестарте процесса: запросы, ошибки, обработанные байты. Gauge — датчик текущего значения, который двигается в обе стороны: длина очереди, занятая память, активные сессии. Histogram — распределение: каждое измерение падает в заранее заданные бакеты, из которых уже на стороне Prometheus считаются доли и перцентили — про латентность и размеры. Summary — второй способ считать квантили, но на стороне клиента: сервис сам агрегирует наблюдения и отдаёт готовые квантильные значения. Первые два типа — про «сколько всего» и «сколько сейчас», третий и четвёртый — про «как распределено». Типичный сервис на Go большую часть времени живёт на Counter и Histogram: рост и латентность — главные сигналы здоровья; Gauge добавляется под очереди и пулы, Summary — точечно. Дальше по карте — два самых ходовых типа в деталях, а гистограммы с summary получат собственные уроки после темы лейблов.
Counter и Gauge: счётчик против датчика#
Разницу удобно держать в голове через пару «пробег автомобиля» и «спидометр». Counter — пробег: число с запуска, убывать не может физически, даже когда события закончились, значение просто стоит на месте. Gauge — спидометр: сейчас сто двадцать, через минуту ноль, и оба показания честные. Отсюда рабочая проверка при выборе типа: спросите себя, что означает ноль. Для счётчика ошибок ноль — «с запуска ошибок не было», нормально. Для числа активных пользователей ноль — «прямо сейчас никто не онлайн», тоже нормально, но смысл другой: это состояние, а не накопление. Самая живая ловушка — рестарт: счётчик обнуляется, датчик начинает с того, что вы в него записали. Prometheus знает про поведение счётчика и в запросах умеет склеивать сбросы; датчику склейка не нужна — у него нет истории, есть момент. Итого: растущее — Counter, текущее состояние — Gauge, и никогда наоборот.
Типичная ошибка: сырой счётчик на графике#
Ошибка интерпретации, на которой я ловлю почти каждого новичка. Счётчик запросов рисуют сырым значением: линия ползёт вверх, после деплоя падает в ноль и снова растёт. Человек, привыкший к датчикам, читает это как «трафик упал до нуля и восстановился» — и начинается поиск аварии, которой не было. На деле провал в ноль — рестарт процесса, а скорость по такой линии на глаз не оценить: рост с тысячи до двух тысяч за сутки и за год выглядит одинаково круто. Правильная картинка — прирост: в PromQL это rate, который сглаживает и сбросы, и рваный ритм событий. Пока PromQL не изучен, держите простое правило: сырое значение счётчика годится для лога, для графика — только скорость. Тот же принцип встретите у гистограмм: сырые бакеты там тоже никто не рисует, рисуют доли и квантили.
Когда тип спорит сам с собой, помогает вопрос к единице измерения: что будет, если сервис оставить в покое на час? Счётчик останется на месте — события кончились, накопление замерло. Датчик вернётся к естественному уровню: очередь разберут, сессии истекут, память отдаст сборщик мусора. Если значение само приходит в норму без вашего кода — это gauge; если оно навсегда хранит след прошлых событий — counter.
Два типа рядом: как это выглядит в Go
GOpackage main
import (
"github.com/prometheus/client_golang/prometheus"
)
var (
orders = prometheus.NewCounter(prometheus.CounterOpts{
Name: "shop_orders_total",
Help: "Всего созданных заказов.",
})
queue = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "shop_queue_size",
Help: "Текущая длина очереди заказов.",
})
)
func main() {
prometheus.MustRegister(orders, queue)
orders.Inc() // +1 заказ
queue.Set(17) // в очереди сейчас 17
queue.Dec() // один заказ обработан
}Counter создаётся через prometheus.NewCounter с CounterOpts, Gauge — через NewGauge с GaugeOpts; у обоих обязательны Name и Help. Методы разные по духу: у счётчика только Inc и Add с неотрицательным аргументом, у датчика — Set, Inc, Dec и Add с любым знаком. MustRegister регистрирует обе метрики в реестре по умолчанию. Полный разбор каждого типа — в двух следующих уроках.
Четыре типа глазами формата экспозиции
GO# HELP shop_orders_total Всего созданных заказов.
# TYPE shop_orders_total counter
shop_orders_total 812
# TYPE shop_queue_size gauge
shop_queue_size 17
# TYPE shop_request_duration_seconds histogram
shop_request_duration_seconds_bucket{le="0.1"} 240
shop_request_duration_seconds_bucket{le="0.5"} 380
shop_request_duration_seconds_bucket{le="+Inf"} 400
shop_request_duration_seconds_sum 92.1
shop_request_duration_seconds_count 400Тип вшит в экспозицию: у counter суффикс _total, у gauge одна строка значения, у histogram — семейство _bucket с меткой le плюс _sum и _count. Строка TYPE объявляет тип до строк значений. Как устроены бакеты и метка le — разбор в седьмом уроке про гистограммы; summary вместо бакетов покажет строки вида quantile.
Четыре типа метрик: сравнение по конструкции#
| Тип | Закон изменения | Что мерить | Ключевые методы в Go |
|---|---|---|---|
| Counter | только вверх, сброс при рестарте | запросы, ошибки, байты | Inc, Add с неотрицательным |
| Gauge | вверх и вниз | очередь, память, сессии | Set, Inc, Dec, Add |
| Histogram | наблюдения в бакеты | латентность, размер ответа | Observe |
| Summary | квантили на клиенте | перцентили одного процесса | Observe |
Histogram: латентность по бакетам#
Гистограмма не хранит каждое измерение — это разорвало бы кардинальность. Вместо этого вы заранее задаёте бакеты, пороги времени: сто миллисекунд, двести, пятьсот, секунда, две, пять. Каждое наблюдение увеличивает счётчики бакетов, чей порог превышен: ответ за триста миллисекунд добавит единицу в бакеты пятьсот, секунду, две и пять. Плюс копятся сумма и число наблюдений. Красота схемы в том, что клиентская часть дёшево стоит, а математика происходит в Prometheus: из накопленных бакетов считается, какая доля запросов быстрее порога, и оцениваются перцентили — медиана и девяносто девятый. Цена — приближённость: значение p99 вычисляется по границам бакетов, и при плохом выборе порогов оценка плывёт. Зато гистограммы агрегируются между инстансами: суммировать бакеты двадцати подов — нормальная операция. Это делает histogram выбором по умолчанию для латентности HTTP — в седьмом уроке соберём её руками и подберём бакеты.
Summary: квантили на стороне клиента#
Summary решает ту же задачу, что гистограмма, но с другой стороны: сервис сам держит скользящее окно наблюдений и отдаёт готовые квантили — скажем, медиану и девяносто девятый перцентиль — просто числами. Плюс честный: значение квантиля точнее, чем оценка по бакетам, и пороги угадывать не надо. Минус один и жирный: квантили нельзя агрегировать. Медиана двух подов — не сумма медиан, и Prometheus не может собрать p99 по кластеру из summary двадцати инстансов; каждая метрика замыкается на своём процессе. Плюс окно квантилей зашито в конфигурацию клиента и не меняется на лету. Поэтому в типичном Go-сервисе summary занимает нишу точечных метрик одного процесса — скажем, латентность внутреннего вызова конкретного инстанса, — а для HTTP-латентности и всего, что надо смотреть по кластеру, выбирают histogram. Сравнение с кодом и выводами — в восьмом уроке, где оба типа лягут рядом.
Спорное мнение: summary почти всегда лишний#
Спорное мнение, к которому я пришёл после пары лет с квантилями: в прикладном Go-сервисе summary почти не нужен. Его честные аргументы — точный квантиль и отсутствие подбора сетки — звучат весомо, пока сервис один. Но практически каждый сервис, доживший до успеха, обрастает вторым инстансом: горизонтальное масштабирование, выкатка в два региона, canary-релиз. С этого момента клиентские квантили замыкаются в себе, рядом всё равно появляется гистограмма, и два типа для одной величины содержать дороже, чем один. Граница, при которой я меняю мнение: фоновый процесс без горизонтального масштабирования и с тяжёлым, плохо предсказуемым хвостом — там summary с одной целью и окном MaxAge действительно проще, чем угадывать бакеты. Встроенный go_gc_duration_seconds к спору не относится: его писали не мы, и убрать его из реестра никто не просил.
Тип вшивается в экспозицию: у счётчика строка TYPE counter и суффикс _total, у гистограммы — набор _bucket, _sum и _count. Поменять тип на живой метрике нельзя: старое имя перестанет существовать, графики лягут, алерты замолчат тихо, без ошибок. Я видел команду, которая за один вечер «переименовала» counter в gauge и неделю не замечала, что алерт по скорости запросов не срабатывает: условие смотрело на имя, которого больше не было. Если тип выбран неверно — заводите новую метрику и гасите старую по плану.
Проверка: выбор типа
Нужно мерить число задач, которые прямо сейчас ждут обработки в очереди. Какой тип выбираете?
Проверка: закон Counter
Что произойдёт со значением Counter после перезапуска процесса?
Что покажет датчик после трёх операций
Прочитайте код и выберите, что он выведет:
queue.Set(42)
queue.Inc()
queue.Inc()
queue.Dec()Законный и незаконный Add
Прочитайте код и выберите, что он выведет:
orders.Add(0.5) // законно?
queue.Add(-3) // законно?Восстановите пару счётчик-датчик
Допишите типы в служебных строках экспозиции: у первой метрики счётчик, у второй — датчик.
Соберите экспозицию двух метрик
Расставьте строки экспозиции в правильном порядке: у каждой метрики сначала описание, потом тип, потом значение.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Карточки: типы метрик
Карта типов у вас в голове — время потрогать её руками. В следующем уроке ставим client_golang: go get, promhttp.Handler() и первая страница /metrics с бесплатными Go-метриками. Потом два урока-разбора: счётчик со всеми методами и ловушками, включая обнуление при рестарте, и датчик с Set, Inc и Dec и правилом, почему растущее нельзя мерить gauge-ом. Гистограммы и summary получат свои уроки после лейблов. Примеры запускаются локальным Go: песочница не поднимает HTTP-сервер, поэтому держите под рукой терминал. Если ваш сервис уже живёт на фреймворке, всё то же самое работает и там — например, в сервисе на gin счётчики встают рядом с middleware без правок маршрутов; карта всех двадцати пяти тем — на странице курса.
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- Типов четыре: Counter растёт, Gauge живёт состоянием, Histogram копит бакеты, Summary считает квантили на клиенте.
- Тип вшивается в экспозицию и имена — смена типа равнозначна заведению новой метрики.
- Проверка выбора: ноль у счётчика — «не было с запуска», у датчика — «пусто сейчас».
- Histogram агрегируется между инстансами и оценивает p99 по бакетам; summary точнее, но не агрегируется.
- В Go: NewCounter и NewGauge с Name и Help, регистрация MustRegister, дальше Inc и Add против Set, Inc, Dec.
- Типичный сервис живёт на Counter и Histogram; Gauge — под очереди и пулы, Summary — точечно.
Домашнее задание
Выберите любой сервис, который знаете: выпишите пять метрик, которые вы завели бы в первый день, и тип каждой. Особое внимание — метрикам, которые хочется мерить счётчиком, хотя они убывают: это самая частая ошибка выбора. Сверьте свои ответы с таблицей из урока; спорные случаи пригодятся на уроке про лейблы.
Личные заметкиПусто
Вопросы и ответы по уроку
Какой тип выбрать для времени ответа API?
Histogram по умолчанию: бакеты дёшевы на клиенте, а перцентили считаются на стороне Prometheus и агрегируются по кластеру. Summary имеет смысл для точечных метрик одного процесса, где агрегация не нужна.
Можно ли измерять деньги счётчиком?
Да, Counter.Add принимает дроби: сумма выручки — классическая бизнес-метрика с суффиксом вроде _total. Важно лишь, чтобы значение не убывало: возвраты заводят отдельной метрикой, а не отрицательным Add.
Что будет, если перепутать типы?
Графики либо бессмысленны, либо лгут: растущая величина в gauge не поддаётся анализу скорости, убывающая в counter паникует при попытке уменьшения. Тип меняется только заводом новой метрики.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Изучайте дальше
Следующая ступень учебного пути
WebSocket в Go: real-time чаты и уведомления
Курс WebSocket на Go: gorilla/websocket, эхо-сервер, hub-паттерн, комнаты, авторизация соединений, ping/pong, reconnect и масштабирование. 25 уроков.
- Поднимать WS-сервер и апгрейдить HTTP-соединения
- Писать readPump/writePump без гонок на каждое соединение
- Строить hub: регистрацию, рассылку и комнаты
- Авторизовывать соединения и ограничивать сообщения