Search

[엘라스틱 서치 실무 가이드] 5장 데이터 집계

5. 데이터 집계

5.1 집계

5.1.1 엘라스틱서치와 데이터 분석
코디네이터 노드, 데이터 노드 기준으로 분산처리가 가능하기 때문에 빠르다
5.1.2 엘라스틱서치가 집계에 사용하는 기술
집계 시 검색보다 더 많은 리소스를 사용
집계 시 주의사항에 알아보자
캐시
캐시는 질의 결과를 임시 버퍼에 둠. 이후 같은 처리에는 버퍼에 보관된 결과를 반환
일반적으로 캐시는 힙 메모리의 1%를 할당, 캐시에 없는 질의의 경우 성능 향상에 별다른 도움 안됨
elasticsearch.yml 파일에서 indices.requests.cache.size: 2% 로 힙 메모리 사용량 증가 가능
엘러스틱 캐시의 종류 3가지
1.
Node query cache
a.
노드의 모든 샤드가 공유하는 LRU(Least-Recently-Used) 캐시.
b.
용량이 가득차면 사용량이 가장 적은 데이터를 삭제하고 새로운 결과값을 캐싱
c.
기본적으로 10% 필터 캐시가 메모리를 제어하고 쿼리 캐싱을 사용할지 여부는 yml 파일에 옵션으로 추가하면 됨.
i.
기본 true
ii.
index.queries.cache.enabled:true
2.
Shard request cache
a.
인덱스의 수평적 확산과 병렬 처리를 통한 성능 향상을 위해 고안된 개념
b.
샤드에서 수행된 쿼리의 결과를 캐싱
c.
샤드의 내용이 변경되면 캐시가 삭제되기 때문에 업데이트가 빈번한 인덱스에서는 오히려 성능 저하
3.
Field data cache
a.
집계 연산 시 모든 필드 값을 메모리에 로드
b.
이러한 이유로 집계 쿼리는 성능적인 측면에서 비용이 많이 든다.
c.
집계가 계산되는 동안 필드의 값을 메모리에 보관
d.
fielddata는 GC 이슈가 존재하기 때문에 요즘에는 디스크 기반의 doc_value를 활성화
e.
doc value 또한 캐시 사용 가능 (운영 체제의 파일 시스템 캐시를 통해 자주 접근하는 데이터는 메모리에 캐시될 수 있습니다)
5.1.3 실습 데이터 살펴보기
텍스트는 키워드 타입으로 해야됨
5.1.4 Aggregation API 이해하기
데이터 필드의 값을 더하거나 평균을 내는 등 검색 쿼리로 반환된 데이터를 집계하는 경우가 많음
버킷 집계 : 쿼리 결과를 특정 기준으로 나누고, 나눈 결과에 대해 계산을 합니다. 여기서 나눈 결과가 각 버킷이 됩니다.
메트릭 집계 : 쿼리 결과에서 필드 값을 더하거나 평균을 내는 등의 계산을 합니다.
파이프라인 집계 : 다른 집계나 관련 계산 결과를 모읍니다.
행렬 집계 : 여러 필드에서 값을 추출해 행렬 계산을 합니다. 다양한 통계 정보를 제공하지만, 아직 공식 기능이 아니라 실험적 기능이므로 사용할 때 주의가 필요합니다.
집계 구문의 구조
엘라스틱서치가 강력한 이유는 집계를 중첩해 사용할 수 있다는 데 있다.
중첩 횟수에 제한은 없으나 중첩할수록 성능 하락이 뒤따른다는 점에 주의해야 한다.
aggs : { // 집계 선언 agg_name :{ // 집계 이름(내가 짓고 싶은 이름) agg_type : { // terms, date_histogram, sum agg_body } } }
Java
복사
집계 영역
질의를 통해 반환된 문서들의 집합 내에서 집계를 수행
질의가 생략된다면 내부적으로 match_all 쿼리로 수행되어 전체 문서에 대해 집계가 수행
집계를 질의 영역과 전체 영역에 대해서 해야할 경우 (글로벌 버킷에서 실행)
{ query : { } "aggs" : { <집계 이름> : { <집계 타입> : { "field" : <필드명> } }, <집계 이름> : { "global" : {} "aggs" : { <집계 이름> : { <집계 타입> : { "field" : <필드명> } } } } } }
JavaScript
복사

5.2 메트릭 집계

단일 숫자 메트릭 집계 : 수행한 결과 값이 하나(avg, max)
다중 숫자 메트릭 집계 : 수행한 결과 값이 여러개(stats, geo_bounds)
{ "took" : 106, // ES가 검색을 실행하는데 소요된 시간 "timed_out" : false, // 검색 시간이 초과됐는지 여부 "_shards" : { // 검색에 영향받은 샤드에 대한 정보 "total" : 5, // 검색에 영향받은 샤드에 대한 총 개수 "successful" : 5,// 검색 요청에 대한 정상 샤드 수 "skipped" : 0, // 검색 요청에 대한 건너뛴 샤드 수 "failed" : 0 // 검색 요청에 대한 실패한 샤드 수 }, "hits" : { // 검색 결과 "total" : { "value" : 10000,// 검색 기준과 일치하는 총 문서 수 "relation" : "gte" }, "max_score" : null, // 검색 결과에 포함된 문서의 최대 스코어 값 "hits" : [ ] // 검색 결과 문서들의 배열(기본 10개) }, "aggregations" :[] // 집계 결과 }
JavaScript
복사
5.2.1 합산 집계
GET /index/_search { query : { constant_score(필터에 해당하는 문서에 같은 스코어를 부여) : { "match" : { "geo.city_name" : "Paris" } } } aggs : { "agg_name" : { "sum" : { // "field" : "bytes" 기본 단일 숫자 통계 "script" : { // 만약 단위를 바꾼다면 script 사용 "lang" : "painless" , //"source" : "doc.bytes.value / params.divide_value", "source" : "doc.bytes.value / (double)params.divide_value", // 아래 문제를 해결하기위해 캐스팅 "params" : { "divide_value" : 1000 // 이럴 경우 문서의 개별적인 값에서 1000으로 나와 처음 값과 다른 값이 나옴 } } } } } }
Java
복사
script를 이용하면 집계 시 더 다양한 연산을 추가적으로 수행할 수 있기 때문에 유용
5.2.2 평균 집계 (avg)
GET /apache-web-log/_search?size=0 { "aggs": {"avg_bytes" : {"avg" : {"field" : "bytes"}}} } ... GET _search?size=0 { "query" : {"constant_score" : {"filter" : {"match" : {"geoip.city_name" : "Paris"}}}}, "aggs": {"avg_bytes" : {"avg": {"field" : "bytes"}}} }
JSON
복사
5.2.3 최소 집계 (min)
GET /apache-web-log/_search?size=0 {"aggs" : {"min_bytes" : {"min" : {"field" : "bytes"}}}} GET /apache-web-log/_search?size=0 {"query" : {"constant_score" : {"filter" : {}} ,"match" : { "geoip.city_name" : "Paris" },"aggs" : {"min_bytes" : {"min" : {"field" : "bytes"}}}}}
JSON
복사
5.2.4 최대 집계 (max)
GET /apache-web-log/_search?size=0 { "query": { "constant_score": { "filter": { "match": { "geoip.city_name": "Paris" } } } }, "aggs": { "max_bytes": { "max": { "field": "bytes" } } } }
JavaScript
복사
5.2.5 개수 집계 (value count aggregation, 단일 숫자 메트릭 집계)
GET /index/_search { query : { constant_score(필터에 해당하는 문서에 같은 스코어를 부여) : { "match" : { "geo.city_name" : "Paris" } } } aggs : { "agg_name" : { "value_count": : { "field" : "bytes" } } } }
Java
복사
5.2.6 통계 집계 (stats)
여러 개의 결과(위에 5개)를 포함한 집계
통계 집계를 사용하면 앞서 살펴본 합. 평균, 최대/최솟값, 개수를 한번의 쿼리로 집계할 수 있다.
GET /index/_search { query : { constant_score(필터에 해당하는 문서에 같은 스코어를 부여) : { "match" : { "geo.city_name" : "Paris" } } } aggs : { "bytes_stats" : { "stats": : { "field" : "bytes" } } } }
Java
복사
5.2.7 확장 통계 집계 (extends_stats)
위의 결과를 포함한 더 많은 통계 결과 (통계 집계를 확장해서 표준편차 같은 통겟값이 추가)
GET /index/_search?size=0 { "aggs" : { "bytes_stats" : { "extended_stats": { "field" : "bytes" } } } }
Java
복사
{ "aggregations" : { "bytes_stats" : { "count" : 9330, "min" : 35.0, "max" : 6.9192717E7, "avg" : 294456.8601286174, "sum" : 2.747282505E9, "sum_of_squares" : 1.18280314234513344E17, // 제곱합 "variance" : 1.2590713617814016E13, // 확률변수의 분산 "variance_population" : 1.2590713617814016E13, "variance_sampling" : 1.2592063249459188E13, "std_deviation" : 3548339.5578515334, // 표준편자 "std_deviation_population" : 3548339.5578515334, "std_deviation_sampling" : 3548529.730671449, "std_deviation_bounds" : { // 표준편차의 범위 "upper" : 7391135.975831684, // 표준편차의 상한 값 "lower" : -6802222.25557445, // 표준편차의 하한 값 "upper_population" : 7391135.975831684, "lower_population" : -6802222.25557445, "upper_sampling" : 7391516.321471516, "lower_sampling" : -6802602.60121428 } } } }
JavaScript
복사
5.2.8 카디널리티 집계 (cardinality)
단일 숫자 메트릭 집계
중복된 값은 제외한 고유한 값에 대한 집계를 수행
모든 중복된 값을 집계하는 것 대신에 근사치를 통해 집계 수행(성능 이슈)
예) 미국 내에서 몇 개의 도시에서 유입이 있었는지
GET /index/_search { "query" : { "constant_score": { "filter" : { "match" : { "geoip.country_name" : "United States" } } } }, "aggs" : { "us_card" : { "cardinality": { "field" : "geoip.city_name.keyword" } } } }
Java
복사
근사치 계산 방법이 책에 나옴 (HyperLogLog++ 알고리즘 사용, 해시기반)
매핑을 설정할 때 근사치 계산이 수행될 필드에 미리 해시 설정을 해두면 색인할 때 해시 값이 미리 계산되기 때문에, 집계 시해시 계산이 필요 없게 되어 성능이 향상된다.
5.2.9 백분위 수 집계
백분위 분포도 알 수 있음
백분위 수 집계 또한 카디널리티 집계와 같이 근사치 계산으로 집계.
문서들의 집합에서 특정 필드에 대한 백분위 수를 구해야 하기 때문에 카디널리티 집계와는 달리 TDiges t 알고리즘을 사용
GET /index/_search?size=0 { aggs : { "bytes_percentiles" : { "percentiles": : { "field" : "bytes", "percents" : [10,20,30,40 ~~ 90] // 직접 백분위에 입력 가능 } } } } // 결과 "aggregations" : { "bytes_percentiles" : { "values" : { "1.0" : 229.0, "5.0" : 358.38888888888886, "25.0" : 3644.220869565217, "50.0" : 12217.633591101996, "75.0" : 37335.65963636364, "95.0" : 171334.22222222222, "99.0" : 1204031.8000000163 } } }
Java
복사
5.2.10 백분위 수 랭크 집계
수치를 통해 백분위 계산
GET /index/_search?size=0 { aggs : { "bytes_percentiles" : { "percentiles_ranks": : { "field" : "bytes", "values" : [5000, 10000] // 직접 백분위에 입력 가능 } } } }
Java
복사
5.2.11 지형 경계 집계
지형 경계 집계 (Geo Bounds Aggregation)는 지형 좌표를 포함하는 필드에 대해 해당 지역 경계 상자를 계산하는 메트릭 집계.
계산하려는 필드의 타입이 geo_point인 경우에 사용합니다.
5.2.12 지형 중심 집계
지형 중심 집계 (Geo Centroid Aggregation)는 앞서 살펴본 지형 경계 집계의 범위에서 정가운데의 위 치를 반환

5.3 버킷 집계

메트릭과 다르게 계산하지 않고 버킷을 생성
버킷
집계된 결과 데이터 집합을 메모리에 저장한다는 의미, 중첩도 가능.
하지만 성능에 영향을 주므로 search.max_buckets 에서 최대 버킷 수 수정 가능
집계 질의를 요청할 때 버킷 크기를 -1(전체 대상) 또는 10000 이상의 값을 지정할 경우에는 엘라 스틱서치에서 경고 메시지를 반환
5.3.1 범위 집계
범위 내에서 집계 수행 , from(이상) , to(미만)
배열이라 여러 개 가능 , 이름도 지정해서 특정 가능
GET /index/_search { aggs : { "bytes_range" : { "range": : { "field" : "bytes", "ranges" : [ { "from" : 1000, "to" : 2000 } ] } } } }
Java
복사
결과로 제공되는 key에는 기본적으로 범위가 지정돼 있는데 좀 더 명확하게 표현하기 위해 다음과 같이 직접 원하는 정보를 설정
5.3.2 날짜 범위 집계
범위 집계 날짜 버전
날짜 형식으로는 엘라스틱서치에서 지원하는 형식만 시용
GET /index/_search { aggs : { "my_date_range" : { "date_range": : { "field" : "@timestamp", "ranges" : [ { "from" : "2020-10-10T05:14:00.000Z", "to" : "2020-10-31T05:14:00.000Z" } ] } } } }
JSON
복사
5.3.3 히스토그램 집계
숫자 범위 처리
간격(interval) 내에서 집계를 수행
GET /index/_search { aggs : { "bytes_histogram" : { "histogram": : { "field" : "@timestamp", "interval" : 10000, "min_doc_count" : 1 //최소 숫자까지 포함(해당은 0인 문서는 제외) } } } }
JSON
복사
5.3.4 날짜 히스토그램 집계
날짜 히스토그램 집계 (Date Histogram Aggrega tion)는 다중 버킷 집계에 속하며 히스토그램 집계와 유사
분 단위로 얼마만큼의 사용자 유입이 있었는지 확인해보기 위해 다음과 같은 집계를 수행
GET /index/_search { aggs : { "my_date_histogram" : { "date_histogram": : { "field" : "timestamp", "interval" : "minute", //30m , 1.5h 같이 세밀한 표현도 가능 "format" : "yyyy-MM-dd" // 포맷 설정 가능 "time_zone" : "+09:00" // 한국 시간으로 변경 "offset" : "+3h" // 집계 기준이 일자별이라면 offset을 주어 3시부터 집계 가능 } } } }
JSON
복사
5.3.5 텀즈(terms) 집계
버킷이 동적으로 생성되는 다중 버킷 집계
집계 시 지정한 필드에 대해 빈도수가 높은 텀의 순위로 결과가 반환
가장 많이 접속하는 시용지를 알아낸다거나 국가별로 어느 정도의 빈도로 서버에 접속하는지 등의 집계를 수행
GET /index/_search { aggs : { "my_terms" : { "terms": : { "field" : "country", "size" : 100 // 사이즈를 주어 모든 문서가 포함되게 함 } } } }
JavaScript
복사
>> 결과 "aggregations": { "request count by country": { "doc_count_error_upper_bound": 47, "sum_other_doc_count": 2334, "buckets": [ { "key": "United States", "doc_count": 3974 }, { "key": "France", "doc_count": 855 }, { "key": "Germany", "doc_count": 510 }, { "key": "Sweden", "doc_count": 440 } ] } }
JSON
복사
sum_other_doc_count : size가 기본 10이라 결과에 포함되지 않은 집계가 있다는 의미
결과에 모든 문서가 포함되지 않을 수 있을 때 결과가 달라짐으로 주의
집계를 할 때 각 샤드에 요청을 보내고, 각 샤드는 집계 결과를 정렬하여 자신만의 뷰를 만듭니다.
이 뷰들을 합쳐서 최종 뷰를 만드는데, 만약 모든 문서가 포함되지 않으면, 집계 결과가 정확하지 않을 수 있습니다.
size 증가 시 결과 값에 대한 정확도도 높아지지만 버킷에 더 많은 양의 데이터를 담아야 하기에 메모리도 많이 듬
doc_count_error_upper_bound : 최종 집계 결과에서 포함되지 않은 잠재 문서의 수
위에 경우 size, shard_size를 조절해 정확도를 높이자
집계와 샤드 크기

5.4 파이프라인 집계

다른 집계로 생성된 버킷을 참조해서 집계를 수행
부모, 형제 두 가지 유형이 있음
파이프라인 집계를 수행할 때는 buckets_path 파라마터를 사용해 참조할 집계의 경로를 지정함으로써 체 인 형식으로 집계 간의 연산이 이뤄진다.
AGG_SEPARATOR = '>' , METRIC_SEPARATOR = ' . ' AGG_NAME = 〈집계 이름〉 ; METRIC = 〈메트릭 집계 이름(다중 메트릭 집계인 경우); PATH = <AGG_NAME > [ <AGG_SEPARATOR >, <AGG_NAME > ]* [ <METRIC_SEPARATOR >, <METRIC> ] ;
Shell
복사
5.4.1 형제 집계
동일 선상의 위치에서 수행되는 새 집계를 의미
기존 버킷에 추가되는 형태가 아니라 동일 선상의 위치에 새 집계가 생성되는 파이프라인 집계
최대 버킷 집계를 통해 분 단위 데이터량 합산과 더불어 가장 큰 데이터량을 구하는 예제.
GET index/_search { "aggs" : { "histo" : { "date_histogram" : { "field" : "timestamp", "interval" : "minute" }, "aggs" : { "bytes_sum" : { "sum" : { "field" : "bytes" } } } }, "my_bucket_pipe" : { "max_bucket" : { // 버킷에 대한 종류 많음 "buckets_path" : "histo>bytes_sum" } } } }
JSON
복사
5.4.2 부모 집계
집계를 통해 생성된 버킷을 사용해 계산을 수행하고, 그 결과를 기존 집계에 반영
부모에 해당하는 상위 집계를 통해 생성된 버킷에 대해 집계를 수행
GET /apache-web-log/_search?size=0 { "aggs": { "histo": { //부모 히스토그램 일 단위로 집계를 수행한다. "date_histogram": { "field": "timestamp", "interval": "day" }, "aggs": { "bytes_sum": { //일 단위로 데이터의 크기를 합산하는 sum 집계를 수행한다. "sum": { "field": "bytes" }, "sum_deriv": { //일 단위로 데이터 크기의 합산된 값을 버킷의 현재 값과 이전 값을 비교하는 집계를 수행 "derivative": { "buckets_path": "bytes_sum" } } } } } } }
JSON
복사

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

일부 집계 연산의 경우 성능 문제로 근삿값을 기반으로 한 결과를 제공
5.5.1 집계 연산과 정확도
위에서 설명한 집계 유형
버킷 집계(Buckets Aggregations)
특정 기준을 만족하는 문서들을 버킷으로 분류하는 집계
메트릭 집계(Metrics Aggregations) → 수학적 계산 수행
버킷에 있는 문서들을 이용하여 다양한 통계 지표를 생성하는 집계
파이프라인 집계(Pipeline Aggregations)
서로 다른 메트릭 집계의 결과를 연결하는 집계 패밀리
행렬 집계(Matrix Aggregations)
추출한 값을 기반으로 결과별 행렬을 생성하는 집계 패밀리
근사값 기반으로 결과를 내는 3가지
1.
카디널리티 집계 : 특히 자주 사용하므로 주의 하자
a.
성능 문제는 내부적으로 HyperLogLog++ 알고리즘을 이용하여 근사값(Approximate Count)으로 계산됩니다 (95% 수준).
2.
백분위 수 집계
a.
성능 문제를 해결하기 위해 내부적으로 T-Digest 알고리즘을 이용하여 근사치로 계산됩니다.
b.
버킷의 크기가 작을수록 100% 정확도를 얻을 수 있고, 크기가 커질수록 정확도가 떨어진다.
3.
백분위 수 랭크 집계
a.
백분위 수 집계와 마찬가지로 근사치로 계산됩니다.
5.5.2 분산 환경에서 집계 연산의 어려움
엘라스틱서치는 분산처리에 적합하지 않다는 이유로 루씬에서 기본적으로 제공하는 그루핑 기술안 Facet API를 사용하지 않는다.
엘라스틱서치는 독자적인 그루핑 기술인 Aggregation API를 제공
데이터는 샤드의 개수만큼 분산 저장되므로, 요청이 들어오면 각 샤드에서 먼저 집계 작업을 수행합니다. 그 후, 이 중간 결과들을 취합하여 최종 결과를 생성하고 제공하는 방식으로 작동합니다.
분산된 노드가 모두 연산을 수행하기 때문에, 이는 일종의 맵리듀스(Map-Reduce)와 유사하게 동작합니다.
다른 집계(평균 집계, 최솟값 집계, 최댓값 집계, 합산 집계)와 카디널리티 차이
일반 집계
• 각 샤드에서 중간 집계 결과로 계산 값만 코디네이터 노드로 전송 • 일반적인 집계들은 모두 중간 집계 결과만 가지고도 최종 결과를 뽑아낼 수 있음
카디널리티
• 중복을 제거한 데이터 리스트를 코디네이터 노드로 전송 • 중복 제거를 위해 모든 중간 결과 리스트를 하나로 모아서 중복 제거해 최종 결과를 만드는 과정 • 한번 더 메모리에 올려 중복 제거를 함 • 메모리에 올린다는 것은 데이터 크기에 따라서 부하가 엄청 드는 작업 • 그래서 근사치를 제공하게 됨
시스템 도입 시 포기하는 요구 사항
1.
분산환경을 포기
a.
정확한 데이터를 실시간으로 제공할 수 있다(정확도 + 실시간성)
b.
단일 서버로 서비스를 구성하고 모든 데이터를 메모리에서 처리한다
c.
처리 가능한 데이터의 크기에 제한이 있음
d.
복잡한 연산을 수행할 때 정확하고 빠르게 처리할 수 있다.
2.
실시간성을 포기
a.
분산환경에서 정확한 데이터를 제공할 수 있다(분산환경 + 정확도)
b.
디스크 기반의 분산 환경으로 서비스를 구성한다(예: 하둡)
c.
데이터의 크기에 제한이 없고 대용량 데이터를 처리할 수 있다
d.
복잡한 연산을 수항할 때 정확하게 처리할 수 있지만 처리 시간이 오래 걸린다.
3.
정확도를 포기
a.
분산환경에서 데이터를 실시간으로 제공할 수 있다.
b.
메모리 기반의 분산환경으로 서비스를 구성한다
c.
데이터의 크기에 제한이 없고 대용량 데이터를 처리할 수 있다.
d.
복잡한 연산을 수행할 때 근사치를 계산해서 빠르게 처리할 수 있다.
e.
 엘라스틱서치는 정확도를 포기히는 방식으로 분산환경에서 복잡한 연산들을 처리