Saltar al contenido principal
DB-1001 • 34 Horas • Nivel Avanzado / Principal In-Memory DB Architect

Bases de Datos In-Memory y Estructuras de Baja Latencia: Redis Enterprise Cluster, Streams, Lua y Cache Stampede

Estructuras internas de memoria (SDS, ZipList/ListPack, SkipList, HyperLogLog, Roaring Bitmaps), particionamiento de 16,384 Hash Slots y protocolo Gossip, atomicidad con scripts Lua, Redis Streams y prevención matemática de Cache Stampede (XFetch).

Módulo 1: Módulo 1: Internals del Reactor Single-Threaded, Estructuras de Memoria y Persistencia (RDB/AOF)

Arquitectura del Event Loop Multiplexado (epoll/kqueue) y Codificaciones Compactas (ListPack & SkipList) (50 min)

Arquitectura del Event Loop Multiplexado (epoll/kqueue) y Codificaciones Compactas (ListPack & SkipList)

1. Por Qué un Único Hilo de Ejecución Supera los 150,000 OPS Sub-Milisegundo

Redis ejecuta los comandos de datos en un Event Loop Single-Threaded respaldado por multiplexación de I/O del kernel (epoll en Linux, kqueue en BSD) y Threaded I/O únicamente para la serialización/deserialización de sockets RESP3: - Cero Contención de Mutex / Locks: No existen cambios de contexto de CPU ni locks pesimistas entre hilos compitiendo por mutar un Sorted Set. - Peligro de Comandos $\mathcal{O}(N)$: Un único comando KEYS *, SMEMBERS sobre un conjunto de $10^6$ elementos, o borrar una llave gigante de forma síncrona (DEL en lugar de UNLINK asíncrono) bloquea el hilo único y congela el $100\%$ del clúster.

2. Transición Interna de Codificaciones: listpack vs. hashtable / skiplist

Un objeto ZSET (Sorted Set) en Redis 7+ utiliza dos estructuras según su tamaño: 1. listpack (Bloque contiguo de memoria amigable con la caché L1/L2 del procesador): Mientras tenga $\le 128$ elementos (zset-max-listpack-entries) de $\le 64\text{ bytes}$, ahorra hasta un $70\%$ de RAM al eliminar punteros de 64 bits. 2. dict (Hash Table $\mathcal{O}(1)$) + zskiplist (SkipList $\mathcal{O}(\log N)$): Cuando supera el umbral, mantiene simultáneamente acceso en $\mathcal{O}(1)$ al score de un miembro y barridos por rango (ZRANGEBYSCORE) en $\mathcal{O}(\log N + M)$.

Estructuras Probabilísticas y Bitmaps: HyperLogLog (12 KB), Filtros de Bloom/Cuckoo y Roaring Bitmaps (50 min)

Estructuras Probabilísticas y Bitmaps: HyperLogLog (12 KB), Filtros de Bloom/Cuckoo y Roaring Bitmaps

1. Conteo de Cardinalidad Masiva (PFADD / PFCOUNT) en Exactamente 12 KB de RAM

Contar usuarios únicos diarios (DAU) de $100$ millones de visitantes con un SET tradicional consumiría $> 3.2\text{ GB}$ de RAM por día. HyperLogLog divide el hash de 64 bits en $m = 2^{14} = 16,384$ registros de 6 bits cada uno ($16,384 \times 6\text{ bits} = 12\text{ KB}$), estimando la cardinalidad mediante la media armónica del máximo número de ceros iniciales observados con un error estándar de apenas $0.81\%$:

$E = \alpha_m m^2 \left(\sum_{j=1}^{m} 2^{-R_j}\right)^{-1}, \qquad \sigma_{\text{error}} \approx \frac{1.04}{\sqrt{16384}} = 0.0081$

Ingeniería de Persistencia y Memoria: Fork Copy-on-Write (CoW), AOF Rewrite y Fragmentación jemalloc (45 min)

Ingeniería de Persistencia y Memoria: Fork Copy-on-Write (CoW), AOF Rewrite y Fragmentación jemalloc

1. Cómo Funciona BGSAVE Sin Congelar la Memoria: Copy-on-Write del Kernel Linux

Cuando Redis dispara un snapshot RDB o una compactación AOF, invoca la syscall fork(). El proceso hijo no duplica físicamente los $32\text{ GB}$ de RAM, sino que comparte las mismas páginas físicas de memoria marcadas como solo-lectura en la tabla de páginas de la MMU. Solo cuando el proceso padre modifica una llave durante el snapshot, el kernel duplica esa página específica de $4\text{ KB}$ (Copy-on-Write).

[!WARNING]
Directiva Obligatoria de Kernel en Producción: Deshabilitar Transparent Huge Pages (THP) (echo never > /sys/kernel/mm/transparent_hugepage/enabled) y activar vm.overcommit_memory = 1. Con THP activo, cada escritura de 10 bytes durante un BGSAVE copia una página gigante de $2\text{ MB}$ en lugar de $4\text{ KB}$, agotando la RAM y provocando picos de latencia de decenas de milisegundos.

Módulo 2: Módulo 2: Patrones de Caché de Alta Escala, Prevención de Cache Stampede y Atomicidad con Lua

Prevención Matemática de Cache Stampede (Thundering Herd): Expiración Probabilística Temprana (XFetch) (55 min)

Prevención Matemática de Cache Stampede (Thundering Herd): Expiración Probabilística Temprana (XFetch)

1. Anatomía de una Avalancha de Caché (Cache Stampede)

Cuando una llave caliente (Hot Key, ej. portada o configuración global con $25,000\text{ req/s}$) alcanza su TTL = 0, miles de workers experimentan simultáneamente un Cache Miss y lanzan $25,000$ consultas costosas contra PostgreSQL en el mismo milisegundo, derribando la base de datos primaria.

2. Algoritmo Óptimo XFetch (Vattani et al. - VLDB) y Singleflight Coalescing

En lugar de esperar a que el TTL expire para todos al mismo tiempo, almacenamos junto al valor el tiempo de cómputo $\delta$ que tomó generarlo y recalculamos la llave de forma asíncrona y anticipada en un único hilo elegido probabilísticamente antes del vencimiento $t_{\text{exp}}$ cuando se cumple:

$t_{\text{now}} - \delta \cdot \beta \cdot \ln(\text{rand}(0, 1)) \ge t_{\text{exp}}$

Donde $\text{rand}(0,1) \sim \mathcal{U}(0,1)$ y $\beta \ge 1.0$. A medida que $t_{\text{now}}$ se acerca a $t_{\text{exp}}$, la probabilidad exponencial de que una sola petición refresque la llave en segundo plano tiende a $1$, mientras el resto de los $24,999$ clientes siguen leyendo el valor cacheado con $0\text{ ms}$ de espera.

Transacciones Deterministas sin Race Conditions: Scripts Lua (EVALSHA), Redis Functions y Rate Limiting GCRA (50 min)

Transacciones Deterministas sin Race Conditions: Scripts Lua (EVALSHA), Redis Functions y Rate Limiting GCRA

1. Por Qué MULTI/EXEC No Basta Cuando la Lógica Depende de una Lectura Previa

En un bloque MULTI/EXEC, todos los comandos se encolan y no puedes leer el resultado de un GET para tomar un if/else dentro de la misma transacción sin recurrir a WATCH (que falla y aborta bajo alta contención).

En cambio, un Script Lua ejecutado vía EVALSHA o Redis Functions (FCALL) se ejecuta de principio a fin de manera 100% atómica y aislada dentro del motor de Redis en microsegundos:

-- Reserva Atómica de Inventario con Idempotencia (KEYS[1]=stock_key, KEYS[2]=idemp_key, ARGV[1]=qty, ARGV[2]=order_id)
if redis.call("EXISTS", KEYS[2]) == 1 then
  return 0 -- Idempotent duplicate
end
local current = tonumber(redis.call("GET", KEYS[1]) or "0")
local requested = tonumber(ARGV[1])
if current < requested then
  return -1 -- Insufficient stock
end
redis.call("DECRBY", KEYS[1], requested)
redis.call("SET", KEYS[2], ARGV[2], "EX", 86400)
return current - requested

Bloqueos Distribuidos Seguros: La Crítica de Kleppmann a Redlock y el Patrón de Fencing Tokens (45 min)

Bloqueos Distribuidos Seguros: La Crítica de Kleppmann a Redlock y el Patrón de Fencing Tokens

1. Por Qué un Lock con TTL Puede Fallar Ante Pausas GC o Saltos de Reloj

Si el Cliente A adquiere un lock en Redis con SET resource_lock random_val NX PX 5000 y sufre una pausa de Garbage Collection (Stop-The-World) de $6$ segundos: 1. El lock expira en Redis a los $5$ segundos. 2. El Cliente B adquiere el lock legítimamente y escribe en el almacenamiento. 3. El Cliente A despierta de su pausa GC y escribe sin saber que su lock expiró, corrompiendo el estado.

Solución Estricta (Fencing Tokens Monotónicos): El servicio de bloqueo entrega un entero estrictamente creciente (fencing_token = 34, luego 35). El recurso de almacenamiento rechaza cualquier escritura cuyo fencing_token < max_seen_token.

Módulo 3: Módulo 3: Redis Cluster (16,384 Hash Slots), Hash Tags y Event Streaming con Redis Streams

Topología de Redis Cluster: CRC16 Modulo 16384, Enrutamiento MOVED/ASK y Co-Localización con Hash Tags (50 min)

Topología de Redis Cluster: CRC16 Modulo 16384, Enrutamiento MOVED/ASK y Co-Localización con Hash Tags

1. Asignación Determinista de Llaves a Nodos (CRC16(key) mod 16384)

Redis Cluster divide el espacio de claves en exactamente $16,384$ Hash Slots distribuidos entre los nodos primarios:

$\text{Slot}(k) = \text{CRC16}(k) \pmod{16384}$

flowchart LR
    KEY["Llave: order:{tenant_42}:cart"] --> TAG["Extractor de Hash Tag: 'tenant_42'"]
    TAG --> CRC["CRC16('tenant_42') mod 16384 = Slot 8912"]
    CRC --> N2["Shard Primario 2 (Slots 5461 .. 10922)"]
    N2 --> R2["Réplica 2A (Failover vía Gossip Raft-like)"]

[!IMPORTANT]
Operaciones Multi-Key y Scripts Lua en Redis Cluster: En un clúster particionado, ejecutar un script Lua o transacción MGET sobre dos llaves con distinto Hash Slot arroja el error CROSSSLOT Keys in request don't hash to the same slot. Encerrar la clave de partición entre llaves {...} (Hash Tags) obliga a Redis a calcular el CRC16 únicamente sobre la subcadena dentro de {...}, garantizando co-localización física en el mismo shard.

Redis Streams Internals: Árboles Radix + ListPacks, Consumer Groups, PEL y Recuperación con XAUTOCLAIM (50 min)

Redis Streams Internals: Árboles Radix + ListPacks, Consumer Groups, PEL y Recuperación con XAUTOCLAIM

1. Por Qué PUB/SUB Clásico es Fire-and-Forget y Streams Garantiza Entrega At-Least-Once

El PUBLISH/SUBSCRIBE tradicional descarta mensajes si el consumidor se desconecta por un milisegundo. Redis Streams almacena un log append-only indexado por un Árbol Radix (Rax) de IDs temporales - con nodos hoja listpack comprimidos:

1. XREADGROUP GROUP cg_payments worker_1 COUNT 10 STREAMS tx_stream >: Entrega mensajes nuevos al consumidor y los registra atómicamente en la PEL (Pending Entries List) hasta recibir XACK. 2. Recuperación de Consumidores Caídos (XAUTOCLAIM): Si worker_1 muere antes de enviar XACK, otro worker ejecuta XAUTOCLAIM tx_stream cg_payments worker_2 30000 0-0 para reclamar automáticamente todos los mensajes pendientes con más de $30\text{ s}$ de inactividad e incrementar su contador de entregas (para desviar a Dead-Letter Stream tras $K$ fallos).

Políticas de Evicción de Memoria: LFU Aproximado con Contadores Logarítmicos de Morris vs. LRU (45 min)

Políticas de Evicción de Memoria: LFU Aproximado con Contadores Logarítmicos de Morris vs. LRU

1. Cómo Redis Almacena Frecuencia + Decaimiento Temporal en Solo 24 Bits del robj

En la cabecera de cada objeto Redis (redisObject), el campo lru dispone de únicamente 24 bits. En el modo allkeys-lfu (superior a LRU ante escaneos puntuales que ensucian la caché), Redis divide esos 24 bits en: - 16 bits superiores: Timestamp del último decaimiento en minutos (lfu-decay-time). - 8 bits inferiores: Contador probabilístico logarítmico de Morris (de $0$ a $255$) capaz de representar hasta $10^6$ accesos incrementándose con probabilidad $P = \frac{1}{\text{counter} \cdot \text{lfu_log_factor} + 1}$.

Módulo 4: Módulo 4: Laboratorio de Auditoría de Memoria/Hit-Ratio y Evaluación de Certificación

Diagnóstico SRE de Redis: Fragmentación de Memoria (mem_fragmentation_ratio) y Mitigación de Hot Slots (45 min)

Diagnóstico SRE de Redis: Fragmentación de Memoria (mem_fragmentation_ratio) y Mitigación de Hot Slots

El indicador mem_fragmentation_ratio = used_memory_rss / used_memory mide la eficiencia del asignador jemalloc: - $1.00 - 1.35$: Rango óptimo saludable. - $> 1.50$: Fragmentación física severa en el sistema operativo (activar activedefrag yes). - $< 1.00$: Alerta Crítica Roja: El sistema operativo está enviando páginas de Redis a Swap en disco, destruyendo la latencia sub-milisegundo.

Laboratorio Práctico: Auditoría de Cache Hit Ratio y Detección de Nodos con Swapping / Fragmentación (60 min)

Laboratorio Práctico: Auditoría de Cache Hit Ratio y Detección de Nodos con Swapping / Fragmentación

Contexto del Desafío SRE In-Memory

La tabla redis_cluster_metrics contiene las métricas recolectadas de los 4 shards primarios de nuestro clúster Redis Enterprise: - shard_id (TEXT) - keyspace_hits (BIGINT) - keyspace_misses (BIGINT) - used_memory_mb (NUMERIC: memoria lógica ocupada por datos en Redis) - used_memory_rss_mb (NUMERIC: memoria física residente reportada por el kernel Linux)

Objetivo de la Consulta SQL

Escribe una consulta SQL para todos los shards que calcule y devuelva: 1. shard_id 2. hit_ratio_pct: ROUND(100.0 * keyspace_hits / (keyspace_hits + keyspace_misses), 2) 3. frag_ratio: ROUND(used_memory_rss_mb / used_memory_mb, 2) 4. health_status: - 'CRITICAL_SWAP' si used_memory_rss_mb / used_memory_mb < 1.0 - 'HIGH_FRAGMENTATION' si used_memory_rss_mb / used_memory_mb > 1.5 - 'HEALTHY' en cualquier otro caso. Ordena el resultado por shard_id ASC.

Evaluación de Certificación: Bases de Datos In-Memory, Redis Cluster y Estructuras de Baja Latencia (45 min)

Evaluación de Certificación: DB-1001

Comprueba tu dominio sobre la arquitectura interna de Redis, Hash Slots, scripts Lua, Redis Streams y prevención de Cache Stampede.