Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 5 из 25 ~16 мин +20 XP

Gauge-метрика: что это и как применять в Go

Gauge-метрика — это датчик текущего состояния в Go: prometheus.NewGauge хранит длину очереди, занятую память и число активных пользователей, а Set, Inc и Dec двигают значение в обе стороны. Разберём, что такое gauge, его методы, живой пример и ловушку растущих значений.

Содержание урока · 11 разделов

Prometheus gauge в Go: Set, Inc и Dec#

После счётчиков мир выглядит неудобно: не всё в системе растёт. Очередь задач то пустеет, то переполняется; память то освобождается, то кончается; пользователи заходят и выходят. Для таких величин у Prometheus есть Gauge — датчик текущего значения. Он обязан быть скучным на фоне драмы с рестартами счётчиков: gauge просто хранит число, которое вы записали последним, и отдаёт его при каждом скрейпе. Сложность gauge не в механике, а в дисциплине: нужно вовремя писать актуальное значение и не пытаться мерить им то, что только растёт. В уроке — NewGauge и все методы, два живых примера: очередь, которая обновляется событиями, и число задач из базы, которое синхронизируется таймером.

Создание зеркально счётчику: prometheus.NewGauge со структурой GaugeOpts из Name и Help. Name — snake_case без суффикса _total: он зарезервирован за счётчиками, и его отсутствие на датчиках — часть конвенции. Регистрация всё та же prometheus.MustRegister, один раз при старте. Дальше четыре метода, и у каждого своя семантика. Set записывает абсолютное значение — это главный метод gauge, им вы говорите «сейчас значение равно вот этому». Inc и Dec — плюс и минус единица для событий: задачу взяли, задачу отдали. Add и Sub меняют значение на произвольную величину с любым знаком: в отличие от счётчика убывание здесь законный основной сценарий. Все методы потокобезопасны — под нагрузкой из горутин значение не порвётся, хотя логику «кто и когда обновляет» библиотека за вас не придумает.

Очередь заказов: gauge, который живёт событиями

GO
package main

import (
        "net/http"

        "github.com/prometheus/client_golang/prometheus"
        "github.com/prometheus/client_golang/prometheus/promhttp"
)

var queueSize = prometheus.NewGauge(prometheus.GaugeOpts{
        Name: "shop_queue_size",
        Help: "Текущая длина очереди заказов.",
})

func init() {
        prometheus.MustRegister(queueSize)
}

func main() {
        http.HandleFunc("/enqueue", func(w http.ResponseWriter, r *http.Request) {
                queueSize.Inc()
                w.Write([]byte("добавлено"))
        })

        http.HandleFunc("/done", func(w http.ResponseWriter, r *http.Request) {
                queueSize.Dec()
                w.Write([]byte("обработано"))
        })

        http.Handle("/metrics", promhttp.Handler())
        http.ListenAndServe(":8080", nil)
}

Очередь — идеальный житель gauge: каждый новый заказ делает Inc, каждый обработанный — Dec, а /metrics в любой момент показывает актуальную длину. Имя без _total — конвенция датчиков. Модель честная: значение всегда соответствует состоянию процесса, потому что обновляется в момент события.

Событийная модель работает, пока события в ваших руках. Для величин, которые считает не процесс, а внешний мир — число строк в таблице, размер каталога, температура в серверной, — есть второй паттерн: синхронизация. Значение периодически читается из источника правды и записывается Set-ом, а gauge остаётся витриной последнего прочитанного. Так меряют всё, до чего хендлеры не дотягиваются: глубину отстающей очереди, размер кэша в объектах, число соединений, которыми управляет отдельный компонент. Смотрите на код ниже — он станет шаблоном для половины ваших датчиков.

Синхронизация датчика из источника правды

GO
var pendingJobs = prometheus.NewGauge(prometheus.GaugeOpts{
        Name: "worker_pending_jobs",
        Help: "Задачи, ожидающие обработки, по данным базы.",
})

func watchPendingJobs() {
        ticker := time.NewTicker(10 * time.Second)
        for range ticker.C {
                n := countPendingJobs() // SELECT count(*) ...
                pendingJobs.Set(float64(n))
        }
}

go watchPendingJobs()

Фоновая горутина раз в десять секунд читает актуальное значение и пишет его Set-ом — это и есть синхронизация датчика. Между обновлениями gauge хранит прошлое значение, и скрейп поймает именно его: разрешение метрики задаёт ваш таймер, а не только scrape-интервал. Слишком частые тяжёлые запросы к базе — самоповреждение, подберите интервал по цене запроса.

Инцидент: очередь, которая никогда не пустела#

Инцидент, после которого в нашей команде появилось правило «проверяй ноль». В обработчике платежей датчик shop_payments_pending инкрементился на входе и уменьшался на выходе — всё сходилось, пока не появился ранний выход: при невалидной карте функция возвращалась до Dec, и значение навсегда оставалось завышенным на единицу. За три месяца таких выходов набралось сорок: график «в очереди сорок платежей» превратился в константу, на которую никто не смотрел, а настоящий застой очереди заметили по жалобам, а не по метрике. Ловушка в том, что gauge честен ровно настолько, насколько симметричны пары Inc и Dec. Противоядия три: уменьшение в defer, как учит урок про отложенные вызовы; сборка значения из источника правды Set-ом вместо парных вызовов; периодическая сверка с базой. С третьим способом график иногда рисует ступеньку вниз — это не баг, это правда о том, что датчик раньше врал.

Gauge метрика пример: что мерят датчиками на практике#

Разберём типовую коллекцию. Встроенные метрики, которые вы видели в третьем уроке, по большей части — gauge: go_goroutines — число горутин сейчас, go_memstats_heap_inuse_bytes — занятая куча, process_open_fds — открытые дескрипторы. Это подсказка: если метрика отвечает на вопрос «сколько сейчас», в вашем коде, вероятно, тоже нужен gauge. Собственные датчики типового веб-сервиса: in-flight запросы — сколько обрабатывается прямо сейчас; размер кэша в объектах и в байтах; число активных сессий или WebSocket-соединений — для real-time у нас есть отдельный курс; глубина пула соединений к базе; заполненность дисковой партиции в процентах. Общий признак: значение имеет смысл только в моменте, а ноль — не пустая история, а честное «пусто сейчас».

Counter против Gauge: сводная таблица#

СвойствоCounterGauge
Закон изменениятолько вверхв обе стороны
Рестарт процессаобнуляется, rate склеивает сбросыначинается с первого Set или с нуля
Ключевые методыInc, Add с неотрицательнымSet, Inc, Dec, Add, Sub
Типичные примерызапросы, ошибки, байты, деньгиочередь, память, горутины, сессии
PromQL на дашбордахrate() и производныесырое значение, avg_over_time

Почему растущее нельзя мерить gauge-ом#

Соблазн велик: «заказов всего» растёт, значит поставлю gauge и буду Set-ить накопленное. График будет выглядеть правдоподобно ровно до первого вопроса о скорости: у gauge нет понятия сброса при рестарте, поэтому после деплоя линия резко упадёт с сорока тысяч до пяти и так же резко ляжет в новый рост — график честно нарисует гору, которая ничего не означает. rate по gauge не считают: этот запрос рассчитан на монотонность счётчика, и алерты по скорости тоже не построить. Растущее значение — counter, и это не вкусовщина: тип определяет, на какие вопросы метрика умеет отвечать. Обратная ошибка мягче: убывающая величина в counter либо паникует при попытке уменьшения, либо заводится как разность двух счётчиков на уровне запросов — работает, но зачем, если есть gauge.

Яма после рестарта: как не читать график датчика#

Типичная ошибка интерпретации датчика начинается после деплоя. График активных сессий падает почти в ноль и снова растёт: половина команды читает «пользователи ушли и вернулись», хотя правдивое чтение — процесс перезапустился, память очистилась, а Set при старте никто не поставил. Отличить одно от другого просто: след рестарта совпадает с моментом выката на графике выкладок, а поведение пользователей так синхронно не ходит. Второе чтение той же ямы — «сервис потерял данные»: если датчик отвечает за очередь, яма выглядит как «очередь разобрана», и все радуются, хотя очередь просто перестала быть видимой. Лечится двумя строками: при старте выставлять актуальное значение из источника правды и держать в дашборде вторую серию — время жизни процесса. Когда обе линии прыгают одновременно, вы смотрите на рестарт, а не на бизнес; лечится это не перезапуском, а инициализацией.

Гибрид: события и сверка в одном датчике#

Вариация примера, которую я использую чаще всего: гибрид двух моделей. События двигают датчик Inc и Dec для мгновенной реакции, а фоновая сверка раз в минуту выставляет Set из источника правды, исправляя накопленный дрейф: пропущенный Dec в редкой ветке перестаёт быть вечной ошибкой и превращается в минутную. Спорная сторона гибрида — двойная логика: читатель метрики должен знать, что значение может прыгнуть на сверке, и не искать в этот момент аварию. Моё правило: для короткоживущих сущностей с чёткими входами и выходами хватит чистых Inc и Dec; для долгоживущих состояний с редкими нештатными ветками гибрид честнее. А если источник правды сам дешевле события — например, длина очереди уже хранится в Redis — берите чистую синхронизацию и не поддерживайте два механизма одновременно.

Скрейп-интервал и потерянные пики#

Gauge отдаёт значение в момент опроса: если пик длился две секунды между скрейпами по пятнадцать — Prometheus его никогда не увидит. Для большинства метрик это нормально: очередь длиной десять тысяч, а через две секунды пустая, — событие, а не состояние, и для событий есть счётчики и логи. Но знать про слепые окна надо, иначе вы будете искать «пик, который точно был» по графику, где его физически нет. Вторая особенность — усреднения: avg_over_time по датчику показывает среднее по точкам скрейпа, и результат зависит от интервала. Спорное мнение из практики: in-flight запросы я предпочитаю мерить gauge-ом в middleware, а не вычислять из счётчиков разностью — датчик честен в моменте и не требует бережного обращения с рестартами.

Проверка: Set против Inc

Чем Set принципиально отличается от Inc?

Проверка: gauge после рестарта

Сервис с датчиком «активные сессии» перезапустили. Что покажет метрика до первого Set?

Какое значение увидит скрейп

Прочитайте код и выберите, что он выведет:

queue.Set(42)
queue.Inc()
queue.Add(3)
queue.Dec()
queue.Set(10)

Соберите датчик очереди

Восстановите создание gauge и два способа обновления: абсолютным значением и уменьшением на единицу.

var queueSize = prometheus.(prometheus.{ Name: "shop_queue_size", Help: "Текущая длина очереди заказов.", }) // актуальное значение из базы: queueSize.(float64(n)) // одна задача обработана: queueSize.()

Объявление и первое обновление датчика

Расставьте строки в правильном порядке: от создания gauge до первого обновления.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Карточки: Gauge

Термин

Gauge

нажмите, чтобы перевернуть
Определение

Датчик текущего значения: очередь, память, сессии. Set, Inc, Dec, Add и Sub с любым знаком; живёт последним записанным значением.

1 / 6

Датчики закрывают вторую половину бытовых метрик — дальше метрики станут многомерными. Следующий урок — лейблы: одна метрика shop_orders_total и бесконечность срезов по маршрутам и статусам через CounterVec; там же первая встреча с кардинальностью, главным тихим убийцей Prometheus. После лейблов — гистограмма и её бакеты для латентности. Перечитайте перед практикой урок про счётчик: пара counter плюс gauge закрывает девять задач из десяти, и путаница между ними — самый частый вопрос на ревью. Практика по сегодняшнему материалу — только локальный Go: песочница сайта не поднимает HTTP-сервер. Карта всех двадцати пяти тем, включая будущие PromQL, Grafana и итоговый проект, — на странице курса.

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • Gauge — текущее состояние: Set, Inc, Dec, Add и Sub с любым знаком, потокобезопасно, значение — последнее записанное.
  • NewGauge с GaugeOpts без _total; MustRegister при старте; суффикс _total зарезервирован за счётчиками.
  • Два паттерна обновления: события — Inc и Dec в коде, синхронизация — Set из источника правды по таймеру.
  • Скрейп ловит момент: пики между опросами теряются, усреднения зависят от интервала.
  • Растущее нельзя мерить gauge-ом: нет сбросов и rate — график рисует ложные ямы и горы после рестартов.
  • Готовые датчики уже в /metrics: go_goroutines, память кучи, дескрипторы; свои — только для вашего домена.

Домашнее задание

Соберите сервис с очередью из урока: нагоните Inc-ов, потом Dec-ов и посмотрите, как значение ходит туда-сюда на /metrics. Затем добавьте второй датчик worker_pending_jobs с синхронизацией из источника правды и попробуйте «сломать» график: ставьте Set-ом растущую сумму и сравните картину с counter-версией из прошлого урока. Песочница не подойдёт — нужен локальный Go.

Личные заметкиПусто
Хранятся только в вашем браузере (Prometheus · Gauge-метрика: что это и как применять в Go)

Вопросы и ответы по уроку

Чем Gauge отличается от Counter в одном предложении?

Counter копит и только растёт, gauge показывает, сколько сейчас, и ходит в обе стороны. Растущее — счётчик, текущее состояние — датчик; перепутанные типы дают бессмысленные графики.

Как часто обновлять gauge из базы?

По цене запроса: тяжёлый подсчёт раз в десять секунд — нормально, раз в секунду — самоповреждение. Между обновлениями Prometheus видит прошлое значение: разрешение метрики ограничено и таймером, и scrape-интервалом.

Нужно ли защищать gauge от гонок?

Методы потокобезопасны: значение не порвётся под конкурентными Set и Inc. Гонки вашей логики библиотека не ловит: две горутины, считающие «актуальное» значение из разных источников, — уже ваша ошибка согласованности.

Термины урока

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

Следующая ступень учебного пути

WebSocket25 уроков~13 чпрактика25 уроков уже доступны

WebSocket в Go: real-time чаты и уведомления

Курс WebSocket на Go: gorilla/websocket, эхо-сервер, hub-паттерн, комнаты, авторизация соединений, ping/pong, reconnect и масштабирование. 25 уроков.

  • Поднимать WS-сервер и апгрейдить HTTP-соединения
  • Писать readPump/writePump без гонок на каждое соединение
  • Строить hub: регистрацию, рассылку и комнаты
  • Авторизовывать соединения и ограничивать сообщения