Introducción
Las bases de datos NoSQL cambian la consistencia estricta y el modelo relacional de SQL por flexibilidad, crecimiento horizontal y patrones de acceso especializados. Elegir la correcta significa casar la forma de tus datos, tus patrones de consulta y tus requisitos de consistencia con el store adecuado.
Las Cuatro Familias NoSQL
| Familia | Estructura | Ideal para | Ejemplos |
|---|---|---|---|
| Documentos | Documentos tipo JSON con estructuras anidadas | Gestión de contenidos, perfiles de usuario, catálogos | MongoDB, Firestore, Couchbase |
| Clave-Valor | Búsquedas simples clave → valor | Sesiones, caching, feature flags | Redis, DynamoDB, Riak |
| Columnas Anchas | Familias de columnas con filas como mapas dispersos | Series de tiempo, telemetría de alta escritura, mensajería | Cassandra, HBase, ScyllaDB |
| Grafos | Nodos y relaciones con propiedades | Redes sociales, motores de recomendación, detección de fraude | Neo4j, Amazon Neptune |
Algo que todas comparten: la consistencia ajustable. Cassandra, MongoDB y DynamoDB te permiten graduar el compromiso por operación — escribe en una réplica por velocidad (W=1, R=1), exige un quórum por corrección (W+R>N), o paga por lecturas estrictas (DynamoDB ConsistentRead, MongoDB majority). Elige el store primero, pero ajusta las perillas de consistencia a propósito.
Un Proceso de Selección que Funciona
La mayoría de equipos elige la base de datos NoSQL al revés — empiezan con el nombre de un producto y luego doblan sus datos para que encajen. El orden que evita reescrituras:
- Lista tus patrones de acceso, no tus entidades. “Obtener usuario por ID, obtener posts por autor ordenados por fecha, contar notificaciones sin leer” — esa lista es lo que la base realmente sirve. Las entidades cambian; los patrones de acceso, no.
- Elimina por restricción dura. ¿Todos los datos caben en RAM? → Redis viable, pero no como store primario. ¿Las consultas son mayormente traversias de relaciones? → store de grafos. ¿La tasa de escritura es de miles por segundo sostenidas? → columnas anchas. La mayoría de opciones muere aquí, antes de cualquier benchmark.
- Haz prototipos de los dos candidatos finales con tu distribución real de claves — en forma de Zipf, no uniforme. Un store que rinde plano bajo carga uniforme suele desarmarse con sesgo real.
- Decide la carga operativa. DynamoDB no tiene clúster que cuidar; Cassandra necesita un equipo que entienda repairs, compaction y gossip. La prima de un servicio gestionado suele salir más barata que la rotación de guardias.
La matriz de decisión más abajo comprime el paso 2. Para el paso 3, mira la sección de benchmarking — las pruebas sintéticas mienten más de lo que cuentan.
Documentos: MongoDB
Cuándo Elegir
- Estructuras de datos ricas y anidadas con arrays y subdocumentos
- Esquema flexible que evoluciona con el tiempo
- Necesidad de índices secundarios y pipelines de agregación
- Consultas que se parecen a matching de objetos JavaScript
Ejemplo
// Un documento de producto con reviews y variantes anidadas
db.products.insertOne({
sku: "SHOE-001",
name: "Trail Runner",
price: 89.99,
attributes: { color: "red", size: 42 },
reviews: [
{ user_id: 42, rating: 5, comment: "¡Gran agarre!" }
],
tags: ["running", "trail", "waterproof"]
})
// Consulta flexible con matching anidado
db.products.find({ "reviews.rating": { $gte: 4 }, tags: "trail" })
Compromisos
| Pro | Contra |
|---|---|
| Esquema flexible | La validación de esquema debe configurarse explícitamente |
| Lenguaje de consulta rico | Los joins son caros y limitados |
| Índices secundarios | Los índices consumen RAM y ralentizan escrituras |
| Escalado horizontal (sharding) | El sharding añade complejidad operativa |
Coste y Operaciones
- El working set debe caber en RAM — si los datos calientes y sus índices superan la memoria, MongoDB va a disco y la latencia sube. Dimensiona para el working set, no para el dataset.
- Los índices cuestan escrituras — cada índice suma trabajo a cada insert; las colecciones con carga de escritura alta exigen disciplina de índices.
- La shard key es permanente — elígela mal (monotónica, baja cardinalidad) y tocará rebalancear un clúster en producción.
- MongoDB Atlas vs self-hosted: Atlas quita carga operativa (backups, upgrades, monitorización) pero cobra la prima. Autoalojar clústeres con shards exige expertise operacional real.
Clave-Valor: DynamoDB y Redis
DynamoDB (AWS)
Ideal para: latencia predecible a cualquier escala, patrones de lectura/escritura simples, arquitecturas serverless.
import boto3
from boto3.dynamodb.conditions import Key
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('Users')
# Get por partition key (latencia de un dígito en ms)
table.get_item(Key={'user_id': 'user-123'})
# Query por partition + sort key
table.query(
KeyConditionExpression=Key('user_id').eq('user-123') &
Key('timestamp').gt('2024-01-01')
)
Restricción crítica de diseño: los patrones de acceso deben conocerse de antemano. DynamoDB está optimizado para rutas de consulta conocidas, no para exploración ad-hoc.
Redis
Ideal para: caching, leaderboards en tiempo real, rate limiting, almacenamiento de sesiones.
# Cachear un valor calculado por 5 minutos
SET user:123:profile '{"name":"Alice"}' EX 300
# Incremento atómico para rate limiting
INCR rate_limit:ip:192.168.1.1
EXPIRE rate_limit:ip:192.168.1.1 60
Restricción crítica: todos los datos deben caber en RAM. Redis no es un store primario para datasets grandes.
Coste y Operaciones
- DynamoDB cobra por petición — el modo on-demand factura por RCU/WCU consumida; el provisionado sale más barato con carga estable pero exige planificación de capacidad. El tamaño del ítem importa: una escritura de 4KB cuesta 4× la de 1KB.
- Sin clúster que gestionar — backups (point-in-time recovery), cifrado y escalado vienen integrados. El coste operativo es casi cero; el coste de diseño es alto.
- Redis es single-threaded — una instancia mueve ~100K+ ops/seg, pero el dataset más el margen de replicación debe caber en la RAM del nodo. Redis Cluster añade sharding a cambio de restringir operaciones multi-clave.
Columnas Anchas: Cassandra
Cuándo Elegir
- Cargas intensivas de escritura (series de tiempo, IoT, mensajería — para motores dedicados ver la guía de bases de datos de series temporales)
- Necesidad de crecimiento lineal sobre hardware commodity
- Tolerancia a consistencia eventual y CQL (Cassandra Query Language)
Modelo de Datos
-- Datos de sensores de series de tiempo
CREATE TABLE sensor_readings (
sensor_id UUID,
day TEXT, -- componente de partition key
timestamp TIMESTAMP,
temperature DOUBLE,
humidity DOUBLE,
PRIMARY KEY ((sensor_id, day), timestamp)
) WITH CLUSTERING ORDER BY (timestamp DESC);
-- Consulta: últimas 100 lecturas de un sensor hoy
SELECT * FROM sensor_readings
WHERE sensor_id = ? AND day = '2024-06-12'
LIMIT 100;
Cassandra es query-first: las tablas se diseñan alrededor de consultas de lectura específicas, no de entidades normalizadas.
Compromisos
| Pro | Contra |
|---|---|
| Throughput de escritura masivo | Sin JOINs, sin subqueries, sin agregaciones entre particiones |
| Crecimiento lineal | Complejidad operativa (gossip, repairs, compaction) |
| Replicación multi-datacenter | Consistencia eventual por defecto |
| Consistencia ajustable | CQL es limitado comparado con SQL |
Coste y Operaciones
- Los repairs son obligatorios — saltárselos deja que los datos diverjan en silencio entre réplicas. Cuenta con una rutina fija de
nodetool repairo un servicio gestionado que la cubra. - La compaction lo afecta todo — la estrategia (STCS, LCS, TWCS) cambia la amplificación de lectura/escritura; la equivocada quema I/O.
- Expertise operacional de JVM — pausas de GC, ajuste de heap y dimensionado de nodos son trabajo real. Con equipo pequeño, mira ScyllaDB (compatible con Cassandra, internals autogestionados) o Amazon Keyspaces (gestionado, compatible con CQL).
- El clúster mínimo viable son 3 nodos — por datacenter. El multi-DC activo-activo es la gran baza de Cassandra, pero cada datacenter es un clúster completo.
Grafos: Neo4j
Ideal para: datos donde las relaciones son el valor — grafos sociales, anillos de fraude, análisis de dependencias.
// Recomendaciones amigo-de-amigo
MATCH (me:User {id: "user-123"})-[:FOLLOWS]->(:User)
-[:FOLLOWS]->(rec:User)
WHERE NOT (me)-[:FOLLOWS]->(rec) AND me <> rec
RETURN rec.id, rec.name, count(*) AS mutual_friends
ORDER BY mutual_friends DESC LIMIT 10;
Restricción crítica: los stores de grafos brillan en profundidad de traversia; rinden mal como OLTP de propósito general. Si tus consultas son mayormente lecturas de entidad única, un store de documentos o clave-valor lo resuelve más barato.
Coste y Operaciones
- El grafo caliente debe caber en RAM — el page cache de Neo4j determina la velocidad de consulta; las traversias que van a disco se degradan rápido.
- El escalado de escritura es limitado — los clústeres de Neo4j replican para lectura y disponibilidad, pero las escrituras pasan por el líder. Los grafos con escritura intensiva encajan mal.
- Aura como oferta gestionada — Neo4j Aura elimina la operación del clúster (backups, upgrades, failover) a cambio de la suscripción; autoalojar significa cargar con el tuning de JVM y los scripts de backup.
Matriz de Decisión
| Requisito | Mejor elección | Por qué |
|---|---|---|
| Documentos JSON flexibles y anidados | MongoDB | Modelo de documento nativo, lenguaje rico |
| Búsquedas por clave con latencia predecible a escala | DynamoDB | Un dígito en ms, auto-scaling, serverless |
| Escrituras de series de tiempo de alto throughput | Cassandra | Almacenamiento log-structured, excelente performance de escritura |
| Datos efímeros y caching | Redis | Velocidad en memoria, estructuras de datos ricas |
| Traversia de relaciones complejas | Neo4j | Traversias de grafo optimizadas |
| Transacciones ACID multi-ítem | PostgreSQL | Los stores NoSQL típicamente carecen de transacciones cross-document |
| Leaderboards en tiempo real y datos ordenados | Redis | Los sorted sets (ZADD/ZRANGEBYSCORE) son estructuras nativas |
| Búsqueda full-text sobre documentos | Elasticsearch | Índice invertido hecho para buscar — no un store primario |
| Telemetría de series de tiempo a escala | Cassandra o una TSDB dedicada | Columnas anchas aguantan el volumen; las TSDB añaden políticas de retención |
| Event sourcing / logs inmutables | DynamoDB o Cassandra | Particiones append-only con claves ordenadas por tiempo |
Bases del Escalado y los Clústeres
Cada familia escala de forma distinta, y el modelo de clúster determina tanto el techo como la carga operativa:
| Store | Modelo de Replicación | Techo de Escala | Operación del Clúster |
|---|---|---|---|
| MongoDB | Líder + replica sets, con shards | ~50 shards típicos | Media — config servers, routing mongos |
| DynamoDB | Multi-AZ totalmente gestionado | Prácticamente ilimitado | Ninguna — no hay clúster visible |
| Redis | Líder + réplicas, Redis Cluster | 1000 shards | Baja-Media — resharding de slots |
| Cassandra | Sin líder, multi-DC | Miles de nodos | Alta — gossip, repairs, compaction |
| Neo4j | Líder + followers | Tamaño de un grafo | Media — disciplina de backup/restore |
Dos patrones que conviene conocer desde el principio:
- Basado en líder (MongoDB, Redis, Neo4j): un nodo acepta escrituras y los demás replican. Razonamiento simple sobre consistencia; el líder es un cuello de botella de escritura y un evento de failover.
- Sin líder (Cassandra): cualquier nodo acepta escrituras y coordina con el resto — sin ceremonia de failover, pero gestionas niveles de consistencia y resolución de conflictos por consulta.
DynamoDB esconde todo esto tras una abstracción gestionada — el “clúster” existe pero lo opera AWS. Ese es el intercambio real: simplicidad operativa a cambio de precio por petición y restricciones de diseño.
El cross-region es el otro eje: Cassandra nació para escrituras activo-activo multi-datacenter (escribe en Europa y EE. UU. a la vez, converge después). DynamoDB Global Tables ofrece el mismo patrón totalmente gestionado. MongoDB y Neo4j pueden replicar entre regiones pero mantienen un único escritor, así que los usuarios lejanos o toleran latencia de escritura o se enrutan a un primario más cercano. Si tu app escribe desde dos continentes, esta restricción elimina media tabla antes de empezar los benchmarks.
Consejos de Migración desde SQL
| Hábito SQL | Adaptación NoSQL |
|---|---|
| Tablas normalizadas | Embebe datos relacionados cuando se acceden juntos; referencia cuando se acceden por separado |
| JOINs por todas partes | Diseña tablas/colecciones alrededor de patrones de consulta, no de entidades |
| IDs auto-increment | Usa UUIDs o claves compuestas (user_id + timestamp) |
| Analítica ad-hoc | Usa change data capture para streamear a un data warehouse |
| Única fuente de verdad | Acepta que distintos stores pueden tener distintas vistas de la verdad (CQRS) |
Lo que funciona
- Modela para tus lecturas, no tus escrituras — el rendimiento NoSQL depende del patrón de acceso
- Evita las particiones calientes — distribuye las escrituras uniformemente entre partition keys (sufijos aleatorios o time bucketing)
- Configura TTLs donde aplique — expira los datos viejos automáticamente en lugar de correr jobs de limpieza
- Prueba con volúmenes de datos de producción — el comportamiento a 1K filas no predice el de 1.000M
- Ten un camino de migración — la gravedad de datos es real; elige con cuidado porque migrar después es caro
Errores Comunes
- Usar MongoDB como caché (Redis es más barato y rápido)
- Usar DynamoDB para analítica ad-hoc (Athena/BigQuery encajan mejor)
- Usar Cassandra para OLTP con consultas complejas (Cassandra destaca en consultas simples acotadas por partición)
- Tratar NoSQL como “SQL que crece mejor” — el modelo de datos es fundamentalmente distinto
- Ignorar la complejidad operativa — Cassandra y MongoDB con shards requieren expertise operacional dedicada
Solución de Problemas
- Unas pocas particiones hacen todo el trabajo (particiones calientes): partition keys sesgadas concentran la carga en unos pocos nodos — añade un sufijo aleatorio o time bucket a la clave y revisa métricas por partición (DynamoDB Contributor Insights,
nodetool cfstatsen Cassandra). - Las lecturas se enlentecen al acumularse datos borrados (tombstones en Cassandra): los borrados masivos dejan tombstones que los escaneos deben saltar — alinea
gc_grace_secondscon tu cadencia de repair y prefiere expiraciones TTL sobre DELETEs explícitos. - Las consultas de DynamoDB se ahogan con volumen bajo global: desajuste on-demand vs provisioned, o una partition key caliente que supera el límite de 1000 WCU/3000 RCU por partición — inspecciona
ThrottledRequestsde CloudWatch por clave. - El pipeline de agregación de MongoDB se arrastra en una colección grande: el pipeline escanea sin índice — reordena para que las etapas
$match/$sortvayan primero y verifica conexplain("executionStats"). - Los datos divergen entre stores: en setups poliglotas, un consumidor caído deja los stores inconsistentes — reconcilia vía el log de eventos (replay desde Kafka) o jobs de diff periódicos en lugar de arreglar filas a mano.
Pon a Prueba tu Elección
Los benchmarks sintéticos engañan más de lo que ayudan — YCSB con claves uniformes favorece a todos los stores. Para que la comparación sea honesta:
- Usa tu distribución real de claves. El tráfico de producción sigue una forma de Zipf: pocas claves calientes y una cola larga. Prueba con datos sesgados o tus resultados no valen nada.
- Mide el p99, no las medias. Un store con 5ms de lectura media aún puede dar 500ms en la cola y romper tu SLA — y el comportamiento de cola es donde se nota el precio de los niveles de consistencia.
- Mata un nodo a mitad de la prueba. Toda base NoSQL va bien hasta que muere una réplica. Mide throughput y latencia durante el fallo, no solo en estado estable.
- Prueba tus 3 patrones de acceso principales — las consultas que tu app ejecuta el 95% del tiempo — no una mezcla de carga genérica.
- Incluye la superficie operativa en la tabla. Una ventaja del 20% en throughput vale poco si el clúster necesita un SRE dedicado.
Y corre la misma prueba dos veces — una con la versión gestionada, otra self-hosted si estás sopesando ambas. DynamoDB gestionado contra Cassandra autoalojado, o Atlas contra tu propio replica set de MongoDB, suele darle la vuelta a la comparación cuando entran el precio y las horas de guardia en la ecuación.
Temas Avanzados
Escenario: Persistencia Políglota para una Red Social
Sistema: 50M usuarios, 200M posts, 2.000M interacciones/día
Stores: 6 bases de datos, cada una optimizada para su carga
Selección de stores:
| Dato | Store | Por qué | Patrón de Acceso |
|------|-------|---------|------------------|
| Perfiles | MongoDB | Esquema flexible | Lecturas puntuales por user_id |
| Posts | Cassandra | Alto volumen de escritura | Partición por user_id |
| Grafo | Neo4j | Traversia de relaciones | Amigo-de-amigo |
| Feed | Redis | Sorted sets de baja latencia | ZREVRANGE |
| Búsqueda | Elasticsearch | Full-text, facetas | Búsqueda multi-campo |
| Analítica | ClickHouse | OLAP columnar | Agregaciones |
Flujo de datos:
El usuario crea un post
-> Escritura en Cassandra (durable)
-> Publicar evento a Kafka
-> Consumidores:
a. Feed: ZADD a Redis (fan-out a seguidores)
b. Búsqueda: indexar en Elasticsearch
c. Analítica: insertar en ClickHouse
d. Grafo: actualizar relaciones en Neo4j
Esquema Cassandra:
CREATE TABLE posts_by_user (
user_id UUID, post_id TIMEUUID, content TEXT,
media_urls LIST<TEXT>, created_at TIMESTAMP,
PRIMARY KEY ((user_id), post_id)
) WITH CLUSTERING ORDER BY (post_id DESC);
Timeline Redis (fan-out en escritura):
ZADD feed:user:{follower_id} {timestamp} {post_id}
EXPIRE feed:user:{follower_id} 86400
ZREVRANGE feed:user:{user_id} 0 49 WITHSCORES
Grafo social Neo4j:
MATCH (me:User {id: "user-123"})-[:FOLLOWS]->(:User)
-[:FOLLOWS]->(rec:User)
WHERE NOT (me)-[:FOLLOWS]->(rec) AND me <> rec
RETURN rec.id, rec.name, count(*) AS mutual_friends
ORDER BY mutual_friends DESC LIMIT 10;
Analítica ClickHouse:
CREATE TABLE interactions (
event_date Date, user_id UInt64, post_id UInt64,
event_type Enum8("like"=1,"comment"=2,"share"=3,"view"=4),
timestamp DateTime
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_type, timestamp);
Complejidad operativa:
| Desafío | Mitigación |
|---------|------------|
| 6 bases de datos | Equipo dedicado por store |
| Consistencia entre stores | Outbox + eventos Kafka |
| Evolución de esquema | Eventos versionados |
| Duplicación de datos | Aceptar; cada store optimizado |
| Monitorización | Dashboard unificado por store |
Lecciones:
- La persistencia políglota es capaz pero cara
- Empieza con 1-2 stores, añade más cuando los patrones diverjan
- La sincronización por eventos (Kafka) es el pegamento
- Cada store posee un patrón de acceso específico
- La duplicación de datos es aceptable; gestiónala vía eventos
El flujo de eventos de ese escenario, visualizado:
Ver También
- Patrones de Modelado de Datos NoSQL — embed vs referencia, diseño guiado por patrones de acceso
- Guía del Teorema CAP — el compromiso consistencia/disponibilidad detrás de cada store de arriba
- Documentación de MongoDB — pipeline de agregación, sharding, índices
- Documentación de DynamoDB — diseño de particiones, GSI, modos de capacidad
- Documentación de Apache Cassandra — modelado de datos, consistencia ajustable, tombstones
- Repositorio companion — esquemas y ejemplos ejecutables de los cuatro stores
Preguntas frecuentes
¿Debería migrar de PostgreSQL a MongoDB por flexibilidad?
No por flexibilidad sola. PostgreSQL tiene JSONB, que te da flexibilidad de documentos conservando las transacciones ACID. Migra a MongoDB cuando necesites sharding horizontal o un lenguaje de consulta document-nativo.
¿Puedo usar varias bases NoSQL en una aplicación?
Sí, y es común. Usa Redis para caché/sesiones, DynamoDB para perfiles de usuario y Elasticsearch para búsqueda. Esto es persistencia políglota. El compromiso es la complejidad operativa.
¿Cómo manejo transacciones entre bases NoSQL?
La mayoría de stores NoSQL no soportan transacciones ACID cross-document o cross-table. Usa sagas, patrones outbox u operaciones idempotentes con entrega at-least-once para lograr consistencia eventual.
¿Cómo manejo la evolución de esquemas entre stores?
Versiona tus eventos y esquemas. Usa un schema registry para compatibilidad. Aplica cambios retrocompatibles primero (añade campos, nunca quites). Los consumidores manejan campos faltantes con defaults. Para cambios que rompen, publica un nuevo tipo de evento y migra consumidores gradualmente.
¿Cuándo NO debería usar NoSQL en absoluto?
Cuando los joins y las consultas ad-hoc son la carga de trabajo, cuando las transacciones abarcan muchos registros, o cuando tus datos son genuinamente relacionales. La mayoría de apps CRUD con unos pocos millones de filas corren mejor en PostgreSQL con un par de índices que en un clúster NoSQL distribuido.
Recursos Relacionados
Guía de Diseño de Bases de Datos
Guía práctica para diseñar bases de datos relacionales con normalización, indexación y modelado de relaciones.
GuideOptimización de Rendimiento SQL
Una guía práctica para optimizar consultas SQL: estrategias de indexación, reescritura de queries, análisis de EXPLAIN plans y anti-patrones comunes a evitar.
GuideEntrevistas de System Design: Framework, Ejemplos y Práctica
Framework práctico para entrevistas de system design: estructura tu respuesta, los conceptos que evalúan los entrevistadores y dos ejemplos resueltos.
GuideTeorema CAP y Trade-offs de Bases de Datos
Teorema CAP explicado para practicantes: trade-offs CP vs AP, PACELC, consistencia tunable por base de datos y cómo evitar split-brain.
GuideGuía Completa de Sharding de Bases de Datos
Domina el sharding de bases de datos: estrategias por rango, hash, lista y directorio, consistent hashing, shard keys, consultas cross-shard, resharding, Vitess y Citus.