Design de sistema de um encurtador de URL: diagrama de arquitetura e trade-offs

Projete um encurtador de URL passo a passo: números de capacidade, geração de códigos curtos, 301 ou 302, cache, analytics de cliques e um diagrama de arquitetura.

· Fundador e desenvolvedor

Publicado em

Design de sistema de um encurtador de URL: diagrama de arquitetura e trade-offs

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

O usuário chega a um CDN e a um balanceador de carga. Os GETs vão ao serviço de redirecionamento: primeiro o Redis e, no miss, o armazenamento de links. Os POSTs vão à API de encurtamento: faixas de ID do alocador, checagem de reputação e gravação do link. Os cliques passam pelo Kafka.

13 componentes · 12 conexões

Abrir no Archboard
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
Um encurtador de URL com caminhos de leitura e escrita separados. Os redirecionamentos consultam primeiro o cache, links novos recebem IDs de faixas reservadas e os eventos de clique vão para o analytics sem atrasar o redirecionamento.

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 /links vai 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

  1. Entre com Google ou GitHub e crie um diagrama.
  2. Do pack Modelo C4, adicione uma Pessoa para o usuário e, do pack Nuvem, um CDN e um Balanceador de carga na nuvem.
  3. 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.
  4. 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.
  5. 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.

Comece grátis — um assento, sem cartão de crédito.

Artigos relacionados

← Voltar ao blog

Abra um canvas em um minuto

Entre com Google ou GitHub. Grátis para um assento. Faça upgrade quando a equipe entrar.