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:
- Dashboards desenhados sobre intuição mostram métricas que ninguém consulta.
- Limites calibrados sem baseline geram falsos positivos que destroem confiança no sistema.
- 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étrica | Chave Redis | TTL | Granularidade |
|---|---|---|---|
| Requisições por endpoint | olp:m:{endpoint}:{minuto} | 2h | minuto |
| Requisições por escola | olp:m:escola:{escola_id}:{minuto} | 2h | minuto |
| Requisições por usuário (autenticadas) | olp:m:user:{usuario_id}:{minuto} | 2h | minuto |
| Bloqueios (429) por endpoint | olp:m:blocked:{endpoint}:{minuto} | 2h | minuto |
| Distribuição de status HTTP | olp:m:status:{code}:{minuto} | 2h | minuto |
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.
| Categoria | Exemplo | Motivo |
|---|---|---|
| Conteúdo de mensagens | Texto de broadcast, OTP, mensagem WhatsApp | LGPD + irrelevante para rate limit |
| Dados pessoais | Nome, CPF, email, telefone | LGPD |
| Body de request | JSON enviado pelo cliente | Volume + privacidade |
| Body de response | JSON retornado pelo backend | Volume + privacidade |
| Headers além de UA truncado | Authorization, cookies | Segurança |
| IP completo | — | Apenas 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
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:
# 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)
wrangler tail olp-gateway --format=prettyMostra requests ao vivo. Útil para investigar comportamento momentâneo.
Postgres gateway_metrics (histórico)
-- 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ção | Por quê |
|---|---|---|
| 1 | Pergunta operacional recorrente sem resposta via console Upstash ou query SQL | Sinal claro de que CLI/SQL não cobre o caso |
| 2 | Anomalia recorrente (≥3 ocorrências em 14 dias) que exige análise visual | Padrão temporal precisa de gráfico |
| 3 | ≥15 escolas ativas no sistema | Volume 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ção | Comportamento |
|---|---|
| Telemetria normal | Sim, sempre coletada (olp:m:/me:{minuto}) |
| Rate limit por bloqueio | Nunca — em nenhuma fase, em nenhum ambiente |
| Alerta por threshold anômalo | Sim — quando usuario_id ultrapassa N requests/min (N a calibrar com baseline), alerta ntfy é disparado sem bloquear o request |
| Investigação de abuso | Manual, 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.