지난 시간에 프로메테우스의 데이터 모델과 PromQL의 기본 문법을 배웠습니다. 이제부터는 PromQL의 진짜 힘을 보여주는 핵심 함수와 연산자들을 통해, 흩어져 있는 메트릭 데이터에서 의미 있는 인사이트를 추출하는 방법을 배우게 됩니다. 이 장을 마스터하면 여러분은 그라파나 대시보드를 자유자재로 만들 수 있는 능력을 갖추게 될 것입니다.
고급 함수를 배우기 전에, PromQL의 가장 기본이 되는 두 가지 벡터 타입을 다시 한번 상기해야 합니다. 모든 PromQL 함수는 이 두 타입 중 하나를 입력으로 받기 때문입니다.
http_requests_total 처럼 메트릭 이름만 쓰면 순간 벡터가 반환됩니다.http_requests_total[5m] 처럼 메트릭 이름 뒤에 [시간]을 붙여 표현합니다. rate(), increase() 같은 함수는 반드시 범위 벡터를 입력으로 받습니다.Counter는 '총 요청 수'처럼 계속 누적되는 값이라 그 자체로는 의미를 파악하기 어렵습니다. "그래서 초당 요청 수(RPS)가 얼마인데?" 와 같은 질문에 답하기 위해 아래 함수들을 사용합니다.
| 함수 | 설명 | 언제 사용하나? |
|---|---|---|
rate(v[d]) |
시간 범위 d 동안의 초당 평균 증가율을 계산합니다. 가장 널리 사용되는 함수입니다. |
- "지난 5분간의 평균 RPS는 얼마인가?" - 그래프로 표현했을 때 부드러운 추세를 보고 싶을 때. |
increase(v[d]) |
시간 범위 d 동안의 총 증가량을 계산합니다. |
- "지난 1시간 동안 발생한 총 에러 수는?" - increase(errors_total[1h]) |
irate(v[d]) |
시간 범위 d 내의 마지막 두 데이터 포인트만을 사용하여 순간적인 초당 증가율을 계산합니다. |
- 짧은 순간의 피크(peak)를 민감하게 감지하고 싶을 때. - 그래프가 매우 뾰족하게(spiky) 나타나므로, 알림(Alerting) 설정에 유용할 수 있음. |
rate(metric[5m])는 지난 5분간의 데이터를 모두 고려하여 평균적인 변화율을 계산하므로, 마치 '5분간의 평균 심박수'처럼 안정적인 추세를 보여줍니다.
반면 irate(metric[5m])는 5분 범위 내에서 가장 최근의 두 데이터 포인트만 보고 변화율을 계산합니다. 이는 '지금 이 순간의 심박수'를 재는 것과 같아서, 갑작스러운 변화를 즉시 포착할 수 있지만 값이 계속 크게 튈 수 있습니다. 일반적인 대시보드에는 rate()를 사용하는 것이 좋습니다.
`[5m]`이라는 시간 범위가 주어졌을 때, PromQL 함수가 실제로 어떻게 동작하는지 단계별로 살펴보겠습니다.
가정: http_requests_total 메트릭이 1분 간격으로 수집되고, 지난 5분 동안 아래와 같은 값을 가졌다고 가정해봅시다.
| 수집 시간 | http_requests_total 값 (누적) |
|---|---|
| 00:00 | 1000 |
| 01:00 | 1030 |
| 02:00 | 1050 |
| 03:00 | 1100 |
| 04:00 | 1140 |
| 05:00 | 1180 |
1. increase(http_requests_total[5m]) 계산 과정
increase는 주어진 시간 범위 내에서 순수하게 '총 증가량'만 계산합니다. 즉, (마지막 값 - 첫 값) 입니다.
1180 (05:00의 값) - 1000 (00:00의 값) = 180
결과: 180. "지난 5분 동안 총 180개의 요청이 더 들어왔다"는 의미입니다.
2. rate(http_requests_total[5m]) 계산 과정
rate는 총 증가량을 시간 범위(초 단위)로 나누어 '초당 평균 증가율'을 계산합니다.
(1180 - 1000) / (5 * 60초) => 180 / 300 = 0.6
결과: 0.6. "지난 5분 동안 초당 평균 0.6개의 요청(RPS)이 처리되었다"는 의미입니다.
3. irate(http_requests_total[5m]) 계산 과정
irate는 '순간' 증가율을 계산하기 위해, 주어진 시간 범위 내에서 가장 마지막 두 개의 데이터 포인트만을 사용합니다. 이 예시에서는 04:00과 05:00의 데이터입니다.
(1180 - 1140) / ( (05:00의 타임스탬프 - 04:00의 타임스탬프) 초 단위 ) => 40 / 60초 = 0.667
결과: 0.667. "가장 최근 샘플링 구간(04:00~05:00)의 순간적인 RPS는 약 0.67이다"라는 의미입니다. `rate`의 평균값 0.6보다 더 최근의 변화에 민감하게 반응하는 것을 볼 수 있습니다.
[5m]은 "5분치 데이터를 모두 사용하라"는 의미가 아니라, "쿼리 시점으로부터 최대 5분 전까지 거슬러 올라가서 마지막 두 개의 데이터 포인트를 찾아라"는 '탐색 범위'를 의미합니다.
irate(metric[30s])처럼 시간 범위를 너무 짧게 잡으면, 30초 안에는 데이터 포인트가 하나밖에 없을 수 있습니다. 이 경우 irate는 계산을 할 수 없습니다.따라서, 안정적인 계산을 위해 시간 범위는 최소한 scrape 간격의 2배 이상으로 설정하는 것이 일반적인 규칙입니다. 이렇게 해야 네트워크 지연 등으로 scrape가 약간 늦어지더라도 최소 2개의 데이터 포인트를 확보할 수 있습니다.
이것도 아주 날카로운 질문입니다. 이론적으로는 그게 더 효율적이겠죠. 하지만 PromQL의 동작 방식을 이해해야 합니다. PromQL은 '데이터 선택'과 '함수 실행'이라는 두 단계로 동작합니다.
metric[5m] 부분이 먼저 실행됩니다. 이 단계에서 프로메테우스는 지정된 시간 범위(5분) 내의 모든 데이터 포인트를 일단 메모리로 가져옵니다. 함수가 무엇인지는 아직 신경 쓰지 않습니다.irate() 함수는 1단계에서 전달받은 '5분치 데이터 묶음'을 가지고, 그중에서 마지막 두 개의 포인트만 골라서 계산을 수행합니다. 나머지 앞선 데이터들은 그냥 버려집니다.
비유하자면, "지난주 서류 뭉치 전부 다 가져와!" ([1w]) 라고 시킨 뒤, 막상 서류를 받아보고는 "음, 어제랑 오늘 것만 보면 되겠네" 하고 나머지는 보지 않는 것과 같습니다. 먼저 '가져오는' 행위가 끝나야 '보는' 행위를 할 수 있는 것이죠.
이러한 설계는 PromQL의 일관성과 예측 가능성을 높여줍니다. 어떤 함수를 쓰든 '데이터 선택' 단계는 동일하게 동작하기 때문입니다.
수많은 시계열 데이터를 의미 있는 단위로 묶어서 합계, 평균 등을 계산할 때 사용합니다. sum, avg, count, min, max, topk(상위 k개), bottomk(하위 k개) 등이 있습니다.
이때 by와 without 키워드를 사용하여 어떤 레이블을 기준으로 그룹화할지 결정합니다.
# 1. 전체 클러스터의 총 RPS 계산
sum(rate(http_requests_total[5m]))
# 2. 각 API 엔드포인트(handler) 별로 RPS 계산
# by (handler) : handler 레이블은 남기고, 나머지 레이블은 버리고 합산
sum by (handler) (rate(http_requests_total[5m]))
# 3. 각 인스턴스(서버)를 제외하고, 서비스 전체의 평균 응답 시간 계산
# without (instance) : instance 레이블만 버리고, 나머지 레이블 기준으로 평균 계산
avg without (instance) (http_request_duration_seconds)
# 4. CPU 사용률이 가장 높은 5개의 파드(pod) 찾기
topk(5, pod_cpu_usage_seconds_total)
"API 평균 응답 시간은 100ms" 라는 말보다 "API 응답 시간의 99%는 500ms 미만" 이라는 말이 훨씬 유용합니다. 이렇게 백분위수(Percentile)를 계산하기 위해 사용하는 것이 바로 Histogram 타입의 메트릭과 histogram_quantile() 함수입니다.
Histogram 메트릭은 _bucket이라는 접미사를 가지며, le(less than or equal)이라는 특별한 레이블을 가집니다.
# http_request_duration_seconds_bucket{le="0.1"} 20 => 0.1초 이하로 걸린 요청이 20개
# http_request_duration_seconds_bucket{le="0.5"} 50 => 0.5초 이하로 걸린 요청이 50개
이 누적 데이터를 사용하여 p99(99번째 백분위수)를 계산하는 쿼리는 다음과 같습니다.
# 각 핸들러별로 99%의 요청이 몇 초 안에 처리되었는지 계산
histogram_quantile(
0.99,
sum by (le, handler) (
rate(http_request_duration_seconds_bucket[5m])
)
)
전교생의 키 분포를 알고 싶을 때, 한 명 한 명의 키를 모두 기록하는 것은 비효율적입니다. 대신, "160cm 이하 몇 명", "170cm 이하 몇 명", "180cm 이하 몇 명" 과 같이 구간별로 인원수(_bucket)를 세는 것과 같습니다. histogram_quantile(0.95, ...)는 이 분포표를 보고 "상위 5%를 제외한 학생들의 키는 몇 cm인가요?" 라고 묻는 것과 같습니다.
마치 SQL의 JOIN처럼, 서로 다른 메트릭을 레이블 기준으로 합쳐서 계산하는 PromQL의 가장 강력한 기능입니다. 하지만 처음에는 가장 헷갈리는 부분이기도 합니다. 예시를 통해 차근차근 이해해봅시다.
벡터 매칭에는 크게 두 가지 관계가 있습니다.
가장 간단한 경우입니다. 예를 들어 '전체 요청 수'와 '실패한 요청 수'를 가지고 '에러율'을 계산해봅시다.
# 메트릭 예시
http_requests_total{instance="A", handler="/api/users"} 100
http_requests_failed_total{instance="A", handler="/api/users"} 5
이 두 메트릭은 instance와 handler라는 레이블을 똑같이 가지고 있습니다. 따라서 프로메테우스는 자동으로 같은 레이블을 가진 시계열끼리 짝을 지어 계산합니다.
# 에러율 계산 (지난 5분간)
rate(http_requests_failed_total[5m]) / rate(http_requests_total[5m])
만약 레이블이 다르다면? on(label)이나 ignoring(label)을 사용합니다. 예를 들어, http_requests_total에만 version 레이블이 있다면, version을 무시하고 계산해야 합니다.
# version 레이블을 무시하고 instance, handler로 매칭
rate(http_requests_failed_total[5m]) / ignoring(version) rate(http_requests_total[5m])
이것이 group_left와 group_right가 필요한 진짜 이유입니다. 'Pod별 요청 수' 메트릭에 '어떤 Node에서 실행 중인지'라는 정보를 붙여보고 싶다고 가정해봅시다.
# 요청 수 메트릭 (레이블: pod, namespace) - Many 쪽
http_requests_total{pod="A", namespace="prod"} 200
http_requests_total{pod="B", namespace="prod"} 300
# Pod 정보 메트릭 (레이블: pod, namespace, node) - One 쪽 (정보를 제공)
# 이 메트릭의 값은 보통 1이며, 레이블 자체가 정보입니다.
kube_pod_info{pod="A", namespace="prod", node="node-1"} 1
kube_pod_info{pod="B", namespace="prod", node="node-2"} 1
우리의 목표는 http_requests_total 결과에 node 레이블을 추가하는 것입니다. 즉, 정보가 많은 쪽(Many)인 http_requests_total을 기준으로, 정보가 적은 쪽(One)인 kube_pod_info의 node 레이블을 가져와야 합니다.
# http_requests_total 결과에 node="node-1", node="node-2" 레이블을 붙여보자!
rate(http_requests_total[5m]) * on(pod, namespace) group_left(node) kube_pod_info
*: 곱셈 연산자를 사용해 두 벡터를 합칩니다. kube_pod_info의 값은 1이므로 결과값에 영향을 주지 않습니다.on(pod, namespace): pod와 namespace 레이블이 같은 것끼리 짝을 지으라는 의미입니다.group_left(node): 가장 중요한 부분입니다. "왼쪽(left) 벡터인 http_requests_total을 기준으로 매칭하고, 오른쪽(right) 벡터인 kube_pod_info에서 node 레이블을 가져와서 결과에 포함시켜줘!" 라는 의미입니다.이 쿼리의 결과는 다음과 같은 형태가 됩니다. node 레이블이 성공적으로 추가되었죠.
# 쿼리 결과 (가상)
{pod="A", namespace="prod", node="node-1"} 0.5 # 초당 0.5 요청
{pod="B", namespace="prod", node="node-2"} 0.8 # 초당 0.8 요청
이제 sum by (node) (...) 같은 집계 연산을 통해 '노드별 총 요청 수' 같은 고급 정보를 만들어낼 수 있습니다.
위 쿼리가 실제로 그라파나의 테이블 패널에서 어떻게 보이는지 확인하면 group_left의 마법을 직관적으로 이해할 수 있습니다.
1. 쿼리 A: rate(http_requests_total[5m]) 결과
| pod | namespace | Value (RPS) |
|---|---|---|
| A | prod | 0.5 |
| B | prod | 0.8 |
2. 쿼리 B: kube_pod_info 결과
| pod | namespace | node | Value |
|---|---|---|---|
| A | prod | node-1 | 1 |
| B | prod | node-2 | 1 |
3. 최종 쿼리: 쿼리 A * on(pod, namespace) group_left(node) 쿼리 B 결과
on(pod, namespace)로 두 테이블을 조인하고, group_left(node)를 통해 쿼리 B의 node 레이블을 쿼리 A에 붙여줍니다. 그 결과, 아래와 같이 node 레이블이 추가된 새로운 테이블이 만들어집니다.
| pod | namespace | node | Value (RPS) |
|---|---|---|---|
| A | prod | node-1 | 0.5 |
| B | prod | node-2 | 0.8 |
Q: "PromQL에서 `rate()`와 `increase()`의 차이점은 무엇이고, 각각 어떤 상황에서 사용해야 하나요?"
A: "`rate()`와 `increase()`는 둘 다 Counter 타입의 메트릭을 처리하는 함수이지만, 반환하는 값의 의미가 다릅니다. `rate(metric[5m])`는 지난 5분간의 데이터를 기반으로 '초당 평균 증가율'을 계산합니다. 따라서 RPS(초당 요청 수)처럼 시간에 따른 변화율을 그래프로 보거나 알림을 설정할 때 유용합니다. 반면, `increase(metric[1h])`는 지난 1시간 동안의 '총 증가량'을 계산합니다. 이는 "지난 1시간 동안 총 몇 개의 에러가 발생했는가?" 와 같이 특정 기간 동안의 누적치를 확인할 때 사용합니다. 즉, 추세를 볼 때는 `rate()`, 총량을 볼 때는 `increase()`를 사용합니다."
Q: "우리 서비스의 API 응답 시간 p99 값을 PromQL로 어떻게 계산할 수 있을까요? 필요한 메트릭 타입과 쿼리를 설명해주세요."
A: "API 응답 시간의 p99 값을 계산하기 위해서는 먼저 메트릭을 `Histogram` 타입으로 수집해야 합니다. Histogram은 `http_request_duration_seconds_bucket`과 같이 `_bucket` 접미사와 `le` 레이블을 가진 메트릭을 생성합니다. 이 메트릭을 사용하여 `histogram_quantile()` 함수로 p99 값을 계산할 수 있습니다.
쿼리는 다음과 같습니다: `histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))`. 이 쿼리는 먼저 `rate()` 함수로 지난 5분간 각 버킷의 초당 증가율을 계산하고, `sum by (le)`로 다른 레이블들을 제거하여 순수한 분포를 만듭니다. 마지막으로 `histogram_quantile()` 함수가 이 분포 데이터를 보고 99%의 요청이 몇 초 안에 처리되었는지를 계산해줍니다."
Q: "CPU 사용량이 가장 높은 상위 5개의 컨테이너를 찾는 PromQL 쿼리를 작성해주세요."
A: "`topk()`라는 집계 연산자를 사용하면 간단하게 찾을 수 있습니다. 먼저 `rate(container_cpu_usage_seconds_total[1m])` 쿼리를 통해 각 컨테이너의 분당 평균 CPU 사용 시간을 계산합니다. 그런 다음, 이 결과를 `topk()` 함수에 넣어주면 됩니다. 전체 쿼리는 `topk(5, rate(container_cpu_usage_seconds_total[1m]))` 입니다. 이 쿼리는 모든 컨테이너 중 CPU 사용률이 가장 높은 상위 5개의 시계열 데이터를 반환해줍니다."