LabHub
배우기 러닝패스 코스

PCA — Prometheus Certified Associate

The dashboard said p90 was 0.48s; the logs said 0.42s

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

같은 요청을 버킷 경계가 다른 두 버전으로 집계해 histogram_quantile 의 추정 오차를 직접 재고, le 를 버린 식·버전을 섞은 식·rate 를 뺀 식이 어떤 숫자를 내는지 확인한 뒤 SLO 에 맞는 버킷을 설계합니다.

왜 중요한가

히스토그램은 표본을 버킷 개수로만 기억합니다. 그래서 분위수는 버킷 안에 표본이 고르게 퍼져 있다고 가정한 선형 보간의 결과이고, 오차의 크기는 순위가 떨어지는 버킷의 폭이 정합니다. 경계 밖으로 나간 꼬리는 아예 보이지 않습니다. 대시보드 숫자가 로그와 다를 때 쿼리를 의심할지 버킷 설계를 의심할지 가를 수 있어야 SLO 를 믿을 수 있습니다.

준비된 환경

python3 /opt/fixtures/pca_histogram_lab.py init/root/pca-histogram/ 에 v2 파드의 요청 로그(access-log-v2.csv)와 README 를 둡니다. 시계열은 http_request_duration_seconds_bucket/_count/_sum 이고 레이블은 route(/checkout, /search), version(v1, v2), le 입니다. v1 은 경계 0.1 0.5 1, v2 는 0.05 0.1 0.2 0.3 0.4 0.5 1 2.5 입니다. 0분부터 60분까지 1분마다 긁었고 지금 시각은 60m 입니다.

python3 /opt/fixtures/pca_histogram_lab.py eval -f 파일 --at 60m 은 lab-k8s 이미지의 promtool 3.0.1 PromQL 엔진으로 표현식을 평가합니다. Prometheus 서버는 띄우지 않으며, 채점기도 같은 엔진으로 여러분의 식과 기준 식을 다시 계산해 적은 숫자와 대조합니다.

단계

  1. /root/pca-histogram/access-log-v2.csv 에서 /checkout 의 56분부터 60분까지 요청 100건을 골라, 정렬한 뒤 ceil(q×N) 번째 값(nearest-rank)으로 p90 과 p99 를 구하세요. /root/pca-histogram/01-raw.txtraw_p90=raw_p99= 두 줄(초 단위)로 적습니다. 재료가 없으면 python3 /opt/fixtures/pca_histogram_lab.py init 을 먼저 실행합니다.
  2. /root/pca-histogram/02-v1-p90.promql 에 version="v1", route="/checkout" 의 최근 5분 p90 식을 씁니다(histogram_quantile(0.9, ...), rate(...[5m]), le 유지). python3 /opt/fixtures/pca_histogram_lab.py eval -f /root/pca-histogram/02-v1-p90.promql --at 60m 로 결과를 보고 /root/pca-histogram/02-v1.txtv1_p90=error=(v1_p90 에서 raw_p90 을 뺀 값)를 적으세요.
  3. /root/pca-histogram/03-v2-p90.promql 에 같은 식을 version="v2" 로 쓰고 결과를 확인합니다. /root/pca-histogram/03-v2.txtv2_p90=, error=(v2_p90 에서 raw_p90 을 뺀 값), closer=(로그의 p90 에 더 가까운 버전 v1 또는 v2)를 적으세요.
  4. v1·v2 두 버전의 /checkout 최근 5분 p99 를 eval 로 확인하고, /root/pca-histogram/04-slow.promql 에 v1 /checkout 의 최근 5분 동안 1초를 넘은 요청 수를 increase(..._bucket{le="+Inf"}[5m]) 에서 le="1" 버킷을 빼는 식으로 씁니다(le 값이 달라 매칭 조건이 필요합니다). /root/pca-histogram/04-p99.txtv1_p99=, v2_p99=, slow_requests= 를 적으세요.
  5. /root/pca-histogram/05-broken.promqlsum by (route) 로 le 를 버린 v2 p90 식을, /root/pca-histogram/05-fixed.promql 에 le 와 route 를 함께 지킨 v2 p90 식을 씁니다. 두 식을 eval 하고 /root/pca-histogram/05-le.txtbroken_series=(결과 시계열 수), fixed_series=, search_p90=(/search 의 값)을 적으세요.
  6. 카나리 배포 중 대시보드가 version 을 가리지 않고 /checkout 버킷을 합칩니다. /root/pca-histogram/06-mixed.promql 에 version 선택 없이 route="/checkout" 만 고른 p90 식을 쓰고, count by (le) (http_request_duration_seconds_bucket{route="/checkout"}) 로 le 값의 종류를 셉니다. /root/pca-histogram/06-mixed.txtmixed_p90=distinct_le= 를 적으세요.
  7. /root/pca-histogram/07-lifetime.promql 에 rate 없이 v2 /checkout 누적 버킷을 그대로 합친 p90 식을 씁니다. /root/pca-histogram/07-lifetime.txtlifetime_p90=window_p90=(03 단계의 최근 5분 v2 p90)을 적어, 31분에 느려진 사실이 어느 쪽에 더 잘 보이는지 비교하세요.
  8. /root/pca-histogram/08-buckets.txt 에 새 버킷 경계(오름차순, 유한 경계 10개 이하, +Inf 는 쓰지 않음)를 한 줄로 적습니다. 조건은 세 가지입니다 — SLO 경계 0.3 을 포함하고, 가장 큰 유한 경계가 최대 지연 1.8초 이상이며, 로그 p90 과의 오차가 0.01 이하. python3 /opt/fixtures/pca_histogram_lab.py design 으로 같은 요청을 새 경계로 다시 집계한 결과를 보고 /root/pca-histogram/08-design.txtdesigned_p90=under_300ms_ratio= 를 적으세요.

참고

로그에서 진짜 p90·p99 세기

/root/pca-histogram/access-log-v2.csv 에서 /checkout 의 56분부터 60분까지 요청 100건을 골라, 정렬한 뒤 ceil(q×N) 번째 값(nearest-rank)으로 p90 과 p99 를 구하세요. /root/pca-histogram/01-raw.txtraw_p90=raw_p99= 두 줄(초 단위)로 적습니다. 재료가 없으면 python3 /opt/fixtures/pca_histogram_lab.py init 을 먼저 실행합니다.

선형 보간을 하는 도구(numpy 기본값 등)는 두 표본 사이 값을 돌려줍니다. 여기서는 순위에 해당하는 실제 표본 하나를 고릅니다.

굵은 버킷(v1)이 말하는 p90

/root/pca-histogram/02-v1-p90.promql 에 version="v1", route="/checkout" 의 최근 5분 p90 식을 씁니다(histogram_quantile(0.9, ...), rate(...[5m]), le 유지). python3 /opt/fixtures/pca_histogram_lab.py eval -f /root/pca-histogram/02-v1-p90.promql --at 60m 로 결과를 보고 /root/pca-histogram/02-v1.txtv1_p90=error=(v1_p90 에서 raw_p90 을 뺀 값)를 적으세요.

분위수는 버킷 안에서 선형 보간됩니다. v1 은 0.1 과 0.5 사이에 경계가 없어서 그 구간 전체를 고르게 퍼진 것으로 가정합니다.

촘촘한 버킷(v2)과 비교하기

/root/pca-histogram/03-v2-p90.promql 에 같은 식을 version="v2" 로 쓰고 결과를 확인합니다. /root/pca-histogram/03-v2.txtv2_p90=, error=(v2_p90 에서 raw_p90 을 뺀 값), closer=(로그의 p90 에 더 가까운 버전 v1 또는 v2)를 적으세요.

v2 는 0.4 와 0.5 에 경계가 있습니다. 순위가 떨어지는 버킷의 폭이 좁을수록 보간 오차의 상한이 줄어듭니다.

마지막 유한 경계에 갇힌 p99

v1·v2 두 버전의 /checkout 최근 5분 p99 를 eval 로 확인하고, /root/pca-histogram/04-slow.promql 에 v1 /checkout 의 최근 5분 동안 1초를 넘은 요청 수를 increase(..._bucket{le="+Inf"}[5m]) 에서 le="1" 버킷을 빼는 식으로 씁니다(le 값이 달라 매칭 조건이 필요합니다). /root/pca-histogram/04-p99.txtv1_p99=, v2_p99=, slow_requests= 를 적으세요.

순위가 +Inf 버킷에 떨어지면 histogram_quantile 은 가장 큰 유한 경계를 돌려줍니다. 두 버킷 시계열은 le 레이블만 다르므로 ignoring(le) 가 필요합니다.

le 를 버린 대시보드 고치기

/root/pca-histogram/05-broken.promqlsum by (route) 로 le 를 버린 v2 p90 식을, /root/pca-histogram/05-fixed.promql 에 le 와 route 를 함께 지킨 v2 p90 식을 씁니다. 두 식을 eval 하고 /root/pca-histogram/05-le.txtbroken_series=(결과 시계열 수), fixed_series=, search_p90=(/search 의 값)을 적으세요.

histogram_quantile 은 le 레이블이 있는 시계열만 버킷으로 읽습니다. 오류 없이 빈 결과가 나오는 것이 가장 위험한 형태입니다.

경계가 다른 두 버전을 합치면

카나리 배포 중 대시보드가 version 을 가리지 않고 /checkout 버킷을 합칩니다. /root/pca-histogram/06-mixed.promql 에 version 선택 없이 route="/checkout" 만 고른 p90 식을 쓰고, count by (le) (http_request_duration_seconds_bucket{route="/checkout"}) 로 le 값의 종류를 셉니다. /root/pca-histogram/06-mixed.txtmixed_p90=distinct_le= 를 적으세요.

합친 결과의 le 는 두 버전 경계의 합집합입니다. 한쪽에만 있는 경계에서는 누적 개수가 뒤틀려 엔진이 단조성을 억지로 맞춥니다.

rate 없이 누적 버킷을 읽으면

/root/pca-histogram/07-lifetime.promql 에 rate 없이 v2 /checkout 누적 버킷을 그대로 합친 p90 식을 씁니다. /root/pca-histogram/07-lifetime.txtlifetime_p90=window_p90=(03 단계의 최근 5분 v2 p90)을 적어, 31분에 느려진 사실이 어느 쪽에 더 잘 보이는지 비교하세요.

카운터 누적값은 프로세스가 뜬 뒤 전체 요청을 담습니다. 느려지기 전 30분이 섞이면 최근 변화가 희석됩니다.

SLO 에 맞춰 버킷을 다시 설계하기

/root/pca-histogram/08-buckets.txt 에 새 버킷 경계(오름차순, 유한 경계 10개 이하, +Inf 는 쓰지 않음)를 한 줄로 적습니다. 조건은 세 가지입니다 — SLO 경계 0.3 을 포함하고, 가장 큰 유한 경계가 최대 지연 1.8초 이상이며, 로그 p90 과의 오차가 0.01 이하. python3 /opt/fixtures/pca_histogram_lab.py design 으로 같은 요청을 새 경계로 다시 집계한 결과를 보고 /root/pca-histogram/08-design.txtdesigned_p90=under_300ms_ratio= 를 적으세요.

오차는 순위가 떨어지는 버킷의 폭에서 옵니다. 모든 곳을 촘촘히 할 필요는 없고, p90 이 머무는 구간과 SLO 경계만 좁히면 됩니다.