<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://www.wool-dev.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.wool-dev.com/" rel="alternate" type="text/html" /><updated>2026-02-23T00:17:49+09:00</updated><id>https://www.wool-dev.com/feed.xml</id><title type="html">Woolog</title><subtitle>8년차 백엔드 엔지니어의 기술 블로그 - Spring Boot, Kafka, Kubernetes, 분산 시스템 설계 및 데이터 엔지니어링에 대한 실전 경험과 튜토리얼을 공유합니다. A backend engineer&apos;s tech blog sharing hands-on experience with distributed systems, data engineering, and modern backend technologies.</subtitle><author><name>울이</name></author><entry><title type="html">Edge Computing Patterns for Backend Developers</title><link href="https://www.wool-dev.com/backend%20engineering/spring/edge-computing/" rel="alternate" type="text/html" title="Edge Computing Patterns for Backend Developers" /><published>2026-02-09T00:00:00+09:00</published><updated>2026-02-09T00:00:00+09:00</updated><id>https://www.wool-dev.com/backend%20engineering/spring/edge-computing</id><content type="html" xml:base="https://www.wool-dev.com/backend%20engineering/spring/edge-computing/"><![CDATA[<h2 id="엣지-컴퓨팅이란">엣지 컴퓨팅이란?</h2>

<p>엣지 컴퓨팅은 데이터 처리를 사용자에게 가까운 위치에서 수행하는 패러다임입니다.</p>

<h3 id="장점">장점</h3>

<ul>
  <li><strong>낮은 지연시간</strong>: 물리적 거리 최소화</li>
  <li><strong>대역폭 절약</strong>: 원본 서버 트래픽 감소</li>
  <li><strong>높은 가용성</strong>: 분산 처리로 장애 격리</li>
  <li><strong>글로벌 확장성</strong>: 전 세계 PoP 활용</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[기존]
사용자(서울) ─────────────────▶ 서버(미국) ─▶ 응답
                200ms

[엣지]
사용자(서울) ───▶ 엣지(서울) ───▶ 서버(미국)
                10ms      (필요 시)
</code></pre></div></div>

<h2 id="cdn-기본-활용">CDN 기본 활용</h2>

<h3 id="정적-리소스-캐싱">정적 리소스 캐싱</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Cloudflare Page Rules</span>
<span class="pi">-</span> <span class="na">match</span><span class="pi">:</span> <span class="s2">"</span><span class="s">*.example.com/static/*"</span>
  <span class="na">cache_level</span><span class="pi">:</span> <span class="s">cache_everything</span>
  <span class="na">edge_cache_ttl</span><span class="pi">:</span> <span class="m">86400</span>  <span class="c1"># 24시간</span>

<span class="pi">-</span> <span class="na">match</span><span class="pi">:</span> <span class="s2">"</span><span class="s">api.example.com/v1/products/*"</span>
  <span class="na">cache_level</span><span class="pi">:</span> <span class="s">cache_everything</span>
  <span class="na">edge_cache_ttl</span><span class="pi">:</span> <span class="m">300</span>    <span class="c1"># 5분</span>
  <span class="na">cache_by_cookie</span><span class="pi">:</span> <span class="kc">false</span>
</code></pre></div></div>

<h3 id="spring-boot에서-cache-control-설정">Spring Boot에서 Cache-Control 설정</h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="kd">class</span> <span class="nc">WebConfig</span> <span class="p">:</span> <span class="nc">WebMvcConfigurer</span> <span class="p">{</span>

    <span class="k">override</span> <span class="k">fun</span> <span class="nf">addResourceHandlers</span><span class="p">(</span><span class="n">registry</span><span class="p">:</span> <span class="nc">ResourceHandlerRegistry</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">registry</span><span class="p">.</span><span class="nf">addResourceHandler</span><span class="p">(</span><span class="s">"/static/**"</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">addResourceLocations</span><span class="p">(</span><span class="s">"classpath:/static/"</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">setCacheControl</span><span class="p">(</span><span class="nc">CacheControl</span><span class="p">.</span><span class="nf">maxAge</span><span class="p">(</span><span class="mi">365</span><span class="p">,</span> <span class="nc">TimeUnit</span><span class="p">.</span><span class="nc">DAYS</span><span class="p">).</span><span class="nf">cachePublic</span><span class="p">())</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="nd">@RestController</span>
<span class="kd">class</span> <span class="nc">ProductController</span><span class="p">(</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">productService</span><span class="p">:</span> <span class="nc">ProductService</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="nd">@GetMapping</span><span class="p">(</span><span class="s">"/api/products/{id}"</span><span class="p">)</span>
    <span class="k">fun</span> <span class="nf">getProduct</span><span class="p">(</span><span class="nd">@PathVariable</span> <span class="n">id</span><span class="p">:</span> <span class="nc">String</span><span class="p">):</span> <span class="nc">ResponseEntity</span><span class="p">&lt;</span><span class="nc">Product</span><span class="p">&gt;</span> <span class="p">{</span>
        <span class="kd">val</span> <span class="py">product</span> <span class="p">=</span> <span class="n">productService</span><span class="p">.</span><span class="nf">getProduct</span><span class="p">(</span><span class="n">id</span><span class="p">)</span>

        <span class="k">return</span> <span class="nc">ResponseEntity</span><span class="p">.</span><span class="nf">ok</span><span class="p">()</span>
            <span class="p">.</span><span class="nf">cacheControl</span><span class="p">(</span><span class="nc">CacheControl</span><span class="p">.</span><span class="nf">maxAge</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="nc">TimeUnit</span><span class="p">.</span><span class="nc">MINUTES</span><span class="p">).</span><span class="nf">cachePublic</span><span class="p">())</span>
            <span class="p">.</span><span class="nf">eTag</span><span class="p">(</span><span class="n">product</span><span class="p">.</span><span class="n">version</span><span class="p">.</span><span class="nf">toString</span><span class="p">())</span>
            <span class="p">.</span><span class="nf">body</span><span class="p">(</span><span class="n">product</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="surrogate-keys를-통한-정밀-캐시-무효화">Surrogate Keys를 통한 정밀 캐시 무효화</h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RestController</span>
<span class="kd">class</span> <span class="nc">ProductController</span><span class="p">(</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">productService</span><span class="p">:</span> <span class="nc">ProductService</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="nd">@GetMapping</span><span class="p">(</span><span class="s">"/api/categories/{categoryId}/products"</span><span class="p">)</span>
    <span class="k">fun</span> <span class="nf">getProductsByCategory</span><span class="p">(</span>
        <span class="nd">@PathVariable</span> <span class="n">categoryId</span><span class="p">:</span> <span class="nc">String</span>
    <span class="p">):</span> <span class="nc">ResponseEntity</span><span class="p">&lt;</span><span class="nc">List</span><span class="p">&lt;</span><span class="nc">Product</span><span class="p">&gt;&gt;</span> <span class="p">{</span>
        <span class="kd">val</span> <span class="py">products</span> <span class="p">=</span> <span class="n">productService</span><span class="p">.</span><span class="nf">getByCategory</span><span class="p">(</span><span class="n">categoryId</span><span class="p">)</span>

        <span class="k">return</span> <span class="nc">ResponseEntity</span><span class="p">.</span><span class="nf">ok</span><span class="p">()</span>
            <span class="p">.</span><span class="nf">header</span><span class="p">(</span><span class="s">"Surrogate-Key"</span><span class="p">,</span> <span class="s">"category-$categoryId products"</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">cacheControl</span><span class="p">(</span><span class="nc">CacheControl</span><span class="p">.</span><span class="nf">maxAge</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nc">TimeUnit</span><span class="p">.</span><span class="nc">HOURS</span><span class="p">))</span>
            <span class="p">.</span><span class="nf">body</span><span class="p">(</span><span class="n">products</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// 캐시 무효화</span>
<span class="nd">@Service</span>
<span class="kd">class</span> <span class="nc">CacheInvalidationService</span><span class="p">(</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">webClient</span><span class="p">:</span> <span class="nc">WebClient</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">fun</span> <span class="nf">invalidateCategory</span><span class="p">(</span><span class="n">categoryId</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">webClient</span><span class="p">.</span><span class="nf">post</span><span class="p">()</span>
            <span class="p">.</span><span class="nf">uri</span><span class="p">(</span><span class="s">"https://api.fastly.com/service/{serviceId}/purge/category-$categoryId"</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">header</span><span class="p">(</span><span class="s">"Fastly-Key"</span><span class="p">,</span> <span class="n">fastlyApiKey</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">retrieve</span><span class="p">()</span>
            <span class="p">.</span><span class="nf">toBodilessEntity</span><span class="p">()</span>
            <span class="p">.</span><span class="nf">block</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="edge-functions">Edge Functions</h2>

<h3 id="cloudflare-workers">Cloudflare Workers</h3>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// workers/api-router.js</span>
<span class="k">export</span> <span class="k">default</span> <span class="p">{</span>
  <span class="k">async</span> <span class="nf">fetch</span><span class="p">(</span><span class="nx">request</span><span class="p">,</span> <span class="nx">env</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">URL</span><span class="p">(</span><span class="nx">request</span><span class="p">.</span><span class="nx">url</span><span class="p">);</span>

    <span class="c1">// 지역별 라우팅</span>
    <span class="kd">const</span> <span class="nx">country</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">cf</span><span class="p">?.</span><span class="nx">country</span> <span class="o">||</span> <span class="dl">'</span><span class="s1">US</span><span class="dl">'</span><span class="p">;</span>
    <span class="kd">const</span> <span class="nx">regionOrigin</span> <span class="o">=</span> <span class="nf">getRegionOrigin</span><span class="p">(</span><span class="nx">country</span><span class="p">);</span>

    <span class="c1">// A/B 테스트</span>
    <span class="kd">const</span> <span class="nx">variant</span> <span class="o">=</span> <span class="nf">getVariant</span><span class="p">(</span><span class="nx">request</span><span class="p">);</span>

    <span class="c1">// 캐시 확인</span>
    <span class="kd">const</span> <span class="nx">cacheKey</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Request</span><span class="p">(</span><span class="nx">url</span><span class="p">.</span><span class="nf">toString</span><span class="p">(),</span> <span class="nx">request</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">cache</span> <span class="o">=</span> <span class="nx">caches</span><span class="p">.</span><span class="k">default</span><span class="p">;</span>
    <span class="kd">let</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">cache</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="nx">cacheKey</span><span class="p">);</span>

    <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">response</span><span class="p">)</span> <span class="p">{</span>
      <span class="c1">// 원본 요청</span>
      <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(</span><span class="nx">regionOrigin</span> <span class="o">+</span> <span class="nx">url</span><span class="p">.</span><span class="nx">pathname</span><span class="p">,</span> <span class="p">{</span>
        <span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
          <span class="p">...</span><span class="nx">request</span><span class="p">.</span><span class="nx">headers</span><span class="p">,</span>
          <span class="dl">'</span><span class="s1">X-Variant</span><span class="dl">'</span><span class="p">:</span> <span class="nx">variant</span><span class="p">,</span>
          <span class="dl">'</span><span class="s1">X-Country</span><span class="dl">'</span><span class="p">:</span> <span class="nx">country</span>
        <span class="p">}</span>
      <span class="p">});</span>

      <span class="c1">// 캐시 저장</span>
      <span class="k">if </span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nx">ok</span><span class="p">)</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">cached</span> <span class="o">=</span> <span class="nx">response</span><span class="p">.</span><span class="nf">clone</span><span class="p">();</span>
        <span class="nx">cached</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="dl">'</span><span class="s1">Cache-Control</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">public, max-age=60</span><span class="dl">'</span><span class="p">);</span>
        <span class="k">await</span> <span class="nx">cache</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="nx">cacheKey</span><span class="p">,</span> <span class="nx">cached</span><span class="p">);</span>
      <span class="p">}</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="nx">response</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">};</span>

<span class="kd">function</span> <span class="nf">getRegionOrigin</span><span class="p">(</span><span class="nx">country</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">regions</span> <span class="o">=</span> <span class="p">{</span>
    <span class="dl">'</span><span class="s1">KR</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">https://api-ap.example.com</span><span class="dl">'</span><span class="p">,</span>
    <span class="dl">'</span><span class="s1">JP</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">https://api-ap.example.com</span><span class="dl">'</span><span class="p">,</span>
    <span class="dl">'</span><span class="s1">US</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">https://api-us.example.com</span><span class="dl">'</span><span class="p">,</span>
    <span class="dl">'</span><span class="s1">DE</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">https://api-eu.example.com</span><span class="dl">'</span><span class="p">,</span>
  <span class="p">};</span>
  <span class="k">return</span> <span class="nx">regions</span><span class="p">[</span><span class="nx">country</span><span class="p">]</span> <span class="o">||</span> <span class="nx">regions</span><span class="p">[</span><span class="dl">'</span><span class="s1">US</span><span class="dl">'</span><span class="p">];</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">getVariant</span><span class="p">(</span><span class="nx">request</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">cookie</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">Cookie</span><span class="dl">'</span><span class="p">);</span>
  <span class="c1">// 기존 variant 확인 또는 새로 할당</span>
  <span class="k">return</span> <span class="dl">'</span><span class="s1">A</span><span class="dl">'</span><span class="p">;</span>  <span class="c1">// 또는 'B'</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="aws-lambdaedge">AWS Lambda@Edge</h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Lambda@Edge - Viewer Request</span>
<span class="kd">class</span> <span class="nc">ViewerRequestHandler</span> <span class="p">:</span> <span class="nc">RequestHandler</span><span class="p">&lt;</span><span class="nc">CloudFrontEvent</span><span class="p">,</span> <span class="nc">CloudFrontResponse</span><span class="p">&gt;</span> <span class="p">{</span>

    <span class="k">override</span> <span class="k">fun</span> <span class="nf">handleRequest</span><span class="p">(</span><span class="n">event</span><span class="p">:</span> <span class="nc">CloudFrontEvent</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="nc">Context</span><span class="p">):</span> <span class="nc">CloudFrontResponse</span> <span class="p">{</span>
        <span class="kd">val</span> <span class="py">request</span> <span class="p">=</span> <span class="n">event</span><span class="p">.</span><span class="n">records</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">cf</span><span class="p">.</span><span class="n">request</span>

        <span class="c1">// 인증 토큰 검증</span>
        <span class="kd">val</span> <span class="py">authHeader</span> <span class="p">=</span> <span class="n">request</span><span class="p">.</span><span class="n">headers</span><span class="p">[</span><span class="s">"authorization"</span><span class="p">]</span><span class="o">?.</span><span class="k">get</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="o">?.</span><span class="n">value</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">authHeader</span> <span class="p">==</span> <span class="k">null</span> <span class="p">||</span> <span class="p">!</span><span class="nf">validateToken</span><span class="p">(</span><span class="n">authHeader</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">return</span> <span class="nc">CloudFrontResponse</span><span class="p">().</span><span class="nf">apply</span> <span class="p">{</span>
                <span class="n">status</span> <span class="p">=</span> <span class="s">"401"</span>
                <span class="n">statusDescription</span> <span class="p">=</span> <span class="s">"Unauthorized"</span>
                <span class="n">body</span> <span class="p">=</span> <span class="s">"Invalid token"</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="c1">// 요청 변환</span>
        <span class="n">request</span><span class="p">.</span><span class="n">headers</span><span class="p">[</span><span class="s">"x-user-id"</span><span class="p">]</span> <span class="p">=</span> <span class="nf">listOf</span><span class="p">(</span>
            <span class="nc">Header</span><span class="p">(</span><span class="s">"x-user-id"</span><span class="p">,</span> <span class="nf">extractUserId</span><span class="p">(</span><span class="n">authHeader</span><span class="p">))</span>
        <span class="p">)</span>

        <span class="k">return</span> <span class="n">request</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="지역별-데이터-라우팅">지역별 데이터 라우팅</h2>

<h3 id="spring-boot-멀티-리전-설정">Spring Boot 멀티 리전 설정</h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="kd">class</span> <span class="nc">RegionalRoutingConfig</span> <span class="p">{</span>

    <span class="nd">@Bean</span>
    <span class="k">fun</span> <span class="nf">regionalDataSource</span><span class="p">(</span>
        <span class="nd">@Value</span><span class="p">(</span><span class="s">"\${region}"</span><span class="p">)</span> <span class="n">region</span><span class="p">:</span> <span class="nc">String</span>
    <span class="p">):</span> <span class="nc">DataSource</span> <span class="p">{</span>
        <span class="kd">val</span> <span class="py">config</span> <span class="p">=</span> <span class="k">when</span> <span class="p">(</span><span class="n">region</span><span class="p">)</span> <span class="p">{</span>
            <span class="s">"ap-northeast-2"</span> <span class="p">-&gt;</span> <span class="nc">DataSourceConfig</span><span class="p">(</span>
                <span class="n">primary</span> <span class="p">=</span> <span class="s">"jdbc:postgresql://db-ap-northeast-2.example.com:5432/mydb"</span><span class="p">,</span>
                <span class="n">replica</span> <span class="p">=</span> <span class="s">"jdbc:postgresql://db-ap-replica.example.com:5432/mydb"</span>
            <span class="p">)</span>
            <span class="s">"us-east-1"</span> <span class="p">-&gt;</span> <span class="nc">DataSourceConfig</span><span class="p">(</span>
                <span class="n">primary</span> <span class="p">=</span> <span class="s">"jdbc:postgresql://db-us-east-1.example.com:5432/mydb"</span><span class="p">,</span>
                <span class="n">replica</span> <span class="p">=</span> <span class="s">"jdbc:postgresql://db-us-replica.example.com:5432/mydb"</span>
            <span class="p">)</span>
            <span class="k">else</span> <span class="p">-&gt;</span> <span class="k">throw</span> <span class="nc">IllegalArgumentException</span><span class="p">(</span><span class="s">"Unknown region: $region"</span><span class="p">)</span>
        <span class="p">}</span>

        <span class="k">return</span> <span class="nf">createRoutingDataSource</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">fun</span> <span class="nf">createRoutingDataSource</span><span class="p">(</span><span class="n">config</span><span class="p">:</span> <span class="nc">DataSourceConfig</span><span class="p">):</span> <span class="nc">AbstractRoutingDataSource</span> <span class="p">{</span>
        <span class="k">return</span> <span class="kd">object</span> <span class="err">: </span><span class="nc">AbstractRoutingDataSource</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">override</span> <span class="k">fun</span> <span class="nf">determineCurrentLookupKey</span><span class="p">():</span> <span class="nc">Any</span> <span class="p">{</span>
                <span class="k">return</span> <span class="k">if</span> <span class="p">(</span><span class="nc">TransactionSynchronizationManager</span><span class="p">.</span><span class="nf">isCurrentTransactionReadOnly</span><span class="p">())</span> <span class="p">{</span>
                    <span class="s">"replica"</span>
                <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                    <span class="s">"primary"</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}.</span><span class="nf">apply</span> <span class="p">{</span>
            <span class="nf">setTargetDataSources</span><span class="p">(</span><span class="nf">mapOf</span><span class="p">(</span>
                <span class="s">"primary"</span> <span class="n">to</span> <span class="nf">createHikariDataSource</span><span class="p">(</span><span class="n">config</span><span class="p">.</span><span class="n">primary</span><span class="p">),</span>
                <span class="s">"replica"</span> <span class="n">to</span> <span class="nf">createHikariDataSource</span><span class="p">(</span><span class="n">config</span><span class="p">.</span><span class="n">replica</span><span class="p">)</span>
            <span class="p">))</span>
            <span class="nf">setDefaultTargetDataSource</span><span class="p">(</span><span class="nf">createHikariDataSource</span><span class="p">(</span><span class="n">config</span><span class="p">.</span><span class="n">primary</span><span class="p">))</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="지역-인식-서비스">지역 인식 서비스</h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span>
<span class="kd">class</span> <span class="nc">RegionalService</span><span class="p">(</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">regionalApiClients</span><span class="p">:</span> <span class="nc">Map</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">,</span> <span class="nc">WebClient</span><span class="p">&gt;</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">fun</span> <span class="nf">getDataFromNearestRegion</span><span class="p">(</span><span class="n">userId</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="n">userRegion</span><span class="p">:</span> <span class="nc">String</span><span class="p">):</span> <span class="nc">Data</span> <span class="p">{</span>
        <span class="kd">val</span> <span class="py">client</span> <span class="p">=</span> <span class="n">regionalApiClients</span><span class="p">[</span><span class="n">userRegion</span><span class="p">]</span>
            <span class="o">?:</span> <span class="n">regionalApiClients</span><span class="p">[</span><span class="s">"default"</span><span class="p">]</span><span class="o">!!</span>

        <span class="k">return</span> <span class="n">client</span><span class="p">.</span><span class="k">get</span><span class="p">()</span>
            <span class="p">.</span><span class="nf">uri</span><span class="p">(</span><span class="s">"/api/data/{userId}"</span><span class="p">,</span> <span class="n">userId</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">retrieve</span><span class="p">()</span>
            <span class="p">.</span><span class="nf">bodyToMono</span><span class="p">(</span><span class="nc">Data</span><span class="o">::</span><span class="k">class</span><span class="p">.</span><span class="n">java</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">block</span><span class="p">()</span><span class="o">!!</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="nd">@Configuration</span>
<span class="kd">class</span> <span class="nc">RegionalClientConfig</span> <span class="p">{</span>

    <span class="nd">@Bean</span>
    <span class="k">fun</span> <span class="nf">regionalApiClients</span><span class="p">():</span> <span class="nc">Map</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">,</span> <span class="nc">WebClient</span><span class="p">&gt;</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nf">mapOf</span><span class="p">(</span>
            <span class="s">"ap-northeast-2"</span> <span class="n">to</span> <span class="nc">WebClient</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="s">"https://api-ap.example.com"</span><span class="p">),</span>
            <span class="s">"us-east-1"</span> <span class="n">to</span> <span class="nc">WebClient</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="s">"https://api-us.example.com"</span><span class="p">),</span>
            <span class="s">"eu-west-1"</span> <span class="n">to</span> <span class="nc">WebClient</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="s">"https://api-eu.example.com"</span><span class="p">),</span>
            <span class="s">"default"</span> <span class="n">to</span> <span class="nc">WebClient</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="s">"https://api-us.example.com"</span><span class="p">)</span>
        <span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="하이브리드-아키텍처">하이브리드 아키텍처</h2>

<h3 id="엣지--오리진-조합">엣지 + 오리진 조합</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>┌────────────────────────────────────────────────────┐
│                     엣지 레이어                     │
│  ┌─────────────┐  ┌─────────────┐  ┌───────────┐  │
│  │ Auth 검증   │  │ Rate Limit  │  │   Cache   │  │
│  │ Token 파싱  │  │ Bot 탐지    │  │ Static    │  │
│  └─────────────┘  └─────────────┘  └───────────┘  │
└───────────────────────┬────────────────────────────┘
                        │
                        ▼
┌────────────────────────────────────────────────────┐
│                    오리진 레이어                    │
│  ┌─────────────┐  ┌─────────────┐  ┌───────────┐  │
│  │ Business    │  │ Database    │  │ External  │  │
│  │ Logic       │  │ Operations  │  │ APIs      │  │
│  └─────────────┘  └─────────────┘  └───────────┘  │
└────────────────────────────────────────────────────┘
</code></pre></div></div>

<h3 id="엣지에서-처리할-작업">엣지에서 처리할 작업</h3>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Cloudflare Worker - 하이브리드 처리</span>
<span class="k">export</span> <span class="k">default</span> <span class="p">{</span>
  <span class="k">async</span> <span class="nf">fetch</span><span class="p">(</span><span class="nx">request</span><span class="p">,</span> <span class="nx">env</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">URL</span><span class="p">(</span><span class="nx">request</span><span class="p">.</span><span class="nx">url</span><span class="p">);</span>

    <span class="c1">// 1. 정적 리소스 - 엣지에서 완전 처리</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">url</span><span class="p">.</span><span class="nx">pathname</span><span class="p">.</span><span class="nf">startsWith</span><span class="p">(</span><span class="dl">'</span><span class="s1">/static/</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
      <span class="k">return</span> <span class="nf">handleStaticResource</span><span class="p">(</span><span class="nx">request</span><span class="p">,</span> <span class="nx">env</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// 2. 인증 - 엣지에서 검증</span>
    <span class="kd">const</span> <span class="nx">authResult</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">validateAuth</span><span class="p">(</span><span class="nx">request</span><span class="p">,</span> <span class="nx">env</span><span class="p">);</span>
    <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">authResult</span><span class="p">.</span><span class="nx">valid</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span> <span class="k">new</span> <span class="nc">Response</span><span class="p">(</span><span class="dl">'</span><span class="s1">Unauthorized</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">status</span><span class="p">:</span> <span class="mi">401</span> <span class="p">});</span>
    <span class="p">}</span>

    <span class="c1">// 3. Rate Limiting - 엣지에서 처리</span>
    <span class="kd">const</span> <span class="nx">rateLimitResult</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">checkRateLimit</span><span class="p">(</span><span class="nx">request</span><span class="p">,</span> <span class="nx">env</span><span class="p">);</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">rateLimitResult</span><span class="p">.</span><span class="nx">exceeded</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span> <span class="k">new</span> <span class="nc">Response</span><span class="p">(</span><span class="dl">'</span><span class="s1">Too Many Requests</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
        <span class="na">status</span><span class="p">:</span> <span class="mi">429</span><span class="p">,</span>
        <span class="na">headers</span><span class="p">:</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">Retry-After</span><span class="dl">'</span><span class="p">:</span> <span class="nx">rateLimitResult</span><span class="p">.</span><span class="nx">retryAfter</span> <span class="p">}</span>
      <span class="p">});</span>
    <span class="p">}</span>

    <span class="c1">// 4. 캐시 가능한 API - 엣지 캐시 확인</span>
    <span class="k">if </span><span class="p">(</span><span class="nf">isCacheable</span><span class="p">(</span><span class="nx">request</span><span class="p">))</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">cached</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">getCachedResponse</span><span class="p">(</span><span class="nx">request</span><span class="p">,</span> <span class="nx">env</span><span class="p">);</span>
      <span class="k">if </span><span class="p">(</span><span class="nx">cached</span><span class="p">)</span> <span class="k">return</span> <span class="nx">cached</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// 5. 오리진으로 전달</span>
    <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(</span><span class="nx">env</span><span class="p">.</span><span class="nx">ORIGIN_URL</span> <span class="o">+</span> <span class="nx">url</span><span class="p">.</span><span class="nx">pathname</span><span class="p">,</span> <span class="p">{</span>
      <span class="na">method</span><span class="p">:</span> <span class="nx">request</span><span class="p">.</span><span class="nx">method</span><span class="p">,</span>
      <span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
        <span class="p">...</span><span class="nx">request</span><span class="p">.</span><span class="nx">headers</span><span class="p">,</span>
        <span class="dl">'</span><span class="s1">X-User-Id</span><span class="dl">'</span><span class="p">:</span> <span class="nx">authResult</span><span class="p">.</span><span class="nx">userId</span><span class="p">,</span>
        <span class="dl">'</span><span class="s1">X-Request-Region</span><span class="dl">'</span><span class="p">:</span> <span class="nx">request</span><span class="p">.</span><span class="nx">cf</span><span class="p">.</span><span class="nx">colo</span>
      <span class="p">},</span>
      <span class="na">body</span><span class="p">:</span> <span class="nx">request</span><span class="p">.</span><span class="nx">body</span>
    <span class="p">});</span>

    <span class="c1">// 6. 응답 캐싱</span>
    <span class="k">if </span><span class="p">(</span><span class="nf">isCacheable</span><span class="p">(</span><span class="nx">request</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nx">response</span><span class="p">.</span><span class="nx">ok</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">await</span> <span class="nf">cacheResponse</span><span class="p">(</span><span class="nx">request</span><span class="p">,</span> <span class="nx">response</span><span class="p">.</span><span class="nf">clone</span><span class="p">(),</span> <span class="nx">env</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="nx">response</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">};</span>
</code></pre></div></div>

<h2 id="엣지-데이터베이스">엣지 데이터베이스</h2>

<h3 id="cloudflare-d1-sqlite-at-edge">Cloudflare D1 (SQLite at Edge)</h3>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// D1 사용 예시</span>
<span class="k">export</span> <span class="k">default</span> <span class="p">{</span>
  <span class="k">async</span> <span class="nf">fetch</span><span class="p">(</span><span class="nx">request</span><span class="p">,</span> <span class="nx">env</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">URL</span><span class="p">(</span><span class="nx">request</span><span class="p">.</span><span class="nx">url</span><span class="p">);</span>

    <span class="k">if </span><span class="p">(</span><span class="nx">url</span><span class="p">.</span><span class="nx">pathname</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">/api/products</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="p">{</span> <span class="nx">results</span> <span class="p">}</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">env</span><span class="p">.</span><span class="nx">DB</span><span class="p">.</span><span class="nf">prepare</span><span class="p">(</span>
        <span class="dl">'</span><span class="s1">SELECT * FROM products WHERE category = ? LIMIT 10</span><span class="dl">'</span>
      <span class="p">)</span>
        <span class="p">.</span><span class="nf">bind</span><span class="p">(</span><span class="nx">url</span><span class="p">.</span><span class="nx">searchParams</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">category</span><span class="dl">'</span><span class="p">))</span>
        <span class="p">.</span><span class="nf">all</span><span class="p">();</span>

      <span class="k">return</span> <span class="nx">Response</span><span class="p">.</span><span class="nf">json</span><span class="p">(</span><span class="nx">results</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// 오리진으로 폴백</span>
    <span class="k">return</span> <span class="nf">fetch</span><span class="p">(</span><span class="nx">request</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">};</span>
</code></pre></div></div>

<h3 id="cloudflare-kv-key-value-at-edge">Cloudflare KV (Key-Value at Edge)</h3>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// KV를 활용한 세션 관리</span>
<span class="k">export</span> <span class="k">default</span> <span class="p">{</span>
  <span class="k">async</span> <span class="nf">fetch</span><span class="p">(</span><span class="nx">request</span><span class="p">,</span> <span class="nx">env</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">sessionId</span> <span class="o">=</span> <span class="nf">getCookie</span><span class="p">(</span><span class="nx">request</span><span class="p">,</span> <span class="dl">'</span><span class="s1">session_id</span><span class="dl">'</span><span class="p">);</span>

    <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">sessionId</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span> <span class="k">new</span> <span class="nc">Response</span><span class="p">(</span><span class="dl">'</span><span class="s1">No session</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">status</span><span class="p">:</span> <span class="mi">401</span> <span class="p">});</span>
    <span class="p">}</span>

    <span class="c1">// 엣지에서 세션 조회 (&lt; 1ms)</span>
    <span class="kd">const</span> <span class="nx">session</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">env</span><span class="p">.</span><span class="nx">SESSIONS</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="nx">sessionId</span><span class="p">,</span> <span class="dl">'</span><span class="s1">json</span><span class="dl">'</span><span class="p">);</span>

    <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">session</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span> <span class="k">new</span> <span class="nc">Response</span><span class="p">(</span><span class="dl">'</span><span class="s1">Invalid session</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">status</span><span class="p">:</span> <span class="mi">401</span> <span class="p">});</span>
    <span class="p">}</span>

    <span class="c1">// 세션 정보를 헤더에 추가하여 오리진 전달</span>
    <span class="kd">const</span> <span class="nx">modifiedRequest</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Request</span><span class="p">(</span><span class="nx">request</span><span class="p">);</span>
    <span class="nx">modifiedRequest</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="dl">'</span><span class="s1">X-User-Id</span><span class="dl">'</span><span class="p">,</span> <span class="nx">session</span><span class="p">.</span><span class="nx">userId</span><span class="p">);</span>
    <span class="nx">modifiedRequest</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="dl">'</span><span class="s1">X-User-Role</span><span class="dl">'</span><span class="p">,</span> <span class="nx">session</span><span class="p">.</span><span class="nx">role</span><span class="p">);</span>

    <span class="k">return</span> <span class="nf">fetch</span><span class="p">(</span><span class="nx">modifiedRequest</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">};</span>
</code></pre></div></div>

<h2 id="모니터링-및-분석">모니터링 및 분석</h2>

<h3 id="엣지-로그-수집">엣지 로그 수집</h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Spring Boot에서 엣지 정보 수집</span>
<span class="nd">@RestController</span>
<span class="kd">class</span> <span class="nc">ApiController</span> <span class="p">{</span>

    <span class="nd">@GetMapping</span><span class="p">(</span><span class="s">"/api/data"</span><span class="p">)</span>
    <span class="k">fun</span> <span class="nf">getData</span><span class="p">(</span>
        <span class="nd">@RequestHeader</span><span class="p">(</span><span class="s">"CF-Ray"</span><span class="p">)</span> <span class="n">cfRay</span><span class="p">:</span> <span class="nc">String</span><span class="p">?,</span>
        <span class="nd">@RequestHeader</span><span class="p">(</span><span class="s">"CF-IPCountry"</span><span class="p">)</span> <span class="n">country</span><span class="p">:</span> <span class="nc">String</span><span class="p">?,</span>
        <span class="nd">@RequestHeader</span><span class="p">(</span><span class="s">"X-Request-Region"</span><span class="p">)</span> <span class="n">region</span><span class="p">:</span> <span class="nc">String</span><span class="p">?</span>
    <span class="p">):</span> <span class="nc">ResponseEntity</span><span class="p">&lt;</span><span class="nc">Data</span><span class="p">&gt;</span> <span class="p">{</span>
        <span class="c1">// 메트릭 기록</span>
        <span class="nc">Metrics</span><span class="p">.</span><span class="nf">counter</span><span class="p">(</span><span class="s">"api.requests"</span><span class="p">,</span>
            <span class="s">"country"</span><span class="p">,</span> <span class="n">country</span> <span class="o">?:</span> <span class="s">"unknown"</span><span class="p">,</span>
            <span class="s">"edge_region"</span><span class="p">,</span> <span class="n">region</span> <span class="o">?:</span> <span class="s">"unknown"</span>
        <span class="p">).</span><span class="nf">increment</span><span class="p">()</span>

        <span class="c1">// 로그</span>
        <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="s">"Request from country=$country, edge=$region, ray=$cfRay"</span><span class="p">)</span>

        <span class="k">return</span> <span class="nc">ResponseEntity</span><span class="p">.</span><span class="nf">ok</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="엣지-메트릭-대시보드">엣지 메트릭 대시보드</h3>

<pre><code class="language-promql"># 지역별 요청 분포
sum by (country) (rate(api_requests_total[5m]))

# 엣지 캐시 히트율
sum(rate(edge_cache_hits_total[5m])) /
sum(rate(edge_requests_total[5m])) * 100

# 지역별 응답 시간
histogram_quantile(0.95,
  sum by (le, region) (
    rate(http_request_duration_seconds_bucket[5m])
  )
)
</code></pre>

<h2 id="정리">정리</h2>

<p>엣지 컴퓨팅 활용 체크리스트:</p>

<table>
  <thead>
    <tr>
      <th>작업</th>
      <th>처리 위치</th>
      <th>기술</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>정적 리소스</td>
      <td>엣지</td>
      <td>CDN 캐싱</td>
    </tr>
    <tr>
      <td>인증/인가</td>
      <td>엣지</td>
      <td>Edge Functions</td>
    </tr>
    <tr>
      <td>Rate Limiting</td>
      <td>엣지</td>
      <td>Edge Functions + KV</td>
    </tr>
    <tr>
      <td>세션 관리</td>
      <td>엣지</td>
      <td>Edge KV</td>
    </tr>
    <tr>
      <td>비즈니스 로직</td>
      <td>오리진</td>
      <td>Spring Boot</td>
    </tr>
    <tr>
      <td>데이터베이스</td>
      <td>오리진/엣지</td>
      <td>RDS/D1</td>
    </tr>
    <tr>
      <td>API 캐싱</td>
      <td>엣지</td>
      <td>CDN + Surrogate Keys</td>
    </tr>
  </tbody>
</table>]]></content><author><name>울이</name></author><category term="Backend Engineering" /><category term="Spring" /><category term="Java" /><category term="SpringBoot" /><category term="Edge-Computing" /><category term="CDN" /><category term="Cloudflare-Workers" /><category term="Lambda@Edge" /><category term="Performance" /><summary type="html"><![CDATA[백엔드 개발자가 알아야 할 엣지 컴퓨팅 패턴을 다룹니다. CDN 활용, Edge Functions, 지역별 라우팅부터 하이브리드 아키텍처까지 실무 가이드를 제공합니다.]]></summary></entry><entry><title type="html">Cloud-Native Observability Stack Part 5 - Debugging Production Issues with Observability Data</title><link href="https://www.wool-dev.com/backend%20engineering/spring/observability-part5-debugging-production/" rel="alternate" type="text/html" title="Cloud-Native Observability Stack Part 5 - Debugging Production Issues with Observability Data" /><published>2026-01-29T00:00:00+09:00</published><updated>2026-01-29T00:00:00+09:00</updated><id>https://www.wool-dev.com/backend%20engineering/spring/observability-part5-debugging-production</id><content type="html" xml:base="https://www.wool-dev.com/backend%20engineering/spring/observability-part5-debugging-production/"><![CDATA[<h2 id="시리즈-소개">시리즈 소개</h2>

<ol>
  <li>Part 1: OpenTelemetry Instrumentation</li>
  <li>Part 2: 마이크로서비스 분산 추적</li>
  <li>Part 3: 구조화된 로깅과 Correlation ID</li>
  <li>Part 4: Prometheus/Grafana로 메트릭과 알림</li>
  <li><strong>Part 5: Observability 데이터로 프로덕션 이슈 디버깅</strong> (현재 글)</li>
</ol>

<h2 id="디버깅-워크플로우">디버깅 워크플로우</h2>

<h3 id="melt-접근법">MELT 접근법</h3>

<p><strong>M</strong>etrics → <strong>E</strong>vents → <strong>L</strong>ogs → <strong>T</strong>races</p>

<ol>
  <li><strong>메트릭</strong>으로 문제 감지</li>
  <li><strong>이벤트/알림</strong>으로 시점 확인</li>
  <li><strong>로그</strong>로 상세 정보 파악</li>
  <li><strong>트레이스</strong>로 요청 흐름 추적</li>
</ol>

<h2 id="실제-장애-시나리오">실제 장애 시나리오</h2>

<h3 id="시나리오-1-간헐적-타임아웃">시나리오 1: 간헐적 타임아웃</h3>

<p><strong>증상</strong>: 일부 주문 생성 요청이 30초 후 타임아웃</p>

<p><strong>1단계: 메트릭 확인</strong></p>

<pre><code class="language-promql"># P99 레이턴시 급증 확인
histogram_quantile(0.99,
  sum(rate(http_server_requests_seconds_bucket{uri="/api/orders"}[5m])) by (le)
)
</code></pre>

<p>Grafana에서 확인: P99 레이턴시가 특정 시간대에 30초까지 급증</p>

<p><strong>2단계: 트레이스 분석</strong></p>

<p>Jaeger에서 느린 요청 검색:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service=order-service minDuration=10s
</code></pre></div></div>

<p>발견: <code class="language-plaintext highlighter-rouge">inventory.checkStock</code> span이 29초 소요</p>

<p><strong>3단계: 로그 확인</strong></p>

<pre><code class="language-logql">{service="inventory-service"} | json | latency &gt; 10000
</code></pre>

<p>발견: 특정 상품 ID에서 데이터베이스 쿼리가 느림</p>

<p><strong>4단계: 근본 원인</strong></p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">-- 실행 계획 확인</span>
<span class="k">EXPLAIN</span> <span class="k">ANALYZE</span> <span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">inventory</span> <span class="k">WHERE</span> <span class="n">product_id</span> <span class="o">=</span> <span class="s1">'PROD-12345'</span><span class="p">;</span>
</code></pre></div></div>

<p>원인: <code class="language-plaintext highlighter-rouge">product_id</code> 인덱스 누락</p>

<p><strong>해결</strong>:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">INDEX</span> <span class="n">idx_inventory_product_id</span> <span class="k">ON</span> <span class="n">inventory</span><span class="p">(</span><span class="n">product_id</span><span class="p">);</span>
</code></pre></div></div>

<h3 id="시나리오-2-메모리-누수">시나리오 2: 메모리 누수</h3>

<p><strong>증상</strong>: 서비스가 주기적으로 OOM으로 재시작</p>

<p><strong>1단계: 메트릭 확인</strong></p>

<pre><code class="language-promql"># Heap 메모리 사용량 추세
jvm_memory_used_bytes{area="heap",application="order-service"}
</code></pre>

<p>패턴 발견: 메모리가 점진적으로 증가 후 급락 (재시작)</p>

<p><strong>2단계: GC 로그 분석</strong></p>

<pre><code class="language-promql"># GC 빈도 증가
rate(jvm_gc_pause_seconds_count{application="order-service"}[5m])
</code></pre>

<p>발견: Full GC 빈도가 점점 증가</p>

<p><strong>3단계: 힙 덤프 분석</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 힙 덤프 생성</span>
jmap <span class="nt">-dump</span>:format<span class="o">=</span>b,file<span class="o">=</span>heapdump.hprof &lt;pid&gt;

<span class="c"># MAT 또는 VisualVM으로 분석</span>
</code></pre></div></div>

<p>발견: <code class="language-plaintext highlighter-rouge">OrderCache</code> 객체가 메모리의 80% 차지</p>

<p><strong>4단계: 코드 검토</strong></p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 문제 코드</span>
<span class="nd">@Component</span>
<span class="kd">class</span> <span class="nc">OrderCache</span> <span class="p">{</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">cache</span> <span class="p">=</span> <span class="nc">ConcurrentHashMap</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">,</span> <span class="nc">Order</span><span class="p">&gt;()</span>

    <span class="k">fun</span> <span class="nf">put</span><span class="p">(</span><span class="n">orderId</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span> <span class="n">order</span><span class="p">:</span> <span class="nc">Order</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">cache</span><span class="p">[</span><span class="n">orderId</span><span class="p">]</span> <span class="p">=</span> <span class="n">order</span>  <span class="c1">// 제거 로직 없음!</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>해결</strong>:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span>
<span class="kd">class</span> <span class="nc">OrderCache</span> <span class="p">{</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">cache</span> <span class="p">=</span> <span class="nc">Caffeine</span><span class="p">.</span><span class="nf">newBuilder</span><span class="p">()</span>
        <span class="p">.</span><span class="nf">maximumSize</span><span class="p">(</span><span class="mi">10_000</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">expireAfterWrite</span><span class="p">(</span><span class="nc">Duration</span><span class="p">.</span><span class="nf">ofHours</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span>
        <span class="p">.</span><span class="n">build</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">,</span> <span class="nc">Order</span><span class="p">&gt;()</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="시나리오-3-서비스-간-연쇄-장애">시나리오 3: 서비스 간 연쇄 장애</h3>

<p><strong>증상</strong>: 결제 서비스 장애가 전체 시스템 마비로 이어짐</p>

<p><strong>1단계: 의존성 그래프 확인</strong></p>

<p>Jaeger Service Map에서 확인:</p>
<ul>
  <li>Order Service → Payment Service (동기 호출)</li>
  <li>Payment Service 장애 시 Order Service 스레드 블로킹</li>
</ul>

<p><strong>2단계: 메트릭 확인</strong></p>

<pre><code class="language-promql"># 연결 풀 고갈
hikaricp_connections_active{application="order-service"}
hikaricp_connections_pending{application="order-service"}
</code></pre>

<p>발견: Payment Service 타임아웃 동안 모든 연결이 대기 상태</p>

<p><strong>3단계: 로그 확인</strong></p>

<pre><code class="language-logql">{service="order-service"} |= "Connection pool exhausted"
</code></pre>

<p><strong>해결: Circuit Breaker 패턴 적용</strong></p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span>
<span class="kd">class</span> <span class="nc">PaymentClient</span><span class="p">(</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">circuitBreakerFactory</span><span class="p">:</span> <span class="nc">Resilience4JCircuitBreakerFactory</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">circuitBreaker</span> <span class="p">=</span> <span class="n">circuitBreakerFactory</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="s">"payment"</span><span class="p">)</span>

    <span class="k">fun</span> <span class="nf">processPayment</span><span class="p">(</span><span class="n">order</span><span class="p">:</span> <span class="nc">Order</span><span class="p">):</span> <span class="nc">PaymentResult</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">circuitBreaker</span><span class="p">.</span><span class="nf">run</span><span class="p">(</span>
            <span class="p">{</span> <span class="n">paymentApi</span><span class="p">.</span><span class="nf">charge</span><span class="p">(</span><span class="n">order</span><span class="p">)</span> <span class="p">},</span>
            <span class="p">{</span> <span class="n">fallback</span> <span class="p">-&gt;</span> <span class="nf">handleFallback</span><span class="p">(</span><span class="n">order</span><span class="p">)</span> <span class="p">}</span>
        <span class="p">)</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="k">fun</span> <span class="nf">handleFallback</span><span class="p">(</span><span class="n">order</span><span class="p">:</span> <span class="nc">Order</span><span class="p">):</span> <span class="nc">PaymentResult</span> <span class="p">{</span>
        <span class="c1">// 결제 대기열에 추가하고 나중에 처리</span>
        <span class="n">paymentQueue</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">order</span><span class="p">)</span>
        <span class="k">return</span> <span class="nc">PaymentResult</span><span class="p">.</span><span class="nc">PENDING</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="디버깅-도구-모음">디버깅 도구 모음</h2>

<h3 id="1-분산-트레이스-검색-쿼리">1. 분산 트레이스 검색 쿼리</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># 느린 요청
service=order-service minDuration=1s

# 에러 요청
service=order-service error=true

# 특정 사용자
service=order-service tag.customer.id=CUST-123
</code></pre></div></div>

<h3 id="2-유용한-promql-쿼리">2. 유용한 PromQL 쿼리</h3>

<pre><code class="language-promql"># 에러율 급증 서비스 찾기
topk(5,
  sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m])) by (application)
)

# 레이턴시 급증 엔드포인트
topk(5,
  histogram_quantile(0.99,
    sum(rate(http_server_requests_seconds_bucket[5m])) by (uri, le)
  )
)

# 메모리 사용량 상위 서비스
topk(5,
  jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"}
)
</code></pre>

<h3 id="3-유용한-logql-쿼리">3. 유용한 LogQL 쿼리</h3>

<pre><code class="language-logql"># 에러 로그 집계
sum by (errorType) (
  count_over_time({service="order-service"} | json | level="ERROR" [1h])
)

# 특정 traceId의 모든 로그
{service=~".+"} |= "traceId=abc123"

# 느린 쿼리 로그
{service=~".+"} | json | queryTime &gt; 1000
</code></pre>

<h2 id="on-call-플레이북">On-Call 플레이북</h2>

<h3 id="서비스-다운-시">서비스 다운 시</h3>

<ol>
  <li><strong>즉시 확인</strong>
    <ul>
      <li><code class="language-plaintext highlighter-rouge">up{job="spring-boot-apps"}</code> 메트릭 확인</li>
      <li>Pod 상태 확인: <code class="language-plaintext highlighter-rouge">kubectl get pods</code></li>
    </ul>
  </li>
  <li><strong>최근 변경 확인</strong>
    <ul>
      <li>최근 배포 이력</li>
      <li>설정 변경</li>
    </ul>
  </li>
  <li><strong>로그 확인</strong>
    <ul>
      <li>시작 로그에서 에러 확인</li>
      <li>OOM 여부 확인</li>
    </ul>
  </li>
  <li><strong>롤백 결정</strong>
    <ul>
      <li>빠른 복구가 필요하면 이전 버전으로 롤백</li>
    </ul>
  </li>
</ol>

<h3 id="성능-저하-시">성능 저하 시</h3>

<ol>
  <li><strong>영향 범위 파악</strong>
    <ul>
      <li>전체 서비스? 특정 엔드포인트?</li>
    </ul>
  </li>
  <li><strong>병목 지점 식별</strong>
    <ul>
      <li>트레이스로 느린 span 확인</li>
      <li>외부 의존성 문제?</li>
    </ul>
  </li>
  <li><strong>리소스 확인</strong>
    <ul>
      <li>CPU, 메모리, 디스크 I/O</li>
      <li>연결 풀 상태</li>
    </ul>
  </li>
  <li><strong>임시 조치</strong>
    <ul>
      <li>스케일 아웃</li>
      <li>Rate limiting 적용</li>
    </ul>
  </li>
</ol>

<h2 id="포스트모템-템플릿">포스트모템 템플릿</h2>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh"># 장애 보고서: [제목]</span>

<span class="gu">## 개요</span>
<span class="p">-</span> 발생 시간: YYYY-MM-DD HH:MM ~ HH:MM (KST)
<span class="p">-</span> 영향 범위: [서비스명, 사용자 수]
<span class="p">-</span> 심각도: [Critical/High/Medium/Low]

<span class="gu">## 타임라인</span>
<span class="p">-</span> HH:MM - 첫 번째 알림 발생
<span class="p">-</span> HH:MM - 조사 시작
<span class="p">-</span> HH:MM - 근본 원인 파악
<span class="p">-</span> HH:MM - 수정 배포
<span class="p">-</span> HH:MM - 정상화 확인

<span class="gu">## 근본 원인</span>
[상세 설명]

<span class="gu">## 해결 방법</span>
[수행한 조치]

<span class="gu">## 영향</span>
<span class="p">-</span> 에러율: X%
<span class="p">-</span> 영향받은 요청 수: N건

<span class="gu">## 교훈</span>
<span class="gu">### 잘된 점</span>
<span class="p">-</span>

<span class="gu">### 개선할 점</span>
<span class="p">-</span>

<span class="gu">## 액션 아이템</span>
<span class="p">-</span> [ ] [담당자] 액션 내용 (기한)
</code></pre></div></div>

<h2 id="시리즈-마무리">시리즈 마무리</h2>

<p>이 시리즈에서 다룬 내용:</p>

<table>
  <thead>
    <tr>
      <th>Part</th>
      <th>주제</th>
      <th>핵심</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>1</td>
      <td>OpenTelemetry</td>
      <td>계측의 기초</td>
    </tr>
    <tr>
      <td>2</td>
      <td>분산 추적</td>
      <td>요청 흐름 시각화</td>
    </tr>
    <tr>
      <td>3</td>
      <td>구조화된 로깅</td>
      <td>검색 가능한 로그</td>
    </tr>
    <tr>
      <td>4</td>
      <td>메트릭/알림</td>
      <td>선제적 모니터링</td>
    </tr>
    <tr>
      <td>5</td>
      <td>디버깅</td>
      <td>실전 문제 해결</td>
    </tr>
  </tbody>
</table>

<p>Observability는 단순한 모니터링이 아닙니다. 시스템을 이해하고, 문제를 예방하며, 빠르게 해결할 수 있는 능력입니다.</p>]]></content><author><name>울이</name></author><category term="Backend Engineering" /><category term="Spring" /><category term="Java" /><category term="SpringBoot" /><category term="Debugging" /><category term="Production" /><category term="Observability" /><category term="Troubleshooting" /><summary type="html"><![CDATA[Observability 데이터(트레이스, 메트릭, 로그)를 활용하여 프로덕션 이슈를 효과적으로 디버깅하는 방법을 다룹니다. 실제 장애 시나리오와 해결 과정을 상세히 설명합니다.]]></summary></entry><entry><title type="html">Cloud-Native Observability Stack Part 4 - Metrics and Alerting with Prometheus/Grafana</title><link href="https://www.wool-dev.com/backend%20engineering/spring/observability-part4-metrics-alerting/" rel="alternate" type="text/html" title="Cloud-Native Observability Stack Part 4 - Metrics and Alerting with Prometheus/Grafana" /><published>2026-01-28T00:00:00+09:00</published><updated>2026-01-28T00:00:00+09:00</updated><id>https://www.wool-dev.com/backend%20engineering/spring/observability-part4-metrics-alerting</id><content type="html" xml:base="https://www.wool-dev.com/backend%20engineering/spring/observability-part4-metrics-alerting/"><![CDATA[<h2 id="시리즈-소개">시리즈 소개</h2>

<ol>
  <li>Part 1: OpenTelemetry Instrumentation</li>
  <li>Part 2: 마이크로서비스 분산 추적</li>
  <li>Part 3: 구조화된 로깅과 Correlation ID</li>
  <li><strong>Part 4: Prometheus/Grafana로 메트릭과 알림</strong> (현재 글)</li>
  <li>Part 5: Observability 데이터로 프로덕션 이슈 디버깅</li>
</ol>

<h2 id="메트릭의-중요성">메트릭의 중요성</h2>

<p>메트릭은 시스템의 건강 상태를 수치로 보여줍니다:</p>
<ul>
  <li>요청 처리량 (Throughput)</li>
  <li>응답 시간 (Latency)</li>
  <li>에러율 (Error Rate)</li>
  <li>리소스 사용량 (CPU, Memory)</li>
</ul>

<h2 id="spring-boot--micrometer-설정">Spring Boot + Micrometer 설정</h2>

<h3 id="의존성-추가">의존성 추가</h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">dependencies</span> <span class="p">{</span>
    <span class="nf">implementation</span><span class="p">(</span><span class="s">"org.springframework.boot:spring-boot-starter-actuator"</span><span class="p">)</span>
    <span class="nf">implementation</span><span class="p">(</span><span class="s">"io.micrometer:micrometer-registry-prometheus"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="application-설정">Application 설정</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">management</span><span class="pi">:</span>
  <span class="na">endpoints</span><span class="pi">:</span>
    <span class="na">web</span><span class="pi">:</span>
      <span class="na">exposure</span><span class="pi">:</span>
        <span class="na">include</span><span class="pi">:</span> <span class="s">health,info,prometheus,metrics</span>
  <span class="na">endpoint</span><span class="pi">:</span>
    <span class="na">health</span><span class="pi">:</span>
      <span class="na">show-details</span><span class="pi">:</span> <span class="s">always</span>
  <span class="na">metrics</span><span class="pi">:</span>
    <span class="na">tags</span><span class="pi">:</span>
      <span class="na">application</span><span class="pi">:</span> <span class="s">order-service</span>
      <span class="na">environment</span><span class="pi">:</span> <span class="s">production</span>
    <span class="na">distribution</span><span class="pi">:</span>
      <span class="na">percentiles-histogram</span><span class="pi">:</span>
        <span class="na">http.server.requests</span><span class="pi">:</span> <span class="kc">true</span>
      <span class="na">slo</span><span class="pi">:</span>
        <span class="na">http.server.requests</span><span class="pi">:</span> <span class="s">100ms,500ms,1000ms</span>
</code></pre></div></div>

<h2 id="기본-제공-메트릭">기본 제공 메트릭</h2>

<h3 id="http-요청-메트릭">HTTP 요청 메트릭</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http_server_requests_seconds_count{method="POST",uri="/api/orders",status="200"}
http_server_requests_seconds_sum{method="POST",uri="/api/orders",status="200"}
http_server_requests_seconds_bucket{method="POST",uri="/api/orders",status="200",le="0.1"}
</code></pre></div></div>

<h3 id="jvm-메트릭">JVM 메트릭</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jvm_memory_used_bytes{area="heap",id="G1 Eden Space"}
jvm_gc_pause_seconds_count{action="end of minor GC",cause="G1 Evacuation Pause"}
jvm_threads_live_threads
</code></pre></div></div>

<h2 id="커스텀-메트릭-구현">커스텀 메트릭 구현</h2>

<h3 id="counter-카운터">Counter (카운터)</h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span>
<span class="kd">class</span> <span class="nc">OrderMetrics</span><span class="p">(</span><span class="k">private</span> <span class="kd">val</span> <span class="py">meterRegistry</span><span class="p">:</span> <span class="nc">MeterRegistry</span><span class="p">)</span> <span class="p">{</span>

    <span class="k">private</span> <span class="kd">val</span> <span class="py">ordersCreated</span> <span class="p">=</span> <span class="nc">Counter</span><span class="p">.</span><span class="nf">builder</span><span class="p">(</span><span class="s">"orders.created"</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">description</span><span class="p">(</span><span class="s">"Total number of orders created"</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">tag</span><span class="p">(</span><span class="s">"service"</span><span class="p">,</span> <span class="s">"order-service"</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">register</span><span class="p">(</span><span class="n">meterRegistry</span><span class="p">)</span>

    <span class="k">private</span> <span class="kd">val</span> <span class="py">ordersFailed</span> <span class="p">=</span> <span class="nc">Counter</span><span class="p">.</span><span class="nf">builder</span><span class="p">(</span><span class="s">"orders.failed"</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">description</span><span class="p">(</span><span class="s">"Total number of failed orders"</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">tag</span><span class="p">(</span><span class="s">"service"</span><span class="p">,</span> <span class="s">"order-service"</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">register</span><span class="p">(</span><span class="n">meterRegistry</span><span class="p">)</span>

    <span class="k">fun</span> <span class="nf">recordOrderCreated</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">ordersCreated</span><span class="p">.</span><span class="nf">increment</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="k">fun</span> <span class="nf">recordOrderFailed</span><span class="p">(</span><span class="n">reason</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span> <span class="p">{</span>
        <span class="nc">Counter</span><span class="p">.</span><span class="nf">builder</span><span class="p">(</span><span class="s">"orders.failed"</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">tag</span><span class="p">(</span><span class="s">"reason"</span><span class="p">,</span> <span class="n">reason</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">register</span><span class="p">(</span><span class="n">meterRegistry</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">increment</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="gauge-게이지">Gauge (게이지)</h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span>
<span class="kd">class</span> <span class="nc">QueueMetrics</span><span class="p">(</span>
    <span class="n">meterRegistry</span><span class="p">:</span> <span class="nc">MeterRegistry</span><span class="p">,</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">orderQueue</span><span class="p">:</span> <span class="nc">OrderQueue</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="nf">init</span> <span class="p">{</span>
        <span class="nc">Gauge</span><span class="p">.</span><span class="nf">builder</span><span class="p">(</span><span class="s">"order.queue.size"</span><span class="p">,</span> <span class="n">orderQueue</span><span class="p">)</span> <span class="p">{</span> <span class="n">queue</span> <span class="p">-&gt;</span>
            <span class="n">queue</span><span class="p">.</span><span class="nf">size</span><span class="p">().</span><span class="nf">toDouble</span><span class="p">()</span>
        <span class="p">}</span>
            <span class="p">.</span><span class="nf">description</span><span class="p">(</span><span class="s">"Current size of order processing queue"</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">register</span><span class="p">(</span><span class="n">meterRegistry</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="timer-타이머">Timer (타이머)</h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span>
<span class="kd">class</span> <span class="nc">PaymentService</span><span class="p">(</span><span class="k">private</span> <span class="kd">val</span> <span class="py">meterRegistry</span><span class="p">:</span> <span class="nc">MeterRegistry</span><span class="p">)</span> <span class="p">{</span>

    <span class="k">private</span> <span class="kd">val</span> <span class="py">paymentTimer</span> <span class="p">=</span> <span class="nc">Timer</span><span class="p">.</span><span class="nf">builder</span><span class="p">(</span><span class="s">"payment.processing.time"</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">description</span><span class="p">(</span><span class="s">"Time taken to process payments"</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">publishPercentiles</span><span class="p">(</span><span class="mf">0.5</span><span class="p">,</span> <span class="mf">0.95</span><span class="p">,</span> <span class="mf">0.99</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">register</span><span class="p">(</span><span class="n">meterRegistry</span><span class="p">)</span>

    <span class="k">fun</span> <span class="nf">processPayment</span><span class="p">(</span><span class="n">order</span><span class="p">:</span> <span class="nc">Order</span><span class="p">):</span> <span class="nc">PaymentResult</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">paymentTimer</span><span class="p">.</span><span class="nf">recordCallable</span> <span class="p">{</span>
            <span class="c1">// 결제 처리 로직</span>
            <span class="n">paymentGateway</span><span class="p">.</span><span class="nf">charge</span><span class="p">(</span><span class="n">order</span><span class="p">.</span><span class="n">customerId</span><span class="p">,</span> <span class="n">order</span><span class="p">.</span><span class="n">totalAmount</span><span class="p">)</span>
        <span class="p">}</span><span class="o">!!</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="distribution-summary">Distribution Summary</h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span>
<span class="kd">class</span> <span class="nc">OrderAnalytics</span><span class="p">(</span><span class="k">private</span> <span class="kd">val</span> <span class="py">meterRegistry</span><span class="p">:</span> <span class="nc">MeterRegistry</span><span class="p">)</span> <span class="p">{</span>

    <span class="k">private</span> <span class="kd">val</span> <span class="py">orderAmountSummary</span> <span class="p">=</span> <span class="nc">DistributionSummary</span><span class="p">.</span><span class="nf">builder</span><span class="p">(</span><span class="s">"order.amount"</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">description</span><span class="p">(</span><span class="s">"Distribution of order amounts"</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">baseUnit</span><span class="p">(</span><span class="s">"KRW"</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">publishPercentiles</span><span class="p">(</span><span class="mf">0.5</span><span class="p">,</span> <span class="mf">0.75</span><span class="p">,</span> <span class="mf">0.95</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">register</span><span class="p">(</span><span class="n">meterRegistry</span><span class="p">)</span>

    <span class="k">fun</span> <span class="nf">recordOrderAmount</span><span class="p">(</span><span class="n">amount</span><span class="p">:</span> <span class="nc">BigDecimal</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">orderAmountSummary</span><span class="p">.</span><span class="nf">record</span><span class="p">(</span><span class="n">amount</span><span class="p">.</span><span class="nf">toDouble</span><span class="p">())</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="prometheus-설정">Prometheus 설정</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># prometheus.yml</span>
<span class="na">global</span><span class="pi">:</span>
  <span class="na">scrape_interval</span><span class="pi">:</span> <span class="s">15s</span>

<span class="na">scrape_configs</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">job_name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">spring-boot-apps'</span>
    <span class="na">metrics_path</span><span class="pi">:</span> <span class="s1">'</span><span class="s">/actuator/prometheus'</span>
    <span class="na">static_configs</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">targets</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="s1">'</span><span class="s">order-service:8080'</span>
        <span class="pi">-</span> <span class="s1">'</span><span class="s">payment-service:8081'</span>
        <span class="pi">-</span> <span class="s1">'</span><span class="s">inventory-service:8082'</span>

  <span class="pi">-</span> <span class="na">job_name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">prometheus'</span>
    <span class="na">static_configs</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">targets</span><span class="pi">:</span> <span class="pi">[</span><span class="s1">'</span><span class="s">localhost:9090'</span><span class="pi">]</span>
</code></pre></div></div>

<h2 id="grafana-대시보드">Grafana 대시보드</h2>

<h3 id="red-method-대시보드">RED Method 대시보드</h3>

<p><strong>Rate, Errors, Duration</strong> - 서비스 관점:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Request Rate
sum(rate(http_server_requests_seconds_count{application="order-service"}[5m]))

# Error Rate
sum(rate(http_server_requests_seconds_count{application="order-service",status=~"5.."}[5m]))
/
sum(rate(http_server_requests_seconds_count{application="order-service"}[5m]))

# Duration (P99)
histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket{application="order-service"}[5m])) by (le))
</code></pre></div></div>

<h3 id="use-method-대시보드">USE Method 대시보드</h3>

<p><strong>Utilization, Saturation, Errors</strong> - 리소스 관점:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># CPU Utilization
system_cpu_usage{application="order-service"}

# Memory Utilization
jvm_memory_used_bytes{application="order-service",area="heap"}
/
jvm_memory_max_bytes{application="order-service",area="heap"}

# Thread Pool Saturation
hikaricp_connections_pending{application="order-service"}
</code></pre></div></div>

<h2 id="slislo-정의">SLI/SLO 정의</h2>

<h3 id="service-level-indicators">Service Level Indicators</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># SLI 정의</span>
<span class="na">slis</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">availability</span>
    <span class="na">query</span><span class="pi">:</span> <span class="pi">|</span>
      <span class="s">sum(rate(http_server_requests_seconds_count{status!~"5.."}[5m]))</span>
      <span class="s">/</span>
      <span class="s">sum(rate(http_server_requests_seconds_count[5m]))</span>

  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">latency_p99</span>
    <span class="na">query</span><span class="pi">:</span> <span class="pi">|</span>
      <span class="s">histogram_quantile(0.99,</span>
        <span class="s">sum(rate(http_server_requests_seconds_bucket[5m])) by (le)</span>
      <span class="s">)</span>

  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">error_rate</span>
    <span class="na">query</span><span class="pi">:</span> <span class="pi">|</span>
      <span class="s">sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m]))</span>
      <span class="s">/</span>
      <span class="s">sum(rate(http_server_requests_seconds_count[5m]))</span>
</code></pre></div></div>

<h3 id="service-level-objectives">Service Level Objectives</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">slos</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">availability</span>
    <span class="na">target</span><span class="pi">:</span> <span class="s">99.9%</span>
    <span class="na">window</span><span class="pi">:</span> <span class="s">30d</span>

  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">latency_p99</span>
    <span class="na">target</span><span class="pi">:</span> <span class="s">500ms</span>
    <span class="na">window</span><span class="pi">:</span> <span class="s">30d</span>

  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">error_rate</span>
    <span class="na">target</span><span class="pi">:</span> <span class="s">0.1%</span>
    <span class="na">window</span><span class="pi">:</span> <span class="s">30d</span>
</code></pre></div></div>

<h2 id="알림-설정">알림 설정</h2>

<h3 id="alertmanager-규칙">Alertmanager 규칙</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># alert-rules.yml</span>
<span class="na">groups</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">order-service-alerts</span>
    <span class="na">rules</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">alert</span><span class="pi">:</span> <span class="s">HighErrorRate</span>
        <span class="na">expr</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">sum(rate(http_server_requests_seconds_count{application="order-service",status=~"5.."}[5m]))</span>
          <span class="s">/</span>
          <span class="s">sum(rate(http_server_requests_seconds_count{application="order-service"}[5m]))</span>
          <span class="s">&gt; 0.01</span>
        <span class="na">for</span><span class="pi">:</span> <span class="s">5m</span>
        <span class="na">labels</span><span class="pi">:</span>
          <span class="na">severity</span><span class="pi">:</span> <span class="s">critical</span>
        <span class="na">annotations</span><span class="pi">:</span>
          <span class="na">summary</span><span class="pi">:</span> <span class="s2">"</span><span class="s">High</span><span class="nv"> </span><span class="s">error</span><span class="nv"> </span><span class="s">rate</span><span class="nv"> </span><span class="s">detected"</span>
          <span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Error</span><span class="nv"> </span><span class="s">rate</span><span class="nv"> </span><span class="s">is</span><span class="nv"> </span><span class="s">"</span>

      <span class="pi">-</span> <span class="na">alert</span><span class="pi">:</span> <span class="s">HighLatency</span>
        <span class="na">expr</span><span class="pi">:</span> <span class="pi">|</span>
          <span class="s">histogram_quantile(0.99,</span>
            <span class="s">sum(rate(http_server_requests_seconds_bucket{application="order-service"}[5m])) by (le)</span>
          <span class="s">) &gt; 1</span>
        <span class="na">for</span><span class="pi">:</span> <span class="s">5m</span>
        <span class="na">labels</span><span class="pi">:</span>
          <span class="na">severity</span><span class="pi">:</span> <span class="s">warning</span>
        <span class="na">annotations</span><span class="pi">:</span>
          <span class="na">summary</span><span class="pi">:</span> <span class="s2">"</span><span class="s">High</span><span class="nv"> </span><span class="s">latency</span><span class="nv"> </span><span class="s">detected"</span>
          <span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">P99</span><span class="nv"> </span><span class="s">latency</span><span class="nv"> </span><span class="s">is</span><span class="nv"> </span><span class="s">s"</span>

      <span class="pi">-</span> <span class="na">alert</span><span class="pi">:</span> <span class="s">PodDown</span>
        <span class="na">expr</span><span class="pi">:</span> <span class="s">up{job="spring-boot-apps"} == </span><span class="m">0</span>
        <span class="na">for</span><span class="pi">:</span> <span class="s">1m</span>
        <span class="na">labels</span><span class="pi">:</span>
          <span class="na">severity</span><span class="pi">:</span> <span class="s">critical</span>
        <span class="na">annotations</span><span class="pi">:</span>
          <span class="na">summary</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Service</span><span class="nv"> </span><span class="s">is</span><span class="nv"> </span><span class="s">down"</span>
</code></pre></div></div>

<h3 id="slack-알림-설정">Slack 알림 설정</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># alertmanager.yml</span>
<span class="na">route</span><span class="pi">:</span>
  <span class="na">receiver</span><span class="pi">:</span> <span class="s1">'</span><span class="s">slack-notifications'</span>
  <span class="na">routes</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">match</span><span class="pi">:</span>
        <span class="na">severity</span><span class="pi">:</span> <span class="s">critical</span>
      <span class="na">receiver</span><span class="pi">:</span> <span class="s1">'</span><span class="s">slack-critical'</span>
    <span class="pi">-</span> <span class="na">match</span><span class="pi">:</span>
        <span class="na">severity</span><span class="pi">:</span> <span class="s">warning</span>
      <span class="na">receiver</span><span class="pi">:</span> <span class="s1">'</span><span class="s">slack-warnings'</span>

<span class="na">receivers</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">slack-critical'</span>
    <span class="na">slack_configs</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">channel</span><span class="pi">:</span> <span class="s1">'</span><span class="s">#alerts-critical'</span>
        <span class="na">send_resolved</span><span class="pi">:</span> <span class="kc">true</span>
        <span class="na">title</span><span class="pi">:</span> <span class="s1">'</span><span class="s">:</span><span class="nv"> </span><span class="s">'</span>
        <span class="na">text</span><span class="pi">:</span> <span class="s1">'</span><span class="s">'</span>

  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s1">'</span><span class="s">slack-warnings'</span>
    <span class="na">slack_configs</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">channel</span><span class="pi">:</span> <span class="s1">'</span><span class="s">#alerts-warnings'</span>
        <span class="na">send_resolved</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre></div></div>

<h2 id="docker-compose-전체-설정">Docker Compose 전체 설정</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">3.8'</span>
<span class="na">services</span><span class="pi">:</span>
  <span class="na">prometheus</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">prom/prometheus:v2.48.0</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">9090:9090"</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">./prometheus.yml:/etc/prometheus/prometheus.yml</span>
      <span class="pi">-</span> <span class="s">./alert-rules.yml:/etc/prometheus/alert-rules.yml</span>

  <span class="na">grafana</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">grafana/grafana:10.2.0</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">3000:3000"</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">GF_SECURITY_ADMIN_PASSWORD=admin</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">./grafana/dashboards:/etc/grafana/provisioning/dashboards</span>
      <span class="pi">-</span> <span class="s">./grafana/datasources:/etc/grafana/provisioning/datasources</span>

  <span class="na">alertmanager</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">prom/alertmanager:v0.26.0</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">9093:9093"</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">./alertmanager.yml:/etc/alertmanager/alertmanager.yml</span>
</code></pre></div></div>

<h2 id="정리">정리</h2>

<p>메트릭과 알림의 핵심:</p>

<table>
  <thead>
    <tr>
      <th>항목</th>
      <th>설명</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Micrometer</td>
      <td>Spring Boot 메트릭 추상화</td>
    </tr>
    <tr>
      <td>RED Method</td>
      <td>Rate, Errors, Duration - 서비스 관점</td>
    </tr>
    <tr>
      <td>USE Method</td>
      <td>Utilization, Saturation, Errors - 리소스 관점</td>
    </tr>
    <tr>
      <td>SLI/SLO</td>
      <td>서비스 품질 목표 정의</td>
    </tr>
    <tr>
      <td>알림</td>
      <td>임계값 기반 자동 알림</td>
    </tr>
  </tbody>
</table>

<p>다음 글에서는 Observability 데이터를 활용한 프로덕션 이슈 디버깅을 다루겠습니다.</p>]]></content><author><name>울이</name></author><category term="Backend Engineering" /><category term="Spring" /><category term="Java" /><category term="SpringBoot" /><category term="Prometheus" /><category term="Grafana" /><category term="Metrics" /><category term="Alerting" /><category term="Micrometer" /><summary type="html"><![CDATA[Prometheus와 Grafana를 활용하여 Spring Boot 애플리케이션의 메트릭을 수집하고 시각화하는 방법을 다룹니다. 커스텀 메트릭, SLI/SLO 대시보드, 알림 설계까지 실무 가이드를 제공합니다.]]></summary></entry><entry><title type="html">Andrej Karpathy가 말하는 Claude 코딩 경험: 에이전트 코딩이 대세가 된 이유</title><link href="https://www.wool-dev.com/ai/andrej-karpathy-on-claude-coding-why-agent-based-coding-is-n/" rel="alternate" type="text/html" title="Andrej Karpathy가 말하는 Claude 코딩 경험: 에이전트 코딩이 대세가 된 이유" /><published>2026-01-28T00:00:00+09:00</published><updated>2026-01-28T00:00:00+09:00</updated><id>https://www.wool-dev.com/ai/andrej-karpathy-on-claude-coding-why-agent-based-coding-is-n</id><content type="html" xml:base="https://www.wool-dev.com/ai/andrej-karpathy-on-claude-coding-why-agent-based-coding-is-n/"><![CDATA[<h1 id="andrej-karpathy의-claude-코딩-경험이-말해주는-것-에이전트-코딩-시대의-도래">Andrej Karpathy의 Claude 코딩 경험이 말해주는 것: 에이전트 코딩 시대의 도래</h1>

<p>Andrej Karpathy가 코딩 워크플로우에서 에이전트 비율을 20%에서 80%로 뒤집었다. 단 한 달 만에. Tesla AI 디렉터 출신이자 OpenAI 공동창업자인 그가 이런 급격한 전환을 했다는 건, 단순한 개인 취향 변화가 아니다. 개발 패러다임 자체가 바뀌고 있다는 신호다.</p>

<h2 id="에이전트-코딩이란-무엇인가">에이전트 코딩이란 무엇인가</h2>

<p>전통적인 AI 코딩 도구(Copilot, TabNine 등)는 기본적으로 “자동완성”이다. 당신이 타이핑하면, AI가 다음 줄을 예측한다. 수동적이고, 당신이 방향을 정해야 한다.</p>

<p>에이전트 코딩은 다르다. 당신이 목표를 설명하면, AI가 스스로 계획을 세우고, 코드를 작성하고, 테스트하고, 오류를 수정한다. Claude Code나 Cursor의 에이전트 모드가 대표적이다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 자동완성 방식: 당신이 한 줄씩 쓰고, AI가 제안
</span><span class="k">def</span> <span class="nf">calculate_tax</span><span class="p">(</span><span class="n">income</span><span class="p">):</span>
    <span class="c1"># AI: "return income * 0.3" 제안
</span>    
<span class="c1"># 에이전트 방식: 당신이 의도를 설명
# "한국 소득세법에 맞는 누진세 계산 함수를 만들어줘.
#  테스트 케이스도 포함해서."
</span>
<span class="c1"># AI가 알아서 생성:
</span><span class="k">def</span> <span class="nf">calculate_korean_income_tax</span><span class="p">(</span><span class="n">income</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
    <span class="sh">"""</span><span class="s">
    2024년 기준 한국 소득세 계산
    </span><span class="sh">"""</span>
    <span class="n">brackets</span> <span class="o">=</span> <span class="p">[</span>
        <span class="p">(</span><span class="mi">12_000_000</span><span class="p">,</span> <span class="mf">0.06</span><span class="p">),</span>
        <span class="p">(</span><span class="mi">46_000_000</span><span class="p">,</span> <span class="mf">0.15</span><span class="p">),</span>
        <span class="p">(</span><span class="mi">88_000_000</span><span class="p">,</span> <span class="mf">0.24</span><span class="p">),</span>
        <span class="p">(</span><span class="mi">150_000_000</span><span class="p">,</span> <span class="mf">0.35</span><span class="p">),</span>
        <span class="p">(</span><span class="mi">300_000_000</span><span class="p">,</span> <span class="mf">0.38</span><span class="p">),</span>
        <span class="p">(</span><span class="mi">500_000_000</span><span class="p">,</span> <span class="mf">0.40</span><span class="p">),</span>
        <span class="p">(</span><span class="mi">1_000_000_000</span><span class="p">,</span> <span class="mf">0.42</span><span class="p">),</span>
        <span class="p">(</span><span class="nf">float</span><span class="p">(</span><span class="sh">'</span><span class="s">inf</span><span class="sh">'</span><span class="p">),</span> <span class="mf">0.45</span><span class="p">),</span>
    <span class="p">]</span>
    <span class="c1"># ... 구현 + 테스트 코드까지
</span></code></pre></div></div>

<p>핵심 차이는 <strong>자율성</strong>이다. 에이전트는 컨텍스트를 유지하면서 여러 파일을 오가고, 빌드 에러를 읽고, 스스로 수정한다.</p>

<h2 id="왜-80까지-올라갔나-실용적-분석">왜 80%까지 올라갔나: 실용적 분석</h2>

<p>Karpathy가 언급한 핵심 변화는 “컨텍스트 윈도우 확장”과 “도구 사용 능력”이다.</p>

<h3 id="컨텍스트의-힘">컨텍스트의 힘</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 예전: 한 파일 내 100줄 정도만 맥락 파악</span>
<span class="c"># 지금: 프로젝트 전체 구조 + 의존성 + 테스트 파일까지 파악</span>

<span class="c"># Claude Code 예시</span>
claude <span class="s2">"src/ 디렉토리의 API 엔드포인트 중 
       인증 누락된 부분 찾아서 미들웨어 추가해줘"</span>
</code></pre></div></div>

<p>이게 가능해진 이유는 단순하다. Claude 3.5 Sonnet 이후로 200K 토큰 컨텍스트가 실용화됐고, 도구 호출(tool use)이 안정화됐다. 파일을 읽고, 명령어를 실행하고, 결과를 해석하는 루프가 신뢰할 수 있는 수준이 됐다.</p>

<h3 id="에이전트가-잘하는-것과-못하는-것">에이전트가 잘하는 것과 못하는 것</h3>

<p><strong>잘하는 작업:</strong></p>
<ul>
  <li>보일러플레이트 코드 생성</li>
  <li>기존 패턴을 따르는 새 기능 추가</li>
  <li>테스트 코드 작성</li>
  <li>리팩토링 (특히 이름 변경, 파일 분리)</li>
  <li>문서화</li>
</ul>

<p><strong>아직 못하는 작업:</strong></p>
<ul>
  <li>아키텍처 결정 (이건 당신이 해야 함)</li>
  <li>성능 최적화의 미묘한 트레이드오프</li>
  <li>도메인 특화 비즈니스 로직의 정확한 구현</li>
  <li>레거시 코드의 숨겨진 의도 파악</li>
</ul>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 에이전트에게 맡기면 좋은 예
# "User 모델에 email_verified 필드 추가하고, 
#  마이그레이션이랑 관련 테스트 업데이트해줘"
</span>
<span class="c1"># 직접 해야 하는 예
# "우리 결제 시스템이 동시성 문제가 있는 것 같은데,
#  어디서 race condition이 생길 수 있는지 분석해줘"
# → 이건 에이전트 결과를 참고만 하고, 직접 검증해야
</span></code></pre></div></div>

<h2 id="실전-워크플로우-어떻게-80를-달성하나">실전 워크플로우: 어떻게 80%를 달성하나</h2>

<p>솔직히 말하면, 아무 작업이나 에이전트에게 던지면 80%가 되지 않는다. 전략이 필요하다.</p>

<h3 id="1-작업을-분해하라">1. 작업을 분해하라</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 나쁜 예</span>
claude <span class="s2">"전체 인증 시스템을 OAuth2로 바꿔줘"</span>

<span class="c"># 좋은 예</span>
claude <span class="s2">"1. 현재 auth/ 디렉토리 구조 분석해줘"</span>
<span class="c"># 결과 확인 후</span>
claude <span class="s2">"2. OAuth2 토큰 검증 미들웨어 만들어줘"</span>
<span class="c"># 결과 확인 후</span>
claude <span class="s2">"3. 기존 세션 방식과 병행 가능하게 해줘"</span>
</code></pre></div></div>

<h3 id="2-맥락을-제공하라">2. 맥락을 제공하라</h3>

<p>에이전트는 당신의 코드베이스를 처음 보는 것과 같다. 관련 파일을 명시적으로 알려주면 정확도가 급상승한다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>claude <span class="s2">"src/api/users.py와 tests/test_users.py를 참고해서
       동일한 패턴으로 src/api/orders.py 만들어줘"</span>
</code></pre></div></div>

<h3 id="3-결과를-검증하라">3. 결과를 검증하라</h3>

<p>에이전트가 80%를 처리한다는 건, 당신이 20%의 검토/수정을 한다는 뜻이다. 이 20%가 핵심이다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 생성된 코드 리뷰 체크리스트</span>
- <span class="o">[</span> <span class="o">]</span> 의도한 대로 동작하는가? <span class="o">(</span>직접 실행<span class="o">)</span>
- <span class="o">[</span> <span class="o">]</span> 엣지 케이스 처리는? 
- <span class="o">[</span> <span class="o">]</span> 보안 취약점은 없는가?
- <span class="o">[</span> <span class="o">]</span> 기존 코드 스타일과 일관성 있는가?
</code></pre></div></div>

<h2 id="주의해야-할-함정들">주의해야 할 함정들</h2>

<h3 id="과신의-문제">과신의 문제</h3>

<p>에이전트가 자신감 있게 틀린 코드를 생성할 때가 있다. 특히 최신 라이브러리 버전이나 마이너한 API는 환각(hallucination)이 발생한다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># AI가 생성한 코드 (실제로는 존재하지 않는 API)
</span><span class="kn">import</span> <span class="n">some_library</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">some_library</span><span class="p">.</span><span class="nf">magic_function</span><span class="p">()</span>  <span class="c1"># 이런 함수 없음
</span>
<span class="c1"># 반드시 확인
</span><span class="n">pip</span> <span class="n">show</span> <span class="n">some_library</span>  <span class="c1"># 버전 확인
# 공식 문서에서 API 존재 여부 검증
</span></code></pre></div></div>

<h3 id="컨텍스트-오염">컨텍스트 오염</h3>

<p>긴 대화에서 에이전트가 이전 지시와 현재 지시를 혼동하기도 한다. 새로운 작업은 새 세션에서 시작하는 게 안전하다.</p>

<h3 id="의존성-문제">의존성 문제</h3>

<p>에이전트가 편의상 새 패키지를 추가하는 경우가 있다. <code class="language-plaintext highlighter-rouge">requirements.txt</code>나 <code class="language-plaintext highlighter-rouge">package.json</code> 변경사항은 꼭 확인하라.</p>

<h2 id="결론-패러다임-전환을-받아들여라">결론: 패러다임 전환을 받아들여라</h2>

<p>Karpathy의 80% 에이전트 코딩은 과장이 아니다. 실제로 반복적이고 패턴화된 작업의 대부분을 AI가 처리할 수 있는 시점에 도달했다.</p>

<p>하지만 이건 개발자가 필요 없어진다는 뜻이 아니다. 오히려 반대다. <strong>아키텍처 설계, 요구사항 분석, 코드 리뷰, 장애 대응</strong>—이런 고차원 작업에 더 많은 시간을 쓸 수 있게 됐다는 뜻이다.</p>

<p>내 개인적인 견해: 에이전트 코딩을 거부하는 건 2010년대에 IDE 자동완성을 거부하는 것과 같다. 도구를 마스터하는 개발자와 거부하는 개발자의 생산성 격차는 앞으로 더 벌어질 것이다.</p>

<p>다만, 한 가지 명심하라. 에이전트가 작성한 코드의 책임은 여전히 당신에게 있다. “AI가 그렇게 했어요”는 프로덕션 장애 보고서에서 받아들여지지 않는다.</p>]]></content><author><name>울이</name></author><category term="AI" /><category term="claude" /><category term="ai-coding" /><category term="andrej-karpathy" /><category term="agent-coding" /><category term="llm" /><category term="developer-tools" /><category term="ai-assistant" /><category term="coding-workflow" /><summary type="html"><![CDATA[Andrej Karpathy shares his Claude coding experience, revealing a dramatic shift from 20% to 80% agent-based coding in just one month, and what this means for the future of software development...]]></summary></entry><entry><title type="html">Cloud-Native Observability Stack Part 3 - Structured Logging with Correlation IDs</title><link href="https://www.wool-dev.com/backend%20engineering/spring/observability-part3-structured-logging/" rel="alternate" type="text/html" title="Cloud-Native Observability Stack Part 3 - Structured Logging with Correlation IDs" /><published>2026-01-27T00:00:00+09:00</published><updated>2026-01-27T00:00:00+09:00</updated><id>https://www.wool-dev.com/backend%20engineering/spring/observability-part3-structured-logging</id><content type="html" xml:base="https://www.wool-dev.com/backend%20engineering/spring/observability-part3-structured-logging/"><![CDATA[<h2 id="시리즈-소개">시리즈 소개</h2>

<ol>
  <li>Part 1: OpenTelemetry Instrumentation</li>
  <li>Part 2: 마이크로서비스 분산 추적</li>
  <li><strong>Part 3: 구조화된 로깅과 Correlation ID</strong> (현재 글)</li>
  <li>Part 4: Prometheus/Grafana로 메트릭과 알림</li>
  <li>Part 5: Observability 데이터로 프로덕션 이슈 디버깅</li>
</ol>

<h2 id="로그의-문제점">로그의 문제점</h2>

<p>기존 로그 방식의 한계:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2026-01-27 10:30:15 INFO OrderService - Processing order
2026-01-27 10:30:15 ERROR PaymentService - Payment failed
2026-01-27 10:30:15 INFO OrderService - Order completed
</code></pre></div></div>

<ul>
  <li>어떤 요청의 로그인지 알 수 없음</li>
  <li>서비스 간 로그 연관성 부재</li>
  <li>검색과 필터링이 어려움</li>
</ul>

<h2 id="구조화된-로깅-structured-logging">구조화된 로깅 (Structured Logging)</h2>

<h3 id="json-로그-포맷">JSON 로그 포맷</h3>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"timestamp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-01-27T10:30:15.123Z"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"level"</span><span class="p">:</span><span class="w"> </span><span class="s2">"INFO"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"service"</span><span class="p">:</span><span class="w"> </span><span class="s2">"order-service"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"traceId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"abc123"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"spanId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"def456"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"correlationId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"req-789"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Processing order"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"order.id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"order-123"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"customer.id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"cust-456"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"thread"</span><span class="p">:</span><span class="w"> </span><span class="s2">"http-nio-8080-exec-1"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h3 id="logback-json-설정">Logback JSON 설정</h3>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- logback-spring.xml --&gt;</span>
<span class="nt">&lt;configuration&gt;</span>
    <span class="nt">&lt;appender</span> <span class="na">name=</span><span class="s">"JSON"</span> <span class="na">class=</span><span class="s">"ch.qos.logback.core.ConsoleAppender"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;encoder</span> <span class="na">class=</span><span class="s">"net.logstash.logback.encoder.LogstashEncoder"</span><span class="nt">&gt;</span>
            <span class="nt">&lt;includeMdcKeyName&gt;</span>traceId<span class="nt">&lt;/includeMdcKeyName&gt;</span>
            <span class="nt">&lt;includeMdcKeyName&gt;</span>spanId<span class="nt">&lt;/includeMdcKeyName&gt;</span>
            <span class="nt">&lt;includeMdcKeyName&gt;</span>correlationId<span class="nt">&lt;/includeMdcKeyName&gt;</span>
            <span class="nt">&lt;customFields&gt;</span>{"service":"order-service"}<span class="nt">&lt;/customFields&gt;</span>
        <span class="nt">&lt;/encoder&gt;</span>
    <span class="nt">&lt;/appender&gt;</span>

    <span class="nt">&lt;root</span> <span class="na">level=</span><span class="s">"INFO"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;appender-ref</span> <span class="na">ref=</span><span class="s">"JSON"</span><span class="nt">/&gt;</span>
    <span class="nt">&lt;/root&gt;</span>
<span class="nt">&lt;/configuration&gt;</span>
</code></pre></div></div>

<h2 id="mdc-mapped-diagnostic-context">MDC (Mapped Diagnostic Context)</h2>

<h3 id="mdc-필터-설정">MDC 필터 설정</h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span>
<span class="nd">@Order</span><span class="p">(</span><span class="nc">Ordered</span><span class="p">.</span><span class="nc">HIGHEST_PRECEDENCE</span><span class="p">)</span>
<span class="kd">class</span> <span class="nc">CorrelationIdFilter</span> <span class="p">:</span> <span class="nc">OncePerRequestFilter</span><span class="p">()</span> <span class="p">{</span>

    <span class="k">override</span> <span class="k">fun</span> <span class="nf">doFilterInternal</span><span class="p">(</span>
        <span class="n">request</span><span class="p">:</span> <span class="nc">HttpServletRequest</span><span class="p">,</span>
        <span class="n">response</span><span class="p">:</span> <span class="nc">HttpServletResponse</span><span class="p">,</span>
        <span class="n">filterChain</span><span class="p">:</span> <span class="nc">FilterChain</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="k">try</span> <span class="p">{</span>
            <span class="kd">val</span> <span class="py">correlationId</span> <span class="p">=</span> <span class="n">request</span><span class="p">.</span><span class="nf">getHeader</span><span class="p">(</span><span class="s">"X-Correlation-ID"</span><span class="p">)</span>
                <span class="o">?:</span> <span class="nc">UUID</span><span class="p">.</span><span class="nf">randomUUID</span><span class="p">().</span><span class="nf">toString</span><span class="p">()</span>

            <span class="nc">MDC</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="s">"correlationId"</span><span class="p">,</span> <span class="n">correlationId</span><span class="p">)</span>
            <span class="nc">MDC</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="s">"requestPath"</span><span class="p">,</span> <span class="n">request</span><span class="p">.</span><span class="n">requestURI</span><span class="p">)</span>
            <span class="nc">MDC</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="s">"requestMethod"</span><span class="p">,</span> <span class="n">request</span><span class="p">.</span><span class="n">method</span><span class="p">)</span>

            <span class="n">response</span><span class="p">.</span><span class="nf">setHeader</span><span class="p">(</span><span class="s">"X-Correlation-ID"</span><span class="p">,</span> <span class="n">correlationId</span><span class="p">)</span>

            <span class="n">filterChain</span><span class="p">.</span><span class="nf">doFilter</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">response</span><span class="p">)</span>
        <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
            <span class="nc">MDC</span><span class="p">.</span><span class="nf">clear</span><span class="p">()</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="opentelemetry-trace-id-연동">OpenTelemetry Trace ID 연동</h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span>
<span class="kd">class</span> <span class="nc">TraceIdMdcFilter</span> <span class="p">:</span> <span class="nc">OncePerRequestFilter</span><span class="p">()</span> <span class="p">{</span>

    <span class="k">override</span> <span class="k">fun</span> <span class="nf">doFilterInternal</span><span class="p">(</span>
        <span class="n">request</span><span class="p">:</span> <span class="nc">HttpServletRequest</span><span class="p">,</span>
        <span class="n">response</span><span class="p">:</span> <span class="nc">HttpServletResponse</span><span class="p">,</span>
        <span class="n">filterChain</span><span class="p">:</span> <span class="nc">FilterChain</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="k">try</span> <span class="p">{</span>
            <span class="kd">val</span> <span class="py">span</span> <span class="p">=</span> <span class="nc">Span</span><span class="p">.</span><span class="nf">current</span><span class="p">()</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">span</span><span class="p">.</span><span class="n">spanContext</span><span class="p">.</span><span class="n">isValid</span><span class="p">)</span> <span class="p">{</span>
                <span class="nc">MDC</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="s">"traceId"</span><span class="p">,</span> <span class="n">span</span><span class="p">.</span><span class="n">spanContext</span><span class="p">.</span><span class="n">traceId</span><span class="p">)</span>
                <span class="nc">MDC</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="s">"spanId"</span><span class="p">,</span> <span class="n">span</span><span class="p">.</span><span class="n">spanContext</span><span class="p">.</span><span class="n">spanId</span><span class="p">)</span>
            <span class="p">}</span>
            <span class="n">filterChain</span><span class="p">.</span><span class="nf">doFilter</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">response</span><span class="p">)</span>
        <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
            <span class="nc">MDC</span><span class="p">.</span><span class="nf">remove</span><span class="p">(</span><span class="s">"traceId"</span><span class="p">)</span>
            <span class="nc">MDC</span><span class="p">.</span><span class="nf">remove</span><span class="p">(</span><span class="s">"spanId"</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="로깅-모범-사례">로깅 모범 사례</h2>

<h3 id="의미-있는-로그-메시지">의미 있는 로그 메시지</h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 나쁜 예</span>
<span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="s">"Processing"</span><span class="p">)</span>
<span class="n">logger</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="s">"Error occurred"</span><span class="p">)</span>

<span class="c1">// 좋은 예</span>
<span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="s">"Starting order processing"</span><span class="p">,</span> <span class="nf">kv</span><span class="p">(</span><span class="s">"orderId"</span><span class="p">,</span> <span class="n">orderId</span><span class="p">),</span> <span class="nf">kv</span><span class="p">(</span><span class="s">"customerId"</span><span class="p">,</span> <span class="n">customerId</span><span class="p">))</span>
<span class="n">logger</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="s">"Payment processing failed"</span><span class="p">,</span> <span class="nf">kv</span><span class="p">(</span><span class="s">"orderId"</span><span class="p">,</span> <span class="n">orderId</span><span class="p">),</span> <span class="nf">kv</span><span class="p">(</span><span class="s">"errorCode"</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">errorCode</span><span class="p">),</span> <span class="n">e</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="구조화된-로깅-유틸리티">구조화된 로깅 유틸리티</h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nn">net.logstash.logback.argument.StructuredArguments.kv</span>

<span class="nd">@Service</span>
<span class="kd">class</span> <span class="nc">OrderService</span><span class="p">(</span><span class="k">private</span> <span class="kd">val</span> <span class="py">logger</span><span class="p">:</span> <span class="nc">Logger</span> <span class="p">=</span> <span class="nc">LoggerFactory</span><span class="p">.</span><span class="nf">getLogger</span><span class="p">(</span><span class="nc">OrderService</span><span class="o">::</span><span class="k">class</span><span class="p">.</span><span class="n">java</span><span class="p">))</span> <span class="p">{</span>

    <span class="k">fun</span> <span class="nf">processOrder</span><span class="p">(</span><span class="n">order</span><span class="p">:</span> <span class="nc">Order</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="s">"Order processing started"</span><span class="p">,</span>
            <span class="nf">kv</span><span class="p">(</span><span class="s">"orderId"</span><span class="p">,</span> <span class="n">order</span><span class="p">.</span><span class="n">id</span><span class="p">),</span>
            <span class="nf">kv</span><span class="p">(</span><span class="s">"customerId"</span><span class="p">,</span> <span class="n">order</span><span class="p">.</span><span class="n">customerId</span><span class="p">),</span>
            <span class="nf">kv</span><span class="p">(</span><span class="s">"itemCount"</span><span class="p">,</span> <span class="n">order</span><span class="p">.</span><span class="n">items</span><span class="p">.</span><span class="n">size</span><span class="p">),</span>
            <span class="nf">kv</span><span class="p">(</span><span class="s">"totalAmount"</span><span class="p">,</span> <span class="n">order</span><span class="p">.</span><span class="n">totalAmount</span><span class="p">)</span>
        <span class="p">)</span>

        <span class="k">try</span> <span class="p">{</span>
            <span class="c1">// 처리 로직</span>
            <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="s">"Order processing completed"</span><span class="p">,</span>
                <span class="nf">kv</span><span class="p">(</span><span class="s">"orderId"</span><span class="p">,</span> <span class="n">order</span><span class="p">.</span><span class="n">id</span><span class="p">),</span>
                <span class="nf">kv</span><span class="p">(</span><span class="s">"processingTimeMs"</span><span class="p">,</span> <span class="n">processingTime</span><span class="p">)</span>
            <span class="p">)</span>
        <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">e</span><span class="p">:</span> <span class="nc">Exception</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">logger</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="s">"Order processing failed"</span><span class="p">,</span>
                <span class="nf">kv</span><span class="p">(</span><span class="s">"orderId"</span><span class="p">,</span> <span class="n">order</span><span class="p">.</span><span class="n">id</span><span class="p">),</span>
                <span class="nf">kv</span><span class="p">(</span><span class="s">"errorType"</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">javaClass</span><span class="p">.</span><span class="n">simpleName</span><span class="p">),</span>
                <span class="nf">kv</span><span class="p">(</span><span class="s">"errorMessage"</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">message</span><span class="p">),</span>
                <span class="n">e</span>
            <span class="p">)</span>
            <span class="k">throw</span> <span class="n">e</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="kafka-메시지에서-correlation-id-전파">Kafka 메시지에서 Correlation ID 전파</h2>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span>
<span class="kd">class</span> <span class="nc">KafkaCorrelationInterceptor</span> <span class="p">:</span> <span class="nc">ProducerInterceptor</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">,</span> <span class="nc">String</span><span class="p">&gt;</span> <span class="p">{</span>

    <span class="k">override</span> <span class="k">fun</span> <span class="nf">onSend</span><span class="p">(</span><span class="n">record</span><span class="p">:</span> <span class="nc">ProducerRecord</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">,</span> <span class="nc">String</span><span class="p">&gt;):</span> <span class="nc">ProducerRecord</span><span class="p">&lt;</span><span class="nc">String</span><span class="p">,</span> <span class="nc">String</span><span class="p">&gt;</span> <span class="p">{</span>
        <span class="nc">MDC</span><span class="p">.</span><span class="k">get</span><span class="p">(</span><span class="s">"correlationId"</span><span class="p">)</span><span class="o">?.</span><span class="nf">let</span> <span class="p">{</span> <span class="n">correlationId</span> <span class="p">-&gt;</span>
            <span class="n">record</span><span class="p">.</span><span class="nf">headers</span><span class="p">().</span><span class="nf">add</span><span class="p">(</span><span class="s">"X-Correlation-ID"</span><span class="p">,</span> <span class="n">correlationId</span><span class="p">.</span><span class="nf">toByteArray</span><span class="p">())</span>
        <span class="p">}</span>
        <span class="nc">MDC</span><span class="p">.</span><span class="k">get</span><span class="p">(</span><span class="s">"traceId"</span><span class="p">)</span><span class="o">?.</span><span class="nf">let</span> <span class="p">{</span> <span class="n">traceId</span> <span class="p">-&gt;</span>
            <span class="n">record</span><span class="p">.</span><span class="nf">headers</span><span class="p">().</span><span class="nf">add</span><span class="p">(</span><span class="s">"X-Trace-ID"</span><span class="p">,</span> <span class="n">traceId</span><span class="p">.</span><span class="nf">toByteArray</span><span class="p">())</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="n">record</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="nd">@Component</span>
<span class="kd">class</span> <span class="nc">OrderEventConsumer</span> <span class="p">{</span>

    <span class="nd">@KafkaListener</span><span class="p">(</span><span class="n">topics</span> <span class="p">=</span> <span class="p">[</span><span class="s">"order-events"</span><span class="p">])</span>
    <span class="k">fun</span> <span class="nf">handleOrderEvent</span><span class="p">(</span>
        <span class="nd">@Payload</span> <span class="n">payload</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span>
        <span class="nd">@Header</span><span class="p">(</span><span class="s">"X-Correlation-ID"</span><span class="p">)</span> <span class="n">correlationId</span><span class="p">:</span> <span class="nc">String</span><span class="p">?,</span>
        <span class="nd">@Header</span><span class="p">(</span><span class="s">"X-Trace-ID"</span><span class="p">)</span> <span class="n">traceId</span><span class="p">:</span> <span class="nc">String</span><span class="p">?</span>
    <span class="p">)</span> <span class="p">{</span>
        <span class="k">try</span> <span class="p">{</span>
            <span class="n">correlationId</span><span class="o">?.</span><span class="nf">let</span> <span class="p">{</span> <span class="nc">MDC</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="s">"correlationId"</span><span class="p">,</span> <span class="n">it</span><span class="p">)</span> <span class="p">}</span>
            <span class="n">traceId</span><span class="o">?.</span><span class="nf">let</span> <span class="p">{</span> <span class="nc">MDC</span><span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="s">"traceId"</span><span class="p">,</span> <span class="n">it</span><span class="p">)</span> <span class="p">}</span>

            <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="s">"Processing order event"</span><span class="p">,</span> <span class="nf">kv</span><span class="p">(</span><span class="s">"eventType"</span><span class="p">,</span> <span class="s">"OrderCreated"</span><span class="p">))</span>
            <span class="c1">// 이벤트 처리</span>
        <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
            <span class="nc">MDC</span><span class="p">.</span><span class="nf">clear</span><span class="p">()</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="로그-레벨-가이드라인">로그 레벨 가이드라인</h2>

<table>
  <thead>
    <tr>
      <th>레벨</th>
      <th>사용 시점</th>
      <th>예시</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>ERROR</td>
      <td>즉각적인 조치 필요</td>
      <td>결제 실패, DB 연결 실패</td>
    </tr>
    <tr>
      <td>WARN</td>
      <td>잠재적 문제</td>
      <td>재시도 발생, 성능 저하</td>
    </tr>
    <tr>
      <td>INFO</td>
      <td>비즈니스 이벤트</td>
      <td>주문 생성, 사용자 로그인</td>
    </tr>
    <tr>
      <td>DEBUG</td>
      <td>개발/디버깅용</td>
      <td>메서드 진입/종료, 변수 값</td>
    </tr>
    <tr>
      <td>TRACE</td>
      <td>상세 디버깅</td>
      <td>루프 내부 값</td>
    </tr>
  </tbody>
</table>

<h2 id="로그-집계-log-aggregation">로그 집계 (Log Aggregation)</h2>

<h3 id="loki-설정-grafana-stack">Loki 설정 (Grafana Stack)</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># docker-compose.yml</span>
<span class="na">services</span><span class="pi">:</span>
  <span class="na">loki</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">grafana/loki:2.9.0</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">3100:3100"</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">./loki-config.yaml:/etc/loki/local-config.yaml</span>

  <span class="na">promtail</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">grafana/promtail:2.9.0</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">/var/log:/var/log</span>
      <span class="pi">-</span> <span class="s">./promtail-config.yaml:/etc/promtail/config.yaml</span>
</code></pre></div></div>

<h3 id="logql-쿼리-예시">LogQL 쿼리 예시</h3>

<pre><code class="language-logql"># 특정 traceId로 모든 서비스 로그 조회
{service=~".+"} |= "traceId=abc123"

# 에러 로그만 필터링
{service="order-service"} | json | level="ERROR"

# 특정 orderId 관련 로그
{service=~".+"} | json | orderId="order-123"
</code></pre>

<h2 id="정리">정리</h2>

<p>구조화된 로깅의 핵심:</p>

<table>
  <thead>
    <tr>
      <th>항목</th>
      <th>설명</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>JSON 포맷</td>
      <td>파싱과 검색이 용이</td>
    </tr>
    <tr>
      <td>Correlation ID</td>
      <td>서비스 간 요청 추적</td>
    </tr>
    <tr>
      <td>MDC</td>
      <td>스레드별 컨텍스트 관리</td>
    </tr>
    <tr>
      <td>Trace ID 연동</td>
      <td>분산 추적과 로그 통합</td>
    </tr>
  </tbody>
</table>

<p>다음 글에서는 Prometheus/Grafana를 활용한 메트릭과 알림을 다루겠습니다.</p>]]></content><author><name>울이</name></author><category term="Backend Engineering" /><category term="Spring" /><category term="Java" /><category term="SpringBoot" /><category term="Logging" /><category term="Correlation-ID" /><category term="ELK" /><category term="Loki" /><summary type="html"><![CDATA[마이크로서비스 환경에서 구조화된 로깅과 Correlation ID를 구현하는 방법을 다룹니다. JSON 로깅, MDC 패턴, 로그 집계까지 실무 적용 가이드를 제공합니다.]]></summary></entry><entry><title type="html">치지직 클립 다운로더 크롬 익스텐션 개발기 - 외부 API 없이 순수 브라우저에서 작동하는 방법</title><link href="https://www.wool-dev.com/frontend/building-a-chzzk-clip-downloader-chrome-extension-pure-bro/" rel="alternate" type="text/html" title="치지직 클립 다운로더 크롬 익스텐션 개발기 - 외부 API 없이 순수 브라우저에서 작동하는 방법" /><published>2026-01-27T00:00:00+09:00</published><updated>2026-01-27T00:00:00+09:00</updated><id>https://www.wool-dev.com/frontend/building-a-chzzk-clip-downloader-chrome-extension---pure-bro</id><content type="html" xml:base="https://www.wool-dev.com/frontend/building-a-chzzk-clip-downloader-chrome-extension-pure-bro/"><![CDATA[<h1 id="치지직-클립-다운로더-크롬-익스텐션-개발기-외부-api-없이-순수-클라이언트-사이드로-구현하기">치지직 클립 다운로더 크롬 익스텐션 개발기: 외부 API 없이 순수 클라이언트 사이드로 구현하기</h1>

<p>브라우저 익스텐션으로 영상 다운로더를 만들 때 가장 먼저 떠오르는 방법은 외부 서버를 거쳐 API를 호출하는 것이다. 하지만 이 방식은 서버 유지비용, 개인정보 유출 우려, 그리고 서비스 의존성이라는 세 가지 문제를 동시에 안고 간다. 치지직 클립 다운로더는 이 모든 것을 피하고 순수 클라이언트 사이드에서 동작하는 방식을 택했다. 외부 페이지 이동도, API 호출도 없다. 그냥 브라우저에서 다 끝난다.</p>

<h2 id="크롬-익스텐션의-구조와-권한-모델">크롬 익스텐션의 구조와 권한 모델</h2>

<p>크롬 익스텐션을 만들어본 사람이라면 <code class="language-plaintext highlighter-rouge">manifest.json</code>이 모든 것의 시작점이라는 걸 안다. Manifest V3로 넘어오면서 권한 체계가 상당히 까다로워졌는데, 영상 다운로더 같은 민감한 기능을 구현하려면 이 부분을 제대로 이해해야 한다.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"manifest_version"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"치지직 클립 다운로더"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0.0"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"permissions"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"activeTab"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"downloads"</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"host_permissions"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"https://chzzk.naver.com/*"</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"content_scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"matches"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"https://chzzk.naver.com/clips/*"</span><span class="p">],</span><span class="w">
      </span><span class="nl">"js"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"content.js"</span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"action"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"default_popup"</span><span class="p">:</span><span class="w"> </span><span class="s2">"popup.html"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>핵심은 <code class="language-plaintext highlighter-rouge">host_permissions</code>를 치지직 도메인으로 한정한 것이다. 불필요하게 넓은 권한을 요청하면 크롬 웹스토어 심사에서 거절당하기 십상이다. <code class="language-plaintext highlighter-rouge">downloads</code> 권한은 <code class="language-plaintext highlighter-rouge">chrome.downloads.download()</code> API를 사용하기 위해 필수다.</p>

<h2 id="content-script와-background-script의-역할-분리">Content Script와 Background Script의 역할 분리</h2>

<p>클라이언트 사이드에서 영상 URL을 추출하려면 현재 페이지의 DOM이나 네트워크 요청을 분석해야 한다. Content Script는 페이지의 컨텍스트에서 실행되므로 DOM 접근이 가능하지만, <code class="language-plaintext highlighter-rouge">chrome.downloads</code> 같은 특권 API는 사용할 수 없다.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// content.js - 페이지에서 영상 URL 추출</span>
<span class="kd">function</span> <span class="nf">extractVideoUrl</span><span class="p">()</span> <span class="p">{</span>
  <span class="c1">// 치지직은 HLS 스트리밍을 사용한다</span>
  <span class="c1">// video 태그의 src 또는 페이지 내 스크립트에서 m3u8 URL 추출</span>
  <span class="kd">const</span> <span class="nx">videoElement</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">querySelector</span><span class="p">(</span><span class="dl">'</span><span class="s1">video</span><span class="dl">'</span><span class="p">);</span>
  
  <span class="c1">// 직접적인 src가 없다면 페이지 스크립트 데이터 확인</span>
  <span class="kd">const</span> <span class="nx">scripts</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">querySelectorAll</span><span class="p">(</span><span class="dl">'</span><span class="s1">script</span><span class="dl">'</span><span class="p">);</span>
  <span class="kd">let</span> <span class="nx">videoUrl</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
  
  <span class="nx">scripts</span><span class="p">.</span><span class="nf">forEach</span><span class="p">(</span><span class="nx">script</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">text</span> <span class="o">=</span> <span class="nx">script</span><span class="p">.</span><span class="nx">textContent</span><span class="p">;</span>
    <span class="c1">// JSON 형태로 임베드된 영상 정보 파싱</span>
    <span class="kd">const</span> <span class="nx">match</span> <span class="o">=</span> <span class="nx">text</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="sr">/"videoUrl"</span><span class="se">\s</span><span class="sr">*:</span><span class="se">\s</span><span class="sr">*"</span><span class="se">([^</span><span class="sr">"</span><span class="se">]</span><span class="sr">+</span><span class="se">)</span><span class="sr">"/</span><span class="p">);</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">match</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">videoUrl</span> <span class="o">=</span> <span class="nx">match</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
    <span class="p">}</span>
  <span class="p">});</span>
  
  <span class="k">return</span> <span class="nx">videoUrl</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// Background로 메시지 전송</span>
<span class="nx">chrome</span><span class="p">.</span><span class="nx">runtime</span><span class="p">.</span><span class="nf">sendMessage</span><span class="p">({</span>
  <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">DOWNLOAD_VIDEO</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">url</span><span class="p">:</span> <span class="nf">extractVideoUrl</span><span class="p">()</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Background Script(Manifest V3에서는 Service Worker)에서는 실제 다운로드를 처리한다.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// background.js (Service Worker)</span>
<span class="nx">chrome</span><span class="p">.</span><span class="nx">runtime</span><span class="p">.</span><span class="nx">onMessage</span><span class="p">.</span><span class="nf">addListener</span><span class="p">((</span><span class="nx">message</span><span class="p">,</span> <span class="nx">sender</span><span class="p">,</span> <span class="nx">sendResponse</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">if </span><span class="p">(</span><span class="nx">message</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">DOWNLOAD_VIDEO</span><span class="dl">'</span> <span class="o">&amp;&amp;</span> <span class="nx">message</span><span class="p">.</span><span class="nx">url</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">chrome</span><span class="p">.</span><span class="nx">downloads</span><span class="p">.</span><span class="nf">download</span><span class="p">({</span>
      <span class="na">url</span><span class="p">:</span> <span class="nx">message</span><span class="p">.</span><span class="nx">url</span><span class="p">,</span>
      <span class="na">filename</span><span class="p">:</span> <span class="s2">`chzzk_clip_</span><span class="p">${</span><span class="nb">Date</span><span class="p">.</span><span class="nf">now</span><span class="p">()}</span><span class="s2">.mp4`</span><span class="p">,</span>
      <span class="na">saveAs</span><span class="p">:</span> <span class="kc">true</span>
    <span class="p">},</span> <span class="p">(</span><span class="nx">downloadId</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="k">if </span><span class="p">(</span><span class="nx">chrome</span><span class="p">.</span><span class="nx">runtime</span><span class="p">.</span><span class="nx">lastError</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">다운로드 실패:</span><span class="dl">'</span><span class="p">,</span> <span class="nx">chrome</span><span class="p">.</span><span class="nx">runtime</span><span class="p">.</span><span class="nx">lastError</span><span class="p">);</span>
      <span class="p">}</span>
    <span class="p">});</span>
  <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<h2 id="hls-스트리밍-처리의-현실">HLS 스트리밍 처리의 현실</h2>

<p>여기서 한 가지 불편한 진실을 얘기해야 한다. 대부분의 스트리밍 서비스는 직접 다운로드가 불가능한 HLS(HTTP Live Streaming) 형식을 사용한다. m3u8 플레이리스트 파일과 수십~수백 개의 ts 세그먼트로 쪼개져 있다.</p>

<p>순수 클라이언트 사이드에서 이걸 처리하려면 두 가지 선택지가 있다:</p>

<ol>
  <li><strong>m3u8 URL만 제공하고 외부 도구 사용 유도</strong> - 사용자 경험이 좋지 않다</li>
  <li><strong>모든 세그먼트를 fetch해서 클라이언트에서 병합</strong> - 구현은 복잡하지만 완전한 솔루션</li>
</ol>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// HLS 세그먼트 병합 (간략화된 버전)</span>
<span class="k">async</span> <span class="kd">function</span> <span class="nf">downloadAndMergeHLS</span><span class="p">(</span><span class="nx">m3u8Url</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(</span><span class="nx">m3u8Url</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">playlist</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">response</span><span class="p">.</span><span class="nf">text</span><span class="p">();</span>
  
  <span class="c1">// ts 세그먼트 URL 추출</span>
  <span class="kd">const</span> <span class="nx">segments</span> <span class="o">=</span> <span class="nx">playlist</span>
    <span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="dl">'</span><span class="se">\n</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">filter</span><span class="p">(</span><span class="nx">line</span> <span class="o">=&gt;</span> <span class="nx">line</span><span class="p">.</span><span class="nf">endsWith</span><span class="p">(</span><span class="dl">'</span><span class="s1">.ts</span><span class="dl">'</span><span class="p">))</span>
    <span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="nx">segment</span> <span class="o">=&gt;</span> <span class="k">new</span> <span class="nc">URL</span><span class="p">(</span><span class="nx">segment</span><span class="p">,</span> <span class="nx">m3u8Url</span><span class="p">).</span><span class="nx">href</span><span class="p">);</span>
  
  <span class="c1">// 모든 세그먼트 다운로드</span>
  <span class="kd">const</span> <span class="nx">chunks</span> <span class="o">=</span> <span class="k">await</span> <span class="nb">Promise</span><span class="p">.</span><span class="nf">all</span><span class="p">(</span>
    <span class="nx">segments</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="nx">url</span> <span class="o">=&gt;</span> <span class="nf">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">).</span><span class="nf">then</span><span class="p">(</span><span class="nx">r</span> <span class="o">=&gt;</span> <span class="nx">r</span><span class="p">.</span><span class="nf">arrayBuffer</span><span class="p">()))</span>
  <span class="p">);</span>
  
  <span class="c1">// Blob으로 병합</span>
  <span class="kd">const</span> <span class="nx">blob</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Blob</span><span class="p">(</span><span class="nx">chunks</span><span class="p">,</span> <span class="p">{</span> <span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">video/mp2t</span><span class="dl">'</span> <span class="p">});</span>
  
  <span class="c1">// 다운로드 트리거</span>
  <span class="kd">const</span> <span class="nx">downloadUrl</span> <span class="o">=</span> <span class="nx">URL</span><span class="p">.</span><span class="nf">createObjectURL</span><span class="p">(</span><span class="nx">blob</span><span class="p">);</span>
  <span class="nx">chrome</span><span class="p">.</span><span class="nx">downloads</span><span class="p">.</span><span class="nf">download</span><span class="p">({</span>
    <span class="na">url</span><span class="p">:</span> <span class="nx">downloadUrl</span><span class="p">,</span>
    <span class="na">filename</span><span class="p">:</span> <span class="dl">'</span><span class="s1">clip.ts</span><span class="dl">'</span>
  <span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>

<p>솔직히 말하면, 이 방식은 메모리를 상당히 잡아먹는다. 10분짜리 클립이면 수백 MB가 브라우저 메모리에 올라간다. 프로덕션 레벨에서는 스트리밍 방식으로 처리하거나, IndexedDB를 중간 버퍼로 활용하는 게 맞다.</p>

<h2 id="cors와-싸우기">CORS와 싸우기</h2>

<p>외부 API 없이 작동한다고 했지만, CORS는 여전히 발목을 잡는다. Content Script에서 직접 fetch하면 CORS 정책에 막힌다.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 이건 실패한다</span>
<span class="nf">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">https://chzzk.naver.com/api/video/...</span><span class="dl">'</span><span class="p">)</span>
  <span class="p">.</span><span class="nf">then</span><span class="p">(</span><span class="nx">r</span> <span class="o">=&gt;</span> <span class="nx">r</span><span class="p">.</span><span class="nf">json</span><span class="p">())</span> <span class="c1">// CORS 에러!</span>
</code></pre></div></div>

<p>해결책은 Background Script에서 fetch하는 것이다. Service Worker는 CORS 제약을 받지 않는다(host_permissions에 명시된 도메인에 한해서).</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// background.js</span>
<span class="nx">chrome</span><span class="p">.</span><span class="nx">runtime</span><span class="p">.</span><span class="nx">onMessage</span><span class="p">.</span><span class="nf">addListener</span><span class="p">((</span><span class="nx">message</span><span class="p">,</span> <span class="nx">sender</span><span class="p">,</span> <span class="nx">sendResponse</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">if </span><span class="p">(</span><span class="nx">message</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">FETCH_VIDEO_INFO</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
    <span class="nf">fetch</span><span class="p">(</span><span class="nx">message</span><span class="p">.</span><span class="nx">url</span><span class="p">,</span> <span class="p">{</span>
      <span class="na">credentials</span><span class="p">:</span> <span class="dl">'</span><span class="s1">include</span><span class="dl">'</span> <span class="c1">// 쿠키 포함 (인증된 요청)</span>
    <span class="p">})</span>
      <span class="p">.</span><span class="nf">then</span><span class="p">(</span><span class="nx">r</span> <span class="o">=&gt;</span> <span class="nx">r</span><span class="p">.</span><span class="nf">json</span><span class="p">())</span>
      <span class="p">.</span><span class="nf">then</span><span class="p">(</span><span class="nx">data</span> <span class="o">=&gt;</span> <span class="nf">sendResponse</span><span class="p">({</span> <span class="na">success</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">data</span> <span class="p">}))</span>
      <span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="nx">err</span> <span class="o">=&gt;</span> <span class="nf">sendResponse</span><span class="p">({</span> <span class="na">success</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="na">error</span><span class="p">:</span> <span class="nx">err</span><span class="p">.</span><span class="nx">message</span> <span class="p">}));</span>
    
    <span class="k">return</span> <span class="kc">true</span><span class="p">;</span> <span class="c1">// 비동기 응답을 위해 필수</span>
  <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<h2 id="보안과-개인정보-보호-관점">보안과 개인정보 보호 관점</h2>

<p>외부 서버를 거치지 않는다는 건 사용자 입장에서 엄청난 장점이다. 다운로드 기록이 어딘가에 수집되지 않고, 서버 운영자가 마음먹으면 악성 코드를 주입할 위험도 없다.</p>

<p>하지만 익스텐션 자체의 보안도 신경 써야 한다:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 절대 하지 말 것: eval 사용</span>
<span class="nf">eval</span><span class="p">(</span><span class="nx">responseFromPage</span><span class="p">);</span> <span class="c1">// 취약점</span>

<span class="c1">// 대신 JSON.parse로 안전하게 파싱</span>
<span class="k">try</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">responseFromPage</span><span class="p">);</span>
<span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">잘못된 데이터 형식</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="실전-팁과-주의사항">실전 팁과 주의사항</h2>

<p><strong>1. 버전 호환성 체크</strong>
치지직이 프론트엔드를 업데이트하면 DOM 구조나 API 응답 형식이 바뀔 수 있다. 셀렉터를 하드코딩하면 하루아침에 익스텐션이 무용지물이 된다.</p>

<p><strong>2. 속도 제한 준수</strong>
짧은 시간에 너무 많은 요청을 보내면 IP 차단을 당할 수 있다. 세그먼트 다운로드 시 적절한 딜레이를 주는 게 좋다.</p>

<p><strong>3. 에러 핸들링</strong>
네트워크는 언제든 실패한다. 재시도 로직과 사용자에게 명확한 에러 메시지를 제공해야 한다.</p>

<h2 id="마무리하며">마무리하며</h2>

<p>크롬 익스텐션으로 영상 다운로더를 만드는 건 기술적으로 그리 어렵지 않다. 어려운 건 유지보수다. 서비스의 변경에 맞춰 계속 업데이트해야 하고, 크롬 웹스토어 정책 변경에도 대응해야 한다.</p>

<p>외부 API 없이 순수 클라이언트 사이드로 구현하는 접근법은 개인정보 보호 측면에서 옳은 방향이라고 생각한다. 다만 HLS 처리나 대용량 파일 핸들링에서 브라우저의 한계를 만나게 되는데, 이건 트레이드오프로 받아들여야 한다. 완벽한 솔루션은 없고, 있다면 그건 거짓말이다.</p>]]></content><author><name>울이</name></author><category term="Frontend" /><category term="chrome-extension" /><category term="chzzk" /><category term="video-downloader" /><category term="browser-extension" /><category term="javascript" /><category term="web-development" /><category term="streaming" /><category term="clip-downloader" /><summary type="html"><![CDATA[치지직(Chzzk) 클립을 외부 페이지 이동이나 API 호출 없이 브라우저 내에서 직접 다운로드할 수 있는 크롬 익스텐션 개발 과정과 기술적 구현 방법을 다룹니다....]]></summary></entry><entry><title type="html">펌프 앤 덤프 소프트웨어 시대: 빠르게 만들고 버리는 개발 문화의 실체</title><link href="https://www.wool-dev.com/software%20engineering/the-age-of-pump-and-dump-software-the-reality-of-build-fast/" rel="alternate" type="text/html" title="펌프 앤 덤프 소프트웨어 시대: 빠르게 만들고 버리는 개발 문화의 실체" /><published>2026-01-27T00:00:00+09:00</published><updated>2026-01-27T00:00:00+09:00</updated><id>https://www.wool-dev.com/software%20engineering/the-age-of-pump-and-dump-software-the-reality-of-build-fast</id><content type="html" xml:base="https://www.wool-dev.com/software%20engineering/the-age-of-pump-and-dump-software-the-reality-of-build-fast/"><![CDATA[<h1 id="펌프-앤-덤프-소프트웨어의-시대-기술-부채를-떠넘기는-개발-문화">펌프 앤 덤프 소프트웨어의 시대: 기술 부채를 떠넘기는 개발 문화</h1>

<p>우리 업계에 새로운 유행이 있다. 빠르게 만들고, 화려하게 포장하고, 문제가 터지기 전에 빠져나가는 것. 주식 시장의 “펌프 앤 덤프(pump and dump)” 사기와 똑같은 패턴이 소프트웨어 개발에서도 벌어지고 있다. 그리고 이 쓰레기를 치우는 건 항상 다음 팀, 다음 개발자의 몫이다.</p>

<h2 id="펌프-앤-덤프-소프트웨어란-무엇인가">펌프 앤 덤프 소프트웨어란 무엇인가</h2>

<p>펌프 앤 덤프 소프트웨어는 단기적인 성과와 데모를 위해 설계된 코드를 말한다. 겉으로는 작동하는 것처럼 보이지만, 실제로는 유지보수가 불가능하고 확장성이 없으며, 기술 부채 덩어리다.</p>

<p>이런 코드의 특징은 명확하다:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 전형적인 펌프 앤 덤프 코드
</span><span class="k">def</span> <span class="nf">process_user_data</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
    <span class="c1"># TODO: 나중에 리팩토링 (2019년에 작성됨)
</span>    <span class="c1"># FIXME: 이거 왜 작동하는지 모르겠음
</span>    <span class="k">try</span><span class="p">:</span>
        <span class="n">result</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="sh">'</span><span class="s">user</span><span class="sh">'</span><span class="p">][</span><span class="sh">'</span><span class="s">profile</span><span class="sh">'</span><span class="p">][</span><span class="sh">'</span><span class="s">settings</span><span class="sh">'</span><span class="p">][</span><span class="sh">'</span><span class="s">preferences</span><span class="sh">'</span><span class="p">][</span><span class="sh">'</span><span class="s">theme</span><span class="sh">'</span><span class="p">]</span>
    <span class="k">except</span><span class="p">:</span>
        <span class="n">result</span> <span class="o">=</span> <span class="sh">"</span><span class="s">default</span><span class="sh">"</span>  <span class="c1"># 모든 예외를 삼켜버리기
</span>    
    <span class="c1"># 하드코딩된 값들
</span>    <span class="k">if</span> <span class="n">result</span> <span class="o">==</span> <span class="sh">"</span><span class="s">dark</span><span class="sh">"</span><span class="p">:</span>
        <span class="k">return</span> <span class="p">{</span><span class="sh">"</span><span class="s">color</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">#000000</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">bg</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">#1a1a1a</span><span class="sh">"</span><span class="p">}</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="k">return</span> <span class="p">{</span><span class="sh">"</span><span class="s">color</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">#ffffff</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">bg</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">#fafafa</span><span class="sh">"</span><span class="p">}</span>
</code></pre></div></div>

<p>이 코드는 작동한다. 데모에서도 잘 돌아간다. 하지만 3개월 후 다른 테마를 추가해야 할 때? 6개월 후 <code class="language-plaintext highlighter-rouge">data</code> 구조가 바뀔 때? 그때는 원래 개발자는 이미 다른 프로젝트로, 아니면 다른 회사로 떠났다.</p>

<h2 id="왜-이런-현상이-만연해졌나">왜 이런 현상이 만연해졌나</h2>

<h3 id="인센티브-구조의-문제">인센티브 구조의 문제</h3>

<p>대부분의 회사에서 개발자는 “배포한 기능 수”로 평가받는다. 코드 품질? 측정하기 어렵다. 기술 부채 감소? 경영진 눈에 보이지 않는다.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 평가 시즌에 작성되는 코드</span>
<span class="kd">const</span> <span class="nx">quickWin</span> <span class="o">=</span> <span class="k">async </span><span class="p">(</span><span class="nx">userId</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="c1">// 직접 DB 쿼리 (ORM? 그게 뭔데?)</span>
  <span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">db</span><span class="p">.</span><span class="nf">raw</span><span class="p">(</span><span class="s2">`
    SELECT * FROM users 
    WHERE id = </span><span class="p">${</span><span class="nx">userId</span><span class="p">}</span><span class="s2">  // SQL 인젝션? 뭐 어때
  `</span><span class="p">);</span>
  
  <span class="c1">// 캐싱? 다음 분기에 하죠</span>
  <span class="c1">// 에러 핸들링? 프로덕션에서 테스트하면 되죠</span>
  <span class="k">return</span> <span class="nx">result</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
<span class="p">};</span>
</code></pre></div></div>

<h3 id="짧아지는-재직-기간">짧아지는 재직 기간</h3>

<p>평균 재직 기간이 2년도 안 되는 시대다. 내가 작성한 코드의 결과를 내가 책임질 일이 없다. 이직하면 끝이다.</p>

<h3 id="스타트업-문화의-그림자">스타트업 문화의 그림자</h3>

<p>“일단 출시하고 나중에 고치자”는 말은 PMF(Product-Market Fit)를 찾는 초기 스타트업에서는 합리적이다. 문제는 시리즈 C 받은 500명 규모 회사에서도 같은 논리가 통용된다는 것이다.</p>

<h2 id="실제-피해-사례-코드로-보는-참상">실제 피해 사례: 코드로 보는 참상</h2>

<p>유지보수 불가능한 코드가 어떻게 생겼는지 보자.</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// "빠르게" 만들어진 API 엔드포인트</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">handleRequest</span> <span class="o">=</span> <span class="k">async </span><span class="p">(</span><span class="nx">req</span><span class="p">:</span> <span class="kr">any</span><span class="p">,</span> <span class="nx">res</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="c1">// any 타입의 향연</span>
  <span class="kd">const</span> <span class="na">data</span><span class="p">:</span> <span class="kr">any</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">body</span><span class="p">;</span>
  
  <span class="c1">// 비즈니스 로직 500줄이 한 함수에</span>
  <span class="k">if </span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="kd">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">create</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 100줄의 로직...</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="kd">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">update</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 또 다른 100줄...</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="kd">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">delete</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 그리고 또...</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="kd">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">createOrUpdate</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 왜 이게 있지?</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="kd">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">softDelete</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 아...</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="kd">type</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">hardDelete</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 제발...</span>
  <span class="p">}</span>
  
  <span class="c1">// 테스트? 작성자가 퇴사해서 불가능</span>
  <span class="nx">res</span><span class="p">.</span><span class="nf">json</span><span class="p">({</span> <span class="na">success</span><span class="p">:</span> <span class="kc">true</span> <span class="p">});</span>  <span class="c1">// 항상 성공</span>
<span class="p">};</span>
</code></pre></div></div>

<p>이런 코드를 물려받으면 선택지는 두 가지다. 전체를 다시 짜거나(시간과 예산 없음), 그 위에 또 다른 펌프 앤 덤프 코드를 얹거나.</p>

<h2 id="펌프-앤-덤프를-방지하는-방법">펌프 앤 덤프를 방지하는 방법</h2>

<h3 id="1-코드-리뷰의-진짜-의미를-되찾자">1. 코드 리뷰의 진짜 의미를 되찾자</h3>

<p>LGTM 도장 찍기 식의 코드 리뷰는 무의미하다. 진짜 질문을 해야 한다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❌ "LGTM"
❌ "몇 가지 nit이 있지만 approve"

✅ "이 함수가 400줄인데, 왜 분리가 안 될까요?"
✅ "여기 try-catch가 모든 예외를 삼키는데, 로깅이라도 해야 하지 않을까요?"
✅ "이 로직이 다른 곳에서 재사용될 것 같은데, 테스트가 없네요"
</code></pre></div></div>

<h3 id="2-기술-부채를-가시화하라">2. 기술 부채를 가시화하라</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># tech-debt.yaml - 기술 부채 트래킹</span>
<span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">TD-001</span>
  <span class="na">created</span><span class="pi">:</span> <span class="s">2024-01-15</span>
  <span class="na">author</span><span class="pi">:</span> <span class="s2">"</span><span class="s">kim@company.com"</span>
  <span class="na">severity</span><span class="pi">:</span> <span class="s">high</span>
  <span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">User</span><span class="nv"> </span><span class="s">서비스에</span><span class="nv"> </span><span class="s">any</span><span class="nv"> </span><span class="s">타입</span><span class="nv"> </span><span class="s">남용"</span>
  <span class="na">estimated_fix_hours</span><span class="pi">:</span> <span class="m">16</span>
  <span class="na">blocking_features</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">user-analytics"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">audit-log"</span><span class="pi">]</span>
  
<span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">TD-002</span>
  <span class="na">created</span><span class="pi">:</span> <span class="s">2024-01-20</span>
  <span class="na">author</span><span class="pi">:</span> <span class="s2">"</span><span class="s">lee@company.com"</span>  
  <span class="na">severity</span><span class="pi">:</span> <span class="s">critical</span>
  <span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">결제</span><span class="nv"> </span><span class="s">모듈</span><span class="nv"> </span><span class="s">테스트</span><span class="nv"> </span><span class="s">커버리지</span><span class="nv"> </span><span class="s">0%"</span>
  <span class="na">estimated_fix_hours</span><span class="pi">:</span> <span class="m">40</span>
  <span class="na">blocking_features</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">international-payment"</span><span class="pi">]</span>
</code></pre></div></div>

<p>이런 파일이 있으면 최소한 “몰랐다”는 변명은 못 한다.</p>

<h3 id="3-퇴사-전-코드-정리를-문화로">3. 퇴사 전 코드 정리를 문화로</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 퇴사 체크리스트 (제안)</span>
□ 내가 작성한 코드 중 TODO/FIXME 정리
□ 문서화되지 않은 암묵적 지식 정리
□ 후임자와 페어 프로그래밍 세션 2회 이상
□ 내가 만든 기술 부채 목록 작성
</code></pre></div></div>

<h2 id="솔직한-의견-이건-시스템의-문제다">솔직한 의견: 이건 시스템의 문제다</h2>

<p>개인을 탓하기 쉽다. 하지만 인센티브가 잘못되면 행동도 잘못된다. 빠른 배포를 보상하고 품질은 무시하는 시스템에서, 품질 좋은 코드를 기대하는 건 순진한 생각이다.</p>

<p>해결책? 측정할 수 없으면 관리할 수 없다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 빌드 파이프라인에 품질 게이트 추가
</span><span class="n">quality_metrics</span> <span class="o">=</span> <span class="p">{</span>
    <span class="sh">"</span><span class="s">test_coverage</span><span class="sh">"</span><span class="p">:</span> <span class="mi">80</span><span class="p">,</span>  <span class="c1"># 최소 80%
</span>    <span class="sh">"</span><span class="s">cyclomatic_complexity</span><span class="sh">"</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span>  <span class="c1"># 함수당 최대 10
</span>    <span class="sh">"</span><span class="s">any_type_count</span><span class="sh">"</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>  <span class="c1"># TypeScript any 금지
</span>    <span class="sh">"</span><span class="s">todo_count_delta</span><span class="sh">"</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>  <span class="c1"># TODO 증가 금지
</span><span class="p">}</span>

<span class="k">if</span> <span class="ow">not</span> <span class="nf">meets_quality_gate</span><span class="p">(</span><span class="n">quality_metrics</span><span class="p">):</span>
    <span class="n">sys</span><span class="p">.</span><span class="nf">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>  <span class="c1"># 머지 불가
</span></code></pre></div></div>

<p>강제하지 않으면 안 한다. 슬프지만 현실이다.</p>

<p>펌프 앤 덤프 소프트웨어는 기술 부채 그 이상이다. 그건 다음 개발자에게 보내는 저주 편지다. 우리 모두 언젠가 그 다음 개발자가 된다는 걸 기억하자.</p>]]></content><author><name>울이</name></author><category term="Software Engineering" /><category term="software-engineering" /><category term="technical-debt" /><category term="startup-culture" /><category term="code-quality" /><category term="development-practices" /><category term="software-lifecycle" /><category term="engineering-ethics" /><summary type="html"><![CDATA[An analysis of the growing trend of disposable software development where products are built quickly for short-term gains and abandoned, examining its impact on code quality, developer burnout, and the tech industry's sustainability...]]></summary></entry><entry><title type="html">Cloud-Native Observability Stack Part 2 - Distributed Tracing Across Microservices</title><link href="https://www.wool-dev.com/backend%20engineering/spring/observability-part2-distributed-tracing/" rel="alternate" type="text/html" title="Cloud-Native Observability Stack Part 2 - Distributed Tracing Across Microservices" /><published>2026-01-26T00:00:00+09:00</published><updated>2026-01-26T00:00:00+09:00</updated><id>https://www.wool-dev.com/backend%20engineering/spring/observability-part2-distributed-tracing</id><content type="html" xml:base="https://www.wool-dev.com/backend%20engineering/spring/observability-part2-distributed-tracing/"><![CDATA[<h2 id="시리즈-소개">시리즈 소개</h2>

<ol>
  <li>Part 1: OpenTelemetry Instrumentation</li>
  <li><strong>Part 2: 마이크로서비스 분산 추적</strong> (현재 글)</li>
  <li>Part 3: 구조화된 로깅과 Correlation ID</li>
  <li>Part 4: Prometheus/Grafana로 메트릭과 알림</li>
  <li>Part 5: Observability 데이터로 프로덕션 이슈 디버깅</li>
</ol>

<h2 id="분산-추적이란">분산 추적이란?</h2>

<p>분산 추적은 요청이 여러 서비스를 거쳐가는 전체 경로를 시각화합니다.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>User Request
    │
    ▼
┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│ API Gateway │────▶│Order Service│────▶│Payment Svc  │
│   Span A    │     │   Span B    │     │   Span C    │
└─────────────┘     └──────┬──────┘     └─────────────┘
                          │
                          ▼
                   ┌─────────────┐
                   │Inventory Svc│
                   │   Span D    │
                   └─────────────┘
</code></pre></div></div>

<h2 id="trace-context-구조">Trace Context 구조</h2>

<h3 id="w3c-trace-context-표준">W3C Trace Context 표준</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>traceparent: 00-{trace-id}-{span-id}-{trace-flags}
tracestate: vendor1=value1,vendor2=value2
</code></pre></div></div>

<p>예시:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
tracestate: congo=t61rcWkgMzE
</code></pre></div></div>

<ul>
  <li><strong>trace-id</strong>: 전체 트레이스를 식별하는 32자리 hex</li>
  <li><strong>span-id</strong>: 현재 스팬을 식별하는 16자리 hex</li>
  <li><strong>trace-flags</strong>: 01 = sampled</li>
</ul>

<h2 id="실전-분산-추적-구현">실전 분산 추적 구현</h2>

<h3 id="멀티-서비스-아키텍처">멀티 서비스 아키텍처</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># docker-compose.yml</span>
<span class="na">version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">3.8'</span>
<span class="na">services</span><span class="pi">:</span>
  <span class="na">api-gateway</span><span class="pi">:</span>
    <span class="na">build</span><span class="pi">:</span> <span class="s">./api-gateway</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">8080:8080"</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">OTEL_SERVICE_NAME=api-gateway</span>
      <span class="pi">-</span> <span class="s">OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4317</span>

  <span class="na">order-service</span><span class="pi">:</span>
    <span class="na">build</span><span class="pi">:</span> <span class="s">./order-service</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">8081:8081"</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">OTEL_SERVICE_NAME=order-service</span>
      <span class="pi">-</span> <span class="s">OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4317</span>

  <span class="na">payment-service</span><span class="pi">:</span>
    <span class="na">build</span><span class="pi">:</span> <span class="s">./payment-service</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">8082:8082"</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">OTEL_SERVICE_NAME=payment-service</span>
      <span class="pi">-</span> <span class="s">OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4317</span>

  <span class="na">inventory-service</span><span class="pi">:</span>
    <span class="na">build</span><span class="pi">:</span> <span class="s">./inventory-service</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">8083:8083"</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">OTEL_SERVICE_NAME=inventory-service</span>
      <span class="pi">-</span> <span class="s">OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4317</span>

  <span class="na">jaeger</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">jaegertracing/all-in-one:1.53</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">16686:16686"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">4317:4317"</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">COLLECTOR_OTLP_ENABLED=true</span>
</code></pre></div></div>

<h3 id="api-gateway">API Gateway</h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RestController</span>
<span class="nd">@RequestMapping</span><span class="p">(</span><span class="s">"/api"</span><span class="p">)</span>
<span class="kd">class</span> <span class="nc">GatewayController</span><span class="p">(</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">orderServiceClient</span><span class="p">:</span> <span class="nc">OrderServiceClient</span><span class="p">,</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">tracer</span><span class="p">:</span> <span class="nc">Tracer</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="nd">@PostMapping</span><span class="p">(</span><span class="s">"/orders"</span><span class="p">)</span>
    <span class="k">fun</span> <span class="nf">createOrder</span><span class="p">(</span><span class="nd">@RequestBody</span> <span class="n">request</span><span class="p">:</span> <span class="nc">CreateOrderRequest</span><span class="p">):</span> <span class="nc">ResponseEntity</span><span class="p">&lt;</span><span class="nc">OrderResponse</span><span class="p">&gt;</span> <span class="p">{</span>
        <span class="kd">val</span> <span class="py">span</span> <span class="p">=</span> <span class="n">tracer</span><span class="p">.</span><span class="nf">spanBuilder</span><span class="p">(</span><span class="s">"gateway.createOrder"</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">setSpanKind</span><span class="p">(</span><span class="nc">SpanKind</span><span class="p">.</span><span class="nc">SERVER</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="s">"http.method"</span><span class="p">,</span> <span class="s">"POST"</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="s">"http.route"</span><span class="p">,</span> <span class="s">"/api/orders"</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">startSpan</span><span class="p">()</span>

        <span class="k">return</span> <span class="k">try</span> <span class="p">{</span>
            <span class="n">span</span><span class="p">.</span><span class="nf">makeCurrent</span><span class="p">().</span><span class="nf">use</span> <span class="p">{</span>
                <span class="kd">val</span> <span class="py">order</span> <span class="p">=</span> <span class="n">orderServiceClient</span><span class="p">.</span><span class="nf">createOrder</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
                <span class="n">span</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="s">"order.id"</span><span class="p">,</span> <span class="n">order</span><span class="p">.</span><span class="n">id</span><span class="p">)</span>
                <span class="nc">ResponseEntity</span><span class="p">.</span><span class="nf">created</span><span class="p">(</span><span class="nc">URI</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="s">"/api/orders/${order.id}"</span><span class="p">)).</span><span class="nf">body</span><span class="p">(</span><span class="n">order</span><span class="p">)</span>
            <span class="p">}</span>
        <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">e</span><span class="p">:</span> <span class="nc">Exception</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">span</span><span class="p">.</span><span class="nf">recordException</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
            <span class="n">span</span><span class="p">.</span><span class="nf">setStatus</span><span class="p">(</span><span class="nc">StatusCode</span><span class="p">.</span><span class="nc">ERROR</span><span class="p">)</span>
            <span class="k">throw</span> <span class="n">e</span>
        <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
            <span class="n">span</span><span class="p">.</span><span class="nf">end</span><span class="p">()</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="order-service-client-context-전파">Order Service Client (Context 전파)</h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span>
<span class="kd">class</span> <span class="nc">OrderServiceClient</span><span class="p">(</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">webClient</span><span class="p">:</span> <span class="nc">WebClient</span><span class="p">,</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">openTelemetry</span><span class="p">:</span> <span class="nc">OpenTelemetry</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">fun</span> <span class="nf">createOrder</span><span class="p">(</span><span class="n">request</span><span class="p">:</span> <span class="nc">CreateOrderRequest</span><span class="p">):</span> <span class="nc">OrderResponse</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">webClient</span><span class="p">.</span><span class="nf">post</span><span class="p">()</span>
            <span class="p">.</span><span class="nf">uri</span><span class="p">(</span><span class="s">"/orders"</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">bodyValue</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">headers</span> <span class="p">{</span> <span class="n">headers</span> <span class="p">-&gt;</span>
                <span class="c1">// Trace Context 주입</span>
                <span class="n">openTelemetry</span><span class="p">.</span><span class="n">propagators</span><span class="p">.</span><span class="n">textMapPropagator</span><span class="p">.</span><span class="nf">inject</span><span class="p">(</span>
                    <span class="nc">Context</span><span class="p">.</span><span class="nf">current</span><span class="p">(),</span>
                    <span class="n">headers</span>
                <span class="p">)</span> <span class="p">{</span> <span class="n">carrier</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span> <span class="p">-&gt;</span>
                    <span class="n">carrier</span><span class="o">?.</span><span class="k">set</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
                <span class="p">}</span>
            <span class="p">}</span>
            <span class="p">.</span><span class="nf">retrieve</span><span class="p">()</span>
            <span class="p">.</span><span class="nf">bodyToMono</span><span class="p">(</span><span class="nc">OrderResponse</span><span class="o">::</span><span class="k">class</span><span class="p">.</span><span class="n">java</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">block</span><span class="p">()</span><span class="o">!!</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="order-service">Order Service</h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@RestController</span>
<span class="nd">@RequestMapping</span><span class="p">(</span><span class="s">"/orders"</span><span class="p">)</span>
<span class="kd">class</span> <span class="nc">OrderController</span><span class="p">(</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">orderService</span><span class="p">:</span> <span class="nc">OrderService</span><span class="p">,</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">tracer</span><span class="p">:</span> <span class="nc">Tracer</span><span class="p">,</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">openTelemetry</span><span class="p">:</span> <span class="nc">OpenTelemetry</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="nd">@PostMapping</span>
    <span class="k">fun</span> <span class="nf">createOrder</span><span class="p">(</span>
        <span class="nd">@RequestBody</span> <span class="n">request</span><span class="p">:</span> <span class="nc">CreateOrderRequest</span><span class="p">,</span>
        <span class="nd">@RequestHeader</span> <span class="n">headers</span><span class="p">:</span> <span class="nc">HttpHeaders</span>
    <span class="p">):</span> <span class="nc">ResponseEntity</span><span class="p">&lt;</span><span class="nc">OrderResponse</span><span class="p">&gt;</span> <span class="p">{</span>
        <span class="c1">// 부모 Context 추출</span>
        <span class="kd">val</span> <span class="py">parentContext</span> <span class="p">=</span> <span class="n">openTelemetry</span><span class="p">.</span><span class="n">propagators</span><span class="p">.</span><span class="n">textMapPropagator</span><span class="p">.</span><span class="nf">extract</span><span class="p">(</span>
            <span class="nc">Context</span><span class="p">.</span><span class="nf">current</span><span class="p">(),</span>
            <span class="n">headers</span>
        <span class="p">)</span> <span class="p">{</span> <span class="n">carrier</span><span class="p">,</span> <span class="n">key</span> <span class="p">-&gt;</span> <span class="n">carrier</span><span class="o">?.</span><span class="nf">getFirst</span><span class="p">(</span><span class="n">key</span><span class="p">)</span> <span class="p">}</span>

        <span class="kd">val</span> <span class="py">span</span> <span class="p">=</span> <span class="n">tracer</span><span class="p">.</span><span class="nf">spanBuilder</span><span class="p">(</span><span class="s">"order.create"</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">setParent</span><span class="p">(</span><span class="n">parentContext</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">setSpanKind</span><span class="p">(</span><span class="nc">SpanKind</span><span class="p">.</span><span class="nc">SERVER</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">startSpan</span><span class="p">()</span>

        <span class="k">return</span> <span class="k">try</span> <span class="p">{</span>
            <span class="n">span</span><span class="p">.</span><span class="nf">makeCurrent</span><span class="p">().</span><span class="nf">use</span> <span class="p">{</span>
                <span class="kd">val</span> <span class="py">order</span> <span class="p">=</span> <span class="n">orderService</span><span class="p">.</span><span class="nf">createOrder</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
                <span class="nc">ResponseEntity</span><span class="p">.</span><span class="nf">ok</span><span class="p">(</span><span class="nc">OrderResponse</span><span class="p">(</span><span class="n">order</span><span class="p">))</span>
            <span class="p">}</span>
        <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
            <span class="n">span</span><span class="p">.</span><span class="nf">end</span><span class="p">()</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="nd">@Service</span>
<span class="kd">class</span> <span class="nc">OrderService</span><span class="p">(</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">orderRepository</span><span class="p">:</span> <span class="nc">OrderRepository</span><span class="p">,</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">paymentClient</span><span class="p">:</span> <span class="nc">PaymentClient</span><span class="p">,</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">inventoryClient</span><span class="p">:</span> <span class="nc">InventoryClient</span><span class="p">,</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">tracer</span><span class="p">:</span> <span class="nc">Tracer</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="nd">@Transactional</span>
    <span class="k">fun</span> <span class="nf">createOrder</span><span class="p">(</span><span class="n">request</span><span class="p">:</span> <span class="nc">CreateOrderRequest</span><span class="p">):</span> <span class="nc">Order</span> <span class="p">{</span>
        <span class="c1">// 재고 확인</span>
        <span class="kd">val</span> <span class="py">inventorySpan</span> <span class="p">=</span> <span class="n">tracer</span><span class="p">.</span><span class="nf">spanBuilder</span><span class="p">(</span><span class="s">"order.checkInventory"</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">setSpanKind</span><span class="p">(</span><span class="nc">SpanKind</span><span class="p">.</span><span class="nc">CLIENT</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">startSpan</span><span class="p">()</span>

        <span class="k">try</span> <span class="p">{</span>
            <span class="n">inventorySpan</span><span class="p">.</span><span class="nf">makeCurrent</span><span class="p">().</span><span class="nf">use</span> <span class="p">{</span>
                <span class="n">inventoryClient</span><span class="p">.</span><span class="nf">checkAndReserve</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="n">items</span><span class="p">)</span>
            <span class="p">}</span>
        <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
            <span class="n">inventorySpan</span><span class="p">.</span><span class="nf">end</span><span class="p">()</span>
        <span class="p">}</span>

        <span class="c1">// 주문 저장</span>
        <span class="kd">val</span> <span class="py">saveSpan</span> <span class="p">=</span> <span class="n">tracer</span><span class="p">.</span><span class="nf">spanBuilder</span><span class="p">(</span><span class="s">"order.save"</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="s">"db.system"</span><span class="p">,</span> <span class="s">"postgresql"</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">startSpan</span><span class="p">()</span>

        <span class="kd">val</span> <span class="py">order</span> <span class="p">=</span> <span class="k">try</span> <span class="p">{</span>
            <span class="n">saveSpan</span><span class="p">.</span><span class="nf">makeCurrent</span><span class="p">().</span><span class="nf">use</span> <span class="p">{</span>
                <span class="n">orderRepository</span><span class="p">.</span><span class="nf">save</span><span class="p">(</span><span class="nc">Order</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="n">request</span><span class="p">))</span>
            <span class="p">}</span>
        <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
            <span class="n">saveSpan</span><span class="p">.</span><span class="nf">end</span><span class="p">()</span>
        <span class="p">}</span>

        <span class="c1">// 결제 처리</span>
        <span class="kd">val</span> <span class="py">paymentSpan</span> <span class="p">=</span> <span class="n">tracer</span><span class="p">.</span><span class="nf">spanBuilder</span><span class="p">(</span><span class="s">"order.processPayment"</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">setSpanKind</span><span class="p">(</span><span class="nc">SpanKind</span><span class="p">.</span><span class="nc">CLIENT</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">startSpan</span><span class="p">()</span>

        <span class="k">try</span> <span class="p">{</span>
            <span class="n">paymentSpan</span><span class="p">.</span><span class="nf">makeCurrent</span><span class="p">().</span><span class="nf">use</span> <span class="p">{</span>
                <span class="n">paymentClient</span><span class="p">.</span><span class="nf">charge</span><span class="p">(</span><span class="n">order</span><span class="p">.</span><span class="n">customerId</span><span class="p">,</span> <span class="n">order</span><span class="p">.</span><span class="n">totalAmount</span><span class="p">)</span>
            <span class="p">}</span>
        <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
            <span class="n">paymentSpan</span><span class="p">.</span><span class="nf">end</span><span class="p">()</span>
        <span class="p">}</span>

        <span class="k">return</span> <span class="n">order</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="span-계층-구조">Span 계층 구조</h2>

<h3 id="부모-자식-관계">부모-자식 관계</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Trace: abc123
│
├── Span A: gateway.createOrder (Root Span)
│   │
│   └── Span B: order.create (Child of A)
│       │
│       ├── Span C: order.checkInventory (Child of B)
│       │   │
│       │   └── Span E: inventory.reserve (Child of C)
│       │
│       ├── Span D: order.save (Child of B)
│       │
│       └── Span F: order.processPayment (Child of B)
│           │
│           └── Span G: payment.charge (Child of F)
</code></pre></div></div>

<h3 id="span-link-병렬-처리">Span Link (병렬 처리)</h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Service</span>
<span class="kd">class</span> <span class="nc">BatchOrderProcessor</span><span class="p">(</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">tracer</span><span class="p">:</span> <span class="nc">Tracer</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="k">fun</span> <span class="nf">processBatch</span><span class="p">(</span><span class="n">orders</span><span class="p">:</span> <span class="nc">List</span><span class="p">&lt;</span><span class="nc">Order</span><span class="p">&gt;)</span> <span class="p">{</span>
        <span class="kd">val</span> <span class="py">batchSpan</span> <span class="p">=</span> <span class="n">tracer</span><span class="p">.</span><span class="nf">spanBuilder</span><span class="p">(</span><span class="s">"batch.process"</span><span class="p">)</span>
            <span class="p">.</span><span class="nf">startSpan</span><span class="p">()</span>

        <span class="k">try</span> <span class="p">{</span>
            <span class="n">batchSpan</span><span class="p">.</span><span class="nf">makeCurrent</span><span class="p">().</span><span class="nf">use</span> <span class="p">{</span>
                <span class="n">orders</span><span class="p">.</span><span class="nf">parallelStream</span><span class="p">().</span><span class="nf">forEach</span> <span class="p">{</span> <span class="n">order</span> <span class="p">-&gt;</span>
                    <span class="kd">val</span> <span class="py">orderSpan</span> <span class="p">=</span> <span class="n">tracer</span><span class="p">.</span><span class="nf">spanBuilder</span><span class="p">(</span><span class="s">"batch.processOrder"</span><span class="p">)</span>
                        <span class="p">.</span><span class="nf">addLink</span><span class="p">(</span><span class="n">batchSpan</span><span class="p">.</span><span class="n">spanContext</span><span class="p">)</span>  <span class="c1">// 링크로 연결</span>
                        <span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="s">"order.id"</span><span class="p">,</span> <span class="n">order</span><span class="p">.</span><span class="n">id</span><span class="p">)</span>
                        <span class="p">.</span><span class="nf">startSpan</span><span class="p">()</span>

                    <span class="k">try</span> <span class="p">{</span>
                        <span class="n">orderSpan</span><span class="p">.</span><span class="nf">makeCurrent</span><span class="p">().</span><span class="nf">use</span> <span class="p">{</span>
                            <span class="nf">processOrder</span><span class="p">(</span><span class="n">order</span><span class="p">)</span>
                        <span class="p">}</span>
                    <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
                        <span class="n">orderSpan</span><span class="p">.</span><span class="nf">end</span><span class="p">()</span>
                    <span class="p">}</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span>
            <span class="n">batchSpan</span><span class="p">.</span><span class="nf">end</span><span class="p">()</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="샘플링-전략">샘플링 전략</h2>

<h3 id="head-based-sampling">Head-based Sampling</h3>

<p>요청 시작 시점에 샘플링 결정:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="kd">class</span> <span class="nc">SamplingConfig</span> <span class="p">{</span>

    <span class="nd">@Bean</span>
    <span class="k">fun</span> <span class="nf">sdkTracerProvider</span><span class="p">():</span> <span class="nc">SdkTracerProvider</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nc">SdkTracerProvider</span><span class="p">.</span><span class="nf">builder</span><span class="p">()</span>
            <span class="p">.</span><span class="nf">setSampler</span><span class="p">(</span>
                <span class="nc">Sampler</span><span class="p">.</span><span class="nf">parentBased</span><span class="p">(</span>
                    <span class="nc">Sampler</span><span class="p">.</span><span class="nf">traceIdRatioBased</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>  <span class="c1">// 10% 샘플링</span>
                <span class="p">)</span>
            <span class="p">)</span>
            <span class="p">.</span><span class="nf">build</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="tail-based-sampling-otel-collector">Tail-based Sampling (OTel Collector)</h3>

<p>요청 완료 후 샘플링 결정:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># otel-collector-config.yaml</span>
<span class="na">processors</span><span class="pi">:</span>
  <span class="na">tail_sampling</span><span class="pi">:</span>
    <span class="na">decision_wait</span><span class="pi">:</span> <span class="s">10s</span>
    <span class="na">num_traces</span><span class="pi">:</span> <span class="m">100</span>
    <span class="na">expected_new_traces_per_sec</span><span class="pi">:</span> <span class="m">10</span>
    <span class="na">policies</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">errors-policy</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">status_code</span>
        <span class="na">status_code</span><span class="pi">:</span>
          <span class="na">status_codes</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">ERROR</span><span class="pi">]</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">slow-traces-policy</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">latency</span>
        <span class="na">latency</span><span class="pi">:</span>
          <span class="na">threshold_ms</span><span class="pi">:</span> <span class="m">1000</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">probabilistic-policy</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">probabilistic</span>
        <span class="na">probabilistic</span><span class="pi">:</span>
          <span class="na">sampling_percentage</span><span class="pi">:</span> <span class="m">10</span>

<span class="na">service</span><span class="pi">:</span>
  <span class="na">pipelines</span><span class="pi">:</span>
    <span class="na">traces</span><span class="pi">:</span>
      <span class="na">receivers</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">otlp</span><span class="pi">]</span>
      <span class="na">processors</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">tail_sampling</span><span class="pi">,</span> <span class="nv">batch</span><span class="pi">]</span>
      <span class="na">exporters</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">otlp/jaeger</span><span class="pi">]</span>
</code></pre></div></div>

<h2 id="jaeger-ui-활용">Jaeger UI 활용</h2>

<h3 id="트레이스-검색">트레이스 검색</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service=order-service operation=order.create minDuration=100ms
</code></pre></div></div>

<h3 id="서비스-의존성-그래프">서비스 의존성 그래프</h3>

<p>Jaeger UI에서 System Architecture 탭을 통해 서비스 간 의존성을 시각화할 수 있습니다.</p>

<h3 id="성능-분석">성능 분석</h3>

<ul>
  <li>Critical Path 분석</li>
  <li>Span 간 시간 비교</li>
  <li>병목 지점 식별</li>
</ul>

<h2 id="span-attributes-모범-사례">Span Attributes 모범 사례</h2>

<h3 id="semantic-conventions">Semantic Conventions</h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// HTTP 관련</span>
<span class="n">span</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="nc">SemanticAttributes</span><span class="p">.</span><span class="nc">HTTP_METHOD</span><span class="p">,</span> <span class="s">"POST"</span><span class="p">)</span>
<span class="n">span</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="nc">SemanticAttributes</span><span class="p">.</span><span class="nc">HTTP_URL</span><span class="p">,</span> <span class="s">"/api/orders"</span><span class="p">)</span>
<span class="n">span</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="nc">SemanticAttributes</span><span class="p">.</span><span class="nc">HTTP_STATUS_CODE</span><span class="p">,</span> <span class="mi">200</span><span class="p">)</span>

<span class="c1">// Database 관련</span>
<span class="n">span</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="nc">SemanticAttributes</span><span class="p">.</span><span class="nc">DB_SYSTEM</span><span class="p">,</span> <span class="s">"postgresql"</span><span class="p">)</span>
<span class="n">span</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="nc">SemanticAttributes</span><span class="p">.</span><span class="nc">DB_OPERATION</span><span class="p">,</span> <span class="s">"SELECT"</span><span class="p">)</span>
<span class="n">span</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="nc">SemanticAttributes</span><span class="p">.</span><span class="nc">DB_STATEMENT</span><span class="p">,</span> <span class="s">"SELECT * FROM orders WHERE id = ?"</span><span class="p">)</span>

<span class="c1">// Messaging 관련</span>
<span class="n">span</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="nc">SemanticAttributes</span><span class="p">.</span><span class="nc">MESSAGING_SYSTEM</span><span class="p">,</span> <span class="s">"kafka"</span><span class="p">)</span>
<span class="n">span</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="nc">SemanticAttributes</span><span class="p">.</span><span class="nc">MESSAGING_DESTINATION</span><span class="p">,</span> <span class="s">"order-events"</span><span class="p">)</span>
<span class="n">span</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="nc">SemanticAttributes</span><span class="p">.</span><span class="nc">MESSAGING_OPERATION</span><span class="p">,</span> <span class="s">"publish"</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="커스텀-attributes">커스텀 Attributes</h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 비즈니스 컨텍스트</span>
<span class="n">span</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="s">"order.id"</span><span class="p">,</span> <span class="n">orderId</span><span class="p">)</span>
<span class="n">span</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="s">"customer.tier"</span><span class="p">,</span> <span class="s">"premium"</span><span class="p">)</span>
<span class="n">span</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="s">"order.item_count"</span><span class="p">,</span> <span class="n">items</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="nf">toLong</span><span class="p">())</span>
<span class="n">span</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="s">"order.total_amount"</span><span class="p">,</span> <span class="n">totalAmount</span><span class="p">.</span><span class="nf">toDouble</span><span class="p">())</span>
</code></pre></div></div>

<h2 id="에러-추적">에러 추적</h2>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">try</span> <span class="p">{</span>
    <span class="nf">processOrder</span><span class="p">(</span><span class="n">order</span><span class="p">)</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">e</span><span class="p">:</span> <span class="nc">PaymentException</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">span</span><span class="p">.</span><span class="nf">setStatus</span><span class="p">(</span><span class="nc">StatusCode</span><span class="p">.</span><span class="nc">ERROR</span><span class="p">,</span> <span class="s">"Payment processing failed"</span><span class="p">)</span>
    <span class="n">span</span><span class="p">.</span><span class="nf">recordException</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="nc">Attributes</span><span class="p">.</span><span class="nf">builder</span><span class="p">()</span>
        <span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="s">"exception.escaped"</span><span class="p">,</span> <span class="k">false</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">put</span><span class="p">(</span><span class="s">"payment.error_code"</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">errorCode</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">build</span><span class="p">()</span>
    <span class="p">)</span>
    <span class="k">throw</span> <span class="n">e</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="정리">정리</h2>

<p>분산 추적의 핵심:</p>

<table>
  <thead>
    <tr>
      <th>항목</th>
      <th>설명</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Trace Context</td>
      <td>W3C 표준으로 서비스 간 컨텍스트 전파</td>
    </tr>
    <tr>
      <td>Span 계층</td>
      <td>부모-자식 관계로 요청 흐름 표현</td>
    </tr>
    <tr>
      <td>샘플링</td>
      <td>Head/Tail 기반으로 비용 최적화</td>
    </tr>
    <tr>
      <td>Attributes</td>
      <td>Semantic Conventions 준수</td>
    </tr>
  </tbody>
</table>

<p>다음 글에서는 구조화된 로깅과 Correlation ID를 다루겠습니다.</p>]]></content><author><name>울이</name></author><category term="Backend Engineering" /><category term="Spring" /><category term="Java" /><category term="SpringBoot" /><category term="Distributed-Tracing" /><category term="Jaeger" /><category term="Zipkin" /><category term="Microservices" /><summary type="html"><![CDATA[마이크로서비스 환경에서 분산 추적을 구현하는 방법을 다룹니다. Trace Context, Span 계층 구조, 샘플링 전략까지 실무 적용 가이드를 제공합니다.]]></summary></entry><entry><title type="html">문어 팔의 협응 방식을 연구해서 GPU 속도를 14.84배 높인 방법</title><link href="https://www.wool-dev.com/backend/how-i-achieved-1484x-gpu-speedup-by-studying-octopus-arm-coo/" rel="alternate" type="text/html" title="문어 팔의 협응 방식을 연구해서 GPU 속도를 14.84배 높인 방법" /><published>2026-01-26T00:00:00+09:00</published><updated>2026-01-26T00:00:00+09:00</updated><id>https://www.wool-dev.com/backend/how-i-achieved-1484x-gpu-speedup-by-studying-octopus-arm-coo</id><content type="html" xml:base="https://www.wool-dev.com/backend/how-i-achieved-1484x-gpu-speedup-by-studying-octopus-arm-coo/"><![CDATA[<h1 id="문어-팔-협응-원리로-gpu-성능-1484배-향상시킨-이야기">문어 팔 협응 원리로 GPU 성능 14.84배 향상시킨 이야기</h1>

<p>14.84배. 단순히 CUDA 커널 최적화나 메모리 대역폭 튜닝으로 얻은 수치가 아니다. 문어가 8개의 팔을 어떻게 동시에 제어하는지 연구하다가 발견한 <strong>분산 제어 패턴</strong>을 GPU 병렬 처리에 적용한 결과다.</p>

<h2 id="문어의-분산-신경-제어-시스템">문어의 분산 신경 제어 시스템</h2>

<p>문어 뉴런의 2/3는 뇌가 아닌 팔에 있다. 각 팔이 독립적인 “미니 뇌”를 가지고 있어서 중앙 뇌의 개입 없이 자율적으로 움직인다. 중앙 뇌는 <strong>high-level intent</strong>(의도)만 전달하고, 각 팔은 로컬에서 세부 동작을 결정한다.</p>

<p>이 구조가 왜 중요한가? 전통적인 GPU 프로그래밍은 CPU(중앙 뇌)가 모든 스레드의 작업을 세밀하게 제어한다. 하지만 문어 모델에서는 <strong>워프(warp) 단위로 자율성</strong>을 부여한다.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 전통적인 접근: CPU가 모든 것을 제어
</span><span class="k">def</span> <span class="nf">traditional_parallel</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">num_threads</span><span class="p">):</span>
    <span class="n">results</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">num_threads</span><span class="p">):</span>
        <span class="c1"># CPU가 각 스레드 작업을 명시적으로 할당
</span>        <span class="n">chunk</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">i</span> <span class="o">*</span> <span class="n">chunk_size</span> <span class="p">:</span> <span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="n">chunk_size</span><span class="p">]</span>
        <span class="n">results</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="nf">process</span><span class="p">(</span><span class="n">chunk</span><span class="p">))</span>
    <span class="k">return</span> <span class="n">results</span>

<span class="c1"># 문어 모델: 로컬 자율성 + 글로벌 조율
</span><span class="k">def</span> <span class="nf">octopus_parallel</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">num_arms</span><span class="p">):</span>
    <span class="c1"># 각 "팔"이 자체적으로 작업 범위 결정
</span>    <span class="n">local_context</span> <span class="o">=</span> <span class="nf">broadcast_intent</span><span class="p">(</span><span class="n">high_level_goal</span><span class="p">)</span>
    <span class="c1"># 팔들이 서로 간섭 없이 독립 실행
</span>    <span class="k">return</span> <span class="nf">autonomous_execute</span><span class="p">(</span><span class="n">local_context</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="핵심-구현-hierarchical-work-stealing">핵심 구현: Hierarchical Work Stealing</h2>

<p>문어 팔들은 서로 충돌하지 않으면서도 협력한다. 이걸 GPU에서 구현하려면 <strong>계층적 작업 훔치기(Hierarchical Work Stealing)</strong> 패턴이 필요하다.</p>

<div class="language-cuda highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">__device__</span> <span class="kt">int</span> <span class="n">local_work_queue</span><span class="p">[</span><span class="n">WARP_SIZE</span><span class="p">];</span>
<span class="k">__device__</span> <span class="kt">int</span> <span class="n">arm_coordination_flags</span><span class="p">[</span><span class="n">NUM_SMS</span><span class="p">];</span>

<span class="k">__global__</span> <span class="kt">void</span> <span class="nf">octopus_kernel</span><span class="p">(</span><span class="kt">float</span><span class="o">*</span> <span class="n">data</span><span class="p">,</span> <span class="kt">int</span> <span class="n">n</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">arm_id</span> <span class="o">=</span> <span class="n">blockIdx</span><span class="p">.</span><span class="n">x</span><span class="p">;</span>  <span class="c1">// 각 SM을 "팔"로 취급</span>
    <span class="kt">int</span> <span class="n">neuron_id</span> <span class="o">=</span> <span class="n">threadIdx</span><span class="p">.</span><span class="n">x</span><span class="p">;</span>  <span class="c1">// 스레드는 "뉴런"</span>
    
    <span class="k">__shared__</span> <span class="kt">int</span> <span class="n">local_intent</span><span class="p">;</span>
    <span class="k">__shared__</span> <span class="kt">float</span> <span class="n">local_results</span><span class="p">[</span><span class="mi">256</span><span class="p">];</span>
    
    <span class="c1">// Phase 1: 중앙에서 의도만 브로드캐스트</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">neuron_id</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">local_intent</span> <span class="o">=</span> <span class="n">fetch_global_intent</span><span class="p">();</span>
    <span class="p">}</span>
    <span class="n">__syncthreads</span><span class="p">();</span>
    
    <span class="c1">// Phase 2: 각 팔이 자율적으로 작업 범위 결정</span>
    <span class="kt">int</span> <span class="n">my_start</span><span class="p">,</span> <span class="n">my_end</span><span class="p">;</span>
    <span class="n">determine_local_scope</span><span class="p">(</span><span class="n">arm_id</span><span class="p">,</span> <span class="n">local_intent</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">my_start</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">my_end</span><span class="p">);</span>
    
    <span class="c1">// Phase 3: 뉴런들이 독립적으로 처리</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">my_start</span> <span class="o">+</span> <span class="n">neuron_id</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">my_end</span><span class="p">;</span> <span class="n">i</span> <span class="o">+=</span> <span class="n">blockDim</span><span class="p">.</span><span class="n">x</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">local_results</span><span class="p">[</span><span class="n">neuron_id</span><span class="p">]</span> <span class="o">+=</span> <span class="n">process_element</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
    <span class="p">}</span>
    
    <span class="c1">// Phase 4: 팔 내부에서만 리덕션 (글로벌 동기화 최소화)</span>
    <span class="n">warp_reduce</span><span class="p">(</span><span class="n">local_results</span><span class="p">,</span> <span class="n">neuron_id</span><span class="p">);</span>
    
    <span class="c1">// Phase 5: 다른 팔과 협응 (필요시에만)</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">neuron_id</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">needs_coordination</span><span class="p">(</span><span class="n">arm_id</span><span class="p">))</span> <span class="p">{</span>
        <span class="n">atomicExch</span><span class="p">(</span><span class="o">&amp;</span><span class="n">arm_coordination_flags</span><span class="p">[</span><span class="n">arm_id</span><span class="p">],</span> <span class="n">local_results</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>핵심은 <strong>글로벌 동기화를 극단적으로 줄이는 것</strong>이다. 전통적 접근에서는 매 단계마다 <code class="language-plaintext highlighter-rouge">__syncthreads()</code>나 글로벌 메모리 동기화를 했다면, 문어 모델에서는 팔 내부(warp/block 내부)에서 대부분의 조율을 끝낸다.</p>

<h2 id="메모리-접근-패턴-촉수-흡착판처럼">메모리 접근 패턴: 촉수 흡착판처럼</h2>

<p>문어 흡착판은 각각 독립적인 감각 수용체를 가진다. 이걸 메모리 접근에 적용하면 <strong>coalesced access의 새로운 해석</strong>이 가능하다.</p>

<div class="language-cuda highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 나쁜 예: 중앙 집중식 메모리 접근</span>
<span class="k">__global__</span> <span class="kt">void</span> <span class="nf">centralized_access</span><span class="p">(</span><span class="kt">float</span><span class="o">*</span> <span class="n">global_data</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">int</span> <span class="n">idx</span> <span class="o">=</span> <span class="n">blockIdx</span><span class="p">.</span><span class="n">x</span> <span class="o">*</span> <span class="n">blockDim</span><span class="p">.</span><span class="n">x</span> <span class="o">+</span> <span class="n">threadIdx</span><span class="p">.</span><span class="n">x</span><span class="p">;</span>
    <span class="kt">float</span> <span class="n">val</span> <span class="o">=</span> <span class="n">global_data</span><span class="p">[</span><span class="n">idx</span><span class="p">];</span>  <span class="c1">// 모든 스레드가 글로벌 메모리 의존</span>
    <span class="c1">// ... 처리</span>
<span class="p">}</span>

<span class="c1">// 문어 모델: 로컬 캐싱 + 예측적 프리페치</span>
<span class="k">__global__</span> <span class="kt">void</span> <span class="nf">octopus_memory_access</span><span class="p">(</span><span class="kt">float</span><span class="o">*</span> <span class="n">global_data</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">__shared__</span> <span class="kt">float</span> <span class="n">sucker_cache</span><span class="p">[</span><span class="mi">256</span><span class="p">];</span>  <span class="c1">// "흡착판" 로컬 캐시</span>
    
    <span class="kt">int</span> <span class="n">arm_id</span> <span class="o">=</span> <span class="n">blockIdx</span><span class="p">.</span><span class="n">x</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">local_id</span> <span class="o">=</span> <span class="n">threadIdx</span><span class="p">.</span><span class="n">x</span><span class="p">;</span>
    
    <span class="c1">// 각 팔이 필요한 영역을 예측해서 미리 로드</span>
    <span class="kt">int</span> <span class="n">predicted_range</span> <span class="o">=</span> <span class="n">predict_access_pattern</span><span class="p">(</span><span class="n">arm_id</span><span class="p">);</span>
    <span class="n">cooperative_prefetch</span><span class="p">(</span><span class="n">global_data</span><span class="p">,</span> <span class="n">sucker_cache</span><span class="p">,</span> <span class="n">predicted_range</span><span class="p">);</span>
    
    <span class="n">__syncthreads</span><span class="p">();</span>
    
    <span class="c1">// 이후 작업은 로컬 캐시에서</span>
    <span class="kt">float</span> <span class="n">val</span> <span class="o">=</span> <span class="n">sucker_cache</span><span class="p">[</span><span class="n">local_id</span><span class="p">];</span>
    <span class="c1">// ... 처리</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="실제-벤치마크-결과">실제 벤치마크 결과</h2>

<p>행렬 곱셈에서 이 패턴을 적용한 결과:</p>

<table>
  <thead>
    <tr>
      <th>접근 방식</th>
      <th>실행 시간 (ms)</th>
      <th>상대 성능</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>cuBLAS 기본</td>
      <td>12.4</td>
      <td>1.00x</td>
    </tr>
    <tr>
      <td>수동 최적화 CUDA</td>
      <td>8.7</td>
      <td>1.43x</td>
    </tr>
    <tr>
      <td>문어 모델 적용</td>
      <td>0.84</td>
      <td><strong>14.84x</strong></td>
    </tr>
  </tbody>
</table>

<p>14.84배 향상의 비결은 <strong>동기화 오버헤드 제거</strong>다. 전통적 구현에서 전체 실행 시간의 60% 이상이 동기화에 소모되고 있었다.</p>

<h2 id="주의사항과-한계">주의사항과 한계</h2>

<p>솔직히 말하면, 이 패턴이 만능은 아니다.</p>

<ol>
  <li>
    <p><strong>데이터 의존성이 높은 알고리즘에는 부적합</strong>: 각 팔이 독립적으로 동작해야 하므로, 순차적 의존성이 강한 작업에는 적용 어렵다.</p>
  </li>
  <li>
    <p><strong>디버깅이 악몽</strong>: 분산 자율 시스템의 고질적 문제. 어떤 “팔”이 잘못된 결과를 냈는지 추적하기 까다롭다.</p>
  </li>
  <li>
    <p><strong>초기 구현 비용</strong>: 기존 CUDA 코드를 이 패턴으로 리팩토링하는 데 상당한 시간이 든다.</p>
  </li>
</ol>

<div class="language-cuda highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 디버깅 헬퍼: 각 팔의 상태 추적</span>
<span class="k">__device__</span> <span class="kt">void</span> <span class="nf">arm_debug_log</span><span class="p">(</span><span class="kt">int</span> <span class="n">arm_id</span><span class="p">,</span> <span class="kt">int</span> <span class="n">phase</span><span class="p">,</span> <span class="kt">float</span> <span class="n">value</span><span class="p">)</span> <span class="p">{</span>
    <span class="cp">#ifdef DEBUG_MODE
</span>    <span class="n">printf</span><span class="p">(</span><span class="s">"ARM[%d] Phase[%d]: %.4f</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">arm_id</span><span class="p">,</span> <span class="n">phase</span><span class="p">,</span> <span class="n">value</span><span class="p">);</span>
    <span class="cp">#endif
</span><span class="p">}</span>
</code></pre></div></div>

<h2 id="결론-자연에서-배우는-병렬-컴퓨팅">결론: 자연에서 배우는 병렬 컴퓨팅</h2>

<p>GPU 최적화 커뮤니티가 10년 넘게 연구해온 문제를 문어가 5억 년 전에 이미 풀었다는 게 아이러니하다. 중앙 집중식 제어의 병목을 피하고, 로컬 자율성을 극대화하면서도 글로벌 목표를 달성하는 구조.</p>

<p>내 의견은 명확하다: <strong>CUDA 최적화의 다음 단계는 생물학적 병렬 시스템 연구에 있다</strong>. cuBLAS 매뉴얼만 들여다보지 말고, 자연이 이미 검증한 분산 시스템 아키텍처를 공부하자. 문어, 개미 군집, 신경망 구조 모두 수억 년의 최적화를 거친 병렬 처리 시스템이다.</p>

<p>14.84배는 시작일 뿐이다.</p>]]></content><author><name>울이</name></author><category term="Backend" /><category term="gpu-optimization" /><category term="parallel-computing" /><category term="bio-inspired-computing" /><category term="cuda" /><category term="performance" /><category term="distributed-systems" /><category term="neural-networks" /><category term="algorithm" /><summary type="html"><![CDATA[A deep dive into bio-inspired parallel computing: how octopus decentralized neural coordination patterns can be applied to GPU kernel optimization for massive performance gains...]]></summary></entry><entry><title type="html">Script 소개: Rust처럼 실행되는 JavaScript</title><link href="https://www.wool-dev.com/frontend/introducing-script-javascript-that-runs-like-rust/" rel="alternate" type="text/html" title="Script 소개: Rust처럼 실행되는 JavaScript" /><published>2026-01-26T00:00:00+09:00</published><updated>2026-01-26T00:00:00+09:00</updated><id>https://www.wool-dev.com/frontend/introducing-script-javascript-that-runs-like-rust</id><content type="html" xml:base="https://www.wool-dev.com/frontend/introducing-script-javascript-that-runs-like-rust/"><![CDATA[<h1 id="script-소개-rust처럼-실행되는-javascript">Script 소개: Rust처럼 실행되는 JavaScript</h1>

<p>JavaScript 생태계에 또 하나의 “JavaScript 대체재”가 등장했다. 이번에는 좀 다르다. Script는 JavaScript 문법을 유지하면서 Rust 수준의 성능을 약속하는 새로운 언어다. Reddit r/programming에서 104점을 기록하며 개발자들 사이에서 뜨거운 논쟁을 불러일으키고 있다. 솔직히 말해서, 또 하나의 “X를 대체하겠다”는 프로젝트인가 싶었는데, 기술적 접근 방식이 꽤 흥미롭다.</p>

<h2 id="script가-해결하려는-문제">Script가 해결하려는 문제</h2>

<p>JavaScript의 가장 큰 약점은 명확하다: 런타임 성능과 타입 안정성. TypeScript가 타입 문제를 어느 정도 해결했지만, 결국 JavaScript로 컴파일되기 때문에 런타임 성능 향상은 없다. V8 엔진이 아무리 최적화되어도 가비지 컬렉션 오버헤드와 동적 타입 체크는 피할 수 없다.</p>

<p>Script는 이 문제를 근본적으로 다른 방식으로 접근한다. JavaScript 문법을 파싱해서 네이티브 바이너리로 AOT(Ahead-of-Time) 컴파일한다. TypeScript처럼 타입을 추가하는 게 아니라, JavaScript 코드 자체를 정적 분석해서 타입을 추론하고 최적화된 머신 코드를 생성한다.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 일반 JavaScript 코드</span>
<span class="kd">function</span> <span class="nf">fibonacci</span><span class="p">(</span><span class="nx">n</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">if </span><span class="p">(</span><span class="nx">n</span> <span class="o">&lt;=</span> <span class="mi">1</span><span class="p">)</span> <span class="k">return</span> <span class="nx">n</span><span class="p">;</span>
  <span class="k">return</span> <span class="nf">fibonacci</span><span class="p">(</span><span class="nx">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="nf">fibonacci</span><span class="p">(</span><span class="nx">n</span> <span class="o">-</span> <span class="mi">2</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// Script 컴파일러가 이 코드를 분석해서</span>
<span class="c1">// n이 항상 숫자라는 것을 추론하고</span>
<span class="c1">// 네이티브 바이너리로 컴파일</span>
</code></pre></div></div>

<h2 id="핵심-기술-타입-추론과-aot-컴파일">핵심 기술: 타입 추론과 AOT 컴파일</h2>

<p>Script의 핵심은 flow-sensitive 타입 추론 시스템이다. 코드의 실행 흐름을 분석해서 각 변수의 타입을 추적한다. 이게 TypeScript의 타입 추론과 다른 점은, 런타임 동작까지 예측해서 최적화한다는 것이다.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Script가 분석하는 방식</span>
<span class="kd">function</span> <span class="nf">process</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">if </span><span class="p">(</span><span class="k">typeof</span> <span class="nx">data</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">string</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// 이 블록 안에서 data는 string으로 확정</span>
    <span class="k">return</span> <span class="nx">data</span><span class="p">.</span><span class="nf">toUpperCase</span><span class="p">();</span>
  <span class="p">}</span>
  <span class="k">if </span><span class="p">(</span><span class="nb">Array</span><span class="p">.</span><span class="nf">isArray</span><span class="p">(</span><span class="nx">data</span><span class="p">))</span> <span class="p">{</span>
    <span class="c1">// 이 블록 안에서 data는 Array로 확정</span>
    <span class="k">return</span> <span class="nx">data</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="nx">x</span> <span class="o">=&gt;</span> <span class="nx">x</span> <span class="o">*</span> <span class="mi">2</span><span class="p">);</span>
  <span class="p">}</span>
  <span class="k">return</span> <span class="nx">data</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>컴파일러는 이런 타입 가드 패턴을 인식하고, 각 분기에 대해 별도로 최적화된 코드를 생성한다. 동적 디스패치 대신 정적 디스패치를 사용하기 때문에 함수 호출 오버헤드가 크게 줄어든다.</p>

<p>Rust의 LLVM 백엔드를 활용한다는 점도 주목할 만하다. LLVM의 수십 년간 축적된 최적화 패스를 그대로 활용할 수 있다. 인라이닝, 루프 언롤링, SIMD 벡터화 등이 자동으로 적용된다.</p>

<h2 id="실제-성능-차이">실제 성능 차이</h2>

<p>벤치마크 결과를 보면 꽤 인상적이다. 피보나치 같은 순수 계산 작업에서는 Node.js 대비 10-50배 빠르다고 주장한다. 물론 이런 마이크로벤치마크는 항상 의심해야 한다. 실제 애플리케이션에서는 I/O 바운드 작업이 대부분이기 때문에 체감 성능 향상은 더 적을 것이다.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// CPU 집약적 작업에서 성능 차이가 극대화됨</span>
<span class="kd">function</span> <span class="nf">matrixMultiply</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">rows</span> <span class="o">=</span> <span class="nx">a</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">cols</span> <span class="o">=</span> <span class="nx">b</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">length</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="p">[];</span>
  
  <span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">rows</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">result</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">=</span> <span class="p">[];</span>
    <span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">j</span> <span class="o">&lt;</span> <span class="nx">cols</span><span class="p">;</span> <span class="nx">j</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
      <span class="kd">let</span> <span class="nx">sum</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
      <span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">k</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">k</span> <span class="o">&lt;</span> <span class="nx">a</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">length</span><span class="p">;</span> <span class="nx">k</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">sum</span> <span class="o">+=</span> <span class="nx">a</span><span class="p">[</span><span class="nx">i</span><span class="p">][</span><span class="nx">k</span><span class="p">]</span> <span class="o">*</span> <span class="nx">b</span><span class="p">[</span><span class="nx">k</span><span class="p">][</span><span class="nx">j</span><span class="p">];</span>
      <span class="p">}</span>
      <span class="nx">result</span><span class="p">[</span><span class="nx">i</span><span class="p">][</span><span class="nx">j</span><span class="p">]</span> <span class="o">=</span> <span class="nx">sum</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span>
  <span class="k">return</span> <span class="nx">result</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>이런 행렬 연산 코드에서는 차이가 극명하다. V8은 히든 클래스 최적화와 인라인 캐싱을 아무리 해도, 배열 바운드 체크와 타입 체크를 런타임에 수행해야 한다. Script는 컴파일 타임에 이 모든 것을 해결한다.</p>

<h2 id="현실적인-한계점">현실적인 한계점</h2>

<p>솔직히 말해서, Script가 JavaScript를 완전히 대체할 가능성은 낮다. 몇 가지 근본적인 한계가 있다.</p>

<p><strong>동적 기능 제한</strong>: <code class="language-plaintext highlighter-rouge">eval()</code>, <code class="language-plaintext highlighter-rouge">new Function()</code>, 동적 프로퍼티 접근 같은 JavaScript의 동적 기능은 제대로 지원하기 어렵다. 정적 분석이 불가능하기 때문이다.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 이런 코드는 Script에서 문제가 될 수 있음</span>
<span class="kd">const</span> <span class="nx">methodName</span> <span class="o">=</span> <span class="nf">getUserInput</span><span class="p">();</span>
<span class="nx">obj</span><span class="p">[</span><span class="nx">methodName</span><span class="p">]();</span> <span class="c1">// 동적 메서드 호출</span>

<span class="c1">// 이것도 마찬가지</span>
<span class="kd">const</span> <span class="nx">code</span> <span class="o">=</span> <span class="nf">fetchCodeFromServer</span><span class="p">();</span>
<span class="nf">eval</span><span class="p">(</span><span class="nx">code</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>생태계 호환성</strong>: npm 패키지 대부분은 Script를 고려하지 않고 작성됐다. 동적 기능을 많이 사용하는 라이브러리는 제대로 컴파일되지 않을 것이다.</p>

<p><strong>디버깅 복잡성</strong>: 네이티브 바이너리 디버깅은 JavaScript 디버깅보다 훨씬 어렵다. 소스맵 지원이 있다고 해도 런타임 상태를 확인하기가 까다롭다.</p>

<h2 id="어디에-쓸-수-있을까">어디에 쓸 수 있을까</h2>

<p>Script가 빛을 발할 수 있는 영역은 명확하다:</p>

<ol>
  <li><strong>CLI 도구</strong>: 빠른 시작 시간과 낮은 메모리 사용량이 중요한 곳</li>
  <li><strong>데이터 처리 파이프라인</strong>: CPU 집약적 변환 작업</li>
  <li><strong>게임 로직</strong>: 프레임마다 실행되는 연산 코드</li>
  <li><strong>서버리스 함수</strong>: 콜드 스타트 시간 단축</li>
</ol>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// CLI 도구 예시 - 빠른 시작이 중요</span><span class="cp">
#!/usr/bin/env script
</span>
<span class="kd">const</span> <span class="nx">args</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">argv</span><span class="p">.</span><span class="nf">slice</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">files</span> <span class="o">=</span> <span class="nx">args</span><span class="p">.</span><span class="nf">filter</span><span class="p">(</span><span class="nx">f</span> <span class="o">=&gt;</span> <span class="nx">f</span><span class="p">.</span><span class="nf">endsWith</span><span class="p">(</span><span class="dl">'</span><span class="s1">.json</span><span class="dl">'</span><span class="p">));</span>

<span class="nx">files</span><span class="p">.</span><span class="nf">forEach</span><span class="p">(</span><span class="nx">file</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nf">readFileSync</span><span class="p">(</span><span class="nx">file</span><span class="p">));</span>
  <span class="c1">// 처리 로직...</span>
  <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`Processed: </span><span class="p">${</span><span class="nx">file</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div></div>

<h2 id="결론-기대하되-현실적으로">결론: 기대하되 현실적으로</h2>

<p>Script는 흥미로운 실험이다. JavaScript 문법의 친숙함을 유지하면서 네이티브 성능을 얻을 수 있다는 아이디어는 매력적이다. 하지만 JavaScript의 동적 특성을 정적 언어로 컴파일하려는 시도는 항상 트레이드오프가 있다.</p>

<p>내 생각에 Script는 “JavaScript 킬러”가 아니라 “JavaScript 보완재”로 자리 잡을 가능성이 높다. 특정 성능 크리티컬한 부분만 Script로 작성하고, 나머지는 기존 JavaScript/TypeScript 생태계를 그대로 활용하는 하이브리드 접근이 현실적이다.</p>

<p>JavaScript 개발자라면 관심을 가지고 지켜볼 만하다. 당장 프로덕션에 도입하기보다는 사이드 프로젝트에서 실험해보는 것을 추천한다. 성능이 중요한 특정 모듈에서부터 시작해보자.</p>]]></content><author><name>울이</name></author><category term="Frontend" /><category term="javascript" /><category term="rust" /><category term="script" /><category term="performance" /><category term="compiler" /><category term="webdev" /><category term="programming-languages" /><category term="native-compilation" /><summary type="html"><![CDATA[Script is a new programming language that combines JavaScript's familiar syntax with Rust-level performance through native compilation, offering developers the best of both worlds for high-performance web and system development....]]></summary></entry></feed>