집계 (Aggregations)
집계 (Aggregations)
집계(aggregation)는 데이터를 지표(metric)·통계·기타 분석 형태로 요약해 줍니다. 다음 같은 질문에 답할 때 집계를 쓰면 돼요.
- 내 웹사이트의 평균 로드 타임은 얼마인가요?
- 거래량 기준으로 가장 가치 있는 고객은 누구인가요?
- 네트워크에서 어떤 파일이 큰 파일로 간주될까요?
- 각 상품 카테고리에는 상품이 몇 개나 있나요?
Elasticsearch는 집계를 세 가지 범주로 나눕니다.
- 지표(Metric) 집계 — 필드 값에서 합(sum)이나 평균(avg) 같은 지표를 계산해요.
- 버킷(Bucket) 집계 — 필드 값, 범위, 또는 다른 기준에 따라 문서를 버킷(또는 bin)으로 묶어요.
- 파이프라인(Pipeline) 집계 — 문서나 필드가 아니라 다른 집계의 출력을 입력으로 받아요.
검색 안에서 집계 실행하기
집계는 검색의 일부로 실행할 수 있어요. 검색 API의 aggs 파라미터에 집계를 지정하면 됩니다. 아래 검색은 my-field에 대해 terms 집계를 실행해요.
GET /my-index-000001/_search
{
"aggs": {
"my-agg-name": {
"terms": {
"field": "my-field"
}
}
}
}
집계 결과는 응답의 aggregations 객체에 들어 있어요.
{
"took": 78,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 5,
"relation": "eq"
},
"max_score": 1.0,
"hits": [...]
},
"aggregations": {
"my-agg-name": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": []
}
}
}
my-agg-name집계의 결과입니다.
query 파라미터로 집계가 실행되는 문서의 범위를 제한할 수도 있어요.
GET /my-index-000001/_search
{
"query": {
"range": {
"@timestamp": {
"gte": "now-1d/d",
"lt": "now/d"
}
}
},
"aggs": {
"my-agg-name": {
"terms": {
"field": "my-field"
}
}
}
}
기본적으로 집계가 포함된 검색은 검색 히트와 집계 결과를 둘 다 반환합니다. 집계 결과만 필요한 경우엔 size를 0으로 설정하면 되죠.
GET /my-index-000001/_search
{
"size": 0,
"aggs": {
"my-agg-name": {
"terms": {
"field": "my-field"
}
}
}
}
같은 요청 안에 여러 개의 집계를 지정할 수도 있어요.
GET /my-index-000001/_search
{
"aggs": {
"my-first-agg-name": {
"terms": {
"field": "my-field"
}
},
"my-second-agg-name": {
"avg": {
"field": "my-other-field"
}
}
}
}
하위 집계 (Sub-aggregations)
버킷 집계는 버킷 집계나 지표 집계를 **하위 집계(sub-aggregation)**로 가질 수 있어요. 예를 들어 terms 집계가 평균(avg) 하위 집계를 가지면, 각 버킷의 문서들에 대해 평균 값을 계산해요. 하위 집계를 중첩하는 깊이에는 제한이 없습니다.
GET /my-index-000001/_search
{
"aggs": {
"my-agg-name": {
"terms": {
"field": "my-field"
},
"aggs": {
"my-sub-agg-name": {
"avg": {
"field": "my-other-field"
}
}
}
}
}
}
응답은 하위 집계 결과를 부모 집계 아래에 중첩해서 보여줍니다.
{
...
"aggregations": {
"my-agg-name": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "foo",
"doc_count": 5,
"my-sub-agg-name": {
"value": 75.0
}
}
]
}
}
}
- 부모 집계
my-agg-name의 결과입니다. my-agg-name의 하위 집계my-sub-agg-name의 결과입니다.
메타데이터와 집계 타입
meta 객체를 사용해 집계에 커스텀 메타데이터를 연결할 수 있어요.
GET /my-index-000001/_search
{
"aggs": {
"my-agg-name": {
"terms": {
"field": "my-field"
},
"meta": {
"my-metadata-field": "foo"
}
}
}
}
응답은 meta 객체를 그 자리에 그대로 돌려줍니다.
{
...
"aggregations": {
"my-agg-name": {
"meta": {
"my-metadata-field": "foo"
},
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": []
}
}
}
기본적으로 집계 결과에는 집계의 이름만 포함되고 타입은 포함되지 않아요. 집계 타입을 반환하려면 typed_keys 쿼리 파라미터를 사용하면 됩니다.
GET /my-index-000001/_search?typed_keys
{
"aggs": {
"my-agg-name": {
"histogram": {
"field": "my-field",
"interval": 1000
}
}
}
}
응답은 집계 이름 앞에 집계 타입을 접두사로 붙여 반환해요.
중요
일부 집계는 요청의 타입과 다른 집계 타입을 반환합니다. 예를 들어 terms, significant terms, percentiles 집계는 집계 대상 필드의 데이터 타입에 따라 다른 집계 타입을 반환해요.
{
...
"aggregations": {
"histogram#my-agg-name": {
"buckets": []
}
}
}
- 집계 타입
histogram뒤에#구분자와 집계 이름my-agg-name이 붙은 모습입니다.
런타임 필드와 집계
필드가 필요한 집계와 정확히 맞지 않는다면, **런타임 필드(runtime field)**에 대해 집계를 실행하면 돼요.
GET /my-index-000001/_search?size=0
{
"runtime_mappings": {
"message.length": {
"type": "long",
"script": "emit(doc['message.keyword'].value.length())"
}
},
"aggs": {
"message_length": {
"histogram": {
"interval": 10,
"field": "message.length"
}
}
}
}
스크립트는 필드 값을 동적으로 계산하므로 집계에 약간의 오버헤드가 더해집니다. 계산에 걸리는 시간 외에도, terms나 filters 같은 일부 집계는 런타임 필드에서 일부 최적화를 사용할 수 없어요. 결과적으로 런타임 필드를 쓸 때의 성능 비용은 집계마다 다릅니다.
성능 관련 참고
응답을 더 빠르게 만들기 위해, Elasticsearch는 자주 실행되는 집계의 결과를 **샤드 요청 캐시(shard request cache)**에 캐시할 수 있어요. 캐시된 결과를 얻으려면 각 검색에서 같은 preference 문자열을 사용해야 합니다. 검색 히트가 필요 없다면 size를 0으로 설정해 캐시가 차는 걸 피하는 게 좋아요.
Elasticsearch는 같은 preference 문자열을 가진 검색을 같은 샤드로 라우팅합니다. 검색 사이에 샤드의 데이터가 바뀌지 않는다면, 샤드는 캐시된 집계 결과를 반환하죠.
집계를 실행할 때 Elasticsearch는 숫자 데이터를 담고 표현하는 데 double 값을 사용해요. 그 결과, 2^53보다 큰 long 숫자에 대한 집계는 근사값이 됩니다.