Elasticsearch: aggregazioni a bucket [parte 2]

Con le aggregazioni a bucket di Elasticsearch possiamo creare gruppi di documenti. Dopo aver visto nell'articolo precedente le aggregazioni basate sui campi di tipo keyword, ora ci concentreremo su altre funzioni orientate ad altre tipologie di dato. In particolare, useremo aggregazioni per la definizioni di intervalli numerici, di date o gruppi basati su dati georeferenziati.

Share

Reading time: 9 minutes

Come visto nell’articolo XXX, Elasticsearch permette di fare aggregazioni a bucket che definiscono gruppi di documenti. Abbiamo visto come definire bucket basati sui termini e gestire i valori mancanti. In questo articolo analizzeremo, invece, le aggregazioni che coinvolgono la definizione di intervalli di valori (date e numeri) e dati geografici.

histogram, date_histogram

L’aggregazione a istogramma raggruppa i documenti in base a un intervallo specificato.

Con le aggregazioni a istogramma, è possibile visualizzare le distribuzioni dei valori in un determinato intervallo di documenti in modo molto semplice. Naturalmente Elasticsearch non restituisce un grafico vero e proprio, a questo serve Kibana. Ma vi fornirà la risposta JSON che potrete utilizzare per costruire il vostro grafico.

L’esempio che segue suddivide il campo numero_di_byte in 10.000 intervalli:

GET kibana_sample_data_logs/_search
{
  "size": 0,
  "aggs": {
    "number_of_bytes": {
      "histogram": {
        "field": "bytes",
        "interval": 10000
      }
    }
  }
} 

Risposta di esempio

...
"aggregations" : {
  "number_of_bytes" : {
    "buckets" : [
      {
        "key" : 0.0,
        "doc_count" : 13372
      },
      {
        "key" : 10000.0,
        "doc_count" : 702
      }
    ]
  }
 }
} 

L’aggregazione date_histogram utilizza la matematica delle date per generare istogrammi per le serie temporali.

Ad esempio, è possibile trovare il numero di visite del proprio sito web al mese:

GET kibana_sample_data_logs/_search
{
  "size": 0,
  "aggs": {
    "logs_per_month": {
      "date_histogram": {
        "field": "@timestamp",
        "calendar_interval": "month"
      }
    }
  }
} 

Risposta di esempio

...
"aggregations" : {
  "logs_per_month" : {
    "buckets" : [
      {
        "key_as_string" : "2020-10-01T00:00:00.000Z",
        "key" : 1601510400000,
        "doc_count" : 1635
      },
      {
        "key_as_string" : "2020-11-01T00:00:00.000Z",
        "key" : 1604188800000,
        "doc_count" : 6844
      },
      {
        "key_as_string" : "2020-12-01T00:00:00.000Z",
        "key" : 1606780800000,
        "doc_count" : 5595
      }
    ]
  }
}
} 

La risposta contiene tre mesi di log. Se si tracciano i grafici di questi valori, si possono vedere i picchi e le valli del traffico di richieste al proprio sito web mese per mese.

range, date_range, ip_range

L’aggregazione per intervallo consente di definire l’intervallo per ogni bucket.

Ad esempio, è possibile trovare il numero di byte tra 1000 e 2000, 2000 e 3000 e 3000 e 4000. All’interno del parametro range, è possibile definire gli intervalli come oggetti di un array.

GET kibana_sample_data_logs/_search
{
  "size": 0,
  "aggs": {
    "number_of_bytes_distribution": {
      "range": {
        "field": "bytes",
        "ranges": [
          {
            "from": 1000,
            "to": 2000
          },
          {
            "from": 2000,
            "to": 3000
          },
          {
            "from": 3000,
            "to": 4000
          }
        ]
      }
    }
  }
} 

La risposta include i valori della chiave from ed esclude i valori della chiave to.

Risposta di esempio

...
"aggregations" : {
  "number_of_bytes_distribution" : {
    "buckets" : [
      {
        "key" : "1000.0-2000.0",
        "from" : 1000.0,
        "to" : 2000.0,
        "doc_count" : 805
      },
      {
        "key" : "2000.0-3000.0",
        "from" : 2000.0,
        "to" : 3000.0,
        "doc_count" : 1369
      },
      {
        "key" : "3000.0-4000.0",
        "from" : 3000.0,
        "to" : 4000.0,
        "doc_count" : 1422
      }
    ]
  }
 }
} 

L’aggregazione date_range è concettualmente uguale all’aggregazione range, ma consente di eseguire la matematica delle date. Ad esempio, è possibile ottenere tutti i documenti degli ultimi 10 giorni. Per rendere la data più leggibile, includere il formato con un parametro format.

GET kibana_sample_data_logs/_search
{
  "size": 0,
  "aggs": {
    "number_of_bytes": {
      "date_range": {
        "field": "@timestamp",
        "format": "MM-yyyy",
        "ranges": [
          {
            "from": "now-10d/d",
            "to": "now"
          }
        ]
      }
    }
  }
} 

Risposta di esempio

...
"aggregations" : {
  "number_of_bytes" : {
    "buckets" : [
      {
        "key" : "03-2021-03-2021",
        "from" : 1.6145568E12,
        "from_as_string" : "03-2021",
        "to" : 1.615451329043E12,
        "to_as_string" : "03-2021",
        "doc_count" : 0
      }
    ]
  }
 }
} 

L’aggregazione ip_range è per gli indirizzi IP. Funziona sui campi di tipo ip. È possibile definire gli intervalli e le maschere IP nella notazione CIDR .

GET kibana_sample_data_logs/_search
{
  "size": 0,
  "aggs": {
    "access": {
      "ip_range": {
        "field": "ip",
        "ranges": [
          {
            "from": "1.0.0.0",
            "to": "126.158.155.183"
          },
          {
            "mask": "1.0.0.0/8"
          }
        ]
      }
    }
  }
} 

Risposta di esempio

...
"aggregations" : {
  "access" : {
    "buckets" : [
      {
        "key" : "1.0.0.0/8",
        "from" : "1.0.0.0",
        "to" : "2.0.0.0",
        "doc_count" : 98
      },
      {
        "key" : "1.0.0.0-126.158.155.183",
        "from" : "1.0.0.0",
        "to" : "126.158.155.183",
        "doc_count" : 7184
      }
    ]
  }
 }
} 

filter, filters

Un filtro di aggregazione è una clausola di query, esattamente come una query di ricerca – match o termine o intervallo. È possibile utilizzare l’aggregazione del filtro per restringere l’intero insieme di documenti a un insieme specifico prima di creare i bucket.

L’esempio seguente mostra l’aggregazione avg eseguita nel contesto di un filtro. L’aggregazione avg aggrega solo i documenti che corrispondono alla query di intervallo:

GET kibana_sample_data_ecommerce/_search
{
  "size": 0,
  "aggs": {
    "low_value": {
      "filter": {
        "range": {
          "taxful_total_price": {
            "lte": 50
          }
        }
      },
      "aggs": {
        "avg_amount": {
          "avg": {
            "field": "taxful_total_price"
          }
        }
      }
    }
  }
} 

Risposta di esempio

"aggregations" : {
  "low_value" : {
    "doc_count" : 1633,
    "avg_amount" : {
      "value" : 38.363175998928355
    }
  }
 }
} 

Un’aggregazione di filtri è uguale all’aggregazione di filtri, ma consente di utilizzare più aggregazioni di filtri. Mentre l’aggregazione di filtri dà come risultato un singolo bucket, l’aggregazione di filtri restituisce più bucket, uno per ciascuno dei filtri definiti.

Per creare un bucket per tutti i documenti che non corrispondono a nessuna delle query di filtro, impostare la proprietà other_bucket su true:

GET kibana_sample_data_logs/_search
{
  "size": 0,
  "aggs": {
    "200_os": {
      "filters": {
        "other_bucket": true,
        "filters": [
          {
            "term": {
              "response.keyword": "200"
            }
          },
          {
            "term": {
              "machine.os.keyword": "osx"
            }
          }
        ]
      },
      "aggs": {
        "avg_amount": {
          "avg": {
            "field": "bytes"
          }
        }
      }
    }
  }
} 

Risposta di esempio

...
"aggregations" : {
  "200_os" : {
    "buckets" : [
      {
        "doc_count" : 12832,
        "avg_amount" : {
          "value" : 5897.852711970075
        }
      },
      {
        "doc_count" : 2825,
        "avg_amount" : {
          "value" : 5620.347256637168
        }
      },
      {
        "doc_count" : 1017,
        "avg_amount" : {
          "value" : 3247.0963618485744
        }
      }
    ]
  }
 }
} 

global

Le aggregazioni globali consentono di uscire dal contesto di aggregazione di un filtro di aggregazione. Anche se si è inclusa una query di filtro che restringe un insieme di documenti, l’aggregazione globale aggrega tutti i documenti come se la query di filtro non ci fosse. Ignora l’aggregazione del filtro e assume implicitamente la query match_all.

L’esempio seguente restituisce il valore medio del campo taxful_total_price da tutti i documenti dell’indice:

GET kibana_sample_data_ecommerce/_search
{
  "size": 0,
  "query": {
    "range": {
      "taxful_total_price": {
        "lte": 50
      }
    }
  },
  "aggs": {
    "total_avg_amount": {
      "global": {},
      "aggs": {
        "avg_price": {
          "avg": {
            "field": "taxful_total_price"
          }
        }
      }
    }
  }
} 

Risposta di esempio

...
"aggregations" : {
  "total_avg_amount" : {
    "doc_count" : 4675,
    "avg_price" : {
      "value" : 75.05542864304813
    }
  }
 }
} 

Si può notare che il valore medio per il campo taxful_total_price è 75,05 e non 38,36 come visto nell’esempio del filtro quando la query è stata abbinata.

geo_distance, geohash_grid

L’aggregazione geo_distance raggruppa i documenti in cerchi concentrici in base alle distanze da un campo geo-punto di origine. È la stessa cosa dell’aggregazione range, ma funziona su posizioni geografiche.

Ad esempio, si può usare l’aggregazione geo_distanza per trovare tutte le pizzerie nel raggio di 1 km. I risultati della ricerca sono limitati al raggio di 1 km specificato dall’utente, ma è possibile aggiungere un altro risultato trovato entro 2 km.

È possibile utilizzare l’aggregazione geo_distanza solo sui campi mappati come geo_punto.

Un punto è una singola coordinata geografica, come la posizione attuale mostrata dallo smartphone. Un punto in Elasticsearch è rappresentato come segue:

{
  "location": {
    "type": "point",
    "coordinates": {
      "lat": 83.76,
      "lon": -81.2
    }
  }
} 

È anche possibile specificare la latitudine e la longitudine come array [-81.20, 83.76] o come stringa “83.76, -81.20”.

Questa tabella elenca i campi rilevanti di un’aggregazione di geo_distance:

Campo Descrizione Obbiligatorio
field

Specifica il campo di punti geografici su cui si desidera lavorare.

origin

Specifica il punto geo utilizzato per calcolare le distanze.

ranges

Specifica un elenco di intervalli per raccogliere i documenti in base alla loro distanza dal punto di destinazione.

unit

Definisce le unità utilizzate nell'array degli intervalli. L'unità predefinita è m (metri), ma è possibile passare ad altre unità come km (chilometri), mi (miglia), in (pollici), yd (iarde), cm (centimetri) e mm (millimetri).

No
distance_type

Specifica il modo in cui Elasticsearch calcola la distanza. L'impostazione predefinita è sloppy_arc (più veloce ma meno precisa), ma può essere impostata anche su arc (più lenta ma più precisa) o plane (più veloce ma meno precisa). A causa degli elevati margini di errore, utilizzare plane solo per piccole aree geografiche.

No

La sintassi è la seguente:

{
  "aggs": {
    "aggregation_name": {
      "geo_distance": {
        "field": "field_1",
        "origin": "x, y",
        "ranges": [
          {
            "to": "value_1"
          },
          {
            "from": "value_2",
            "to": "value_3"
          },
          {
            "from": "value_4"
          }
        ]
      }
    }
  }
} 

Questo esempio forma dei bucket dalle seguenti distanze da un campo di geopunti:

  • Meno di 10 km
  • Da 10 a 20 km
  • Da 20 a 50 km
  • Da 50 a 100 km
  • Oltre 100 km
GET kibana_sample_data_logs/_search
{
  "size": 0,
  "aggs": {
    "position": {
      "geo_distance": {
        "field": "geo.coordinates",
        "origin": {
          "lat": 83.76,
          "lon": -81.2
        },
        "ranges": [
          {
            "to": 10
          },
          {
            "from": 10,
            "to": 20
          },
          {
            "from": 20,
            "to": 50
          },
          {
            "from": 50,
            "to": 100
          },
          {
            "from": 100
          }
        ]
      }
    }
  }
} 

Risposta di esempio

...
"aggregations" : {
  "position" : {
    "buckets" : [
      {
        "key" : "*-10.0",
        "from" : 0.0,
        "to" : 10.0,
        "doc_count" : 0
      },
      {
        "key" : "10.0-20.0",
        "from" : 10.0,
        "to" : 20.0,
        "doc_count" : 0
      },
      {
        "key" : "20.0-50.0",
        "from" : 20.0,
        "to" : 50.0,
        "doc_count" : 0
      },
      {
        "key" : "50.0-100.0",
        "from" : 50.0,
        "to" : 100.0,
        "doc_count" : 0
      },
      {
        "key" : "100.0-*",
        "from" : 100.0,
        "doc_count" : 14074
      }
    ]
  }
 }
} 

Il geohash_grid aggrega i documenti per l’analisi geografica. Organizza una regione geografica in una griglia di regioni più piccole di dimensioni o precisioni diverse. Valori più bassi di precisione rappresentano aree geografiche più grandi e valori più alti rappresentano aree geografiche più piccole e più precise.

Il numero di risultati restituiti da una query potrebbe essere troppo elevato per visualizzare ogni singolo punto geografico su una mappa. L’aggregazione geohash_grid raggruppa i punti geo vicini calcolando il Geohash per ogni punto, al livello di precisione definito dall’utente (da 1 a 12; il valore predefinito è 5).

I dati dell’esempio dei log web sono distribuiti su un’ampia area geografica, quindi è possibile utilizzare un valore di precisione inferiore. È possibile ingrandire la mappa aumentando il valore di precisione:

GET kibana_sample_data_logs/_search
{
  "size": 0,
  "aggs": {
    "geo_hash": {
      "geohash_grid": {
        "field": "geo.coordinates",
        "precision": 4
      }
    }
  }
} 

Risposta di esempio

...
"aggregations" : {
  "geo_hash" : {
    "buckets" : [
      {
        "key" : "c1cg",
        "doc_count" : 104
      },
      {
        "key" : "dr5r",
        "doc_count" : 26
      },
      {
        "key" : "9q5b",
        "doc_count" : 20
      },
      {
        "key" : "c20g",
        "doc_count" : 19
      },
      {
        "key" : "dr70",
        "doc_count" : 18
      }
      ...
    ]
  }
 }
} 

È possibile visualizzare la risposta aggregata su una mappa utilizzando Kibana.

Più precisa è l’aggregazione, più risorse consuma Elasticsearch, a causa del numero di bucket che l’aggregazione deve calcolare. Per impostazione predefinita, Elasticsearch non genera più di 10.000 bucket. È possibile modificare questo comportamento utilizzando l’attributo size, ma si tenga presente che le prestazioni potrebbero risentire di query molto ampie composte da migliaia di bucket.

adjacency_matrix

L’aggregazione adjacency_matrix consente di definire espressioni di filtro e restituisce una matrice dei filtri che si intersecano, in cui ogni cella non vuota della matrice rappresenta un bucket. È possibile scoprire quanti documenti rientrano in qualsiasi combinazione di filtri.

Utilizzare l’aggregazione adjacency_matrix per scoprire come i concetti sono correlati, visualizzando i dati come grafici.

Ad esempio, nel dataset eCommerce di esempio, per analizzare come sono correlate le diverse aziende manifatturiere:

GET kibana_sample_data_ecommerce/_search
{
  "size": 0,
  "aggs": {
    "interactions": {
      "adjacency_matrix": {
        "filters": {
          "grpA": {
            "match": {
              "manufacturer.keyword": "Low Tide Media"
            }
          },
          "grpB": {
            "match": {
              "manufacturer.keyword": "Elitelligence"
            }
          },
          "grpC": {
            "match": {
              "manufacturer.keyword": "Oceanavigations"
            }
          }
        }
      }
    }
  }
} 

Risposta di esempio

{
   ...
   "aggregations" : {
     "interactions" : {
       "buckets" : [
         {
           "key" : "grpA",
           "doc_count" : 1553
         },
         {
           "key" : "grpA&grpB",
           "doc_count" : 590
         },
         {
           "key" : "grpA&grpC",
           "doc_count" : 329
         },
         {
           "key" : "grpB",
           "doc_count" : 1370
         },
         {
           "key" : "grpB&grpC",
           "doc_count" : 299
         },
         {
           "key" : "grpC",
           "doc_count" : 1218
         }
       ]
     }
   }
 } 

Diamo un’occhiata più da vicino al risultato:

{
    "key" : "grpA&grpB",
    "doc_count" : 590
} 
  • grpA: Prodotti fabbricati da Low Tide Media.
  • grpB: Prodotti fabbricati da Elitelligence.
  • 590: Numero di prodotti fabbricati da entrambi.

È possibile utilizzare Kibana per rappresentare questi dati con un grafico di rete.

nested, reverse_nested

L’aggregazione annidata consente di aggregare i campi all’interno di un oggetto annidato. Il tipo nidificato è una versione specializzata del tipo di dati object, che consente di indicizzare array di oggetti in modo che possano essere interrogati indipendentemente l’uno dall’altro.

Con il tipo oggetto, tutti i dati sono memorizzati nello stesso documento, quindi le corrispondenze per una ricerca possono attraversare i sottodocumenti. Ad esempio, immaginiamo un indice di log con le pagine mappate come tipo di dati oggetto:

PUT logs/_doc/0
{
  "response": "200",
  "pages": [
    {
      "page": "landing",
      "load_time": 200
    },
    {
      "page": "blog",
      "load_time": 500
    }
  ]
} 

Elasticsearch unisce tutte le sottoproprietà delle relazioni tra entità che hanno un aspetto simile a questo:

{
  "logs": {
    "pages": ["landing", "blog"],
    "load_time": ["200", "500"]
  }
} 

Quindi, se si volesse cercare in questo indice con pages=landing e load_time=500, questo documento corrisponderebbe ai criteri anche se il valore di load_time per landing è 200.

Se si vuole essere sicuri che queste corrispondenze tra oggetti non avvengano, mappare il campo come tipo annidato:

PUT logs
{
  "mappings": {
    "properties": {
      "pages": {
        "type": "nested",
        "properties": {
          "page": { "type": "text" },
          "load_time": { "type": "double" }
        }
      }
    }
  }
} 

I documenti annidati consentono di indicizzare lo stesso documento JSON, ma mantengono le pagine in documenti Lucene separati, facendo sì che solo ricerche come pages=landing e load_time=200 restituiscano il risultato atteso. Internamente, gli oggetti annidati indicizzano ogni oggetto dell’array come un documento nascosto separato, il che significa che ogni oggetto annidato può essere interrogato indipendentemente dagli altri.

È necessario specificare un percorso annidato relativo al genitore che contiene i documenti annidati:

GET logs/_search
{
  "query": {
    "match": { "response": "200" }
  },
  "aggs": {
    "pages": {
      "nested": {
        "path": "pages"
      },
      "aggs": {
        "min_load_time": { "min": { "field": "pages.load_time" } }
      }
    }
  }
} 

Risposta di esempio

...
"aggregations" : {
  "pages" : {
    "doc_count" : 2,
    "min_price" : {
      "value" : 200.0
    }
  }
 }
} 

È anche possibile aggregare i valori dai documenti annidati al loro genitore; questa aggregazione è chiamata reverse_nested. Si può usare reverse_nested per aggregare un campo dal documento padre dopo aver raggruppato per il campo dell’oggetto annidato. L’aggregazione reverse_nested “unisce” la pagina principale e ottiene il tempo di caricamento per ogni variazione.

L’aggregazione reverse_nested è una sotto-aggregazione all’interno di un’aggregazione nidificata. Accetta una singola opzione, denominata percorso. Questa opzione definisce il numero di passi indietro nella gerarchia dei documenti che Elasticsearch compie per calcolare le aggregazioni.

GET logs/_search
{
  "query": {
    "match": { "response": "200" }
  },
  "aggs": {
    "pages": {
      "nested": {
        "path": "pages"
      },
      "aggs": {
        "top_pages_per_load_time": {
          "terms": {
            "field": "pages.load_time"
          },
          "aggs": {
            "comment_to_logs": {
              "reverse_nested": {},
              "aggs": {
                "min_load_time": {
                  "min": {
                    "field": "pages.load_time"
                  }
                }
              }
            }
          }
        }
      }
    }
  }
} 

Risposta di esempio

...
"aggregations" : {
  "pages" : {
    "doc_count" : 2,
    "top_pages_per_load_time" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : 200.0,
          "doc_count" : 1,
          "comment_to_logs" : {
            "doc_count" : 1,
            "min_load_time" : {
              "value" : null
            }
          }
        },
        {
          "key" : 500.0,
          "doc_count" : 1,
          "comment_to_logs" : {
            "doc_count" : 1,
            "min_load_time" : {
              "value" : null
            }
          }
        }
      ]
    }
  }
 }
} 

La risposta mostra che l’indice dei log ha una pagina con un tempo di caricamento di 200 e una con un tempo di caricamento di 500.

More To Explore

Intelligenza artificiale

DBSCAN: come funziona

Gli algoritmi di clustering permettono di raggruppare i dati in base alle loro caratteristiche intrinseche. Esistono molti algoritmi che sono stati sviluppati negli anni. Tra quelli più famosi non possiamo dimenticare il DBSCAN. Scopriamo, passo passo, come questo metodo riesce ad individuare cluster di dati di qualsiasi forma e dimensione grazie a soli due parametri.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Design with MongoDB

Design with MongoDB!!!

Buy the new book that will help you to use MongoDB correctly for your applications. Available now on Amazon!