Monitoramento do PostgreSQL

Problemas de performance no banco geralmente ficam invisíveis até causar indisponibilidade. Monitoramento proativo identifica queries lentas, bloqueios, acúmulo de conexões e degradação de performance antes que os usuários percebam. Combinando pg_stat_statements com Prometheus e Grafana, você tem visibilidade completa do banco.

Habilitar pg_stat_statements para análise de queries

A extensão pg_stat_statements agrega estatísticas de todas as queries executadas:

sql
# postgresql.conf:
shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.max = 10000
pg_stat_statements.track = all
pg_stat_statements.track_utility = on
track_activity_query_size = 4096     # aumentar para queries longas

# Reiniciar PostgreSQL
sudo systemctl restart postgresql

# Criar extensão no banco de monitoramento:
sudo -u postgres psql minha_db
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- Top 10 queries por tempo total (em minutos de CPU):
SELECT round(total_exec_time::numeric / 60000, 2) AS total_min,
       calls,
       round(mean_exec_time::numeric, 2) AS media_ms,
       round((100 * total_exec_time / sum(total_exec_time) OVER ())::numeric, 2) AS pct_total,
       left(query, 80) AS query_trunc
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;

Monitorar conexões, bloqueios e esperas

Identifique conexões problemáticas e deadlocks em tempo real:

sql
-- Conexões por estado:
SELECT state,
       count(*) AS total,
       max(now() - state_change) AS max_tempo
FROM pg_stat_activity
WHERE datname = 'minha_db'
GROUP BY state
ORDER BY total DESC;

-- Queries rodando há mais de 30 segundos:
SELECT pid,
       now() - query_start AS duracao,
       substring(query, 1, 100) AS query_trunc,
       state,
       wait_event_type,
       wait_event
FROM pg_stat_activity
WHERE datname = 'minha_db'
  AND state != 'idle'
  AND now() - query_start > interval '30 seconds'
ORDER BY duracao DESC;

-- Bloqueios ativos (quem está bloqueando quem):
SELECT blocked.pid AS pid_bloqueado,
       blocked.query AS query_bloqueada,
       blocking.pid AS pid_bloqueador,
       blocking.query AS query_bloqueadora,
       now() - blocked.query_start AS tempo_bloqueado
FROM pg_stat_activity blocked
JOIN pg_stat_activity blocking
  ON blocking.pid = ANY(pg_blocking_pids(blocked.pid))
WHERE blocked.cardinality(pg_blocking_pids(blocked.pid)) > 0;

-- Encerrar query travada:
SELECT pg_terminate_backend(PID);

Exportar métricas para Prometheus

postgres_exporter coleta métricas do PostgreSQL e as expõe para o Prometheus:

yaml
# Docker Compose — postgres_exporter:
services:
  postgres-exporter:
    image: prometheuscommunity/postgres-exporter:latest
    restart: always
    environment:
      DATA_SOURCE_NAME: "postgresql://monitor:senha_monitor@localhost:5432/postgres?sslmode=disable"
      PG_EXPORTER_DISABLE_DEFAULT_METRICS: "false"
    ports:
      - "127.0.0.1:9187:9187"
    extra_hosts:
      - "localhost:host-gateway"

# No PostgreSQL — criar usuário de monitoramento (somente leitura):
CREATE ROLE monitor WITH LOGIN PASSWORD 'senha_monitor';
GRANT pg_monitor TO monitor;   # papel de monitoramento built-in (PG 10+)

# prometheus.yml — adicionar scrape config:
scrape_configs:
  - job_name: 'postgresql'
    static_configs:
      - targets: ['localhost:9187']
    relabel_configs:
      - source_labels: [__address__]
        target_label: instance
        replacement: 'postgres-prod'

Dashboard Grafana para PostgreSQL

Métricas essenciais para monitorar em painéis do Grafana:

promql
# Importar dashboard pronto para postgres_exporter:
# Dashboard ID: 9628 (PostgreSQL Database) — importar no Grafana
# Settings → Import → Dashboard ID: 9628

# Métricas principais do postgres_exporter:

# Taxa de conexões (alerta se > 80% do max):
pg_stat_activity_count{state="active"} / pg_settings_max_connections * 100

# Cache hit ratio (alerta se < 99%):
rate(pg_stat_database_blks_hit[5m]) /
(rate(pg_stat_database_blks_hit[5m]) + rate(pg_stat_database_blks_read[5m])) * 100

# Tamanho do banco (crescimento anormal):
pg_database_size_bytes

# Transações por segundo:
rate(pg_stat_database_xact_commit[1m]) + rate(pg_stat_database_xact_rollback[1m])

# Queries lentas (requer pg_stat_statements exporter):
topk(10, pg_stat_statements_mean_exec_time_seconds)

# Lag de replicação (se tiver standby):
pg_replication_lag

Alertas críticos para PostgreSQL

Regras de alerta no Prometheus para situações que requerem ação imediata:

yaml
# prometheus-rules-postgres.yml
groups:
  - name: postgresql
    rules:
      # Muitas conexões abertas
      - alert: PostgresConnectionsHigh
        expr: |
          sum(pg_stat_activity_count) by (instance)
          / pg_settings_max_connections * 100 > 80
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "PostgreSQL: conexões > 80% do máximo em {{ $labels.instance }}"

      # Cache hit ratio baixo
      - alert: PostgresCacheHitRatioLow
        expr: |
          rate(pg_stat_database_blks_hit[5m])
          / (rate(pg_stat_database_blks_hit[5m]) + rate(pg_stat_database_blks_read[5m]) + 0.001)
          * 100 < 95
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "PostgreSQL: cache hit ratio < 95%: considere aumentar shared_buffers"

      # Replicação com lag alto
      - alert: PostgresReplicationLagHigh
        expr: pg_replication_lag > 60
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "PostgreSQL: lag de replicação > 60s"

      # Bloqueios de longa duração
      - alert: PostgresLongRunningLocks
        expr: pg_locks_count > 20
        for: 5m
        labels:
          severity: warning

Não quer configurar manualmente?

Implante o VPS em menos de 5 minutos com a Runstack. Infraestrutura da OPEN Datacenter, com servidores em São Paulo.

Perguntas frequentes

Primeiro passo: pg_stat_statements ordenado por total_exec_time, mostra o acumulado. Para problemas ativos agora: SELECT * FROM pg_stat_activity WHERE state = "active" AND query_start < now() - interval "10s". Use EXPLAIN (ANALYZE, BUFFERS) na query problemática para ver o plano e onde o tempo está sendo gasto.

Cache hit ratio < 99% indica que o banco está lendo frequentemente do disco em vez do buffer cache. Soluções em ordem de impacto: (1) aumentar shared_buffers (até 25-40% da RAM); (2) identificar quais tabelas causam mais cache miss com pg_statio_user_tables; (3) adicionar mais RAM à VPS se shared_buffers já está no máximo razoável.

Conexões idle ("idle" no pg_stat_activity) estão abertas mas sem query ativa, consumindo um processo (~5 MB RAM) sem fazer trabalho. Aplicações Node.js com pool de conexões grande criam muitas connections idle. Solução: use PgBouncer para reduzir conexões reais ao banco, ou configure idle_in_transaction_session_timeout no PostgreSQL para terminar sessões ociosas.

pg_database_size_bytes via postgres_exporter no Grafana com alerta de crescimento anormal. Para análise manual: SELECT pg_size_pretty(pg_database_size("minha_db")). Para ver quais tabelas crescem mais: monitore pg_total_relation_size por tabela ao longo do tempo. Alerte quando o crescimento ultrapassar X% em 24h ou quando o disco estiver próximo de 80%.

PostgreSQL detecta e resolve deadlocks automaticamente (aborta uma das transações). No log: ERROR: deadlock detected DETAIL: Process X waits for ShareLock on transaction Y; blocked by process Z. Para investigar: habilite log_lock_waits = on e deadlock_timeout = 1s no postgresql.conf. Deadlocks frequentes indicam bug na aplicação (ordem inconsistente de acesso a recursos).