Dominando el Query DSL de Elasticsearch: Comandos Esenciales para la Recuperación de Datos
Desbloquea el poder de la recuperación de Elasticsearch dominando el Query DSL. Esta guía desglosa las estructuras esenciales de consultas JSON, centrándose en el uso práctico de las consultas `match`, `term` y de rango. Aprende la diferencia crítica entre las cláusulas `must` (puntuación) y `filter` (caché) dentro de la consulta fundamental `bool`, lo que te permitirá construir búsquedas de datos complejas y de alto rendimiento de manera eficiente.
Dominando el Query DSL de Elasticsearch: Comandos Esenciales para la Recuperación de Datos
El Query DSL de Elasticsearch es el lenguaje JSON que usas cuando una simple caja de búsqueda no es suficiente. Te permite mezclar búsqueda de texto completo, filtros exactos, rangos de fechas, ordenación, paginación y agregaciones en una sola solicitud. Esa flexibilidad es útil, pero también facilita escribir una consulta que devuelve los documentos incorrectos o que funciona bien en pruebas y se ralentiza en producción.
La mejor manera de aprender Query DSL es tener en cuenta dos preguntas: "¿Estoy buscando texto por relevancia?" y "¿Estoy filtrando valores exactos?" La mayoría de las elecciones de consulta se derivan de esa división.
La Anatomía de una Solicitud de Búsqueda en Elasticsearch
Todas las búsquedas de Elasticsearch se realizan contra el endpoint _search de un índice (o índices) específico. Una solicitud de búsqueda básica es una solicitud POST que contiene un cuerpo JSON que define los parámetros de la consulta. La parte más crítica de este cuerpo es el objeto query.
Estructura Básica:
POST /nombre_de_tu_indice/_search
{
"query": { ... Define aquí la estructura de tu consulta ... },
"size": 10,
"from": 0
}
Tipos de Consulta Centrales: Precisión y Relevancia
El Query DSL ofrece una amplia gama de consultas adaptadas a diferentes tipos de datos y necesidades de coincidencia. La elección de la consulta impacta significativamente tanto en la puntuación de relevancia como en el rendimiento.
1. Búsqueda de Texto Completo: La Consulta match
La consulta match es el estándar para la búsqueda de texto completo en campos analizados. Tokeniza el término de búsqueda y verifica si hay tokens coincidentes en el/los campo(s) especificado(s).
Caso de Uso: Buscar texto en lenguaje natural donde la puntuación de relevancia importa.
Ejemplo: Encontrar documentos donde el campo 'description' contenga la palabra 'cloud' o 'computing'.
GET /products/_search
{
"query": {
"match": {
"description": "cloud computing"
}
}
}
2. Coincidencia de Valor Exacto: La Consulta term
La consulta term busca documentos que contengan el término exacto especificado. A diferencia de match, no realiza análisis en la cadena de búsqueda, lo que la hace ideal para coincidencias exactas en palabras clave, IDs o campos indexados numéricamente.
Caso de Uso: Filtrar por valores exactos en campos no analizados (como campos keyword o números).
Ejemplo: Recuperar un producto con el ID exacto SKU10021.
GET /products/_search
{
"query": {
"term": {
"product_id": "SKU10021"
}
}
}
3. Consultas de Rango
Las consultas de rango te permiten filtrar documentos donde el valor de un campo cae dentro de un rango especificado (numérico, de fecha o de cadena).
Sintaxis: Usa gt (mayor que), gte (mayor o igual que), lt (menor que) y lte (menor o igual que).
Ejemplo: Encontrar pedidos realizados después del 1 de enero de 2024.
GET /orders/_search
{
"query": {
"range": {
"order_date": {
"gte": "2024-01-01",
"lt": "2025-01-01"
}
}
}
}
4. Filtrar por Presencia: La Consulta exists
La consulta exists identifica documentos donde un campo específico está presente (es decir, no es nulo ni falta).
Ejemplo: Encontrar todos los usuarios que han proporcionado una dirección de correo electrónico.
GET /users/_search
{
"query": {
"exists": {
"field": "email_address"
}
}
}
Construyendo Lógica Compleja con la Consulta bool
Para prácticamente todas las aplicaciones de búsqueda del mundo real, necesitas combinar múltiples criterios. La consulta bool es la herramienta esencial para esto, permitiéndote combinar otras cláusulas de consulta usando lógica booleana.
Cláusulas dentro de bool
La consulta bool acepta cuatro cláusulas principales:
must: Todas las cláusulas dentro de este array deben coincidir. Las cláusulas enmustcontribuyen a la puntuación de relevancia.filter: Todas las cláusulas dentro de este array deben coincidir, pero se ejecutan en un contexto sin puntuación. Esto las hace mucho más rápidas para criterios estrictos de inclusión/exclusión.should: Al menos una cláusula en este array debería coincidir. Estas cláusulas influyen en la puntuación de relevancia pero son opcionales para la coincidencia.must_not: Ninguna de las cláusulas en este array debe coincidir (el equivalente a un NOT lógico).
Ejemplo Práctico de Consulta bool
Combinemos varios conceptos para encontrar documentos de alta prioridad que mencionen 'security' pero excluyan borradores y estén disponibles en la región 'US'.
GET /logs/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"content": "security breach"
}
}
],
"filter": [
{
"term": {
"region.keyword": "US"
}
}
],
"should": [
{
"term": {
"priority": 5
}
}
],
"must_not": [
{
"term": {
"status.keyword": "DRAFT"
}
}
]
}
}
}
Explicación del Ejemplo:
- Must: El documento debe contener la frase "security breach" en el campo de contenido analizado.
- Filter: El documento debe estar etiquetado para la región 'US' (una coincidencia exacta y rápida).
- Should: Los documentos que coincidan con
priority: 5recibirán un impulso en su puntuación de relevancia, pero los documentos con prioridades más bajas que cumplan con las cláusulasmustyfilteraún se devolverán. - Must Not: Los documentos marcados como 'DRAFT' están estrictamente excluidos.
Mejores Prácticas para la Construcción de Consultas
Para asegurar que tus búsquedas sean precisas y eficientes, adhiérete a estas pautas:
- Prefiere
filtersobremustpara criterios sin puntuación. Si solo estás verificando inclusión/exclusión (por ejemplo, filtrando por ID, fecha exacta o estado), usa siempre la cláusulafilterdentro de una consultabool. Esto aprovecha el almacenamiento en caché y evita costosos cálculos de puntuación. - Usa Consultas Exactas con Sabiduría: Para campos mapeados como
text(analizados), usamatch. Para campos mapeados comokeyword(no analizados), usatermo consultas de rango. - Evita el Anidamiento Profundo: Aunque es posible, las consultas
boolprofundamente anidadas pueden volverse difíciles de leer y depurar, y a veces pueden llevar a una degradación del rendimiento. - Aprovecha
minimum_should_match: Para cláusulasshould, establecerminimum_should_match(por ejemplo, a1o2) obliga a que se cumplan un cierto número de esos criterios opcionales, convirtiéndolos efectivamente en criterios requeridos mientras aún se les permite contribuir a la puntuación.
El Mapeo Decide Qué Consulta Tiene Sentido
La mayoría de los errores de Query DSL comienzan con el mapeo. Una consulta puede verse correcta y aún así devolver resultados confusos si el campo está mapeado de manera diferente a lo que piensas.
Un patrón común es un campo de texto con un subcampo de palabra clave:
{
"mappings": {
"properties": {
"title": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
},
"status": { "type": "keyword" },
"created_at": { "type": "date" },
"price": { "type": "double" }
}
}
}
Usa match en title cuando quieras un comportamiento de texto completo analizado. Usa term en title.keyword cuando necesites el valor exacto del título. Usa term en status porque ya es una palabra clave. Usa range en created_at o price porque esos campos son valores de fecha y numéricos.
Si una consulta term en un campo de texto no funciona como esperas, el problema suele ser el análisis. Los tokens almacenados pueden estar en minúsculas, divididos, derivados o modificados de otra manera. Verifica el mapeo antes de cambiar la consulta.
GET /products/_mapping
Para problemas de análisis de texto, _analyze es útil:
GET /products/_analyze
{
"field": "description",
"text": "Cloud Computing"
}
Eso muestra contra qué tokens buscará Elasticsearch.
match, match_phrase y multi_match
match es la consulta de texto completo cotidiana, pero no es la única que usarás.
Usa match_phrase cuando el orden de las palabras importe:
GET /products/_search
{
"query": {
"match_phrase": {
"description": "wireless charging stand"
}
}
}
Esto es útil para nombres de productos, mensajes de registro, títulos de documentos y frases donde la secuencia exacta tiene significado. Es más estricto que match, por lo que puede devolver menos documentos.
Usa multi_match cuando la misma entrada de usuario deba buscar en varios campos:
GET /products/_search
{
"query": {
"multi_match": {
"query": "noise cancelling headphones",
"fields": ["title^3", "description", "brand^2"]
}
}
}
Los impulsos ^3 y ^2 le indican a Elasticsearch que las coincidencias en title y brand deben contar más que las coincidencias en description. El impulso no es una garantía de que un documento se clasifique primero; es una sugerencia de puntuación. Prueba con consultas reales antes de ajustar los impulsos de manera demasiado agresiva.
Paginación Sin Dañar el Clúster
Los parámetros básicos from y size son adecuados para la paginación superficial:
GET /products/_search
{
"from": 20,
"size": 10,
"query": {
"match": {
"description": "laptop sleeve"
}
}
}
La paginación profunda es diferente. Solicitar la página 1,000 obliga a Elasticsearch a ordenar y saltar muchos resultados. Para la búsqueda orientada al usuario, evita la paginación profunda ilimitada. Para exportaciones o escaneos en segundo plano, usa search_after con una ordenación estable:
GET /products/_search
{
"size": 100,
"sort": [
{ "created_at": "asc" },
{ "_id": "asc" }
],
"search_after": ["2025-01-10T12:00:00Z", "abc123"],
"query": {
"term": {
"status": "active"
}
}
}
Los valores en search_after provienen del array sort del último resultado en la respuesta anterior. Este enfoque es más estable para recorrer grandes conjuntos de resultados.
El Filtrado de Fuente Mantiene las Respuestas Útiles
El rendimiento de la búsqueda no es solo la ejecución de la consulta. Devolver documentos enormes puede ralentizar el cliente, la red y el nodo coordinador. Si la interfaz de usuario solo necesita unos pocos campos, solicita esos campos:
GET /orders/_search
{
"_source": ["order_id", "customer_id", "total", "created_at", "status"],
"query": {
"bool": {
"filter": [
{ "term": { "status": "paid" } },
{ "range": { "created_at": { "gte": "now-7d/d" } } }
]
}
}
}
Esto hace que la respuesta sea más fácil de leer y puede reducir el tamaño del payload. No reemplaza un buen diseño de índice, pero ayuda cuando los documentos contienen grandes descripciones, blobs de metadatos o arrays anidados que la página actual no necesita.
La Ordenación y las Agregaciones Necesitan los Campos Correctos
Ordenar por texto analizado suele ser un error. Ordena por campos de palabra clave, numéricos o de fecha:
GET /products/_search
{
"sort": [
{ "price": "asc" },
{ "title.keyword": "asc" }
],
"query": {
"term": {
"status": "active"
}
}
}
Lo mismo se aplica a muchas agregaciones. Si quieres conteos por estado, agrega en un campo de palabra clave:
GET /orders/_search
{
"size": 0,
"aggs": {
"orders_by_status": {
"terms": {
"field": "status"
}
}
},
"query": {
"range": {
"created_at": {
"gte": "now-30d/d"
}
}
}
}
size: 0 le dice a Elasticsearch que solo quieres resultados de agregación, no documentos coincidentes. Eso es un pequeño hábito que mantiene las respuestas más limpias.
Depura Consultas Con explain y profile
Cuando un resultado se clasifica de manera extraña, usa explain en un solo documento:
GET /products/_explain/SKU10021
{
"query": {
"match": {
"description": "cloud computing"
}
}
}
Cuando una consulta es lenta, usa profile en una prueba de no producción o producción cuidadosamente controlada:
GET /products/_search
{
"profile": true,
"query": {
"bool": {
"must": [
{ "match": { "description": "cloud computing" } }
],
"filter": [
{ "term": { "status": "active" } }
]
}
}
}
La salida del perfil es detallada, pero puede mostrar si el tiempo se gasta en una consulta de texto, un filtro, un script u otra parte de la solicitud. No dejes la creación de perfiles habilitada en el código de la aplicación; úsala como una herramienta de depuración.
Un Hábito Sensato para Construir Consultas
Para la mayoría de las búsquedas de aplicaciones, construye la solicitud en este orden:
- Pon las restricciones exactas en
filter: ID de inquilino, estado, región, ventana de fecha, permisos. - Pon el texto ingresado por el usuario en
mustconmatch,match_phraseomulti_match. - Usa
shouldpara preferencias de clasificación, no requisitos estrictos, a menos que establezcasminimum_should_match. - Limita
_sourcea los campos que necesita el llamante. - Agrega una ordenación estable si la paginación o las exportaciones importan.
- Verifica el mapeo antes de culpar a Elasticsearch.
El Query DSL es poderoso porque separa el filtrado, la puntuación, la ordenación y la forma de la respuesta. Una vez que mantengas esos trabajos separados, las consultas se vuelven más fáciles de leer, más fáciles de ajustar y menos sorprendentes en producción.
Un Pequeño Ejemplo de Solución de Problemas
Supongamos que un usuario busca ACME-1000 y no obtiene ningún resultado, aunque el producto existe. No agregues comodines inmediatamente. Primero verifica el mapeo. Si sku es una keyword, esto debería funcionar:
GET /products/_search
{
"query": {
"term": {
"sku": "ACME-1000"
}
}
}
Si sku se mapeó accidentalmente como text, el análisis puede haber dividido o cambiado el valor. Aún puedes consultarlo en algunos casos, pero la mejor solución suele ser un cambio de mapeo para índices futuros. Los identificadores exactos, estados, regiones e IDs de inquilinos deben ser campos similares a palabras clave. Las descripciones y títulos escritos por humanos deben ser campos de texto. El Query DSL se vuelve mucho más fácil cuando el mapeo coincide con la forma en que las personas realmente recuperan los datos.