Elasticsearch 마스터 노트 시리즈 6편. Aggregations가 Elasticsearch를 분석 엔진으로 만드는 메커니즘, Metrics·Bucket·Pipeline 3 종 분류, terms·date_histogram·range bucket aggregations, sum·avg·percentiles metrics, sub-aggregation으로 다단 집계, Pipeline aggregation으로 집계 결과의 집계까지.
이 글은 Elasticsearch 마스터 노트 시리즈의 여섯 번째 편입니다. 1~5편이 검색이었다면, 이번엔 분석·통계 — Aggregations.
ES를 검색 엔진 + 분석 엔진으로 만드는 핵심. Kibana 대시보드의 모든 차트가 Aggregations 기반.
처음 Aggregations가 어렵게 느껴지는 이유
처음 이 단원이 어렵게 느껴지는 이유는 두 가지예요. 첫째, Metrics·Bucket·Pipeline 분류가 막연합니다. 둘째, Sub-aggregation의 중첩이 헷갈립니다.
해결법은 한 가지예요. "Bucket = 그룹화 / Metrics = 통계 / Pipeline = 결과의 결과" 한 줄. SQL의 GROUP BY = Bucket, COUNT/SUM = Metrics. 이 매핑만 잡으면 끝.
3 종 분류
Aggregations
├── Bucket Aggregations — 그룹화 (SQL GROUP BY)
├── Metrics Aggregations — 숫자 계산 (COUNT·SUM·AVG)
└── Pipeline Aggregations — 다른 집계 결과 처리
Metrics — 숫자 계산
avg·sum·min·max
GET /products/_search
{
"size": 0,
"aggs": {
"avg_price": { "avg": { "field": "price" } },
"sum_price": { "sum": { "field": "price" } },
"min_price": { "min": { "field": "price" } },
"max_price": { "max": { "field": "price" } }
}
}
# 결과
{
"aggregations": {
"avg_price": { "value": 25000 },
"sum_price": { "value": 5000000 },
...
}
}
여기서 시험 함정이 하나 있어요. size: 0 = 문서 결과 X·집계만. 집계만 필요할 때 권장 (페이로드 ↓).
stats — 한 번에 여러
"aggs": {
"price_stats": {
"stats": { "field": "price" }
}
}
# 결과 — count·min·max·avg·sum 모두
extended_stats
"aggs": {
"price_extended": {
"extended_stats": { "field": "price" }
}
}
# 결과 — stats + 분산·표준편차
cardinality — 고유 값 수 (DISTINCT COUNT)
"aggs": {
"unique_categories": {
"cardinality": { "field": "category.keyword" }
}
}
# 결과 — 고유 카테고리 수
여기서 정말 중요한 시험 함정 — cardinality는 근사치 (HyperLogLog++). 정확히 X·매우 빠름·메모리 효율. 일반 통계용엔 충분.
percentiles
"aggs": {
"price_percentiles": {
"percentiles": {
"field": "price",
"percents": [50, 75, 95, 99]
}
}
}
# 결과 — p50·p75·p95·p99
p50 (중앙값), p99 (상위 1%) 등.
Bucket — 그룹화
terms — 카테고리별
GET /products/_search
{
"size": 0,
"aggs": {
"categories": {
"terms": {
"field": "category.keyword",
"size": 10
}
}
}
}
# 결과
{
"aggregations": {
"categories": {
"buckets": [
{ "key": "IT", "doc_count": 1500 },
{ "key": "Books", "doc_count": 800 },
...
]
}
}
}
여기서 정말 중요한 시험 함정 — field = keyword·numeric·date (text X). text는 분석되어 토큰별로 그룹화 = 의도와 다름. text의 keyword multi-field 사용.
date_histogram — 시간 그룹
GET /products/_search
{
"size": 0,
"aggs": {
"by_month": {
"date_histogram": {
"field": "created_at",
"calendar_interval": "month",
"time_zone": "Asia/Seoul"
}
}
}
}
# 결과 — 월별 문서 수
calendar_interval — minute·hour·day·week·month·quarter·year.
"date_histogram": {
"field": "created_at",
"fixed_interval": "30d" # 정확히 30일
}
여기서 시험 함정이 하나 있어요. calendar_interval vs fixed_interval. calendar = 달력 단위 (월의 일 수 다름), fixed = 정확한 단위. 일반 = calendar.
histogram — 숫자 범위
"aggs": {
"price_histogram": {
"histogram": {
"field": "price",
"interval": 10000
}
}
}
# 결과 — 0~10000·10000~20000·...
range — 명시적 범위
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "to": 10000 },
{ "from": 10000, "to": 50000 },
{ "from": 50000 }
]
}
}
}
각 구간 이름 붙이기:
"ranges": [
{ "key": "저가", "to": 10000 },
{ "key": "중가", "from": 10000, "to": 50000 },
{ "key": "고가", "from": 50000 }
]
filter / filters
"aggs": {
"premium_products": {
"filter": { "term": { "premium": true } },
"aggs": {
"avg_price": { "avg": { "field": "price" } }
}
}
}
특정 조건 문서만 → 그 안에서 집계.
Sub-aggregation — 중첩
GET /products/_search
{
"size": 0,
"aggs": {
"by_category": {
"terms": { "field": "category.keyword" },
"aggs": {
"avg_price": { "avg": { "field": "price" } },
"by_brand": {
"terms": { "field": "brand.keyword" },
"aggs": {
"max_price": { "max": { "field": "price" } }
}
}
}
}
}
}
흐름:
카테고리별 그룹
├── 각 카테고리 평균 가격
└── 카테고리 안 브랜드별 그룹
└── 각 브랜드 최대 가격
여기서 정말 중요한 시험 함정 — Sub-aggregation = SQL 다중 GROUP BY. 중첩 무한 가능 (성능 한계 안에서).
Pipeline Aggregations
다른 집계 결과 처리:
avg_bucket
"aggs": {
"by_month": {
"date_histogram": {
"field": "created_at",
"calendar_interval": "month"
},
"aggs": {
"monthly_revenue": { "sum": { "field": "amount" } }
}
},
"avg_monthly_revenue": {
"avg_bucket": {
"buckets_path": "by_month>monthly_revenue"
}
}
}
월별 수익 → 월별 평균. 이미 집계된 결과의 평균.
derivative — 변화량
"aggs": {
"by_day": {
"date_histogram": {...},
"aggs": {
"sales": { "sum": { "field": "amount" } },
"sales_diff": {
"derivative": { "buckets_path": "sales" }
}
}
}
}
전일 대비 증감.
moving_avg — 이동 평균
"sales_moving_avg": {
"moving_avg": {
"buckets_path": "sales",
"window": 5
}
}
5일 이동 평균.
여기서 시험 함정이 하나 있어요. Pipeline은 시계열 분석 강력. 일별 트렌드·이상 탐지·예측 등.
bucket_sort·bucket_selector
bucket_sort
"aggs": {
"categories": {
"terms": { "field": "category.keyword" },
"aggs": {
"total_revenue": { "sum": { "field": "price" } },
"sort_by_revenue": {
"bucket_sort": {
"sort": [{ "total_revenue": "desc" }],
"size": 5
}
}
}
}
}
집계 결과 정렬·top N.
bucket_selector — 필터링
"sort_by_revenue": {
"bucket_selector": {
"buckets_path": { "revenue": "total_revenue" },
"script": "params.revenue > 100000"
}
}
조건 만족 bucket만.
Top Hits — Bucket 안 문서
"aggs": {
"by_category": {
"terms": { "field": "category.keyword" },
"aggs": {
"top_3": {
"top_hits": {
"size": 3,
"sort": [{ "price": "desc" }]
}
}
}
}
}
각 카테고리 가장 비싼 3 상품. SQL의 ROW_NUMBER 비슷.
실전 — 대시보드 데이터
GET /orders/_search
{
"size": 0,
"aggs": {
"daily_revenue": {
"date_histogram": {
"field": "ordered_at",
"calendar_interval": "day"
},
"aggs": {
"revenue": { "sum": { "field": "amount" } },
"unique_customers": {
"cardinality": { "field": "customer_id" }
},
"avg_order_value": { "avg": { "field": "amount" } }
}
},
"top_products": {
"terms": {
"field": "product_id",
"size": 10
},
"aggs": {
"total_sold": { "sum": { "field": "quantity" } }
}
}
}
}
일별 수익·고유 고객·평균 주문가·인기 상품 한 번에. Kibana 대시보드 표준.
성능 — Aggregations vs 검색
여기서 정말 중요한 시험 함정 — 큰 cardinality terms = 느림·메모리 폭주. size: 1000 같은 거 X. 권장 = 100 이하 또는 composite aggregation (페이징).
# 큰 결과 페이징
"aggs": {
"categories": {
"composite": {
"size": 100,
"sources": [
{ "category": { "terms": { "field": "category.keyword" } } }
]
}
}
}
시험 직전 한 번 더 — 자주 헷갈리는 함정 모음
여기까지가 6편의 핵심입니다. 시험 직전 또는 실무에서 헷갈릴 때 다시 펼쳐 볼 수 있게 압축 노트로 마무리할게요.
- 3 종 — Metrics (계산) / Bucket (그룹) / Pipeline (결과의 결과)
size: 0= 집계만, 문서 X- Metrics — avg·sum·min·max·stats·extended_stats·cardinality·percentiles
- cardinality = 근사치 (HyperLogLog++, 빠름)
- percentiles — p50·p95·p99 등
- Bucket — terms (카테고리) / date_histogram (시간) / histogram (숫자) / range (명시 범위) / filter / filters
- terms는 keyword·numeric·date만 (text X — multi-field 사용)
calendar_intervalvsfixed_interval— 달력 단위 vs 정확 단위- 일반 = calendar
- Sub-aggregation = SQL 다중 GROUP BY (중첩 무한)
- Pipeline — avg_bucket·derivative·moving_avg
- 시계열 분석·트렌드·예측
bucket_sort= top N /bucket_selector= 조건 필터- Top Hits = bucket 안 문서 (SQL ROW_NUMBER)
- 운영 — 큰 cardinality terms = 느림·메모리 폭주
- 권장 size 100 이하 또는
composite aggregation(페이징) - 대시보드 — 일별 + 누적 + top N + 평균 한 번에
시리즈 다른 편
- 1편 — 기본 개념·Cluster·Shard
- 2편 — Mapping·데이터 타입
- 3편 — Analyzer·Tokenizer·한국어
- 4편 — Query DSL
- 5편 — Full-text Search·Relevance
- 6편 — Aggregations (현재 글)
- 7편 — Bulk API·Reindex
- 8편 — Spring Data Elasticsearch
- 9편 — 검색 엔진 프로젝트 설계
- 10편 — Security·인증·인가·TLS
공식 문서: Aggregations 에서 더 깊이.
다음 글(7편)에서는 Bulk API — 대량 인덱싱·업데이트·삭제, _bulk 엔드포인트 형식, Reindex API, 성능 튜닝까지 풀어 갑니다.