tutoriales.com

Analizando Datos con Agregaciones en Elasticsearch: Guía Completa de Uso

Las agregaciones en Elasticsearch son una herramienta poderosa para obtener insights de tus datos. Este tutorial te guiará a través de los diferentes tipos de agregaciones, desde las más básicas hasta las más complejas, permitiéndote transformar tus datos crudos en información valiosa y actionable.

Intermedio15 min de lectura12 views
Reportar error

Las bases de datos NoSQL como Elasticsearch son conocidas por su velocidad y flexibilidad en la búsqueda de texto completo. Sin embargo, su verdadero potencial analítico brilla cuando utilizamos las agregaciones. Las agregaciones nos permiten procesar y analizar grandes volúmenes de datos, extrayendo métricas, estadísticas y agrupaciones de forma eficiente.

En este tutorial, exploraremos en profundidad el mundo de las agregaciones de Elasticsearch. Aprenderemos cómo utilizarlas para generar informes, construir paneles de control y entender mejor la información contenida en nuestros índices.

🚀 ¿Qué son las Agregaciones en Elasticsearch?

Imagina que tienes millones de documentos en Elasticsearch, cada uno representando una venta en tu tienda online. Quieres saber el total de ventas por región, el producto más vendido en el último mes o la cantidad promedio de artículos por pedido. Realizar estas consultas con búsquedas individuales sería ineficiente o imposible. Aquí es donde entran las agregaciones.

Las agregaciones son operaciones que procesan los datos de tus documentos para devolver resultados analíticos. Son similares a las cláusulas GROUP BY y funciones de agregación (SUM, AVG, COUNT, etc.) en SQL, pero con una flexibilidad y escalabilidad mucho mayores, diseñadas para el ecosistema distribuido de Elasticsearch.

🎯 Tipos principales de Agregaciones

Elasticsearch clasifica las agregaciones en cuatro categorías principales:

  1. Metric Aggregations: Calculan métricas sobre un conjunto de documentos (suma, promedio, mínimo, máximo, recuento, etc.).
  2. Bucket Aggregations: Agrupan documentos en "cubos" (buckets) basándose en un criterio. Cada bucket puede contener métricas o sub-agregaciones.
  3. Pipeline Aggregations: Operan sobre la salida de otras agregaciones, permitiendo cálculos más complejos como el promedio de promedios o la diferencia entre totales.
  4. Matrix Aggregations: Realizan cálculos sobre múltiples campos de documentos y producen una matriz de resultados. Menos comunes, pero útiles para correlaciones multidimensionales.

En este tutorial, nos centraremos principalmente en las Metric y Bucket Aggregations, ya que son las más fundamentales y ampliamente utilizadas.


🛠️ Comenzando: Tu Primeras Agregaciones

Para empezar, necesitamos algunos datos. Vamos a simular un índice de ventas (sales) con documentos que contienen información como el price, product_id, category y region.

📝 Preparando los datos de ejemplo

Primero, creamos un índice simple sin mapeo explícito para que Elasticsearch lo infiera:

PUT /sales
{
  "settings": {
    "number_of_shards": 1
  }
}

Ahora, indexamos algunos documentos de ejemplo:

POST /sales/_bulk
{"index":{"_id":"1"}}
{"price": 100.50, "product_id": "P001", "category": "Electronics", "region": "North", "timestamp": "2023-01-01T10:00:00Z"}
{"index":{"_id":"2"}}
{"price": 50.00, "product_id": "P002", "category": "Books", "region": "South", "timestamp": "2023-01-01T11:00:00Z"}
{"index":{"_id":"3"}}
{"price": 75.25, "product_id": "P001", "category": "Electronics", "region": "North", "timestamp": "2023-01-01T12:00:00Z"}
{"index":{"_id":"4"}}
{"price": 200.00, "product_id": "P003", "category": "Electronics", "region": "West", "timestamp": "2023-01-01T13:00:00Z"}
{"index":{"_id":"5"}}
{"price": 30.00, "product_id": "P002", "category": "Books", "region": "North", "timestamp": "2023-01-01T14:00:00Z"}
{"index":{"_id":"6"}}
{"price": 120.00, "product_id": "P004", "category": "Apparel", "region": "East", "timestamp": "2023-01-01T15:00:00Z"}
{"index":{"_id":"7"}}
{"price": 90.00, "product_id": "P001", "category": "Electronics", "region": "South", "timestamp": "2023-01-01T16:00:00Z"}
{"index":{"_id":"8"}}
{"price": 60.00, "product_id": "P005", "category": "Books", "region": "West", "timestamp": "2023-01-01T17:00:00Z"}
{"index":{"_id":"9"}}
{"price": 150.00, "product_id": "P003", "category": "Electronics", "region": "East", "timestamp": "2023-01-01T18:00:00Z"}
{"index":{"_id":"10"}}
{"price": 80.00, "product_id": "P004", "category": "Apparel", "region": "North", "timestamp": "2023-01-01T19:00:00Z"}
{"index":{"_id":"11"}}
{"price": 110.00, "product_id": "P001", "category": "Electronics", "region": "North", "timestamp": "2023-01-01T20:00:00Z"}

💡 Desactivando los hits para optimizar

Cuando solo estamos interesados en los resultados de las agregaciones, podemos indicar a Elasticsearch que no devuelva los documentos (hits) que coinciden con la consulta. Esto reduce la carga en el clúster y acelera la respuesta.

GET /sales/_search
{
  "size": 0, 
  "aggs": {
    "total_sales": {
      "sum": {
        "field": "price"
      }
    }
  }
}

En la respuesta, verás un hits.total.value que indica cuántos documentos se consideraron para la agregación, pero la lista hits.hits estará vacía. La sección clave es aggregations.

💡 Consejo: Siempre usa `"size": 0` cuando tu consulta principal sea solo para agregaciones.

📊 Agregaciones de Métrica (Metric Aggregations)

Estas agregaciones calculan un único valor (o un conjunto de valores) sobre un campo numérico en un grupo de documentos. Son las más sencillas de entender y aplicar.

📈 Sum Aggregation (Suma)

Calcula la suma total de los valores de un campo numérico.

Pregunta: ¿Cuál es el total de ventas de todos los productos?

GET /sales/_search
{
  "size": 0,
  "aggs": {
    "total_revenue": {
      "sum": {
        "field": "price"
      }
    }
  }
}

Resultado esperado (fragmento):

...
"aggregations": {
  "total_revenue": {
    "value": 1075.75
  }
}
...

⚖️ Avg Aggregation (Promedio)

Calcula el promedio de los valores de un campo numérico.

Pregunta: ¿Cuál es el precio promedio de una venta?

GET /sales/_search
{
  "size": 0,
  "aggs": {
    "average_price": {
      "avg": {
        "field": "price"
      }
    }
  }
}

📉 Min/Max Aggregations (Mínimo/Máximo)

Encuentra el valor mínimo y máximo de un campo numérico.

Pregunta: ¿Cuál fue la venta más barata y la más cara?

GET /sales/_search
{
  "size": 0,
  "aggs": {
    "min_price": {
      "min": {
        "field": "price"
      }
    },
    "max_price": {
      "max": {
        "field": "price"
      }
    }
  }
}

🔢 Value Count Aggregation (Conteo de Valores)

Cuenta el número de documentos que tienen un valor en un campo específico.

Pregunta: ¿Cuántos productos diferentes se han vendido (basado en product_id)?

GET /sales/_search
{
  "size": 0,
  "aggs": {
    "unique_product_count": {
      "value_count": {
        "field": "product_id"
      }
    }
  }
}
📌 Nota: `value_count` no cuenta valores *únicos*, sino el número de documentos donde el campo existe. Para valores únicos, usaremos `cardinality` más adelante.

📊 Stats Aggregation (Estadísticas)

Combina varias métricas básicas (min, max, avg, sum, count) en una sola agregación, muy útil para un resumen rápido.

Pregunta: Obtén un resumen estadístico de los precios de venta.

GET /sales/_search
{
  "size": 0,
  "aggs": {
    "price_stats": {
      "stats": {
        "field": "price"
      }
    }
  }
}

Resultado esperado (fragmento):

...
"aggregations": {
  "price_stats": {
    "count": 11,
    "min": 30.0,
    "max": 200.0,
    "avg": 97.79545454545455,
    "sum": 1075.75
  }
}
...

📈 Extended Stats Aggregation (Estadísticas Extendidas)

Similar a stats, pero añade estadísticas más avanzadas como desviación estándar, varianza y suma de cuadrados.

Pregunta: Obtén estadísticas detalladas de los precios, incluyendo desviación estándar.

GET /sales/_search
{
  "size": 0,
  "aggs": {
    "price_extended_stats": {
      "extended_stats": {
        "field": "price"
      }
    }
  }
}

📦 Agregaciones de Cubo (Bucket Aggregations)

Estas agregaciones no calculan un valor único, sino que agrupan documentos en "cubos" o categorías. Cada cubo puede contener sus propias agregaciones de métrica o incluso sub-cubos, permitiendo análisis jerárquicos.

🏷️ Terms Aggregation (Términos)

Agrupa documentos por valores únicos de un campo (generalmente keyword o campos text con fielddata habilitado, aunque esto último es desaconsejado). Es el equivalente a GROUP BY.

Pregunta: ¿Cuántas ventas hay por categoría de producto?

GET /sales/_search
{
  "size": 0,
  "aggs": {
    "sales_by_category": {
      "terms": {
        "field": "category.keyword",
        "size": 10 
      }
    }
  }
}
🔥 Importante: Para los campos de tipo `text`, Elasticsearch los analiza y tokeniza. Si quieres agrupar por el valor exacto del campo, usa la versión `.keyword` (si el mapeo lo generó automáticamente) o mapea el campo como `keyword` explícitamente.

El size dentro de terms controla cuántos buckets (términos) quieres que Elasticsearch devuelva. Por defecto, son 10. Si necesitas todos, puedes poner un valor muy alto o configurar shard_size.

Resultado esperado (fragmento):

...
"aggregations": {
  "sales_by_category": {
    "doc_count_error_upper_bound": 0,
    "sum_other_doc_count": 0,
    "buckets": [
      {
        "key": "Electronics",
        "doc_count": 6
      },
      {
        "key": "Books",
        "doc_count": 3
      },
      {
        "key": "Apparel",
        "doc_count": 2
      }
    ]
  }
}
...

🔢 Cardinality Aggregation (Cardinalidad)

Calcula el número aproximado de valores únicos para un campo. Es muy eficiente para contar valores únicos en grandes conjuntos de datos, aunque puede haber un pequeño margen de error (configuración precision_threshold).

Pregunta: ¿Cuántos productos únicos (product_id) se han vendido?

GET /sales/_search
{
  "size": 0,
  "aggs": {
    "unique_product_ids": {
      "cardinality": {
        "field": "product_id.keyword"
      }
    }
  }
}

⏱️ Date Histogram Aggregation (Histograma de Fechas)

Agrupa documentos en cubos basados en un intervalo de tiempo. Ideal para series temporales y gráficos de tendencias.

Pregunta: ¿Cuántas ventas hubo por hora el 1 de enero de 2023?

GET /sales/_search
{
  "size": 0,
  "aggs": {
    "sales_per_hour": {
      "date_histogram": {
        "field": "timestamp",
        "fixed_interval": "1h", 
        "format": "yyyy-MM-dd HH"
      }
    }
  }
}

El fixed_interval puede ser 1s, 1m, 1h, 1d, 1w, 1M (mes), 1y (año), etc.

📌 Nota: Para intervalos más flexibles basados en el reloj de pared (como principios de día, semana, mes, etc.), se usa `calendar_interval`. Para un intervalo fijo desde el inicio de los tiempos, `fixed_interval`.

📊 Range Aggregation (Rango)

Agrupa documentos en cubos definidos por rangos de valores para un campo numérico. Útil para clasificar datos en intervalos discretos.

Pregunta: ¿Cuántas ventas están en rangos de precios bajos, medios y altos?

GET /sales/_search
{
  "size": 0,
  "aggs": {
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          { "to": 50, "key": "Low" },
          { "from": 50, "to": 100, "key": "Medium" },
          { "from": 100, "key": "High" }
        ]
      }
    }
  }
}
💡 Consejo: Los rangos son exclusivos del `to` y inclusivos del `from` por defecto. Es decir, `[from, to)`.

⛓️ Anidando Agregaciones: Un Poder Analítico Inmenso

El verdadero poder de las agregaciones radica en la capacidad de anidarlas. Puedes colocar agregaciones de métrica dentro de agregaciones de cubo, o incluso múltiples capas de agregaciones de cubo.

Agregación: terms (Región) Bucket: "Norte" Bucket: "Sur" sum Ventas avg Precio sum Ventas avg Precio

🌍 Ventas por Región y Categoría, con Total y Promedio

Pregunta: Quiero saber el total de ventas y el precio promedio para cada categoría, agrupado por región.

GET /sales/_search
{
  "size": 0,
  "aggs": {
    "sales_by_region": {
      "terms": {
        "field": "region.keyword",
        "size": 5
      },
      "aggs": {
        "sales_by_category": {
          "terms": {
            "field": "category.keyword",
            "size": 5
          },
          "aggs": {
            "total_category_revenue": {
              "sum": {
                "field": "price"
              }
            },
            "average_category_price": {
              "avg": {
                "field": "price"
              }
            }
          }
        }
      }
    }
  }
}

Análisis de la Estructura:

  1. sales_by_region (Bucket Aggregation - terms): Crea cubos para cada region.
  2. sales_by_category (Bucket Aggregation - terms): Anidada dentro de cada region bucket, crea cubos para cada category.
  3. total_category_revenue (Metric Aggregation - sum): Anidada dentro de cada category bucket, suma los precios de los documentos en ese cubo.
  4. average_category_price (Metric Aggregation - avg): Anidada dentro de cada category bucket, calcula el promedio de los precios.

Este ejemplo demuestra cómo puedes construir informes complejos y multidimensionales con una sola consulta a Elasticsearch.

🗓️ Ventas por Día y Producto más Vendido

Pregunta: Quiero ver el total de ventas diarias y, para cada día, cuál fue el product_id más vendido.

GET /sales/_search
{
  "size": 0,
  "aggs": {
    "sales_per_day": {
      "date_histogram": {
        "field": "timestamp",
        "calendar_interval": "1d",
        "format": "yyyy-MM-dd"
      },
      "aggs": {
        "daily_revenue": {
          "sum": {
            "field": "price"
          }
        },
        "top_product_daily": {
          "terms": {
            "field": "product_id.keyword",
            "size": 1
          },
          "aggs": {
            "product_revenue": {
              "sum": {
                "field": "price"
              }
            }
          }
        }
      }
    }
  }
}

Aquí estamos anidando un terms aggregation (top_product_daily) dentro de cada bucket de date_histogram (sales_per_day), y dentro de ese terms aggregation, anidamos un sum (product_revenue). Esto nos permite obtener el producto más vendido (limitando size a 1) y sus ventas para cada día.


⚡ Agregaciones de Pipeline (Pipeline Aggregations)

Las agregaciones de pipeline operan sobre la salida de otras agregaciones. Esto permite realizar cálculos que no son posibles directamente con las métricas o cubos básicos, como calcular diferencias, promedios de promedios o mover promedios.

📉 Moving Average (Promedio Móvil)

Calcula un promedio móvil sobre una serie de datos generada por otra agregación (típicamente un date_histogram). Es útil para suavizar datos y ver tendencias.

Pregunta: Calcula el total de ventas diarias y luego el promedio móvil de 3 días para esas ventas.

GET /sales/_search
{
  "size": 0,
  "aggs": {
    "sales_per_day": {
      "date_histogram": {
        "field": "timestamp",
        "calendar_interval": "1d",
        "format": "yyyy-MM-dd"
      },
      "aggs": {
        "daily_revenue": {
          "sum": {
            "field": "price"
          }
        },
        "three_day_moving_avg": {
          "moving_fn": {
            "buckets_path": "daily_revenue",
            "window": 3,
            "script": "MovingFunctions.unweightedAvg(values)"
          }
        }
      }
    }
  }
}
📌 Nota: `moving_fn` es una agregación de pipeline versátil que requiere un `buckets_path` (la ruta a la métrica de la agregación padre) y un script para definir la función de promedio móvil (o cualquier otra función personalizada).

📈 Derivative Aggregation (Derivada)

Calcula la derivada de una métrica en una serie temporal. Esto es útil para ver la tasa de cambio entre intervalos.

Pregunta: ¿Cuál es la tasa de cambio en las ventas diarias?

GET /sales/_search
{
  "size": 0,
  "aggs": {
    "sales_per_day": {
      "date_histogram": {
        "field": "timestamp",
        "calendar_interval": "1d",
        "format": "yyyy-MM-dd"
      },
      "aggs": {
        "daily_revenue": {
          "sum": {
            "field": "price"
          }
        },
        "daily_revenue_derivative": {
          "derivative": {
            "buckets_path": "daily_revenue"
          }
        }
      }
    }
  }
}

🔍 Filtrando Agregaciones

Muchas veces, no querrás agregar todos los documentos de tu índice, sino solo un subconjunto. Puedes aplicar filtros a tus agregaciones de varias maneras.

🕵️‍♀️ Query con Agregaciones

La forma más común es incluir tus agregaciones en una consulta _search regular que ya tiene una cláusula query.

Pregunta: Solo quiero las ventas de la categoría 'Electronics', y luego agruparlas por región.

GET /sales/_search
{
  "size": 0,
  "query": {
    "term": {
      "category.keyword": "Electronics"
    }
  },
  "aggs": {
    "electronics_sales_by_region": {
      "terms": {
        "field": "region.keyword"
      },
      "aggs": {
        "total_region_revenue": {
          "sum": {
            "field": "price"
          }
        }
      }
    }
  }
}

En este caso, la query (term en category.keyword) se aplica antes de que se ejecuten las agregaciones, lo que significa que solo los documentos de la categoría 'Electronics' serán considerados para la agregación electronics_sales_by_region.

🧱 Filter Aggregation (Agregación de Filtro)

Si necesitas aplicar diferentes filtros a diferentes ramas de tus agregaciones (y no a la consulta global), puedes usar la agregación filter.

Pregunta: Quiero el total de ventas global, pero también el total de ventas solo de la región 'North' y solo de la región 'South' como agregaciones separadas.

GET /sales/_search
{
  "size": 0,
  "aggs": {
    "global_total_sales": {
      "sum": {
        "field": "price"
      }
    },
    "north_region_sales": {
      "filter": {
        "term": {
          "region.keyword": "North"
        }
      },
      "aggs": {
        "total_north_sales": {
          "sum": {
            "field": "price"
          }
        }
      }
    },
    "south_region_sales": {
      "filter": {
        "term": {
          "region.keyword": "South"
        }
      },
      "aggs": {
        "total_south_sales": {
          "sum": {
            "field": "price"
          }
        }
      }
    }
  }
}

Aquí, global_total_sales considera todos los documentos. north_region_sales y south_region_sales aplican sus respectivos filtros antes de calcular la suma, sin afectar a otras agregaciones en el mismo nivel.

🔳 Filters Aggregation (Agregación de Filtros Múltiples)

Si tienes múltiples filtros discretos que quieres aplicar para crear diferentes cubos, filters es más conciso que múltiples filter aggregations.

Pregunta: Agrupa las ventas en cubos para productos 'P001' y 'P002' y para 'Electronics' y 'Books' simultáneamente.

GET /sales/_search
{
  "size": 0,
  "aggs": {
    "custom_filters": {
      "filters": {
        "filters": {
          "product_P001": {
            "term": {
              "product_id.keyword": "P001"
            }
          },
          "product_P002": {
            "term": {
              "product_id.keyword": "P002"
            }
          },
          "category_Electronics": {
            "term": {
              "category.keyword": "Electronics"
            }
          },
          "category_Books": {
            "term": {
              "category.keyword": "Books"
            }
          }
        }
      },
      "aggs": {
        "total_sales_in_filter": {
          "sum": {
            "field": "price"
          }
        }
      }
    }
  }
}

Cada clave dentro de filters.filters (ej. product_P001) se convierte en un bucket independiente, y las sub-agregaciones se aplican a los documentos que pasan ese filtro.


⚙️ Optimizando tus Agregaciones

Las agregaciones pueden ser costosas en términos de recursos, especialmente con grandes volúmenes de datos. Aquí hay algunos consejos para optimizarlas:

  • Usa size: 0: Como mencionamos, si solo necesitas las agregaciones, suprime los hits.
  • Campos .keyword: Para terms y otras agregaciones de cubos basadas en texto, usa siempre el campo .keyword (o mapea el campo como keyword directamente) para evitar la sobrecarga de fielddata en campos text.
  • Reduce el size de terms: El size por defecto es 10. Si solo necesitas los 5 principales, especifica "size": 5. Si necesitas todos los términos únicos, considera la implicación en rendimiento o usa composite aggregation para paginación si hay muchísimos.
  • precision_threshold en cardinality: Ajusta este parámetro para encontrar un equilibrio entre precisión y uso de memoria para el conteo de valores únicos.
  • Filtra primero: Si puedes aplicar un filtro general a tu consulta _search, hazlo. Cuantos menos documentos necesiten procesar las agregaciones, mejor.
  • Caché de agregaciones: Elasticsearch cachea los resultados de las agregaciones, lo que puede acelerar consultas repetidas. Asegúrate de que tus índices tengan index.store.preload configurado si es apropiado para tu carga de trabajo.
Eficiencia de Agregaciones: 90%

🌐 Visualización con Kibana y Agregaciones

Kibana, la herramienta de visualización y gestión de Elasticsearch, se basa en gran medida en las agregaciones. Cada gráfico o tabla que creas en Kibana utiliza una o más agregaciones de Elasticsearch para extraer y mostrar los datos.

Ejemplo en Kibana
  1. Explorar en Discover: Ve a la sección Discover en Kibana. Selecciona tu índice sales. Verás todos los documentos.
  2. Crear un "Lens" o "Visualize": Navega a Analytics -> Lens o Visualize Library.
  3. Configurar un gráfico de barras:
    • Arrastra el campo region.keyword al eje X. Kibana automáticamente creará una terms aggregation y te mostrará el conteo de documentos por región.
    • Arrastra el campo price al eje Y y selecciona "Sum" como métrica. Kibana agregará un sum aggregation anidado dentro de cada bucket de región, mostrando el total de ventas por región.

Este proceso es una interfaz gráfica para las mismas consultas de agregación que hemos estado construyendo manualmente.

📌 Nota: Entender cómo funcionan las agregaciones te da un control mucho mayor al diseñar visualizaciones complejas en Kibana o al integrarlas en tus propias aplicaciones.

✅ Conclusión

Las agregaciones son el corazón del análisis de datos en Elasticsearch. Desde obtener métricas simples hasta construir complejos informes multidimensionales y series temporales, dominarlas es fundamental para extraer valor de tus datos.

Hemos cubierto los tipos básicos, la poderosa capacidad de anidamiento y algunas agregaciones de pipeline clave. Con esta base, estás listo para empezar a transformar tus datos crudos en inteligencia accionable.

Experimenta con diferentes combinaciones, anidamientos y filtros. ¡El límite es tu imaginación y la estructura de tus datos!

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!