Um encurtador de URL parece projeto de fim de semana: recebe uma URL longa, devolve um código curto e redireciona quando alguém clica. É por isso que ele aparece em tantas entrevistas de system design. A superfície pequena esconde decisões reais sobre geração de IDs, cache, semântica HTTP e analytics, e cada uma muda a arquitetura.
Este artigo projeta um de ponta a ponta. Começa pelos números, mostra como gerar códigos curtos sem colisões, resolve a dúvida entre 301 e 302 e termina com um diagrama de arquitetura que você pode copiar e adaptar.
Requisitos e números
Requisitos funcionais:
- Criar um link curto para uma URL longa, opcionalmente com um alias personalizado.
- Redirecionar um link curto para a URL longa.
- Contar cliques por link.
- Expirar links quando solicitado.
Requisitos não funcionais: os redirecionamentos precisam ser rápidos e altamente disponíveis, os códigos curtos não podem ser adivinhados em sequência se os links puderem ser privados, e o sistema não pode virar um redirecionador gratuito para páginas de phishing.
Uma conta de guardanapo deixa o formato claro:
| Premissa | Valor | Por segundo |
|---|---|---|
| Links novos por mês | 100 milhões | cerca de 40 gravações/s |
| Redirecionamentos por mês | 10 bilhões | cerca de 3.900 leituras/s |
| Proporção leitura/escrita | 100:1 | |
| Tamanho do registro | cerca de 500 bytes | |
| Armazenamento em 5 anos | 6 bilhões de links | cerca de 3 TB |
As leituras superam as escritas em duas ordens de grandeza, então o caminho de leitura fica com o cache, as réplicas e a atenção. O caminho de escrita é pequeno e pode ser simples.
A API
Dois endpoints sustentam o produto:
POST /links
Content-Type: application/json
{"url": "https://example.com/very/long/path", "alias": "launch", "expiresAt": "2027-01-01T00:00:00Z"}
201 Created
{"code": "launch", "shortUrl": "https://sho.rt/launch"}
GET /launch
302 Found
Location: https://example.com/very/long/path
Mantenha-os em serviços separados. O serviço de redirecionamento não tem estado e só lê, então você pode escalá-lo sozinho e fazer deploy raramente.
Gerando o código curto
O código curto é um número escrito em base62 (a–z, A–Z, 0–9). Sete caracteres dão 62^7, cerca de 3,5 trilhões de códigos; seis dão cerca de 56,8 bilhões. Com 100 milhões de links por mês, sete caracteres duram milhares de anos.
A pergunta real é de onde vem o número:
| Abordagem | Como funciona | Cuidado com |
|---|---|---|
| Contador + base62 | Uma sequência global, codificada | Um único ponto de contenção; códigos sequenciais e adivinháveis |
| Faixas de ID | Um alocador entrega a cada instância da API um bloco de IDs (por exemplo, 100.000) | Lacunas quando uma instância morre; ainda sequencial dentro do bloco |
| Hash + truncamento | Fazer hash da URL e manter os 7 primeiros caracteres base62 | Colisões; é preciso checar e tentar de novo |
| IDs no estilo Snowflake | Timestamp + ID da máquina + sequência em 64 bits | Códigos mais longos (11 caracteres em base62) |
O hash é tentador porque a mesma URL sempre recebe o mesmo código. O problema é o paradoxo do aniversário: com 3,5 trilhões de códigos possíveis, a chance de pelo menos uma colisão passa de 50% depois de só cerca de 2,2 milhões de links. A partir daí, cada inserção precisa de uma checagem de unicidade e de uma nova tentativa com salt, o que transforma uma escrita em uma leitura mais uma escrita.
Faixas de ID são o padrão pragmático. Cada instância da API reserva um bloco no alocador em uma transação pequena e depois distribui os códigos a partir da memória, sem coordenação nenhuma. Se você não quer códigos sequenciais, passe o ID por uma permutação reversível (uma cifra de bloco sobre o espaço de IDs, por exemplo) antes de codificar. O Snowflake, do Twitter, mostra o outro caminho: um timestamp de 41 bits, um ID de máquina de 10 bits e uma sequência de 12 bits geram IDs únicos de 64 bits sem alocador central, ao custo de códigos mais longos.
Aliases personalizados pulam o gerador. Guarde-os na mesma tabela e confie em uma restrição de unicidade na coluna do código para rejeitar duplicatas.
301 ou 302?
A RFC 9110 define os códigos de redirecionamento. A diferença que importa aqui é o cache: uma resposta 301 (Moved Permanently) é cacheável por heurística, então os navegadores podem guardá-la e pular o seu servidor no próximo clique. Um 302 (Found) é temporário e não vai para o cache, a menos que você diga.
- Use 302 quando precisar contar cada clique ou quiser mudar o destino depois.
- Use 301 quando o link nunca muda e você prefere menos requisições a um analytics completo.
307 e 308 são as versões de 302 e 301 que preservam o método. Para cliques comuns no navegador, que são requisições GET, a escolha entre eles raramente importa.
Armazenamento e cache
Cada redirecionamento é uma busca pontual pelo código, então qualquer armazenamento com buscas rápidas por chave serve: um key-value store como DynamoDB ou Cassandra, ou Postgres com o código como chave primária. Faça sharding pelo código quando um nó não bastar mais; um hash do código espalha a carga por igual porque, por design, nenhum código é mais quente que outro. Os links populares são a exceção, e o cache os absorve.
Coloque um cache Redis na frente do armazenamento com leitura cache-aside: procure o código no Redis, recorra ao banco no miss e depois preencha o cache com um TTL. A popularidade dos links segue uma cauda longa, então um cache com uma pequena fração dos links atende a maioria dos cliques. O artigo sobre estratégias de cache trata de TTLs, stampedes e invalidação em detalhe. Réplicas de leitura ajudam o caminho de fallback; o artigo sobre replicação de banco de dados explica o atraso com que você terá de conviver.
Um CDN ou cache de borda pode servir diretamente os redirecionamentos mais populares se você enviar um cabeçalho Cache-Control, mas aí a borda responde a cliques que seus servidores nunca veem. Ou registre na borda, ou deixe os redirecionamentos sem cache.
Analytics de cliques sem atrasar o redirecionamento
Nunca grave um contador de cliques de forma síncrona no caminho do redirecionamento. O serviço de redirecionamento publica um pequeno evento de clique (código, timestamp, referrer, país) em um log como o Kafka e responde na hora. Um consumidor agrega as contagens por link e por dia e as grava em um armazenamento analítico. Se o analytics ficar para trás, os redirecionamentos continuam funcionando; é o mesmo desacoplamento descrito no artigo sobre arquitetura orientada a eventos.
O diagrama de arquitetura
Conteúdo do diagrama
- User
- Edge / CDN
- Load balancer
- Redirect service
- Shorten API
- Redis: code to URL
- Links store
- ID allocator
- URL reputation API
- Click events
- Click aggregator
- Analytics store
- GET /{code}
- POST /links
- 1. lookup
- 2. on miss
- insert code and URL
- ID range
- check the target
- click event
- consume
- daily counts
Como ler:
- Caminho de leitura (em cima). Requisições
GET /{code}passam pela borda e pelo balanceador de carga até o serviço de redirecionamento, que consulta primeiro o Redis e só vai ao armazenamento de links no miss. - Caminho de escrita (embaixo).
POST /linksvai para a API de encurtamento. Ela checa o destino em um serviço de reputação de URLs, pega IDs de uma faixa reservada no alocador, codifica em base62 e grava o link. - Analytics (à direita). Os eventos de clique são tracejados porque são assíncronos. O agregador os transforma em contagens diárias.
- O bloco Markdown mantém os números de capacidade ao lado do design, para que quem revisar possa conferi-los.
Abuso, expiração e aliases personalizados
Um encurtador público é uma ferramenta de phishing, a menos que você impeça. Verifique novos destinos em um serviço de reputação como o Google Safe Browsing, limite a criação de links por conta e por IP e tenha um jeito de desativar um código rapidamente. Reverifique links antigos de tempos em tempos, porque um domínio limpo pode se tornar malicioso depois.
Para a expiração, guarde expiresAt com o link, verifique no redirecionamento e apague as linhas expiradas com um job em segundo plano. Retorne 404 ou 410 para códigos expirados em vez de redirecionar.
O que colocar no diagrama
- Separe os caminhos de leitura e escrita. Eles escalam de forma diferente, e o diagrama deve mostrar isso.
- Rotule o status do redirecionamento (301 ou 302) no serviço de redirecionamento. Ele decide se o analytics é completo.
- Mostre de onde vêm os IDs. “Gerar código” dentro de uma caixa esconde a decisão mais discutida do design.
- Desenhe fluxos assíncronos tracejados. Quem lê deve ver de relance que o analytics não pode atrasar um redirecionamento.
- Escreva os números. Um design sem escritas por segundo e tamanho de armazenamento não pode ser revisado.
O exemplo de diagrama de system design mostra as mesmas convenções em um sistema maior.
Monte no Archboard
- Entre com Google ou GitHub e crie um diagrama.
- Do pack Modelo C4, adicione uma Pessoa para o usuário e, do pack Nuvem, um CDN e um Balanceador de carga na nuvem.
- Adicione quatro nós Contêiner do pack Modelo C4: o serviço de redirecionamento, a API de encurtamento, o alocador de IDs e o agregador de cliques. Coloque em cada um a tecnologia e uma descrição de uma linha.
- Adicione Redis, Kafka e um Armazém de dados do pack Dados, um Banco de dados do pack Modelo C4 para o armazenamento de links e um Sistema externo para a API de reputação de URLs.
- Conecte o caminho de leitura em cima e o de escrita embaixo, desenhe os eventos de clique como arestas tracejadas e adicione um bloco Markdown do pack Básico com os números de capacidade.
O exemplo usa 13 nós de diagrama e fica dentro do plano Free.
Perguntas frequentes
Qual deve ser o tamanho de um código curto? Sete caracteres base62 dão cerca de 3,5 trilhões de códigos, o suficiente para décadas com 100 milhões de links por mês. Comece com sete e deixe os códigos crescerem para oito depois; nada no design depende de um tamanho fixo.
Devo fazer hash da URL longa para obter o código? Só se a deduplicação importar mais do que escritas simples. Hashes truncados colidem cedo (a partir de 2,2 milhões de links com 7 caracteres, é mais provável haver pelo menos uma colisão do que nenhuma), então cada inserção precisa de checagem e nova tentativa.
301 ou 302: qual é melhor para um encurtador de URL? Um 302 se você precisa contar cada clique ou pode mudar o destino. Um 301 deixa os navegadores guardarem o redirecionamento em cache, o que economiza requisições mas esconde cliques repetidos do seu analytics.
Qual banco de dados deve guardar os links? Qualquer armazenamento com buscas rápidas por chave primária. O Postgres basta por muito tempo nesses números; um key-value store fica atraente quando você precisa fazer sharding das escritas em muitos nós.
Como evito que as pessoas adivinhem os links? Não exponha IDs sequenciais. Permute o ID antes de codificar ou use códigos aleatórios com checagem de unicidade, e nunca trate um link curto como segredo para conteúdo privado.