Módulo 1: Cómo abordar un problema de system design

6. Enlace en una sola caja

Descripción

Llegó el momento que estuvimos posponiendo a propósito durante cinco lecciones: dibujar. Con los requisitos claros (lecciones 2–4) y la estimación corrida (lección 5), por fin ejecutamos el paso 3 del marco —el diseño de alto nivel— y le damos a Enlace su primera forma. Y esa forma es, deliberadamente, la más humilde posible: una sola caja. Un servidor de aplicación y una base de datos, y nada más. Sin caché, sin réplicas, sin balanceador, sin microservicios. No porque no sepamos que Enlace acabará necesitando todo eso —los siete módulos siguientes lo van a añadir—, sino porque el diseño correcto empieza simple y crece solo cuando un número lo obliga. Empezar con la arquitectura más compleja que se te ocurre es el error de novato; empezar con la más simple que cumple los requisitos, y complicarla bajo presión numérica, es el oficio.

En esta lección vas a dibujar los dos caminos que recorre Enlace —el de escritura (shorten: llega una URL larga, sale un código corto) y el de lectura (resolve: llega un código, sale una redirección)— sobre la caja única. Vas a ver, con números de orden de magnitud, qué aguanta una sola caja y qué no: que las ~40 escrituras/s le sobran, que las ~4000 lecturas/s le caben pero sin gran margen, que el disco se le llena en unos meses, y que —lo más importante— si esa única caja se cae, Enlace entero desaparece. Esas grietas no son un defecto del diseño: son el mapa de lo que arreglan los módulos 4 a 7. La lección 7 las va a nombrar una por una; esta las hace visibles dibujando la caja y viendo dónde cruje.

Conexión con el módulo: esta es la lección donde el método produce, por fin, un diseño. Es el paso 3 aplicado a Enlace, apoyado en los requisitos (paso 1) y la estimación (paso 2) de las lecciones anteriores. Y es la antesala de la lección 7, que toma esta caja única y señala exactamente dónde se rompe —cada grieta mapeada a un módulo futuro—. El mini-proyecto (lección 8) te pedirá dibujar tu propia caja única para un sistema nuevo. Aquí aprendes a hacerlo bien: simple primero, con los dos caminos claros, y con honestidad sobre sus límites.

La tienda de la esquina

Piénsalo así. Abres una tienda de barrio. No arrancas construyendo un supermercado con veinte cajas registradoras, tres almacenes y una flota de camiones —te arruinarías antes de vender el primer chicle—. Arrancas con lo mínimo que funciona: un mostrador donde atiendes a la gente y una trastienda donde guardas la mercancía. Tú, detrás del mostrador, haces las dos cosas: cuando alguien quiere comprar, buscas el producto en la trastienda y se lo das (eso es leer); cuando llega mercancía nueva, la acomodas en la trastienda (eso es escribir). Un mostrador, una trastienda, una persona. Y funciona —de verdad funciona— mientras el barrio sea pequeño y la fila corta.

Fíjate en por qué es la decisión correcta para empezar: con pocos clientes, esa tienda los atiende a todos sin problema, costó casi nada montarla, y es facilísima de entender y operar. Sería absurdo montar el supermercado de veinte cajas para vender a las diez personas que pasan al día. La simplicidad no es una limitación aquí; es la respuesta adecuada al tamaño del problema. El error sería el contrario: sobredimensionar desde el día uno "por si el barrio crece".

Pero la tienda de una persona tiene límites que ya puedes anticipar, y son exactamente los que va a tener la caja única de Enlace. Si el barrio crece y llegan cien clientes a la vez, una sola persona en un solo mostrador no da abasto: la fila se hace eterna (el mostrador se satura). Si la trastienda se llena, no cabe más mercancía (el almacenamiento se agota). Y —el más grave— si tú, la única persona, te enfermas un día, la tienda no abre: no hay nadie más que atienda (un único punto de fallo). Ninguno de esos problemas existe cuando el barrio es pequeño; todos aparecen al crecer. Y para cada uno hay una solución que la tienda irá adoptando: contratar más cajeros (más servidores + balanceador), abrir una segunda trastienda (sharding), tener un cajero de respaldo (redundancia).

Enlace en una sola caja es esa tienda de una persona. Un servidor (el mostrador, que atiende peticiones) y una base de datos (la trastienda, que guarda los Link). Hace las dos operaciones —escribir al crear un enlace, leer al resolverlo— y funciona perfectamente mientras la escala sea modesta. Empezamos aquí a propósito, porque es lo correcto para empezar; y dibujamos sus límites con claridad, porque son el mapa del resto de la guía.

Conviene decirlo con todas las letras:

El diseño de alto nivel empieza con lo más simple que cumple los requisitos —para Enlace, una sola caja: un servidor y una base de datos—. Se complica solo cuando un número obliga. Sobredimensionar desde el día uno es tan error como quedarse corto.

La arquitectura de una sola caja

Aquí está Enlace en su forma más simple. Dos componentes, y el cliente que los usa:

graph LR
    C[Cliente / Navegador] -->|HTTP| S[Servidor Enlace<br/>shorten / resolve]
    S -->|SQL| DB[(Base de datos<br/>tabla links)]

Eso es todo. El servidor Enlace recibe las peticiones HTTP y ejecuta la lógica (shorten y resolve). La base de datos guarda los registros Link en una tabla. El cliente —un navegador, o quien llame a la API— habla con el servidor; el servidor habla con la base de datos. Tres cajas, dos flechas. Un diseñador ansioso querría meter aquí una caché, un balanceador, réplicas; resiste la tentación. Los números del paso 2 todavía no exigen nada de eso, y meterlo ahora sería como montar el supermercado para vender chicles.

La tabla en la base de datos es directísima, un reflejo del registro Link de la lección 1:

-- la tabla links: el corazon de Enlace en una sola caja
CREATE TABLE links (
    short_code  VARCHAR(7)   PRIMARY KEY,   -- el codigo, indexado por ser PK
    long_url    TEXT         NOT NULL,       -- la URL original (~500 bytes)
    created_at  TIMESTAMP    NOT NULL,
    expires_at  TIMESTAMP,                   -- NULL = no expira
    clicks      BIGINT       DEFAULT 0       -- diferido en v1, pero el campo existe
);

Fíjate en short_code VARCHAR(7) PRIMARY KEY. Que sea la clave primaria significa que la base de datos mantiene un índice sobre él automáticamente. Y ese índice es lo que hace que resolve sea rápido: buscar una fila por su clave primaria no recorre los 6000 millones de registros uno por uno, sino que salta directo a la fila correcta en unos pocos pasos (por dentro, un árbol B que la BD navega en tiempo logarítmico). Sin ese índice, cada resolución sería un desastre; con él, es una búsqueda casi instantánea. Este detalle —la clave primaria da el índice, el índice da la velocidad de lectura— es lo que permite que una sola caja aguante las 4000 lecturas/s, como veremos.

El camino de escritura: shorten

Cuando alguien acorta una URL, el servidor recorre este camino:

sequenceDiagram
    participant C as Cliente
    participant S as Servidor Enlace
    participant DB as Base de datos
    C->>S: POST /shorten { long_url }
    S->>S: generar short_code (base62, 7 chars)
    S->>DB: INSERT INTO links (short_code, long_url, ...)
    DB-->>S: OK
    S-->>C: 201 { short_code: "aX9kR2q" }

Paso a paso: (1) el cliente manda la URL larga; (2) el servidor genera un short_code único —cómo se genera sin chocar es todo el módulo 3, aquí basta con "produce 7 caracteres base62"—; (3) el servidor inserta el registro Link en la tabla; (4) devuelve el código al cliente. Una sola escritura a la base de datos por cada acortamiento. Con ~40 escrituras/s, esto es un paseo para cualquier base de datos: ni se despeina.

El camino de lectura: resolve

Cuando alguien visita un enlace corto —el camino que ocurre ~100 veces más seguido—:

sequenceDiagram
    participant U as Navegador
    participant S as Servidor Enlace
    participant DB as Base de datos
    U->>S: GET /aX9kR2q
    S->>DB: SELECT long_url FROM links WHERE short_code = 'aX9kR2q'
    DB-->>S: long_url (busqueda por indice PK)
    S-->>U: 302 Location: https://...url-larga
    U->>U: el navegador sigue la redireccion

Paso a paso: (1) el navegador pide el código corto; (2) el servidor busca la fila por short_code —búsqueda por índice de clave primaria, rapidísima—; (3) obtiene la long_url; (4) responde con una redirección (302) que apunta a la URL larga; (5) el navegador la sigue. Una sola lectura a la base de datos por cada resolución. Este es el camino caliente de Enlace —~4000 veces por segundo— y es el que, al crecer, va a pedir a gritos una caché (módulo 4). Por ahora, con el índice, la caja única lo sostiene.

Qué aguanta una sola caja (y qué no)

Aquí es donde el diseño se vuelve honesto. Una sola caja no aguanta infinito, y un buen diseñador sabe exactamente dónde empieza a crujir. Calculemos los tres límites con aritmética de servilleta:

# ¿hasta donde llega una sola caja para Enlace?
GB = 1e9
TB = 1e12
seconds_per_month = 30 * 24 * 3600

# --- 1. Escrituras: ~40/s. ¿Las aguanta una BD? De sobra. ---
qps_write = 100_000_000 / seconds_per_month     # ~38.6/s

# --- 2. Lecturas: ~4000/s. ¿Las aguanta un servidor con indice? ---
qps_read = qps_write * 100                       # ~3858/s
# un point-lookup indexado cuesta decimas de ms; a 0.5 ms/lookup:
lookups_per_core = 1000 / 0.5                     # 2000/s por core
cores_needed = qps_read / lookups_per_core

# --- 3. Almacenamiento: ¿cuanto crece y cuando se llena un disco? ---
growth_per_month = 100_000_000 * 1024            # bytes/mes
for disk_tb in [1, 4, 8]:
    months_to_fill = disk_tb * TB / growth_per_month
    print(f"disco {disk_tb} TB se llena en {months_to_fill:4.1f} meses "
          f"({months_to_fill/12:.1f} anos)")

print(f"\nescrituras/s = {qps_write:.0f}  -> trivial para una BD")
print(f"lecturas/s   = {qps_read:.0f}  -> ~{cores_needed:.0f} cores con indice: cabe, sin gran margen")
print(f"crecimiento  = {growth_per_month/GB:.0f} GB/mes = {growth_per_month*12/TB:.2f} TB/ano")

Qué esperar. Corriendo esto con Python 3.14.0:

disco 1 TB se llena en  9.8 meses (0.8 anos)
disco 4 TB se llena en 39.1 meses (3.3 anos)
disco 8 TB se llena en 78.1 meses (6.5 anos)

escrituras/s = 39  -> trivial para una BD
lecturas/s   = 3858  -> ~2 cores con indice: cabe, sin gran margen
crecimiento  = 102 GB/mes = 1.23 TB/ano

Leamos los tres límites con calma, porque son el veredicto sobre la caja única:

  • Escrituras: sobra. ~40/s es ridículamente poco para una base de datos moderna, que maneja miles de escrituras/s sin sudar. El camino de escritura de Enlace nunca será el problema en una sola caja. Bien: no hay que tocarlo.
  • Lecturas: cabe, pero apretado. ~4000 lecturas/s, con búsqueda por índice, las sostiene un servidor de unos pocos cores. Cabe. Pero fíjate en "sin gran margen": no hay holgura para picos (un enlace que se vuelve viral), ni para crecimiento. Y todas esas lecturas golpean la misma BD. Aquí es donde, al crecer, entra la caché (módulo 4): servir el 90% de las lecturas desde memoria libera a la BD casi por completo.
  • Almacenamiento: se llena, y pronto. Enlace crece ~102 GB al mes, 1.23 TB al año. Un disco de 1 TB se llena en 10 meses; uno de 8 TB, en 6.5 años. Es decir: una sola caja con un disco grande aguanta un par de años, pero los 6 TB a 5 años ya no caben cómodos en una sola máquina para siempre. Aquí es donde entran réplicas y sharding (módulo 5).

Y falta el límite que ningún cálculo de capacidad muestra pero que es el más grave de todos: la caja única es un único punto de fallo. Si ese servidor se reinicia, se queda sin memoria, o su disco falla, Enlace entero desaparece —no hay 40 escrituras/s ni 0, hay servicio o no hay servicio—. Con un objetivo de 99.9% de disponibilidad (recuerda: máximo 8.76 horas de caída al año), una sola caja no lo garantiza: una sola máquina falla más que eso. Aquí es donde entran la redundancia y el failover (módulo 7).

graph TD
    Box[Enlace en una sola caja] --> W[Escrituras ~40/s<br/>SOBRA -> no se toca]
    Box --> R[Lecturas ~4000/s<br/>CABE apretado -> cache M4]
    Box --> St[Almacenamiento 6 TB<br/>SE LLENA -> replicas/sharding M5]
    Box --> F[Punto unico de fallo<br/>NO cumple 99.9% -> redundancia M7]

Fíjate en la elegancia de esto: la caja única, dibujada con honestidad, se convierte en el índice de los próximos cuatro módulos. Cada límite que encontramos es un módulo que lo resuelve. Ese es el viaje de la guía, y empieza aquí, en el diseño más simple posible visto con ojos críticos.

Por qué no empezamos con el diseño complejo

Podrías objetar: "si sabemos que Enlace va a necesitar caché, réplicas, sharding y balanceo, ¿por qué no dibujarlos ya y ahorrarnos los pasos?". Es una tentación real y vale la pena responderla, porque la respuesta es una de las lecciones más importantes del oficio.

Primero, porque el diseño complejo podría estar mal dimensionado. Sin haber recorrido los límites de la caja única, no sabrías cuánta caché, cuántas réplicas, cuántos shards. Terminarías copiando una arquitectura genérica de ocho nodos que quizá sobra o falta para los números reales. La complejidad se añade midiendo dónde crují la caja simple, no de antemano.

Segundo, porque cada pieza de complejidad tiene un costo. Una caché puede servir datos viejos (invalidación). Las réplicas introducen retraso de propagación (lag). El sharding complica las consultas. El balanceo añade una capa que puede fallar. Cada una resuelve un problema y crea uno nuevo. Meterlas todas desde el día uno es cargar con todos esos problemas nuevos antes de tener los problemas viejos que las justifican. La caja única no tiene ninguno de esos costos —y por eso es el punto de partida correcto—.

Tercero, porque simple es más fácil de entender, operar y depurar. Cuando algo falla en la caja única, hay un servidor y una BD que revisar. Cuando algo falla en un sistema de veinte componentes, el fallo puede estar en cualquiera de ellos o en cómo interactúan. La simplicidad no es solo elegante; es operacionalmente más barata. Se paga complejidad cuando la escala la exige, no antes.

La regla, entonces, es la disciplina que atraviesa toda la ingeniería de sistemas: añade complejidad solo cuando un número demuestra que la simplicidad ya no alcanza. La caja única de Enlace es la encarnación de esa regla. Cumple todos los requisitos funcionales hoy, sostiene la escala actual (apretada pero real), y sus grietas están dibujadas con nombre y apellido. Cuando el módulo 4 meta la caché, será porque las 4000 lecturas/s lo exigen —no "porque los sistemas grandes tienen caché"—.

Errores comunes

Empezar con la arquitectura más compleja "porque se ve más profesional". Qué pasa: alguien dibuja de entrada microservicios, colas, múltiples cachés y sharding para Enlace, sin haber estimado nada. El diseño impresiona pero no está justificado: no sabe si esos ocho shards sobran o faltan. Por qué pasa: la complejidad se confunde con competencia; una caja sola "parece poco". Cómo detectarlo: si tu primer diagrama tiene más de tres o cuatro cajas y no puedes justificar cada una con un número, empezaste demasiado arriba. Cómo corregirlo: empieza siempre con la caja única (o el equivalente mínimo), verifica que cumple los requisitos, y añade complejidad solo cuando un número del paso 2 la exija. Simple primero, siempre.

Dibujar la caja única pero no conocer sus límites. Qué pasa: alguien dibuja el servidor y la BD, dice "listo, aquí está Enlace", y no puede responder "¿hasta cuántas operaciones aguanta? ¿cuándo se llena el disco? ¿qué pasa si el servidor se cae?". El diseño está incompleto no por lo que le falta dibujar, sino por lo que no sabe de lo que dibujó. Por qué pasa: se confunde "dibujé el diagrama" con "entiendo el sistema". Cómo detectarlo: si no puedes nombrar los tres o cuatro puntos donde tu diseño se rompe, no lo entiendes del todo. Cómo corregirlo: por cada caja, pregúntate "¿qué la satura, qué la llena, qué pasa si muere?". Dibujar la caja es el paso fácil; conocer sus grietas es el diseño de verdad —y es lo que hace la lección 7—.

Olvidar el índice (o no saber por qué la lectura es rápida). Qué pasa: alguien dibuja la caja única y asume que resolve es rápido "porque sí", sin darse cuenta de que la velocidad depende de que short_code esté indexado. Si por descuido la búsqueda no usa el índice, cada resolve escanea la tabla entera —6000 millones de filas— y el sistema se arrastra, aun con poca carga. Por qué pasa: el índice es invisible en el diagrama de cajas; se da por sentado. Cómo detectarlo: si no puedes explicar por qué buscar un short_code es rápido, te falta entender el mecanismo. Cómo corregirlo: recuerda que PRIMARY KEY crea el índice, y que sin índice una búsqueda es un escaneo completo. El módulo 3 profundiza en esto; por ahora, que quede claro que la velocidad de lectura de la caja única depende del índice, no es gratis.

Ejercicios

Ejercicio 1 — Dibuja el camino que falta. El camino de lectura (resolve) de arriba responde con un 302. Dibuja (en ASCII o describiendo los pasos) cómo cambiaría el diagrama de secuencia si el enlace no existe (código inexistente). ¿Qué le devuelve el servidor al cliente, y toca la base de datos o no?

Ver solución

El camino de un código inexistente:

Navegador -> Servidor:  GET /noexiste
Servidor  -> BD:        SELECT long_url FROM links WHERE short_code = 'noexiste'
BD        -> Servidor:  (0 filas: no hay ninguna con ese codigo)
Servidor  -> Navegador: 404 Not Found

Sí toca la base de datos: el servidor tiene que preguntar para saber que el código no existe (la BD responde "cero filas"). Solo entonces devuelve un 404. Fíjate en un detalle de diseño interesante que reaparecerá con la caché: una búsqueda de un código inexistente también cuesta una consulta a la BD, aunque no devuelva nada. Si alguien bombardeara Enlace con códigos inventados, cada uno gastaría una lectura —un problema de diseño real (a veces se cachean incluso los "no existe" para evitarlo), pero eso es materia del módulo 4—. Por ahora, lo importante: el 404 es parte del contrato de resolve (requisito funcional F3), y sí consulta la BD.

Ejercicio 2 — ¿Qué límite se rompe primero? Imagina que Enlace, en su caja única, crece más rápido de lo esperado. Para cada escenario, di cuál de los cuatro límites (escrituras, lecturas, almacenamiento, punto único de fallo) se rompe primero y a qué módulo futuro manda: (a) Un enlace se vuelve viral y recibe 50,000 visitas/s durante una hora. (b) Enlace lleva tres años funcionando y el disco de 4 TB está casi lleno. (c) El servidor se reinicia por una actualización y Enlace queda inaccesible 5 minutos. (d) La empresa duplica su marketing y ahora entran 200 millones de URLs nuevas/mes.

Ver solución
  • (a) Viral, 50,000 visitas/s. Se rompe el límite de lecturas: 50,000/s está muy por encima de las ~4000/s que sostiene la caja única. La BD se satura. → Módulo 4 (caché): servir el enlace viral desde memoria descarga la BD casi por completo.
  • (b) Disco de 4 TB casi lleno a los 3 años. Se rompe el almacenamiento (coincide con nuestro cálculo: 4 TB se llenan en ~3.3 años). → Módulo 5 (réplicas/sharding): repartir los datos en varias máquinas.
  • (c) Reinicio, 5 minutos inaccesible. Es el punto único de fallo: sin redundancia, cualquier parada del único servidor tumba Enlace. Cinco minutos por reinicio, repetidos, comen el presupuesto de 99.9%. → Módulo 7 (redundancia y failover).
  • (d) 200M URLs/mes. Sube las escrituras a ~80/s (aún trivial) pero duplica las lecturas a ~8000/s y duplica el crecimiento de disco a ~2.5 TB/año. Se rompen primero las lecturas (y luego el almacenamiento más rápido). → Módulos 4 y 5.

La lección: cada forma de crecer estresa un límite distinto, y cada límite tiene su módulo. La caja única, vista con honestidad, ya te dice a dónde vas a tener que ir según cómo crezca el sistema. Diseñar es anticipar estas grietas, no fingir que no existen.

Ejercicio 3 — Diseña la caja única de otro sistema. Aplica el paso 3 (diseño de alto nivel en una sola caja) a este enunciado: "Diseña un servicio de encuestas: la gente crea una encuesta con opciones y comparte un enlace; los votantes abren el enlace y votan una opción; cualquiera puede ver los resultados." Dibuja (en ASCII o describiendo) la caja única —qué componentes, qué tabla(s)—, e identifica cuál de sus caminos (crear, votar, ver resultados) será el más caliente y por qué.

Ver solución

La caja única del servicio de encuestas, con la misma forma que Enlace (servidor + BD):

Cliente  --HTTP-->  Servidor Encuestas  --SQL-->  Base de datos
                    (create / vote / results)      tablas: polls, options, votes

Un modelo de datos razonable:

CREATE TABLE polls   ( poll_id TEXT PRIMARY KEY, question TEXT, created_at TIMESTAMP );
CREATE TABLE options ( option_id TEXT PRIMARY KEY, poll_id TEXT, label TEXT );
CREATE TABLE votes   ( vote_id TEXT PRIMARY KEY, option_id TEXT, voted_at TIMESTAMP );

Los tres caminos:

  • Crear encuesta (escritura): rara, unas pocas por usuario. Como shorten en Enlace: tranquila.
  • Votar (escritura): más frecuente —muchos votantes por encuesta— pero acotada (cada persona vota una vez por encuesta).
  • Ver resultados (lectura): el más caliente, casi seguro. La gente refresca los resultados muchas más veces de las que vota (miras cómo va la encuesta una y otra vez). Es un patrón de lectura pesada, igual que Enlace.

El camino más caliente es ver resultados, y por la misma razón que en Enlace: se lee mucho más de lo que se escribe. Esto anticipa que, al crecer, "ver resultados" pedirá caché —y que contar votos en tiempo real, si se agrega, sería "las botas de montaña" de este sistema (como la analítica de clics en Enlace), porque agregaría carga sobre cada vista—. Fíjate en cómo el método transfiere: distinto dominio, misma estructura de caja única, mismo análisis de qué camino es el caliente.

Resumen y siguiente paso

En esta lección ejecutaste el paso 3 del marco y le diste a Enlace su primera forma: una sola caja —un servidor y una base de datos, como la tienda de barrio de una persona—. Dibujaste sus dos caminos: el de escritura (shorten: generar código, insertar registro) y el de lectura (resolve: buscar por índice, redirigir con 302), y viste que la velocidad de lectura depende del índice de clave primaria sobre short_code, no es gratis.

Y —lo más importante— dibujaste la caja con honestidad, midiendo sus cuatro límites: las ~40 escrituras/s le sobran (no se toca), las ~4000 lecturas/s le caben pero apretadas (caché, M4), el almacenamiento crece 1.23 TB/año y se llena pronto (réplicas/sharding, M5), y es un único punto de fallo que no cumple 99.9% (redundancia, M7). Esas grietas no son defectos: son el índice del resto de la guía. Y viste por qué se empieza simple —el diseño complejo podría estar mal dimensionado, cada pieza tiene un costo, y simple es más fácil de operar—: se añade complejidad solo cuando un número la exige.

Antes de avanzar deberías poder: dibujar la caja única de Enlace con sus dos caminos; explicar por qué resolve es rápido (el índice); nombrar los cuatro límites de la caja única y a qué módulo manda cada uno; y defender por qué se empieza simple en vez de con la arquitectura compleja.

Lo que sigue es afilar la mentalidad que convierte estos límites en decisiones. La lección 7 instala la idea central del oficio —no hay una respuesta correcta, hay tradeoffs— y toma la caja única para señalar, una por una, dónde se rompe y qué se gana y se paga al arreglarla. Es el puente entre "entendí y dibujé el problema" y "sé razonar sus decisiones", y la antesala directa del mini-proyecto.

Recursos

  • System Design Primer — "Step 2: Create a high level design" — el paso de dibujar la arquitectura de alto nivel con los componentes principales, exactamente lo que hicimos con la caja única. El Primer insiste en empezar simple y justificar cada componente, la misma disciplina de esta lección.
  • PostgreSQL — "Indexes" — por qué una búsqueda por clave primaria es rápida y una sin índice es un escaneo completo de la tabla. Es el mecanismo que hace que la caja única sostenga las 4000 lecturas/s; el módulo 3 lo retoma a fondo.
  • Designing Data-Intensive Applications (DDIA), Capítulo 1 — sitio oficial — la discusión de Kleppmann sobre por qué la simplicidad y la evolucionabilidad son requisitos de primera clase. Es el fundamento teórico de "empieza con una caja y complica solo cuando un número lo exija".
  • MDN — "Redirections in HTTP" — la referencia completa de cómo funcionan las redirecciones HTTP (301, 302, la cabecera Location), que es lo que devuelve el camino de lectura de Enlace. Útil para entender qué hace exactamente el navegador al recibir el 302.