Resumen
Propongo un acortador de URL distribuido globalmente y con caché primero, que incluye: una ruta de redirección de borde ligera (CDN + función de borde + caché local) para cumplir con el requisito de latencia de redirección p99 de <10 ms; un almacén primario duradero y multirregión para mapeos canónicos; un asignador de ID distribuido para generar códigos cortos únicos codificados en base62 (<=7 caracteres); un canal de análisis asíncrono (Kafka/Kinesis → procesadores de flujo → almacén OLAP) que está estrictamente desacoplado de la ruta de redirección; y replicación y conmutación por error multirregión para garantizar la disponibilidad durante una interrupción completa del centro de datos.
A. Arquitectura de alto nivel (componentes e interacciones)
- Capa de borde (CDN): Cloud CDN (por ejemplo, Amazon CloudFront, Fastly) antepone todas las solicitudes de redirección GET /{code}. La mayoría de las solicitudes se atienden desde la caché de borde de la CDN con una respuesta 301/302 en caché.
- Función de borde (CloudFront Function / Fastly Compute / Lambda@Edge): si la caché falla, una pequeña función de borde invoca una API de redirección regional (a través de un RPC/HTTP corto) para obtener la URL de destino canónica y devolver un 301/302. La función de borde agrega una lógica mínima para elegir el TTL de caché correcto y manejar códigos eliminados/faltantes.
- API de redirección regional / Nivel de caché de lectura: servidores de lectura sin estado en cada región que primero consultan una caché regional en memoria (clúster Redis / ElastiCache o Memcached) y luego recurren al almacén duradero de clave-valor si es necesario.
- Almacén duradero de clave-valor (Base de datos primaria): Tablas globales de DynamoDB o Cassandra (multirregión) que almacenan short_code -> long_url y metadatos como la fuente de verdad canónica.
- Servicio de asignación de ID (asignador de rangos): un pequeño servicio que entrega bloques de ID a los servidores de la API de escritura para la generación de códigos cortos locales (garantiza la unicidad sin un bloqueo central por escritura).
- API de escritura: servicio que maneja las solicitudes de creación, reserva/genera el short_code (a través del bloque de ID), lo persiste en la base de datos primaria y propaga una invalidación a las cachés y la CDN. Las escrituras son síncronas a la base de datos primaria para garantizar la durabilidad.
- Invalidation y propagación de caché: en la creación/actualización/eliminación, la API de escritura actualiza las cachés regionales e invalida las entradas de la CDN a través de la API de invalidación de la CDN o escribiendo encabezados de control de caché y utilizando un token de versión en la URL si es necesario.
- Canal de análisis (asíncrono): los eventos de redirección se registran de forma asíncrona (no en la ruta de redirección síncrona). Los registradores de borde o los servidores de lectura regionales envían eventos de clics ligeros a un bus de mensajes (Kafka / Kinesis). El procesamiento de flujo (Flink / Spark Streaming) agrega y escribe en un almacén de análisis (ClickHouse / BigQuery) y en contadores preagregados en un almacén optimizado para lectura para paneles.
- API y UI del panel: lee análisis agregados de almacenes OLAP/agregados y sirve paneles de usuario. Las consultas del panel nunca acceden a la ruta de redirección.
B. Estrategia de generación de URL
Objetivos: unicidad, compacidad (<=7 caracteres alfanuméricos), alto rendimiento, baja contención.
Enfoque elegido: ID numéricos secuenciales asignados en bloques de ID distribuidos, codificados en base62 para producir el código corto.
- Por qué ID secuenciales base62: base62 (a–z, A–Z, 0–9) proporciona 62^7 ≈ 3.52 × 10^12 códigos posibles con cadenas de hasta 7 caracteres, mucho más de lo que se espera para la vida útil. Los ID secuenciales se codifican de forma compacta y son fáciles de revertir a valores numéricos si es necesario. El mapeo determinista evita colisiones de hash.
- Implementación de asignación de ID: un asignador central entrega rangos de ID monótonamente crecientes (por ejemplo, bloques de 1M) a cada clúster de servidor de escritura. Cada servidor de escritura emite ID locales de su bloque sin coordinación remota, lo que garantiza la unicidad y una latencia muy baja. El asignador en sí es pequeño y puede respaldarse con un almacén de alta disponibilidad (RDS o un contador ligero basado en ZooKeeper/etcd) y solo se utiliza para recargas de bloques (QPS bajo).
- Codificación: ID numérico -> cadena base62. Si el ID numérico < 62^7, la longitud de la codificación es <=7. Con 100 millones de acortamientos nuevos por mes (1.2 mil millones/año), la capacidad de 62^7 proporciona más de 2900 años de espacio.
- Manejo de colisiones: no se esperan porque los ID numéricos son únicos. Aun así, la API de escritura utiliza una inserción condicional contra la base de datos primaria (PUT con clave primaria == short_code) y reintenta en caso de un conflicto raro en la base de datos primaria (no debería ocurrir si el asignador es correcto). Para alias personalizados solicitados por el usuario, verifique y devuelva un error si ya está ocupado.
- Deduplicación opcional: opcionalmente, mantenga un índice hash secundario (por ejemplo, SHA-256 de long_url) para devolver un código corto existente si la misma URL se acortó anteriormente por el mismo usuario; este es un comportamiento a nivel de aplicación y es opcional.
C. Modelo de datos y almacenamiento
Opciones de almacén de datos primario: Tablas globales de DynamoDB (gestionado, multirregión, lectura/escritura de un solo dígito ms), o Apache Cassandra / ScyllaDB (multirregión autogestionado) como las opciones canónicas. Recomiendo Tablas globales de DynamoDB para operaciones más rápidas y una replicación multirregión más simple, a menos que deba ser independiente de la nube.
Esquema de la tabla de mapeo primario (optimizado para clave-valor):
- Nombre de la tabla: URL_Mapping
- Clave primaria: short_code (cadena, PK)
- Atributos: long_url (texto), user_id (cadena), created_at (timestamp), custom_alias_flag (booleano), deleted_flag (booleano), metadata (JSON/mapa disperso), analytics_enabled (booleano), version (int)
- Índices secundarios (opcional): user_id -> lista de short_codes (GSI) para la interfaz de administración; long_hash -> short_code (para deduplicación si se desea)
Estimaciones de almacenamiento: suponga que cada registro almacena 200 bytes en promedio (short_code ~7 bytes, URL promedio 200? pero podemos comprimir; suponga 200-400 bytes conservadores). Con 100 millones de filas nuevas por mes: 100 millones * 200 B = 20 GB/mes. Anual ≈ 240 GB. Diez años ≈ 2.4 TB. DynamoDB/Cassandra pueden manejar fácilmente esta escala.
Almacenes de análisis: los eventos de clics brutos van a sistemas de solo adición (Kafka/Kinesis) y luego a un almacén de análisis a largo plazo (ClickHouse o BigQuery) para agregación y paneles. Los contadores preagregados (por short_code por intervalo de tiempo) se pueden almacenar en ClickHouse y los contadores activos en caché en Redis para consultas de paneles.
D. Optimización de la ruta de lectura (logrando <10 ms p99 redirecciones)
El objetivo es servir redirecciones del percentil 99 por debajo de 10 ms desde la llegada de la solicitud hasta la emisión de 301/302.
Técnicas utilizadas:
- CDN + Caché de borde (optimización principal): almacena en caché respuestas 301 completas en los bordes de la CDN para casi todas las solicitudes. Establezca TTL muy largos (efectivamente sin caducidad) ya que los mapeos no caducan, pero admita la invalidación inmediata cuando se actualiza/elimina un mapeo.
- Con acierto de caché de CDN, la latencia al cliente suele ser <10 ms a nivel mundial.
- Función de borde muy pequeña para búsqueda de fallos de caché: CloudFront Function o el cómputo de borde de Fastly para minimizar la sobrecarga de tiempo de ejecución (~sub-ms). Si la caché falla, la función de borde llama a una API de redirección regional a través de una conexión TCP corta (keepalive) y devuelve 301 a la CDN.
- Caché de lectura regional (Redis en cada región): la caché regional es un almacén de memoria primero para búsquedas de mapeo; GET de Redis típico <1 ms. Objetivo de tasa de aciertos de caché: >=99% para códigos activos. Utilice la expulsión LFU/LRU y el tamaño para mantener el conjunto de trabajo.
- Ejemplo de dimensionamiento de caché: suponga un RPS global máximo = 40k lecturas/segundo; conjunto de trabajo de los 50 millones de códigos principales (cola activa) — almacene pares short_code->long_url (promedio 200 bytes). Memoria = 50 millones * 200 B ≈ 10 GB. Un clúster Redis modesto multishard (por ejemplo, 4-8 nodos de 32 GB cada uno) por región puede manejar esto.
- Acceso a la base de datos de origen solo en caso de fallo de caché: DynamoDB GetItem de una sola fila suele ser de ms bajos (1-10 ms), pero diseñamos para evitar estar en la ruta crítica para p99 almacenando en caché de forma intensiva.
- Mantener la función de borde + ruta HTTP mínimas: utilice HTTP/2 o HTTP/3 entre la CDN y el origen para reducir la latencia de conexión y permitir la reutilización de conexiones.
- Enrutamiento Anycast local + consciente de la geografía: envíe al cliente a la región/borde más cercano para mantener baja la RTT.
Medición y SLA: pruebe con tráfico sintético y presupuestos de latencia del percentil 99 asignados: acierto de CDN (objetivo <5 ms), función de borde + Redis <10 ms, respaldo de origen aceptable para percentiles bajos pero se monitoreará y ajustará.
E. Ruta de escritura: creación y persistencia
- El cliente realiza POST /create (o a través de la UI) a la API de escritura (endpoint consciente de la región). La capa de la API de escritura no tiene estado y se escala automáticamente.
- La API de escritura obtiene un ID numérico de su bloque asignado localmente (asignador de rangos). Si el bloque se agota, solicita un nuevo bloque al asignador.
- Codifica el ID numérico a short_code base62.
- Persiste en la base de datos primaria con una inserción condicional: PutItem(short_code, long_url, metadata) con una expresión condicional de que short_code no existe (evita la sobrescritura accidental de alias personalizados). Asegura la escritura atómica para la durabilidad.
- Tras una escritura exitosa:
- Actualiza la caché de lectura regional (write-through) para que las redirecciones posteriores acierten en la caché.
- Envía una solicitud de precalentamiento de caché de CDN o publica una invalidación para este short_code en la CDN para que el nuevo mapeo se almacene en caché inmediatamente en los bordes (o aproveche el control de caché + versionado de la CDN para que sea efectivo).
- Devuelve el short_code creado al usuario.
Durabilidad y consistencia: escritura síncrona a la base de datos primaria con replicación entre regiones (tablas globales de DynamoDB o Cassandra con replicación multizona). Si se utiliza DynamoDB, el modelo de consistencia puede ser eventualmente consistente para las lecturas, pero las escrituras son duraderas y replicadas.
Números operativos: QPS de escritura promedio ~40 escrituras/segundo (100 millones/mes ≈ 38.6/s). Son posibles ráfagas máximas; el canal de escritura es fácil de escalar horizontalmente.
F. Estrategia de escalado (escalado horizontal)
- Servidores API / Redirección sin estado: escalado automático horizontal detrás de un balanceador de carga global (ALB / GCLB). Mantenga los servidores sin estado para que sean fáciles de escalar.
- Asignador de ID: QPS bajo: escale haciéndolo tolerante a fallos (activo/pasivo + contador persistido o asignación de rangos delegada). Asigne bloques más grandes para reducir la carga del asignador.
- Cachés: clústeres Redis/ElastiCache por región, fragmentados (hashing consistente). Agregue fragmentos para aumentar el rendimiento de la memoria.
- Base de datos primaria: escalado automático de DynamoDB o clúster Cassandra que puede agregar nodos y aumentar el rendimiento. Elija tamaños de instancia y factor de replicación para cumplir con la capacidad de lectura/escritura.
- Bus de mensajes (Kafka/Kinesis): particione el flujo de clics por hash de short_code para escalar la ingesta. Utilice suficientes particiones para cumplir con el rendimiento máximo (por ejemplo, si las redirecciones máximas son 38k RPS que generan 38k eventos/segundo, aprovisione particiones y brokers de Kafka para manejar ~50-100k eventos/s con un factor de replicación de 3).
- Cómputo de análisis: escale clústeres Flink / Spark horizontalmente según el volumen de eventos.
- La CDN escala automáticamente; la configuración de la CDN debe ajustarse a las tasas de solicitud.
G. Fiabilidad y tolerancia a fallos
Objetivos: sobrevivir a la caída de un centro de datos completo, garantizar la no pérdida de datos.
- Despliegue multirregión: despliegue al menos dos regiones activas (activo-activo) con enrutamiento global. Utilice balanceo de carga global + comprobaciones de estado para enrutar alrededor de regiones fallidas.
- Replicación de la base de datos primaria: las tablas globales de DynamoDB proporcionan replicación activo-activo entre regiones; Cassandra/Scylla se pueden configurar con un factor de replicación entre centros de datos. Esto proporciona durabilidad si se pierde un centro de datos.
- Cachés regionales en caliente ante fallos: cuando una región falla, el tráfico se enruta a la siguiente región, que tendrá su propia caché. El enfriamiento inicial de una región después de la conmutación por error puede generar más lecturas de origen hasta que las cachés se calienten, pero se preserva la disponibilidad.
- Tolerancia a fallos del asignador de ID: estado del asignador persistido en un almacén de alta disponibilidad; asigne bloques grandes para reducir la necesidad de acceder al asignador en caso de fallo.
- Replicación del bus de mensajes: Kafka con factor de replicación >=3 entre racks/regiones o Kinesis administrado con replicación entre regiones para durabilidad.
- Comprobaciones de estado y conmutación por error automatizada: monitoreo activo, disyuntores, limitación de velocidad para evitar sobrecargas durante la conmutación por error.
- Copias de seguridad: instantáneas periódicas de la base de datos primaria y exportaciones de metadatos. Para DynamoDB, habilite la recuperación en punto en el tiempo; para Cassandra, instantáneas programadas.
H. Canal de análisis (recopilar/procesar/servir sin afectar las redirecciones)
Principio de diseño: las escrituras de análisis deben ser asíncronas y nunca deben bloquear las redirecciones.
- Generación de eventos ligera: el borde (CDN/función de borde) emite un evento pequeño para cada redirección (short_code, timestamp, client_ip o etiqueta geo, referente, user-agent). Para minimizar la latencia de redirección, la emisión se realiza a través de un push muy rápido similar a UDP a un proxy local o mediante lotes en memoria en el servidor de lectura y se envía de forma asíncrona.
- Bus de mensajes: los eventos se envían a temas de Kafka o Kinesis particionados por short_code (o hash) para admitir el procesamiento paralelo escalable. El productor debe ser asíncrono y no bloqueante; si el búfer local de Kafka está lleno, recurra al muestreo o elimine campos de bajo valor para garantizar que la latencia de redirección no se vea afectada. Los productores utilizan almacenamiento en búfer local y políticas de contrapresión.
- Procesamiento de flujo: Flink / Kafka Streams / Spark Streaming consume eventos, los enriquece (búsqueda geo-IP, análisis de UA) y calcula agregados en tiempo real (recuentos de clics, distribución geográfica, referentes) en granularidad de minutos/horas. Preagregue por short_code por ventana de tiempo.
- Almacén OLAP y agregados: escriba datos agregados en ClickHouse o BigQuery para consultas analíticas rápidas y almacenamiento a largo plazo. Para servir paneles, almacene agregados recientes en un almacén de lectura rápida (Redis o Druid) para consultas interactivas.
- API del panel: lee solo de los almacenes de agregados/OLAP; sin consulta directa del almacén de análisis a la ruta de redirección. Implemente límites de velocidad y cuotas por usuario para las consultas del panel.
- Muestreo y registro por niveles: para short_codes de tráfico extremadamente alto, opcionalmente muestree eventos para reducir la carga del canal y preservar análisis representativos.
Ejemplo de rendimiento: redirecciones máximas ~40k/s → eventos 40k/s → ingesta de Kafka fácilmente manejable con 100 particiones y algunos brokers. Los procesadores de flujo escalan horizontalmente para manejar este volumen y escriben agregados resumidos cada minuto en ClickHouse.
I. Compromisos clave (al menos tres) con justificación
- Caché de borde de CDN vs consistencia inmediata para eliminaciones/actualizaciones
- Compromiso: Usar la caché de borde de CDN de larga duración produce una latencia de redirección muy baja, pero hace que la propagación de actualizaciones/eliminaciones sea un poco más compleja y no instantánea en todas partes.
- Justificación: El SLA de latencia de redirección es estricto (<10 ms p99). Las operaciones típicas para modificar/eliminar una URL corta son raras en comparación con las redirecciones. Preferimos una latencia ultrabaja para las redirecciones y aceptamos una invalidación de caché ligeramente retrasada (proporcionamos API de invalidación inmediata para casos importantes). También incluimos un token de versión o una estrategia de invalidación de TTL corta para actualizaciones críticas.
- Asignación de ID secuencial (bloques de rangos + base62) vs esquema de aleatorización/hash completo
- Compromiso: La asignación secuencial con bloques requiere un pequeño servicio asignador y un manejo cuidadoso durante la conmutación por error entre regiones, mientras que la generación aleatoria/hash puede ser completamente sin estado pero requiere resolución de colisiones o una longitud de código mayor para reducir las colisiones.
- Justificación: Los ID secuenciales producen códigos compactos y muy cortos de forma determinista y evitan el manejo de colisiones en el momento de la escritura. Con la asignación de bloques, la carga del asignador es mínima y escalable. Dado el espacio de claves extremadamente grande (62^7), no necesitamos aleatoriedad para evitar colisiones.
- DynamoDB (gestionado, multirregión) vs Cassandra autohospedado
- Compromiso: DynamoDB simplifica la carga operativa y proporciona replicación multirregión administrada, pero puede ser más costoso y algo menos flexible en los patrones de consulta en comparación con Cassandra/Scylla autohospedado.
- Justificación: El patrón de acceso del sistema es lectura/escritura simple de clave primaria; valoramos la simplicidad operativa, la fiabilidad y el escalado automático, por lo que recomendamos Tablas globales de DynamoDB, a menos que las restricciones de costos obliguen a Cassandra.
- Caché síncrona write-through vs propagación eventual a las cachés
- Compromiso: La escritura síncrona a las cachés aumenta ligeramente la latencia de escritura, pero garantiza la disponibilidad inmediata en las cachés de lectura; la propagación eventual reduce la latencia de escritura, pero puede haber una breve ventana en la que las redirecciones fallen.
- Justificación: Las escrituras tienen un QPS bajo (≈40/s), por lo que realizar una escritura en caché en el momento de la creación es aceptable para garantizar que los enlaces recién creados estén inmediatamente disponibles para las redirecciones y evitar fallos de caché en frío para los códigos cortos recién creados.
Consideraciones operativas y telemetría
- Monitoreo: latencia de redirección p99, relación de aciertos de caché, RPS de origen, latencia de escritura, retraso del bus de mensajes, retraso del canal de análisis. Alertas de retraso de replicación entre centros de datos.
- Planificación de capacidad: planificar con un margen de seguridad 10x del pico. Números de ejemplo:
- QPS de lectura (promedio): 3858 RPS = 10 mil millones/mes. Suposición de pico 10x => ~38.6k RPS.
- Objetivo de acierto de borde de CDN: 99% → RPS de origen ≈ 386 RPS. Cachés regionales dimensionadas para mantener el conjunto de trabajo activo.
- Dimensionamiento de caché: para mantener las 50 millones de claves principales a 200 B cada una → 10 GB; usar múltiples fragmentos por región.
- Rendimiento de Kafka: a un pico de 40k eventos/s, con un tamaño de evento promedio de 500 B => ~20 MB/s de ingesta; con un factor de replicación de 3 y sobrecarga, planificar ~60 MB/s de rendimiento del clúster.
- Rendimiento de la base de datos primaria: escrituras ~40/s, lecturas de origen después de fallos de caché ~386/s agregadas (o divisiones por región). El escalado automático de capacidad de DynamoDB admite fácilmente estos niveles.
Seguridad y características operativas
- Limitación de velocidad y detección de abusos en el borde para mitigar spam o bots.
- Limitación de escrituras de análisis durante sobrecargas, muestreo para códigos extremadamente activos.
- Controles de acceso (OAuth/tokens API) para crear y administrar URL cortas.
- Registros de auditoría para eliminaciones para cumplir con la garantía de no caducidad a menos que se eliminen explícitamente.
Conclusión
Este diseño prioriza la latencia de redirección y la disponibilidad a través de la caché de borde de CDN + lógica de borde mínima, almacenamiento multirregión duradero para mapeos permanentes, un esquema de asignación de ID y codificación base62 simple y robusto (que garantiza cero colisiones y <=7 caracteres), y un canal de análisis completamente asíncrono para que los análisis nunca degraden el rendimiento de la redirección. La arquitectura escala horizontalmente, sobrevive a interrupciones completas del centro de datos a través de la replicación multirregión y proporciona palancas operativas (dimensionamiento de caché, invalidación de CDN, muestreo) para equilibrar costos frente a rendimiento.