Saltar al contenido

072 — Persistencia políglota: decidir por evidencia y no por moda

🗂️ parte 🎚️ nivel ⏱️ duración 📗 clase

Programa · Parte 14 · ← Anterior · Siguiente →

Parte 14 — Arquitectura y proyecto final · Avanzado · 3 horas estimadas · motores postgresql, mongodb, redis, qdrant · laboratorio labs/02-polyglot-modeling · 3 fuentes.

Conceptos centrales: carga de trabajo · criterio de selección · costo de operación · complejidad añadida

En este caso se comparan 7 motores: 5 lo resuelven (0 con el resultado comprobado por máquina) y 2 no, con el motivo escrito.

De qué trata esta clase

La elección de motores hecha como decisión técnica: carga de trabajo cuantificada, criterio de comparación escrito antes de mirar los productos, y la complejidad añadida contada como lo que cuesta —otro modelo de fallo, otro respaldo, otra guardia. Es la clase que convierte las catorce partes anteriores en un método de decisión.

flowchart LR
    C["🗄️ Clase 072"]
    C --> K1["carga de trabajo"]
    C --> K2["criterio de selección"]
    C --> K3["costo de operación"]
    C --> K4["complejidad añadida"]
    classDef raiz fill:#0b3d2e,stroke:#3fb950,color:#fff
    class C raiz

Antes de empezar

Esta clase supone que ya trabajaste lo siguiente. Si algo de la última columna no te suena, vuelve a esa clase antes de seguir: aquí se usa sin volver a explicarlo.

# Clase previa Lo que se da por sabido
010 El mapa de los motores: seis familias y un criterio familias de motores · modelo de agregado · patrón de acceso · multimodelo
064 OLTP frente a OLAP: por qué se separan carga transaccional · carga analítica · contención · formato de almacenamiento

Vocabulario de la clase

Los términos que siguen se usan más adelante con este significado exacto. La definición completa, con sus términos relacionados, está en el glosario del programa.

Término Qué significa Procedencia
carga de trabajo La descripción cuantificada de lo que el sistema tendrá que aguantar: volumen, proporción de lecturas y escrituras, latencia objetivo, consultas dominantes, crecimiento previsto. Es lo que convierte la elección de motor en una decisión técnica y no en una preferencia. se introduce aquí
criterio de selección La lista escrita de propiedades que se van a comparar entre candidatos, con su peso, fijada antes de mirar los productos. Escribirla después es escribir la justificación de lo que ya se había decidido. se introduce aquí
costo de operación Todo lo que cuesta mantener vivo un motor después de instalarlo: respaldos probados, actualizaciones, monitorización, personas de guardia. Suele superar con creces el costo de licencia o de cómputo. se introdujo en la 009
complejidad añadida Lo que cuesta cada sistema adicional: otro modelo de fallo, otro respaldo, otra guardia, otra consistencia que reconciliar. Es el argumento más fuerte a favor de un solo motor multimodelo mientras la carga lo permita. se introduce aquí

Propósito

Decidir cuántos almacenes de datos usar. Cada motor añadido resuelve un problema y crea varios permanentes; el criterio debe ser una medición, no una tendencia.

Resultados de aprendizaje

Al terminar podrás:

  1. Caracterizar una carga de trabajo con números antes de elegir motor.
  2. Enumerar el costo permanente de añadir un almacén.
  3. Aplicar la regla de agotar primero las capacidades del motor existente.
  4. Diseñar la sincronización entre almacenes y su verificación.
  5. Defender una arquitectura de un solo motor cuando corresponda.

Fundamentos

Caracterizar antes de elegir

Sadalage y Fowler acuñaron «persistencia poliglota»: usar el almacén adecuado a cada carga. La parte que se cita menos es que cada almacén adicional es un sistema completo que operar.

La caracterización necesaria, con números reales:

Dimensión Pregunta
Volumen ¿Cuántos datos hoy y en 3 años?
Caudal Lecturas/s y escrituras/s, con su pico
Patrón de acceso ¿Por clave, por rango, agregado, recorrido, similitud?
Latencia exigida p50 y p99, por operación
Consistencia ¿Qué invariantes deben ser atómicas?
Ciclo de vida ¿Retención, expiración, histórico?
Consultas no previstas ¿Con qué frecuencia aparecen?

Sin estas cifras, la elección de motor es una preferencia.

El costo permanente

Añadir un almacén cuesta, para siempre:

Costo Detalle
Operación Actualizar, parchear, dimensionar, vigilar
Copias Otro plan de respaldo y otra prueba de restauración (clase 048)
Seguridad Otro modelo de control de acceso, otras credenciales
Sincronización Un canal que puede desincronizarse (clase 056)
Consistencia Invariantes que cruzan almacenes y ya no son atómicas (clase 047)
Conocimiento Alguien debe saber operarlo; y su suplente también
Guardia Otra fuente de incidentes de madrugada

Regla del programa: un almacén nuevo debe resolver un problema medido que el actual no puede resolver, no un problema anticipado.

Agotar primero lo que ya se tiene

PostgreSQL cubre por sí solo una superficie sorprendente:

Necesidad Motor «natural» PostgreSQL
Documentos MongoDB jsonb + GIN (clase 021)
Clave-valor Redis UNLOGGED + índice hash
Búsqueda de texto OpenSearch tsvector + GIN (clase 031)
Vectores Qdrant pgvector (clase 059)
Series temporales InfluxDB TimescaleDB (clase 030)
Cola de trabajos RabbitMQ SELECT ... FOR UPDATE SKIP LOCKED
Analítica ClickHouse Réplica + agregados, o Parquet + DuckDB
Grafos Neo4j CTE recursiva (clase 028)

Ninguna de estas alternativas es la mejor en su especialidad. Todas son suficientes hasta cierto punto, y ese punto está mucho más lejos de lo que se supone. La pregunta correcta no es «¿cuál es el mejor motor para X?», sino «¿ha dejado de bastarme el que ya opero?».

flowchart TD
    C["Carga caracterizada<br/>con números"] --> A{"¿El motor actual<br/>la sostiene?"}
    A -- "Sí" --> OK["No añadir nada"]
    A -- "No" --> B{"¿Con una extensión<br/>o un cambio de diseño?"}
    B -- "Sí" --> EXT["Extensión · índice ·<br/>modelo (medir de nuevo)"]
    B -- "No" --> D{"¿El costo permanente<br/>es menor que la ganancia<br/>MEDIDA?"}
    D -- "No" --> OK
    D -- "Sí" --> E["Añadir almacén<br/>+ canal + verificación<br/>+ copias + guardia"]
    E --> F["Registrar la decisión<br/>y su criterio de reversión"]

Ejemplo trabajado

Plataforma educativa. Caracterización real de sus cinco cargas:

# Carga Volumen Caudal Patrón Latencia Consistencia
1 Inscripciones y notas 5 M filas 300 l/s, 5 e/s Relacional, reuniones p99 < 50 ms Transaccional
2 Sesiones 50 k activas 2 000 l/s, 200 e/s Clave-valor, TTL p99 < 5 ms Se puede perder
3 Búsqueda de contenido 200 k fragmentos 50 c/s Léxica + semántica p99 < 200 ms Desfase de minutos
4 Panel de dirección 5 M filas 12 c/día Agregación completa < 30 s Desfase de horas
5 Telemetría de uso 200 M eventos/año 3 000 e/s Escritura, ventanas escritura < 10 ms Se puede perder

Análisis carga por carga:

1. PostgreSQL. No hay discusión: transacciones, integridad, reuniones.

2. Sesiones. ¿Redis? Primero la medición sobre PostgreSQL:

CREATE UNLOGGED TABLE sesiones (   -- UNLOGGED: sin WAL, mucho más rápido, se pierde al caer
  token      TEXT PRIMARY KEY,
  student_id INTEGER NOT NULL,
  expira_en  TIMESTAMPTZ NOT NULL
);
CREATE INDEX ON sesiones (expira_en);
Medición: 2 000 lecturas/s por clave primaria → p99 = 1,8 ms
Exigencia: p99 < 5 ms  ✔
Veredicto: NO añadir Redis. PostgreSQL cumple con margen.

UNLOGGED es la clave: sin WAL, y perder las sesiones al caer el servidor ya se había declarado aceptable.

3. Búsqueda. Léxica con tsvector y semántica con pgvector, sobre 200 000 fragmentos:

Medición: híbrida RRF (clase 060) → p99 = 84 ms, recall@5 = 0,89
Exigencia: p99 < 200 ms  ✔
Veredicto: NO añadir OpenSearch ni Qdrant.
Revisión: si el corpus llega a 5 M de fragmentos, volver a medir.

La última línea es tan importante como el veredicto: la decisión lleva su condición de revisión escrita.

4. Panel. Ejecutado sobre el primario degradaba el OLTP (clase 054):

Opción A: réplica de solo lectura       → 47 s, sin impacto en OLTP  ✔
Opción B: exportación a Parquet+DuckDB  → 1,2 s, sin servidor nuevo  ✔✔
Veredicto: opción B. Coste: un trabajo programado.

5. Telemetría. 3 000 escrituras/s, 200 M de eventos al año:

Medición sobre PostgreSQL con TimescaleDB:
  ingesta sostenida: 3 000/s ✔
  almacenamiento con compresión y retención por niveles (clase 030): 34 GB/año ✔
  consulta de ventana con agregado continuo: 180 ms ✔
Veredicto: TimescaleDB, que es una extensión del motor que YA se opera.
Coste marginal: una extensión, no un sistema.

Arquitectura resultante:

PostgreSQL 16
  + TimescaleDB      (telemetría)
  + pgvector         (búsqueda semántica)
  + tsvector/GIN     (búsqueda léxica)
  + tabla UNLOGGED   (sesiones)
  + réplica de solo lectura
  + exportación nocturna a Parquet, consultada con DuckDB

Almacenes que operar: UNO.

Frente a la arquitectura «de manual» —PostgreSQL + Redis + OpenSearch + Qdrant + ClickHouse + InfluxDB, seis sistemas—, esta cumple todas las exigencias medidas con uno.

Y cuándo dejaría de bastar, escrito por adelantado:

Umbral Motor a añadir
Sesiones > 20 000 lecturas/s Redis
Corpus > 5 M fragmentos o recall < 0,85 Qdrant
Telemetría > 50 000 escrituras/s ClickHouse
Panel sobre > 500 GB o varias fuentes Almacén columnar

Este es el entregable de la clase: no una arquitectura, sino una arquitectura con sus condiciones de cambio. Cada umbral es medible y cada uno tiene una alerta.

Comparación

Enfoque Sistemas Coste operativo Rendimiento por carga Riesgo
Un motor con extensiones 1 Bajo Bueno en todo, óptimo en nada Techo por carga
Poliglota por evidencia 2–3 Medio Óptimo donde se midió Sincronización
Poliglota por tendencia 5+ Alto Óptimo y desaprovechado Divergencia, guardia, conocimiento

Errores frecuentes

  1. Elegir motor por la arquitectura de otra empresa. Sus cifras no son las tuyas.
  2. No caracterizar la carga. Sin números, la elección es estética.
  3. Ignorar el costo permanente. Se contabiliza el desarrollo y no la operación.
  4. Añadir un almacén sin canal verificado. Divergencia silenciosa.
  5. Suponer que PostgreSQL no sirve. Suele servir mucho más allá de lo que se cree.
  6. No escribir la condición de revisión. La decisión se vuelve dogma.
  7. Optimizar para un volumen que no se tiene. Complejidad hoy por un problema hipotético.

De la clase a la operación

Cada almacén añadido multiplica los estados posibles del sistema, y por tanto los modos de fallo. Un equipo pequeño con seis almacenes no los opera bien: los tiene. La arquitectura defendible es la que el equipo puede sostener a las tres de la mañana.

Reto de transferencia

  1. Caracteriza las cinco cargas principales de tu sistema con números reales.
  2. Mide cada una contra tu motor actual, con las extensiones disponibles.
  3. Justifica cada almacén adicional con la medición que lo hace necesario.
  4. Escribe los umbrales de revisión y configura una alerta para cada uno.

Preguntas de evaluación

  1. Enumera el costo permanente de añadir un almacén a tu sistema.
  2. Da una carga tuya que hoy usa un motor especializado y podría volver al principal.
  3. ¿Qué medirías antes de añadir un motor de búsqueda dedicado?
  4. Escribe el umbral de revisión de una de tus decisiones y cómo lo vigilarías.

🌐 El mismo problema en cada motor

Caso: Un sistema real con cinco cargas distintas, y la pregunta de si hacen falta cinco motores

Una plataforma educativa tiene cinco cargas: inscripciones (transaccional, con invariantes que no se pueden romper), sesiones (clave-valor, alta frecuencia, desechable), búsqueda del catálogo (texto, con relevancia), panel de dirección (analítico, sobre todo el histórico) y recomendación semántica (vectorial).

La respuesta fácil es un motor por carga. Es casi siempre la respuesta equivocada, porque el costo de la persistencia políglota no está en licencias ni en servidores: está en la coherencia entre sistemas, que ninguna transacción cubre, y en las cinco formas distintas de respaldar, monitorizar, actualizar y contratar personal.

La regla que esta parte defiende: un motor entra en la arquitectura cuando hay evidencia medida de que el anterior no puede, y esa evidencia se anota en un registro de decisión. Aquí se compara qué carga justifica de verdad a cada motor y a partir de qué umbral.

Esta comparación es conceptual: la decisión no se reduce a una consulta con resultado, así que aquí no hay sello de máquina. Lo que se compara es lo que cada motor ofrece y a qué precio, con la página oficial al lado de cada afirmación.

Motor ¿Resuelve el caso? Nivel de prueba Código Fuente
PostgreSQL conceptual doc oficial
Redis conceptual doc oficial
OpenSearch conceptual doc oficial
DuckDB conceptual doc oficial
Qdrant conceptual doc oficial
MongoDB no doc oficial
Apache Cassandra no doc oficial

Los que resuelven el caso

PostgreSQL

Redis

OpenSearch

DuckDB

Qdrant

Los que no resuelven este caso — y qué se hace en su lugar

Descartar un motor con un argumento es tan formativo como usarlo. Ninguna de estas filas dice que el motor sea peor: dice que este problema no es el suyo.

Motor Por qué no Qué se hace en su lugar Fuente
MongoDB En este sistema no hay ninguna carga que lo justifique frente a PostgreSQL: lo semiestructurado cabe en jsonb con índices GIN y con las transacciones y las claves foráneas del resto del esquema al lado. Añadirlo sería sumar un sistema sin una carga que solo él resuelva. Se justifica cuando el modelo documental es el dominio —agregados grandes que se leen y escriben enteros y no se relacionan entre sí— o cuando el esquema tiene que evolucionar sin migraciones coordinadas. Esa es la evidencia que hay que aportar, no la preferencia. doc
Apache Cassandra Su modelo cuesta lo que cuesta —una tabla por consulta, sin reuniones, sin transacciones, con reparaciones periódicas— y solo se paga cuando el volumen de escritura supera lo que un nodo primario puede absorber. Una plataforma educativa no llega ahí, y adoptarlo «por si crecemos» es pagar el costo sin recibir el beneficio. Particionado dentro de PostgreSQL y réplicas de lectura, que cubren un crecimiento de dos órdenes de magnitud sin cambiar de modelo de datos ni de forma de razonar. doc

Laboratorio

python scripts/validate_repository.py
# labs/02-polyglot-modeling se entrega escrito: no hay guion que ejecutar

Guarda como evidencia la salida completa, la versión del motor y la semilla o los parámetros usados. Una captura sin comando no es evidencia: no se puede repetir.

Evaluación

Criterio Peso Qué se comprueba
Comprensión conceptual 25 % Explica el mecanismo, no solo el resultado
Ejecución reproducible 25 % Otra persona obtiene lo mismo con las instrucciones dadas
Interpretación basada en evidencia 25 % Cada conclusión se apoya en una salida o una medición
Límites y riesgos declarados 25 % Dice qué no demuestra el ejercicio y qué faltaría en producción

La clase se da por superada cuando la respuesta explica el mecanismo, muestra la salida que la respalda y declara al menos un límite del ejercicio.

Fuentes de esta clase

Todo lo afirmado arriba procede de estas obras. Los identificadores viven en catalog/sources.json y el estado de los enlaces se comprueba con python scripts/check_external_links.py.


Programa · Parte 14 · ← Anterior · Siguiente →