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

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

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

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

Реестры и namespace метрик

prometheus namespace — это Namespace, Subsystem и Name в опциях метрики, из которых собирается итоговое имя, а registry go — реестр, который решает, что вообще попадёт в /metrics. К моменту, когда в сервисе тридцать метрик и две общие библиотеки, оба механизма становятся вопросом дисциплины: без неё имена расползаются, а дубликат регистрации роняет процесс при старте.

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

Сервис стартует на стенде после внедрения общей библиотеки оплаты — и падает на первой же секунде с сообщением duplicate metrics collector registration attempted. Паника честная: и библиотека, и приложение зарегистрировали счётчик с именем payments_total через MustRegister, а два коллектора с одним именем в одном реестре запрещены. До этой встречи многие месяцы живут, не задумываясь о реестре: promhttp.Handler() из третьего урока обслуживает дефолтный глобальный регистр, и всё кажется бесплатным. Но глобальное состояние — то, о чём библиотека не имеет права забывать: она не знает, кто ещё живёт в том же процессе. В этом уроке разберём, из чего собирается имя метрики, как завести собственный реестр и когда это оправдано, и как тесты перестают видеть чужие метрики.

prometheus namespace: префикс, который экономит grep#

Итоговое имя метрики собирается из трёх частей: Namespace, Subsystem и Name, склеенных подчёркиваниями — пустые части пропускаются. В GaugeOpts и CounterOpts поля называются именно так, и это единственный легальный способ получить префикс: имя не склеивают конкатенацией строк, потому что Help и настройки принадлежат метрике целиком. Зачем префикс: в /metrics смешиваются go_-метрики рантайма, process_-метрики процесса, метрики библиотек и ваши собственные, и без доменного префикса grep по нужной группе превращается в чтение сотен строк. Префикс службы — скажем, shop_ — сразу отделяет метрики домена от чужих, а второй уровень subsystem_ группирует их внутри: shop_orders_queue_depth читается как маршрут к владельцу кода. Тот же словарь используется в PromQL из двенадцатого урока и в переменных дашборда: грамотный префикс экономит минуты на каждом разборе. Словарь живёт рядом с кодом метрик: две строчки в комментарии пакета — какой префикс у домена и какие суффиксы единиц — экономят час обсуждения на каждом код-ревью.

Имя из трёх частей

GO
queueDepth := prometheus.NewGauge(prometheus.GaugeOpts{
        Namespace: "shop",
        Subsystem: "orders",
        Name:      "queue_depth",
        Help:      "Глубина очереди заказов на обработку",
})
// Итоговое имя: shop_orders_queue_depth

paidTotal := prometheus.NewCounter(prometheus.CounterOpts{
        Namespace: "shop",
        Subsystem: "payments",
        Name:      "paid_total",
        Help:      "Успешные оплаты",
})
// Итоговое имя: shop_payments_paid_total

Части склеиваются подчёркиваниями, и по имени сразу видно владельца: shop_orders — модуль заказов, shop_payments — модуль платежей. Единицы держат в Name по конвенции: _total для счётчиков, _seconds и _bytes для наблюдаемых величин. Help обязан быть у каждой метрики — он виден в /metrics и в подсказках дашборда.

Свой Registry в Go: prometheus.NewRegistry#

Реестр — это словарь коллекторов, и в client_golang он реализован интерфейсами Registerer и Gatherer. Дефолтный реестр глобален: DefaultRegisterer принимает всё, что зарегистрировали через prometheus.MustRegister, а promhttp.Handler() собирает метрики из DefaultGatherer. Свой реестр создаётся одной строкой — prometheus.NewRegistry() — и живёт по тем же правилам: MustRegister добавляет коллекторы и паникует на дубликате, Register возвращает ошибку вместо паники. Два следствия, о которых забывают. Первое: в новом реестре нет ничего, включая go_- и process_-метрики, — их вносит вручную NewGoCollector() и NewProcessCollector(), если они нужны. Второе: promhttp.Handler() дефолтный реестр не увидит — на HTTP выставляют HandlerFor(reg, promhttp.HandlerOpts{}). Для сокращения записи есть promauto.With(reg): конструкторы сразу регистрируют метрику в вашем реестре и возвращают готовый объект.

Свой реестр и экспозиция через HandlerFor

GO
reg := prometheus.NewRegistry()
reg.MustRegister(
        collectionDuration,
        queueDepth,
        paidTotal,
        prometheus.NewGoCollector(),      // go_* — по желанию
        prometheus.NewProcessCollector(   // process_* — по желанию
                prometheus.ProcessCollectorOpts{}),
)

http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))

MustRegister принимает список коллекторов: реестр один, паника на дубликате сработает только при настоящем конфликте имён внутри него. HandlerFor привязывает HTTP-хендлер к конкретному реестру, поэтому в /metrics попадает ровно то, что зарегистрировано здесь, — поведение, которое дефолтный глобальный регистр не гарантирует.

Изоляция тестов: свой реестр в go test#

Тесты метрик на дефолтном реестре ломаются двумя способами: значения накапливаются между тестами, потому что глобальный регистр один на процесс, и параллельные пакеты паникуют на дубликатах имён. Лечение — новый реестр на каждый тест: конструктор приложения получает его параметром, тест создаёт свежий, регистрирует свои метрики и проверяет значения через reg.Gather(). Gather возвращает срез семейств метрик в protobuf-структурах — из него берут значение нужного счётчика и сравнивают с ожиданием; это тот же интерфейс, которым Prometheus собирает данные по сети, только без HTTP. Для строгой проверки есть NewPedanticRegistry(): он дополнительно проверяет согласованность коллектора — тот же набор серий в Describe и Collect, что ловит расхождение декларации и реальности, о котором восемнадцатый урок писал применительно к своим коллекторам; pedantic-режим включают в CI на пакете метрик, где цена расхождения выше скорости. Бюджет прост: реестр в тесте — одна строка, а расхождение значений между тестами ищется час; Unregister существует, но в тестах им пользуются редко — новый реестр дешевле и чище, чем разрегистрация половины коллекторов.

Дисциплина имён: словарь префиксов сервиса#

Namespace работает, когда словарь префиксов один на команду. Верхний уровень — домен сервиса: shop_, billing_, notifications_; он же совпадает с job_name в конфиге скрейпа из следующего урока, и по графику сразу ясно, чей это сервис. Второй уровень — модуль: orders_, payments_, auth_. Дальше — имя с единицей по конвенции Prometheus: счётчики заканчиваются на _total, латентности на _seconds, размеры на _bytes, а базовые единицы не множатся: миллисекунды не пишут в имя, значение переводят в секунды. Лейблы не дублируют имя: route у счётчика запросов — да, но node или env в лейбле сервиса обычно избыточны, это та же кардинальная дисциплина, что в уроке семнадцатом. Практическая проверка словаря — тест на пять секунд: по одному имени метрики в /metrics должно быть видно, какой модуль отвечает, и в каких единицах значение.

Из чего собирается имя метрики#

ЧастьЧто несётПример
Namespaceдомен или имя сервисаshop
Subsystemмодуль внутри доменаorders
Nameсуть и единица измеренияqueue_depth
Итоговое имясклейка подчёркиваниямиshop_orders_queue_depth
_totalсуффикс счётчика по конвенцииshop_payments_paid_total
go_, process_метрики рантайма и процессаgo_goroutines

Спорное мнение: дефолтный реестр — не зло#

Спорная часть урока: для монолита с одним приложением свой реестр часто лишний. Дефолтный регистр даёт ноль строк конфигурации, promhttp.Handler() работает из коробки, и пока в процессе живёт только ваш код, риск дубликата остаётся штатной паникой при старте — шумной, но честной: она падает в момент запуска, а не в разгар дня. Граница проходит там, где появляется второй владелец метрик: общая библиотека, встраиваемый модуль, несколько инстансов одного клиента, тесты. Тогда глобальное состояние перестаёт принадлежать вам, и свой реестр становится способом явной передачи владения. Отсюда практика, которая не спорит ни с одной стороной: приложению можно жить на дефолте, но каждый публичный пакет обязан принимать Registerer параметром — тогда переезд на свой реестр занимает одну строку в конструкторе, а не переписывание половины зависимостей.

Переезд с дефолтного реестра на свой#

Переезд занимает один коммит, если приложение собирало метрики через публичные конструкторы. Шаги: создать реестр, заменить prometheus.MustRegister на reg.MustRegister для всех объявлений, поменять promhttp.Handler() на HandlerFor(reg, ...) и прогнать тесты — они ловят глобальное состояние первым. Затем инвентаризация: curl по старому хендлеру показывает пустоту, и это правильно — метрики переехали, а не пропали; убедитесь зелёным up на новом пути из конфига скрейпа. Две ловушки переезда стоят отдельного абзаца. Первая — библиотеки, написанные в глобальный регистр: их вызовы продолжают складывать метрики в DefaultRegisterer, и на дашборде возникает расщепление половин; лечится параметром Registerer в конструкторе, как в warn-врезке выше. Вторая — забыть о go_-коллекторе: новый реестр пуст, и привычные go_goroutines исчезают, пока NewGoCollector не внесён вручную. Обе ловушки видны на первой странице /metrics, поэтому переезд проверяется глазами, а не только зелёными тестами.

Проверка: итоговое имя

Namespace: shop, Subsystem: payments, Name: failed_total. Какое имя окажется в /metrics?

Проверка: дубликат регистрации

Что произойдёт при повторном MustRegister того же коллектора?

Что увидит curl в свой реестр

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

reg := prometheus.NewRegistry()
reg.MustRegister(ordersTotal)

http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))
// curl localhost:8080/metrics

Соберите свой реестр

Создайте реестр, зарегистрируйте метрику и выставьте хендлер.

reg := prometheus.() reg.(ordersTotal) http.Handle("/metrics", promhttp.(reg, promhttp.HandlerOpts{}))

Путь метрики через свой реестр

Расставьте шаги от объявления до экспозиции.

Строки

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

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

Карточки: реестры и имена

Термин

Namespace и Subsystem

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

Две части итогового имени: домен сервиса и модуль. Склеиваются с Name подчёркиваниями, пустые части пропускаются.

1 / 5

Реестр и namespace замыкают инженерную часть курса: метрики объявлены, названы и изолированы. Счётчики, на которых проверялись имена, выросли из урока про Counter, лейблы — из шестого, а связка с логами осталась в предыдущем уроке. Дальше — prometheus.yml и скрейпинг: Prometheus ищет ваш /metrics по job_name, который логично сделать равным namespace из этого урока. Когда метрик станет много, урок про кардинальность напомнит про бюджет серий, а весь маршрут — на странице Prometheus. Домашняя проверка словаря проста: откройте /metrics своего проекта, разбейте имена по префиксам и проверьте, что по каждому виден владелец и единица измерения — если нет, словарь пора дополнять до следующих релизов.

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

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

Вывод

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

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

  • Имя метрики собирается из Namespace, Subsystem и Name: префикс домена и модуля вместо конкатенации строк.
  • Свой реестр — prometheus.NewRegistry: пуст на старте, go_- и process_-коллекторы вносятся вручную.
  • Экспозиция своего реестра — promhttp.HandlerFor, дефолтный глобальный регистр при этом не участвует.
  • Дубликат имени в реестре — паника при старте: fail fast, который честнее молчаливой замены.
  • Библиотека не пишет в глобальное состояние: свой реестр или Registerer в параметре конструктора.
  • В тестах свой реестр на каждый тест и проверка значений через reg.Gather().
  • Словарь префиксов: домен, модуль, имя с единицей; job_name в конфиге скрейпа совпадает с namespace.

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

Переведите учебный сервис на собственный реестр: NewRegistry, MustRegister для трёх своих метрик с Namespace и Subsystem, HandlerFor на /metrics и ручная регистрация Go-коллектора. Проверьте curl-ом, что go_-метрики появились только после явной регистрации. Напишите тест с отдельным реестром на каждый случай и проверьте значение счётчика через reg.Gather(). Затем добавьте в проект библиотечный пакет, принимающий Registerer параметром, и убедитесь, что два инстанса пакета не паникуют.

Личные заметкиПусто
Хранятся только в вашем браузере (Prometheus · Реестры и namespace метрик)

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

Чем Namespace отличается от обычного префикса в Name?

Технически — ничем: итоговое имя одинаково. Разница в структуре кода: Namespace и Subsystem задают уровень владельца один раз в опциях, а конкатенация строк расползается по вызовам и забывается при рефакторинге. Плюс Help и настройки принадлежат метрике целиком, и по опциям видно, кто отвечает за имя.

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

Если метрики регистрируются в конструкторе приложения, тесты всё равно создают инстансы — и на глобальном реестре второй тест с тем же набором имён упадёт. Свой реестр на тест снимает и эту проблему, и накопление значений между тестами. Даже без проверок значений это одна строка в setup-функции.

Можно ли зарегистрировать одну метрику в два реестра?

Можно: коллектор не привязан к реестру жёстко, Registerer — это интерфейс. Но метрика с общим состоянием в двух реестрах соберёт значения вместе, и каждый реестр покажет одну и ту же серию. Если нужна изоляция — два инстанса метрики, по одному на реестр, и лейбл для разделения, а не общая метрика в двух местах.

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

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

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

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

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

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

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