El arte de diseñar sistemas que escalan
Imagina que eres el arquitecto de un restaurante. No solo decides donde van las mesas, sino como fluye la cocina, cuantos cocineros necesitas, donde almacenas los ingredientes, y que pasa cuando llegan 500 clientes en vez de 50.
El diseño de sistemas es exactamente eso: planificar como construir software que funcione bien hoy y pueda crecer mañana.
Un buen diseño de sistema no es el mas complejo, sino el que resuelve el problema actual con espacio para crecer.
Monolito vs Microservicios
La primera decision arquitectonica que enfrentaras.
Monolito: Todo en uno
┌─────────────────────────────────────┐
│ APLICACION │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │Auth │ │Users│ │Orders│ │Pay │ │
│ └─────┘ └─────┘ └─────┘ └─────┘ │
│ Una base de datos │
│ ┌───┐ │
│ │ DB│ │
│ └───┘ │
└─────────────────────────────────────┘
Ventajas:
- Simple de desarrollar y desplegar
- Facil de debuggear (todo en un lugar)
- Una sola base de datos = consistencia
- Ideal para equipos pequenos (<10 devs)
Desventajas:
- Escala todo o nada
- Un bug puede tumbar todo
- Deployments arriesgados
- Dificil de mantener cuando crece
Microservicios: Divide y conquista
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Auth │ │ Users │ │ Orders │
│ Service │ │ Service │ │ Service │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│Auth DB │ │Users DB │ │Orders DB│
└─────────┘ └─────────┘ └─────────┘
Ventajas:
- Escala solo lo que necesitas
- Equipos independientes
- Falla un servicio, no todo
- Tecnologias diferentes por servicio
Desventajas:
- Complejidad operacional alta
- Debugging distribuido es dificil
- Consistencia eventual (no inmediata)
- Requiere DevOps maduros
Cuando usar cada uno
| Escenario | Recomendacion |
|---|---|
| Startup, MVP, < 5 devs | Monolito |
| Producto probado, > 20 devs | Microservicios |
| Partes con cargas muy diferentes | Hibrido |
| No sabes cual elegir | Monolito |
Regla de oro: Empieza con monolito. Extrae microservicios cuando el dolor sea real, no imaginado.
El Teorema CAP
En sistemas distribuidos, solo puedes tener 2 de 3:
Consistency
/\
/ \
/ \
/ \
/ ?? \
/ \
/____________\
Availability Partition
Tolerance
- Consistency (C): Todos ven los mismos datos al mismo tiempo
- Availability (A): El sistema siempre responde
- Partition Tolerance (P): Funciona aunque haya fallas de red
En la practica
Las particiones de red SIEMPRE pueden ocurrir. Entonces realmente eliges entre:
| Sistema | Elige | Sacrifica | Ejemplo |
|---|---|---|---|
| CP | Consistencia | Disponibilidad | Bancos, inventarios |
| AP | Disponibilidad | Consistencia | Redes sociales, cache |
Ejemplo real: En un banco, si hay falla de red, prefieres que el cajero diga "No disponible" (CP) a que te deje retirar dinero que no tienes (AP).
Escalamiento: Vertical vs Horizontal
Vertical: Maquina mas grande
Antes: Despues:
┌─────┐ ┌─────────┐
│ 4GB │ → │ 64GB │
│ 2CPU│ │ 32CPU │
└─────┘ └─────────┘
- Simple: solo cambias el servidor
- Tiene limite fisico
- Un solo punto de falla
Horizontal: Mas maquinas
Antes: Despues:
┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
│ 4GB │ → │ 4GB │ │ 4GB │ │ 4GB │
└─────┘ └─────┘ └─────┘ └─────┘
- Teoricamente infinito
- Requiere Load Balancer
- Tu app debe ser stateless
Load Balancers
Distribuyen trafico entre multiples servidores.
┌─────────┐
│ Load │
Usuarios → │Balancer │
└────┬────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│Server 1 │ │Server 2 │ │Server 3 │
└─────────┘ └─────────┘ └─────────┘
Algoritmos de distribucion
| Algoritmo | Como funciona | Cuando usarlo |
|---|---|---|
| Round Robin | 1, 2, 3, 1, 2, 3... | Servidores iguales |
| Least Connections | Al que tenga menos | Conexiones largas |
| IP Hash | Mismo cliente → mismo server | Sesiones sticky |
| Weighted | Mas al mas potente | Servidores diferentes |
Escalando Bases de Datos
Replicacion: Copias de lectura
Writes
│
▼
┌─────────┐
│ Primary │──────────────┐
│ (RW) │ │ Replicacion
└─────────┘ │
│ │
▼ ▼
┌─────────┐ ┌─────────┐
│ Replica │ │ Replica │
│ (RO) │ │ (RO) │
└─────────┘ └─────────┘
▲ ▲
│ │
Reads Reads
- Escala lecturas, no escrituras
- Consistencia eventual (retraso de replicacion)
Sharding: Divide los datos
user_id 1-1000 user_id 1001-2000 user_id 2001-3000
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Shard 1 │ │ Shard 2 │ │ Shard 3 │
└─────────┘ └─────────┘ └─────────┘
- Escala tanto lecturas como escrituras
- Complejidad: JOINs entre shards son costosos
- Elegir buena shard key es critico
Caching: La clave del performance
Estrategias de cache
Cache-Aside (Lazy Loading)
1. App pide dato
2. Cache miss? → Lee de DB → Guarda en cache
3. Cache hit? → Retorna de cache
┌─────┐ miss ┌─────┐ ┌────┐
│ App │ ──────→ │Cache│ │ DB │
│ │ ←────── │ │ │ │
└─────┘ hit └─────┘ └────┘
│ ▲
└──────────────────────────────┘
miss: lee y guarda
Write-Through
Escribe en cache Y en DB al mismo tiempo
- Datos siempre consistentes
- Escrituras mas lentas
Write-Behind (Write-Back)
Escribe en cache, luego async a DB
- Escrituras rapidas
- Riesgo de perder datos si cache falla
Que cachear
| Candidato | Prioridad |
|---|---|
| Datos que no cambian (config) | Alta |
| Datos leidos frecuentemente | Alta |
| Resultados de calculos costosos | Alta |
| Datos de usuario activo | Media |
| Datos que cambian cada segundo | Baja |
Message Queues
Para comunicacion asincrona entre servicios.
┌─────────┐ ┌─────────────┐ ┌─────────────┐
│Producer │ ──→ │ Queue │ ──→ │ Consumer │
│ (API) │ │ (RabbitMQ) │ │ (Worker) │
└─────────┘ └─────────────┘ └─────────────┘
Casos de uso
- Envio de emails: API encola, worker envia
- Procesamiento de imagenes: Upload encola, worker procesa
- Notificaciones: Evento encola, multiples consumers notifican
Herramientas populares
| Tool | Mejor para |
|---|---|
| RabbitMQ | Mensajeria tradicional, routing complejo |
| Redis Streams | Simple, ya tienes Redis |
| Kafka | Alto volumen, event sourcing |
| SQS | AWS nativo, simple |
Caso practico: Disenando un URL Shortener
Requisitos
Funcionales:
- Acortar URL larga → codigo corto
- Redirigir codigo → URL original
- URLs expiran (opcional)
No funcionales:
- 100M URLs nuevas/mes
- 10:1 ratio lectura:escritura
- Latencia < 100ms
Estimaciones
URLs/mes: 100M
URLs/seg: 100M / (30 * 24 * 3600) ≈ 40 URLs/seg escritura
Lecturas: 40 * 10 = 400 URLs/seg lectura
Storage (5 años):
100M * 12 * 5 = 6B URLs
6B * 500 bytes = 3TB
Diseno del codigo corto
Base62: [a-zA-Z0-9] = 62 caracteres
7 caracteres = 62^7 = 3.5 trillones de combinaciones
Suficiente para 100M/mes por siglos
Arquitectura final
┌───────────┐
Usuarios ───→ │ LB │
└─────┬─────┘
│
┌───────────┴───────────┐
▼ ▼
┌─────────┐ ┌─────────┐
│ API 1 │ │ API 2 │
└────┬────┘ └────┬────┘
│ │
└───────────┬───────────┘
│
┌─────┴─────┐
│ Redis │ (Cache hot URLs)
└─────┬─────┘
│
┌─────┴─────┐
│ Postgres │ (Sharded by hash)
└───────────┘
Recursos recomendados
- Libro: "Designing Data-Intensive Applications" - Martin Kleppmann
- Libro: "System Design Interview" - Alex Xu
- Web: system-design-primer
- Practica: Exercism System Design
Practica
-> Workshop de Arquitectura - Disena un sistema real paso a paso