Saltar a contenido

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:

¿CP o AP?
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 > N para 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)

shard = hash(user_id) % num_shards

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, status con 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