<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>khseon7 님의 블로그</title>
    <link>https://khseon7.tistory.com/</link>
    <description>인공지능과 관련된 이것저것 정리해보는 블로그</description>
    <language>ko</language>
    <pubDate>Wed, 13 May 2026 03:36:35 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>khseon7</managingEditor>
    <image>
      <title>khseon7 님의 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/7501394/attach/6accb78fb20c40678f8cff959b11bee9</url>
      <link>https://khseon7.tistory.com</link>
    </image>
    <item>
      <title>[EKS/Istio] 버전 기반 라우팅을 넘어 실전 카나리 배포와 모니터링 환경 구축하기</title>
      <link>https://khseon7.tistory.com/40</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난 포스팅에서는 AWS EKS 클러스터 위에 서비스를 배포하고, 별도의 라우팅 설정 없이 트래픽이 흐르는 모습을 관찰했다. 당시 Istio 내부 설정이 없어 EKS 내부의 쿠버네티스 서비스는 모든 파드에 트래픽을 균등하게 배분하는 라운드 로빈 방식으로 동작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 이 기본 모드를 해제하고, 엔지니어가 직접 가중치를 조정해보는 과정을 기록한다. Istio의 VirtualService를 적용하여 트래픽의 비율을 정교하게 제어하는 카나리 배포를 수행하고, 이 통제가 의도대로 이루어지는지 Prometheus와 Grafana를 통해 기술적 근거를 확보해 보겠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 카나리 배포(Canary Deployment)란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카나리 배포는 새로운 소프트웨어 버전을 전체 사용자에게 한꺼번에 배포하기 전, 일부 사용자에게만 소량의 트래픽을 노출시켜 안정성을 검증하는 점진적 배포 전략이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;핵심 메커니즘&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;그룹 분리&lt;/b&gt;: 동일한 서비스 이름 아래 기존 버전(v1)과 신규 버전(v2)의 파드를 동시에 운영&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가중치 제어&lt;/b&gt;: 시스템 설정을 통해 트래픽의 비율을 결정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;지표 분석&lt;/b&gt;: Prometheus와 Grafana 같은 모니터링 도구를 사용하여 v2의 성공률, 응답 속도 등을 실시간으로 감시&lt;/li&gt;
&lt;li&gt;&lt;b&gt;최종 전환 또는 롤백&lt;/b&gt;: v2이 안정적이면 가중치를 높여 100%까지 전환하고, 문제가 발견되면 즉시 가중치를 0으로 낮춰 장애 차단&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;장점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;리스크의 계량화&lt;/b&gt;: 신규 버전에 결함이 있더라도 &lt;u&gt;피해 범위를 설정한 가중치 이내로 한정&lt;/u&gt;할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 기반 의사결정&lt;/b&gt;: 실제 운영 환경에서 수집된 에러율과 지연 시간 데이터를 근거로 배포 지속 여부를 결정한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;무중단 배포&lt;/b&gt;: 사용자는 서비스 중단을 경험하지 않으며, 백그라운드에서 조용히 시스템의 세대교체가 일어난다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;EKS와 Istio 환경에서의 구현&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;DestinationRule&lt;/b&gt;: 파드의 라벨을 기준으로 v1, v2라는 &lt;u&gt;논리적 그룹을 정의&lt;/u&gt;한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;VirtualService&lt;/b&gt;: 각 그룹으로 향할 &lt;u&gt;트래픽의 구체적인 가중치를 명시&lt;/u&gt;한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 카나리 배포 진행 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 라운드 로빈 방식으로 흐르던 트래픽을 제어하기 위해, 다음과 같은 YAML 파일을 작성했다.&lt;/p&gt;
&lt;pre id=&quot;code_1778573193177&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: reviews
spec:
  host: reviews
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
  - reviews
  http:
  - route:
    - destination:
        host: reviews
        subset: v1
      weight: 90
    - destination:
        host: reviews
        subset: v2
      weight: 10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 YAML 파일을 확인해 보면 DestinationRule에서는 reviews 서비스의 subsets가 v1, v2로만 정의하고 있습니다. 이로 인해 기존 bookinfo.yaml 에 포함되어 있는 v3는 라우팅 대상에서 완전히 제외되어 격리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, VirtualService를 통해 정의된 각 subset에 대해 90:10의 가중치로 트래픽을 분리하도록 설정했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;질문: 이미 배포된 서비스에 영향 없이 트래픽 비율만 바꿀 수 있나?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;답변: 쿠버네티스의 선언적 방식 덕분에 VirtualService 파일에서 weight 필드만 수정하여 apply 하면, 파드 재시작 없이도 Envoy 프락시들이 즉시 새로운 교통 지침을 수행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;질문: 새로운 서비스는 기존 환경에 영향 없이 어떻게 추가하나?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;답변: EKS 클러스터에 version: v2 라벨을 가진 파드를 독립적으로 배치한 뒤 Istio의 DestinationRule로 이를 논리적 그룹(Subset)으로 정의하고, 최종적으로 VirtualService를 통해 새 그룹으로 흐를 트래픽 비중을 결정함으로써 기존 환경에 영향 없이 새 버전을 준비시키는 과정이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 모니터링을 통한 가시성 확보&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 설정을 통해 실제 productpage에 들어가서 새로고침을 반복해 본 결과, 화면에 나타나는 리뷰의 별점에서 빨간색만 표시되지 않는 것을 확인할 수 있었다. v3 파드는 클러스터 내에 엄연히 존재하지만, DestinationRule의 서브셋 정의와 VirtualService의 라우팅 경로에서 의도적으로 제외했기에 단 한 건의 트래픽도 도달하지 않았음을 실제 화면을 통해 1차적으로 증명한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 보이지 않는 인프라 하단의 데이터를 Grafana 대시보드로 시각화해 보았다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;SSH 터널링&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS EKS 환경 내부의 메트릭을 관찰하기 위해 SSH 터널링 기법을 적용했다. istio-manager(EC2)와 내 로컬 PC를 연결하여, 클러스터 내부의 Grafana 대시보드를 내 로컬 PC로 가져왔다. 이후 지표를 분석한 결과 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-12 오후 5.23.19.png&quot; data-origin-width=&quot;2974&quot; data-origin-height=&quot;914&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhJSz3/dJMcahLaHeH/0wl1TGRA5Xnt7KL2cZZfH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhJSz3/dJMcahLaHeH/0wl1TGRA5Xnt7KL2cZZfH1/img.png&quot; data-alt=&quot;실제 트래픽 분산(v1:v2:v3=90:10:0) 이후 대시보드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhJSz3/dJMcahLaHeH/0wl1TGRA5Xnt7KL2cZZfH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhJSz3%2FdJMcahLaHeH%2F0wl1TGRA5Xnt7KL2cZZfH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2974&quot; height=&quot;914&quot; data-filename=&quot;스크린샷 2026-05-12 오후 5.23.19.png&quot; data-origin-width=&quot;2974&quot; data-origin-height=&quot;914&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실제 트래픽 분산(v1:v2:v3=90:10:0) 이후 대시보드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;정교한 트래픽 분산 비율&lt;/b&gt;: Incoming Requests By Destination 패널을 보면 reviews-v1은 약 1.5 ops/s를 기록하는 반면, reviews-v2는 0.1~0.2 ops/s 내외의 낮은 수치를 유지하는 것을 보면 위에서 설정한 90:10의 가중치가 인프라 하단에서 정확히 실현되고 있음을 보여준다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;시스템 안정성 증명&lt;/b&gt;: Incoming Success Rate 지표에서 두 버전 모두 100%의 성공률을 기록하고 있다. 이를 통해 신규 버전인 v2가 실시간 트래픽을 받았음에도 결함 없이 동작하고 있다는 것을 확인했다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;v3의 완벽한 격리&lt;/b&gt;: 대시보드 하단 범례를 확인하면 reviews-v3의 수치는 0에 수렴하여 선조차 보이지 않는 것을 보면 위 라우팅 설정이 v3로 가는 길을 완벽히 차단했음을 증명한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;다중 가중치 설정을 통한 트래픽 핸들링&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카나리 배포가 단순히 두 버전 사이의 전환을 넘어, 여러 버전의 파드에 대해 엔지니어가 의도한 대로 트래픽을 쪼개어 보낼 수 있다는 것을 확인하기 위해, Destination의 subset에 v3를 추가하고 VirtualService의 subset 추가 및 가중치를 v1(60%):v2(30%):v3(10%)으로 설정하고 그 결과를 관찰했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBBkve/dJMcagFwd1U/KxVgvtke1GDfDJBzTFPKT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBBkve/dJMcagFwd1U/KxVgvtke1GDfDJBzTFPKT0/img.png&quot; data-alt=&quot;실제 트래픽 분산(v1:v2:v3=60:30:10) 이후 대시보드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBBkve/dJMcagFwd1U/KxVgvtke1GDfDJBzTFPKT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBBkve%2FdJMcagFwd1U%2FKxVgvtke1GDfDJBzTFPKT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2048&quot; height=&quot;614&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실제 트래픽 분산(v1:v2:v3=60:30:10) 이후 대시보드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;의도한 비율대로 정렬된 트래픽 곡선&lt;/b&gt;: Incoming Requests By Destination 패널을 보면, 가장 높은 위치의 초록색 선(v1), 중간의 노란색 선(v2), 그리고 가장 낮은 파란색 선(v3)이 뚜렷한 층을 이루고 있다. 이는 각 버전에 흐르는 요청 수가 설정한 비율을 정확히 추종하여 계단식 구조를 형성하고 있음을 보여준다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다중 버전의 동시 검증&lt;/b&gt;: 이전 단계에서 격리했던 v3까지 포함하여 세 버전을 동시에 운영하면서도, 신규 버전들(v2, v3)에 노출되는 트래픽을 각각 30%와 10%로 차등 제어함으로써 리스크를 분산했다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;지표의 일관성&lt;/b&gt;: 트래픽 양은 서로 다르지만, Incoming Success Rate 패널에서 세 버전 모두 100%의 성공률을 기록하고 있다. 이는 가중치 변화와 상관없이 전체 시스템이 안정적인 상태를 유지하고 있는 것을 확인할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;후기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 실습은 AWS EKS와 Istio를 활용해 인프라의 기본 설정을 넘어 엔지니어의 명시적 통제권을 확보하는 과정이었다. VirtualService 가중치 설정을 통해 트래픽 흐름을 직접 설계하고, 해당 결과를 Prometheus/Grafana를 통해 설계가 시스템 하단에 정확히 투영되었음을 확인했다. 다음번에는 단순 배포를 넘어, 지표를 근거로 안정성을 확언할 수 있는 역량을 강화하기 위해 &lt;b&gt;지표 기반 자동 롤백&lt;/b&gt;과 &lt;b&gt;조건부 라우팅&lt;/b&gt;을 실습하고 결과를 공유할 예정이다.&lt;/p&gt;</description>
      <category>코딩/k8s</category>
      <category>AWS</category>
      <category>Canary Deployment</category>
      <category>EKS</category>
      <category>Grafana</category>
      <category>istio</category>
      <category>Prometheus</category>
      <category>service mesh</category>
      <author>khseon7</author>
      <guid isPermaLink="true">https://khseon7.tistory.com/40</guid>
      <comments>https://khseon7.tistory.com/40#entry40comment</comments>
      <pubDate>Tue, 12 May 2026 17:44:58 +0900</pubDate>
    </item>
    <item>
      <title>[EKS/Istio] 서비스 메시와 Istio를 활용한 버전 기반 라우팅 실습</title>
      <link>https://khseon7.tistory.com/39</link>
      <description>&lt;figure id=&quot;og_1778483878415&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;서비스 메시란? - 서비스 메시 설명 - AWS&quot; data-og-description=&quot;서비스 메시란 무엇이고 비즈니스에서 서비스 메시를 사용하는 방법 및 이유와 AWS를 통해 서비스 메시를 사용하는 방법을 알아봅니다.&quot; data-og-host=&quot;aws.amazon.com&quot; data-og-source-url=&quot;https://aws.amazon.com/ko/what-is/service-mesh/&quot; data-og-url=&quot;https://aws.amazon.com/ko/what-is/service-mesh/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/BqHpY/dJMb87ga3pu/P7LgtEF84FjYq0p0dLZeH1/img.png?width=1024&amp;amp;height=557&amp;amp;face=0_0_1024_557,https://scrap.kakaocdn.net/dn/cimLNo/dJMb87N08AA/hI8YvUPzRwulsD8dTzKLtK/img.png?width=1024&amp;amp;height=341&amp;amp;face=0_0_1024_341&quot;&gt;&lt;a href=&quot;https://aws.amazon.com/ko/what-is/service-mesh/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://aws.amazon.com/ko/what-is/service-mesh/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/BqHpY/dJMb87ga3pu/P7LgtEF84FjYq0p0dLZeH1/img.png?width=1024&amp;amp;height=557&amp;amp;face=0_0_1024_557,https://scrap.kakaocdn.net/dn/cimLNo/dJMb87N08AA/hI8YvUPzRwulsD8dTzKLtK/img.png?width=1024&amp;amp;height=341&amp;amp;face=0_0_1024_341');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;서비스 메시란? - 서비스 메시 설명 - AWS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;서비스 메시란 무엇이고 비즈니스에서 서비스 메시를 사용하는 방법 및 이유와 AWS를 통해 서비스 메시를 사용하는 방법을 알아봅니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;aws.amazon.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1778485808885&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;istio/samples/bookinfo/platform/kube/bookinfo.yaml at master &amp;middot; istio/istio&quot; data-og-description=&quot;Connect, secure, control, and observe services. Contribute to istio/istio development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/istio/istio/blob/master/samples/bookinfo/platform/kube/bookinfo.yaml&quot; data-og-url=&quot;https://github.com/istio/istio/blob/master/samples/bookinfo/platform/kube/bookinfo.yaml&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bmdqQk/dJMb8TCegtw/2ENoBGLK2Kxj7lDLp3DxY1/img.png?width=3360&amp;amp;height=1676&amp;amp;face=0_0_3360_1676,https://scrap.kakaocdn.net/dn/VHQlB/dJMb8QeqJmu/pKBbzIqsBAx0OwKV2Jw2D0/img.png?width=3360&amp;amp;height=1676&amp;amp;face=0_0_3360_1676&quot;&gt;&lt;a href=&quot;https://github.com/istio/istio/blob/master/samples/bookinfo/platform/kube/bookinfo.yaml&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/istio/istio/blob/master/samples/bookinfo/platform/kube/bookinfo.yaml&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bmdqQk/dJMb8TCegtw/2ENoBGLK2Kxj7lDLp3DxY1/img.png?width=3360&amp;amp;height=1676&amp;amp;face=0_0_3360_1676,https://scrap.kakaocdn.net/dn/VHQlB/dJMb8QeqJmu/pKBbzIqsBAx0OwKV2Jw2D0/img.png?width=3360&amp;amp;height=1676&amp;amp;face=0_0_3360_1676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;istio/samples/bookinfo/platform/kube/bookinfo.yaml at master &amp;middot; istio/istio&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Connect, secure, control, and observe services. Contribute to istio/istio development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얼마 전 참여했던 Dev Meetup에서 가장 흥미롭게 들었던 부분은 Service Mesh 중 Istio 였다. &quot;애플리케이션 코드 수정 없이 네트워크를 제어한다&quot;는 개념은 매우 매력적인 주제였다. Meetup에서 간단히 이론을 듣는 것에 그치지 않고, 직접 AWS EKS 환경 위에 Istio를 올리고 istio에서 제공하는 bookinfo를 바탕으로 3개의 서비스 버전을 제어하며 가졌던 의문과 해답들을 정리해본다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 서비스 메시(Service Mesh)란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 메시는 마이크로서비스 아키텍처(MSA)에서 각 서비스 간의 통신을 관리하기 위한 전용 소프트웨어 계층으로, 애플리케이션의 비즈니스 로직과 분리되어 네트워크 통신을 제어하며, 가시성, 보안, 안정성을 제공한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;341&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqe2Wt/dJMcagFvewx/jKnCmizJdMr4p5jdngHhlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqe2Wt/dJMcagFvewx/jKnCmizJdMr4p5jdngHhlK/img.png&quot; data-alt=&quot;https://d2908q01vomqb2.cloudfront.net/1b6453892473a467d07372d45eb05abc2031647a/2018/11/28/appmesh-proxy-1024x341.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqe2Wt/dJMcagFvewx/jKnCmizJdMr4p5jdngHhlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbqe2Wt%2FdJMcagFvewx%2FjKnCmizJdMr4p5jdngHhlK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;731&quot; height=&quot;243&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;341&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://d2908q01vomqb2.cloudfront.net/1b6453892473a467d07372d45eb05abc2031647a/2018/11/28/appmesh-proxy-1024x341.png&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;주요 기능 및 이점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;서비스 검색&lt;/b&gt;: 메시 내 서비스들을 동적으로 추적하여 서비스 위치에 관계없이 서로를 찾고 통신할 수 있게 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;로드 밸런싱&lt;/b&gt;: 요청을 여러 인스턴스에 지능적으로 분산하여 리소스 활용도를 최적화한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;트래픽 관리&lt;/b&gt;: 카나리 배포, 요청 미러링 등을 통해 안전한 배포와 테스트를 지원한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보안&lt;/b&gt;: 상호 TLS 암호화를 통해 데이터 기밀성을 보장하고, 서비스 간 인증 및 권한 부여를 처리한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모니터링 및 관찰성&lt;/b&gt;: 지연 시간, 오류율 등의 지표 수집과 분산 추적을 통해 시스템 상태를 상세히 파악한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;작동 원리&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;데이터 영역&lt;/b&gt;: 각 서비스 옆에 사이드카 형태로 배치된 프록시들로 구성된다. 실제 서비스 간 모든 트래픽을 가로채고 전달하는 역할&lt;/li&gt;
&lt;li&gt;&lt;b&gt;제어 영역&lt;/b&gt;: 관리자가 정책과 구성을 정의하는 중앙 관리 계층으로 정의된 설정을 데이터 영역의 프록시들에 배포하여 동작을 제어한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;도입 시 고려사항&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 플레인 리소스 (사이드카 오버헤드)
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;메모리 및 CPU 소모&lt;/b&gt;: 서비스 메시는 각 마이크로서비스마다 사이드카 프록시를 하나씩 배치하는데 이 때문에 서비스가 많을 경우 전체 클러스터에서 무시할 수 없는 수준의 인프라 비용이 추가된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;네트워크 지연&lt;/b&gt;: 트래픽이 반드시 프록시를 거쳐야 하므로, 홉이 늘어남에 따라 미세한 네트워크 지연이 발생한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;컨트롤 플레인 부하
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;설정 배포 부하&lt;/b&gt;: 마이크로서비스의 개수가 수천 개로 늘어나면, 컨트롤 플레인이 모든 사이드카 프록시에 실시간으로 설정을 전파하는 과정에서 부하가 급증한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;관리 복잡도&lt;/b&gt;: 컨트롤 플레인 자체가 고가용성을 유지해야 하므로, 이를 관리하기 위한 운영 인력과 모니터링 비용이 추가로 발생한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. EKS 클러스터 구축&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 AWS에서 실제로 전체 인프라를 조종할 물리 서버(EC2)를 생성하고, eksctl을 활용해 t3.medium 인스턴스 2개 규모의 클러스터를 생성했다. 인프라 재현성을 위해 모든 과정은 Bash 스크립트로 자동화했다.&lt;/p&gt;
&lt;pre id=&quot;code_1778485130450&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# eksctl을 활용한 클러스터 생성
eksctl create cluster \
  --name istio-lab \
  --region ap-northeast-2 \
  --nodegroup-name istio-nodes \
  --node-type t3.medium \
  --nodes 2 \
  --managed&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;질문: 노드는 2개인데, 어떻게 서비스 버전은 3개가 돌아가는가?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;통찰: 노드는 하위 계층에서 CPU와 메모리를 제공하는 &lt;b&gt;물리적 자원 풀&lt;/b&gt;이며, 서비스 버전은 그 위에서 구동되는 &lt;b&gt;논리적 객체&lt;/b&gt;이다. 따라서 노드의 개수는 서비스의 가짓수가 아닌, 시스템의 장애 내성을 결정하는 통제 변수로 이해해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Istio 설치와 사이드카 패턴 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EKS 구축 후 Istio를 설치하고 네임스페이스(default)에 자동 주입 설정을 마친 뒤, Bookinfo 샘플 앱을 배포했다.&lt;/p&gt;
&lt;pre id=&quot;code_1778485389968&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# default 네임스페이스에 배포되는 모든 Pod에 Istio 프록시가 자동으로 붙도록 라벨 설정
kubectl label namespace default istio-injection=enabled

# Istio 샘플 디렉토리에 있는 bookinfo.yaml 배포
kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;질문: Bookinfo 앱 배포 후 kubectl get pods에서 보이는 2/2 는 무엇을 의미하는가?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;통찰: 하나의 Pod 안에 &lt;b&gt;비즈니스 앱 컨테이너와 Istio 프록시 컨테이너가 하나로 묶여 있기 때문&lt;/b&gt;에 2/2로 표시된다. 해당 Pod 내부에 있는 프록시가 앱의 입출구 역할을 대신해주기 때문에 통제권이 인프라 계층으로 넘어오게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. Bookinfo 앱을 통한 트래픽 제어 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포 후 웹 페이지를 새로고침할 때마다 별점 모양(없음/검은별/빨간별)이 순차적으로 바뀌는 것을 확인했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;질문: 새로고침할 때마다 바뀌는 이유는?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;통찰: 새로고침마다 화면이 바뀌는 것은 라벨로 묶인 여러 파드들 사이를 Istio가 &lt;b&gt;라운드 로빈 방식&lt;/b&gt;으로 연결해주기 때문이다. 즉, 서비스의 외적인 변화는 하단의 메타데이터 관리와 트래픽 분배 정책에 의해 결정된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 후기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Meetup에서의 호기심으로 시작한 실습은 EC2-Pod-Container로 이어지는 수직적 구조와, 이를 관통하는 Istio의 통제 방식을 직접 경험하는 시간이었다. 이제 깔끔하게 정리된 내용 위에, 다음 번에는 카나리 배포를 통한 세밀한 트래픽 전환과 프로메테우스/그라파나 기반의 정밀 모니터링을 실습하고 결과를 공유할 예정이다.&lt;/p&gt;</description>
      <category>코딩/k8s</category>
      <category>AWS</category>
      <category>EKS</category>
      <category>istio</category>
      <category>servicemesh</category>
      <author>khseon7</author>
      <guid isPermaLink="true">https://khseon7.tistory.com/39</guid>
      <comments>https://khseon7.tistory.com/39#entry39comment</comments>
      <pubDate>Mon, 11 May 2026 17:05:13 +0900</pubDate>
    </item>
    <item>
      <title>[논문 리뷰] TurboQuant: Online Vector Quantization with Near-optimalDistortion Rate</title>
      <link>https://khseon7.tistory.com/38</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://arxiv.org/abs/2504.19874&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://arxiv.org/abs/2504.19874&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1776741490998&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;TurboQuant: Online Vector Quantization with Near-optimal Distortion Rate&quot; data-og-description=&quot;Vector quantization, a problem rooted in Shannon's source coding theory, aims to quantize high-dimensional Euclidean vectors while minimizing distortion in their geometric structure. We propose TurboQuant to address both mean-squared error (MSE) and inner &quot; data-og-host=&quot;arxiv.org&quot; data-og-source-url=&quot;https://arxiv.org/abs/2504.19874&quot; data-og-url=&quot;https://arxiv.org/abs/2504.19874v1&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/L7kca/dJMb9jgzqvO/TpnA0tSTvnHCscO6deTK61/img.png?width=1200&amp;amp;height=700&amp;amp;face=0_0_1200_700,https://scrap.kakaocdn.net/dn/jLT7u/dJMb8Z3tqSw/VjVlikqxN3aYzptR0MSSck/img.png?width=1000&amp;amp;height=1000&amp;amp;face=0_0_1000_1000&quot;&gt;&lt;a href=&quot;https://arxiv.org/abs/2504.19874&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://arxiv.org/abs/2504.19874&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/L7kca/dJMb9jgzqvO/TpnA0tSTvnHCscO6deTK61/img.png?width=1200&amp;amp;height=700&amp;amp;face=0_0_1200_700,https://scrap.kakaocdn.net/dn/jLT7u/dJMb8Z3tqSw/VjVlikqxN3aYzptR0MSSck/img.png?width=1000&amp;amp;height=1000&amp;amp;face=0_0_1000_1000');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;TurboQuant: Online Vector Quantization with Near-optimal Distortion Rate&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Vector quantization, a problem rooted in Shannon's source coding theory, aims to quantize high-dimensional Euclidean vectors while minimizing distortion in their geometric structure. We propose TurboQuant to address both mean-squared error (MSE) and inner&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;arxiv.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 모델이 커질수록 가장 먼저 문제가 되는 것은 단순한 연산량이 아니다. 실제 서비스 환경에서는 메모리 사용량, GPU 대역폭, 그리고 데이터 이동 비용이 더 큰 병목이 된다. 특히 LLM은 모델 파라미터 자체도 크지만, 추론 과정에서 생성되는 KV Cache와 중간 activation까지 포함하면 막대한 자원을 요구한다. 이 때문에 최근 AI 인프라에서는 모델을 더 빠르게 만드는 기술만큼 벡터를 얼마나 효율적으로 저장하고 저장하고 전송하느냐가 중요해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 지점에서 등장하는 기술이 바로 Quantization이다. 그리고 이번에 소개하는 TurboQuant는 단순히 숫자의 정밀도를 낮추는 수준을 넘어, 고차원 벡터를 매우 적은 비트로 압축하면서도 성능 저하를 최소화하는 새로운 방식의 Vector Quantization 알고리즘이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 벡터 양자화가 중요한가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현대 AI 시스템은 대부분 벡터를 중심으로 동작한다. LLM 내부의 attention key/value 벡터, 검색 시스템의 embedding 벡터, 추천 시스템의 사용자 표현값, 멀티모달 모델의 이미지 특징값까지 모두 고차원 벡터이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 이 벡터들이 대부분 float16, float32 형태로 저장된다는 점이다. 차원이 커질수록 메모리 사용량은 급격히 증가하고, GPU 내부 메모리(HBM)와 연산 캐시(SRAM) 사이에서 데이터를 옮기는 비용도 커진다. 결국 모델이 느려지는 이유는 계산이 아니라 데이터를 읽고 쓰는 과정인 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 많은 연구들이 벡터를 int8, int4처럼 더 작은 정밀도로 압축하려고 시도한다. 하지만 단순히 숫자 비트만 줄이면 벡터의 의미 구조가 망가질 수 있다. 예를 들어 원래 비슷했던 두 벡터가 압축 후에는 멀어질 수 있고, 검색 정확도나 attention score도 크게 흔들릴 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 핵심은 단순 압축이 아니라, 벡터의 기하학적 구조를 유지하면서 압축하는 것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기존 방식의 한계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 대표적인 벡터 양자화 방식으로는 Product Quantization(PQ)이 있습니다. PQ는 벡터를 여러 구간으로 나눈 뒤 각각을 codebook 기반으로 압축하는 방식인데, 벡터 검색 분야에서는 오랫동안 표준처럼 사용되어 왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 PQ는 사전에 학습된 codebook이 필요하고, 데이터셋마다 다시 최적화해야 하며, 대규모 인덱싱 시간이 길다는 단점이 있다. 검색 시스템에서는 유용하지만, 실시간으로 계속 생성되는 KV Cache 같은 환경에서는 적용이 쉽지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 좌표별로 단순하게 int8/int4로 줄이는 방식은 매우 빠르고 구현도 쉽다. 하지만 고차원 벡터의 거리나 내적 구조를 제대로 보존하지 못해 품질 저하가 자주 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 기존 기술은 대체로 두 갈래였다. 정확하지만 느리거나, 빠르지만 손실이 컸다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TurboQuant의 출발점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TurboQuant는 이 두 문제를 동시에 해결하려는 시도이다. 논문은 &quot;&lt;i&gt;빠르고, 온라인 적용 가능하며, GPU 친화적이면서도 이론적으로 거의 최적 수준의 distortion을 달성하는 양자화기를 만들 수 있는가?&lt;/i&gt;&quot;라는 질문에서 출발한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 말하는 이론적 최적 수준이란, 어떠한 압축 알고리즘도 도달할 수 없는 절대적 한계치인 &lt;b&gt;Shannon Lower Bound&lt;/b&gt;를 의미한다. 놀랍게도 TurboQuant는 모든 비트 폭과 차원에 걸쳐 이 수학적 하한선과 불과 약 2.7배 이내의 차이밖에 나지 않는 완벽에 가까운 최적화를 이뤄냈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 그 해답으로 제시한 방식이 매우 흥미롭다. 복잡한 codebook 탐색 대신, &lt;b&gt;입력 벡터를 먼저 랜덤하게 회전&lt;/b&gt;시키고, &lt;b&gt;각 좌표를 독립적으로 처리&lt;/b&gt;하는 구조를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언뜻 단순해 보이지만, 고차원 공간에서는 이 접근이 놀라울 정도로 강력하게 작동한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;랜덤 회전이 왜 중요한가&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TurboQant는 먼저 입력 벡터($\mathcal{x}$)에 랜덤 회전을 적용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$\mathcal{y}=\Pi\mathcal{x}$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 $\PI$는 직교행렬이며, 벡터의 길이는 유지한 채 좌표축만 무작위로 바꾸는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 거치면 &lt;i&gt;특정 좌표 하나에 큰 값이 몰려 있던 벡터도 전체 차원에 값이 비교적 고르게 퍼지게 된다&lt;/i&gt;. 다시 말해 outlier가 완화되고, 각 좌표가 비슷한 통계적 성질을 갖게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 매우 중요하다. 기존에는 벡터 전체를 한 번에 다뤄야 했지만, &lt;b&gt;회전 이후에는 좌표 하나하나를 독립적으로 양자화해도 성능이 잘나온다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 복잡한 고차원 문제를 단순한 1차원 문제 여러 개로 바꾸는 셈이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;좌표 분포와 Beta Distsribution&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논문은 랜덤 회전된 고차원 unit vector의 각 좌표가 특정 형태의 &lt;b&gt;Beta 분포&lt;/b&gt;를 따른다는 사실을 활용한다. 고차원에서는 좌표값들이 중심에 몰리고, 서로 거의 독립적인 것처럼 행동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 성질 덕분에 각 좌표마다 최적의 scalar quantizer를 설계할 수 있다. 즉 벡터 전체를 위해 거대한 codebook을 만들 필요 없이, 좌표별 최적값만 계산하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 TurboQuant가 빠르면서도 정확한 이유이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;520&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eim4NL/dJMcaiJL0x9/KmVwaCxOtADP3ppQ2C57Fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eim4NL/dJMcaiJL0x9/KmVwaCxOtADP3ppQ2C57Fk/img.png&quot; data-alt=&quot;독립적으로 변환된 각 좌표가 따르는 Beta 분포&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eim4NL/dJMcaiJL0x9/KmVwaCxOtADP3ppQ2C57Fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feim4NL%2FdJMcaiJL0x9%2FKmVwaCxOtADP3ppQ2C57Fk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;664&quot; height=&quot;291&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;520&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;독립적으로 변환된 각 좌표가 따르는 Beta 분포&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Lloyd-Max Quantizer를 활용한 좌표별 최적화&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TubeoQuant는 각 좌표에 대해 &lt;b&gt;Lloyd-Max quantizer&lt;/b&gt;를 적용한다. 이는 주어진 확률분포에서 평균 제곱 오차(MSE)를 최소화하는 대표적인 스칼라 양자화 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 2bit라면 4개의 대표값을 만들고, 입력 좌표가 어느 구간에 속하는지에 따라 가장 가까운 대표값으로 치환한다. 3bit라면 8개 대표값을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정은 GPU에서 병렬 처리하기 매우 좋다. 차원마다 독립적으로 계산하면 되기 때문에 SIMD 구조와도 잘 맞고, lookup 기반으로 매우 빠르게 동작한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;그런데 MSE만 좋다고 끝이 아니다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벡터를 잘 복원하는 것과, 벡터 간 관계를 잘 보존하는 것은 다른 문제이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM의 attention score나 벡터 검색은 결국 두 벡터의 내적 값으로 결정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$\mathcal{x}^\mathrm{T}\mathcal{y}$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 압축 후 벡터가 원본과 비슷해 보여도, 내적이 틀어지면 검색 결과나 모델 성능이 무너질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논문은 MSE 기준으로 최적인 &lt;b&gt;양자화기가 inner product estimation에는 bias를 만들 수 있다&lt;/b&gt;고 지적한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$\mathrm{E}[\hat{\mathcal{x}}^\mathrm{T}\mathcal{y}]\neq\mathcal{x}^\mathrm{T}\mathcal{y}$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 차이는 실제 서비스에서 상당히 치명적일 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1130&quot; data-origin-height=&quot;666&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPzA3g/dJMcagSMacZ/kJIkROglgGSAvujMsW0mz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPzA3g/dJMcagSMacZ/kJIkROglgGSAvujMsW0mz1/img.png&quot; data-alt=&quot;Figure 1: Error distribution of TurboQuant_prod and TurboQuant-mse&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPzA3g/dJMcagSMacZ/kJIkROglgGSAvujMsW0mz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPzA3g%2FdJMcagSMacZ%2FkJIkROglgGSAvujMsW0mz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;667&quot; height=&quot;393&quot; data-origin-width=&quot;1130&quot; data-origin-height=&quot;666&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Figure 1: Error distribution of TurboQuant_prod and TurboQuant-mse&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TurboQuant의 두 번째 핵심: Residual 보정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TurboQuant는 이 문제를 2단계 구조로 해결한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 MSE 기준으로 벡터를 압축한 뒤, 남은 오차를 계산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$\mathcal{r=x-\hat{x}}$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이 residual에 대해 1-bit Quantized Johnson-Lindenstrauss(QJL) 기법을 적용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QJL은 랜덤 projection 후 부호만 저장하는 매우 가벼운 방식인데, 놀랍게도 inner product에 대해 unbiased estimator 성질을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 핵심은 비트 예산(Bit-budget)의 분배이다. 만약 목표 압축 용량이 b비트라면, 1단계에 b-1비트를 할당하여 오차를 최소화하고, 남은 1비트를 2단계에 사용하여 정확히 목표 용량 b비트를 맞추게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 TurboQuant는 추가적인 메모리 낭비 없이 벡터 자체는 정확히 복원하면서도, 내적 계산의 편향까지 동시에 잡아낸다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실험 결과&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;논문에서는 LLM KV Cache 압축 실험에서 3.5bit 수준만으로 &lt;b&gt;full precision과 거의 동일한 품질&lt;/b&gt;을 달성했다고 보고한다. 2.5bit에서도 성능 저하는 매우 제한적이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 단순 계산으로도 &lt;b&gt;메모리를 4배 이상 절약&lt;/b&gt;할 수 있다는 뜻이다. 긴 context 모델이나 동시 사용자 수가 많은 서비스에서는 엄청난 차이이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 벡터 검색 실험에서는 기존 Product Quantization 대비 더 높은 recall을 보이면서도, &lt;b&gt;인덱싱 시간은 거의 0&lt;/b&gt;에 가깝다고 설명합니다. 즉 검색 품질과 구축 속도를 동시에 가져간 셈이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;644&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RtFah/dJMcah5dW2w/VLzoWUah9HvAOV57TKgJEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RtFah/dJMcah5dW2w/VLzoWUah9HvAOV57TKgJEK/img.png&quot; data-alt=&quot;Evaluation of Llama-3.1-8B-Instruct on the &amp;quot;Needle-In-A-Haystack&amp;quot; test&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RtFah/dJMcah5dW2w/VLzoWUah9HvAOV57TKgJEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRtFah%2FdJMcah5dW2w%2FVLzoWUah9HvAOV57TKgJEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;602&quot; height=&quot;321&quot; data-origin-width=&quot;1208&quot; data-origin-height=&quot;644&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Evaluation of Llama-3.1-8B-Instruct on the &quot;Needle-In-A-Haystack&quot; test&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-04-21 오후 1.56.34.png&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSIMhj/dJMcafzzyZL/7p0JzDkUNdAkGRqgM7P7p1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSIMhj/dJMcafzzyZL/7p0JzDkUNdAkGRqgM7P7p1/img.png&quot; data-alt=&quot;Quantization time (in seconds)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSIMhj/dJMcafzzyZL/7p0JzDkUNdAkGRqgM7P7p1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSIMhj%2FdJMcafzzyZL%2F7p0JzDkUNdAkGRqgM7P7p1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;465&quot; height=&quot;93&quot; data-filename=&quot;스크린샷 2026-04-21 오후 1.56.34.png&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;152&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Quantization time (in seconds)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>논문</category>
      <category>LLM</category>
      <category>QUANTIZATION</category>
      <category>TurboQuant</category>
      <author>khseon7</author>
      <guid isPermaLink="true">https://khseon7.tistory.com/38</guid>
      <comments>https://khseon7.tistory.com/38#entry38comment</comments>
      <pubDate>Tue, 21 Apr 2026 14:06:34 +0900</pubDate>
    </item>
    <item>
      <title>[K8s] K8s 환경에서 Python 애플리케이션 모니터링 구축</title>
      <link>https://khseon7.tistory.com/37</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;295&quot; data-origin-height=&quot;171&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/erp2sC/dJMcaiQuFv4/Xzvag5mUOwKP9gciZKF3lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/erp2sC/dJMcaiQuFv4/Xzvag5mUOwKP9gciZKF3lk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/erp2sC/dJMcaiQuFv4/Xzvag5mUOwKP9gciZKF3lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ferp2sC%2FdJMcaiQuFv4%2FXzvag5mUOwKP9gciZKF3lk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;295&quot; height=&quot;171&quot; data-origin-width=&quot;295&quot; data-origin-height=&quot;171&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 docker-compose로 간단히 구축하던 Personal AI Dispatcher 서비스를 쿠버네티스(Minikube) 환경으로 이관하여 가시성을 확보하기 위해 모니터링 시스템을 구축했다. 단순 설치를 넘어 k8s 네트워크 계층과 Prometheus Operator의 동작 원리를 이해했던 내용들을 정리한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Application Layer: Prometheus Metrics 서버 구축&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Python 앱에서 지표를 노출하기 위해, prometheus_client 라이브러리를 활용해 메트릭 서버를 별도의 스레드로 실행했다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  지표 정의와 'Lazy Initialization'의 함정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로메테우스의 Counter는 선언만 한다고 데이터가 생성되지 않는다. 실제로 데이터가 기록되기 전까지는 Endpoint에 나타나지 않는 Lazy Initialization 특성이 있다. 이를 해결하지 않으면 봇이 처음 가동될 때 그라파나에서 지표를 찾을 수 없는 현상이 발생한다.&lt;/p&gt;
&lt;pre id=&quot;code_1776328626742&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from prometheus_client import start_http_server, Counter

# 지표 정의 (라벨을 사용하여 브리핑 종류 구분)
BRIEFING_SENT = Counter('briefing_sent_total', 'Total number of briefings sent', ['type'])

def start_metrics_server():
    # 8000번 포트로 HTTP 서버 시작
    start_http_server(8000)
    
    # [핵심] 초기값 0.0을 생성하여 프로메테우스가 즉시 인식하게 함
    BRIEFING_SENT.labels(type='morning').inc(0)
    
    logger.info(&quot;Prometheus metrics server started on port 8000&quot;)
    
# 실제 메시지 전송 로직 수행 후 카운트 증가
BRIEFING_SENT.labels(type='count').inc()&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Infra Layer: Kubernetes 객체 설계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션이 포트를 열었다면, k8s 클러스터 내부에서 이를 찾을 수 있게 다리를 놓아야 한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Service: 통로 개설&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pod의 IP는 유동적이다. 따라서 고정적인 Cluster IP를 부여하는 Service 객체가 필요하다. 이때 포트의 name을 metrics로 명시하는 것이 중요하다. 그 이유는 ServiceMonitor가 이 이름을 보고 데이터를 긁어갈 대상을 찾기 때문이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ServiceMonitor: 자동 탐지&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Prometheus Operator를 사용한다면 ServiceMonitor가 핵심이다. 여기서 가장 중요한 것은 labels 섹션의 release 값이다. 현재 설치된 프로메테우스 헬름 차트의 릴리스 이름과 일치해야 오퍼레이터가 이 설정을 읽어간다. 만약 이 레이블이 다를 경우 오퍼레이터가 설정을 무시한다.&lt;/p&gt;
&lt;pre id=&quot;code_1776328856029&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spec:
  selector:
    matchLabels:
      app: dispatcher # Deployment의 레이블과 일치해야 함
  endpoints:
  - port: metrics     # Service에 정의된 포트 이름
    path: /metrics
    interval: 30s&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 최종 결과 확인 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 설정이 완료되었다면 다음 경로를 통해 시스템이 정상인지 확인하면된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Metrics 엔드포인트 확인&lt;br /&gt;8000번 포트로 포워딩 후 http://localhost:8000/metrics 접속. &lt;i&gt;briefing_sent_total{type=&quot;count&quot;} 1.0&lt;/i&gt; 같은 문구가 보이면 성공이다.(앱 가동시에&amp;nbsp;&lt;/li&gt;
&lt;li&gt;prometheus Targets 확인&lt;br /&gt;9090번 포트로 포워딩 후 Targets 메뉴 진입 후 설정한 monitor가 UP 상태인지 확인한다. 만약 여기서 안보인다면 ServiceMonitor의 레이블 셀렉터 문제 혹은 포트 포워딩 오류일 수 있다.&lt;/li&gt;
&lt;li&gt;Grafana 대시보드&lt;br /&gt;3000번 포트로 포워딩 후 접속하여 Explore 메뉴에서 briefing_sent_total 쿼리를 실행한다. 봇이 브리핑을 보낼 때마다 그래프가 상승하는 것을 볼 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-04-16 오후 6.03.42.png&quot; data-origin-width=&quot;2978&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJepvi/dJMcacCNvGR/SfMck9J56RfkkgsHWLl2K0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJepvi/dJMcacCNvGR/SfMck9J56RfkkgsHWLl2K0/img.png&quot; data-alt=&quot;Prometheus Targets 내부에서 serviceMonitor가 켜져있는 경우&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJepvi/dJMcacCNvGR/SfMck9J56RfkkgsHWLl2K0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJepvi%2FdJMcacCNvGR%2FSfMck9J56RfkkgsHWLl2K0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2978&quot; height=&quot;368&quot; data-filename=&quot;스크린샷 2026-04-16 오후 6.03.42.png&quot; data-origin-width=&quot;2978&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Prometheus Targets 내부에서 serviceMonitor가 켜져있는 경우&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-04-16 오후 6.05.37.png&quot; data-origin-width=&quot;2612&quot; data-origin-height=&quot;1228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/P1LmD/dJMcaaLJ19x/P6d4icZOCY51K4le8xgjZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/P1LmD/dJMcaaLJ19x/P6d4icZOCY51K4le8xgjZ0/img.png&quot; data-alt=&quot;Grafana에서 briefing_sent_total 에 대해서 쿼리 조회를 한 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/P1LmD/dJMcaaLJ19x/P6d4icZOCY51K4le8xgjZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FP1LmD%2FdJMcaaLJ19x%2FP6d4icZOCY51K4le8xgjZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2612&quot; height=&quot;1228&quot; data-filename=&quot;스크린샷 2026-04-16 오후 6.05.37.png&quot; data-origin-width=&quot;2612&quot; data-origin-height=&quot;1228&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Grafana에서 briefing_sent_total 에 대해서 쿼리 조회를 한 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. 트러블슈팅 - Connection Refused&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;port-forward 도중 발생한 에러로, 터널은 뚫렸지만 정작 포드 안의 파이썬 프로세스가 크래시 나거나 포트가 열리지 않았을 때 발생한다. 이를 통해 &quot;kubectl logs로 프로세스 생존 여부 확인&quot; 과 &quot;lsof 로 포트를 점유하고 있는 서비스 여부를 확인하는 습관&quot;이 중요함을 배웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>코딩/k8s</category>
      <category>DevOps</category>
      <category>Grafana</category>
      <category>k8s</category>
      <category>kubernetes</category>
      <category>Prometheus</category>
      <author>khseon7</author>
      <guid isPermaLink="true">https://khseon7.tistory.com/37</guid>
      <comments>https://khseon7.tistory.com/37#entry37comment</comments>
      <pubDate>Thu, 16 Apr 2026 18:08:38 +0900</pubDate>
    </item>
    <item>
      <title>Swap 메모리로 1GB RAM 맷집 키우기</title>
      <link>https://khseon7.tistory.com/36</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Oracle Cloud와 같은 클라우드 무료 티어나 저가형 서버를 쓰다 보면 당황스러운 순간이 온다. Docker를 올리거나 Nginx 설정을 바꾸는데 갑자기 터미널이 멈추고 서버 접속이 끊겨버리는 경우가 있다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;원인은 명확하다. &lt;b data-index-in-node=&quot;11&quot; data-path-to-node=&quot;4&quot;&gt;1GB라는 미니미한 RAM 용량&lt;/b&gt; 때문입니다. 서버가 처리할 데이터는 많은데 담을 그릇이 부족하니 결국 서버가 '기절'해버리는 것이다. 오늘은 이 좁은 그릇을 대신해 하드디스크의 일부를 메모리처럼 사용하는 &lt;b data-index-in-node=&quot;125&quot; data-path-to-node=&quot;4&quot;&gt;Swap(스왑)&lt;/b&gt; 설정으로 서버에 8GB급 맷집을 키워보자.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size23&quot;&gt; ️ 1GB RAM 서버 심폐소생술 (Swap 7GB 설정)&lt;/h3&gt;
&lt;h4 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size20&quot;&gt;1. 7GB 크기의 빈 공간 확보하기&lt;/h4&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;서버 안에 7GB짜리 거대한 빈 파일을 만든다. 약 10~20초 정도 소요&lt;/p&gt;
&lt;pre id=&quot;code_1776161905709&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo fallocate -l 7G /swapfile&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size20&quot;&gt;2. &quot;나만 볼 거야&quot; 권한 설정&lt;/h4&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;이 파일은 메모리 대용이라 보안이 중요합니다. 다른 사용자가 함부로 읽지 못하도록 권한을 제한&lt;/p&gt;
&lt;pre id=&quot;code_1776161898268&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo chmod 600 /swapfile&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size20&quot;&gt;3. 파일을 '스왑용'으로 변신시키기&lt;/h4&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;그냥 빈 파일이었던 것을 리눅스 시스템이 메모리처럼 인식할 수 있게 포맷하는 과정&lt;/p&gt;
&lt;pre id=&quot;code_1776161885917&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo mkswap /swapfile&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size20&quot;&gt;4. 스왑 엔진 가동&lt;/h4&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;이제 실제로 리눅스 시스템이 이 공간을 메모리처럼 사용 시작&lt;/p&gt;
&lt;pre id=&quot;code_1776161872460&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo swapon /swapfile&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-path-to-node=&quot;20&quot; data-ke-size=&quot;size20&quot;&gt;5. 재부팅해도 유지되도록 영구 저장&lt;/h4&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;위의 설정들은 서버를 껐다 켜면 사라집니다. 재부팅 시에도 자동으로 적용되도록 설정 파일(fstab)에 내용을 기록&lt;/p&gt;
&lt;pre id=&quot;code_1776161875771&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab&lt;/code&gt;&lt;/pre&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwj2roeuje2TAxUAAAAAHQAAAAAQTg&quot; data-hveid=&quot;0&quot;&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-path-to-node=&quot;24&quot; data-ke-size=&quot;size20&quot;&gt;✅ 제대로 됐는지 검사하기&lt;/h4&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;모든 명령어를 입력했다면, 아래 명령어로 최종 확인을 하면 다음과 같이 뜨는 것을 볼 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1776161842102&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;free -h&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-04-14 오후 7.17.09.png&quot; data-origin-width=&quot;1138&quot; data-origin-height=&quot;84&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nqAzg/dJMcagSHhVC/ydFMsyTKLNuO13NyFMqCK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nqAzg/dJMcagSHhVC/ydFMsyTKLNuO13NyFMqCK0/img.png&quot; data-alt=&quot;swap 엔진 가동 후 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nqAzg/dJMcagSHhVC/ydFMsyTKLNuO13NyFMqCK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnqAzg%2FdJMcagSHhVC%2FydFMsyTKLNuO13NyFMqCK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1138&quot; height=&quot;84&quot; data-filename=&quot;스크린샷 2026-04-14 오후 7.17.09.png&quot; data-origin-width=&quot;1138&quot; data-origin-height=&quot;84&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;swap 엔진 가동 후 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;</description>
      <category>코딩/Linux</category>
      <category>swap</category>
      <category>ubuntu</category>
      <category>메모리</category>
      <author>khseon7</author>
      <guid isPermaLink="true">https://khseon7.tistory.com/36</guid>
      <comments>https://khseon7.tistory.com/36#entry36comment</comments>
      <pubDate>Tue, 14 Apr 2026 19:20:37 +0900</pubDate>
    </item>
    <item>
      <title>[K8s] 메모리 고갈로 인한 OOMKilled 테스팅</title>
      <link>https://khseon7.tistory.com/35</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;대규모 언어 모델을 포함한 AI 서빙 환경에서 가장 치명적인 위협은 CPU 부하가 아닌 메모리 고갈(OOM, Out Of Memory)이다. CPU 부하는 서비스의 지연을 초래하지만, 메모리 임계치 초과는 쿠버네티스 커널에 의한 컨테이너 즉시 사살로 이어지기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 실습에서는 의도적으로 메모리 부하를 일으켜 k8s의 &lt;b&gt;Self-healing 메커니즘&lt;/b&gt;과 &lt;b&gt;메모리 기반 HPA(Horizontal Pod Autoscaler)&lt;/b&gt;의 실제 동작 방식을 분석했다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실험 환경 구축&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Infra&lt;/b&gt;: Minikube (Metrics Server 활성화)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Monitoring&lt;/b&gt;: Prometheus &amp;amp; Grafana (w. Helm)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;App&lt;/b&gt;: Flask 기반의 'Mock-LLM' 서버 (요청 시마다 20MB씩 메모리 점유하는 로직 구현)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;핵심 설정: Deployment &amp;amp; HPA&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장애 상황을 정밀하게 관측하기 위해 메모리 임계치를 타이트하게 설정했다.&lt;/p&gt;
&lt;pre id=&quot;code_1775465697643&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Deployment 리소스 설정
resources:
  requests:
    memory: &quot;100Mi&quot;
  limits:
    memory: &quot;300Mi&quot; # 300MB 초과 시 즉시 종료(OOMKilled)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1775465728672&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# HPA 설정 (v2)
metrics:
- type: Resource
  resource:
    name: memory
    target:
      type: Utilization
      averageUtilization: 60 # 메모리 사용량 60% 도달 시 스케일 아웃&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;장애 재현 및 관측&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;curl loop를 통해 부하 테스트를 진행해본 결과 급격한 메모리 점유가 유도했ㅇ르 때, 다음과 같은 시스템 변화가 포착되었다.&lt;/p&gt;
&lt;pre id=&quot;code_1775465796590&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# kubectl get pods -w 관측 로그
mock-app-xxx   1/1   Running            1    (2s ago)   8m22s
mock-app-xxx   0/1   OOMKilled          1    (4m29s ago)   12m
mock-app-xxx   0/1   CrashLoopBackOff   1    (15s ago)     13m
mock-app-xxx   1/1   Running            2    (16s ago)     13m&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;재시작 후 연결 단절 문제&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포드가 Running 상태로 복구되었음에도 불구, 로컬 터미널에서 connect 에러가 발생했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;원인 분석&lt;/b&gt;: kubectl port-forward는 특정 프로세스와의 1:1 터널링이다. 프로세스가 OOM으로 종료되는 순간 이 터널은 파괴된다. 터미널에 출력된 Broken Pipe 에러가 이를 증명했습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;해결&lt;/b&gt;: 기존 포트 포워딩 세션을 종료하고 재성립함으로써 연결을 복구했습니다. 이는 인프라의 휘발성을 보여주는 전형적인 사례이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;329&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Qb9uG/dJMcahYf9zc/ngZVDl71BEq7ypQoSVlC40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Qb9uG/dJMcahYf9zc/ngZVDl71BEq7ypQoSVlC40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Qb9uG/dJMcahYf9zc/ngZVDl71BEq7ypQoSVlC40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQb9uG%2FdJMcahYf9zc%2FngZVDl71BEq7ypQoSVlC40%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;797&quot; height=&quot;87&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;3000&quot; data-origin-height=&quot;329&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Engineering Insights&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;HPA와 OOM 사이의 &lt;b&gt;골든 타임&lt;/b&gt;&lt;br /&gt;HPA가 메트릭을 수집하고 새 포드를 준비하는 속도보다 &lt;b&gt;메모리가 차오르는 속도가 더 빠르면&lt;/b&gt; 오토스케일링이 작동하기 전에 서버가 먼저 죽는다는 것을 확인했습니다. 이를 방지하기 위해 실무에서는 Requests와 Limits 사이의 충분한 &lt;b&gt;Buffer&lt;/b&gt; 설계가 필수적임을 알게되었다.&lt;/li&gt;
&lt;li&gt;Running과 Ready의 엄격한 구분&lt;br /&gt;컨테이너가 다시 살아나도 내부 애플리케이션이 런타임을 준비하는 &lt;b&gt;Warm-up 시간&lt;/b&gt;이 필요했다. 이를 위해 &lt;b&gt;Readiness Probe&lt;/b&gt;를 설정하여, 서비스가 실제로 요청을 받을 준비가 되었을 때만 트래픽을 유입시키는 설계의 중요성을 이해했다.&lt;/li&gt;
&lt;li&gt;Self-healing의 한계와 보완&lt;br /&gt;쿠버네티스가 포드를 살려내더라도 기존 네트워크 세션은 유실된다. 따라서 시스템 안정성을 위해서는 인프라의 복구뿐만 아니라, 클라이언트 레벨의 &lt;b&gt;재시도 로직&lt;/b&gt;과 로드밸런서의 &lt;b&gt;정밀한 헬스 체크&lt;/b&gt;가 병행되어야 함을 알게되었다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;결론&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안정적인 인프라란 단순히 장애가 없는 상태가 아니라, 장애가 발생했을 때 시스템이 얼마나 예측 가능하게 반응하고 자동으로 복구되느냐에 달려 있다. 이번 실습을 통해 리소스 설계와 관측 가능성의 중요성을 다시 한번 확인할 수 있었습니다.&lt;/p&gt;</description>
      <category>코딩/k8s</category>
      <category>k8s</category>
      <category>Kubenetes</category>
      <category>minikube</category>
      <category>OOM</category>
      <author>khseon7</author>
      <guid isPermaLink="true">https://khseon7.tistory.com/35</guid>
      <comments>https://khseon7.tistory.com/35#entry35comment</comments>
      <pubDate>Mon, 6 Apr 2026 18:07:52 +0900</pubDate>
    </item>
    <item>
      <title>[K8s] Minikube 환경에서 HPA와 k6로 구현하는 부하 테스트</title>
      <link>https://khseon7.tistory.com/34</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bp9qv9/dJMcagLNrWl/0GcQyQijyMqWOCzz8gzWMK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bp9qv9/dJMcagLNrWl/0GcQyQijyMqWOCzz8gzWMK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bp9qv9/dJMcagLNrWl/0GcQyQijyMqWOCzz8gzWMK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbp9qv9%2FdJMcagLNrWl%2F0GcQyQijyMqWOCzz8gzWMK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;332&quot; height=&quot;233&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;506&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 환경인 Minikube에서 쿠버네티스의 핵심 기능 중 하나인 HPA(Horizontal Pod Autoscaler)가 실제로 어떻게 동작하는지를 확인하기 위해, k6를 이용해 트래픽 부하를 주고, Prometheus 와 Grafana로 모니터링하며 Pod가 자동으로 확장되는 과정을 정리해본다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 환경 세팅: Minikube&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 맥북의 자원을 효율적으로 사용하기 위해 Docker 드라이버를 사용하여 minikube를 시작한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Minikube 설치 및 시작&lt;/h4&gt;
&lt;pre id=&quot;code_1775118057139&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew install minikube

# Docker 데스크탑 설정에서 CPU 4, Memory 8GB 이상으로 설정 권장
minikube start --driver=docker --cpus=4 --memory=8192mb&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 모니터링 구축: Prometheus &amp;amp; Grafana&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스의 상태를 한눈에 파악하기 위해 kube-prometheus-stack을 설치한다.&lt;/p&gt;
&lt;pre id=&quot;code_1775118133782&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew install helm

# 리포지토리 추가 및 업데이트
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

# Prometheus Stack 설치 (수집+시각화+알림 패키지)
helm install my-release prometheus-community/kube-prometheus-stack&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Grafana 접속&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 완료되면 포트 포워딩을 통해 대시보드에 접속한다.&lt;/p&gt;
&lt;pre id=&quot;code_1775118157121&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Grafana 서비스 이름 확인
kubectl get svc -l &quot;app.kubernetes.io/name=grafana&quot;

# 포트 포워딩 (3000번 포트)
kubectl port-forward svc/my-release-grafana 3000:80&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ID: admin&lt;/li&gt;
&lt;li&gt;Password: 초기 비밀번호는 아래 명령어로 확인 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1775118183432&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl get secret --namespace default my-release-grafana -o jsonpath=&quot;{.data.admin-password}&quot; | base64 --decode ; echo&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 테스트용 앱 배포 및 HPA 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부하를 테스트할 앱으로 CPU를 의도적으로 많이 소모하는 php-apache를 사용&lt;/p&gt;
&lt;pre id=&quot;code_1775118219259&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 앱 배포
kubectl apply -f https://k8s.io/examples/application/php-apache.yaml

# HPA 설정: CPU 사용량이 50%를 넘으면 Pod를 최대 10개까지 증설
kubectl autoscale deployment php-apache --cpu=50% --min=1 --max=10&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 부하 테스트: k6 활용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 k6를 설치하고 자바스크립트 기반의 테스트 스크립트를 작성하여 트래픽을 쏴보자&lt;/p&gt;
&lt;pre id=&quot;code_1775118252761&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;brew install k6&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1775118267564&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import http from 'k6/http';
import { sleep } from 'k6';

export const options = {
  stages: [
    { duration: '30s', target: 50 }, // 30초 동안 사용자 50명까지 증가
    { duration: '1m', target: 100 },  // 1분 동안 100명 유지 (Peak 부하)
    { duration: '30s', target: 0 },  // 종료 시점까지 서서히 감소
  ],
};

export default function () {
  http.get('http://localhost:8080'); // 포트 포워딩 주소
  sleep(0.1);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 실행하기 전에 Host PC에서 트래픽을 보낼 수 있게 포트 포워딩은 까먹지 말자&lt;/p&gt;
&lt;pre id=&quot;code_1775118298078&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl port-forward svc/php-apache 8080:80
# 실행
k6 run script.js&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 트러블슈팅: 왜 HPA가 작동하지 않을까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명 위 스크립트를 바탕으로 부하를 강하게 줬는데도 kubectl get hpa 를 쳤을 때 TARGETS 값이 &amp;lt;unknown&amp;gt;으로 뜨고 Pod가 늘어나지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인 결과 쿠버네티스는 자체적으로 각 Pod의 자원 사용량을 알지 못해, metrics-server 애드온이 켜져 있어야만 HPA가 &quot;아, 지금 CPU가 50%를 넘었구나!&quot;라고 인지하고 스케일 아웃을 진행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1775118398962&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# metrics-server 활성화
minikube addons enable metrics-server

# 정상 작동 확인
kubectl get pods -n kube-system | grep metrics-server&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;변화 확인&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 70.4647%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.9147%; text-align: center;&quot;&gt;상태&lt;/td&gt;
&lt;td style=&quot;width: 27.4031%; text-align: center;&quot;&gt;kubectl get hpa 결과(TARGETS)&lt;/td&gt;
&lt;td style=&quot;width: 29.1472%; text-align: center;&quot;&gt;비고&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.9147%; text-align: center;&quot;&gt;애드온 추가 전&lt;/td&gt;
&lt;td style=&quot;width: 27.4031%; text-align: center;&quot;&gt;&amp;lt;unkown&amp;gt; / 50%&lt;/td&gt;
&lt;td style=&quot;width: 29.1472%; text-align: center;&quot;&gt;HPA가 자원량을 몰라 멍하니 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.9147%; text-align: center;&quot;&gt;애드온 추가 후&lt;/td&gt;
&lt;td style=&quot;width: 27.4031%; text-align: center;&quot;&gt;85% / 50%&lt;/td&gt;
&lt;td style=&quot;width: 29.1472%; text-align: center;&quot;&gt;사용량 감지 즉시 Pod 개수 증가 시작&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 파드가 늘어났는데도 왜 여전히 느릴까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;metrics-server를 켜서 파드가 정상적으로 5개로 늘어나는 것을 확인했는데도 다음 그림과 같이 20초 이상의 지연 시간과 타임아웃이 발생했다. 이것에 대한 원인을 분석해본 결과 다음과 같았다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;kubectl port-forward는 대규모 트래픽용이 아니다.&lt;br /&gt;우리가 localhost:8080 으로 쏘고 있는 트래픽은 사실 매우 좁은 통로를 지나고 있다. port-forward는 개발자가 디버깅 용도로 쓰라고 만든 임시 통로이다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;병목 현상: 이 통로는 100 VU(Virtual User) 같은 대량의 동시 접속을 처리하도록 설계되지 않았다. Pod가 아무리 많아도 맥북과 Minikube 사이를 잇는 이 통로에서 트래픽이 다 막힌것이다.&lt;/li&gt;
&lt;li&gt;해결책: 실제 minikube service php-apache --url 명령어로 나오는 실제 IP와 포트로 직접 트래픽을 쏘거나, Ingress를 통해 접근해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;HPA는 사후 처방이다. (Scaling Lag)&lt;br /&gt;HPA가 파드를 늘리는 과정을 시간순으로 복기해보면 다음과 같다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;트래픽 폭주 발생 (0초)&lt;/li&gt;
&lt;li&gt;기존 파드가 응답 지연 시작 (5~10초)&lt;/li&gt;
&lt;li&gt;Metrics-server가 부하 감지 (30초~1분)&lt;/li&gt;
&lt;li&gt;HPA가 증설 결정 및 파드 생성 (1분~1분 30초)&lt;/li&gt;
&lt;li&gt;새 파드가 Ready 상태가 되어 트래픽 분산 (2분 내외)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr;&amp;nbsp;우리의 테스트는 보통 2~3분 내외로 끝난다. 즉, 새 파드들이 일을 시작하려고 할 때 이미 테스트는 끝물이거나, 기존 통로가 이미 포화 상태에 빠진 뒤여서 Pod가 늘어나도 트래픽 분산이 정상적으로 이뤄지지 않은것으로 보인다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1354&quot; data-origin-height=&quot;1584&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vJa7y/dJMcacP82EU/wDKkZhKSq2fACaKFKNk51K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vJa7y/dJMcacP82EU/wDKkZhKSq2fACaKFKNk51K/img.png&quot; data-alt=&quot;k6를 통해 실제 트래픽 테스트한 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vJa7y/dJMcacP82EU/wDKkZhKSq2fACaKFKNk51K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvJa7y%2FdJMcacP82EU%2FwDKkZhKSq2fACaKFKNk51K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;594&quot; height=&quot;695&quot; data-origin-width=&quot;1354&quot; data-origin-height=&quot;1584&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;k6를 통해 실제 트래픽 테스트한 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7.&amp;nbsp; Ingress를 통한 트래픽 분산&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ingress 활성화하고, 다음 yaml 파일을 생성한 후 터널을 열어둔다.&lt;/p&gt;
&lt;pre id=&quot;code_1775120269315&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;minikube addons enable ingress&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1775120325947&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 파일 이름 예시: ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: php-apache-ingress
  spec:
    # Nginx Ingress Controller를 사용하겠다는 설정입니다.
    ingressClassName: nginx
spec:
  rules:
  - host: fake.test  # 브라우저나 k6에서 접속할 가짜 도메인
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: php-apache  # 아까 확인한 서비스 이름과 똑같아야 함
            port:
              number: 80&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1775120374851&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 쿠버네티스에 배포
kubectl apply -f ingress.yaml&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;맥북에서 가짜 도메인 인식시키기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 쿠버네티스 안에서 fake.test라는 문은 생겼지만, Host 맥북은 그 주소가 어디인지 모르기에 이걸 연결해줘야한다.&lt;/p&gt;
&lt;pre id=&quot;code_1775120420885&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 터미널에서 명령어 입력
sudo vi /etc/hosts

# 파일 맨 아래에 다음 내용 추가
127.0.0.1 fake.test&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Ingress tunnel 열기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맥북은 보안상 Ingress가 기본적으로 닫혀 있어 별도의 명령어를 열고 아래 명령어를 계속 켜두면된다.&lt;/p&gt;
&lt;pre id=&quot;code_1775120515740&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;minikube tunnel&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Ingress로 변경 후 테스트 결과&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ingress 추가 후 테스트를 해본 결과 이전과의 결과를 비교하면 다음 표로 나타낼 수 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 71.1619%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 9.4186%; text-align: center;&quot;&gt;항목&lt;/td&gt;
&lt;td style=&quot;width: 12.5581%; text-align: center;&quot;&gt;Port-forward&lt;/td&gt;
&lt;td style=&quot;width: 16.0466%; text-align: center;&quot;&gt;Ingress&lt;/td&gt;
&lt;td style=&quot;width: 33.1395%; text-align: center;&quot;&gt;의미&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 9.4186%; text-align: center;&quot;&gt;에러율&lt;/td&gt;
&lt;td style=&quot;width: 12.5581%; text-align: center;&quot;&gt;6~8%&lt;/td&gt;
&lt;td style=&quot;width: 16.0466%; text-align: center;&quot;&gt;0.00%&lt;/td&gt;
&lt;td style=&quot;width: 33.1395%; text-align: center;&quot;&gt;타임아웃 없이 모든 요청을 다 받아냈다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 9.4186%; text-align: center;&quot;&gt;처리량&lt;/td&gt;
&lt;td style=&quot;width: 12.5581%; text-align: center;&quot;&gt;약 300회&lt;/td&gt;
&lt;td style=&quot;width: 16.0466%; text-align: center;&quot;&gt;1,005회 (3배 이상)&lt;/td&gt;
&lt;td style=&quot;width: 33.1395%; text-align: center;&quot;&gt;시스템이 3배 이상의 일을 소화함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 9.4186%; text-align: center;&quot;&gt;중간값&lt;/td&gt;
&lt;td style=&quot;width: 12.5581%; text-align: center;&quot;&gt;20초 이상&lt;/td&gt;
&lt;td style=&quot;width: 16.0466%; text-align: center;&quot;&gt;415ms (0.4초)&lt;/td&gt;
&lt;td style=&quot;width: 33.1395%; text-align: center;&quot;&gt;대다수 사용자가 매우 쾌적함을 느낄 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 9.4186%; text-align: center;&quot;&gt;평균 응답&lt;/td&gt;
&lt;td style=&quot;width: 12.5581%; text-align: center;&quot;&gt;24초&lt;/td&gt;
&lt;td style=&quot;width: 16.0466%; text-align: center;&quot;&gt;7.09초&lt;/td&gt;
&lt;td style=&quot;width: 33.1395%; text-align: center;&quot;&gt;전체적으로 시스템 안정성 대폭 개선&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 표를 볼때 Ingress에서 중간값은 0.4초인데 반해 평균 응답이 7초인 이유는 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초반: 테스트 직후, 파드가 1개일 때는 트래픽 감당을 못 해 응답 시간이 치솟음&lt;/li&gt;
&lt;li&gt;중반: HPA가 작동해서 파드가 5~10개로 늘어난 시점부터는 응답 속도가 0.4초로 뚝 떨어졌다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 시스템이 스스로 판단하고 확장해서 안정을 찾았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 추가적으로 kubectl get hpa -w 결과를 바탕으로 트래픽이 줄어들며 자원을 다시 반납하는 것까지 확인할 수 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-04-02 오후 6.04.42.png&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;1118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbojz0/dJMb990dygk/vxpkT1setJ1gjF65u3BuK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbojz0/dJMb990dygk/vxpkT1setJ1gjF65u3BuK1/img.png&quot; data-alt=&quot;Post-forwarding을 Ingress로 변경 후 트래픽 테스팅 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbojz0/dJMb990dygk/vxpkT1setJ1gjF65u3BuK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbbojz0%2FdJMb990dygk%2FvxpkT1setJ1gjF65u3BuK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;629&quot; height=&quot;515&quot; data-filename=&quot;스크린샷 2026-04-02 오후 6.04.42.png&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;1118&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Post-forwarding을 Ingress로 변경 후 트래픽 테스팅 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-04-02 오후 6.12.23.png&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/delFQW/dJMcafTD9F6/LT0My430zkQ1Xaek4aTuh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/delFQW/dJMcafTD9F6/LT0My430zkQ1Xaek4aTuh1/img.png&quot; data-alt=&quot;명령어를 통한 CPU 사용량에 따른 REPLICAS 변화량 추이&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/delFQW/dJMcafTD9F6/LT0My430zkQ1Xaek4aTuh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdelFQW%2FdJMcafTD9F6%2FLT0My430zkQ1Xaek4aTuh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;611&quot; height=&quot;194&quot; data-filename=&quot;스크린샷 2026-04-02 오후 6.12.23.png&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;336&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;명령어를 통한 CPU 사용량에 따른 REPLICAS 변화량 추이&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>코딩/k8s</category>
      <category>HPA</category>
      <category>ingress</category>
      <category>K6</category>
      <category>k8s</category>
      <category>minikube</category>
      <category>부하테스트</category>
      <author>khseon7</author>
      <guid isPermaLink="true">https://khseon7.tistory.com/34</guid>
      <comments>https://khseon7.tistory.com/34#entry34comment</comments>
      <pubDate>Thu, 2 Apr 2026 18:14:17 +0900</pubDate>
    </item>
    <item>
      <title>[Git/GitHub] 계정 전환 오류 해결: 왜 자꾸 이전 계정으로 요청을 보낼까?</title>
      <link>https://khseon7.tistory.com/33</link>
      <description>&lt;p data-path-to-node=&quot;1&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;GitHub 계정을 여러 개 사용하다 보면, 분명 로그인은 새 계정으로 했는데 Git은 여전히 이전 계정의 권한을 사용하려다 403 Forbidden 오류를 내뱉는 경우가 많습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 **이전 계정(Old)**에서 **신규 계정(New)**으로 깔끔하게 갈아타는 5단계 해결법을 정리합니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size23&quot;&gt;  이번 가이드의 설정 기준&lt;/h3&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;블로그를 보시는 분들은 아래 명칭에 본인의 계정명을 대입해 주세요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,0,0&quot;&gt;OLD_USER&lt;/b&gt;: 이전에 사용하던 계정&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,0&quot;&gt;NEW_USER&lt;/b&gt;: 새로 사용하려는 계정&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;11&quot; data-ke-size=&quot;size23&quot;&gt;1단계: GitHub CLI(gh) 현재 상태 확인&lt;/h3&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;먼저 GitHub CLI가 현재 어떤 계정을 'Active(활성)' 상태로 잡고 있는지 확인합니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiPmc7uk66TAxUAAAAAHQAAAAAQuwE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;gh auth status
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,0,0&quot;&gt;확인:&lt;/b&gt; Active account가 본인의 **NEW_USER**인지 체크하세요.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,0&quot;&gt;해결:&lt;/b&gt; 만약 다른 계정이 활성화되어 있다면 아래 명령어로 즉시 전환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;gh auth switch
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-path-to-node=&quot;16&quot; data-ke-size=&quot;size23&quot;&gt;2단계: 인증 캐시 완전 초기화 (핵심! ⭐)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;상태 확인 후에도 계정이 꼬인다면, 기존의 인증 정보를 완전히 밀어버리고 다시 로그인하는 것이 가장 확실합니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiPmc7uk66TAxUAAAAAHQAAAAAQvQE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;# 1. 기존 세션 로그아웃
gh auth logout --hostname github.com

# 2. 신규 계정(NEW_USER)으로 다시 로그인
gh auth login
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote data-path-to-node=&quot;19&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-path-to-node=&quot;19,0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,0&quot;&gt;  로그인 시 주의사항&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,1,0,0&quot;&gt;- Account:&lt;/b&gt; NEW_USER 계정 정보 입력&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,1,1,0&quot;&gt;- Protocol:&lt;/b&gt; 반드시 HTTPS 선택&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,1,2,0&quot;&gt;- Authentication:&lt;/b&gt; Web 브라우저 인증 방식 권장&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-path-to-node=&quot;21&quot; data-ke-size=&quot;size23&quot;&gt;3단계: Git Credential Helper 설정 정돈&lt;/h3&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;Git이 gh 도구 외에 다른 자격 증명 관리자(Windows 자격 증명 등)를 이중으로 참조하면 계정이 꼬일 수 있습니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiPmc7uk66TAxUAAAAAHQAAAAAQvgE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# 현재 설정된 헬퍼 확인
git config --global credential.helper
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;24&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;24,0,0&quot;&gt;정상:&lt;/b&gt; !gh auth git-credential 하나만 출력되어야 함.&lt;/li&gt;
&lt;li&gt;설정이 꼬여있다면? 아래 명령어로 초기화 후 다시 설정하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# 기존 설정 모두 제거
git config --global --unset credential.helper

# gh 전용 헬퍼로 재설정
git config --global credential.helper '!gh auth git-credential'
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-path-to-node=&quot;26&quot; data-ke-size=&quot;size23&quot;&gt;4단계: Remote URL(원격 주소) 점검&lt;/h3&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;간혹 로컬 저장소의 원격 주소(Remote URL) 자체에 이전 계정 정보가 포함되어 있는 경우가 있습니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiPmc7uk66TAxUAAAAAHQAAAAAQwAE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;git remote -v
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;29&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;29,0,0&quot;&gt;올바른 형태:&lt;/b&gt; https://github.com/NEW_USER/repository-name.git&lt;/li&gt;
&lt;li&gt;만약 주소에 OLD_USER가 포함되어 있다면 아래 명령어로 수정하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;git remote set-url origin https://github.com/NEW_USER/repository-name.git&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-path-to-node=&quot;31&quot; data-ke-size=&quot;size23&quot;&gt;5단계: 강제 인증 거부(Reject) 및 최종 Push&lt;/h3&gt;
&lt;p data-path-to-node=&quot;32&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로 메모리에 남아있을지 모르는 이전 계정의 잔재를 강제로 밀어내고(Reject) 다시 시도합니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwiPmc7uk66TAxUAAAAAHQAAAAAQwgE&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;# 기존 인증 정보 거부 요청
git credential reject &amp;lt;&amp;lt;EOF
protocol=https
host=github.com
EOF

# 이제 새 계정으로 Push!
git push -u origin main
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-path-to-node=&quot;35&quot; data-ke-size=&quot;size23&quot;&gt;  마무리하며&lt;/h3&gt;
&lt;p data-path-to-node=&quot;36&quot; data-ke-size=&quot;size16&quot;&gt;이 과정을 모두 마치면 Git은 더 이상 구 계정(OLD_USER)을 찾지 않고, 새롭게 인증된 NEW_USER 계정으로 정상적인 요청을 보내게 됩니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;37&quot; data-ke-size=&quot;size16&quot;&gt;계정 전환이 잦은 환경이라면 gh auth status를 수시로 확인하는 습관을 들이면 좋습니다!&lt;/p&gt;</description>
      <category>코딩</category>
      <category>Git</category>
      <category>github</category>
      <author>khseon7</author>
      <guid isPermaLink="true">https://khseon7.tistory.com/33</guid>
      <comments>https://khseon7.tistory.com/33#entry33comment</comments>
      <pubDate>Fri, 20 Mar 2026 18:28:58 +0900</pubDate>
    </item>
    <item>
      <title>[논문 리뷰] TERMINAL-BENCH:BENCHMARKING AGENTS ON HARD, REALISTICTASKS IN COMMAND LINE INTERFACES</title>
      <link>https://khseon7.tistory.com/32</link>
      <description>&lt;figure id=&quot;og_1773802138915&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Terminal-Bench: Benchmarking Agents on Hard, Realistic Tasks in Command Line Interfaces&quot; data-og-description=&quot;AI agents may soon become capable of autonomously completing valuable, long-horizon tasks in diverse domains. Current benchmarks either do not measure real-world tasks, or are not sufficiently difficult to meaningfully measure frontier models. To this end,&quot; data-og-host=&quot;arxiv.org&quot; data-og-source-url=&quot;https://arxiv.org/abs/2601.11868&quot; data-og-url=&quot;https://arxiv.org/abs/2601.11868v1&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/RFbr1/dJMb9dHm1kT/rCK7MoRqn9ctEZMUHLJiH1/img.png?width=1200&amp;amp;height=700&amp;amp;face=0_0_1200_700,https://scrap.kakaocdn.net/dn/uhi0R/dJMb9bv1e6r/tUFibFLNGKhwVXdrYzmOj1/img.png?width=1000&amp;amp;height=1000&amp;amp;face=0_0_1000_1000&quot;&gt;&lt;a href=&quot;https://arxiv.org/abs/2601.11868&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://arxiv.org/abs/2601.11868&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/RFbr1/dJMb9dHm1kT/rCK7MoRqn9ctEZMUHLJiH1/img.png?width=1200&amp;amp;height=700&amp;amp;face=0_0_1200_700,https://scrap.kakaocdn.net/dn/uhi0R/dJMb9bv1e6r/tUFibFLNGKhwVXdrYzmOj1/img.png?width=1000&amp;amp;height=1000&amp;amp;face=0_0_1000_1000');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Terminal-Bench: Benchmarking Agents on Hard, Realistic Tasks in Command Line Interfaces&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;AI agents may soon become capable of autonomously completing valuable, long-horizon tasks in diverse domains. Current benchmarks either do not measure real-world tasks, or are not sufficiently difficult to meaningfully measure frontier models. To this end,&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;arxiv.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Task Formulation&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Terminal-Bench의 테스크는 에이전트가 현실적인 터미널 환경에서 상호작용하며 문제를 해결하도록 설계되었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;구성 요소&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;각 테스크는 지시 사항&lt;/li&gt;
&lt;li&gt;초기 환경이 세팅된 Docker 이미지&lt;/li&gt;
&lt;li&gt;정답을 확인할 수 있는 테스트 세트&lt;/li&gt;
&lt;li&gt;인간이 작성한 예제 솔루션&lt;/li&gt;
&lt;li&gt;시간 제한&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결과 중심적 평가&lt;/b&gt;: 테스트는 에이전트가 입력한 명령어 세트나 콘솔 출력을 검사하는 것이 아니라, 최종적으로 &lt;u&gt;컨테이너의 상태가 지시 사항을 충족했는지만을 평가&lt;/u&gt;한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;상호작용적 탐색&lt;/b&gt;: 에이전트는 제공된 Bash 명령어 등 다양한 도구를 사용해 스스로 환경을 탐색하고 조작하며 목표를 달성해야 합니다. 이 과정은 Harbor 프레임워크를 통해 실행되며 Cluade Code, Codex CLI 등 다양한 에이전트를 지원합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1330&quot; data-origin-height=&quot;453&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beaDHq/dJMcahjqTX6/kc2214mlx7cXCrAG7Lk550/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beaDHq/dJMcahjqTX6/kc2214mlx7cXCrAG7Lk550/img.png&quot; data-alt=&quot;Terminal-Bench task architecture&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beaDHq/dJMcahjqTX6/kc2214mlx7cXCrAG7Lk550/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeaDHq%2FdJMcahjqTX6%2Fkc2214mlx7cXCrAG7Lk550%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;670&quot; height=&quot;274&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1330&quot; data-origin-height=&quot;453&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Terminal-Bench task architecture&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Verification&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터셋의 품질을 보장하기 위해 각 테스크는 평균 3시간 가량의 철저한 다단계 검증 과정을 거쳤습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3대 품질기준
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;구체성&lt;/b&gt;: 테스크가 모든 올바른 최종 상태를 명시하고, 테스트가 이를 정확히 포착하는지 확인합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;해결 가능성&lt;/b&gt;: 수동으로 작성된 예제 솔루션을 실행했을 때 모든 테스트가 통과되는지 확인합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;무결성&lt;/b&gt;: 에이전트가 미래의 Git 커밋 내역을 미리 보는 등 현실에 없는 '편법'을 써서 테스트를 통과할 수 없도록 차단합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;검토 프로세스&lt;/b&gt;: 기여자들의 체크리스트 작성, LLM을 활용한 자동화된 실수 검토, 자동화된 솔루션 테스트 외에도, 여러 최신 모델을 통해 테스크를 직접 실행해 보거나 적대적 공격 에이전트를 투입해 시스템의 허점을 악용할 수 있는지 검사하는 과정을 거쳤습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1332&quot; data-origin-height=&quot;753&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/emnYXg/dJMcacbj76s/yU7WdYpLFuldfOkw41CFu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/emnYXg/dJMcacbj76s/yU7WdYpLFuldfOkw41CFu0/img.png&quot; data-alt=&quot;검증 프로세스 흐름도&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/emnYXg/dJMcacbj76s/yU7WdYpLFuldfOkw41CFu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FemnYXg%2FdJMcacbj76s%2FyU7WdYpLFuldfOkw41CFu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;668&quot; height=&quot;463&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1332&quot; data-origin-height=&quot;753&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;검증 프로세스 흐름도&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Composition&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 제출된 229개의 테스크 중 엄격한 심사를 통과한 89개의 테스크로 구성되어 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;다양한 도메인&lt;/b&gt;: 소프트웨어 엔지니어링을 비롯해 시스템 관리, 데이터 과학, 보안, 기계 학습, 비디오 처리 등 매우 광범위한 분야를 다룬다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실제적인 복잡성&lt;/b&gt;: 단순히 패키지를 설치하는 것을 넘어, 'Python 비동기 작업 관리 시 키보드 인터럽트 처리', '레거시 COBOL 코드를 Python으로 완벽히 재작성하기' 등 고도의 작업이 포함되어 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;소요 시간의 현실성&lt;/b&gt;: 전문가의 경우 95% 이상의 테스크를 1일 이내에 해결할 수 있도록 설계되었으나, 주니어 엔지니어 기준으로는 하루에서 최대 일주일 이상이 걸리는 테스크(OCaml 가비지 콜렉터 버그 수정)도 포함되어 있어 모델의 긴 호흡(long-horizon) 능력을 요구한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;935&quot; data-origin-height=&quot;415&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dwkVVH/dJMcahcCQaS/lFJPF3w2KFSMgrKLRKZ8tK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dwkVVH/dJMcahcCQaS/lFJPF3w2KFSMgrKLRKZ8tK/img.png&quot; data-alt=&quot;벤치마크에 포함된 도메인 분포&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dwkVVH/dJMcahcCQaS/lFJPF3w2KFSMgrKLRKZ8tK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdwkVVH%2FdJMcahcCQaS%2FlFJPF3w2KFSMgrKLRKZ8tK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;527&quot; height=&quot;234&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;935&quot; data-origin-height=&quot;415&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;벤치마크에 포함된 도메인 분포&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1300&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wJkxT/dJMcaibyQrr/XAsRKZzZPjMkUlRWj4iQbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wJkxT/dJMcaibyQrr/XAsRKZzZPjMkUlRWj4iQbK/img.png&quot; data-alt=&quot;주니어 엔지니어와 도메인 전문가 기준 테스크를 해결하는데 걸리는 예상 소요 시간 분포&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wJkxT/dJMcaibyQrr/XAsRKZzZPjMkUlRWj4iQbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwJkxT%2FdJMcaibyQrr%2FXAsRKZzZPjMkUlRWj4iQbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;685&quot; height=&quot;82&quot; data-origin-width=&quot;1300&quot; data-origin-height=&quot;156&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;주니어 엔지니어와 도메인 전문가 기준 테스크를 해결하는데 걸리는 예상 소요 시간 분포&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Results&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;16개의 최신 모델과 6개의 에이전트 조합을 통해 3만 번 이상의 테스트를 수행한 결과입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;성능 순위&lt;/b&gt;: 상위권 모델도 65% 미만의 해결률을 보였습니다. GPT-5.2와 Codex CLI 조합(63%)이 1위를 차지했으며, Terminus 2 에이전트 기반의 Claude Opus 4.5(58%), Gemini 3 Pro(57%)가 뒤를 이었습니다. 오픈 소스 모델 중에서는 Kimi K2 Thinking이 36%로 가장 높았습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;에이전트보다 모델의 역량이 중요&lt;/b&gt;: 동일한 에이전트를 사용하더라도 모델을 상위 버전으로 교체했을 때 성능 향상 폭이 훨씬 커, 에이전트 스캐폴딩보다 기반 모델 자체의 능력이 더 중요함을 시사합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;체감 난이도 차이&lt;/b&gt;: 인간이 '어려움'으로 평가한 테스크의 93.3%는 모델에게도 실제로 어려웠습니다. 반면, 인간이 '보통'이라고 평가한 테스크의 54.5%를 모델은 풀지 못했는데, 이는 패턴 인식보다는 &lt;u&gt;창의적이거나 적대적인 추론이 필요한 경우 모델이 취약함&lt;/u&gt;을 보여줍니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모델별 맞춤형 오류 패턴&lt;/b&gt;: 최상위 폐쇄형 모델들은 주로 지시사항을 무시하거나 같은 단계를 무의미하게 반복하는 '실행 오류'가 압도적으로 많이 발생합니다. 반면 오픈소스 모델은 실행, 일관성 유지, 결과 검증 등 모든 영역에서 고르게 실패하는 패턴을 보였습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;명령어 단위의 가장 흔한 실패 원인&lt;/b&gt;: 에이전트가 입력한 명령어의 실패율은 모델에 따라 9.2%~26.7%까지 다양하게 나타납니다. 전체 실패 원인 중 '&lt;u&gt;Command not found&lt;/u&gt;'이 24.1%로 1위를 차지했는데, 이는 에이전트들이 설치되지 않은 패키지나 경로에 없는 실행 파일을 무작정 호출하려다 가로막히는 기초적인 환경 인식 오류를 자주 범하고 있음을 보여줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.05.43.png&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;898&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chTjH8/dJMcafTqEgm/rRWsyHu7qwjXbwWK1Ow8J0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chTjH8/dJMcafTqEgm/rRWsyHu7qwjXbwWK1Ow8J0/img.png&quot; data-alt=&quot;다양한 언어 모델과 에이전트 조합이 달성한 최종 테스크 해결률&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chTjH8/dJMcafTqEgm/rRWsyHu7qwjXbwWK1Ow8J0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchTjH8%2FdJMcafTqEgm%2FrRWsyHu7qwjXbwWK1Ow8J0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;568&quot; height=&quot;386&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.05.43.png&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;898&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;다양한 언어 모델과 에이전트 조합이 달성한 최종 테스크 해결률&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.13.23.png&quot; data-origin-width=&quot;1324&quot; data-origin-height=&quot;824&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjoYwm/dJMcaaR8yn3/DL1hucjRlkChnmdzVpbGmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjoYwm/dJMcaaR8yn3/DL1hucjRlkChnmdzVpbGmk/img.png&quot; data-alt=&quot;모델 출시일에 따른 벤치마크 성능 변화 추이&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjoYwm/dJMcaaR8yn3/DL1hucjRlkChnmdzVpbGmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjoYwm%2FdJMcaaR8yn3%2FDL1hucjRlkChnmdzVpbGmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;411&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.13.23.png&quot; data-origin-width=&quot;1324&quot; data-origin-height=&quot;824&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;모델 출시일에 따른 벤치마크 성능 변화 추이&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.12.30.png&quot; data-origin-width=&quot;572&quot; data-origin-height=&quot;326&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9qWmD/dJMcafeQ2Pr/i71X2UILMwkCb0lhIaurck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9qWmD/dJMcafeQ2Pr/i71X2UILMwkCb0lhIaurck/img.png&quot; data-alt=&quot;사람이 예측한 난이도와 모델이 풀며 나타난 난이도 간 상관관계&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9qWmD/dJMcafeQ2Pr/i71X2UILMwkCb0lhIaurck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9qWmD%2FdJMcafeQ2Pr%2Fi71X2UILMwkCb0lhIaurck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;445&quot; height=&quot;254&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.12.30.png&quot; data-origin-width=&quot;572&quot; data-origin-height=&quot;326&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;사람이 예측한 난이도와 모델이 풀며 나타난 난이도 간 상관관계&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.14.40.png&quot; data-origin-width=&quot;1312&quot; data-origin-height=&quot;436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C2EH9/dJMcaflAwOM/N1bSxg5ohesd7VVdJWKEvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C2EH9/dJMcaflAwOM/N1bSxg5ohesd7VVdJWKEvK/img.png&quot; data-alt=&quot;LLM 종류별 오류 비중&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C2EH9/dJMcaflAwOM/N1bSxg5ohesd7VVdJWKEvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC2EH9%2FdJMcaflAwOM%2FN1bSxg5ohesd7VVdJWKEvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;667&quot; height=&quot;222&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.14.40.png&quot; data-origin-width=&quot;1312&quot; data-origin-height=&quot;436&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;LLM 종류별 오류 비중&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.14.49.png&quot; data-origin-width=&quot;976&quot; data-origin-height=&quot;638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddyzUf/dJMcabjc5Zv/7V23Ny64tApzhIAr1MnXz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddyzUf/dJMcabjc5Zv/7V23Ny64tApzhIAr1MnXz0/img.png&quot; data-alt=&quot;에이전트가 실패한 개별 명령어를 시각화한 원형 차트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddyzUf/dJMcabjc5Zv/7V23Ny64tApzhIAr1MnXz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddyzUf%2FdJMcabjc5Zv%2F7V23Ny64tApzhIAr1MnXz0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;511&quot; height=&quot;334&quot; data-filename=&quot;스크린샷 2026-03-18 오후 12.14.49.png&quot; data-origin-width=&quot;976&quot; data-origin-height=&quot;638&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;에이전트가 실패한 개별 명령어를 시각화한 원형 차트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Limitations&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벤치마크의 현실성을 극대화하기 위해 인터넷 접속을 허용하면서 발생한 불가피한 한계들입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;재현성의 문제&lt;/b&gt;: 의존성 패키지나 외부 API 환경이 시간이 지남에 따라 변동될 수 있으며, 실행되는 기기의 하드웨어 리소스 차이로 인해 결과가 달라질 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;부정행위 및 데이터 오염 위험&lt;/b&gt;: 에이전트가 인터넷에서 벤치마크의 정답을 직접 검색하여 부정행위를 할 가능성이 존재합니다. 또한, LLM 개발사들이 이 데이터셋을 학습에 사용할 위험을 막기 위해 '카나리아 문자열'을 삽입했으나, 의도적인 학습 오염을 완벽히 막기엔 한계가 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;잔존 오류 가능성&lt;/b&gt;: 테스크당 3시간 이상의 방대한 수작업 검증과 LLM 검토를 거쳤음에도 불구하고, 과제들이 워낙 다양하고 복잡하여 여전히 일부 테스트 스펙이나 지시 사항에 결함이 남아있을 가능성을 배제할 수 없습니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Agent</category>
      <category>benchmark</category>
      <category>LLM</category>
      <category>Terminal-bench</category>
      <author>khseon7</author>
      <guid isPermaLink="true">https://khseon7.tistory.com/32</guid>
      <comments>https://khseon7.tistory.com/32#entry32comment</comments>
      <pubDate>Wed, 18 Mar 2026 12:37:50 +0900</pubDate>
    </item>
    <item>
      <title>[kotlin/JVM] 힙 메모리는 충분한데 왜 OOM이 뜰까?</title>
      <link>https://khseon7.tistory.com/31</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 운영중인 Kotlin 기반 WAS 시스템에서 java.lang.OutOfMemoryError: Java heap space가 발생했습니다. 처음에는 단순한 메모리 누수(Memory Leak)라고 생각했지만, 분석 결과 범인은 JVM 내부의 &lt;b data-index-in-node=&quot;139&quot; data-path-to-node=&quot;5&quot;&gt;GCLocker&lt;/b&gt;였습니다. 주니어 개발자로서 이 문제를 어떻게 분석하고 해결했는지 기록을 남깁니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3Pa4X/dJMcaca6Iyv/GIRgvKDSKm78qKQq5p129k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3Pa4X/dJMcaca6Iyv/GIRgvKDSKm78qKQq5p129k/img.png&quot; data-alt=&quot;Generated with Google Gemini&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3Pa4X/dJMcaca6Iyv/GIRgvKDSKm78qKQq5p129k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3Pa4X%2FdJMcaca6Iyv%2FGIRgvKDSKm78qKQq5p129k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;438&quot; height=&quot;438&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Generated with Google Gemini&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 문제 현상 및 분석&lt;/h3&gt;
&lt;h4 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size20&quot;&gt;[현상]&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 시점에 스레드가 급증(Thread Explosion)함.&lt;/li&gt;
&lt;li&gt;충분한 Heap 메모리가 할당되어 있음에도 불구하고 GC가 수행되지 않고 OOM 발생.&lt;/li&gt;
&lt;li&gt;로그 확인 결과, JNI(Java Native Interface) 호출과 관련된 지점에서 병목 현상 발견.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size20&quot;&gt;[원인: GCLocker란?]&lt;/h4&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11&quot;&gt;GCLocker&lt;/b&gt;는 Java 코드에서 JNI를 통해 Native 코드를 실행할 때, Native 코드에서 참조하는 Java 객체가 GC에 의해 위치가 바뀌거나 삭제되지 않도록 &lt;b&gt;GC를 일시적으로 차단(Lock)&lt;/b&gt;하는 메커니즘입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,0&quot;&gt;문제의 연결고리&lt;/b&gt;:
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;12,0,1&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;스케줄러가 짧은 주기로 수많은 Task를 생성하며 JNI 임계 구역(Critical Section)에 진입.&lt;/li&gt;
&lt;li&gt;GCLocker가 활성화되어 GC가 트리거되지 못하고 대기 상태에 빠짐.&lt;/li&gt;
&lt;li&gt;그 사이 Heap에는 계속해서 객체가 쌓이고, 결국 GC가 돌기도 전에 메모리가 꽉 차버려 OOM이 발생.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;14&quot; data-ke-size=&quot;size23&quot;&gt;2. 해결 과정: 최적화 전략&lt;/h3&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;문제를 해결하기 위해 크게 두 가지 방향으로 접근했습니다.&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;16&quot; data-ke-size=&quot;size20&quot;&gt;① ThreadPoolExecutor 도입 (Concurrency Control)&lt;/h4&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;무분별하게 생성되던 스레드를 제어하기 위해 ThreadPoolExecutor를 적용했습니다. 스레드 개수를 제한함으로써 JNI 임계 구역에 동시에 머무는 스레드 수를 조절했고, 이는 GCLocker가 해제될 틈을 만들어 주었습니다.&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;18&quot; data-ke-size=&quot;size20&quot;&gt;② 객체 생성 최소화 (Object Allocation Optimization)&lt;/h4&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;매 루프마다 불필요하게 생성되던 객체들을 재사용하거나 생성을 억제했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;20&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,0,0&quot;&gt;Before&lt;/b&gt;: 반복문 내에서 매번 새로운 객체 할당 &amp;rarr; GC 부하 증가&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,1,0&quot;&gt;After&lt;/b&gt;: 싱글톤 패턴이나 객체 풀링(Pooling) 개념을 적용하여 Heap 할당량 자체를 줄임&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;22&quot; data-ke-size=&quot;size23&quot;&gt;3. 마치며: 배운 점&lt;/h3&gt;
&lt;p data-path-to-node=&quot;23&quot; data-ke-size=&quot;size16&quot;&gt;이번 장애를 통해 단순히 &quot;메모리가 부족하면 늘린다&quot;는 식의 접근이 얼마나 위험한지 깨달았습니다. JVM의 GC 메커니즘, 특히 &lt;b&gt;Native 영역과의 상호작용(GCLocker)&lt;/b&gt;이 Java Heap 영역에 어떤 영향을 주는지 깊이 이해할 수 있는 계기가 되었습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;특히 주니어 단계에서 겪기 힘든 &lt;b data-index-in-node=&quot;18&quot; data-path-to-node=&quot;24&quot;&gt;OOM 디버깅과 스레드 최적화&lt;/b&gt;를 직접 경험하며, 시스템의 안정성은 코드 한 줄뿐만 아니라 런타임 환경에 대한 이해에서 온다는 것을 다시 한번 확인했습니다.&lt;/p&gt;</description>
      <category>코딩</category>
      <category>JVM</category>
      <category>kotlin</category>
      <category>OOM</category>
      <category>troubleshooting</category>
      <author>khseon7</author>
      <guid isPermaLink="true">https://khseon7.tistory.com/31</guid>
      <comments>https://khseon7.tistory.com/31#entry31comment</comments>
      <pubDate>Tue, 24 Feb 2026 23:14:47 +0900</pubDate>
    </item>
  </channel>
</rss>