Encurtar uma URL parece simples: receber um endereço longo, gerar um código curto e redirecionar quem acessar esse código. Em pequena escala, uma aplicação e uma tabela resolvem o problema. Em grande escala, o desafio muda: as leituras superam muito as escritas, códigos não podem colidir, links populares criam picos e o redirecionamento precisa continuar rápido mesmo durante falhas.

Este estudo de caso mostra como conduzir essa discussão em uma entrevista de System Design. Os números são hipóteses para orientar decisões, não uma receita universal.

O que é um URL Shortener?

Um URL Shortener, ou encurtador de URLs, transforma uma URL longa em um endereço curto e fácil de compartilhar.

URL longa:  https://exemplo.com/produtos?categoria=livros&campanha=primavera
URL curta:  https://dvp.to/aZ3k9B

Quando alguém acessa dvp.to/aZ3k9B, o serviço procura o destino associado ao código aZ3k9B e responde com um redirecionamento HTTP.

Há dois fluxos principais:

criação:        URL longa → validação → código curto → armazenamento
redirecionamento: código curto → busca do destino → resposta HTTP 3xx

O segundo fluxo costuma dominar o volume. Essa assimetria favorece uma arquitetura otimizada para leitura.

Defina os requisitos antes da arquitetura

Uma boa solução começa alinhando o escopo. Sem isso, é fácil adicionar componentes sofisticados para problemas que não existem.

Requisitos funcionais

  • Criar uma URL curta a partir de uma URL longa;
  • Redirecionar o código curto para o destino correto;
  • Permitir um alias personalizado, se estiver disponível;
  • Aceitar uma data de expiração opcional;
  • Desabilitar links maliciosos ou removidos;
  • Registrar cliques para estatísticas sem atrasar o redirecionamento.

Requisitos não funcionais

  • Baixa latência no redirecionamento;
  • Alta disponibilidade para leitura;
  • Códigos únicos e difíceis de adivinhar em sequência;
  • Durabilidade dos links enquanto estiverem válidos;
  • Escala para um volume muito maior de leituras que de escritas;
  • Proteção contra abuso, phishing e criação automatizada em massa.

Também é importante esclarecer o que fica fora do escopo inicial. Painel de campanhas, QR Codes, domínio personalizado e relatórios em tempo real podem ser evoluções, mas não são necessários para provar o desenho principal.

Faça estimativas de ordem de grandeza

Suponha os seguintes requisitos para exercitar a capacidade:

novas URLs por mês:                 100 milhões
proporção leitura : escrita:        100 : 1
retenção média:                     5 anos
tamanho médio de um registro:       1 KB

Isso produz aproximadamente:

escritas médias:
100.000.000 / 30 dias / 86.400 s ≈ 39 escritas/s

leituras médias:
39 × 100 ≈ 3.900 leituras/s

links em 5 anos:
100.000.000 × 12 × 5 = 6 bilhões

armazenamento bruto:
6 bilhões × 1 KB ≈ 6 TB

Picos podem ser várias vezes maiores que a média, especialmente quando um link viraliza. Réplicas, índices e metadados também aumentam o armazenamento real. Ainda assim, a estimativa já revela as decisões importantes: o volume de dados é administrável, o caminho de leitura merece cache e a distribuição de popularidade será desigual.

Modele as APIs

Uma API mínima de criação pode ser:

POST /v1/links
Content-Type: application/json
Idempotency-Key: 1f3b...

{
  "url": "https://exemplo.com/artigos/system-design",
  "customAlias": "system-design",
  "expiresAt": "2027-09-28T00:00:00Z"
}
{
  "code": "aZ3k9B",
  "shortUrl": "https://dvp.to/aZ3k9B",
  "expiresAt": "2027-09-28T00:00:00Z"
}

O redirecionamento pode usar uma rota pública simples:

GET /aZ3k9B

HTTP/1.1 302 Found
Location: https://exemplo.com/artigos/system-design

Uma chave de idempotência impede que o retry de uma requisição de criação produza vários links. O alias personalizado precisa de uma restrição de unicidade e deve rejeitar palavras reservadas como api, admin e login.

301 ou 302?

O 301 Moved Permanently permite cache agressivo por navegadores e intermediários, reduzindo carga. Porém, dificulta trocar o destino, revogar um link rapidamente e registrar cada clique no serviço.

O 302 Found mantém mais controle no servidor e é uma escolha inicial flexível. O custo é receber mais tráfego de redirecionamento. Se os links forem imutáveis e a medição puder ser aproximada, respostas permanentes podem ser habilitadas por política.

Modele os dados pelo padrão de acesso

O acesso principal é uma busca exata pelo código curto. Um modelo lógico suficiente seria:

CampoFinalidade
codeChave única usada no redirecionamento
destinationURL completa de destino
created_atAuditoria e políticas de retenção
expires_atExpiração opcional
statusAtivo, desabilitado, expirado ou removido
owner_idProprietário, quando houver conta
redirect_type

Política de resposta, como 302 ou 301

Um banco relacional funciona bem no início: oferece índice único, transações e operação conhecida. Um banco chave-valor distribuído se torna atraente quando o volume e a distribuição geográfica exigem particionamento horizontal simples.

Não é preciso decidir apenas pela categoria “SQL ou NoSQL”. Pergunte:

  • O acesso é quase sempre pela chave?
  • Precisamos de consultas relacionais ou relatórios no banco principal?
  • Qual consistência é necessária ao criar, editar e revogar um link?
  • Como ocorrerão backup, restauração, replicação e expansão de capacidade?

Estatísticas de clique possuem outro padrão de acesso e outro volume. Elas não precisam compartilhar a mesma tabela nem a mesma tecnologia dos redirecionamentos.

Como gerar códigos curtos?

Um alfabeto Base62 usa letras minúsculas, maiúsculas e números:

0-9 + a-z + A-Z = 62 símbolos

Com 7 caracteres, o espaço teórico é:

62⁷ ≈ 3,5 trilhões de combinações

Isso é muito maior que os 6 bilhões de links da estimativa. Alguns códigos podem ser reservados, bloqueados ou nunca utilizados, portanto o espaço não deve ser explorado até perto do limite.

ID sequencial codificado em Base62

O sistema obtém um ID único e o converte para Base62.

ID 125 → código "21"

Vantagens: não há colisões, o código é compacto e a geração é barata.

Custos: um contador central pode limitar a escala; a sequência revela aproximadamente o volume do sistema; códigos vizinhos são enumeráveis. Para distribuir a geração, é possível reservar blocos de IDs por instância ou usar um serviço de IDs, mas isso aumenta a operação.

Código aleatório

O serviço gera uma sequência aleatória e tenta inseri-la sob uma restrição de unicidade. Em caso de colisão, gera outra.

Vantagens: reduz a previsibilidade e não depende de um contador global.

Custos: exige uma fonte aleatória adequada, índice único e retry. A chance de colisão cresce conforme o espaço é ocupado, efeito conhecido como paradoxo do aniversário.

Para um código público, não use um gerador previsível se a enumeração revelar destinos sensíveis. Mesmo um código aleatório não substitui autorização: quem possuir uma URL curta pública normalmente poderá acessá-la.

Hash da URL longa

Calcular um hash parece determinístico, mas introduz perguntas difíceis: quantos caracteres manter, como resolver colisões, como criar dois links distintos para a mesma URL e como incorporar expiração ou proprietário. O hash pode participar da solução, mas não elimina a necessidade de identidade e unicidade.

Desenhe o fluxo de escrita

cliente
  → API de criação
    → autenticação e rate limiting
      → validação e normalização da URL
        → geração do código
          → INSERT com restrição única
            → resposta com a URL curta

O serviço deve aceitar apenas protocolos permitidos, normalmente http e https, impor limites de tamanho e normalizar somente o que for seguro. Transformações agressivas podem mudar o significado da URL: parâmetros repetidos, maiúsculas no caminho e fragmentos nem sempre são equivalentes.

A escrita precisa ser confirmada pelo banco antes de responder. Se houver cache, ele pode ser preenchido depois do commit. Publicar primeiro no cache cria o risco de servir um link que não existe na fonte de verdade.

Aliases personalizados usam o mesmo caminho, mas o código é fornecido pelo cliente. A restrição única decide de forma atômica qual requisição venceu a disputa.

Desenhe o fluxo de redirecionamento

usuário
  → DNS e CDN ou edge
    → load balancer
      → serviço de redirecionamento
        → cache distribuído
          ├─ hit  → responde 302
          └─ miss → banco → preenche cache → responde 302

O cache segue o padrão cache-aside. O código curto é a chave; o destino e o estado formam o valor. Links populares permanecem em memória e evitam leituras repetidas no banco.

Também vale armazenar por pouco tempo resultados negativos, como códigos inexistentes ou expirados. Isso reduz a carga causada por scanners, erros de digitação e tentativas de enumeração. O TTL negativo deve ser curto para não esconder por muito tempo um link recém-criado.

Invalidação e consistência

Quando um link é alterado ou desabilitado:

1. atualize a fonte de verdade
2. invalide a entrada no cache
3. aceite uma pequena janela de inconsistência ou use uma política mais forte

Se a revogação for uma exigência de segurança, um TTL longo pode ser inaceitável. O sistema pode combinar TTL curto, invalidação por evento e uma lista de bloqueio de rápida propagação. A consistência necessária vem do requisito, não do componente escolhido.

Hot links

Um único código viral pode concentrar tráfego. Réplicas de leitura e particionamento do banco ajudam pouco se todas as requisições chegam à mesma chave. Cache distribuído, cache local curto e CDN absorvem melhor esse padrão.

Ao usar CDN, considere que o redirecionamento pode não chegar ao backend. Isso melhora latência e disponibilidade, mas muda a coleta de métricas e a velocidade de revogação. Logs da borda ou eventos agregados podem recuperar parte da visibilidade.

Separe analytics do caminho crítico

O usuário não deve esperar a gravação de uma estatística para ser redirecionado.

redirecionamento → resposta 302
        └───────→ evento de clique → fila ou stream → consumidores → analytics

O evento pode conter código, horário, região aproximada, referenciador e categoria do dispositivo, respeitando minimização de dados e privacidade. Consumidores agregam os eventos em janelas e atualizam um armazenamento analítico.

Esse fluxo normalmente aceita consistência eventual. Duplicatas podem ocorrer quando há retry, então os consumidores devem ser idempotentes ou as métricas devem tolerar uma pequena supercontagem documentada. “Cliques” também precisam de uma definição: requisições de bots, previews de redes sociais e retries não representam necessariamente pessoas.

Escale por etapas

Uma evolução razoável evita começar com a arquitetura final:

Etapa 1: aplicação e banco relacional

  • Uma aplicação sem estado;
  • Banco com índice único em code;
  • Rate limiting básico;
  • Backups e observabilidade.

Etapa 2: cache e réplicas

  • Cache-aside para redirecionamentos;
  • Réplicas de leitura, se o banco continuar recebendo carga relevante;
  • Métricas assíncronas;
  • Mais instâncias atrás de um load balancer.

Etapa 3: particionamento e distribuição geográfica

  • Particionar por hash do código para distribuir dados de forma uniforme;
  • Replicar dados para regiões próximas dos usuários;
  • Definir qual região aceita escritas e como novos links se propagam;
  • Automatizar rebalanceamento, backup e recuperação por partição.

Particionar por hash distribui bem códigos aleatórios, mas dificulta consultas por proprietário. Um índice secundário ou armazenamento separado pode atender o painel do usuário. Isso é uma consequência direta de modelar a fonte principal para o redirecionamento.

Disponibilidade e falhas

O desenho deve explicar o comportamento durante falhas, não apenas no cenário saudável.

FalhaResposta esperada
Cache indisponívelConsultar o banco com timeout e limitar concorrência
Banco primário indisponível

Pausar criações; manter leituras por cache e réplica quando seguro

Réplica atrasada

Um link novo pode retornar 404; usar leitura no primário após criar

Fila de analytics indisponívelRedirecionar e descartar ou armazenar eventos em buffer limitado
Região indisponível

Direcionar tráfego para outra região com dados suficientemente atuais

O redirecionamento e a criação podem ter objetivos de disponibilidade diferentes. Durante um incidente, faz sentido manter links existentes funcionando e rejeitar temporariamente novas criações.

Defina SLOs separados, como disponibilidade e percentis de latência para criação e redirecionamento. Monitore hit rate, latência do cache e banco, códigos não encontrados, colisões, atrasos de replicação, tamanho das filas e links bloqueados.

Segurança e prevenção de abuso

Um encurtador esconde o domínio de destino e pode ser usado para phishing, malware e spam. Segurança faz parte do desenho central.

  • Aplique rate limiting por conta, IP e outros sinais de risco;
  • Bloqueie protocolos perigosos e entradas malformadas;
  • Consulte listas de reputação e permita denúncia de links;
  • Faça varredura assíncrona e desabilite destinos abusivos;
  • Proteja endpoints administrativos com autorização e auditoria;
  • Evite buscar a URL fornecida a partir da rede interna sem controles contra SSRF;
  • Defina retenção, anonimização e acesso para dados de analytics;
  • Mostre uma página de aviso quando o risco não puder ser decidido com segurança.

Não siga redirecionamentos de uma URL desconhecida em um worker com acesso à rede interna. Se a inspeção for necessária, use isolamento, regras de saída e bloqueio de endereços privados e metadados de nuvem.

Vantagens da arquitetura proposta

  • Mantém o redirecionamento simples e otimizado para leitura;
  • Usa unicidade no armazenamento para resolver corridas de criação;
  • Adiciona cache onde a proporção leitura-escrita justifica;
  • Separa analytics do caminho de baixa latência;
  • Permite escalar aplicação, cache e dados de forma independente;
  • Evolui em etapas, de acordo com métricas reais.

Limitações e trade-offs

DecisãoBenefícioCusto ou risco
Código aleatório em Base62Menos previsível e geração distribuídaColisões exigem restrição única e retry

Redirecionamento 302

Permite edição, revogação e métricasMais requisições chegam ao serviço
Cache-asideReduz latência e carga no bancoInvalidação e dados temporariamente antigos
Analytics assíncronoNão atrasa o usuárioMétricas chegam depois e podem conter duplicatas
Particionamento por hashDistribui a carga por códigoConsultas por proprietário exigem outro índice
CDN para links popularesAbsorve picos perto do usuárioRevogação e contagem de cliques ficam mais difíceis

Erros comuns

Começar pelos componentes, não pelos requisitos

Adicionar filas, múltiplas regiões e vários bancos antes de estimar o tráfego produz um diagrama complexo sem justificativa. Comece com um fluxo funcional e evolua a partir dos gargalos.

Dizer apenas “vamos usar um hash”

Um algoritmo não responde como colisões serão detectadas, qual será o tamanho do código, se a saída é previsível nem como dois links para o mesmo destino serão tratados.

Ignorar hot keys

Distribuição uniforme dos códigos não implica distribuição uniforme dos acessos. Um link viral pode concentrar a maior parte do tráfego e precisa ser absorvido por camadas de cache.

Registrar analytics de forma síncrona

Uma escrita analítica no caminho do redirecionamento aumenta latência e cria mais uma dependência capaz de derrubar a função principal.

Tratar cache como fonte de verdade

Entradas podem expirar, ser removidas ou ficar antigas. O banco durável continua responsável pelo vínculo e por seu estado.

Esquecer abuso e revogação

Um sistema que cria links rapidamente, mas não consegue bloquear phishing com rapidez, está incompleto.

Perguntas de entrevista

  1. Como você estimaria tráfego, armazenamento e tamanho necessário do código?
  2. Quais são os trade-offs entre ID sequencial, código aleatório e hash da URL?
  3. Quando escolher 301 ou 302 para o redirecionamento?
  4. Como impedir que uma colisão associe o código ao destino errado?
  5. Como o sistema se comporta quando cache, banco ou fila estão indisponíveis?
  6. Como você trataria um único link recebendo milhões de acessos por minuto?
  7. Como editar ou revogar um link que está em caches e CDNs?
  8. Como contar cliques sem aumentar a latência do redirecionamento?
  9. Como particionar os dados e quais consultas ficam mais difíceis?
  10. Quais controles reduzem phishing, spam, enumeração e SSRF?

Próximos conteúdos

Continue por Redis, para aprofundar cache, TTL, eviction e hot keys, e por SQL ou NoSQL, para escolher o armazenamento a partir do padrão de acesso. Depois, estude idempotência para tornar a criação segura diante de retries e duplicatas.

Referências