Un acortador de URLs parece un proyecto de fin de semana: recibes una URL larga, devuelves un código corto y rediriges cuando alguien hace clic. Por eso aparece en tantas entrevistas de diseño de sistemas. Su superficie pequeña esconde decisiones reales sobre generación de ID, caché, semántica HTTP y analítica, y cada una cambia la arquitectura.
Este artículo diseña uno de principio a fin. Empieza por las cifras, explica cómo generar códigos cortos sin colisiones, resuelve la duda entre 301 y 302 y termina con un diagrama de arquitectura que puedes copiar y adaptar.
Requisitos y cifras
Requisitos funcionales:
- Crear un enlace corto para una URL larga, opcionalmente con un alias propio.
- Redirigir un enlace corto a su URL larga.
- Contar los clics por enlace.
- Hacer que los enlaces caduquen cuando se pida.
Requisitos no funcionales: las redirecciones deben ser rápidas y muy disponibles, los códigos cortos no deben poder adivinarse en secuencia si los enlaces pueden ser privados y el sistema no debe convertirse en un redirector gratuito para páginas de phishing.
Unas cuentas rápidas dejan clara la forma:
| Supuesto | Valor | Por segundo |
|---|---|---|
| Enlaces nuevos al mes | 100 millones | unas 40 escrituras/s |
| Redirecciones al mes | 10 000 millones | unas 3900 lecturas/s |
| Proporción de lecturas y escrituras | 100:1 | |
| Tamaño del registro | unos 500 bytes | |
| Almacenamiento a 5 años | 6000 millones de enlaces | unos 3 TB |
Las lecturas superan a las escrituras en dos órdenes de magnitud, así que la ruta de lectura se lleva la caché, las réplicas y la atención. La ruta de escritura es pequeña y puede ser simple.
La API
Dos endpoints sostienen el producto:
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
Mantenlos en servicios separados. El servicio de redirección no tiene estado y solo lee, así que puedes escalarlo por su cuenta y desplegarlo pocas veces.
Generar el código corto
El código corto es un número escrito en base62 (a–z, A–Z, 0–9). Siete caracteres dan 62^7, unos 3,5 billones de códigos; seis dan unos 56 800 millones. Con 100 millones de enlaces al mes, siete caracteres duran miles de años.
La pregunta real es de dónde sale el número:
| Enfoque | Cómo funciona | Cuidado con |
|---|---|---|
| Contador + base62 | Una secuencia global, codificada | Un único punto de contención; códigos secuenciales y adivinables |
| Rangos de ID | Un asignador da a cada instancia de la API un bloque de ID (por ejemplo, 100 000) | Huecos cuando una instancia muere; sigue siendo secuencial dentro de un bloque |
| Hash + truncado | Hacer hash de la URL y quedarse con los 7 primeros caracteres base62 | Colisiones; necesitas comprobar y reintentar |
| ID al estilo Snowflake | Marca de tiempo + ID de máquina + secuencia en 64 bits | Códigos más largos (11 caracteres en base62) |
El hash tienta porque la misma URL obtiene siempre el mismo código. El problema es la paradoja del cumpleaños: con 3,5 billones de códigos posibles, la probabilidad de al menos una colisión supera el 50 % tras solo unos 2,2 millones de enlaces. A partir de ahí, cada inserción necesita una comprobación de unicidad y un reintento con sal, lo que convierte una escritura en una lectura más una escritura.
Los rangos de ID son la opción pragmática por defecto. Cada instancia de la API reserva un bloque en el asignador con una transacción pequeña y luego reparte códigos desde memoria sin ninguna coordinación. Si no quieres códigos secuenciales, pasa el ID por una permutación reversible (por ejemplo, un cifrado por bloques sobre el espacio de ID) antes de codificarlo. Snowflake, de Twitter, muestra el otro camino: una marca de tiempo de 41 bits, un ID de máquina de 10 bits y una secuencia de 12 bits dan ID únicos de 64 bits sin asignador central, a cambio de códigos más largos.
Los alias propios se saltan el generador. Guárdalos en la misma tabla y confía en una restricción de unicidad sobre la columna del código para rechazar duplicados.
¿301 o 302?
El RFC 9110 define los códigos de redirección. La diferencia que importa aquí es la caché: una respuesta 301 (Moved Permanently) es cacheable por heurística, así que los navegadores pueden recordarla y saltarse tu servidor en el siguiente clic. Una 302 (Found) es temporal y no se guarda en caché salvo que lo indiques.
- Usa 302 cuando necesites contar cada clic o quieras cambiar el destino más adelante.
- Usa 301 cuando el enlace no cambie nunca y prefieras menos peticiones a una analítica completa.
307 y 308 son las versiones de 302 y 301 que conservan el método. Para clics normales del navegador, que son peticiones GET, la elección entre ellas rara vez importa.
Almacenamiento y caché
Cada redirección es una búsqueda puntual por código, así que sirve cualquier almacén con búsquedas rápidas por clave: un almacén clave-valor como DynamoDB o Cassandra, o Postgres con el código como clave primaria. Particiona por código cuando un nodo ya no baste; un hash del código reparte la carga de forma uniforme porque, por diseño, ningún código está más solicitado que otro. Los enlaces populares son la excepción, y la caché los absorbe.
Pon una caché Redis delante del almacén con lectura cache-aside: busca el código en Redis, recurre a la base de datos si falla y luego llena la caché con un TTL. La popularidad de los enlaces sigue una cola larga, así que una caché con una pequeña fracción de los enlaces atiende la mayoría de los clics. El artículo sobre estrategias de caché trata en detalle los TTL, las estampidas y la invalidación. Las réplicas de lectura ayudan a la ruta de respaldo; el artículo sobre replicación de bases de datos explica el retraso que entonces tienes que tolerar.
Un CDN o una caché en el borde puede servir directamente las redirecciones más populares si envías una cabecera Cache-Control, pero entonces el borde responde clics que tus servidores nunca ven. O registras en el borde o dejas las redirecciones sin caché.
Analítica de clics sin frenar la redirección
Nunca escribas un contador de clics de forma síncrona en la ruta de redirección. El servicio de redirección publica un pequeño evento de clic (código, marca de tiempo, referrer, país) en un log como Kafka y responde de inmediato. Un consumidor agrega los totales por enlace y por día y los escribe en un almacén analítico. Si la analítica se retrasa, las redirecciones siguen funcionando; es el mismo desacoplamiento que se describe en el artículo sobre arquitectura orientada a eventos.
El diagrama de arquitectura
Contenido del 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
Cómo leerlo:
- Ruta de lectura (arriba). Las peticiones
GET /{code}atraviesan el borde y el balanceador de carga hasta el servicio de redirección, que consulta primero Redis y solo acude al almacén de enlaces si falla. - Ruta de escritura (abajo).
POST /linksva a la API de acortado. Comprueba el destino con un servicio de reputación de URLs, toma ID de un rango que reservó en el asignador, los codifica en base62 y escribe el enlace. - Analítica (a la derecha). Los eventos de clic son discontinuos porque son asíncronos. El agregador los convierte en totales diarios.
- El bloque Markdown mantiene las cifras de capacidad junto al diseño para que quien lo revise pueda comprobarlas.
Abuso, caducidad y alias propios
Un acortador público es una herramienta de phishing salvo que lo impidas. Comprueba los destinos nuevos con un servicio de reputación como Google Safe Browsing, limita la creación de enlaces por cuenta y por IP y ten una forma de desactivar un código rápidamente. Vuelve a comprobar los enlaces antiguos cada cierto tiempo, porque un dominio limpio puede volverse malicioso más tarde.
Para la caducidad, guarda expiresAt con el enlace, compruébalo en la redirección y borra las filas caducadas con un trabajo en segundo plano. Devuelve 404 o 410 para los códigos caducados en lugar de redirigir.
Qué poner en el diagrama
- Separa las rutas de lectura y escritura. Escalan de forma distinta, y el diagrama debe mostrarlo.
- Etiqueta el código de redirección (301 o 302) en el servicio de redirección. Decide si la analítica es completa.
- Muestra de dónde salen los ID. «Generar código» dentro de una caja esconde la decisión más debatida del diseño.
- Dibuja discontinuos los flujos asíncronos. Quien lo lea debe ver de un vistazo que la analítica no puede frenar una redirección.
- Escribe las cifras. Un diseño sin escrituras por segundo ni tamaño de almacenamiento no se puede revisar.
El ejemplo de diagrama de diseño de sistemas aplica las mismas convenciones a un sistema más grande.
Constrúyelo en Archboard
- Inicia sesión con Google o GitHub y crea un diagrama.
- Del pack Modelo C4, añade una Persona para el usuario, y del pack Nube un CDN y un Balanceador de carga en la nube.
- Añade cuatro nodos Contenedor del pack Modelo C4: el servicio de redirección, la API de acortado, el asignador de ID y el agregador de clics. Escribe en cada uno la tecnología y una descripción de una línea.
- Añade Redis, Kafka y un Almacén de datos del pack Datos, una Base de datos del pack Modelo C4 para el almacén de enlaces y un Sistema externo para la API de reputación de URLs.
- Conecta la ruta de lectura arriba y la de escritura abajo, dibuja los eventos de clic como aristas discontinuas y añade un bloque Markdown del pack Básico con las cifras de capacidad.
El ejemplo usa 13 nodos de diagrama, dentro del plan Free.
Preguntas frecuentes
¿Qué longitud debe tener un código corto? Siete caracteres base62 dan unos 3,5 billones de códigos, suficientes para décadas a 100 millones de enlaces al mes. Empieza con siete y deja que los códigos crezcan a ocho más adelante; nada en el diseño depende de una longitud fija.
¿Debo hacer hash de la URL larga para obtener el código? Solo si la deduplicación importa más que unas escrituras simples. Los hash truncados colisionan pronto (a partir de 2,2 millones de enlaces con 7 caracteres es más probable que haya al menos una colisión que ninguna), así que cada inserción necesita una comprobación y un reintento.
¿Qué es mejor para un acortador de URLs, 301 o 302? Una 302 si necesitas contar cada clic o puedes cambiar el destino. Una 301 permite que los navegadores guarden la redirección en caché, lo que ahorra peticiones pero oculta los clics repetidos a tu analítica.
¿Qué base de datos debería guardar los enlaces? Cualquier almacén con búsquedas rápidas por clave primaria. Postgres basta durante mucho tiempo con estas cifras; un almacén clave-valor resulta atractivo cuando necesitas particionar las escrituras entre muchos nodos.
¿Cómo evito que la gente adivine los enlaces? No expongas ID secuenciales. Permuta el ID antes de codificarlo o usa códigos aleatorios con comprobación de unicidad, y nunca trates un enlace corto como un secreto para contenido privado.