[엘라스틱서치] Elasticsearch in action 정리(5) - 집계 데이터

2020. 8. 3. 15:53 Big Data/빅데이터

엘라스틱서치의 집계는 검색과 일치하는 문서를 적재하고, 문자 필드의 단어를 세거나, 숫자 필드의 평균을 구하는 것과 같은 계산 업무를 해결할 수 있다. 집계가 동작하는 걸 보기 위해, 예시를 확인한다. 사용자는 태그를 선택할 수 있고 선택된 태그에 포함되는 문서를 필터할 수 있다. 이것은 사용자가 자신의 관심 그룹을 쉽게 찾을 수 있도록 한다.

 

엘라스틱서치에서 인기있는 태그 목록을 얻으려면, 집계를 사용하면 된다. 이 경우에 텀즈 집계를 태그 필드에 사용하여 필드에서 단어의 발생을 계산하고 가장 많이 반복되는 단어를 돌려보내면 된다. 

 

 

facet은 무엇인가?

루씬, 솔라 또는 엘라스틱서치를 써본 경험이 있다면, facets을 들어봤을 것이다. facets은 집계와 유사하다. 이것도 질의에 일치하는 문서를 적재하여 통계정보를 계산하여 돌려준다. Facets은 엘라스틱서치 1.x 버전에서는 지원되고, 2.0 버전에서는 제거될 것이다. Facets과 집계와의 다른 점은 facets에는 여러 타입을 사용할 수 없다는 점이다. 이건 데이터 검색을 일부 제한한다. 집계는 이러한 제한을 제거하고 문서에 대해 더 깊이 있는 분석을 제공한다. 예를 들어, 온라인 상점 로그가 엘라스틱서치에 저장되어 있다고 가정한다. 집계를 이용하여 가장 많이 팔린 상품뿐만 아니라, 각 국가에서 가장 많이 팔린 상품, 각 국가/상품별 추세와 같은 내용을 검색할 수 있다.

 

 

집계는 지표/버킷이라는 두가지 분류로 나눌 수 있다.

1. 지표 집계는 문서 그룹의 통계 분석을 나타낸다. 결과는 최솟값, 최댓값, 표준 편차 그 외 여러 지표로 표현된다.

2. 버킷 집합은 하나 또는 여러 개의 버킷에 일치하는 문서를 나눈 다음 각 버킷의 문서의 수를 돌려준다. 가장 인기 있는 태그를 찾을 때, 텀즈 집계는 문서의 태그에 대한 버킷을 만들고 각 버킷의 문서의 수를 제공한다.

 

 

버킷 집합 내에서 다른 집계를 중첩할 수 있다. 최상위 집계 때문에 생성된 각 문서의 버킷에 하위 집계를 실행할 수 있다. 텀즈 집계를 사용하여 가장 인기 있는 그룹 태그를 가지고 올 수 있고 또한 각 태그와 일치하는 그룹 구성원의 평균 수를 얻을 수 있다. 그리고 매년 생성된 그룹의 수, 태그 정보를 엘라스틱서치에 요청할 수 있다.

 

많은 유형의 집계를 조합하여 사용하는 다양한 방법이 있다. 데이터 분석에 더 나은 뷰를 가지기 위해, 지표/버킷을 살펴보는 것이 좋다. 그러고 나서 어떻게 이것들을 조합할지 고민해본다.

 

모든 집계는 유형과 관계없이 몇가지 규칙을 따른다.

  • 질의와 같은 JSON 요청에 집계를 정의하고, 키를 "aggregations" 또는 "aggs"로 표시한다. 각각 이름을 지정하고 특정 유형과 유형에 대한 옵션을 지정한다.
  • 집계는 질의 결과에 실행한다. global 집계를 사용하지 않는 한 질의에 일치하지 않는 문서는 처리되지 않는다.
  • 집계에 영향을 주지 않고 질의 결과를 필터할 수 있다. 이를 위해 포스트 필터를 어떻게 사용하는지 확인한다. 예를 들어, 온라인 상점에서 키워드로 검색할 때, 키워드에 일치하는 모든 아이템에 대한 통계를 만들 수 있지만, 포스트 필터는 오직 재고가 있는 경우에만 보여줄 수도 있다.

텀즈 집계를 사용하여 모든 집계가 따라야 하는 규칙에 대해 알아본다.

 

 

(1) 집계 요청의 구조

get-together 그룹에서 가장 빈번한 태그를 가지고 오는 텀즈 집계를 실행한다. 이 텀즈 집계의 구조는 다른 모든 집계에 적용된다.

curl 'localhost:9200/get-together/group/_search?pretty' -d '{
  "aggregations": { // aggregations 키는 요청의 집계 부분을 표시
    "top_tags": { // 집계의 이름을 지정
      "terms": {  // 집계의 타입을 terms로 지정
        "field": "tags.verbatim" // not_anlyzed 필드인 verbatim 필드는 "big", "data" 분리되어 사용하지 않고,
                                 // "big data" 하나의 텀으로 사용됨
      }
    }
  }
}'

...
  "hits": { // _search에 질의가 없더라도, 결과는 항상 표시된.
    "total": 5,
    "max_score": 1.0,
    "hits": [{
      ...
      "name": "Denver Clojure",
      ...
      "name": "Elasticsearch Denver",
      ...
    },
    "aggregations": [
      "top_tags": {
        "buckets": [{
          "key": "big data",
          "doc_count": 3
        }, {
          "key": "open source",
          "doc_count": 3
        }, {
          "key": "denver",
          "doc_count": 2
        } ...]
      }
    ]
  • 최상위 aggregation 키는 aggs로 줄여서 사용할 수 있다.
  • 다음 단계에서 집계의 이름을 지정해야 한다. 지정된 이름은 응답에서 볼 수 있다. 이건 여러 집계를 한번의 요청으로 할때 유용하다. 그래서 쉽게 각 결괏값의 의미를 볼 수있다.
  • 집계 유형과 특정 옵션을 지정해야 한다.

일반적인 검색을 할 때, 역색인의 특성 때문에 빠르게 실행된다. 찾고자 하는 단어의 개수를 제한하면, 엘라스틱서치는 해당 단어를 포함하는 문서를 찾아 돌려준다. 반면에 집계는 각 문서에 일치하는 질의 단어와 같이 동작해야 한다. 이건 문서 ID와 단어 간의 매핑이 필요하다. 역색인과 반대로 어떤 문서에 용어를 매핑한다.

 

엘라스틱서치는 역색인을 필드 데이터로 역치환한다. 더 많은 단어 필드 데이터를 사용하는 경우 더 많은 메모리가 필요한다. 이것이 엘라스틱서치에 크고 넉넉한 힙을 제공해야 하는 이유다.. 특히 많은 문서에 대해 집계를 하거나 문서에 하나 이상의 단어를 분석하는 경우에 그렇다. not_analyzed 필드의 경우 doc values를 사용해서 색인 시점에 역 치환한 데이터 구조를 생성하여 디스크에 저장할 수도 있다.

 

 

(2) 질의 결과에 실행하는 집계

전체 데이터 집합에 대해 지표를 계산하는 것은 집계를 사용할 수 있는 사용 사례 중 하나이다. 다음은 집계의 기본 동작이다. 암묵적으로 match_all 질의가 실행되었던 위의 예제와는 달리, 다음은 location 필드에 Denver를 검색하는 질의를 하고, Denver 정보만 가지고 집계를 한다.

 

curl 'localhost:9200/get-together/group/_search?pretty' -d '{
  "query": {
    "match": {
      "location": "Denver" // 이 질의는 그룹 중 Denver에 속한 데이터만 포함시키도록 한다.
    }
  },
  "aggregations": {
    "top_tags": {
      "terms": {
        "field": "tags.verbatim"
      }
    }
  }
}'
  "hits": {
    "total": 2,  // Denver만 찾고 있음
    "max_score": 1.44856,
    "hits" : [{
  ...
  "name": "Denver Clojure",
  ...
  "name": "Elasticsearch Denver",
  ...
  },
  "aggregations": {
    "top_tags": {
      "buckets": [ {
        "key": "denver",
        "doc_count": 2
      }, {
        "key": "big data",
        "doc_count": 1
  ...

 

질의에 from과 size 파라미터를 이용하여 질의 결과를 페이지별로 가지고 올 수 있다. 이 파라미터들은 집계에 영향을 미치지 않는다. 왜냐하면, 집계는 항상 질의에 일치하는 문서에 대해서만 동작하기 때문이다. 만약 집계의 제한 없이 질의 결과를 제한하고 싶으면, post filter를 사용하면 된다. 

 

 

(3) 필터와 집계

필터는 점수를 계산하지 않고 캐시가 가능하며, 필터와 유사한 질의보다 빠르다. 다음과 같은 filter를 감싼 filtered 질의도 가능하다.

curl 'localhost:9200/get-together/group/_search?pretty' -d '{
  "query": {
    "filtered": {
      "filter": {
        "term": {
          "location": "denver"
        }
      }
    }
  }
}'

이 방법으로 필터를 사용하면 전체 질의 성능에 좋다. 필터는 처음에 실행되기 때문이다. 그리고 질의는 오직 필터에 일치되는 문서에서만 동작하게 된다. 집계는 질의에 의해 걸러진 결과에 대하여 동작한다.

 

 

 

"filtered 질의 동작은 다른 질의들이 집계가 왔을대 하는 동작과 유사하다" 그러나 필터를 동작시키는 다른 방법이 있다. 집계와 독립적으로 질의 이후에 실행되는 post filter를 사용하은 방법이다. 다음 질의요청은 이전의 filtered 질의와 같은 결과를 반환한다.

curl 'localhost:9200/get-together/group/_search?pretty' -d '{
  "post_filter": {
    "term": {
      "location": "denver"
    }
  }
}'



post filter는 두가지가 filtered 질의와 다르다.

  • 성능 - post filter는 질의 이후에 실행된다. 질의는 모든 문서에 대해 실행된다는 것을 기억해야 한다. 그리고 필터는 오직 질의에 일치하는 문서에서만 동작한다. 전체 요청을 하는 건 filtered 질의와 같이 먼저 결과를 필터하는 것보다 일반적으로 느리다.
  • 집계에 의해 처리되는 문서 집합 - 문서가 post filter에 일치하지 않더라도, 집계에 의해 처리된다.

 

질의와 필터, 집계의 연관성뿐만 아니라 집계 요청의 전체 구조에 대해 이해해보자. 집계에 대해 더 깊이 보고 다른 집계 유형을 알아보고, 지표 집계와 버킷 지표에 대해 시작하여 어떻게 조합하여 실시간으로 데이터에 대한 깊은 이해를 해본다.

 

지표 집계

지표(Metrics) 집계는 문서의 집합에서 통계 정보를 추출하거나 다른 집계에서 나온 문서의 버킷을 분석할 수 있다. 이런 통계들은 일반적으로 숫자 필드에 수행된다. 최소나 평균값과 같은 것들이다. 통계를 얻거나 stats 집계를 통해 한번에 같이 받을 수도 있다. 표준편차나 합의 제곱과 같은 고급 통계는 extended_stats 집계를 통해 가능한다.

 

숫자 필드와 비숫자 필드로부터 cadinality 집계를 이용하여 유일한 값에 대한 통계 정보를 가질 수 있다.

 

 

(1) 통계

지표 집계를 이용하여 각 이벤트 참가자의 수에 대한 통계를 얻을 수 있다. 이벤트 문서는 참가자 목록을 가지고 있다. 질의 시점에 스크립트를 통해 참가자의 숫자를 계산할 수도 있다. 일반적으로 엘라스틱서치 질의에 각 문서의 값을 반환하는 작은 스크립트를 사용할 수 있다. 이 경우에 이 값은 참가자 배열의 요소들의 수가 된다.

 

다음 목록에서 모든 이벤트의 참석자 수에 대한 통계 정보를 요청할 수 있다. 스크립트에서 참가자의 수를 얻은 다음, doc['attendees'].values를 이용하여 참석자의 배열을 받을 수 있다. 길이 속성을 추가하면 참석자의 수를 알 수 있다.

 

URI=localhost:9200/get-together/event/_search
curl "$URI?pretty&search_type=count" -d '{
  "aggregations": {
    "attendees_stats": {
      "stats": {
        // 참석자의 수를 계산하는 스크립트,
        // 실제 필드를 지정하여 필드 대신 스크립트를 사용
        "script": "doc['"'attendess'"'].values.length"
      }
    }
  }
}'
  "aggregations": {
    "attendees_stats": {
      "count": 15,
      "min": 3.0,
      "max": 5.0,
      "avg": 3.86666666667,
      "sum": 58.0
    }
  }
}

 

각 이벤트당 최소 참가자 수뿐만 아니라 최대, 합, 평균 등을 알 수 있다. 또한, 이런 통계는 이미 계산된 문서의 수를 가져온다.

 

만약 이 중에서 하나의 통계만 필요하다면 따로 요청하여 받을 수 있다. 예를 들어 각 이벤트의 평균 참석자를 계산하려면, 다음 목록에서 보듯이 avg 집계를 이용할 수 있다.

 

URI=localhost:9200/get-together/event/_search
curl "$URI?pretty&search_type=count" -d '{
  "aggregations": {
    "attendees_avg": {
      "avg": {
        "script": "doc['"'attendees'"'].values.length"
      }
    }
  }
}'
{
  "aggregations": {
    "attendees_avg": {
      "value": 3.8666666667
    }
  }
}

avg 집계와 유사하게 min, max, sum, value_count 등과 같은 다른 지표 집계를 사용할 수 있다. avg라는 집계 이름을 사용하고자 하는 지표 집계 이름으로 변경하면 된다. 통계를 분리하는 것의 장점은 엘라스틱서치가 필요하지 않은 지표를 계산하기 위하여 시간을 낭비하지 않는다는 것이다.

 

 

(2) 고급 통계

stats 집계를 이용하여 추가적인 통계 정보를 얻을 수 있다. 숫자 필드에 extended_stats 집계를 동작하여 제곱의 합, 분산, 표준 편차 등의 정보를 얻을 수 있다.

URI=localhost:9200/get-together/event/_search
curl "$URI?pretty&search_type=count" -d '{
  "aggregations": {
    "attendees_extended_stats": {
      "extended_stats": {
        "script": "doc['"'attendees'"'].values.length"
      }
    }
  }
}'

이런 모든 통계는 질의에 일치하는 모든 문서의 값을 찾아 계산하여, 매번 100% 정확하다. 다음은 빠르고 근사 알고리즘을 사용하는 통계에 대해 살펴본다. 정확성은 낮지만 적은 메모리를 사용하며 더 빠르다.

 

 

(3) 근사치 통계

일부 통계는 문서에서 값을 높은 정확성으로 찾아 계산한다. 이것은 실행 시간과 메모리 사용량을 모두 제한한다. 엘라스틱서치에서 percentiles와 cardinality 두가지 통계를 어떻게 가지고 오는지 살펴보겠다. percentiles는 주어진 x를 가지고 전체 값에서 x% 이하인 값을 찾는다.

 

예를 들어 온라인 상점을 운영하고 있다면, 각 장바구니에 있는 값을 기록하고, 사용자들이 어떤 가격 범위를 장바구니에 담는지 확인하기에 유용하다. 아마도 대부분의 사용자는 1~2개의 상품을 구매할 것이다. 그러나 상위 10% 사용자는 많은 상품을 구매하고 수익의 대부분을 창출한다.

 

Cardinality는 필드의 유일한 값의 개수이다. 이건 웹사이트에 접근한 유일한 IP 개수를 알고 싶은때 유용하다.

 

백분위 수

백분위 수(Percentiles)의 경우, 이벤트의 참가자 수를 다시 한번 생각해보고, 보통의 참석자 수와 높은 참석자의 최대 수를 결정한다. 80%와 99% 백분위 수로 계산할 수 있다. 이때, 80% 이상 99% 이하의 숫자를 고려한다. 상위 1%는 예외적으로 높은 수치이기때문에 무시할 수 있다.

 

이를 위해서는 백분위 집계를 사용하여야 한다. 그리고 이 특정 백분위 값을 가지고 오기 위해 80와 99를 배열에 설정해야 한다.

URI=localhost:9200/get-together/event/_search
curl "$URI?pretty&search_type=count" -d '{
  "aggregations": {
    "attendees_percentiles": {
      "percentiles": {
        "script": "doc['"'attendees'"'].values.length",
        "percents": [80, 99]
      }
    }
  }
}'
{
  "aggregations": {
    "attendees_percentiles": {
      "values": {
        "80.0": 4.0,
        "99.0": 5.0
      }
    }
  }
}

 

작은 데이터 집합에서는 100% 정확도의 데이터를 얻을 수 있다. 그러나 실제 큰 데이터 사이즈 환경에서는 그렇지 못할 수도 있다. 대부분의 데이터와 백분위 수를 사용할 경우 기본 설정으로 99.9% 이상의 데이터 정확도를 가질 수 있기는 하다. 하지만 50번째와 같은 백분위 수의 경우 정확도가 매우 안 좋을 수 있다. 정확도는 0 또는 100으로 향할수록 더 좋은 결과를 얻는다.

 

기본값이 100인 compression 파라미터의 값을 증가하여 메모리 사용량과 정확도를 조절할 수 있다. compression 값에 비례하여 메모리 소비량은 증가하고 백분위 수의 근사치를 구할때 얼마나 많은 값을 사용할지 조절할 수도 있다.

 

반대의 결과를 얻을 수 있는 precentile_ranks 집계도 있다. 즉, 값의 집합을 명시하고 명시한 값까지 일치하는 문서의 백분위를 얻는다.

curl "$URI?pretty&search_type=count" -d '{
  "aggregations": {
    "attendees_percentile_ranks": {
      "percentile_ranks": {
        "script": "doc['"'attendees'"'].values.length",
        "value": [4, 5]
      }
    }
  }
}'

 

카디널리티

카디널리티의 경우, 유일한 값을 찾을때 유용하다. 다음에서 보여주는 것과 같이 카디널리티 집계를 사용할 수 있다.

URI=localhost:9200/get-together/group/_search
curl "$URI?pretty&search_type=count" -d '{
  "aggregations": {
    "members_cardinality": {
      "cardinality": {
        "field": "members"
      }
    }
  }
}'
{
  "aggregations": {
    "members_cardinality": {
      "value": 8
    }
  }
}

백분위 집계와 같이 카디널리티 집계도 근사치 이다. 이러한 근사 알고리즘을 이해하기 위해 다은으로 사용될 수 있는 것들을 살펴본다. 버전 1.1.0에서 카디널리티가 소개되기 이전에는 필드의 카디널리티를 얻는 일반적인 방법은 텀즈 집계를 이용한 것이다. 단어 집계는 각 단어의 상위 N개의 값을 알 수 있고, 여기서 N은 설정 가능한 파라미터이기 때문이다. 만약 이 숫자가 충분히 크다면, 모든 유일한 단어를 가질 수 있다. 이것을 계산하여 카디널리티값을 구할 수 있다.

 

  • 메모리 - 유일한 단어를 계산하기 위해 메모리에 모든 단어가 적재되어야 한다.
  • CPU - 결과는 정렬되어 보여야 한다. 기본적으로 순서는 각 용어가 발생한 횟수로 정렬된다.
  • 네트워크 - 각 샤드에서 유일한 단어로 정렬된 큰 배열이 사용자의 요청을 받은 노드로 전송되어야 한다. 그리고 전달받은 노드는 사용자에게 값을 반환하기 위하여 전달받은 큰 배열을 하나의 배열로 합치는 작업을 진행해야 한다.

이것이 근사치 알고리즘이 역할을 할수 있는 부분이다. 카디널리티 필드는 HyperLogLog++라는 알고리즘으로 동작한다. 이건 검색을 원한느 필드로부터 해시값을 구하고, 해시값을 이용하여 근사값을 구하는 알고리즘이다. 메모리에 이런 해시값을 한번만 적재하기 때문에, 얼마나 많은 단어를 검사하는지에 상관없이 메모리 메모리 사용량이 일정하다.

 

 

메모리와 카디널리티

카디널리티 집계는 메모리 사용량이 일정하다고 했다. 하지만 얼마나 크게 설정 가능할까? precision_threshold 파라미터를 통해서 설정할 수 있다. threshold가 높으면, 더 정확한 결과를 얻을 수 있지만, 더 많은 메모리를 사용한다. 만약 직접 카디널리티 집계를 실행한다면, precision_threshold * 8byte 만큼의 메모리를 각 샤드에서 질의를 동작시키기 위해 사용된다.

 

카디널리티 집계도 다른 집계와 마찬가지로, bucket 집계에서 중첩할 수 있다. 그렇게 하면 메모리 사용량은 상위 집계에서 생성된 bucket의 갯수만큼 곱해진다.

 

집계 타입사용 예제
stats

여러 상점에서 같은 상품을 팔고, 가격에 대한 통계를 수집한다.
얼마나 많은 상점에서 상품을 파는지 그리고 최소, 최대 편균값이 얼마인지 구한다.

individual stats
(min, max, sum, avg, value_count)
여러 상점에서 같은 상품을 판매할 때 상품의 최소 가격을 표시
extended_stats문서는 성격 검사 결과를 포함. 분산과 표준편자를 이용하여 유사한 성격의 사람을 그룹 지어 통계를 수집
percentiles웹사이트에 접속한 시간: 웹사이트에 접근 시 발생하는 지연과 가장 늦은 응답
percentile_ranksSLA를 충족하는지 확인: 99%의 요청이 100ms 이내에 제공할 수 있어야 한다면, 실제 얼마나 충족하는지를 확인
cardinality서비스에 접근하는 유일한 IP 주소의 갯수

 

다중 버킷 집계

지표 집계는 모든 문서를 기반으로 생성한 하나 이상의 숫자로 설명할 수 있다. 다중 버킷 집계(Multi-bucket aggregations)는 각 태그에 일치하는 문서의 그룹을 버킷에 넣는다. 그리고 각 버킷에 있는 태그의 그룹 수를 집계한 하나 이상의 값을 가질 수 있다.

 



 

하나의 큰 버킷으로 이전 예제들을 생각할 수 있다. 다른 집계들도 이런 버킷을 생성한다. 예를 들어 색인 중인 로그가 국가 코드를 가지고 있다면, 텀즈 집계를 이용하여 국가별로 문서를 담을 버킷을 생성할 수 있다. 그리고, 집계를 중첩해서 사용할 수도 있다. 나라별 유일한 사용자의 숫자를 알기 위해 텀즈 집계로 생성된 커밋에 카디널리티 집계를 중첩하여 사용할 수도 있다.

 



  • 텀즈 집계(Terms aggregations)는 문서에서 각 단어의 빈도를 계산하려고 할때 사용한다. 텀즈 집계는 각 단어가 몇번 나타났는지를 돌려주는데, 브롤그에서 사용자들이 자주 접근하는 글을 찾거나 인기있는 태그를 찾을때 유용하다. 그리고 significant_terms 집계도 있다. 이것은 전체 문서에서 발생한 단어 빈도와 질의 결과에서 발생하는 차이점을 돌려주며, 검색 문맥에서 의미 있는 단어를 추천할때 유용하다. 예를 들어, "search engine"에 관련된 문서에서 "elasticsearch"를 추천하는 경우를 들 수 있다.
  • 범위 집계는 버킷을 생성한 후 어떤 숫자, 날짜 또는 IP 주소 범위를 버킷에 나누는 방법이다. 이 방법은 사용자가 예상하는 값에 대해 분석을 하기에 유용하다. 예를 들어, 온라인 상점에서 랩톱에 대하여 검색한다고 할때, 가장 인기 있는 랩톱의 가격 범위를 알 수 있게 한다.
  • 히스토그램 집계는 숫자형이나 날짜형 둘 중 하나를 사용하며 범위 집계와 유사하다. 그러나 각 범위를 지정하는 대신에 각 간격에 대하여 정의를 한다. 엘라스틱서치는 간격에 맞춰 버킷을 생성한다. 이건 사용자가 어떤 값을 볼지 모르는 경우에 유용하다. 예를 들어, 매달 얼마나 많은 이벤트가 발생하는지 도표로 보여줄 수 있다.
  • 중첩, 역 중첩, 그리고 자식 집계는 문서의 관계에 걸쳐서 집계를 실행한다.
  • geo instance, geohash grid 집계는 지리정보를 기반으로 버킷을 생성한다.

 

(1) 텀즈 집계

일반적인 사용 예제는 문서에서 자주 발생하는 상위 X개의 정보를 가지고 올때 사용한다. 여기서 X는 사용자의 이름, 태그, 카테고리와 같은 문서의 필드가 된다. 왜냐하면, 텀즈 집계는 각 필드의 값이 아닌 각 단어에 대해 계산한다. 그래서 분석되지 않은 필드에서도 돌릴 수 있다. "big data"의 경우에는 "big" 한 번, "data" 한번으로 계산된다.

 

텀즈 집계는 이벤트의 설명처럼 분석된 필드에서 가장 많이 사용된 단어를 뽑아낼때 사용할 수 있다. 이 정보를 통해 워드 클라우드를 생성할 수도 있다. 만약 많은 문서를 가지고 있거나 문서에 많은 단어를 포함하고 있다면 모든 필드가 메모리에 적재될 수 있도록 넉넉한 메모리가 있어야 한다는 점을 잊지말자.

 

상위 X개의 정보를 가지고 오는 사용 예제에서 정렬은 기본적으로 계산된 단어 개수의 내림차순 정렬이다. 그러나 오름차순으로 정렬하거나, 다른 조건 예를 들어 단어의 이름 같은 정보로도 정렬할 수 있다. 다음 목록에서 정렬 속성을 사용하여 어떻게 그룹 태그를 알파벳순으로 정렬하는지 확인할 수 있다.

 

URI=localhost:9200/get-together/group/_search
curl "$URI?pretty&search_type=count" -d '{
  "aggregations": {
    "tags": {
      "terms": {
        "field": "tags.verbatim",
        "order": {
          "_term": "asc" // 정렬 기준(버킷의 단어)와 정렬 방법(오름차순)
        }
      }
    }
  }
}'
{
  "aggregations": {
    "tags": {
      "buckets": [ {
        "key": "apache lucene",
        "doc_count": 1
      }, {
        "key": "big data",
        "doc_count": 3
      }, {
        "key": "clojure",
        "doc_count": 1
      ...

 

만약 텀즈 집계 밑에 지표 집계를 중첩하는 경우, 지표에 의해 단어를 정렬할 수 있다. 예를 들어 태그당 평균 그룹 멤버의 평균을 구하기 위해 텀즈 집계 밑에 평균 지표 집계를 사용할 수 있다. 그리고 avg_members: desc(_term: asc 대신에)와 같은 지표 집계 이름을 참조하여 구성원의 수에 따라 태그를 정렬할 수 있다.

 

 

응답에 포함할 단어

기본적으로 텀즈 집계는 선택한 상위 10개의 단어만 돌려준다. 그러나 size 파라미터를 통해서 조절할 수 있다. 0으로 설정하면 모든 단어를 다 돌려준다. 하지만 높은 카디널리티를 가진 필드에서 사용할 경우 위험하다. 왜냐하면, 매우 큰 결과를 반환하기 때문에 정렬하기 위해 CPU를 많이 사용하고 네트워크를 포화시킬 수 있다.

 

상위 10개(또는 설정한 크기의 단어)를 가져오기 위해 엘라스틱서치는 각 샤드에서 단어의 개수를 가지고 와서(shard_size를 통해 설정된) 결과를 집계해야 한다. 다음은 shard_size가 2로 설정된 경우 동작하는 것을 보여준다.

 

이 메커니즘은 개별 샤드에서 상위 정보를 가지고 올때 몇가지 단어에 대해 부정확한 개수를 얻을 수 있음을 함축하고 있다. lucene의 전체 값은 7이지만, 각 샤드의 상위 2개의 값에 들지 못해 전체 태그의 상위 2개의 값에 들지 못해 잘못된 값을 되돌려 줄수도 있다.

 

전체 상위 X개의 값을 가져오는 경우 값이 정확하지 않다. 왜냐하면, 각 샤드별로 상위 X개의 값을 반환하기 때문이다.

 

결과의 정확도를 올리기 위해 더 큰 크기의 shard_size를 설정할 수도 있다. 그러나 이 작업은 집계하기 위해 더많은 자원을 사용한다. 왜냐하면 메모리에 더 많은 버킷을 유지해야 하기 때문이다.

 

정확한 결과를 가져오기 위한 아이디어로, 집계 결과의 시작 부부분에서 값을 확인할 수 있다.

"tags": {
  "doc_count_error_upper_bound": 0,
  "sum_other_doc_count": 6,

첫번째 숫자는 최악의 시나리오 오차범위다. 예를 들어, 샤드에서 반환하는 텀 발생 수의 최솟값이 5라고 한다면, 같은 샤드에서 4번 발생한 텀은 반환되지 않을 수 있다. 이 단어가 최종 결과로 나와야 한다면, 최악의 에러(worst-case error)는 4다. 모든 샤드에 대한 숫자의 합계가 doc_count_error_upp_bound를 구성한다. 우리의 코드 예제를 보면, 그 값은 언제나 0이다. 우리는 오직 1개의 샤드만 가지고 있기 때문이다. 즉, 샤드의 상위 단어의 갯수와 전체 샤드의 상위 단어가 같다.

 

두번째 숫자는 상위 갯수에 포함되지 않는 텀의 전체 발생 숫자를 나타낸다. show_term_doc_count_error를 true로 설정하여 단어별 doc_count_error_upper_bound 값을 얻을 수 있다. 단어마다 최악의 시나리오를 취한다. 예를 들어 샤드에서 "big data"를 돌려준다면, 그것이 정확한 값이라는 것을 알 수 있다. 하지만 다른 샤드가 "big data"를 전혀 반환하지 않는다면, 최악의 시나리오는 "big data"가 마지막 값을 반환한 텀 바로 밑에 있는 경우이다. 텀을 반환하지 않는 샤드들에 대해서 이 에러 숫자를 합산하면 텀별 doc_count_error_upp_bound를 구성한다.

 

정확도 영역의 다른 끝에서, 낮은 빈도를 갖는 텀들을 관련 없다고 간주하고 결과에서 완전히 제외할 수 있다. 텀을 빈도가 아닌 다른 방법으로 정렬하려고 할 때 유용하다. 낮은 빈도의 텀들이 나타나지만, 오타 값은 무관한 결과로 인해 결과를 오염시키길 원하지 않는다. 그렇게 하려면 기본값이 1로 설정되어 있는 min_doc_count 값을 변경해야 한다. 샤드에서 낮은 빈도의 단어를 잘라버리고 싶다면, shard_min_doc_count를 사용하면 된다.

 

마지막으로 결과에서 특정 단어를 포함하거나 제거할 수 있다. 그렇기 하기 위해서는 include와 exclude 옵션의 값에 정규표현식 값을 넣어 사용할 수 있다. include 옵션을 단독으로 사용하면 오직 패턴이 일치하는 단어들만 포함한다. exclude 옵션을 단독으로 사용하면 패턴에 일치하는 단어들을 제외한다. 두 개를 같이 사용한다면 exclude가 먼저 실행되고, exclude 옵션으로 제거된 값들에 include 옵션을 적용한다.

 

URI=localhost:9200/get-together/group/_search
curl "$URI?pretty&search_type=count" -d '{
  "aggregations": {
    "tags": {
      "terms": {
        "field": "tags.verbatim",
        "include": ".*search.*"
      }
    }
  }
}'
{
  "aggregations": {
    "tags": {
      "buckets": [
        {
          "key": "elasticsearch",
          "doc_count": 2
        },
        {
          "key": "enterprise search",
          "doc_count": 1
        }
      ]
    }
  }
}

 

기본적으로 엘라스틱서치는 단일 패스로 모든 집계를 수행한다. 예를 들어 텀즈 집계와 카디널리티 집계를 중첩해서 사용한다면, 엘라스틱 서치는 각 단어의 버킷을 만들고, 각 버킷의 카디널리티를 계산하고, 버킷을 정렬하여 상위 X개의 값을 반환한다.

 

대부분의 경우에는 잘 동작하지만, 버킷이 많거나 하위 집계가 많으면 시간과 메모리 또한 많이 사용한다. 특히 하위 집계도 버킷이 많은 다중-버킷 집계인 경우에 그렇다. 이런 경우에는 2단계의 접근방법이 더 좋다. 처음으로 상위 집계의 버킷을 생성하고, 상위 X개의 값을 정렬하고 메모리에 적재한다. 그리고 하위 집계의 경우 이 상위 X개에 대해서만 계산을 한다.

 

엘라스틱서치에서 collect_mode를 설정함으로써 위 접근 방법을 사용할 수 있다. 기본값은 depth_first이고, 2단계 접근법은 breadth_first다.

 

 

중요한 단어

significant_terms 집계는 현재 검색 결과에서 평균보다 더 자주 나타나는 단어를 보려고 하면 유용하다. get-together 모든 그룹에서 clojure 단어는 자주 반복되지 않는다. 백만 건 중 10번(0.001 %)만 나타난다고 추측해보자. Denver로 검색한 결과에서, clojure가 만 건에서 7(0.07%)번 나타난다고 가정한다면, 백분율은 전체 그룹 결과보다 높고, Denver에서 다른 지역과 비교해서 더 강한 clojure 커뮤니티를 나타낸다. programming이나 devops와 같은 단어들이 더 자주 나타나는 것은 중요하지 않다.

 

significant_terms 집계는 단어를 계산하는 것이 텀즈 집계와 유사하다. 하지만 결과 버킷을 질의에 일치하는 문서인 포그라운드 문서와 색인에 있는 모든 문서의 백그라운드 문서의 백분율 차이로 매긴 점수를 기준으로 정렬한다.

 

다음 예제에서 get-together 사이트에서 이벤트를 위하여 Lee 사용자와 유사한 성향의 사용자를 찾을 것이다. 그러기 위해서는 Lee가 참석한 이벤트에 대해 질의하고, significant_terms 집계를 이용하여 같이 참석한 참석자들을 찾아서 그들이 참석한 이벤트를 전체 이벤트에서 비교한다.

 

URI=localhost:9200/get-together/event/_search
curl "$URI?pretty&search_type=count" -d '{
  "query": {
    "match": {
      "attendees": "lee"      // Lee가 참석한 이벤트에 대한 검색
    }
  },
  "aggregations": {
    "significant_attendees": {
      "significant_terms": {
        "field": "attendees", // Lee가 참석한 이벤트에 평균보다 더 참석한 사용자를 찾음
        "min_doc_count": 2,  // 적어도 2번 이상 같은 이벤트에 참석한 참가자를 가지고 옴
        "exclude": "lee"  // 비교 대상에서 Lee는 제외함, Lee는 동일한 이벤트 참석 경향을 가짐.
      }
    }
  }
}'
{
  "aggregations": {
    "significant_attendees": {
      "doc_count": 5, // Lee가 참석한 이벤트는 총 5개
      "buckets": [
        {
          "key": "greg",
          "doc_count": 3,
          "score": ...,
          "bg_count": 3
        },
        {
          "key": "mide",
          "doc_count": 2,
          "score": ...,
          "bg_count": 2
        },
        {
          "key": "daniel",
          "doc_count": 2,
          "score": ...,
          "bg_count": 3
        }
      ]
    }
  }
}

significant_terms 집계는 돌아올 값을 조절하기 위하여 텀즈 집계와 같은 size, shard_size, min_doc_count, shard_min_doc_count, include, exclude 옵션을 가진다. 그 외에도 background_filter 파라미터를 이용하여 정의된 필터와 일치하는 항목만 백그라운드 문서로 변경할 수 있다. 예를 들어 Lee가 오직 기술 이벤트에만 참석했다는 것을 알 수도 있고 그가 관심이 없을 것 같은 이벤트들을 필터하는 것을 고려해볼 수 있다.

 

텀즈와 significant_terms 집계 모두 문자 필드에서 동작을 잘한다. 숫자 필드의 경우 범위와 막대 그래프 집계가 더 적절하다.

 

 

(2) 범위 집계

terms 집계는 대부분 문자열과 함께 사용되지만, 숫자 형 값에도 동작한다. 이건 노트북의 보증이 2년 또는 3년 아니면 그 이상인지와 같은 작은 카디널리티 정보를 알고 싶을때 유용한다.

 

나이나 가격과 같이 높은 카디널리티 필드에서는 아마도 범위 값을 찾을 것이다. 사용자의 나이가 18~39세 사이인지, 40~60세 사이인지 아니면 범위인지 알고 싶을 수 있다. 텀즈 집계를 이용해서도 이런 작업을 할 수 있다. 하지만 지루한 추가 작업이 필요하다. 첫번째 버킷을 얻기 위해 프로그램에서 18세, 19세부터 39세까지 나이를 계산하도록 추가해야 한다.

 

수치에 대해 이런 문제를 해결하기 위한 범위 집계가 있다. 이름에서 추측할 수 있듯이, 원하는 수치 범위를 제공하고 각 버킷에 해당 값을 사용하여 문서를 계산하여 나눈다. 데이터 시각화를 위해 이러한 집계를 사용할 수 있다.

 

엘라스틱서치는 날짜 문자열을 저장할 때 밀리초 단위인 유닉스 시간을 long 타입으로 저장하는 것을 알 수 있다. 날짜 범위로 작업할 때, 범위 집계의 다른 형식인 date_range 집계를 사용하면 된다.

 

 

범위 집계에는 범위 목록을 전달하여 해당 정보를 얻을 수 있다. 범위의 최솟값은 버킷에 포함되지만, 최댓값은 버킷에 포함되지 않는다. 범위는 꼭 인접할 필요는 없고, 완전히 분리되어 있거나 겹쳐질 수 있다. 대부분의 경우 모든 값을 포함해야 맞지만, 이것이 필수는 아니다.

  • 4명 미만의 참석자가 참석한 이벤트
  • 4명 이상 6명 미만의 참석자가 참석한 이벤트
  • 6명 이상의 참석자가 참석한 이벤트
URI=localhost:9200/get-together/event/_search
curl "$URI?pretty&search_type=count" -d '{
  "aggregations": {
    "attendees_breakdown": {
      "range": {
        "script": "doc['"'attendees'"'].values.length",
        "ranges": [
          { "to": 4 },
          { "from": 4, "to": 6 },
          { "from": 6 }
        ]
      }
    }
  }
}'
{
  "aggregations": {
    "attendees_breakdown": {
      "buckets": [ {
        "key": "*-4.0",
        "to": "4.0",
        "to_as_string": "4.0",
        "doc_count": 4 // 각 범위에 존재하는 문서의 숫자
      }, {
        "key": "4.0-6.0",
        "from": 4.0,
        "from_as_string": "4.0",
        "to": 6.0,
        "to_as_string": "6.0",
        "doc_count": 11
      }, {
        "key": "6.0-*",
        "from": 6.0,
        "from_as_string": "6.0",
        "doc_count": 0 // 문서가 존재하지 않더라도 0으로 표시
      } ]
    }
  }
}

 

 

날짜 범위 집계

범위에 날짜 문자열을 넣는 것을 제외하고 날짜 범위 집계는 범위 집계처럼 동작한다. 그러므로 날짜 형식을 정의해야 한다. 그래야만 엘라스틱서치가 문자열을 저장된 것과 같은 숫자 형식의 유닉스 시간 정보로 변환할 수 있다.

 

URI=localhost:9200/get-together/event/_search
curl "$URI?pretty&search_type=count" -d '{
  "aggregations": {
    "dates_breakdown": {
      "date_range": {
        "field": "date",
        "format": "YYYY.MM", // 날짜 형식 정의
        "ranges": [
          { "to": "2013.07" }.
          { "from": "2013.07" }
        ]
      }
    }
  }
}'
{
  "aggregations": {
    "dates_breakdown": {
      "buckets": [{
        "key": "*-2013.07",
        "to": 1.3726368E12,
        "to_as_string": "2013.07",
        "doc_count": 8
      }, {
        "key": "2013.07-*",
        "from": 1.3726368E12,
        "from_as_string": "2013.07",
        "doc_count": 7
      }]
    }
  }
}

 

 

(3) 히스토그램 집계

숫자 범위를 처리하기 위해 히스토그램 집계도 있다. 범위 집계와 유사하지만 각 범위를 수동으로 지정하는 대신에 고정된 간격을 정의하면, 엘라스틱서치가 간격을 만들어준다. 예를 들어, people 문서에서 연령별 그룹정보를 얻고 싶을때 간격을 10년으로 정의한다면, 0-10(10 제외) / 10-20(20 제외) 등의 버킷을 얻을 수 있다.

 

범위 집계와 마찬가지로, 날짜 형식에서 동작하는 날짜 히스토그램 집계가 있다. 이건 매일 메일링 리스트가 전송한 이메일의 갯수를 막대 그래프 차트로 만들때 유용하다.

 

히스토그램 집계를 실행하면 범위 집계를 실행하는 것과 비슷하다. 엘라스틱서치는 최솟값부터 최댓값을 포함할 때까지 간격을 추가해서 범위를 만든다. 예를 들어, 간격을 1로 지정한다면 3명이 참석한 이벤트, 4명, 5명이 참석한 이벤트를 볼 수 있다.

 

URI=localhost:9200/get-together/event/_search
curl "$URI?pretty&search_type=count" -d '{
  "aggregations": {
    "attendees_histogram": {
      "histogram": {
        "script": "doc['"'attendees'"'].values.length",
        "interval": 1  // 범위를 만들 간격 설정. 여기서는 모든 값으로 설정
      }
    }
  }
}'
{
  "aggregations": {
    "attendees_histogram": {
      "buckets": [ {
        /* key는 범위 값을 나타냄. 다음 키값은 key + interval 값 */
        "key": 3,
        "doc_count": 4
      }, {
        /* 다음 "from" 값은 이전 값의 "to" 값 */
        "key": 4,
        "doc_count": 9
      }, {
        "key": 5,
        "doc_count": 2
      } ]
    }
  }
}

 

텀즈 집계와 마찬가지로 히스토그램 집계로 min_doc_count를 특정 값으로 지정할 수 있다. 이 값은 적은 문서를 가지고 있는 버킷을 무시할 때 유용하다. min_doc_count는 비어 있는 버킷을 볼때도 유용하다. 기본적으로 최솟값과 최댓값 사이에 문서가 없는 간격이 있다면, 해당 간격은 모두 생략하게 된다. min_doc_count 0으로 설정하면 문서가 없어도 해당 간격은 나타나도록 설정된다.

 

 

날짜 히스토그램 집계

히스토그램 집계와 같이 날짜 히스토그램 집계를 사용할 수 있다. 간격 필드에는 날짜를 입력해야 한다. 날짜값은 date_range 집계와 같이 1M 또는 1.5h 같은 Joda Time annotation으로 지정한다. 다음과 같이 매달 일어난 이벤트에 대해 분석할 수도 있다.

URI=localhost:9200/get-together/event/_search
curl "$URI?pretty&search_type=count" -d '{
  "aggreations": {
    "event_dates": {
      "date_histogram": {
        "field": "date",
        "interval": "1M"
      }
    }
  }
}'
{
  "aggregations": {
    "event_dates": {
      "buckets": [ {
        "key_as_string" : "2013-02-01T00:00",
        "key" : 1359676800000,
        "doc_count" : 1
      }, {
        "key_as_string" : "2013-03-01T00:00",
        "key" : 1362096000000,
        "doc_count" : 1
      }, {
        "key_as_string" : "2013-04-01T00:00",
        "key" : 1364774400000,
        "doc_count" : 2
      }]
    }
  }
}

 

일반적인 히스토그램 집계와 같이 min_doc_count를 사용하여 비어있는 버킷을 보여주거나 적은 문서를 가지고 있는 버킷을 생략할 수 있다. 날짜 히스토그램 집계도 다른 모든 다중 버킷 집계와 두가지 공통점이 있다.

  • 문서가 특정 단어를 가지고 있는 경우를 계산한다.
  • 버킷을 생성해서 각 범주에 맞는 문서를 나눈다.

버킷은 다중 버킷 집계에서 다른 집계를 중첩할 때 유용하다. 이것은 데이터에 더 깊은 통찰을 제공한다.

집계 유형사용 유형 예제
terms블로그 사이트의 상위 태그 보여주기; 뉴스사이트의 이번주 인기있는 주제 보기
significant_terms평상시보다 이번달에 사용자들이 사용하거나 다운로드한 새로운 기술 트렌드 정보 동향
range와 date_range저렴한, 중간 가격의, 매우 비싼 랩톱 정보 보여주기, 지나간 이벤트, 금주의 이벤트, 다가오는 이벤트 보여주기
histogram과 date_histogram분포 보여주기: 연령대별로 얼마나 많은 사용자가 운동하는지 또는 매일 산 상품에 대한 추세 보여주기

 

중첩 집계

집계의 진정한 사용은 중첩 집계이다. 예를 들어 블로그를 가지고 있고, 각 게시글의 접근에 대해 기록하고 있다면, 텀즈 집계를 이용하여 사용자들이 가장 많이 접근하는 게시글을 찾을 수 있다. 카디널리티 집계를 텀즈 집계 하위에 중첩해서 사용하면, 각 게시글의 유일한 접근자의 수를 볼 수 있고, 텀즈 집계의 정렬을 수정하여 가장 많은 유일한 사용자가 본 게시글을 볼 수 있다.

 

집계를 중첩하여 사용하는 것은 데이터를 탐색하는데 새로운 가능성을 열어준다. 집계를 중첩하여 사용하는 건 엘라스틱서치의 facets을 대체하기 위해 나타났다. facets은 중첩해서 사용할 수 없기 때문이다.

 

다중 버킷 집계는 일반적으로 집계를 중첩하는 시작 지점이다. 예를 들어 각 태그에 대해 매달 얼마나 많은 그룹이 생성되었는지도 알 수 있다.

 



중첩 집계 중 result grouping이라 불리는 사용 사례에 대해 볼 것이다. result grouping은 관련도에 따라 상위 N개의 결과를 가지고 오는 일반 검색과 다르게, 부모 집계로부터 생성된 각 문서의 버킷에서 상위 N개의 결과를 가지고 온다. 온라인 상점을 운영 중이고, 누군가 윈도우를 검색한다면, 보통 때는 처음에 Windows OS의 많은 버전을 검색 결과로 보여줄 것이다. 이건 가장 좋은 사용자 경험을 제공해주지 않는다. 검색 시점에 사용자들이 모든 윈도우 OS를 구매하기 위한 것이라고 볼 수 없기 때문이다.

 

어떤 소프트웨어는 윈도우를 위해 개발되었을 수도 있고, 특정 하드웨어가 윈도우에서 동작할 수도 있다. 이것이 result grouping이 필요한 이유다. 각 운영체제, 소프트웨어, 하드웨어 범주의 상위 3개의 결과를 보여주고, 사용자가 선택할 수 있는 넓은 선택 범위를 제공한다. 사용자는 특정 범위에 대해 검색하기 위해 범위명을 클릭할 수도 있다.

 



엘라스틱서치에서 top_hits라는 특별한 집계를 사용하여 결과를 묶어서 가지고 올 수 있다. 이건 사용자가 설정한 점수를 기반으로 부모 집계의 각 버킷을 정렬하여 상위 N개의 결과를 돌려준다. 여기서 부모 집계는 온라인 상점 예제에서처럼 카테고리 필드에 대해 동작하는 terms 집계가 될 수 있다.

 

집계가 실행되는 문서의 집합을 제어하는 예제도 살펴본다. 예를 들어 질의에 상관없이, 작년에 get-together 그룹에서 생성된 상위 태그를 보고 싶을 수 있다. 이를 위해서 제공된 필터에 일치하는 문서의 버킷을 생성하여 다른 집계와 중첩할 수 있는 필터 집계를 사용할 수 있다.

 

 

(1) 다중 버킷 집계 중첩

다른 집계와 집계를 중첩하려면 aggregation이나 aggs키를 부모 집계와 같은 레벨에서 사용하면 된다. 다중 버킷 집계에서 이건 무한히 수행이 가능하다. 예를 들어 다음 예제에서 텀즈 집계를 이용하여 상위 태그를 보려고 한다. 각 상위 태그에 대해서 날짜 히스토그램 집계를 이용하여 매달 얼마나 많은 그룹이 생성되었는지 보고, 최종적으로 각 그룹의 버킷에 대하여 범위 집계를 이용하여 3명 미만/3명 이상의 멤버를 가기고 있는 그룹을 확인해보자.

 

URI=localhost:9200/get-together/group/_search
curl "$URI?pretty&search_type=count" -d '{
  "aggregations": {
    /* 일반적인 상위 값을 가져오는 텀즈 집계 */
    "top_tags": {
      "terms": {
        "field": "tags.verbatim"
      },
      /* aggregation 키를 이용하여 하위 집계 정의 */
      "aggregations": {
        "groups_per_month": {
          "date_histogram": {
            "field": "created_on",
            "interval": "1M"
          },
          /* 날짜 히스토그램 집계를 하위 집계로 설정 */
          "aggregations": {
            "number_of_members": {
              "range": {
                "script": "doc['"'members'"'].values.length",
                /* 범위 집계는 모든 태그와 날짜 버킷에 적용 */
                "range": [
                  { "to": 3 },
                  { "from": 3 }
                ]
              }
            }
          }
        }
      }
    }
  }
}'
{
  "aggregations": {
    "top_tags": {
      "buckets": [{
        /* "big data"는 상위 태그이고 3개의 문서에 나타남 */
        "key": "big data",
        "doc_count": 3,
        "groups_per_month": {
          /* "big data" 문서가 생성된 월별로 버킷이 생성됨 */
          "buckets": [ {
            "key_as_string" : "2010-04-01",
            "key": 1270080000000,
            "doc_count": 1,
            "number_of_members": {
              "buckets": [ {
                "key": "*-3.0",
                "to": 3.0,
                "to_as_string": "3.0",
                "doc_count": 1
              }, {
                "key": "3.0-*",
                "from": 3.0,
                "from_as_string": "3.0",
                "doc_count": 0
              } ]
            }
          }, {
            "key_as_string": "2012-08-01"
          ...
          ]
        }
      }]
    }
  }
}

 

항상 버킷 집계 안에서 지표 집계를 중첩할 수 있다. 예를 들어 3명 미만/3명 이상의 범위 대신에 그룹의 평균 멤버를 보고 싶다면, avg나 stats 집계를 이용할 수 있다.

 

top_hits라는 특정 집계 유형도 존재한다. 그건 부모 집계의 각 버킷에 설정한 단어로 정렬하여 상위 N개의 결과를 돌려준다. 다음으로 어떻게 top_hits 집계를 이용해서 결과를 가지고 올지 알아본다.

 

 

(2) 결과를 그룹 지어 가지고 오는 중첩 집계

결과를 묶어서 보는건 상위 결과를 범주별로 묶어서 보려고 할때 유용하다. 구글 같은 사이트에 많은 결과를 보여줄 때 해당 사이트에서 상위 3개 정도의 결과만 보고 다른 사이트의 결과를 보고 싶을 수 있다. 질의에 일치하는 모든 결과를 보기 위해서 사이트의 이름을 클릭하면 보여준다.

 

결과를 묶어서 보는건 결과에 다른 좋은 정보나 아이디어를 줄 수 있도록 한다. 사용자에게 최신 이벤트를 표시하고 더 다양한 결과를 만들고 가장 참석이 많은 참석자에게 최신 이벤트에 대한 정보를 보여주고 싶을 것이다. 이럴 경우 다음 예제에서와 같이 참석자 필드에 텀즈 집계를 한 결과에 top_hits를 중첩해서 사용하면 된다.

 

URI=localhost:9200/get-together/event/_search
curl "$URI?pretty&search_type=count" -d '{
  "aggregations": {
    "frequent_attendees": {
      // 이 텀즈 집계는 2명의 참가자가 가장 많이 참석한 이벤트를 확인한다.
      "terms": {
        "field": "attendees",
        "size": 2
      },
      "aggregations": {
        "recent_events": {
          /* top_hits 집계는 실제 이벤트 정보를 준다 */
          "top_hits": {
            "sort": {
              /* 가장 최근 이벤트를 첫번째로 반환한다. */
              "date": "desc"
            },
            "_source": {
              "include": [ "title" ]
            },
            /* 버킷에 가져올 결과 갯수를 설정 */
            "size": 1
          }
        }
      }
    }
  }
}'
{
  "aggregations": {
    "frequent_attendees" : {
      "buckets": [ {
        /* "Lee"는 가장 자주 참석했다. 5번의 이벤트 참석을 했다. */
        "key": "lee",
        "doc_count": 5,
        "recent_events": {
          "hits": {
            "total": 5,
            "max_score": 1.0,
            "hits": [ {
              "_index": "get-together",
              "_type": "event",
              "_id": "100",
              "_score": 1.0,
              "_source": {"title":"Liberator and Immutant"},
              "sort": [ ... ]
            } ]
          }
        }
      }, {
        "key": "shay",
        "doc_count": 4,
        "recent_events": {
          "hits": {
            ...
            "_source": {"title": "..."}
            ...
      } ]
    }
  }
}

 

처음에는 결과를 묶어서 보기 위해 집계를 사용하는 것이 이상하게 생각될 수도 있다. 그러나 집계가 무엇인지 배운 후에는 버킷과 중첩에 대한 개념은 매우 강력하고 질의 결과에 대한 통계를 수집하는 것보다 훨씬 더 많은 일을 할 수 있다는 것을 안다. top_hits 집계는 비통계 집계 결과의 예이다.

 

기본적으로 집계를 실행할때 질의 결과에만 한정하지 않는다. 하지만 필요하다면 피해서 실행할 수도 있다. 블로그 사이드바에 가장 인기 있는 포스트 태그를 보여주려고 한다. 그리고 사용자의 검색과 상관없이 사이드바에서 보여주려고 한다. 이를 위해서 질의에 독립적으로 모든 블로그 포스트에 대해 텀즈 집계를 동작시켜야 한다.다음과 같은 경우에 global 집계가 유용하다. 검색하는 조건(검색하는 색인과 타입)의 모든 문서를 버킷에 담고, 이 모든 문서 밑에서 다른 집계를 중첩해서 사용할 때 유용하다.

 

global 집계는 다른 집계에 동작하기 위해 문서의 집합을 변경하는 단일 버킷 집계의 하나이다.

 

 

(3) 단일 버킷 집계 사용하기

엘라스틱서치는 기본적으로 질의 결과에 집계를 동작시킨다. 기본값을 변경하기 위해서는, 단일 버킷 집계를 사용해야만 한다. 다음은 단일버킷 집계의 종류 3가지 이다.

 

  • global 집계는 검색하는 색인과 타입의 모든 문서 타입을 버킷으로 생성한다. 이건 질의에 상관없이 모든 문서에 대해 집계를 실행할 때 유용하다.
  • filter와 filters 집계는 하나 또는 그 이상의 필터에 일치하는 문서를 버킷으로 생성한다. 이건 문서의 집합을 더 제한할 필요가 있을때 유용하다. 예를 들어 상점에 물품이 있는 상품만 검색하거나 재고가 있는 상품과 프로모션 상품을 분리해서 집계하는 경우다.
  • missing 집계는 특정 필드를 가지고 있지 않은 문서들로 버킷을 생성한다. 이건 필드에 어떤 계산을 하는 집계를 실행시킬때, 필드의 데이터가 없어서 집계에서 다루지 못하는 경우에 유용하다. 예를 들어 여러 상점의 상품 평균 가격을 보여주고 이 상품이 없는 상점을 보여주려고 할때 사용할 수 있다.

 

GLOBAL

전체에서 가장 빈번하게 사용되는 태그를 보고 싶다. 예를 들어 상위 태그를 사용자가 검색한 결과에 관련없이 사이드바에 보여주고 싶다. 이를 위해서는 집계하는 질의의 데이터의 흐름을 변경할 수 있는 global 집계가 필요하다.



 

다음과 같이 제목에서 "elasticsearch"를 찾고 있을지라도, 전 문서의 가장 많이 사용되는 태그를 얻기 위해서 global 집계에서 텀즈 집계를 중첩해서 사용한다. 여기서 "모든 문서"라 함은 검색 URI에 정의된 검색의 모든 문서를 말한다. 전체 get-together 색인에서 그룹 유형에 대해 검색하는 경우 모든 그룹을 고려해야 한다. 모든 get-together 색인에서 검색한다면, 집계는 그룹과 이벤트를 포함해야 한다.

URI=localhost:9200/get-together/group/_search
curl "$URI?pretty&search_type=count" -d '{
  "query": {
    "match": {
      "name": "elasticsearch"
    }
  },
  "aggregations": {
    "all_documents": {
      "global": {},
      "aggregations": {
        "top_tags": {
          "terms": {
            "field": "tags.verbatim"
          }
        }
      }
    }
  }
}'

 

 

필터

JSON 요청에 filtered 질의를 사용하는 대신에 필터를 직접 정의할때 post 필터를 사용한다. post 필터는 집계에 영향을 주지 않고 결과를 제한한다. 필터 집계는 반대로 동작한다. 이것은 집계가 동작하는 문서를 제한하고 결과에는 영향을 미치지 않는다.

 



이벤트 제목에서 "elasticsearch"를 검색하려고 한다면, 이벤트 설명을 포함해서 워드 클라우드를 만들고 싶을 것이다. 하지만 최근 이벤트의 정보만 나타내고 싶다.

 

그렇게 하려면 다음과 같이 평소와 동일하게 질의를 사용하되, 필터 집계를 같이 사용하면 된다. 첫번째 필터 집계는 문서를 7월 1일 이후로 제한한다. 그리고 텀즈 집계를 중첩하여 워드 클라우드를 생성하면 된다.

 

URI=localhost:9200/get-together/event/_search
curl "$URI?pretty&search_type=count" -d '{
  "query": {
    "match": {
      "title": "elasticsearch"
    }
  },
  "aggregations": {
    "since_july": {
      "filter": {
        "range": {
          "date": {
            "gt": "2013-07-01T00:00"
          }
        }
      },
      "aggregations": {
        "description_cloud": {
          "terms": {
            "field": "description"
          }
        }
      }
    }
  }
}'

 

MISSING

우리가 지금까지 본 집계의 대부분은 문서의 버킷을 생성하고 필드의 값으로부터 통계 정보를 얻는다. 만약 문서가 필드를 가지고 있지 않다면, 버킷에 포함되지 못하고 어떠한 통계에도 집계될 수 없다.

 

예를 들어 이벤트 날짜에 날짜 히스토그램 집계를 사용한다고 해보자. 하지만 일부 이벤트는 이벤트 날짜 정보가 설정되지 않았다. 날짜 정보가 설정되지 않은 정보도 집계하고 싶다면 missing 집계를 사용하면 된다.

 

다른 단일 버킷 집계처럼, missing 집계도 다른 집계와 같이 중첩해서 사용할 수 있다. 날짜가 지정되지 않은 이벤트일지라도 최대 집계를 이용하여 하나의 이벤트에 참석하는 최대 사용자 수를 알 수 있다.

 

중첩된 문서를 사용하면 엘라스틱서치에서 관계형 데이터와 같은 작업이 가능해진다.



출처: https://12bme.tistory.com/482?category=737765 [길은 가면, 뒤에 있다.]