Worker v3 + Upstash Redis — Plano de Implementação
Status: Em implementação — Fase 0 pendente (provisionamento Upstash) Última revisão: 2026-04-19 Documentos relacionados: WORKER_TELEMETRY · CLOUDFLARE_WORKER_GATEWAY · UPSTASH_REDIS_MIGRATION · RATE_LIMITS
1. Contexto e limitações do Worker v2
O Cloudflare Worker v2 atual (ver CLOUDFLARE_WORKER_GATEWAY.md) cumpre três funções: roteamento de domínio, encaminhamento de cookies e burst rate limit em /portal-escola. Está em produção desde fevereiro de 2026 e atende ao tráfego atual com folga, mas acumula limitações estruturais que bloqueiam a próxima fase de proteção:
| Limitação v2 | Impacto |
|---|---|
Burst rate limit via Map in-memory | Contador é fragmentado por PoP da Cloudflare. Um atacante distribuído contorna o limite trivialmente. |
Burst só em /portal-escola | Demais endpoints (auth, dashboards, CRUD, broadcast) não têm camada anti-burst no Worker. |
| Sem telemetria estruturada | Não temos baseline de tráfego por endpoint, escola, IP ou usuário. Impossível calibrar limites com dados reais. |
| Sem headers RFC 6585 | Respostas 429 não informam X-RateLimit-Limit, Retry-After, Reset. Frontend não sabe quando tentar de novo. |
| Sem rate limit por usuário autenticado | Tudo que passa do /me é proxy puro. Operações autenticadas (importação, broadcast) não têm proteção no edge. |
Arquivo único index.js ~600 linhas | Mistura roteamento, cookies, CORS, burst, OAuth — bloqueia evolução incremental. |
A migração para v3 ataca todas essas limitações simultaneamente, com Upstash Redis como backing store global e telemetria fire-and-forget como pré-requisito de qualquer bloqueio novo.
2. Estrutura de arquivos do Worker v3
O Worker v3 adota arquitetura multi-módulo. Cada arquivo tem uma responsabilidade única e é importado pelo orquestrador:
cloudflare-worker/
├── src/
│ ├── index.js # Orquestrador: routing, fluxo principal, fail-open
│ ├── redis.js # Cliente Upstash REST + helpers (INCR, EXPIRE, SCAN)
│ ├── telemetry.js # Coleta fire-and-forget (ctx.waitUntil)
│ ├── ratelimit.js # Lógica de bloqueio (burst Redis + janelas)
│ ├── cookies.js # Parsing/forwarding de olp_auth e olp_mural
│ └── cors.js # Headers CORS unificados
└── wrangler.tomlCritério de aceite da estrutura: nenhum arquivo passa de 250 linhas; cada módulo é testável isoladamente.
3. Key schema Redis completo
Toda chave usa prefixo de ambiente: olp: em produção, olp-stg: em staging. Mesmo database Upstash, isolamento por prefixo.
| Chave | TTL | Propósito | Fase |
|---|---|---|---|
olp:rl:burst:{ip} | 5s | Burst global por IP, substitui Map in-memory | 2 |
olp:m:{endpoint}:{minuto} | 2h | Contador de requisições por endpoint/minuto | 1 |
olp:m:escola:{escola_id}:{minuto} | 2h | Contador por escola/minuto | 1 |
olp:m:blocked:{endpoint}:{minuto} | 2h | Contador de 429 por endpoint/minuto | 1 |
olp:m:status:{code}:{minuto} | 2h | Distribuição de status HTTP por minuto | 1 |
olp:rl:api:{usuario_id}:{minuto} | 2min | Rate limit autenticado por usuário/minuto | 3 |
olp:bl:{jti} | até expiração do token | Token blacklist (revogação) | futura |
Regras absolutas: toda chave DEVE ter TTL. Nenhuma exceção. Chave sem TTL = memory leak no Redis.
4. Fases de implementação
Fase 0 — Setup Upstash + wrangler local
Pré-requisito manual, sem código.
- Provisionar database Upstash (região gru1).
- Adicionar secrets
UPSTASH_REDIS_REST_URLeUPSTASH_REDIS_REST_TOKENno Cloudflare (produção e staging). - Validar
wrangler devcom Redis remoto.
Critério de saída: wrangler dev consegue executar INCR olp-stg:test contra Upstash.
Fase 1 — Telemetria fire-and-forget
Worker registra contadores em Redis via ctx.waitUntil() para todo request, em produção e staging. Zero bloqueio novo. O Worker v2 continua aplicando burst em /portal-escola exatamente como hoje.
Critério de entrada: Fase 0 concluída. Critério de saída: 7 dias contínuos com contadores olp:m:* populando consistentemente; cron horário de agregação para gateway_metrics operando (ver WORKER_TELEMETRY.md).
Fase 2 — Burst Redis global
Substitui o Map in-memory por INCR olp:rl:burst:{ip} EX 5. Aplicado em staging e produção simultaneamente — burst de 5 segundos é o limite menos arriscado de mover, não afeta usuários legítimos.
Critério de entrada: Fase 1 estável por ≥7 dias. Critério de saída: zero regressão em /portal-escola (taxa de 429 igual ou menor que v2) por ≥3 dias.
Fase 3a — Rate limit autenticado em modo observação (produção)
Worker calcula o limite por usuário/escola e adiciona o header X-RateLimit-Would-Block: true em respostas que seriam bloqueadas, mas deixa a requisição passar normalmente. Telemetria registra os "would-block" em olp:m:blocked:* para análise.
Critério de entrada: Fase 2 estável. Critério de saída: 4 semanas contínuas de coleta com baseline de tráfego real por usuário/escola/endpoint.
Fase 3b — Bloqueio real (staging primeiro, produção depois)
Em staging, bloqueio real ativo desde a Fase 1 com os limites propostos (entram como teste de funcionamento, não como guarda-rail real porque staging não tem usuários). Scripts de stress test validam que o código bloqueia, retorna headers RFC 6585 corretos e mensagens contextuais PT-BR.
Em produção, bloqueio real só ativa após Fase 3a completa (4 semanas) e calibração dos limites com dados reais. Os limites finais podem ser diferentes dos propostos.
Critério de entrada (produção): Fase 3a completa + calibração aprovada. Critério de saída: zero ticket de "fui bloqueado indevidamente" por 14 dias.
5. Regras obrigatórias de código
| # | Regra | Motivo |
|---|---|---|
| 1 | Toda chave Redis DEVE ter TTL | Sem TTL = memory leak |
| 2 | redis.keys() PROIBIDO — usar SCAN com cursor | keys bloqueia o Redis em produção |
| 3 | Telemetria sempre via ctx.waitUntil() — nunca no caminho crítico | Latência do gateway não pode depender de Redis |
| 4 | Fallback obrigatório: se Redis falhar, Worker continua sem rate limit (fail-open) | Backend tem RLS e lockout próprios; nunca derrubar o site por falha de Redis |
| 5 | Prefixo de ambiente obrigatório: olp: produção, olp-stg: staging | Isolamento de dados sem segundo database |
| 6 | Respostas 429 incluem headers RFC 6585 completos | Frontend precisa saber quando retentar |
| 7 | Mensagens 429 sempre em PT-BR e contextuais ao escopo | UX consistente com o resto do sistema |
Detalhes dos headers RFC 6585 e mensagens contextuais: ver RATE_LIMITS.md seção "Worker Gateway — Rate Limits (v3)".
6. Documentos a atualizar após cada fase
| Fase | Documento | Mudança esperada |
|---|---|---|
| 0 | CLOUDFLARE_WORKER_GATEWAY.md | Adicionar variáveis Upstash, nota de pré-requisito |
| 1 | WORKER_TELEMETRY.md | Confirmar schema gateway_metrics, exemplos reais de leitura |
| 1 | CRON_JOBS.md | Registrar cron horário de agregação Redis → Postgres |
| 2 | CLOUDFLARE_WORKER_CODE.md | Substituir trecho de burst Map pelo trecho Redis |
| 2 | RATE_LIMITS.md | Marcar burst como "global via Redis" em vez de "in-memory por PoP" |
| 3a | RATE_LIMITS.md | Tabela de baseline real (substitui valores propostos por medidos) |
| 3b | RATE_LIMITS.md | Marcar Grupo B como "bloqueio ativo" |
| 3b | INCIDENT_POLICY.md | Procedimento de relax temporário de limite via Cloudflare KV |
7. Riscos e mitigações
| Risco | Impacto | Mitigação |
|---|---|---|
| Upstash indisponível | Worker perde rate limit | Fail-open obrigatório (Regra 4); backend mantém RLS e lockout; alerta ntfy crítico |
| Custo Upstash escala inesperada | Billing surpresa | Alerta no Upstash dashboard > 500k cmd/dia; TTL natural limita acúmulo |
| Latência Upstash > 50ms | Degrada gateway | Telemetria sempre via waitUntil; rate limit autenticado tem timeout de 30ms com fail-open |
Vazamento de prefixo (olp: em staging ou vice-versa) | Contadores misturam ambientes | Helper redisKey() único, prefixo lido de env.ENVIRONMENT; lint check no CI |
| Falha no cron de agregação | Perda de série temporal pós-2h | Cron tem retry automático; Upstash mantém 2h de buffer; perda máxima = 1h |
| Bloqueio falso-positivo em produção | Usuário legítimo travado | Fase 3a (observação 4 semanas) antes de qualquer bloqueio real; mensagens 429 com Retry-After claro |
8. Fora de escopo desta migração
- Token blacklist via Redis (
olp:bl:{jti}) — entra em fase futura, depois que Worker v3 estiver estável. - Sessões ativas em Redis — depende de blacklist.
- Drop das tabelas
portal_rate_metricsetoken_blacklistno Postgres — só após Worker v3 absorver totalmente as funções equivalentes. - Interface de observabilidade dedicada — só construída quando uma das três condições objetivas for verdadeira (ver
WORKER_TELEMETRY.md§7).