<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jj_project100000</title>
        <link>https://velog.io/</link>
        <description>성장중</description>
        <lastBuildDate>Tue, 31 Mar 2026 01:44:44 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jj_project100000</title>
            <url>https://velog.velcdn.com/images/jj_project10000/profile/cbb9f0cf-aca6-4fec-bbec-eacfe722f829/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jj_project100000. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jj_project10000" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[MSK(Managed Streaming for Apache Kafka)]]></title>
            <link>https://velog.io/@jj_project10000/MSKManaged-Streaming-for-Apache-Kafka</link>
            <guid>https://velog.io/@jj_project10000/MSKManaged-Streaming-for-Apache-Kafka</guid>
            <pubDate>Tue, 31 Mar 2026 01:44:44 GMT</pubDate>
            <description><![CDATA[<h4 id="msk">MSK</h4>
<p>MSK는 AWS에서 제공하는 Managed Streaming for Apache Kafka 의 약자로, 대중적인 오픈소스 분산 스트리밍 플랫폼인 Apache Kafka를 AWS가 대신 관리해 주는 서비스이다.</p>
<h5 id="주요-특징과-장점">주요 특징과 장점</h5>
<ul>
<li>완전 관리형: 서버 관리, 패치, 설정 등 운영 부담이 없다. AWS가 클러스터의 상태를 모니터링하고 문제가 생기면 자동으로 복구한다.</li>
<li>고가용성: 데이터 복제와 다중 가용 영역(Multi-AZ)배치를 통해 데이터 유실 가능성을 최소화하고 안정성을 높인다.</li>
<li>보안: AWS IAM을 통한 권한 관리, VPC 내 격리, 데이터 암호화등 강력한 보안 기능을 기본으로 제공한다.</li>
<li>확장성: 트래픽이 늘어나면 클릭 몇 번으로 브로커 노드의 수나 스토리지 용량을 쉽게 확장할 수 있다.</li>
</ul>
<h5 id="메세지-유실-관리">메세지 유실 관리</h5>
<ol>
<li>다중 가용 영역 및 복제
MSK는 데이터를 물리적으로 떨어진 여러 데이터 센터에 분산 저장하여 하드웨어 장애나 특정 지역의 재해로부터 데이터를 보호한다</li>
</ol>
<ul>
<li>다중 AZ 배치: 클러스터를 생성할 때 2개 또는 3개의 AZ를 선택하면, MSK가 브로커 노드를 각 AZ에 균등하게 배치한다.</li>
<li>복제 계수: 기본적으로 데이터를 최소 2개 또는 3개의 복제본으로 유지한다. 하나의 브로커나 AZ 전체에 장애가 발생해도 다른 AZ에 있는 복제본을 통해 서비스를 계속하고 데이터를 보존할 수 있다.</li>
</ul>
<ol start="2">
<li>생산자(Producer)의 데이터 전송 보장
메세지가 브로커에 도달하기 전이나 도달하는 과정에서의 유실을 막기 위해 acks 설정을 활용한다.</li>
</ol>
<ul>
<li>acks=all: MSK 권장 설정으로, 리더(Leader)와 모든 팔로워(Follower) 복제본에 데이터가 완전히 기록되었다는 응답을 받은 후에야 전송 성공으로 간주한다. 지연 시간은 조금 늘어날 수 있지만 데이터 유실을 방지하는 가장 강력한 방법이다.
** 자동 리더 교체: Producer는 항상 리더 브로커에게 메세지를 보낸다. 그러다가 리더가 죽으면 수 초내에 팔로워가 리더로 승격된다.</li>
</ul>
<ol start="3">
<li>영구 스토리지 및 자동 복구</li>
</ol>
<ul>
<li>EBS 기반 저장: MSK 브로커는 AWS의 고성능 블록 스토리지인 EBS를 사용하여 데이터를 저장한다. 서버 자체에 문제가 생겨도 데이터가 담긴 스토리지는 독립적으로 유지된다.</li>
<li>자동 브로커 교체: 특정 브로커에 장애가 감지되면 MSK는 즉시 새로운 브로커 노드를 프로비저닝하고 기존 스토리지를 연결하여 클러스터를 정상 상태로 되돌린다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Batch]]></title>
            <link>https://velog.io/@jj_project10000/%ED%8E%98%EC%9D%B4%EC%A7%95-%EA%B8%B0%EB%B0%98-vs-%EC%BB%A4%EC%84%9C-%EA%B8%B0%EB%B0%98</link>
            <guid>https://velog.io/@jj_project10000/%ED%8E%98%EC%9D%B4%EC%A7%95-%EA%B8%B0%EB%B0%98-vs-%EC%BB%A4%EC%84%9C-%EA%B8%B0%EB%B0%98</guid>
            <pubDate>Sun, 29 Mar 2026 10:56:11 GMT</pubDate>
            <description><![CDATA[<h3 id="spring-batch">Spring Batch</h3>
<p>대용량 레코드 처리를 위한 가볍고 포괄적인 배치 프레임워크.
수백만 건의 데이터를 안정적으로 읽고, 가공하고, 저장하는 작업을 자동화하고 관리해주는 도구</p>
<h4 id="왜-batch를-쓰는가">왜 Batch를 쓰는가</h4>
<ol>
<li><p>시스템 자원의 효율적 사용
사용자가 몰리는 낮 시간대에 무거운 데이터 계산 작업을 수행하면 서버가 느려지거나 멈출 수 있다. 실시간 서비스에 영향을 주지 않도록, 트래픽이 적은 심야 시간대에 자원을 집중해서 처리한다. 또한 DB 커넥션을 맺고 끊는 과정을 수만 번 반복하는 대신, 한 번 연결해서 대량의 데이터를 쏟아붓는 방식이 훨씬 빠르다.</p>
</li>
<li><p>데이터 무결성과 안정성
Spring Batch는 어디까지 처리했는지 DB에 기록한다. 서버가 뻗어도 처음부터 다시 하지 않아도 된다. 
1000개 단위로 묶어 처리하면, 중간에 오류가 나도 해당 덩어리만 롤백하고 나머지는 안전하게 저장할 수 있다.</p>
</li>
<li><p>사용자 경험 분리
사용자가 즉시 결과를 알 필요가 없는 작업(포인트 정산, 대량 메일 발송)은 백그라운드 배치로 돌리고 사용자에게는 &#39;접수되었습니다&#39;라는 응답만 즉시 보낸다.</p>
</li>
</ol>
<h5 id="페이징-기반-배치paging-based-batch">페이징 기반 배치(Paging-based Batch)</h5>
<p>데이터 베이스의 Offset과 Limit을 사용하여 데이터를 페이지 단위로 끊어서 조회하는 방식이다.</p>
<ul>
<li>작동 원리: 1번 페이지(1<del>10번), 2번 페이지(11</del>20번)와 같이 게시판의 페이징과 유사하게 데이터를 읽어온다.</li>
<li>특징<ul>
<li>상태 비저장(Stateless): 각 페이지를 조회할 때마다 새로운 쿼리를 실행하므로, DB커넥션을 오래 유지하지 않는다.</li>
<li>병렬 처리 유리: 페이지 번호만 지정하면 여러 스레드가 서로 다른 페이지를 동시에 처리하기 쉽다.<blockquote>
<p>단, 처리 도중 데이터가 추가되거나 삭제되면 Offset이 밀리면서 특정 데이터를 건너뛰거나 중복 조회할 위험이 있다.</p>
</blockquote>
</li>
</ul>
</li>
</ul>
<h5 id="커서-기반-배치cursor-based-batch">커서 기반 배치(Cursor-based Batch)</h5>
<p>DB 서버에 커서를 열어두고, 한 번에 하나씩 일정 묶음(Chunk)씩 데이터를 스트리밍하듯 가져오는 방식이다.</p>
<ul>
<li>작동 원리: DB서버와 연결을 유지한 채로 &#39;다음 데이터&#39;를 가리키는 포인터를 이동시키며 데이터를 읽는다.</li>
<li>특징<ul>
<li>성능 일관성: 페이징 방식은 뒤로 갈수록 Offset 계산 비용이 커지지만, 커서는 현재 위치에서 다음데이터만 가져오므로 대용량 처리에 성능 저하가 적다.<ul>
<li>데이터 무결성: 한번 열린 커서 범위 내에서 데이터를 읽으므로, 처리 중 데이터 변화에 영향을 덜 받는다.<blockquote>
<p>단 조회 시간이 너무 길어지면 DB커넥션을 오래 점유하게 되어 타임아웃이 발생하거나 DB 부하가 생길 수 있다.</p>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[NoSQL]]></title>
            <link>https://velog.io/@jj_project10000/NoSQL</link>
            <guid>https://velog.io/@jj_project10000/NoSQL</guid>
            <pubDate>Sun, 22 Mar 2026 07:16:34 GMT</pubDate>
            <description><![CDATA[<h3 id="nosql">NoSQL</h3>
<p>Not Only SQL - 기존의 RDB가 가진 한계를 깨기 위해 나온 새로운 방식의 데이터베이스들을 통칭한다.</p>
<h4 id="왜-nosql티-태어났을까">왜 NoSQL티 태어났을까?</h4>
<p>RDB는 정해진 틀이 엄격하고 데이터의 무결성을 지키는데 목숨을 건다. 하지만 동시 접속 트래픽이 많은 환경에서는 오히려 RDB의 꼼꼼함이 독이 된다.
한대의 서버를 Scale-up하는 것에 한계가 있고, Scale-out하고 싶어도 RDB는 여러 대 사이에서 락을 걸고 쿼럼을 맞추기가 너무 무겁고 느리다.
정합성과 확장성 사이에서, 확장성을 극대화하기 위해 정합성의 일부를 의식적으로 포기한 시스템이 NoSQL이다.</p>
<h5 id="apache-cassandra">Apache Cassandra</h5>
<p>카산드라는 태어날 때부터 &#39;서버 한 대로는 부족하다. 무조건 여러 대가 팀으로 움직인다&#39;는 전제로 만들어진 데이터베이스이다.</p>
<ul>
<li>구조: 모든 서버는 동등한 권한을 가진다. master 서버가 따로 없어서 서버 한 두 대가 죽어도 시스템 전체에 아무런 타격이 없다.</li>
<li>쓰기 최적화: WAL과 비슷한 원리로 , 디스크에 직접 쓰지 않고 메모리에 먼저 쓴 뒤 나중에 순차적으로 저장한다. 덕분에 쓰기 속도가 미친듯이 빠르다.</li>
<li>쿼럼의 활용: 저장할 때 &#39;몇 대의 서버에 복제되면 성공으로 칠까?&#39;를 개발자가 직접 설정할 수 있다(ex. LOCAL_QUORUM)<blockquote>
<p>여러대의 서버가 동등하게 원형으로 모여서, 데이터를 골고루 나눠 갖고, 서로 복제본을 챙겨주며 절대 죽지 않는 시스템을 만드는 DB이다.</p>
</blockquote>
</li>
</ul>
<pre><code>&lt;동작 방식&gt;
1. 파티션 키 (Partition Key): 첫 번째 목적지 찾기
데이터(예: user_id = &#39;merong&#39;)가 들어오면, 카산드라는 이 키를 해시 함수(Hash Function)라는 마법 상자에 넣는다.

입력: merong
출력: -152348721 (엄청나게 큰 숫자 중 하나)
역할: 이 숫자가 속한 범위를 관리하는 서버가 첫 번째 저장소(Primary Replica)가 된다. 예를 들어 -2억 ~ 0 사이는 1번 서버 담당이라면, 데이터는 일단 1번 서버로 간다.

2. 복제 (Replication): 옆집으로 복사하기
카산드라는 복제 계수(Replication Factor, RF)라는 설정값을 미리 알고 있다. 만약 RF = 3이라면:
  1단계: 파티션 키로 찾은 1번 서버에 데이터를 저장한다.
  2단계: 링(Ring) 모양의 구조에서 1번 서버의 시계 방향 바로 다음 서버(2번 서버)에 복사본을 보낸다.
  3단계: 또 그 다음 서버인 3번 서버에 마지막 복사본을 보낸다.

결과적으로: 하나의 데이터가 1번(원본), 2번(복제), 3번(복제) 서버에 나란히 저장된다.</code></pre><h5 id="aws-dynamodb">AWS DynamoDB</h5>
<p>AWS의 완전 관리형 key-value/document 스토어.</p>
<ul>
<li>구조: Partition Key &amp; Sort Key
  -partition key : 데이터의 어느 물리적 창고에 넣을지 결정(ex. user_id)
 -sort key: 같은 파티션 키 안에서 데이터를 순서대로 정렬하는 키(ex. created_at)</li>
</ul>
<p>DynamoDB는 데이터가 늘어나면 내부적으로 창고(partition)을 계속 쪼갠다.</p>
<pre><code>1. 처음에는 partition 1개에 데이터를 다 넣는다
2. 데이터가 10GB가 넘거나, 요청량이 설정한 한계를 넘으면 창고를 2개로 쪼개고 파티션 키 범위에 따라 데이터를 나눠 담는다.
3. 이 과정을 무한히 반복한다. 그래서 데이터가 10TB가 되어도 성능이 떨어지지 않는다.</code></pre><pre><code>&lt;파티션 분할 과정&gt;
1. 임계치 도달: 1번 파티션이 10GB에 도달하거나, 설정한 RCU/WCU 성능 한계에 부딪힌다.
2. 새 창고 확보: AWS가 뒤에서 몰래 2번 파티션(새 서버)을 준비한다.
3. 데이터 이사 (Split): 1번 파티션에 있던 데이터를 해시 범위(Hash Range)에 따라 절반으로 나눈다.
예: 해시값이 0~50인 데이터는 1번에 남기고, 51~100인 데이터는 2번으로 옮긴다.
4. 완료: 이제 테이블은 2개의 물리적인 서버에 나누어 저장된 상태가 된다.</code></pre><table>
<thead>
<tr>
<th align="left">비교 항목</th>
<th align="center">Apache Cassandra</th>
<th align="center">AWS DynamoDB</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>서비스 형태</strong></td>
<td align="center">오픈소스 (설치형/관리형 선택)</td>
<td align="center">완전 관리형 (Serverless)</td>
</tr>
<tr>
<td align="left"><strong>관리 부담</strong></td>
<td align="center"><strong>높음</strong> (서버 설치, 패치, 복제 직접 수행)</td>
<td align="center"><strong>매우 낮음</strong> (AWS 자동 관리)</td>
</tr>
<tr>
<td align="left"><strong>확장 방식</strong></td>
<td align="center">노드(Node) 물리적 추가</td>
<td align="center">처리량(RCU/WCU) 수치 조절</td>
</tr>
<tr>
<td align="left"><strong>복제 방식</strong></td>
<td align="center">복제 계수(RF) 직접 설정 (보통 3)</td>
<td align="center"><strong>3개 가용 영역(AZ)</strong> 자동 복제</td>
</tr>
<tr>
<td align="left"><strong>일관성 제어</strong></td>
<td align="center">쿼럼(Quorum) 수치 미세 조정 (0~N)</td>
<td align="center">결과적/강한 일관성 중 선택</td>
</tr>
<tr>
<td align="left"><strong>비용 모델</strong></td>
<td align="center">서버 대수당 고정 비용 (24시간 가동)</td>
<td align="center">쓴 만큼 지불 (Pay-as-you-go)</td>
</tr>
<tr>
<td align="left"><strong>특장점</strong></td>
<td align="center">특정 클라우드 종속 없음 (어디서든 실행)</td>
<td align="center">AWS 생태계(Lambda 등)와 강력한 결합</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[JVM]]></title>
            <link>https://velog.io/@jj_project10000/JVM</link>
            <guid>https://velog.io/@jj_project10000/JVM</guid>
            <pubDate>Sat, 14 Mar 2026 11:01:59 GMT</pubDate>
            <description><![CDATA[<h4 id="jvm은-왜-태어났나">JVM은 왜 태어났나</h4>
<p>과거에는 윈도우용 프로그램과 리눅스용 프로그램, 맥용 프로그램을 전부 따로 만들어야 했다. 그래서 자바는 엄청난 선언을 한다</p>
<blockquote>
<p>Write Once, Run Anywhere</p>
</blockquote>
<h4 id="jvm의-내부-구조">JVM의 내부 구조</h4>
<ol>
<li><p>클래스 로더(Class Loader)
프로그램이 실행되면 가장 먼저 하드디스크에 있는 바이트코드를 싹 모아서 메모리에 올린다.</p>
</li>
<li><p>실행 엔진(Execution Engine)
메모리에 올라온 바이트 코드를 기계어로 번역하고 실행한다. 이때 자주 쓰이는 코드는 매번 번역하지 않고 아예 기계어로 바꿔버리는 JIT(Just-In-Time)컴파일러를 사용하여 파이썬 보다 훨씬 빠른 계산 속도를 낸다.</p>
</li>
<li><p>메모리</p>
</li>
</ol>
<ul>
<li>스택 영역: 각 쓰레드가 각자 가지는 공간</li>
<li>힙 영역: 덩치가 큰 공유 데이터를 저장하는 공간</li>
</ul>
<h4 id="쓰레드-풀과-톰캣의-동작-방식">쓰레드 풀과 톰캣의 동작 방식</h4>
<p>스프링 부트로 서버를 띄우면 기본적으로 내장된 톰캣이라는 매니저가 프로그램 문을 연다.
미리 쓰레드를 생성해둔다.(200개의 Thread Pool)</p>
<pre><code>1. 손님 입장 (요청 도착): 손님이 10명 들어옵니다.
2. 대기실에서 직원 호출: 톰캣 매니저가 쓰레드 풀(대기실)에서 쉬고 있던 직원 10명을 부릅니다.
3. 전담 마크 시작: 10명의 직원이 각각 1명의 손님을 맡아 주문을 처리합니다. DB 응답을 기다릴 때는 다른 일은 하지 않고 얌전히 기다립니다(Blocking).
4. 대기실 복귀 (반납): 식사(응답 완료)가 끝나면, 이 직원은 퇴사(쓰레드 소멸)하는 것이 아니라 다시 대기실(Pool)로 돌아가 다음 손님을 기다립니다.</code></pre><blockquote>
<p>손님이 201명 왔는데요?</p>
</blockquote>
<p>FastAPI라면 1명의 직원이 비동기로 201번째 주문도 순식간에 받아버렸겠지만, 톰캣은 원칙상 직원이 남아있지 못하면 주문을 받지 못한다.
이에 대비하여 톰캣은 웨이팅 줄(Queue, Accept Count)를 준비해둔다.</p>
<pre><code>- 큐(Queue) 대기: 201번째 손님부터는 식당 문 앞의 대기열(Queue)에서 줄을 서서 기다립니다. (기본적으로 100명까지 줄을 설 수 있습니다.)
- 직원 순환: 안에 있던 200명 중 누군가 서빙을 마치고 대기실로 돌아오면, 매니저가 잽싸게 그 직원을 대기열 1번 손님에게 배정합니다.
- 진짜 대참사 (Connection Refused): 만약 줄 서는 공간마저 꽉 차버린 상태에서 새로운 손님이 오면? 톰캣은 **&quot;죄송합니다. 오늘 영업 끝났습니다&quot;**라며 에러를 뱉어냅니다(이것이 그 유명한 서버 다운, 503 Service Unavailable 등의 상태입니다).</code></pre><p>그래서 만약 트래픽이 몰릴 것이 예상되면, 이 쓰레드 풀의 크기(Max Threads)를 200에서 400으로 늘리거나, 대기열의 크기를 튜닝하는 작업을 하게된다.</p>
<h4 id="가비지-컬렉터garbage-collectorgc">가비지 컬렉터(Garbage Collector,GC)</h4>
<p>앞서 톰캣 서버는 쓰레드가 쉴 새 없이 요청을 전담해서 처리한다고 했다. 그럼 공용으로 쓰이는 Heap 메모리에는 이 전 요청을 처리할 때 사용했던 데이터 객체들이 산더미 처럼 쌓인다.</p>
<blockquote>
<p>생성된 데이터의 98%는 잠깐 쓰이고 바로 버려진다. 끝까지 살아남은 놈은 극소수다</p>
</blockquote>
<p>예를 들어 손님이 주문할 때 잠깐 쓴 &#39;주문서 객체&#39;는 요리가 나오면 바로 쓰레기가 된다. 반면 &#39;메뉴판 객체&#39;나 &#39;식당 설정 정보&#39;는 식당이 문을 닫을 때 까지 살아있어야 한다.
그래서 GC는 힙 메모리라는 거대한 식당을 아예 두 구역으로 쪼개버린다.</p>
<ol>
<li>신규 구역(Young Generation): 패스트푸드 존</li>
</ol>
<ul>
<li><p>Minor GC : 이 구역이 꽉 차차면 청소부 아저씨가 빗자루를 들고 쓱 훑고 지나간다. 워낙 순식간에 일어나서 서버 성능에는 거의 영향을 주지 않는다.</p>
<ol start="2">
<li>고인물 구역(Old Generation): VIP 룸
신규 구역에서 청소부 아저씨의 빗자루 질을 여러 번 버텨낸 질긴 객체가 있다. 이런 애들은 고인물 구역으로 Promotion 하여 자리를 잡는다.</li>
</ol>
<ul>
<li>Major GC/Full GC : 청소부 아저씨가 대청소를 선언한다. 이곳은 공간이 워낙 넓고 복잡해서 청소하는데 시간이 아주 오래 걸린다.</li>
</ul>
</li>
</ul>
<blockquote>
<p> 🚨&quot;전원 멈춰! 청소 끝날 때까지 아무도 움직이지 마!&quot;
     Stop-The-World(STW)</p>
</blockquote>
<p>대청소가 시작되는 순간 톰캣의 200개의 애플리케이션 쓰레드는 하던 일을 완전히 멈추고 얼음상태가 된다. STW가 0.1초 만에 끝나면 다행이지만, 만약 메모리가 너무 꼬여서 3~5초 동안 멈춘다면? 그 5초 동안은 어떤 손님도 주문을 낼 수 없고, 식당 밖에서 대기하던 손님들은 앱 화면이 멈춰버리는 끔찍한 경험을 하게 된다. (API 지연 시간 폭발)</p>
<p>따라서 어떻게 하면 객체를 덜 만들어서 이 대청소를 최대한 안 일어나게 할까라는 고민에서 나온 것이 JVM 튜닝이다.</p>
<h4 id="jvm-튜닝">JVM 튜닝</h4>
<p>1) Heap Size 조절: 가장 기본적이고 직관적인 튜닝. 즉 청소할 공간의 절대적인 크기를 정해준다.</p>
<ul>
<li>-Xms(초기 힙 크기)/-Xmx(최대 힙 크기)
  ☑️ 크게 잡으면: 식당이 아주 넓어지니 쓰레기가 꽉 차는 데 오래 걸립니다. 즉, 대청소(Major GC) 횟수가 줄어듭니다. 하지만 한 번 대청소를 시작하면 치울 게 너무 많아서 STW 시간(멈춤 현상)이 엄청나게 길어집니다.
☑️ 작게 잡으면: 반대로 대청소를 훅훅 끝낼 수 있지만, 5분마다 대청소를 하겠다고 식당을 멈춰 세우니 서버가 제 기능을 못 합니다.</li>
</ul>
<p>2) 구역의 비율 조절(Young vs Old 영역 크기)</p>
<ul>
<li>-XX:NewRatio (Old 영역과 Young 영역의 비율)
Young구역을 넓히면 금방 먹고 나갈 손님들(단기 객체)를 수용하기 좋아진다. 하지만 VIP룸이 좁아지며 조금만 오래 앉아있어도 VIP룸이 꽉 차서 무거운 대청소가 너무 자주 발생한다.
따라서 서비스가 금방 쓰고 버리는 데이터가 많은지 오래 들고 있어야 할 캐시 데이터가 많은지를 분석하여 파티션을 밀고 당기는 것이 중요하다.</li>
</ul>
<p>3) 청소 업체 알고리즘(GC 알고리즘)
    옛날에는 식당 크기나 비율을 수동으로 깎는 튜닝을 많이 했지만, 요즘 현대 자바(Java 8 이후 ~ Java 21)에서는 <strong>&quot;그냥 더 똑똑하고 비싼 청소 업체를 고용하자!&quot;</strong>로 트렌드가 바뀌었습니다.
서버를 띄울 때 어떤 청소 업체를 부를지 옵션으로 정해줄 수 있다.</p>
<ul>
<li>Parallel GC : 청소부 아저씨가 한 명이 아니라, 여러 명의 직원이 동시에 빗자루를 들고 우르르 치운다. 대청소를 할 때는 여전히 식당 셔터를 다 내리고 다 같이 치워야 한다. 서버의 전체적인 처리량은 좋지만 한번 멈출 때 꽤 부하가 발생한다.</li>
<li>G1 GC(Garbage-First GC) : 식당을 Young/Old 두 구역으로만 나누는 게 아니라, 바둑판 처럼 수십 개의 작은 구역을 잘개 쪼갠다. 전체를 다 치우는 것이 아니라 쓱 둘러보고 쓰레기가 가장 많이 쌓인 구역(Garbage-First)만 잽싸게 치우고 빠진다. 덕분에 STW시간이 매우 짧고 일관되게 유지된다.</li>
<li>ZGC / Shenandoah GC(최신 하이엔드 기술) : STW 시간을 1ms이하로 줄이는 것을 목표로 하는 기술이다. 손님이 밥을 먹고 직원들이 서빙을 하는 와중에도 식당을 멈추지 않고 청소부들이 밑에서 몰래 몰래 쓰레기를 치운다. </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[FastAPI]]></title>
            <link>https://velog.io/@jj_project10000/FastAPI</link>
            <guid>https://velog.io/@jj_project10000/FastAPI</guid>
            <pubDate>Fri, 13 Mar 2026 14:24:42 GMT</pubDate>
            <description><![CDATA[<h4 id="fastapi">FastAPI</h4>
<ol>
<li><p>요청 수신: 여러 사용자가 동시에 API 요청을 보냅니다.</p>
</li>
<li><p>이벤트 루프의 스케줄링: Uvicorn(FastAPI를 실행하는 ASGI 서버)의 이벤트 루프가 이 요청들을 하나의 큐(Queue)에 담아 순차적으로 실행하기 시작합니다.</p>
</li>
<li><p>I/O 작업과 제어권 양보 (await): 요청을 처리하다가 데이터베이스 조회나 외부 API 호출 같이 시간이 오래 걸리는 I/O(입출력) 작업을 만나면 await 키워드가 실행됩니다.</p>
</li>
<li><p>다른 Task 처리: await를 만난 함수는 &quot;나 여기서 응답 올 때까지 기다려야 하니까, 내 차례는 뒤로 미루고 다른 일 먼저 해!&quot;라며 이벤트 루프에 제어권을 자발적으로 반납(Yield)합니다. </p>
</li>
<li><p>작업 재개: 이벤트 루프는 대기열에 있던 다른 요청을 처리하다가, 아까 요청했던 DB 조회가 끝났다는 신호(Callback)를 받으면 멈췄던 지점부터 다시 코드를 실행합니다.</p>
</li>
</ol>
<h5 id="fastapi의-스케쥴링">FastAPI의 스케쥴링</h5>
<p>: 자발적 양보
FastAPI의 단일 쓰레드 환경에서는 OS가 개입하지 않습니다. 대신 파이썬의 이벤트 루프(Event Loop)라는 녀석이 스케줄러 역할을 합니다.</p>
<pre><code>상황: 알바생(단일 쓰레드)은 딱 1명뿐입니다.

이벤트 루프의 스케줄링 방식: 알바생은 커피 머신(DB 등)을 작동시킨 뒤 await(제어권 양보)를 외칩니다. 그러면 현황판(이벤트 루프)을 쓱 쳐다봅니다.
현황판(스케줄러)에는 이렇게 적혀 있습니다.

&quot;지금 1번 손님 커피는 기계가 내리고 있으니 놔두고, 너 노는 동안 빨리 2번 손님 주문부터 받아!&quot;

특징: 알바생은 매니저(OS)에게 강제로 쫓겨나는 게 아닙니다. 자기가 붕 뜨는 시간(I/O 대기)에 스스로 현황판(이벤트 루프)의 지시(스케줄링)를 보고 다음 할 일을 찾아갑니다.</code></pre><h5 id="task와-couroutine">Task와 Couroutine</h5>
<pre><code>알바생(단일 쓰레드)이 혼자서 수십 명의 주문을 처리할 때, 머릿속으로 모든 걸 기억하려면 당연히 터져버릴 겁니다. 그래서 알바생 앞에는 **클립보드(이벤트 루프)**가 있고, 손님들의 주문 상태를 **포스트잇(Task 객체)**에 적어서 붙여놓습니다.

1. 상태 기록: 알바생이 A 손님의 에스프레소 버튼을 누르고 제어권을 양보(await)할 때, 멍하니 다른 일을 하러 가는 게 아닙니다.
2. 포스트잇에 이렇게 적습니다: &quot;A 손님 / 아메리카노 / 에스프레소 추출 대기 중 / 머신 울리면 다음 할 일: 물 붓기&quot;
3. 클립보드에 붙이기: 이 포스트잇을 클립보드(대기열)에 붙여둡니다.
4. 다음 일 확인: 알바생은 클립보드를 보고 다음으로 해야 할 B 손님의 포스트잇을 떼어서 봅니다. &quot;B 손님 / 생과일주스 / 믹서기 돌릴 차례&quot; 4. 작업 재개: 커피 머신이 다 되었다고 울리면(이벤트 발생), 알바생은 A 손님의 포스트잇을 다시 꺼내 읽습니다. &quot;아하, 에스프레소가 다 내려왔으니 이제 물을 부을 차례군!&quot; 하고 정확히 멈췄던 지점부터 일을 다시 시작합니다.</code></pre><p>FastAPI에서는 메모리의 Heap에 Task라는 이름의 객체를 만들어둔다. 이 객체 안에는 현재까지의 변수 값들과 다음에 실행해야 하는 코드의 줄 번호가 저장되어 있다.
이벤트 루프는 파이썬의 메모리 상에 각 요청들의 현재 상태와 멈춘 지점을 꼼꼼하게 기록해둔 객체(Task, Coroutine)들을 아주 빠르게 번갈아가며 읽고 실행한다.</p>
<blockquote>
<p>Coroutine 객체에 따른 Heap의 부하는 없을까?</p>
</blockquote>
<p>동시에 10,000개의 요청을 힙에 올려두어도 고작 수십 메가바이트(MB) 정도밖에 차지하지 않는다. 
게다가 이 객체들이 힙에 평생 쌓여있는 것도 아닙니다.요청이 완전히 끝나서 손님에게 응답을 보내고 나면, 해당 코루틴 객체는 더 이상 필요가 없어진다.</p>
<p>이때 파이썬의 <strong>가비지 컬렉터(Garbage Collector)</strong>가 백그라운드에서 쓱 다가와 &quot;어? 이 포스트잇 이제 안 쓰네?&quot; 하고 힙 메모리에서 즉시 지워버린다.</p>
<h5 id="자바의-jvm과의-비교">자바의 JVM과의 비교</h5>
<table>
<thead>
<tr>
<th>비교 포인트</th>
<th>⚡FastAPI (비동기 이벤트 루프)</th>
<th>☕ JVM (전통적인 멀티 쓰레드)</th>
</tr>
</thead>
<tbody><tr>
<td><strong>일꾼(Thread)의 수</strong></td>
<td>단 1개 (기본적으로)</td>
<td>수십 ~ 수백 개 (미리 만들어 둠 - Thread Pool)</td>
</tr>
<tr>
<td><strong>스케줄링 주체</strong></td>
<td>이벤트 루프 (파이썬 내부에서 알아서)</td>
<td>운영체제(OS) (강제로 교체)</td>
</tr>
<tr>
<td><strong>요청 1개당 메모리</strong></td>
<td>아주 가벼움 (수십 KB 힙 메모리 객체)</td>
<td>무거움 (약 1MB 이상의 OS 쓰레드 스택 메모리)</td>
</tr>
<tr>
<td><strong>대기 시간 처리법</strong></td>
<td><code>await</code>로 멈춰두고 다른 손님 주문 받으러 감</td>
<td>DB 응답이 올 때까지 그 직원은 아무것도 안 하고 쉼 (Blocking)</td>
</tr>
<tr>
<td><strong>컨텍스트 스위칭 비용</strong></td>
<td>거의 없음 (포스트잇 바꿔 읽기)</td>
<td>높음 (운영체제가 직원을 통째로 교체)</td>
</tr>
<tr>
<td><strong>가장 큰 강점</strong></td>
<td>트래픽이 엄청나게 몰리는 채팅, I/O 바운드 작업</td>
<td>복잡한 수학 계산, 데이터 변환 등 CPU 바운드 작업</td>
</tr>
</tbody></table>
<pre><code>1. RUNNING (실행 상태): &quot;DB야, 데이터 내놔!&quot;
Thread-A는 CPU를 차지하고 열심히 코드를 실행합니다. 그리고 네트워크를 통해 DB 서버로 쿼리를 전송합니다.

2. BLOCKED / WAITING (대기 상태): &quot;OS 매니저님, 저 DB 올 때까지 잘게요.&quot; (이게 블로킹입니다!)
DB 서버가 데이터를 찾아서 돌려주려면 네트워크를 타고 가야 하니 시간이 걸립니다(수십~수백 밀리초).
이때 OS 스케줄러(매니저)가 개입합니다.
OS는 &quot;Thread-A야, 너 어차피 당장 할 수 있는 연산(계산) 없지? CPU 아까우니까 너 저기 &#39;대기실(Wait Queue)&#39;에 가서 자고 있어!&quot;라며 Thread-A를 CPU에서 강제로 쫓아냅니다. (컨텍스트 스위칭 발생)
이제 Thread-A는 CPU를 0% 사용하며 완전히 멈춰 있는 수면 상태가 됩니다. 일을 전혀 안 하는 거죠.
그 사이 OS는 대기표를 뽑고 기다리던 Thread-B를 CPU에 올려서 다른 손님의 요청을 처리하게 합니다. (CPU는 쉬지 않습니다!)

3. READY (준비 상태): &quot;앗, DB 왔다! 저 다시 일할래요!&quot;
DB 서버에서 드디어 데이터 응답이 도착했습니다. 네트워크 카드(NIC)가 OS에게 &quot;데이터 왔어요!&quot;라고 인터럽트(신호)를 보냅니다.
OS는 대기실에서 자고 있던 Thread-A의 멱살을 잡아 깨워서 &#39;준비 줄(Ready Queue)&#39;에 세웁니다.
차례가 오면 다시 CPU를 배정받아 하던 일을 마저 끝냅니다.</code></pre><blockquote>
<p>어? CPU가 안 놀고 다른 쓰레드(B, C)를 실행하면 효율적인 거 아닌가요?</p>
</blockquote>
<p>앞서 자바(Spring/Tomcat)는 미리 정해진 Thread-Pool(보통 200개)를 사용한다. 만약 어떤 이벤트가 터져서 손님 200명이 동시에 접속했다. 쓰레드 200개이 각각 요청을 받고, 전부 DB에 쿼리를 날린다. 그리고 DB 응답을 기다리기 위해 200개의 쓰레드는 몽땅 Wait Queue에 들어간다.</p>
<ul>
<li>CPU 사용률: 쓰레드 200개가 다 Wait상태여서 CPU는 스케쥴링할 프로세스가 없다. </li>
<li>서버상태: 하지만 그 다음 요청이 들어오면? 모든 쓰레드가 대기 상태이기 때문에 요청이 거절되고 로딩만 돈다. 이를 Thread Pool Hell이라고 한다.</li>
</ul>
<p>하지만 FastAPI는 단일 쓰레드이지만, Wait 상태가 되지 않는다. (Non-blocking)
대신 오래 걸리는 DB 응답은 나중에 이벤트 루프가 알려줄 테니깐 wait 상태로 자지 않고 바로 다음 요청을 받아 CPU위에서 움직인다.</p>
<h5 id="시나리오">시나리오</h5>
<pre><code>만약 파이썬 비동기 환경에서 예전의 동기식 라이브러리를 사용한다면?</code></pre><p>결과: 쓰레드 자체가 멈춘다. </p>
<p>해결책 1. 비동기를 지원하는 최신 라이브러리로 바꾼다.
해결책 2. 예비 쓰레드를 깨워서 시킨다</p>
<pre><code># 🛠️ 예비 인력에게 떠넘기기 (FastAPI의 마법)
@app.get(&quot;/heavy-sync-task&quot;)
def do_sync_work():
    # async가 없는 일반 def 함수입니다!
    # FastAPI가 알아서 &quot;이건 멈추는 작업이네? 메인 쓰레드 말고 백그라운드 쓰레드 풀(Thread Pool)에 던져서 실행해!&quot; 라고 처리합니다.
    result = heavy_sync_db_query() 
    return result</code></pre><p>async def안에서 멈추는 블로킹 코드를 쓰면 서버 전체가 죽지만, 그냥 def로 선언하면 FastAPI 내부적으로 마치 JVM 처럼 별도의 쓰레드 풀에 작업을 위임해버려서 메인 이벤트 루프를 안전하게 보호한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백엔드 캐싱]]></title>
            <link>https://velog.io/@jj_project10000/%EB%B0%B1%EC%97%94%EB%93%9C-%EC%BA%90%EC%8B%B1</link>
            <guid>https://velog.io/@jj_project10000/%EB%B0%B1%EC%97%94%EB%93%9C-%EC%BA%90%EC%8B%B1</guid>
            <pubDate>Sun, 08 Mar 2026 14:32:59 GMT</pubDate>
            <description><![CDATA[<h4 id="백엔드-캐싱">백엔드 캐싱</h4>
<p>자주 접근하는 데이터를 더 빨리 접근 가능한 위치에 저장한다. 요청마다 DB에서 데이터를 다시 계산하거나 검색할 필요 없이 캐시에서 데이터를 빠르게 데이터를 제공할 수 있다. </p>
<h4 id="캐싱의-4가지-전략">캐싱의 4가지 전략</h4>
<ul>
<li>Read Through : 애플리케이션이 데이터를 읽을 때 먼저 캐시를 조회하고, 캐시에 데이터가 없을 경우 DB에서 가져온 후 캐시에 저장. 이후 동일한 요청이 들어오면 캐시에서 즉시 반환</li>
<li>Write Through : 애플리케이션이 데이터를 변경할 때 먼저 캐시에 쓰고, 동시에 DB에 데이터를 갱신한다. 데이터 일관성이 중요하고 변경 빈도가 낮은 데이터에 적합하다. </li>
<li>Write Behind(Write Back) : 데이터를 변경할 때 먼저 캐시에 저장하고, DB에는 일정 시간 뒤에 비동기적으로 쓰는 방식이다. 이로 인해 DB에 대한 쓰기 부하가 줄어들지만, 캐시와 DB의 일시적 불일치가 발생할 수 있다. 쓰기 작접이 빈번하지만 즉각적인 일관성이 중요하지 않은 로그데이터에 적합하다.</li>
<li>Cache Aside: 애플리케이션이 직접 캐시를 관리하는 방식으로, 먼저 캐시를 조회하고 데이터가 없을 경우 DB에서 데이터를 가져와 캐시에 저장하는 패턴이다. 데이터 변경시에도 애플리케이션이 캐시 무효화를 직접 처리한다. 캐시 갱신 정책을 애플리케이션이 세밀하게 제어해야 할 때 사용된다. 예를 들면 사용자별 맞춤 데이터를 캐시에 저장할 때 적합하다.</li>
</ul>
<h5 id="캐시-스탬피드-현상">캐시 스탬피드 현상</h5>
<p>캐시가 만료되거나 초기화될 때 여러 클라이언트가 동시에 동일한 데이터를 요청하면서 대량의 요청이 한꺼번에 데이터베이스로 몰리는 현상. 
이로 인해 서버가 과부하에 걸리거나 DB에 성능이 크게 저하될 수 있음.</p>
<pre><code>- Locking : 캐시에 데이터가 없을 경우 첫 번째 요청만이 DB에 접근하도록 잠금을 설정하고 나머지 요청은 대기
- ~~Lazy Expiration: 캐시가 만료될 때, 요청을 처리하면서 비동기적으로 캐시를 갱신한다.~~
- Randomized TTL : 데이터마다 만료시간을 랜덤하게 설정하여 동일 시간에 캐시가 대거 만료되는 것을 방지한다.</code></pre><h5 id="redis">redis</h5>
<p>Redis는 오픈소스 인메모리 데이터 구조 저장소이다. 캐싱 뿐만 아니라 세션 관리, 실시간 분석, 메세지 큐 등 다양하게 활용할 수 있다. </p>
<h5 id="memcached">Memcached</h5>
<p>오픈 분산 메모리 캐싱 시스템이다. RAM에 데이터를 저장한다. 읽기 작업이 많은 애플리케이션에 이상적이다. 하지만 RAM에만 데이터를 저장하기 때문에 영구적이지 않다. 따라서 데이터 손실이 용납될 수 없는 경우 Redis와 같은 영구적인 솔루션이 더 선호된다.</p>
<h4 id="cdn">CDN</h4>
<p>정적 콘텐츠를 전 세계의 여러 서버에 캐싱하여 사용자가 가장 가까운 서버에서 콘텐츠에 엑세스할 수 있도록 보장한다. 이를 통해 서버 부하가 줄어들고, 대역폭 비용이 낮아지며 페이지 로딩 속도가 크게 향상된다.</p>
<h4 id="캐싱의-단점">캐싱의 단점</h4>
<p>캐싱 시스템의 주요 단점 중 하나는 데이터 불일치 문제이다. 캐시된 데이터가 최신이 아닐 수 있으며, 이로 인해 사용자에게 오래되거나 잘못된 정보가 제공될 수 있다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Index]]></title>
            <link>https://velog.io/@jj_project10000/Index</link>
            <guid>https://velog.io/@jj_project10000/Index</guid>
            <pubDate>Wed, 04 Mar 2026 04:44:04 GMT</pubDate>
            <description><![CDATA[<h4 id="index란">Index란?</h4>
<p>방대한 데이터 속에서 원하는 정보를 빠르게 찾기 위해 사용하는 색인. </p>
<h4 id="인덱스의-종류">인덱스의 종류</h4>
<ul>
<li>클러스터형: 기본키(PK)를 생성하면 자동으로 지정된다. 실제 데이터 자체가 정렬되어 저장된다.</li>
<li>비클러스터형: 별도의 인덱스 페이지를 만든다. 실제 데이터는 그대로 있고, 주소값만 가지고 있다.</li>
</ul>
<blockquote>
<p>클러스터형 인덱스는 테이블 당 딱 하나만 만들 수 있다. 따라서 클러스터형 인덱스로는 책의 본문을 두가지 순서로 동시에 정렬할 수는 없다.</p>
</blockquote>
<h5 id="북마크-룩업">북마크 룩업</h5>
<p>비클러스터형 인덱스에서 주소를 확인한 뒤 실제 데이터를 가지러 가는 과정을 Lookup이라고 한다. 인덱스만 보고 끝나는 게 아니라 실제 데이터를 찾으로 한 번 더 움직여야 하므로, 너무 많은 데이터를 조회할 때는 오히려 그냥 처음부터 끝까지 다 읽는 Full scan보다 느려질 수 있다.</p>
<h5 id="인덱스-내부의-모습">인덱스 내부의 모습</h5>
<p>만약 데이터베이스에 Gemini, Apple, Zebra라는 데이터가 순서 없이 들어있다고 가정해보자. 이름 컬럼에 클러스터형 인덱스를 걸면 내부적으로 다음과 같은 정렬된 인덱스 리스트가 생긴다.</p>
<pre><code>이름 (정렬됨),데이터의 실제 주소 (Pointer)
Apple,3번 페이지의 2번째 줄
Gemini,1번 페이지의 5번째 줄
Zebra,2번 페이지의 1번째 줄</code></pre><p> 이때 만약 Where 이름=&#39;Zebra&#39;라는 쿼리를 날리면 컴퓨터는 원본 데이터를 뒤지는 게 아니라 이 정렬된 리스트(B-tree 구조)를 찾아간다.</p>
<pre><code> 1) 이진 탐색: 리스트가 알파벳 순서대로 정렬되어 있기 때문에, 이진 탐색한다.
 2) 주소 확인: &#39;Zebra&#39;옆에 적힌 주소를 확인한다.
 3) 데이터 인출: 그 주소로 바로 점프해서 나머지 데이터를 가져온다.</code></pre><h5 id="trade-off">Trade-off</h5>
<p>인덱스가 정렬 상태를 유지하기 때문에 새로운 데이터가 들어오면 골치 아파진다. 
인덱스 리스트의 맨 뒤에 그냥 추가하는 것이 아니라 적절한 위치에 사이를 비집고 들어가서 순서를 다시 맞춰야 한다. 따라서 인덱스가 많아지면 데이터를 넣거나 수정하 때 속도가 느려진다.</p>
<h5 id="복합-인덱스">복합 인덱스</h5>
<p>2개 이상의 컬럼을 묶어서 하나의 인덱스로 만들어 쿼리 조건에 딱 맞는 전용 인덱스를 사용할 수 있다.
만약 복합인덱스를 (이름, 카테고리) 이렇게 설정했다면 쿼리에서 순서가 매우 중요하다. 인덱스가 이름-&gt;카테고리로 적혀있는데, 만약 WHERE 카테고리=&#39;중식&#39;으로 쿼리가 작성되어 있으면 인덱스는 작동하지 않는다.</p>
<pre><code>- 복합 인덱스 설정: (이름, 카테고리) 순으로 생성함
- 쿼리 A: WHERE 이름=&#39;xxx&#39; AND 카테고리=&#39;중식&#39; (작동 OK)
- 쿼리 B: WHERE 카테고리=&#39;중식&#39; AND 이름=&#39;xxx&#39; (작동 OK - 옵티마이저가 순서 바꿈)</code></pre><p>복합 인덱스를 만들 때 (이름, 카테고리)로 할지, (카테고리,이름)으로 할지 결정하는 기준은 카디널리티이다. 카디널리티가 높은 것을 앞에 두는게 유리하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[서킷브레이커 패턴]]></title>
            <link>https://velog.io/@jj_project10000/%EC%84%9C%ED%82%B7%EB%B8%8C%EB%A0%88%EC%9D%B4%EC%BB%A4-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@jj_project10000/%EC%84%9C%ED%82%B7%EB%B8%8C%EB%A0%88%EC%9D%B4%EC%BB%A4-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Fri, 27 Feb 2026 10:25:01 GMT</pubDate>
            <description><![CDATA[<h4 id="서킷브레이커-패턴의-등장">서킷브레이커 패턴의 등장</h4>
<p>개발을 하다보면 외부 API를 호출야 하는 경우가 있다. 특히나 MSA환경에서는 매우 빈번하다. 문제는 각각 서버들에 장애가 발생할 수 있다는 점이다. 문제가 생긴 서버를 호출한 다른 서비스들까지 장애가 전파된다!</p>
<blockquote>
<p>그래서 장애가 발생한 서비스를 탐지하고, 요청을 보내지 않도록 차단할 필요가 생기게 되었다.</p>
</blockquote>
<h4 id="서킷브레이커-패턴이란">서킷브레이커 패턴이란?</h4>
<p>문제가 발생한 지점을 감지하고 실패하는 요청을 계속 보내지 않도록 방지한다. 이를 통해 시스템의 장애 확산을 막고, 장애 복구를 도와주며 사용자는 불필요한 대기하지 않게 된다. 서킷 브레이커 패턴은 클라이언트 측면에서 장애를 방지하기 위한 도구로써, 실패할 수 있는 작업을 계속 시도하지 않도록 방지한다.</p>
<h4 id="서킷브레이커의-3가지-상태">서킷브레이커의 3가지 상태</h4>
<table style="width: 100%; border-collapse: collapse; margin: 20px 0; font-family: sans-serif; border: 1px solid #ddd;">
  <thead>
    <tr style="background-color: #f8f9fa; text-align: left;">
      <th style="padding: 12px; border: 1px solid #ddd; width: 20%;">구분</th>
      <th style="padding: 12px; border: 1px solid #ddd; width: 26.6%;">Closed</th>
      <th style="padding: 12px; border: 1px solid #ddd; width: 26.6%;">Open</th>
      <th style="padding: 12px; border: 1px solid #ddd; width: 26.6%;">Half Open</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="padding: 12px; border: 1px solid #ddd; background-color: #fdfdfd; font-weight: bold;">상황</td>
      <td style="padding: 12px; border: 1px solid #ddd;">모든 것이 정상인 상황</td>
      <td style="padding: 12px; border: 1px solid #ddd;">외부(Callee)에 장애가 발생한 상황</td>
      <td style="padding: 12px; border: 1px solid #ddd;">Open 상태가 되고 일정 시간이 지난 상황</td>
    </tr>
    <tr>
      <td style="padding: 12px; border: 1px solid #ddd; background-color: #fdfdfd; font-weight: bold;">요청</td>
      <td style="padding: 12px; border: 1px solid #ddd;">정상적으로 요청을 전달</td>
      <td style="padding: 12px; border: 1px solid #ddd;">외부(Callee)로의 요청을 차단하고 바로 에러를 반환 (Fail-fast)</td>
      <td style="padding: 12px; border: 1px solid #ddd;">외부(Callee)로의 요청을 차단하고 바로 에러를 반환</td>
    </tr>
    <tr>
      <td style="padding: 12px; border: 1px solid #ddd; background-color: #fdfdfd; font-weight: bold;">상태 전이</td>
      <td style="padding: 12px; border: 1px solid #ddd;">장애가 일정 임계치를 넘으면 <b>Open</b> 상태로 변경</td>
      <td style="padding: 12px; border: 1px solid #ddd;">특정 시간이 지나면 <b>Half Open</b> 상태가 됨</td>
      <td style="padding: 12px; border: 1px solid #ddd;">일부 허용된 요청들이 성공한 경우 <b>Closed</b> 상태로, 실패인 경우 <b>Open</b> 상태로 변경</td>
    </tr>
  </tbody>
</table>

<p>외부에 장애가 발생했는지 판단하는 기준을 크게 2가지가 있는데, 각각의 정해진 임계치가 넘어갈 경우 요청이 차단된다.</p>
<ul>
<li>slow call: 기준 시간보다 오래 걸린 요청<ul>
<li>failure call: 실패하거나 오류를 응답받은 요청</li>
</ul>
</li>
</ul>
<h4 id="서킷-브레이커의-동작-방식">서킷 브레이커의 동작 방식</h4>
<pre><code>1) 외부 서버가 정상 실행중이라면, 서킷이 닫혀있고 요청이 정상적으로 실행됨.
2) 🚨 외부 서버에서 장애 발생
3) 요청이 계속 실패하고, 회로가 Open 상태가 됨.
4) 이후 요청들은 더 이상 전달되지 않고 빠르게 차단되며, 빠르게 에러 또는 실패 응답을 반환함.
5) 이후에 외부 서버가 정상 복구됨.
6) 회로가 Open 상태가 된지 특정 시간이 지나고, Half Open 상태로 변경됨.
7) 일부 요청들이 외부 서버로 전달되고, 응답이 성공하여 Closed 상태가 됨.
8) 모든 요청들이 정상 요청</code></pre><h5 id="장애-전파-시나리오">장애 전파 시나리오</h5>
<p>동시에 100명의 사용자가 외부 서비스를 호출하는 기능을 요청한다.그리고 타임아웃은 30초이다
-&gt; 100개의 요청이 30초씩 대기하는 동안 각각이 쓰레드를 붙잡고 있는다. 그런데 만약 서버의 쓰레드 풀이 100개가 최대라면, 그 30초 동안 다른 기능(ex. 상품조회, 로그인)을 요청하지 못하게 된다.
서킷브레이커가 이러한 상황을 빠르게 차단하여 쓰레드가 묶이는 걸 막아주는 것이다.</p>
<h4 id="서킷브레이커가-작동했다-그런데">서킷브레이커가 작동했다. 그런데..?</h4>
<p>서킷브레이커 패턴은 장애가 더 이상 전파되지 않도록 막는 것이지 장애 복구의 개념이 아니다.
서버 A가 서버 B에 추천상품을 GET하는 요청을 했는데 서버 B가 장애가 나서 응답을 주지 않는다.
그럼 사용자 입장에선 갑자기 상품이 보이지 않는 상황이니 참으로 황당할 것이다. 
만약 마트에서 자주 사던 음료가 품절인데, 점원이 &quot;비슷한 음료인데 이건 어떠세요?&quot; 하거나, &quot;지난번에 사셨던 거 다시 드릴까요?&quot; 라고 하면 어떨까?
우리는 이걸 캐시화해서 저장할 수 있다. 캐시에 있는 값이 엄격한 최근 데이터는 아닐지라도 &#39;완벽한 데이터&#39;보다는 &#39;적당히 좋은 사용자 경험&#39;이 나을 수 있기 때문이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DB 파티셔닝 vs DB 샤딩]]></title>
            <link>https://velog.io/@jj_project10000/DB-%ED%8C%8C%ED%8B%B0%EC%85%94%EB%8B%9D-vs-DB-%EC%83%A4%EB%94%A9</link>
            <guid>https://velog.io/@jj_project10000/DB-%ED%8C%8C%ED%8B%B0%EC%85%94%EB%8B%9D-vs-DB-%EC%83%A4%EB%94%A9</guid>
            <pubDate>Fri, 27 Feb 2026 02:09:15 GMT</pubDate>
            <description><![CDATA[<h4 id="🚨문제-상황">🚨문제 상황</h4>
<p>DB가 단일 테이블, 단일 DB 구조가 한계에 도달했다.</p>
<ul>
<li>I/O 병목 심화: 데이터 증가로 인덱스 깊이 증가, 디스크 접근 비용 상승</li>
<li>동시성 충돌 증가: 트랜잭션 경합 및 대기 시간 증가</li>
<li>운영 비용 폭증: 백업, 복구, 배포 시간이 기하급수적으로 증가</li>
<li>물리적 한계 도달: CPU,메모리,디스크 확장의 한계<blockquote>
<p>  해결책: 데이터를 논리적으로 또는 물리적으로 나누어야 한다.</p>
</blockquote>
</li>
</ul>
<h4 id="db파티셔닝이란">DB파티셔닝이란?</h4>
<p>한 지붕 아래 여러 방을 만든다. 단일 DB 서버 내부에서 하나의 거대한 테이블을 여러 파티션(저장단위)으로 나누어 저장한다. 개발자는 하나의 테이블처럼 사용하지만, DB 내부에서는 여러 파티션으로 분리되어 저장된다. 그리고 이는 DB 엔진 수준에서 알아서 관리해준다. </p>
<h5 id="파티셔닝-종류">파티셔닝 종류</h5>
<ul>
<li>Range(범위): 날짜나 숫자 범위를 기준으로 분할(ex: 월별 로그, 연도별 데이터)</li>
<li>List(목록): 특정 값의 목록을 기준으로 분할(ex: 지역코드- 서울, 부산)</li>
<li>Hash(해시): 해시 함수를 통해 데이터를 균등하게 분산(특정 값의 쏠림을 방지하기 위한 전략)</li>
<li>Composite(복합): 두 개 이상의 파티션 전략을 조합(Range+Hash)</li>
</ul>
<p>DB는 파티션 정보를 메타데이터로 관리한다. 이걸 Partition Prunning이라고 한다.**</p>
<h4 id="파티셔닝은-언제-사용할까">파티셔닝은 언제 사용할까?</h4>
<ul>
<li>로그테이블: 시간 기준으로 지속적으로 누적되는 대용량 테이블</li>
<li>날짜 기반 데이터: 최신 데이터 조회 성능이 중요할 때(조회 조건에 날짜 칼럼이 자주 사용될때)</li>
<li>대량 데이터 정리 효율: 오래된 데이터를 통째로 정리하고 싶을때(Drop Paritioning)</li>
</ul>
<h4 id="샤딩이란">샤딩이란?</h4>
<p>옆 동네에 새 집 짓기. 데이터를 서로 달느 물리적 서버(인스턴스)들에 분산 저장한다. 각 서버를 샤드라고 부르며 수평적 확장의 핵심 기술이다.</p>
<h4 id="샤딩은-언제-사용할까">샤딩은 언제 사용할까?</h4>
<ul>
<li>대규모 트래픽/ 데이터 규모: 단일 DB 한계 넘을 때</li>
<li>글로벌 서비스: 한국 서버, 미국 서버 등 지역별 데이터 물리 분산(데이터 로컬리티 확보)</li>
<li>성능 극한(Scale-Up 한계): 단일 서버의 사양을 아무리 높여도 성능이 안 나올 때</li>
<li>주의: 샤딩을 하면 Join 연산이 매우 힘들어지므로 데이터 모델링이 중요하다</li>
</ul>
<blockquote>
<p>샤딩이라고 하면 보통 수평 분할(행 단위)를 의미한다. 수직 분할(열 단위)는 자주 쓰는 컬럼과 안 쓰는 컬럼을 분리할 때 사용한다.</p>
</blockquote>
<h4 id="샤딩-vs-파티셔닝">샤딩 vs 파티셔닝</h4>
<ol>
<li><p>물리적 위치가 다르다. 
파티셔닝은 같은 DB 인스턴스 안에서 분리되지만, 샤딩은 서로 다른 DB 인스턴스로의 분산이다.</p>
</li>
<li><p>확장 방향이 다르다.
파티셔닝은 단일 서버 안에서의 최적화(Scale-Up 한계 내), 샤딩은 여러 서버로의 확장(Scale-Out) 전략</p>
</li>
<li><p>책임 주체가 다르다.
파티셔닝은 DB엔진이, 샤딩은 대게 애플리케이션(또는 미들웨어)이 어디로 보낼지 결정한다.(샤딩은 Shard Key 기반 라우팅 로직이 필요) </p>
</li>
</ol>
<h4 id="요점-정리">요점 정리</h4>
<p>파티셔닝은 단일 DB 서버 내에서 조회성능과 관리 효율을 위해 테이블을 쪼개는 내부 최적화이다. 샤딩은 하드웨어 한계를 넘기 위해 데이터를 여러 대의 서버에 분산하는 물리적 확장이다. 
파티셔닝은 SQL 변경 없이 투명하게 적용 가능하지만, 샤딩은 애플리케이션 레벨에서 데이터 분산 로직을 신경 써야 하며 Join 제약이 생긴다.
샤딩은 선택이 아니라 최후의 수단에 가깝다. 단일 DB의 한계를 넘었을 때 도입한다.</p>
<p>출처: 코딩하는 기술사 - 대량 데이터를 나누는 기술, 파티셔닝과 샤딩</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[비관 락 vs 원자적 업데이트]]></title>
            <link>https://velog.io/@jj_project10000/%EB%B9%84%EA%B4%80-%EB%9D%BD-vs-%EC%9B%90%EC%9E%90%EC%A0%81-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8</link>
            <guid>https://velog.io/@jj_project10000/%EB%B9%84%EA%B4%80-%EB%9D%BD-vs-%EC%9B%90%EC%9E%90%EC%A0%81-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8</guid>
            <pubDate>Mon, 23 Feb 2026 02:13:28 GMT</pubDate>
            <description><![CDATA[<h4 id="비관락과-원자적-업데이트의-차이">비관락과 원자적 업데이트의 차이</h4>
<p>원자적 UPDATE는 단일 쿼리로 조건 체크+ 수정을 수행한다. 락 범위는 행 수정 순간만 잡고, 쿼리가 끝난 이후에는 (트랜잭션이 아직 안끝나도) 바로 놓아버린다. 실패시 즉시 반환해버리기 때문에 대기가 거의 없다. 
반면 비관 락은 최소 2개 이상의 쿼리를 한다(SELECT+UPDATE). 트랜잭션 종료 전까지 락 대기를 안놓는다. </p>
<blockquote>
<p>비관 락을 쓰면 100명이 동시에 요청할 때 99명이 대기해야 하지만 , 원자적 UPDATE는 모두 즉시 처리된다(성공or 실패)</p>
</blockquote>
<h4 id="언제-뭘-쓸까">언제 뭘 쓸까?</h4>
<p>원자적 UPDATE가 적합한 경우는 단순 증감 연산(재고 -1, 좋아요 +1)이 있다. 조건이 단순(remaining &gt;0)하고 선착순 이벤트같은 높은 동시성이 필요할 때 적합하다.
비관 락은 조회 후 복잡한 계산이 필요할 때 쓰인다. 여러 필드를 조건부로 수정하고 다른 테이블과 연계된 로직일 때 적합하다.</p>
<h4 id="락-유지-시간-비교">락 유지 시간 비교</h4>
<pre><code>
  &lt;원자적 UPDATE&gt;

  DB 내부에서 모든 게 처리됨
  ┌─────────────────────────┐
  │ 락 획득 → 조건체크 → 수정 → 락 해제 │  ← ~1ms
  └─────────────────────────┘

  &lt;비관적 락 (SELECT FOR UPDATE)&gt;

  App ←────────── 네트워크 ──────────→ DB

  1. SELECT FOR UPDATE 요청 ─────────→ 락 획득
  2. 결과 반환 ←─────────────────────  │
  3. Java 비즈니스 로직 실행            │ 락 유지 중
  4. UPDATE 요청 ───────────────────→  │
  5. 커밋 ──────────────────────────→ 락 해제

  └──────────── 수십~수백 ms ────────────┘```

</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[MVCC에 대해서 공부해보자]]></title>
            <link>https://velog.io/@jj_project10000/MVCC%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EA%B3%B5%EB%B6%80%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@jj_project10000/MVCC%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EA%B3%B5%EB%B6%80%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Sun, 22 Feb 2026 07:00:04 GMT</pubDate>
            <description><![CDATA[<h4 id="mvcc가-무엇인가">MVCC가 무엇인가</h4>
<blockquote>
<p>Multi-Version Concurrency Control</p>
</blockquote>
<p>데이터를 제자리에서 덮어쓰지 않고 변경 시 새로운 버전을 하나 더 만들어 둔다.
각 트랜잭션은 시작 시점(or 특정 시점)의 스냅샷을 기준으로 데이터를 읽기 때문에, 다른 트랜잭션이 수정중이어도 일관된 읽기가 가능하다. 덕분에 읽기 작업은 보통 락을 잡지 않고 수행되고, 쓰기 충돌이 날 때만 롤백, 재시도를 통해 정합성을 맞춘다.</p>
<h4 id="mvcc와-격리수준과의-관계">MVCC와 격리수준과의 관계</h4>
<ul>
<li>Read Committed: 매 쿼리마다 최신 커밋 버전 기준으로 본다.</li>
<li>Repeatable Read: 트랜잭션 시작 시점 스냅샷을 유지한다. 따라서 한 트랜잭션 안에서는 동일한 스냅샷을 바라본다.</li>
<li>Serializable: 읽기 작업에도 배타적 락을 건다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[redis 분산락]]></title>
            <link>https://velog.io/@jj_project10000/redis-%EB%B6%84%EC%82%B0%EB%9D%BD</link>
            <guid>https://velog.io/@jj_project10000/redis-%EB%B6%84%EC%82%B0%EB%9D%BD</guid>
            <pubDate>Sat, 21 Feb 2026 13:06:24 GMT</pubDate>
            <description><![CDATA[<h4 id="분산-락이란">분산 락이란?</h4>
<p>다수의 서버가 동시에 같은 자원에 접근할 때 발생할 수 있는 동시성 문제를 해결하기 위해 사용되는 동기화 메커니즘이다. 분산 락의 핵심은 &#39;하나의 자원에 대해 한번 에 하나의 서버만 작업을 수행할 수 있다&#39;는 것이다.</p>
<h4 id="분산락이-필요한-이유">분산락이 필요한 이유</h4>
<p>서버가 1대라면 서버의 내부 자원 잠금(Lock) 기능으로 동시성을 제어할 수 있다. 하지만 서버가 n대로 늘어나면 어떨까</p>
<blockquote>
<p>상황: 남은 쿠폰이 딱 1장인 상태.
문제: 서버 A, B, C에 각각 사용자가 동시에 요청을 보낸다
결과: 각 서버는 &quot;어? 아직 1장 남았네?&quot;라고 판단하고 동시에 쿠폰을 지급해 버립니다. 결국 1장 남은 쿠폰이 3명에게 나가는 사고가 발생한다.</p>
</blockquote>
<h4 id="redis를-활용한-해결책">Redis를 활용한 해결책</h4>
<p>Redis는 속도가 매우 빠르고, 모든 서버가 공통으로 바라볼 수 있는 저장소이기 때문에 분산락을 구현하기에 최적이다.
분산 락은 마치 공용 화장실의 열쇠와 같다.</p>
<blockquote>
<p>락 획득: 서버 A가 작업을 시작하기 전, Redis에 coupon_lock이라는 키를 생성한다. (열쇠를 가져감)
상호 배제: 서버 B와 C가 Redis를 확인했을 때 이미 키가 존재하면, &quot;누군가 사용 중이구나&quot;라고 판단하고 대기한다.
락 해제: 서버 A가 쿠폰 지급을 완료하면 Redis에서 해당 키를 삭제한다 (열쇠를 반납함)
다음 차례: 대기하던 서버 중 하나가 다시 열쇠를 차지하고 작업을 수행한다.</p>
</blockquote>
<p>Redis로 구현할 때는 주로 두가지 라이브러리를 사용한다.
<img src="https://velog.velcdn.com/images/jj_project10000/post/cd638ea5-02a2-4406-b6d1-1a607b81e6ff/image.png" alt=""></p>
<p>만약 서버 A가 열쇠를 가져갔는데, 갑자기 서버가 다운되면 어떻게 될까? 열쇠를 영영 반납하지 않아 시스템이 멈출 수 있다. 이를 방지하기 위해 유효시간을 반드시 설정해야 한다. 시간이 지나면 열쇠가 자동으로 소멸된다.</p>
<h4 id="db락과의-비교">DB락과의 비교</h4>
<ol>
<li><p>성능과 속도
Redis 락은 메모리(RAM)기반이어서 읽고 쓰는 속도가 압도적으로 빠르기 때문에 락을 잡고 해제하는 과정이 매우 가볍다. 
반면 DB락은 디스크 기반이다. 락 정보를 기록하는 관리과정이 Redis보다 무겁고 느리다.</p>
</li>
<li><p>DB서버의 CPU와 메모리 점유율 부담
모든 요청이 DB에 직접 락을 걸면 CPU와 메모리 점유율이 올라간다. DB락이 길어지면 다른 일반적인 조회 업무까지 느려지는 병목 현상이 발생한다.</p>
<blockquote>
<p>왜 DB락은 CPU와 메모리가 힘들어 할까?</p>
</blockquote>
</li>
</ol>
<h5 id="context-switching">Context Switching</h5>
<p>OS는 계속 스케쥴링을 한다. 1번 요청이 락을 잡고 있는 상황에서, 1번-&gt;2번-&gt;3번-&gt;1번 순서로 스케쥴링이 되었다고 해보자.
1번 요청은 트랜잭션을 끝마치지 못하고 CPU를 2번에게 넘겨준다. 2번 요청의 첫번째 작업은 안타깝게도 락을 확인하는 것. 락이 없으니 CPU를 3번에게 넘겨준다. 3번 요청의 시작도 락을 확인하는 것이다. 할 수 있는게 없으니 다시 CPU는 1번 작업을 한다.
이 과정에서 CPU는 context-switching을 하는데 이 자체가 CPU에게는 엄청난 에너지 소모이다.</p>
<p><del>CPU는 가만히 있지 못하는 성격이다. 1번 요청이 락을 잡고 작업하는 동안, CPU는 놀지 않으려고 2번,3번 요청을 살짝 쳐다본다.하지만 &#39;아직 락이 안풀렸네?&#39;하고 1번으로 돌아온다. 이 과정을 수만 번 반복하면서 CPU는 &#39;기다리는 것 자체&#39;에 엄청난 에너지를 소모한다.</del></p>
<blockquote>
<p>redis 분산 락은 context-switching을 안하나요?</p>
</blockquote>
<ul>
<li>일반적인 방식 (Lettuce): 말씀하신 대로 2번이 CPU를 잡고 &quot;락 있니?&quot; 물어보고 내려오는 과정을 반복한다.</li>
<li>Redisson 방식 (Pub/Sub): 2번 스레드가 락 획득에 실패하면, CPU에게 <strong>&quot;나 락 풀릴 때까지 잠들게(Waiting)&quot;</strong>라고 요청한다. 그러면 OS는 2번 스레드를 &#39;실행 가능한 목록&#39;에서 아예 빼버린다.</li>
</ul>
<h5 id="connection-pool-고갈">Connection Pool 고갈</h5>
<p>DB와 통신하려면 연결 통로가 필요하다. 그런데 1000명이 락이 풀리기만을 기다리면서 통로를 하나씩 차지하고 있다면? 락과 상관없는 조회업무를 하려는 일반 사용자의 요청도 들어올 통로가 없어서 입구컷을 당한다. </p>
<h5 id="도미노-현상">도미노 현상</h5>
<p>정상 상황이라면 0.01초만에 락이 풀려 대기 줄이 금방 줄어든다. 하지만 네트워크나 복잡한 로직때문에 락이 1초동안 유지된다면? 그 1초 사이에 새로운 요청이 500개 더 들어와 대기열기 기하급수적으로 늘어난다. DB가 수많은 연결을 관리하다가 메모리 부족으로 뻗어버릴 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DB Connection Pool]]></title>
            <link>https://velog.io/@jj_project10000/DB-Connection-Pool</link>
            <guid>https://velog.io/@jj_project10000/DB-Connection-Pool</guid>
            <pubDate>Fri, 20 Feb 2026 06:12:26 GMT</pubDate>
            <description><![CDATA[<h4 id="✅-db-connection-pool이란">✅ DB Connection Pool이란?</h4>
<p>애플리케이션과 데이터베이스 사이의 연결을 미리 여러 개 생성해 두고, 필요할 때마다
빌려준 뒤 다 쓰면 다시 반납받는 연결관리소.</p>
<h4 id="✅-왜-사용해야-할까">✅ 왜 사용해야 할까?</h4>
<p>데이터베이스와 새로운 연결을 맺고 끊는 과정은 컴퓨터 입장에서 비용이 매우 크고 시간이 오래 걸리는 무거운 작업이다.
만약 사용자가 웹사이트에 접속할 때마다 DB와 매번 새로운 연결을 맺어야 한다면 응답속도가 크게 떨어지고, DB 서버에도 엄청난 부하가 발생한다. 커넥션 풀을 사용하면 미리 만들어둔 연결을 재사용하기 때문에 애플리케이션의 성능과 처리 속도가 획기적으로 향상된다.</p>
<h4 id="✅-락과-커넥션-반납-지연의-관계">✅ 락과 커넥션 반납 지연의 관계</h4>
<p>스프링부트에서 @Transactional 등을 통해 트랜잭션이 시작되면, 애플리케이션은 커넥션 풀에서 커넥션을 하나 빌려온다. 중요한 점은 트랜잭션이 완전히 종료(Commit 또는 Rollback) 될 때까지 이 커넥션을 절대 풀에 반납하지 않는다.
만약 트랜잭션 내부에서 특정 데이터에 락을 잡고 오랫동안 놓아주지 않는다면, 자연스럽게 해당 커넥션도 오랜 시간 어플리케이션에 묶여 있게 된다.</p>
<h4 id="✅-반납이-늦어지면-발생하는-현상">✅ 반납이 늦어지면 발생하는 현상</h4>
<ol>
<li>커넥션 고갈(Connection Starvation): 락 대기 등으로 지연되는 트랜잭션들이 쌓이면, 풀(Pool)에 있는 모든 커넥션이 사용중이 된다</li>
<li>대기열 발생(Thread Block): 새로운 요청을 보낸 사용자들은 풀에 남은 커넥션이 없으므로, 누군가 커넥션을 반납할 때 까지 대기한다. 애플리케이션의 스레드들이 커넥션을 기다니며 멈춰버린다.</li>
<li>타임아웃 및 장애: 설정된 대기시간이 지나도 커넥션을 얻지 못하면 애플리케이션은 예외를 던진다. </li>
</ol>
<h4 id="✅-실무에서의-예방책">✅ 실무에서의 예방책</h4>
<ul>
<li>트랜잭션 범위 최소화: 핵심로직에만 좁게 트랜잭션을 건다</li>
<li>트랜잭션 내부에서 외부 I/O 작업 절대 금지: 트랜잭션 안에서 외부API를 호출하거나 무거운 파일 처리를 하는 건 금물이다. 만약 외부 API 응답이 5초 지연되면 귀한 DB 커넥션 1개도 아무 일을 안하면서 낭비된다.</li>
<li>쿼리 튜닝 및 인덱스 최적화: 락을 잡는 시간 자체를 최소화하기 위해 데이터베이스가 데이터를 빠르게 찾고 락을 빨리 풀 수 있도록 인덱스를 적절하게 타도록 설계해야 한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[DDD 패턴]]></title>
            <link>https://velog.io/@jj_project10000/DDD-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@jj_project10000/DDD-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Fri, 20 Feb 2026 06:09:17 GMT</pubDate>
            <description><![CDATA[<p>현재 배달앱 커머스를 개발 중인데 layered architecture로 구현했다가 비즈니스 로직이 서비스에 집중돼서 테스트 코드 및 유지보수가 너무 어렵다..
그래서 DDD패턴으로 바꿔보고자 한다.</p>
<p>Domain-Driven-Design</p>
<ul>
<li>비즈니스 로직이 Entity/Aggregate에 응집된다. 그래서 비즈니스 규칙 변경시 Domain만 수정하면 된다.</li>
<li>Bounded Context 분리로 MSA 환경에서 확장성이 용이하다.</li>
<li>Aggregate Root가 비즈니스 규칙을 강제한다.</li>
</ul>
<h4 id="bounded-context">Bounded Context</h4>
<blockquote>
<p>특정 도메인 모델이 유효하게 적용되는 명확한 경계. 
가령, 배달 앱에서의 고객(Customer) 이라는 단어가 쓰이더라도 각 비즈니스(Context)마다 고객이 의미하는 바와 필요한 데이터가 다르다. 주문 컨텍스트에서 고객은 무슨 메뉴를 장바구니에 담았고 결제는 어떻게 했는지가 중요한 주체이다. 하지만 배달 컨텍스트에서는 고객의 결제 수단이나 메뉴는 중요하지 않다. 여기서의 고객은 오직 음식을 받은 도착치 좌표와 수령시 요청사항으로 정의된다.
&#39;여기서부터 여기까지는 이 단어를 이런 의미로만 쓴다&#39;라고 선을 긋는 작업이 Bounded Context이다.</p>
</blockquote>
<pre><code>┌─────────────────────────────────────────────────────────────┐
│  Presentation Layer                                         │
│  • Controller, DTO (Request/Response)                       │
│  • 사용자 입력 검증, 응답 변환                                  │
└─────────────────────────────────────────────────────────────┘
│ depends on
▼
┌─────────────────────────────────────────────────────────────┐
│  Application Layer                                          │
│  • Application Service (유스케이스 조율)                      │
│  • Command/Query Service (CQRS)                             │
│  • 트랜잭션 관리, 이벤트 발행                                  │
└─────────────────────────────────────────────────────────────┘
│ depends on
▼
┌─────────────────────────────────────────────────────────────┐
│  Domain Layer (핵심)                                         │
│  • Entity, Value Object, Aggregate                          │
│  • Domain Service, Domain Event                             │
│  • Repository Interface                                     │
│  ※ 외부 의존성 없음, 순수 Java 코드                            │
└─────────────────────────────────────────────────────────────┘
▲
│ implements
┌─────────────────────────────────────────────────────────────┐
│  Infrastructure Layer                                       │
│  • Repository 구현체 (JPA, Redis)                           │
│  • 외부 서비스 클라이언트 (Feign)                             │
│  • 메시징 (Kafka Producer/Consumer)                         │
└─────────────────────────────────────────────────────────────┘</code></pre><p>이 구조에서는 비즈니스 규칙이 수정되더라도 Domain Layer만 수정하면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[온라인 대기열 : 웹소켓 vs 폴링]]></title>
            <link>https://velog.io/@jj_project10000/%EC%98%A8%EB%9D%BC%EC%9D%B8-%EB%8C%80%EA%B8%B0%EC%97%B4-%EC%9B%B9%EC%86%8C%EC%BC%93-vs-%ED%8F%B4%EB%A7%81</link>
            <guid>https://velog.io/@jj_project10000/%EC%98%A8%EB%9D%BC%EC%9D%B8-%EB%8C%80%EA%B8%B0%EC%97%B4-%EC%9B%B9%EC%86%8C%EC%BC%93-vs-%ED%8F%B4%EB%A7%81</guid>
            <pubDate>Thu, 19 Feb 2026 12:38:40 GMT</pubDate>
            <description><![CDATA[<p>웹소켓과 폴링은 실시간 데이터 통신을 위한 기술이다.
웹소켓은 양방향 통신을 가능하게 하여 서버와 클라이언트 간에 실시간 데이터를 교환할 수 있게 한다.
폴링은 클라이언트가 n초 간격으로 주기적으로 서버에 요청을 보내 새로운 데이터가 있는지 확인하는 방법이다.</p>
<p>온라인 대기열을 구현할 때 크게 두가지 방식이 쓰이는 것 같다.</p>
<ol>
<li><p>Redis+폴링
유저가 예매 버튼을 누르면 Redis의 Sorted Set에 유저 ID와 타임스탬프를 저장해서 순번을 매긴다. 클라이언트는 1~3초 간격으로 폴링을 보내 내 앞에 몇 명이 남았는지 확인한다.
서버가 연결을 계속 유지할 필요가 없어 메모리 부담이 적고 redis의 원자적 연산 덕분에 순번 보장이 확실하다.</p>
</li>
<li><p>웹소켓
대기 순번이 실시간으로 줄어드는 것을 시각적으로 부드럽게 보여줘야 할 때 유리하다
대기열에 진입하는 순간 웹소켓 연결을 맺고 서버가 대기열 상태에 변할 때마다 푸시를 보낸다.
다만 대기자 수대로 커넥션을 유지해서 Gateway 서버의 부하가 매우 커지고 연결이 끊겼을 때 재연결 및 상태 복구 로직이 복잡한 편이다.</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[ORM , JPA , Hibernate]]></title>
            <link>https://velog.io/@jj_project10000/ORM-JPA-Hibernate</link>
            <guid>https://velog.io/@jj_project10000/ORM-JPA-Hibernate</guid>
            <pubDate>Sun, 25 Jan 2026 12:08:53 GMT</pubDate>
            <description><![CDATA[<h1 id="1-orm">1. ORM</h1>
<blockquote>
<p>Object-Relational Mapping
객체와 관계형 데이터 베이스의 데이터를 매핑하는 기술</p>
</blockquote>
<h1 id="2-jpa란">2. JPA란</h1>
<blockquote>
<p>Java Persistence API 데이터베이스를 쉽게 다루기 위한 데이터 엑세스 기술로 ORM기법을 사용하여 자바 어플리케이션에서 사용하는 객체와 관계형 데이터베이스 사이의 매핑을 관리하는 ORM에 대한 API 표준 명세서.</p>
</blockquote>
<p>JPA는 표준화된 API를 제공함으로써, 다양한 ORM 프레임워크(Hibernate,OpenJPA, EclipseLink) 와의 호환성을 보장한다.</p>
<p><img src="https://velog.velcdn.com/images/jj_project10000/post/466cd100-93de-42e2-ab2b-2684a7c36962/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jj_project10000/post/839a99d2-0516-4084-8ba1-e2c0440c6230/image.png" alt=""></p>
<pre><code>public interface UserRepository extends JpaRepository&lt;UserEntity, UUID&gt; {
    Optional&lt;UserEntity&gt; findByUserNickname(String nickname);
    boolean existsByUserEmail(String email);
    boolean existsByUserNickname(String nickname);
}</code></pre><pre><code>
✔️ 인터페이스에 약속된 규칙대로만 메서드를 쓰면, 구현체와 SQL이 자동으로 생성된다. </code></pre><pre><code>SELECT * FROM users WHERE nick_name=&#39;user3&#39; 이라는 쿼리를 직접 쓰지 않고
userRepository.findByUserNickName(&#39;user3&#39;) 
이렇게 간편하게 쿼리 작업을 할 수 있다. 
또한 반환값은 다시 매핑해줄 필요가 없는 객체(혹은 반대는 테이블)에 매핑해줄 필요가 없다.</code></pre><blockquote>
<p>만약 JPA가 없었다면 어떻게 해야 했을까?</p>
</blockquote>
<ol>
<li>SQL 문자열을 직접 작성한다.</li>
<li>jdbcTemplate에게 작성한 SQL실행을 부탁한다</li>
<li>DB 결과를 자바 객체로 직접 매핑해줘야 한다.</li>
<li>데이터가 없으면 직접 예외 처리를 해줘야 한다.</li>
</ol>
<p>참고:: <a href="https://adjh54.tistory.com/421">https://adjh54.tistory.com/421</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[코테 5문제 챌린지]day 18]]></title>
            <link>https://velog.io/@jj_project10000/%EC%BD%94%ED%85%8C-5%EB%AC%B8%EC%A0%9C-%EC%B1%8C%EB%A6%B0%EC%A7%80day-18</link>
            <guid>https://velog.io/@jj_project10000/%EC%BD%94%ED%85%8C-5%EB%AC%B8%EC%A0%9C-%EC%B1%8C%EB%A6%B0%EC%A7%80day-18</guid>
            <pubDate>Thu, 13 Nov 2025 07:00:00 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>1.미로탈출
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/159993">https://school.programmers.co.kr/learn/courses/30/lessons/159993</a></p>
</blockquote>
<blockquote>
<p>2.연속 펄스 부분 수열의 합
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/161988">https://school.programmers.co.kr/learn/courses/30/lessons/161988</a>
#카데인 알고리즘 다시풀기</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[코테 5문제 챌린지]day 17]]></title>
            <link>https://velog.io/@jj_project10000/%EC%BD%94%ED%85%8C-5%EB%AC%B8%EC%A0%9C-%EC%B1%8C%EB%A6%B0%EC%A7%80day-17</link>
            <guid>https://velog.io/@jj_project10000/%EC%BD%94%ED%85%8C-5%EB%AC%B8%EC%A0%9C-%EC%B1%8C%EB%A6%B0%EC%A7%80day-17</guid>
            <pubDate>Sun, 09 Nov 2025 14:14:46 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ol>
<li>수레 움직이기
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/250134">https://school.programmers.co.kr/learn/courses/30/lessons/250134</a></li>
</ol>
</blockquote>
<blockquote>
<ol start="2">
<li>구슬탈출
<a href="https://www.acmicpc.net/problem/13460">https://www.acmicpc.net/problem/13460</a></li>
</ol>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[코테 5문제 챌린지]day 17]]></title>
            <link>https://velog.io/@jj_project10000/%EC%BD%94%ED%85%8C-5%EB%AC%B8%EC%A0%9C-%EC%B1%8C%EB%A6%B0%EC%A7%80day-17-0zfw3btk</link>
            <guid>https://velog.io/@jj_project10000/%EC%BD%94%ED%85%8C-5%EB%AC%B8%EC%A0%9C-%EC%B1%8C%EB%A6%B0%EC%A7%80day-17-0zfw3btk</guid>
            <pubDate>Fri, 07 Nov 2025 04:15:20 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ol>
<li>리코쳇 로봇
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/169199">https://school.programmers.co.kr/learn/courses/30/lessons/169199</a></li>
</ol>
</blockquote>
<blockquote>
<p>2.혼자서 하는 틱택토
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/160585">https://school.programmers.co.kr/learn/courses/30/lessons/160585</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[코테 5문제 챌린지]day 16]]></title>
            <link>https://velog.io/@jj_project10000/%EC%BD%94%ED%85%8C-5%EB%AC%B8%EC%A0%9C-%EC%B1%8C%EB%A6%B0%EC%A7%80day-16</link>
            <guid>https://velog.io/@jj_project10000/%EC%BD%94%ED%85%8C-5%EB%AC%B8%EC%A0%9C-%EC%B1%8C%EB%A6%B0%EC%A7%80day-16</guid>
            <pubDate>Thu, 30 Oct 2025 15:11:10 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>1.이름에 el이 들어가는 동물 찾기
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/59047">https://school.programmers.co.kr/learn/courses/30/lessons/59047</a></p>
</blockquote>
<blockquote>
<ol start="2">
<li>카테고리 별 상품 개수 구하기
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/131529">https://school.programmers.co.kr/learn/courses/30/lessons/131529</a></li>
</ol>
</blockquote>
<blockquote>
<p>3.자동차 대여 기록 별 대여 금액 구하기
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/151141">https://school.programmers.co.kr/learn/courses/30/lessons/151141</a>
<strong>다시풀기</strong>(어려움)</p>
</blockquote>
<blockquote>
<ol start="4">
<li>두 원 사이의 정수 쌍
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/181187">https://school.programmers.co.kr/learn/courses/30/lessons/181187</a></li>
</ol>
</blockquote>
<blockquote>
<ol start="5">
<li>과제 진행하기
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/176962">https://school.programmers.co.kr/learn/courses/30/lessons/176962</a></li>
</ol>
<p><strong>다시풀기</strong></p>
</blockquote>
<blockquote>
<p>6.오랜 기간 보호한 동물(2)
<a href="https://school.programmers.co.kr/learn/courses/30/lessons/59411">https://school.programmers.co.kr/learn/courses/30/lessons/59411</a>
<strong>다시풀기</strong></p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>