<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>방구석 개발자의 실험실</title>
    <link>https://snapcode.tistory.com/</link>
    <description>즐거운 마음으로 끝없이 성장의 길을 걷는 평범한 개발자</description>
    <language>ko</language>
    <pubDate>Tue, 14 Apr 2026 19:19:24 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>snapcoder</managingEditor>
    <image>
      <title>방구석 개발자의 실험실</title>
      <url>https://tistory1.daumcdn.net/tistory/6452800/attach/44f0bccac950492e86f10addd9820a59</url>
      <link>https://snapcode.tistory.com</link>
    </image>
    <item>
      <title>[AWS] ACM(Certificate Manager) 완벽 가이드: SSL/TLS 인증서 관리부터 ALB 연결까지</title>
      <link>https://snapcode.tistory.com/entry/AWS-ACMCertificate-Manager-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-%E2%80%94-SSLTLS-%EC%9D%B8%EC%A6%9D%EC%84%9C-%EA%B4%80%EB%A6%AC%EB%B6%80%ED%84%B0-ALB-%EC%97%B0%EA%B2%B0%EA%B9%8C%EC%A7%80</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eTdaEQ/dJMcaadRZ0s/TTc75ioF8wKc9sQbEKe250/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eTdaEQ/dJMcaadRZ0s/TTc75ioF8wKc9sQbEKe250/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eTdaEQ/dJMcaadRZ0s/TTc75ioF8wKc9sQbEKe250/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeTdaEQ%2FdJMcaadRZ0s%2FTTc75ioF8wKc9sQbEKe250%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;666&quot; height=&quot;375&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  들어가기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라우드 환경에서 HTTPS 통신을 구현하려면 &lt;b&gt;SSL/TLS 인증서&lt;/b&gt;가 필수입니다. AWS는 이를 관리하기 위해 &lt;b&gt;ACM(AWS Certificate Manager)&lt;/b&gt;라는 서비스를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 ACM이 무엇인지부터 시작해, 인증서 선택 기준, 등록 방법, 그리고 ALB와의 연결 방식까지 &lt;b&gt;실무 관점에서의 완전한 가이드&lt;/b&gt;를 소개합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;상품 페이지 : &lt;a href=&quot;https://aws.amazon.com/ko/certificate-manager/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://aws.amazon.com/ko/certificate-manager/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1776088876150&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;Certificate Manager - AWS Certificate Manager - AWS&quot; data-og-description=&quot;AWS Certificate Manager를 사용하여 AWS 서비스 및 연결된 내부 리소스를 통해 퍼블릭 및 프라이빗 SSL/TLS 인증서를 프로비저닝, 관리 및 배포하는 데 도움이 됩니다.&quot; data-og-host=&quot;aws.amazon.com&quot; data-og-source-url=&quot;https://aws.amazon.com/ko/certificate-manager/&quot; data-og-url=&quot;https://aws.amazon.com/ko/certificate-manager/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://aws.amazon.com/ko/certificate-manager/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://aws.amazon.com/ko/certificate-manager/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;Certificate Manager - AWS Certificate Manager - AWS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;AWS Certificate Manager를 사용하여 AWS 서비스 및 연결된 내부 리소스를 통해 퍼블릭 및 프라이빗 SSL/TLS 인증서를 프로비저닝, 관리 및 배포하는 데 도움이 됩니다.&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;p data-ke-size=&quot;size16&quot;&gt;사용 설명서 : &lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/acm/latest/userguide/acm-overview.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.aws.amazon.com/ko_kr/acm/latest/userguide/acm-overview.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1776088945983&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 Certificate Manager란 무엇인가요? - AWS 인증서 관리자&quot; data-og-description=&quot;이 페이지에 작업이 필요하다는 점을 알려 주셔서 감사합니다. 실망시켜 드려 죄송합니다. 잠깐 시간을 내어 설명서를 향상시킬 수 있는 방법에 대해 말씀해 주십시오.&quot; data-og-host=&quot;docs.aws.amazon.com&quot; data-og-source-url=&quot;https://docs.aws.amazon.com/ko_kr/acm/latest/userguide/acm-overview.html&quot; data-og-url=&quot;https://docs.aws.amazon.com/ko_kr/acm/latest/userguide/acm-overview.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/acm/latest/userguide/acm-overview.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.aws.amazon.com/ko_kr/acm/latest/userguide/acm-overview.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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 Certificate Manager란 무엇인가요? - AWS 인증서 관리자&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 페이지에 작업이 필요하다는 점을 알려 주셔서 감사합니다. 실망시켜 드려 죄송합니다. 잠깐 시간을 내어 설명서를 향상시킬 수 있는 방법에 대해 말씀해 주십시오.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.aws.amazon.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1️⃣ SSL/TLS 인증서란 무엇인가?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 개념&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SSL/TLS 인증서&lt;/b&gt;는 웹 서버와 클라이언트 간의 통신을 &lt;b&gt;암호화&lt;/b&gt;하고, &lt;b&gt;서버의 신원을 검증&lt;/b&gt;하는 디지털 인증서입니다.&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;HTTP 통신 (평문)
  &amp;darr;
사용자 데이터 노출 위험 ❌

HTTPS 통신 (암호화된 HTTP + SSL/TLS)
  &amp;darr;
사용자 데이터 보호 ✅&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SSL/TLS의 역할&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;데이터 암호화&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;사용자 입력 정보(비밀번호, 개인정보)를 암호화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;서버 검증&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;접속한 서버가 진짜인지 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;무결성 보장&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;데이터가 전송 중에 변조되지 않았음을 보장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;SEO 개선&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;검색엔진이 HTTPS 사이트를 더 높게 평가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2️⃣ AWS ACM(Certificate Manager)이란?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ACM의 역할&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AWS Certificate Manager(ACM)&lt;/b&gt;는 &lt;b&gt;SSL/TLS 인증서를 생성, 관리, 배포&lt;/b&gt;하는 관리형 서비스입니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;수동 방식 (외부 CA에서 구입):
  1. 외부 CA 선택
  2. CSR(Certificate Signing Request) 생성
  3. 도메인 검증
  4. 인증서 다운로드
  5. 서버에 설치
  6. 갱신 시기 주의
  &amp;rarr; 번거로움

AWS ACM (자동 관리):
  1. ACM에서 인증서 요청
  2. AWS가 도메인 검증
  3. ALB/NLB에 자동 연결
  4. 자동 갱신 처리
  &amp;rarr; 간편함&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ACM의 주요 특징&lt;/h3&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;: AWS 리소스(ALB, NLB, CloudFront 등)와 함께 사용하면 무료&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;자동 갱신&lt;/b&gt;: 만료 전 자동으로 새 인증서 발급&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;사설 CA 지원&lt;/b&gt;: 자체 인증서 생성 가능&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;멀티 도메인&lt;/b&gt;: 와일드카드(&lt;code&gt;*.example.com&lt;/code&gt;) 지원&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;무료 SSL/TLS 검증&lt;/b&gt;: DNS/이메일 검증 모두 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3️⃣ ACM vs 외부 인증서(Imported) 비교&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ACM 인증서 (AWS에서 발급)&lt;/h3&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;장점:
  ✅ 무료 (AWS 리소스 사용 시)
  ✅ 자동 갱신 (번거로움 없음)
  ✅ 생성 및 관리 간단
  ✅ AWS 리소스와 자동 연동

단점:
  ❌ AWS 리소스에만 사용 가능 (온프레미스 불가)
  ❌ 인증서 다운로드 불가 (오직 AWS 내에서만)
  ❌ 와일드카드만 사용 가능 (단일 도메인 불가)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;외부 인증서 (제3자 CA에서 구입 후 Import)&lt;/h3&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;장점:
  ✅ 다양한 환경 지원 (온프레미스, 다른 클라우드 등)
  ✅ 인증서 다운로드 가능
  ✅ 레거시 시스템 지원

단점:
  ❌ 구매 비용 발생
  ❌ 수동 갱신 필요
  ❌ 관리 복잡도 높음
  ❌ 만료 전 알림 관리 필요&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;언제 어떤 걸 선택할까?&lt;/h3&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;AWS 환경만 사용 &amp;rarr; ACM 추천 (무료 + 자동 갱신)
온프레미스도 필요 &amp;rarr; 외부 인증서 Import (유연성)&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4️⃣ 왜 ALB(Application Load Balancer)에 인증서를 등록하는가?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TLS 종료(Termination) 개념&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TLS 종료&lt;/b&gt;란 &lt;b&gt;로드밸런서가 클라이언트로부터 받은 암호화된 트래픽을 복호화&lt;/b&gt;하는 것을 말합니다.&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;클라이언트 (HTTPS)
  &amp;darr; (암호화된 요청)
ALB (TLS 종료 - 복호화)
  &amp;darr; (HTTP로 복호화)
백엔드 EC2 (일반 HTTP)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 ALB에 인증서를 등록할까?&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;이유&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;암호화 담당&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;클라이언트-ALB 구간만 HTTPS 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;백엔드 부하 감소&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;EC2가 암복호화 작업 하지 않음 &amp;rarr; CPU 절약&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;중앙 관리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;모든 인증서를 ALB에서 통합 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;비용 효율&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;EC2 개별 인증서 관리 불필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;자동 갱신&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;ACM 자동 갱신이 ALB까지 자동 반영&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;예시:
  사용자 ──(HTTPS/암호화)── ALB(인증서 등록)
                            ──(HTTP/평문)── EC2 인스턴스
                            ──(HTTP/평문)── EC2 인스턴스&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5️⃣ NLB vs ALB 인증서 적용 차이&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ALB (Application Load Balancer)&lt;/h3&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;특징:
  - 애플리케이션 계층(Layer 7) 로드밸런싱
  - HTTPS 리스너에 인증서 바로 등록
  - SNI(Server Name Indication) 지원으로 여러 인증서 매핑 가능
  - 호스트명 기반 라우팅 가능

사용 사례:
  - 웹 애플리케이션
  - API 서버
  - 마이크로서비스&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ALB + 인증서 구조&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;ALB의 HTTPS 리스너 (포트 443)
  ├─ 인증서 1: example.com
  ├─ 인증서 2: api.example.com
  └─ 인증서 3: *.example.com

각 인증서에 대해 라우팅 규칙 적용&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;NLB (Network Load Balancer)&lt;/h3&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;특징:
  - 전송 계층(Layer 4) 로드밸런싱
  - 높은 성능과 극저 지연시간
  - TLS 오프로딩 지원 (ALB와 유사)
  - UDP/TCP 동시 지원

사용 사례:
  - 실시간 게임 서버
  - IoT 데이터 수집
  - 극도로 높은 성능 필요&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;NLB + 인증서 구조&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;NLB의 TLS 리스너 (포트 443)
  ├─ 기본 인증서: example.com
  └─ SNI 인증서 매핑도 가능

백엔드는 다양한 프로토콜 지원&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ALB vs NLB 선택 기준&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;ALB&lt;/th&gt;
&lt;th&gt;NLB&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;사용 계층&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Layer 7(응용)&lt;/td&gt;
&lt;td&gt;Layer 4(전송)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;인증서 관리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;쉬움 (호스트명 기반)&lt;/td&gt;
&lt;td&gt;복잡 (IP 기반)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;성능&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;중간&lt;/td&gt;
&lt;td&gt;매우 높음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;비용&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;저가&lt;/td&gt;
&lt;td&gt;고가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;추천 대상&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;일반 웹앱&lt;/td&gt;
&lt;td&gt;고성능 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6️⃣ 키 알고리즘 종류 (ECDSA P-256 vs RSA 2048)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RSA 2048&lt;/h3&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;특징:
  - 오래된 표준 알고리즘
  - 2048비트 키 길이
  - 호환성 매우 높음

장점:
  ✅ 거의 모든 브라우저/디바이스 지원
  ✅ 레거시 시스템 호환

단점:
  ❌ 키 크기 크다 (인증서 크기 증가)
  ❌ 암호화/검증 속도 느림
  ❌ 보안강도 상대적으로 낮음

예):
  인증서 크기: ~2KB
  검증 시간: 상대적으로 느림&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ECDSA P-256 (권장)&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;특징:
  - 최신 타원곡선 암호화
  - 256비트 키 길이 (RSA 2048과 동등 보안강도)

장점:
  ✅ 키 크기 작다 (인증서 크기 작음)
  ✅ 암호화/검증 속도 빠름
  ✅ 보안강도 우수함
  ✅ 모던 브라우저 전부 지원

단점:
  ❌ 매우 오래된 디바이스는 미지원 (극히 드물음)

예):
  인증서 크기: ~900B (RSA 2048 대비 45%)
  검증 시간: 2배 이상 빠름&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;성능 비교&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;RSA 2048:
  인증서 크기: 2,048 bytes
  서명 생성: ~10ms
  서명 검증: ~5ms

ECDSA P-256:
  인증서 크기: 900 bytes  &amp;larr; 45% 작음
  서명 생성: ~3ms         &amp;larr; 3배 빠름
  서명 검증: ~2ms         &amp;larr; 2.5배 빠름&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7️⃣ 키 알고리즘 선택 기준&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ECDSA P-256을 선택해야 할 때 (권장)&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;✅ 현대적인 웹 애플리케이션
✅ 모바일 앱이 주요 사용자
✅ API 서버 (성능 중요)
✅ 극도의 호환성이 필수 아닐 때

&amp;rarr; 대부분의 경우 ECDSA P-256 선택&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RSA 2048을 선택해야 할 때&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;✅ 레거시 디바이스 지원 필수
✅ IE 8 이하 구 브라우저 필요
✅ 매우 오래된 시스템과 호환

&amp;rarr; 극히 드문 경우만&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ACM에서 선택 방법&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;ACM 인증서 요청 페이지:

  키 알고리즘
  ├─ RSA 2048 (호환성 최우선)
  └─ ECDSA P-256 (권장) &amp;larr; 선택&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실무 추천&lt;/b&gt;: &lt;b&gt;ECDSA P-256&lt;/b&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;/li&gt;
&lt;li&gt;호환성도 99.9% 수준&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8️⃣ 인증서 유효 기간 및 자동 갱신&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인증서 유효 기간&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;ACM에서 발급하는 인증서:
  유효 기간: 1년 (13개월)
  갱신 시기: 만료 30일 전부터 자동 갱신 시작&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자동 갱신 흐름&lt;/h3&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;Day 1 (인증서 발급)
  &amp;darr;
Day 335 (만료 30일 전)
  &amp;darr;
AWS: &quot;자동 갱신 진행&quot;
  - 새로운 인증서 생성
  - 도메인 검증 (DNS 또는 이메일)
  - 기존 리소스(ALB 등)에 자동 적용
  &amp;darr;
Day 365 (만료일)
  &amp;darr;
기존 인증서 만료
새 인증서 활성화 ✅
  (기존 서비스 중단 없음)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;만료 전 알림 설정&lt;/h3&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;SNS 토픽 구독으로 알림 수신:
  - 만료 45일 전
  - 만료 30일 전
  - 만료 7일 전
  - 갱신 실패 시&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;중요&lt;/b&gt;: ACM이 자동 갱신하지만, SNS 알림을 꼭 설정해서 문제 발생 시 대비하세요!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9️⃣ 계정/리전별 인증서 독립 관리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;계정별 독립 관리&lt;/h3&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;AWS 계정 A
  └─ ACM 인증서 (example.com)
     └─ 이 계정 내 ALB/NLB만 사용 가능

AWS 계정 B
  └─ 동일한 도메인 인증서 필요
     └─ 계정 B에서도 별도 ACM 인증서 요청 필요&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주의&lt;/b&gt;: &lt;b&gt;한 계정의 인증서는 다른 계정에서 사용 불가&lt;/b&gt;&lt;br /&gt;&amp;rarr; 멀티 계정 환경에서는 각 계정별로 인증서 요청 필요&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;리전별 독립 관리&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;ACM은 리전별 서비스:

  us-east-1 (버지니아)
    └─ 인증서 A 등록

  eu-west-1 (아일랜드)
    └─ 동일 도메인이라도 별도 인증서 필요

  ap-northeast-1 (도쿄)
    └─ 동일 도메인이라도 별도 인증서 필요&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예외&lt;/b&gt;: CloudFront&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;CloudFront는 us-east-1 인증서만 사용 가능
&amp;rarr; CloudFront 연결 시 us-east-1에 인증서 요청&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;리전별 인증서 동기화 (자동 불가)&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;권장 구조:

  Main Region (us-east-1)
    &amp;darr; (수동 구독)
  SNS 토픽
    &amp;darr;
  Backup Region (eu-west-1)
    └─ 별도 ACM 인증서 유지&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;팁&lt;/b&gt;: 멀티 리전 운영 시 각 리전마다 같은 도메인으로 인증서 요청 후, 자동 갱신 설정&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  도메인 검증 방식 (DNS 검증 vs 이메일 검증)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DNS 검증 (권장)&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;특징:
  - ACM이 DNS에 CNAME 레코드 생성
  - AWS가 자동 검증

장점:
  ✅ 완전 자동화 (수동 작업 불필요)
  ✅ 자동 갱신도 자동으로 검증 완료
  ✅ 이메일 받지 못해도 괜찮음

단점:
  ❌ Route 53 또는 지원하는 DNS 공급자 필요
  ❌ DNS 레코드 수정 권한 필요

검증 절차:
  1. ACM 인증서 요청
  2. &quot;DNS로 검증&quot; 선택
  3. AWS 제시 CNAME 레코드 Route 53에 추가
  4. AWS 자동 검증 (보통 5분 이내)
  5. ✅ 인증서 발급&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CNAME 레코드 예시&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;Name: _abc123.example.com
Type: CNAME
Value: _xyz789.acm-validations.aws.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이메일 검증&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;특징:
  - 도메인 관리자 이메일로 검증

장점:
  ✅ DNS 수정 권한 불필요
  ✅ 어떤 DNS 공급자든 가능

단점:
  ❌ 수동 확인 필요
  ❌ 이메일 못 받으면 검증 실패
  ❌ 자동 갱신 시 매번 이메일 승인 필요
  ❌ 갱신 실패 위험 높음

검증 절차:
  1. ACM 인증서 요청
  2. &quot;이메일로 검증&quot; 선택
  3. 도메인 관리자 이메일로 검증 링크 수신
  4. 링크 클릭해서 승인
  5. ✅ 인증서 발급&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;선택 기준&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;AWS Route 53 사용 중    &amp;rarr; DNS 검증 (완전 자동화)
타 DNS 서비스 사용      &amp;rarr; 상황에 따라
  - 자동화 필요         &amp;rarr; DNS 검증
  - 수동 작업 괜찮음    &amp;rarr; 이메일 검증&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;강력 권장&lt;/b&gt;: &lt;b&gt;DNS 검증&lt;/b&gt; (자동 갱신 안정성 때문에)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1️⃣1️⃣ ACM 인증서 등록 방법 (단계별)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 1: ACM 콘솔 접속&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;AWS Management Console
  &amp;darr;
ACM (Certificate Manager) 검색
  &amp;darr;
&quot;인증서 요청&quot; 클릭&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 2: 인증서 유형 선택&lt;/h3&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;공개 인증서 (일반적)
  └─ HTTPS/TLS 통신용

프라이빗 CA
  └─ 내부 통신용&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 3: 도메인 이름 입력&lt;/h3&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;도메인 이름:
  example.com

추가 도메인:
  www.example.com
  *.example.com (와일드카드)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 4: 검증 방식 선택&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;DNS 검증 (권장)
  or
이메일 검증&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 5: 태그 추가 (선택)&lt;/h3&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;키: Environment
값: Production

키: Project
값: MyApp&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 6: 리뷰 및 요청&lt;/h3&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;&quot;요청&quot; 클릭
  &amp;darr;
검증 대기 (DNS: 5분, 이메일: 며칠)
  &amp;darr;
✅ 발급 완료&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1️⃣2️⃣ ALB 리스너 규칙과 인증서 연결 구조&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ALB 기본 구조&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;클라이언트 (인터넷)
  &amp;darr; (HTTPS 443)
ALB (리스너)
  ├─ 리스너 1: 포트 443 (HTTPS)
  │  ├─ 인증서: example.com
  │  └─ 대상 그룹: TargetGroup-1
  │
  └─ 리스너 2: 포트 80 (HTTP)
     └─ 규칙: 포트 443으로 리다이렉트&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ALB HTTPS 리스너 설정 (포트 443)&lt;/h3&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;1. 리스너 생성
   - 프로토콜: HTTPS
   - 포트: 443

2. 보안 정책
   - ELBSecurityPolicy-TLS-1-2-2017-01 (기본)
   - or 커스텀 정책

3. 인증서 선택
   - ACM에서 발급한 인증서 목록 표시
   - 기본 인증서: example.com
   - 추가 인증서: *.example.com

4. 라우팅 규칙
   - 호스트명 기반: example.com &amp;rarr; TargetGroup-A
   - 호스트명 기반: api.example.com &amp;rarr; TargetGroup-B
   - 패턴 매칭: /api/* &amp;rarr; TargetGroup-C&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인증서와 라우팅 규칙 연결 예시&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;ALB 포트 443 (HTTPS)
│
├─ 인증서 1: example.com
│  ├─ 규칙 1: 호스트명 = example.com &amp;rarr; TargetGroup-1 (웹사이트)
│  └─ 규칙 2: 호스트명 = www.example.com &amp;rarr; TargetGroup-1
│
├─ 인증서 2: api.example.com
│  └─ 규칙 3: 호스트명 = api.example.com &amp;rarr; TargetGroup-2 (API)
│
└─ 인증서 3: *.example.com (와일드카드, 폴백)
   └─ 규칙 4: 기타 모든 호스트명 &amp;rarr; TargetGroup-3 (기본값)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SNI(Server Name Indication) 활용&lt;/h3&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;클라이언트가 TLS 핸드셰이크 시 접속하려는 도메인명 전달
  &amp;darr;
ALB가 SNI로 받은 도메인명 확인
  &amp;darr;
해당 인증서로 응답
  &amp;darr;
단일 IP에서 여러 인증서 운영 가능 ✅&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;사용자 1이 example.com 접속
  &amp;rarr; SNI: example.com 전달
  &amp;rarr; ALB: example.com 인증서로 응답

사용자 2가 api.example.com 접속
  &amp;rarr; SNI: api.example.com 전달
  &amp;rarr; ALB: api.example.com 인증서로 응답

모두 동일 ALB IP를 사용하지만 인증서는 분리&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  실무 체크리스트&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ACM 인증서 구축 시&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;✅ ECDSA P-256 키 알고리즘 선택&lt;/li&gt;
&lt;li&gt;✅ DNS 검증 방식 선택&lt;/li&gt;
&lt;li&gt;✅ Route 53에 CNAME 레코드 추가&lt;/li&gt;
&lt;li&gt;✅ 인증서 발급 대기&lt;/li&gt;
&lt;li&gt;✅ SNS 알림 구독 설정&lt;/li&gt;
&lt;li&gt;✅ ALB 포트 443 HTTPS 리스너 생성&lt;/li&gt;
&lt;li&gt;✅ ACM 인증서 연결&lt;/li&gt;
&lt;li&gt;✅ 라우팅 규칙 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;멀티 리전/계정 환경에서&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;✅ 각 리전별로 ACM 인증서 요청&lt;/li&gt;
&lt;li&gt;✅ 각 계정별로 ACM 인증서 요청&lt;/li&gt;
&lt;li&gt;✅ CloudFront 사용 시 us-east-1에만 인증서 구성&lt;/li&gt;
&lt;li&gt;✅ 자동 갱신 설정 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AWS ACM&lt;/b&gt;은 SSL/TLS 인증서를 무료로 생성하고 자동 관리해주는 서비스입니다.&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;ECDSA P-256&lt;/b&gt; 키 알고리즘 선택으로 성능 최적화&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DNS 검증&lt;/b&gt; 방식으로 완전 자동화&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ALB HTTPS 리스너&lt;/b&gt;에 인증서 등록해서 TLS 종료&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SNI&lt;/b&gt; 활용으로 단일 IP에서 다중 도메인 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조를 정확히 이해하면 AWS 환경에서 HTTPS를 안전하고 효율적으로 운영할 수 있습니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;</description>
      <category>IT/Cloud</category>
      <category>ACM</category>
      <category>ALB</category>
      <category>AWS</category>
      <category>DevOps</category>
      <category>https</category>
      <category>SSL</category>
      <category>TLS</category>
      <category>보안</category>
      <category>인증서</category>
      <category>클라우드보안</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/215</guid>
      <comments>https://snapcode.tistory.com/entry/AWS-ACMCertificate-Manager-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C-%E2%80%94-SSLTLS-%EC%9D%B8%EC%A6%9D%EC%84%9C-%EA%B4%80%EB%A6%AC%EB%B6%80%ED%84%B0-ALB-%EC%97%B0%EA%B2%B0%EA%B9%8C%EC%A7%80#entry215comment</comments>
      <pubDate>Mon, 13 Apr 2026 23:06:53 +0900</pubDate>
    </item>
    <item>
      <title>[DevOps] 무중단 배포 전략 4가지 총정리: 롤링,카나리,블루그린,A/B 배포 장단점</title>
      <link>https://snapcode.tistory.com/entry/DevOps-%EB%AC%B4%EC%A4%91%EB%8B%A8-%EB%B0%B0%ED%8F%AC-%EC%A0%84%EB%9E%B5-4%EA%B0%80%EC%A7%80-%EC%B4%9D%EC%A0%95%EB%A6%AC-%EB%A1%A4%EB%A7%81%EC%B9%B4%EB%82%98%EB%A6%AC%EB%B8%94%EB%A3%A8%EA%B7%B8%EB%A6%B0AB-%EB%B0%B0%ED%8F%AC-%EC%9E%A5%EB%8B%A8%EC%A0%90</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;423&quot; data-origin-height=&quot;460&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNVsix/dJMcag58mYJ/gibW90B5k5IPKxPrWBTgg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNVsix/dJMcag58mYJ/gibW90B5k5IPKxPrWBTgg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNVsix/dJMcag58mYJ/gibW90B5k5IPKxPrWBTgg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNVsix%2FdJMcag58mYJ%2FgibW90B5k5IPKxPrWBTgg1%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;423&quot; height=&quot;460&quot; data-origin-width=&quot;423&quot; data-origin-height=&quot;460&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  들어가기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스를 운영하다 보면 새로운 기능을 배포해야 합니다. 하지만 배포 중 서비스가 중단되면 사용자는 불편을 겪게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;무중단 배포(Zero-Downtime Deployment)&lt;/b&gt;는 서비스 운영 중에도 중단 없이 새로운 버전을 배포하는 전략입니다. 오늘은 실무에서 자주 사용되는 &lt;b&gt;4가지 무중단 배포 전략&lt;/b&gt;을 비교하고, 각각의 특징과 선택 기준을 소개합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1️⃣ 롤링 배포 (Rolling Deployment)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정의&lt;/b&gt;: &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;순차적으로&lt;/b&gt;&lt;/span&gt; 구 버전 인스턴스를 제거하고 새 버전으로 교체하는 배포 방식입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동작 원리&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;초기 상태: [v1] [v1] [v1] (총 3개 인스턴스)
           (모두 로드밸런서에 연결)

Step 1: [v1] [v1] [v1]
        └─ 첫 번째 인스턴스를 LB에서 제거
        └ 새로운 버전(v2) 배포
        └ [v2] [v1] [v1]

Step 2: [v2] [v1] [v1]
        └─ 두 번째 인스턴스를 LB에서 제거
        └ [v2] [v2] [v1]

Step 3: [v2] [v2] [v1]
        └─ 세 번째 인스턴스를 LB에서 제거
        └ [v2] [v2] [v2]

완료: 모든 인스턴스가 새 버전(v2)로 업데이트됨&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&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; &amp;mdash; 추가 서버 필요 없음&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;간단한 구현&lt;/b&gt; &amp;mdash; 기본적인 배포 자동화로 가능&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;빠른 배포&lt;/b&gt; &amp;mdash; 병렬로 여러 인스턴스 배포 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점&lt;/h3&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; &amp;mdash; v1과 v2가 동시에 운영되므로 호환성 관리 필요&lt;/li&gt;
&lt;li&gt;❌ &lt;b&gt;문제 발생 시 즉시 복구 어려움&lt;/b&gt; &amp;mdash; 롤백이 오래 걸림&lt;/li&gt;
&lt;li&gt;❌ &lt;b&gt;카나리 테스트 불가&lt;/b&gt; &amp;mdash; 일부만 먼저 배포할 수 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;언제 사용?&lt;/h3&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;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2️⃣ 카나리 배포 (Canary Deployment)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정의&lt;/b&gt;: 새 버전을 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;일부 사용자에게만 먼저 배포&lt;/b&gt;&lt;/span&gt;하고, 문제 없으면 점진적으로 확대하는 방식입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;카나리 배포의 유래&lt;/b&gt;: 광산에서 위험한 가스를 감지하기 위해 카나리새를 광산에 먼저 보냈습니다. 새가 살아있으면 안전하다고 판단했죠. 배포도 마찬가지입니다. 새 버전을 먼저 일부 사용자에게 배포해 &quot;위험한 버그&quot;가 없는지 확인하는 방식입니다!  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동작 원리&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;초기 상태: [v1] [v1] [v1] (100% v1)
           LB 트래픽 분배

Step 1 (10% 카나리): [v2] [v1] [v1]
                     10%  90%
                     (10% 사용자만 v2 체험)

Step 2 (50% 점진): [v2] [v2] [v1]
                   50%  50%
                   (문제 없으면 비율 확대)

Step 3 (100% 완료): [v2] [v2] [v2]
                    (모든 사용자가 v2 사용)

문제 발생 시: 즉시 카나리 버전 중단 후 롤백&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&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; &amp;mdash; 일부 사용자만 영향받음&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;실시간 모니터링&lt;/b&gt; &amp;mdash; 실제 사용자의 반응을 관찰&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;빠른 롤백&lt;/b&gt; &amp;mdash; 문제 감지 시 즉시 중단 가능&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;신뢰도 높음&lt;/b&gt; &amp;mdash; 운영 환경에서 미리 검증&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점&lt;/h3&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; &amp;mdash; 점진적 확대에 시간 필요&lt;/li&gt;
&lt;li&gt;❌ &lt;b&gt;모니터링 필수&lt;/b&gt; &amp;mdash; 지표 추적 및 알림 체계 구축 필요&lt;/li&gt;
&lt;li&gt;❌ &lt;b&gt;버전 혼재&lt;/b&gt; &amp;mdash; 여러 버전이 동시 운영되므로 호환성 관리 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;언제 사용?&lt;/h3&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;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3️⃣ 블루그린 배포 (Blue-Green Deployment)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정의&lt;/b&gt;: 구 버전(Blue)과 새 버전(Green)을 &lt;b&gt;동시에 준비&lt;/b&gt;한 후, 로드밸런서의 트래픽을 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;한 번에 전환&lt;/b&gt;&lt;/span&gt;하는 방식입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동작 원리&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;사전 준비: 구 버전(Blue)과 새 버전(Green) 동시 준비

[Blue 환경]        [Green 환경]
[v1] [v1] [v1] &amp;larr;&amp;rarr; [v2] [v2] [v2]
(현재 운영)        (새 버전 테스트)

LB: Blue &amp;rarr; 100% 트래픽

Step 1: 새 버전(Green)에서 충분한 테스트 수행
       - 기능 검증
       - 성능 테스트
       - 데이터 마이그레이션 확인

Step 2: 테스트 완료 후 LB 전환

LB: Blue &amp;larr; &amp;rarr; Green
    0%    100% (트래픽 즉시 전환)

Step 3: 문제 발생 시 즉시 Blue로 롤백

LB: Blue &amp;larr; &amp;rarr; Green
    100%   0%  (즉시 원래 버전으로 복구)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&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; &amp;mdash; 테스트 완료 후 즉시 전환&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;빠른 롤백&lt;/b&gt; &amp;mdash; 한 번에 이전 버전으로 복구 가능&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;버전 격리&lt;/b&gt; &amp;mdash; v1과 v2가 분리되어 있어 호환성 문제 없음&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;0% 다운타임&lt;/b&gt; &amp;mdash; 트래픽 전환만으로 배포 완료&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;❌ &lt;b&gt;리소스 2배 필요&lt;/b&gt; &amp;mdash; Blue와 Green 환경을 동시 유지해야 함&lt;/li&gt;
&lt;li&gt;❌ &lt;b&gt;비용 증가&lt;/b&gt; &amp;mdash; 인프라 비용 2배&lt;/li&gt;
&lt;li&gt;❌ &lt;b&gt;데이터 동기화 복잡&lt;/b&gt; &amp;mdash; Blue와 Green 간 데이터 정합성 관리 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;언제 사용?&lt;/h3&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;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4️⃣ A/B 배포 (A/B Deployment)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정의&lt;/b&gt;: 구 버전(A)과 새 버전(B)을 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;동시에 운영&lt;/b&gt;&lt;/span&gt;하면서, 특정 사용자 그룹별로 &lt;b&gt;어느 버전을 사용할지 선택&lt;/b&gt;하는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(카나리 배포와 비슷해 보이지만, A/B는 &quot;비율&quot; 기반이 아니라 &quot;그룹&quot; 기반입니다)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동작 원리&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;[버전 A 환경]       [버전 B 환경]
[v1] [v1]          [v2] [v2]

라우팅 규칙:
  - OS = iOS    &amp;rarr; B 버전 (v2)
  - OS = Android &amp;rarr; A 버전 (v1)
  또는
  - User ID % 2 == 0 &amp;rarr; B 버전 (v2)
  - User ID % 2 == 1 &amp;rarr; A 버전 (v1)
  또는
  - Region = Asia &amp;rarr; A 버전 (v1)
  - Region = US &amp;rarr; B 버전 (v2)

결과: 사용자에 따라 다른 버전 경험&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&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; &amp;mdash; 특정 사용자 그룹만 새 버전 체험&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;기능별 검증&lt;/b&gt; &amp;mdash; 특정 국가나 디바이스에서만 배포 가능&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;사용자 세분화&lt;/b&gt; &amp;mdash; 타겟 사용자에게만 새 기능 공개 가능&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;점진적 확대&lt;/b&gt; &amp;mdash; 리스크를 최소화하며 배포 확대&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점&lt;/h3&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; &amp;mdash; 사용자별 어느 버전을 보여줄지 관리 필요&lt;/li&gt;
&lt;li&gt;❌ &lt;b&gt;데이터 일관성 문제&lt;/b&gt; &amp;mdash; A 버전과 B 버전의 데이터 동기화 어려움&lt;/li&gt;
&lt;li&gt;❌ &lt;b&gt;디버깅 어려움&lt;/b&gt; &amp;mdash; 버전별 사용자 행동이 다르면 원인 파악 어려움&lt;/li&gt;
&lt;li&gt;❌ &lt;b&gt;오래 운영 불가&lt;/b&gt; &amp;mdash; 장기 운영 시 유지보수 비용 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;언제 사용?&lt;/h3&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;UI/UX 개선 효과를 측정하고 싶을 때&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  4가지 배포 전략 비교표&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;롤링&lt;/th&gt;
&lt;th&gt;카나리&lt;/th&gt;
&lt;th&gt;블루그린&lt;/th&gt;
&lt;th&gt;A/B&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;배포 시간&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;느림&lt;/td&gt;
&lt;td&gt;매우 느림&lt;/td&gt;
&lt;td&gt;빠름&lt;/td&gt;
&lt;td&gt;중간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;리소스 필요&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;최소&lt;/td&gt;
&lt;td&gt;최소&lt;/td&gt;
&lt;td&gt;2배&lt;/td&gt;
&lt;td&gt;2배&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;롤백 속도&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;느림&lt;/td&gt;
&lt;td&gt;빠름&lt;/td&gt;
&lt;td&gt;매우 빠름&lt;/td&gt;
&lt;td&gt;중간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;버전 격리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;위험도&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;높음&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;td&gt;매우 낮음&lt;/td&gt;
&lt;td&gt;중간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;모니터링&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;기본&lt;/td&gt;
&lt;td&gt;필수&lt;/td&gt;
&lt;td&gt;기본&lt;/td&gt;
&lt;td&gt;필수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;추천 환경&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;저위험&lt;/td&gt;
&lt;td&gt;고위험&lt;/td&gt;
&lt;td&gt;높은 확실성&lt;/td&gt;
&lt;td&gt;실험/검증&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  배포 전략 선택 가이드&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;상황별 추천 전략&lt;/h3&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;Q1: 빠른 배포가 최우선이다
  &amp;rarr; 블루그린 배포

Q2: 버전 호환성이 좋고 리소스가 부족하다
  &amp;rarr; 롤링 배포

Q3: 새 버전이 얼마나 안정적인지 확신할 수 없다
  &amp;rarr; 카나리 배포

Q4: 특정 사용자 그룹에만 새 기능을 공개하고 싶다
  &amp;rarr; A/B 배포

Q5: 장애가 생기면 안되는 주요 서비스이다. (ex 결제쪽)
  &amp;rarr; 블루그린 또는 카나리 배포&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단계별 배포 전략&lt;/h3&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;Dev 환경
  &amp;darr;
Staging: 카나리 배포로 10% 사용자 대상 테스트
  &amp;darr;
Production: 블루그린 배포로 빠르게 전환
  &amp;darr;
모니터링: 이상 발생 시 즉시 롤백&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  실전 팁&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 모니터링은 필수&lt;/h3&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;- 에러율 모니터링 (CPU, 메모리 등)
- 사용자 응답 시간 추적
- 에러 로그 실시간 수집
- 알림 설정 (임계값 도과 시)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 롤백 계획 수립&lt;/h3&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;- 이전 버전의 데이터베이스 백업
- 라우팅 설정 자동화 (빠른 전환)
- 롤백 테스트 주기적 실행&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 버전 간 호환성 검증&lt;/h3&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;- API 응답 형식 호환성 확인
- 데이터베이스 스키마 마이그레이션 테스트
- 캐시 무효화 계획 수립&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;무중단 배포 전략&lt;/b&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;: 빠르고 간단하지만 위험도 높음&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;A/B 배포&lt;/b&gt;: 실험과 검증에 최적화됨&lt;/li&gt;
&lt;/ul&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;당신의 서비스 특성과 인프라에 맞는 배포 전략을 선택하고, 항상 &lt;b&gt;모니터링과 롤백 계획&lt;/b&gt;을 먼저 수립하세요!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;</description>
      <category>IT/etc</category>
      <category>CI/CD</category>
      <category>DevOps</category>
      <category>Kubernetes</category>
      <category>ZeroDowntime</category>
      <category>롤링배포</category>
      <category>무중단배포</category>
      <category>배포</category>
      <category>배포전략</category>
      <category>블루그린</category>
      <category>카나리배포</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/214</guid>
      <comments>https://snapcode.tistory.com/entry/DevOps-%EB%AC%B4%EC%A4%91%EB%8B%A8-%EB%B0%B0%ED%8F%AC-%EC%A0%84%EB%9E%B5-4%EA%B0%80%EC%A7%80-%EC%B4%9D%EC%A0%95%EB%A6%AC-%EB%A1%A4%EB%A7%81%EC%B9%B4%EB%82%98%EB%A6%AC%EB%B8%94%EB%A3%A8%EA%B7%B8%EB%A6%B0AB-%EB%B0%B0%ED%8F%AC-%EC%9E%A5%EB%8B%A8%EC%A0%90#entry214comment</comments>
      <pubDate>Mon, 6 Apr 2026 21:08:11 +0900</pubDate>
    </item>
    <item>
      <title>[SQL] ORDER BY Ties: 하나의 데이터가 여러 Page에 조회되는 이유</title>
      <link>https://snapcode.tistory.com/entry/SQL-ORDER-BY-Ties-%ED%95%98%EB%82%98%EC%9D%98-%EB%8D%B0%EC%9D%B4%ED%84%B0%EA%B0%80-%EC%97%AC%EB%9F%AC-Page%EC%97%90-%ED%91%9C%EC%8B%9C%EB%90%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;제가 아는 tie 는 넥타이 뿐입니다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 하실 분들을 위해(저도 포함입니다), 용어부터 짚고 넘어가보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;# 영어에서, &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;tie&lt;/span&gt; = 묶다 / 연결하다 / 동점&lt;/b&gt; =&amp;gt; 스포츠 경기 동점되면, &amp;ldquo;The game is tied&amp;rdquo; 라고 표현&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;# &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;DB에서, &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;ORDER BY tie&lt;/span&gt; = 정렬 기준 같은 row 존재&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt; =&amp;gt; &amp;ldquo;ORDER BY tie 발생했다&quot; 라고 표현&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;#&amp;nbsp;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;tie-breaker&lt;/span&gt; = 동점을 깨는 기준&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;467&quot; data-origin-height=&quot;523&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vKly7/dJMcahjAZuX/rltTOTq1Ah5EgfiBlUaqR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vKly7/dJMcahjAZuX/rltTOTq1Ah5EgfiBlUaqR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vKly7/dJMcahjAZuX/rltTOTq1Ah5EgfiBlUaqR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvKly7%2FdJMcahjAZuX%2FrltTOTq1Ah5EgfiBlUaqR1%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;333&quot; height=&quot;373&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;523&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 조회 쿼리에서 페이지네이션을 구현할 때 마주친 &lt;b&gt;ORDER BY Ties 버그&lt;/b&gt;를 소개합니다.&lt;br /&gt;&lt;b&gt;실제로는 1개의 행만 존재&lt;/b&gt;하는데, &lt;b&gt;1페이지와 2페이지에 동일한 데이터가 나타나는 현상&lt;/b&gt;을 겪었고, 그 원인과 해결 방법을 정리했습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제 유형:&lt;/b&gt; 데이터베이스 &amp;mdash; LIMIT/OFFSET 페이지네이션 불안정성&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제: 1페이지와 2페이지에서 같은 데이터가 조회된다&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1373&quot; data-origin-height=&quot;908&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9OcdM/dJMcac3A7pF/qLFmCKLyY9g4r6F93yK641/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9OcdM/dJMcac3A7pF/qLFmCKLyY9g4r6F93yK641/img.png&quot; data-alt=&quot;예시 사진은 20개씩 페이징 처리 =&amp;amp;gt; 407번 id가 1페이지와 2페이지에 표시되고 있음.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9OcdM/dJMcac3A7pF/qLFmCKLyY9g4r6F93yK641/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9OcdM%2FdJMcac3A7pF%2FqLFmCKLyY9g4r6F93yK641%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;1373&quot; height=&quot;908&quot; data-origin-width=&quot;1373&quot; data-origin-height=&quot;908&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;예시 사진은 20개씩 페이징 처리 =&amp;gt; 407번 id가 1페이지와 2페이지에 표시되고 있음.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;상황&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DB에는 실제로 데이터가 1개 존재&lt;/li&gt;
&lt;li&gt;API에서 페이지 크기 10으로 첫 번째 페이지(&lt;code&gt;LIMIT 10 OFFSET 0&lt;/code&gt;) 조회&lt;/li&gt;
&lt;li&gt;그 다음 두 번째 페이지(&lt;code&gt;LIMIT 10 OFFSET 10&lt;/code&gt;) 조회&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결과: 같은 1개의 데이터가 두 페이지 모두에 표시됨&lt;/b&gt; ❌&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트 시나리오&lt;/h3&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- DB의 실제 데이터: 1행
SELECT COUNT(*) FROM sample_table;  -- 결과: 1


-- 1페이지 조회
SELECT * FROM sample_table 
ORDER BY created_at DESC, view_count DESC
LIMIT 10 OFFSET 0;
-- 결과: 데이터 1행 출력


-- 2페이지 조회
SELECT * FROM sample_table
ORDER BY created_at DESC, view_count DESC
LIMIT 10 OFFSET 10;
-- 결과: 데이터 1행 출력 (같은 데이터!)&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원인: ORDER BY Ties 문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ORDER BY Ties(데드히트, 동점)&lt;/b&gt;란 정렬 조건의 값이 동일한 여러 행이 있을 때 발생합니다.&lt;br /&gt;LIMIT/OFFSET 페이지네이션에서는 각 쿼리마다 정렬 순서가 보장되지 않아 &lt;b&gt;중복 또는 누락&lt;/b&gt;이 발생할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시로 이해하기&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;실제 데이터 3행:
ID | created_at | view_count
1  | 2025-01-01 | 100
2  | 2025-01-01 | 100   &amp;larr; created_at과 view_count가 같음
3  | 2025-01-01 | 100   &amp;larr; created_at과 view_count가 같음


쿼리 1: ORDER BY created_at DESC, view_count DESC LIMIT 2 OFFSET 0
결과 (경우 1): [ID=1, ID=2]
결과 (경우 2): [ID=1, ID=3]  &amp;larr; 정렬 순서가 보장되지 않음


쿼리 2: ORDER BY created_at DESC, view_count DESC LIMIT 2 OFFSET 2
결과 (경우 1): [ID=3]        &amp;larr; ID=2 누락
결과 (경우 2): [ID=2]        &amp;larr; ID=3 누락&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정렬 값이 같으면, 데이터베이스는 임의의 순서로 반환&lt;/b&gt;할 수 있습니다.&lt;br /&gt;페이지네이션 경계에서 순서가 뒤바뀌면 데이터가 중복되거나 누락됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결책: 최종 Tie-Breaker로 Primary Key 추가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;정렬 조건의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;마지막에 Primary Key(id DESC)를 추가&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;1361&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xRpq9/dJMcabRaySu/KyfnD9WNGFCDsyOA6iu541/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xRpq9/dJMcabRaySu/KyfnD9WNGFCDsyOA6iu541/img.png&quot; data-alt=&quot;2페이지(우측)에서 407 중복 조회되지 않음.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xRpq9/dJMcabRaySu/KyfnD9WNGFCDsyOA6iu541/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxRpq9%2FdJMcabRaySu%2FKyfnD9WNGFCDsyOA6iu541%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;1361&quot; height=&quot;454&quot; data-origin-width=&quot;1361&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2페이지(우측)에서 407 중복 조회되지 않음.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;수정 전&lt;/h3&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;SELECT * FROM sample_table
ORDER BY created_at DESC, view_count DESC
LIMIT 10 OFFSET 0;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;수정 후&lt;/h3&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- id DESC를 최종 tie-breaker로 추가
SELECT * FROM sample_table
ORDER BY created_at DESC, view_count DESC, id DESC
LIMIT 10 OFFSET 0;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개선 효과&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 행이 &lt;b&gt;고유한 id로 최종 정렬&lt;/b&gt; 처리로, 동일한 정렬값을 가진 행들의 순서 보장 문제 해결&lt;/li&gt;
&lt;li&gt;LIMIT/OFFSET 페이지네이션에서 &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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;적용 예시: Java에서 동적 쿼리 생성&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Before (문제 있는 코드)&lt;/h3&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;String query = &quot;SELECT * FROM data_table &quot;
    + &quot;ORDER BY created_at &quot; + direction
    + &quot;, view_count DESC&quot;
    + &quot;, CASE WHEN deadline IS NULL THEN 1 ELSE 0 END&quot;
    + &quot;, deadline ASC&quot;;
    // &amp;larr; 여기서 끝남. Tie-breaker 없음


String sql = query + &quot; LIMIT &quot; + pageSize + &quot; OFFSET &quot; + offset;
ResultSet rs = statement.executeQuery(sql);&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;After (개선된 코드)&lt;/h3&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;// 마지막에 id DESC를 추가 처리로, 정렬 동점에서 발생하는 페이지 경계 중복 데이터 문제 해결
String query = &quot;SELECT * FROM data_table &quot;
    + &quot;ORDER BY created_at &quot; + direction
    + &quot;, view_count DESC&quot;
    + &quot;, CASE WHEN deadline IS NULL THEN 1 ELSE 0 END&quot;
    + &quot;, deadline ASC&quot;
    + &quot;, id DESC&quot;;  // &amp;larr; Tie-breaker 추가


String sql = query + &quot; LIMIT &quot; + pageSize + &quot; OFFSET &quot; + offset;
ResultSet rs = statement.executeQuery(sql);&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;적용 포인트&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;정렬 방식&lt;/th&gt;
&lt;th&gt;수정 사항&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;등록일순&lt;/td&gt;
&lt;td&gt;&lt;code&gt;... created_at DESC&lt;/code&gt; &amp;rarr; &lt;code&gt;... created_at DESC, id DESC&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;조회수순&lt;/td&gt;
&lt;td&gt;&lt;code&gt;... view_count DESC&lt;/code&gt; &amp;rarr; &lt;code&gt;... view_count DESC, id DESC&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;마감일순&lt;/td&gt;
&lt;td&gt;&lt;code&gt;... deadline ASC&lt;/code&gt; &amp;rarr; &lt;code&gt;... deadline ASC, id DESC&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;복합 정렬&lt;/td&gt;
&lt;td&gt;마지막 조건 뒤에 &lt;code&gt;id DESC&lt;/code&gt; 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 정리&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;내용&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;문제&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;정렬값이 같은 행들에서 LIMIT/OFFSET이 불안정함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;원인&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Tie가 발생했을 때 DB의 반환 순서가 보장되지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;해결책&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Primary Key(&lt;code&gt;id DESC&lt;/code&gt;)를 최종 정렬 조건으로 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;효과&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;모든 행의 순서가 고유한 값으로 보장되어 페이지네이션 안정화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;주의사항&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;모든 정렬 방식에 일관되게 적용해야 효과적&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 페이지네이션에서는 &lt;b&gt;정렬 순서의 명확성&lt;/b&gt;이 중요하여&lt;br /&gt;정렬값이 같은 경우를 대비해 &lt;b&gt;항상 Primary Key를 최종 tie-breaker로 지정&lt;/b&gt;하는 것을 권장드립니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/DB</category>
      <category>backend</category>
      <category>DATABASE</category>
      <category>DatabaseTuning</category>
      <category>orderby</category>
      <category>pagination</category>
      <category>SQL</category>
      <category>Tie</category>
      <category>TieBreaker</category>
      <category>버그수정</category>
      <category>페이지네이션</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/212</guid>
      <comments>https://snapcode.tistory.com/entry/SQL-ORDER-BY-Ties-%ED%95%98%EB%82%98%EC%9D%98-%EB%8D%B0%EC%9D%B4%ED%84%B0%EA%B0%80-%EC%97%AC%EB%9F%AC-Page%EC%97%90-%ED%91%9C%EC%8B%9C%EB%90%98%EB%8A%94-%EC%9D%B4%EC%9C%A0#entry212comment</comments>
      <pubDate>Mon, 30 Mar 2026 01:52:10 +0900</pubDate>
    </item>
    <item>
      <title>[Codility] Round Robin 대기시간 합 구하기</title>
      <link>https://snapcode.tistory.com/entry/Codility-Round-Robin-%EB%8C%80%EA%B8%B0%EC%8B%9C%EA%B0%84-%ED%95%A9-%EA%B5%AC%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구현(Simulation) + 수식 유도&lt;/b&gt; 유형 문제를 복기합니다.&lt;br /&gt;단계별 시뮬레이션 없이 &lt;b&gt;수학적 공식으로 각 작업의 완료 시간을 직접 계산&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;736&quot; data-origin-height=&quot;588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uFTtb/dJMcaiir8Nt/wu3l1lfiiKfx2GJLVP28Gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uFTtb/dJMcaiir8Nt/wu3l1lfiiKfx2GJLVP28Gk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uFTtb/dJMcaiir8Nt/wu3l1lfiiKfx2GJLVP28Gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuFTtb%2FdJMcaiir8Nt%2Fwu3l1lfiiKfx2GJLVP28Gk%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;555&quot; height=&quot;443&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;588&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제 유형:&lt;/b&gt; 구현(Simulation) &amp;mdash; 라운드로빈 스케줄링 + 수식 유도&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 요약&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;N개의 작업이 주어진다. 각 작업은 처리에 필요한 시간(정수)을 가진다.&lt;br /&gt;&lt;b&gt;라운드로빈(Round Robin)&lt;/b&gt; 방식으로 1 단위씩 번갈아 처리할 때, 모든 작업의 &lt;b&gt;완료 시간 합&lt;/b&gt;을 구하라.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력 예시:&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;3
3 1 2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 예시:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;13&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;라운드로빈(Round Robin)이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;라운드로빈&lt;/b&gt;은 CPU 스케줄링에서 자주 등장하는 방식입니다.&lt;br /&gt;여러 작업을 &lt;b&gt;정해진 시간 단위(여기서는 1)만큼 공평하게 번갈아 처리&lt;/b&gt;합니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;작업 목록: [3, 1, 2]

시간 1: 0번 작업 처리 (3&amp;rarr;2)
시간 2: 1번 작업 처리 (1&amp;rarr;0) &amp;rarr; 완료! (완료 시간 = 2)
시간 3: 2번 작업 처리 (2&amp;rarr;1)
시간 4: 0번 작업 처리 (2&amp;rarr;1)
시간 5: 2번 작업 처리 (1&amp;rarr;0) &amp;rarr; 완료! (완료 시간 = 5)
시간 6: 0번 작업 처리 (1&amp;rarr;0) &amp;rarr; 완료! (완료 시간 = 6)

총 완료 시간 합: 2 + 5 + 6 = 13&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;풀이 접근 &amp;mdash; 수식으로 직접 계산&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 한 단위씩 시뮬레이션하면 최악의 경우 &lt;code&gt;O(N &amp;times; max_task)&lt;/code&gt; 시간이 걸립니다.&lt;br /&gt;대신 &lt;b&gt;각 작업이 언제 완료되는지를 수식으로 직접 계산&lt;/b&gt;하면 &lt;code&gt;O(N&amp;sup2;)&lt;/code&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;&lt;code&gt;i번째 작업의 완료 시간 = commonDisturbTime + frontDisturbTime + 1&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 항의 의미는 다음과 같습니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;disturbTime = task[i] - 1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;작업 i가 완료되기 직전까지, 다른 작업들이 끼어들 수 있는 최대 횟수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;commonDisturbTime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;모든 작업 j가 &lt;code&gt;min(task[j], disturbTime)&lt;/code&gt; 만큼 방해한 시간의 합&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;frontDisturbTime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;앞에 있으면서 &lt;code&gt;task[i]&lt;/code&gt; 이상인 작업들이 마지막 라운드에서 한 번 더 끼어드는 횟수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;+1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;작업 i 본인의 마지막 처리 1회&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시 추적: [3, 1, 2]&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;0번 작업 (task = 3):&lt;/b&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;code&gt;disturbTime = 2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;commonDisturbTime = min(3,2) + min(1,2) + min(2,2) = 2+1+2 = 5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;frontDisturbTime = 0&lt;/code&gt; (앞에 아무것도 없음)&lt;/li&gt;
&lt;li&gt;완료 시간 = &lt;code&gt;5 + 0 + 1 = 6&lt;/code&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1번 작업 (task = 1):&lt;/b&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;code&gt;disturbTime = 0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;commonDisturbTime = min(3,0) + min(1,0) + min(2,0) = 0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;frontDisturbTime = 1&lt;/code&gt; (앞의 3이 1 이상)&lt;/li&gt;
&lt;li&gt;완료 시간 = &lt;code&gt;0 + 1 + 1 = 2&lt;/code&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2번 작업 (task = 2):&lt;/b&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;code&gt;disturbTime = 1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;commonDisturbTime = min(3,1) + min(1,1) + min(2,1) = 1+1+1 = 3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;frontDisturbTime = 1&lt;/code&gt; (앞의 3이 2 이상)&lt;/li&gt;
&lt;li&gt;완료 시간 = &lt;code&gt;3 + 1 + 1 = 5&lt;/code&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;총합: 6 + 2 + 5 = 13&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드&lt;/h2&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.*;

public class Main {

    public static void main(String[] args) throws IOException{

        // 라운드로빈 대기시간 합 문제
        // RoundRobin : 공평하게 정해진 시간만큼 번갈아가며 처리하는 알고리즘

        // 3, 1, 2 =&amp;gt; 정답은 13
//        시간 1: 0번 (3&amp;rarr;2)
//        시간 2: 1번 (1&amp;rarr;0) &amp;rarr; 완료 (2)
//        시간 3: 2번 (2&amp;rarr;1)
//        시간 4: 0번 (2&amp;rarr;1)
//        시간 5: 2번 (1&amp;rarr;0) &amp;rarr; 완료 (5)
//        시간 6: 0번 (1&amp;rarr;0) &amp;rarr; 완료 (6)

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        StringTokenizer st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken());

        int[] task = new int[n];
        st = new StringTokenizer(br.readLine());
        for(int i=0; i&amp;lt;n; i++){
            task[i] = Integer.parseInt(st.nextToken());
        }

        int dap = 0;
        // i번째 위치한 작업이, 언제 종료되는지를 계산
        for(int i=0; i&amp;lt;n; i++){
            // 본인 포함하여, 완료 직전까지, 한바퀴씩 돌면서, 방해받는 최소 기준 시간
            int disturbTime = task[i]-1;

            // i번째 작업이, 모든 Task들로부터 방해받는 시간들 계산
            int commonDisturbTime = 0;
            for(int j=0; j&amp;lt;n; j++){
                commonDisturbTime+= Math.min(task[j], disturbTime);
            }

            // i번째 작업이, 본인보다 앞에 있고 &amp;amp; 크거나 같은 작업들로부터, 방해받는 시간들 계산
            int frontDisturbTime = 0;
            for(int j=0; j&amp;lt;i; j++){
                if(task[j] &amp;gt;= task[i]){
                    frontDisturbTime++;
                }
            }

            // i번째 작업이, 방해받아왔던 시간들 + 본인 작업 마무리 1 더해주기
            int totalDisturbTime = commonDisturbTime + frontDisturbTime + 1;
            dap += totalDisturbTime;
        }

        // Test Case Example : 3,1,2
        // 3,1,2에서 첫번째 작업 3은, (하나작은 2기준으로 min했을때 2,1,2 총 다섯번 방해받음) + (3이 제일 앞에 위치해서 앞쪽 방해 없음) + 본인마무리한번 = 5+0+1 = 6걸림.
        // 3,1,2에서 두번째 작업 1은, (하나작은 0기준으로 min했을때 0,0,0 방해 없이 바로 끝남) + (1보다 앞에 큰건 3 한개임) + 본인마무리한번 = 0+1+1 = 2걸림.
        // 3,1,2에서 세번째 작업 2는, (하나작은 1기준으로 min했을때 1,1,1 총 세번 방해받음) + (2보다 앞에 큰건 3 한개임) + 본인마무리한번 = 3+1+1 = 5걸림.
        // 총 걸리는 시간은 6+2+5 = 13

        bw.write(String.valueOf(dap));
        bw.flush();
        bw.close();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;요소&lt;/th&gt;
&lt;th&gt;처리 방법&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;완료 전 방해 기준&lt;/td&gt;
&lt;td&gt;&lt;code&gt;disturbTime = task[i] - 1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;전체 방해 시간&lt;/td&gt;
&lt;td&gt;모든 j에 대해 &lt;code&gt;min(task[j], disturbTime)&lt;/code&gt; 합산&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;앞줄 추가 방해&lt;/td&gt;
&lt;td&gt;&lt;code&gt;task[j] &amp;gt;= task[i]&lt;/code&gt;인 앞 작업 카운트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;완료 시간 공식&lt;/td&gt;
&lt;td&gt;&lt;code&gt;commonDisturbTime + frontDisturbTime + 1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;i번째 작업이 방해받는 시간&lt;/b&gt;을 계산하여 완료 시간을 구하면 됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘 단련장/Codility</category>
      <category>codility</category>
      <category>Roundrobin</category>
      <category>라운드로빈</category>
      <category>문제풀이</category>
      <category>스케줄링</category>
      <category>알고리즘</category>
      <category>자바</category>
      <category>코딜리티</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/211</guid>
      <comments>https://snapcode.tistory.com/entry/Codility-Round-Robin-%EB%8C%80%EA%B8%B0%EC%8B%9C%EA%B0%84-%ED%95%A9-%EA%B5%AC%ED%95%98%EA%B8%B0#entry211comment</comments>
      <pubDate>Sat, 28 Mar 2026 23:08:25 +0900</pubDate>
    </item>
    <item>
      <title>[Codility] 그룹화 및 정렬 후 순서 출력하기 (+자릿수 동적 처리)</title>
      <link>https://snapcode.tistory.com/entry/Codility-%EA%B7%B8%EB%A3%B9%ED%99%94-%EB%B0%8F-%EC%A0%95%EB%A0%AC-%ED%9B%84-%EC%88%9C%EC%84%9C-%EC%B6%9C%EB%A0%A5%ED%95%98%EA%B8%B0-%EC%9E%90%EB%A6%BF%EC%88%98-%EB%8F%99%EC%A0%81-%EC%B2%98%EB%A6%AC</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;241&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zSOE0/dJMcagkC0sE/7m8mcZUhiuGAccSxz1kAO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zSOE0/dJMcagkC0sE/7m8mcZUhiuGAccSxz1kAO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zSOE0/dJMcagkC0sE/7m8mcZUhiuGAccSxz1kAO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzSOE0%2FdJMcagkC0sE%2F7m8mcZUhiuGAccSxz1kAO0%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;564&quot; height=&quot;241&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;241&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구현(Simulation)&lt;/b&gt; 유형 문제를 복기합니다.&lt;br /&gt;조건을 그대로 코드로 옮기는 구현 문제이지만, 원본 순서 보존과 자릿수 자동 조정 두 가지를 동시에 챙겨야 했던 문제입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제 유형:&lt;/b&gt; 구현(Simulation) &amp;mdash; 그룹화 + 정렬 + 포맷 처리&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 요약&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;N개의 사진 정보가 주어진다. 각 줄은 &lt;code&gt;파일명, 도시명, 촬영시간&lt;/code&gt; 형식이다.&lt;br /&gt;입력 순서를 유지하면서, 각 사진 이름을 &lt;code&gt;도시명 + 도시 내 시간순 번호 + 확장자&lt;/code&gt; 형식으로 변환하라.&lt;br /&gt;단, 번호 자릿수는 해당 도시의 사진 수에 맞춰 동적으로 맞춘다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력 예시:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;photo.jpg, CityA, 2013-09-05 14:08:15
friend.png, CityB, 2015-06-20 15:13:22
sunset.jpg, CityA, 2013-09-05 14:07:13
lake.png, CityC, 2020-01-01 00:00:01
tree.jpeg, CityB, 2015-06-20 15:13:20
mountain.jpg, CityC, 2020-01-01 00:00:02&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 예시:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;CityA2.jpg
CityB2.png
CityA1.jpg
CityC1.png
CityB1.jpeg
CityC2.jpg&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;풀이 접근&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 아이디어&lt;/b&gt;는 세 단계입니다.&lt;/p&gt;
&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; &amp;mdash; 사진 자체가 아닌 인덱스를 저장해 원본 순서를 보존&lt;/li&gt;
&lt;li&gt;&lt;b&gt;도시 내에서 시간순 정렬&lt;/b&gt; &amp;mdash; 같은 도시 사진끼리만 &lt;code&gt;Collections.sort&lt;/code&gt;로 정렬&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자릿수 동적 계산&lt;/b&gt; &amp;mdash; 도시 내 사진 수의 자릿수를 구해 &lt;code&gt;%0Nd&lt;/code&gt; 포맷으로 번호 생성&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자릿수 처리:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;도시 사진이  9장 이하 &amp;rarr; 1자리 (1, 2, ...)
도시 사진이 10장 이상 &amp;rarr; 2자리 (01, 02, ...)
도시 사진이 100장 이상 &amp;rarr; 3자리 (001, 002, ...)&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 코드&lt;/h2&gt;
&lt;pre id=&quot;code_1774692663468&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.*;

public class Main {

    public static void main(String[] args) throws IOException{

        // 사진 이름 수정 문제. (파일명, 도시, 촬영시간)
        // 도시별로 그룹화하고, 같은 도시 내에서 시간순 정렬하고, 1부터 번호 부여한다음, 원래 입력 순서대로 출력하자.

        int n;
        String dap = &quot;&quot;;

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

        StringBuilder sb = new StringBuilder();
        String line;

        while((line=br.readLine()) != null &amp;amp;&amp;amp; !line.isEmpty()){
            sb.append(line).append(&quot;\n&quot;);
        }
        String[] lines = sb.toString().trim().split(&quot;\n&quot;);
        n = lines.length;

        // mountain.jpg, Seoul, 2020-01-01 00:00:02
        String[] photos = new String[n];
        String[] exts = new String[n];
        String[] cities = new String[n];
        String[] times = new String[n];

        Map&amp;lt;String, List&amp;lt;Integer&amp;gt;&amp;gt; cityMap = new HashMap&amp;lt;&amp;gt;();
        for(int i=0; i&amp;lt;n; i++){
            String[] parts = lines[i].split(&quot;, &quot;);
            photos[i] = parts[0].toString();
            exts[i] = parts[0].toString().substring(parts[0].toString().lastIndexOf(&quot;.&quot;)+1);
            String city = parts[1].toString();
            cities[i] = city;
            times[i] = parts[2].toString();
            // System.out.println(&quot;photo: &quot; + photo + &quot;, ext: &quot; + ext + &quot;, city: &quot; + city + &quot;, time: &quot; + time);

            if(!cityMap.containsKey(city)){
                cityMap.put(city, new ArrayList&amp;lt;Integer&amp;gt;());
            }
            cityMap.get(city).add(i);
        }
//        City: Warsaw
//            Index: 0, Line: photo.jpg, Warsaw, 2013-09-05 14:08:15
//            Index: 2, Line: myFriends.png, Warsaw, 2013-09-05 14:07:13
//            Index: 3, Line: sunset.jpg, Warsaw, 2013-09-05 14:10:00
//        City: Seoul
//            Index: 5, Line: lake.png, Seoul, 2020-01-01 00:00:01
//            Index: 6, Line: mountain.jpg, Seoul, 2020-01-01 00:00:02
//            Index: 7, Line: a.jpg, Seoul, 2020-01-01 00:00:02
//            Index: 8, Line: b.jpg, Seoul, 2020-01-01 00:00:02
//        City: London
//            Index: 1, Line: john.png, London, 2015-06-20 15:13:22
//            Index: 4, Line: tree.jpeg, London, 2015-06-20 15:13:20




        // 각 도시별로 사진 인덱스 리스트를 시간순으로 정렬한 다음, 번호를 붙여서 결과 배열에 저장한다. (2자릿수로 강제 번호 생성)
//        String[] result = new String[n];
//        for(String city : cityMap.keySet()){
//            List&amp;lt;Integer&amp;gt; list = cityMap.get(city);
//            Collections.sort(list, (a,b) -&amp;gt; times[a].compareTo(times[b])); // 같은 도시 내에서, 사진을 시간순으로 정렬
//
//            int cityPhotoCount = list.size();
//            for(int i=0; i&amp;lt;cityPhotoCount; i++){
//                int photoIndex = list.get(i);
//                String printIndex = String.format(&quot;%02d&quot;, i+1); // 2자리로 맞춰서 번호 생성 (01, 02, ...)
//                // 출력 포맷 : 도시이름 + 번호 + 확장자
//                result[photoIndex] = city + printIndex + &quot;.&quot; + exts[photoIndex];
//            }
//        }

        // 각 도시별로 사진 인덱스 리스트를 시간순으로 정렬한 다음, 번호를 붙여서 결과 배열에 저장한다. (유동적인 번호 생성)
        String[] result = new String[n];
        for(String city : cityMap.keySet()){
            List&amp;lt;Integer&amp;gt; list = cityMap.get(city);
            Collections.sort(list, (a,b) -&amp;gt; times[a].compareTo(times[b])); // 같은 도시 내에서, 사진을 시간순으로 정렬

            int cityPhotoCount = list.size();
            int digit = String.valueOf(cityPhotoCount).length(); // 10장이면 2자릿수, 100장이면 3자릿수, ...
            for(int i=0; i&amp;lt;cityPhotoCount; i++){
                int photoIndex = list.get(i);
                String printIndex = String.format(&quot;%0&quot; + digit + &quot;d&quot;, i+1); // 2자리로 맞춰서 번호 생성 (01, 02, ...)
                // 출력 포맷 : 도시이름 + 번호 + 확장자
                result[photoIndex] = city + printIndex + &quot;.&quot; + exts[photoIndex];
            }
        }

        dap = String.join(&quot;\n&quot;, result);

        bw.write(dap);
        bw.flush();
        bw.close();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;고민했던 포인트&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원본 순서를 어떻게 보존할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 사진 객체를 정렬하면 원본 인덱스가 사라진다는 점을 놓쳤습니다.&lt;br /&gt;&lt;b&gt;인덱스를 따로 저장&lt;/b&gt;하고, 결과를 &lt;code&gt;result[원본인덱스]&lt;/code&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;처음 작성 시 &lt;code&gt;%02d&lt;/code&gt;로 고정했는데, 도시 사진이 100장 이상이면 &lt;code&gt;099&lt;/code&gt;, &lt;code&gt;100&lt;/code&gt;이 섞여 오답이 됩니다.&lt;br /&gt;&lt;code&gt;String.valueOf(list.size()).length()&lt;/code&gt;로 &lt;b&gt;자릿수를 동적으로 계산&lt;/b&gt;해야 모든 케이스를 커버합니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;도시 사진 수&lt;/th&gt;
&lt;th&gt;고정 &lt;code&gt;%02d&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;동적 처리&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;9장&lt;/td&gt;
&lt;td&gt;&lt;code&gt;01&lt;/code&gt;~&lt;code&gt;09&lt;/code&gt; ✅&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;~&lt;code&gt;9&lt;/code&gt; ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10장&lt;/td&gt;
&lt;td&gt;&lt;code&gt;01&lt;/code&gt;~&lt;code&gt;10&lt;/code&gt; ✅&lt;/td&gt;
&lt;td&gt;&lt;code&gt;01&lt;/code&gt;~&lt;code&gt;10&lt;/code&gt; ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100장&lt;/td&gt;
&lt;td&gt;&lt;code&gt;01&lt;/code&gt;~&lt;code&gt;100&lt;/code&gt; ❌&lt;/td&gt;
&lt;td&gt;&lt;code&gt;001&lt;/code&gt;~&lt;code&gt;100&lt;/code&gt; ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시간 파싱 없이 정렬 가능한가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;2020-01-01 00:00:02&lt;/code&gt; 형식은 &lt;b&gt;문자열 사전순 정렬이 시간순 정렬과 동일&lt;/b&gt;합니다.&lt;br /&gt;&lt;code&gt;Date&lt;/code&gt; 파싱 없이 &lt;code&gt;times[a].compareTo(times[b])&lt;/code&gt;만으로 충분합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;요소&lt;/th&gt;
&lt;th&gt;처리 방법&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;도시별 그룹화&lt;/td&gt;
&lt;td&gt;&lt;code&gt;HashMap&amp;lt;String, List&amp;lt;Integer&amp;gt;&amp;gt;&lt;/code&gt; &amp;mdash; 인덱스 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;시간순 정렬&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Collections.sort&lt;/code&gt; + 문자열 비교&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;자릿수 맞춤&lt;/td&gt;
&lt;td&gt;&lt;code&gt;String.valueOf(size).length()&lt;/code&gt; &amp;rarr; &lt;code&gt;%0Nd&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;원본 순서 보존&lt;/td&gt;
&lt;td&gt;&lt;code&gt;result[원본인덱스]&lt;/code&gt;에 직접 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HashMap으로 그룹화하고, 인덱스를 기준으로 결과를 역추적&lt;/b&gt;하는 패턴으로 해결.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘 단련장/Codility</category>
      <category>Java</category>
      <category>구현</category>
      <category>그룹화</category>
      <category>문자열비교</category>
      <category>복기</category>
      <category>시뮬레이션</category>
      <category>알고리즘</category>
      <category>정렬</category>
      <category>코딜리티</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/210</guid>
      <comments>https://snapcode.tistory.com/entry/Codility-%EA%B7%B8%EB%A3%B9%ED%99%94-%EB%B0%8F-%EC%A0%95%EB%A0%AC-%ED%9B%84-%EC%88%9C%EC%84%9C-%EC%B6%9C%EB%A0%A5%ED%95%98%EA%B8%B0-%EC%9E%90%EB%A6%BF%EC%88%98-%EB%8F%99%EC%A0%81-%EC%B2%98%EB%A6%AC#entry210comment</comments>
      <pubDate>Sat, 28 Mar 2026 19:11:44 +0900</pubDate>
    </item>
    <item>
      <title>[Codility] 자릿수 합이 N인 가장 작은 수 (#Greedy)</title>
      <link>https://snapcode.tistory.com/entry/Codility-%EC%9E%90%EB%A6%BF%EC%88%98-%ED%95%A9%EC%9D%B4-N%EC%9D%B8-%EA%B0%80%EC%9E%A5-%EC%9E%91%EC%9D%80-%EC%88%98-Greedy</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[Codility]&amp;nbsp;자릿수&amp;nbsp;합이&amp;nbsp;N인&amp;nbsp;가장&amp;nbsp;작은&amp;nbsp;수&amp;nbsp;(#Greedy)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;765&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cssUxL/dJMcahX9pge/2dQ0ougRfzBAXSZQO5G4QK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cssUxL/dJMcahX9pge/2dQ0ougRfzBAXSZQO5G4QK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cssUxL/dJMcahX9pge/2dQ0ougRfzBAXSZQO5G4QK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcssUxL%2FdJMcahX9pge%2F2dQ0ougRfzBAXSZQO5G4QK%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;765&quot; height=&quot;402&quot; data-origin-width=&quot;765&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그리디 알고리즘&lt;/b&gt; 문제를 복기합니다.&lt;br /&gt;처음엔 단순해 보였지만, 히든 테스트케이스에서 꼼꼼하게 생각하게끔 만드는 문제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 요약&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;양의 정수 N이 주어졌을 때, &lt;b&gt;각 자릿수의 합이 N이 되는 가장 작은 자연수&lt;/b&gt;를 구하시오.&lt;/p&gt;
&lt;/blockquote&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;N = 9 &amp;rarr; &lt;code&gt;9&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;N = 10 &amp;rarr; &lt;code&gt;19&lt;/code&gt; (1+9=10, 최솟값)&lt;/li&gt;
&lt;li&gt;N = 18 &amp;rarr; &lt;code&gt;99&lt;/code&gt; (9+9=18)&lt;/li&gt;
&lt;li&gt;N = 20 &amp;rarr; &lt;code&gt;299&lt;/code&gt; (2+9+9=20)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;풀이 접근: Greedy&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 아이디어&lt;/b&gt;는 두 가지입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;자릿수 합을 빠르게 채우려면 9를 최대한 써야 한다.&lt;/b&gt;&lt;br /&gt;9보다 작은 숫자를 여러 자리에 쪼개면 자리수가 늘어나서 수가 커집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;수를 최소로 만들려면 나머지를 가장 앞자리에 배치해야 한다.&lt;/b&gt;&lt;br /&gt;예) N=20: &lt;code&gt;9,9,2&lt;/code&gt; 순서면 &lt;code&gt;992&lt;/code&gt;, &lt;code&gt;2,9,9&lt;/code&gt; 순서면 &lt;code&gt;299&lt;/code&gt; &amp;rarr; 앞자리가 작아야 전체 수가 작아집니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;몫 = N / 9    &amp;rarr; 9를 몫 개 만큼 이어 붙임
나머지 = N % 9  &amp;rarr; 나머지가 0이 아니면 맨 앞에 배치&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// N을 9로 나눈 몫&amp;middot;나머지로 분해해, 나머지를 앞에 배치하는 Greedy 처리로 자릿수 최소화 문제 해결
public static void main(String[] args) throws IOException {

    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

    int n = Integer.parseInt(new StringTokenizer(br.readLine()).nextToken());
    String dap;

    if (n == 0) {
        // 입력이 0이면 답은 0
        dap = &quot;0&quot;;
    } else if (n &amp;lt; 10) {
        // 9 이하면 자기 자신이 가장 작은 수
        dap = String.valueOf(n);
    } else {
        int mok  = n / 9;
        int rest = n % 9;

        StringBuilder sb = new StringBuilder();

        // 나머지가 0이 아닐 때만 맨 앞에 추가(히든 케이스: rest=0이면 앞에 0이 붙어 오답)
        if (rest != 0) sb.append(rest);

        // 9를 몫 개 만큼 뒤에 붙임
        for (int i = 0; i &amp;lt; mok; i++) sb.append(9);

        dap = sb.toString();
    }

    bw.write(dap + &quot;\n&quot;);
    bw.flush();
    bw.close();
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;히든 테스트케이스 &amp;mdash; 나머지가 0일 때&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 작성 시 &lt;code&gt;rest&lt;/code&gt;가 0이어도 무조건 앞에 붙였더니 &lt;code&gt;N=18&lt;/code&gt;에서 &lt;code&gt;099&lt;/code&gt;가 출력되는 문제가 있었습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;if (rest != 0)&lt;/code&gt; 조건을 추가해야 선행 0 없이 정상 출력됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;N&lt;/th&gt;
&lt;th&gt;몫&lt;/th&gt;
&lt;th&gt;나머지&lt;/th&gt;
&lt;th&gt;잘못된 출력&lt;/th&gt;
&lt;th&gt;올바른 출력&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;09&lt;/code&gt; ❌&lt;/td&gt;
&lt;td&gt;&lt;code&gt;9&lt;/code&gt; ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;099&lt;/code&gt; ❌&lt;/td&gt;
&lt;td&gt;&lt;code&gt;99&lt;/code&gt; ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0999&lt;/code&gt; ❌&lt;/td&gt;
&lt;td&gt;&lt;code&gt;999&lt;/code&gt; ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;조건&lt;/th&gt;
&lt;th&gt;처리&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;N = 0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&quot;0&quot;&lt;/code&gt; 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;N &amp;lt; 10&lt;/td&gt;
&lt;td&gt;N 그대로 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;N &amp;ge; 10&lt;/td&gt;
&lt;td&gt;나머지(있을 때만) + 9 &amp;times; 몫&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Greedy 알고리즘&lt;/b&gt;의 핵심은 매 순간 최선의 선택을 하는 것입니다.&lt;br /&gt;이 문제에서는 &quot;가장 큰 한 자리 숫자(9)를 최대한 쓰고, 남은 나머지를 앞에 두면 항상 최솟값&quot;으로 해결할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘 단련장/Codility</category>
      <category>codility</category>
      <category>GREEDY</category>
      <category>Java</category>
      <category>그리디알고리즘</category>
      <category>복기</category>
      <category>알고리즘</category>
      <category>자릿수합</category>
      <category>코딜리티</category>
      <category>코딩테스트</category>
      <category>탐욕법</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/209</guid>
      <comments>https://snapcode.tistory.com/entry/Codility-%EC%9E%90%EB%A6%BF%EC%88%98-%ED%95%A9%EC%9D%B4-N%EC%9D%B8-%EA%B0%80%EC%9E%A5-%EC%9E%91%EC%9D%80-%EC%88%98-Greedy#entry209comment</comments>
      <pubDate>Sat, 28 Mar 2026 17:36:24 +0900</pubDate>
    </item>
    <item>
      <title>[Index] 복합 인덱스 적용기: 트리 구조 개념부터 실전 적용까지</title>
      <link>https://snapcode.tistory.com/entry/Index-%EB%B3%B5%ED%95%A9-%EC%9D%B8%EB%8D%B1%EC%8A%A4-%EC%A0%81%EC%9A%A9%EA%B8%B0-%ED%8A%B8%EB%A6%AC-%EA%B5%AC%EC%A1%B0%EB%B6%80%ED%84%B0-%EC%8B%A4%EC%A0%84-%EC%A0%81%EC%9A%A9%EA%B9%8C%EC%A7%80</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은, 데이터를 조회하는 쿼리에 복합 인덱스를 추가하면서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스가 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;어떤 원리로 동작&lt;/b&gt;&lt;/span&gt;하는지 다시 한번 정리했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;321&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bG9WQP/dJMcajhhIfE/wxmkRyl8A1FZFkSaOrfPQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bG9WQP/dJMcajhhIfE/wxmkRyl8A1FZFkSaOrfPQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bG9WQP/dJMcajhhIfE/wxmkRyl8A1FZFkSaOrfPQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbG9WQP%2FdJMcajhhIfE%2FwxmkRyl8A1FZFkSaOrfPQ1%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;538&quot; height=&quot;321&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;321&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경: 인덱스 없이 조회하면 어떻게 될까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시는 조회 쿼리의 WHERE 절입니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;WHERE (use_yn != 'N' OR created_at::date = CURRENT_DATE - 1)
  AND (created_at IS NULL OR created_at::date &amp;gt;= CURRENT_DATE - 1)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스가 없으면 PostgreSQL은 &lt;b&gt;Sequential Scan(Seq Scan)&lt;/b&gt; 을 수행합니다.&lt;br /&gt;테이블의 모든 행을 첫 줄부터 끝까지 읽으며 조건을 하나씩 확인하는 방식입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 100건이면 100번, 1만 건이면 1만 번 읽습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 적을 때는 문제없지만, 쌓일수록 응답 속도가 그만큼 느려집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그런데, 인덱스는 내부적으로 어떻게 동작할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스가 빠른 이유를 이해하려면 그 내부 구조를 알아야 합니다.&lt;br /&gt;MySQL(InnoDB), PostgreSQL, Oracle 등 &lt;b&gt;대부분의 RDB는 인덱스 자료구조로 B+Tree를 사용&lt;/b&gt;합니다.&lt;br /&gt;트리 계열의 자연스러운 발전 과정으로 나온 구조입니다. 흐름을 따라가 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Tree (트리)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모-자식 관계로 이어진 계층 구조입니다. 파일 시스템의 디렉토리 구조가 대표적인 예입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Binary Tree (이진 트리)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 노드가 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;최대 2개의 자식&lt;/b&gt;&lt;/span&gt;을 갖는 트리입니다. 구조 제약만 있을 뿐, 값의 정렬 규칙은 없습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Binary Search Tree (이진 탐색 트리, BST)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;왼쪽 자식 &amp;lt; 부모 &amp;lt; 오른쪽 자식 규칙&lt;/b&gt;&lt;/span&gt;을 지키는 이진 트리입니다. 탐색 시 절반씩 범위를 줄여나가므로 이론상 O(log n)이지만, 데이터가 편향 삽입되면 한쪽으로 치우쳐 최악 O(n)이 됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;B-Tree (Balanced Tree)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BST의 편향 문제를 해결한 구조입니다. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;노드 하나에 여러 키&lt;/b&gt;&lt;/span&gt;를 담고, 삽입/삭제 시 자동으로 균형을 맞춥니다. 루트에서 모든 리프까지의 높이가 동일하게 유지됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;B+Tree&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B-Tree의 변형으로, &lt;b&gt;실제 데이터(또는 포인터)는 리프 노드에만&lt;/b&gt; 저장되고 내부 노드는 탐색 경로 역할만 합니다. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;리프 노드끼리 링크드 리스트로 연결&lt;/b&gt;&lt;/span&gt;되어 있어 범위 조회(&lt;code&gt;&amp;gt;=&lt;/code&gt;, &lt;code&gt;BETWEEN&lt;/code&gt;)에 매우 유리합니다.&lt;/p&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;2024&quot; data-origin-height=&quot;629&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SNlo0/dJMcagZb6ML/ywgex35Jez9LqKWkM4ICR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SNlo0/dJMcagZb6ML/ywgex35Jez9LqKWkM4ICR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SNlo0/dJMcagZb6ML/ywgex35Jez9LqKWkM4ICR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSNlo0%2FdJMcagZb6ML%2Fywgex35Jez9LqKWkM4ICR1%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;2024&quot; height=&quot;629&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2024&quot; data-origin-height=&quot;629&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;PostgreSQL의 기본 인덱스 타입이 B+Tree입니다.&lt;/b&gt; &lt;code&gt;CREATE INDEX&lt;/code&gt; 시 별도 타입을 지정하지 않으면 자동으로 B+Tree가 적용됩니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;복합 인덱스 적용&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인덱스 생성&lt;/h3&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- (use_yn, created_at) 복합 인덱스 생성으로,
-- use_yn 필터와 created_at 범위 조건을 인덱스 단계에서 함께 처리해 Seq Scan 제거
CREATE INDEX IF NOT EXISTS idx_sample_use_yn_created_at
    ON sample_table(use_yn, created_at);&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동작 원리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복합 인덱스 &lt;code&gt;(use_yn, created_at)&lt;/code&gt;은 내부적으로 다음과 같이 정렬된 B+Tree를 구성합니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;use_yn = 'Y', created_at = 2026-03-01
use_yn = 'Y', created_at = 2026-03-15
use_yn = 'Y', created_at = 2026-04-30
use_yn = 'N', created_at = 2026-01-10   &amp;larr; 쿼리 플래너가 이 구간은 읽지 않음
use_yn = 'N', created_at = 2026-02-28   &amp;larr; 마찬가지&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리에 &lt;code&gt;use_yn != 'N'&lt;/code&gt; 조건이 있으면, 플래너는 &lt;code&gt;use_yn = 'Y'&lt;/code&gt; 구간만 스캔합니다.&lt;br /&gt;&lt;code&gt;use_yn = 'N'&lt;/code&gt;인 행들은 인덱스 레벨에서 아예 건너뜁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 &lt;code&gt;created_at &amp;gt;= CURRENT_DATE - 1&lt;/code&gt; 조건도 인덱스 내 정렬된 순서를 이용해 범위 스캔(Index Range Scan)으로 처리합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Seq Scan과 비교&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;Seq Scan&lt;/th&gt;
&lt;th&gt;Index Range Scan&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;읽는 행 수&lt;/td&gt;
&lt;td&gt;테이블 전체&lt;/td&gt;
&lt;td&gt;조건에 맞는 범위만&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;use_yn = 'N' 행&lt;/td&gt;
&lt;td&gt;전부 읽고 버림&lt;/td&gt;
&lt;td&gt;아예 접근 안 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;범위 조건 처리&lt;/td&gt;
&lt;td&gt;조건마다 전체 재탐색&lt;/td&gt;
&lt;td&gt;정렬된 B+Tree에서 이진 탐색&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실제로 얼마나 빨라졌나?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;수천 건 이하 규모에서는 체감 속도 차이를 측정하기 어렵습니다.&lt;/b&gt;&lt;br /&gt;PostgreSQL은 테이블이 작으면 인덱스를 타는 비용보다 전체를 한 번에 읽는 Seq Scan이 더 빠르다고 판단해 인덱스를 아예 무시하기도 합니다.&lt;/p&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;1만 건 미만&lt;/b&gt;: Seq Scan과 차이 없음. 플래너가 인덱스를 무시할 수 있음&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;1만 ~ 10만 건&lt;/b&gt;: 인덱스 효과가 나타나기 시작&lt;/u&gt;. 조건 선택도가 높을수록 유리&lt;/li&gt;
&lt;li&gt;&lt;b&gt;10만 건 이상&lt;/b&gt;: 인덱스 유무가 응답 속도에 직접적인 영향을 줌&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 적용의 핵심은, 당장의 속도 개선은 미미하겠지만 &lt;b&gt;탐색 구조의 개선&lt;/b&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;/li&gt;
&lt;li&gt;이후: 조건에 맞는 범위만 스캔, 데이터가 늘어도 &lt;b&gt;불필요한 행을 읽지 않음&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 나중을 대비한 &lt;b&gt;사전 최적화&lt;/b&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;심화: 인덱스, &lt;span style=&quot;color: #ee2323;&quot;&gt;어떤 컬럼&lt;/span&gt;으로 &lt;span style=&quot;color: #ee2323;&quot;&gt;어떤 순서&lt;/span&gt;로 잡아야 할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스를 걸기로 했다면 이제 두 가지 질문이 남습니다. &lt;b&gt;어떤 컬럼에&lt;/b&gt;, &lt;b&gt;어떤 순서로&lt;/b&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;두 가지 조건을 함께 보면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;① WHERE절에 자주 등장하는 컬럼&lt;/b&gt; &amp;mdash; 인덱스를 타려면 일단 조건에 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;② 그 중에서 카디널리티(Cardinality)가 높은 컬럼&lt;/b&gt; &amp;mdash; 카디널리티란 컬럼이 가질 수 있는 값의 종류 수입니다. 값의 종류가 많을수록 인덱스로 걸러낼 수 있는 행이 많아집니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;컬럼&lt;/th&gt;
&lt;th&gt;WHERE 등장 빈도&lt;/th&gt;
&lt;th&gt;카디널리티&lt;/th&gt;
&lt;th&gt;인덱스 효과&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;&lt;code&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;use_yn&lt;/span&gt;&lt;/code&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;항상&lt;/td&gt;
&lt;td&gt;낮음 (&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Y/N 2가지&lt;/b&gt;&lt;/span&gt;)&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;약함&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;user_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;자주&lt;/td&gt;
&lt;td&gt;높음 (수만 가지)&lt;/td&gt;
&lt;td&gt;강함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;created_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;자주&lt;/td&gt;
&lt;td&gt;높음 (날짜별 다양)&lt;/td&gt;
&lt;td&gt;강함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;&lt;code&gt;use_yn&lt;/code&gt; 단독 인덱스는 효과가 거의 없습니다.&lt;/b&gt; Y/N 두 가지뿐이라 절반밖에 걸러내지 못하기 때문입니다.&lt;br /&gt;대신 &lt;code&gt;(use_yn, created_at)&lt;/code&gt; 복합 인덱스처럼 &lt;b&gt;카디널리티 낮은 컬럼(등호 조건) + 카디널리티 높은 컬럼(범위 조건)&lt;/b&gt; 조합이 실전에서 가장 많이 쓰이는 패턴입니다.&lt;/p&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;code&gt;=&lt;/code&gt; 조건 컬럼을 먼저, 범위(&lt;code&gt;&amp;gt;=&lt;/code&gt;, &lt;code&gt;BETWEEN&lt;/code&gt;) 조건 컬럼을 나중에&lt;/b&gt; 두는 것이 기본 전략입니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ORDER BY&lt;/code&gt;에 사용되는 컬럼도 인덱스에 포함하면 정렬 비용을 줄일 수 있습니다.&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;복합 인덱스 &lt;code&gt;(A, B)&lt;/code&gt;와 &lt;code&gt;(B, A)&lt;/code&gt;는 완전히 다르게 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B+Tree는 &lt;b&gt;첫 번째 컬럼 기준으로 먼저 정렬&lt;/b&gt;되기 때문에, 쿼리에서 첫 번째 컬럼을 조건으로 사용하지 않으면 인덱스 자체를 타지 못합니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;-- 인덱스: (use_yn, created_at)

WHERE use_yn = 'Y' AND created_at &amp;gt;= '2026-01-01'  -- ✅ 인덱스 정상 사용
WHERE created_at &amp;gt;= '2026-01-01'                    -- ❌ 첫 컬럼 빠짐 &amp;rarr; 인덱스 무시될 수 있음
WHERE use_yn = 'Y'                                  -- ✅ 첫 컬럼만 있어도 사용 가능&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 &lt;b&gt;선두 컬럼 원칙(Leftmost Prefix Rule)&lt;/b&gt; 이라고 합니다. 인덱스를 설계할 때 &quot;어떤 컬럼이 항상 조건에 포함되는가&quot;를 먼저 파악하고, 그 컬럼을 앞에 두는 것이 핵심입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인덱스가 오히려 &lt;span style=&quot;color: #ee2323;&quot;&gt;독이 되는 경우&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스는 조회 성능을 높이는 대신 &lt;b&gt;쓰기 비용을 높입니다.&lt;/b&gt; &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt; 가 발생할 때마다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인덱스도 함께 갱신&lt;/b&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;/li&gt;
&lt;li&gt;카디널리티가 낮은 컬럼(예: &lt;code&gt;status&lt;/code&gt; 컬럼에 값이 2~3가지뿐)에 단독 인덱스를 걸면 효과가 거의 없습니다.&lt;/li&gt;
&lt;li&gt;인덱스가 많을수록 쿼리 플래너가 최적 경로를 고르는 비용도 늘어납니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스는 많이 걸수록 좋은 게 아니라, &lt;b&gt;필요한 곳에 정확히&lt;/b&gt; 걸어야 합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 케이스처럼 &lt;code&gt;WHERE&lt;/code&gt; 절에 자주 등장하는 컬럼 조합을 복합 인덱스로 구성하면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 쌓여도 쿼리 비용이 급격히 늘어나는 것을 방지할 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작을 때 만들어두는 인덱스가, 나중에 가장 값진 한 줄이 되기를.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/DB</category>
      <category>btree</category>
      <category>DATABASE</category>
      <category>DB최적화</category>
      <category>index</category>
      <category>개발블로그</category>
      <category>백엔드</category>
      <category>복합인덱스</category>
      <category>성능개선</category>
      <category>인덱스</category>
      <category>쿼리최적화</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/208</guid>
      <comments>https://snapcode.tistory.com/entry/Index-%EB%B3%B5%ED%95%A9-%EC%9D%B8%EB%8D%B1%EC%8A%A4-%EC%A0%81%EC%9A%A9%EA%B8%B0-%ED%8A%B8%EB%A6%AC-%EA%B5%AC%EC%A1%B0%EB%B6%80%ED%84%B0-%EC%8B%A4%EC%A0%84-%EC%A0%81%EC%9A%A9%EA%B9%8C%EC%A7%80#entry208comment</comments>
      <pubDate>Wed, 25 Mar 2026 23:20:50 +0900</pubDate>
    </item>
    <item>
      <title>[JVM] Heap 구조 뜯어보기: new 한 줄이 메모리에서 벌어지는 일</title>
      <link>https://snapcode.tistory.com/entry/JVM</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서 부하테스트를 주제로 포스팅하면서 JVM Heap 구조를 간략히 언급했는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 &lt;b&gt;JVM 자체를 한 번 깊게&lt;/b&gt; 다뤄보고자 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://snapcode.tistory.com/entry/Jmeter-%EB%B6%80%ED%95%98%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%99%80-JVM-Heap-%EC%B5%9C%EC%A0%81%ED%99%94&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2026.03.16 - [IT/java|Spring] - [부하테스트] JMeter Ramp-up부터 JVM Heap 최적화까지: 서버 성능 개선기&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1773747059292&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[부하테스트] JMeter Ramp-up부터 JVM Heap 최적화까지: 서버 성능 개선기&quot; data-og-description=&quot;배경여러 서비스를 운영하면서 공통 질문이 생겼습니다. &amp;quot;POD 한 대가 실제로 얼마나 버틸 수 있는가?&amp;quot;부하테스트를 준비하던 중, 검증계&amp;middot;운영계 모두 JVM Heap이 Pod 메모리와 맞지 않게 설정되어 &quot; data-og-host=&quot;snapcode.tistory.com&quot; data-og-source-url=&quot;https://snapcode.tistory.com/entry/Jmeter-%EB%B6%80%ED%95%98%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%99%80-JVM-Heap-%EC%B5%9C%EC%A0%81%ED%99%94&quot; data-og-url=&quot;https://snapcode.tistory.com/entry/Jmeter-%EB%B6%80%ED%95%98%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%99%80-JVM-Heap-%EC%B5%9C%EC%A0%81%ED%99%94&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/tRuiP/dJMb8T9YnfN/lWduSSpbU8vMkVU3Q49IK1/img.png?width=585&amp;amp;height=317&amp;amp;face=0_0_585_317,https://scrap.kakaocdn.net/dn/XJbgY/dJMb8XR4x1E/7kMFFJy33MtanBFzInuVVk/img.png?width=585&amp;amp;height=317&amp;amp;face=0_0_585_317,https://scrap.kakaocdn.net/dn/nFqqu/dJMb8UHN3TM/cA70RqeUMhJVbqrK6pHND0/img.png?width=922&amp;amp;height=922&amp;amp;face=0_0_922_922&quot;&gt;&lt;a href=&quot;https://snapcode.tistory.com/entry/Jmeter-%EB%B6%80%ED%95%98%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%99%80-JVM-Heap-%EC%B5%9C%EC%A0%81%ED%99%94&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://snapcode.tistory.com/entry/Jmeter-%EB%B6%80%ED%95%98%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%99%80-JVM-Heap-%EC%B5%9C%EC%A0%81%ED%99%94&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/tRuiP/dJMb8T9YnfN/lWduSSpbU8vMkVU3Q49IK1/img.png?width=585&amp;amp;height=317&amp;amp;face=0_0_585_317,https://scrap.kakaocdn.net/dn/XJbgY/dJMb8XR4x1E/7kMFFJy33MtanBFzInuVVk/img.png?width=585&amp;amp;height=317&amp;amp;face=0_0_585_317,https://scrap.kakaocdn.net/dn/nFqqu/dJMb8UHN3TM/cA70RqeUMhJVbqrK6pHND0/img.png?width=922&amp;amp;height=922&amp;amp;face=0_0_922_922');&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;[부하테스트] JMeter Ramp-up부터 JVM Heap 최적화까지: 서버 성능 개선기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;배경여러 서비스를 운영하면서 공통 질문이 생겼습니다. &quot;POD 한 대가 실제로 얼마나 버틸 수 있는가?&quot;부하테스트를 준비하던 중, 검증계&amp;middot;운영계 모두 JVM Heap이 Pod 메모리와 맞지 않게 설정되어&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;snapcode.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;알고 있다고 생각하면서도, 막상 설명하려면 막히는 내용&lt;/b&gt;&amp;nbsp;이기도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 알고 계신 분께는 복습이, 처음 접하시는 분께는 새로운 발견이 되길 바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;배경&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서 JVM Heap을 Pod 메모리의 75%로 조정하는 최적화를 다뤘습니다. 그 과정에서 &quot;Heap 안에 Young Generation, Eden, Survivor, Old Generation이 있다&quot;는 내용을 언급했는데, 이번 글에서는 그 구조를 &lt;b&gt;객체의 생애주기&lt;/b&gt; 흐름으로 풀어보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;new Object()&lt;/code&gt;를 호출하는 순간, 그 객체는 JVM 메모리 안에서 어떻게 되는지&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;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjxdpz/dJMcac3qOJ8/xFqJEkuf8EBQkAeUAkcQAK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjxdpz/dJMcac3qOJ8/xFqJEkuf8EBQkAeUAkcQAK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjxdpz/dJMcac3qOJ8/xFqJEkuf8EBQkAeUAkcQAK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbjxdpz%2FdJMcac3qOJ8%2FxFqJEkuf8EBQkAeUAkcQAK%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;777&quot; height=&quot;437&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;JVM 메모리 전체 구성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 전체 그림부터 짚고 넘어갑니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;# JVM 프로세스 메모리 구성 (4,096 MiB Pod 기준)
│
├─ Heap Memory                    2,048 MiB  &amp;larr; -Xms(초기 크기) / -Xmx(최대 크기) 로 제어
│   ├─ Young Generation                      &amp;larr; -XX:NewSize / -XX:MaxNewSize 로 제어
│   │   │                                      (새로 생성된 객체가 최초로 할당되는 영역)
│   │   ├─ Eden Space                          (새 객체 최초 할당 공간, 꽉 차면 Minor GC 트리거)
│   │   └─ Survivor Space                      (Minor GC 후 살아남은 객체 임시 보관)
│   │       ├─ From (S0)
│   │       └─ To   (S1)
│   └─ Old Generation                          (오래 살아남은 객체 저장, Full GC 대상)
│
├─ Non-Heap Memory
│   ├─ Metaspace              ~256 MiB         (클래스 메타데이터 저장 / Java 8 이전의 Permanent Generation에 해당)
│   └─ Code Cache              ~64 MiB         (JIT 컴파일된 코드 저장으로, 반복 실행 성능 향상)
│
└─ 기타 JVM 프로세스 메모리
    ├─ Thread Stack            ~200 MiB         (스레드 200개 &amp;times; 1 MiB, 각 스레드의 호출 스택)
    ├─ Direct Memory           ~128 MiB         (NIO/Netty 버퍼 처리로, GC 범위 밖에서 I/O 성능 확보)
    ├─ GC 내부 구조             ~50 MiB
    ├─ OpenTelemetry Agent     ~256 MiB         (APM 계측 오버헤드 처리로, 트레이싱 비용 상시 발생)
    ├─ OS 버퍼                 ~100 MiB
    └─ 여유                    ~994 MiB
                               ─────────
                               4,096 MiB&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;&amp;lt; 이해를 한층 두껍게 해주는, 용어 풀이 &amp;gt;&lt;/b&gt; &lt;br /&gt;*&amp;nbsp;MiB&amp;nbsp;:&amp;nbsp;Mebibyte.&amp;nbsp;1&amp;nbsp;MiB&amp;nbsp;=&amp;nbsp;1,024&amp;nbsp;KiB&amp;nbsp;=&amp;nbsp;약&amp;nbsp;1MB.&amp;nbsp;메모리&amp;nbsp;단위 &lt;br /&gt;*&amp;nbsp;Pod&amp;nbsp;:&amp;nbsp;Kubernetes에서&amp;nbsp;컨테이너를&amp;nbsp;실행하는&amp;nbsp;최소&amp;nbsp;단위.&amp;nbsp;서버&amp;nbsp;인스턴스&amp;nbsp;1개라고&amp;nbsp;이해하면&amp;nbsp;됨 &lt;br /&gt;*&amp;nbsp;Heap&amp;nbsp;Memory&amp;nbsp;:&amp;nbsp;JVM이&amp;nbsp;객체를&amp;nbsp;저장하는&amp;nbsp;주&amp;nbsp;메모리&amp;nbsp;공간.&amp;nbsp;GC가&amp;nbsp;관리 &lt;br /&gt;*&amp;nbsp;Xms&amp;nbsp;:&amp;nbsp;X&amp;nbsp;memory&amp;nbsp;start.&amp;nbsp;JVM&amp;nbsp;시작&amp;nbsp;시&amp;nbsp;초기&amp;nbsp;Heap&amp;nbsp;크기 &lt;br /&gt;*&amp;nbsp;Xmx&amp;nbsp;:&amp;nbsp;X&amp;nbsp;memory&amp;nbsp;maximum.&amp;nbsp;JVM이&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;최대&amp;nbsp;Heap&amp;nbsp;크기 &lt;br /&gt;*&amp;nbsp;Young&amp;nbsp;Generation&amp;nbsp;:&amp;nbsp;새로&amp;nbsp;생성된&amp;nbsp;객체가&amp;nbsp;할당되는&amp;nbsp;Heap&amp;nbsp;영역.&amp;nbsp;Minor&amp;nbsp;GC&amp;nbsp;대상 &lt;br /&gt;*&amp;nbsp;XX:NewSize&amp;nbsp;/&amp;nbsp;XX:MaxNewSize&amp;nbsp;:&amp;nbsp;Young&amp;nbsp;Generation&amp;nbsp;크기를&amp;nbsp;제어하는&amp;nbsp;JVM&amp;nbsp;옵션 &lt;br /&gt;*&amp;nbsp;Eden&amp;nbsp;Space&amp;nbsp;:&amp;nbsp;객체가&amp;nbsp;처음&amp;nbsp;생성되어&amp;nbsp;할당되는&amp;nbsp;공간.&amp;nbsp;에덴동산에서&amp;nbsp;유래 &lt;br /&gt;*&amp;nbsp;Minor&amp;nbsp;GC&amp;nbsp;:&amp;nbsp;Young&amp;nbsp;Generation이&amp;nbsp;꽉&amp;nbsp;찼을&amp;nbsp;때&amp;nbsp;발생하는&amp;nbsp;빠른&amp;nbsp;가비지&amp;nbsp;컬렉션 &lt;br /&gt;*&amp;nbsp;GC&amp;nbsp;:&amp;nbsp;Garbage&amp;nbsp;Collection.&amp;nbsp;더&amp;nbsp;이상&amp;nbsp;참조되지&amp;nbsp;않는&amp;nbsp;객체를&amp;nbsp;자동으로&amp;nbsp;메모리에서&amp;nbsp;제거하는&amp;nbsp;과정 &lt;br /&gt;*&amp;nbsp;Survivor&amp;nbsp;Space&amp;nbsp;:&amp;nbsp;Minor&amp;nbsp;GC에서&amp;nbsp;살아남은&amp;nbsp;객체를&amp;nbsp;임시&amp;nbsp;보관하는&amp;nbsp;공간 &lt;br /&gt;*&amp;nbsp;From&amp;nbsp;(S0)&amp;nbsp;/&amp;nbsp;To&amp;nbsp;(S1)&amp;nbsp;:&amp;nbsp;Survivor의&amp;nbsp;두&amp;nbsp;칸.&amp;nbsp;항상&amp;nbsp;한&amp;nbsp;칸은&amp;nbsp;비워두고&amp;nbsp;살아남은&amp;nbsp;객체를&amp;nbsp;번갈아&amp;nbsp;복사 &lt;br /&gt;*&amp;nbsp;Old&amp;nbsp;Generation&amp;nbsp;:&amp;nbsp;Survivor를&amp;nbsp;여러&amp;nbsp;번&amp;nbsp;살아남은&amp;nbsp;객체가&amp;nbsp;승격되는&amp;nbsp;영역.&amp;nbsp;Full&amp;nbsp;GC&amp;nbsp;대상 &lt;br /&gt;*&amp;nbsp;Full&amp;nbsp;GC&amp;nbsp;:&amp;nbsp;Heap&amp;nbsp;전체를&amp;nbsp;대상으로&amp;nbsp;하는&amp;nbsp;무거운&amp;nbsp;GC.&amp;nbsp;실행&amp;nbsp;중&amp;nbsp;애플리케이션이&amp;nbsp;잠깐&amp;nbsp;멈춤(STW) &lt;br /&gt;*&amp;nbsp;STW&amp;nbsp;:&amp;nbsp;Stop-The-World.&amp;nbsp;Full&amp;nbsp;GC&amp;nbsp;실행&amp;nbsp;중&amp;nbsp;모든&amp;nbsp;애플리케이션&amp;nbsp;스레드가&amp;nbsp;일시&amp;nbsp;정지되는&amp;nbsp;현상 &lt;br /&gt;*&amp;nbsp;Non-Heap&amp;nbsp;Memory&amp;nbsp;:&amp;nbsp;Heap&amp;nbsp;외&amp;nbsp;JVM이&amp;nbsp;사용하는&amp;nbsp;메모리.&amp;nbsp;GC&amp;nbsp;대상이&amp;nbsp;아님 &lt;br /&gt;*&amp;nbsp;Metaspace&amp;nbsp;:&amp;nbsp;클래스&amp;nbsp;구조&amp;middot;메서드&amp;nbsp;정보&amp;nbsp;등&amp;nbsp;메타데이터를&amp;nbsp;저장하는&amp;nbsp;영역.&amp;nbsp;Java&amp;nbsp;8부터&amp;nbsp;PermGen&amp;nbsp;대체 &lt;br /&gt;*&amp;nbsp;Permanent&amp;nbsp;Generation&amp;nbsp;(PermGen)&amp;nbsp;:&amp;nbsp;Java&amp;nbsp;7&amp;nbsp;이하에서&amp;nbsp;클래스&amp;nbsp;메타데이터를&amp;nbsp;저장하던&amp;nbsp;고정&amp;nbsp;크기&amp;nbsp;영역.&amp;nbsp;Java&amp;nbsp;8부터&amp;nbsp;Metaspace로&amp;nbsp;교체 &lt;br /&gt;*&amp;nbsp;Code&amp;nbsp;Cache&amp;nbsp;:&amp;nbsp;JIT&amp;nbsp;컴파일러가&amp;nbsp;변환한&amp;nbsp;기계어&amp;nbsp;코드를&amp;nbsp;저장해두는&amp;nbsp;영역 &lt;br /&gt;*&amp;nbsp;JIT&amp;nbsp;:&amp;nbsp;Just-In-Time&amp;nbsp;컴파일러.&amp;nbsp;자주&amp;nbsp;실행되는&amp;nbsp;코드를&amp;nbsp;런타임에&amp;nbsp;기계어로&amp;nbsp;번역해&amp;nbsp;성능&amp;nbsp;향상 &lt;br /&gt;*&amp;nbsp;Thread&amp;nbsp;Stack&amp;nbsp;:&amp;nbsp;각&amp;nbsp;스레드마다&amp;nbsp;독립적으로&amp;nbsp;갖는&amp;nbsp;호출&amp;nbsp;스택.&amp;nbsp;메서드&amp;nbsp;호출&amp;nbsp;순서&amp;middot;지역변수&amp;nbsp;저장 &lt;br /&gt;*&amp;nbsp;Direct&amp;nbsp;Memory&amp;nbsp;:&amp;nbsp;GC&amp;nbsp;범위&amp;nbsp;밖에서&amp;nbsp;OS와&amp;nbsp;직접&amp;nbsp;통신하는&amp;nbsp;메모리&amp;nbsp;버퍼 &lt;br /&gt;* NIO : Non-blocking I/O. 블로킹(응답 올때까지 기다리는 것) 없이 I/O를 처리하는 Java 표준 API &lt;br /&gt;*&amp;nbsp;Netty&amp;nbsp;:&amp;nbsp;NIO&amp;nbsp;기반의&amp;nbsp;고성능&amp;nbsp;네트워크&amp;nbsp;프레임워크.&amp;nbsp;Direct&amp;nbsp;Memory를&amp;nbsp;활용해&amp;nbsp;대용량&amp;nbsp;I/O&amp;nbsp;처리 &lt;br /&gt;*&amp;nbsp;OpenTelemetry&amp;nbsp;Agent&amp;nbsp;:&amp;nbsp;분산&amp;nbsp;트레이싱&amp;middot;메트릭&amp;middot;로그를&amp;nbsp;자동&amp;nbsp;계측하는&amp;nbsp;Java&amp;nbsp;Agent.&amp;nbsp;-javaagent&amp;nbsp;옵션으로&amp;nbsp;JVM에&amp;nbsp;부착 &lt;br /&gt;*&amp;nbsp;APM&amp;nbsp;:&amp;nbsp;Application&amp;nbsp;Performance&amp;nbsp;Monitoring.&amp;nbsp;응답시간&amp;middot;처리량&amp;middot;에러율&amp;nbsp;등을&amp;nbsp;실시간&amp;nbsp;수집&amp;middot;시각화하는&amp;nbsp;모니터링&amp;nbsp;체계 &lt;br /&gt;*&amp;nbsp;OS&amp;nbsp;버퍼&amp;nbsp;:&amp;nbsp;운영체제가&amp;nbsp;소켓&amp;middot;파일&amp;nbsp;I/O&amp;nbsp;처리를&amp;nbsp;위해&amp;nbsp;사용하는&amp;nbsp;커널&amp;nbsp;레벨&amp;nbsp;메모리&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;우선 크게 세가지 영역&lt;/b&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;Heap&lt;/b&gt; : 객체가 살고 죽는 공간. GC의 주 무대&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Non-Heap&lt;/b&gt; : 클래스 정보&amp;middot;JIT 코드 저장. GC 대상이 아님&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기타&lt;/b&gt; : 스레드&amp;middot;I/O&amp;middot;에이전트 등 JVM 프로세스 운영에 필요한 나머지 메모리&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLVJjV/dJMcagLzJrf/cGplR4IlooHwnlHUL8sd4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLVJjV/dJMcagLzJrf/cGplR4IlooHwnlHUL8sd4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLVJjV/dJMcagLzJrf/cGplR4IlooHwnlHUL8sd4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLVJjV%2FdJMcagLzJrf%2FcGplR4IlooHwnlHUL8sd4K%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;1462&quot; height=&quot;792&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;792&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Heap &amp;mdash; 객체의 생애주기&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span&gt;태어난걸 축하해&lt;/span&gt;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1단계 : 탄생 &amp;mdash; Eden Space&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;new Object()&lt;/code&gt;를 호출하면 객체는 &lt;b&gt;Eden Space&lt;/b&gt;에 할당됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;Eden&quot; 이라는 이름은 &lt;b&gt;성경의 에덴동산(Garden of Eden)에서 유래&lt;/b&gt;했습니다. 새로 태어난 생명(객체)이 처음 머무는 곳이라는 의미입니다. 대부분의 객체는 이곳에서 짧게 살다가 사라집니다. &lt;b&gt;요청 하나를 처리하면서 만들어진 임시 객체들, 루프 안에서 생성된 문자열 등이 대표적&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Eden이 꽉 차면 &lt;b&gt;Minor GC가 트리거&lt;/b&gt;됩니다. 이 시점에 JVM은 Eden에 있는 객체를 전수 검사합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;더 이상 참조되지 않는 객체 &amp;rarr; 즉시 제거&lt;/li&gt;
&lt;li&gt;&lt;b&gt;아직 참조 중인 객체 &amp;rarr; Survivor Space로 이동&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-origin-width=&quot;1462&quot; data-origin-height=&quot;792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dIa7J1/dJMcabQ0nkC/GkhLkKlm30iTkEqEz4iqgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dIa7J1/dJMcabQ0nkC/GkhLkKlm30iTkEqEz4iqgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dIa7J1/dJMcabQ0nkC/GkhLkKlm30iTkEqEz4iqgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdIa7J1%2FdJMcabQ0nkC%2FGkhLkKlm30iTkEqEz4iqgk%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;1462&quot; height=&quot;792&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;792&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span&gt;에덴동산이 꽉찼어. 정리하자.&lt;/span&gt;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2단계 : 생존 &amp;mdash; Survivor Space (From / To)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Survivor Space는 &lt;b&gt;From(S0)&lt;/b&gt; 과 &lt;b&gt;To(S1)&lt;/b&gt; 두 칸으로 나뉩니다. 언뜻 보면 왜 두 칸인지 의아할 수 있는데, Minor GC 동작 방식 때문입니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# Minor GC 발생 시 Survivor 동작 흐름

[Minor GC 전]
  Eden   : 객체 가득 참
  From   : 이전 GC에서 살아남은 객체들 존재
  To     : 비어있음

[Minor GC 실행]
  1. Eden + From에서 살아있는 객체를 To로 복사
  2. 복사 시 각 객체의 age(생존 횟수) += 1
  3. Eden + From 전체를 비움

[Minor GC 후]
  Eden   : 비어있음
  From   : 비어있음  &amp;larr; 역할이 뒤바뀜 (기존 To가 새로운 From)
  To     : 비어있음  &amp;larr; 다음 GC를 위해 항상 한 칸은 비워둠&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;b&gt;항상 한 칸(To)은 비어있어야 한다&lt;/b&gt;는 것입니다. 살아있는 객체를 한쪽에서 &lt;b&gt;다른 쪽으로 통째로 복사하는 방식&lt;/b&gt;(Copying GC)을 쓰기 때문입니다. 덕분에 메모리 단편화 없이 빠르게 정리할 수 있습니다. GC가 끝나면 From과 To의 역할은 뒤바뀝니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;* 단편화란? 메모리 곳곳에 작은 빈 공간이 흩어져서, 전체 여유는 있는데 막상 큰 객체를 넣을 자리가 없는 상태&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1HiEB/dJMcai3Cmhz/gpO3RBqiMA8tfkAMAHRTLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1HiEB/dJMcai3Cmhz/gpO3RBqiMA8tfkAMAHRTLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1HiEB/dJMcai3Cmhz/gpO3RBqiMA8tfkAMAHRTLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1HiEB%2FdJMcai3Cmhz%2FgpO3RBqiMA8tfkAMAHRTLk%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;1462&quot; height=&quot;792&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;792&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span&gt;실버타운으로 모셔드릴께요.&lt;/span&gt;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3단계 : 승격 &amp;mdash; Old Generation&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Minor GC를 거칠 때마다 살아남은 객체의 &lt;b&gt;age(나이)&lt;/b&gt; 가 1씩 증가합니다. age가 임계값(기본 15)에 도달하면 &lt;b&gt;Old Generation으로 승격(Promotion)&lt;/b&gt; 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Old Generation에는 오랫동안 살아남은 객체들이 모입니다. &lt;b&gt;캐시, 싱글톤, DB 커넥션 풀 등이 대표적&lt;/b&gt;입니다. Old Generation이 꽉 차면 &lt;b&gt;Full GC&lt;/b&gt;가 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Full GC는 Minor GC보다 훨씬 무겁습니다. Heap 전체를 스캔하고, 그 동안 &lt;b&gt;Stop-the-World(STW)&lt;/b&gt; &amp;mdash; 즉 애플리케이션이 잠깐 멈춥니다. &lt;b&gt;응답 지연 스파이크의 주원인 중 하나&lt;/b&gt;&amp;nbsp;입니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;# 객체 생애주기 요약

new Object()
    &amp;darr;
 Eden Space  &amp;rarr;  (Minor GC 탈락)  &amp;rarr;  소멸
    &amp;darr; 살아남음
 Survivor (From &amp;harr; To 반복)
    &amp;darr; age &amp;gt;= 15
 Old Generation  &amp;rarr;  (Full GC 탈락)  &amp;rarr;  소멸&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bru7te/dJMcahRdSuW/vbXwZiB8kPiSU9Zk5zuJa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bru7te/dJMcahRdSuW/vbXwZiB8kPiSU9Zk5zuJa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bru7te/dJMcahRdSuW/vbXwZiB8kPiSU9Zk5zuJa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbru7te%2FdJMcahRdSuW%2FvbXwZiB8kPiSU9Zk5zuJa0%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;1462&quot; height=&quot;792&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;792&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Non-Heap &amp;mdash; 코드와 클래스의 공간&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Metaspace&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클래스 정의(메서드 목록, 필드 정보, 어노테이션 등)가 저장&lt;/b&gt;됩니다. Java 8 이전에는 Permanent Generation(PermGen) 이라는 고정 크기 영역에 저장했는데, 클래스가 많아지면 &lt;code&gt;java.lang.OutOfMemoryError: PermGen space&lt;/code&gt; 에러가 잦았습니다. &lt;b&gt;Java 8부터 Metaspace로 교체&lt;/b&gt;되면서, Native 메모리 영역으로 이동, 필요에 따라 자동 확장됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7JYPQ/dJMcahcCyyG/lQ6Alz8pUq7P9oyQsQXH00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7JYPQ/dJMcahcCyyG/lQ6Alz8pUq7P9oyQsQXH00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7JYPQ/dJMcahcCyyG/lQ6Alz8pUq7P9oyQsQXH00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7JYPQ%2FdJMcahcCyyG%2FlQ6Alz8pUq7P9oyQsQXH00%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;1462&quot; height=&quot;792&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;792&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;매번 한줄씩 읽기 힘들어. 기계어로 번역해서 저장해두자.&lt;/span&gt;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Code Cache&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM은 처음에 &lt;b&gt;소스코드(.java) &amp;rarr; 바이트코드(.class) &amp;rarr; JVM이 한 줄씩 해석(인터프리터) &amp;rarr; 실행&lt;/b&gt;하다가,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자주 호출되는 코드를 감지하면 &lt;b&gt;JIT(Just-In-Time) 컴파일러&lt;/b&gt;가 기계어로 변환해 Code Cache에 저장합니다. 이후 해당 코드는 변환 없이 바로 실행되어 성능이 향상됩니다. =&amp;gt; &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;이게 부하테스트에서 Ramp-up이 중요한 이유와도 연결됨!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&amp;nbsp; * 실무에서 자주 JIT에 올라가는 것들:&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;- HTTP 요청마다 호출되는 핵심 비즈니스 로직 &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;- 루프 안에서 반복 실행되는 코드 &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;- &lt;b&gt;자주 쓰는 유틸 메서드 (날짜 변환, 문자열 처리 등)&lt;/b&gt; &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;- 프레임워크 내부 코드 (Spring의 DispatcherServlet 등)&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TVwWk/dJMcahwWG4s/kdeJg3sOEddkQmw6vH0Ezk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TVwWk/dJMcahwWG4s/kdeJg3sOEddkQmw6vH0Ezk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TVwWk/dJMcahwWG4s/kdeJg3sOEddkQmw6vH0Ezk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTVwWk%2FdJMcahwWG4s%2FkdeJg3sOEddkQmw6vH0Ezk%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;1462&quot; height=&quot;792&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;792&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기타 JVM 프로세스 메모리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap&amp;middot;Non-Heap 외에도 JVM 프로세스는 추가 메모리를 사용합니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;영역&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Thread Stack&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;각 스레드마다 독립적으로 가지는 호출 스택. 스레드 200개 &amp;times; 1 MiB &amp;asymp; 200 MiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Direct Memory&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;NIO/Netty가 GC 범위 밖에서 직접 관리하는 버퍼 메모리. I/O 성능 확보에 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;GC 내부 구조&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;GC 알고리즘이 참조 추적&amp;middot;카드 테이블 관리 등에 사용하는 내부 메모리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;OpenTelemetry Agent&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;-javaagent로 부착된 APM 계측 오버헤드. 트레이싱 비용 상시 발생 (~256 MiB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;OS 버퍼&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;OS 레벨 소켓&amp;middot;파일 I/O 버퍼&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhIWg1/dJMcaivNR9L/vUh8f8sDAJ8mEBjwTFjjt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhIWg1/dJMcaivNR9L/vUh8f8sDAJ8mEBjwTFjjt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhIWg1/dJMcaivNR9L/vUh8f8sDAJ8mEBjwTFjjt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhIWg1%2FdJMcaivNR9L%2FvUh8f8sDAJ8mEBjwTFjjt0%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;1462&quot; height=&quot;792&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;792&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reserved vs Committed&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM 메모리를 이야기할 때 &lt;b&gt;자주 등장하는 두 개념&lt;/b&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;Reserved&lt;/b&gt; : OS에 주소 공간만 예약해둔 상태. 실제 RAM은 미사용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Committed&lt;/b&gt; : 실제 RAM에 매핑된 상태. 실제로 사용 중&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예) &lt;code&gt;-Xmx3072m&lt;/code&gt; 설정 시 JVM은 3,072 MiB를 Reserved로 잡아두고 =&amp;gt; 객체가 쌓이면서 점진적으로 Committed로 전환합니다. Heap뿐 아니라 Metaspace 등 Non-Heap 영역도 동일한 방식으로 동작합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvnaUM/dJMcacCoT0f/mwSJKqimjOtSefpXpj0Bt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvnaUM/dJMcacCoT0f/mwSJKqimjOtSefpXpj0Bt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvnaUM/dJMcacCoT0f/mwSJKqimjOtSefpXpj0Bt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvnaUM%2FdJMcacCoT0f%2FmwSJKqimjOtSefpXpj0Bt1%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;1462&quot; height=&quot;792&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;792&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;new Object()&lt;/code&gt; &amp;rarr; &lt;b&gt;Eden Space&lt;/b&gt; 할당 (에덴동산: 새 생명이 태어나는 곳)&lt;/li&gt;
&lt;li&gt;Eden이 꽉 차면 &lt;b&gt;Minor GC&lt;/b&gt; &lt;b&gt;트리거&lt;/b&gt; &amp;rarr; 살아남은 객체는 &lt;b&gt;Survivor Space 이동&lt;/b&gt;(From &amp;harr; To)&lt;/li&gt;
&lt;li&gt;From/To는 항상 한 칸이 비어있어야 Copying GC 방식으로 단편화 없이 정리 가능&lt;/li&gt;
&lt;li&gt;age &amp;gt;= 15 &amp;rarr; &lt;b&gt;Old Generation 승격&lt;/b&gt; &amp;rarr; &lt;b&gt;Full GC&lt;/b&gt; 대상&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Non-Heap&lt;/b&gt;(Metaspace&amp;middot;Code Cache)은 GC 대상이 아닌 클래스 정보&amp;middot;JIT 코드 저장 공간&lt;/li&gt;
&lt;li&gt;Reserved(예약) &amp;rarr; Committed(실제 사용) 전환 방식으로 메모리 효율 확보&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM&amp;nbsp;메모리&amp;nbsp;구조를&amp;nbsp;알고&amp;nbsp;나면,&amp;nbsp;-Xmx&amp;nbsp;&lt;b&gt;하나 건드릴 때도 &quot;왜 이 값인가&quot; &lt;/b&gt;를 설명&amp;nbsp;할 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pod 메모리 안에서 Heap과 Non-Heap이 어떻게 나뉘는지 감이 오셨으면 좋겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무 업무 하시면서, 메모리 설정값을 &lt;b&gt;자신있게 건드려 보시길&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;1462&quot; data-origin-height=&quot;792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HRbqP/dJMcadVyq8S/xS6je8luw9eme2yAzgpsWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HRbqP/dJMcadVyq8S/xS6je8luw9eme2yAzgpsWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HRbqP/dJMcadVyq8S/xS6je8luw9eme2yAzgpsWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHRbqP%2FdJMcadVyq8S%2FxS6je8luw9eme2yAzgpsWk%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;1462&quot; height=&quot;792&quot; data-origin-width=&quot;1462&quot; data-origin-height=&quot;792&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/java|Spring</category>
      <category>GC</category>
      <category>Java</category>
      <category>jvm</category>
      <category>JVM튜닝</category>
      <category>MinorGC</category>
      <category>가비지컬렉션</category>
      <category>메모리최적화</category>
      <category>백엔드</category>
      <category>자바</category>
      <category>힙메모리</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/207</guid>
      <comments>https://snapcode.tistory.com/entry/JVM#entry207comment</comments>
      <pubDate>Tue, 17 Mar 2026 20:17:05 +0900</pubDate>
    </item>
    <item>
      <title>[부하테스트] JMeter Ramp-up부터 JVM Heap 최적화까지: 서버 성능 개선기</title>
      <link>https://snapcode.tistory.com/entry/Jmeter-%EB%B6%80%ED%95%98%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%99%80-JVM-Heap-%EC%B5%9C%EC%A0%81%ED%99%94</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;301&quot; data-origin-height=&quot;167&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAqovc/dJMcaaxN3Bq/OiKLAbwMeIaWoFpGNOkYpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAqovc/dJMcaaxN3Bq/OiKLAbwMeIaWoFpGNOkYpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAqovc/dJMcaaxN3Bq/OiKLAbwMeIaWoFpGNOkYpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAqovc%2FdJMcaaxN3Bq%2FOiKLAbwMeIaWoFpGNOkYpk%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;444&quot; height=&quot;246&quot; data-origin-width=&quot;301&quot; data-origin-height=&quot;167&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 서비스를 운영하면서 공통 질문이 생겼습니다. &quot;&lt;b&gt;POD 한 대가 실제로 얼마나 버틸 수 있는가?&lt;/b&gt;&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부하테스트를 준비하던 중, 검증계&amp;middot;운영계 모두 &lt;b&gt;JVM Heap이 Pod 메모리와 맞지 않게 설정&lt;/b&gt;되어 있다는 것을 발견했습니다. &lt;b&gt;Heap을 먼저 바로잡고&lt;/b&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;JVM Heap 최적화&lt;/b&gt;: 인수인계 시점부터 고정되어 있던 잘못된 설정을 발견 및 개선&lt;/li&gt;
&lt;li&gt;&lt;b&gt;부하테스트&lt;/b&gt;: 최적화 이후 실제 성능을 데이터로 검증&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JVM Heap 최적화&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 발견&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부하테스트 준비 중 Heap 설정을 살펴보다 이상한 점을 발견했습니다. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;인수인계 시점부터 검증계&amp;middot;운영계 모두 &lt;code&gt;Xms=Xmx=2048m&lt;/code&gt;으로 고정&lt;/b&gt;&lt;/span&gt;되어 있었는데, 환경별 Pod 메모리와 맞지 않는 설정이었습니다. 초기 개발 시 놓친 부분으로 판단했습니다.&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;검증계 (Pod 2 GiB)&lt;/b&gt;: Heap 2,048m &amp;rarr; Non-Heap 영역 여유 없음 &amp;rarr; &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;OOM 위험&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;운영계 (Pod 4 GiB)&lt;/b&gt;: Heap 2,048m &amp;rarr; &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;나머지 2 GiB가 활용되지 않는 상태&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[ 검증계 ]                          [ 운영계 ]
Pod 메모리:  2 GiB (2,048 MiB)      Pod 메모리:  4 GiB (4,096 MiB)
JVM Heap:   2 GiB (2,048 MiB)      JVM Heap:   2 GiB (2,048 MiB)
차이:        0  &amp;larr; Non-Heap 여유 없음  차이:        2 GiB &amp;larr; Heap이 절반만 활용&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;632&quot; data-origin-height=&quot;936&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beocL0/dJMcach5aFG/v1U0NWu33X3elKqrhniC9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beocL0/dJMcach5aFG/v1U0NWu33X3elKqrhniC9k/img.png&quot; data-alt=&quot;할당된 자원 내 최적화 요청 (비용 변동x)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beocL0/dJMcach5aFG/v1U0NWu33X3elKqrhniC9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeocL0%2FdJMcach5aFG%2Fv1U0NWu33X3elKqrhniC9k%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;555&quot; height=&quot;822&quot; data-origin-width=&quot;632&quot; data-origin-height=&quot;936&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;할당된 자원 내 최적화 요청 (비용 변동x)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Non-Heap 영역이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM 프로세스는 Heap 외에도 &lt;b&gt;Non-Heap 영역&lt;/b&gt;을 상당량 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;JVM 프로세스 메모리 구성 (4,096 MiB Pod 기준)
│
├─ Heap Memory                    2,048 MiB  &amp;larr; -Xms(초기 크기) / -Xmx(최대 크기) 로 제어
│   ├─ Young Generation                      &amp;larr; -XX:NewSize / -XX:MaxNewSize 로 제어
│   │   │                                      (새로 생성된 객체가 최초로 할당되는 영역)
│   │   ├─ Eden Space                          (새 객체 최초 할당 공간, 꽉 차면 Minor GC 트리거)
│   │   └─ Survivor Space                      (Minor GC 후 살아남은 객체 임시 보관)
│   │       ├─ From (S0)
│   │       └─ To   (S1)
│   └─ Old Generation                          (오래 살아남은 객체 저장, Full GC 대상)
│
├─ Non-Heap Memory
│   ├─ Metaspace              ~256 MiB         (클래스 메타데이터 저장 / Java 8 이전의 Permanent Generation에 해당)
│   └─ Code Cache              ~64 MiB         (JIT 컴파일된 코드 저장으로, 반복 실행 성능 향상)
│
└─ 기타 JVM 프로세스 메모리
    ├─ Thread Stack            ~200 MiB         (스레드 200개 &amp;times; 1 MiB, 각 스레드의 호출 스택)
    ├─ Direct Memory           ~128 MiB         (NIO/Netty 버퍼 처리로, GC 범위 밖에서 I/O 성능 확보)
    ├─ GC 내부 구조             ~50 MiB
    ├─ OpenTelemetry Agent     ~256 MiB         (APM 계측 오버헤드 처리로, 트레이싱 비용 상시 발생) &amp;larr; 중요
    ├─ OS 버퍼                 ~100 MiB
    └─ 여유                    ~994 MiB
                               ─────────
                               4,096 MiB&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 분산 추적(Trace), 메트릭, 로그를 자동 계측해서 백엔드로 전송하기 위한,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenTelemetry Agent 오버헤드(~256 MiB)가 예상보다 크며, 이를 무시하면 &lt;b&gt;OOM 위험&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;585&quot; data-origin-height=&quot;317&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caktv2/dJMb99TbTvo/jmPZeute4skKAmibttgGeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caktv2/dJMb99TbTvo/jmPZeute4skKAmibttgGeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caktv2/dJMb99TbTvo/jmPZeute4skKAmibttgGeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcaktv2%2FdJMb99TbTvo%2FjmPZeute4skKAmibttgGeK%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;585&quot; height=&quot;317&quot; data-origin-width=&quot;585&quot; data-origin-height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자세한 JVM 내용은, 다음 컨텐츠에서 다룹니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://snapcode.tistory.com/entry/JVM&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2026.03.17 - [IT/java|Spring] - [JVM] new 한 줄이 메모리에서 벌어지는 일: JVM 힙 구조 뜯어보기&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1773748007337&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;JVM&quot; data-og-description=&quot; &quot; data-og-host=&quot;snapcode.tistory.com&quot; data-og-source-url=&quot;https://snapcode.tistory.com/entry/JVM&quot; data-og-url=&quot;https://snapcode.tistory.com/entry/JVM&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/kRcvE/dJMb9jgv6AQ/6nC8GlKyFS1NOen5lgWj80/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bg3QJ6/dJMb9iIF0mk/A3weK4QvWpxMSaKcBCQTzK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cvcQVC/dJMb8Qek6oE/6e7hWSTH1KiVAVji7GSjwK/img.png?width=922&amp;amp;height=922&amp;amp;face=0_0_922_922&quot;&gt;&lt;a href=&quot;https://snapcode.tistory.com/entry/JVM&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://snapcode.tistory.com/entry/JVM&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/kRcvE/dJMb9jgv6AQ/6nC8GlKyFS1NOen5lgWj80/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bg3QJ6/dJMb9iIF0mk/A3weK4QvWpxMSaKcBCQTzK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cvcQVC/dJMb8Qek6oE/6e7hWSTH1KiVAVji7GSjwK/img.png?width=922&amp;amp;height=922&amp;amp;face=0_0_922_922');&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;JVM&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;snapcode.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개선 방향&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Xms&lt;/code&gt; (X memory start) : JVM 시작 시 초기 Heap 크기&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Xmx&lt;/code&gt; (X memory maximum) : JVM이 사용할 수 있는 최대 Heap 크기&lt;/li&gt;
&lt;li&gt;둘이 다르면 트래픽 증가 시 Heap이 동적으로 확장되며 &lt;b&gt;그 과정에서 응답 지연 발생&lt;/b&gt; &amp;rarr; &lt;code&gt;Xms = Xmx&lt;/code&gt;로 고정해 처음부터 최대 메모리를 확보&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Xms = Xmx&lt;/code&gt; 고정 &amp;rarr; 런타임 중 Heap 확장 없음 &amp;rarr; &lt;b&gt;응답 지연 방지 &lt;span style=&quot;color: #ee2323;&quot;&gt;(성능 최우선 옵션 채택)&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Heap = Pod 메모리의 75%&lt;/b&gt; 수준으로 조정&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;나머지 25% &amp;rarr; Non-Heap 영역(Metaspace, Thread Stack, OTel 등) 확보 &amp;rarr; &lt;b&gt;OOM 방지&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;# Heap을 Pod 메모리의 75%로 설정해 Non-Heap 영역 OOM 방지 처리로, 서비스 안정성 강화

# STG (검증계): Pod 2 GiB
env:
  - name: JAVA_OPTS
    value: &quot;-Xms1536m -Xmx1536m&quot;   # 2,048 MiB &amp;times; 75% &amp;asymp; 1,536 MiB

# PRD (운영계): Pod 4 GiB
env:
  - name: JAVA_OPTS
    value: &quot;-Xms3072m -Xmx3072m&quot;   # 4,096 MiB &amp;times; 75% &amp;asymp; 3,072 MiB&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;환경&lt;/th&gt;
&lt;th&gt;Pod 메모리&lt;/th&gt;
&lt;th&gt;변경 전 Heap&lt;/th&gt;
&lt;th&gt;변경 후 Heap&lt;/th&gt;
&lt;th&gt;방향&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;STG (검증계)&lt;/td&gt;
&lt;td&gt;2 GiB&lt;/td&gt;
&lt;td&gt;2,048m&lt;/td&gt;
&lt;td&gt;1,536m&lt;/td&gt;
&lt;td&gt;하향 (부하테스트에서 검증 완료)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PRD (운영계)&lt;/td&gt;
&lt;td&gt;4 GiB&lt;/td&gt;
&lt;td&gt;2,048m&lt;/td&gt;
&lt;td&gt;3,072m&lt;/td&gt;
&lt;td&gt;상향 (성능 개선)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;부하테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap 최적화 이후, 실제 성능을 데이터로 검증하기 위해 부하테스트를 진행했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트 목표&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;단일 POD 기준 최대 TPS&lt;/b&gt;는 얼마인가?&lt;/li&gt;
&lt;li&gt;목표 TPS 달성을 위한 &lt;b&gt;POD 구성(수평 확장)&lt;/b&gt; 기준 수립&lt;/li&gt;
&lt;li&gt;현재 인프라 자원이 과소/과도하게 설정되어 있지는 않은가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트 설계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스별로 스펙과 트래픽 패턴이 다르기 때문에, 아래 기준을 공통으로 적용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;기준&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;베이스라인 POD 사양&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1 vCore / 2 GiB (서비스 공통)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POD 수 변화&lt;/td&gt;
&lt;td&gt;1개 &amp;rarr; 2개 &amp;rarr; 4개&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;동시 유저 수&lt;/td&gt;
&lt;td&gt;100 / 200 / 400 또는 100 / 500 / 1,000 (서비스별 상이)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;부하 증가 방식&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Ramp-up&lt;/b&gt; (period: 60s, duration: 300s)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저 수 상한은 서비스별로 다르게 설정했으며, 최대 응답시간이 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;10초를 초과하는 구간부터는 서비스 관점에서 의미 없다고 판단&lt;/span&gt;해 진행을 제외&lt;/b&gt;했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Ramp-up &amp;mdash; 60초&lt;/b&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;점진적으로 증가&lt;/b&gt;&lt;/span&gt;시키는 방식&lt;/li&gt;
&lt;li&gt;예) 동시 유저 100명 / Ramp-up 60초 &amp;rarr; 6초마다 약 10명씩 추가, 60초 시점에 100명 도달&lt;/li&gt;
&lt;li&gt;처음부터 최대 부하를 주면 서버가 워밍업되지 않아 초반 수치가 왜곡되므로, &lt;b&gt;서버가 안정된 이후의 실제 성능&lt;/b&gt;을 측정하기 위해 60초로 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Duration &amp;mdash; 300초(5분)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Ramp-up이 끝난 이후 실제 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;부하를 유지&lt;/b&gt;&lt;/span&gt;하는 시간&lt;/li&gt;
&lt;li&gt;Ramp-up 60초 완료 후 61초~360초까지 300초간 부하 유지 &amp;rarr; &lt;b&gt;전체 테스트 시간 360초&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;184 TPS 기준 300초면 약 55,000건 수집 &amp;rarr; 샘플이 적으면 응답 지연 수치가 왜곡될 수 있어 &lt;b&gt;신뢰할 수 있는 수치 확보&lt;/b&gt;를 위해 300초로 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트 시나리오&lt;/h3&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; 기반 구성 &amp;mdash; 실제 요청 파라미터 그대로 사용, 현실적인 분포 반영을 위해 &lt;b&gt;중복 요청 의도적 포함&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;샘플 데이터 종류: 약 20,000여 개&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동일 시나리오 반복 실행&lt;/b&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;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;동일한 시나리오를 3회 반복&lt;/b&gt;&lt;/span&gt; 실행&lt;/li&gt;
&lt;li&gt;1회 결과만으로는 네트워크 지연&amp;middot;서버 상태 등 일시적 변수 배제 어려움&lt;/li&gt;
&lt;li&gt;3회 반복 &amp;rarr; 수치의 &lt;b&gt;재현성 확인&lt;/b&gt;, 해당 스펙에서의 실제 성능임을 검증&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;성능 테스트 결과&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;P95 / P99&lt;/b&gt; &amp;mdash; 전체 요청 중 빠른 순서로 95% / 99% 지점의 응답시간. P95가 700ms라면 100건 중 95건은 700ms 이내 응답. 평균보다 느린 요청까지 반영한 &lt;b&gt;체감 응답시간 지표&lt;/b&gt;입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;100 User &amp;mdash; 단일 POD&lt;/b&gt;&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;시나리오&lt;/th&gt;
&lt;th&gt;POD&lt;/th&gt;
&lt;th&gt;TPS&lt;/th&gt;
&lt;th&gt;평균(ms)&lt;/th&gt;
&lt;th&gt;P95(ms)&lt;/th&gt;
&lt;th&gt;P99(ms)&lt;/th&gt;
&lt;th&gt;에러율&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;#1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;168.37&lt;/td&gt;
&lt;td&gt;534.57&lt;/td&gt;
&lt;td&gt;719.9&lt;/td&gt;
&lt;td&gt;796&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;182.94&lt;/td&gt;
&lt;td&gt;491.89&lt;/td&gt;
&lt;td&gt;733&lt;/td&gt;
&lt;td&gt;799&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;b&gt;184.24&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;488.8&lt;/td&gt;
&lt;td&gt;722&lt;/td&gt;
&lt;td&gt;774&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3회 반복 중 최대 &lt;b&gt;184.24 TPS&lt;/b&gt; 달성, 에러율 0% 안정적.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;629&quot; data-origin-height=&quot;229&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpLcCb/dJMcahDFdYz/xe5kc9A0bXLlwQ3BmwyQbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpLcCb/dJMcahDFdYz/xe5kc9A0bXLlwQ3BmwyQbK/img.png&quot; data-alt=&quot;Grafana 16:25~16:35 시간대 모니터링 (샘플수 55,300여개)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpLcCb/dJMcahDFdYz/xe5kc9A0bXLlwQ3BmwyQbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpLcCb%2FdJMcahDFdYz%2Fxe5kc9A0bXLlwQ3BmwyQbK%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;229&quot; data-origin-width=&quot;629&quot; data-origin-height=&quot;229&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Grafana 16:25~16:35 시간대 모니터링 (샘플수 55,300여개)&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;Ramp-up 구간 (0~60s)&lt;/b&gt;: 유저 수 점진 증가와 함께 CPU가 빠르게 상승&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Steady State&lt;/b&gt;: CPU &amp;asymp; 100% 포화 구간 &amp;mdash; TPS 184 달성 시점과 일치&lt;/li&gt;
&lt;li&gt;&lt;b&gt;테스트 종료 후&lt;/b&gt;: CPU가 점진적으로 하강하며 0%에 수렴&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;200 User &amp;mdash; 단일 POD&lt;/b&gt;&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;시나리오&lt;/th&gt;
&lt;th&gt;POD&lt;/th&gt;
&lt;th&gt;TPS&lt;/th&gt;
&lt;th&gt;평균(ms)&lt;/th&gt;
&lt;th&gt;P95(ms)&lt;/th&gt;
&lt;th&gt;P99(ms)&lt;/th&gt;
&lt;th&gt;에러율&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;#4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;182.18&lt;/td&gt;
&lt;td&gt;986.19&lt;/td&gt;
&lt;td&gt;1,602&lt;/td&gt;
&lt;td&gt;1,682&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#5&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;175.52&lt;/td&gt;
&lt;td&gt;1,024&lt;/td&gt;
&lt;td&gt;1,606&lt;/td&gt;
&lt;td&gt;1,714&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;#6&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;179.48&lt;/td&gt;
&lt;td&gt;1,001.4&lt;/td&gt;
&lt;td&gt;1,633&lt;/td&gt;
&lt;td&gt;1,729&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시 유저 2배 증가 시 &lt;b&gt;TPS는 유지&lt;/b&gt;되나, 평균 응답시간이 ~2배 상승. POD 추가 없이 처리량 한계 도달.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;400 User &amp;mdash; POD 수평 확장&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;height: 55px;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 23px;&quot;&gt;
&lt;th style=&quot;height: 23px;&quot;&gt;시나리오&lt;/th&gt;
&lt;th style=&quot;height: 23px;&quot;&gt;POD&lt;/th&gt;
&lt;th style=&quot;height: 23px;&quot;&gt;TPS&lt;/th&gt;
&lt;th style=&quot;height: 23px;&quot;&gt;평균(ms)&lt;/th&gt;
&lt;th style=&quot;height: 23px;&quot;&gt;P95(ms)&lt;/th&gt;
&lt;th style=&quot;height: 23px;&quot;&gt;P99(ms)&lt;/th&gt;
&lt;th style=&quot;height: 23px;&quot;&gt;에러율&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 16px;&quot;&gt;
&lt;td style=&quot;height: 16px;&quot;&gt;#7&lt;/td&gt;
&lt;td style=&quot;height: 16px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;height: 16px;&quot;&gt;108.38&lt;/td&gt;
&lt;td style=&quot;height: 16px;&quot;&gt;3,305.66&lt;/td&gt;
&lt;td style=&quot;height: 16px;&quot;&gt;5,020&lt;/td&gt;
&lt;td style=&quot;height: 16px;&quot;&gt;5,628&lt;/td&gt;
&lt;td style=&quot;height: 16px;&quot;&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 16px;&quot;&gt;
&lt;td style=&quot;height: 16px;&quot;&gt;#8&lt;/td&gt;
&lt;td style=&quot;height: 16px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;height: 16px;&quot;&gt;109.7&lt;/td&gt;
&lt;td style=&quot;height: 16px;&quot;&gt;3,266.24&lt;/td&gt;
&lt;td style=&quot;height: 16px;&quot;&gt;4,143&lt;/td&gt;
&lt;td style=&quot;height: 16px;&quot;&gt;4,956&lt;/td&gt;
&lt;td style=&quot;height: 16px;&quot;&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;400 User 구간은 평균 응답시간이 3초 이상으로 치솟아 서비스 허용 범위를 벗어났습니다. 이 구간은 &lt;b&gt;실 서비스에서 도달할 가능성이 낮은 임계치&lt;/b&gt;를 확인하기 위한 참고용 테스트로, 이상 동작 없이 정상 응답했음을 확인하는 것으로 목적을 달성했습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결과 요약&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 테스트를 통해 &lt;b&gt;단일 POD(1 vCore / 2 GiB) 기준 최대 TPS는 184&lt;/b&gt;임을 확인했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 서비스는 초기 단계로 목표 TPS가 한 자릿수~수십 단위 수준 =&amp;gt; 부하테스트 결과, 트래픽이 예상치를 크게 상회하더라도 &lt;b&gt;POD 1개로 충분히 커버 가능&lt;/b&gt;함을 데이터로 검증 완료.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 서비스 목표 TPS는 수십 단위 수준으로, 단일 POD 최대 TPS 184 대비 &lt;b&gt;충분한 여유를 확보한 상태로 확인할 수 있었습니다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;인수인계된 &lt;code&gt;Xms=Xmx=2048m&lt;/code&gt; 고정 설정에서 &lt;b&gt;환경별 Pod 메모리와의 불일치&lt;/b&gt;를 발견 및 개선&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Heap = Pod 메모리의 75%&lt;/b&gt; 룰 적용 &amp;rarr; Non-Heap &lt;b&gt;OOM 위험 해소&lt;/b&gt;, 운영계 성능 향상&lt;/li&gt;
&lt;li&gt;Heap 최적화 이후 부하테스트 진행 &amp;rarr; &lt;b&gt;단일 POD 최대 TPS &lt;/b&gt;확인&lt;/li&gt;
&lt;li&gt;현재 목표 TPS 대비 충분한 여유 확보 &amp;mdash; &lt;b&gt;POD 1개로 충분한 수준&lt;/b&gt;임을 데이터로 검증&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/java|Spring</category>
      <category>DevOps</category>
      <category>heap</category>
      <category>JMeter</category>
      <category>jvm</category>
      <category>JVM튜닝</category>
      <category>ramp up</category>
      <category>TPS</category>
      <category>부하테스트</category>
      <category>성능최적화</category>
      <category>최적화</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/206</guid>
      <comments>https://snapcode.tistory.com/entry/Jmeter-%EB%B6%80%ED%95%98%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%99%80-JVM-Heap-%EC%B5%9C%EC%A0%81%ED%99%94#entry206comment</comments>
      <pubDate>Tue, 17 Mar 2026 01:46:54 +0900</pubDate>
    </item>
    <item>
      <title>[Kanana 429] 카나나 앰배서더 첫날 &amp;mdash; 100명 모인 발대식 현장 후기</title>
      <link>https://snapcode.tistory.com/entry/Kanana-429-%EC%B9%B4%EB%82%98%EB%82%98-%EC%95%B0%EB%B0%B0%EC%84%9C%EB%8D%94-%EC%B2%AB%EB%82%A0-%E2%80%94-100%EB%AA%85%EC%9D%B4-%EB%AA%A8%EC%9D%B8-%EB%B0%9C%EB%8C%80%EC%8B%9D-%ED%98%84%EC%9E%A5-%ED%9B%84%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요. 오늘은,&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;KANANA 429 앰배서더 발대식&lt;/b&gt;&amp;nbsp;에 다녀왔습니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2026년 3월 13일, 카카오 AI 캠퍼스(경기 용인)에서 &lt;b&gt;KANANA 429 앰배서더 발대식&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;1080&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xzQMb/dJMcabXKjbG/PEL4nyOMJrZUNPas721050/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xzQMb/dJMcabXKjbG/PEL4nyOMJrZUNPas721050/img.png&quot; data-alt=&quot;AI캠퍼스 입구&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xzQMb/dJMcabXKjbG/PEL4nyOMJrZUNPas721050/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxzQMb%2FdJMcabXKjbG%2FPEL4nyOMJrZUNPas721050%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;444&quot; height=&quot;592&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AI캠퍼스 입구&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;738&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFidgn/dJMb99ZXgAC/IrwRRawvJHppGsSqlBTMik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFidgn/dJMb99ZXgAC/IrwRRawvJHppGsSqlBTMik/img.png&quot; data-alt=&quot;감각적인 포토부스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFidgn/dJMb99ZXgAC/IrwRRawvJHppGsSqlBTMik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFidgn%2FdJMb99ZXgAC%2FIrwRRawvJHppGsSqlBTMik%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;444&quot; height=&quot;592&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;738&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;대학생 / 크리에이터 / AI 전문가 세 그룹이 한자리에 모인 자리로, &lt;b&gt;참석자만 약 100명&lt;/b&gt;에 달했습니다.&lt;/p&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;1080&quot; data-origin-height=&quot;563&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mpsGC/dJMcajnRFBW/s79bKPL1yDyrpSisz1tFy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mpsGC/dJMcajnRFBW/s79bKPL1yDyrpSisz1tFy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mpsGC/dJMcajnRFBW/s79bKPL1yDyrpSisz1tFy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmpsGC%2FdJMcajnRFBW%2Fs79bKPL1yDyrpSisz1tFy1%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;555&quot; height=&quot;478&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;563&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;입장: 키캡 &amp;middot; 이름표 &amp;middot; 죠르디 카드&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도착하자마자 귀여운 &lt;b&gt;빈 키캡&lt;/b&gt;과 타임테이블이 적힌 이름표를 받았습니다.&lt;/p&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;1031&quot; data-origin-height=&quot;902&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bX4gDr/dJMcacPS2Bh/yK9fHkyMpScUespXqm46Qk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bX4gDr/dJMcacPS2Bh/yK9fHkyMpScUespXqm46Qk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bX4gDr/dJMcacPS2Bh/yK9fHkyMpScUespXqm46Qk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbX4gDr%2FdJMcacPS2Bh%2FyK9fHkyMpScUespXqm46Qk%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;444&quot; height=&quot;592&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1031&quot; data-origin-height=&quot;902&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dU4QxL/dJMcaiWPqU7/VYh6wNnFdRd3iSwMQw8IyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dU4QxL/dJMcaiWPqU7/VYh6wNnFdRd3iSwMQw8IyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dU4QxL/dJMcaiWPqU7/VYh6wNnFdRd3iSwMQw8IyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdU4QxL%2FdJMcaiWPqU7%2FVYh6wNnFdRd3iSwMQw8IyK%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;444&quot; height=&quot;592&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;1080&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d5yywL/dJMcadnGyhJ/agzKpeNSKOZkKH7UEI2Jg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d5yywL/dJMcadnGyhJ/agzKpeNSKOZkKH7UEI2Jg1/img.png&quot; data-alt=&quot;이벤트 미션 모두 성공&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d5yywL/dJMcadnGyhJ/agzKpeNSKOZkKH7UEI2Jg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd5yywL%2FdJMcadnGyhJ%2FagzKpeNSKOZkKH7UEI2Jg1%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;444&quot; height=&quot;344&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;836&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;카나나 모델을 활용한 것으로 알고있고, &lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;카카오톡 사진 우측상단의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&quot;영상으로 만들기&quot; 기능도 처음 사용&lt;/b&gt;해볼 수 있었습니다.&lt;/p&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;1080&quot; data-origin-height=&quot;1149&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjOtQm/dJMcagSii9M/KIRiUBB2ddkaKaoSjq30bk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjOtQm/dJMcagSii9M/KIRiUBB2ddkaKaoSjq30bk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjOtQm/dJMcagSii9M/KIRiUBB2ddkaKaoSjq30bk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjOtQm%2FdJMcagSii9M%2FKIRiUBB2ddkaKaoSjq30bk%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;333&quot; height=&quot;444&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1149&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;각종 굿즈 퀄리티도 상당&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;1080&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbtfYa/dJMcacvBJkR/t6ODCG5rF0pscRtdNLxbd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbtfYa/dJMcacvBJkR/t6ODCG5rF0pscRtdNLxbd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbtfYa/dJMcacvBJkR/t6ODCG5rF0pscRtdNLxbd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbtfYa%2FdJMcacvBJkR%2Ft6ODCG5rF0pscRtdNLxbd1%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;555&quot; height=&quot;740&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1196&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byag77/dJMcahXXo7h/gmp6Q5a5hxBSKod06IWoj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byag77/dJMcahXXo7h/gmp6Q5a5hxBSKod06IWoj1/img.png&quot; data-alt=&quot;명함 홀더&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byag77/dJMcahXXo7h/gmp6Q5a5hxBSKod06IWoj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbyag77%2FdJMcahXXo7h%2Fgmp6Q5a5hxBSKod06IWoj1%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;333&quot; height=&quot;369&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1196&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;디저트&lt;/b&gt;도 정말 맛있었습니다.&lt;br /&gt;에그 타르트도 맛있었습니다. 잘먹었습니다 감사합니다&lt;/p&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;1080&quot; data-origin-height=&quot;846&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cApTuS/dJMcafZ7yds/IBTWJd182HfAB5nlFHWN0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cApTuS/dJMcafZ7yds/IBTWJd182HfAB5nlFHWN0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cApTuS/dJMcafZ7yds/IBTWJd182HfAB5nlFHWN0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcApTuS%2FdJMcafZ7yds%2FIBTWJd182HfAB5nlFHWN0k%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;444&quot; height=&quot;348&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;846&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;앰배서더 활동 시작에 앞서&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;유의사항&lt;/b&gt;과, 카카오안의 AI서비스들을 확인할 수 있었습니다. &lt;b&gt;AI국민비서 '구삐'는 매우 잘 사용&lt;/b&gt;하고 있었는데, 이 외에도 &lt;b&gt;아직 사용해보지 못한 서비스들&lt;/b&gt;이 의외로 많았습니다.&lt;/p&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;1080&quot; data-origin-height=&quot;551&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/swzlk/dJMcabDrYGq/gng3z6PhIY3RsXUhmLwNdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/swzlk/dJMcabDrYGq/gng3z6PhIY3RsXUhmLwNdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/swzlk/dJMcabDrYGq/gng3z6PhIY3RsXUhmLwNdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fswzlk%2FdJMcabDrYGq%2Fgng3z6PhIY3RsXUhmLwNdK%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;1080&quot; height=&quot;551&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;551&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;세션 1: 조코딩님 &amp;mdash; 최신 AI 트렌드 &amp;amp; 카나나-o 활용 사례&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유튜브 코딩 채널로 유명한 &lt;b&gt;조코딩&lt;/b&gt;님께서 최신 AI 트렌드와 카나나-o 서비스 활용 사례를 발표해 주셨습니다.&lt;br /&gt;비IT 분야 참석자들도 &lt;b&gt;쉽게 이해할 수 있도록&lt;/b&gt; 풀어주셔서, AI 개념 접근성 면에서 좋은 시간이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조코딩님께서는 무엇보다도 AI가 어떻게 활용되고 있고, 앞으로 어떻게 활용할 수 있는지, &lt;b&gt;&quot;AI 활용&quot; 이라는 키워드&lt;/b&gt;에 있어서는 정말 컨텐츠로 잘 다루시는 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한가지 기억나는 점은, &lt;b&gt;시댄스2.0이 제작한 재난 영화의 퀄리티가, 수십 수백억을 공들여 만든 것처럼&lt;/b&gt; 진짜 같았던게 제 머릿속에는 매우 임팩트가 컸습니다. 신기했습니다.&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-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1151&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3AQsz/dJMcad2gTXY/RUPcmPLwmQ3ZHOlc8nXWUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3AQsz/dJMcad2gTXY/RUPcmPLwmQ3ZHOlc8nXWUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3AQsz/dJMcad2gTXY/RUPcmPLwmQ3ZHOlc8nXWUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3AQsz%2FdJMcad2gTXY%2FRUPcmPLwmQ3ZHOlc8nXWUk%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;444&quot; height=&quot;473&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1151&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;점심: 코스요리&lt;/b&gt;&lt;/h2&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-filename=&quot;blob&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;1334&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bm5jba/dJMcaaYO5xC/0vedJzYog6x3TeBLIgz8K0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bm5jba/dJMcaaYO5xC/0vedJzYog6x3TeBLIgz8K0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bm5jba/dJMcaaYO5xC/0vedJzYog6x3TeBLIgz8K0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbm5jba%2FdJMcaaYO5xC%2F0vedJzYog6x3TeBLIgz8K0%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;444&quot; height=&quot;638&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;1334&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모두 찍진 못했지만 메인디시 2개만 보여드립니다.&lt;/p&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;1080&quot; data-origin-height=&quot;932&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E4TeT/dJMcaf6TRsv/fT03V4zik2lBSV7OotjxPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E4TeT/dJMcaf6TRsv/fT03V4zik2lBSV7OotjxPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E4TeT/dJMcaf6TRsv/fT03V4zik2lBSV7OotjxPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE4TeT%2FdJMcaf6TRsv%2FfT03V4zik2lBSV7OotjxPk%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;444&quot; height=&quot;592&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;932&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdbPEV/dJMcadgXcCE/Gkuexopv8imukxFV9Ah3Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdbPEV/dJMcadgXcCE/Gkuexopv8imukxFV9Ah3Kk/img.png&quot; data-alt=&quot;입에서 녹습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdbPEV/dJMcadgXcCE/Gkuexopv8imukxFV9Ah3Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdbPEV%2FdJMcadgXcCE%2FGkuexopv8imukxFV9Ah3Kk%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;444&quot; height=&quot;592&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;세션 2: Hunter님 ㅡ 네트워킹: AI 전문가 그룹&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오후에는 AI 전문가 그룹으로 이동해 hunter님께서 진행해주시는&amp;nbsp;&lt;b&gt;네트워킹&lt;/b&gt; 시간을 가졌습니다.&lt;br /&gt;저서를 출간하신 분, 석&amp;middot;박사 과정 중인 분, 현업 종사자, 취업 준비생까지 다양한 분들이 계셨고, 생각보다 훨씬 높은 수준의 자리여서 많이 놀랐습니다. 스펙이 대단하신분들이 정말 많이 계셨습니다..&lt;/p&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;1080&quot; data-origin-height=&quot;889&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3XVVf/dJMcaiP3rpO/lk3Sfdo3eKvlrEQ4pAdJM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3XVVf/dJMcaiP3rpO/lk3Sfdo3eKvlrEQ4pAdJM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3XVVf/dJMcaiP3rpO/lk3Sfdo3eKvlrEQ4pAdJM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3XVVf%2FdJMcaiP3rpO%2Flk3Sfdo3eKvlrEQ4pAdJM1%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;444&quot; height=&quot;365&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;889&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;세션 3: Peter님 &amp;mdash; Deep Request&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이어서 &lt;b&gt;Peter님의 Deep Request 세션&lt;/b&gt;에 참석했습니다.&lt;br /&gt;카나나 &lt;b&gt;모델의 구조와 동작 방식&lt;/b&gt;에 대해 깊이 있는 내용을 다뤄주셔서, 모델에 대한 이해를 한층 높이며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번에 포스팅한 내용을 한번 더 되새겨볼 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://snapcode.tistory.com/200&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://snapcode.tistory.com/200&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1773403910359&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Kanana-o] 한국 최초 옴니 AI, 직접 써봤습니다. (feat. 부산 사투리)&quot; data-og-description=&quot;안녕하세요. 운이 좋게도 Kanana-o Beta Tester 로 선정되어,멀티모달 언어 모델을 직접 사용해보고, 간단한 서비스로 구현해본 경험을 공유하고자 합니다.실제로 사용해보며 느낀 점과 함께, 기능 개&quot; data-og-host=&quot;snapcode.tistory.com&quot; data-og-source-url=&quot;https://snapcode.tistory.com/200&quot; data-og-url=&quot;https://snapcode.tistory.com/200&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bhXfdK/dJMb9hCZQYQ/aKypT2Wh3RKFPFKDGtrk11/img.png?width=800&amp;amp;height=451&amp;amp;face=0_0_800_451,https://scrap.kakaocdn.net/dn/rdfiM/dJMb9bv0Qmu/GAodBGLzdb4Ahk6GDprNW0/img.png?width=800&amp;amp;height=451&amp;amp;face=0_0_800_451,https://scrap.kakaocdn.net/dn/dpzjcV/dJMb9fZtR89/HXG4d4z3VPtnFKrKxgVJeK/img.jpg?width=1280&amp;amp;height=500&amp;amp;face=0_0_1280_500&quot;&gt;&lt;a href=&quot;https://snapcode.tistory.com/200&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://snapcode.tistory.com/200&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bhXfdK/dJMb9hCZQYQ/aKypT2Wh3RKFPFKDGtrk11/img.png?width=800&amp;amp;height=451&amp;amp;face=0_0_800_451,https://scrap.kakaocdn.net/dn/rdfiM/dJMb9bv0Qmu/GAodBGLzdb4Ahk6GDprNW0/img.png?width=800&amp;amp;height=451&amp;amp;face=0_0_800_451,https://scrap.kakaocdn.net/dn/dpzjcV/dJMb9fZtR89/HXG4d4z3VPtnFKrKxgVJeK/img.jpg?width=1280&amp;amp;height=500&amp;amp;face=0_0_1280_500');&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;[Kanana-o] 한국 최초 옴니 AI, 직접 써봤습니다. (feat. 부산 사투리)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요. 운이 좋게도 Kanana-o Beta Tester 로 선정되어,멀티모달 언어 모델을 직접 사용해보고, 간단한 서비스로 구현해본 경험을 공유하고자 합니다.실제로 사용해보며 느낀 점과 함께, 기능 개&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;snapcode.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;세션 4: CHOI님 ㅡ AI기술 중심 콘텐츠 전략&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다른 앰배서더분들께서 입을 모아서 &lt;b&gt;&quot;우리나라에서 가장 AI소식이 빠른 분이시다.&quot;&lt;/b&gt; 라고 하실만큼, 최신 동향을 파악하고 전파하는데에 힘쓰고 계신것으로 알고있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;인상 깊었던 점은, &lt;b&gt;쓰레드를 선택한 이유&lt;/b&gt;도 그렇고, &lt;b&gt;본인이 가진 생각과 콘텐츠 전략&lt;/b&gt;을 선보이시는게 멋있었습니다.&lt;/p&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;1080&quot; data-origin-height=&quot;648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mnXXE/dJMcajamwoe/9FjYbMc3gbm3eDl4dopfBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mnXXE/dJMcajamwoe/9FjYbMc3gbm3eDl4dopfBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mnXXE/dJMcajamwoe/9FjYbMc3gbm3eDl4dopfBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmnXXE%2FdJMcajamwoe%2F9FjYbMc3gbm3eDl4dopfBk%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;555&quot; height=&quot;333&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;648&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이동: 판교아지트 &amp;lt;---&amp;gt; 카카오 AI 캠퍼스&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출발과 귀환 모두&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;셔틀버스&lt;/b&gt;를 이용했습니다.&lt;br /&gt;역세권인 카카오 판교아지트 1층 버스정류장에서 승하차해서 덕분에 매우 편리했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNH556/dJMcad2gThQ/RWvAyPRgzIL9W6RxVLk8b0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNH556/dJMcad2gThQ/RWvAyPRgzIL9W6RxVLk8b0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNH556/dJMcad2gThQ/RWvAyPRgzIL9W6RxVLk8b0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNH556%2FdJMcad2gThQ%2FRWvAyPRgzIL9W6RxVLk8b0%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;333&quot; height=&quot;444&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카카오가 지금껏 쌓아온 모델을 직접 먼저 경험해보고, 앞으로의 성장을 함께 지켜볼 수 있다는 점에서 기대가 큽니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘을 준비해주신 모든 분들께 감사드립니다. 짧은 시간이었지만 밀도 있는 하루였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;KANANA 429&lt;/b&gt;, 잘 부탁드립니다.&lt;/p&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;1440&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bA94iN/dJMcacoOCDQ/M4lmfknRmhXSe7G2wZAwNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bA94iN/dJMcacoOCDQ/M4lmfknRmhXSe7G2wZAwNk/img.png&quot; data-alt=&quot;굿즈 모음&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bA94iN/dJMcacoOCDQ/M4lmfknRmhXSe7G2wZAwNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbA94iN%2FdJMcacoOCDQ%2FM4lmfknRmhXSe7G2wZAwNk%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;1440&quot; height=&quot;1080&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1080&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;blob&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;961&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rDsIb/dJMcagkqSnH/8GJmogqc6yjUs4ijBAsfUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rDsIb/dJMcagkqSnH/8GJmogqc6yjUs4ijBAsfUk/img.png&quot; data-alt=&quot;앰배서더 바람막이 등 로고&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rDsIb/dJMcagkqSnH/8GJmogqc6yjUs4ijBAsfUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrDsIb%2FdJMcagkqSnH%2F8GJmogqc6yjUs4ijBAsfUk%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;1440&quot; height=&quot;961&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;961&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기억에 남으시는 분들 - 영상 합성 데이터 데2빗님, 마음 치유 hn마인드님, 연차안쓴 기획자님, 파워E 엔C 데이터 jsy님, 모델 연구 석사님, 예술계 sin코님, 취준중이신님, 제주도 훈ter님, 러닝크루 인플루언서님, 가정기술 교직원님, 지급받은 명함100개중 1개빼고 전부 교환했다는 대학생님 등등&lt;/p&gt;</description>
      <category>IT/AI</category>
      <category>Choi</category>
      <category>choi.openai</category>
      <category>KANANA429</category>
      <category>발대식</category>
      <category>앰배서더</category>
      <category>조코딩</category>
      <category>카나나</category>
      <category>카나나앰배서더</category>
      <category>카카오</category>
      <category>카카오AI</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/205</guid>
      <comments>https://snapcode.tistory.com/entry/Kanana-429-%EC%B9%B4%EB%82%98%EB%82%98-%EC%95%B0%EB%B0%B0%EC%84%9C%EB%8D%94-%EC%B2%AB%EB%82%A0-%E2%80%94-100%EB%AA%85%EC%9D%B4-%EB%AA%A8%EC%9D%B8-%EB%B0%9C%EB%8C%80%EC%8B%9D-%ED%98%84%EC%9E%A5-%ED%9B%84%EA%B8%B0#entry205comment</comments>
      <pubDate>Fri, 13 Mar 2026 21:24:28 +0900</pubDate>
    </item>
    <item>
      <title>[CI/CD] paths-filter 하나로 빌드 시간 72% 단축하기</title>
      <link>https://snapcode.tistory.com/entry/CICD-paths-filter-%ED%95%98%EB%82%98%EB%A1%9C-%EB%B9%8C%EB%93%9C-%EC%8B%9C%EA%B0%84-72-%EB%8B%A8%EC%B6%95%ED%95%98%EA%B8%B0</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;629&quot; data-origin-height=&quot;648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/013b4/dJMcaa5ABfc/LEXarZpCNbdJDS7Hm6EN91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/013b4/dJMcaa5ABfc/LEXarZpCNbdJDS7Hm6EN91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/013b4/dJMcaa5ABfc/LEXarZpCNbdJDS7Hm6EN91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F013b4%2FdJMcaa5ABfc%2FLEXarZpCNbdJDS7Hm6EN91%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;648&quot; data-origin-width=&quot;629&quot; data-origin-height=&quot;648&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이드 프로젝트를 개발하면서 GitHub Actions로 CI/CD를 운영하고 있습니다.&lt;br /&gt;프론트엔드(Next.js)와 백엔드(Spring Boot + Docker)를 하나의 레포에서 관리하다 보니,&lt;br /&gt;&lt;b&gt;텍스트 하나 수정해도 매번 풀빌드&lt;/b&gt;가 돌았습니다.&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;Node.js 설치 &amp;rarr; npm ci &amp;rarr; Next.js 빌드
JDK 설치 &amp;rarr; Gradle 빌드 &amp;rarr; Docker 빌드 &amp;rarr; Docker 푸시 &amp;rarr; 서버 배포&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평균 소요 시간: &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;약 3분대...!&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;FE 수정 1줄 &amp;rarr; 백엔드 Docker 이미지까지 새로 빌드 &amp;rarr; 배포까지 3분 대기&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 분석&lt;/h2&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;# 기존: 모든 커밋에서 무조건 전체 실행
- name: Build with Gradle       # BE 변경이 없어도 항상 실행
  run: ./gradlew build -x test

- name: Build and push Docker image  # FE 변경만 있어도 항상 실행
  uses: docker/build-push-action@v6&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FE 변경 시 낭비되는 단계:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JDK 설치 / Gradle 빌드 / Docker 빌드&amp;middot;푸시 / BE 서버 배포&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;BE 변경 시 낭비되는 단계:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Node.js 설치 / npm ci / Next.js 빌드 / rsync 전송 / FE 서버 배포&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결: dorny/paths-filter로 변경 경로 감지&lt;/h2&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# fetch-depth: 2 설정 처리로, 이전 커밋과 diff 비교 불가 문제 해결
- name: Checkout code
  uses: actions/checkout@v4
  with:
    fetch-depth: 2

# 변경된 경로 감지 처리로, FE/BE 빌드 분기 문제 해결
- name: Detect changed paths
  uses: dorny/paths-filter@v3
  id: filter
  with:
    filters: |
      fe:
        - 'frontend/**'
      be:
        - 'src/**'
        - 'build.gradle'
        - 'Dockerfile'
        - 'gradle/**'&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# FE 변경 시만 실행 처리로, 불필요한 Node.js 빌드 스킵 문제 해결
- name: Build Next.js Frontend
  if: steps.filter.outputs.fe == 'true'
  run: cd frontend &amp;amp;&amp;amp; npm run build

# BE 변경 시만 실행 처리로, 불필요한 Docker 빌드 스킵 문제 해결
- name: Build and push Docker image
  if: steps.filter.outputs.be == 'true'
  uses: docker/build-push-action@v6
  with:
    context: .
    push: true
    tags: sample-username/sample-app:latest
    cache-from: type=gha   # GHA 레이어 캐시 사용 처리로, 변경된 레이어만 재빌드 문제 해결
    cache-to: type=gha,mode=max&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;추가: Docker 이미지 정리 전략&lt;/h2&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;# 기존: 전체 이미지 삭제(-a 옵션) 처리 &amp;rarr; 빌드 캐시 레이어까지 제거되어 재취득 비용 발생
docker image prune -af

# 변경: dangling 이미지만 삭제 처리로, 구 이미지 누적 + 캐시 레이어 손실 문제 해결
docker image prune -f&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;docker pull&lt;/code&gt;로 새 &lt;code&gt;latest&lt;/code&gt;를 받으면 기존 태그는 자동으로 &lt;code&gt;&amp;lt;none&amp;gt;:&amp;lt;none&amp;gt;&lt;/code&gt; dangling 상태가 됩니다.&lt;br /&gt;&lt;code&gt;-f&lt;/code&gt;만으로 충분히 정리되며, 유효한 레이어 캐시는 그대로 유지됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;적용 전&lt;/th&gt;
&lt;th&gt;적용 후&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FE 단독 변경&lt;/td&gt;
&lt;td&gt;약 3분 3초 (풀빌드 20회 평균)&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;51초&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;감소율&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&amp;mdash;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;약 72% 감소&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;측정 기준: GitHub Actions 실제 run 기록 20회 평균(풀빌드) vs 조건부 빌드 첫 FE 단독 실행&lt;/b&gt;&lt;br /&gt;&lt;b&gt;=&amp;gt; BE 단독 변경 시에도 Node.js / npm ci / Next.js 빌드 / rsync 등이 스킵되어 추가 단축 효과가 기대됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;dorny/paths-filter&lt;/code&gt;&lt;/b&gt; 하나로 변경 경로를 감지하고, 각 스텝에 &lt;code&gt;if:&lt;/code&gt; 조건을 추가하는 것만으로&lt;br /&gt;빌드 시간을 크게 줄일 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;모노레포 구조에서 FE / BE를 함께 관리한다면 적용을 추천&lt;/b&gt;합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/etc</category>
      <category>cicd</category>
      <category>docker</category>
      <category>GithubActions</category>
      <category>nextjs</category>
      <category>springboot</category>
      <category>깃헙액션</category>
      <category>도커</category>
      <category>모노레포</category>
      <category>빌드최적화</category>
      <category>자동화</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/204</guid>
      <comments>https://snapcode.tistory.com/entry/CICD-paths-filter-%ED%95%98%EB%82%98%EB%A1%9C-%EB%B9%8C%EB%93%9C-%EC%8B%9C%EA%B0%84-72-%EB%8B%A8%EC%B6%95%ED%95%98%EA%B8%B0#entry204comment</comments>
      <pubDate>Fri, 13 Mar 2026 00:57:08 +0900</pubDate>
    </item>
    <item>
      <title>[Linux] crontab &amp;times; find 조합으로 N일 초과 파일 자동 삭제하기</title>
      <link>https://snapcode.tistory.com/entry/Linux-crontab-%C3%97-find-%EC%A1%B0%ED%95%A9%EC%9C%BC%EB%A1%9C-N%EC%9D%BC-%EC%B4%88%EA%B3%BC-%ED%8C%8C%EC%9D%BC-%EC%9E%90%EB%8F%99-%EC%82%AD%EC%A0%9C%ED%95%98%EA%B8%B0</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;758&quot; data-origin-height=&quot;213&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d43Qqc/dJMcahKpHWz/O3aS0kdoehvjSNKFtJuV5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d43Qqc/dJMcahKpHWz/O3aS0kdoehvjSNKFtJuV5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d43Qqc/dJMcahKpHWz/O3aS0kdoehvjSNKFtJuV5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd43Qqc%2FdJMcahKpHWz%2FO3aS0kdoehvjSNKFtJuV5k%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;758&quot; height=&quot;213&quot; data-origin-width=&quot;758&quot; data-origin-height=&quot;213&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버를 운영하다 보면 &lt;b&gt;로그 파일&lt;/b&gt;이 조용히 쌓여 어느 날 디스크를 꽉 채우는 상황을 겪게 됩니다.&lt;br /&gt;크롤러 로그가 100MB를 넘긴 후에야 뒤늦게 수동으로 지웠는데, 이 과정을 자동화한 방법을 소개합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;상황 정리&lt;/h2&gt;
&lt;pre class=&quot;pf&quot;&gt;&lt;code&gt;/home/user/project/logs/
├── crawler_20260308.log   # 날짜별 로그
├── crawler_20260309.log
├── crawler_20260310.log
├── crawler_20260311.log
└── crawler.log            # 누적 메인 로그 (108MB...)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;날짜별 로그&lt;/b&gt;(&lt;code&gt;crawler_YYYYMMDD.log&lt;/code&gt;)는 매일 생성되지만, 오래된 파일은 굳이 보관할 필요가 없습니다.&lt;br /&gt;7일치만 남기고 초과분은 매일 자동 삭제되도록 설정합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 명령어: &lt;code&gt;find&lt;/code&gt; + &lt;code&gt;-mtime&lt;/code&gt;&lt;/h2&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;# -name 패턴으로 삭제 범위 제한 + -mtime +7로 최근 파일 보호 처리로, 실수로 현재 로그 삭제되는 문제 해결
find /home/user/project/logs -name &quot;crawler_*.log&quot; -mtime +7 -delete&lt;/code&gt;&lt;/pre&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;옵션&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-name &quot;crawler_*.log&quot;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;날짜별 로그 파일만 대상 (메인 로그 제외)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-mtime +7&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;마지막 수정 시간 기준 &lt;b&gt;7일 초과&lt;/b&gt; 파일만 선택&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-delete&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;조건에 맞는 파일 즉시 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;crawler.log&lt;/code&gt;는 파일명 패턴(&lt;code&gt;crawler_*.log&lt;/code&gt;)에 해당하지 않으므로 삭제 대상에서 자동 제외됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;crontab 등록&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;KST 기준 매일 새벽 5시&lt;/b&gt;에 실행되도록 등록합니다.&lt;br /&gt;UTC 서버(Oracle Cloud 등) 기준으로는 &lt;code&gt;UTC 20:00&lt;/code&gt;에 해당합니다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;crontab -e&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;# 매일 KST 05:00 (UTC 20:00) - 7일 초과 날짜별 로그 자동 삭제
0 20 * * * find /home/user/project/logs -name &quot;crawler_*.log&quot; -mtime +7 -delete&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;등록 확인:&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;crontab -l&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;최종 crontab 구성 예시&lt;/h2&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;# 매시간 0분 - 크롤러 실행
0 * * * * source /home/user/project/venv/bin/activate &amp;amp;&amp;amp; cd /home/user/project &amp;amp;&amp;amp; python main.py &amp;gt;&amp;gt; /home/user/project/logs/crawler.log 2&amp;gt;&amp;amp;1

# 매일 KST 05:00 (UTC 20:00) - 7일 초과 날짜별 로그 삭제
0 20 * * * find /home/user/project/logs -name &quot;crawler_*.log&quot; -mtime +7 -delete&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&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;code&gt;find&lt;/code&gt; + &lt;code&gt;-mtime&lt;/code&gt;&lt;/b&gt; 조합으로 N일 초과 파일을 조건부 선택&lt;/li&gt;
&lt;li&gt;&lt;b&gt;파일명 패턴&lt;/b&gt;으로 삭제 대상 범위를 명확히 제한해 안전하게 운영&lt;/li&gt;
&lt;li&gt;&lt;b&gt;crontab&lt;/b&gt;에 등록해 매일 자동 실행, 수동 관리 불필요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하지만 서버 디스크 관리에 꽤 실용적인 조합입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/etc</category>
      <category>crontab</category>
      <category>DevOps</category>
      <category>Find</category>
      <category>linux</category>
      <category>oraclecloud</category>
      <category>디스크관리</category>
      <category>로그관리</category>
      <category>서버관리</category>
      <category>자동화</category>
      <category>크론탭</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/203</guid>
      <comments>https://snapcode.tistory.com/entry/Linux-crontab-%C3%97-find-%EC%A1%B0%ED%95%A9%EC%9C%BC%EB%A1%9C-N%EC%9D%BC-%EC%B4%88%EA%B3%BC-%ED%8C%8C%EC%9D%BC-%EC%9E%90%EB%8F%99-%EC%82%AD%EC%A0%9C%ED%95%98%EA%B8%B0#entry203comment</comments>
      <pubDate>Thu, 12 Mar 2026 01:37:05 +0900</pubDate>
    </item>
    <item>
      <title>[Claude Code] 반복 프롬프트 이제 그만: 슬래시 커맨드로 나만의 AI 단축키 만들기</title>
      <link>https://snapcode.tistory.com/entry/Claude-Code-%EB%B0%98%EB%B3%B5-%ED%94%84%EB%A1%AC%ED%94%84%ED%8A%B8-%EC%9D%B4%EC%A0%9C-%EA%B7%B8%EB%A7%8C-%EC%8A%AC%EB%9E%98%EC%8B%9C-%EC%BB%A4%EB%A7%A8%EB%93%9C%EB%A1%9C-%EB%82%98%EB%A7%8C%EC%9D%98-AI-%EB%8B%A8%EC%B6%95%ED%82%A4-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;375&quot; data-origin-height=&quot;134&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m7jom/dJMcabi7s6o/50xOGE7HonnIiERktlbWA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m7jom/dJMcabi7s6o/50xOGE7HonnIiERktlbWA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m7jom/dJMcabi7s6o/50xOGE7HonnIiERktlbWA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm7jom%2FdJMcabi7s6o%2F50xOGE7HonnIiERktlbWA0%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;666&quot; height=&quot;238&quot; data-origin-width=&quot;375&quot; data-origin-height=&quot;134&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude Code를 쓰다 보면 매번 같은 긴 프롬프트를 붙여넣게 되는 순간이 옵니다.&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;&lt;a href=&quot;https://code.claude.com/docs/ko/slash-commands&quot;&gt;https://code.claude.com/docs/ko/slash-commands&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;슬래시 커맨드란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Claude Code에서 &lt;code&gt;/&lt;/code&gt; 로 시작하는 명령어입니다.&lt;br /&gt;기본 내장 커맨드(&lt;code&gt;/help&lt;/code&gt;, &lt;code&gt;/clear&lt;/code&gt; 등) 외에도, &lt;b&gt;사용자가 직접 만들어 등록&lt;/b&gt;할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;만드는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 루트에 &lt;code&gt;.claude/commands/&lt;/code&gt; 디렉토리를 만들고, 그 안에 &lt;code&gt;.md&lt;/code&gt; 파일을 생성하면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;.claude/
└── commands/
    └── review.md   &amp;rarr;   /review 로 호출됨&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;파일명 = 커맨드명&lt;/b&gt;이고, 파일 내용이 실행될 프롬프트입니다.&lt;br /&gt;사용자 입력을 받으려면 &lt;code&gt;$ARGUMENTS&lt;/code&gt;를 사용합니다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!-- .claude/commands/review.md --&amp;gt;
아래 코드를 리뷰해줘. 개선점이 있으면 제안해줘.

대상: $ARGUMENTS&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호출 시:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;/review src/components/MyComponent.tsx&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; &lt;code&gt;$ARGUMENTS&lt;/code&gt; 자리에 &lt;code&gt;src/components/MyComponent.tsx&lt;/code&gt; 가 자동으로 들어가 실행됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;적용 범위&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;위치&lt;/th&gt;
&lt;th&gt;범위&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.claude/commands/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;현재 프로젝트에서만 동작&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;~/.claude/commands/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;모든 프로젝트에서 동작 (전역)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;많이들 쓰는 커맨드 예시&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. &lt;code&gt;/review&lt;/code&gt; &amp;mdash; 코드 리뷰 자동화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커뮤니티에서 가장 보편적으로 쓰이는 패턴입니다.&lt;br /&gt;PR 올리기 전 빠르게 셀프 리뷰를 받을 수 있어 실용적입니다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!-- .claude/commands/review.md --&amp;gt;
다음 파일을 코드 리뷰해줘.

- 버그 가능성
- 성능 이슈
- 가독성 개선점

파일: $ARGUMENTS&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. &lt;code&gt;/test&lt;/code&gt; &amp;mdash; 테스트 코드 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현 후 바로 테스트 코드를 뽑아내는 용도로 많이 사용됩니다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!-- .claude/commands/test.md --&amp;gt;
아래 파일에 대한 단위 테스트 코드를 작성해줘.
기존 테스트 스타일을 참고해서 일관성 있게 작성해줘.

대상: $ARGUMENTS&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬래시 커맨드는 만드는 데 1분도 걸리지 않지만, 반복 작업을 줄여주는 효과는 상당합니다.&lt;br /&gt;자주 쓰는 프롬프트가 있다면 바로 커맨드로 등록해두시길 추천합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;</description>
      <category>IT/AI</category>
      <category>ai개발도구</category>
      <category>AI활용</category>
      <category>claude</category>
      <category>ClaudeCode</category>
      <category>개발생산성</category>
      <category>개발자팁</category>
      <category>슬래시커맨드</category>
      <category>프롬프트자동화</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/202</guid>
      <comments>https://snapcode.tistory.com/entry/Claude-Code-%EB%B0%98%EB%B3%B5-%ED%94%84%EB%A1%AC%ED%94%84%ED%8A%B8-%EC%9D%B4%EC%A0%9C-%EA%B7%B8%EB%A7%8C-%EC%8A%AC%EB%9E%98%EC%8B%9C-%EC%BB%A4%EB%A7%A8%EB%93%9C%EB%A1%9C-%EB%82%98%EB%A7%8C%EC%9D%98-AI-%EB%8B%A8%EC%B6%95%ED%82%A4-%EB%A7%8C%EB%93%A4%EA%B8%B0#entry202comment</comments>
      <pubDate>Tue, 10 Mar 2026 20:16:44 +0900</pubDate>
    </item>
    <item>
      <title>[OCI] 무료 서버 잡기: API 스크립트로 A1.Flex 인스턴스 자동 선점 (+Discord 알람 연동)</title>
      <link>https://snapcode.tistory.com/entry/OCI-%EB%AC%B4%EB%A3%8C-%EC%84%9C%EB%B2%84-%EC%9E%A1%EA%B8%B0-API-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%A1%9C-A1Flex-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EC%9E%90%EB%8F%99-%EC%84%A0%EC%A0%90%ED%95%98%EA%B8%B0</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;294&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rG6Bm/dJMcagEIAEO/Kkr3LKpPI4lCgfJSChSA50/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rG6Bm/dJMcagEIAEO/Kkr3LKpPI4lCgfJSChSA50/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rG6Bm/dJMcagEIAEO/Kkr3LKpPI4lCgfJSChSA50/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrG6Bm%2FdJMcagEIAEO%2FKkr3LKpPI4lCgfJSChSA50%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;294&quot; height=&quot;172&quot; data-origin-width=&quot;294&quot; data-origin-height=&quot;172&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Elasticsearch를 자체 호스팅하려면 최소 4GB RAM 이상&lt;/b&gt;의 서버가 필요합니다.&lt;br /&gt;JVM 힙 메모리를 대량으로 사용하는 구조라 메모리가 부족하면 OOM으로 죽거나 정상 기동이 어렵습니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;Elasticsearch 최소 권장&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RAM&lt;/td&gt;
&lt;td&gt;4GB 이상 (힙 2GB + OS 캐시)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;2 Core 이상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disk&lt;/td&gt;
&lt;td&gt;SSD 권장&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Oracle Cloud Free Tier의 ARM 인스턴스(VM.Standard.A1.Flex)는 최대 &lt;b&gt;4 OCPU / 24GB RAM까지 무료&lt;/b&gt;로 사용할 수 있어 Elasticsearch 운영에 충분한 스펙입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 항상 &lt;b&gt;&quot;Out of host capacity&quot;&lt;/b&gt; 오류로 생성이 막힙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 OCI REST API + 쉘스크립트 + 크론을 이용해 &lt;b&gt;용량이 생기는 순간 자동으로 인스턴스를 선점하는 방법&lt;/b&gt;을 소개합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;목표 스펙&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;값&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shape&lt;/td&gt;
&lt;td&gt;VM.Standard.A1.Flex&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OCPU&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory&lt;/td&gt;
&lt;td&gt;6GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;Oracle Linux 9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Region&lt;/td&gt;
&lt;td&gt;ap-seoul-1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사전 준비&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. OCI API Key 발급&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OCI Console &amp;rarr; 내 프로파일 &amp;rarr; API 키 &amp;rarr; &lt;b&gt;API 키 추가&lt;/b&gt; &amp;rarr; API 키 쌍 생성 &amp;rarr; 비공개 키 다운로드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발급 후 구성 파일 미리보기에서 아래 값을 확인합니다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;user=ocid1.user.oc1..YOUR_USER_OCID
fingerprint=xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx
tenancy=ocid1.tenancy.oc1..YOUR_TENANCY_OCID
region=ap-seoul-1&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 서버에 PEM 키 업로드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다운로드한 &lt;code&gt;.pem&lt;/code&gt; 파일을 서버에 올리거나, vi로 직접 붙여넣기 합니다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;vi /etc/oci/oci_api_key.pem
# 다운로드한 .pem 내용 붙여넣기 후 저장

chmod 600 /etc/oci/oci_api_key.pem&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 필요한 OCID 수집&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;확인 위치&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tenancy OCID&lt;/td&gt;
&lt;td&gt;OCI Console &amp;rarr; 내 프로파일 &amp;rarr; 테넌시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User OCID&lt;/td&gt;
&lt;td&gt;OCI Console &amp;rarr; 내 프로파일&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compartment OCID&lt;/td&gt;
&lt;td&gt;ID &amp;amp; 보안 &amp;rarr; 구획 (루트 사용 시 Tenancy OCID와 동일)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subnet OCID&lt;/td&gt;
&lt;td&gt;네트워킹 &amp;rarr; VCN &amp;rarr; 서브넷&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image OCID&lt;/td&gt;
&lt;td&gt;인스턴스 생성 화면 &amp;rarr; 개발자도구 Network 탭에서 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;쉘스크립트 작성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크론 전용으로 설계하여 &lt;b&gt;1회 시도 후 종료&lt;/b&gt;합니다. 성공 시 크론을 자동으로 삭제합니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;#!/bin/bash
# OCI 인스턴스 생성 스크립트 (1회 시도 후 종료 - 크론 전용)

# =====================
# 설정값
# =====================
TENANCY_OCID=&quot;ocid1.tenancy.oc1..YOUR_TENANCY_OCID&quot;
USER_OCID=&quot;ocid1.user.oc1..YOUR_USER_OCID&quot;
FINGERPRINT=&quot;xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx&quot;
PRIVATE_KEY_PATH=&quot;/etc/oci/oci_api_key.pem&quot;
REGION=&quot;ap-seoul-1&quot;
COMPARTMENT_OCID=&quot;ocid1.tenancy.oc1..YOUR_TENANCY_OCID&quot;
AVAILABILITY_DOMAIN=&quot;XXXX:AP-SEOUL-1-AD-1&quot;
SUBNET_OCID=&quot;ocid1.subnet.oc1.ap-seoul-1.YOUR_SUBNET_OCID&quot;
IMAGE_OCID=&quot;ocid1.image.oc1.ap-seoul-1.YOUR_IMAGE_OCID&quot;
SSH_PUBLIC_KEY=&quot;ssh-rsa YOUR_PUBLIC_KEY_HERE&quot;
LOG_FILE=&quot;/etc/oci/create_instance_log.txt&quot;

HOST=&quot;iaas.${REGION}.oraclecloud.com&quot;
ENDPOINT=&quot;https://${HOST}/20160918/instances&quot;

# =====================
# OCI API 서명 함수 (RSA-SHA256)
# =====================
sign_request() {
    local method=$1
    local path=$2
    local body=$3

    local date=$(date -u &quot;+%a, %d %b %Y %H:%M:%S GMT&quot;)
    local body_sha256=$(echo -n &quot;$body&quot; | openssl dgst -binary -sha256 | openssl base64)
    local content_length=${#body}

    local signing_string=&quot;date: ${date}
(request-target): $(echo $method | tr '[:upper:]' '[:lower:]') ${path}
host: ${HOST}
content-length: ${content_length}
content-type: application/json
x-content-sha256: ${body_sha256}&quot;

    local signature=$(echo -n &quot;$signing_string&quot; | openssl dgst -binary -sha256 -sign &quot;$PRIVATE_KEY_PATH&quot; | openssl base64 | tr -d '\n')

    local auth_header=&quot;Signature version=\&quot;1\&quot;,keyId=\&quot;${TENANCY_OCID}/${USER_OCID}/${FINGERPRINT}\&quot;,algorithm=\&quot;rsa-sha256\&quot;,headers=\&quot;date (request-target) host content-length content-type x-content-sha256\&quot;,signature=\&quot;${signature}\&quot;&quot;

    echo &quot;$date|$body_sha256|$content_length|$auth_header&quot;
}

# =====================
# 1회 API 호출
# =====================
instance_name=&quot;my-instance-$(date +%s)&quot;

body=&quot;{
  \&quot;availabilityDomain\&quot;: \&quot;${AVAILABILITY_DOMAIN}\&quot;,
  \&quot;compartmentId\&quot;: \&quot;${COMPARTMENT_OCID}\&quot;,
  \&quot;displayName\&quot;: \&quot;${instance_name}\&quot;,
  \&quot;sourceDetails\&quot;: { \&quot;sourceType\&quot;: \&quot;image\&quot;, \&quot;imageId\&quot;: \&quot;${IMAGE_OCID}\&quot; },
  \&quot;shape\&quot;: \&quot;VM.Standard.A1.Flex\&quot;,
  \&quot;shapeConfig\&quot;: { \&quot;ocpus\&quot;: 1, \&quot;memoryInGBs\&quot;: 6 },
  \&quot;createVnicDetails\&quot;: { \&quot;assignPublicIp\&quot;: true, \&quot;subnetId\&quot;: \&quot;${SUBNET_OCID}\&quot; },
  \&quot;metadata\&quot;: { \&quot;ssh_authorized_keys\&quot;: \&quot;${SSH_PUBLIC_KEY}\&quot; }
}&quot;

path=&quot;/20160918/instances&quot;
sig_data=$(sign_request &quot;POST&quot; &quot;$path&quot; &quot;$body&quot;)

date_header=$(echo &quot;$sig_data&quot; | cut -d'|' -f1)
body_sha256=$(echo &quot;$sig_data&quot; | cut -d'|' -f2)
content_length=$(echo &quot;$sig_data&quot; | cut -d'|' -f3)
auth_header=$(echo &quot;$sig_data&quot; | cut -d'|' -f4)

response=$(curl -s -w &quot;\n%{http_code}&quot; -X POST &quot;$ENDPOINT&quot; \
    -H &quot;Date: $date_header&quot; \
    -H &quot;Host: $HOST&quot; \
    -H &quot;Content-Type: application/json&quot; \
    -H &quot;Content-Length: $content_length&quot; \
    -H &quot;x-content-sha256: $body_sha256&quot; \
    -H &quot;Authorization: $auth_header&quot; \
    -d &quot;$body&quot;)

http_code=$(echo &quot;$response&quot; | tail -1)
body_resp=$(echo &quot;$response&quot; | head -n -1)

if [ &quot;$http_code&quot; = &quot;200&quot; ]; then
    echo &quot;[$(date '+%Y-%m-%d %H:%M:%S')] SUCCESS&quot; &amp;gt;&amp;gt; &quot;$LOG_FILE&quot;
    echo &quot;$body_resp&quot; | grep -o '&quot;id&quot;:&quot;[^&quot;]*&quot;' | head -1 &amp;gt;&amp;gt; &quot;$LOG_FILE&quot;

    # Discord 웹훅 알림
    curl -s -X POST &quot;https://discord.com/api/webhooks/YOUR_WEBHOOK_ID/YOUR_WEBHOOK_TOKEN&quot; \
        -H &quot;Content-Type: application/json&quot; \
        -d &quot;{\&quot;content\&quot;: \&quot;✅ OCI 인스턴스 생성 성공! $(date '+%Y-%m-%d %H:%M:%S')\&quot;}&quot;

    # 성공 시 크론 자동 삭제
    crontab -l | grep -v 'create_instance.sh' | crontab -
else
    echo &quot;[$(date '+%Y-%m-%d %H:%M:%S')] HTTP $http_code - $body_resp&quot; &amp;gt;&amp;gt; &quot;$LOG_FILE&quot;
fi&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Discord 알림 설정 (선택)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성공 시 Discord로 알림을 받을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;웹훅 URL 생성:&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Discord &amp;rarr; 서버 만들기 &amp;rarr; 비공개 채널 생성&lt;/li&gt;
&lt;li&gt;채널 편집 &amp;rarr; 연동 &amp;rarr; &lt;b&gt;웹후크 만들기&lt;/b&gt; &amp;rarr; URL 복사&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크립트의 &lt;code&gt;YOUR_WEBHOOK_ID/YOUR_WEBHOOK_TOKEN&lt;/code&gt; 부분을 복사한 URL로 교체하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성공하면 아래처럼 Discord 알림이 도착합니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;✅ OCI 인스턴스 생성 성공! 2026-03-10 09:10:35&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQ2jYs/dJMcabwBwOn/txFlGo3BIiwWGhbbTOH3k1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQ2jYs/dJMcabwBwOn/txFlGo3BIiwWGhbbTOH3k1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQ2jYs/dJMcabwBwOn/txFlGo3BIiwWGhbbTOH3k1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQ2jYs%2FdJMcabwBwOn%2FtxFlGo3BIiwWGhbbTOH3k1%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;956&quot; height=&quot;518&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;518&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;크론 등록&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cron은 초 단위를 지원하지 않으므로, &lt;code&gt;sleep 10&lt;/code&gt;으로 10초 딜레이를 줍니다.&lt;br /&gt;OCI 자원 회수가 정각에 이루어지는 경우가 많아 00초 직후를 노리는 전략입니다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;chmod +x /etc/oci/create_instance.sh
crontab -e&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 내용 추가:&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;* * * * * sleep 10 &amp;amp;&amp;amp; /bin/bash /etc/oci/create_instance.sh&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;동작 확인&lt;/h2&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;# 수동 1회 테스트
sh /etc/oci/create_instance.sh

# 로그 실시간 확인
tail -f /etc/oci/create_instance_log.txt

# 크론 등록 확인
crontab -l&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;699&quot; data-origin-height=&quot;437&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9ajcy/dJMcaibsEgu/PMOKwO862lyqhICzOYNjA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9ajcy/dJMcaibsEgu/PMOKwO862lyqhICzOYNjA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9ajcy/dJMcaibsEgu/PMOKwO862lyqhICzOYNjA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9ajcy%2FdJMcaibsEgu%2FPMOKwO862lyqhICzOYNjA1%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;699&quot; height=&quot;437&quot; data-origin-width=&quot;699&quot; data-origin-height=&quot;437&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;응답 코드 의미:&lt;/b&gt;&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;코드&lt;/th&gt;
&lt;th&gt;의미&lt;/th&gt;
&lt;th&gt;조치&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;200&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;성공 &amp;mdash; 인스턴스 생성됨&lt;/td&gt;
&lt;td&gt;크론 자동 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;429 TooManyRequests&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;요청 횟수 초과&lt;/td&gt;
&lt;td&gt;자동 해소 대기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;500 Out of host capacity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;무료 자원 부족&lt;/td&gt;
&lt;td&gt;용량 생길 때까지 대기&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그에 아래처럼 출력되면 성공입니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[2026-03-09 23:40:35] SUCCESS
&quot;id&quot;:&quot;ocid1.instance.oc1.ap-seoul-1....&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성공과 동시에 크론이 자동 삭제되어 불필요한 API 호출이 중단됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OCI 콘솔에서 수동으로 눌러가며 시도하는 것보다 훨씬 효율적입니다.&lt;br /&gt;스크립트 실행 후 기다리면 용량이 생기는 순간 자동으로 잡힙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;잘 잡히길 기대&lt;/b&gt;해봐야겠습니다..!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;</description>
      <category>IT/Cloud</category>
      <category>crontab</category>
      <category>discord</category>
      <category>freetier</category>
      <category>OCI</category>
      <category>oraclecloud</category>
      <category>리눅스</category>
      <category>무료서버</category>
      <category>쉘스크립트</category>
      <category>자동화</category>
      <category>크론</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/201</guid>
      <comments>https://snapcode.tistory.com/entry/OCI-%EB%AC%B4%EB%A3%8C-%EC%84%9C%EB%B2%84-%EC%9E%A1%EA%B8%B0-API-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%A1%9C-A1Flex-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EC%9E%90%EB%8F%99-%EC%84%A0%EC%A0%90%ED%95%98%EA%B8%B0#entry201comment</comments>
      <pubDate>Mon, 9 Mar 2026 23:56:13 +0900</pubDate>
    </item>
    <item>
      <title>[Kanana-o] 한국 최초 옴니 AI, 직접 써봤습니다. (feat. 부산 사투리)</title>
      <link>https://snapcode.tistory.com/entry/Kanana-o-%ED%95%9C%EA%B5%AD-%EC%B5%9C%EC%B4%88-%EC%98%B4%EB%8B%88-AI-%EC%A7%81%EC%A0%91-%EC%8D%A8%EB%B4%A4%EC%8A%B5%EB%8B%88%EB%8B%A4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;안녕하세요.&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;642&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0cbwa/dJMcagSdL3Q/8FOXOhdgBVk1y2LyqL357k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0cbwa/dJMcagSdL3Q/8FOXOhdgBVk1y2LyqL357k/img.png&quot; data-alt=&quot;출처 https://tech.kakao.com/posts/811&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0cbwa/dJMcagSdL3Q/8FOXOhdgBVk1y2LyqL357k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0cbwa%2FdJMcagSdL3Q%2F8FOXOhdgBVk1y2LyqL357k%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;777&quot; height=&quot;390&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;642&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 https://tech.kakao.com/posts/811&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운이 좋게도&amp;nbsp;&lt;b&gt;Kanana-o Beta Tester&lt;/b&gt; 로 선정되어,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티모달&amp;nbsp;언어&amp;nbsp;&lt;b&gt;모델을&amp;nbsp;직접&amp;nbsp;사용&lt;/b&gt;해보고, 간단한 &lt;b&gt;서비스로&amp;nbsp;구현&lt;/b&gt;해본&amp;nbsp;경험을&amp;nbsp;공유하고자&amp;nbsp;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로&amp;nbsp;사용해보며&amp;nbsp;&lt;b&gt;느낀&amp;nbsp;점&lt;/b&gt;과 함께, 기능&amp;nbsp;&lt;b&gt;개발&amp;nbsp;및&amp;nbsp;테스트&amp;nbsp;과정&lt;/b&gt;도&amp;nbsp;정리해보았습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; 사람처럼 보고, 듣고, 말하는 AI의 탄생&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;722&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVTEgF/dJMcafy1c3v/dclGOnYO4Wj4izSUumIf5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVTEgF/dJMcafy1c3v/dclGOnYO4Wj4izSUumIf5K/img.png&quot; data-alt=&quot;출처 https://omni.kanana.ai/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVTEgF/dJMcafy1c3v/dclGOnYO4Wj4izSUumIf5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVTEgF%2FdJMcafy1c3v%2FdclGOnYO4Wj4izSUumIf5K%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;777&quot; height=&quot;438&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;722&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 https://omni.kanana.ai/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Kanana-o&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카카오가 공개한 '카나나-o(Kanana-o)'는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;국내 최초&lt;/b&gt;로 텍스트, 음성, 이미지를 &lt;b&gt;동시에 이해하고 처리&lt;/b&gt;하는 통합 멀티모달(Multimodal)의 &lt;b&gt;옴니 AI 언어모델&lt;/b&gt;&amp;nbsp;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 홈페이지 : &lt;a href=&quot;https://omni.kanana.ai/&quot;&gt;https://omni.kanana.ai/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772898395291&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;Kanana-o&quot; data-og-description=&quot;더욱 똑똑하게 답하며, 더욱 풍부한 감정표현을 향한 Kanana-o의 진화 과정&quot; data-og-host=&quot;omni.kanana.ai&quot; data-og-source-url=&quot;https://omni.kanana.ai/&quot; data-og-url=&quot;https://omni.kanana.ai&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c8RzcS/dJMb85vMUZ2/MsDfVpMVP7DozE2guzxJ2k/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800,https://scrap.kakaocdn.net/dn/uEtZI/dJMb8TB7z2Q/OwKbLD00PAuSbB30yAK5I0/img.png?width=1030&amp;amp;height=1030&amp;amp;face=0_0_1030_1030,https://scrap.kakaocdn.net/dn/bLKNgi/dJMb84p6HMA/hZ2CrzC1NoOJOtFyCckMx1/img.png?width=1360&amp;amp;height=720&amp;amp;face=0_0_1360_720&quot;&gt;&lt;a href=&quot;https://omni.kanana.ai/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://omni.kanana.ai/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c8RzcS/dJMb85vMUZ2/MsDfVpMVP7DozE2guzxJ2k/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800,https://scrap.kakaocdn.net/dn/uEtZI/dJMb8TB7z2Q/OwKbLD00PAuSbB30yAK5I0/img.png?width=1030&amp;amp;height=1030&amp;amp;face=0_0_1030_1030,https://scrap.kakaocdn.net/dn/bLKNgi/dJMb84p6HMA/hZ2CrzC1NoOJOtFyCckMx1/img.png?width=1360&amp;amp;height=720&amp;amp;face=0_0_1360_720');&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;Kanana-o&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;더욱 똑똑하게 답하며, 더욱 풍부한 감정표현을 향한 Kanana-o의 진화 과정&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;omni.kanana.ai&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Kanana-v(Vision) + &lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Kanana-a(Audio) = &lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Kanana-o&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bedOzo/dJMcabwAp7I/gYrUaZ95sGPxiISB7vmAZ1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bedOzo/dJMcabwAp7I/gYrUaZ95sGPxiISB7vmAZ1/img.jpg&quot; data-alt=&quot;출처 https://tech.kakao.com/posts/802&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bedOzo/dJMcabwAp7I/gYrUaZ95sGPxiISB7vmAZ1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbedOzo%2FdJMcabwAp7I%2FgYrUaZ95sGPxiISB7vmAZ1%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;1280&quot; height=&quot;500&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 https://tech.kakao.com/posts/802&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이미지와 음성을 아우르다.&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 여러 모델을 연결한 구조라기보다는 &lt;b&gt;Kanana-v(Vision)와 Kanana-a(Audio)를 Model Merging&lt;/b&gt; 방식으로 결합해&lt;br /&gt;하나의 LLM backbone 위에서 동작하도록 설계된 통합 멀티모달 모델이라고 소개되고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말이 참 어렵습니다. &lt;b&gt;구조적으로 쉽게 이해해보자&lt;/b&gt;면 아래와 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Audio Encoder (Whisper) // &lt;b&gt;사람의&amp;nbsp;귀&lt;/b&gt;처럼&amp;nbsp;음성을&amp;nbsp;텍스트&amp;nbsp;형태로&amp;nbsp;이해할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;정보로&amp;nbsp;바꿔줌 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;darr; &lt;br /&gt;Audio Projector&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;// 텍스트를 LLM이 이해할 수 있는 형태로 번역기처럼 가공해줌 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;darr;&lt;br /&gt;&amp;nbsp;LLM backbone&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;// &lt;b&gt;사람의&amp;nbsp;뇌&lt;/b&gt;처럼&amp;nbsp;여러&amp;nbsp;데이터&amp;nbsp;보고&amp;nbsp;문맥&amp;nbsp;파악,&amp;nbsp;질문&amp;nbsp;이해,&amp;nbsp;결론&amp;nbsp;도출 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;uarr; &lt;br /&gt;Vision Encoder&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // &lt;b&gt;사람의&amp;nbsp;눈&lt;/b&gt;처럼&amp;nbsp;이미지를&amp;nbsp;읽어들여서 &lt;br /&gt;Vision Projector&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;// 이미지를 LLM이 이해할 수 있는 형태로 번역기처럼 가공해줌 &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;darr; &lt;br /&gt;Voice Token LM&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;// &lt;b&gt;사람의 성대&lt;/b&gt;처럼 목소리나 톤, 속도 등의 발화 스타일을 만들어줌&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;darr; &lt;br /&gt;TTS&amp;nbsp;(Voicebox&amp;nbsp;+&amp;nbsp;Univnet)&amp;nbsp;//&amp;nbsp;&lt;b&gt;사람의&amp;nbsp;입&lt;/b&gt;처럼&amp;nbsp;음성으로&amp;nbsp;말해줌&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;한줄 요약 :말을 하면 &amp;rarr; 귀(Whisper)가 듣고 &amp;rarr; 뇌(LLM)가 생각하고 &amp;rarr; 필요하면 눈(vision) 정보도 같이 보고 &amp;rarr; 합쳐서 이해한 뒤 &amp;rarr; 입(TTS)으로 다시 말해주는 모델&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;당연히 짚고 넘어가야 할&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;음성 처리 방식&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;한국어 음성 특화 모델&lt;/b&gt;인 만큼, Kanana-o의 음성 구조에서 중요한 부분은 &lt;b&gt;말투가 결정되고 만들어지는 단계&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;흥미로운 점은, &lt;b&gt;텍스트 응답을 먼저 생성&lt;/b&gt;한 뒤 -&amp;gt; &lt;b&gt;음성 토큰으로 변환&lt;/b&gt;하는 two-stage 방식이 특징입&lt;/span&gt;&lt;span&gt;니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;음성 &amp;rarr; Whisper &amp;rarr; 임베딩&lt;br /&gt;&amp;darr;&lt;br /&gt;LLM 이해&lt;br /&gt;&amp;darr;&lt;br /&gt;텍스트 응답 생성&lt;br /&gt;&amp;darr;&lt;br /&gt;&lt;b&gt;Voice Token LM&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // 어떤 말투로 말할지 결정하는 곳 (억양, 톤, 속도, 감정 등)&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;darr;&lt;/b&gt;&lt;br /&gt;&lt;b&gt;Token &amp;rarr; Mel spectrogram&amp;nbsp; // 위에서 결정된 성대 진동 패턴을 만드는 단계&lt;/b&gt;&lt;br /&gt;&amp;darr;&lt;br /&gt;Vocoder &amp;rarr; 음성&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt; &lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Kanana-v(Vision)의&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;사진 처리 방식&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;184&quot; data-start=&quot;128&quot; data-ke-size=&quot;size16&quot;&gt;흥미로운 점은, 이미지를 LLM이 이해할 수 있는 형태로 변환할때 LLM에 바로 넣지 않고,&lt;/p&gt;
&lt;p data-end=&quot;184&quot; data-start=&quot;128&quot; data-ke-size=&quot;size16&quot;&gt;Vision Encoder &amp;rarr; Projector &amp;rarr; LLM&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;구조를 거쳐,&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;184&quot; data-start=&quot;128&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이미지 정보를 &lt;b&gt;LLM이 이해할 수 있는 embedding 형태로 변환&lt;/b&gt;하는 방식이 특징입니다. &lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-end=&quot;296&quot; data-start=&quot;186&quot; data-ke-style=&quot;style3&quot;&gt;사진 입력 &lt;br /&gt;&amp;darr;&lt;br /&gt;Vision Encoder (CLIP 기반)&amp;nbsp; &amp;nbsp; &amp;nbsp;// 이미지 특징 추출&lt;br /&gt;&amp;darr;&lt;br /&gt;Image Feature Embedding&amp;nbsp; &amp;nbsp; &amp;nbsp;// 고차원 feature로 표현&lt;br /&gt;&amp;darr;&lt;br /&gt;&lt;b&gt;C-Abstractor (Projector)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // feature를 LLM이 이해할 수 있는 토큰(Visual Token) 형태로 변환&lt;/b&gt;&lt;br /&gt;&amp;darr;&lt;br /&gt;LLM 입력&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;// 이미지 + 텍스트 context 함께 이해&lt;br /&gt;&amp;darr;&lt;br /&gt;텍스트 응답 생성&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;// 사진 설명, 객체 인식, OCR, 차트 해석 등 수행&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;둘러 볼 만한&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;카카오의 &lt;b&gt;차별화 전략&lt;/b&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;1) 멀티모달 데이터셋 직접 구축&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;&lt;b&gt;텍스트 입력에서는 정확&lt;/b&gt;하고 풍부한 설명을 제공하던 모델이, &lt;b&gt;동일한 질문을 오디오로 전달하면 답변 품질이 저하&lt;/b&gt;될 수도 있다고 합 니다. &lt;b&gt;모달리티에 따라 답변 품질이 다르다&lt;/b&gt;는 것.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;카카오는 이를 해결하고자&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;단순한 양의 확보를 넘어, 난이도&amp;middot;도메인 다양성&amp;middot;모달 조합의 균형까지 모두 고려한 &lt;b&gt;구조화된 데이터셋을 직접 설계 구축&lt;/b&gt;함으로써,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #374151; text-align: start;&quot;&gt;모달리티에 무관하게 &lt;b&gt;멀티모달 지시 이행 능력 고도화&lt;/b&gt;를 진행했다고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;텍스트로&lt;/b&gt; &quot;~방법 알려줘&quot; =&amp;gt; 답변 &lt;b&gt;품질 좋음&lt;/b&gt;&lt;br /&gt;VS&lt;br /&gt;&lt;b&gt;음성으로&lt;/b&gt; &quot;~방법 알려줘&quot; =&amp;gt; 답변 &lt;b&gt;품질 떨어짐(모달리티별 성능 차이) =&amp;gt; 데이터셋 확보 =&amp;gt; 멀티모달 지시 이행 능력 개선&lt;/b&gt;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2) 멀티턴 memory 기반 음성 대화&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;일반적인 LLM =&amp;gt; [질문 &amp;rarr; 답변], [질문 &amp;rarr; 답변], &lt;b&gt;[질문 &amp;rarr; 답변] 학습&lt;/b&gt; =&amp;gt; 질문하면 답변하고 끝.&lt;br /&gt;VS&lt;br /&gt;Kanana-o =&amp;gt; 하나의 Sequence로 &lt;b&gt;[대화 전체] 학습 =&amp;gt; 대화 흐름 유지 + context 기억 + tone 유지&lt;/b&gt;.&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3) 감정 음성 (DPO 학습)&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;결론부터 말하면, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;AI가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;감정 없이 읽는 음성이 아니라, &lt;b&gt;사람처럼 감정을 담아서 말하도록 훈련&lt;/b&gt;했다는 겁니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;단순 TTS가 아니라, DPO(Direct Preference Optimization) 방식으로 감정 표현을 학습했다고 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를들면 아래와 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;일반 AI =&amp;gt; &quot;감사합니다&quot; =&amp;gt; 감정이 없음&lt;br /&gt;VS&lt;br /&gt;Kanana-o =&amp;gt; &lt;b&gt;&quot;감사합니다;;&quot; =&amp;gt; 딱딱 당황&lt;/b&gt; =&amp;gt; 이건 별로라는걸 AI에게 &lt;b&gt;계속 가르침 =&amp;gt; 부정 톤 학습&lt;br /&gt;&lt;/b&gt;Kanana-o =&amp;gt; &lt;b&gt;&quot;감사합니다.&quot; =&amp;gt; 정중 미지근&lt;/b&gt; =&amp;gt; 무미건조한걸 AI에게 &lt;b&gt;계속 가르침 =&amp;gt; 평범 톤 학습&lt;/b&gt;&lt;br /&gt;Kanana-o =&amp;gt; &lt;b&gt;&quot;감사합니다~!&quot; =&amp;gt; 밝고 긍정적&lt;/b&gt; =&amp;gt; 이게 더 좋다는걸 AI에게 &lt;b&gt;계속 가르침 =&amp;gt; 긍정 톤 학습&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-size=&quot;size16&quot; data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;앞으로 기대해도 좋은&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;미래 성장 로드맵&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kanana-o 모델의 &lt;b&gt;성장은 멈추지 않습니다.&lt;/b&gt; 카카오의 다음 목표는 아래와 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. Full-duplex 대화 =&amp;gt;서로 &lt;b&gt;쉼 없이 동시에 말할 수 있고&lt;/b&gt;, 남의 말도 끊을 수 있고, 정말 사람처럼.&lt;br /&gt;2. On-device 경량화 =&amp;gt; 클라우드 의존 없이, &lt;b&gt;기기 안에서 바로 실행&lt;/b&gt;&lt;br /&gt;3. 음성 외 모든 오디오 생성 =&amp;gt; 박수소리, 게임 사운드, &lt;b&gt;고기 굽는 소리 등&lt;/b&gt; 다른 소리도 만들자.&lt;br /&gt;4. Human preference alignment =&amp;gt; AI를 &lt;b&gt;취향에 맞게 조정&lt;/b&gt;하는 것&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;주요 기능&lt;/b&gt;&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;기능&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Image Captioning&lt;/td&gt;
&lt;td&gt;이미지 설명 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image-based Instruction Following&lt;/td&gt;
&lt;td&gt;이미지 + 텍스트/음성 기반 지시 수행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OCR-based Reasoning&lt;/td&gt;
&lt;td&gt;이미지 내 텍스트 인식 및 추론&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ASR (자동 음성 인식)&lt;/td&gt;
&lt;td&gt;한국어/영어 음성 &amp;rarr; 텍스트 변환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speech Translation&lt;/td&gt;
&lt;td&gt;음성 &amp;rarr; 다국어 번역&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text-to-Speech&lt;/td&gt;
&lt;td&gt;텍스트 &amp;rarr; 자연스러운 음성 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Style-instructed TTS&lt;/td&gt;
&lt;td&gt;스타일 지정 음성 합성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Podcast-style Speech&lt;/td&gt;
&lt;td&gt;이미지/텍스트 &amp;rarr; 팟캐스트 형식 음성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiturn Conversation&lt;/td&gt;
&lt;td&gt;멀티턴 음성/텍스트 대화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Omnimodal QA&lt;/td&gt;
&lt;td&gt;텍스트 + 이미지 + 음성 통합 질의응답&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 링크 : &lt;a href=&quot;https://huggingface.co/kakaocorp/Kanana-1.5-o-9.8B-instruct-2602-API_Doc&quot;&gt;https://huggingface.co/kakaocorp/Kanana-1.5-o-9.8B-instruct-2602-API_Doc&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772899099443&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;kakaocorp/Kanana-1.5-o-9.8B-instruct-2602-API_Doc &amp;middot; Hugging Face&quot; data-og-description=&quot;We&amp;rsquo;re on a journey to advance and democratize artificial intelligence through open source and open science.&quot; data-og-host=&quot;huggingface.co&quot; data-og-source-url=&quot;https://huggingface.co/kakaocorp/Kanana-1.5-o-9.8B-instruct-2602-API_Doc&quot; data-og-url=&quot;https://huggingface.co/kakaocorp/Kanana-1.5-o-9.8B-instruct-2602-API_Doc&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/sca7b/dJMb8UHM9lx/9KHVGrIxrZ8D6STiHW646k/img.png?width=1200&amp;amp;height=648&amp;amp;face=0_0_1200_648,https://scrap.kakaocdn.net/dn/qyXNV/dJMb8QL9XpX/EbesknpyMPfMDWkIXNWkx1/img.png?width=1200&amp;amp;height=648&amp;amp;face=0_0_1200_648,https://scrap.kakaocdn.net/dn/umYqA/dJMb8SpFWji/mCSjnkaktGZhawkbk2S2K0/img.jpg?width=3024&amp;amp;height=4032&amp;amp;face=0_0_3024_4032&quot;&gt;&lt;a href=&quot;https://huggingface.co/kakaocorp/Kanana-1.5-o-9.8B-instruct-2602-API_Doc&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://huggingface.co/kakaocorp/Kanana-1.5-o-9.8B-instruct-2602-API_Doc&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/sca7b/dJMb8UHM9lx/9KHVGrIxrZ8D6STiHW646k/img.png?width=1200&amp;amp;height=648&amp;amp;face=0_0_1200_648,https://scrap.kakaocdn.net/dn/qyXNV/dJMb8QL9XpX/EbesknpyMPfMDWkIXNWkx1/img.png?width=1200&amp;amp;height=648&amp;amp;face=0_0_1200_648,https://scrap.kakaocdn.net/dn/umYqA/dJMb8SpFWji/mCSjnkaktGZhawkbk2S2K0/img.jpg?width=3024&amp;amp;height=4032&amp;amp;face=0_0_3024_4032');&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;kakaocorp/Kanana-1.5-o-9.8B-instruct-2602-API_Doc &amp;middot; Hugging Face&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;We&amp;rsquo;re on a journey to advance and democratize artificial intelligence through open source and open science.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;huggingface.co&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; 여기까지가&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kanana-o 모델에 대한 리뷰였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 기능 구현 및 Beta 테스트 내용입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;구현 아이디어: AI 커리어 상담사 '커나나'&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번 &lt;b&gt;PlayMCP 등록&lt;/b&gt; 이후,&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사람과 얼마나 비슷할까?&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &lt;b&gt;AI 채용 상담사 '커나나'&lt;/b&gt; 를 만들어보았습니다. (&lt;i&gt;&lt;b&gt;커&lt;/b&gt;리어 + 카&lt;b&gt;나나&lt;/b&gt;&lt;/i&gt; 의 합성어)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;346&quot; data-origin-height=&quot;524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HUVId/dJMcacPOvYX/pzZY0V5b6UBC80z97aBFCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HUVId/dJMcacPOvYX/pzZY0V5b6UBC80z97aBFCK/img.png&quot; data-alt=&quot;썸네일 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HUVId/dJMcacPOvYX/pzZY0V5b6UBC80z97aBFCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHUVId%2FdJMcacPOvYX%2FpzZY0V5b6UBC80z97aBFCK%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;346&quot; height=&quot;524&quot; data-origin-width=&quot;346&quot; data-origin-height=&quot;524&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서비스에 배포&lt;/b&gt;된, Kanana-o 연동 기반 기능들은 아래 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;명함 &amp;middot; 포트폴리오 이미지를 첨부해 &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;(Speaker A / B) 선택 기능&lt;/li&gt;
&lt;li&gt;모의면접 수준의 자연스러운 &lt;b&gt;멀티턴 대화&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개발 내용&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(1) WebSocket 실시간 스트리밍 + 멀티턴 히스토리 관리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;WebSocket&lt;/b&gt; 기반으로 채팅을 구성했습니다.&lt;br /&gt;&lt;b&gt;스트리밍 응답을 청크 단위로 실시간 전달&lt;/b&gt;하기 위해 WebSocket 메시지 버퍼 크기도 별도로 증설했습니다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;// WebSocket 설정 (샘플 코드)
@Configuration
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(chatHandler, &quot;/ws/chat&quot;)
                .setAllowedOrigins(&quot;*&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대화 히스토리는 서버에서 세션 단위로 관리해, &lt;b&gt;이전 맥락을 유지한 채 연속 질문&lt;/b&gt;이 가능합니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 멀티턴 히스토리 관리 (샘플 코드)
List&amp;lt;Message&amp;gt; history = sessionHistory.getOrDefault(sessionId, new ArrayList&amp;lt;&amp;gt;());
history.add(new Message(&quot;user&quot;, userInput));
history.add(new Message(&quot;assistant&quot;, assistantReply));
sessionHistory.put(sessionId, history);&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(2) 이미지 분석 + SmartKeywordExtractor (멀티모달 활용)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kanana-o의 &lt;b&gt;멀티모달 기능을 활용&lt;/b&gt;해 명함이나 포트폴리오 이미지를 업로드하면,&lt;br /&gt;이미지에서 직무 키워드를 추출해 자동으로 채용공고를 검색하는 흐름을 구현했습니다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;[명함 이미지 업로드]
        &amp;darr;
[Kanana-o 이미지 분석 &amp;rarr; 직무/기술 키워드 추출]
        &amp;darr;
[SmartKeywordExtractor &amp;rarr; 검색 쿼리 정규화]
        &amp;darr;
[관련 채용공고 목록 인라인 표시]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지는 &lt;b&gt;Base64로 인코딩&lt;/b&gt;해 API에 전달했으며, data URL 포맷도 별도로 처리했습니다.&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;// 이미지 멀티모달 요청 (샘플 코드)
List&amp;lt;ContentPart&amp;gt; content = List.of(
    new ImageUrlPart(base64DataUrl),
    new TextPart(KEYWORD_EXTRACT_PROMPT)
);
String response = kananaClient.chat(content);
List&amp;lt;String&amp;gt; keywords = smartKeywordExtractor.parse(response);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;SmartKeywordExtractor&lt;/code&gt;는 AI 응답에서 직무명, 기술스택, 경력 수준 등을 파싱해 데이터 검색 키워드를 정규화하는 역할을 담당합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(3) 음성 파이프라인 구축&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에서 수집한 PCM 오디오를 Kanana-o에 전달하기 전에 전처리가 필요했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불필요한&amp;nbsp;고주파&amp;nbsp;대역을&amp;nbsp;제거하고, 데이터 전송량을 줄여, &lt;b&gt;실시간 음성 처리 효율 개선&lt;/b&gt;을 위해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;WAV 변환 후&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;16kHz로 리샘플링하여 전송&lt;/b&gt;하도록 처리했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- iPhone / iOS Safari 기본 샘플레이트 : 44.1kHz (44100 Hz)&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// WAV 변환 + 리샘플링 (샘플 코드)
byte[] resampled = AudioResampler.resample(rawPcm, originalRate, TARGET_RATE_16K);
byte[] wavBytes  = WavConverter.toWav(resampled, TARGET_RATE_16K, CHANNELS, SAMPLE_WIDTH);
String b64Audio  = Base64.getEncoder().encodeToString(wavBytes);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답 음성은 스트리밍으로 수신해 &lt;b&gt;청크 단위로 저장한 뒤 병합하여 재생&lt;/b&gt;합니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;# 음성 청크 수집 및 병합 (샘플 코드)
audio_chunks = []

for chunk in stream_response:
    audio_data = extract_audio(chunk)
    if audio_data:
        save_wav_chunk(audio_data, chunk_index)
        audio_chunks.append(audio_data)

merge_wav_chunks(audio_chunks, output_path=&quot;result/output.wav&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(4) 음성 UX &amp;mdash; 아이들 타임아웃 &amp;amp; 녹음 충돌 방지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음성 대화 모드에서 UX를 다듬는 데 &lt;b&gt;생각보다 많은 공&lt;/b&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;Voice Idle Timeout&lt;/b&gt;: 일정 시간 침묵 시 자동으로 녹음 종료 처리&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이중 API Key&lt;/b&gt;: 쿼터 제한 대응을 위해 두 개의 API 키를 번갈아 사용&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;pre class=&quot;arcade&quot;&gt;&lt;code&gt;// 아이들 타임아웃 처리 (샘플 코드)
function startIdleTimer() {
    idleTimer = setTimeout(() =&amp;gt; {
        if (isRecording) stopRecording();
    }, IDLE_TIMEOUT_MS);
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(5) 사투리 페르소나 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 프롬프트에 방언 가이드를 명시해 &lt;b&gt;서울 표준어 &amp;middot; 부산 사투리 두 가지 페르소나&lt;/b&gt;를 구현했습니다.&lt;br /&gt;별도로 TTS 화자(A / B)는 &lt;b&gt;목소리 톤 선택 제공&lt;/b&gt;되며, 방언과 화자는 독립적으로 조합할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인사말, 에러 메시지, 안내 문구 등도 방언별로 분기 처리해 전체적인 일관성을 유지했습니다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;// 사투리별 인사말 분기 (샘플 코드)
const GREETINGS = {
    seoul: &quot;안녕하세요! 무엇을 도와드릴까요?&quot;,
    busan: &quot;어서오이소~ 뭐 도와드릴까예?&quot;
};&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# 사투리 페르소나 설정 예시 (샘플 코드)
response = client.chat.completions.create(
    model=&quot;kanana-o&quot;,
    messages=[
        {&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: DIALECT_SYSTEM_PROMPT},
        {&quot;role&quot;: &quot;user&quot;,   &quot;content&quot;: user_message}
    ],
    modalities=[&quot;text&quot;, &quot;audio&quot;],
    audio={&quot;voice&quot;: &quot;preset_spk_2&quot;},  # 화자 B
    stream=True,
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;테스터 분들과 공유하고 싶은&lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;꿀팁&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(1) 빠른 시작 &amp;mdash; OpenAI SDK 그대로 재활용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kanana-o는 OpenAI SDK 호환 인터페이스를 제공합니다.&lt;br /&gt;&lt;b&gt;&lt;code&gt;base_url&lt;/code&gt;과 &lt;code&gt;model&lt;/code&gt;만 바꾸면&lt;/b&gt; 기존 코드를 그대로 쓸 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;client = OpenAI(
    base_url=&quot;https://kanana-o-endpoint.example.com/v1&quot;,
    api_key=&quot;&amp;lt;KANANA_API_KEY&amp;gt;&quot;
)
response = client.chat.completions.create(
    model=&quot;kanana-o&quot;,
    messages=[{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;안녕하세요!&quot;}]
)&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(2) 멀티모달 출력 형식 강제 프롬프트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지를 함께 전달하면 모델이 지정한 출력 형식을 무시하는 경우가 있습니다.&lt;br /&gt;&lt;b&gt;FORMAT 태그 + Few-shot 예시&lt;/b&gt;를 함께 쓰면 준수율이 높아집니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;이미지를 분석하고, 반드시 아래 [FORMAT] 안의 형식으로만 답하세요.
다른 텍스트는 절대 포함하지 마세요.

[FORMAT]
KEYWORD: &amp;lt;키워드1&amp;gt;, &amp;lt;키워드2&amp;gt;
LEVEL: &amp;lt;junior|mid|senior&amp;gt;
[/FORMAT]

--- 예시 ---
입력: (백엔드 개발자 명함 이미지)
출력:
[FORMAT]
KEYWORD: Java, Spring Boot, AWS
LEVEL: mid
[/FORMAT]&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 다른 테스터분들의 프롬프트 전략도 궁금한 포인트입니다. 좋은 방법이 있다면 공유해 주세요!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(3) 사투리 시스템 프롬프트 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TTS 화자 프리셋뿐만 아니라, 시스템 프롬프트에 &lt;b&gt;방언 규칙과 예시를 구체적으로 명시&lt;/b&gt;하면 효과가 더 좋습니다.&lt;/p&gt;
&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;당신은 부산 출신 AI 상담사입니다.
반드시 부산 사투리를 사용해 답변하세요.

[방언 규칙]
- ~습니다 &amp;rarr; ~습니더
- ~이에요 &amp;rarr; ~이라예
- ~인가요? &amp;rarr; ~인교?

[예시]
표준어: 무엇을 도와드릴까요?
부산어: 뭐 도와드릴까예?

표준어: 잠시만요.
부산어: 잠깐만예.&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(4) 쿼터 소진 방지 테스트 팁&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; &amp;mdash; 음성 출력은 쿼터 소모가 빠를것으로 예상되어, 로직은 텍스트 모드로 먼저 검증&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모달리티별 단독 우선 테스트&lt;/b&gt; &amp;mdash; 이미지 / 음성 조합 전에 각각 단독으로 먼저 테스트&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(5) 앞으로 해보고 싶은 것들&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 테스트해보지 못한 기능이 많습니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;기능&lt;/th&gt;
&lt;th&gt;활용 아이디어&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Podcast-style Speech&lt;/td&gt;
&lt;td&gt;채용 뉴스나 JD를 AI가 팟캐스트 형식으로 읽어주는 기능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;감정 표현력 TTS&lt;/td&gt;
&lt;td&gt;면접 상황에 따라 긴장감 있는 / 친근한 톤 전환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audio Multiturn&lt;/td&gt;
&lt;td&gt;음성만으로 연속 모의면접 진행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image-to-Podcast&lt;/td&gt;
&lt;td&gt;포트폴리오 이미지를 자동으로 음성 자기소개로 변환&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;시연 Shorts&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스의 사용자 경험&lt;b&gt; 시나리오&lt;/b&gt;는 아래와 같습니다.&lt;/p&gt;
&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; 모드 활성화 후 인사&lt;/li&gt;
&lt;li&gt;커나나가 &lt;b&gt;부산 사투리&lt;/b&gt;로 반갑게 응답&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&quot; 부산 사투리까지 하는 멀티모달 AI 등장... &quot;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;※ &lt;span style=&quot;color: #ee2323;&quot;&gt;20초부터&lt;/span&gt; 사투리 음성을 들으실 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/u-mUY1Mn_aI&quot; width=&quot;463&quot; height=&quot;823&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span&gt;&lt;b&gt;Kanana-o의 사투리를 실제로 들어보니 어떠셨나요?&lt;/b&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사투리를 사용하지 않는 저로서는 이 발음이 정확한 사투리인지, 흉내 낸 발음인지 판단하긴 어려웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답 텍스트에는 &quot;듣겠습니&lt;b&gt;더&quot;&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;로 표시되어 있었지만, 음성에서는 &quot;듣겠습니&lt;/span&gt;&lt;b&gt;다&quot;&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;에 가깝게 발음된 것처럼 들리기도 했습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 전반적인&amp;nbsp;&lt;b&gt;한국어 발화 자체는 매우 자연스럽게&lt;/b&gt; 느껴졌습니다.&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;서울 사람인 제 기준에서는 &lt;b&gt;사투리 표현도 충분히 신기하고 인상적&lt;/b&gt;으로 들렸습니다.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;(솔직히 저보다 커나나가 사투리를 더 잘하는 것 같습니다..)&lt;/del&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;테스트 한계&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 멀티모달 요청에서 규칙 미준수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명함 이미지와 텍스트를 함께 입력했을 때 Tool Call처럼 활용하기 위해 특정 출력 형식을 명시했지만,&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프롬프트 설계를 조금 더 정교하게 다듬어야 할 부분도 있겠지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성형 모델 특성상 출력 형식을 완전히 강제하기는 쉽지 않은 부분이 아닐까 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;향후 모델 측면에서도 점차 개선되지 않을까 기대하고 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 일일 쿼터 제한&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 1일 횟수 제한이 있으며, 잔여 쿼터 조회 기능은 아직 지원되지 않아,&lt;br /&gt;개발 사이클 상 더딘 부분이 있지만, 베타 기간인 만큼 이해는 하고 있습니다.&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;1098&quot; data-origin-height=&quot;519&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PSmOk/dJMcafy1c4o/yxKckcqZm4GjPw7M99wB2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PSmOk/dJMcafy1c4o/yxKckcqZm4GjPw7M99wB2K/img.png&quot; data-alt=&quot;출처 https://api-omni.kanana.ai/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PSmOk/dJMcafy1c4o/yxKckcqZm4GjPw7M99wB2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPSmOk%2FdJMcafy1c4o%2FyxKckcqZm4GjPw7M99wB2K%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;666&quot; height=&quot;315&quot; data-origin-width=&quot;1098&quot; data-origin-height=&quot;519&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 https://api-omni.kanana.ai/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;앞으로의 목표&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;명함 &amp;middot; 포트폴리오 이미지 기반&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;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다중 화자&lt;/b&gt; 대화 TTS 심층 테스트&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;919&quot; data-origin-height=&quot;711&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6fFfD/dJMcadul0WN/XjHEJ3uOyHlAYo1Qi3aAnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6fFfD/dJMcadul0WN/XjHEJ3uOyHlAYo1Qi3aAnk/img.png&quot; data-alt=&quot;출처 https://omni.kanana.ai/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6fFfD/dJMcadul0WN/XjHEJ3uOyHlAYo1Qi3aAnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6fFfD%2FdJMcadul0WN%2FXjHEJ3uOyHlAYo1Qi3aAnk%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;555&quot; height=&quot;429&quot; data-origin-width=&quot;919&quot; data-origin-height=&quot;711&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 https://omni.kanana.ai/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 이런 기회를 주신 카카오 관계자분들께 감사드립니다.&lt;br /&gt;앞으로 모델 성능이 개선될수록 &lt;b&gt;더 많은 가능성이 열릴 것 같아 기대&lt;/b&gt;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;맺음말&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 테스터분들은 &lt;b&gt;어떻게 Kanana-o를 활용하고 계신가요?&lt;/b&gt;&lt;br /&gt;디스코드 혹은 댓글로 편하게 공유해 주세요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고문헌&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kanana-o API 명세서, &lt;a href=&quot;https://huggingface.co/kakaocorp/Kanana-1.5-o-9.8B-instruct-2602-API_Doc&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://huggingface.co/kakaocorp/Kanana-1.5-o-9.8B-instruct-2602-API_Doc&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772902086015&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;kakaocorp/Kanana-1.5-o-9.8B-instruct-2602-API_Doc &amp;middot; Hugging Face&quot; data-og-description=&quot;We&amp;rsquo;re on a journey to advance and democratize artificial intelligence through open source and open science.&quot; data-og-host=&quot;huggingface.co&quot; data-og-source-url=&quot;https://huggingface.co/kakaocorp/Kanana-1.5-o-9.8B-instruct-2602-API_Doc&quot; data-og-url=&quot;https://huggingface.co/kakaocorp/Kanana-1.5-o-9.8B-instruct-2602-API_Doc&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/sca7b/dJMb8UHM9lx/9KHVGrIxrZ8D6STiHW646k/img.png?width=1200&amp;amp;height=648&amp;amp;face=0_0_1200_648,https://scrap.kakaocdn.net/dn/qyXNV/dJMb8QL9XpX/EbesknpyMPfMDWkIXNWkx1/img.png?width=1200&amp;amp;height=648&amp;amp;face=0_0_1200_648,https://scrap.kakaocdn.net/dn/umYqA/dJMb8SpFWji/mCSjnkaktGZhawkbk2S2K0/img.jpg?width=3024&amp;amp;height=4032&amp;amp;face=0_0_3024_4032&quot;&gt;&lt;a href=&quot;https://huggingface.co/kakaocorp/Kanana-1.5-o-9.8B-instruct-2602-API_Doc&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://huggingface.co/kakaocorp/Kanana-1.5-o-9.8B-instruct-2602-API_Doc&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/sca7b/dJMb8UHM9lx/9KHVGrIxrZ8D6STiHW646k/img.png?width=1200&amp;amp;height=648&amp;amp;face=0_0_1200_648,https://scrap.kakaocdn.net/dn/qyXNV/dJMb8QL9XpX/EbesknpyMPfMDWkIXNWkx1/img.png?width=1200&amp;amp;height=648&amp;amp;face=0_0_1200_648,https://scrap.kakaocdn.net/dn/umYqA/dJMb8SpFWji/mCSjnkaktGZhawkbk2S2K0/img.jpg?width=3024&amp;amp;height=4032&amp;amp;face=0_0_3024_4032');&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;kakaocorp/Kanana-1.5-o-9.8B-instruct-2602-API_Doc &amp;middot; Hugging Face&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;We&amp;rsquo;re on a journey to advance and democratize artificial intelligence through open source and open science.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;huggingface.co&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kanana-o 모델 이해를 위한, edwin.ai / james.e 님의 &lt;a href=&quot;https://tech.kakao.com/posts/702&quot;&gt;이미지와 음성을 아우르는 카카오의 멀티모달 언어모델 Kanana-o 알아보기&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772899020997&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;이미지와 음성을 아우르는 카카오의 멀티모달 언어모델 Kanana-o 알아보기 - tech.kakao.com&quot; data-og-description=&quot;안녕하세요, 카카오의 AI 모델 개발을 담당하는 카나나(Kanana) 조직의 Ed...&quot; data-og-host=&quot;tech.kakao.com&quot; data-og-source-url=&quot;https://tech.kakao.com/posts/702&quot; data-og-url=&quot;https://tech.kakao.com/posts/702&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qvwLj/dJMb9iIE5Xh/t0kbi9JCFEZFXKMtdCP4H1/img.png?width=896&amp;amp;height=506&amp;amp;face=0_0_896_506,https://scrap.kakaocdn.net/dn/cT77cm/dJMb9lL9xgJ/ifZQKWHFIDo4Mst4IlKESk/img.png?width=896&amp;amp;height=506&amp;amp;face=0_0_896_506,https://scrap.kakaocdn.net/dn/bwwNAw/dJMb9dHl2K8/9IibbfPqIS49t6bpguArGK/img.png?width=896&amp;amp;height=506&amp;amp;face=0_0_896_506&quot;&gt;&lt;a href=&quot;https://tech.kakao.com/posts/702&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tech.kakao.com/posts/702&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qvwLj/dJMb9iIE5Xh/t0kbi9JCFEZFXKMtdCP4H1/img.png?width=896&amp;amp;height=506&amp;amp;face=0_0_896_506,https://scrap.kakaocdn.net/dn/cT77cm/dJMb9lL9xgJ/ifZQKWHFIDo4Mst4IlKESk/img.png?width=896&amp;amp;height=506&amp;amp;face=0_0_896_506,https://scrap.kakaocdn.net/dn/bwwNAw/dJMb9dHl2K8/9IibbfPqIS49t6bpguArGK/img.png?width=896&amp;amp;height=506&amp;amp;face=0_0_896_506');&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;이미지와 음성을 아우르는 카카오의 멀티모달 언어모델 Kanana-o 알아보기 - tech.kakao.com&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요, 카카오의 AI 모델 개발을 담당하는 카나나(Kanana) 조직의 Ed...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;tech.kakao.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티모달 지시 이행 능력과 멀티턴 대화 이해를 위한, edwin.ai / james.e / jessie.e 님의, &lt;a href=&quot;https://tech.kakao.com/posts/802&quot;&gt;더욱 똑똑하게 답하며, 더욱 풍부한 감정표현을 향한 Kanana-o의 진화 과정&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772899024697&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;더욱 똑똑하게 답하며, 더욱 풍부한 감정표현을 향한 Kanana-o의 진화 과정 - tech.kakao.com&quot; data-og-description=&quot;안녕하세요, 카카오의 AI 모델 개발을 담당하는 카나나(Kanana) 조직의 Ed...&quot; data-og-host=&quot;tech.kakao.com&quot; data-og-source-url=&quot;https://tech.kakao.com/posts/802&quot; data-og-url=&quot;https://tech.kakao.com/posts/802&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bctKhC/dJMb86nVwNE/j4LvWmZ0UNtoVtkqtEcxek/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504,https://scrap.kakaocdn.net/dn/bgM72Y/dJMb83kqUHw/tVKoOts9IiXW4SzAEUinbk/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504,https://scrap.kakaocdn.net/dn/b1qyCv/dJMb8866PZj/kOXZoDRYuHkpYPf0NNfTM1/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504&quot;&gt;&lt;a href=&quot;https://tech.kakao.com/posts/802&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tech.kakao.com/posts/802&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bctKhC/dJMb86nVwNE/j4LvWmZ0UNtoVtkqtEcxek/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504,https://scrap.kakaocdn.net/dn/bgM72Y/dJMb83kqUHw/tVKoOts9IiXW4SzAEUinbk/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504,https://scrap.kakaocdn.net/dn/b1qyCv/dJMb8866PZj/kOXZoDRYuHkpYPf0NNfTM1/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504');&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;더욱 똑똑하게 답하며, 더욱 풍부한 감정표현을 향한 Kanana-o의 진화 과정 - tech.kakao.com&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요, 카카오의 AI 모델 개발을 담당하는 카나나(Kanana) 조직의 Ed...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;tech.kakao.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 처리 방식 이해를 위한, logan.c 님의, &lt;a href=&quot;https://tech.kakao.com/posts/667&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이미지도&amp;nbsp;찰떡같이&amp;nbsp;이해하는&amp;nbsp;카카오의&amp;nbsp;멀티모달&amp;nbsp;언어모델&amp;nbsp;Kanana-v&amp;nbsp;알아보기&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772912196792&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;이미지도 찰떡같이 이해하는 카카오의 멀티모달 언어모델 Kanana-v 알아보기 - tech.kakao.com&quot; data-og-description=&quot;안녕하세요, 카카오의 AI 모델 개발을 담당하는 카나나 알파(Kanana ⍺) 조...&quot; data-og-host=&quot;tech.kakao.com&quot; data-og-source-url=&quot;https://tech.kakao.com/posts/667&quot; data-og-url=&quot;https://tech.kakao.com/posts/667&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/3v9BH/dJMb82eK2zO/LvhHoj8LWtSRlrwkWbuIp1/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504,https://scrap.kakaocdn.net/dn/cNSKZN/dJMb83SgOzf/6aQfXT9YIaoDve7N5x1rKk/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504,https://scrap.kakaocdn.net/dn/KyhWw/dJMb86nVxeM/TuxbOXGIAoSjWYM6LRQQKK/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504&quot;&gt;&lt;a href=&quot;https://tech.kakao.com/posts/667&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tech.kakao.com/posts/667&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/3v9BH/dJMb82eK2zO/LvhHoj8LWtSRlrwkWbuIp1/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504,https://scrap.kakaocdn.net/dn/cNSKZN/dJMb83SgOzf/6aQfXT9YIaoDve7N5x1rKk/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504,https://scrap.kakaocdn.net/dn/KyhWw/dJMb86nVxeM/TuxbOXGIAoSjWYM6LRQQKK/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504');&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;이미지도 찰떡같이 이해하는 카카오의 멀티모달 언어모델 Kanana-v 알아보기 - tech.kakao.com&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요, 카카오의 AI 모델 개발을 담당하는 카나나 알파(Kanana ⍺) 조...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;tech.kakao.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 모델 라인업, &lt;a href=&quot;https://tech.kakao.com/posts/660&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;카카오의&amp;nbsp;AI&amp;nbsp;모델,&amp;nbsp;카나나&amp;nbsp;모델&amp;nbsp;패밀리를&amp;nbsp;소개합니다&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772912331158&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;카카오의 AI 모델, 카나나 모델 패밀리를 소개합니다 - tech.kakao.com&quot; data-og-description=&quot;지난 if(kakaoAI)2024에서는 카카오의 독자적인 AI 모델 라인업, Ka...&quot; data-og-host=&quot;tech.kakao.com&quot; data-og-source-url=&quot;https://tech.kakao.com/posts/660&quot; data-og-url=&quot;https://tech.kakao.com/posts/660&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/mIKTx/dJMb8UHM9Tc/yKhQvKrSsAWoKUBuWBnAy1/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504,https://scrap.kakaocdn.net/dn/cW5r5s/dJMb8TB7ADp/gkcfaK8kPA4uSBSVezqa40/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504,https://scrap.kakaocdn.net/dn/Hi8bK/dJMb8TB7ADo/OO9ATujDDqEuYcCfaORkZ0/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504&quot;&gt;&lt;a href=&quot;https://tech.kakao.com/posts/660&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tech.kakao.com/posts/660&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/mIKTx/dJMb8UHM9Tc/yKhQvKrSsAWoKUBuWBnAy1/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504,https://scrap.kakaocdn.net/dn/cW5r5s/dJMb8TB7ADp/gkcfaK8kPA4uSBSVezqa40/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504,https://scrap.kakaocdn.net/dn/Hi8bK/dJMb8TB7ADo/OO9ATujDDqEuYcCfaORkZ0/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504');&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;카카오의 AI 모델, 카나나 모델 패밀리를 소개합니다 - tech.kakao.com&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;지난 if(kakaoAI)2024에서는 카카오의 독자적인 AI 모델 라인업, Ka...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;tech.kakao.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI 기반 아이디어 발굴 과정에서, hunter.jo 님의 &lt;a href=&quot;https://tech.kakao.com/posts/782&quot;&gt;카카오 x 한국정보과학회 AI 에이전트 경진대회&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772899027962&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;(FAQ) 카카오 x 한국정보과학회 AI 에이전트 경진대회 - tech.kakao.com&quot; data-og-description=&quot;본 경진대회에 관심을 가져주신 모든 분께 감사드립니다. 참가자들이 AI 에이전트 ...&quot; data-og-host=&quot;tech.kakao.com&quot; data-og-source-url=&quot;https://tech.kakao.com/posts/782&quot; data-og-url=&quot;https://tech.kakao.com/posts/782&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bJnZoN/dJMb8QL9XD9/oPnLmCwk80WBXKa2pVKTH1/img.png?width=896&amp;amp;height=504&amp;amp;face=547_137_815_200,https://scrap.kakaocdn.net/dn/btRy2I/dJMb8YpTqh0/C8oLkqv1MxvVeGLQSU8hxK/img.png?width=896&amp;amp;height=504&amp;amp;face=547_137_815_200,https://scrap.kakaocdn.net/dn/cCPdgj/dJMb8ZvzrTn/vwhYBjm33SZf10j7wBvUhk/img.png?width=896&amp;amp;height=504&amp;amp;face=547_137_815_200&quot;&gt;&lt;a href=&quot;https://tech.kakao.com/posts/782&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tech.kakao.com/posts/782&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bJnZoN/dJMb8QL9XD9/oPnLmCwk80WBXKa2pVKTH1/img.png?width=896&amp;amp;height=504&amp;amp;face=547_137_815_200,https://scrap.kakaocdn.net/dn/btRy2I/dJMb8YpTqh0/C8oLkqv1MxvVeGLQSU8hxK/img.png?width=896&amp;amp;height=504&amp;amp;face=547_137_815_200,https://scrap.kakaocdn.net/dn/cCPdgj/dJMb8ZvzrTn/vwhYBjm33SZf10j7wBvUhk/img.png?width=896&amp;amp;height=504&amp;amp;face=547_137_815_200');&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;(FAQ) 카카오 x 한국정보과학회 AI 에이전트 경진대회 - tech.kakao.com&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;본 경진대회에 관심을 가져주신 모든 분께 감사드립니다. 참가자들이 AI 에이전트 ...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;tech.kakao.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 컨텐츠 내용상 문제의 소지가 있는 경우 말씀주시면 개정하도록 하겠습니다. 감사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/AI</category>
      <category>kakao</category>
      <category>Kanana</category>
      <category>Kanana-o</category>
      <category>멀티모달</category>
      <category>베타테스터</category>
      <category>사투리</category>
      <category>생성형AI</category>
      <category>옴니AI</category>
      <category>옴니모달</category>
      <category>카카오</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/200</guid>
      <comments>https://snapcode.tistory.com/entry/Kanana-o-%ED%95%9C%EA%B5%AD-%EC%B5%9C%EC%B4%88-%EC%98%B4%EB%8B%88-AI-%EC%A7%81%EC%A0%91-%EC%8D%A8%EB%B4%A4%EC%8A%B5%EB%8B%88%EB%8B%A4#entry200comment</comments>
      <pubDate>Sat, 7 Mar 2026 23:27:04 +0900</pubDate>
    </item>
    <item>
      <title>[Kanana-o] 베타 테스터로 선정되신 것을 축하드립니다! (#앰배서더)</title>
      <link>https://snapcode.tistory.com/entry/Kanana-%EB%B2%A0%ED%83%80-%ED%85%8C%EC%8A%A4%ED%84%B0%EB%A1%9C-%EC%84%A0%EC%A0%95%EB%90%98%EC%8B%A0-%EA%B2%83%EC%9D%84-%EC%B6%95%ED%95%98%EB%93%9C%EB%A6%BD%EB%8B%88%EB%8B%A4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;저번 달 Kanana-o 모델 공개 당시 소개 글을 작성한 적이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[AI] Kanana-o, 카카오 독자 개발 옴니(Omni) 모델. 과연 사람과 얼마나 가까울까? (&lt;a href=&quot;https://snapcode.tistory.com/195&quot;&gt;https://snapcode.tistory.com/195)&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772620879519&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[AI] Kanana-o, 카카오 독자 개발 옴니(Omni) 모델. 과연 사람과 얼마나 가까울까?&quot; data-og-description=&quot;2026.02.12 모집 시작. 그리고 2026.02.20, 안내 메시지가 도착했습니다. 요즘 카카오는 확실히 AI에 집중하고 있다는 느낌을 받습니다. 이번에 공개된 모델은 Kanana-o국내 최초 통합 멀티모달 언어모델&quot; data-og-host=&quot;snapcode.tistory.com&quot; data-og-source-url=&quot;https://snapcode.tistory.com/195&quot; data-og-url=&quot;https://snapcode.tistory.com/195&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bTONBT/dJMb9bvZ0Sx/nK1cY8l3pWDbdb87dfVtTK/img.png?width=283&amp;amp;height=390&amp;amp;face=0_0_283_390,https://scrap.kakaocdn.net/dn/cmRC2W/dJMb9eTNnx8/QgSkorg8fcJdfGaCkQXUk1/img.png?width=283&amp;amp;height=390&amp;amp;face=0_0_283_390,https://scrap.kakaocdn.net/dn/EOjsV/dJMb9hCY0V6/nVs7ujRyv0VHMobuScKFu1/img.png?width=1280&amp;amp;height=677&amp;amp;face=0_0_1280_677&quot;&gt;&lt;a href=&quot;https://snapcode.tistory.com/195&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://snapcode.tistory.com/195&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bTONBT/dJMb9bvZ0Sx/nK1cY8l3pWDbdb87dfVtTK/img.png?width=283&amp;amp;height=390&amp;amp;face=0_0_283_390,https://scrap.kakaocdn.net/dn/cmRC2W/dJMb9eTNnx8/QgSkorg8fcJdfGaCkQXUk1/img.png?width=283&amp;amp;height=390&amp;amp;face=0_0_283_390,https://scrap.kakaocdn.net/dn/EOjsV/dJMb9hCY0V6/nVs7ujRyv0VHMobuScKFu1/img.png?width=1280&amp;amp;height=677&amp;amp;face=0_0_1280_677');&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;[AI] Kanana-o, 카카오 독자 개발 옴니(Omni) 모델. 과연 사람과 얼마나 가까울까?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;2026.02.12 모집 시작. 그리고 2026.02.20, 안내 메시지가 도착했습니다. 요즘 카카오는 확실히 AI에 집중하고 있다는 느낌을 받습니다. 이번에 공개된 모델은 Kanana-o국내 최초 통합 멀티모달 언어모델&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;snapcode.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 오늘 저녁.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운이 좋게도, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;카카오의 멀티모달 AI 모델&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;u&gt;&lt;b&gt;Kanana-o API 베타 테스터에 참여&lt;/b&gt;&lt;/u&gt;하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Kanana] 베타 테스터로 선정되신 것을 축하드립니다!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;379&quot; data-origin-height=&quot;383&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHksMI/dJMcabcfdVo/9QlXvFF7RcAL8NcYA8Z5Tk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHksMI/dJMcabcfdVo/9QlXvFF7RcAL8NcYA8Z5Tk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHksMI/dJMcabcfdVo/9QlXvFF7RcAL8NcYA8Z5Tk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHksMI%2FdJMcabcfdVo%2F9QlXvFF7RcAL8NcYA8Z5Tk%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;379&quot; height=&quot;383&quot; data-origin-width=&quot;379&quot; data-origin-height=&quot;383&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선정 안내 메시지와 함께 전용 API 콘솔 접근 권한이 부여되었고, 개인별 API Key를 발급받았습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/c1cYi1/dJMcagkkiDd/AAAAAAAAAAAAAAAAAAAAAHdaf0jGH6aJYbX3BgFoZQah-wMQws6zOIWYXu6LTlNU/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1774969199&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=oIEjQpXeigE1dujg83RKm35OeBQ%3D&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  베타 프로그램 개요&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모델: Kanana-o (Omni AI)&lt;/li&gt;
&lt;li&gt;지원 기능: &lt;u&gt;&lt;b&gt;텍스트 &amp;middot; 이미지 &amp;middot; 음성 입력 및 멀티모달 응답&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;제공 방식: 전용 API 콘솔을 통한 Key 발급&lt;/li&gt;
&lt;li&gt;사용 기간: 약 5개월&lt;/li&gt;
&lt;li&gt;공식 디스코드 채널 운영 (가이드, 버그 제보, 이벤트 진행)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오프라인 발대식 및 네트워킹 세션도 함께 운영될 예정이라고 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  API 리뷰 (코드생략)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kanana-o API는 Chat Completion 기반 구조로 제공되며&lt;br /&gt;기존 LLM과 유사한 방식으로 빠르게 연동할 수 있을 것 같습니다.&lt;br /&gt;&lt;br /&gt;멀티모달&amp;nbsp;입력과&amp;nbsp;스트리밍&amp;nbsp;응답을&amp;nbsp;지원하며,&amp;nbsp;단일&amp;nbsp;메시지&amp;nbsp;구조&amp;nbsp;안에서&amp;nbsp;통합&amp;nbsp;처리가&amp;nbsp;가능합니다.&amp;nbsp;&amp;nbsp; &lt;br /&gt;이를 바탕으로 음성 인터페이스나 콘텐츠 자동화 등 다양한 서비스 설계에 활용할 수 있을 것으로 보입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  앞으로&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;베타 기간 동안 실제 서비스 관점에서 활용 가능성을 추가로 실험해볼 예정입니다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;일일 응답 횟수가 10회로 제한되어 있다는 것 같아, 신중하게 테스트해야 할 것 같습니다.&lt;/span&gt;&lt;br /&gt;&lt;span&gt;다음 글에서는 실제 API 사용 경험을 정리해보겠습니다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;288&quot; data-origin-height=&quot;190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYgJXH/dJMcaadm2gp/1a59vuhMPspmh5gkl4O5E0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYgJXH/dJMcaadm2gp/1a59vuhMPspmh5gkl4O5E0/img.png&quot; data-alt=&quot;쿼터 할당이 늘어났으면 하는 바램입니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYgJXH/dJMcaadm2gp/1a59vuhMPspmh5gkl4O5E0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYgJXH%2FdJMcaadm2gp%2F1a59vuhMPspmh5gkl4O5E0%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;288&quot; height=&quot;190&quot; data-origin-width=&quot;288&quot; data-origin-height=&quot;190&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;  참고링크&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kanana-o API 기술 문서 &lt;a href=&quot;https://huggingface.co/kakaocorp/Kanana-1.5-o-9.8B-instruct-2602-API_Doc&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://huggingface.co/kakaocorp/Kanana-1.5-o-9.8B-instruct-2602-API_Doc&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772623088112&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;kakaocorp/Kanana-1.5-o-9.8B-instruct-2602-API_Doc &amp;middot; Hugging Face&quot; data-og-description=&quot;We&amp;rsquo;re on a journey to advance and democratize artificial intelligence through open source and open science.&quot; data-og-host=&quot;huggingface.co&quot; data-og-source-url=&quot;https://huggingface.co/kakaocorp/Kanana-1.5-o-9.8B-instruct-2602-API_Doc&quot; data-og-url=&quot;https://huggingface.co/kakaocorp/Kanana-1.5-o-9.8B-instruct-2602-API_Doc&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/JkrcU/dJMb81GUVtP/BYbka1KyfklWikYjr3pAK1/img.png?width=1200&amp;amp;height=648&amp;amp;face=0_0_1200_648,https://scrap.kakaocdn.net/dn/dmHHkn/dJMb82MANrA/CryT0vGuh5aHUC5lKCCGZk/img.png?width=1200&amp;amp;height=648&amp;amp;face=0_0_1200_648,https://scrap.kakaocdn.net/dn/WZDHz/dJMb89ybgSI/h369QnTR4rQtUl4MDHT9HK/img.jpg?width=4284&amp;amp;height=3213&amp;amp;face=0_0_4284_3213&quot;&gt;&lt;a href=&quot;https://huggingface.co/kakaocorp/Kanana-1.5-o-9.8B-instruct-2602-API_Doc&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://huggingface.co/kakaocorp/Kanana-1.5-o-9.8B-instruct-2602-API_Doc&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/JkrcU/dJMb81GUVtP/BYbka1KyfklWikYjr3pAK1/img.png?width=1200&amp;amp;height=648&amp;amp;face=0_0_1200_648,https://scrap.kakaocdn.net/dn/dmHHkn/dJMb82MANrA/CryT0vGuh5aHUC5lKCCGZk/img.png?width=1200&amp;amp;height=648&amp;amp;face=0_0_1200_648,https://scrap.kakaocdn.net/dn/WZDHz/dJMb89ybgSI/h369QnTR4rQtUl4MDHT9HK/img.jpg?width=4284&amp;amp;height=3213&amp;amp;face=0_0_4284_3213');&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;kakaocorp/Kanana-1.5-o-9.8B-instruct-2602-API_Doc &amp;middot; Hugging Face&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;We&amp;rsquo;re on a journey to advance and democratize artificial intelligence through open source and open science.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;huggingface.co&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/AI</category>
      <category>ai</category>
      <category>API개발</category>
      <category>Kanana-o</category>
      <category>llm</category>
      <category>OmniAI</category>
      <category>독자개발</category>
      <category>멀티모달</category>
      <category>베타테스터</category>
      <category>카나나</category>
      <category>카카오</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/199</guid>
      <comments>https://snapcode.tistory.com/entry/Kanana-%EB%B2%A0%ED%83%80-%ED%85%8C%EC%8A%A4%ED%84%B0%EB%A1%9C-%EC%84%A0%EC%A0%95%EB%90%98%EC%8B%A0-%EA%B2%83%EC%9D%84-%EC%B6%95%ED%95%98%EB%93%9C%EB%A6%BD%EB%8B%88%EB%8B%A4#entry199comment</comments>
      <pubDate>Wed, 4 Mar 2026 19:57:08 +0900</pubDate>
    </item>
    <item>
      <title>[PlayMCP] 내가 만든 MCP 서버를 카카오에 등록하다. 2편 심사통과 후기</title>
      <link>https://snapcode.tistory.com/entry/PlayMCP-%EB%82%B4%EA%B0%80-%EB%A7%8C%EB%93%A0-MCP-%EC%84%9C%EB%B2%84%EB%A5%BC-%EC%B9%B4%EC%B9%B4%EC%98%A4%EC%97%90-%EB%93%B1%EB%A1%9D%ED%95%98%EB%8B%A4-2%ED%8E%B8-%EC%8B%AC%EC%82%AC%ED%86%B5%EA%B3%BC-%ED%9B%84%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번 글에서 썼던 것처럼, 연휴 직전에 MCP를 직접 구현하고 등록했다. &lt;br /&gt;&lt;a href=&quot;https://snapcode.tistory.com/189&quot;&gt;https://snapcode.tistory.com/189&lt;/a&gt; &lt;/p&gt;
&lt;figure id=&quot;og_1772014412677&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[PlayMCP] 내가 만든 MCP 서버를 카카오에 등록하다.&quot; data-og-description=&quot;오늘 드디어 MCP 서버를 배포했습니다.원래 공모전이 뜨자마자 @DBrider3와 참가하려했지만, 일정상 참가하진 못했고, 이렇게 만들어보았습니다.https://playmcp.kakao.com/ PlayMCP | 새로운 AI 경험의 시작Pl&quot; data-og-host=&quot;snapcode.tistory.com&quot; data-og-source-url=&quot;https://snapcode.tistory.com/189&quot; data-og-url=&quot;https://snapcode.tistory.com/189&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/gvso3/dJMb84p5PAc/D6VtgtUc82scPQqeKbDkdK/img.png?width=800&amp;amp;height=709&amp;amp;face=0_0_800_709,https://scrap.kakaocdn.net/dn/kv2nO/dJMb88eXKdw/xLlTQjewyLLbXK0bfsmke1/img.png?width=800&amp;amp;height=709&amp;amp;face=0_0_800_709,https://scrap.kakaocdn.net/dn/SesLl/dJMb86nUD7P/nxrFvv3OGKsignbMWHxvy1/img.png?width=1917&amp;amp;height=910&amp;amp;face=0_0_1917_910&quot;&gt;&lt;a href=&quot;https://snapcode.tistory.com/189&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://snapcode.tistory.com/189&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/gvso3/dJMb84p5PAc/D6VtgtUc82scPQqeKbDkdK/img.png?width=800&amp;amp;height=709&amp;amp;face=0_0_800_709,https://scrap.kakaocdn.net/dn/kv2nO/dJMb88eXKdw/xLlTQjewyLLbXK0bfsmke1/img.png?width=800&amp;amp;height=709&amp;amp;face=0_0_800_709,https://scrap.kakaocdn.net/dn/SesLl/dJMb86nUD7P/nxrFvv3OGKsignbMWHxvy1/img.png?width=1917&amp;amp;height=910&amp;amp;face=0_0_1917_910');&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;[PlayMCP] 내가 만든 MCP 서버를 카카오에 등록하다.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;오늘 드디어 MCP 서버를 배포했습니다.원래 공모전이 뜨자마자 @DBrider3와 참가하려했지만, 일정상 참가하진 못했고, 이렇게 만들어보았습니다.https://playmcp.kakao.com/ PlayMCP | 새로운 AI 경험의 시작Pl&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;snapcode.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;57&quot; data-start=&quot;0&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;심사를 요청해둔 상태였는데, 오늘 메일 한 통을 받았다. &lt;/p&gt;
&lt;blockquote data-end=&quot;72&quot; data-start=&quot;59&quot; data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&amp;ldquo;심사가 완료되었습니다.&amp;rdquo;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;422&quot; data-origin-height=&quot;928&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLEbGZ/dJMcaaqP0GX/FwfP0kcdvcEELYPvvjNRB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLEbGZ/dJMcaaqP0GX/FwfP0kcdvcEELYPvvjNRB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLEbGZ/dJMcaaqP0GX/FwfP0kcdvcEELYPvvjNRB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLEbGZ%2FdJMcaaqP0GX%2FFwfP0kcdvcEELYPvvjNRB1%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;422&quot; height=&quot;928&quot; data-origin-width=&quot;422&quot; data-origin-height=&quot;928&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;158&quot; data-start=&quot;74&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;158&quot; data-start=&quot;74&quot; data-ke-size=&quot;size16&quot;&gt;사실 처음에는 모델 연결 실패로 한 차례 재심사를 요청했었는데, 이번에는 무사히 통과됐다.&lt;/p&gt;
&lt;p data-end=&quot;158&quot; data-start=&quot;74&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;158&quot; data-start=&quot;74&quot; data-ke-size=&quot;size16&quot;&gt;Tool 호출 건수가 조금 올라간 것을 보니, 실제로 여러 테스트 케이스를 돌려보며 정책 준수 여부를 확인해주신 것 같다.&lt;br /&gt;@심사 담당자님, 도움주셔서 감사합니다.&lt;/p&gt;
&lt;p data-end=&quot;158&quot; data-start=&quot;74&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;158&quot; data-start=&quot;74&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;158&quot; data-start=&quot;74&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;144&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csivTD/dJMcajnHK2l/PNeZLVopBjkflcpCoGWPek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csivTD/dJMcajnHK2l/PNeZLVopBjkflcpCoGWPek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csivTD/dJMcajnHK2l/PNeZLVopBjkflcpCoGWPek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsivTD%2FdJMcajnHK2l%2FPNeZLVopBjkflcpCoGWPek%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;567&quot; height=&quot;144&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;144&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;158&quot; data-start=&quot;74&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;158&quot; data-start=&quot;74&quot; data-ke-size=&quot;size16&quot;&gt;요즘 개인 프로젝트로 하루하루 배움의 덕을 쌓아가면서, 참 개발이 재미있단걸 또 느끼고 있다.&lt;br /&gt;판도가 빠르게 바뀌고 있다는 걸 체감하고 있고, 앞으로 어디까지 변할지 기대되면서도 조금은 두렵다. (AI의 성장 속도가 정말 빠르다&amp;hellip;) &lt;/p&gt;
&lt;p data-end=&quot;158&quot; data-start=&quot;74&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;158&quot; data-start=&quot;74&quot; data-ke-size=&quot;size16&quot;&gt;아무튼 이번 경험은 개인적으로 의미가 크다.&lt;br /&gt;단순 실험이 아니라, &lt;b&gt;누구나 사용할 수 있는 형태의 서비스로 공개되었다는 점&lt;/b&gt;에서 작은 성장의 이정표처럼 느껴진다. &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-end=&quot;775&quot; data-start=&quot;671&quot; data-ke-size=&quot;size16&quot;&gt;Agent 구조를 이해하고 MCP를 직접 설계하며 요청/응답 흐름, 스키마, 예외 처리를 서비스 관점에서 고민해볼 수 있었다.&lt;br /&gt;이론으로만 알던 개념을 실제 구조로 만들어본 경험이었다. 앞으로 조금 더 고도화해볼까 한다.&lt;br /&gt;이쯤 되면 카페24꺼로, 아주 작은 서버 하나 따로 운영해야 하나 고민도 된다.&lt;/p&gt;
&lt;p data-end=&quot;262&quot; data-start=&quot;160&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;311&quot; data-start=&quot;264&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;</description>
      <category>IT/AI</category>
      <category>Agent</category>
      <category>ai</category>
      <category>kakao</category>
      <category>MCP</category>
      <category>PlayMCP</category>
      <category>Tool</category>
      <category>경험</category>
      <category>등록</category>
      <category>심사완료</category>
      <category>에이전트</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/198</guid>
      <comments>https://snapcode.tistory.com/entry/PlayMCP-%EB%82%B4%EA%B0%80-%EB%A7%8C%EB%93%A0-MCP-%EC%84%9C%EB%B2%84%EB%A5%BC-%EC%B9%B4%EC%B9%B4%EC%98%A4%EC%97%90-%EB%93%B1%EB%A1%9D%ED%95%98%EB%8B%A4-2%ED%8E%B8-%EC%8B%AC%EC%82%AC%ED%86%B5%EA%B3%BC-%ED%9B%84%EA%B8%B0#entry198comment</comments>
      <pubDate>Wed, 25 Feb 2026 19:32:59 +0900</pubDate>
    </item>
    <item>
      <title>[AI] GitHub Copilot Pro 한도 초과 후기: LTE 다 쓰고 3G로 버티는 기분</title>
      <link>https://snapcode.tistory.com/entry/AI-GitHub-Copilot-Pro-%ED%95%9C%EB%8F%84-%EC%B4%88%EA%B3%BC-%ED%9B%84%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신나게 개발하고 있었는데, 나에게도 드디어 찾아왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;한 도 초 과.&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;788&quot; data-origin-height=&quot;277&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHS56X/dJMcajnDR7D/omGFKf41UBeQeq2WvExoc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHS56X/dJMcajnDR7D/omGFKf41UBeQeq2WvExoc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHS56X/dJMcajnDR7D/omGFKf41UBeQeq2WvExoc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHS56X%2FdJMcajnDR7D%2FomGFKf41UBeQeq2WvExoc0%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;788&quot; height=&quot;277&quot; data-origin-width=&quot;788&quot; data-origin-height=&quot;277&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색해보니 GitHub Copilot 설정 페이지를 들어가보라고 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/settings/copilot/features&quot;&gt;https://github.com/settings/copilot/features&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1771585895504&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;GitHub &amp;middot; Change is constant. GitHub keeps you ahead.&quot; data-og-description=&quot;Join the world's most widely adopted, AI-powered developer platform where millions of developers, businesses, and the largest open source community build software that advances humanity.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/settings/copilot/features&quot; data-og-url=&quot;https://github.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bng0RJ/dJMb83kpAmU/srObQbzTxkR3cIY3lLMI41/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/FUlrK/dJMb88F1xEb/1PBGpzOgtU1A2NXhtEED31/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://github.com/settings/copilot/features&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/settings/copilot/features&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bng0RJ/dJMb83kpAmU/srObQbzTxkR3cIY3lLMI41/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/FUlrK/dJMb88F1xEb/1PBGpzOgtU1A2NXhtEED31/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;GitHub &amp;middot; Change is constant. GitHub keeps you ahead.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Join the world's most widely adopted, AI-powered developer platform where millions of developers, businesses, and the largest open source community build software that advances humanity.&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;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 빨간색 바를 마주했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;417&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bt60W3/dJMcadHJMi3/bOKHjtXDTapMbkOc72VJAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bt60W3/dJMcadHJMi3/bOKHjtXDTapMbkOc72VJAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bt60W3/dJMcadHJMi3/bOKHjtXDTapMbkOc72VJAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbt60W3%2FdJMcadHJMi3%2FbOKHjtXDTapMbkOc72VJAk%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;848&quot; height=&quot;417&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;417&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Premium Requests: 100.1%&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 경험했다. 이게 다 써지는 거였나?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;GitHub에서는 &amp;ldquo;정확한 토큰 수&amp;rdquo;를 공개하지 않아&lt;/b&gt;&lt;/u&gt;서 모르겠지만 넉넉할 줄 알았다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;내가 사용 중인 플랜&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 &lt;b&gt;GitHub Copilot Pro 플랜&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 개인용 유료 플랜으로, IDE&amp;middot;CLI&amp;middot;모바일에서 Copilot을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Copilot Pro 주요 제공 사항&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IDE 내 코드 자동완성&lt;/li&gt;
&lt;li&gt;Chat 기반 코드 설명 및 리팩토링&lt;/li&gt;
&lt;li&gt;Copilot CLI 지원&lt;/li&gt;
&lt;li&gt;GitHub Mobile Chat 지원&lt;/li&gt;
&lt;li&gt;월별 Premium Requests 제공&lt;/li&gt;
&lt;li&gt;모델 선택 기능 (지원 모델 범위 내)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 단순 자동완성이 아니라 거의 &amp;ldquo;AI 페어 프로그래머&amp;rdquo; 수준이라고 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Premium Requests란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 자동완성보다&lt;br /&gt;더 많은 연산이 필요한 요청들:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Copilot Chat&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;p data-ke-size=&quot;size16&quot;&gt;이런 요청이 Premium Request로 차감된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 나는&amp;hellip;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;100.1% 사용.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 다 썼을까?&lt;/h2&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;LLM Agent 서버 설계&lt;/li&gt;
&lt;li&gt;Spring 리팩토링&lt;/li&gt;
&lt;li&gt;Docker 구조 수정&lt;/li&gt;
&lt;li&gt;Nginx 설정&lt;/li&gt;
&lt;li&gt;CI/CD 개선&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거의 모든 코드를 Copilot과 함께 작업했다.&lt;/p&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;/li&gt;
&lt;li&gt;설정 파일 생성&lt;/li&gt;
&lt;li&gt;오류 분석&lt;/li&gt;
&lt;li&gt;리팩토링 제안&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 계속 던졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과는 이번달 한도 초과.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;느낀 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 단순히 &amp;ldquo;많이 썼다&amp;rdquo;의 문제가 아니라,&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI를 개발 도구가 아니라 개발 동료처럼 쓰고 있다는 증거&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Copilot이 없으면 생산성이 확실히 떨어질 것 같다는 느낌도 들었다. (이젠 사실 좀 무섭기도 하다..AI의 성장 잠재력이..)&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Premium Requests 100% 초과.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의도한&amp;nbsp;건&amp;nbsp;아니지만,&amp;nbsp;AI를&amp;nbsp;적극적으로&amp;nbsp;활용하는&amp;nbsp;개발&amp;nbsp;습관이&amp;nbsp;이미&amp;nbsp;자리&amp;nbsp;잡았다는&amp;nbsp;신호&amp;nbsp;같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Plan, Agent, Edit, Ask 차이도 모르던 때가 엊그젠데.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 달 리셋이 빨리 돌아왔으면 좋겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퍼플렉시티 1년 무료, 뭐 무료, 뭐 대학생 제공, 이런거좀 해둘걸 싶다.&lt;/p&gt;</description>
      <category>IT/AI</category>
      <category>AI개발</category>
      <category>Copilot</category>
      <category>devlife</category>
      <category>github</category>
      <category>llm</category>
      <category>개발생산성</category>
      <category>개발자</category>
      <category>리팩토링</category>
      <category>자동완성</category>
      <category>프로그래밍</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/197</guid>
      <comments>https://snapcode.tistory.com/entry/AI-GitHub-Copilot-Pro-%ED%95%9C%EB%8F%84-%EC%B4%88%EA%B3%BC-%ED%9B%84%EA%B8%B0#entry197comment</comments>
      <pubDate>Fri, 20 Feb 2026 20:19:07 +0900</pubDate>
    </item>
    <item>
      <title>[Server] 내장 톰캣 대신 Nginx 리버스 프록시를 도입한 이유</title>
      <link>https://snapcode.tistory.com/entry/Server-%EB%82%B4%EC%9E%A5-%ED%86%B0%EC%BA%A3-%EB%8C%80%EC%8B%A0-Nginx-%EB%A6%AC%EB%B2%84%EC%8A%A4-%ED%94%84%EB%A1%9D%EC%8B%9C%EB%A5%BC-%EB%8F%84%EC%9E%85%ED%95%9C-%EC%9D%B4%EC%9C%A0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 빠르고 간단히 내장톰캣으로 프로젝트 개발을 시작했었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;implementation&amp;nbsp;'org.springframework.boot:spring-boot-starter-web'&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;465&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUwIkP/dJMcacWodUP/bWAkaEjwEhtSNMtNu2HPZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUwIkP/dJMcacWodUP/bWAkaEjwEhtSNMtNu2HPZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUwIkP/dJMcacWodUP/bWAkaEjwEhtSNMtNu2HPZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUwIkP%2FdJMcacWodUP%2FbWAkaEjwEhtSNMtNu2HPZ1%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;666&quot; height=&quot;387&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;465&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;[Server] 내장 톰캣 대신 Nginx 리버스 프록시를 도입한 이유&lt;/h1&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;Next.js (static export)&lt;/li&gt;
&lt;li&gt;Backend API 서버&lt;/li&gt;
&lt;li&gt;LLM Agent 서버&lt;/li&gt;
&lt;li&gt;크롤러&lt;/li&gt;
&lt;li&gt;이미지 리소스&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 &lt;b&gt;모두 한 서버에서, 각기 다른 포트로 떠 있었다는 점&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 Nginx를 고민했을까?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ 포트 단일화&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FE: 정적 파일&lt;/li&gt;
&lt;li&gt;BE: 8080&lt;/li&gt;
&lt;li&gt;Agent: 8081&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 하나의 도메인으로 접속하지만 실제 서비스는 여러 포트에서 동작 중이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 다음처럼 정리하고 싶었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;example.com &amp;rarr; 정적 파일&lt;br /&gt;example.com/api &amp;rarr; Backend&lt;br /&gt;example.com/agent &amp;rarr; LLM Agent&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;리버스 프록시 목적&lt;/b&gt;이었다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ CORS 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저 기준으로 origin이 달라지면서 API 호출 시 CORS 이슈가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트와 API를 같은 도메인 아래로 묶으면&lt;br /&gt;애초에 &lt;b&gt;CORS 설정을 복잡하게 가져갈 필요가 없다.&lt;/b&gt; &lt;b&gt;Nginx는 이 문제를 구조적으로 해결&lt;/b&gt;해준다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ Next.js static export&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 프로젝트는 &lt;code&gt;next start&lt;/code&gt; 방식이 아니라 &lt;b&gt;static export 빌드 결과물을 서빙하는 구조&lt;/b&gt;였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Node 서버가 반드시 필요하지 않다. 정적 파일이라면 굳이 애플리케이션 서버로 서빙할 이유가 없다.&lt;br /&gt;&lt;b&gt;Nginx가 훨씬 가볍고 효율적&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4️⃣ 저사양 서버 환경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무료 클라우드 서버 환경에서는 메모리 사용량도 중요한 요소다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 서버 여러 개를 띄우는 것보다 Nginx로 정적 파일을 분리하는 편이 안정적이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실제 리소스 비교&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  도입 전 (내장 톰캣 사용)&lt;/h3&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;1085&quot; data-origin-height=&quot;141&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMNX09/dJMcac9TbsD/o2KO6b7eZxNtOb9MSvBWr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMNX09/dJMcac9TbsD/o2KO6b7eZxNtOb9MSvBWr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMNX09/dJMcac9TbsD/o2KO6b7eZxNtOb9MSvBWr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMNX09%2FdJMcac9TbsD%2Fo2KO6b7eZxNtOb9MSvBWr1%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;1085&quot; height=&quot;141&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1085&quot; data-origin-height=&quot;141&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Spring Boot 내장 톰캣이 정적 파일까지 함께 처리했다. (그래서 무거웠음)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  Nginx 도입 후&lt;/h3&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;958&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AVhAO/dJMcac9TbsC/0KMgVAFeZlAIGtBmcAKUY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AVhAO/dJMcac9TbsC/0KMgVAFeZlAIGtBmcAKUY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AVhAO/dJMcac9TbsC/0KMgVAFeZlAIGtBmcAKUY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAVhAO%2FdJMcac9TbsC%2F0KMgVAFeZlAIGtBmcAKUY0%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;958&quot; height=&quot;242&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전 후가 매우 많이 차이난다...!&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;  컨테이너 메모리 224MiB &amp;rarr; 86.77MiB (약 61% 감소, -137MiB)&lt;/b&gt;&lt;br /&gt;&lt;b&gt;  전체 시스템 메모리 사용량 413MiB &amp;rarr; 287MiB (약 30% 감소, -126MiB)&lt;/b&gt;&lt;br /&gt;&lt;b&gt;  컨테이너 CPU 사용률 0.23% &amp;rarr; 0.13% (약 43% 감소)&lt;/b&gt;&lt;br /&gt;&lt;b&gt;  가용 메모리(available) 331MiB &amp;rarr; 459MiB (약 38% 증가)&lt;/b&gt;&lt;br /&gt;&lt;b&gt; &amp;nbsp;정적&amp;nbsp;리소스&amp;nbsp;처리&amp;nbsp;주체&amp;nbsp;변경&amp;nbsp;(내장&amp;nbsp;톰캣&amp;nbsp;&amp;rarr;&amp;nbsp;Nginx&amp;nbsp;분리) &lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;rarr;&amp;nbsp;애플리케이션&amp;nbsp;서버는&amp;nbsp;API&amp;nbsp;처리에&amp;nbsp;집중&amp;nbsp;가능&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그래서, 꼭 써야 할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리해보면 이렇다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;상황&lt;/th&gt;
&lt;th&gt;Nginx 필요성&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;단일 Node 서버&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;정적 + API 분리&lt;/td&gt;
&lt;td&gt;높음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;멀티 서비스 운영&lt;/td&gt;
&lt;td&gt;높음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CORS 이슈 존재&lt;/td&gt;
&lt;td&gt;매우 높음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 프로젝트에서는&lt;br /&gt;&lt;b&gt;리버스 프록시 + 정적 서빙 + 포트 통합&lt;/b&gt; 목적이 명확했기 때문에&lt;br /&gt;도입을 검토하는 게 합리적이었다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx는 &quot;무조건 쓰는 기술&quot;이 아니라&lt;br /&gt;&lt;b&gt;구조가 복잡해지는 순간 자연스럽게 필요해지는 도구&lt;/b&gt;라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스가 단순할 때는 필요 없다.&lt;/p&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;/li&gt;
&lt;li&gt;API가 분리되고&lt;/li&gt;
&lt;li&gt;Agent 서버가 추가되는 순간&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그때부터는 고민이 아니라 선택에 가까워지더라.&lt;/p&gt;</description>
      <category>IT/etc</category>
      <category>Agent</category>
      <category>cors</category>
      <category>infra</category>
      <category>nextjs</category>
      <category>nginx</category>
      <category>port</category>
      <category>ReverseProxy</category>
      <category>staticexport</category>
      <category>서버구조</category>
      <category>서버최적화</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/196</guid>
      <comments>https://snapcode.tistory.com/entry/Server-%EB%82%B4%EC%9E%A5-%ED%86%B0%EC%BA%A3-%EB%8C%80%EC%8B%A0-Nginx-%EB%A6%AC%EB%B2%84%EC%8A%A4-%ED%94%84%EB%A1%9D%EC%8B%9C%EB%A5%BC-%EB%8F%84%EC%9E%85%ED%95%9C-%EC%9D%B4%EC%9C%A0#entry196comment</comments>
      <pubDate>Fri, 20 Feb 2026 20:02:45 +0900</pubDate>
    </item>
    <item>
      <title>[AI] Kanana-o, 카카오 독자 개발 옴니(Omni) 모델. 과연 사람과 얼마나 가까울까?</title>
      <link>https://snapcode.tistory.com/entry/AI-Kanana-o-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%8F%85%EC%9E%90-%EA%B0%9C%EB%B0%9C-%EC%98%B4%EB%8B%88Omni-%EB%AA%A8%EB%8D%B8-%EA%B3%BC%EC%97%B0-%EC%82%AC%EB%9E%8C%EA%B3%BC-%EC%96%BC%EB%A7%88%EB%82%98-%EA%B0%80%EA%B9%8C%EC%9A%B8%EA%B9%8C</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;283&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rgF7p/dJMcabQIFoy/6BVtwWjeuzy0Rjg1hB2CTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rgF7p/dJMcabQIFoy/6BVtwWjeuzy0Rjg1hB2CTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rgF7p/dJMcabQIFoy/6BVtwWjeuzy0Rjg1hB2CTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrgF7p%2FdJMcabQIFoy%2F6BVtwWjeuzy0Rjg1hB2CTk%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;283&quot; height=&quot;390&quot; data-origin-width=&quot;283&quot; data-origin-height=&quot;390&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2026.02.12&amp;nbsp;모집&amp;nbsp;시작.&amp;nbsp;&amp;nbsp; &lt;br /&gt;그리고&amp;nbsp;2026.02.20,&amp;nbsp;안내&amp;nbsp;메시지가&amp;nbsp;도착했습니다. &lt;br /&gt;&lt;br /&gt;요즘&amp;nbsp;카카오는&amp;nbsp;확실히&amp;nbsp;AI에&amp;nbsp;집중하고&amp;nbsp;있다는&amp;nbsp;느낌을&amp;nbsp;받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 공개된 모델은 &lt;u&gt;&lt;b&gt;Kanana-o&lt;/b&gt;&lt;/u&gt;&lt;br /&gt;국내&amp;nbsp;최초&amp;nbsp;통합&amp;nbsp;멀티모달&amp;nbsp;언어모델이라고&amp;nbsp;소개합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://omni.kanana.ai/?t_src=talk&amp;amp;t_ch=msg&quot;&gt;https://omni.kanana.ai/?t_src=talk&amp;amp;t_ch=msg&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1771583198426&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;Kanana-o&quot; data-og-description=&quot;더욱 똑똑하게 답하며, 더욱 풍부한 감정표현을 향한 Kanana-o의 진화 과정&quot; data-og-host=&quot;omni.kanana.ai&quot; data-og-source-url=&quot;https://omni.kanana.ai/?t_src=talk&amp;amp;t_ch=msg&quot; data-og-url=&quot;https://omni.kanana.ai&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/yZotR/dJMb81fPeZg/moXsSAHKxVn7ts1E9lCnSK/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800,https://scrap.kakaocdn.net/dn/hckgs/dJMb8SXupEO/uZCKRbk2KPAcL7vZzNpTOk/img.png?width=1030&amp;amp;height=1030&amp;amp;face=0_0_1030_1030,https://scrap.kakaocdn.net/dn/2sWED/dJMb8SXupEN/apzxekDDzMgn67ZFKeezz1/img.png?width=1360&amp;amp;height=720&amp;amp;face=0_0_1360_720&quot;&gt;&lt;a href=&quot;https://omni.kanana.ai/?t_src=talk&amp;amp;t_ch=msg&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://omni.kanana.ai/?t_src=talk&amp;amp;t_ch=msg&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/yZotR/dJMb81fPeZg/moXsSAHKxVn7ts1E9lCnSK/img.png?width=1600&amp;amp;height=800&amp;amp;face=0_0_1600_800,https://scrap.kakaocdn.net/dn/hckgs/dJMb8SXupEO/uZCKRbk2KPAcL7vZzNpTOk/img.png?width=1030&amp;amp;height=1030&amp;amp;face=0_0_1030_1030,https://scrap.kakaocdn.net/dn/2sWED/dJMb8SXupEN/apzxekDDzMgn67ZFKeezz1/img.png?width=1360&amp;amp;height=720&amp;amp;face=0_0_1360_720');&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;Kanana-o&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;더욱 똑똑하게 답하며, 더욱 풍부한 감정표현을 향한 Kanana-o의 진화 과정&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;omni.kanana.ai&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; 사람에 가까운 자연스러운 &lt;/span&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 엄청 강조하는 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근&amp;nbsp;LLM은&amp;nbsp;성능&amp;nbsp;경쟁을&amp;nbsp;넘어&amp;nbsp;&amp;nbsp; &lt;br /&gt;&lt;u&gt;&lt;b&gt;&amp;ldquo;어떻게&amp;nbsp;경험을&amp;nbsp;설계하느냐&amp;rdquo;&lt;/b&gt;&lt;/u&gt;의&amp;nbsp;단계로&amp;nbsp;넘어가고&amp;nbsp;있다고&amp;nbsp;느낍니다. &lt;br /&gt;&lt;br /&gt;Kanana-o&amp;nbsp;역시&amp;nbsp;&amp;nbsp; &lt;br /&gt;멀티모달&amp;nbsp;+&amp;nbsp;자연스러운&amp;nbsp;발화&amp;nbsp;+&amp;nbsp;한국어&amp;nbsp;최적화라는&amp;nbsp;방향으로&amp;nbsp;&amp;nbsp; &lt;br /&gt;카카오식&amp;nbsp;AI&amp;nbsp;전략을&amp;nbsp;보여주는&amp;nbsp;사례라고&amp;nbsp;생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 대기등록은 신청해두었는데 앰배서더처럼 발표 일자가 기재되어있지 않아,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발표를 기다려봐야겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 홈페이지와 동일 내용 발췌&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;div data-astro-cid-iiytciq4=&quot;&quot;&gt;
&lt;div data-astro-cid-iiytciq4=&quot;&quot;&gt;
&lt;div data-astro-cid-iiytciq4=&quot;&quot;&gt;
&lt;div data-astro-cid-iiytciq4=&quot;&quot;&gt;
&lt;div style=&quot;text-align: center;&quot; data-astro-cid-iiytciq4=&quot;&quot;&gt;
&lt;div data-astro-cid-iiytciq4=&quot;&quot;&gt;
&lt;div style=&quot;color: #ffffff;&quot; data-astro-cid-iiytciq4=&quot;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;color: #ffffff; text-align: center;&quot; data-astro-cid-iiytciq4=&quot;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;color: #ffffff; text-align: center;&quot; data-astro-cid-iiytciq4=&quot;&quot;&gt;
&lt;p data-astro-cid-iiytciq4=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;국내 최초 통합 멀티모달 언어모델&lt;br /&gt;Kanana-o를 가장 먼저 만나보세요&lt;/p&gt;
&lt;/div&gt;
&lt;div data-astro-cid-iiytciq4=&quot;&quot;&gt;
&lt;div data-astro-cid-iiytciq4=&quot;&quot;&gt;
&lt;div style=&quot;background-color: #000000; color: #ffffff;&quot; data-astro-cid-iiytciq4=&quot;&quot;&gt;&lt;b&gt;Kanana-o란?&lt;/b&gt;
&lt;p data-astro-cid-iiytciq4=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;2025년 5월 국내 최초로 개발된 사람처럼 보고, 듣고, 이해하며 풍부한 감정까지 표현하는 통합 멀티모달 언어모델입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #000000; color: #ffffff;&quot; data-astro-cid-iiytciq4=&quot;&quot;&gt;&lt;b&gt;API (Beta) 최초 공개&lt;/b&gt;
&lt;p data-astro-cid-iiytciq4=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;안정적인 서비스 제공을 위해, Kanana-o는 대기 등록 후 선정된 사용자부터 순차적으로 이용하실 수 있습니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #000000; color: #ffffff;&quot; data-astro-cid-iiytciq4=&quot;&quot;&gt;&lt;b&gt;신청 방법&lt;/b&gt;
&lt;p data-astro-cid-iiytciq4=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;방법: 하단 버튼을 눌러 대기 등록하기&lt;br /&gt;기간: 2026/5/27까지 주차별 선별&lt;br /&gt;안내: 카카오톡 채널 메시지로 개별 안내&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;h3 style=&quot;color: #191919;&quot; data-ke-size=&quot;size23&quot;&gt;FEATURE&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;div&gt;
&lt;h4 style=&quot;color: #191919;&quot; data-ke-size=&quot;size20&quot;&gt;가장 깊게 이해하는 AI&lt;/h4&gt;
&lt;p style=&quot;color: #696969;&quot; data-ke-size=&quot;size16&quot;&gt;텍스트, 이미지, 음성 등 두 가지 이상의 정보를 동시에 이해하고 처리해, 한국어와 한국적 맥락 속 복잡한 의도와 상황까지 깊이 해석합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;677&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckW7dv/dJMcafMoizn/9SCID9FrtGd1UHc4rTVQWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckW7dv/dJMcafMoizn/9SCID9FrtGd1UHc4rTVQWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckW7dv/dJMcafMoizn/9SCID9FrtGd1UHc4rTVQWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckW7dv%2FdJMcafMoizn%2F9SCID9FrtGd1UHc4rTVQWK%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;1280&quot; height=&quot;677&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;677&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;div&gt;
&lt;h4 style=&quot;color: #191919;&quot; data-ke-size=&quot;size20&quot;&gt;가장 자연스럽게 말하는 AI&lt;/h4&gt;
&lt;p style=&quot;color: #696969;&quot; data-ke-size=&quot;size16&quot;&gt;억양, 속도, 감정, 화자 특성을 고려해 풍부한 감정과 자연스러운 표현으로 사람처럼 말하고 반응합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #e3d6ff; text-align: center;&quot;&gt;&lt;span&gt;정확한 발음과&lt;br /&gt;깨끗한 음질&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffd7ed; text-align: center;&quot;&gt;&lt;span&gt;가장 자연스러운&lt;br /&gt;한국어 발화&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #c5fbe6; text-align: center;&quot;&gt;&lt;span&gt;풍부한&lt;br /&gt;감정 표현력&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;div&gt;
&lt;h4 style=&quot;color: #191919;&quot; data-ke-size=&quot;size20&quot;&gt;가장 다재다능한 AI&lt;/h4&gt;
&lt;p style=&quot;color: #696969;&quot; data-ke-size=&quot;size16&quot;&gt;카나나는 특정 태스크에 한정되지 않는 범용 멀티모달 모델로, 다양한 실생활 유스케이스를 폭넓게 지원합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #c3f5fa; text-align: center;&quot;&gt;&lt;span&gt;팟캐스트&lt;br /&gt;발화 지원&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffebbe; text-align: center;&quot;&gt;&lt;span&gt;멀티턴 대화&lt;br /&gt;시나리오&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffd9e3; text-align: center;&quot;&gt;&lt;span&gt;다중 화자&lt;br /&gt;대화 TTS&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;영상&lt;/h3&gt;
&lt;div style=&quot;background-color: #000000;&quot; data-astro-cid-mrk3q7f7=&quot;&quot; data-play-when-inview=&quot;true&quot;&gt;&lt;span data-astro-cid-mrk3q7f7=&quot;&quot;&gt;소리 켜기&lt;/span&gt;&lt;span data-astro-cid-mrk3q7f7=&quot;&quot; aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;&lt;span data-astro-cid-mrk3q7f7=&quot;&quot; aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;&lt;/div&gt;
&lt;a style=&quot;background-color: #000000; color: #ffffff;&quot; href=&quot;https://tech.kakao.com/posts/802&quot; data-tiara=&quot;개발기더보기&quot;&gt;개발기 더보기&lt;/a&gt;
&lt;figure id=&quot;og_1771583057181&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;더욱 똑똑하게 답하며, 더욱 풍부한 감정표현을 향한 Kanana-o의 진화 과정 - tech.kakao.com&quot; data-og-description=&quot;안녕하세요, 카카오의 AI 모델 개발을 담당하는 카나나(Kanana) 조직의 Ed...&quot; data-og-host=&quot;tech.kakao.com&quot; data-og-source-url=&quot;https://tech.kakao.com/posts/802&quot; data-og-url=&quot;https://tech.kakao.com/posts/802&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bwE3Nm/dJMb85vLxLA/GIUaVjM1XCVOIpRZv9wkN0/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504,https://scrap.kakaocdn.net/dn/bjmA1I/dJMb9fZrYdg/jRlNlPNXY0u0nWh4YHNLZK/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504,https://scrap.kakaocdn.net/dn/vn4Xi/dJMb9c9ux3U/9bq5tBzpwhoxR9Ch7HUUq1/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504&quot;&gt;&lt;a href=&quot;https://tech.kakao.com/posts/802&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tech.kakao.com/posts/802&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bwE3Nm/dJMb85vLxLA/GIUaVjM1XCVOIpRZv9wkN0/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504,https://scrap.kakaocdn.net/dn/bjmA1I/dJMb9fZrYdg/jRlNlPNXY0u0nWh4YHNLZK/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504,https://scrap.kakaocdn.net/dn/vn4Xi/dJMb9c9ux3U/9bq5tBzpwhoxR9Ch7HUUq1/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504');&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;더욱 똑똑하게 답하며, 더욱 풍부한 감정표현을 향한 Kanana-o의 진화 과정 - tech.kakao.com&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;안녕하세요, 카카오의 AI 모델 개발을 담당하는 카나나(Kanana) 조직의 Ed...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;tech.kakao.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;h3 style=&quot;color: #191919;&quot; data-ke-size=&quot;size23&quot;&gt;FAQ&lt;/h3&gt;
&lt;div data-astro-cid-oqjbs5yv=&quot;&quot;&gt;&lt;br /&gt;
&lt;div id=&quot;accordion_uth81o99z&quot; data-astro-cid-oqjbs5yv=&quot;&quot; data-icon-close=&quot;&amp;lt;svg width='32' height='32' viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'&amp;gt;                 &amp;lt;path d='M4.5 16H27.5' stroke='#191919' stroke-width='2.5'/&amp;gt;                 &amp;lt;path d='M16 27.5L16 4.5' stroke='#191919' stroke-width='2.5'/&amp;gt;               &amp;lt;/svg&amp;gt;&quot; data-icon-open=&quot;&amp;lt;svg width='32' height='32' viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'&amp;gt;                 &amp;lt;path d='M4.5 16H27.5' stroke='#191919' stroke-width='2.5'/&amp;gt;               &amp;lt;/svg&amp;gt;&quot; data-on-toggle=&quot;&quot; data-allow-multiple=&quot;true&quot;&gt;
&lt;div data-astro-cid-oqjbs5yv=&quot;&quot; data-item-id=&quot;1&quot;&gt;이번 베타 서비스의 목적은 무엇인가요?&lt;span style=&quot;color: #666666;&quot; data-astro-cid-oqjbs5yv=&quot;&quot; data-icon-open=&quot;true&quot; aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;
&lt;div id=&quot;accordion_uth81o99z_panel_1&quot; data-astro-cid-oqjbs5yv=&quot;&quot; data-open=&quot;true&quot;&gt;
&lt;div style=&quot;color: #666666;&quot; data-astro-cid-oqjbs5yv=&quot;&quot;&gt;
&lt;p data-astro-cid-oqjbs5yv=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;Kanana-o 신규 모델을 제한된 인원에게 API를 제공하는 클로즈드 베타 테스트(Closed Beta Test)입니다. 최신 모델을 무료로 체험해 보실 수 있으나, 베타 특성상 이용량에 제한이 있고 서비스 안정성이 변동될 수 있는 점 양해 부탁드립니다.&lt;/p&gt;
&lt;p data-astro-cid-oqjbs5yv=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-astro-cid-oqjbs5yv=&quot;&quot; data-item-id=&quot;2&quot;&gt;Kanana-o 모델(API)로 무엇을 할 수 있나요?&lt;/div&gt;
&lt;div data-astro-cid-oqjbs5yv=&quot;&quot; data-item-id=&quot;2&quot;&gt;&lt;span style=&quot;color: #666666;&quot; data-astro-cid-oqjbs5yv=&quot;&quot; data-icon-open=&quot;true&quot; aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;
&lt;div id=&quot;accordion_uth81o99z_panel_2&quot; data-astro-cid-oqjbs5yv=&quot;&quot; data-open=&quot;true&quot;&gt;
&lt;div style=&quot;color: #666666;&quot; data-astro-cid-oqjbs5yv=&quot;&quot;&gt;Kanana-o는 텍스트, 음성, 이미지를 통합적으로 이해하고 생성할 수 있는 최신 통합 멀티모달 언어모델로, 사람에 가까운 자연스러운 발화와 향상된 지시 이행 능력을 갖추고 있습니다. 단순한 벤치마크 성능을 넘어 실제 사용자 경험을 개선하는 데 초점을 맞춘 실용적인 기술을 제공하며, 자세한 모델 구조 및 스펙은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0a73dc;&quot; href=&quot;https://tech.kakao.com/posts/802&quot;&gt;카카오 기술 블로그&lt;/a&gt;에서 확인하실 수 있습니다.&lt;/div&gt;
&lt;div style=&quot;color: #666666;&quot; data-astro-cid-oqjbs5yv=&quot;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-astro-cid-oqjbs5yv=&quot;&quot; data-item-id=&quot;3&quot;&gt;베타 테스터 선정 기준은 무엇인가요?&lt;span style=&quot;color: #666666;&quot; data-astro-cid-oqjbs5yv=&quot;&quot; data-icon-open=&quot;true&quot; aria-hidden=&quot;true&quot;&gt;&lt;/span&gt;
&lt;div id=&quot;accordion_uth81o99z_panel_3&quot; data-astro-cid-oqjbs5yv=&quot;&quot; data-open=&quot;true&quot;&gt;
&lt;div style=&quot;color: #666666;&quot; data-astro-cid-oqjbs5yv=&quot;&quot;&gt;단순한 호기심보다는 구체적인 활용 시나리오와 기술적 구현 역량을 갖추고, 적극적으로 피드백을 주실 수 있는 분을 우선하여 선정합니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;Kanana-o를 활용해 실질적인 가치를 창출하고, 초기 팬덤으로서 함께 성장할 개발자분들의 많은 지원 바랍니다.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/AI</category>
      <category>API</category>
      <category>Kanana</category>
      <category>Kanana-o</category>
      <category>llm</category>
      <category>멀티모달</category>
      <category>베타테스트</category>
      <category>카나나</category>
      <category>카나나오</category>
      <category>카카오</category>
      <category>한국어ai</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/195</guid>
      <comments>https://snapcode.tistory.com/entry/AI-Kanana-o-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%8F%85%EC%9E%90-%EA%B0%9C%EB%B0%9C-%EC%98%B4%EB%8B%88Omni-%EB%AA%A8%EB%8D%B8-%EA%B3%BC%EC%97%B0-%EC%82%AC%EB%9E%8C%EA%B3%BC-%EC%96%BC%EB%A7%88%EB%82%98-%EA%B0%80%EA%B9%8C%EC%9A%B8%EA%B9%8C#entry195comment</comments>
      <pubDate>Fri, 20 Feb 2026 19:31:02 +0900</pubDate>
    </item>
    <item>
      <title>[AI] 카카오 AI 앰배서더, AI를 소비하면서 동시에 제품으로 만드는 개발자.</title>
      <link>https://snapcode.tistory.com/entry/AI-%EC%B9%B4%EC%B9%B4%EC%98%A4-AI-%EC%95%B0%EB%B0%B0%EC%84%9C%EB%8D%94-AI%EB%A5%BC-%EC%86%8C%EB%B9%84%ED%95%98%EB%A9%B4%EC%84%9C-%EB%8F%99%EC%8B%9C%EC%97%90-%EC%A0%9C%ED%92%88%EC%9C%BC%EB%A1%9C-%EB%A7%8C%EB%93%9C%EB%8A%94-%EA%B0%9C%EB%B0%9C%EC%9E%90</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://tech.kakao.com/posts/809&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://tech.kakao.com/posts/809&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772902331575&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;카카오 AI 앰배서더 &amp;lsquo;KANANA 429 앰배서더&amp;rsquo;를 신규 모집합니다. - tech.kakao.com&quot; data-og-description=&quot;이 글은 카카오 공식 보도자료로, 기술블로그에 동시 게재합니다. KANANA...&quot; data-og-host=&quot;tech.kakao.com&quot; data-og-source-url=&quot;https://tech.kakao.com/posts/809&quot; data-og-url=&quot;https://tech.kakao.com/posts/809&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Ctl8i/dJMb82MA4UM/ZWaTwL9ZQ4zfjSjlCYdWmK/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504,https://scrap.kakaocdn.net/dn/3LiJP/dJMb83kqUTN/jQJdlHsnfSt2oB2KLuUw00/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504,https://scrap.kakaocdn.net/dn/Hc8gr/dJMb87NUoa5/MRrmT1vkrTHlXIkYnqCrm0/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504&quot;&gt;&lt;a href=&quot;https://tech.kakao.com/posts/809&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://tech.kakao.com/posts/809&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Ctl8i/dJMb82MA4UM/ZWaTwL9ZQ4zfjSjlCYdWmK/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504,https://scrap.kakaocdn.net/dn/3LiJP/dJMb83kqUTN/jQJdlHsnfSt2oB2KLuUw00/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504,https://scrap.kakaocdn.net/dn/Hc8gr/dJMb87NUoa5/MRrmT1vkrTHlXIkYnqCrm0/img.png?width=896&amp;amp;height=504&amp;amp;face=0_0_896_504');&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;카카오 AI 앰배서더 &amp;lsquo;KANANA 429 앰배서더&amp;rsquo;를 신규 모집합니다. - tech.kakao.com&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 글은 카카오 공식 보도자료로, 기술블로그에 동시 게재합니다. KANANA...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;tech.kakao.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;어느날 &quot;카카오 AI 앰배서더&quot; 공개 모집 알림톡이 날라왔습니다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;br /&gt;AI를 실사용 및 개발하고있는 요즘, 외부 커뮤니티 활동에도 참여하면 좋을 것 같다&lt;/u&gt;고 생각하여 신청했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;642&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lu2wO/dJMcagxICDe/0HJRF4QKf4EhGtQLa9gHBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lu2wO/dJMcagxICDe/0HJRF4QKf4EhGtQLa9gHBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lu2wO/dJMcagxICDe/0HJRF4QKf4EhGtQLa9gHBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flu2wO%2FdJMcagxICDe%2F0HJRF4QKf4EhGtQLa9gHBK%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;362&quot; height=&quot;642&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;642&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이왕이면&amp;nbsp;내부&amp;nbsp;프로젝트에만&amp;nbsp;머무르지&amp;nbsp;않고,&amp;nbsp;&amp;nbsp; &lt;br /&gt;외부&amp;nbsp;커뮤니티&amp;nbsp;활동을&amp;nbsp;통해&amp;nbsp;더&amp;nbsp;넓은&amp;nbsp;관점에서&amp;nbsp;AI를&amp;nbsp;다뤄보고&amp;nbsp;싶었습니다.&amp;nbsp;&amp;nbsp; &lt;br /&gt;그래서&amp;nbsp;지원했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 안보이지만, 3가지 중에 하나를 선택해야 하더군요. &lt;br /&gt;지원&amp;nbsp;분야는&amp;nbsp;세&amp;nbsp;가지였습니다. &lt;br /&gt;&lt;br /&gt;-&amp;nbsp;AI&amp;nbsp;전문가&amp;nbsp;&amp;nbsp; &lt;br /&gt;-&amp;nbsp;콘텐츠&amp;nbsp;크리에이터&amp;nbsp;&amp;nbsp; &lt;br /&gt;-&amp;nbsp;대학생&amp;nbsp;&amp;nbsp; &lt;br /&gt;&lt;br /&gt;저는 AI 전문가를 선택했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pDrLt/dJMb99L7Neb/UfEeVKqiFAVXrWjIAk0GYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pDrLt/dJMb99L7Neb/UfEeVKqiFAVXrWjIAk0GYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pDrLt/dJMb99L7Neb/UfEeVKqiFAVXrWjIAk0GYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpDrLt%2FdJMb99L7Neb%2FUfEeVKqiFAVXrWjIAk0GYk%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;555&quot; height=&quot;312&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카카오 AI 앰배서더는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;598&quot; data-start=&quot;576&quot;&gt;최신 AI 기술을 실무에 연결하거나&lt;/li&gt;
&lt;li data-end=&quot;634&quot; data-start=&quot;599&quot;&gt;실제 서비스 관점에서 활용 사례를 만들 수 있는 기회이며&lt;/li&gt;
&lt;li data-end=&quot;674&quot; data-start=&quot;635&quot;&gt;같은 방향성을 가진 개발자들과 교류할 수 있는 장이라고 생각합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/AI</category>
      <category>429</category>
      <category>ai</category>
      <category>Kanana</category>
      <category>KANANA429</category>
      <category>llm</category>
      <category>MCP</category>
      <category>아이디어</category>
      <category>카나나</category>
      <category>카나나429</category>
      <category>카카오 AI 앰배서더</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/194</guid>
      <comments>https://snapcode.tistory.com/entry/AI-%EC%B9%B4%EC%B9%B4%EC%98%A4-AI-%EC%95%B0%EB%B0%B0%EC%84%9C%EB%8D%94-AI%EB%A5%BC-%EC%86%8C%EB%B9%84%ED%95%98%EB%A9%B4%EC%84%9C-%EB%8F%99%EC%8B%9C%EC%97%90-%EC%A0%9C%ED%92%88%EC%9C%BC%EB%A1%9C-%EB%A7%8C%EB%93%9C%EB%8A%94-%EA%B0%9C%EB%B0%9C%EC%9E%90#entry194comment</comments>
      <pubDate>Fri, 20 Feb 2026 19:20:26 +0900</pubDate>
    </item>
    <item>
      <title>[MCP] JSON-RPC 2.0 API 동적 대시보드 만들기: Swagger처럼 브라우저에서 Tool 테스트하기</title>
      <link>https://snapcode.tistory.com/entry/MCP-JSON-RPC-20-%EA%B8%B0%EB%B0%98-API-%EB%8C%80%EC%8B%9C%EB%B3%B4%EB%93%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0-Swagger%EC%B2%98%EB%9F%BC-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EC%97%90%EC%84%9C-Tool-%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[MCP]&amp;nbsp;JSON-RPC&amp;nbsp;2.0&amp;nbsp;API&amp;nbsp;동적&amp;nbsp;대시보드&amp;nbsp;만들기:&amp;nbsp;Swagger처럼&amp;nbsp;브라우저에서&amp;nbsp;Tool&amp;nbsp;테스트하기&lt;/p&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;827&quot; data-origin-height=&quot;996&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNMTSY/dJMcahi8c7o/JK6rlXvvr4dg7lN1uKgG50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNMTSY/dJMcahi8c7o/JK6rlXvvr4dg7lN1uKgG50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNMTSY/dJMcahi8c7o/JK6rlXvvr4dg7lN1uKgG50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNMTSY%2FdJMcahi8c7o%2FJK6rlXvvr4dg7lN1uKgG50%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;827&quot; height=&quot;996&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;827&quot; data-origin-height=&quot;996&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST API 서버를 개발할 때 Swagger UI가 얼마나 유용한지 알 것입니다. 요청/응답을 시각적으로 확인하고 바로 테스트할 수 있으니까요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 &lt;b&gt;JSON-RPC 2.0 프로토콜&lt;/b&gt; 기반 MCP 서버를 만들면서 같은 편의성이 필요했습니다. PlayMCP 클라이언트도 좋지만, 개발 중에는 빠른 디버깅이 필요하거든요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 만들었습니다: &lt;b&gt;GET /api/mcp 요청 시 동적으로 생성되는 인터랙티브 HTML 대시보드&lt;/b&gt;.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제: 프로토콜이 다르면 테스트 도구도 달라진다&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Swagger UI&lt;/b&gt;: REST API용 (각 엔드포인트별 문서화)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Postman&lt;/b&gt;: JSON-RPC 2.0? 복잡한 요청 포매팅 필수&lt;/li&gt;
&lt;li&gt;&lt;b&gt;우리의 MCP 서버&lt;/b&gt;: Tool 목록, 입력 스키마, 실행 폼이 동적임&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; &lt;b&gt;자동 생성되는 테스트 페이지&lt;/b&gt;가 필요했습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;솔루션: 브라우저 기반 인터랙티브 테스트 대시보드&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;아키텍처&lt;/h3&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;GET https://example.com/api/mcp
  &amp;darr;
브라우저(text/html Accept)인지 확인
  &amp;darr;
└&amp;rarr; Yes: HTML 대시보드 반환
└&amp;rarr; No: SSE 스트림 반환&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;핵심 구현&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1️⃣ 브라우저 vs API 클라이언트 구분&lt;/h4&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;@GetMapping
public Object streamMessages(HttpServletRequest httpRequest) {
    String acceptHeader = httpRequest.getHeader(&quot;Accept&quot;);
    boolean isBrowserRequest = acceptHeader != null &amp;amp;&amp;amp; 
                               acceptHeader.contains(&quot;text/html&quot;);

    if (isBrowserRequest) {
        String htmlContent = generateToolsDashboard();
        return ResponseEntity.ok()
            .header(&quot;Content-Type&quot;, &quot;text/html; charset=UTF-8&quot;)
            .body(htmlContent);
    }
    // API 클라이언트용 SSE 스트림...
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2️⃣ 동적 HTML 생성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등록된 모든 Tool을 읽어서 각각 카드로 렌더링:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;private String generateToolsDashboard() {
    List&amp;lt;Tool&amp;gt; tools = ToolRegistry.getAllTools();

    // 헤더: 서버 정보, 상태 표시
    // Tool 섹션: 등록된 Tool 목록 동적 생성
    // 각 Tool별 카드:
    //   - 설명
    //   - 입력 스키마 (JSON)
    //   - 실행 폼 (동적 입력창)
    //   - 요청 예제
    //   - 결과 표시 영역
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3️⃣ 각 Tool마다 실행 폼 자동 생성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입력 스키마&lt;/b&gt;에서 매개변수 추출:&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;private String generateExecutionForm(
    String toolName, 
    Map&amp;lt;String, Object&amp;gt; inputSchema) {

    // inputSchema.get(&quot;properties&quot;)에서 필드 정보 추출
    // for each field:
    //   - 타입에 따라 &amp;lt;input type=&quot;text&quot;&amp;gt; 또는 &amp;lt;input type=&quot;number&quot;&amp;gt;
    //   - required 여부 확인
    //   - placeholder로 설명 표시
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과 HTML (예시):&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;form onsubmit=&quot;event.preventDefault(); executeTool('search_jobs');&quot;&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;label&amp;gt;keyword &amp;lt;span style=&quot;color:red&quot;&amp;gt;*&amp;lt;/span&amp;gt;&amp;lt;/label&amp;gt;
        &amp;lt;input type=&quot;text&quot; name=&quot;keyword&quot; required /&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
        &amp;lt;label&amp;gt;limit&amp;lt;/label&amp;gt;
        &amp;lt;input type=&quot;number&quot; name=&quot;limit&quot; /&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;button type=&quot;submit&quot;&amp;gt;  실행&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4️⃣ 브라우저에서 Tool 실행&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaScript로 &lt;b&gt;JSON-RPC 2.0 요청&lt;/b&gt; 생성 후 호출:&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function executeTool(toolName) {
    // 폼 데이터 &amp;rarr; arguments 객체 변환
    const arguments_ = {};
    for (let [key, value] of formData.entries()) {
        arguments_[key] = isNaN(value) ? value : Number(value);
    }

    // JSON-RPC 2.0 포맷
    const request = {
        jsonrpc: '2.0',
        method: 'tools/call',
        params: {
            name: toolName,
            arguments: arguments_
        },
        id: 1
    };

    // POST /api/mcp/call_tool
    fetch('/api/mcp/call_tool', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(request)
    })
    .then(res =&amp;gt; res.json())
    .then(data =&amp;gt; {
        // 결과 표시 (✅ 성공 / ❌ 오류)
        // 실행 시간 표시
        // JSON 포맷팅된 결과 표시
    });
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;UI 디자인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그라데이션 배경&lt;/b&gt; + &lt;b&gt;깔끔한 카드 레이아웃&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;body {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}

.tool-card {
    background: #f9fafb;
    border-radius: 8px;
    padding: 25px;
    transition: all 0.3s ease;
}

.tool-card:hover {
    border-color: #667eea;
    box-shadow: 0 5px 15px rgba(102, 126, 234, 0.1);
}

.execution-panel {
    background: #f0f4ff;
    border: 2px solid #667eea;
    padding: 20px;
}

.execute-button {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    padding: 12px 24px;
    border-radius: 6px;
    font-weight: 600;
}

.execute-button:hover {
    transform: translateY(-2px);
    box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;접속 방법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ 서버 실행&lt;/h3&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;./gradlew bootRun&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ 브라우저 접속&lt;/h3&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;https://sample-domain.example/api/mcp&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ 대시보드 확인&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버 상태 정보 (프로토콜, 버전, Tool 개수)&lt;/li&gt;
&lt;li&gt;등록된 모든 Tool 목록&lt;/li&gt;
&lt;li&gt;각 Tool별 설명, 스키마, 테스트 폼&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4️⃣ Tool 실행&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;폼에 파라미터 입력&lt;/li&gt;
&lt;li&gt;&lt;b&gt;  실행&lt;/b&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;✅ 성공: 실행 시간 + JSON 결과&lt;/li&gt;
&lt;li&gt;❌ 오류: 에러 메시지 + 상세 정보&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기술 스택&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;요소&lt;/th&gt;
&lt;th&gt;기술&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;프로토콜&lt;/td&gt;
&lt;td&gt;JSON-RPC 2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;템플릿&lt;/td&gt;
&lt;td&gt;동적 StringBuilder HTML 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;스타일&lt;/td&gt;
&lt;td&gt;인라인 CSS (별도 파일 없음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;스크립팅&lt;/td&gt;
&lt;td&gt;Vanilla JavaScript (라이브러리 불필요)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;마크업&lt;/td&gt;
&lt;td&gt;HTML5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 장점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;개발자 친화적&lt;/b&gt;: 별도 클라이언트 없이 브라우저로 즉시 테스트&lt;br /&gt;✅ &lt;b&gt;자동 동기화&lt;/b&gt;: Tool 등록/수정 시 자동으로 대시보드 갱신&lt;br /&gt;✅ &lt;b&gt;시각적 피드백&lt;/b&gt;: 실행 시간, 상태, 오류 메시지 한눈에 파악&lt;br /&gt;✅ &lt;b&gt;학습 자료&lt;/b&gt;: 각 Tool의 입력 포맷, 요청 예제 자동 생성&lt;br /&gt;✅ &lt;b&gt;프로덕션 배포&lt;/b&gt;: 경량 (외부 라이브러리 불필요)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주의사항&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ &lt;b&gt;개발/테스트 목적&lt;/b&gt;: 프로덕션에서 API 인터페이스는 보호 필요&lt;br /&gt;⚠️ &lt;b&gt;Origin 검증&lt;/b&gt;: CORS, DNS Rebinding 공격 방지 로직 포함&lt;br /&gt;⚠️ &lt;b&gt;타임아웃&lt;/b&gt;: 긴 작업은 30초 제한 설정&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Swagger UI 없이도&lt;/b&gt; JSON-RPC 2.0 기반 API를 효과적으로 테스트할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저만으로 Tool 목록 확인, 파라미터 입력, 결과 확인까지 한 번에. 개발 속도가 빨라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/AI</category>
      <category>API</category>
      <category>JsonRPC</category>
      <category>MCP</category>
      <category>swagger</category>
      <category>개발도구</category>
      <category>대시보드</category>
      <category>백엔드</category>
      <category>브라우저</category>
      <category>자동생성</category>
      <category>테스트</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/193</guid>
      <comments>https://snapcode.tistory.com/entry/MCP-JSON-RPC-20-%EA%B8%B0%EB%B0%98-API-%EB%8C%80%EC%8B%9C%EB%B3%B4%EB%93%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0-Swagger%EC%B2%98%EB%9F%BC-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EC%97%90%EC%84%9C-Tool-%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%95%98%EA%B8%B0#entry193comment</comments>
      <pubDate>Fri, 20 Feb 2026 02:24:37 +0900</pubDate>
    </item>
    <item>
      <title>[Backend] SSE vs WebSocket: 실시간 통신 방식 언제 뭘 쓸까?</title>
      <link>https://snapcode.tistory.com/entry/Backend-REST-vs-SSE-vs-WebSocket-%EC%8B%A4%EC%8B%9C%EA%B0%84-%ED%86%B5%EC%8B%A0-%EB%B0%A9%EC%8B%9D-%EC%96%B8%EC%A0%9C-%EB%AD%98-%EC%93%B8%EA%B9%8C</link>
      <description>&lt;p&gt;[Backend] SSE vs WebSocket: 실시간 통신 방식 언제 뭘 쓸까?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;976&quot; data-origin-height=&quot;782&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TZhq0/dJMcad10BIi/cAFTMsAyknXAEEHuNFCFVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TZhq0/dJMcad10BIi/cAFTMsAyknXAEEHuNFCFVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TZhq0/dJMcad10BIi/cAFTMsAyknXAEEHuNFCFVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTZhq0%2FdJMcad10BIi%2FcAFTMsAyknXAEEHuNFCFVk%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;444&quot; height=&quot;356&quot; data-origin-width=&quot;976&quot; data-origin-height=&quot;782&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2&gt;  빠른 비교표&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;SSE&lt;/th&gt;
&lt;th&gt;WebSocket&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;프로토콜&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;HTTP&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;통신 방향&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;단방향 (서버→클라이언트)&lt;/td&gt;
&lt;td&gt;양방향 (전이중)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;연결 유지&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;HTTP 연결 유지&lt;/td&gt;
&lt;td&gt;별도 소켓 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;자동 재연결&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ (기본 내장)&lt;/td&gt;
&lt;td&gt;❌ (수동 구현)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CORS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;간단&lt;/td&gt;
&lt;td&gt;복잡&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;구현 복잡도&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;쉬움&lt;/td&gt;
&lt;td&gt;어려움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;리소스 사용&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;중간&lt;/td&gt;
&lt;td&gt;많음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;  언제 뭘 쓸까?&lt;/h2&gt;
&lt;h3&gt;1️⃣ SSE (Server-Sent Events)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;서버가 일방적으로 계속 밀어주기&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Usecase:
- 장시간 작업의 진행상황 표시 (파일 업로드, 데이터 처리)
- 실시간 알림 (새 메시지, 주문 상태 변경)
- 라이브 피드 (실시간 뉴스, 대시보드)

흐름:
[클라이언트] --연결 유지--&amp;gt; [서버]
                           ├─ [0.5초] 진행 상황 1 푸시
                           ├─ [1.5초] 진행 상황 2 푸시
                           └─ [5초] 최종 결과 푸시&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2️⃣ WebSocket&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&amp;quot;양쪽이 자유롭게 대화하기&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Usecase:
- 채팅 (양방향 메시지)
- 온라인 게임 (실시간 플레이어 위치)
- 협업 도구 (실시간 문서 편집)
- 실시간 양방향 상호작용

흐름:
[클라이언트] &amp;lt;--양방향--&amp;gt; [서버]
  ├─ &amp;quot;검색해줘&amp;quot; ─────────────&amp;gt;
  │                         [처리 중]
  &amp;lt;──────── &amp;quot;진행: 50%&amp;quot; ←────┤
  &amp;quot;음, 다시 검색&amp;quot; ─────────────&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;  코드 예시&lt;/h2&gt;
&lt;h3&gt;SSE (Server-Sent Events)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;서버 (Spring Boot)&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@RestController
public class StreamController {
    @GetMapping(&amp;quot;/api/stream-search&amp;quot;)
    public SseEmitter search() {
        SseEmitter emitter = new SseEmitter(60 * 1000L);
        new Thread(() -&amp;gt; {
            try {
                // [0.5초] 진행 상황 1
                emitter.send(SseEmitter.event()
                    .id(&amp;quot;progress&amp;quot;)
                    .data(&amp;quot;{\&amp;quot;loaded\&amp;quot;: 100}&amp;quot;));
                Thread.sleep(500);
                // [1초] 진행 상황 2
                emitter.send(SseEmitter.event()
                    .id(&amp;quot;progress&amp;quot;)
                    .data(&amp;quot;{\&amp;quot;loaded\&amp;quot;: 200}&amp;quot;));
                // [5초] 최종 결과
                emitter.send(SseEmitter.event()
                    .id(&amp;quot;complete&amp;quot;)
                    .data(&amp;quot;{\&amp;quot;results\&amp;quot;: [...]}&amp;quot;));
                emitter.complete();
            } catch (IOException e) {
                emitter.completeWithError(e);
            }
        }).start();
        return emitter;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;클라이언트 (JavaScript)&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const es = new EventSource(&amp;#39;/api/stream-search&amp;#39;);
es.addEventListener(&amp;#39;progress&amp;#39;, (event) =&amp;gt; {
    const data = JSON.parse(event.data);
    console.log(`진행: ${data.loaded}개`);  // [중간중간 받음]
    updateUI(data.loaded);
});
es.addEventListener(&amp;#39;complete&amp;#39;, (event) =&amp;gt; {
    const result = JSON.parse(event.data);
    console.log(&amp;#39;완료:&amp;#39;, result.results);  // [최종 결과]
    es.close();
});&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h3&gt;WebSocket&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;서버 (Spring Boot)&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new MessageHandler(), &amp;quot;/ws/app&amp;quot;).setAllowedOrigins(&amp;quot;*&amp;quot;);
    }
}

@Component
public class MessageHandler extends TextWebSocketHandler {
    private Set&amp;lt;WebSocketSession&amp;gt; sessions = new HashSet&amp;lt;&amp;gt;();

    //   클라이언트에서 메시지 받기
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) {
        String msg = message.getPayload();  // 클라이언트로부터 받은 메시지
        System.out.println(&amp;quot;  받음: &amp;quot; + msg);

        //   모든 연결된 세션에 메시지 보내기
        for (WebSocketSession s : sessions) {
            try {
                s.sendMessage(new TextMessage(msg));  // 메시지 전송
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        sessions.add(session);  // 새 연결 등록
        System.out.println(&amp;quot;✅ 연결됨: &amp;quot; + session.getId());
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        sessions.remove(session);  // 연결 제거
        System.out.println(&amp;quot;❌ 연결 종료: &amp;quot; + session.getId());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;클라이언트 (JavaScript)&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const ws = new WebSocket(&amp;#39;ws://sample-server.example.com/ws/app&amp;#39;);

// ✅ 연결 수립
ws.onopen = () =&amp;gt; {
    console.log(&amp;#39;✅ 소켓 연결됨&amp;#39;);

    //   서버에 메시지 보내기
    ws.send(JSON.stringify({message: &amp;#39;hello&amp;#39;}));
    console.log(&amp;#39;  보냄: hello&amp;#39;);
};

//   서버에서 메시지 받기
ws.onmessage = (event) =&amp;gt; {
    const data = JSON.parse(event.data);
    console.log(&amp;#39;  받음:&amp;#39;, data.message);  // 서버로부터 받은 메시지
    updateUI(data.message);
};

// ❌ 연결 종료
ws.onerror = (error) =&amp;gt; {
    console.error(&amp;#39;❌ 에러:&amp;#39;, error);
};

ws.onclose = () =&amp;gt; {
    console.log(&amp;#39;❌ 연결 종료됨&amp;#39;);
};

// 언제든 메시지 보낼 수 있음 (클라이언트 주도)
setTimeout(() =&amp;gt; {
    ws.send(JSON.stringify({message: &amp;#39;again&amp;#39;}));  //   다시 보내기
}, 2000);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;  핵심 차이점&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;SSE&lt;/th&gt;
&lt;th&gt;WebSocket&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;연결 수명&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;유지&lt;/td&gt;
&lt;td&gt;유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;데이터 흐름&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;단방향&lt;/td&gt;
&lt;td&gt;양방향&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;브라우저 지원&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;IE 미지원&lt;/td&gt;
&lt;td&gt;대부분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;메모리 사용&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;중간&lt;/td&gt;
&lt;td&gt;많음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;지연시간&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;  실전 의사결정 플로우&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;quot;실시간 양방향 통신 필요?&amp;quot;
  ├─ [NO] → SSE ✅ (진행상황, 알림)
  └─ [YES] → WebSocket ✅ (채팅, 게임)&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;  실제 개발 팁&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;SSE 선택&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;장시간 작업의 진행상황 (파일 업로드, 데이터 처리)&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;p&gt;&lt;strong&gt;WebSocket 선택&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;채팅, 협업 도구&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;hr&gt;</description>
      <category>IT/java|Spring</category>
      <category>API</category>
      <category>MCP</category>
      <category>Spring</category>
      <category>SSE</category>
      <category>WebSocket</category>
      <category>백엔드</category>
      <category>실시간통신</category>
      <category>웹개발</category>
      <category>프로토콜</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/192</guid>
      <comments>https://snapcode.tistory.com/entry/Backend-REST-vs-SSE-vs-WebSocket-%EC%8B%A4%EC%8B%9C%EA%B0%84-%ED%86%B5%EC%8B%A0-%EB%B0%A9%EC%8B%9D-%EC%96%B8%EC%A0%9C-%EB%AD%98-%EC%93%B8%EA%B9%8C#entry192comment</comments>
      <pubDate>Wed, 18 Feb 2026 23:43:29 +0900</pubDate>
    </item>
    <item>
      <title>[MCP] Gemini + LangChain4j 활용하여 검색 기능 개선</title>
      <link>https://snapcode.tistory.com/entry/MCP-Gemini-LangChain4j-%ED%99%9C%EC%9A%A9%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[ MCP Inspertor Search Result ]&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;897&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n2sq2/dJMcaaRP1vQ/dNhMwItXvV4aFRFLA73ju1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n2sq2/dJMcaaRP1vQ/dNhMwItXvV4aFRFLA73ju1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n2sq2/dJMcaaRP1vQ/dNhMwItXvV4aFRFLA73ju1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn2sq2%2FdJMcaaRP1vQ%2FdNhMwItXvV4aFRFLA73ju1%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;777&quot; height=&quot;749&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;897&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 검색은 정해진 SQL로 단순 키워드만 조회했습니다. 이번에 &lt;b&gt;Gemini AI(무료 플랜) + LangChain4j&lt;/b&gt;를 결합하여 자연어를 JSON으로 변환한 후, 동적 SQL을 안전하게 생성하는 시스템을 구현했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;핵심 개선사항&lt;/h3&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;: 고정 SQL, WHERE 조건 사전 정의&lt;/li&gt;
&lt;li&gt;&lt;b&gt;개선&lt;/b&gt;: 자연어 &amp;rarr; LLM 분석 &amp;rarr; JSON 추출 &amp;rarr; 동적 SQL (유연성 &amp;uarr;)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;아키텍처 흐름&lt;/h2&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;사용자 입력 (자연어)
    &amp;darr;
[Gemini 2.0 Flash] 검색 조건 분석
    &amp;darr;
JSON 파싱 (필터 조건 구조화)
    &amp;darr;
[PreparedStatement] 안전한 SQL 조립
    &amp;darr;
데이터베이스 실행 &amp;rarr; 결과 반환&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실제 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입력&lt;/b&gt;: &lt;code&gt;&quot;금융 회사의 5년 이상 경력 개발자 공고 10개&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;LLM 변환 JSON&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;company&quot;: &quot;금융 핀테크 결제&quot;,
  &quot;experience_level&quot;: &quot;5년 이상 경력&quot;,
  &quot;keyword&quot;: &quot;금융 결제 핀테크 개발자&quot;,
  &quot;limit&quot;: 10
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동적 생성 SQL&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;SELECT t.id, t.title, t.link, c.name as company_name,
       d.level, d.job_type, d.description
FROM job t
LEFT JOIN detail d ON t.id = d.job_id
LEFT JOIN company c ON t.company_id = c.id
WHERE t.status != 'DELETED'
  AND (c.name ILIKE '%금융%' OR c.alias ILIKE '%금융%')
  AND (t.title ILIKE '%개발자%' OR d.description ILIKE '%개발자%')
ORDER BY t.created_at DESC
LIMIT 10&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 구현: 3단계 파이프라인&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 1: LLM이 자연어를 JSON으로 변환&lt;/h3&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;private String generateSearchCriteria(String userInput) {
    String prompt = &quot;&quot;&quot;
        사용자의 자연어 검색을 JSON으로 변환하세요.

        동의어 확장 예시:
        - &quot;금융&quot; &amp;rarr; &quot;금융 핀테크 결제 송금&quot;
        - &quot;헬스&quot; &amp;rarr; &quot;헬스 의료 건강 웰니스&quot;
        - &quot;게임&quot; &amp;rarr; &quot;게임 엔터테인먼트 게이밍&quot;

        JSON 형식:
        {
          &quot;company&quot;: &quot;회사명 또는 null&quot;,
          &quot;experience_level&quot;: &quot;경력요건 또는 null&quot;,
          &quot;keyword&quot;: &quot;검색어 + 동의어&quot;,
          &quot;limit&quot;: 10
        }

        입력: &quot;&quot;&quot; + userInput;

    return chatModel.chat(ChatRequest.builder()
        .messages(UserMessage.from(prompt))
        .build()).aiMessage().text();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Gemini 2.0 Flash (빠른 응답)&lt;/li&gt;
&lt;li&gt;자동 동의어 확장&lt;/li&gt;
&lt;li&gt;JSON 응답만 추출&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 2: JSON 파싱 및 검증&lt;/h3&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;private Map&amp;lt;String, Object&amp;gt; parseSearchCriteria(String json) throws Exception {
    JsonNode node = objectMapper.readTree(json);
    Map&amp;lt;String, Object&amp;gt; criteria = new HashMap&amp;lt;&amp;gt;();

    criteria.put(&quot;company&quot;, node.has(&quot;company&quot;) ? 
        node.get(&quot;company&quot;).asText() : null);
    criteria.put(&quot;experience_level&quot;, node.has(&quot;experience_level&quot;) ? 
        node.get(&quot;experience_level&quot;).asText() : null);
    criteria.put(&quot;keyword&quot;, node.has(&quot;keyword&quot;) ? 
        node.get(&quot;keyword&quot;).asText() : null);
    criteria.put(&quot;limit&quot;, node.has(&quot;limit&quot;) ? 
        node.get(&quot;limit&quot;).asInt() : 10);

    return criteria;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Step 3: PreparedStatement로 안전한 SQL 생성&lt;/h3&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;private List&amp;lt;Map&amp;lt;String, Object&amp;gt;&amp;gt; buildAndExecuteQuery(Map&amp;lt;String, Object&amp;gt; criteria) 
        throws SQLException {

    validateInput(criteria);  // 길이/범위 검증

    StringBuilder sql = new StringBuilder();
    sql.append(&quot;SELECT t.id, t.title, t.link, c.name as company_name, &quot;);
    sql.append(&quot;       d.level, d.job_type, d.description, t.created_at &quot;);
    sql.append(&quot;FROM job t &quot;);
    sql.append(&quot;LEFT JOIN detail d ON t.id = d.job_id &quot;);
    sql.append(&quot;LEFT JOIN company c ON t.company_id = c.id &quot;);
    sql.append(&quot;WHERE t.status != 'DELETED' &quot;);

    List&amp;lt;Object&amp;gt; params = new ArrayList&amp;lt;&amp;gt;();

    // 회사명 필터
    String company = (String) criteria.get(&quot;company&quot;);
    if (company != null &amp;amp;&amp;amp; !company.isEmpty()) {
        sql.append(&quot;AND (c.name ILIKE ? OR c.alias ILIKE ?) &quot;);
        params.add(&quot;%&quot; + company.trim() + &quot;%&quot;);
        params.add(&quot;%&quot; + company.trim() + &quot;%&quot;);
    }

    // 키워드 필터 (공백 분할 &amp;rarr; OR)
    String keyword = (String) criteria.get(&quot;keyword&quot;);
    if (keyword != null &amp;amp;&amp;amp; !keyword.isEmpty()) {
        String[] words = keyword.trim().split(&quot;\\s+&quot;);
        sql.append(&quot;AND (&quot;);
        for (int i = 0; i &amp;lt; words.length; i++) {
            if (i &amp;gt; 0) sql.append(&quot; OR &quot;);
            sql.append(&quot;(t.title ILIKE ? OR d.description ILIKE ?)&quot;);
            params.add(&quot;%&quot; + words[i] + &quot;%&quot;);
            params.add(&quot;%&quot; + words[i] + &quot;%&quot;);
        }
        sql.append(&quot;) &quot;);
    }

    int limit = (Integer) criteria.getOrDefault(&quot;limit&quot;, 10);
    sql.append(&quot;ORDER BY t.created_at DESC LIMIT ?&quot;);
    params.add(limit);

    // 쿼리 실행
    try (Connection conn = dataSource.getConnection();
         PreparedStatement pstmt = conn.prepareStatement(sql.toString())) {

        pstmt.setQueryTimeout(5);
        for (int i = 0; i &amp;lt; params.size(); i++) {
            Object param = params.get(i);
            if (param instanceof String) {
                pstmt.setString(i + 1, (String) param);
            } else if (param instanceof Integer) {
                pstmt.setInt(i + 1, (Integer) param);
            }
        }

        List&amp;lt;Map&amp;lt;String, Object&amp;gt;&amp;gt; results = new ArrayList&amp;lt;&amp;gt;();
        try (ResultSet rs = pstmt.executeQuery()) {
            while (rs.next()) {
                Map&amp;lt;String, Object&amp;gt; row = new HashMap&amp;lt;&amp;gt;();
                ResultSetMetaData meta = rs.getMetaData();
                for (int i = 1; i &amp;lt;= meta.getColumnCount(); i++) {
                    row.put(meta.getColumnLabel(i), rs.getObject(i));
                }
                results.add(row);
            }
        }
        return results;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;보안 기능&lt;/b&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;code&gt;PreparedStatement&lt;/code&gt; 사용 (SQL Injection 방지)&lt;/li&gt;
&lt;li&gt;입력 길이 검증 (100자 제한)&lt;/li&gt;
&lt;li&gt;limit 범위 제한 (최대 20)&lt;/li&gt;
&lt;li&gt;5초 타임아웃&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기술 스택&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구성&lt;/th&gt;
&lt;th&gt;기술&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;LLM&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Gemini 2.0 Flash&lt;/td&gt;
&lt;td&gt;무료 플랜, 빠른 응답&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;라이브러리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;LangChain4j&lt;/td&gt;
&lt;td&gt;Java LLM 통합&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;SQL 보안&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;PreparedStatement&lt;/td&gt;
&lt;td&gt;파라미터 바인딩&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;JSON&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Jackson&lt;/td&gt;
&lt;td&gt;타입 안전 파싱&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;DB&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;ILIKE 검색 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;설정 방법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Gemini API 키 발급&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;공식 링크&lt;/b&gt;: &lt;a href=&quot;https://aistudio.google.com/&quot;&gt;https://aistudio.google.com/&lt;/a&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;API 키 발급 후 복사&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;832&quot; data-origin-height=&quot;494&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tZXNH/dJMcadHHBgN/9yGxqLWTsPSfMmxwtFKTPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tZXNH/dJMcadHHBgN/9yGxqLWTsPSfMmxwtFKTPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tZXNH/dJMcadHHBgN/9yGxqLWTsPSfMmxwtFKTPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtZXNH%2FdJMcadHHBgN%2F9yGxqLWTsPSfMmxwtFKTPK%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;832&quot; height=&quot;494&quot; data-origin-width=&quot;832&quot; data-origin-height=&quot;494&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;build.gradle 의존성&lt;/h3&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;dependencies {
    implementation 'dev.langchain4j:langchain4j-google-ai-gemini:VERSION'
    implementation 'dev.langchain4j:langchain4j-core:VERSION'
    implementation 'com.fasterxml.jackson.core:jackson-databind:VERSION'
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;환경 설정&lt;/h3&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;GEMINI_API_KEY=YOUR_API_KEY_HERE
GEMINI_MODEL=gemini-2.0-flash&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Gemini + LangChain4j + PreparedStatement&lt;/b&gt;로 안전하면서도 유연한 자연어 검색을 구현했습니다. LLM이 자연어를 구조화 데이터로 변환하고, 검증된 데이터로만 SQL을 조립해 보안과 사용성을 모두 확보했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무료 플랜으로도 실무 프로토타입 구현이 가능합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;div id=&quot;gtx-trans&quot; style=&quot;position: absolute; left: 209px; top: 88.2396px;&quot;&gt;
&lt;div class=&quot;gtx-trans-icon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>IT/AI</category>
      <category>Gemini</category>
      <category>Java</category>
      <category>LangChain4j</category>
      <category>llm</category>
      <category>MCP</category>
      <category>preparedstatement</category>
      <category>SQLInjection방지</category>
      <category>동적sql</category>
      <category>백엔드</category>
      <category>자연어검색</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/191</guid>
      <comments>https://snapcode.tistory.com/entry/MCP-Gemini-LangChain4j-%ED%99%9C%EC%9A%A9%EA%B8%B0#entry191comment</comments>
      <pubDate>Tue, 17 Feb 2026 04:02:17 +0900</pubDate>
    </item>
    <item>
      <title>[Supabase] DB 연결 실패? Connection Pool Size 부족 문제 해결하기</title>
      <link>https://snapcode.tistory.com/entry/Supabase-DB-%EC%97%B0%EA%B2%B0-%EC%8B%A4%ED%8C%A8-Connection-Pool-Size-%EB%B6%80%EC%A1%B1-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;무료형 SaaS DB를 처음 써보는 것이기도 하고, 이렇게 작게 기본값이 되어 있을 줄 몰랐다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sCIf3/dJMcacPzJ7e/3CZ1C15hcjrlbUXzeeIAs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sCIf3/dJMcacPzJ7e/3CZ1C15hcjrlbUXzeeIAs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sCIf3/dJMcacPzJ7e/3CZ1C15hcjrlbUXzeeIAs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsCIf3%2FdJMcacPzJ7e%2F3CZ1C15hcjrlbUXzeeIAs1%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;1044&quot; height=&quot;336&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;336&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Supabase Postgres를 사용하던 중, Spring Boot 애플리케이션에서 간헐적으로 DB 명령문이 실행되지 않는 현상이 발생했다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;❌ [ERROR] java.sql.SQLException: Cannot connect to database
org.postgresql.util.PSQLException: too many connections for role &quot;user_account&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색해보니 &lt;b&gt;Supabase의 기본 Connection Pool Size가 너무 작다는 것&lt;/b&gt;이 원인이었다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원인 분석&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Supabase는 &lt;b&gt;Compute Size(사양)&lt;/b&gt;에 따라 기본 Pool Size가 결정된다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Compute Size&lt;/th&gt;
&lt;th&gt;Default Pool Size&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Nano&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Small&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 프로젝트는 &lt;b&gt;Nano 사양에 기본값 15&lt;/b&gt;로 설정되어 있었다. 이는 동시에 15개 이상의 데이터베이스 연결이 필요한 경우 새로운 요청을 수용할 수 없다는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Spring Boot의 HikariCP 기본 설정&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Maximum Pool Size: 10&lt;/li&gt;
&lt;li&gt;Minimum Idle: 10&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;최악의 경우 HikariCP만 10개, 그 외 시스템 연결 5개&lt;/b&gt;로 이미 Pool이 가득 찬다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결 방법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방법 1: Supabase 콘솔에서 Pool Size 증가 (권장)&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Supabase 대시보드 &amp;gt; 프로젝트 선택&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Settings &amp;gt; Database &amp;gt; Connection Pooling&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Pool size 변경&lt;/b&gt; (기본값 &amp;rarr; 30 이상)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;[Before] Default Pool Size: 15
[After]  Pool Size: 30 &amp;larr; 변경&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방법 2: Spring Boot Connection Pool 최적화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;application.yml&lt;/code&gt; 또는 &lt;code&gt;application.properties&lt;/code&gt; 설정:&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;spring:
  datasource:
    hikari:
      maximum-pool-size: 8        # 풀 최대 사이즈 감소
      minimum-idle: 2             # 최소 유휴 연결 감소
      idle-timeout: 60000         # 유휴 연결 타임아웃
      max-lifetime: 1800000       # 최대 연결 유지시간
      auto-commit: true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 환경변수로 설정:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;SPRING_DATASOURCE_HIKARI_MAXIMUM_POOL_SIZE=8
SPRING_DATASOURCE_HIKARI_MINIMUM_IDLE=2&lt;/code&gt;&lt;/pre&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;pre class=&quot;yaml&quot;&gt;&lt;code&gt;// application.yml
server:
  tomcat:
    threads:
      max: 10              # 동시 요청 수 제한
      min-spare: 5&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개선 결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;로그에서 에러가 사라졌다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;✅ [BEFORE] ❌ DB 연결 오류: too many connections
✅ [AFTER]  ✅ DB 검색 완료: '백엔드' (150ms, 결과: 42건)&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주의사항&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Pool Size를 무한정 늘릴 수 없다&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Supabase 구독 계획에 따라 최대값이 결정됨&lt;/li&gt;
&lt;li&gt;Nano &amp;rarr; Small/Medium 업그레이드로 해결 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Spring의 HikariCP 설정도 함께 조정해야 한다&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Supabase Pool Size &amp;gt; HikariCP Maximum Pool Size&lt;/li&gt;
&lt;li&gt;설정 안 하면 여전히 연결 실패 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Supabase 기본 설정은 소규모 프로젝트 기준이다. 동시 요청이 많거나 여러 도구가 DB를 공유하는 경우, &lt;b&gt;Pool Size를 명시적으로 증가&lt;/b&gt;시키는 것이 필수다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/DB</category>
      <category>backend</category>
      <category>ConnectionPool</category>
      <category>DATABASE</category>
      <category>HikariCP</category>
      <category>postgres</category>
      <category>springboot</category>
      <category>supabase</category>
      <category>배포</category>
      <category>트러블슈팅</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/190</guid>
      <comments>https://snapcode.tistory.com/entry/Supabase-DB-%EC%97%B0%EA%B2%B0-%EC%8B%A4%ED%8C%A8-Connection-Pool-Size-%EB%B6%80%EC%A1%B1-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0#entry190comment</comments>
      <pubDate>Thu, 12 Feb 2026 01:01:15 +0900</pubDate>
    </item>
    <item>
      <title>[PlayMCP] 내가 만든 MCP 서버를 카카오에 등록하다.</title>
      <link>https://snapcode.tistory.com/entry/PlayMCP-%EB%82%B4%EA%B0%80-%EB%A7%8C%EB%93%A0-MCP-%EC%84%9C%EB%B2%84%EB%A5%BC-%EB%93%B1%EB%A1%9D%ED%95%98%EB%8B%A4</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;오늘 드디어 MCP 서버를 배포했습니다.&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 공모전이 뜨자마자 @DBrider3와 참가하려했지만, 일정상 참가하진 못했고, 이렇게 만들어보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://playmcp.kakao.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://playmcp.kakao.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770809616259&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;PlayMCP | 새로운 AI 경험의 시작&quot; data-og-description=&quot;PlayMCP와 함께하는 AI 에이전트 세상, 새로운 AI 경험을 만들어 보세요.&quot; data-og-host=&quot;playmcp.kakao.com&quot; data-og-source-url=&quot;https://playmcp.kakao.com/&quot; data-og-url=&quot;https://playmcp.kakao.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/blsj7g/dJMb9kl8NhG/6OPH77W2dPbTbKwhpINR10/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/M2fqU/dJMb9fZrhXI/ScQSDEIysu61kTi7zqTR0k/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400&quot;&gt;&lt;a href=&quot;https://playmcp.kakao.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://playmcp.kakao.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/blsj7g/dJMb9kl8NhG/6OPH77W2dPbTbKwhpINR10/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/M2fqU/dJMb9fZrhXI/ScQSDEIysu61kTi7zqTR0k/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400');&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;PlayMCP | 새로운 AI 경험의 시작&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;PlayMCP와 함께하는 AI 에이전트 세상, 새로운 AI 경험을 만들어 보세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;playmcp.kakao.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 심사 전이지만, 앞으로의 기능 확장이 기대됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;등록화면&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1084&quot; data-origin-height=&quot;961&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KeOTG/dJMcaaYvpXm/0mlyscBjJMkMpw13V6nrh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KeOTG/dJMcaaYvpXm/0mlyscBjJMkMpw13V6nrh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KeOTG/dJMcaaYvpXm/0mlyscBjJMkMpw13V6nrh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKeOTG%2FdJMcaaYvpXm%2F0mlyscBjJMkMpw13V6nrh1%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;1084&quot; height=&quot;961&quot; data-origin-width=&quot;1084&quot; data-origin-height=&quot;961&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Inspector 테스트 성공 화면&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1917&quot; data-origin-height=&quot;910&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSy8wt/dJMcafFwAcQ/pmbMv07BAstJ5FmxcaihVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSy8wt/dJMcafFwAcQ/pmbMv07BAstJ5FmxcaihVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSy8wt/dJMcafFwAcQ/pmbMv07BAstJ5FmxcaihVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSy8wt%2FdJMcafFwAcQ%2FpmbMv07BAstJ5FmxcaihVk%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;1917&quot; height=&quot;910&quot; data-origin-width=&quot;1917&quot; data-origin-height=&quot;910&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;MCP 서버 적용 및 사용 화면&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;339&quot; data-origin-height=&quot;904&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2NPco/dJMcahi37vc/2A9gP0T5f2DKANkbunag7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2NPco/dJMcahi37vc/2A9gP0T5f2DKANkbunag7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2NPco/dJMcahi37vc/2A9gP0T5f2DKANkbunag7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2NPco%2FdJMcahi37vc%2F2A9gP0T5f2DKANkbunag7K%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;339&quot; height=&quot;904&quot; data-origin-width=&quot;339&quot; data-origin-height=&quot;904&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로, 저처럼 MCP 서버를 만들어보고자 하신다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 가이드를 우선적으로 읽으시면 좋을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카카오에서 &lt;u&gt;공식 배포한 가이드&lt;/u&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.notion.so/MCP-2d89b97b4888808a9e1dc17a13e70187&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.notion.so/MCP-2d89b97b4888808a9e1dc17a13e70187&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로, 디테일 정보들이 흩뿌려져있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;MCP 서버 생성&lt;/u&gt; 가이드에서는 못본 것 같은데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;MCP 심사 정책&lt;/u&gt; 에는 응답크기 제한 등의 내용이 있었던 것 같습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 전에 여러 문서를 빠르게라도 한번 쭉 읽어보시고 진행하시면 좋을 것 같습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고링크&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가이드 : &lt;a href=&quot;https://www.notion.so/MCP-2d89b97b4888808a9e1dc17a13e70187&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.notion.so/MCP-2d89b97b4888808a9e1dc17a13e70187&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움말 : &lt;a href=&quot;https://www.notion.so/2189b97b4888803dbbdcef264e7eff58&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.notion.so/2189b97b4888803dbbdcef264e7eff58&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심사정책 : &lt;a href=&quot;https://www.notion.so/21b9b97b48888024922ec3dfcacf97e5&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.notion.so/21b9b97b48888024922ec3dfcacf97e5&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;json schema : &lt;a href=&quot;https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-03-26/schema.json&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-03-26/schema.json&lt;/a&gt;&lt;/p&gt;</description>
      <category>IT/AI</category>
      <category>Agent</category>
      <category>ai</category>
      <category>HTTP</category>
      <category>kakao</category>
      <category>MCP</category>
      <category>PlayMCP</category>
      <category>SSE</category>
      <category>Stateless</category>
      <category>Streamble</category>
      <category>카카오</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/189</guid>
      <comments>https://snapcode.tistory.com/entry/PlayMCP-%EB%82%B4%EA%B0%80-%EB%A7%8C%EB%93%A0-MCP-%EC%84%9C%EB%B2%84%EB%A5%BC-%EB%93%B1%EB%A1%9D%ED%95%98%EB%8B%A4#entry189comment</comments>
      <pubDate>Wed, 11 Feb 2026 20:33:55 +0900</pubDate>
    </item>
    <item>
      <title>[MCP] JSON 응답 vs 스트리밍, 실전에서 고른 구현 방식</title>
      <link>https://snapcode.tistory.com/entry/MCP-JSON-%EC%9D%91%EB%8B%B5-vs-%EC%8A%A4%ED%8A%B8%EB%A6%AC%EB%B0%8D-%EC%8B%A4%EC%A0%84%EC%97%90%EC%84%9C-%EA%B3%A0%EB%A5%B8-%EA%B5%AC%ED%98%84-%EB%B0%A9%EC%8B%9D</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bObzUZ/dJMcagj6p42/J77zjuUtHFJlj0Tjbo1gO0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bObzUZ/dJMcagj6p42/J77zjuUtHFJlj0Tjbo1gO0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bObzUZ/dJMcagj6p42/J77zjuUtHFJlj0Tjbo1gO0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbObzUZ%2FdJMcagj6p42%2FJ77zjuUtHFJlj0Tjbo1gO0%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;555&quot; height=&quot;370&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Model Context Protocol(MCP)로 AI에 기능을 붙일 때, 경량 구현과 스트리밍 중 뭘 선택할까? 각 방식의 장단점을 비교하고 선택 기준을 정리했습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  두 가지 패턴&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ 경량 구현: 한 번에 응답&lt;/h3&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;사용자 &amp;rarr; AI 에이전트 &amp;rarr; [요청] &amp;rarr; MCP 서버
                    &amp;larr; [2초 대기] &amp;larr;
                    &amp;larr; [전체 데이터] &amp;larr;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심&lt;/b&gt;: HTTP POST/JSON으로 한 번의 왕복으로 완성된 응답 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;요청 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;scilab&quot;&gt;&lt;code&gt;curl -X POST http://localhost:8080/api/mcp/call_tool \
  -H &quot;Content-Type: application/json&quot; \
  -d '{
    &quot;jsonrpc&quot;: &quot;2.0&quot;,
    &quot;method&quot;: &quot;tools/call&quot;,
    &quot;params&quot;: {
      &quot;name&quot;: &quot;search_jobs&quot;,
      &quot;arguments&quot;: {&quot;query&quot;: &quot;백엔드&quot;}
    }
  }'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;응답 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;jsonrpc&quot;: &quot;2.0&quot;,
  &quot;result&quot;: {
    &quot;content&quot;: [{
      &quot;type&quot;: &quot;json&quot;,
      &quot;data&quot;: [
        {&quot;id&quot;: 1, &quot;company&quot;: &quot;Company A&quot;, &quot;role&quot;: &quot;Backend Engineer&quot;},
        {&quot;id&quot;: 2, &quot;company&quot;: &quot;Company B&quot;, &quot;role&quot;: &quot;Senior Backend&quot;}
      ]
    }]
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구현 예시 (Java)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@PostMapping(&quot;/api/mcp/call_tool&quot;)
public MCPResponse callTool(@RequestBody MCPRequest request) {
    try {
        MCPResult result = tool.execute(request.getParams());
        response.setResult(result);
    } catch (Exception e) {
        response.setError(new MCPError(-1, e.getMessage()));
    }
    return response;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ 스트리밍: 실시간 응답&lt;/h3&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;사용자 &amp;rarr; AI 에이전트 &amp;rarr; [요청] &amp;rarr; MCP 서버
                    &amp;larr; [0.2초] &amp;larr; [첫 데이터]
                    &amp;larr; [0.2초] &amp;larr; [두 번째 데이터]
                    &amp;larr; [0.2초] &amp;larr; [세 번째 데이터]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심&lt;/b&gt;: SSE/WebSocket으로 데이터를 조금씩 실시간 스트리밍&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;요청 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;curl -N http://localhost:8080/api/mcp/stream_tool?tool=search_jobs&amp;amp;query=backend&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;응답 스트림 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;event: tool_result
data: {&quot;id&quot;: 1, &quot;company&quot;: &quot;Company A&quot;, &quot;role&quot;: &quot;Backend Engineer&quot;}

event: tool_result
data: {&quot;id&quot;: 2, &quot;company&quot;: &quot;Company B&quot;, &quot;role&quot;: &quot;Senior Backend&quot;}

event: done
data: {&quot;status&quot;: &quot;completed&quot;}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구현 예시 (Java / SseEmitter)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;@GetMapping(&quot;/stream_tool&quot;)
public SseEmitter streamTool(@RequestParam String tool, @RequestParam String query) {
    SseEmitter emitter = new SseEmitter(60000L);

    new Thread(() -&amp;gt; {
        try {
            List&amp;lt;Map&amp;lt;String, Object&amp;gt;&amp;gt; results = searchJobsFromDB(query);
            for (Map&amp;lt;String, Object&amp;gt; result : results) {
                Thread.sleep(200);
                emitter.send(SseEmitter.event()
                    .name(&quot;tool_result&quot;)
                    .data(result));
            }
            emitter.send(SseEmitter.event().name(&quot;done&quot;).data(&quot;{}&quot;));
            emitter.complete();
        } catch (IOException e) {
            emitter.completeWithError(e);
        }
    }).start();

    return emitter;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⚖️ 비교표&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;경량 구현&lt;/th&gt;
&lt;th&gt;스트리밍&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;개발 시간&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;⏱️ 30분&lt;/td&gt;
&lt;td&gt;⏱️ 2시간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;응답 첫 데이터&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2~5초&lt;/td&gt;
&lt;td&gt;0.5초&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;대역폭 사용&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;한 번에 모두&lt;/td&gt;
&lt;td&gt;점진적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;UX&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&quot;로딩 중...&quot;&lt;/td&gt;
&lt;td&gt;실시간 글자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;디버깅&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;✅ 쉬움&lt;/td&gt;
&lt;td&gt;❌ 어려움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;에러 처리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;✅ 간단&lt;/td&gt;
&lt;td&gt;❌ 복잡&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;서버 리소스&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;✅ 효율적&lt;/td&gt;
&lt;td&gt;❌ 많이 소모&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;배포 난이도&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;✅ 낮음&lt;/td&gt;
&lt;td&gt;❌ 높음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 경량 구현 선택 기준&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 REST API 로직 재활용&lt;/li&gt;
&lt;li&gt;Postman에서 간단히 테스트&lt;/li&gt;
&lt;li&gt;추가 설정 불필요&lt;/li&gt;
&lt;li&gt;멀티스레딩/비동기 처리 불필요&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터가 많으면 대기 시간 증가&lt;/li&gt;
&lt;li&gt;실시간 느낌 없음&lt;/li&gt;
&lt;li&gt;느린 네트워크에서 불편&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;적합한 경우&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;✅ 데이터 크기 &amp;lt; 1MB
✅ 응답 시간 &amp;lt; 5초 허용
✅ 채용공고 검색, 날씨, 주식 정보
✅ 서버 리소스 제한적&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  스트리밍 선택 기준&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0.5초부터 데이터 도착&lt;/li&gt;
&lt;li&gt;ChatGPT처럼 실시간 답변&lt;/li&gt;
&lt;li&gt;점진적 대역폭 사용&lt;/li&gt;
&lt;li&gt;AI가 첫 데이터부터 병렬 처리 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SseEmitter/WebSocket 복잡한 구현&lt;/li&gt;
&lt;li&gt;스레드 관리 어려움&lt;/li&gt;
&lt;li&gt;에러 처리 복잡&lt;/li&gt;
&lt;li&gt;프록시/타임아웃 설정 필요 (Nginx)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;적합한 경우&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;✅ 데이터 크기 &amp;gt; 10MB
✅ 실시간 느낌 필수
✅ AI 텍스트 생성, 비디오/음성 스트리밍
✅ 서버 리소스 충분&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  CareerLens 프로젝트: 경량 구현 선택&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트는 &lt;b&gt;경량 구현&lt;/b&gt;을 채택했습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;선택 이유&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;이유&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;데이터량&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;채용공고 20~50개 (약 500KB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;응답 시간&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1~2초 대기 허용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;개발 시간&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;4시간 내 완성 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;배포&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;기존 Spring Boot 그대로 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용자 경험&lt;/h3&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;사용자: &quot;백엔드 개발자 공고 찾아&quot;
&amp;darr; (1초 대기)
AI: &quot;백엔드 개발자는 45개 공고가 있습니다.
    경력 3년 이상 권장하며, 연봉은
    8,000만~1억5,000만원대입니다.&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실제 구현&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@PostMapping(&quot;/api/mcp/call_tool&quot;)
public MCPResponse callTool(@RequestBody MCPRequest request) {
    // 1. 요청 검증
    // 2. Tool 조회
    // 3. DB 쿼리 (동기)
    // 4. JSON-RPC 응답 반환
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  향후 개선&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 실시간 스트리밍을 요구하면:&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Phase 2: SSE 엔드포인트 추가
GET /api/mcp/stream_tool &amp;rarr; 스트리밍 응답&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현 단계에서는 &lt;b&gt;단순함이 최고의 설계&lt;/b&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  참고 자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://modelcontextprotocol.io/&quot;&gt;Model Context Protocol (공식)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://playmcp.kakao.com/&quot;&gt;PlayMCP 플랫폼&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events&quot;&gt;Server-Sent Events (MDN)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/SseEmitter.html&quot;&gt;Spring SseEmitter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/AI</category>
      <category>ai에이전트</category>
      <category>API설계</category>
      <category>JsonRPC</category>
      <category>MCP</category>
      <category>개발패턴</category>
      <category>백엔드</category>
      <category>성능최적화</category>
      <category>스트리밍</category>
      <category>스프링부트</category>
      <category>아키텍처</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/188</guid>
      <comments>https://snapcode.tistory.com/entry/MCP-JSON-%EC%9D%91%EB%8B%B5-vs-%EC%8A%A4%ED%8A%B8%EB%A6%AC%EB%B0%8D-%EC%8B%A4%EC%A0%84%EC%97%90%EC%84%9C-%EA%B3%A0%EB%A5%B8-%EA%B5%AC%ED%98%84-%EB%B0%A9%EC%8B%9D#entry188comment</comments>
      <pubDate>Mon, 9 Feb 2026 23:13:15 +0900</pubDate>
    </item>
    <item>
      <title>[Next.js] 카카오톡 썸네일 공유 기능 추가 - OG 메타 태그 설정방법</title>
      <link>https://snapcode.tistory.com/entry/Nextjs-%EC%B9%B4%EC%B9%B4%EC%98%A4%ED%86%A1-%EC%8D%B8%EB%84%A4%EC%9D%BC-%EA%B3%B5%EC%9C%A0-%EA%B8%B0%EB%8A%A5-%EC%B6%94%EA%B0%80-OG-%EB%A9%94%ED%83%80-%ED%83%9C%EA%B7%B8-%EC%84%A4%EC%A0%95%EB%B0%A9%EB%B2%95</link>
      <description>&lt;h1&gt;제미나이로 OG 이미지부터 만드는중...&lt;/h1&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;661&quot; data-origin-height=&quot;604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnx4qS/dJMcacvgzHc/1sFMZUWU2ika5WbZXLOUD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnx4qS/dJMcacvgzHc/1sFMZUWU2ika5WbZXLOUD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnx4qS/dJMcacvgzHc/1sFMZUWU2ika5WbZXLOUD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbnx4qS%2FdJMcacvgzHc%2F1sFMZUWU2ika5WbZXLOUD0%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;661&quot; height=&quot;604&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;604&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;[Next.js] [Next.js] 카카오톡 썸네일 공유 기능 추가 - OG 메타 태그 설정방법&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹사이트를 카카오톡/페이스북에 공유할 때 썸네일이 안 뜨는 문제를 OG 메타 태그로 5분 안에 해결합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  문제: 카카오톡 공유 시 썸네일 미표시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js 프로젝트를 배포했는데, 링크를 카카오톡에 붙여 넣어도 썸네일 이미지가 나오지 않습니다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;❌ 예상: 썸네일 이미지 + 제목 + 설명
✅ 현실: 텍스트만 표시됨&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원인&lt;/b&gt;: Open Graph(OG) 메타 태그 미설정&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  해결방법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ 썸네일 이미지 준비&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크기: &lt;b&gt;1200x630px&lt;/b&gt; (카카오톡, 페이스북 표준)&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;frontend/
└── public/
    └── og-image.png&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ Next.js Metadata 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;파일&lt;/b&gt;: &lt;code&gt;app/layout.tsx&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;import type { Metadata } from &quot;next&quot;;

export const metadata: Metadata = {
  title: &quot;MyService - 스마트 검색 플랫폼&quot;,
  description: &quot;AI가 분석해주는 스마트 검색 경험&quot;,

  openGraph: {
    title: &quot;MyService - 스마트 검색 플랫폼&quot;,
    description: &quot;AI가 분석해주는 스마트 검색 경험&quot;,
    type: &quot;website&quot;,
    url: &quot;https://example-domain.com&quot;,
    images: [
      {
        url: &quot;https://example-domain.com/og-image.png&quot;,
        width: 1200,
        height: 630,
        alt: &quot;MyService 썸네일&quot;
      }
    ]
  }
};&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ 필수 필드&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;필드&lt;/th&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;th&gt;예시&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;og:title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;제목&lt;/td&gt;
&lt;td&gt;&quot;MyService - 스마트 검색&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;og:description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;설명&lt;/td&gt;
&lt;td&gt;&quot;AI 분석 검색&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;og:image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;썸네일&lt;/td&gt;
&lt;td&gt;&quot;&lt;a href=&quot;https://example.com/og-image.png&amp;quot;&quot;&gt;https://example.com/og-image.png&quot;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;og:url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;페이지 URL&lt;/td&gt;
&lt;td&gt;&quot;&lt;a href=&quot;https://example.com/&amp;quot;&quot;&gt;https://example.com/&quot;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;og:type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;콘텐츠 타입&lt;/td&gt;
&lt;td&gt;&quot;website&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;카카오톡&lt;/b&gt;: 채팅창에 링크 입력 &amp;rarr; 미리보기 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;온라인 검증&lt;/b&gt;: &lt;a href=&quot;https://metatags.io%EC%97%90%EC%84%9C&quot;&gt;https://metatags.io&lt;/a&gt;&amp;nbsp; 에서 URL 입력 후 미리보기&lt;/p&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;841&quot; data-origin-height=&quot;643&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d0SgEa/dJMcagqRPEC/l478jTtvvzGhYRiPySKbMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d0SgEa/dJMcagqRPEC/l478jTtvvzGhYRiPySKbMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d0SgEa/dJMcagqRPEC/l478jTtvvzGhYRiPySKbMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd0SgEa%2FdJMcagqRPEC%2Fl478jTtvvzGhYRiPySKbMk%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;444&quot; height=&quot;339&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;643&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⚠️ 주의사항&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 이미지 경로는 절대 URL&lt;/h3&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;// ❌ 상대 경로 (작동 안 함)
url: &quot;/og-image.png&quot;

// ✅ 절대 URL (작동함)
url: &quot;https://example-domain.com/og-image.png&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 캐시 문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SNS가 캐시하고 있으면 변경사항이 반영되지 않을 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;카카오톡: URL 다시 입력&lt;/li&gt;
&lt;li&gt;페이스북: &lt;a href=&quot;https://developers.facebook.com/tools/debug/&quot;&gt;검증 도구&lt;/a&gt;에서 &quot;Scrape Again&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 이미지 사양&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;권장: 1200x630px&lt;/li&gt;
&lt;li&gt;최대: 300KB 이하&lt;/li&gt;
&lt;li&gt;형식: PNG, JPG&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  보너스: Twitter Card&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트위터도 지원하려면:&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;twitter: {
  card: &quot;summary_large_image&quot;,
  title: &quot;MyService - 스마트 검색&quot;,
  description: &quot;AI 분석 검색&quot;,
  images: [&quot;https://example-domain.com/og-image.png&quot;]
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  정리&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;단계&lt;/th&gt;
&lt;th&gt;소요 시간&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;이미지 준비&lt;/td&gt;
&lt;td&gt;5분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;코드 수정&lt;/td&gt;
&lt;td&gt;2분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;배포&lt;/td&gt;
&lt;td&gt;1분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;총합&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;8분&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 공유 링크에 썸네일이 표시됩니다!  &lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;전&lt;/b&gt;&lt;/h2&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;253&quot; data-origin-height=&quot;96&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdpO7U/dJMcadAQQTc/JyOVDDRkh3MZLcbLk4gWYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdpO7U/dJMcadAQQTc/JyOVDDRkh3MZLcbLk4gWYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdpO7U/dJMcadAQQTc/JyOVDDRkh3MZLcbLk4gWYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdpO7U%2FdJMcadAQQTc%2FJyOVDDRkh3MZLcbLk4gWYK%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;253&quot; height=&quot;96&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;253&quot; data-origin-height=&quot;96&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;&lt;b&gt;후&lt;/b&gt;&lt;br /&gt;&lt;b&gt;(카카오톡 채팅방 &lt;span style=&quot;color: #ee2323;&quot;&gt;캐시&lt;/span&gt;가 살아있을 수 있어서, &lt;span style=&quot;color: #ee2323;&quot;&gt;새 URL로 공유&lt;/span&gt;하면 즉시 테스트 가능합니다. =&amp;gt; &lt;b&gt;&lt;a href=&quot;https://www.naver.com/?v=1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://{도메인}/?v=1&lt;/a&gt;&lt;/b&gt; )&lt;br /&gt;&lt;/b&gt;&lt;/h2&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;387&quot; data-origin-height=&quot;336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AubhK/dJMcaa5ghYM/FZBBFBQdWnuejcRdI4Gozk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AubhK/dJMcaa5ghYM/FZBBFBQdWnuejcRdI4Gozk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AubhK/dJMcaa5ghYM/FZBBFBQdWnuejcRdI4Gozk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAubhK%2FdJMcaa5ghYM%2FFZBBFBQdWnuejcRdI4Gozk%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;387&quot; height=&quot;336&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;387&quot; data-origin-height=&quot;336&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;</description>
      <category>IT/Next.js</category>
      <category>Next.js</category>
      <category>og메타태그</category>
      <category>opengraph</category>
      <category>SEO</category>
      <category>sns공유</category>
      <category>메타데이터</category>
      <category>배포</category>
      <category>썸네일</category>
      <category>웹개발</category>
      <category>카카오톡공유</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/187</guid>
      <comments>https://snapcode.tistory.com/entry/Nextjs-%EC%B9%B4%EC%B9%B4%EC%98%A4%ED%86%A1-%EC%8D%B8%EB%84%A4%EC%9D%BC-%EA%B3%B5%EC%9C%A0-%EA%B8%B0%EB%8A%A5-%EC%B6%94%EA%B0%80-OG-%EB%A9%94%ED%83%80-%ED%83%9C%EA%B7%B8-%EC%84%A4%EC%A0%95%EB%B0%A9%EB%B2%95#entry187comment</comments>
      <pubDate>Mon, 9 Feb 2026 01:49:20 +0900</pubDate>
    </item>
    <item>
      <title>[정부API] &amp;quot;개인회원은 사용할 수 없습니다&amp;quot;: 공공데이터 Open API 사용기</title>
      <link>https://snapcode.tistory.com/entry/%EC%A0%95%EB%B6%80API-%EA%B0%9C%EC%9D%B8%ED%9A%8C%EC%9B%90%EC%9D%80-%EC%82%AC%EC%9A%A9%ED%95%A0-%EC%88%98-%EC%97%86%EC%8A%B5%EB%8B%88%EB%8B%A4-%EA%B3%B5%EA%B3%B5%EB%8D%B0%EC%9D%B4%ED%84%B0-Open-API-%EC%82%AC%EC%9A%A9%EA%B8%B0</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;871&quot; data-origin-height=&quot;209&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UBSWS/dJMcadt1FAc/lpYqKosKI1aRhKCGE1T3RK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UBSWS/dJMcadt1FAc/lpYqKosKI1aRhKCGE1T3RK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UBSWS/dJMcadt1FAc/lpYqKosKI1aRhKCGE1T3RK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUBSWS%2FdJMcadt1FAc%2FlpYqKosKI1aRhKCGE1T3RK%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;871&quot; height=&quot;209&quot; data-origin-width=&quot;871&quot; data-origin-height=&quot;209&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;본문&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배경: 설레는 마음으로 시작한 API 연동&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Career Lens 프로젝트를 진행하면서 채용정보를 실시간으로 제공하고 싶었습니다. 마침 한국고용정보원(워크넷)에서 제공하는 &quot;채용정보 API&quot;를 발견했고, 공공데이터포털에서 Open API 서비스라고 명시되어 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;생각:&lt;/b&gt; &quot;오! 공공데이터면 누구나 쓸 수 있겠지?&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;현실:&lt;/b&gt; &quot;개인회원은 사용할 수 없는 OPEN-API입니다.&quot;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  상황 분석: 뭐가 문제지?&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1단계: 인증키 신청&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공공데이터포털(data.go.kr)에서 &quot;한국고용정보원_워크넷 채용정보&quot; API를 검색&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;  API 정보
- 제공기관: 한국고용정보원
- API 유형: LINK
- 데이터포맷: XML
- 활용신청: 8,597명
- 비용: 무료&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;무료이고, 활용신청이 8,000명 이상? 대박이네!&quot;라고 생각했습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2단계: 인증키 발급&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고용24 공식 사이트(work24.go.kr)에서:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;회원가입 (개인회원으로)&lt;/li&gt;
&lt;li&gt;OPEN-API 서비스 신청&lt;/li&gt;
&lt;li&gt;담당자 심사 (24시간 이내 완료)&lt;/li&gt;
&lt;li&gt;인증키 발급 ✅&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;야! 인증키도 받았다. 이제 API 호출하면 되겠지?&quot;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3단계: API 호출 및 절망&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;sas&quot;&gt;&lt;code&gt;GET https://www.sample-api-gateway.go.kr/cm/openApi/call/wk/callOpenApiSvcInfo210L01.do
?authKey=sample_auth_key_abc123def456
&amp;amp;callTp=L
&amp;amp;returnType=XML
&amp;amp;startPage=1
&amp;amp;display=10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;응답:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;GO24&amp;gt;
  &amp;lt;script/&amp;gt;
  &amp;lt;error&amp;gt;개인회원은 사용할 수 없는 OPEN-API입니다.&amp;lt;/error&amp;gt;
&amp;lt;/GO24&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 순간의 심정:&lt;/b&gt;  &lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  원인 분석: &quot;공공&quot;의 정의가 다르다?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공공데이터포털의 설명과 실제 API 사용 정책이 다릅니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;공공데이터포털 설명&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;채용정보 API를 이용하여 다양한 카테고리의 구인정보를 구성할 수 있습니다.
키워드, 직종, 희망지역을 토대로 나에게 맞는 일자리를 검색할 수 있습니다.&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  마치 누구나 쓸 수 있는 것처럼 표현&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;고용24 OPEN-API 이용안내&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;OPEN-API는 고용24 기업회원 전용 서비스입니다.
비회원인 경우 회원가입 후 이용 가능합니다.&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  개인회원은 원천 차단&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;공식 공지사항&lt;/b&gt; (최근 추가)&lt;/h4&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;워크넷 OPEN-API 개인회원 개방 안내&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  아, 최근에 일부 API는 개인회원도 개방되기 시작했다고?&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; ️ 가능한 해결 방법&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;방법 1: 기업회원으로 전환&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;❌ 장점: 모든 API 사용 가능&lt;/li&gt;
&lt;li&gt;❌ 단점: 사업자등록증 필요, 복잡한 승인 절차&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;방법 2: 다른 공개 API 찾기&lt;/b&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;/li&gt;
&lt;li&gt;✅ 단점: 데이터 품질과 최신성 미보장&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가능한 대안 API:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Indeed API&lt;/b&gt; - 국내 미지원&lt;/li&gt;
&lt;li&gt;&lt;b&gt;LinkedIn Jobs API&lt;/b&gt; - 개인사용자 접근 제한&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GitHub Jobs API&lt;/b&gt; - 기술직 중심&lt;/li&gt;
&lt;li&gt;&lt;b&gt;웹 스크래핑&lt;/b&gt; - 법적/윤리적 이슈 고려 필요&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;방법 3: 공공데이터포털에 개선 요청&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;  고객센터: 1566-0025&lt;/li&gt;
&lt;li&gt;  개선의견: &quot;개인 개발자도 사용 가능하도록 정책 개선&quot;&lt;/li&gt;
&lt;li&gt;  공식 채널: 공공데이터포털 &amp;rarr; 개선의견 게시판&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  배운 점: &quot;무료&quot;와 &quot;누구나&quot;는 다르다&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;공공데이터 API 사용 전 체크리스트&lt;/b&gt;&lt;/h4&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;확인 사항&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;✅ 비용&lt;/td&gt;
&lt;td&gt;무료인가?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⚠️ &lt;b&gt;접근 권한&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;개인/기업/모두 가능한가?&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⚠️ &lt;b&gt;심사 기간&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;기업심사는 얼마나 걸리는가?&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⚠️ &lt;b&gt;제한사항&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;상업이용, 수정, 배포 가능한가?&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⚠️ &lt;b&gt;문서 신뢰성&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;공공데이터포털 vs 제공기관 사이트 일치하는가?&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⚠️ &lt;b&gt;SLA&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;장애 시 책임은 누가 지는가?&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  참고 자료&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공공데이터포털: &lt;a href=&quot;https://www.data.go.kr/&quot;&gt;https://www.data.go.kr/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;워크넷 고용24: &lt;a href=&quot;https://www.work24.go.kr/&quot;&gt;https://www.work24.go.kr/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;공공데이터포털 고객센터: 1566-0025&lt;/li&gt;
&lt;li&gt;고용24 고객센터: 1577-7114&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  당신의 경험은?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시 공공데이터 API를 사용하면서 비슷한 경험이 있으신가요?&lt;br /&gt;또는 좋은 대안 API가 있다면 댓글로 공유해주세요!&lt;/p&gt;</description>
      <category>IT/etc</category>
      <category>API개발</category>
      <category>OpenAPI</category>
      <category>개인개발자</category>
      <category>고용24</category>
      <category>공공데이터</category>
      <category>공공서비스</category>
      <category>기술블로그</category>
      <category>데이터활용</category>
      <category>워크넷</category>
      <category>채용정보</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/186</guid>
      <comments>https://snapcode.tistory.com/entry/%EC%A0%95%EB%B6%80API-%EA%B0%9C%EC%9D%B8%ED%9A%8C%EC%9B%90%EC%9D%80-%EC%82%AC%EC%9A%A9%ED%95%A0-%EC%88%98-%EC%97%86%EC%8A%B5%EB%8B%88%EB%8B%A4-%EA%B3%B5%EA%B3%B5%EB%8D%B0%EC%9D%B4%ED%84%B0-Open-API-%EC%82%AC%EC%9A%A9%EA%B8%B0#entry186comment</comments>
      <pubDate>Wed, 28 Jan 2026 20:31:57 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Security] 자동화된 봇 공격 분석 및 방어: 로그 노이즈 90% 제거하기</title>
      <link>https://snapcode.tistory.com/entry/Spring-Security-%EC%9E%90%EB%8F%99%ED%99%94%EB%90%9C-%EB%B4%87-%EA%B3%B5%EA%B2%A9-%EB%B6%84%EC%84%9D-%EB%B0%8F-%EB%B0%A9%EC%96%B4-%EB%A1%9C%EA%B7%B8-%EB%85%B8%EC%9D%B4%EC%A6%88-90-%EC%A0%9C%EA%B1%B0%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[Spring&amp;nbsp;Security]&amp;nbsp;자동화된&amp;nbsp;봇&amp;nbsp;공격&amp;nbsp;분석&amp;nbsp;및&amp;nbsp;방어:&amp;nbsp;로그&amp;nbsp;노이즈&amp;nbsp;90%&amp;nbsp;제거하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;703&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YrDyZ/dJMcahpH7No/BqZkLA3A5s6kUJjPBcCsPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YrDyZ/dJMcahpH7No/BqZkLA3A5s6kUJjPBcCsPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YrDyZ/dJMcahpH7No/BqZkLA3A5s6kUJjPBcCsPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYrDyZ%2FdJMcahpH7No%2FBqZkLA3A5s6kUJjPBcCsPK%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;555&quot; height=&quot;483&quot; data-origin-width=&quot;703&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배경: 아직은 사용자가 나밖에 없는데.. 갑자기 증가한 로그 (귀신이 왔다갔나...)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로덕션 서버에 배포한 Career Lens 애플리케이션(example.com)의 Docker 컨테이너 로그를 확인했을 때, 이상한 패턴의 에러 메시지들이 대량으로 쌓여 있었습니다.&lt;/p&gt;
&lt;pre class=&quot;oxygene&quot;&gt;&lt;code&gt;2026-01-27T02:06:08.623+09:00  WARN --- Request method 'POST' is not supported
2026-01-27T02:44:09.104+09:00  INFO Error parsing HTTP request header
java.lang.IllegalArgumentException: Invalid character found in method name [0x050x010x00...]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 애플리케이션 버그인 줄 알고 당황했지만, 자세히 분석해보니 외부 공격이었습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  로그 분석: 무엇이 일어났는가?&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;에러 유형별 분류&lt;/b&gt;&lt;/h4&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;에러 유형&lt;/th&gt;
&lt;th&gt;발생 빈도&lt;/th&gt;
&lt;th&gt;원인&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HttpRequestMethodNotSupportedException: Request method 'POST'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;매우 높음&lt;/td&gt;
&lt;td&gt;잘못된 HTTP 메서드 사용 시도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Invalid character found in method name [0x05...]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;높음&lt;/td&gt;
&lt;td&gt;이진 데이터를 HTTP 헤더로 전송&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/index.php?s=/index/\think\app/invokefunction&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;중간&lt;/td&gt;
&lt;td&gt;&lt;b&gt;ThinkPHP RCE 취약점 공격&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PROPFIND 요청&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;td&gt;WebDAV 메서드를 이용한 시스템 스캔&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Path with &quot;WEB-INF&quot;&lt;/code&gt; 접근 시도&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;td&gt;서블릿 내부 경로 탐색&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;공격자 프로필&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;대상: 인터넷 전체 포트 80/443 스캔
목표: PHP 기반 애플리케이션 취약점 찾기
전술: CVE 기반 자동화된 스캔 봇
위험도:   낮음 (Java 애플리케이션은 PHP 취약점에 면역)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 주목할 점은 &lt;code&gt;/index.php?s=/index/\think\app/invokefunction&amp;amp;function=call_user_func_array&lt;/code&gt; 같은 ThinkPHP 익스플로잇 공격이었다는 것입니다. 이는 자동화된 취약점 스캔 봇이 모든 서버에 일괄 공격을 퍼붓는 전형적인 패턴입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  해결 방법: 3단계 방어 전략&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Step 1️⃣: 로깅 설정 최적화&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;application.properties에 추가:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;# 악의적 요청 로그 노이즈 제거
logging.level.org.apache.coyote.http11.Http11Processor=WARN
logging.level.org.apache.catalina=WARN
logging.level.org.apache.tomcat=WARN
logging.level.org.springframework.security=INFO
logging.level.org.springframework.web.servlet.mvc.method.annotation=WARN&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;효과:&lt;/b&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;code&gt;Invalid character found...&lt;/code&gt; 같은 반복 로그가 INFO/DEBUG 레벨에서 WARN으로 상향 조정&lt;/li&gt;
&lt;li&gt;실제 에러만 화면에 표시&lt;/li&gt;
&lt;li&gt;&lt;b&gt;로그 노이즈 90% 감소&lt;/b&gt; ✅&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Step 2️⃣: Spring Security 의존성 추가&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;build.gradle에 추가:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-security'
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Step 3️⃣: SecurityConfig 클래스로 강화된 보안 설정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;src/main/java/com/example/config/SecurityConfig.java:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;package com.example.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Arrays;
import java.util.logging.Logger;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private static final Logger logger = Logger.getLogger(SecurityConfig.class.getName());

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .cors(cors -&amp;gt; cors.configurationSource(corsConfigurationSource()))
            .csrf(csrf -&amp;gt; csrf.ignoringRequestMatchers(&quot;/api/**&quot;))
            .authorizeHttpRequests(auth -&amp;gt; auth.anyRequest().permitAll())
            .headers(headers -&amp;gt; headers
                .frameOptions(frameOptions -&amp;gt; frameOptions.deny())
                .httpStrictTransportSecurity(hsts -&amp;gt; hsts
                    .maxAgeInSeconds(31536000)
                    .includeSubDomains(true)
                )
            );

        logger.info(&quot;✅ Spring Security 설정 완료&quot;);
        return http.build();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList(
            &quot;http://localhost:3000&quot;,
            &quot;http://localhost:8080&quot;,
            &quot;https://example.com&quot;
        ));
        configuration.setAllowedMethods(Arrays.asList(
            &quot;GET&quot;, &quot;POST&quot;, &quot;PUT&quot;, &quot;DELETE&quot;, &quot;OPTIONS&quot;
        ));
        configuration.setAllowedHeaders(Arrays.asList(
            &quot;Content-Type&quot;, &quot;Authorization&quot;
        ));
        configuration.setAllowCredentials(true);
        configuration.setMaxAge(3600L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration(&quot;/**&quot;, configuration);

        logger.info(&quot;✅ CORS 설정 완료&quot;);
        return source;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  결과: 보안 강화 체크리스트&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;적용 여부&lt;/th&gt;
&lt;th&gt;효과&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;✅ 로그 레벨 조정&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;로그 노이즈 90% 감소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;✅ HTTP 메서드 검증&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;이상한 요청 자동 차단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;✅ CORS 제한&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;신뢰할 수 있는 도메인만 API 접근&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;✅ CSRF 보호&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;크로스사이트 요청 위조 방지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;✅ 보안 헤더&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;XSS, Clickjacking, MIME 스니핑 방지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;✅ HTTPS 강제&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;모든 통신 암호화&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  결론&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;&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;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Java + Spring Security의 조합으로 PHP 공격에는 완전히 안전하며, 로깅 설정으로 노이즈까지 제거했습니다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  참고 자료&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Security 6.1+ 공식 문서: &lt;a href=&quot;https://docs.spring.io/spring-security/reference/&quot;&gt;https://docs.spring.io/spring-security/reference/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;OWASP Top 10: &lt;a href=&quot;https://owasp.org/www-project-top-ten/&quot;&gt;https://owasp.org/www-project-top-ten/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;HTTP 보안 헤더: &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;76&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OWt37/dJMcaiIR5vN/FojXcXaQXKuVVijLlJkaP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OWt37/dJMcaiIR5vN/FojXcXaQXKuVVijLlJkaP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OWt37/dJMcaiIR5vN/FojXcXaQXKuVVijLlJkaP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOWt37%2FdJMcaiIR5vN%2FFojXcXaQXKuVVijLlJkaP0%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;708&quot; height=&quot;76&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;76&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>IT/java|Spring</category>
      <category>cors</category>
      <category>CSRF</category>
      <category>DevOps</category>
      <category>Java</category>
      <category>Springsecurity</category>
      <category>공격대응</category>
      <category>로그관리</category>
      <category>보안</category>
      <category>웹보안</category>
      <category>자동배포</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/185</guid>
      <comments>https://snapcode.tistory.com/entry/Spring-Security-%EC%9E%90%EB%8F%99%ED%99%94%EB%90%9C-%EB%B4%87-%EA%B3%B5%EA%B2%A9-%EB%B6%84%EC%84%9D-%EB%B0%8F-%EB%B0%A9%EC%96%B4-%EB%A1%9C%EA%B7%B8-%EB%85%B8%EC%9D%B4%EC%A6%88-90-%EC%A0%9C%EA%B1%B0%ED%95%98%EA%B8%B0#entry185comment</comments>
      <pubDate>Wed, 28 Jan 2026 00:59:30 +0900</pubDate>
    </item>
    <item>
      <title>[Docker] 컨테이너 파일을 서버에 마운트하기 : Docker + Volume Mount</title>
      <link>https://snapcode.tistory.com/entry/Docker-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%ED%8C%8C%EC%9D%BC%EC%9D%84-%EC%84%9C%EB%B2%84%EC%97%90-%EB%A7%88%EC%9A%B4%ED%8A%B8%ED%95%98%EA%B8%B0-Docker-Volume-Mount</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;308&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q9gmr/dJMcahb8WiH/HVpuCwa3YKnQeKCp2frcN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q9gmr/dJMcahb8WiH/HVpuCwa3YKnQeKCp2frcN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q9gmr/dJMcahb8WiH/HVpuCwa3YKnQeKCp2frcN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq9gmr%2FdJMcahb8WiH%2FHVpuCwa3YKnQeKCp2frcN1%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;308&quot; height=&quot;164&quot; data-origin-width=&quot;308&quot; data-origin-height=&quot;164&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  문제: 컨테이너의 파일을 호스트에서 어떻게 접근할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일반적인 상황:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;Docker 컨테이너 내부
└─ /app/crawler/main.py (크롤러 파일)

호스트(서버) 시스템
└─ /home/opc/career-lens/crawler/ (비어있음  )

크론잡
└─ python3 /home/opc/career-lens/crawler/main.py 
   (파일이 없어서 실행 불가!)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원인:&lt;/b&gt; 컨테이너와 호스트의 파일시스템이 분리되어 있음&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✨ 해결책: Docker Volume Mount (-v 옵션)&lt;/h2&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;docker run -v /app/crawler:/home/opc/career-lens/crawler \
  career-lens:latest&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;의미:&lt;/b&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;code&gt;/app/crawler/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&amp;harr;️ 호스트의 &lt;code&gt;/home/opc/career-lens/crawler/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;양방향으로 동기화!&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; ️ 파일시스템 마운트 구조&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Before (마운트 없음)&lt;/h3&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;┌─────────────────────────────┐
│   Docker 컨테이너           │
│  ┌───────────────────────┐  │
│  │ /app/crawler/         │  │
│  │ ├─ main.py ✅         │  │
│  │ ├─ config.py ✅       │  │
│  │ └─ crawlers/ ✅       │  │
│  └───────────────────────┘  │
│   (컨테이너 내부에만 존재)  │
└─────────────────────────────┘

┌─────────────────────────────┐
│   호스트(서버) 시스템       │
│  ┌───────────────────────┐  │
│  │ /home/opc/crawler/    │  │
│  │ (비어있음) ❌         │  │
│  └───────────────────────┘  │
└─────────────────────────────┘

❌ 파일 접근 불가능&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;After (Volume Mount 적용)&lt;/h3&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;┌─────────────────────────────┐
│   Docker 컨테이너           │
│  ┌───────────────────────┐  │
│  │ /app/crawler/         │  │
│  │ ├─ main.py ✅         │  │
│  │ ├─ config.py ✅       │  │
│  │ └─ crawlers/ ✅       │  │
│  └─────────┬─────────────┘  │
│            │ 마운트 (-v)     │
└────────────┼─────────────────┘
             │
┌────────────&amp;darr;─────────────────┐
│   호스트(서버) 시스템        │
│  ┌───────────────────────┐   │
│  │ /home/opc/crawler/    │   │
│  │ ├─ main.py ✅         │   │
│  │ ├─ config.py ✅       │   │
│  │ └─ crawlers/ ✅       │   │
│  └───────────────────────┘   │
│                              │
│  ⬅ 크론잡이 여기서 읽음       │
└──────────────────────────────┘

✅ 양방향 동기화 완벽!&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  구현: Docker Volume Mount&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ Dockerfile - 컨테이너에 파일 포함&lt;/h3&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;FROM eclipse-temurin:17-jdk-jammy

# JAR 파일 복사
ADD ./build/libs/*.jar careerlensApp.jar

# ✅ 핵심: 호스트의 crawler/ 폴더를 컨테이너에 복사
COPY crawler/ /app/crawler/

EXPOSE 80
EXPOSE 443

ENTRYPOINT [&quot;java&quot;, &quot;-Xmx256m&quot;, &quot;-Xms128m&quot;, &quot;-Duser.timezone=Asia/Seoul&quot;, &quot;-jar&quot;, &quot;/careerlensApp.jar&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;포인트:&lt;/b&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;code&gt;COPY crawler/ /app/crawler/&lt;/code&gt; &amp;rarr; 빌드 시점에 호스트 파일을 컨테이너로 복사&lt;/li&gt;
&lt;li&gt;컨테이너 내부 경로: &lt;code&gt;/app/crawler/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ Docker Run - Volume Mount 설정&lt;/h3&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;docker run -d \
  --name career-lens \
  -p 80:80 \
  -p 443:443 \
  -v /etc/letsencrypt:/etc/letsencrypt:ro \
  -v /app/crawler:/home/opc/career-lens/crawler \
  --user root \
  career-lens:latest&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 라인 분석:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;-v /app/crawler:/home/opc/career-lens/crawler
   │ 컨테이너 경로  │ 호스트 경로

   컨테이너 파일시스템
   └─ /app/crawler/
      ├─ main.py
      ├─ config.py
      └─ crawlers/

          ↕️ 양방향 마운트

   호스트 파일시스템
   └─ /home/opc/career-lens/crawler/
      ├─ main.py
      ├─ config.py
      └─ crawlers/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;마운트 옵션 설명:&lt;/b&gt;&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;옵션&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-v&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Volume Mount (파일시스템 마운트)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/app/crawler&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;컨테이너 내부 경로&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/home/opc/career-lens/crawler&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;호스트 시스템 경로&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(기본값)&lt;/td&gt;
&lt;td&gt;양방향 읽기/쓰기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:ro&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read-Only (읽기만 가능)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ GitHub Actions 배포 스크립트&lt;/h3&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;# 호스트 경로 준비
mkdir -p /home/opc/career-lens/crawler
echo &quot;✅ 호스트 마운트 경로 준비 완료&quot;

# 컨테이너 시작 + Volume Mount
docker run -d \
  --name career-lens \
  -p 80:80 \
  -p 443:443 \
  -e SPRING_PROFILES_ACTIVE=prd \
  -e DB_USERNAME=${{ secrets.DB_USERNAME }} \
  -e DB_PASSWORD=${{ secrets.DB_PASSWORD }} \
  -v /etc/letsencrypt:/etc/letsencrypt:ro \
  -v /app/crawler:/home/opc/career-lens/crawler \
  --user root \
  career-lens:latest

# 마운트 검증
echo &quot;  Volume Mount 확인:&quot;
docker exec career-lens ls -la /app/crawler/&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 실제 적용 결과&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컨테이너 내부 (마운트된 경로)&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;$ docker exec career-lens ls -la /app/crawler/
total 28
drwxr-xr-x. 3 root root  126 Jan 26 15:26 .
drwxr-xr-x. 3 root root   21 Jan 26 15:28 ..
-rw-r--r--. 1 root root  562 Jan 26 15:26 config.py
drwxr-xr-x. 2 root root   97 Jan 26 15:26 crawlers
-rw-r--r--. 1 root root  915 Jan 26 15:26 logger.py
-rw-r--r--. 1 root root 4452 Jan 26 15:26 main.py
-rw-r--r--. 1 root root    1 Jan 26 15:26 README.md
-rw-r--r--. 1 root root  113 Jan 26 15:26 requirements.txt
-rw-r--r--. 1 root root  393 Jan 26 15:26 run.sh&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;호스트 파일시스템 (마운트된 경로)&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;$ ls -la /home/opc/career-lens/crawler/
total 28
drwxr-xr-x. 3 opc opc 126 Jan 26 15:26 .
drwxr-xr-x. 3 opc opc  21 Jan 26 15:28 ..
-rw-r--r--. 1 opc opc 562 Jan 26 15:26 config.py
drwxr-xr-x. 2 opc opc  97 Jan 26 15:26 crawlers
-rw-r--r--. 1 opc opc 915 Jan 26 15:26 logger.py
-rw-r--r--. 1 opc opc 4452 Jan 26 15:26 main.py
-rw-r--r--. 1 opc opc 113 Jan 26 15:26 requirements.txt
-rw-r--r--. 1 opc opc 393 Jan 26 15:26 run.sh&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ &lt;b&gt;완벽하게 동기화됨!&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  양방향 동기화 흐름&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;변경 흐름 1: 컨테이너 파일 변경 &amp;rarr; 호스트 반영&lt;/h3&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;1️⃣ 컨테이너 내부에서 파일 수정
   $ docker exec career-lens echo &quot;수정됨&quot; &amp;gt; /app/crawler/main.py

2️⃣ 호스트 파일시스템에 자동 반영
   $ cat /home/opc/career-lens/crawler/main.py
   수정됨 ✅

3️⃣ 크론잡이 최신 파일 읽음
   0 9 * * * python3 /home/opc/career-lens/crawler/main.py
   (최신 버전으로 실행!)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;변경 흐름 2: 호스트 파일 변경 &amp;rarr; 컨테이너 반영&lt;/h3&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;1️⃣ 호스트 파일시스템에서 파일 수정
   $ echo &quot;호스트에서 수정&quot; &amp;gt; /home/opc/career-lens/crawler/config.py

2️⃣ 컨테이너 내부에 자동 반영
   $ docker exec career-lens cat /app/crawler/config.py
   호스트에서 수정 ✅

3️⃣ 컨테이너의 애플리케이션이 최신 설정 읽음
   (config.py가 변경되었으므로 자동 리로드)&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  Volume Mount 원리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파일시스템 마운트란?&lt;/h3&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;마운트 = 두 파일시스템을 연결하는 작업

-v /app/crawler:/home/opc/career-lens/crawler
  &amp;darr;
  &quot;컨테이너의 /app/crawler/ 디렉토리를
   호스트의 /home/opc/career-lens/crawler/ 에 마운트하라&quot;
  &amp;darr;
  결과: 두 경로가 동일한 파일을 가리킴&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;호스트 입장에서의 마운트&lt;/h3&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;# 마운트 확인
$ mount | grep crawler
# 또는
$ df /home/opc/career-lens/crawler/

# 마운트 정보 상세 보기
$ docker inspect career-lens --format='{{json .Mounts}}' | python3 -m json.tool&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;출력 예:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[
  {
    &quot;Type&quot;: &quot;bind&quot;,
    &quot;Source&quot;: &quot;/app/crawler&quot;,
    &quot;Destination&quot;: &quot;/home/opc/career-lens/crawler&quot;,
    &quot;Mode&quot;: &quot;&quot;,
    &quot;RW&quot;: true,
    &quot;Propagation&quot;: &quot;rprivate&quot;
  }
]&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  마운트 유형 비교&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;유형&lt;/th&gt;
&lt;th&gt;문법&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;th&gt;사용처&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Bind Mount&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-v /host:/container&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;호스트 경로를 컨테이너에 마운트&lt;/td&gt;
&lt;td&gt;파일 공유&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Named Volume&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-v myvolume:/container&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Docker가 관리하는 볼륨&lt;/td&gt;
&lt;td&gt;데이터 보존&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;tmpfs&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--tmpfs /container&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;메모리 기반 임시 파일시스템&lt;/td&gt;
&lt;td&gt;임시 파일&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;우리의 경우:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;-v /app/crawler:/home/opc/career-lens/crawler
&amp;rarr; Bind Mount 사용
&amp;rarr; 호스트의 실제 디렉토리를 마운트&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  주의사항&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ 호스트 경로 미리 생성&lt;/h3&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;# ❌ 이렇게 하면 안 됨
docker run -v /app/crawler:/nonexistent/path ...

# ✅ 이렇게 해야 함
mkdir -p /home/opc/career-lens/crawler
docker run -v /app/crawler:/home/opc/career-lens/crawler ...&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ 권한 문제&lt;/h3&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;# 호스트에서 권한 확인
ls -la /home/opc/career-lens/crawler/

# 권한 조정 필요 시
chmod 755 /home/opc/career-lens/crawler/
chown opc:opc /home/opc/career-lens/crawler/&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ 파일 덮어쓰기&lt;/h3&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;# 호스트와 컨테이너가 동일한 파일을 가짐
# 한쪽에서 삭제하면 다른 쪽에서도 삭제됨

docker exec career-lens rm /app/crawler/main.py
# &amp;rarr; /home/opc/career-lens/crawler/main.py 도 삭제됨!&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  실전 예제&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;크론잡에서 마운트된 파일 사용&lt;/h3&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;# Crontab 설정
$ crontab -e

# 추가
0 9 * * * python3 /home/opc/career-lens/crawler/main.py &amp;gt;&amp;gt; /var/log/crawler.log 2&amp;gt;&amp;amp;1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동작:&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;매일 9시에 크론잡 실행&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/home/opc/career-lens/crawler/main.py&lt;/code&gt; 실행 (마운트된 경로)&lt;/li&gt;
&lt;li&gt;컨테이너의 &lt;code&gt;/app/crawler/main.py&lt;/code&gt;와 동일한 파일&lt;/li&gt;
&lt;li&gt;최신 코드로 자동 반영!&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;호스트에서 로그 수집&lt;/h3&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;# 컨테이너에서 생성한 로그를 호스트에서 읽기
docker exec career-lens cat /app/crawler/logs/output.log

# 또는 직접 호스트에서 읽기 (마운트되었으므로)
cat /home/opc/career-lens/crawler/logs/output.log&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  마운트 상태 확인&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방법 1: Docker Inspect&lt;/h3&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;docker inspect career-lens --format='{{json .Mounts}}'&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방법 2: Mount 명령어&lt;/h3&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;mount | grep career-lens&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방법 3: 파일 확인&lt;/h3&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;# 컨테이너에서 파일 생성
docker exec career-lens touch /app/crawler/testfile.txt

# 호스트에서 확인
ls -la /home/opc/career-lens/crawler/testfile.txt
# ✅ 파일이 보임 = 마운트 성공!&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  정리: Volume Mount의 핵심&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ 파일시스템 마운트란?&lt;/h3&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;컨테이너 경로 &amp;harr;️ 호스트 경로 (양방향 동기화)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ 마운트 선언&lt;/h3&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;-v /app/crawler:/home/opc/career-lens/crawler&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ 마운트 동작&lt;/h3&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;컨테이너에서 /app/crawler/ 파일 수정
  &amp;darr;
호스트의 /home/opc/career-lens/crawler/ 에 자동 반영
  &amp;darr;
크론잡이 호스트 경로에서 읽음
  &amp;darr;
최신 파일로 자동 실행!&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4️⃣ 확인 명령어&lt;/h3&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;# 컨테이너 내부
docker exec career-lens ls -la /app/crawler/

# 호스트 시스템
ls -la /home/opc/career-lens/crawler/

# 동일한 파일들이 보이면 마운트 성공!&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker Volume Mount를 사용하면:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 컨테이너와 호스트의 파일시스템을 연결&lt;br /&gt;✅ 양방향으로 자동 동기화&lt;br /&gt;✅ 크론잡에서 쉽게 접근 가능&lt;br /&gt;✅ 파일 수정 시 즉시 반영&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이것이 현대적인 컨테이너 파일시스템 운영의 핵심입니다!&lt;/b&gt;  &lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;</description>
      <category>IT/etc</category>
      <category>cicd</category>
      <category>docker</category>
      <category>github</category>
      <category>Mount</category>
      <category>VolumeMount</category>
      <category>깃헙</category>
      <category>마운트</category>
      <category>호스트</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/184</guid>
      <comments>https://snapcode.tistory.com/entry/Docker-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%ED%8C%8C%EC%9D%BC%EC%9D%84-%EC%84%9C%EB%B2%84%EC%97%90-%EB%A7%88%EC%9A%B4%ED%8A%B8%ED%95%98%EA%B8%B0-Docker-Volume-Mount#entry184comment</comments>
      <pubDate>Tue, 27 Jan 2026 00:38:48 +0900</pubDate>
    </item>
    <item>
      <title>[DB] Flyway 마이그레이션 실패 원인: desc 컬럼의 함정</title>
      <link>https://snapcode.tistory.com/entry/DB-Flyway-%EB%A7%88%EC%9D%B4%EA%B7%B8%EB%A0%88%EC%9D%B4%EC%85%98-%EC%8B%A4%ED%8C%A8-%EC%9B%90%EC%9D%B8-desc-%EC%BB%AC%EB%9F%BC%EC%9D%98-%ED%95%A8%EC%A0%95</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[DB]&amp;nbsp;Flyway&amp;nbsp;마이그레이션&amp;nbsp;실패&amp;nbsp;원인:&amp;nbsp;desc&amp;nbsp;컬럼의&amp;nbsp;함정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTvfDi/dJMcai29r05/GFM1StE8FWgpINOeYGm5Pk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTvfDi/dJMcai29r05/GFM1StE8FWgpINOeYGm5Pk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTvfDi/dJMcai29r05/GFM1StE8FWgpINOeYGm5Pk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTvfDi%2FdJMcai29r05%2FGFM1StE8FWgpINOeYGm5Pk%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;718&quot; height=&quot;492&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;492&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  문제 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot + Flyway + PostgreSQL 환경에서&lt;br /&gt;컨테이너 기동 시 다음과 같은 에러가 발생했다.&lt;/p&gt;
&lt;pre class=&quot;subunit&quot;&gt;&lt;code&gt;ERROR: syntax error at or near &quot;desc&quot;
SQL State  : 42601
Location   : db/migration/V2__insert_initial_data.sql
Line       : 10
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flyway 마이그레이션 도중 &lt;b&gt;SQL 문법 오류&lt;/b&gt;로 인해&lt;br /&gt;컨테이너가 정상적으로 기동되지 않는 상황이었다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  에러가 발생한 SQL (문제 코드)&lt;/h2&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;INSERT INTO common_code (type, code, name, desc, created_at, updated_at) VALUES
    ('COMPANY_SCALE', 'LARGE', '대기업', '임직원 1000명 이상', NOW(), NOW()),
    ('COMPANY_SCALE', 'MEDIUM', '중견기업', '임직원 300~1000명', NOW(), NOW())
ON CONFLICT DO NOTHING;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러 메시지는 단순히 desc 근처에서 문법 오류가 났다고만 알려준다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  원인 분석&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ desc는 PostgreSQL 예약어&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;desc는 PostgreSQL에서 &lt;b&gt;ORDER BY DESC&lt;/b&gt;에 사용되는&lt;br /&gt;&lt;b&gt;예약어(Keyword)&lt;/b&gt; 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 아래 구문에서:&lt;/p&gt;
&lt;pre class=&quot;clojure&quot;&gt;&lt;code&gt;(name, desc, created_at)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PostgreSQL은 desc를 &lt;b&gt;컬럼명이 아닌 키워드&lt;/b&gt;로 해석한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ MySQL에선 되는데 PostgreSQL에선 터진 이유&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MySQL: 예약어 사용에 비교적 관대&lt;/li&gt;
&lt;li&gt;PostgreSQL: 예약어 사용 엄격&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;DB 변경 시 가장 자주 터지는 지점 중 하나&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; ️ 해결 방법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;❌ 선택하지 않은 방법&lt;/h3&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;desc&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 &lt;b&gt;더블 쿼트로 감싸는 방식&lt;/b&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;/li&gt;
&lt;li&gt;유지보수 지옥&lt;/li&gt;
&lt;li&gt;ORM/JPA와 궁합 최악&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 선택한 방법 (권장)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컬럼명을 예약어가 아닌 이름으로 변경&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✏️ 수정 내용&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ V1__init_schema.sql&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;변경 전&lt;/h4&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;CREATE TABLE common_code (
    id BIGSERIAL PRIMARY KEY,
    type VARCHAR(50),
    code VARCHAR(50),
    name VARCHAR(100),
    desc VARCHAR(255),
    created_at TIMESTAMP,
    updated_at TIMESTAMP
);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;변경 후&lt;/h4&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;CREATE TABLE common_code (
    id BIGSERIAL PRIMARY KEY,
    type VARCHAR(50),
    code VARCHAR(50),
    name VARCHAR(100),
    description VARCHAR(255),
    created_at TIMESTAMP,
    updated_at TIMESTAMP
);
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 변경을 company 테이블에도 적용&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ V2__insert_initial_data.sql&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;변경 전&lt;/h4&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;INSERT INTO common_code (type, code, name, desc, created_at, updated_at)
VALUES ('SAMPLE', 'CODE', '샘플명', '설명 텍스트', NOW(), NOW());
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;변경 후&lt;/h4&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;INSERT INTO common_code (type, code, name, description, created_at, updated_at)
VALUES ('SAMPLE', 'CODE', '샘플명', '설명 텍스트', NOW(), NOW());
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 INSERT 문에서 desc &amp;rarr; description으로 변경&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  결과&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flyway 마이그레이션 정상 수행&lt;/li&gt;
&lt;li&gt;컨테이너 정상 기동&lt;/li&gt;
&lt;li&gt;애플리케이션 정상 배포 완료 ✅&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  교훈 정리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ PostgreSQL 예약어는 컬럼명으로 쓰지 말자&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;desc&lt;/li&gt;
&lt;li&gt;user&lt;/li&gt;
&lt;li&gt;order&lt;/li&gt;
&lt;li&gt;group&lt;/li&gt;
&lt;li&gt;limit&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ DB 마이그레이션 스크립트는&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;가장 엄격한 DB 기준&lt;/b&gt;으로 작성하자&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✔ Flyway 실패 = 서비스 기동 실패&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단순 SQL 오류도 &lt;b&gt;전체 서비스 장애&lt;/b&gt;로 이어질 수 있다...매우위험...&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✨ 한 줄 요약&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;PostgreSQL에서 desc는 컬럼명이 아니라 폭탄이다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/DB</category>
      <category>DB</category>
      <category>desc</category>
      <category>error</category>
      <category>flyway</category>
      <category>migration</category>
      <category>PostgreSQL</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/183</guid>
      <comments>https://snapcode.tistory.com/entry/DB-Flyway-%EB%A7%88%EC%9D%B4%EA%B7%B8%EB%A0%88%EC%9D%B4%EC%85%98-%EC%8B%A4%ED%8C%A8-%EC%9B%90%EC%9D%B8-desc-%EC%BB%AC%EB%9F%BC%EC%9D%98-%ED%95%A8%EC%A0%95#entry183comment</comments>
      <pubDate>Mon, 26 Jan 2026 22:07:32 +0900</pubDate>
    </item>
    <item>
      <title>[Flyway] spring-boot-starter-flyway vs flyway-core 차이 정리</title>
      <link>https://snapcode.tistory.com/entry/FLYWAY-spring-boot-starter-flyway-vs-flyway-core-%EC%B0%A8%EC%9D%B4-%EC%A0%95%EB%A6%AC</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[Flyway]&amp;nbsp;spring-boot-starter-flyway&amp;nbsp;vs&amp;nbsp;flyway-core&amp;nbsp;차이&amp;nbsp;정리&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1852&quot; data-origin-height=&quot;504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bL7XnV/dJMcacBVv6c/4ZoJFudxZpNj3yoiVB9DT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bL7XnV/dJMcacBVv6c/4ZoJFudxZpNj3yoiVB9DT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bL7XnV/dJMcacBVv6c/4ZoJFudxZpNj3yoiVB9DT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbL7XnV%2FdJMcacBVv6c%2F4ZoJFudxZpNj3yoiVB9DT0%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;1852&quot; height=&quot;504&quot; data-origin-width=&quot;1852&quot; data-origin-height=&quot;504&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot 3.x 프로젝트에서 Flyway를 사용해&lt;br /&gt;PostgreSQL 마이그레이션을 적용하던 중, 아래와 같은 의존성 구성이 문제가 되었다.&lt;/p&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;implementation 'org.springframework.boot:spring-boot-starter-flyway'
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 문제없이 동작하던 설정이었지만,&lt;br /&gt;Spring Boot 3.x + PostgreSQL 환경에서는 &lt;b&gt;버전 충돌 및 DB 인식 오류&lt;/b&gt;가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 다음과 같이 의존성을 변경했다.&lt;/p&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;// ❌ 제거됨
implementation 'org.springframework.boot:spring-boot-starter-flyway'

// ✅ 추가됨
implementation 'org.flywaydb:flyway-core:9.22.3'
implementation 'org.flywaydb:flyway-database-postgresql:9.22.3'
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 &lt;b&gt;이 두 방식의 차이점과 왜 문제가 발생했는지&lt;/b&gt;를 정리한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  spring-boot-starter-flyway란?&lt;/h2&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;implementation 'org.springframework.boot:spring-boot-starter-flyway'
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Boot에서 제공하는 &lt;b&gt;편의용 스타터&lt;/b&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;flyway-core&lt;/li&gt;
&lt;li&gt;Spring Boot가 관리하는 Flyway 버전&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자동 설정(Auto Configuration)&lt;/b&gt; 을 통해 별도 설정 없이 동작&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flyway 버전을 &lt;b&gt;직접 제어할 수 없음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Spring Boot가 지정한 Flyway 버전이
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PostgreSQL 드라이버&lt;/li&gt;
&lt;li&gt;PostgreSQL 서버 버전&lt;br /&gt;과 &lt;b&gt;호환되지 않는 경우 발생&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  특히 &lt;b&gt;PostgreSQL 최신 버전&lt;/b&gt;을 사용할 때 자주 문제 발생&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  flyway-core + flyway-database-postgresql이란?&lt;/h2&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;implementation 'org.flywaydb:flyway-core:9.22.3'
implementation 'org.flywaydb:flyway-database-postgresql:9.22.3'
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;역할 분리 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리역할&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;flyway-core&lt;/td&gt;
&lt;td&gt;Flyway 핵심 엔진&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flyway-database-postgresql&lt;/td&gt;
&lt;td&gt;PostgreSQL 전용 SQL 파서 및 DB 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flyway &lt;b&gt;공식 권장 방식&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;DB별 모듈을 명확히 분리&lt;/li&gt;
&lt;li&gt;PostgreSQL 버전 대응이 빠름&lt;/li&gt;
&lt;li&gt;Spring Boot 버전에 덜 종속적&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;DB 호환성 문제를 직접 통제 가능&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;⚠️ 왜 starter를 쓰면 문제가 생겼을까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot 3.x 환경에서:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;spring-boot-starter-flyway
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;rarr; Spring Boot BOM이 관리하는 Flyway 버전 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PostgreSQL
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;rarr; 최신 버전 사용 중&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;PostgreSQL 버전을 Flyway가 제대로 인식하지 못함&lt;/li&gt;
&lt;li&gt;마이그레이션 SQL 파싱 오류&lt;/li&gt;
&lt;li&gt;Unsupported Database 관련 에러&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  원인은 &lt;b&gt;Flyway와 PostgreSQL 간 버전 불일치&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 해결 전략&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;❌ 지양&lt;/h3&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;implementation 'org.springframework.boot:spring-boot-starter-flyway'
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 권장&lt;/h3&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;implementation 'org.flywaydb:flyway-core:9.22.3'
implementation 'org.flywaydb:flyway-database-postgresql:9.22.3'
&lt;/code&gt;&lt;/pre&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;Flyway 버전을 명시적으로 관리&lt;/li&gt;
&lt;li&gt;PostgreSQL 지원 모듈을 명확히 포함&lt;/li&gt;
&lt;li&gt;Spring Boot 업그레이드에도 영향 최소화&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  정리 한 줄 요약&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Spring Boot 3.x + PostgreSQL 환경에서는&lt;br /&gt;spring-boot-starter-flyway 대신&lt;br /&gt;flyway-core + flyway-database-postgresql 조합이 훨씬 안정적이다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✨ 개인적인 팁&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;운영 환경에서 DB 마이그레이션은 &lt;b&gt;절대 자동에만 맡기지 말 것&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Flyway는 &lt;b&gt;DB 종류별 모듈 분리 방식&lt;/b&gt;이 기본 철학&lt;/li&gt;
&lt;li&gt;starter는 편하지만, 문제 생기면 디버깅이 훨씬 어렵다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/DB</category>
      <category>flyway</category>
      <category>flyway-core</category>
      <category>spring-boot-starter-flyway</category>
      <category>비교</category>
      <category>정리</category>
      <category>차이</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/182</guid>
      <comments>https://snapcode.tistory.com/entry/FLYWAY-spring-boot-starter-flyway-vs-flyway-core-%EC%B0%A8%EC%9D%B4-%EC%A0%95%EB%A6%AC#entry182comment</comments>
      <pubDate>Mon, 26 Jan 2026 21:54:55 +0900</pubDate>
    </item>
    <item>
      <title>[Flyway] Spring Boot에서 PostgreSQL 마이그레이션 자동화</title>
      <link>https://snapcode.tistory.com/entry/Flyway-Spring-Boot%EC%97%90%EC%84%9C-PostgreSQL-%EB%A7%88%EC%9D%B4%EA%B7%B8%EB%A0%88%EC%9D%B4%EC%85%98-%EC%9E%90%EB%8F%99%ED%99%94</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;1141&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uefnk/dJMcafrRUvC/jfJWzUczaBIkCAxKiyJYlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uefnk/dJMcafrRUvC/jfJWzUczaBIkCAxKiyJYlk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uefnk/dJMcafrRUvC/jfJWzUczaBIkCAxKiyJYlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fuefnk%2FdJMcafrRUvC%2FjfJWzUczaBIkCAxKiyJYlk%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;444&quot; height=&quot;422&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;1141&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[Flyway] Spring Boot에서 PostgreSQL 마이그레이션 자동화&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  목차&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a href=&quot;#flyway%EB%9E%80&quot;&gt;Flyway란?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%99%98%EA%B2%BD&quot;&gt;프로젝트 환경&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#%EA%B5%AC%ED%98%84-%EA%B3%BC%EC%A0%95&quot;&gt;구현 과정&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#%EC%A3%BC%EC%9A%94-%ED%95%99%EC%8A%B5-%ED%8F%AC%EC%9D%B8%ED%8A%B8&quot;&gt;주요 학습 포인트&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#%EC%8B%A4%ED%96%89-%EB%B0%8F-%EA%B2%80%EC%A6%9D&quot;&gt;실행 및 검증&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#%EA%B2%B0%EB%A1%A0&quot;&gt;결론&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Flyway란?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Flyway&lt;/b&gt;는 데이터베이스 스키마 버전 관리 및 마이그레이션 자동화 도구입니다. Git처럼 SQL 스크립트의 버전을 관리하고, 애플리케이션 시작 시 자동으로 데이터베이스 스키마를 최신 상태로 유지합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;필요성&lt;/h3&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;: DB 스키마 변경 이력을 코드로 추적&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;자동화&lt;/b&gt;: 수동 SQL 실행 불필요 &amp;rarr; 휴먼 에러 감소&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;일관성&lt;/b&gt;: 모든 팀원이 동일한 DB 상태 유지&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;배포 단순화&lt;/b&gt;: CI/CD 파이프라인에 자동 포함&lt;/li&gt;
&lt;li&gt;✅ &lt;b&gt;개발 효율성&lt;/b&gt;: 새로운 팀원도 자동으로 DB 초기화&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;자동 마이그레이션&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;애플리케이션 시작 시 자동 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;버전 추적&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;모든 변경사항을 코드로 기록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;롤백 안전성&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;이전 버전 상태 복구 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;멀티 환경&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;로컬/테스트/운영 동일 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;복잡한 마이그레이션&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;대용량 데이터 변환은 성능 고려 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;학습곡선&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;초기 설정 및 네이밍 규칙 학습 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;프로덕션 주의&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;스크립트 검증 필수, 실패 시 DB 손상 위험&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로젝트 환경&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기술 스택&lt;/h3&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;- Backend: Spring Boot 3.3.2
- Database: PostgreSQL
- 마이그레이션: Flyway
- 빌드: Gradle&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로젝트 구조&lt;/h3&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;[프로젝트경로]/
├── build.gradle
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/careerlens/
│   │   └── resources/
│   │       ├── db/migration/
│   │       │   ├── V1__init_schema.sql
│   │       │   └── V2__insert_initial_data.sql
│   │       ├── application.properties
│   │       └── application-local.properties
│   └── test/
└── frontend/&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구현 과정&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1단계: build.gradle에 Flyway 의존성 추가&lt;/h3&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-flyway'  // &amp;larr; 추가
    runtimeOnly 'org.postgresql:postgresql'
    // ... 기타 의존성
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2단계: 마이그레이션 파일 생성&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  디렉터리 생성&lt;/h4&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;src/main/resources/db/migration/&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  V1__init_schema.sql - 초기 스키마 정의&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flyway의 네이밍 규칙을 따름:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;V&lt;/code&gt; + 버전번호 + &lt;code&gt;__&lt;/code&gt; + 설명&lt;/li&gt;
&lt;li&gt;예: &lt;code&gt;V1__init_schema.sql&lt;/code&gt;, &lt;code&gt;V2__insert_initial_data.sql&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;-- ✅ 1. COMMON_CODE 테이블 (공통코드)
CREATE TABLE IF NOT EXISTS common_code (
    id BIGSERIAL PRIMARY KEY,
    type VARCHAR(50),
    code VARCHAR(50),
    name VARCHAR(100),
    desc VARCHAR(255),
    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

-- ✅ 2. COMPANY 테이블 (회사정보)
CREATE TABLE IF NOT EXISTS company (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(255),
    alias TEXT,
    desc VARCHAR(255),
    del_yn VARCHAR(1) DEFAULT 'N',
    deleted_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

-- ✅ 3. JOB_POSTING 테이블 (채용공고)
CREATE TABLE IF NOT EXISTS job_posting (
    id BIGSERIAL PRIMARY KEY,
    company_id BIGINT,
    name VARCHAR(255),
    link VARCHAR(500),
    experience_level VARCHAR(100),
    work_type VARCHAR(100),
    etc1 VARCHAR(100),
    etc2 VARCHAR(100),
    etc3 VARCHAR(100),
    del_yn VARCHAR(1) DEFAULT 'N',
    deleted_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (company_id) REFERENCES company(id)
);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;  V2__insert_initial_data.sql - 초기 데이터 삽입&lt;/h4&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;-- 공통코드 (기업 규모)
INSERT INTO common_code (type, code, name, desc, created_at, updated_at) VALUES
    ('COMPANY_SCALE', 'LARGE', '대기업', '임직원 1000명 이상', NOW(), NOW()),
    ('COMPANY_SCALE', 'MEDIUM', '중견기업', '임직원 300~1000명', NOW(), NOW()),
    ('COMPANY_SCALE', 'SMALL', '중소기업', '임직원 50~300명', NOW(), NOW()),
    ('COMPANY_SCALE', 'MICRO', '스타트업', '임직원 50명 이하', NOW(), NOW()),
    ('COMPANY_SCALE', 'FOREIGN', '외국계', '외국 본사의 국내 법인', NOW(), NOW())
ON CONFLICT DO NOTHING;

-- 회사정보
INSERT INTO company (name, alias, desc, del_yn, created_at, updated_at) VALUES
    ('[회사A]', '[회사A](주),[회사영문A]', '핀테크 결제 솔루션 기업', 'N', NOW(), NOW()),
    ('[회사B]', '[회사B](주),[회사영문B]', 'IT 금융 플랫폼 기업', 'N', NOW(), NOW()),
    ('[회사C]', '[회사C](주),[회사영문C]', 'IT 포털 및 서비스 기업', 'N', NOW(), NOW()),
    ('[회사D]', '[회사D](주),[회사영문D]', '로컬 커머스 플랫폼', 'N', NOW(), NOW()),
    ('[회사E]', '[회사E](주),[회사영문E]', 'E-커머스 및 로지스틱스 기업', 'N', NOW(), NOW())
ON CONFLICT DO NOTHING;

-- 채용공고
INSERT INTO job_posting (company_id, name, link, experience_level, work_type, etc1, etc2, etc3, del_yn, created_at, updated_at) VALUES
    (1, '2026년 [회사A] 신입 공채', 'https://example.com/job/1', '신입', '정규직', '결제시스템', '대규모공채', NULL, 'N', NOW(), NOW()),
    (2, '2026년 [회사B] 백엔드 경력직 채용', 'https://example.com/job/2', '2년+', '정규직', '백엔드', '경력채용', NULL, 'N', NOW(), NOW()),
    (2, '2026년 [회사B] 프론트엔드 채용', 'https://example.com/job/3', '주니어', '정규직', '프론트엔드', '경력채용', NULL, 'N', NOW(), NOW()),
    (3, '2026년 [회사C] 신입 공채', 'https://example.com/job/4', '신입', '정규직', '풀스택', '대규모공채', 'Tech', 'N', NOW(), NOW()),
    (3, '2026년 [회사C] 데이터 경력직 채용', 'https://example.com/job/5', '5년+', '정규직', '데이터분석', '경력채용', 'ML', 'N', NOW(), NOW()),
    (4, '[회사D] 모바일 개발자 채용', 'https://example.com/job/6', '주니어', '정규직', '모바일', '경력채용', NULL, 'N', NOW(), NOW()),
    (5, '[회사E] 인프라 엔지니어 채용', 'https://example.com/job/7', '신입', '정규직', '인프라', '대규모공채', NULL, 'N', NOW(), NOW()),
    (5, '[회사E] 백엔드 개발자 채용', 'https://example.com/job/8', '주니어', '정규직', '백엔드', '경력채용', NULL, 'N', NOW(), NOW())
ON CONFLICT DO NOTHING;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3단계: application.properties 설정&lt;/h3&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;# ============================================================
# Database Configuration
# ============================================================
spring.datasource.url=jdbc:postgresql://[DB_HOST]:[DB_PORT]/[DB_NAME]
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}

# ============================================================
# JPA / Hibernate Configuration
# ============================================================
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=false

# ============================================================
# Flyway Configuration
# ============================================================
spring.flyway.enabled=true
spring.flyway.locations=classpath:db/migration
spring.flyway.baseline-on-migrate=true
spring.flyway.validate-on-migrate=true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주요 설정 설명:&lt;/b&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;code&gt;ddl-auto=validate&lt;/code&gt; - Flyway가 스키마 관리하므로 Hibernate는 검증만 수행&lt;/li&gt;
&lt;li&gt;&lt;code&gt;baseline-on-migrate=true&lt;/code&gt; - 기존 DB도 마이그레이션 호환성 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주요 학습 포인트&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ CREATE TABLE IF NOT EXISTS 중요성&lt;/h3&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;-- ❌ 나쁜 예: 테이블이 이미 존재하면 에러 발생
CREATE TABLE common_code (...)

-- ✅ 좋은 예: 테이블이 없을 때만 생성, 있으면 무시
CREATE TABLE IF NOT EXISTS common_code (...)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이점:&lt;/b&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;/li&gt;
&lt;li&gt;개발 편의성: 데이터 손실 없이 스크립트 재실행 가능&lt;/li&gt;
&lt;li&gt;멱등성(Idempotent): 같은 결과 보장&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ TIMESTAMP vs TIMESTAMPTZ 선택&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;TIMESTAMP&lt;/th&gt;
&lt;th&gt;TIMESTAMPTZ&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;타임존 정보&lt;/td&gt;
&lt;td&gt;❌ 미포함&lt;/td&gt;
&lt;td&gt;✅ 포함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;저장 형식&lt;/td&gt;
&lt;td&gt;서버 로컬 시간&lt;/td&gt;
&lt;td&gt;UTC 기준&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;글로벌 서비스&lt;/td&gt;
&lt;td&gt;❌ 위험&lt;/td&gt;
&lt;td&gt;✅ 권장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;자동 변환&lt;/td&gt;
&lt;td&gt;❌ 수동 처리&lt;/td&gt;
&lt;td&gt;✅ 자동&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;-- ❌ 나쁜 예: 타임존 미포함
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP

-- ✅ 좋은 예: 타임존 포함 (PostgreSQL 권장)
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실제 차이:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;서버 시간대: 한국(+09:00)에서 2026-01-26 10:00 저장
&amp;darr;
TIMESTAMP:   2026-01-26 10:00:00 (타임존 정보 없음)
TIMESTAMPTZ: 2026-01-26 10:00:00+09:00 (타임존 정보 포함)
&amp;darr;
미국(EST) 서버에서 조회하면
TIMESTAMP:   2026-01-26 10:00:00 (???)
TIMESTAMPTZ: 2026-01-25 20:00:00-05:00 (정확함!)&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ INSERT VALUES로 대량 데이터 삽입 최적화&lt;/h3&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- ❌ 나쁜 예: 한 번에 하나씩 삽입 (4줄 &amp;times; 5개 = 20줄)
INSERT INTO common_code (...) 
SELECT 'COMPANY_SCALE', 'LARGE', ... WHERE NOT EXISTS (...);
INSERT INTO common_code (...) 
SELECT 'COMPANY_SCALE', 'MEDIUM', ... WHERE NOT EXISTS (...);
INSERT INTO common_code (...) 
SELECT 'COMPANY_SCALE', 'SMALL', ... WHERE NOT EXISTS (...);
-- ... 반복

-- ✅ 좋은 예: VALUES로 한 번에 삽입 (5줄)
INSERT INTO common_code (type, code, name, desc, created_at, updated_at) VALUES
    ('COMPANY_SCALE', 'LARGE', '대기업', ..., NOW(), NOW()),
    ('COMPANY_SCALE', 'MEDIUM', '중견기업', ..., NOW(), NOW()),
    ('COMPANY_SCALE', 'SMALL', '중소기업', ..., NOW(), NOW()),
    ('COMPANY_SCALE', 'MICRO', '스타트업', ..., NOW(), NOW()),
    ('COMPANY_SCALE', 'FOREIGN', '외국계', ..., NOW(), NOW())
ON CONFLICT DO NOTHING;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;성능 개선:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드 라인: 50% 단축&lt;/li&gt;
&lt;li&gt;네트워크 왕복: 5회 &amp;rarr; 1회&lt;/li&gt;
&lt;li&gt;실행 속도: 약 3~5배 향상&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4️⃣ ON CONFLICT DO NOTHING으로 재실행 안전성&lt;/h3&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- ❌ 나쁜 예: 중복 데이터 오류 발생
INSERT INTO common_code (...) VALUES (...)

-- ✅ 좋은 예: 중복 시 무시 (멱등성 보장)
INSERT INTO common_code (...) VALUES (...)
ON CONFLICT DO NOTHING;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;용도:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flyway 재실행 시 중복 오류 방지&lt;/li&gt;
&lt;li&gt;데이터 무결성 유지&lt;/li&gt;
&lt;li&gt;스크립트 재사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5️⃣ 외래키(Foreign Key) 제약조건 활용&lt;/h3&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;CREATE TABLE job_posting (
    id BIGSERIAL PRIMARY KEY,
    company_id BIGINT,
    -- ...
    FOREIGN KEY (company_id) REFERENCES company(id)  -- &amp;larr; 참조 무결성 보장
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이점:&lt;/b&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;/li&gt;
&lt;li&gt;✅ 일관성: 회사 삭제 시 자동 처리 (설정에 따라)&lt;/li&gt;
&lt;li&gt;✅ 명확한 관계: 테이블 간 관계를 명시적으로 표현&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6️⃣ Soft Delete (논리적 삭제) 패턴&lt;/h3&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;CREATE TABLE company (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(255),
    del_yn VARCHAR(1) DEFAULT 'N',        -- 삭제 여부
    deleted_at TIMESTAMPTZ,              -- 삭제 시간
    -- ...
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용 예시:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- 회사 삭제 (물리 삭제 아님, 논리적 삭제)
UPDATE company 
SET del_yn = 'Y', deleted_at = NOW() 
WHERE id = 1;

-- 활성 회사만 조회
SELECT * FROM company WHERE del_yn = 'N';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점:&lt;/b&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;/li&gt;
&lt;li&gt;✅ 감사 추적: 삭제 시간 기록&lt;/li&gt;
&lt;li&gt;✅ 데이터 분석: 삭제 이력 분석 가능&lt;/li&gt;
&lt;li&gt;✅ 관계 무결성: 참조 중인 데이터 안전&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실행 및 검증&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마이그레이션 실행 결과&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실행 시 콘솔 로그&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;...
2026-01-26 10:30:45.123 INFO  ... Initializing Flyway...
2026-01-26 10:30:45.456 INFO  ... Current schema version: 0
2026-01-26 10:30:46.789 INFO  ... Migrating schema to version 1
2026-01-26 10:30:47.012 INFO  ... Successfully applied 1 migration
2026-01-26 10:30:47.234 INFO  ... Migrating schema to version 2
2026-01-26 10:30:48.567 INFO  ... Successfully applied 1 migration
2026-01-26 10:30:49.890 INFO  ... Flyway completed successfully
...&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;PostgreSQL에서 확인&lt;/h4&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- Flyway 마이그레이션 이력 확인
SELECT * FROM flyway_schema_history;

-- 생성된 테이블 확인
\dt  -- 모든 테이블 목록

-- 데이터 확인
SELECT COUNT(*) FROM common_code;  -- 16개 (공통코드)
SELECT COUNT(*) FROM company;       -- 5개 (회사)
SELECT COUNT(*) FROM job_posting;   -- 8개 (채용공고)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;트러블슈팅: Flyway 버전 오류&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;에러 메시지:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;Could not find org.springframework.boot:spring-boot-starter-flyway:.
Required by: project :&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원인:&lt;/b&gt;&lt;br /&gt;의존성 관리 플러그인이 Flyway 버전을 찾지 못함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결 방법:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;# Gradle 캐시 정리 및 의존성 다시 다운로드
./gradlew clean --refresh-dependencies

# 의존성 확인
./gradlew dependencies --configuration compileClasspath&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Flyway 적용의 이점&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;측면&lt;/th&gt;
&lt;th&gt;효과&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;개발 생산성&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;자동 마이그레이션으로 수동 작업 제거&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;팀 협업&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;모든 팀원이 동일한 DB 상태 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;배포 안정성&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;CI/CD 파이프라인에 통합된 자동화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;데이터 무결성&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;스키마 버전 관리로 일관성 보장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;개발 속도&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;새로운 팀원도 자동으로 DB 초기화&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배운 점&lt;/h3&gt;
&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; - IF NOT EXISTS, ON CONFLICT DO NOTHING으로 안전한 재실행 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;타임존 관리의 중요성&lt;/b&gt; - TIMESTAMPTZ는 글로벌 서비스 필수&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자동화의 효율성&lt;/b&gt; - Flyway로 DB 관리를 코드화하면 휴먼 에러 대폭 감소&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://flywaydb.org/&quot;&gt;Flyway 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://spring.io/guides/gs/accessing-data-mysql/&quot;&gt;Spring Boot Flyway 가이드&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.postgresql.org/docs/current/datatype-datetime.html&quot;&gt;PostgreSQL TIMESTAMPTZ vs TIMESTAMP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Soft_delete&quot;&gt;Soft Delete 패턴&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;</description>
      <category>IT/DB</category>
      <category>DB</category>
      <category>flyway</category>
      <category>migration</category>
      <category>PG</category>
      <category>spring boot</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/181</guid>
      <comments>https://snapcode.tistory.com/entry/Flyway-Spring-Boot%EC%97%90%EC%84%9C-PostgreSQL-%EB%A7%88%EC%9D%B4%EA%B7%B8%EB%A0%88%EC%9D%B4%EC%85%98-%EC%9E%90%EB%8F%99%ED%99%94#entry181comment</comments>
      <pubDate>Mon, 26 Jan 2026 21:35:09 +0900</pubDate>
    </item>
    <item>
      <title>[보안] Spring Boot HTTP&amp;rarr;HTTPS 리다이렉트 완전정복</title>
      <link>https://snapcode.tistory.com/entry/%EB%B3%B4%EC%95%88-Spring-Boot-HTTP%E2%86%92HTTPS-%EB%A6%AC%EB%8B%A4%EC%9D%B4%EB%A0%89%ED%8A%B8-%EC%99%84%EC%A0%84%EC%A0%95%EB%B3%B5</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[보안]&amp;nbsp;Spring&amp;nbsp;Boot&amp;nbsp;HTTP&amp;rarr;HTTPS&amp;nbsp;리다이렉트&amp;nbsp;완전정복&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bE9uNF/dJMb99SJgQd/Eah2qNwuG1xnZMRpsngp10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bE9uNF/dJMb99SJgQd/Eah2qNwuG1xnZMRpsngp10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bE9uNF/dJMb99SJgQd/Eah2qNwuG1xnZMRpsngp10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbE9uNF%2FdJMb99SJgQd%2FEah2qNwuG1xnZMRpsngp10%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;1080&quot; height=&quot;608&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;608&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 Spring Boot(백엔드)에서 &lt;b&gt;HTTP &amp;rarr; HTTPS 자동 리다이렉트&lt;/b&gt;를 구현하는 방법을 정리합니다.&lt;br /&gt;사용자가 &lt;code&gt;http://your-domain.com&lt;/code&gt;으로 접속해도 자동으로 &lt;code&gt;https://your-domain.com&lt;/code&gt;으로 이동하게 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심:&lt;/b&gt; 사용자는 신경 쓸 필요 없이 자동으로 보안 연결로 이동합니다!  &lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;목표&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;✅ 모든 HTTP 요청(포트 80) &amp;rarr; HTTPS(포트 443)로 자동 리다이렉트&lt;/li&gt;
&lt;li&gt;✅ Spring Boot에서 HTTP(80) + HTTPS(443) 동시 리스닝&lt;/li&gt;
&lt;li&gt;✅ 사용자 경험 개선 (자동 이동, 보안 경고 제거)&lt;/li&gt;
&lt;li&gt;✅ SEO 최적화 (Google HTTPS 우대)&lt;/li&gt;
&lt;li&gt;✅ 보안 강화 (중간자 공격 방지)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;전제&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도메인 &amp;rarr; 서버 IP(A 레코드) 연결 완료&lt;/li&gt;
&lt;li&gt;포트 80, 443 접근 허용(방화벽/보안그룹)&lt;/li&gt;
&lt;li&gt;Spring Boot 3.x, Java 17 이상&lt;/li&gt;
&lt;li&gt;Let's Encrypt 인증서 발급 완료&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 왜 HTTP &amp;rarr; HTTPS 리다이렉트가 필수인가?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.1 사용자 경험 (UX)&lt;/h3&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;❌ 리다이렉트 없음 (보안 경고)
사용자: http://your-domain.com 입력
  &amp;darr;
브라우저: ⚠️ &quot;이 사이트는 안전하지 않습니다&quot; 경고 표시
  &amp;darr;
사용자: 불신 &amp;rarr; 이탈 ❌

✅ 리다이렉트 적용 (자동 보안 연결)
사용자: http://your-domain.com 입력
  &amp;darr;
자동으로 https://your-domain.com로 이동
  &amp;darr;
사용자: 자동으로 보안 연결 ✅ (주소창에  )&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.2 SEO (검색 엔진 최적화)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Google은 HTTPS 사이트를 검색 순위에서 우대&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;HTTP 사이트는 &quot;Not Secure&quot; 경고로 클릭률 저하&lt;/li&gt;
&lt;li&gt;웹마스터 도구에서 HTTPS 이전 권장&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.3 보안&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;중간자 공격(MITM) 방지&lt;/b&gt; - 데이터 암호화&lt;/li&gt;
&lt;li&gt;쿠키, 세션, 로그인 정보 암호화&lt;/li&gt;
&lt;li&gt;신용카드 정보 전송 시 필수 (PCI-DSS 준수)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.4 기술 표준&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;거의 모든 상용 웹사이트가 구현 (Amazon, Google, Facebook 등)&lt;/li&gt;
&lt;li&gt;최신 브라우저 기능(HTTP/2, HTTP/3)은 &lt;b&gt;HTTPS에서만 동작&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Spring Boot 설정 (HTTP &amp;rarr; HTTPS 리다이렉트)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.1 application.properties - SSL 및 리다이렉트 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;파일 위치:&lt;/b&gt; &lt;code&gt;src/main/resources/application.properties&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;# ========================================
# SSL/TLS 설정 (Let's Encrypt PEM 방식)
# ========================================
server.port=443
server.ssl.enabled=true
server.ssl.key-store-type=PEM
server.ssl.certificate=/etc/letsencrypt/live/{YOUR_DOMAIN}/fullchain.pem
server.ssl.certificate-private-key=/etc/letsencrypt/live/{YOUR_DOMAIN}/privkey.pem

# HTTP/2 지원
server.http2.enabled=true

# 보안 쿠키 설정
server.servlet.session.cookie.secure=true
server.servlet.session.cookie.http-only=true
server.servlet.session.cookie.same-site=strict

# HTTP &amp;rarr; HTTPS 리다이렉트 설정
server.tomcat.redirect-context-root=true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;변수 설명:&lt;/b&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;code&gt;{YOUR_DOMAIN}&lt;/code&gt; &amp;rarr; 실제 도메인으로 대체 (예: &lt;code&gt;example.com&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.2 WebConfig.java - HTTP &amp;rarr; HTTPS 자동 리다이렉트 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;파일 위치:&lt;/b&gt; &lt;code&gt;src/main/java/com/yourcompany/config/WebConfig.java&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;package com.yourcompany.config;

import org.apache.catalina.connector.Connector;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * HTTP &amp;rarr; HTTPS 자동 리다이렉트 설정
 * 
 * 동작 원리:
 * 1. 사용자가 http://your-domain.com 입력 (포트 80)
 * 2. Spring Boot HTTP Connector(80)가 요청 감지
 * 3. Redirect-Port(443)로 자동 리다이렉트
 * 4. 브라우저가 Location 헤더 읽음 &amp;rarr; https://your-domain.com 재요청
 * 5. Spring Boot HTTPS Connector(443)가 요청 처리
 * 6. TLS/SSL 보안 연결 확립 ✅
 */
@Configuration
public class WebConfig {

    /**
     * Tomcat 서블릿 웹서버 팩토리
     * HTTP(80)과 HTTPS(443) 두 포트를 동시에 리스닝하도록 설정
     */
    @Bean
    public ServletWebServerFactory servletContainer() {
        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();

        // HTTP 포트(80)에서 들어온 요청을 HTTPS(443)로 리다이렉트
        tomcat.addAdditionalTomcatConnectors(createHttpConnector());

        return tomcat;
    }

    /**
     * HTTP 포트(80) 커넥터 생성
     * 
     * 매개변수 설명:
     * - setPort(80): HTTP 표준 포트 (포트 포워딩으로 80에서 수신)
     * - setScheme(&quot;http&quot;): HTTP 프로토콜 명시
     * - setSecure(false): 비보안 연결 (TLS 없음)
     * - setRedirectPort(443): 리다이렉트 대상 포트 (HTTPS)
     */
    private Connector createHttpConnector() {
        Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
        connector.setPort(80);             // HTTP 리스닝 포트
        connector.setScheme(&quot;http&quot;);       // HTTP 스킴
        connector.setSecure(false);        // 비보안 연결
        connector.setRedirectPort(443);    // HTTPS 리다이렉트 대상
        return connector;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. HTTP &amp;rarr; HTTPS 리다이렉트 동작 원리 (상세 흐름도)&lt;/h2&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────┐
│ 1️⃣ 사용자가 브라우저에 입력                                 │
│    URL: http://your-domain.com                              │
│    (자동으로 포트 80 사용)                                   │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│ 2️⃣ 브라우저가 HTTP 요청 생성                                │
│    GET / HTTP/1.1                                           │
│    TCP 포트 80으로 연결 시도                                 │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│ 3️⃣ Spring Boot Tomcat HTTP Connector 수신                  │
│    connector.setPort(80) 설정된 커넥터가 요청 수신         │
│    HTTP 프로토콜로 처리 시작                                │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│ 4️⃣ HTTP &amp;rarr; HTTPS 리다이렉트 결정                             │
│    connector.setRedirectPort(443) 설정 확인                 │
│    HTTP 301 (Moved Permanently) 응답 생성                  │
│    Location 헤더: https://your-domain.com/                 │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│ 5️⃣ 브라우저가 Location 헤더 읽음                            │
│    HTTP 301 응답에 포함된 새 URL 감지                       │
│    자동으로 https://your-domain.com으로 재요청             │
│    (사용자는 주소창 변화만 인식)                             │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│ 6️⃣ HTTPS 연결 확립                                          │
│    TCP 포트 443으로 연결                                     │
│    TLS 핸드셰이크 수행                                       │
│    - 클라이언트: 암호화 제안                                 │
│    - 서버: Let's Encrypt 인증서 전송                        │
│    - 인증서 검증 (CA 체인 확인)                             │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│ 7️⃣ Spring Boot Tomcat HTTPS Connector 수신                 │
│    server.port=443으로 설정된 커넥터가 요청 수신             │
│    SSL/TLS 보안 연결 확립 ✅                                │
│    application.properties의 인증서 로드:                    │
│    - fullchain.pem (공개 인증서)                            │
│    - privkey.pem (개인 키)                                  │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│ 8️⃣ 응답 반환 (암호화된 통신)                                │
│    안전한 HTTPS 통신으로 콘텐츠 전송                         │
│    사용자: https://your-domain.com ✅                       │
│    주소창에   자물쇠 아이콘 표시                           │
└─────────────────────────────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. HTTP &amp;rarr; HTTPS 리다이렉트 테스트&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.1 curl 명령어 테스트&lt;/h3&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;# HTTP 요청 시 리다이렉트 확인
curl -i http://your-domain.com

# 예상 결과:
# HTTP/1.1 301 Moved Permanently
# Location: https://your-domain.com/
# Server: Apache Tomcat&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.2 브라우저에서 테스트&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 브라우저 주소창에 입력: http://your-domain.com
2. 자동으로 https://your-domain.com으로 이동 ✅
3. 주소창에   자물쇠 아이콘 표시 확인
4. 개발자 도구 (F12) &amp;rarr; Network 탭
   - Request URL이 https://로 시작하는지 확인
   - Status: 301 Moved Permanently 확인&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.3 SSL 인증서 검증&lt;/h3&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;# 로컬에서 인증서 정보 확인
openssl s_client -connect your-domain.com:443 -tls1_2

# 인증서 만료날짜 확인
openssl x509 -in /etc/letsencrypt/live/your-domain.com/fullchain.pem -noout -dates

# 결과:
# notBefore=Jan 23 11:23:08 2024 GMT
# notAfter=Apr 22 11:23:08 2024 GMT

# 온라인 SSL 검증 (SSL Labs)
# https://www.ssllabs.com/ssltest/analyze.html?d=your-domain.com&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 프로젝트 파일 구조 및 위치&lt;/h2&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;career-lens/
│
├── backend/
│   ├── src/main/
│   │   ├── java/com/yourcompany/
│   │   │   └── config/
│   │   │       └── WebConfig.java              &amp;larr; HTTP&amp;rarr;HTTPS 리다이렉트 (핵심!)
│   │   └── resources/
│   │       ├── application.properties           &amp;larr; SSL 인증서 경로 설정
│   │       └── application-prd.properties       &amp;larr; 프로덕션 설정
│   └── Dockerfile                              &amp;larr; Docker 이미지 정의
│
├── frontend/
│   ├── next.config.js                          &amp;larr; 보안 헤더 설정
│   ├── .env.production                         &amp;larr; HTTPS API 기본 URL
│   └── .env.local                              &amp;larr; 개발 환경 설정
│
├── docker-compose.yml                          &amp;larr; 포트 매핑, 볼륨 설정
├── .github/workflows/
│   └── deploy.yml                              &amp;larr; CI/CD 배포 스크립트
│
└── TEMP.md                                     &amp;larr; 이 파일&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 운영 체크리스트&lt;/h2&gt;
&lt;pre class=&quot;haml&quot;&gt;&lt;code&gt;✅ Spring Boot 설정
  ☐ application.properties에 SSL 인증서 경로 설정
    - server.ssl.certificate=/etc/letsencrypt/live/{YOUR_DOMAIN}/fullchain.pem
    - server.ssl.certificate-private-key=/etc/letsencrypt/live/{YOUR_DOMAIN}/privkey.pem
  ☐ WebConfig.java에서 HTTP(80) &amp;rarr; HTTPS(443) 리다이렉트 구현
  ☐ server.port=443 및 server.ssl.enabled=true 확인
  ☐ Spring Boot 시작 로그 확인:
    - &quot;Application started on port 443 (https)&quot;
    - &quot;HTTP Connector listening on port 80&quot;

✅ 보안 검증
  ☐ HTTP &amp;rarr; HTTPS 리다이렉트 테스트
    - curl -i http://your-domain.com
    - 결과: HTTP/1.1 301 Moved Permanently
  ☐ SSL 인증서 유효성 확인
    - https://www.ssllabs.com/ssltest/
  ☐ 브라우저에서 자물쇠 아이콘 표시 확인&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 트러블슈팅&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Q: 포트 80, 443 충돌&lt;/h3&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;# 포트 사용 프로세스 확인
sudo lsof -i :80
sudo lsof -i :443

# 프로세스 강제 종료
sudo kill -9 &amp;lt;PID&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Q: SSL 인증서 로드 오류&lt;/h3&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;# 인증서 파일 존재 여부 확인
ls -la /etc/letsencrypt/live/your-domain.com/

# 인증서 파일 권한 확인
sudo chmod 755 /etc/letsencrypt/live
sudo chmod 755 /etc/letsencrypt/archive&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Q: HTTP 요청이 리다이렉트되지 않음&lt;/h3&gt;
&lt;pre class=&quot;vala&quot;&gt;&lt;code&gt;# Spring Boot 로그에서 포트 확인
# Application started on port 443 (https)
# HTTP Connector listening on port 80

# 리다이렉트 테스트
curl -v http://localhost
# 결과: HTTP/1.1 301 &amp;larr; 성공&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 최종 요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot 내장 Tomcat 웹서버에 Let's Encrypt PEM 방식 SSL을 적용하고, &lt;b&gt;HTTP(80) &amp;rarr; HTTPS(443) 자동 리다이렉트&lt;/b&gt;를 구현하면&lt;br /&gt;사용자는 보안 경고 없이 자동으로 안전한 연결으로 이동합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 설정:&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;application.properties&lt;/code&gt;에서 SSL 인증서 경로 설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WebConfig.java&lt;/code&gt;에서 HTTP(80) &amp;rarr; HTTPS(443) 리다이렉트 구현&lt;/li&gt;
&lt;li&gt;Spring Boot 시작 시 두 포트 동시 리스닝&lt;/li&gt;
&lt;li&gt;사용자 자동 리다이렉트 완료 ✅&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 참고 자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Let's Encrypt 공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.webserver.use-ssl&quot;&gt;Spring Boot SSL 설정&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security&quot;&gt;HSTS (HTTP Strict-Transport-Security)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://owasp.org/www-project-secure-headers/&quot;&gt;OWASP 보안 헤더 가이드&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/java|Spring</category>
      <category>HTTP</category>
      <category>https</category>
      <category>SSL</category>
      <category>WebConfig</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/180</guid>
      <comments>https://snapcode.tistory.com/entry/%EB%B3%B4%EC%95%88-Spring-Boot-HTTP%E2%86%92HTTPS-%EB%A6%AC%EB%8B%A4%EC%9D%B4%EB%A0%89%ED%8A%B8-%EC%99%84%EC%A0%84%EC%A0%95%EB%B3%B5#entry180comment</comments>
      <pubDate>Sun, 25 Jan 2026 23:42:29 +0900</pubDate>
    </item>
    <item>
      <title>[SSL] Let's Encrypt 무료 인증서로 HTTPS 자동갱신 구축하기: Certbot + Docker 완전 자동화 가이드</title>
      <link>https://snapcode.tistory.com/entry/SSL-Spring-Boot-%EB%82%B4%EC%9E%A5-%EC%9B%B9%EC%84%9C%EB%B2%84%EC%97%90-Lets-Encrypt-%EC%9E%90%EB%8F%99%EA%B0%B1%EC%8B%A0-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-PEM-%EB%B0%A9%EC%8B%9D%EC%9D%98-%EB%AC%B4%ED%95%9C-%EA%B0%B1%EC%8B%A0-%EC%9E%90%EB%8F%99%ED%99%94</link>
      <description>&lt;p&gt;[SSL] Let&amp;#39;s Encrypt 무료 인증서로 HTTPS 자동갱신 구축하기: Certbot + Docker 완전 자동화 가이드&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkuGRH/dJMcacu7MwV/OOdfnKpA26kelmwKk4zFZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkuGRH/dJMcacu7MwV/OOdfnKpA26kelmwKk4zFZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkuGRH/dJMcacu7MwV/OOdfnKpA26kelmwKk4zFZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkuGRH%2FdJMcacu7MwV%2FOOdfnKpA26kelmwKk4zFZ0%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;444&quot; height=&quot;181&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;[SSL] Let&amp;#39;s Encrypt 무료 인증서로 HTTPS 자동갱신 구축하기: Certbot + Docker 완전 자동화 가이드&lt;/h1&gt;
&lt;hr&gt;
&lt;h2&gt;개요&lt;/h2&gt;
&lt;p&gt;이 글은 &lt;strong&gt;Let&amp;#39;s Encrypt 무료 SSL 인증서&lt;/strong&gt;를 Certbot으로 발급하고, Docker 볼륨 마운트를 통해 &lt;strong&gt;이미지 재빌드 없이 자동갱신&lt;/strong&gt;하는 방법을 정리합니다.&lt;/p&gt;
&lt;p&gt;Spring Boot 백엔드는 application.properties에 설정된 인증서 경로를 통해 자동으로 HTTPS를 처리하며, Systemd 타이머가 매달 정기적으로 인증서를 갱신합니다.&lt;/p&gt;
&lt;h2&gt;목표&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;✅ Let&amp;#39;s Encrypt 무료 SSL 인증서 발급&lt;/li&gt;
&lt;li&gt;✅ 포트 80 (HTTP) → 포트 443 (HTTPS) 자동 리다이렉트&lt;/li&gt;
&lt;li&gt;✅ Certbot을 통한 자동 인증서 갱신&lt;/li&gt;
&lt;li&gt;✅ Docker 볼륨 마운트로 이미지 재빌드 없이 인증서 적용&lt;/li&gt;
&lt;li&gt;✅ Systemd 타이머로 완전 자동화&lt;/li&gt;
&lt;li&gt;✅ 개발자는 코드만 작성, 나머지는 모두 자동&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;전제&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;도메인 → 서버 IP(A 레코드) 연결 완료&lt;/li&gt;
&lt;li&gt;포트 80, 443 접근 허용 (방화벽/보안그룹)&lt;/li&gt;
&lt;li&gt;CentOS/RHEL 또는 Ubuntu 계열 서버&lt;/li&gt;
&lt;li&gt;Docker 설치 필요&lt;/li&gt;
&lt;li&gt;Certbot 설치 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;1. Let&amp;#39;s Encrypt 인증서 발급 (Certbot)&lt;/h2&gt;
&lt;h3&gt;1.1 Certbot 설치&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;CentOS/RHEL:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo yum install certbot
certbot --version&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Ubuntu:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt update
sudo apt install -y certbot
certbot --version&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.2 인증서 발급 (Standalone 방식)&lt;/h3&gt;
&lt;p&gt;Spring Boot가 포트 443을 사용하므로, 사전에 Spring Boot를 중지한 후 Certbot으로 인증서를 발급합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 1. Spring Boot 앱 중지
sudo docker stop career-lens

# 2. 인증서 발급
sudo certbot certonly --standalone -d {YOUR_DOMAIN}

# 3. Spring Boot 앱 재시작
sudo docker start career-lens&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;발급 결과:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/{YOUR_DOMAIN}/fullchain.pem
Key is saved at: /etc/letsencrypt/live/{YOUR_DOMAIN}/privkey.pem
This certificate expires on 2026-04-22 (만료날짜).&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;1.3 인증서 파일 구조&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/etc/letsencrypt/live/{YOUR_DOMAIN}/
├── cert.pem           (공개 인증서)
├── privkey.pem        (개인 키)
├── chain.pem          (인증 체인)
└── fullchain.pem      (cert + chain, Spring Boot에서 사용)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;변수 설명:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;{YOUR_DOMAIN}&lt;/code&gt; → 실제 도메인으로 대체 (예: &lt;code&gt;example.com&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;2. Spring Boot 서버 설정&lt;/h2&gt;
&lt;h3&gt;2.1 application.properties - SSL 및 HTTP → HTTPS 리다이렉트 설정&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;파일 위치:&lt;/strong&gt; &lt;code&gt;src/main/resources/application.properties&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-properties&quot;&gt;# ========================================
# SSL Configuration (Let&amp;#39;s Encrypt - PEM)
# ========================================
server.port=443
server.ssl.enabled=true
server.ssl.key-store-type=PEM
server.ssl.certificate=/etc/letsencrypt/live/{YOUR_DOMAIN}/fullchain.pem
server.ssl.certificate-private-key=/etc/letsencrypt/live/{YOUR_DOMAIN}/privkey.pem

# HTTP/2 지원
server.http2.enabled=true

# 보안 쿠키 설정
server.servlet.session.cookie.secure=true
server.servlet.session.cookie.http-only=true
server.servlet.session.cookie.same-site=strict

# HTTP → HTTPS 리다이렉트 설정
server.tomcat.redirect-context-root=true&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 WebConfig.java - HTTP/HTTPS 커넥터 설정&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;파일 위치:&lt;/strong&gt; &lt;code&gt;src/main/java/com/yourcompany/config/WebConfig.java&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Spring Boot의 WebConfig 클래스에서 HTTP(80)과 HTTPS(443) 두 포트를 동시에 리스닝하도록 설정합니다.&lt;/p&gt;
&lt;p&gt;HTTP 포트(80)에 들어온 모든 요청은 자동으로 HTTPS(443)로 리다이렉트됩니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;설정 내용:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTTP Connector: 포트 80에 수신&lt;/li&gt;
&lt;li&gt;Redirect Port: 443으로 설정 (HTTP → HTTPS 자동 리다이렉트)&lt;/li&gt;
&lt;li&gt;HTTPS Connector: 포트 443에서 TLS/SSL 보안 연결 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;3. HTTP → HTTPS 리다이렉트 동작 원리&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;┌──────────────────────────────────────┐
│ 사용자: http://{domain} 입력         │
│ (포트 80 자동 사용)                   │
└────────────┬─────────────────────────┘
             │
             ▼
┌──────────────────────────────────────┐
│ Spring Boot HTTP Connector(80) 수신  │
│ (WebConfig에서 설정)                 │
└────────────┬─────────────────────────┘
             │
             ▼
┌──────────────────────────────────────┐
│ HTTP 301 Redirect 응답               │
│ Location: https://{domain}/          │
└────────────┬─────────────────────────┘
             │
             ▼
┌───────────────��──────────────────────┐
│ 브라우저 자동 재요청                 │
│ https://{domain}                     │
└────────────┬─────────────────────────┘
             │
             ▼
┌──────────────────────────────────────┐
│ Spring Boot HTTPS(443) + TLS 연결    │
│ 보안 연결 확립 ✅                    │
│ (application.properties 인증서 로드)  │
└──────────────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;4. Docker에서 인증서 볼륨 마운트 설정&lt;/h2&gt;
&lt;h3&gt;4.1 docker run 명령어 (GitHub Actions)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker run -d \
  --name career-lens \
  -p 80:80 \
  -p 443:443 \
  -e SPRING_PROFILES_ACTIVE=prd \
  -e DB_USERNAME={YOUR_DB_USER} \
  -e DB_PASSWORD={YOUR_DB_PASSWORD} \
  -v /etc/letsencrypt:/etc/letsencrypt:ro \
  {DOCKER_IMAGE}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;핵심 옵션 설명:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-p 80:80&lt;/code&gt; — HTTP 포트 80을 컨테이너 포트 80으로 매핑&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-p 443:443&lt;/code&gt; — HTTPS 포트 443을 컨테이너 포트 443으로 매핑&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-v /etc/letsencrypt:/etc/letsencrypt:ro&lt;/code&gt; — &lt;strong&gt;호스트의 인증서를 읽기 전용으로 마운트&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.2 Docker 볼륨 마운트 구조&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;호스트 서버 (/etc/letsencrypt)
├── live/{YOUR_DOMAIN}/
│   ├── fullchain.pem        ← Spring Boot가 읽음
│   ├── privkey.pem          ← Spring Boot가 읽음
│   └── ...
│
└─→ Docker 컨테이너 (볼륨 마운트, 읽기 전용)
    └── /etc/letsencrypt/live/{YOUR_DOMAIN}/
        ├── fullchain.pem    ← application.properties에서 참조
        ├── privkey.pem      ← application.properties에서 참조
        └── ...&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;이점:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;인증서 갱신 후 Docker 이미지 재빌드 불필요&lt;/li&gt;
&lt;li&gt;컨테이너 재시작만으로 새 인증서 적용&lt;/li&gt;
&lt;li&gt;Spring Boot는 application.properties의 경로에서 자동으로 인증서 로드&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;5. Certbot 자동갱신 설정&lt;/h2&gt;
&lt;h3&gt;5.1 Post-hook 스크립트 생성&lt;/h3&gt;
&lt;p&gt;인증서 갱신 후 자동으로 실행될 스크립트를 생성합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# renewal-hooks/post 디렉토리 생성
sudo mkdir -p /etc/letsencrypt/renewal-hooks/post

# 스크립트 파일 생성
sudo nano /etc/letsencrypt/renewal-hooks/post/renew-docker.sh&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;스크립트 내용:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
# Certbot 인증서 갱신 후 Docker 컨테이너 재시작
# 새 인증서가 /etc/letsencrypt에 저장됨
# Docker 재시작 → Spring Boot가 자동으로 새 인증서 로드

docker restart career-lens || true&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실행 권한 부여:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo chmod +x /etc/letsencrypt/renewal-hooks/post/renew-docker.sh&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.2 Certbot renewal 설정 확인&lt;/h3&gt;
&lt;p&gt;Certbot이 자동으로 생성한 renewal 설정 파일을 확인합니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo cat /etc/letsencrypt/renewal/{YOUR_DOMAIN}.conf&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;결과 예시:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# renew_before_expiry = 30 days
version = 1.22.0
archive_dir = /etc/letsencrypt/archive/{YOUR_DOMAIN}
cert = /etc/letsencrypt/live/{YOUR_DOMAIN}/cert.pem
privkey = /etc/letsencrypt/live/{YOUR_DOMAIN}/privkey.pem
chain = /etc/letsencrypt/live/{YOUR_DOMAIN}/chain.pem
fullchain = /etc/letsencrypt/live/{YOUR_DOMAIN}/fullchain.pem

[renewalparams]
account = {ACCOUNT_ID}
server = https://acme-v02.api.letsencrypt.org/directory
authenticator = standalone&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;5.3 Systemd 타이머 활성화 (자동갱신)&lt;/h3&gt;
&lt;p&gt;Certbot이 매달 정기적으로 자동 갱신되도록 Systemd 타이머를 활성화합니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 타이머 활성화
sudo systemctl enable certbot-renew.timer
sudo systemctl start certbot-renew.timer

# 상태 확인
sudo systemctl status certbot-renew.timer

# 다음 갱신 예정시간 확인
sudo systemctl list-timers certbot-renew.timer&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;결과 예시:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NEXT                         LEFT     LAST PASSED UNIT                ACTIVATES
Fri 2026-04-22 11:23:08 UTC  89 days  n/a  n/a    certbot-renew.timer certbot-renew.service&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;6. 자동갱신 테스트&lt;/h2&gt;
&lt;h3&gt;6.1 Dry-run (갱신 시뮬레이션)&lt;/h3&gt;
&lt;p&gt;실제 갱신 없이 모든 과정을 테스트합니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo certbot renew --dry-run --verbose&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;성공 결과:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Congratulations, all simulated renewals succeeded:
  /etc/letsencrypt/live/{YOUR_DOMAIN}/fullchain.pem (success)

Running post-hook command: /etc/letsencrypt/renewal-hooks/post/renew-docker.sh
Hook &amp;#39;post-hook&amp;#39; ran with output:
 career-lens&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;6.2 인증서 상태 확인&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo certbot certificates&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;결과 예시:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Found the following certs:
  Certificate Name: {YOUR_DOMAIN}
    Serial Number: 588e7ef361b38882fab9a321d5ca8e9d620
    Key Type: RSA
    Domains: {YOUR_DOMAIN}
    Expiry Date: 2026-04-22 11:23:08+00:00 (VALID: 89 days)
    Certificate Path: /etc/letsencrypt/live/{YOUR_DOMAIN}/fullchain.pem
    Private Key Path: /etc/letsencrypt/live/{YOUR_DOMAIN}/privkey.pem&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;7. 최종 자동갱신 흐름도&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────┐
│ 1️⃣ Systemd Timer 발동                                       │
│    (매달 자동으로 정기적인 시간에 실행)                       │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│ 2️⃣ Certbot이 Let&amp;#39;s Encrypt에 갱신 요청                      │
│    (인증서 유효기간 30일 전부터 자동 갱신)                    │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│ 3️⃣ 새 인증서 다운로드 및 설치                               │
│    /etc/letsencrypt/live/{YOUR_DOMAIN}/ 업데이트             │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│ 4️⃣ Post-hook 스크립트 실행                                 │
│    /etc/letsencrypt/renewal-hooks/post/renew-docker.sh      │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│ 5️⃣ Docker 컨테이너 자동 재시작                              │
│    docker restart career-lens                               │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│ 6️⃣ Spring Boot 시작                                        │
│    application.properties에서 자동으로 새 인증서 로드:        │
│    - /etc/letsencrypt/live/{YOUR_DOMAIN}/fullchain.pem     │
│    - /etc/letsencrypt/live/{YOUR_DOMAIN}/privkey.pem       │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│ 7️⃣ ✅ HTTPS 서빙 시작                                      │
│    새 인증서 적용 완료                                       │
│    Docker 이미지 재빌드 불필요!                            │
└────────────────────────────────────────────────────��────────┘&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2&gt;8. 모니터링 및 확인&lt;/h2&gt;
&lt;h3&gt;8.1 인증서 상태 확인&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo certbot certificates&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;8.2 Certbot 로그 확인&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo tail -50 /var/log/letsencrypt/letsencrypt.log&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;8.3 Docker 컨테이너 상태 확인&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker ps | grep career-lens
docker logs career-lens&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;8.4 HTTPS 접속 테스트&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -v https://{YOUR_DOMAIN}

# 예상 결과:
# HTTP/2 200 (보안 연결 성공)&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;9. 핵심 체크리스트&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;완료&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Certbot 설치&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Let&amp;#39;s Encrypt 인증서 발급&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;application.properties SSL 설정&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebConfig.java HTTP/HTTPS 커넥터 설정&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker 볼륨 마운트 설정&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Post-hook 스크립트 생성&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Systemd 타이머 활성화&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dry-run 테스트 성공&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;hr&gt;
&lt;h2&gt;10. 트러블슈팅&lt;/h2&gt;
&lt;h3&gt;Q: 포트 80, 443 충돌&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 포트 사용 프로세스 확인
sudo lsof -i :80
sudo lsof -i :443

# 프로세스 종료
sudo kill -9 &amp;lt;PID&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Q: Docker 컨테이너가 재시작되지 않음&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 스크립트 실행 권한 확인
ls -la /etc/letsencrypt/renewal-hooks/post/renew-docker.sh

# Docker 권한 확인
sudo whoami  # root 권한 필요&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Q: 인증서가 업데이트되지 않음&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Certbot 갱신 강제 실행
sudo certbot renew --force-renewal --verbose

# 타이머 상태 확인
sudo systemctl status certbot-renew.timer
sudo journalctl -u certbot-renew.service -n 50&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Q: Spring Boot가 인증서를 읽지 못함&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 인증서 파일 권한 확인
sudo chmod 755 /etc/letsencrypt/live
sudo chmod 755 /etc/letsencrypt/archive

# Docker 로그 확인
docker logs career-lens | grep -i ssl&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;결론&lt;/h2&gt;
&lt;p&gt;Let&amp;#39;s Encrypt의 무료 SSL 인증서 + Certbot + Docker 볼륨 마운트의 조합으로 &lt;strong&gt;이미지 재빌드 없이 인증서만 자동갱신&lt;/strong&gt;할 수 있습니다.&lt;/p&gt;
&lt;p&gt;Systemd 타이머가 매달 정기적으로 자동 갱신하므로, 개발자는 코드 개발에만 집중하고 인증서 관리는 완전히 자동화할 수 있습니다!  &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;최종 구성:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Certbot: 매달 자동 인증서 갱신&lt;/li&gt;
&lt;li&gt;Post-hook: 갱신 후 Docker 자동 재시작&lt;/li&gt;
&lt;li&gt;Spring Boot: application.properties에서 새 인증서 자동 로드&lt;/li&gt;
&lt;li&gt;사용자: 자동으로 HTTPS 보안 연결&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/etc</category>
      <category>Certbot</category>
      <category>HTTP</category>
      <category>https</category>
      <category>pem</category>
      <category>pkcs12</category>
      <category>SSL</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/179</guid>
      <comments>https://snapcode.tistory.com/entry/SSL-Spring-Boot-%EB%82%B4%EC%9E%A5-%EC%9B%B9%EC%84%9C%EB%B2%84%EC%97%90-Lets-Encrypt-%EC%9E%90%EB%8F%99%EA%B0%B1%EC%8B%A0-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-PEM-%EB%B0%A9%EC%8B%9D%EC%9D%98-%EB%AC%B4%ED%95%9C-%EA%B0%B1%EC%8B%A0-%EC%9E%90%EB%8F%99%ED%99%94#entry179comment</comments>
      <pubDate>Thu, 22 Jan 2026 21:44:40 +0900</pubDate>
    </item>
    <item>
      <title>[Next.js] 별도 FE 서버 없이 기존 백엔드에 웹 붙이기</title>
      <link>https://snapcode.tistory.com/entry/Nextjs-%EB%B3%84%EB%8F%84-FE-%EC%84%9C%EB%B2%84-%EC%97%86%EC%9D%B4-%EA%B8%B0%EC%A1%B4-%EB%B0%B1%EC%97%94%EB%93%9C%EC%97%90-%EC%9B%B9-%EB%B6%99%EC%9D%B4%EA%B8%B0</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;331&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpeAJ1/dJMcaiPAizr/4p5K3kMZluJZgYckJHVssk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpeAJ1/dJMcaiPAizr/4p5K3kMZluJZgYckJHVssk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpeAJ1/dJMcaiPAizr/4p5K3kMZluJZgYckJHVssk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpeAJ1%2FdJMcaiPAizr%2F4p5K3kMZluJZgYckJHVssk%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;331&quot; height=&quot;152&quot; data-origin-width=&quot;331&quot; data-origin-height=&quot;152&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h1&gt;FE 서버 없이 Spring Boot 하나로 PC&amp;middot;모바일 웹까지 배포하기&lt;/h1&gt;
&lt;a id=&quot;user-content-fe-서버-없이-spring-boot-하나로-pc모바일-웹까지-배포하기&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#fe-%EC%84%9C%EB%B2%84-%EC%97%86%EC%9D%B4-spring-boot-%ED%95%98%EB%82%98%EB%A1%9C-pc%EB%AA%A8%EB%B0%94%EC%9D%BC-%EC%9B%B9%EA%B9%8C%EC%A7%80-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;웹 서비스를 개발하면서 다음과 같은 요구사항이 있었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PC 웹과 모바일 웹을 모두 지원하고 싶다&lt;/li&gt;
&lt;li&gt;FE용 서버를 별도로 운영하고 싶지 않다&lt;/li&gt;
&lt;li&gt;기존 Spring Boot 프로젝트 안에서 FE 수정까지 함께 배포하고 싶다&lt;/li&gt;
&lt;li&gt;배포 단위를 하나로 유지하고 싶다&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 글에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Spring Boot 프로젝트 하나만 배포해도&lt;br /&gt;FE와 BE가 함께 반영되는 구조&lt;/b&gt;를 어떻게 만들었는지 정리한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 전체 구조 요약&lt;/h2&gt;
&lt;a id=&quot;user-content-1-전체-구조-요약&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#1-%EC%A0%84%EC%B2%B4-%EA%B5%AC%EC%A1%B0-%EC%9A%94%EC%95%BD&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 구조의 핵심은 단순하다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #59636e; text-align: start;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FE는 빌드 결과물만 사용하고&lt;br /&gt;실제 서비스는 Spring Boot가 담당한다&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;구조는 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FE: Next.js (Static Export)&lt;/li&gt;
&lt;li&gt;BE: Spring Boot&lt;/li&gt;
&lt;li&gt;배포 단위: 하나의 Spring Boot 애플리케이션&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;별도의 FE 서버나 프로젝트를 운영하지 않는다&lt;/b&gt;.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 왜 이런 구조를 선택했는가&lt;/h2&gt;
&lt;a id=&quot;user-content-2-왜-이런-구조를-선택했는가&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#2-%EC%99%9C-%EC%9D%B4%EB%9F%B0-%EA%B5%AC%EC%A1%B0%EB%A5%BC-%EC%84%A0%ED%83%9D%ED%96%88%EB%8A%94%EA%B0%80&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-1. 서버를 하나만 운영하고 싶었다&lt;/h3&gt;
&lt;a id=&quot;user-content-2-1-서버를-하나만-운영하고-싶었다&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#2-1-%EC%84%9C%EB%B2%84%EB%A5%BC-%ED%95%98%EB%82%98%EB%A7%8C-%EC%9A%B4%EC%98%81%ED%95%98%EA%B3%A0-%EC%8B%B6%EC%97%88%EB%8B%A4&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;FE 서버 + BE 서버를 나누면 다음 문제가 생긴다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배포 파이프라인이 2개로 늘어남&lt;/li&gt;
&lt;li&gt;환경 변수, 도메인 관리 복잡도 증가&lt;/li&gt;
&lt;li&gt;사이드 프로젝트 대비 과한 인프라&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트에서는&lt;br /&gt;&lt;b&gt;운영 복잡도를 최소화하는 것이 가장 중요한 기준&lt;/b&gt;이었다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-2. Spring Boot 중심 구조를 유지하고 싶었다&lt;/h3&gt;
&lt;a id=&quot;user-content-2-2-spring-boot-중심-구조를-유지하고-싶었다&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#2-2-spring-boot-%EC%A4%91%EC%8B%AC-%EA%B5%AC%EC%A1%B0%EB%A5%BC-%EC%9C%A0%EC%A7%80%ED%95%98%EA%B3%A0-%EC%8B%B6%EC%97%88%EB%8B%A4&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이미 Spring Boot 기반 프로젝트가 있었고,&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;백엔드 기준으로 잘 잡혀 있었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;FE를 별도 프로젝트로 분리하는 대신,&lt;br /&gt;&lt;b&gt;기존 Spring Boot 프로젝트의 &lt;span style=&quot;color: #ee2323;&quot;&gt;정적 리소스로 포함&lt;/span&gt;시키는 방식&lt;/b&gt;이 더 적합했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. FE 구성: Next.js를 Static Export로 사용하기&lt;/h2&gt;
&lt;a id=&quot;user-content-3-fe-구성-nextjs를-static-export로-사용하기&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#3-fe-%EA%B5%AC%EC%84%B1-nextjs%EB%A5%BC-static-export%EB%A1%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-1. 왜 Static Export인가&lt;/h3&gt;
&lt;a id=&quot;user-content-3-1-왜-static-export인가&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#3-1-%EC%99%9C-static-export%EC%9D%B8%EA%B0%80&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Next.js는 기본적으로 SSR 프레임워크지만,&lt;br /&gt;이번 구조에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;정적 파일 생성용으로만 사용&lt;/b&gt;했다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이유는 명확하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Boot에서 바로 서빙 가능&lt;/li&gt;
&lt;li&gt;Node 서버 불필요&lt;/li&gt;
&lt;li&gt;빌드 결과물이 HTML / JS / CSS로 떨어짐&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Spring Boot의 정적 리소스로 그대로 포함 가능&lt;/b&gt;하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Spring Boot에서 FE 서빙하기&lt;/h2&gt;
&lt;a id=&quot;user-content-4-spring-boot에서-fe-서빙하기&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#4-spring-boot%EC%97%90%EC%84%9C-fe-%EC%84%9C%EB%B9%99%ED%95%98%EA%B8%B0&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-1. 정적 리소스 위치&lt;/h3&gt;
&lt;a id=&quot;user-content-4-1-정적-리소스-위치&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#4-1-%EC%A0%95%EC%A0%81-%EB%A6%AC%EC%86%8C%EC%8A%A4-%EC%9C%84%EC%B9%98&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot는 기본적으로 다음 경로의 정적 파일을 자동으로 서빙한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;src/main/resources/static&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Next.js 빌드 결과물을&lt;br /&gt;이 경로로 복사하는 구조를 사용했다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;디렉터리 구조 예시&lt;/h3&gt;
&lt;a id=&quot;user-content-디렉터리-구조-예시&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#%EB%94%94%EB%A0%89%ED%84%B0%EB%A6%AC-%EA%B5%AC%EC%A1%B0-%EC%98%88%EC%8B%9C&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;pre class=&quot;axapta&quot; style=&quot;background-color: #f6f8fa; color: #1f2328;&quot;&gt;&lt;code&gt;spring-boot-project
└─ src
   └─ main
      └─ resources
         └─ static
            ├─ index.html
            ├─ _next/
            └─ assets/
&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빌드 결과물이 HTML / JS / CSS로 떨어짐&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Spring Boot의 정적 리소스로 그대로 포함 가능&lt;/b&gt;하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-2. Next.js 설정 핵심&lt;/h3&gt;
&lt;a id=&quot;user-content-3-2-nextjs-설정-핵심&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#3-2-nextjs-%EC%84%A4%EC%A0%95-%ED%95%B5%EC%8B%AC&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Next.js 설정에서 중요한 포인트는 다음이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;출력 방식: Static Export&lt;/li&gt;
&lt;li&gt;API Route 사용 ❌&lt;/li&gt;
&lt;li&gt;서버 컴포넌트 최소화&lt;/li&gt;
&lt;li&gt;순수 FE 역할만 담당&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;빌드 결과물은&lt;br /&gt;index.html,&lt;span&gt;&amp;nbsp;&lt;/span&gt;_next/,&lt;span&gt;&amp;nbsp;&lt;/span&gt;assets/&lt;span&gt;&amp;nbsp;&lt;/span&gt;형태의 정적 파일이 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Spring Boot에서 FE 서빙하기&lt;/h2&gt;
&lt;a id=&quot;user-content-4-spring-boot에서-fe-서빙하기-1&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#4-spring-boot%EC%97%90%EC%84%9C-fe-%EC%84%9C%EB%B9%99%ED%95%98%EA%B8%B0-1&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-1. 정적 리소스 위치&lt;/h3&gt;
&lt;a id=&quot;user-content-4-1-정적-리소스-위치-1&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#4-1-%EC%A0%95%EC%A0%81-%EB%A6%AC%EC%86%8C%EC%8A%A4-%EC%9C%84%EC%B9%98-1&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot는 기본적으로 다음 경로의 정적 파일을 자동으로 서빙한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;src/main/resources/static&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Next.js 빌드 결과물을&lt;br /&gt;이 경로로 복사하는 구조를 사용했다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;pre class=&quot;axapta&quot; style=&quot;background-color: #f6f8fa; color: #1f2328;&quot;&gt;&lt;code&gt;spring-boot-project
└─ src
└─ main
└─ resources
└─ static
├─ index.html
├─ _next/
└─ assets/
&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면&lt;br /&gt;&lt;b&gt;Spring Boot 애플리케이션 하나만 실행해도&lt;br /&gt;FE 화면이 바로 노출된다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-2. 라우팅 처리&lt;/h3&gt;
&lt;a id=&quot;user-content-4-2-라우팅-처리&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#4-2-%EB%9D%BC%EC%9A%B0%ED%8C%85-%EC%B2%98%EB%A6%AC&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;SPA 구조를 유지하기 위해&lt;br /&gt;모든 FE 라우팅 요청을&lt;span&gt;&amp;nbsp;&lt;/span&gt;index.html로 포워딩한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이를 통해:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PC 웹&lt;/li&gt;
&lt;li&gt;모바일 웹&lt;/li&gt;
&lt;li&gt;새로고침&lt;/li&gt;
&lt;li&gt;직접 URL 접근&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;모두 정상 동작하도록 구성했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. PC 웹 + 모바일 웹 대응 전략&lt;/h2&gt;
&lt;a id=&quot;user-content-5-pc-웹--모바일-웹-대응-전략&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#5-pc-%EC%9B%B9--%EB%AA%A8%EB%B0%94%EC%9D%BC-%EC%9B%B9-%EB%8C%80%EC%9D%91-%EC%A0%84%EB%9E%B5&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5-1. 별도 앱은 만들지 않는다&lt;/h3&gt;
&lt;a id=&quot;user-content-5-1-별도-앱은-만들지-않는다&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#5-1-%EB%B3%84%EB%8F%84-%EC%95%B1%EC%9D%80-%EB%A7%8C%EB%93%A4%EC%A7%80-%EC%95%8A%EB%8A%94%EB%8B%A4&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트에서는 다음을 선택했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;반응형 웹 ⭕&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5-2. 반응형 설계 방식&lt;/h3&gt;
&lt;a id=&quot;user-content-5-2-반응형-설계-방식&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#5-2-%EB%B0%98%EC%9D%91%ED%98%95-%EC%84%A4%EA%B3%84-%EB%B0%A9%EC%8B%9D&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모바일 퍼스트 레이아웃&lt;/li&gt;
&lt;li&gt;Media Query 기반 반응형&lt;/li&gt;
&lt;li&gt;Tailwind CSS Responsive Utility 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 방식의 장점은 명확하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&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;PC / 모바일 동시 대응&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유지보수 비용 최소화&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. FE 수정 &amp;rarr; BE 배포 = 전체 반영 구조&lt;/h2&gt;
&lt;a id=&quot;user-content-6-fe-수정--be-배포--전체-반영-구조&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#6-fe-%EC%88%98%EC%A0%95--be-%EB%B0%B0%ED%8F%AC--%EC%A0%84%EC%B2%B4-%EB%B0%98%EC%98%81-%EA%B5%AC%EC%A1%B0&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 구조의 가장 큰 장점은 이것이다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #59636e; text-align: start;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FE를 수정해도&lt;br /&gt;Spring Boot 한 번 배포하면 전부 반영된다&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;배포 흐름은 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;FE 코드 수정&lt;/li&gt;
&lt;li&gt;Next.js 빌드&lt;/li&gt;
&lt;li&gt;빌드 결과물을 Spring Boot static 경로에 포함&lt;/li&gt;
&lt;li&gt;Spring Boot 애플리케이션 빌드&lt;/li&gt;
&lt;li&gt;서버 배포&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;결과적으로&lt;br /&gt;&lt;b&gt;배포 단위는 항상 하나&lt;/b&gt;다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 이 구조의 장단점&lt;/h2&gt;
&lt;a id=&quot;user-content-7-이-구조의-장단점&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#7-%EC%9D%B4-%EA%B5%AC%EC%A1%B0%EC%9D%98-%EC%9E%A5%EB%8B%A8%EC%A0%90&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&gt;
&lt;a id=&quot;user-content-장점&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#%EC%9E%A5%EC%A0%90&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버 하나만 운영&lt;/li&gt;
&lt;li&gt;배포 파이프라인 단순&lt;/li&gt;
&lt;li&gt;기존 Spring Boot 구조 유지&lt;/li&gt;
&lt;li&gt;사이드 프로젝트에 최적화&lt;/li&gt;
&lt;li&gt;설명하기 쉬운 아키텍처&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점&lt;/h3&gt;
&lt;a id=&quot;user-content-단점&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#%EB%8B%A8%EC%A0%90&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SSR 사용 불가&lt;/li&gt;
&lt;li&gt;FE 빌드 시간이 BE 빌드에 포함됨&lt;/li&gt;
&lt;li&gt;FE/BE 완전 분리 구조는 아님&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이번 프로젝트의 목적에는&lt;br /&gt;&lt;b&gt;충분히 합리적인 트레이드오프&lt;/b&gt;라고 판단했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 마무리&lt;/h2&gt;
&lt;a id=&quot;user-content-8-마무리&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#8-%EB%A7%88%EB%AC%B4%EB%A6%AC&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 구조의 핵심은 단순하다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #59636e; text-align: start;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FE를 독립 서버로 운영하지 않고,&lt;br /&gt;Spring Boot 애플리케이션의 일부로 취급한다&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사이드 프로젝트나&lt;br /&gt;운영 부담을 줄이고 싶은 개인 프로젝트에서는&lt;br /&gt;&lt;b&gt;가장 현실적이고 설명 가능한 선택지&lt;/b&gt;라고 생각한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;앞으로도 프로젝트 규모와 목적에 따라&lt;br /&gt;&lt;b&gt;구조를 과하게 만들지 않는 선택&lt;/b&gt;을 계속 해나갈 예정이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;9. 구현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1단계:&amp;nbsp;Gradle&amp;nbsp;빌드&amp;nbsp;설정&amp;nbsp;-&amp;nbsp;FE&amp;nbsp;빌드&amp;nbsp;자동화 &lt;br /&gt;Spring&amp;nbsp;Boot&amp;nbsp;프로젝트의&amp;nbsp;build.gradle을&amp;nbsp;수정해서&amp;nbsp;Next.js&amp;nbsp;빌드&amp;nbsp;결과물을&amp;nbsp;자동으로&amp;nbsp;static&amp;nbsp;폴더에&amp;nbsp;복사하도록&amp;nbsp;설정&lt;/p&gt;
&lt;pre id=&quot;code_1768926147454&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// build.gradle 수정 내용
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.x.x'
    id 'io.spring.dependency-management' version '1.x.x'
}

// ... 기존 설정 ...

// ✅ FE 빌드 자동화 추가
task buildNextJS(type: Exec) {
    workingDir = file('frontend')
    commandLine = ['npm', 'run', 'build']
    onlyIf {
        file('frontend').exists()
    }
}

task copyNextJSBuild(type: Copy) {
    dependsOn buildNextJS
    from file('frontend/out')
    into file('src/main/resources/static')
    onlyIf {
        file('frontend/out').exists()
    }
}

// Spring Boot 빌드 전에 FE 빌드 + 복사 실행
processResources.dependsOn copyNextJSBuild&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2단계:&amp;nbsp;SPA&amp;nbsp;라우팅&amp;nbsp;처리&amp;nbsp;(Spring&amp;nbsp;Boot&amp;nbsp;Controller&amp;nbsp;설정) &lt;br /&gt;모든&amp;nbsp;FE&amp;nbsp;라우팅&amp;nbsp;요청을&amp;nbsp;index.html로&amp;nbsp;포워딩하는&amp;nbsp;설정&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;새&amp;nbsp;파일&amp;nbsp;생성:&amp;nbsp;src/main/java/com/example/config/WebConfig.java &lt;br /&gt;이&amp;nbsp;파일에서: &lt;br /&gt;- 정적 리소스 서빙 설정 &lt;br /&gt;- SPA 라우팅 포워딩 규칙 추가&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1768926356820&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 정적 리소스 처리 (_next, assets 등)
        registry.addResourceHandler(&quot;/**&quot;)
                .addResourceLocations(&quot;classpath:/static/&quot;)
                .setCachePeriod(3600);
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // SPA 라우팅: 정적 파일이 아닌 모든 경로를 index.html로 포워딩
        registry.addViewController(&quot;/{spring:\\w+}&quot;)
                .setViewName(&quot;forward:/index.html&quot;);
        registry.addViewController(&quot;/**/{spring:\\w+}&quot;)
                .setViewName(&quot;forward:/index.html&quot;);
        registry.addViewController(&quot;/**/{spring:\\w+}/**&quot;)
                .setViewName(&quot;forward:/index.html&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이&amp;nbsp;설정으로: &lt;br /&gt;✅&amp;nbsp;/&amp;nbsp;&amp;rarr;&amp;nbsp;index.html&amp;nbsp;로드 &lt;br /&gt;✅&amp;nbsp;/about&amp;nbsp;&amp;rarr;&amp;nbsp;index.html&amp;nbsp;로드&amp;nbsp;(새로고침&amp;nbsp;가능) &lt;br /&gt;✅&amp;nbsp;/_next/*&amp;nbsp;&amp;rarr;&amp;nbsp;정적&amp;nbsp;파일&amp;nbsp;직접&amp;nbsp;서빙 &lt;br /&gt;✅&amp;nbsp;/api/*&amp;nbsp;&amp;rarr;&amp;nbsp;컨트롤러로&amp;nbsp;라우팅&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3단계: Next.js 프로젝트 초기화 &lt;br /&gt;이제 frontend/ 디렉터리에 Next.js 프로젝트를 생성하고 Static Export로 설정&lt;/p&gt;
&lt;pre id=&quot;code_1768927815738&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx create-next-app@latest frontend --typescript --tailwind --no-eslint
cd frontend&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그 다음 next.config.ts를 수정:&lt;/p&gt;
&lt;pre id=&quot;code_1768927828403&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import type { NextConfig } from &quot;next&quot;;

const nextConfig: NextConfig = {
    /* config options here */
    output: &quot;export&quot;,
    reactStrictMode: true,
};

export default nextConfig;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;package.json&amp;nbsp;확인:&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768927896662&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;scripts&quot;: {
    &quot;build&quot;: &quot;next build&quot;,
    &quot;dev&quot;: &quot;next dev&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Next.js&amp;nbsp;프로젝트&amp;nbsp;생성완료&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;4단계: 통합 빌드 테스트&lt;/p&gt;
&lt;pre id=&quot;code_1768928294788&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;./gradlew clean build&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;빌드&amp;nbsp;성공&amp;nbsp;시: &lt;br /&gt;✅&amp;nbsp;frontend/out/&amp;nbsp;디렉터리&amp;nbsp;생성됨 &lt;br /&gt;✅&amp;nbsp;build/resources/main/static/&amp;nbsp;에&amp;nbsp;Next.js&amp;nbsp;정적&amp;nbsp;파일&amp;nbsp;복사됨 &lt;br /&gt;✅&amp;nbsp;build/libs/*.jar&amp;nbsp;생성됨 &lt;br /&gt;이제 java -jar build/libs/*.jar 실행하면 Spring Boot에서 Next.js 프론트엔드를 서빙&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;현재&amp;nbsp;상태&amp;nbsp;정리: &lt;br /&gt;✅&amp;nbsp;build.gradle&amp;nbsp;-&amp;nbsp;FE&amp;nbsp;빌드&amp;nbsp;자동화&amp;nbsp;설정&amp;nbsp;완료 &lt;br /&gt;✅&amp;nbsp;WebConfig.java&amp;nbsp;-&amp;nbsp;SPA&amp;nbsp;라우팅&amp;nbsp;설정&amp;nbsp;완료 &lt;br /&gt;✅&amp;nbsp;frontend/&amp;nbsp;-&amp;nbsp;Next.js&amp;nbsp;Static&amp;nbsp;Export&amp;nbsp;설정&amp;nbsp;완료 &lt;br /&gt;✅&amp;nbsp;./gradlew&amp;nbsp;clean&amp;nbsp;build&amp;nbsp;-&amp;nbsp;통합&amp;nbsp;빌드&amp;nbsp;완료 &lt;br /&gt;✅&amp;nbsp;java&amp;nbsp;-jar&amp;nbsp;build/libs/*.jar&amp;nbsp;-&amp;nbsp;JAR&amp;nbsp;실행&amp;nbsp;확인&amp;nbsp;완료&lt;/p&gt;
&lt;pre id=&quot;code_1768928515646&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 전체 CI/CD 흐름 정리

1. 커밋 발생 (master 브랜치)
   &amp;darr;
2. GitHub Actions 트리거 (docker-ci.yml)
   &amp;darr;
3. 환경 설정
   ├─ 코드 체크아웃
   ├─ JDK 17 설치
   ├─ Node.js 18 설치
   └─ gradlew 실행 권한 부여
   &amp;darr;
4. Gradle 빌드 (./gradlew build -x test)
   ├─ buildNextJS 태스크 실행
   │  └─ npm run build (Next.js 정적 빌드)
   ├─ copyNextJSBuild 태스크 실행
   │  └─ frontend/out &amp;rarr; src/main/resources/static 복사
   └─ Spring Boot JAR 생성 (FE 포함)
   &amp;darr;
5. Docker 이미지 빌드
   └─ Dockerfile로 JAR를 기반으로 이미지 생성
   &amp;darr;
6. Docker Hub 로그인 및 푸시
   └─ signalix:latest 이미지 업로드
   &amp;darr;
7. Oracle Cloud 서버 배포 (SSH)
   ├─ Docker 이미지 풀
   ├─ 기존 컨테이너 중지/제거
   └─ 새 컨테이너 실행 (포트 80, 환경변수 설정)
   &amp;darr;
8. 배포 완료 ✅
   └─ http://서버IP 접속 가능&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심: &lt;br /&gt;✅&amp;nbsp;FE&amp;nbsp;수정&amp;nbsp;&amp;rarr;&amp;nbsp;커밋&amp;nbsp;&amp;rarr;&amp;nbsp;자동&amp;nbsp;반영 &lt;br /&gt;✅&amp;nbsp;BE&amp;nbsp;수정&amp;nbsp;&amp;rarr;&amp;nbsp;커밋&amp;nbsp;&amp;rarr;&amp;nbsp;자동&amp;nbsp;반영 &lt;br /&gt;✅&amp;nbsp;둘&amp;nbsp;다&amp;nbsp;수정&amp;nbsp;&amp;rarr;&amp;nbsp;커밋&amp;nbsp;&amp;rarr;&amp;nbsp;하나의&amp;nbsp;JAR로&amp;nbsp;배포&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;현재&amp;nbsp;상태:&amp;nbsp;완전&amp;nbsp;자동화&amp;nbsp;완료!&amp;nbsp; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;625&quot; data-origin-height=&quot;722&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bz30Kq/dJMcahJYbOy/HMqnxzLhp2BKk7cUdy1vR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bz30Kq/dJMcahJYbOy/HMqnxzLhp2BKk7cUdy1vR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bz30Kq/dJMcahJYbOy/HMqnxzLhp2BKk7cUdy1vR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbz30Kq%2FdJMcahJYbOy%2FHMqnxzLhp2BKk7cUdy1vR1%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;625&quot; height=&quot;722&quot; data-origin-width=&quot;625&quot; data-origin-height=&quot;722&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;배포결과&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 서비스 소개 페이지처럼 만들어달라했더니.. 너무 예쁘게 해줬다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커밋 한번 하면, BE/FE 둘다 반영되어 앞으로 토이 프로젝트 개발이 진짜 편할 것 같다~ &lt;s&gt;(무료 서버 1개 치고는 만족)&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;950&quot; data-origin-height=&quot;909&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bekyB4/dJMcacWaLZW/eE0qPyKNrRqvUilF7PPX00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bekyB4/dJMcacWaLZW/eE0qPyKNrRqvUilF7PPX00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bekyB4/dJMcacWaLZW/eE0qPyKNrRqvUilF7PPX00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbekyB4%2FdJMcacWaLZW%2FeE0qPyKNrRqvUilF7PPX00%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;666&quot; height=&quot;637&quot; data-origin-width=&quot;950&quot; data-origin-height=&quot;909&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모바일도 ok&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;737&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LnDc9/dJMcachztIt/1bYEQ8cg6FTNBIX5r94V9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LnDc9/dJMcachztIt/1bYEQ8cg6FTNBIX5r94V9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LnDc9/dJMcachztIt/1bYEQ8cg6FTNBIX5r94V9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLnDc9%2FdJMcachztIt%2F1bYEQ8cg6FTNBIX5r94V9K%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;222&quot; height=&quot;434&quot; data-origin-width=&quot;737&quot; data-origin-height=&quot;1440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>IT/Next.js</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/178</guid>
      <comments>https://snapcode.tistory.com/entry/Nextjs-%EB%B3%84%EB%8F%84-FE-%EC%84%9C%EB%B2%84-%EC%97%86%EC%9D%B4-%EA%B8%B0%EC%A1%B4-%EB%B0%B1%EC%97%94%EB%93%9C%EC%97%90-%EC%9B%B9-%EB%B6%99%EC%9D%B4%EA%B8%B0#entry178comment</comments>
      <pubDate>Wed, 21 Jan 2026 08:55:10 +0900</pubDate>
    </item>
    <item>
      <title>[Github Copilot] java.lang.IllegalArgumentException: Argument for @NotNull parameter 'fileType'</title>
      <link>https://snapcode.tistory.com/entry/Github-Copilot-javalangIllegalArgumentException-Argument-for-NotNull-parameter-fileType</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[Github&amp;nbsp;Copilot]&amp;nbsp;java.lang.IllegalArgumentException:&amp;nbsp;Argument&amp;nbsp;for&amp;nbsp;@NotNull&amp;nbsp;parameter&amp;nbsp;'fileType'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;494&quot; data-origin-height=&quot;131&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZNi4C/dJMcagEi2LB/12fcV6diCsc6w34SknQqIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZNi4C/dJMcagEi2LB/12fcV6diCsc6w34SknQqIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZNi4C/dJMcagEi2LB/12fcV6diCsc6w34SknQqIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZNi4C%2FdJMcagEi2LB%2F12fcV6diCsc6w34SknQqIk%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;494&quot; height=&quot;131&quot; data-origin-width=&quot;494&quot; data-origin-height=&quot;131&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;인텔리제이(IntelliJ IDEA)를 사용하다가 갑자기 GitHub Copilot Chat에서 발생하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;java.lang.IllegalArgumentException&lt;span&gt;&amp;nbsp;&lt;/span&gt;오류로 당황하셨을 분들을 위해 해결 방법을 정리해 드립니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h1&gt;[오류 해결] IntelliJ x GitHub Copilot Chat: &quot;Argument for @NotNull parameter 'fileType'&quot;&lt;/h1&gt;
&lt;a id=&quot;user-content-오류-해결-intellij-x-github-copilot-chat-argument-for-notnull-parameter-filetype&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0-intellij-x-github-copilot-chat-argument-for-notnull-parameter-filetype&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;최근 GitHub Copilot Chat 기능을 사용하려 할 때 아래와 같은 에러 로그와 함께 기능이 멈추는 현상이 보고되고 있습니다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #59636e; text-align: start;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;java.lang.IllegalArgumentException: Argument for @NotNull parameter 'fileType' of ... must not be null&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;결론부터 말씀드리면, 이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;인텔리제이 본체 버전과 코파일럿 플러그인 버전 사이의 호환성 문제&lt;/b&gt;일 가능성이 매우 높습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 에러의 의미: 왜 이런 일이 발생할까?&lt;/h2&gt;
&lt;a id=&quot;user-content-1-에러의-의미-왜-이런-일이-발생할까&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#1-%EC%97%90%EB%9F%AC%EC%9D%98-%EC%9D%98%EB%AF%B8-%EC%99%9C-%EC%9D%B4%EB%9F%B0-%EC%9D%BC%EC%9D%B4-%EB%B0%9C%EC%83%9D%ED%95%A0%EA%B9%8C&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 에러를 기술적으로 한 줄 해석하면 다음과 같습니다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #59636e; text-align: start;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;IDE가 플러그인에게 '지금 무슨 파일 타입이야?'라고 물었는데, 플러그인이 '몰라(null)'라고 답해서 터진 상황&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;인텔리제이는 내부 API 메서드를 호출할 때&lt;span&gt;&amp;nbsp;&lt;/span&gt;@NotNull&lt;span&gt;&amp;nbsp;&lt;/span&gt;어노테이션을 통해 특정 파라미터(여기서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;fileType)가 절대 비어있으면 안 된다고 명시합니다. 하지만 플러그인이 IDE의 기대치와 다른 값을 넘겨주면서 예외가 발생합니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 &quot;업데이트 미루기&quot;가 원인이 될까?&lt;/h3&gt;
&lt;a id=&quot;user-content-왜-업데이트-미루기가-원인이-될까&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#%EC%99%9C-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8-%EB%AF%B8%EB%A3%A8%EA%B8%B0%EA%B0%80-%EC%9B%90%EC%9D%B8%EC%9D%B4-%EB%90%A0%EA%B9%8C&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;잦은 API 변경:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;IntelliJ는 버전이 올라갈 때마다 플러그인이 참조하는 내부 API를 자주 변경합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;코파일럿의 깊은 개입:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Copilot Chat은 단순히 채팅만 하는 게 아니라 에디터, 파일 타입, PSI(구문 분석 트리), UI 패널 등을 깊게 건드립니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;버전 불일치:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Copilot 플러그인은 최신 IDE 버전에 맞춰 업데이트되었는데, 사용하는 IntelliJ가 구버전이면 플러그인이 존재하지 않는 API를 호출하거나 잘못된 값을 전달하게 됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 내 코드의 문제일 확률은?&lt;/h2&gt;
&lt;a id=&quot;user-content-2-내-코드의-문제일-확률은&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#2-%EB%82%B4-%EC%BD%94%EB%93%9C%EC%9D%98-%EB%AC%B8%EC%A0%9C%EC%9D%BC-%ED%99%95%EB%A5%A0%EC%9D%80&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  0%에 가깝습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 에러는 여러분이 작성 중인 Java 코드의 문법 오류나 런타임 에러가 아닙니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;IDE(인텔리제이)라는 프로그램 자체와 확장 도구(코파일럿) 사이의 충돌&lt;/b&gt;일 뿐입니다. 안심하고 도구 설정만 손보시면 됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 가장 확실한 해결 순서 (Action Plan)&lt;/h2&gt;
&lt;a id=&quot;user-content-3-가장-확실한-해결-순서-action-plan&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#3-%EA%B0%80%EC%9E%A5-%ED%99%95%EC%8B%A4%ED%95%9C-%ED%95%B4%EA%B2%B0-%EC%88%9C%EC%84%9C-action-plan&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;문제를 해결하기 위해 아래 순서대로 진행해 보세요.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 1순위: IntelliJ IDEA 업데이트&lt;/h3&gt;
&lt;a id=&quot;user-content--1순위-intellij-idea-업데이트&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#-1%EC%88%9C%EC%9C%84-intellij-idea-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;가장 권장하는 방법입니다. 현재 사용 중인 메이저 버전을 확인하고 업데이트를 진행하세요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;방법:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Help&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Check for Updates&lt;span&gt;&amp;nbsp;&lt;/span&gt;(Mac은&lt;span&gt;&amp;nbsp;&lt;/span&gt;IntelliJ IDEA&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Check for Updates)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;팁:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;2024.1 버전을 쓰고 있다면 최소 2024.3 등 최신 패치 버전으로 올리는 것이 좋습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 2순위: Copilot 플러그인 업데이트&lt;/h3&gt;
&lt;a id=&quot;user-content--2순위-copilot-플러그인-업데이트&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#-2%EC%88%9C%EC%9C%84-copilot-%ED%94%8C%EB%9F%AC%EA%B7%B8%EC%9D%B8-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;IDE를 업데이트했다면 플러그인도 최신 상태인지 확인해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;방법:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Settings (Ctrl+Alt+S)&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Plugins&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Installed&lt;span&gt;&amp;nbsp;&lt;/span&gt;탭&lt;/li&gt;
&lt;li&gt;GitHub Copilot과&lt;span&gt;&amp;nbsp;&lt;/span&gt;GitHub Copilot Chat&lt;span&gt;&amp;nbsp;&lt;/span&gt;두 가지 모두 업데이트 버튼이 떠 있는지 확인하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 3순위: 캐시 무효화 (Invalidate Caches)&lt;/h3&gt;
&lt;a id=&quot;user-content--3순위-캐시-무효화-invalidate-caches&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#-3%EC%88%9C%EC%9C%84-%EC%BA%90%EC%8B%9C-%EB%AC%B4%ED%9A%A8%ED%99%94-invalidate-caches&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;업데이트 후에도 찌꺼기가 남아 에러가 발생할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;방법:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;File&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Invalidate Caches...&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&amp;gt; 모든 항목 체크 후&lt;span&gt;&amp;nbsp;&lt;/span&gt;Invalidate and Restart&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 언제 무시해도 되고, 언제 고쳐야 할까?&lt;/h2&gt;
&lt;a id=&quot;user-content-4-언제-무시해도-되고-언제-고쳐야-할까&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#4-%EC%96%B8%EC%A0%9C-%EB%AC%B4%EC%8B%9C%ED%95%B4%EB%8F%84-%EB%90%98%EA%B3%A0-%EC%96%B8%EC%A0%9C-%EA%B3%A0%EC%B3%90%EC%95%BC-%ED%95%A0%EA%B9%8C&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현상심각도조치&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot;&gt;
&lt;td&gt;로그에만 찍히고 사용엔 지장이 없다&lt;/td&gt;
&lt;td&gt;&lt;b&gt;낮음&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;시간 날 때 업데이트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #f6f8fa;&quot;&gt;
&lt;td&gt;채팅창이 하얗게 나오거나 응답이 없다&lt;/td&gt;
&lt;td&gt;&lt;b&gt;중간&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;플러그인 재시작 또는 업데이트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;background-color: #ffffff;&quot;&gt;
&lt;td&gt;&lt;b&gt;입력할 때마다 에러 팝업이 뜨고 IDE가 멈춘다&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;높음&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;즉시 업데이트 필수&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  요약 및 결론&lt;/h2&gt;
&lt;a id=&quot;user-content--요약-및-결론&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/signalix/new/master?filename=README.md#-%EC%9A%94%EC%95%BD-%EB%B0%8F-%EA%B2%B0%EB%A1%A0&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 에러는 **IntelliJ 업데이트를 안 해서 생길 가능성이 거의 99%**입니다. Copilot은 클라우드 기반 서비스라 서버 쪽 로직이 계속 변하고, 이에 맞춰 플러그인도 빠르게 업데이트됩니다. 이 속도를 IDE 본체가 따라가지 못해 생기는 일종의 '세대 차이' 에러라고 이해하시면 됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;지금 바로 인텔리제이를 업데이트하고 쾌적한 AI 페어 프로그래밍을 다시 즐겨보세요!&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;도움이 되셨나요?&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;혹시 현재 사용 중인 IntelliJ 버전에서 업데이트가 불가능한 특수한 상황(회사 보안 정책 등)이라면, 해당 버전에서 작동하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;안전한 이전 버전의 Copilot 플러그인 설치 방법&lt;/b&gt;을 추가로 가이드해 드릴 수 있습니다. 궁금하시면 댓글로 알려주세요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b42r42/dJMcadHwrDB/ciaHOSo1VTDfROsWMtmyFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b42r42/dJMcadHwrDB/ciaHOSo1VTDfROsWMtmyFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b42r42/dJMcadHwrDB/ciaHOSo1VTDfROsWMtmyFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb42r42%2FdJMcadHwrDB%2FciaHOSo1VTDfROsWMtmyFK%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;502&quot; height=&quot;224&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>IT/AI</category>
      <category>Argument for @NotNull parameter 'fileType'</category>
      <category>Copilot</category>
      <category>Github Copilot</category>
      <category>IllegalArgumentException</category>
      <category>방법</category>
      <category>에러</category>
      <category>오류</category>
      <category>코파일럿</category>
      <category>해결</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/177</guid>
      <comments>https://snapcode.tistory.com/entry/Github-Copilot-javalangIllegalArgumentException-Argument-for-NotNull-parameter-fileType#entry177comment</comments>
      <pubDate>Tue, 20 Jan 2026 23:27:21 +0900</pubDate>
    </item>
    <item>
      <title>[CICD] 커밋만 하면 배포되게 만들기: GitHub Actions + Docker CI/CD 삽질기</title>
      <link>https://snapcode.tistory.com/entry/CICD-GitHub-Actions-Docker-Hub-Oracle-Cloud-%EB%AC%B4%EB%A3%8C-%EC%84%9C%EB%B2%84%EB%A1%9C-%EB%B0%B0%ED%8F%AC%EA%B9%8C%EC%A7%80</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;313&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCvGNg/dJMcacaOOCe/kkUEyI0dZXvgqRbtt8PMz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCvGNg/dJMcacaOOCe/kkUEyI0dZXvgqRbtt8PMz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCvGNg/dJMcacaOOCe/kkUEyI0dZXvgqRbtt8PMz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCvGNg%2FdJMcacaOOCe%2FkkUEyI0dZXvgqRbtt8PMz1%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;557&quot; height=&quot;313&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;313&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h1 data-sourcepos=&quot;1:1-1:43&quot;&gt;커밋만 하면 배포되게 만들고 싶었다: GitHub Actions + Docker CI/CD 삽질기&lt;/h1&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-sourcepos=&quot;7:1-7:30&quot; data-ke-size=&quot;size26&quot;&gt;1. 글을 쓰게 된 배경&lt;/h2&gt;
&lt;a id=&quot;user-content-1-글을-쓰게-된-배경&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/ldh5574/edit/master/README.md#1-%EA%B8%80%EC%9D%84-%EC%93%B0%EA%B2%8C-%EB%90%9C-%EB%B0%B0%EA%B2%BD&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;9:1-9:177&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;Spring Boot 프로젝트를 하나 새로 시작하면서,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;커밋만 하면 자동으로 서버에 배포되는 구조&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;를 처음부터 제대로 만들어보고 싶었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;11:1-12:118&quot; data-ke-size=&quot;size16&quot;&gt;그동안 회사에서는 이미 잘 짜여진 CI/CD 파이프라인 위에서만 작업하다 보니, 정작 Docker, GitHub Actions, 서버 배포를 처음부터 끝까지 직접 구성해본 경험은 많지 않았다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;14:1-14:73&quot; data-ke-size=&quot;size16&quot;&gt;이번에 개인 프로젝트를 하면서 다음을 목표로 잡았다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;16:1-20:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;16:1-16:23&quot;&gt;Spring Boot + Java 17&lt;/li&gt;
&lt;li data-sourcepos=&quot;17:1-17:29&quot;&gt;GitHub Actions 기반 CI/CD&lt;/li&gt;
&lt;li data-sourcepos=&quot;18:1-18:40&quot;&gt;Docker Hub 이미지 빌드 및 푸시&lt;/li&gt;
&lt;li data-sourcepos=&quot;19:1-20:0&quot;&gt;Oracle Cloud 무료 서버에 컨테이너 배포&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;21:1-21:63&quot; data-ke-size=&quot;size16&quot;&gt;그리고&amp;hellip; 역시나&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;한 번에 될 리는 없었다&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt; &lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;23:1-23:55&quot; data-ke-size=&quot;size16&quot;&gt;이 글은 그 삽질 과정을 정리한 기록이다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-sourcepos=&quot;27:1-27:32&quot; data-ke-size=&quot;size26&quot;&gt;2. 전체 아키텍처 구성&lt;/h2&gt;
&lt;a id=&quot;user-content-2-전체-아키텍처-구성&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/ldh5574/edit/master/README.md#2-%EC%A0%84%EC%B2%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EA%B5%AC%EC%84%B1&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;29:1-29:46&quot; data-ke-size=&quot;size16&quot;&gt;구성은 아래와 같다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;pre class=&quot;less&quot; style=&quot;background-color: #f6f8fa; color: #1f2328;&quot;&gt;&lt;code&gt;[GitHub Repository]
        &amp;darr; (push to master)
[GitHub Actions]
        &amp;darr;
[Docker Image Build]
        &amp;darr;
[Docker Hub]
        &amp;darr;
[Oracle Cloud VM]
        &amp;darr;
[Spring Boot Container 실행]
&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;45:1-48:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;45:1-45:21&quot;&gt;브랜치:&lt;span&gt;&amp;nbsp;&lt;/span&gt;master&lt;/li&gt;
&lt;li data-sourcepos=&quot;46:1-46:19&quot;&gt;트리거:&lt;span&gt;&amp;nbsp;&lt;/span&gt;push&lt;/li&gt;
&lt;li data-sourcepos=&quot;47:1-48:0&quot;&gt;배포 방식: SSH 접속 후 docker pull &amp;amp; run&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-sourcepos=&quot;49:1-50:0&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-sourcepos=&quot;51:1-51:43&quot; data-ke-size=&quot;size26&quot;&gt;3. GitHub Actions 워크플로우 설정&lt;/h2&gt;
&lt;a id=&quot;user-content-3-github-actions-워크플로우-설정&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/ldh5574/edit/master/README.md#3-github-actions-%EC%9B%8C%ED%81%AC%ED%94%8C%EB%A1%9C%EC%9A%B0-%EC%84%A4%EC%A0%95&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-sourcepos=&quot;53:1-53:17&quot; data-ke-size=&quot;size23&quot;&gt;docker-ci.yml&lt;/h3&gt;
&lt;a id=&quot;user-content-docker-ciyml&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/ldh5574/edit/master/README.md#docker-ciyml&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;pre class=&quot;http&quot; style=&quot;background-color: #f6f8fa; color: #1f2328;&quot;&gt;&lt;code&gt;name: Docker CI/CD

on:
  push:
    branches:
      - master

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      - name: Build with Gradle
        run: ./gradlew build -x test

      - name: Log in to Docker Hub
        uses: docker/login-action@v2
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}

      - name: Build Docker image
        run: docker build -t ${{ secrets.DOCKER_USERNAME }}/signalix:latest .

      - name: Push Docker image
        run: docker push ${{ secrets.DOCKER_USERNAME }}/signalix:latest

      - name: Deploy to Oracle Cloud
        uses: appleboy/ssh-action@v0.1.8
        with:
          host: ${{ secrets.ORACLE_HOST }}
          username: ${{ secrets.ORACLE_USER }}
          key: ${{ secrets.ORACLE_SSH_PRIVATE_KEY }}
          port: 22
          script: |
            docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
            docker pull ${{ secrets.DOCKER_USERNAME }}/signalix:latest
            docker stop signalix || true
            docker rm signalix || true
            docker run -d \
              --name signalix \
              -p 80:80 \
              -e SPRING_PROFILES_ACTIVE=prd \
              -e DB_USERNAME=${{ secrets.DB_USERNAME }} \
              -e DB_PASSWORD=${{ secrets.DB_PASSWORD }} \
              ${{ secrets.DOCKER_USERNAME }}/signalix:latest&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #59636e; text-align: start;&quot; data-sourcepos=&quot;116:1-116:117&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-sourcepos=&quot;116:3-116:117&quot; data-ke-size=&quot;size16&quot;&gt;  초기 단계에서는 테스트 때문에 CI가 자주 깨질 수 있어서&lt;span&gt;&amp;nbsp;&lt;/span&gt;-x test&lt;span&gt;&amp;nbsp;&lt;/span&gt;옵션을 사용했다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-sourcepos=&quot;118:1-119:0&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-sourcepos=&quot;120:1-120:23&quot; data-ke-size=&quot;size26&quot;&gt;4. Dockerfile 설정&lt;/h2&gt;
&lt;a id=&quot;user-content-4-dockerfile-설정&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/ldh5574/edit/master/README.md#4-dockerfile-%EC%84%A4%EC%A0%95&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-sourcepos=&quot;122:1-122:21&quot; data-ke-size=&quot;size23&quot;&gt;최종 Dockerfile&lt;/h3&gt;
&lt;a id=&quot;user-content-최종-dockerfile&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/ldh5574/edit/master/README.md#%EC%B5%9C%EC%A2%85-dockerfile&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;pre class=&quot;dockerfile&quot; style=&quot;background-color: #f6f8fa; color: #1f2328;&quot;&gt;&lt;code&gt;FROM eclipse-temurin:17-jre-jammy

WORKDIR /app

COPY build/libs/*.jar app.jar

EXPOSE 80

ENTRYPOINT [&quot;java&quot;, &quot;-Duser.timezone=Asia/Seoul&quot;, &quot;-jar&quot;, &quot;app.jar&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-sourcepos=&quot;136:1-136:31&quot; data-ke-size=&quot;size23&quot;&gt;왜 eclipse-temurin 인가?&lt;/h3&gt;
&lt;a id=&quot;user-content-왜-eclipse-temurin-인가&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/ldh5574/edit/master/README.md#%EC%99%9C-eclipse-temurin-%EC%9D%B8%EA%B0%80&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;138:1-138:46&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 아래 이미지를 사용했다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #f6f8fa; color: #1f2328;&quot;&gt;&lt;code&gt;FROM openjdk:17-jdk-slim&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;144:1-144:61&quot; data-ke-size=&quot;size16&quot;&gt;하지만 GitHub Actions에서 아래 에러가 발생했다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;pre class=&quot;subunit&quot; style=&quot;background-color: #f6f8fa; color: #1f2328;&quot;&gt;&lt;code&gt;ERROR: docker.io/library/openjdk:17-jdk-slim: not found
&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;150:1-150:104&quot; data-ke-size=&quot;size16&quot;&gt;조사해보니&lt;span&gt;&amp;nbsp;&lt;/span&gt;openjdk&lt;span&gt;&amp;nbsp;&lt;/span&gt;공식 이미지 중 일부 태그가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;deprecated / 제거&lt;/b&gt;된 상태였다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;152:1-152:49&quot; data-ke-size=&quot;size16&quot;&gt;  현재 Java 17 + Spring Boot 기준으로는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;154:1-156:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;154:1-154:39&quot;&gt;eclipse-temurin이 사실상 표준&lt;/li&gt;
&lt;li data-sourcepos=&quot;155:1-156:0&quot;&gt;GitHub Actions, Docker Hub, 클라우드 환경 모두에서 안정적&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;157:1-157:33&quot; data-ke-size=&quot;size16&quot;&gt;이라는 결론에 도달했다.&lt;/p&gt;
&lt;hr data-sourcepos=&quot;159:1-160:0&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-sourcepos=&quot;161:1-161:36&quot; data-ke-size=&quot;size26&quot;&gt;5. 실제 삽질 포인트 정리&lt;/h2&gt;
&lt;a id=&quot;user-content-5-실제-삽질-포인트-정리&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/ldh5574/edit/master/README.md#5-%EC%8B%A4%EC%A0%9C-%EC%82%BD%EC%A7%88-%ED%8F%AC%EC%9D%B8%ED%8A%B8-%EC%A0%95%EB%A6%AC&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-sourcepos=&quot;163:1-163:25&quot; data-ke-size=&quot;size23&quot;&gt;1️⃣ exit code 126&lt;/h3&gt;
&lt;a id=&quot;user-content-1️⃣-exit-code-126&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/ldh5574/edit/master/README.md#1%EF%B8%8F%E2%83%A3-exit-code-126&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;pre class=&quot;awk&quot; style=&quot;background-color: #f6f8fa; color: #1f2328;&quot;&gt;&lt;code&gt;Process completed with exit code 126
&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;169:1-169:7&quot; data-ke-size=&quot;size16&quot;&gt;원인:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;171:1-172:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;171:1-172:0&quot;&gt;gradlew&lt;span&gt;&amp;nbsp;&lt;/span&gt;실행 권한 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;173:1-173:7&quot; data-ke-size=&quot;size16&quot;&gt;해결:&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;pre class=&quot;dockerfile&quot; style=&quot;background-color: #f6f8fa; color: #1f2328;&quot;&gt;&lt;code&gt;- run: chmod +x gradlew&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-sourcepos=&quot;179:1-180:0&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-sourcepos=&quot;181:1-181:48&quot; data-ke-size=&quot;size23&quot;&gt;2️⃣ Deploy 단계가 실행되지 않음&lt;/h3&gt;
&lt;a id=&quot;user-content-2️⃣-deploy-단계가-실행되지-않음&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/ldh5574/edit/master/README.md#2%EF%B8%8F%E2%83%A3-deploy-%EB%8B%A8%EA%B3%84%EA%B0%80-%EC%8B%A4%ED%96%89%EB%90%98%EC%A7%80-%EC%95%8A%EC%9D%8C&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;pre class=&quot;excel&quot; style=&quot;background-color: #f6f8fa; color: #1f2328;&quot;&gt;&lt;code&gt;Evaluating condition for step: success()
=&amp;gt; false
&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;188:1-188:7&quot; data-ke-size=&quot;size16&quot;&gt;원인:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;190:1-192:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;190:1-190:39&quot;&gt;앞 단계(Docker build)에서 실패&lt;/li&gt;
&lt;li data-sourcepos=&quot;191:1-192:0&quot;&gt;Deploy 단계는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;아예 실행되지 않음&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;193:1-193:78&quot; data-ke-size=&quot;size16&quot;&gt;  항상&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;가장 먼저 실패한 step 로그부터 확인&lt;/b&gt;해야 한다.&lt;/p&gt;
&lt;hr data-sourcepos=&quot;195:1-196:0&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-sourcepos=&quot;197:1-197:40&quot; data-ke-size=&quot;size23&quot;&gt;3️⃣ Docker 이미지 pull 실패&lt;/h3&gt;
&lt;a id=&quot;user-content-3️⃣-docker-이미지-pull-실패&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/ldh5574/edit/master/README.md#3%EF%B8%8F%E2%83%A3-docker-%EC%9D%B4%EB%AF%B8%EC%A7%80-pull-%EC%8B%A4%ED%8C%A8&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;199:1-199:7&quot; data-ke-size=&quot;size16&quot;&gt;원인:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;201:1-202:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;201:1-202:0&quot;&gt;잘못된 base image 태그&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;203:1-203:7&quot; data-ke-size=&quot;size16&quot;&gt;해결:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;205:1-206:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;205:1-206:0&quot;&gt;openjdk&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;eclipse-temurin&lt;span&gt;&amp;nbsp;&lt;/span&gt;교체&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-sourcepos=&quot;207:1-208:0&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-sourcepos=&quot;209:1-209:19&quot; data-ke-size=&quot;size26&quot;&gt;6. 배포 결과&lt;/h2&gt;
&lt;a id=&quot;user-content-6-배포-결과&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/ldh5574/edit/master/README.md#6-%EB%B0%B0%ED%8F%AC-%EA%B2%B0%EA%B3%BC&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;211:1-216:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;211:1-211:28&quot;&gt;master 브랜치에 커밋&lt;/li&gt;
&lt;li data-sourcepos=&quot;212:1-212:30&quot;&gt;GitHub Actions 자동 실행&lt;/li&gt;
&lt;li data-sourcepos=&quot;213:1-213:32&quot;&gt;Docker Hub에 이미지 생성&lt;/li&gt;
&lt;li data-sourcepos=&quot;214:1-214:57&quot;&gt;Oracle Cloud 서버에서 컨테이너 자동 재시작&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;313&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCvGNg/dJMcacaOOCe/kkUEyI0dZXvgqRbtt8PMz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCvGNg/dJMcacaOOCe/kkUEyI0dZXvgqRbtt8PMz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCvGNg/dJMcacaOOCe/kkUEyI0dZXvgqRbtt8PMz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCvGNg%2FdJMcacaOOCe%2FkkUEyI0dZXvgqRbtt8PMz1%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;557&quot; height=&quot;313&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;313&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;217:1-217:54&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr data-sourcepos=&quot;219:1-220:0&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-sourcepos=&quot;221:1-221:21&quot; data-ke-size=&quot;size26&quot;&gt;7. 마무리하며&lt;/h2&gt;
&lt;a id=&quot;user-content-7-마무리하며&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/ldh5574/edit/master/README.md#7-%EB%A7%88%EB%AC%B4%EB%A6%AC%ED%95%98%EB%A9%B0&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;223:1-223:38&quot; data-ke-size=&quot;size16&quot;&gt;이번에 느낀 점은 딱 하나다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #59636e; text-align: start;&quot; data-sourcepos=&quot;225:1-225:84&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-sourcepos=&quot;225:3-225:84&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CI/CD는 한 번에 되는 게 아니라, 로그를 읽으면서 차분히 하면 결국엔 완성된다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;227:1-227:90&quot; data-ke-size=&quot;size16&quot;&gt;특히 GitHub Actions는 에러 메시지를 굉장히 솔직하게 보여주기 때문에&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;229:1-231:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;229:1-229:27&quot;&gt;어디서 실패했는지&lt;/li&gt;
&lt;li data-sourcepos=&quot;230:1-231:0&quot;&gt;왜 다음 step이 실행되지 않았는지&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;232:1-232:58&quot; data-ke-size=&quot;size16&quot;&gt;를 차분히 따라가면 반드시 해결할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;234:1-234:40&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;242:1-242:96&quot; data-ke-size=&quot;size16&quot;&gt;  비슷한 삽질을 하고 있는 분들에게 이 글이 도움이 되었으면 좋겠다.&lt;/p&gt;</description>
      <category>IT/etc</category>
      <category>actions</category>
      <category>cicd</category>
      <category>docker</category>
      <category>github</category>
      <category>Oracle Cloud</category>
      <category>실행까지</category>
      <category>자동배포</category>
      <category>컨테이너</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/176</guid>
      <comments>https://snapcode.tistory.com/entry/CICD-GitHub-Actions-Docker-Hub-Oracle-Cloud-%EB%AC%B4%EB%A3%8C-%EC%84%9C%EB%B2%84%EB%A1%9C-%EB%B0%B0%ED%8F%AC%EA%B9%8C%EC%A7%80#entry176comment</comments>
      <pubDate>Tue, 20 Jan 2026 22:47:46 +0900</pubDate>
    </item>
    <item>
      <title>[CICD] Execute permission for Gradle Wrapper in CI workflow</title>
      <link>https://snapcode.tistory.com/entry/CICD-Execute-permission-for-Gradle-Wrapper-in-CI-workflow</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;github actions 126 에러 코드 발생시 해결 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;background-color: #ffffff; color: #59636e; text-align: start;&quot; href=&quot;https://github.com/ldh5574/signalix/actions/runs/21143910935/job/60804768362#step:5:18&quot;&gt;&lt;b&gt;build-and-deploy&lt;/b&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #59636e; text-align: start;&quot;&gt;
&lt;div data-target=&quot;annotation-message.annotationContainer&quot;&gt;
&lt;div&gt;Process completed with exit code 126.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;607&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d99dHs/dJMcadOfLVK/tcB2pPvhkLHQC5ywWmOcnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d99dHs/dJMcadOfLVK/tcB2pPvhkLHQC5ywWmOcnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d99dHs/dJMcadOfLVK/tcB2pPvhkLHQC5ywWmOcnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd99dHs%2FdJMcadOfLVK%2FtcB2pPvhkLHQC5ywWmOcnK%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;938&quot; height=&quot;607&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;607&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.github/workflows/docker-ci.yml 파일에 Gradle Wrapper 실행 권한 부여해주기 (3.1.처럼)&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;# 3.1. Gradle Wrapper 실행 권한 부여 (exit code 126 해결)
- name: Grant execute permission for gradlew
  run: chmod +x gradlew

# 3.2. Gradle 빌드 (초기엔 테스트 제외 권장)
- name: Build with Gradle
  # run: ./gradlew build # 전체 빌드 (테스트 포함)
  run: ./gradlew build -x test  # 테스트 제외하고 빌드&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;685&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wsAC6/dJMcafL8Ooi/7zGhJEd7OUxNf7G9htznXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wsAC6/dJMcafL8Ooi/7zGhJEd7OUxNf7G9htznXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wsAC6/dJMcafL8Ooi/7zGhJEd7OUxNf7G9htznXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwsAC6%2FdJMcafL8Ooi%2F7zGhJEd7OUxNf7G9htznXk%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;478&quot; data-origin-width=&quot;685&quot; data-origin-height=&quot;478&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>IT/etc</category>
      <category>126</category>
      <category>actions</category>
      <category>cicd</category>
      <category>CODE</category>
      <category>docker</category>
      <category>error</category>
      <category>exit</category>
      <category>github</category>
      <category>에러</category>
      <category>코드</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/175</guid>
      <comments>https://snapcode.tistory.com/entry/CICD-Execute-permission-for-Gradle-Wrapper-in-CI-workflow#entry175comment</comments>
      <pubDate>Tue, 20 Jan 2026 01:07:49 +0900</pubDate>
    </item>
    <item>
      <title>[DB] Supabase가 DBeaver에 연결되지 않는 경우 해결 방법</title>
      <link>https://snapcode.tistory.com/entry/DB-Supabase%EA%B0%80-DBeaver%EC%97%90-%EC%97%B0%EA%B2%B0%EB%90%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EA%B2%BD%EC%9A%B0-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;394&quot; data-origin-height=&quot;311&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k0FSo/dJMcahpDeYW/4shv2AIjP2UKmLZQHXkLjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k0FSo/dJMcahpDeYW/4shv2AIjP2UKmLZQHXkLjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k0FSo/dJMcahpDeYW/4shv2AIjP2UKmLZQHXkLjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk0FSo%2FdJMcahpDeYW%2F4shv2AIjP2UKmLZQHXkLjk%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;394&quot; height=&quot;311&quot; data-origin-width=&quot;394&quot; data-origin-height=&quot;311&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;699&quot; data-start=&quot;674&quot; data-ke-size=&quot;size16&quot;&gt;이와 함께 아래와 같은 안내 문구가 표시된다.&lt;/p&gt;
&lt;blockquote data-end=&quot;794&quot; data-start=&quot;701&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;794&quot; data-start=&quot;703&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Not IPv4 compatible&lt;/b&gt;&lt;br /&gt;Use Session Pooler if on a IPv4 network or purchase IPv4 add-on&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;836&quot; data-start=&quot;796&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 이 문구를 못봤어서 원인을 한참 헤맸다.&lt;/p&gt;
&lt;hr data-end=&quot;841&quot; data-start=&quot;838&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;851&quot; data-start=&quot;843&quot; data-ke-size=&quot;size26&quot;&gt;문제 원인&lt;/h2&gt;
&lt;h3 data-end=&quot;883&quot; data-start=&quot;853&quot; data-ke-size=&quot;size23&quot;&gt;Direct connection은 IPv6 전용&lt;/h3&gt;
&lt;p data-end=&quot;947&quot; data-start=&quot;885&quot; data-ke-size=&quot;size16&quot;&gt;Supabase의 &lt;b&gt;Direct connection&lt;/b&gt; 방식은 기본적으로 &lt;b&gt;IPv6 환경&lt;/b&gt;에서만 동작한다.&lt;/p&gt;
&lt;p data-end=&quot;975&quot; data-start=&quot;949&quot; data-ke-size=&quot;size16&quot;&gt;하지만 대부분의 개인 개발 환경은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1007&quot; data-start=&quot;977&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;986&quot; data-start=&quot;977&quot;&gt;가정용 인터넷&lt;/li&gt;
&lt;li data-end=&quot;994&quot; data-start=&quot;987&quot;&gt;개인 PC&lt;/li&gt;
&lt;li data-end=&quot;1007&quot; data-start=&quot;995&quot;&gt;일반 회사 네트워크&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1037&quot; data-start=&quot;1009&quot; data-ke-size=&quot;size16&quot;&gt;➡️ 거의 대부분 &lt;b&gt;IPv4-only 네트워크&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;1114&quot; data-start=&quot;1039&quot; data-ke-size=&quot;size16&quot;&gt;이 상태에서 Direct connection JDBC 주소를 그대로 사용하면&lt;br /&gt;DBeaver에서는 &lt;b&gt;연결이 실패할 수밖에 없다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-end=&quot;1119&quot; data-start=&quot;1116&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1155&quot; data-start=&quot;1121&quot; data-ke-size=&quot;size26&quot;&gt;해결 방법 1: Session Pooler 사용 (권장)&lt;/h2&gt;
&lt;p data-end=&quot;1217&quot; data-start=&quot;1157&quot; data-ke-size=&quot;size16&quot;&gt;Supabase는 IPv4 환경을 위한 &lt;b&gt;Session Pooler (PgBouncer)&lt;/b&gt; 를 제공한다.&lt;/p&gt;
&lt;h3 data-end=&quot;1240&quot; data-start=&quot;1219&quot; data-ke-size=&quot;size23&quot;&gt;Session Pooler 특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1327&quot; data-start=&quot;1242&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1260&quot; data-start=&quot;1242&quot;&gt;IPv4 환경에서도 연결 가능&lt;/li&gt;
&lt;li data-end=&quot;1297&quot; data-start=&quot;1261&quot;&gt;DBeaver, DataGrip 같은 DB Client에 적합&lt;/li&gt;
&lt;li data-end=&quot;1327&quot; data-start=&quot;1298&quot;&gt;Direct connection과 다른 포트 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1396&quot; data-start=&quot;1329&quot; data-ke-size=&quot;size16&quot;&gt;Supabase 콘솔에서&lt;br /&gt;Session Pooler 항목을 선택하면 다음과 같은 JDBC 정보를 확인할 수 있다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;jdbc:postgresql://aws-0-ap-northeast-2.pooler.supabase.com:6543/postgres ?user=postgres &amp;amp;password=[YOUR-PASSWORD] &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1562&quot; data-start=&quot;1525&quot; data-ke-size=&quot;size16&quot;&gt;이 주소를 그대로 DBeaver에 입력하면&lt;br /&gt;정상적으로 연결된다.&lt;/p&gt;
&lt;p data-end=&quot;1562&quot; data-start=&quot;1525&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1562&quot; data-start=&quot;1525&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1562&quot; data-start=&quot;1525&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;526&quot; data-origin-height=&quot;448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RWuvj/dJMcagK1Ifr/daJdnQpTJgWK4TWJGKmpH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RWuvj/dJMcagK1Ifr/daJdnQpTJgWK4TWJGKmpH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RWuvj/dJMcagK1Ifr/daJdnQpTJgWK4TWJGKmpH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRWuvj%2FdJMcagK1Ifr%2FdaJdnQpTJgWK4TWJGKmpH0%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;526&quot; height=&quot;448&quot; data-origin-width=&quot;526&quot; data-origin-height=&quot;448&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결 성공 화면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/DB</category>
      <category>DB</category>
      <category>dbeaver</category>
      <category>RDB</category>
      <category>supabase</category>
      <category>데이터베이스</category>
      <category>방법</category>
      <category>연결</category>
      <category>해결</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/174</guid>
      <comments>https://snapcode.tistory.com/entry/DB-Supabase%EA%B0%80-DBeaver%EC%97%90-%EC%97%B0%EA%B2%B0%EB%90%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EA%B2%BD%EC%9A%B0-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95#entry174comment</comments>
      <pubDate>Thu, 15 Jan 2026 23:48:27 +0900</pubDate>
    </item>
    <item>
      <title>[DB] BaaS형 postgres 툴 추천 (개인 프로젝트에 강추)</title>
      <link>https://snapcode.tistory.com/entry/DB-SaaS%ED%98%95-postgres-%ED%88%B4-%EC%B6%94%EC%B2%9C-%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90-%EA%B0%95%EC%B6%94</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[DB]&amp;nbsp;BaaS형&amp;nbsp;postgres&amp;nbsp;툴&amp;nbsp;추천&amp;nbsp;(개인&amp;nbsp;프로젝트에&amp;nbsp;강추)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://supabase.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://supabase.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1768485994426&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;Supabase | The Postgres Development Platform.&quot; data-og-description=&quot;Build production-grade applications with a Postgres database, Authentication, instant APIs, Realtime, Functions, Storage and Vector embeddings. Start for free.&quot; data-og-host=&quot;supabase.com&quot; data-og-source-url=&quot;https://supabase.com/&quot; data-og-url=&quot;https://supabase.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Xo104/dJMb8T9SVXG/HKWjjksJgsQ0YFlX43d2K0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://supabase.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://supabase.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Xo104/dJMb8T9SVXG/HKWjjksJgsQ0YFlX43d2K0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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;Supabase | The Postgres Development Platform.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Build production-grade applications with a Postgres database, Authentication, instant APIs, Realtime, Functions, Storage and Vector embeddings. Start for free.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;supabase.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;&lt;b&gt;웹에서 DBeaver처럼 DB작업 가능하고, Connection String까지 제공해준다.&lt;/b&gt;&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1117&quot; data-origin-height=&quot;743&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chsh4R/dJMcafyzwz5/ilFf9GeQiMq49Kf2PPXSj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chsh4R/dJMcafyzwz5/ilFf9GeQiMq49Kf2PPXSj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chsh4R/dJMcafyzwz5/ilFf9GeQiMq49Kf2PPXSj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fchsh4R%2FdJMcafyzwz5%2FilFf9GeQiMq49Kf2PPXSj0%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;1117&quot; height=&quot;743&quot; data-origin-width=&quot;1117&quot; data-origin-height=&quot;743&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h1 data-sourcepos=&quot;1:1-1:49&quot;&gt;[DB] SaaS형 PostgreSQL 툴 추천 &amp;ndash; Supabase&lt;/h1&gt;
&lt;a id=&quot;user-content-db-saas형-postgresql-툴-추천--supabase&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/ldh5574/edit/master/README.md#db-saas%ED%98%95-postgresql-%ED%88%B4-%EC%B6%94%EC%B2%9C--supabase&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;3:1-4:101&quot; data-ke-size=&quot;size16&quot;&gt;개인 프로젝트나 사이드 프로젝트를 진행하다 보면&lt;br /&gt;&lt;b&gt;DB 구축과 운영 자체가 목적이 아니라, 기능 구현이 목적&lt;/b&gt;인 경우가 많다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;6:1-7:56&quot; data-ke-size=&quot;size16&quot;&gt;이럴 때 직접 DB 서버를 띄우고, 보안 설정하고, 백업까지 관리하는 방식은&lt;br /&gt;생각보다 많은 시간과 에너지를 소모한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;9:1-10:21&quot; data-ke-size=&quot;size16&quot;&gt;이런 상황에서 유용하게 사용할 수 있는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;SaaS형 PostgreSQL 서비스&lt;/b&gt;가 바로&lt;br /&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Supabase&lt;/b&gt;다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;12:1-12:39&quot; data-ke-size=&quot;size16&quot;&gt;공식 사이트:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://supabase.com/&quot;&gt;https://supabase.com/&lt;/a&gt;&lt;/p&gt;
&lt;hr data-sourcepos=&quot;14:1-15:0&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-sourcepos=&quot;16:1-16:15&quot; data-ke-size=&quot;size26&quot;&gt;Supabase란?&lt;/h2&gt;
&lt;a id=&quot;user-content-supabase란&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/ldh5574/edit/master/README.md#supabase%EB%9E%80&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;18:1-18:93&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Supabase는 PostgreSQL 기반의 오픈소스 BaaS(Backend as a Service)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;플랫폼이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;20:1-20:24&quot; data-ke-size=&quot;size16&quot;&gt;한 줄로 요약하면:&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #ffffff; color: #59636e; text-align: start;&quot; data-sourcepos=&quot;22:1-22:109&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-sourcepos=&quot;22:3-22:109&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;PostgreSQL을 기반으로 인증, API, 스토리지까지 한 번에 제공하는 DB 중심 SaaS&amp;rdquo;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;24:1-25:94&quot; data-ke-size=&quot;size16&quot;&gt;단순히 DB만 제공하는 것이 아니라,&lt;br /&gt;&lt;b&gt;백엔드 개발에 필요한 핵심 기능들을 함께 제공&lt;/b&gt;하는 것이 특징이다.&lt;/p&gt;
&lt;hr data-sourcepos=&quot;27:1-28:0&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-sourcepos=&quot;29:1-29:41&quot; data-ke-size=&quot;size26&quot;&gt;왜 PostgreSQL 기반이 중요한가?&lt;/h2&gt;
&lt;a id=&quot;user-content-왜-postgresql-기반이-중요한가&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/ldh5574/edit/master/README.md#%EC%99%9C-postgresql-%EA%B8%B0%EB%B0%98%EC%9D%B4-%EC%A4%91%EC%9A%94%ED%95%9C%EA%B0%80&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;31:1-32:86&quot; data-ke-size=&quot;size16&quot;&gt;Supabase의 가장 큰 장점 중 하나는&lt;br /&gt;&lt;b&gt;DB가 추상화된 NoSQL이 아니라, &amp;lsquo;진짜 PostgreSQL&amp;rsquo;이라는 점&lt;/b&gt;이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;34:1-37:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;34:1-34:26&quot;&gt;표준 SQL 사용 가능&lt;/li&gt;
&lt;li data-sourcepos=&quot;35:1-35:62&quot;&gt;기존 PostgreSQL 문법, 함수, 인덱스 그대로 사용&lt;/li&gt;
&lt;li data-sourcepos=&quot;36:1-37:0&quot;&gt;로컬 PostgreSQL &amp;rarr; Supabase 이전이 쉬움&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;38:1-38:83&quot; data-ke-size=&quot;size16&quot;&gt;즉,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;학습용이나 포트폴리오용으로도 경험이 그대로 쌓인다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-sourcepos=&quot;40:1-41:0&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-sourcepos=&quot;42:1-42:25&quot; data-ke-size=&quot;size26&quot;&gt;Supabase 주요 기능&lt;/h2&gt;
&lt;a id=&quot;user-content-supabase-주요-기능&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/ldh5574/edit/master/README.md#supabase-%EC%A3%BC%EC%9A%94-%EA%B8%B0%EB%8A%A5&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-sourcepos=&quot;44:1-44:25&quot; data-ke-size=&quot;size23&quot;&gt;1. Managed PostgreSQL&lt;/h3&gt;
&lt;a id=&quot;user-content-1-managed-postgresql&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/ldh5574/edit/master/README.md#1-managed-postgresql&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;46:1-49:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;46:1-46:33&quot;&gt;PostgreSQL 최신 버전 기반&lt;/li&gt;
&lt;li data-sourcepos=&quot;47:1-47:26&quot;&gt;자동 백업 및 관리&lt;/li&gt;
&lt;li data-sourcepos=&quot;48:1-49:0&quot;&gt;웹 콘솔에서 테이블 / 쿼리 관리 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;50:1-50:56&quot; data-ke-size=&quot;size16&quot;&gt;  &amp;ldquo;DB 관리&amp;rdquo;보다 &amp;ldquo;개발&amp;rdquo;에 집중 가능&lt;/p&gt;
&lt;hr data-sourcepos=&quot;52:1-53:0&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-sourcepos=&quot;54:1-54:40&quot; data-ke-size=&quot;size23&quot;&gt;2. 인증(Auth) 기능 기본 제공&lt;/h3&gt;
&lt;a id=&quot;user-content-2-인증auth-기능-기본-제공&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/ldh5574/edit/master/README.md#2-%EC%9D%B8%EC%A6%9Dauth-%EA%B8%B0%EB%8A%A5-%EA%B8%B0%EB%B3%B8-%EC%A0%9C%EA%B3%B5&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;56:1-59:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;56:1-56:18&quot;&gt;Email / Password&lt;/li&gt;
&lt;li data-sourcepos=&quot;57:1-57:28&quot;&gt;OAuth (Google, GitHub 등)&lt;/li&gt;
&lt;li data-sourcepos=&quot;58:1-59:0&quot;&gt;JWT 기반 인증&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;60:1-61:69&quot; data-ke-size=&quot;size16&quot;&gt;  별도의 인증 서버 없이도&lt;br /&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;서비스 수준의 인증 구조를 빠르게 구성 가능&lt;/b&gt;&lt;/p&gt;
&lt;hr data-sourcepos=&quot;63:1-64:0&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-sourcepos=&quot;65:1-65:41&quot; data-ke-size=&quot;size23&quot;&gt;3. Auto-generated REST &amp;amp; Realtime API&lt;/h3&gt;
&lt;a id=&quot;user-content-3-auto-generated-rest--realtime-api&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/ldh5574/edit/master/README.md#3-auto-generated-rest--realtime-api&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;67:1-69:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;67:1-67:51&quot;&gt;테이블 생성만 해도 REST API 자동 생성&lt;/li&gt;
&lt;li data-sourcepos=&quot;68:1-69:0&quot;&gt;실시간 데이터 변경 구독(Reatime) 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;70:1-71:44&quot; data-ke-size=&quot;size16&quot;&gt;  CRUD API를 직접 만들지 않아도 됨&lt;br /&gt;  프론트엔드와 바로 연동 가능&lt;/p&gt;
&lt;hr data-sourcepos=&quot;73:1-74:0&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-sourcepos=&quot;75:1-75:59&quot; data-ke-size=&quot;size23&quot;&gt;4. 무료 플랜 제공 (개인 프로젝트에 충분)&lt;/h3&gt;
&lt;a id=&quot;user-content-4-무료-플랜-제공-개인-프로젝트에-충분&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/ldh5574/edit/master/README.md#4-%EB%AC%B4%EB%A3%8C-%ED%94%8C%EB%9E%9C-%EC%A0%9C%EA%B3%B5-%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90-%EC%B6%A9%EB%B6%84&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;77:1-80:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;77:1-77:18&quot;&gt;Free tier 제공&lt;/li&gt;
&lt;li data-sourcepos=&quot;78:1-78:76&quot;&gt;소규모 트래픽, 개인 프로젝트, 포트폴리오 용도로 충분&lt;/li&gt;
&lt;li data-sourcepos=&quot;79:1-80:0&quot;&gt;카드 없이 시작 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;81:1-81:69&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;학생 / 취준생 / 사이드 프로젝트에 특히 적합&lt;/b&gt;&lt;/p&gt;
&lt;hr data-sourcepos=&quot;83:1-84:0&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-sourcepos=&quot;85:1-85:33&quot; data-ke-size=&quot;size26&quot;&gt;이런 경우에 특히 추천&lt;/h2&gt;
&lt;a id=&quot;user-content-이런-경우에-특히-추천&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/ldh5574/edit/master/README.md#%EC%9D%B4%EB%9F%B0-%EA%B2%BD%EC%9A%B0%EC%97%90-%ED%8A%B9%ED%9E%88-%EC%B6%94%EC%B2%9C&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;87:1-91:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;87:1-87:39&quot;&gt;개인 프로젝트 / 포트폴리오&lt;/li&gt;
&lt;li data-sourcepos=&quot;88:1-88:45&quot;&gt;빠르게 MVP를 만들어야 하는 경우&lt;/li&gt;
&lt;li data-sourcepos=&quot;89:1-89:61&quot;&gt;DB 운영보다 기능 구현에 집중하고 싶은 경우&lt;/li&gt;
&lt;li data-sourcepos=&quot;90:1-91:0&quot;&gt;PostgreSQL 경험을 쌓고 싶은 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;92:1-92:10&quot; data-ke-size=&quot;size16&quot;&gt;반대로,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;94:1-97:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;94:1-94:21&quot;&gt;대규모 트래픽&lt;/li&gt;
&lt;li data-sourcepos=&quot;95:1-95:40&quot;&gt;커스터마이징이 많은 DB 튜닝&lt;/li&gt;
&lt;li data-sourcepos=&quot;96:1-97:0&quot;&gt;복잡한 인프라 설계가 목적&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;98:1-98:68&quot; data-ke-size=&quot;size16&quot;&gt;이라면 직접 DB를 운영하는 편이 더 적합할 수 있다.&lt;/p&gt;
&lt;hr data-sourcepos=&quot;100:1-101:0&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-sourcepos=&quot;102:1-102:39&quot; data-ke-size=&quot;size26&quot;&gt;실제 사용해보며 느낀 장점&lt;/h2&gt;
&lt;a id=&quot;user-content-실제-사용해보며-느낀-장점&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/ldh5574/edit/master/README.md#%EC%8B%A4%EC%A0%9C-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EB%A9%B0-%EB%8A%90%EB%82%80-%EC%9E%A5%EC%A0%90&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;104:1-108:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;104:1-104:35&quot;&gt;초기 세팅이 매우 빠르다&lt;/li&gt;
&lt;li data-sourcepos=&quot;105:1-105:30&quot;&gt;콘솔 UI가 직관적이다&lt;/li&gt;
&lt;li data-sourcepos=&quot;106:1-106:49&quot;&gt;PostgreSQL 기반이라 학습 가치가 높다&lt;/li&gt;
&lt;li data-sourcepos=&quot;107:1-108:0&quot;&gt;&amp;ldquo;임시 DB&amp;rdquo;가 아니라 &amp;ldquo;실제 서비스 DB&amp;rdquo;로 써도 무방하다&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;109:1-110:55&quot; data-ke-size=&quot;size16&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;단순한 토이 프로젝트를 넘어,&lt;br /&gt;&amp;lsquo;설계 경험&amp;rsquo;까지 가져갈 수 있는 도구&lt;/b&gt;&lt;/p&gt;
&lt;hr data-sourcepos=&quot;112:1-113:0&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h2 data-sourcepos=&quot;114:1-114:12&quot; data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;a id=&quot;user-content-마무리&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/ldh5574/edit/master/README.md#%EB%A7%88%EB%AC%B4%EB%A6%AC&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;116:1-118:99&quot; data-ke-size=&quot;size16&quot;&gt;Supabase는&lt;br /&gt;**&amp;ldquo;DB를 직접 관리하는 부담은 줄이고,&lt;br /&gt;PostgreSQL의 장점은 그대로 가져가고 싶은 개발자&amp;rdquo;**에게 잘 맞는 선택이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;120:1-122:48&quot; data-ke-size=&quot;size16&quot;&gt;특히 개인 프로젝트나 취업 준비용 프로젝트에서&lt;br /&gt;&lt;b&gt;빠른 개발 + 실무 감각&lt;/b&gt;을 동시에 가져가고 싶다면&lt;br /&gt;한 번쯤은 충분히 써볼 가치가 있다.&lt;/p&gt;
&lt;hr data-sourcepos=&quot;124:1-125:0&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;div style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;
&lt;h3 data-sourcepos=&quot;126:1-126:17&quot; data-ke-size=&quot;size23&quot;&gt;참고 링크&lt;/h3&gt;
&lt;a id=&quot;user-content-참고-링크&quot; style=&quot;background-color: #000000; color: #0969da;&quot; href=&quot;https://github.com/ldh5574/ldh5574/edit/master/README.md#%EC%B0%B8%EA%B3%A0-%EB%A7%81%ED%81%AC&quot;&gt;&lt;/a&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-sourcepos=&quot;127:1-128:42&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;127:1-127:50&quot;&gt;Supabase 공식 사이트:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://supabase.com/&quot;&gt;https://supabase.com/&lt;/a&gt;&lt;/li&gt;
&lt;li data-sourcepos=&quot;128:1-128:42&quot;&gt;Supabase Docs:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://supabase.com/docs&quot;&gt;https://supabase.com/docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1768486319211&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Supabase Docs&quot; data-og-description=&quot;Supabase is the Postgres development platform providing all the backend features you need to build a product.&quot; data-og-host=&quot;supabase.com&quot; data-og-source-url=&quot;https://supabase.com/docs&quot; data-og-url=&quot;https://supabase.com/docs&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dIJJX3/dJMb8862seJ/ek1mRiW68EkKWrYe0Y3U1K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/sNXaj/dJMb9aKyAw7/WFPDhqve45S3CHDEAxfkM0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://supabase.com/docs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://supabase.com/docs&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dIJJX3/dJMb8862seJ/ek1mRiW68EkKWrYe0Y3U1K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/sNXaj/dJMb9aKyAw7/WFPDhqve45S3CHDEAxfkM0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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;Supabase Docs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Supabase is the Postgres development platform providing all the backend features you need to build a product.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;supabase.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보너스&lt;/p&gt;
&lt;h2 data-end=&quot;263&quot; data-start=&quot;235&quot; data-ke-size=&quot;size26&quot;&gt;1️⃣ SaaS vs BaaS 차이 (핵심만)&lt;/h2&gt;
&lt;h3 data-end=&quot;297&quot; data-start=&quot;265&quot; data-ke-size=&quot;size23&quot;&gt;SaaS (Software as a Service)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;333&quot; data-start=&quot;298&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;317&quot; data-start=&quot;298&quot;&gt;&lt;b&gt;완성된 소프트웨어&lt;/b&gt;를 제공&lt;/li&gt;
&lt;li data-end=&quot;333&quot; data-start=&quot;318&quot;&gt;사용자는 기능을 &amp;ldquo;쓴다&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;338&quot; data-start=&quot;335&quot; data-ke-size=&quot;size16&quot;&gt;예시:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;370&quot; data-start=&quot;339&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;347&quot; data-start=&quot;339&quot;&gt;Notion&lt;/li&gt;
&lt;li data-end=&quot;355&quot; data-start=&quot;348&quot;&gt;Slack&lt;/li&gt;
&lt;li data-end=&quot;362&quot; data-start=&quot;356&quot;&gt;Jira&lt;/li&gt;
&lt;li data-end=&quot;370&quot; data-start=&quot;363&quot;&gt;Figma&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;396&quot; data-start=&quot;372&quot; data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;개발하지 않아도 바로 사용 가능&lt;/b&gt;&lt;/p&gt;
&lt;hr data-end=&quot;401&quot; data-start=&quot;398&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;434&quot; data-start=&quot;403&quot; data-ke-size=&quot;size23&quot;&gt;BaaS (Backend as a Service)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;483&quot; data-start=&quot;435&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;461&quot; data-start=&quot;435&quot;&gt;&lt;b&gt;개발자를 위한 백엔드 구성 요소&lt;/b&gt; 제공&lt;/li&gt;
&lt;li data-end=&quot;483&quot; data-start=&quot;462&quot;&gt;인증, DB, API, 스토리지 등&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;488&quot; data-start=&quot;485&quot; data-ke-size=&quot;size16&quot;&gt;예시:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;521&quot; data-start=&quot;489&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;499&quot; data-start=&quot;489&quot;&gt;Supabase&lt;/li&gt;
&lt;li data-end=&quot;510&quot; data-start=&quot;500&quot;&gt;Firebase&lt;/li&gt;
&lt;li data-end=&quot;521&quot; data-start=&quot;511&quot;&gt;Appwrite&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;544&quot; data-start=&quot;523&quot; data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;서비스를 만들기 위한 재료&lt;/b&gt;&lt;/p&gt;</description>
      <category>IT/DB</category>
      <category>Baas</category>
      <category>DB</category>
      <category>postgres</category>
      <category>RDB</category>
      <category>RDBMS</category>
      <category>supabase</category>
      <category>개인프로젝트</category>
      <category>개인플젝</category>
      <category>데이터베이스</category>
      <category>디비</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/173</guid>
      <comments>https://snapcode.tistory.com/entry/DB-SaaS%ED%98%95-postgres-%ED%88%B4-%EC%B6%94%EC%B2%9C-%EA%B0%9C%EC%9D%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90-%EA%B0%95%EC%B6%94#entry173comment</comments>
      <pubDate>Thu, 15 Jan 2026 23:08:57 +0900</pubDate>
    </item>
    <item>
      <title>[AI] 국내외 AI 전사 확산 도입 사례 모음</title>
      <link>https://snapcode.tistory.com/entry/AI-%EA%B5%AD%EB%82%B4%EC%99%B8-AI-%EC%A0%84%EC%82%AC-%ED%99%95%EC%82%B0-%EB%8F%84%EC%9E%85-%EC%82%AC%EB%A1%80-%EB%AA%A8%EC%9D%8C</link>
      <description>&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;503&quot; data-end=&quot;632&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1792&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZ1Etf/dJMcajnnSRj/9HUy9mbGkY5mkQPpQOGRmk/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZ1Etf/dJMcajnnSRj/9HUy9mbGkY5mkQPpQOGRmk/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZ1Etf/dJMcajnnSRj/9HUy9mbGkY5mkQPpQOGRmk/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZ1Etf%2FdJMcajnnSRj%2F9HUy9mbGkY5mkQPpQOGRmk%2Fimg.webp&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;1792&quot; height=&quot;1024&quot; data-origin-width=&quot;1792&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;503&quot; data-end=&quot;632&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 id=&quot;국내_기업_사례&quot; style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;국내 기업 사례&lt;/b&gt;&lt;/h3&gt;
&lt;table style=&quot;background-color: #ffffff; color: #172b4d; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #9b9b9b; color: #ffffff; text-align: center;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #9b9b9b; color: #ffffff; text-align: center;&quot;&gt;&lt;b&gt;기업 명&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #9b9b9b; color: #ffffff; text-align: center;&quot;&gt;&lt;b&gt;프로그램 명&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #9b9b9b; color: #ffffff; text-align: center;&quot;&gt;&lt;b&gt;주요 특징&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #9b9b9b; color: #ffffff; text-align: center;&quot;&gt;&lt;b&gt;세부 내용&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #9b9b9b; color: #ffffff; text-align: center;&quot;&gt;&lt;b&gt;출처&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #f4f5f7; text-align: center;&quot; rowspan=&quot;8&quot; data-highlight-colour=&quot;#f4f5f7&quot;&gt;&lt;b&gt;교육&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;삼성전자&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;생성형 AI 파워 유저 교육 (2024.06~)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;전 임직원 대상&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;4단계 교육 과정
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;이론 교육:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;생성형 AI의 이해, 업무별 활용 사례&lt;/b&gt;&lt;span style=&quot;color: #1c1c1c;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;등&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;실습 교육:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;AI 도구 제작 등&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #1c1c1c;&quot;&gt;&lt;a href=&quot;https://www.sedaily.com/NewsView/2DE5EEKWR9&quot;&gt;https://www.sedaily.com/NewsView/2DE5EEKWR9&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef; text-align: center;&quot;&gt;&lt;b&gt;포스코그룹&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;&lt;b&gt;임원 대상 AI 체험 실습 워크숍 (2024.11)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;챗GPT 기본기 및 챗봇 생성 실습&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;외부 교육 기업(AI Ground)과 협업&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;&lt;a href=&quot;https://www.aiground.co.kr/posco-executive-ai-workshop/&quot;&gt;https://www.aiground.co.kr/posco-executive-ai-workshop/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef; text-align: center;&quot;&gt;&lt;b&gt;LG그룹&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;LG AI 대학원 (2025.09~)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #1c1c1c;&quot;&gt;사내 포털을 통해&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;석사 수준의 과목 제공&lt;/b&gt;(최신 AI Agent 트랙 포함)&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #1c1c1c;&quot;&gt;현업 데이터를 활용한 실전형&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;AI 인재 자체 육성&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;+&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;외부 인재 유치&lt;/b&gt;(중장기) 목적&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;수료 시 LG AI 대학원 자체 명의의 석&amp;middot;박사(교육부 인가 후 국가 공인)&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #1c1c1c;&quot;&gt;교수진: LG AI Research 내부 박사&amp;middot;임원급 연구자 20여 명 + 외부 석학&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #1c1c1c;&quot;&gt;&lt;a href=&quot;https://www.lgresearch.ai/aigraduateschool/introduction&quot;&gt;https://www.lgresearch.ai/aigraduateschool/introduction&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef; text-align: center;&quot; rowspan=&quot;3&quot;&gt;&lt;b&gt;LG전자&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;&lt;b&gt;생성형 AI 사내 교육 프로그램&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;초급 과정 - 생성형 AI의 이해 및 활용 사례&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;중급 과정 - 챗GPT, GPTs를 활용한 효율적인 업무 수행 방법&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;오프라인 교육도 개설 (2025.02~)&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot; rowspan=&quot;3&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;https://live.lge.co.kr/2502-lg-aigptedu/&quot;&gt;https://live.lge.co.kr/2502-lg-aigptedu/&lt;/a&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef; text-align: left;&quot;&gt;&lt;b&gt;임원 대상 AI 및 SW 교육 (2024.05~09)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;최신 기술 트렌드 교육
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;AI 기술이 적용된 제품 사례&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;새로운 콘텐츠를 만드는 생성형 AI&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef; text-align: left;&quot;&gt;&lt;b&gt;AI 기술 트렌드 온라인 세미나 (2024.11)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;MIT&amp;middot;코넬대 교수 등 전문가 초청&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;최신 기술 동향 소개&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef; text-align: center;&quot;&gt;&lt;b&gt;한화그룹&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;임원 대상 생성형 AI 역량 강화 교육 (2024.09)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #172b4d;&quot;&gt;챗GPT 사용 업무 효율화 방법 교육 (프롬프트 엔지니어링 등)&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;외부 교육 기업(&lt;span style=&quot;color: #172b4d;&quot;&gt;주식회사 사용성연구소&lt;/span&gt;)과 협업&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://www.theuxlabs.com/hanhwa-ai-education/&quot;&gt;https://www.theuxlabs.com/hanhwa-ai-education/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef; text-align: center;&quot;&gt;&lt;b&gt;넷마블&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;&lt;b&gt;Game Developer Forum (GDF)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;내&amp;middot;외부 전문가 강연 (지식 공유 목적)&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #232323;&quot;&gt;2024년에는 LLM, AI 관련 실무 적용 사례도 소개&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;&lt;a href=&quot;https://ch.netmarble.com/ESG/EverybodysESG/Detail?post_seq=5374&amp;amp;bbs_code=1015&quot;&gt;https://ch.netmarble.com/ESG/EverybodysESG/Detail?post_seq=5374&amp;amp;bbs_code=1015&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #c1c7d0; text-align: center;&quot; rowspan=&quot;6&quot; data-highlight-colour=&quot;#c1c7d0&quot;&gt;&lt;b&gt;해커톤&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;포스코그룹&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;WX 해커톤 (2024.12)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;WX&lt;/b&gt;: 포스코 그룹 고유의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;DX 기반 일하는 방식 변화&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;그룹사 혼합 팀 구성
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;AI 솔루션 공동 개발&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;외부 교육 기업(팀스파르타)과 협업&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://b2b.spartacodingclub.kr/blog/posco-wx-hackathon-interview&quot;&gt;https://b2b.spartacodingclub.kr/blog/posco-wx-hackathon-interview&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef; text-align: center;&quot;&gt;&lt;b&gt;LG전자&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;&lt;b&gt;전사 생성형 AI 해커톤 (2024.08)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;생성형 AI 기반 반복 업무 효율화 아이디어 발굴&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;수상작:&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;span&gt;1.&amp;nbsp;글로벌 맞춤형 광고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;콘텐츠 자동 제작&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span&gt;2. VOC&amp;middot;리뷰를 실시간 분석해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;고객경험 개선점&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;추출&lt;/span&gt;&lt;br /&gt;&lt;span&gt;3.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;SW 개발 문서 자동화&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;파이프라인&lt;/span&gt;&lt;br /&gt;&lt;span&gt;&amp;rarr; 현업에 적용&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;https://live.lge.co.kr/2412_lg_genai-hackerthon/&quot;&gt;https://live.lge.co.kr/2412_lg_genai-hackerthon/&lt;/a&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef; text-align: center;&quot; rowspan=&quot;2&quot;&gt;&lt;b&gt;카카오&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;사내 해커톤 &amp;lsquo;24K&amp;rsquo; (2024.06)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;&amp;lsquo;AI native company&amp;rsquo;&amp;nbsp;&lt;/b&gt;&lt;span style=&quot;color: #172b4d;&quot;&gt;라는 주제로 진행&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #172b4d;&quot;&gt;서비스를 포함한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;다양한 AI 기술 적용 아이디어&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;모집&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #172b4d;&quot;&gt;다양한 직군의 카카오 크루 170여 명 참여&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;AI 서비스 프로토타입 24시간 동안 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://www.kakaocorp.com/page/detail/11132&quot;&gt;https://www.kakaocorp.com/page/detail/11132&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef; text-align: left;&quot;&gt;&lt;b&gt;&lt;b&gt;사&lt;/b&gt;내 해커톤 &amp;lsquo;10K&amp;rsquo; (2025.06)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;&amp;rsquo;에이전틱 AI&amp;rsquo;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;주제로 바이브코딩 활용하여 진행&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;전 직군 대상&lt;/b&gt;으로 확대, 참여율 전년 대비 50% 증가&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;10시간으로 축소 &amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;짧은 시간 안에 MVP(최소 기능 제품) 구현&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;목표&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;&lt;a href=&quot;https://tech.kakao.com/posts/708&quot;&gt;https://tech.kakao.com/posts/708&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef; text-align: center;&quot;&gt;&lt;b&gt;카카오페이&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;사내 해커톤 &amp;lsquo;카페톤&amp;rsquo; (2025.04)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;AWS와 공동 진행&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;AWS 생성형 AI로 금융 서비스 개선 아이디어 제시&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;수상작:&lt;/li&gt;
&lt;/ul&gt;
&lt;span&gt;1.&amp;nbsp;고객 얼굴 + 결제 이력 인식 &amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;초(超)개인화 메뉴 추천&amp;middot;결제&lt;/b&gt;(RAG 활용)&lt;/span&gt;&lt;br /&gt;&lt;span&gt;2. 거래 내역 기반&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;이미지 생성&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; 일상 기록 서비스&lt;/span&gt;&lt;br /&gt;&lt;span&gt;3. 오디오 타입에 맞는 최적화된 스크립트와 유용한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;회의록을 생성&lt;/b&gt;&amp;nbsp;&amp;rarr; AI가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;카카오페이 내부 용어 학습&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://tech.kakaopay.com/post/kakaopay-dr-04/&quot;&gt;https://tech.kakaopay.com/post/kakaopay-dr-04/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef; text-align: center;&quot;&gt;&lt;b&gt;카카오엔터테인먼트&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;&lt;b&gt;사내 해커톤 '엔터톤' (2024.12)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;'AI와 함께하는 엔터테인먼트&amp;rsquo;&lt;/b&gt;를 주제로 전체 크루 대상 진행&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;수상작:&lt;/li&gt;
&lt;/ul&gt;
&lt;span&gt;1. 이용자들의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;음원 댓글 반응을 측정&lt;/b&gt;하는 AI 모델&lt;/span&gt;&lt;br /&gt;&lt;span&gt;2. 카카오페이지 웹툰, 웹소설&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;베스트 댓글을 가공하여 마케팅에 활용&lt;/b&gt;하는 AI&lt;/span&gt;&lt;br /&gt;&lt;span&gt;3. 아티스트 실시간 방송 댓글을 분석하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;직접 하이라이트 요약 영상을 제작&lt;/b&gt;하는 AI&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;&lt;a href=&quot;https://kakaoent.com/pr/detail/173&quot;&gt;https://kakaoent.com/pr/detail/173&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #f4f5f7; text-align: center;&quot; rowspan=&quot;3&quot; data-highlight-colour=&quot;#f4f5f7&quot;&gt;&lt;b&gt;사내&lt;br /&gt;제도 등&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;b&gt;삼성전자&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;AI 크루 제도 (2025.04~)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;300명 규모로 운영 (사업부 별로 모집)&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;현장 AI 과제 발굴 및 실행을 주도하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;선봉대 역할&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #1c1c1c;&quot;&gt;&lt;a href=&quot;https://news.nate.com/view/20250603n11503&quot;&gt;https://news.nate.com/view/20250603n11503&lt;/a&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef; text-align: center;&quot;&gt;&lt;b&gt;SK그룹&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;&lt;b&gt;AI 역량 인증 시험 &amp;lsquo;SKADA&amp;rsquo; (2023.06~)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #1c1c1c;&quot;&gt;사내 교육 플랫폼 '써니' + KAIST가 공동 개발&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;span style=&quot;color: #1c1c1c;&quot;&gt;해당 인증을 따면 사내 평가와 승진&amp;middot;인사이동 면에서 혜택&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9; text-align: left;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #1c1c1c;&quot;&gt;&lt;a href=&quot;https://www.sedaily.com/NewsView/2DE5EEKWR9&quot;&gt;https://www.sedaily.com/NewsView/2DE5EEKWR9&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef; text-align: center;&quot;&gt;&lt;b&gt;크래프톤&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;b&gt;AI Fellowship&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;대학생 대상&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;2개월 간 인턴십 + AI 프로젝트 경험 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://www.krafton.ai/ko/career/ai-fellowship/&quot;&gt;https://www.krafton.ai/ko/career/ai-fellowship/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;해외_기업_사례&quot; style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;해외 기업 사례&lt;/b&gt;&lt;/h3&gt;
&lt;table style=&quot;color: #333333; text-align: start; border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-style=&quot;style12&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #9b9b9b; color: #ffffff; text-align: center;&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #9b9b9b; color: #ffffff; text-align: center;&quot;&gt;&lt;b&gt;기업 명&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #9b9b9b; color: #ffffff; text-align: center;&quot;&gt;&lt;b&gt;&lt;span&gt;프로그램 명&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #9b9b9b; color: #ffffff; text-align: center;&quot;&gt;&lt;b&gt;주요 특징&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #9b9b9b; color: #ffffff; text-align: center;&quot;&gt;&lt;b&gt;&lt;span&gt;세부 내용&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #9b9b9b; color: #ffffff; text-align: center;&quot;&gt;&lt;b&gt;&lt;span&gt;출처&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef; text-align: center;&quot; rowspan=&quot;3&quot; data-highlight-colour=&quot;grey&quot;&gt;&lt;b&gt;편의&lt;br /&gt;기능&lt;br /&gt;/서비스&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Salesforce&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://agentexchange.salesforce.com/&quot;&gt;AgentExchange&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;(2025.03~)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;AI 에이전트 전용 마켓플레이스&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span&gt;&amp;rarr; 개발자, 파트너, 사용자 커뮤니티가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;AI 에이전트를 구축하고 수익화&lt;/b&gt;할 수 있는 플랫폼&lt;/span&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;200개 이상의 파트너&lt;/b&gt;(Google Cloud, Docusign, Workday 등)가 참여&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;Salesforce의 에이전트 개발 도구 '&lt;b&gt;Agent Builder'&lt;/b&gt;에 직접 통합되어 사용자 친화적 탐색 및 적용 가능&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;사전 구축된&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;AI 액션, 프롬프트 템플릿, 토픽, 에이전트 템플릿&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;제공&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;보안 검토와 고객 리뷰&lt;/b&gt;를 통과한 신뢰할 수 있는 컴포넌트만 등록&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;파트너 사례&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;Google Cloud&lt;/b&gt;: Vertex AI 기반 실시간 검색 가능 에이전트&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;Box&lt;/b&gt;: 비정형 데이터에서 인사이트 추출&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;Docusign&lt;/b&gt;: 문서 작성-서명-추적 자동화&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;Workday&lt;/b&gt;: 직원 온보딩, 복리후생 등 셀프서비스 자동화&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;a href=&quot;https://www.salesforce.com/news/press-releases/2025/03/04/agentexchange-announcement/&quot;&gt;https://www.salesforce.com/news/press-releases/2025/03/04/agentexchange-announcement/&lt;/a&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef;&quot;&gt;&lt;b&gt;Activision&lt;br /&gt;Blizzard&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;&lt;span&gt;&lt;b&gt;Blizzard&lt;br /&gt;Diffusion&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;사내 컨셉 아트 생성 AI 툴&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span&gt;&amp;rarr; 게임 환경, 캐릭터, 외형 디자인의 컨셉 아트 제작 지원&lt;/span&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;자체 데이터셋(월드 오브 워 크래프트, 디아블로 이미지 등) 학습&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;&lt;span&gt;&lt;a href=&quot;https://www.gamedeveloper.com/business/blizzard-says-new-ai-concept-art-tool-represents-major-evolution-&quot;&gt;https://www.gamedeveloper.com/business/blizzard-says-new-ai-concept-art-tool-represents-major-evolution-&lt;/a&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef;&quot;&gt;&lt;b&gt;Meta&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;Devmate&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;(2025.06~)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;사내 AI 코딩 어시스턴트&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;b&gt;Anthropic의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://wiki.smilegate.net/pages/viewpage.action?pageId=539619278&quot; data-linked-resource-id=&quot;539619278&quot; data-linked-resource-version=&quot;18&quot; data-linked-resource-type=&quot;page&quot; data-linked-resource-default-alias=&quot;Claude, 클로드&quot; data-base-url=&quot;https://wiki.smilegate.net&quot;&gt;Claude&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/a&gt;모델 활용&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;a href=&quot;https://www.digitaltoday.co.kr/news/articleView.html?idxno=570865&quot;&gt;https://www.digitaltoday.co.kr/news/articleView.html?idxno=570865&lt;/a&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef; text-align: center;&quot; rowspan=&quot;4&quot; data-highlight-colour=&quot;#c1c7d0&quot;&gt;&lt;b&gt;교육&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;&lt;b&gt;Microsoft&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;&lt;b&gt;Viva Learning - Copilot Academy&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;(2025.05~)&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;Viva Learning(직원 학습, 교육 플랫폼) 안에&amp;nbsp;&lt;b&gt;사내 전용 Copilot 학습 허브&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;자동 생성&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;관리자-맞춤 커리큘럼으로 전 직원 실습&amp;middot;평가&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/viva/learning/academy-copilot&quot;&gt;https://learn.microsoft.com/en-us/viva/learning/academy-copilot&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef;&quot; rowspan=&quot;2&quot;&gt;&lt;b&gt;Google&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://grow.google/ai-essentials/?utm_source=chatgpt.com&quot;&gt;AI Essentials 인증 코스&lt;/a&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;직원 및 외부 연수용 단기 코스(온라인)&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;5개 모듈(생산성&amp;middot;프롬프트&amp;middot;Responsible AI 등) 수료 과정, 수료증 지급&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://grow.google/ai-essentials/&quot;&gt;https://grow.google/ai-essentials/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef;&quot;&gt;&lt;b&gt;사내 교육 플랫폼 'Grow' 개편&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;(2025.06)&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;직원들의 AI 사용을 장려하기 위해 기존의 교육 과정을 대부분&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;AI 관련 과정으로 대체&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;&lt;a href=&quot;https://www.nbcnewyork.com/news/business/money-report/google-overhauls-internal-learning-platform-to-focus-on-ai-business-priorities/6297101/&quot;&gt;https://www.nbcnewyork.com/news/business/money-report/google-overhauls-internal-learning-platform-to-focus-on-ai-business-priorities/6297101/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef;&quot;&gt;&lt;b&gt;Amazon&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;AI Ready&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;(2023.11~)&lt;/td&gt;
&lt;td&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;2025년까지&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;200만명에게 무료로 AI 기술 교육을 제공&lt;/b&gt;하는 것을 목표로 시작&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;a style=&quot;color: #0070d1; text-align: left;&quot; href=&quot;https://cts.businesswire.com/ct/CT?id=smartlink&amp;amp;url=https%3A%2F%2Faws.amazon.com%2Feducation%2Fawseducate%2F&amp;amp;esheet=53860555&amp;amp;newsitemid=20231119572231&amp;amp;lan=en-US&amp;amp;anchor=AWS+Educate&amp;amp;index=3&amp;amp;md5=adf2ec929d6bf094cbcf6e716765cf59&amp;amp;_gl=1*1ldc4xe*_gcl_au*MTYwNzc3MjUwOS4xNzUwMjE2MTY3*_ga*NzAzNzU1NTQ1LjE3NTAyMTYxNjg.*_ga_ZQWF70T3FK*czE3NTAyMTYxNjckbzEkZzAkdDE3NTAyMTYxNjckajYwJGwwJGgw&quot;&gt;AWS Educate&lt;/a&gt;을 통해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;비즈니스/비기술 직무&lt;/b&gt;&amp;nbsp;참여자를 위한 과정 제공 (생성형 AI 기초 사용법 등)&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;a style=&quot;color: #0070d1; text-align: left;&quot; href=&quot;https://cts.businesswire.com/ct/CT?id=smartlink&amp;amp;url=https%3A%2F%2Fexplore.skillbuilder.aws%2Flearn%2Fcourse%2Fexternal%2Fview%2Felearning%2F17763%2Ffoundations-of-prompt-engineering&amp;amp;esheet=53860555&amp;amp;newsitemid=20231119572231&amp;amp;lan=en-US&amp;amp;anchor=AWS+Skill+Builder&amp;amp;index=6&amp;amp;md5=778b054d4106f072b7613f2c4f58a23d&amp;amp;_gl=1*1inxwbc*_gcl_au*MTYwNzc3MjUwOS4xNzUwMjE2MTY3*_ga*NzAzNzU1NTQ1LjE3NTAyMTYxNjg.*_ga_ZQWF70T3FK*czE3NTAyMTYxNjckbzEkZzAkdDE3NTAyMTYxNjckajYwJGwwJGgw&quot;&gt;AWS Skill Builder&lt;/a&gt;을 통해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;개발자/기술 직무&lt;/b&gt;&amp;nbsp;참여자를 위한 과정 제공 (언어모델 구축, 미세조정 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://www.businesswire.com/news/home/20231119572231/en/Amazon-Announces-AI-Ready-a-New-Initiative-Designed-to-Provide-Free-AI-Skills-Training-to-2-Million-People-by-2025&quot;&gt;https://www.businesswire.com/news/home/20231119572231/en/Amazon-Announces-AI-Ready-a-New-Initiative-Designed-to-Provide-Free-AI-Skills-Training-to-2-Million-People-by-2025&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef; text-align: center;&quot; data-highlight-colour=&quot;grey&quot;&gt;&lt;b&gt;해커톤&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;&lt;b&gt;Microsoft&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;&lt;b&gt;Copilot Studio Enterprise Agent Challenge&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;(2025.05~06)&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;3 주간 직원/파트너 약 7,500 명이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&amp;lsquo;업무 프로세스 자동화 에이전트&lt;/b&gt;&amp;rsquo; 시제품을 개발 (&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.microsoft.com/en-us/microsoft-copilot/microsoft-copilot-studio&quot;&gt;Copilot Studio&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;활용)&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;우수 팀은 CEO 타운홀에서 시연&lt;/li&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;Power CAT 팀 주도&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;&lt;a href=&quot;https://www.microsoft.com/en-us/microsoft-copilot/blog/copilot-studio/register-now-for-the-upcoming-copilot-studio-enterprise-agent-challenge/&quot;&gt;https://www.microsoft.com/en-us/microsoft-copilot/blog/copilot-studio/register-now-for-the-upcoming-copilot-studio-enterprise-agent-challenge/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT/AI</category>
      <category>ai</category>
      <category>it</category>
      <category>MCP</category>
      <category>개발</category>
      <category>국내</category>
      <category>도입</category>
      <category>모음</category>
      <category>사례</category>
      <category>전사</category>
      <category>해외</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/172</guid>
      <comments>https://snapcode.tistory.com/entry/AI-%EA%B5%AD%EB%82%B4%EC%99%B8-AI-%EC%A0%84%EC%82%AC-%ED%99%95%EC%82%B0-%EB%8F%84%EC%9E%85-%EC%82%AC%EB%A1%80-%EB%AA%A8%EC%9D%8C#entry172comment</comments>
      <pubDate>Thu, 15 Jan 2026 22:00:31 +0900</pubDate>
    </item>
    <item>
      <title>[코딜리티] FrogJmp</title>
      <link>https://snapcode.tistory.com/entry/%EC%BD%94%EB%94%9C%EB%A6%AC%ED%8B%B0-FrogJmp</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[코딜리티]&amp;nbsp;FrogJmp&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제정의 : 개구리의 현재위치에서, 원하는 위치 이상까지 가려면, 몇번 점프해야 하는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이 : 가야할 거리를 구하고, 몫을 소수로 계산해서 올림처리.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;636&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfIxxW/dJMcabiCHhl/lb85LU2BTiVUuJgVvIzKJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfIxxW/dJMcabiCHhl/lb85LU2BTiVUuJgVvIzKJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfIxxW/dJMcabiCHhl/lb85LU2BTiVUuJgVvIzKJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfIxxW%2FdJMcabiCHhl%2Flb85LU2BTiVUuJgVvIzKJK%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;638&quot; height=&quot;636&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;636&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #5d666f; text-align: start;&quot;&gt;Tasks Details&lt;/span&gt;&lt;/p&gt;
&lt;div id=&quot;task-0&quot; style=&quot;background-color: #ffffff; color: #464646; text-align: start;&quot;&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;div style=&quot;background-color: #d6eebf;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #393c4b;&quot;&gt;easy&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;1.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a id=&quot;task-0-name&quot; style=&quot;background-color: #000000; color: #046ba2;&quot; href=&quot;https://app.codility.com/demo/results/trainingU5FZM4-GEZ/#&quot;&gt;FrogJmp&lt;/a&gt;&lt;/div&gt;
&lt;span&gt;Count minimal number of jumps from position X to Y.&lt;/span&gt;&lt;/div&gt;
&lt;div id=&quot;task_result_0&quot;&gt;
&lt;div&gt;&lt;span&gt;&lt;b&gt;Task Score&lt;/b&gt;&lt;/span&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #e9edf1;&quot;&gt;
&lt;div style=&quot;background-color: #393c4b;&quot;&gt;
&lt;div style=&quot;color: #ffffff; text-align: left;&quot; data-test-id=&quot;task-score-value&quot;&gt;100%&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;b&gt;Correctness&lt;/b&gt;&lt;/span&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #e9edf1;&quot;&gt;
&lt;div style=&quot;background-color: #393c4b;&quot;&gt;
&lt;div style=&quot;color: #ffffff; text-align: left;&quot; data-test-id=&quot;correctness-value&quot;&gt;100%&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;b&gt;Performance&lt;/b&gt;&lt;/span&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #e9edf1;&quot;&gt;
&lt;div style=&quot;background-color: #393c4b;&quot;&gt;
&lt;div style=&quot;color: #ffffff; text-align: left;&quot; data-test-id=&quot;performance-value&quot;&gt;100%&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #000000;&quot;&gt;
&lt;div style=&quot;background-color: #000000;&quot;&gt;
&lt;h3 style=&quot;color: #393c4b;&quot; data-ke-size=&quot;size23&quot;&gt;Task description&lt;/h3&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #000000;&quot;&gt;
&lt;div style=&quot;background-color: #000000;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;task-0-text&quot;&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A small frog wants to get to the other side of the road. The frog is currently located at position X and wants to get to a position greater than or equal to Y. The small frog always jumps a fixed distance, D.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Count the minimal number of jumps that the small frog must perform to reach its target.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Write a function:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;class Solution { public int solution(int X, int Y, int D); }&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;that, given three integers X, Y and D, returns the minimal number of jumps from position X to a position equal to or greater than Y.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;For example, given:&lt;/p&gt;
X = 10 Y = 85 D = 30
&lt;p data-ke-size=&quot;size16&quot;&gt;the function should return 3, because the frog will be positioned as follows:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;after the first jump, at position 10 + 30 = 40&lt;/li&gt;
&lt;li&gt;after the second jump, at position 10 + 30 + 30 = 70&lt;/li&gt;
&lt;li&gt;after the third jump, at position 10 + 30 + 30 + 30 = 100&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Write an&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;b&gt;efficient&lt;/b&gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;algorithm for the following assumptions:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;X, Y and D are integers within the range [&lt;span&gt;1&lt;/span&gt;..&lt;span&gt;1,000,000,000&lt;/span&gt;];&lt;/li&gt;
&lt;li&gt;X &amp;le; Y.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div&gt;Copyright 2009&amp;ndash;2026 by Codility Limited. All Rights Reserved. Unauthorized copying, publication or disclosure prohibited.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #000000; color: #393c4b;&quot;&gt;Solution&lt;/div&gt;
&lt;div style=&quot;background-color: #f5f7f9;&quot;&gt;&lt;span&gt;Programming language used&lt;/span&gt;&lt;span&gt;Java 21&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;&lt;span&gt;Time spent on task&lt;/span&gt;&lt;span&gt;10 minutes&lt;/span&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #000000;&quot;&gt;
&lt;div style=&quot;background-color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-help&quot;&gt;help&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #f5f7f9;&quot;&gt;&lt;span&gt;Notes&lt;/span&gt;
&lt;div id=&quot;sol-desc-0-container&quot;&gt;
&lt;div id=&quot;sol-desc-0&quot; style=&quot;text-align: left;&quot;&gt;&lt;span&gt;&lt;i&gt;not defined yet&lt;/i&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;task-0-timeline-header&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h3 style=&quot;color: #393c4b;&quot; data-ke-size=&quot;size23&quot;&gt;Task timeline&lt;/h3&gt;
&lt;span style=&quot;background-color: #feeaf3; color: #98074c;&quot;&gt;Beta&lt;/span&gt;&lt;/div&gt;
&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-help&quot;&gt;help&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;solution-placeholder-0&quot;&gt;
&lt;div id=&quot;task-0-submit-code-current&quot;&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #eeeeee; text-align: left;&quot;&gt;
&lt;div&gt;Code: 14:23:20 UTC, java, final, score:&amp;nbsp;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;100&lt;/b&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #046ba2;&quot; href=&quot;https://app.codility.com/demo/results/trainingU5FZM4-GEZ/#&quot;&gt;show code in pop-up&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;1&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;2&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;3&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;4&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;5&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;6&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;7&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;8&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;9&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;10&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;11&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;12&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;13&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div data-raw-evaluations=&quot;[{&amp;quot;score&amp;quot;: 10.0, &amp;quot;review&amp;quot;: [], &amp;quot;name&amp;quot;: &amp;quot;/tmp/solutions/solution.java&amp;quot;}]&quot; data-raw-task_library_microfrontend_url=&quot;&amp;quot;https://library-production.tasks.services.codility.com/assets/taskLibrary.js&amp;quot;&quot; data-raw-task_index=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;// you can also use imports, for example:
// import java.util.*;

// you can write to stdout for debugging purposes, e.g.
// System.out.println(&quot;this is a debug message&quot;);

class Solution {
    public int solution(int X, int Y, int D) {
        // Implement your solution here
        int diff = Y-X;     // 75
        return (int) Math.ceil( (double)diff/D ); // 75/30=3
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;task-0-timeline-content&quot;&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #4a64e9;&quot;&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #d8dfe4; color: #222222; text-align: left;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #000000;&quot; data-state=&quot;closed&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-play_arrow&quot;&gt;play_arrow&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #000000;&quot; data-state=&quot;closed&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-play_arrow&quot;&gt;play_arrow&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #000000;&quot; data-state=&quot;closed&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-play_arrow&quot;&gt;play_arrow&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #000000;&quot; data-state=&quot;closed&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-play_arrow&quot;&gt;play_arrow&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #000000;&quot; data-state=&quot;closed&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-play_arrow&quot;&gt;play_arrow&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #000000;&quot; data-state=&quot;closed&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-play_arrow&quot;&gt;play_arrow&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #000000;&quot; data-state=&quot;closed&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-check&quot;&gt;check&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;p style=&quot;color: #393c4b;&quot; data-ke-size=&quot;size16&quot;&gt;14:13:32&lt;/p&gt;
&lt;p style=&quot;color: #393c4b;&quot; data-ke-size=&quot;size16&quot;&gt;14:23:21&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;p style=&quot;color: #5d666f;&quot; data-ke-size=&quot;size16&quot;&gt;Away from Codility tab&lt;/p&gt;
&lt;p style=&quot;color: #c32f2f;&quot; data-state=&quot;closed&quot; data-ke-size=&quot;size16&quot;&gt;0%&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;task-0-submit-analysis-current&quot;&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #000000; color: #393c4b;&quot;&gt;Analysis summary&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The solution obtained perfect score.&lt;/p&gt;
&lt;div style=&quot;background-color: #000000; color: #393c4b;&quot;&gt;Analysis&lt;/div&gt;
&lt;div style=&quot;background-color: #f5f7f9;&quot;&gt;
&lt;div&gt;Detected time complexity:&lt;/div&gt;
&lt;b&gt;O(1)&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #5d666f; color: #ffffff; text-align: center;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #ffffff;&quot; href=&quot;https://app.codility.com/demo/results/trainingU5FZM4-GEZ/#&quot;&gt;expand all&lt;/a&gt;&lt;b&gt;Example tests&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingU5FZM4-GEZ/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;example&lt;br /&gt;example test&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #5d666f; color: #ffffff; text-align: center;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #ffffff;&quot; href=&quot;https://app.codility.com/demo/results/trainingU5FZM4-GEZ/#&quot;&gt;expand all&lt;/a&gt;&lt;b&gt;Correctness tests&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingU5FZM4-GEZ/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;simple1&lt;br /&gt;simple test&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingU5FZM4-GEZ/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;simple2&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingU5FZM4-GEZ/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;extreme_position&lt;br /&gt;no jump needed&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingU5FZM4-GEZ/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;small_extreme_jump&lt;br /&gt;one big jump&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #5d666f; color: #ffffff; text-align: center;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #ffffff;&quot; href=&quot;https://app.codility.com/demo/results/trainingU5FZM4-GEZ/#&quot;&gt;expand all&lt;/a&gt;&lt;b&gt;Performance tests&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingU5FZM4-GEZ/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;many_jump1&lt;br /&gt;many jumps, D = 2&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingU5FZM4-GEZ/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;many_jump2&lt;br /&gt;many jumps, D = 99&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingU5FZM4-GEZ/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;many_jump3&lt;br /&gt;many jumps, D = 1283&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingU5FZM4-GEZ/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;big_extreme_jump&lt;br /&gt;maximal number of jumps&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingU5FZM4-GEZ/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;small_jumps&lt;br /&gt;many small jumps&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>알고리즘 단련장/Codility</category>
      <category>codility</category>
      <category>FrogJmp</category>
      <category>문제풀이</category>
      <category>알고리즘</category>
      <category>자바</category>
      <category>코딜리티</category>
      <category>코딩</category>
      <category>코딩테스트</category>
      <category>테스트</category>
      <category>풀이</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/171</guid>
      <comments>https://snapcode.tistory.com/entry/%EC%BD%94%EB%94%9C%EB%A6%AC%ED%8B%B0-FrogJmp#entry171comment</comments>
      <pubDate>Sun, 11 Jan 2026 23:26:27 +0900</pubDate>
    </item>
    <item>
      <title>[코딜리티] OddOccurrencesInArray</title>
      <link>https://snapcode.tistory.com/entry/%EC%BD%94%EB%94%9C%EB%A6%AC%ED%8B%B0-OddOccurrencesInArray</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[코딜리티]&amp;nbsp;OddOccurrencesInArray&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제정의 : 배열의 값중에서 1개만 나오는 솔로값 찾기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이 : 배열 전체검색해서 카운팅해주고, Key값이 홀수인 경우 찾기.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;696&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ywRZx/dJMcagRK1gb/I88uZVOfNMVHBLYLVLmD71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ywRZx/dJMcagRK1gb/I88uZVOfNMVHBLYLVLmD71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ywRZx/dJMcagRK1gb/I88uZVOfNMVHBLYLVLmD71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FywRZx%2FdJMcagRK1gb%2FI88uZVOfNMVHBLYLVLmD71%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;696&quot; height=&quot;654&quot; data-origin-width=&quot;696&quot; data-origin-height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #5d666f; text-align: start;&quot;&gt;Tasks Details&lt;/span&gt;&lt;/p&gt;
&lt;div id=&quot;task-0&quot; style=&quot;background-color: #ffffff; color: #464646; text-align: start;&quot;&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;div style=&quot;background-color: #d6eebf;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #393c4b;&quot;&gt;easy&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;1.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a id=&quot;task-0-name&quot; style=&quot;background-color: #000000; color: #046ba2;&quot; href=&quot;https://app.codility.com/demo/results/trainingHH8GPM-96H/#&quot;&gt;OddOccurrencesInArray&lt;/a&gt;&lt;/div&gt;
&lt;span&gt;Find value that occurs in odd number of elements.&lt;/span&gt;&lt;/div&gt;
&lt;div id=&quot;task_result_0&quot;&gt;
&lt;div&gt;&lt;span&gt;&lt;b&gt;Task Score&lt;/b&gt;&lt;/span&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #e9edf1;&quot;&gt;
&lt;div style=&quot;background-color: #393c4b;&quot;&gt;
&lt;div style=&quot;color: #ffffff; text-align: left;&quot; data-test-id=&quot;task-score-value&quot;&gt;100%&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;b&gt;Correctness&lt;/b&gt;&lt;/span&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #e9edf1;&quot;&gt;
&lt;div style=&quot;background-color: #393c4b;&quot;&gt;
&lt;div style=&quot;color: #ffffff; text-align: left;&quot; data-test-id=&quot;correctness-value&quot;&gt;100%&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;b&gt;Performance&lt;/b&gt;&lt;/span&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #e9edf1;&quot;&gt;
&lt;div style=&quot;background-color: #393c4b;&quot;&gt;
&lt;div style=&quot;color: #ffffff; text-align: left;&quot; data-test-id=&quot;performance-value&quot;&gt;100%&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #000000;&quot;&gt;
&lt;div style=&quot;background-color: #000000;&quot;&gt;
&lt;h3 style=&quot;color: #393c4b;&quot; data-ke-size=&quot;size23&quot;&gt;Task description&lt;/h3&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #000000;&quot;&gt;
&lt;div style=&quot;background-color: #000000;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;task-0-text&quot;&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A non-empty array A consisting of N integers is given. The array contains an odd number of elements, and each element of the array can be paired with another element that has the same value, except for one element that is left unpaired.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;For example, in array A such that:&lt;/p&gt;
A[0] = 9 A[1] = 3 A[2] = 9 A[3] = 3 A[4] = 9 A[5] = 7 A[6] = 9
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;the elements at indexes 0 and 2 have value 9,&lt;/li&gt;
&lt;li&gt;the elements at indexes 1 and 3 have value 3,&lt;/li&gt;
&lt;li&gt;the elements at indexes 4 and 6 have value 9,&lt;/li&gt;
&lt;li&gt;the element at index 5 has value 7 and is unpaired.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Write a function:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;class Solution { public int solution(int[] A); }&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;that, given an array A consisting of N integers fulfilling the above conditions, returns the value of the unpaired element.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;For example, given array A such that:&lt;/p&gt;
A[0] = 9 A[1] = 3 A[2] = 9 A[3] = 3 A[4] = 9 A[5] = 7 A[6] = 9
&lt;p data-ke-size=&quot;size16&quot;&gt;the function should return 7, as explained in the example above.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Write an&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;b&gt;efficient&lt;/b&gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;algorithm for the following assumptions:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;N is an odd integer within the range [1..1,000,000];&lt;/li&gt;
&lt;li&gt;each element of array A is an integer within the range [&lt;span&gt;1&lt;/span&gt;..&lt;span&gt;1,000,000,000&lt;/span&gt;];&lt;/li&gt;
&lt;li&gt;all but one of the values in A occur an even number of times.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div&gt;Copyright 2009&amp;ndash;2026 by Codility Limited. All Rights Reserved. Unauthorized copying, publication or disclosure prohibited.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #000000; color: #393c4b;&quot;&gt;Solution&lt;/div&gt;
&lt;div style=&quot;background-color: #f5f7f9;&quot;&gt;&lt;span&gt;Programming language used&lt;/span&gt;&lt;span&gt;Java 21&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;&lt;span&gt;Time spent on task&lt;/span&gt;&lt;span&gt;38 minutes&lt;/span&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #000000;&quot;&gt;
&lt;div style=&quot;background-color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-help&quot;&gt;help&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #f5f7f9;&quot;&gt;&lt;span&gt;Notes&lt;/span&gt;
&lt;div id=&quot;sol-desc-0-container&quot;&gt;
&lt;div id=&quot;sol-desc-0&quot; style=&quot;text-align: left;&quot;&gt;&lt;span&gt;&lt;i&gt;not defined yet&lt;/i&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;task-0-timeline-header&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h3 style=&quot;color: #393c4b;&quot; data-ke-size=&quot;size23&quot;&gt;Task timeline&lt;/h3&gt;
&lt;span style=&quot;background-color: #feeaf3; color: #98074c;&quot;&gt;Beta&lt;/span&gt;&lt;/div&gt;
&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-help&quot;&gt;help&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;solution-placeholder-0&quot;&gt;
&lt;div id=&quot;task-0-submit-code-current&quot;&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #eeeeee; text-align: left;&quot;&gt;
&lt;div&gt;Code: 14:03:18 UTC, java, final, score:&amp;nbsp;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;100&lt;/b&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #046ba2;&quot; href=&quot;https://app.codility.com/demo/results/trainingHH8GPM-96H/#&quot;&gt;show code in pop-up&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;1&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;2&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;3&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;4&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;5&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;6&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;7&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;8&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;9&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;10&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;11&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;12&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;13&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;14&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;15&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;16&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;17&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;18&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;19&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;20&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;21&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;22&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;23&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;24&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;25&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div data-raw-evaluations=&quot;[{&amp;quot;score&amp;quot;: 8.28, &amp;quot;review&amp;quot;: [{&amp;quot;category&amp;quot;: &amp;quot;Complex Method&amp;quot;, &amp;quot;functions&amp;quot;: [{&amp;quot;title&amp;quot;: &amp;quot;solution&amp;quot;, &amp;quot;details&amp;quot;: &amp;quot;cc = 4&amp;quot;, &amp;quot;start_line&amp;quot;: 9, &amp;quot;end_line&amp;quot;: 24}], &amp;quot;indication&amp;quot;: 2, &amp;quot;description&amp;quot;: &amp;quot;A Complex Method has a high cyclomatic complexity. The recommended threshold for the Java language is a cyclomatic complexity lower than 4.\n\nSeverity: Brain Method - Complex Method - Long Method.&amp;quot;}], &amp;quot;name&amp;quot;: &amp;quot;/tmp/solutions/solution.java&amp;quot;}]&quot; data-raw-task_library_microfrontend_url=&quot;&amp;quot;https://library-production.tasks.services.codility.com/assets/taskLibrary.js&amp;quot;&quot; data-raw-task_index=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;// you can also use imports, for example:
// import java.util.*;
import java.util.*;

// you can write to stdout for debugging purposes, e.g.
// System.out.println(&quot;this is a debug message&quot;);

class Solution {
    public int solution(int[] A) {
        // Implement your solution here

        Map&amp;lt;Integer, Integer&amp;gt; map = new HashMap&amp;lt;&amp;gt;();
        for(int i=0; i&amp;lt;A.length; i++){
            int cur = A[i];
            map.put(cur, map.getOrDefault(cur, 0) + 1);
        }

        for(int key : map.keySet()){
            if(map.get(key) % 2 == 1){
                return key;
            }
        }
        return A[0];
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;task-0-timeline-content&quot;&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #4a64e9;&quot;&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #d8dfe4; color: #222222; text-align: left;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #000000;&quot; data-state=&quot;closed&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-play_arrow&quot;&gt;play_arrow&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #000000;&quot; data-state=&quot;closed&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-play_arrow&quot;&gt;play_arrow&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #000000;&quot; data-state=&quot;closed&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-play_arrow&quot;&gt;play_arrow&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #000000;&quot; data-state=&quot;closed&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-check&quot;&gt;check&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;p style=&quot;color: #393c4b;&quot; data-ke-size=&quot;size16&quot;&gt;13:25:38&lt;/p&gt;
&lt;p style=&quot;color: #393c4b;&quot; data-ke-size=&quot;size16&quot;&gt;14:03:19&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;p style=&quot;color: #5d666f;&quot; data-ke-size=&quot;size16&quot;&gt;Away from Codility tab&lt;/p&gt;
&lt;p style=&quot;color: #c32f2f;&quot; data-state=&quot;closed&quot; data-ke-size=&quot;size16&quot;&gt;0%&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;task-0-submit-analysis-current&quot;&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #000000; color: #393c4b;&quot;&gt;Analysis summary&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The solution obtained perfect score.&lt;/p&gt;
&lt;div style=&quot;background-color: #000000; color: #393c4b;&quot;&gt;Analysis&lt;/div&gt;
&lt;div style=&quot;background-color: #f5f7f9;&quot;&gt;
&lt;div&gt;Detected time complexity:&lt;/div&gt;
&lt;b&gt;O(N) or O(N*log(N))&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #5d666f; color: #ffffff; text-align: center;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #ffffff;&quot; href=&quot;https://app.codility.com/demo/results/trainingHH8GPM-96H/#&quot;&gt;expand all&lt;/a&gt;&lt;b&gt;Example tests&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingHH8GPM-96H/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;example1&lt;br /&gt;example test&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #5d666f; color: #ffffff; text-align: center;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #ffffff;&quot; href=&quot;https://app.codility.com/demo/results/trainingHH8GPM-96H/#&quot;&gt;expand all&lt;/a&gt;&lt;b&gt;Correctness tests&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingHH8GPM-96H/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;simple1&lt;br /&gt;simple test n=5&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingHH8GPM-96H/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;simple2&lt;br /&gt;simple test n=11&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingHH8GPM-96H/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;extreme_single_item&lt;br /&gt;[42]&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingHH8GPM-96H/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;small1&lt;br /&gt;small random test n=201&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingHH8GPM-96H/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;small2&lt;br /&gt;small random test n=601&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #5d666f; color: #ffffff; text-align: center;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #ffffff;&quot; href=&quot;https://app.codility.com/demo/results/trainingHH8GPM-96H/#&quot;&gt;expand all&lt;/a&gt;&lt;b&gt;Performance tests&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingHH8GPM-96H/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;medium1&lt;br /&gt;medium random test n=2,001&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingHH8GPM-96H/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;medium2&lt;br /&gt;medium random test n=100,003&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingHH8GPM-96H/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;big1&lt;br /&gt;big random test n=999,999, multiple repetitions&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingHH8GPM-96H/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;big2&lt;br /&gt;big random test n=999,999&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>알고리즘 단련장/Codility</category>
      <category>codility</category>
      <category>OddOccurrencesInArray</category>
      <category>문제풀이</category>
      <category>알고리즘</category>
      <category>자바</category>
      <category>코딜리티</category>
      <category>코딩테스트</category>
      <category>풀이</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/170</guid>
      <comments>https://snapcode.tistory.com/entry/%EC%BD%94%EB%94%9C%EB%A6%AC%ED%8B%B0-OddOccurrencesInArray#entry170comment</comments>
      <pubDate>Sun, 11 Jan 2026 23:12:35 +0900</pubDate>
    </item>
    <item>
      <title>[코딜리티] CyclicRotation</title>
      <link>https://snapcode.tistory.com/entry/%EC%BD%94%EB%94%9C%EB%A6%AC%ED%8B%B0-CyclicRotation</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[코딜리티]&amp;nbsp;CyclicRotation&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;272&quot; data-origin-height=&quot;267&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mWaH2/dJMcacu3hsG/9kXxiFqiuHiiTmHkegNN00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mWaH2/dJMcacu3hsG/9kXxiFqiuHiiTmHkegNN00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mWaH2/dJMcacu3hsG/9kXxiFqiuHiiTmHkegNN00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmWaH2%2FdJMcacu3hsG%2F9kXxiFqiuHiiTmHkegNN00%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;272&quot; height=&quot;267&quot; data-origin-width=&quot;272&quot; data-origin-height=&quot;267&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제요약 : 배열을 주어진 수만큼 shift해서 재정렬한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이 :&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열의 길이보다 K가 클때는 동일한 결과가 나오기 때문에, 몫을 찾아준다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1e1e; color: #d4d4d4;&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;K = K % len; &amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #6a9955;&quot;&gt;// shift 2 == shift 7 == shift 12 == ...&lt;/span&gt;&lt;span style=&quot;color: #6a9955;&quot;&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뒤에서 K번째부터 정답배열에 저장하고,&lt;br /&gt;len에 도달하면 처음부터 저장해주면 된다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1e1e; color: #d4d4d4;&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #6a9955;&quot;&gt;// A = [3, 8, 9, 7, 6]&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #6a9955;&quot;&gt;// K = 2&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #6a9955;&quot;&gt;// [7, 6] + [3, 8, 9]&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #d4d4d4;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #6a9955;&quot;&gt;// return [7, 6, 3, 8, 9]&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #5d666f; text-align: start;&quot;&gt;Tasks Details&lt;/span&gt;&lt;/p&gt;
&lt;div id=&quot;task-0&quot; style=&quot;background-color: #ffffff; color: #464646; text-align: start;&quot;&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;div style=&quot;background-color: #d6eebf;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #393c4b;&quot;&gt;easy&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;1.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a id=&quot;task-0-name&quot; style=&quot;background-color: #000000; color: #046ba2;&quot; href=&quot;https://app.codility.com/demo/results/trainingDY49RC-KC8/#&quot;&gt;CyclicRotation&lt;/a&gt;&lt;/div&gt;
&lt;span&gt;Rotate an array to the right by a given number of steps.&lt;/span&gt;&lt;/div&gt;
&lt;div id=&quot;task_result_0&quot;&gt;
&lt;div&gt;&lt;span&gt;&lt;b&gt;Task Score&lt;/b&gt;&lt;/span&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #e9edf1;&quot;&gt;
&lt;div style=&quot;background-color: #393c4b;&quot;&gt;
&lt;div style=&quot;color: #ffffff; text-align: left;&quot; data-test-id=&quot;task-score-value&quot;&gt;100%&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;b&gt;Correctness&lt;/b&gt;&lt;/span&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #e9edf1;&quot;&gt;
&lt;div style=&quot;background-color: #393c4b;&quot;&gt;
&lt;div style=&quot;color: #ffffff; text-align: left;&quot; data-test-id=&quot;correctness-value&quot;&gt;100%&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;b&gt;Performance&lt;/b&gt;&lt;/span&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #5d666f;&quot;&gt;&lt;span&gt;Not assessed&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #000000;&quot;&gt;
&lt;div style=&quot;background-color: #000000;&quot;&gt;
&lt;h3 style=&quot;color: #393c4b;&quot; data-ke-size=&quot;size23&quot;&gt;Task description&lt;/h3&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #000000;&quot;&gt;
&lt;div style=&quot;background-color: #000000;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;task-0-text&quot;&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;An array A consisting of N integers is given. Rotation of the array means that each element is shifted right by one index, and the last element of the array is moved to the first place. For example, the rotation of array A = [3, 8, 9, 7, 6] is [6, 3, 8, 9, 7] (elements are shifted right by one index and 6 is moved to the first place).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The goal is to rotate array A K times; that is, each element of A will be shifted to the right K times.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Write a function:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;class Solution { public int[] solution(int[] A, int K); }&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;that, given an array A consisting of N integers and an integer K, returns the array A rotated K times.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;For example, given&lt;/p&gt;
A = [3, 8, 9, 7, 6] K = 3
&lt;p data-ke-size=&quot;size16&quot;&gt;the function should return [9, 7, 6, 3, 8]. Three rotations were made:&lt;/p&gt;
[3, 8, 9, 7, 6] -&amp;gt; [6, 3, 8, 9, 7] [6, 3, 8, 9, 7] -&amp;gt; [7, 6, 3, 8, 9] [7, 6, 3, 8, 9] -&amp;gt; [9, 7, 6, 3, 8]
&lt;p data-ke-size=&quot;size16&quot;&gt;For another example, given&lt;/p&gt;
A = [0, 0, 0] K = 1
&lt;p data-ke-size=&quot;size16&quot;&gt;the function should return [0, 0, 0]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Given&lt;/p&gt;
A = [1, 2, 3, 4] K = 4
&lt;p data-ke-size=&quot;size16&quot;&gt;the function should return [1, 2, 3, 4]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Assume that:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;N and K are integers within the range [&lt;span&gt;0&lt;/span&gt;..&lt;span&gt;100&lt;/span&gt;];&lt;/li&gt;
&lt;li&gt;each element of array A is an integer within the range [&lt;span&gt;&amp;minus;1,000&lt;/span&gt;..&lt;span&gt;1,000&lt;/span&gt;].&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;In your solution, focus on&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;b&gt;correctness&lt;/b&gt;&lt;/b&gt;. The performance of your solution will not be the focus of the assessment.&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;Copyright 2009&amp;ndash;2026 by Codility Limited. All Rights Reserved. Unauthorized copying, publication or disclosure prohibited.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #000000; color: #393c4b;&quot;&gt;Solution&lt;/div&gt;
&lt;div style=&quot;background-color: #f5f7f9;&quot;&gt;&lt;span&gt;Programming language used&lt;/span&gt;&lt;span&gt;Java 21&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;&lt;span&gt;Time spent on task&lt;/span&gt;&lt;span&gt;6 minutes&lt;/span&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #000000;&quot;&gt;
&lt;div style=&quot;background-color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-help&quot;&gt;help&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #f5f7f9;&quot;&gt;&lt;span&gt;Notes&lt;/span&gt;
&lt;div id=&quot;sol-desc-0-container&quot;&gt;
&lt;div id=&quot;sol-desc-0&quot; style=&quot;text-align: left;&quot;&gt;&lt;span&gt;&lt;i&gt;not defined yet&lt;/i&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;task-0-timeline-header&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h3 style=&quot;color: #393c4b;&quot; data-ke-size=&quot;size23&quot;&gt;Task timeline&lt;/h3&gt;
&lt;span style=&quot;background-color: #feeaf3; color: #98074c;&quot;&gt;Beta&lt;/span&gt;&lt;/div&gt;
&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-help&quot;&gt;help&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;solution-placeholder-0&quot;&gt;
&lt;div id=&quot;task-0-submit-code-current&quot;&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #eeeeee; text-align: left;&quot;&gt;
&lt;div&gt;Code: 14:03:17 UTC, java, final, score:&amp;nbsp;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;100&lt;/b&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #046ba2;&quot; href=&quot;https://app.codility.com/demo/results/trainingDY49RC-KC8/#&quot;&gt;show code in pop-up&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;1&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;2&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;3&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;4&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;5&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;6&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;7&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;8&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;9&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;10&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;11&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;12&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;13&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;14&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;15&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;16&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;17&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;18&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;19&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;20&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;21&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;22&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;23&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;24&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;25&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;26&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;27&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div data-raw-evaluations=&quot;[{&amp;quot;score&amp;quot;: 10.0, &amp;quot;review&amp;quot;: [], &amp;quot;name&amp;quot;: &amp;quot;/tmp/solutions/solution.java&amp;quot;}]&quot; data-raw-task_library_microfrontend_url=&quot;&amp;quot;https://library-production.tasks.services.codility.com/assets/taskLibrary.js&amp;quot;&quot; data-raw-task_index=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;// you can also use imports, for example:
// import java.util.*;

// you can write to stdout for debugging purposes, e.g.
// System.out.println(&quot;this is a debug message&quot;);

class Solution {
    public int[] solution(int[] A, int K) {

        // A = [3, 8, 9, 7, 6]
        // K = 2
        // [7, 6] + [3, 8, 9]
        // return [7, 6, 3, 8, 9]

        int len = A.length;
        if (len == 0) return A;

        K = K % len;    // shift 2 == shift 7 == shift 12 == ...

        int[] dap = new int[len];
        for (int i = 0; i &amp;lt; len; i++) {
            dap[i] = A[(len - K + i) % len];
        }

        return dap;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;task-0-timeline-content&quot;&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #4a64e9;&quot;&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #d8dfe4; color: #222222; text-align: left;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #000000;&quot; data-state=&quot;closed&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-play_arrow&quot;&gt;play_arrow&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #000000;&quot; data-state=&quot;closed&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-play_arrow&quot;&gt;play_arrow&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #000000;&quot; data-state=&quot;closed&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-check&quot;&gt;check&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;p style=&quot;color: #393c4b;&quot; data-ke-size=&quot;size16&quot;&gt;13:57:25&lt;/p&gt;
&lt;p style=&quot;color: #393c4b;&quot; data-ke-size=&quot;size16&quot;&gt;14:03:17&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;p style=&quot;color: #5d666f;&quot; data-ke-size=&quot;size16&quot;&gt;Away from Codility tab&lt;/p&gt;
&lt;p style=&quot;color: #c32f2f;&quot; data-state=&quot;closed&quot; data-ke-size=&quot;size16&quot;&gt;0%&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;task-0-submit-analysis-current&quot;&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #000000; color: #393c4b;&quot;&gt;Analysis summary&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The solution obtained perfect score.&lt;/p&gt;
&lt;div style=&quot;background-color: #000000; color: #393c4b;&quot;&gt;Analysis&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #5d666f; color: #ffffff; text-align: center;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #ffffff;&quot; href=&quot;https://app.codility.com/demo/results/trainingDY49RC-KC8/#&quot;&gt;expand all&lt;/a&gt;&lt;b&gt;Example tests&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingDY49RC-KC8/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;example&lt;br /&gt;first example test&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingDY49RC-KC8/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;example2&lt;br /&gt;second example test&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingDY49RC-KC8/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;example3&lt;br /&gt;third example test&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #5d666f; color: #ffffff; text-align: center;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #ffffff;&quot; href=&quot;https://app.codility.com/demo/results/trainingDY49RC-KC8/#&quot;&gt;expand all&lt;/a&gt;&lt;b&gt;Correctness tests&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingDY49RC-KC8/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;extreme_empty&lt;br /&gt;empty array&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingDY49RC-KC8/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;single&lt;br /&gt;one element, 0 &amp;lt;= K &amp;lt;= 5&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingDY49RC-KC8/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;double&lt;br /&gt;two elements, K &amp;lt;= N&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingDY49RC-KC8/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;small1&lt;br /&gt;small functional tests, K &amp;lt; N&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingDY49RC-KC8/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;small2&lt;br /&gt;small functional tests, K &amp;gt;= N&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingDY49RC-KC8/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;small_random_all_rotations&lt;br /&gt;small random sequence, all rotations, N = 15&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingDY49RC-KC8/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;medium_random&lt;br /&gt;medium random sequence, N = 100&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingDY49RC-KC8/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;maximal&lt;br /&gt;maximal N and K&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘 단련장/Codility</category>
      <category>codility</category>
      <category>cyclicRotation</category>
      <category>문제풀이</category>
      <category>알고리즘</category>
      <category>자바</category>
      <category>코딜리티</category>
      <category>코딩테스트</category>
      <category>코테</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/169</guid>
      <comments>https://snapcode.tistory.com/entry/%EC%BD%94%EB%94%9C%EB%A6%AC%ED%8B%B0-CyclicRotation#entry169comment</comments>
      <pubDate>Sat, 10 Jan 2026 23:05:38 +0900</pubDate>
    </item>
    <item>
      <title>[코딜리티] BinaryGap (+플랫폼에 대한 사용 후기)</title>
      <link>https://snapcode.tistory.com/entry/%EC%BD%94%EB%94%9C%EB%A6%AC%ED%8B%B0-BinaryGap-%ED%94%8C%EB%9E%AB%ED%8F%BC%EC%97%90-%EB%8C%80%ED%95%9C-%EC%82%AC%EC%9A%A9-%ED%9B%84%EA%B8%B0</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #5d666f; text-align: start;&quot;&gt;문제&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #5d666f; text-align: start;&quot;&gt;1과 1 사이의 갭차이의 큰 값 구하기.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #5d666f; text-align: start;&quot;&gt;풀이&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #5d666f; text-align: start;&quot;&gt;1일때 시작점인지, 종료점인지 체크해서 gap 계산 + 0일때는 카운팅만&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #5d666f;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;첫 후기&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span style=&quot;color: #5d666f;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;editor는 해커랭크보단 느리고 가독성이 좋지 않습니다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #5d666f;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;대신 단순히 O/X 로 결과가 아니라, &lt;u&gt;&lt;b&gt;Reporting에 어마어마한 힘을 준 플랫폼&lt;/b&gt;&lt;/u&gt;이라고 느꼈습니다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #5d666f;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #5d666f;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;문제 푸는데 걸린 시간은 물론이고,&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #5d666f;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;아래 3가지 요소에 대해 평가되는게 다른 플랫폼과는 달랐습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-end=&quot;104&quot; data-start=&quot;79&quot; data-ke-size=&quot;size26&quot;&gt;1️⃣ Task Score (과제 점수)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;237&quot; data-start=&quot;105&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;132&quot; data-start=&quot;105&quot;&gt;&lt;b&gt;전체 과제(Task)에 대한 최종 점수&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;181&quot; data-start=&quot;133&quot;&gt;&lt;b&gt;Correctness + Performance + 다른 요소들&lt;/b&gt;을 종합한 점수&lt;/li&gt;
&lt;li data-end=&quot;196&quot; data-start=&quot;182&quot;&gt;범위: 0 ~ 100%&lt;/li&gt;
&lt;li data-end=&quot;237&quot; data-start=&quot;197&quot;&gt;예: Task Score 60% &amp;rarr; 과제 통과는 했지만 완전하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;242&quot; data-start=&quot;239&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;268&quot; data-start=&quot;244&quot; data-ke-size=&quot;size26&quot;&gt;2️⃣ Correctness (정확도)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;398&quot; data-start=&quot;269&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;309&quot; data-start=&quot;269&quot;&gt;제출한 코드가 &lt;b&gt;모든 테스트 케이스에서 올바른 결과를 내는 정도&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;333&quot; data-start=&quot;310&quot;&gt;단순히 &amp;ldquo;맞는 답을 반환했는지&amp;rdquo;만 평가&lt;/li&gt;
&lt;li data-end=&quot;348&quot; data-start=&quot;334&quot;&gt;범위: 0 ~ 100%&lt;/li&gt;
&lt;li data-end=&quot;398&quot; data-start=&quot;349&quot;&gt;예: Correctness 60% &amp;rarr; 일부 테스트 케이스는 통과했지만, 나머지는 틀림&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;403&quot; data-start=&quot;400&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;428&quot; data-start=&quot;405&quot; data-ke-size=&quot;size26&quot;&gt;3️⃣ Performance (성능)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;584&quot; data-start=&quot;429&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;449&quot; data-start=&quot;429&quot;&gt;알고리즘의 &lt;b&gt;시간/공간 효율&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;481&quot; data-start=&quot;450&quot;&gt;예: O(N&amp;sup2;) 로직이 큰 입력에서 느리면 점수 낮음&lt;/li&gt;
&lt;li data-end=&quot;542&quot; data-start=&quot;482&quot;&gt;Correctness가 100%여도 Performance가 낮으면 Task Score가 100%가 안 됨&lt;/li&gt;
&lt;li data-end=&quot;584&quot; data-start=&quot;543&quot;&gt;이건 BinaryGap처럼 단순 문제에서는 &lt;b&gt;종종 평가 안 됨&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #5d666f;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;br /&gt;&lt;br /&gt;추가적으로, 타이핑 과정까지 녹화가 되는건 처음이라 신기했습니다&lt;br /&gt;한영전환 안되어있어서 생긴 오탈자까지 전부 캡쳐됩니다.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #5d666f;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;s&gt;면접 준비하면서 내 얼굴을 영상 찍어보는 기분이랄까요&lt;/s&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;885&quot; data-origin-height=&quot;629&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biPO1T/dJMcacaJ6V1/CtzLexTPtxQHemeKMkIoT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biPO1T/dJMcacaJ6V1/CtzLexTPtxQHemeKMkIoT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biPO1T/dJMcacaJ6V1/CtzLexTPtxQHemeKMkIoT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiPO1T%2FdJMcacaJ6V1%2FCtzLexTPtxQHemeKMkIoT0%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;885&quot; height=&quot;629&quot; data-origin-width=&quot;885&quot; data-origin-height=&quot;629&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #5d666f; text-align: start;&quot;&gt;Tasks Details&lt;/span&gt;&lt;/p&gt;
&lt;div id=&quot;task-0&quot; style=&quot;background-color: #ffffff; color: #464646; text-align: start;&quot;&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;div style=&quot;background-color: #d6eebf;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #393c4b;&quot;&gt;easy&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;1.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a id=&quot;task-0-name&quot; style=&quot;background-color: #000000; color: #046ba2;&quot; href=&quot;https://app.codility.com/demo/results/trainingYC3HZ2-FRG/#&quot;&gt;BinaryGap&lt;/a&gt;&lt;/div&gt;
&lt;span&gt;Find longest sequence of zeros in binary representation of an integer.&lt;/span&gt;&lt;/div&gt;
&lt;div id=&quot;task_result_0&quot;&gt;
&lt;div&gt;&lt;span&gt;&lt;b&gt;Task Score&lt;/b&gt;&lt;/span&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #e9edf1;&quot;&gt;
&lt;div style=&quot;background-color: #393c4b;&quot;&gt;
&lt;div style=&quot;color: #ffffff; text-align: left;&quot; data-test-id=&quot;task-score-value&quot;&gt;100%&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;b&gt;Correctness&lt;/b&gt;&lt;/span&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #e9edf1;&quot;&gt;
&lt;div style=&quot;background-color: #393c4b;&quot;&gt;
&lt;div style=&quot;color: #ffffff; text-align: left;&quot; data-test-id=&quot;correctness-value&quot;&gt;100%&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;b&gt;Performance&lt;/b&gt;&lt;/span&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #5d666f;&quot;&gt;&lt;span&gt;Not assessed&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #000000;&quot;&gt;
&lt;div style=&quot;background-color: #000000;&quot;&gt;
&lt;h3 style=&quot;color: #393c4b;&quot; data-ke-size=&quot;size23&quot;&gt;Task description&lt;/h3&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #000000;&quot;&gt;
&lt;div style=&quot;background-color: #000000;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;task-0-text&quot;&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;i&gt;binary gap&lt;/i&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;within a positive integer N is any maximal sequence of consecutive zeros that is surrounded by ones at both ends in the binary representation of N.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;For example, number 9 has binary representation&lt;span&gt;&amp;nbsp;&lt;/span&gt;1001&lt;span&gt;&amp;nbsp;&lt;/span&gt;and contains a binary gap of length 2. The number 529 has binary representation&lt;span&gt;&amp;nbsp;&lt;/span&gt;1000010001&lt;span&gt;&amp;nbsp;&lt;/span&gt;and contains two binary gaps: one of length 4 and one of length 3. The number 20 has binary representation&lt;span&gt;&amp;nbsp;&lt;/span&gt;10100&lt;span&gt;&amp;nbsp;&lt;/span&gt;and contains one binary gap of length 1. The number 15 has binary representation&lt;span&gt;&amp;nbsp;&lt;/span&gt;1111&lt;span&gt;&amp;nbsp;&lt;/span&gt;and has no binary gaps. The number 32 has binary representation&lt;span&gt;&amp;nbsp;&lt;/span&gt;100000&lt;span&gt;&amp;nbsp;&lt;/span&gt;and has no binary gaps.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Write a function:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;class Solution { public int solution(int N); }&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;that, given a positive integer N, returns the length of its longest binary gap. The function should return 0 if N doesn't contain a binary gap.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;For example, given N = 1041 the function should return 5, because N has binary representation&lt;span&gt;&amp;nbsp;&lt;/span&gt;10000010001&lt;span&gt;&amp;nbsp;&lt;/span&gt;and so its longest binary gap is of length 5. Given N = 32 the function should return 0, because N has binary representation '100000' and thus no binary gaps.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Write an&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;b&gt;efficient&lt;/b&gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;algorithm for the following assumptions:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;N is an integer within the range [&lt;span&gt;1&lt;/span&gt;..&lt;span&gt;2,147,483,647&lt;/span&gt;].&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div&gt;Copyright 2009&amp;ndash;2026 by Codility Limited. All Rights Reserved. Unauthorized copying, publication or disclosure prohibited.&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #000000; color: #393c4b;&quot;&gt;Solution&lt;/div&gt;
&lt;div style=&quot;background-color: #f5f7f9;&quot;&gt;&lt;span&gt;Programming language used&lt;/span&gt;&lt;span&gt;Java 21&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;&lt;span&gt;Time spent on task&lt;/span&gt;&lt;span&gt;11 minutes&lt;/span&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #000000;&quot;&gt;
&lt;div style=&quot;background-color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-help&quot;&gt;help&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #f5f7f9;&quot;&gt;&lt;span&gt;Notes&lt;/span&gt;
&lt;div id=&quot;sol-desc-0-container&quot;&gt;
&lt;div id=&quot;sol-desc-0&quot; style=&quot;text-align: left;&quot;&gt;&lt;span&gt;&lt;i&gt;not defined yet&lt;/i&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;task-0-timeline-header&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h3 style=&quot;color: #393c4b;&quot; data-ke-size=&quot;size23&quot;&gt;Task timeline&lt;/h3&gt;
&lt;span style=&quot;background-color: #feeaf3; color: #98074c;&quot;&gt;Beta&lt;/span&gt;&lt;/div&gt;
&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-help&quot;&gt;help&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;solution-placeholder-0&quot;&gt;
&lt;div id=&quot;task-0-submit-code-current&quot;&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #eeeeee; text-align: left;&quot;&gt;
&lt;div&gt;Code: 11:49:36 UTC, java, final, score:&amp;nbsp;&lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;100&lt;/b&gt;&lt;/div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #046ba2;&quot; href=&quot;https://app.codility.com/demo/results/trainingYC3HZ2-FRG/#&quot;&gt;show code in pop-up&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff;&quot;&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;1&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;2&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;3&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;4&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;5&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;6&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;7&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;8&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;9&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;10&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;11&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;12&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;13&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;14&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;15&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;16&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;17&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;18&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;19&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;20&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;21&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;22&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;23&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;24&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;25&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;26&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;27&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;28&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;29&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;30&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;31&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;32&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;33&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;34&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;35&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;36&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;37&lt;/a&gt;&lt;a style=&quot;background-color: #000000; color: #5d666f;&quot;&gt;38&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div data-raw-evaluations=&quot;[{&amp;quot;score&amp;quot;: 7.43, &amp;quot;review&amp;quot;: [{&amp;quot;category&amp;quot;: &amp;quot;Deep, Nested Complexity&amp;quot;, &amp;quot;functions&amp;quot;: [{&amp;quot;title&amp;quot;: &amp;quot;solution&amp;quot;, &amp;quot;details&amp;quot;: &amp;quot;Nesting depth = 3 conditionals&amp;quot;, &amp;quot;start_line&amp;quot;: 8, &amp;quot;end_line&amp;quot;: 37}], &amp;quot;indication&amp;quot;: 3, &amp;quot;description&amp;quot;: &amp;quot;Deep nested logic means that you have control structures like if-statements or loops inside other control structures. Deep nested logic increases the cognitive load on the programmer reading the code. The human working memory has a maximum capacity of 3-4 items; beyond that threshold, we struggle with keeping things in our head. Consequently, deep nested logic has a strong correlation to defects and accounts for roughly 20% of all programming mistakes.\n\nCodeScene measures the maximum nesting depth inside each function. The deeper the nesting, the lower the code health. The threshold for the Java language is 3 levels of nesting.&amp;quot;}, {&amp;quot;category&amp;quot;: &amp;quot;Complex Method&amp;quot;, &amp;quot;functions&amp;quot;: [{&amp;quot;title&amp;quot;: &amp;quot;solution&amp;quot;, &amp;quot;details&amp;quot;: &amp;quot;cc = 5&amp;quot;, &amp;quot;start_line&amp;quot;: 8, &amp;quot;end_line&amp;quot;: 37}], &amp;quot;indication&amp;quot;: 2, &amp;quot;description&amp;quot;: &amp;quot;A Complex Method has a high cyclomatic complexity. The recommended threshold for the Java language is a cyclomatic complexity lower than 4.\n\nSeverity: Brain Method - Complex Method - Long Method.&amp;quot;}], &amp;quot;name&amp;quot;: &amp;quot;/tmp/solutions/solution.java&amp;quot;}]&quot; data-raw-task_library_microfrontend_url=&quot;&amp;quot;https://library-production.tasks.services.codility.com/assets/taskLibrary.js&amp;quot;&quot; data-raw-task_index=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;// you can also use imports, for example:
// import java.util.*;

// you can write to stdout for debugging purposes, e.g.
// System.out.println(&quot;this is a debug message&quot;);

class Solution {
    public int solution(int N) {
        // Implement your solution here

        String b = Integer.toBinaryString(N);

        int max = 0;
        int cur = 0;
        boolean started = false;

        // 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 =&amp;gt; return 2;
        for(int i=0; i&amp;lt;b.length(); i++){
            char c = b.charAt(i);

            if(c == '1'){       // 1
                // gap end =&amp;gt; max check
                if(started){
                    max = Math.max(max, cur);
                }

                // start now ! =&amp;gt; init
                started = true;
                cur = 0;
            }
            else if(started){   // 0
                cur++;
            }
        }

        return max;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;task-0-timeline-content&quot;&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #4a64e9;&quot;&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #d8dfe4; color: #222222; text-align: left;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #000000;&quot; data-state=&quot;closed&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-play_arrow&quot;&gt;play_arrow&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #000000;&quot; data-state=&quot;closed&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-play_arrow&quot;&gt;play_arrow&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #000000;&quot; data-state=&quot;closed&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-play_arrow&quot;&gt;play_arrow&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #000000;&quot; data-state=&quot;closed&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-play_arrow&quot;&gt;play_arrow&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #000000;&quot; data-state=&quot;closed&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-play_arrow&quot;&gt;play_arrow&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #000000;&quot; data-state=&quot;closed&quot;&gt;&lt;span style=&quot;color: #000000;&quot; data-test-id=&quot;icon-check&quot;&gt;check&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;p style=&quot;color: #393c4b;&quot; data-ke-size=&quot;size16&quot;&gt;11:39:10&lt;/p&gt;
&lt;p style=&quot;color: #393c4b;&quot; data-ke-size=&quot;size16&quot;&gt;11:49:36&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;p style=&quot;color: #5d666f;&quot; data-ke-size=&quot;size16&quot;&gt;Away from Codility tab&lt;/p&gt;
&lt;p style=&quot;color: #c32f2f;&quot; data-state=&quot;closed&quot; data-ke-size=&quot;size16&quot;&gt;0%&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;task-0-submit-analysis-current&quot;&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #000000; color: #393c4b;&quot;&gt;Analysis summary&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The solution obtained perfect score.&lt;/p&gt;
&lt;div style=&quot;background-color: #000000; color: #393c4b;&quot;&gt;Analysis&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #5d666f; color: #ffffff; text-align: center;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #ffffff;&quot; href=&quot;https://app.codility.com/demo/results/trainingYC3HZ2-FRG/#&quot;&gt;expand all&lt;/a&gt;&lt;b&gt;Example tests&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingYC3HZ2-FRG/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;example1&lt;br /&gt;example test n=1041=10000010001_2&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingYC3HZ2-FRG/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;example2&lt;br /&gt;example test n=15=1111_2&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingYC3HZ2-FRG/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;example3&lt;br /&gt;example test n=32=100000_2&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;background-color: #5d666f; color: #ffffff; text-align: center;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #ffffff;&quot; href=&quot;https://app.codility.com/demo/results/trainingYC3HZ2-FRG/#&quot;&gt;expand all&lt;/a&gt;&lt;b&gt;Correctness tests&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingYC3HZ2-FRG/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;extremes&lt;br /&gt;n=1, n=5=101_2 and n=2147483647=2**31-1&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingYC3HZ2-FRG/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;trailing_zeroes&lt;br /&gt;n=6=110_2 and n=328=101001000_2&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingYC3HZ2-FRG/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;power_of_2&lt;br /&gt;n=5=101_2, n=16=2**4 and n=1024=2**10&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingYC3HZ2-FRG/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;simple1&lt;br /&gt;n=9=1001_2 and n=11=1011_2&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingYC3HZ2-FRG/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;simple2&lt;br /&gt;n=19=10011 and n=42=101010_2&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingYC3HZ2-FRG/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;simple3&lt;br /&gt;n=1162=10010001010_2 and n=5=101_2&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingYC3HZ2-FRG/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;medium1&lt;br /&gt;n=51712=110010100000000_2 and n=20=10100_2&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingYC3HZ2-FRG/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;medium2&lt;br /&gt;n=561892=10001001001011100100_2 and n=9=1001_2&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingYC3HZ2-FRG/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;medium3&lt;br /&gt;n=66561=10000010000000001_2&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingYC3HZ2-FRG/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;large1&lt;br /&gt;n=6291457=11000000000000000000001_2&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingYC3HZ2-FRG/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;large2&lt;br /&gt;n=74901729=100011101101110100011100001&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingYC3HZ2-FRG/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;large3&lt;br /&gt;n=805306373=110000000000000000000000000101_2&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingYC3HZ2-FRG/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;large4&lt;br /&gt;n=1376796946=1010010000100000100000100010010_2&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingYC3HZ2-FRG/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;large5&lt;br /&gt;n=1073741825=1000000000000000000000000000001_2&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div style=&quot;text-align: right;&quot;&gt;&lt;a style=&quot;background-color: #000000; color: #000000;&quot; href=&quot;https://app.codility.com/demo/results/trainingYC3HZ2-FRG/#&quot;&gt;▶&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;large6&lt;br /&gt;n=1610612737=1100000000000000000000000000001_2&lt;/div&gt;
&lt;div style=&quot;color: #00b500;&quot;&gt;✔&lt;/div&gt;
&lt;div&gt;&lt;b&gt;OK&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>알고리즘 단련장/Codility</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/168</guid>
      <comments>https://snapcode.tistory.com/entry/%EC%BD%94%EB%94%9C%EB%A6%AC%ED%8B%B0-BinaryGap-%ED%94%8C%EB%9E%AB%ED%8F%BC%EC%97%90-%EB%8C%80%ED%95%9C-%EC%82%AC%EC%9A%A9-%ED%9B%84%EA%B8%B0#entry168comment</comments>
      <pubDate>Fri, 9 Jan 2026 21:05:29 +0900</pubDate>
    </item>
    <item>
      <title>[코딜리티] CodilitySync - GitHub 자동 연동 Chrome 확장프로그램</title>
      <link>https://snapcode.tistory.com/entry/%EC%BD%94%EB%94%9C%EB%A6%AC%ED%8B%B0-CodilitySync-GitHub-%EC%9E%90%EB%8F%99-%EC%97%B0%EB%8F%99-Chrome-%ED%99%95%EC%9E%A5%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[코딜리티]&amp;nbsp;CodilitySync&amp;nbsp;-&amp;nbsp;GitHub&amp;nbsp;자동&amp;nbsp;연동&amp;nbsp;Chrome&amp;nbsp;확장프로그램&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;60&quot; data-origin-height=&quot;60&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/L3K5X/dJMcaa43Wdq/AV3xRImSLi6HDxBEjbIRXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/L3K5X/dJMcaa43Wdq/AV3xRImSLi6HDxBEjbIRXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/L3K5X/dJMcaa43Wdq/AV3xRImSLi6HDxBEjbIRXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FL3K5X%2FdJMcaa43Wdq%2FAV3xRImSLi6HDxBEjbIRXk%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;60&quot; height=&quot;60&quot; data-origin-width=&quot;60&quot; data-origin-height=&quot;60&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; &lt;span style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot;&gt;코딜리티(Codility)는 공식적으로 GitHub와 직접적인 연동 기능을 제공하지는 않습니다.&lt;/span&gt; &lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot;&gt;즉, 코딜리티 플랫폼 내에서 코딩 테스트를 진행하는 동안 자동으로 코드가 개인 GitHub 리포지토리로 푸시되거나 하는 공식적인 방법은 없습니다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot; data-wiz-uids=&quot;x0mQTb_b,x0mQTb_c&quot;&gt;&lt;span data-wiz-attrbind=&quot;class=x0mQTb_a/TKHnVd&quot; data-animation-atomic=&quot;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #001d35; text-align: start;&quot; data-processed=&quot;true&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;비공식 연동 방법: Chrome 확장 프로그램 활용&lt;/b&gt;&lt;span data-wiz-uids=&quot;x0mQTb_l,x0mQTb_m&quot;&gt;&lt;span data-wiz-attrbind=&quot;class=x0mQTb_k/TKHnVd&quot; data-animation-atomic=&quot;&quot;&gt;&lt;span style=&quot;color: #001d35;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;div style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot; data-processed=&quot;true&quot; data-hveid=&quot;CAQQAA&quot; data-sfc-cp=&quot;&quot;&gt;가장 일반적인 비공식 방법은&lt;span&gt;&amp;nbsp;&lt;/span&gt;CodilitySync와 같은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Chrome 웹 스토어 확장 프로그램&lt;/b&gt;을 이용하는 것입니다. 이 확장 프로그램은 사용자가 코딜리티에서 문제를 풀고 제출할 때, 그 코드와 문제 설명을 사용자의 GitHub 리포지토리로 자동으로 동기화해 주는 기능을 제공합니다.&lt;span data-wiz-uids=&quot;x0mQTb_r,x0mQTb_s&quot;&gt;&lt;span data-wiz-attrbind=&quot;class=x0mQTb_q/TKHnVd&quot; data-animation-atomic=&quot;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://chromewebstore.google.com/detail/codilitysync/fphhigcdafaenfgknafdhgkjdfafdooo&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://chromewebstore.google.com/detail/codilitysync/fphhigcdafaenfgknafdhgkjdfafdooo&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1767956393172&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;CodilitySync - Chrome 웹 스토어&quot; data-og-description=&quot;Sync Codility submissions to your Github&quot; data-og-host=&quot;chromewebstore.google.com&quot; data-og-source-url=&quot;https://chromewebstore.google.com/detail/codilitysync/fphhigcdafaenfgknafdhgkjdfafdooo&quot; data-og-url=&quot;https://chromewebstore.google.com/detail/codilitysync/fphhigcdafaenfgknafdhgkjdfafdooo&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ovEeq/hyZPDx0iKg/b3M9bblA1X94SAJ1OinFr1/img.jpg?width=128&amp;amp;height=128&amp;amp;face=0_0_128_128&quot;&gt;&lt;a href=&quot;https://chromewebstore.google.com/detail/codilitysync/fphhigcdafaenfgknafdhgkjdfafdooo&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://chromewebstore.google.com/detail/codilitysync/fphhigcdafaenfgknafdhgkjdfafdooo&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ovEeq/hyZPDx0iKg/b3M9bblA1X94SAJ1OinFr1/img.jpg?width=128&amp;amp;height=128&amp;amp;face=0_0_128_128');&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;CodilitySync - Chrome 웹 스토어&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Sync Codility submissions to your Github&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;chromewebstore.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성함 맨 뒤가choi로 끝나는 걸 보니, 한국분께서 만드신건가 싶네요.&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;1200&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUxdS4/dJMcaaRxaDQ/qKYujQL5B8Ee0tukipIenk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUxdS4/dJMcaaRxaDQ/qKYujQL5B8Ee0tukipIenk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUxdS4/dJMcaaRxaDQ/qKYujQL5B8Ee0tukipIenk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUxdS4%2FdJMcaaRxaDQ%2FqKYujQL5B8Ee0tukipIenk%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;400&quot; height=&quot;200&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;333&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qSaZL/dJMcagc891l/Gk9pIvrTZTcX7UybNL6eQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qSaZL/dJMcagc891l/Gk9pIvrTZTcX7UybNL6eQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qSaZL/dJMcagc891l/Gk9pIvrTZTcX7UybNL6eQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqSaZL%2FdJMcagc891l%2FGk9pIvrTZTcX7UybNL6eQK%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;333&quot; height=&quot;150&quot; data-origin-width=&quot;333&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;692&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkz6p4/dJMcagYuTCo/WQ7QJz5byafjOaossuhxIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkz6p4/dJMcagYuTCo/WQ7QJz5byafjOaossuhxIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkz6p4/dJMcagYuTCo/WQ7QJz5byafjOaossuhxIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbkz6p4%2FdJMcagYuTCo%2FWQ7QJz5byafjOaossuhxIK%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;692&quot; height=&quot;220&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>알고리즘 단련장/Codility</category>
      <category>codility</category>
      <category>CodilitySync</category>
      <category>github</category>
      <category>깃허브</category>
      <category>깃헙</category>
      <category>동기화</category>
      <category>연동</category>
      <category>자동</category>
      <category>코딜리티</category>
      <category>확장프로그램</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/167</guid>
      <comments>https://snapcode.tistory.com/entry/%EC%BD%94%EB%94%9C%EB%A6%AC%ED%8B%B0-CodilitySync-GitHub-%EC%9E%90%EB%8F%99-%EC%97%B0%EB%8F%99-Chrome-%ED%99%95%EC%9E%A5%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8#entry167comment</comments>
      <pubDate>Fri, 9 Jan 2026 20:12:19 +0900</pubDate>
    </item>
    <item>
      <title>[코딜리티] Codility 플랫폼 친해지기 및 연습문제 시작하기</title>
      <link>https://snapcode.tistory.com/entry/%EC%BD%94%EB%94%9C%EB%A6%AC%ED%8B%B0-Codility-%ED%94%8C%EB%9E%AB%ED%8F%BC-%EC%B9%9C%ED%95%B4%EC%A7%80%EA%B8%B0-%EB%B0%8F-%EC%97%B0%EC%8A%B5%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[코딜리티]&amp;nbsp;Codility&amp;nbsp;플랫폼&amp;nbsp;친해지기&amp;nbsp;및&amp;nbsp;연습문제&amp;nbsp;시작하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JTYmp/dJMcacBONdn/p3IlwcLTngc4f0Kzr8jeD0/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JTYmp/dJMcacBONdn/p3IlwcLTngc4f0Kzr8jeD0/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JTYmp/dJMcacBONdn/p3IlwcLTngc4f0Kzr8jeD0/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJTYmp%2FdJMcacBONdn%2Fp3IlwcLTngc4f0Kzr8jeD0%2Fimg.webp&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;400&quot; height=&quot;210&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;630&quot;/&gt;&lt;/span&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;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;517&quot; data-origin-height=&quot;299&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfWDc4/dJMcagqFxIW/SkcIXl72AGYhNGUk31ItoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfWDc4/dJMcagqFxIW/SkcIXl72AGYhNGUk31ItoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfWDc4/dJMcagqFxIW/SkcIXl72AGYhNGUk31ItoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfWDc4%2FdJMcagqFxIW%2FSkcIXl72AGYhNGUk31ItoK%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;517&quot; height=&quot;299&quot; data-origin-width=&quot;517&quot; data-origin-height=&quot;299&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제풀이를 하려면 공식 홈페이지에서는 접속 경로가 잘 안보이고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코딜리티의 연습문제 데모 테스트는 아래 링크에 위치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://app.codility.com/programmers/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://app.codility.com/programmers/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1767955164740&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;Developer Training | Test Coding Skills Online - Codility&quot; data-og-description=&quot;Find longest sequence of zeros in binary representation of an integer.&quot; data-og-host=&quot;app.codility.com&quot; data-og-source-url=&quot;https://app.codility.com/programmers/&quot; data-og-url=&quot;https://app.codility.com/programmers/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://app.codility.com/programmers/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://app.codility.com/programmers/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;Developer Training | Test Coding Skills Online - Codility&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Find longest sequence of zeros in binary representation of an integer.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;app.codility.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우상단 Sign up 눌러주고 회원가입 진행하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;899&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o2wLY/dJMcaiWirjC/9N9jz3VnNv2NPx1jbuxiq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o2wLY/dJMcaiWirjC/9N9jz3VnNv2NPx1jbuxiq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o2wLY/dJMcaiWirjC/9N9jz3VnNv2NPx1jbuxiq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo2wLY%2FdJMcaiWirjC%2F9N9jz3VnNv2NPx1jbuxiq0%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;899&quot; height=&quot;559&quot; data-origin-width=&quot;899&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인 후, See all Lessons를 클릭하면&lt;br /&gt;&lt;a href=&quot;https://app.codility.com/programmers/lessons/1-iterations/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://app.codility.com/programmers/lessons/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;847&quot; data-origin-height=&quot;825&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bO9wm4/dJMcahpAYS0/BKBUaGpT6kS8az8SK5ZUYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bO9wm4/dJMcahpAYS0/BKBUaGpT6kS8az8SK5ZUYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bO9wm4/dJMcahpAYS0/BKBUaGpT6kS8az8SK5ZUYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbO9wm4%2FdJMcahpAYS0%2FBKBUaGpT6kS8az8SK5ZUYK%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;847&quot; height=&quot;825&quot; data-origin-width=&quot;847&quot; data-origin-height=&quot;825&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 17강으로 레슨이 구성되어 있다.&lt;br /&gt;저 주황색 글씨의&amp;nbsp; &quot;&lt;a id=&quot;readings&quot; style=&quot;background-color: #ffffff; color: #fb6f01; text-align: start;&quot; href=&quot;https://codility.com/media/train/Iterations.pdf&quot; data-ga-label=&quot;Iterations&quot; data-ga-action=&quot;Lesson Open Readings&quot;&gt;Open reading material (PDF)&lt;/a&gt;&quot; 열람용 자료 버튼을 눌러서 pdf를 다운받으면, 코딜리티에서 중요하게 보는 요소를 파악할 수 있다.(고 하는데, 봐보니깐 대학교때 강의자료 느낌인거같다)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;762&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFVJc4/dJMcahJTKDI/c3UN8hOzzby84xI9gc4Vt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFVJc4/dJMcahJTKDI/c3UN8hOzzby84xI9gc4Vt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFVJc4/dJMcahJTKDI/c3UN8hOzzby84xI9gc4Vt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFVJc4%2FdJMcahJTKDI%2Fc3UN8hOzzby84xI9gc4Vt0%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;710&quot; height=&quot;762&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;762&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;777&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zN37N/dJMcacIAJdb/QZFakf7PFnsmTS9wXzYmK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zN37N/dJMcacIAJdb/QZFakf7PFnsmTS9wXzYmK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zN37N/dJMcacIAJdb/QZFakf7PFnsmTS9wXzYmK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzN37N%2FdJMcacIAJdb%2FQZFakf7PFnsmTS9wXzYmK1%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;886&quot; height=&quot;777&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;777&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 화이팅 해보자 !&lt;br /&gt;&lt;br /&gt;아차차&lt;br /&gt;그 전에 github 자동 연동 확장 프로그램은 다음글에서&lt;/p&gt;
&lt;div id=&quot;gtx-trans&quot; style=&quot;position: absolute; left: -315px; top: -23px;&quot;&gt;
&lt;div class=&quot;gtx-trans-icon&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>알고리즘 단련장/Codility</category>
      <category>codility</category>
      <category>문제풀이</category>
      <category>알고리즘</category>
      <category>코딜리티</category>
      <category>코딩</category>
      <category>코딩테스트</category>
      <category>코테</category>
      <category>테스트</category>
      <category>플랫폼</category>
      <category>화이팅</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/166</guid>
      <comments>https://snapcode.tistory.com/entry/%EC%BD%94%EB%94%9C%EB%A6%AC%ED%8B%B0-Codility-%ED%94%8C%EB%9E%AB%ED%8F%BC-%EC%B9%9C%ED%95%B4%EC%A7%80%EA%B8%B0-%EB%B0%8F-%EC%97%B0%EC%8A%B5%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4#entry166comment</comments>
      <pubDate>Fri, 9 Jan 2026 19:55:43 +0900</pubDate>
    </item>
    <item>
      <title>[Gemini] 제미나이에서 지브리 스타일 이미지 만들기 (막차 탑승)</title>
      <link>https://snapcode.tistory.com/entry/Gemini-%EC%A0%9C%EB%AF%B8%EB%82%98%EC%9D%B4%EC%97%90%EC%84%9C-%EC%A7%80%EB%B8%8C%EB%A6%AC-%EC%8A%A4%ED%83%80%EC%9D%BC-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;스튜디오 지브리의 감성을 담은 그림을 인공지능으로 생성하기 위해,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;구글의 제미나이(Google Gemini)를 활용해보고자 합니다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;누구나 손쉽게 지브리 스타일의 이미지를 만들 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot; data-start=&quot;221&quot; data-end=&quot;242&quot;&gt;스튜디오 지브리 스타일이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;244&quot; data-end=&quot;363&quot;&gt;&lt;span&gt;스튜디오 지브리는 '이웃집 토토로', '센과 치히로의 행방불명' 등으로 유명한 일본의 애니메이션 스튜디오입니다.&lt;/span&gt; &lt;span&gt;그들의 작품은 다음과 같은 특징을 가지고 있습니다:&lt;/span&gt;​&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;365&quot; data-end=&quot;636&quot;&gt;
&lt;li data-start=&quot;365&quot; data-end=&quot;421&quot;&gt;&lt;b&gt;부드럽고 따뜻한 색감&lt;/b&gt;: &lt;span&gt;파스텔 톤과 자연스러운 색채를 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-start=&quot;422&quot; data-end=&quot;474&quot;&gt;&lt;b&gt;자연과의 조화&lt;/b&gt;: &lt;span&gt;풍부한 자연 배경과 생명력 있는 풍경을 그립니다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-start=&quot;475&quot; data-end=&quot;532&quot;&gt;&lt;b&gt;감성적인 캐릭터 디자인&lt;/b&gt;: &lt;span&gt;큰 눈과 섬세한 표정을 가진 캐릭터들이 등장합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li data-start=&quot;533&quot; data-end=&quot;636&quot;&gt;&lt;b&gt;마법과 현실의 융합&lt;/b&gt;: &lt;span&gt;현실적인 세계에 마법적인 요소를 자연스럽게 녹여냅니다.&lt;/span&gt;​&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot; data-start=&quot;643&quot; data-end=&quot;672&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot; data-start=&quot;643&quot; data-end=&quot;672&quot;&gt;제미나이로 지브리 스타일 이미지 생성하기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot; data-start=&quot;674&quot; data-end=&quot;694&quot;&gt;1. 제미나이 접속 및 로그인&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;696&quot; data-end=&quot;905&quot;&gt;
&lt;li data-start=&quot;696&quot; data-end=&quot;754&quot;&gt;&lt;b&gt;웹사이트&lt;/b&gt;: &lt;a href=&quot;https://gemini.google.com&quot; data-start=&quot;708&quot; data-end=&quot;754&quot;&gt;gemini.google.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot; data-start=&quot;907&quot; data-end=&quot;925&quot;&gt;2. 이미지 생성 요청하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;927&quot; data-end=&quot;1052&quot;&gt;&lt;span&gt;제미나이는 텍스트 프롬프트를 통해 이미지를 생성합니다.&lt;/span&gt; &lt;span&gt;예를 들어:&lt;/span&gt;​&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot; data-start=&quot;1054&quot; data-end=&quot;1141&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;1056&quot; data-end=&quot;1141&quot;&gt;&lt;span&gt;&quot;벚꽃이 흩날리는 공원에서 우산을 쓰고 있는 소녀, 지브리 스타일로 그려줘&quot;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span data-state=&quot;closed&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;1143&quot; data-end=&quot;1228&quot;&gt;&lt;span&gt;이러한 프롬프트를 입력하면 제미나이가 해당하는 이미지를 생성해 줍니다.&lt;/span&gt;​&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot; data-start=&quot;1230&quot; data-end=&quot;1249&quot;&gt;3. 이미지 수정 및 재생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;1251&quot; data-end=&quot;1336&quot;&gt;&lt;span&gt;생성된 이미지가 마음에 들지 않으면 다음과 같이 요청할 수 있습니다:&lt;/span&gt;​&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-start=&quot;1338&quot; data-end=&quot;1509&quot;&gt;
&lt;li data-start=&quot;1338&quot; data-end=&quot;1379&quot;&gt;&lt;span&gt;&quot;다른 버전으로 다시 그려줘&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li data-start=&quot;1380&quot; data-end=&quot;1421&quot;&gt;&lt;span&gt;&quot;배경에 큰 벚나무를 추가해줘&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li data-start=&quot;1422&quot; data-end=&quot;1509&quot;&gt;&lt;span&gt;&quot;비가 내리는 분위기로 바꿔줘&quot;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;1511&quot; data-end=&quot;1596&quot;&gt;&lt;span&gt;이러한 추가 지시를 통해 원하는 이미지를 얻을 때까지 수정할 수 있습니다.&lt;/span&gt;​&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot; data-start=&quot;1598&quot; data-end=&quot;1611&quot;&gt;4. 이미지 저장&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot; data-start=&quot;1613&quot; data-end=&quot;1698&quot;&gt;&lt;span&gt;마음에 드는 이미지가 생성되면, 이미지 위에서 마우스 오른쪽 버튼을 클릭하여 '다른 이름으로 저장'을 선택하거나, 모바일에서는 이미지를 길게 눌러 저장할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;원본&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;356&quot; data-origin-height=&quot;356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/N0I5j/dJMcaf6d77m/dyP0sQuiMTrMB0Am4oGFxk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/N0I5j/dJMcaf6d77m/dyP0sQuiMTrMB0Am4oGFxk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/N0I5j/dJMcaf6d77m/dyP0sQuiMTrMB0Am4oGFxk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FN0I5j%2FdJMcaf6d77m%2FdyP0sQuiMTrMB0Am4oGFxk%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;356&quot; height=&quot;356&quot; data-origin-width=&quot;356&quot; data-origin-height=&quot;356&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;프롬프트&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;869&quot; data-origin-height=&quot;413&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ow6rl/dJMcagxh56P/facbBDbg8q6wn6oQ0FUJjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ow6rl/dJMcagxh56P/facbBDbg8q6wn6oQ0FUJjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ow6rl/dJMcagxh56P/facbBDbg8q6wn6oQ0FUJjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fow6rl%2FdJMcagxh56P%2FfacbBDbg8q6wn6oQ0FUJjK%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;869&quot; height=&quot;413&quot; data-origin-width=&quot;869&quot; data-origin-height=&quot;413&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1차 결과&lt;/b&gt;&lt;/h2&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/ol2ky/dJMcabQhRpW/XPxIH9TaRhiAtIyTeem2OK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ol2ky/dJMcabQhRpW/XPxIH9TaRhiAtIyTeem2OK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ol2ky/dJMcabQhRpW/XPxIH9TaRhiAtIyTeem2OK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fol2ky%2FdJMcabQhRpW%2FXPxIH9TaRhiAtIyTeem2OK%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;500&quot; height=&quot;500&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;AI Agentic BE개발자 스럽게 다시 그려줘&lt;/b&gt;&lt;/h2&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/kTZMY/dJMcagxh58U/ipJwIMl4KqSOWLt6nDffG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kTZMY/dJMcagxh58U/ipJwIMl4KqSOWLt6nDffG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kTZMY/dJMcagxh58U/ipJwIMl4KqSOWLt6nDffG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkTZMY%2FdJMcagxh58U%2FipJwIMl4KqSOWLt6nDffG1%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;500&quot; height=&quot;500&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;배경이 너무 정신없어 정리해줘&lt;/b&gt;&lt;/h2&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/cPc1Zq/dJMcacuTXgk/JQkNNRpvpi2g5Y7r2G0Pz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPc1Zq/dJMcacuTXgk/JQkNNRpvpi2g5Y7r2G0Pz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPc1Zq/dJMcacuTXgk/JQkNNRpvpi2g5Y7r2G0Pz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPc1Zq%2FdJMcacuTXgk%2FJQkNNRpvpi2g5Y7r2G0Pz0%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;500&quot; height=&quot;500&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;키보드 타이핑 하는 모습이었으면 좋겠어&lt;/b&gt;&lt;/h2&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/BKn7m/dJMcaajxHLy/q7eXeOzT5TfIDPKzSdB821/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BKn7m/dJMcaajxHLy/q7eXeOzT5TfIDPKzSdB821/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BKn7m/dJMcaajxHLy/q7eXeOzT5TfIDPKzSdB821/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBKn7m%2FdJMcaajxHLy%2Fq7eXeOzT5TfIDPKzSdB821%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;500&quot; height=&quot;500&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;배경을 좀더 디지털스럽게 바꿔줘&lt;/b&gt;&lt;/h2&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/mUP8t/dJMcagjKZ5b/j2QYtRtl6zGhqK81KQTcn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mUP8t/dJMcagjKZ5b/j2QYtRtl6zGhqK81KQTcn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mUP8t/dJMcagjKZ5b/j2QYtRtl6zGhqK81KQTcn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmUP8t%2FdJMcagjKZ5b%2Fj2QYtRtl6zGhqK81KQTcn0%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;500&quot; height=&quot;500&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>IT/AI</category>
      <category>ai</category>
      <category>Gemini</category>
      <category>생성</category>
      <category>생성형AI</category>
      <category>이미지</category>
      <category>제미나이</category>
      <category>지브리</category>
      <author>snapcoder</author>
      <guid isPermaLink="true">https://snapcode.tistory.com/165</guid>
      <comments>https://snapcode.tistory.com/entry/Gemini-%EC%A0%9C%EB%AF%B8%EB%82%98%EC%9D%B4%EC%97%90%EC%84%9C-%EC%A7%80%EB%B8%8C%EB%A6%AC-%EC%8A%A4%ED%83%80%EC%9D%BC-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0#entry165comment</comments>
      <pubDate>Wed, 17 Dec 2025 01:20:59 +0900</pubDate>
    </item>
  </channel>
</rss>