SQL vs NoSQL: Las Compensaciones Fundamentales
Elegir la base de datos correcta es una de las decisiones más consecuentes en el diseño de sistemas. Las bases de datos SQL y NoSQL no son competidoras -- son herramientas diseñadas para diferentes problemas. Entender sus fortalezas, debilidades y características de escalado es esencial para diseñar sistemas que funcionen bien bajo carga.
Definiciones Rápidas
- SQL (Relacional): Datos estructurados organizados en tablas con filas y columnas, con relaciones enforced a través de claves foráneas y joins. Ejemplos: PostgreSQL, MySQL, SQL Server.
- NoSQL (No Relacional): Una categoría amplia que abarca almacenes de documentos, almacenes clave-valor, bases de datos de familia de columnas y bases de datos de grafos. Ejemplos: MongoDB, Redis, Cassandra, Neo4j.
ACID vs BASE
La diferencia fundamental en garantías entre las bases de datos SQL y NoSQL se puede resumir con dos acrónimos: ACID y BASE.
Propiedades ACID vs BASE
| ACID (SQL) | BASE (NoSQL) |
|---|---|
| Atomicidad - Transacciones de todo o nada | Básicamente Disponible - El sistema garantiza disponibilidad |
| Consistencia - Datos siempre válidos según restricciones | Estado suave - El estado puede cambiar con el tiempo sin entrada |
| Aislamiento - Las transacciones concurrentes no interfieren | Consistencia eventual - El sistema eventualmente se volverá consistente |
| Durabilidad - Los datos confirmados sobreviven a fallos | Prioriza disponibilidad y tolerancia a particiones |
// Ejemplo de transacción ACID (SQL)
async function transferMoney(fromId: string, toId: string, amount: number) {
const trx = await db.transaction();
try {
// Ambas operaciones tienen éxito o ambas fallan (Atomicidad)
await trx.execute(
"UPDATE accounts SET balance = balance - $1 WHERE id = $2 AND balance >= $1",
[amount, fromId]
);
await trx.execute(
"UPDATE accounts SET balance = balance + $1 WHERE id = $2",
[amount, toId]
);
await trx.commit(); // Durabilidad: escrito a disco
} catch (error) {
await trx.rollback(); // Atomicidad: nada ocurrió
throw error;
}
}
// Enfoque BASE (NoSQL)
// La misma transferencia usando consistencia eventual
async function transferMoneyEventual(fromId: string, toId: string, amount: number) {
// Debitar al remitente (puede tener éxito independientemente)
await dynamodb.update({
TableName: "accounts",
Key: { id: fromId },
UpdateExpression: "SET balance = balance - :amount",
ConditionExpression: "balance >= :amount",
ExpressionAttributeValues: { ":amount": amount },
});
// Acreditar al receptor (procesado asincrónicamente via evento)
await sqs.sendMessage({
QueueUrl: TRANSFER_QUEUE,
MessageBody: JSON.stringify({ toId, amount, fromId }),
});
// Si esto falla, un proceso de reconciliación detectará
// y corregirá la inconsistencia
}
Tipos de Bases de Datos NoSQL
1. Bases de Datos de Documentos
Almacenan datos como documentos semi-estructurados (JSON, BSON). Cada documento puede tener una estructura diferente, haciéndolos flexibles para esquemas en evolución. Los documentos se agrupan típicamente en colecciones.
- Ejemplos: MongoDB, CouchDB, Firestore
- Mejor para: Gestión de contenido, perfiles de usuario, catálogos de productos, cualquier dominio con estructuras de datos variadas o anidadas
- Estilo de consulta: Lenguaje de consulta rico, índices secundarios, pipelines de agregación
2. Almacenes Clave-Valor
El modelo NoSQL más simple. Los datos se almacenan como pares clave-valor, como un hash map gigante. Extremadamente rápido para búsquedas por clave pero no ofrecen consulta por valor.
- Ejemplos: Redis, DynamoDB, Memcached
- Mejor para: Caché, gestión de sesiones, leaderboards en tiempo real, limitación de tasa
- Estilo de consulta: GET/SET solo por clave (algunos soportan índices secundarios)
3. Almacenes de Familia de Columnas (Wide-Column)
Almacenan datos en columnas en lugar de filas. Cada fila puede tener un conjunto diferente de columnas. Optimizados para leer y escribir grandes volúmenes de datos a través de muchas máquinas.
- Ejemplos: Cassandra, HBase, ScyllaDB
- Mejor para: Datos de series de tiempo, datos IoT, logging de eventos, analíticas a escala masiva
- Estilo de consulta: Limitado a consultas por clave de partición y columna de clustering
4. Bases de Datos de Grafos
Representan datos como nodos (entidades) y aristas (relaciones). Optimizados para recorrer relaciones complejas entre entidades.
- Ejemplos: Neo4j, Amazon Neptune, ArangoDB
- Mejor para: Redes sociales, motores de recomendación, detección de fraude, grafos de conocimiento
- Estilo de consulta: Consultas de recorrido de grafos (Cypher, Gremlin, SPARQL)
Características de Escalado
Cómo Escalan las Diferentes Bases de Datos
| Tipo de Base de Datos | Escalado Vertical | Escalado Horizontal | Complejidad |
|---|---|---|---|
| SQL (PostgreSQL, MySQL) | Excelente | Difícil (sharding es manual) | Alta para sharding |
| Documento (MongoDB) | Bueno | Bueno (sharding incorporado) | Media |
| Clave-Valor (Redis) | Bueno | Bueno (Redis Cluster) | Baja-Media |
| Familia de Columnas (Cassandra) | Bueno | Excelente (diseñado para ello) | Baja |
| Grafos (Neo4j) | Bueno | Limitado (particionar grafos es difícil) | Alta |
Estrategias de Escalado SQL
- Réplicas de lectura: Agregar réplicas para manejar tráfico de lectura (primer paso más común)
- Pool de conexiones: Usar PgBouncer o ProxySQL para gestionar conexiones de base de datos eficientemente
- Escalado vertical: Actualizar CPU, RAM y usar SSDs rápidos
- Particionamiento / Sharding: Dividir datos a través de múltiples servidores de base de datos por una clave de shard
- NewSQL: CockroachDB, TiDB y Vitess proporcionan interfaces SQL con escalado horizontal incorporado
Estrategias de Escalado NoSQL
- Sharding nativo: La mayoría de las bases de datos NoSQL hacen sharding automáticamente basándose en una clave de partición
- Consistencia ajustable: Ajustar niveles de consistencia por consulta (ej., lecturas de quórum en Cassandra)
- Desnormalización: Almacenar datos en la forma que tus consultas necesitan para evitar joins
- Estrategias de compactación: Optimizar configuraciones del motor de almacenamiento para tu patrón de escritura
// Modelando los mismos datos en SQL vs NoSQL
// SQL: Esquema normalizado (3NF)
// Tabla Users
// | id | name | email |
// | 1 | Alice | alice@mail.com |
// Tabla Orders
// | id | user_id | product_id | quantity | created_at |
// | 1 | 1 | 100 | 2 | 2025-01-15 |
// Tabla Products
// | id | name | price |
// | 100 | Widget | 29.99 |
// Consulta SQL: Obtener usuario con sus pedidos y nombres de productos
// SELECT u.name, o.quantity, p.name as product_name, p.price
// FROM users u
// JOIN orders o ON u.id = o.user_id
// JOIN products p ON o.product_id = p.id
// WHERE u.id = 1;
// NoSQL (Documento): Desnormalizado para rendimiento de lectura
interface UserDocument {
_id: string;
name: string;
email: string;
orders: Array<{
orderId: string;
product: {
productId: string;
name: string;
price: number;
};
quantity: number;
createdAt: Date;
}>;
}
// Una sola lectura para obtener todo - no se necesitan joins
// Pero actualizar el nombre de un producto requiere actualizar cada documento
// que referencia ese producto (compensación de desnormalización)
Marco de Decisión
Usa este marco al decidir entre SQL y NoSQL en entrevistas de diseño de sistemas o proyectos reales.
Elige SQL Cuando:
- Los datos tienen relaciones claras y necesitas consultar a través de esas relaciones con JOINs
- Las transacciones ACID son críticas (sistemas financieros, inventario, sistemas de reservas)
- El esquema está bien definido y es improbable que cambie dramáticamente
- Se necesitan consultas complejas con agregaciones, agrupamiento y filtrado en múltiples columnas
- La integridad de datos es primordial (claves foráneas, restricciones únicas, restricciones de verificación)
Elige NoSQL Cuando:
- Se necesita rendimiento masivo de escritura (logging, IoT, streams de eventos)
- Los datos son naturalmente jerárquicos o anidados (documentos JSON, contenido generado por usuarios)
- La flexibilidad de esquema es importante (iteración rápida, formas de datos variadas)
- El escalado horizontal es un requisito primario desde el primer día
- Patrones de acceso simples (búsqueda por clave, sin joins complejos)
- Lecturas de baja latencia a cualquier escala (capa de caché, almacenamiento de sesiones)
Ejemplos del Mundo Real
| Empresa/Caso de Uso | Elección de Base de Datos | Por Qué |
|---|---|---|
| Banca / Pagos | PostgreSQL | Transacciones ACID para integridad financiera |
| Catálogo de Productos (Amazon) | DynamoDB | Búsquedas clave-valor a escala masiva, atributos de producto variados |
| Grafo Social (Facebook) | TAO (tipo Grafo) | Optimizado para recorrido de relaciones |
| Métricas de Series de Tiempo | Cassandra / InfluxDB | Alto rendimiento de escritura, particionamiento basado en tiempo |
| Almacenamiento de Sesiones | Redis | Lecturas sub-milisegundo, expiración automática |
| Pedidos de E-commerce | PostgreSQL + Redis | SQL para integridad de pedidos, Redis para caché |
Consejo de Entrevista: Persistencia Políglota
La mayoría de los sistemas del mundo real usan múltiples bases de datos para diferentes propósitos. En entrevistas, no te sientas obligado a elegir solo una. Un sistema bien diseñado podría usar PostgreSQL para datos de usuario y pedidos, Redis para caché y sesiones, Elasticsearch para búsqueda de texto completo, y Cassandra para logging de eventos. Este enfoque se llama persistencia políglota, y mencionarlo demuestra madurez arquitectónica.