본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 11. 29. · 20 Views
Elasticsearch 집계(Aggregations) 기초 완벽 가이드
Elasticsearch에서 데이터를 분석하고 통계를 내는 핵심 기능인 집계(Aggregations)를 초급 개발자도 쉽게 이해할 수 있도록 설명합니다. 평균, 합계부터 시계열 분석까지 실무에서 바로 활용할 수 있는 예제와 함께 배워봅니다.
목차
1. 집계 쿼리 구조
김개발 씨는 이커머스 회사에 입사한 지 한 달이 되었습니다. 어느 날 팀장님이 다가와 말했습니다.
"이번 달 판매 데이터 통계 좀 뽑아줄 수 있어요? Elasticsearch에 다 들어있으니까요." 김개발 씨는 검색은 해봤지만, 통계를 뽑는 건 처음이라 막막했습니다.
**집계(Aggregations)**는 Elasticsearch에서 데이터를 검색하는 것을 넘어, 데이터를 분석하고 통계를 계산하는 강력한 기능입니다. 마치 엑셀의 피벗 테이블처럼 대량의 데이터를 요약하고 패턴을 찾아낼 수 있습니다.
집계를 이해하면 단순한 검색 엔진을 넘어 실시간 분석 도구로 Elasticsearch를 활용할 수 있습니다.
다음 코드를 살펴봅시다.
// Elasticsearch 집계 쿼리의 기본 구조
const searchQuery = {
// 검색 조건 (선택사항)
query: {
match: { status: "completed" }
},
// 집계 정의 시작
aggs: {
// 집계 이름 (원하는 대로 지정)
my_aggregation_name: {
// 집계 타입과 설정
terms: {
field: "category.keyword"
}
}
},
// 검색 결과는 제외하고 집계만 반환
size: 0
};
김개발 씨는 Elasticsearch 문서를 펼쳐 들었습니다. 검색은 match, bool 같은 쿼리로 하는 건 알겠는데, 통계는 어떻게 내는 걸까요?
선배 개발자 박시니어 씨가 커피를 건네며 말했습니다. "집계를 쓰면 돼요.
SQL의 GROUP BY와 집계 함수를 합쳐놓은 거라고 생각하면 편해요." 그렇다면 **집계(Aggregations)**란 정확히 무엇일까요? 쉽게 비유하자면, 집계는 마치 도서관 사서가 책을 분류하고 통계를 내는 것과 같습니다.
사서는 "이번 달에 대출된 책이 총 몇 권인지", "어떤 장르가 가장 인기 있는지", "요일별로 대출 건수가 어떻게 되는지"를 파악할 수 있습니다. Elasticsearch의 집계도 마찬가지로 대량의 데이터에서 이런 통계 정보를 뽑아냅니다.
집계 쿼리의 구조는 크게 세 부분으로 나뉩니다. 첫 번째는 query 부분입니다.
이 부분은 선택사항인데, 집계할 데이터의 범위를 지정합니다. 예를 들어 "완료된 주문만 대상으로 통계를 내고 싶다"면 여기에 조건을 넣습니다.
생략하면 전체 데이터를 대상으로 집계합니다. 두 번째는 aggs 블록입니다.
이곳이 바로 집계의 핵심입니다. aggs 안에는 집계 이름을 자유롭게 지정할 수 있습니다.
my_aggregation_name처럼요. 이 이름은 결과에서 해당 집계를 식별하는 데 사용됩니다.
세 번째는 집계 타입과 설정입니다. terms, avg, sum 같은 집계 타입을 지정하고, 어떤 필드를 대상으로 할지 설정합니다.
위 코드를 자세히 살펴보겠습니다. query 부분에서는 status가 completed인 문서만 대상으로 지정했습니다.
aggs 블록 안의 my_aggregation_name은 우리가 정한 집계 이름입니다. terms 집계는 category.keyword 필드의 값별로 문서를 그룹화합니다.
여기서 중요한 팁이 있습니다. size: 0을 설정하면 검색 결과(hits)는 반환하지 않고 집계 결과만 받습니다.
통계만 필요할 때 이렇게 하면 응답 속도가 훨씬 빨라집니다. 실무에서는 대시보드를 만들 때 이 구조를 많이 사용합니다.
예를 들어 실시간 판매 현황판에서 "오늘 매출 총액", "카테고리별 판매량", "시간대별 주문 추이"를 보여줘야 한다면, 각각을 집계 쿼리로 작성하면 됩니다. 박시니어 씨가 덧붙였습니다.
"집계 이름은 의미 있게 지어야 나중에 결과를 파싱할 때 헷갈리지 않아요. total_sales, category_breakdown 이런 식으로요." 김개발 씨는 고개를 끄덕였습니다.
집계의 기본 구조가 이해되기 시작했습니다.
실전 팁
💡 - 집계만 필요하면 size: 0으로 설정하여 성능을 높이세요
- 집계 이름은 결과 파싱을 고려해 의미 있게 지정하세요
- 문자열 필드는 .keyword 서브필드를 사용해야 집계가 가능합니다
2. Metric 집계 avg sum
김개발 씨가 집계의 기본 구조를 익히자, 팀장님이 첫 번째 미션을 던졌습니다. "이번 달 주문의 평균 금액과 총 매출액을 알려줘요." 숫자를 계산해야 하는 상황입니다.
SQL이라면 AVG와 SUM 함수를 쓰겠지만, Elasticsearch에서는 어떻게 할까요?
Metric 집계는 숫자 데이터를 계산하는 집계입니다. avg(평균), sum(합계), min(최솟값), max(최댓값), value_count(개수) 등이 있습니다.
SQL의 집계 함수와 비슷하지만, Elasticsearch는 분산 환경에서 대용량 데이터를 빠르게 계산한다는 장점이 있습니다.
다음 코드를 살펴봅시다.
// Metric 집계: 평균과 합계 계산하기
const metricQuery = {
query: {
range: {
order_date: {
gte: "2024-01-01",
lte: "2024-01-31"
}
}
},
aggs: {
// 평균 주문 금액
average_order_amount: {
avg: { field: "amount" }
},
// 총 매출액
total_sales: {
sum: { field: "amount" }
},
// 최고 주문 금액
max_order: {
max: { field: "amount" }
}
},
size: 0
};
김개발 씨는 주문 데이터가 담긴 orders 인덱스를 열어보았습니다. 각 주문에는 amount(금액), order_date(주문일), customer_id(고객 ID) 같은 필드가 있었습니다.
박시니어 씨가 옆에서 설명을 시작했습니다. "Elasticsearch 집계는 크게 두 종류로 나뉘어요.
숫자를 계산하는 Metric 집계와 데이터를 그룹으로 묶는 Bucket 집계요. 지금 필요한 건 Metric 집계예요." Metric 집계는 마치 계산기와 같습니다.
숫자 데이터를 입력받아 평균, 합계, 최솟값, 최댓값 같은 결과를 내놓습니다. 차이점이라면, 이 계산기는 수백만 건의 데이터도 순식간에 처리한다는 것입니다.
위 코드를 살펴보겠습니다. query 부분에서 range를 사용해 2024년 1월의 주문만 대상으로 지정했습니다.
그리고 aggs 블록 안에 세 가지 Metric 집계를 정의했습니다. avg 집계는 지정한 필드의 평균값을 계산합니다.
average_order_amount라는 이름으로 amount 필드의 평균을 구했습니다. 이 쿼리를 실행하면 1월 주문의 평균 금액을 알 수 있습니다.
sum 집계는 합계를 계산합니다. total_sales라는 이름으로 전체 매출액을 구했습니다.
1월에 발생한 모든 주문 금액을 더한 값이 나옵니다. max 집계는 최댓값을 반환합니다.
가장 큰 주문 금액이 얼마인지 알 수 있습니다. 마찬가지로 min을 사용하면 최솟값을 구할 수 있습니다.
한 가지 중요한 점은, 여러 Metric 집계를 한 번의 쿼리로 동시에 실행할 수 있다는 것입니다. 위 예제처럼 평균, 합계, 최댓값을 각각 따로 쿼리하지 않고 한 번에 요청하면 네트워크 비용을 줄일 수 있습니다.
이 쿼리의 응답은 다음과 같은 형태로 돌아옵니다. aggregations 객체 안에 우리가 정한 이름들이 키로 들어있고, 각각의 value에 계산 결과가 담깁니다.
실무에서 Metric 집계는 대시보드의 핵심 지표를 표시할 때 많이 사용합니다. "오늘의 총 매출", "평균 응답 시간", "최대 동시 접속자 수" 같은 숫자들이 바로 Metric 집계로 계산됩니다.
주의할 점도 있습니다. Metric 집계는 숫자 타입 필드에만 사용할 수 있습니다.
문자열 필드에 avg를 적용하면 에러가 발생합니다. 또한 null 값은 계산에서 자동으로 제외됩니다.
김개발 씨는 쿼리를 실행해보았습니다. 화면에 평균 주문 금액 45,000원, 총 매출 150,000,000원이라는 결과가 떴습니다.
팀장님이 원하던 바로 그 정보였습니다.
실전 팁
💡 - 여러 Metric 집계를 한 쿼리에 묶어서 네트워크 비용을 줄이세요
- stats 집계를 사용하면 count, min, max, avg, sum을 한 번에 얻을 수 있습니다
- missing 파라미터로 null 값의 기본값을 지정할 수 있습니다
3. Bucket 집계 terms range
팀장님이 만족한 표정으로 다음 요청을 했습니다. "이제 카테고리별로 판매량이 어떻게 되는지, 그리고 가격대별로 상품이 몇 개인지도 알고 싶어요." 김개발 씨는 데이터를 그룹으로 나눠야 한다는 걸 깨달았습니다.
바로 Bucket 집계가 필요한 순간입니다.
Bucket 집계는 데이터를 특정 기준에 따라 그룹(버킷)으로 나누는 집계입니다. terms 집계는 필드의 고유한 값별로 그룹을 만들고, range 집계는 숫자 범위별로 그룹을 만듭니다.
SQL의 GROUP BY와 유사하지만 더 유연한 그룹화가 가능합니다.
다음 코드를 살펴봅시다.
// Bucket 집계: terms와 range 활용하기
const bucketQuery = {
aggs: {
// 카테고리별 그룹화 (terms)
by_category: {
terms: {
field: "category.keyword",
size: 10, // 상위 10개 카테고리
order: { _count: "desc" } // 문서 수 기준 정렬
}
},
// 가격대별 그룹화 (range)
price_ranges: {
range: {
field: "price",
ranges: [
{ key: "저가", to: 10000 },
{ key: "중가", from: 10000, to: 50000 },
{ key: "고가", from: 50000 }
]
}
}
},
size: 0
};
박시니어 씨가 화이트보드에 그림을 그리며 설명했습니다. "Metric 집계가 계산기라면, Bucket 집계는 분류함이에요.
데이터를 조건에 따라 여러 상자에 나눠 담는 거죠." Bucket 집계를 이해하려면 실제 분류 작업을 떠올리면 됩니다. 도서관에서 책을 분류한다고 생각해봅시다.
"장르별로 나누기"를 하면 소설, 과학, 역사 등의 상자가 생깁니다. "출판연도 범위별로 나누기"를 하면 1990년대, 2000년대, 2010년대 상자가 생깁니다.
Bucket 집계도 똑같이 동작합니다. terms 집계부터 살펴보겠습니다.
이 집계는 지정한 필드의 고유한 값들을 찾아 각각을 하나의 버킷으로 만듭니다. 위 코드에서 category.keyword 필드로 terms 집계를 했으니, "전자제품", "의류", "식품" 같은 카테고리별로 버킷이 생성됩니다.
terms 집계에서 size 파라미터는 상위 몇 개의 버킷을 반환할지 지정합니다. 기본값은 10이며, 카테고리가 수백 개라도 상위 10개만 반환합니다.
필요하면 늘릴 수 있지만, 너무 크게 설정하면 성능에 영향을 줄 수 있습니다. order 파라미터로 정렬 기준을 바꿀 수 있습니다.
_count는 문서 수 기준, _key는 키값(카테고리명) 기준입니다. desc는 내림차순, asc는 오름차순입니다.
다음으로 range 집계를 보겠습니다. 이 집계는 숫자 필드를 지정한 범위별로 그룹화합니다.
위 코드에서는 가격을 "10,000원 미만", "10,000원 이상 50,000원 미만", "50,000원 이상"으로 나누었습니다. range에서 to는 해당 값 미만(exclusive), from은 해당 값 이상(inclusive)을 의미합니다.
key를 지정하면 결과에서 해당 범위를 식별하기 쉬운 이름으로 표시됩니다. 이 쿼리를 실행하면 응답에 각 버킷과 해당 버킷에 속한 문서 수(doc_count)가 함께 반환됩니다.
예를 들어 "전자제품" 버킷에 1,234개, "의류" 버킷에 987개 이런 식으로요. 실무에서 terms 집계는 카테고리별 통계, 지역별 분포, 상태별 현황 등을 파악할 때 사용합니다.
range 집계는 나이대별 사용자 분포, 가격대별 상품 현황, 점수 구간별 학생 수 등에 활용됩니다. 주의할 점이 있습니다.
terms 집계는 keyword 타입 필드에 사용해야 합니다. text 타입 필드에 직접 사용하면 분석된 토큰별로 버킷이 생겨 원하지 않는 결과가 나옵니다.
그래서 category가 아닌 category.keyword를 사용한 것입니다. 김개발 씨는 쿼리 결과를 보며 감탄했습니다.
"전자제품"이 가장 많이 팔리고, "중가" 상품이 전체의 60%를 차지한다는 사실을 한눈에 알 수 있었습니다.
실전 팁
💡 - terms 집계에서 size는 기본 10이므로, 더 많은 버킷이 필요하면 명시적으로 지정하세요
- 문자열 필드는 반드시 .keyword 서브필드를 사용하세요
- range의 key를 지정하면 결과 파싱이 훨씬 편해집니다
4. date histogram 시계열
팀장님의 요구사항은 끝이 없었습니다. "시간대별로 주문이 어떻게 들어오는지 추이를 보고 싶어요.
일별로, 월별로도요." 김개발 씨는 시간 축을 기준으로 데이터를 나눠야 한다는 걸 알았습니다. 바로 date_histogram이 필요한 상황입니다.
date_histogram 집계는 날짜/시간 필드를 기준으로 데이터를 일정한 시간 간격으로 그룹화합니다. 일별, 주별, 월별, 시간별 등 다양한 간격으로 시계열 데이터를 분석할 수 있습니다.
로그 분석, 매출 추이, 사용자 활동 패턴 파악 등에 필수적인 집계입니다.
다음 코드를 살펴봅시다.
// date_histogram 집계: 시계열 분석
const timeSeriesQuery = {
query: {
range: {
order_date: {
gte: "2024-01-01",
lte: "2024-12-31"
}
}
},
aggs: {
// 월별 주문 추이
monthly_orders: {
date_histogram: {
field: "order_date",
calendar_interval: "month", // 월 단위
format: "yyyy-MM", // 날짜 포맷
min_doc_count: 0, // 0건인 달도 표시
extended_bounds: { // 범위 고정
min: "2024-01-01",
max: "2024-12-31"
}
}
},
// 시간대별 주문 패턴
hourly_pattern: {
date_histogram: {
field: "order_date",
fixed_interval: "1h" // 1시간 단위
}
}
},
size: 0
};
박시니어 씨가 모니터를 가리키며 말했습니다. "매출 그래프, 트래픽 추이, 사용자 증가 곡선...
이런 시계열 차트를 본 적 있죠? 그 데이터를 뽑는 게 바로 date_histogram 집계예요." date_histogram은 마치 달력에 스티커를 붙이는 것과 같습니다.
1월에 주문 10건, 2월에 15건, 3월에 20건... 이렇게 시간 축을 따라 데이터를 정리합니다.
이 데이터를 차트 라이브러리에 넘기면 멋진 그래프가 완성됩니다. 위 코드에서 두 가지 시간 간격 설정을 볼 수 있습니다.
첫 번째는 calendar_interval입니다. "month", "week", "day", "year" 같은 달력 기준 간격을 지정합니다.
1월은 31일, 2월은 28일(또는 29일)처럼 실제 달력의 불규칙한 간격을 자동으로 처리해줍니다. 두 번째는 fixed_interval입니다.
"1h"(1시간), "30m"(30분), "1d"(24시간) 같은 고정된 시간 단위를 지정합니다. 정확히 동일한 간격이 필요할 때 사용합니다.
format 파라미터는 응답에서 날짜가 표시되는 형식을 지정합니다. "yyyy-MM"으로 설정하면 "2024-01", "2024-02" 형태로 반환됩니다.
중요한 옵션이 하나 더 있습니다. min_doc_count: 0입니다.
기본적으로 date_histogram은 데이터가 없는 구간을 생략합니다. 하지만 차트를 그릴 때는 빈 구간도 0으로 표시해야 할 때가 많습니다.
이 옵션을 0으로 설정하면 데이터가 없는 달도 결과에 포함됩니다. extended_bounds는 결과의 시작과 끝 범위를 강제로 지정합니다.
데이터가 3월부터 시작하더라도 1월부터 보여주고 싶을 때 사용합니다. min_doc_count: 0과 함께 사용하면 완전한 시간 축을 만들 수 있습니다.
실무에서 date_histogram은 정말 자주 사용됩니다. 일별 매출 추이, 시간대별 서버 부하, 월별 신규 가입자 수, 주간 활성 사용자 변화...
시간과 관련된 거의 모든 통계에 이 집계가 필요합니다. 로그 분석에서도 핵심 역할을 합니다.
"어제 몇 시에 에러가 급증했나", "이번 주 트래픽 패턴은 어떤가" 같은 질문에 답할 수 있습니다. 김개발 씨는 date_histogram 결과를 차트 라이브러리에 연결했습니다.
화면에 깔끔한 막대 그래프가 나타났습니다. 팀장님이 흡족한 미소를 지었습니다.
"오, 12월에 매출이 확 뛰었네요. 연말 프로모션 효과군요!"
실전 팁
💡 - 달력 기준 간격은 calendar_interval, 고정 간격은 fixed_interval을 사용하세요
- 차트용 데이터라면 min_doc_count: 0과 extended_bounds를 함께 사용하세요
- 타임존이 중요하다면 time_zone 파라미터로 명시적으로 지정하세요
5. 중첩 집계 활용
김개발 씨가 팀장님께 보고서를 보여드리자, 새로운 요청이 들어왔습니다. "카테고리별로 나누고, 각 카테고리의 평균 가격도 함께 보여줄 수 있어요?" 그룹을 나눈 다음 각 그룹 안에서 또 계산을 해야 하는 상황입니다.
바로 중첩 집계가 필요합니다.
**중첩 집계(Nested Aggregations)**는 하나의 집계 안에 다른 집계를 넣는 것입니다. Bucket 집계로 그룹을 나눈 뒤, 각 버킷 내부에서 Metric 집계나 또 다른 Bucket 집계를 수행할 수 있습니다.
이를 통해 "카테고리별 평균 가격", "월별-일별 판매량" 같은 복잡한 분석이 가능해집니다.
다음 코드를 살펴봅시다.
// 중첩 집계: 버킷 안에 메트릭 집계
const nestedQuery = {
aggs: {
// 1단계: 카테고리별 그룹화
by_category: {
terms: { field: "category.keyword" },
// 2단계: 각 카테고리 내부에서 집계
aggs: {
avg_price: {
avg: { field: "price" }
},
total_revenue: {
sum: { field: "amount" }
},
// 3단계: 추가 하위 버킷
by_month: {
date_histogram: {
field: "order_date",
calendar_interval: "month"
}
}
}
}
},
size: 0
};
박시니어 씨가 러시아 인형 마트료시카를 예로 들었습니다. "중첩 집계는 마트료시카 인형과 같아요.
큰 인형 안에 작은 인형이 들어있고, 그 안에 더 작은 인형이 있죠. 집계도 마찬가지로 집계 안에 집계를 넣을 수 있어요." 이 개념은 실무에서 굉장히 강력합니다.
단순히 "카테고리별 판매량"이 아니라 "카테고리별 평균 단가, 총 매출, 그리고 월별 추이"를 한 번의 쿼리로 가져올 수 있으니까요. 위 코드의 구조를 살펴보겠습니다.
가장 바깥쪽에 by_category라는 terms 집계가 있습니다. 이것이 1단계 그룹화입니다.
카테고리별로 버킷이 생성됩니다. 그런데 by_category 안에 또 aggs 블록이 있습니다.
이것이 바로 중첩 집계입니다. 이 내부 aggs는 각 카테고리 버킷 안에서 실행됩니다.
내부에는 세 가지 집계가 있습니다. avg_price는 해당 카테고리 상품들의 평균 가격을 계산합니다.
total_revenue는 해당 카테고리의 총 매출을 계산합니다. by_month는 해당 카테고리 내에서 다시 월별로 그룹화합니다.
이것이 3단계 중첩입니다. 결과는 다음과 같은 구조로 돌아옵니다.
각 카테고리 버킷 안에 avg_price, total_revenue의 값과 by_month 버킷 배열이 함께 들어있습니다. 이런 중첩 구조가 없다면 어땠을까요?
"전자제품의 평균 가격", "의류의 평균 가격", "전자제품의 월별 판매량"... 이렇게 여러 번 쿼리를 날려야 했을 것입니다.
중첩 집계 덕분에 한 번의 요청으로 모든 정보를 얻을 수 있습니다. 실무에서는 드릴다운(drill-down) 분석에 많이 활용됩니다.
대시보드에서 전체 현황을 보다가 특정 카테고리를 클릭하면 그 카테고리의 상세 통계를 보여주는 식입니다. 이때 중첩 집계로 미리 데이터를 준비해두면 빠른 응답이 가능합니다.
주의할 점도 있습니다. 중첩이 너무 깊어지면 쿼리 복잡도와 메모리 사용량이 늘어납니다.
일반적으로 3-4단계 정도가 적당하며, 그 이상은 쿼리를 분리하는 것이 좋습니다. 김개발 씨는 중첩 집계 결과를 파싱하는 코드를 작성했습니다.
카테고리를 순회하면서 각각의 평균 가격과 매출을 테이블로 표시하고, 클릭하면 월별 차트가 나타나는 인터랙티브한 대시보드가 완성되었습니다.
실전 팁
💡 - 중첩은 3-4단계까지가 적당합니다. 너무 깊으면 쿼리를 분리하세요
- 결과 파싱 로직을 먼저 설계하고 쿼리를 작성하면 실수를 줄일 수 있습니다
- 중첩 집계의 결과 크기를 예측하고, 필요하면 size를 제한하세요
6. cardinality 유니크 카운트
마지막으로 팀장님이 물었습니다. "이번 달에 주문한 고객이 몇 명이에요?
중복 없이 순수하게요." 김개발 씨는 잠시 생각했습니다. value_count를 쓰면 전체 주문 건수가 나올 텐데, 고유한 고객 수는 어떻게 구하지?
바로 cardinality 집계가 필요한 순간입니다.
cardinality 집계는 특정 필드의 고유한 값 개수를 계산합니다. SQL의 COUNT(DISTINCT)와 같은 역할을 합니다.
내부적으로 HyperLogLog++ 알고리즘을 사용하여 대용량 데이터에서도 메모리를 적게 사용하면서 빠르게 근사값을 계산합니다.
다음 코드를 살펴봅시다.
// cardinality 집계: 고유 값 카운트
const cardinalityQuery = {
query: {
range: {
order_date: {
gte: "2024-01-01",
lte: "2024-01-31"
}
}
},
aggs: {
// 고유 고객 수
unique_customers: {
cardinality: {
field: "customer_id.keyword",
precision_threshold: 1000 // 정확도 임계값
}
},
// 판매된 고유 상품 수
unique_products: {
cardinality: { field: "product_id.keyword" }
},
// 일별 고유 방문자 수 (중첩 활용)
daily_unique_visitors: {
date_histogram: {
field: "order_date",
calendar_interval: "day"
},
aggs: {
unique_visitors: {
cardinality: { field: "customer_id.keyword" }
}
}
}
},
size: 0
};
박시니어 씨가 손가락을 꼽으며 설명했습니다. "전체 주문이 10,000건이어도 주문한 사람이 500명일 수 있잖아요.
한 사람이 여러 번 주문했을 테니까요. 이렇게 중복을 제거한 고유 개수를 구하는 게 cardinality 집계예요." cardinality는 마치 출석부와 같습니다.
학생이 하루에 여러 번 교실에 들어와도 출석은 한 번만 체크되죠. "오늘 등교한 학생이 몇 명인가"를 세는 것과 같습니다.
위 코드를 살펴보겠습니다. unique_customers 집계는 customer_id 필드의 고유한 값 개수를 셉니다.
만약 1월에 10,000건의 주문이 있었는데 그중 고유 고객이 2,500명이라면, 결과는 2,500이 됩니다. precision_threshold 파라미터가 눈에 띕니다.
cardinality는 정확한 값이 아닌 근사값을 계산합니다. 대용량 데이터에서 정확한 고유 개수를 세려면 모든 값을 메모리에 올려야 하는데, 이는 비효율적입니다.
그래서 HyperLogLog++라는 확률적 알고리즘을 사용합니다. precision_threshold는 이 근사치의 정확도를 조절합니다.
값이 높을수록 정확하지만 메모리를 더 사용합니다. 기본값은 3,000이며, 이 값까지는 거의 정확한 결과를 보장합니다.
그 이상의 고유 값이 있을 때 약간의 오차가 발생할 수 있습니다. 세 번째 집계 daily_unique_visitors는 중첩 집계를 활용한 예입니다.
먼저 date_histogram으로 일별 버킷을 만들고, 각 버킷 안에서 cardinality를 계산합니다. 결과는 "1월 1일: 150명, 1월 2일: 203명, ..." 형태로 일별 고유 방문자 수를 보여줍니다.
cardinality는 실무에서 다양하게 활용됩니다. DAU(일별 활성 사용자), MAU(월별 활성 사용자), 고유 IP 수, 고유 검색어 수, 고유 상품 종류 수 등 중복을 제외한 카운팅이 필요한 모든 곳에서 사용됩니다.
주의할 점이 있습니다. cardinality는 근사값이라는 것을 기억해야 합니다.
고유 값이 수백만 개일 때 약간의 오차가 있을 수 있습니다. 대부분의 분석 용도에서는 문제가 되지 않지만, 정확한 값이 필요하다면 다른 방법을 고려해야 합니다.
또한 cardinality를 너무 많은 중첩 버킷에서 사용하면 성능에 영향을 줄 수 있습니다. 예를 들어 1,000개의 카테고리 각각에서 cardinality를 계산하면 1,000번의 HyperLogLog 연산이 필요합니다.
김개발 씨는 마침내 모든 통계 쿼리를 완성했습니다. 총 매출, 평균 주문액, 카테고리별 현황, 시간대별 추이, 그리고 고유 고객 수까지.
팀장님이 환하게 웃으며 말했습니다. "이제 Elasticsearch 집계는 완전히 마스터했네요!"
실전 팁
💡 - precision_threshold의 기본값 3,000은 대부분의 경우 충분합니다
- 정확한 카운트가 필요하면 scripted_metric이나 애플리케이션 레벨에서 처리하세요
- DAU, MAU 계산에 date_histogram과 cardinality 조합을 활용하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
서비스 메시 완벽 가이드
마이크로서비스 간 통신을 안전하고 효율적으로 관리하는 서비스 메시의 핵심 개념부터 실전 도입까지, 초급 개발자를 위한 완벽한 입문서입니다. Istio와 Linkerd 비교, 사이드카 패턴, 실무 적용 노하우를 담았습니다.
EFK 스택 로깅 완벽 가이드
마이크로서비스 환경에서 로그를 효과적으로 수집하고 분석하는 EFK 스택(Elasticsearch, Fluentd, Kibana)의 핵심 개념과 실전 활용법을 초급 개발자도 쉽게 이해할 수 있도록 정리한 가이드입니다.
Grafana 대시보드 완벽 가이드
실시간 모니터링의 핵심, Grafana 대시보드를 처음부터 끝까지 배워봅니다. Prometheus 연동부터 알람 설정까지, 초급 개발자도 쉽게 따라할 수 있는 실전 가이드입니다.
분산 추적 완벽 가이드
마이크로서비스 환경에서 요청의 전체 흐름을 추적하는 분산 추적 시스템의 핵심 개념을 배웁니다. Trace, Span, Trace ID 전파, 샘플링 전략까지 실무에 필요한 모든 것을 다룹니다.
CloudFront CDN 완벽 가이드
AWS CloudFront를 활용한 콘텐츠 배포 최적화 방법을 실무 관점에서 다룹니다. 배포 생성부터 캐시 설정, HTTPS 적용까지 단계별로 알아봅니다.