ラベルにuser_idを入れてはいけない理由
한국어 원문으로 표시합니다.
한 줄 요약
메트릭의 시계열 개수는 라벨 값 조합의 곱입니다. 라벨 하나를 잘못 고르면 시계열이 수백만 개가 되고, 그 순간 모니터링 시스템이 관측 대상보다 먼저 죽습니다.
왜 이게 필요했나
http_requests_total 에 라벨을 붙입니다.
http_requests_total{method, status, endpoint}
method: 5가지 (GET, POST, PUT, DELETE, PATCH)status: 8가지endpoint: 40가지
시계열 개수는 5 × 8 × 40 = 1,600개. 괜찮습니다.
여기에 "사용자별로 보고 싶다" 는 요청이 들어와 user_id 를 추가합니다.
사용자가 10만 명이면,
5 × 8 × 40 × 100,000 = 1억 6천만 개.
프로메테우스는 시계열마다 메모리에 인덱스를 유지합니다. 서버가 OOM 으로 죽고, 그러면 장애를 관측할 수단이 장애 때문에 사라집니다.
카디널리티 폭발을 만드는 라벨들
절대 라벨에 넣지 말아야 할 것들입니다.
| 라벨 | 왜 위험한가 |
|---|---|
user_id, session_id |
사용자 수만큼 증가. 상한이 없다 |
request_id, trace_id |
요청마다 고유. 무한 |
email, ip |
사실상 무한 + 개인정보 |
timestamp |
시간마다 새 시계열 (시계열에 시간을 넣는 모순) |
url (쿼리스트링 포함) |
?page=1, ?page=2… 무한 |
| 에러 메시지 전문 | 메시지에 값이 섞이면 무한 |
특히 마지막. error="connection to 10.0.3.17:5432 timed out" 처럼
IP·포트가 들어간 메시지를 라벨로 쓰면 조합이 폭발합니다.
error="db_timeout" 처럼 분류된 값을 씁니다.
판단 기준 — 이 라벨의 값은 몇 가지인가
라벨을 추가하기 전에 스스로 묻습니다.
- 가능한 값이 몇 개인가? 세어서 답할 수 있어야 합니다. "많아요" 는 답이 아닙니다.
- 시간이 지나면 늘어나는가? 늘어난다면 상한이 있는가?
- 이 라벨로 실제 알림을 만들 것인가? 대시보드에서 한 번 볼 것 같다면 메트릭이 아니라 로그나 트레이스로 가야 합니다.
경험칙으로 라벨 하나당 값 100개 이하, 메트릭 하나당 시계열 1만 개 이하를 목표로 잡습니다.
URL 은 반드시 정규화한다
# 나쁨 — 주문 개수만큼 시계열
endpoint="/api/orders/8f3a91"
# 좋음 — 라우트 패턴
endpoint="/api/orders/:id"
프레임워크의 라우트 정의를 쓰면 자동으로 정규화됩니다. 직접 문자열을 쓰면 반드시 폭발합니다. 이건 실수의 문제가 아니라 시간 문제입니다.
세 신호를 나눠 쓴다
높은 카디널리티 정보가 정말 필요할 때가 있습니다. "이 사용자의 요청이 왜 느렸나" 같은 질문입니다. 그건 메트릭의 일이 아닙니다.
| 신호 | 카디널리티 | 답하는 질문 |
|---|---|---|
| 메트릭 | 낮아야 함 | "얼마나 많이/빠르게?" 추세와 알림 |
| 로그 | 높아도 됨 | "그때 무슨 일이?" 개별 사건 |
| 트레이스 | 높아도 됨 | "시간이 어디서 갔나?" 요청 하나의 경로 |
메트릭으로 알림을 받고 → 시간을 특정하고 → 로그·트레이스로 개별 요청을 파고듭니다. 세 신호는 대체재가 아니라 조사 순서입니다.
이미 폭발했다면
- 어떤 메트릭인지 찾습니다. 프로메테우스의 TSDB 상태 페이지에 시계열이 많은 메트릭·라벨 순위가 나옵니다.
- 수집 단계에서 떨어뜨립니다. relabel 설정으로 문제 라벨을 제거하거나 해당 메트릭 자체를 drop 합니다. 애플리케이션 배포를 기다릴 필요가 없습니다.
- 애플리케이션을 고칩니다. 근본 해결은 라벨을 안 붙이는 것입니다.
- 보존과 샤딩을 조정합니다. 급한 불을 끄는 용도이지 해결책은 아닙니다.
현장에서 만나는 모습
- 프로메테우스가 주기적으로 OOM → 새로 추가된 라벨을 의심.
- 대시보드 쿼리가 30초씩 걸림 → 시계열이 너무 많아 스캔 범위가 큼.
- 배포 직후 메모리가 계단식으로 상승 → 그 배포에 라벨이 추가됐다.
이미 터진 뒤에 되돌리는 순서
카디널리티는 늘 사고가 난 뒤에 알게 된다. 프로메테우스가 메모리를 다 쓰고 죽거나, 질의가 시간 초과로 안 돌아온다. 그때 순서가 있다.
먼저 무엇이 얼마나 차지하는지 센다. 프로메테우스의 상태 화면(/tsdb-status)에
가장 많은 시계열을 만든 지표와 라벨이 나온다. 명령으로도 볼 수 있다.
topk(10, count by (__name__)({__name__=~".+"}))
count(app_request_duration_seconds_bucket)
count(count by (user_id)(app_request_total))
마지막 줄이 그 라벨의 실제 값 가지 수다. 이 숫자가 수천이면 그 라벨 하나가 원인이다.
막는 것은 저장 직전에 한다. 애플리케이션을 고쳐 배포하는 것이 정답이지만 시간이 걸린다. 그동안은 스크레이프 설정에서 라벨을 지우거나 지표를 통째로 버린다.
metric_relabel_configs:
- source_labels: [__name__]
regex: 'app_request_total'
target_label: user_id
replacement: '' # 라벨 값을 비운다
- source_labels: [__name__]
regex: 'debug_.*'
action: drop # 이 지표는 아예 저장하지 않는다
이미 저장된 시계열은 저절로 사라지지 않는다. 새로 안 들어와도 보존 기간
동안 남아 메모리를 쓴다. 급하면 관리 API 로 지운다(--web.enable-admin-api 가
켜져 있어야 하고, 지우면 되돌릴 수 없다).
히스토그램은 조용히 배가 된다. 버킷 하나가 곧 시계열 하나다. 버킷 12개짜리 히스토그램에 라벨 세 개를 곱하면 순식간에 수만이 된다. 라벨을 줄일 수 없다면 버킷 수를 먼저 줄인다.
같은 실수를 막는 것은 배포 전 검사다. 지표 이름과 라벨 목록을 코드에서 뽑아, 허용 목록에 없는 라벨이 붙으면 CI 에서 막는다. 사람의 주의력에 기대면 바쁜 주에 반드시 다시 들어온다.
다음 확인에서 볼 것
이어지는 퀴즈에서는 라벨 값의 곱으로 시계열 수를 계산하고, 메트릭과 로그·트레이스의 역할을 구분합니다. 이미 폭발이 시작된 상황에서 수집 단계 완화와 애플리케이션의 근본 수정 중 무엇을 먼저 적용할지도 판단해 보세요.