Enunciado y alcance
Diseña un servicio que acepte una URL HTTP o HTTPS y devuelva un enlace corto. Al visitar el enlace corto, se debe redirigir al usuario al destino almacenado. El diseño base admite un alias personalizado y un tiempo de expiración opcionales. La analítica de clics, los dominios personalizados, la gestión de cuentas y las vistas previas de enlaces son temas de seguimiento en lugar de requisitos fundamentales.
Usa estas suposiciones de caso para que cada afirmación de capacidad sea reproducible:
- 1 millón de enlaces nuevos y 100 millones de redirecciones por día;
- el tráfico tiene picos de hasta 10 veces el promedio diario;
- los enlaces se retienen durante cinco años a menos que expiren o se deshabiliten;
- la ruta de redirección tiene como objetivo un 99.99% de disponibilidad mensual y una latencia de
servicio p99 inferior a 100 ms;
- un mapeo almacenado promedia 500 bytes antes de índices y replicación.
Estas son suposiciones de entrevista, no mediciones de un producto real. Implican un sistema con uso intensivo de lecturas, pero el diseño aún debe preservar la unicidad durante la creación concurrente, devolver un enlace recién creado de manera confiable tras tiempos de espera ambiguos y dejar de servir enlaces expirados o abusivos dentro de una ventana de propagación definida.
Qué evalúa el entrevistador
La primera señal es el control de los requisitos. Una respuesta útil separa la creación de enlaces y la redirección de las funciones de analítica y de cuentas, define si los destinos pueden cambiar y pregunta cómo se comportan la expiración y los alias personalizados. Añadir una cola, un motor de búsqueda o una base de datos de grafos antes de definir esos contratos debilita el diseño.
La segunda señal es si las estimaciones de escala modifican las decisiones. La carga de trabajo asumida promedia alrededor de 12 creaciones y 1,200 redirecciones por segundo, con picos cercanos a 120 y 12,000 por segundo. Cinco años de creación rinden alrededor de 1.8 mil millones de mapeos y aproximadamente 0.9 TB de datos de mapeo puros. La replicación, los índices, la sobrecarga de almacenamiento y el margen de seguridad hacen que la huella aprovisionada sea varias veces mayor. Estos números justifican un almacenamiento duradero particionable y una caché, pero no justifican todos los componentes distribuidos posibles.
La tercera señal es la corrección de los identificadores. Un código Base62 de ocho caracteres tiene 62^8, o alrededor de 218 billones (trillion en inglés), de valores posibles. Con 1.8 mil millones de enlaces retenidos, la ocupación es inferior al 0.001%. Por lo tanto, una nueva extracción aleatoria tiene una probabilidad de colisión minúscula; sin embargo, la probabilidad de que el sistema haya visto alguna colisión se vuelve grande tras suficientes extracciones. La aleatoriedad reduce la previsibilidad; no garantiza la unicidad. La escritura duradera debe afirmar atómicamente que el código no existe y reintentar ante una colisión aleatoria.
La cuarta señal es el razonamiento sobre la ruta de lectura y los fallos. La caché es una optimización, no la fuente de la verdad. La expiración debe comprobarse durante las lecturas en lugar de depender de un trabajo de limpieza. Una clave saturada (hot key), una interrupción de la caché, un tiempo de espera en la base de datos, un POST duplicado, el retraso de replicación regional, la acumulación de analíticas pendientes y una baja de emergencia requieren, cada uno, un comportamiento explícito.
Finalmente, una respuesta sólida trata la seguridad como un requisito fundamental de la redirección. Los enlaces cortos pueden ocultar destinos de phishing y los códigos predecibles pueden permitir la enumeración. El análisis sintáctico de URLs, los esquemas permitidos, los límites de tasa, las comprobaciones de reputación, el reporte de abusos, la deshabilitación rápida y los códigos públicos no secuenciales pertenecen al diseño en lugar de a un recuadro genérico de “añadir seguridad más tarde”.
Preguntas de clarificación antes de responder
- ¿Puede cambiar un destino después de su creación? El mapeo base es inmutable. La
inmutabilidad simplifica el almacenamiento en caché y el historial de auditoría. Si se requiere edición, añade control de versiones y un SLO estricto de invalidación.
- ¿Deben las URLs largas idénticas compartir un mismo código? No. Diferentes propietarios,
campañas, tiempos de expiración y políticas pueden necesitar enlaces distintos. La desduplicación puede ser una opción explícita, no un efecto secundario accidental de aplicar hash al destino.
- ¿Se requieren alias personalizados? Son opcionales y únicos en el dominio seleccionado. Un
conflicto devuelve 409; el servicio nunca cambia silenciosamente un alias solicitado.
- ¿Qué sucede al expirar? Un código que se sabe que está expirado o deshabilitado devuelve
410; un código desconocido devuelve 404. Las lecturas comprueban expires_at, mientras que la eliminación asíncrona solo recupera almacenamiento.
- ¿Qué estado de redirección se espera? Usa
302por defecto porque los mapeos pueden
deshabilitarse y el servicio puede necesitar cada solicitud por políticas o analítica. Ofrece 301 solo para enlaces inmutables cuyo propietario acepte un almacenamiento en caché prolongado por parte del cliente y de los intermediarios.
- ¿Qué consistencia se requiere? La reserva de códigos y la creación de alias personalizados
requieren una unicidad fuerte. Las redirecciones existentes favorecen la disponibilidad, pero una creación exitosa debe poder leerse de inmediato mediante la inserción en caché o una ruta de lectura tras escritura.
- ¿Debe la analítica ser sin pérdida de datos? Está fuera de la ruta base. Si se añade, define la
pérdida aceptable y la frescura por separado para que una canalización de analítica retrasada no bloquee las redirecciones.
- ¿Obtiene el servicio el contenido de destino? La ruta de redirección no lo hace. Cualquier vista
previa o escáner de malware que obtenga URLs se ejecuta en un servicio asíncrono aislado con defensas contra SSRF.
Estructura de respuesta en 30 segundos
“Mantendré la creación y la redirección como los dos flujos principales. Con un millón de creaciones y cien millones de redirecciones al día, el promedio es de unas 12 escrituras y 1,200 lecturas por segundo, con picos de 10 veces esa cantidad. Generaré códigos Base62 de ocho caracteres mediante aleatoriedad criptográfica y los reservaré con una inserción atómica condicional si no existe; los alias personalizados usan la misma condición. El almacenamiento duradero de mapeos se particiona por un hash del código y sigue siendo la fuente de la verdad. Los servidores de redirección utilizan cache-aside, comprueban el estado y la expiración, y luego devuelven una respuesta 302 Location. La creación es idempotente, las entradas de caché nunca sobreviven a la expiración del enlace y las actualizaciones o bajas envían invalidaciones. Escalaré la ruta de lectura de alta demanda de forma independiente, protegeré las fallas de caché contra avalanchas (stampedes) y mantendré la analítica asíncrona. Validaré las condiciones de carrera de unicidad, los reintentos por pérdida de respuesta, las claves saturadas, los fallos de caché y de base de datos, los límites de expiración y la propagación de deshabilitación por abuso frente a SLOs explícitos.”
Análisis detallado paso a paso
Comienza con un pequeño balance de capacidad:
Creates: 1,000,000 / 86,400 ≈ 12/s average, ≈ 120/s at 10x peak
Redirects: 100,000,000 / 86,400 ≈ 1,200/s average, ≈ 12,000/s at 10x peak
Mappings: 1,000,000 × 365 × 5 = 1.825 billion
Raw data: 1.825 billion × 500 bytes ≈ 0.9 TB before overhead and replicas
Code space: 62^8 = 218,340,105,584,896; occupancy remains below 0.001%La API central puede mantenerse reducida:
POST /v1/links
Idempotency-Key: client-generated-key
{ "url": "https://example.com/a", "customAlias": null, "expiresAt": null }
-> 201 { "code": "aZ3kP9qR", "shortUrl": "https://sho.rt/aZ3kP9qR" }
GET /{code}
-> 302 Location: https://example.com/a
-> 404 when the code never existed
-> 410 when it is expired or disabledEl patrón de acceso principal es una búsqueda puntual por código, por lo que un registro duradero solo necesita los campos que sirven para la creación, redirección y política de ciclo de vida:
links
code primary key
long_url
owner_id
status ACTIVE | DISABLED
created_at
expires_at nullable
version
create_requests
owner_id + idempotency_key unique key
request_fingerprint
code
status
expires_atUtiliza un generador aleatorio criptográficamente seguro para ocho caracteres Base62. Un espacio de siete caracteres ya contiene cerca de 3.5 billones de valores, pero el octavo carácter deja más margen y dificulta la enumeración en línea con un costo de URL insignificante. Un generador aleatorio evita un asignador numérico centralizado y secuencias predecibles. Aún así necesita una escritura condicional atómica: insertar el mapeo solo si code no está presente. Si la condición falla para un código generado, extrae nuevamente con un límite de reintentos acotado. Si un alias personalizado entra en conflicto, devuelve 409 porque cambiarlo violaría el contrato con el cliente.
Un contador codificado en Base62 es una alternativa válida. Garantiza valores compactos únicos si el asignador es correcto, y el arrendamiento de rangos puede reducir la coordinación. Sus costos son la recuperación del asignador, la pérdida de rangos, la pertenencia regional y la enumeración predecible. Aplicar hash a la URL larga no es una solución gratuita: el truncamiento puede colisionar, destinos iguales pueden necesitar enlaces separados y resolver una colisión todavía requiere almacenamiento. Indica qué propiedad importa antes de elegir entre códigos aleatorios, contadores arrendados y hashes.
La creación procede en este orden:
- Autentica donde sea necesario, limita la tasa del cliente, analiza sintácticamente la URL, permite
únicamente http y https, aplica límites de longitud y políticas, y normaliza el alias personalizado.
- Comprueba la clave de idempotencia. Reutilizarla con una huella digital de solicitud diferente es
un conflicto; reutilizarla con la misma solicitud devuelve el resultado original.
- Genera o acepta un código. En una sola transacción, reserva condicionalmente el código y persiste
el registro de idempotencia. La transacción evita que dos creadores ganen el mismo alias y evita que una respuesta perdida cree un enlace diferente al reintentar.
- Tras la confirmación duradera, puebla o invalida el estado de la caché y encola el escaneo de
reputación asíncrono. Nunca devuelvas un código que solo exista en la caché.
Si la escritura agota el tiempo de espera, el cliente reintenta con la misma clave de idempotencia. El servicio primero lee el registro de la solicitud y devuelve el resultado confirmado si está presente. Generar ciegamente otro código convierte una respuesta ambigua en un estado duradero duplicado. Si el almacén no puede probar si la transacción se confirmó, informa un resultado en curso o reintentable en lugar de declarar un fallo y crear un nuevo mapeo.
Para la redirección, un servicio de redirección stateless o en el borde comprueba una lista de denegación propagada rápidamente y luego busca code en una caché distribuida. Un acierto (hit) aún comprueba status y expires_at. Un fallo (miss) realiza una lectura puntual desde el almacén duradero, valida los mismos campos del ciclo de vida y almacena en caché el mapeo. Establece el TTL de la caché a no más tardar de expires_at; añade una pequeña variación aleatoria (jitter) a los TTL amplios para que muchas entradas no expiren juntas. Almacena en caché los códigos desconocidos brevemente para absorber escaneos, pero invalida una entrada negativa cuando se crea un alias personalizado con ese código.
Devuelve 302 con un encabezado Location por defecto. La semántica de HTTP define 302 como una ubicación temporal, por lo que los clientes continúan usando la URL corta en solicitudes futuras. Un 301 indica un nuevo URI permanente y es almacenable heurísticamente en caché; puede quitarle tráfico al servicio, pero también retrasa la revocación, los cambios de destino y la analítica a nivel de solicitud. El estado de redirección y Cache-Control son contratos de producto, no un interruptor de rendimiento oculto dentro del servicio.
El almacén duradero puede ser una base de datos clave-valor o una base de datos relacional particionada mediante un hash de code. El requisito principal es la creación atómica si no existe, la replicación duradera, las lecturas puntuales, las copias de seguridad y una ruta de restauración probada. Los códigos aleatorios distribuyen el tráfico normal de forma natural, aunque un código viral sigue siendo una clave saturada (hot key). Replica la caché, añade una pequeña caché local para enlaces extremadamente populares y fusiona los fallos de caché concurrentes para que una expiración no envíe miles de lecturas idénticas a la base de datos.
La política de fallos debe preservar el límite de la fuente de la verdad:
- Si la caché no está disponible, utiliza un disyuntor (circuit breaker), lecturas directas acotadas,
entradas populares locales y control de admisión. La omisión descontrolada de la caché puede convertir un incidente de caché en un incidente de base de datos.
- Si la ruta de lectura de la base de datos no está disponible, sirve una entrada de caché positiva y
desactualizada pero acotada solo cuando el producto acepte ese riesgo. Nunca extiendas un enlace expirado ni omitas una lista de denegación por bajas.
- Si la ruta de escritura duradera no puede garantizar la unicidad, falla la creación. La
disponibilidad no justifica emitir dos destinos para un mismo código.
- Si la analítica se retrasa, las redirecciones continúan y los eventos de clics se almacenan en
búfer, se muestrean o se descartan de acuerdo con el contrato de analítica definido por separado.
- Si la limpieza se detiene, las lecturas siguen aplicando la expiración. El almacenamiento crece,
pero no se sirven enlaces expirados.
La validación de seguridad comienza en la creación y continúa después de ella. Rechaza esquemas que no sean HTTP y URLs mal formadas con un analizador real. Limita la tasa por cuenta, red y señal de riesgo; escanea destinos asíncronamente; mantén flujos de reporte y apelación; y propaga las bajas confirmadas a la ruta de redirección rápidamente. Los códigos no deben otorgar acceso a contenido privado. Si el destino necesita autorización, el sistema de destino debe exigirla; la oscuridad en un código corto no es control de acceso.
La verificación debe poner a prueba las propiedades y los fallos. Haz competir a muchos creadores por un único alias personalizado y confirma que exactamente uno tenga éxito. Pierde la primera respuesta del POST y confirma que el reintento idempotente devuelva el mismo código. Prueba un segundo antes, en el momento exacto y un segundo después de la expiración. Crea un código inmediatamente después de una búsqueda en caché negativa. Genera suficientes códigos aleatorios para forzar conflictos condicionales artificialmente. Carga el pico de 10 veces tanto con un conjunto de trabajo amplio como con un único código popular, luego haz fallar nodos de caché, limita la base de datos, retrasa las invalidaciones y detén a los trabajadores de limpieza y analítica. Mide el éxito de la redirección, la latencia p99, la tasa de aciertos de caché, la carga de fallos en base de datos, los conflictos condicionales, la entrega de enlaces desactualizados y la propagación de bajas en lugar de reportar únicamente el rendimiento promedio.
Ejemplo de respuesta de alta calidad
“Definiría el alcance del sistema base en crear y resolver enlaces cortos, con alias personalizados y expiración opcionales. Aclararía que los destinos son inmutables, las URLs largas idénticas pueden recibir códigos diferentes, los enlaces que se sabe que están expirados devuelven 410 y la analítica no bloquea las redirecciones.
Usando las suposiciones del caso, la creación promedia unas 12 solicitudes por segundo y alcanza picos cercanos a 120; las redirecciones promedian unas 1,200 y alcanzan picos cercanos a 12,000. Cinco años retienen cerca de 1.8 mil millones de mapeos, o aproximadamente 0.9 TB en bruto a 500 bytes cada uno. Por lo tanto, usaría un almacén duradero que admita lecturas puntuales, particionamiento, replicación e inserciones condicionales atómicas, con code como clave de partición.
Para los enlaces generados, extraería un código Base62 de ocho caracteres con aleatoriedad criptográfica. El espacio es de unos 218 billones de valores, por lo que la posibilidad de colisión por inserción se mantiene minúscula a nuestra escala, pero la aleatoriedad no prueba la unicidad. Reservo el código con inserción si no existe y reintento ante una colisión generada. Un conflicto de alias personalizado devuelve 409. El POST también lleva una clave de idempotencia; el mapeo y el registro de la solicitud se confirman juntos para que una respuesta perdida pueda devolver el mismo código.
El servicio de redirección consulta una lista de denegación por bajas y una caché. Ante un fallo de caché, realiza una lectura puntual, comprueba el estado activo y la expiración, almacena en caché por un tiempo no superior a la vida útil restante y devuelve un 302 con Location. Uso 302 por defecto porque el mapeo puede deshabilitarse y el servicio puede requerir políticas o analítica a nivel de solicitud; los enlaces inmutables pueden optar por 301 y un almacenamiento en caché más agresivo. Las entradas negativas reciben TTLs cortos, y la creación de un alias personalizado invalida cualquier entrada negativa en caché.
El mapeo duradero es la fuente de la verdad. Un fallo de caché se degrada a lecturas acotadas en la base de datos con control de admisión, un fallo de base de datos puede usar entradas positivas desactualizadas acotadas solo bajo políticas permitidas, y la creación falla si no se puede garantizar la unicidad. Las claves saturadas usan almacenamiento en caché por capas y fusión de fallos de caché. La analítica y el escaneo de reputación son asíncronos, mientras que el abuso confirmado deshabilita el enlace a través de una ruta de control de propagación rápida.
Probaría el diseño mediante competencias concurrentes por alias, reintentos por pérdida de respuesta, límites de expiración, invalidación de caché negativa, carga sobre claves populares y conjuntos amplios, pérdida de caché, limitación de base de datos, fallo de limpieza y propagación de bajas. La aceptación está vinculada a un 99.99% de disponibilidad de redirección, p99 inferior a 100 ms bajo el pico declarado, cero ganadores de códigos duplicados, ningún enlace expirado servido y una ventana de propagación de deshabilitación medida.”
Errores comunes
- Aplicar hash a la URL larga y asumir unicidad → el truncamiento colisiona y URLs idénticas
pueden necesitar políticas separadas → **Usa una reserva atómica y define si se desea la desduplicación.**
- Usar códigos aleatorios sin una escritura condicional → la probabilidad se confunde con una
garantía → Inserta solo cuando el código esté ausente y reintenta las colisiones generadas.
- Devolver un código nuevo tras un tiempo de espera de escritura → una sola acción del cliente
crea múltiples enlaces → **Vincula los reintentos a una clave de idempotencia persistida y recupera el resultado original.**
- Escribir en la caché antes del almacenamiento duradero → un enlace que parecía exitoso
desaparece al ser desalojado de la caché → **Confirma primero la fuente de la verdad, luego puebla la caché.**
- Depender de un trabajo de eliminación para la expiración → un trabajo retrasado sirve enlaces
expirados → **Comprueba expires_at en cada ruta de resolución y usa la limpieza solo para recuperar espacio.**
- Llamar al 301 “más rápido” y al 302 “sin caché” → el comportamiento de la caché y la
mutabilidad se simplifican en exceso → **Elige la semántica de redirección y los controles de caché explícitos a partir del contrato de producto.**
- Almacenar en caché el 404 indefinidamente → un alias personalizado recién creado permanece
inaccesible → Usa un TTL negativo corto e invalídalo al momento de la creación.
- Enviar cada fallo de caché directamente a la base de datos → la expiración de una clave popular
crea una avalancha (stampede) → **Usa fusión de solicitudes (request coalescing), variación aleatoria de TTL (jitter) y almacenamiento en caché por capas para claves populares.**
- Hacer que la analítica sea síncrona → una interrupción en una canalización no crítica interrumpe
las redirecciones → **Emite eventos después de resolver y define la pérdida/frescura de la analítica por separado.**
- Tratar un código corto como autorización → la enumeración o el compartirlo expone contenido
protegido → Requiere autorización en el destino y usa el código solo como un localizador.
Preguntas de seguimiento y cómo manejarlas
Pregunta de seguimiento 1: ¿Cómo añadirías analítica de clics casi en tiempo real?
Emite un evento de clic después de la decisión de redirección con code, marca temporal del evento, ID de solicitud y únicamente las dimensiones aprobadas por privacidad. Particiona el flujo por código para una agregación ordenada por enlace, pero añade sal (salt) o divide los códigos excepcionalmente populares si una partición se satura. Los consumidores actualizan los agregados por minuto y diarios de forma idempotente. Define la pérdida aceptable, la duplicación, la frescura, la retención, el filtrado de bots y el consentimiento antes de elegir confirmaciones (acknowledgements); la redirección no debe esperar al almacén analítico.
Pregunta de seguimiento 2: ¿Cómo desplegarías activo-activo entre regiones?
Mantén las lecturas locales mediante cachés y réplicas regionales. La creación de códigos aún necesita unicidad global: utiliza un almacén globalmente condicional, asigna espacios de nombres numéricos o aleatorios disjuntos por región, o enruta la creación a una región principal (home region). Una creación exitosa necesita una estrategia de lectura tras escritura hasta que la replicación se ponga al día. Los metadatos de baja requieren una ruta de propagación más rápida y medida de forma separada a la replicación ordinaria de mapeos.
Pregunta de seguimiento 3: ¿Qué cambia si los destinos son editables?
Añade actualizaciones condicionales versionadas, un registro de auditoría, autorización del propietario e invalidación de caché indexada por código y versión. Define si las respuestas 301 ya almacenadas en caché pueden permanecer obsoletas; si las ediciones rápidas o la revocación importan, usa 302 por defecto con una frescura de caché acotada. Las actualizaciones concurrentes requieren una versión esperada para que un editor no sobrescriba silenciosamente a otro.
Pregunta de seguimiento 4: ¿Cómo manejas un enlace que recibe millones de solicitudes por segundo?
Sírvelo desde una CDN o caché en el borde, cachés regionales y una pequeña caché en el proceso, con comprobaciones coherentes de bajas. Replica el valor popular en lugar de intentar fragmentar una única clave por su código. Fusiona las actualizaciones, actualiza antes de la expiración y aísla el tráfico de claves saturadas para que no consuma todo el presupuesto de conexiones de la caché o de la base de datos. Haz pruebas de carga para la invalidación, ya que un enlace viral en caché también es el más difícil de revocar rápidamente.
Pregunta de seguimiento 5: ¿Cómo afectan los dominios personalizados al modelo de claves y enrutamiento?
La unicidad pasa a ser (domain, code), no solo code. Verifica la propiedad del dominio, aprovisiona certificados, enruta por host y mantén cuotas de inquilinos y políticas de abuso. La clave de caché y la clave de partición de la base de datos deben incluir el dominio. Si existe el mismo alias en dos dominios, ninguno debe sobrescribir o invalidar al otro.
Pregunta de seguimiento 6: ¿Qué pasa si una eliminación legal debe borrar un destino inmediatamente?
Separa la inhabilitación del servicio del borrado físico. Primero marca el registro como deshabilitado, actualiza la lista de denegación, invalida las cachés y verifica que cada región devuelva 410 dentro del SLO de baja. Luego elimina o borra criptográficamente el registro duradero, las copias de seguridad, las dimensiones de analítica y las copias en motores de búsqueda o escáneres según la política de retención. Un trabajo de eliminación asíncrono por sí solo no puede probar que el enlace haya dejado de resolverse.
Pregunta de seguimiento 7: ¿Cómo migrarías de códigos de ocho caracteres a códigos más largos?
Haz que el sistema de resolución acepte un rango versionado de longitudes antes de que cambien los escritores. Los nuevos escritores pueden emitir códigos más largos mientras que los mapeos antiguos continúan resolviéndose sin cambios. El particionamiento no debe depender de una posición de carácter fija que el nuevo formato elimine. Monitorea los errores del sistema de resolución y el análisis de claves de caché, y luego retira las versiones antiguas de los escritores; nunca reescribas códigos públicos existentes simplemente para estandarizar la longitud.