들어가며
PromQL 작성의 어려움
Alert Rule을 설정하거나 Grafana 대시보드를 만들다 보면 "이 쿼리가 정말 내가 모니터링하려는 대상을 정확하게 표현하고 있을까?"라는 고민이 들곤 했다. AI가 PromQL을 꽤 잘 작성해주기는 하지만, 결국 좋은(정확한) 쿼리를 작성하려면 무엇을 관측하려는지 명확히 정의하고, 메트릭의 특성을 이해한 뒤 적절한 함수를 조합하는 것이 중요하다고 느꼈다.
이를 위해서는 먼저 메트릭이 어떤 특성(Counter, Gauge 등)을 가지는지 이해해야 한다. 또한, 같은 메트릭이라도 관측하려는 목적에 따라 쿼리가 달라질 수 있다.
✔ 경험상 아래 세 가지가 PromQL 정확도, 가시화에 도움되었다.
- 메트릭 타입(Metric Type): Counter, Gauge, Histogram
- 벡터 타입(Vector Type): Instant Vector, Range Vector
- 집계 함수: sum, avg, max, rate, ....
이 세가지 축을 중심으로 PromQL을 해석하고, 보다 정확한 모니터링 쿼리를 작성하는 방법을 정리해본다.
✔ 따라서 이 글에서 다루고자 하는 것은 다음과 같다.
- Prometheus 메트릭 타입(Counter, Gauge, Histogram, Summary)과 각 타입에 맞는 함수
- 실무에서 매일 쓰는 집계 함수 — sum, avg, max, rate, increase, sum_over_time, avg_over_time 등
- 라벨 매칭 연산자(on, ignoring, group_left) 같은 join
1. Metric Type: Counter, Gauge, Histogram
쿼리를 작성할 때 가장 먼저 던져야 하는 질문은 `이 메트릭이 어떤 타입인가`다. Counter냐 Gauge냐에 따라 사용해야할 집계 함수가 달라지기 때문이다. 쿼리하고자 하는 메트릭의 타입을 파악하고 rate를 걸지, over_time을 걸지 판단하면 정확한 쿼리를 만드는 데에 도움된다.
1.1. Counter
누적되어 단조 증가하는 값이다. 요청 수, 에러 수, 전송 Bytes에 해당하는 메트릭이다. 이 메트릭의 raw value 자체는 거의 의미가 없어서 보통은 rate, increase, irate와 같은 함수를 사용하여 변화율을 측정한다. (집계함수에 대한 설명은 3에서 자세히 다룬다. 일단 Counter에 같이 쓰인다는 점만 이해하고 넘어가자.)
- 보통
_total으로 끝나는 컨벤션을 가진다.
사용 예시
rate(http_requests_total[5m]) # 초당 평균 증가율 (req/s)
increase(http_requests_total[5m]) # 5분 구간 동안의 총 증가량
irate(http_requests_total[5m]) # 마지막 두 샘플 기준 순간 증가율

1.2. Gauge
CPUUtilis, MemoryUtilis와 같이 시스템 상태를 나타내는, 상태의 추세를 보이는 전형적인 메트릭이다.

Gauge는 원시 값 그대로가 의미가 있다. 시간축 통계가 필요하면 _over_time 계열과 함께 쓰인다. (over_time은 뒤에서 자세히)
avg_over_time(aws_ec2_cpuutilization_average[1h]) # 1시간 평균 CPU
max_over_time(aws_ec2_cpuutilization_maximum[6h]) # 6시간 내 피크 CPU
✔ Gauge 메트릭에 rate, avg와 같은 연산자를 사용하면, 데이터가 왜곡될 위험이 있다.
Gauge를 사용할 때 주의할 점은 Gauge 자체가 변화율을 뜻하므로 rate, increase, avg와 같은 집계 연산자와 함께 사용하는 경우 데이터가 보정(aggregation)되어 결과가 왜곡될 수 있는 점이다.

위와 동일한 메트릭에 rate를 씌우면 결과가 왜곡되는 점을 알 수 있다. 원본 그래프[패널 1]는 0~70% 사이를 오르내리는 형태다. 하지만 rate를 건 패널 2는 0~0.3이라는 전혀 다른 스케일의 스파이크 덩어리가 된 것을 확인할 수 있다.

반면 avg는 rate와 다르게 틀린 연산은 아니고 평균을 낸다. 하지만 by 없이 단순 avg()를 사용하면 여러 시계열이 하나로 뭉개진다 [패널 3]. [패널 1]에서는 클러스터별 선이 각각 ~70%까지 올라가는 게 보이는데, [패널 3]에서는 평균선이 ~32%로 낮아졌다. avg로 의도치 않은 연산이 되어 관측하고자 하는 영역이 뭉개지는 점을 조심하자!
따라서 Gauge 메트릭의 평균(avg)을 보려면 아래와 같이 사용하는 것이 적절하다.

2. 자주 사용하는 집계 함수들
처음 PromQL을 배울 때 집계함수와 관련해서 alice_k106님의 블로그를 많이 참고했었다. 좋은 자료라 공유...😀
2.1. 집계 함수 종류
집계 함수는 크게 세 부류로 나눠서 보면 기억하기 좋다.
- 시계열 간 집계 (across series): 같은 시점의 여러 시계열을 하나로 묶는다.
- sum, avg, max, count, topk ...
- 시간 축 집계 (over time): 하나의 시계열을 시간 구간으로 묶는다.
- avg_over_time, sum_over_time, max_over_time ...
- Counter 변화율: 단조 증가하는 Counter의 변화량
- rate, irate, increase
2.2. 그 전에: Instant Vector vs Range Vector
✔ 집계 함수가 어떤 입력을 받는지 알려면 벡터 타입을 먼저 이해해야 한다.
- Instant Vector: 각 시계열마다 한 시점의 값 하나.
node_cpu_seconds_total처럼 메트릭 이름만 쓰면 Instant Vector다. - Range Vector: 각 시계열마다 시간 구간의 샘플 묶음.
node_cpu_seconds_total[5m]처럼 [5m] 구간을 붙이면 Range Vector다.
규칙은 단순하다!
sum,avg,max같은 시계열 간 집계 연산자는 Instant Vector를 받는다.rate,increase,_over_time계열은 Range Vector를 받는다. 그래서[5m]같은 구간이 반드시 필요하다.
rate(metric)처럼 구간 없이 쓰면 에러가 나는 이유가 이거다. rate는 구간 안의 샘플들을 봐야 변화율을 계산할 수 있기 때문!
2.3. sum, avg, max, count, topk
여러 시계열을 같은 시점에서 하나로 묶는다.
| 함수 | 의미 |
| sum | 합 |
| avg | 평균 |
| min/max | 최소/최대 |
| count | 시계열 개수 |
| stddev/stdvar | 표준편차/분산 |
| topk/bottomk | 상위/하위 N개 |
by, without를 사용해 데이터를 묶을 라벨 기준을 정할 수 있다. by 지시자는 aggregation 집계 함수에서 사용할 수 있다. by의 역할은 함수의 결과값에 원하는 라벨만 남기는 데에 활용된다. 이 점을 활용하는 것이 쿼리 연산에 많은 도움이 되니, 꼭 기억하고 활용하자.
- by (label): 명시한 라벨만 남기고 나머지는 다 합친다.
- without (label): 명시한 라벨만 버리고 나머지는 남긴다.
✔ by 활용

라벨 매칭을 통해 job을 지정했음에도 불구하고 다양한 정보들이 보인다. 메트릭에 커서를 올려서 특정 시점의 데이터를 확인해보면, 이미지와 같이 다양한 label이 결과에 포함되어있다. 하지만 우리가 패널을 만들거나 얼럿을 쿼리할 때 이 모든 Label을 사용하지는 않는다. 오히려 필요한 label만 조합하여 사용하는 게 중요하다. 이에 도움을 주는 것이 바로 by!

2.4. Counter 변화율: rate, increase, irate
2.4.1. rate
Counter Metric은 앞서 말했듯 raw 값에 큰 의미가 있기 보다, rate와 함께 쓰이며 변화율 측정에 쓰인다. 수집된 데이터의 1초당 증가율을 계산한다.
rate(v range-vector) calculates the per-second average rate of increase of the time series in the range vector. [docs]
sum by (instance) ( node_cpu_seconds_total{job="haproxy_node", mode="user"})

단순 Counter 메트릭이고 user 모드에서 누적된 CPU 시간(초)를 보여준다.
sum by (instance) (rate(node_cpu_seconds_total{job="haproxy_node", mode="user"}[5m]))

rate 함수가 어떻게 적용되었을지 해석해보면, 평가 시점 직전 5분 동안 1초에 user mode 사용량이 얼마나 증가했는지 측정하는 것이다. 값이 1이면 1초에 CPU 사용 시간이 1코어라는 뜻이고, 이는 core 1개를 100% 쓴다는 의미다.
예를 들어,
08:00 → 1000초
08:05 → 1240초
5분간 240초 증가
240초 / 300초 = 0.8
따라서 rate = 0.8 (= 그 5분 동안 평균 80% 사용)
2.4.2. irate
irate도 counter의 초당 증가율을 구하지만, 구간 전체의 평균이 아니라 가장 최근 두 데이터 포인트만으로 순간(instant) 증가율을 계산한다.
irate(v range-vector) calculates the per-second instant rate of increase of the time series in the range vector. This is based on the last two data points. [docs]
irate(node_cpu_seconds_total{job="haproxy_node", mode="user"}[5m]) 여기서 [5m]은 rate처럼 "5분 동안 평균을 낸다"는 뜻이 아니다. irate는 윈도우 안에서 가장 최근 두 샘플만 골라 쓰므로, [5m]은 단지 "두 점을 찾기 위해 최대 5분까지 거슬러 본다"는 lookback 범위일 뿐이다. 그래서 윈도우 크기를 바꿔도([1m]이든 [5m]이든) 값은 거의 변하지 않는다 — 마지막 두 점이 같으면 결과도 같다.
계산식은 단순하다. 마지막 두 샘플을 (t1, v1), (t2, v2)라 하면
irate = (v2 - v1) / (t2 - t1)
rate와 달리 extrapolation 같은 양 끝 보정이 없고, 딱 이 두 점만으로 계산한다(counter reset 보정은 동일하게 적용).
✔ rate vs irate 비교
15초 간격으로 수집된 값이 [10, 20, 50, 80] (각각 10s, 25s, 40s, 55s 지점)라고 하자.
rate : 구간 전체(첫 10 ~ 끝 80)를 보고 extrapolation까지 적용 → 약 1.46
irate : 마지막 두 점만 → (80 - 50) / (55s - 40s) = 30 / 15 = 2.0
irate는 가장 최근 구간(40s→55s)의 가파른 변화를 그대로 반영해 2.0이 나오고, rate는 5분 전체를 평균 내 변화를 완만하게 보여준다.
✔ rate? irate?
공식 문서는 irate를 변동이 크고 빠르게 움직이는 counter를 그래프로 그릴 때만 쓰라고 권한다. 반대로 알림(alert)이나 느리게 변하는 counter에는 rate가 적합하다. irate는 순간값이라 그래프가 뾰족해지는데, 이러면 알림의 FOR 절이 짧은 변동에도 리셋되기 쉽고, 가끔 튀는 spike만 가득한 그래프는 읽기도 어렵기 때문이다.
다음주에 이어서 _over_time & join 연산 추가하기
'Monitoring > PromQL & Grafana' 카테고리의 다른 글
| [Monitoring] 시계열 데이터와 TSDB (0) | 2025.09.15 |
|---|