Módulo 8: Proyecto — diseña Enlace de punta a punta
4. Paso 3 — Diseño de alto nivel y el diagrama
Descripción
Con el contrato (paso 1) y la tabla de capacidad (paso 2) en la mano, llegó por fin el momento de dibujar: el paso 3 del marco, el diseño de alto nivel, y su artefacto estrella, el diagrama. En el módulo 1 dibujaste Enlace en una sola caja —un servidor y una base de datos— y mediste, con honestidad, sus cuatro grietas: la lectura apretada, el almacenamiento que se llena, el punto único de fallo. Ahora esas grietas están tapadas por lo que aprendiste en los módulos 4 a 7, y el diagrama del capstone las incorpora todas: el balanceador, los servidores sin estado, la caché, el primary con réplicas, el sharding. El resultado es la arquitectura distribuida completa de Enlace, en un solo diagrama mermaid, donde —y esto es la disciplina de la lección— cada caja señala la fila de la tabla de capacidad que la justifica.
En esta lección construyes ese diagrama de forma incremental: partes de la caja única, y agregas cada componente nombrando el número que lo exige, para que veas que el diseño no se copia de un patrón sino que se deriva de la estimación. Vas a entender por qué el balanceador está ahí (el cómputo escala), por qué la caché va delante de la base de datos (las 4,000 lecturas/s), por qué la base de datos es un primary con réplicas sharded (los 6 TB y el ratio 100:1), y —crucial— por qué el diagrama sigue siendo lo más simple que cumple los requisitos, sin una caja de más. Es el plano de Enlace, el que las lecciones 5, 6 y 7 van a profundizar.
Conexión con el módulo: esta es la bisagra del recorrido, entre la estimación (lección 3) y el profundizar (lecciones 5–7). Toma cada señal de la tabla de capacidad y la convierte en una caja del diagrama; y el diagrama, a su vez, se convierte en el mapa de lo que hay que profundizar: cada componente que dibujas aquí es una de las lecciones que siguen. Es el paso 3 aplicado a Enlace a plena escala, la versión "crecida" de la caja única del módulo 1.
El plano de la casa, con todas sus instalaciones
Piénsalo así. Un arquitecto no dibuja un solo plano de la casa; dibuja varias capas superpuestas sobre la misma planta. La primera capa son los muros: dónde van las habitaciones, las puertas, las ventanas —la estructura—. Pero encima van otras: la instalación eléctrica (dónde los enchufes, el tablero, los cables), la de plomería (las tuberías de agua que entran, las de desagüe que salen), la de clima. Cada instalación resuelve una necesidad distinta —los muros dan la forma, la eléctrica da la luz, la plomería da el agua— y todas comparten la misma planta. El plano de alto nivel las muestra juntas, en una vista, para que se entienda cómo colabora todo: dónde entra el agua, por dónde sale, dónde está el tablero que alimenta cada cuarto.
Fíjate en dos cosas de ese plano. Primera: cada instalación está ahí porque una necesidad la pide, no por decoración. No hay una tubería que no lleve agua a ningún lado; no hay un cable que no alimente nada. Si el arquitecto dibujara una tubería "por si acaso", el plomero le preguntaría "¿y esta a dónde va?", y no habría respuesta. Segunda: el plano es legible —muestra el flujo—. Puedes seguir con el dedo cómo el agua entra por la calle, pasa por el medidor, sube al tanque, y baja a cada llave. El plano no es un montón de símbolos; es una historia de cómo fluye cada cosa por la casa.
El diagrama de alto nivel de un sistema es ese plano con todas sus instalaciones. Los servidores, la caché, la base de datos, el balanceador son las "instalaciones" de Enlace, y cada una está ahí porque un número de la tabla la pide —el balanceador porque el cómputo escala, la caché porque las lecturas son 4,000/s, las réplicas porque hay que repartirlas—. Y el diagrama es legible: muestra cómo fluye una petición de escritura (entra por el balanceador, la sirve un servidor, la escribe el primary) y una de lectura (entra, la sirve un servidor, la busca primero en la caché, y solo si falla, en una réplica). Igual que el plano de la casa, cada caja tiene su justificación y el conjunto cuenta la historia de cómo fluyen los dos caminos de Enlace.
Conviene decirlo con todas las letras:
El diseño de alto nivel es el plano del sistema: muestra todos los componentes juntos y cómo fluyen las peticiones entre ellos. Cada componente está ahí porque un número del paso 2 lo exige —ni una caja de más—, y el conjunto es legible: se puede seguir con el dedo el camino de una escritura y el de una lectura. Es lo más simple que cumple los requisitos a la escala estimada.
Del diseño simple al distribuido, una caja a la vez
La mejor forma de entender el diagrama final es construirlo, agregando cada componente con el número que lo justifica —así ves que nada se copia, todo se deriva—. Partimos de la caja única del módulo 1 y crecemos.
Punto de partida: la caja única. Un servidor y una base de datos. Cumple los funcionales, pero la tabla de capacidad marcó sus grietas: la lectura apretada, los 6 TB que no caben, el punto único de fallo.
graph LR
C[Cliente / Navegador] -->|HTTP| S[Servidor Enlace]
S -->|SQL| DB[(Base de datos)]
Agregamos la caché (señal: ~4,000 lecturas/s + latencia <100 ms). La fila de lectura de la tabla dice "cuello de botella". La caché absorbe el 90% de las lecturas desde RAM (5.90 ms de latencia media), y deja a la base de datos solo 386/s. Va delante de la base de datos, en el camino de lectura.
Agregamos primary + réplicas y sharding (señal: 6 TB + ratio 100:1). La fila de almacenamiento dice "no cabe en una máquina": se shardea por short_code (con consistent hashing) para repartir los 6 TB. Y como las lecturas son 100× las escrituras, cada shard es un primary (acepta las ~40 escrituras/s repartidas) con réplicas de lectura (sirven las lecturas que la caché no absorbe).
Agregamos el balanceador + servidores sin estado (señal: el cómputo también escala). Un solo servidor de aplicación es, él también, un punto único de fallo y un cuello. Se ponen varios servidores sin estado (cualquiera atiende cualquier petición, porque el estado vive en la caché y la base de datos, no en el servidor) detrás de un balanceador que reparte el tráfico y saca de rotación al que falle (health checks). Esto tapa la última grieta de la caja única: el punto único de fallo del cómputo.
Puestas todas juntas, esta es la arquitectura distribuida completa de Enlace —el diagrama que va al entregable—:
graph TD
C[Cliente / Navegador]
C -->|HTTP| LB{{Balanceador<br/>round-robin + health checks}}
LB --> S1[Servidor Enlace 1<br/>sin estado]
LB --> S2[Servidor Enlace 2<br/>sin estado]
LB --> S3[Servidor Enlace 3<br/>sin estado]
S1 -->|lectura: 1o consulta| CACHE[(Cache Redis<br/>~333 MB working set<br/>hit ratio 0.90)]
S2 --> CACHE
S3 --> CACHE
CACHE -.miss 10%.-> ROUTER[Router de shards<br/>consistent hashing<br/>por short_code]
S1 -->|escritura| ROUTER
S2 --> ROUTER
S3 --> ROUTER
ROUTER --> P0[(Shard 0<br/>PRIMARY)]
ROUTER --> P1[(Shard 1<br/>PRIMARY)]
P0 -->|replication log| R0a[(replica)]
P0 --> R0b[(replica)]
P1 --> R1a[(replica)]
P1 --> R1b[(replica)]
Qué esperar. Ese diagrama es Enlace entero, a plena escala, en una vista. Léelo siguiendo los dos caminos con el dedo, como el plano de la casa:
- Camino de lectura (
resolve, ~4,000/s): el cliente llega al balanceador, que lo manda a uno de los servidores sin estado (cualquiera sirve). El servidor consulta primero la caché: el 90% de las veces (hit) lalong_urlestá ahí y responde en ~1 ms —fin del camino, ni toca la base de datos—. El 10% restante (miss) va al router de shards, que con consistent hashing sobre elshort_codesabe a qué shard pertenece, y lee de una réplica de ese shard. - Camino de escritura (
shorten, ~40/s): el cliente llega al balanceador → un servidor sin estado → el router de shards → el primary del shard que le toca al nuevoshort_code. El primary copia el cambio a sus réplicas por el replication log. Una sola escritura, al primary del shard correcto.
Fíjate en la asimetría que la tabla de capacidad predijo: muchas flechas de lectura que en su mayoría mueren en la caché (el 90%), y pocas flechas de escritura que van directas al primary. El diagrama dibuja el ratio 100:1 y la lectura pesada. Y fíjate en que cada componente tiene su número: la caché (~333 MB, hit 0.90), los shards (6 TB repartidos), los servidores (sin estado, para escalar el cómputo). No hay una sola caja sin justificación en la tabla —ni un motor de búsqueda, ni una cola, ni un microservicio de más—.
Por qué el estado vive fuera del servidor
Hay una decisión en ese diagrama que merece detenerse, porque es la que hace posible el balanceo: los servidores son sin estado (stateless). Significa que un servidor de Enlace no guarda nada propio entre peticiones —ni sesiones, ni datos de usuario, ni la última consulta—. Todo el estado vive en dos lugares compartidos: la caché (el working set caliente) y la base de datos (los 6 TB). El servidor es pura lógica: recibe una petición, consulta la caché o la base de datos, responde, y olvida.
¿Por qué importa? Porque si el estado viviera dentro de cada servidor, el balanceador no podría mandar la siguiente petición a cualquiera —tendría que mandarla al mismo servidor que atendió la anterior (sesiones "pegajosas", sticky)—, y si ese servidor se cae, el estado se pierde y el usuario lo nota. Con servidores sin estado, en cambio, cualquier servidor atiende cualquier petición: el balanceador reparte con libertad, agregar capacidad es solo enchufar otro servidor idéntico, y si uno se cae, el health check lo saca de rotación y los demás siguen —sin que ningún usuario pierda nada—. La ausencia de estado en el servidor es, exactamente, lo que hace que el cómputo escale horizontal y tolere fallos. Es una decisión de diseño con un número detrás: para servir ~4,000 lecturas/s con redundancia, necesitas varios servidores, y "varios servidores" solo funciona bien si son intercambiables, o sea, sin estado.
Por qué este diagrama, y no uno más complejo (ni más simple)
El diagrama de Enlace es exactamente tan complejo como los números exigen —ni más, ni menos—, y vale la pena defender esa frontera en las dos direcciones.
Por qué no más simple. No podríamos entregar la caja única del módulo 1, porque la tabla de capacidad la rompe: 6 TB no caben en una máquina (necesitas sharding), 4,000 lecturas/s sobre una sola BD la saturan (necesitas caché y réplicas), y una sola caja no cumple 99.9% de disponibilidad (necesitas redundancia y balanceo). Cada caja que agregamos tapa una grieta medida. Quitar cualquiera reabre una grieta que la tabla ya identificó.
Por qué no más complejo. Tampoco metemos colas de mensajes (la analítica está fuera del alcance —paso 1—, y las colas son de la guía de eventos), ni microservicios (dos operaciones sobre ~40 escrituras/s no piden partir el servicio; ese debate es de la guía de estilos), ni un motor de búsqueda (Enlace busca por clave primaria, no por texto), ni circuit breakers y bulkheads a fondo (patrones de resiliencia, guía hermana). Cada una de esas piezas o no tiene número que la justifique, o pertenece a otra guía del ecosistema. Meterlas sería el diseño inflado del ejercicio 3 de la lección 1.
La regla, otra vez, es la del oficio: el diseño más simple que cumple los requisitos a la escala estimada gana. El diagrama de Enlace es la encarnación de esa regla —cinco tipos de componente (balanceador, servidores sin estado, caché, primaries, réplicas), cada uno con su número, y ni uno de más—. Esa disciplina es lo que separa un diseño de ingeniería de una colección de componentes impresionantes.
Errores comunes
Dibujar componentes sin el número que los justifica. Qué pasa: alguien dibuja el diagrama completo —balanceador, caché, réplicas, shards— de memoria, porque "así es la arquitectura del acortador", sin poder señalar qué fila de la tabla exige cada caja. El diseño es correcto de casualidad, no derivado. Se nota cuando el enunciado cambia: si Enlace fuera de intranet (0.6 lecturas/s), el mismo diagrama estaría sobre-construido por un factor de mil, y quien lo copió no lo notaría. Por qué pasa: es más fácil memorizar el diagrama canónico que reconstruir por qué es ese. Cómo detectarlo: por cada caja, pregúntate "¿qué número la exige?". Si no tienes respuesta, la caja está sin justificar. Cómo corregirlo: construye el diagrama incrementalmente, agregando cada componente con su señal —"caché porque 4,000 lecturas/s", "sharding porque 6 TB"—. Un diagrama derivado cambia con los números; uno copiado, no.
Poner el estado dentro de los servidores (sesiones pegajosas por defecto). Qué pasa: alguien dibuja varios servidores detrás de un balanceador pero guarda estado en cada uno (sesión de usuario, caché local, la última consulta), y para que funcione tiene que usar sesiones pegajosas —cada usuario siempre al mismo servidor—. Cuando ese servidor se cae, el usuario pierde su estado; y agregar capacidad no ayuda a los usuarios ya "pegados" a un servidor lleno. El balanceo deja de balancear de verdad. Por qué pasa: guardar estado en el servidor es lo intuitivo (es donde corre el código). Cómo detectarlo: si tu balanceador necesita mandar a cada usuario siempre al mismo servidor, tus servidores tienen estado. Cómo corregirlo: saca el estado del servidor y ponlo en un lugar compartido (caché + base de datos). Servidores sin estado = cualquiera atiende a cualquiera = balanceo libre, capacidad elástica y tolerancia a fallos. Es la decisión que hace posible el resto del diagrama.
Dibujar la base de datos como una sola caja "porque cabe en el diagrama". Qué pasa: alguien, por comodidad de dibujo, pone la base de datos como un único cilindro, aunque la tabla dijo 6 TB (sharding) y ratio 100:1 (réplicas). El diagrama esconde las dos decisiones de escala más importantes de Enlace bajo un solo símbolo, y el "profundizar" posterior no tiene dónde apoyarse. Por qué pasa: dibujar un cilindro es más fácil que dibujar primary + réplicas × shards. Cómo detectarlo: si tu base de datos es una sola caja pero tu tabla dice 6 TB y lectura pesada, el diagrama contradice la estimación. Cómo corregirlo: el diagrama de alto nivel debe mostrar las decisiones de escala que los números exigen —el primary por shard, las réplicas de lectura, el router de consistent hashing—, aunque sea de forma esquemática (dos shards representan "N shards"). El diagrama es el plano; si esconde la plomería, no sirve para construir.
Ejercicios
Ejercicio 1 — Justifica cada caja. Para cada componente del diagrama de Enlace, di qué fila de la tabla de capacidad (lección 3) lo justifica, en una frase: (a) la caché; (b) el sharding de la base de datos; (c) las réplicas de lectura; (d) los varios servidores sin estado detrás del balanceador; (e) el router de consistent hashing.
Ver solución
- (a) La caché: la fila ~4,000 lecturas/s + latencia <100 ms. Es de lectura pesada; la caché absorbe el 90% desde RAM (5.90 ms) y descarga la base de datos.
- (b) El sharding: la fila ~6 TB a 5 años. No cabe cómodo en una máquina; se reparten los datos entre shards.
- (c) Las réplicas de lectura: la fila ratio 100:1 (lectura pesada). Las lecturas que la caché no absorbe (386/s, más en el pico) se reparten entre réplicas; un solo nodo por shard no da abasto y no da redundancia.
- (d) Los servidores sin estado + balanceador: implícitamente, la fila de lecturas/s + disponibilidad 99.9%. Un solo servidor de aplicación es cuello y punto único de fallo; varios sin estado detrás de un balanceador escalan el cómputo y toleran fallos.
- (e) El router de consistent hashing: deriva del sharding + la necesidad de crecer (M5): reparte las claves entre shards y permite agregar uno moviendo solo ~1/(N+1) de los datos (13.1%, no 88.9%).
La lección: cada caja del diagrama tiene, detrás, una fila de la tabla. Un diagrama sin esta correspondencia es decoración; con ella, es un diseño derivado.
Ejercicio 2 — Dibuja el diagrama del Enlace de intranet. Un cliente quiere Enlace para su intranet: 1,000 URLs nuevas al mes, mismo ratio 100:1. Estima rápido las escrituras/s y lecturas/s, y dibuja (en ASCII o describiendo) el diagrama de alto nivel apropiado. ¿Cuántas de las cajas del diagrama grande sobreviven, y por qué?
Ver solución
Estimación rápida: 1,000 / (30·24·3600) ≈ 0.0004 escrituras/s; lecturas = × 100 ≈ 0.04 lecturas/s. Es decir, una lectura cada ~25 segundos. Almacenamiento a 5 años: 1,000 × 12 × 5 × 1 KB ≈ 60 MB.
El diagrama apropiado es la caja única del módulo 1:
Cliente --HTTP--> Servidor Enlace --SQL--> Base de datos
Casi ninguna caja del diagrama grande sobrevive, y por una razón: los números no las exigen. 0.04 lecturas/s no necesitan caché (una sola BD las sirve dormida), ni réplicas (no hay carga que repartir), ni sharding (60 MB caben mil veces en una máquina), ni balanceo (un servidor sobra para 0.04 peticiones/s). Meter cualquiera de esos componentes sería sobre-ingeniería por un factor de miles. Quizá sobreviva una réplica —no por carga, sino por redundancia si la intranet exige 99.9%—, pero ni eso es obligatorio.
La lección, que es la del ejercicio 3 de la lección 1 vista al derecho: el mismo problema, con otros números, es otro diseño. El diagrama de Enlace no es "el diagrama del acortador"; es el diagrama de este acortador, a esta escala. Cambia los números y cambia el diseño. Por eso el paso 2 va antes del paso 3: la tabla decide cuántas cajas.
Ejercicio 3 — Detecta la caja que sobra y la que falta. Un compañero dibuja este diagrama de Enlace: cliente → balanceador → tres servidores sin estado → una cola de mensajes Kafka → un único servidor de base de datos (sin réplicas, sin shards). (a) ¿Qué componente sobra y por qué (qué número no lo justifica y a qué guía pertenece)? (b) ¿Qué le falta y por qué (qué número lo exige)? (c) Corrige el diseño en una frase.
Ver solución
- (a) Sobra la cola de Kafka. Ningún número de Enlace la justifica: las escrituras son ~40/s (un primary las absorbe directo, sin necesidad de encolarlas) y la analítica —que sí podría usar una cola— está fuera del alcance (paso 1). Además, las colas de mensajes son territorio de
event-driven-architecture-guide, no de esta guía. Es una caja de más, del diseño inflado. - (b) Falta la caché y falta el escalado de la base de datos (réplicas + sharding). La caché la exige la fila ~4,000 lecturas/s + latencia <100 ms (sin caché, las 4,000 lecturas caen todas sobre la BD). El sharding lo exige la fila 6 TB (no caben en un solo nodo), y las réplicas, la fila ratio 100:1 (hay que repartir las lecturas). Su "único servidor de base de datos" reabre tres grietas que los números ya cerraron.
- (c) Corrección: quita la cola de Kafka, mete una caché delante de la base de datos, y convierte el "único servidor de BD" en primaries sharded (por
short_code, consistent hashing) con réplicas de lectura.
La lección: un diagrama se evalúa en las dos direcciones —lo que sobra (sin número que lo justifique, o de otra guía) y lo que falta (un número lo exige y no está)—. El compañero metió complejidad donde no hacía falta (Kafka) y la escatimó donde sí (caché, réplicas, sharding). El diseño correcto de Enlace es humilde donde los números lo permiten y robusto donde los números lo exigen.
Resumen y siguiente paso
En esta lección ejecutaste el paso 3 del marco: el diseño de alto nivel de Enlace y su diagrama mermaid, el tercer artefacto del entregable. Lo construiste incrementalmente, partiendo de la caja única del módulo 1 y agregando cada componente con el número que lo justifica: la caché (por las ~4,000 lecturas/s), el sharding por short_code (por los 6 TB), las réplicas de lectura (por el ratio 100:1), y los servidores sin estado detrás del balanceador (para escalar el cómputo y cumplir 99.9%). El diagrama final dibuja la asimetría que la tabla predijo —muchas lecturas que mueren en la caché, pocas escrituras al primary— y es legible como un plano: se siguen con el dedo los dos caminos.
Viste por qué el estado vive fuera del servidor (servidores sin estado = balanceo libre, capacidad elástica, tolerancia a fallos), y por qué el diagrama es exactamente tan complejo como los números exigen —sin colas ni microservicios ni motor de búsqueda de más (frontera con las guías hermanas), sin esconder la base de datos en un solo cilindro—.
Antes de avanzar deberías poder: construir el diagrama de Enlace agregando cada caja con su justificación numérica; explicar por qué los servidores son sin estado; seguir con el dedo el camino de lectura y el de escritura; y detectar en un diagrama la caja que sobra y la que falta.
Lo que sigue es el paso 4: profundizar. Con el plano dibujado, bajamos al detalle de cada componente crítico, empezando por el camino que el diagrama muestra corto y tranquilo. En la lección 5 profundizas en el camino de escritura: cómo shorten genera el short_code (contador + base62, y por qué 62⁷ alcanza para 6,000 M), a qué shard va la escritura, y por qué las ~40 escrituras/s hacen de este el camino fácil de Enlace. Es el primer "profundizar" del capstone.
Recursos
- System Design Primer — "Step 2: Create a high level design" — el paso de dibujar la arquitectura con sus componentes principales y las conexiones entre ellos, exactamente lo que hicimos aquí. Su ejemplo del acortador termina en un diagrama muy parecido al de Enlace (balanceador, servidores web, caché, base de datos).
- Mermaid — documentación de diagramas de flujo (
flowchart/graph) — la sintaxis con la que está escrito el diagrama de esta lección. Útil si quieres reproducir o modificar el plano de Enlace: nodos, aristas, formas (cilindro para bases de datos, rombo para el balanceador). - Designing Data-Intensive Applications, Kleppmann — Capítulo 1, sobre escalabilidad y componentes sin estado — el fundamento de por qué los servicios sin estado escalan horizontalmente y por qué el estado se empuja a las capas de datos (caché, base de datos). El respaldo teórico de la decisión "el estado vive fuera del servidor".