ADR-0010 Limitação de pedidos
Limitação de pedidos ao nível da app, padrão da empresa — rate-limiter-flexible no middleware global de pedidos do Start, contadores no Valkey.
- Decisão
- rate-limiter-flexible + ValkeyGuardar e proteger
- Estado
- Aceite
- Data
- Âmbito
- Predefinição da empresa para todas as novas apps
Ligações
Apoia-se em
Citado por
- 0004Camada de API
- 0009Autenticação
- 0012Obtenção de dados e estado
- 0013Formulários
- 0014Testes
- 0015Observabilidade
- 0017Reverse proxy e TLS
- 0018Tabelas
- 0023Debounce e throttle
- 0025SDK de IA
- 0026Estrutura do projeto
- 0027Princípios de engenharia
- 0028Jobs em segundo plano e agendamento
- 0035Runtime e toolchain
- 0036Camada de API
Contexto
Seção intitulada “Contexto”As apps precisam de proteção contra força bruta (início de sessão, reposição de palavra-passe), contra abuso de endpoints de escrita e contra clientes de API que excedem a sua quota. A identidade está definida — Better Auth (ADR-0009) — pelo que os limites podem usar como chave utilizadores e chaves de API, e não apenas IPs.
Forças em jogo:
- Pontos de entrada: SSR, server functions e as server routes em
/api/*(ADR-0036), tudo num único servidor TanStack Start (ADR-0002). O middleware global de pedidos do Start corre antes de todos - Topologia: uma só máquina por predefinição (ADR-0003), mas os deploys graduais executam brevemente dois processos, os reinícios apagam a memória do processo e um produto pode escalar horizontalmente
- O Better Auth traz o seu próprio limitador para
/api/auth/*, em memória por predefinição — um segundo limitador, com configuração e store próprias, se o mantivermos - O que um limitador na app não consegue fazer: travar uma inundação de pedidos. O servidor continua a aceitar todas as ligações e a executar middleware antes de as rejeitar. A proteção contra inundações pertence à frente da app — o reverse proxy (ADR-0017)
Opções consideradas
Seção intitulada “Opções consideradas”Onde corre
Seção intitulada “Onde corre”1. Middleware do Hono (hono-rate-limiter)
Seção intitulada “1. Middleware do Hono (hono-rate-limiter)”Prós: idiomático para a camada de API; pequeno. Contras: o Hono nunca vê server functions nem SSR — um formulário de início de sessão feito com server function ficaria desprotegido.
2. O limitador próprio de cada biblioteca (o do Better Auth + Hono + …)
Seção intitulada “2. O limitador próprio de cada biblioteca (o do Better Auth + Hono + …)”Prós: predefinições específicas por endpoint sem esforço. Contras: várias configurações, várias stores, vários formatos de 429; ninguém tem a visão de conjunto.
3. Um só limitador no middleware global de pedidos do Start (rate-limiter-flexible)
Seção intitulada “3. Um só limitador no middleware global de pedidos do Start (rate-limiter-flexible)”Prós
- Um único ponto de controlo para todos os pontos de entrada: SSR, server functions, server routes,
/api/* rate-limiter-flexible: agnóstico de framework, sem dependências, ISC, ativamente mantido; stores para memória, Postgres, Drizzle e Valkey- Fallback em memória quando a store está em baixo (
insuranceLimiter); as chaves que já excederam o limite são bloqueadas em memória sem tocar na store (inMemoryBlockOnConsumed)
Contras
- Passamos a manter as regras dos endpoints de autenticação que o Better Auth forneceria
Armazenamento de suporte
Seção intitulada “Armazenamento de suporte”A. Memória do processo
Seção intitulada “A. Memória do processo”Prós: zero infraestrutura, o mais rápido. Contras: por processo — os contadores são repostos a cada deploy e dividem-se assim que existe uma segunda réplica.
B. Postgres
Seção intitulada “B. Postgres”Prós: sem nova infraestrutura; partilhado e durável. Contras: uma escrita por pedido limitado no nosso sistema de registo; a contenção durante um ataque recai sobre a base de dados de que a app mais precisa.
C. Valkey
Seção intitulada “C. Valkey”Prós
- Feito para isto: contadores atómicos em memória com TTL, na escala dos microssegundos
- Mantém a carga de escrita do rate limiting fora do Postgres (ADR-0007)
- Fork do Redis com licença BSD, sob a Linux Foundation; fala o protocolo Redis, pelo que os clientes Redis funcionam
- Disponível para ADRs futuros (cache, filas, secondary storage do Better Auth) assim que existir
Contras
- Um novo serviço por ambiente para executar e monitorizar
Decisão
Seção intitulada “Decisão”rate-limiter-flexible no middleware global de pedidos do Start, com os contadores no Valkey, é a limitação de pedidos padrão da empresa para todas as novas apps.
Em concreto:
-
Um só limitador para tudo. O limitador integrado do Better Auth está desativado (
rateLimit: { enabled: false }); o nosso middleware cobre também/api/auth/* -
Âmbito: todos os pedidos que não sejam
GET/HEAD(escritas por server function, submissões de formulários), todos os pedidos a/api/*independentemente do método e todos os endpoints de autenticação. Os carregamentos de página e as server functionsGETnão são limitados na app — as inundações são trabalho do proxy -
Chaves: ID do utilizador quando existe sessão, chave de API para clientes de API, IP do cliente nos restantes casos. O IPv6 usa como chave o prefixo
/64. O IP do cliente vem apenas do único cabeçalho de confiança que o edge garante (CLIENT_IP_HEADER:X-Real-Ip, definido pelo Traefik a partir da ligação TCP — ADR-0017) — nunca de uma cadeiaX-Forwarded-Forfornecida pelo cliente — e a porta da app nunca é acessível a não ser através do edge -
Os endpoints de credenciais (início de sessão, criação de conta, reposição de palavra-passe, verificação 2FA, envio de magic links e de emails de verificação) têm limites estritos, com chave por IP e por email normalizado, para que o credential stuffing espalhado por muitos IPs continue a bater num muro. A lista de caminhos vive numa só configuração junto da configuração de autenticação e é revista em cada upgrade do Better Auth
-
Limites iniciais (ajustar por produto):
Âmbito Chave Limite Endpoints de credenciais IP 3 / 10 s Endpoints de credenciais email 10 / 15 min Escritas e /api/*IP 100 / 60 s Escritas e /api/*utilizador 300 / 60 s /api/*com chave de APIchave de API conforme o contrato do produto -
Valkey: imagem oficial
valkey/valkey, com major fixada (9.x), como contentor irmão, tal como o Postgres. Serve também de store de sessões do Better Auth (ADR-0009). Persistência AOF emaxmemory-policy noeviction, porque as filas de jobs também vivem aqui (ADR-0028); os contadores continuam descartáveis e as sessões mantêm a cópia durável no Postgres. (Alterado em 2026-11-12: este ADR correu primeiro o Valkey sem persistência.) Protegido por palavra-passe e acessível apenas na rede Docker interna — guarda tokens de sessão ativos -
Cliente: uma única ligação
ioredis5.x partilhada pelo limitador e pelo@better-auth/redis-storagedo Better Auth (que exigeioredis^5). JS puro. O cliente Redis integrado no Bun não é opção aqui: orate-limiter-flexiblee o@better-auth/redis-storageaceitam apenas os clientes que listam, e um só cliente para o Valkey vale mais do que dois (ADR-0035) -
Modo de falha: se o Valkey estiver inacessível, o
insuranceLimiterem processo assume — os limites mantêm-se ativos, por processo, até o Valkey voltar. As consultas de sessão recorrem ao Postgres -
Resposta:
429 Too Many Requestscom um cabeçalhoRetry-After(segundos), com a mesma forma em todos os pontos de entrada
Os fatores decisivos: um único ponto de controlo e uma só configuração para todos os pontos de entrada, contadores que sobrevivem a reinícios e se mantêm corretos entre réplicas, e a carga de escrita do rate limiting fora do Postgres. Aceitamos, em troca, operar o Valkey e ser donos das regras dos endpoints de autenticação.
Consequências
Seção intitulada “Consequências”Mais fácil:
- Um único sítio para ler, ajustar e testar todos os limites de uma app
- Correto entre deploys e réplicas desde o primeiro dia — escalar horizontalmente não exige retrabalho do limitador
- O Valkey já está a correr quando ADRs futuros (cache, filas) o quiserem; as sessões já o usam (ADR-0009)
Mais difícil:
- O Valkey é mais um contentor por ambiente: upgrades de imagem, health checks, dimensionamento de memória
- Somos donos das regras dos endpoints de credenciais; um upgrade do Better Auth que acrescente ou renomeie endpoints de autenticação tem de atualizar a lista, ou o novo endpoint cai no limite genérico de escritas
- Ler o corpo do pedido para usar o email como chave implica clonar o pedido nos endpoints de credenciais — limitar isto a esses caminhos
- Definir
src/start.tspara o limitador desativa o middleware CSRF automático do Start para server functions; registar aí tambémcreateCsrfMiddleware()(ADR-0013) - Os IPs de cliente corretos dependem do edge (ADR-0017): o Traefik tem de continuar a ser o primeiro salto, sem forwarders de confiança configurados, ou o cabeçalho do IP do cliente pode ser forjado
Do que abdicamos: das predefinições por endpoint que o Better Auth mantém e dos contadores em memória sem infraestrutura. Aceite — um limitador com estado partilhado vale mais do que dois parciais, e o Valkey justifica o seu contentor. A proteção contra inundações fica explicitamente fora do âmbito deste ADR; o reverse proxy trata do que uma só máquina consegue (ADR-0017).

