---
title: "Summary против Histogram"
description: "Summary против histogram: prometheus summary на Go — Objectives и квантили на стороне клиента, экспозиция quantile и почему гистограмма выигрывает в агрегации."
course: "Мониторинг Go с Prometheus: метрики, Grafana, алерты"
url: "https://golangmaster.ru/courses/prometheus/lessons/08-summary/"
published: "2026-09-19"
updated: "2026-09-24"
minutes: 16
---

# Summary против Histogram

*Курс «Мониторинг Go с Prometheus: метрики, Grafana, алерты», урок 8 из 25. ~16 минут. Обновлено 24.09.2026. Источник: https://golangmaster.ru/courses/prometheus/lessons/08-summary/*

Summary в prometheus считает квантили в самом приложении — задал Objectives, наблюдаешь, получаешь готовые p50, p90, p99 в экспозиции. Звучит как идеал, поэтому summary vs histogram в реальных системах почти всегда заканчивается гистограммой: разбор устройства, погрешности и границы применимости — в этом уроке.

Квантиль — это значение, ниже которого лежит заданная доля наблюдений: медиана 0.5 описывает типового пользователя, p99 — худший случай из ста, на котором держатся SLO. Гистограмма из прошлого урока хранит счётчики по бакетам и доверяет подсчёт квантиля серверу Prometheus. Summary переворачивает схему: приложение само ведёт потоковое приближение квантилей и отдаёт в экспозицию готовые числа. Взамен теряет то, за что любят гистограммы, — агрегацию. Квантиль нелинейна: медиана суммы никогда не равна сумме медиан, поэтому две summary с двух инстансов математически нельзя сложить ни в PromQL, ни на бумажке. У каждого типа своя ниша, и пересекаются они меньше, чем кажется на первый взгляд: summary — про точный квантиль одного процесса, histogram — про любой срез по ферме процессов. Отсюда и правило выбора, к которому урок придёт в конце: для HTTP-латентности берут гистограмму почти всегда.

### Summary в prometheus: Objectives и экспозиция

NewSummary принимает SummaryOpts; ключевой параметр — Objectives, карта вида map[float64]float64{0.99: 0.001}. Ключ — целевой квантиль, значение — допустимая погрешность: 0.001 означает, что заявленное число лежит в интервале шириной в две тысячные вокруг истинного квантиля. Чем строже погрешность и больше целей, тем больше памяти на потоковые структуры в процессе — подробнее чуть ниже. Обязательно учитывайте версию библиотеки: в современных client_golang квантили задаются явно, дефолтного набора 0.5, 0.9 и 0.99 давно нет — summary без Objectives экспортирует только sum и count. В экспозиции каждый квантиль выглядит отдельной серией с лейблом quantile="0.99"; сумма и счётчик идут рядом, как у гистограммы, поэтому среднее тоже доступно. Внешне summary почти неотличим от гистограммы без бакетов — различие прячется в том, кто и когда считает квантиль.

### Summary с тремя целями

```go
var requestDuration = prometheus.NewSummary(
        prometheus.SummaryOpts{
                Name: "http_request_duration_seconds",
                Help: "Время ответа",
                Objectives: map[float64]float64{
                        0.5:  0.05,
                        0.9:  0.01,
                        0.99: 0.001,
                },
        },
)

// в хендлере или middleware
requestDuration.Observe(elapsed)
```

Objectives запрашивает три квантиля с разной строгостью: медиане допускаем пять процентов, а p99 — уже тысячную. Погрешность — это ваш выбор и ваше обещание: цифра из дашборда честна только в пределах заявленного интервала. Observe сигнатурно совпадает с гистограммой, поэтому при переезде между типами меняется только объявление метрики.

### Экспозиция summary

```go
http_request_duration_seconds{quantile="0.5"} 0.031
http_request_duration_seconds{quantile="0.9"} 0.148
http_request_duration_seconds{quantile="0.99"} 0.402
http_request_duration_seconds_sum 61.8
http_request_duration_seconds_count 2000
```

Квантили посчитаны на стороне клиента и отдаются готовыми числами; quantile — обычный лейбл, как route у векторов из шестого урока. Сумма 61.8 секунды и 2000 наблюдений дают среднюю 31 мс — sum и count экспортируются всегда, даже без единой цели в Objectives.

### Погрешность Objectives и цена точности

Значение в карте Objectives — это обещание точности, и за него платят памятью процесса. Погрешность 0.05 у медианы означает: заявленное число лежит в интервале плюс-минус пять процентов от истинного квантиля; для p99 те же пять процентов — слишком щедро, поэтому цели обычно сужают с ростом квантиля. Чем больше целей и строже погрешности, тем крупнее внутренние буферы наблюдений, которые summary держит на каждую цель. Практическое правило: не больше трёх-четырёх целей на метрику и только те квантили, что реально рисуются на дашбордах. Квантиль, который никто не смотрит, — это память процесса, потраченная впустую. И помните про версионность: если после обновления client_golang из экспозиции внезапно пропали квантили, проверьте, заданы ли Objectives явно — дефолтного набора в свежих версиях больше нет, а старые туториалы могут показывать иное.

### Инцидент: квантиль исчез после обновления

Инцидент на обновлении зависимости. Команда подняла версию client_golang, сервис перезапустился, панель p99 замолчала: ряды исчезли, алерт по квантилю перестал существовать. Виноват не Prometheus: summary после обновления отдал только sum и count, потому что Objectives в этой версии задаётся явно, а код создавал метрику без карты целей. Никакой ошибки в логах: квантиль, о котором не попросили, не считается — это документированное поведение, а не регресс. Урок шире библиотеки: зависимость от клиентских квантилей — зависимость от настроек кода, которые меняются молча. У гистограммы схожий риск — смена сетки — но разрыв виден на графике, а исчезновение серии — нет. После обновлений клиентских библиотек прогоняйте короткую проверку: имя метрики существует, нужный лейбл quantile на месте, значение не пусто. Такая проверка вообще полезна в CI: экспозиция сервиса сверяется со списком ожидаемых имён, и любое исчезновение ловится до выката.

### Summary против histogram: где считаются квантили

Практическая разница сводится к четырём пунктам. Агрегация: бакеты гистограммы — обычные счётчики, их суммируют sum by (le) и подают в histogram_quantile, получая честный p99 фермы или срез по конкретному маршруту; квантили summary суммировать нельзя — у каждого инстанса свой ряд quantile="0.99", и никакой запрос не соберёт их в общий. Новые квантили: гистограмма отдаёт любой перцентиль из имеющейся сетки — захотелось p95, меняется запрос без релиза; summary считает только то, что зашито в Objectives, новый квантиль — это релиз. Точность: summary гарантирует заявленную погрешность на потоке наблюдений; гистограмма ограничена сеткой бакетов и может быть грубее. Стоимость: summary тяжелеет от строгих погрешностей, гистограмма — фиксированные бакеты. Отсюда правило по умолчанию: HTTP-латентность и всё, что агрегируется, — гистограмма; summary — точечный инструмент одиночного процесса.

### Один инстанс врал за весь кластер

Ошибка интерпретации, характерная именно для summary: p99 одного инстанса читают как p99 сервиса. История: восемь подов, на дашборде ряд без агрегации — Prometheus показывал первый попавшийся. Половина трафика шла на новый релиз, у него квантиль был в пять раз выше, но на графике это смешивалось с семью зелёными подами и выглядело шумом. Честный срез по релизу построить нельзя: квантили не суммируются, а записи вида «по инстансу» — молитва на балансировщик, который раскладывает трафик как захочет. Правило для дежурного: если на панели латентности нет агрегации и рядов по подам, панель отвечает не на вопрос «как сервис», а на вопрос «как повезло инстансу, который попал в выборку». Для summary это не лечится запросом — лечится заменой типа, и этот аргумент в споре с гистограммой обычно решающий. Ту же ошибку делают с двумя регионами: p99 региона живёт своей жизнью.

### Summary и Histogram рядом

| Критерий | Summary | Histogram |
| --- | --- | --- |
| Где считается квантиль | в приложении, на потоке наблюдений | на сервере Prometheus, из бакетов |
| Агрегация по инстансам | невозможна: квантили не складываются | sum by (le) плюс histogram_quantile |
| Новый квантиль | только с релизом, через Objectives | любой, правкой запроса |
| Погрешность | задаётся явно, например 0.001 | ограничена сеткой бакетов |
| Серии на метрику | по одной на цель, плюс sum и count | бакеты с +Inf, плюс sum и count |
| Типичное применение | точный квантиль одного процесса | HTTP-латентность и любые срезы |

> **Средний p99 — самое популярное бессмысленное число.** Самая частая ошибка с summary — «средний p99» по инстансам: avg по рядам quantile="0.99". Число получится, график нарисуется, а смысла не будет ни в одном знаке: квантиль — нелинейная функция распределения, и медианы двух выборок не складываются в медиану объединённой. Пример: у десяти инстансов p99 по 0.1 секунды и у одного 3 секунды — среднее 0.39, а реальный p99 фермы ближе к 1.5. Нужен общий p99 — берите гистограмму и histogram_quantile по сумме бакетов.

> **Когда summary всё же уместен.** Summary не провалит проект в двух случаях: одиночный инстанс без горизонтального масштабирования и потребность в строгом квантиле без подбора сетки бакетов — например, длительность фоновой задачи с тяжёлым хвостом. Для ориентировки: в дефолтном реестре go-процесса уже живёт summary — go_gc_duration_seconds с квантилями 0, 0.25, 0.5, 0.75 и 1: сборщику мусора нужны готовые квантили, которые нельзя вывести из счётчиков постфактум.

### Вариация: MaxAge для фоновой задачи

Вариация примера, где summary действительно хорош: длительность ночной фоновой задачи. Наблюдений мало — сотни за сутки, квантиль один, агрегировать не с чем: p99 задачи интересен там же, где она выполняется. Objectives с единственной целью 0.99 и погрешностью 0.001 стоит недорого, а MaxAge в сутки отсекает древнюю историю: без него после недели апгрейдов квантиль помнит времена старой версии кода и ползёт вниз неделями — улучшение, которого нет. Подводный камень окна: при малом числе наблюдений квантиль дёргается от каждой задачи, и график превращается в пилу. Для ночных задач с сотней запусков окно в сутки достаточно; для задачи раз в час — мало, и честнее отказаться от квантиля в пользу [гистограммы](/courses/prometheus/lessons/07-histogram/) с несколькими бакетами. MaxAge — единственный способ заставить клиентские квантили забывать прошлое.

### Проверка: сторона подсчёта

**Вопрос:** Где считается квантиль у summary?

- На сервере Prometheus при выполнении запроса
- ✅ В приложении, на потоке наблюдений
- В Grafana при отрисовке панели
- Квантиль не считается вовсе

Summary — клиентские квантили: приложение отдаёт готовые числа с лейблом quantile. Гистограмма отдаёт бакеты, и квантиль считает сервер — на этом и держится её агрегируемость.

### Проверка: экспозиция

**Вопрос:** Сколько серий экспортирует summary с Objectives {0.9: 0.01, 0.99: 0.001}?

- 2
- 3
- ✅ 4
- 1

Две серии с quantile="0.9" и quantile="0.99", плюс _sum и _count — итого четыре. Среднее считается из суммы и счётчика, как у гистограммы.

### Проверка: выбор типа

**Вопрос:** Нужен p99 по всем 20 инстансам и по каждому отдельно. Что берёте?

- Summary на каждом инстансе и avg по quantile-рядам
- ✅ Histogram плюс histogram_quantile по сумме бакетов
- Gauge, в который пишете p99 из кода
- Два summary: один на инстанс, один общий

Бакеты — обычные счётчики: их суммируют sum by (le) и получают распределение фермы, из которого histogram_quantile достаёт честный общий p99; срез по инстансу — тот же запрос с by. Среднее по чужим квантилям смысла не имеет.

### Summary без целей

```go
var opDuration = prometheus.NewSummary(
        prometheus.SummaryOpts{
                Name: "op_duration_seconds",
                Help: "Длительность операции",
        },
)

opDuration.Observe(0.2)
opDuration.Observe(0.4)
```

**Что выведет программа?**

- Квантили 0.5, 0.9 и 0.99 по умолчанию
- ✅ Только _sum и _count: в свежих версиях дефолтных целей нет
- Ошибку при регистрации в реестре
- Одну серию quantile="0.5"

В современных client_golang Objectives задаётся явно: без целей summary экспортирует сумму и счётчик — доступна только средняя. В старых версиях был дефолт 0.5, 0.9, 0.99, поэтому старые туториалы могут показывать иное.

### Соберите summary

Допишите создание summary, строгую погрешность для p99 и наблюдение значения.

```go
var opDuration = prometheus.___(
        prometheus.SummaryOpts{
                Name:       "op_duration_seconds",
                Help:       "Длительность операции",
                Objectives: map[float64]float64{0.99: ___},
        },
)

opDuration.___(elapsed)
```

Подсказка: Summary создаёт NewSummary, квантиль 0.99 со строгой погрешностью — это 0.001, а значение наблюдают через Observe.

Правильные ответы в пропусках: `NewSummary`, `0.001`, `Observe`

### Соберите summary для фоновой задачи

Расставьте строки объявления summary с квантилем 0.99 и его регистрации.

Правильный порядок строк:

1. `var latency = prometheus.NewSummary(prometheus.SummaryOpts{`
2. `        Name: "job_seconds",`
3. `        Help: "Длительность фоновой задачи",`
4. `        Objectives: map[float64]float64{0.99: 0.001},`
5. `})`
6. `prometheus.MustRegister(latency)`

### Карточки: summary

- **Summary** — Метрика с клиентскими квантилями: приложение ведёт потоковое приближение и отдаёт готовые числа с лейблом quantile, плюс _sum и _count.
- **Objectives** — Карта целей map[float64]float64{0.99: 0.001}: ключ — квантиль, значение — допустимая погрешность. В свежих client_golang задаётся явно.
- **Погрешность** — Обещание точности: заявленное значение лежит в интервале заданной ширины вокруг истинного квантиля. Строгая погрешность стоит памяти процесса.
- **Почему квантили не суммируются** — Квантиль нелинейна: медиана суммы не равна сумме медиан. Поэтому p99 фермы нельзя собрать из p99 инстансов — нужен histogram_quantile по сумме бакетов.
- **MaxAge** — SummaryOpts.MaxAge задаёт длину скользящего окна наблюдений; ноль по умолчанию означает окно без ограничения по времени — квантили считаются по всей истории процесса.
- **go_gc_duration_seconds** — Встроенная summary дефолтного реестра: квантили длительности пауз GC 0, 0.25, 0.5, 0.75, 1. Пример легитимного применения клиентских квантилей.

Теперь у вас есть оба типа для распределений, и выбор между ними сводится к одному вопросу: будете ли вы агрегировать. Для HTTP-метрик ответ почти всегда «да», и в следующем уроке мы уже не будем выбирать типы руками: promhttp даёт готовые обёртки InstrumentHandlerDuration и InstrumentHandlerCounter, которые заводят латентность и статусы для всех маршрутов одним middleware. Если гистограмма из прошлого урока осталась туманной — вернитесь к нему: summary опирается на те же sum и count. Формулы с histogram_quantile и агрегацией бакетов разобраны в уроке про PromQL. Полный план — на странице курса Prometheus. Привычная оговорка про окружение: client_golang запускается только локальным Go — песочница сайта библиотечные пакеты не исполняет, зато экспозицию из этого урока можно читать curl-ом и сравнивать с гистограммой из прошлого урока глазами.

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

- Summary считает квантили в приложении: готовые числа с лейблом quantile в экспозиции.
- Objectives — карта квантиль-погрешность; в свежих client_golang задаётся явно, дефолта больше нет.
- sum и count экспортируются всегда: средняя доступна без целей и без бакетов.
- Квантили не агрегируются: медианы не складываются, поэтому p99 фермы — только через гистограмму.
- Новый квантиль у summary — релиз; у гистограммы — правка запроса.
- Правило: HTTP-латентность — histogram; summary — точный квантиль одиночного процесса.

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

Заведите в учебном сервисе одну и ту же метрику дважды: histogram и summary с парой целей — на время выведите обе в /metrics. Сравните экспозиции глазами: где какие серии, сколько их, что доступно без запросов. Затем попытайтесь написать PromQL-запрос общего p99 для двух инстансов и убедитесь, что для summary такой запрос невозможен — это лучший способ запомнить главное отличие. Нужен локальный Go.

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

### Почему в свежих версиях client_golang у summary пропали квантили?

Дефолтный набор целей 0.5, 0.9, 0.99 убрали: Objectives задаётся явно в SummaryOpts. Summary без целей экспортирует только sum и count — добавьте карту Objectives, и квантили вернутся.

### Можно ли summary с двух инстансов усреднить хотя бы примерно?

Нет: среднее квантилей — не квантиль среднего, и ошибку не ограничить никакой погрешностью из Objectives. Распределение фермы собирается из гистограммы; summary оставьте метрикой одного процесса.

### Что даёт MaxAge в SummaryOpts?

Длину скользящего окна: наблюдения старше окна перестают влиять на квантиль, и метрика отражает недавнее поведение, а не всю историю процесса с момента старта. По умолчанию окно отключено.
