Skip to content

Worker Gateway — Telemetria

Status: Especificação aprovada — implementação na Fase 1 do Worker v3 Última revisão: 2026-04-19 Documentos relacionados: WORKER_V3_REDIS_PLAN · RATE_LIMITS · CRON_JOBS


1. Filosofia: baseline antes de interface

A regra é simples e não negociável: coletar dados por ~4 semanas antes de construir qualquer dashboard. Construir interface antes de ter baseline produz três efeitos previsíveis e ruins:

  1. Dashboards desenhados sobre intuição mostram métricas que ninguém consulta.
  2. Limites calibrados sem baseline geram falsos positivos que destroem confiança no sistema.
  3. Tempo de engenharia gasto em UI antes de saber se o problema existe é tempo perdido.

A telemetria entra em produção em modo observação total na Fase 1 do Worker v3. O console do Upstash, wrangler tail e queries SQL diretas em gateway_metrics cobrem 100% das perguntas operacionais durante a janela de coleta. Interface dedicada só é construída quando pelo menos uma das três condições objetivas (§7) for verdadeira.


2. O que é coletado

Todo request que passa pelo Worker incrementa contadores em Redis com TTL de 2h. O conjunto mínimo é:

MétricaChave RedisTTLGranularidade
Requisições por endpointolp:m:{endpoint}:{minuto}2hminuto
Requisições por escolaolp:m:escola:{escola_id}:{minuto}2hminuto
Requisições por usuário (autenticadas)olp:m:user:{usuario_id}:{minuto}2hminuto
Bloqueios (429) por endpointolp:m:blocked:{endpoint}:{minuto}2hminuto
Distribuição de status HTTPolp:m:status:{code}:{minuto}2hminuto

Toda gravação é fire-and-forget via ctx.waitUntil(). Latência do request principal não é afetada.

Em staging, mesmo schema com prefixo olp-stg:. O isolamento é por prefixo, não por database — produção e staging compartilham o mesmo Upstash.


3. O que NÃO é coletado

A telemetria é estritamente quantitativa. Nada de PII, nada de payload.

CategoriaExemploMotivo
Conteúdo de mensagensTexto de broadcast, OTP, mensagem WhatsAppLGPD + irrelevante para rate limit
Dados pessoaisNome, CPF, email, telefoneLGPD
Body de requestJSON enviado pelo clienteVolume + privacidade
Body de responseJSON retornado pelo backendVolume + privacidade
Headers além de UA truncadoAuthorization, cookiesSegurança
IP completoApenas hash truncado para chave de burst, descartado em 5s

Logs de auditoria continuam em logs_transacoes no Postgres (responsabilidade das Edge Functions, não do Worker).


4. Pipeline de persistência

Worker (request)

    ├─▶ ctx.waitUntil( INCR olp:m:{endpoint}:{minuto} EX 7200 )

    └─▶ resposta ao cliente (latência preservada)

Cron horário (Supabase)

    ├─▶ SCAN olp:m:* (cursor, count=100)
    ├─▶ MGET valores
    ├─▶ INSERT INTO gateway_metrics (agregado por hora)
    └─▶ DEL chaves processadas (TTL natural cobre falha)

Buffer quente: Redis mantém 2h de granularidade por minuto. Suficiente para investigação reativa via console Upstash.

Persistência longa: cron horário lê chaves do Redis, agrega por hora e grava em gateway_metrics no Postgres. Retenção de 90 dias gerenciada pelo maintenance-cron.


5. Schema da tabela gateway_metrics

sql
CREATE TABLE gateway_metrics (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  hora_inicio     TIMESTAMPTZ NOT NULL,        -- truncada à hora
  endpoint        TEXT NOT NULL,
  escopo          TEXT NOT NULL,                -- 'endpoint' | 'escola' | 'usuario' | 'status' | 'blocked'
  identificador   TEXT,                         -- escola_id, usuario_id, status code; NULL quando escopo=endpoint
  contagem        BIGINT NOT NULL,
  ambiente        TEXT NOT NULL,                -- 'producao' | 'staging'
  criado_em       TIMESTAMPTZ NOT NULL DEFAULT now(),
  
  UNIQUE (hora_inicio, endpoint, escopo, identificador, ambiente)
);

CREATE INDEX idx_gateway_metrics_hora ON gateway_metrics (hora_inicio DESC);
CREATE INDEX idx_gateway_metrics_endpoint ON gateway_metrics (endpoint, hora_inicio DESC);
CREATE INDEX idx_gateway_metrics_escola ON gateway_metrics (identificador, hora_inicio DESC) 
  WHERE escopo = 'escola';

RLS: ativo sem policies — leitura apenas via service_role (consultas administrativas).

Cleanup: maintenance-cron remove registros com hora_inicio < now() - interval '90 days'.


6. Como ler dados durante a Fase 1

Sem dashboard. Três ferramentas cobrem 100% das perguntas:

Console Upstash (últimas 2h)

Acessar console.upstash.com → CLI tab. Exemplos:

text
# Top endpoints na última hora
SCAN 0 MATCH olp:m:* COUNT 100

# Bloqueios em /me no minuto X
GET olp:m:blocked:/me:{epoch_minuto}

# Volume da escola Y na hora atual
GET olp:m:escola:{escola_id}:{epoch_minuto}

wrangler tail (tempo real)

bash
wrangler tail olp-gateway --format=pretty

Mostra requests ao vivo. Útil para investigar comportamento momentâneo.

Postgres gateway_metrics (histórico)

sql
-- Top 10 endpoints da última hora
SELECT endpoint, sum(contagem) AS total
FROM gateway_metrics
WHERE escopo = 'endpoint' AND hora_inicio >= now() - interval '1 hour'
  AND ambiente = 'producao'
GROUP BY endpoint ORDER BY total DESC LIMIT 10;

-- Pico horário de uma escola nas últimas 4 semanas
SELECT date_trunc('hour', hora_inicio) AS hora, sum(contagem) AS total
FROM gateway_metrics
WHERE escopo = 'escola' AND identificador = '{escola_id}'
  AND hora_inicio >= now() - interval '28 days'
GROUP BY hora ORDER BY total DESC LIMIT 20;

-- Taxa de 429 por endpoint
SELECT endpoint,
       sum(CASE WHEN escopo = 'blocked' THEN contagem ELSE 0 END)::float
       / NULLIF(sum(CASE WHEN escopo = 'endpoint' THEN contagem ELSE 0 END), 0)
       AS taxa_bloqueio
FROM gateway_metrics
WHERE hora_inicio >= now() - interval '7 days'
GROUP BY endpoint;

7. Quando construir interface de observabilidade

Interface dedicada (componente React + página admin) só é construída quando pelo menos uma das três condições for verdadeira:

#CondiçãoPor quê
1Pergunta operacional recorrente sem resposta via console Upstash ou query SQLSinal claro de que CLI/SQL não cobre o caso
2Anomalia recorrente (≥3 ocorrências em 14 dias) que exige análise visualPadrão temporal precisa de gráfico
3≥15 escolas ativas no sistemaVolume justifica painel agregador

Antes disso, interface = bikeshedding. O console do Upstash é gratuito, rápido e suficiente para a escala atual.


8. Política especial do endpoint /me

/me é chamado pelo auth-context.tsx em todo carregamento de página do sistema autenticado. Bloqueá-lo quebra a sessão inteira do usuário, derrubando navegação, atualizações de cache e a próxima chamada de /me necessária para reauth.

Por isso, /me segue regra própria:

AçãoComportamento
Telemetria normalSim, sempre coletada (olp:m:/me:{minuto})
Rate limit por bloqueioNunca — em nenhuma fase, em nenhum ambiente
Alerta por threshold anômaloSim — quando usuario_id ultrapassa N requests/min (N a calibrar com baseline), alerta ntfy é disparado sem bloquear o request
Investigação de abusoManual, via query em gateway_metrics filtrada por escopo = 'usuario' e endpoint = '/me'

A racionalização: alerta sem bloqueio dá visibilidade ao problema (loop infinito no frontend, polling acidental, bug em hook) sem causar regressão grave para o usuário final. O custo de um falso positivo bloqueado em /me é muito maior que o custo de detectar o abuso 5 minutos depois e mitigar manualmente.