<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Observability Cost on jyukki's Blog</title><link>https://jyukki.com/tags/observability-cost/</link><description>Recent content in Observability Cost on jyukki's Blog</description><generator>Hugo -- 0.147.0</generator><language>ko-kr</language><lastBuildDate>Sat, 04 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://jyukki.com/tags/observability-cost/index.xml" rel="self" type="application/rss+xml"/><item><title>백엔드 커리큘럼 심화: 메트릭 카디널리티 예산과 라벨 거버넌스 플레이북</title><link>https://jyukki.com/learning/deep-dive/deep-dive-metric-cardinality-budget-label-governance-playbook/</link><pubDate>Sat, 04 Jul 2026 00:00:00 +0000</pubDate><guid>https://jyukki.com/learning/deep-dive/deep-dive-metric-cardinality-budget-label-governance-playbook/</guid><description>Prometheus와 OpenTelemetry 메트릭이 라벨 폭증으로 비용·메모리·알람 품질을 망치지 않도록, 카디널리티 예산과 리뷰 기준을 숫자 중심으로 정리합니다.</description><content:encoded><![CDATA[<p>메트릭은 처음 붙일 때는 가볍습니다. <code>http_requests_total</code>, <code>order_created_total</code>, <code>payment_latency_seconds</code> 같은 지표 몇 개만 있으면 서비스 상태가 훨씬 선명해집니다. 문제는 시간이 지나면서 라벨이 늘어날 때 시작됩니다. 장애를 빨리 보고 싶어서 <code>userId</code>, <code>tenantId</code>, <code>requestId</code>, <code>path</code>, <code>exceptionMessage</code>를 붙이고, 대시보드 필터가 필요하다는 이유로 <code>featureFlag</code>, <code>experimentGroup</code>, <code>clientVersion</code>까지 추가하면 Prometheus나 장기 저장소가 감당해야 하는 시계열 수가 갑자기 폭발합니다.</p>
<p>운영에서 중요한 점은 &ldquo;메트릭을 많이 남기자&quot;가 아닙니다. <strong>반복해서 집계할 질문만 메트릭으로 만들고, 고유값이 큰 맥락은 로그와 트레이스로 넘기는 것</strong>입니다. 이 글은 <a href="/learning/deep-dive/deep-dive-observability-baseline/">관측성 베이스라인</a>, <a href="/learning/deep-dive/deep-dive-apm-basics/">APM 기본</a>, <a href="/learning/deep-dive/deep-dive-opentelemetry/">OpenTelemetry 통합 관측</a>, <a href="/learning/deep-dive/deep-dive-structured-logging/">구조화 로깅</a>을 이미 붙인 팀이 다음 단계에서 정해야 할 카디널리티 예산과 라벨 리뷰 기준을 다룹니다.</p>
<h2 id="이-글에서-얻는-것">이 글에서 얻는 것</h2>
<ul>
<li>메트릭 카디널리티가 왜 비용 문제가 아니라 <strong>알람 품질과 장애 대응 속도 문제</strong>인지 이해할 수 있습니다.</li>
<li>새 라벨을 추가할 때 허용·주의·금지 기준을 숫자로 판단할 수 있습니다.</li>
<li>Prometheus, Micrometer, OpenTelemetry 기반 서비스에서 적용 가능한 라벨 예산표와 리뷰 체크리스트를 가져갈 수 있습니다.</li>
<li>로그·트레이스·메트릭 중 어떤 신호에 어떤 정보를 넣어야 하는지 실무 기준을 잡을 수 있습니다.</li>
</ul>
<h2 id="핵심-개념이슈">핵심 개념/이슈</h2>
<h3 id="1-시계열-수는-라벨-값-조합의-곱으로-늘어난다">1) 시계열 수는 라벨 값 조합의 곱으로 늘어난다</h3>
<p>Prometheus 계열에서 하나의 시계열은 대략 <code>metric name + label set</code>으로 결정됩니다. 예를 들어 아래 메트릭이 있다고 해 봅시다.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>http_server_requests_seconds_bucket{
</span></span><span style="display:flex;"><span>  method=&#34;GET&#34;,
</span></span><span style="display:flex;"><span>  status=&#34;200&#34;,
</span></span><span style="display:flex;"><span>  uri=&#34;/orders/{id}&#34;,
</span></span><span style="display:flex;"><span>  tenant=&#34;enterprise-a&#34;,
</span></span><span style="display:flex;"><span>  le=&#34;0.5&#34;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>여기서 각 라벨의 값 종류가 늘어나면 시계열 수는 더하기가 아니라 곱으로 증가합니다.</p>
<table>
  <thead>
      <tr>
          <th>라벨</th>
          <th style="text-align: right">값 종류 예시</th>
          <th>시계열 영향</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>method</code></td>
          <td style="text-align: right">5개</td>
          <td>낮음</td>
      </tr>
      <tr>
          <td><code>status</code></td>
          <td style="text-align: right">10개</td>
          <td>낮음</td>
      </tr>
      <tr>
          <td><code>uri</code></td>
          <td style="text-align: right">120개</td>
          <td>중간</td>
      </tr>
      <tr>
          <td><code>tenant</code></td>
          <td style="text-align: right">3,000개</td>
          <td>매우 높음</td>
      </tr>
      <tr>
          <td>histogram bucket <code>le</code></td>
          <td style="text-align: right">20개</td>
          <td>기존 조합을 20배로 증폭</td>
      </tr>
  </tbody>
</table>
<p>단순 계산으로도 <code>5 x 10 x 120 x 3,000 x 20 = 360,000,000</code> 조합 가능성이 생깁니다. 실제로 모든 조합이 동시에 생기지는 않더라도, 피크 트래픽과 배포 변화가 겹치면 저장소 메모리, remote write 비용, 쿼리 시간이 함께 흔들립니다. 그래서 histogram을 켤 때는 버킷 수와 라벨 수를 같이 봐야 합니다. <a href="/learning/deep-dive/deep-dive-observability-alarms/">관측 알람 설계</a>에서 P95/P99를 계산하려면 histogram이 필요하지만, 모든 비즈니스 차원에 bucket을 곱하는 것은 별개의 문제입니다.</p>
<h3 id="2-라벨은-검색-편의가-아니라-반복-집계-기준이어야-한다">2) 라벨은 &ldquo;검색 편의&quot;가 아니라 &ldquo;반복 집계 기준&quot;이어야 한다</h3>
<p>라벨은 대시보드에서 필터하기 쉽기 때문에 자꾸 늘어납니다. 하지만 메트릭 라벨은 로그 필드가 아닙니다. 좋은 라벨은 반복해서 집계할 축입니다.</p>
<p>허용하기 쉬운 라벨:</p>
<ul>
<li><code>service</code>, <code>env</code>, <code>region</code>, <code>cluster</code></li>
<li><code>method</code>, <code>status_class</code>, <code>route_template</code></li>
<li><code>outcome</code>: <code>success</code>, <code>fail</code>, <code>timeout</code>, <code>rejected</code></li>
<li><code>dependency</code>: <code>mysql</code>, <code>redis</code>, <code>payment-api</code>처럼 값이 제한된 의존성 이름</li>
</ul>
<p>주의가 필요한 라벨:</p>
<ul>
<li><code>tenant</code>: 상위 20개 테넌트만 별도 분리하고 나머지는 <code>other</code>로 묶을 수 있는가</li>
<li><code>clientVersion</code>: 모바일 앱처럼 버전 종류가 많으면 major/minor까지만 남길 수 있는가</li>
<li><code>errorCode</code>: 도메인 코드가 수십 개 수준으로 관리되는가</li>
<li><code>featureFlag</code>: 임시 플래그 제거 정책이 있는가</li>
</ul>
<p>금지에 가까운 라벨:</p>
<ul>
<li><code>userId</code>, <code>email</code>, <code>sessionId</code>, <code>requestId</code></li>
<li>실제 URL 경로(<code>/orders/12345</code>) 또는 raw query string</li>
<li>exception message 전문</li>
<li>SQL 전문, 검색어 전문, 파일명, IP 전체</li>
</ul>
<p>고유값이 큰 정보는 <a href="/learning/deep-dive/deep-dive-structured-logging/">구조화 로깅</a>이나 trace attribute에 제한적으로 남기는 편이 낫습니다. 메트릭은 &ldquo;얼마나 자주, 어느 정도로&quot;를 답하고, 로그와 트레이스는 &ldquo;어떤 요청에서 왜&quot;를 답하게 역할을 나눠야 합니다.</p>
<h3 id="3-uri-라벨은-실제-경로가-아니라-route-template이어야-한다">3) <code>uri</code> 라벨은 실제 경로가 아니라 route template이어야 한다</h3>
<p>가장 흔한 사고는 HTTP 지표의 <code>uri</code> 라벨에 실제 경로가 들어가는 경우입니다.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span># 나쁜 예
</span></span><span style="display:flex;"><span>uri=&#34;/orders/123456&#34;
</span></span><span style="display:flex;"><span>uri=&#34;/orders/123457&#34;
</span></span><span style="display:flex;"><span>uri=&#34;/orders/123458&#34;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span># 좋은 예
</span></span><span style="display:flex;"><span>uri=&#34;/orders/{orderId}&#34;
</span></span></code></pre></div><p>Spring MVC나 WebFlux 계측은 보통 route template을 잡아주지만, 커스텀 필터나 gateway 계층에서 직접 메트릭을 만들면 raw path가 들어가기 쉽습니다. 특히 404, 500, fallback route에서는 템플릿 매칭이 안 되어 raw path가 들어가는 경우가 있습니다. 그래서 HTTP 라벨 정책에는 &ldquo;정상 요청뿐 아니라 unknown route도 저카디널리티 값으로 접는다&quot;가 들어가야 합니다.</p>
<p>실무 기본값:</p>
<ul>
<li>매칭된 route는 <code>/orders/{id}</code> 형태로 기록</li>
<li>매칭 실패는 <code>unknown</code> 또는 <code>not_found</code> 하나로 접기</li>
<li>query string은 metric label에 금지</li>
<li>customer id, order id, file id는 로그·trace에서만 검색</li>
</ul>
<p>이 기준은 <a href="/learning/deep-dive/deep-dive-api-resource-budgeting/">API 리소스 예산 설계</a>와도 연결됩니다. API가 사용하는 CPU, DB, 네트워크 예산을 보려면 경로별 집계는 필요하지만, 리소스 ID별 집계는 대부분 메트릭 저장소를 망칩니다.</p>
<h3 id="4-histogram은-p99를-주지만-라벨-폭발도-증폭한다">4) Histogram은 P99를 주지만, 라벨 폭발도 증폭한다</h3>
<p>Latency P95/P99를 보려면 histogram이 필요합니다. 하지만 histogram은 bucket마다 시계열을 만들기 때문에 라벨 수가 많을수록 비용이 크게 늘어납니다. 예를 들어 <code>http_server_requests_seconds_bucket</code>에 20개 버킷이 있고, <code>method/status/uri</code> 조합이 2,000개라면 bucket 시계열만 40,000개가 됩니다. 여기에 <code>tenant</code>를 붙이면 바로 감당하기 어려운 숫자가 됩니다.</p>
<p>권장 기준은 아래와 같습니다.</p>
<ul>
<li>사용자 영향 API: route template 기준 histogram 허용</li>
<li>내부 짧은 함수: counter 또는 timer summary 수준으로 제한</li>
<li>tenant별 latency: 상위 N개만 별도 metric, 전체 tenant 분석은 trace/log 샘플링으로 처리</li>
<li>batch job: job type과 outcome 중심으로 집계, input file id는 로그로 이동</li>
<li>error message별 latency: 금지, error class 또는 domain error code만 허용</li>
</ul>
<p>P99가 필요하다는 이유만으로 모든 차원을 histogram에 붙이면 대시보드는 화려해지지만 쿼리와 알람이 느려집니다. 관측성은 저장량이 아니라 질문의 선명도로 평가해야 합니다.</p>
<h3 id="5-카디널리티-사고는-보통-코드-리뷰에서-막아야-한다">5) 카디널리티 사고는 보통 코드 리뷰에서 막아야 한다</h3>
<p>카디널리티는 배포 후에야 크게 보이는 경우가 많습니다. 그래서 운영팀이 Prometheus 메모리 사용량을 보고 뒤늦게 잡는 방식은 늦습니다. 새 메트릭과 새 라벨은 코드 리뷰 단계에서 아래 정보를 요구해야 합니다.</p>
<ul>
<li>이 메트릭이 답하려는 운영 질문은 무엇인가</li>
<li>라벨별 예상 값 종류는 몇 개인가</li>
<li>1일/7일 기준 최대 시계열 수는 얼마로 예상하는가</li>
<li>알람에 쓰이는가, 대시보드에만 쓰이는가</li>
<li>고유 식별자는 로그나 trace로 대체할 수 없는가</li>
<li>제거 기준과 owner는 누구인가</li>
</ul>
<p>이 기준은 <a href="/posts/2026-03-20-observability-finops-telemetry-pipeline-trend/">Telemetry Pipeline FinOps</a>에서 말한 비용 제어와 같은 방향입니다. 메트릭 비용은 저장소 요금만이 아니라 쿼리 지연, 알람 누락, 온콜 피로까지 포함합니다.</p>
<h2 id="실무-적용">실무 적용</h2>
<h3 id="1-서비스별-메트릭-예산을-숫자로-둔다">1) 서비스별 메트릭 예산을 숫자로 둔다</h3>
<p>처음부터 완벽한 기준은 어렵습니다. 그래도 예산이 없으면 리뷰가 취향 싸움이 됩니다. 중간 규모 백엔드 서비스의 출발점은 아래처럼 잡을 수 있습니다.</p>
<table>
  <thead>
      <tr>
          <th>항목</th>
          <th style="text-align: right">시작 기준</th>
          <th>조치 기준</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>서비스당 active series</td>
          <td style="text-align: right">10,000 이하</td>
          <td>30,000 초과 시 리뷰</td>
      </tr>
      <tr>
          <td>단일 metric series</td>
          <td style="text-align: right">2,000 이하</td>
          <td>5,000 초과 시 owner 승인</td>
      </tr>
      <tr>
          <td>단일 label 값 종류</td>
          <td style="text-align: right">100 이하</td>
          <td>500 초과 시 drop/bucket 검토</td>
      </tr>
      <tr>
          <td>route label 값 종류</td>
          <td style="text-align: right">200 이하</td>
          <td>raw path 유입 여부 점검</td>
      </tr>
      <tr>
          <td>histogram bucket 수</td>
          <td style="text-align: right">10~20</td>
          <td>라벨 추가 전 재계산</td>
      </tr>
      <tr>
          <td>새 custom metric</td>
          <td style="text-align: right">PR당 근거 필수</td>
          <td>owner/unit/cardinality estimate 없으면 반려</td>
      </tr>
  </tbody>
</table>
<p>트래픽이 큰 플랫폼 서비스는 이보다 큰 예산이 필요할 수 있습니다. 반대로 소규모 서비스는 더 작아야 합니다. 핵심은 절대값보다 <strong>초기 예산, 초과 조건, 승인 경로</strong>가 있다는 점입니다.</p>
<h3 id="2-라벨-허용표를-코드-리뷰-템플릿에-넣는다">2) 라벨 허용표를 코드 리뷰 템플릿에 넣는다</h3>
<p>실무에서는 긴 문서보다 짧은 표가 더 잘 작동합니다.</p>
<table>
  <thead>
      <tr>
          <th>분류</th>
          <th>예시</th>
          <th>정책</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>허용</td>
          <td><code>env</code>, <code>service</code>, <code>region</code>, <code>method</code>, <code>status_class</code></td>
          <td>기본 허용</td>
      </tr>
      <tr>
          <td>제한 허용</td>
          <td><code>uri</code>, <code>dependency</code>, <code>error_code</code>, <code>client_type</code></td>
          <td>값 종류와 owner 필요</td>
      </tr>
      <tr>
          <td>승인 필요</td>
          <td><code>tenant</code>, <code>plan</code>, <code>feature_flag</code>, <code>client_version</code></td>
          <td>상위 N개/버킷화 전략 필요</td>
      </tr>
      <tr>
          <td>금지</td>
          <td><code>user_id</code>, <code>email</code>, <code>request_id</code>, raw path, raw query</td>
          <td>로그/trace로 이동</td>
      </tr>
  </tbody>
</table>
<p>이 표가 있으면 &ldquo;디버깅하기 편해서 넣었다&quot;는 이유만으로 무제한 라벨이 들어오는 일을 줄일 수 있습니다.</p>
<h3 id="3-micrometer-계측-예시">3) Micrometer 계측 예시</h3>
<p>나쁜 예는 보통 이렇게 생겼습니다.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Counter.<span style="color:#50fa7b">builder</span>(<span style="color:#f1fa8c">&#34;payment.failed&#34;</span>)
</span></span><span style="display:flex;"><span>    .<span style="color:#50fa7b">tag</span>(<span style="color:#f1fa8c">&#34;userId&#34;</span>, userId)
</span></span><span style="display:flex;"><span>    .<span style="color:#50fa7b">tag</span>(<span style="color:#f1fa8c">&#34;orderId&#34;</span>, orderId)
</span></span><span style="display:flex;"><span>    .<span style="color:#50fa7b">tag</span>(<span style="color:#f1fa8c">&#34;message&#34;</span>, exception.<span style="color:#50fa7b">getMessage</span>())
</span></span><span style="display:flex;"><span>    .<span style="color:#50fa7b">register</span>(meterRegistry)
</span></span><span style="display:flex;"><span>    .<span style="color:#50fa7b">increment</span>();
</span></span></code></pre></div><p>좋은 예는 집계 가능한 차원만 남깁니다.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Counter.<span style="color:#50fa7b">builder</span>(<span style="color:#f1fa8c">&#34;payment.failed&#34;</span>)
</span></span><span style="display:flex;"><span>    .<span style="color:#50fa7b">tag</span>(<span style="color:#f1fa8c">&#34;provider&#34;</span>, providerName)
</span></span><span style="display:flex;"><span>    .<span style="color:#50fa7b">tag</span>(<span style="color:#f1fa8c">&#34;reason&#34;</span>, normalizeReason(exception))
</span></span><span style="display:flex;"><span>    .<span style="color:#50fa7b">tag</span>(<span style="color:#f1fa8c">&#34;retryable&#34;</span>, String.<span style="color:#50fa7b">valueOf</span>(isRetryable(exception)))
</span></span><span style="display:flex;"><span>    .<span style="color:#50fa7b">register</span>(meterRegistry)
</span></span><span style="display:flex;"><span>    .<span style="color:#50fa7b">increment</span>();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>log.<span style="color:#50fa7b">warn</span>(<span style="color:#f1fa8c">&#34;payment failed orderId={} userId={} provider={} reason={}&#34;</span>,
</span></span><span style="display:flex;"><span>    orderId, userId, providerName, normalizeReason(exception));
</span></span></code></pre></div><p><code>orderId</code>와 <code>userId</code>는 운영 추적에 필요하지만, 메트릭 라벨에는 맞지 않습니다. 대신 로그에 남기고 <code>traceId</code>로 메트릭-로그-트레이스를 연결합니다. 이 방식은 <a href="/learning/deep-dive/deep-dive-distributed-tracing-adoption-playbook/">분산 추적 도입 플레이북</a>과 같이 적용하면 효과가 큽니다.</p>
<h3 id="4-주간-점검-쿼리">4) 주간 점검 쿼리</h3>
<p>Prometheus 계열에서는 아래처럼 상위 시계열을 대략 확인할 수 있습니다.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-promql" data-lang="promql"><span style="display:flex;"><span><span style="color:#ff79c6">topk</span><span style="color:#ff79c6">(</span><span style="color:#bd93f9">20</span>, <span style="color:#ff79c6">count</span> <span style="color:#ff79c6">by</span> <span style="color:#ff79c6">(</span><span style="color:#8be9fd;font-style:italic">__name__</span><span style="color:#ff79c6">)(</span>{<span style="color:#8be9fd;font-style:italic">__name__</span><span style="color:#ff79c6">=~</span>&#34;<span style="color:#f1fa8c">.+</span>&#34;}<span style="color:#ff79c6">))</span>
</span></span></code></pre></div><p>서비스별로 보고 싶다면 공통 라벨을 붙여 집계합니다.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-promql" data-lang="promql"><span style="display:flex;"><span><span style="color:#ff79c6">topk</span><span style="color:#ff79c6">(</span><span style="color:#bd93f9">20</span>, <span style="color:#ff79c6">count</span> <span style="color:#ff79c6">by</span> <span style="color:#ff79c6">(</span><span style="color:#8be9fd;font-style:italic">job</span>, <span style="color:#8be9fd;font-style:italic">__name__</span><span style="color:#ff79c6">)(</span>{<span style="color:#8be9fd;font-style:italic">__name__</span><span style="color:#ff79c6">=~</span>&#34;<span style="color:#f1fa8c">.+</span>&#34;}<span style="color:#ff79c6">))</span>
</span></span></code></pre></div><p>라벨 값 종류가 많은 후보도 봅니다.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-promql" data-lang="promql"><span style="display:flex;"><span><span style="color:#ff79c6">topk</span><span style="color:#ff79c6">(</span><span style="color:#bd93f9">20</span>, <span style="color:#ff79c6">count</span> <span style="color:#ff79c6">by</span> <span style="color:#ff79c6">(</span><span style="color:#8be9fd;font-style:italic">job</span>, <span style="color:#8be9fd;font-style:italic">uri</span><span style="color:#ff79c6">)</span> <span style="color:#ff79c6">(</span><span style="color:#8be9fd;font-style:italic">http_server_requests_seconds_count</span><span style="color:#ff79c6">))</span>
</span></span></code></pre></div><p>운영 루틴은 단순하게 둡니다.</p>
<ol>
<li>매주 active series 상위 20개 metric을 본다.</li>
<li>전주 대비 30% 이상 증가한 metric을 표시한다.</li>
<li>새로 생긴 label key를 리뷰한다.</li>
<li>raw path, user id, request id 의심 값을 샘플링한다.</li>
<li>owner가 없는 metric은 삭제 후보로 올린다.</li>
</ol>
<h3 id="5-도입-순서">5) 도입 순서</h3>
<p><strong>1단계, inventory</strong></p>
<ul>
<li>현재 metric name, label key, label value cardinality를 뽑습니다.</li>
<li>active series 상위 20개를 기준으로 비용을 잡아먹는 지표를 찾습니다.</li>
</ul>
<p><strong>2단계, policy</strong></p>
<ul>
<li>허용/제한/금지 라벨 표를 만듭니다.</li>
<li>새 메트릭 PR 템플릿에 owner, unit, cardinality estimate, alert 사용 여부를 넣습니다.</li>
</ul>
<p><strong>3단계, cleanup</strong></p>
<ul>
<li>raw path, request id, user id가 들어간 라벨을 먼저 제거합니다.</li>
<li>route template 미매칭 값은 <code>unknown</code>으로 접습니다.</li>
</ul>
<p><strong>4단계, guardrail</strong></p>
<ul>
<li>저장소 active series가 예산을 넘으면 알람을 냅니다.</li>
<li>새 라벨 추가는 관측성 owner 또는 서비스 owner 승인 대상으로 둡니다.</li>
</ul>
<h2 id="트레이드오프주의점">트레이드오프/주의점</h2>
<p>첫째, 카디널리티를 줄이면 분석 세밀도가 떨어질 수 있습니다. tenant별 문제를 바로 보고 싶을 때 모든 tenant를 라벨로 넣고 싶은 유혹이 큽니다. 하지만 모든 tenant를 실시간 metric label로 두는 대신 상위 N개, 샘플링 trace, 로그 검색, 별도 분석 배치로 나누는 편이 운영 비용이 낮습니다.</p>
<p>둘째, 라벨을 너무 적게 두면 알람이 둔해집니다. 예를 들어 모든 외부 API 실패를 <code>external_error_total</code> 하나로만 보면 어떤 의존성이 문제인지 모릅니다. <code>dependency</code>처럼 값 종류가 제한된 라벨은 적극적으로 두는 편이 좋습니다.</p>
<p>셋째, 비용 절감만 목표로 삼으면 관측성이 약해집니다. 목표는 적게 저장하는 것이 아니라 <strong>반복 의사결정에 쓰이는 신호만 선명하게 저장하는 것</strong>입니다.</p>
<p>넷째, 로그로 넘긴다고 모든 문제가 해결되지는 않습니다. 로그도 고비용 저장소입니다. 다만 로그는 검색·보존·샘플링 정책을 다르게 적용할 수 있고, 고유 식별자를 metric label로 두는 것보다 운영적으로 낫습니다.</p>
<p>다섯째, OpenTelemetry attribute와 Prometheus label은 같은 의미로 다뤄야 합니다. SDK에서는 attribute처럼 보이더라도 exporter와 backend에서 metric label로 변환되면 카디널리티 비용이 생깁니다.</p>
<p>의사결정 우선순위는 <strong>알람에 필요한 저카디널리티 지표 &gt; 장애 원인 추적용 trace/log &gt; 임시 디버깅 필드 &gt; 편의성 필터</strong>입니다. 편의성 필터가 알람과 저장소 안정성을 이기면 안 됩니다.</p>
<h2 id="체크리스트-또는-연습">체크리스트 또는 연습</h2>
<h3 id="체크리스트">체크리스트</h3>
<ul>
<li><input disabled="" type="checkbox"> 새 metric PR에 owner, unit, label 목록, 예상 카디널리티가 적혀 있다.</li>
<li><input disabled="" type="checkbox"> HTTP <code>uri</code> 라벨은 raw path가 아니라 route template이다.</li>
<li><input disabled="" type="checkbox"> <code>userId</code>, <code>requestId</code>, <code>sessionId</code>, <code>email</code>, raw query string은 metric label로 쓰지 않는다.</li>
<li><input disabled="" type="checkbox"> histogram metric에 새 라벨을 붙이기 전에 bucket 수까지 곱해 계산했다.</li>
<li><input disabled="" type="checkbox"> 서비스별 active series 예산과 초과 알람이 있다.</li>
<li><input disabled="" type="checkbox"> 고유 식별자는 로그 또는 trace로 이동하고 <code>traceId</code>로 연결한다.</li>
<li><input disabled="" type="checkbox"> owner 없는 metric과 오래된 feature flag label을 주기적으로 제거한다.</li>
</ul>
<h3 id="연습">연습</h3>
<ol>
<li>현재 서비스의 active series 상위 20개 metric을 뽑고, 라벨 조합이 큰 이유를 한 줄씩 적어보세요.</li>
<li><code>http_server_requests_seconds</code>에 붙은 <code>uri</code> 값 중 raw id가 섞여 있는지 확인하고, <code>unknown</code> 처리 기준을 정해보세요.</li>
<li>신규 비즈니스 metric 하나를 설계하면서 <code>허용/제한/금지 라벨</code> 표에 따라 어떤 값을 metric에서 빼야 하는지 리뷰해보세요.</li>
</ol>
<h2 id="관련-글">관련 글</h2>
<ul>
<li><a href="/learning/deep-dive/deep-dive-observability-baseline/">관측성 베이스라인: 로그·메트릭·트레이스</a></li>
<li><a href="/learning/deep-dive/deep-dive-apm-basics/">APM 기본과 Micrometer 지표 설계</a></li>
<li><a href="/learning/deep-dive/deep-dive-opentelemetry/">OpenTelemetry 통합 관측 표준</a></li>
<li><a href="/learning/deep-dive/deep-dive-structured-logging/">구조화 로깅: 검색 가능한 로그 설계</a></li>
<li><a href="/posts/2026-03-20-observability-finops-telemetry-pipeline-trend/">Telemetry Pipeline FinOps</a></li>
</ul>
]]></content:encoded></item></channel></rss>