Intro::
Elasticsearch가 다양한 목적의 데이터 시스템으로 사용되는 이유인 Aggregation 기능에 대해 알아봅시다.
// 테스트를 위한 my_stations 인덱스에 데이터 입력
PUT my_stations/_bulk
{"index": {"_id": "1"}}
{"date": "2019-06-01", "line": "1호선", "station": "종각", "passangers": 2314}
{"index": {"_id": "2"}}
{"date": "2019-06-01", "line": "2호선", "station": "강남", "passangers": 5412}
{"index": {"_id": "3"}}
{"date": "2019-07-10", "line": "2호선", "station": "강남", "passangers": 6221}
{"index": {"_id": "4"}}
{"date": "2019-07-15", "line": "2호선", "station": "강남", "passangers": 6478}
{"index": {"_id": "5"}}
{"date": "2019-08-07", "line": "2호선", "station": "강남", "passangers": 5821}
{"index": {"_id": "6"}}
{"date": "2019-08-18", "line": "2호선", "station": "강남", "passangers": 5724}
{"index": {"_id": "7"}}
{"date": "2019-09-02", "line": "2호선", "station": "신촌", "passangers": 3912}
{"index": {"_id": "8"}}
{"date": "2019-09-11", "line": "3호선", "station": "양재", "passangers": 4121}
{"index": {"_id": "9"}}
{"date": "2019-09-20", "line": "3호선", "station": "홍제", "passangers": 1021}
{"index": {"_id": "10"}}
{"date": "2019-10-01", "line": "3호선", "station": "불광", "passangers": 971}
JSON
복사
메트릭 - Metrics Aggregations
min, max, sum, avg
// my_stations 인덱스의 passangers 필드 합 (sum) 을 가져오는 aggs
GET my_stations/_search
{
"size": 0,
"aggs": {
"all_passangers": {
"sum": {
"field": "passangers"
}
}
}
}
JSON
복사
말 드대로 최소, 최대, 합, 평균을 구해준다. 이때 쿼리의 영향을 받는다는 점을 인지하고 있어야 합니다.
stats
min, max, sum, avg 모두 가져와야 한다면 stats aggregation을 사용하면 됩니다.
// stats 로 passangers 필드의 min, max, sum, avg 값을 가져오는 aggs
GET my_stations/_search
{
"size": 0,
"aggs": {
"passangers_stats": {
"stats": {
"field": "passangers"
}
}
}
}
JSON
복사
cardinality
필드의 값이 모두 몇 종류인지 분포값을 알려면 cardinality aggregation을 사용해서 구할 수 있습니다. Cardinality는 일반적으로 text 필드에서는 사용할 수 없으며 숫자 필드나 keyword, ip 필드 등에 사용이 가능합니다. 사용자 접속 로그에서 IP주소 필드를 가지고 실제로 접속한 사용자가 몇명인지 파악하는 등의 용도로 주로 사용됩니다.
// line 필드의 값이 몇 종류인지를 가져오는 aggs
GET my_stations/_search
{
"size": 0,
"aggs": {
"uniq_lines ": {
"cardinality": {
"field": "line.keyword"
}
}
}
}
JSON
복사
percentiles, percentile_ranks
값들을 백분위 별로 볼때 percentiles aggregation이 사용 가능합니다. percentiles aggregation은 디폴트로 1%, 5%, 25%, 50%, 75%, 95%, 99% 구간에 위치 해 있는 값들을 표시 해 줍니다. 백분위 구간을 직접 지정하고 싶으면 percents 옵션을 이용해서 지정이 가능합니다. 다음은 20%, 60%, 80% 백분위의 값을 가져오는 percentiles aggregation 입니다.
// passangers 필드의 백분위를 가져오는 aggs
GET my_stations/_search
{
"size": 0,
"aggs": {
"pass_percentiles": {
"percentiles": {
"field": "passangers"
//"percents": [ 20, 60, 80 ]
}
}
}
}
// 결과
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 10,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"pass_percentiles" : {
"values" : {
"20.0" : 1667.5,
"60.0" : 5568.0,
"80.0" : 6021.0
}
}
}
}
JSON
복사
percentile_ranks aggregation을 이용하면 반대로 값을 입력해서 그 값이 위치 해 있는 백분위를 볼 수 있습니다.
// passangers 필드의 값을 지정해서 백분위를 가져오는 aggs
GET my_stations/_search
{
"size": 0,
"aggs": {
"pass_percentile_ranks": {
"percentile_ranks": {
"field": "passangers",
"values": [ 1000, 3000, 6000 ]
}
}
}
}
// 결과
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 10,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"pass_percentile_ranks" : {
"values" : {
"1000.0" : 10.059568131049886,
"3000.0" : 29.218263576617087,
"6000.0" : 79.1549295774648
}
}
}
}
JSON
복사
버킷 - Bucket Aggregations
range
range는 숫자 필드값으로 범위를 지정하고 각 범위에 해당하는 버킷을 만드는 aggregation입니다. field 옵션에 해당 필드의 이름을 지정하고 ranges 옵션에 배열로 from, to 값(이상, 미만)을 가진 오브젝트 값을 나열해서 범위를 지정합니다.
// range aggs 를 이용해서 passangers 필드의 값을 버킷으로 구분
GET my_stations/_search
{
"size": 0,
"aggs": {
"passangers_range": {
"range": {
"field": "passangers",
"ranges": [
{
"to": 1000
},
{
"from": 1000,
"to": 4000
},
{
"from": 4000
}
]
}
}
}
}
// 결과
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 10,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"passangers_range" : {
"buckets" : [
{
"key" : "*-1000.0",
"to" : 1000.0,
"doc_count" : 1
},
{
"key" : "1000.0-4000.0",
"from" : 1000.0,
"to" : 4000.0,
"doc_count" : 3
},
{
"key" : "4000.0-*",
"from" : 4000.0,
"doc_count" : 6
}
]
}
}
}
JSON
복사
histogram
range 와는 조금 다르게 interval 옵션을 주어 버킷을 구분합니다. 결과는 range와 유사합니다.
// histogram aggs 를 이용해서 passangers 필드의 값을 버킷으로 구분
GET my_stations/_search
{
"size": 0,
"aggs": {
"passangers_his": {
"histogram": {
"field": "passangers",
"interval": 2000
}
}
}
}
JSON
복사
date_range, date_histogram
시계열 데이터에서 날짜별로 값을 표기할 때 유용한 aggregation입니다.
// date_histogram 을 이용해서 date 값을 1개월 간격의 버킷으로 구분
GET my_stations/_search
{
"size": 0,
"aggs": {
"date_his": {
"date_histogram": {
"field": "date",
"interval": "month"
}
}
}
}
JSON
복사
terms
keyword 필드의 문자열 별로 버킷을 나누어 집계가 가능합니다.
// terms 을 이용해서 station 값을 별로 버킷 생성
GET my_stations/_search
{
"size": 0,
"aggs": {
"stations": {
"terms": {
"field": "station.keyword"
}
}
}
}
// 결
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 10,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"stations" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "강남",
"doc_count" : 5
},
{
"key" : "불광",
"doc_count" : 1
},
{
"key" : "신촌",
"doc_count" : 1
},
{
"key" : "양재",
"doc_count" : 1
},
{
"key" : "종각",
"doc_count" : 1
},
{
"key" : "홍제",
"doc_count" : 1
}
]
}
}
}
JSON
복사
하위 - sub-aggregations
Bucket Aggregation 으로 만든 버킷들 내부에 다시 "aggs" : { } 를 선언해서 또다른 버킷을 만들거나 Metrics Aggregation 을 만들어 사용이 가능합니다.
// terms aggs 아래에 avg aggs 사용
GET my_stations/_search
{
"size": 0,
"aggs": {
"stations": {
"terms": {
"field": "station.keyword"
},
"aggs": {
"avg_psg_per_st": {
"avg": {
"field": "passangers"
}
}
}
}
}
}
JSON
복사
// terms aggs 아래에 하위 terms aggs 사용
GET my_stations/_search
{
"size": 0,
"aggs": {
"lines": {
"terms": {
"field": "line.keyword"
},
"aggs": {
"stations_per_lines": {
"terms": {
"field": "station.keyword"
}
}
}
}
}
}
// 결과
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 10,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"lines" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "2호선",
"doc_count" : 6,
"stations_per_lines" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "강남",
"doc_count" : 5
},
{
"key" : "신촌",
"doc_count" : 1
}
]
}
},
{
"key" : "3호선",
"doc_count" : 3,
"stations_per_lines" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "불광",
"doc_count" : 1
},
{
"key" : "양재",
"doc_count" : 1
},
{
"key" : "홍제",
"doc_count" : 1
}
]
}
},
{
"key" : "1호선",
"doc_count" : 1,
"stations_per_lines" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "종각",
"doc_count" : 1
}
]
}
}
]
}
}
}
JSON
복사
파이프라인 - Pipeline Aggregations
Aggregation 중에는 다른 metrics aggregation의 결과를 새로운 입력으로 하는 pipeline aggregation이 있습니다. pipeline 에는 다른 버킷의 결과들을 다시 연산하는 min_bucket, max_bucket, avg_bucket, sum_bucket, stats_bucket, 이동 평균을 구하는 moving_avg, 미분값을 구하는 derivative, 값의 누적 합을 구하는 cumulative_sum 등이 있습니다. Pipeline aggregation 은 "buckets_path": "<버킷 이름>" 옵션을 이용해서 입력 값으로 사용할 버킷을 지정합니다.
// passangers 의 값을 입력으로 받는 cumulative_sum aggs 실행
GET my_stations/_search
{
"size": 0,
"aggs": {
"months": {
"date_histogram": {// 한달별로 집계
"field": "date",
"interval": "month"
},
"aggs": {
"sum_psg": {// 승객 합
"sum": {
"field": "passangers"
}
},
"accum_sum_psg": {
"cumulative_sum": {// 누적
"buckets_path": "sum_psg"// 승객합을 파이프 라인으로 사용
}
}
}
}
}
}
JSON
복사
서로 다른 버킷에 있는 값들도 bucket_path에 > 기호를 이용해서 "부모>자녀" 형태로 지정이 가능합니다. 다음은 sum_bucket 을 이용해서 mon>sum_psg 버킷에 있는 passangers 필드값의 합을 구하는 예제입니다.
// 다른 부모의 자녀 버킷에 있는 필드를 입력으로 받는 pipeline aggs
GET my_stations/_search
{
"size": 0,
"aggs": {
"mon": {
"date_histogram": {
"field": "date",
"interval": "month"
},
"aggs": {
"sum_psg": {
"sum": {
"field": "passangers"
}
}
}
},
"bucket_sum_psg": {
"sum_bucket": {
"buckets_path": "mon>sum_psg"
}
}
}
}
// 결과
{
"took" : 4,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 10,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"mon" : {
"buckets" : [
{
"key_as_string" : "2019-06-01T00:00:00.000Z",
"key" : 1559347200000,
"doc_count" : 2,
"sum_psg" : {
"value" : 7726.0
}
},
{
"key_as_string" : "2019-07-01T00:00:00.000Z",
"key" : 1561939200000,
"doc_count" : 2,
"sum_psg" : {
"value" : 12699.0
}
},
{
"key_as_string" : "2019-08-01T00:00:00.000Z",
"key" : 1564617600000,
"doc_count" : 2,
"sum_psg" : {
"value" : 11545.0
}
},
{
"key_as_string" : "2019-09-01T00:00:00.000Z",
"key" : 1567296000000,
"doc_count" : 3,
"sum_psg" : {
"value" : 9054.0
}
},
{
"key_as_string" : "2019-10-01T00:00:00.000Z",
"key" : 1569888000000,
"doc_count" : 1,
"sum_psg" : {
"value" : 971.0
}
}
]
},
"bucket_sum_psg" : {
"value" : 41995.0
}
}
}
JSON
복사