Мониторинг самого gotcha
gotcha следит за вашими сервисами. Эта страница — о том, как следить за самим gotcha: что он рассказывает о своём здоровье и куда смотреть, если вы подозреваете, что он теряет данные.
Эндпойнты
| Путь | Для чего |
|---|---|
/metrics | Счётчики буферов, потерь и отказов вставки в формате Prometheus. Не обращается к базам вообще, поэтому отвечает даже когда PostgreSQL или ClickHouse лежит — а именно тогда он и нужен. |
/healthz | Живость: отвечает 200, пока процесс обслуживает HTTP. Состояние баз есть в теле (postgres, clickhouse, version), но на код ответа не влияет — вешайте сюда liveness-пробу. |
/readyz | Готовность: те же поля плюс status, но 503, пока PostgreSQL или ClickHouse недоступны. Сюда — readiness-пробу и healthcheck контейнера. |
/version | Метаданные сборки: version, commit, date, go (версия Go, которой собран бинарь) и stamped — вшиты ли в сборку git-метаданные. stamped: false означает, что образ собран мимо make и версия взята из исходников, а не из тега релиза. |
Разделение важно: liveness-проба на ручке, которая падает при сбое хранилища, перезапускает живой процесс, а каждый перезапуск выбрасывает буферы — то есть ровно ту телеметрию, которую они копили, дожидаясь возвращения хранилища.
Ни один не требует аутентификации и ни один не отдаёт персональных данных:
/metrics содержит только счётчики, никогда — содержимое событий. Если инстанс
смотрит в интернет, ограничьте /metrics на реверс-прокси: числа выдают объём
вашего трафика, а его не всегда стоит публиковать.
Когда контейнер стал unhealthy
Штатный compose-файл даёт контейнеру gotcha healthcheck на /readyz.
Проверка — подкоманда самого бинаря, gotcha --healthcheck: она переживёт
переход на distroless-базу, где нет curl. Целевой URL строится из
GOTCHA_ADDR (хост всегда 127.0.0.1 — проверка ходит к себе), поэтому смена
порта прослушивания не оставляет проверку стучаться в мёртвый :8080; для
нестандартных схем — TLS внутри контейнера, другой путь — URL переопределяется
флагом --healthcheck-url=<url>.
Но важно понимать, что даёт healthcheck: docker compose не перезапускает
unhealthy контейнер. Проваленная проверка только меняет метку в docker ps (реакция
на unhealthy есть у Swarm/Kubernetes); политика restart ловит упавший
процесс, но не зависший. Посмотреть состояние и вывод последних проверок:
docker ps # колонка STATUS: (healthy) / (unhealthy)
docker inspect --format '{{json .State.Health}}' gotcha-gotcha-1
Чтобы состояние было видно снаружи, следите не за меткой, а за сервисом:
направьте на /readyz аптайм-монитор с другого инстанса gotcha (мониторинг
HTTP-ручки — ровно то, что продукт умеет штатно) или заведите алерт на метрику
gotcha_up вашего сборщика.
Автолекаря с доступом к docker-сокету в поставке нет намеренно: доступ к сокету — это root на хосте, и такая поставка обменяла бы безопасность всей установки на сценарий, который лучше решает супервизор.
Как собирать
Подойдёт любой агент, понимающий формат Prometheus, — Prometheus, VictoriaMetrics, Grafana Agent, OpenTelemetry Collector:
scrape_configs:
- job_name: gotcha
static_configs:
# 8080 — порт ВНУТРИ контейнера; наружу штатный compose публикует 59080
# (GOTCHA_PORT). Укажите тот порт, по которому инстанс доступен вам.
- targets: ["gotcha.example.com:59080"]
Что означают метрики
gotcha_writer_buffered_rows{writer="…"} — строки, лежащие в памяти в
ожидании записи в ClickHouse. Писатели: events, spans, metrics,
profiles, logs, uptime_results. На здоровом инстансе значение держится
около нуля и сбрасывается за секунду-другую. Число, которое растёт и не
падает, означает, что ClickHouse не принимает записи.
gotcha_writer_insert_failures_total{writer="…"} — сколько батчей не
записалось. Само по себе это не потеря: батч возвращается в буфер и
повторяется. Рост счётчика при стабильном буфере означает, что транзиентные
ошибки поглощаются; рост вместе с растущим буфером — что повторы не
проходят, и дело идёт к потере.
gotcha_writer_dropped_rows_total{writer="…"} — строки, выброшенные из-за
переполнения буфера. Они потеряны безвозвратно. Любое ненулевое значение
заслуживает внимания; растущее означает, что телеметрия теряется прямо сейчас.
gotcha_pipeline_queued_tasks / gotcha_pipeline_queue_capacity —
глубина и размер очереди приёма между HTTP-обработчиком и воркерами. Глубина,
подолгу близкая к вместимости, означает, что воркеры не успевают — обычно из-за
медленного PostgreSQL (каждая задача делает апсерт issue).
gotcha_pipeline_queued_bytes — байты, занятые задачами в этой очереди.
У очереди помимо счётного есть байтовый бюджет (GOTCHA_MAX_QUEUE_BYTES):
тысяча мелких событий и тысяча мегабайтных — очень разная нагрузка при одной
глубине. Когда дропы идут с reason="queue_bytes", кончился именно этот
бюджет.
gotcha_pipeline_dropped_tasks_total{reason="…"} — события и транзакции,
выброшенные конвейером. Тоже безвозвратно. Метка reason говорит, что чинить:
reason | Что произошло | Что делать |
|---|---|---|
queue_full | обработка не успевает за приёмом | больше воркеров, быстрее PostgreSQL |
queue_bytes | исчерпан байтовый бюджет очереди — задачи крупнее обычного | смотреть GOTCHA_MAX_QUEUE_BYTES и размер событий |
storage_error | не удалось записать в PostgreSQL (обычно таймаут апсерта) | чинить базу; очередь тут ни при чём |
panic | обработчик упал на конкретном элементе | ошибка продукта: сообщите её нам с логом |
closed | приём уже остановлен, событие пришло на излёте | нормально при выключении; постоянный рост означает рестарт-цикл |
Разделение важно потому, что первые две причины лечатся размером очереди, а
storage_error — нет: сколько ни увеличивай очередь, недоступная база от этого
не станет доступной.
gotcha_cardinality_collapsed_total / gotcha_cardinality_tracked_values
— работа защиты кардинальности: сколько значений открытых полей (имена
транзакций, окружения, имена метрик, сервисы, операции) схлопнуто в
переполненную корзину из-за достижения проектом GOTCHA_CARDINALITY_LIMIT, и
сколько различных значений защита помнит прямо сейчас. Растущий счётчик
схлопнутого означает, что часть имён пропала из списков — на затронутых
экранах показывается предупреждение; обычная причина — идентификатор, попавший
в имя. Gauge отслеживаемых значений — собственный расход памяти защиты.
gotcha_host_evaluator_last_tick_timestamp_seconds /
gotcha_host_evaluator_tick_duration_seconds — момент последнего
завершённого прохода оценщика хостов (диск, память, нагрузка, тишина) и его
длительность. Наблюдать здесь надо не отказ, а продолжение работы: умерший
оценщик снаружи выглядит ровно как «на хостах всё спокойно» — тишина и есть его
обычный вывод. Разница между текущим временем и меткой, заметно превышающая
GOTCHA_HOST_EVAL_INTERVAL, означает, что пороги хостов не вычисляются;
длительность, подбирающаяся к интервалу, — что оценщик перестаёт укладываться
в свой период (обычно из-за медленного ClickHouse или разросшегося парка).
Метку времени двигает только проход, дошедший до конца. Проход, оборванный
собственным дедлайном (он успевает оценить часть парка и выходит), метку НЕ
обновляет — иначе оценщик, который каждый раз обрывается на половине хостов,
выглядел бы отсюда идеально здоровым. Длительность при этом публикуется всегда:
по ней и виден упор в бюджет. Причина обрыва — строкой tick did not finish within its budget в журнале.
gotcha_slo_evaluator_last_tick_timestamp_seconds /
gotcha_slo_evaluator_tick_duration_seconds — момент последнего
завершённого прохода оценщика burn rate по всем включённым SLO и его
длительность. Слепая зона та же, что у оценщика хостов: тишина — нормальный
вывод, поэтому умерший оценщик выглядит ровно как «все бюджеты в порядке».
Разница между текущим временем и меткой, заметно превышающая
GOTCHA_SLO_EVAL_INTERVAL, означает, что burn rate не пересчитывается, а
инциденты сжигания бюджета не открываются и не закрываются; длительность,
подбирающаяся к интервалу, — что оценщик перестаёт укладываться в период.
gotcha_host_registration_failures_total — сколько фоновых записей реестра
хостов не удалось выполнить. Пока счётчик растёт, last_seen хостов не
обновляется, а значит инциденты «тишина» могут открываться по живым машинам;
причина почти всегда в недоступности PostgreSQL.
gotcha_host_registrations_rejected_total — новые имена хостов, отброшенные
потолком в 1000 хостов на проект. Ненулевое значение означает, что в разделе
«Хосты» не появляются новые машины: либо парк действительно дорос до потолка,
либо в имя хоста попал идентификатор (поды, автоскейл), и каждый экземпляр
регистрируется как отдельная машина.
gotcha_notify_pending_jobs / gotcha_notify_oldest_pending_age_seconds —
глубина очереди доставки и возраст самой старой ждущей задачи. Возраст важнее
глубины: только он отличает «очередь пуста, потому что всё доставлено» от
«очередь стоит». Растущий возраст при живом процессе означает, что доставка
упирается в канал — смотрите gotcha_notify_retried_total и журнал доставок в
интерфейсе.
gotcha_notify_sent_total / gotcha_notify_failed_total /
gotcha_notify_retried_total — доставлено, брошено после исчерпания попыток,
отложено на повтор. gotcha_notify_failed_jobs — сколько таких брошенных
задач лежит в очереди прямо сейчас.
gotcha_memory_limit_bytes — потолок кучи, который продукт вывел из лимита
контейнера (80% лимита). Ноль означает, что лимита нет: буферы будут расти,
пока не кончится память ХОСТА, и первым сработает OOM-killer ядра — а он
выбрасывает всё накопленное, а не избыток. Если здесь ноль, задайте mem_limit
контейнеру или GOMEMLIMIT вручную.
gotcha_entities_purged_total — строки, удалённые из PostgreSQL по
истечении GOTCHA_RETENTION_DAYS: группы, закрытые инциденты, регрессии.
Ожидаемое поведение, а не сбой; счётчик существует потому, что у любого
исчезновения данных должно быть число, которое можно посмотреть. Устойчивый ноль
при заданном сроке хранения означает, что чистка не работает — и список проблем
показывает группы, событий которых уже нет.
gotcha_purge_queue_depth / gotcha_purge_queue_oldest_seconds —
сколько проектов ждут очистки телеметрии в ClickHouse после удаления и сколько
секунд ждёт самая старая заявка. Удаление проекта ставит заявку той же
транзакцией, что удаляет строку, а выполняет её фоновый исполнитель — запрос не
держит восемь тяжёлых мутаций. Величины две, потому что глубина одна ничего не
говорит: одна заявка, висящая третьи сутки, по глубине неотличима от
поставленной минуту назад. Растущий возраст означает невыполненное требование об
удалении данных — причина последней попытки лежит в project_purge_queue.last_error.
gotcha_projects_purged_total — сколько проектов очищено. Как и
gotcha_entities_purged_total, существует потому, что у исчезновения данных
должно быть число.
gotcha_storage_free_bytes{store="…"} / gotcha_storage_total_bytes{store="…"}
— свободно и всего байт на томе, где хранилище физически держит данные.
Сегодня их отдаёт только store="clickhouse" (системная таблица дисков
ClickHouse): у PostgreSQL через обычное соединение узнать объём ТОМА нельзя —
СУБД знает размер своих данных, но не размер диска под собой, — поэтому под
store="postgres" этих двух метрик нет и не будет; смотрите
gotcha_storage_used_bytes ниже. Значение — NaN, а не 0, пока опрос ни
разу не завершился успешно. Это не «подождите пару минут после запуска»:
первый опрос синхронный и происходит прямо при регистрации метрик, до того как
открывается порт, — к моменту, когда /metrics вообще можно прочитать, первый
опрос уже случился. Поэтому NaN в выдаче означает ровно одно: опрос
проваливается — например, у служебного пользователя нет доступа к системной
таблице дисков ClickHouse, или запрос к PostgreSQL не укладывается в таймаут.
Рядом в логе — запись storage metrics: poll failed с полями store и
error, которая скажет, почему именно. Повтор — раз в 5 минут; ноль вместо
NaN тут не подошёл бы: он читался бы как «диск заполнен», а не как «опрос
ломается».
gotcha_storage_used_bytes{store="postgres"} — сколько места на диске
уже занимают данные PostgreSQL (размер базы). Это не свободное место и не
общий объём тома — см. предыдущий пункт про то, почему PostgreSQL их не
отдаёт. Чтобы понять, сколько ещё есть запаса, сопоставьте это число с
известным вам размером тома PostgreSQL (обычно один том на инстанс) —
вручную: gotcha не может сделать это за вас, он не знает размер вашего диска.
gotcha_web_cross_origin_rejected_total — POST-запросы, отклонённые из-за
несовпадения Origin/Referer с GOTCHA_BASE_URL (защита интерфейса от
межсайтовых запросов). Редкие единицы — шум сканеров; постоянный рост от
живых пользователей обычно означает, что GOTCHA_BASE_URL не совпадает с
адресом, по которому реально открывается интерфейс — например, за прокси,
переписывающим схему.
gotcha_build_info — всегда 1, версия и режим лежат в метках. Полезно,
чтобы убедиться, что развёрнуто именно то, что вы думаете. Метка stamped
говорит, несёт ли сборка git-метаданные: stamped="false" означает образ,
собранный мимо make, — его строка версии взята из исходников, и сверить по
ней «задеплоено именно то» нельзя.
Какие алерты стоит завести
# Данные теряются прямо сейчас.
increase(gotcha_writer_dropped_rows_total[5m]) > 0
increase(gotcha_pipeline_dropped_tasks_total[5m]) > 0
# Хранилище не успевает — потери впереди.
gotcha_writer_buffered_rows > 5000
gotcha_pipeline_queued_tasks / gotcha_pipeline_queue_capacity > 0.5
Первые два — те, по которым стоит будить дежурного: они означают, что телеметрия уже потеряна и никакой повтор её не вернёт.
Место на диске — отдельная пара правил для каждого хранилища, не одна на оба:
ClickHouse отдаёт настоящую долю, а PostgreSQL — нет (см. gotcha_storage_used_bytes
выше), поэтому для него порог приходится строить на прогнозе роста, а не на
проценте.
# ClickHouse: доля свободного места известна напрямую — free и total
# приходят из одной и той же строки system.disks, дробь честная.
gotcha_storage_free_bytes{store="clickhouse"} / gotcha_storage_total_bytes{store="clickhouse"} < 0.1
# PostgreSQL: gotcha не знает объём тома, поэтому вместо доли — прогноз по
# тренду: predict_linear экстраполирует used_bytes на сутки вперёд по
# последним 6 часам роста. Порог ниже — пример для тома 20 ГБ (минимум из
# требований к диску), 90% от него; подставьте 90% от ВАШЕГО известного
# размера тома в байтах.
predict_linear(gotcha_storage_used_bytes{store="postgres"}[6h], 24*3600) > 1.8e10
Сравнение с NaN не проходит, поэтому пока опрос не даёт значения, оба
правила выше молчат — не срабатывают. Из этого следует то, о чём легко не
подумать: само правило о сломанном опросе не сообщит. Оно рассчитано на
случай, когда число ЕСТЬ и превышает порог, а не на случай, когда числа нет
вообще. Значит на «опрос не проходит» нужен отдельный признак — не порог по
значению, а слежение за самим фактом NaN в выдаче /metrics (или, что
надёжнее, за логом: записи storage metrics: poll failed с полем store).
Если «часть событий не доходит»
- Посмотрите
gotcha_writer_dropped_rows_totalиgotcha_pipeline_dropped_tasks_total. Ненулевые значения означают, что выбросил их сам gotcha. Смотрите меткуreason— она и говорит, что чинить. - Потерь нет? Значит события не доехали. Проверьте DSN отправителя и не
исчерпала ли организация квоту: отклонённое по квоте событие попадает в
счётчик «отброшено» на странице настроек организации — это другой счётчик,
не эти. Проверьте заодно
/readyz: при недоступном PostgreSQL приём отвечает, но события до хранилища не доходят. - Следите за буфером по ходу разбора. Ровный буфер без потерь означает, что приём здоров и проблема выше по течению, до gotcha.
Если место на диске подходит к концу
- Оцените, насколько заполнено. Для ClickHouse — доля
gotcha_storage_free_bytes{store="clickhouse"} / gotcha_storage_total_bytes{store="clickhouse"}: ниже 10% значит близко к отказу. Для PostgreSQL готовой доли нет — сравнитеgotcha_storage_used_bytes{store="postgres"}с известным вам размером тома вручную; тренд роста важнее самого числа — он говорит, сколько ещё есть времени, а не только сколько занято сейчас. - Проверьте, работает ли чистка.
gotcha_entities_purged_totalдолжен расти, если заданGOTCHA_RETENTION_DAYS; устойчивый ноль означает, что чистка PostgreSQL не работает, хотя должна (см. выше). Для ClickHouse TTL применяется автоматически, но у каждого вида данных свой срок хранения — см. Конфигурацию. - Определите, что растёт быстрее всего. По умолчанию самые тяжёлые по
объёму — профили (
GOTCHA_PROFILE_RETENTION_DAYS, поэтому его дефолт и короче остальных — 7 дней). Если вы отправляете continuous profiling, но не смотрите флеймографы регулярно, это первый кандидат на сокращение срока хранения или остановку на стороне SDK. - Освободите место сейчас, а не только через TTL. Удаление проекта («Настройки проекта» → «Опасная зона» → «Удалить проект») стирает его телеметрию из ClickHouse сразу, а не постепенно — годится для тестовых или заброшенных проектов, накопивших данные впустую.
- Если места мало систематически — уменьшите срок хранения. Сроки
задаются отдельно на каждый вид данных (
GOTCHA_RETENTION_DAYS,GOTCHA_SPAN_RETENTION_DAYS,GOTCHA_METRIC_RETENTION_DAYS,GOTCHA_PROFILE_RETENTION_DAYS— см. Конфигурацию). Изменение действует со следующего старта и не восстанавливает уже удалённое задним числом; и место при этом освобождается не мгновенно — ClickHouse удаляет просроченные данные фоновыми слияниями по обычному расписанию, а не в момент правки конфигурации.
Что дальше
- Конфигурация — хранение, квоты и детализация логов.
- Бэкап и восстановление — включая файл
.env.