[Prometheus & Grafana] Chapter 3. 데이터 모델
참고: 이 글은 Prometheus (v3.2.1)와 Grafana 공식 문서를 기반으로 요약·정리한 내용입니다. 정확한 내용은 공식 문서를 참조해 주세요.
3.1 타임시리즈의 정의
Prometheus는 모든 데이터를 **타임시리즈(time series)**로 저장한다. 타임시리즈란 다음과 같이 정의된다.
메트릭 이름과 레이블 집합으로 고유하게 식별되는, 시간 순서로 정렬된 값들의 스트림
쉽게 말해, 특정 측정 대상(메트릭 이름)의 특정 차원(레이블)에 대한 시간별 값의 기록이다.
예시:
http_requests_total{method="GET", status="200"} → [t1: 100, t2: 150, t3: 230, ...]
http_requests_total{method="POST", status="500"} → [t1: 2, t2: 3, t3: 5, ...]
위 예시에서 http_requests_total{method="GET", status="200"}과 http_requests_total{method="POST", status="500"}은 서로 다른 타임시리즈다. 메트릭 이름은 같지만 레이블이 다르기 때문이다.
타임시리즈의 구성 요소
하나의 타임시리즈는 다음으로 구성된다.
| 구성 요소 | 설명 | 예시 |
|---|---|---|
| 메트릭 이름 | 무엇을 측정하는가 | http_requests_total |
| 레이블 집합 | 어떤 차원인가 | {method="GET", status="200"} |
| 샘플 스트림 | 시간별 값들 | [1709000000: 100, 1709000015: 105, ...] |
3.2 메트릭 이름 규칙
메트릭 이름은 시스템에서 측정하는 대상을 명확하게 표현해야 한다.
문자 규칙
Prometheus v3.0.0부터 UTF-8 문자를 지원하지만, 호환성을 위해 다음 패턴을 권장한다.
[a-zA-Z_:][a-zA-Z0-9_:]*
유효한 이름:
http_requests_totalnode_cpu_seconds_totalprocess_resident_memory_bytes
피해야 할 이름:
http-requests(하이픈 사용 — 권장하지 않음)123_metric(숫자로 시작)
네이밍 규칙
공식 문서의 베스트 프랙티스에 따른 네이밍 규칙은 다음과 같다.
1. 애플리케이션 접두사 사용
소속 시스템을 명확히 한다.
prometheus_notifications_total ← Prometheus 자체 메트릭
node_cpu_seconds_total ← Node Exporter 메트릭
myapp_http_requests_total ← 커스텀 애플리케이션 메트릭
2. 기본 단위(Base Unit) 사용
변환이 필요 없는 기본 단위를 사용한다.
| 분류 | 기본 단위 | 올바른 예 | 잘못된 예 |
|---|---|---|---|
| 시간 | seconds | request_duration_seconds |
request_duration_ms |
| 데이터 크기 | bytes | response_size_bytes |
response_size_kb |
| 비율 | ratio (0~1) | cpu_usage_ratio |
cpu_usage_percent |
| 온도 | celsius | temperature_celsius |
temperature_fahrenheit |
3. 접미사 규칙
메트릭의 의미를 접미사로 표현한다.
| 접미사 | 의미 | 예시 |
|---|---|---|
_total |
누적 카운터 | http_requests_total |
_seconds |
시간 (초 단위) | request_duration_seconds |
_bytes |
데이터 크기 | response_size_bytes |
_info |
메타데이터 (값은 항상 1) | node_uname_info |
_timestamp_seconds |
Unix 타임스탬프 | process_start_time_seconds |
_bucket |
히스토그램 버킷 | request_duration_seconds_bucket |
_sum |
히스토그램/서머리 합계 | request_duration_seconds_sum |
_count |
히스토그램/서머리 개수 | request_duration_seconds_count |
4. 콜론(:)은 Recording Rule 전용
사용자가 정의하는 Recording Rule의 출력 메트릭에만 콜론을 사용한다.
# Recording Rule 출력 예시
level:metric:operations
job:http_requests:rate5m
instance:node_cpu:avg
일반 메트릭 이름에는 콜론을 사용하지 않는다.
3.3 레이블 (Labels)
레이블은 Prometheus 데이터 모델의 핵심이다. 동일한 메트릭의 다양한 차원을 구분하는 **키-값 쌍(key-value pair)**이다.
레이블의 역할
레이블이 없다면, http_requests_total이라는 메트릭은 전체 요청 수만 알려줄 수 있다. 레이블을 추가하면 다차원으로 분석할 수 있다.
http_requests_total{method="GET", handler="/api/users", status="200"} = 15234
http_requests_total{method="POST", handler="/api/users", status="201"} = 532
http_requests_total{method="GET", handler="/api/users", status="500"} = 12
이제 다음 질문에 모두 답할 수 있다.
- 전체 요청 수는? →
sum(http_requests_total) - GET 요청만? →
http_requests_total{method="GET"} - 500 에러만? →
http_requests_total{status="500"} /api/users핸들러의 에러율? → 필터링 + 산술 연산
레이블 이름 규칙
[a-zA-Z_][a-zA-Z0-9_]*
__(이중 언더스코어)로 시작하는 레이블 이름은 Prometheus 내부용으로 예약되어 있다.__name__: 메트릭 이름 (내부적으로 레이블로 저장됨)__address__: 스크래핑 대상의 주소__scheme__: 스크래핑 프로토콜 (http/https)
레이블과 카디널리티
**카디널리티(cardinality)**란 레이블 값의 고유한 조합 수를 의미한다. 이것은 Prometheus 운영에서 가장 중요한 개념 중 하나다.
왜 중요한가?
레이블 값의 모든 고유한 조합이 별도의 타임시리즈를 생성한다.
# 레이블이 2개이고 각각 3개, 5개의 값을 가지면
# 3 × 5 = 15개의 타임시리즈가 생성된다
http_requests_total{method="GET", status="200"}
http_requests_total{method="GET", status="201"}
http_requests_total{method="GET", status="404"}
http_requests_total{method="GET", status="500"}
http_requests_total{method="GET", status="503"}
http_requests_total{method="POST", status="200"}
... (총 15개)
카디널리티 관리 원칙:
| 규칙 | 설명 |
|---|---|
| 메트릭당 카디널리티 10 이하 유지 | 이상적인 수준 |
| 100 이상이면 재설계 필요 | 시스템에 부담이 된다 |
| user_id, email 같은 고유 식별자는 레이블로 사용 금지 | 수만~수백만 개의 타임시리즈 폭발 |
나쁜 예시:
# 사용자 ID를 레이블로 사용 — 절대 하지 말 것
http_requests_total{user_id="12345"}
http_requests_total{user_id="12346"}
... (사용자 수만큼 타임시리즈 생성)
좋은 예시:
# 제한된 값만 레이블로 사용
http_requests_total{method="GET"} # method: 5~10가지
http_requests_total{status="200"} # status: 5~20가지
빈 레이블 값
빈 문자열("")을 가진 레이블은 해당 레이블이 존재하지 않는 것과 동일하게 취급된다. PromQL 쿼리 시 이 점을 주의해야 한다.
3.4 샘플: float64 값 + 밀리초 타임스탬프
타임시리즈를 구성하는 개별 데이터 포인트를 **샘플(sample)**이라 한다. 각 샘플은 두 가지로 구성된다.
| 구성 요소 | 타입 | 설명 |
|---|---|---|
| 값 (value) | float64 또는 Native Histogram | 측정된 수치 |
| 타임스탬프 (timestamp) | int64 (밀리초) | Unix epoch 기준 밀리초 |
예시:
# 메트릭: http_requests_total{method="GET"}
# 샘플 스트림:
# 타임스탬프 값
1709000000000 100 ← 2024-02-27 00:00:00 UTC
1709000015000 105 ← 15초 후
1709000030000 112 ← 30초 후
저장 효율
Prometheus는 샘플당 평균 1~2바이트만 사용한다. 이는 고도로 최적화된 압축 알고리즘 덕분이다. 연속된 값은 델타 인코딩과 XOR 기반 압축을 통해 매우 작은 크기로 저장된다.
용량 계산 예시:
100개 타임시리즈 × 15초 간격 = 초당 약 7개 샘플
= 시간당 약 24,000개 샘플
= 일당 약 576,000개 샘플
≈ 일당 약 1MB (샘플당 2바이트 기준)
3.5 표기법: metric{label="value"}
Prometheus에서 타임시리즈를 식별하는 표준 표기법은 다음과 같다.
기본 표기법
<metric_name>{<label_name>="<label_value>", ...}
예시:
api_http_requests_total{method="POST", handler="/messages"}
name 레이블을 사용한 대체 표기법
메트릭 이름은 내부적으로 __name__이라는 특수 레이블로 저장된다. 따라서 다음 두 표기법은 동일하다.
# 기본 표기법
api_http_requests_total{method="POST"}
# __name__ 레이블 사용
{__name__="api_http_requests_total", method="POST"}
이 대체 표기법은 메트릭 이름에 정규표현식을 적용할 때 유용하다.
# "job:"으로 시작하는 모든 메트릭 조회
{__name__=~"job:.*"}
UTF-8 특수 문자 처리 (v3.0+)
Prometheus v3.0.0부터 UTF-8 메트릭 이름과 레이블 이름을 지원한다. 특수 문자가 포함된 경우 인용 부호를 사용한다.
{"metric.name.with.dots", label_name="value"}
다만 아직 생태계 전체가 UTF-8으로 전환 중이므로, 당분간은 기존의 ASCII 문자 세트를 사용하는 것을 권장한다.
정리
| 개념 | 핵심 |
|---|---|
| 타임시리즈 | 메트릭 이름 + 레이블 집합으로 고유하게 식별되는 값의 스트림 |
| 메트릭 이름 | 접두사(소속) + 단위 접미사로 명확하게 명명 |
| 레이블 | 다차원 분석을 가능하게 하는 키-값 쌍 |
| 카디널리티 | 레이블 조합 수, 10 이하 유지 권장 |
| 샘플 | float64 값 + 밀리초 타임스탬프, 샘플당 1~2바이트 |
| 표기법 | metric{label="value"}, __name__ 대체 표기 가능 |