<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jade_95.log</title>
        <link>https://velog.io/</link>
        <description>기획과 설계 그리고 구현까지 하는 개발자가 되고 싶습니다</description>
        <lastBuildDate>Thu, 04 Jun 2026 00:13:08 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jade_95.log</title>
            <url>https://velog.velcdn.com/images/jade_95/profile/245cda05-ae86-47d1-9d60-6c106a9060f9/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jade_95.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jade_95" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[OMX와 OMC 요약 정리]]></title>
            <link>https://velog.io/@jade_95/OMX%EC%99%80-OMC-%EC%9A%94%EC%95%BD-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@jade_95/OMX%EC%99%80-OMC-%EC%9A%94%EC%95%BD-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 04 Jun 2026 00:13:08 GMT</pubDate>
            <description><![CDATA[<h1 id="omx와-omc-요약-정리">OMX와 OMC 요약 정리</h1>
<p>AI 코딩 도구는 이제 단순히 “코드를 생성해주는 도구”를 넘어가고 있다.</p>
<p>예전에는 개발자가 요청을 입력하면 AI가 코드 조각을 만들어주는 수준이었다면, 최근에는 요구사항을 정리하고, 작업을 계획하고, 여러 역할로 나누어 실행하고, 결과를 검증하는 방식으로 발전하고 있다.</p>
<p>이런 흐름에서 등장한 것이 OMX와 OMC다.</p>
<p>OMX는 Codex CLI를 더 체계적으로 사용하기 위한 워크플로우 레이어이고, OMC는 Claude Code를 멀티 에이전트 방식으로 확장해주는 플러그인이다.</p>
<p>쉽게 말하면 둘 다 AI 코딩 도구를 실무형 작업 도구로 바꿔주는 보조 시스템이라고 보면 된다.</p>
<hr>
<h2 id="1-omx란">1. OMX란?</h2>
<p>OMX는 oh-my-codex의 약자다.</p>
<p>OpenAI Codex CLI 위에 워크플로우, 작업 계획, 로그, 메모리, 상태 관리 기능을 얹는 도구다.</p>
<p>Codex CLI 자체를 대체하는 것은 아니고, Codex CLI를 더 잘 쓰기 위한 관리 레이어에 가깝다.</p>
<p>쉽게 비유하면 Codex CLI가 실제로 코드를 작성하는 실행 엔진이라면, OMX는 그 위에서 작업 순서, 기록, 상태, 역할 분담을 관리하는 작업 지휘 시스템이다.</p>
<hr>
<h2 id="2-omx의-핵심-기능">2. OMX의 핵심 기능</h2>
<p>OMX의 핵심은 AI 코딩 작업을 즉흥적으로 처리하지 않고, 일정한 흐름에 따라 진행하게 만드는 것이다.</p>
<p>대표적인 기능은 다음과 같다.</p>
<ul>
<li>요구사항이 애매할 때 질문을 통해 명확하게 정리</li>
<li>구현 전에 계획을 먼저 수립</li>
<li>승인된 계획을 바탕으로 코드 작성</li>
<li>여러 에이전트를 병렬로 실행</li>
<li>작업 로그와 메모리를 <code>.omx/</code> 디렉터리에 저장</li>
<li>세션 상태를 유지하여 이어서 작업 가능</li>
<li>Codex CLI의 hook 설정 관리</li>
<li>현재 작업 상태를 확인할 수 있는 HUD 기능 제공</li>
</ul>
<p>대표 명령어는 다음과 같다.</p>
<pre><code class="language-bash">$deep-interview
$ralplan
$ralph
$team</code></pre>
<p>각 명령어의 역할은 다음과 같다.</p>
<p><code>$deep-interview</code>는 요구사항이 애매할 때 사용한다.
AI가 바로 구현하지 않고 먼저 질문을 던지면서 요구사항을 명확하게 만든다.</p>
<p><code>$ralplan</code>은 구현 전에 계획을 세울 때 사용한다.
planner, architect, critic 같은 역할을 통해 계획을 검토하고 정리하는 방식이다.</p>
<p><code>$ralph</code>는 승인된 계획을 바탕으로 끝까지 구현을 밀어붙이는 모드다.
단순한 답변이 아니라 실제 작업 완료를 목표로 한다.</p>
<p><code>$team</code>은 여러 에이전트를 병렬로 실행할 때 사용한다.
예를 들어 프론트엔드, 백엔드, 테스트 역할을 나누어 동시에 진행하는 방식이다.</p>
<hr>
<h2 id="3-omx-설치-및-사용-흐름">3. OMX 설치 및 사용 흐름</h2>
<p>OMX는 보통 Codex CLI와 함께 설치해서 사용한다.</p>
<p>Codex CLI가 아직 설치되어 있지 않다면 다음처럼 설치한다.</p>
<pre><code class="language-bash">npm install -g @openai/codex</code></pre>
<p>이후 OMX를 설치한다.</p>
<pre><code class="language-bash">npm install -g oh-my-codex</code></pre>
<p>그다음 터미널에서 OMX 초기 설정을 진행한다.</p>
<pre><code class="language-bash">omx setup</code></pre>
<p>Codex를 실행한다.</p>
<pre><code class="language-bash">codex</code></pre>
<p>필요하면 OMX를 고성능 설정으로 실행할 수도 있다.</p>
<pre><code class="language-bash">omx --madmax --high</code></pre>
<p>실제 작업에서는 다음과 같이 사용할 수 있다.</p>
<pre><code class="language-bash">$deep-interview &quot;요구사항이 애매한 기능 정리&quot;
$ralplan &quot;구현 계획 수립&quot;
$ralph &quot;승인된 계획을 끝까지 구현&quot;
$team 3:executor &quot;3명이 병렬로 구현&quot;</code></pre>
<p>주의할 점은 <code>omx setup</code>은 Codex 대화창 안에서 입력하는 명령이 아니라, 터미널에서 실행하는 OMX CLI 명령이라는 점이다.</p>
<p>또한 기존에 Codex CLI를 Homebrew 등 다른 방식으로 설치했다면 npm 설치 버전과 충돌할 수 있으므로, 설치 경로를 확인하는 것이 좋다.</p>
<hr>
<h2 id="4-omx를-쓰면-좋은-경우">4. OMX를 쓰면 좋은 경우</h2>
<p>OMX는 다음과 같은 상황에서 유용하다.</p>
<ul>
<li>Codex CLI를 매번 즉흥적으로 사용하고 있는 경우</li>
<li>작업 계획과 로그를 남기고 싶은 경우</li>
<li>AI가 무엇을 했는지 추적하고 싶은 경우</li>
<li>요구사항 정리부터 구현, 검증까지 흐름을 고정하고 싶은 경우</li>
<li>여러 작업을 병렬로 처리하고 싶은 경우</li>
<li>하나의 긴 작업을 세션이 끊겨도 이어서 진행하고 싶은 경우</li>
</ul>
<p>즉, 단순한 코드 생성이 아니라 프로젝트 단위의 작업 흐름을 만들고 싶을 때 OMX가 도움이 된다.</p>
<hr>
<h2 id="5-omc란">5. OMC란?</h2>
<p>OMC는 oh-my-claudecode의 약자다.</p>
<p>Claude Code를 더 강력하게 사용하기 위한 플러그인이다.</p>
<p>Claude Code가 기본적으로 하나의 AI 개발 도구라면, OMC는 그 위에 여러 전문 에이전트와 실행 모드를 얹어서 더 복잡한 개발 작업을 처리할 수 있게 만든다.</p>
<p>예를 들어 단순히 “회원가입 기능 만들어줘”라고 요청하는 것이 아니라, 요구사항 분석, 설계, 구현, 테스트, 검증을 역할별로 나누어 처리하게 할 수 있다.</p>
<p>OMC의 핵심은 Claude Code를 단순한 코딩 도구가 아니라 팀처럼 움직이는 AI 개발 시스템으로 확장하는 것이다.</p>
<hr>
<h2 id="6-omc-설치-및-초기-설정">6. OMC 설치 및 초기 설정</h2>
<p>OMC는 Claude Code의 플러그인 마켓플레이스를 통해 설치한다.</p>
<p>Claude Code 안에서 아래 명령을 한 줄씩 실행한다.</p>
<pre><code class="language-bash">/plugin marketplace add https://github.com/Yeachan-Heo/oh-my-claudecode</code></pre>
<pre><code class="language-bash">/plugin install oh-my-claudecode</code></pre>
<p>설치 후 초기 설정은 다음 명령으로 진행한다.</p>
<pre><code class="language-bash">/omc-setup</code></pre>
<p>일부 문서에서는 다음과 같은 형태로 안내되어 있을 수 있다.</p>
<pre><code class="language-bash">/oh-my-claudecode:omc-setup</code></pre>
<p>하지만 최신 빠른 시작 기준으로는 <code>/omc-setup</code>을 사용하는 것이 더 간단하다.</p>
<p>설치 후에는 다음 명령어로 상태를 점검할 수 있다.</p>
<pre><code class="language-bash">/omc-doctor</code></pre>
<p>프로젝트를 처음 시작할 때는 다음 명령어를 사용한다.</p>
<pre><code class="language-bash">/deepinit</code></pre>
<p><code>/deepinit</code>은 프로젝트 구조를 분석하고, AI가 참고할 수 있는 AGENTS.md 문서를 생성하는 역할을 한다.</p>
<p>OMC는 <code>.omc/</code> 디렉터리 아래에 상태, 메모리, 계획, PRD, 로그 등을 저장한다.</p>
<hr>
<h2 id="7-omc의-주요-실행-모드">7. OMC의 주요 실행 모드</h2>
<p>OMC에는 여러 실행 모드가 있다.</p>
<p>대표적인 모드는 다음과 같다.</p>
<h3 id="autopilot">autopilot</h3>
<p>계획부터 구현, 테스트, 검증까지 자동으로 진행하는 모드다.</p>
<p>반복적이고 비교적 명확한 작업에 적합하다.</p>
<h3 id="ralph">ralph</h3>
<p>작업이 완료될 때까지 계속 밀어붙이는 모드다.</p>
<p>중간에 멈추지 않고 결과물을 끝까지 만드는 데 초점이 있다.</p>
<h3 id="team">team</h3>
<p>여러 에이전트가 팀처럼 역할을 나누어 작업하는 모드다.</p>
<p>프론트엔드, 백엔드, 테스트, 리뷰 등을 나누어 처리할 수 있다.</p>
<h3 id="ultrawork--ulw">ultrawork / ulw</h3>
<p>여러 개의 독립적인 작업을 병렬로 처리하는 모드다.</p>
<p>작업량이 많고 서로 의존성이 낮을 때 유용하다.</p>
<h3 id="eco">eco</h3>
<p>비용 절감을 우선하는 모드다.</p>
<p>가능하면 저렴한 모델을 먼저 사용하고, 필요한 경우에만 강한 모델을 사용하는 방식이다.</p>
<h3 id="deep-interview">deep-interview</h3>
<p>요구사항이 애매할 때 사용하는 모드다.</p>
<p>바로 구현하지 않고 질문을 통해 요구사항을 명확하게 만든다.</p>
<h3 id="ralplan">ralplan</h3>
<p>구현 전에 계획을 수립하고 검토하는 모드다.</p>
<p>planner, architect, critic 같은 역할을 통해 계획을 더 탄탄하게 만든다.</p>
<hr>
<h2 id="8-omc를-쓰면-좋은-경우">8. OMC를 쓰면 좋은 경우</h2>
<p>OMC는 다음과 같은 상황에서 유용하다.</p>
<ul>
<li>Claude Code를 더 체계적으로 쓰고 싶은 경우</li>
<li>큰 기능을 한 번에 구현해야 하는 경우</li>
<li>요구사항 정리, 설계, 구현, 테스트를 나누고 싶은 경우</li>
<li>여러 에이전트를 동시에 활용하고 싶은 경우</li>
<li>Claude Code 작업 기록과 상태를 유지하고 싶은 경우</li>
<li>비용을 아끼면서 AI 코딩을 돌리고 싶은 경우</li>
<li>프로젝트 전체 구조를 AI가 이해하게 만들고 싶은 경우</li>
</ul>
<p>즉, Claude Code를 실무 개발팀처럼 사용하고 싶을 때 OMC가 적합하다.</p>
<hr>
<h2 id="9-omx와-omc-비교">9. OMX와 OMC 비교</h2>
<p>OMX와 OMC는 비슷해 보이지만 사용하는 기반 도구가 다르다.</p>
<p>OMX는 OpenAI Codex CLI 기반이고, OMC는 Claude Code 기반이다.</p>
<p>OMX는 Codex CLI를 위한 워크플로우 레이어에 가깝고, OMC는 Claude Code를 멀티 에이전트 개발 환경으로 확장하는 플러그인에 가깝다.</p>
<p>정리하면 다음과 같다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>OMX</th>
<th>OMC</th>
</tr>
</thead>
<tbody><tr>
<td>기반 도구</td>
<td>OpenAI Codex CLI</td>
<td>Claude Code</td>
</tr>
<tr>
<td>성격</td>
<td>Codex CLI용 워크플로우 레이어</td>
<td>Claude Code용 에이전트 플러그인</td>
</tr>
<tr>
<td>저장 위치</td>
<td><code>.omx/</code></td>
<td><code>.omc/</code></td>
</tr>
<tr>
<td>핵심 목적</td>
<td>Codex 작업 표준화, 상태 유지, 로그 관리</td>
<td>Claude Code를 멀티 에이전트 방식으로 확장</td>
</tr>
<tr>
<td>주요 명령</td>
<td><code>$deep-interview</code>, <code>$ralplan</code>, <code>$ralph</code>, <code>$team</code></td>
<td><code>/deepinit</code>, <code>/autopilot</code>, <code>/ralph</code>, <code>/team</code>, <code>/ultrawork</code>, <code>/deep-interview</code>, <code>/omc-setup</code></td>
</tr>
<tr>
<td>강점</td>
<td>Codex CLI 작업을 체계화하기 좋음</td>
<td>다양한 에이전트와 실행 모드 활용 가능</td>
</tr>
</tbody></table>
<hr>
<h2 id="10-어떤-걸-쓰면-좋을까">10. 어떤 걸 쓰면 좋을까?</h2>
<p>Codex CLI를 주로 사용한다면 OMX가 적합하다.</p>
<p>Claude Code를 주로 사용한다면 OMC가 적합하다.</p>
<p>둘 중 하나가 무조건 더 좋다기보다는, 내가 사용하는 AI 코딩 도구가 무엇인지에 따라 선택하면 된다.</p>
<p>정리하면 다음과 같다.</p>
<ul>
<li>Codex CLI를 쓴다 → OMX</li>
<li>Claude Code를 쓴다 → OMC</li>
<li>요구사항이 애매하다 → deep-interview</li>
<li>구현 전에 설계가 필요하다 → ralplan</li>
<li>큰 기능을 끝까지 구현하고 싶다 → ralph</li>
<li>여러 작업을 병렬로 처리하고 싶다 → team 또는 ultrawork</li>
<li>비용을 줄이고 싶다 → eco</li>
</ul>
<hr>
<h2 id="11-사용할-때-주의할-점">11. 사용할 때 주의할 점</h2>
<p>OMX와 OMC는 AI 코딩을 더 강하게 만들어주는 도구지만, 무조건 자동으로 완벽한 결과를 보장하는 것은 아니다.</p>
<p>특히 다음 부분은 주의해야 한다.</p>
<ul>
<li>명령어와 설치 방식은 버전에 따라 바뀔 수 있다.</li>
<li>AI가 만든 계획이나 코드는 사람이 반드시 검토해야 한다.</li>
<li>멀티 에이전트 방식은 편리하지만, 잘못된 방향으로 병렬 작업을 하면 오히려 수정 비용이 커질 수 있다.</li>
<li>대규모 작업을 맡길 때는 먼저 요구사항을 명확히 해야 한다.</li>
<li>실제 서비스 코드에 반영하기 전에는 테스트와 코드 리뷰가 필요하다.</li>
<li>비용이 발생하는 환경에서는 실행 모드와 모델 사용량을 확인해야 한다.</li>
</ul>
<p>즉, OMX와 OMC는 개발자를 대체하는 도구라기보다는 개발자가 AI를 더 체계적으로 지휘하기 위한 도구에 가깝다.</p>
<hr>
<h2 id="12-최종-정리">12. 최종 정리</h2>
<p>OMX와 OMC는 AI 코딩 도구를 더 실무적으로 사용하기 위한 도구다.</p>
<p>기존의 AI 코딩 방식은 개발자가 질문하고 AI가 답변하는 단순한 구조였다.</p>
<p>하지만 OMX와 OMC는 여기서 한 단계 더 나아가 요구사항 정리, 계획 수립, 역할 분담, 구현, 검증, 기록까지 하나의 흐름으로 관리할 수 있게 해준다.</p>
<p>결국 핵심은 AI에게 그냥 “코드 짜줘”라고 시키는 것이 아니다.</p>
<p>AI에게 명확한 요구사항을 주고, 계획을 세우게 하고, 역할을 나누고, 검증까지 시키는 구조를 만드는 것이다.</p>
<p>앞으로 AI 코딩을 실무에서 제대로 활용하려면 단순한 프롬프트보다 이런 워크플로우 도구가 더 중요해질 가능성이 높다.</p>
<p>OMX와 OMC는 그런 흐름에서 나온 도구라고 볼 수 있다.</p>
<p>Codex CLI를 쓴다면 OMX, Claude Code를 쓴다면 OMC를 활용해보면 좋다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[별 헤는 밤을 운영하며 배운 것 — 결국 Simple is Best]]></title>
            <link>https://velog.io/@jade_95/%EB%B3%84-%ED%97%A4%EB%8A%94-%EB%B0%A4%EC%9D%84-%EC%9A%B4%EC%98%81%ED%95%98%EB%A9%B0-%EB%B0%B0%EC%9A%B4-%EA%B2%83-%EA%B2%B0%EA%B5%AD-Simple-is-Best</link>
            <guid>https://velog.io/@jade_95/%EB%B3%84-%ED%97%A4%EB%8A%94-%EB%B0%A4%EC%9D%84-%EC%9A%B4%EC%98%81%ED%95%98%EB%A9%B0-%EB%B0%B0%EC%9A%B4-%EA%B2%83-%EA%B2%B0%EA%B5%AD-Simple-is-Best</guid>
            <pubDate>Sat, 03 Jan 2026 08:12:28 GMT</pubDate>
            <description><![CDATA[<h1 id="별-헤는-밤을-운영하며-배운-것--결국-simple-is-best">별 헤는 밤을 운영하며 배운 것 — 결국 Simple is Best</h1>
<p>사이드 프로젝트로 <strong>별 헤는 밤</strong>이라는 커뮤니티 사이트를 배포하고 운영하면서, 기능을 만들고 고치고 다시 걷어내는 과정을 꽤 오래 겪었다.<br>그중에서도 가장 기억에 남는 건 <strong>날씨 캐싱 설계</strong>와 <strong>회원 탈퇴 처리</strong>였다.</p>
<p>지금 돌아보면, 정말 많은 시간을 쏟았는데 결과적으로는<br>“굳이 이렇게까지 했어야 했나?”라는 생각이 든다.</p>
<hr>
<h2 id="좌표-기반-온디맨드-날씨-캐싱--이상과-현실의-괴리">좌표 기반 온디맨드 날씨 캐싱 — 이상과 현실의 괴리</h2>
<p>별 헤는 밤은 <strong>접속자의 좌표를 기준으로 도시를 판단하고</strong>,  
그 도시의 날씨를 수집해 별 관측에 적합한지 알려주는 서비스였다.</p>
<p>제공하던 정보는 다음과 같았다.</p>
<ul>
<li>구름량</li>
<li>날씨 상태</li>
<li>이를 기반으로 계산한 시정</li>
<li>오늘 별 관측 가능 상태 (Excellent ~ Bad)</li>
</ul>
<p>처음에는 서버 비용과 성능을 모두 잡고 싶어서<br><strong>온디맨드 방식의 캐싱 구조</strong>로 구현했다.</p>
<h3 id="처음-설계">처음 설계</h3>
<ul>
<li>사용자가 접속</li>
<li>좌표 → 도시 판별</li>
<li>해당 도시 날씨 수집</li>
<li>15분 캐싱</li>
<li>이후 동일 도시 요청은 캐시 제공</li>
</ul>
<p>이론상으로는 꽤 그럴듯했다.<br>“필요한 도시만 수집하니까 효율적이겠지”라는 생각이었다.</p>
<h3 id="현실">현실</h3>
<ul>
<li>구현이 생각보다 복잡해짐</li>
<li>좌표 기반 도시 판별 + 캐시 키 관리가 까다로움</li>
<li>사이트 특성상 트래픽이 많지 않음</li>
<li>결과적으로 <strong>캐시 히트율이 매우 낮음</strong></li>
</ul>
<p>정리하면,</p>
<blockquote>
<p>복잡도는 높은데, 얻는 이득은 거의 없는 구조</p>
</blockquote>
<p>였다.</p>
<hr>
<h2 id="단순화-이후--성능도-비용도-더-좋아졌다">단순화 이후 — 성능도, 비용도 더 좋아졌다</h2>
<p>결국 구조를 단순하게 바꿨다.</p>
<h3 id="변경-후-설계">변경 후 설계</h3>
<ul>
<li>주요 도시 기준</li>
<li><strong>30분 간격으로 정기 수집</strong></li>
<li>모든 사용자에게 동일 데이터 제공</li>
</ul>
<p>결과는 의외로 훨씬 좋았다.</p>
<ul>
<li>구현이 단순해짐</li>
<li>캐시 구조가 명확해짐</li>
<li>서버 비용 감소</li>
<li>성능 안정</li>
<li>운영 난이도 하락</li>
</ul>
<p>“온디맨드”라는 말에 너무 끌렸지,<br>이 프로젝트에 정말 필요한 구조인지는 깊게 고민하지 않았던 것 같다.</p>
<hr>
<h2 id="탈퇴-처리--왜-이렇게-나눴는지-기억도-안-난다">탈퇴 처리 — 왜 이렇게 나눴는지 기억도 안 난다</h2>
<p>회원 탈퇴 처리도 비슷한 시행착오를 겪었다.</p>
<p>초기 구현은 다음과 같았다.</p>
<ul>
<li>일반 회원: 탈퇴 시 즉시 탈퇴</li>
<li>소셜 로그인 회원:  <ul>
<li>탈퇴 후 1개월 이내 로그인 시 복구 가능</li>
</ul>
</li>
</ul>
<p>지금 와서 보면,</p>
<blockquote>
<p>왜 이렇게 나눴는지 스스로도 설명을 못 하겠다.</p>
</blockquote>
<p>아마도 “서비스답게 만들어야 한다”는 강박이 있었던 것 같다.</p>
<p>사실은 더 단순하게 구현할 수 있었다.</p>
<ul>
<li>모든 회원 동일한 정책 적용</li>
<li>탈퇴 후 1개월 이내 로그인 시 복구</li>
<li>1년 경과 시 완전 삭제</li>
</ul>
<p>이 정도면 충분했고,<br>유저 경험도 더 일관됐을 것이다.</p>
<hr>
<h2 id="진짜로-배운-점">진짜로 배운 점</h2>
<p>이 프로젝트에서 얻은 가장 큰 교훈은 기술 그 자체가 아니었다.</p>
<ul>
<li>더 복잡한 구조가</li>
<li>더 멋진 설계가</li>
<li>더 정교한 아키텍처가</li>
</ul>
<p><strong>항상 더 좋은 결과를 주는 건 아니었다.</strong></p>
<p>특히 사이드 프로젝트나 작은 서비스일수록 더 그렇다.</p>
<blockquote>
<p><strong>Simple is Best.</strong></p>
</blockquote>
<p>트레이드오프를 고려해서<br>“지금 이 프로젝트에 정말 필요한가?”를 기준으로 선택해야 한다.</p>
<p>괜히 의미 없는 복잡함에 시간을 쓰기보다는,<br>단순하고 명확한 구조로 빠르게 검증하고 운영하는 게 훨씬 낫다.</p>
<hr>
<h2 id="마무리">마무리</h2>
<p>돌이켜보면 진짜 개고생이었다.<br>그런데 이상하게도, 그 고생이 헛되다고만은 느껴지지 않는다.</p>
<p>이제는 안다.</p>
<ul>
<li>무엇을 만들지보다</li>
<li><strong>무엇을 만들지 말아야 하는지</strong>가 더 중요하다는 걸.</li>
</ul>
<p>다음 프로젝트에서는<br>조금 덜 고생하고,<br>조금 더 현명한 선택을 할 수 있을 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[별 헤는 밤 웹 서비스 회고록]]></title>
            <link>https://velog.io/@jade_95/%EB%B3%84-%ED%97%A4%EB%8A%94-%EB%B0%A4-%ED%9A%8C%EA%B3%A0%EB%A1%9D</link>
            <guid>https://velog.io/@jade_95/%EB%B3%84-%ED%97%A4%EB%8A%94-%EB%B0%A4-%ED%9A%8C%EA%B3%A0%EB%A1%9D</guid>
            <pubDate>Wed, 22 Oct 2025 07:20:02 GMT</pubDate>
            <description><![CDATA[<h1 id="별-헤는-밤-프로젝트-회고">별 헤는 밤 프로젝트 회고</h1>
<h2 id="프로젝트를-시작한-이유">프로젝트를 시작한 이유</h2>
<p>&#39;별 헤는 밤&#39;이라는 웹 서비스를 만들기로 결심한 이유는 세 가지였다.</p>
<ol>
<li><p><strong>완성의 경험이 필요했다</strong><br>정규 학습 외에 내가 혼자 처음부터 끝까지 완성한 프로젝트가 없었다. </p>
</li>
<li><p><strong>실전 경험의 부재</strong><br>자바와 스프링을 공부만 하고 직접 개발하며 상황을 겪어보지 않으니 모든 것이 막연하게만 느껴졌다.</p>
</li>
<li><p><strong>단순한 열정</strong><br>우리나라에는 왜 우주 관련 커뮤니티 사이트가 없을까? 내가 한번 만들어보자는 어린 생각이었다.</p>
</li>
</ol>
<p>나는 어렸을 때부터 천문학자가 꿈일 정도로 우주를 좋아했다. 하지만 공부를 그다지 잘하지 못해 취미로만 우주라는 콘텐츠를 즐겨왔다. 유튜브를 보면 많은 사람들이 우주 콘텐츠 영상을 좋아하는데, 왜 이런 주제를 다루는 사이트는 없는 걸까 궁금했다. 그래서 직접 만들어보기로 했다.</p>
<p>하지만 프로젝트를 진행하며 이것이 왜 &#39;어린 생각&#39;이었는지 깨닫게 되었다.</p>
<hr>
<h2 id="프로젝트를-통해-깨달은-현실">프로젝트를 통해 깨달은 현실</h2>
<h3 id="1-우주는-매우-매니악한-주제다">1. 우주는 매우 매니악한 주제다</h3>
<p>진짜 쉽게 비유하자면 이렇다. 달에 소행성이 충돌했다는 뉴스 기사를 보고 다른 사람에게 이 주제를 꺼낸다면, 대부분은 &quot;그래서 뭐 어쩌라고?&quot;라고 반응한다.</p>
<p>우주는 우리 삶의 많은 부분에 영향을 끼치지만, 그 소식을 안다고 해서 당장 삶에 큰 메리트가 있는 분야는 아니다. 과학이라는 학문 자체가 대부분 확정적이지 않고 &quot;그럴 것이다&quot;라고 짐작하는 영역이다. </p>
<p>AI 시대가 도래한 지금, 사람들이 가장 원하는 것은 <strong>확답</strong>이다. 지금 내 삶에 직접적인 영향을 미치는 것에 관심을 가지는 게 당연하다. 우주는 그런 확답을 주기 어려운 주제였다.</p>
<h3 id="2-우주-콘텐츠는-비즈니스적으로-수익화가-어렵다">2. 우주 콘텐츠는 비즈니스적으로 수익화가 어렵다</h3>
<p>사람들은 매력적인 서비스가 등장하면 많은 돈을 내서라도 그 콘텐츠를 즐기기 위해 결제한다. 하지만 우주는 이미 시중에 무료 자료가 넘쳐난다. 돈을 지불하면서까지 즐기려는 경우는 드물다.</p>
<p>예를 들어, 헬스장 비용이 비싸다고 해도 사람들은 계속 다닌다. 운동하기 싫어도 건강을 위해 어쩔 수 없이 다니는 것이다. 하지만 우주는 다르다. 사람들이 돈을 지불하면서까지 이 콘텐츠를 즐길 이유가 없다.</p>
<p>따라서 우주라는 주제로 웹사이트를 만드는 것은 사실상 <strong>돈 벌 생각이 없다는 소리</strong>에 가깝다. 내 서비스는 비즈니스적으로 수익을 낼 방법이 거의 제로에 가깝다. 설령 내가 돈을 벌 생각이 없다 해도, 사람들이 언제든 즐길 수 있게 사이트를 열어두는 것 자체에도 비용이 든다. 최소한의 서버 유지비조차 감당하기 어렵다는 얘기다.</p>
<p>솔직히 말해서, 이번 프로젝트를 통해 규모를 키워보겠다는 생각은 엄연한 나의 착각이었고 어린 생각이었다.</p>
<h3 id="3-웹사이트는-점점-사용자가-줄어들고-있다">3. 웹사이트는 점점 사용자가 줄어들고 있다</h3>
<p>정확히 말하자면, 사람들은 더 이상 컴퓨터에 앉아 커뮤니티 게시판을 방문해 소통하고 양질의 콘텐츠를 생성하지 않는다. AI의 영향도 크고, 스마트폰에 익숙한 세대에게 커뮤니티 서비스를 PC로 즐기라는 건 구시대적 발상이다.</p>
<p>이전에 내 웹서비스를 다른 사람에게 소개했을 때가 생각난다. 첫 번째 질문은 &quot;언제 앱 출시할 거냐?&quot;였다. 그리고 두 번째 피드백은 &quot;제공하는 정보들이 우주를 모르는 사람에게는 전혀 이해할 수 없는 내용이 많다&quot;는 것이었다.</p>
<p>사실 나는 사용자 친화적인 서비스를 만들기 위해 여러 번 노력했다. 하지만 서비스를 처음 운영해봤기에 턱없이 부족했다. 그리고 위에서 언급했듯, 우주에 관심 없는 사람들은 어려운 용어나 익숙하지 않은 현상에 큰 관심이 없다. 결국 흥미를 잃는 것뿐이다.</p>
<hr>
<h2 id="마치며">마치며</h2>
<p>&#39;별 헤는 밤&#39;은 나의 첫 완성 프로젝트였다. 비록 성공적인 서비스는 아니었지만, 이 과정을 통해 기술적 성장뿐 아니라 시장과 사용자에 대한 현실적인 통찰을 얻었다. </p>
<p>어쩌면 이 프로젝트의 가장 큰 가치는 실패를 통해 배운 교훈들에 있을지도 모른다.</p>
<p>나중에 이어서 기술적 요소들을 회고해보려 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA N+1 문제 정리 & 해결 방법]]></title>
            <link>https://velog.io/@jade_95/JPA-N1-%EB%AC%B8%EC%A0%9C-%EC%A0%95%EB%A6%AC-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@jade_95/JPA-N1-%EB%AC%B8%EC%A0%9C-%EC%A0%95%EB%A6%AC-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Wed, 22 Oct 2025 07:01:34 GMT</pubDate>
            <description><![CDATA[<h2 id="✅-n1-문제란">✅ N+1 문제란?</h2>
<hr>
<blockquote>
<p>JPA에서 연관 관계가 LAZY 로딩일 때,</p>
<p><strong>1번의 메인 쿼리 실행 후, 연관된 엔티티를 N번 추가 조회하는 현상</strong></p>
</blockquote>
<hr>
<h3 id="💡-예시-상황">💡 예시 상황</h3>
<pre><code class="language-java">java
복사편집
List&lt;Post&gt; posts = postRepository.findAll();
for (Post post : posts) {
    System.out.println(post.getUser().getName()); // 작성자 정보 조회
}
</code></pre>
<ul>
<li><p><code>Post</code>와 <code>User</code>는 <code>@ManyToOne(fetch = LAZY)</code> 관계</p>
</li>
<li><p>게시글이 10개라면:</p>
<ul>
<li><p><code>Post</code> 조회 쿼리: 1번</p>
</li>
<li><p>각 <code>User</code> 조회 쿼리: 10번</p>
<p>  → <strong>총 11번 쿼리 발생 = N+1 문제</strong></p>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="🍜-라면-비유로-이해하기">🍜 라면 비유로 이해하기</h3>
<table>
<thead>
<tr>
<th>방식</th>
<th>설명</th>
<th>비유</th>
</tr>
</thead>
<tbody><tr>
<td>❌ N+1 문제</td>
<td>게시글은 한 번에 가져오고, 작성자는 하나씩 가져옴</td>
<td><strong>라면은 상자째 받지만, 브랜드 정보는 본사에 하나씩 전화해서 확인</strong></td>
</tr>
<tr>
<td>✅ fetch join / EntityGraph</td>
<td>게시글과 작성자를 한 번에 가져옴</td>
<td><strong>라면 10개와 브랜드 정보가 한 포장에 같이 들어 있음</strong></td>
</tr>
</tbody></table>
<hr>
<h3 id="🔧-해결-방법-1-fetch-join">🔧 해결 방법 1: Fetch Join</h3>
<pre><code class="language-java">java
복사편집
@Query(&quot;SELECT p FROM Post p JOIN FETCH p.user&quot;)
List&lt;Post&gt; findAllWithUser();
</code></pre>
<ul>
<li>JPQL에 <code>JOIN FETCH</code> 명시</li>
<li><code>Post</code>와 <code>User</code>를 <strong>한 번에 JOIN해서 가져옴</strong></li>
<li>쿼리 1번으로 N+1 해결</li>
</ul>
<hr>
<h3 id="🔧-해결-방법-2-entitygraph">🔧 해결 방법 2: EntityGraph</h3>
<pre><code class="language-java">java
복사편집
@EntityGraph(attributePaths = {&quot;user&quot;})
List&lt;Post&gt; findAll();
</code></pre>
<ul>
<li>메서드에 <code>@EntityGraph</code> 선언</li>
<li>내부적으로 <code>JOIN FETCH</code>처럼 작동</li>
<li><strong>JPQL 없이도</strong> N+1 문제 해결</li>
</ul>
<hr>
<h3 id="📊-쿼리-비교">📊 쿼리 비교</h3>
<table>
<thead>
<tr>
<th>방식</th>
<th>쿼리 횟수</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>❌ 일반 LAZY</td>
<td>1 + N</td>
<td>Post 1번 + User N번</td>
</tr>
<tr>
<td>✅ Fetch Join</td>
<td>1</td>
<td>Post + User JOIN</td>
</tr>
<tr>
<td>✅ EntityGraph</td>
<td>1</td>
<td>Post + User JOIN (선언형)</td>
</tr>
</tbody></table>
<hr>
<h3 id="⚖️-fetch-join-vs-entitygraph">⚖️ Fetch Join vs EntityGraph</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>Fetch Join</th>
<th>EntityGraph</th>
</tr>
</thead>
<tbody><tr>
<td>사용 위치</td>
<td>JPQL 쿼리 안</td>
<td>메서드 위 어노테이션</td>
</tr>
<tr>
<td>조건 추가</td>
<td>O (WHERE, ORDER 가능)</td>
<td>X (정적 fetch만 가능)</td>
</tr>
<tr>
<td>복잡한 JOIN</td>
<td>O (정밀 제어 가능)</td>
<td>X (과도한 JOIN 가능성)</td>
</tr>
<tr>
<td>간단한 fetch</td>
<td>덜 직관적</td>
<td>✅ 가장 깔끔</td>
</tr>
</tbody></table>
<hr>
<h3 id="✅-핵심-요약">✅ 핵심 요약</h3>
<blockquote>
<p>N+1 문제 = &quot;1번 가져오고, 연관된 것 N번 더 가져오는 비효율&quot;</p>
<p>이를 해결하는 방법은:</p>
<ul>
<li>복잡한 조건이 있으면 → <code>fetch join</code></li>
<li>단순 조회라면 → <code>EntityGraph</code></li>
</ul>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[🧠 Oracle CDB와 PDB 차이 쉽게 이해하기]]></title>
            <link>https://velog.io/@jade_95/Oracle-CDB%EC%99%80-PDB-%EC%B0%A8%EC%9D%B4-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jade_95/Oracle-CDB%EC%99%80-PDB-%EC%B0%A8%EC%9D%B4-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 16 Oct 2025 04:17:36 GMT</pubDate>
            <description><![CDATA[<h1 id="💡-한마디-요약">💡 한마디 요약</h1>
<blockquote>
<p>Oracle 12c부터 DB를 CDB(큰 집)과 PDB(방)으로 나누어 관리해요.</p>
<p>여러 개의 데이터베이스를 하나의 DB 시스템 안에서 <strong>동임적으로</strong> 사용하고 싶을 때 필요해요.</p>
</blockquote>
<hr>
<h2 id="🏢-비유로-이해하기">🏢 비유로 이해하기</h2>
<table>
<thead>
<tr>
<th>비유</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>🌊 <strong>CDB (Container Database)</strong></td>
<td>아파트 건물 전체 (관리사무소 포함)</td>
</tr>
<tr>
<td>🏠 <strong>PDB (Pluggable Database)</strong></td>
<td>아파트 안에 있는 각각의 세대 (집, 방)</td>
</tr>
<tr>
<td>👮‍♂️ 관리자 권한</td>
<td>CDB는 전체 아파트를 관리하는 관리인 역할</td>
</tr>
<tr>
<td>👤 사용자 권한</td>
<td>PDB는 각 세대에 살고 있는 세입자, 각자 문도 다르고 공간도 분리됨</td>
</tr>
</tbody></table>
<hr>
<h2 id="📌-주요-차이점-정보">📌 주요 차이점 정보</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>CDB (Container DB)</th>
<th>PDB (Pluggable DB)</th>
</tr>
</thead>
<tbody><tr>
<td>정의</td>
<td>전체 Oracle 시스템의 틀</td>
<td>동임적인 실제 데이터베이스</td>
</tr>
<tr>
<td>구성 수</td>
<td>하나만 존재</td>
<td>여러 개 포함 가능</td>
</tr>
<tr>
<td>계정</td>
<td>C##계정, SYS, SYSTEM 등</td>
<td>일반 사용자, 서비스 계정 등</td>
</tr>
<tr>
<td>권한 부여</td>
<td>CDB에서는 전체 관리, PDB에 지정 필요</td>
<td>PDB 안에서 다른 권한 부여</td>
</tr>
<tr>
<td>Spring 연결</td>
<td>❌ 일반적으로 연결 안함</td>
<td>✅ <code>jdbc:oracle:thin:@localhost:1521/XEPDB1</code> 이러고 연결</td>
</tr>
<tr>
<td>사용 목적</td>
<td>DBA 관리, 시스템 전체 설정</td>
<td>실제 서비스 데이터 저장 및 사용</td>
</tr>
</tbody></table>
<hr>
<h2 id="🧪-예시로-정리">🧪 예시로 정리</h2>
<ul>
<li><code>CDB</code> = <code>오라클 전체 시스템 (건물)</code></li>
<li><code>PDB</code> = <code>서비스에 쓰이는 DB 하나하나 (방)</code></li>
<li><code>C##OUTVEN</code> 사용자 → CDB에서 생성됨 → <code>CREATE SESSION</code> 권한을 PDB 안에서도 다시 주어야 사용 가능</li>
</ul>
<hr>
<h2 id="🛠️-자주-쓰는-명령어">🛠️ 자주 쓰는 명령어</h2>
<pre><code class="language-sql">-- 현재 접속한 DB가 어디인지 확인
SELECT SYS_CONTEXT(&#39;USERENV&#39;,&#39;CON_NAME&#39;) FROM dual;

-- CDB에서 PDB 리스트 확인
SELECT name, open_mode FROM v$pdbs;

-- CDB → PDB 접속 전환
ALTER SESSION SET CONTAINER = XEPDB1;
</code></pre>
<hr>
<h2 id="✅-마무리">✅ 마무리</h2>
<ul>
<li>개발자는 대부만에서 <strong>PDB에 연결</strong>해서 사용해요.</li>
<li>CDB에서 권한을 줄 경우도 <strong>PDB에서도 다시 주어야 적용되요.</strong></li>
<li>Spring에서 에러나는 대부만에서 <strong>PDB에서 권한이 없어서</strong>예에요!</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[🌌 별 헤는 밤: S3 아키텍처 개선 회고록]]></title>
            <link>https://velog.io/@jade_95/%EB%B3%84-%ED%97%A4%EB%8A%94-%EB%B0%A4-S3-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EA%B0%9C%EC%84%A0-%ED%9A%8C%EA%B3%A0%EB%A1%9D</link>
            <guid>https://velog.io/@jade_95/%EB%B3%84-%ED%97%A4%EB%8A%94-%EB%B0%A4-S3-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EA%B0%9C%EC%84%A0-%ED%9A%8C%EA%B3%A0%EB%A1%9D</guid>
            <pubDate>Fri, 19 Sep 2025 06:10:09 GMT</pubDate>
            <description><![CDATA[<h1 id="☁️-s3--cloudfront-분리-구조-도입기">☁️ S3 + CloudFront 분리 구조 도입기</h1>
<blockquote>
<p><strong>업로드는 S3 Presigned URL, 조회는 CloudFront Signed URL</strong>로 성능과 보안을 동시에 확보한 과정</p>
</blockquote>
<h2 id="🎯-문제-상황">🎯 문제 상황</h2>
<h3 id="기존-구조의-한계">기존 구조의 한계</h3>
<ul>
<li><strong>단일 S3 URL</strong>: 업로드와 조회 모두 동일한 S3 URL 사용</li>
<li><strong>보안 취약점</strong>: 조회 URL이 영구적으로 노출되어 무단 접근 가능</li>
<li><strong>성능 제약</strong>: CDN 없이 S3 직접 접근으로 글로벌 사용자 경험 저하</li>
<li><strong>비용 증가</strong>: S3 직접 접근으로 인한 데이터 전송 비용 상승</li>
</ul>
<h3 id="요구사항">요구사항</h3>
<ul>
<li><strong>업로드</strong>: S3 Presigned URL로 직접 업로드 (서버 부하 최소화)</li>
<li><strong>조회</strong>: CloudFront Signed URL로 보안 강화 + CDN 성능</li>
<li><strong>기존 호환성</strong>: 기존 API 유지하면서 점진적 전환</li>
</ul>
<hr>
<h2 id="🔧-해결-과정">🔧 해결 과정</h2>
<h3 id="1단계-의존성-추가">1단계: 의존성 추가</h3>
<p><strong>build.gradle 수정</strong></p>
<pre><code class="language-gradle">// AWS S3 &amp; CloudFront
implementation &#39;software.amazon.awssdk:s3&#39;
implementation &#39;software.amazon.awssdk:cloudfront&#39;  // 추가
implementation &#39;software.amazon.awssdk:auth&#39;</code></pre>
<p><strong>불필요한 의존성 제거</strong></p>
<pre><code class="language-gradle">// 제거된 의존성
// testImplementation &#39;org.testcontainers:junit-jupiter&#39;</code></pre>
<h3 id="2단계-config-server-설정-추가">2단계: Config Server 설정 추가</h3>
<p><strong>로컬 개발 환경 (byeolnight-local.yml)</strong></p>
<pre><code class="language-yaml">cloud:
  aws:
    s3:
      bucket: byeolnight-bucket
    cloudfront:
      distribution-domain: &#39;dummy-cloudfront-domain.cloudfront.net&#39;
      key-pair-id: &#39;APKAI23HVI2C4JEXAMPLE&#39;
      private-key-path: &#39;/path/to/cloudfront-private-key.pem&#39;
      signed-url-expiration: 3600</code></pre>
<p><strong>운영 환경 (byeolnight-prod.yml)</strong></p>
<pre><code class="language-yaml">cloud:
  aws:
    cloudfront:
      distribution-domain: &#39;{cipher}d1234567890abcdef.cloudfront.net&#39;
      key-pair-id: &#39;{cipher}APKAI23HVI2C4JEXAMPLE&#39;
      private-key-path: &#39;/app/cloudfront-private-key.pem&#39;
      signed-url-expiration: 3600</code></pre>
<h3 id="3단계-s3service-리팩토링">3단계: S3Service 리팩토링</h3>
<p><strong>CloudFront 기능 추가</strong></p>
<pre><code class="language-java">@Value(&quot;${cloud.aws.cloudfront.distribution-domain}&quot;)
private String cloudFrontDomain;

@Value(&quot;${cloud.aws.cloudfront.key-pair-id}&quot;)
private String keyPairId;

@Value(&quot;${cloud.aws.cloudfront.private-key-path}&quot;)
private String privateKeyPath;

@Value(&quot;${cloud.aws.cloudfront.signed-url-expiration:3600}&quot;)
private int signedUrlExpiration;

/**
 * CloudFront Signed URL 생성 (조회/다운로드용)
 */
public String generateCloudFrontSignedUrl(String s3Key) {
    try {
        String resourceUrl = String.format(&quot;https://%s/%s&quot;, cloudFrontDomain, s3Key);
        Instant expiration = Instant.now().plusSeconds(signedUrlExpiration);

        Path privateKeyFile = Paths.get(privateKeyPath);

        CannedSignerRequest signerRequest = CannedSignerRequest.builder()
                .resourceUrl(resourceUrl)
                .privateKey(privateKeyFile)
                .keyPairId(keyPairId)
                .expirationDate(expiration)
                .build();

        CloudFrontUtilities utilities = CloudFrontUtilities.create();
        SignedUrl signedUrl = utilities.getSignedUrlWithCannedPolicy(signerRequest);

        return signedUrl.url();

    } catch (Exception e) {
        log.error(&quot;CloudFront Signed URL 생성 실패: {}&quot;, s3Key, e);
        // Fallback to direct S3 URL
        return String.format(&quot;%s/%s&quot;, baseUrl, s3Key);
    }
}</code></pre>
<p><strong>기존 메서드 수정</strong></p>
<pre><code class="language-java">// 업로드 시 CloudFront URL 반환
String viewUrl = generateCloudFrontSignedUrl(s3Key);
result.put(&quot;url&quot;, viewUrl);  // CloudFront 조회용
result.put(&quot;uploadUrl&quot;, presignedUrl);  // S3 업로드용</code></pre>
<h3 id="4단계-api-엔드포인트-분리">4단계: API 엔드포인트 분리</h3>
<p><strong>새로운 API 추가</strong></p>
<pre><code class="language-java">@PostMapping(&quot;/upload-url&quot;)
public ResponseEntity&lt;CommonResponse&lt;Map&lt;String, String&gt;&gt;&gt; getUploadUrl(
        @RequestParam(&quot;filename&quot;) String filename,
        @RequestParam(value = &quot;contentType&quot;, required = false) String contentType) {
    // S3 업로드 전용 API
}

@GetMapping(&quot;/view-url&quot;)
public ResponseEntity&lt;CommonResponse&lt;Map&lt;String, String&gt;&gt;&gt; getViewUrl(
        @RequestParam(&quot;s3Key&quot;) String s3Key) {
    // CloudFront 조회 전용 API
}

@PostMapping(&quot;/presigned-url&quot;)
@Deprecated
public ResponseEntity&lt;CommonResponse&lt;Map&lt;String, String&gt;&gt;&gt; getPresignedUrl() {
    // 기존 호환성을 위한 레거시 API
}</code></pre>
<h3 id="5단계-프론트엔드-수정">5단계: 프론트엔드 수정</h3>
<p><strong>s3Upload.ts 업데이트</strong></p>
<pre><code class="language-typescript">// 1. 업로드 URL 요청 (새로운 엔드포인트)
response = await axios.post(&#39;/files/upload-url&#39;, null, {
  params: { filename: file.name, contentType: file.type }
});

// 2. S3 직접 업로드
await fetch(presignedData.uploadUrl, {
  method: &#39;PUT&#39;,
  body: file,
  headers: { &#39;Content-Type&#39;: presignedData.contentType }
});

// 3. CloudFront 조회 URL 생성
const viewUrlResponse = await axios.get(&#39;/files/view-url&#39;, {
  params: { s3Key: presignedData.s3Key }
});
const viewUrl = viewUrlResponse.data.data.viewUrl;

// 4. CloudFront URL 반환
return {
  url: viewUrl,  // CloudFront URL
  s3Key: presignedData.s3Key,
  originalName: presignedData.originalName,
  contentType: presignedData.contentType
};</code></pre>
<hr>
<h2 id="📊-성과-및-효과">📊 성과 및 효과</h2>
<h3 id="보안-강화">보안 강화</h3>
<ul>
<li><strong>임시 URL</strong>: CloudFront Signed URL로 1시간 제한 접근</li>
<li><strong>무단 접근 차단</strong>: 서명된 URL 없이는 파일 접근 불가</li>
<li><strong>키 관리</strong>: AWS Key Pair 기반 안전한 서명 시스템</li>
</ul>
<h3 id="성능-향상">성능 향상</h3>
<ul>
<li><strong>CDN 활용</strong>: 전 세계 엣지 로케이션에서 빠른 파일 제공</li>
<li><strong>캐시 효율</strong>: CloudFront 캐싱으로 S3 직접 접근 감소</li>
<li><strong>대역폭 절약</strong>: 압축 및 최적화된 전송</li>
</ul>
<h3 id="비용-최적화">비용 최적화</h3>
<ul>
<li><strong>S3 요청 감소</strong>: CloudFront 캐시 히트로 S3 GET 요청 감소</li>
<li><strong>데이터 전송 비용</strong>: CloudFront 요금이 S3 직접 전송보다 저렴</li>
<li><strong>글로벌 서비스</strong>: 지역별 최적화된 비용 구조</li>
</ul>
<h3 id="아키텍처-개선">아키텍처 개선</h3>
<ul>
<li><strong>관심사 분리</strong>: 업로드(S3) vs 조회(CloudFront) 명확한 역할 분담</li>
<li><strong>확장성</strong>: CDN 기반으로 트래픽 증가에 유연한 대응</li>
<li><strong>모니터링</strong>: CloudFront 메트릭으로 상세한 사용량 분석</li>
</ul>
<hr>
<h2 id="🚀-배포-및-운영">🚀 배포 및 운영</h2>
<h3 id="cloudfront-배포-설정">CloudFront 배포 설정</h3>
<pre><code class="language-bash"># 1. CloudFront Distribution 생성
aws cloudfront create-distribution --distribution-config file://cloudfront-config.json

# 2. Key Pair 생성 및 등록
aws cloudfront create-public-key --public-key-config file://public-key-config.json

# 3. Private Key 서버 배포
scp cloudfront-private-key.pem server:/app/</code></pre>
<h3 id="모니터링-설정">모니터링 설정</h3>
<ul>
<li><strong>CloudWatch 메트릭</strong>: 요청 수, 캐시 히트율, 오류율</li>
<li><strong>로그 분석</strong>: CloudFront 액세스 로그로 사용 패턴 분석</li>
<li><strong>알림 설정</strong>: 오류율 임계값 초과 시 자동 알림</li>
</ul>
<h3 id="장애-대응">장애 대응</h3>
<ul>
<li><strong>Fallback 메커니즘</strong>: CloudFront 실패 시 S3 직접 URL 제공</li>
<li><strong>키 로테이션</strong>: 정기적인 Key Pair 교체 프로세스</li>
<li><strong>캐시 무효화</strong>: 긴급 시 CloudFront 캐시 즉시 무효화</li>
</ul>
<hr>
<h2 id="🔍-트러블슈팅">🔍 트러블슈팅</h2>
<h3 id="자주-발생하는-문제">자주 발생하는 문제</h3>
<p><strong>1. CloudFront Signed URL 생성 실패</strong></p>
<pre><code>원인: Private Key 파일 경로 오류 또는 권한 문제
해결: 파일 경로 확인 및 읽기 권한 부여</code></pre><p><strong>2. Key Pair ID 불일치</strong></p>
<pre><code>원인: CloudFront에 등록된 Key Pair ID와 설정값 불일치
해결: AWS 콘솔에서 Key Pair ID 재확인 후 설정 업데이트</code></pre><p><strong>3. 캐시 문제</strong></p>
<pre><code>원인: CloudFront 캐시로 인한 이전 버전 파일 제공
해결: 파일 업데이트 시 캐시 무효화 또는 버전 관리</code></pre><h3 id="성능-최적화-팁">성능 최적화 팁</h3>
<p><strong>1. 캐시 정책 최적화</strong></p>
<pre><code class="language-json">{
  &quot;CachePolicyId&quot;: &quot;custom-policy&quot;,
  &quot;TTL&quot;: {
    &quot;DefaultTTL&quot;: 86400,
    &quot;MaxTTL&quot;: 31536000
  }
}</code></pre>
<p><strong>2. 압축 활성화</strong></p>
<pre><code class="language-json">{
  &quot;Compress&quot;: true,
  &quot;ViewerProtocolPolicy&quot;: &quot;redirect-to-https&quot;
}</code></pre>
<p><strong>3. 지역별 최적화</strong></p>
<pre><code class="language-json">{
  &quot;PriceClass&quot;: &quot;PriceClass_100&quot;,
  &quot;Restrictions&quot;: {
    &quot;GeoRestriction&quot;: {
      &quot;RestrictionType&quot;: &quot;whitelist&quot;,
      &quot;Locations&quot;: [&quot;KR&quot;, &quot;JP&quot;, &quot;US&quot;]
    }
  }
}</code></pre>
<hr>
<h2 id="📈-향후-개선-계획">📈 향후 개선 계획</h2>
<h3 id="단기-계획-1-2개월">단기 계획 (1-2개월)</h3>
<ul>
<li><strong>이미지 최적화</strong>: CloudFront에서 WebP 변환 및 리사이징</li>
<li><strong>보안 강화</strong>: WAF 연동으로 DDoS 및 악성 요청 차단</li>
<li><strong>모니터링 고도화</strong>: 실시간 대시보드 및 알림 시스템</li>
</ul>
<h3 id="중기-계획-3-6개월">중기 계획 (3-6개월)</h3>
<ul>
<li><strong>멀티 CDN</strong>: CloudFront + 다른 CDN 조합으로 가용성 향상</li>
<li><strong>엣지 컴퓨팅</strong>: Lambda@Edge로 동적 이미지 처리</li>
<li><strong>비용 최적화</strong>: 사용 패턴 분석 기반 캐시 정책 자동 조정</li>
</ul>
<h3 id="장기-계획-6개월">장기 계획 (6개월+)</h3>
<ul>
<li><strong>글로벌 확장</strong>: 지역별 최적화된 CDN 전략</li>
<li><strong>AI 기반 최적화</strong>: 머신러닝 기반 캐시 예측 및 프리로딩</li>
<li><strong>완전 자동화</strong>: 인프라 코드화 및 자동 스케일링</li>
</ul>
<hr>
<h2 id="💡-교훈-및-베스트-프랙티스">💡 교훈 및 베스트 프랙티스</h2>
<h3 id="핵심-교훈">핵심 교훈</h3>
<ol>
<li><strong>점진적 전환</strong>: 기존 API 유지하면서 새로운 구조 도입</li>
<li><strong>Fallback 필수</strong>: 항상 대안 경로 준비</li>
<li><strong>모니터링 우선</strong>: 변경 사항의 영향도 실시간 추적</li>
<li><strong>보안과 성능</strong>: 두 마리 토끼를 모두 잡을 수 있는 설계</li>
</ol>
<h3 id="베스트-프랙티스">베스트 프랙티스</h3>
<ul>
<li><strong>설정 암호화</strong>: 민감한 CloudFront 설정은 반드시 암호화</li>
<li><strong>키 관리</strong>: Private Key 보안 저장 및 정기 로테이션</li>
<li><strong>캐시 전략</strong>: 파일 타입별 차별화된 캐시 정책</li>
<li><strong>비용 모니터링</strong>: CloudFront 사용량 정기 검토 및 최적화</li>
</ul>
<hr>
<p><strong>🎯 결론</strong>: S3 + CloudFront 분리 구조로 <strong>보안 강화 + 성능 향상 + 비용 최적화</strong>를 동시에 달성했습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🌙 별 헤는 밤: ERR_TOO_MANY_REDIRECTS에서 JWT 암호화 문제까지]]></title>
            <link>https://velog.io/@jade_95/%EB%B3%84-%ED%97%A4%EB%8A%94-%EB%B0%A4-ERRTOOMANYREDIRECTS%EC%97%90%EC%84%9C-JWT-%EC%95%94%ED%98%B8%ED%99%94-%EB%AC%B8%EC%A0%9C%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@jade_95/%EB%B3%84-%ED%97%A4%EB%8A%94-%EB%B0%A4-ERRTOOMANYREDIRECTS%EC%97%90%EC%84%9C-JWT-%EC%95%94%ED%98%B8%ED%99%94-%EB%AC%B8%EC%A0%9C%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Wed, 17 Sep 2025 22:38:57 GMT</pubDate>
            <description><![CDATA[<h2 id="📅-2025년-9월-18일-오전-7시-운영-서버-장애-발생">📅 2025년 9월 18일 오전 7시, 운영 서버 장애 발생</h2>
<p>평소와 같이 커피 한 잔과 함께 모니터링 대시보드를 확인하던 중, <strong>별 헤는 밤</strong> 서비스에 접속이 안 되는 것을 발견했다. 브라우저에는 무정하게도 <code>ERR_TOO_MANY_REDIRECTS</code> 오류만 반복되고 있었다.</p>
<blockquote>
<p>&quot;어제까지 잘 되던 사이트가 갑자기 왜...?&quot;</p>
</blockquote>
<h2 id="🔍-첫-번째-용의자-nginx-리다이렉트-중복">🔍 첫 번째 용의자: Nginx 리다이렉트 중복</h2>
<p>처음에는 당연히 <strong>Nginx 설정 문제</strong>라고 생각했다. 최근 배포 과정에서 메인 서버와 프론트엔드에 있는 <code>nginx.conf</code> 파일 둘 다 리다이렉트 코드가 중복되어 있었고, API 키를 여러 번 호출해야 하는 우리 웹서비스 특성상 이게 근본 원인일 거라 확신했다.</p>
<pre><code class="language-bash"># 급하게 Nginx 설정 수정
vim /etc/nginx/nginx.conf
# 중복된 리다이렉트 규칙 제거
sudo systemctl reload nginx</code></pre>
<p>다행히 수정 후 서비스가 정상화되었다. &quot;역시 Nginx 문제였구나&quot; 하며 안도의 한숨을 쉬었다.</p>
<h2 id="😱-데자뷰-또-다시-발생한-동일-증상">😱 데자뷰: 또 다시 발생한 동일 증상</h2>
<p>며칠 후, 서비스가 잘 굴러가고 있다고 생각하며 새로운 기능을 배포했다. 그런데...</p>
<p><strong>또 같은 이슈가 발생했다!</strong></p>
<p>이번에는 브라우저 오류가 아니라 <strong>서버 로그</strong>에서 더 심각한 문제를 발견했다:</p>
<pre><code>2025-09-18T07:01:29.821+09:00 ERROR 1 --- [byeolnight] [main] 
Caused by: java.lang.IllegalArgumentException: 
Could not resolve placeholder &#39;app.security.jwt.secret&#39; in value &quot;${app.security.jwt.secret}&quot;</code></pre><blockquote>
<p>&quot;Nginx 문제가 아니었다... 진짜 원인은 따로 있었구나.&quot;</p>
</blockquote>
<h2 id="🕵️-진짜-범인을-찾아서">🕵️ 진짜 범인을 찾아서</h2>
<h3 id="config-server는-살아있는데-jwt만-죽었다">&quot;Config Server는 살아있는데 JWT만 죽었다?&quot;</h3>
<p>로그를 자세히 보니 이상한 점이 있었다:</p>
<pre><code>✅ Located environment: name=byeolnight, profiles=[prod]
✅ MySQL 비밀번호: 정상 복호화
✅ Redis 비밀번호: 정상 복호화  
✅ AWS API 키들: 정상 복호화
❌ JWT 시크릿: Could not resolve placeholder</code></pre><blockquote>
<p>&quot;다른 암호화된 설정들은 멀쩡한데 JWT 시크릿만 왜...?&quot;</p>
</blockquote>
<h3 id="미스터리한-암호화-데이터">미스터리한 암호화 데이터</h3>
<p><code>byeolnight-prod.yml</code> 파일을 열어보니 JWT 시크릿이 이상했다:</p>
<pre><code class="language-yaml"># 정상적인 다른 설정들
password: &#39;{cipher}4f645acb62e6302d47f02b2d3b88c8cda3c90e64f91d042dc9ff2613955ba***&#39;

# 문제의 JWT 시크릿
jwt:
  secret: &#39;{cipher}eaebecbd17000326cf1b0281b5e9b9c905f10300270c747f95207cea0c029d34***&#39;</code></pre>
<p><strong>추리 결과:</strong></p>
<ul>
<li>파일 복사 과정에서 암호화 데이터 일부 손실?</li>
<li>Git 커밋 시 인코딩 문제?</li>
<li>다른 암호화 키로 암호화된 이력?</li>
</ul>
<blockquote>
<p>&quot;범인은... 손상된 암호화 데이터였다!&quot;</p>
</blockquote>
<h2 id="🚑-응급처치-일단-살려놓고-보자">🚑 응급처치: 일단 살려놓고 보자</h2>
<h3 id="평문으로라도-일단-서비스부터-살리자">&quot;평문으로라도 일단 서비스부터 살리자!&quot;</h3>
<p>운영 서비스가 다운된 상황에서 원인 분석보다는 <strong>빠른 복구</strong>가 우선이었다.</p>
<pre><code class="language-yaml"># 응급처치: JWT 시크릿을 평문으로 변경
jwt:
  secret: &#39;byeolnight-jwt-secret-key-2025-very-long-and-secure-key-for-production-use-only&#39;</code></pre>
<pre><code class="language-bash"># 서버 재시작
docker-compose restart app</code></pre>
<blockquote>
<p>&quot;휴... 일단 서비스는 살아났다. 이제 진짜 해결책을 찾아보자.&quot;</p>
</blockquote>
<h2 id="🔧-근본-해결-config-server로-재암호화">🔧 근본 해결: Config Server로 재암호화</h2>
<h3 id="encrypt-simplebat-너가-내-구원자다">&quot;encrypt-simple.bat, 너가 내 구원자다!&quot;</h3>
<p>평문으로 임시 해결한 후, 보안을 위해 <strong>제대로 된 암호화</strong>를 진행했다.</p>
<pre><code class="language-bash"># 1단계: Config Server 시작
cd config-server
gradlew.bat bootRun

# 2단계: JWT 시크릿 재암호화
encrypt-simple.bat &quot;byeolnight-jwt-secret-key-2025-very-long-and-secure-key-for-production-use-only&quot;</code></pre>
<p><strong>터미널 출력:</strong></p>
<pre><code>Encrypting: &quot;byeolnight-jwt-secret-key-2025-very-long-and-secure-key-for-production-use-only&quot;

8bab426a814eb620e297d3a336d22ebf6ede3b285003154b24898d87ba743416***[새로운 암호화 키 생성됨]</code></pre><blockquote>
<p>&quot;새로운 암호화 키가 생성되었다! 이제 이걸로 교체하자.&quot;</p>
</blockquote>
<h3 id="최종-해결-새-암호화-키-적용">최종 해결: 새 암호화 키 적용</h3>
<pre><code class="language-yaml"># byeolnight-prod.yml 최종 수정
jwt:
  secret: &#39;{cipher}8bab426a814eb620e297d3a336d22ebf6ede3b285003154b24898d87ba743416***&#39;</code></pre>
<h2 id="🎉-해피엔딩-별-헤는-밤이-다시-빛났다">🎉 해피엔딩: 별 헤는 밤이 다시 빛났다</h2>
<h3 id="드디어-정상-로그가-떴다">&quot;드디어 정상 로그가 떴다!&quot;</h3>
<pre><code class="language-bash"># 서버 재시작 후 로그 확인
docker logs -f byeolnight-app-1</code></pre>
<p><strong>기다리던 성공 로그:</strong></p>
<pre><code>✅ JwtAuthenticationFilter: Filter &#39;jwtAuthenticationFilter&#39; configured for use
✅ TomcatWebServer: Tomcat started on port 8080 (http)  
✅ ByeolnightApplication: Started ByeolnightApplication in 28.808 seconds
✅ 별 헤는 밤이 다시 살아났습니다! 🌟</code></pre><h3 id="검증-모든-기능이-정상-작동">검증: 모든 기능이 정상 작동</h3>
<ul>
<li>✅ JWT 토큰 생성/검증 완벽</li>
<li>✅ 사용자 로그인/로그아웃 정상</li>
<li>✅ API 호출 인증 성공</li>
<li>✅ 소셜 로그인 (Google, Kakao, Naver) 정상</li>
<li>✅ 실시간 채팅 WebSocket 연결 성공</li>
</ul>
<blockquote>
<p>&quot;이제야 마음 놓고 커피를 마실 수 있겠다... ☕&quot;</p>
</blockquote>
<h2 id="🔧-사용된-도구-및-기술">🔧 사용된 도구 및 기술</h2>
<h3 id="config-server-암호화-구조">Config Server 암호화 구조</h3>
<pre><code class="language-yaml"># Config Server (application.yml)
encrypt:
  key: byeolnight-config-encryption-key-2025!@#$%^&amp;*()
  fail-on-error: false</code></pre>
<h3 id="암호화-스크립트-encrypt-simplebat">암호화 스크립트 (encrypt-simple.bat)</h3>
<pre><code class="language-batch">@echo off
echo Encrypting: &quot;%1&quot;
curl -u config-admin:config-secret-2024 ^
  &quot;http://localhost:8888/encrypt&quot; ^
  -d &quot;%1&quot; ^
  -H &quot;Content-Type: text/plain&quot;</code></pre>
<h3 id="jwt-설정-구조">JWT 설정 구조</h3>
<pre><code class="language-java">@Component
public class JwtTokenProvider {
    public JwtTokenProvider(@Value(&quot;${app.security.jwt.secret}&quot;) String secret, 
                           StringRedisTemplate redisTemplate) {
        this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
        this.redisTemplate = redisTemplate;
    }
}</code></pre>
<h2 id="📚-학습-포인트">📚 학습 포인트</h2>
<h3 id="spring-cloud-config-암호화-특성">Spring Cloud Config 암호화 특성</h3>
<ul>
<li><strong>대칭키 암호화 (AES)</strong> 사용</li>
<li><strong>시간 기반 만료 없음</strong> (JWT와 다름)</li>
<li><strong>암호화 키 일치 시</strong> 언제든 복호화 가능</li>
<li><strong>데이터 무결성</strong> 중요</li>
</ul>
<h3 id="문제-해결-접근법">문제 해결 접근법</h3>
<ol>
<li><strong>증상 vs 원인</strong> 구분</li>
<li><strong>다른 설정과 비교</strong> 분석</li>
<li><strong>단계별 검증</strong> (임시 → 영구 해결)</li>
<li><strong>재현 가능한 해결책</strong> 적용</li>
</ol>
<h3 id="운영-환경-고려사항">운영 환경 고려사항</h3>
<ul>
<li>Config Server <strong>고가용성</strong> 필요</li>
<li>암호화 키 <strong>백업 및 관리</strong></li>
<li><strong>모니터링 및 알림</strong> 체계</li>
<li><strong>롤백 계획</strong> 수립</li>
</ul>
<h2 id="🚀-향후-개선-방안">🚀 향후 개선 방안</h2>
<h3 id="1-모니터링-강화">1. 모니터링 강화</h3>
<pre><code class="language-yaml"># Config Server Health Check
management:
  endpoints:
    web:
      exposure:
        include: health,info,decrypt</code></pre>
<h3 id="2-암호화-검증-자동화">2. 암호화 검증 자동화</h3>
<pre><code class="language-bash"># 배포 전 암호화 검증 스크립트
validate-config.sh byeolnight prod</code></pre>
<h3 id="3-백업-전략">3. 백업 전략</h3>
<ul>
<li>Config 파일 <strong>Git 이력 관리</strong></li>
<li>암호화 키 <strong>안전한 저장소</strong> 보관</li>
<li><strong>복구 절차</strong> 문서화</li>
</ul>
<h2 id="🎓-이-사건에서-배운-교훈들">🎓 이 사건에서 배운 교훈들</h2>
<h3 id="1-증상과-원인은-다를-수-있다">1. &quot;증상과 원인은 다를 수 있다&quot;</h3>
<ul>
<li>처음 <code>ERR_TOO_MANY_REDIRECTS</code>는 Nginx 문제로 보였지만</li>
<li>진짜 원인은 <strong>JWT 암호화 데이터 손상</strong>이었다</li>
<li><strong>근본 원인</strong>을 찾지 않으면 같은 문제가 반복된다</li>
</ul>
<h3 id="2-config-server-암호화는-생각보다-취약하다">2. &quot;Config Server 암호화는 생각보다 취약하다&quot;</h3>
<ul>
<li>파일 복사, Git 커밋 과정에서 데이터 손상 가능</li>
<li><strong>정기적인 암호화 데이터 검증</strong>이 필요하다</li>
<li>백업된 암호화 키 관리의 중요성</li>
</ul>
<h3 id="3-응급처치-→-근본해결-순서가-중요하다">3. &quot;응급처치 → 근본해결 순서가 중요하다&quot;</h3>
<ul>
<li>운영 서비스 다운 시: <strong>빠른 복구 우선</strong></li>
<li>안정화 후: <strong>보안을 고려한 근본 해결</strong></li>
<li>평문 → 재암호화 단계적 접근이 효과적</li>
</ul>
<hr>
<p><strong>에필로그:</strong> 그날 밤, 별 헤는 밤 서비스는 다시 평화롭게 사용자들의 우주 이야기를 담아내고 있었다. 때로는 예상치 못한 곳에서 문제가 발생하지만, <strong>차근차근 원인을 찾아 해결하는 과정</strong>이야말로 개발자의 진짜 실력이 아닐까? 🌙✨</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[운영체제(OS) 개념, 화장실로 이해해보기 💡]]></title>
            <link>https://velog.io/@jade_95/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9COS-%EA%B0%9C%EB%85%90-%ED%99%94%EC%9E%A5%EC%8B%A4%EB%A1%9C-%EC%9D%B4%ED%95%B4%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@jade_95/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9COS-%EA%B0%9C%EB%85%90-%ED%99%94%EC%9E%A5%EC%8B%A4%EB%A1%9C-%EC%9D%B4%ED%95%B4%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Wed, 27 Aug 2025 03:02:07 GMT</pubDate>
            <description><![CDATA[<h3 id="운영체제os-개념-화장실로-이해해보기-💡"><strong>운영체제(OS) 개념, 화장실로 이해해보기</strong> 💡</h3>
<p>운영체제 개념이 어렵게 느껴지는 분들을 위해, 복잡한 이론 대신 우리에게 익숙한 <strong>&#39;화장실&#39;</strong>을 비유로 들어 쉽게 이해해보자</p>
<hr>
<h3 id="등장인물-비유-정리"><strong>등장인물 비유 정리</strong></h3>
<ul>
<li><strong>프로세스 (Process)</strong>: 화장실을 이용하려는 <strong>사람</strong></li>
<li><strong>임계영역 (Critical Section)</strong>: 한 번에 한 사람만 들어갈 수 있는 <strong>변기칸</strong></li>
<li><strong>자원 (Resource)</strong>: 변기칸을 사용할 권한, 즉 <strong>열쇠</strong></li>
<li><strong>운영체제 (OS)</strong>: 화장실을 관리하는 <strong>관리자</strong></li>
</ul>
<hr>
<h3 id="각-개념별-비유-설명"><strong>각 개념별 비유 설명</strong></h3>
<p><strong>🔐 뮤텍스 (Mutex: Mutual Exclusion)</strong>
변기칸이 하나뿐인 화장실에서, <strong>열쇠를 가진 사람만</strong> 화장실을 사용할 수 있는 상황입니다. 열쇠는 오직 한 사람에게만 주어지며, 그 사람이 나오기 전에는 다른 사람은 절대 들어갈 수 없습니다.</p>
<ul>
<li><strong>특징</strong>: 상호 배제(Mutual Exclusion)를 보장하며, <strong>열쇠(자원)를 소유한 사람만</strong> 잠금을 해제할 수 있습니다.</li>
</ul>
<p><strong>⚖️ 세마포어 (Semaphore)</strong>
변기칸이 여러 개 있는 화장실에서, 동시에 여러 사람(예: 3명)이 들어갈 수 있는 상황입니다. 세마포어는 <strong>사용 가능한 변기 수(자원 수)</strong>를 숫자로 관리하며, 이 숫자가 0이 되면 다음 사람은 대기해야 합니다.</p>
<ul>
<li><strong>특징</strong>: <strong>제한된 동시 접근</strong>을 허용하며, 열쇠(자원)를 소유하지 않은 사람도 다른 사람이 열쇠를 반납하면 사용할 수 있습니다.</li>
</ul>
<p><strong>🔒 락 (Lock)</strong>
임계영역(변기칸)에 들어간 사람을 보호하는 <strong>잠금장치</strong>입니다. <strong>Lock</strong>은 Mutex, Semaphore 등을 포괄하는 넓은 개념으로, 자원에 대한 접근을 제어하기 위해 사용되는 모든 잠금 기술을 의미합니다.</p>
<p><strong>🧳 모니터 (Monitor)</strong>
OS가 변기칸의 잠금과 조건 대기를 <strong>알아서 자동으로 관리</strong>해주는 스마트 화장실 시스템입니다. 사용자(개발자)는 락이나 조건 변수를 직접 조작할 필요가 없어 실수가 줄어듭니다.</p>
<ul>
<li><strong>예시</strong>: 변기칸마다 자동 잠금장치가 있어 사람이 들어가면 자동으로 잠기고, 사용 시간이 초과되면 자동으로 잠금이 해제되어 다음 사람이 이용할 수 있게 되는 구조입니다. 자바의 <code>synchronized</code>가 대표적인 예입니다.</li>
</ul>
<p><strong>🔄 스핀락 (Spinlock)</strong>
화장실 문 앞에서 대기자가 <strong>계속 문을 당겨보며</strong> &quot;혹시 문이 열렸나?&quot; 확인하는 상황입니다.</p>
<ul>
<li><strong>특징</strong>: 문이 곧 열릴 것을 예상하고 CPU를 놓지 않고 기다리는 방식이라, 짧은 대기 시간에는 효율적이지만 대기 시간이 길어지면 CPU 낭비가 심해집니다.</li>
</ul>
<p><strong>⛔ 데드락 (Deadlock)</strong>
A가 1번 변기칸의 열쇠를 갖고 2번 변기칸 열쇠를 기다리고, B가 2번 변기칸 열쇠를 갖고 1번 변기칸 열쇠를 기다리는 상황입니다. <strong>서로 다른 자원을 점유하고, 서로의 자원을 기다리느라</strong> 아무도 변기칸을 사용할 수 없게 됩니다.</p>
<ul>
<li><strong>핵심</strong>: 단순한 자원 점유가 아니라 <strong>&#39;서로 상대방의 자원을 기다리는&#39; 상호 대기</strong>가 데드락의 본질입니다.</li>
</ul>
<p><strong>↔️ 문맥 교환 (Context Switching)</strong>
변기를 이용하던 사람이 다음 사람에게 <strong>&#39;바톤 터치&#39;</strong>를 하는 과정입니다. 화장실 관리자(OS)가 사용 중인 사람의 상태(어디까지 사용했는지)를 저장하고, 다음 사람을 호출하는 행위를 의미합니다.</p>
<p><strong>🌍 스케줄링 (Scheduling)</strong>
다음에 변기칸을 사용할 <strong>사람을 화장실 관리자(OS)가 결정</strong>하는 과정입니다.</p>
<ul>
<li><strong>예시</strong>:<ul>
<li><strong>FIFO(선착순)</strong>: 먼저 온 사람이 먼저 사용</li>
<li><strong>라운드 로빈(Round Robin)</strong>: 모든 사람이 공평하게 일정 시간만 사용하고 다음 사람에게 넘기기</li>
</ul>
</li>
</ul>
<hr>
<h3 id="전체-흐름-요약"><strong>전체 흐름 요약</strong></h3>
<ol>
<li><strong>스케줄링</strong>을 통해 화장실을 이용할 다음 사람이 결정됩니다.</li>
<li>이용자가 변기칸에 들어가려 할 때, <strong>Lock, Mutex, Semaphore</strong>를 사용해 자원(변기칸)을 경쟁적으로 사용하지 않도록 제어합니다.</li>
<li>한 사람의 사용이 끝나면 <strong>문맥 교환(Context Switching)</strong>이 발생하여 다음 사람이 화장실을 사용할 수 있게 됩니다.</li>
<li>만약 여러 사람이 서로의 자원을 끝없이 기다리는 상황이 되면 <strong>데드락</strong>이 발생해 아무도 화장실을 이용할 수 없게 됩니다.</li>
</ol>
<p><strong>마무리:</strong>
이처럼 비유를 통해 OS 개념을 이해하면, 복잡한 이론을 쉽게 설명하고 면접에서도 당신의 뛰어난 <strong>설명력</strong>을 어필할 수 있습니다. 운영체제는 개발자의 필수 기초 지식이므로 반드시 짚고 넘어가세요!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🔒 Redisson 분산락 도입 해보기]]></title>
            <link>https://velog.io/@jade_95/Redisson-%EB%B6%84%EC%82%B0%EB%9D%BD-%EB%8F%84%EC%9E%85-%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@jade_95/Redisson-%EB%B6%84%EC%82%B0%EB%9D%BD-%EB%8F%84%EC%9E%85-%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Mon, 04 Aug 2025 06:04:44 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>&quot;분산락이 필요한 이유가 뭔가요? Redis와 Redisson의 차이점은?&quot;</strong>  </p>
</blockquote>
<h2 id="🎯-왜-redisson을-도입했을까">🎯 왜 Redisson을 도입했을까?</h2>
<h3 id="기존-상황">기존 상황</h3>
<ul>
<li><strong>Redis Template</strong>: 단순한 캐싱과 세션 관리에 사용</li>
<li><strong>동시성 문제</strong>: 포인트 지급, 출석체크 등에서 Race Condition 발생 가능</li>
<li><strong>기술면접 대비</strong>: 분산 시스템에서의 동시성 제어에 대한 깊은 이해 필요</li>
</ul>
<h2 id="🏗️-구현-과정에서-겪은-시행착오">🏗️ 구현 과정에서 겪은 시행착오</h2>
<h3 id="1단계-기본-구조-설계">1단계: 기본 구조 설계</h3>
<pre><code class="language-java">@Service
public class RedissonCacheService {
    private final RedissonClient redissonClient;

    // 기본적인 캐시 기능부터 시작
    public &lt;T&gt; void set(String key, T value, Duration ttl) {
        RBucket&lt;T&gt; bucket = redissonClient.getBucket(key);
        bucket.set(value, ttl.toSeconds(), TimeUnit.SECONDS);
    }
}</code></pre>
<h3 id="2단계-분산-맵-기능-추가-시-첫-번째-실수">2단계: 분산 맵 기능 추가 시 첫 번째 실수</h3>
<pre><code class="language-java">// ❌ 처음 시도: RMap 사용
public &lt;K, V&gt; void putToMap(String mapName, K key, V value, Duration ttl) {
    RMap&lt;K, V&gt; map = redissonClient.getMap(mapName);
    map.put(key, value, ttl.toSeconds(), TimeUnit.SECONDS); // 컴파일 에러!
}</code></pre>
<p><strong>문제점</strong>: <code>RMap</code>은 개별 키에 TTL을 설정할 수 없음</p>
<h3 id="3단계-rmapcache로-해결">3단계: RMapCache로 해결</h3>
<pre><code class="language-java">// ✅ 해결: RMapCache 사용
public &lt;K, V&gt; void putToMap(String mapName, K key, V value, Duration ttl) {
    RMapCache&lt;K, V&gt; map = redissonClient.getMapCache(mapName);
    map.put(key, value, ttl.toSeconds(), TimeUnit.SECONDS); // 성공!
}</code></pre>
<h2 id="🤔-개발-중-고민했던-포인트들">🤔 개발 중 고민했던 포인트들</h2>
<h3 id="1-인수가-2개여야-하는데-4개로-입력되어-있어">1. &quot;인수가 2개여야 하는데 4개로 입력되어 있어?&quot;</h3>
<pre><code class="language-java">// 고민: TTL 없는 버전 vs TTL 있는 버전
putToMap(mapName, key, value)           // 2개 인수
putToMap(mapName, key, value, ttl)      // 4개 인수</code></pre>
<p><strong>결론</strong>: 분산락 효율성을 위해 TTL 포함 버전 선택</p>
<ul>
<li>자동 만료로 데드락 방지</li>
<li>메모리 효율성 확보</li>
<li>예측 가능한 생명주기 관리</li>
</ul>
<h3 id="2-rmap-vs-rmapcache-선택">2. RMap vs RMapCache 선택</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>RMap</th>
<th>RMapCache</th>
</tr>
</thead>
<tbody><tr>
<td>개별 키 TTL</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td>메모리 효율성</td>
<td>보통</td>
<td>높음</td>
</tr>
<tr>
<td>분산락 적합성</td>
<td>낮음</td>
<td>높음</td>
</tr>
</tbody></table>
<h2 id="🧹-코드-정리-과정">🧹 코드 정리 과정</h2>
<h3 id="불필요한-코드-제거">불필요한 코드 제거</h3>
<pre><code class="language-java">// 제거된 코드들
- RMap import 및 getMap() 메서드 (RMapCache만 사용)
- get() 메서드의 Class&lt;T&gt; type 파라미터 (미사용)</code></pre>
<h3 id="최종-핵심-기능">최종 핵심 기능</h3>
<ul>
<li><strong>RBucket</strong>: 단순 키-값 저장 (TTL 지원)</li>
<li><strong>RMapCache</strong>: 분산 맵 저장 (개별 키 TTL 지원)</li>
</ul>
<h2 id="🎯-결론">🎯 결론</h2>
<p><strong>Q: Redis와 Redisson의 차이점은?</strong></p>
<pre><code>A: Redis는 인메모리 데이터베이스이고, Redisson은 Redis 기반의 
   Java 분산 객체 및 서비스 프레임워크입니다.

   - Redis Template: 단순한 키-값 조작
   - Redisson: 분산락, 분산 컬렉션 등 고급 기능 제공</code></pre><p><strong>Q: 분산락이 왜 필요한가요?</strong></p>
<pre><code>A: 분산 환경에서 동시성 제어를 위해 필요합니다.

   예시: 포인트 지급 시스템
   - 사용자가 동시에 여러 요청을 보낼 때
   - 중복 지급 방지를 위한 락 필요
   - TTL로 데드락 방지</code></pre><p><strong>Q: RMap과 RMapCache의 차이점은?</strong></p>
<pre><code>A: 개별 키 TTL 지원 여부가 핵심 차이점입니다.

   - RMap: 맵 전체에만 TTL 설정 가능
   - RMapCache: 개별 키마다 다른 TTL 설정 가능
   - 분산락에서는 RMapCache가 더 적합</code></pre><h2 id="📈-도입-효과">📈 도입 효과</h2>
<h3 id="기술적-효과">기술적 효과</h3>
<ul>
<li><strong>동시성 제어</strong>: Race Condition 방지</li>
<li><strong>메모리 효율성</strong>: TTL 기반 자동 정리</li>
<li><strong>확장성</strong>: 분산 환경에서의 안정성 확보</li>
</ul>
<h3 id="학습-효과">학습 효과</h3>
<ul>
<li><strong>분산 시스템 이해도</strong> 향상</li>
<li><strong>동시성 제어</strong> 메커니즘 학습</li>
<li><strong>기술면접 대비</strong> 실무 경험 축적</li>
</ul>
<h2 id="🔮-향후-활용-계획">🔮 향후 활용 계획</h2>
<h3 id="적용-예정-도메인">적용 예정 도메인</h3>
<ol>
<li><strong>포인트 시스템</strong>: 중복 지급 방지</li>
<li><strong>출석체크</strong>: 동시 요청 제어</li>
<li><strong>게시글 좋아요</strong>: 중복 클릭 방지</li>
<li><strong>채팅방 입장</strong>: 동시 접속 제어</li>
</ol>
<h3 id="추가-학습-계획">추가 학습 계획</h3>
<ul>
<li><strong>분산락 패턴</strong> 심화 학습</li>
<li><strong>성능 테스트</strong> 및 최적화</li>
<li><strong>모니터링</strong> 및 <strong>알람</strong> 시스템 구축</li>
</ul>
<h2 id="💡-회고">💡 회고</h2>
<h3 id="잘한-점">잘한 점</h3>
<ul>
<li><strong>실무 중심 접근</strong>: 왜 도입하였는지 명확한 목표제시</li>
<li><strong>점진적 개선</strong>: 문제 발견 → 해결 → 정리의 체계적 과정</li>
<li><strong>코드 품질</strong>: 불필요한 코드 제거로 가독성 향상</li>
</ul>
<h3 id="아쉬운-점">아쉬운 점</h3>
<ul>
<li><strong>성능 테스트</strong> 부족: 실제 부하 상황에서의 검증 필요</li>
<li><strong>모니터링</strong> 미비: 분산락 사용량 및 성능 지표 수집 필요</li>
</ul>
<h3 id="다음-단계">다음 단계</h3>
<ul>
<li><strong>실제 도메인 적용</strong>: 포인트 시스템에 분산락 적용</li>
<li><strong>성능 측정</strong>: 락 획득/해제 시간 모니터링</li>
<li><strong>장애 시나리오</strong>: 락 타임아웃, 네트워크 분할 등 대응 방안 수립</li>
</ul>
<hr>
<p><strong>🎯 핵심 메시지</strong></p>
<blockquote>
<p>기술면접 대비를 위해 시작한 Redisson 도입이 실제 분산 시스템의 동시성 제어에 대한 깊은 이해로 이어졌습니다. 단순한 기술 스택 추가가 아닌, 실무에서 마주할 수 있는 문제들을 미리 경험하고 해결책을 준비하는 과정이었습니다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[📚 Spring DI 정리 – 의존성 주입, Autowired, RequiredArgsConstructor, Mock 이해]]></title>
            <link>https://velog.io/@jade_95/Spring-DI-%EC%A0%95%EB%A6%AC-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85-Autowired-RequiredArgsConstructor-Mock-%EC%9D%B4%ED%95%B4</link>
            <guid>https://velog.io/@jade_95/Spring-DI-%EC%A0%95%EB%A6%AC-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85-Autowired-RequiredArgsConstructor-Mock-%EC%9D%B4%ED%95%B4</guid>
            <pubDate>Tue, 29 Jul 2025 08:40:28 GMT</pubDate>
            <description><![CDATA[<h2 id="✅-의존성-주입di-dependency-injection이란">✅ 의존성 주입(DI, Dependency Injection)이란?</h2>
<hr>
<blockquote>
<p>내가 직접 객체를 생성하지 않고, Spring이 필요한 객체를 대신 주입해주는 개념</p>
<p>→ 객체 간 결합도를 낮추고 테스트 가능성을 높임</p>
</blockquote>
<hr>
<h3 id="🔥-di-방식-3가지">🔥 DI 방식 3가지</h3>
<table>
<thead>
<tr>
<th>방식</th>
<th>설명</th>
<th>실무 추천</th>
</tr>
</thead>
<tbody><tr>
<td><strong>필드 주입</strong><code>@Autowired</code></td>
<td>객체를 만든 후 필드에 값 주입</td>
<td>❌ 지양</td>
</tr>
<tr>
<td><strong>생성자 주입</strong><code>@RequiredArgsConstructor</code></td>
<td>객체 생성 시점에 주입</td>
<td>✅ 표준</td>
</tr>
<tr>
<td><strong>세터 주입</strong><code>setX(...)</code></td>
<td>필요할 때 주입 가능</td>
<td>❌ 지양 (불변 깨짐)</td>
</tr>
</tbody></table>
<hr>
<h3 id="🆚-autowired-vs-requiredargsconstructor">🆚 <code>@Autowired</code> vs <code>@RequiredArgsConstructor</code></h3>
<table>
<thead>
<tr>
<th>항목</th>
<th><code>@Autowired</code> (필드 주입)</th>
<th>생성자 주입 (<code>@RequiredArgsConstructor</code>)</th>
</tr>
</thead>
<tbody><tr>
<td>주입 시점</td>
<td>객체 만든 후 나중에 주입</td>
<td>객체 생성 시점에 주입</td>
</tr>
<tr>
<td><code>final</code> 사용</td>
<td>❌ 불가능</td>
<td>✅ 가능 (불변성 유지)</td>
</tr>
<tr>
<td>테스트 용이성</td>
<td>❌ 어렵다</td>
<td>✅ Mock 주입 쉬움</td>
</tr>
<tr>
<td>가독성</td>
<td>❌ 숨겨져 있어 추적 어려움</td>
<td>✅ 생성자에 명시적으로 드러남</td>
</tr>
<tr>
<td>오류 탐지</td>
<td>❌ 실행 시점 오류 (NPE 등)</td>
<td>✅ 컴파일 타임 오류로 빠르게 확인</td>
</tr>
<tr>
<td>Spring 공식 권장</td>
<td>❌ 아님</td>
<td>✅ 공식 추천 방식</td>
</tr>
</tbody></table>
<hr>
<h3 id="🍪-개발자-감각-비유">🍪 개발자 감각 비유</h3>
<ul>
<li><code>@Autowired</code> = <strong>쟁반</strong>: 먼저 쟁반 놓고 나중에 이것저것 올림 (중간에 이상한 거 섞일 수 있음)</li>
<li>생성자 주입 = <strong>쿠키틀</strong>: 처음부터 필요한 재료만 넣을 수 있는 틀, 안전하고 변경 불가</li>
</ul>
<hr>
<h3 id="❌-setter는-왜-지양할까">❌ <code>@Setter</code>는 왜 지양할까?</h3>
<table>
<thead>
<tr>
<th>이유</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>보안 문제</td>
<td>비밀번호, 이메일 등 민감 데이터가 외부에서 변경될 수 있음</td>
</tr>
<tr>
<td>불변성 깨짐</td>
<td><code>final</code> 불가, 중간에 값이 바뀔 수 있음</td>
</tr>
<tr>
<td>객체 안정성 저하</td>
<td>런타임에 예상 못 한 동작 발생 위험</td>
</tr>
</tbody></table>
<p>✅ 실무에선 <code>@Getter</code> + <code>@Builder</code> 조합 + 불변 객체 설계를 선호</p>
<hr>
<h3 id="🧪-mock이란">🧪 Mock이란?</h3>
<blockquote>
<p>테스트용 가짜 객체</p>
<p>실제 동작은 하지 않지만 <strong>호출 여부 / 반환값 설정</strong> 등을 통해 <strong>테스트를 빠르고 안전하게</strong> 할 수 있음</p>
</blockquote>
<table>
<thead>
<tr>
<th>도구</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td><code>Mockito.mock(...)</code></td>
<td>진짜 객체 대신 가짜 객체 생성</td>
</tr>
<tr>
<td><code>when(...).thenReturn(...)</code></td>
<td>원하는 값 반환하도록 설정</td>
</tr>
<tr>
<td><code>verify(...)</code></td>
<td>메서드가 몇 번 호출됐는지 검증</td>
</tr>
</tbody></table>
<p>✅ JUnit + Mockito 조합은 Spring Boot 테스트의 사실상 표준</p>
<hr>
<h3 id="✅-실무-기준-요약">✅ 실무 기준 요약</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>권장 여부</th>
<th>이유</th>
</tr>
</thead>
<tbody><tr>
<td><code>@Autowired</code></td>
<td>❌</td>
<td>변경 위험, 테스트 어려움</td>
</tr>
<tr>
<td>생성자 주입 + <code>@RequiredArgsConstructor</code></td>
<td>✅</td>
<td>불변성, 안정성, 가독성 우수</td>
</tr>
<tr>
<td><code>@Setter</code></td>
<td>❌</td>
<td>외부 변경 위험, 불변 객체 설계와 충돌</td>
</tr>
<tr>
<td>Mock 테스트</td>
<td>✅</td>
<td>외부 영향 없이 빠르고 안정적인 테스트 가능</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[📘 Partitioning & Sharding 정리]]></title>
            <link>https://velog.io/@jade_95/Partitioning-Sharding-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@jade_95/Partitioning-Sharding-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 29 Jul 2025 08:36:57 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-개념-요약">📌 개념 요약</h2>
<hr>
<table>
<thead>
<tr>
<th>항목</th>
<th>Partitioning</th>
<th>Sharding</th>
</tr>
</thead>
<tbody><tr>
<td>적용 범위</td>
<td>하나의 DB 인스턴스</td>
<td>여러 개의 DB 인스턴스</td>
</tr>
<tr>
<td>목적</td>
<td>성능 최적화, 관리 용이</td>
<td>확장성 확보, 분산 처리</td>
</tr>
<tr>
<td>구조</td>
<td>파티션은 DB 내부 구성</td>
<td>샤드는 독립적인 DB</td>
</tr>
<tr>
<td>확장성</td>
<td>제한적 (Scale-up 중심)</td>
<td>뛰어남 (Scale-out 중심)</td>
</tr>
<tr>
<td>구현 위치</td>
<td>DBMS 내부 (Oracle, MySQL 등 지원)</td>
<td>애플리케이션 또는 샤딩 미들웨어</td>
</tr>
<tr>
<td>쿼리 처리</td>
<td>단일 인스턴스 내에서 가능</td>
<td>Cross-shard 쿼리는 복잡</td>
</tr>
<tr>
<td>예시</td>
<td>대형 ERP 시스템</td>
<td>대규모 SNS, 쇼핑몰, SaaS 등</td>
</tr>
</tbody></table>
<hr>
<h3 id="✅-1-partitioning-파티셔닝">✅ 1. Partitioning (파티셔닝)</h3>
<blockquote>
<p>Partitioning은 하나의 데이터베이스 내에서 테이블 데이터를 여러 파티션(partition)으로 분할하는 기법입니다.</p>
</blockquote>
<h3 id="🔹-종류">🔹 종류</h3>
<ul>
<li><p><strong>수평 파티셔닝 (Horizontal Partitioning)</strong></p>
<p>  행(Row) 기준으로 데이터 분할</p>
<p>  → 예: 사용자 ID 1<del>10000 / 10001</del>20000 등</p>
</li>
<li><p><strong>수직 파티셔닝 (Vertical Partitioning)</strong></p>
<p>  열(Column) 기준으로 데이터 분할</p>
<p>  → 예: 자주 조회되는 컬럼 / 드물게 쓰이는 컬럼 구분</p>
</li>
</ul>
<h3 id="🔹-장점">🔹 장점</h3>
<ul>
<li>쿼리 성능 최적화 (필요한 파티션만 조회)</li>
<li>데이터 관리 및 유지보수 용이</li>
<li>백업 및 복구 단위 설정 가능</li>
</ul>
<h3 id="🔹-단점">🔹 단점</h3>
<ul>
<li>잘못된 파티셔닝 설계 시 성능 저하</li>
<li>JOIN/집계 연산 복잡해질 수 있음</li>
<li>단일 DB 서버에 의존</li>
</ul>
<hr>
<h3 id="✅-2-sharding-샤딩">✅ 2. Sharding (샤딩)</h3>
<blockquote>
<p>Sharding은 데이터를 여러 개의 DB 서버에 분산시켜 저장하는 방식입니다.</p>
<p>수평 파티셔닝을 <strong>분산 시스템 수준</strong>으로 확장한 개념입니다.</p>
</blockquote>
<h3 id="🔹-샤딩-방식">🔹 샤딩 방식</h3>
<ul>
<li><strong>Range-based Sharding</strong>: 값의 범위로 샤드 분할</li>
<li><strong>Hash-based Sharding</strong>: 키 값을 해시로 계산하여 샤드 결정</li>
<li><strong>Directory-based Sharding</strong>: 라우팅 테이블로 샤드 위치 추적</li>
</ul>
<h3 id="🔹-장점-1">🔹 장점</h3>
<ul>
<li>수평 확장성 (서버 추가로 확장 가능)</li>
<li>부하 분산</li>
<li>장애 격리 가능</li>
</ul>
<h3 id="🔹-단점-1">🔹 단점</h3>
<ul>
<li>샤딩 로직 복잡 (개발/운영 부담)</li>
<li>Cross-shard JOIN, 트랜잭션 어렵다</li>
<li>샤드 재배치(Migration) 까다로움</li>
</ul>
<hr>
<h3 id="💼-실무-활용-사례">💼 실무 활용 사례</h3>
<h3 id="🔹-partitioning">🔹 Partitioning</h3>
<table>
<thead>
<tr>
<th>활용 분야</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>로그 데이터 관리</strong></td>
<td>월별 파티션 생성 → 오래된 데이터 *아카이빙</td>
</tr>
<tr>
<td><strong>거래/결제 내역</strong></td>
<td>사용자 ID 또는 날짜로 분할해 조회 성능 향상</td>
</tr>
<tr>
<td><strong>ERP 시스템</strong></td>
<td>회계연도, 부서 기준으로 분할 → 보고서 효율적</td>
</tr>
</tbody></table>
<h3 id="📦-아카이빙이란">📦 아카이빙이란…</h3>
<blockquote>
<p>📁 &quot;오래된 파일을 백업 폴더로 옮기는 것&quot;</p>
<p>⛔️ &quot;삭제하는 게 아니라, 덜 중요한 위치로 보내는 것&quot;</p>
</blockquote>
<h3 id="🔹-sharding">🔹 Sharding</h3>
<table>
<thead>
<tr>
<th>활용 분야</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>SNS 서비스</strong></td>
<td>사용자 ID 해시로 샤딩 → 서버 간 부하 분산</td>
</tr>
<tr>
<td><strong>전자상거래</strong></td>
<td>지역/국가 기반 샤딩 → 지연 감소, 법률 대응</td>
</tr>
<tr>
<td><strong>SaaS 플랫폼</strong></td>
<td>고객사 단위로 데이터 분리 → 보안성 강화</td>
</tr>
<tr>
<td><strong>게임 서버</strong></td>
<td>서버(월드)별 샤딩 → 유저 접속량 분산 가능</td>
</tr>
</tbody></table>
<hr>
<h3 id="🧠-보완-팁">🧠 보완 팁</h3>
<ul>
<li>Partitioning은 MySQL, PostgreSQL, Oracle 등에서 기본 기능으로 제공</li>
<li>Sharding은 MongoDB, Cassandra, Vitess, Citus 등 분산 DB에서 자주 사용</li>
<li>둘은 함께 사용하는 경우도 많음 (ex. 샤딩된 DB 안에 파티셔닝된 테이블)</li>
</ul>
<hr>
<h3 id="🖼️-구조-다이어그램">🖼️ 구조 다이어그램</h3>
<p><img src="https://velog.velcdn.com/images/jade_95/post/abe13c71-1f26-4d7d-9cde-2b0309c877dc/image.png" alt=""></p>
<h2 id="✅-partitioning-vs-sharding-비유-정리">✅ Partitioning vs Sharding 비유 정리</h2>
<hr>
<h3 id="📦-partitioning-파티셔닝">📦 Partitioning (파티셔닝)</h3>
<blockquote>
<p>하나의 창고 안을 구역별로 나눠서 데이터 관리</p>
</blockquote>
<ul>
<li>창고는 <strong>하나</strong>지만</li>
<li>그 안에서 <strong>구역(파티션)</strong>을 나눠서 <strong>월별, ID별, 카테고리별</strong> 등으로 데이터를 관리</li>
</ul>
<p>🟡 예시</p>
<pre><code>복사편집
하나의 사용자 테이블을 월별로 나눔
└─ 1월 파티션
└─ 2월 파티션
└─ 3월 파티션</code></pre><p>📌 특징 요약</p>
<ul>
<li>DB 인스턴스는 <strong>하나</strong></li>
<li>성능 최적화, 관리 편의를 위한 내부 분할</li>
<li>DBMS가 직접 파티션 처리 가능</li>
</ul>
<hr>
<h3 id="🏢-sharding-샤딩">🏢 Sharding (샤딩)</h3>
<blockquote>
<p>창고를 아예 여러 개 지어서 데이터를 나눠 보관</p>
</blockquote>
<ul>
<li><strong>서울 지점</strong>에는 서울 고객 데이터</li>
<li><strong>부산 지점</strong>에는 부산 고객 데이터</li>
<li><strong>대구 지점</strong>에는 대구 고객 데이터</li>
</ul>
<p>👉 이때 각 지점이 <strong>샤드(Shard)</strong>입니다.</p>
<p>🟢 핵심 개념 정리</p>
<ul>
<li><strong>양식은 똑같다</strong> → 같은 테이블 구조 (스키마)</li>
<li><strong>보관 중인 데이터는 다르다</strong> → 지역별, ID 범위별 데이터 분산</li>
<li><strong>지점끼리는 독립적이다</strong> → DB 인스턴스도 각각 따로 존재</li>
</ul>
<p>📌 특징 요약</p>
<ul>
<li>DB 인스턴스가 <strong>여러 개</strong></li>
<li>대규모 시스템에서 확장성과 성능을 위해 사용</li>
<li>샤딩 로직은 직접 구현하거나 미들웨어 사용 필요</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[☕ CORS를 스타벅스 비유로 쉽게 이해해보자]]></title>
            <link>https://velog.io/@jade_95/CORS%EB%A5%BC-%EC%8A%A4%ED%83%80%EB%B2%85%EC%8A%A4-%EB%B9%84%EC%9C%A0%EB%A1%9C-%EC%99%84%EB%B2%BD%ED%95%98%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jade_95/CORS%EB%A5%BC-%EC%8A%A4%ED%83%80%EB%B2%85%EC%8A%A4-%EB%B9%84%EC%9C%A0%EB%A1%9C-%EC%99%84%EB%B2%BD%ED%95%98%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 26 Jul 2025 08:55:31 GMT</pubDate>
            <description><![CDATA[<p>웹 개발하다 보면 누구나 한 번쯤은 마주치는 <strong>CORS 에러</strong>.  
처음 보면 굉장히 당황스럽고,  </p>
<blockquote>
<p>“왜 요청은 성공했는데 브라우저는 응답을 못 받지?”<br>라는 혼란이 생기죠.</p>
</blockquote>
<p>오늘은 이 <strong>CORS(Cross-Origin Resource Sharing)</strong> 개념을<br><strong>스타벅스 비유</strong>로 쉽고 재밌게 설명해보겠습니다. 😎</p>
<hr>
<h2 id="🏬-배경-설정">🏬 배경 설정</h2>
<p>우리는 제가 운영중인 프로젝트를 기준으로 설명해볼게요:</p>
<ul>
<li>프론트엔드 (React) → <code>https://byeolnight.com</code></li>
<li>백엔드 (Spring Boot) → <code>http://localhost:8080</code> (또는 내부 API 주소)</li>
<li>Nginx → 모든 요청을 받아 React 빌드 파일 or Spring API로 전달</li>
</ul>
<hr>
<h2 id="🎭-비유-등장인물">🎭 비유 등장인물</h2>
<table>
<thead>
<tr>
<th>역할</th>
<th>시스템 구성 요소</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>손님</td>
<td>브라우저 사용자</td>
<td>웹에서 앱을 사용하는 유저</td>
</tr>
<tr>
<td>스타벅스 입구</td>
<td>Nginx</td>
<td>요청을 받고 어디로 보낼지 결정하는 프록시</td>
</tr>
<tr>
<td>카페 공간</td>
<td>React 앱</td>
<td>유저가 머무는 공간 (정적 파일 UI)</td>
</tr>
<tr>
<td>사이렌오더</td>
<td>fetch, axios</td>
<td>브라우저가 백엔드에 API 요청 보내는 방식</td>
</tr>
<tr>
<td>바리스타</td>
<td>Spring Boot 서버</td>
<td>데이터를 처리하고 응답해주는 백엔드</td>
</tr>
<tr>
<td>주문 확인 포스기</td>
<td>CORS 정책</td>
<td>이 요청을 <strong>진짜 허용해도 되는지</strong> 확인하는 보안 시스템</td>
</tr>
<tr>
<td>손님의 출신 매장 정보</td>
<td>Origin</td>
<td>요청을 보낸 도메인 정보</td>
</tr>
</tbody></table>
<hr>
<h2 id="🧾-시나리오">🧾 시나리오</h2>
<h3 id="1-손님이-카페에-들어온다">1. 손님이 카페에 들어온다</h3>
<ul>
<li>유저가 <code>https://byeolnight.com</code> 에 접속</li>
<li>React 앱이 로딩되고, 카페 화면이 브라우저에 뜸</li>
</ul>
<h3 id="2-손님이-아이스-아메리카노-하나요-버튼-클릭">2. 손님이 “아이스 아메리카노 하나요~” 버튼 클릭</h3>
<ul>
<li>React 앱에서 <code>/api/post/create</code>로 주문 요청을 보냄</li>
<li>이 요청은 Nginx를 통해 Spring 서버로 전달됨</li>
</ul>
<h3 id="3-바리스타가-주문을-만들기-전에-포스기cors가-주문서를-스캔">3. 바리스타가 주문을 만들기 전에, 포스기(CORS)가 주문서를 스캔</h3>
<ul>
<li>포스기: “이 요청은 어디서 온 거지?”</li>
<li>요청 Header에 있는 <code>Origin: https://byeolnight.com</code> 확인</li>
</ul>
<h3 id="✅-만약-백엔드에서-허용했다면">✅ 만약 백엔드에서 허용했다면?</h3>
<p>Spring 서버가 응답에 이렇게 알려줌:</p>
<pre><code class="language-http">Access-Control-Allow-Origin: https://byeolnight.com
Access-Control-Allow-Credentials: true</code></pre>
<p>포스기 OK! 바리스타는 주문을 만들어 손님에게 응답 전달 → <strong>성공!</strong></p>
<hr>
<h3 id="❌-만약-허용하지-않은-origin에서-요청했다면">❌ 만약 허용하지 않은 Origin에서 요청했다면?</h3>
<p>손님은 실제로는 <code>https://evil-cafe.com</code>에서 요청했는데,
Spring 서버는 이 Origin을 허용하지 않음.</p>
<blockquote>
<p>포스기: “어? 이 손님은 우리 매장 손님 아닌데?”<br>→ <strong>주문 거절! (CORS 에러 발생)</strong></p>
</blockquote>
<hr>
<h2 id="🔒-cors가-왜-필요한가요">🔒 CORS가 왜 필요한가요?</h2>
<p>브라우저는 보안상, 다른 Origin에서 오는 요청을 무조건 신뢰하지 않아요.<br>특히 쿠키 인증, 민감한 데이터 요청이 포함되면 더욱 엄격해집니다.</p>
<p>그래서 서버는 반드시 <strong>명시적으로 허락된 Origin만 허용</strong>해야 해요.</p>
<hr>
<h2 id="👀-개발자가-자주-마주치는-에러">👀 개발자가 자주 마주치는 에러</h2>
<pre><code>Access to fetch at &#39;http://localhost:8080/api/user&#39; 
from origin &#39;http://localhost:3000&#39; has been blocked 
by CORS policy: No &#39;Access-Control-Allow-Origin&#39; header is present.</code></pre><blockquote>
<p>→ 요청은 갔는데 응답이 “CORS 정책 위반”으로 막힌 경우입니다.</p>
</blockquote>
<hr>
<h2 id="🛠️-spring-boot에서-cors-설정하기">🛠️ Spring Boot에서 CORS 설정하기</h2>
<pre><code class="language-java">@Configuration
public class WebConfig implements WebMvcConfigurer {
  @Override
  public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping(&quot;/api/**&quot;)
      .allowedOriginPatterns(&quot;https://*.byeolnight.com&quot;)
      .allowedMethods(&quot;GET&quot;, &quot;POST&quot;, &quot;PUT&quot;, &quot;DELETE&quot;)
      .allowCredentials(true);
  }
}</code></pre>
<p>✅ 핵심 포인트:</p>
<ul>
<li><code>allowedOriginPatterns</code>: 와일드카드 지원 (<code>https://*.byeolnight.com</code>)</li>
<li><code>allowCredentials(true)</code>: 쿠키 인증 허용 시 꼭 필요</li>
<li><code>allowedMethods</code>: 허용할 HTTP 메서드 지정</li>
</ul>
<hr>
<h2 id="✅-비유-요약-정리">✅ 비유 요약 정리</h2>
<table>
<thead>
<tr>
<th>구성 요소</th>
<th>비유</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>React 앱</td>
<td>카페 공간</td>
<td>유저가 UI를 통해 앱과 상호작용</td>
</tr>
<tr>
<td>Nginx</td>
<td>스타벅스 입구</td>
<td>요청을 어디로 보낼지 분기</td>
</tr>
<tr>
<td>Spring</td>
<td>바리스타</td>
<td>요청을 처리하고 응답 생성</td>
</tr>
<tr>
<td>CORS</td>
<td>포스기</td>
<td>요청이 진짜 우리 고객인지 판단 (Origin 확인)</td>
</tr>
<tr>
<td>credentials</td>
<td>쿠폰</td>
<td>인증 정보 (쿠키 등)</td>
</tr>
<tr>
<td>CORS 미설정</td>
<td>포스기가 없는 매장</td>
<td>무조건 모든 외부 손님 차단됨 (브라우저가 막음)</td>
</tr>
</tbody></table>
<hr>
<h2 id="🧠-한-줄-정리">🧠 한 줄 정리</h2>
<blockquote>
<p><strong>CORS는 백엔드가 “이 요청, 진짜 우리 손님 맞아?”를 판단하는 보안 절차다.</strong><br>설정 안 하면, 요청은 가도 <strong>브라우저가 응답을 끝까지 못 받는다.</strong></p>
</blockquote>
<hr>
<h2 id="✍️-마치며">✍️ 마치며</h2>
<p>기술적인 개념도 중요하지만, 이렇게 <strong>비유를 통해 감각적으로 이해</strong>하는 게<br>실전에서 에러를 빠르게 파악하고 대응하는 데 훨씬 큰 도움이 됩니다.</p>
<p>📌 <strong>여러분도 이제부터 브라우저 콘솔에 CORS 에러가 뜨면, 포스기가 주문을 거절한 장면을 떠올리세요.</strong>
바리스타는 아무 문제 없고, <strong>문 앞에서 보안이 막은 거니까요!</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🎉 SpringCamp 2025 후기 – 실무 아키텍처와 철학이 만나는 개발자의 축제]]></title>
            <link>https://velog.io/@jade_95/SpringCamp-2025-%ED%9B%84%EA%B8%B0-%EC%8B%A4%EB%AC%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EC%99%80-%EC%B2%A0%ED%95%99%EC%9D%B4-%EB%A7%8C%EB%82%98%EB%8A%94-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%EC%B6%95%EC%A0%9C</link>
            <guid>https://velog.io/@jade_95/SpringCamp-2025-%ED%9B%84%EA%B8%B0-%EC%8B%A4%EB%AC%B4-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EC%99%80-%EC%B2%A0%ED%95%99%EC%9D%B4-%EB%A7%8C%EB%82%98%EB%8A%94-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%9D%98-%EC%B6%95%EC%A0%9C</guid>
            <pubDate>Tue, 22 Jul 2025 07:38:31 GMT</pubDate>
            <description><![CDATA[<p>지난주 토요일, 강남 삼성역 인근에서 열린 <strong>SpringCamp 2025</strong>에 다녀왔습니다.<br>사실 처음엔 “개발자 행사야 다 거기서 거기지”라는 마음도 조금 있었는데,<br>결론부터 말하자면 <strong>기술, 조직, 사고방식까지 통으로 자극받고 돌아온 값진 경험</strong>이었습니다.</p>
<p><img src="https://velog.velcdn.com/images/jade_95/post/32708474-dc5d-4a09-95c7-932fa4b545f2/image.jpg" alt=""></p>
<p>이번 후기에선 단순한 요약을 넘어서,<br>제가 <strong>실제로 느끼고 배운 점</strong>, 그리고 <strong>실무와 포트폴리오에 어떻게 적용할지</strong>까지 함께 정리해보려 합니다.</p>
<hr>
<h2 id="✅-행사-전체-분위기">✅ 행사 전체 분위기</h2>
<ul>
<li>장소: 스페이스쉐어 삼성 B1 (서울 강남구)</li>
<li>참가자: 현업 백엔드 개발자 + 커뮤니티 중심 개발자들</li>
<li>특징: <strong>Track 1 / Track 2 분리</strong>, 자유롭게 이동 가능</li>
</ul>
<p>슬로건이 정말 인상 깊었습니다.</p>
<blockquote>
<p><strong>“빨리 가려면 혼자가고, 멀리 가려면 함께 가라.”</strong></p>
</blockquote>
<p>그 말처럼, 기술만이 아니라 <strong>동료 개발자들과 함께 성장할 수 있는 커뮤니티 문화</strong>가 강하게 느껴졌습니다.</p>
<hr>
<h2 id="🧭-내가-선택한-세션-루트">🧭 내가 선택한 세션 루트</h2>
<table>
<thead>
<tr>
<th>시간</th>
<th>트랙</th>
<th>세션</th>
</tr>
</thead>
<tbody><tr>
<td>10:30</td>
<td>Track 2</td>
<td>인가 플랫폼과 HR SaaS 복잡성 (이명현)</td>
</tr>
<tr>
<td>11:30</td>
<td>Track 2</td>
<td>Amazon Q Developer + 생성형 AI (이상현)</td>
</tr>
<tr>
<td>13:15</td>
<td>Track 2</td>
<td>올리브영 물류 시스템 개선기</td>
</tr>
<tr>
<td>14:15</td>
<td>Track 1</td>
<td>🔥 실전! MSA 트랜잭션 개발 가이드</td>
</tr>
<tr>
<td>15:15</td>
<td>Track 2</td>
<td>카카오뱅크 Spring Boot Starter</td>
</tr>
<tr>
<td>16:15</td>
<td>Track 2</td>
<td>레일웨이 지향 프로그래밍과 Spring</td>
</tr>
</tbody></table>
<hr>
<h2 id="🔍-실전-msa-트랜잭션-개발-가이드--김용욱">🔍 실전! MSA 트랜잭션 개발 가이드 – 김용욱</h2>
<p><strong>주제 요약</strong>  </p>
<ul>
<li>MSA에서 데이터 정합성 유지</li>
<li>Saga / Outbox / Compensation 등 실제 사례 중심의 분산 트랜잭션 설명</li>
</ul>
<p><strong>기억에 남는 내용</strong></p>
<ul>
<li>서비스 간 동기 호출보다 <strong>이벤트 기반 처리</strong>가 실무에서 더 안전하다는 점</li>
<li><strong>&quot;완벽한 일관성은 포기하되, 예측 가능한 흐름은 보장하라&quot;</strong> 는 설계 철학</li>
</ul>
<p><strong>내가 얻은 인사이트</strong></p>
<pre><code class="language-plaintext">📌 내 프로젝트의 게시글 작성 → 알림 발송 로직에 Outbox 패턴 적용할 계획
📌 트랜잭션 경계와 이벤트 발행 타이밍을 더 명확히 관리할 필요성 느낌</code></pre>
<hr>
<h2 id="🧱-카카오뱅크-spring-boot-starter--손수민">🧱 카카오뱅크 Spring Boot Starter – 손수민</h2>
<p><strong>주제 요약</strong>  </p>
<ul>
<li>공통 기능을 사내 starter 모듈로 만들어서 서비스별로 쉽게 사용하게 만든 구조 소개</li>
</ul>
<p><strong>재미있었던 표현</strong></p>
<blockquote>
<p>&quot;하울의 움직이는 성 같은 레거시 시스템을 해체하기 위한 모듈화 전략&quot;</p>
</blockquote>
<p><strong>Starter에 포함된 주요 기능</strong></p>
<ul>
<li>고정 길이 메시지 직렬화 (Jackson Custom Serializer)</li>
<li>사내 API용 HTTP Client 추상화</li>
<li>분산 트레이싱: <code>traceId + baggage</code> 구조</li>
</ul>
<p><strong>내가 얻은 인사이트</strong></p>
<pre><code class="language-plaintext">📦 공통 로깅, 예외, 인증 모듈을 따로 분리해서 나도 starter 구조 연습해볼 계획</code></pre>
<hr>
<h2 id="⚙️-레일웨이-지향-프로그래밍과-spring--이선협">⚙️ 레일웨이 지향 프로그래밍과 Spring – 이선협</h2>
<p><strong>주제 요약</strong>  </p>
<ul>
<li>함수형 예외 흐름 처리 전략: ROP (Railway Oriented Programming)</li>
<li>성공/실패 흐름을 명시적으로 분기 처리</li>
<li><code>Result</code>, <code>Either</code>, <code>recover</code>, <code>fold</code>, <code>panic</code> 등의 개념 활용</li>
</ul>
<p><strong>인상 깊었던 문장</strong></p>
<blockquote>
<p>&quot;예외는 던지는 게 아니라, 다루는 것이다. 성공/실패 모두 하나의 값이다.&quot;</p>
</blockquote>
<p><strong>Spring에서의 적용 팁</strong></p>
<ul>
<li><code>@Transactional</code>은 예외 발생 시만 rollback → <code>Result.failure</code>는 rollback 안 됨</li>
<li>따라서 필요한 경우 명시적으로 <code>.orElseThrow()</code>를 사용해 예외로 전환해야 함</li>
</ul>
<p><strong>내가 얻은 인사이트</strong></p>
<pre><code class="language-java">Result&lt;User&gt; user = validate(input)
  .flatMap(this::findUser)
  .flatMap(this::verifyPassword)
  .orElseThrow(UnauthorizedException::new);</code></pre>
<hr>
<h2 id="💬-그-외-세션-요약-간단-정리">💬 그 외 세션 요약 (간단 정리)</h2>
<ul>
<li><p><strong>올리브영 물류 시스템 개선기</strong></p>
<ul>
<li>운영 현장의 물류 흐름을 개발적으로 풀어낸 흥미로운 사례</li>
<li>실측 데이터 기반 개선 접근이 좋았음</li>
</ul>
</li>
<li><p><strong>Amazon Q Developer + 생성형 AI</strong></p>
<ul>
<li>AI가 점점 &quot;함께 개발하는 팀원&quot;이 되어가고 있음을 실감</li>
</ul>
</li>
<li><p><strong>HR SaaS 인가 플랫폼 설계</strong></p>
<ul>
<li>다양한 ABAC/RBAC 전략의 장단점 비교</li>
<li>실무에서 느낀 보안 설계 현실이 인상 깊었음</li>
</ul>
</li>
</ul>
<hr>
<h2 id="🧠-행사에서-얻은-깨달음">🧠 행사에서 얻은 깨달음</h2>
<ul>
<li>단순히 “기능 구현”이 아닌, <strong>&quot;어떻게 구조화하고 설계할 것인가&quot;</strong>가 실력의 기준</li>
<li>설계를 잘 한다는 건, <strong>공통성과 예외 흐름을 통제 가능하게 만든다는 것</strong></li>
<li>커뮤니티 속 개발자들의 경험을 듣는 건 그 자체로도 설계 스킬을 높이는 길</li>
</ul>
<hr>
<h2 id="🚀-나의-적용-계획">🚀 나의 적용 계획</h2>
<ul>
<li><code>Result&lt;T&gt;</code> 도입해서 서비스 흐름 개선 + rollback 전략 실험</li>
<li>알림/이벤트 처리 로직에 Outbox 적용</li>
<li>공통 모듈 분리 → mini spring-boot-starter 패키징 시도</li>
<li>Kotlin 기반 ROP 실험 (직접 모나드 구현)</li>
</ul>
<hr>
<h2 id="🙏-마치며">🙏 마치며</h2>
<p>이번 SpringCamp 2025는 단순한 기술 컨퍼런스가 아니었습니다.<br><strong>“왜 이렇게 코드를 짜야 하는가?”라는 질문을 스스로에게 던지는 계기</strong>였고,<br>앞으로 개발자로서 한 단계 더 나아가기 위한 훌륭한 이정표였습니다.</p>
<p>내년에도 꼭 참석하고 싶고,<br>그땐 청중이 아닌 <strong>발표자 자격으로 무대에 서는 걸 목표로</strong> 삼아봅니다.</p>
<blockquote>
<p>감사합니다, KSUG 🙇‍♂️</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[🔐 AuthenticationProvider 커스텀 구현 정리]]></title>
            <link>https://velog.io/@jade_95/AuthenticationProvider-%EC%BB%A4%EC%8A%A4%ED%85%80-%EA%B5%AC%ED%98%84-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@jade_95/AuthenticationProvider-%EC%BB%A4%EC%8A%A4%ED%85%80-%EA%B5%AC%ED%98%84-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 19 Jun 2025 11:49:50 GMT</pubDate>
            <description><![CDATA[<h2 id="✅-authenticationprovider란">✅ AuthenticationProvider란?</h2>
<blockquote>
<p>Spring Security에서 <strong>실제 인증 로직을 담당하는 핵심 컴포넌트</strong></p>
</blockquote>
<ul>
<li>사용자의 ID/PW 또는 JWT 토큰 등으로부터 인증을 수행하고</li>
<li>인증된 사용자 정보(<code>Authentication</code>)를 반환함</li>
<li>다양한 인증 방식을 Provider로 분리할 수 있음 (예: Form Login, JWT, OAuth2 등)</li>
</ul>
<hr>
<h2 id="🧠-동작-구조-요약">🧠 동작 구조 요약</h2>
<pre><code class="language-text">AuthenticationManager
      ↓ (delegates)
AuthenticationProvider(s)
      ↓
authenticate() 수행 → 성공 시 Authentication 객체 반환</code></pre>
<ul>
<li><code>AuthenticationManager</code>는 여러 Provider 중 하나에 위임하여 인증 수행</li>
<li><code>supports()</code> 메서드로 어떤 타입의 인증을 처리할지 판단</li>
</ul>
<hr>
<h2 id="🔧-커스텀-구현-예시-jwt-기반">🔧 커스텀 구현 예시 (JWT 기반)</h2>
<pre><code class="language-java">@Component
public class JwtAuthenticationProvider implements AuthenticationProvider {

    private final UserDetailsService userDetailsService;
    private final JwtTokenProvider jwtTokenProvider;

    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String token = (String) authentication.getCredentials();
        String username = jwtTokenProvider.getUsername(token);

        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        if (!jwtTokenProvider.validateToken(token, userDetails)) {
            throw new BadCredentialsException(&quot;Invalid JWT token&quot;);
        }

        return new UsernamePasswordAuthenticationToken(userDetails, token, userDetails.getAuthorities());
    }

    public boolean supports(Class&lt;?&gt; authentication) {
        return JwtAuthenticationToken.class.isAssignableFrom(authentication);
    }
}</code></pre>
<hr>
<h2 id="⚙-jwtauthenticationtoken-클래스-예시">⚙ JwtAuthenticationToken 클래스 예시</h2>
<pre><code class="language-java">public class JwtAuthenticationToken extends UsernamePasswordAuthenticationToken {
    public JwtAuthenticationToken(String token) {
        super(null, token);
    }
}</code></pre>
<hr>
<h2 id="🧩-연동-구조-요약">🧩 연동 구조 요약</h2>
<ol>
<li>JWT 필터에서 토큰 추출</li>
<li>JwtAuthenticationToken을 생성하여 AuthenticationManager에 전달</li>
<li>AuthenticationManager → JwtAuthenticationProvider로 위임</li>
<li>Provider에서 검증 후, 인증 완료된 Authentication 객체 반환</li>
<li>SecurityContextHolder에 저장</li>
</ol>
<hr>
<h2 id="🧠-면접용-정리-멘트">🧠 면접용 정리 멘트</h2>
<blockquote>
<p>“AuthenticationProvider는 Spring Security에서 실질적인 인증 로직을 담당하는 컴포넌트입니다.
JWT 기반 인증의 경우 커스텀 Provider를 구현해 토큰에서 사용자 정보를 추출하고,
인증된 Authentication 객체를 반환함으로써 유연한 인증 방식을 구성할 수 있습니다.”</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[🔐 Spring Security 인증 필터 흐름 정리]]></title>
            <link>https://velog.io/@jade_95/Spring-Security-%EC%9D%B8%EC%A6%9D-%ED%95%84%ED%84%B0-%ED%9D%90%EB%A6%84-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@jade_95/Spring-Security-%EC%9D%B8%EC%A6%9D-%ED%95%84%ED%84%B0-%ED%9D%90%EB%A6%84-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 19 Jun 2025 11:47:48 GMT</pubDate>
            <description><![CDATA[<h2 id="✅-spring-security-인증-구조-요약">✅ Spring Security 인증 구조 요약</h2>
<blockquote>
<p>스프링 시큐리티는 필터 기반으로 작동하며, 인증/인가 과정을 체계적으로 분리하여 처리함.
<code>FilterChain</code> → <code>AuthenticationFilter</code> → <code>AuthenticationManager</code> → <code>Provider</code> 흐름으로 구성됨.</p>
</blockquote>
<hr>
<h2 id="🔁-전체-인증-흐름-순서">🔁 전체 인증 흐름 순서</h2>
<h3 id="1-클라이언트-요청">1. 클라이언트 요청</h3>
<ul>
<li>로그인 시: <code>/login</code>, 또는 사용자 지정 엔드포인트</li>
<li>일반 요청 시: <code>/api/**</code> 등</li>
</ul>
<h3 id="2-security-filter-chain">2. Security Filter Chain</h3>
<ul>
<li>요청이 들어오면 <code>FilterChainProxy</code>에서 등록된 시큐리티 필터 체인을 거침</li>
</ul>
<h3 id="3-주요-필터-구성">3. 주요 필터 구성</h3>
<table>
<thead>
<tr>
<th>필터</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td><code>UsernamePasswordAuthenticationFilter</code></td>
<td>로그인 시 ID/PW 기반 인증 수행</td>
</tr>
<tr>
<td><code>OncePerRequestFilter</code> (JWT 커스텀 필터)</td>
<td>매 요청마다 JWT 토큰 파싱 및 사용자 인증 처리</td>
</tr>
<tr>
<td><code>ExceptionTranslationFilter</code></td>
<td>인증/인가 실패 예외 처리</td>
</tr>
<tr>
<td><code>FilterSecurityInterceptor</code></td>
<td>권한(인가) 검사 수행</td>
</tr>
</tbody></table>
<hr>
<h2 id="🧩-jwt-사용-시-흐름-커스텀-인증-필터-포함">🧩 JWT 사용 시 흐름 (커스텀 인증 필터 포함)</h2>
<pre><code class="language-text">클라이언트 → 요청
        ↓
SecurityFilterChain (여러 필터들)
        ↓
✅ JwtAuthenticationFilter (OncePerRequestFilter 상속)
        ↓
JWT 검증 &amp; 사용자 정보 추출
        ↓
SecurityContextHolder.setAuthentication(...)
        ↓
인증 성공 → 다음 필터로 전달</code></pre>
<hr>
<h2 id="🔒-인증-후-흐름">🔒 인증 후 흐름</h2>
<ul>
<li><code>Authentication</code> 객체가 <code>SecurityContextHolder</code>에 저장됨</li>
<li>이후 컨트롤러/서비스에서 <code>@AuthenticationPrincipal</code>, <code>SecurityContextHolder.getContext().getAuthentication()</code> 등으로 접근 가능</li>
</ul>
<hr>
<h2 id="📦-주요-클래스-요약">📦 주요 클래스 요약</h2>
<table>
<thead>
<tr>
<th>구성 요소</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>Authentication</code></td>
<td>인증 정보 객체 (id, role, credentials 등 포함)</td>
</tr>
<tr>
<td><code>UserDetails</code></td>
<td>사용자 상세 정보 (CustomUserDetails로 구현)</td>
</tr>
<tr>
<td><code>AuthenticationProvider</code></td>
<td>인증 처리 로직 구현체 (e.g. JWT, DB 조회 기반 등)</td>
</tr>
<tr>
<td><code>SecurityContextHolder</code></td>
<td>인증된 사용자를 요청 스레드 내에 저장하는 컨텍스트</td>
</tr>
</tbody></table>
<hr>
<h2 id="🧠-면접용-정리-멘트">🧠 면접용 정리 멘트</h2>
<blockquote>
<p>“Spring Security는 필터 기반 구조로 요청이 들어오면 먼저 여러 보안 필터를 거칩니다.
JWT를 사용하는 경우 <code>OncePerRequestFilter</code>를 상속한 커스텀 필터에서 토큰을 검증하고,
인증 정보를 <code>SecurityContextHolder</code>에 저장하여 이후 인가 처리까지 연동되도록 구성합니다.”</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[🔄 Java ThreadPool 구조 정리]]></title>
            <link>https://velog.io/@jade_95/Java-ThreadPool-%EA%B5%AC%EC%A1%B0-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@jade_95/Java-ThreadPool-%EA%B5%AC%EC%A1%B0-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 19 Jun 2025 11:46:07 GMT</pubDate>
            <description><![CDATA[<h2 id="✅-threadpool이란">✅ ThreadPool이란?</h2>
<blockquote>
<p>자바에서 다수의 요청을 처리하기 위해 <strong>미리 생성된 쓰레드 집합(Pool)을 재사용하는 구조</strong></p>
</blockquote>
<ul>
<li>매번 새 쓰레드를 만들지 않고, 일정 개수의 쓰레드를 <strong>반복 사용하여 성능을 최적화</strong></li>
<li>주로 비동기 작업, 백그라운드 작업, 대규모 요청 처리 등에 사용됨</li>
</ul>
<hr>
<h2 id="🧠-왜-필요한가">🧠 왜 필요한가?</h2>
<table>
<thead>
<tr>
<th>문제</th>
<th>ThreadPool의 해결 방식</th>
</tr>
</thead>
<tbody><tr>
<td>매번 new Thread() 생성 → 성능 저하</td>
<td>쓰레드를 재사용해서 자원 낭비 방지</td>
</tr>
<tr>
<td>쓰레드 너무 많음 → OOM 가능성</td>
<td>쓰레드 개수 제한 가능 (최대 스레드 수 설정)</td>
</tr>
<tr>
<td>작업 큐 미지원</td>
<td>작업 큐(BlockingQueue)를 통해 작업을 순차 처리</td>
</tr>
</tbody></table>
<hr>
<h2 id="⚙-핵심-구성-요소">⚙ 핵심 구성 요소</h2>
<table>
<thead>
<tr>
<th>구성요소</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Core Pool Size</td>
<td>기본 유지되는 스레드 수</td>
</tr>
<tr>
<td>Maximum Pool Size</td>
<td>최대 스레드 수 (큐가 꽉 찼을 때까지 확장)</td>
</tr>
<tr>
<td>Queue (작업 큐)</td>
<td>실행 전 대기 중인 작업 보관 (BlockingQueue)</td>
</tr>
<tr>
<td>RejectedExecutionHandler</td>
<td>큐/스레드 모두 꽉 찼을 때 처리 방식 (예외, 무시 등)</td>
</tr>
</tbody></table>
<hr>
<h2 id="🔧-사용-예시-코드">🔧 사용 예시 코드</h2>
<pre><code class="language-java">ExecutorService executor = Executors.newFixedThreadPool(5);

for (int i = 0; i &lt; 10; i++) {
    executor.submit(() -&gt; {
        System.out.println(Thread.currentThread().getName() + &quot; 작업 처리 중&quot;);
    });
}

executor.shutdown();</code></pre>
<hr>
<h2 id="☑️-executors-팩토리-메서드-종류">☑️ Executors 팩토리 메서드 종류</h2>
<table>
<thead>
<tr>
<th>메서드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>newFixedThreadPool(n)</td>
<td>고정된 개수의 쓰레드 유지</td>
</tr>
<tr>
<td>newCachedThreadPool()</td>
<td>요청마다 새로운 스레드 → 재사용함 (무한 확장 위험)</td>
</tr>
<tr>
<td>newSingleThreadExecutor()</td>
<td>쓰레드 1개로 모든 작업 순차 처리</td>
</tr>
<tr>
<td>newScheduledThreadPool(n)</td>
<td>주기적 작업 예약 (스케줄링)</td>
</tr>
</tbody></table>
<hr>
<h2 id="🔍-실무에서-주의할-점">🔍 실무에서 주의할 점</h2>
<ul>
<li><code>Executors</code>는 내부적으로 <code>ThreadPoolExecutor</code>를 감싸고 있어 <strong>세밀한 제어가 어려움</strong></li>
<li>실무에서는 직접 <code>ThreadPoolExecutor</code>를 생성해 <strong>큐 종류, 정책, 예외 처리 전략</strong> 등을 명시적으로 설정함</li>
</ul>
<hr>
<h2 id="🧠-면접용-정리-멘트">🧠 면접용 정리 멘트</h2>
<blockquote>
<p>“ThreadPool은 자바에서 다수의 작업을 효율적으로 처리하기 위한 구조로, 미리 생성된 쓰레드를 재사용하여 성능을 최적화합니다.
주로 <code>ExecutorService</code>와 <code>BlockingQueue</code>를 활용하고, 실무에서는 직접 ThreadPoolExecutor를 설정해 자원 제어와 예외 처리 전략을 구성합니다.”</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[🔐 Spring Security + JWT + Redis 인증 구조 정리]]></title>
            <link>https://velog.io/@jade_95/Spring-Security-JWT-Redis-%EC%9D%B8%EC%A6%9D-%EA%B5%AC%EC%A1%B0-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@jade_95/Spring-Security-JWT-Redis-%EC%9D%B8%EC%A6%9D-%EA%B5%AC%EC%A1%B0-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 19 Jun 2025 11:44:57 GMT</pubDate>
            <description><![CDATA[<h2 id="✅-전체-흐름-요약">✅ 전체 흐름 요약</h2>
<blockquote>
<p>JWT는 무상태 인증 방식이기 때문에, 토큰을 클라이언트에만 저장하면 <strong>서버는 상태를 기억하지 못함.</strong>
이를 보완하기 위해 Redis를 사용해 <strong>로그아웃된 토큰이나 강제로 만료시킬 토큰을 관리하는 구조</strong>를 설계함.</p>
</blockquote>
<hr>
<h2 id="🧭-인증-흐름-전체-구조">🧭 인증 흐름 전체 구조</h2>
<h3 id="1-로그인-시">1. <strong>로그인 시</strong></h3>
<ul>
<li>클라이언트가 로그인 요청 → 서버가 JWT 발급 (access + optional refresh)</li>
<li>토큰은 클라이언트에 저장 (예: localStorage, cookie)</li>
</ul>
<h3 id="2-요청-시">2. <strong>요청 시</strong></h3>
<ul>
<li>클라이언트가 Authorization 헤더에 토큰 포함하여 API 호출</li>
<li>서버에서는 필터에서 JWT를 디코딩 → 사용자 정보 추출</li>
<li>유효하다면 <code>SecurityContextHolder</code>에 사용자 정보 저장 → 이후 요청에서 인증 정보 사용 가능</li>
</ul>
<h3 id="3-로그아웃-시">3. <strong>로그아웃 시</strong></h3>
<ul>
<li>클라이언트가 로그아웃 요청</li>
<li>서버는 해당 JWT를 Redis 블랙리스트에 저장 + TTL은 토큰의 남은 만료 시간만큼 설정</li>
<li>이후 이 토큰으로 요청이 오면 Redis에서 차단됨</li>
</ul>
<h3 id="4-토큰-검증-시-필터-내부">4. <strong>토큰 검증 시 (필터 내부)</strong></h3>
<pre><code class="language-java">if (isValid(jwt)) {
    if (!redisService.hasKey(jwt)) {
        Authentication auth = getAuthentication(jwt);
        SecurityContextHolder.getContext().setAuthentication(auth);
    } else {
        // 블랙리스트에 등록된 토큰 → 인증 실패 처리
    }
}</code></pre>
<hr>
<h2 id="📦-redis의-역할">📦 Redis의 역할</h2>
<table>
<thead>
<tr>
<th>역할</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>블랙리스트 저장소</td>
<td>로그아웃된 JWT를 Redis에 저장하여 인증 거부 처리</td>
</tr>
<tr>
<td>TTL 기반 관리</td>
<td>남은 토큰 만료 시간을 기준으로 Redis에 TTL 설정 → 자동 삭제</td>
</tr>
<tr>
<td>빠른 조회</td>
<td>RDB보다 빠른 in-memory 조회로 인증 성능 최적화</td>
</tr>
</tbody></table>
<hr>
<h2 id="🔍-redis-없이-구현하는-대안은">🔍 Redis 없이 구현하는 대안은?</h2>
<table>
<thead>
<tr>
<th>방식</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td>RDB에 블랙리스트 테이블</td>
<td>매 요청마다 DB IO → 성능 저하 + TTL 관리 복잡</td>
</tr>
<tr>
<td>클라이언트에서만 토큰 삭제</td>
<td>서버는 여전히 토큰을 유효하다고 판단함 (보안 취약)</td>
</tr>
<tr>
<td>accessToken 짧게 + refreshToken</td>
<td>일정 시간 내 accessToken은 여전히 유효 → 실시간 차단 불가</td>
</tr>
</tbody></table>
<p>➡️ 결론: Redis는 <strong>토큰 무효화와 세션 제어를 실시간으로 처리</strong>하면서도 <strong>퍼포먼스까지 확보할 수 있는 최적의 선택지</strong></p>
<hr>
<h2 id="🧠-면접용-정리-멘트">🧠 면접용 정리 멘트</h2>
<blockquote>
<p>“JWT는 무상태 인증 구조이기 때문에, 로그아웃이나 강제 만료가 어려운 단점이 있습니다.
이를 보완하기 위해 Redis에 블랙리스트를 관리하고 TTL을 설정함으로써, 서버 측에서 토큰을 실시간으로 무효화할 수 있도록 했습니다.”</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[☕ JVM GC 및 메모리 구조 정리]]></title>
            <link>https://velog.io/@jade_95/JVM-GC-%EB%B0%8F-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B5%AC%EC%A1%B0-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@jade_95/JVM-GC-%EB%B0%8F-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B5%AC%EC%A1%B0-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 19 Jun 2025 11:44:21 GMT</pubDate>
            <description><![CDATA[<h2 id="✅-jvm-메모리-영역-개요">✅ JVM 메모리 영역 개요</h2>
<p>JVM은 실행 중인 Java 애플리케이션의 메모리를 아래와 같이 구분해 관리함:</p>
<ul>
<li><p><strong>Heap 영역</strong>: 객체가 저장되는 공간 (GC의 주요 대상)</p>
<ul>
<li>Young Generation (Eden, Survivor)</li>
<li>Old Generation (Tenured)</li>
</ul>
</li>
<li><p><strong>Method Area (PermGen / Metaspace)</strong>: 클래스 정보, 메서드 정보</p>
</li>
<li><p><strong>Stack</strong>: 각 쓰레드마다 생성되는 프레임 기반 메모리 (지역 변수 저장)</p>
</li>
<li><p><strong>PC Register, Native Stack 등</strong>: 기타 JVM 내부 운영용</p>
</li>
</ul>
<hr>
<h2 id="📦-young-generation-구조-객체-생명주기-중심">📦 Young Generation 구조 (객체 생명주기 중심)</h2>
<h3 id="1-eden-영역">1. <strong>Eden 영역</strong></h3>
<ul>
<li>new 연산자로 생성된 객체가 처음 저장되는 곳</li>
<li>가장 많은 객체가 여기서 생성되고, 대부분 GC 대상됨</li>
</ul>
<h3 id="2-survivor-영역-s0--s1">2. <strong>Survivor 영역 (S0 / S1)</strong></h3>
<ul>
<li>Eden에서 Minor GC로 살아남은 객체가 잠깐 머무는 곳</li>
<li>S0, S1 두 개가 있고, 번갈아가며 복사/교환</li>
<li>여러 번 Minor GC에서 살아남으면 Old로 승격</li>
</ul>
<h3 id="3-old-영역-tenured">3. <strong>Old 영역 (Tenured)</strong></h3>
<ul>
<li>장기간 살아남은 객체가 이동하는 공간</li>
<li>공간이 부족하면 Major GC(또는 Full GC) 발생</li>
<li>GC 성능과 STW(Stop The World) 현상에 큰 영향</li>
</ul>
<hr>
<h2 id="🔁-gc-동작-방식-요약">🔁 GC 동작 방식 요약</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>트리거 조건</th>
<th>대상 영역</th>
<th>특징</th>
</tr>
</thead>
<tbody><tr>
<td>Minor GC</td>
<td>Eden 가득 참</td>
<td>Young Gen (Eden + Survivor)</td>
<td>빠르고 자주 발생함</td>
</tr>
<tr>
<td>Major GC</td>
<td>Old 영역 가득 참</td>
<td>Old Gen</td>
<td>느리고 STW 발생 가능성 높음</td>
</tr>
<tr>
<td>Full GC</td>
<td>전체 Heap 점검 필요 시</td>
<td>전체 Heap (Young + Old)</td>
<td>성능에 가장 큰 영향</td>
</tr>
</tbody></table>
<hr>
<h2 id="💡-객체-생명-흐름-정리-직관적-설명">💡 객체 생명 흐름 정리 (직관적 설명)</h2>
<pre><code>[Eden] → [Survivor S0] → [Survivor S1] → [Old]
 (갓 생성됨)     (1번 생존)       (2~3번 생존)    (오래 살아남음)</code></pre><ul>
<li>Eden: 메서드 호출로 생성된 객체가 저장됨</li>
<li>Survivor: 살아남은 객체가 Old로 갈지 대기하는 중간 단계</li>
<li>Old: 여러 번 살아남은 객체를 위한 장기 저장 공간</li>
</ul>
<hr>
<h2 id="🔚-면접용-정리-멘트">🔚 면접용 정리 멘트</h2>
<blockquote>
<p>“JVM은 새로 생성된 객체를 Eden에 저장하고, GC에서 살아남으면 Survivor → Old 순으로 이동시킵니다.
객체 생존 시간에 따라 영역을 분리해서, GC 비용을 최소화하려는 구조입니다.”</p>
</blockquote>
<blockquote>
<p>“Minor GC는 Eden이 가득 찼을 때 발생하고 빠르며, Major GC는 Old 영역이 가득 찰 때 발생해 상대적으로 느리고 Stop-The-World를 유발할 수 있습니다.”</p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>