관측성 · 카디널리티 폭발 · 실습
라벨 하나를 더했더니 시계열이 백 배가 됐다
목표
지금 시계열이 무엇에 얼마나 쓰이는지 재고, 라벨을 더했을 때 얼마가 될지 먼저 예측한 뒤 실제로 띄워 확인하고, 예산 상한을 검사 스크립트로 못박아 무엇을 남기고 무엇을 버릴지 결정합니다.
왜 중요한가
카디널리티는 사고가 나기 전에는 아무도 보지 않는 비용이다. 라벨을 하나 더하는 일은 코드 한 줄이지만, 그 라벨의 값이 사용자 수만큼 늘어나면 시계열은 곱으로 늘고 저장과 메모리와 쿼리 시간이 함께 늘어난다. 그리고 그 비용은 '느려졌다' 는 모양으로 나타나서 원인을 찾기 어렵다. 그래서 두 가지를 습관으로 만들어야 한다 — 라벨을 더하기 전에 조합 수를 먼저 곱해 보는 것, 그리고 지표별 시계열 상한을 검사로 못박아 사람이 아니라 파이프라인이 막게 하는 것이다. 무엇을 버릴지 정할 때의 기준은 '이 라벨로 무엇을 결정하는가' 이고, 버린 축이 답하던 질문은 로그나 추적이 대신 답할 수 있는지 함께 확인해야 한다.
단계
1. /root/obs-cardinality-budget/inventory.tsv 를 만드세요. job="shop-api" 가 내보내는 지표마다 시계열이 몇 개인지를 <지표이름> <시계열 수> 두 칸으로, 많은 순서대로 모두 적습니다(다섯 줄입니다). 파드 전체가 아니라 이 잡 하나로 좁히는 이유는, Prometheus 자신이 내는 지표가 실습 도중에도 계속 늘어나 기준이 흔들리기 때문입니다.
2. /root/obs-cardinality-budget/labels.tsv 를 만드세요. 세 줄이고 각 줄은 <라벨이름> <서로 다른 값의 수> 입니다. le 와 handler 는 히스토그램 지표 http_request_duration_seconds_bucket 에서, status 는 카운터 http_requests_total 에서 셉니다(둘 다 job 은 shop-api). 많은 순서대로 적으세요. 그리고 /root/obs-cardinality-budget/02-note.txt 에 top_label=<1위 라벨 이름> 과 product=<le 값 수 × handler 값 수> 두 줄을 적으세요.
3. /root/obs-cardinality-budget/cost.txt 에 세 줄을 적으세요. series= 는 job="shop-api" 의 시계열 수, samples_per_day= 는 그 시계열이 하루에 남기는 표본 수, bytes_per_day= 는 그 표본이 차지하는 바이트입니다. 이 파드의 스크레이프 간격은 15초이고, 표본 하나는 평균 2바이트로 잡습니다(Prometheus 저장소 문서가 말하는 1바이트에서 2바이트 사이 중 보수적인 쪽). 세 값은 서로 곱셈으로 맞아떨어져야 합니다.
4. 새 지표 checkout_requests_total 을 내보내려 합니다. 라벨은 region(ap1·ap2·us1·eu1), tier(free·pro·team), endpoint(cart·pay·refund·ship·track) 셋입니다. /root/obs-cardinality-budget/forecast.txt 에 formula= 와 predicted_series= 두 줄을 적으세요. formula 는 곱셈식(예: 2*3*4), predicted_series 는 그 결과입니다. 아직 아무것도 띄우지 마세요.
5. /root/obs-cardinality-budget/exporter.py 를 만드세요. 인자로 --print 를 주면 노출 텍스트를 표준 출력에 찍고 끝나고, 인자가 없으면 127.0.0.1:9102 에서 /metrics 를 서비스합니다. 4단계의 세 라벨 조합마다 checkout_requests_total 한 줄을 냅니다. 환경 변수 USERS 가 0 보다 크면 user_id 라벨이 붙습니다. 그다음 /etc/prometheus/prometheus.yml 에 cardinality-lab 이라는 스크레이프 잡을 더하고 설정을 다시 읽히세요. 첫 스크레이프가 끝난 뒤 /root/obs-cardinality-budget/measured.txt 에 measured_series=<수> 한 줄을 적습니다.
6. USERS=100 으로 exporter 를 다시 띄워 user_id 라벨을 붙이세요. /root/obs-cardinality-budget/explode.tsv 에 두 줄을 적습니다. 각 줄은 탭으로 나눈 세 칸 <이름> <시계열 수> <하루 바이트> 이고, 첫 줄의 이름은 before(user_id 없이), 둘째 줄은 after(user_id 포함)입니다. 하루 바이트는 3단계와 같은 방식(시계열 × 5760 × 2)으로 계산합니다.
7. /root/obs-cardinality-budget/budget.sh 를 만드세요. bash budget.sh <상한표> 로 부르면 상한표의 각 줄 (<지표이름> <최대 시계열 수>)마다 지금 시계열 수를 세어 <지표이름> <지금> <상한> OK 또는 ... OVER 를 한 줄씩 출력하고, 하나라도 상한을 넘으면 종료 코드 1, 아니면 0 으로 끝납니다. 그리고 /root/obs-cardinality-budget/caps.tsv 에 checkout_requests_total 의 상한을 500 으로 적고 그 표로 한 번 돌려 출력을 /root/obs-cardinality-budget/gate-out.txt 에 저장하세요.
8. 시계열 상한이 500 입니다. /root/obs-cardinality-budget/decision.txt 에 네 줄을 적으세요. keep= 에는 남길 라벨 이름을 쉼표로(예: region,tier), drop= 에는 버릴 라벨 이름을 쉼표로, projected_series= 에는 남긴 라벨만으로 계산한 시계열 수를, lost_question= 에는 그 라벨을 버렸을 때 더 이상 답할 수 없게 되는 질문을 30자 이상으로 적습니다. 라벨의 값 개수는 region 4 · tier 3 · endpoint 5 · user_id 100 입니다.
참고
- 작업 디렉터리는
/root/obs-cardinality-budget입니다. - TSDB 상태:
curl -s 'http://127.0.0.1:9090/api/v1/status/tsdb?limit=5' | jq—headStats·seriesCountByMetricName·labelValueCountByLabelName을 봅니다. - 설정 반영:
curl -X POST http://127.0.0.1:9090/-/reload. 스크레이프 간격이 15초라 첫 값까지 조금 걸립니다. - 흔한 실수:
count()결과가 비었을 때 0 으로 보지 않아 게이트가 오류로 끝나는 것. - 흔한 실수: 라벨 값이 독립이라고 가정하고 곱했는데 실제로는 일부 조합만 존재하는 경우 — 예측은 상한이고, 실측이 그보다 작으면 그 차이도 정보입니다.
- [Storage](https://prometheus.io/docs/prometheus/latest/storage/) · [TSDB status API](https://prometheus.io/docs/prometheus/latest/querying/api/) · [Instrumentation practices](https://prometheus.io/docs/practices/instrumentation/) · [Naming](https://prometheus.io/docs/practices/naming/)
단계 8개
- 한 서비스의 시계열이 어디에 쓰이는지 센다
- 어느 라벨이 값을 많이 만드는가
- 이 서비스의 지표가 하루에 얼마를 먹는가
- 재기 전에 먼저 예측한다
- exporter 를 띄워 예측을 확인한다
- 라벨 하나가 시계열을 백 배로 만든다
- 상한을 코드로 못박는다
- 예산 안에 들어가려면 무엇을 버릴 것인가