본문 바로가기
IT/Cloud

[Prometheus] Label 하나가 시스템을 느리게 만든다 - 2. Cardinality

by Jany 2026. 6. 21.
반응형

1편 [Prometheus] 왜 다들 프로메테우스를 사용할까

2편 [Prometheus] Label 하나가 시스템을 느리게 만든다 -  1. Metric, Label, TimeSeries


2편에서는 Prometheus에서 TimeSeries가 어떻게 만들어지는지 봤다.

핵심은 이 흐름이었다.

Metric → Label 조합 → TimeSeries

Prometheus에서 같은 Metric이라도 Label 조합이 다르면 다른 TimeSeries가 된다.

이번 글에서는 이 Label 조합이 많아질 때 발생하는 문제를 다룬다.

바로 Cardinality다.

 

1. Cardinality란 무엇인가

Cardinality는 쉽게 말하면 값의 종류 또는 조합의 개수다.

Prometheus에서는 보통 TimeSeries 개수를 이야기할 때 Cardinality라는 표현을 쓴다.

예를 들어 다음 Metric이 있다고 하자.

http_requests_total{method, status}

method 값이 3개라고 해보자.

GET
POST
PUT

status 값이 4개라고 해보자.

200
400
404
500

그러면 만들어질 수 있는 TimeSeries는 다음과 같다.

3 × 4 = 12개

여기까지는 큰 문제가 없다.

하지만 여기에 path Label이 추가되면 이야기가 달라진다.

http_requests_total{method, status, path}

만약 path 값이 100개라면 TimeSeries는 이렇게 늘어난다.

3 × 4 × 100 = 1,200개

Label 하나가 추가됐을 뿐인데 TimeSeries가 12개에서 1,200개가 됐다.

이것이 Cardinality 문제의 시작이다.

 

2. 위험한 Label은 값이 계속 늘어나는 Label이다

모든 Label이 위험한 것은 아니다.

문제가 되는 것은 값의 종류가 계속 늘어나는 Label이다.

예를 들어 이런 Label은 조심해야 한다.

user_id
request_id
session_id
trace_id
email
ip
order_id
pod_uid
container_id

이 값들은 요청마다, 사용자마다, 배포마다 계속 달라질 수 있다.

예를 들어 다음 Metric을 보자.

api_request_duration_seconds{method="GET", status="200", user_id="12345"}

처음에는 유용해 보인다.

사용자별 요청 지연 시간을 볼 수 있기 때문이다.

하지만 사용자가 100만 명이라면 어떻게 될까?

method 5개 × status 10개 × user_id 1,000,000개
= 50,000,000 TimeSeries

Metric은 하나다.

하지만 TimeSeries는 수천만 개가 될 수 있다.

Prometheus가 느려지는 이유는 숫자 값이 많아서만이 아니다.

그 숫자를 구분하는 Label 조합이 너무 많기 때문이다.

 

3. path Label도 위험할 수 있다

운영 환경에서 자주 만나는 문제 중 하나가 path Label이다.

다음과 같은 Label은 위험하다.

path="/users/12345/orders/98765"

URL 안에 사용자 ID나 주문 ID가 들어가면 요청마다 새로운 path 값이 생긴다.

이렇게 되면 path Label의 값이 계속 늘어난다.

Prometheus에서는 이런 값을 그대로 Label로 쓰면 안 된다.

더 좋은 방식은 route 형태로 정규화하는 것이다.

route="/users/{user_id}/orders/{order_id}"

이렇게 하면 실제 ID가 달라도 같은 route로 묶을 수 있다.

/users/12345/orders/98765
/users/77777/orders/11111
/users/99999/orders/22222

→ /users/{user_id}/orders/{order_id}

Prometheus Metric은 개별 요청을 추적하는 도구가 아니다.

개별 요청은 Log나 Trace에서 보는 것이 맞다.

Prometheus는 집계 가능한 숫자를 다루는 데 적합하다.

 

4. Cardinality가 높으면 왜 느려질까

Cardinality가 높아지면 Prometheus가 관리해야 할 TimeSeries가 많아진다.

TimeSeries가 많아지면 비용도 같이 늘어난다.

메모리를 더 많이 사용한다.

저장해야 할 메타데이터가 늘어난다.

쿼리할 때 읽어야 하는 Series가 많아진다.

Grafana 대시보드도 느려진다.

예를 들어 다음 쿼리를 보자.

sum(rate(http_requests_total[5m]))

이 쿼리는 http_requests_total에 해당하는 TimeSeries를 읽어야 한다.

이 Metric의 TimeSeries가 100개라면 큰 문제가 없을 수 있다.

하지만 100만 개라면 계산해야 할 대상이 완전히 달라진다.

특히 다음처럼 Cardinality가 높은 Label로 그룹핑하면 더 무거워진다.

sum by (method, status, path) (
  rate(http_requests_total[5m])
)

path 값이 많으면 결과도 많아지고 계산 비용도 커진다.

느린 쿼리는 PromQL만의 문제가 아니다.

Metric과 Label 설계가 잘못되어 있으면 어떤 쿼리도 무거워질 수 있다.

 

5. 좋은 Label과 나쁜 Label

좋은 Label은 값의 종류가 제한적이다.

예를 들면 이런 Label이다.

method="GET"
status="200"
region="ap-northeast-2"
service="payment"
environment="prod"

이런 값은 종류가 어느 정도 정해져 있다.

반대로 나쁜 Label은 값이 계속 늘어난다.

user_id="12345"
request_id="abc-123"
session_id="xyz-999"
trace_id="..."
email="user@example.com"

기준은 단순하다.

이 Label 값이 앞으로 몇 개까지 늘어날 수 있는가?

수십 개 수준이라면 괜찮을 수 있다.

수천 개라면 조심해야 한다.

사용자 수만큼, 요청 수만큼, 객체 수만큼 늘어난다면 Prometheus Label로 쓰면 안 된다.

 

6. Kubernetes에서는 더 조심해야 한다

Kubernetes 환경에서는 Cardinality가 더 쉽게 늘어난다.

Pod, Container, Node, Namespace 같은 Label이 자연스럽게 붙기 때문이다.

예를 들어 다음과 같은 Metric이 있다.

container_cpu_usage_seconds_total{
  namespace="default",
  pod="api-server-7d9f8c9b5c-xk2lm",
  container="app"
}

여기까지는 흔한 형태다.

하지만 여기에 pod_uid, container_id, image_id 같은 값이 붙으면 Cardinality가 더 빠르게 늘어난다.

이 값들은 배포나 재시작 때마다 바뀔 수 있다.

운영에서 중요한 질문은 보통 이런 것이다.

서비스별 CPU 사용량은?

Namespace별 메모리 사용량은?

Deployment별 요청 수는?

5xx 응답이 많은 서비스는?

이 질문에 답하는 데 pod_uidcontainer_id가 꼭 필요한지 생각해야 한다.

필요 없다면 수집하지 않거나, 더 낮은 Cardinality의 Metric으로 집계해서 보는 것이 좋다.

 

마무리

Prometheus에서 Cardinality는 단순히 숫자가 많은 문제가 아니다.

Label 조합이 많아지는 문제다.

Label 조합이 많아지면 TimeSeries가 늘어난다.

TimeSeries가 늘어나면 메모리, 저장소, 쿼리 비용이 같이 늘어난다.

그래서 Prometheus를 운영할 때는 항상 이 질문을 해야 한다.

이 Label은 정말 필요한가?

이 Label 값은 얼마나 많이 늘어날 수 있는가?

Prometheus는 숫자를 저장하는 시스템처럼 보인다.

하지만 운영 관점에서는 Label 조합을 저장하는 시스템에 가깝다.

Label 하나가 TimeSeries를 만들고, TimeSeries가 Cardinality를 만들고, Cardinality가 Prometheus의 성능을 결정한다.

반응형

댓글