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-croninvocado pelo pg_cron (isolate A)send-otpchamado por usuário no mesmo segundo (isolate B, comskipDelay:truepara "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 fieldswasender:ok|rate_limited|failure. - Auth: header
X-MsgQueue-Secretconstant-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,
/acquireretorna{acquired:true, degraded:true}e Edge Function decide usar fallback in-process. - Tests: Vitest + miniflare (
auth,acquire,record,health, fallback Redis, prefix). Bundled emworkers/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). recordSendfire-and-forget viaEdgeRuntime.waitUntilquando disponível.
- Timeout
- Factory:
getMessagingQueue()lêMSG_QUEUE_BACKEND; suportainprocess(default) eworker. 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_lockno 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=trueno login:send-otpusaskipDelay:truee ignora resultado deacquireSlot, então não há regressão. Worker mandadeferred:true, Edge Function ignora (mesmo comportamento que InProcessQueue retornavaacquired:trueapós esperar 0ms). Documentado. - Worker v3 (telemetria global, rate-limit autenticado, blocklist): plano próprio (
WORKER_V3_REDIS_PLAN.md).
3. Eixos de auditoria
| Eixo | Status | Notas |
|---|---|---|
| Backend | OK | Validação manual (sem Zod, payloads pequenos e tipados estritamente). CORS preflight padrão. try/catch global no Worker. EdgeRuntime.waitUntil em recordSend. |
| Segurança | OK | Constant-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. |
| Performance | OK | Worker→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 safety | OK | MSG_QUEUE_BACKEND ausente = comportamento atual (InProcessQueue). Reversível por toggle. Fail-open em todas as falhas exceto auth. |
| Frontend | N/A | Sem mudança — send-otp mantém contrato. |
| Docs | OK | UPSTASH_REDIS.md (novo, SSOT), MESSAGING_RATE_LIMITS.md (atualizado), workers/olp-msgqueue/README.md (novo), este audit, sidebar VitePress, mem://. |
| Testes | OK | Worker: 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):
- F-1 (info): Worker não tem cobertura E2E real contra Upstash sandbox (apenas mocks). Recomendado smoke test pós-deploy via curl autenticado.
- 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). - F-3 (info):
recordSendcai em fallback silencioso se Worker estiver fora — perda de telemetria, mas não de envio. Aceitável. - 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_LIMITEDemsms_logpor 7 dias apósMSG_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-msgqueueem 30 dias.
7. Referências
workers/olp-msgqueue/— código + README + testssupabase/functions/_shared/messaging-queue-worker.tssupabase/functions/_shared/messaging-queue.tssupabase/functions/_shared/messaging-rate-limits.tsdocs/architecture/UPSTASH_REDIS.mddocs/architecture/MESSAGING_RATE_LIMITS.mddocs/architecture/WORKER_V3_REDIS_PLAN.mdmem://infrastructure/upstash-redis-standardmem://infrastructure/wasender-rate-limiting-standard