PromQL과 알림 규칙
PromQL이 필요한 이유
Prometheus가 수집한 시계열 데이터는 그 자체로는 숫자 더미다. 의미를 만들려면 쿼리가 필요하다. PromQL은 레이블을 기준으로 수천 개의 시계열을 한 번에 집계하고, 변화율을 계산하고, 퍼센타일을 추정할 수 있게 설계된 전용 언어다. SQL과 달리 테이블 조인이 없고, 시계열 연산에 특화된 함수들이 중심이다.
네 가지 데이터 타입
PromQL 표현식의 반환 타입은 네 가지다.
| 타입 | 설명 | 예시 |
|---|---|---|
| Instant vector | 각 레이블 집합의 최신 샘플 한 개씩 | http_requests_total |
| Range vector | 특정 기간 동안의 샘플 이력 | http_requests_total[5m] |
| Scalar | 단일 부동 소수점 숫자 | 1.5 |
| String | 문자열 (거의 쓰이지 않음) | "hello" |
Instant vector는 지금 이 순간 각 레이블 집합에 대해 가장 최근 샘플을 하나씩 반환한다. 대부분의 집계 함수가 Instant vector를 입력으로 받는다.
Range vector는 특정 기간의 샘플 이력을 담는다. [5m]은 "현재 기준 5분 이내의 샘플들"을 의미한다. rate() 같은 변화율 함수는 반드시 Range vector를 입력으로 받는다.
셀렉터와 매처
중괄호 안에 레이블 매처를 써서 원하는 시계열만 선택한다.
http_requests_total{job="api-server"} -- 완전 일치
http_requests_total{status!="200"} -- 제외
http_requests_total{method=~"GET|POST"} -- 정규식 일치
http_requests_total{method!~"GET|POST"} -- 정규식 제외매처 없이 메트릭 이름만 쓰면 그 이름을 가진 모든 시계열이 선택된다. 카디널리티가 높은 메트릭에서 매처 없이 쿼리하면 부하가 커진다.
rate와 irate: Counter를 의미 있게 만드는 함수
Counter는 값이 단조 증가하므로 현재 절댓값 자체는 별 의미가 없다. rate()로 초당 평균 변화율을 구해야 의미 있는 수치가 된다.
rate(http_requests_total[5m])이 쿼리는 "최근 5분 동안의 HTTP 요청 초당 평균 수"를 돌려준다. 범위가 넓을수록 값이 평탄해지고 일시 스파이크가 희석된다.
irate()는 범위 벡터 안의 마지막 두 샘플만 보고 순간 변화율을 계산한다. 스파이크를 잡는 데 유리하지만 노이즈가 많다. 알림 규칙보다는 대시보드에서 실시간 트렌드를 볼 때 더 적합하다.
irate(http_requests_total[5m]) -- 5m은 샘플 탐색 범위, 계산은 마지막 두 점increase()는 rate의 변형으로, 범위 동안의 증가 절댓값을 돌려준다.
increase(http_requests_total[1h]) -- 지난 1시간 동안 총 증가 요청 수집계 연산자
Instant vector를 레이블 기준으로 집계한다.
sum(rate(http_requests_total[5m])) -- 전체 합계
sum by (job)(rate(http_requests_total[5m])) -- job 레이블별 합계
sum without (instance)(rate(http_requests_total[5m])) -- instance만 제거 후 합계by는 "이 레이블들만 남기고 집계", without은 "이 레이블들만 제거하고 집계"다. 자주 쓰는 집계 연산자는 sum, avg, max, min, count, topk, bottomk다.
topk(5, rate(http_requests_total[5m]))처럼 상위 N개를 고르는 것도 자주 쓰인다.
histogram_quantile: 퍼센타일 계산
Histogram 타입 메트릭에서 p99 지연 시간처럼 퍼센타일을 구할 때 쓴다.
histogram_quantile(
0.99,
sum by (le)(rate(http_request_duration_seconds_bucket[5m]))
)이 쿼리의 의미는 "최근 5분 동안의 HTTP 요청 중 99%가 응답한 시간 이하인 값"이다. 0.5이면 p50(중앙값), 0.95이면 p95다.
중요한 제약이 있다. histogram_quantile은 수학적 추정이다. Histogram의 bucket 경계와 실제 데이터 분포가 다르면 오차가 생긴다. 그래도 Summary보다 유연한 이유는 여러 인스턴스의 bucket을 서버에서 합산한 뒤 퍼센타일을 계산할 수 있기 때문이다.
Recording Rules: 반복 쿼리를 미리 저장
대시보드나 알림 규칙에 같은 복잡한 쿼리가 반복 등장하면 Prometheus가 매번 계산해야 한다. Recording rules는 그 결과를 새 시계열로 미리 저장해 둔다.
groups:
- name: http_aggregations
interval: 1m
rules:
- record: job:http_requests:rate5m
expr: sum by (job)(rate(http_requests_total[5m]))이 규칙은 1분마다 평가되며 결과를 job:http_requests:rate5m이라는 새 메트릭으로 저장한다. 이후 대시보드는 복잡한 원본 쿼리 대신 이 메트릭만 조회한다.
Recording rule 이름 관례는 level:metric:operations 형식이다. 위 예시에서 job이 level, http_requests가 metric, rate5m이 operation이다. 일관된 이름은 대시보드 쿼리를 단순하게 유지해 주고, 나중에 원본 메트릭이 바뀌어도 대시보드 쿼리를 건드리지 않아도 된다.
Alerting Rules: 조건과 상태 전이
알림 규칙은 PromQL 표현식이 참인 동안 알림을 발생시킨다.
groups:
- name: api_alerts
rules:
- alert: HighErrorRate
expr: >
rate(http_requests_total{status=~"5.."}[5m])
/
rate(http_requests_total[5m])
> 0.05
for: 2m
labels:
severity: warning
annotations:
summary: "High error rate on {{ $labels.job }}"
description: "Error rate {{ $value | humanizePercentage }} over 5m."for: 2m이 핵심이다. 표현식이 참이 된 순간 바로 firing이 되지 않는다. 2분 동안 계속 참이어야 비로소 firing 상태가 된다. 그 사이는 pending 상태다.
for를 짧게 설정하면 일시 스파이크에도 알림이 오고, 너무 길게 설정하면 실제 장애를 늦게 인지한다. 인프라 알림은 5분, 비즈니스 지표는 10~15분이 흔한 시작점이다.
Alertmanager: 알림의 생명주기
Prometheus는 알림 조건만 평가하고, 실제 통보 처리는 Alertmanager가 담당한다. Alertmanager가 하는 일은 네 가지다.
- 그루핑(Grouping): 같은 조건으로 발생한 여러 알림을 하나의 메시지로 묶는다. 인스턴스 50개가 동시에 다운됐을 때 50개의 Slack 메시지가 오지 않게 한다.
- 라우팅(Routing): 레이블을 기준으로 알림을 적절한 수신자(팀, 채널)로 보낸다.
- 억제(Inhibition): 상위 경보가 발생하면 하위 경보를 묵힌다. 노드가 다운됐을 때 그 노드의 서비스 알림을 추가로 보내지 않는 것이 전형적인 사례다.
- 침묵(Silence): 계획 유지보수 중에 특정 레이블 집합의 알림을 임시로 차단한다.
# alertmanager.yml
route:
group_by: ['alertname', 'cluster']
group_wait: 30s # 첫 알림 후 추가 알림을 묶어 기다리는 시간
group_interval: 5m # 같은 그룹에 새 알림 발생 시 재전송까지 대기
repeat_interval: 1h # 미해결 알림 반복 전송 주기
receiver: 'slack-default'
routes:
- match:
severity: critical
receiver: 'pagerduty-critical'
receivers:
- name: 'slack-default'
slack_configs:
- api_url: 'https://hooks.slack.com/services/...'
channel: '#alerts'
title: '[{{ .Status | toUpper }}] {{ .CommonLabels.alertname }}'
- name: 'pagerduty-critical'
pagerduty_configs:
- routing_key: '<integration-key>'
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'cluster', 'service']group_wait이 짧으면 알림이 빠르지만 연속 장애 시 그루핑이 덜 된다. group_interval은 같은 그룹에 새 알림이 생겼을 때만 영향을 준다. repeat_interval은 문제가 계속 지속될 때 담당자가 알림을 잊지 않게 주기적으로 상기시켜 주는 역할이다.
전체 흐름 정리
→ 새 시계열 저장 Alerting rules
→ pending / firing → Alertmanager 그루핑 라우팅 억제 / 침묵 → Slack / PagerDuty
Email / Webhook
실무 체크리스트
| 영역 | 체크할 것 |
|---|---|
| Counter | rate()로 사용; 절댓값 직접 비교 금지 |
| Histogram | bucket 경계를 예상 값 분포에 맞게 설정 |
| 레이블 카디널리티 | user_id, request_id 같은 고유값 레이블 금지 |
| Recording rule | 대시보드에 반복 등장하는 쿼리를 미리 계산; 이름은 level:metric:ops |
Alerting for 절 | 일시 노이즈를 걸러 줄 정도의 기간 설정 |
| Alertmanager grouping | 폭탄 알림(alert storm) 방지 |
| Inhibition | 종속 시스템 다운 시 하위 알림 억제 |
| Silence | 유지보수 창마다 미리 설정 |
한 줄 정리
PromQL은 Instant vector와 Range vector를 레이블 기준으로 집계하는 언어다. Recording rules는 반복 쿼리의 비용을 줄이고, Alerting rules의 for 절은 일시 노이즈를 걸러내며, Alertmanager가 그루핑·라우팅·억제·침묵으로 알림의 실제 전달을 관리한다.
References
- https://prometheus.io/docs/prometheus/latest/querying/basics/
- https://prometheus.io/docs/prometheus/latest/querying/functions/
- https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/
- https://prometheus.io/docs/prometheus/latest/configuration/recording_rules/
- https://prometheus.io/docs/alerting/latest/alertmanager/
- https://signoz.io/guides/promql-cheat-sheet/
- https://last9.io/blog/prometheus-recording-rules/
- https://victoriametrics.com/blog/alerting-recording-rules-alertmanager/
- https://oneuptime.com/blog/post/2026-01-25-promql-queries-alerting/view