Elido
10 min de lecturaIngeniería

Cómo construir un acortador de URL: arquitectura y código

Cómo construir un acortador de URL que sobreviva en producción: generación de códigos cortos, la ruta de redirección, caché, seguimiento de clics, defensa contra abusos y qué mantener.

Marius Voß
DevRel · edge infra
Diagrama de arquitectura de un acortador de URL que muestra la ruta de escritura que codifica un código corto y la ruta de lectura que resuelve una redirección desde la caché

Para construir un acortador de URL necesitas cuatro cosas: un lugar donde almacenar el mapeo de un código corto a una URL de destino, una forma de generar un código único para cada nuevo enlace, un manejador de redirección que consulte el código y devuelva una redirección HTTP, y una caché delante de la consulta porque las lecturas superan a las escrituras por un margen amplio. Ese es el núcleo completo, y puedes montarlo en una tarde.

La trampa es pensar que la versión de la tarde es el producto. Una redirección que funciona en tu portátil y un servicio de acortamiento de URL que sobrevive a que desconocidos lo apunten con malware, lo bombardeen con tráfico y esperen cuatro nueves de disponibilidad son problemas de ingeniería diferentes. El primero es un algoritmo. El segundo es un compromiso operativo.

Este recorrido construye el núcleo con honestidad y luego dedica la mayor parte del tiempo a la parte que los tutoriales de diseño de sistemas omiten: lo que aún tienes que construir después de que la redirección funciona. Si primero quieres el marco conceptual, cómo funcionan los acortadores de URL cubre la mecánica sin el código.

Dos rutas en un acortador de URL: la ruta de escritura codifica un ID único en un código corto y lo almacena, la ruta de lectura resuelve un clic a través de la caché hacia una redirección

La versión corta: lo que realmente hace un acortador de URL#

Un acortador de URL es una búsqueda clave-valor con una redirección HTTP. La clave es el código corto, el valor es la URL larga, y el trabajo completo es convertir example.com/aB3x9 en un 302 que apunte a la dirección original.

El modelo de datos es una tabla:

CREATE TABLE links (
    id          BIGSERIAL PRIMARY KEY,
    short_code  TEXT NOT NULL UNIQUE,
    long_url    TEXT NOT NULL,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE UNIQUE INDEX idx_links_short_code ON links (short_code);

Hay dos rutas a través de él. La ruta de escritura toma una URL larga, genera un código corto e inserta la fila. La ruta de lectura toma un código corto, consulta la fila y devuelve una redirección. Las lecturas dominan por una proporción que habitualmente ronda 1000 a 1, por lo que casi toda tu atención de ingeniería debe centrarse en hacer que la consulta sea rápida y barata. El índice único en short_code es lo que mantiene esa consulta como una búsqueda de índice en lugar de un escaneo. Ese es todo el núcleo.

Generación del código corto: base62, aleatorio o hash#

El código corto es donde reside la decisión interesante. Tienes tres estrategias realistas, y conllevan compromisos entre longitud, predecibilidad y la dificultad de manejar colisiones.

Base62 de un ID único es el enfoque clásico. Toma el ID de fila autoincremental y codifícalo en base62, los 62 caracteres a-z, A-Z y 0-9. Los códigos son cortos, nunca colisionan porque cada ID es único, y crecen un carácter más de largo aproximadamente cada 62x en volumen. El inconveniente es que son secuenciales y predecibles, por lo que cualquiera puede recorrer tu espacio de nombres.

const alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

// encode turns a positive integer ID into a base62 short code.
func encode(id uint64) string {
	if id == 0 {
		return string(alphabet[0])
	}
	var b []byte
	for id > 0 {
		b = append(b, alphabet[id%62])
		id /= 62
	}
	// reverse, since we built the digits least-significant first
	for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
		b[i], b[j] = b[j], b[i]
	}
	return string(b)
}

Las cadenas aleatorias solucionan la predecibilidad. Genera un código aleatorio corto, por ejemplo con una biblioteca como nanoid, y compruébalo contra el índice único antes de guardar. Con siete caracteres de base62 tienes billones de posibilidades, por lo que las colisiones son raras, pero aún tienes que manejar la rara inserción que falla la restricción de unicidad reintentando con un nuevo código.

El hash de la URL es la tercera opción y habitualmente la peor. Un hash de la URL larga es determinista, lo que parece conveniente, pero aun así tienes que truncarlo, aun así obtienes colisiones, y las URLs idénticas se mapean a códigos idénticos, lo que filtra información. La mayoría de los servicios en producción eligen base62 para IDs internos o códigos aleatorios para los públicos. Los slugs personalizados o con marca - los códigos que un usuario escribe a mano - se validan contra el mismo índice único antes de aceptarlos.

La ruta de redirección: 301 frente a 302 y por qué decide tu analítica#

El código de estado de la redirección no es una elección cosmética. Decide si alguna vez verás un segundo clic.

Un 301 Moved Permanently le dice a los navegadores y proxies que el traslado es permanente, por lo que lo almacenan en caché. Tras la primera visita, el navegador puede enviar futuros clics directamente al destino sin tocar tu servidor. Excelente para la velocidad bruta, fatal para la analítica, porque los clics que más quieres contar son los que nunca te llegan. La semántica HTTP está detallada en RFC 9110, que define tanto las redirecciones permanentes como las temporales.

Un 302 Found o 307 Temporary Redirect se vuelve a solicitar cada vez. El navegador consulta tu servidor en cada clic, lo que significa que puedes contar cada visita y cambiar el destino más adelante sin luchar contra cachés obsoletas. Para un acortador de enlaces cuyo valor completo son los enlaces editables y los datos de clics, ese es el predeterminado correcto. El coste es un viaje de ida y vuelta en red por clic, que un acierto de caché hace insignificante.

La regla general: usa 302 a menos que tengas una razón específica para querer el enlace congelado y almacenado en caché para siempre. El artículo sobre redirecciones 301 vs 302 analiza el compromiso en detalle, y tipos de redirecciones cubre el resto de la familia 3xx, incluyendo cuándo importan el 307 y el 308.

Almacenamiento y caché: diseñar para una proporción de lectura/escritura de 1000:1#

Dado que las lecturas superan masivamente a las escrituras, la base de datos no es tu cuello de botella, sino tu estrategia de caché. El patrón es una caché de lectura anticipada: en un clic, comprueba primero una caché en memoria, y solo recurre a la base de datos en un fallo, escribiendo el resultado de vuelta en la caché para la próxima vez.

func resolve(ctx context.Context, code string) (string, error) {
	if url, ok := cache.Get(code); ok {
		return url, nil // hot path: served from memory
	}
	url, err := db.LookupLongURL(ctx, code)
	if err != nil {
		return "", err
	}
	cache.Set(code, url) // populate for the next click
	return url, nil
}

En producción esto suele convertirse en dos niveles: una caché en proceso pequeña para los enlaces más activos, respaldada por un almacén en memoria compartida como Redis para que cada instancia del servidor se beneficie de una consulta que cualquiera de ellas ya haya realizado. La base de datos, la fuente de verdad, solo se toca en un fallo en frío genuino. Haz bien esta capa y un único servidor modesto maneja un volumen de clics enorme. El artículo sobre estrategia de caché para redirecciones de URL profundiza en las decisiones de desalojo y dimensionamiento, y el artículo fundamental sobre alcanzar p95 por debajo de 15ms cubre cómo se ve una ruta de redirección ajustada bajo carga.

Si prefieres no gestionar nada de esto, la API de Elido te da el nivel de redirección, la caché y la entrega en región EU con p95 por debajo de 15ms en un acierto de caché, detrás de una única llamada. Empieza gratis y omite las operaciones.

Contar clics sin ralentizar la redirección#

El error que mata la latencia de la redirección es escribir el clic en la base de datos dentro del manejador de redirección. Haz eso y cada visitante espera tu escritura de analítica antes de recibir su redirección.

Desacóplalos. El manejador emite la redirección de inmediato, luego envía el evento de clic a un log duradero o cola de mensajes como trabajo de disparar-y-olvidar. Un consumidor separado lee ese flujo y escribe eventos en un almacén de analítica a su propio ritmo. El visitante nunca espera, y una consulta de informes que escanea millones de filas de clics nunca compite con la ruta de redirección por recursos. Una base de datos analítica columnar maneja esas consultas agregadas mucho mejor que un almacén por filas, por eso los eventos de clics habitualmente acaban en un lugar diferente a la tabla de enlaces. El artículo sobre ingesta de clics con disparar-y-olvidar detalla el lado de la cola, y por qué un almacén columnar supera a Postgres para la analítica de clics explica la elección de almacenamiento. La analítica de Elido sigue esta arquitectura para que los clics sean consultables en segundos sin añadir milisegundos a la redirección.

El backlog de producción más allá de una redirección funcional: escaneo de abusos, limitación de tasa, TLS de dominio personalizado, datos de clics seguros según el GDPR y alta disponibilidad

Lo que aún tienes que construir: el 80 por ciento difícil#

Esta es la parte que los tutoriales de diseño de sistemas dejan fuera. Una redirección funcional es quizás un quinto de un servicio real de acortamiento de URL. El resto es todo lo que convierte una demo en algo que puedes poner en la internet pública.

  • Escaneo de abusos y seguridad. Un acortador público es un imán de phishing a las pocas horas del lanzamiento. Necesitas comprobar los destinos contra un feed de amenazas como Google Safe Browsing y volver a escanear, porque una URL limpia en el momento de creación puede volverse maliciosa más tarde. La lista de verificación de seguridad para acortadores de URL es la lista completa.
  • Limitación de tasa e idempotencia. Un endpoint de creación abierto es atacado por scripts al instante. Necesitas límites por clave e idempotencia para que una solicitud reintentada no cree enlaces duplicados. La mecánica está en límites de tasa de API e idempotencia.
  • Dominios personalizados con TLS. Los enlaces con marca implican emitir certificados para dominios que no posees, bajo demanda y sin pasos manuales.
  • Datos de clics seguros según el GDPR. En el momento en que registras clics estás procesando datos personales. Truncar las direcciones IP y documentar la retención no es opcional en la UE, como describe GDPR para acortadores de URL.
  • Alta disponibilidad. Tu redirección está ahora en la ruta crítica de cada enlace que alguien haya compartido. El tiempo de inactividad rompe el contenido de otras personas, por lo que el nivel de disponibilidad requerido es más alto que para la mayoría de las apps.

Ninguno de estos es exótico. Son simplemente mucho trabajo sostenido, y nunca termina, que es la razón honesta por la que la mayoría de los equipos se detienen en el MVP y recurren a algo ya mantenido.

Construir, comprar o autoalojar#

Construirlo tú mismo es la mejor forma de entender las redirecciones, la codificación y la caché, y para una herramienta interna cerrada, el MVP puede ser todo lo que necesitas. Constrúyelo. Aprenderás más en un fin de semana que con cualquier preparación para entrevistas.

Para cualquier cosa pública o de cara al negocio, sopesa el mantenimiento con honestidad. La redirección es gratuita; el manejo de abusos, el TLS de dominio personalizado, la canalización de analítica y la rotación de guardia no lo son. Si quieres el control sin escribirlo desde cero, puedes autoalojar un servicio existente, y Elido ofrece una ruta de autoalojamiento exactamente para eso, con el artículo sobre opciones de código abierto mostrándolas en paralelo. Si prefieres delegarlo por completo, la solución para desarrolladores y el inicio rápido de API y SDK te dan un nivel de redirección en producción sin el backlog anterior.

Relacionados en el blog#

Prueba Elido

Pega una URL, obtén un enlace corto

Sin registro. El enlace vive 30 días. Crea una cuenta para conservarlo.

Gratis, sin registro · 2 por día

Prueba Elido

Acortador de URL alojado en la UE: dominios personalizados, análisis profundo y API abierta. Plan gratuito - sin tarjeta de crédito.

Etiquetas
build a url shortener
url shortener system design
short code generation
base62 encoding
url redirect
url shortener architecture
link shortener api

Seguir leyendo