5. Мониторинг и алертинг PostgreSQL
Мониторинг ключевых метрик (подключения, replication lag, vacuum, диск, WAL, блокировки и deadlock) и алерты без лишнего шума. Инструменты: postgres_exporter + Prometheus, Grafana, системные представления (pg_stat_activity, pg_locks). В разделе — примеры запросов и правил алертов с комментариями и best practices для production.
12. Метрики: что мониторить
Connections
Число подключений к экземпляру не должно приближаться к max_connections — иначе новые подключения получают «too many connections». Мониторить: текущее число по БД и по состоянию (active, idle, idle in transaction).
-- Текущее число подключений и лимит
SELECT count(*), (SELECT setting::int FROM pg_settings WHERE name = 'max_connections') AS max_conn
FROM pg_stat_activity;
-- По базе и состоянию
SELECT datname, state, count(*) FROM pg_stat_activity GROUP BY datname, state ORDER BY count DESC;
В Prometheus postgres_exporter отдаёт метрику pg_stat_activity_count по state/datname; алерт: pg_stat_activity_count / max_connections > 0.8.
Replication lag
Отставание реплики от primary по байтам WAL или по времени воспроизведения. Большой lag — риск потери данных при failover и задержка чтения на реплике.
-- На PRIMARY: lag в байтах по репликам
SELECT application_name, pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS lag_bytes
FROM pg_stat_replication;
-- На STANDBY: задержка воспроизведения (секунды)
SELECT EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp())) AS replay_lag_seconds;
postgres_exporter: pg_replication_lag (в байтах или секундах в зависимости от настроек). Алерт: lag > N секунд или > M байт.
Vacuum status
Следить, что autovacuum успевает: не накапливаются ли мёртвые строки, не отстаёт ли возраст транзакций (wraparound). Метрики: n_dead_tup, n_live_tup, last_autovacuum, возраст datfrozenxid.
-- Таблицы с большим числом мёртвых строк и давно не вакуумившиеся
SELECT schemaname, relname, n_live_tup, n_dead_tup,
round(100.0 * n_dead_tup / NULLIF(n_live_tup + n_dead_tup, 0), 1) AS dead_pct,
last_autovacuum, last_vacuum
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC LIMIT 20;
-- Возраст самой старой незамороженной транзакции (предупреждение при > 150M–200M)
SELECT max(age(datfrozenxid)) AS max_frozen_age FROM pg_database;
postgres_exporter даёт метрики по pg_stat_user_tables (dead tuples, last vacuum); кастомный запрос или отдельный экспортер — для age(datfrozenxid). Алерт: dead_pct выше порога, last_autovacuum давно не обновлялся, frozen age приближается к порогу wraparound.
Disk usage
Свободное место на разделах с данными БД и WAL. При нехватке места PostgreSQL может отказаться писать WAL или падать.
- На сервере: метрики с node_exporter (например, node_filesystem_avail_bytes) по mountpoint каталога данных и pg_wal.
- Размер БД и таблиц — для планирования:
SELECT pg_database.datname, pg_size_pretty(pg_database_size(pg_database.datname)) AS size
FROM pg_database ORDER BY pg_database_size(pg_database.datname) DESC;
SELECT relname, pg_size_pretty(pg_total_relation_size(relid)) AS size
FROM pg_stat_user_tables ORDER BY pg_total_relation_size(relid) DESC LIMIT 20;
Алерт: свободное место на диске < 15% (или абсолютный порог).
WAL generation
Скорость генерации WAL (байт/сек) влияет на заполнение диска и нагрузку на репликацию/архив. Метрики: прирост позиции WAL во времени (через postgres_exporter или кастомный скрипт). Резкий рост — признак тяжёлой записи или долгих транзакций.
-- Текущая позиция WAL (для расчёта прироста между замерами)
SELECT pg_current_wal_lsn();
Locks и deadlocks
Долгие блокировки блокируют другие запросы; deadlock завершается прерыванием одной из транзакций. Мониторить: число ожидающих блокировку, длительность ожидания, факты deadlock (счётчик в pg_stat_database).
-- Активные блокировки и кто ждёт
SELECT blocked_locks.pid AS blocked_pid,
blocking_locks.pid AS blocking_pid,
blocked_activity.query AS blocked_query,
blocking_activity.query AS blocking_query,
now() - blocked_activity.query_start AS blocked_duration
FROM pg_catalog.pg_locks blocked_locks
JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid
JOIN pg_catalog.pg_locks blocking_locks ON blocking_locks.locktype = blocked_locks.locktype
AND blocking_locks.database IS NOT DISTINCT FROM blocked_locks.database
AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
AND blocking_locks.pid != blocked_locks.pid
JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
WHERE NOT blocked_locks.granted;
-- Число deadlock'ов по БД (накопленный счётчик)
SELECT datname, deadlocks FROM pg_stat_database;
postgres_exporter: pg_stat_database_deadlocks; для блокировок — кастомный запрос или алерт по длительности запросов в pg_stat_activity (state = 'active' и долгое время). Алерт: deadlocks > 0 за период; долго висящие blocking-запросы.
Idle in transaction
Сессии в состоянии idle in transaction держат соединение и блокировки (в т.ч. невидимые для VACUUM строки), не делая полезной работы. Долгие idle in transaction — частая причина bloat и блокировок.
-- Сессии idle in transaction дольше 5 минут
SELECT pid, usename, application_name, state, now() - state_change AS idle_duration, query
FROM pg_stat_activity
WHERE state = 'idle in transaction'
AND state_change < now() - interval '5 minutes';
Алерт: число таких сессий выше порога или максимальная длительность idle in transaction > N минут.
Инструменты: postgres_exporter, Prometheus, Grafana
postgres_exporter — экспортер метрик PostgreSQL для Prometheus. Запускается рядом с БД или в сети с доступом к ней; подключается к одной или нескольким БД по DSN, отдаёт метрики по активности, репликации, размерам, блокам и т.д. Рекомендуется отдельный пользователь с ограниченными правами (только чтение системных каталогов и представлений).
# Пример флага для подключения (переменные окружения или аргументы)
# DATA_SOURCE_NAME="postgresql://monitor:password@localhost:5432/postgres?sslmode=disable"
# Запуск: ./postgres_exporter
В Prometheus добавляют scrape job на порт postgres_exporter (обычно 9187). В Grafana используют готовые дашборды (например, «PostgreSQL» по ID 9628) или строят свои на основе метрик postgres_exporter и node_exporter.
pg_stat_activity и pg_locks — при необходимости детальной диагностики запросы выполняют вручную или через скрипты/кастомные метрики (например, текст запроса блокирующей сессии, длительность).
13. Алерты: реальные и без шума
Алерты должны срабатывать по действительным проблемам и вести к действию (runbook). Избегать порогов, при которых алерт «дёргается» при нормальной нагрузке (например, кратковременный всплеск соединений). Использовать for в Prometheus, чтобы не алертить на короткий скачок.
Примеры правил алертинга (Prometheus)
Ниже — идеи выражений; точные имена метрик зависят от установки postgres_exporter и версии.
# Replication lag > 60 секунд (на standby или с метрики primary)
# Пример: pg_replication_lag > 60
- alert: PostgresReplicationLagHigh
expr: pg_replication_lag > 60
for: 5m
labels: { severity: warning }
annotations:
summary: "PostgreSQL replication lag high on {{ $labels.instance }}"
# Autovacuum не успевает: много мёртвых строк и last_autovacuum старый
# Упрощённо: таблицы с n_dead_tup выше порога (если экспортер отдаёт такие метрики)
# Или кастомная метрика по запросу к pg_stat_user_tables
- alert: PostgresDeadTuplesHigh
expr: pg_stat_user_tables_n_dead_tup > 100000
for: 30m
labels: { severity: warning }
annotations:
summary: "PostgreSQL table {{ $labels.relname }} has many dead tuples"
# Свободное место на диске < 15% (node_exporter, раздел с данными)
- alert: DiskSpaceLow
expr: (node_filesystem_avail_bytes / node_filesystem_size_bytes) * 100 < 15
for: 5m
labels: { severity: critical }
annotations:
summary: "Disk space below 15% on {{ $labels.instance }} {{ $labels.mountpoint }}"
# Слишком много idle in transaction (кастомная метрика или текст-запрос)
# Пример: count по pg_stat_activity где state = 'idle in transaction' и state_change старше 5 min
- alert: PostgresIdleInTransactionTooLong
expr: pg_stat_activity_idle_in_transaction_count > 5
for: 10m
labels: { severity: warning }
annotations:
summary: "PostgreSQL: many long idle in transaction sessions"
Дополнительно: алерт на connections / max_connections > 0.8; на deadlocks за последние N минут; на replication slot inactive (реплика отвалилась, слот не освобождён). Для каждой алерты описать в аннотациях или в runbook, что делать (проверить запросы, убить долгие idle in transaction, увеличить место на диске, проверить реплику и т.д.).
Best practices для production
| Область | Рекомендация |
|---|---|
| Метрики | Обязательно: connections, replication lag, vacuum (dead tuples / last autovacuum), disk space, при необходимости WAL rate и locks. |
| Экспортер | Отдельная роль с минимальными правами; не под суперпользователем. |
| Алерты | Использовать for (5–10 мин), чтобы не шуметь на кратковременных всплесках; severity разделять (critical — реагировать сразу, warning — в рабочее время). |
| Runbook | У каждой алерты — краткое описание действий (проверить pg_stat_activity, реплику, vacuum, диск). |
| Idle in transaction | Мониторить и алертить; в приложении закрывать транзакции или ограничивать таймаут idle in transaction (idle_in_transaction_session_timeout). |
Паттерны и антипаттерны
| Паттерн | Описание |
|---|---|
| Один дашборд по кластеру/инстансу | Connections, lag, vacuum, disk, топ запросов — в одном месте. |
| Алерты с for и осмысленными порогами | Меньше ложных срабатываний, понятная реакция. |
| idle_in_transaction_session_timeout | Автоматическое обрывание долгих idle in transaction (PostgreSQL 9.6+). |
| Антипаттерн | Почему плохо | Что делать |
|---|---|---|
| Алертить на каждую метрику без for | Шум при нормальных колебаниях. | Добавить for и поднять пороги. |
| Нет алерта на диск | Заполнение диска — падение или остановка записи. | Алерт на свободное место (например, < 15%). |
| Игнорировать idle in transaction | Bloat и блокировки. | Мониторить, алертить, настроить timeout в приложении/БД. |