<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dev_nana.log</title>
        <link>https://velog.io/</link>
        <description>BackEnd Developer,  기록의 힘을 믿습니다.</description>
        <lastBuildDate>Tue, 24 Jun 2025 14:22:18 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dev_nana.log</title>
            <url>https://velog.velcdn.com/images/dev_nana/profile/9468f72b-bd7e-47e8-86e8-14ae583c3755/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dev_nana.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dev_nana" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[진도저장 프로세스 신규 기술 도입기 - 1]]></title>
            <link>https://velog.io/@dev_nana/%EC%A7%84%EB%8F%84%EC%A0%80%EC%9E%A5-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EC%8B%A0%EA%B7%9C-%EA%B8%B0%EC%88%A0-%EB%8F%84%EC%9E%85%EA%B8%B0-1</link>
            <guid>https://velog.io/@dev_nana/%EC%A7%84%EB%8F%84%EC%A0%80%EC%9E%A5-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EC%8B%A0%EA%B7%9C-%EA%B8%B0%EC%88%A0-%EB%8F%84%EC%9E%85%EA%B8%B0-1</guid>
            <pubDate>Tue, 24 Jun 2025 14:22:18 GMT</pubDate>
            <description><![CDATA[<p>현재 회사는 온라인 학습을 서비스하는 회사이다. 
20년 넘은 회사인데다 B2B위주로 돌아가다보니 고객의 니즈를 충족시키기위한 커스텀으로 얼룩져있어 레거시 코드가 많이 잔존해있다. </p>
<p>매년 개선 사항으로 거론되는 것이 <strong>현재 진도 프로세스를 어떻게 개선할 것인가</strong>인데, 몇년동안 거론돼 왔음에도 불구하고 뾰족한 수를 찾지 못했다. </p>
<p>그러다 레디스와 카프카같은, 혹은 그에 준하는 좋은 기술들을 사용해볼 수 있지 않을까 하여 지피티와 여러방면으로 소통을 해 보았다. </p>
<hr>
<h2 id="1-현재-상황">1. 현재 상황</h2>
<h3 id="환경">환경</h3>
<ul>
<li><p>.Net Framework 4.8 </p>
</li>
<li><p>C# 7.3 </p>
</li>
<li><p>동시 접속자 최대 14,000~15,000명 </p>
</li>
</ul>
<h3 id="현재-진도-저장-방식의-문제점">현재 진도 저장 방식의 문제점</h3>
<blockquote>
<p>클라이언트(브라우저) AJAX OR 페이지 호출 &gt; 진도관련 프로시저 직접 호출 &gt; 로그 테이블 INSERT </p>
</blockquote>
<ul>
<li>DB부하집중 </li>
<li>직접 호출 실패 시 데이터 유실 가능 </li>
<li>사용자 반응 느려짐(응답지연) </li>
<li>오류 재 처리 불가(재전송 없으면 끝) </li>
<li>많은 유입 경로로 인한 관리의 어려움 </li>
<li>영상 유형에 따라 각기 다른 진도 저장, 계산 방식 </li>
</ul>
<hr>
<h2 id="2-kafka-도입-검토">2. Kafka 도입 검토</h2>
<p>현재 카프카를 쓰고있기도 하고, 대용량의 데이터를 일괄로 처리하기 위해 카프카도 괜찮을 것 같다는 생각을 했다. 
우선 장단점을 살펴보았다. </p>
<h3 id="✅-장점">✅ 장점</h3>
<ul>
<li>높은 확장성 (초당 수십만 메시지 처리 가능) </li>
<li>메시지 유실 방지(ack, 복제, 디스크 기반 저장) </li>
<li>장애 시 재처리, 로그 기반 분석 용이 </li>
<li>구조가 느슨하게 결합되어 유지보수에 유리 </li>
</ul>
<h3 id="❌-단점">❌ 단점</h3>
<ul>
<li><p>운영 복잡(Broker, ZooKeeper, 클러스터 구성 필요) </p>
</li>
<li><p>컨슈머가 늘어나면 파티션 수 증가 필요 → 인프라 자원 증가 </p>
</li>
<li><p>Kafka 자체는 무료; but  운영 / 인프라 / 학습 비용 높음 </p>
</li>
</ul>
<p>난잡하게 존재하는 프로세스를 하나로 합치는게 우선 큰 문제였고, 
프로세스를 개선하려면 혼자서는 못하는 큰 작업인데 운영이 복잡하고 학습비용과 인프라 비용이 높다는 단점에서 <em>당장의 해결책으로 쓰일 수 있을까</em>라는 고민이 있었다. </p>
<hr>
<h2 id="3-redis-streams-도입-검토">3. Redis Streams 도입 검토</h2>
<p>내가 알고있는건 Redis였는데, GPT는 <strong>Redis Streams</strong>라는 것을 추천해 주었다. </p>
<h4 id="⭐-redis-stream-이란">⭐ Redis Stream 이란?</h4>
<ul>
<li>Kafka와 유사한 기능들을 제공하면서 여러 개 pod(?)에서 구동해도 데이터 중복이나 유실없이 처리가 가능하다. </li>
<li>Redis 5.0에서 등장한 기술 </li>
<li>TPS가 수만이하 → Redis 단일 노드 또는 수직 확장으로 커버 가능 </li>
<li>메시지 보존 1~2일 이내 → Streams에 짧게 보존 후 삭제(XTRIM) </li>
<li>컨슈머 그룹 1~5개 → 처리 구조 단순</li>
<li>빠른 전송, 짧은 대기 → 실시간 반응 필요( ex. 진도 저장, 알림 큐 등) </li>
</ul>
<h3 id="✅-장점-1">✅ 장점</h3>
<ul>
<li>빠름(메모리 기반, 수만TPS까지 처리 가능)<ul>
<li>적은 리소스로 운영 가능 ( 단일 노드 또는 Sentinel 구성) </li>
<li>XACK, CPENDING 등으로 메시지 유실 방지 기능 </li>
<li>구현 및 운영이 Kafka보다 간단 </li>
<li>적절한 XTRIM, 샤딩을 통해 수명관리, 확장 가능  </li>
</ul>
</li>
</ul>
<h3 id="❌-단점-1">❌ 단점</h3>
<ul>
<li>메모리 기반이므로 보존기관 관리 필요( XTRIM으로 해결) </li>
<li>대규모 분석/장기 보존에는 불리 </li>
<li>Streams는 TTL이 직접 적용되지 않음. </li>
</ul>
<p>💡 
내가 하려고 했던 방식은
polling방식으로 계속 로그를 쌓은 뒤 일정 시간이 지나거나, 학습 종료 이벤트가 발생하면 로그성으로 저장된 학습데이터들을 집계하여 학습시간을 쌓는  것이었다. </p>
<p>적은 리소스로 운영이 가능하고 메모리 기반이어서 빠르다는 장점이 가장 내가 원하는 방식에 맞다고 생각이 들었다. </p>
<h4 id="redis-stream-vs-redis-pubsub">Redis Stream VS Redis Pub/Sub</h4>
<blockquote>
<ul>
<li>Pub/Sub
일반적으로 Redis를 이용해 메시지를 Broadcasting 할 때는 pub/sub을 많이 사용한다. 
하지만 이 방식은 publicsher가 메시지를 발행했을 때 subscriber가 존재하지 않거나 애플리케이션에 이슈가 발생하면 수신 여부에 관계없이 메시지가 휘발되는 단점이 있다. </li>
<li><em>여러개의 subscriber를 구동하면 모두에게 동일한 메시지를 발행해 데이터가 중복되는 이슈가 발생한다.*</em> </li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>Stream 
휘발성이 아닌, Kafka의 offset개념처럼 마지막으로 수신한 record id를 저장하고 XADD, XREADGROUP, XACK, XPENDING, XCLAIM으로 이어지는 처리 프로세스를 통해 메시지를 컨트롤 할 수 있는 다양한 방법을 제공한다. 
Consumer Group을 지원하기 때문에 producer가 발행한 메시지를 여러개의 consumer가 하나의 그룹을 형성해서 <strong>중복없이 순차적으로 병렬처리</strong>를 할 수 있다. 
XACK명령어를 사용해 <strong>메시지 처리 여부</strong>를 확인할 수 있다. 일정시간 처리되지 못한 메시지들도 Pending Entries List를 이용해서 <strong>재처리</strong>할 수 있는 방법을 제공한다. </li>
</ul>
</blockquote>
<hr>
<p> 지금까지 나온 세가지 방식을 비교해 보자면, </p>
<table>
<thead>
<tr>
<th>항목</th>
<th>유실가능성</th>
<th>처리 성능</th>
<th>운영 복잡도</th>
<th>확장성</th>
<th>비용</th>
</tr>
</thead>
<tbody><tr>
<td>DB 직접 저장(현재방식)</td>
<td>있음(네트워크 실패 등)</td>
<td>낮음(DB 병목 발생)</td>
<td>낮음</td>
<td>낮음</td>
<td>DB부하로 간접비용 많음</td>
</tr>
<tr>
<td>Redis Streams</td>
<td>거의 없음(ACK, 재처리가능)</td>
<td>높음(수만 TPS처리가능)</td>
<td>중간</td>
<td>중간</td>
<td>저렴(단일 인스턴스로 충분)</td>
</tr>
<tr>
<td>Kafka</td>
<td>없음 수준(복제, 로그저장)</td>
<td>매우 높음(수십만 TPS이상)</td>
<td>높음</td>
<td>높음</td>
<td>높음(클러스터/모니터링 등)</td>
</tr>
</tbody></table>
<p>Redis Streams의 유실가능성이 조금 걱정되긴하지만 재처리 기능이 있기에 구조만 단단하게 만들어 놓으면 유실가능성이 적을 것이라 예상되었다. 
무엇보다 비용적인 측면에서 직접적으로 DB를 호출하는 것 보다 별개의 비동기방식으로 계산하는게 운영, 비용적인 측면에서 유리할거라고 생각했다. </p>
<hr>
<h3 id="4-전환-시나리오">4. 전환 시나리오</h3>
<p>자, 그럼 어떻게 전환해 가면 좋을까? </p>
<blockquote>
<p>DB직접저장 (현재) 
▶️ Redis Streams로 전환 (동접 14,000명 수준은 Redis Streams로 커버가능 / 메시지 유실도 거의 없음 XACK, XPENDING활용)
▶️ TPS 2~3만 이상, 장기 보존이나 분석 필요한 경우, <strong>컨슈머 병렬성 증가 시점에 Kafka로 전환</strong></p>
</blockquote>
<p>Redis Streams 적용 구조 </p>
<blockquote>
<p>[Client] 
⬇ (30초마다 요청) 
[XADD] → Redis Stream 
    ⬇ 
[Redis Consumer] 
    ⬇ 
[DB INSERT] → day_log 테이블 
    ⬇ 
[XACK] (정상 처리 완료) </p>
</blockquote>
<p>🔅 클라이언트 요청은 Redis에 넣기만 하고 끝
 → Consumer가 나중에 DB비동기로 저장 
→ 빠르고 유실없이 확장성도 높다. </p>
<p>Redis에서 Kafka로 전환하는 과정에서 일을 두번만드는 것은 아닐까 걱정이 된다. 
회사가 성장세라고는 하지만 동접 2만의 시대는 아직 많이 멀은 것 같다. 
그리고 1차 작업으로 레디스를 사용해 놓으면 카프카로 전환하는 작업은 어렵지 않게 할 수 있을 것이라 예상되었다. </p>
<p>나는 이 분석 문서들을 들고 회의에 들어갔다. 
To Be Continue..... </p>
<hr>
<h3 id="💎-용어--개념-정리">💎 용어 / 개념 정리</h3>
<p><code>TPS (Transactions Per Second)</code> : 초당 처리되는 트랜잭션(작업, 요청)의 수 </p>
<p>ex) 1초에 사용자 10명이 ‘진도 저장’ 요청 &gt; 10TPS </p>
<p>ex 2) 1초에 Kafka가 50개의 메시지 수신 &gt; 50TPS</p>
<p>ex 3) 1초에 Redis Streams에 200개의 XADD요청 &gt; 200TPS </p>
<p>cf) QPS(Queries Per Second) : 초당 쿼리 처리 건수 =&gt; DB성능 측정에 사용 </p>
<p>cf2) RPS (Requests Per Second) : 초당 HTTP 요청 수  =&gt; 웹 서버나 API 측정 시 사용 </p>
<p>cf3) Throughput : 단위 시간당 처리량 =&gt; TPS와 유사하지만 넓은 개념 </p>
<p>👉🏼 TPS가 높을수록 성능이 좋은 시스템 </p>
<p>TPS 가 너무 낮으면 동시 사용자 증가 시 지연, 타임아웃, 장애가 발생할 수 있음. </p>
<p><code>TTL</code> : 일반 Redis키에 적용되는 만료. Streams에는 직접 적용 불가. =&gt; 지정된 시간이 지나면 해당 키는 자동 삭제 됨. </p>
<p><code>XTRIM</code> : Streams의 길이 제한 (메시지 자동 삭제용) </p>
<p><code>XACK</code> : 메시지 소비 완료를 명시적으로 알림 </p>
<p><code>샤딩</code> : Stream키를 나누거나 Redis Cluster구성으로 수평 확장 </p>
<h3 id="redis-streams-용어">Redis Streams 용어</h3>
<table>
<thead>
<tr>
<th>용어</th>
<th>의미</th>
<th>쉽게 설명</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td>XADD</td>
<td>메시지 추가</td>
<td>Stream에 메시지를 넣는다</td>
<td>XADD mystream * field1 value1</td>
</tr>
<tr>
<td>XREAD</td>
<td>메시지 읽기</td>
<td>단순 읽기 (비동기 아님)</td>
<td>XREAD COUNT 1 STREAMS mystream 0</td>
</tr>
<tr>
<td>XREADGROUP</td>
<td>메시지 읽기 (그룹 기반)</td>
<td>&quot;소비자 그룹으로 읽는다 (Kafka의 Consumer Group과 유사)&quot;</td>
<td>XREADGROUP GROUP mygroup myconsumer STREAMS mystream &gt;</td>
</tr>
<tr>
<td>XACK</td>
<td>메시지 처리 완료</td>
<td>&quot;메시지 잘 받았다고 알리기 (Kafka의 commit 비슷)&quot;</td>
<td>XACK mystream mygroup 169...-0</td>
</tr>
<tr>
<td>XPENDING</td>
<td>처리 안 된 메시지 확인</td>
<td>&quot;아직 ACK 안 한 메시지들 목록 보기&quot;</td>
<td>XPENDING mystream mygroup</td>
</tr>
<tr>
<td>XCLAIM</td>
<td>메시지 재할당</td>
<td>&quot;다른 컨슈머가 처리 못한 메시지를 내가 가져오기&quot;</td>
<td>XCLAIM mystream mygroup new-consumer 60000 169...-0</td>
</tr>
<tr>
<td>XTRIM</td>
<td>오래된 메시지 삭제</td>
<td>&quot;Stream 길이를 제한하여 오래된 것 잘라내기&quot;</td>
<td>XTRIM mystream MAXLEN ~ 1000</td>
</tr>
<tr>
<td>Consumer Group</td>
<td>소비자 그룹</td>
<td>여러 컨슈머가 메시지를 나눠 처리할 수 있도록 묶은 그룹</td>
<td>XGROUP CREATE mystream mygroup $</td>
</tr>
<tr>
<td>Stream Key</td>
<td>Stream 이름</td>
<td>데이터가 들어가는 큐 같은 공간의 이름</td>
<td>mystream, lecture_progress</td>
</tr>
</tbody></table>
<h3 id="kafka-용어">Kafka 용어</h3>
<table>
<thead>
<tr>
<th>용어</th>
<th>의미</th>
<th>쉽게 설명</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td>Producer</td>
<td>메시지 발행자</td>
<td>메시지를 Kafka에 보내는 역할</td>
<td>C# 클라이언트에서 Kafka에 메시지 전송</td>
</tr>
<tr>
<td>Consumer</td>
<td>메시지 소비자</td>
<td>메시지를 Kafka에서 읽어오는 역할</td>
<td>백엔드 서비스에서 메시지 읽음</td>
</tr>
<tr>
<td>Topic</td>
<td>메시지 카테고리</td>
<td>메시지를 구분하는 &#39;폴더 이름&#39; 같은 역할</td>
<td>lecture-progress, video-log</td>
</tr>
<tr>
<td>Partition</td>
<td>메시지 분산 단위</td>
<td>하나의 Topic을 나눠서 여러 Consumer가 병렬로 처리 가능</td>
<td>Topic lecture-progress → 3개의 Partition</td>
</tr>
<tr>
<td>Offset</td>
<td>메시지 위치</td>
<td>메시지가 Topic에서 몇 번째인지 위치값</td>
<td>offset = 15면 15번째 메시지</td>
</tr>
<tr>
<td>Consumer Group</td>
<td>컨슈머 묶음</td>
<td>메시지를 나눠 처리할 수 있게 여러 Consumer를 하나로 묶은 구조</td>
<td></td>
</tr>
<tr>
<td>group_id = mygroup</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Broker</td>
<td>Kafka 서버</td>
<td>메시지를 저장하고 전송하는 Kafka 인스턴스</td>
<td>최소 3개로 구성 권장</td>
</tr>
<tr>
<td>ZooKeeper</td>
<td>클러스터 관리 도구</td>
<td>Kafka 2.x까지 필요, 클러스터 리더 선출 등</td>
<td>Kafka 3.x부터는 선택 사항</td>
</tr>
<tr>
<td>Retention</td>
<td>메시지 보존 기간</td>
<td>메시지를 Kafka에 얼마나 오래 저장할지 설정</td>
<td>기본 7일, 설정 가능</td>
</tr>
<tr>
<td>acks</td>
<td>저장 확인 옵션</td>
<td>메시지를 Kafka에 보낼 때 &quot;몇 개의 복제본에 저장되었는지&quot; 확인하는 설정</td>
<td>acks=all은 가장 안전함</td>
</tr>
<tr>
<td>commit</td>
<td>처리 완료 표시</td>
<td>메시지를 읽고 나서 &quot;처리 완료&quot;했다고 표시 (수동 또는 자동)</td>
<td>enable.auto.commit=false일 경우 직접 commit</td>
</tr>
</tbody></table>
<h3 id="알기-쉽게-설명-2">알기 쉽게 설명 2</h3>
<p> ✔️ Kafka : 대형 물류 센터 (견고한 구조) </p>
<ul>
<li><p>물건(메시지)을 입고 → 무조건 저장소(디스크)에 저장. </p>
</li>
<li><p>모든 입고 물품에 기록(로그)를 남기고 영구보관도 가능. </p>
</li>
<li><p>직원들(Consumer)이 박스를 나눠서, 파트별로 병렬로 처리</p>
</li>
<li><p>직원이 일을 안하면, 다른사람이 대신 처리할 수도 있음. </p>
</li>
<li><p>기록을 못 찾는 일은 거의 없음(복제, 로그 저장, 디스크 기반) </p>
</li>
</ul>
<p> ✔️ Redis : 빠른 택배 보관소 (가벼운 구조) </p>
<ul>
<li><p>빠르게 물건(메시지)를 받고 → 메모리에 저장 </p>
</li>
<li><p>오래 두면 자동으로 지워짐(보관기간 짧음) </p>
</li>
<li><p>단순하지만 빠르게 꺼내쓸 수 있음 </p>
</li>
<li><p>속도는 매우 빠르나 오래 보관하거나 대규모 작업에는 부적합 </p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DataBase] 쿼리 최적화를 하는 10가지 방법 ]]></title>
            <link>https://velog.io/@dev_nana/DataBase-%EC%BF%BC%EB%A6%AC-%EC%B5%9C%EC%A0%81%ED%99%94%EB%A5%BC-%ED%95%98%EB%8A%94-10%EA%B0%80%EC%A7%80-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@dev_nana/DataBase-%EC%BF%BC%EB%A6%AC-%EC%B5%9C%EC%A0%81%ED%99%94%EB%A5%BC-%ED%95%98%EB%8A%94-10%EA%B0%80%EC%A7%80-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Wed, 14 May 2025 06:56:17 GMT</pubDate>
            <description><![CDATA[<h2 id="db-최적화는-왜-중요할까">DB 최적화는 왜 중요할까?</h2>
<h3 id="시스템이-안정적으로-동작하려면-어떤-요소들이-가장-중요할까">시스템이 안정적으로 동작하려면 어떤 요소들이 가장 중요할까?</h3>
<p>많은 백엔드 개발자들이 아래와 같은 시스템 품질 요소들을 떠올릴 것이다.</p>
<ul>
<li><p>신뢰성(Reliability): 시스템이 정상적으로 작동하고, 실패 시에도 빠르게 복구되는 능력</p>
</li>
<li><p>가용성(Availability): 시스템이 언제든 접근 가능하고 멈추지 않는 것</p>
</li>
<li><p>일관성(Consistency): 데이터가 항상 정확하고 신뢰할 수 있는 상태를 유지하는 것</p>
</li>
<li><p>확장성(Scalability): 트래픽 증가에 유연하게 대응할 수 있는 구조</p>
</li>
<li><p>관측 가능성(Observability): 문제 발생 시 원인을 빠르게 파악할 수 있도록 시스템 내부 상태가 모니터링되는 것</p>
</li>
</ul>
<p>이 모든 요소들은 각기 중요하지만, 그 바탕에는 <strong>‘잘 설계된 데이터베이스’</strong> 와 <strong>‘최적화된 쿼리’</strong> 가 있다고 생각한다.</p>
<p>아무리 시스템 구조가 견고하고 마이크로서비스로 분리되어 있더라도,</p>
<p><em>데이터베이스가 병목이 되면 전체 서비스의 신뢰성, 가용성, 심지어 확장성까지도 무너질 수 있다.</em></p>
<p>이 글에서는 단순히 쿼리 튜닝 기법을 나열하는 데 그치지 않고,
<strong>왜 DB 최적화가 중요한지,
시스템 전반에 어떤 영향을 미치는지,
그리고 실무에서 자주 마주하는 DB 병목 패턴과 이를 해결하는 전략들</strong>에 대해 이야기해보려 한다.</p>
<hr>
<h2 id="db가-시스템-전반에-미치는-영향">DB가 시스템 전반에 미치는 영향</h2>
<p>데이터베이스는 현대 애플리케이션의 핵심 구성요소이다. 
성능 최적화는 시스템의 전반적인 효율성을 높이는 데 필수적이고, 데이터베이스의 성능이 저하된다면 애플리케이션의 응답시간이 길어지고 사용자 경험이 저하될 수 있다. </p>
<p>앞서 말한 요소들 대부분을 보장하는 데에 중요하므로 데이터베이스 최적화는 필수적으로 해야하는 작업이라고 볼 수 있다. </p>
<hr>
<h2 id="⭐-db-최적화는-왜-중요할까">⭐ DB 최적화는 왜 중요할까?</h2>
<h3 id="1-시스템-성능-병목은-대부분-db에서-발생한다">1. 시스템 성능 병목은 대부분 DB에서 발생한다.</h3>
<p>잘못된 인덱스 설계, 비효율적인 쿼리 하나가 수많은 사용자의 응답 속도를 떨어뜨릴 수 있다. </p>
<p>ex) 게시판 목록 조회 쿼리에 인덱스가 빠져있다면 1만건 중 10건을 찾는데도 전체 테이블을 다 스캔하게 된다. </p>
<h3 id="2-db는-리소스가-제한적이다">2. DB는 리소스가 제한적이다.</h3>
<p>서버는 수평 확장이 비교적 쉽지만, <strong>DB는 수평확장이 어렵고 비용이 많이 든다</strong>. 
DB최적화를 통해 CPU, 메모리, I/O자원을 최소화 하는 쿼리와 구조가 중요하다. </p>
<h3 id="3-서비스의-신뢰성-가용성-확장성을-모두-좌우함">3. 서비스의 신뢰성, 가용성, 확장성을 모두 좌우함.</h3>
<ul>
<li>느린 쿼리 = 타임아웃 = 사용자 오류발생(신뢰성 저하) </li>
<li>DB CPU 100% = 전체 서비스 응답 불가 (가용성 저하) </li>
<li>트래픽 증가 = DB에 과부하(확장성 한계) </li>
</ul>
<p>❗ <strong>DB가 병목이면 아무리 잘 만든 시스템도 무너진다.</strong> </p>
<h3 id="4-비용-절감에도-직접적">4. 비용 절감에도 직접적</h3>
<p>클라우드 환경에서는 RDS Aurora등의 사용량이 곧 비용과 직결된다. 
한 쿼리의 최적화로 수천만원의 비용을 줄이는 사례도 실무에서 자주 있다. </p>
<h3 id="5-유지보수가-쉬워짐">5. 유지보수가 쉬워짐.</h3>
<p>데이터 모델이 명확하고, 쿼리가 예측 가능하며, 인덱스가 체계적이면 
<strong>장기적으로 기능 추가나 변경이 훨씬 수월</strong>해진다. </p>
<blockquote>
<p><strong>DB최적화는 &#39;성능 향상&#39;을 넘어, &#39;시스템 전체의 안정성·확장성·비용·유지보수성’까지 책임지는 핵심 작업&#39;</strong> 이다. </p>
</blockquote>
<p>Redis나 Kafka, 등등의 기술들이 DB의 부하를 줄여준다지만 결국 그들또한 보조도구일 뿐이고 기본은 DB쿼리부터 시작되어야한다. </p>
<hr>
<h2 id="🧨자주-마주하는-병목-패턴">🧨자주 마주하는 병목 패턴</h2>
<h3 id="1-인덱스-미사용-또는-잘못된-인덱스-설계">1. 인덱스 미사용 또는 잘못된 인덱스 설계</h3>
<p><code>WHERE절, JOIN, ORDER BY</code>등에 필요한 인덱스가 없으면 테이블을 전체 스캔(Full Table Scan)하게 된다. </p>
<h3 id="2-n1-문제">2. N+1 문제</h3>
<p>JPA나 ORM사용 시, 연관된 데이터를 <code>지연로딩(lazy loading)</code>하면서 반복문 안에서 추가 쿼리가 발생한다. </p>
<pre><code class="language-java">for (Order order : orders) {
    System.out.println(order.getCustomer().getName()); // N+1 발생 가능
}
</code></pre>
<h3 id="3-불필요한-distinct-group-by-join-남용">3. 불필요한 DISTINCT, GROUP BY, JOIN 남용</h3>
<p>무의식 적으로 사용한다면 성능에 큰 영향을 줄 수 있다. 
: 쿼리 복잡도 증가, 정렬/집계비용 발생 </p>
<h3 id="4-비효율적인-like검색">4. 비효율적인 LIKE검색</h3>
<p><code>%</code>로 시작하는 검색은 <strong>인덱스를 사용할 수 없다</strong> 
: 테이블 풀스캔하게됨. </p>
<pre><code class="language-sql">SELECT * FROM product WHERE name LIKE &#39;%샴푸%&#39;;</code></pre>
<h3 id="5-과도한-트랜잭션-범위">5. 과도한 트랜잭션 범위</h3>
<p>트랜잭션을 오래 유지하면 락이 길게 걸려 다른 쿼리가 대기하게 된다. 
: 대기 시간 증가, 데드락 발생 가능성 증가</p>
<pre><code class="language-java">@Transactional
public void process() {
    // 여기서 외부 API 호출하면 트랜잭션이 길어짐
}
</code></pre>
<h3 id="6-데이터-양이-너무-많아진-테이블핫-테이블">6. 데이터 양이 너무 많아진 테이블(핫 테이블)</h3>
<p>파티셔닝 없이 수천만 건 이상 쌓인 단일 테이블은 쿼리 성능이 급격히 떨어짐. 
: 조회, 정렬 인덱스 유지 비용 증가 
👉 테이블 파티셔닝, 기간별 분할 테이블 운영 으로 해결 </p>
<h3 id="7-db-connection-pool-부족">7. DB Connection pool 부족</h3>
<p>DB 커넥션 수가 부족하면 애플리케이션이 DB 커넥션을 받기 위해 대기하게 됨
: 전체 응답 지연, 타임아웃</p>
<hr>
<h2 id="⭐-쿼리-최적화-방법">⭐ 쿼리 최적화 방법</h2>
<h3 id="1-불필요한-select--제거">1. 불필요한 SELECT * 제거</h3>
<p><code>SELECT *</code>는 모든 컬럼을 불러오기 때문에 I/O비용이 커지고, 인덱스만으로 처리할 수 없는 경우가 많다. </p>
<p>SELECT * 보다는 필요한 컬럼, 전체 조회가 필요할 때는 모든 컬럼을 직접 적는 방법으로 최적화 할 수 있다. </p>
<h3 id="2-적절한-인덱스-활용">2. 적절한 인덱스 활용</h3>
<ul>
<li>단일 인덱스 : WHERE, JOIN, ORDER BY 등에 자주 사용되는 컬럼에 생성 </li>
<li>복합 인덱스 : 자주 같이 검색되는 컬럼은 <strong>순서를 고려</strong>해 복합 인덱스로. </li>
</ul>
<h3 id="3-인덱스가-잘-타는-조건-사용">3. 인덱스가 잘 타는 조건 사용</h3>
<ul>
<li><p><code>=</code>, 범위 조건(<code>&lt;</code>,<code>&gt;</code>,<code>BETWEEN</code>)은 인덱스를 잘 탄다. </p>
<ul>
<li>cf) not 조건인 <code>&lt;&gt;</code> 보다 &#39;<code>=</code>가 인덱스를 잘 탄다. <pre><code> `NOT IN` 보다 `IN`이 인덱스를 잘 탄다. </code></pre></li>
</ul>
</li>
<li><p><code>%</code>로 <strong>시작</strong>하는 LIKE는 인덱스 사용이 불가하다. 
```sql </p>
</li>
<li><ul>
<li>✅ 인덱스 사용 가능
SELECT * FROM users WHERE name LIKE &#39;홍%&#39;;</li>
</ul>
</li>
</ul>
<p>-- ❌ 인덱스 사용 불가
SELECT * FROM users WHERE name LIKE &#39;%홍&#39;;</p>
<pre><code>
### 4. 서브 쿼리보다 JOIN을 고려하기. 
서브쿼리는 상황에 따라 느릴 수 있다. 
서브쿼리는 루프마다 실행되기 때문에 성능이 저하될 수 있음을 염두해두자. 

### 5. JOIN 순서, 필터 순서 고려 
* 레코드 수가 적은 테이블을 먼저 JOIN하도록 쿼리를 작성하면 DB 옵티마이저가 효율적으로 실행 계획을 세운다. 
* WHERE 조건도 선 필터링 가능한 조건부터 작성한다. 

✅ 가끔 레코드 적은것 부터 순서대로 작성하더라도 옵티마이저가 판단하여 조인 순서를 잘못 설정하는 경우도있다. 
각 DBMS의 강제 순서 조건을 부여하여 조인 할 수 있다. 
내가 사용하는 MS-SQL은 `FORCE ORDER`라는 조건을 붙여주면 된다. 

✅ 요즘 옵티마이저는 순서대로 굳이 적지 않아도 알아서 레코드 개수를 판단하여 하는 경우도 있다고 하니 참고하자. 

### 6. LIMIT, OFFSET 효율적으로 사용하기 
* `OFFSET`이 클 수록 성능이 나빠진다 (건너뛴 row도 모두 읽기 때문에) 
* 커서 기반 페이지네이션 또는 `WHERE last_id &lt;? LIMIT N`방식을 추천한다. 

### 7. GROUP BY, DISTINCT 최소화 
* 중복 제거나 집계가 필요하지 않다면 제거한다. 
* 반드시 필요한 경우엔 정렬이 포함된 DISTINCT, 다중 GROUP BY는 피하는게 좋다. 

### 8. 함수 사용 최소화 
* WHERE 조건절에서 컬럼에 함수 사용 시 인덱스 사용이 불가하다. 

```sql
-- ❌ 인덱스 못 탐
WHERE DATE(created_at) = &#39;2025-01-01&#39;;

-- ✅ 인덱스 탐
WHERE created_at &gt;= &#39;2025-01-01&#39; AND created_at &lt; &#39;2025-01-02&#39;;</code></pre><p>✅ **좌변(컬럼) 은 건드리지 않는다. </p>
<h3 id="9-실행-계획explain확인">9. 실행 계획(EXPLAIN)확인</h3>
<ul>
<li>쿼리 튜닝의 시작과 끝은 <strong>실행 계획</strong>을 분석하는 일. </li>
<li>주요 확인 포인트  <ul>
<li>type: ALL(풀스캔), index, ref, range, const</li>
<li>rows: 얼마나 읽는가?</li>
<li>Extra: Using filesort, temporary 등 주의</li>
</ul>
</li>
</ul>
<h3 id="10-정규화와-반정규화의-균형">10. 정규화와 반정규화의 균형</h3>
<p><strong>과도한 정규화는 JOIN 과다로 이어지고
과도한 반정규화는 데이터 중복과 무결성 문제를 유발</strong>할 수 있다. </p>
<p><em>쿼리 성능*과 *데이터 정합성</em> 사이에서 적절한 트레이드오프가 필요함</p>
<hr>
<h2 id="📍-쿼리-최적화-적용-순서">📍 쿼리 최적화 적용 순서</h2>
<blockquote>
<ol>
<li>쿼리 실행 계획 확인 (EXPLAIN, ANALYZE)</li>
<li>느린 쿼리 로그 분석 (MySQL: slow_query_log)</li>
<li>인덱스 추가/삭제 실험</li>
<li>쿼리 리팩토링 (JOIN, WHERE 조건 최적화)</li>
<li>데이터 모델 개선 (필요시 테이블 분할/파티셔닝 등)</li>
<li>장기적으로는 캐싱(Memcached, Redis) 도입 고려</li>
</ol>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[개발자끼리의 커뮤니티는 중요할까? ]]></title>
            <link>https://velog.io/@dev_nana/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%81%BC%EB%A6%AC%EC%9D%98-%EC%BB%A4%EB%AE%A4%EB%8B%88%ED%8B%B0%EB%8A%94-%EC%A4%91%EC%9A%94%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@dev_nana/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%81%BC%EB%A6%AC%EC%9D%98-%EC%BB%A4%EB%AE%A4%EB%8B%88%ED%8B%B0%EB%8A%94-%EC%A4%91%EC%9A%94%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Fri, 04 Apr 2025 14:15:43 GMT</pubDate>
            <description><![CDATA[<p>지난번 <span href='https://velog.io/@dev_nana/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%8A%94-%EB%81%8A%EC%9E%84%EC%97%86%EC%9D%B4-%EA%B3%B5%EB%B6%80%ED%95%B4%EC%95%BC%ED%95%9C%EB%8B%A4'>개발자는 끊임없이 공부해야한다</span> 이라는 포스팅이 반응이 좋았다. </p>
<p>거기서 잠깐 <em>개발 커뮤니티</em>에 대한 종류를 소개한 적이 있는데, 이번엔 개발자에게 커뮤니티가 중요할까? 라는 주제로 말해보려한다. </p>
<hr>
<h2 id="개발자에게-커뮤니티가-중요할까">개발자에게 커뮤니티가 중요할까?</h2>
<p>정답은 
<img src="https://velog.velcdn.com/images/dev_nana/post/39b36b01-7c85-4fdc-ba18-00d1e1a0674d/image.png" alt=""></p>
<p>개발 공부를 시작한 초반에는 이런 생각을 많이 했다. </p>
<ul>
<li>&#39;굳이 커뮤니티에 글 쓰고, 질문하고, 소통해야하나?
자기 코드만 잘 짜면 되는거 아닌가?&#39;*</li>
</ul>
<p>그런데 개발자로서 시간이 쌓일수록, 그리고 내 코드를 돌아볼수록 
<strong>혼자만의 노력으로 성장하는 데 한계가 있다</strong>는 걸 깨닫게 됐다. </p>
<p>그래서 개발자로 살아가며 내가 느낀 ⭐️커뮤니티의 중요성⭐️과 끝에는 커뮤니티 몇가지를 소개해 주고자 한다. </p>
<hr>
<h2 id="🚀-왜-개발자에게-커뮤니티가-중요할까">🚀 왜 개발자에게 커뮤니티가 중요할까?</h2>
<h3 id="1-정보의-흐름을-가장-빠르게-접할-수-있다">1. 정보의 흐름을 가장 빠르게 접할 수 있다.</h3>
<ul>
<li><p>기술 트렌드는 정말 빠르게 변한다. 
  새로운 라이브러리, 새로운 기술, 프레임워크, best practice들이 매일같이 쏟아진다. </p>
</li>
<li><p>이런 정보를 <strong>공식 문서나 구글 검색</strong>으로만 따라가면 뒤쳐지기 쉽다. 
  하지만 커뮤니티에서는 기술 트렌드, 라이브러리 업데이트, 실무에서 맞닥뜨리는 문제들 등 <strong>공식 문서보다 빠르게 실 사용자들의 경험과 사례</strong>가 커뮤니티에 올라온다.</p>
</li>
<li><p>예를 들어 AI의 <code>MCP(Model Context Protocol)</code>과 같은 최신 기술에 대한 설명이나 실제 사용사례 등의 따끈따끈한 정보는 뉴스나 기사를 통해 접하는 것 보다 기술 커뮤니티에서 접하는게 좀 더 깊이있다. </p>
<blockquote>
<p>나 혼자 찾아다니는 것보다 훨씬 빠르고, 현실적인 정보들이 커뮤니티에 있다. </p>
</blockquote>
</li>
</ul>
<h3 id="2-내가-겪는-문제가-나만의-문제가-아님을-깨닫게-해준다">2. 내가 겪는 문제가 나만의 문제가 아님을 깨닫게 해준다.</h3>
<ul>
<li>개발하면서 &#39;남들은 몇분만에 해결할 수 있는 문제를 나 혼자 몇 시간을 끙끙대는건가? 나 바본가?&#39; 이런 생각을 많이 하게되는데, 커뮤니티에서는 나만 이런 고민을 하는 것이 아니란걸 느낄 수 있다. </li>
<li>이미 누군가 똑같이 삽질을 겪었고, 해결 방법까지 올라와 있는 경우가 대부분이다. <blockquote>
<p>개발자의 고충은 대부분 &#39;나만의 문제&#39;가 아니다. </p>
</blockquote>
</li>
</ul>
<h3 id="3-성장-동기부여">3. 성장 동기부여</h3>
<p>커뮤니티에서는 다양한 연차의 개발자들이 모이는 장이다. 
내 또래 개발자, 나보다 경력이 많은 개발자, 신입 개발자까지
각자의 고민과 성장기를 공유한다.</p>
<p>누군가는 사이드 프로젝트를 시작했고
누군가는 오픈소스 기여를 하고 있었고
누군가는 매일 기술 블로그에 글을 쓴다.</p>
<p>그 모습을 보고
&quot;나도 해야겠다&quot;
&quot;저건 나도 할 수 있을 것 같은데?&quot;
라는 자극을 받는 순간이 많았다.</p>
<p>개발자로 오래 버티기 위해서,
같이 달리는 사람들이 있다는 걸 체감하는 건 정말 중요하다.</p>
<blockquote>
<p>혼자 공부할 땐 쉽게 지치지만, 커뮤니티에서는 <strong>서로 응원하고 경쟁하며 성장</strong>할 수 있다.  </p>
</blockquote>
<h3 id="4-네트워킹--기회">4. 네트워킹 &amp; 기회</h3>
<ul>
<li>커뮤니티에서 <strong>좋은 팀원, 동료, 멘토, 채용 기회</strong>가 생길 수 있다. </li>
<li>비슷한 스택, 관심사를 가진 개발자들과 연결되어있으면 함께 사이드프로젝트 하기도 쉽고, 커리어에 긍정적인 영향을 준다. </li>
</ul>
<h3 id="5-배운-것을-공유하고-정리하는-습관">5. 배운 것을 공유하고 정리하는 습관</h3>
<ul>
<li>질문/답변, 글쓰기, 발표 같은 활동을 통해 내가 알고 있던 지식도 다시 정리하게 되고, 정확하게 이해하지 못했던 부분을 발견할 수 있다. (역시 지식은 가르쳐봐야 정말 내 것이 되는 것 같다.)</li>
</ul>
<hr>
<h2 id="❌-장점만-있는-것은-아니다">❌ 장점만 있는 것은 아니다.</h2>
<p>커뮤니티가 개발자에게 좋은 자극이 되긴 하지만
너무 의존하거나, 비교의 늪에 빠지는 것은 위험하다.</p>
<p>다른 사람들의 기술 스택, 속도, 커리어에 휘둘리기 시작하면
내 페이스를 잃어버릴 수도 있다.</p>
<p>커뮤니티는 어디까지나 도구고
주인공은 결국 나 자신이라는 걸 잊지 말자.</p>
<h2 id="커뮤니티-추천-사용법">커뮤니티 추천 사용법</h2>
<ul>
<li><p>주기적으로 둘러보기: 하루 5~10분, 커뮤니티 순회하면서 이슈/글 읽기</p>
</li>
<li><p>질문은 무조건 남기기: 부끄럽더라도, 질문을 남기는 습관이 실력을 키움</p>
</li>
<li><p>받기만 하지 말고, 짧게라도 답변/공유하기: 작은 글도 누군가에겐 큰 도움이 됨</p>
</li>
<li><p>좋은 글은 북마크 or Notion 정리: 나중에 필요할 때 찾기 좋음</p>
</li>
</ul>
<hr>
<h2 id="마무리">마무리</h2>
<p>나만 겪고 있을 것 같던 이 길도 누군가는 다 걸어온 길이었다. 
대학 동기들도 다 개발자와는 먼길을 갔고 회사사람들에게는 털어 놓을 수 없는 나만의 고충이 있었을 때 커뮤니티가 많은 도움이 되었다. </p>
<p>그리고 시간이 지나, 
** 내 경험이 또 다른 누군가에게 도움이 될 때 ** 개발자로서 가장 뿌듯한 순간이었다. 
지금은 얕은 경험만 공유하고 있지만 언젠간 많은 사람들에게 도움을 줄 수 있는 깊이 있는 개발자가 되는게 목표다. </p>
<hr>
<h2 id="커뮤니티-모음">커뮤니티 모음</h2>
<h2 id="🔗-내가-자주-찾는-개발자-커뮤니티-모음">🔗 내가 자주 찾는 개발자 커뮤니티 모음</h2>
<h3 id="📌-국내-커뮤니티">📌 국내 커뮤니티</h3>
<table>
<thead>
<tr>
<th>이름</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><a href="https://devtalk.kakao.com/">DevTalk (디프만, 코드스테이츠 등)</a></td>
<td>다양한 부트캠프/스터디 기반 커뮤니티. 스타트업 개발자들이 활발하게 활동 중</td>
</tr>
<tr>
<td><a href="https://okky.kr/">OKKY</a></td>
<td>실무 개발자들의 기술 고민, 이직 정보, 연봉 토론 등 실전형 커뮤니티</td>
</tr>
<tr>
<td><a href="https://velog.io/">velog.io</a></td>
<td>개발자들의 기술 블로그 플랫폼. 댓글을 통한 소통도 활발함</td>
</tr>
<tr>
<td><a href="https://fastcampus.co.kr/">FastCampus 커뮤니티</a></td>
<td>수강생 커뮤니티지만 종종 비수강생도 기술 Q&amp;A 참여 가능</td>
</tr>
<tr>
<td><a href="https://www.inflearn.com/community">인프런 커뮤니티</a></td>
<td>학습자들끼리 기술 관련 질문/답변 주고받는 공간</td>
</tr>
</tbody></table>
<h3 id="🌍-해외-커뮤니티">🌍 해외 커뮤니티</h3>
<table>
<thead>
<tr>
<th>이름</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><a href="https://stackoverflow.com/">Stack Overflow</a></td>
<td>전 세계 개발자들이 모인 기술 Q&amp;A의 본진</td>
</tr>
<tr>
<td><a href="https://www.reddit.com/r/learnprogramming/">Reddit - r/learnprogramming</a></td>
<td>공부 시작한 개발자들이 모여 질문/정보 나누는 공간</td>
</tr>
<tr>
<td><a href="https://dev.to/">Dev.to</a></td>
<td>전 세계 개발자들이 직접 글을 쓰고 공유하는 기술 커뮤니티</td>
</tr>
<tr>
<td><a href="https://news.ycombinator.com/">Hacker News</a></td>
<td>스타트업, 기술 트렌드에 민감한 개발자들의 소식 창고</td>
</tr>
</tbody></table>
<h3 id="🛠️-실시간-소통형-커뮤니티-디스코드슬랙-등">🛠️ 실시간 소통형 커뮤니티 (디스코드/슬랙 등)</h3>
<table>
<thead>
<tr>
<th>이름</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><a href="https://discord.gg/devkorea">한국 디스코드 개발자 서버 (Korea Dev Discord)</a></td>
<td>한국 개발자들이 디스코드 기반으로 실시간 토론/질문</td>
</tr>
<tr>
<td><a href="https://twitter.com/search?q=dev%20OR%20code%20OR%20programming&amp;src=typed_query">Tech Twitter</a></td>
<td>해시태그 중심으로 전 세계 개발자들과 빠르게 트렌드 교류</td>
</tr>
<tr>
<td>[Slack 커뮤니티 (ex. Java Korea)]</td>
<td>언어별, 기술별 Slack 그룹도 존재함 (초대 필요한 경우 있음)</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[개발자는 끊임없이 공부해야한다. ]]></title>
            <link>https://velog.io/@dev_nana/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%8A%94-%EB%81%8A%EC%9E%84%EC%97%86%EC%9D%B4-%EA%B3%B5%EB%B6%80%ED%95%B4%EC%95%BC%ED%95%9C%EB%8B%A4</link>
            <guid>https://velog.io/@dev_nana/%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%8A%94-%EB%81%8A%EC%9E%84%EC%97%86%EC%9D%B4-%EA%B3%B5%EB%B6%80%ED%95%B4%EC%95%BC%ED%95%9C%EB%8B%A4</guid>
            <pubDate>Sat, 29 Mar 2025 14:00:47 GMT</pubDate>
            <description><![CDATA[<h2 id="아-하기-싫어">아 하기 싫어.</h2>
<p>그치만 개발자는 끊임없이 공부해야한다. </p>
<p>나는 이 말을 이직한 후에야 느끼기 시작했다. 
*&quot;왜 공부해야해? 어차피 똑같은 일 계속할거고 어딜가나 똑같이 일 할거 아닌가?&quot;*
이 생각이 든다면 당장 키보드에 머리를 박아라! (그렇다고 진짜 박진말구.. 살짝쿵 ㅠ) </p>
<p>후회하는게 있다면 *<em>&#39;왜 난 좀 더 빨리 이 공부하는 재미를 알지 못했을까.&#39; *</em>였다. 
이 글을 읽는 당신이 그동안 공부를 하지 않았다면 지금도 늦지 않았으니 공부를 시작해보자.</p>
<p>어떤걸 해야할지 모르겠다면, <em>공부에 흥미를 느끼게 된 내 이야기를 참고삼아 보길바란다.</em> </p>
<hr>
<h2 id="내가-공부를-하다니">내가 공부를 하다니!</h2>
<h3 id="💡-발단">💡 발단</h3>
<p>일단 내 성격을 얘기하자면 찍먹을 다양하게 해보다가 취향 맞는걸 찾으면 무섭게 오래 파고드는 성격이다. </p>
<br/>

<p>*&quot;일단 해보자&quot;*
<br/></p>
<p>일단 해보고 안되면 말고. 
분명 장점도 존재하고 단점도 존재한다. 
허나 개발자라는 직업에는 이 성격이 굉장히 장점이라고 생각된다. 
내가 맡은 일이 잘 풀리지 않더라도 멘탈이 흔들리긴 해도 박살나진않는다. 그리고 회복력이 꽤나 빠르다. </p>
<p>이렇다 보니 <strong>어쩔 수 없는 환경</strong>에 나를 던져왔고, 대체로 좋은 결과를 가져왔다.  </p>
<p>누군가가 강제성을 부여하게 되면 어떻게든 책임감을 가지고 하게되니 프로젝트가 있을 때에도 손을 들어 나서는 편이어서 이렇게 일을 닥치는 대로 많이 하다보면 성장할거라고 생각했다. </p>
<p>그런데 그냥 일을 받아서 처리만 하다보니 일정에 쫓겨 늘 하던 방식으로만 진행하게 되었고 손에 익어 일은 빨리 처리가 되었으나 성장은 더딘 느낌이었다. </p>
<p>그래서 이번에도 나를 &#39;어쩔 수 없는 환경&#39;에 던져보기로했다. </p>
<h3 id="💡-전개">💡 전개</h3>
<p>우선 자바 개발자로의 전향을 원했으나 인강으로는 강제성이 부족하다고 생각이 들었다. 
그래서 <strong>항해플러스</strong>에 💵의지를 돈 주고 사기💵로 했다. </p>
<p>10주간 말 그대로 반 송장처럼 살았고 더이상 학업을 미룰 수 없다고 생각이 들었다. 
나는 연차만 쌓인 물경력 개발자였고, 실무에서 1-2년 동안 자바를 다룬 동기들과 실력차이가 월등히 났다. </p>
<p>개발 실력 뿐만 아니라 기본 개발 지식이 달랐다. </p>
<p>처음 보는 툴, 생소한 용어, 낯선 컨퍼런스. 그야말로 우물안 개구리에서 우물 밖으로 고개를 빼꼼 내밀게 되는 순간이었다. </p>
<p>괜찮다 다독였다. 남은 날 중에 오늘이 가장 젊은 날일 테니. 
한 순간이라도 더 빠른 시일내에 시작한 본인을 칭찬해주기로했다. </p>
<p>밤을 새어가며 과제를 완성하면서 단기간에 많은 성장을 이뤄낸 내가 뿌듯했다. 
그리고 동기나 코치분들께 현직에서 얻을 수 있는 꿀팁도 많이 얻어냈다고 생각한다. 
그걸 체득하는건 본인의 노력 여하에 달려있겠지만. </p>
<h3 id="💡-위기">💡 위기</h3>
<p>그 부트캠프가 끝나면 뭐든지 다 잘 할수있는 개발자가 될 것 같지만 실은 그렇지 않다. 
말 그대로 &quot;방향성을 제시&quot;해 주는 것이고 이후에는 끊임 없는 노력이 필요하다. 
나에게 공부할 기준을 세워주었다는 것, 개발 공부가 재밌어지게 만들어 줬다는 것이 항해의 가장 큰 장점이라고 생각한다. </p>
<p>공부 해야할 항목이 너무 많다. 
내가 해왔던 노력 &amp; 앞으로도 계속 해야할 것들은 좀 더 아래에서 적겠다. </p>
<hr>
<h2 id="🔻-공부하는-방법">🔻 공부하는 방법</h2>
<p>공부하는 방법은 너무나도 많다. 
많이도 아니고 정말 잘 알려진 몇가지를 소개하고자 한다. </p>
<p>너무 한 곳의 의견에 치우치지 않게 자신에게 잘 맞는 방법으로 섞어서 공부하면 좋을 것 같다.</p>
<h3 id="📖-개발-서적">📖 개발 서적</h3>
<h4 id="스프링">스프링</h4>
<ul>
<li>자바 웹 프로그래밍 넥스트 스텝</li>
<li>토비의 스프링 1권
  <a href="https://github.com/tobyilee/tobyspringin5/tree/main">https://github.com/tobyilee/tobyspringin5/tree/main</a><h4 id="db">DB</h4>
</li>
<li>친절한 SQL 튜닝</li>
<li>Real MySQL 8.0</li>
</ul>
<h4 id="아키텍처">아키텍처</h4>
<ul>
<li><p>클린 아키텍처</p>
</li>
<li><p>만들면서 배우는 클린 아키텍처(헥사고날)
  <a href="https://github.com/thombergs/buckpal">https://github.com/thombergs/buckpal</a></p>
<h4 id="시스템-디자인">시스템 디자인</h4>
</li>
<li><p>가상면접 사례로 배우는 대규모 시스템 설계1,2</p>
<h3 id="📺-유튜브">📺 유튜브</h3>
</li>
<li><p><a href='https://www.youtube.com/@woowatech'>우아한 테크</a> : 우아한 형제들의 컨퍼런스 <strong>우아콘</strong>에서는 배달의 민족에서 실제로 쓰이는 기술적인 부분에 대해, <strong>우아한 코스 테코톡</strong>에서는 개발에 필요한 개념들을 주니어 개발자들의 눈높이에서 잘 설명하고 있다. 
출근 준비를 하며 가볍게 듣기에 좋아 라디오처럼 듣고있다.</p>
</li>
<li><p><a href='https://www.youtube.com/@devbadak'>개발바닥</a>: 2명의 시니어의 개발 트렌드 관련 잡담, 토론 + 초대손님 컨텐츠</p>
</li>
<li><p><a href='https://www.youtube.com/@AWSKorea'>Amazon WebService Korea</a> : 한국 AWS 세미나 </p>
</li>
</ul>
<ul>
<li><a href='https://www.youtube.com/@SKplanetTacademy'>SKplanet Tacademy</a> : SK 강의 및 세미나 영상 (SK는 자체 개발자 커뮤니티 데보션을 운영함. 신입에게 양질 컨텐츠 많음)</li>
</ul>
<h3 id="🎞️-인터넷-강의무료유료">🎞️ 인터넷 강의(무료/유료)</h3>
<p><a href='https://www.inflearn.com/users/74366/@yh'>인프런 김영한</a>님 강의 : spring계의 GOAT / 커리큘럼을 따라가다보면 자연스레 기본개념 + 실무에서 사용되는 방법 &amp; 쓰면 안되는 기술 등등을 함께 익힐 수 있다. </p>
<h3 id="👯-개발-커뮤니티">👯 개발 커뮤니티</h3>
<h4 id="국내">국내</h4>
<ul>
<li><p><a href='https://okky.kr/community/life'>OKKY</a> </p>
</li>
<li><p><a href='https://careerly.co.kr/'>커리어리</a> </p>
</li>
<li><p><a href='https://yozm.wishket.com/magazine/'>요즘 IT</a> : 구독을 해 놓으면 원하는 시간대에 IT 인사이트가 담긴 아티클을 전송해준다. </p>
</li>
<li><p><a href='https://www.inflearn.com/community/studies'>Inflearn</a> : 개발 스터디 구하기 좋다. </p>
</li>
</ul>
<h4 id="해외">해외</h4>
<ul>
<li><p><a href='https://stackoverflow.com/questions'>Stack Overflow</a></p>
</li>
<li><p><a href='https://dev.to/'>Dev Community</a></p>
</li>
<li><p><a href='https://medium.com/'>medium</a></p>
</li>
</ul>
<h3 id="🍀-부트캠프">🍀 부트캠프</h3>
<p>혼자서는 도저히 공부를 못하겠다! 하면 (나처럼) <em>돈주고 의지를 사는 방법</em>도 있다..ㅎ 
대부분은 내일배움카드를 통한 구직자 전용 부트캠프가 있지만 환승이직을 목표로 하는 나는 재직자 전형을 알아볼 수 밖에 없었다 ㅠㅠ </p>
<ul>
<li>항해 플러스 : 10주간 백엔드 / 프론트엔드 / AI개발자 로서의 역량 강화를 목적으로 하는 부트캠프. 재직자를 타겟으로 하여 힘들지만 감당 가능한 수준의 미션을 매주 수행한다 ㅠㅠㅠ.. 
(관심있으시다면 추천인 코드 <span style='color : red'>G2ZAL7</span> 를 넣어주시면 등록금 할인혜택이 주어져요..!!!)</li>
<li><a href='https://f-lab.kr/?utm_source=gdn&utm_medium=da&utm_campaign=performancemax_4&utm_content=max_20250324&gad_source=1&gclid=Cj0KCQjwtJ6_BhDWARIsAGanmKf7jyOoX8SBmLWZ7Cl9Qh8jWa87RpzNgHUHVwMTmZcQ57salxYiVaMaAjgHEALw_wcB'>F-Lab</a> : 4개월 동안 멘토링을 1:1로 들을 수 있다는 장점..! 그만큼 가격은 사악하다 ㅎㅎ; </li>
</ul>
<hr>
<h3 id="💡-절정">💡 절정</h3>
<p>아직 진행중이다. 
누군가는 몇십년간 꾸준히 노력해서 얻어낸 결과일텐데, 고작 공부 시작한지 몇달도 채 되지 않은 내가 그 많은 지식을 습득할리 만무하다. </p>
<p>그저 내가 할 수 있는건 꾸준하게 하는 것. 
인생이란 마라톤에서 포기하고 싶은 순간이 수도없이 오겠지만 그 또한 더 먼 곳으로 나아가기위한 과정 중 하나라는 것. </p>
<hr>
<h2 id="끝으로">끝으로</h2>
<p>모르니까 공부하는거고 모르니까 물어보는거다. 
다 알면 내가 이미 실리콘밸리든 어디든지 가 있겠지 ㅎㅎ 
꾸준히 하는 공부는 언젠간 의도치 않은 곳에서 빛을 발할 순간이 온다고 믿는다. </p>
<p>이 글을 쓰면서 다시한번 공부에 대한 의지를 다잡게 되었다. 
아직 미숙하지만 미숙하기에 성장할 가능성도 더 많은 것이라 생각하며 이 글을 마친다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] 트랜잭션(@Transactional) 막 쓰면 성능 저하를 초래할 수 있다?]]></title>
            <link>https://velog.io/@dev_nana/Spring-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98Transactional-%EB%A7%89-%EC%93%B0%EB%A9%B4-%EC%84%B1%EB%8A%A5-%EC%A0%80%ED%95%98%EB%A5%BC-%EC%B4%88%EB%9E%98%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8B%A4</link>
            <guid>https://velog.io/@dev_nana/Spring-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98Transactional-%EB%A7%89-%EC%93%B0%EB%A9%B4-%EC%84%B1%EB%8A%A5-%EC%A0%80%ED%95%98%EB%A5%BC-%EC%B4%88%EB%9E%98%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8B%A4</guid>
            <pubDate>Sat, 22 Mar 2025 16:59:48 GMT</pubDate>
            <description><![CDATA[<h2 id="o-들어가기에-앞서">o. 들어가기에 앞서</h2>
<p>현재 일하는 곳에서는 프로시저를 이용한 처리를 좀 더 자주 사용한다. 
일괄 처리를 할 때 자주 사용하는게 트랜잭션인데, 사실 공부를 하지 않을 때에는 트랜잭션의 중요성을 알지 못했다. </p>
<p>트랜잭션이 주는 이점보다는 트랜잭션으로 인해 다른 처리가 락이 걸려버리는 상황이 자주 있어서 &#39;안쓰느니만 못한 것&#39;이라고 인식하던 때가 있었기 때문이다. </p>
<p><em>하지만 트랜잭션은 잘못없어!!!!</em> 
<span style='color:gray'>잘못한건 트랜잭션을 올바르지 않은 위치에 건 나지.😭</span> </p>
<p>모든 기능이 잘 쓰면 약, 못 쓰면 독이다. 
Spring에서 제공하는 <code>@Transactional</code>은 다양한 옵션을 제공하고 있고, 옵션에 따라 성능 저하까지 초래할 수 있다고 하는데!!?? </p>
<p>지피지기면 백전백승이라고 <strong>진짜 제대로 알아보자!</strong></p>
<hr>
<h2 id="1-트랜잭션이란">1. 트랜잭션이란?</h2>
<p>트랜잭션(Transaction)은 프로그래밍, 데이터베이스, 가산자산 등에서 사용되는 용어로, 
<strong>연속적인 작업의 묶음</strong>을 뜻한다. </p>
<p>Spring에는 <code>@Transactional</code>(선언적 트랜잭션)이라는 어노테이션을 제공하는데, 
이 녀석의 장점 중 하나가 <strong>여러 트랜잭션을 묶어서 커다란 하나의 트랜잭션 경계를 만들 수 있다</strong>는 점이다. </p>
<p>트랜잭션을 시작하는 방법은 하나이지만, 끝나는 방법은 <code>rollback</code> / <code>commit</code> 두 개가 존재한다. 
트랜잭션을 시작하고 끝내는 작업을 설정하는 걸 <em>트랜잭션 경계설정</em> 이라고 하고, 트랜잭션 경계 내에서 논리적으로 묶이길 원하는 로직들이 실행되게 된다. </p>
<h2 id="2-트랜잭션-전파속성propagation-종류">2. 트랜잭션 전파속성(Propagation) 종류</h2>
<blockquote>
<p>트랜잭션 전파 속성? 
👉🏻 트랜잭션이 진행중일 때 추가 트랜잭션을 어떻게 할지 결정하는 것</p>
</blockquote>
<p><code>@Transactional(propagation = Propagation.전파타입)</code></p>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/9f83f44d-ec75-4fb4-9aef-182801a675fc/image.png" alt="">
<img src="https://velog.velcdn.com/images/dev_nana/post/c631fca0-fdd4-4760-b316-5b52680c926b/image.png" alt=""></p>
<p>스프링에서 외부 트랜잭션과 내부 트랜잭션을 묶어준다. =&gt; &quot;내부 트랜잭션이 외부 트랜잭션에 참여한다.&quot;라고 표현한다.</p>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/40b2e53b-be1a-4369-b209-1dc907f9bbee/image.png" alt="">
물리 트랜잭션 : 실제 데이터베이스에 적용되는 트랜잭션 
<strong>실제 커넥션</strong>을 통해 트랜잭션을 시작하고 종료(커밋, 롤백)하는 단위 </p>
<p>논리 트랜잭션도 커밋과 롤백을 요청할 수는 있지만 실제 데이터베이스에 적용되지는 않는다. </p>
<p>✨모든 논리 트랜잭션이 커밋돼야 물리 트랜잭션이 커밋된다. 
= 하나의 논리 트랜잭션이라도 롤백되면 물리 트랜잭션은 롤백된다. </p>
<p><strong>신규 트랜잭션만이 물리 트랜잭션을 종료(커밋, 롤백)할 수 있다.</strong></p>
<pre><code class="language-java">public interface TransactionDefinition {

    int PROPAGATION_REQUIRED = 0;

    int PROPAGATION_SUPPORTS = 1;

    int PROPAGATION_MANDATORY = 2;

    int PROPAGATION_REQUIRES_NEW = 3;

    int PROPAGATION_NOT_SUPPORTED = 4;

    int PROPAGATION_NEVER = 5;

    int PROPAGATION_NESTED = 6;

    // ...
}</code></pre>
<blockquote>
<p>✚ @Transactional 어노테이션을 사용하는 경우 속성 값은 RuleBasedTransactionAttribute라는 객체로 변환되는데, 이는 TransactionDefinition의 서브타입이다. </p>
</blockquote>
<h3 id="2-1-required-default">2-1. REQUIRED (Default)</h3>
<ul>
<li>트랜잭션이 존재하는 경우 해당 트랜잭션을 사용하고, 트랜잭션이 없는 경우 트랜잭션을 생성한다. <h3 id="2-2-required_new">2-2. REQUIRED_NEW</h3>
</li>
<li>트랜잭션이 존재하는 경우 트랜잭션을 잠시 보류시키고, 신규 트랜잭션을 생성하여 사용한다. <h3 id="2-3-mandatory">2-3. MANDATORY</h3>
</li>
<li>트랜잭션이 반드시 있어야 한다. </li>
<li>트랜잭션이 없다면 예외가 발생한다. </li>
<li>만약 트랜잭션이 존재한다면 해당 트랜잭션을 사용한다. <h3 id="2-4-supports">2-4. SUPPORTS</h3>
</li>
<li>트랜잭션이 존재하는 경우 트랜잭션을 잠시 보류하고, 트랜잭션이 없는 상태로 처리한다. <h3 id="2-5-nested">2-5. NESTED</h3>
</li>
<li>트랜잭션이 있다면 SAVEPOINT를 남기고 중첩 트랜잭션을 시작한다. </li>
<li>만약 없는 경우에는 새로운 트랜잭션을 시작한다. </li>
<li>부모 트랜잭션 커밋, 롤백엔 영향을 받음 </li>
<li>자신의 커밋, 롤백은 부모 트랜잭션에 영향 못 줌 <h3 id="2-6-never">2-6. NEVER</h3>
</li>
<li>트랜잭션이 존재하는 경우 예외를 발생시키고, 트랜잭션이 없다면 생성하지 않는다. </li>
</ul>
<hr>
<h2 id="3-트랜잭션-적용-사례-장바구니-기능---피드백">3. 트랜잭션 적용 사례: 장바구니 기능 &amp;  피드백</h2>
<p>트랜잭션의 종류를 알아보았다면, 
이전에 작성했던 이커머스 프로젝트의 <strong>장바구니</strong>기능을 예시로 들어보자. </p>
<h3 id="3-1-시나리오">3-1. 시나리오</h3>
<ol>
<li>장바구니에 상품을 담을 때 동일한 상품이 담겨있는지 확인한다.</li>
<li>기존에 들어있는 상품인 경우 수량을 업데이트 해 준다. </li>
<li>새로운 상품인 경우 장바구니 테이블에 추가해준다. </li>
<li>품절된 상품은 담을 수 없다. </li>
<li>구매 시 장바구니를 거쳐 구매 과정이 이루어진다. </li>
</ol>
<h3 id="3-2-예시-코드">3-2. 예시 코드</h3>
<pre><code class="language-java">@Transactional(propagation = Propagation.REQUIRES_NEW)
public CartResult addCartProducts(Long customerId, List&lt;CartInfo&gt; cartInfos) {
    List&lt;CartDetailResponse&gt; responseProduct = new ArrayList&lt;&gt;();

    try {
        // 1️⃣ Named Lock을 획득하여 같은 productId에 대한 동시 접근을 방지
        cartRepository.getLock(cartInfos.getFirst().productId().toString());

        // 2️⃣ 고객의 최신 장바구니 조회
        List&lt;Cart&gt; carts = getCartList(customerId);
        log.info(&quot;최신 카트 : &quot; + carts.getFirst().getProduct());

        for (CartInfo cr : cartInfos) {
            Optional&lt;Cart&gt; exists = carts.stream()
                    .filter(c -&gt; c.getProduct().getProductId().equals(cr.productId()))
                    .findFirst();

            log.info(&quot;exists ? : &quot; + exists.toString());

            if (exists.isPresent()) {
                // 3️⃣ 기존에 장바구니에 있는 경우 수량 추가
                Cart existingCart = exists.get();
                existingCart.addCartAmount(cr.amount());
                log.info(existingCart.toString());
                cartRepository.save(existingCart);
                responseProduct.add(setProductDetail(cr.productId(), existingCart.getAmount()));
            } else {
                // 4️⃣ 장바구니에 없는 상품이면 새롭게 추가
                addProductsToCart(customerId, cr.productId(), cr.amount());
                responseProduct.add(setProductDetail(cr.productId(), cr.amount()));
            }
        }
    } finally {
        // 5️⃣ Named Lock 해제 (중요: 반드시 finally에서 실행)
        cartRepository.releaseLock(cartInfos.getFirst().productId().toString());
    }

    return new CartResult(
            customerId,
            responseProduct,
            LocalDateTime.now()
    );
}
</code></pre>
<h3 id="3-3-코드-분석">3-3. 코드 분석</h3>
<p>1️⃣ Named Lock 사용 (cartRepository.getLock())</p>
<p>특정 productId 기준으로 Named Lock을 획득하여 동시에 동일한 상품이 중복 추가되는 것 방지
SQL에서 GET_LOCK(productId, 10); 같은 방식으로 처리될 가능성이 높음</p>
<p>2️⃣ REQUIRES_NEW 트랜잭션 사용</p>
<p>장바구니에 상품을 추가하는 로직이 <strong>기존 트랜잭션과 독립적으로 실행되어야 함</strong>
REQUIRES_NEW를 사용하면 부모 트랜잭션의 성공 여부와 관계없이 장바구니에 상품 추가 과정이 독립적으로 커밋되므로, Named Lock 해제 로직이 안전하게 실행된다.</p>
<p>3️⃣ finally 블록에서 Named Lock 해제 (cartRepository.releaseLock())</p>
<p>트랜잭션이 성공하든 실패하든 반드시 락을 해제해야 함</p>
<h3 id="3-4-requires_new-사용-이유">3-4. REQUIRES_NEW 사용 이유?</h3>
<p>만약 구매 프로세스에서 장바구니에 담기 &gt; 장바구니에 있는 항목에서 바로 구매로 이루어 진다고 해보자. </p>
<p>계속해서 트랜잭션이 구매 트랜잭션과 묶이게 된다면, 구매가 실패하면 장바구니에 담긴 상품도 롤백될 위험이 있다.</p>
<p>즉, 장바구니는 구매와는 독립적으로 관리되어야 하기 때문에 Propagation.REQUIRES_NEW 를 사용하여 기존 트랜잭션과 별도로 관리하도록 설계한 것이다. </p>
<h3 id="3-5-트랜잭션-전파-속성에-따른-장바구니-트랜잭션-처리-비교">3-5. 트랜잭션 전파 속성에 따른 장바구니 트랜잭션 처리 비교</h3>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/3e114400-1b01-4b4d-afd2-26a4f9acc737/image.png" alt=""></p>
<blockquote>
<p>시니어 코치의 피드백 :
처음 트랜잭션을 사용했을 때 디폴트 속성인 REQUIRED를 사용했다. 
하지만 장바구니와 구매는 독립적인 비즈니스 영역이므로 REQUIRED_NEW를 사용하는 것을 권고받고 수정하였다. </p>
</blockquote>
<h2 id="4-트랜잭션-격리수준isolation종류">4. 트랜잭션 격리수준(Isolation)종류</h2>
<p>여러 트랜잭션이 진행될 때에 트랜잭션의 작업 결과를 타 트랜잭션에게 어떻게 노출할지 결정.</p>
<h3 id="4-1-default">4-1. DEFAULT</h3>
<ul>
<li>사용하는 데이터 접근 기술, DB드라이버의 기본 설정</li>
<li>Oracle은 READ_COMMITED, Mysql은 REPEATABLE_READ를 기본 격리 수준으로 가진다. </li>
</ul>
<h3 id="4-2-격리-수준-종류">4-2. 격리 수준 종류</h3>
<p>READ_UNCOMMITTED &gt; READ_COMMITTED &gt; REPEATABLE_READ &gt; SERIALIZABLE 
격리 수준에 대한 내용은 <a href='https://velog.io/@dev_nana/동시성-이슈Concurrency-Issues의-제어-2-DB-Lock을-통한-제어-방법들'>이 포스트</a>를 확인하면 된다. </p>
<h2 id="5-기타-트랜잭션-옵션">5. 기타 트랜잭션 옵션</h2>
<h3 id="5-1-timeout">5-1. Timeout</h3>
<ul>
<li>트랜잭션을 수행하는 제한 시간을 설정. </li>
<li>기본 옵션에는 제한시간이 없음 <h3 id="5-2-readonly">5-2. readOnly</h3>
</li>
<li>트랜잭션 내에서 데이터를 조작하려는 시도를 막음. </li>
<li>데이터 접근 기술, 사용 DB에 따라 (힌트를 구현하지 않았다면) 적용 차이가 있음. <ul>
<li>구현이 되어있다면 트랜잭션 ID관련 설정에서의 오버헤드를 줄여 실제 읽기 행동 시 참여하는 데이터구조를 감소시켜 성능을 개선시킬 수 있다. </li>
</ul>
</li>
</ul>
<h3 id="5-3-rollback-for">5-3. rollback-for</h3>
<ul>
<li>기본적으로 RuntimeException시 롤백 </li>
<li>체크 예외지만 롤백 대상으로 삼고 싶다면 사용 </li>
</ul>
<h3 id="5-4-no-rollback-for">5-4. no-rollback-for</h3>
<ul>
<li>롤백 대상인 RuntimeException을 커밋 대상으로 지정. </li>
</ul>
<h2 id="6-마무리">6. 마무리</h2>
<p>Spring에서의 @Transational은 바르게 사용하면 강력한 도구지만, <strong>잘못 사용하면 불필요한 락과 성능 저하를 초래</strong>할 수 있다. 
트랜잭션 전파 속성을 잘 활용하면 <strong>성능 최적화와 데이터 무결성을 동시에 보장</strong>할 수 있다.</p>
<hr>
<p><a href='https://www.youtube.com/watch?v=b0s9RzKyHN0'>[10분 테코톡] 키아라의 스프링 트랜잭션 전파</a>
<a href='https://www.youtube.com/watch?v=cc4M-GS9DoY'>[10분 테코톡] 후니의 스프링 트랜잭션</a>
<a href='https://dev-ws.tistory.com/109'>물리 트랜잭션과 논리 트랜잭션, 그리고 트랜잭션 전파에 관하여</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TDD/방법론] 자네, TDD가 뭔지 아는가?]]></title>
            <link>https://velog.io/@dev_nana/%EC%9E%90%EB%84%A4-TDD%EA%B0%80-%EB%AD%94%EC%A7%80-%EC%95%84%EB%8A%94%EA%B0%80</link>
            <guid>https://velog.io/@dev_nana/%EC%9E%90%EB%84%A4-TDD%EA%B0%80-%EB%AD%94%EC%A7%80-%EC%95%84%EB%8A%94%EA%B0%80</guid>
            <pubDate>Sat, 15 Mar 2025 06:04:54 GMT</pubDate>
            <description><![CDATA[<h2 id="🙋🏻♀️tdd와의-첫-만남">🙋🏻‍♀️TDD와의 첫 만남.</h2>
<p>작년 겨울, 처음 만난 TDD는 나에게 정말 새로운 경험이었다. 
어쩌면 이 글을 읽는 누군가는 TDD를 이 글로 인해 처음 알게 될 수도 있을 것이다. </p>
<p><code>TDD / BDD / DDD</code>
*위 세가지에 관해 정리한 글은 <a href='https://velog.io/@dev_nana/TDD방법론-TDD-vs-BDD-vs-DDD'>여기</a>에 있다. *</p>
<p>개발자라면 어디선가 한번 쯤은 들어봤을 용어들. 
그치만 생각보다 테스트 코드를 도입하지 않은 회사가 많다. (일단 현재 회사) </p>
<p>여기저기서 이렇게 많이 떠들고 강조하는 걸 보니 중요한거같은데! 
&quot;당장 쓰든, 안 쓰든 <strong>지대넓얇(지적인 대화를 위한 넓고 얕은 지식)</strong>을 쌓아보자!&quot; 가 이 글의 취지이다. </p>
<p>이전에 썼던 개념들도 정리하고, TDD도입의 사례를 보며 &#39;TDD도입이 정말 좋은 선택인걸까?&#39; 에 대해 함께 고민하는 시간을 가졌으면 좋겠다. </p>
<h2 id="✅-tdd가-뭘까">✅ TDD가 뭘까?</h2>
<blockquote>
<p><strong>TDD(Test Driven Development)</strong> : <strong>테스트 주도 개발</strong> 
반복 테스트를 이용한 소프트웨어 방법론으로, 작은 단위의 테스트케이스를 작성하고 이를 통과하는 코드를 추가하는 단계를 반복하여 구현한다. 
개발 시작하기 전에 테스트 케이스를 먼저 작성하고, 이 테스트 케이스를 통과하는 코드를 작성하는 방법론이다.</p>
</blockquote>
<p>말 그대로 우리는 그동안 기능에 대한 구현 &gt; 테스트 코드 작성해서 잘 되는지 확인! 
이었다면 TDD는 
기능에 대해 뼈대를 만들고 테스트를 통과하면 실제 코드에 작성 &gt; 조금 더 작은 단위의 테스트코드 작성 &gt; 통과하면 실제 코드에 적용 의 과정을 반복하는 것이다. </p>
<h3 id="📌-tdd의-핵심-과정">📌 TDD의 핵심 과정</h3>
<ol>
<li><span style='color : red'>테스트 코드 작성</span> </li>
</ol>
<ul>
<li>요구사항을 기반으로 테스트를 먼저 작성한다. </li>
<li>잘 실패하는 코드를 만든다. </li>
</ul>
<ol start="2">
<li><span style='color : green'>실제 코드 작성 </span></li>
</ol>
<ul>
<li>테스트를 통과할 최소한의 코드를 작성한다. </li>
</ul>
<ol start="3">
<li>리팩토링</li>
</ol>
<ul>
<li>중복 제거 및 코드 개선</li>
<li>유지보수하기 쉽게 다듬는다. </li>
<li>성능 최적화가 가능하다. </li>
<li>리팩토링 후에도 기존 테스트가 통과해야한다. </li>
</ul>
<p>위 과정을 반복하며 <strong>코드 품질</strong>과 <strong>안정성</strong>을 보장하는 방식이다. </p>
<pre><code class="language-java">// 1️⃣ 실패하는 테스트 작성
@Test
void addNumbers_shouldReturnSum() {
    Calculator calculator = new Calculator();
    assertEquals(5, calculator.add(2, 3)); // ❌ Calculator 클래스가 없으므로 컴파일 에러
}

// 2️⃣ 최소한의 코드 작성 (클래스만 생성, 메서드 구현 X)
public class Calculator {}

// 3️⃣ 실패하는 테스트 (메서드가 없어서 컴파일 에러는 해결되지만 여전히 실패)
public class Calculator {
    public int add(int a, int b) { 
        return 0; // 임시로 0 반환 -&gt; 실패 유지
    }
}

// 4️⃣ 테스트를 통과하도록 최소한의 코드 작성
public class Calculator {
    public int add(int a, int b) {
        return a + b; // ✅ 이제 테스트 통과!
    }
}

</code></pre>
<h2 id="⭐️-tdd의-특징과-장점">⭐️ TDD의 특징과 장점</h2>
<ol>
<li><strong>버그 발견이 빠르다.</strong> </li>
</ol>
<ul>
<li>기능 구현하기 전에 테스트를 먼저 작성하기 때문에 버그를 사전에 방지할 수 있다. </li>
<li>예측하지 못한 오류를 줄일 수 있다. </li>
</ul>
<ol start="2">
<li><strong>설계 품질이 향상된다.(유지보수성 향상)</strong> </li>
</ol>
<ul>
<li>테스트 코드가 자연스럽게 <strong>명확한 인터페이스</strong>를 설계하게 해준다. </li>
<li>코드가 모듈화되고, 단일 책임 원칙(SRP)이 적용되기 쉽다. </li>
</ul>
<ol start="3">
<li><strong>안전한 리팩토링이 가능하다.</strong> </li>
</ol>
<ul>
<li>리팩토링 후에도 기존 테스트를 다시 실행하면서 정상동작 여부를 검증할 수 있다. </li>
<li>기능이 깨지는 것을 방지할 수 있다. </li>
</ul>
<ol start="4">
<li><strong>개발 속도 증가</strong></li>
</ol>
<ul>
<li>처음에는 개발 속도가 느려질 수 있지만, 버그 수정 시간 절약 효과가 크다. </li>
<li>배포 이후 버그 수정 비용이 줄어들어 <strong>전체 개발 생산성이 증가</strong>한다. </li>
</ul>
<h2 id="✅-testable-한-코드란">✅ Testable 한 코드란?</h2>
<p>이전에 <a href='https://velog.io/@dev_nana/TDD방법론-Testable-Code란'/>Testable한 코드에 대해 작성한 글</a>이 있다.</p>
<p>요약하면, </p>
<ul>
<li>몇번을 수행해도 항상 같은 결과가 반환되는 코드가 <code>Testable 한 코드</code>라고 할 수 있다. </li>
</ul>
<h2 id="🤚그럼-장점만-있는걸까">🤚그럼 장점만 있는걸까?</h2>
<p>이렇게 좋은 점만 나열해놨는데, TDD의 단점은 없는걸까? </p>
<ol>
<li>초기 개발 속도의 지연 
TDD는 &quot;테스트 먼저&quot; 접근 방식이기 때문에 처음 개발할 때 시간이 더 걸릴 수 있다. </li>
</ol>
<ul>
<li>기능 구현 전에 테스트를 먼저 작성해야 하기 때문에 초기 개발 속도가 느려질 수 있다. </li>
<li>빠르게 프로토타이핑해야 하는 스타트업 환경에서 부담이 될 수 있다. </li>
<li>개발 일정이 빠듯하면 TDD 적용이 어려울 수 있다. </li>
</ul>
<blockquote>
<p>💡 해결책: 
    - 모든 기능을 TDD로 개발할 필요는 없다. 중요한 핵심 로직이나 복잡한 비즈니스에 우선 적용한다. 
    - 익숙해지면 개발 속도가 점점 빨라진다는 점을 고려해야한다. </p>
</blockquote>
<ol start="2">
<li>테스트 코드 유지보수 필요. 
기능이 바뀔 때마다 <strong>테스트 코드도 함께 수정</strong>해야한다. </li>
</ol>
<ul>
<li>작은 기능 변경에도 관련된 테스트 코드가 깨질 수 있다. =&gt; 그치만 상관 없는 코드까지 너무많이 깨진다면? 테스트코드를 잘못 짠건 아닌지 확인해보자! </li>
<li>유지보수할 코드가 늘어나면서 개발자의 부담 증가. </li>
<li>너무 많은 테스트 코드작성은 테스트 자체가 복잡성을 증가시킬 수도 있다.</li>
</ul>
<blockquote>
<p>💡 해결책:
    - 지나치게 세부적인 테스트 보다는 <strong>핵심 로직을 검증하는 테스트</strong> 집중하기. 
    -  테스트가 지나치게 구체적이면 유지보수가 어렵기 때문에 적절한 추상화 필요. </p>
</blockquote>
<ol start="3">
<li>UI테스트와 같은 복잡한 테스트 적용이 어렵다. </li>
</ol>
<ul>
<li>백엔드 로직(비즈니스 로직, API, 서비스 레이어)에는 적합하지만,
UI(프론트엔드)개발에서는 적용이 어렵다. </li>
<li>특히 비동기 처리, 애니메이션, 외부 API연동 같은 요소는 TDD로 관리하기 어렵다. </li>
</ul>
<ol start="4">
<li>모든 상황을 테스트하기 어렵다. </li>
</ol>
<ul>
<li>특히 멀티스레딩, 데이터베이스 트랜잭션, 네트워크 요청 같은 요소들은 실제 환경과 다를 수 있어서 테스트가 무의미해질 수도 있다. </li>
<li>외부 API를 호출하는 경우 Mocking이 필요하지만, 모든 상황을 Mocking하는 것도 쉽지 않다. </li>
</ul>
<blockquote>
<p>💡 해결책 : 
    - 통합 테스트와 병행하여 중요한 핵심 로직만 TDD로 관리하기. 
    - Mocking과 실제 테스트 데이터를 적절히 조합해서 테스트하기. </p>
</blockquote>
<ol start="5">
<li>개발팀의 TDD 숙련도가 중요. </li>
</ol>
<ul>
<li>단순히 테스트 코드를 먼저 작성하는게 아닌
  설계, 유지보수, 리팩토링까지 고려해야하는 방법론이다. </li>
<li>잘못된 설계유지나 과도한 테스트로 인해 개발 속도가 떨어질 수 있다. </li>
<li>팀 전체가 TDD를 이해하고 실천해야 효과가 나타나는데, 개발자마다 경험과 스타일이 다르다 보니 팀 내 통일이 어려울 수도 있다. </li>
</ul>
<h2 id="💁🏻♀️tdd-도입-사례">💁🏻‍♀️TDD 도입 사례</h2>
<p>그럼 어디에 TDD를 도입 했을 때 도움이 되는걸까? 
<strong>결제 시스템</strong>, <strong>인증 시스템</strong>, <strong>API개발</strong> 등 실무에서 주요 비즈니스 로직에 적용될 수 있다. </p>
<p>특정 금융 서비스 회사는 TDD를 도입하여 개발 프로세스를 개선하고, 결함률을 크게 줄였다. 
이 회사는 TDD를 통해 개발 초기 단계에서 오류를 발견하고 수정하여 전체적인 프로젝트의 지연을 방지하고, 고객만족도를 향상시켰다. </p>
<p>많은 오픈 소스 프로젝트들은 TDD방법론을 채택하여 개발 과정에서의 품질을 보장한다. 
왜냐하면 TDD는 개발 과정에서 발생할 수 있는 오류를 사전에 예방하고, 유지보수가 용이한 코드를 작성할 수 있게 해주기 때문이다. </p>
<h2 id="끝으로">끝으로.</h2>
<p>TDD 말만 많이 들어보고 실제 비즈니스에 적용해본 적이 없어서 &#39;꼭 써야해? TDD 안써도 잘 돌아가지않나?&#39; 라고 막연하게 생각했다. 
많은 기업에서 <strong>안정적인 시스템</strong>을 강조하는 만큼 안정적인 비즈니스 로직을 구축하려면, 그리고 좋은 개발 문화를 갖기 위해서 도입하는게 여러모로 장점이 많은 것 같다. </p>
<hr>
<p><a href='https://f-lab.kr/insight/understanding-and-applying-tdd'>TDD(Test-Driven Development)의 이해와 실제 적용 사례</a></p>
<p><a href='https://oliveyoung.tech/2023-10-30/wcare-tdd-development/'>올리브영 테크블로그 : W CARE 서비스 프론트엔드를 TDD로 개발해본 후기</a>
<a href='https://tech.kakaopay.com/post/implementing-tdd-in-practical-applications/'>카카오 페이 테크블로그 : 실전에서 TDD하기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2024년의 나, 왜 그렇게 짰니??]]></title>
            <link>https://velog.io/@dev_nana/2024%EB%85%84-%EC%BD%94%EB%93%9C-%EB%90%98%EB%8F%8C%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@dev_nana/2024%EB%85%84-%EC%BD%94%EB%93%9C-%EB%90%98%EB%8F%8C%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sat, 08 Mar 2025 06:59:04 GMT</pubDate>
            <description><![CDATA[<h1 id="💁🏻-2024년-나는-어떤-코딩을-했을까">💁🏻 2024년, 나는 어떤 코딩을 했을까?</h1>
<h2 id="24년도에-대한-회고">24년도에 대한 회고?</h2>
<p>24년 회고라기엔 벌써 25년 3월의 중반부를 향해 달려가고 있기에 지난 해에 대한 반성, 그로 비롯된 개선점 찾기 정도로 해야겠다. </p>
<p>24년은 나에게 참 많은 변화가 있었다. </p>
<p>특히나 내가 가진 직업에 대한 시각과 마음가짐이 많이 달라졌다. 
단순히 업무를 처리하기 위해 코드만 작성하다가 훗날 내 코드를 보게 될 개발자들을 위해 깊이있는 고민을 하기 시작했다. </p>
<p>또한 새로운 기술과 개발 용어에 더욱 관심을 가지게 되었고 <em>이제서야 진정한 개발자의 마인드셋을 가진 것 같았다.</em> </p>
<p>때문에 새로운 코드를 작성하는 것도 중요하지만, <strong>지난 내 코드를 다시 뜯어보고 알게모르게 있던 내 좋지 않은 습관들을 고쳐나가고자 한다.</strong> </p>
<h2 id="내가-하던-코딩-방식">내가 하던 코딩 방식</h2>
<p>평소에 <strong>일단 하자</strong>라는 마인드로 살았다. 코드를 일단 써 내려간 다음 기능이 완성되고나면 놓아버리곤 했다. 실무를 바쁘게 하는 개발자들은 모두 공감하겠지만 하나 처리하고나면 또 다른 일이 뒤에 기다리기에 내 지난 코드를 돌아볼 여유가 부족하다ㅠㅠ </p>
<p>작년에 Java 개발자로 전향하겠다는 원대한 목표를 세운 후 부트캠프를 통한 코드를 작성하면서 그 주의 과제를 일주일 내내 밤을 새서 완성하고 나면 진이 빠져버려 내가 짠 코드가 맘껏 휘갈겨 져 있는 것도 모른채 그냥 넘어가버리곤했다. </p>
<p>➡️ <em>아무튼 고쳐야 할 코드가 너무너무 많단 의미</em> </p>
<h1 id="💻-어떤-방향으로-리팩토링을-할까">💻 어떤 방향으로 리팩토링을 할까?</h1>
<h2 id="👩🏻🍳-내가-요리하게-될-코드">👩🏻‍🍳 내가 요리하게 될 코드</h2>
<p>작년에 생성한 프로젝트 중 어떤걸 가장 먼저 리팩토링할까 고민해보았다. 
내 아픈손가락 같은 존재인 <strong>수강신청 프로젝트</strong>를 변경해 보려고한다. </p>
<p>클린아키텍처를 처음 적용해 본 프로젝트라 폴더 구조부터 어떻게 잡아야할지 몰랐고 매일 새벽 지피티를 부여잡고 엉엉 울며 정말 열심히 작성했으나 <code>Entity</code>를 <code>Controller</code>에서 불러오는 실수(피드백 받고도 왜 fail인지 몰랐음)를 하며 fail을 받았던.. 내 프로젝트.. </p>
<h2 id="🚫-수강신청-프로젝트의-문제점">🚫 수강신청 프로젝트의 문제점</h2>
<h3 id="1-clean-architecture--rest-api에-대한-이해가-부족한-상태로-작성됨">1. Clean Architecture &amp; REST API에 대한 이해가 부족한 상태로 작성됨.</h3>
<pre><code class="language-java">/**생략**/
import org.springframework.web.bind.annotation.*; 

import java.util.List;

/**생략**/
@RequestMapping(&quot;/lecture&quot;)

public class LectureController {

    private final LectureApplyService lectureApplyService;
    private final LectureService lectureService;
    private final LectureHistoryService lectureHistoryService;

    //특강 신청 API
    @PostMapping(&quot;/apply&quot;)
    public ResponseEntity&lt;String&gt; applyLecture(@RequestBody LectureApplyDTO lectureApplyDTO) {
/*메서드 내용 생략 */
    }

    //신청 가능 리스트 조회API
    @GetMapping(&quot;/list&quot;)
    public ResponseEntity&lt;List&lt;Lecture&gt;&gt; getAvailableLectures(@ModelAttribute @Valid LectureCommand.Date command) {
        /*메서드 내용 생략 */
    }

    //신청 완료 목록 조회 API
    @GetMapping(&quot;/{studentId}/list&quot;)
    public ResponseEntity&lt;List&lt;LectureHistory&gt;&gt; getLectureApplicationHistory(@PathVariable Long studentId) {
       /*메서드 내용 생략 */
    }
}
</code></pre>
<p>▶️  &#39;<em>&#39; 사용은 지양해야한다. 
▶️  REST API 위반 사항(*</em>End Point** URL 수정 필요) </p>
<h3 id="2-command-pattern을-제대로-알지-못한-채로-사용함">2. Command Pattern을 제대로 알지 못한 채로 사용함.</h3>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/471b8409-eb7a-4c43-a719-efa98f7f1e7b/image.png" alt=""></p>
<h3 id="3-사용하지-않는-코드dto-중복된-코드-등이-많음">3. 사용하지 않는 코드(DTO, 중복된 코드 등)이 많음.</h3>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/e2860d08-2e76-4e99-b1ae-ce28c20f3697/image.png" alt=""></p>
<h3 id="4-작성-시-가독성을-고려하지-않음">4. 작성 시 <em>가독성</em>을 고려하지 않음.</h3>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/e4f784ca-ebec-4bed-9da1-1ab187057282/image.png" alt="">
▶️ JPQL 작성 시 쿼리가 긴데도 한줄로 작성했다. 
<img src="https://velog.velcdn.com/images/dev_nana/post/e42ba8cb-7892-4784-a6df-b19a390d16f7/image.png" alt="">
▶️ 어떤건 한줄로, 어떤건 줄바꿈으로 개행문자를 열었다. </p>
<h3 id="5-불필요한-테스트-코드가-많음">5. 불필요한 테스트 코드가 많음.</h3>
<pre><code class="language-java">@Test
    @DisplayName(&quot;🟢전체 특강 조회&quot;)
    void getAvailableLectures_SUCCESS_ALL() {
        Lecture mockLecture3;
        Lecture mockLecture4;

        mockLecture3 = new Lecture().builder().lectureNm(&quot;특강3&quot;).capacity(30L).enrollStartDate(LocalDate.parse(&quot;2024-10-01&quot;, DateTimeFormatter.ISO_DATE)).build();
        mockLecture4 = new Lecture().builder().lectureNm(&quot;특강4&quot;).capacity(25L).enrollStartDate(LocalDate.parse(&quot;2024-10-02&quot;, DateTimeFormatter.ISO_DATE)).build();


        when(lectureJpaAdaptor.findAll()).thenReturn(Arrays.asList(mockLecture1, mockLecture2, mockLecture3, mockLecture4));

        List&lt;Lecture&gt; Lectures = lectureJpaAdaptor.findAll();

        assertNotNull(Lectures);
        assertEquals(4, Lectures.size());
        assertEquals(&quot;특강1&quot;, Lectures.get(0).getLectureNm());
        assertEquals(&quot;특강2&quot;, Lectures.get(1).getLectureNm());
        assertEquals(&quot;특강3&quot;, Lectures.get(2).getLectureNm());
        assertEquals(&quot;특강4&quot;, Lectures.get(3).getLectureNm());
    }</code></pre>
<p>▶️ 좀 더 깔끔한 테스트 코드를 작성할 수 있지 않았을까? </p>
<h3 id="6-성능-개선을-고려하지-않음">6. 성능 개선을 고려하지 않음.</h3>
<p>... 등등 더 많지만 리팩토링 하는 과정에서 추가해 보고자 한다.</p>
<h2 id="✅-개선-포인트">✅ 개선 포인트</h2>
<blockquote>
<ul>
<li><code>RESTful</code>하게 작성하기. </li>
</ul>
</blockquote>
<ul>
<li><strong>계층분리</strong>를 정확하게 할 것. </li>
<li>네이밍 어색한 것 변경하기. </li>
<li><strong>테스트 코드 점검</strong>(불필요한 테스트코드는 없는지 / 꼭 필요한 코드인데 누락하지는 않았는지)</li>
<li>기존 기능이 깨지지 않도록 <strong>작은 단위</strong>로 진행하기. </li>
</ul>
<h1 id="✅-주요-리팩토링-기법">✅ 주요 리팩토링 기법</h1>
<p> 📍 <strong>메서드 추출</strong> (Extract Method)</p>
<ul>
<li>하나의 메서드가 너무 많은 일을 하고 있다면, 기능별로 분리</li>
</ul>
<p>📍 <strong>중복 코드 제거</strong></p>
<ul>
<li>여러 곳에서 반복적으로 사용되는 로직을 유틸 클래스나 공통 서비스 계층으로 이동시키기. </li>
</ul>
<p>📍 <strong>DTO 활용</strong></p>
<ul>
<li>⭐️ API 응답을 엔티티 그대로 반환하는 대신, DTO를 사용하여 계층 분리 강화시키기.</li>
</ul>
<p>📍 *<em>Repository 쿼리 최적화 *</em></p>
<ul>
<li>N+1문제 해결, 불필요한 <code>findAll()</code>호출 제거, 페이징 적용 등</li>
</ul>
<p>📍 <strong>예외처리 개선</strong></p>
<ul>
<li>일괄된 <code>GlobalExceptionHandler</code>도입 </li>
<li><code>@ControllerAdvice</code>활용</li>
</ul>
<h1 id="🔥-마무리">🔥 마무리</h1>
<p>이렇게 정리해보니, 한 프로젝트만 훑어본 것인데도 정말 손볼 게 많다.
하지만 천천히 하나씩 개선하면서 더 좋은 개발자로 성장하는 과정이라고 생각하자.
2025년에는 더 깔끔한 코드와 함께 돌아볼 수 있길! 🚀</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring Security] CSRF공격과 대응방법 ]]></title>
            <link>https://velog.io/@dev_nana/CSRF%EA%B3%B5%EA%B2%A9</link>
            <guid>https://velog.io/@dev_nana/CSRF%EA%B3%B5%EA%B2%A9</guid>
            <pubDate>Tue, 04 Mar 2025 15:27:37 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>💡사이트 간 요청 위조(Cross-site Request Forgery, CSRF) 
: 사용자가 자신의 의지와 상관없이 공격자가 의도한 행위(ex. 보안에 취약하게 만들기 / 수정 또는 삭제 등의 작업하게 만들기)를 특정 웹사이트에 요청하도록 하는 것을 의미 </p>
</blockquote>
<h1 id="xss-공격과-차이">XSS 공격과 차이</h1>
<p><code>XSS공격 (사이트 간 스크립팅)</code>공격 : <strong>사용자가 웹사이트를 신용</strong>하여 악성스크립트가 실행됨. 
<code>CSRF공격</code> : <strong>특정 웹사이트가 사용자의 브라우저를 신용</strong>하여 발생. 
✅ XXS는 악성 코드가 <em>클라이언트</em>에서 발생, CSRF는 악성코드가 <em>서버</em>에서 발생. </p>
<h1 id="csrf-공격-방법">CSRF 공격 방법</h1>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/2ac761a9-fd92-4654-8aac-d996e8c20bdb/image.png" alt=""></p>
<ul>
<li>공격자는 이메일이나 게시판에 CSRF 스크립트가 포함된 게시물을 전송한다. </li>
<li>관리자는 공격자가 등록한 CSRF 스크립트가 포함된 게시물을 확인한다. </li>
<li>관리자가 CSRF스크립트가 포함된 게시물을 열람하면, 관리자의 권한으로 공격자가 원하는 CSRF스크립트 요청이 발생한다. </li>
<li>공격자가 원하는 CSRF스크립트가 실행되어, 관리자 및 사용자의 피해가 발생한다. </li>
</ul>
<p>ex) 공격자가 만든 사이트 내부에 다음과 같은 태그가 존재할 수 있다. 
<code>&lt;img src =&quot;https://velog.io/member/changePassword?newpassword=aaaa1111&quot;/&gt;</code></p>
<p>공격자 사이트에 방문한 사용자는 자신의 의지와 무관하게 img태그로 인해 세션 ID가 포함된 쿠키와 함께 <strong>비밀번호 요청을 velog서버로 전달</strong>한다. </p>
<h1 id="csrf-공격은-어떻게-방어할까">CSRF 공격은 어떻게 방어할까?</h1>
<h2 id="1-http-헤더-중-하나인-referer요청-헤더-사용">1. HTTP 헤더 중 하나인 Referer요청 헤더 사용</h2>
<ul>
<li>HTTP 헤더에 있는 Referer로 해당 요청이 요청된 페이지의 정보를 확인하는 방법. </li>
<li>간단해서 소규모 웹사이트에 주로 이용됨. </li>
<li>해당 정보는 Paros나 Zap, fiddler같은 프로그램으로 조작 가능. </li>
</ul>
<h2 id="2-getpost-요청-구분">2. GET/POST 요청 구분</h2>
<ul>
<li>img 태그 등의 경우 GET요청으로, form 태그로 받을 경우 POST를 이용하여 요청을 구분해줌. 
ex) 
중요한 요청의 경우 POST방식으로만 받을 수 있도록 변경하면 된다. <pre><code class="language-html">&lt;form method=&quot;POST&quot; action=&quot;https://victim.com/transfer&quot;&gt;
  &lt;input type=&quot;hidden&quot; name=&quot;amount&quot; value=&quot;10000&quot;&gt;
  &lt;input type=&quot;hidden&quot; name=&quot;to&quot; value=&quot;hacker&quot;&gt;
  &lt;input type=&quot;submit&quot; value=&quot;전송&quot;&gt;
&lt;/form&gt;</code></pre>
이렇게 하면 <strong>GET요청을 통한 자동 실행 방지</strong>가 된다. 
<code>&lt;img&gt;</code> 태그나 <code>&lt;script&gt;</code> 태그로는 POST 요청을 보낼 수 없음 → 공격이 어려워짐.</li>
</ul>
<p> ex2) </p>
<pre><code class="language-java"> @RestController
@RequestMapping(&quot;/api&quot;)
public class TransferController {

    @PostMapping(&quot;/transfer&quot;)
    public ResponseEntity&lt;String&gt; transferMoney(
            @RequestParam int amount,
            @RequestParam String to,
            HttpServletRequest request) {

        // GET 요청 차단 (이 코드는 사실 필요 없음, @PostMapping 자체가 POST만 허용)
        if (!request.getMethod().equalsIgnoreCase(&quot;POST&quot;)) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                    .body(&quot;GET 요청은 허용되지 않습니다.&quot;);
        }

        // 실제 송금 처리 로직 (예제이므로 로직은 생략)
        return ResponseEntity.ok(&quot;송금 완료&quot;);
    }
}
</code></pre>
<h2 id="3-csrf-토큰-방법-사용">3. CSRF 토큰 방법 사용.</h2>
<blockquote>
<p>*<em>CSRF토큰이란? *</em>
서버측 애플리케이션에서 생성되고 클라이언트와 공유되는 인증 값. 
클라이언트는 서버의 통신에 올바른 CSRF토큰을 포함해야한다. 
그렇지 않으면 서버는 요청된 작업 수행을 거부한다. 
CSRF 토큰을 클라이언트와 공유하는 일반적인 방법은 HTML 형식의 숨겨진 매개변수로 포함하는 것. </p>
</blockquote>
<ul>
<li>보통의 CSRF Token은 hidden상태로 보안되어있다. </li>
</ul>
<h2 id="4-그-외">4. 그 외</h2>
<h3 id="check-double-submit-cookie">Check Double-Submit Cookie</h3>
<p>브라우저의 <code>SameOrigin 정책</code>을 이용. 
<code>SameOrigin</code>이 아닌 경우 JavaScript로 쿠키값을 확인하거나 수정하지 못한다는 점을 이용한 검증방법. 
동일한 도메인 주소에서 동작하도록 해당 사이트에 게시글등을 통해 악성 스크립트를 심는 경우 이 방어는 무효하다. 
** 도메인이 다른 사이트를 이용해 공격하는 경우에만 방어 코드가 유효**하다.</p>
<blockquote>
<p>❓<code>SameOrigin</code>
Same-Origin(동일 출처) 정책(SOP, Same-OriginP Policy)은 웹 보안 모델 중 하나로, 다른 출처(origin)에서 온 리소스에 대한 접근을 제한하는 정책이다. 
웹 브라우저는 기본적으로 동일출처에서만 리소스를 공유할 수 있도록 제한하고, 다른 출처의 리소스를 불러오려면 CORS(Cross-Origin Resource Sharing)같은 특별한 방법이 필요하다. </p>
</blockquote>
<h2 id="추가">추가.</h2>
<p>스프링 시큐리티 관련 자료들에서 CSRF설정을 종종 비활성화시키곤 한다. </p>
<pre><code class="language-java">@Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
    }</code></pre>
<p> 스프링 시큐리티 프레임워크는 기본적으로 CSRF공격에 대한 방어를 수행한다. 
 CSRF공격에 대처할 설정을 <code>disable</code>시키는 것이 과연 좋은 방법일까? 
 CSRF공격은 쿠키가 없으면 불가능하다. 브라우저에 저장되는 쿠키가 CSRF공격의 매개체이기 때문이다. </p>
<p> REST API방식은 쿠키나 세션에 의존하지 않는 경향이 크므로 CSRF공격에 대한 방어 설정을 비활성화 시키는 경우가 많으며, 쿠키 대신에 <code>로컬스토리지(localStorage)</code>, 세션 대신 <code>JWT(Json Web Token)</code>를 사용하면 CSRF공격에 대한 방어가 필요 없다. </p>
<hr>
<p><a href='https://velog.io/@gwanuuoo/CSRF-%EA%B3%B5%EA%B2%A9%EA%B3%BC-%EB%B0%A9%EC%96%B4-%EA%B8%B0%EB%B2%95'>CSRF 공격과 방어 기법</a>
<a href='https://www.xn--hy1b43d247a.com/critical-info-infrastructure/01-account-management/csrf-token'>CSRF TOKEN에 관하여</a>
<a href='https://junhyunny.github.io/information/security/spring-boot/spring-security/cross-site-reqeust-forgery/#google_vignette'>CSRF공격과 방어</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Clean Architecture - 의존성 역전하기 ]]></title>
            <link>https://velog.io/@dev_nana/Clean-Architecture-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%97%AD%EC%A0%84%ED%95%98%EA%B8%B0-4kvhxxgt</link>
            <guid>https://velog.io/@dev_nana/Clean-Architecture-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%97%AD%EC%A0%84%ED%95%98%EA%B8%B0-4kvhxxgt</guid>
            <pubDate>Sat, 01 Mar 2025 07:14:09 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>본 시리즈는 &quot;만들면서 배우는 클린 아키텍처&quot;(톰 홈버그 저)의 책으로 공부하며 정리한 내용을 담고있습니다.</p>
</blockquote>
<p align="center"><img src="https://velog.velcdn.com/images/dev_nana/post/c2bea46f-4fc1-41cf-9c95-5c18fb10aa08/image.png" width="40%" height="60%" ></p>


<h1 id="단일-책임-원칙single-responsibility-principle-srp">단일 책임 원칙(Single Responsibility Principle, SRP)</h1>
<p>단일 책임 원칙의 일반적인 해석은 다음과 같다. </p>
<blockquote>
<p>하나의 컴포넌트는 오로지 한 가지 일만 해야 하고, 그것을 올바르게 수행해야 한다. </p>
</blockquote>
<p>하지만, 단일 책임 원칙의 실제 정의는 다음과 같다. </p>
<blockquote>
<p><strong>컴포넌트를 변경하는 이유는 오직 하나뿐이어야 한다.</strong></p>
</blockquote>
<p><em>책임</em>은 &#39;오로지 한 가지 일만 하는 것&#39; 보다는 <strong>&#39;변경할 이유&#39;</strong>로 해석해야 한다. </p>
<p>이 말인 즉, 만약 컴포넌트를 변경할 이유가 한 가지라면 우리가 어떤 다른 이유로 소프트웨어를 변경하더라도 이 컴포넌트에 대해서는 전혀 신경 쓸 필요가 없다. 
소프트웨어가 변경되더라도 여전히 우리가 기대한 대로 동작할 것이기 때문이다. </p>
<p>많은 코드는 단일 책임 원칙을 위반하기 때문에 시간이 갈수록 변경하기가 더 어려워지고 변경 비용도 증가한다. 
위에서 말한 <strong>변경할 이유</strong>가 많이 쌓인 코드는 한 컴포넌트를 바꾸는 것이 다른 컴포넌트가 실패하는 원인으로 작용할 수 있다. </p>
<h1 id="의존성-역전-원칙-dependency-inversion-principle-dip">의존성 역전 원칙 (Dependency Inversion Principle, DIP)</h1>
<p>계층형 아키텍처에서 계층 간 의존성은 항상 다음 계층인 아래 방향을 가리킨다. 
영속성 계층에 대한 도메인 계층의 의존성 떄문에 영속성 계층을 변경할 때마다 잠재적으로 도메인 계층도 변경해야한다. 
애플리케이션에서 가장 중요한 코드인 <em>도메인 코드</em>가 영속성 코드가 바뀐다고 해서 바뀌고 싶지 않다면 어떻게 해야할까? </p>
<blockquote>
<p>의존성 역전 원칙 (Dependency Inversion Principle, DIP) : 
코드상의 어떤 의존성이든 그 방향을 바꿀 수 (역전시킬 수) 있다. </p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/e8fcce0e-3d9f-4144-8d49-c1f141467008/image.png" alt=""></p>
<p>👆🏻도메인 계층에 영속성 계층의 엔티티와 리포지토리가 상호작용하는 서비스에서 
<strong>도메인 계층에 인터페이스 도입함으로서 의존성 역전</strong>이 가능해졌다. 
그 덕분에 영속성 계층이 도메인 계층에 의존하게 되었다. </p>
<p>엔티티는 도메인 객체를 표현하고 도메인 코드는 엔티티들의 상태를 변경하는 일을 중심으로 하므로, 엔티티를 도메인 계층으로 올림. 
영속성 계층의 리포지토리가 도메인 계층에 있는 엔티티에 의존하기 때문에 두 계층 사이에 순환의존성이 생긴다. 
도메인 계층에 리포지토리에 대한 인터페이스, 실제 리포지토리는 영속성 계층에서 구현. </p>
<h1 id="클린-아키텍처">클린 아키텍처</h1>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/b1c81b87-1b3a-4091-856b-c27214b7343d/image.png" alt=""></p>
<p>클린아키텍처에서는</p>
<ul>
<li>설계가 비즈니스 규칙의 테스트를 용이하게 함. </li>
<li>비즈니스 규칙은 프레임워크, 데이터베이스, UI기술, 그 밖의 외부 애플리케이션이나 인터페이스로부터 독립적일 수 있다. </li>
</ul>
<p>&#39;도메인 코드가 바깥으로 향하는 어떤 의존성도 없어야 함&#39;을 의미. 
대신 <strong>모든 의존성이 도메인 코드를 향하고있다.</strong></p>
<p>도메인 코드에서 어떤 영속성 프레임워크나 UI프레임 워크가 사용되는지 알 수 없기 때문에 비즈니스 규칙에 집중할 수 있다. &gt; 도메인 코드를 자유롭게 모델링 가능</p>
<p>도메인 계층이 영속성이나 UI계층과 같은 외부계층에서 철저히 분리되어야 하므로 애플리케이션의 <strong>엔티티에 대한 모델을 각 계층에서 유지보수</strong> 해야한다.</p>
<h1 id="헥사고날-아키텍처">헥사고날 아키텍처</h1>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/42ee58ec-cb31-4076-81aa-f4db62ee9ac0/image.png" alt=""></p>
<p>육각형 안에는 도메인 엔티티와 이와 상호작용하는 유스케이스가 있다. 
육각형에서 외부로 향하는 의존성이 없으므로 의존성 규칙이 그대로 적용된다. 
대신 모든 의존성은 코어를 향한다. </p>
<p>왼쪽 어댑터들은 애플리케이션을 주도하는 어댑터, 오른쪽 어댑터들은 애플리케이션에 의해 주도되는 어댑터들이다. </p>
<p>애플리케이션 코어와 어댑터들 간의 통신이 가능하려면 애플리케이션 코어가 각각의 포트를 제공해야한다. </p>
<p> 도메인의 비즈니스 로직을 외부 라이브러리 및 툴로부터 분리 할 때 포트와 어댑터라고 부르는 인터페이스를 사용하기 때문에 <strong>포트&amp;어댑터 아키텍처</strong>라고도 부른다.</p>
<p> 도메인 비즈니스 로직이 외부요소에 의존하지 않게 만들고, 프레젠테이션 계층(controller)과 데이터 소스 계층(persistence) 같은 외부 요소들이 도메인 계층에 의존하도록 한다.
→ 외부와의 접촉을 인터페이스로 추상화하여 비즈니스 로직 안에 외부 코드나 로직의 주입을 막는 것, 외부 라이브러리 및 툴로부터 분리시키는 것이 아키텍처의 핵심</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] Java 8 문법을 알아보자. -1 (람다표현식&Stream API / Optional )]]></title>
            <link>https://velog.io/@dev_nana/Java-Java-8-%EB%AC%B8%EB%B2%95%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@dev_nana/Java-Java-8-%EB%AC%B8%EB%B2%95%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Tue, 25 Feb 2025 15:27:40 GMT</pubDate>
            <description><![CDATA[<h1 id="0-왜-많고-많은-버전-중-java-8인가">0. 왜 많고 많은 버전 중 Java 8인가?</h1>
<p>Java를 배우고자 시작했다면 Java 8이 가장 중요하다 라는 말을 많이 들어봤을 것이다. 
현재 2n버전까지 출시된 마당에 8버전이 왜 중요하다는걸까? </p>
<p>그 이유는 이후의 Java업데이트들이 대부분 Java8의 개념을 확장하거나 개선한 형태이기 때문이다. 즉, Java8을 이해하면 이후의 버전의 변화도 쉽게 따라갈 수 있다는 뜻이다. </p>
<p>두 번의 포스팅에 걸쳐 Java 8 문법의 변화를 따라가보고자 한다. </p>
<h1 id="1️⃣-함수형-프로그래밍-도입람다-표현식--stream-api">1️⃣ 함수형 프로그래밍 도입(람다 표현식 &amp; Stream API)</h1>
<p>Java 8 이전까지는 객체지향 스타일이 중심이었으나, <strong>함수형 프로그래밍의 개념이 처음으로 도입</strong>되었다. </p>
<pre><code class="language-java">//기존 방식(익명 클래스 사용) 
        Comparator&lt;String&gt; comparator = new Comparator&lt;String&gt;() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareTo(o2);
            }
        }

// Java8 람다 표현식 사용 
Comparator&lt;String&gt; comparator = (o1, o2) -&gt; o1.compareTo(o2);
</code></pre>
<p>이전 코드보다 훨씬 짧고 가독성이 좋아졌다!</p>
<h1 id="2️⃣-스트림api로-데이터-처리-방식-혁신">2️⃣ 스트림API로 데이터 처리 방식 혁신</h1>
<p>8 이전에는 컬렉션을 다룰 때 <strong>반복문(Loop)</strong>을 사용해야했다. 
하지만 Stream API의 사용으로 가독성과 성능이 좋아졌고, 병렬처리도 가능하게 되었다. </p>
<pre><code class="language-java">List&lt;String&gt; names = Arrays.asList(&quot;Apple&quot;, &quot;Banana&quot;, &quot;Avocado&quot;);

// Java 7 방식 (반복문 사용)
List&lt;String&gt; result = new ArrayList&lt;&gt;();
for (String name : names) {
    if (name.startsWith(&quot;A&quot;)) {
        result.add(name);
    }
}

// Java 8 스트림 API 사용
List&lt;String&gt; resultStream = names.stream()
                                 .filter(name -&gt; name.startsWith(&quot;A&quot;))
                                 .collect(Collectors.toList());</code></pre>
<ul>
<li>코드가 간결해지고, 가독성이 향상되었다. </li>
<li>중간 연산(map, filter)과 최종 연산(collect, forEach)을 분리하여 이해하기 쉽다. </li>
<li>parallelStream()을 사용하면 병렬처리가 가능하여 성능이 최적화 된다. </li>
</ul>
<h1 id="3️⃣-optional을-통한-npenullpointerexception-방지">3️⃣ Optional을 통한 NPE(NullPointerException) 방지</h1>
<p>Java 8이전에는 null체크를 직접 해야했다. 
Java 8의 <code>Optional</code>을 사용하면 null처리를 보다 안전하게 할 수 있다. </p>
<pre><code class="language-java">// 기존 방식
String name = getName();
if (name != null) {
    System.out.println(name.toUpperCase());
}

// Java 8 Optional 사용
Optional&lt;String&gt; nameOpt = Optional.ofNullable(getName());
nameOpt.ifPresent(name -&gt; System.out.println(name.toUpperCase()));
</code></pre>
<ul>
<li>NPE방지를 위한 if체크가 불필요해지고, 가독성이 좋아짐. </li>
</ul>
<h1 id="4️⃣-javatime-패키지로-날짜-및-시간-처리-개선">4️⃣ java.time 패키지로 날짜 및 시간 처리 개선</h1>
<p>Java 8 이전에는 <code>Date</code>, <code>Calendar</code> 클래스를 사용해야 했는데, 이 클래스들은 불변하지 않고 설계가 불편했다. 
Java 8에서 <code>LocalDate</code>, <code>LocalTime</code>, <code>LocalDateTime</code>등을 추가하면서 날짜 처리 방식이 개선되었다. </p>
<pre><code class="language-java">// 기존 방식 (java.util.Date)
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat(&quot;yyyy-MM-dd&quot;);
String formattedDate = sdf.format(date);

// Java 8 방식 (LocalDate)
LocalDate today = LocalDate.now();
System.out.println(today); // 2024-02-18</code></pre>
<ul>
<li>불면객체로 설계되어 스레드 안전(Thread-Safe)하고, 사용법이 직관적이다. </li>
</ul>
<h1 id="5️⃣-인터페이스에-default-및-static-메서드-추가">5️⃣ 인터페이스에 default 및 static 메서드 추가</h1>
<p>Java8이전에는 인터페이스에 추상 메서드만 정의할 수 있었다. 
하지만 Java 8에서 default method와 static method를 추가할 수 있게 되어 기존 인터페이스를 쉽게 확장 가능하다. </p>
<pre><code class="language-java">interface MyInterface {
    default void defaultMethod() {
        System.out.println(&quot;This is a default method&quot;);
    }

    static void staticMethod() {
        System.out.println(&quot;This is a static method&quot;);
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Database Connection Pool(데이터베이스 커넥션 풀)을 사용하지 않으면 일어나는 일!!?]]></title>
            <link>https://velog.io/@dev_nana/Database-Connection-Pool%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%BB%A4%EB%84%A5%EC%85%98-%ED%92%80%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%A7%80-%EC%95%8A%EC%9C%BC%EB%A9%B4-%EC%9D%BC%EC%96%B4%EB%82%98%EB%8A%94-%EC%9D%BC</link>
            <guid>https://velog.io/@dev_nana/Database-Connection-Pool%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%BB%A4%EB%84%A5%EC%85%98-%ED%92%80%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%A7%80-%EC%95%8A%EC%9C%BC%EB%A9%B4-%EC%9D%BC%EC%96%B4%EB%82%98%EB%8A%94-%EC%9D%BC</guid>
            <pubDate>Sat, 08 Feb 2025 09:37:20 GMT</pubDate>
            <description><![CDATA[<p>커넥션 풀에 관련된 이미지를 만들어달라고 했더니 진짜 풀장을 만들어줬다 ㅋㅋ</p>
<h1 id="✅-database-connection이란">✅ Database Connection이란?</h1>
<p>커넥션 풀에 대해 설명하기에 앞서 데이터베이스 커넥션이 무엇인지 살펴보자. </p>
<h2 id="데이터베이스-커넥션의-과정">데이터베이스 커넥션의 과정</h2>
<ol>
<li>데이터베이스 드라이버를 사용하여 데이터베이스 연결 열기 </li>
<li>데이터를 읽고 쓰기 위해 TCP소켓 열기 </li>
<li>TCP 소켓을 사용하여 데이터 통신 (데이터 읽기/쓰기)</li>
<li>데이터베이스 연결 닫기 </li>
<li>TCP 소켓 닫기 </li>
</ol>
<p>위와 같은 과정을 통해 데이터베이스 커넥션이 이루어지고, 사용자로부터 웹 애플리케이션에 요청이 들어올때마다 데이터베이스 연결을 수립하고 해제하는 것은 굉장히 비효율적으로 보인다. </p>
<h2 id="db-connection-구조">DB Connection 구조</h2>
<h3 id="2-tier">2-Tier</h3>
<p>클라이언트로서의 자바 프로그램(JSP)이 직접 DB 서버에 접근해 데이터를 액세스하는 구조이다.</p>
<h3 id="3-tier">3-Tier</h3>
<p>자바 프로그램과 DB 서버 중간에 미들웨어 층을 둔다.
미들웨어 층한테 비즈니스 로직, 트랜잭션 처리, 리소스 관리를 전부 맡기는 구조이다.</p>
<h1 id="✅-connection-pool커넥션-풀이란-무엇일까">✅ Connection Pool(커넥션 풀)이란 무엇일까?</h1>
<p><strong>데이터베이스 커넥션 풀(Database Connection Pool)은 데이터베이스와의 연결(Connection)을 미리 여러 개 생성해 두고, 필요할 때마다 이를 재사용 하는 기술</strong>이다. </p>
<p>데이터베이스 연결을 새로 생성하는 것은 시간과 자원을 많이 소모한다.
애플리케이션과 데이터베이스 간의 연결이 네트워크를 통해 이루어지고, 이 과정에서 <a href='https://mindnet.tistory.com/entry/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-22%ED%8E%B8-TCP-3-WayHandshake-4-WayHandshake'>TCP/IP 핸드셰이크</a>와 같은 여러 단계를 거쳐야한다. 
이 과정에서 연결을 맺는 데 상당한 시간이 소모되며 데이터 커넥션 풀을 사용해 미리 생성해 둔 연결을 재사용하면서 연결 생성에 소요되는 시간을 절약하고 애플리케이션의 성능을 향상시킬 수 있다. </p>
<p>데이터베이스 커넥션 풀을 사용하면 데이터베이스 요청이 들어올 때 마다 데이터베이스 연결을 수립하고, 통신한 뒤, 닫는 과정을 거치지 않아도 된다. 데이터베이스 커넥션 풀에는 사전에 데이터베이스와 이미 연결이 수립된 다수의 커넥션들이 존재한다. 커넥션 풀 안의 커넥션들은 데이터베이스 요청이 들어올 때 마다 새롭게 연결을 수립하고 닫는대신 항상 연결을 열린 상태로 유지한다. </p>
<p>WAS(Web Application Server)는 데이터베이스 커넥션이 필요할 때 직접 커넥션을 생성하지 않고, 커넥션 풀 컨테이너로부터 커넥션을 하나 건네받고, 사용을 마치면 반납한다. 이렇게 함으로써 데이터베이스 <strong>연결을 열고, 닫는 비용을 절약</strong>할 수 있다.
특히 동시에 많은 요청을 처리해야 하는 웹 애플리케이션에서 <strong>데이터베이스 성능의 병목 현상</strong>을 줄이는 데 큰 도움이 된다.</p>
<h2 id="커넥션-풀의-주요-구성-요소와-설정">커넥션 풀의 주요 구성 요소와 설정</h2>
<ul>
<li><p>최소 연결 수(min)</p>
<ul>
<li>풀이 관리하는 연결의 최소 개수 </li>
<li>애플리케이션 서버가 시작될 때 이 수만큼의 연결이 미리 생성됨 </li>
<li>애플리케이션의 초기 응답 속도를 높이는 데 도움이 됨. </li>
</ul>
</li>
<li><p>최대 연결 수(max)</p>
<ul>
<li>풀이 관리할 수 있는 연결의 최대 개수</li>
<li>동시에 처리할 수 있는 요청의 수와 직접적 관련있다. </li>
<li>너무 낮으면 동시 요청 시 응답시간이 길어지고, 너무 높으면 불필요한 자원을 소모할 수 있다. </li>
</ul>
</li>
<li><p>연결 대기 시간(timeout)</p>
<ul>
<li>사용 가능한 연결을 기다리는 최대 시간. </li>
<li>이 시간이 초과되면 예외가 발생함. </li>
<li>적절한 대기 시간 설정은 리소스 사용과 응답 시간 사이의 균형을 맞추는 데 중요함. </li>
</ul>
</li>
</ul>
<p>커넥션 풀의 설정은 애플리케이션의 요구 사항과 데이터베이스 서버의 성능에 따라 달라질 수 있으며, 적절한 모니터링을 통해 지속적으로 조정해야함. </p>
<h2 id="커넥션-풀-최적화-전략">커넥션 풀 최적화 전략</h2>
<p><em>적절한 커넥션 풀 설정은 애플리케이션의 성능과 안정성에 큰 영향을 미친다.</em> </p>
<ol>
<li><p>실제 트래픽 패턴을 분석하여 최소 연결 수와 최대 연결 수를 적절히 설정하기. 
▶️ 피크 타임에도 충분한 연결을 제공하면서, 비활성 시간에는 자원을 낭비하지 않도록 할 수 있다. </p>
</li>
<li><p>유휴 연결 검사와 연결 유효성 검사를 활성화하여, 항상 유효한 연결만을 유지 
▶️ 불필요하게 자원을 소모하는 연결을 정리함으로서 애플리케이션의 안정성을 높이는데 도움이 된다. </p>
</li>
<li><p>애플리케이션의 성능 모니터링 도구를 사용하여 커넥션 풀의 상태를 지속적으로 모니터링하고, 필요에 따라 설정을 조정한다. 
▶️ 애플리케이션의 성능을 지속적 관리하고 최적화 할 수 있다. </p>
</li>
<li><p>다양한 커넥션 풀 라이브러리 중에서 애플리케이션의 요구 사항에 가장 적합한 것을 선택. 
▶️ ex) HikariCP는 성능과 안정성 면에서 높은 평가를 받는 커넥션 풀 라이브러리 중 하나이다.
이 외 데이터 커넥션 풀 프레임워크로는 대표적으로 Apache Commons DBCP, Tomcat DBCP, HikariCP, Oracle UCP등이 있다. </p>
</li>
</ol>
<h1 id="✅-database-connection-pool을-사용함으로써-얻을-수-있는-장점">✅ Database Connection Pool을 사용함으로써 얻을 수 있는 장점</h1>
<p>애플리케이션과 데이터베이스 간의 연결을 효율적으로 관리하여 성능을 향상시킬 수 있다.
데이터베이스에 접근할 때마다 새로운 연결을 생성하고 종료하는 대신, <strong>미리 준비된 연결을 재사용함으로써 성능을 향상시키고 자원 사용을 최적화</strong>할 수 있다. </p>
<p>적절한 커넥션 풀 설정과 최적화 전략으르 잘 세운다면 <strong>애플리케이션의 성능과 안정성을 크게 향상</strong>시킬 수 있다. </p>
<h1 id="❓-connection-pool-사이즈는-크면-클-수록-좋을까">❓ Connection Pool 사이즈는 크면 클 수록 좋을까?</h1>
<h3 id="답은-❌">답은 ❌</h3>
<p>DBCP(사이즈를 무식하게 크게 설정하면 DeadLock(교착상태)고 뭐고 문제가 발생하지 않을거 같지만 커넥션도 결국 객체이기 때문에 <strong>메모리 공간을 차지</strong>한다. 
따라서 커넥션 개수와 메모리는 trade-off관계이며 성능 테스트를 통해 최적을 값을 찾아내야한다. </p>
<p>커넥션을 사용하는 주체는 스레드(Thread)이기 때문에 만약 커넥션 풀 사이즈가 스레드 풀 사이즈보다 크면, <strong>스레드 증가로 인해 더 많은 <code>Contect Switching</code>이 발생</strong>한다. 
Disk 경합 측면에서 성능 한계가 발생하고, 데이터베이스는 하드 디스크 하나 당 하나의 I/O를 처리하므로 블로킹이 발생한다. 
▶️ 특정 시점부터는 성능적 증가가 Disk 병목으로 인해 미비해진다.</p>
<p>반대로 커넥션 풀 사이즈가 스레드 풀 사이즈보다 작으면, <strong>스레드가 커넥션이 반환되기를 기다려야 하기 때문에 작업이 지연</strong>된다. </p>
<p>커넥션 풀 사이즈와 스레드 풀 사이즈의 균형이 맞더라도 너무 큰 사이즈로 설정하면, 데이터베이스 서버, 애플리케이션 서버의 메모리와 CPU를 과도하게 사용하게 되므로 성능이 저하된다. </p>
<p>따라서 데이터베이스 입자에서 Connection은 Thread와 어느 정도 일치한다고 볼 수 있다. Connection이 많다는 의미는 데이터베이스 서버가 Thread를 많이 사용한다는 것을 의미하고, 이에 따라 Context Switching으로 인한 오버헤드가 더 많이 발생하기 때문에 Connection Pool을 아무리 늘리더라도 성능적인 한계가 존재한다.</p>
<h3 id="connection-pool의-크기는-얼마나-적절할까">Connection Pool의 크기는 얼마나 적절할까?</h3>
<p>Hikari CP의 공식 문서에 의하면, 
<code>1 connections = ((core_count) * 2) + effective_spindle_count)</code> 로 정의하고 있다.</p>
<ul>
<li><code>core_count</code> : 현재 사용하는 서버에서의 cpu개수. <ul>
<li>core_count * 2 를 하는 이유는 Context Switching 및 Disk I/O와 관련이 있다.</li>
<li>Context Switching으로 인한 오버헤드를 고려하더라도 데이터베이스에서 Disk I/O(혹은 DRAM이 처리하는 속도)보다 CPU 속도가 월등히 빠르다. </li>
<li>Thread가 Disk와 같은 작업에서 블로킹되는 시간에 다른 Thread의 작업을 처리할 수 있는 여유가 생기고, 여유 정도에 따라 멀티 스레드 작업을 수행할 수 있게 된다. Hikari CP가 제시한 공식에서는 계수를 2로 선정하여 Thread 개수를 지정하였다.</li>
</ul>
</li>
</ul>
<p><code>effective_spindle_count</code> : 기본적으로 DB 서버가 관리할 수 있는 동시 I/O 요청 수.</p>
<ul>
<li>하드 디스크 하나는 spindle 하나를 갖는다.</li>
<li>디스크가 16개 있는 경우, 시스템은 동시에 16개의 I/O 요청을 처리할 수 있다.</li>
</ul>
<h1 id="글을-마치며">글을 마치며</h1>
<p>사실 저연차에서 애플리케이션을 개발하면서 커넥션 풀까지 고려하기란 쉽지 않다. 
아마 알면서도 못하는 경우도 많지만 몰라서 적용하지 못하는 경우가 더 많을 것 같다. </p>
<p>커넥션 풀에 대해 공부했으니 실천할 수 있는 프로젝트를 만난다면 꼭 고려할것이다..! </p>
<hr>
<p><a href='https://hudi.blog/dbcp-and-hikaricp/'>데이터베이스 커넥션 풀 (Connection Pool)과 HikariCP</a>
<a href='https://www.baeldung.com/java-connection-pooling'>A Simple Guide to Connection Pooling in Java</a>
<a href='https://steady-coding.tistory.com/564'>[데이터베이스] Connection Pool이란?</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Clean Architecture - 계층형 아키텍처의 문제]]></title>
            <link>https://velog.io/@dev_nana/Clean-Architecture-%EA%B3%84%EC%B8%B5%ED%98%95-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EC%9D%98-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@dev_nana/Clean-Architecture-%EA%B3%84%EC%B8%B5%ED%98%95-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98%EC%9D%98-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Mon, 27 Jan 2025 11:44:54 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>본 시리즈는 &quot;만들면서 배우는 클린 아키텍처&quot;(톰 홈버그 저)의 책으로 공부하며 정리한 내용을 담고있습니다.</p>
</blockquote>
<p align="center"><img src="https://velog.velcdn.com/images/dev_nana/post/c2bea46f-4fc1-41cf-9c95-5c18fb10aa08/image.png" width="40%" height="60%" ></p>

<h2 id="서론">서론</h2>
<p>맨날 레거시 코드 개편만 하다가 정말 뒤늦게 <code>클린 아키텍처</code>라는 개념을 접하게 되었다. 
내가 하고있는 개발과는 너무나도 다른 방식, 처음 구조 잡는게 어렵지 장점이 훨씬 많은 아키텍처임을 알게되면서 조금 더 자세히 알아보고 싶어졌다. </p>
<p>해당 책은 이미 유명한 개발서적 중 하나로 알려져있는 책으로.. 아키텍처 공부할 때 추천 목록에서 빠지지 않는 책이다. 
얇아서 부담스럽지 않게 읽기 좋은 책인 것 같다. </p>
<h1 id="계층형-아키텍처란">계층형 아키텍처란?</h1>
<p>전통적인 웹 애플리케이션은 <strong>웹계층, 도메인 계층, 영속성 계층</strong>으로 구성되어있다. </p>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/8075de84-e675-4baf-9288-927db6f9b55e/image.png" alt=""></p>
<p>웹 계층에서는 요청을 받아 도메인 혹은 비즈니스 계층에 있는 서비스로 요청을 보낸다. 
서비스에서는 필요한 비즈니스로직 수행, 도메인 엔티티의 현재 상태를 조회하거나 변경하기 위해 영속성 계층의 컴포넌트를 호출한다. </p>
<p><em>잘 만들어진 계층형 아키텍처는 선택의 폭을 넓히고, 변화하는 요구사항과 외부 요인에 빠르게 적응할 수 있게 해준다.</em></p>
<p>필자는 계층형 아키텍처가 코드에 나쁜 습관들이 스며들기 쉽게 만들고, 시간이 지날 수록 소프트웨어를 점점 더 변경하기 어렵게 만드는 수많은 허점을 가지고 있다고 한다. </p>
<h2 id="문제-1-데이터베이스-주도-설계를-유도한다">문제 1. 데이터베이스 주도 설계를 유도한다.</h2>
<p>데이터 베이스의 구조를 먼저 생각하고 이를 토대로 도메인 로직을 구현한 경험이 있을것이다. </p>
<p>위의 이미지대로 모든것은 <strong>영속성 계층</strong>을 토대로 만들어지고 의존성의 방향에 따라 자연스럽게 &#39;도메인 로직&#39;이 아닌 &#39;데이터베이스&#39;를 토대로 아키텍처를 구현해왔다. 
이는 비즈니스 관점에서는 전혀 맞지 않는 방법이며 도메인 로직을 먼저 만들어야한다. 
그래야만 로직을 제대로 이해했는지 확인할 수 있으며, 도메인 로직이 맞다는 것을 확인한 후에 이를 기반으로 영속성 계층과 웹 계층을 만들어야한다.</p>
<p>데이터 베이스 중심적인 아키텍처가 만들어지는 원인은 ORM(Object-Relational-Mapping)프레임워크를 사용하기 때문이다. 
ORM에 의해 관리되는 엔티티들은 일반적으로 영속성 계층에 두고, 계층은 아래 방향으로만 접근 가능하기 때문에 도메인 계층에서는 엔티티에 접근할 수 있다. </p>
<p>이렇게 되면 <strong>영속성 계층과 도메인 계층 사이에 강결합</strong>이 생긴다. 
서비스는 <strong>영속성 모델을 비즈니스 모델처럼 사용</strong>하게 된다.
이로 인해 도메인 로직뿐만 아니라 영속성 계층과 관련된 작업(<code>즉시로딩(eager loading)</code>, <code>지연로딩(lazy loading)</code>, <code>데이터베이스 트랜잭션</code>, <code>캐시 플러시</code>등등..)을 해야한다. </p>
<p>👉🏻 유연하고 선택의 폭을 넓혀준다던 계층형 아키텍처의 목표와 정확히 반대되는 상황!! </p>
<h2 id="문제-2-지름길을-택하기-쉬워진다">문제 2. 지름길을 택하기 쉬워진다.</h2>
<p>전통적인 계층형 아키텍처에서 전체적으로 적용되는 유일한 규칙은, <em>특정 계층에서는 같은 계층에 있는 컴포넌트나 아래에 있는 계층만 접근 가능</em>하다는 것이다. </p>
<p>따라서 상위 계층에 위치한 컴포넌트 접근을 위해선 컴포넌트를 계층 아래로 내리면 되는데 이런식의 개발이 계속되면 영속성 계층은 컴포넌트르 아래 계층으로 내릴수록 비대해진다.</p>
<h2 id="문제-3-테스트하기-어려워진다">문제 3. 테스트하기 어려워진다.</h2>
<p>계층형 아키텍처를 사용할 때 일반적으로 <strong>계층을 건너뛰는 형태</strong>로 변화한다.
하지만 이런 일이 자주 일어난다면 두 가지 문제점이 생긴다. </p>
<ul>
<li>단 하나의 필드를 조작하는 것에 불과하더라도 도메인 로직을 웹 계층에 구현하게 된다. </li>
<li><blockquote>
<p>유스케이스가 확장되면 애플리케이션 전반에 걸쳐 책임이 섞이고 핵심 도메인 로직들이 퍼져나갈 확률이 높음. </p>
</blockquote>
</li>
<li>웹 계층 테스트에서 도메인 계층뿐만 아니라 영속성 계층도 모킹(moking)해야 한다. </li>
<li><blockquote>
<p>단위 테스트의 복잡도가 올라간다. -&gt; 테스트를 전혀 작성하지 않는 방향으로 간다. </p>
</blockquote>
</li>
</ul>
<p>👉🏻 시간이 흘러 웹 컴포넌트의 규모가 커지면 다양한 영속성 컴포넌트에 의존성이 많이 쌓이며 테스트의 복잡도를 높인다. 
실제로 테스트 코드를 작성하는 것 보다 종속성을 이해하고 목(mock)을 만드는 데 더 많은 시간이 걸리게 된다. </p>
<h2 id="문제-4-유스케이스를-숨긴다">문제 4. 유스케이스를 숨긴다.</h2>
<p>개발자들은 기능을 추가하거나 변경할 적절한 위치를 찾는 일이 빈번하기 때문에 <strong>아키텍처는 코드를 빠르게 탐색하는 데 도움</strong>이 되어야 한다. </p>
<p>하지만 계층형 아키텍처에서는 도메인 로직이 여러 계층에 걸쳐 흩어지기 쉽고, 유스케이스가 간단해서 도메인 계층을 생략한다면 웹 계층에 존재할 수도, 도메인 계층과 영속성 계층 모두에서 접근할 수 있도록 특정 컴포넌트를 아래로 내렸다면 영속성 계층에 존재할 수도 있다. 
이럴 경우 새로운 기능을 추가할 적당한 위치를 찾는 일이 어렵다. 넓은 서비스는 영속성 계층에 많은 의존성을 갖게되고, 다시 웹 레이어의 많은 컴포넌트가 이 서비스에 의존하게 된다. </p>
<p>👉🏻 서비스를 테스트하기도 어려워지고 작업해야 할 유스케이스를 책임지는 서비스를 찾기도 어려워진다. </p>
<h2 id="문제-5-동시-작업이-어려워진다">문제 5. 동시 작업이 어려워진다.</h2>
<p>적절한 규모의 프로젝트에서는 인원이 더 투입될 경우 개발이 빨라진다고 생각할 수 있다. 
그렇게 되려면 동시 작업을 지원해야하지만 계층형 아키텍처는 이런 측면에서 도움이 되지 않는다.</p>
<p>ex) 애플리케이션에 새로운 유스케이스를 추가하려고 한다. 
세 명의 개발자가 웹 계층, 도메인 계층, 영속성 계층에 각각 기능을 추가할 수 있을까? </p>
<p>계층형 아키텍처에서는 모든 것이 영속성 계층 위에 만들어지기 때문에 영속성 계층을 먼저 개발해야 하고, 그 다음에 도메인 계층, 마지막으로 웹 계층을 만들어야한다. 
그렇기 때문에 특정 기능은 동시에 한 명의 개발자만 작업할 수 있다. </p>
<p>Q. 인터페이스를 먼저 같이 정의하고 작업하면 되지 않을까? 
A. 물론 가능하지만 데이터베이스 주도설계를 하지 않는 경우에만 가능하다. 
데이터베이스 주도 설계는 영속성 로직이 도메인 로직과 너무 뒤섞여서 각 측면을 개별적으로 작업할 수 없다. </p>
<p>코드에 넓은 서비스가 있다면 서로 다른 기능을 동시에 작업하기 더욱 어렵다. 
같은 서비스를 동시에 편집하는 상황이 발생하면 <code>병합충돌(merge conflict)</code>이 생기고, 잠재적으로 이전 코드로 되돌려야하는 문제를 야기한다. </p>
<hr>
<p>출처 : <a href='https://m.yes24.com/Goods/Detail/105138479'>만들면서 배우는 클린아키텍처 </a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] DTO vs Record / Record를 DTO로 사용하는 이유?]]></title>
            <link>https://velog.io/@dev_nana/Java-DTO-vs-Record-Record%EB%A5%BC-DTO%EB%A1%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@dev_nana/Java-DTO-vs-Record-Record%EB%A5%BC-DTO%EB%A1%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Wed, 01 Jan 2025 08:47:12 GMT</pubDate>
            <description><![CDATA[<p>내가 자바를 배우며 제일 신기했던 점은 데이터 전송하는 방식이 다양했다는 점이었다. 
(다른 언어에도 있는데 굳이 신경쓰지 않았던 것일 수도..) 
Java애플리케이션의 다양한 계층간, 서비스간에 데이터를 전송해야할 때 DTO를 사용하는데, Java14부터 새로운 기능인 <code>Record</code>가 도입되었다. 
이 둘의 차이점과 사용법을 알아보고 어떤 것이 더 적합한지 알아보자. </p>
<h1 id="dto">DTO</h1>
<h2 id="dtodata-transfer-object란">DTO(Data Transfer Object)란</h2>
<p>이름 그대로 데이터를 전송하는 객체이다. 
<em>복잡한 동작이나 로직 없이 데이터를 담기 위한 간단한 객체</em>이며, 
이 객체의 역할은 데이터를 묶어서 필요한 곳에 전달하는 것이다. </p>
<ul>
<li>애플리케이션의 계층 간에 데이터를 운반하는 컨테이너 </li>
<li>비즈니스 로직이나 복잡한 동작이 없고, 그저 데이터를 담고있는 역할만 한다. </li>
</ul>
<h2 id="dto-구현요소">DTO 구현요소</h2>
<ul>
<li>데이터를 담는 private 필드 </li>
<li>데이터 접근, 수정을 위한 Getter/Setter메서드 </li>
<li>객체를 생성하는 생성자(Constructor) </li>
<li>객체 비교, 출력시 유용한 <code>toString()</code>, <code>hashCode()</code>, <code>equals()</code>메서드 재정의(Override) </li>
</ul>
<blockquote>
<p><strong>Lombok(롬복)</strong>같은 도구를 사용하면 반복적인 코드 작성 않고도 완전한 기능을 갖춘 DTO를 만들 수 있다. 
하지만 Record는 기본적으로 <em>불변성(immutability)</em>를 가지며, 반복적인 코드를 제거하는 또 다른 방식을 제공한다. </p>
</blockquote>
<h2 id="구현-예제">구현 예제</h2>
<pre><code class="language-java">import java.util.Objects;

public class UserDTO {
    private String username;
    private String password;

    //생성자
    public UserDTO(String username, String password) {
        this.username = username;
        this.password = password;
    }
    //getter, setter
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }

    //equals재정의
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        UserDTO userDTO = (UserDTO) o;
        return Objects.equals(username, userDTO.username);
    }

    //toString재정의
    @Override
    public String toString() {
        return &quot;UserDTO{&quot; +
                &quot;name=&#39;&quot;+ username +
                &quot;password = &quot; + password +&#39;}&#39;;
    }

    //hashCode()재정의 
    @Override
    public int hashCode() {
        return Objects.hash(username);
    }
}
</code></pre>
<h2 id="사용-방법">사용 방법</h2>
<pre><code class="language-java"> public static void main(String[] args) {
        UserDTO userDTO = new UserDTO(&quot;nana&quot;, &quot;password123&quot;);

        //데이터 접근 
        System.out.println(userDTO.getUsername());
        System.out.println(userDTO.getPassword());

        //toString() 
        System.out.println(userDTO);

        //비교 
        UserDTO userDTO2 = new UserDTO(&quot;nana&quot;, &quot;password1234&quot;);
        System.out.println(userDTO.equals(userDTO2));

    }</code></pre>
<h2 id="커스터마이징customiztion">커스터마이징(Customiztion)</h2>
<p>DTO에서는 데이터 유효성 검사, 데이터 변환 메서드 또는 비즈니스 로직을 추가할 수 있다. 
예를 들어, password필드가 유효한 형식인지 확인하는 검증 메서드를 추가할 수 있다. </p>
<blockquote>
<p>Record는 커스터마이징이 제한적이다. 
가볍고 불변성을 유지하도록 설계되었기 때문에 내부 상태를 수정하거나 복잡한 로직을 쉽게 추가할 수 없다. 
데이터 객체에 커스터마이징된 동작이나 로직이 필요하다면 DTO가 더 유연한 선택이다. </p>
</blockquote>
<h1 id="record">Record</h1>
<h2 id="record란">Record란</h2>
<ul>
<li>Java14에서 도입된 기능 </li>
<li>반복적으로 해야하는 많은 작업들을 자동으로 처리해준다. <ul>
<li>Getter 자동 생성 </li>
<li>동등성 검사(equals)</li>
<li>toString()메서드 처리 </li>
</ul>
</li>
</ul>
<h2 id="주요-특징">주요 특징</h2>
<ul>
<li>불변성 : Setter를 통해 데이터를 변경할 수 있는 DTO와는 다르게 한번 Record를 만들면 데이터를 변경할 수 없다. </li>
<li>간결한 문법 : 필드만 선언하면 Java가 자동으로 생성자, Getter, equals(), hashCode(), toString()메서드를 만들어줌. </li>
<li>Setter없음 : 불변객체이므로 Setter메서드를 제공하지 않는다. </li>
</ul>
<h2 id="구현-예제-1">구현 예제</h2>
<pre><code class="language-java">public record UserRecord(String userName, String password){

    public UserRecord{
        validate(userName, password);
    }
}</code></pre>
<ul>
<li>생성자 사용 시 파라미터 생략 가능, 초기화 로직은 마지막에 자동으로 호출(this.userName = userName)</li>
</ul>
<h2 id="사용-방법-1">사용 방법</h2>
<pre><code class="language-java">public static void main(String[] args) {
        UserRecord userRecord = new UserRecord(&quot;nana&quot;, &quot;password123&quot;);

        //데이터 접근 
        System.out.println(userRecord.userName());
        System.out.println(userRecord.password());

        //toString()사용 
        System.out.println(userRecord);

        //객체 비교 
        UserRecord userRecord1 = new UserRecord(&quot;nana&quot;, &quot;password123&quot;);
        System.out.println(userRecord.equals(userRecord1));
    }</code></pre>
<h2 id="👉🏻-왜-record를-사용할까">👉🏻 왜 Record를 사용할까?</h2>
<h3 id="불필요한-코드boilerplate-code-감소">불필요한 코드(<code>Boilerplate Code</code>) 감소</h3>
<blockquote>
<p>Boilerplate Code란?
컴퓨터 프로그래밍에서 보일러플레이트 또는 보일러플레이트 코드라고 부르는 것은 최소한의 변경으로 여러곳에서 재사용되며, 반복적으로 비슷한 형태를 띄는 코드를 말한다.</p>
</blockquote>
<h3 id="불변성-보장">불변성 보장</h3>
<p>객체 생성 이후 데이터 변경을 못하므로 다중 스레드 환경에서 더 안전하게 사용할 수 있다. </p>
<h3 id="명확한-의도">명확한 의도</h3>
<p>Record를 사용하면 해당 객체가 추가적인 동작이나 로직 없이 데이터를 전달하기 위한 것임을 분명하게 알 수 있다. </p>
<h3 id="데이터-표현방식이-깔끔">데이터 표현방식이 깔끔</h3>
<p>데이터를 간결하고 직관적으로 표현한다. Record    선언에는 필드만 포함되므로 코드가 더 깔끔하고 읽기 쉽다. <em>특히 데이터 모델이 많은 프로젝트에서 유지보수가 용이하다</em> </p>
<h3 id="함수형-프로그래밍과의-연관성">함수형 프로그래밍과의 연관성</h3>
<p>함수형 프로그래밍의 핵심 원칙 중 하나인 불변성(Immutability)과 잘 맞다. 
불변 데이터 객체를 사용하고자 하는 시스템에 적합하다. </p>
<h1 id="👉🏻-dto-vs-record-언제-사용해야할까">👉🏻 DTO vs Record, 언제 사용해야할까?</h1>
<h2 id="dto-사용해야할-때">DTO 사용해야할 때</h2>
<h3 id="1-데이터-수정이-필요한-경우">1. 데이터 수정이 필요한 경우</h3>
<p>객체의 수명 주기 동안 데이터가 계속해서 업데이트 되는 상황 
ex) 웹앱에서 사용자 등록 양식을 통해 일부 필드가 비어있는 상태로 UserDTO가 생성됨. 
사용자가 프로필을 업데이트 하면서 UserDTO들의 값이 변경되어야 하는 경우. </p>
<h3 id="2-추가적인-동작이나-검증-로직이-필요한-경우">2. 추가적인 동작이나 검증 로직이 필요한 경우.</h3>
<p><strong>데이터 객체가 단순히 데이터를 전달하는 것 이상으로 동작해야할 때</strong>, 예를들어 이메일 형식을 확인하거나 입력을 정제하는 로직이 필요할 떄. 
=&gt; DTO는 검증, 변환, 추가 메서드 등 맞춤형 동작을 추가하는 데 더 유연하다. </p>
<h3 id="3-java16-이전-버전과의-호환이-필요한-경우">3. Java16 이전 버전과의 호환이 필요한 경우.</h3>
<p>Java16 이전 버전의 프로젝트는 Record를 사용할 수 없다. 
이런 경우에는 DTO나 Lombok과 같은 대안으로 코드를 간소화 해야한다. </p>
<h2 id="record를-사용해야-할-때">Record를 사용해야 할 때</h2>
<h3 id="1-간결하고-불변성을-가진-데이터-전달-객체가-필요한-경우">1. 간결하고 불변성을 가진 데이터 전달 객체가 필요한 경우</h3>
<p>Record는 가볍고 불변성을 가진 객체로 데이터를 전달해야할 때 이상적이다. </p>
<p>마이크로 서비스 아키텍쳐(MSA)에서 서비스 간 데이터를 전달할 때 데이터를 수정할 필요가 없다면 Record사용이 유의미하다. </p>
<h3 id="2-읽기-전용-데이터-전송이-필요한-경우">2. 읽기 전용 데이터 전송이 필요한 경우</h3>
<p>데이터를 전달하기만 하고 수정할 필요가 없을 경우. 
불변성을 보장하여 데이터 일관성을 유지하므로, 데이터베이스에서 서비스 계층으로 또는 서비스간 데이터를 전달하는 데 적합하다. </p>
<h3 id="3-최신-javajava-16-이상">3. 최신 Java(Java 16 이상)</h3>
<p>최신Java 애플리케이션에서 데이터 표현을 간소화 하도록 설계되었기 때문에 기존 DTO가 가지고 있던 불필요한 반복적인 코드를 줄이는 데 도움이 된다. </p>
<h2 id="record의-한계">Record의 한계</h2>
<p>** 확장이 어렵다.**
extends를 사용하여 다른 클래스를 상속할 수 없고, 필드가 final로 선언되기 때문. 
주로 데이터를 전달하려는 목적으로 설계되었기 때문에 비즈니스 로직을 포함하기에 적절하지 않다. 
Java14, 16 이전 버전에서는 호환이 불가능하다. </p>
<h2 id="-record-vs-vo">+ Record vs VO</h2>
<p>Record와 VO 모두 객체의 상태가 변경되지 않는 것을 보장한다. 
또 데이터를 캡슐화하여 표현하는 데 초점을 맞춘다. 
VO는 값 기반의 동등성을 가지며, Record도 동일한 필드 값을 가지면 동일한 객체로 간주된다는 점이 공톰점이다. </p>
<p>VO는 도메인 모델내에서 특정 개념을 표현하고, 도메인 로직과 밀접하게 관련이 있다. 
VO는 비즈니스 로직이나 규칙을 가질 수 있으나 Record는 단순히 데이터를 캡슐화하여 전달하는데 의미가 있다. </p>
<p>=&gt; Record는 VO를 구현하는 데 적합하지만, VO의 모든 특성을 완벽히 대체하지는 않는다. 
VO는 더 넓은 도메인 맥락에서 사용되고, 비즈니스 로직을 포함할 수 있다. </p>
<h1 id="성능-고려사항">성능 고려사항</h1>
<h2 id="메모리-효율성">메모리 효율성</h2>
<p>Record가 설계상 간결하므로 DTO보다 메모리를 조금 덜 사용할 수 있다. </p>
<h2 id="불변성과-스레드-안정성">불변성과 스레드 안정성</h2>
<p>Record는 불변이기 때문에 스레드 간에 공유될 때 동기화나 잠금(locking)메커니즈이 필요하지 않다. 
스레드 간 경쟁(thread contention)으로 성능이 저하되는 상황에서 성능을 향상할 수 있다. </p>
<p>반면, 가변(mutable) DTO를 멀티 스레드 환경에서 사용할 경우, 스레드 안정성을 보장하기 위해 접근을 동기화하거나 다른 메커니즘을 사용해야 하므로 성능에 추가적인 부담이 생기고 애플리케이션이 느려질 수 있다. </p>
<h2 id="가비지-컬렉션">가비지 컬렉션</h2>
<p>DTO와 Record 모두 일반적인 Java객체(POJO, Plain old Java Object)이므로 동일한 가비지 컬렉션 처리에 따라 관리된다. 
하지만 Record가 더 간결하니 메모리에 적은 객체가 생성, 유지 될 수 있어 가비지 컬렉션이 조금 더 빠르게 이루어질 수 있다. 
=&gt; 대량의 데이터 객체를 처리하는 장기 실행 애플리케이션에서 <strong>성능 향상에 기여</strong>할 수 있다. </p>
<h2 id="cpu-오버헤드">CPU 오버헤드</h2>
<p>Record는 컴파일러에 의해 자동 생성 되며, 성능을 최적화하도록 설계되어 있어, 객체 생성, 메서드 호출, 비교작업에서 CPU성능이 조금 더 향상될 수 있다. 
복잡한 DTO는 수동으로 구현된 메서드에서 비효율성이 발생할 수 있으나, Record는 일관되고 최적화된 방식으로 작업을 처리하므로 더 효율적이다. </p>
<h2 id="실제-성능">실제 성능</h2>
<p>DTO와 Record간의 성능 차이는 대부분 애플리케이션에서 매우 적거나 무시할만한 수준이다. 
대용량 데이터 처리, 높은 처리량을 요구하는 애플리케이션, 또는 리소스가 제한된 환경에서만 눈에 띌 정도의 성능 차이를 경험할 수 있다. </p>
<h1 id="추가-내용">추가 내용</h1>
<h2 id="왜-레코드는-getxxx가-아닐까">왜 레코드는 getXXX가 아닐까?</h2>
<ul>
<li>레코드를 선언할 때 간결성을 제공하지만 <em>보일러 플레이트와의 전쟁</em>을 선언하는 것은 목표가 아님. </li>
<li>Java Bean 명명 규칙을 사용하는 일반클래스의 문제를 해결하는 것은 목표가 아님. </li>
</ul>
<h2 id="lombok을-사용하면-안될까">Lombok을 사용하면 안될까?</h2>
<ul>
<li>가독성 측면에서도 좋음 </li>
<li>롬복은 별도의 라이브러리나 외부 플러그인을 설치해 줘야한다. </li>
</ul>
<h2 id="record를-도메인-객체에-사용할-수-있을까">Record를 도메인 객체에 사용할 수 있을까?</h2>
<p>레코드는 <strong>불변 데이터를 전달하기 위한 캐리어</strong>이기 때문에 <strong>단순한 값의 집합</strong>을 표현하는 객체 지향적인 구성을 고안한다. 
개발자가 확장 가능한 동작보다는 <strong>불변의 데이터 모델링</strong>에 집중할 수 있도록 도와준다. </p>
<h3 id="💁🏻-마치며">💁🏻 마치며..</h3>
<p>과제를 하면서 레코드 사용한 부분에 대해 좋은 피드백을 받았던 적이 있는데 사실 왜 잘 사용한건지 몰랐다. 
근데 해당 내용을 공부하면서 왜 DTO가 아닌 Record사용으로 좋은 피드백을 받았던 것인지 이해가 되었다 :) </p>
<hr>
<p><a href='https://blog.ashutoshkrris.in/dto-vs-record-in-java-which-should-you-use'>DTO vs Record in Java: Which Should You Use?</a>
<a href='https://yozm.wishket.com/magazine/detail/2814/'>자바 DTO vs Record, 무엇을 사용해야 할까?</a>
<a href='https://www.youtube.com/watch?v=MiHxFpTgAog'>[10분 테코톡] 타칸의 Record</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[항해플러스 백엔드6기] ⛵물경력의 늪에서 헤엄치기/항해 추천,비추천 이유]]></title>
            <link>https://velog.io/@dev_nana/%ED%95%AD%ED%95%B4%ED%94%8C%EB%9F%AC%EC%8A%A4-%EB%B0%B1%EC%97%94%EB%93%9C6%EA%B8%B0-%EB%AC%BC%EA%B2%BD%EB%A0%A5%EC%9D%98-%EB%8A%AA%EC%97%90%EC%84%9C-%ED%97%A4%EC%97%84%EC%B9%98%EA%B8%B0%ED%95%AD%ED%95%B4-%EC%B6%94%EC%B2%9C%EB%B9%84%EC%B6%94%EC%B2%9C-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@dev_nana/%ED%95%AD%ED%95%B4%ED%94%8C%EB%9F%AC%EC%8A%A4-%EB%B0%B1%EC%97%94%EB%93%9C6%EA%B8%B0-%EB%AC%BC%EA%B2%BD%EB%A0%A5%EC%9D%98-%EB%8A%AA%EC%97%90%EC%84%9C-%ED%97%A4%EC%97%84%EC%B9%98%EA%B8%B0%ED%95%AD%ED%95%B4-%EC%B6%94%EC%B2%9C%EB%B9%84%EC%B6%94%EC%B2%9C-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Mon, 09 Dec 2024 14:14:31 GMT</pubDate>
            <description><![CDATA[<h1 id="0-who-am-i">0. Who am I?</h1>
<p>지방대 4년제 컴퓨터 공학과 출신. 
왜 컴퓨터공학과로 갔냐고? ⭐간지나잖아요⭐
이유가 어떻던 간에 괜찮은 성적으로 졸업했고, 학교 선배가 다니는 회사에 들어가게 되면서 학부생 때는 배워보지도 못한 c#이란 언어를 시작하게되었다.</p>
<p>더 자세한 내용은 <a href='https://velog.io/@dev_nana/%ED%95%AD%ED%95%B4%ED%94%8C%EB%9F%AC%EC%8A%A4-%EB%B0%B1%EC%97%94%EB%93%9C6%EA%B8%B0WIL-%ED%95%AD%ED%95%B4-%ED%94%8C%EB%9F%AC%EC%8A%A4-5%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0-%ED%95%AD%ED%94%8C-%EC%B6%94%EC%B2%9C%EB%B9%84%EC%B6%94%EC%B2%9C-%EC%86%94%EC%A7%81-%ED%9B%84%EA%B8%B0Feat.-%EB%82%98%EB%8A%94-%EC%96%BC%EB%A7%88%EB%82%98-%EC%84%B1%EC%9E%A5%ED%96%88%EC%9D%84%EA%B9%8C'>여기</a>에 있다. </p>
<h1 id="1-부트캠프를-결심한-이유">1. 부트캠프를 결심한 이유</h1>
<h2 id="연차에-비해-부족한-기본기">연차에 비해 부족한 기본기</h2>
<p>어떠한 분야라도 연차마다 기대하는 능력치가 있다. 
보통의 3-5년차의 백엔드 개발자에게 요구하는 역량은 <code>트러블슈팅</code>, <code>테스트코드</code>, <code>대용량 처리 경험</code>, <code>프로그래밍 언어 기본 지식</code>등등.. 주니어개발자들이 알아야 하는 지식이 산더미처럼 쌓여 있으나 나는 &#39;굳이 이걸 왜 알아야해? 회사에서 안쓰는데?&#39; 라는 생각을 해왔다. </p>
<img src="https://velog.velcdn.com/images/2000sdh/post/88c49265-bc4b-4dc5-bb10-d132ecfe8cd0/image.png">

<p>더군다나 국내에서 비주류인 언어를 사용하고 있기 때문에 언제든 이직하고자 하면 할 수 있을거라고 생각했다. </p>
<p>그러나 개발자 붐은 사그라들었고 취업시장은 얼어붙으면서 내가 설 수 있는 자리는 더더욱 좁아졌고 살아남기 위해선 공부를 해야했다. </p>
<p>그러나 안일하게 보냈던 5년이 부메랑이 되어 돌아와서 손 쓸수가 없었고 <em>여전할 것인가, 역전할 것인가</em>의 기로에 서 있었다. </p>
<h2 id="crud만-반복하는-업무에서-오는-매너리즘">CRUD만 반복하는 업무에서 오는 매너리즘</h2>
<p>보통 이직을 결심하는 시기가 1,3,5년차로 온다고 하는데 일에도 적응이 되었고 더이상 회사에서 하는 일이 재미있지가 않았다. 
그도 그럴것이 변화를 두려워하고 주먹구구식으로 일하고, 신기술을 익히는 것 보다 새로운 상품만 빨리빨리 찍어내는 형식의 업무들로 인해 &#39;여기서 내가 더 성장할 수 있을까?&#39;라는 의문이 들기 시작했다. </p>
<h1 id="2-왜-항플이어야했을까">2. 왜 항플이어야했을까?</h1>
<h2 id="해보지-못한-것들을-해본다는-것">해보지 못한 것들을 해본다는 것.</h2>
<p><code>TDD</code>,<code>클린아키텍처</code>, <code>레이어드아키텍처</code>, <code>Redis</code>, <code>Kafka</code>,, 말만 들어봤지 실제로 써보지도 못했고, 정확히 뭔지도 몰랐다. </p>
<p>현업에서 써볼 수 있나?를 생각해봐도 이미 구축되어있지 않는 이상 주니어 개발자가 신규로 도입한다는 것은 의지와는 상관없이 어려운일이라 생각하고, 개발위주의 탄탄한 회사가 아니라면 더 힘들다 생각했다. </p>
<p>그런데 항플을 하면 10주안에 이 모든것을 경험해 볼 수 있다!
이게 찍어먹는게 될지 범벅으로 떠먹는건지는 본인의 노력여하에 따라 달려있지만. </p>
<h2 id="시니어-코치진의-멘토링">시니어 코치진의 멘토링</h2>
<p>개발씬에서 이름나 있는 회사에 재직중인 시니어 코치분들의 코칭이 궁금했다. 
회사에서는 기술적으로 가르쳐주기보단 업무에 대해 가르쳐 주고 그 업무를 처리하는 방식에 대해서는 아무도 알려주지않았다. </p>
<p>현업에서 일하면서 얻어가는 노하우, 몇년간 노력해서 알게되는 고급 지식들을 코치님들이 떠먹여주신다. </p>
<p><em>(물론 먹여준걸 씹어 삼키는건 내 능력)</em> </p>
<p>이런 기술들을 알게되기까지 얼마나 많은 노력을 하셨을지, 얼마나 파고들었을지가 너무 느껴지는 깊이있는 답변을 들으면서 동기들과 매번 놀랐었다. </p>
<p>너무나도 많은 정보 속에서 어떤걸 먼저 공부해야할지 고민해가던 와중에 <strong>어떻게 공부를 해 나가야할지</strong> 방향성을 제시받은 것 만으로도 많이 얻어간 것이라고 생각한다.</p>
<h2 id="시나리오-기반으로-프로젝트를-고도화해가는-방식">시나리오 기반으로 프로젝트를 고도화해가는 방식.</h2>
<p>모든 이야기엔 &quot;build up&quot;이 필요하다. </p>
<p>1,2주차에는 TDD와 클린아키텍처 구현을 통해 앞으로 해 나갈 프로젝트 구조에 대해 학습하고 3~5주차에는 시나리오를 선택, 그리고 그 구현한 코드를 바탕으로 살을 붙여나가는 방식으로 고도화 해 나간다. </p>
<p>점점 살이 붙어가는 프로젝트를 보면서 막연하게만 느껴지던 프로젝트 고도화를 어떻게 진행해야할지 감을 잡았다. 
간단한 것 부터 적용해 보고 싶다는 생각이 들었다. </p>
<h1 id="3-제대로-헤엄치는-법을-알게되었다🏊🏻♂️">3. 제대로 헤엄치는 법을 알게되었다.🏊🏻‍♂️</h1>
<p>항해를 하기 전의 나는 &#39;헤엄만 칠 줄 아는 아이&#39; 였다. 
여기서 헤엄은, 다들 알고있는 자유형, 평형 등이 아니라 &#39;개헤엄&#39;이었다. 
어떻게든 헤엄쳐서 바다에서 살아가고있긴하나 언제 가라앉아도 이상하지 않은 상태였던것이다. </p>
<h2 id="항해에서-가르쳐-주는-것">항해에서 가르쳐 주는 것</h2>
<p>항해에서는 크루즈에 태워 바다를 건너게 해 주는 것이 아니다.
혼자서 바다에서 헤엄을 칠 수 있게 헤엄치는 법을 알려주고, 방향을 알려준다. 
(그리고 가끔 허우적대면 살려준다..) </p>
<h2 id="항해에서-가르쳐-주지-않는-것">항해에서 가르쳐 주지 않는 것</h2>
<p>*오답은 있으나 정답은 없다. *</p>
<p>이렇게 알고있는건 잘못된 거예요! 라고 잘못된 부분은 정확히 짚어 알려주지만 <em>이런 시각도 있으니 참고해보세요, 이런 방법도 있는데 저는 보통 이런 방법을 선호해요</em> 라는 말을 코치님들이 많이 말씀하신다. </p>
<p>덕분에 본질은 같으나 다른 시각을 가진 시니어 코치님들의 멘토링을 청강하며 견문을 넓혀갈 수 있었다. </p>
<h1 id="4-👁️눈에-띄게-변한-것">4. 👁️눈에 띄게 변한 것.</h1>
<h2 id="1-자가진단">1) 자가진단</h2>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/13c82a36-47f6-4ec8-8e35-6145b1637d12/image.png" alt=""></p>
<p>항해에서는 매주 발제 종료 후 그 주차의 자가진단을 제출하게 한다. 
내가 제대로 알게된게 맞나? 라고 생각이 들 때가 많았는데 정말 아무것도 모르던 처음과 비교하면 얻게된 지식이 많다. 
여기서 좀 더 디테일 하게 체득하는걸 목표로 내년 상반기를 보내볼까한다.  </p>
<h2 id="2-메타인지">2) 메타인지</h2>
<p>사람은 자고로 메타인지가 중요하다했다. 
나의 현 위치를 알아야 발전할 수 있기 때문이다. 
전 회사와 현 회사를 거치면서 나는 우물안 개구리에 불과했다는 것을 인지하기 시작했고, 이 세상 밖에선 다들 치열하게 공부하며 살고있었다. </p>
<p>또, 일찌감치 성장하고싶어하는 또래 혹은 그보다 더 어린 개발자들이 넘쳤다는 것. </p>
<p>내가 이걸 1-2년만 더 빨리 깨달았으면 어땠을까 생각했지만 이미 시간은 흘렀고 더 늦기전에 지금이라도 시작한게 정말 다행이라 생각했다. </p>
<h3 id="🔸이-글을-읽는-당신-고민은-성장만-늦출-뿐입니다-어떻게-해야할지-모르겠다면-일단-시작해보세요">🔸이 글을 읽는 당신! 고민은 성장만 늦출 뿐입니다. 어떻게 해야할지 모르겠다면 일단 시작해보세요.</h3>
<h2 id="3-공부하는-습관과-업무를-대하는-태도">3) 공부하는 습관과 업무를 대하는 태도</h2>
<p>내가 부족하다는 것은 알고있었지만 외면해왔었다. 
공부? 굳이 해야하나? 안해도 회사에선 인정받고 잘 하고있는거같은데? 라는 안일한 태도로 보내왔다. </p>
<p>항해의 주차를 거듭할 수록 내가 보는 시야가 넓어짐과 동시에 늘 하던 업무를 바라보는 시각이 달라졌다. </p>
<p>&quot;여기에서 더 확인할 수 있는 것은 없을까?&quot;
&quot;이걸 반복하지 않으려면 어떻게 바꿔야할까?&quot; 
&quot;여기서 적용시킬 수 있는 기술들은 어떤게 있을까?&quot; 
등 정말 고착화된 사고에서 유연한 사고로 바뀌어갔다. </p>
<p>그리고 집에 와서 공부는 커녕 운동 &gt; 저녁 &gt; 누워서 도파민 충전 으로 하루를 끝내던 내가 책상앞에 며칠을 밤을 새며 앉아있는 모습을 보면서 스스로가 대견하기도 하고 뿌듯하기도 했다. </p>
<h2 id="4-번외">4) 번외..</h2>
<p>놀기 좋아하고 친구 좋아하던 내가 공부하느라 친구도 못 만나고 공부한다고 하니 주변에서 나를 보는 시각이 달라졌다. 그리고 눈빛이 달라졌다한다. </p>
<p>꿈이 있는자와 없는자의 눈빛은 확실히 다르다는 것을 나를 보며 또 한번 느꼈다.
매일 밤새며 과제를 해서 피곤해도 목표를 향해 달려나가는 나.. 좀 멋있게 느껴졌다. </p>
<p>더 멋있게 변할 내가 기대된다. </p>
<h1 id="5-그-무엇보다-값진-6️⃣기-그리고-8️⃣팀❤️">5. 그 무엇보다 값진 6️⃣기, 그리고 8️⃣팀❤️</h1>
<p>10주라는 길다면 길고 짧다면 짧은.. 
정말 즐겁기도 하고 고통스러운 두달 반이라는 시간을 함께 버티게 해 준 6기, 그리고 8팀..
<img src="https://velog.velcdn.com/images/dev_nana/post/7a4619fe-ca6c-48d6-86c4-360e49ca110f/image.png" alt="">
본인들도 모르지만 최대한 서로 알려주려고 하고 아는건 최대한 나누려고하며 좋은 정보 또한 공유하는 강력한 커뮤니티가 생긴게 너무 좋았다. </p>
<p>그리고 개발얘기 하며 신나하는 그들을 보며 내가 있던 개발집단과는 다르단 생각에 신선하기도 했다. </p>
<p>막판에 야근이 몰리며 포기하고 싶을 때가 많았지만 서로 격려하고 북돋아주며 의지해서 무사히 10주를 마무리 할 수가 있었다.</p>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/f28f7a9a-7a97-4863-9155-4d00adbdfd47/image.png" alt=""></p>
<p>이 글을 빌려 무사히 수료하게 해 준, 어쩌면 그냥 스쳐 지나갔을 지도 모르는 나와 함께해준 모든 분들께 감사합니다. ❤️</p>
<h1 id="6-항해플러스-다음-기수가-될-예정이신가요📬">6. 항해플러스 다음 기수가 될 예정이신가요?📬</h1>
<ul>
<li><p>겁? 날 수 있어요. </p>
</li>
<li><p>모르는거? 당연합니다. 알면 코치하지 왜 돈 주고 수강생이 되겠어요?</p>
</li>
<li><p>질문? 계속하세요. 상대방이 귀찮을지언정 그런거 하려고 돈주고 듣는거니까요. </p>
</li>
<li><p>그 주의 과제는 제출하지 못하더라도 개념은 꼭 익히고가세요. -&gt; 그렇지 않으면 다음 주차가 괴로워질 것입니다.</p>
</li>
<li><p>동기와의 연대? 다다익선입니다. 나중에 어떻게 만날지 모르니까요 :) </p>
</li>
</ul>
<h2 id="이런분들에게는-비추천합니다">이런분들에게는 비추천합니다.</h2>
<ul>
<li>일이 너무 많으신 분들. &gt; 체력적으로 너무 힘듭니다. 
번아웃 온 동기들도 많이 봤고, 뒤엔 포기하고 다음 기수로 넘긴 분들도 많이 봤습니다. </li>
<li>혼자서 학습을 못 하는 분들 &gt; 코치님들이 떠먹여주는 것 까진 해도 씹어주진 않습니다. 받아먹기만 하면 되는 형태를 기대하신거라면 항해와는 맞지 않아요! (그리고 그런 부트캠프는 어디에도 없습니다.)</li>
</ul>
<h2 id="⭐-여전할-것인가-역전할-것인가">⭐ 여전할 것인가. 역전할 것인가.</h2>
<p>어떤걸 선택하실건가요?</p>
<hr>
<h1 id="👉🏻항해플러스에-관심이-있으시다면">👉🏻항해플러스에 관심이 있으시다면?</h1>
<p>항해 플러스에 관심이 있다면 추천인 코드 <span style="color : #39e5b3">G2ZAL7</span>  를 넣어주시면 등록금 할인혜택이 주어져요..!!!
추천인 코드 넣고 할인 꼭 받아가세요!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[간단한 E-Commerce 프로젝트를 통해 알아보는 장애대응]]></title>
            <link>https://velog.io/@dev_nana/%EA%B0%84%EB%8B%A8%ED%95%9C-E-Commerce-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A5%BC-%ED%86%B5%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-%EC%9E%A5%EC%95%A0%EB%8C%80%EC%9D%91</link>
            <guid>https://velog.io/@dev_nana/%EA%B0%84%EB%8B%A8%ED%95%9C-E-Commerce-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A5%BC-%ED%86%B5%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-%EC%9E%A5%EC%95%A0%EB%8C%80%EC%9D%91</guid>
            <pubDate>Tue, 26 Nov 2024 15:41:46 GMT</pubDate>
            <description><![CDATA[<h1 id="0-장애대응이-왜-필요할까">0. 장애대응이 왜 필요할까?</h1>
<h2 id="🙅🏻장애란">🙅🏻장애란?</h2>
<p>특정 상황이 발생해 현재 운영 중인 서버가 정상 작동이 불가능한 상태. </p>
<ul>
<li><code>Lack of Resource</code> : 메모리 누수 및 OOM(Out of Memory Error)로 인한 APP 비정상 종료 </li>
<li><code>Unhandled Exception</code> : 처리되지 않은 Exception으로 인한 APP Cash </li>
<li><code>Slow Query, I/O</code> : 응답 지연으로 이어지는 최적화 되지 않은 작업 </li>
</ul>
<p>이 외에도 예측불가한 상황과 유저의 행동 또는 환경들로 인해 언제든 발생할 수 있는게 장애이다. </p>
<p>장애가 지속 ➡️ 사용자의 불편 초래 ➡️ 서비스 이탈❗로 이루어 지기 때문에 <em>장애를 모두 막을 순 없지만 일어난 장애에 대해 빠르게 대응해야한다.</em> </p>
<h2 id="장애-감지는-어떻게-할까">장애 감지는 어떻게 할까?</h2>
<h3 id="모니터링-및-로깅중인-지표">모니터링 및 로깅중인 지표</h3>
<ol>
<li>서버의 리소스(Latency, Disk) 등의 지표를 확인한다. </li>
<li>데이터를 올바르게 파싱하지 못하는 경우에 로깅 및 알림을 받는다. </li>
<li>에러의 빈도가 잦은 경우에 알림을 받는다. </li>
<li>모든 Request, Response를 로깅한다. </li>
</ol>
<h3 id="중앙화된-로거-및-로깅-규칙">중앙화된 로거 및 로깅 규칙</h3>
<ul>
<li>Health Checker </li>
<li>Datadog </li>
<li>OpenSearch</li>
<li>Slack Alert Bot </li>
</ul>
<p>일전에 갑작스럽게 트래픽이 몰렸을 때를 대비한 작업들(<code>인덱스</code>, <code>캐시</code>, <code>카프카</code> 등.. )이 정말 트래픽을 견딜 수 있는지 미리 확인하기 위해서는 <strong>부하테스트</strong>를 해봐야한다. </p>
<h1 id="1-부하테스트는-어떻게-할까">1. 부하테스트는 어떻게 할까?</h1>
<h2 id="🔥부하테스트의-목적">🔥부하테스트의 목적</h2>
<ul>
<li>장애를 유발할 수 있는 테스트 시나리오를 작성한다.</li>
<li>미리 서버의 성능에 대해 목표를 정하고 다음과 같은 상항들에 대해 점검한다. <blockquote>
<p>1) 예상 TPS(Transaction Per Second) 
2) 평균/중간/최대 응답시간 </p>
</blockquote>
</li>
<li>평균 응답시간? : 
3) 다량의 트래픽 유입 시 동시성 이슈 발생 여부 </li>
</ul>
<p>위 사항들을 점검하고 목표치를 달성하지 못 하거나 기대치에 못 미치는 경우 <em>원인을 분석하고 성능 개선을 진행</em>한다. </p>
<h2 id="부하테스트의-대상">부하테스트의 대상</h2>
<p>우리 서비스에서 제공하는 전체 API를 나열해 보고, 각각의 목표 TPS를 대략적으로 작성한다. 
목표 TPS를 활용하여 User, Response Time등을 설정해서 시나리오를 만들어 본다. </p>
<p>특수한 트래픽을 처리하기 위한 기능, 동시성 이슈를 고려한 기능 등 어떤 유형의 부하가 주어졌을 때, 기능이 예측과 같이 동작하는지 혹은 너무 낮은 성능을 보이고 있지는 않은지 등을 점검한다. </p>
<h3 id="테스트-시나리오-설계">테스트 시나리오 설계</h3>
<ul>
<li>예상 <strong>병목지점</strong>에 대해 고려하고 시나리오를 설계한다. </li>
<li>Db조회 등 연산이 무거운 API에 대해 <strong>Slow Query</strong>를 확인하는 시나리오를 생각해 본다. </li>
</ul>
<h2 id="성과-측정">성과 측정</h2>
<p>부하 테스트로 다음의 지표를 수집한다. </p>
<ul>
<li>평균 응답 시간(Response Time)</li>
<li>처리량(Throughput)</li>
<li>실패율(Error Rate)</li>
<li>최대 동시 사용자 수 (Concurrent Users)</li>
</ul>
<h1 id="2-부하-테스트-대상-선정하기">2. 부하 테스트 대상 선정하기.</h1>
<h2 id="🛒e-commerce에서-발생할-수-있는-성능-문제">🛒E-Commerce에서 발생할 수 있는 성능 문제.</h2>
<p>이커머스에서 갑자기 트래픽이 몰리는 사례</p>
<h3 id="1-프로모션-및-할인-이벤트🏷️">1. 프로모션 및 할인 이벤트🏷️</h3>
<p>특정 시간 동안 할인을 제공하거나, 한정된 수량의 상품을 판매하는 이벤트를 진행한다. </p>
<ul>
<li>트래픽 급증으로 인해 서버 과부하 발생. </li>
<li>장바구니 추가, 주문, 결제 API요청이 집중됨. </li>
<li>DB에서 상품 재고 업데이트와 관련된 <strong>동시성 이슈</strong></li>
</ul>
<h3 id="2-신상품-출시">2. 신상품 출시</h3>
<p>인기가 많은 브랜드나 카테고리에서 새롭게 출시된 상품을 구매하려는 고객이 몰림. </p>
<ul>
<li>상품 조회 API가 집중적으로 호출됨. </li>
<li>빠른 품절로 인한 재고 관련 트랜잭션 처리 증가 </li>
<li>특정 시간대에 높은 동시성 요청 발생.</li>
</ul>
<h3 id="3-특정-시즌">3. 특정 시즌</h3>
<p>ex) 블랙프라이데이, 크리스마스, 설날, 추석 등 쇼핑 수요가 높은 시기. </p>
<ul>
<li>특정 시간, 기간에 집중적으로 트래픽이 증가. </li>
<li>장시간 고부하 상태가 지속될 가능성이 있음. </li>
<li>고객의 주문 및 결제 실패로 인해 CS증가. </li>
</ul>
<h3 id="4-한정판-상품-판매">4. 한정판 상품 판매</h3>
<p>수량이 제한된 한정판 상품 특정시간에 판매. </p>
<ul>
<li>트래픽이 판매 시작 직전, 시작 후 몇 분간 최고조에 도달. </li>
<li>특정 상품의 재고 조회, 주문 API가 집중 호출. </li>
<li>실패한 요청 재시도로 인한 요청 폭팔. </li>
</ul>
<h3 id="5-바이럴-효과">5. 바이럴 효과</h3>
<p>특정 상품이 소셜 미디어, 뉴스 등을 통해 갑작스럽게 유명해짐. </p>
<ul>
<li>예측이 불가하다 &gt; 유명 인플루언서의 라이브 방송 소개로 인해 갑작스럽게 찾게될 수 있음. </li>
<li>해당 상품 조회, 주문 API요청 폭발.</li>
<li>전체 시스템의 자원이 특정 상품에 집중될 수 있음. </li>
</ul>
<h3 id="6-시스템-장애-복구-직후">6. 시스템 장애 복구 직후</h3>
<p>장애로 인해 중단된 서비스를 복구한 직후에 요청이 한꺼번에 몰림. </p>
<ul>
<li>복구 후 밀렸던 요청이 한꺼번에 처리되어 부하 발생. </li>
<li>대기 상태였던 주문 및 장바구니 관련 요청 증가. </li>
</ul>
<h2 id="내-e-commerce-프로젝트에서-가능한-부하테스트-대상">내 e-commerce 프로젝트에서 가능한 부하테스트 대상.</h2>
<ul>
<li><p>포인트 조회 : 대량 사용자 요청 시 성능 확인. 
➡️ 상품 주문 시 현재 가지고 있는 포인트 조회를 하게된다. 
1초에 100명의 사용자가 포인트를 조회할 경우. </p>
</li>
<li><p>상품 주문 : 주문 시 동시성 문제 및 재고에 대한 데이터 일관성 확인 
➡️100명의 사용자가 동시에 같은 상품을 주문할 경우에 대한 동시성 제어 </p>
</li>
<li><p>같은 상품 조회 : 높은 조회 빈도에 대한 캐싱
➡️ 500명이 1초에 10번씩 조회 </p>
</li>
</ul>
<h1 id="3-트래픽-집중-시-발생하는-문제--대응-방안">3. 트래픽 집중 시 발생하는 문제 &amp; 대응 방안</h1>
<h2 id="발생하는-문제">발생하는 문제</h2>
<ol>
<li>서버 과부하 : 응답시간이 느려지거나 다운됨. </li>
<li>동시성 문제 : 재고 감소 또는 포인트 차감 시 데이터 불일치 발생. </li>
<li>데이터베이스 병목 : 읽기/쓰기 요청이 몰리면서 성능 저하. </li>
<li>캐시 부족 : 트래픽 폭증 시 캐시 용량 초과로 인한 DB과부하</li>
<li>사용자 경험 저하 : 주문 실패, 페이지 로드 지연, 장바구니 추가 실패 등 </li>
</ol>
<h2 id="대응-방안">대응 방안</h2>
<ol>
<li>트래픽 예측</li>
</ol>
<ul>
<li>이벤트 전 트래픽 증가율을 예측하여 인프라 확장 계획.</li>
<li>APM 도구를 활용해 과거 이벤트 데이터를 분석.</li>
</ul>
<ol start="2">
<li>캐싱 사용</li>
</ol>
<ul>
<li>상품 조회, 카테고리 리스트에 캐싱 적용 (예: Redis).</li>
<li>캐시를 활용해 DB 접근 횟수 감소.</li>
</ul>
<ol start="3">
<li>오토스케일링</li>
</ol>
<ul>
<li>클라우드 환경에서 트래픽에 따라 자동으로 서버 확장.</li>
<li>CPU 사용량 기준으로 동적으로 조정.</li>
</ul>
<ol start="4">
<li>큐잉 시스템</li>
</ol>
<ul>
<li>RabbitMQ, Kafka 등을 활용해 주문 요청을 큐에 적재하여 처리 속도를 제어.</li>
</ul>
<ol start="5">
<li>리소스 제한</li>
</ol>
<ul>
<li>Docker에서 리소스 제한을 설정해 이벤트 동안 안정적으로 서비스 제공.</li>
</ul>
<ol start="6">
<li>로드 밸런싱</li>
</ol>
<ul>
<li>트래픽을 여러 서버로 분산.</li>
</ul>
<h1 id="4-k6를-통한-부하테스트">4. K6를 통한 부하테스트</h1>
<h3 id="주요-지표"><strong>주요 지표</strong></h3>
<ul>
<li><code>http_req_failed</code>: 실패한 요청의 수.</li>
<li><code>http_req_waiting</code>: 요청이 성공하기까지 대기한 시간.</li>
<li><code>http_reqs</code>: 전체 요청 수.</li>
<li><code>iteration_duration</code> : 1번의 사이클당 걸린 시간을 의미. </li>
<li><code>p(90)</code>, <code>p(95)</code>, <code>p(99)</code>: 특정 값이 전체 데이터에서 차지하는 위치.</li>
</ul>
<h2 id="포인트-조회">포인트 조회</h2>
<pre><code class="language-javascript">import http from &#39;k6/http&#39;;
import { sleep } from &#39;k6&#39;;

export const options = {
    vus: 100, // 가상 사용자 수 (100명)
    duration: &#39;1s&#39;, // 테스트 실행 시간
};

export default function () {
    // __VU은 현재 Virtual User ID를 나타냄 (1부터 시작)
    let userId = __VU;
    let url = `http://localhost:8080/customers/${userId}/balance`;

    let params = {
        headers: {
            &#39;Content-Type&#39;: &#39;application/json; charset=utf-8&#39;,
        },
    };

    // HTTP GET 요청
    http.get(url, params);

    // 1초 대기 (필요 시 조정)
    sleep(1);
}</code></pre>
<p>customerId가 url로 들어가므로 __VU를 이용해서 customerId를 하나씩 늘려주며 요청을 보냈다. </p>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/4cb8a35b-6df7-4827-81a8-bf8982e71e52/image.png" alt=""></p>
<ul>
<li><code>http_req_failed</code>: 0.00%</li>
<li><code>http_req_waiting</code>: 13.8ms</li>
<li><code>http_reqs</code>: 590</li>
<li><code>iteration_duration</code> : 1.02s</li>
<li><code>p(90)</code>, <code>p(95)</code> : http_req_waiting을 보면 95%의 사용자가 15.75ms 이내로 응답을 받은 것을 알 수 있다. </li>
</ul>
<h2 id="주문-테스트">주문 테스트</h2>
<pre><code class="language-javascript">import http from &#39;k6/http&#39;;
import { sleep } from &#39;k6&#39;;
import { check } from &#39;k6&#39;;

export const options = {
    vus: 100, // 가상 사용자 수
    duration: &#39;1s&#39;, // 테스트 실행 시간
};

export default function () {
    // 1~10000 사이의 랜덤 customerId 생성
    const customerId = Math.floor(Math.random() * 10000) + 1;

    const url = &#39;http://localhost:8080/orders&#39;;
    const payload = JSON.stringify({
        customerId: customerId,
        orderProducts: [
            {
                productId: 3,
                amount: 2,
            },
        ],
    });

    const params = {
        headers: {
            &#39;Content-Type&#39;: &#39;application/json; charset=utf-8&#39;,
        },
    };

    // POST 요청 실행
    const response = http.post(url, payload, params);

    // 응답 상태 체크
    check(response, {
        &#39;is status 200&#39;: (r) =&gt; r.status === 200,
    });

    // 1초 대기
    sleep(1);
}
</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/a5b2359a-ddeb-44d8-a5e4-1d9236c0b70c/image.png" alt="">
상품 3의 재고를 150개로 설정해 두었고, 부하 테스트로 3번 상품을 2개씩 100번 구매요청을 보내므로 <em>25번의 요청은 실패를 할 것으로 예상</em>했다. </p>
<p>그러나, </p>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/cea3f91f-44c1-4fdd-8ec3-43a18ca4e5ea/image.png" alt=""></p>
<ul>
<li>대다수(90.33%)의 응답이 실패.</li>
<li>평균 요청시간은 짧으나 많은 요청이 실패함. 
➡️ 서버에서 오류 처리 도는 요청 자체가 성공하지 못해 빠르게 반환된 것으로 보인다. </li>
</ul>
<h3 id="문제점-분석">문제점 분석</h3>
<ul>
<li>높은 실패율 &gt; <code>서버의 부하</code>, <code>API로직 문제</code>, <code>서버 설정 오류</code></li>
<li>처리 대기시간 &gt; 서버가 높은 요청량을 처리하지 못하고 병목이 발생함. </li>
<li>송수신 데이터량이 요청 수에 비례하지 않는 것으로 보아, 서버가 오류를 반환하거나 일부 요청이 무시된 것으로 추측된다. </li>
</ul>
<h3 id="추가로-해야-할-작업-및-테스트">추가로 해야 할 작업 및 테스트</h3>
<p>1) 요청 사용자 수를 점진적으로 늘려보기.
2) 부하를 일으키는 로직 개선하기 
3) 캐시를 적용할 수 있는 부분들은 캐시를 적용하고 성능 개선하기. 
4) cpu 성능 향상시키기 </p>
<h3 id="병목-위치를-파악하려고-하였는데-갑자기-된다">병목 위치를 파악하려고 하였는데, 갑자기 된다(?)</h3>
<blockquote>
<p>🏷️Trouble Shooting 
<code>read:connection reset by peer</code> :소켓 개수의 제한으로 발생하는 문제. </p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/b232812f-6266-42d2-a2e5-ad34498fecca/image.png" alt=""></p>
<ul>
<li>30초에 1명 </li>
</ul>
<p>최소 9ms, 최대 548ms의 편차가 발생했지만 네트워크 지연 초기 요청 설정 때문으로 보인다. </p>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/53b99594-cf12-43bb-bbe0-f095e1bd6c14/image.png" alt=""></p>
<ul>
<li>30초에 10명 </li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/4fa8c50a-d4d8-4cde-9889-41078752f3a2/image.png" alt=""></p>
<ul>
<li>30초에 100명 
➡️100명의 가상 사용자가 동시에 요청을 보냈음에도 서버가 안정적으로 처리했다.</li>
</ul>
<p>점진적 과부화를 줬더니 다시 된다..? </p>
<h3 id="성능-상태">성능 상태</h3>
<ul>
<li>모든 요청이 정상응답(HTTP 200)으로 완료되었다. </li>
<li>100VUs까지 부하를 증가시켰음에도 평균 응답시간이 40ms이내로 유지되었고, 성공률 또한 100%로 유지되었다. </li>
<li>p(95) 기준 응답시간이 91.6ms로 안정적임을 알 수 있다. </li>
</ul>
<h3 id="병목-지점">병목 지점</h3>
<ul>
<li>100명의 가상 사용자 테스트 시 최대 응답시간이 증가하였으나 <em>데이터베이스나 네트워크I/O</em> 병목이 발생했을 가능성이 있다. </li>
</ul>
<h2 id="상품-조회">상품 조회</h2>
<pre><code class="language-javascript">import http from &#39;k6/http&#39;;
import { sleep } from &#39;k6&#39;;

export const options = {
    stages: [
        { duration: &#39;10s&#39;, target: 500 }, // 10초 동안 500명의 사용자로 증가
        { duration: &#39;1m&#39;, target: 500 },  // 1분 동안 500명의 사용자 유지
        { duration: &#39;10s&#39;, target: 0 },   // 10초 동안 사용자를 0으로 감소
    ],
    thresholds: {
        http_req_failed: [&#39;rate&lt;0.01&#39;],   // 1% 미만의 요청만 실패해야 함
        http_req_duration: [&#39;p(95)&lt;500&#39;], // 95% 요청이 500ms 미만이어야 함
    },
};

export default function () {
    let url = &#39;http://localhost:8080/products/10&#39;;
    let params = {
        headers: {
            &#39;Content-Type&#39;: &#39;application/json; charset=utf-8&#39;,
        },
    };

    for (let i = 0; i &lt; 10; i++) {
        http.get(url, params);
        sleep(1); // 각 요청 사이에 1초의 대기 시간
    }
}</code></pre>
<p>500명의 고객이 1초에 10번씩 조회하도록 하는 스크립트이다.</p>
<p>지난번에 <a href='https://velog.io/@dev_nana/DataBase%EA%B0%84%EB%8B%A8%ED%95%9C-E-Commerce-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A5%BC-%ED%86%B5%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-Indexing-Index%EB%A1%9C-%EC%84%B1%EB%8A%A5-%EA%B0%9C%EC%84%A0%ED%95%B4%EB%B3%B4%EA%B8%B02'>인덱스 설정</a>이 잘못되어 성능을 향상시킬 수 없었는데, 이번 부하테스트를 위해선 필연적으로 인덱스를 적용시켜야만 했다. </p>
<p>인덱스를 요리조리 바꿔보다가 
<img src="https://velog.velcdn.com/images/dev_nana/post/bc773f02-ee26-4e46-a794-b4198f2dea85/image.png" alt=""></p>
<p><code>product_id</code>와 <code>reg_date</code>의 순서를 변경하고 <code>amount</code>를 추가한 복합인덱스로 변경하였다. 
<img src="https://velog.velcdn.com/images/dev_nana/post/2f5f1601-8493-49c3-880a-7ec8c0aadc24/image.png" alt="">
그 결과 내가 의도한 인덱스를 타게되었고 성능또한 향상되었다. </p>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/fcc640e0-2d81-4bec-9ed7-2d1972da2a1b/image.png" alt="">
1번 호출 시 213ms가 걸렸고 이제 부하테스트를 해보겠다. </p>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/7a9d71dd-7a6b-49a9-83d4-69848e9236a6/image.png" alt="">
인덱스와 캐싱의 조합은 효과가 대단했다..! </p>
<p><code>http_req_failed</code> : 0% 
<code>p(95)</code> : 47.01ms </p>
<p>정도로 500명의 요청자가 무리없이 50ms 내로 응답을 받았다. </p>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/cc9ce203-858c-4d5b-9a87-1ca11c45ff56/image.png" alt=""></p>
<ul>
<li>5000명, 30s </li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/2ae82118-8c99-48ac-8587-ec75c2860920/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/e4214ea7-2b1a-4b9e-afa4-a40fe8647115/image.png" alt=""></p>
<ul>
<li>7500명까지도 버텼으나 8000명 부터 실패건수가 생기기 시작했다. 
<img src="https://velog.velcdn.com/images/dev_nana/post/164c3867-dc69-4c36-bbb3-53a0834360d9/image.png" alt=""></li>
<li>10000명, 30s 
응답속도가 현저히 느려진 것을 볼 수 있다. 
캐시와 인덱스조차도 힘을 내지 못하는 상황에 와 버린 것이다.</li>
</ul>
<h3 id="해결방안-도출하기">해결방안 도출하기.</h3>
<pre><code class="language-java">spring.datasource.hikari.maximum-pool-size=20
</code></pre>
<p>히카리의 풀 사이즈를 최대로 잡아보았다. 
<img src="https://velog.velcdn.com/images/dev_nana/post/42c30c7f-abce-4c8e-9e3d-1f9ad87bcddf/image.png" alt=""></p>
<ul>
<li>아까 실패건수가 생겼던 8000명을 실패없이 모두 처리했다. </li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/4ae8c719-854d-4ab9-96e6-18c4b039a6b7/image.png" alt="">
하지만 10000명에서는 조금 더 많은 실패를 보였지만 <code>p(95)</code>의 응답시간은 많이 줄어들었다. </p>
<p>이 방법 말고도 위에서 언급한 로드밸런싱을 통해 요청을 분산시키는 방법으로도 트래픽을 대비할 수 있을 것이다. </p>
<h1 id="5-장애-대응은-어떻게해야할까">5. 장애 대응은 어떻게해야할까?</h1>
<h2 id="장애-탐지">장애 탐지</h2>
<ul>
<li>모니터링 시스템으로 비정상적인 오류나 트래픽을 탐지한다.</li>
<li>담당자에게 신속하게 전파 ex)온콜</li>
</ul>
<h2 id="장애-분류-및-전파">장애 분류 및 전파</h2>
<ul>
<li>담당자에 의해 장애 영향도에 따른 등급 분류</li>
<li>장애 등급에 따른 심각도에 따라 관련 채널을 통해 장애 전파 </li>
<li>각 담당자의 대응 작업 산정 및 고객 선제 응대 </li>
</ul>
<h2 id="장애-복구-및-보고">장애 복구 및 보고</h2>
<ul>
<li>장애 원인의 파악 및 대응 진행</li>
<li>장애 복구 진행 상황에 대한 신속한 공유 및 업데이트</li>
<li>장애 상황 요약 및 분석, 개선 내용 보고 </li>
</ul>
<h2 id="장애-상황-해서-통지">장애 상황 해서 통지</h2>
<ul>
<li>장애 복구가 완료되어 장애 상황이 해소되었다면 유관 부서 및 사내에 전파 </li>
<li>장애 해소 및 서비스 정상화에 대해 고객에게 공지 </li>
</ul>
<h2 id="장애-회고">장애 회고</h2>
<ul>
<li>장애 상황 피드백 및 추가 개선점 분석 </li>
</ul>
<hr>
<p><a href="https://velog.io/@nnijgnus/%EC%84%B1%EB%8A%A5-%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%A5%BC-%EC%A7%84%ED%96%89%ED%95%B4%EB%B3%B4%EC%9E%90-with-k6">https://velog.io/@nnijgnus/%EC%84%B1%EB%8A%A5-%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%A5%BC-%EC%A7%84%ED%96%89%ED%95%B4%EB%B3%B4%EC%9E%90-with-k6</a></p>
<p><a href='https://www.whatap.io/bbs/board.php?bo_table=blog&wr_id=66'>평균응답시간</a></p>
<p><a href='https://engineering-skcc.github.io/performancetest/Performance-Testing-Terminologies/'>성능 테스트 유형 알아보기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[간단한 E-Commerce 프로젝트를 통해 알아보는 트랜잭션 범위에 대한 이해.]]></title>
            <link>https://velog.io/@dev_nana/%EA%B0%84%EB%8B%A8%ED%95%9C-E-Commerce-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A5%BC-%ED%86%B5%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EB%B2%94%EC%9C%84%EC%97%90-%EB%8C%80%ED%95%9C-%EC%9D%B4%ED%95%B4</link>
            <guid>https://velog.io/@dev_nana/%EA%B0%84%EB%8B%A8%ED%95%9C-E-Commerce-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A5%BC-%ED%86%B5%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EB%B2%94%EC%9C%84%EC%97%90-%EB%8C%80%ED%95%9C-%EC%9D%B4%ED%95%B4</guid>
            <pubDate>Thu, 14 Nov 2024 17:42:20 GMT</pubDate>
            <description><![CDATA[<h1 id="0-나의-이커머스-프로젝트">0. 나의 이커머스 프로젝트</h1>
<p>나의 OhSir39cm E-Commerce 프로그램에는 특정 한 부분에 <code>@Transactional</code> 어노테이션이 걸려있다.</p>
<p>커머스에서 가장 중요한 부분이라고 할 수 있는 <em>주문&amp;결제</em> 부분인데, 
기존에는 <strong>주문과 결제가 성공하면 재고를 차감하고 외부 데이터플랫폼으로 전송</strong> 해 주는 형태이다. </p>
<pre><code>  ├─order
    │  ├─application
    │  │  └─dataPlatform
    │  ├─domain
    │  ├─infrastructure
    │  │  ├─jpaRepository
    │  │  └─repositoryImpl
    │  └─presentation
    │      └─dto
</code></pre><p>파일 구조는 이렇게 되어있고 <code>presentation</code> &gt; <code>infrastructure</code> &gt; <code>domain</code> &gt; <code>application</code> 으로 가는 레이어드 클린 아키텍처로 이루어져있다. </p>
<p>각 기능을 독립적인 계층으로 구분하고있어서 <strong>관심사의 분리를 명확</strong>하게 하였고, 각 계층이 특정한 역할을 맡도록 설계가 되어있다. </p>
<h1 id="1-현재-구조의-문제점">1. 현재 구조의 문제점</h1>
<pre><code class="language-java">@Transactional 
function createOrder(Long customerId, List&lt;Order&gt; order){
    유저 잔고 조회();
    재고 조회();
    주문 저장();
    재고 차감();
    데이터플랫폼 전송();
}</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/6f54e263-14dd-425f-adcb-ac4ab45dd9a3/image.png" alt=""></p>
<p>주문 서비스는 위와 같은 구조로 이루어져있다. </p>
<h2 id="1-1-rollback-issue">1-1. Rollback Issue</h2>
<p>얼핏보기엔 문제없어보이지만 <em>주문이 모두 성공</em>해도 주요 <strong>상품주문</strong>이라는 로직과 상관없는 외부 연동 시스템(ex. 주문 성공 시 알림톡을 보낸다.) <strong>데이터플랫폼 전송</strong>에서 실패하면 앞에 주문로직이 모두 성공을 해도 Rollback이 되는 상황이 발생하게 된다. 
<img src="https://velog.velcdn.com/images/dev_nana/post/b709a6f5-ed0e-44bf-b8ee-e97f3582902a/image.png" alt=""></p>
<h2 id="1-2-slowread">1-2. SlowRead</h2>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/441916b4-6700-4137-8190-863604201ad4/image.png" alt=""></p>
<ul>
<li>다수의 <code>SlowRead</code>작업으로 인해 요청처리에 영향을 줄 수 있다. </li>
<li>Transaction 범위 내에서 Lock을 사용하고 있을 경우, 해당 자원에 다른 요청의 대기 혹은 데드락 상황을 유발할 수 있다. 
ex) 주문1번의 프로세스 내 재고차감을 위해 락을 걸어놓고있는데 주문2번이 재고 차감에 접근한다면 주문1번에의해 주문2번이 대기상태가 되고, 주문 2번이 처리하는데 전체적으로 길어지는 상황이 발생할 수 있다. </li>
</ul>
<h2 id="1-3-무결성-문제">1-3. 무결성 문제</h2>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/2e003292-3634-41b1-8259-19088545905c/image.png" alt=""></p>
<p>externalApi의 타임아웃으로 트랜잭션을 롤백시켰으나, external서비스에서는 사실 정상적으로 처리되었다면. 
주문이 없는데 externalAPI에는 데이터가 정상적으로 전송이 되었을 때 무결성 문제가 발생할 수 있다. </p>
<h1 id="2-애플리케이션-이벤트를-통한-관심사-분리">2. 애플리케이션 이벤트를 통한 관심사 분리.</h1>
<p>일단 근본적인 문제는 &#39;주문&#39;이라는 비즈니스 로직에 너무 많은 책임이 주어져 있다. 
주문생성 &gt; 주문하는 고객 잔고 조회 &gt; 재고 확인 &gt; 결제 &gt; 재고차감 &gt; 외부 API에 데이터 전송 </p>
<p><em>너무 많은 관심사를 하나의 작업으로 처리하면 위와같은 문제가 발생할 수 있다.</em></p>
<h2 id="2-1-이벤트-기반-흐름-제어">2-1. 이벤트 기반 흐름 제어</h2>
<p>(Event Delivery) : 어떻게하면 효율적으로 정보를 전달할 수 있는가?</p>
<h3 id="publish-subscribe-pattern-pub-sub-pattern">Publish-Subscribe Pattern (Pub-Sub Pattern)</h3>
<p>동기식 이벤트 처리방식이 가지고 있는 대기와 지연에 대한 문제를 이벤트기반의 비동기 방식으로 유연성 있고 확장성 있게 처리하기 위한 방식 </p>
<ul>
<li>하나의 이벤트에 대해서 여러 마이크로 서비스들이 각각 필요한 비즈니스로직을 수행할 수 있는 로직</li>
</ul>
<h2 id="2-2-연결성-및-조합">2-2. 연결성 및 조합</h2>
<h3 id="service-choreography-pattern">Service Choreography Pattern</h3>
<ul>
<li><p>여러 마이크로서비스를 조합하는 비즈니스 기능의 구현을 이벤트 기반의 <em>비동기 통신</em>으로 합성하는 패턴 </p>
</li>
<li><p>유연성, 확장성, 변경비용을 고려해서 서비스들간의 낮은 결합도(Decoupled)와 비동기 통신이 필요. </p>
</li>
<li><p>해결책 : 다른 마이크로서비스를 능동적으로 직접 호출하지 아낳고 이벤트와 메시지를 기반으로 반응 모드로 작동</p>
</li>
</ul>
<p>이벤트 구독하고 있는 서비스들이 개별적으로 구독해서 독립적으로 수행하는 방식 </p>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/d245bba8-07f0-4034-9dc3-e05fc2804af1/image.png" alt=""></p>
<h3 id="saga-pattern">Saga Pattern</h3>
<p>분산 application에서 여러 application에 걸쳐져 있는 트랜잭션들을 조정해서 데이터의 일관성을 유지해주기 위한 분산 트랜잭션 관리 패턴.</p>
<p>로컬 트랜잭션으로 각각 분리해서 실행 &gt; 실패했을 경우 보상트랜잭션 발생시켜 데이터 일관성을 유지함. </p>
<ul>
<li>문제 : 마이크로서비스 구조에서 긴밀한 결합 없이 여러마이크로서비스에 걸친 트랜잭션 처리의 복잡성 </li>
<li>해결책 : 모든 트랜잭션 이벤트를 게시하고 결과에 따라 다음 트랜잭션을 시작, 혹은 실패할 경우 보상 트랜잭션 실행. </li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/26a4a0ba-fa92-4c94-b381-beada551a13c/image.png" alt=""></p>
<p>상품 주문 결재 예시를 saga패턴으로 나타낸 그림이다. 
주문 생성 &gt; 재고 차감 &gt; 결제 &gt; 쿠폰 승인 &gt; 포인트 차감 &gt; 주문완료 
이 프로세스에서 실패하는 상황이 발생한다면  <code>ReleaseStock()</code>으로 보상트랜잭션을 실행시키고 상품을 취소한다. </p>
<ul>
<li><p>성능 향상
모든 트랜잭션을 한 번에 처리하는 대신, Saga는 각 서비스에서 독립적으로 트랜잭션을 처리한다. 
이렇게 함으로써 트랜잭션을 나누어 병렬로 처리할 수 있어 성능을 높일 수 있다.</p>
</li>
<li><p>트랜잭션 복구 용이
하나의 서비스가 실패할 경우, 해당 서비스의 작업을 취소하거나 되돌리기 위한 보상 트랜잭션을 정의할 수 있다. 실패한 작업에 대해 다른 서비스들이 자동으로 복구 작업을 수행하므로, 시스템의 신뢰성을 높일 수 있다.</p>
</li>
<li><p>서비스 간 결합도 낮추기
각 서비스가 독립적으로 트랜잭션을 처리하고, 다른 서비스와의 의존도를 최소화하기 때문에 서비스 간의 결합도가 낮아지고, 시스템의 유연성과 유지보수성이 향상된다.</p>
</li>
<li><p>에러 처리가 명확함
Saga 패턴은 실패 시 롤백을 통해 시스템의 일관성을 유지한다. 
실패가 발생하면 각 서비스가 보상 트랜잭션을 통해 문제를 해결할 수 있어, 에러를 처리하는 로직이 명확하고 효과적이다.</p>
</li>
<li><p>비동기적 처리
Saga 패턴은 각 서비스가 비동기적으로 트랜잭션을 처리하기 때문에, 다른 서비스의 응답을 기다리는 동안 다른 작업을 진행할 수 있어 시스템의 효율성을 높인다.</p>
</li>
<li><p>대규모 시스템에 적합
마이크로서비스 아키텍처에서 서비스가 분산되어 있을 때, 전체 시스템의 일관성을 유지하는 데 유리한 패턴이다. 각 서비스가 개별적으로 트랜잭션을 처리하므로, 시스템 확장성이 높고, 장애 발생 시 영향을 최소화할 수 있다.</p>
</li>
</ul>
<h1 id="3-1차-관심사-분리">3. 1차 관심사 분리</h1>
<p><code>MSA(Micro Service Architecture)</code>라는 것이 관심사가 분리된다는 점에서는 아주 좋지만 트랜잭션이 여러 마이크로서비스에 걸쳐있어 처리가 복잡하다는 단점이 있다. </p>
<p>서비스가 작을 때에는 한 트랜잭션이 오래걸린다거나 중요하지 않은 로직에의해 모든 작업이 롤백되는 한이 있더라도 크게 영향을 받지 않아 전체 소스 관리 측면에서는 &quot;빠른 원인분석&quot; 및 수정이 가능하여 이점이 있으나, </p>
<p>서비스 확장이 필요한 순간에는 어쩔 수 없이 트랜잭션을 쪼개야하는 상황이 발생하게 된다. </p>
<p>나의 이커머스 프로젝트에도 <strong>관심사 분리</strong>를 적용하기 위해 <code>@TransactionalEventListener</code>를 적용하였다. </p>
<pre><code class="language-java">@Transactional 
function createOrder(Long customerId, List&lt;Order&gt; order){
    try{
      유저 잔고 조회();
      재고 조회();
      주문 저장();
      재고 차감();
      eventPublisher.publishEvent(new 데이터플랫폼 전송(source, 주문정보));
    }
    catch(e){
        log.warn(e);
    }
}</code></pre>
<p>🔽 비동기로 이벤트 발행하기 </p>
<pre><code class="language-java"> @Async
    @TransactionalEventListener(phase= TransactionPhase.AFTER_COMMIT)
    public void sendOrderMessage(OrderEvent event) {
        List&lt;OrderProductRequest&gt; order = event.getOrder();
        log.info(&quot;주문 데이터가 전송되었습니다: &quot; + order);
    }</code></pre>
<p>이렇게 짜면 위의 비즈니스 로직과는 무관한 이벤트 발행이 된다. </p>
<h1 id="4-이후-적용할-패턴">4. 이후 적용할 패턴</h1>
<p><code>SAGA Pattern</code>도입을 통해 데이터의 일관성을 유지하면서 확장에는 유연한 서비스를 만들고자 한다. </p>
<hr>
<p><a href='https://www.youtube.com/watch?v=8OFTB57G9IU&t=1229s'>12가지 디자인 패턴으로 알아보는 클라우드 네이티브 마이크로서비스 아키텍처</a>
<a href='https://learn.microsoft.com/ko-kr/azure/architecture/reference-architectures/saga/saga#orchestration'>마이크로소프트 SAGA공식문서</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DataBase]간단한 E-Commerce 프로젝트를 통해 알아보는 Indexing 🔍(Index로 성능 개선해보기2)]]></title>
            <link>https://velog.io/@dev_nana/DataBase%EA%B0%84%EB%8B%A8%ED%95%9C-E-Commerce-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A5%BC-%ED%86%B5%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-Indexing-Index%EB%A1%9C-%EC%84%B1%EB%8A%A5-%EA%B0%9C%EC%84%A0%ED%95%B4%EB%B3%B4%EA%B8%B02</link>
            <guid>https://velog.io/@dev_nana/DataBase%EA%B0%84%EB%8B%A8%ED%95%9C-E-Commerce-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A5%BC-%ED%86%B5%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-Indexing-Index%EB%A1%9C-%EC%84%B1%EB%8A%A5-%EA%B0%9C%EC%84%A0%ED%95%B4%EB%B3%B4%EA%B8%B02</guid>
            <pubDate>Thu, 14 Nov 2024 17:09:12 GMT</pubDate>
            <description><![CDATA[<h1 id="0-전-테스트에서-간과한-것">0. 전 테스트에서 간과한 것.</h1>
<p>그냥 몇 만건의 데이터를 넣어놓고 api에서 여러번 조회 부하테스트를 넣는걸로 테스트가 가능할거라 생각했다.</p>
<p>전에 한게 아까워서 그대로 두는게 절대아니고^..^ 나름의 유의미한 데이터가 되지않을까..하여 놔둔다.</p>
<p>👉🏻적은 데이터가 있을 때에는 <em>Index Seek</em> 을 통해 찾는 것 보다 <em>Full Scan</em>이 더 빠를 수 있기 때문에 이번엔 찐 dummy data를 통해 <strong>인덱스를 내가 생각한 대로 타는지</strong> <em>실행계획</em>을 통해 파악해 보고자 한다. </p>
<ul>
<li>DB : MySQL 8.0</li>
</ul>
<h1 id="1-dummy-data-생성">1. Dummy Data 생성</h1>
<p>인덱스가 제대로 동작하는지 보려면 최소 100만건 이상의 데이터는 존재해야한다. </p>
<pre><code class="language-sql">
USE ecommerce;

-- 테이블 초기화
TRUNCATE TABLE order_product;
TRUNCATE TABLE customer_order;
TRUNCATE TABLE product_inventory;
TRUNCATE TABLE product;
TRUNCATE TABLE cart;
TRUNCATE TABLE customer;

-- 1. customer 테이블에 1,000명의 고객 데이터 생성
INSERT INTO customer (balance)
WITH RECURSIVE cte AS (
    SELECT 1 AS n
    UNION ALL
    SELECT n + 1 FROM cte WHERE n &lt; 1000
)
SELECT FLOOR(RAND() * 10000) AS balance
FROM cte;

-- 2. product 테이블에 1,000,000개의 제품 데이터 생성
SET SESSION cte_max_recursion_depth = 10000000;


-- product 테이블에 1,000,000개의 제품 데이터 생성
INSERT INTO product (product_nm, price, category)
WITH RECURSIVE cte (n) AS (
    SELECT 1
    UNION ALL
    SELECT n + 1 FROM cte WHERE n &lt;= 1000000
)
SELECT
    CONCAT(&#39;Product&#39;, LPAD(n, 7, &#39;0&#39;)) AS product_nm,
    FLOOR(RAND() * 1000) AS price,
    CASE WHEN n % 4 = 0 THEN &#39;food&#39;
         WHEN n % 4 = 1 THEN &#39;clothes&#39;
         WHEN n % 4 = 2 THEN &#39;elec&#39;
         ELSE &#39;etc&#39; END AS category
FROM cte;

-- product_inventory에 각 product의 재고 데이터를 생성
INSERT INTO product_inventory (product_id, amount)
SELECT product_id, FLOOR(RAND() * 100)
FROM product;

-- customer_order에 주문 데이터 생성
INSERT INTO customer_order (customer_id)
SELECT customer_id FROM customer ORDER BY RAND() LIMIT 1000000;

-- order_product에 각 주문마다 임의의 제품 추가
INSERT INTO order_product (order_id, product_id, amount, price)
SELECT
    customer_order.order_id,
    product.product_id,
    FLOOR(RAND() * 5) + 1 AS amount,
    product.price
FROM
    customer_order
        JOIN
    product ON RAND() &lt; 0.01;  -- 일부 제품을 임의로 추가</code></pre>
<p>1000명의 고객과 100만건의 상품, 주문 데이터를 생성했다. </p>
<h1 id="2-인덱스-생성하기">2. 인덱스 생성하기.</h1>
<h2 id="2-1-재고가-있는-상품-전체-조회">2-1. 재고가 있는 상품 전체 조회</h2>
<pre><code class="language-sql">SELECT p.product_id, p.product_nm, p.price, p.category, PI.amount 
FROM product p 
INNER JOIN product_inventory PI 
ON p.product_id = PI.product_id
WHERE PI.amount &gt; 0</code></pre>
<p><code>product</code>의 <code>product_id</code>는 PK이기 때문에 인덱스가 기본적으로 걸려있다. 
조인하는 컬럼인 <code>product_inventory</code>의 <code>product_id</code>와 <code>amount</code>가 조회하는 쿼리에 사용되었고 <code>product</code>와 <code>product_inventory</code>는 1:1관계이므로 <em>Cardinality</em>가 높을 것으로 예상된다. </p>
<h2 id="2-1-1-인덱스-없을-때-조회">2-1-1. 인덱스 없을 때 조회</h2>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/86ddbe80-7399-4fa1-ae06-d19cb294e693/image.png" alt=""></p>
<p>1) <code>product_inventory</code>테이블은 <strong>Full Scan</strong>을 했고, <code>product</code>테이블은 <strong>eq_ref</strong>를 했다. </p>
<blockquote>
<p>🎲 실행계획 type </p>
</blockquote>
<ul>
<li><code>eq_ref</code> :
조인이 수행될 때 드리븐 테이블의 데이터에 접근하며, 고유 인덱스 또는 기본키로 단 1건의 데이터를 조회하는 방식. 
현재 테이블의 각 행에 대해 다른 테이블에서 단 하나의 행만이 조인될 것을 의미. </li>
<li><code>ALL</code> : 
테이블의 처음부터 끝까지 읽는 full-scan방식. 
활용할 인덱스가 없거나 인덱스를 활용하는게 오히려 비효율적이라고 옵티마이저가 판단할 경우 선택됨. </li>
</ul>
<p>2) key
옵티마이저가 SQL문을 최적화 하고자 사용한 기본키(PK) 또는 인덱스 명을 의미한다. 
어느 인덱스로 데이터를 검색했는지 확인할 수 있기에 해당 정보를 통해 비효율적인 인덱스 사용이나 인덱스 사용하지 않았을 경우 SQL튜닝을 고려할 수 있다. </p>
<p>해당 쿼리에서는 <code>product</code>의 PK를 사용하고<code>product_inventory</code>는 key가 사용되지 않았다. </p>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/8a22f2ee-1ef8-4a10-ae41-1c517077a371/image.png" alt=""></p>
<ul>
<li>조회 건 수 : 1,980,054 건</li>
<li>쿼리를 실행 : 0.031s </li>
<li>전체 조회 : 네트워크 전송을 포함 약 80.032s
약 200만건을 조회하는데 80초 정도 걸렸다. </li>
</ul>
<h2 id="2-1-2-인덱스-걸고-조회">2-1-2. 인덱스 걸고 조회</h2>
<p>1) <code>product_inventory</code>의 <strong>product_id</strong>와 <strong>amount</strong>로 걸면 Cardinality가 높아 적절한 인덱스일 것으로 예상했다. </p>
<pre><code class="language-sql">CREATE INDEX idx_product_id ON PRODUCT_INVENTORY (product_id);</code></pre>
<pre><code class="language-sql">CREATE INDEX idx_amount ON product_inventory (amount);</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/aaf93a02-e69e-48a8-8c19-2a9567c67df3/image.png" alt="">
<img src="https://velog.velcdn.com/images/dev_nana/post/76a9810e-08bc-4219-99b0-961bab751c39/image.png" alt=""></p>
<ul>
<li>product_inventory의 type : ALL</li>
<li>product_inventory의 key : null</li>
</ul>
<p>인덱스를 생성했으나 사용되지 않았다. 
옵티마이저가 full scan이 더 성능이 좋다고 판단했기 때문이다. </p>
<p>2) 이번엔 select에 사용된 조건인 &quot;amount&quot;와 &quot;product_id&quot;의 복합인덱스를 생성해서 비교해보았다. </p>
<pre><code class="language-sql">CREATE INDEX idx_product_inventory_amount_product_id ON PRODUCT_INVENTORY(amount, product_id);</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/4435a387-49a0-41dc-bb3b-e6c8313921df/image.png" alt=""></p>
<ul>
<li>product_inventory의 type : ALL ➡️ range </li>
<li>product_inventory의 key : null ➡️ idx_product_inventory_amount_product_id</li>
</ul>
<p>이번엔 내가 생성한 인덱스가 정상적으로 타는 것을 볼 수 있다. </p>
<blockquote>
<p>🎲 *<em>filtered *</em></p>
</blockquote>
<ul>
<li>스토리지 엔진  MySQL 엔진으로 가져온 데이터를 대상으로 필터 조건에 따라 필터링 된 건수를 백분율로 계산해 보여주는 항목.</li>
<li>예를 들어 where 사원번호 between 1 and 10 조건으로 100건의 데이터 중 10건의 데이터가 필터링 되었다면 filtered의 값은 10(%)가 될 것이다. </li>
</ul>
<blockquote>
<p>🎲 <strong>extra</strong>
SQL문을 어떻게 수행 할 것 인지에 대한 추가 정보를 보여주는 항목
세미콜론; 으로 구분해서 여러 정보를 나열한다. 
이건 종류가 너무 많으니 <a href='https://catsbi.oopy.io/c6410158-5165-4145-9332-58896020f7cc'>여기</a>에서 봐주시길! </p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/1227a206-0b5d-4289-a1cc-430d0ac037c3/image.png" alt="">
인덱스를 탔더니 조회 시간이 줄어들었다. </p>
<blockquote>
<p>💡왜 복합인덱스에서 성능이 나온걸까? 
product_id는 어차피 1:1관계에 있으므로 인덱스를 거는 것 만으로 크게 성능을 향상시킬 수 없다. 
where절에 amount가 있으므로 product_id 인덱스를 찾았을 때 amount까지 필요한 데이터로 가져올 수 있기 때문에 성능개선을 확실히 느낄 수 있다. </p>
</blockquote>
<h2 id="2-2-판매-랭킹-조회">2-2. 판매 랭킹 조회</h2>
<pre><code class="language-sql">explain SELECT op.product_id, SUM(op.amount)
FROM order_product op 
WHERE op.reg_date &gt;=  &#39;2024-11-10&#39;
GROUP BY op.product_id  
ORDER BY SUM(op.amount) DESC</code></pre>
<p>주문일 최근3일간의 데이터를 집계하여 상위 5개의 상품을 보여주는 쿼리이다. 
전체를 조회해서 상위 5개를 뽑아내는건 소스 내에서 한다.</p>
<h3 id="2-2-1-인덱스-없음">2-2-1. 인덱스 없음</h3>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/224bd1c5-1b01-4d33-a9d4-b17bb67238c7/image.png" alt="">
찾은 행: 1,999,281  <strong>쿼리: 00:02:19.0 (+ 0.719 초 네트워크)</strong></p>
<h3 id="2-2-2-product_id-인덱스-추가">2-2-2. product_id 인덱스 추가</h3>
<pre><code class="language-sql">CREATE INDEX idx_product_id ON order_product (product_id);</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/f2badb90-fb5f-476c-a70d-1f182a4b543d/image.png" alt=""></p>
<h3 id="2-2-3-reg_date-인덱스-추가">2-2-3. reg_date 인덱스 추가</h3>
<pre><code class="language-sql">CREATE INDEX idx_createdAt ON order_product (reg_date);</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/1beccea0-a0a1-4db8-a14d-30bf95e49da5/image.png" alt=""></p>
<h3 id="2-2-4--reg_date-product_id-인덱스-추가">2-2-4.  reg_date, product_id 인덱스 추가</h3>
<pre><code class="language-sql">CREATE INDEX idx_createdAt_product_id ON order_product (reg_date, product_id);</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/855de0ab-e6e3-49fd-8004-814a3a8e5728/image.png" alt=""></p>
<p>👉🏻 가장 필터가 많이 된 것은 product_id에서 였다. </p>
<p>이 부분이 너무 이상해서 SOS를 했다 ㅠ </p>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/03d2d25a-1081-45be-87ce-95ffe734574b/image.png" alt=""></p>
<p>쩝..해 봤는데도 안되는군요 ㅠㅠ 그치만..
다시 해보는걸로...</p>
<h1 id="2-3-주문-정보-조회">2-3. 주문 정보 조회</h1>
<pre><code class="language-sql">SELECT * 
FROM customer_order o 
INNER JOIN order_product op 
ON o.order_id = op.order_id
WHERE o.order_id = 13</code></pre>
<p>주문 정보로 조회하기 때문에 <code>order_product</code>에 <code>order_id</code>로 인덱스를  걸어주면 성능향상을 기대할 수 있다. </p>
<h2 id="2-3-1-인덱스-적용-전">2-3-1. 인덱스 적용 전</h2>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/a69ed4f6-7229-4685-a76e-42522c0f812b/image.png" alt=""></p>
<p>5건을 조회하는데 16 s 892 ms이 걸렸다. 
pk로 적용된 customer_order의 order_id를 제외하고는 조인에 영향을 준 컬럼이 없다. 
<code>filtered</code>도 10만 걸러주었고 rows도 전체 row를 검색하고있었다.</p>
<h2 id="2-3-2-order_id-인덱스-추가">2-3-2. Order_id 인덱스 추가</h2>
<p><code>sql CREATE INDEX idx_orderId ON ORDER_PRODUCT (order_id)</code>
<img src="https://velog.velcdn.com/images/dev_nana/post/e25629d0-d2f8-40af-8b88-71956c3a8df7/image.png" alt=""></p>
<p> ⏰ 16 s 892 ms ➡️ 76 ms
 실행계획을 봐도 정확히 <code>order_id</code>를 통한 조인이 되었다. 
 <code>filtered</code>도 100%로 향상되었다. </p>
<hr>
<p><a href="https://catsbi.oopy.io/c6410158-5165-4145-9332-58896020f7cc">https://catsbi.oopy.io/c6410158-5165-4145-9332-58896020f7cc</a>
<a href="https://velog.io/@juhyeon1114/MySQL-%EC%8B%A4%ED%96%89-%EA%B3%84%ED%9A%8D-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0">https://velog.io/@juhyeon1114/MySQL-%EC%8B%A4%ED%96%89-%EA%B3%84%ED%9A%8D-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[MSSQL&SpringBatch]Current CallableStatement ou was not a ResultSet, but getResultList was called]]></title>
            <link>https://velog.io/@dev_nana/MSSQLSpringBatchCurrent-CallableStatement-ou-was-not-a-ResultSet-but-getResultList-was-called</link>
            <guid>https://velog.io/@dev_nana/MSSQLSpringBatchCurrent-CallableStatement-ou-was-not-a-ResultSet-but-getResultList-was-called</guid>
            <pubDate>Wed, 13 Nov 2024 01:34:49 GMT</pubDate>
            <description><![CDATA[<ul>
<li>java : 11ver</li>
<li>mssql :14.0ver</li>
</ul>
<p>OfficeWriter 라이브러리를 이용하여 C# Form으로 기업별 연간 학습 보고서를 엑셀파일로 만들어주는 프로그램이있다. </p>
<p>해당 프로그램이 너무 노후화되고 여러가지오류( 향후 아티클로 다시 다뤄볼것이다.)가 있어서 Spring Batch로의 전환작업을 하고있는데, 프로시저에서 결과를 받아와 리스트로 반환하는 작업 중에 며칠을 검색해봐도 답이 나오지 않는 문제가 있었다. </p>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/eb0307b3-51ec-4edf-a91e-cbdc27f88744/image.png" alt="">
Batch 내 Processor부분 
<img src="https://velog.velcdn.com/images/dev_nana/post/2d69dbbd-c582-4fe7-a138-7a1491bde950/image.png" alt="">
프로시저 소스 </p>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/f37ba741-5774-4484-a109-e0d5d2fada0a/image.png" alt="">
문제의 오류 </p>
<p>계속 <code>Current CallableStatement ou was not a ResultSet, but getResultList was called</code> 오류가 났는데 출력 값이 없는 프로시저 호출이라고 떴다. </p>
<p>분명 프로시저 내에선 select query만 있는데 왜 자꾸 출력값이 없다고하지?</p>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/b30590a4-1bf1-4b6a-988a-ac360a02933c/image.png" alt=""></p>
<pre><code class="language-java">public List&lt;R&gt; getResultList() {
        if (this.getMaxResults() == 0) {
            return Collections.EMPTY_LIST;
        } else {
            try {
                Output rtn = this.outputs().getCurrent();
                if (!ResultSetOutput.class.isInstance(rtn)) {
                    throw new IllegalStateException(&quot;Current CallableStatement ou was not a ResultSet, but getResultList was called&quot;);
                } else {
                    return ((ResultSetOutput)rtn).getResultList();
                }
            } catch (NoMoreReturnsException var2) {
                return null;
            } catch (HibernateException var3) {
                throw this.getExceptionConverter().convert(var3);
            } catch (RuntimeException var4) {
                this.getProducer().markForRollbackOnly();
                throw var4;
            }
        }
    }</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/a9777c16-b92a-43a5-a594-0c59c542808e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/f4cbb68e-5952-40a5-8a5c-c0cc8de1a1fc/image.png" alt=""></p>
<p>문제는 <code>SET NOCOUNT ON</code>에 있었다.</p>
<blockquote>
<p>SET NOCOUNT ON의 <a href='https://learn.microsoft.com/ko-kr/sql/t-sql/statements/set-nocount-transact-sql?view=sql-server-ver16'>공식문서</a><br/>
&quot;ON경우 SET NOCOUNT 개수가 반환되지 않습니다. 이 경우 SET NOCOUNT OFF개수가 반환됩니다.
이 함수는 @@ROWCOUNT 다음과 같은 경우에도 SET NOCOUNT 업데이트됩니다 ON.&quot;</p>
</blockquote>
<p>ON인 경우 결과 행의 수를 반환하지 않고 OFF인경우 반환한다.</p>
<p>&quot;~~행이 영향을 받음&quot; 이 문구 때문에 select문으로 인식하지 않아 <code>getResultList</code>를 사용할 수 없었던 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DataBase]간단한 E-Commerce 프로젝트를 통해 알아보는 Indexing 🔍(Index로 성능 개선해보기.)]]></title>
            <link>https://velog.io/@dev_nana/DataBase%EA%B0%84%EB%8B%A8%ED%95%9C-E-Commerce-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A5%BC-%ED%86%B5%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-Indexing-Index%EB%A1%9C-%EC%84%B1%EB%8A%A5-%EA%B0%9C%EC%84%A0%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@dev_nana/DataBase%EA%B0%84%EB%8B%A8%ED%95%9C-E-Commerce-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A5%BC-%ED%86%B5%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-Indexing-Index%EB%A1%9C-%EC%84%B1%EB%8A%A5-%EA%B0%9C%EC%84%A0%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sun, 10 Nov 2024 06:05:42 GMT</pubDate>
            <description><![CDATA[<h1 id="0-이번-포스트의-주제">0. 이번 포스트의 주제</h1>
<p><a href='https://velog.io/@dev_nana/DataBaseDB-Query-OptimizationIndex'>[DataBase]DB Query Optimization(Index)</a> 아티클을 통해 데이터베이스 인덱스에 대해 간단하게 알아보았다. </p>
<p>이커머스 시스템에서 기능별로 고도화 작업을 진행하고 있는데, </p>
<ol>
<li><a href='https://velog.io/@dev_nana/JavaSpring%EA%B0%84%EB%8B%A8%ED%95%9C-E-Commerce-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A5%BC-%ED%86%B5%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4-2-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EA%B2%A9%EB%A6%AC%EC%88%98%EC%A4%80%EC%9C%BC%EB%A1%9C-%EC%95%8C%EC%95%84%EB%B3%B8-%EC%84%B1%EB%8A%A5'>DB Lock을 통한 동시성 제어</a>방법</li>
<li><a href='https://velog.io/@dev_nana/Redis%EA%B0%84%EB%8B%A8%ED%95%9C-E-Commerce-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A5%BC-%ED%86%B5%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4-3-Redis-Cache%EB%A1%9C-DB%EB%B6%80%ED%95%98-%EC%A4%84%EC%9D%B4%EA%B8%B0-Redis%EB%A1%9C-%EC%96%BC%EB%A7%88%EB%82%98-%EC%A4%84%EC%96%B4%EB%93%9C%EB%8A%94%EC%A7%80-%EC%84%B1%EB%8A%A5%EB%B9%84%EA%B5%90%ED%95%98%EA%B8%B0'>캐싱을 이용한 성능개선</a>
을 진행하였다. </li>
</ol>
<p>이번엔 인덱스를 추가하여 <em>얼마나 성능 개선이 되는지</em> 알아보려고 한다. </p>
<h1 id="1-어디에-적용하면-좋을까">1. 어디에 적용하면 좋을까?</h1>
<h2 id="인덱스가-적용되어야-하는-곳">인덱스가 적용되어야 하는 곳.</h2>
<ul>
<li>데이터 중복이 적어 한번에 찾을 수 있어야하고</li>
<li>데이터 삽입, 수정이 적은 컬럼</li>
<li>조회에 자주 사용되며 </li>
<li>규모가 작지 않은 테이블</li>
</ul>
<p>에서 사용되어야 한다. </p>
<h2 id="조회가-많이-일어나는-곳">조회가 많이 일어나는 곳?</h2>
<p>이커머스에서 사용되는 기능 중 조회가 많이 일어나는 곳은 </p>
<ul>
<li>상품 조회</li>
<li>주문 조회 </li>
<li>랭킹 시스템</li>
<li>포인트 조회 </li>
</ul>
<h2 id="update-delete가-적은-곳">UPDATE, DELETE가 적은 곳</h2>
<ul>
<li>랭킹 시스템 <ul>
<li>주문 정보를 조회만 하며 update나 delete가 쿼리 내에서 일어나지 않는다. </li>
</ul>
</li>
<li>주문 조회 <ul>
<li>주문 정보가 INSERT는 되지만 UPDATE나 DELETE는 잘 일어나지 않는다.</li>
<li>더군다나 내 주문 테이블에는 배송 상태에 대한 값이 없으므로 더욱더 수정이 일어날 일이 없다. </li>
</ul>
</li>
<li>상품 조회 <ul>
<li>상품 재고에 대한 변경은 자주 일어나지만 상품 정보자체에 대한 변경은 잘 일어나지 않으므로 적절하다. </li>
</ul>
</li>
<li>포인트 조회 <ul>
<li>포인트 금액은 변하지만 한번 들어간 고객정보는 변하지 않는다. </li>
</ul>
</li>
</ul>
<h1 id="2-k6를-이용한-부하테스트">2. K6를 이용한 부하테스트</h1>
<h2 id="dummy-data-생성">Dummy Data 생성</h2>
<pre><code class="language-sql">-- 고객 더미 데이터 생성
INSERT INTO customer (balance)
SELECT FLOOR(RAND() * 100000)
FROM dual
LIMIT 10000;

-- product 테이블에 100개의 상품 데이터 생성
INSERT INTO product (product_nm, price, category)
SELECT 
    CONCAT(&#39;Product_&#39;, LPAD(seq.seq, 3, &#39;0&#39;)),
    FLOOR(RAND() * 1000) + 1000,  -- 가격은 1000~1999 사이로 설정
    CASE WHEN RAND() &lt; 0.5 THEN &#39;category1&#39; ELSE &#39;category2&#39; END
FROM 
    (SELECT @rownum := @rownum + 1 AS seq FROM (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) a, 
          (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) b, 
          (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) c, 
          (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) d,
          (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) e,
          (SELECT @rownum := 0) r
    ) seq
LIMIT 100;

-- product_inventory 테이블에 각 product의 inventory 데이터 생성
INSERT INTO product_inventory (product_id, amount)
SELECT 
    product_id,
    FLOOR(RAND() * 50) + 1  -- 재고는 1~50 사이로 설정
FROM 
    product
LIMIT 100;


-- 고객 주문 더미 데이터 생성
INSERT INTO customer_order (customer_id)
SELECT
    FLOOR(RAND() * 10000) + 1
FROM dual
LIMIT 10000;

-- 주문 상품 더미 데이터 생성
INSERT INTO order_product (order_id, product_id, amount, price)
SELECT
    FLOOR(RAND() * 10000) + 1,
    FLOOR(RAND() * 10000) + 1,
    FLOOR(RAND() * 10) + 1,
    FLOOR(RAND() * 1000) + 1000
FROM dual
LIMIT 10000;

-- 장바구니 더미 데이터 생성
INSERT INTO cart (customer_id, product_id, amount)
SELECT
    FLOOR(RAND() * 10000) + 1,
    FLOOR(RAND() * 10000) + 1,
    FLOOR(RAND() * 5) + 1       
FROM dual
LIMIT 10000;
</code></pre>
<h2 id="2-ranking-api에-k6적용">2. Ranking API에 K6적용</h2>
<pre><code class="language-javascript">import http from &#39;k6/http&#39;;
import { sleep } from &#39;k6&#39;;

export default function () {

    let url = &#39;http://localhost:8080/ranks/2024-11-10\&#39;;
    let params = {
        headers: {
            &#39;Content-Type&#39; : &#39;application/json; charset=utf-8&#39;,
        },
    };
    http.get(url, params);
    sleep(1);
}</code></pre>
<p>ranks는 최근 3일간의 판매 Top5를 뽑아내는 API이다. </p>
<h2 id="3-k6-적용-후-성능-테스트">3. k6 적용 후 성능 테스트</h2>
<h3 id="3-1-ranking-api-성능-테스트">3-1. Ranking API 성능 테스트</h3>
<p>js코드가 있는 위치로 가서 
<code>k6 run --vus 10(가상 사용자 수) --duration 30s(부하시간) --out csv=test.csv(출력되는 결과물 형식) script.js(실행할 스크립트)</code>
해당 코드를 PowerShell에서 실행시킨다.</p>
<h4 id="3-1-1-캐싱❌-인덱싱❌">3-1-1. 캐싱❌ 인덱싱❌</h4>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/ecd5c795-0bfb-4307-a46e-1065e660b0b6/image.png" alt="">
dummyData를 넣어준 날짜로 ranking api를 조회해 보았다. 
10000명이 30초동안 몰린다고 생각했을 때 성능이다. </p>
<h3 id="성능-테스트-분석">성능 테스트 분석</h3>
<p>1) <code>http_req_duration</code> (avg: 8.03s):
평균 8초 이상 소요되는 것은 응답 시간이 상당히 느리다는 뜻이고 
최댓값은 19.32초로, 특정 요청은 응답이 매우 오래 걸렸다. 
이는 서버가 많은 요청을 동시에 처리하면서 성능 저하가 발생했을 가능성을 보여준다.</p>
<p>2) <code>http_req_failed</code> (31.36%):
테스트 중 31.36%의 요청이 실패한 것으로 나타났다. 
요청이 약 1/3정도가 실패한걸 보면 서버가 높은 부하를 감당하지 못했거나 시간 초과 등의 이유로 요청이 실패했을 수 있다. 
서버 성능을 높이거나 요청을 효율적으로 분산할 필요가있다.</p>
<p>3) <code>iterations</code> (22,908):
30초 동안 약 22,908회의 요청이 완료되었으며, 100회의 요청이 중단되었다.
이는 10,000명의 가상 사용자가 30초 동안 요청을 발생시켰을 때 수행된 요청 횟수를 의미 한다.</p>
<p>4) <code>iteration_duration</code> (avg: 12.89s):
각 반복에 걸린 시간은 평균 12.89s 이며 사용자 요청이 완료되는 데 걸리는 총 시간이다. 
최댓값은 26.63초로, 특정 요청이 완료되는 데 오랜 시간이 소요된 경우가 있었다.</p>
<p>5) <code>http_req_waiting</code> (avg: 7.99s):
요청을 보내고 응답을 받을 때까지의 대기 시간은 평균 7.99초로, 서버가 요청을 처리하는 데 상당한 지연이 있음을 나타낸다.</p>
<p>6) <code>http_req_receiving</code> (avg: 36.08ms):</p>
<p>서버에서 데이터를 전송받는 데 걸리는 시간이다. 평균 36ms로, 전송 시간 자체는 비교적 짧으나 실제 응답이 오기까지의 대기 시간이 길기 때문에 전체 응답 시간에 영향을 미친다고 볼 수 있다.</p>
<p>ranking API에는 캐싱도 적용해 놓았기 때문에 다시한번 실행해 보았다.</p>
<h4 id="3-1-2-캐싱⭕-인덱싱❌">3-1-2. 캐싱⭕ 인덱싱❌</h4>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/424d0bfb-4b09-4699-a879-1aa62abc665c/image.png" alt=""></p>
<p>1) <code>http_req_duration</code> (avg: 2.78s):
avg 8s ➡️ avg 2.78s</p>
<p>2) <code>http_req_failed</code> (10.43%):
31.36% ➡️ 10.43% 
테스트 중 10.43%의 요청이 실패한 것으로 나타났다. 
1/3의 실패에서 1/10으로 줄어들었다.</p>
<p>3) <code>iterations</code> (61262):
30초 동안 약 61262회의 요청이 완료되었으며, 100회의 요청이 중단되었다.</p>
<p>4) <code>iteration_duration</code> (avg: 12.89s):
12.89s ➡️ 4.83s 
최대값은 16.23s 로 처음 요청의 26.63초보다 10초 줄어들었다. </p>
<p>5) <code>http_req_waiting</code> (avg: 2.76s):
7.99s ➡️2.76s</p>
<p>6) <code>http_req_receiving</code> (avg: 13.35ms):
36.08ms ➡️ 13.35ms </p>
<p>캐싱을 적용하니 훨씬 빨라졌다. </p>
<h4 id="3-1-3-캐싱❌-인덱싱⭕">3-1-3. 캐싱❌ 인덱싱⭕</h4>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/8b9052f0-74e6-44cf-afd8-7a69063f9fcf/image.png" alt="">
랭킹시스템의 쿼리는 위와같이 되어있다. </p>
<p>어떤 컬럼을 적용하는게 가장 성능을 향상시킬지 알아보자. </p>
<h4 id="1-productid">1) productId</h4>
<p>regDate로 검색해서 집계를 하는 쿼리인데 <code>Cardinality</code>가 높은 컬럼은 현재 productId일 것 같다.</p>
<pre><code class="language-sql">CREATE INDEX idx_produtId ON ORDER_PRODUCT (product_id)</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/342da7de-5b9f-4aaf-8d34-86ab68fab0e9/image.png" alt=""></p>
<p>1) <code>http_req_duration</code> (avg: 19.91s):
avg 8s ➡️ avg 19.91s</p>
<p>2) <code>http_req_failed</code> (26.63%):
31.36% ➡️ 26.63%</p>
<p>3) <code>iterations</code> (7708):
22908 ➡️ 7708</p>
<p>4) <code>iteration_duration</code> (avg: 31.35s):
12.89s ➡️ 31.35s 
최대가 58.98s </p>
<p>5) <code>http_req_waiting</code> (avg: 19.57s):
7.99s ➡️ 19.57s </p>
<p>6) <code>http_req_receiving</code> (avg: 6.58ms):
36.08ms ➡️ 6.58ms </p>
<p>👉🏻 http_req_receiving 을 제외하곤 전부 성능이 더 악화됐다!!! 
내가 생각한 인덱스가 단단히 잘못 걸린 듯 하다. </p>
<h4 id="2-regdate">2) regDate</h4>
<p>앞의 productId 인덱스를 삭제하고 regDate로 설정해보았다. </p>
<pre><code class="language-sql">ALTER TABLE order_product DROP INDEX idx_produtId;

CREATE INDEX idx_regDate ON ORDER_PRODUCT (reg_date);</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/91b08706-665b-437f-9173-efcafae44c55/image.png" alt="">
흠..? 딱봐도 성능이 더 안좋아졌다!!!!??</p>
<h4 id="3-1-4-캐싱-⭕-인덱싱⭕">3-1-4 캐싱 ⭕ 인덱싱⭕</h4>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/87c18e4c-d2be-4a45-a426-b6b5f9dfdc78/image.png" alt=""></p>
<p>1) <code>http_req_duration</code> (avg: 5.96s):
avg 8s ➡️ avg 5.96s</p>
<p>2) <code>http_req_failed</code> (19.18%):
31.36% ➡️ 19.18% </p>
<p>3) <code>iterations</code> (27522):</p>
<p>4) <code>iteration_duration</code> (avg: 10.25s):
12.89s ➡️ 10.25s 
최대값은 20.11s</p>
<p>5) <code>http_req_waiting</code> (avg: 5.77s):
7.99s ➡️ 5.77s</p>
<p>6) <code>http_req_receiving</code> (avg: 13.35ms):
36.08ms ➡️ 13.35ms </p>
<p>👉🏻 해당 API에서는 Index를 적용하면 성능이 더 악화되므로 성능개선을 위해선 Index사용 보다는 <strong>캐시</strong>사용이 적절하다.(?)</p>
<h3 id="3-2-주문-조회-api">3-2. 주문 조회 API</h3>
<p>주문 조회는 주문번호를 이용한 단건 조회로 이루어지므로 캐시를 따로 사용하지않는다. 
그러므로 인덱스 유무에 따른 성능 테스트만 해보려고한다. </p>
<h4 id="3-2-1-인덱스❌">3-2-1. 인덱스❌</h4>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/69ea37d9-38d9-44f3-8792-dc1424f680e6/image.png" alt=""></p>
<p>1) <code>http_req_duration</code> (avg: 15.4s)</p>
<p>2) <code>http_req_failed</code> (21.47%)</p>
<p>3) <code>iterations</code> (23743):</p>
<p>4) <code>iteration_duration</code> (avg: 15.4s)</p>
<p>5) <code>http_req_waiting</code> (avg: 11.26s)</p>
<p>6) <code>http_req_receiving</code> (avg: 323.2ms)</p>
<h4 id="3-2-2-인덱스⭕customer_order테이블만">3-2-2. 인덱스⭕(customer_order테이블만)</h4>
<pre><code class="language-sql">CREATE INDEX idx_orderId ON CUSTOMER_ORDER (order_id);</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/743277c3-e2fa-4a76-929f-5fde0c5cd42f/image.png" alt=""></p>
<p>1) <code>http_req_duration</code>
avg 15.4s ➡️ avg 12.41s
2) <code>http_req_failed</code> 
21.47% ➡️ 18.45%
3) <code>iterations</code> 
23743 ➡️ 23255
4) <code>iteration_duration</code> 
15.4s ➡️ 16.28s 
5) <code>http_req_waiting</code>
avg 11.26s ➡️ 12.03s
6) <code>http_req_receiving</code>
avg 323.2ms ➡️ avg 326.02ms </p>
<p>👉🏻일정 부분에서 미세한 성능 향상이 일어났지만 눈에 띄는 변화는 아닌듯 하다. 
왜냐하면 MySQL에서는 기본적으로 PK가 클러스터 인덱스이며, <em>PK가 없으면 내부적으로 PK를 만들어내므로</em> 모든 테이블이 클러스터링 테이블이라고 볼 수 있다.</p>
<p>이미 인덱스로 설정되어있는 pk에 또 인덱스를 건 격이다. </p>
<h4 id="3-2-3-인덱스⭕order_product-인덱스-추가">3-2-3. 인덱스⭕(order_product 인덱스 추가)</h4>
<pre><code class="language-sql">CREATE INDEX idx_orderId ON ORDER_PRODUCT (order_id);</code></pre>
<p>주문 정보 조회는 customer_order테이블과 order_product를 함께 조회하고 있어 두 곳 모두 order_id로 인덱스를 걸어주었다. </p>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/54bd2156-8524-412e-bacd-6bc3bd128694/image.png" alt=""></p>
<p>1) <code>http_req_duration</code>
avg 15.4s ➡️ avg 12.41s ➡️ avg 6.88s ✔️
2) <code>http_req_failed</code> 
21.47% ➡️ 18.45% ➡️ 22.86%
3) <code>iterations</code> 
23743 ➡️ 23255 ➡️35047
4) <code>iteration_duration</code> 
15.4s ➡️ 16.28s ➡️ 9.6s ✔️
5) <code>http_req_waiting</code>
avg 11.26s ➡️ 12.03s ➡️ 6.72s ✔️
6) <code>http_req_receiving</code>
avg 323.2ms ➡️ avg 326.02ms ➡️ 161.06ms ✔️</p>
<p>👉🏻 같은 조회 쿼리를 같은 횟수로 전송하였는데 속도가 눈에 띄게 줄어들었다!</p>
<h3 id="3-3-상품-조회api">3-3. 상품 조회API</h3>
<pre><code class="language-sql">CREATE TABLE product (
     product_id INT PRIMARY KEY AUTO_INCREMENT,
     product_nm VARCHAR(255) NOT NULL,
     price INT NOT NULL,
     category VARCHAR(10) DEFAULT &#39;etc&#39;,
     reg_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
     edit_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

CREATE TABLE product_inventory (
    id INT PRIMARY KEY AUTO_INCREMENT,
    product_id INT NOT NULL,
    amount INT NOT NULL,
    reg_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    edit_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);</code></pre>
<p>상품 테이블과 상품 재고 테이블이다. 
pk는 각각 <code>product_id</code>, <code>id</code>에 걸려있다. 
product_id로 조인되는 형태이므로 위의 결과를 참고하여 예상해보았을 때, <code>product_inventory</code>의 <code>product_id</code>에 index를 건다면 성능향상의 이점을 볼 수 있을 것이다. </p>
<pre><code class="language-java"> @GetMapping()
    public ResponseEntity&lt;?&gt; getProductsInStock() {
        try{
            List&lt;ProductInfo&gt; products = productFacade.getProducts();

            return ResponseUtil.buildSuccessResponse(&quot;현재 재고가 있는 상품들입니다.&quot;, products);
        }
        catch(BusinessException e){
            log.warn(ECommerceExceptions.OUT_OF_STOCK.getMessage());
            return ResponseUtil.buildErrorResponse(ECommerceExceptions.OUT_OF_STOCK, ECommerceExceptions.OUT_OF_STOCK.getMessage());
        }
    }</code></pre>
<p><a href="http://localhost:8080/products">http://localhost:8080/products</a> 는 재고가 존재하는 상품을 모두 반환한다. </p>
<pre><code class="language-javascript">import http from &#39;k6/http&#39;;
import { sleep } from &#39;k6&#39;;

export default function () {

    let url = &#39;http://localhost:8080/products&#39;;
    let params = {
        headers: {
            &#39;Content-Type&#39; : &#39;application/json; charset=utf-8&#39;,
        },
    };
    http.get(url, params);
    sleep(1);
}</code></pre>
<p>현재 상품 데이터를 100개 넣어놨고 재고가 0인 상품은 없으므로 
고객 한명 당 100개의 데이터를 반환해야한다. 
인덱스를 적용하지 않으면 조회만 하는데 굉장한 부하가 생길것이다. 
(1000개 했다가 뻗어서 100개로 줄임..) </p>
<h4 id="3-3-1-인덱스❌-캐시❌">3-3-1. 인덱스❌ 캐시❌</h4>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/bc82c632-30cc-4649-afec-17121316235f/image.png" alt=""></p>
<p>1) <code>http_req_duration</code> (avg: 5.4s)</p>
<p>2) <code>http_req_failed</code> (82.45%)</p>
<p>3) <code>iterations</code> (6132):</p>
<p>4) <code>iteration_duration</code> (avg: 15.49s)</p>
<p>5) <code>http_req_waiting</code> (avg: 5.33s)</p>
<p>6) <code>http_req_receiving</code> (avg: 2.26ms)</p>
<h4 id="3-3-2-인덱스product_id⭕-캐시❌">3-3-2. 인덱스(product_id)⭕ 캐시❌</h4>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/1032a456-1d2a-4ef5-9580-53a1feb8afff/image.png" alt=""></p>
<p>1) <code>http_req_duration</code> 
avg 5.4s ➡️ avg 16.62s
2) <code>http_req_failed</code>
82.45% ➡️ 77.69%</p>
<p>3) <code>iterations</code> 
6132 ➡️ 4351 </p>
<p>4) <code>iteration_duration</code> 
15.49s ➡️ 16.62s
5) <code>http_req_waiting</code> 
avg 5.33s ➡️ 5.95s 
6) <code>http_req_receiving</code> 
avg 2.26ms ➡️ 3.07ms </p>
<p>성능이 더 안 좋아졌다!!!! 
그 이유는  <code>List&lt;ProductInfo&gt; products = productFacade.getProducts()</code> 
여기에 있다. </p>
<pre><code class="language-java">public List&lt;ProductServiceResponse&gt; getProducts(){
        List&lt;ProductInventory&gt; productInventories = productInventoryRepository.findProductsByAmountGreaterThanZero();

        if (productInventories == null) {
            throw new BusinessException(ECommerceExceptions.OUT_OF_STOCK);
        }

        return productInventories.stream()
                .map(inventory -&gt; {
                    Product product = productRepository.findByProductId(inventory.getProductId());

                    return new ProductServiceResponse(
                            product.getProductId(),
                            product.getProductName(),
                            product.getCategory(),
                            product.getPrice(),
                            inventory.getAmount()
                    );
                }).collect(Collectors.toList());

    }</code></pre>
<p><code>productInventoryRepository.findProductsByAmountGreaterThanZero</code> 여기에서 재고가 있는 상품의 product_id를 뽑아서 리스트로 받아온 뒤 product테이블에서 다시 product_id로 조회하는데, 상품 개수가 작을 때는 문제없이 반환되지만 
조회 결과가 많으면 성능저하의 원인인 ✔️<strong>N+1문제</strong>가 발생할 수 있다. </p>
<p>ProductInventory 데이터가 많아질수록, 각 Product를 개별적으로 조회하기 때문에 데이터베이스와의 왕복 요청이 많이 발생하게 된다. 이 경우 ProductInventory 목록을 가져오는 1개의 쿼리와 각 Product를 조회하는 N개의 쿼리가 실행되므로 총 1 + N개의 쿼리가 발생하게 된다. </p>
<p>그래서, product와 product_inventory를 조인해서 가져오는 방식으로 변환하였다. </p>
<pre><code class="language-java">public List&lt;ProductServiceResponse&gt; getProducts() {
        List&lt;ProductServiceResponse&gt; products = productInventoryRepository.findProductsWithInventoryGreaterThanZero();

        if (products.isEmpty()) {
            throw new BusinessException(ECommerceExceptions.OUT_OF_STOCK);
        }

        return products;
    }</code></pre>
<p>ProductService.java </p>
<pre><code class="language-java">@Query(&quot;SELECT new com.tdd.ecommerce.product.application.ProductServiceResponse(p.productId, p.productName, p.category, p.price, pi.amount) &quot; +
            &quot;FROM ProductInventory pi &quot; +
            &quot;INNER JOIN Product p &quot; +
            &quot;ON pi.productId = p.productId &quot; +
            &quot;WHERE pi.amount &gt; 0&quot;)
    List&lt;ProductServiceResponse&gt; findProductsWithInventoryGreaterThanZero();</code></pre>
<p>ProductInventoryJpaRepository.java</p>
<h4 id="3-3-3-인덱스product_id⭕-캐시❌">3-3-3. 인덱스(product_id)⭕ 캐시❌</h4>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/2dbb738f-2a35-4f2e-a4da-d4f7997fa455/image.png" alt="">
이번엔 변경된 코드로 다시 실행해보았다. </p>
<p>1) <code>http_req_duration</code> 
avg 5.4s ➡️ avg 16.62s ➡️ 15.93s</p>
<p>2) <code>http_req_failed</code>
82.45% ➡️ 77.69% ➡️ 19.02%</p>
<p>3) <code>iterations</code> 
6132 ➡️ 4351 ➡️ 19852</p>
<p>4) <code>iteration_duration</code> 
15.49s ➡️ 16.62s ➡️ 15.93s</p>
<p>5) <code>http_req_waiting</code> 
avg 5.33s ➡️ 5.95s ➡️ 7.06s</p>
<p>6) <code>http_req_receiving</code> 
avg 2.26ms ➡️ 3.07ms ➡️ 2.32ms
👉🏻 전체 처리 개수는 눈에 띄게 늘었으나 응답속도에서는 눈에띄는 향상이 일어나지 않았다. </p>
<h4 id="3-3-3-인덱스amount-product_id⭕-캐시❌">3-3-3. 인덱스(amount, product_id)⭕ 캐시❌</h4>
<pre><code class="language-sql">CREATE INDEX idx_product_inventory_amount_product_id ON PRODUCT_INVENTORY(amount, product_id);</code></pre>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/497024cf-4c3c-46cf-b830-006928bdc1cd/image.png" alt=""></p>
<p>1) <code>http_req_duration</code> 
avg 5.4s ➡️ avg 16.62s ➡️ avg 15.93s ➡️ avg 10.35s</p>
<p>2) <code>http_req_failed</code>
82.45% ➡️ 77.69% ➡️ 19.02% ➡️ 15.18%</p>
<p>3) <code>iterations</code> 
6132 ➡️ 4351 ➡️ 19852 ➡️ 32657</p>
<p>4) <code>iteration_duration</code> 
15.49s ➡️ 16.62s ➡️ 15.93s ➡️ 15.28s</p>
<p>5) <code>http_req_waiting</code> 
avg 5.33s ➡️ 5.95s ➡️ 7.06s ➡️ 10.17s</p>
<p>6) <code>http_req_receiving</code> 
avg 2.26ms ➡️ 3.07ms ➡️ 2.32ms ➡️ 111.51 ms </p>
<p>👉🏻 이 친구도 처리속도는 별로 나아지지 않았으나 더 많은 부하를 견딜 수 있는 것으로 파악된다. </p>
<p>해당 내용을 개선할 수 있는 방법은 여러가지이다. </p>
<ul>
<li>캐싱의 사용</li>
<li>FETCH JOIN의 사용 &gt; 지양하는편..! </li>
<li>데이터 페이징 </li>
</ul>
<h4 id="3-3-4-인덱스amount-product_id⭕-캐시⭕">3-3-4. 인덱스(amount, product_id)⭕ 캐시⭕</h4>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/a40cf709-74b8-4c43-aef3-56b719d7e822/image.png" alt=""></p>
<p>1) <code>http_req_duration</code> 
avg 5.4s ➡️ avg 16.62s ➡️ avg 15.93s ➡️ avg 10.35s ➡️ 4.03s </p>
<p>2) <code>http_req_failed</code>
82.45% ➡️ 77.69% ➡️ 19.02% ➡️ 15.18% ➡️ 11.88%</p>
<p>3) <code>iterations</code> 
6132 ➡️ 4351 ➡️ 19852 ➡️ 32657 ➡️ 444528</p>
<p>4) <code>iteration_duration</code> 
15.49s ➡️ 16.62s ➡️ 15.93s ➡️ 15.28s ➡️ 6.98s</p>
<p>5) <code>http_req_waiting</code> 
avg 5.33s ➡️ 5.95s ➡️ 7.06s ➡️ 10.17s ➡️ 4s </p>
<p>6) <code>http_req_receiving</code> 
avg 2.26ms ➡️ 3.07ms ➡️ 2.32ms ➡️ 111.51 ms ➡️ 20.41ms </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DataBase]DB Query Optimization(Index)]]></title>
            <link>https://velog.io/@dev_nana/DataBaseDB-Query-OptimizationIndex</link>
            <guid>https://velog.io/@dev_nana/DataBaseDB-Query-OptimizationIndex</guid>
            <pubDate>Sat, 09 Nov 2024 14:48:39 GMT</pubDate>
            <description><![CDATA[<h1 id="0-index란">0. Index란?</h1>
<p>1000페이지 짜리의 데이터베이스 책이 있다고 상상해보자. 
Index에 관한 내용을 찾고 싶은데 책의 앞에 위치한 &quot;목차&quot; 또는 &quot;INDEX&quot; 가 없다면 우리는 처음부터 끝까지 책을 읽어서 인덱스에 관한 내용을 찾아내야할 것이다. </p>
<p>딱 맞는 인덱스를 찾는다면 몇 시간이 걸려서 찾을 수도 있는 내용을 한번에 그 페이지로 점프해서 찾을 수 있다! </p>
<p>이처럼 인덱스는, <em>정렬</em>되어있고 정보를 찾기 위한 <em>페이지 정보</em>를 주기 때문에 색인 시간을 확 줄일 수 있다! </p>
<h2 id="database에서의-🔍index">Database에서의 🔍Index</h2>
<blockquote>
<p>추가적인 쓰기 작업과 저장 공간을 활용하여 데이터베이스 테이블의 검색 속도를 향상시키기 위한 자료구조. </p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/2e1bcfba-778b-4da5-99fc-2580ef97f3ae/image.png" alt=""></p>
<p>인덱스 된 열 값과 함께 테이블 해당 행에 대한 포인터가 보관된다. 
인덱스가 없다면 데이터 베이스는 모든 테이블을 읽는<code>FullScan</code>을 하게 될 것이고 성능은 매우 느려질 것이다. </p>
<h1 id="1-인덱스-사용시-고려해야할-점--사용하는-이유">1. 인덱스 사용시 고려해야할 점 &amp; 사용하는 이유?</h1>
<h2 id="인덱스-사용에-고려해야할-점">인덱스 사용에 고려해야할 점</h2>
<ul>
<li>한번에 찾을 수 있는 값 : 데이터 중복이 적은 컬럼 </li>
<li>인덱스 재정렬 최소화 : 데이터 삽입, 수정이 적은 컬럼</li>
<li>인덱스 목적은 <strong>검색</strong> : 조회에 자주 사용되는 컬럼 (ex. JOIN / WHERE / ORDER BY)
👉🏻 만약 CREATE, DELETE, UPDATE가 빈번한 속성에 인덱스를 걸게 되면 <strong>인덱스의 크기가 비대해져서 성능이 오히려 저하되는 역효과</strong>가 발생할 수 있다. 
👉🏻 UPDATE와 DELETE는 기존의 인덱스를 삭제하지 않고 &#39;사용하지 않음&#39;처리를 하기 때문이다. </li>
<li>너무 많지 않은 인덱스 : 인덱스 또한 공간을 차지함 </li>
<li>규모가 작지 않은 테이블 </li>
</ul>
<h2 id="인덱스를-사용하는-이유">인덱스를 사용하는 이유</h2>
<p>인덱스를 활용하면 조회뿐만아니라 UPDATE / DELETE의 성능이 함께 향상된다. 
왜냐면 연산을 수행하기 위해선 조건에 대한 SELECT가 먼저 수행되어야 하기 때문이다. </p>
<h2 id="인덱스의-장단점">인덱스의 장단점</h2>
<ul>
<li><p>장점 </p>
<ul>
<li>테이블을 조회하는 속도와 그에 따른 성능 향상 가능. </li>
<li>전반적인 시스템의 부하를 줄일 수 있다. </li>
</ul>
</li>
<li><p>단점 </p>
<ul>
<li>인덱스를 관리하기 위해 DB의 약 10%에 해당하는 저장공간이 필요하다. </li>
<li>인덱스를 관리하기 위해 추가 작업이 필요하다. </li>
<li>인덱스 잘못 사용하면 오히려 성능이 저하된다. </li>
</ul>
</li>
</ul>
<h1 id="2-인덱스-생성하는-법">2. 인덱스 생성하는 법</h1>
<h2 id="단일-인덱스">단일 인덱스</h2>
<pre><code class="language-sql">CREATE ITEM
(
        ITEM_NM VARCHAR(30)
    ,    ITEM_PRICE INT
    ,    CATEGORY VARCHAR(20)
    ,    AMOUNT INT 
)</code></pre>
<p>이런 간단한 ITEM테이블이 있다고 할 때 인덱스는 <em>데이터 중복이 적은 컬럼</em>이어야 한다. 
또한 조회에 자주 사용되는 컬럼이라는 점 등을 미뤄보았을 때 현재 테이블에서는 ITEM_NM이 적절해 보인다. </p>
<pre><code class="language-mysql">CREATE INDEX idx_item_nm ON item (item_nm);</code></pre>
<p>이런식으로 인덱스를 생성해 주면 된다. </p>
<h2 id="복합-인덱스">복합 인덱스</h2>
<p>여러 컬럼을 조합하여 인덱스를 만드는 것도 가능하다. 
이런 경우, 일반적으로 <strong>카디널리티가 높은 순</strong>으로 배치한다. </p>
<blockquote>
<p>*<em>✔️카디널리티(Cardinality) *</em>
데이터의 중복 수치를 말하며, 카디널리티가 높다 = 중복이 적다 를 뜻한다. </p>
</blockquote>
<pre><code class="language-sql">CREATE INDEX idx_item_info1 ON item (item_nm, item_price);</code></pre>
<p>이렇게 복합인덱스를 설정하면 좀 더 유용한 검색 쿼리를 사용할 수 있다. </p>
<h1 id="3-인덱스의-자료구조">3. 인덱스의 자료구조</h1>
<p>인덱스를 구현하기 위한 가장 대표적인 자료구조로 <code>해시테이블</code>과 <code>B+ Tree</code>가 있다. </p>
<h2 id="해시-테이블hash-table">해시 테이블(Hash Table)</h2>
<ul>
<li>(Key, Value)로 데이터를 저장함. </li>
<li>빠른 데이터 검색이 필요할 때 유용함.  &gt; Key값을 이용해 고유한 index를 생성하여 그 index에 저장된 값을 꺼내오는 구조. </li>
</ul>
<p><img src="https://velog.velcdn.com/images/dev_nana/post/0b417d0c-6211-4016-8e7d-e0452c254da9/image.png" alt=""></p>
<ul>
<li>시간복잡도는 O(1), 매우 빠른 검색 지원함. </li>
<li>등호(=) 연산에만 특화되었기 때문에 값이 1이라도 달라지면 완전히 다른 해시값을 생성한다. </li>
<li>부등호 연산(&lt;, &gt;)이 자주 사용되는 데이터 베이스 검색을 위해서는 해시테이블이 적합하지 않다. </li>
</ul>
<h2 id="btree">B+Tree</h2>
<ul>
<li>DB의 인덱스를 위해 <strong>자식 노드가 2개 이상인 B-Tree를 개선시킨 자료구조</strong>. </li>
<li>리프노드(데이터노드)만 인덱스와 함께 데이터(Value)를 가지고 있고, 나머지 노드(인덱스노드)들은 데이터를 위한 인덱스(Key)만을 갖는다.</li>
<li>리프노드들은 LinkedList로 연결되어 있다.</li>
<li>데이터 노드 크기는 인덱스 노드의 크기와 같지 않아도 된다.</li>
</ul>
<p>데이터베이스의 인덱스 컬럼은 부등호를 이용한 순차 검색 연산이 자주 발생될 수 있다. 
때문에 BTree의 리프노드들을 LinkedList로 연결하여 순차 검색을 용이하게 하는 등 BTree를 인덱스에 맞게 최적화 한다. 
👉🏻 B+Tree는 O(log2nlog2n{log_2n}) 의 시간복잡도를 갖지만 해시테이블보다 인덱싱에 더욱 적합한 자료구조가 되었다.</p>
<h1 id="4-인덱스-사용-시-유의할-점">4. 인덱스 사용 시 유의할 점</h1>
<h3 id="4-1-좌변은-건드리지-않는다">4-1. 좌변은 건드리지 않는다.</h3>
<pre><code class="language-sql">## price 컬럼에 Index 가 적용된 경우

# 올바른 인덱스 사용
where price &gt; 10000 / 100; // price 컬럼에 대한 Index Search

## 잘못된 인덱스 사용
where price * 100 &gt; 10000; // price * 100 에 대한 Index X</code></pre>
<p>좌변(인덱스가 존재하는 쪽)을 변경하게 되면 price에 전부 100을 곱한 뒤 찾게된다 ㅎㄷㄷ..</p>
<h3 id="4-2-like-between--등-범위-조건의-컬럼은-index가-적용되지만-그-뒤-컬럼은-index가-적용되지-않는다">4-2. LIKE, BETWEEN, &lt;, &gt;등 범위 조건의 컬럼은 Index가 적용되지만 그 뒤 컬럼은 Index가 적용되지 않는다.</h3>
<h3 id="4-3-and는-row를-줄이지만-or는-비교를-위해-row를-늘리므로-full-scan발생확률이-높다">4-3. AND는 ROW를 줄이지만 OR는 비교를 위해 ROW를 늘리므로 FULL-SCAN발생확률이 높다.</h3>
<p>WHERE절에서 OR연산을 사용할 때는 이를 고려해야한다. </p>
<h3 id="4-4-in--은-다음-컬럼도-인덱스를-사용한다">4-4. IN, = 은 다음 컬럼도 인덱스를 사용한다.</h3>
<p>IN은 = 연산을 여러번 수행한 것이므로 다음 컬럼도 인덱스를 태울 수 있다. </p>
<hr>
<p><a href='https://mangkyu.tistory.com/96'>[Database] 인덱스(index)란?</a>
<a href='https://mangkyu.tistory.com/286'>[MySQL] B-Tree로 인덱스(Index)에 대해 쉽고 완벽하게 이해하기</a>
<a href='https://blog.algomaster.io/p/a-detailed-guide-on-database-indexes'>Database Indexes: A detailed guide</a></p>
]]></description>
        </item>
    </channel>
</rss>