Aprende a Diseñar y Optimizar Mappings en Elasticsearch para Datos Estructurados
Este tutorial profundiza en la creación y optimización de mappings en Elasticsearch. Aprenderás a definir tipos de datos, analizadores y configuraciones avanzadas para estructurar tus documentos y mejorar significativamente la relevancia de tus búsquedas y el rendimiento general de tu clúster.
Elasticsearch es una potente base de datos distribuida de código abierto para búsquedas, análisis y almacenamiento de datos. En su núcleo, la forma en que Elasticsearch interpreta y almacena tus datos está definida por algo llamado mapping. Un mapping es como el esquema de una tabla en una base de datos relacional, pero con mucha más flexibilidad y capacidades de análisis.
Sin un mapping bien diseñado, tus búsquedas pueden ser ineficientes, irrelevantes o incluso imposibles. Este tutorial te guiará a través de los conceptos fundamentales, las mejores prácticas y técnicas avanzadas para diseñar y optimizar mappings en Elasticsearch.
💡 ¿Qué es un Mapping en Elasticsearch?
En Elasticsearch, un mapping es el proceso de definir cómo se almacena y se indexa un documento y sus campos. Describe los campos de un documento, su tipo de datos (por ejemplo, string, integer, date), y cómo deben ser procesados por los analizadores para la búsqueda.
Cuando indexas un documento, Elasticsearch intenta inferir el tipo de datos de cada campo (conocido como dynamic mapping). Si bien esto es útil para empezar rápidamente, para aplicaciones de producción y datos estructurados, es crucial definir tus mappings explícitamente. Esto te da control sobre:
- Tipos de datos: Asegura que los campos se traten como números, fechas, texto, etc.
- Analizadores: Define cómo el texto se tokeniza y normaliza para la búsqueda.
- Indexación: Controla si un campo es buscable y cómo.
- Formatos: Especifica formatos para fechas o números.
Tipos de Mappings
Existen dos tipos principales de mappings en Elasticsearch:
- Dynamic Mapping: Elasticsearch intenta adivinar el tipo de datos de un campo cuando se indexa por primera vez. Esto es conveniente pero puede llevar a mappings subóptimos o incorrectos.
- Explicit Mapping: Defines el esquema de tus documentos de antemano. Esta es la práctica recomendada para la mayoría de los casos de uso en producción.
🛠️ Creación de Mappings Explícitos
Para crear un mapping explícito, utilizas la API _mapping o lo defines al crear un índice. Vamos a ver un ejemplo básico. Imagina que queremos indexar documentos que representan productos.
PUT /productos
{
"mappings": {
"properties": {
"nombre": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"descripcion": {
"type": "text"
},
"precio": {
"type": "float"
},
"stock": {
"type": "integer"
},
"fecha_creacion": {
"type": "date",
"format": "yyyy/MM/dd HH:mm:ss||yyyy/MM/dd"
},
"categoria": {
"type": "keyword"
},
"disponible": {
"type": "boolean"
}
}
}
}
En este ejemplo:
nombreydescripcionson de tipotextporque queremos buscar dentro de su contenido. El camponombretambién tiene un subcampokeywordpara búsquedas exactas (filtros, agregaciones).precioesfloatpara números decimales.stockesintegerpara números enteros.fecha_creacionesdatey especificamos múltiples formatos para que Elasticsearch pueda parsearlos correctamente.categoriaeskeywordporque queremos buscar coincidencias exactas y usarlo en agregaciones (facetas).disponibleesbooleanpara valores verdadero/falso.
✅ Obtener un Mapping Existente
Puedes obtener el mapping actual de un índice con el siguiente comando:
GET /productos/_mapping
🎯 Tipos de Datos Fundamentales y su Uso
Elegir el tipo de datos correcto es crucial para el rendimiento y la funcionalidad. Aquí están algunos de los tipos más comunes:
| Tipo de Datos | Descripción | Caso de Uso Típico |
|---|---|---|
text | Para texto de formato libre que debe ser analizado para la búsqueda de texto completo. | Descripciones de productos, cuerpos de artículos, comentarios. |
keyword | Para valores exactos que no deben ser analizados. Ideal para filtros, ordenaciones y agregaciones. | IDs de producto, nombres de etiquetas, categorías, países. |
integer, long, short, byte | Para números enteros de diferentes tamaños. | Contadores de stock, edades, recuentos. |
float, double, half_float, scaled_float | Para números de punto flotante. | Precios, temperaturas, coordenadas. |
date | Para fechas y horas. | Fechas de creación, timestamps de eventos. |
boolean | Para valores verdaderos/falsos. | Campos de estado (activo, disponible). |
object | Para campos que contienen otros objetos JSON. | Datos anidados como {"direccion": {"calle": "..."}}. |
nested | Similar a object pero trata los objetos anidados como documentos separados internamente, preservando la relación entre sus campos. | Arrays de objetos donde los elementos deben ser buscados de forma independiente. |
Campos text vs keyword
Esta es una de las decisiones más importantes.
text: El contenido se analiza. Esto significa que se divide en tokens (palabras individuales), se normaliza (se convierte a minúsculas, se eliminan stop words, etc.) y se indexa. Es ideal para búsquedas de texto completo (matchquery).keyword: El contenido se indexa tal cual, sin análisis. Ideal para búsquedas de igualdad (termquery), filtros, ordenaciones (sort) y agregaciones (aggregations).
Es común mapear el mismo campo como text y keyword usando multi-fields. Esto permite buscar el campo de ambas maneras:
"nombre": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
Aquí, nombre es text (para búsqueda de texto completo) y nombre.keyword es keyword (para filtros y agregaciones). ignore_above evita indexar cadenas muy largas como keywords, ahorrando espacio.
✨ Analizadores y Tokens
Los analizadores son los cerebros detrás de cómo Elasticsearch procesa el texto para la búsqueda. Un analizador es una combinación de:
- Character filters: Limpian el texto (e.g., eliminan etiquetas HTML).
- Tokenizer: Divide el texto en tokens (palabras).
- Token filters: Modifican los tokens (e.g., lowercase, stemming, stop words).
Elasticsearch tiene analizadores predefinidos como standard, simple, whitespace, english, etc. También puedes crear analizadores personalizados.
Ejemplo de Analizador Personalizado
Supongamos que queremos un analizador para nombres de productos que elimine apóstrofes y convierta todo a minúsculas, además de usar un diccionario de sinónimos.
PUT /productos_v2
{
"settings": {
"analysis": {
"char_filter": {
"apóstrofe_filter": {
"type": "mapping",
"mappings": ["' => " ]
}
},
"filter": {
"sinonimos_filter": {
"type": "synonym",
"synonyms": [
"usb, universal serial bus",
"pc, ordenador personal"
]
}
},
"analyzer": {
"mi_analizador_producto": {
"type": "custom",
"char_filter": ["html_strip", "apóstrofe_filter"],
"tokenizer": "standard",
"filter": ["lowercase", "sinonimos_filter", "stop"]
}
}
}
},
"mappings": {
"properties": {
"nombre": {
"type": "text",
"analyzer": "mi_analizador_producto",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"descripcion": {
"type": "text",
"analyzer": "english"
}
}
}
}
Aquí, nombre usa mi_analizador_producto que limpia apóstrofes, tokeniza, convierte a minúsculas, aplica sinónimos y elimina stop words. La descripcion usa el analizador english por defecto para un análisis más avanzado en inglés.
📈 Optimización de Mappings para el Rendimiento
Un mapping bien optimizado no solo mejora la relevancia de las búsquedas, sino también la velocidad de indexación y el uso del disco.
Reducir el Número de Campos Indexados
Cada campo indexado consume recursos (CPU, RAM, disco). Si no necesitas buscar en un campo o usarlo para agregaciones, puedes deshabilitar su indexación con "index": false.
"campo_solo_visualizacion": {
"type": "text",
"index": false
}
El campo seguirá siendo almacenado en _source y podrás recuperarlo, pero no será buscable.
Deshabilitar _source (Con Cautela)
El campo _source almacena el documento JSON original. Deshabilitarlo ahorra espacio, pero significa que no podrás recuperar el documento original directamente, ni usar funciones como update o reindex.
PUT /indice_sin_source
{
"mappings": {
"_source": {
"enabled": false
},
"properties": {
"id": {"type": "keyword"}
}
}
}
Uso de Multi-fields Inteligentes
Como vimos con text y keyword, los multi-fields son muy potentes. Considera qué combinaciones son realmente necesarias. Por ejemplo, si tienes un campo ciudad, podrías querer:
ciudad.text: Para búsquedas de texto completo (e.g., "nueva york").ciudad.keyword: Para filtros exactos y agregaciones (e.g., "New York").ciudad.lowercase: Para búsquedas exactas pero case-insensitive.
"ciudad": {
"type": "text",
"fields": {
"keyword": {"type": "keyword"},
"lowercase": {
"type": "keyword",
"normalizer": "lowercase_normalizer"
}
}
}
Para el lowercase_normalizer, necesitarías definirlo en settings:
"settings": {
"analysis": {
"normalizer": {
"lowercase_normalizer": {
"type": "custom",
"char_filter": [],
"token_filter": ["lowercase"]
}
}
}
}
Campos object vs nested
Cuando tienes arrays de objetos, la elección entre object y nested es crucial.
-
object: Elasticsearch aplana los objetos en el array. Esto puede romper la correlación entre los campos del mismo objeto.Por ejemplo, si tienes
"tags": [{"nombre": "rojo", "tipo": "color"}, {"nombre": "grande", "tipo": "tamaño"}]Una búsqueda paranombre:rojo AND tipo:tamañopodría encontrar el documento, incluso sirojoytamañoprovienen de objetos diferentes. -
nested: Trata cada objeto en el array como un documento oculto e independiente. Esto mantiene la correlación y permite búsquedas precisas en arrays de objetos.
"etiquetas": {
"type": "nested",
"properties": {
"nombre": {"type": "keyword"},
"tipo": {"type": "keyword"}
}
}
Luego, para buscar, usarías una `nested query`:
GET /productos/_search
{
"query": {
"nested": {
"path": "etiquetas",
"query": {
"bool": {
"must": [
{ "match": { "etiquetas.nombre": "rojo" } },
{ "match": { "etiquetas.tipo": "color" } }
]
}
}
}
}
}
<div class="callout note">📌 <strong>Nota:</strong> Los campos `nested` aumentan el uso de memoria y disco debido a la indexación de documentos ocultos. Úsalos solo cuando la correlación de campos en arrays de objetos sea fundamental.</div>
Plantillas Dinámicas (Dynamic Templates)
Las plantillas dinámicas te permiten aplicar mappings específicos a campos que aún no existen, basándose en su nombre, tipo o ruta. Son muy útiles para estandarizar cómo se mapean nuevos campos.
Por ejemplo, mapear todas las cadenas que terminan en _id como keyword:
PUT /logs
{
"mappings": {
"dynamic_templates": [
{
"string_as_keyword": {
"match_mapping_type": "string",
"match": "*_id",
"mapping": {
"type": "keyword"
}
}
}
]
}
}
📊 Escenarios Comunes y Soluciones de Mappings
Aquí hay algunos escenarios comunes y cómo los mappings pueden ayudar:
- Búsqueda de Texto Completo y Filtrado Exacto: Usa multi-fields
textykeyword. - Fechas y Rangos: Define el
type: datey usa el formato adecuado. Para rangos, Elasticsearch puede inferirlos o puedes usarrangequeries. - Datos Geoespaciales: Utiliza
geo_pointogeo_shapepara coordenadas o formas geográficas.
"ubicacion": {
"type": "geo_point"
}
- Búsqueda en Objetos Anidados: Usa
nestedpara arrays de objetos.
🚧 Buenas Prácticas y Consideraciones Finales
- Planifica tus Mappings: Antes de indexar grandes volúmenes de datos, define tus mappings explícitamente. Cambiar un mapping más tarde es costoso (reindexación).
- Monitorea el Tamaño del Índice: Mappings complejos o demasiados campos indexados pueden hinchar el tamaño del índice y ralentizar las operaciones.
- Usa Alias: En lugar de apuntar directamente a un índice (
productos_v1), usa un alias (productos). Cuando necesites cambiar el mapping, creaproductos_v2con el nuevo mapping, reindexa los datos deproductos_v1aproductos_v2, y luego actualiza el alias para que apunte aproductos_v2. Esto permite un tiempo de inactividad mínimo.
- Evita el
field_dataen Campostext: Para ordenaciones y agregaciones en campostext, Elasticsearch debe cargar elfield_dataen memoria, lo cual es costoso. Si necesitas ordenar o agregar un campo de texto, mapea un subcampokeywordotextcon un analizador que produzca un único token por valor.
FAQ: ¿Qué es la reindexación?
La reindexación es el proceso de copiar documentos de un índice a otro. Es necesaria cuando se hacen cambios drásticos en el mapping, como cambiar el tipo de un campo o la configuración de un analizador a nivel de índice.POST /_reindex
{
"source": {
"index": "productos_v1"
},
"dest": {
"index": "productos_v2"
}
}
Este proceso puede llevar tiempo para grandes volúmenes de datos y debe planificarse cuidadosamente.
Diseñar y optimizar tus mappings es una habilidad fundamental para cualquier usuario de Elasticsearch. Al comprender cómo los datos se almacenan y se procesan, puedes crear soluciones de búsqueda más eficientes, precisas y escalables.
Tutoriales relacionados
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!