Skip to content

AUDIT — Wasender Rate Limit / Redis Queue (Etapa 2)

Data: 2026-05-13 Escopo: Worker olp-msgqueue (Cloudflare + Upstash Redis) + backend worker na MessagingQueue das Edge Functions + bump delayEntreEnviosMs 6000→7000. Tipo: @audit completo pós-implementação.

1. Diagnóstico (causa raiz)

Os bans esporádicos do Wasender ("rate_limited" no sms_log) não vinham de paralelização intencional. Vinham de isolates Deno distintos rodando concorrentemente:

  • faturamento-cron invocado pelo pg_cron (isolate A)
  • send-otp chamado por usuário no mesmo segundo (isolate B, com skipDelay:true para "furar a fila" da UI)

Cada isolate tinha seu próprio Map<sessionKey, lastSendAt> em memória. A janela de 5s do Account Protection Mode era violada sem que nenhum dos dois isolates "soubesse" do outro.

InProcessQueue (Etapa 1) era correto dentro de uma execução, incorreto entre execuções concorrentes — limitação documentada e aceita à época.

2. Solução implementada

Worker olp-msgqueue (novo)

  • Localização: workers/olp-msgqueue/ (JS puro, ESM, @upstash/redis/cloudflare).
  • Endpoints: POST /acquire, POST /record, GET /health.
  • Token bucket: Lua atômico em Upstash (olp:wa:bucket:{session_key}, TTL 60s).
  • Telemetria: HASH olp:wa:metrics:{session_key}:{YYYYMMDDHHmm} (TTL 300s) com fields wasender:ok|rate_limited|failure.
  • Auth: header X-MsgQueue-Secret constant-time. 503 fail-close se secret não configurado.
  • CORS: allowlist explícito (olp.digital, staging.olp.digital) — defesa em profundidade; uso real é server-to-server.
  • Prefixo por env: olp: (prod) / olp-stg: (staging) / olp-dev: (local).
  • Hard cap: min_interval_ms ≥ 500ms, max_wait_ms ≤ 30s (cliente não pode pedir 0 nem travar Worker).
  • Fail-open Redis: se Upstash cair, /acquire retorna {acquired:true, degraded:true} e Edge Function decide usar fallback in-process.
  • Tests: Vitest + miniflare (auth, acquire, record, health, fallback Redis, prefix). Bundled em workers/olp-msgqueue/test/.

Edge Function — backend HTTP

  • Novo: _shared/messaging-queue-worker.ts (WorkerMessagingQueue implements MessagingQueue).
    • Timeout acquire = maxWaitMs + 2s (cap 30s); record = 800ms.
    • 401/403 do Worker → NÃO cai em fallback (config errada visível, não silenciosa).
    • 5xx/timeout/network/JSON inválido → cai em InProcessQueue (fail-open).
    • recordSend fire-and-forget via EdgeRuntime.waitUntil quando disponível.
  • Factory: getMessagingQueue()MSG_QUEUE_BACKEND; suporta inprocess (default) e worker. Worker sem envs → cai pra inprocess com warn.
  • Bump: delayEntreEnviosMs: 6000 → 7000 (margem 40% sobre janela de 5s).
  • Tests: 13 casos verdes (8 backend Worker + 5 factory).

Não implementado (out of scope justificado)

  • pg_try_advisory_lock no cron: requer função RPC wrapper (SECURITY DEFINER) e migração; bucket Redis já elimina concorrência cross-isolate. Pode entrar como Etapa 3 se monitoramento mostrar necessidade.
  • UX deferred=true no login: send-otp usa skipDelay:true e ignora resultado de acquireSlot, então não há regressão. Worker manda deferred:true, Edge Function ignora (mesmo comportamento que InProcessQueue retornava acquired:true após esperar 0ms). Documentado.
  • Worker v3 (telemetria global, rate-limit autenticado, blocklist): plano próprio (WORKER_V3_REDIS_PLAN.md).

3. Eixos de auditoria

EixoStatusNotas
BackendOKValidação manual (sem Zod, payloads pequenos e tipados estritamente). CORS preflight padrão. try/catch global no Worker. EdgeRuntime.waitUntil em recordSend.
SegurançaOKConstant-time auth. Sem PII no Worker (session_key opaco). Hard cap impede DoS. Fail-close se secret ausente. CORS allowlist. Fallback NÃO mascara 401.
PerformanceOKWorker→Upstash gru1 ~5–15ms. Edge→Worker ~10ms. Total ~25ms p/ acquire (ou +waitMs server-side se houver fila). record é fire-and-forget — zero impacto na resposta.
Rollout safetyOKMSG_QUEUE_BACKEND ausente = comportamento atual (InProcessQueue). Reversível por toggle. Fail-open em todas as falhas exceto auth.
FrontendN/ASem mudança — send-otp mantém contrato.
DocsOKUPSTASH_REDIS.md (novo, SSOT), MESSAGING_RATE_LIMITS.md (atualizado), workers/olp-msgqueue/README.md (novo), este audit, sidebar VitePress, mem://.
TestesOKWorker: cobertura crítica (auth, acquire single/concorrente/deferred/degraded, record, health, prefix). Edge: 13 verdes.

4. Findings

Nenhum crítico. Follow-ups (não bloqueantes):

  1. F-1 (info): Worker não tem cobertura E2E real contra Upstash sandbox (apenas mocks). Recomendado smoke test pós-deploy via curl autenticado.
  2. F-2 (info): Telemetria do Worker (olp:wa:metrics:*) não tem dashboard ainda — leitura manual via Upstash CLI ou consulta direta. Endereçar com Worker v3 (m:* namespace).
  3. F-3 (info): recordSend cai em fallback silencioso se Worker estiver fora — perda de telemetria, mas não de envio. Aceitável.
  4. F-4 (info): Cron sem advisory lock — defesa em profundidade futura. Risco baixo agora (bucket Redis cobre).

5. Rollout recomendado

text
1. Merge em produção                             ← MSG_QUEUE_BACKEND ausente, zero impacto
2. Deploy olp-msgqueue (staging)                 ← npm run deploy:staging
3. Smoke staging: curl /health, /acquire         ← validar 200/auth
4. Setar MSG_QUEUE_BACKEND=worker em staging     ← supabase secrets
5. Validar 48h: sms_log sem RATE_LIMITED         ← supabase analytics
6. Deploy olp-msgqueue (production) + secrets    ← janela controlada
7. Setar MSG_QUEUE_BACKEND=worker em prod        ← monitorar 7d
8. Reduzir delayEntreEnviosMs (7000 → 3000 → 1000 → 500)
9. Avaliar desligar Account Protection Mode no Wasender (apenas se 8 estável)

6. Critério de sucesso

  • 0 RATE_LIMITED em sms_log por 7 dias após MSG_QUEUE_BACKEND=worker.
  • Latência adicional média ≤30ms em send-otp (telemetria Supabase Edge).
  • 100% testes verdes em CI.
  • Nenhum incidente atribuível a olp-msgqueue em 30 dias.

7. Referências

  • workers/olp-msgqueue/ — código + README + tests
  • supabase/functions/_shared/messaging-queue-worker.ts
  • supabase/functions/_shared/messaging-queue.ts
  • supabase/functions/_shared/messaging-rate-limits.ts
  • docs/architecture/UPSTASH_REDIS.md
  • docs/architecture/MESSAGING_RATE_LIMITS.md
  • docs/architecture/WORKER_V3_REDIS_PLAN.md
  • mem://infrastructure/upstash-redis-standard
  • mem://infrastructure/wasender-rate-limiting-standard