Design clássico · Júnior
Encurtador de URL
Hash ou contador? Base62, colisões, cache de redirecionamento e 100:1 de leitura para escrita.
Resposta curta
Hash ou contador? Base62, colisões, cache de redirecionamento e 100:1 de leitura para escrita. Um encurtador de URL recebe um endereço longo e devolve um código curto. Quando alguém abre o link curto, o sistema descobre o endereço original e redireciona. É simples de explicar e por isso é um bom primeiro exercício de levantamento de requisitos.
01 · CONTEXTO E REQUISITOS
O que um encurtador precisa fazer
Um encurtador de URL recebe um endereço longo e devolve um código curto. Quando alguém abre o link curto, o sistema descobre o endereço original e redireciona. É simples de explicar e por isso é um bom primeiro exercício de levantamento de requisitos.
Os requisitos funcionais são só dois: criar o link curto e redirecionar a partir dele. Um terceiro, opcional, é permitir que a pessoa escolha o próprio alias. Entre os requisitos não funcionais, três mandam no desenho: cada código aponta para uma única URL longa, o redirecionamento é rápido e o serviço fica quase sempre no ar, com disponibilidade acima de consistência.
02 · ESTIMATIVA DE ESCALA
Contas de guardanapo
O livro da ByteByteGo parte de cem milhões de URLs criadas por dia, o que dá cerca de mil e cem escritas por segundo. Com uma razão de dez leituras para cada escrita, são mais de onze mil leituras por segundo. Em dez anos seriam 365 bilhões de registros e, com URLs de cerca de cem bytes, em torno de 36,5 TB.
A Hello Interview usa outras premissas: um bilhão de URLs e cem milhões de usuários ativos por dia, com uma proporção de mil cliques para cada link criado. As duas fontes discordam na razão entre leitura e escrita, e isso é esperado: estimativa é hipótese, não fato. O que importa é a conclusão comum de que a leitura domina muito a escrita.
03 · API E MODELO DE DADOS
Dois endpoints e uma tabela
A API tem uma rota para criar, por exemplo um POST em /urls, e outra para redirecionar, um GET que recebe o código curto e responde com um redirecionamento HTTP para a URL longa.
O modelo de dados é uma tabela que liga o código à URL longa. Na versão mais simples do livro ela tem três colunas: id, código curto e URL longa. A chave de busca é o código curto, porque é ele que chega em cada clique.
O navegador envia a URL longa para a API do encurtador.
A API decide um código curto e grava o par código e URL longa no banco.
Depois, quem abre o link curto faz um GET com o código; a API consulta o banco e devolve o redirecionamento.
Esta primeira versão funciona, mas deixa em aberto a pergunta central do caso: como a API decide o código curto?
04 · GERAR A CHAVE CURTA
Hash ou contador, e o papel da base62
Há duas famílias de solução. A primeira aplica uma função de hash, como CRC32, MD5 ou SHA-1, à URL longa e usa só os primeiros caracteres do resultado. A saída de um hash parece aleatória, e esse resultado costuma ser codificado em base62.
Base62 usa letras minúsculas, maiúsculas e dígitos. Com sete caracteres há cerca de 3,5 trilhões de combinações, mais do que as 365 bilhões de URLs da estimativa. Com oito caracteres são cerca de 218 trilhões.
O problema do hash é a colisão: duas URLs diferentes podem gerar o mesmo código, e com mais URLs guardadas a chance de duplicata cresce. Resolver consultando o banco a cada pedido é caro. Duas defesas são uma restrição UNIQUE na coluna do código e um filtro de Bloom, que responde rápido e usa pouca memória se um código já existe.
A segunda família usa um contador. Cada URL recebe o próximo número, convertido em base62. Como cada número é único, não há colisão. O custo é depender de um gerador de IDs únicos, e códigos sequenciais são previsíveis, o que permite enumerar links.
| Critério | Hash + base62 | Contador + base62 |
|---|---|---|
| Colisão | Possível e precisa ser resolvida | Não ocorre, pois cada ID é único |
| Dependência extra | Não precisa de gerador de IDs | Depende de um gerador de IDs únicos |
| Previsibilidade | Código sem relação com a ordem de criação | Códigos sequenciais podem ser enumerados |
Para esta versão de nível júnior, o contador é a escolha mais simples de explicar e evita o tratamento de colisões. Nesse desenho, o serviço de escrita pede ao contador, por exemplo um Redis, um número único e converte para base62. Como Redis é de thread única e tem operações atômicas, ele serve de contador; para não pedir um número a cada escrita, cada instância pode reservar um lote de valores.
O navegador envia a URL longa; o balanceador a encaminha a uma instância do serviço de escrita.
O serviço de escrita pede um número ao contador. Cada valor devolvido é único, por isso não há colisão.
O serviço converte o número para base62 e grava o par código e URL longa no banco.
Para não ir ao contador a cada escrita, a instância pode reservar um lote de valores e consumir localmente.
05 · CACHE E REDIRECIONAMENTO
Leitura rápida: cache e o código 301 ou 302
Como as leituras dominam, vale pôr um cache em memória, como Redis ou Memcached, na frente do banco. Boa prática: o tempo de vida (TTL) do cache deve ser igual ou menor que a expiração da URL, para o cache nunca servir um link já vencido. Separar o serviço de leitura do de escrita permite escalar cada lado de forma independente.
O navegador abre o link curto; o balanceador envia o clique a uma instância de leitura.
O serviço de leitura consulta o cache. Se o código está lá, responde de imediato sem tocar no banco.
Se o código não está no cache, o serviço busca no banco, guarda o resultado no cache e responde.
Falta escolher o código HTTP do redirecionamento. O 301 diz que a URL mudou para sempre: o navegador guarda a resposta e os cliques seguintes nem chegam ao encurtador. O 302 diz que a mudança é temporária: todo clique passa de novo pelo serviço.
| Critério | 301 (permanente) | 302 (temporário) |
|---|---|---|
| Navegador | Guarda a resposta em cache | Não guarda; volta ao encurtador a cada clique |
| Carga no servidor | Menor | Maior |
| Análise de cliques e controle do link | Perde parte dos cliques | Registra cliques e permite atualizar ou expirar o link |
Se o objetivo é reduzir carga, o 301 faz sentido; se importam análise de cliques e poder de atualizar ou expirar links, o 302 é a escolha melhor. A Hello Interview adota o 302 em seu desenho.
06 · EVOLUÇÃO E LIMITES
O que muda quando o sistema cresce
Com mais tráfego, o cache e o banco precisam dar conta de mais leituras. Com seis caracteres o espaço acaba em cerca de 56 bilhões de URLs, e então é preciso passar a sete. Em várias regiões, cada uma pode receber faixas separadas do contador, para não repetir números.
Perguntas frequentes
O que é Encurtador de URL?
Hash ou contador? Base62, colisões, cache de redirecionamento e 100:1 de leitura para escrita.
O que o design de Encurtador de URL cobre?
Ele está organizado nestas seções: O que um encurtador precisa fazer, Contas de guardanapo, Dois endpoints e uma tabela, Hash ou contador, e o papel da base62, Leitura rápida: cache e o código 301 ou 302 e O que muda quando o sistema cresce.
Quanto tempo leva Encurtador de URL e qual é o nível?
Leva cerca de 12 minutos para ler e tem nível Júnior.
Agora projete você mesmo
Rode este caso como uma entrevista na escola de System Design: você toma as decisões, o tutor as questiona e você vê a nota do seu desenho.