Entrada

Bases de Datos NoSQL: MongoDB, Redis y Elasticsearch para Aplicaciones Modernas

Bases de Datos NoSQL: MongoDB, Redis y Elasticsearch para Aplicaciones Modernas

Bases de Datos NoSQL: MongoDB, Redis y Elasticsearch para Aplicaciones Modernas

En la era del Big Data y las aplicaciones web escalables, las bases de datos relacionales tradicionales (SQL) a menudo enfrentan limitaciones en términos de escalabilidad, flexibilidad de esquema y rendimiento. Las bases de datos NoSQL han emergido como una solución poderosa para abordar estos desafíos, ofreciendo nuevos paradigmas para almacenar y consultar datos.

¿Qué son las Bases de Datos NoSQL?

NoSQL (Not Only SQL) se refiere a un conjunto de tecnologías de bases de datos diseñadas para manejar grandes volúmenes de datos no estructurados o semi-estructurados, con requisitos de escalabilidad horizontal y alta disponibilidad.

Características Fundamentales

  1. Esquema flexible: Permiten evolución de la estructura sin migraciones complejas
  2. Escalabilidad horizontal: Fácil distribución across múltiples servidores
  3. Alto rendimiento: Optimizadas para operaciones específicas
  4. Disponibilidad: Diseñadas para tolerancia a fallos
  5. Variety de modelos: Diferentes tipos para diferentes casos de uso

Diferencias Clave vs SQL

AspectoSQLNoSQL
EsquemaRígido, predefinidoFlexible, dinámico
EscalabilidadVertical (más potencia)Horizontal (más servidores)
ACIDGarantías completasEventual consistency
ConsultasSQL estándarAPIs específicas
RelacionesJoins complejosDesnormalización

Tipos de Bases de Datos NoSQL

1. Document Stores (MongoDB)

Almacenan datos en documentos similares a JSON, ideal para datos semi-estructurados.

Casos de uso:

  • Aplicaciones web y móviles
  • Gestión de contenido
  • Catálogos de productos
  • Perfiles de usuario

2. Key-Value Stores (Redis)

Estructura simple de clave-valor, extremadamente rápida para operaciones básicas.

Casos de uso:

  • Cache de aplicaciones
  • Sesiones de usuario
  • Contadores en tiempo real
  • Colas de mensajes

3. Search Engines (Elasticsearch)

Especializadas en búsqueda de texto completo y análisis de datos.

Casos de uso:

  • Búsqueda de sitios web
  • Análisis de logs
  • Monitoreo de aplicaciones
  • Business intelligence

4. Column-Family (Cassandra)

Organizan datos en familias de columnas, ideal para big data.

5. Graph Databases (Neo4j)

Modelan relaciones complejas entre entidades.

MongoDB: Base de Datos de Documentos

MongoDB es una base de datos de documentos que almacena datos en formato BSON (Binary JSON), ofreciendo flexibilidad y potencia para aplicaciones modernas.

Conceptos Fundamentales

Estructura de Datos

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Documento de ejemplo
{
  "_id": ObjectId("507f1f77bcf86cd799439011"),
  "name": "Juan Pérez",
  "email": "juan@example.com",
  "age": 30,
  "address": {
    "street": "Calle Principal 123",
    "city": "Madrid",
    "country": "España"
  },
  "interests": ["programación", "música", "deportes"],
  "created_at": ISODate("2025-07-08T10:30:00Z"),
  "last_login": ISODate("2025-07-08T09:15:00Z")
}

Operaciones CRUD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// CREATE - Insertar documentos
db.users.insertOne({
  name: "Ana García",
  email: "ana@example.com",
  age: 28,
  skills: ["JavaScript", "Python", "MongoDB"]
});

db.users.insertMany([
  {name: "Carlos López", age: 35, department: "Engineering"},
  {name: "María Rodríguez", age: 29, department: "Design"}
]);

// READ - Consultar documentos
db.users.find({age: {$gte: 25}});
db.users.findOne({email: "juan@example.com"});

// Proyecciones
db.users.find(
  {age: {$gte: 25}},
  {name: 1, email: 1, _id: 0}
);

// UPDATE - Actualizar documentos
db.users.updateOne(
  {email: "juan@example.com"},
  {$set: {age: 31, last_login: new Date()}}
);

db.users.updateMany(
  {department: "Engineering"},
  {$inc: {salary: 5000}}
);

// DELETE - Eliminar documentos
db.users.deleteOne({email: "juan@example.com"});
db.users.deleteMany({age: {$lt: 18}});

Consultas Avanzadas

Operadores de Consulta

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Operadores de comparación
db.products.find({price: {$gt: 100, $lt: 500}});
db.users.find({age: {$in: [25, 30, 35]}});
db.products.find({category: {$ne: "electronics"}});

// Operadores lógicos
db.users.find({
  $and: [
    {age: {$gte: 25}},
    {department: "Engineering"}
  ]
});

db.products.find({
  $or: [
    {category: "books"},
    {price: {$lt: 20}}
  ]
});

// Consultas en arrays
db.users.find({skills: "JavaScript"});
db.users.find({skills: {$all: ["JavaScript", "MongoDB"]}});
db.users.find({skills: {$size: 3}});

// Consultas en documentos embebidos
db.users.find({"address.city": "Madrid"});
db.users.find({"address.country": {$in: ["España", "France"]}});

Agregación (Aggregation Pipeline)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// Pipeline de agregación básico
db.sales.aggregate([
  // Stage 1: Filtrar documentos
  {$match: {date: {$gte: ISODate("2025-01-01")}}},
  
  // Stage 2: Agrupar y calcular
  {$group: {
    _id: "$product_category",
    total_sales: {$sum: "$amount"},
    avg_sale: {$avg: "$amount"},
    count: {$sum: 1}
  }},
  
  // Stage 3: Ordenar resultados
  {$sort: {total_sales: -1}},
  
  // Stage 4: Limitar resultados
  {$limit: 5}
]);

// Agregación compleja con lookup
db.orders.aggregate([
  {$match: {status: "completed"}},
  
  // Join con collection de users
  {$lookup: {
    from: "users",
    localField: "user_id",
    foreignField: "_id",
    as: "user_info"
  }},
  
  // Descomponer array resultado del lookup
  {$unwind: "$user_info"},
  
  // Proyectar campos específicos
  {$project: {
    order_id: "$_id",
    total: 1,
    user_name: "$user_info.name",
    user_email: "$user_info.email",
    order_date: 1
  }}
]);

Índices en MongoDB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Índices simples
db.users.createIndex({email: 1}); // Ascendente
db.products.createIndex({price: -1}); // Descendente

// Índices compuestos
db.users.createIndex({age: 1, department: 1});

// Índices de texto completo
db.articles.createIndex({
  title: "text",
  content: "text"
});

// Búsqueda de texto
db.articles.find({$text: {$search: "mongodb tutorial"}});

// Índices geoespaciales
db.locations.createIndex({coordinates: "2dsphere"});

// Búsqueda geoespacial
db.locations.find({
  coordinates: {
    $near: {
      $geometry: {type: "Point", coordinates: [-3.7038, 40.4168]},
      $maxDistance: 1000
    }
  }
});

Redis: Cache y Almacenamiento Key-Value

Redis (Remote Dictionary Server) es una estructura de datos en memoria que funciona como base de datos, cache y message broker.

Tipos de Datos en Redis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# Strings
SET user:1001:name "Juan Pérez"
GET user:1001:name
INCR page_views
EXPIRE user:1001:session 3600

# Hashes - Objetos
HSET user:1001 name "Juan Pérez" email "juan@example.com" age 30
HGET user:1001 name
HGETALL user:1001
HINCRBY user:1001 age 1

# Lists - Arrays ordenados
LPUSH tasks "send email"
LPUSH tasks "update database"
RPOP tasks
LRANGE tasks 0 -1

# Sets - Colecciones únicas
SADD user:1001:skills "JavaScript"
SADD user:1001:skills "Python"
SMEMBERS user:1001:skills
SINTER user:1001:skills user:1002:skills

# Sorted Sets - Sets ordenados
ZADD leaderboard 1500 "player1"
ZADD leaderboard 1200 "player2"
ZRANGE leaderboard 0 -1 WITHSCORES
ZREVRANK leaderboard "player1"

# Streams - Logs de eventos
XADD mystream * sensor-id 1234 temperature 25.5
XREAD STREAMS mystream 0

Casos de Uso Prácticos

Sistema de Cache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import redis
import json
from datetime import timedelta

# Conexión a Redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)

class CacheManager:
    def __init__(self):
        self.redis_client = r
        
    def set_cache(self, key, value, expiration=3600):
        """Almacenar en cache con expiración"""
        serialized_value = json.dumps(value)
        self.redis_client.setex(key, expiration, serialized_value)
        
    def get_cache(self, key):
        """Obtener del cache"""
        cached_value = self.redis_client.get(key)
        if cached_value:
            return json.loads(cached_value)
        return None
        
    def invalidate_cache(self, pattern):
        """Invalidar cache por patrón"""
        keys = self.redis_client.keys(pattern)
        if keys:
            self.redis_client.delete(*keys)

# Uso del cache
cache = CacheManager()

# Almacenar datos de usuario
user_data = {"name": "Juan", "email": "juan@example.com"}
cache.set_cache("user:1001", user_data, 1800)

# Recuperar datos
cached_user = cache.get_cache("user:1001")
if cached_user:
    print(f"Usuario desde cache: {cached_user}")

Sistema de Sesiones

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class SessionManager:
    def __init__(self, redis_client):
        self.redis = redis_client
        self.session_prefix = "session:"
        
    def create_session(self, user_id, session_data, duration=86400):
        """Crear nueva sesión"""
        session_id = f"session_{user_id}_{int(time.time())}"
        key = f"{self.session_prefix}{session_id}"
        
        # Almacenar datos de sesión
        self.redis.hmset(key, session_data)
        self.redis.expire(key, duration)
        
        return session_id
        
    def get_session(self, session_id):
        """Obtener datos de sesión"""
        key = f"{self.session_prefix}{session_id}"
        return self.redis.hgetall(key)
        
    def update_session(self, session_id, data):
        """Actualizar sesión existente"""
        key = f"{self.session_prefix}{session_id}"
        self.redis.hmset(key, data)
        
    def destroy_session(self, session_id):
        """Eliminar sesión"""
        key = f"{self.session_prefix}{session_id}"
        self.redis.delete(key)

Sistema de Colas

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class TaskQueue:
    def __init__(self, redis_client, queue_name):
        self.redis = redis_client
        self.queue_name = queue_name
        
    def enqueue(self, task_data):
        """Agregar tarea a la cola"""
        task_json = json.dumps(task_data)
        self.redis.lpush(self.queue_name, task_json)
        
    def dequeue(self, timeout=0):
        """Obtener tarea de la cola (blocking)"""
        result = self.redis.brpop(self.queue_name, timeout=timeout)
        if result:
            return json.loads(result[1])
        return None
        
    def queue_length(self):
        """Obtener longitud de la cola"""
        return self.redis.llen(self.queue_name)

# Worker para procesar tareas
def worker():
    queue = TaskQueue(r, "email_queue")
    
    while True:
        task = queue.dequeue(timeout=10)
        if task:
            print(f"Procesando tarea: {task}")
            # Procesar la tarea
            process_email_task(task)
        else:
            print("No hay tareas pendientes")

Elasticsearch: Motor de Búsqueda y Analytics

Elasticsearch es un motor de búsqueda y análisis distribuido basado en Apache Lucene, diseñado para búsqueda de texto completo y análisis de datos en tiempo real.

Conceptos Fundamentales

Estructura de Datos

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
  "index": "products",
  "document": {
    "id": "1",
    "name": "Laptop Gaming",
    "description": "Potente laptop para gaming con procesador Intel i7",
    "category": "electronics",
    "brand": "TechBrand",
    "price": 1299.99,
    "specifications": {
      "processor": "Intel i7-11800H",
      "ram": "16GB DDR4",
      "storage": "1TB SSD",
      "graphics": "RTX 3070"
    },
    "tags": ["gaming", "laptop", "high-performance"],
    "release_date": "2025-01-15",
    "in_stock": true,
    "rating": 4.5,
    "reviews_count": 142
  }
}

Operaciones Básicas

Indexación y Consultas

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# Crear índice
PUT /products
{
  "mappings": {
    "properties": {
      "name": {"type": "text", "analyzer": "spanish"},
      "description": {"type": "text", "analyzer": "spanish"},
      "category": {"type": "keyword"},
      "price": {"type": "float"},
      "release_date": {"type": "date"},
      "specifications": {
        "properties": {
          "processor": {"type": "text"},
          "ram": {"type": "keyword"},
          "storage": {"type": "keyword"}
        }
      }
    }
  }
}

# Indexar documento
POST /products/_doc/1
{
  "name": "Laptop Gaming",
  "description": "Potente laptop para gaming",
  "category": "electronics",
  "price": 1299.99,
  "release_date": "2025-01-15"
}

# Búsqueda simple
GET /products/_search
{
  "query": {
    "match": {
      "description": "gaming laptop"
    }
  }
}

# Búsqueda compuesta
GET /products/_search
{
  "query": {
    "bool": {
      "must": [
        {"match": {"description": "laptop"}},
        {"range": {"price": {"gte": 1000, "lte": 2000}}}
      ],
      "filter": [
        {"term": {"category": "electronics"}},
        {"term": {"in_stock": true}}
      ]
    }
  },
  "sort": [
    {"price": "asc"},
    {"rating": "desc"}
  ]
}

Búsquedas Avanzadas

Análisis de Texto

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# Búsqueda fuzzy (tolerante a errores)
GET /products/_search
{
  "query": {
    "fuzzy": {
      "name": {
        "value": "laptap",
        "fuzziness": 2
      }
    }
  }
}

# Búsqueda con autocompletado
GET /products/_search
{
  "query": {
    "match_phrase_prefix": {
      "name": "lap"
    }
  }
}

# Búsqueda con destacado
GET /products/_search
{
  "query": {"match": {"description": "gaming"}},
  "highlight": {
    "fields": {
      "description": {}
    }
  }
}

Agregaciones

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# Agregaciones estadísticas
GET /products/_search
{
  "size": 0,
  "aggs": {
    "avg_price": {
      "avg": {"field": "price"}
    },
    "price_stats": {
      "stats": {"field": "price"}
    },
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          {"to": 500},
          {"from": 500, "to": 1000},
          {"from": 1000}
        ]
      }
    }
  }
}

# Agregaciones por categoría
GET /products/_search
{
  "size": 0,
  "aggs": {
    "categories": {
      "terms": {
        "field": "category",
        "size": 10
      },
      "aggs": {
        "avg_price": {
          "avg": {"field": "price"}
        }
      }
    }
  }
}

Implementación con Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
from elasticsearch import Elasticsearch
from datetime import datetime

class ProductSearchEngine:
    def __init__(self, host='localhost:9200'):
        self.es = Elasticsearch([host])
        self.index_name = 'products'
        
    def create_index(self):
        """Crear índice con mapping"""
        mapping = {
            "mappings": {
                "properties": {
                    "name": {"type": "text", "analyzer": "spanish"},
                    "description": {"type": "text", "analyzer": "spanish"},
                    "category": {"type": "keyword"},
                    "brand": {"type": "keyword"},
                    "price": {"type": "float"},
                    "tags": {"type": "keyword"},
                    "release_date": {"type": "date"},
                    "specifications": {
                        "type": "nested",
                        "properties": {
                            "processor": {"type": "text"},
                            "ram": {"type": "keyword"},
                            "storage": {"type": "keyword"}
                        }
                    }
                }
            }
        }
        
        self.es.indices.create(
            index=self.index_name,
            body=mapping,
            ignore=400  # Ignorar si ya existe
        )
        
    def index_product(self, product_id, product_data):
        """Indexar producto"""
        return self.es.index(
            index=self.index_name,
            id=product_id,
            body=product_data
        )
        
    def search_products(self, query, filters=None, size=10):
        """Búsqueda de productos con filtros"""
        search_body = {
            "size": size,
            "query": {
                "bool": {
                    "must": [
                        {
                            "multi_match": {
                                "query": query,
                                "fields": ["name^2", "description", "brand"],
                                "fuzziness": "AUTO"
                            }
                        }
                    ]
                }
            },
            "highlight": {
                "fields": {
                    "name": {},
                    "description": {}
                }
            }
        }
        
        # Aplicar filtros
        if filters:
            search_body["query"]["bool"]["filter"] = []
            
            if "category" in filters:
                search_body["query"]["bool"]["filter"].append({
                    "term": {"category": filters["category"]}
                })
                
            if "price_range" in filters:
                search_body["query"]["bool"]["filter"].append({
                    "range": {
                        "price": {
                            "gte": filters["price_range"]["min"],
                            "lte": filters["price_range"]["max"]
                        }
                    }
                })
        
        return self.es.search(index=self.index_name, body=search_body)
        
    def get_category_stats(self):
        """Estadísticas por categoría"""
        search_body = {
            "size": 0,
            "aggs": {
                "categories": {
                    "terms": {
                        "field": "category",
                        "size": 20
                    },
                    "aggs": {
                        "avg_price": {"avg": {"field": "price"}},
                        "min_price": {"min": {"field": "price"}},
                        "max_price": {"max": {"field": "price"}}
                    }
                }
            }
        }
        
        return self.es.search(index=self.index_name, body=search_body)

# Uso del motor de búsqueda
search_engine = ProductSearchEngine()
search_engine.create_index()

# Indexar productos
products = [
    {
        "name": "Laptop Gaming Pro",
        "description": "Laptop de alto rendimiento para gaming",
        "category": "electronics",
        "brand": "TechBrand",
        "price": 1599.99,
        "tags": ["gaming", "laptop", "high-performance"]
    }
]

for i, product in enumerate(products):
    search_engine.index_product(i + 1, product)

# Realizar búsquedas
results = search_engine.search_products(
    query="laptop gaming",
    filters={
        "category": "electronics",
        "price_range": {"min": 1000, "max": 2000}
    }
)

Comparación y Casos de Uso

MongoDB vs SQL

AspectoMongoDBSQL
EsquemaFlexible, documentos JSONRígido, tablas relacionales
EscalabilidadHorizontal nativaPrincipalmente vertical
ConsultasMongoDB Query LanguageSQL estándar
TransaccionesACID en documentosACID completo
DesarrolloRápido prototipadoDesarrollo estructurado

Redis vs Memcached

AspectoRedisMemcached
Tipos de datosMúltiples estructurasSolo strings
PersistenciaOpcionalNo
ReplicaciónNo
FuncionalidadCache + DB + BrokerSolo cache
MemoriaMenos eficienteMás eficiente

Elasticsearch vs Bases de Datos Tradicionales

AspectoElasticsearchRDBMS
Búsqueda textoExcelenteBásica
Análisis de datosPotenteLimitado
EscalabilidadHorizontalVertical
ConsistenciaEventualInmediata
ComplejidadAltaMedia

Mejores Prácticas

MongoDB

  1. Diseño de esquema: Desnormalizar según patrones de acceso
  2. Índices: Crear índices para consultas frecuentes
  3. Proyecciones: Solo obtener campos necesarios
  4. Aggregation: Usar pipelines para consultas complejas
  5. Sharding: Distribuir datos para escalabilidad

Redis

  1. Expiration: Siempre establecer TTL para evitar memory leaks
  2. Tipos apropiados: Usar la estructura de datos correcta
  3. Pipelining: Agrupar comandos para mejor rendimiento
  4. Monitoring: Supervisar uso de memoria y conexiones
  5. Persistence: Configurar según requisitos de durabilidad

Elasticsearch

  1. Mapping: Definir tipos y analizadores apropiados
  2. Índices: Usar rolling indices para datos temporales
  3. Consultas: Optimizar queries con filtros y caches
  4. Agregaciones: Limitar cardinalidad en agregaciones
  5. Cluster: Distribuir shards según carga y datos

Conclusión

Las bases de datos NoSQL han revolucionado el panorama de almacenamiento de datos, ofreciendo soluciones especializadas para diferentes necesidades:

  • MongoDB: Ideal para aplicaciones que requieren flexibilidad de esquema
  • Redis: Perfecto para cache, sesiones y datos de alta velocidad
  • Elasticsearch: Excelente para búsqueda y análisis de datos

La clave del éxito está en:

  1. Entender los requisitos específicos de tu aplicación
  2. Evaluar las características de cada tecnología
  3. Combinar múltiples bases de datos según necesidades
  4. Monitorear y optimizar el rendimiento continuamente

El futuro de las aplicaciones modernas está en arquitecturas híbridas que aprovechan las fortalezas de diferentes paradigmas de bases de datos.


Andrés Nuñez - t4ifi

Esta entrada está licenciada bajo CC BY 4.0 por el autor.