본문 바로가기

언어/Java

ElasticSearch 스터디 정리 (6) - 데이터 집계 2

버킷 집계

예전에 최초 스터디 내용에서도 집계 관련된 내용이 나왔다. 그때는 간략히 버킷 집계에 대해서 아래와 같이 기록했었다.

  • 제일 많이 사용하는 집계
  • 어떤 text 나 keyword 또는 히스토그램에 대한 범위 집계 등

일단 버킷이라는 단어에 대해서 좀 알아보았다. 공식 문서에는 따로 언급이 없고 주로 AWS 에서 사용을 많이 하는 단어 인듯하다. 다만 통상적으로 아래와 같이 사용한다.

A bucket is most commonly a type of data buffer or a type of document in which data is divided into regions. - wikipedia

내가 이해한바로는 어떤 메모리 그룹을 뜻한다고 생각한다. 버킷 집계는 예를 들어서 날짜 범위 집계 경우 범위에 해당하는 결과를 버킷으로 만들어서 메모리에 올려둔다. (그럼 메트릭 집계는 어떻게 다르냐고? 내 생각에는 평균을 구하는 경우 그 값을 다 더해서 나눈값만 계산하는 것이다. 그래서 중첩 계산을 진행하면 해당 루프를 다시 돌아야 할것인데, 버킷 집계는 하위에서 다시 집계를 한다면 새로 다시 루프를 돌 필요 없이 해당 버킷에서만 루프를 돌면서 집계를 할 수 있다. 물론 이렇게 계속 중첩 집계를 하면 계속 값들이 메모리에 올라오기 떄문에 문제가 생길 수도 있다.)

아래는 간단히 대표적인 버킷 집계를 보면서 같이 이해해보자.

범위 집계

범위 집계는 사용자가 지정한 범위 내에서 집계를 수행한다.

아래는 apache-web-log 인덱스에서 bytes 라는 필드의 크기가 1000 ~ 2000 사이를 집계 해보자.

## GET /apache-web-log/_search?size=0
{
  "aggs": {
      "bytes_range": {
        "range": {
          "field": "bytes",
          "ranges": [
            {
              "from": 1000,
              "to": 2000
            }
          ]
        }
    }
  }
}

아래와 같은 응답을 받을수 있다.

{
  "aggregations": {
    "bytes_range": {
      "buckets": [
        {
          "key": "1000.0-2000.0",
          "from": 1000,
          "to": 2000,
          "doc_count": 754
        }
      ]
    }
  }
}

우리는 또 ranges를 여러개 범위로 지정한다면 각 범위 별로 결과를 얻을수 있다.

## GET /apache-web-log/_search?size=0
{
  "aggs": {
      "bytes_range": {
        "range": {
          "field": "bytes",
          "ranges": [
            {
              "to": 1000
            },
            {
              "from": 1000,
              "to": 2000
            },
            {
              "from": 2000,
              "to": 3000
            },
          ]
        }
    }
  }
}

아래와 같은 응답을 받을수 있다.

{
  "aggregations": {
    "bytes_range": {
      "buckets": [
        {
          "key": "*-1000.0",
          "to": 1000,
          "doc_count": 666
        },
        {
          "key": "1000.0-2000.0",
          "from": 1000,
          "to": 2000,
          "doc_count": 754
        },
        {
          "key": "2000.0-3000.0",
          "from": 2000,
          "to": 3000,
          "doc_count": 81
        }
      ]
    }
  }
}

물론 명시적으로 key 설정 할수도 있다.

## GET /apache-web-log/_search?size=0
{
  "aggs": {
      "bytes_range": {
        "range": {
          "field": "bytes",
          "ranges": [
            {
              "key": "hey",
              "from": 1000,
              "to": 2000
            }
          ]
        }
    }
  }
}

아래와 같은 응답을 받을수 있다.

{
  "aggregations": {
    "bytes_range": {
      "buckets": [
        {
          "key": "hey",
          "from": 1000,
          "to": 2000,
          "doc_count": 754
        }
      ]
    }
  }
}

추가적으로 아래와 같이 간단한 스크립트를 이용 할 수도 있다. 자세한 내용은 공식 문서 참고. (그냥 보면 대충 알게 됨) https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-range-aggregation.html

## GET /sales/_search
{
    "aggs" : {
        "price_ranges" : {
            "range" : {
                "field" : "price",
                "script" : {
                    "source": "_value * params.conversion_rate",
                    "params" : {
                        "conversion_rate" : 0.8
                    }
                },
                "ranges" : [
                    { "to" : 35 },
                    { "from" : 35, "to" : 70 },
                    { "from" : 70 }
                ]
            }
        }
    }
}

날짜 범위 집계

날짜를 범위로 지정해서 집계를 수행 할 수 있다.

아래와 같이 날짜 계산으로 값을 넣어도 된다. (특정 날짜도 물론 가능하다.)

expression meaning
y Years
M Months
w Weeks
d Days
h Hours
H Hours
m Minutes
s Seconds

현재 시간을 2020-01-16 09:32:12 라고 했을때

  • now+1h : 2020-01-16 10:32:12
  • now-1h/d : 2020-01-16 00:00:00 (d 이하 생략하라는 말)
    • now-1h/M : 2020-01-01 00:00:00
## POST /sales/_search?size=0
{
    "aggs": {
        "range": {
            "date_range": {
                "field": "date",
                "format": "MM-yyyy",
                "ranges": [
                    { "to": "now-10M/M" }, 
                    { "from": "now-10M/M" } 
                ]
            }
        }
    }
}

그리고 또 하나 missing 이라는 설정값이 있는데, 이를 활용하면 집계에 페이크를 줄수 있다. 아래와 같은 요청시 1976/11/30 값이 빠져있으니 같이 집계해라는 의미가 된다. 즉 Older 이라는 key를 가진 집계는 +1된 값을 추가 할 것이다. 테스트를 하지 못했고, 문서에서 정확히 확인하진 못했지만, 아마 여러개 빠진 값을 추가 할수 있다고 본다. 그리고 이 설정은 여러 집계에서 사용 가능 할수 있으니 꼼꼼히 확인해보자.

## POST /sales/_search?size=0
{
   "aggs": {
       "range": {
           "date_range": {
               "field": "date",
               "missing": "1976/11/30",
               "ranges": [
                  {
                    "key": "Older",
                    "to": "2016/02/01"
                  }, 
                  {
                    "key": "Newer",
                    "from": "2016/02/01",
                    "to" : "now/d"
                  }
              ]
          }
      }
   }
}

히스토그램 집계

히스토그램 집계는 숫자 범위를 처리하기 위한 집계이다.

우리는 일정한 범위에 대해서 집계를 하고 싶을수 있다. 예를 들면 범위 집계에 나온 *-1000, 1000-2000, 2000-30000 처럼 말이다. 하지만 더 뒤의 범위를 알기 위해서 계속 range 필드를 확장해야 할까? 물론 아니다. 간단히 히스토그램 집계를 이용하면 된다. 아래 처럼 간격(interval)을 설정함으로 모든 저 간격에 포함된 집계 결과를 얻을수 있다.

## POST /sales/_search?size=0
{
    "aggs" : {
        "prices" : {
            "histogram" : {
                "field" : "price",
                "interval" : 1000
            }
        }
    }
}

그리고 또는 무의미한 집계를 피하기 위해서 최소 몇개 이상 결과를 가진 결과만 보려면 아래와 같이 요청하면 된다.

## POST /sales/_search?size=0
{
    "aggs" : {
        "prices" : {
            "histogram" : {
                "field" : "price",
                "interval" : 50,
                "min_doc_count" : 1
            }
        }
    }
}

날짜 히스토그램 집계

위 히스토그램 집계와 비슷하다. 다만 간격을 분단위, 시간 단위등으로 볼수 있다. 우리 회사에 있는 로그앤크래쉬 시스템을 생각하면 쉽다. 그냥 간단히 예제만 보면 이해가 될듯 싶다. 시간인 만큼 타임존/오프셋 같은 설정도 가능하니 참고하자.

## GET my_index/_search?size=0
{
  "aggs": {
    "by_day": {
      "date_histogram": {
        "field":     "date",
        "calendar_interval":  "day",
        "time_zone": "-01:00",
        "offset":    "+6h"
      }
    }
  }
}

텀즈 집계

집계시 지정한 필드에 대해서 빈도가 높은 텀의 순위로 결과를 반환한다.

## GET /_search
{
    "aggs" : {
        "genres" : {
            "terms" : { "field" : "genre" } 
        }
    }
}

집계시, 최상위 결과만 보고 싶을수 있다. 상위 5개 결과를 보기 위해서 아래와 같이 요청 할수 있다. 다만 주의 할 점이 있다.

위와 같은 표가 있다면 우리는 A~J까지 모두 합을 하고 그 중에서 최상위 5개 결과가 반환 된다고 착각(?) 할수 있지만, 사실은 클러스터 이슈 때문에 각 샤드에서 최상위 5개 결과를 집계해서 그 중에서 최상위 5개 결과를 반환한다.

## GET /_search
{
    "aggs" : {
        "products" : {
            "terms" : {
                "field" : "product",
                "size" : 5
            }
        }
    }
}

그래서 일반적으로 더 정확한 결과를 얻고 싶다면 최대한 큰 사이즈로 요청을 하면 된다. 이는 메모리 사용량과 직접 연관이 있기 때문에 주의하자.
사용자 요청을 처리하는 노드는 각 샤드에 shard_size만큼 텀즈 집계 결과를 요구한다. 설정을 하지 않는다면 자동으로 계산 될것이다. 그래서 각 샤드로부터 받은 shard_size만큼 결과를 하나로 통일하고 최종 size 만큼 결과를 사용자에 반환한다. 책에 설술되길, 경험적으로 shard_size = size * 1.5 + 10 로 설정하면 된다. 더 자세한 내용은 공부 하면 좋을것 같아서 따로 포스팅 하겠다.

파이프라인 집계

파이프라인 집계는 특정 집계를 수행하는것이 아니라, 다른 집계로 생성된 버킷을 참조해서 집계를 수행한다고 생각하면 된다.

아래는 간단히 설명하기 위한 예제이다.

## POST /_search
{
    "aggs" : {
        "sales_per_month" : {
            "date_histogram" : {
                "field" : "date",
                "calendar_interval" : "month"
            },
            "aggs": {
                "sales": {
                    "sum": {
                        "field": "price"
                    }
                }
            }
        },
        "max_monthly_sales": {
            "max_bucket": {
                "buckets_path": "sales_per_month>sales" 
            }
        }
    }
}

sales_per_month 이라는 월단위 날짜 히스토그램 집계를 생성한다. 그리고 sales 라는 price 를 합계 버킷 집계를 생성한다. 그리고 마지막으로 max_monthly_sales 라는 파이프라인 집계를 생성하는데, 이는 최대값을 구하는 최대버킷집계인데, sales_per_month 안의 sales 집계 결과 중에서 최대 값을 구하는것이다.

그러면 대략 아래와 같은 결과가 나올것이다.

{
  "aggregations": {
    "sales_per_month": {
      "buckets": [
        {
          "key_as_string": "2015-05-17T10:05:00.000Z",
          "key": "1431857100000",
          "doc_count": 74,
          "sales": {
            "value": 5185322
          }
        },
        {
          ## ...
        }
      ]
    }
  },
  "max_monthly_sales": {
    "value": 5185322,
    "keys": [
      "2015-05-17T10:05:00.000Z"
    ]
  }
}

그리고 파이프라인 집계는 아래와 같이 구분된다.

  • 형제 집계: 동일 선상에서 수행되는 새 집계
    • 평균 버킷 집계
    • 최대 버킷 집계
    • 최소 버킷 집계
    • 합계 버킷 집계
    • 통계 버킷 집계
    • 확장 통계 버킷 집계
    • 백분위수 버킷 집계
    • 이동 평균 집계
  • 부모 집계: 집계를 통해 생성된 버킷을 사용해 계산을 수행하고, 그 결과를 기존 집계에 반영한다.
    • 파생 집계
    • 누적 집계
    • 버킷 스크립트 집계
    • 버킷 셀렉터 집계
    • 시계열 차분 집계

근삿값으로 제공되는 집계 연산