Elasticsearch 마스터 — Aggregations

2026-05-03확률과 통계 마스터 노트

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_intervalminute·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_interval vs fixed_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 + 평균 한 번에

시리즈 다른 편

공식 문서: Aggregations 에서 더 깊이.

다음 글(7편)에서는 Bulk API — 대량 인덱싱·업데이트·삭제, _bulk 엔드포인트 형식, Reindex API, 성능 튜닝까지 풀어 갑니다.

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!