Saltar al contenido principal
DB-701 • 34 Horas • Nivel Avanzado / Principal Distributed NoSQL Architect

Motores NoSQL Distribuidos: MongoDB Sharded Clusters (WiredTiger), Elasticsearch (Lucene Internals) y Redis Cluster

Arquitectura interna de WiredTiger (MVCC, Checkpoints y Journal), diseño de Shard Keys anti-Jumbo Chunks, garantías de consistencia linealizable (Read/Write Concerns), índices invertidos BM25/BKD-Trees en Lucene y bloqueos distribuidos en Redis Cluster.

Módulo 1: Módulo 1: Internals de MongoDB, Motor de Almacenamiento WiredTiger y Consistencia en Replica Sets

Anatomía del Motor WiredTiger: Árboles B+ en Memoria, Concurrencia MVCC, Eviction Threads y Oplog Idempotente (55 min)

Anatomía del Motor WiredTiger: Árboles B+ en Memoria, Concurrencia MVCC, Eviction Threads y Oplog Idempotente

1. Arquitectura de Memoria de WiredTiger y Regla del 50% de RAM

El motor de almacenamiento WiredTiger gestiona dos niveles de caché en el nodo: 1. WiredTiger Internal Cache: Por defecto reserva $\max\left(0.5 \times (\text{RAM} - 1\text{ GB}), \; 256\text{ MB}\right)$, donde las páginas de los árboles B+ residen descomprimidas y con estructuras de listas de actualización MVCC (Update Restore Chains) por documento. 2. OS Filesystem Page Cache: El resto de la RAM libre del kernel Linux almacena los bloques de disco comprimidos (Snappy / Zstandard), evitando lecturas físicas a NVMe cuando una página es desalojada del caché interno de WiredTiger.

flowchart LR
    WR["Write Operation (insert/update)"] --> MEM["WiredTiger In-Memory B+Tree Page (Dirty)"]
    WR --> JRN["Write-Ahead Journal Buffer (Flush cada 50-100ms o j:true)"]
    WR --> OPL["local.oplog.rs (Idempotent Replication Stream)"]
    MEM -->|Checkpoint cada 60s| DSK["Data Files *.wt (Bloques Comprimidos Zstd + Nuevo Root Pointer)"]

2. Umbrales Críticos de Desalojo (Dirty Cache Eviction Stalls)

- eviction_Dirty_target = 5%: Los hilos de fondo de WiredTiger comienzan a escribir páginas sucias a disco de forma asíncrona. - eviction_dirty_trigger = 20%: Peligro SRE: Los hilos de aplicación de los clientes son forzados a detener sus operaciones y ayudar a limpiar páginas sucias (Application Thread Stalls), disparando la latencia p99 de $2\text{ ms}$ a $> 800\text{ ms}$.'

Consistencia Distribuida en Replica Sets: Write Concern (w:majority), Read Concern Linearizable y Rollbacks (55 min)

Consistencia Distribuida en Replica Sets: Write Concern (w:majority), Read Concern Linearizable y Rollbacks

1. Cómo Ocurre una Pérdida Silenciosa de Datos Confirmados (w:1 + Partición de Red)

Si una aplicación escribe con w: 1 en el nodo Primario $P_1$ e inmediatamente $P_1$ queda aislado por red antes de replicar esa entrada del Oplog a los Secundarios $S_2, S_3$: 1. $S_2$ y $S_3$ forman quórum mayoritario ($2/3$) y eligen a $S_2$ como nuevo Primario. 2. Cuando $P_1$ se reconecta al clúster, descubre que el nuevo Primario $S_2$ avanzó en un término de elección superior ($ ext{term} + 1$) sin sus últimas escrituras. 3. $P_1$ ejecuta un Rollback forzoso, descartando las escrituras que ya habían devuelto éxito al cliente con w: 1.

2. Matriz de Garantías de Consistencia ante Particiones de Red (Jepsen Safe)

| Combinación Cliente | ¿Previene Dirty Reads (Datos sujetos a Rollback)? | ¿Garantiza Lectura Monótona Real-Time (Read-Your-Writes)? | Costo de Latencia | | :--- | :--- | :--- | :--- | | w: 1, readConcern: "local" | ❌ No (Lee datos volátiles no confirmados por quórum) | ❌ No en lecturas desde secundarios | Mínima ($1\text{ ms}$) | | w: "majority", readConcern: "majority" | ✅ Sí (Lee únicamente el Majority Commit Point) | ✅ Sí usando Causal Consistency Sessions (Logical Clock + OperationTime) | Media ($3$–$8\text{ ms}$) | | w: "majority", readConcern: "linearizable" | ✅ Sí | ✅ Sí (El Primario consulta quórum en tiempo real antes de responder para probar que no ha sido depuesto) | Alta ($2\times$ RTT de red) |'

Diseño Matemático de Shard Keys en Clústeres Particionados: Cardinalidad, Frecuencia y Monotonía (50 min)

Diseño Matemático de Shard Keys en Clústeres Particionados: Cardinalidad, Frecuencia y Monotonía

1. Las Tres Propiedades Inmutables de una Shard Key de Producción

Una clave de particionamiento horizontal en MongoDB (Shard Key) se evalúa sobre tres ejes ortogonales: 1. Alta Cardinalidad ($|\mathcal{K}| \to \infty$): Si usas status ($3$ valores posibles), el clúster jamás podrá dividir los datos en más de $3$ fragmentos (Jumbo Chunks indivisibles), aunque agregues $50$ servidores. 2. Baja Frecuencia por Valor (Distribución Uniforme): Ningún valor único de la clave debe concentrar un porcentaje desproporcionado de documentos. 3. Cambio No Monótono en Escritura: Si particionas por Rango sobre created_at o ObjectId (cuyos primeros $4\text{ bytes}$ son el timestamp Unix en segundos), el $100\%$ de las inserciones nuevas golpean exclusivamente el último Chunk en un único Shard (Hot Shard Bottleneck), dejando los demás Shards ociosos en escritura.

2. Patrón Compuesto Óptimo: { tenant_id: 1, event_bucket_date: 1, _id: 1 } o Hashed Prefix

Permite Targeted Queries (el router mongos consulta únicamente el Shard exacto del tenant_id evitando Scatter-Gather a todos los nodos) y al mismo tiempo permite dividir (Split) el tráfico de un tenant grande a través de múltiples rangos temporales.

Módulo 2: Módulo 2: Motores de Búsqueda Distribuida: Elasticsearch y Apache Lucene Internals

Estructuras de Datos en Apache Lucene: FST (Finite State Transducers), Roaring Bitmaps, BKD-Trees y DocValues (55 min)

Estructuras de Datos en Apache Lucene: FST (Finite State Transducers), Roaring Bitmaps, BKD-Trees y DocValues

1. Cómo un Segmento Inmutable de Lucene Resuelve Consultas Híbridas en Milisegundos

Cada shard de Elasticsearch / OpenSearch es una instancia de Apache Lucene compuesta por Segmentos Inmutables que combinan cuatro estructuras físicas especializadas:

flowchart TD
    SEG["Lucene Immutable Segment"] --> FST["1. Term Index (FST en RAM) -> Term Dictionary -> Posting Lists"]
    SEG --> BKD["2. BKD-Trees Multidimensionales (Filtros Numéricos, Fechas y GeoPoints)"]
    SEG --> DV["3. DocValues Columnar en Page Cache (Sorting y Aggregations sin Heap)"]
    SEG --> HNSW["4. Grafos k-NN HNSW Cuantizados (Búsqueda Vectorial Densa)"]

2. Compresión de Listas de Posteo y Roaring Bitmaps para Filtros Booleanos

Cuando combinas múltiples filtros (status = "ACTIVE" AND country = "CL" AND price BETWEEN 100 AND 500), Lucene representa los conjuntos de DocIDs de $32$ bits dividiendo el espacio en bloques de $2^{16} = 65,536$ enteros (Roaring Bitmaps): - Si el bloque tiene $< 4,096$ documentos: usa un arreglo ordenado compacto de enteros de $16$ bits ($2\text{ bytes/doc}$). - Si el bloque tiene $\ge 4,096$ documentos: usa un Bitset fijo de $65,536$ bits ($8\text{ KB}$) donde la intersección AND se ejecuta mediante una sola instrucción vectorial VPAND de CPU.'

Matemática de Relevancia Léxica Okapi BM25: Saturación No Lineal de Frecuencia y Penalización de Longitud (55 min)

Matemática de Relevancia Léxica Okapi BM25: Saturación No Lineal de Frecuencia y Penalización de Longitud

1. Por qué TF-IDF Clásico Falla y cómo Okapi BM25 Domina la Recuperación Léxica

En TF-IDF tradicional, un documento que repite la palabra "hipoteca" $20$ veces recibe un puntaje diez veces mayor que uno que la menciona $2$ veces. Okapi BM25 introduce dos hiperparámetros críticos ($k_1 \approx 1.2$, $b \approx 0.75$) para acotar asintóticamente la contribución de repeticiones y normalizar por la longitud del documento $|D|$ respecto al promedio del corpus $\text{avgdl}$:

$\text{BM25}(D, Q) = \sum_{t \in Q} \text{IDF}(t) \cdot \frac{f(t, D) \cdot (k_1 + 1)}{f(t, D) + k_1 \cdot \left( 1 - b + b \cdot \frac{|D|}{\text{avgdl}} \right)}$

donde la frecuencia inversa de documento suavizada de Lucene garantiza valores no negativos:

$\text{IDF}(t) = \ln \left( 1 + \frac{N - n(t) + 0.5}{n(t) + 0.5} \right)$

export function computeLuceneBm25TermScore(params: {
  totalDocsN: number;
  docFreqNt: number;
  termFreqInDoc: number;
  docLength: number;
  avgDocLength: number;
  k1?: number;
  b?: number;
}): number {
  const k1 = params.k1 ?? 1.2;
  const b = params.b ?? 0.75;

const idf = Math.log( 1.0 + (params.totalDocsN - params.docFreqNt + 0.5) / (params.docFreqNt + 0.5) );

const normLength = 1.0 - b + b * (params.docLength / params.avgDocLength); const tfSaturated = (params.termFreqInDoc (k1 + 1.0)) / (params.termFreqInDoc + k1 normLength);

return Number((idf * tfSaturated).toFixed(6)); }

Ciclo de Vida Near-Real-Time (NRT): Translog, Refresh Interval, Segment Merging y Dimensionamiento de Shards (50 min)

Ciclo de Vida Near-Real-Time (NRT): Translog, Refresh Interval, Segment Merging y Dimensionamiento de Shards

1. Diferencia Crítica entre Refresh (Visibilidad de Búsqueda) y Flush (Durabilidad en Disco)

1. Indexación + Translog: Al indexar un documento, se escribe en un buffer en memoria y se anexa al Translog (Write-Ahead Log en disco para durabilidad ante caídas). En este instante el documento aún NO es buscable. 2. Refresh (cada 1s por defecto): El buffer en memoria se convierte en un nuevo segmento inmutable de Lucene en el Page Cache del sistema operativo y se abre para búsqueda. - Optimización de Ingesta Masiva: Durante cargas pesadas, cambiar index.refresh_interval: "30s" evita generar miles de micro-segmentos y multiplica el throughput de indexación por $4\times$. 3. Flush: Vacía el Translog confirmado y sincroniza los segmentos en disco con fsync.

2. Prevención de Oversharding (Regla de los 20 a 50 GB por Shard)

Cada shard de Elasticsearch consume estructuras fijas en el Heap de la JVM y descriptores de archivos. Tener $1,000$ shards de $50\text{ MB}$ cada uno ("Oversharding") degrada gravemente el clúster; el punto óptimo de ingeniería para búsqueda de baja latencia es mantener cada shard entre $20\text{ GB}$ y $50\text{ GB}$ utilizando Index Lifecycle Management (ILM) y Rollover.

Módulo 3: Módulo 3: Redis Cluster de Ultra-Baja Latencia, Estructuras Probabilísticas y Consistencia

Topología de Redis Cluster: 16,384 Hash Slots (CRC16), Enrutamiento Determinista con Hash Tags y Protocolo Gossip (55 min)

Topología de Redis Cluster: 16,384 Hash Slots (CRC16), Enrutamiento Determinista con Hash Tags y Protocolo Gossip

1. Particionamiento por Slots Fijos ($ ext{CRC16}( ext{key}) \bmod 16384$)

En lugar de usar hashing consistente puro con tokens aleatorios, Redis Cluster divide el espacio de claves en exactamente $16,384$ Hash Slots distribuidos entre los nodos maestros:

$\text{Slot}(k) = \text{CRC16}(k) \;\&\; \text{0x3FFF}$

2. Transacciones Multi-Clave y Scripts Lua mediante {Hash Tags}

En Redis Cluster, ejecutar una operación atómica MGET, SUNION o un script EVAL sobre dos claves que residen en distintos Hash Slots arroja inmediatamente el error CROSSSLOT Keys in request don't hash to the same slot.

Para garantizar colocalización determinista en el mismo nodo maestro sin perder el particionamiento del resto de las entidades, encerramos el identificador de partición entre llaves {...}. Redis calcula el CRC16 exclusivamente sobre la subcadena dentro del primer par de llaves {...}: - cart:{user_8841}:items $\to \text{CRC16}(\text{"user\_8841"}) \bmod 16384$ - cart:{user_8841}:totals $\to \text{CRC16}(\text{"user\_8841"}) \bmod 16384$ (Mismo Slot garantizado).'

Resiliencia de Caché a Escala: Prevención de Cache Stampede (Algoritmo XFetch), Penetration (Bloom) y Avalanche (55 min)

Resiliencia de Caché a Escala: Prevención de Cache Stampede (Algoritmo XFetch), Penetration (Bloom) y Avalanche

1. Anatomía de una Estampida de Caché (Thundering Herd / Cache Stampede)

Cuando expira en el mismo milisegundo la clave en caché de la portada de un diario o un producto en oferta con $15,000\text{ req/s}$ concurrentes, los $15,000$ hilos ven CACHE MISS simultáneamente y lanzan $15,000$ consultas pesadas contra PostgreSQL, tumbando la base de datos primaria.

2. Expiración Anticipada Probabilística Óptima (Algoritmo XFetch — Vattani et al.)

En lugar de esperar a que el $\text{TTL}$ llegue a cero para que todos los lectores fallen al mismo tiempo, cada lector evalúa en tiempo de lectura una desigualdad estocástica basada en el tiempo que tardó en computarse el valor ($Delta_{\text{compute}}$) y una variable aleatoria exponencial ($-\ln(\text{rand}()) $):

$t_{\text{now}} - \left( \Delta_{\text{compute}} \cdot \beta \cdot \ln(\text{rand}()) \right) \ge t_{\text{expiry}}$

Un único hilo gana probabilísticamente antes de la expiración real y refresca la clave en segundo plano mientras los demás $14,999$ hilos siguen recibiendo el valor en caché con $0\text{ ms}$ de bloqueo.

export interface XFetchCacheEntry {
  payload: T;
  computeDurationMs: number;
  expiryEpochMs: number;
}

/ * Evalúa según el algoritmo óptimo XFetch si el hilo actual debe * recomputar proactivamente la entrada antes de que expire el TTL físico. */ export function shouldTriggerXFetchEarlyRecompute( entry: XFetchCacheEntry, nowEpochMs: number, beta = 1.0, randomUniform01 = Math.random() ): boolean { const safeRand = Math.max(1e-9, Math.min(1.0, randomUniform01)); const stochasticLeadTimeMs = -entry.computeDurationMs beta Math.log(safeRand); return nowEpochMs + stochasticLeadTimeMs >= entry.expiryEpochMs; }

Sincronización Distribuida Segura: Crítica de Martin Kleppmann a Redlock, Leases y Monotonic Fencing Tokens (50 min)

Sincronización Distribuida Segura: Crítica de Martin Kleppmann a Redlock, Leases y Monotonic Fencing Tokens

1. Por qué un Lock con TTL Simple No Garantiza Exclusión Mutua Correcta

Supongamos que el Cliente 1 adquiere un lock en Redis con $\text{TTL} = 10\text{ segundos}$, pero sufre una pausa de Garbage Collection o de hipervisor de $12\text{ segundos}$ justo antes de escribir en el almacenamiento compartido: 1. En el segundo $10$, el lock en Redis expira automáticamente. 2. En el segundo $11$, el Cliente 2 adquiere legítimamente el lock y escribe en el almacenamiento. 3. En el segundo $12$, el Cliente 1 despierta de su pausa (creyendo que aún tiene el lock) y sobrescribe y corrompe los datos del Cliente 2.

sequenceDiagram
    participant C1 as Cliente 1 (Pausa GC 12s)
    participant C2 as Cliente 2
    participant LOCK as Lock Service (Fencing Counter)
    participant DB as Storage Engine (Valida Fencing Token)
    C1->>LOCK: Acquire Lock -> Recibe Fencing Token #33
    Note over C1: Pausa de GC / Red (Expira TTL)
    C2->>LOCK: Acquire Lock -> Recibe Fencing Token #34
    C2->>DB: Write(Data, token=34) -> ACEPTADO (max_token = 34)
    C1->>DB: Write(StaleData, token=33) -> RECHAZADO (33 < 34!)

2. La Solución Inmutable: Monotonic Fencing Tokens Validados en el Recurso Destino

Cada adquisición de lock debe devolver un entero estrictamente creciente (Fencing Token $f_t$). El almacenamiento o base de datos destino rechaza cualquier operación cuyo token sea inferior al máximo token ya procesado para esa entidad ($f_{\text{entrante}} < f_{\text{max\_visto}}$).'

Módulo 4: Módulo 4: Modelado Políglota, Auditoría de Shards y Certificación Práctica

Arquitectura de Persistencia Políglota: Sincronización Consistente entre PostgreSQL/MongoDB, Elasticsearch y Redis (50 min)

Arquitectura de Persistencia Políglota: Sincronización Consistente entre PostgreSQL/MongoDB, Elasticsearch y Redis

1. Asignación de Cargas de Trabajo por Estructura Física de Acceso

| Motor de Datos | Estructura Primaria en Disco/RAM | Patrón de Acceso Óptimo | Anti-Patrón Prohibido | | :--- | :--- | :--- | :--- | | MongoDB Sharded Cluster | Árboles B+ WiredTiger + Documentos BSON | Agregados jerárquicos polimórficos accedidos por Shard Key | Búsqueda de texto libre con expresiones regulares /$term/i sin índice invertido | | Elasticsearch (Lucene) | FST + Posting Lists BM25 + BKD-Trees + HNSW | Búsqueda híbrida léxica/vectorial y facetas en tiempo real | Usarlo como fuente de verdad transaccional primaria (OLTP bancario) | | Redis Cluster | Tablas Hash en RAM + SkipLists (ZSET) + HyperLogLog | Estado efímero sub-milisegundo, Rate Limiting atómico, Leaderboards | Almacenar blobs pesados ($> 100\text{ KB}$) que bloqueen el hilo único de comandos |'

Laboratorio Práctico SQL: Detección de Hot Shards / Jumbo Chunks en MongoDB y Eficiencia de Redis Cluster (60 min)

Laboratorio Práctico SQL: Detección de Hot Shards / Jumbo Chunks en MongoDB y Eficiencia de Redis Cluster

Contexto del Laboratorio

Monitoreas la telemetría consolidada de un clúster MongoDB Sharded en la tabla mongodb_shard_telemetry (collection_name, shard_id, doc_count, write_ops_sec, jumbo_chunks_count, wiredtiger_dirty_pct).

Reglas de Negocio a Consultar

Para cada collection_name, calcula: 1. shard_count: cantidad de shards que alojan la colección (COUNT(*)::INT). 2. max_shard_write_share_pct: el porcentaje del tráfico total de escritura de la colección que recae sobre su shard más cargado, redondeado a 2 decimales (ROUND((MAX(write_ops_sec)::NUMERIC / SUM(write_ops_sec)) * 100.0, 2)). 3. total_jumbo_chunks: suma de jumbo_chunks_count en la colección (SUM(jumbo_chunks_count)::INT). 4. max_wt_dirty_pct: el máximo porcentaje de caché sucia de WiredTiger observado en cualquiera de sus shards (MAX(wiredtiger_dirty_pct)). 5. cluster_diagnosis: - 'HOT_SHARD_MONOTONIC_KEY' si max_shard_write_share_pct >= 75.00. - 'JUMBO_CHUNK_LOW_CARDINALITY' si no es Hot Shard pero total_jumbo_chunks > 0. - 'HEALTHY_BALANCED_SHARDS' en caso contrario. 6. Ordena el resultado por max_shard_write_share_pct DESC.

Examen de Certificación: Principal Distributed NoSQL, Search & In-Memory Architect (DB-701) (45 min)

Examen de Certificación: Principal Distributed NoSQL, Search & In-Memory Architect (DB-701)

Valida tu dominio técnico sobre MongoDB Sharded Clusters y WiredTiger, estructuras internas de Apache Lucene / Elasticsearch y arquitectura de Redis Cluster de alta concurrencia.