STATS 명령어
STATS 명령어
stats 명령어는 검색 결과에 대한 집계(aggregation)를 계산해요. 여러 집계 함수와 by 절, span 구간 그룹핑을 지원해요.
출처: 문서
본문
stats 명령어는 검색 결과에 대한 집계를 계산해요.
stats, eventstats, streamstats 비교
stats, eventstats, streamstats 명령어를 변환 동작, 출력 형식, 집계 범위, 사용 사례 측면에서 자세히 비교하려면 Comparing stats, eventstats, and streamstats를 참고해요.
구문 (Syntax)
stats 명령어의 구문은 다음과 같아요:
stats [bucket_nullable=bool] <aggregation>... [by-clause]
매개변수 (Parameters)
stats 명령어는 다음 매개변수를 지원해요.
| 매개변수 | 필수/선택 | 설명 |
|---|---|---|
<aggregation> |
필수 | 집계 함수예요. |
<by-clause> |
선택 | 지정한 필드나 표현식으로 결과를 그룹화해요. 구문: by [span-expression,] [field,]... by 절을 지정하지 않으면 stats 명령어는 전체 검색 결과에 대한 집계인 한 행만 반환해요. |
bucket_nullable |
선택 | 그룹별 집계에서 null 버킷을 포함할지 여부를 제어해요. false로 설정하면 그룹화 필드가 null인 레코드를 무시해 성능이 빨라져요. 기본값은 plugins.ppl.syntax.legacy.preferred의 값이에요. |
<span-expression> |
선택 | 필드를 간격(interval)별 버킷으로 나눠요 (최대 1개). 구문: span(field_expr, interval_expr). 기본적으로 간격은 필드의 기본 단위를 사용해요. 날짜/시간 필드의 경우 집계 결과는 null 값을 무시해요. 예를 들어 span(age, 10)은 10년 단위 나이 버킷을 만들고, span(timestamp, 1h)은 시간 단위 버킷을 만들어요. 유효한 시간 단위는 밀리초(ms), 초(s), 분(m), 시간(h), 일(d), 주(w), 월(M), 분기(q), 년(y)이에요. |
집계 함수 (Aggregation functions)
stats 명령어는 다음 집계 함수를 지원해요:
COUNT/C– 값의 개수SUM– 숫자 값의 합AVG– 숫자 값의 평균MAX– 최댓값MIN– 최솟값VAR_SAMP– 표본 분산VAR_POP– 모집단 분산STDDEV_SAMP– 표본 표준편차STDDEV_POP– 모집단 표준편차DISTINCT_COUNT_APPROX– 근사 중복 제외 개수TAKE– 원본 값 목록PERCENTILE/PERCENTILE_APPROX– 백분위 계산PERC<percent>/P<percent>– 백분위 축약 함수MEDIAN– 50번째 백분위EARLIEST– 타임스탬프 기준 가장 이른 값LATEST– 타임스탬프 기준 가장 최근 값FIRST– 첫 번째 non-null 값LAST– 마지막 non-null 값LIST– 모든 값을 배열로 수집VALUES– 고유 값을 정렬된 배열로 수집
각 함수의 자세한 문서는 집계 함수 (Aggregation Functions)를 참고해요.
예제 1: 이벤트 개수 계산하기
다음 쿼리는 로그 항목의 총 개수를 세어요. 로그 수집의 기본적인 상태 점검이라고 볼 수 있어요:
source=otellogs
| stats count() as total_logs
쿼리는 다음과 같은 결과를 반환해요:
total_logs
20
예제 2: 필드의 평균 계산하기
다음 쿼리는 모든 로그에 걸친 평균 심각도 숫자를 계산해요. 시간이 지남에 따라 평균이 올라가면 시스템 불안정이 증가하고 있음을 나타낼 수 있어요:
source=otellogs
| stats avg(severityNumber) as avg_severity
쿼리는 다음과 같은 결과를 반환해요:
avg_severity
12.0
예제 3: 그룹별 개수 계산하기
다음 쿼리는 심각도 수준별로 로그 수를 세어, 시스템 상태를 한눈에 파악할 수 있게 해줘요:
source=otellogs
| stats count() as log_count by severityText
| sort - log_count
쿼리는 다음과 같은 결과를 반환해요:
log_count | severityText
7 | ERROR
6 | INFO
4 | WARN
3 | DEBUG
예제 4: 그룹별로 여러 집계 계산하기
다음 쿼리는 서비스별 총 로그 수와 심각도 범위를 계산해, 어떤 서비스가 가장 활발하고 가장 문제가 많은지 파악하는 데 도움을 줘요:
source=otellogs
| stats count() as total, min(severityNumber) as min_sev, max(severityNumber) as max_sev by `resource.attributes.service.name`
| sort - total
| head 5
쿼리는 다음과 같은 결과를 반환해요:
total | min_sev | max_sev | resource.attributes.service.name
4 | 9 | 9 | frontend
4 | 5 | 17 | product-catalog
3 | 5 | 9 | cart
3 | 9 | 17 | checkout
3 | 13 | 17 | frontend-proxy
예제 5: span으로 개수 계산하기
다음 쿼리는 로그를 10 단위의 심각도 버킷으로 그룹화해, 낮은(0–9), 중간(10–19), 높은(20+) 심각도 범위에 걸친 분포를 보여줘요:
source=otellogs
| stats count() as log_count by span(severityNumber, 10)
쿼리는 다음과 같은 결과를 반환해요:
log_count | span(severityNumber,10)
9 | 0
11 | 10
예제 6: 필드와 span으로 개수 계산하기
다음 쿼리는 심각도 숫자 범위 내에서 심각도 수준별로 로그 수를 세어, 심각도 텍스트가 숫자 범위에 어떻게 매핑되는지 보여줘요:
source=otellogs
| stats count() as cnt by span(severityNumber, 10) as sev_range, severityText
| sort sev_range
쿼리는 다음과 같은 결과를 반환해요:
cnt | sev_range | severityText
3 | 0 | DEBUG
6 | 0 | INFO
7 | 10 | ERROR
4 | 10 | WARN
예제 7: 필드의 중복 제외 개수 계산하기
다음 쿼리는 로그를 보고하는 서비스의 총 개수와 고유 개수를 세어, 예상되는 모든 서비스가 보고 중인지 확인하는 데 유용해요:
source=otellogs
| stats count(`resource.attributes.service.name`) as total_entries, distinct_count(`resource.attributes.service.name`) as unique_services
쿼리는 다음과 같은 결과를 반환해요:
total_entries | unique_services
20 | 7
예제 8: 그룹별로 VALUES를 사용해 고유 값 수집하기
다음 쿼리는 각 심각도 수준에 대한 고유 서비스 이름을 수집해, 각 수준에서 어떤 서비스가 영향을 받는지 빠르게 확인하는 데 유용해요:
source=otellogs
| stats values(`resource.attributes.service.name`) as services by severityText
| sort severityText
쿼리는 다음과 같은 결과를 반환해요:
services | severityText
[cart,product-catalog] | DEBUG
[checkout,frontend-proxy,payment,product-catalog,recommendation] | ERROR
[cart,checkout,frontend] | INFO
[frontend-proxy,product-catalog] | WARN
예제 9: 필드의 백분위 계산하기
다음 쿼리는 심각도 숫자의 90번째 백분위를 계산해, 심각도 분포를 파악하는 데 도움을 줘요:
source=otellogs
| stats percentile(severityNumber, 90) as p90_severity
쿼리는 다음과 같은 결과를 반환해요:
p90_severity
17
예제 10: VALUES를 사용해 고유 값 수집하기
다음 쿼리는 로그에 존재하는 모든 고유 심각도 수준을 수집해요:
source=otellogs
| stats values(severityText) as severity_levels
쿼리는 다음과 같은 결과를 반환해요:
severity_levels
[DEBUG,ERROR,INFO,WARN]
예제 11: null 버킷 무시하기
다음 쿼리는 bucket_nullable=false로 설정해 그룹핑에서 null 값을 제외해, 정의된 namespace가 있는 서비스만 보고 싶을 때 유용해요:
source=otellogs
| stats bucket_nullable=false count() as cnt by instrumentationScope.name
쿼리는 다음과 같은 결과를 반환해요:
cnt | instrumentationScope.name
2 | @opentelemetry/instrumentation-http
1 | Microsoft.Extensions.Hosting
1 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
예제 12: null 처리를 포함한 날짜 span 그룹핑
다음 예제는 이 샘플 인덱스 데이터를 사용해요:
Name | DEPTNO | birthday
Alice | 1 | 2024-04-21
Bob | 2 | 2025-08-21
Jeff | null | 2025-04-22
Adam | 2 | null
다음 쿼리는 birthday 필드를 연 단위 span으로 그룹화하며, null 값을 자동으로 제외해요:
source=example
| stats count() as cnt by span(birthday, 1y) as year
쿼리는 다음과 같은 결과를 반환해요:
cnt | year
1 | 2024-01-01
2 | 2025-01-01
연 단위 span과 부서 번호로 함께 그룹화할 때는 (기본적으로 null DEPTNO 값이 결과에 포함돼요):
source=example
| stats count() as cnt by span(birthday, 1y) as year, DEPTNO
쿼리는 다음과 같은 결과를 반환해요:
cnt | year | DEPTNO
1 | 2024-01-01 | 1
1 | 2025-01-01 | 2
1 | 2025-01-01 | null
bucket_nullable=false를 사용해 그룹핑에서 null DEPTNO 값을 제외해요:
source=example
| stats bucket_nullable=false count() as cnt by span(birthday, 1y) as year, DEPTNO
쿼리는 다음과 같은 결과를 반환해요:
cnt | year | DEPTNO
1 | 2024-01-01 | 1
1 | 2025-01-01 | 2
예제 13: 암시적 @timestamp 필드로 개수 계산하기
span 함수에서 field 매개변수를 생략하면 자동으로 암시적 @timestamp 필드를 사용해요:
source=big5
| stats count() by span(1month)
쿼리는 다음과 같은 결과를 반환해요:
count() | span(1month)
1 | 2023-01-01 00:00:00
제한 사항 (Limitations)
stats 명령어에는 다음 제한 사항이 적용돼요.
고카디널리티(high-cardinality) 필드의 버킷 집계 결과는 근사치일 수 있음
OpenSearch에서 terms 버킷 집계의 doc_count 값은 근사치일 수 있어요. 따라서 해당 버킷에서 수행되는 sum이나 avg 같은 집계도 근사치일 수 있어요.
예를 들어 다음 쿼리는 상위 10개 URL을 조회해요:
source=hits
| stats bucket_nullable=false count() as c by URL
| sort - c
| head 10
이 쿼리는 OpenSearch에서 "order": { "_count": "desc" }를 가진 terms 집계로 변환돼요. 카디널리티가 높은 필드의 경우 일부 버킷이 버려질 수 있으므로 결과가 근사치일 수 있어요.
doc_count를 오름차순으로 정렬하면 부정확한 결과가 나올 수 있음
카디널리티가 높은 필드에서 가장 드문(least frequent) 항목을 조회할 때 결과가 부정확할 수 있어요. 샤드 수준 집계는 전역적으로 드문 항목을 놓치거나 빈도를 잘못 나타낼 수 있어 전체 결과에 오류가 생겨요.
예를 들어 다음 쿼리는 빈도가 가장 낮은 10개 URL을 조회해요:
source=hits
| stats bucket_nullable=false count() as c by URL
| sort + c
| head 10
전역적으로 드문 항목은 모든 샤드에서 드물게 보이지 않거나 일부 샤드 결과에서 완전히 사라질 수 있어요. 반대로 한 샤드에서 빈도가 낮은 항목이 다른 샤드에서는 흔할 수도 있어요. 두 경우 모두 샤드 수준 근사로 인해 드문 항목이 누락돼 전체 결과가 부정확해질 수 있어요.