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

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

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

Урок 10 из 25 ~18 мин +20 XP

Бизнес-метрики: заказы и оплаты

Бизнес метрики go-сервиса — заказы, выручка, очередь платежей — измеряются теми же Counter, Gauge и Histogram, что и технические, но зритель другой: продукт, а не дежурный. В уроке — shop_orders_total по payment_type, счётчик выручки, конверсия в PromQL и самая дорогая ошибка темы: двойной подсчёт на ретраях.

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

Бизнес метрики go: заказы и оплаты#

Зелёный дашборд с латентностью — и ноль заказов за час: так выглядит сломанный платёжный провайдер при совершенно здоровом приложении. Технические метрики отвечают на вопрос «жив ли сервис», бизнес-метрики — «работает ли бизнес»; второе при первом не гарантировано. Разница в устройстве небольшая, в последствиях большая: на заказы смотрят продукт и аналитики, от них считают конверсию и выручку, по ним ставят самые дорогие алерты — «заказы упали до нуля» будильник громче любого CPU. Приятный бонус: те же четыре типа метрик из второго урока, тот же client_golang, только имена и лейблы теперь из языка предметной области. Осторожность нужна в двух местах. Первое — семантика: бизнес-числа любят сверять с базой, поэтому у каждой метрики должно быть ясное определение, что именно посчитано. Второе — кардинальность: искушение положить в лейбл id заказа или пользователя здесь даже сильнее, чем в HTTP-метриках, и разбирать её будем ещё раз в конце.

Счётчик заказов почти всегда вектор: платят картой и наличкой, подписками и разовыми покупками — лейбл payment_type с двумя-четырьмя значениями ничего не стоит и открывает срезы для продукта. Имя строится от домена: shop_orders_total, shop_revenue_rubles_total — префикс сервиса, сущность, единица измерения в имени. Выручка — тоже Counter: деньги суммируются и только растут, Add(amount) ложится идеально; единицу кладут прямо в имя, чтобы рубли не спутать с копейками и центами. Средний чек отдельной метрикой не заводят: это частное rate(revenue) на rate(orders) в PromQL, и производная в коде создала бы вторую правду об одном числе. Инкременты живут в сервисном слое, сразу после успешного завершения бизнес-операции: не в HTTP-хендлере, который может не знать исхода транзакции, и не до записи в базу. Порядок «сначала коммит, потом Inc» — половина всех драм этой темы, и ниже у него отдельный разбор.

Заказы и выручка

GO
package main

import (
        "github.com/prometheus/client_golang/prometheus"
)

var ordersTotal = prometheus.NewCounterVec(
        prometheus.CounterOpts{
                Name: "shop_orders_total",
                Help: "Оформленные заказы, попытки без ретраев",
        },
        []string{"payment_type"},
)

var revenueRubles = prometheus.NewCounter(
        prometheus.CounterOpts{
                Name: "shop_revenue_rubles_total",
                Help: "Оплаченная выручка в рублях",
        },
)

func orderCompleted(o Order) {
        ordersTotal.WithLabelValues(o.PaymentType).Inc()
        revenueRubles.Add(o.Amount)
}

Два счётчика: вектор по способу оплаты и плоский счётчик денег. В Help записана семантика — оформленные заказы без ретраев: через полгода никто не помнит, считает ли метрика попытки или события, и подпись спасает разборы. Add принимает сумму одним вызовом; инкременты живут в orderCompleted — там, где бизнес-операция фактически завершена.

Метрики заказа: где ставить Inc и что помнить о рестартах#

Классическая ловушка: инкремент стоит до записи в базу, сетевая ошибка уводит обработчик в ретрай — счётчик прибавил два, заказ в базе один. Правильный порядок только один: успешный коммит, затем Inc; упавшая попытка счётчик не трогает. Реверс тоже правда: выручку из метрик никогда не сверяют с бухгалтерией — это инструмент тренда и алертов, источник истины живёт в базе. Второе обязательное знание: счётчики живут в памяти процесса и после рестарта обнуляются. Prometheus компенсирует сброс в rate, но «выручка с начала суток» на графике будет прыгать после каждого деплоя — абсолютные суммы считают в базе, а метрика отвечает на «как сейчас относительно обычно»: rate за окно, сравнение с прошлой неделей, дельта после релиза. Третье: очередь платежей и их длительность — не счётчики. Число платежей в обработке меняется в обе стороны, это Gauge; время проведения платежа — распределение, это Histogram с сеткой под таймауты провайдера.

Деньги после рестарта: что видит график, а что нет#

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

Очередь платежей и их длительность

GO
var pendingPayments = prometheus.NewGauge(
        prometheus.GaugeOpts{
                Name: "shop_payments_pending",
                Help: "Платежи в обработке прямо сейчас",
        },
)

var paymentDuration = prometheus.NewHistogram(
        prometheus.HistogramOpts{
                Name:    "shop_payment_duration_seconds",
                Help:    "Время проведения платежа",
                Buckets: []float64{0.1, 0.5, 1, 5, 15, 60},
        },
)

func paymentStarted()  { pendingPayments.Inc() }
func paymentDone(seconds float64) {
        pendingPayments.Dec()
        paymentDuration.Observe(seconds)
}

Gauge растёт и убывает: Inc при старте платежа, Dec при завершении — дисбаланс этих вызовов и есть утечка значения, о которой ниже. Сетка гистограммы построена под таймауты платёжного провайдера: границы до минуты, потому что попытка дольше минуты обычно и есть провал, и p99 здесь важнее средней.

Конверсия и выручка — это запросы, не метрики

GO
# доля заказов от просмотров товара: конверсия за окно 5 минут
rate(shop_orders_total[5m]) / rate(shop_product_views_total[5m])

# выручка в час
rate(shop_revenue_rubles_total[5m]) * 3600

# платежи, которые висят прямо сейчас
shop_payments_pending

Конверсия и выручка считаются запросами, а не метриками: в коде живут только первичные события. Деление rate на rate даёт долю — по отдельным payment_type сработает без изменений, потому что лейблы совпадают. Умножение на 3600 переводит секунды в час; это единственная арифметика над деньгами в уроке, и она в PromQL, а не в Go.

Четыре типа на службе у продукта#

Что меряемТипМетрика и срезы
Оформленные заказыCounterVecshop_orders_total{payment_type}
ВыручкаCountershop_revenue_rubles_total, Add(amount) после коммита
Платежи в обработкеGaugeshop_payments_pending, Inc и Dec парами
Время проведения платежаHistogramshop_payment_duration_seconds, сетка до 60 секунд

Алерты на бизнес-события#

Бизнес-метрики дают алерты, которых не бывает у технических. Первый — тишина: заказов нет. Если днём rate(shop_orders_total[5m]) стабильно не ноль, а упал до нуля, приложение может быть здоровым — сломался провайдер, кончились лимиты, отвязался платёжный вебхук; такой алерт ставят с учётом времени суток, ночью пороги другие. Второй — аномалия величины: выручка в час заметно ниже скользящей недели при живом трафике — растёт доля отмен или обесценился средний чек. Третий — застревание: shop_payments_pending растёт и не убывает десятки минут, значит, платежи висят, и скоро это увидит поддержка через жалобы. Правило баланса: алерты на бизнес-события дороже технических и ложно срабатывают чаще — ставьте их на события с реальными деньгами и не заводите на каждое колебание, иначе дежурный научится их игнорировать, а это хуже, чем их отсутствие.

Инцидент: алерт «заказов нет» в четыре утра#

Инцидент из разряда «сам себя наказал». На витрине заказов поставили алерт: rate(shop_orders_total[5m]) ниже порога — сообщение в чат дежурному. В четыре утра воскресенья витрина законно пустела, алерт разбудил человека, человек отключил канал, а через две недели настоящая поломка платёжного провайдера прошла незамеченной — ровно потому, что канал уже никто не читал. Ошибка не в метрике и не в пороге — в отсутствии сезонности: у бизнеса есть суточный и недельный ритм, и порог, честный для вторника днём, лжёт ночью. Рабочая схема: сравнение с тем же часом прошлой недели, а не с абсолютным нулём; отдельный тихий алерт на полное отсутствие событий через absent; и правило: каждое ложное срабатывание либо чинится, либо алерт гасится вовсе — молча игнорируемый алерт хуже его отсутствия.

Вариация: продукт просит срез, а не новую метрику#

Вариация примера, которая экономит месяцы: прежде чем заводить новую метрику, проверьте, что срез не получается из существующей. Продукт спрашивает «сколько заказов с доставкой в регионы» — если лейбл delivery_region существовал с первого дня, ответом будет суммирование rate по нему; если нет — метрика даётся дорого: релиз, накопление истории заново, тихие графики. Отсюда практика первого дня: сядьте с аналитиком и выпишите срезы, которые точно спросят — способ оплаты, регион, платформа клиента, — и заведите их лейблами сразу. Спорная граница: не больше пяти-шести лейблов на метрику и только с малым числом значений, иначе кардинальность съест выигрыш. Список срезов живёт рядом с кодом метрик и пересматривается раз в квартал: срез, который никто не открывал за квартал, — кандидат на удаление.

Проверка: тип для выручки

Накопленная выручка за всё время — какой тип метрики?

Проверка: рестарт процесса

Что случится с shop_orders_total после рестарта процесса?

Чему равен shop_payments_pending?

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

paymentStarted()   // Inc
paymentStarted()   // Inc
paymentDone(0.4)   // Dec
paymentStarted()   // Inc

Соберите вектор заказов

Допишите создание вектора, имя лейбла способа оплаты и инкремент ребёнка.

var ordersTotal = prometheus.( prometheus.CounterOpts{ Name: "shop_orders_total", Help: "Оформленные заказы", }, []string{""}, ) func orderCompleted(o Order) { ordersTotal.(o.PaymentType).Inc() }

Соберите счётчик выручки

Расставьте строки объявления счётчика выручки и добавления суммы после коммита.

Строки

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

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

Карточки: бизнес-метрики

Термин

Бизнес-метрика

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

Метрика из языка предметной области: заказы, выручка, очередь платежей. Читается продуктом, служит основой алертов на бизнес-события.

1 / 6

Бизнес-слои готовы: заказы, деньги, очередь и длительность платежей измеряются, конверсия считается запросом. Дальше курс разворачивается внутрь процесса: в следующем уроке разбираем встроенные метрики Go-приложения — горутины, память и сборку мусора, которые уже лежат в вашем /metrics бесплатно. Предыдущий шаг темы — метрики HTTP из девятого урока: бизнес-счётчики обычно ставят рядом с ними, в том же сервисном слое. Если в лейблы бизнес-метрик тянет положить id заказа или пользователя — перечитайте урок про кардинальность до деплоя, а не после. Полный план — на странице курса Prometheus. И финальная оговорка о среде: client_golang исполняется локальным Go, песочница сайта его не запустит; соберите сервис, сделайте несколько тестовых заказов и убедитесь curl-ом, что инкременты встали после коммита, а не до него.

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

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

Вывод

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

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

  • Бизнес-метрики — те же Counter, Gauge и Histogram, но имена и лейблы из языка домена: shop_orders_total, payment_type.
  • Выручка — Counter с Add(amount); средний чек и конверсия считаются в PromQL, не в коде.
  • Inc строго после успешного коммита: иначе ретрай удваивает заказ.
  • Счётчики живут до рестарта: тренды и алерты — в метриках, отчёты и сверки — в базе.
  • Очередь платежей — Gauge с парными Inc и Dec; время платежа — Histogram с сеткой под провайдера.
  • order_id и user_id в лейблы не идут: кардинальность убьёт Prometheus быстрее, чем вы заметите.

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

Добавьте в учебный сервис вектор заказов по payment_type и счётчик выручки; инкремент поставьте строго после «коммита» — заглушки достаточно. Сделайте несколько тестовых заказов, включая один с проваленной первой попыткой, и проверьте curl-ом, что счётчик прибавил ровно один заказ. Затем перезапустите процесс и посмотрите, как обнулились счётчики и что это значит для графика. Нужен локальный Go.

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

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

Стоит ли писать в метрики сумму заказа и его id?

Сумма — да, в Counter через Add: выручка суммируется по определению. Id — никогда: кардинальность растёт с каждым заказом. Разовые крупные суммы и разборы отдельных заказов — дело базы, а не Prometheus.

Кто должен инкрементить бизнес-счётчик: хендлер или сервис?

Слой, где бизнес-операция фактически завершена: после коммита транзакции или подтверждения провайдера. Хендлер может не знать исхода, а воркер очереди — знает; инкремент живёт рядом с коммитом, а не рядом с входом запроса.

Как алертить на падение заказов?

Порогом по rate с учётом времени суток: днём rate(shop_orders_total[5m]) ниже обычного — сигнал. Для полной тишины полезен absent по метрике; точные правила и Alertmanager разбираются позже в курсе.

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

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

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

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

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

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

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