03 - Bases de Datos¶
Tabla de Contenidos¶
SQL vs NoSQL¶
Bases de Datos Relacionales (SQL)¶
Almacenan datos en tablas con esquemas definidos, relaciones formales y garantías ACID.
ACID: - Atomicity: Una transacción se ejecuta completamente o no se ejecuta - Consistency: La DB pasa de un estado válido a otro estado válido - Isolation: Transacciones concurrentes no interfieren entre sí - Durability: Una vez confirmada, la transacción sobrevive a fallos
Ejemplos: PostgreSQL, MySQL, Oracle, SQL Server
Ideal para: Transacciones financieras, datos con relaciones complejas, reporting, sistemas donde la integridad es crítica.
Bases de Datos No Relacionales (NoSQL)¶
Sacrifican algunas garantías ACID a cambio de flexibilidad, escalabilidad horizontal y rendimiento.
Tipos de NoSQL¶
graph TD
NoSQL --> KV[Key-Value<br/>Redis, DynamoDB]
NoSQL --> DOC[Document<br/>MongoDB, CouchDB]
NoSQL --> COL[Column-Family<br/>Cassandra, HBase]
NoSQL --> GRAPH[Graph<br/>Neo4j, Amazon Neptune]
NoSQL --> TS[Time Series<br/>InfluxDB, TimescaleDB]
| Tipo | Modelo | Caso de Uso | Ejemplo |
|---|---|---|---|
| Key-Value | Pares clave-valor simples | Sessions, cache, carrito | Redis, DynamoDB |
| Document | Documentos JSON/BSON flexibles | CMS, catálogos, perfiles | MongoDB, Firestore |
| Column-Family | Columnas agrupadas por familias | Analytics, time-series, logs | Cassandra, HBase |
| Graph | Nodos y aristas con propiedades | Redes sociales, recomendaciones | Neo4j, Neptune |
Comparativa Directa¶
| Criterio | SQL | NoSQL |
|---|---|---|
| Esquema | Rígido (DDL) | Flexible / Schema-less |
| Escalabilidad | Principalmente vertical | Diseñado para horizontal |
| Transacciones | ACID completo | BASE (eventual consistency) |
| Joins | Nativos y eficientes | Limitados o inexistentes |
| Consistencia | Fuerte por defecto | Eventual por defecto |
| Modelo de datos | Tablas normalizadas | Desnormalizado, orientado a queries |
| Madurez | 40+ años | ~15 años |
BASE (NoSQL)¶
- Basically Available: El sistema responde siempre (aunque con datos potencialmente stale)
- Soft State: El estado puede cambiar sin input (por propagación eventual)
- Eventually Consistent: Los datos convergen a consistencia dado suficiente tiempo
Cuándo Elegir Cada Uno¶
graph TD
Start[¿Qué necesitas?] --> Q1{¿Transacciones ACID<br/>estrictas?}
Q1 -->|Sí| SQL[SQL: PostgreSQL / MySQL]
Q1 -->|No| Q2{¿Esquema muy<br/>variable?}
Q2 -->|Sí| DOC[Document: MongoDB]
Q2 -->|No| Q3{¿Escala masiva con<br/>escrituras intensas?}
Q3 -->|Sí| COL[Column: Cassandra]
Q3 -->|No| Q4{¿Relaciones complejas<br/>tipo grafo?}
Q4 -->|Sí| GRAPH[Graph: Neo4j]
Q4 -->|No| Q5{¿Latencia ultra baja<br/>key-value simple?}
Q5 -->|Sí| KV[Key-Value: Redis / DynamoDB]
Q5 -->|No| SQL
Teorema CAP¶
Formulado por Eric Brewer en 2000. Establece que un sistema distribuido solo puede garantizar dos de tres propiedades simultáneamente:
Las Tres Propiedades¶
- Consistency (C): Todos los nodos ven los mismos datos al mismo tiempo. Una lectura después de una escritura siempre retorna el valor más reciente.
- Availability (A): Todo request recibe una respuesta (no necesariamente el dato más reciente), sin garantía de que contenga la escritura más reciente.
- Partition Tolerance (P): El sistema sigue funcionando a pesar de que mensajes entre nodos se pierdan o retrasen.
La Realidad del CAP¶
En un sistema distribuido, las particiones de red van a ocurrir. Por lo tanto, P no es opcional. La decisión real es:
graph TD
subgraph "Teorema CAP"
C((Consistency))
A((Availability))
P((Partition<br/>Tolerance))
C --- CP[CP Systems<br/>MongoDB, HBase<br/>Redis Cluster]
C --- CA[CA Systems<br/>PostgreSQL single-node<br/>No distribuido realmente]
A --- CA
A --- AP[AP Systems<br/>Cassandra, DynamoDB<br/>CouchDB]
P --- CP
P --- AP
end
| Tipo | Comportamiento durante partición | Ejemplo |
|---|---|---|
| CP | Rechaza writes/reads para mantener consistencia | MongoDB (con write concern majority) |
| AP | Acepta reads/writes pero pueden ser stale | Cassandra, DynamoDB |
| CA | No tolera particiones (no es realmente distribuido) | PostgreSQL single-node |
PACELC: Extensión del CAP¶
Cuando no hay partición (else), también hay un trade-off:
Si hay Partición: ¿eliges Availability o Consistency?
Else (sin partición): ¿eliges Latency o Consistency?
| Sistema | P+A/C | E+L/C |
|---|---|---|
| Cassandra | PA | EL (baja latencia, eventual consistency) |
| MongoDB | PC | EC (consistencia sobre latencia) |
| DynamoDB | PA | EL (configurable) |
Replicación¶
Mantener copias de los mismos datos en múltiples nodos para mejorar disponibilidad, durabilidad y rendimiento de lectura.
Replicación Síncrona vs Asíncrona¶
sequenceDiagram
participant Client
participant Primary
participant Replica1
participant Replica2
Note over Client,Replica2: Replicación Síncrona
Client->>Primary: WRITE
Primary->>Replica1: Replicar
Primary->>Replica2: Replicar
Replica1-->>Primary: ACK
Replica2-->>Primary: ACK
Primary-->>Client: OK (después de todos los ACKs)
Note over Client,Replica2: Replicación Asíncrona
Client->>Primary: WRITE
Primary-->>Client: OK (inmediato)
Primary-)Replica1: Replicar (async)
Primary-)Replica2: Replicar (async)
| Tipo | Consistencia | Latencia de Write | Durabilidad | Riesgo |
|---|---|---|---|---|
| Síncrona | Fuerte | Alta (espera ACKs) | Alta | Menor throughput |
| Asíncrona | Eventual | Baja | Menor (replication lag) | Pérdida de datos si Primary cae |
| Semi-síncrona | Compromiso | Media | Media-Alta | Una réplica confirma, las demás async |
Topologías de Replicación¶
1. Single-Leader (Primary-Replica)¶
graph TD
W[Writes] --> P[Primary]
P --> R1[Replica 1]
P --> R2[Replica 2]
P --> R3[Replica 3]
R1 --> Reads1[Reads]
R2 --> Reads2[Reads]
R3 --> Reads3[Reads]
- Todas las escrituras van al Primary
- Las réplicas sirven lecturas
- Failover: una réplica se promueve a Primary
- Usado por: PostgreSQL, MySQL, MongoDB (replica set)
2. Multi-Leader¶
graph LR
L1[Leader DC1] <-->|Replicación| L2[Leader DC2]
L1 --> R1[Replica 1]
L2 --> R2[Replica 2]
- Múltiples nodos aceptan escrituras
- Conflictos deben resolverse (last-write-wins, merge, custom)
- Usado por: CouchDB, configuraciones multi-datacenter
3. Leaderless (Peer-to-Peer)¶
- Todos los nodos aceptan lecturas y escrituras
- Quorum:
W + R > Npara garantizar consistencia - N = número de réplicas
- W = nodos que confirman write
- R = nodos que confirman read
- Usado por: Cassandra, DynamoDB, Riak
Ejemplo quorum: N=3, W=2, R=2. Siempre leerás al menos un nodo actualizado.
Sharding¶
Dividir los datos horizontalmente entre múltiples bases de datos (shards). Cada shard contiene un subconjunto de los datos totales.
¿Por Qué Sharding?¶
Cuando un solo servidor no puede manejar: - El volumen de datos (>capacidad de disco) - El throughput de queries (>capacidad de CPU/IO) - El write throughput (una sola máquina es el cuello de botella)
Estrategias de Sharding¶
1. Range-Based (Por Rango)¶
Shard A: users con ID 1 - 1,000,000
Shard B: users con ID 1,000,001 - 2,000,000
Shard C: users con ID 2,000,001 - 3,000,000
Ventaja: Queries por rango son eficientes (todos los datos contiguos están juntos). Desventaja: Hotspots si la distribución no es uniforme (ej: usuarios nuevos siempre van al último shard).
2. Hash-Based (Por Hash)¶
Ventaja: Distribución uniforme. Desventaja: Queries por rango requieren fan-out a todos los shards. Resharding es costoso.
3. Directory-Based (Por Directorio)¶
Un servicio de lookup mantiene un mapa de qué datos están en qué shard.
Ventaja: Flexible, permite mover datos entre shards fácilmente. Desventaja: El directorio es un SPOF y un cuello de botella.
4. Geo-Based (Geográfico)¶
graph TD
Router[Shard Router] --> |user.country = US| US[Shard US<br/>Virginia]
Router --> |user.country = EU| EU[Shard EU<br/>Frankfurt]
Router --> |user.country = APAC| APAC[Shard APAC<br/>Tokyo]
Problemas del Sharding¶
| Problema | Descripción | Mitigación |
|---|---|---|
| Cross-shard joins | No puedes JOIN entre shards eficientemente | Desnormalizar datos, application-level joins |
| Cross-shard transactions | Transacciones ACID entre shards son muy costosas | Saga pattern, 2PC (two-phase commit) |
| Resharding | Añadir/remover shards requiere migrar datos | Consistent hashing, virtual shards |
| Hotspots | Un shard recibe más tráfico que otros | Mejor shard key, split del shard caliente |
| Complejidad operacional | Backups, migraciones, monitoring multiplicados | Automatización, herramientas como Vitess |
Shard Key: La Decisión Más Importante¶
La shard key determina cómo se distribuyen los datos. Una mala elección causa hotspots y cross-shard queries.
Buena shard key: - Alta cardinalidad (muchos valores únicos) - Distribución uniforme - Alineada con los queries más frecuentes
Ejemplo e-commerce:
- order_id: buena distribución, pero no permite queries por usuario eficientes
- user_id: queries por usuario son locales, pero usuarios con muchos pedidos crean hotspots
- user_id + order_date: compound key, buen balance
Índices¶
Estructuras de datos que aceleran las operaciones de lectura a costa de espacio adicional y escrituras más lentas.
B-Tree (el más común)¶
Estructura de árbol balanceado usada por PostgreSQL, MySQL, SQL Server.
graph TD
R[Root: 50] --> L[30]
R --> M[70]
L --> LL[10, 20]
L --> LR[35, 40]
M --> ML[55, 60]
M --> MR[80, 90]
- Búsqueda: O(log n)
- Inserción: O(log n)
- Ideal para: queries por rango, igualdad, ORDER BY
Hash Index¶
- Búsqueda: O(1) promedio
- No soporta queries por rango
- Ideal para: lookups exactos (WHERE id = 123)
Tipos de Índices¶
| Tipo | Descripción | Ejemplo SQL |
|---|---|---|
| Primary | Único, auto-creado con PK | PRIMARY KEY (id) |
| Secondary | Índice adicional sobre columnas | CREATE INDEX idx_email ON users(email) |
| Composite | Múltiples columnas (orden importa) | CREATE INDEX idx ON orders(user_id, created_at) |
| Unique | Garantiza unicidad | CREATE UNIQUE INDEX idx ON users(email) |
| Partial/Filtered | Solo indexa filas que cumplen condición | CREATE INDEX idx ON orders(status) WHERE status = 'pending' |
| Covering | El índice contiene todas las columnas del query | CREATE INDEX idx ON orders(user_id) INCLUDE (total, status) |
| Full-Text | Búsqueda de texto libre | CREATE INDEX idx ON articles USING GIN(to_tsvector('spanish', body)) |
Composite Index y el Leftmost Prefix¶
Un índice compuesto (A, B, C) puede ser utilizado para:
- Queries por A
- Queries por A, B
- Queries por A, B, C
NO puede ser utilizado eficientemente para:
- Queries por B solo
- Queries por C solo
- Queries por B, C
Cuándo NO Indexar¶
- Tablas muy pequeñas (full scan es más rápido)
- Columnas con baja cardinalidad (ej:
boolean,statuscon 3 valores) a menos que sea partial index - Tablas con escrituras muy intensas y pocas lecturas
- Columnas que raramente aparecen en WHERE/JOIN/ORDER BY
EXPLAIN: Tu Mejor Herramienta¶
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 123 AND status = 'completed';
-- Output ejemplo:
-- Index Scan using idx_orders_user_status on orders
-- Index Cond: (user_id = 123 AND status = 'completed')
-- Rows Removed by Filter: 0
-- Planning Time: 0.123 ms
-- Execution Time: 0.045 ms
Busca: - Seq Scan en tablas grandes = probablemente falta un índice - Rows Removed by Filter alto = el índice no es óptimo - Sort sin índice = potencial para índice en ORDER BY columns
Recursos Recomendados¶
- Libro: Designing Data-Intensive Applications - Martin Kleppmann (Capítulos 3, 5, 6)
- Libro: Database Internals - Alex Petrov
- Paper: "Dynamo: Amazon's Highly Available Key-value Store" - DeCandia et al.
- Paper: "Bigtable: A Distributed Storage System" - Chang et al. (Google)
- Video: CMU Database Group - Andy Pavlo (YouTube, curso completo gratuito)
- Herramienta: use-the-index-luke.com - Guía visual de indexación SQL
- Blog: Percona Database Performance Blog