Cómo elegir una base de datos
En resumen: Elige una base de datos según tus datos y las preguntas que les haces: una base relacional guarda la verdad, y otro tipo se añade a su lado solo donde ella es débil.
Problema
Muchos tipos de bases de datos sobre la mesa
El módulo ha mostrado clave-valor, documentos, flujos de eventos, grafos, columnas y series temporales, búsqueda de texto, vectores y clústeres. Cada una de estas bases de datos es excelente en algo. El nuevo proyecto de la tienda pregunta: ¿cuál elegimos?
El camino equivocado es empezar por la base de datos («usamos MongoDB porque es moderna»). El camino correcto es empezar por los datos y por las preguntas que les harás, y añadir una base de datos nueva solo cuando haya una pregunta que la principal no responde bien.
La idea
Cinco preguntas
- ¿Qué forma tienen los datos? Filas con las mismas columnas, documentos enteros con partes anidadas, conexiones entre cosas, registros a lo largo del tiempo, texto libre.
- ¿Qué preguntas? Una cosa por clave, un informe sobre todo, «quién está conectado con quién», «qué palabras», «qué es parecido».
- ¿Qué importancia tiene la consistencia? El dinero y el stock necesitan transacciones y leer la última escritura. Un contador de «me gusta» puede ir con un segundo de retraso.
- ¿Qué tamaño tienen los datos y la carga? Miles o miles de millones de filas, diez o cien mil consultas por segundo. La mayoría de los proyectos son mucho más pequeños de lo que creen.
- ¿Quién se va a ocupar? Cada base de datos necesita copias, monitorización, actualizaciones y alguien que sepa qué hacer cuando se cae.
| La pregunta | Tipo de base de datos |
|---|---|
| Dinero, pedidos, stock, todo lo que tiene que estar bien | relacional (PostgreSQL, MySQL) |
| Lo mismo por clave miles de veces, sesiones, carritos | clave-valor (Redis) |
| Objetos enteros con estructuras distintas | documental (MongoDB) o JSONB |
| Muchos sistemas se enteran del mismo evento | flujo de eventos (Kafka) |
| Recomendaciones, redes, fraude a través de varios pasos | de grafos (Neo4j) |
| Informes sobre millones de filas, sensores, métricas | columnar, series temporales (ClickHouse, TimescaleDB) |
| Un cuadro de búsqueda, palabras en un texto | búsqueda de texto completo (Elasticsearch) |
| «Parecido en significado», un chatbot sobre documentos | vectorial (pgvector, Qdrant) |
La idea
Empieza con PostgreSQL
Para la mayoría de los proyectos nuevos, el comienzo sensato es una sola base de datos relacional. PostgreSQL por sí sola cubre buena parte del módulo: columnas JSONB para documentos, tsvector para la búsqueda de texto, pgvector para vectores, TimescaleDB para series temporales, réplicas de lectura y la partición de tablas por tiempo. No es la mejor en cada una de ellas, pero es suficientemente buena, y una base de datos es mucho más fácil de mantener que cinco.
Una base de datos aparte compensa cuando hay una pregunta concreta que la principal responde despacio o con dificultad, y lo has medido: la caché, porque la misma consulta se ejecuta 2000 veces por minuto; el buscador, porque la búsqueda es la tarea principal del sitio; el almacén de datos, porque los informes frenan la caja.
Más precisamente, para curiosos
Esto se llama persistencia políglota: distintas bases de datos para distintas partes del sistema. Funciona bien cuando una de ellas es claramente la fuente de la verdad y las otras son copias que se pueden reconstruir. Funciona mal cuando dos bases de datos guardan partes distintas de la misma verdad y nadie sabe cuál tiene razón.
Míralo
La tienda cuando crece
Así podría verse la tienda al cabo de unos años, con cada parte añadida cuando hizo falta:
- PostgreSQL guarda los clientes, los productos, los pedidos y los pagos. Esta es la verdad. Tiene una réplica síncrona y una asíncrona.
- Redis guarda la caché del catálogo, las sesiones y los carritos.
- Kafka lleva los eventos «pedido realizado», «pagado», «enviado» al almacén, a las facturas y al correo electrónico.
- Elasticsearch guarda una copia de los productos y las reseñas para el cuadro de búsqueda. Se llena a partir de los eventos.
- ClickHouse es el almacén de datos para los informes. Se llena cada noche o desde el flujo.
- pgvector en la misma PostgreSQL da «productos parecidos».
Todas las copias vienen de PostgreSQL. Si se rompe el índice de Elasticsearch, se reconstruye desde la base de datos. Al revés no se puede.
Problema
Las dos escrituras
El pedido se escribe en PostgreSQL, y después el programa envía un evento a Kafka. Si el programa se cae entre las dos cosas, el pedido está en la base de datos, pero falta el evento: el almacén nunca prepara el paquete. Si es al revés (primero el evento, después la base de datos), el almacén puede preparar un paquete para un pedido que luego fracasó.
Los dos sistemas no tienen una transacción común. La solución es una tabla outbox: el evento se escribe en una tabla de la misma base de datos, en la misma transacción que el pedido. O se escriben los dos, o ninguno.
published_at es NULL: el evento aún no se ha enviado. Un programa aparte (un relay) lee los no enviados en orden, los envía al flujo y solo entonces los marca:
Y si algo falla antes del COMMIT, no hay ni cambio ni evento:
Más precisamente, para curiosos
El relay puede caerse después de haber enviado el evento pero antes de marcarlo. Entonces, al reiniciarse, lo envía otra vez. Por eso los consumidores del evento tienen que ser idempotentes, como las facturas en la lección sobre flujos. En lugar de un programa que lea la tabla, se suele usar Debezium: lee el registro de cambios de PostgreSQL (el WAL) y envía cada fila nueva de la outbox a Kafka. Esto se llama captura de cambios de datos (change data capture, CDC).
Pruébalo
Cancelar un pedido
El cliente cancela el pedido 42. En una transacción cambia su estado a cancelled y escribe un evento en la outbox para el tema orders con la clave order-42 y el contenido {"order":42,"event":"cancelled"}.
Solución
BEGIN;
UPDATE orders SET status = 'cancelled' WHERE id = 42;
INSERT INTO outbox (topic, key, payload)
VALUES ('orders', 'order-42', '{"order":42,"event":"cancelled"}');
COMMIT;BEGIN, UPDATE orders SET status = 'cancelled' WHERE id = 42, INSERT INTO outbox (topic, key, payload) VALUES ('orders', 'order-42', '…') y COMMIT. Solo el UPDATE cambia el estado, pero el almacén y el correo nunca se enteran. La clave es el número del pedido, para que la cancelación vaya a la misma partición que «realizado» y «pagado» y se lea después de ellos.
Ejercicio
Cinco proyectos
Para cada proyecto: ¿qué base de datos es el cimiento, o qué añades a su lado?
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Bases de datos columnares y series temporales · Buscar en el texto · Buscar por significado · Replicación, sharding y CAP
- Ejercicios sobre el tema: Tipos de bases de datos