본 포스팅은 2023년 01월 01일 기준으로 작성되었습니다.
Bucket은 집계 결과를 집합으로 만들어 메모리에 저장합니다. 그렇기에 너무 많은 중첩 집계는 과도한 메모리를 사용하여 OOM(Out Of Memory)현상이 발생 할 수 있습니다.
Range Aggregations(범위 집계)는 사용자가 지정한 범위 내에서 집계를 수행하는 다중 버킷 집계 입니다. 집계가 수행되면 쿼리의 결과가 범위에 해당하는 지 체크하고, 범위에 해당되는 문서들에 대해서만 집계를 수행합니다. (from과 to 속성을 지정하고, to에 지정한 값은 결과에서 제외됩니다.)
{
"aggs":{
"bytes_range":{
"range":{
"field":"bytes",
"ranges":[
{"key":"small","from":1000,"to":2000},
{"key":"medium","from":2000,"to":4000}
]
}
}
}
}
Response:{
"took": 3,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 10001,
"max_score": 0.0,
"hits": []
},
"aggregations": {
"bytes_range": {
"buckets": [
{
"key": "small",
"from": 1000.0,
"to": 2000.0,
"doc_count": 754
},
{
"key": "medium",
"from": 2000.0,
"to": 4000.0,
"doc_count": 1004
}
]
}
}
}
"key"는 집계할 범위를 뜻하고, "from"은 시작, "to"는 끝, "doc_count"는 범위 내의 문서수를 의미합니다.
Date Range를 사용하여 날짜 범위로 집계를 수행할 수 있습니다.
{
"aggs":{
"request_count_date":{
"date_range":{
"field":"timestamp",
"ranges":[
{"from":"2015-05-04T05:16:00.000Z","to":"2015-05-18T05:16:00.000Z"}
]
}
}
}
}
Response:{
"took": 6,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 10001,
"max_score": 0.0,
"hits": []
},
"aggregations": {
"request_count_date": {
"buckets": [
{
"key": "2015-05-04T05:16:00.000Z-2015-05-18T05:16:00.000Z",
"from": 1.43071656E12, //시작날짜의 밀리초 값
"from_as_string": "2015-05-04T05:16:00.000Z",
"to": 1.43192616E12, //끝날짜의 밀리초 값
"to_as_string": "2015-05-18T05:16:00.000Z",
"doc_count": 2345 //날짜 범위에 해당되는 문서 수
}
]
}
}
}
지정한 범위 간격으로 집계를 냅니다. 만약 10000으로 지정하였다면, 0~10000(10,000 제외), 10000~20000(20,000 제외) 의 간격으로 집계를 냅니다.
{
"aggs":{
"bytes_histogram":{
"histogram":{
"field":"bytes", //집계 필드
"interval":10000, //집계 간격
"min_doc_count":1 //최소 1개 이상되어야 결과에 포함
}
}
}
}
Response:{
"took": 3,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 10001,
"max_score": 0.0,
"hits": []
},
"aggregations": {
"bytes_histogram": {
"buckets": [
{
"key": 0.0,
"doc_count": 4196
},
{
"key": 10000.0,
"doc_count": 1930
},
{
"key": 20000.0,
"doc_count": 539
},
...
{
"key": 5.43E7,
"doc_count": 24
},
{
"key": 6.525E7,
"doc_count": 2
},
{
"key": 6.919E7,
"doc_count": 2
}
]
}
}
}
Date Histogram(날짜 히스토그램) 집계는 분, 시간, 월, 연도를 구간으로 집계를 수행할 수 있습니다.
{
"aggs":{
"daily_request_count":{
"date_histogram":{
"field":"timestamp",
"interval":"day",
"format":"yyyy-MM-dd-HH:mm:ss",
"time_zone":"+09:00"
}
}
}
}
Response:{
"took": 3,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 10001,
"max_score": 0.0,
"hits": []
},
"aggregations": {
"daily_request_count": {
"buckets": [
{
"key_as_string": "2015-05-17-00:00:00",
"key": 1431788400000,
"doc_count": 538
},
{
"key_as_string": "2015-05-18-00:00:00",
"key": 1431874800000,
"doc_count": 2898
},
{
"key_as_string": "2015-05-19-00:00:00",
"key": 1431961200000,
"doc_count": 2902
},
{
"key_as_string": "2015-05-20-00:00:00",
"key": 1432047600000,
"doc_count": 2862
},
{
"key_as_string": "2015-05-21-00:00:00",
"key": 1432134000000,
"doc_count": 799
}
]
}
}
}
key_as_string은 집계한 기준 날짜입니다. UTC가 기본이며 "yyyy-MM-dd'T'HH:mm: ss.SSS" 형식을 사용합니다. ("format" 필드로 포맷 변경 가능) key는 집계 기준 날짜를 밀리초로 나타낸 값입니다.
구간 지정을 위해 interval 속성을 사용하여 year, quarter, month, week, day, hour, minute, second 단위로 지정할 수 있고, 더 세밀하게 30m(30분 간격), 1.5h(1시간 30분 간격)와 같은 값도 사용할 수 있습니다.
Timezone 말고도 offset을 사용해 집계 기준이 되는 날짜 값을 조정할 수 있습니다. 위의 예제에서는 daily로 집계했을 때, 00시 기준이었는데, 3시를 기준으로 하고 싶다면 아래와 같이 사용하면 됩니다.
{
"aggs":{
"daily_request_count":{
"date_histogram":{
"field":"timestamp",
"interval":"day",
"format":"yyyy-MM-dd-HH:mm:ss",
"offset":"+3h"
}
}
}
}
Response:{
"took": 4,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 10001,
"max_score": 0.0,
"hits": []
},
"aggregations": {
"daily_request_count": {
"buckets": [
{
"key_as_string": "2015-05-17-03:00:00",
"key": 1431831600000,
"doc_count": 1991
},
{
"key_as_string": "2015-05-18-03:00:00",
"key": 1431918000000,
"doc_count": 2898
},
{
"key_as_string": "2015-05-19-03:00:00",
"key": 1432004400000,
"doc_count": 2895
},
{
"key_as_string": "2015-05-20-03:00:00",
"key": 1432090800000,
"doc_count": 2215
}
]
}
}
}
terms 집계는 버킷이 동적으로 생성되는 다중 버킷 집계입니다. 집계 시 지정한 필드에 대해 빈도수가 높은 텀의 순위로 결과가 반환 됩니다.
{
"aggs":{
"request_count_country":{
"terms":{
"field":"geoip.country_name.keyword"
}
}
}
}
Response:{
"took": 8,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 10001,
"max_score": 0.0,
"hits": []
},
"aggregations": {
"request_count_country": {
"doc_count_error_upper_bound": 48,
"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
},
{
"key": "India",
"doc_count": 428
},
{
"key": "China",
"doc_count": 416
},
{
"key": "United Kingdom",
"doc_count": 276
},
{
"key": "Spain",
"doc_count": 227
},
{
"key": "Canada",
"doc_count": 224
},
{
"key": "Russia",
"doc_count": 214
}
]
}
}
}
"doc_count_error_upper_bound"는 문서 수에 대한 오류 상한선입니다. 오류 상한선이 있는 이유는 각 샤드별로 성능을 고려해 근사치를 계산하기에 문서 수가 정확하지 않아 최대 오류 상한선을 보여주는 것입니다. "sum_other_doc_count"는 결과에 포함되지 않은 모든 문서 수를 뜻합니다.(size를 늘려 결과에 더 많은 집계 데이터를 포함시킬 수 있습니다.) key는 집계의 필드이고, doc_count는 각 집계 필드에 해당하는 문서 수이다.
"doc_count_error_upper_bound" 값에 대해 조금 더 자세히 보겠습니다. 집계는 각 샤드에서 집계를 한 후에 모든 결과를 병합해서 최종 집계 결과 반환하게 됩니다. 하지만 아래와 같은 상황이 있다고 가정해 보겠습니다.
샤드 A 샤드 B 샤드 C
1 Product A(25) Product A(30) Product A(45)
2 Product B(18) Product B(25) Product C(44)
3 Product C(25)
데이터 분포가 위와 같다고 가정하고 집계 시 size를 2로 지정하면 아래와 같은 결과를 반환하게 됩니다.
1 Product A(100)
2 Product B(43)
3 Product C(44)
결과는 나왔지만, Product C의 값에 오차가 생겼습니다. 즉, 쿼리 작성 시 size값을 조절해서 오차를 줄이거나 데이터 전부를 포함시켜야 합니다. 하지만 size를 키울 수록 집계 비용은 올라감을 유의해 주세요. 즉, 위의 예시의 경우 doc_count_error_upper_bound 값이 25가 될 것입니다.
집계와 샤드 크기
각 샤드는 정확성을 위해 size의 크기가 아닌 shard_size(default: size*1.5+10)의 크기를 가져와 집계를 수행합니다.
terms 집계 결과로 받을 텀의 개수를 정확하게 파악할 수 있는 경우에는 shard_size 속성을 사용해 각 샤드에서 집계할 크기를 직접 지정해 불필요한 연산을 줄이면서 정확도를 높힐 수 있습니다.
shard_size는 size보다 작을 수 없습니다. 이 경우 Elasticsearch는 이를 재정의하고 size와 같도록 재설정합니다.