광고
~ / posts / developer
smith@lab:~/posts$ cat developer/prometheus-scrape-interval-retention-cardinality-sizing-guide.md

프로메테우스 용량 계산, scrape_interval·보존 기간·시계열 수 정하는 법

프로메테우스를 처음 올릴 때 scrape_interval은 15초로 할지 1분으로 할지, 디스크는 몇 GB를 잡을지, 보존 기간은 며칠로 할지부터 막히죠. 결론부터 말하면 디스크는 "활성 시계열 수 ÷ 간격 × 보존 기간(초) × 1~2바이트"로 어림하고, 간격보다 시계열 수를 먼저 줄이는 게 훨씬 효과가 커요. 50만 시계열을 15초 간격으로 15일 보관하면 블록만 40~81GiB이고, 디스크는 그보다 20% 넉넉하게 잡아요.

스크레이프·TSDB·rate()가 안에서 어떻게 도는지는 알고 있다고 보고, 이 글은 설정값을 고르는 데 필요한 숫자만 다뤄요. 시계열 수 세는 법, 간격·보존 기간 판단표, rate() 범위 고르기, 그래프가 비었을 때의 확인 순서, 도입 전에 따질 점 순서예요. 기준은 2026-10-26 현재 최신 안정판 Prometheus 3.15.1과 공식 문서 latest예요.

광고

먼저 셀 것, 내 서버의 시계열 수

용량 계산의 첫 칸은 활성 시계열 수예요. 시계열 하나는 지표 이름과 라벨 값 조합 하나라서, 라벨이 붙은 지표의 시계열 수는 라벨마다 나올 수 있는 값 개수를 곱한 값이에요.

예시로 웹 서버 파드 20개가 요청 지연 히스토그램 하나를 노출한다고 해 볼게요. 라벨은 method 4종, code 5종, route 30종이에요.

같은 라벨로 카운터 하나만 두면 600 × 20 = 12,000개예요. 지표 하나가 히스토그램이냐 카운터냐에 따라 17배가 갈려요.

라벨 조합별 시계열 수 (파드 20개 예시)
카운터 method·code카운터 method·code · 400개400개카운터 + route 30종카운터 + route 30종 · 12,000개12,000개히스토그램 method·code히스토그램 method·code · 6,800개6,800개히스토그램 + route 30종히스토그램 + route 30종 · 204,000개204,000개

route 같은 라벨 하나가 30배, 히스토그램 여부가 17배를 만들어요

단위: 개 · 자료: 라벨 값 개수 곱 × 파드 수로 직접 계산 (히스토그램은 파이썬 클라이언트 기본 버킷 15개 + _sum·_count)
표로 보기
구분시계열 수
카운터 method·code400
카운터 + route 30종12,000
히스토그램 method·code6,800
히스토그램 + route 30종204,000

공식 계측 가이드는 지표 하나의 카디널리티를 10 아래로 두고, 100을 넘거나 그렇게 커질 수 있으면 차원을 줄이거나 분석을 범용 처리 시스템으로 옮기라고 해요. 대부분의 지표는 라벨이 없어야 한다고도 적었어요. 위 예시의 route 30종은 이미 이 기준을 넘어요. 그래서 라우트는 원문 경로(/users/1234) 대신 템플릿(/users/{id})으로 넣고, 히스토그램은 꼭 필요한 곳에만 붙여요.

운영 중인 서버라면 계산 대신 직접 물어보면 돼요. HTTP API 문서의 /api/v1/status/tsdb가 head 블록의 시계열 수와 지표별·라벨별 상위 목록을 줘요.

# 전체 활성 시계열 수와 시계열이 가장 많은 지표 상위 10개
curl -s localhost:9090/api/v1/status/tsdb | \
  jq '.data.headStats.numSeries, .data.seriesCountByMetricName[:10]'

# 값 종류가 가장 많은 라벨 (user_id·path 같은 게 위에 있으면 의심)
curl -s localhost:9090/api/v1/status/tsdb | jq '.data.labelValueCountByLabelName[:10]'

응답의 minTime·maxTime은 밀리초 Unix 시각이라 타임스탬프 변환기에 넣으면 바로 읽혀요. 초로 착각했을 때 생기는 일은 초·밀리초 타임스탬프 혼동 버그에 있어요.

디스크 용량 계산, 시계열 수 × 간격 × 보존 기간

시계열 수가 나오면 나머지는 곱셈이에요.

시계열 50만 개 ÷ 15초 × 1,296,000초 × 1~2바이트로 블록 40.2~80.5GiB, 80% 규칙으로 디스크 약 101GiB를 구하는 계산식 분해도
샘플당 바이트는 데이터에 따라 달라서, 디스크는 2바이트 쪽으로 잡는 게 안전해요
  1. 초당 샘플 수 = 500,000 ÷ 15초 = 33,333
  2. 보존 15일 = 1,296,000초 → 샘플 432억 개
  3. 1바이트면 40.2GiB, 1.5바이트면 60.3GiB, 2바이트면 80.5GiB

아래는 같은 50만 시계열에서 간격과 보존 기간을 바꿨을 때 샘플당 1.5바이트로 계산한 값이에요.

간격·보존 기간별 블록 크기 (활성 시계열 50만 개, 샘플당 1.5바이트)
보존 15일보존 30일보존 90일
010020030040015초 · 보존 15일 60.3GiB15초 · 보존 30일 121GiB15초 · 보존 90일 362GiB15초30초 · 보존 15일 30.2GiB30초 · 보존 30일 60.3GiB30초 · 보존 90일 181GiB30초60초 · 보존 15일 15.1GiB60초 · 보존 30일 30.2GiB60초 · 보존 90일 90.5GiB60초

간격이 길어지면 압축률이 떨어져 샘플당 바이트가 커질 수 있어서, 60초 막대는 실제로 조금 더 커요

단위: GiB · 자료: Prometheus Storage 문서의 어림식(needed_disk_space)으로 직접 계산
표로 보기
구분보존 15일보존 30일보존 90일
15초60.3121362
30초30.260.3181
60초15.130.290.5

간격을 15초에서 60초로 늘리면 계산상 4분의 1이지만, 이웃 샘플끼리 압축되는 구조라 샘플당 바이트가 오히려 늘 수 있어요. 반면 route 30종을 템플릿화해 10종으로 줄이면 그 지표가 정확히 3분의 1이 되고 해상도는 그대로예요.

GiB와 GB 단위가 헷갈릴 때는 데이터 용량 변환기로 1000·1024 기준을 나란히 보세요. 프로메테우스의 retention.size는 문서에 "2의 거듭제곱 기준, 1KB = 1024B"라고 적혀 있어서, 100GB라고 쓰면 실제로 100GiB예요.

scrape_interval 판단표, 15초·30초·60초

전역 기본값은 설정 문서 기준 scrape_interval: 1m, scrape_timeout: 10s, evaluation_interval: 1m이에요. 공식 저장소의 예제 prometheus.yml이 15초로 적혀 있어서 15초를 기본으로 아는 경우가 많은데, 지정하지 않으면 1분이에요.

상황권하는 간격이유
웹·API 서비스 지표, 5분 안에 알림이 필요15초rate(x[1m])에 샘플 4개가 들어가 1분 단위 반응이 가능해요
노드·DB 같은 인프라, 알림 기준이 5분 이상30초디스크가 15초의 절반이고 rate(x[2m])이면 샘플 4개예요
용량 추세·비용 지표, 주 단위로 보는 것60초기본값. rate(x[4m]) 이상으로 봐야 해요
응답에 수 초 걸리는 무거운 익스포터60초 이상 + scrape_timeout 상향타임아웃은 간격보다 길 수 없어요
수 초 단위로 살았다 죽는 배치스크레이프 대신 Pushgateway 검토간격보다 짧게 사는 작업은 아예 안 잡혀요

간격은 잡(job)마다 다르게 줄 수 있어요. 알림이 걸린 서비스 잡만 15초, 나머지는 30초·60초로 두면 디스크와 CPU가 직접 줄어요. 간격이 다른 잡을 한 패널에 그릴 때는 rate() 범위를 가장 긴 간격 기준으로 잡아요.

rate() 범위 고르기, 간격의 몇 배인가

rate()는 창 안에 샘플이 최소 2개 있어야 값을 내요. 공식 함수 문서에는 이 조건이 명시돼 있지 않지만, PromQL 소스 functions.go의 외삽 계산 함수가 샘플이 2개 미만이면 그 시계열을 결과에서 빼요. 창 안에 들어오는 샘플 수는 대략 "범위 ÷ 간격"이고, 스크레이프가 한 번 늦거나 빠지면 하나 줄어요.

rate() 범위별 창 안의 샘플 수 (스크레이프가 제때 될 때)
간격 15초간격 30초간격 60초
05101520범위 1분 · 간격 15초 4개범위 1분 · 간격 30초 2개범위 1분 · 간격 60초 1개범위 1분범위 2분 · 간격 15초 8개범위 2분 · 간격 30초 4개범위 2분 · 간격 60초 2개범위 2분범위 5분 · 간격 15초 20개범위 5분 · 간격 30초 10개범위 5분 · 간격 60초 5개범위 5분

2개 이하인 칸은 스크레이프 한 번만 빠져도 결과가 비어 그래프가 끊겨요

단위: 개 · 자료: 범위 ÷ 스크레이프 간격으로 직접 계산
표로 보기
구분간격 15초간격 30초간격 60초
범위 1분421
범위 2분842
범위 5분20105

그래서 범위 ≥ 간격 × 4를 기본으로 잡으면 스크레이프 한두 번이 빠져도 샘플이 2개 이상 남아요. 60초 간격에 rate(x[1m])을 쓰면 창에 샘플이 1개뿐인 순간이 많아서 그래프가 점선처럼 끊겨요.

범위를 너무 길게 잡는 것도 비용이 있어요. 범위 벡터는 시계열마다 그 범위의 샘플을 모두 메모리에 올려요. 시계열 10만 개에 15초 간격으로 x[1h]를 고르면 시계열당 240개, 합쳐 2,400만 샘플이에요. 명령줄 플래그 문서의 --query.max-samples 기본값이 5,000만이라 절반을 한 번에 써요. 대시보드에서 긴 범위가 필요하면 레코딩 규칙으로 rate(x[5m])을 미리 계산해 두고 그 결과를 집계하는 쪽이 싸요.

용도함수와 범위메모
알림 규칙rate(x[5m]) 이상irate()는 짧은 출렁임이 for 대기를 초기화해요
실시간 대시보드간격 × 4 이상 (그라파나라면 $__rate_interval)변동이 큰 카운터만 irate()
하루·한 주 합계 표시increase(x[1d])외삽 때문에 소수점이 붙는 추정치예요
여러 파드 합계sum by (code) (rate(x[5m]))집계는 항상 rate 다음에

알림·집계 순서 권고는 공식 함수 문서에 그대로 있어요. 먼저 rate를 해야 타깃 재시작에 따른 카운터 리셋을 감지해요.

보존 기간 설정, retention.time과 retention.size

보존 기간 기본값은 15일이에요. 최신 문서에서 바뀐 점이 하나 있어요. 지금까지 쓰던 --storage.tsdb.retention.time·--storage.tsdb.retention.size 플래그는 사용 중단(deprecated) 으로 표시됐고, 설정 파일의 storage.tsdb.retention 항목이 플래그보다 우선해요. 설정 파일 쪽은 런타임 재로드가 돼서 재시작 없이 보존 기간을 바꿀 수 있어요.

# prometheus.yml — 시간·용량 둘 다 걸면 먼저 닿는 쪽이 적용돼요
storage:
  tsdb:
    retention:
      time: 30d
      size: 80GB   # 2의 거듭제곱 기준이라 80GiB. 디스크 100GiB의 80%

판단 기준은 이렇게 잡아요.

알아 둘 동작이 세 가지 있어요. 첫째, 컴팩션 블록은 보존 기간의 10%와 31일 중 작은 쪽까지 커지고, 블록 안 샘플이 모두 만료돼야 지워져요. 그래서 디스크에는 보존 기간보다 조금 더 남아요. 둘째, 만료 블록 정리는 최대 2시간 늦을 수 있어요. 셋째, size에는 WAL과 chunks_head도 포함되지만 지울 수 있는 건 블록뿐이라, 디스크는 이 둘의 최고치보다는 커야 해요.

retention.time컴팩션 블록 최대 길이이렇게 쓰는 경우가 많아요
15d (기본)1.5일장애 대응용 단기 보관
30d3일월간 리포트를 로컬에서 직접 볼 때
90d9일분기 추세. 디스크가 15일의 6배예요
365d31일(상한)단일 노드로는 드물어요. 장기 보관은 원격 저장소로
광고
광고

운영에서 자주 나는 문제와 확인 순서

"그래프가 비었다", "값이 이상하다"는 신고가 오면 수집 → 한도 → 카디널리티 → 쿼리 → 저장소 순으로 봐요. 앞 단계에서 걸리면 뒤 단계는 볼 필요가 없어요.

up, 타임아웃 비율, sample_limit, 시계열 폭증, 쿼리, 저장소 순서로 확인하는 여섯 단계 점검 순서도
1~3번은 수집 쪽, 4번은 데이터 모델, 5~6번은 서버 쪽 문제예요

각 단계에서 돌릴 쿼리예요. 서버가 스크레이프마다 직접 남기는 up·scrape_* 시계열을 쓰는데, 목록은 공식 Jobs and instances 문서에 있어요.

# 1. 죽은 타깃
up == 0

# 2. 타임아웃의 80%를 넘긴 스크레이프 (곧 실패로 넘어갈 후보)
scrape_duration_seconds / scrape_timeout_seconds > 0.8

# 3. sample_limit 의 90%를 넘긴 타깃 (한도가 없으면 0 이라 > 0 으로 걸러요)
scrape_samples_post_metric_relabeling / (scrape_sample_limit > 0) > 0.9

# 4. 지난 1시간 동안 새로 생긴 시계열이 많은 타깃 상위 10개
topk(10, sum_over_time(scrape_series_added[1h]))

scrape_timeout_seconds와 scrape_sample_limit은 기본으로 생기지 않고 --enable-feature=extra-scrape-metrics를 켜야 생겨요. scrape_sample_limit은 한도를 안 건 타깃에서 0이라, 그냥 나누면 무한대가 나와 모든 타깃이 걸려요. 그래서 위 쿼리는 0보다 큰 것만 남긴 뒤 나눠요.

3번이 특히 헷갈려요. sample_limit은 metric_relabel 뒤 샘플 수로 세는데, 넘으면 그 스크레이프 전체가 실패로 처리돼요. 일부만 버리는 게 아니라 up이 0이 되고 그 타깃의 모든 지표가 끊겨요. 배포 직후 특정 서비스 그래프가 통째로 사라졌다면 새 라벨이 한도를 넘겼는지부터 봐요. label_limit·label_value_length_limit도 넘으면 똑같이 스크레이프 전체가 실패해요.

5번에서 자주 나오는 원인은 앞 절의 두 가지예요. 범위가 간격의 4배보다 짧거나, rate(sum(x)[5m:])처럼 합친 뒤 rate를 씌운 경우예요. 큰 범위를 고른 쿼리는 query.max-samples를 넘으면 실패하고, 기본 --query.timeout 2분을 넘겨도 끊겨요.

6번에서는 디스크 사용률과 재시작 시간을 봐요. 재시작 때 WAL을 다시 읽어 head를 복원하는 동안은 수집도 쿼리도 안 되고, 시계열이 많을수록 길어져요. NFS(AWS EFS 포함)는 스토리지 문서가 지원하지 않는다고 못 박았으니 로컬 디스크나 블록 볼륨을 써요.

metric_relabel_configs:
  # 원문 경로가 들어간 라벨은 저장 전에 뗀다
  - regex: "raw_path|user_id"
    action: labeldrop
  # 쓰지 않는 히스토그램 버킷은 지표째 버린다
  - source_labels: [__name__]
    regex: "grpc_server_handling_seconds_bucket"
    action: drop

relabel 정규식은 앞뒤가 고정(anchored)돼서 user만 쓰면 user_id에 안 맞아요. 정규식 테스트 도구에서 ^...$로 감싸 미리 확인해 보세요.

도입 전에 따질 것

프로메테우스 단일 서버가 맞는지는 아래 네 가지로 갈려요.

  1. 정확한 개수가 필요한가: 공식 개요 문서는 요청당 과금처럼 100% 정확도가 필요하면 프로메테우스가 좋은 선택이 아니라고 밝혀요. 과금·정산 숫자는 따로 세요.
  2. 보존 기간이 몇 달 이상인가: 로컬 저장소는 단일 노드의 확장성과 내구성에 묶여 있어요. 90일 이상 보거나 디스크 장애에도 데이터가 남아야 하면 원격 쓰기(remote write)로 장기 저장소를 붙이는 걸 처음부터 설계에 넣어요.
  3. 고가용성이 필요한가: 흔한 방법은 같은 설정의 서버 두 대가 같은 타깃을 따로 긁는 것인데, 이러면 디스크·CPU·타깃 부하가 모두 2배예요. 앞의 용량 계산에 2를 곱해 두세요.
  4. 활성 시계열이 몇 개인가: 공식 문서에 서버 한 대의 고정 상한은 없어요. numSeries를 날마다 기록해 증가 추세를 보는 게 가장 확실한 근거예요.
광고
광고

자주 묻는 질문

scrape_interval을 지정하지 않으면 몇 초인가요?

전역 기본값은 1분이에요. 공식 예제 설정 파일이 15초로 적혀 있어서 헷갈리는데, 설정 문서의 기본값은 scrape_interval: 1m, scrape_timeout: 10s예요. 1분 간격에서는 rate() 범위를 4분 이상으로 잡아야 그래프가 끊기지 않아요.

디스크를 줄이려면 간격을 늘리는 게 나은가요, 라벨을 줄이는 게 나은가요?

라벨(시계열 수)을 줄이는 쪽이에요. 간격을 늘리면 해상도와 알림 반응 속도까지 잃지만, 원문 경로·사용자 ID 라벨을 떼면 해상도는 그대로 두고 시계열이 몇 배씩 줄어요.

retention.size를 디스크 크기와 똑같이 잡으면 안 되나요?

안 돼요. 컴팩션 중에는 원본 블록과 합친 새 블록이 동시에 디스크에 있어서 잠깐 size를 넘어요. 공식 문서는 디스크의 최대 80~85%를 권하고, 나머지 15~20%를 이 임시 공간으로 남기라고 해요.

increase()로 하루 주문 수를 세도 되나요?

추세용으로는 괜찮지만 정산 숫자로는 안 돼요. 창 끝까지 외삽한 추정치라 정수 카운터에도 소수점이 붙고, 스크레이프가 빠진 구간에서 실제 값과 어긋나요. 정확한 개수는 주문 DB에서 세요.

보존 기간을 줄였는데 디스크가 바로 안 비워져요.

정상이에요. 만료된 블록 정리는 백그라운드에서 돌아 최대 2시간 늦을 수 있고, 블록 안의 샘플이 모두 만료돼야 지워져요. 설정 파일에서 바꾼 보존 기간은 다음 블록 재로드(1분 안 또는 컴팩션 완료 시)부터 적용돼요.

정리

용량 단위를 바꿔 볼 때는 데이터 용량 변환기, API 응답의 밀리초 시각을 읽을 때는 타임스탬프 변환기를 쓰면 편해요. 타임존을 섞어 집계할 때의 함정은 KST·UTC 저장과 날짜 집계 글에 따로 정리해 뒀어요.

더 깊이 읽기

이 글은 실무에서 판단할 때 필요한 것 위주로 정리했어요. 같은 주제의 내부 동작 원리는 devboi 기술 블로그의 프로메테우스 동작 원리 — 스크레이프·TSDB·rate() 계산에서 도식과 코드로 단계별로 풀었어요.

참고한 자료

#프로메테우스 용량 계산#scrape_interval 설정#프로메테우스 보존 기간#retention.size#카디널리티 줄이기#rate 범위#프로메테우스 장애 점검#PromQL 실무
← 이전 글연산자 우선순위 헷갈리는 조합, 언어별 함정표와 괄호·린터 규칙다음 글 →파이썬·자바 정렬 성능 판단표, key·Comparator 고르는 법과 비용
광고

더 둘러보기

💻 개발자 다른 도구

🔥 많이 찾는 도구

🔮 재미로 한 번 더

📝 읽어 볼 만한 글

글 전체 보기 →