<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>playername_ltt.log</title>
        <link>https://velog.io/</link>
        <description>개발자에서 엔지니어로, 엔지니어에서 리더로</description>
        <lastBuildDate>Tue, 28 Apr 2026 00:09:02 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>playername_ltt.log</title>
            <url>https://velog.velcdn.com/images/playername_ltt/profile/6387d941-fd2f-4d97-ad70-1f354aa6beff/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. playername_ltt.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/playername_ltt" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[CMUX x AIM 해커톤 회고]]></title>
            <link>https://velog.io/@playername_ltt/CMUX-x-AIM-%ED%95%B4%EC%BB%A4%ED%86%A4-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@playername_ltt/CMUX-x-AIM-%ED%95%B4%EC%BB%A4%ED%86%A4-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Tue, 28 Apr 2026 00:09:02 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/playername_ltt/post/f2d2225a-dafc-415f-86b4-22f9603737f5/image.png" alt=""></p>
<h1 id="introduce">Introduce</h1>
<hr>
<p>살면서 처음 해커톤을 나갔습니다.</p>
<p>제가 아는 일반적인 해커톤과는 다르게 8시간이 주어졌고, 거의 full 바이브 코딩으로 이어졌습니다.</p>
<p>트랙은 3가지입니다.</p>
<ol>
<li><strong>Developer Tooling</strong>: CLI 기반 개발자 도구를 만드는 트랙입니다.</li>
<li><strong>Business &amp; Apps</strong>: AI 서비스를 개발하는 트랙입니다.</li>
<li><strong>AI Safety &amp; Security</strong>: AI 취약점을 찾고, 드러내고, 방어하는 트랙입니다.</li>
</ol>
<p>그 중에서도 저희 팀은 “<strong>Developer Tooling</strong>” 트랙으로 결정했습니다.</p>
<h1 id="main-concept">Main Concept</h1>
<hr>
<p>저희 팀은 우선 한 가지 생각을 했습니다.</p>
<blockquote>
<p>우리가 필요한 도구를 만들자.</p>
</blockquote>
<ul>
<li>저희 팀원 중 한명은 <code>Grafana</code>와 같은 서버 모니터링 툴이 필요하지만, 초기 세팅이 많이 불편하다고 했습니다.</li>
<li>저는 로그를 추적하는 데에 있어서 너무 많은 공수가 든다고 했습니다.</li>
</ul>
<p>그래서 저희는 “<strong>Agent CLI 스타일의 모니터링&amp;분석 툴</strong>”을 개발하기로 했습니다.</p>
<h1 id="start-of-monix">Start of “Monix”</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/a73400b4-3310-44e7-bcdb-5de65cb8f01a/image.gif" alt=""></p>
<p><code>Monix</code>는 위에서 말했듯이 “<strong>Agent CLI 스타일의 모니터링&amp;분석 툴</strong>”입니다.</p>
<p>일단 두괄식으로 시작하겠습니다. 저희의 repo &amp; demo영상 입니다.</p>
<p>한번 구경하시고 피드백해주시면 지속적으로 시간 될 때마다 디벨롭해 나갈 생각입니다. 
물론 contribution를 받는것도 고려 중입니다.</p>
<blockquote>
<ul>
<li>Github Repo: <a href="https://github.com/co-tox/monix">monix repository link</a></li>
<li>Demo Video: <a href="https://drive.google.com/file/d/1jE6OPbg0eVTfLMZMoNPrI6TYKX4QiDFZ/view?usp=embed_facebook">monix demo video link</a></li>
</ul>
</blockquote>
<h2 id="development-plan">Development Plan</h2>
<hr>
<p>다들 처음 순수 바이브 코딩으로만 개발하는 것도 익숙하지 않고, 처음 개발 계획을 세우는 것이 난관이었습니다.</p>
<p>우선 저희가 리스트업한 해커톤에서 완성하려고 목표한 기능은 다음과 같았습니다.</p>
<ul>
<li><code>htop</code>처럼 CPU, Memory, Swap Memory 등등 성능을 실시간으로 모니터링 할 수 있는 기능</li>
<li>사용자가 지정한 interval로 성능 매트릭스를 수집하여 이를 기록으로 남기고, 분석할 수 있도록 하는 기능</li>
<li>app, docker, nginx의 로그를 각각 실시간으로 모니터링할 수 있도록 하는 기능</li>
<li>인스턴스 내의 각종 로그를 우리 툴에서 쉽게 확인하고 분석할 수 있도록 하는 기능</li>
<li>자연어로 성능 지표, 로그 등을 LLM과 Q&amp;A할 수 있도록 하는 기능</li>
</ul>
<p>사실상 마지막 기능이 핵심이었습니다. 우리는 <code>Claude Code</code>같은 모니터링 툴을 만들고 싶었으니까요.</p>
<p>저희의 협업 계획은 의외로 간단한 프로세스로 이루어졌습니다.</p>
<ol>
<li>(공통) 초기 디렉토리 셋업 &amp; PRD 정리 &amp; 테스트 코드 작성 및 실행 공통 Skill 추가</li>
<li>A - llm기능 개발 / B - 서버 성능 모니터링 기능 개발 / C - 로그 기능 개발 / D - UI 및 입/출력 관련기능 개발</li>
<li><code>main</code>에서 각자 브랜치 &amp; 디렉토리 하나씩 파서 기능 개발 후, 주기적으로 main에 병합하여 conflict를 최소화</li>
<li>무한반복</li>
</ol>
<p>이론상은 간단하지만 이 과정에 도출하기까지 한 사이클정도 걸렸습니다.</p>
<h2 id="start-developing">Start Developing</h2>
<hr>
<p>저는 위의 기능 중 “로그 기능 개발”을 맡았습니다.</p>
<p>이번 해커톤의 주최사 중 하나가 ‘CMUX’인 만큼 바로 사용하기 시작했습니다.</p>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/907dc4d4-f1d8-4cbe-81ec-aadefa3d52a2/image.png" alt=""></p>
<p>CMUX는 위와 같이 에이전트 멀티태스킹 개발에 특화된 터미널 개발 툴입니다. 제 실제 해커톤에서 개발중이던 화면입니다.</p>
<p>Claude Code로 기능 개발과 오류 수정, Gemini로 git 관리와 document 관리, 창 하나는 테스트용으로 사용했습니다.</p>
<p>중간중간 작업이 끝날 때마다 <code>cmux</code>에서 이를 확인해 알림을 알려주는 것이 너무 좋더라고요. 아쉽게도 mac에서만 지원합니다.</p>
<h3 id="develop-base-features">Develop base features</h3>
<p>우선 저는 회사에서 여러 서비스의 백엔드 로그를 디렉토리 옮겨다니며 작업하는 것이 불편했기에, 이 툴 하나로 한꺼번에 해결하고 싶었습니다.</p>
<p>제가 선택한 방법은 “alias” 였습니다.</p>
<table>
<thead>
<tr>
<th>명령어</th>
<th>용도</th>
</tr>
</thead>
<tbody><tr>
<td><code>/log add @alias -app &lt;path&gt;</code></td>
<td>애플리케이션 로그를 별칭으로 등록</td>
</tr>
<tr>
<td><code>/log add @alias -nginx &lt;path&gt;</code></td>
<td>Nginx 로그 등록</td>
</tr>
<tr>
<td><code>/log add @alias -docker &lt;name&gt;</code></td>
<td>Docker 컨테이너 로그 등록</td>
</tr>
<tr>
<td><code>/log list</code></td>
<td>등록된 모든 별칭 표시</td>
</tr>
<tr>
<td><code>/log @alias [-n N]</code></td>
<td>등록된 로그 tail</td>
</tr>
<tr>
<td><code>/log @alias --search [pattern]</code></td>
<td>에러 / 정규식 패턴 필터링</td>
</tr>
<tr>
<td><code>/log @alias --live</code></td>
<td>라이브 스트리밍</td>
</tr>
<tr>
<td><code>/log /path [-n N] [--live]</code></td>
<td>직접 경로 접근(등록 불필요)</td>
</tr>
<tr>
<td><code>/log remove @alias</code></td>
<td>등록 해제</td>
</tr>
<tr>
<td><code>/logs &lt;path&gt; [N]</code></td>
<td>일회성 tail (레거시 형식)</td>
</tr>
</tbody></table>
<p>위와 같은 기능들을 리스트업해두고, “alias”를 통해 추적하고자 하는 로그 파일을 등록하고 사용하는 방식을 선택했습니다.</p>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/4b2eaf2b-941b-4d7e-a934-bab9e3d24e33/image.png" alt=""></p>
<p>정신없이 진행한 저만의 개발 프로세스는 다음과 같습니다.</p>
<ol>
<li>claude code 세션 하나로 신규 기능을 개발</li>
<li>다른 claude code 세션 하나로 아쉬운점 피드백을 통한 수정 및 오류 수정</li>
<li>gemini 현재까지의 작업을 git에 push</li>
<li>다른 gemini 하나로 변경사항을 확인해 document 파일 업데이트 또는 다음 개발사항 체크하여 문서화</li>
<li>저는 직접 실사용 테스트 수행</li>
</ol>
<p>세션별로 진행중인 개발상황을 공유하기 위해서는 별도의 개발 진행상황 및 plan 문서를 작성해두어 다른 에이전트가 읽고 수행할 수 있도록 했는데, 이게 일차원적이긴 하지만 꽤나 유용한 방법 중 하나였습니다.</p>
<h3 id="difficulty-in-develop-agent-tool-calling">Difficulty in Develop, Agent Tool calling</h3>
<p>저의 난관은 툴 개발에서 막혔습니다. 제가 잘 모르기 때문에 이건 진짜 하나도 감이 안왔습니다.</p>
<p>일단 하나도 몰랐기 때문에 “llm에서 log 툴을 사용할 수 있도록 개발해줘.”라고 지시를 내렸습니다.</p>
<p>하지만 이후 llm에게 <code>alias</code>와 함께  자연어로 요청을 할 때마다 그냥 단순 로그 출력만 하고 끝나는 상황을 보게 되었습니다. 의도한 방향이 아니었어요.</p>
<p>몇번 버그를 고치려고 명령을 아무리 내려봐도, 결과는 같았습니다.</p>
<p>그래서 저는 “기획 의도”를 에이전트에게 주입했습니다. 우리의 서비스는 “에이전트 기반 성능 모니터링 &amp; 로그 분석 툴”이라고, <strong>모든 베이스는 사용자가 llm에게 내리는 자연어 명령</strong>이라고 말입니다.</p>
<p>에이전트를 제대로 써먹기 위한 핵심은 “기획”을 에이전트에게 얼마나 잘 전달하느냐였습니다.</p>
<h2 id="finish-development">Finish Development</h2>
<hr>
<p>중간중간 팀원과 <code>main</code>브랜치에서 싱크를 맞추고 계속 에러 핸들링을 해가며 개발을 완료했습니다.</p>
<p>아쉬운 점도 있지만, 짧은 시간 내에 이만큼이나 했다는 데에 만족스러웠습니다.</p>
<p>팀원 중 한명의 사이드 프로젝트 인스턴스에 우리 툴을 띄워 테스트했습니다.</p>
<p>마지막은 팀원들과 분배하여 document작성, demo 영상 촬영, PPT 작성, 피칭 계획 등을 진행하고 마무리했습니다.</p>
<p>저희는 아마 이후</p>
<ul>
<li>모니터링하다 문제가 발생할 경우 webhook을 통한 알림 기능(Slack, Discord 등)</li>
<li><code>/커맨드</code>자동완성. 지금 가능한 커맨드는 보여주지만, 자동완성 기능은 제공하지 않습니다.</li>
<li>로컬에서 클라우드 인스턴스를 모니터링하며 문제가 발생할 경우, llm이 직접 조치사항을 제어할 수 있는 mcp 기능</li>
</ul>
<p>기능들을 목표로 추가 개발/유지보수에 들어갈 것 같습니다.</p>
<h1 id="⭐️-what-did-you-get-from-this-hackathon">⭐️ What did you get from this Hackathon?</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/c0534b25-c41d-4d83-a9f1-c8dd3ef31679/image.png" alt=""></p>
<p>제가 이번 해커톤에서 얻은 가장 큰 것은 의외로 “자신감” 입니다. 소위 “괴물”이 많았음에도 불구하고요.</p>
<p>솔직히 AI를 원없이 써본 것은 처음이었습니다. full vibe 코딩만으로 뭔가를 만들어낸 것도 처음이고요.</p>
<p>CLI 개발도 처음이었습니다. 이번에 개발자 도구를 만들기로 했지만 걱정이 많이 되었어요.</p>
<p>그런데 그게 무색해질만큼 에이전트는 너무 일을 잘했고, 기본적인 개발 지식과 에이전트만 잘 다룰 줄 안다면 가능했습니다. 처음이 두렵지 시작하고 나면 뭐든 해낼 수 있습니다. AI가 잘 보조할거고, 뚝딱대더라도 버벅거리더라도 언젠가는 목표를 달성할 수 있죠.</p>
<p>바이브 코딩이 힘들거나 에이전트 활용법이 고민되는 분들이 있다면 같이 공유해주시면 너무 좋을 것 같습니다. 모른다면 공부해서라도 같이 싸매며 나아가봐요.</p>
<p>AI는 너무 급격히 발전을 이루었고 이건 이제 멈출 수 없습니다.</p>
<p><strong>“4차 산업혁명”이라는 말이 너무나 와닿는 해커톤이었습니다.</strong></p>
<p>이번일로 많이 고민이 되더라고요. 회사에서 지원하는 ‘Claude Pro’만으로는 너무 부족해서 개인적으로 Max를 결제할까 생각 좀 해봐야겠습니다.</p>
<p><em>이번 해커톤을 계기로 앞으로 재미있는 해커톤이 있다면 찾아서 나가봐야겠습니다. 피드백은 언제나 환영입니다.</em></p>
<p><em>monix의 피드백과 추가 기능 추천도 해주신다면 너무 감사드리겠습니다. 팀원들과 공유하여 디벨롭하겠습니다 :)</em></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AI 코딩 툴, 제대로 쓰고 계신가요?]]></title>
            <link>https://velog.io/@playername_ltt/AI-%EC%BD%94%EB%94%A9-%ED%88%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%93%B0%EA%B3%A0-%EA%B3%84%EC%8B%A0%EA%B0%80%EC%9A%94</link>
            <guid>https://velog.io/@playername_ltt/AI-%EC%BD%94%EB%94%A9-%ED%88%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%93%B0%EA%B3%A0-%EA%B3%84%EC%8B%A0%EA%B0%80%EC%9A%94</guid>
            <pubDate>Tue, 17 Mar 2026 00:08:04 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>본 컨텐츠는 길벗출판사에서 보내준 <a href="https://gilbut.co/c/26010575Dq">&lt;AI 자율학습 클로드 코드·코덱스 CLI·제미나이 CLI 완전 활용법&gt;</a> 을 읽고 작성한 컨텐츠 입니다.</p>
<p>서평 작성이라서 생각을 많이 했는데 이 활용법을 읽고, 이 학습서의 대략적인 내용을 <strong>저의 추가 활용방안에 대한 생각과 작성해 보려고 합니다.</strong> 아마 독후감과 서평 사이라고 보시면 될 것 같습니다.</p>
<p>마지막에 제가 책을 읽고 직접 만들어본 <strong><code>Claude Code를 위한 Springboot 프로젝트 템플릿</code></strong>을 공유드립니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/1a313273-0e13-456b-842d-11ffe45dc549/image.png" alt=""></p>
<p>언제까지 브라우저 창과 IDE를 오가며 &#39;복사-붙여넣기&#39;의 굴레에 갇혀 있을 수는 없습니다. 이제는 터미널 안에서 코드베이스를 직접 이해하는 AI를 만나야 할 때입니다.</p>
<h1 id="왜-지금-ai-코딩-cli인가">왜 지금 ‘AI 코딩 CLI’인가?</h1>
<hr>
<p>현 시점에서 IT 업계 종사자라면 AI를 사용하지 않아본 분들은 없을 겁니다.</p>
<p>아, 그니까 웹 브라우저에서 사용하는 AI 말입니다.</p>
<p>다들 모르는 문제에 대하여 질문을 작성하고, GPT가 내놓은 답변을 읽고(때때로는 귀찮아 코드만 복사하고), 내용을 붙여넣고, 돌려보고, 안된다고 푸념하고, 다시 작성하고…… 반복해본 경험 있으실 겁니다.</p>
<p>여기서 다루는 가장 큰 세 가지인 <code>Claude Code</code>, <code>Codex CLI</code>, <code>Gemini CLI</code>는 여러분의 코드베이스를 읽어 여러분이 물어본 질문에 답변하고 요청한 작업을 수행합니다.</p>
<p>우선 이 책에서는 AI 코딩 도구를 크게 두 가지 분류로 나누어 말합니다.</p>
<p><code>IDE형</code>과 <code>터미널형</code>으로요.</p>
<p>AI 코딩 도구가 처음 각광받기 시작하던 시점에는 IDE가 시장을 지배했었지만, 현 시점에서는 터미널 기반 툴이 단연코 시장을 압도하고 있습니다.</p>
<p>이 책은 그 “CLI 툴”을 입문부터 실무까지 사용하는 방법을 소개하고 있습니다.</p>
<p>툴 사용을 위한 기초적인 배경지식, 툴 설치부터 프로젝트에 툴 사용하기까지 광범위한 내용을 다루고 있습니다.</p>
<h1 id="cli-툴들-사용해보기">CLI 툴들 사용해보기</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/7525115e-84f2-4899-8d38-e4471640c574/image.png" alt=""></p>
<p>책에서 다루는 가장 첫 번째 툴은 “Claude Code”입니다.</p>
<p>많은 사람들이 입을 모아 “가장 개발을 잘한다”라고 얘기하는 도구이죠.</p>
<blockquote>
<p>이전 글(<a href="https://velog.io/@playername_ltt/%ED%95%9C%EB%8B%AC%EB%A7%8C%EC%97%90-AI%EB%A1%9C-%EC%BD%94%EB%93%9C-3%EB%A7%8C%EB%9D%BC%EC%9D%B8-%EC%A7%9C%EB%A9%B0-%EA%B9%A8%EB%8B%AC%EC%9D%80-%EA%B2%83">한달만에 AI로 코드 3만라인 짜며 깨달은 것</a>)을 작성하던 시점에는 Claude Code를 사용해보지 않았지만, 최근 회사에서 Claude Code를 지원해주기 시작하여 열심히 써먹어보고 있습니다.</p>
</blockquote>
<p>우선 빠르게 각 챕터별 핵심 내용 위주로 소개해드리겠습니다.</p>
<p>처음 써보는 사람도 상세하게 따라 할 수 있도록 책에 상세하게 잘 정리되어있으니 궁금하시면 책 참고하시면 좋을 것 같습니다.</p>
<h2 id="기본-사용법">기본 사용법</h2>
<hr>
<p><strong>“컨텍스트(Context) 관리하기”</strong></p>
<p>쉽게 말해 대화 내용 관리입니다. 최근 RLM(Recursive Language Model)의 발견 등으로 인해 컨텍스트 윈도우의 크기(한 세션에서 AI가 맥락을 이해할 수 있는 길이)가 비약적으로 발전 중이지만, 이는 다 비용으로 이어집니다.</p>
<p>무엇보다 비용이 문제가 아닌, AI가 사용자의 의도를 잘못 이해하기 시작하면 이는 곧 생산성 저하로 이어지기 때문에 이를 제대로 관리하는 것이 핵심이라고 할 수 있습니다.</p>
<p>상황 하나를 가정해봅시다.</p>
<p>AI에게 </p>
<p><em>“이벤트를 날짜별로 조회할 수 있는 기능을 만들어줘”</em> </p>
<p>라고 입력했더니 AI는 이벤트의 조회 조건을 하루하루 끊어서 진짜 순수 날짜별로만 조회할 수 있게 만들어버립니다.</p>
<p>잘못된 기능이기에 다시 요청합니다. </p>
<p><em>“아니 이게 아니고 이벤트 날짜별 조회인데 특정 기간을 지정하고 그 사이에 진행하는 모든 이벤트를 조회하고 싶었던 거야.”</em></p>
<p>이 두 컨텍스트는 AI에게 불필요한 컨텍스트가 되어 AI 기능과 기억력에 사소하게 마이너스 요소로 작동합니다.</p>
<p>이를 해결하기 위해 가장 쉬운 방법은 “Claude Code”에서 <code>/compact</code>를 실행해버리면 끝입니다. 핵심만 추출하고 이전 컨텍스트들은 날려버리거든요.</p>
<p>AI의 성능과 속도를 위해선 <code>/compact</code>가 아니더라도, 컨텍스트 관리가 필수적입니다.</p>
<p>이 부분이 다들 놓치고 넘어갈 수 있는 부분인데 언급하고 사유까지 더 상세하게 이해하기 쉽게 설명해준 부분이 상당히 인상적이었습니다.</p>
<h2 id="프로젝트-운영">프로젝트 운영</h2>
<hr>
<p><strong>“md 파일로 프로젝트 구조잡고 운영하기”</strong></p>
<p>사실 이 부분은 제가 거의 써먹지 않은 방법이었습니다. </p>
<p>멀티모듈 프로젝트를 운영하며 모듈별 관리에 고민이 많았는데, 책에서 제안한 &#39;모듈별 <code>.md</code> 관리 방식&#39;이 무척 유용했습니다.
각 모듈의 폴더 내에 직접 설명 문서를 두는 방식을 최근 적용하기 시작했는데, 확실히 최초 페르소나 부여 및 프로젝트 구조 파악이 빨라지는 효과를 체감하고 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/7e31af85-97c4-433f-be4f-fa12fbd41447/image.png" alt=""></p>
<h3 id="1-계층적-컨텍스트-구조-hierarchical-context">1. 계층적 컨텍스트 구조 (Hierarchical Context)</h3>
<p>가장 대표적인 사례는 Anthropic의 <strong>Claude Code</strong>가 제안하는 <strong><code>CLAUDE.md</code></strong> 기반 구조입니다.</p>
<p>프로젝트 루트에만 가이드를 두는 것이 아니라, <strong>각 모듈이나 디렉토리별로 컨텍스트 파일을 분산 배치</strong>하여 AI가 해당 폴더에 진입했을 때 필요한 정보만 읽게 하는 방식입니다.</p>
<ul>
<li><strong>Root <code>CLAUDE.md</code></strong>: 전체 기술 스택, 빌드 명령어, 전역 코드 스타일.</li>
<li><strong>Module <code>CLAUDE.md</code> (혹은 <code>README.md</code>)</strong>: 해당 모듈의 도메인 로직, 의존 관계, 특정 설계 패턴(예: &quot;이 모듈은 Querydsl만 사용함&quot;).</li>
</ul>
<blockquote>
<p><strong>Claude Code 공식 문서:</strong> <a href="https://code.claude.com/docs/en/best-practices">Best Practices for Claude Code</a></p>
<p>문서에서는 &quot;코드만으로 추론할 수 없는 의도&quot;를 담기 위해 <code>CLAUDE.md</code>를 적극 권장하며, 대규모 프로젝트에서는 하위 디렉토리에 전용 가이드를 둘 것을 제안합니다.</p>
</blockquote>
<h3 id="2-agentsmd-표준화-프로젝트">2. <code>AGENTS.md</code> 표준화 프로젝트</h3>
<p>특정 도구(Claude, Cursor)에 종속되지 않고, 모든 AI 에이전트가 읽을 수 있는 공통 규약을 만들려는 시도입니다.</p>
<p>프로젝트 루트에 <code>AGENTS.md</code>를 두고 아래와 같은 섹션을 명확히 구분합니다.</p>
<ol>
<li><strong>Onboarding:</strong> 프로젝트 목적 및 환경 설정.</li>
<li><strong>Architecture:</strong> 데이터 흐름 및 모듈 구조.</li>
<li><strong>Rules &amp; Standards:</strong> 금지 사항(Anti-patterns) 및 필수 준수 사항.</li>
</ol>
<blockquote>
<p><strong>AGENTS.md 공식 사이트:</strong> <a href="https://agents.md/">agents.md</a></p>
<p>OpenAI Codex, Google, Cursor 등의 개발자들이 협력하여 만든 오픈 형식입니다. 여러 에이전트가 공통으로 참조할 수 있는 &quot;AI 전용 README&quot;의 표준을 제시합니다.</p>
</blockquote>
<h3 id="3-규칙-기반-모듈화-cursorrules-및-clauderules">3. 규칙 기반 모듈화 (<code>.cursorrules</code> 및 <code>.claude/rules/</code>)</h3>
<p>최근에는 설정 파일 하나에 모든 규칙을 때려 넣는 대신, <strong>관심사별로 규칙 파일을 쪼개는 방식</strong>이 주류입니다.</p>
<pre><code># 구조 예시

project-root/
├── .claude/
│   └── rules/
│       ├── testing.md       # 테스트 코드 작성 원칙
│       ├── api-design.md    # API 네이밍 및 DTO 규약
│       └── db-schema.md     # JPA 및 DB 마이그레이션 규칙
├── module-websocket/
│   └── README.md            # WebSocket 명세 및 로직 설명
└── module-api/
    └── README.md            # API 명세 및 인증 흐름</code></pre><blockquote>
<p><strong>Cursor 가이드:</strong> <a href="https://cursor.com/docs/rules">Cursor Rules Best Practices</a></p>
<p>&quot;규칙은 짧고(500라인 이하), 구체적이며, 파일 기반으로 참조(Reference files)해야 한다&quot;고 명시합니다. 특히 복잡한 백엔드 프로젝트에서 AI의 환각을 줄이는 가장 효과적인 방법으로 꼽힙니다.</p>
</blockquote>
<h2 id="고급-기능-활용">고급 기능 활용</h2>
<hr>
<p>MCP 사용법과, 서브에이전트 사용법, Hook 사용법 등을 설명해 주고 있습니다.</p>
<p>이 부분에서는 고급 기능인 만큼 충분히 익숙해지고 이해도를 높인 다음 사용하는 것을 추천드립니다.</p>
<p>(저도 사실 아직 제대로 못써보고 있습니다)</p>
<p>최근 AI의 지나친 권한 부여, 보안 등 이슈가 많았거든요. 발전속도에 비해 못따라오는 듯한 그런 느낌도 있고요.</p>
<ul>
<li><a href="https://www.tomshardware.com/tech-industry/artificial-intelligence/claude-code-deletes-developers-production-setup-including-its-database-and-snapshots-2-5-years-of-records-were-nuked-in-an-instant">개발자의 프로덕션 설정을 날려버린 Claude Code</a></li>
<li><a href="https://zdnet.co.kr/view/?no=20251108141527">AI 보안이슈 보고</a></li>
</ul>
<p>혹시나 읽어보시거나 사용하실 때 공유할만한 팁이 있다면 공유해주세요 :)</p>
<h1 id="ai-워크플로우-자동화-개발-흐름-완성">AI 워크플로우, 자동화 개발 흐름 완성</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/63d8f84c-7992-4d9f-a492-b4386ac9da3e/image.png" alt=""></p>
<p>앞에서 쌓은 지식들을 바탕으로 AI를 만땅으로 사용하는 개발 워크플로우를 순차적으로 상세하게 설명해줍니다.</p>
<h2 id="프로젝트-계획-단계">프로젝트 계획 단계</h2>
<hr>
<blockquote>
<p>PRD 작성 → 실행계획과 WBS 작성 → 컨텍스트 파일 작성</p>
</blockquote>
<h3 id="prd">PRD</h3>
<p>PRD는 문서를 완벽하게 만드는 것이 아니라, AI가 참고할 핵심 기준을 만드는 것을 주요 목적으로 합니다.</p>
<ul>
<li>제품 개요, 핵심 기능, 대상 사용자, 기술 스택 등을 작성합니다.</li>
<li>저같은 경우는 귀찮아서 AI한테 초안 작성하라고 한다음에 수정합니다.</li>
</ul>
<h3 id="실행계획과-wbs-작성">실행계획과 WBS 작성</h3>
<p>실행 계획은 프로젝트를 어떤 순서대로 진행할지 정리하는 로드맵입니다.</p>
<ul>
<li>기존에는 타임라인 정의, 작업 간 의존성 파악, 리소스 배분, 주요 마일스톤 설정이 필수적입니다.</li>
</ul>
<p>WBS는 큰 작업을 작은 작업들로 세분화하고 관리하기 쉽게 만드는 구조입니다.</p>
<ul>
<li>PRD를 잘 작성해 두었다면, Plan 모드를 켜서 실행계획과 WBS 또한 AI가 상세하게 작성해줍니다.</li>
</ul>
<h3 id="컨텍스트-파일-작성">컨텍스트 파일 작성</h3>
<p>PRD로 무엇을 만들지, WBS로 어떻게 만들지를 정의했다면 이를 활용하여 컨텍스트 파일을 작성하여 계획을 마무리짓습니다.</p>
<ul>
<li>AI가 프로젝트를 안정적으로 이해하기 위한 파일문서 입니다.</li>
<li>프로젝트 요약, 기술 스택, 핵심 규칙, 폴더 구조 등을 명시해둡니다.</li>
</ul>
<pre><code>// 컨텍스트 파일 example

# 프로젝트 컨텍스트: [프로젝트 명]

## 1. 프로젝트 요약
- **목적**: [예: 실시간 컨퍼런스 통번역 및 기록 서비스]
- **대상 사용자**: [예: 다국어 행사를 진행하는 운영진 및 참가자]
- **핵심 가치**: 저지연 번역(500ms 이내), 높은 도메인 용어 정확도

## 2. 기술 스택 (Tech Stack)
- **Backend**: Java 21, Spring Boot 3.4
- **Database**: PostgreSQL (Main), Redis (Cache)
- **AI/ML**: Google Gemini API, LangChain4j
- **Infrastructure**: Docker, Kubernetes

## 3. 핵심 개발 규칙 (Core Rules)
- **Architecture**: 멀티모듈 구조를 따르며, 각 모듈의 책임(Domain/API/Infra)을 엄격히 분리한다.
- **Code Style**: Google Java Style Guide를 준수하고, 모든 비즈니스 로직은 Service 계층이 아닌 Domain 엔티티 내부에서 처리하도록 노력한다.
- **Exception**: `CustomException`을 정의하여 사용하며, 모든 API 응답은 `ApiResponse&lt;T&gt;` 포맷으로 통일한다.
- **Testing**: JUnit 5와 AssertJ를 사용하여 핵심 도메인 로직에 대한 단위 테스트를 반드시 작성한다.

## 4. 폴더 구조 (Project Structure)
- `project-api/`: 외부 클라이언트 요청 처리 (Controller, DTO)
- `project-domain/`: 순수 비즈니스 로직 및 엔티티
- `project-infra/`: DB 설정, 외부 API(Gemini) 연동부
- `docs/`: PRD, WBS 등 기획 문서 보관</code></pre><h3 id="추가적인-계획-단계의-팁">추가적인 계획 단계의 팁</h3>
<ul>
<li><strong>&quot;AI에게는 지도가 필요&quot;</strong>: PRD가 &#39;무엇을(What)&#39; 만들지 정의한다면, 컨텍스트 파일은 &#39;어떻게(How)&#39; 유지할지를 정의합니다. 폴더 구조만 명시해줘도 AI가 파일을 찾느라 엉뚱한 곳을 헤매는 시간을 80% 이상 줄일 수 있습니다.</li>
<li><strong>&quot;규칙은 구체적일수록 좋음&quot;</strong>: 단순히 &quot;Clean Code를 지켜줘&quot;라고 하기보다 &quot;Service 계층에서 인터페이스를 쓰지 마&quot;처럼 <strong>도구적으로 명확한 지시</strong>를 담는 것이 핵심입니다.</li>
<li><strong>&quot;살아있는 문서화&quot;</strong>: 프로젝트가 진행되면서 새로운 기술이나 규칙이 추가되면 이 파일을 가장 먼저 업데이트하세요. AI는 항상 최신화된 이 파일을 바탕으로 다음 단계의 코드를 생성합니다</li>
</ul>
<h2 id="실전-프로젝트-수행">실전 프로젝트 수행</h2>
<hr>
<blockquote>
<p>부트스트래핑 → 단계별 개발 → TDD 개발 → 코드 품질 개선 → 배포와 운영</p>
</blockquote>
<p>이 파트에서는 각 파트별 AI로 “무지성 바이브 코딩”이 아닌 “똑똑한 바이브 코딩”하는 법을 예시와 함께 상세하게 안내해 줍니다.</p>
<p>각 예시를 직접 해보며 하는 것을 추천드리며, 이 부분은 책을 따라가서 해보면 좋을 것 같습니다.</p>
<p>아래는 제가 자주 사용하는 코드 리팩토링 프롬프트입니다.</p>
<pre><code>&lt;클래스&gt;를 리팩토링하세요.
- SOLID원칙과 클린코드 원칙 준수
- 디자인패턴 적용이 가능한 포인트 점검 후 적용
- 과도한 추상화 및 클래스 분리 지양</code></pre><h1 id="⭐claude-code를-위한-springboot-프로젝트-템플릿">⭐Claude Code를 위한 Springboot 프로젝트 템플릿</h1>
<hr>
<p>아래는 책을 읽고 제가 나름대로 만들어본 Claude Code를 위한 Springboot 프로젝트 템플릿입니다.</p>
<blockquote>
<p><a href="https://github.com/ksj000625/springboot-for-claude-code-template">Springboot Template for Claude-Code</a></p>
</blockquote>
<p>코드를 보고 편의에 맞게 수정해서 사용하세요.</p>
<p>추후 시간이 될 때 종종 업데이트하고, FastAPI등의 다른 프레임워크의 boilerplate도 추가하겠습니다.</p>
<h1 id="번외-ai-쓰면-공부가-안될-거-같은데요">번외: AI 쓰면 공부가 안될 거 같은데요</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/a3263aa7-e7c7-4ca4-8a20-c16327ad0a69/image.png" alt=""></p>
<p>반은 맞고 반은 틀립니다. 개발자가 되고 싶어 갓 공부를 시작한 대학교 저학년이라면 이게 충분히 맞는 말입니다.</p>
<p>하지만 어느정도 개발에 대한 이해를 하고 나면 AI가 오히려 공부에 도움이 됩니다.</p>
<p>AI 툴은 훌륭한 조수입니다. 여러분의 생각을 보조하고, 필요하다면 방향도 제시해줄 수 있죠.</p>
<p>혼자 작성한 코드만을 바라보며 공부한다면 자신의 스타일과 구현 방법 등에 매몰되어 더딘 성장을 할 거라고 생각합니다.</p>
<p>AI 툴은 여러분이 생각하지 못한 문제 해결 방법과 코드를 제공합니다. 남의 코드를 보는 것은 성장의 발판 중 하나이죠.</p>
<p>또한, 한번이라도 써보고 본인 가치관에 맞는 생각을 해보길 추천합니다.</p>
<h1 id="마무리하며">마무리하며</h1>
<hr>
<p>AI 툴도 공부하며 잘 쓸줄 알아야 하는 시대가 다가오고 있습니다. 개발자는 점점 AI들을 사용하는 기술자이자 지휘자가 되어가고 있습니다.</p>
<p>저는 개발자를 ‘기능을 구현하는 사람’이라고 정의하고, 엔지니어를 ‘문제를 정의하고 해결방법을 찾는 사람’이라과 정의합니다. 이제 ‘개발’은 AI에게 맡기고, ‘엔지니어링’은 사람이 맡아 진행해야 한다고 생각합니다.</p>
<p>미뤄오던 AI 툴 공부, 이제 슬슬 함께 시작해봅시다 :)</p>
<p><em>피드백이나 소개해줄 팁이 있다면 언제든 댓글 달아주세요.</em></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[한달만에 AI로 코드 3만라인 짜며 깨달은 것]]></title>
            <link>https://velog.io/@playername_ltt/%ED%95%9C%EB%8B%AC%EB%A7%8C%EC%97%90-AI%EB%A1%9C-%EC%BD%94%EB%93%9C-3%EB%A7%8C%EB%9D%BC%EC%9D%B8-%EC%A7%9C%EB%A9%B0-%EA%B9%A8%EB%8B%AC%EC%9D%80-%EA%B2%83</link>
            <guid>https://velog.io/@playername_ltt/%ED%95%9C%EB%8B%AC%EB%A7%8C%EC%97%90-AI%EB%A1%9C-%EC%BD%94%EB%93%9C-3%EB%A7%8C%EB%9D%BC%EC%9D%B8-%EC%A7%9C%EB%A9%B0-%EA%B9%A8%EB%8B%AC%EC%9D%80-%EA%B2%83</guid>
            <pubDate>Thu, 26 Feb 2026 00:09:02 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/playername_ltt/post/b8dcf195-1602-4e61-adfa-a8f33c523433/image.png" alt=""></p>
<p>우선 제목에서 나왔듯이 약 한달간 spring 약 2만라인, flutter 약 1만라인 정도를 짰습니다.</p>
<p>당연히 손수 3만라인을 작성하기에는 하루 8~9시간으로는 턱도 없어요.</p>
<p>그러면 프로젝트의 메인 기능을 맡게 된 만 1년차 개발자가
AI를 도대체 어떻게 썼는지, 무엇을 깨달았는지 공유해드리겠습니다.</p>
<h1 id="무슨-ai를-사용하셨나요">무슨 AI를 사용하셨나요?</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/5fe7fd1e-8c77-4862-8eeb-a52b13341fd6/image.png" alt=""></p>
<p>요즘 다들 <code>Claude</code>에 열광합니다. <code>Claude Code</code>는 현존 최고의 코드 에이전트라고 해도 무방할 정도로 무섭게 명성을 쌓아가고 있죠.</p>
<p>하지만 제가 사용한 툴은 <code>Gemini CLI</code> 와 <code>Cursor</code>입니다.</p>
<p>이유는 간단했습니다. 제가 Gemini pro 6개월 무료이용권을 받아서, Cursor는 회사에서 결제해줘서죠.</p>
<p>만약 AI 코드 에이전트를 써보고 싶은데 돈값 잘 못할까봐 걱정되는 분들은 무료 사용량이 넉넉한 <code>Gemini CLI</code>로 시작하는 것을 강력 추천합니다.</p>
<p>저는 무료 사용기간이 끝나면 그때 <code>Claude Code</code>로 갈아탈 예정입니다.</p>
<h1 id="ai와-협업하는-개발-루틴">AI와 협업하는 개발 루틴</h1>
<hr>
<p>실제 최근에 제가 지시한 내용은 아니고, 간단한 예시들 위주로 저만의 루틴을 공유해드리겠습니다.</p>
<h2 id="1-페르소나-설정-및-업무범위-지시">1. 페르소나 설정 및 업무범위 지시</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/05d5b99e-4077-44e6-9e0e-cf38cda9fdc6/image.png" alt=""></p>
<p>우선 저는 하루의 업무를 시작하기 전, 다음과 같이 지시합니다.</p>
<pre><code>당신은 시니어급 코어 백엔드 엔지니어입니다.
오늘 당신은 저의 지시를 받아 &lt;프로젝트 이름&gt;의 개발을 진행할 부하직원 개발자입니다.

모든 작업은 **근거있는 계획** 하에 이루어져야 하며, 빌드를 통한 테스트를 해야 합니다.
작업 후에는 이전과 이후를 비교하여 **한국어**로 결과보고를 해야 합니다.

우선 오늘은 &lt;프로젝트 이름&gt;의 &lt;하위 패키지&gt;를 개발할 예정입니다.
전체 코드를 대략적으로 읽어 해당 서버의 기술 스택을 이해하고,
&lt;하위 패키지&gt;의 파일을 꼼꼼히 읽어 비즈니스와 로직을 파악하세요.</code></pre><p>이 외에는 업무별로 강조하고 싶은 것을 추가로 작성하는 편입니다.</p>
<p>한국어를 굳이굳이 강조한 이유는 얘가 자꾸 영어를 뱉습니다. 저렇게 해놔도 자꾸 영어로 말할 때가 있긴 해요.</p>
<p>그리고 이렇게 코드를 많이 읽게 한다면 <strong>“토큰은 썩어나냐”</strong> 라고 할 수도 있는데 괜찮은 이유는 다음과 같습니다.</p>
<ol>
<li>코드를 읽어서 파악하게 할 때에는 <code>gemini-3-flash-preview</code>모델로 설정하여 토큰을 잘 사용하지 않는 모델로 파악하게 하여 캐시시켜 둡니다.
개발 시에는 <code>gemini-3-pro-preview</code>모델로 변경하여 진행하기 때문에 캐시된 코드를 읽고 사고하는 것은 pro모델이라 사용량이 별개입니다.</li>
<li>캐시라는 것이 존재하기 때문에 이렇게 해도 진짜 빡세게 하루종일 쓰지 않는 한 거의 부족하지 않습니다.</li>
</ol>
<p>모든 판단을 AI에게 맡기지 않고 로직 설계 후 AI에게 업무지시를 내리는 것이기 때문에 “바이브 코더”분들만큼까지는 많은 토큰을 사용하지 않습니다.</p>
<h2 id="2-1차-작업-지시기초">2. 1차 작업 지시(기초)</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/330c8166-c8cf-4588-b827-368c86c0b159/image.png" alt=""></p>
<p>아무리 AI가 코드를 잘 짜도 100%마음에 드는 코드를 뱉기란 쉽지 않습니다. 그래서 저는 애초에 한 기능을 여러 차례에 걸쳐 진행할 생각을 하고 1차 업무지시를 내립니다.</p>
<h3 id="rest-api-개발-예시희망편">Rest API 개발 예시(희망편)</h3>
<pre><code>&lt;클래스&gt;의 &lt;메소드&gt;에 &quot;이벤트 검색 API&quot;의 엔드포인트만 지정해 둔 껍데기만 만들어두었습니다.
다음 요구사항을 만족하는 API를 개발하세요.
---
* 기능명: 이벤트 검색 API
* 검색 파라메터: 이벤트명, 주최사, 기간(시작일시, 종료일시), 이벤트 상태코드, 장소명
* Response DTO: 이벤트명, 주최사, 이벤트 날짜, 이벤트 상태코드, 장소명, 생성일시, 수정일시
* 상세 요구사항:
- 이벤트의 도메인 클래스는 &quot;Event&quot;입니다
- &quot;시작일시 &lt; 이벤트 날짜 &lt; 종료일시&quot; 인 데이터
- 이벤트 상태코드 클래스는 &quot;EventStatusCode&quot;클래스를 참조하세요</code></pre><p>명확하게 말로 설명하기 쉬운 작업은 위와 같이 깔끔하게 정리하여 요구사항을 전달합니다.</p>
<p>저기서 조금 더 대략적으로 설명해도 됩니다.</p>
<p>추가적으로 저의 팁은, 꽤나 복잡하거나 멋대로 수정하면 곤란한 경우에는
”개발 계획을 세우고 저에게 보고하세요. 저의 승인 후 개발을 진행해야 합니다.” 와 같은 방식을 사용하는 것도 괜찮습니다.</p>
<h3 id="rest-api-개발-예시절망편">Rest API 개발 예시(절망편?)</h3>
<pre><code>Event 도메인 객체를 사용하는 CRUD API를 설계하고 작성하세요.
비슷한 예시로는 &quot;NoticeController&quot;의 전체적인 로직과 비슷하게 짜면 됩니다.
단계적으로 생각하고 꼼꼼히 똑바로 짜세요.</code></pre><p>라는 방식도 괜찮아요. 어차피 잘못된 거는 읽고 수정하라고 하면 되니까요.</p>
<h2 id="3-2차-작업-지시수정">3. 2차 작업 지시(수정)</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/270f2640-fb17-4402-9a6a-08c553c610b3/image.png" alt=""></p>
<p>진짜 쉬운 CRUD를 제외하고는 마음에 100% 들지 않는 결과물이 나올 확률이 꽤나 높습니다.</p>
<p>저는 1차 작업 결과물을 읽어 로직의 흐름을 파악하고 다음과 같은 방식으로 지시합니다.</p>
<h3 id="비즈니스-로직-수정-예시희망편">비즈니스 로직 수정 예시(희망편)</h3>
<pre><code>방금 작업물에서 다음 내용들을 수정해야 합니다.
---
1. 방금 작업한 EventService 클래스의 getEventList 메소드의 검증 로직을 살펴보면,
EventStatusCode가 실제 DB 레코드에는 들어가지 않지만, 조회에만 사용하는 &quot;ALL&quot;이 존재합니다.
해당 케이스를 고려하여 비즈니스 로직을 수정하세요.

2. updateEvent 메소드에서 코드 컨벤션을 준수하기 위해, Domain 객체를 @Builder를 사용하여
메소드 내부에서 생성하여 사용하는데, 이 내용을 EventConverter 클래스에 선언하고 가져다 쓰도록 수정해주세요.

3. (내용은 대충 기억나진 않지만, 제가 생각한 비즈니스 로직의 순서로 수정하라고 지시하기도 합니다.)</code></pre><p>사실 2번같은 자잘한 내용들은 프롬프트 적는 시간에 제가 직접 수정하는 것이 훨씬 빠르지만 예시로 작성했습니다.</p>
<p>코드를 읽고 상세하게 개발 의도를 파악하여 반박해보고 가능한 한 상세하게
(메소드 레벨까지, 필요시 변수도) 수정 흐름을 지시하는 편이 좋습니다.</p>
<h3 id="비즈니스-로직-수정-예시절망편">비즈니스 로직 수정 예시(절망편)</h3>
<pre><code>검색조건에는 &quot;ALL&quot;이 존재합니다. 수정하세요.
또한 updateEvent()에서 Event를 만들어 쓰는 것이 아닌 것 같습니다.
다른 방법 생각해서 수정해주세요.</code></pre><p>바쁠때는 이렇게 하기도 합니다.</p>
<p>프롬프트는 어떻게 짜든 상관 없지만 저의 결론은 다음과 같습니다.</p>
<blockquote>
<p>내용이 어렵고 복잡한 로직일 수록 최대한 상세하고 가독성 좋게 지시한다.</p>
</blockquote>
<h2 id="4-마무리-작업-지시정리">4. 마무리 작업 지시(정리)</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/39f2ca0b-321a-4835-87c0-541f00400476/image.png" alt=""></p>
<p>이건 최근에 너무 복잡한 기능을 짜다보니 코드를 제대로 구조화하지 않으면 AI가 개발한 작업물들을 읽고 판단하기에 어려워집니다.</p>
<p>따라서 요즘에는 다음과 같은 간단한 프롬프트를 추가로 지시합니다.</p>
<pre><code>방금 개발한 코드들을 리팩토링하세요.
SOLID원칙과 클린코드 원칙을 최대한 준수하여 작업하세요.
추가로 디자인패턴 적용이 가능한 포인트가 있는지 한번 검토하세요.
적당한 수준에서 디자인패턴을 적용하세요.</code></pre><p>또는 간단한 수정만 했다면</p>
<pre><code>방금 개발한 updateEvent 메소드는 너무 fat합니다.(혹은, 파라메터가 많아 code smell을 경계해야 합니다)
해당 메소드에서 내부적으로 사용하는 비즈니스 로직들을 책임에 따라 분리하세요.</code></pre><p>이렇게 하여 이후 코드의 가용성과 유지보수성을 최대한 높입니다.</p>
<p>또한 어느정도 작업이 한번 이루어지면 한번 더 지시합니다.
계속 작업하다 보면, 중앙화시킬 수 있는 코드들이 분명 생기기 때문이죠.</p>
<p>사람 뿐만이 아니라 AI도 결국 코드를 읽는 것이기 때문에 의도에 맞게 클래스와 메소드를 분리하고,
한 클래스나 메소드에 책임 등이 너무 몰리는 것을 경계해야 합니다.</p>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/418c2b20-bd16-4934-84e2-7825bb013663/image.png" alt=""></p>
<p><em>번외로 ‘클린코드’라는 책을 추천합니다.</em> </p>
<p><em>사람이 코드 짜는 시대는 저물어가지만, AI의 사용성을 높이기에도 좋은 소프트웨어 개발의 가장 근본적인 책이라고 볼 수 있습니다.</em></p>
<h2 id="번외">번외</h2>
<hr>
<p>제가 위에서 예시로 작성한 코드들을 살펴보면 “하지 마라”라는 키워드는 단 한번도 사용하지 않았습니다.</p>
<p>요즘에는 많이들 알고있듯이, AI도 사람과같이 “하지 마라”등의 부정적인 언어는 그걸 더 생각하게 되는 경향을 보인다는 이야기가 있거든요.</p>
<p>원래 사람도 하지말라는 것을 더 하고 싶어지는 법이잖아요.</p>
<h1 id="ai와-회의하기">AI와 회의하기</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/296da557-d712-4c02-9020-6c31af59bc7f/image.png" alt=""></p>
<p>때때로 특정 기능에 대한 처음 가닥을 잡기가 막막할 때가 있습니다.</p>
<p>이럴 때 사용하는 저만의 방법은 요구사항을 달성하기 위해 “AI와 회의를 하는 것”입니다.</p>
<h3 id="예시">예시</h3>
<pre><code>Redis streams를 사용하여 행사의 상태를 다른 클라이언트에게 전파하고 해당 내용을 저장해야 합니다.
이걸 WebSocket에서 텍스트메세지를 받아 handleTextMessage() 메소드에 구현하려고 하는데,
어떤 방법이 최적의 방법일까요?

또한 scale-out되었을 때를 고려하여
이벤트의 상태 업데이트 시점을 handleTextMessage()에 클라이언트의 요청이 도착했을 때 시점이 아닌,
브로드캐스팅을 위해 구현하 BroadcastService에서 다른 클라이언트에 메세지를 내려주기 직전의
시점에서 구현하는 것이 낫다고 생각하는데, 어떻게 생각하나요? 혹시 다른 좋은 방법이 있을까요?

--- 다른 예시

상태 변경이 비동기 멀티스레딩 환경이라 객체의 원자성이
updateEventStatusCode() 메소드에서 보장이 되나요?

--- 다른 예시

ConcurrentHashMap으로 Event객체들을 관리하는데,
메모리 해제가 제대로 이루어져야 장기적인 운영환경에서 안정적으로 돌아갑니다.
메모리 누수가 일어날 수 있는 부분이 있을까요?
또한 이를 방지하기 위해 배치 스케줄러를 통해 좀비 객체들을 주기적으로 정리하는 것은 어떻게 생각하나요?</code></pre><p>위에는 제가 비슷하게 물어보며 회의했던 질문의 유형들입니다.</p>
<p>이 외에도 몇 가지가 있지만, 직접 AI와 회의하는 것이 의미있기 때문에 이만 줄이겠습니다.</p>
<h1 id="에이전트를-쓰며-내린-결론">에이전트를 쓰며 내린 결론</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/5bacdc62-9fbc-4abc-807f-3e0747124b94/image.png" alt=""></p>
<p>요즘 시대에 AI없이 개발한다는 말은 “저는 시대에 뒤떨어지는 사람입니다”라고 고백한다는 말과 같습니다.</p>
<p>AI를 잘 쓰기 위해서는 많이 써봐야 되고, 자신에게 최적화된 사용법이 중요합니다.</p>
<p>바이브코더처럼 AI로 단기간에 폭발적인 속도로 개발하는 것도 좋지만, 개발자라면 개발자에게 어울리는 AI 활용법이 존재합니다.</p>
<p>비즈니스 로직을 검토하고, 메모리 관리에 더 나은 방법을 검토하고, 오류 발생시 추적하기 쉽도록 하고, fallback로직을 설계하고… 바이브코더와는 다른 개발자만의 장점을 AI로 살릴 수 있을 때 경쟁력이 올라간다고 생각합니다.</p>
<p>AI 에이전트를 사용하는데 익숙하지 않다면, <code>Gemini CLI</code>로 저처럼 개발을 시작해보는 것을 다시 한 번 강력하게 추천드립니다.</p>
<p>다른 AI 활용 팁들이 있다면 공유해주세요.</p>
<p>피드백은 언제나 환영합니다 :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AI 엔지니어링 재직자 부트캠프 회고]]></title>
            <link>https://velog.io/@playername_ltt/AI-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-%EC%9E%AC%EC%A7%81%EC%9E%90-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@playername_ltt/AI-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81-%EC%9E%AC%EC%A7%81%EC%9E%90-%EB%B6%80%ED%8A%B8%EC%BA%A0%ED%94%84-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Fri, 13 Feb 2026 12:13:18 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/playername_ltt/post/c5d43738-c00b-4dbb-bff5-a894a40da322/image.png" alt=""></p>
<p>열심히 백엔드 개발자로서 일하고 있었습니다.</p>
<p>AI를 잘 활용하는 편이라고 생각하며 개발해 나가는 와중 링크드인에는 “클로드 코드로 <del>했습니다.”, “Agent가 ~</del>입니다.” 등등 수많은 글이 올라오고 있었고요.</p>
<p>시대가 빠르게 변하는게 하루하루 느껴지고 있었습니다.</p>
<p>저는 단기 목표를 세웠죠.</p>
<blockquote>
<p>바뀐 시대에 걸맞는 AI형 인재가 되자.
그리고 가능하다면 AI 엔지니어까지 도전해보자.</p>
</blockquote>
<h1 id="어디에-왜-지원했나요">어디에 왜 지원했나요?</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/b2dfaa35-6061-4a76-ad9f-20429df95c80/image.png" alt=""></p>
<p>우선 저는 몇번 들어본 “스파르타클럽 재직자 부트캠프”의 <strong>“AI 서비스 엔지니어링 트랙”</strong>에 지원했습니다. 2025년 11월 말부터 26년 2월 초까지 진행했습니다.</p>
<p>회사에서 텍스트 후보정한다고 오픈소스인 모델 가져다 쓴 것도 재미있었고, AI가 떠오르기도 하고 해서였습니다. 결론은 재밌어보여서였죠.</p>
<p>처음에는 완전 전문적인 AI 부트캠프 또는 대학원 진학도 고민해봤었습니다.</p>
<p>하지만 현실적으로 일을 관두고 전문적인 부트캠프로 들어가기에는 부담스러웠습니다.</p>
<p>매달 나가는 생활비, 월세, 공과금, 적금 등등.</p>
<p>그래서 재직자 부트캠프를 선택했습니다. </p>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/1bfbcac6-8cbf-49d2-8794-aa92f96fd2fd/image.png" alt=""></p>
<p>일하면서 해당 내용 공부를 얕게나마 해볼 수 있다는 점이 메리트로 느껴졌었거든요.</p>
<p>마침 파이썬도 기본적인건 써봤고, 서버지식과 웹지식도 있기에 지원했습니다.</p>
<h1 id="절반은-필요없었습니다">절반은 필요없었습니다</h1>
<hr>
<p>먼저 단점부터 얘기해보자면 저에게는 과정의 절반가까이 필요 없는 내용이었습니다.</p>
<p>아무래도 재직자 부트캠프 특성상 개발자만 오는 것은 아니고, 오히려 비개발자분들이 더 많기도 하기 때문에 파이썬 기초와 API호출하고 사용하는법 등부터 설명해야 했고 그 과정은 저에게 필요 없었습니다.</p>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/e7341483-2f9e-40ba-8ffd-42671d381562/image.png" alt=""></p>
<p>하지만 강의를 들으며 설명해주시는 튜터분들도 설명을 이해하기 쉽게 해주시고(비개발자라고 생각하고 들어봤을 때) 힘들어하시는 비개발자분들을 열심히 챙겨주셨어요.</p>
<h1 id="절반은-도움-많이-됐습니다">절반은 도움 많이 됐습니다</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/c33b269a-0e33-4c50-addc-aae5eeb3a1c8/image.png" alt=""></p>
<p>물론 앞의 내용은 기본적으로 알고 있는 내용이었지만 뒤의 내용은 도움이 꽤 될 법한 내용들로 구성이 되어있었는데, 진행했던 과제 위주로 설명해보자면</p>
<ul>
<li>RNN 모델 학습</li>
<li>영어-독일어 번역모델 학습</li>
<li>프롬프트 엔지니어링</li>
<li>RAG 챗봇 구현</li>
<li>Multimodal 기초(하이퍼 파라메터 조정)</li>
</ul>
<p>위와 같습니다. 꽤나 알차게 절반의 시간을 보낸거 같고 솔직히 50%정도 이해했나 싶습니다… AI선생님과 함께 혼자 더 공부해봐야죠.</p>
<h1 id="이제-ai-엔지니어-되는건가요">이제 AI 엔지니어 되는건가요?</h1>
<hr>
<p>아직은 한참 부족합니다. 제가 부캠에서 얻은건 사실 전문지식보다는 아이디어와 어떠한 문제를 이런식으로 해결할수 있다는 관점? 같은 느낌을 배워온게 더 크다고 생각합니다.</p>
<p>단적인 예시로 회사에서 LLM 번역 기능을 개발중인데,</p>
<ol>
<li>번역사전 적용</li>
<li>텍스트를 목표 언어로 번역</li>
<li>번역된 텍스트 검증</li>
</ol>
<p>등과 같은 AI 워크플로우를 만들어 사용하고 싶었으나…</p>
<p>시간도 부족하고 실시간으로 제공해줘야 하는 서비스이기에</p>
<ol>
<li>텍스트를 목표 언어로 번역(핵심로직이라 그대로)</li>
<li>OpenNLP 라이브러리로 번역결과 잘못된 언어인지만 검증</li>
</ol>
<p>플로우까지밖에 우선 작성하지 못했습니다. 이후 이 문제를 어떻게 해결할 수 있을지 더 고민해보고 해봐야겠죠.</p>
<p>이런 AI를 이용한 문제 해결 등이 재밌어서 이쪽 공부를 계속 해가며 도전해볼 생각은 있습니다. </p>
<p>제 잡지식이 많은 장점을 살릴 수 있는 것은 Product Engineer (괜시리 있어보이게)처럼 전반적인 개발문제를 다루는 직업이 적합하지 않을까 혼자 생각해봤습니다.</p>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/9f8a9697-a471-40df-882b-318a032af38c/image.png" alt=""></p>
<p>물론 인생이야 어떻게 흘러갈지 아무도 모르는거죠.</p>
<h1 id="재직자-부트캠프-추천하시나요">재직자 부트캠프, 추천하시나요?</h1>
<hr>
<p>우선 두 가지 유형의 분들에게 추천합니다.</p>
<ol>
<li>IT직종의 비개발자(PM, 디자이너)</li>
<li>AI에 관심은 있지만 어디서부터 시작할지 모르겠는 개발자</li>
</ol>
<p>물론 이 외에도 배우고 싶다는 분들은 당연히 들으셔야죠.</p>
<p>비개발자분들은 많이 힘들어하십니다. 개발에 대한 기본 베이스가 많이 부족하시거든요. </p>
<p>하지만 IT직종의 비개발자분들은 소통을 해오며 완전 기본적인 이야기라도 자주 접하시는 분들이기 때문에 가장 많은 것을 배울 수 있는 분들이지 않을까 싶습니다.</p>
<p>물론 10주는 턱없이 부족한 시간이기에(월화수목 3시간씩 + 토 8시간) 혼자 공부할 시간이 많이 필요할 것 같습니다.</p>
<p>하물며 본업이 있으신 재직자분들은 교육기간에도 야근이 잡히면 따라잡기가 많은 힘이 들긴 합니다.</p>
<p>하지만 이를 견뎌내면 저의 경우에는 “이거 해보고싶다”와 같은 아이디어? 욕구? 같은 것들이 조금 생겼습니다.</p>
<h1 id="이제-뭘-하실건가요">이제 뭘 하실건가요?</h1>
<hr>
<p>제 특기가 일 벌여놓고 수습하기라서, 뭔가 혼자를 뚝딱거리며 만들어볼 것 같습니다.</p>
<p>이론만 주구장창 쳐다보면 재미없거든요. 뭐라도 결과물이 보여야 흥미가 생기는 법이거든요.</p>
<p>이렇게 혼자 뚝딱거리다 어디서든 써먹을 수 있지 않을까 싶을 때 사이드 프로젝트도 해보고, 더 나아가 원한다면 AI 엔지니어로 직무 변환도 할 수 있지 않을까 싶기도 합니다.</p>
<p>약 3달 가까이 퇴근 후의 시간을 반납한 사람의 부트캠프 회고였습니다.</p>
<p><em>조언과 피드백, 좋은 인사이트 공유는 언제나 환영입니다 :)</em></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[과유불급(過猶不及)과 기술 의사결정: Redis Streams 적용기]]></title>
            <link>https://velog.io/@playername_ltt/%EA%B3%BC%EC%9C%A0%EB%B6%88%EA%B8%89%E9%81%8E%E7%8C%B6%E4%B8%8D%E5%8F%8A%EA%B3%BC-%EA%B8%B0%EC%88%A0-%EC%9D%98%EC%82%AC%EA%B2%B0%EC%A0%95-Redis-Streams-%EC%A0%81%EC%9A%A9%EA%B8%B0</link>
            <guid>https://velog.io/@playername_ltt/%EA%B3%BC%EC%9C%A0%EB%B6%88%EA%B8%89%E9%81%8E%E7%8C%B6%E4%B8%8D%E5%8F%8A%EA%B3%BC-%EA%B8%B0%EC%88%A0-%EC%9D%98%EC%82%AC%EA%B2%B0%EC%A0%95-Redis-Streams-%EC%A0%81%EC%9A%A9%EA%B8%B0</guid>
            <pubDate>Thu, 29 Jan 2026 00:29:55 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/playername_ltt/post/70d2361f-750b-4ad6-8bee-e0b9df7412b2/image.png" alt=""></p>
<h1 id="redis의-두-가지-용도">Redis의 두 가지 용도</h1>
<hr>
<h2 id="1-redis-cachepubsub">1. Redis Cache(Pub/sub)</h2>
<hr>
<p><strong>Redis Pub/Sub</strong>은 메시지를 보내는 <strong>발행자(Publisher)</strong>와 메시지를 받는 <strong>구독자(Subscriber)</strong>가 서로를 알 필요 없이 <strong>채널(Channel)</strong>을 통해 통신하는 구조입니다.</p>
<p>Redis Pub/Sub이 다른 메시징 시스템(Kafka, Redis Streams 등)과 가장 차별화되는 특징은 <strong>&#39;데이터의 비영속성*</strong>과 <strong>&#39;실시간성&#39;</strong>입니다.</p>
<p>자세한 내용은 <a href="https://lucas-owner.tistory.com/60">Redis Pub/Sub</a> 을 참고하세요. 오늘의 핵심 주제는 <strong>Redis Streams</strong>입니다.</p>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/1754d441-c307-4b7d-bb11-29c3416b52be/image.png" alt=""></p>
<h2 id="2-redis-streams">2. Redis Streams</h2>
<hr>
<p><strong>Redis streams</strong>는 <strong>Append-only Log(추가 전용 로그)</strong> 형태의 ‘자료구조’로, 시간 순서대로 이벤트나 메시지를 저장하고 관리하는 데 특화되어 있습니다.</p>
<p>자세한 설명은 <a href="https://splendidlolli.tistory.com/762">Redis streams</a> 을 참고하면 될 것 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/cc04ec11-3574-475a-bbee-efb6227c5793/image.png" alt=""></p>
<h1 id="그래서-redis-streams를-왜-알아봤는데">그래서 Redis streams를 왜 알아봤는데</h1>
<hr>
<p>제가 담당한 기능의 요구사항은 다음과 같았습니다. 상세하게 다 적기에는 부담스러우니 대략적으로만 정리해보겠습니다.</p>
<ol>
<li>대화 종료 시, 약 1시간분량의 대화내역을 파일로 다운로드받아볼 수 있어야 함</li>
<li>실시간으로 대화내역도 모니터링 할 수 있어야 함</li>
</ol>
<p>기존에 Redis pub/sub을 이용하여 간단한 메세지 전달만 수행하고 있던 저에게 난관이 봉착했습니다.</p>
<p>기존에 유명한 해결책은 바로 ‘<strong>Kafka</strong>’. 바로 도입해봐야겠다 싶었는데, 혹시나 하는 마음에 요구사항을 프롬프트로 풀어 GPT에게 입력한 결과. 그는 ‘Redis streams’라는 키워드를 뱉었습니다.</p>
<h1 id="kafka-vs-redis-streams">Kafka vs Redis streams</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/65e24c98-2e8d-48cd-8211-6b2bf6d41f2b/image.png" alt=""></p>
<p>이 둘을 비교하며 고민해봅시다.</p>
<h2 id="1-아키텍처-및-데이터-저장-방식-architecture--data-storage"><strong>1. 아키텍처 및 데이터 저장 방식 (Architecture &amp; Data Storage)</strong></h2>
<hr>
<ul>
<li><strong>Apache Kafka</strong>:<ul>
<li><strong>디스크 기반 (Disk-based)</strong>: 데이터를 디스크에 저장하며 메모리 캐싱을 활용합니다. 이로 인해 대용량 데이터의 장기 보관(수일~수년)에 적합합니다.</li>
<li><strong>분산 로그 구조</strong>: 분산 커밋 로그(Commit Log)로 작동하며, 브로커(Broker), 토픽(Topic), 파티션(Partition)으로 구성된 분산 아키텍처를 가집니다.</li>
</ul>
</li>
<li><strong>Redis Streams</strong>:<ul>
<li><strong>인메모리 기반 (In-memory)</strong>: 데이터를 주 메모리(RAM)에 저장합니다. 이로 인해 속도가 매우 빠르지만, 메모리 용량에 의해 데이터 저장량이 제한됩니다.</li>
<li><strong>자료구조</strong>: Redis의 Append-only 로그 데이터 구조(Radix Tree 기반)로, Redis 서버의 단일 스레드 이벤트 루프 내에서 동작합니다. 데이터 영속성(AOF/RDB)은 선택 사항입니다.</li>
</ul>
</li>
</ul>
<p>말이 어렵습니다. </p>
<p>쉽게 정리하면, 양이 많고 오래 보관해야 하면 Kafka</p>
<p>양이 적고, 단시간 보관해야 하면 Redis streams 가 적합해 보였습니다.</p>
<h2 id="2-성능-특성-performance-characteristics"><strong>2. 성능 특성 (Performance Characteristics)</strong></h2>
<hr>
<p>성능 측면에서는 <strong>처리량(Throughput)</strong>과 <strong>지연 시간(Latency)</strong> 사이의 트레이드오프가 존재합니다.</p>
<table>
<thead>
<tr>
<th>특성</th>
<th>Redis Streams</th>
<th>Apache Kafka</th>
</tr>
</thead>
<tbody><tr>
<td><strong>지연 시간 (Latency)</strong></td>
<td><strong>1ms 미만 (Sub-millisecond)</strong>. 메모리에서 직접 처리하므로 즉각적인 응답이 가능</td>
<td><strong>밀리초 단위 (Low, but higher than Redis)</strong>. 디스크 I/O 및 복제 과정 등으로 인해 Redis보다는 높음</td>
</tr>
<tr>
<td><strong>처리량 (Throughput)</strong></td>
<td><strong>높음</strong>. 하지만 메모리와 단일 스레드 구조의 한계가 있음</td>
<td><strong>매우 높음</strong>. 대규모 데이터 스트리밍 처리에 최적화되어 있으며, 초당 수백만 건의 이벤트를 처리할 수 있음</td>
</tr>
</tbody></table>
<p>요약하자면,</p>
<ul>
<li>지연시간: Redis streams &gt; Kafka</li>
<li>처리량: Redis streams &lt; Kafka</li>
</ul>
<p>입니다.</p>
<h2 id="3-메시지-소비-및-처리-모델-consumption-model"><strong>3. 메시지 소비 및 처리 모델 (Consumption Model)</strong></h2>
<hr>
<p>두 시스템 모두 <strong>컨슈머 그룹(Consumer Group)</strong> 개념을 지원하지만, 작동 방식에는 차이가 있습니다.</p>
<ul>
<li><strong>Kafka</strong>:<ul>
<li><strong>Pull 방식</strong>: 컨슈머가 브로커로부터 데이터를 가져옵니다(Poll). 이를 통해 컨슈머가 처리 속도를 제어할 수 있습니다.</li>
<li><strong>파티션 기반 분산</strong>: 컨슈머 그룹 내의 각 컨슈머는 특정 <strong>파티션</strong>에 할당됩니다. 파티션 내에서의 순서는 보장되지만, 파티션이 나뉘면 전체 순서는 보장되지 않을 수 있습니다.</li>
</ul>
</li>
<li><strong>Redis Streams</strong>:<ul>
<li><strong>Push 방식 (대기 가능)</strong>: 클라이언트가 차단(Blocking) 모드로 대기하면 새 메시지가 도착하는 즉시 전달받습니다.</li>
<li><strong>로드 밸런싱 방식</strong>: Redis의 컨슈머 그룹은 파티션 개념 없이 하나의 스트림 키에서 메시지를 여러 컨슈머에게 <strong>로드 밸런싱</strong>하여 분배합니다. 따라서 특정 컨슈머가 더 빠르면 더 많은 메시지를 가져갈 수 있으며, 이 경우 그룹 전체의 처리 순서가 섞일 수 있습니다.</li>
</ul>
</li>
</ul>
<p>핵심은 순서 보장 관련이었습니다. 이것 같은 경우는 요구사항에 따라 중요한 것이 달라질 수 있을 것 같습니다.</p>
<h2 id="4-확장성-및-데이터-보존-scalability--retention"><strong>4. 확장성 및 데이터 보존 (Scalability &amp; Retention)</strong></h2>
<hr>
<ul>
<li><strong>Kafka</strong>:<ul>
<li><strong>수평적 확장</strong>: 파티션을 여러 브로커에 분산시켜 처리량과 저장 용량을 무제한에 가깝게 확장할 수 있습니다.</li>
<li><strong>데이터 보존</strong>: 디스크 기반이므로 설정에 따라 데이터를 영구적으로 보존하거나 매우 긴 기간(수년) 동안 저장할 수 있습니다.</li>
</ul>
</li>
<li><strong>Redis Streams</strong>:<ul>
<li><strong>수직적 확장 중심</strong>: 단일 노드의 메모리 크기에 제한을 받습니다. 수평 확장을 위해서는 클러스터링이나 샤딩을 직접 구현해야 하는 복잡함이 있을 수 있습니다.</li>
<li><strong>데이터 보존</strong>: 메모리 효율을 위해 주로 짧은 기간(수시간~수일)의 데이터를 보관하며, <code>MAXLEN</code> 등을 사용해 오래된 데이터를 정리하는 것이 일반적입니다</li>
</ul>
</li>
</ul>
<p>솔직히 둘다 직접 만져보지 않는 한 모르겠습니다. 우선 이렇구나 라고 넘어가겠습니다.</p>
<h1 id="아-그래서-redis-streams를-선택했구나">아 그래서 Redis streams를 선택했구나</h1>
<hr>
<p>맞습니다. 위의 요구사항에서 살펴보았다시피,</p>
<ol>
<li>데이터를 오래 보관할 필요 없고</li>
<li>바로바로 데이터 소모 후 채널을 초기화 할 예정이기에 대용량 아키텍처 또한 필요 없습니다</li>
</ol>
<p>기존에 Redis Pub/Sub을 사용하고 있었기에 기술학습비용이 적기도 하고, 인프라 비용 측면에서도 고려해 보았을 때, Redis Streams이 적합하다고 판단했습니다.</p>
<p>‘과유불급’, 멋있는말로는 ‘오버테크놀로지’를 피하는 것 또한 역량 중 하나이니까요.</p>
<h1 id="redis-streams만-사용하면-끝나는-거네-이제">Redis streams만 사용하면 끝나는 거네 이제?</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/d9a28db1-12cc-4cd4-b7dd-2b71998b2317/image.png" alt=""></p>
<p>아쉽게도 아니었습니다. TTS 음성데이터도 처리를 했어야 하기 때문입니다.</p>
<blockquote>
<p>TTS데이터도 Redis streams로 관리하면 안되는거야?</p>
</blockquote>
<p>네. 안됩니다. 저장할 필요가 없는 데이터이기 때문입니다.</p>
<blockquote>
</blockquote>
<p>이유는 다음과 같습니다.</p>
<h2 id="기본적으로-redis-streams는-자료구조">기본적으로 Redis streams는 ‘자료구조’</h2>
<hr>
<p>Redis streams는 자료구조입니다. Redis streams로 Produce한 데이터는 내부에 쌓이게 됩니다.</p>
<p>이는 수동으로 비우지 않으면 계속해서 쌓이기 때문에 메모리의 성능이 저하됩니다. (메모리가 빨리 차기 때문)</p>
<p>특정 개수를 지정해놓고 관리하는 방법도 있다(GPT피셜)고는 하지만 그다지 효율적이라고 생각되지 않았습니다.</p>
<p>결국 장기적으로는 잠재 결함(latent defect)과 비결정적 장애 요인을 누적시켜 시스템의 안정성과 예측 가능성을 구조적으로 저하시킬 위험이 있기 때문이죠.</p>
<p>말이 어려웠습니다. 결론은 “예상치 못한 문제가 발생할 수 있기 때문”입니다.</p>
<h1 id="문제를-해결하기-위한-해결책">문제를 해결하기 위한 해결책</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/33455a0b-3749-4c81-b844-0ba028ce1a99/image.png" alt=""></p>
<p>답은 의외로 간단했습니다.</p>
<p>Redis streams와 Redis Pub/Sub을 동시에 사용하면 되는 것입니다.</p>
<ul>
<li>저장해야 하는 텍스트 데이터 → Redis streams</li>
<li>휘발되어야 하는 binary 데이터 → Redis Pub/Sub</li>
</ul>
<p>하나의 Redis 서버는 각각 streams나 cache 방식으로 단일 목적으로 사용하는 것이 가장 안정성이 좋다고 하여(GPT피셜) 서버를 각각 다르게 설정하고 띄웠습니다.</p>
<h2 id="springboot-설정">Springboot 설정</h2>
<hr>
<p>구글링해보니 하나를 <code>@Primary</code>설정을 하여 하나를 우선으로 쓰는 다중 Redis 설정법이 대부분이길래, 제 상황과 맞는 솔루션이 아니기에, GPT에게 물어가며 진행했습니다.</p>
<h3 id="yml">.yml</h3>
<pre><code class="language-yaml">spring:
  redis:
    cache:
      host: {redis_cache_server_url} # pub/sub
      port: {redis_cache_port}
      password: {redis_cache_password}
    stream:
      host: {redis_streams_server_url} # streams
      port: {redis_streams_port}
      password: {redis_streams_password}</code></pre>
<p>각 Properties는 </p>
<p><code>@ConfigurationProperties(prefix = &quot;spring.redis.stream&quot;)</code> 
<code>@ConfigurationProperties(prefix = &quot;spring.redis.cache&quot;)</code>로 별도의 property 객체를 만들어줬습니다.</p>
<h3 id="multiredisconfig">MultiRedisConfig</h3>
<pre><code class="language-java">@Configuration
@EnableConfigurationProperties({
        RedisCacheProperties.class,
        RedisStreamProperties.class
})
public class MultiRedisConfig {

    @Primary // 기본 연결 공장으로 지정
    @Bean(&quot;redisCacheConnectionFactory&quot;)
    public RedisConnectionFactory redisCacheConnectionFactory(RedisCacheProperties props) {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(props.getHost(), props.getPort());
        config.setPassword(props.getPassword());

        return new LettuceConnectionFactory(config);
    }

    @Bean(&quot;redisStreamConnectionFactory&quot;)
    public RedisConnectionFactory redisStreamConnectionFactory(RedisStreamProperties props) {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(props.getHost(), props.getPort());
        config.setPassword(props.getPassword());

        return new LettuceConnectionFactory(config);
    }

    @Bean(&quot;redisCacheTemplate&quot;)
    public StringRedisTemplate redisCacheTemplate(
            @Qualifier(&quot;redisCacheConnectionFactory&quot;) RedisConnectionFactory factory) {
        return new StringRedisTemplate(factory);
    }

    @Bean(&quot;redisStreamTemplate&quot;)
    public StringRedisTemplate redisStreamTemplate(
            @Qualifier(&quot;redisStreamConnectionFactory&quot;) RedisConnectionFactory factory) {
        return new StringRedisTemplate(factory);
    }
}</code></pre>
<h3 id="streamsconfig">StreamsConfig</h3>
<pre><code class="language-java">@Slf4j
@Configuration
public class StreamConfig {

    @Bean
    public StreamMessageListenerContainer&lt;String, MapRecord&lt;String, String, String&gt;&gt;
    streamListenerContainer(
            @Qualifier(&quot;redisStreamConnectionFactory&quot;)
            RedisConnectionFactory factory
    ) {
        StreamMessageListenerContainer.StreamMessageListenerContainerOptions&lt;String, MapRecord&lt;String, String, String&gt;&gt;
                options = StreamMessageListenerContainer.StreamMessageListenerContainerOptions.builder()
                .pollTimeout(Duration.ofSeconds(1))
                .build();

        StreamMessageListenerContainer&lt;String, MapRecord&lt;String, String, String&gt;&gt; container =
                StreamMessageListenerContainer.create(factory, options);

        // Consumers are now managed dynamically by ConferenceStreamManager.
        // We only start the container here.
        container.start();
        return container;
    }
}</code></pre>
<p>저는 Consumer Group을 동적으로 할당하기 위해 위와 같이 설정해두었습니다.</p>
<h3 id="cacheconfig">CacheConfig</h3>
<pre><code class="language-java">@Configuration
public class CacheConfig {

    @Bean
    public RedisMessageListenerContainer pubSubContainer(
            @Qualifier(&quot;redisCacheConnectionFactory&quot;)
            RedisConnectionFactory factory,
            MessageListenerAdapter subscriber
    ) {
        RedisMessageListenerContainer container =
                new RedisMessageListenerContainer();
        container.setConnectionFactory(factory);
        container.addMessageListener(subscriber, new PatternTopic(&quot;conference:*&quot;));
        return container;
    }

    @Bean
    public MessageListenerAdapter subscriber(CacheSubscriber subscriber) {
        return new MessageListenerAdapter(subscriber, &quot;onMessage&quot;);
    }
}</code></pre>
<p>이후 Streams의 <code>Producer</code>와 <code>Consumer</code>, Pub/Sub의 <code>Publisher</code>와 <code>Subscriber</code>로직은 각자에 맞는 방식으로 구현하면 됩니다.</p>
<p>물론 설정도 각자에 맞게 하는 것이지만요.</p>
<h1 id="결론">결론</h1>
<hr>
<p>Kafka의 명성에 가려져 주목받지 못하는 Redis streams를 상황에 맞게 적절히 선택하여 사용해보기에 충분히 매력적인 기술이라고 생각합니다.</p>
<p><a href="%5Bhttps://velog.io/@playername_ltt/1%EB%85%84%EC%B0%A8-%EC%A3%BC%EB%8B%88%EC%96%B4%EA%B0%80-%EC%8B%A4%EC%A0%9C-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B3%B4%EC%97%AC%EC%A3%BC%EB%8A%94-%EA%B8%B0%EC%88%A0%EC%A0%81-%EC%9D%98%EC%82%AC%EA%B2%B0%EC%A0%95-%EA%B3%BC%EC%A0%95%5D(https://velog.io/@playername_ltt/1%EB%85%84%EC%B0%A8-%EC%A3%BC%EB%8B%88%EC%96%B4%EA%B0%80-%EC%8B%A4%EC%A0%9C-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B3%B4%EC%97%AC%EC%A3%BC%EB%8A%94-%EA%B8%B0%EC%88%A0%EC%A0%81-%EC%9D%98%EC%82%AC%EA%B2%B0%EC%A0%95-%EA%B3%BC%EC%A0%95)">1년차 주니어가 실제 사례로 보여주는 기술적 의사결정 과정</a>에서 설명했다시피, 모든 기술 결정에는 근거가 필요하다고 생각합니다.</p>
<p>모든 기술 좋고 유행한다고 쓰는 것이 아니라 필요 여부에 따라 명확하게 나누어 오버테크놀로지를 경계하고 적합한 기술을 채택하는 것이 중요한 역량 중 하나라고 생각합니다.</p>
<p><em>피드백은 언제나 환영입니다 :)</em></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[당신의 AI는 이제 금붕어가 아니다(feat. RLM)]]></title>
            <link>https://velog.io/@playername_ltt/%EB%8B%B9%EC%8B%A0%EC%9D%98-AI%EB%8A%94-%EC%9D%B4%EC%A0%9C-%EA%B8%88%EB%B6%95%EC%96%B4%EA%B0%80-%EC%95%84%EB%8B%88%EB%8B%A4feat.-RLM</link>
            <guid>https://velog.io/@playername_ltt/%EB%8B%B9%EC%8B%A0%EC%9D%98-AI%EB%8A%94-%EC%9D%B4%EC%A0%9C-%EA%B8%88%EB%B6%95%EC%96%B4%EA%B0%80-%EC%95%84%EB%8B%88%EB%8B%A4feat.-RLM</guid>
            <pubDate>Tue, 06 Jan 2026 15:40:17 GMT</pubDate>
            <description><![CDATA[<p>이 글은 LLM 추론의 새로운 지평, “Recursive Language Model(RLM)”에 관한 글입니다.</p>
<h1 id="1-서론-llm-추론은-어디에서-한계에-부딪히는가">1. 서론: LLM 추론은 어디에서 한계에 부딪히는가</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/50712714-acfb-4bcd-aab5-5bbefd0b3984/image.png" alt=""></p>
<h2 id="11-우리가-이미-잘-아는-llm-추론-패턴">1.1 우리가 이미 잘 아는 LLM 추론 패턴</h2>
<hr>
<p>지금까지 우리는 LLM의 성능을 극대화하기 위해 몇 가지 주요 패턴을 사용해 왔습니다.</p>
<ul>
<li><strong>CoT(Chain-of-Thought):</strong> &quot;단계별로 생각해보자&quot;는 프롬프트를 통해 선형적인 사고 과정을 유도합니다.</li>
<li><strong>Self-Refinement:</strong> 생성된 결과물을 모델이 다시 검토하고 수정하게 합니다.</li>
<li><strong>Agent Loop:</strong> LLM이 외부 도구(Search, SQL 등)를 사용하고 그 결과를 받아 다음 행동을 결정하는 루프를 돕습니다.</li>
</ul>
<p>이 방식들은 훌륭하지만 공통점이 있습니다. </p>
<p>바로 <strong>구조적으로 외부에서 제어되는 반복(Externally controlled loops)</strong>이라는 점입니다. </p>
<p>즉, 모델 자체가 재귀적인 사고 구조를 가진 것이 아니라, 우리가 프롬프트나 파이프라인으로 그렇게 시키고 있는 것입니다.</p>
<h2 id="12-기존-방식의-공통적인-구조적-한계">1.2 기존 방식의 공통적인 구조적 한계</h2>
<hr>
<p>이러한 외부 제어 방식은 다음과 같은 태생적 한계를 가집니다.</p>
<ul>
<li><strong>컨텍스트 윈도우 의존성:</strong> 사고 과정이 길어질수록 이전의 모든 단계를 컨텍스트에 담아야 하므로 효율성이 급격히 떨어집니다.</li>
<li><strong>상태 일관성 문제:</strong> 반복 호출 사이에서 이전의 상태(State)를 관리하기 위해 외부 데이터베이스나 복잡한 상태 머신이 필요합니다.</li>
<li><strong>고정된 추론 깊이:</strong> 개발자가 사전에 설계한 워크플로우 이상의 깊이 있는 추론을 수행하기 어렵습니다.</li>
</ul>
<h2 id="13-rlm이라는-문제의식의-등장">1.3 RLM이라는 문제의식의 등장</h2>
<hr>
<p>&quot;왜 추론의 흐름을 밖에서 관리해야 하는가? 모델이 스스로 문제를 정의하고, 필요하다면 자기 자신을 다시 호출하며 문제를 해결할 수는 없을까?&quot;</p>
<p>RLM은 단순한 성능 향상을 위한 기법이 아닙니다. 추론의 <strong>오케스트레이션(Orchestration) 주체를 모델 내부로 옮기려는 시도</strong>에서 출발한 개념입니다.</p>
<h1 id="2-rlm이란-무엇인가-정의와-핵심-아이디어">2. RLM이란 무엇인가: 정의와 핵심 아이디어</h1>
<hr>
<h2 id="21-recursive-language-model의-개념과-등장-배경">2.1 Recursive Language Model의 개념과 등장 배경</h2>
<hr>
<h3 id="211-rlm의-등장-배경-왜-더-큰-컨텍스트만으로는-부족한가">2.1.1 RLM의 등장 배경: 왜 &#39;더 큰 컨텍스트&#39;만으로는 부족한가?</h3>
<p>지금까지 LLM 업계의 지표는 &quot;얼마나 더 많은 텍스트를 한 번에 집어넣을 수 있는가(Context Window Scaling)&quot;에 집중되어 왔습니다. 하지만 컨텍스트가 1M, 10M로 커짐에 따라 새로운 문제들이 수면 위로 떠올랐습니다. </p>
<p>이를 <strong>MIT</strong>와 <strong>Prime Intellect</strong>에서 제안한 Python REPL 환경에서 LLM이 스스로 컨텍스트를 재귀적으로 탐색하고 조합하는 새로운 방법론, Recursive Language Models (RLM)을 공개했습니다.</p>
<ol>
<li><strong>정보 밀도의 한계 (Lost in the Middle):</strong> 컨텍스트가 길어질수록 모델은 중간에 위치한 중요한 정보를 놓치는 경향이 있습니다. 단순히 &#39;읽을 수 있다&#39;는 것과 &#39;완벽히 이해하고 추론한다&#39;는 것 사이의 간극이 발생한 것입니다.</li>
<li><strong>비용과 자원의 비효율성:</strong> 10M 토큰에 달하는 방대한 데이터를 매번 전체 컨텍스트에 넣고 추론하는 것은 엄청난 컴퓨팅 비용과 지연 시간(Latency)을 초래합니다.</li>
<li><strong>비선형적 문제 해결의 필요성:</strong> 현실 세계의 복잡한 문제는 &#39;A 다음 B&#39; 식의 선형 구조가 아닙니다. 큰 문제 안에 작은 문제가 있고, 그 안에 또 다른 변수가 얽혀 있는 <strong>계층적(Hierarchical) 구조</strong>입니다.</li>
</ol>
<p>이러한 한계를 돌파하기 위해 <strong>&quot;컨텍스트를 무작정 늘리는 대신, 문제를 계층적으로 쪼개어 효율적으로 처리하자&quot;</strong>는 패러다임의 전환이 일어났고, 그 결과물이 바로 RLM입니다.</p>
<h3 id="212-rlm의-개념적-정의">2.1.2 RLM의 개념적 정의</h3>
<p>RLM은 <strong>&quot;자기 자신을 재귀적으로 호출하여 복잡한 문제를 더 작은 하위 문제로 분해하고 해결하는 추론 실행 모델&quot;</strong>입니다. 단순히 문맥을 길게 유지하는 것이 아니라, 필요할 때마다 새로운 추론 스택을 쌓아 올림으로써 <strong>기존 LLM(약 200K대 토큰) 대비 50~100배에 달하는 10M+ 토큰 급의 정보 처리 능력</strong>을 효율적으로 구현합니다.</p>
<h2 id="22-rlm을-구성하는-3가지-핵심-요소">2.2 RLM을 구성하는 3가지 핵심 요소</h2>
<hr>
<ol>
<li><strong>자기 호출(Self-Invocation):</strong> 모델이 문제를 해결하다가 너무 복잡하다고 판단되면, 하위 문제를 정의하고 자기 자신을 다시 호출합니다.</li>
<li><strong>자기 상태 갱신(Self-State Update):</strong> 하위 문제의 결과를 받아 원래의 문제 상태를 업데이트합니다.</li>
<li><strong>재귀적 추론(Recursive Reasoning):</strong> 위 과정이 종료 조건(Base case)을 만날 때까지 반복됩니다.</li>
</ol>
<blockquote>
<p>Key Insight: RLM은 &quot;생각을 길게 이어가는 것&quot;이 아니라, <strong>&quot;문제를 더 단순한 단위로 쪼개어 나에게 다시 맡기는 것&quot;</strong>입니다.</p>
</blockquote>
<h1 id="3-기존-llm-추론-방식과의-구조적-비교">3. 기존 LLM 추론 방식과의 구조적 비교</h1>
<hr>
<table>
<thead>
<tr>
<th><strong>구분</strong></th>
<th><strong>Chain-of-Thought (CoT)</strong></th>
<th><strong>Agent Loop (External)</strong></th>
<th><strong>Recursive LM (RLM)</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>추론 구조</strong></td>
<td>선형적 (Linear)</td>
<td>반복적 (Iterative)</td>
<td><strong>재귀적 (Nested/Recursive)</strong></td>
</tr>
<tr>
<td><strong>상태 관리</strong></td>
<td>단일 컨텍스트 내 유지</td>
<td>외부 Orchestrator가 관리</td>
<td><strong>모델 내부의 스택 구조 활용</strong></td>
</tr>
<tr>
<td><strong>복잡도 대응</strong></td>
<td>사고가 길어질수록 혼동 증가</td>
<td>외부 로직에 의존</td>
<td><strong>문제를 작게 쪼개어 단순화</strong></td>
</tr>
<tr>
<td><strong>제어 주체</strong></td>
<td>프롬프트</td>
<td>코드/워크플로우 엔진</td>
<td><strong>모델 내부의 &#39;RECURSE&#39; 행동</strong></td>
</tr>
</tbody></table>
<h1 id="4-rlm의-실제-추론-흐름">4. RLM의 실제 추론 흐름</h1>
<hr>
<h2 id="41-단계적-추론-흐름-예시">4.1 단계적 추론 흐름 예시</h2>
<hr>
<ol>
<li><strong>입력:</strong> &quot;2024년 노벨 물리학상 수상자의 연구가 현대 반도체 산업에 미친 영향은?&quot;</li>
<li><strong>분해:</strong> 모델이 문제를 보고 <code>RECURSE</code> 결정.<ul>
<li>하위 문제 1: &quot;2024년 노벨 물리학상 수상자와 그들의 핵심 연구는 무엇인가?&quot;</li>
<li>하위 문제 2: &quot;해당 연구가 반도체 기술과 어떻게 연결되는가?&quot;</li>
</ul>
</li>
<li><strong>재귀 호출:</strong> 하위 문제 1을 해결하기 위해 모델이 자신을 다시 호출.</li>
<li><strong>결과 합성:</strong> 하위 문제들의 답을 모아 최종 답변 생성.</li>
</ol>
<h2 id="42-rlm-추론의-의사코드pseudocode">4.2 RLM 추론의 의사코드(Pseudocode)</h2>
<hr>
<p>엔지니어의 관점에서 RLM의 로직은 다음과 같이 표현될 수 있습니다.</p>
<pre><code class="language-python">def RLM(problem_state):
    # 1. 종료 조건 평가 (Base Case)
    if is_simple_enough(problem_state):
        return solve_directly(problem_state)

    # 2. 문제 분해 (Decomposition)
    sub_problems = decompose(problem_state)

    # 3. 재귀 호출 및 결과 수집 (Recursive Call)
    sub_results = []
    for sub in sub_problems:
        sub_results.append(RLM(sub))

    # 4. 상태 갱신 및 최종 답변 (State Update &amp; Synthesis)
    final_answer = synthesize(problem_state, sub_results)
    return final_answer</code></pre>
<h1 id="5-시스템-아키텍처-관점에서-본-rlm">5. 시스템 아키텍처 관점에서 본 RLM</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/87738cf0-0b7d-4b92-b88c-1b736eb3f27b/image.png" alt=""></p>
<p>기존의 LLM 에이전트 시스템은 <strong>Orchestrator</strong>가 모든 중심을 잡고 있었습니다. 하지만 RLM은 이 복잡성을 모델 내부로 밀어 넣습니다.</p>
<ul>
<li><strong>기존 아키텍처:</strong> <code>Python/LangGraph/CrewAI</code> 등이 루프와 조건문을 관리.</li>
<li><strong>RLM 기반 아키텍처:</strong> 인프라 레이어는 단순히 모델의 <code>RECURSE</code> 신호를 처리하고 새로운 컨텍스트 스택을 생성해 주는 <strong>실행 환경(Runtime)</strong> 역할만 수행.</li>
</ul>
<p>이로 인해 외부 워크플로우 엔진의 의존도는 낮아지지만, 모델이 재귀를 멈추지 않거나 잘못된 방향으로 문제를 쪼갤 경우 디버깅이 매우 어려워지는 트레이드오프가 발생합니다.</p>
<h1 id="6-rlm의-주요-성과와-냉정한-평가">6. RLM의 주요 성과와 냉정한 평가</h1>
<hr>
<p>RLM은 단순한 이론적 제안을 넘어, 실제 벤치마크와 대규모 데이터 처리에서 압도적인 수치를 증명하고 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/8c7757ab-768c-466e-a440-93f765f97d20/image.png" alt=""></p>
<h2 id="61-핵심-성과-하이라이트">6.1 핵심 성과 하이라이트</h2>
<hr>
<ul>
<li>✅ <strong>10M+ 토큰 처리:</strong> 기존 LLM이 가진 컨텍스트 한계를 <strong>50~100배</strong> 뛰어넘는 방대한 데이터를 처리할 수 있습니다.</li>
<li>✅ <strong>압도적인 성능 우위:</strong> 단순한 직접 LLM 호출(Direct Call) 대비 정답률과 논리적 완결성에서 큰 차이를 보입니다.</li>
<li>✅ <strong>검증된 강건성(Robustness):</strong> 다양한 장문 컨텍스트 태스크(Long-context tasks)에서 일관되게 안정적인 성능을 입증했습니다.</li>
<li>✅ <strong>탁월한 효율성:</strong> RAG나 단순 Context Scaling 등 다른 방법론과 비교했을 때, 추론의 정확도와 자원 활용 면에서 우수한 성능을 보여줍니다.</li>
</ul>
<h2 id="62-장점-왜-rlm인가">6.2 장점: 왜 RLM인가</h2>
<hr>
<ul>
<li><strong>동적 추론 깊이:</strong> 문제의 난이도에 따라 모델이 스스로 얼마나 깊게 파고들지 결정합니다.</li>
<li><strong>컨텍스트 효율성:</strong> 전체 추론 과정을 하나에 담지 않고, 하위 문제별로 독립된 컨텍스트를 가질 수 있어 &quot;압축된 정보&quot;만 상위로 전달합니다.</li>
</ul>
<h2 id="63-단점-한계와-비용-문제">6.3 단점: 한계와 비용 문제</h2>
<hr>
<ul>
<li><strong>비용 폭증:</strong> 재귀 호출은 기하급수적으로 호출 횟수를 늘릴 수 있습니다.</li>
<li><strong>무한 루프:</strong> 종료 조건을 제대로 인식하지 못하면 모델이 영원히 자기를 호출할 위험이 있습니다.</li>
<li><strong>학습의 어려움:</strong> <code>RECURSE</code> 토큰을 언제 쓰고 상태를 어떻게 요약할지 학습시키기 위해 고도의 SFT와 RL이 필요합니다.</li>
</ul>
<h1 id="7-기존-llm-시스템과의-연결과-확장-가능성">7. 기존 LLM 시스템과의 연결과 확장 가능성</h1>
<hr>
<p>RLM은 기존 시스템을 대체하기보다는 <strong>강력한 &#39;추론 코어&#39;</strong>로 작동할 것입니다.</p>
<ul>
<li><strong>RLM + Tool Calling:</strong> 재귀의 어느 단계에서 모델이 특정 도구를 호출하여 사실 관계를 확인하고 다시 재귀를 이어가는 방식.</li>
<li><strong>RLM + Agent:</strong> 에이전트의 &#39;Planning&#39; 단계를 RLM으로 구현하여 훨씬 정교한 계획 수립 가능.</li>
</ul>
<h1 id="8-결론-rlm은-만능-해법이-아니다">8. 결론: RLM은 만능 해법이 아니다</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/f4e65e00-5ac6-488b-ba58-ea1dd33a9332/image.png" alt=""></p>
<p>RLM은 &quot;다음 세대의 LLM 모델&quot;이라기보다, <strong>복잡한 추론 문제를 해결하기 위한 고도화된 설계 패턴이자 학습 방식</strong>입니다.</p>
<p>단순한 질의응답이나 요약 태스크에 RLM을 도입하는 것은 오버엔지니어링일 가능성이 큽니다. 하지만 수학 문제 풀이, 복잡한 코드 아키텍처 설계, 다단계 논리 추론이 필요한 영역에서는 기존 CoT의 한계를 돌파할 핵심 열쇠가 될 것입니다. </p>
<p>특히 최근 주목받는 <strong>MCP(Model Context Protocol)</strong>를 활용하는 AI Coding Agent들에게는 RLM이 가뭄의 단비 같은 해법이 될 수도 있을 것 같습니다. 이제 &#39;Ultra&#39; 모델을 넘어선 <strong>&#39;Super Ultra&#39;나 &#39;Reasoning Max&#39;</strong> 같은 이름으로 무장하고, 수천 줄의 코드를 재귀적으로 분석하는 괴물 같은 모델이 등장하려나요? (그때가 되면 우리는 프롬프트 대신 재귀 종료 조건(Base Case)을 잘 써달라고 기도해야 할지도 모르겠네요)</p>
<h1 id="9-reference">9. Reference</h1>
<hr>
<ul>
<li>[[[Hada.io] Recursive Language Model: LLM 추론의 새로운 패러다임] (<a href="https://news.hada.io/topic?id=25563)%5D(https://news.hada.io/topic?id=25563)">https://news.hada.io/topic?id=25563)](https://news.hada.io/topic?id=25563)</a></li>
<li><a href="https://www.aitimes.com/news/articleView.html?idxno=205307">[AI Times] 프라임 인텔렉트, &#39;재귀적 언어 모델(RLM)&#39; 발표</a></li>
<li><a href="https://www.primeintellect.ai/blog/rlm">[Prime Intellect Blog] RLM: Training Language Models to Solve Complex Tasks via Recursion</a></li>
<li><a href="https://arxiv.org/pdf/2512.24601">[Arxiv] Recursive Language Models (2512.24601)</a></li>
<li><a href="https://alexzhang13.github.io/blog/2025/rlm/">Alex L. Zhang Github Pages</a></li>
<li>그 외 기타 기술 블로그…</li>
</ul>
<p>제가 잘못 알고 있거나, 부족한 내용을 알려주시면 감사하겠습니다 :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[2025 회고] 무너져도 덤덤하게 일어나기]]></title>
            <link>https://velog.io/@playername_ltt/2025-%ED%9A%8C%EA%B3%A0-%EB%AC%B4%EB%84%88%EC%A0%B8%EB%8F%84-%EB%8D%A4%EB%8D%A4%ED%95%98%EA%B2%8C-%EC%9D%BC%EC%96%B4%EB%82%98%EA%B8%B0</link>
            <guid>https://velog.io/@playername_ltt/2025-%ED%9A%8C%EA%B3%A0-%EB%AC%B4%EB%84%88%EC%A0%B8%EB%8F%84-%EB%8D%A4%EB%8D%A4%ED%95%98%EA%B2%8C-%EC%9D%BC%EC%96%B4%EB%82%98%EA%B8%B0</guid>
            <pubDate>Mon, 29 Dec 2025 00:17:12 GMT</pubDate>
            <description><![CDATA[<p>연말이라면 빼놓을 수 없는 주제가 있죠. </p>
<p>그건 바로 “회고”입니다.</p>
<p>2025년은 저에게 &#39;롤러코스터&#39; 같은 한 해였습니다. 불안함에 밤잠을 설치기도 했고, 번아웃의 문턱에서 모든 것을 내려놓기도 했지만, 결국 뭔가 진짜 소소한 것들이라도 이루어냈습니다.</p>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/d0d25c98-4f9b-44c6-9de0-9b0582d430d7/image.png" alt=""></p>
<h1 id="🚀-인턴에서-정직원으로의-제대로-된-시작">🚀 인턴에서 정직원으로의 제대로 된 시작</h1>
<hr>
<p>올해의 시작은 &#39;인턴&#39;이었습니다. 1월과 2월, 실무를 배우는 기쁨도 컸지만 마음 한구석은 늘 불안했습니다.</p>
<ul>
<li><strong>멘탈의 위기:</strong> 제가 한 IPP 일학습병행은 기본적으로 ‘채용연계형 인턴십’이었기에 정규직 자리는 당연히 따고 있었다고 생각했는데, &quot;인턴 끝나면 뭐 할 예정이에요?&quot;라는 대표님의 질문은 저를 참 작아지게 만들었습니다. &#39;내가 부족해서 하시는 말씀인가?&#39; 하는 생각에 멘탈이 흔들리기도 했죠.</li>
<li><strong>전환의 순간:</strong> 하지만 묵묵히 제 몫을 해내려 노력했고, 다행히 3월에 <strong>정직원 전환</strong>이라는 값진 결과를 얻었습니다.</li>
</ul>
<h2 id="무엇을-개발했나">무엇을 개발했나?</h2>
<hr>
<ul>
<li><strong>사내 업무:</strong> 앱 서비스 운영용 관리자 웹 풀스택 개발 (<code>Spring Boot</code>, <code>Vue.js</code>)</li>
<li><strong>사이드 프로젝트:</strong> 중고 장비 대여 플랫폼 백엔드 개발 (<code>Spring Boot</code>)</li>
</ul>
<p>사내 업무에서는 비즈니스 개발의 전반적인 역량을 늘릴 수 있었고, 가장 값지게 얻은 것은 “비즈니스를 고려한 개발자의 설계 안목”이라고 생각합니다. 물론 아직은 부족하겠지만요.</p>
<p>사이드 프로젝트에서는 제가 깊이 있게 해보고 싶은 백엔드 분야에 집중했습니다. 물론 서비스를 제대로 운영할 환경까지는 갖춰지지 못했지만, 팀원들과도 소통해보며 재밌게 진행했었습니다 :)
<a href="%5Bhttps://velog.io/@playername_ltt/%ED%9A%8C%EA%B3%A0-IPP-%EC%9D%BC%ED%95%99%EC%8A%B5%EB%B3%91%ED%96%89%EC%9D%84-%EB%A7%88%EC%B9%98%EB%A9%B0%5D(https://velog.io/@playername_ltt/%ED%9A%8C%EA%B3%A0-IPP-%EC%9D%BC%ED%95%99%EC%8A%B5%EB%B3%91%ED%96%89%EC%9D%84-%EB%A7%88%EC%B9%98%EB%A9%B0)"><strong>[회고] IPP 일학습병행을 마치며…</strong></a></p>
<h2 id="정직원-되고-나서는-뭘-했는가">정직원 되고 나서는 뭘 했는가?</h2>
<hr>
<ul>
<li><strong>사내 업무:</strong> 실시간 통/번역 서비스 개발(<code>Springboot</code>, <code>FastAPI</code>, <code>Flutter</code>)</li>
<li><strong>퇴근 후:</strong> 코테 재활훈련(?) - 효과는 크게 없던 것 같습니다… 한번 플랜 제대로 세워서 해봐야겠습니다.</li>
</ul>
<p>개발자로서 차근차근 성장해나가기 시작했다고 생각합니다. 모르던 기술도 배우고, 이것저것 성장에 가속을 붙이기 시작했었죠.</p>
<h1 id="🍞-잠시-멈춤-토스트-아웃의-시기">🍞 잠시 멈춤, &quot;토스트 아웃&quot;의 시기</h1>
<hr>
<p>정직원이 된 후 앞만 보고 달려온 탓일까요? 6월쯤 개인적인 사유와 함께 소위 말하는 <strong>&#39;토스트 아웃(Toast-out)&#39;</strong>이 찾아왔습니다. 완전히 타버린 번아웃은 아니지만, 노릇하게 구워진 토스트처럼 더 이상 무언가를 채울 여력이 없는 상태였죠.</p>
<p>결국 공부를 잠시 쉬기로 결정했습니다. 처음에는 뒤처질까 봐 무서웠지만, 이 휴식 덕분에 다시 달릴 에너지를 얻을 수 있었던 것 같아요.</p>
<h1 id="📈-다시-시작된-기록">📈 다시 시작된 기록</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/20b49bf7-9aca-4d57-a411-d83311fd0afc/image.png" alt=""></p>
<p>9월 말, 다시 기지개를 켰습니다. 멈춰있던 블로그를 재활성화하고 생소했던 링크드인을 시작했습니다. 결과는 “뿌듯했다” 라고 정의하겠습니다.</p>
<ul>
<li><strong>블로그의 성장:</strong> 일 평균 “5회”였던 방문자 수가 ”<strong>50~60회”</strong>로 10배 이상 증가했습니다. 누군가 내 기록을 보고 도움을 얻는다는 사실이 큰 동기부여가 되었습니다.</li>
<li><strong>네트워크의 확장:</strong> 개발자 인맥이 전무했던 제가 링크드인 일촌을 <strong>6~7명에서 약150명</strong>까지 늘리며 세상과 연결되는 즐거움을 알게 되었습니다. 멋진 분들이 판치는 링크드인에 뻔뻔하게 얼굴에 철판깔고 제 생각을 공유하기 시작했죠.</li>
</ul>
<h2 id="나는-도대체-뭘-쓰고-공유하는가">나는 도대체 뭘 쓰고 공유하는가</h2>
<hr>
<p>개인적으로 제가 공유하는 이야기는 다음과 같습니다.</p>
<ul>
<li>최신 기술 트렌드 정리</li>
<li>기술적 이슈 트러블슈팅</li>
<li>간단한 회고</li>
</ul>
<p>제가 처음 대학교 시절 개발 배울때 트렌드였던 “교과서적으로”기술을 알려주는 블로그는 이미 트랜드가 지났다고 생각하기에, 흔한 내용은 최대한 덜 다루려고 합니다.</p>
<h1 id="🤖-새로운-도전-ai-서비스-엔지니어링">🤖 새로운 도전, AI 서비스 엔지니어링</h1>
<hr>
<p>현재에 안주하지 않기 위해 11월 말부터 <strong>&#39;AI 서비스 엔지니어링&#39; 재직자 부트캠프</strong>에 참여하고 있습니다. 단순한 웹 개발을 넘어 AI를 서비스에 어떻게 녹여낼지 고민하며 매일 새로운 자극을 받는 중입니다.</p>
<p>AI를 활용하여 서비스를 개발해내는 엔지니어로 성장하길 바라며 공부 중에 있습니다.</p>
<h2 id="2026년을-준비하며">2026년을 준비하며</h2>
<hr>
<p>지금은 제가 무엇을 더 잘 할 수 있을지, 어떤 가치를 만드는 개발자가 될지 고민하며 다음 계획들을 수립하고 있습니다.</p>
<p>몇 가지 개인적인 목표들이 있습니다.</p>
<ol>
<li>멈췄던 다이어트</li>
<li>개인으로 서비스 사이드 프로젝트 개발해서 수익 100원 이상 내보기</li>
<li>책 3권 이상 읽기</li>
<li>자격증 1개 이상 따기</li>
</ol>
<p>잘 해내보겠습니다 :)</p>
<h1 id="무너짐은-무능함이-아닌-성장의-과정">무너짐은 무능함이 아닌 성장의 과정</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/2ea9a55c-b1b6-44fc-a63b-3dfd9a5ae8ca/image.png" alt=""></p>
<blockquote>
<p>시작은 미약하였으나 그 끝은 창대하리라</p>
</blockquote>
<p>올해를 되돌아보니 참 많이 무너져보기도 했고, 일을 손에서 놓아버린 순간도 있었습니다. 하지만 지금은 압니다. <strong>그 모든 순간이 제가 더 높이 점프하기 위해 무릎을 굽혔던 과정이었다는 것을요.</strong></p>
<p>지금은 별 거 없어보이는 제가, 어디까지고 한 번 올라가보겠습니다.</p>
<p>2025년의 밑거름을 발판 삼아, 2026년에는 더 깊이 있고 단단한 개발자로 성장하겠습니다. 모두 고생 많으셨습니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[1년차 주니어가 실제 사례로 보여주는 기술적 의사결정 과정]]></title>
            <link>https://velog.io/@playername_ltt/1%EB%85%84%EC%B0%A8-%EC%A3%BC%EB%8B%88%EC%96%B4%EA%B0%80-%EC%8B%A4%EC%A0%9C-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B3%B4%EC%97%AC%EC%A3%BC%EB%8A%94-%EA%B8%B0%EC%88%A0%EC%A0%81-%EC%9D%98%EC%82%AC%EA%B2%B0%EC%A0%95-%EA%B3%BC%EC%A0%95</link>
            <guid>https://velog.io/@playername_ltt/1%EB%85%84%EC%B0%A8-%EC%A3%BC%EB%8B%88%EC%96%B4%EA%B0%80-%EC%8B%A4%EC%A0%9C-%EC%82%AC%EB%A1%80%EB%A1%9C-%EB%B3%B4%EC%97%AC%EC%A3%BC%EB%8A%94-%EA%B8%B0%EC%88%A0%EC%A0%81-%EC%9D%98%EC%82%AC%EA%B2%B0%EC%A0%95-%EA%B3%BC%EC%A0%95</guid>
            <pubDate>Fri, 12 Dec 2025 01:22:21 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/playername_ltt/post/3f37dcd0-cc2d-4023-b77a-896935f3b794/image.png" alt=""></p>
<h1 id="introduce-비동기-로직을-적용은-해보았다만">Introduce: 비동기 로직을 적용은 해보았다만…</h1>
<hr>
<p>현재 맡고 있는 프로젝트는 음성 데이터를 실시간으로 처리하는 파이프라인을 가지고 있습니다. </p>
<p>처음에는 별 생각 없이 기존 서버들에 구성되어 있는 환경인 <strong>“Spring Boot 3.4.4 &amp; Java 17”</strong> 환경에서 개발을 진행하고 있었습니다. 기능적으로 필요해서 만든 서버였기에 처음에는 버전과 환경, 최적화 등에 대한 생각을 크게 하고 있지 않았습니다.</p>
<p>그러다 추가적인 진행방향 기획 등을 듣고 나니 걱정들이 생겨납니다.</p>
<ol>
<li>트래픽이 몰려도 성능적으로 괜찮나?</li>
<li>구조상 외부 네트워크 API를 많이 호출하게 된다. 지연시간이 너무 발생하지 않을까?</li>
<li>오류 상황이 최소화되어야 하는데 스레드 풀 관리를 제대로 할 수 있을까?</li>
</ol>
<p>이러한 고민을 해결하기 위해 <code>Virtual Thread</code>를 어떻게 도입했는지 주니어 개발자의 <strong>“기술적 의사결정 과정”</strong>을 공유해볼까 합니다.</p>
<h1 id="1-프로젝트-배경-및-문제-정의">1. 프로젝트 배경 및 문제 정의</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/866f4f3f-e71d-4df8-b541-5d4bb1d1bdd4/image.png" alt=""></p>
<h2 id="11-서비스-로직-io-bound의-연속">1.1. 서비스 로직: I/O Bound의 연속</h2>
<hr>
<p>제가 담당한 서비스는 다국어 컨퍼런스, 회의 등을 위한 파이프라인으로 다음과 같은 서비스 Flow를 가집니다.</p>
<blockquote>
<p>STT(음성 인식) → Translation API → TTS(음성 합성) API → 결과 Redis Publish</p>
</blockquote>
<p>각 단계는 Redis Publish를 제외하고는 전부 외부 API에 의존하게 되는, 전형적인 I/O Bound 작업입니다.</p>
<h2 id="12-기존-환경의-잠재적-한계java-17--platform-thread">1.2. 기존 환경의 잠재적 한계(Java 17 &amp; Platform Thread)</h2>
<hr>
<p>기존 <code>Java 17</code>환경에서는 OS 스레드와 1:1로 매핑되는 <code>Platform Thread</code>를 사용했었습니다.</p>
<ul>
<li>외부 API 응답을 기다리는 동안 해당 스레드는 <strong>Blocking</strong> 상태가 됩니다.</li>
<li>동시 요청이 그 정도로 늘어날 상황은 많지 않다고 판단했지만, 만약 동시 요청이 급증하게 된다면 <strong>“Thread Pool이 고갈”</strong>되거나 스레드를 늘리면 메모리 사용량과 컨텍스트 스위칭 비용이 급증할 수 있습니다.</li>
</ul>
<h2 id="13-채널별-스레드-할당-방식의-한계">1.3. 채널별 스레드 할당 방식의 한계</h2>
<hr>
<p>제가 개발한 서비스는 회의(Conference)마다 여러 언어(Lang) 채널이 존재합니다. </p>
<p>데이터의 순차적 처리와 채널 간 격리를 위해, 기존에는 다음과 같이 <strong>Key(회의ID+언어코드)별로 단일 스레드(Single Thread Executor)를 생성</strong>하여 캐싱하는 전략을 사용했습니다.</p>
<pre><code class="language-java">// AS-IS: Java 17 (Platform Thread)
// 채널마다 무거운 OS 스레드가 하나씩 생성되어 대기함
public ExecutorService getExecutorForLangGroup(String conferenceId, String langCode) {
    String key = conferenceId + &quot;:&quot; + langCode;
    return langExecutors.computeIfAbsent(key, k -&gt;
        Executors.newSingleThreadExecutor(r -&gt; {
            Thread thread = new Thread(r);
            thread.setDaemon(true); // 채널마다 OS 스레드 점유
            return thread;
        })
    );
}</code></pre>
<p>이 방식은 로직은 단순하고 명확해 보일 수 있지만 단점이 명확했습니다.</p>
<ul>
<li>확장성 한계: 동시 진행되는 회의가 100개이고 각 10개 언어를 지원한다면, 순식간에 <strong>1,000개의 OS 스레드</strong>가 생성됩니다.</li>
<li><strong>리소스 낭비:</strong> 대화가 없는 조용한 방에서도 스레드가 점유된 상태로 대기(Idle)하여 메모리를 낭비합니다.
(물론 이부분은 임시로 방에 활성화된 언어 채널을 계속해서 감시하며 해결했지만, 이 또한 비용에 해당하긴 합니다.)</li>
<li><strong>Context Switching:</strong> 활성 스레드가 많아지면 CPU가 스레드를 교체하는 비용이 작업 처리 비용보다 커집니다.</li>
</ul>
<h1 id="2-해결책-모색-java-21과-virtual-thread">2. 해결책 모색: Java 21과 Virtual Thread</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/8f760674-9c11-40b1-8c8a-3d845aaf7de4/image.png" alt=""></p>
<p>우선 첫 번째로 고민했던 것은 리액티브 프로그래밍(Webflux)이었습니다. </p>
<p>하지만 “기존 코드를 갈아엎어야 한다”라는 것과 “다른 API들은 어떻게 처리해야 하지?”의 두 가지 고민으로 인해 무산되었습니다.</p>
<p>그러다 떠오른 “Java 21에 <code>Virtual Thread</code>라는 것이 있다고 했었는데…”</p>
<p>이게 진짜 가볍고? 빠르고? 아무튼 좋다? 라고 했던 것 같아 냅다 알아봤습니다.</p>
<h1 id="3-virtual-thread란-무엇인가">3. Virtual Thread란 무엇인가</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/a2dd84d7-00f0-4c08-b71f-a45da6e24eb0/image.png" alt=""></p>
<h2 id="31-간단-개념">3.1. 간단 개념</h2>
<hr>
<p>기존의 <code>Platform Thread</code>가 OS 스레드 하나를 점유하는 무거운 객체라면, <code>Virtual Thread</code>는 JVM 내부에서 관리되는 경량 스레드입니다.</p>
<ul>
<li>Platfrom Thread: OS 스레드 = 1:1 매핑(비쌈, 개수 제한)</li>
<li>Virtual Thread: OS 스레드(Carrier) = N:M 매핑(매우 저렴, 수백만 개 생성 가능)</li>
</ul>
<p>Virtual Thread는 I/O Blocking이 발생하면, 실제 OS 스레드(Carrier Thread)를 점유하지 않고 다른 Virtual Thread에게 자리를 양보(Yield)합니다. 덕분에 적은 수의 OS 스레드로도 수많은 동시 요청을 처리할 수 있습니다.</p>
<p><strong>가장 Virtual Thread의 장점이 극대화되는 케이스에 해당했기 때문에 도입하지 않을 이유가 없었습니다.</strong></p>
<ul>
<li>Blocking I/O 를 그대로 사용해도 OS 스레드 점유 시간이 거의 없음</li>
<li>API 응답 대기 중 OS 스레드를 반납하기 때문에 적은 리소스에서 많은 요청 병렬 처리 가능</li>
</ul>
<p>Virtual Thread 참고자료: <a href="https://openjdk.org/jeps/444">Oracle 공식 문서 - Virtual Threads</a></p>
<h1 id="4-도입-과정">4. 도입 과정</h1>
<hr>
<h2 id="41-java-버전-업그레이드">4.1. Java 버전 업그레이드</h2>
<hr>
<p>먼저 Java 17 → Java 21로 변경할때 Springboot 3.4.4가 지원하는데, 다행이도 완벽하게 지원했습니다.</p>
<p>JDK 설치 및 <code>build.gradle</code>설정 변경만으로도 충분했습니다.</p>
<p>물론 이 과정 이후에 Springboot 버전도 검토하여 3.5.6 버전으로 버전업 하였습니다.(가장 큰 사유 LTS 버전이라)</p>
<h2 id="42-springboot-설정-적용">4.2. Springboot 설정 적용</h2>
<hr>
<p>많은 기술블로그들에서 Spring Boot 3.2부터는 옵션 하나로 톰캣(Tomcat)과 TaskExecutor가 Virtual Thread를 사용하도록 설정할 수 있습니다.</p>
<pre><code class="language-yaml"># application.yml
spring:
  threads:
    virtual:
      enabled: true</code></pre>
<p>Chat GPT한테 물어보니 설정 적용 안해도 가능하다고는 합니다.</p>
<h2 id="43--로직-변경">4.3.  로직 변경</h2>
<hr>
<p>기존에 사용하던 <code>getExecutorForLangGroup()</code>을 수정해주었습니다. (1.3. 참고)</p>
<pre><code class="language-java">// TO-BE: Java 21 (Virtual Thread)
public ExecutorService getExecutorForLangGroup(String conferenceId, String langCode) {
    String key = conferenceId + &quot;:&quot; + langCode;
    return langExecutors.computeIfAbsent(key, k -&gt;
        // 더 이상 물리 스레드를 미리 만들지 않음.
        // 작업이 들어올 때마다 가볍게 생성되는 Virtual Thread Executor 사용
        Executors.newVirtualThreadPerTaskExecutor()
    );
}</code></pre>
<h1 id="5-성능-측정-및-수치적-이점">5. 성능 측정 및 수치적 이점</h1>
<hr>
<h2 id="51-변경의-이론적-이점">5.1. 변경의 이론적 이점</h2>
<hr>
<table>
<thead>
<tr>
<th><strong>항목</strong></th>
<th><strong>기존 (newSingleThreadExecutor)</strong></th>
<th><strong>변경 후 (newVirtualThreadPerTaskExecutor)</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>I/O 작업 처리</strong></td>
<td>단일 스레드에서 <strong>순차적</strong> 블로킹</td>
<td>병렬 처리 가능, I/O 대기 시 플랫폼 스레드 <strong>해제 (언마운트)</strong></td>
</tr>
<tr>
<td><strong>처리량 (Throughput)</strong></td>
<td>매우 낮음 (하나의 느린 작업에 종속)</td>
<td><strong>매우 높음</strong> (대부분의 시간을 I/O 대기 없이 활용)</td>
</tr>
<tr>
<td><strong>시스템 Latency</strong></td>
<td>높음 (큐 대기 시간 발생)</td>
<td><strong>낮음</strong></td>
</tr>
<tr>
<td><strong>리소스 효율</strong></td>
<td>비효율적 (플랫폼 스레드 점유)</td>
<td><strong>매우 효율적</strong> (가벼운 가상 스레드 사용)</td>
</tr>
</tbody></table>
<h2 id="52-변경의-수치적-이점">5.2. 변경의 수치적 이점</h2>
<hr>
<p>저는 간이로만 테스트를 진행하겠습니다.</p>
<p>회의 하나에서 언어 2개 채널을 오픈하고 각각 같은 길이의 문장을 처리 완료하는데 걸린 시간을 체크했습니다.</p>
<ul>
<li>As-Is(Platform Thread): 2.5s</li>
<li>To-Be(Virtual Thread): 1.3s</li>
</ul>
<p>약 48%의 속도 개선을 할 수 있었습니다.</p>
<p>부끄럽지만 부하 테스트를 제대로 해 본 적이 없어, 추후 공부하여 제대로 테스트 할 수 있도록 해보겠습니다.</p>
<h1 id="6-수치-이외의-잠재적-이점">6. 수치 이외의 잠재적 이점</h1>
<hr>
<p>수치적인 성능 향상도 향상이지만 잠재적으로 얻을 수 있던 이점은 <strong>개발 생산성</strong>이었습니다.</p>
<ol>
<li><strong>코드의 단순함:</strong> <code>WebFlux</code> 같은 복잡한 비동기 코드나 콜백 지옥 없이, 익숙한 <strong>동기 스타일(Imperative style)</strong>로 코드를 짜도 비동기처럼 동작합니다.</li>
<li><strong>디버깅 용이성:</strong> 스택 트레이스(Stack Trace)가 끊기지 않고 연결되어 있어, 에러 추적이 훨씬 수월합니다.</li>
<li><strong>자원 효율성:</strong> 스레드 풀 사이즈를 고민하거나 튜닝하는 시간이 사라졌습니다.</li>
</ol>
<h1 id="7-주니어-개발자로서의-의견">7. 주니어 개발자로서의 의견</h1>
<hr>
<p>많은 분들이 저와 같을 것이라고 생각합니다. </p>
<ul>
<li>기획대로 기능을 완성하기 위해 우선 설계부터 하는데, 어떻게 설계해야 확장성이 좋지?</li>
<li>DB 테이블은 어떻게 설계하지? 컬럼만 추가할까? 매핑 테이블을 만들까?</li>
<li>병렬 처리를 위해 뭘 해야되는거지?</li>
</ul>
<p>이런 고민들을 거쳐 “기능 완성”이라는 멋진 성과를 내놓습니다.</p>
<p>하지만 이번에 저는 “기능 완성”에서 그치지 않았습니다.</p>
<p>기능개발 이후 집중했던 내용은 “리소스 절약”과 “성능 향상” 이었습니다. </p>
<ul>
<li>현재 방식이 어떤 한계점을 맞을 수 있는지 잠재적인 위협을 고려하고(단순 <code>Platform Thread</code>만을 사용하게 되는 상황 경계)</li>
<li>어떤 기술을 “왜” 선택해야 하는지(Java 21의 명확한 도입 이유)</li>
</ul>
<p>무엇이든 “다들 이렇게 하니까”라는 것은 경계하기로 했습니다. 어떤 기술 스택을 적용할 지에는 “근거”가 필요합니다. “왜”라는 것은 성장의 원동력이 되니까요.</p>
<p>저는 요즘 이틀에 최소 30분씩은 기존 로직을 검토합니다. </p>
<p>성능상 어떤 것을 개선할 수 있을지, 이 부분이 중복되는데 디자인패턴을 적용할 수 있을지, 아니면 진짜 사소하게 Util 클래스에서 DB데이터를 조회해야 하는데 Service로직을 거쳐서 가져올지,  아니면 바로 Mapper로 접근할지 등을 말이죠.</p>
<p>작은 습관이 미래에 더 나은 나를, 멋진 시니어로 성장하는 나를 만들 수 있을 것이라고 믿습니다.</p>
<h1 id="reference">Reference</h1>
<hr>
<ul>
<li><a href="https://openjdk.org/jeps/444">JEP 444: Virtual Threads</a></li>
<li><a href="https://www.google.com/search?q=https://docs.spring.io/spring-boot/docs/current/reference/html/features.html%23features.spring-application.virtual-threads">Spring Boot Virtual Threads Support</a></li>
<li><a href="https://www.google.com/search?q=placeholder">우아한형제들 기술블로그 - Java Virtual Thread 도입기</a></li>
<li><a href="https://techblog.woowahan.com/15398/">우아한형제들 기술블로그 - Java의 미래, Virtual Thread</a></li>
<li><a href="%5Bhttps://velog.io/@juhyeon1114/Java-JDK21%EC%9D%98-Virtual-Thread-%EC%A0%95%EB%A6%AC-w.-%EC%84%B1%EB%8A%A5-%ED%85%8C%EC%8A%A4%ED%8A%B8%5D(https://velog.io/@juhyeon1114/Java-JDK21%EC%9D%98-Virtual-Thread-%EC%A0%95%EB%A6%AC-w.-%EC%84%B1%EB%8A%A5-%ED%85%8C%EC%8A%A4%ED%8A%B8)">[Java] JDK21의 Virtual Thread 정리 (w. 성능 테스트)</a></li>
<li>[virtual thread(jdk21, jdk24)]
(<a href="https://c-king.tistory.com/620">https://c-king.tistory.com/620</a>)</li>
<li>[Oracle Core Libraries]
(<a href="https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html#GUID-2DDA5807-5BD5-4ABC-B62A-A1230F0566E0">https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html#GUID-2DDA5807-5BD5-4ABC-B62A-A1230F0566E0</a>)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Agent 개발의 미래, Google의 ADK]]></title>
            <link>https://velog.io/@playername_ltt/Agent-%EA%B0%9C%EB%B0%9C%EC%9D%98-%EB%AF%B8%EB%9E%98-Google%EC%9D%98-ADK</link>
            <guid>https://velog.io/@playername_ltt/Agent-%EA%B0%9C%EB%B0%9C%EC%9D%98-%EB%AF%B8%EB%9E%98-Google%EC%9D%98-ADK</guid>
            <pubDate>Tue, 02 Dec 2025 00:08:43 GMT</pubDate>
            <description><![CDATA[<p>지난 25.11.30에 <strong>“GDG Devfest Seoul 2025”</strong>에 다녀왔습니다. 들었던 세션 중 <strong>“Building AI Agents with Agent Development Kit Go”</strong>라는 Hands-on 세션에서 소개받은 <strong>“ADK(Agent Development Kit)”</strong>에 대해 소개해볼까 합니다.</p>
<p>기술의 발전 속도는 눈부시며, 그 중심에는 <strong>AI</strong>가 있습니다. 특히 사용자의 의도를 이해하고 복잡한 작업을 자율적으로 수행하는 <strong>에이전트(Agent)</strong> 기술은 AI의 다음 혁명을 이끌 핵심으로 주목받고 있죠. 이러한 흐름 속에서 Google은 개발자들이 혁신적인 AI 에이전트를 보다 쉽고 빠르게 구축할 수 있도록 강력한 도구를 2025년 4월, 세상에 공개했습니다.</p>
<h1 id="그래서-adk가-무엇인가요">그래서 ADK가 무엇인가요?</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/edd81b7a-0e16-4c26-821e-42ff6d117a2b/image.png" alt=""></p>
<p><strong>ADK</strong>는 Google의 첨단 <strong>대규모 언어 모델(LLM)</strong>, 즉 <strong>Gemini</strong>를 기반으로 하여, <strong>복잡하고 다단계적인 사용자 요청</strong>을 이해하고 실행하는 <strong>AI 에이전트</strong>를 구축하기 위한 포괄적인 프레임워크이자 도구 모음입니다.</p>
<p>ADK의 핵심 목표는 개발자가 에이전트의 <strong>계획(Planning)</strong>, <strong>도구 사용(Tool Use)</strong>, <strong>기억(Memory)</strong>, 그리고 <strong>사용자 피드백을 통한 학습(Learning)</strong>과 같은 핵심 기능을 손쉽게 통합하고 맞춤 설정할 수 있도록 돕는 것입니다. 쉽게 말해, <strong>ADK는 AI가 단순한 질문에 답하는 것을 넘어, 현실 세계에서 목표를 설정하고 달성할 수 있는 &#39;행동하는 AI&#39;를 만드는 프레임워크</strong>라고 할 수 있습니다.</p>
<p>Gemini 및 Google 생태계에 최적화되어 있지만, 모델에 구애받지 않고(model-agnostic), 배포 환경에 제약이 없고(deployment-agnostic), 다른 프레임워크와의 호환성을 위해 구축되었다고 합니다. 핵심은 Agent개발이 소프트웨어 개발처럼 느껴지도록 설계되었다는 것입니다.</p>
<h2 id="에이전트-개발의-새로운-표준">에이전트 개발의 새로운 표준</h2>
<hr>
<p>Google ADK는 단순히 에이전트를 만드는 도구를 넘어, AI 에이전트의 <strong>상호 운용성</strong>과 <strong>확장성</strong>을 높이는 것을 목표로 합니다. 이는 다양한 애플리케이션과 플랫폼에서 일관되고 강력한 성능을 발휘하는 에이전트를 개발할 수 있게 해줍니다.</p>
<p>ADK를 통해 개발자들은 금융 분석, 개인 비서, 복잡한 데이터 관리 등 <strong>특정 목적에 최적화된 고성능 AI 에이전트</strong>를 구현할 수 있습니다.</p>
<h1 id="langchain은-끝난건가요">LangChain은 끝난건가요?</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/2a9101aa-9883-4004-a4d4-4cb5ee6d1757/image.png" alt=""></p>
<p>AI 에이전트 개발 분야에서 Google의 <strong>ADK</strong>가 등장하기 전까지, <strong>LangChain</strong>은 에이전트 구축의 사실상의 표준이었습니다. 두 프레임워크는 모두 대규모 언어 모델(LLM)을 활용하여 복잡한 작업을 수행하는 에이전트를 만드는 것을 목표로 하지만, 그 <strong>접근 방식, 아키텍처, 그리고 통합되는 생태계</strong>에서 차이를 보입니다.</p>
<table>
<thead>
<tr>
<th><strong>특징</strong></th>
<th><strong>LangChain</strong></th>
<th><strong>Google ADK</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>핵심 철학</strong></td>
<td><strong>모듈성 및 범용성</strong></td>
<td><strong>End-to-End 통합 및 확장성</strong></td>
</tr>
<tr>
<td><strong>기반 모델</strong></td>
<td>다양한 LLM 지원 (OpenAI, Anthropic, Google Gemini 등)</td>
<td><strong>Google Gemini</strong> 모델에 최적화 및 깊이 통합됨</td>
</tr>
<tr>
<td><strong>구성 요소</strong></td>
<td><strong>Chains, Agents, Tools, Memory</strong> 등 레고 블록 형태의 모듈</td>
<td>에이전트의 핵심 기능(계획, 도구 사용 등)을 통합된 <strong>서비스</strong> 형태로 제공</td>
</tr>
<tr>
<td><strong>통합</strong></td>
<td>다양한 데이터 소스, 벡터 데이터베이스, LLM Provider와의 유연한 연결</td>
<td>Google의 광범위한 서비스 및 인프라(Google Cloud, Workspace 등)와의 <strong>깊은 통합</strong></td>
</tr>
</tbody></table>
<h1 id="langchain과-adk의-작동-방식-및-개발-흐름의-차이">LangChain과 ADK의 작동 방식 및 개발 흐름의 차이</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/1dac752e-f9e2-4ed8-89d5-0480a5d8bae4/image.png" alt=""></p>
<h2 id="1-langchain의-접근-방식-조합과-유연성">1. LangChain의 접근 방식: 조합과 유연성</h2>
<hr>
<p><strong>LangChain</strong>은 개발자가 에이전트의 각 구성 요소를 <strong>독립적인 모듈</strong>로 정의하고 조합하도록 설계되었습니다.</p>
<ul>
<li><strong>Chains:</strong> 일련의 동작을 연결합니다 (예: 사용자 입력 → 프롬프트 → LLM 응답 → 파싱).</li>
<li><strong>Agents:</strong> 동적으로 어떤 도구(Tool)를 사용할지 결정하는 추론 로직을 제공합니다.</li>
<li><strong>유연성:</strong> 개발자는 추론 방식(ReAct, Zero-shot), 도구, 메모리 유형 등을 자유롭게 선택하고 교체할 수 있어 <strong>최대한의 커스터마이징</strong>이 가능합니다.</li>
</ul>
<pre><code class="language-python"># pip install -qU &quot;langchain[anthropic]&quot; to call the model

from langchain.agents import create_agent

def get_weather(city: str) -&gt; str:
    &quot;&quot;&quot;Get weather for a given city.&quot;&quot;&quot;
    return f&quot;It&#39;s always sunny in {city}!&quot;

agent = create_agent(
    model=&quot;claude-sonnet-4-5-20250929&quot;,
    tools=[get_weather],
    system_prompt=&quot;You are a helpful assistant&quot;,
)

# Run the agent
agent.invoke(
    {&quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;what is the weather in sf&quot;}]}
)</code></pre>
<h2 id="2-google-adk의-접근-방식-빌트인-및-최적화">2. Google ADK의 접근 방식: 빌트인 및 최적화</h2>
<hr>
<p><strong>ADK</strong>는 LLM인 <strong>Gemini</strong>가 에이전트의 핵심 지능을 담당하도록 하고, 에이전트가 필요한 기능을 <strong>&#39;서비스&#39;</strong> 형태로 호출하고 통합하는 데 중점을 둡니다.</p>
<ul>
<li><strong>Deep Gemini Integration:</strong> ADK는 특히 <strong>Gemini의 네이티브 멀티모달리티 및 복잡한 추론 능력</strong>을 활용하여 더 정교한 계획(Planning) 및 도구 호출을 수행하도록 설계되었습니다.</li>
<li><strong>Agent Service Model:</strong> ADK는 &#39;계획 수립&#39;, &#39;도구 실행&#39;, &#39;기억 관리&#39; 등의 복잡한 로직을 개발자가 직접 구현하기보다는, <strong>고도로 최적화된 빌트인 구성 요소</strong>를 통해 쉽게 호출하고 확장하도록 합니다. 이는 <strong>대규모 및 엔터프라이즈급 배포</strong>에 유리합니다.</li>
</ul>
<pre><code class="language-go">package main

import (
    &quot;context&quot;
    &quot;log&quot;
    &quot;os&quot;
    &quot;fmt&quot;

    &quot;google.golang.org/adk/agent&quot;
    &quot;google.golang.org/adk/agent/llmagent&quot;
    &quot;google.golang.org/adk/cmd/launcher&quot;
    &quot;google.golang.org/adk/cmd/launcher/full&quot;
    &quot;google.golang.org/adk/model/gemini&quot;
    &quot;google.golang.org/adk/tool&quot;
    &quot;google.golang.org/adk/tool/functiontool&quot;

    &quot;google.golang.org/genai&quot;
)

type getWeatherArgs struct {
    City string `json:&quot;city&quot; jsonschema:&quot;The city to get weather for.&quot;`
}

func getWeather(ctx tool.Context, args getWeatherArgs) (string, error) {
    fmt.Printf(&quot;[Tool] Getting weather for %s...\n&quot;, args.City)
    return fmt.Sprintf(&quot;The weather in %s is Sunny, 25°C&quot;, args.City), nil
}

func main() {
    ctx := context.Background()

    model, err := gemini.NewModel(ctx,
        &quot;gemini-3-pro-preview&quot;,
        &amp;genai.ClientConfig{
            APIKey: os.Getenv(&quot;GOOGLE_API_KEY&quot;),
        })
    if err != nil {
        log.Fatalf(&quot;Failed to create model: %v&quot;, err)
    }

    weatherTool, _ := functiontool.New(functiontool.Config{
        Name: &quot;get_weather&quot;, Description: &quot;Get weather for a city&quot;},
        getWeather,
    )

    sentimentTool, _ := functiontool.New(
        functiontool.Config{Name: &quot;analyze_sentiment&quot;, Description: &quot;Analyze text sentiment&quot;},
        analyzeSentiment)

    myAgent, err := llmagent.New(llmagent.Config{
        Name:  &quot;helper_agent&quot;,
        Model: model,
        Instruction: &quot;You are a helper. If asked about weather, use get_weather.  &quot; +
            &quot;Then analyze the user&#39;s reaction using analyze_sentiment.&quot;,
        Tools: []tool.Tool{weatherTool, sentimentTool},
    })

    if err != nil {
        log.Fatalf(&quot;Failed to create agent: %v&quot;, err)
    }

    config := &amp;launcher.Config{
        AgentLoader: agent.NewSingleLoader(myAgent),
    }

    l := full.NewLauncher()

    if err = l.Execute(ctx, config, os.Args[1:]); err != nil {
        log.Fatalf(&quot;Run failed: %v\n\n%s&quot;, err, l.CommandLineSyntax())
    }
}</code></pre>
<h2 id="3-사용-사례-및-선택-기준">3. 사용 사례 및 선택 기준</h2>
<hr>
<table>
<thead>
<tr>
<th><strong>기준</strong></th>
<th><strong>LangChain 선택 시</strong></th>
<th><strong>Google ADK 선택 시</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>LLM 종속성</strong></td>
<td>특정 LLM에 구애받지 않고 <strong>다양한 모델</strong>을 실험하고 비교하고 싶을 때.</td>
<td><strong>Google Gemini</strong>의 최첨단 기능과 성능을 최대한 활용하고자 할 때. (다른 LLM도 가능함)</td>
</tr>
<tr>
<td><strong>커스터마이징</strong></td>
<td>에이전트의 내부 추론 로직(프롬프트, 파서 등)을 <strong>세밀하게 제어</strong>하고 싶을 때.</td>
<td>복잡한 에이전트를 <strong>더 빠르고 간편하게</strong> 구축하고 배포 시간을 단축하고 싶을 때.</td>
</tr>
<tr>
<td><strong>배포 환경</strong></td>
<td>로컬 환경이나 다양한 클라우드 환경에서 유연하게 운영하고자 할 때.</td>
<td><strong>Google Cloud 또는 Workspace 생태계 내</strong>에서 에이전트를 구축하고 서비스와 깊이 통합해야 할 때.</td>
</tr>
<tr>
<td><strong>주요 목표</strong></td>
<td>연구 개발(R&amp;D) 및 새로운 AI 에이전트 패턴의 신속한 프로토타이핑.</td>
<td><strong>생산 환경에 최적화된</strong> 안정적이고 확장 가능한 엔터프라이즈급 에이전트 구축.</td>
</tr>
</tbody></table>
<p>요약하자면, <strong>LangChain</strong>은 <strong>유연성과 모듈성을 극대화</strong>하여 개발자에게 &#39;무엇이든 만들 수 있는 레고 세트&#39;를 제공하는 반면, <strong>Google ADK</strong>는 <strong>Gemini 기반의 깊은 통합과 최적화</strong>를 통해 &#39;고성능의 완성도 높은 에이전트를 빠르게 제작할 수 있는 통합 엔지니어링 툴킷&#39;을 제공한다고 볼 수 있습니다.</p>
<h1 id="adk의-핵심-기능">ADK의 핵심 기능</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/f63e94da-bacf-4939-9cde-119591093af4/image.png" alt=""></p>
<ul>
<li><strong>코드 기반 제어성</strong>: 에이전트의 동작, 툴 호출, 오케스트레이션 로직을 소스 코드에서 직접 정의할 수 있어 높은 제어력과 정밀한 테스트 전략을 마련할 수 있습니다. Python뿐만 아니라 <strong>Java, Go 등 다양한 언어 SDK</strong>가 제공되므로 팀의 기술 스택에 맞춘 개발이 가능합니다.</li>
<li><strong>다중 에이전트 구조 지원</strong>: 여러 개의 전문화된 에이전트를 계층적 또는 병렬 구조로 구성하여 복잡한 업무 흐름을 분리·모듈화할 수 있습니다. 대규모 애플리케이션에서도 유지보수성과 확장성을 확보할 수 있습니다.</li>
<li><strong>풍부한 도구 통합 능력</strong>: 사전 정의된 기본 도구 세트 외에도 사용자 정의 함수, 외부 API 명세(OpenAPI 등), 사내 시스템을 연결하여 에이전트의 기능을 자유롭게 확장할 수 있습니다. 이를 통해 모델이 실제 업무 시스템에 액세스해 실행 가능한 작업을 수행할 수 있습니다.</li>
<li><strong>유연한 오케스트레이션 옵션</strong>: 고정된 절차 기반 파이프라인을 구성할 수도 있고, LLM 기반 정책을 사용해 상황에 따라 적응적으로 경로를 선택하는 동적 워크플로우도 구현할 수 있습니다. 복잡한 직렬·병렬 실행 구조까지 자연스럽게 표현됩니다.</li>
<li><strong>통합 개발 환경 제공</strong>: CLI 기반 개발 도구와 시각적 웹 UI가 함께 제공되어 로컬에서 에이전트를 실행하고 디버깅하며, 단계별 실행 로그를 시각적으로 추적할 수 있습니다. 개발–테스트–배포 사이의 피드백 루프가 크게 단축됩니다.</li>
<li><strong>평가 기능 내장</strong>: 응답 품질뿐 아니라 단계별 실행 경로, 도구 호출 결과까지 평가할 수 있어 회귀 테스트와 시나리오 기반 비교 분석이 가능합니다. 에이전트의 안정성 및 품질 확보에 중요한 역할을 합니다.</li>
<li><strong>배포 친화적 구조</strong>: 컨테이너화를 기본으로 설계되어 Docker, Cloud Run, Vertex AI Agent Engine 등 다양한 환경에서 실행할 수 있습니다. 서버리스·쿠버네티스 기반 운영에도 적합하여 확장성과 안정성이 뛰어납니다.</li>
<li><strong>네이티브 스트리밍 지원</strong>: 텍스트와 오디오 모두에서 양방향 스트리밍을 제공하므로, 실시간 상호작용이 필요한 대화형 UI, 음성 기반 시스템, 실시간 처리 워크플로우에 자연스럽게 적용할 수 있습니다.</li>
<li><strong>상태·메모리·아티팩트 관리</strong>: 단기 대화 상태, 장기 메모리, 파일 업로드·다운로드, 실행 아티팩트 추적 등 에이전트가 복합적인 문맥을 다룰 수 있도록 상태 관리 기능이 제공됩니다. 복잡한 멀티턴 시나리오에서도 일관된 동작을 유지할 수 있습니다.</li>
<li><strong>확장성 중심 설계</strong>: 콜백 훅과 플러그인 구조를 활용하여 에이전트의 실행 과정, 로깅, 정책 등을 세부적으로 커스터마이즈할 수 있습니다. 또한 서드파티 서비스 및 사내 시스템과의 통합도 용이합니다.</li>
</ul>
<h1 id="에이전트-개발의-패러다임-변화">에이전트 개발의 패러다임 변화</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/515468bb-e8c6-4e6f-8533-0a877648544f/image.png" alt=""></p>
<p>ADK를 이용한 Agent개발을 직접 실습하다 보니 처음 해보는 저도 쉽게 따라할 수 있을 정도로 공부좀 하면 가능할 것 같더군요. ADK는 분명 멀지 않은 미래에 AI Agent 개발의 패러다임 변화를 불러올 수 있을 것 같습니다.</p>
<h2 id="1-ai-엔지니어링의-산업화-industrialization-of-ai-engineering">1. AI 엔지니어링의 산업화 (Industrialization of AI Engineering)</h2>
<hr>
<p>LangChain 등의 초기 프레임워크가 AI 에이전트 개발의 &#39;실험실&#39; 단계였다면, ADK는 이를 <strong>&#39;대량 생산 공장&#39; 단계</strong>로 끌어올리고 있습니다.</p>
<ul>
<li><strong>SW 개발 표준 채택:</strong> ADK는 &#39;에이전트 개발이 소프트웨어 개발처럼 느껴지도록&#39; 설계되었다는 핵심 철학을 실현합니다. <strong>코드 중심 제어, 통합 개발 환경(IDE), 내장된 테스트 및 평가 기능</strong>은 AI 에이전트 개발을 프롬프트 엔지니어링의 영역에서 <strong>정교한 소프트웨어 엔지니어링의 영역</strong>으로 이동시킵니다.</li>
<li><strong>엔터프라이즈 레디:</strong> <strong>배포 친화적 구조(컨테이너화), 멀티 에이전트 아키텍처, 상태 및 메모리 관리</strong> 기능은 기업이 요구하는 <strong>확장성, 안정성, 유지보수성</strong> 기준을 충족합니다. 이는 파일럿 프로젝트가 아닌, 수백만 사용자를 대상으로 하는 실제 프로덕션 시스템에 AI 에이전트를 도입할 수 있는 길을 열어줍니다.</li>
<li><strong>Gemini의 잠재력 극대화:</strong> Google의 최신 LLM인 Gemini가 가진 <strong>복잡한 추론, 멀티모달리티, 장기 컨텍스트 처리</strong> 능력이 ADK를 통해 구조화된 워크플로우 내에서 안정적으로 활용될 수 있게 되었습니다.</li>
</ul>
<h2 id="2-개발팀-구조의-변화-ai와-sw-개발의-융합">2. 개발팀 구조의 변화: AI와 SW 개발의 융합</h2>
<hr>
<p>ADK는 기존의 데이터 과학팀이나 LLM 연구팀뿐만 아니라, <strong>일반 소프트웨어 개발팀(Backend, Frontend)</strong>이 AI 에이전트 개발에 깊이 참여할 수 있도록 합니다.</p>
<ul>
<li><strong>다양한 언어 지원 (Go, Java, Python):</strong> 기존 백엔드 시스템을 운영하는 팀이 익숙한 언어로 에이전트 로직과 도구를 개발할 수 있게 됩니다.</li>
<li><strong>역할 분리:</strong> ADK의 모듈화된 구조는 개발팀 내에서 역할을 분리하기 용이합니다. 예를 들어, <strong>ML 엔지니어</strong>는 LLM 정책 및 평가 로직을, <strong>SW 엔지니어</strong>는 도구 통합 및 오케스트레이션 파이프라인을 담당하는 방식으로 협업할 수 있습니다.</li>
</ul>
<h1 id="마무리하며">마무리하며</h1>
<hr>
<p><strong>“GDG Devfest Seoul 2025”</strong>에서 만난 <strong>&quot;Agent Development Kit&quot;</strong>는 AI 에이전트 개발을 이전보다 훨씬 더 쉽고 체계적인 영역으로 끌어올린 것 같습니다. 당장에 풀스택 개발자인 저도 하루빨리 Agent 개발을 해 보고 싶다는 생각이 무럭무럭 솟네요.</p>
<p>이미 대부분의 서비스에 AI Agent는 ‘옵션’이 아닌 ‘필수’가 거의 되어버렸습니다. 코드 중심의 개발 방식, 뛰어난 확장성, 그리고 Google 생태계의 지원을 등에 업은 ADK는 이 ‘필수’를 쉽게 이끌어줄 강력한 툴이 될 것이라고 생각합니다.</p>
<p>다들 한번 알아보시고 시작해보시길 추천드립니다. 당신의 다음 AI 프로젝트는 ADK 한번 도입해보는 것이 어떨까요.</p>
<h1 id="reference">Reference</h1>
<hr>
<ul>
<li><a href="https://discuss.pytorch.kr/t/google-ai-adk-agent-development-kit/6746">https://discuss.pytorch.kr/t/google-ai-adk-agent-development-kit/6746</a></li>
<li><a href="https://medium.com/@hams_ollo/googles-agent-development-kit-adk-revolutionizing-multi-agent-application-development-f74f9a08cdce">https://medium.com/@hams_ollo/googles-agent-development-kit-adk-revolutionizing-multi-agent-application-development-f74f9a08cdce</a></li>
<li><a href="https://www.langchain.com/">https://www.langchain.com/</a></li>
<li><a href="https://github.com/google/adk-samples">https://github.com/google/adk-samples</a></li>
<li><a href="https://google.github.io/adk-docs/">https://google.github.io/adk-docs/</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[당신은 AI 주도 개발을 받아들일 수 있나요? (AIDD)]]></title>
            <link>https://velog.io/@playername_ltt/%EB%8B%B9%EC%8B%A0%EC%9D%80-AI-%EC%A3%BC%EB%8F%84-%EA%B0%9C%EB%B0%9C%EC%9D%84-%EB%B0%9B%EC%95%84%EB%93%A4%EC%9D%BC-%EC%88%98-%EC%9E%88%EB%82%98%EC%9A%94-AIDD</link>
            <guid>https://velog.io/@playername_ltt/%EB%8B%B9%EC%8B%A0%EC%9D%80-AI-%EC%A3%BC%EB%8F%84-%EA%B0%9C%EB%B0%9C%EC%9D%84-%EB%B0%9B%EC%95%84%EB%93%A4%EC%9D%BC-%EC%88%98-%EC%9E%88%EB%82%98%EC%9A%94-AIDD</guid>
            <pubDate>Thu, 27 Nov 2025 13:34:19 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/playername_ltt/post/63c0047d-7c41-4508-afd0-60cd0c164156/image.png" alt=""></p>
<p>개발 환경은 지금, 그 어느 때보다 빠르게 변화하고 있습니다. GitHub Copilot, Cursor, Claude Code, Windsurf…</p>
<p><strong>AI 코딩 도구의 춘추전국시대</strong>가 도래한 것이죠.</p>
<p>이제 개발자에게 중요한 질문은 더 이상</p>
<blockquote>
<p>“AI 코딩 도구를 쓰면 기본 지식이 뒤쳐지지 않을까?”</p>
</blockquote>
<p>가 아니라,</p>
<blockquote>
<p>“AI를 어떻게 하면 팀과 개발 프로세스에 맞게 잘 활용할 것인가?”</p>
</blockquote>
<p>로 바뀌고 있습니다.</p>
<p>과거에는 AI 코딩 도구를 사용할 것인지 말 것인지가 논쟁거리였습니다. 하지만 2025년의 개발 환경에서 그 질문은 이미 시대에 뒤처졌습니다.</p>
<p><strong>이제 핵심은</strong> </p>
<p><strong>“AI를 얼마나 잘 활용해 더 나은 개발 사이클을 만드는가?”</strong></p>
<p>그리고 이 질문은 단순 자동완성 다음의, 새로운 개발 패러다임으로 확장되고 있습니다. 그것이 바로 <strong>AI-Driven Development(AIDD)</strong>입니다.</p>
<h1 id="ai가-마구마구-뱉어내는-스파게티-코드">AI가 마구마구 뱉어내는 &quot;스파게티 코드&quot;</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/c0ec103e-2dda-42c1-8b14-73aa3f963dcd/image.png" alt=""></p>
<p>AI 코딩 툴이 마냥 좋기만 할까요? 2024년 발표된 <strong>GitClear의 리포트</strong>는 꽤 충격적인 데이터를 보여줬었는데요.</p>
<blockquote>
<p>&quot;AI 도입 후 코드 이동은 늘었고, 코드 재사용률은 오히려 줄었다.&quot;</p>
</blockquote>
<p>AI가 코드를 너무 쉽게 짜주다 보니, 기존 코드를 재사용하기보다 <strong>&quot;복사/붙여넣기&quot;</strong>하는 경향이 강해졌다는 것입니다. 이는 장기적으로 유지보수가 불가능한 &#39;기술 부채(Technical Debt)&#39;를 가속화합니다.</p>
<ul>
<li><strong>과거:</strong> 고민해서 10줄 작성</li>
<li><strong>현재:</strong> AI가 1초 만에 100줄 생성 -&gt; 검증 없이 Commit -&gt; 레거시 폭탄</li>
</ul>
<p>우리는 <strong>“AI가 짠 코드를 믿지 않는 것&quot;</strong>에서부터 AIDD를 시작해야 합니다. 물론 AI는 점점 발전하며 신뢰성 있는 코드를 짜 나아갈테지만 말이죠.</p>
<h1 id="단순-자동완성의-시대의-끝-aidd란">단순 자동완성의 시대의 끝, AIDD란?</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/94c6aab7-7db2-457e-8bfd-cbfc156bf318/image.png" alt=""></p>
<p>이제 막 <strong>Copilot v1</strong>이 다음 변수명이나 함수 시그니처를 추천해주던 시대는 끝났습니다. 단순한 <strong>&#39;코드 스니펫 생성(Code Snippet Generation)&#39;</strong>은 더 이상 AIDD라고 부르지 않습니다.</p>
<p>그렇다면 진정한 AIDD는 무엇일까요? </p>
<p><strong>AI를 개발 프로세스 전반을 주도하는 &#39;사고 파트너(Thought Partner)&#39;로 활용</strong>하는 개발 방법론의 총체를 의미합니다.</p>
<h3 id="aidd가-기존-자동완성과-다른-결정적인-차이점">AIDD가 기존 자동완성과 다른 결정적인 차이점</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>단순 자동완성 (2022년 이전)</th>
<th>AIDD (AI Driven Development)</th>
</tr>
</thead>
<tbody><tr>
<td><strong>인식 범위</strong></td>
<td>현재 파일, 최근 몇 줄의 코드</td>
<td><strong>전체 코드베이스</strong> (프로젝트 구조, 컨벤션, 의존성)</td>
</tr>
<tr>
<td><strong>수행 임무</strong></td>
<td>다음 단어/줄 예측, 단순 함수 생성</td>
<td><strong>아키텍처 변경</strong>, <strong>모듈 간 버그 수정</strong>, <strong>테스트 코드 작성</strong></td>
</tr>
<tr>
<td><strong>개발자 역할</strong></td>
<td>타이핑 속도를 높여주는 보조자</td>
<td>복잡한 문제 해결을 함께 논의하는 <strong>사고 파트너</strong></td>
</tr>
</tbody></table>
<h2 id="aidd의-두-가지-핵심">AIDD의 두 가지 핵심</h2>
<hr>
<p>AIDD는 개발자에게 두 가지 근본적인 변화를 요구합니다.</p>
<h3 id="1-ragretrieval-augmented-generation-기반의-맥락-이해">1. RAG(Retrieval-Augmented Generation) 기반의 &#39;맥락 이해&#39;</h3>
<p>Cursor와 같은 최신 도구들은 <strong><code>RAG</code></strong> 기술을 이용해 단순히 다음 코드를 예측하는 것을 넘어, <strong>프로젝트 전체 파일의 구조와 팀 컨벤션</strong>을 인덱싱합니다.</p>
<p>이로 인해 AI는 &quot;여기에 <code>getUserInfo</code>를 넣으면 되겠네&quot;가 아니라, &quot;<strong>이 로직은 우리 팀의 Redux 아키텍처에서 Store와 Action을 어떻게 변경해야 할지</strong>&quot;까지 이해하고 제안할 수 있게 됩니다. 이는 곧 <strong>&#39;코딩&#39;의 영역에서 &#39;설계 및 리팩토링&#39;의 영역</strong>으로 AI의 역할이 확장되었음을 의미합니다.</p>
<h3 id="2-ai-코드의-신뢰성-확보를-위한-방법론-통합">2. &#39;AI 코드&#39;의 신뢰성 확보를 위한 방법론 통합</h3>
<p>AI는 빠르게 코드를 짜지만, 그 코드가 항상 정확하고 견고하지는 않습니다. 바로 앞서 언급했던 GitClear 리포트의 경고가 여기에 해당됩니다.</p>
<p>따라서 AIDD는 <strong>&#39;AI는 구현 도구, TDD는 검증 도구&#39;</strong>라는 명제를 핵심으로 삼습니다. AI에게 &quot;테스트 통과 코드&quot;를 요청하고, 개발자는 &quot;테스트 코드가 비즈니스 요구사항을 정확히 반영하는지&quot;를 검토하는 방식으로 역할이 재정의됩니다.</p>
<p>AIDD는 곧 <strong>AI를 비판적으로 활용하여 개발 방법론(TDD, Test-Driven Development)에 녹여내는 과정</strong>이라고 할 수 있겠습니다.</p>
<h1 id="ai-driven-development라고-했는데-tdd가-왜-튀어나오나요">AI-Driven Development라고 했는데 TDD가 왜 튀어나오나요?</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/f2bf57dc-bde9-467c-90c9-658690dac22f/image.png" alt=""></p>
<p>AIDD(AI Driven Development)의 핵심은 궁극적으로 <strong>&#39;신뢰할 수 있는 소프트웨어&#39;를 빠르고 효율적으로 만드는 것</strong>입니다. 하지만 AI가 코드를 짜는 과정에서 근본적인 한계점이 발생하고, 이 지점에서 <strong>TDD(Test-Driven Development, 테스트 주도 개발)</strong>는 단순한 개발 방법론을 넘어 AIDD의 <strong>안전망이자 핵심 방법론</strong>이 됩니다.</p>
<h2 id="1-ai-코드의-본질적인-한계-불확실성과-환각-hallucination">1. AI 코드의 본질적인 한계: 불확실성과 환각 (Hallucination)</h2>
<hr>
<p>LLM(Large Language Models) 기반의 AI 코딩 도구는 <strong>&#39;가장 확률 높은 다음 토큰&#39;</strong>을 예측하여 코드를 생성합니다. 이 과정에서 발생하는 AI 코드의 가장 큰 위험 요소는 다음과 같습니다.</p>
<ul>
<li><strong>불확실성 (Non-Deterministic):</strong> 같은 프롬프트에도 다른 코드가 나옵니다.</li>
<li><strong>환각 (Hallucination):</strong> 존재하지 않는 API나 라이브러리를 사용하거나, 현재 프로젝트 맥락과 맞지 않는 잘못된 로직을 자신 있게 제시합니다.</li>
<li><strong>오버 엔지니어링 (Over-Engineering):</strong> 요구 사항보다 훨씬 복잡하거나 불필요한 코드를 생성하여 기술 부채를 유발합니다. (앞서 언급된 [GitClear Report]의 핵심 경고입니다.)</li>
</ul>
<p>즉, <strong>AI는 &#39;빠른 코드 작성&#39;이라는 생산성은 제공하지만, &#39;코드의 정확성과 안정성&#39;이라는 품질은 보장하지 못합니다.</strong></p>
<h2 id="2-tdd-ai-코드를-신뢰할-수-있는-코드로-만드는-검증-안전-장치">2. TDD: AI 코드를 &#39;신뢰할 수 있는 코드&#39;로 만드는 검증 안전 장치</h2>
<hr>
<p>TDD는 코드를 짜기 전에 테스트 코드를 먼저 작성하는 개발 방식입니다. AIDD에 TDD가 결합되어야 하는 이유는 AI의 불안정성을 완벽하게 상쇄시키기 때문입니다.</p>
<h3 id="a-ai에-대한-명확한-지시서-역할">A. AI에 대한 명확한 지시서 역할</h3>
<p>AIDD에서 테스트 코드는 단순한 검증 도구가 아닌, AI에게 던지는 <strong>가장 명확하고 구체적인 요구사항 정의서</strong>가 됩니다.</p>
<ul>
<li><strong>일반 프롬프트:</strong> &quot;회원가입 기능을 만들어줘.&quot; (AI에게 넓은 자유도)</li>
<li><strong>TDD 기반 프롬프트:</strong> (테스트 코드를 제시하며) &quot;이 테스트 케이스를 통과하는 <code>UserService.register</code> 함수를 구현해줘.&quot; (AI에게 명확한 목표 제시)</li>
</ul>
<p>이는 AI가 요구사항을 잘못 해석하거나 불필요한 코드를 넣는 <strong>환각의 위험을 최소화</strong>합니다.</p>
<h3 id="b-red-green-refactor-사이클의-생산성-극대화">B. Red-Green-Refactor 사이클의 생산성 극대화</h3>
<p>TDD의 <strong>&quot;빨간불 - 초록불 - 리팩토링&quot;</strong> 사이클이 AI를 만나 혁신적으로 빨라집니다.</p>
<ol>
<li><strong>🔴 Red (빨간불):</strong> 개발자가 실패하는 <strong>테스트 코드</strong>를 작성합니다. (인간의 설계)</li>
<li><strong>🟢 Green (초록불):</strong> AI에게 <strong>&#39;이 테스트를 통과하는 구현 코드&#39;</strong>를 작성하게 합니다. (AI의 구현 속도 극대화)</li>
<li><strong>Refactor (리팩토링):</strong> AI에게 <strong>&#39;방금 생성된 구현 코드를 더 깔끔하게 리팩토링하고 SOLID 원칙을 적용해달라&#39;</strong>고 요청합니다. (인간의 검토 및 AI의 보조 리팩토링)</li>
</ol>
<p>이러한 협력 모델은 <strong>인간의 사고 능력</strong>과 <strong>AI의 구현 속도 및 패턴 인식 능력</strong>을 결합하여, 단순히 속도만 빠른 개발이 아니라 <strong>품질과 속도를 동시에 잡는 개발</strong>을 가능하게 합니다.</p>
<h1 id="바이브-코더와-주니어에게-aidd가-필수인-이유--시니어처럼-일하기">바이브 코더와 주니어에게 AIDD가 &#39;필수&#39;인 이유 = 시니어처럼 일하기</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/33122023-5419-4996-a8a0-fb22dcd0708c/image.png" alt=""></p>
<p>바이브 코더(Vibe Coder)나 개발 경력이 길지 않은 주니어 개발자에게 <strong>AI Driven Development (AIDD)</strong>는 단순한 생산성 도구를 넘어 <strong>&quot;시니어 개발자처럼 일하는 방법&quot;</strong>을 가르쳐주는 핵심적인 성장 수단이 될 수 있다고 생각합니다.</p>
<p>AIDD를 채택해야 하는 구체적인 이유 3가지를 정리했습니다.</p>
<h2 id="시니어-엔지니어의-사고방식-학습">시니어 엔지니어의 &#39;사고방식&#39; 학습</h2>
<hr>
<p>주니어 개발자가 가장 어려움을 느끼는 부분은 <strong>&#39;어떻게 코드를 짜야 하는가&#39;</strong>가 아니라, <strong>&#39;왜 이 코드를 이렇게 짜야 하는가&#39;</strong>에 대한 의사 결정 과정입니다.</p>
<ul>
<li><strong>즉각적인 피드백 루프:</strong> 바이브 코더는 코드를 짠 후 AI에게 &quot;이 코드가 <strong>SOLID 원칙</strong>을 따르나요?&quot;, &quot;이 함수는 <strong>테스트하기 쉽게</strong> 리팩토링할 수 있나요?&quot;라고 질문하고 즉시 피드백을 받을 수 있습니다.</li>
<li><strong>패턴 및 컨벤션 내재화:</strong> Cursor의 <code>.cursorrules</code> , Claude Code의 <code>CLAUDE.md</code>등을 통해 팀의 컨벤션(명명 규칙, 아키텍처 패턴)을 AI에게 학습시키고, AI가 생성한 코드를 분석함으로써 <strong>팀이 중요하게 생각하는 코딩 습관</strong>을 빠르게 체화할 수 있습니다. 물론 작성 후 리뷰를 요청해도 가능합니다. AI가 일종의 <strong>24시간 대기하는 코드 리뷰어이자 멘토</strong> 역할을 하는 것입니다.</li>
</ul>
<h2 id="불안-해소-및-생산성-가속화">불안 해소 및 생산성 가속화</h2>
<hr>
<p>바이브 코더는 새로운 기능을 구현할 때 &#39;어디서부터 시작해야 할지&#39;, &#39;혹시나 심각한 버그를 만들지 않을지&#39;에 대한 불안감을 느끼기 쉽습니다.</p>
<ul>
<li><strong>TDD 기반 개발 습관 획득:</strong> AIDD는 TDD를 강제합니다. 코드를 직접 짜기 전에 테스트 코드를 작성하는 경험을 반복하면, 바이브 코더는 구현보다는 <strong>기능의 설계</strong>에 집중하는 습관을 들이게 됩니다. AI가 빠른 속도로 구현(Green)을 책임지므로, 구현 실패의 부담이 줄어들어 개발 속도가 가속화됩니다.</li>
<li><strong>레거시 코드의 즉각적인 해석:</strong> 난해한 레거시 코드를 만나도 AI에게 &quot;이 모듈의 주요 역할과 의존성은 뭐야?&quot;등을 질문하여 <strong>코드 문맥을 빠르게 파악</strong>할 수 있습니다. 이는 시니어 개발자가 상황을 파악하는 속도, 순서와 유사합니다.</li>
</ul>
<h2 id="풀스택-개발-역량-확장">풀스택 개발 역량 확장</h2>
<hr>
<p>현대 개발 환경은 백엔드(Python/Java), 프론트엔드(React/TypeScript), 인프라(YAML/Terraform) 등 다양한 언어와 기술 스택을 요구합니다.</p>
<ul>
<li><strong>다국어(Polyglot) 코드 생성:</strong> 바이브 코더가 주력 언어가 아닌 다른 스택을 다룰 때, AI는 즉시 해당 언어의 <strong>Idiomatic Code(관용적인 코드)</strong>를 생성해 줍니다. 예를 들어, <strong>&quot;TypeScript로 작성된 React 컴포넌트를 Svelte 컴포넌트로 바꿔줘&quot;</strong>와 같은 요청을 통해, 문법 오류에 대한 걱정 없이 새로운 기술 스택을 빠르게 탐색하고 적용할 수 있습니다.</li>
<li><strong>시간 절약:</strong> 레퍼런스 문서를 뒤지며 사소한 문법이나 설정 파일을 찾는 시간을 절약하고, <strong>&quot;문제 해결&quot;</strong>이라는 본질적인 작업에 에너지를 집중할 수 있게 됩니다.</li>
</ul>
<p>결론적으로, AIDD는 주니어에게 <strong>시니어의 지식과 경험을 복사하고, 실수의 불안감을 줄이며, 빠른 속도로 다양한 기술 스택을 경험하게 해주는</strong> 최고의 개발 방법론이자 교육 환경으로 자리잡고 있습니다.</p>
<h1 id="ai-개발-피할-수-없다면-더-똑똑하게-사용하자">AI 개발, 피할 수 없다면 더 똑똑하게 사용하자</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/9bef5e25-ae80-4d3d-af32-8afcf8c83c3b/image.png" alt=""></p>
<p>풀 AI 개발보다는 사람의 손을 많이 탄 개발을 선호하는 사람들은 대개 이유가 명확합니다.</p>
<ol>
<li>유지보수성이 떨어진다.</li>
<li>구조가 뒤죽박죽이다.</li>
</ol>
<p>이 외에도 여러가지가 있을 수 있지만 이 두가지가 대표적이기에 언급해봤습니다.</p>
<p>반대로 생각해보면 이 두 가지만 해결할 경우 반대하는 사람의 과반수는 마음을 돌릴 수 있게 될 것 같습니다. 무작정 AI 도입을 통해 후회하기 보다는, AI를 제대로 활용하는 다양한 방법들을 익히시고 사용하는 연습을 해보면 좋을 것 같습니다.</p>
<p>AI Agent 사용이 익숙하지 않은 개발자분들은 우선 익숙한 <code>Github Copilot</code>으로 코드 부분부분 작성하는 것부터 익숙해지고, 다음은 <code>Gemini CLI</code> 로, 필요하다면 <code>Claude Code</code>, <code>Cursor</code>, 이번에 나온 <code>AntiGravity</code>등등 필요 여부에 따른 툴들로 확장시켜 나가면 좋을 것 같습니다.</p>
<p>더 이상 <strong>“기본기는 진짜 빵빵한”</strong> 개발자는 메리트가 없는 것 같습니다. 기본기는 이제 AI가 훨씬 잘하니까요.</p>
<h1 id="reference">Reference</h1>
<hr>
<ul>
<li><a href="https://onephase.com/en/ai-driven-software-development-speed-efficiency-and-innovation/">https://onephase.com/en/ai-driven-software-development-speed-efficiency-and-innovation/</a></li>
<li><a href="https://www.itworld.co.kr/article/3996306/lg-cns-ai-%EC%BD%94%EB%94%A9-%ED%94%8C%EB%9E%AB%ED%8F%BC-%EB%8D%B0%EB%B8%8C%EC%98%A8-aidd%EB%A1%9C-%EA%B0%9C%EB%B0%9C-%EC%A0%84-%EB%8B%A8%EA%B3%84%EC%97%90-ai-%ED%88%AC%EC%9E%85.html">https://www.itworld.co.kr/article/3996306/lg-cns-ai-%EC%BD%94%EB%94%A9-%ED%94%8C%EB%9E%AB%ED%8F%BC-%EB%8D%B0%EB%B8%8C%EC%98%A8-aidd%EB%A1%9C-%EA%B0%9C%EB%B0%9C-%EC%A0%84-%EB%8B%A8%EA%B3%84%EC%97%90-ai-%ED%88%AC%EC%9E%85.html</a></li>
<li><a href="https://aws.amazon.com/ko/blogs/tech/ai-driven-development-life-cycle/">https://aws.amazon.com/ko/blogs/tech/ai-driven-development-life-cycle/</a></li>
<li><a href="https://medium.com/effortless-programming/better-ai-driven-development-with-test-driven-development-d4849f67e339">https://medium.com/effortless-programming/better-ai-driven-development-with-test-driven-development-d4849f67e339</a></li>
<li><a href="https://dev.to/bain_forge/tdd-and-ai-enabled-engineering-chl">https://dev.to/bain_forge/tdd-and-ai-enabled-engineering-chl</a></li>
<li><a href="https://www.scribd.com/document/705355560/Coding-on-Copilot-2024-Developer-Research">https://www.scribd.com/document/705355560/Coding-on-Copilot-2024-Developer-Research</a></li>
<li><a href="https://tidyfirst.substack.com/p/the-life-changing-magic-of-tidying">https://tidyfirst.substack.com/p/the-life-changing-magic-of-tidying</a></li>
<li><a href="https://medium.com/bgl-tech/what-are-the-solid-design-principles-c61feff33685">https://medium.com/bgl-tech/what-are-the-solid-design-principles-c61feff33685</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로젝트 파악, 이제 그만두세요.]]></title>
            <link>https://velog.io/@playername_ltt/Repo-%ED%8C%8C%EC%95%85%EB%8F%84-%EC%9D%B4%EC%A0%9C%EB%8A%94-AI%ED%95%9C%ED%85%8C-%EB%A7%A1%EA%B8%B0%EB%9D%BC%EA%B3%A0</link>
            <guid>https://velog.io/@playername_ltt/Repo-%ED%8C%8C%EC%95%85%EB%8F%84-%EC%9D%B4%EC%A0%9C%EB%8A%94-AI%ED%95%9C%ED%85%8C-%EB%A7%A1%EA%B8%B0%EB%9D%BC%EA%B3%A0</guid>
            <pubDate>Fri, 14 Nov 2025 09:01:05 GMT</pubDate>
            <description><![CDATA[<p>개발자의 시간을 가장 많이 잡아먹는 건 새 코드 작성이 아닙니다. 바로 <strong>남이 쓴 코드 읽기</strong>죠. Google은 이 문제를 AI로 풀겠다며 <strong>Code Wiki</strong>를 공개 프리뷰로 출시했습니다.</p>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/13b24dbf-5edc-4922-a287-659c76fb4028/image.png" alt=""></p>
<p>Google Cloud Developer 팀이 발표한 이 플랫폼은 코드 레포지토리를 스캔해 자동으로 위키 문서를 생성하고, Gemini 기반 챗봇이 이를 활용해 질문에 답합니다. 신입 개발자가 첫날부터 커밋하고, 시니어 개발자가 몇 분 만에 새로운 라이브러리를 파악할 수 있다는 것이 핵심 메시지입니다.</p>
<p>이건 제가 비슷한 아이디어로 고민 중이었는데, 이번에 공개되었다고 해서 아주 흥미롭네요.</p>
<h1 id="왜-code-wiki가-등장했나">왜 Code Wiki가 등장했나</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/1a55cdfc-1bef-48a3-9b08-0c2f777c9f99/image.png" alt=""></p>
<p>현업 개발자라면 누구나 공감하는 세 가지 현실이 있습니다.</p>
<ul>
<li>새 코드 작성보다 기존 코드 이해에 더 많은 시간이 든다.</li>
<li>문서화는 어렵고, 코드는 빠르게 변하며, 문서는 쉽게 낡는다.</li>
<li>설계자 또는 작성자가 떠난 팀에서는 코드의 &quot;의도&quot;가 사라진다.</li>
</ul>
<p>Google은 “코드를 읽는 것이 소프트웨어 개발에서 가장 큰 병목 중 하나”라고 말하며, Code Wiki를 통해 이 문제를 해결하겠다고 선언했습니다.</p>
<h1 id="code-wiki의-핵심-기능">Code Wiki의 핵심 기능</h1>
<hr>
<h3 id="자동-업데이트--지속적-문서화">자동 업데이트 &amp; 지속적 문서화</h3>
<p>Code Wiki는 레포지토리 전체를 스캔해 <strong>코드 변경 시마다 문서를 자동 재생성</strong>합니다.</p>
<p>누군가 수동으로 문서를 고칠 필요가 없으며, 문서가 코드와 엇나가는 상황을 원천적으로 제거합니다.</p>
<h3 id="컨텍스트를-이해하는-gemini-기반-챗봇">컨텍스트를 이해하는 Gemini 기반 챗봇</h3>
<p>Code Wiki는 위키 전체를 Gemini 챗봇의 <strong>지식 베이스</strong>로 사용합니다.</p>
<p>즉, 다음과 같은 질문을 하면:</p>
<ul>
<li>“이 함수는 왜 존재하나요?”</li>
<li>“이 모듈은 어디서 호출되나요?”</li>
<li>“이 버그를 고치려면 어떤 코드를 봐야 하나요?”</li>
</ul>
<p>Gemini는 코드베이스 전체 맥락을 이해하고 문서화된 구조를 활용해 즉각 답합니다.</p>
<h3 id="아키텍처-다이어그램-자동-생성--하이퍼링크-탐색">아키텍처 다이어그램 자동 생성 &amp; 하이퍼링크 탐색</h3>
<p>생성된 문서는 단순 텍스트가 아닙니다.</p>
<ul>
<li>아키텍처 다이어그램</li>
<li>클래스 다이어그램</li>
<li>시퀀스 다이어그램</li>
</ul>
<p>이 모두가 <strong>현재 코드 상태를 반영한 최신 버전으로 자동 생성</strong>됩니다.</p>
<p>또한 문서의 모든 개념은 <strong>코드 파일, 클래스, 함수 정의로 직접 연결되는 하이퍼링크</strong>를 포함합니다.</p>
<p>즉, 문서 → 코드 → 연관 코드로 자연스럽게 이어지는 탐색이 가능합니다.</p>
<h1 id="기존-문서화-도구들과-무엇이-다른가">기존 문서화 도구들과 무엇이 다른가?</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/b8c4d90e-eab2-4230-b0a1-0a9e36c2249d/image.png" alt=""></p>
<p>Doxygen, Sphinx, Javadoc 등 기존 도구들이 수십 년간 쓰여 왔습니다.</p>
<p>그러나 그 방식에는 본질적인 한계가 있습니다.</p>
<ol>
<li><p><strong>개발자가 직접 주석을 작성해야 한다.</strong></p>
<p> 주석이 빠지거나 오래되면 문서도 무용지물이 됩니다.</p>
</li>
<li><p><strong>정적 문서 생성 방식.</strong></p>
<p> 코드가 바뀌어도 문서는 그대로라 항상 뒤처지게 됩니다.</p>
</li>
<li><p><strong>문서-코드 간 맥락 단절.</strong></p>
<p> 문서를 읽다가 궁금한 부분이 생기면 직접 코드를 뒤져야 합니다.</p>
</li>
</ol>
<p>Code Wiki는 이 세 가지 문제를 모두 해결합니다.</p>
<ul>
<li>주석 없이 전체 코드베이스 분석</li>
<li>코드 변경 시 문서 자동 업데이트</li>
<li><strong>AI 챗봇 기반 질의응답</strong></li>
<li>문서-코드 하이퍼링크 통합 탐색</li>
</ul>
<h2 id="ai가-당신의-코드베이스를-완전히-아는-상황">AI가 당신의 코드베이스를 &#39;완전히 아는&#39; 상황</h2>
<hr>
<p>Code Wiki의 Gemini는 범용 AI 챗봇과 다릅니다.</p>
<p><strong>코드베이스 전체를 스캔한 뒤 문서화된 위키를 기반으로 답변</strong>합니다.</p>
<p>따라서 다음과 같은 실질적인 활용이 가능합니다.</p>
<ul>
<li>특정 모듈의 동작 흐름 파악</li>
<li>함수 호출 관계 확인</li>
<li>버그가 발생한 코드 경로 추적</li>
<li>특정 기능이 어디에서 구현되었는지 검색</li>
</ul>
<p>단순한 코드 요약이 아니라 <strong>전체 코드 구조·맥락을 고려한 이해</strong>를 제공합니다.</p>
<h2 id="첫날-커밋과-몇-분-만의-구조-파악">첫날 커밋과 몇 분 만의 구조 파악</h2>
<hr>
<p>Google은 Code Wiki의 핵심 가치를 두 가지로 설명합니다.</p>
<h3 id="1-신입-개발자는-day-1-커밋이-가능해진다">1) 신입 개발자는 “Day 1 커밋”이 가능해진다</h3>
<p>레거시 코드, 문서 없는 시스템 앞에서 며칠씩 헤매지 않아도 됩니다.</p>
<p>필요한 내용을 Gemini에 물으며 필요한 코드만 빠르게 파악할 수 있습니다.</p>
<h3 id="2-시니어-개발자는-새로운-라이브러리를-몇-분-만에-파악">2) 시니어 개발자는 새로운 라이브러리를 몇 분 만에 파악</h3>
<p>외부 오픈소스나 신규 라이브러리를 도입할 때 시간을 크게 단축할 수 있습니다.</p>
<p>전체 구조를 빠르게 이해하고 바로 코드에 활용할 수 있죠.</p>
<h2 id="프라이빗-레포지토리도-곧-지원-예정">프라이빗 레포지토리도 곧 지원 예정</h2>
<hr>
<p>현재 Code Wiki는 <strong>공개 오픈소스 레포지토리</strong>만 지원합니다.</p>
<p>그러나 Google은 <strong>Gemini 기반 CLI 확장</strong>을 개발 중이라고 밝혔습니다.</p>
<p>곧 다음이 가능해질 전망입니다.</p>
<ul>
<li>팀 내부 프라이빗 레포지토리에 Code Wiki 적용</li>
<li>사내 네트워크에서 안전하게 실행</li>
<li>레거시 시스템 분석 자동화</li>
</ul>
<p>특히 작성자가 이미 없는 레거시 코드 유지보수에 큰 가치를 제공할 것으로 보입니다.</p>
<h1 id="문서-작성-부담-vs-코드-이해-속도">문서 작성 부담 vs. 코드 이해 속도</h1>
<hr>
<p>개발자들은 오랫동안 두 가지 중 하나를 선택해야 했습니다.</p>
<ul>
<li>문서를 꼼꼼히 작성해 개발 속도를 낮추거나</li>
<li>빠르게 개발하고 문서를 포기하거나</li>
</ul>
<p>Code Wiki는 이 오랜 트레이드오프를 제거합니다.</p>
<p>문서 작성 부담 없이도 문서는 항상 최신 상태를 유지하고,코드를 이해하는 속도는 극적으로 빨라지게 됩니다.</p>
<p>Google이 &quot;개발자는 더 이상 코드를 해독하는 데 시간을 쓰지 말고, 진짜 개발에 집중하라&quot;고 말하는 이유죠.</p>
<p><a href="https://codewiki.google/">Code Wiki</a></p>
<h1 id="개인적인-생각">개인적인 생각</h1>
<hr>
<p>솔직히 개발자가 코드베이스를 이해하는 과정은 늘 반복되지만, 자동화하기 가장 어려운 영역이었습니다. 기존 문서화 도구들이 API 수준의 정적 정보에 머물렀다면, Code Wiki는 코드 전체를 <strong>“읽고 이해하는 주체”</strong>까지 자동화하려는 시도라는 점에서 큰 방향 전환을 보여줍니다.</p>
<p>특히 위키와 챗봇이 하나의 지식 베이스로 통합된 구조는 ‘문서는 사람이 읽는 것’이라는 패러다임을 넘어, ‘문서는 AI가 대신 읽고 설명하는 것’이라는 새로운 형태의 개발 워크플로를 만들어낼 가능성이 있습니다.</p>
<p>다만 아직은 퍼블릭 프리뷰 단계인 만큼 실제 대규모 프라이빗 레포지토리에서 어느 정도 정확도와 속도를 낼지, 팀마다 다른 개발 표준·스타일을 어떻게 처리할지 등은 남아 있는 과제입니다. 또 “문서가 항상 맞다”는 전제를 두기 어려운 초기 버전에서는, AI가 생성한 설명을 신뢰해도 되는지에 대한 검증 체계도 필요합니다.</p>
<p>그럼에도 불구하고, 만약 Code Wiki가 안정적으로 프라이빗 코드베이스까지 지원하게 된다면, 개발자 온보딩이나 레거시 시스템 유지보수 같은 오랜 난제가 실질적으로 해소될 가능성이 있습니다. 결국 문서화를 위해 시간을 투자하는 대신, <strong>문서화를 자동화해 버리고 개발 자체에 더 집중하는 흐름</strong>이 주류가 될지도 모르겠습니다.</p>
<h1 id="reference">Reference</h1>
<hr>
<ul>
<li><a href="https://developers.googleblog.com/en/introducing-code-wiki-accelerating-your-code-understanding/">https://developers.googleblog.com/en/introducing-code-wiki-accelerating-your-code-understanding</a></li>
<li><a href="https://codewiki.google/">https://codewiki.google/</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[바이브 코딩이 죽어가고 있다?]]></title>
            <link>https://velog.io/@playername_ltt/%EB%B0%94%EC%9D%B4%EB%B8%8C-%EC%BD%94%EB%94%A9%EC%9D%98-%ED%98%84%EC%A3%BC%EC%86%8C%EC%97%90-%EB%8C%80%ED%95%9C-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@playername_ltt/%EB%B0%94%EC%9D%B4%EB%B8%8C-%EC%BD%94%EB%94%A9%EC%9D%98-%ED%98%84%EC%A3%BC%EC%86%8C%EC%97%90-%EB%8C%80%ED%95%9C-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Fri, 24 Oct 2025 02:02:48 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/playername_ltt/post/34fd69ab-e4f3-4226-94de-3860273e97f6/image.jpg" alt=""></p>
<h1 id="개요">개요</h1>
<hr>
<p>지속적으로 ‘바이브 코딩(Vibe Coding)’이라는 용어가 뜨겁습니다. 개발자가 직접 코드 타이핑하는 대신, 자연어 프롬프트나 AI 모델에게 “이런 기능 만들어줘”라고 말하면 코드가 생성되는 방식입니다. 하지만 <strong>Gary Marcus</strong>는 최근 글에서 이 흐름이 “아마도 끝을 향해 가고 있다”는 관점에서 논의를 펼칩니다.</p>
<p><a href="https://garymarcus.substack.com/p/is-vibe-coding-dying">https://garymarcus.substack.com/p/is-vibe-coding-dying</a></p>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/53f5e191-9dd5-4a22-8617-7dd01ce66eab/image.png" alt=""></p>
<p>이 글에서는 그 글의 주요 논점과 기술적 함의, 그리고 우리가 개발자로서 고려할 변화들을 정리해보겠습니다.</p>
<h1 id="바이브-코딩이란-무엇인가">바이브 코딩이란 무엇인가?</h1>
<hr>
<p>먼저 개념 정의부터 짚고 넘어가겠습니다.</p>
<ul>
<li><p>용어의 기원: <strong>Andrej Karpathy</strong>가 2025년 2월에 제안한 개념으로, 개발자가 직접 코드 라인을 작성하기보다는 “감(바이브)”에 따라 AI에게 기능을 설명하면 코드가 자동으로 생성되는 방식입니다.</p>
</li>
<li><p>핵심 특징:</p>
<ul>
<li>자연어 또는 비교적 고수준의 설명으로 기능을 요청</li>
<li>생성된 코드를 사람이 <strong>라인 단위로 설계하거나 전통적 방식으로 구현</strong>하지 않고 실행 결과 중심으로 검토</li>
<li>빠른 프로토타이핑, 비전문가도 어느 정도 기능 구현 가능성 확보</li>
</ul>
</li>
<li><p>기대 효과:</p>
<ul>
<li>코딩 진입 장벽 감소</li>
<li>아이디어 → 실행 빠르게 전환 가능</li>
<li>스타트업이나 프로토타입 단계에서 소규모 팀으로도 기능 구현 가능</li>
</ul>
</li>
</ul>
<p>하지만 동시에 다음과 같은 경고도 병존합니다.</p>
<ul>
<li>코드 품질·유지보수성 저하 위험</li>
<li>설계·아키텍처 이해 부족 → 디버깅 및 확장 시 골치 아픔</li>
<li>보안 및 신뢰성 우려</li>
</ul>
<h1 id="바이브-코딩이-죽어가고-있다">“바이브 코딩이 죽어가고 있다?”</h1>
<hr>
<p>Marcus는 글에서 두 가지 주장을 중심으로 논의를 펼칩니다.</p>
<ol>
<li><p><strong>아마추어가 팀 코더를 대체하지 못할 것</strong></p>
<p> Marcus는 제목에서도 “Amateurs might <strong>not</strong> be replacing teams of coders, after all.”라고 말합니다.</p>
<p> 즉, 바이브 코딩이 비전문가나 아마추어에 의해 기존의 엔지니어링 집단을 대체할 것이란 기대는 과장되었다는 입장입니다.</p>
</li>
<li><p><strong>용도는 제한적이고, ‘생산 시스템’으로는 한계가 있다</strong></p>
<p> 그는 다음과 같이 지적합니다:</p>
<blockquote>
<p>“Vibe coding can be fine if you are building something very familiar, but…”</p>
<p>즉, 익숙한 기능·패턴이라면 바이브 코딩으로 빠르게 구현할 수 있지만, 복잡하거나 새로운 시스템 설계·아키텍처를 요구하는 상황에서는 한계가 드러난다는 이야기입니다.</p>
</blockquote>
</li>
</ol>
<p>이 두 주장으로부터 그는 다음과 같은 의미 있는 시사점을 도출합니다.</p>
<ul>
<li>바이브 코딩을 통해 만들어진 제품이나 코드베이스의 <strong>책임 소재</strong>와 <strong>유지보수 가능성</strong>이 문제될 수 있다.</li>
<li>AI가 자동으로 코딩해주는 것처럼 보이지만, 실제로는 사람이 이해하고 설계하며 운영할 수 있어야 한다는 엔지니어링 본질이 사라질 위험이 있다.</li>
<li>따라서 “바이브 코딩 → 코드 생산 → 팀 불필요”라는 흐름은 현실적으로는 매끄럽지 않다는 경고입니다.</li>
</ul>
<h1 id="왜-바이브-코딩이-지금-끝물이라는-평가인가">왜 바이브 코딩이 지금 ‘끝물’이라는 평가인가?</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/41d1c131-fb0c-482c-8be4-5378bf69da94/image.png" alt=""></p>
<p>Marcus의 주장을 기술 트렌드 및 현장 목소리와 연관지어 보면 몇 가지 이유가 보입니다.</p>
<h3 id="1-코드의-이해-가능성-문제">1. 코드의 <strong>이해 가능성</strong> 문제</h3>
<p>AI가 생성한 코드라 해도 결국 사람이 유지보수해야 할 가능성이 높습니다. 그는 “코드를 이해하지 못하는 상태에서 코드베이스를 넘겨받는 것은 자산이 아니라 오히려 부채(liability)”라는 표현을 사용합니다.</p>
<p>즉, 바이브 코딩 방식은 초기 속도는 빠를지 몰라도 장기적 관점에서 문제를 일으킬 수 있다는 뜻입니다.</p>
<h3 id="2-복잡한-시스템·아키텍처에서의-한계">2. 복잡한 시스템·아키텍처에서의 한계</h3>
<p>단순 기능, 익숙한 패턴이라면 AI 코드 생성이 유리하지만, 기존 시스템과의 통합, 비정형 요구사항, 성능 튜닝, 운영·보안 고려 등이 중요한 경우에는 여전히 사람이 주도해야 합니다. Marcus가 이 부분을 강조하고 있습니다.</p>
<h3 id="3-스타트업·초기-프로덕트에서는-가능했지만-스케일업에는-갭이-있다">3. 스타트업·초기 프로덕트에서는 가능했지만 스케일업에는 갭이 있다</h3>
<p>실제로는 일부 신생 스타트업들이 “코드의 95 %가 AI 생성”이라는 사례도 나왔지만, 그 이후 유지보수·확장 과정에서 어려움을 겪고 있다는 보고도 존재합니다.</p>
<p>즉, 바이브 코딩은 ‘아이디어 → 빠르게 기능 확보’의 목적에는 적합하지만, ‘100 만 유저 이상’ 수준의 서비스 안정성 확보 단계에서는 그 효과가 제한적이라는 평가입니다.</p>
<h1 id="그렇다면-개발자와-기업은-어떻게-해야-하나">그렇다면 개발자와 기업은 어떻게 해야 하나?</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/ad998c81-41f6-4727-a83d-d69ae14ecba6/image.png" alt=""></p>
<p>Marcus의 글에서 시사되는 실전 과제를 요약하면 다음과 같습니다.</p>
<h3 id="바이브-코딩을-보완적-수단으로-활용">바이브 코딩을 보완적 수단으로 활용</h3>
<p>바이브 코딩이 “코딩을 완전히 대체”하는 방식이 되기보다는,</p>
<ul>
<li><p>빠른 프로토타이핑</p>
</li>
<li><p>아이디어 검증 (MVP)</p>
</li>
<li><p>반복 실험 및 초기 사용자 흐름 점검</p>
<p>  등에서 유리합니다. 하지만 이후에는 전통적 소프트웨어 엔지니어링 방식(설계·코드리뷰·테스트·운영)이 필수입니다.</p>
</li>
</ul>
<h3 id="코드-이해·설계-역량-유지">코드 이해·설계 역량 유지</h3>
<p>AI가 자동으로 코드를 생성하더라도 코드의 <strong>구조, 흐름, 성능, 보안</strong>을 이해할 수 있어야 합니다. Marcus는 “아마추어가 팀을 대체할 수 없다”는 이유로 이 부분을 강조합니다.</p>
<p>따라서 개발자는 프롬프트 입력만 할 뿐 아니라 생성된 코드를 검토하고 이해하는 역량을 유지해야 합니다.</p>
<h3 id="품질·유지보수·보안-체크리스트-마련">품질·유지보수·보안 체크리스트 마련</h3>
<p>특히 아래 이슈들에 대비해야 합니다.</p>
<ul>
<li><p>코드가 <strong>블랙박스화</strong>되어 내부가 보이지 않는 위험</p>
</li>
<li><p>버그, 보안취약점이 숨어 있을 가능성</p>
</li>
<li><p>AI가 생성한 코드가 조직·팀의 표준에 맞지 않을 수 있음</p>
<p>  논문에서도 “속도 대비 품질 트레이드오프”가 존재한다는 결과가 나왔습니다.</p>
</li>
</ul>
<h3 id="조직·프로세스-변화-대비">조직·프로세스 변화 대비</h3>
<p>바이브 코딩이 유행하면서 팀 구성·역할·프로세스도 변화하고 있습니다. 하지만 Marcus의 논점처럼 “기존 팀 + 숙련된 개발자” 구조는 아직 유효합니다.</p>
<p>기업은</p>
<ul>
<li>AI 코드 생성 도구를 도입하되</li>
<li>코드 리뷰 및 아키텍처 설계 역량을 갖춘 인재를 유지하고</li>
<li>AI가 만든 코드를 운영 가능한 상태로 전환할 수 있는 프로세스를 설계해야 합니다.</li>
</ul>
<h1 id="요약-및-결론">요약 및 결론</h1>
<hr>
<ul>
<li>바이브 코딩은 개발 방식에 꽤 큰 변화를 가져왔습니다.</li>
<li>하지만 Gary Marcus는 이 방식이 <strong>기존 코더 집단을 아마추어가 완전 대체한다는 기대는 현실적이지 않다</strong>고 지적합니다.</li>
<li>즉, “바이브 코딩 → 개발자 필요 없음”이라는 흐름은 과장된 면이 있습니다.</li>
<li>개발자, 스타트업, 기업 모두 이 변화를 맞아 <strong>속도만 좇기보다는 품질·이해·유지보수성</strong>을 동시에 고려해야 합니다.</li>
<li>따라서 바이브 코딩은 <strong>도구(tool)</strong>이지, <strong>체계(system)</strong> 그 자체가 아닙니다.</li>
<li>앞으로는 <em>AI가 생성한 코드 + 사람이 설계·운영하는 구조</em>가 표준이 될 가능성이 높습니다.</li>
</ul>
<h1 id="개인적인-내-생각">개인적인 내 생각</h1>
<hr>
<p>바이브 코딩을 둘러싼 논의는 단순히 “이 기술이 죽었는가, 살아 있는가”의 문제는 아닌 것 같습니다.</p>
<p>최근 “바이브 워킹(Vibe Working)”이라는 개념이 함께 언급되고 있는 것처럼, <strong>‘바이브’라는 흐름 자체</strong>는 여전히 유효합니다.</p>
<p>이는 단순히 코드를 자동으로 생성하는 기술적 이슈를 넘어, <strong>일의 방식이 변화하고 있음을 상징하는 키워드</strong>로 볼 수 있습니다.</p>
<p>따라서 “바이브 코딩이 죽었다”는 표현은, 단어 의미 그대로 받아들일 문제는 아닙니다.</p>
<p>오히려 기술의 초점이 <strong>‘코드를 빠르게 만드는 것’에서 ‘AI와 함께 일하는 방식’</strong>으로 옮겨가고 있다고 생각합니다.</p>
<p>둘째로, 바이브 코딩이 효율적이라 하더라도, <strong>AI가 생성한 코드를 이해하고 유지보수하려는 태도</strong>는 여전히 필요합니다.</p>
<p>AI가 자동으로 만들어주는 코드를 “그냥 작동하니까 됐다”로 끝내면, 결국 시스템의 근본을 잃게 됩니다.</p>
<p>결국 진짜 실력은 <strong>AI가 만든 코드를 해석하고, 수정하며, 책임질 수 있는 능력</strong>에서 드러납니다.</p>
<p>마지막으로, 바이브 코딩 시대에는 <strong>올바른 설계 역량</strong>이 더욱 중요해졌습니다.</p>
<p>AI는 요청한 대로 코드를 만들어주지만, <strong>무엇을 요청할지, 어떤 구조로 설계할지</strong>는 전적으로 사람의 몫입니다.</p>
<p>즉, 코드를 치는 능력보다 <strong>문제를 정의하고 구조화하는 능력</strong>, 그리고 <strong>AI를 협업 파트너로 이끌 수 있는 설계 감각</strong>이 새로운 경쟁력이 될 것입니다.</p>
<p>결국, 바이브 코딩은 “AI가 인간을 대체하는 시대”의 상징이 아니라,</p>
<blockquote>
<p>인간이 AI를 통해 더 나은 방식으로 사고하고 일하는 시대의 시작</p>
</blockquote>
<p>이라고 생각합니다.</p>
<h1 id="reference">Reference</h1>
<hr>
<ul>
<li><a href="https://garymarcus.substack.com/p/is-vibe-coding-dying">https://garymarcus.substack.com/p/is-vibe-coding-dying</a></li>
<li><a href="https://en.wikipedia.org/wiki/Vibe_coding">https://en.wikipedia.org/wiki/Vibe_coding</a></li>
<li><a href="https://arxiv.org/abs/2509.12491">https://arxiv.org/abs/2509.12491</a></li>
<li><a href="https://www.businessinsider.com/vibe-coding-wont-replace-software-engineers-openai-research-bob-mcgrew-2025-6">https://www.businessinsider.com/vibe-coding-wont-replace-software-engineers-openai-research-bob-mcgrew-2025-6</a></li>
<li><a href="https://arxiv.org/abs/2510.00328">https://arxiv.org/abs/2510.00328</a></li>
<li><a href="https://www.chosun.com/economy/tech_it/2025/10/20/KNE65PSSNBHOTHFHZULHDMVRIE/">https://www.chosun.com/economy/tech_it/2025/10/20/KNE65PSSNBHOTHFHZULHDMVRIE/</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Python 환경 관리 가이드]]></title>
            <link>https://velog.io/@playername_ltt/%ED%97%B7%EA%B0%88%EB%A6%AC%EB%8A%94-Python-%ED%99%98%EA%B2%BD-%EA%B4%80%EB%A6%AC-%EA%B0%80%EC%9D%B4%EB%93%9C</link>
            <guid>https://velog.io/@playername_ltt/%ED%97%B7%EA%B0%88%EB%A6%AC%EB%8A%94-Python-%ED%99%98%EA%B2%BD-%EA%B4%80%EB%A6%AC-%EA%B0%80%EC%9D%B4%EB%93%9C</guid>
            <pubDate>Tue, 21 Oct 2025 05:49:20 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/playername_ltt/post/bfafbf15-8d87-42f8-a56e-98c094debba1/image.png" alt=""></p>
<p>Python을 조금만 깊게 써보면, 누구나 한 번쯤 이런 생각을 한다.</p>
<blockquote>
<p>“venv, pipenv, pyenv... 대체 뭐가 다르고, 뭐부터 써야 하지?”</p>
</blockquote>
<p>이 세 가지는 이름이 비슷해서 헷갈렸었는데, <strong>역할이 전혀 달랐다.</strong></p>
<blockquote>
<p>pyenv → Python 버전 관리</p>
<p><code>venv</code> / <code>pipenv</code> → 가상환경(패키지 격리)을 관리</p>
</blockquote>
<h1 id="pyenv">pyenv</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/1300e03f-7208-4971-b657-aee273c91b7f/image.png" alt=""></p>
<p><code>pyenv</code>는 여러 버전의 Python을 설치하고 전환할 수 있는 <strong>버전 관리 도구</strong>이다.</p>
<p>프로젝트마다 다른 Python 버전을 써야 할 때 필수적이다.</p>
<p><a href="https://github.com/pyenv/pyenv">https://github.com/pyenv/pyenv</a></p>
<p>예를 들어,</p>
<ul>
<li>프로젝트 A: Python 3.8</li>
<li>프로젝트 B: Python 3.12</li>
</ul>
<p>이런 경우 시스템 Python 하나로는 관리하기 어렵다.</p>
<p>이럴 때 사용하는 게 <code>pyenv</code>이다. 이걸 활용하면 쉽게 버전 전환이 가능하다.</p>
<h3 id="주요-명령어">주요 명령어</h3>
<pre><code class="language-bash">pyenv install 3.12.3  # 특정 버전 설치
pyenv global 3.12.3   # 전역 기본 버전 지정
pyenv local 3.10.9    # 현재 디렉토리에만 적용</code></pre>
<p>자세한 건 공식 문서나 블로그를 참고하면 된다.</p>
<h3 id="언제-쓰면-좋은가">언제 쓰면 좋은가</h3>
<ul>
<li>여러 버전의 Python을 테스트하거나 유지보수해야 할 때</li>
<li>시스템 Python을 건드리고 싶지 않을 때</li>
<li>macOS, Ubuntu 등에서 Python 버전 충돌이 자주 날 때</li>
</ul>
<h1 id="venv">venv</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/0685d4f3-6452-4c0a-bba2-fe5b45ecf4f8/image.png" alt=""></p>
<p><code>venv</code>는 <strong>Python 표준 라이브러리에 포함된 가상환경 도구</strong>다.</p>
<p>쉽게 말해, “프로젝트마다 패키지를 따로 관리”할 수 있게 해준다.</p>
<p><a href="https://docs.python.org/3/library/venv.html">https://docs.python.org/3/library/venv.html</a></p>
<p>예를 들어,</p>
<ul>
<li>프로젝트 A에서 <code>Django 3.2</code></li>
<li>프로젝트 B에서 <code>Django 5.0</code></li>
</ul>
<p>이렇게 버전이 다를 때, <code>venv</code>를 쓰면 서로 영향을 주지 않는다.</p>
<h3 id="기본-사용법">기본 사용법</h3>
<pre><code class="language-bash">python -m venv venv         # 가상환경 생성
source venv/bin/activate    # macOS / Linux
venv\Scripts\activate       # Windows
deactivate                  # 비활성화</code></pre>
<h3 id="장점">장점</h3>
<ul>
<li>Python 3.3 이상 기본 내장 (추가 설치 불필요)</li>
<li>가볍고 단순함</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li><code>requirements.txt</code>를 직접 관리해야 함</li>
<li>의존성 잠금(lock) 기능이 없음</li>
</ul>
<h1 id="pipenv">pipenv</h1>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/1bcf63b2-36b5-4aac-a3b4-bb525a538a2a/image.png" alt=""></p>
<p><code>pipenv</code>는 <code>venv</code> + <code>pip</code> + 의존성 관리(<code>Pipfile</code>)를 한 번에 해결하는 도구다.</p>
<p>즉, <strong>가상환경 생성부터 패키지 버전 고정까지 자동으로 관리</strong>해준다.</p>
<p><a href="https://pypi.org/project/pipenv/">https://pypi.org/project/pipenv/</a></p>
<p><a href="https://pipenv.pypa.io/en/latest/">https://pipenv.pypa.io/en/latest/</a></p>
<pre><code class="language-bash">pipenv install requests     # 패키지 설치 + 가상환경 자동 생성
pipenv shell                # 가상환경 진입
pipenv uninstall requests   # 패키지 제거
pipenv lock                 # 의존성 잠금 파일 생성</code></pre>
<h3 id="구조">구조</h3>
<ul>
<li><code>Pipfile</code> → 설치할 패키지 목록</li>
<li><code>Pipfile.lock</code> → 정확한 버전 고정 (재현 가능한 환경 보장)</li>
</ul>
<h3 id="장점-1">장점</h3>
<ul>
<li>의존성 관리 자동화</li>
<li>Python 버전 지정 가능 (<code>Pipfile</code> 내 <code>python_version</code>)</li>
<li>CI/CD 환경에서 재현성 보장</li>
</ul>
<h3 id="단점-1">단점</h3>
<ul>
<li>약간 느림</li>
<li>최근에는 <code>poetry</code>로 대체되는 추세</li>
</ul>
<h1 id="정리">정리</h1>
<hr>
<h2 id="비교-요약표">비교 요약표</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>pyenv</th>
<th>venv</th>
<th>pipenv</th>
</tr>
</thead>
<tbody><tr>
<td>주요 기능</td>
<td>Python 버전 관리</td>
<td>가상환경 관리</td>
<td>가상환경 + 패키지 관리</td>
</tr>
<tr>
<td>설치 필요</td>
<td>O</td>
<td>X (기본 내장)</td>
<td>O</td>
</tr>
<tr>
<td>관리 단위</td>
<td>Python 인터프리터</td>
<td>프로젝트</td>
<td>프로젝트</td>
</tr>
<tr>
<td>버전 고정 파일</td>
<td><code>.python-version</code></td>
<td><code>requirements.txt</code></td>
<td><code>Pipfile</code>, <code>Pipfile.lock</code></td>
</tr>
<tr>
<td>대체 도구</td>
<td>asdf, conda</td>
<td>virtualenv</td>
<td>poetry</td>
</tr>
</tbody></table>
<hr>
<h2 id="실제-조합-예시">실제 조합 예시</h2>
<table>
<thead>
<tr>
<th>상황</th>
<th>추천 조합</th>
</tr>
</thead>
<tbody><tr>
<td>단순 개인 프로젝트</td>
<td><code>venv</code></td>
</tr>
<tr>
<td>협업 / 회사 프로젝트</td>
<td><code>pipenv</code> 또는 <code>poetry</code></td>
</tr>
<tr>
<td>여러 Python 버전 병행</td>
<td><code>pyenv + venv</code></td>
</tr>
<tr>
<td>안정적이고 깔끔한 환경 구성</td>
<td><code>pyenv + pipenv</code></td>
</tr>
</tbody></table>
<h1 id="결론">결론</h1>
<hr>
<p>Python 환경 관리 도구는 다양하지만, 결국 목표는 하나다.</p>
<blockquote>
<p>“내 개발환경을 깨끗하게, 예측 가능하게 유지하기.”</p>
</blockquote>
<p>처음엔 <code>venv</code>만으로도 충분하다.</p>
<p>하지만 프로젝트가 커지고 협업이나 배포가 필요해지면, <code>pipenv</code>나 <code>poetry</code>로 확장해보자.</p>
<p>그리고 여러 버전의 Python을 다뤄야 한다면 <code>pyenv</code>를 꼭 함께 쓰는 걸 추천한다.</p>
<h1 id="개인적으로-추천하는-세팅">개인적으로 추천하는 세팅</h1>
<hr>
<blockquote>
<p>💡 pyenv + pipenv 조합</p>
</blockquote>
<ul>
<li><code>pyenv</code> → Python 버전 제어</li>
<li><code>pipenv</code> → 가상환경 및 패키지/의존성 관리</li>
<li><code>Pipfile</code> 기반이라 재현성과 협업 환경에 유리</li>
</ul>
<pre><code class="language-bash">pyenv install 3.12.3
pyenv local 3.12.3
pipenv install requests
pipenv shell</code></pre>
<p>이 조합은 <strong>버전 충돌 걱정 없이</strong>,</p>
<p><strong>패키지 관리와 환경 격리까지 한 번에 해결할 수 있는</strong> 가장 현실적인 Python 개발 세팅이라고 생각한다.</p>
<p>질문과 피드백은 언제나 환영입니다. :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[타임 박싱에 대해 알고 있나요?]]></title>
            <link>https://velog.io/@playername_ltt/%ED%83%80%EC%9E%84-%EB%B0%95%EC%8B%B1%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EA%B3%A0-%EC%9E%88%EB%82%98%EC%9A%94</link>
            <guid>https://velog.io/@playername_ltt/%ED%83%80%EC%9E%84-%EB%B0%95%EC%8B%B1%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EA%B3%A0-%EC%9E%88%EB%82%98%EC%9A%94</guid>
            <pubDate>Tue, 14 Oct 2025 08:11:28 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/playername_ltt/post/f79e8853-154c-403a-94a9-6bd698e6be5c/image.png" alt=""></p>
<h1 id="introduce">Introduce</h1>
<hr>
<p>이건 대문자 J인 제가 놀 시간을 확실하게 갖고 싶어 도입하게 된 시간 관리법에 대한 이야기입니다.</p>
<p>많이들 사용하는 TODO리스트는 오늘 해야 할 일을 지정해줄 뿐, 이를 효율적으로 처리하기 위한 시간배분을 확인하기 쉽지 않습니다.</p>
<p>오늘 해야 할 일이 많지만, 왜 정작 중요한 일은 잘 끝내지 못할까? 계획을 세워도 미뤄지고, 메일·카톡·알람에 정신이 팔린 경험이 있을 거예요. 이럴 때 <strong>‘타임 박싱’</strong>이라는 방법을 쓰면 시간과 집중력을 동시에 관리할 수 있습니다.</p>
<h1 id="⌛타임박싱이란">⌛타임박싱이란?</h1>
<hr>
<p><strong>타임 박싱</strong>은 ‘이 일은 25분만 하고 쉬자’, ‘오전 10시~11시는 블로그 글쓰기’처럼 ‘얼마 동안만 한다’고 미리 시간 단위를 정해두고 실행하는 방법입니다. </p>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/ed72a419-dbd4-44bb-a441-87e4e9af9f33/image.png" alt=""></p>
<p>오늘의 목표를 설정하고, 이를 시각화하여 시간 계획을 작성하는 방법입니다.</p>
<p>일반적으로 사용하는 TODO 리스트보다, 시간이라는 강제력이 생겨 집중력이 훨씬 높아집니다.</p>
<p>두 번째 기대 효과는 <strong>‘개인 역량 파악’</strong>입니다.</p>
<p>계획했던 타임 박스와 실제 소요된 시간을 비교하면, 어떤 종류의 업무를 할 때 내가 <strong>예상보다 여유로운지</strong>, 혹은 <strong>시간이 부족한지</strong>를 수치로 확인할 수 있습니다. 이를 통해 자신의 일 처리 속도와 집중 패턴을 객관적으로 파악하고, 이후 일정 계획을 더욱 현실적으로 조정할 수 있습니다.</p>
<p>이 기법은 개인적으로만 사용해도 좋지만, 만약 팀원과 함께 ‘타임 박싱 캘린더’를 공유하게 된다면 효율적으로 업무시간을 분배하여 작업할 수 있습니다. 일정과 관련된 커뮤니케이션을 최소화할 수 있기 때문이죠.</p>
<h1 id="📜적용-방법">📜적용 방법</h1>
<hr>
<p>기본적으로 ‘타임 박싱’ 기법을 적용해보는 것은 간단합니다. 저는 아래와 같은 순서로 적용해보는 것을 추천합니다.</p>
<ol>
<li><strong>작업 리스트 작성 :</strong> 오늘/이번 주 해야 할 일들을 모두 적기</li>
<li><strong>시간 단위 설정 :</strong> 15분, 30분, 1시간 등 집중할 시간 블록을 정하기</li>
<li><strong>작업 배치 :</strong> 중요한 일부터, 집중도가 필요한 일부터 시간 블록에 배치</li>
<li><strong>시간 알람 활용 :</strong> 타이머, 스마트폰, PC 앱 등을 활용해 시간 초과 방지 (뽀모도로 타이머 등)</li>
<li><strong>회고 &amp; 조정 :</strong> 실제 걸린 시간과 계획 시간 비교, 다음 계획에 반영</li>
</ol>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/8a932d56-3e92-4bdd-9f4b-dec1d99981d8/image.png" alt=""></p>
<p>이건 제 예시입니다. 구글 캘린더를 쓰는 것을 추천드립니다. 휴대폰, 노트북과 연동하기도 편하거든요.</p>
<p>우선 저는 업무는 ‘일 단위’로, 사이드 프로젝트 등 자기계발 활동은 ‘주 단위’로 시간계획을 작성합니다. 하달받은 업무는 리스트업해 두었다가 여유시간에 타임박싱합니다.</p>
<p>개인별로 편차는 있겠지만 위와 같은 순서로 적용해본다면 괜찮을 것이라 생각합니다. 물론 처음엔 어색하고 시간계획을 작성하는 것이 쉽지 않을 수 있겠지만요.</p>
<h2 id="⚠️-타임-박싱의-함정과-팁">⚠️ 타임 박싱의 함정과 팁</h2>
<hr>
<ul>
<li>너무 빡빡하게 짜면 금방 지칩니다. 완벽주의는 버리고, 70% 완성으로도 괜찮다는 마음가짐이 필요합니다.</li>
<li>예기치 않은 일이 생길 수 있으니, 하루 중 한두 블록은 <strong>‘버퍼(예비 시간)’</strong>으로 남겨두세요.</li>
<li>‘집중 블록’ 사이에는 반드시 <strong>짧은 휴식</strong>을 넣으세요. 25분 일하고 5분 쉬는 <strong>포모도로 방식</strong>이 대표적입니다.</li>
</ul>
<h1 id="마무리">마무리</h1>
<hr>
<p>타임 박싱은 단순히 시간을 쪼개는 기술이 아닙니다. 말 그대로 <strong>‘시간을 다루는 습관’을 만드는 방법</strong>이에요.</p>
<p>처음에는 조금 귀찮게 느껴지실 수도 있어요. “이걸 꼭 다 적어야 하나?” 하고 생각하실 수도 있고요.</p>
<p>하지만 일주일만 꾸준히 해보시면, 하루의 밀도가 확실히 달라지는 걸 느끼실 겁니다.</p>
<p>예전에는 시간에 쫓기면서 허둥지둥 일하셨다면, 이제는 하루를 <strong>직접 설계하고 주도하는 느낌</strong>을 받으실 수 있어요. 머릿속에서 엉켜 있던 해야 할 일들이 퍼즐처럼 딱 맞아떨어지는 경험도 하시게 될 거고요.</p>
<p>타임 박싱은 단순히 열심히 하는 게 아니라, <strong>현명하게 일하는 습관</strong>을 만드는 방법입니다.</p>
<p>오늘 하루도 그냥 흘려보내지 마시고, 내 시간을 직접 디자인해보세요. 엄청 추천드립니다. :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[트러블슈팅] PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException 해결]]></title>
            <link>https://velog.io/@playername_ltt/%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-PKIX-path-building-failed-sun.security.provider.certpath.SunCertPathBuilderException-%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@playername_ltt/%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-PKIX-path-building-failed-sun.security.provider.certpath.SunCertPathBuilderException-%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Mon, 29 Sep 2025 06:22:28 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/playername_ltt/post/29f81ed3-aafa-405f-a4b3-7fa1b2d082db/image.png" alt=""></p>
<h1 id="에러-분석">에러 분석</h1>
<hr>
<p>https 통신을 위해 로컬에서 자체 서명 인증서를 사용하여 개발하고 있었다.</p>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/53a7dc5f-37aa-41dd-a372-986e8370f78a/image.png" alt=""></p>
<p><code>WebClient</code>로 다른 서버를 호출하니 위와 같이 에러가 발생했다.</p>
<pre><code>javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:378)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:321)
    at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:316)
    at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.checkServerCerts(CertificateMessage.java:1351)
    at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.onConsumeCertificate(CertificateMessage.java:1226)
    at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.consume(CertificateMessage.java:1169)
    at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:396)
&lt;28 folded frames&gt;</code></pre><p>여기서 핵심적으로 볼 파트는 여기다.</p>
<blockquote>
<p>unable to find valid certification path to requested target</p>
</blockquote>
<p>요청한 서버의 SSL 인증서 검증 과정에서 서버 인증서를 신뢰할 수 없어서 발생하는 문제이다.</p>
<p>즉, WebClient가 요청을 보내는 대상 서버의 SSL 인증서가 신뢰할 수 있는 <code>CA(Certificate Authority)</code>로 서명되어 있지 않거나, 로컬 JVM의 <code>cacerts</code>(truststore)에 해당 인증서 체인이 등록되어 있지 않아서 생긴 문제이다.</p>
<h1 id="문제-해결">문제 해결</h1>
<hr>
<pre><code class="language-java">@Configuration
@RequiredArgsConstructor
public class WebClientConfig {
    private final String API_KEY_HEADER = &quot;X-API-KEY&quot;;

    @Bean(name = &quot;formatWebClient&quot;)
    public WebClient formatWebClient() throws SSLException {
            // --- 이부분 추가 ---
        SslContext sslContext = SslContextBuilder
                .forClient()
                .trustManager(InsecureTrustManagerFactory.INSTANCE)
                .build();

        HttpClient httpClient = HttpClient.create()
                .secure(ssl -&gt; ssl.sslContext(sslContext));
            // ---

        return WebClient.builder()
                .baseUrl(&quot;&lt;Other Server&#39;s baseURL&gt;&quot;)
                .defaultHeader(API_KEY_HEADER, apiKeyProvider.getEncryptedApiKey())
                            // --- 이부분 추가 ---
                .clientConnector(new ReactorClientHttpConnector(httpClient))
                            // ---
                .filter((request, next) -&gt; next.exchange(request)
                        .flatMap(response -&gt; {
                            if (response.statusCode().isError()) {
                                return response.bodyToMono(String.class)
                                        .defaultIfEmpty(&quot;Unknown error&quot;)
                                        .flatMap(body -&gt; {
                                            log.error(&quot;API Error Response: {}&quot;, body);
                                            return Mono.error(new RuntimeException(&quot;INTERNAL_SERVER_ERROR&quot;));
                                        });
                            } else {
                                return Mono.just(response);
                            }
                        })
                )
                .build();
    }
}</code></pre>
<p>위와 같이 <strong>WebClientConfig</strong>를 설정해주었다. 물론 권장되는 방법은 아니다.</p>
<pre><code class="language-java">SslContext sslContext = SslContextBuilder
        .forClient()
        .trustManager(InsecureTrustManagerFactory.INSTANCE)
        .build();

HttpClient httpClient = HttpClient.create()
        .secure(ssl -&gt; ssl.sslContext(sslContext));</code></pre>
<p>위의 코드는 코드레벨에서 SSL 검증을 무시하는 코드이다. Spring WebClient에 <code>HttpClient</code>를 커스터마이징해서 SSL 검증을 끈 것이다. 즉, 모든 SSL 인증서를 신뢰한다는 것이다.</p>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/8e12f57e-ba1d-4700-aab0-93706aa42358/image.png" alt=""></p>
<p>이런 코드가 보안상 좋을 리가 없다. 그래서 테스트 또는 내부망에서만 사용해야 한다.</p>
<p>호출할 서버가 현재 이 서버만 거쳐서(내부망에서만 이용) 이렇게 작성하고 끝내도 괜찮았다.</p>
<p>하지만 JAVA 인증서 저장소에 인증서를 등록하여 해결하고 싶다면</p>
<p><a href="https://ssow93.tistory.com/73">https://ssow93.tistory.com/73</a></p>
<p>위의 블로그 글을 참고해봐도 괜찮을 것 같다.</p>
<h1 id="reference">Reference</h1>
<hr>
<ul>
<li><a href="https://sentry.io/answers/how-to-solve-pkix-path-building-failed-error-in-java/">https://sentry.io/answers/how-to-solve-pkix-path-building-failed-error-in-java/</a></li>
<li><a href="https://spring.io/blog/2023/06/07/securing-spring-boot-applications-with-ssl">https://spring.io/blog/2023/06/07/securing-spring-boot-applications-with-ssl</a></li>
<li><a href="https://howtodoinjava.com/java/java-security/bypass-ssl-certificate-checking-java/">https://howtodoinjava.com/java/java-security/bypass-ssl-certificate-checking-java/</a></li>
<li><a href="https://www.sslcert.co.kr/guides/kb/47">https://www.sslcert.co.kr/guides/kb/47</a></li>
</ul>
<p>질문과 피드백은 언제나 환영입니다 :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[FastAPI를 배포하는 과정과 트러블슈팅]]></title>
            <link>https://velog.io/@playername_ltt/FastAPI%EB%A5%BC-%EB%B0%B0%ED%8F%AC%ED%95%98%EB%8A%94-%EA%B3%BC%EC%A0%95%EA%B3%BC-%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85</link>
            <guid>https://velog.io/@playername_ltt/FastAPI%EB%A5%BC-%EB%B0%B0%ED%8F%AC%ED%95%98%EB%8A%94-%EA%B3%BC%EC%A0%95%EA%B3%BC-%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85</guid>
            <pubDate>Wed, 24 Sep 2025 05:08:20 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/playername_ltt/post/f3df2808-7aa6-4822-8aad-f2ef757a1ffc/image.png" alt=""></p>
<h1 id="introduce">Introduce</h1>
<hr>
<p>큰 기능이 들어가지 않고, 한번 개발이 끝나면 수정이 크게 들어가지 않는 기능을 수행하는 서버를 <code>FastAPI</code>를 이용하여 만들기로 했다. (사실 파이썬으로 백엔드 서버를 처음 개발하게 됐기에 쉬운거부터 접근한게 크다.)</p>
<p>하지만 배포 과정에서 이런저런 이슈 사항들이 있었다.</p>
<h1 id="1차배포-테스트">1차배포 테스트</h1>
<hr>
<p>우선 현재 프로젝트에서는 <code>Docker</code>를 적용하지 않았다. 그래서 일단은 <code>Docker</code> 이미지로 배포하는 방법은 배제하고 생각하기 시작했다.</p>
<p>그러면 다음으로는 어떻게 해야되지.</p>
<p>우선 기능이 똑바로 돌아가는 것을 로컬로는 확인했으니, 프로젝트 디렉토리를 통째로 서버에 업로드했다.</p>
<p><em>TMI: 나는  SSH툴로 Windows환경에서는 MobaXTerm을, Mac에서는 Termius를 주로 사용한다.</em></p>
<p>그다음 백그라운드에서 돌려봤다.</p>
<pre><code class="language-bash">nohup uvicorn app.main:app --ssl-keyfile=./key.pem --ssl-certfile=./cert.pem --host 0.0.0.0 --port 8000 &amp;</code></pre>
<p>잘 돌아가고, 호출 또한 잘된다. 외부에서 바로 호출을 해봐도, 내부의 다른 서버에서 호출을 해봐도 전부 다 잘 돌아가서 한시름을 놨는데, 뭔가 찝찝하다.</p>
<p>Java의 .jar파일처럼 실행 파일을 따로 만들어서 배포하는 것도 아니고, 디렉토리 통째로 업로드라니…</p>
<p>뭔가 탐탁치 않다.</p>
<h1 id="2차-방안-검토">2차 방안 검토</h1>
<hr>
<p>이 방법은 재배포할 일이 많이 없다지만 보안, 관리상 용이하지 않을 것 같았다. 하지만 큰 제약사항이 두 가지 있는데…</p>
<ol>
<li>현 프로젝트에서는 <code>Docker</code>를 사용하지 않아, 이거 하나때문에 <code>Docker</code>를 적용하기에는 너무 오버다.</li>
<li><code>FastAPI</code>는 <code>Springboot</code>처럼 따로 실행파일을 생성할 수 없다.</li>
</ol>
<p>그래서 우선은 <code>Docker</code>사용을 아예 배제하고 시작해본다. (선임께 얘기했더니 과하다고 컷하신건 비밀)</p>
<h2 id="pyinstaller로-실행파일을-말아볼까">PyInstaller로 실행파일을 말아볼까</h2>
<hr>
<p><a href="https://pyinstaller.org/en/stable/">PyInstaller Manual — PyInstaller 6.13.0 documentation</a></p>
<p>root directory에 <code>app.spec</code> 파일을 추가하고,</p>
<pre><code># -*- mode: python ; coding: utf-8 -*-

a = Analysis(
    [&#39;app/main.py&#39;],
    pathex=[],
    binaries=[],
    datas=[
        (&#39;&lt;Personal Route&gt;\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\jamo\\data\\*.json&#39;, &#39;jamo/data&#39;),
        (&#39;app/libs/g2pk/rules.txt&#39;, &#39;g2pk&#39;),
        (&#39;app/libs/g2pk/idioms.txt&#39;, &#39;g2pk&#39;),
        (&#39;app/libs/g2pk/table.csv&#39;, &#39;g2pk&#39;),
        (&#39;cert.pem&#39;, &#39;.&#39;),
        (&#39;key.pem&#39;, &#39;.&#39;),
        (&#39;.env&#39;, &#39;.&#39;)
    ],
    hiddenimports=[
        &#39;jamo&#39;,
        &#39;jamo.jamo&#39;,
        &#39;jamo.compat&#39;,
        &#39;jamo.data&#39;
    ],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    noarchive=False,
    optimize=0,
)
pyz = PYZ(a.pure)

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.datas,
    [],
    name=&#39;app&#39;,
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    upx_exclude=[],
    runtime_tmpdir=None,
    console=True,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
)
</code></pre><p>파일 생성 후, 파일 내용 설정</p>
<pre><code class="language-bash">pyinstaller app.spec</code></pre>
<p><code>/dist/app.exe</code> 와 같이 <code>dist</code> directory에 <code>app.exe</code>가 생성되는데, 개발 서버는 <code>Linux</code>이다. 그래서 <code>PyInstaller</code>로 .sh파일을 생성하여 리눅스용 실행 파일을 만들어보려 했는데, <strong>Windows에선 리눅스 실행파일이 생성되지 않는다.</strong></p>
<p>그래서,</p>
<h2 id="linux용-실행파일이면-되겠다">Linux용 실행파일이면 되겠다</h2>
<hr>
<p><code>WSL</code>을 설치했다.</p>
<p>윈도우에서 리눅스를 실행하게 해주는 프로그램인데, 이후 과정을 요약해서 정리하자면</p>
<ol>
<li>wsl에서 venv로 가상환경 설치 후 pyinstall app.spec 실행 → 오류<ol>
<li>jamo.json 머시기를 못찾는다.</li>
</ol>
</li>
<li>app.spec 내부 datas 절대경로 리눅스로 수정<ol>
<li>python -c &quot;import jamo; print(jamo.<strong>file</strong>)&quot;</li>
<li>/home/aroms/stt-api-server-project-linux/.venv/lib/python3.12/site-packages/jamo/<strong>init</strong>.py</li>
<li>/home/aroms/stt-api-server-project-linux/.venv/lib/python3.12/site-packages/jamo/data/*.json</li>
</ol>
</li>
<li>pyinstall app.spec 실행</li>
<li>./dist에 ./app 생성됨</li>
</ol>
<p><code>./app.sh</code>실행이 잘됐다. 그러면 이제 이걸 개발서버에 올려보았지만</p>
<p><strong>“glibc 버전이 맞지 않아 실행이 안된다.”</strong></p>
<pre><code class="language-markdown">[PYI-1436136:ERROR] Failed to load Python shared library &#39;/tmp/_MEIFJuMgk/libpython3.12.so.1.0&#39;: dlopen: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.38&#39; not found (required by /tmp/_MEIFJuMgk/libpython3.12.so.1.0)</code></pre>
<p>개발서버에 설치된 Ubuntu 버전이 낮아 이슈로 할 수가 없다.</p>
<h2 id="docker를-써볼까">Docker를 써볼까?</h2>
<hr>
<p>오케이, 그렇다면 docker로 ubuntu 버전울 낮춰서 빌드해보자.</p>
<p>에러 메세지는 깜빡하여 복사하지 못했지만, docker에서 이미지 만드는 중에 모듈 데이터 일부가 경로 다름 이슈가 발생했다. 이 이슈를 해결한 <code>DockerFile</code>을 작성해보자.</p>
<h3 id="dockerfile-작성">DockerFile 작성</h3>
<pre><code># 실행파일 빌드
FROM ubuntu:20.04

ENV DEBIAN_FRONTEND=noninteractive

RUN apt update &amp;&amp; \
    apt install -y python3 python3-pip python3-venv build-essential

WORKDIR /app
COPY . /app

RUN python3 -m venv venv
RUN . venv/bin/activate &amp;&amp; \
    pip install --upgrade pip &amp;&amp; \
    pip install -r requirements.txt

RUN pip install jamo
RUN pip show jamo

RUN pip install pyinstaller &amp;&amp; \
    pyinstaller app.spec</code></pre><pre><code class="language-bash">docker build -t formatter-builder .

docker create --name extract formatter-builder
docker cp extract:/app/dist/app ./dist
docker rm extract

cd dist/app
chmod +x app
./app</code></pre>
<p>일단 결국 빌드되고 실행은 되었다. 하지만 install해뒀던 모든 라이브러리를 잊고야 만 실행파일…</p>
<pre><code class="language-bash">~$ ./app
Traceback (most recent call last):
  File &quot;app/main.py&quot;, line 1, in &lt;module&gt;
ModuleNotFoundError: No module named &#39;fastapi&#39;
[PYI-16153:ERROR] Failed to execute script &#39;main&#39; due to unhandled exception!</code></pre>
<h1 id="결론">결론</h1>
<hr>
<p>이 정도 오면 해결해도 다른 협업하는 분들이 배포 과정을 따라오고 이해하기 힘들다. 수정해야 될 상황이 발생하면 무조건 내가 해야 되는 상황이 발생하기 때문에,</p>
<p> 결국 프로젝트 디렉토리 전부 올려서 해결하는 것으로 결론지었다. (굉장히 맘에 안 들지만)</p>
<p>배포 과정을 그나마 단순화하기 위해 <code>shell</code> 실행파일을 작성했다.</p>
<pre><code class="language-bash">#!/bin/bash

echo &quot;&gt; pid Check&quot;
CURRENT_PID=$(lsof -i | grep uvicorn | grep -v grep | awk &#39;{print $2}&#39;)

echo &quot;&gt; current pid: $CURRENT_PID&quot;

if [ -z &quot;$CURRENT_PID&quot; ]; then
    echo &quot;&gt; No Process&quot;
else
    echo &quot;&gt; sudo kill $CURRENT_PID&quot;
    sudo kill &quot;$CURRENT_PID&quot;
    sleep 4
    if ps -p &quot;$CURRENT_PID&quot; &gt; /dev/null; then
        echo &quot;&gt; server process $CURRENT_PID is not dead&quot;
        echo &quot;&gt; sudo kill -9 $CURRENT_PID&quot;
        sudo kill -9 $CURRENT_PID
        sleep 2
    fi
fi

echo &quot;&gt; installing requirements...&quot;
pip install -r ./&lt;directory name&gt;/requirements.txt

echo &quot;&gt; server deploy&quot;

cd &lt;directory name&gt;/
nohup /home/&lt;username&gt;/.local/bin/uvicorn app.main:app \
  --ssl-keyfile=./key.pem \
  --ssl-certfile=./cert.pem \
  --host 0.0.0.0 \
  --port 8000 &gt; ../nohup.out 2&gt;&amp;1 &amp;
</code></pre>
<p>추후에는 미리 이슈사항들을 예측하고 버전과 배포 과정부터 제대로 체크를 한 후 작업에 들어갈 것 같다. 너무 단편적으로만 고려해서 너무 아쉬운 것 같다.</p>
<p><strong>질문과 피드백은 언제든 환영입니다. :)</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[실시간 미디어 통신을 위한 WebRTC]]></title>
            <link>https://velog.io/@playername_ltt/%EC%8B%A4%EC%8B%9C%EA%B0%84-%EB%AF%B8%EB%94%94%EC%96%B4-%ED%86%B5%EC%8B%A0%EC%9D%84-%EC%9C%84%ED%95%9C-WebRTC</link>
            <guid>https://velog.io/@playername_ltt/%EC%8B%A4%EC%8B%9C%EA%B0%84-%EB%AF%B8%EB%94%94%EC%96%B4-%ED%86%B5%EC%8B%A0%EC%9D%84-%EC%9C%84%ED%95%9C-WebRTC</guid>
            <pubDate>Wed, 24 Sep 2025 04:15:40 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/playername_ltt/post/c4dd311a-eca2-47d6-8475-55c9bc330830/image.png" alt=""></p>
<h1 id="webrtc란">WebRTC란</h1>
<hr>
<p>웹 애플리케이션과 사이트가 중간자 없이, 브라우저 간에 오디오나 영상 미디어를 포착하고 마음대로 스트림할 뿐 아니라, 임의의 데이터도 교환할 수 있도록 하는 기술.</p>
<p>쉽게 생각해보자면, 음성통화, 영상통화, P2P 파일 공유 등으로 활용될 수 있는 <strong>“다양한 플랫폼에서 가능한 실시간 커뮤니케이션 기술”</strong>이라고 정리할 수 있다.</p>
<p>대표적인 서비스로는 Google <strong>Meet, Discord, Zoom</strong> 등이 있다.</p>
<h2 id="주요-개념">주요 개념</h2>
<hr>
<h3 id="시그널링signalling">시그널링(Signalling)</h3>
<p>: P2P로 연결하기 위해 중계 서버가 필요한데, <strong>중걔서버로 클라이언트를 찾고 연결하는 과정</strong>을 시그널링 이라고 함</p>
<p>WebSocket, HTTP 등 알맞은 프로토콜을 개발자가 선택해서 구현하면 된다.</p>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/d4f89893-0ea3-465f-ae5a-74c570e35228/image.png" alt=""></p>
<p>두 클라이언트 사이에 Connection을 만들기 위해, 그 둘을 연결시키는 작업을 해주는 <code>시그널링 서버</code>가 필요하다.</p>
<h3 id="stun">STUN</h3>
<ul>
<li>Public IP를 알아내기 위한 <code>NAT</code>환경을 통과하기 위한 서버이다.</li>
<li>가볍고 빠름, 주로 P2P통신이 가능한 경우에 사용된다.</li>
</ul>
<h3 id="turn">TURN</h3>
<ul>
<li><code>STUN</code>만으로 부족할 때에 같은 목적으로 사용하는 서버이다.</li>
<li>방화벽이 강하거나, 복잡한 네트워크 환경 등에서 사용된다.</li>
</ul>
<h3 id="sdp-session-description-protocol">SDP (Session Description Protocol)</h3>
<p>: 연결하고자 하는 <code>Peer</code> 서로간의 미디어와 네트워크에 관한 정보를 이해하기 위해 사용되는 프로토콜. </p>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/966ae9f3-3404-4d1e-8eac-ea2058dc77e9/image.png" alt=""></p>
<p>아까 나온 이 그림은 각각 <code>offer</code>, <code>answer</code>, <code>candidate</code> 의 SDP를 사용하는 예제이다. (직접 구현해봐야 더 자세히 이해할 수 있을 것 같다.)</p>
<p><a href="https://peppo.tistory.com/203">https://peppo.tistory.com/203</a> 이 블로그를 참고해보면 좋을 것 같다.</p>
<h2 id="webrtc의-종류">WebRTC의 종류</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/9677a809-faf2-44d2-a654-e574e2f58346/image.png" alt=""></p>
<p>Mesh, MCU, SFU 방식이 존재한다.</p>
<h3 id="mesh">Mesh</h3>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/37008454-7c62-45b8-ab87-2a90b409204f/image.png" alt=""></p>
<ul>
<li>미디어 정보를 <code>Peer-to-Peer</code>로 클라이언트끼리 주고받는다.</li>
<li>서버는 <code>Peer</code>끼리 연결을 맺기 위한 시그널 정보를 주고받을 수 있게 돕는 역할을 수행한다.</li>
<li><code>Peer</code>끼리 정보를 주고받기 때문에 클라이언트의 부담이 크다.</li>
<li>1:1에서 가장 많이 사용되는 방식으로, 1:N, 1:M 도 가능하지만 <code>Peer</code>의 부담이 더 커질 것이다.</li>
<li>직접 연결로 데이터를 송수신하기 때문에 <strong>실시간 송수신이 보장된다.</strong></li>
</ul>
<h3 id="mcu-multipoint-control-unit">MCU (Multipoint Control Unit)</h3>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/2ddd77a2-871a-4c3b-aef1-a6f07204b1f9/image.png" alt=""></p>
<ul>
<li>서버에서 <strong>미디어 트래픽을 중계</strong>하는 방식</li>
<li>1개의 <code>upstream</code>과 1개의 <code>downstream</code>을 가지는 구조 → 클라이언트의 부담이 적다.</li>
<li>서버에서 <code>Peer</code>의 스트림을 모아 인코딩 &amp; 디코딩 모두를 하기 때문에 서버 성능이 받쳐줘야 한다. 또한 <strong>실시간 송수신 보장이 어려울 수 있다.</strong></li>
<li><code>Peer</code>의 부하가 가장 적음 → 당연히 서버의 부하는 그만큼 커진다.</li>
<li>N:M 연결에서 효율적이다.</li>
<li>미디어 서버가 필수적으로 필요하다.</li>
</ul>
<h3 id="sfu-selective-forwarding-unit">SFU (Selective Forwarding Unit)</h3>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/5652b6f8-4b9c-4001-b49a-acbbd3f20aa6/image.png" alt=""></p>
<ul>
<li>서버에서 <strong>미디어 트래픽을 중계</strong>하는 방식</li>
<li>1개의 <code>upstream</code>과 N개의 <code>downstream</code>을 가지는 구조</li>
<li>서버가 미디어 트래픽을 중계해주기 때문에 모든 Connection에 대해 클라이언트가 관리할 필요 X → <strong>클라이언트의 부하는 줄고, 서버의 부하는 늘어남</strong></li>
<li><code>Mesh 방식</code>보다는 실시간성이 떨어질 수 있지만, P2P에 걸맞는 <strong>실시간 송수신이 보장된다.</strong></li>
<li><code>Mesh  방식</code>보다 N:M 연결에 있어서 클라이언트의 부하가 줄긴 했지만, 각각의 <code>downstream</code>을 개별적으로 유지해야 하기 때문에, <strong>여전히 클라이언트에 부담이 존재한다.</strong></li>
<li>미디어 서버가 필수적으로 필요하다.</li>
</ul>
<p><code>Mesh</code>방식만으로는 클라이언트가 늘어날 수록 과부하가 기하급수적으로 많이 늘어났기 때문에 SFU와 MCU가 등장하였고, 요구사항에 맞춰 적절하게 활용할 필요성이 있다.</p>
<p>실시간성이 가장 최우선적으로 중요하다면 <code>SFU</code>를, 데이터를 중간에 가공해야 한다면 <code>MCU</code>를 활용해보는 것이 좋을 수 있다. 연결할 클라이언트가 적다면 단순 <code>P2P</code> 방식도 훌륭한 판단일 수 있다.</p>
<h1 id="reference">Reference</h1>
<hr>
<ul>
<li><a href="https://developer.mozilla.org/ko/docs/Web/API/WebRTC_API">https://developer.mozilla.org/ko/docs/Web/API/WebRTC_API</a></li>
<li><a href="https://webrtc.org/?hl=ko">https://webrtc.org/?hl=ko</a></li>
<li>그 외 기술 블로그들</li>
</ul>
<p>오류가 있거나, 잘못 기재된 부분이 있다면 피드백 부탁드립니다 :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[FastAPI 서버 보안 강화: 무작위 스캔 공격 대응과 개선 기록]]></title>
            <link>https://velog.io/@playername_ltt/FastAPI-%EC%84%9C%EB%B2%84-%EB%B3%B4%EC%95%88-%EA%B0%95%ED%99%94-%EB%AC%B4%EC%9E%91%EC%9C%84-%EC%8A%A4%EC%BA%94-%EA%B3%B5%EA%B2%A9-%EB%8C%80%EC%9D%91%EA%B3%BC-%EA%B0%9C%EC%84%A0-%EA%B8%B0%EB%A1%9D</link>
            <guid>https://velog.io/@playername_ltt/FastAPI-%EC%84%9C%EB%B2%84-%EB%B3%B4%EC%95%88-%EA%B0%95%ED%99%94-%EB%AC%B4%EC%9E%91%EC%9C%84-%EC%8A%A4%EC%BA%94-%EA%B3%B5%EA%B2%A9-%EB%8C%80%EC%9D%91%EA%B3%BC-%EA%B0%9C%EC%84%A0-%EA%B8%B0%EB%A1%9D</guid>
            <pubDate>Mon, 07 Jul 2025 02:41:34 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/playername_ltt/post/e88435b9-60e5-4777-8307-04c7d360e761/image.png" alt=""></p>
<h1 id="fastapi-서버-보안-강화-기록-🛡️">FastAPI 서버 보안 강화 기록 🛡️</h1>
<hr>
<h2 id="1-문제-상황">1. 문제 상황</h2>
<hr>
<p>최근 FastAPI 서버 로그에서 아래와 같은 이상 요청들이 다수 발견되었다.</p>
<pre><code class="language-bash">2025-06-24 01:37:46,904 - app_logger - INFO - ENDPOINT: GET /
2025-06-24 01:37:46,906 - app_logger - INFO - RESPONSE: {&quot;detail&quot;:&quot;Not Found&quot;}
2025-06-24 03:48:44,430 - app_logger - INFO - ENDPOINT: GET /login
2025-06-24 03:48:44,431 - app_logger - INFO - RESPONSE: {&quot;detail&quot;:&quot;Not Found&quot;}
2025-06-24 03:48:45,208 - app_logger - INFO - ENDPOINT: GET /ab2g
2025-06-24 03:48:45,210 - app_logger - INFO - RESPONSE: {&quot;detail&quot;:&quot;Not Found&quot;}
</code></pre>
<p>이러한 요청은 <strong>무작위 스캔 공격 (Random Scan Attack)</strong> 의 일종으로, 공격자가 서버에 존재할 법한 다양한 엔드포인트로 무작위 요청을 보내 취약점을 탐색하는 시도다.</p>
<p>개발서버 자체의 설정을 수정하는 건 부담스러웠고, 우선은 <strong>FastAPI 서버 레벨</strong>에서 이러한 공격에 대한 방어 조치를 적용하기로 했다.</p>
<hr>
<h2 id="2-조치사항-리스트업-✅">2. 조치사항 리스트업 ✅</h2>
<hr>
<h3 id="2-1-서버-간-통신-jwt-→-api-key-인증-방식-전환">2-1. 서버 간 통신: JWT → API Key 인증 방식 전환</h3>
<ul>
<li>기존에는 서버 간 통신도 무조건 JWT를 사용했지만, 현재 시스템에서는 사용자 인증이 필요 없는 <strong>서버 간 단순 인증</strong>만으로 충분하다.</li>
<li><strong>API Key + AES 암호화</strong> 방식으로 인증 방식을 개선함.</li>
</ul>
<p>👉 관련 내용: <a href="https://velog.io/@playername_ltt/%EC%84%9C%EB%B2%84%EA%B0%84-%ED%86%B5%EC%8B%A0-%EC%9D%B8%EC%A6%9D-%EB%B0%A9%EC%8B%9D-API-Key">서버간 통신 인증 방식 : API Key</a></p>
<hr>
<h3 id="2-2-배포-쉘스크립트-개선">2-2. 배포 쉘스크립트 개선</h3>
<ul>
<li>기존에는 <code>uvicorn</code> 실행 시 <code>-host 0.0.0.0</code> 설정으로 <strong>외부에서도 접근 가능한 상태</strong>였다.</li>
<li>실제로는 <strong>같은 서버 인스턴스 내의 Spring 서버만 요청</strong>을 보내기 때문에 <code>127.0.0.1</code> 로 수정하여 <strong>로컬 접속만 허용</strong>하도록 변경했다.</li>
</ul>
<pre><code class="language-bash">#!/bin/bash

PYTHON=&lt;파이썬 경로&gt;

echo &quot;&gt; FastAPI 서버 프로세스 확인&quot;
CURRENT_PID=$(lsof -i | grep python3 | grep -v grep | awk &#39;NR==2 {print $2}&#39;)

echo &quot;&gt; 현재 PID: $CURRENT_PID&quot;

if [ -z &quot;$CURRENT_PID&quot; ]; then
    echo &quot;&gt; 프로세스 없음&quot;
else
    echo &quot;&gt; 프로세스 종료 시도: $CURRENT_PID&quot;
    sudo kill &quot;$CURRENT_PID&quot;
    sleep 4
    if ps -p &quot;$CURRENT_PID&quot; &gt; /dev/null; then
        echo &quot;&gt; 프로세스가 종료되지 않아 강제 종료&quot;
        sudo kill -9 &quot;$CURRENT_PID&quot;
        sleep 2
    fi
fi

echo &quot;&gt; 패키지 설치&quot;
$PYTHON -m pip install -r ./&lt;Directory Path&gt;/requirements.txt

echo &quot;&gt; 서버 재시작&quot;
cd &lt;Directory Path&gt;/
nohup $PYTHON -m uvicorn app.main:app \
  --ssl-keyfile=./&lt;ssl-keyfile.pem&gt; \
  --ssl-certfile=./&lt;ssl-certfile.pem&gt; \
  --host 127.0.0.1 \
  --port 8000 &gt; ../nohup.out 2&gt;&amp;1 &amp;
</code></pre>
<p>✅ 개선 효과:</p>
<ul>
<li>외부 공격자가 IP와 포트만 알아내도 접근 가능한 상태 → 내부 접근만 가능하도록 변경</li>
</ul>
<hr>
<h3 id="2-3-swagger-문서-비활성화">2-3. Swagger 문서 비활성화</h3>
<ul>
<li>기본적으로 FastAPI는 <code>/docs</code> 또는 <code>/redoc</code> 경로로 API 문서를 제공한다.</li>
<li>공격자는 해당 문서만으로도 API 엔드포인트를 쉽게 유추할 수 있다.</li>
<li><strong>배포 환경에서는 Swagger 문서를 완전히 비활성화</strong>함.</li>
</ul>
<pre><code class="language-python">from fastapi import FastAPI

app = FastAPI(
    docs_url=None,
    redoc_url=None,
    openapi_url=None
)
</code></pre>
<p>✅ 개선 효과:</p>
<ul>
<li>외부인이 Swagger 문서를 통해 API 정보를 탐색하는 위험 차단</li>
</ul>
<hr>
<h3 id="2-4-로깅-미들웨어-개선-ip-주소-포함">2-4. 로깅 미들웨어 개선 (IP 주소 포함)</h3>
<ul>
<li>기존 로깅에는 <strong>요청자의 IP 정보</strong>가 빠져 있어, 누가 요청했는지 알 수 없었다.</li>
<li><strong>요청자의 IP, 포트 정보</strong>까지 함께 로깅하도록 개선했다.</li>
</ul>
<pre><code class="language-python">from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import JSONResponse
from starlette.background import BackgroundTask
from starlette.types import Message
from app.utils.log_util import logger
import traceback

class LogMiddleware(BaseHTTPMiddleware):
    def entry_log_info(self, endpoint: str):
        logger.info(f&quot;REQUEST: {endpoint}&quot;)

    def end_log_info(self, res_body: bytes):
        logger.info(f&quot;RESPONSE: {res_body.decode(errors=&#39;ignore&#39;)}&quot;)

    async def set_body(self, request: Request, body: bytes):
        async def receive() -&gt; Message:
            return {&quot;type&quot;: &quot;http.request&quot;, &quot;body&quot;: body}
        request._receive = receive

    async def dispatch(self, request: Request, call_next):
        req_body = await request.body()
        await self.set_body(request, req_body)

        # IP + 포트 + 요청 경로
        endpoint = f&quot;[{request.client.host}:{request.client.port}] {request.method} {request.url.path}&quot;
        self.entry_log_info(endpoint)

        try:
            response = await call_next(request)
            res_body = b&quot;&quot;
            async for chunk in response.body_iterator:
                res_body += chunk

            task = BackgroundTask(self.end_log_info, res_body)

            return Response(
                content=res_body,
                status_code=response.status_code,
                headers=dict(response.headers),
                media_type=response.media_type,
                background=task,
            )
        except Exception as e:
            logger.error(f&quot;EXCEPTION OCCURRED: {type(e).__name__}: {str(e)}&quot;)
            logger.debug(traceback.format_exc())

            error_response = {&quot;detail&quot;: &quot;Internal Server Error&quot;, &quot;exception&quot;: str(e)}
            res_body = JSONResponse(status_code=500, content=error_response)
            task = BackgroundTask(self.end_log_info, res_body.body)
            res_body.background = task
            return res_body
</code></pre>
<p>✅ 개선 효과:</p>
<ul>
<li>요청자 IP 기록 → 문제 발생 시 빠른 추적 가능</li>
<li>예외 상황까지 상세 로깅</li>
</ul>
<hr>
<h2 id="3-최종-정리-📝">3. 최종 정리 📝</h2>
<hr>
<table>
<thead>
<tr>
<th>조치</th>
<th>내용</th>
<th>효과</th>
</tr>
</thead>
<tbody><tr>
<td>✅ API Key 인증</td>
<td>서버 간 통신에 JWT 대신 API Key 적용</td>
<td>불필요한 JWT 제거, 간편 인증</td>
</tr>
<tr>
<td>✅ Host 제한</td>
<td>0.0.0.0 → 127.0.0.1</td>
<td>외부 접근 차단</td>
</tr>
<tr>
<td>✅ Swagger 비활성화</td>
<td><code>docs_url=None</code></td>
<td>API 정보 노출 방지</td>
</tr>
<tr>
<td>✅ 로깅 강화</td>
<td>요청 IP 및 포트 기록</td>
<td>공격/오류 추적 가능</td>
</tr>
</tbody></table>
<hr>
<h2 id="4-앞으로의-보완-아이디어-💡">4. 앞으로의 보완 아이디어 💡</h2>
<hr>
<ul>
<li><strong>Fail2Ban 적용</strong>: 비정상 IP 차단 자동화</li>
<li><strong>API Rate Limiting</strong>: 요청 횟수 제한 적용</li>
<li><strong>SSL 인증서 갱신 자동화</strong>: 장기적 서비스 안정화</li>
<li><strong>서버간 Mutual TLS</strong>: 강력한 상호 인증 고려</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[서버간 통신 인증 방식 : API Key]]></title>
            <link>https://velog.io/@playername_ltt/%EC%84%9C%EB%B2%84%EA%B0%84-%ED%86%B5%EC%8B%A0-%EC%9D%B8%EC%A6%9D-%EB%B0%A9%EC%8B%9D-API-Key</link>
            <guid>https://velog.io/@playername_ltt/%EC%84%9C%EB%B2%84%EA%B0%84-%ED%86%B5%EC%8B%A0-%EC%9D%B8%EC%A6%9D-%EB%B0%A9%EC%8B%9D-API-Key</guid>
            <pubDate>Mon, 07 Jul 2025 02:34:53 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/playername_ltt/post/0b767503-87c5-41bf-8569-811f49045e8b/image.png" alt=""></p>
<h1 id="introduce">Introduce</h1>
<hr>
<p>우리가 시스템을 설계할 때 가장 많이 사용하는 인증 방식 중 하나는 바로 <strong>JWT (JSON Web Token)</strong> 기반 인증이다.</p>
<p>JWT는 주로 사용자의 인증 정보와 권한을 서버가 아닌 클라이언트 측에 토큰으로 담아 전달하는 방식이다. 사용자는 해당 토큰을 가지고 서버에 접근하며, 서버는 토큰을 검증함으로써 사용자의 신원을 확인한다.</p>
<p>그렇다면 <strong>서버와 서버 간의 통신</strong>에서도 무조건 JWT를 사용해야 할까?</p>
<p>솔직히 말하면 나는 지금까지 <strong>별다른 고민 없이 무조건 JWT를 사용</strong>해왔다. 하지만 최근 프로젝트에서는 내가 JWT를 굳이 사용할 필요가 없다는 사실을 깨달았다.</p>
<p>내가 고민한 이유는 다음과 같다.</p>
<ol>
<li><strong>FastAPI로 구현한 모델 서버</strong>에서는 사용자의 세션 정보나 개인정보를 전혀 사용하지 않는다.</li>
<li>모든 요청은 오직 <strong>메인 서버</strong>로부터만 들어온다. (즉, 외부 요청 차단)</li>
</ol>
<p>결국 내 경우에는 복잡한 JWT 기반 인증 대신, 단순하면서도 서버 간 신뢰를 보장할 수 있는 방법으로 <strong>API Key 기반 인증</strong>을 도입하기로 결정했다.</p>
<h1 id="로직-설계-및-구현">로직 설계 및 구현</h1>
<hr>
<p>단순히 API Key를 평문으로 전송하는 것은 보안상 위험하다고 판단했다.</p>
<p>물론 내부 통신이기 때문에 큰 위험은 없겠지만, 그래도 <strong>암호화</strong>는 최소한의 보안 조치라고 생각했다.</p>
<p>그래서 암호화 방식을 간단히 살펴보고 적절한 방법을 선택했다.</p>
<h1 id="암호화-방식">암호화 방식</h1>
<hr>
<p>암호화 방식은 크게 두 가지로 나눌 수 있다.</p>
<ol>
<li><strong>대칭키 방식 (Symmetric Key)</strong></li>
<li><strong>비대칭키 방식 (Asymmetric Key)</strong></li>
</ol>
<p>비대칭키 방식은 보안성이 뛰어나지만, 구현이 복잡하고 속도도 상대적으로 느리다.</p>
<p>내 프로젝트는 다음과 같은 특징이 있다.</p>
<ul>
<li>민감한 정보 없음</li>
<li>단순한 인증만 필요</li>
<li>빠른 개발이 중요</li>
</ul>
<p>따라서 이번에는 <strong>대칭키 방식 (AES)</strong> 을 사용하기로 결정했다.</p>
<p>자세한 차이는 아래 블로그 글이 잘 정리되어 있어 추천한다.</p>
<p>👉 <a href="https://velog.io/@octo__/%EB%8C%80%EC%B9%AD%ED%82%A4%EC%99%80-%EA%B3%B5%EA%B0%9C%ED%82%A4%EB%B9%84%EB%8C%80%EC%B9%AD%ED%82%A4">대칭키와 공개키(비대칭키)</a></p>
<h1 id="api-key-암호화-및-전송">API Key 암호화 및 전송</h1>
<hr>
<h2 id="1-spring-서버-api-key-암호화-및-전송">1. Spring 서버 (API Key 암호화 및 전송)</h2>
<p>Spring 서버에서는 API 요청 시, 미리 설정해둔 <strong>API Key</strong>를 암호화한 후 HTTP 헤더에 담아 전송하도록 구성했다.</p>
<h3 id="📌-encryptedapikeyprovider-api-key-암호화">📌 <code>EncryptedApiKeyProvider</code> (API Key 암호화)</h3>
<pre><code class="language-java">@Slf4j
@Getter
@Component
@RequiredArgsConstructor
public class EncryptedApiKeyProvider {
    @Value(&quot;${server.api-key}&quot;)
    private String formatClientApiKey;

    @Value(&quot;${server.salt}&quot;)
    private String salt;

    private final String ALGORITHM = &quot;AES&quot;;
    private String encryptedApiKey;

    @PostConstruct
    public void init() throws Exception {
        try {
            this.encryptedApiKey = encrypt();
            log.info(&quot;API Key encrypted&quot;);
        } catch (Exception e) {
            throw new Exception(&quot;Failed to encrypt API key during initialization&quot;, e);
        }
    }

    public String encrypt() throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(this.salt.getBytes(), ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        byte[] encrypted = cipher.doFinal(this.formatClientApiKey.getBytes());
        return Base64.getEncoder().encodeToString(encrypted);
    }
}
</code></pre>
<h3 id="📌-webclientconfig-api-key-전송">📌 <code>WebClientConfig</code> (API Key 전송)</h3>
<pre><code class="language-java">@Configuration
@RequiredArgsConstructor
public class WebClientConfig {

    @Value(&quot;${stt.server.url}&quot;)
    private String serverUrl;

    private final EncryptedApiKeyProvider apiKeyProvider;

    @Bean(name = &quot;webClient&quot;)
    public WebClient formatWebClient() throws SSLException {
        SslContext sslContext = SslContextBuilder
                .forClient()
                .trustManager(InsecureTrustManagerFactory.INSTANCE)
                .build();

        HttpClient httpClient = HttpClient.create()
                .secure(ssl -&gt; ssl.sslContext(sslContext));

        return WebClient.builder()
                .baseUrl(serverUrl)
                .defaultHeader(&quot;X-API-KEY&quot;, apiKeyProvider.getEncryptedApiKey())
                .clientConnector(new ReactorClientHttpConnector(httpClient))
                .build();
    }
}
</code></pre>
<p>✅ 모든 API 요청은 <code>X-API-KEY</code> 헤더에 암호화된 키가 포함된다.</p>
<h1 id="api-key-복호화-및-인증">API Key 복호화 및 인증</h1>
<hr>
<h2 id="2-fastapi-서버-api-key-복호화-및-검증">2. FastAPI 서버 (API Key 복호화 및 검증)</h2>
<p>FastAPI 서버에서는 Spring 서버로부터 전달받은 암호화된 API Key를 복호화하고, 미리 설정한 Key와 일치하는지 확인한다. 이를 위해 <strong>미들웨어</strong>를 작성했다.</p>
<h3 id="📌-apikeyvalidationmiddleware-복호화-및-인증">📌 <code>ApiKeyValidationMiddleware</code> (복호화 및 인증)</h3>
<pre><code class="language-python">from fastapi import Request, HTTPException
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import JSONResponse
from Crypto.Cipher import AES
from dotenv import load_dotenv
import base64
import os
from app.utils.log_util import logger

# .env 파일 로드
load_dotenv()

class ApiKeyValidationMiddleware(BaseHTTPMiddleware):
    def __init__(self, app):
        super().__init__(app)

        self.control_character = &quot;\x06&quot;
        self.SALT = os.getenv(&quot;SALT&quot;).encode(&quot;utf-8&quot;)
        self.API_KEY = os.getenv(&quot;API_KEY&quot;)

    def decrypt_api_key(self, encrypted_key_base64: str) -&gt; str:
        try:
            cipher = AES.new(self.SALT, AES.MODE_ECB)
            decoded = base64.b64decode(encrypted_key_base64)
            decrypted = cipher.decrypt(decoded)
            return decrypted.decode(&quot;utf-8&quot;).strip(self.control_character)
        except Exception:
            raise HTTPException(status_code=401, detail=&quot;API Key Decryption Failed&quot;)

    async def dispatch(self, request: Request, call_next):
        encrypted_key = request.headers.get(&quot;X-API-KEY&quot;)
        if not encrypted_key:
            return JSONResponse(status_code=401, content={&quot;detail&quot;: &quot;Missing API Key&quot;})

        try:
            decrypted_key = self.decrypt_api_key(encrypted_key)
        except HTTPException as e:
            return JSONResponse(status_code=401, content={&quot;detail&quot;: e.detail})

        if decrypted_key != self.API_KEY:
            return JSONResponse(status_code=401, content={&quot;detail&quot;: &quot;Invalid API Key&quot;})

        return await call_next(request)
</code></pre>
<p>✅ 요청 헤더의 API Key 복호화 → Key 일치 여부 확인 → 미들웨어 통과</p>
<h1 id="정리">정리</h1>
<hr>
<p>요약하자면, 나의 서버 인증 로직은 다음과 같은 단계로 이루어진다.</p>
<ol>
<li><strong>Spring 서버</strong>: API Key 암호화 → <code>X-API-KEY</code> 헤더에 추가하여 요청 전송</li>
<li><strong>FastAPI 서버</strong>: API Key 복호화 → Key 검증 → 인증 성공 시 요청 처리</li>
</ol>
<p>나는 복잡한 JWT 토큰 인증 대신, <strong>단순하고 빠르게 구현 가능한 API Key 암호화 방식</strong>을 선택했다.</p>
<p>물론 이 방식이 모든 상황에 정답은 아니다. 만약 사용자의 권한 분기, 세션 관리, 세밀한 접근 제어가 필요하다면 JWT 또는 OAuth2가 더 적합할 것이다.</p>
<p>하지만 내 상황에서는 <strong>간결함, 속도, 효율</strong>이 우선이라 판단했고, 이에 맞춰 설계하고 구현했다.</p>
<hr>
<p>💬 <strong>추가 의견이나 개선 아이디어</strong></p>
<ul>
<li>ECB 모드는 간단하지만 보안에 약점이 있다 → CBC, GCM 같은 더 안전한 모드 고려 가능</li>
<li>키 관리: <code>.env</code> 파일 대신 Secret Manager 사용 검토</li>
<li>인증 실패시 로깅 강화 및 모니터링</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[FastAPI Logging파일 일별로 관리하기]]></title>
            <link>https://velog.io/@playername_ltt/FastAPI-Logging%ED%8C%8C%EC%9D%BC-%EC%9D%BC%EB%B3%84%EB%A1%9C-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@playername_ltt/FastAPI-Logging%ED%8C%8C%EC%9D%BC-%EC%9D%BC%EB%B3%84%EB%A1%9C-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 23 Jun 2025 01:25:12 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/playername_ltt/post/359d7e09-089c-4a52-a9e5-8dcb48f798b5/image.png" alt=""></p>
<h1 id="introduce">Introduce</h1>
<hr>
<p>로그는 언제나 중요하다. 로컬에서 작업할 때는 몰라도, 배포 환경에서 벌어진 문제에 대응하기 위해서는 로그를 남겨두는 것이 무조건 필요하다.</p>
<p>이제 로그파일을 남겨두기 위해 코드를 작성해보자</p>
<h1 id="1차-작성">1차 작성</h1>
<hr>
<p>우선은 가장 간단한 형태로 작성했다.</p>
<h2 id="log_utilpy">log_util.py</h2>
<pre><code class="language-python">import logging
import os
from datetime import date

today = date.today()
LOG_FILE_PATH = f&quot;logs/{today}.log&quot;

os.makedirs(os.path.dirname(LOG_FILE_PATH), exist_ok=True)

logging.basicConfig(
    level=logging.DEBUG,
    format=&quot;%(asctime)s - %(name)s - %(levelname)s - %(message)s&quot;,
    handlers=[
        logging.FileHandler(LOG_FILE_PATH, mode=&quot;a&quot;, encoding=&quot;utf-8&quot;),
        logging.StreamHandler(),
    ],
)

logger = logging.getLogger(&quot;app_logger&quot;)
logger.info(&quot;Logging setup complete.&quot;)</code></pre>
<p>위와 같이 작성해줬고, 아래와 같이 로그가 쌓인다.</p>
<p><img src="https://velog.velcdn.com/images/playername_ltt/post/fd293266-e170-48f5-b652-0761f4048ebf/image.png" alt=""></p>
<pre><code>2025-06-20 15:52:25,939 - app_logger - INFO - AUTH BY: USER-01JPVYFQ8WMZ2H20X04K18W8NH</code></pre><p>하지만 이렇게만 했을 경우에는 몇가지 문제가 있다.</p>
<ol>
<li>Request가 들어오고 Response가 나가는 것을 하나하나 다 수동으로 찍어줘야 한다.</li>
<li>실행하고 나서 서버를 재구동하지 않으면 날짜에 따라 로그 파일이 갱신되지 않는다.</li>
</ol>
<p>우선 1번부터 해결해보자.</p>
<h1 id="2차-작성">2차 작성</h1>
<hr>
<h2 id="request-response-로깅">Request, Response 로깅</h2>
<hr>
<h3 id="mainpy">main.py</h3>
<pre><code class="language-python">from fastapi import FastAPI, Response, Request
from starlette.background import BackgroundTask
from starlette.types import Message
from app.utils.log_util import logger

app = FastAPI()

# ...
# 추가 초기 설정
# ...

def end_log_info(res_body: bytes):
    logger.info(f&quot;RESPONSE: {res_body.decode(errors=&#39;ignore&#39;)}&quot;)

def entry_log_info(endpoint: str):
    logger.info(f&quot;ENDPOINT: {endpoint}&quot;)

async def set_body(request: Request, body: bytes):
    async def receive() -&gt; Message:
        return {&quot;type&quot;: &quot;http.request&quot;, &quot;body&quot;: body}

    request._receive = receive

@app.middleware(&quot;http&quot;)
async def logging_middleware(request: Request, call_next):
    req_body = await request.body()
    await set_body(request, req_body)
    endpoint = f&quot;{request.method} {request.url.path}&quot;

    entry_log_info(endpoint)

    response = await call_next(request)

    res_body = b&quot;&quot;
    async for chunk in response.body_iterator:
        res_body += chunk

    task = BackgroundTask(end_log_info, res_body)

    return Response(
        content=res_body,
        status_code=response.status_code,
        headers=dict(response.headers),
        media_type=response.media_type,
        background=task,
    )
</code></pre>
<ul>
<li><code>@app.middleware(&quot;http&quot;)</code></li>
</ul>
<blockquote>
<p>HTTP Request와 Response를 처리할 때 이 미들웨어 함수를 중간에 끼워 넣겠다는 의미</p>
<ul>
<li>클라이언트가 HTTP Request를 보냄</li>
<li>FastAPI에서는 <code>@app.middleware(&quot;http&quot;)</code>에 등록된 메소드를 우선 실행</li>
<li>이 함수에서 <code>call_next(request)</code>를 호출해야 실제 라우터 핸들러로 넘어감</li>
<li>응답이 돌아오면 다시 이 미들웨어에서 응답을 가공하거나 처리할 수 있음</li>
</ul>
</blockquote>
<p>그래서 이 미들웨어 메소드를 가지고 HTTP Request가 들어왔을 때 로깅 처리를 해주려고 한다.</p>
<p>이중에서 또 볼 부분은 <code>BackgroundTask</code>이다. </p>
<ul>
<li><code>BackgroundTask</code></li>
</ul>
<blockquote>
<p>[<strong>BackgroundTask</strong>] (<a href="https://fastapi.tiangolo.com/tutorial/background-tasks/">https://fastapi.tiangolo.com/tutorial/background-tasks/</a>)</p>
<ul>
<li>Response이후에 실행할 작업을 정의하는 유틸리티</li>
<li>라우터 함수가 끝나더라도 비동기적으로 백그라운드에서 실행됨</li>
<li>동시성(성능)과 응답속도 향상에 도움이 됨</li>
<li>따라서 로깅으로 인해 Response time에 영향을 주지 않음</li>
</ul>
</blockquote>
<p>혹시모를 로깅으로 인한 지연을 대비해 <code>BackgroundTask</code>를 적용해보았다.</p>
<h2 id="날짜별-로그파일-갱신">날짜별 로그파일 갱신</h2>
<hr>
<h3 id="log_utilpy-1">log_util.py</h3>
<pre><code class="language-python">import logging
import os
from datetime import date
from logging.handlers import TimedRotatingFileHandler

LOG_DIR = &quot;logs&quot;
os.makedirs(LOG_DIR, exist_ok=True)

logger = logging.getLogger(&quot;app_logger&quot;)
logger.setLevel(logging.DEBUG)

# Setting formatter for log messages
formatter = logging.Formatter(&quot;%(asctime)s - %(name)s - %(levelname)s - %(message)s&quot;)

# File Handler
file_handler = TimedRotatingFileHandler(
    filename=os.path.join(LOG_DIR, f&quot;{date.today()}.log&quot;),
    when=&quot;midnight&quot;,
    interval=1,
    encoding=&quot;utf-8&quot;,
)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)

# Console Handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
console_handler.setFormatter(formatter)

# Reset handlers to avoid duplicate logs
if not logger.hasHandlers():
    logger.addHandler(file_handler)
    logger.addHandler(console_handler)

logger.debug(&quot;This is a DEBUG message.&quot;)
logger.info(&quot;Logging setup complete.&quot;)
</code></pre>
<p>위와 같이 <code>TimeRotatingFileHandler</code>를 이용하여 일별로 로그 관리를 하려고 작성했다.</p>
<p>이렇게 해두면 배포했을 때, 일별로 로그파일이 생성되며 관리하기 용이해진다.</p>
]]></description>
        </item>
    </channel>
</rss>