🔍 elastic search

ES - Aggregations

beomsic 2022. 9. 22. 17:23

Aggregations


Elasticsearch는 검색엔진 뿐 아니라 로그 분석 을 비롯한 다양한 목적의 데이터 시스템으로 사용되고 있다.

이렇게 활용이 가능한 이유는 데이터를 단순히 검색할 뿐 아니라 여러가지 연산을 할 수 있는 Aggregation 기능이 있기 때문이다.

Kibana 에서 바 차트, 파이 차트 등으로 데이터를 시각화 할 수 있는데 여기서 Aggregation 기능을 사용

aggregation은 번역하면 “집계” 라는 뜻이지만, ES에서는 원문대로 aggregation 혹은 애그리게이션으로 많이 표현.

크게 세 종류

  1. Metrics Aggregations
  2. Bucket Aggregations
  3. Pipeline Aggregations

Metrics Aggregations


필드 값에서 합계 또는 평균과 같은 메트릭을 계산하는 aggregation

  • ex) min, max, sum, avg, stats, cardinality 등등

min, max, sum, avg

최소, 최대, 합, 평균 값을 가져오는 aggregation

  • 가장 흔하게 사용되는 metrics aggregation

Example - sum

POST /sales/_search?size=0
{
  "query": {
    "constant_score": {
      "filter": {
        "match": { "type": "hat" }
      }
    }
  },
  "aggs": {
    "hat_prices": { "sum": { "field": "price" } }
  }
}

// curl
curl -X POST "localhost:9200/sales/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
  "query": {
    "constant_score": {
      "filter": {
        "match": { "type": "hat" }
      }
    }
  },
  "aggs": {
    "hat_prices": { "sum": { "field": "price" } }
  }
}
'

// response
{
  ...
  "aggregations": {
    "hat_prices": {
      "value": 450.0
    }
  }
}
  • sales 에 있는 hat 필드값의 hat_prices 값의 합을 가져옴.

Stats

min, max, sum, avg 값을 모두 가져와야 한다면 stats aggregation을 사용하면 위 4개의 값 모두와 count 값을 한번에 가져온다.

Example

// grade 필드의 min, max, sum, avg 값을 가져오는 aggs
POST /exams/_search?size=0
{
  "aggs": {
    "grades_stats": { "stats": { "field": "grade" } }
  }
}

// curl
curl -X POST "localhost:9200/exams/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
  "aggs": {
    "grades_stats": { "stats": { "field": "grade" } }
  }
}
'

// response
{
  ...

  "aggregations": {
    "grades_stats": {
      "count": 2,
      "min": 50.0,
      "max": 100.0,
      "avg": 75.0,
      "sum": 150.0
    }
  }
}

Aggregation 결과만 보고 싶다면 ❓

기본적으로 Aggregation을 포함하는 검색은 aggregation 및 검색 결과를 모두 반환한다.

  • 이때, aggregation 결과만 보고 싶다면 “size” : 0 을 추가하면 된다.
GET /students/_search
{
	"size": 0, 
    "aggs": ...
}

cardinality

필드의 값이 모두 몇 종류인지 분포값을 알고 싶을 때 사용

일반적으로 Text 필드에서는 사용할 수 없고

  • 숫자
  • Keyword
  • ip

필드 등에 사용이 가능하다.

 

사용자 접속 로그에서 IP 주소 필드를 가지고 실제 접속한 사용자가 몇명인지 파악하는 등의 용도로 주로 사용된다.

 

Example

// type 필드 가 몇 종류인지 가져오는 aggs
POST /sales/_search?size=0
{
  "aggs": {
    "type_count": {
      "cardinality": {
        "field": "type"
      }
    }
  }
}

// curl 
curl -X POST "localhost:9200/sales/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
  "aggs": {
    "type_count": {
      "cardinality": {
        "field": "type"
      }
    }
  }
}
'

// response
{
  ...
  "aggregations": {
    "type_count": {
      "value": 3
    }
  }
}

Bucket Aggregations


주어진 조건으로 분류된 버킷들을 만들고 각 버킷에 소속되는 도큐먼트들을 모아 그룹으로 구분

  • 각 버킷 별로 포함되는 도큐먼트의 개수가 doc_count 값에 기본적으로 표시된다.
  • 각 버킷 안에 metrics aggregation을 이용한 다른 계산도 가능하다.

range

숫자 필드 값으로 범위를 지정하고 각 범위에 해당하는 버킷을 만드는 aggregation

Example

// price 필드의 값을 range aggs를 이용해 버킷으로 구분
GET sales/_search
{
  "aggs": {
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          { "to": 100.0 },
          { "from": 100.0, "to": 200.0 },
          { "from": 200.0 }
        ]
      }
    }
  }
}

// curl
curl -X GET "localhost:9200/sales/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "aggs": {
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          { "to": 100.0 },
          { "from": 100.0, "to": 200.0 },
          { "from": 200.0 }
        ]
      }
    }
  }
}
'

// response 
{
  ...
  "aggregations": {
    "price_ranges": {
      "buckets": [
        {
          "key": "*-100.0",
          "to": 100.0,
          "doc_count": 2
        },
        {
          "key": "100.0-200.0",
          "from": 100.0,
          "to": 200.0,
          "doc_count": 2
        },
        {
          "key": "200.0-*",
          "from": 200.0,
          "doc_count": 3
        }
      ]
    }
  }
}

histogram

range 와 동일하게 숫자 필드의 범위를 나누는 aggregation

rangefrom / to 를 이용해 각 버킷의 범위를 지정하지만,

histograminterval 옵션 을 이용해서 주어진 간격 크기대로 버킷을 구분한다.

Example

// price 필드의 값을 histogram aggs를 이용해 버킷으로 구분
POST /sales/_search?size=0
{
  "aggs": {
    "prices": {
      "histogram": {
        "field": "price",
        "interval": 50
      }
    }
  }
}

// curl
curl -X POST "localhost:9200/sales/_search?size=0&pretty" -H 'Content-Type: application/json' -d'
{
  "aggs": {
    "prices": {
      "histogram": {
        "field": "price",
        "interval": 50
      }
    }
  }
}
'

// response
{
  ...
  "aggregations": {
    "prices": {
      "buckets": [
        {
          "key": 0.0,
          "doc_count": 1
        },
        {
          "key": 50.0,
          "doc_count": 1
        },
        {
          "key": 100.0,
          "doc_count": 0
        },
        {
          "key": 150.0,
          "doc_count": 2
        },
        {
          "key": 200.0,
          "doc_count": 3
        }
      ]
    }
  }
}

terms

keyword 필드의 문자열 별로 버킷을 나누는 aggregation

Example

// genre 값에 따라 버킷 생성
GET /_search
{
  "aggs": {
    "genres": {
      "terms": { "field": "genre" }
    }
  }
}

// curl
curl -X GET "localhost:9200/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "aggs": {
    "genres": {
      "terms": { "field": "genre" }
    }
  }
}
'

// response
{
  ...
  "aggregations": {
    "genres": {
      "doc_count_error_upper_bound": 0,   
      "sum_other_doc_count": 0,           
      "buckets": [                        
        {
          "key": "electronic",
          "doc_count": 6
        },
        {
          "key": "rock",
          "doc_count": 3
        },
        {
          "key": "jazz",
          "doc_count": 2
        }
      ]
    }
  }
}

⚠️ text 필드로 terms aggregation을 사용했을 경우 ⚠️

오류가 발생 💣

  • 텍스트 필드는 aggregation 및 정렬과 같은 문서별 필드 데이터가 필요한 작업에 최적화 ❌
  • 기본적으로 이러한 작업은 실행 중지된다.
  • 대신 키워드 필드를 사용

즉, 텍스트 필드는 terms 로 나누어서 색인되기 때문에 버킷을 나누기에 적당하지 않다.

  • 입력된 문자열을 하나의 토큰으로 저장하는 키워드 필드를 사용해야 한다!!

Pipeline Aggregations


다른 metrics aggregation의 결과를 새로운 입력으로 하는 pipeline aggregation

 

다른 버킷의 결과들을 다시 연산

  • min_bucket
  • max_bucket
  • avg_bucket
  • sum_bucket
  • stats_bucket
  • moving_avg - 이동 평균 구하기
  • derivative - 미분 값 구하기
  • cumulative_sum - 누적 합 구하기

Pipeline aggregation은 “buckets_path”: “<버킷 이름>” 옵션을 이용해 사용할 버킷을 입력 값으로 지정한다.

Example

// price 의 값을 입력으로 받는 cumulative_sum aggs 실행
POST /sales/_search
{
  "size": 0,
  "aggs": {
    "sales_per_month": {
      "date_histogram": {
        "field": "date",
        "calendar_interval": "month"
      },
      "aggs": {
        "sales": {
          "sum": {
            "field": "price"
          }
        },
        "cumulative_sales": {
          "cumulative_sum": {
            "buckets_path": "sales" 
          }
        }
      }
    }
  }
}

// curl
curl -X POST "localhost:9200/sales/_search?pretty" -H 'Content-Type: application/json' -d'
{
  "size": 0,
  "aggs": {
    "sales_per_month": {
      "date_histogram": {
        "field": "date",
        "calendar_interval": "month"
      },
      "aggs": {
        "sales": {
          "sum": {
            "field": "price"
          }
        },
        "cumulative_sales": {
          "cumulative_sum": {
            "buckets_path": "sales" 
          }
        }
      }
    }
  }
}
'

// response
{
   "took": 11,
   "timed_out": false,
   "_shards": ...,
   "hits": ...,
   "aggregations": {
      "sales_per_month": {
         "buckets": [
            {
               "key_as_string": "2015/01/01 00:00:00",
               "key": 1420070400000,
               "doc_count": 3,
               "sales": {
                  "value": 550.0
               },
               "cumulative_sales": {
                  "value": 550.0
               }
            },
            {
               "key_as_string": "2015/02/01 00:00:00",
               "key": 1422748800000,
               "doc_count": 2,
               "sales": {
                  "value": 60.0
               },
               "cumulative_sales": {
                  "value": 610.0
               }
            },
            {
               "key_as_string": "2015/03/01 00:00:00",
               "key": 1425168000000,
               "doc_count": 2,
               "sales": {
                  "value": 375.0
               },
               "cumulative_sales": {
                  "value": 985.0
               }
            }
         ]
      }
   }
}

Sub Aggregation


Bucket Aggregation으로 만든 버킷들 내부에 다시 “aggs” : {} 을 선언해

또 다른 버킷을 만들거나 Metrics Aggregation을 만들어 사용하는 aggregation

 

Example

// 이미 만든 stations 버킷별로 avg aggs을 이용해 passangers 필드의 평균값 계산 
GET my_stations/_search
{
  "size": 0,
  "aggs": {
    "stations": {
      "terms": {
        "field": "station.keyword"
      },
      "aggs": {
        "avg_psg_per_st": {
          "avg": {
            "field": "passangers"
          }
        }
      }
    }
  }
}

// response
{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "stations" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "강남",
          "doc_count" : 5,
          "avg_psg_per_st" : {
            "value" : 5931.2
          }
        },
        {
          "key" : "불광",
          "doc_count" : 1,
          "avg_psg_per_st" : {
            "value" : 971.0
          }
        },
        {
          "key" : "신촌",
          "doc_count" : 1,
          "avg_psg_per_st" : {
            "value" : 3912.0
          }
        },
        {
          "key" : "양재",
          "doc_count" : 1,
          "avg_psg_per_st" : {
            "value" : 4121.0
          }
        },
        {
          "key" : "종각",
          "doc_count" : 1,
          "avg_psg_per_st" : {
            "value" : 2314.0
          }
        },
        {
          "key" : "홍제",
          "doc_count" : 1,
          "avg_psg_per_st" : {
            "value" : 1021.0
          }
        }
      ]
    }
  }
}

주의 ❗

  • 하위 버킷이 깊어질수록 ES 가 하는 작업량과 메모리 소모량이 기하급수적으로 늘어남
  • → 예상치 못한 오류를 발생
  • 보통은 2레벨의 깊이 이상의 버킷은 생성하지 않는 것이 좋다.

참고자료

https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html

https://esbook.kimjmin.net/08-aggregations

https://velog.io/@soyeon207/ES-7.-aggregations-집계