Backend
Backend - Bases de Datos
Conceptos fundamentales sobre bases de datos: SQL, índices, JOINs, transacciones ACID, normalización, NoSQL, MongoDB y consistencia eventual
ConceptosScreening
Introducción
Las bases de datos son fundamentales en el desarrollo backend. Entender SQL, NoSQL y sus conceptos clave te ayudará a diseñar sistemas eficientes y escalables.
SQL - Índices
1 preguntas
1
Índices: qué son y cuándo usarlos
Definición: Estructura de datos que mejora la velocidad de búsqueda en tablas.
Cómo funcionan:
•Similar a índice de un libro
•Crea estructura ordenada de valores
•Permite búsqueda rápida sin escanear toda la tabla
•Trade-off: Más espacio, más lento en escrituras
Cuándo crear índices:
✓Columnas en WHERE frecuentemente:
sql
WHERE user_id = ? -- Crear índice en user_id
WHERE status = 'active' -- Si hay muchos valores únicos✓Columnas en JOIN:
sql
JOIN orders ON orders.user_id = users.id
-- Índice en orders.user_id✓Columnas en ORDER BY:
sql
ORDER BY created_at DESC
-- Índice en created_at✓Columnas en GROUP BY:
sql
GROUP BY category_id
-- Índice en category_idCuándo NO crear índices:
✗ Tablas pequeñas (< 1000 filas)
✗ Columnas que cambian frecuentemente
✗ Columnas con pocos valores únicos (ej: género)
✗ Columnas raramente usadas en consultas
Tipos de índices:
| Tipo | Descripción | Cuándo usar |
|---|---|---|
B-Tree | Estándar, balanceado | Mayoría de casos (default) |
Hash | Lookup O(1) | Igualdad exacta, no rangos |
Bitmap | Para pocos valores únicos | Columnas booleanas, estados |
Full-Text | Búsqueda de texto completo | Búsquedas en texto largo |
Composite | Múltiples columnas | Consultas con múltiples WHERE |
Ejemplo:
sql
-- Crear índice simple
CREATE INDEX idx_users_email ON users(email);
-- Consulta rápida (usa índice)
SELECT * FROM users WHERE email = 'user@example.com';
-- Índice compuesto
CREATE INDEX idx_orders_user_status
ON orders(user_id, status, created_at);
-- Útil para: WHERE user_id = ? AND status = ? ORDER BY created_atImpacto en rendimiento:
•Sin índice: O(n) - escanea toda la tabla
•Con índice: O(log n) - búsqueda binaria
•Diferencia: De segundos a milisegundos en tablas grandes
Trade-offs:
•Espacio: Índices ocupan espacio adicional
•Escritura: INSERT/UPDATE/DELETE más lentos
•Mantenimiento: Índices necesitan mantenimiento
•Balance: No crear demasiados índices
SQL - JOIN vs SUBQUERY
1 preguntas
1
JOIN vs SUBQUERY: ¿Cuándo usar cada uno?
Comparación:
| Aspecto | JOIN | SUBQUERY |
|---|---|---|
Rendimiento | ✅ Generalmente más rápido | ⚠️ Puede ser más lento |
Legibilidad | ✅ Más clara | ⚠️ Puede ser confusa |
Flexibilidad | ⚠️ Limitada | ✅ Más flexible |
Uso de índices | ✅ Mejor optimización | ⚠️ Depende del optimizador |
Complejidad | ✅ Simple | ❌ Puede ser compleja |
JOIN - Usa cuando:
✓Necesitas datos de múltiples tablas:
sql
-- ✅ JOIN
SELECT u.name, o.total
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.status = 'active';✓Optimización importante:
• Mejor uso de índices
• Optimizador puede optimizar mejor
✓Consultas simples:
• Relaciones directas
• Filtros simples
SUBQUERY - Usa cuando:
✓Condiciones complejas:
sql
-- ✅ SUBQUERY
SELECT * FROM users
WHERE id IN (
SELECT user_id FROM orders
WHERE total > 1000
);✓Agregaciones en condiciones:
sql
-- ✅ SUBQUERY con agregación
SELECT * FROM products
WHERE price > (
SELECT AVG(price) FROM products
);✓EXISTS para existencia:
sql
-- ✅ EXISTS (más eficiente que IN)
SELECT * FROM users u
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.user_id = u.id
);Ejemplos comparativos:
JOIN:
sql
-- Obtener usuarios con sus órdenes
SELECT u.name, o.total, o.created_at
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at > '2024-01-01';SUBQUERY equivalente:
sql
-- Menos eficiente
SELECT u.name,
(SELECT total FROM orders WHERE user_id = u.id LIMIT 1) as total
FROM users u
WHERE u.created_at > '2024-01-01';Mejores prácticas:
✓Preferir JOIN para:
• Datos de múltiples tablas
• Cuando necesitas todas las columnas
• Rendimiento crítico
✓Usar SUBQUERY para:
• Condiciones complejas
• Agregaciones en WHERE
• EXISTS para verificar existencia
✓EXISTS vs IN:
• EXISTS generalmente más eficiente
• IN puede ser más legible para listas pequeñas
Regla general:
•JOIN para combinar datos de tablas
•SUBQUERY para condiciones complejas o agregaciones
SQL - Transacciones y ACID
1 preguntas
1
Transacciones y ACID: ¿Qué son y por qué importan?
Definición: Transacción es una secuencia de operaciones que se ejecutan como una unidad atómica.
ACID - Propiedades:
A - Atomicity (Atomicidad):
•Todas las operaciones se ejecutan o ninguna
•"Todo o nada"
•Si falla una, se revierte todo
C - Consistency (Consistencia):
•La base de datos siempre queda en estado válido
•Se cumplen todas las reglas de integridad
•No se violan constraints
I - Isolation (Aislamiento):
•Transacciones concurrentes no se interfieren
•Cada transacción ve datos consistentes
•Niveles de aislamiento controlan visibilidad
D - Durability (Durabilidad):
•Cambios persisten después de commit
•Sobrevive a fallos del sistema
•Escritos a disco permanente
Ejemplo de transacción:
sql
BEGIN TRANSACTION;
-- Transferencia de dinero
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- Si todo OK
COMMIT;
-- Si hay error
ROLLBACK;Niveles de aislamiento (de menor a mayor):
| Nivel | Descripción | Problemas que evita |
|---|---|---|
READ UNCOMMITTED | Lee datos no confirmados | Ninguno |
READ COMMITTED | Solo lee datos confirmados | Dirty reads |
REPEATABLE READ | Lecturas consistentes | Dirty reads, Non-repeatable reads |
SERIALIZABLE | Aislamiento total | Todos (dirty reads, non-repeatable, phantom reads) |
Problemas de concurrencia:
✗ Dirty Read: Leer datos no confirmados
✗ Non-repeatable Read: Mismo dato cambia entre lecturas
✗ Phantom Read: Aparecen nuevas filas entre lecturas
Ejemplo práctico:
sql
-- Transferencia bancaria (debe ser atómica)
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- Si falla aquí, todo se revierte
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT; -- Confirma cambios
-- O ROLLBACK; -- Revierte todoCuándo usar transacciones:
✓Operaciones que deben ser atómicas
✓Múltiples operaciones relacionadas
✓Mantener consistencia de datos
✓Operaciones críticas (dinero, inventario)
Trade-offs:
•Mayor aislamiento = mejor consistencia pero menor concurrencia
•Menor aislamiento = mejor rendimiento pero posibles inconsistencias
SQL - Normalización vs Desnormalización
1 preguntas
1
Normalización vs Desnormalización: ¿Cuándo usar cada una?
Normalización: Proceso de organizar datos para reducir redundancia y mejorar integridad.
Formas normales:
1NF (Primera Forma Normal):
•Cada columna contiene valores atómicos
•No hay arrays o listas en una celda
2NF (Segunda Forma Normal):
•Está en 1NF
•Todos los atributos no clave dependen completamente de la clave primaria
3NF (Tercera Forma Normal):
•Está en 2NF
•No hay dependencias transitivas
•Atributos no clave no dependen de otros atributos no clave
Ejemplo normalizado:
sql
-- Tablas separadas
users (id, name, email)
orders (id, user_id, total, created_at)
order_items (id, order_id, product_id, quantity)
products (id, name, price)
-- Relaciones claras, sin redundanciaVentajas de normalización:
✓Menos redundancia: Datos no duplicados
✓Integridad: Más fácil mantener consistencia
✓Menos espacio: Almacenamiento eficiente
✓Actualizaciones: Cambiar en un solo lugar
Desventajas de normalización:
✗ Más JOINs: Consultas más complejas
✗ Rendimiento: Múltiples tablas pueden ser más lentas
✗ Complejidad: Más tablas para manejar
Desnormalización: Proceso intencional de agregar redundancia para mejorar rendimiento.
Ejemplo desnormalizado:
sql
-- Agregar datos redundantes para evitar JOINs
orders (
id,
user_id,
user_name, -- Redundante, pero evita JOIN
total,
created_at
)Cuándo desnormalizar:
✓Lecturas frecuentes:
• Muchas más lecturas que escrituras
• JOINs costosos en consultas frecuentes
✓Rendimiento crítico:
• Consultas deben ser muy rápidas
• Latencia es problema
✓Datos de solo lectura:
• Datos históricos que no cambian
• Reportes y analytics
✓Caching:
• Datos calculados pre-computados
• Agregaciones frecuentes
Estrategia híbrida:
✓Normalizar para escritura:
• Mantener datos normalizados como fuente de verdad
✓Desnormalizar para lectura:
• Crear vistas materializadas
• Tablas de denormalización para consultas
• Sincronizar periódicamente
Ejemplo híbrido:
sql
-- Tabla normalizada (fuente de verdad)
orders_normalized (id, user_id, total)
users_normalized (id, name)
-- Vista materializada desnormalizada (para lectura)
CREATE MATERIALIZED VIEW orders_denormalized AS
SELECT o.id, o.total, u.name as user_name
FROM orders_normalized o
JOIN users_normalized u ON o.user_id = u.id;
-- Refresh periódico
REFRESH MATERIALIZED VIEW orders_denormalized;Recomendación:
•Empezar normalizado (3NF)
•Desnormalizar solo cuando sea necesario para rendimiento
•Medir antes y después
•Documentar decisiones de desnormalización
NoSQL
3 preguntas
1
MongoDB vs SQL (cuándo usar cada uno)
Comparación rápida:
| Aspecto | SQL (Relacional) | MongoDB (NoSQL) |
|---|---|---|
Modelo | Tablas con relaciones | Documentos (JSON-like) |
Esquema | ✅ Fijo, rígido | ✅ Flexible, dinámico |
ACID | ✅ Completo | ⚠️ Parcial (transacciones multi-documento) |
Escalabilidad | ⚠️ Vertical principalmente | ✅ Horizontal (sharding) |
JOINs | ✅ Nativo | ❌ No (usar embedding/referencing) |
Consultas complejas | ✅ SQL poderoso | ⚠️ Limitado |
Consistencia | ✅ Fuerte | ⚠️ Eventual (por defecto) |
Uso común | Datos estructurados, relaciones | Datos semi-estructurados, escalabilidad |
SQL - Usa cuando:
✓Datos estructurados:
• Esquema bien definido
• Relaciones complejas entre entidades
✓Transacciones críticas:
• Necesitas ACID completo
• Consistencia fuerte requerida
• Operaciones financieras
✓Consultas complejas:
• Múltiples JOINs
• Agregaciones complejas
• Reportes analíticos
✓Integridad de datos:
• Constraints y validaciones
• Relaciones foreign key
Ejemplo SQL:
sql
-- Consulta compleja con JOINs
SELECT u.name, COUNT(o.id) as order_count, SUM(o.total) as total_spent
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at > '2024-01-01'
GROUP BY u.id, u.name
HAVING COUNT(o.id) > 5;MongoDB - Usa cuando:
✓Datos semi-estructurados:
• Esquema variable
• Documentos con estructura diferente
✓Escalabilidad horizontal:
• Necesitas sharding
• Crecimiento masivo de datos
✓Desarrollo rápido:
• Prototipado
• Cambios frecuentes de esquema
✓Datos jerárquicos:
• Estructura de árbol
• Datos anidados naturalmente
Ejemplo MongoDB:
javascript
// Documento con datos anidados
{
_id: ObjectId("..."),
name: "Juan",
orders: [
{ product: "Laptop", total: 1000 },
{ product: "Mouse", total: 20 }
]
}
// Consulta
db.users.find({
"orders.total": { $gt: 500 }
});Cuándo usar cada uno:
| Escenario | Recomendación | Razón |
|---|---|---|
E-commerce | SQL | Relaciones complejas, transacciones |
Content Management | MongoDB | Contenido variable, escalabilidad |
Analytics/Logs | MongoDB | Datos masivos, estructura flexible |
Sistema bancario | SQL | ACID crítico, consistencia fuerte |
Social Media | MongoDB | Escalabilidad, datos semi-estructurados |
ERP/CRM | SQL | Relaciones complejas, reportes |
Híbrido:
✓Usar SQL para datos transaccionales
✓Usar MongoDB para logs, analytics, cache
✓Cada base de datos para su propósito
2
Embedding vs Referencing en MongoDB
Embedding: Almacenar datos relacionados dentro del mismo documento.
Referencing: Almacenar referencia (ID) a otro documento.
Comparación:
| Aspecto | Embedding | Referencing |
|---|---|---|
Consultas | ✅ Una sola query | ❌ Múltiples queries |
Actualización | ❌ Actualizar múltiples documentos | ✅ Actualizar uno |
Tamaño documento | ❌ Puede crecer mucho | ✅ Documentos pequeños |
Relaciones | ⚠️ 1:1 o 1:muchos pequeños | ✅ Cualquier relación |
Consistencia | ✅ Atómica | ⚠️ Múltiples documentos |
Embedding - Usa cuando:
✓Datos que se leen juntos:
javascript
// ✅ Embedding
{
_id: ObjectId("..."),
name: "Juan",
address: {
street: "123 Main St",
city: "Madrid",
zip: "28001"
}
}✓Relación 1:1 o 1:muchos pequeña:
• Usuario y perfil
• Orden y items (pocos items)
✓Datos que no cambian frecuentemente:
• Dirección de usuario
• Preferencias
✓Necesitas atomicidad:
• Operaciones atómicas
• Transacciones simples
Referencing - Usa cuando:
✓Relación 1:muchos grande:
javascript
// ✅ Referencing
// Usuario
{
_id: ObjectId("user123"),
name: "Juan",
orderIds: [ObjectId("order1"), ObjectId("order2")]
}
// Órdenes separadas
{
_id: ObjectId("order1"),
userId: ObjectId("user123"),
total: 100
}✓Datos que cambian frecuentemente:
• Comentarios en posts
• Likes
✓Datos compartidos:
• Productos en múltiples órdenes
• Usuarios en múltiples grupos
✓Tamaño variable grande:
• Listas que pueden crecer mucho
• Arrays muy grandes
Ejemplo práctico:
Embedding (bueno para pocos items):
javascript
// Orden con pocos items
{
_id: ObjectId("..."),
userId: ObjectId("..."),
items: [
{ product: "Laptop", quantity: 1, price: 1000 },
{ product: "Mouse", quantity: 2, price: 20 }
],
total: 1040
}Referencing (bueno para muchos items):
javascript
// Orden
{
_id: ObjectId("order123"),
userId: ObjectId("user456"),
itemIds: [ObjectId("item1"), ObjectId("item2"), ...],
total: 1040
}
// Items separados
{
_id: ObjectId("item1"),
orderId: ObjectId("order123"),
product: "Laptop",
quantity: 1,
price: 1000
}Regla general:
•Embedding: Datos que se leen juntos, relación pequeña, cambios infrecuentes
•Referencing: Relación grande, datos que cambian frecuentemente, datos compartidos
3
¿Qué es eventual consistency?
Definición: Modelo de consistencia donde el sistema eventualmente alcanzará consistencia, pero no garantiza consistencia inmediata.
Consistencia fuerte vs eventual:
| Aspecto | Consistencia Fuerte | Eventual Consistency |
|---|---|---|
Garantía | ✅ Inmediata | ⚠️ Eventual |
Rendimiento | ❌ Más lento | ✅ Más rápido |
Disponibilidad | ❌ Menor | ✅ Mayor |
Uso | SQL tradicional | NoSQL distribuido |
Cómo funciona:
[Escritura] → [Réplica A] ──┐
[Réplica B] ──┼─→ [Eventualmente consistente]
[Réplica C] ──┘Ejemplo:
javascript
// Usuario actualiza perfil
UPDATE users SET name = 'Juan' WHERE id = 123;
// Réplica A: name = 'Juan' ✅
// Réplica B: name = 'Juan' ✅ (sincronizado)
// Réplica C: name = 'Juan Viejo' ⚠️ (aún no sincronizado)
// Después de unos segundos
// Réplica C: name = 'Juan' ✅ (ahora consistente)Cuándo es aceptable:
✓Datos no críticos:
• Perfiles de usuario
• Contadores de likes
• Timelines de redes sociales
✓Alta disponibilidad:
• Sistema debe estar siempre disponible
• Tolerancia a fallos
✓Escalabilidad:
• Sistemas distribuidos grandes
• Múltiples regiones geográficas
Cuándo NO es aceptable:
✗ Datos críticos:
• Transacciones financieras
• Inventario crítico
• Operaciones médicas
✗ Operaciones atómicas:
• Transferencias de dinero
• Reservas
Patrones para manejar eventual consistency:
✓Versionado:
javascript
// Usar versiones para detectar conflictos
{
_id: "...",
name: "Juan",
version: 5
}
// Al actualizar, verificar versión
if (currentVersion !== expectedVersion) {
// Conflict, resolver
}✓Vector Clocks:
• Rastrear causalidad de eventos
• Detectar conflictos
✓CRDTs (Conflict-free Replicated Data Types):
• Estructuras de datos que se resuelven automáticamente
• Sin conflictos
✓Saga Pattern:
• Para transacciones distribuidas
• Compensación en caso de fallo
Ejemplo real:
Red social (eventual consistency OK):
javascript
// Usuario A sigue a Usuario B
// Puede tomar unos segundos en aparecer en todos los feeds
// Aceptable para este caso de usoSistema bancario (consistencia fuerte requerida):
javascript
// Transferencia debe ser inmediata y consistente
// Eventual consistency NO es aceptableTrade-off CAP Theorem:
•Consistency: Todos ven mismos datos
•Availability: Sistema siempre responde
•Partition tolerance: Funciona con fallos de red
No puedes tener los 3 simultáneamente:
•SQL: CP (Consistency + Partition tolerance)
•NoSQL: AP (Availability + Partition tolerance)
•Eventual consistency es parte de AP
Recomendación:
•Usar consistencia fuerte cuando sea crítico
•Aceptar eventual consistency cuando sea apropiado
•Documentar decisiones de consistencia