Módulo 4: Caché — el camino de lectura pesada
6. El working set y la regla 80/20
Descripción
La lección anterior te dejó queriendo un hit ratio alto —0.90 o más— y una pregunta encima: para lograrlo, ¿cuántos datos tengo que cachear? Enlace tiene 6,000 millones de registros (los calculaste en el módulo 2: 5 años a ~1 KB cada uno, unos 6 TB). Si tuvieras que meter todo eso en RAM para tener buen hit ratio, la caché sería tan cara como inviable —6 TB de RAM cuestan una fortuna—. La buena noticia, y el tema de esta lección, es que no hace falta cachear casi nada de eso. La razón tiene nombre: la regla 80/20 (o principio de Pareto, o distribución de Zipf en su versión formal), que dice que en sistemas como Enlace una fracción pequeña de los datos concentra la enorme mayoría de los accesos. Unos pocos links virales se llevan casi todas las visitas; la larguísima cola de links que nadie mira casi no aporta lecturas.
Eso significa que puedes conseguir un hit ratio del 90% cacheando solo el working set —el conjunto de datos que de verdad están calientes en una ventana de tiempo—, que es minúsculo comparado con el total. Esta lección define el working set, explica por qué la regla 80/20 lo hace pequeño, y calcula el tamaño real del working set de Enlace en Python: cuántas entradas, cuántos bytes cada una, cuánta RAM en total. El resultado —~666,667 entradas, ~333 MB— cabe de sobra en un Redis modesto, y ese número es el que llevas al proyecto de la lección 8.
Conexión con el módulo: la lección 5 demostró que quieres un hit ratio alto; esta te dice cuánta RAM cuesta conseguirlo, y la respuesta feliz es "poca". Se apoya en la localidad de la lección 4 (lo caliente es un subconjunto pequeño) y en la presión de la lección 2 (los ~333 M de lecturas diarias). El número que calculas aquí —los MB del working set— es una de las dos entradas del dimensionamiento del proyecto (la otra es el hit ratio objetivo de la lección 5). Sin esta lección, "necesito una caché" no tiene tamaño; con ella, tiene un número en megabytes.
Los libros de la biblioteca y la mesa de novedades
Piénsalo así. Una biblioteca tiene cien mil libros en sus estanterías. Si observas qué se lleva la gente durante una semana, descubres algo que se repite en toda biblioteca del mundo: la mayoría de los préstamos son de unos pocos cientos de títulos —las novedades, los best-sellers, las lecturas obligatorias del semestre—, mientras que la inmensa mayoría de los cien mil libros no sale ni una vez en toda la semana. No es que los demás no importen: están ahí para cuando alguien los pida. Pero el tráfico se concentra brutalmente en una fracción diminuta del catálogo.
La bibliotecaria lista aprovecha esto. En vez de tener que ir a las estanterías del fondo para cada préstamo, pone una mesa de novedades junto a la entrada con esos pocos cientos de títulos calientes. Como la mayoría de la gente viene justo por esos, la mayoría de los préstamos se resuelven en la mesa de la entrada, sin caminar al fondo. La mesa no tiene ni el 1% del catálogo, pero atiende el 90% de los préstamos. Y lo mejor: la mesa cabe en la entrada precisamente porque es pequeña —no necesita ser una segunda biblioteca, solo un estante con lo caliente—.
Esa mesa de novedades es tu caché, y su tamaño es el working set: el conjunto de datos que están calientes ahora. La biblioteca completa (cien mil libros) es la base de datos; la mesa (unos cientos de títulos) es la caché. La regla que hace que la mesa quepa —"pocos títulos concentran los préstamos"— es la regla 80/20. Y la pregunta de esta lección es exactamente la de la bibliotecaria: ¿cuántos títulos tengo que poner en la mesa para atender el 90% de los préstamos? En Enlace, esa cuenta da unos cientos de miles de links y unos cientos de megabytes —una mesa que cabe holgada en la entrada—.
La regla 80/20 dice que una fracción pequeña de los datos concentra la mayoría de los accesos. Por eso no cacheas todo: cacheas el working set —los datos calientes en una ventana—, que es minúsculo comparado con el total, y con él consigues un hit ratio alto usando poca RAM.
La regla 80/20, con más precisión
"80/20" es un apodo; la forma real de esta concentración es una distribución de Zipf (o power law, ley de potencias), y aparece por todos lados: las palabras más usadas de un idioma, las ciudades más pobladas, los videos más vistos, los productos más vendidos. La idea: el elemento más popular recibe muchísimo más tráfico que el segundo, el segundo más que el tercero, y así, con una caída muy pronunciada. El resultado práctico es que una minoría de elementos acapara una mayoría de los eventos. El "80/20" es solo una forma redonda de decirlo (el 20% de los datos → el 80% de los accesos), pero en muchos sistemas reales es aún más extremo: 10/90, o 1/50.
Para una caché, esto es una noticia excelente, porque significa que el hit ratio sube rápido con las primeras entradas que cacheas y luego se aplana. Míralo así: si cacheas el 1% más caliente, ya atrapas quizás el 50% de las lecturas. Si cacheas el 20% más caliente, atrapas el 90%. Pero para llegar del 90% al 99% tendrías que cachear una fracción mucho mayor —la larga cola de links que se piden de vez en cuando—, y ahí es donde ganar hit ratio se vuelve caro (justo lo que viste en la lección 5: los últimos puntos cuestan). La gráfica mental es una curva que sube empinada y luego se acuesta: las primeras entradas cacheadas dan casi todo el hit ratio; la cola larga da cada vez menos por cada MB añadido.
En Enlace, la concentración viene de dos fuentes que se refuerzan. Primera, la novedad: un link recién creado y compartido recibe una ráfaga de visitas en sus primeras horas o días (mientras circula el tuit, el correo, el mensaje) y luego se enfría. Segunda, la viralidad: de todos los links, unos pocos explotan y reciben órdenes de magnitud más visitas que el resto. Las dos juntas producen una concentración fuerte: el working set caliente de Enlace en un día son, sobre todo, los links recientes y los pocos virales, no los 6,000 millones históricos.
Calculando el working set de Enlace
Bajemos esto a un número. La pregunta concreta: ¿cuántas entradas distintas necesito cachear para tener buen hit ratio, y cuánta RAM ocupan? Vamos a razonarlo por pasos y a ejecutarlo.
Paso 1: ¿cuántos links entran "en juego" cada día? Con 100M de links nuevos al mes, entran unos 100,000,000 / 30 ≈ 3,333,333 links nuevos por día. Como el tráfico se concentra en los links recientes, el grueso del working set caliente de un día son esos links nuevos (más algunos virales de días previos que siguen activos).
Paso 2: ¿qué fracción está de verdad caliente? Aquí aplicamos la regla 80/20: no todos los links nuevos del día están calientes por igual; el 20% más caliente concentra la mayoría de las lecturas. Así que el working set que hay que cachear para atrapar ~90% de las lecturas es del orden del 20% de los links del día: 0.20 × 3,333,333 ≈ 666,667 entradas.
Paso 3: ¿cuánto pesa cada entrada? Una entrada de la caché es short_code → long_url. El short_code son 7 bytes; la long_url es lo que pesa, ~500 bytes en promedio (el número-ancla del módulo 2). Con la sobrecarga de Redis (metadatos por clave), una entrada ronda los 500 bytes. Calculemos también con 200 y 1000 para ver el rango.
# working_set.py — tamaño del working set y memoria de la cache
NEW_URLS_PER_MONTH = 100_000_000
new_links_per_day = NEW_URLS_PER_MONTH / 30
hot_fraction = 0.20 # regla 80/20: el 20% mas caliente
working_set = new_links_per_day * hot_fraction
print(f"links nuevos/dia = {new_links_per_day:,.0f}")
print(f"working set (20%) = {working_set:,.0f} entradas\n")
for entry_bytes in (200, 500, 1000):
mem_bytes = working_set * entry_bytes
print(f"a {entry_bytes:>4} B/entrada -> "
f"{mem_bytes:,.0f} B = {mem_bytes/1e6:,.0f} MB = {mem_bytes/1e9:.2f} GB")
Qué esperar. Con python working_set.py:
links nuevos/dia = 3,333,333
working set (20%) = 666,667 entradas
a 200 B/entrada -> 133,333,333 B = 133 MB = 0.13 GB
a 500 B/entrada -> 333,333,333 B = 333 MB = 0.33 GB
a 1000 B/entrada -> 666,666,667 B = 667 MB = 0.67 GB
Ahí está el número que buscábamos. El working set de un día de Enlace son unas 666,667 entradas, y a ~500 bytes cada una ocupan ~333 MB. Compara eso con los 6 TB de la base de datos completa: la caché necesita ~333 MB para atrapar el 90% de las lecturas, es decir, alrededor del 0.005% del tamaño total de los datos. Ese es el regalo de la regla 80/20 hecho número: con cinco milésimas de porcentaje del almacenamiento, en RAM, resuelves la enorme mayoría del tráfico. Un Redis de 1 o 2 GB —barato, común— sobra para esto. No hace falta cachear los 6 TB; hace falta cachear los ~333 MB calientes.
Cómo la ventana mueve el número
El "666,667 entradas" salió de asumir que el working set caliente es el 20% de un día de links. Pero "caliente" depende de la ventana que elijas: si los links siguen recibiendo visitas durante varios días (no solo el día que se crean), tu working set abarca varios días de calientes, y crece. Veámoslo:
# window.py — el working set segun cuantos dias de calientes retienes
working_set_day = 666_667
entry_bytes = 500
for days in (1, 3, 7, 30):
ws = working_set_day * days
print(f"{days:>2} dia(s) de calientes -> {ws:>10,.0f} entradas = "
f"{ws*entry_bytes/1e9:.2f} GB")
Qué esperar. Con python window.py:
1 dia(s) de calientes -> 666,667 entradas = 0.33 GB
3 dia(s) de calientes -> 2,000,000 entradas = 1.00 GB
7 dia(s) de calientes -> 4,666,667 entradas = 2.33 GB
30 dia(s) de calientes -> 14,000,000 entradas = 7.00 GB
La ventana manda. Si con retener un día de calientes ya tienes buen hit ratio, te bastan ~333 MB. Si tu tráfico hace que los links sigan calientes una semana, necesitas ~2.33 GB. Si un mes, ~7 GB. Todos esos números caben en RAM de un servidor moderno (un Redis con 8 o 16 GB los aguanta), y ninguno se acerca a los 6 TB del total. La decisión de diseño es: ¿cuánta ventana de calientes retengo? Y aquí es donde la evicción de la lección 4 y el TTL de la lección 7 hacen su trabajo: LRU tira automáticamente los links que se enfriaron (los que salieron de la ventana caliente), manteniendo la caché llena de lo actual sin que tú calcules la ventana a mano. Tú das la RAM; LRU decide qué día de calientes cabe.
Por qué esto justifica toda la estrategia
Vale la pena juntar los hilos, porque esta lección cierra el argumento del módulo. La lección 2 dijo "hay que atrapar la mayoría de las ~4,000 lecturas/s antes de que lleguen a la base de datos". La lección 5 dijo "con hit ratio 0.90 la latencia baja a 5.9 ms y la base de datos ve solo 386/s". La pregunta que quedaba —"¿pero cuánta RAM cuesta ese 0.90?"— la responde esta lección: ~333 MB, gracias a que la regla 80/20 concentra las lecturas en un working set diminuto. Sin la regla 80/20, cachear sería inviable (tendrías que meter 6 TB en RAM). Con ella, cachear es baratísimo y por eso es la primera herramienta que se saca en cualquier sistema read-heavy. La caché funciona no por magia, sino porque el tráfico del mundo real está concentrado —y Enlace, con sus links virales y recientes, lo está de sobra—.
Errores comunes
Creer que hay que cachear todos los datos para tener buen hit ratio. Qué pasa: alguien mira los 6 TB de Enlace y concluye que necesita 6 TB de RAM (imposible) o que "la caché no sirve porque no cabe todo". Descarta la caché por un cálculo equivocado. Por qué pasa: se asume que el hit ratio depende de cachear una gran fracción de los datos, cuando depende de cachear los datos calientes, que son una fracción minúscula. Cómo detectarlo: si tu estimación de RAM para la caché se acerca al tamaño total de la base de datos, no estás usando la regla 80/20. Cómo corregirlo: calcula el working set (los datos calientes en una ventana), no el total. En Enlace, ~333 MB (el 0.005% del total) dan hit ratio 0.90. La caché atrapa el tráfico concentrado, no todos los datos.
Confundir "datos totales" con "datos calientes". Qué pasa: alguien dimensiona la caché por el tamaño de la base de datos (6 TB) en vez de por el working set (~333 MB), y pide un Redis gigantesco y carísimo que está 99.99% desperdiciado —lleno de links fríos que nadie pide—. Por qué pasa: es fácil pensar en "todos los datos" y olvidar que el tráfico solo toca unos pocos. Cómo detectarlo: si tu caché tiene mucha más memoria de la que el working set necesita, mira el hit ratio: si ya es alto con una fracción de la RAM, el resto sobra. Cómo corregirlo: dimensiona por el working set. Más RAM de la que el working set necesita casi no sube el hit ratio (la cola larga aporta poco), así que pagas por memoria que no compra hits. La lección 5 lo anticipó: los últimos puntos de hit ratio son caros justamente porque exigen cachear la cola fría.
Olvidar que la ventana caliente cambia el tamaño. Qué pasa: alguien calcula el working set para "un día" y dimensiona la caché para 333 MB, pero en su sistema los datos siguen calientes una semana, así que el working set real es 2.33 GB —y la caché, demasiado chica, desaloja links que todavía se piden, bajando el hit ratio—. Por qué pasa: se toma una ventana arbitraria sin verificar cuánto tiempo siguen calientes los datos de verdad. Cómo detectarlo: si el hit ratio es más bajo de lo esperado y la evicción es alta, tu caché es más chica que el working set real. Cómo corregirlo: mide (o estima con margen) cuánto tiempo siguen calientes los datos y dimensiona para esa ventana. Y deja que LRU (lección 4) haga el ajuste fino: si le das RAM para una semana de calientes, LRU retendrá una semana; si le das para un día, retendrá un día. La ventana emerge del tamaño que le das.
Ejercicios
Ejercicio 1 — Recalcula el working set con otra fracción. El ejemplo usó el 20% más caliente. Recalcula el working set y la memoria (a ~500 bytes/entrada) si en Enlace bastara con cachear el 10% más caliente para tener buen hit ratio, y si en cambio hiciera falta el 30%. Parte de 3,333,333 links nuevos al día.
Ver solución
- Al 10%: working set =
0.10 × 3,333,333 ≈ 333,333entradas. Memoria =333,333 × 500 ≈ 166,666,500 B ≈ **167 MB**. - Al 20% (referencia): 666,667 entradas ≈ 333 MB.
- Al 30%: working set =
0.30 × 3,333,333 ≈ 1,000,000entradas. Memoria =1,000,000 × 500 ≈ **500 MB**.
Los tres números —167 MB, 333 MB, 500 MB— caben holgados en RAM. La lección: aunque la fracción caliente sea el doble o el triple de lo estimado, la caché sigue siendo pequeña y barata (nunca se acerca a los 6 TB del total). Ese es el margen que da la regla 80/20: incluso equivocándote en la fracción, el working set es diminuto comparado con los datos completos. Fíjate también en el rendimiento decreciente: pasar del 10% al 30% (triplicar la RAM, de 167 a 500 MB) probablemente sube el hit ratio solo unos puntos, porque estás cacheando links cada vez más fríos —la cola larga—.
Ejercicio 2 — La curva de Pareto en números. Supón esta distribución de accesos (simplificada) para 5 links en una hora: L1 recibe 500 visitas, L2 recibe 250, L3 recibe 125, L4 recibe 65, L5 recibe 60. (a) ¿Cuántas visitas hay en total? (b) Si cacheas solo los 2 links más calientes (L1 y L2), ¿qué hit ratio obtienes? (c) ¿Qué te dice esto sobre cachear "los pocos calientes"?
Ver solución
- (a) Total:
500 + 250 + 125 + 65 + 60 = 1,000visitas. - (b) Cacheando L1 y L2: esos dos reciben
500 + 250 = 750visitas de las 1,000. Hit ratio =750 / 1,000 = **75%**. Con solo 2 de 5 links (el 40% de los links) atrapas el 75% de las visitas. - (c) Muestra la esencia de la regla 80/20: los accesos se concentran en los primeros elementos, así que cachear "los pocos calientes" (L1, L2) da un hit ratio desproporcionadamente alto respecto a cuántos links son. Cachear un tercer link (L3, con 125 visitas) subiría el hit ratio a
875/1000 = 87.5%—cada link adicional aporta menos, porque la cola es más fría—. Esta es la curva que sube empinada y se acuesta: los primeros datos cacheados compran casi todo el hit ratio; los últimos, cada vez menos. En Enlace, con millones de links y una concentración mucho más extrema que este ejemplo de juguete, cachear el 20% más caliente basta para el 90%.
Ejercicio 3 — ¿Por qué no cachear los 6 TB? Un compañero propone: "Pongamos toda la base de datos de Enlace (6 TB) en RAM para tener hit ratio 100% y nunca ir a disco". Da tres razones por las que esto es mala idea, apoyándote en la regla 80/20 y en la lección 5.
Ver solución
Tres razones:
-
Costo desproporcionado. 6 TB de RAM cuestan órdenes de magnitud más que los ~333 MB que el working set necesita para hit ratio 0.90. Estarías pagando por meter en RAM cara la larguísima cola de links fríos que casi nadie pide —el 99.99% de la caché estaría desperdiciada en datos que no generan tráfico—.
-
Rendimiento decreciente (lección 5). Ir de hit ratio 0.90 (con ~333 MB) a 1.00 (con 6 TB) baja la latencia media de 5.90 ms a 1.00 ms —una mejora de ~5 ms— a cambio de multiplicar la RAM por ~18,000. Los últimos puntos de hit ratio, que exigen cachear la cola fría, son carísimos y compran poquísimo. No vale la pena.
-
La caché deja de ser una caché. Una caché es una copia parcial y desechable de lo caliente; si metes todos los datos, ya no es una caché, es una segunda base de datos en RAM —volátil, cara y sin persistencia—. Pierdes la ventaja de que la caché sea pequeña y barata, que es justo lo que la regla 80/20 hace posible.
La moraleja: el hit ratio 100% no es la meta —es un espejismo caro—. La meta es un hit ratio alto (0.90–0.95) al menor costo de RAM, y la regla 80/20 dice que eso se logra cacheando solo el working set. Perseguir el 100% es pelear contra la cola larga, que da cada vez menos por cada TB. Un buen diseño de caché acepta que siempre habrá algunos misses y se contenta con atrapar el grueso barato del tráfico.
Resumen y siguiente paso
En esta lección respondiste la pregunta que la lección 5 dejó abierta: para un hit ratio de 0.90, no hace falta cachear los 6 TB de Enlace, sino solo el working set —los datos calientes en una ventana—, que la regla 80/20 (distribución de Zipf) hace diminuto: unos pocos links virales y recientes concentran la mayoría de las lecturas, como la mesa de novedades que atiende el 90% de los préstamos con menos del 1% del catálogo. Lo calculaste: ~666,667 entradas, ~333 MB —alrededor del 0.005% del total—, que caben holgadas en un Redis modesto.
Viste que la ventana de "caliente" mueve el número (un día → 333 MB, una semana → 2.33 GB), que todos esos tamaños caben en RAM, y que la evicción LRU (lección 4) ajusta automáticamente qué ventana retiene según la RAM que le des. Y cerraste el argumento del módulo: la caché es barata y por eso es la primera herramienta del read-heavy, porque el tráfico real está concentrado.
Antes de avanzar deberías poder: definir el working set y distinguirlo de "todos los datos"; explicar la regla 80/20 y por qué hace pequeña a la caché; reproducir el cálculo (~666,667 entradas → ~333 MB); y decir por qué cachear el 100% de los datos es mala idea.
Lo que sigue es el lado oscuro de la caché, el problema que hemos ido posponiendo: la copia que guarda la caché puede quedar obsoleta. En la lección 7 vas a ver el TTL (caducidad automática) como red de seguridad barata, la invalidación (borrar la entrada cuando la base de datos cambia), por qué en Enlace esto es fácil (los links casi no cambian) y dónde se pone difícil, y la frontera con la invalidación asíncrona por colas que vive en la guía de eventos.
Recursos
- Pareto principle (regla 80/20) — Wikipedia — el principio general de que una minoría de causas produce la mayoría de los efectos, con ejemplos de muchos dominios. El fundamento intuitivo de por qué el working set es pequeño.
- Zipf's law — Wikipedia — la forma matemática precisa (ley de potencias) de la concentración que en caching llamamos "80/20". Explica por qué el hit ratio sube empinado con las primeras entradas cacheadas y luego se acuesta.
- Designing Data-Intensive Applications, Kleppmann — sobre working set y hot/cold data — el tratamiento de por qué los datos "calientes" caben en memoria mientras los "fríos" viven en disco, que es exactamente la separación working-set-vs-total de esta lección. El marco para dimensionar cualquier caché, no solo la de Enlace.