<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>sweet_sumin.log</title>
        <link>https://velog.io/</link>
        <description>배우는 것이 즐겁다!</description>
        <lastBuildDate>Fri, 29 Aug 2025 05:32:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>sweet_sumin.log</title>
            <url>https://velog.velcdn.com/images/sweet_sumin/profile/a5733270-4322-4d67-98f4-366d09938739/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. sweet_sumin.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/sweet_sumin" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[@TransactionalEventListener 내부 동작 보기]]></title>
            <link>https://velog.io/@sweet_sumin/TransactionalEventListener-%EB%82%B4%EB%B6%80-%EB%8F%99%EC%9E%91-%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@sweet_sumin/TransactionalEventListener-%EB%82%B4%EB%B6%80-%EB%8F%99%EC%9E%91-%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Fri, 29 Aug 2025 05:32:00 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-개요">📌 개요</h2>
<p>스프링을 사용하다 보면 이벤트 기반 아키텍처를 자주 접하게 된다. 그 중 많이 쓰는 것이 @TransactionalEventListener인데, 적용과 별개로 내부 동작을 파헤치는 곳이 없는 것 같아 아주 조금 확인해보는 시간을 가지려고 한다. </p>
<p>@TransactionalEventListener는 Spring의 트랜잭션 동기화(Transaction Synchronization) 메커니즘을 활용해 이벤트 실행 시점을 트랜잭션의 상태와 결합합니다. 핵심적으로, 이벤트가 발행될 때 즉시 실행되지 않고, 트랜잭션의 특정 단계(커밋, 롤백 등)에 맞춰 실행되도록 &#39;예약&#39;하는 방식으로 동작한다.</p>
<h2 id="📌-기본-개념">📌 기본 개념</h2>
<h3 id="기본-이벤트-리스너-vs-트랜잭셔널-이벤트-리스너">기본 이벤트 리스너 vs 트랜잭셔널 이벤트 리스너</h3>
<h4 id="기본-이벤트-리스너">기본 이벤트 리스너</h4>
<pre><code>@Component
class OrderEventHandler {

    @EventListener
    fun handle(event: PaidCompleteEvent) {
        log.info(&quot;일반 이벤트 리스너 호출: ${event.orderUUId}&quot;)
    }
}</code></pre><ul>
<li><p>ApplicationEventPublisher.publishEvent(event) 호출 시 즉시 실행</p>
</li>
<li><p>트랜잭션과 무관하게 동작</p>
</li>
</ul>
<h4 id="트랜잭셔널-이벤트-리스너">트랜잭셔널 이벤트 리스너</h4>
<pre><code>@Component
class OrderEventHandler {

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    fun handle(event: PaidCompleteEvent) {
        log.info(&quot;트랜잭션 커밋 이후 실행: ${event.orderUUId}&quot;)
    }
}</code></pre><ul>
<li>트랜잭션 경계와 결합</li>
<li>phase에 따라 실행 시점 제어 가능</li>
</ul>
<h3 id="실행-시점-transactionphase">실행 시점 (TransactionPhase)</h3>
<p>@TransactionalEventListener는 트랜잭션의 수명 주기에 맞춰 이벤트를 실행한다.</p>
<table>
<thead>
<tr>
<th>Phase</th>
<th>When</th>
</tr>
</thead>
<tbody><tr>
<td><code>BEFORE_COMMIT</code></td>
<td>커밋 직전에 실행</td>
</tr>
<tr>
<td><code>AFTER_COMMIT</code></td>
<td>커밋 성공 직후 실행 (기본값)</td>
</tr>
<tr>
<td><code>AFTER_ROLLBACK</code></td>
<td>롤백 직후 실행</td>
</tr>
<tr>
<td><code>AFTER_COMPLETION</code></td>
<td>커밋/롤백 완료 후 실행 (성공/실패 무관)</td>
</tr>
</tbody></table>
<h2 id="📌-내부-동작">📌 내부 동작</h2>
<h3 id="1-이벤트-발행">1. 이벤트 발행</h3>
<pre><code>eventPublisher.publishEvent(new PaidCompleteEvent(...));
</code></pre><ul>
<li>이 시점에서는 즉시 실행되지 않는다.</li>
<li>대신, TransactionSynchronizationManager에 &quot;트랜잭션 끝날 때 실행할 콜백&quot;을 등록한다.</li>
</ul>
<h3 id="2-transactionaleventlistener의-특별한-처리">2. @TransactionalEventListener의 특별한 처리</h3>
<p>스프링 부트가 실행될 때, TransactionalEventListenerFactory라는 BeanPostProcessor가 등록된다.
<img src="https://velog.velcdn.com/images/sweet_sumin/post/55261860-5215-47f5-a356-ae50d2f0f79c/image.png" alt=""></p>
<p>이 Factory는 @TransactionalEventListener가 붙은 메서드를 스캔한다. 해당 메서드를 TransactionalApplicationListener라는 특별한 리스너 객체로 감싼다. 즉, &quot;이 메서드는 단순한 이벤트 리스너가 아니라, 트랜잭션 경계와 관련된 특별한 리스너&quot;라고 스프링에 알려주는 장치이다.
<strong>역할</strong>: @TransactionalEventListener가 붙은 메서드를 스프링 이벤트 리스너로 등록하는 팩토리</p>
<blockquote>
<p>💡 주요 메서드</p>
</blockquote>
<ul>
<li><strong>supportsMethod : 특정 메서드가 이벤트 리스너로 등록될 수 있는지 판단</strong></li>
<li><strong>createApplicationListener : 실제 이벤트를 처리할 ApplicationListener 인스턴스를 생성</strong><ul>
<li><ol>
<li>일반 이벤트 리스너는 ApplicationListenerMethodAdapter를 쓰지만,
@TransactionalEventListener는 TransactionalApplicationListenerMethodAdapter를 쓴다.</li>
</ol>
</li>
<li><ol start="2">
<li>이 어댑터가 트랜잭션과 연동되어 이벤트 실행 시점을 BEFORE_COMMIT, AFTER_COMMIT, AFTER_ROLLBACK, AFTER_COMPLETION으로 맞춰 준다.</li>
</ol>
</li>
</ul>
</li>
</ul>
<h3 id="3-메서드와-이벤트를-연결하는-adapter--transactionalapplicationlistenermethodadapter">3. 메서드와 이벤트를 연결하는 Adapter : TransactionalApplicationListenerMethodAdapter</h3>
<ul>
<li>역할 : @TransactionalEventListener 메서드를 감싸서 트랜잭션 라이프사이클에 맞게 실행 시점을 조정하는 어댑터</li>
</ul>
<ol>
<li>@TransactionalEventListener가 붙은 메서드를 감싸서 <strong>트랜잭션 동기화 매니저(TransactionSynchronizationManager)</strong>에 등록</li>
<li>지정된 트랜잭션 Phase(BEFORE_COMMIT, AFTER_COMMIT, AFTER_ROLLBACK, AFTER_COMPLETION)에 이벤트 실행 시점을 맞춤</li>
</ol>
<p>내부에 여러 메서드가 있지만 내가 생각했을때 onApplicationEvent 메서드가 핵심인것 같다. 
<img src="https://velog.velcdn.com/images/sweet_sumin/post/3ed54c99-75b2-4cd7-861b-d7e72c9b88d7/image.png" alt=""></p>
<h4 id="트랜잭션이-활성화되어-있을-때">트랜잭션이 활성화되어 있을 때</h4>
<ul>
<li><p><strong>TransactionalApplicationListenerSynchronization.register(...) 호출 시 이벤트를 즉시 실행하지 않고 예약한다.</strong></p>
</li>
<li><p>예약된 이벤트는 트랜잭션이 커밋되거나 롤백될 때 실행된다.</p>
</li>
<li><p>내부적으로 TransactionSynchronizationManager에 등록되어 트랜잭션과 이벤트 실행을 동기화한다.</p>
</li>
</ul>
<h4 id="트랜잭션이-없지만-fallback-실행이-허용된-경우">트랜잭션이 없지만 fallback 실행이 허용된 경우</h4>
<ul>
<li>isDefaultExecution()이 true이면, 트랜잭션 없이도 이벤트를 즉시 실행한다.</li>
<li>단, Phase가 AFTER_ROLLBACK인 경우 경고 로그를 남긴다.<h4 id="트랜잭션도-없고-fallback도-허용되지-않은-경우">트랜잭션도 없고 fallback도 허용되지 않은 경우</h4>
</li>
<li>이벤트는 무시되고 실행되지 않는다.</li>
</ul>
<p>이 메서드 덕분에 트랜잭션 커밋 이후에만 실행해야 하는 로직과, 트랜잭션과 상관없이 바로 실행해도 되는 로직을 명확히 구분할 수 있는 것 같다.</p>
<h4 id="역할-정리">역할 정리</h4>
<p>너무 이해하기 어렵다. 역할을 단순히 정리를 하자면</p>
<ul>
<li>트랜잭션 활성 여부 판단</li>
<li>이벤트를 즉시 실행할지, 등록할지, 무시할지 결정</li>
<li>등록이 성공하면 실제 이벤트 실행은 Synchronization에게 위임</li>
</ul>
<h3 id="4-트랜잭션-시점에-맞춰-이벤트-실행을-담당--transactionalapplicationlistenersynchronization">4. 트랜잭션 시점에 맞춰 이벤트 실행을 담당 : TransactionalApplicationListenerSynchronization</h3>
<h4 id="이벤트-실행-자체와-콜백-관리를-책임지는-핵심-메서드--processeventwithcallbacks">이벤트 실행 자체와 콜백 관리를 책임지는 핵심 메서드:  processEventWithCallbacks()</h4>
<p><img src="https://velog.velcdn.com/images/sweet_sumin/post/8e57ee6e-bb30-4ff0-9c93-adb767325e2d/image.png" alt=""></p>
<ul>
<li>역할: 트랜잭션 시점에 이벤트를 실제로 실행</li>
<li>콜백 처리: 이벤트 실행 전(preProcessEvent), 후(postProcessEvent) 콜백 지원</li>
<li>예외 처리: 이벤트 실행 중 예외 발생 시 postProcessEvent에 예외 전달</li>
</ul>
<h4 id="이벤트-실행-메서드--beforecommit-aftercompletion">이벤트 실행 메서드 : beforeCommit, afterCompletion</h4>
<p><img src="https://velog.velcdn.com/images/sweet_sumin/post/170e73e4-b9ea-49ae-8b3f-0f9f42e1af0e/image.png" alt=""></p>
<ul>
<li>트랜잭션 커밋 전 : beforeCommit (TransactionPhase.BEFORE_COMMIT일 때만)</li>
<li>트랜잭션이 완료된 후 : afterCompletion</li>
</ul>
<h4 id="클래스-역할-정리">클래스 역할 정리</h4>
<ul>
<li>이벤트를 트랜잭션 Commit/Rollback 시점에 실행</li>
<li>BEFORE_COMMIT, AFTER_COMMIT, AFTER_ROLLBACK, AFTER_COMPLETION 모두 처리 가능</li>
<li>이벤트 실행 전후로 콜백(pre/post) 처리 가능</li>
<li>트랜잭션이 없으면 fallbackExecution 옵션에 따라 즉시 실행하거나 무시</li>
</ul>
<h2 id="📌-정리">📌 정리</h2>
<ol>
<li><p><strong>이벤트 발행</strong>: 서비스 계층에서 ApplicationEventPublisher.publishEvent()가 호출</p>
</li>
<li><p><strong>어댑터 실행</strong>: TransactionalApplicationListenerMethodAdapter의 onApplicationEvent 메서드가 호출.</p>
</li>
<li><p><strong>트랜잭션 동기화 등록</strong>: 현재 활성화된 트랜잭션이 있으면, 이벤트와 그 실행 정보를 담은 TransactionalApplicationListenerSynchronization 객체를 TransactionSynchronizationManager에 등록.</p>
</li>
<li><p><strong>트랜잭션 종료</strong>: 트랜잭션이 커밋되거나 롤백되면, TransactionSynchronizationManager는 등록된 TransactionalApplicationListenerSynchronization 객체의 콜백 메서드(beforeCommit 또는 afterCompletion)를 호출.</p>
</li>
<li><p><strong>이벤트 실행</strong>: 콜백 메서드는 @TransactionalEventListener에 지정된 TransactionPhase에 따라 예약된 이벤트 핸들러 메서드를 실제로 호출.</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[트랜잭션 상태를 한눈에! AOP 활용 롤백 테스트 구현기]]></title>
            <link>https://velog.io/@sweet_sumin/%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EC%83%81%ED%83%9C%EB%A5%BC-%ED%95%9C%EB%88%88%EC%97%90-AOP-%ED%99%9C%EC%9A%A9-%EB%A1%A4%EB%B0%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B5%AC%ED%98%84%EA%B8%B0</link>
            <guid>https://velog.io/@sweet_sumin/%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EC%83%81%ED%83%9C%EB%A5%BC-%ED%95%9C%EB%88%88%EC%97%90-AOP-%ED%99%9C%EC%9A%A9-%EB%A1%A4%EB%B0%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B5%AC%ED%98%84%EA%B8%B0</guid>
            <pubDate>Fri, 08 Aug 2025 05:23:26 GMT</pubDate>
            <description><![CDATA[<h1 id="📌-개요">📌 개요</h1>
<p>트랜잭션은 데이터의 일관성을 보장하기 위한 핵심 메커니즘이다.특히 복잡한 비즈니스 로직에서 여러 서비스가 얽혀 있을 때, 어느 한 단계라도 실패하면 전체 작업이 롤백되어야 한다.</p>
<p>이번 글에서는 스프링 트랜잭션 롤백이 잘 동작하는지 테스트하는 방법과
AOP를 활용해 트랜잭션 상태(롤백 여부, 예외 발생 여부)를 실시간으로 추적하는 방법을 주문 기능과 함께 이야기해보려고 한다. </p>
<p>사실 난 지금까지 부끄럽지만 롤백 테스트를 postman을 이용한 실테스트로 진행해왔다. 이번에는 롤백 테스트도 테스트 코드로 해낼 수 있지 않을까?에서 부터 시작된 이야기이다.</p>
<h1 id="📌-트랜잭션-롤백-테스트의-필요성">📌 트랜잭션 롤백 테스트의 필요성</h1>
<p>예를 들어, 주문 생성 시 재고 차감과 결제 포인트 차감이 동시에 이루어진다.
만약 재고 차감에서 예외가 발생하면, 결제 역시 취소되어야 한다.</p>
<p>이를 보장하기 위해서는 전체 주문 프로세스가 하나의 트랜잭션 내에서 실행되어야 하며,한 부분이라도 실패하면 전체가 롤백되어야 한다.</p>
<h1 id="📌-필요한-구성-요소">📌 필요한 구성 요소</h1>
<h3 id="1-트랜잭션-상태-정보-저장용-객체-transactiontraceinfo">1. 트랜잭션 상태 정보 저장용 객체 (TransactionTraceInfo)</h3>
<p>트랜잭션 실행 중 발생한 상태(어떤 메서드에서, 어떤 인자와 함께 실행됐고, 롤백 상태인지, 예외가 있었는지)를 한 곳에 모아 구조화된 데이터로 관리하기 위해 필요하다.</p>
<ul>
<li>메서드 이름과 인자를 기록해 어떤 트랜잭션인지 추적 가능</li>
<li>롤백 여부를 알면 트랜잭션 성공/실패를 판단할 수 있음</li>
<li>예외 발생 여부로 문제 발생 원인 분석 가능</li>
</ul>
<h3 id="2-저장소transactiontraceholder">2. 저장소(TransactionTraceHolder)</h3>
<p>트랜잭션 상태 정보를 스레드 로컬(ThreadLocal) 에 안전하게 저장하고 꺼내는 저장소 역할이다. 동시에 여러 요청이 병렬로 처리되는 서버 환경에서, 각 요청별로 트랜잭션 상태를 독립적으로 관리하기 위해 필요하다.
ThreadLocal은 각 스레드마다 독립된 저장 공간을 제공하기 때문에, 여러 스레드가 동시에 접근해도 데이터가 섞이지 않는다.</p>
<ul>
<li>ThreadLocal을 이용해 스레드별 독립 저장소 제공</li>
<li>여러 스레드가 하나의 전역 변수나 공유 객체를 건드리는 것을 방지해 데이터 충돌, 혼선 방지</li>
<li>테스트나 로그에서 각 트랜잭션의 상태를 정확히 조회하기 위한 안정적이고 안전한 상태 보관소 역할</li>
<li>요청 처리가 끝나면 clear()로 상태 제거해 메모리 누수 및 정보 오염 방지
<img src="https://velog.velcdn.com/images/sweet_sumin/post/65264cc1-b522-459e-b627-dd3b7a7737bf/image.png" alt=""></li>
</ul>
<h3 id="3-aop-처리기-transactiontraceaspect">3. AOP 처리기 (TransactionTraceAspect)</h3>
<p>Spring의 AOP(Aspect-Oriented Programming)를 사용해 @Transactional이 붙은 메서드 호출 시점에 개입하여 트랜잭션 상태(rollback 여부, 예외 발생 여부 등)를 추적하고 기록하는 클래스가 필요하다. 즉, 트랜잭션 동작을 감시하는 관찰자 역할을 하는 TransactionTraceAspect를 만들어냈다. 
이를 통해, 트랜잭션 내부에서 발생한 상태(성공, 실패, 롤백 여부)를 코드 변경 없이도 획득할 수 있어 테스트나 로깅등을 할 수 있다. </p>
<ul>
<li>트랜잭션 동작을 관찰하는 역할</li>
<li>별도 코드 수정 없이 트랜잭션 상태 정보 획득 가능</li>
<li>테스트 및 로깅에 활용 가능
<img src="https://velog.velcdn.com/images/sweet_sumin/post/206074e3-ab76-4d29-9b5d-faf03eded3c0/image.png" alt=""></li>
</ul>
<h1 id="📌-트랜잭션-롤백-테스트와-상태-추적-aop의-원리">📌 트랜잭션 롤백 테스트와 상태 추적 AOP의 원리</h1>
<ol>
<li>스프링 트랜잭션 관리 원리
스프링은 AOP를 구현하기 위해 프록시 패턴을 사용합니다. @Transactional이 붙은 클래스에 대해 동적 프록시를 생성하고, 이 프록시를 통해 메서드 호출을 가로채 트랜잭션 경계를 설정한다.</li>
</ol>
<ul>
<li>스프링은 @Transactional이 붙은 클래스에 대해 프록시 객체를 생성한다.</li>
<li>클라이언트가 실제 객체의 메서드를 호출하면, 스프링은 프록시 객체를 통해 호출을 가로챈다.</li>
<li>프록시는 트랜잭션을 시작하고, 실제 객체의 메서드를 실행한다.</li>
<li>메서드가 정상 종료되면 프록시는 트랜잭션을 커밋한다.</li>
<li>메서드 실행 중 예외가 발생하면 프록시는 트랜잭션을 롤백하여 데이터 일관성을 보장한다.</li>
</ul>
<ol start="2">
<li>트랜잭션 롤백 테스트 원리</li>
</ol>
<ul>
<li>테스트는 @Transactional이 적용된 최상위 메서드를 대상으로 한다.</li>
<li>이때 모킹 프레임워크(Mockito 등)를 사용하여 내부 서비스 메서드에 강제로 예외를 발생시킨다.</li>
<li>최상위 메서드는 이 예외를 감지하고, 스프링의 트랜잭션 관리자에 의해 롤백이 유도된다.</li>
<li>테스트는 이 롤백이 실제로 발생했는지 검증하여 트랜잭션이 올바르게 동작하는지 확인한다.</li>
</ul>
<ol start="3">
<li>트랜잭션 상태 추적을 위한 AOP 원리</li>
</ol>
<ul>
<li>AOP(Aspect Oriented Programming)를 이용해 @Transactional 메서드 실행 시점 전후에 추가 로직을 삽입한다.</li>
<li>메서드 실행 전, AOP는 ThreadLocal 기반 저장소(TransactionTraceHolder)에 트랜잭션 추적 정보를 기록한다.</li>
<li>메서드 실행 중 예외가 발생하면 AOP는 이를 감지하고, 트랜잭션 상태(TransactionAspectSupport.currentTransactionStatus())를 읽어와 롤백 여부, 예외 발생 여부를 추적 정보에 업데이트한다.</li>
<li>이 상태 정보는 테스트 코드나 로깅 시스템에서 참조할 수 있어, 트랜잭션의 동작을 정확히 파악할 수 있다.</li>
</ul>
<h2 id="테스트-진행">테스트 진행</h2>
<p><img src="https://velog.velcdn.com/images/sweet_sumin/post/59f30e3c-3fc8-44e8-ad0f-52aac252c532/image.png" alt="">
롤백 테스트 중 재고차감 실패시 전체 트랜잭션이 롤백되는지의 여부를 확인하는 테스트이다. </p>
<h3 id="핵심-메커니즘">핵심 메커니즘</h3>
<ul>
<li>TransactionAspectSupport.currentTransactionStatus()를 호출해 현재 스프링 트랜잭션의 상태를 얻는다.</li>
<li>isRollbackOnly 값이 true면, 해당 트랜잭션은 롤백 대기 상태임을 의미한다.</li>
<li>@Around 어드바이스는 예외 발생 여부를 감지하고, 메서드 실행 결과와 함께 상태 정보를 저장한다.</li>
</ul>
<h2 id="소감">소감</h2>
<p>처음에는 트랜잭션 롤백 테스트가 복잡하고 어려운 주제로 느껴졌다.특히 여러 서비스가 엮여 있고, 동시에 발생하는 예외 상황까지 고려해야 하다 보니 더욱 그랬다.
하지만 이번에 직접 코드를 작성하고, AOP로 트랜잭션 상태를 추적하는 방식을 적용해보면서
스프링이 제공하는 트랜잭션 관리의 강력함과 유연함을 다시 한번 체감할 수 있었다. 
테스트 코드로 롤백 동작을 검증하니, 수작업 테스트 대비 훨씬 신뢰도가 높고 반복 가능하다는 점도 큰 장점이었고, 이 덕분에 서비스 안정성을 높이는 데 큰 도움이 될 것이라 생각한다. 
또한, AOP를 활용해 트랜잭션 상태 정보를 실시간으로 수집하고 기록하는 구조는
로깅이나 모니터링, 문제 발생 시 원인 분석에도 매우 유용하다는 점에서
운영 환경에서 장애 대응에 큰 힘이 될 것이라는 생각이 들었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[좋아요 멱등성: 올리브영, 무신사, 컬리는 어떻게 다르게 구현했을까?]]></title>
            <link>https://velog.io/@sweet_sumin/%EC%A2%8B%EC%95%84%EC%9A%94-%EB%A9%B1%EB%93%B1%EC%84%B1-%EC%98%AC%EB%A6%AC%EB%B8%8C%EC%98%81-%EB%AC%B4%EC%8B%A0%EC%82%AC-%EC%BB%AC%EB%A6%AC%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%8B%A4%EB%A5%B4%EA%B2%8C-%EA%B5%AC%ED%98%84%ED%96%88%EC%9D%84%EA%B9%8C</link>
            <guid>https://velog.io/@sweet_sumin/%EC%A2%8B%EC%95%84%EC%9A%94-%EB%A9%B1%EB%93%B1%EC%84%B1-%EC%98%AC%EB%A6%AC%EB%B8%8C%EC%98%81-%EB%AC%B4%EC%8B%A0%EC%82%AC-%EC%BB%AC%EB%A6%AC%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%8B%A4%EB%A5%B4%EA%B2%8C-%EA%B5%AC%ED%98%84%ED%96%88%EC%9D%84%EA%B9%8C</guid>
            <pubDate>Fri, 25 Jul 2025 01:39:46 GMT</pubDate>
            <description><![CDATA[<p>개발자라면 한번쯤 들어봤을 멱등성~! 최근에 좋아요를 구현해보려고 하는데 각기 다른 서비스들은 과연 어떻게 구현하고 있을까가 궁금했다. 그래서 오늘은 &#39;좋아요&#39; 기능을 예시로 파고 들어가보려고 한다. 특히 올리브영,무신사,컬리가 멱등성을 보장하는 방식이 다른 것을 확인할 수 있었다. </p>
<h1 id="멱등성이란">멱등성이란?</h1>
<p>멱등성은 같은 작업을 여러번 수행하더라도 항상 동일하게 유지되는 성질이다. 사용자의 실수로 버튼을 여러번 누르거나, 네트워크 불안정으로 요청이 중복 전송될때 시스템이 오작동하지 않도록 방지하는 중요한 개념이다. 즉, 좋아요 기능을 예시로 들자면, 좋아요를 여러번 눌러도 좋아요 개수가 계속 늘어나거나, 이미 좋아요를 취소했는데 다시 취소 요청이 들어와도 오류가 나지 않아야 한다는 의미이다. </p>
<h1 id="비교">비교</h1>
<h2 id="올리브영">올리브영</h2>
<p>: POST (개별 액션 엔드포인트)
올리브영은 좋아요(찜하기) 등록과 취소를 각각 다른 POST 엔드포인트를 통해 명확하게 구분하여 처리한다.</p>
<h3 id="구현-방식-분석">구현 방식 분석</h3>
<ul>
<li><p>좋아요(찜하기) 등록:</p>
<ul>
<li>메서드: POST</li>
<li>엔드포인트: <a href="https://www.oliveyoung.co.kr/store/mypage/getWishListJson.do">https://www.oliveyoung.co.kr/store/mypage/getWishListJson.do</a></li>
<li>사용자가 상품을 찜할 때 이 엔드포인트로 요청을 보낸다.
<img src="https://velog.velcdn.com/images/sweet_sumin/post/5fd1a8d0-dd01-4f47-9602-b76e18b08afc/image.png" alt=""></li>
</ul>
</li>
<li><p>좋아요(찜하기) 취소:</p>
<ul>
<li>메서드: POST</li>
<li>엔드포인트: <a href="https://www.oliveyoung.co.kr/store/mypage/delWishLstAjax.do">https://www.oliveyoung.co.kr/store/mypage/delWishLstAjax.do</a></li>
<li>사용자가 찜한 상품을 취소할 때 이 엔드포인트로 요청을 보낸다.
<img src="https://velog.velcdn.com/images/sweet_sumin/post/51c04b49-403e-45ab-b4f3-b92170018806/image.png" alt=""><h3 id="장점">장점</h3>
</li>
</ul>
</li>
<li><p>명확한 액션 구분: 각 API가 수행하는 액션(등록/삭제)이 URL에 명시적으로 드러나 개발자가 이해하기 쉽다. API의 목적이 URL에 담겨 직관적이다.</p>
</li>
<li><p>클라이언트의 명시적 제어: 클라이언트가 &#39;등록&#39;과 &#39;취소&#39;를 명확히 구분하여 요청을 보낼 수 있다.</p>
</li>
</ul>
<h3 id="멱등성-보장">멱등성 보장</h3>
<p>POST는 본래 멱등성이 보장되지 않는다. 따라서 올리브영은 서버 측에서 다음과 같은 방식으로 멱등성을 보장할 것이라 &#39;예상&#39; 한다.</p>
<ul>
<li><p>등록 요청 시: 이미 해당 상품이 사용자의 찜 목록에 있다면, 중복으로 추가하지 않고 성공 응답을 반환한다. 데이터베이스의 고유 제약 조건(Unique Constraint)을 활용하여 중복 저장을 방지하는 것이 일반적이다.</p>
</li>
<li><p>취소 요청 시: 해당 상품이 찜 목록에 없다면, 삭제할 것이 없으므로 아무것도 하지 않고 성공 응답을 반환한다.</p>
</li>
<li><p>동시성 제어: 여러 번의 요청이 거의 동시에 들어올 경우, 서버는 내부적으로 트랜잭션 또는 락(Lock)을 통해 한 번의 처리만 보장한다.</p>
</li>
</ul>
<h2 id="무신사">무신사</h2>
<p>: POST (단일 엔드포인트 &amp; Body로 액션 구분)
무신사는 &#39;좋아요(찜하기)&#39; 등록과 취소를 하나의 POST 엔드포인트로 처리하되, 요청 본문(Body)의 데이터로 실제 수행할 액션(등록 또는 취소)을 구분한다.</p>
<h3 id="구현-방식-분석-1">구현 방식 분석</h3>
<ul>
<li>메서드: POST</li>
<li>엔드포인트: <a href="https://log.data.musinsa.com/log/user-event/v2_cart_purchase_like">https://log.data.musinsa.com/log/user-event/v2_cart_purchase_like</a>
<img src="https://velog.velcdn.com/images/sweet_sumin/post/6087340c-ae5f-4bf9-92af-0167dc078faf/image.png" alt=""></li>
</ul>
<ul>
<li><p>액션 구분: 등록시</p>
<ul>
<li>요청 본문 data 내에 event_name: &quot;add_to_wishlist&quot; 포함
<img src="https://velog.velcdn.com/images/sweet_sumin/post/40ece9f9-888c-4022-b66d-cfa2a661aa90/image.png" alt=""></li>
</ul>
</li>
<li><p>액션 구분: 취소 시</p>
<ul>
<li>요청 본문 data 내에 event_name: &quot;remove_from_wishlist&quot; 포함
<img src="https://velog.velcdn.com/images/sweet_sumin/post/4a137a2a-e912-4943-b4a9-fd6cd9acc425/image.png" alt=""></li>
</ul>
</li>
</ul>
<h3 id="장점-1">장점</h3>
<ul>
<li><p>단일 API 엔드포인트: 클라이언트가 &#39;찜하기&#39; 관련 기능을 하나의 URL로 관리할 수 있다.</p>
<h3 id="멱등성-보장-1">멱등성 보장</h3>
<p>POST는 멱등성을 가지지 않으므로, 무신사도 서버 측에서 멱등성을 보장해야 한다. 이는 올리브영의 개별 POST 엔드포인트 방식과 유사할 것이라 예상한다.</p>
</li>
<li><p>요청 본문 분석: 서버는 event_name 값을 확인하여 &#39;add&#39; 요청인지 &#39;remove&#39; 요청인지 파악한다.</p>
</li>
<li><p>현재 상태 확인 및 조건부 처리:</p>
<ul>
<li>add_to_wishlist 요청 시: 이미 찜 목록에 있다면 중복 추가하지 않고 성공 응답.</li>
<li>remove_from_wishlist 요청 시: 찜 목록에 없다면 삭제하지 않고 성공 응답.</li>
</ul>
</li>
<li><p>동시성 제어: 여러 번의 중복 요청에 대해 데이터 정합성을 유지하기 위한 서버 내부 로직(고유 제약, 락 등)이 필요하다.</p>
</li>
</ul>
<h1 id="컬리">컬리</h1>
<p>: PUT (단일 엔드포인트 &amp; Body로 상태 변경)
컬리는 &#39;좋아요(찜하기)&#39; 상태 변경을 PUT 메서드를 사용하고, 요청 본문(Body)에 최종적인 찜 상태를 true 또는 false로 명시하는 방식을 사용한다.</p>
<h2 id="구현-방식-분석-2">구현 방식 분석</h2>
<ul>
<li><p>메서드: PUT</p>
</li>
<li><p>엔드포인트: <a href="https://api.kurly.com/member/proxy/pick/v1/picks/products/%7BproductId%7D">https://api.kurly.com/member/proxy/pick/v1/picks/products/{productId}</a> ( {productId}는 실제 상품 ID)
<img src="https://velog.velcdn.com/images/sweet_sumin/post/87ed3d65-4a1f-4ed0-a158-0c2790b5d49d/image.png" alt=""></p>
</li>
<li><p>상태 구분:</p>
</li>
</ul>
<p>찜하기 설정 시: 요청 본문 {&quot;is_pick&quot;: true}
<img src="https://velog.velcdn.com/images/sweet_sumin/post/909ef081-3786-4d56-9cc7-d461e8e0490d/image.png" alt=""></p>
<p>찜하기 해제 시: 요청 본문 {&quot;is_pick&quot;: false}
<img src="https://velog.velcdn.com/images/sweet_sumin/post/b3627e84-0840-479a-96f0-3009c70832b7/image.png" alt=""></p>
<h3 id="장점-2">장점</h3>
<p>PUT 메서드의 멱등성 활용: PUT 메서드는 자원의 전체를 갱신하는 의미를 가지며 그 자체가 멱등성을 보장한다. 같은 요청을 여러 번 보내도 자원의 최종 상태(찜 여부)는 항상 동일하게 유지된다. 이는 서버 측 멱등성 구현 부담을 줄여준다.</p>
<p>단일 API 엔드포인트 &amp; 상태 주도: 클라이언트는 현재 찜 상태와 관계없이 원하는 최종 상태만 is_pick: true 또는 is_pick: false로 전달하면 된다. 클라이언트 로직이 비교적 간결하다.</p>
<h3 id="멱등성-보장-2">멱등성 보장</h3>
<p>PUT 메서드 자체가 멱등성을 보장하므로, 서버는 요청 본문의 is_pick 값에 따라 단순히 해당 상품의 찜 상태를 &#39;설정&#39; 또는 &#39;해제&#39;하면 된다. 중복 요청이 들어와도 결과는 동일하다.</p>
<h2 id="그렇다면-어떤-방법이-더-나을까">그렇다면, 어떤 방법이 &#39;더&#39; 나을까?</h2>
<p>세 가지 방법 모두 멱등성을 성공적으로 보장한다. 어떤 방법이 &#39;더 좋다&#39;고 단정하기는 어려우며, 이는 API 설계 철학, 개발팀의 선호도, 그리고 서비스의 특성에 따라 달라질 수 있다.</p>
<ul>
<li><p>올리브영의 POST (개별 액션): API의 명시성과 직관성을 중요하게 생각하는 경우 유리하다. 각 URL이 어떤 액션을 수행하는지 명확하다. POST의 멱등성은 서버 측에서 견고하게 처리해야 한다.</p>
</li>
<li><p>무신사의 POST (단일 &amp; Body 구분): 단일 엔드포인트를 선호하면서도 POST 메서드를 사용해야 할 때 선택할 수 있는 방식이다. 특히 이벤트 로깅 시스템과 연동이 필요할 때 유용할 수 있다. 역시 POST 멱등성은 서버가 보장한다.</p>
</li>
<li><p>컬리의 PUT (단일 &amp; Body로 상태 변경): HTTP 메서드의 멱등성 속성을 적극적으로 활용하여 서버 측 구현의 복잡성을 줄이고 싶을 때 이상적이다. &#39;찜하기&#39; 상태를 특정 리소스의 속성으로 보고 이를 업데이트하는 RESTful 한 방식에 가깝다.</p>
</li>
</ul>
<p>궁극적으로 중요한 것은 어떤 방식을 선택하든 시스템의 신뢰성을 위해 멱등성이 완벽하게 보장되도록 설계하고 구현하는 것이라 생각한다. 다만 흥미로웠던 점은 같은 기능이 각기 서비스마다 다른 형태를 보였다는 것이다. 이를 통해 같은 방향을 추구하지만 정답이 없음을 다시한번 느꼈다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[테스트는 최종 시험대? 아니, 설계 방향 지도!]]></title>
            <link>https://velog.io/@sweet_sumin/%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%8A%94-%EC%B5%9C%EC%A2%85-%EC%8B%9C%ED%97%98%EB%8C%80-%EC%95%84%EB%8B%88-%EC%84%A4%EA%B3%84-%EB%B0%A9%ED%96%A5-%EC%A7%80%EB%8F%84</link>
            <guid>https://velog.io/@sweet_sumin/%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%8A%94-%EC%B5%9C%EC%A2%85-%EC%8B%9C%ED%97%98%EB%8C%80-%EC%95%84%EB%8B%88-%EC%84%A4%EA%B3%84-%EB%B0%A9%ED%96%A5-%EC%A7%80%EB%8F%84</guid>
            <pubDate>Fri, 18 Jul 2025 02:07:13 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-개요">📌 개요</h2>
<p>예전의 나에게 테스트란 단지 ‘구현이 끝난 후, 이상이 없는지 확인하는 마지막 절차’였다.<br>하지만 지금은 다르다. 테스트는 이제 내가 <strong>어떻게 구현해야 할지를 알려주는 설계 지침서</strong>다.</p>
<p>이 글에서는 테스트가 내 개발 방식과 사고를 어떻게 바꿔놓았는지, 그리고 내가 생각하는 좋은 테스트란 무엇인지를 이야기해보려 한다</p>
<h2 id="📌-e2e-테스트">📌 E2E 테스트</h2>
<p>E2E(End-to-End) 테스트는 사용자의 요청이 프론트엔드를 거쳐 백엔드와 DB까지 연결되는 전체 흐름을 검증하는 테스트다.</p>
<p>예를 들어, ‘포인트 조회’ 기능을 개발한다고 해보자. 테스트를 먼저 작성하면서 다음과 같은 질문이 자연스럽게 떠오른다:</p>
<p><img src="https://velog.velcdn.com/images/sweet_sumin/post/a8b09cdf-ce1e-47ed-8b1a-5ecc1da184aa/image.png" alt=""></p>
<ol>
<li>어떤 endpoint로 요청할 것인가?</li>
<li>어떤 요청값이 필요하고, 어떤 응답값을 줄 것인가?</li>
</ol>
<p><img src="https://velog.velcdn.com/images/sweet_sumin/post/1564c3ce-6fb8-494d-b996-760263493e93/image.png" alt=""></p>
<p>이런 고민을 바탕으로, 먼저 간단한 테스트 코드를 작성하고 mock 응답을 내려주는 방식으로 작업을 시작했다.<br>이를 통해 프론트엔드 개발자와 백엔드 개발자가 기능 구현 이전에 명확한 요청/응답 계약을 공유하고, 병렬로 작업할 수 있는 기반을 만들 수 있었다.</p>
<p>이 단계에서의 테스트는 실제 DB나 구현 로직 없이도 <strong>API의 인터페이스와 흐름</strong>을 검증하는 데 목적이 있다.<br>엄밀히 말하면 이는 ‘Stub 기반 테스트’ 또는 ‘Contract 테스트’에 가깝지만, <strong>이후 실제 구현이 완성되면 자연스럽게 진짜 E2E 테스트로 발전하게 된다.</strong></p>
<p>예전의 나였다면 컨트롤러뿐 아니라 서비스, 레포지토리, 엔티티까지 전부 구현한 후 테스트를 작성했겠지만,<br>지금은 테스트를 먼저 작성하고, 그 테스트를 통과하는데 필요한 최소한의 구조만 정의하고 흐름에 따라 점진적으로 구현을 확장해나간다.
즉, 이 테스트는 일회용이 아닌, 이후 전체 흐름을 점검하는 완전한 E2E 테스트로 진화하며 끝까지 유지된다.</p>
<p>이처럼 테스트는 ‘기능이 다 구현된 후 확인하는 단계&#39;가 아니라,
<strong>처음부터 설계를 이끄는 도구</strong>로 사용될 수 있다. 지금 보여준 예시는 아직 2단계까지 구현된 상태이며,
단위 테스트와 통합 테스트를 거쳐, 이 테스트는 완전한 E2E 테스트로 완성될 예정이다.</p>
<p>다음과 같은 단계를 거쳐 테스트를 완성된 E2E로 발전시켜간다.</p>
<ul>
<li><p>1단계 – Stub 기반 테스트: 요청/응답 형태를 테스트로 먼저 정의 </p>
</li>
<li><p>2단계 – 최소한의 구조 구현: DTO, Controller 등 테스트 통과에 필요한 부분만 작성 </p>
</li>
<li><p>3단계 – 점진적 로직 구현: 실제 서비스/도메인 구성 요소 추가</p>
</li>
<li><p>4단계 – 통합된 E2E 테스트: 실제 데이터, DB, 전 구간 테스트</p>
<h2 id="📌-단위-테스트">📌 단위 테스트</h2>
<p>단위 테스트에서는 핵심 비즈니스 로직에 대한 정합성과 규칙을 검증한다.<br>예를 들어 ‘포인트 충전’ 기능을 구현하면서, 다음과 같은 규칙을 세웠다:</p>
</li>
<li><p>0 이하의 값은 충전할 수 없다.</p>
</li>
</ul>
<p>이다. 
<img src="https://velog.velcdn.com/images/sweet_sumin/post/0c8eff2e-2072-4a12-9255-6b2ddefdb8f4/image.png" alt="">
이 로직을 어디에서 검증할지 고민하게 되었고, 결국 <code>request → command</code>로 변환하는 시점에서 유효성을 체크하기로 했다.<br>이렇게 계층별로 책임을 분리함으로써 테스트 가능성과 유지보수성이 향상되었다.</p>
<p>내가 단위 테스트를 작성하면서 가장 크게 달라진 점이 있다면, </p>
<ul>
<li>그전에는 중요한 로직을 private 함수에 숨겨놨다.<br>→ 중요한 비즈니스 로직은 별도 클래스로 분리하여 작은 public 함수로 노출  </li>
<li>한 메서드에 여러 책임이 혼재되어 있었다 (생성 + validate 등).<br>→ 테스트가 명확해지도록 책임을 분리</li>
<li>단, 외부에 무분별하게 public으로 열기보다는 <strong>설계를 통해 테스트 가능하게</strong> 만들자.</li>
</ul>
<h2 id="📌-통합테스트">📌 통합테스트</h2>
<p>각 기능이 단독으로는 잘 동작하더라도, 실제 서비스에서는 다양한 컴포넌트가 조합되어 동작한다.<br>통합 테스트는 이러한 구성 요소들이 <strong>서로 올바르게 연결되어 있는지</strong>, <strong>데이터 흐름에 문제가 없는지</strong>를 확인하는 테스트다.</p>
<p>예를 들어, 회원가입 기능에서 다음 두 가지가 모두 성공해야 한다:</p>
<ol>
<li>회원 정보가 DB에 저장된다.</li>
<li>저장된 회원 정보가 응답으로 반환된다.
<img src="https://velog.velcdn.com/images/sweet_sumin/post/3d62d8a8-519d-4898-9516-5dbc7ca25ef7/image.png" alt=""></li>
</ol>
<p>이 테스트는 <code>UserService.create()</code>를 호출했을 때,  </p>
<ul>
<li><code>UserRepository.save()</code>가 실제로 호출되었는지,  </li>
<li>저장된 값이 올바르게 매핑되어 응답으로 반환되었는지를 검증한다.</li>
</ul>
<h2 id="📌-좋은-테스트란-무엇인가">📌 좋은 테스트란 무엇인가</h2>
<p>내가 생각하는 <strong>좋은 테스트</strong>는 &quot;잘 실패하는 테스트&quot;다.</p>
<p>기능을 수정하거나 리팩토링할 때,
이전에 작성한 테스트가 실패한다면 자연스럽게 이렇게 생각하게 된다:</p>
<p>&quot;이번 변경이 기존 기능에 어떤 영향을 미친 걸까?&quot;
&quot;어디서 잘못된 흐름이 발생했지?&quot;</p>
<p>이런 고민을 통해 전체 구조와 책임을 다시 한 번 되짚어보게 되고,그 과정은 곧 코드를 더욱 견고하게 다듬는 시간이 된다.</p>
<blockquote>
<p>잘 실패하는 테스트는 결국
<strong>“코드를 되돌아보게 만드는 테스트”</strong>다.</p>
</blockquote>
<p>테스트가 깨지면 무조건 그 원인을 분석하게 되고,
그 분석 과정에서 설계, 구조, 책임의 균형을 검토하게 된다.이것이 내가 테스트를 사랑하게 된 이유이자,
테스트의 진짜 가치라고 믿는 부분이다.</p>
<h3 id="내가-생각하는-좋은-테스트의-조건">내가 생각하는 좋은 테스트의 조건</h3>
<ul>
<li><strong>잘 실패한다</strong>: 코드 변경이 기존 동작에 어떤 영향을 미치는지 즉시 드러난다.</li>
<li><strong>설계를 이끈다</strong>: 테스트를 먼저 작성하면서 구조와 책임이 명확해진다.</li>
<li><strong>신뢰할 수 있다</strong>: 반복 실행 시 항상 같은 결과를 보장한다.</li>
<li><strong>빠르다</strong>: 실행 속도가 빨라, 언제든 부담 없이 돌려볼 수 있다.</li>
</ul>
<h2 id="📌-결론">📌 결론</h2>
<p>우리는 매일 많은 기능을 만들고, 그 기능들은 수많은 사용자가 경험하게 된다.<br>그렇기에 테스트는 단순한 ‘확인’의 수단이 아니라, <strong>설계의 시작점이자 유지보수의 안전망</strong>이 되어야 한다.</p>
<p>TDD에서 말하는 <strong>&quot;Driven&quot;</strong>은 단순히 테스트를 먼저 쓴다는 의미가 아니다.<br>진짜 핵심은, 테스트가 <strong>코드의 방향, 책임 분리, 인터페이스 구조를 이끌어가는 힘</strong>이라는 점이다.</p>
<p>이제 테스트는 ‘시험’이 아니라 ‘설계자’다.
즉, 테스트는 단지 &#39;기능을 확인하는 수단&#39;이 아니다.<br>테스트는 설계를 이끌고, 유지보수성을 확보하며, 개발자의 자신감을 만들어주는 자신감의 근거가 된다고 생각한다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[63,000건 배치 처리 중 락 유발 이슈 회고]]></title>
            <link>https://velog.io/@sweet_sumin/63000%EA%B1%B4-%EB%B0%B0%EC%B9%98-%EC%B2%98%EB%A6%AC-%EC%A4%91-%EB%9D%BD-%EC%9C%A0%EB%B0%9C-%EC%9D%B4%EC%8A%88-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@sweet_sumin/63000%EA%B1%B4-%EB%B0%B0%EC%B9%98-%EC%B2%98%EB%A6%AC-%EC%A4%91-%EB%9D%BD-%EC%9C%A0%EB%B0%9C-%EC%9D%B4%EC%8A%88-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Fri, 02 May 2025 08:02:37 GMT</pubDate>
            <description><![CDATA[<p>운영 서버에서 63,000건의 이미지 배치 작업 중 락이 발생하며 장애가 발생했다. 처음 겪는 규모 있는 데이터 처리 이슈였고, 원인은 단순하지만 치명적이었다. 이 글은 그 문제의 원인, 해결, 그리고 회고를 정리한 실무 경험 공유이다.</p>
<h2 id="배경">배경</h2>
<p>이번에 회사에서 진행했던 프로젝트는 승인서버 프로젝트이다. 승인서버 프로젝트는 외부 연동으로 들어온 데이터를 우리 관리 시스템으로 들어오기 전에 검수하는 시스템을 구축하는 프로젝트다.
<img src="https://velog.velcdn.com/images/sweet_sumin/post/6fed5c7c-cb69-47db-b20a-22c575e9de44/image.png" alt=""></p>
<p>크게 보면 이렇게 볼 수 있다. </p>
<p>여기서 내가 한 일 중 하나는 이미지 배치 처리 기능의 설계 및 구현이다. 주요 작업은 다음과 같다.</p>
<h4 id="s3-이미지-업로드를-위한-비동기-배치-모듈-개발">S3 이미지 업로드를 위한 비동기 배치 모듈 개발</h4>
<ul>
<li>1시간 단위로 실행되는 이미지 배치 작업: INIT, FAIL 상태이며 재시도 횟수가 1 이하인 프로덕트/아이템 이미지를 대상으로 처리</li>
<li>특정 프로덕트 단위의 재연동 기능 구현: 선택된 프로덕트의 INIT, FAIL 상태 이미지만 재처리하며, 재시도 제한 없음<h4 id="이미지-업로드-후-상태-및-재시도-관리">이미지 업로드 후 상태 및 재시도 관리</h4>
</li>
<li>업로드 성공 시: retry 횟수 증가 + 상태 COMPLETE로 변경</li>
<li>업로드 실패 시: retry 횟수 증가 + 상태 FAIL 유지 및 슬랙 알림 전송</li>
</ul>
<h3 id="배치를-적용한-이유">배치를 적용한 이유</h3>
<p>2차 승인 단계에서 등록되는 각 프로덕트/아이템은 여러 개의 이미지를 포함하고 있으며, 이 이미지들은 모두 AWS S3에 업로드되어야 한다. 초기에는 등록 시점에 실시간으로 이미지를 업로드하는 방식도 고려했지만, 아래와 같은 이유로 배치 처리 방식을 도입하게 되었다.</p>
<h4 id="성능-이슈">성능 이슈</h4>
<p>이미지 업로드는 네트워크 I/O와 S3 API 호출이 포함된 작업으로, 처리 시간이 길다. 이를 실시간으로 수행할 경우 등록 속도가 느려지고, 사용자 경험에도 부정적인 영향을 줄 수 있다.</p>
<h4 id="처리-안정성-확보">처리 안정성 확보</h4>
<p>등록 도중 이미지 업로드에 실패하면 전체 등록이 실패할 수 있다. 배치로 처리하면 실패한 이미지에 한해 재처리할 수 있어 안정적인 처리가 가능하다.</p>
<h4 id="s3-요청-비용-최적화">S3 요청 비용 최적화</h4>
<p>AWS S3는 요청 건수에 따라 비용이 발생한다. 실시간으로 이미지를 업로드할 경우, 이미지 수만큼 S3 요청이 발생하고, 트래픽이 많아질수록 비용도 크게 증가한다.
배치 처리 방식은 요청을 모아서 처리하기 때문에 네트워크 연결 및 클라이언트 설정을 재사용할 수 있고, 요청 효율을 높여 비용 절감과 처리 흐름 단순화에 기여한다.</p>
<p>이러한 이유로 이미지 업로드 작업을 프로덕트/아이템 등록 시점이 아닌 배치 처리로 전환하게 되었고, 해당 로직의 설계와 구현은 내가 직접 맡아 수행하였다.</p>
<h2 id="장애-발생-및-대응">장애 발생 및 대응</h2>
<p>시원한 음료 한 잔 마시며 한숨 돌리고 있던 그 때
경고가 떴다!!!!!!
<img src="https://velog.velcdn.com/images/sweet_sumin/post/ef11a743-65ba-4ee4-bca4-897c62e68487/image.png" alt="">
락이 걸린다고? 갑자기?? 배치에서??? 왜???
범인은 나였다. 나야 나...(내가 그 주인공이 될 줄은 몰랐다)</p>
<h4 id="원인">원인</h4>
<p>이미지 업로드를 시키는 과정은 사실 단순하다. </p>
<p>1) INIT,FAIL 상태 &amp; 재시도 1 이하인 임시이미지 조회
2) 해당 이미지 S3에 업로드
3) 해당 이미지 상태, 재시도 개수 수정 저장</p>
<p>근데 문제는 내가 데이터의 규모를 예상치 못하고 조회시 관련 모든 데이터를 가져오게 만들었다는 것이다. 사실 이것만 봤을때는 뭐가 문제인데 할 수 있다. 관련 모든 데이터를 가져오는게 맞잖아?
그러나.. 데이터의 양이 6만 3천개라면?
<img src="https://velog.velcdn.com/images/sweet_sumin/post/5e3ed05d-2227-4f5f-8b61-37225eea1f6f/image.png" alt=""></p>
<h4 id="왜-락이-걸렸을까">왜 락이 걸렸을까?</h4>
<p>쉽게 말하면 6만개의 데이터를 수정하면서도 저장(flush)를 미루니까 트랜잭션이 너무 길어지고 락이 오래 유지되는 상황에서 동시에 같은 row에 접근하려는 다른 트랜잭션과 충돌이 발생하게 된것이다. </p>
<blockquote>
<p>정리하면, 다음 세 가지가 문제의 핵심이었다:</p>
</blockquote>
<ol>
<li>한 번에 모든 데이터를 불러옴 (6만 건)</li>
<li>처리 중간에 flush 없이 트랜잭션을 유지</li>
<li>DB 업데이트가 지연되면서 row-level lock 충돌 발생</li>
</ol>
<h4 id="해결법">해결법</h4>
<p>page 단위로 chunk 100개씩 가져와서 락을 빨리 빨리 해제하도록 수정하였다. </p>
<ul>
<li><p>AS_IS <img src="https://velog.velcdn.com/images/sweet_sumin/post/0493b66f-93fa-4415-a80a-3bbff7f8dc0b/image.png" alt=""></p>
</li>
<li><p>TO_BE
<img src="https://velog.velcdn.com/images/sweet_sumin/post/07dd7da4-25ee-46d6-af76-baca4dff96c7/image.png" alt=""></p>
</li>
</ul>
<h2 id="후기">후기</h2>
<p>해결 방법은 단순했다. 데이터를 페이지 단위로 나눠서 처리하면 되는 일이었다. 하지만 처음에는 그 방식이 떠오르지 않았다. 테스트 환경에서는 문제없이 동작했고, 실제 운영에서 63,000건이 넘는 데이터가 한 번에 들어올 거라고는 예상하지 못했기 때문이다.</p>
<p>이번 일을 통해 대규모 데이터 처리에서의 사소한 설계 차이가 시스템 안정성에 큰 영향을 줄 수 있다는 점을 몸소 깨달았다. 특히 다음 두 가지를 깊이 체감했다:</p>
<ul>
<li><p>운영 환경 데이터를 기준으로 사고할 것</p>
</li>
<li><p>트랜잭션은 짧고 명확하게 유지할 것</p>
</li>
</ul>
<p>실제 장애는 대량 데이터를 한꺼번에 처리하면서 트랜잭션이 길어지고, 그로 인해 DB 락 충돌이 발생한 것이 원인이었다. 이후 100건 단위로 나눠 처리하는 방식으로 개선하면서 락 점유 시간을 줄일 수 있었다.</p>
<p>단순한 설계 실수도 큰 장애로 이어질 수 있다는 점을 다시금 느꼈고, 앞으로는 더 철저하게 사고하고 검증해야겠다는 다짐을 하게 된 경험이었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Hash 마이그레이션 작업 회고]]></title>
            <link>https://velog.io/@sweet_sumin/Hash..%EB%9D%BC%EB%8B%88</link>
            <guid>https://velog.io/@sweet_sumin/Hash..%EB%9D%BC%EB%8B%88</guid>
            <pubDate>Thu, 20 Mar 2025 12:52:34 GMT</pubDate>
            <description><![CDATA[<p>따끈따끈한 이번 작업을 소개하려고 한다. 
오늘의 주인공은 상용데이터의 Hash 마이그레이션이다.</p>
<h2 id="📌-등장-배경">📌 등장 배경</h2>
<p>🌞 화창한 아침..운영 실무자에 의해 데이터 변경 요청이 떨어졌다
<img src="https://velog.velcdn.com/images/sweet_sumin/post/8a2c842f-be64-4341-a56a-8d704cd00eaa/image.png" alt=""></p>
<p>현재 요청된 내용은 프로덕트에 매핑된 속성을 변경하되 그 속성 옵션값은 그대로 유지해달라는 요청이다. 
좀 더 구체적으로 말해보자면, 상품1에 속성으로 PRADA 컬러라는 속성이 있고 그 값으로 빨강, 주황이 있는데 상품 1의 속성을 GUCCI 컬러라는 속성으로 변경하되 값은 그대로 빨강, 주황으로 유지 해달라는 것이다.</p>
<blockquote>
<ul>
<li>운영팀의 요청: 특정 상품의 속성을 다른 속성으로 변경해야 하지만, 속성 값은 유지해야 한다.</li>
</ul>
</blockquote>
<ul>
<li>문제: 기존 정책상 노출 속성은 수정할 수 없으므로, 마이그레이션 작업이 필요하다.</li>
</ul>
<p>현재 정책상 노출 속성은 한번 상품에 매핑되면 수정불가한 정책을 가지고 있기 때문에 우리가 마이그레이션을 해줄 수밖에 없다. </p>
<p><img src="https://velog.velcdn.com/images/sweet_sumin/post/ceb0d43c-ecd6-4a02-ba54-441068935140/image.png" alt=""></p>
<p>내가 할일은 </p>
<ol>
<li>product_attribute에서 attribute를 변경해주고</li>
<li>item_attribute_option에서 attribute와 attributeOption을 변경해줘야한다. 
바꿀 attributeOption은 기존의 attributeOption 값과 일치한 것을 찾아다가 변경해주면된다. </li>
</ol>
<p>사실 여기까지보면 뭐.. 쿼리 돌려주면 되지 한다. 맞다 이 일은 어렵지 않다. 근데 문제는 hash이다. 이번 이야기에서 나오는 주제의 주인공은 HASH이다. </p>
<p>우리는 상품과 아이템의 중복확인을 hash로 구별한다. </p>
<ul>
<li><strong>상품 중복 조건</strong> : 카테고리 + 상품이름 + 노출속성목록 + 브랜드Id + 모델명</li>
<li><strong>아이템 중복 조건</strong> : 상품 Id + 노출속성목록 + 속성값목록</li>
</ul>
<p>이렇게 되어 있다. 
이번에 이 조건들 중에 상품에는 노출 속성 목록이, 아이템에는 노출속성목록과 속성값 목록이 변경되기 때문에 Hash를 새로 변경해줄 수밖에 없다. 
그럼 변경된 상품과 아이템만 변경하면되잖아? 하기에는 이번 작업부터 중복 조건이 일부 변경되었다. 위에 언급한 조건은 이번에 변경되는 조건에 해당된다.
<img src="https://velog.velcdn.com/images/sweet_sumin/post/ccb56ec1-a4d5-4a1b-854b-1bd4a3331b4d/image.png" alt=""></p>
<p>에이 변경하면 되지..하기엔 단순히 몇백개가 아니고 몇십만 단위다..ㅎ 거기다가 상품 중복 조건이 이번 작업부터 변경된 조건이라 해시 충돌이 안날래야 안날 수가 없는 작업이다 ㄸㄹㄹ,, </p>
<p>💡 정리하자면, 상품과 아이템의 중복 여부를 확인하는 데 해시를 사용하는 이번 속성 변경하면서 그 기준이 되는 값 또한 변경이 되었기에 hash 작업이 필요하다</p>
<h2 id="📌-hash">📌 Hash?</h2>
<blockquote>
<p>💡 해시
임의의 크기를 가진 데이터를 고정된 크기의 데이터로 변화시켜 저장하는 것</p>
</blockquote>
<p>결론부터 말하자면, 동일한 입력값이면 항상 동일한 해시값이 생성되므로, 데이터를 직접 비교하지 않고도 빠르게 중복을 판별할 수 있다. 즉, 중복 체크하는데 성능적으로 해시가 더 빠르다고 판단한 것이다. </p>
<h3 id="🐳-직접-비교-vs-해시-비교">🐳 직접 비교 Vs 해시 비교</h3>
<p>상품을 등록하는 걸 예시로 들어보자. 
상품을 등록하기 전 위 중복조건에 의해 중복된 상품이 있는지 확인해야 한다. </p>
<p>만약 직접 비교해야 한다면?</p>
<blockquote>
<p>EXPLAIN SELECT * FROM product 
WHERE category_id = ? 
AND product_name = ? 
AND brand_id = ? 
AND model_no = ? 
AND displayed_attribute_ids = ?;</p>
</blockquote>
<p>해당 쿼리를 돌려 보면
<img src="https://velog.velcdn.com/images/sweet_sumin/post/565877f3-9c95-4456-b970-512710898d43/image.png" alt="">
실행계획에 테이블 풀스캔하고 있음을 확인할 수 있었다. 
즉, 상품을 추가할때 기존 상품과 하나씩 비교한다는 것이다. -&gt; 최악의 경우 O(N)
풀 스캔하는 것을 보니 데이터가 많을수록 성능이 저하됨을 의미하는데 현재 우리 상품은 몇십만개이다. 그럼.. 성능은 아주 최악이 되는 것이다. </p>
<p>그럼 Hash의 경우는?</p>
<blockquote>
<p>EXPLAIN SELECT * FROM product_identity pi
               WHERE pi.hash = ?;</p>
</blockquote>
<p>해당 쿼리를 돌려보면
<img src="https://velog.velcdn.com/images/sweet_sumin/post/2e614f7a-084b-4774-b37b-da9eca313b45/image.png" alt=""></p>
<p>const row lookup으로 인덱스를 확용한 상수 조회를 하고 있음을 확인할 수 있다. 
해당 스캔 방식은 단일 행을 대상으로 하는 상수 값을 기반으로 하는데 Primary Key 또는 Unique Key에 대한 WHERE 절의 상수 값 비교가 있을 때 나타난다. 
hash에 의해 unique를 걸 수 있기 때문에 인덱스를 걸 수 있는 것이다. 
즉, 테이블의 유니크 인덱스 (uk_hash)를 사용하여 단 한개의 행만 조회하고 있다. 
-&gt; 시간 복잡도 O(1) ~ O(logN)</p>
<p>결과적으로 직접 비교보다 해시비교가 풀스캔 방지가 될 뿐만아니라 속도도 훨씬 빠르기 때문에 중복 검사에 해시를 적용하는 것이 성능상 최적이라는 것을 알 수 있다. </p>
<p>정리하자면,</p>
<blockquote>
<ul>
<li>직접 비교
풀스캔, 시간복잡도 : O(N), 데이터 많을수록 성능 저하</li>
</ul>
</blockquote>
<ul>
<li>Hash 비교
O(1) ~ O(logN), 인덱스를 활용해 빠른 조회 가능</li>
</ul>
<h2 id="📌-마이그레이션-작업에-대한-회고">📌 마이그레이션 작업에 대한 회고</h2>
<p>작업에 대한 플로우를 말해보자면,</p>
<ol>
<li>속성 변경에 대한 프로덕트-속성 매핑 데이터, 아이템-속성 매핑 데이터 변경 </li>
<li>중복 조건에 의한 해시 충돌 테스트</li>
<li>충돌된 상품에 대한 실무자 전달 및 데이터 정리</li>
<li>프로덕트, 아이템 해시 테이블에 생성 -&gt; 임시 테이블에 생성</li>
<li>기존 해시 테이블 백업 후 임시테이블을 기존 테이블 명으로 변환</li>
<li>product, item 해시 캐시의 기존 데이터 삭제 후 새로 생성</li>
</ol>
<p>상용 데이터를 다루기도 하고, 내 기준상 데이터가 많아서 사실 두렵기도 하고 어려움도 많았던 작업이었다. 이 작업을 하면서 흥미로웠던 구간은 기존의 데이터에 대한 해시 생성 구간이었다. </p>
<p>아이템만 해도 현재 활성화된 아이템이 몇십만개인데 이걸 어떻게 생성을 하느냐?
방법은 page를 활용해서 한번에 3000개씩 가져와서 배치를 돌리는 것이다. 
( 이건 그냥 여담인데 아무생각없이 10개씩 가져오도록 했다가 반나절이되어도 안끝났다..ㅎ 그래서 확 3000개씩 가져오는 걸로 바꾸었더니 5도 안되어서 끝났다. ㅎㅎ 네트워크 비용에 대한 체감이 확 드는 순간이었다. )</p>
<h3 id="🐳-내부-코드-분석">🐳 내부 코드 분석</h3>
<p>가장 흥미로웠던 코드를 분석해보자.
<img src="https://velog.velcdn.com/images/sweet_sumin/post/ddea23cc-6019-432c-8c39-cbfd60477291/image.png" alt=""></p>
<p>해당 코드는 대량 데이터를 한번에 삽입해야 할때 사용한다. 
먼저 excute 내부 코드를 살펴보면, ( Spring 내부 코드 )
<img src="https://velog.velcdn.com/images/sweet_sumin/post/348b9c0c-25df-40ad-bcc9-a02f927e60f8/image.png" alt=""></p>
<p>1) getTransaction() : 직접 트랜잭션을 가져오고,
2) commit() : 트랜잭션을 직접 커밋하고
3) rollbackOnException() : 트랜잭션을 수동으로 롤백하고
이를 통해 excute() 메소드는 트랜잭션 관리 로직을 직접 실행하는 메서드임을 확인할 수 있다. 
-&gt; 예외 발생시 전체 롤백이 되게 됨을 알 수 있다. 실제로 테스트할때 충돌로 인한 예외 발생시 전체 롤백된 것을 확인하였다. </p>
<p>대망의 batchUpdate 메소드를 살펴보자면,  ( Spring 내부 코드 ) 
이 메소드는 JDBC 기반 배치 업데이트 메서드로 여러개의 데이터를 한번에 업데이트하는 기능을 함을 알 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/sweet_sumin/post/ea79583c-b9f7-4d18-86b8-33277175c5c8/image.png" alt="">
실제로 Sql에 해당 하는 코드가
<img src="https://velog.velcdn.com/images/sweet_sumin/post/8b50ed9f-b19b-40af-a12a-810e964783e7/image.png" alt="">
이렇게 앞전에 선언해놓았고, 이것을 매개변수로 받아서 hash 생성을 한꺼번에 하는 것을 알 수 있었다. </p>
<h3 id="🐳-회고">🐳 회고</h3>
<p>이 작업을 수행하면서, 역시 직접해봐야 좀더 와닿음을 다시 한번 확인하는 순간이었다. 내가 이번에 작업한 해시는 이전부터 중복 검사를 위해 해놓았던 조치였다. 그러나 내가 한게 아니다보니, 해시 작업에 대한 이해도가 낮았다. 
이번에 해시 생성 작업을 하다보니, 왜 필요하고 어떤 코드를 사용했는지 나를 납득해가면서 작업을 하다보니 얻어가는 것도 많고 이해도도 높아짐을 알 수 있었다. 나중에 중복 검사가 필요할때 이 작업을 떠올리며 해시를 생각해내지 않을까? ㅎㅎ😄</p>
<p>처음에 이 작업이 주어졌을때, 상용 데이터를 만진다는 것에 대한 두려움이 있었는데, &#39;이 연차에 어떤 누가 몇십만개의 상용 데이터를 다루는 경험을 하겠어?&#39; 하는 마음으로 임했다. 그래도 무서웠다. 혹시 내가 잘못할까봐 덜덜 떨면서 했다. 며칠 지난 지금, 이 작업을 회고하면 간단한데? 싶다. </p>
<p><strong>이 작업에서 가장 오래 시간이 걸렸던 부분은 해시 충돌 부분이었다.</strong> 
사실 상품 해시 충돌은 예상했지만, <strong>아이템 해시 충돌은 예상 못했다.</strong> <strong>왜냐면 상품 해시 충돌된 것을 해결하면(상품 충돌난 아이템까지 삭제했기에) 아이템 중복 기준에 상품 ID가 있기 때문에 아이템 해시 충돌은 전혀 나지 않을 것이라 예상했기 때문이다.</strong> 그런데 아이템 해시 충돌이 발생했다.</p>
<p>앞선 코드에 보여줬던 것이 하나라도 충돌되면 멈추는 로직이기때문에 몇십만개의 아이템을 다 돌리면서 충돌날때마다 에러나는 상황은 너무나도 시간이 많이 들기때문에 
<img src="https://velog.velcdn.com/images/sweet_sumin/post/57fd8d2d-b37a-4a6c-a4fb-692a9b950591/image.png" alt=""></p>
<p>에러나더라도 계속 돌아가도록 해당 부분을 try-Catch로 감싸서 충돌나서 에러가 나더라도 멈추지 않도록 했고, 페이지네이션으로 3000개씩 데이터를 돌렸더니 몇십만개의 데이터가 모두 거의 5분만에 다 돌아가서 모든 충돌을 잡아낼 수 있었다. 덕분에 빠르게 어떤 아이템들이 충돌났는지 확인할 수 있었다.</p>
<p> 거의 40개의 아이템이 충돌났고, 상품으로 따지자면 3개 상품에 대한 아이템들이었다. 알고보니 임의로 수동으로 넣은 아이템 데이터에서 발생한 것이다. 따라서 해당 아이템들과 상품들을 삭제하니 해결되었던 문제였다. </p>
<p>해시 충돌된 상품들과 아이템들이 다행히 모두 주문이 나간적이 없거나 테스트 상품이라서 삭제하기 어렵지 않았다. 물론 해당 상품과 아이템들을 삭제하기 위해 그 하위 집단인 벤더아이템, 딜벤더아이템, 딜 등의 삭제 작업들을 추가로 해줘야 하지만 오더가 없었기에 스위칭이 아닌 삭제로 해결할 수 있었던 작업이었다. 이번 작업을 통해 실무자와 커뮤니케이션도 해야하고 배포 작업도 걸려있어서 부담이 많이 가는 작업이었다. 하지만, 정말 값진 경험이었다. 앞으로 중복검사나 데이터 마이그레이션이 필요할때 이번 경험이 큰 도움이 될 것 같다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[분산락 적용하기 (실전)]]></title>
            <link>https://velog.io/@sweet_sumin/%EB%B6%84%EC%82%B0%EB%9D%BD-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0-%EC%8B%A4%EC%A0%84</link>
            <guid>https://velog.io/@sweet_sumin/%EB%B6%84%EC%82%B0%EB%9D%BD-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0-%EC%8B%A4%EC%A0%84</guid>
            <pubDate>Wed, 05 Mar 2025 22:44:11 GMT</pubDate>
            <description><![CDATA[<p>이전까지는 왜 분산락을 적용했고, 해당 코드가 어떤지에 대해서 이야기해보았다. 
이번에는 적용한 코드가 우리가 원하는 동시성에 대한 대책이 제대로 되었는지에 대한 이야기를 해볼것이다. </p>
<h3 id="📌-조건은">📌 조건은?</h3>
<p>우선, 난 Jmeter를 활용해서 하나의 락을 거는 @DistributedLock과 여러 락을 동시에 거는@MultiDistributedLock에 대한 동시성 테스트를 진행했다. 
조건은 둘다 똑같이 설정했다.
<img src="https://velog.velcdn.com/images/sweet_sumin/post/849b4964-99df-4eea-a519-a322e784e4b6/image.png" alt="">
조건을 해석해보자면, </p>
<ul>
<li>50개의 스레드 (동시 사용자 50명)</li>
<li>Ramp-up 시간 50초 (1초마다 1개의 스레드가 추가됨)</li>
<li>Loop Count: 10 (각 스레드가 10번 반복 요청)</li>
</ul>
<p>→ 즉, 최대 500개의 요청이 순차적으로 발생</p>
<blockquote>
<p>추가적으로 설명하자면, 
Ramp-up Period는 <strong>모든 스레드(사용자)를 얼마나 걸쳐서 실행할지를 결정하는 값</strong>이다.
즉, JMeter는 총 N개의 스레드를 Ramp-up Period 동안 균등하게 분배하여 시작한다.</p>
</blockquote>
<p>이걸 공식으로 정리하자면,
🔹 스레드 시작 간격 (초) = Ramp-up Period / 총 스레드 개수</p>
<blockquote>
</blockquote>
<p>내가 설정한 조건에 의하면, Ramp-up Period를 50초, 스레드 개수를 50으로 설정하게 되고
50초/50쓰레드 가 되니 1초마다 1개의 스레드가 추가되는 구조가 된다. </p>
<blockquote>
</blockquote>
<p>정리하자면, 50초 동안 50개의 스레드가 점진적으로 늘어나며, 매 초마다 1개의 스레드가 실행된다. 50초가 지나면 모든 스레드가 동시에 실행된 상태에서 반복(Loop Count: 10번)하면서 요청을 보내게 되는 조건이다.</p>
<h3 id="📌-테스트-해볼까">📌 테스트 해볼까?</h3>
<p>이제 본격적으로 테스트를 해보자. </p>
<h4 id="distributedlock-테스트">@DistributedLock 테스트</h4>
<pre><code>{
  &quot;id&quot;: &quot;row:2&quot;
}</code></pre><p>하나의 키를 동시에 여러번 테스트하는 것인데 한 키만을 걸었기 때문일까</p>
<p><img src="https://velog.velcdn.com/images/sweet_sumin/post/d23e7a00-16c5-4c70-b5d9-921d352a8301/image.png" alt="">
모든 요청이 동시에 보냈음에도 불구하고 성공률이 100%였다 ㅋㅋㅋ 
<img src="https://velog.velcdn.com/images/sweet_sumin/post/aee8b3f3-7fa2-4e83-b5b2-f5dcdb045270/image.png" alt="">
하나하나 뜯어보자면, </p>
<ul>
<li>Samples: 500 → 총 500개의 요청이 전송됨</li>
<li>Average: 5006 ms → 모든 요청의 평균 응답 시간이 5006ms (5.006초)</li>
<li>Median: 5006 ms → 응답 시간을 작은 순서대로 정렬했을 때 중간 값이 5006ms</li>
<li>90% Line:    5010 ms    → 응답 시간이 5010ms 이하인 요청이 전체의 90%</li>
<li>95% Line:    5012 ms    → 응답 시간이 5012ms 이하인 요청이 전체의 95%</li>
<li>99% Line:    5015 ms    → 응답 시간이 5015ms 이하인 요청이 전체의 99%</li>
<li>Min: 5001 ms → 가장 빠른 응답 시간이 5001ms</li>
<li>Max: 5028 ms → 가장 느린 응답 시간이 5028ms</li>
<li>Error %: 0.0% → 오류 없이 100% 성공</li>
<li>Throughput: 5.0/sec → 초당 평균 5개의 요청을 처리</li>
<li>Received KB/sec: 0.86 KB/s → 서버에서 초당 0.86KB 데이터를 받아옴</li>
<li>Sent KB/sec: 1.03 KB/s → 서버로 초당 1.03KB 데이터를 보냄</li>
</ul>
<p>평균 응답시간 (Throughput)이 5초인게 아마 코드 상  Thread.sleep(5000) 영향인 듯 싶다. 
사실 어떻게 100% 성공일 수 있지? 설마 락이 안걸리나 싶어서 
<img src="https://velog.velcdn.com/images/sweet_sumin/post/0912875b-5fa1-4058-85bb-03112f3f1c90/image.png" alt="">
log를 걸어서 봤는데 락이 잘 걸려있는 것을 확인할 수 있었다. 
그렇다면 멀티로 락을 걸었을때는 어떨까?</p>
<h4 id="multidistributedlock-테스트">@MultiDistributedLock 테스트</h4>
<p>@DistributedLock과 같은 조건으로 진행하였다. 
사실 처음에 테스트 할때는 이것도 성공률이 100%일줄 알았다. 
<img src="https://velog.velcdn.com/images/sweet_sumin/post/ad959716-4791-4047-93df-a2d511e200a1/image.png" alt=""></p>
<p>뭐야? 이건 왜 간간히 성공해?? 왜 실패한건 500에러야?
<img src="https://velog.velcdn.com/images/sweet_sumin/post/9fa1e95b-a09a-492f-9628-92adfdd3195c/image.png" alt="">
에러 메세지를 확인해보니, 락 거는데 에러가 뜬거였다. </p>
<h5 id="single-락과-multi-락-왜-같은-조건인데-결과가-다를까">Single 락과 Multi 락 왜 같은 조건인데 결과가 다를까??</h5>
<p>✔️ @DistributedLock (Single)
<img src="https://velog.velcdn.com/images/sweet_sumin/post/ccb73d20-3996-413d-8907-5390f226833d/image.png" alt=""></p>
<ul>
<li>요청마다 <strong>하나의 키(row:2 같은 단일 값)</strong>만 사용</li>
<li>waitTime = 15이므로 락을 못 잡으면 최대 15초까지 대기 + 쓰레드 sleep 5초</li>
<li>하나의 키만 처리하므로 충돌 가능성이 낮음</li>
<li>결과적으로 스레드 간 충돌 없이 요청이 순차적으로 처리됨 → 실패율 낮음</li>
</ul>
<p>✔️ @MultiDistributedLock (Multi)
<img src="https://velog.velcdn.com/images/sweet_sumin/post/3ed86a1b-dad1-4e10-a9b7-b59bd2a56aa5/image.png" alt=""></p>
<ul>
<li>여러 개의 키([&quot;row:2&quot;,&quot;row:1&quot;,&quot;row:3&quot;])를 락으로 사용</li>
<li>만약 다른 스레드가 row:1을 선점했어도, row:2와 row:3이 잠겨 있으면 락을 얻지 못함</li>
<li>락을 얻지 못하면 실패 (대기 시간이 지나도 모든 키를 얻지 못하면 실패)</li>
<li>일부 키만 사용 가능해도 전체가 실패하는 구조 → 충돌 가능성이 급증</li>
</ul>
<p>따라서 싱글락과 달리 멀티락에서 에러가 뜨는 것은 동시성 테스트에서 잘 방어를 하고 있다는 뜻이었다. </p>
<p>참고) 나의 github 코드 : <a href="https://github.com/sue4869/lockPractice">https://github.com/sue4869/lockPractice</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[분산락 적용하기 (개념)]]></title>
            <link>https://velog.io/@sweet_sumin/%EB%B6%84%EC%82%B0%EB%9D%BD-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0-%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@sweet_sumin/%EB%B6%84%EC%82%B0%EB%9D%BD-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0-%EA%B0%9C%EB%85%90</guid>
            <pubDate>Wed, 05 Mar 2025 22:42:55 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-적용-배경">📌 적용 배경</h2>
<p>이번에 회사에서 하는 프로젝트는 &#39;오더 상태 관리&#39;이다. &#39;오더서밋, 오더취소, 배송, 오 더컨펌&#39; 까지의 다양한 오더 상태에 대한 관리를 적용하는 프로젝트이다. </p>
<p>우리 회사는 공급사 상품들의 묶음 단위인 딜을 이용해 주문을 한다. 상품이 있으니까 재고가 있겠지? 즉, 각 오더 상태의 역할별로 재고가 차감되거나 복원된다. </p>
<ul>
<li>오더 서밋시 : 재고 차감</li>
<li>오더 취소시 : 재고 복원</li>
<li>오더 컨펌시 : 재고 차감 / 재고 복원
이러한 상태가 변경될때 각 딜에는 항상 중복되는 상품이 존재하기 때문에 동시성 문제가 발생하게 된다. 여기서 추가로 딜에는 여러 상품들이 있기 때문에 여러 상품을 동시에 락을 걸어야 하는 상황이다. </li>
</ul>
<h2 id="📌-동시성-문제를-해결하는-방법">📌 동시성 문제를 해결하는 방법</h2>
<p>여러가지 방법이 있는데 비관적락, 낙관적락, 분산락, 네임드락 등이 있다. 각각의 특징을 간단히 알아보자면,</p>
<p>1) 비관적락(DB락)</p>
<ul>
<li>DB에서 직접 락을 걸어 다른 트랜잭션 차단</li>
<li>장점 : 데이터 정합성 강하게 보장, 실시간 동시 수정 방지 가능</li>
<li>단점 : 성능 저하(트랜잭션이 길어질수록 락 유지시간 증가), 데드락</li>
<li>적용 예시) 은행 계좌 잔고 업데이트</li>
</ul>
<p>2) 낙관적락(버전 필드)</p>
<ul>
<li>충돌 감지 후 재시도 (rollback &amp; retry)</li>
<li>장점 : 락을 안걸어서 성능이 좋음</li>
<li>단점 : 충돌이 빈번할 경우 계속 재시도하여 성능 저하를 일으킴. 정합성이 다소 낮음</li>
</ul>
<p>3) 분산락(Redis, Zookeeper)</p>
<ul>
<li>여러 서버에서 동일한 리소스를 동시에 수정하지 못하도록 제어</li>
<li>장점 : 분산 시스템에도 동기화 가능</li>
<li>단점 : 락 관리(해제, TTL 설정 등) 신경 써야 함, 분산 환경에서 네트워크 이슈로 인해 지연 가능</li>
</ul>
<p>우리 회사의 경우, 멀티 인스턴스 환경에서 오더상태 변경을 해야하고 재고관리에 있어서 강한 정합성을 요구하기 때문에 분산락을 적용하기로 결정하였다.</p>
<h2 id="📌-분산락">📌 분산락</h2>
<p>분산락이란 무엇일까?
앞서 언급했듯이 분산락은 여러 서버에서 동일한 리소스를 동시에 접근하지 못하도록 제어하는 것을 의미한다.(비관적 락이나 낙관적 락은 하나의 DB에서만 동작하는 락) 좀 더 기술적 용어를 사용해서 설명하자면, </p>
<blockquote>
<p>💡 분산락
락을 획득한 프로세스 혹은 스레드만이 공유 자원 혹은 Critical Section 에 접근할 수 있도록 하는 것</p>
</blockquote>
<p><strong>키(락)를 가진 사람(프로세스/스레드)만 보물이 있는 공간(공유자원)의 문을 열 수 있는 것이다</strong> 🗝</p>
<p>분산락을 적용하는 방법은 여러가지가 있다. Redis, Zookeeper, MySql 등등.. 결론적으로 말하자면, 우린 Redis를 사용하였다.
우선 Redis는 그동안 캐시용도로 이미 구성해놓은 반면에 Zookeeper는 추가적인 인프라 구성이 필요하기 때문에 제외하게 되었다. 그리고 알다시피 Redis는 싱글스레드로 작동하기 때문에 동시성 문제도 현저히 작다. 아 물론 Mysql도 있긴 한데, 락을 사용하기 위해 별도의 커넥션 풀을 관리해야 하고 락에 관련된 부하를 RDS에서 받으니 Redis를 사용하는 것이 더 효율적이다.</p>
<h3 id="redisson을-사용한-이유는">Redisson을 사용한 이유는?</h3>
<p>Redis는 인메모리 데이터 저장소로 사용되지만 , 캐시 역할을 넘어서 다양한 분산 시스템 기능을 지원하는 구현제(라이브러리, 프레임워크)들이 존재한다. 그 중 난 분산락을 위한 구현체에 대해 간단히 알아보자면,</p>
<ul>
<li>Jedis -&gt; Lettuce가 성능이 더 좋아서 Lettuce로 대체됨</li>
<li>Lettuce</li>
<li>Redisson</li>
</ul>
<p>1) Lettuce</p>
<ul>
<li>Spring Data Redis에서 기본적으로 사용하는 Redis 클라이언트</li>
<li>setnx를 활용한 스핀락 : 반복적으로 락 획득 시도 -&gt; 레디스에 많은 부하 발생. CPU를 계속 사용하면서 재시도하는 방식</li>
<li>락 획득 방식
(1) SET NX 명령어로 락 획득을 시도
(2) 락이 없으면 성공 → 작업 진행 후 DEL로 락 해제
(3) 이미 락이 있으면 실패 → 일정 시간 대기 후 재시도 (스핀락 방식)
(4) TTL(EX)을 설정하여 데드락 방지</li>
</ul>
<p>2) Redisson</p>
<ul>
<li><p>별도의 Lock interface를 지원 : RedLock, RLock(단일 인스턴스 락) 지원</p>
<blockquote>
<p>💡 RedLock</p>
<ul>
<li>Redis 기반의 분산 락을 더 안전하게 보장하기 위한 알고리즘</li>
<li>멀티 Redis 노드 환경에서 장애 복구가 중요한 경우</li>
<li>데이터 정합성이 중요한 글로벌 시스템</li>
<li>Redis 장애가 발생해도 락을 유지해야 하는 경우</li>
<li>RedLock은 과반수 이상의 Redis 노드에서 락을 획득해야 성공</li>
</ul>
</blockquote>
</li>
<li><p>Pub/Sub 방식을 이용하기에 락이 해제되면 락을 subscribe 하는 클라이언트는 락이 해제되었다는 신호를 받고 락 획득을 시도</p>
</li>
<li><p>Redisson은 락 대기 및 해제 처리를 최적화하여 불필요한 CPU 낭비 없이 안정적으로 락을 관리</p>
</li>
<li><p>락이 만료되기 전에 자동으로 TTL을 연장하여, 장시간 작업에서도 안정적인 락 유지가 가능
( Lettuce는 TTL이 지나면 락이 풀릴 수 있어 작업 중 충돌 위험이 존재 )</p>
</li>
</ul>
<p>결론적으로, Lettuce보다 안정적인 분산 락이 필요했고, CPU 사용을 줄이면서 TTL 자동 연장과 다양한 락 기능을 활용하기 위해 Redisson을 선택하게 된것이다. 그럼 이제, RedLock을 이용할지, RLock을 이용해서 구현할지에 대한 고민이 생긴다.</p>
<h3 id="redlock-rlock--어떤-것을-이용할까">RedLock, RLock ? 어떤 것을 이용할까</h3>
<p>❌ RedLock이 과할 수 있는 경우
싱글 Redis 노드 환경이거나, 락을 걸어야 하는 트랜잭션이 짧다면 RedLock은 오버헤드가 될 수도 있다</p>
<ul>
<li>단일 Redis 인스턴스 환경에서는 RedLock을 사용할 필요 없음</li>
<li>과반수 노드가 죽으면 락 획득이 불가능해질 수도 있음</li>
</ul>
<p>현재 우리의 레디스 환경은 하나의 레디스 인스턴스에서 모든 데이터와 락을 관리하는 싱글 노드 형태이기 때문에 RedLock보다는 RLock을 선택하는 것이 낫다는 판단이 되었다.</p>
<h2 id="📌-코드내에서-주목해야-할점">📌 코드내에서 주목해야 할점</h2>
<p>코드 내에서 주목해야 할 점을 난 2가지를 뽑았다. </p>
<p>1) RLock의 내부 코드 파헤치기
2) 트랜잭션 분리</p>
<h3 id="1🤔-rlock의-내부-코드-파헤치기">1.🤔 RLock의 내부 코드 파헤치기</h3>
<p>Redission을 이용한 분산락 코드는 사실 인터넷을 조금은 서칭하면 거의 비슷하게 나온다. 그런데 정작 내부의 RLock의 코드를 파헤친 기록은 없더이다. 퇴근하고 남는게 시간인데 놀면 뭐하나,, 내부 코드 뒤적거리면서 시간이나 보내야지 ⏳
적용한 코드를 크게 보면 간단하다</p>
<blockquote>
<p>락 객체 생성(열쇠 가져오기) → 락 걸기(열쇠로 잠그기) → 락 해제(열쇠로 잠금 풀기) </p>
</blockquote>
<p>1) 락 객체 생성(열쇠 가져오기)</p>
<p>자.. 락 객체 생성부터 알아볼까?
<img src="https://velog.velcdn.com/images/sweet_sumin/post/c0c778c4-6249-4dd9-b28e-272717b5ae1b/image.png" alt="">
처음 시작은 getLock부터 시작한다. 이 코드를 따라가다보면, 최종적으로 RedissonLock 클래스의 생성자로 연결된다. 
<img src="https://velog.velcdn.com/images/sweet_sumin/post/f3c40997-5bd3-4697-b199-ff112467f38f/image.png" alt="">
첫번째 코드 줄을 통해, RedissonLock은 RedissonBaseLock을 상속받고, 기본적인 락 이름(name)과 명령 실행기(commandExecutor)가 초기화함을 알 수 있다. 
명령 실행기(commandExecutor)라는 것은 <strong>🎁 비동기 Redis 명령어 실행기</strong>를 의미한다. 음 Redis에 직접 명령을 보내는 역할인거다. 예를 들어 tryLock()을 호출하면, 내부적으로 SET NX PX 명령이 Redis에 전송되는 것이다. 그래서 명령 실행기를 초기화한다는 것은 commandExecutor를 통해 Redis와 통신할 준비를 한다는 거라고 생각하면 된다. </p>
<p>internalLockLeaseTime는 자동 락 해제 시간 설정하는 것이다. 여기서 우리가 주목해야 할것은 🎁 <strong>락 워치독 (Watchdog)</strong> 기능이다. 쉽게 말하면, 자동 연장 기능이다. </p>
<blockquote>
<p>📌 락 워치독(Watchdog)은 왜 필요할까?
보통 Redis에서 락을 설정할 때 TTL(만료 시간)을 지정하는데, 작업이 TTL 안에 끝나지 않으면 락이 자동으로 해제되는 문제가 있다. </p>
<p>예를 들어 TTL이 5초인데 작업이 6초걸린다고 치자. 5초 후 락이 만료되고 자동으로 해제되면?
다른 프로세스가 같은 락을 획득할 수 있다 → 데이터 일관성 깨짐 😨
그래서 락을 획득한 스레드가 살아 있는 동안 TTL이 자동으로 연장된다는 기능이다. TTL을 직접 설정하지 않으면 기본 30초 동안 유지된다고 한다. </p>
</blockquote>
<p>마지막 줄인 pubSub은 <strong>🎁 Pub/Sub 기능</strong>을 활용하여 락 해제 이벤트를 감지하는 역할이다. 
Redis에서 분산 락을 사용할 때, 다른 클라이언트가 락을 대기하는 방식에는 2가지 방식이 있다. </p>
<ul>
<li>폴링(Polling) 방식: 주기적으로 Redis를 조회해서 락이 해제되었는지 확인함.</li>
<li>이벤트 기반 방식: 락이 해제될 때 Redis가 직접 알림(Pub/Sub)을 보내서 대기 중인 클라이언트가 즉시 실행됨.</li>
</ul>
<p>만약 폴링 방식이라면? 락을 얻으려는 클라이언트가 주기적으로 Redis에 요청을 보내 락이 해제되었는지 확인해야한다. 듣기만 해도, 불필요한 Redis 부하가 발생하고 클라이언트가 지속적으로 Redis에 요청을 보내므로 트래픽이 많아질 거라는 단점이 느껴지지?
그래서 Redisson에서는 락이 해제될 때 이벤트를 발생시켜 다른 클라이언트가 즉시 실행될 수 있도록 처리한다. <strong>언제? RLock.unlock() 이 호출될때!</strong></p>
<p>2) 락 걸기(열쇠로 잠그기)
이제 락을 어떻게 거는지 알아보자. 코드를 따라가다보면 Redission 클래스에서 tryLock()의 구현체를 확인할 수 있다. 
코드에 대한 내용을 간단하게 정리하자면, 
주어진 대기 시간(waitTime) 내에 락을 획득하려 시도하며, 락을 획득하면 지정된 임대 시간(leaseTime) 동안 락을 유지한다. 락을 즉시 획득하지 못한 경우, 다른 클라이언트의 락 해제 이벤트를 대기하기 위해 Pub/Sub 메커니즘을 활용하고, 대기 시간 내에 락을 획득하지 못하면 false를 반환하는 매커니즘을 확인할 수 있다. 
<img src="https://velog.velcdn.com/images/sweet_sumin/post/942080f9-4adb-40d5-b537-e2ee4f3e0251/image.png" alt=""></p>
<p>3) 락 해제(열쇠로 잠금 풀기)</p>
<p>비동기적으로 락을 해제하는 모습을 볼 수 있다. 
unlock코드를 파보다보면, unlockInnerAsync메소드를 발견할 수 있다. 
<img src="https://velog.velcdn.com/images/sweet_sumin/post/ace50a8b-3116-4e4d-b2f6-721ab484d96c/image.png" alt="">
첫번째 박스인 락 해제시도 코드를 따라가다보면, 아래 사진에 나온 것처럼 레디스에 명령어를 보내는 코드를 확인할 수 있었다. 
<img src="https://velog.velcdn.com/images/sweet_sumin/post/6b7f8b62-d548-41cd-afde-a194d56ce960/image.png" alt=""></p>
<h3 id="2🤔-트랜잭션-분리">2.🤔 트랜잭션 분리</h3>
<p>코드를 살펴보면 락을 걸고 나서 트랜잭션을 분리해서 비즈니스 로직을 실행하는 역할을 하는 것을 볼 수 있다. </p>
<p><img src="https://velog.velcdn.com/images/sweet_sumin/post/55536ec0-d5e8-4f62-964e-08458dbc23c2/image.png" alt="">
음..쉽게 말하면 DistributedLock 어노테이션이 선언된 메서드를 <strong>별도의 트랜잭션으로</strong> 실행하게 만든 코드인 것이다. 
<img src="https://velog.velcdn.com/images/sweet_sumin/post/13dbe77e-56f1-40fc-ad98-d20acca68ccf/image.png" alt=""></p>
<p>Propagation.REQUIRES_NEW 옵션을 지정해 부모 트랜잭션의 유무에 관계없이 별도의 트랜잭션으로 동작하게끔 설정하고 반드시 트랜잭션 커밋 이후 락이 해제되게끔 처리하고 있다. 왜 이렇게 분리를 했을까?
해당 내용은 <a href="https://helloworld.kurly.com/blog/distributed-redisson-lock/">컬리의 블로그</a>에 너무 자세히 써져있다. 내가 진행한 프로젝트도 재고를 위한 분산락인데 여기서도 재고를 예시로 들어서 너무나 적절하게 써져있으니 해당 링크 참고하길 바란다. 결론을 말하자면 데이터 정합성을 위한 방법으로 트랜잭션 커밋 이후 락이 해제되게끔 처리 해놓았다. </p>
<h2 id="📌-추가된-요구사항">📌 추가된 요구사항</h2>
<p>실전으로 넘어가기 전에, 추가할 요구사항이 있다. 앞선 요구사항은 하나의 key 즉, 하나의 row만 락을 거는 형식으로 구현되어 있다. 하지만 우리 회사 특성상 주문시 여러 상품을 동시에 상태 변경하기 때문에 한번에 여러 상품의 재고를 변경해야한다. 따라서 하나의 row가 아닌 여러 row에 락을 걸어야 한다. </p>
<p>그렇다면 기존에 받는 키도 하나에서 여러개를 받게 되고 락도 동시에 여러개를 건다는 말이겠지? 정리하자면, 여러 개의 락을 동시에 걸고, 하나라도 실패하면 전체 실패하도록 하고 싶다는 것이다. 이때 난 RedissonMultiLock이라는 것을 사용했다. </p>
<p><img src="https://velog.velcdn.com/images/sweet_sumin/post/daf79e99-4aee-49a1-ab06-aae8528faa82/image.png" alt=""></p>
<p>즉, 하나의 트랜잭션처럼 모든 락이 성공해야만 실행되도록 할때 사용된다. 그렇다는 말은 락을 해제할때도 한꺼번에 해제한다는 말과 동일하다. </p>
<p>이제 추가된 요구까지 알아보았으니 본격적으로 테스트를 해볼까? 해당 내용은 다음편에 있다. </p>
<p>참고) </p>
<ul>
<li><a href="https://velog.io/@a01021039107/%EB%B6%84%EC%82%B0%EB%9D%BD%EC%9C%BC%EB%A1%9C-%ED%95%B4%EA%B2%B0%ED%95%98%EB%8A%94-%EB%8F%99%EC%8B%9C%EC%84%B1-%EB%AC%B8%EC%A0%9C%EC%9D%B4%EB%A1%A0%ED%8E%B8">https://velog.io/@a01021039107/%EB%B6%84%EC%82%B0%EB%9D%BD%EC%9C%BC%EB%A1%9C-%ED%95%B4%EA%B2%B0%ED%95%98%EB%8A%94-%EB%8F%99%EC%8B%9C%EC%84%B1-%EB%AC%B8%EC%A0%9C%EC%9D%B4%EB%A1%A0%ED%8E%B8</a></li>
<li><a href="https://helloworld.kurly.com/blog/distributed-redisson-lock/">https://helloworld.kurly.com/blog/distributed-redisson-lock/</a></li>
<li><a href="https://techblog.woowahan.com/17416/">https://techblog.woowahan.com/17416/</a></li>
<li><a href="https://velog.io/@jinony/Spring-Boot-Apache-JMeter%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EB%B6%80%ED%95%98-%ED%85%8C%EC%8A%A4%ED%8A%B8">https://velog.io/@jinony/Spring-Boot-Apache-JMeter%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EB%B6%80%ED%95%98-%ED%85%8C%EC%8A%A4%ED%8A%B8</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[논문 - Scaling Memcached at Facebook: A look at the complexities of Caching]]></title>
            <link>https://velog.io/@sweet_sumin/%EB%85%BC%EB%AC%B8-Scaling-Memcached-at-Facebook-A-look-at-the-complexities-of-Caching</link>
            <guid>https://velog.io/@sweet_sumin/%EB%85%BC%EB%AC%B8-Scaling-Memcached-at-Facebook-A-look-at-the-complexities-of-Caching</guid>
            <pubDate>Sun, 24 Nov 2024 03:08:08 GMT</pubDate>
            <description><![CDATA[<p>논문 읽기 스터디를 하고 있는데, 이번엔 나의 발표차례여서 해당 논문을 정리한 내용을 공유하려고 한다. 논문 이외에 조사한 내용도 포함되어 있으니 혹시 잘못된 정보가 존재한다면 피드백은 언제나 환영!!</p>
<h3 id="등장-배경---facebook의-상황">등장 배경 - facebook의 상황</h3>
<p>Facebook은 매일 수억 명의 사용자로부터 발생하는 막대한 읽기/쓰기 요청을 처리해야 한다. 이를 위해 대규모 데이터를 빠르게 처리하고 실시간으로 사용자 경험을 제공하는 것이 필수적이다. 기존의 데이터베이스 시스템만으로는 이러한 규모를 감당하기 어렵기 때문에, <strong>Memcached</strong>를 기반으로 한 분산 캐싱 시스템을 구축했다.</p>
<ul>
<li>초당 수십억 요청을 처리해야 하는 대규모 소셜 네트워크 인프라.</li>
<li>데이터베이스 부하 증가로 인해 응답 시간 지연 발생.</li>
<li>전 세계 사용자에게 실시간 데이터 제공 필요.</li>
</ul>
<h3 id="memcache">memcache</h3>
<p>메모리 내 데이터를 빠르게 저장하고 읽는데 사용되는 해시 테이블 기반 시스템</p>
<p>facebook에서 memcache란?</p>
<ul>
<li>인메모리 캐싱 솔루션 → 읽기/쓰기 요청 경감 및 데어터 접근 속도 향상</li>
<li>분산 키-값 저장소</li>
</ul>
<h3 id="facebook에서의-memcache-아키텍처">facebook에서의 memcache 아키텍처</h3>
<p>Facebook은 Memcached를 단순히 한 대의 서버에서 사용하는 것이 아니라, 여러 서버로 확장하여 클러스터화했다. 데이터는 일관된 해싱 기법을 통해 여러 Memcached 서버에 분산 저장되며, 클라이언트는 Memcached를 먼저 조회한 후 캐시 미스일 경우 데이터베이스에서 데이터를 가져오도록 설계되었다.</p>
<ul>
<li>기본 구조
1)  master- slave 구조
  <img src="https://velog.velcdn.com/images/sweet_sumin/post/ecb7a5b9-5ba9-4b38-9ea1-856f26a54c95/image.png" alt="">
2)  일관된 해싱을 통해 키 분산
3) 모든 웹서버는 짧은 시간 내에 모든 memcache 서버와 통신
(all to all 통신 패턴→ 병목 현상, incast congestion의 원인)</li>
</ul>
<ul>
<li>읽기 및 쓰기 경로</li>
</ul>
<pre><code>- **읽기**: Memcache 조회 → 캐시 미스 시 데이터베이스 접근.
- **쓰기**: 데이터베이스 업데이트 후 Memcache 캐시 삭제(무효화).</code></pre><p><img src="https://velog.velcdn.com/images/sweet_sumin/post/a7d9844f-75bc-497f-9438-d6fc14c46efe/image.png" alt=""></p>
<h2 id="주요-기술-및-최적화">주요 기술 및 최적화</h2>
<h3 id="📌-병렬-요청-및-일괄-처리">📌 병렬 요청 및 일괄 처리</h3>
<p>Facebook의 <strong>Memcache 클라이언트</strong>가 <strong>네트워크 효율성을 극대화하고 요청 지연 시간을 최소화하기 위해 구현한 전략</strong></p>
<ol>
<li><p>병렬 요청 </p>
<p> <strong>문제</strong></p>
<ul>
<li><p>Facebook의 한 사용자 요청(예: 피드 로드)은 Memcache에서 <strong>수백 개의 키를 요청</strong>하는 작업으로 이루어질 수 있다. (한 요청 = 여러 memcache 조회)</p>
</li>
<li><p>이러한 요청을 하나씩 순차적으로 처리하면 네트워크 왕복(Round-Trip Time, RTT)이 증가하여 응답 시간이 길어진다.</p>
</li>
<li><p><em>해결 방법*</em></p>
</li>
<li><p>요청 간의 <strong>의존 관계</strong>를 분석하여 병렬로 처리 가능한 요청을 동시에 실행.</p>
</li>
<li><p>의존 관계를 DAG(Directed Acyclic Graph, 방향 비순환 그래프)로 나타내어 요청을 설계:</p>
<ul>
<li>예: 요청 A가 요청 B에 의존하지 않는다면 두 요청을 병렬로 실행 가능.</li>
<li>요청 B가 요청 A의 결과를 필요로 한다면, A를 완료한 후에 B를 실행.</li>
<li>DAG 구조<ul>
<li>DAG(Directed Acyclic Graph) :  <strong>방향성 비순환 그래프</strong></li>
<li>데이터 또는 작업의 의존 관계를 나타내는 데 사용</li>
<li>Facebook의 Memcache 클라이언트가 요청을 병렬 처리하는 데 중요한 역할</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ol>
<blockquote>
<p>💡 예시</p>
</blockquote>
<ul>
<li>Facebook 뉴스피드 로드 시:<pre><code>   1) 게시물 데이터를 가져오기 위한 요청(A).</code></pre>   2) 각 게시물의 댓글 데이터를 가져오는 요청(B, C, D).
   3) 댓글 좋아요 수 데이터를 가져오는 요청(E, F).<pre><code>이처럼 요청 간 **의존 관계**가 있을 경우, 순차적으로 처리하면 성능이 저하</code></pre><blockquote>
</blockquote>
</li>
<li>DAG 구조의 적용 : 의존 관계를 <strong>DAG로 표현</strong>하여 병렬로 실행 가능한 요청을 구분<pre><code>          - A → (B, C, D) → (E, F).
          - 요청 A가 완료된 후, 요청 B, C, D를 병렬로 실행.
          - B, C, D가 완료되면, 요청 E와 F를 병렬로 실행.</code></pre><blockquote>
</blockquote>
</li>
<li>결과<ul>
<li>요청의 네트워크 왕복 횟수를 최소화하여 <strong>응답 시간을 단축</strong>.</li>
<li>많은 사용자 요청을 빠르게 처리할 수 있는 병렬 처리가 가능해짐.</li>
</ul>
</li>
</ul>
<ol start="2">
<li><p>요청 일괄 처리</p>
<p> <strong>문제:</strong></p>
<ul>
<li><p>Memcache는 단일 요청마다 네트워크 비용이 발생하므로, <strong>많은 키를 개별 요청으로 보낼 경우 네트워크와 서버 부하가 증가한다</strong></p>
</li>
<li><p><em>해결 방법:*</em></p>
</li>
<li><p>여러 키를 한 번의 요청으로 묶어서 일괄 처리(Batch) 형태로 서버에 전송.</p>
<ul>
<li>예: 24개의 키를 요청해야 할 경우, 24번의 개별 요청 대신 <strong>한 번의 요청으로 묶어서 처리</strong>.</li>
</ul>
</li>
<li><p>클라이언트는 <strong>병렬로 묶인 요청 배치</strong>를 Memcache 서버에 보냄으로써 네트워크 부하를 줄임.</p>
</li>
<li><p><em>결과:*</em></p>
</li>
<li><p>평균적으로 한 번의 요청에 <strong>24개의 키</strong>를 포함.</p>
</li>
<li><p>서버와 클라이언트 간 네트워크 왕복(RTT)이 감소.</p>
</li>
<li><p>네트워크 패킷 수를 줄여 <strong>네트워크 대역폭</strong>을 절약.</p>
</li>
</ul>
</li>
</ol>
<blockquote>
<p>💡 사례 예시
사용자가 Facebook 뉴스피드 로드 요청 시</p>
</blockquote>
<ol>
<li><strong>병렬 처리:</strong><ul>
<li>뉴스피드의 각 게시물, 댓글, 좋아요 데이터를 독립적으로 병렬 요청.</li>
</ul>
</li>
<li><strong>일괄 처리:</strong><ul>
<li>게시물 100개에 대한 데이터를 한 번의 요청으로 Memcache에서 가져옴.</li>
</ul>
</li>
</ol>
<h3 id="📌-클라이언트-서버-통신의-최적화-방식">📌 클라이언트-서버 통신의 최적화 방식</h3>
<p>배경</p>
<ul>
<li>Facebook의 Memcache 시스템에서 <strong>웹 서버</strong>(클라이언트)는 Memcache 서버에 자주 요청을 보낸다.</li>
<li>이때 <strong>클라이언트와 서버 간의 통신</strong>은 효율적이어야 하고, 지연 시간을 최소화해야 한다.</li>
<li>초기 설계에서는 <strong>서버 간 통신</strong>이 필요하지 않았지만, 클라이언트와 서버 간의 통신에서 <strong>최적화가 필요</strong>했기 때문에 다양한 방법이 도입됨</li>
</ul>
<p>방식</p>
<ul>
<li>Memcache는 <strong>서버 간의 직접적인 통신을 하지 않는다</strong><ul>
<li><strong>Memcache 서버들</strong>은 서로 <strong>상호작용하지 않고</strong>, 각 클라이언트(웹 서버)가 여러 Memcache 서버에 직접 접근</li>
<li>모든 복잡한 로직(예: 요청 라우팅, 오류 처리 등)은 <strong>클라이언트</strong>(웹 서버)에서 처리</li>
</ul>
</li>
<li><strong>상태 없는(stateless)</strong> 시스템</li>
<li>클라이언트(웹 서버)는 요청을 여러 Memcache 서버로 보낼 때, 여러 작업을 동시에 할 수 있도록 <strong>복잡한 로직</strong>을 처리</li>
<li><strong>UDP와 TCP</strong>를 사용하여 통신 → 네트워크 대역폭 최적화<ul>
<li>UDP : 읽기 요청 - 연결을 설정하지 않고 빠르게 데이터를 전송할 수 있기 때문에</li>
<li>TCP : 쓰기 요청 - <strong>데이터 일관성</strong>을 보장해야 하기 때문에 <strong>신뢰성 있는 전송</strong>이 필요</li>
</ul>
</li>
<li><strong>incast congestion</strong>을 방지하기 위한 흐름 제어(flow control)가 적용<ul>
<li><strong>incast congestion :</strong> 여러 클라이언트가 동시에 많은 요청을 서버에 보내면서 네트워크가 과부하되는 현상</li>
<li><strong>슬라이딩 윈도우</strong> 방식을 사용하여 동시에 보낼 수 있는 요청 수를 제한</li>
</ul>
</li>
</ul>
<h3 id="📌-부하-감소를-위한-방법">📌 부하 감소를 위한 방법</h3>
<p><strong>Memcache 시스템</strong>을 사용하여 서버의 부하를 어떻게 줄일까?</p>
<p>Memcache 시스템은 <strong>읽기 요청</strong>이 매우 많고, <strong>캐시 미스</strong>(캐시에서 찾지 못한 데이터를 데이터베이스에서 다시 가져오는 작업)가 발생할 때마다 <strong>데이터베이스 부하</strong>가 증가</p>
<ol>
<li><p>Leases (리스)</p>
<p> stale sets와 Thundering herds를 해결하기 위한 메커니즘</p>
<ul>
<li>stale sets: 캐시된 값이 최신 상태가 아니어서 데이터가 불일치할 때 발생하는 문제.</li>
<li>Thundering herds: 특정 키가 자주 읽고 쓰여서 여러 클라이언트가 동시에 캐시를 갱신하려고 시도하는 상황에서 발생하는 문제.</li>
<li>Leases <strong>메커니즘</strong>:<ul>
<li>Memcache 인스턴스는 클라이언트에게 Leases <strong>토큰</strong>(64비트 값)을 부여.<ul>
<li>클라이언트가 캐시 누락을 경험할 때 데이터를 캐시에 다시 설정하기 위함</li>
<li>토큰 : 클라이언트가 원래 요청한 특정 키에 바인딩된 값</li>
</ul>
</li>
<li><strong>리스가 유효할 때만</strong> 클라이언트가 Memcache에 데이터를 다시 쓸 수 있다.</li>
<li>리스를 통해 <strong>동시 데이터 업데이트</strong>로 인한 불일치를 방지하고, 여러 클라이언트가 같은 데이터를 동시에 수정하는 문제를 해결</li>
</ul>
</li>
<li><strong>결과:</strong><ul>
<li><strong>캐시 세트</strong>가 발생하는 것을 방지하여 <strong>데이터 일관성</strong> 유지.</li>
<li><strong>스램 문제</strong>를 해결해 데이터베이스 부하를 감소시킴.</li>
</ul>
</li>
</ul>
</li>
</ol>
<blockquote>
<p> 💡 예시
    Facebook에서 사용자가 피드를 로드할 때, 각 게시물의 좋아요 수를 Memcache에서 캐시하고 있다. 여러 사용자가 동일한 게시물을 동시에 좋아요를 눌렀다면, 좋아요 수를 업데이트하려는 여러 요청이 동시에 들어오게 될 수 있다.</p>
<ul>
<li>리스가 없는 경우 (문제 발생):</li>
</ul>
<ol>
<li><strong>사용자 A</strong>와 <strong>사용자 B</strong>가 동시에 같은 게시물에 대해 좋아요를 누른다.<ol start="2">
<li>두 사용자가 Memcache에서 캐시된 좋아요 수를 조회하고, 캐시 미스가 발생하여 데이터베이스에서 값을 가져온다.</li>
</ol>
</li>
<li>데이터베이스에서 가져온 값은 <strong>기존 값</strong>이고, 두 사용자가 <strong>동시에</strong> 좋아요 수를 증가시킨다.</li>
<li>두 사용자가 각각 Memcache에 업데이트를 하게 되면, 최신 상태의 데이터가 아니므로 <strong>일관성</strong>이 깨지게 된다.</li>
</ol>
<ul>
<li>리스 적용 후 (문제 해결)</li>
</ul>
<ol>
<li><strong>사용자 A</strong>가 첫 번째로 Memcache에서 캐시된 좋아요 수를 조회.</li>
<li>Memcache는 좋아요 수를 새로 갱신할 수 있는 <strong>리스 토큰</strong>을 사용자 A에게 할당.</li>
<li>사용자 A는 해당 데이터를 갱신하고, 리스 토큰을 이용해 Memcache에 값을 설정.</li>
<li><strong>사용자 B</strong>가 동일한 게시물의 좋아요 수를 조회할 때, 이미 리스 토큰이 만료되지 않은 <strong>사용자 A</strong>에게만 데이터 갱신 권한이 있기 때문에, 사용자 B는 데이터를 다시 가져와 갱신을 시도할 수 없다.</li>
<li>사용자 B는 새로운 리스 토큰을 요청하고, 사용자 A의 갱신 작업이 완료되면 데이터베이스에서 최신 값을 가져와 업데이트.</li>
</ol>
<ul>
<li>결과
<strong>리스 메커니즘</strong> 덕분에 <strong>사용자 A</strong>만 해당 데이터를 갱신할 수 있으며, <strong>사용자 B</strong>는 갱신 권한을 얻기 전까지 기다린다.
이를 통해 <strong>데이터 일관성</strong>을 유지하고, <strong>스램 문제</strong>를 해결하며, <strong>데이터베이스 부하</strong>도 줄인다.</li>
</ul>
</blockquote>
<ol start="2">
<li>Memcache Pools</li>
</ol>
<p>Memcache 서버는 <strong>여러 워크로드</strong>(작업 부하)를 처리할 수 있지만, 서로 다른 데이터 접근 패턴과 메모리 사용량을 가진 워크로드가 한 풀 안에서 상호작용하면 성능이 저하될 수 있다.</p>
<ul>
<li>예를 들어, 자주 변경되는 데이터와 드물게 접근되는 데이터가 같은 Memcache 풀에 있으면, 자주 변경되는 데이터로 인해 드물게 접근되는 데이터가 메모리에서 지워질 수 있다.</li>
<li><strong>Memcache 풀</strong>:<ul>
<li>Memcache 서버를 여러 개의 <strong>별도 풀</strong>로 나누어 사용<ul>
<li><strong>wildcard pool</strong>: 일반적인 캐시 데이터를 저장. 자주 업데이트되지 않는 데이터</li>
<li><strong>app-specific pool</strong>: 자주 변하는 데이터를 저장.</li>
<li><strong>replicated pool</strong>: 읽기 요청이 많은 데이터를 저장.</li>
<li><strong>regional pool</strong>: 지역적 특성이 있는 데이터를 저장.</li>
</ul>
</li>
</ul>
</li>
<li><strong>결과:</strong><ul>
<li><strong>서로 다른 데이터를 분리</strong>하여 풀에 저장함으로써, 각 데이터 유형에 맞는 최적화된 메모리 관리와 성능 향상을 이끌어낼 수 있다</li>
<li><strong>서로 다른 데이터 유형</strong>이 <strong>메모리 풀 간섭 없이</strong> 저장되므로, 성능이 향상되고 효율적인 <strong>메모리 사용</strong>이 가능</li>
</ul>
</li>
</ul>
<ol start="3">
<li>Replication
특정 키가 <strong>자주 요청</strong>되는 경우, 여러 Memcache 서버에 복제본을 만들어 <strong>데이터 접근 시간</strong>을 단축시키는 기법</li>
</ol>
<ul>
<li>복제된 데이터를 <strong>여러 Memcache 서버에 분산 저장</strong>하여, 읽기 요청이 한 서버에 몰리는 것을 방지.</li>
<li>데이터베이스 요청이 많아지는 시점에 복제를 통해 <strong>다중 서버에서 동시에 데이터 제공</strong>이 가능</li>
</ul>
<h3 id="📌-글로벌-확장">📌 글로벌 확장</h3>
<p>Memcache 시스템이 <strong>글로벌 사용자</strong>에게 안정적이고 빠른 서비스를 제공하였나</p>
<ol>
<li><strong>지역 기반 설계</strong>:</li>
</ol>
<ul>
<li>facebook은 <strong>글로벌 사용자</strong>에게 빠르고 일관된 응답을 제공하기 위해, Memcache를 <strong>지역별로 분산</strong>하여 배치</li>
<li><strong>지역 단위</strong>로 캐시 데이터를 관리하고, <strong>서버 간의 데이터 전파 시간</strong>을 줄여 응답 시간을 최적화</li>
<li><strong>각 지역별 클러스터</strong>:<ul>
<li>Facebook은 <strong>지역별 클러스터</strong>를 구성하여, <strong>서버들이 같은 지역 내에서만 통신</strong>하도록 설계.</li>
<li>예를 들어, <strong>북미, 유럽, 아시아</strong> 등 각 대륙별로 독립적인 Memcache 클러스터를 운영.</li>
<li><strong>각 지역에 서버를 배치</strong>하여, 사용자 요청이 발생할 때 <strong>가장 가까운 서버</strong>에서 데이터를 처리하도록 하여 <strong>응답 시간</strong>을 최소화.</li>
</ul>
</li>
<li><strong>글로벌 서버 분산</strong>:<ul>
<li>데이터는 여러 서버에 <strong>복제</strong>되어 저장. 각 지역에 <strong>복제된 데이터</strong>를 두어, 한 지역에서 장애가 발생하더라도 다른 지역에서 <strong>백업 데이터</strong>를 빠르게 사용할 수 있게 된다.</li>
</ul>
</li>
</ul>
<ol start="2">
<li><strong>클러스터 분할 (Sharding)</strong> : 효율적인 확장</li>
</ol>
<p><strong>샤딩</strong>은 Memcache 서버를 여러 개의 작은 <strong>서브 클러스터</strong>로 나누는 방식으로, 각 클러스터가 <strong>데이터의 일부분만 담당</strong>하도록 한다.</p>
<ol>
<li><strong>데이터 일관성 (Data Consistency)</strong> : 다양한 지역에 배포된 서버들이 동일한 데이터를 제공. </li>
</ol>
<ul>
<li><p>캐시 일관성 유지</p>
<ul>
<li>Facebook은 <strong>일관성 있는 데이터</strong>를 보장하기 위해 <strong>원격 캐시</strong> 및 <strong>데이터 동기화</strong> 전략을 사용.<ul>
<li>각 서버가 다른 지역에 있는 서버들과 <strong>주기적으로 데이터를 동기화</strong>하여, 최신 데이터를 여러 지역에서 동일하게 제공.</li>
<li><strong>데이터베이스</strong>에서 변경된 사항은 <strong>캐시에서 무효화</strong>된 후, 새로 갱신된 데이터를 다시 캐시로 가져오는 방식으로 일관성을 유지.</li>
</ul>
</li>
</ul>
<ul>
<li><p>데이터 전파 및 복제</p>
<ul>
<li>데이터베이스의 <strong>변경 사항</strong>은 <strong>복제된 Memcache 서버</strong>에 자동으로 전파.</li>
<li><strong>동기 복제</strong> 또는 <strong>비동기 복제</strong> 방식을 사용하여 각 지역 서버에 <strong>실시간 데이터 동기화</strong>를 구현.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="📌-단일-서버-개선-사항">📌 단일 서버 개선 사항</h3>
<ul>
<li><p>멀티스레드 구현</p>
</li>
<li><p>Adaptive Slab Allocator 적용
Memcache 서버에서 메모리를 <strong>고정 크기 블록</strong>으로 할당할 경우, <strong>메모리 낭비</strong>가 발생할 수 있음. 특히 작은 크기의 데이터나 자주 변하는 데이터에 대해서는 비효율적인 메모리 사용이 문제이다.</p>
<ul>
<li>메모리 할당을 <strong>동적으로 최적화</strong></li>
<li>작은 데이터는 작은 크기의 슬랩을 사용하고, 큰 데이터는 더 큰 슬랩을 할당하여 <strong>메모리 사용 효율</strong>을 높임</li>
</ul>
</li>
<li><p>LRU(Least Recently Used) 캐시 정책 개선
Memcache는 기본적으로 <strong>LRU(Least Recently Used)</strong> 정책을 사용하여 오래된 데이터를 삭제하고 새로운 데이터를 저장하는 방식으로 동작 → <strong>데이터 삭제</strong>가 발생할 때 불필요한 연산이 발생
<strong>LRU 알고리즘</strong>을 개선하여 <strong>캐시의 유효성</strong>을 최적화하고, 불필요한 삭제를 최소화</p>
</li>
<li><p>비동기 요청 처리 도입</p>
</li>
<li><p>네트워크 지연 시간 감소</p>
</li>
<li><p>메모리 효율성 향상</p>
</li>
</ul>
<h2 id="그래서-redis랑-어떤차이가-있는-건데-🤔">그래서 Redis랑 어떤차이가 있는 건데? 🤔</h2>
<h3 id="데이터-구조">데이터 구조</h3>
<ul>
<li><p>Redis: 다양한 데이터 구조를 지원.</p>
<ul>
<li>문자열 (String)</li>
<li>리스트 (List)</li>
<li>집합 (Set)</li>
<li>정렬된 집합 (Sorted Set)</li>
<li>해시 (Hash)</li>
<li>비트맵, 하이퍼로그로그 등 고급 자료 구조</li>
</ul>
</li>
<li><blockquote>
<p>이를 통해 복잡한 데이터 연산을 인메모리에서 처리할 수 있다.</p>
</blockquote>
</li>
<li><p>Memcached:
단순히 key-value 쌍의 데이터를 문자열 또는 바이너리 형태로 저장.</p>
</li>
<li><blockquote>
<p>구조가 간단하여 캐싱 이외의 복잡한 작업에는 부적합.</p>
</blockquote>
</li>
</ul>
<h3 id="영속성-persistence">영속성 (Persistence)</h3>
<ul>
<li><p>Redis:데이터 영속성을 지원.</p>
<ul>
<li>RDB (주기적으로 데이터 덤프)</li>
<li>AOF (명령 로그 기록)</li>
</ul>
</li>
<li><blockquote>
<p>캐시로뿐만 아니라 데이터베이스로도 활용 가능.</p>
</blockquote>
</li>
<li><p>Memcached:영속성을 지원하지 않는다.</p>
</li>
<li><blockquote>
<p>메모리가 날아가면 데이터도 손실.</p>
</blockquote>
</li>
</ul>
<h3 id="클러스터링-clustering">클러스터링 (Clustering)</h3>
<ul>
<li><p>Redis:Redis 클러스터를 통해 샤딩과 복제를 지원.</p>
</li>
<li><blockquote>
<p>확장성이 높으며, 고가용성을 위한 설정이 가능합니다.</p>
</blockquote>
</li>
<li><p>Memcached:
클러스터링을 자체적으로 지원하지 않는다.</p>
</li>
<li><blockquote>
<p>클라이언트 라이브러리를 사용해 샤딩 구현.</p>
</blockquote>
</li>
</ul>
<h3 id="성능">성능</h3>
<ul>
<li><p>Redis:
다양한 데이터 구조와 기능으로 인해 특정 상황에서 오버헤드가 발생할 수 있습니다.
하지만 대부분의 경우 성능은 매우 뛰어나다.</p>
</li>
<li><p>Memcached:
단순한 구조 덕분에 읽기/쓰기 성능이 매우 빠르다.</p>
</li>
<li><blockquote>
<p>단순 캐싱 용도라면 Redis보다 약간 더 나은 성능을 보일 수 있다.</p>
</blockquote>
</li>
</ul>
<h3 id="사례">사례</h3>
<ul>
<li>Redis: 세션 관리, 실시간 분석 (e.g., 순위, 카운터), 메시지 큐, Pub/Sub 시스템, 복잡한 데이터 구조 캐싱</li>
<li>Memcached:단순 데이터 캐싱, 웹 페이지 렌더링 속도 개선,자주 변경되지 않는 데이터 저장</li>
</ul>
<h3 id="메모리-관리">메모리 관리</h3>
<ul>
<li><p>Redis:</p>
<ul>
<li>LRU(Least Recently Used) 정책으로 메모리 관리.</li>
<li>사용자가 메모리 사용량을 세부적으로 제어 가능.</li>
</ul>
</li>
<li><p>Memcached:
메모리 블록 단위로 데이터를 저장하며, 메모리 초과 시 오래된 데이터를 자동 삭제.</p>
</li>
</ul>
<blockquote>
<p>정리하자면, 
Redis는 다양한 데이터 구조와 영속성을 지원하며, 데이터베이스나 메시지 브로커 등으로도 활용 가능. 복잡한 작업에 적합하다.
Memcached는 단순한 데이터 캐싱에 특화되어 있으며, 빠르고 가볍다</p>
</blockquote>
<p>논문 링크 : <a href="https://www.usenix.org/system/files/conference/nsdi13/nsdi13-final170_update.pdf">https://www.usenix.org/system/files/conference/nsdi13/nsdi13-final170_update.pdf</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[작은것에도 관심을 기울이자 - Float, Double, BigDecimal 편]]></title>
            <link>https://velog.io/@sweet_sumin/%EC%9E%91%EC%9D%80%EA%B2%83%EC%97%90%EB%8F%84-%EA%B4%80%EC%8B%AC%EC%9D%84-%EA%B8%B0%EC%9A%B8%EC%9D%B4%EC%9E%90-Float-Double-BigDecimal-%ED%8E%B8</link>
            <guid>https://velog.io/@sweet_sumin/%EC%9E%91%EC%9D%80%EA%B2%83%EC%97%90%EB%8F%84-%EA%B4%80%EC%8B%AC%EC%9D%84-%EA%B8%B0%EC%9A%B8%EC%9D%B4%EC%9E%90-Float-Double-BigDecimal-%ED%8E%B8</guid>
            <pubDate>Fri, 18 Oct 2024 15:22:24 GMT</pubDate>
            <description><![CDATA[<p>float과 double에 관심을 기울인적 있는가?
float과 double은 우리가 프로그래밍을 배우면서 정말 바로 나오는 애들 중 하나이다. 
그냥 있는거고 원래 있었던 애들.. 회사에서도 사실 잘 사용하지 않아왔었는데 문득 이런 생각이 들었다 </p>
<blockquote>
<p>도대체 왜 만들었지? 🤔</p>
</blockquote>
<p>사실 프로그래밍 공부를 하면서 점점 중요하다가 느끼는 것 중 하나가 &#39;호기심을 잃지 않는 것이다&#39; 처음에 공부를 시작할때는 모든 것이 새로웠고 신기했기에 어디에나 호기심이 가득한 상태로 공부해왔다. 그러나, 시간이 지나고 직장 생활을 하다보니 기존에 존재를 알고 있던 것을 정말 깊숙히 알고 있는 것으로 착각하고, 어렵고 신기한것만 탐구하려는 성향이 강해짐을 느껴진다. 이번에 float과 double에 대해 공부하면서 이런 기초적인 것에도 많은 것이 담겨있는데 난 이제까지 알고 있다고 착각하고 있었구나 라는 것을 느낀다. </p>
<p>자, 본격적으로 이제 내가 공부한 내용을 차근차근 읊어볼까~?</p>
<h3 id="float과-double-차이점에-대해-알고-있어">Float과 Double 차이점에 대해 알고 있어?</h3>
<p>float과 double은 모두 숫자를 저장하기 위한 기본 자료형이다. 
float은 32비트의 저장소로 소수점 이하 약 7자리의 정확도를 가지고 있고, double은 64비트의 저장소로 소수점 이하 약 15자리의 정확도를 가진다. </p>
<p>여기서 우리가 주목해야 할 점은 </p>
<ul>
<li>정확도에 차이가 있다. </li>
<li><blockquote>
<p>double이 float보다 정확도가 높구나?</p>
</blockquote>
</li>
<li>메모리에 차이가 있다 </li>
<li><blockquote>
<p>float이 double의 절반의 메모리를 사용한다. 따라서 메모리 효율성이 float이 더 높다. 그럼 double보다 더 많은 데이터를 다룰 수 있다는 것이고 연산속도도 더 빠르겠네~!</p>
</blockquote>
</li>
<li>부동소수점을 위한 기본 자료형이 float과 double이다. </li>
</ul>
<h3 id="부동소수점">부동소수점?</h3>
<ul>
<li>실수를 표현하는 방식 중 하나</li>
<li>숫자를 지수와 가수로 나누어 저장하여 소수점의 위치가 자유롭게 변할 수 있는 방식</li>
</ul>
<p>&quot;부동&quot;은 &quot;떠다니다&quot; 라는 의미로, 소수점의 위치가 고정되어 있지 않고 숫자의 크기에 따라 위치가 변할 수 있음을 나타낸다.그래서 부동소수점은 매우 큰 수나 작은 수를 지수의 크기에 따라 동적으로 표현 할 수 있다. 즉, 넓은 수자 범위를 효율적으로 표현할 수 있다는 것</p>
<p>-&gt; 모든 실수를 정확하게 표현 할 수 없다</p>
<h3 id="부동소수점의-한계-예시">부동소수점의 한계 예시</h3>
<p>난 이거 알아볼때 사실 감동이었다.ㅎㅎ </p>
<p>1) 0.1
<img src="https://velog.velcdn.com/images/sweet_sumin/post/9cb35b6a-7b0d-4111-9e5d-d58bdcd3de8c/image.png" alt="">
( 아, 이건 번외인데 +라는 기호는 쓰지말자..ㅎ)</p>
<p>단순히 작동하면 출력 결과가
<img src="https://velog.velcdn.com/images/sweet_sumin/post/72f9a531-018b-4cec-ae5a-6d0b2d8cd79e/image.png" alt="">
0.1로 나온다. 그럼 정확한거잖아?? 라고 생각할 수 있다. 하지만 이는 반올림의 결과이다 실제로 내부의 값을 bigDecimal을 이용해 확인해보면</p>
<p><img src="https://velog.velcdn.com/images/sweet_sumin/post/40b50a5d-fe2e-4ff4-abed-c443f38e5b37/image.png" alt="">
결과는? 두둥
<img src="https://velog.velcdn.com/images/sweet_sumin/post/1276f475-27a1-480f-915d-0b21e863f62f/image.png" alt=""></p>
<p>정확히 0.1이 아님을 알 수 있다. </p>
<p>2) 0.2와 0.1의 덧셈
부동소수점의 정밀도로 인해 기대값이 0.3이 아닐 수 있다. 
<img src="https://velog.velcdn.com/images/sweet_sumin/post/e2c58e4b-793d-45a3-80cf-42a63e06ff63/image.png" alt=""></p>
<p>의 결과는?
<img src="https://velog.velcdn.com/images/sweet_sumin/post/5fcf21bb-46eb-421d-87fa-cdb9a0a2d6ca/image.png" alt=""></p>
<p>정확히 0.3이 안나오는데 정확성이 중요한 곳에서는 어떻게 부동소수점을 쓰겠는가?</p>
<p>그럼 정확성이 중요할 때는 뭘 사용해야하지? 
BigDecimal을 사용해야한다.</p>
<h3 id="bigdecimal">BigDecimal</h3>
<p>위의 덧셈 연산을 그대로 bigDecimal을 적용해보자면
<img src="https://velog.velcdn.com/images/sweet_sumin/post/2ea58fa1-3c53-4cd3-bba7-e94c233aa37c/image.png" alt=""></p>
<p>의 결과는
<img src="https://velog.velcdn.com/images/sweet_sumin/post/5cd35798-e547-4228-b7d6-777501e8def4/image.png" alt="">
으로 정확한 숫자가 나오는 것을 확인할 수 있다. 그래서 정확도가 필요한 상황이라면 BigDecimal을 사용해야한다. 하지만 단점도 존재한다. </p>
<ul>
<li>연산 속도가 float, double보다 느리다. </li>
<li>메모리 사용량이 높다</li>
<li>성능 저하</li>
</ul>
<p>BigDecimal은 참조형이다. 즉 매번 덧셈, 곱셈, 나눗셈과 같은 기본 연산에서도 객체를 생성해야한다. 객체를 새로 생성한다는 말은 즉, 메모리를 추가로 사용한다는 말이다. 이는 곧 숫자의 크기가 커질 수록 더많은 연산 자원을 소모해서 성능 저하를 발생시킬 가능성이 있다는 말이다.</p>
<h3 id="선택-정리">선택 정리</h3>
<ul>
<li>느리지만 높은 정확도가 필요하다면?
BigDecimal</li>
<li>높은 정밀도는 필요하지 않지만 많은 소수점 연산을 빠르게 처리해야하거나 메모리 효율이 중요한 경우? 
float</li>
<li>float보다는 정밀도가 높고 성능 저하가 없으며 메모리에 대한 여유가 있으면? 
Double</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[public static void main(String[] args)은 왜 static 인가?]]></title>
            <link>https://velog.io/@sweet_sumin/public-static-void-mainString-args</link>
            <guid>https://velog.io/@sweet_sumin/public-static-void-mainString-args</guid>
            <pubDate>Sat, 12 Oct 2024 15:15:02 GMT</pubDate>
            <description><![CDATA[<p>여러분은 public static void main(String[] args) 가 왜 static으로 선언되어 있어야 했는지 의문이 있었는가?
솔직히 나도 처음에는 관심을 안가졌었는데 오늘은 이상하게 눈에 띄어서 살짝 들춰봤다.</p>
<p>오라클 문서에는 
<img src="https://velog.velcdn.com/images/sweet_sumin/post/057045af-b628-4081-8390-b5333233f88e/image.png" alt="">
&quot;메인 메서드는 반드시 public, static, void로 선언하고, 그 매개변수는 String 배열 타입으로 선언해야 합니다.&quot; 라고 써져있다.</p>
<p>아래 내용을 진행하기전에 우리가 알아야할 배경 지식을 알려주겠다. </p>
<ul>
<li>자바프로그램은 JVM이라고 불리는 자바 가상 머신을 통해 실행된다.</li>
<li>프로그램을 작동시키기 위해 JVM이 처음으로 부르는 시작점은 main 메소드이다.
시작점에 대한 의문은 갖지 마라. 떡하니 entry point라고 써져있다. 자바 프로그램의 진입점으로 예약된 메소드 이름이다. 
<img src="https://velog.velcdn.com/images/sweet_sumin/post/a2592e34-bfdb-4dd7-8ef4-473a19141f06/image.png" alt=""></li>
</ul>
<p>참고) </p>
<ul>
<li><a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.1.4">오라클 jls-12.1.4</a></li>
<li><a href="https://www.herongyang.com/Java/Execution-Entry-Point-Main-Method.html#:~:text=As%20mentioned%20in%20the%20previous%20section%2C%20a%20Java,method%20must%20be%20declared%20as%20%22public%20static%20void%22.">Java Tutorials</a></li>
</ul>
<h2 id="왜-메인-메소드는-static이라고-선언이-되어-있어야-하는가">왜 메인 메소드는 static이라고 선언이 되어 있어야 하는가?</h2>
<p>프로그램을 작동시키기 위해 JVM이 처음으로 부르는 시작점은 main 메소드이기 때문에 main 메소드를 부를 때 JVM은 정말 아무것도 없는 빈털털이로 객체 생성이 아무것도 없는 상태에서 호출하고 있고 객체 생성을 하지도 못하고 있다. 
결론부터 말하자면, 객체 생성 없이 프로그램을 실행할 수 있어야 하기 때문에, 클래스 레벨에서 이름만으로 직접 호출 가능한 메소드로 static으로 선언 되어야 한다. </p>
<h3 id="객체-생성">객체 생성?</h3>
<p>객체가 무엇인데? 객체 생성은 어디에서 이루어지는데? 우선 근본적인 것부터 해결해보자. </p>
<p><img src="https://velog.velcdn.com/images/sweet_sumin/post/641814ea-dae5-41b1-9205-d3a0b3097a28/image.png" alt=""></p>
<p>위의 나온바와 같이, 제임스 고슬링이 작성한 자바 언어 설명서 (The java Language Specification)에는 객체(object)란 클래스의 인스턴스나 배열을 말한다고 정의되어 있다. 
우리가 코드상에서 객체를 만든다고 한다면 흔히 new 선언을 통해 생성해준다. 
<img src="https://velog.velcdn.com/images/sweet_sumin/post/334e54f6-52a0-4c2c-a851-60b7d52b60c8/image.png" alt=""></p>
<p>new로 선언한다고 하니 참조자료형이 생각나지 않나?ㅎㅎ 즉, 객체는 힙메모리에 저장된다.아래 자료에서도 힙에 할당된다고 써져있다. 
<img src="https://velog.velcdn.com/images/sweet_sumin/post/a1f61605-8c04-48e6-a43d-0ebdfb1bbc50/image.png" alt=""></p>
<p>클래스에서 객체를 만들기 때문에 클래스가 로드 된 후에야 객체 생성을 시작할 수 있다. </p>
<h3 id="jvm이-자바-프로그램을-실행-하기까지의-진행-과정은">JVM이 자바 프로그램을 실행 하기까지의 진행 과정은?</h3>
<h4 id="📌-1-클래스-로드-및-메소드-영역-할당-정적-초기화">📌 1. 클래스 로드 및 메소드 영역 할당, 정적 초기화</h4>
<p>자바 애플리케이션이 시작되면 JVM은 클래스 파일을 로드하고, 메소드 영역(Method Area)에 클래스의 static 필드와 메소드등의 클래스 정보를 저장한다. 그리고 정적 초기화가 진행된다 (초기화 과정에서 필요한 값들이 메소드 영역에 저장된다)
이 시점에 인스턴스 생성은 발생하지 않기에 힙 메모리나 스택 메모리는 사용되지 않는다. 
<img src="https://velog.velcdn.com/images/sweet_sumin/post/e230059f-84a5-45cd-b9bd-14f94f6f1f43/image.png" alt=""></p>
<h4 id="📌-2-jvm이-main-메소드-호출">📌 2. JVM이 main 메소드 호출</h4>
<p>JVM이 프로그램 시작점으로 main 메소드를 찾는다. main 메소드는 static으로 선언되어 있기 때문에 인스턴스가 없어도(객체 생성없이도) JVM이 main 클래스를 메모리에 로드하고 호출할 수 있다. </p>
<h4 id="📌-3-메인-메소드-실행">📌 3. 메인 메소드 실행</h4>
<p>main 메소드가 호출되면, 클래스가 메로리에 로드 되었기 때문에 그 안에서 이제 객체 생성이 시작된다. 이제 힙과 스택 메모리가 사용되기 시작하는 것이다. 프로그램이 본격적으로 실행된다. </p>
<p>이를 통해 우리가 알 수 있는 점을 정리하자면, 객체 생성 및 할당을 힙메모리에서 하지만, main 메소드를 호출하기 전에는 JVM이 주로 메소드 영역에만 접근하여 프로그램을 준비하기 때문에 main 메소드 호출전에는 객체 생성을 못한다는 말이며, static으로 메인 메소드를 선언을 해야 객체 생성 없이 클래스 자체에서 main 메소드를 호출할 수 있다는 것을 알 수 있다. </p>
<p>참고) <a href="https://docs.oracle.com/javase/specs/jls/se23/html/index.html">오라클 문서</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[컴파일러와 인터프리터]]></title>
            <link>https://velog.io/@sweet_sumin/%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%AC%EC%99%80-%EC%9D%B8%ED%84%B0%ED%94%84%EB%A6%AC%ED%84%B0</link>
            <guid>https://velog.io/@sweet_sumin/%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%AC%EC%99%80-%EC%9D%B8%ED%84%B0%ED%94%84%EB%A6%AC%ED%84%B0</guid>
            <pubDate>Wed, 09 Oct 2024 14:46:25 GMT</pubDate>
            <description><![CDATA[<p>오늘의 글은 &#39;자바의 신&#39;이라는 책에서 던져준 질문이다. 역시 책은 시간 지나서 다시 읽으면 또 다른 새로운 시각을 준다.</p>
<blockquote>
<p>💡 &#39;컴파일이 뭔지 설명할 수 있어?&#39;</p>
</blockquote>
<p>오늘은 여러가지 내용 중에 컴파일에 관련된 것에 한번 알아보려고 한다.</p>
<h3 id="컴파일은-뭐야">컴파일은 뭐야?</h3>
<p>우선 책의 내용을 정리하자면, compile이라는 단어는 &quot;엮다&quot;라는 말이다. 즉, 내가 만든 프로그램 코드를 컴퓨터가 이해할 수 있도록 엮어주는 작업이 컴파일이라고 한다. 그래서 컴파일러는 번역기라고 생각하면 된다. 우리랑 컴퓨터랑 언어가 다르니까~ </p>
<p><img src="https://velog.velcdn.com/images/sweet_sumin/post/36bc7d3a-d8c3-4469-ad8b-41985fc2f05b/image.png" alt="">
책에서는 딱 이렇게 만 보여줬다 간단하게..ㅎ 궁금하면 나보고 더 파보라는 이야기인가? 이 그림에는 정말 많은 것이 생략되어 있다. 양파같은 녀석..ㅋㅋ</p>
<h3 id="하이브리드-언어">하이브리드 언어</h3>
<p>첫번째로 까볼 양파는..🧅 사진에서도 그렇고 책에서도 그렇고 컴파일러만 등장하지만 사실 &#39;인터프리터&#39;도 존재한다. 자바는 대표적인 하이브리드 언어이다. 
컴파일러와 인터프리터에 대해 간단히 정리하자면, </p>
<h4 id="컴파일러">컴파일러</h4>
<ul>
<li>전체 파일을 통으로 번역한다. ( 전체적으로 한번만 디코딩해서 실행한다)</li>
<li>한꺼번에 번역하고 저장해야하니 저장공간(메모리)가 많이 필요로 한다. </li>
<li>실행시간 측면에서 인터프리터보다 실행속도가 엄청 빠르다.(초기 스캔 시간은 오래 걸린다.)</li>
<li>스캔하는 과정에서 모든 오류를 한꺼번에 출력해서 실행 전에 오류를 알 수 있다. </li>
</ul>
<h4 id="인터프리터">인터프리터</h4>
<ul>
<li>한 줄 단위로 번역하기 때문에 실행속도가 드리다.한 줄마다 디코딩해야한다.</li>
<li>한 줄씩만 번역하고 저장하니 메모리를 덜 소비한다. </li>
<li>한 줄씩 실행하기 때문에 오류를 만나면 그제서야 프로그램 중지한다. 프로그램을 실행해봐야지만 오류 발견한다.</li>
</ul>
<p>그대는 실행속도를 선택할 것인가.. 메모리를 선택할 것인가..</p>
<h3 id="자바는-왜-하이브리드-방식을-선택하였는가">자바는 왜 하이브리드 방식을 선택하였는가?</h3>
<h4 id="진행-과정">진행 과정</h4>
<p>책의 사진을 내 나름대로 다시 꾸며서 진행과정을 그려보자면 아래 그림으로 된다.
<img src="https://velog.velcdn.com/images/sweet_sumin/post/5a5945b7-b6e2-4d87-b3a4-2ed0c8488da7/image.png" alt=""></p>
<p>보다시피 바이트 코드 전까지는 컴파일방식이고 그 뒤에는 JIT 컴파일러와 인터프리터가 공존하는 하이브리드 방식이다.</p>
<h4 id="우선적으로-왜-앞부분은-컴파일-방식일까">우선적으로 왜 앞부분은 컴파일 방식일까?</h4>
<blockquote>
<p>Write Once, Run Anywhere</p>
</blockquote>
<p>자바의 철학을 엿볼 수 있는 구절이다. &quot;한번 작성하면 어디서든 실행 가능하다&quot; 이를 실현하기 위해 바이트 코드가 필요하고 바이트 코드는 주로 컴파일러가 필요하다. </p>
<ul>
<li>바이트 코드 : 플랫폼에 독립적인 중간코드로 JVM에서 실행될 수 있도록 변환된 형태이다. 어느 운영체제에서나 실행가능하다. </li>
</ul>
<h4 id="그럼-컴파일-방식을-쭉-쓰지-왜-인터프리터를-왜-써">그럼 컴파일 방식을 쭉 쓰지.. 왜 인터프리터를 왜 써?</h4>
<p>다른 언어는 바로 실행하는 것에 비해, 앞에서 설명한 것과 같이  자바는 JVM이 알아들을 수 있는 언어로 바꾸는 작업이 추가로 필요하기 때문에 다른 언어에 비해 출발선이 다르다. 
자바 실행했는데 한참 지나도 세월아 하면서 서버가 안뜨면 누가 자바를 쓰겠나?..ㅎㅎ 이때 필요한것은 속도! 
<strong>컴파일러는 전체를 다 읽어야 하기 때문에 초기 시작 시간이 느린데 인터프리터는 한 줄씩 읽고 즉시 실행하기에 프로그램을 빨리 시작할 수 있다</strong>. </p>
<p>이때 우리가 겪을 수 있는 것은 성능문제가 발생한다. 
JVM이 바이트 코드를 한 줄씩 해석하여 그때그때 실행하게 되는데, 매번 바이트코드를 읽고 해석하는 과정이 반복되니 자주 실행되는 코드일 수록 오버헤드가 커지게 되는 것이다.
빨리 시작하면 뭐해 점점 느려져.. 이런 소리를 들을 것인가?</p>
<h4 id="jit-컴파일러-등장">JIT 컴파일러 등장!</h4>
<blockquote>
<p>Wikipedia - Just-in-time compilation
In computing, just-in-time (JIT) compilation (also dynamic translation or run-time compilations) is a way of executing computer code that involves compilation during execution of a program (at run time) rather than before execution.</p>
</blockquote>
<p>그래서 등장한것이 JIT 컴파일러이다. 자주 사용되는 코드(핫스팟)을 감지해서 네이티브 코드로 컴파일 한다. </p>
<ul>
<li>네이티브 코드 : 기계어로 CPU가 바로 실행할 수 있기 때문에 바로 실행된다. 바이트 코드처럼 추가적인 해석 과정이 필요없다. </li>
</ul>
<p>자주 실행되는 코드, 반복되는 코드, 자주 호출되즌 함수를 네이티브 코드로 변환함으로써 더이상 해석없이 빠르게 실행 할 수 있어 추가적으로 속도와 성능이 향상되는 것이다. </p>
<h4 id="정리하자면">정리하자면</h4>
<ul>
<li>JVM에서 초기 실행은 주로 인터프리터 방식을 사용하여 실행 시작을 빠르게 하고,</li>
<li>프로그램이 진행됨에 따라 자주 실행되는 코드에 대해 필요한 부분만 JIT 컴파일러가 개입하여 실행 중에 발생하는 성능 병목을 해결하고 빠른 응답성을 유지하는 것이다. </li>
<li>또한, 자주 사용되지 않는 코드는 굳이 네이티브 코드로 변환하지 않기 때문에 메모리와 CPU 자원을 더 효율적으로 사용할 수 있는 것이다</li>
</ul>
<h3 id="회고">회고</h3>
<p>처음에는 컴파일에 대해서만 쓸려고 했는데 타고타고 가다보니 결론적으로 Execution engine의 일부를 설명하는 글이 되어 버렸다 ㅎㅎ 시간 순삭이다. 재밋긴 했다. 맨날 컴파일 컴파일 해도 그냥 넘겼는데 드디어 양심상 컴파일이라는 단어 앞에 안 작아져도 될 것 같다.</p>
<p><img src="https://velog.velcdn.com/images/sweet_sumin/post/753c727e-dea2-4e7c-ba94-205bbcdfc78b/image.png" alt="">
이 글을 쓰면서 벌써 다음 글감이 나왔다. 클래스 로더 ㅋㅋㅋㅋ 야호~</p>
<p>참고</p>
<ul>
<li>[컴파일러 강의] (<a href="http://www.kocw.net/home/cview.do?cid=483c036ed189cda6">http://www.kocw.net/home/cview.do?cid=483c036ed189cda6</a>)</li>
<li><a href="https://en.wikipedia.org/wiki/Java">https://en.wikipedia.org/wiki/Java</a></li>
<li><a href="https://kangmoo.github.io/posts/Compile-vs-Interpret-vs-Hybrid/">https://kangmoo.github.io/posts/Compile-vs-Interpret-vs-Hybrid/</a></li>
<li><a href="https://junhyunny.github.io/information/java/jvm-execution-engine/">https://junhyunny.github.io/information/java/jvm-execution-engine/</a></li>
<li><a href="https://medium.com/@amitvsolutions/jvm-part-2-architecture-1bf3044a9378">https://medium.com/@amitvsolutions/jvm-part-2-architecture-1bf3044a9378</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[실전 레디스] 1장]]></title>
            <link>https://velog.io/@sweet_sumin/%EC%8B%A4%EC%A0%84-%EB%A0%88%EB%94%94%EC%8A%A4-1%EC%9E%A5</link>
            <guid>https://velog.io/@sweet_sumin/%EC%8B%A4%EC%A0%84-%EB%A0%88%EB%94%94%EC%8A%A4-1%EC%9E%A5</guid>
            <pubDate>Sun, 29 Sep 2024 07:20:04 GMT</pubDate>
            <description><![CDATA[<p>다음달부터 시작하는 책은 <a href="https://www.yes24.com/Product/Goods/126528836">실전 레디스</a> 라는 책이다. 
현재 회사내에서 캐시용으로 사용하는 메모리인데, 그냥 단순히 쓰기만 했지 레디스에 대한 공부를 해보지 않아왔기 아무것도 모르고 그냥 쓰고 있다는 죄책감(?)때문에 이 책을 스터디한다고 해서 바로 달려갔다. 정말 반갑고 기대가 아주 큰 스터디 책이다.</p>
<p>A. 레디스를 왜 썼어요? 라고 누군가 물어본다면 뭐라고 답해야 할까
Q. 디비로 가는 비용이 큰데 굳이 디비로 가지 않아도 되는 데이터인 캐시를 담아둘 저장소가 필요했어요. 그리고 빠른 응답으로 속도가 엄청 빨라요</p>
<p>내가 그동안 레디스에 대한 아는 것을 여기까지다..ㅎ
그럼 어떤 사실을 알고 어떤 것을 모르는지 간단히 1장에서 이야기 하고 있는 레디스의 특징을 통해 기록하고자 한다.</p>
<h3 id="왜-빨라">왜 빨라?</h3>
<p>우리가 일반적으로 알고 있는 MySQL이나 오라클과 같은 RDBMS와 같은 디스크 기반으로 처리하는 데이터베이스 엔진이라도 디스크에 입출력할 필요없이 메모리에서 처리하는 경우가 있다고 한다(오,,몰랐는데?🤔) 레디스는 버퍼 관리 등에 따른 오버헤드를 제거하고 기능을 최소화하는 방식을 통해 빠르게 동작한다고 한다. 
아직 초반이라 간단하게 이야기하고 있는데 레디스가 어떤 매커니즘으로 동작하는 지 파악하고 어떤 방식을 사용하여 왜 빠른지에 대한 결론을 짓는 일이 이 책의 읽는데 목표가 될 것 같다.</p>
<h3 id="싱글-스레드-기반이래">싱글 스레드 기반이래!</h3>
<p>아 난 정말 레디스를 그냥 써왔음을 알게되는 순간이었다. 레디스를 검색만 해도 알게되는 내용인데 이제 알게되다니 ㅠ
싱글 스레드지만 이벤트 루프를 형성하여 많은 요청을 처리할 수 있다고 한다. 
또한 부분적으로 멀티스레드로 데이터 접근부분만 싱글 스레드고 옵션을 통해 I/O 부분은 멀티 스레드 처리를 활성화 할 수 있다고 한다. 기본적으로 싱글 스레드이고 필요시 멀티스레드 기능을 사용하는 것 같다. </p>
<h3 id="자료형이-다양하다">자료형이 다양하다</h3>
<p>그동안 난 레디스를 캐시로만 사용해왔기 때문에 String만 알고 있었는데 이밖에 정말 다양한 자료형이 있음을 확인했다. 각 자료형마다의 명령어도 다르다고 한다. String, list, hash, set, sored set이 있다고 하는데 다음 장에서 알 수 있을 것 같다. </p>
<h3 id="캐시용-말고도-용도가-많아">캐시용 말고도 용도가 많아!</h3>
<p>배치 처리 메시지 큐, Pub/Sub 시스템 구축(클라이언트/서버 모델 기반의 요청/응답 통신)에도 쓰인다고 한다.  </p>
<h3 id="트랜잭션-처리">트랜잭션 처리</h3>
<p>레디스는 RDBMS에 있는 트랜잭션 실패 시 롤백 기능은 지원하지 않는다. 명령어를 실패하더라도 그대로 나머지 처리를 계속해서 수행하는 패턴이다. </p>
<h3 id="데이터의-영속성을-갖추고-있어">데이터의 영속성을 갖추고 있어?</h3>
<p>나는 레디스의 데이터 영속성에 대해 전혀 알고 있지 않았다. 책에서는 레디스가 두가지 종류의 영속성을 제공한다고 한다. </p>
<ul>
<li>RDB(Redis Database)
특정 시점의 레디스 데이터(스냅샷)를 나타내는 여러개의 데이터 파일을 생성하는 방식(백업)</li>
<li>AOF(Append Only File)
AOF로 appendFsync의 값을 always로 설정함으로써 영속성을 보장할 수 있다. 쓰기 작업마다 디스크에 플러시 한다.</li>
</ul>
<p>AOF 내용에 대한 관련 논문이 있다. <a href="http://delab.yonsei.ac.kr/assets/files/publication/legacy/Redis%EC%97%90%EC%84%9C%20AOF%20Rewrite%20%EA%B8%B0%EB%B2%95%EC%9D%98%20%EC%98%A4%EB%B2%84%ED%97%A4%EB%93%9C%20%EB%B6%84%EC%84%9D_%EC%B5%9C%EC%A2%85%EB%B3%B8.pdf">Redis에서 AOF Rewrite 기법의 오버헤드 분석</a>
 해당 논문에 대한 내용은 추후 따로 작성해볼 예정이다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA['책에 대한 생각 모음집' 소개]]></title>
            <link>https://velog.io/@sweet_sumin/%EC%B1%85%EC%97%90-%EB%8C%80%ED%95%9C-%EC%83%9D%EA%B0%81-%EB%AA%A8%EC%9D%8C%EC%A7%91-%EC%86%8C%EA%B0%9C</link>
            <guid>https://velog.io/@sweet_sumin/%EC%B1%85%EC%97%90-%EB%8C%80%ED%95%9C-%EC%83%9D%EA%B0%81-%EB%AA%A8%EC%9D%8C%EC%A7%91-%EC%86%8C%EA%B0%9C</guid>
            <pubDate>Sun, 29 Sep 2024 04:51:00 GMT</pubDate>
            <description><![CDATA[<p>그 전부터 취준을 위해서든 나의 개발생활을 위해서든 책을 읽긴 했지만 본격적으로 책 스터디를 하게 된 것은 올해 1월부터로, 3개월 단위로 여러 책들을 읽어왔다. 대부분 백엔드 엔지니어들로 구성된 스터디내에서는 다들 나보다 연차가 많고 모든 사람들이 다 다른 회사이기도 해서 다른 회사에서는 어떻게 적용하고 있는지 인사이트를 많이 얻어가면서 한번에 두 권의 책을 하기도 한 권을 책을 하기도 했다.
<a href="https://www.yes24.com/Product/Goods/111408749">도커교과서</a>, <a href="https://www.yes24.com/Product/Goods/104491433">소프트웨어 아키텍처 101</a>, <a href="https://www.yes24.com/Product/Goods/124138645">가상 면접 사례로 배우는 대규모 시스템 설계 기초 2</a>, <a href="https://www.yes24.com/Product/Goods/97015247">데이터베이스 인터널스</a> 등등..
사실 이 모든 책을 읽었다고 해도 모르는게 더 많기 때문에 한번은 이런 생각이 들었다. </p>
<blockquote>
<p>읽어도 읽어도 모르는 개념이 너무 많고 이해가 않가는게 너무 많아서 내 부족함에 대한 자괴감이 드는데 나 이대로 괜찮을까? 이런 책을 읽을 만한 위치가 되는가?</p>
</blockquote>
<p>이런 생각 속에서도 그 날도 습관적으로 그냥 책을 읽었고, 모르면 모른대로 넘겼고, 스터디에 참가하였다. 책걸이를 하던 날이던가..? 거의 책을 다 읽어나가고 다음 어떤 스터디를 할 것인가에 대한 이야기를 나누던 중 다음 것도 어려운 것을 하게 되면서 다른 스터디원이 하던 말이 내 부족함에 자괴감에 대한 해답을 주었다.</p>
<blockquote>
<p>어려울 걸 알고, 나도 여기서 나온 개념들이 현재 실무에 쓰이는가?에 대한 생각을 많이 한다. 실제로 지금 전혀 쓰지 않아 잘 와닿지 않을 때가 많다. 하지만 나는 이것들이 배경 지식이라고 생각한다. 지금은 익숙하지 않아서 그냥 넘어가지만, 다음에 언젠가 문제 해결을 위한 방법을 찾아갈 때 &#39;어? 이런거 있었지 않았나?&#39; 하는 그 순간을 위해 어렵지만 읽어나가야한다고 생각한다.</p>
</blockquote>
<p>맞아. 그 순간을 위해 내가 이렇게 해왔지?</p>
<p>덕분에 이로써 부족함에 대한 자괴감에 대한 해결을 되었다. 그리고 다시 일어설 수 있었다. 그리고 그동안 내가 읽어왔던 책들을 정리해보았다. 정리하다보니 나는 어떤 책을 읽어왔는지 증명할 수 있는 기록이 없고 정말 단지 &#39;글자를 읽었다&#39; 라는 개념밖에 지금은 남지 않고 있다는 것을 깨달았다. 
이 시리즈는 내가 읽은 책들에 대한 기록이 될것이다. 사실 지식 전달을 하기보다는 해당 개념에 대한 내 생각을 정리하는 것에 대한 기록을 할 것이다. 해당 지식을 알고 싶으면 직접 책을 다시 읽어라 미래의 내 자신아! 그리고 기록해줘. 다시 읽어보니 또 어떤 생각의 확장이 발생되었는지!</p>
<p>나를 위한 책 시리즈가 될 것이다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kotiln Collection ]]></title>
            <link>https://velog.io/@sweet_sumin/Kotiln-Collection</link>
            <guid>https://velog.io/@sweet_sumin/Kotiln-Collection</guid>
            <pubDate>Fri, 27 Sep 2024 00:53:35 GMT</pubDate>
            <description><![CDATA[<p>자바에서 코틀린으로 전환하면서 다양한 함수들이 있지만 제대로 활용하고 있지 않은것 같아서 어떤 함수들이 있는지 정리해보고자 한다. 존재라도 알면 쓰임을 생각해내서 활용하지 않을까? ⭐️ 표시를 통해 자주 쓰이거나, 이건 기억해두고 싶다라는 것들을 표시해두었다. </p>
<h3 id="component1">component1()</h3>
<p><img src="https://velog.velcdn.com/images/sweet_sumin/post/3b32bb2a-113d-473a-bf6b-ce7926ba3191/image.png" alt="">
리스트의 첫번째 요소를 반환한다. 만약 리스트의 요소를 첫번째 이외에도 가져오고 싶다면 1이 아닌 다른 숫자를 적용하면 된다. 5번째까지 가능한 것 같다.
<img src="https://velog.velcdn.com/images/sweet_sumin/post/28fbc5d6-a943-4c4d-b4df-62a0c1f8e5f1/image.png" alt=""></p>
<h3 id="contains-elementat-elementatorelse-elementatornull">contains(), elementAt(), elementAtOrElse(), elementAtOrNull()</h3>
<p><img src="https://velog.velcdn.com/images/sweet_sumin/post/f6a617ee-40e3-4a1a-b789-4d5a2e488c69/image.png" alt="">
해당 요소가 포함되어 있는지 확인하는 용도로 쓰인다. 
그렇다면 특정 위치의 element를 알고 싶다면? elementAt을 쓰면 된다. 
<img src="https://velog.velcdn.com/images/sweet_sumin/post/5f6df8ce-a94d-4365-bb27-fb18631d8a4d/image.png" alt="">
코드를 보니 elementAtOrElse 를 사용하는 것을 볼 수 있다. 특정 위치에 element가 없다면 대신 다른 것을 내보내는 역할에 쓰인다. 
<img src="https://velog.velcdn.com/images/sweet_sumin/post/670625df-7f21-4e50-8715-ff1ce9b3450f/image.png" alt="">
아 물론 default가 아닌 null을 내보내고 싶을때는 elementAtOrNull()를 쓰면 된다. 
<img src="https://velog.velcdn.com/images/sweet_sumin/post/0414599b-874b-442e-b176-55b34d92235c/image.png" alt=""></p>
<h3 id="find-⭐️">find ⭐️</h3>
<ul>
<li>find : 앞에서부터 탐색하여 만족하는 요소 존재한다면 만족하는 요소의 가장 첫번째를 반환한다. 없다면 null을 반환한다. </li>
<li>findLast: 뒤에서부터 탐색하여 만족하는 요소가 존재한다면 만족하는 요소 있다면 반환하고 없다면 null을 반환한다. </li>
</ul>
<h3 id="get">get</h3>
<ul>
<li>getOrElse : 인덱스 위치에 요소가 있으면 해당 요소를 반환하고 아닌 경우, 지정한 기본값을 반환한다.  </li>
<li>getOrNull : 주어진 인덱스에 대해 element가 없으면 반환해주고, 없으면 null 반환한다. </li>
</ul>
<h3 id="index">index</h3>
<ul>
<li>indexOf : 주어진 요소에 일치하는 첫 인덱스를 반환한다. </li>
<li>indexOfFirst : 람다식에 일치하는 첫 요소의 인덱스를 반환한다. </li>
<li>indexOfLast : 람다식에 일치하는 마지막 요소의 인덱스를 반환한다. </li>
</ul>
<h3 id="last-first-⭐️">last, first ⭐️</h3>
<ul>
<li><p>last: 마지막 요소를 반환한다. 람다식에 일치하는 마지막 요소 반환한다. 
<img src="https://velog.velcdn.com/images/sweet_sumin/post/b17b1e13-9918-4fc2-912e-809db0a0380e/image.png" alt="">
그외 : 
lastIndexOf() - 마지막 인덱스 반환
lastOrNull() - 없으면 null 반환</p>
</li>
<li><p>first: 첫번째 요소를 반환한다. 람다식에 일치하는 첫번째 요소를 반환한다. 
그외
firstNotNullOf - 주어진 람다를 적용한 결과 중 첫 번째로 null이 아닌 값을 반환
( first는 요소 자체를 반환하지만 firstNotNullOf는 요소에 변환을 적용한 후 null이 아닌 결과를 찾는다.)
firstNotNullOfOrNull - NoSuchElementException이 발생하는 상황에서 null을 반환
firstOrNull() - 없으면 null 반환</p>
</li>
</ul>
<h3 id="random">random</h3>
<ul>
<li>random() : collection의 요소 중 하나를 랜덤으로 가져온다</li>
<li>randomOrNull() : collection의 요소 중 하나를 랜덤으로 가져오거나 collection이 비어있으면 null을 반환한다. </li>
</ul>
<h3 id="single">single</h3>
<ul>
<li>single() : 조건을 만족하는 원소가 딱 하나일 때 그 값을 반환한다. 없으면 에러낸다.</li>
<li>singleOrNull() : 조건을 만족하는 원소가 딱 하나일 때 그 값을 반환한다. 없으면 null을 반환한다.</li>
</ul>
<h3 id="drop-⭐️">drop ⭐️</h3>
<ul>
<li>drop(n: Int) : 리스트의 앞부분부터 지정한 개수(n)만큼의 요소를 뺀 새로운 리스트를 생성하는 메서드</li>
<li>dropLast(n: Int) : 리스트의 뒤에서부터 지정한 개수(n)만큼의 요소를 뺀 새로운 리스트를 생성하는 메서드</li>
<li>dropWhile(predicate: (T) -&gt; Boolean) : 조건이 만족할 때까지 앞에서부터 버리고 나머지를 리턴한다</li>
<li>dropLastWhile(predicate: (T) -&gt; Boolean) : 조건이 만족할 때까지 뒤에서부터 버리고 나머지를 리턴한다.</li>
</ul>
<h2 id="filter-⭐️">filter ⭐️</h2>
<ul>
<li><p>filter : 컬렉션에서 주어진 조건을 만족하는 요소들만을 필터링하여 새로운 리스트를 반환
<img src="https://velog.velcdn.com/images/sweet_sumin/post/61c37b2b-05c1-47f2-b836-871622938be7/image.png" alt="">
<img src="https://velog.velcdn.com/images/sweet_sumin/post/67c5401b-3782-4679-a845-77d49ab5eff3/image.png" alt=""></p>
</li>
<li><p>filterTo : 컬렉션에서 주어진 조건을 만족하는 요소들만 필터링하여, 그 결과를 주어진 컬렉션에 추가
<img src="https://velog.velcdn.com/images/sweet_sumin/post/217b493b-c94a-42ae-9286-2c3784ed7fd5/image.png" alt=""></p>
</li>
</ul>
<p>📌 인덱스 활용</p>
<ul>
<li><p>filterIndexed : 리스트의 각 요소와 해당 요소의 인덱스를 기반으로 조건에 맞는 요소들만을 필터링하여 새로운 리스트를 반환.  새로운 리스트가 필요할 때 사용
<img src="https://velog.velcdn.com/images/sweet_sumin/post/ff950cf9-0f81-45b6-b3a6-f5ac74f476e5/image.png" alt="">-&gt; 각 요소의 인덱스를 고려하여 조건을 만족하는 요소만 필터링하고 있음. 여기서 짝수 인덱스이면서 값이 2보다 큰 요소들만 선택됨</p>
</li>
<li><p>filterIndexedTo : 주어진 리스트나 컬렉션에 인덱스와 값을 기준으로 필터링된 요소를 추가. 결과를 다른 컬렉션에 담아야 할 때 유용
<img src="https://velog.velcdn.com/images/sweet_sumin/post/a4e5aab1-12e6-413a-b025-b205bc4c6a13/image.png" alt=""> -&gt; 필터링된 결과를 새로운 리스트에 반환하는 대신, 이미 존재하는 targetList에 추가</p>
</li>
</ul>
<p>📌 특정 타입의 요소들만 필터링</p>
<ul>
<li><p>filterIsInstance : 컬렉션에서 특정 타입의 요소들만 필터링하여 새로운 리스트로 반환. 새로운 리스트가 필요할 때 사용
<img src="https://velog.velcdn.com/images/sweet_sumin/post/acb3859e-b451-44c3-91c2-0e95fdeccc7f/image.png" alt=""> -&gt; String 타입의 요소들만 필터링하여 새로운 리스트를 반환</p>
</li>
<li><p>filterIsInstanceTo : 컬렉션에서 특정 타입의 요소들을 주어진 컬렉션에 필터링하여 추가. 기존 컬렉션을 이용해 결과를 추가할 때 유용
<img src="https://velog.velcdn.com/images/sweet_sumin/post/58decac1-e133-4038-bb01-f991a0a18817/image.png" alt="">-&gt; 필터링된 결과를 새로운 리스트가 아닌, targetList라는 기존의 리스트에 추가</p>
</li>
</ul>
<p>📌 특정 조건에 맞지 않는 요소 또는 null이 아닌 요소를 필터링</p>
<ul>
<li><p>filterNot : 주어진 조건을 만족하지 않는 요소들만 필터링하여 새로운 리스트를 반환
<img src="https://velog.velcdn.com/images/sweet_sumin/post/5336fc98-ff58-434d-b8c3-6429b20bcc65/image.png" alt=""></p>
</li>
<li><p>filterNotTo : 주어진 조건을 만족하지 않는 요소들만 필터링하여, 그 결과를 주어진 컬렉션에 추가
<img src="https://velog.velcdn.com/images/sweet_sumin/post/9f4fcb1f-e6db-4d72-8d83-8acd21a0e9dc/image.png" alt=""></p>
</li>
<li><p>filterNotNull : 컬렉션에서 null이 아닌 요소들만 필터링
<img src="https://velog.velcdn.com/images/sweet_sumin/post/ae90fb7d-6af4-44e2-a749-dc8244269582/image.png" alt=""></p>
</li>
<li><p>filterNotNullTo : 주어진 조건을 만족하지 않는 요소들만 필터링하여, 그 결과를 주어진 컬렉션에 추가
<img src="https://velog.velcdn.com/images/sweet_sumin/post/ac40e234-1bd2-43c3-8547-d6c86c192ee1/image.png" alt=""></p>
</li>
</ul>
<h2 id="기타-⭐️">기타 ⭐️</h2>
<ul>
<li><p>slice : 컬렉션에서 주어진 인덱스 또는 범위에 해당하는 요소를 선택하여 새로운 컬렉션을 반환. 인덱스 범위를 지정하여 그에 해당하는 요소만 선택
<img src="https://velog.velcdn.com/images/sweet_sumin/post/9dcfeaf8-4d2f-44fa-875e-56f096932aea/image.png" alt=""></p>
</li>
<li><p>partition : 컬렉션의 요소를 두 개의 리스트로 나누는 함수
<img src="https://velog.velcdn.com/images/sweet_sumin/post/99732ad1-5527-45e8-897f-755b06cb754c/image.png" alt=""></p>
</li>
<li><p>take : 컬렉션의 처음부터 주어진 개수만큼의 요소를 선택하여 새로운 리스트를 반환
<img src="https://velog.velcdn.com/images/sweet_sumin/post/895600f9-db6d-4804-ae33-7aaf9aa24105/image.png" alt=""></p>
</li>
<li><p>takeLast : 컬렉션의 끝에서부터 주어진 개수만큼의 요소를 선택하여 새로운 리스트를 반환</p>
</li>
<li><p>takeLastWhile : 주어진 조건을 만족하는 마지막 요소들만 선택하여 새로운 리스트를 반환합니다. 조건을 만족하지 않는 첫 번째 요소가 발견될 때까지 선택. 조건에 맞는 요소를 끝에서부터 선택</p>
</li>
<li><p>takeWhile : 주어진 조건을 만족하는 처음부터 요소들을 선택하여 새로운 리스트를 반환합니다. 조건을 만족하지 않는 첫 번째 요소가 발견될 때까지 선택
<img src="https://velog.velcdn.com/images/sweet_sumin/post/f7f9cf9e-cd63-455b-8441-39bb87dcb4fd/image.png" alt=""></p>
</li>
<li><p>reversed : 컬렉션의 요소 순서를 반전시켜 새로운 리스트를 반환 ⭐️
<img src="https://velog.velcdn.com/images/sweet_sumin/post/88315f91-ed4b-440f-a30e-2032a9e5110f/image.png" alt=""></p>
</li>
<li><p>shuffle : 컬렉션의 요소를 무작위로 섞어 새로운 리스트를 반환
<img src="https://velog.velcdn.com/images/sweet_sumin/post/a8ef19a2-294c-416a-b3ad-f0fdd8166060/image.png" alt=""></p>
</li>
</ul>
<h2 id="sort-⭐️">sort ⭐️</h2>
<ul>
<li><p>sortBy : 오름차순으로 정렬</p>
</li>
<li><p>sortByDescending : 내림차순으로 정렬
<img src="https://velog.velcdn.com/images/sweet_sumin/post/2797c0cf-cd55-4c97-a706-48cadcfa8190/image.png" alt=""></p>
</li>
<li><p>sorted : 리스트를 오름차순으로 정렬한 새로운 리스트를 반환</p>
</li>
<li><p>sortedDescending : 리스트를 내림차순으로 정렬한 새로운 리스트를 반환
<img src="https://velog.velcdn.com/images/sweet_sumin/post/b131bf24-9671-4dc5-8c81-0cd01a094dd9/image.png" alt=""></p>
</li>
<li><p>sortedBy : 주어진 <strong>기준</strong>에 따라 오름차순으로 정렬한 새로운 리스트를 반환</p>
</li>
<li><p>sortedByDescending : 주어진 기준에 따라 내림차순으로 정렬한 새로운 리스트를 반환
<img src="https://velog.velcdn.com/images/sweet_sumin/post/5755deb2-9427-4f0a-ba88-0aeb8284bf49/image.png" alt=""></p>
</li>
<li><p>sortedWith : 커스텀 Comparator를 사용하여 정렬한 새로운 리스트를 반환
<img src="https://velog.velcdn.com/images/sweet_sumin/post/babf6100-1647-4c4a-b5f6-86da4fd86c38/image.png" alt=""></p>
</li>
</ul>
<p>sortBy, sortByDescending: 원본 리스트를 기준에 따라 정렬하며, 원본이 변경됨 (MutableList 전용).
sorted, sortedBy, sortedByDescending, sortedDescending, sortedWith: 새로운 리스트를 반환하며, 원본은 변경되지 않음.</p>
<h2 id="변환">변환</h2>
<p>컬렉션을 배열로 변환</p>
<ul>
<li><p>toBooleanArray</p>
</li>
<li><p>toByteArray</p>
</li>
<li><p>toCharArray</p>
</li>
<li><p>toDoubleArray</p>
</li>
<li><p>toFloatArray</p>
</li>
<li><p>toIntArray</p>
</li>
<li><p>toLongArray</p>
</li>
<li><p>toShortArray</p>
</li>
<li><p>toMutableSet</p>
</li>
<li><p>toCollection : 컬렉션을 다른 컬렉션(타입 지정 가능)으로 변환. 주로 새 컬렉션에 기존 데이터를 넣고자 할 때 사용
<img src="https://velog.velcdn.com/images/sweet_sumin/post/ef0281d1-ea00-43ee-b0f1-1f9bd28d9e78/image.png" alt=""></p>
</li>
<li><p>toHashSet : 컬렉션을 HashSet으로 변환. 중복을 제거하고 순서는 보장</p>
</li>
<li><p>toList</p>
</li>
<li><p>toMutableList</p>
</li>
<li><p>toSet</p>
</li>
</ul>
<h2 id="associate-⭐️">associate ⭐️</h2>
<ul>
<li><p>associate : 컬렉션의 각 요소에 대해 Pair를 생성하여, 이를 키-값 쌍으로 Map
<img src="https://velog.velcdn.com/images/sweet_sumin/post/de4f4c87-3870-4f7b-b688-bc2264b5e9b9/image.png" alt=""></p>
</li>
<li><p>associateBy : 각 요소를 키로 변환하는 함수를 제공하여, 이를 기준으로 Map을 만듦
<img src="https://velog.velcdn.com/images/sweet_sumin/post/04aaaef9-7d58-416a-8b28-d99e789077a0/image.png" alt=""></p>
</li>
<li><p>associateByTo : 기존 MutableMap에 각 요소를 키로 변환한 값을 추가하여 MutableMap을 만듦. associateBy와 기능은 동일하지만, 결과를 지정한 MutableMap에 추가
<img src="https://velog.velcdn.com/images/sweet_sumin/post/b02db43f-9397-454d-bfd8-18b3ad64db18/image.png" alt=""></p>
</li>
<li><p>associateTo : 각 요소에 대해 Pair를 생성하고, 이를 주어진 MutableMap에 추가. associate와 기능은 동일하지만, 결과를 지정한 MutableMap에 추가
<img src="https://velog.velcdn.com/images/sweet_sumin/post/45a5d3b1-b8f5-424c-a188-f53f2dbfd5a1/image.png" alt=""></p>
</li>
<li><p>associateWith : 각 요소를 키로 사용하고, 이를 값으로 변환하는 함수를 제공하여 Map을 만든다
<img src="https://velog.velcdn.com/images/sweet_sumin/post/34f2518c-be4e-4487-a573-6027881bed58/image.png" alt=""></p>
</li>
<li><p>associateWithTo : 각 요소를 키로 사용하고, 변환한 값을 제공된 MutableMap에 추가하여 MutableMap을 만듭니다. associateWith와 동일하지만, 결과를 지정한 MutableMap에 추가
<img src="https://velog.velcdn.com/images/sweet_sumin/post/fed2d8b6-bfc8-4049-a8a5-90cad798f3b9/image.png" alt="">
<img src="https://velog.velcdn.com/images/sweet_sumin/post/92637bd0-933a-4965-bb6e-6aab58999b6f/image.png" alt=""></p>
</li>
</ul>
<h2 id="group-⭐️">group ⭐️</h2>
<ul>
<li><p>groupBy : 컬렉션의 요소들을 지정된 키 기준으로 그룹화하여, Map으로 반환
<img src="https://velog.velcdn.com/images/sweet_sumin/post/ecb4e0d9-404d-4e02-9f5b-2c23e0cf571e/image.png" alt=""></p>
</li>
<li><p>groupByTo : groupBy와 동일하지만, 그룹화 결과를 새로운 Map 대신 지정한 MutableMap에 추가
<img src="https://velog.velcdn.com/images/sweet_sumin/post/73f44ddc-6e32-4515-ab5b-69bf1b346a06/image.png" alt=""></p>
</li>
<li><p>groupingBy : 컬렉션을 지연 그룹화하는데 사용됩니다. groupingBy는 즉시 결과를 반환하는 대신 그룹화 전략 객체를 생성. 그룹화된 데이터를 후처리하기 위한 여러 확장 함수와 함께 사용
<img src="https://velog.velcdn.com/images/sweet_sumin/post/cf82047c-31b8-47b0-a499-270e4508a33e/image.png" alt=""></p>
</li>
</ul>
<p>정리
groupBy: 즉시 그룹화하여 새로운 Map 반환.
groupByTo: 결과를 기존 MutableMap에 추가.
groupingBy: 지연 그룹화 후 추가 처리 가능.</p>
<h2 id="map-⭐️">map ⭐️</h2>
<ul>
<li>flatMap : 각 요소를 변환한 후 다중 요소로 확장하고, 이를 <strong>평탄화(flatten)</strong>하여 하나의 리스트로 만든다.
<img src="https://velog.velcdn.com/images/sweet_sumin/post/96891472-f276-44fe-9da0-3914eae78df5/image.png" alt=""></li>
</ul>
<ul>
<li>flatMapIndexed : 각 요소와 인덱스를 사용하여 다중 요소로 변환하고, 이를 <strong>평탄화(flatten)</strong>하여 하나의 리스트로 만든다.
<img src="https://velog.velcdn.com/images/sweet_sumin/post/8268d6a7-278f-48d7-8280-0cae87adf9b1/image.png" alt=""></li>
</ul>
<ul>
<li><p>flatMapIndexedTo : 각 요소와 인덱스를 사용하여 변환한 다중 요소를 지정된 MutableCollection에 추가하고, 이를 평탄화(flatten)
<img src="https://velog.velcdn.com/images/sweet_sumin/post/2260bfe6-a465-42b0-a2ae-fe73efa02071/image.png" alt=""></p>
</li>
<li><p>flatMapTo : 각 요소를 다중 요소로 변환하고, 이를 지정된 MutableCollection에 평탄화(flatten)하여 추가
<img src="https://velog.velcdn.com/images/sweet_sumin/post/10da3bc6-143c-422f-bd25-8937eae65e7f/image.png" alt=""></p>
</li>
</ul>
<hr>
<ul>
<li>map : 컬렉션의 각 요소를 변환하여 새로운 컬렉션으로 만든다.
<img src="https://velog.velcdn.com/images/sweet_sumin/post/3fe4dc05-50c0-414b-baa1-58d868c23b6b/image.png" alt=""></li>
</ul>
<ul>
<li><p>mapIndexed : 각 요소와 해당 요소의 인덱스를 함께 사용하여 변환된 새로운 컬렉션을 만든다.
<img src="https://velog.velcdn.com/images/sweet_sumin/post/7c1ac91e-1ae1-4208-9f49-3ec3cb471970/image.png" alt=""></p>
</li>
<li><p>mapIndexedNotNull : 각 요소와 인덱스를 함께 사용하여 변환하되, null을 제외한 요소들만을 포함하는 새로운 컬렉션을 만든다.
<img src="https://velog.velcdn.com/images/sweet_sumin/post/1ecacf8c-0880-41e9-bf7c-bf256136a63f/image.png" alt=""></p>
</li>
</ul>
<ul>
<li>mapIndexedNotNullTo : 각 요소와 인덱스를 사용하여 변환한 결과에서 null을 제외하고, 지정된 MutableCollection에 추가
<img src="https://velog.velcdn.com/images/sweet_sumin/post/b8e46bab-f39f-4b11-9447-9eabe2e84ed0/image.png" alt=""></li>
</ul>
<ul>
<li>mapIndexedTo : 각 요소와 인덱스를 함께 사용하여 변환된 결과를 지정된 MutableCollection에 추가
<img src="https://velog.velcdn.com/images/sweet_sumin/post/2f597818-f90d-446a-9368-37c4123ac5d3/image.png" alt=""></li>
</ul>
<ul>
<li>mapNotNull : 각 요소를 변환하되, null이 아닌 요소들만 포함하는 새로운 컬렉션을 만든다
<img src="https://velog.velcdn.com/images/sweet_sumin/post/318e31fb-ce35-4aac-a4be-246161e2b7a9/image.png" alt=""></li>
</ul>
<ul>
<li>mapNotNullTo : 각 요소를 변환하여 null을 제외한 결과를 지정된 MutableCollection에 추가한다.
<img src="https://velog.velcdn.com/images/sweet_sumin/post/697f2ef0-5378-4d7c-8e57-3e51984a92ac/image.png" alt=""></li>
</ul>
<ul>
<li>mapTo : 각 요소를 변환한 결과를 지정된 MutableCollection에 추가한다.
<img src="https://velog.velcdn.com/images/sweet_sumin/post/fe1e96c8-afcc-4e03-bf7a-af2d2c664e1f/image.png" alt=""></li>
</ul>
<h2 id="기타">기타</h2>
<ul>
<li><p>withIndex : 컬렉션의 각 요소에 대한 인덱스와 값을 포함하는 시퀀스를 반환
<img src="https://velog.velcdn.com/images/sweet_sumin/post/734a4fc9-aff5-4efc-ac5b-b273a673d0ef/image.png" alt=""></p>
</li>
<li><p>distinct : 컬렉션의 중복 요소를 제거하여 새로운 컬렉션을 반환 ⭐️</p>
</li>
<li><p>distinctBy : 주어진 조건에 따라 중복 요소를 제거
<img src="https://velog.velcdn.com/images/sweet_sumin/post/491795f0-32ba-45a9-bd13-ca37d29b1820/image.png" alt=""></p>
</li>
<li><p>intersect : 두 컬렉션의 교집합을 반환
<img src="https://velog.velcdn.com/images/sweet_sumin/post/5bb9c6bd-785d-44e4-a329-1feeab93d9f3/image.png" alt=""></p>
</li>
<li><p>union : 두 컬렉션의 합집합을 반환 ⭐️</p>
</li>
<li><p>subtract : 컬렉션에서 다른 컬렉션의 요소를 제거하여 새로운 컬렉션을 반환
<img src="https://velog.velcdn.com/images/sweet_sumin/post/fe2e6d70-1e52-4f7e-86ba-50601194cf08/image.png" alt=""></p>
</li>
<li><p>all : 컬렉션의 모든 요소가 주어진 조건을 만족하는지 확인 ⭐️
<img src="https://velog.velcdn.com/images/sweet_sumin/post/a477c04d-b1b5-4699-aecb-710d36f33a6f/image.png" alt=""></p>
</li>
<li><p>any : 컬렉션의 어떤 요소라도 주어진 조건을 만족하는지 확인 ⭐️
<img src="https://velog.velcdn.com/images/sweet_sumin/post/176d427f-43c4-4337-be33-8aea4fc90c68/image.png" alt=""></p>
</li>
<li><p>count : 주어진 조건을 만족하는 요소의 개수를 반환 ⭐️</p>
</li>
<li><p>none : 컬렉션의 모든 요소가 주어진 조건을 만족하지 않는지 확인 ⭐️</p>
</li>
<li><p>windowed : 컬렉션을 지정한 크기의 슬라이딩 윈도우로 나누어 리스트의 리스트를 반환 ⭐️
<img src="https://velog.velcdn.com/images/sweet_sumin/post/a8e7f63e-562b-458e-9834-6874f7fd84f4/image.png" alt=""></p>
</li>
<li><p>zip : 두 컬렉션의 요소를 쌍으로 묶어 새로운 리스트를 생성 ⭐️
<img src="https://velog.velcdn.com/images/sweet_sumin/post/3ffa18e1-a4ae-49c3-ac6a-19e520df9249/image.png" alt=""></p>
</li>
<li><p>zipWithNext : 컬렉션의 요소를 인접한 요소와 쌍으로 묶어 새로운 리스트를 생성
<img src="https://velog.velcdn.com/images/sweet_sumin/post/ecb71440-ed4c-4ab9-a190-bee6fca7559c/image.png" alt=""></p>
</li>
<li><p>joinTo : 컬렉션의 모든 요소를 지정한 대상에 문자열로 추가 
<img src="https://velog.velcdn.com/images/sweet_sumin/post/92e974bd-1ac7-4fba-b208-d184c4ebbcc3/image.png" alt=""></p>
</li>
<li><p>joinToString : 컬렉션의 모든 요소를 문자열로 결합하여 반환. 각 요소 사이에 지정된 구분자를 사용하고, 필요에 따라 접두사와 접미사를 추가할 수 있다 ⭐️</p>
</li>
</ul>
<pre><code>형식)
joinToString(
    separator: String = &quot;, &quot;,
    prefix: String = &quot;&quot;,
    postfix: String = &quot;&quot;,
    limit: Int = -1,
    truncated: String = &quot;...&quot;,
    transform: ((T) -&gt; CharSequence)? = null
): String</code></pre><p><img src="https://velog.velcdn.com/images/sweet_sumin/post/afe066da-ca2c-4329-a057-bff430be5479/image.png" alt=""></p>
<ul>
<li><p>asSequence : 컬렉션을 시퀀스로 변환하여, 지연 평가를 사용할 수 있게 합니다. 이는 큰 컬렉션을 처리할 때 성능을 개선할 수 있습니다. 특징: 시퀀스는 중간 연산을 지연 실행하므로, 효율적으로 요소를 처리할 수 있다. ⭐️
<img src="https://velog.velcdn.com/images/sweet_sumin/post/f35a16db-f57e-4ad2-ac57-86badd9dc2a5/image.png" alt=""></p>
</li>
<li><p>average : 숫자형 컬렉션의 평균 값을 계산하여 반환</p>
</li>
</ul>
<h2 id="fold-⭐️">fold ⭐️</h2>
<ul>
<li><p>fold :  컬렉션의 왼쪽부터(앞에서부터) 각 요소를 누적하여 하나의 값으로 축약합니다. 초기값을 설정한다.
  -- 형식: fold(initial: R, operation: (acc: R, T) -&gt; R)
  <img src="https://velog.velcdn.com/images/sweet_sumin/post/b8c4af02-71a5-4511-942a-5c77d1791f67/image.png" alt=""></p>
</li>
<li><p>foldIndexed : fold와 동일하게 왼쪽부터 처리하되, 인덱스를 포함하여 요소를 처리한다.
  -- 형식: foldIndexed(initial: R, operation: (index: Int, acc: R, T) -&gt; R)
  <img src="https://velog.velcdn.com/images/sweet_sumin/post/a9146041-bc5e-482c-8c51-9f750735fc58/image.png" alt=""></p>
</li>
<li><p>foldRight : 컬렉션의 오른쪽부터(뒤에서부터) 각 요소를 누적하여 하나의 값으로 축약합니다. 초기값을 설정할 수 있다.
  -- 형식: foldRight(initial: R, operation: (T, acc: R) -&gt; R)
  <img src="https://velog.velcdn.com/images/sweet_sumin/post/4b5b31f0-db13-49a6-a357-135083ac3ca5/image.png" alt=""></p>
</li>
<li><p>foldRightIndexed
  -- 형식: foldRightIndexed(initial: R, operation: (index: Int, T, acc: R) -&gt; R)
  <img src="https://velog.velcdn.com/images/sweet_sumin/post/2e94944a-9496-4784-b612-901e25da1e71/image.png" alt=""></p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sweet_sumin/post/a596bbf9-6d59-4d1e-910f-76aa159fd756/image.png" alt=""></p>
<h2 id="each-⭐️">Each ⭐️</h2>
<ul>
<li>forEach : 컬렉션의 각 요소에 대해 주어진 작업을 수행함.</li>
<li>forEachIndexed : forEach와 동일하나, 각 요소와 함께 인덱스 정보도 제공</li>
<li>onEach : 컬렉션의 각 요소에 대해 주어진 작업을 수행한 후, 원래 컬렉션을 그대로 반환합니다. 보통 중간 연산으로 사용되며, 작업을 수행한 후 결과를 다시 사용</li>
<li>onEachIndexed : onEach와 동일하나, 각 요소와 함께 인덱스 정보도 제공합니다. 작업 후 원래 컬렉션을 반환</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sweet_sumin/post/bf453da9-8239-4245-90ed-e25c438df23d/image.png" alt=""></p>
<ul>
<li>forEach 계열은 주로 부작용을 발생시키며 결과를 반환하지 않는다.</li>
<li>onEach 계열은 부작용을 발생시키면서도 원래 컬렉션을 반환하여 체이닝에 유용</li>
</ul>
<h2 id="max-⭐️">max ⭐️</h2>
<ul>
<li><p>max : 컬렉션에서 최댓값을 찾기</p>
</li>
<li><p>maxOrNull : 컬렉션에서 최댓값을 찾으며, 비어 있는 경우 null을 반환</p>
</li>
<li><p>maxByOrNull : 주어진 람다식에 따라 특정 기준을 기반으로 최댓값을 찾으며, 비어 있는 경우 null을 반환</p>
</li>
<li><p>maxBy : 주어진 람다식에 따라 특정 기준을 기반으로 최댓값을 찾는다. 비어 있는 컬렉션이면 예외가 발생
<img src="https://velog.velcdn.com/images/sweet_sumin/post/4b161718-1634-4424-9869-803dd893a5c0/image.png" alt=""></p>
</li>
<li><p>maxOf : 주어진 람다식을 적용하여 최댓값을 반환합니다. 비어 있는 컬렉션이면 예외가 발생
<img src="https://velog.velcdn.com/images/sweet_sumin/post/cc7ffc14-5a87-49e8-b08f-49ccc9a01716/image.png" alt=""></p>
</li>
<li><p>maxOfOrNull : 주어진 람다식을 적용하여 최댓값을 반환하며, 비어 있는 경우 null을 반환</p>
</li>
<li><p>maxOfWith : 주어진 <strong>비교 기준(Comparator)</strong>을 사용하여 특정 람다식을 적용한 후 최댓값을 반환합니다. 비어 있는 컬렉션이면 예외가 발생
<img src="https://velog.velcdn.com/images/sweet_sumin/post/c8e9b4b4-533c-4545-b3ed-6469ee644ff7/image.png" alt=""></p>
</li>
<li><p>maxOfWithOrNull : 주어진 비교 기준과 람다식을 사용하여 최댓값을 찾으며, 비어 있는 경우 null을 반환</p>
</li>
<li><p>maxWith : 주어진 <strong>비교 기준(Comparator)</strong>을 사용하여 컬렉션에서 최댓값을 반환합니다. 비어 있는 컬렉션이면 예외
<img src="https://velog.velcdn.com/images/sweet_sumin/post/791bc39c-3c8a-43ce-abbe-31a98699ee8d/image.png" alt=""></p>
</li>
<li><p>maxWithOrNull : 주어진 <strong>비교 기준(Comparator)</strong>을 사용하여 최댓값을 찾으며, 비어 있는 경우 null을 반환</p>
</li>
</ul>
<h2 id="min-⭐️">min ⭐️</h2>
<ul>
<li>min</li>
<li>minBy</li>
<li>minByOrNull</li>
<li>minOf</li>
<li>minOfOrNull</li>
<li>minOfWith</li>
<li>minOfWithOrNull</li>
<li>minOrNull</li>
<li>minWith</li>
<li>minWithOrNull</li>
</ul>
<h2 id="reduce-⭐️">reduce ⭐️</h2>
<p>reduce 함수들은 컬렉션의 요소들을 <strong>누적(accumulate)</strong>하여 하나의 값으로 축약하는 연산을 수행합니다. <strong>fold</strong>와 비슷하지만, reduce는 초기값을 사용하지 않고 컬렉션의 첫 번째 요소를 누적값으로 시작합니다.</p>
<ul>
<li><p>reduce: 컬렉션의 왼쪽부터 차례대로 요소를 누적하며 하나의 값으로 축약합니다. 첫 번째 요소를 시작점으로 사용합니다. 비어 있는 컬렉션이면 예외가 발생
<img src="https://velog.velcdn.com/images/sweet_sumin/post/6bd89fd6-b239-4f1a-a0d9-9ba541f07842/image.png" alt="">
초기값 없이 컬렉션의 첫 번째 요소를 누적값으로 사용</p>
</li>
<li><p>reduceOrNull : reduce와 동일하지만, 비어 있는 컬렉션일 경우 null을 반환</p>
</li>
<li><p>reduceIndexed : reduce와 동일하지만, 인덱스 정보를 함께 사용하여 요소를 누적합니다. 첫 번째 요소를 시작점으로 사용합니다. 비어 있는 컬렉션이면 예외가 발생
<img src="https://velog.velcdn.com/images/sweet_sumin/post/aa09fb0c-0833-4d26-979a-262834fc828b/image.png" alt=""></p>
</li>
<li><p>reduceIndexedOrNull : reduceIndexed와 동일하지만, 비어 있는 컬렉션일 경우 null을 반환</p>
</li>
</ul>
<h4 id="reduceright-계열은-오른쪽부터-처리한다">reduceRight 계열은 오른쪽부터 처리한다</h4>
<ul>
<li>reduceRight</li>
<li>reduceRightIndexed</li>
<li>reduceRightIndexedOrNull</li>
<li>reduceRightOrNull</li>
</ul>
<h2 id="running-⭐️">running ⭐️</h2>
<p><strong>runningFold</strong>와 <strong>runningReduce</strong> 계열 함수들은 중간 결과를 포함한 연산의 누적 결과 리스트를 반환하는 함수들입니다. 기존의 <strong>fold</strong>와 <strong>reduce</strong>처럼 연산을 누적하는 방식이지만, 매 단계마다의 중간 결과를 리스트에 저장하여 최종 결과뿐만 아니라 각 단계에서의 결과를 확인할 수 있다.</p>
<ul>
<li><p>runningFold : 주어진 <strong>초기값(initial value)</strong>에서 시작하여, 컬렉션의 각 요소마다 누적값을 적용한 중간 결과 리스트를 반환
<img src="https://velog.velcdn.com/images/sweet_sumin/post/d3a62be4-3aca-4048-9a5a-abccfe6d90b7/image.png" alt=""></p>
</li>
<li><p>runningFoldIndexed : runningFold와 동일하지만, 인덱스 정보를 함께 사용하여 각 요소마다 누적 연산을 수행하고 중간 결과 리스트를 반환</p>
</li>
<li><p>runningReduce : reduce와 유사하게, 초기값 없이 컬렉션의 첫 번째 요소를 누적값으로 시작하며, 각 요소마다 누적 결과 리스트를 반환
<img src="https://velog.velcdn.com/images/sweet_sumin/post/8a4dcc5a-40d3-4f2a-a1fb-3e2a6fd097d7/image.png" alt=""></p>
</li>
<li><p>runningReduceIndexed : runningReduce와 동일하지만, 인덱스 정보를 함께 사용하여 각 요소마다 누적 연산을 수행하고 중간 결과 리스트를 반환</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sweet_sumin/post/5161deb8-ff2c-4e7c-a9c9-09daaf7de3b7/image.png" alt="">
이 함수들은 각 단계별 중간 결과를 기록하는 점에서 단순한 fold 및 reduce와 차이가 있습니다.</p>
<h2 id="scan-⭐️">scan ⭐️</h2>
<ul>
<li><p>scan : 컬렉션의 각 요소를 누적하여 결과 리스트를 반환합니다. 첫 번째 요소를 시작점으로 사용하고, 각 요소마다 누적 결과를 포함. 첫 번째 요소를 누적값으로 사용하며, 각 단계의 누적 결과가 리스트로 저장
<img src="https://velog.velcdn.com/images/sweet_sumin/post/2a476157-078c-4e88-98e2-d87417362dfe/image.png" alt=""></p>
</li>
<li><p>scanIndexed : scan과 동일하지만, 인덱스 정보를 함께 사용하여 각 요소마다 누적 연산을 수행하고 결과 리스트를 반환합니다.
<img src="https://velog.velcdn.com/images/sweet_sumin/post/b92a9e61-30c6-40c0-925d-702f96c7305c/image.png" alt=""></p>
</li>
</ul>
<p>중간 결과를 기록하는 점에서 유용하며, 복잡한 누적 연산을 쉽게 구현할 수 있게 도와준다.</p>
<h2 id="sum">sum</h2>
<ul>
<li><p>sumBy : 컬렉션의 각 요소에 대해 주어진 변환 함수를 적용하여 그 결과의 합을 계산합니다. 주로 정수 값을 반환하는 함수와 함께 사용
<img src="https://velog.velcdn.com/images/sweet_sumin/post/1b9a8118-dbe9-4dbd-9dc7-f92bb5a4cddc/image.png" alt=""></p>
</li>
<li><p>sumByDouble : 컬렉션의 각 요소에 대해 주어진 변환 함수를 적용하여 그 결과의 합을 계산합니다. 주로 Double 값을 반환하는 함수와 함께 사용합니다. 현재는 deprecated된 함수입니다.
<img src="https://velog.velcdn.com/images/sweet_sumin/post/abf6e731-644b-410b-8741-d8023c51d04b/image.png" alt=""></p>
</li>
<li><p>sumOf : 컬렉션의 각 요소에 대해 주어진 변환 함수를 적용하여 그 결과의 합을 계산합니다. Int, Double, Long 등 다양한 타입의 합계를 지원 ⭐️
<img src="https://velog.velcdn.com/images/sweet_sumin/post/7fd0de79-3775-402a-aa2d-b6487d030362/image.png" alt=""></p>
</li>
</ul>
<h2 id="require-chunk-⭐️">require, chunk ⭐️</h2>
<ul>
<li><p>requireNoNulls : 컬렉션에 널(null) 값이 포함되어 있지 않음을 보장하는 함수입니다. 만약 컬렉션에 널 값이 포함되어 있다면, IllegalArgumentException을 발생시킵니다.
주로 널 값을 허용하지 않는 컬렉션을 필요로 하는 경우 사용합니다
<img src="https://velog.velcdn.com/images/sweet_sumin/post/ab41fc9a-0038-409e-ad24-0f62eaeaa63f/image.png" alt=""></p>
</li>
<li><p>chunked : 컬렉션을 주어진 <strong>크기(chunk size)</strong>로 나누어 작은 리스트의 리스트로 변환하는 함수. 남은 요소가 있는 경우, 마지막 청크는 주어진 크기보다 작을 수 있습니다. 이를 통해 대규모 데이터를 작은 조각으로 나누어 처리할 수 있습니다.
<img src="https://velog.velcdn.com/images/sweet_sumin/post/c562a596-56aa-4f14-b648-774846cc0614/image.png" alt=""></p>
</li>
</ul>
<h2 id="minus-plus">minus, plus</h2>
<ul>
<li>minus</li>
<li>minusElement :  <strong>minus</strong>와 기능적으로 유사하지만, 명시적으로 minusElement를 사용하여 의도를 더욱 분명히 할 수 있습니다.</li>
<li>plus</li>
<li>plusElement : plus와 기능 같습니다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[리플랙션 활용하기]]></title>
            <link>https://velog.io/@sweet_sumin/%EB%A6%AC%ED%94%8C%EB%9E%99%EC%85%98-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sweet_sumin/%EB%A6%AC%ED%94%8C%EB%9E%99%EC%85%98-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 25 Mar 2024 11:58:09 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-개요">📌 개요</h2>
<p>새로운 시니어 개발자가 들어왔당🎉
그때 들었던 말 중에 하나가 </p>
<blockquote>
<p>나랑 일하려면 리플랙션 알아야 행 &gt;&lt;</p>
</blockquote>
<p>였다. 리플랙션? 그게 뭐였지? 그 동안 코드 상에서 리플랙션을 사용해본 적이 없어서 &#39;뭐였더라? 예전에 어노테이션이랑 이런거 관련된 거 였는데&#39; 정도만 생각하고 있었다. 
이전에 내가 써놓은 이론적인 내용을 찾아보니 &gt;&gt; <a href="https://velog.io/@sweet_sumin/Object%EC%99%80-Reflection">Object와 Reflection</a></p>
<blockquote>
<p>리플렉션은 구체적인 클래스 타입을 알지 못해도, 그 클래스의 메소드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API이다. 프로그램이 동적으로 소스 코드를 해석하여 클래스의 정보를 분석해 내는 기술</p>
</blockquote>
<p>이라고 써져 있었다. 취준 기간에는 단순히 어떤 것인지 알아보았다면 이번에는 실무에서 얼마나 활용하는지 확인해보려고 한다. </p>
<h2 id="📌-리플랙션을-통해-가져올-수-있는-것">📌 리플랙션을 통해 가져올 수 있는 것</h2>
<p>간단히 리플랙션을 통해 알 수 있는 정보를 정리해보자면, </p>
<blockquote>
</blockquote>
<ul>
<li>getXXX() : 상속받은 클래스와 인터페이스를 포함하여 모든 public 요소를 가져온다.</li>
<li>getDeclaredXXX() : 상속받은 클래스와 인터페이스를 제외하고 해당 클래스에 직접 정의된 내용만 가져온다. 또한 접근 제어자와 상관없이 요소에 접근할 수 있다
👉 메소드, 필드, 어노테이션 다 가져올 수 있다는 말<blockquote>
</blockquote>
</li>
</ul>
<ol>
<li>Class 객체를 획득 할 수 있다. </li>
<li>Class 를 사용해서 생성자를 Constructor 타입으로 가져올 수 있다</li>
<li>필드 (목록) 가져올 수 있다 : 리플렉션을 사용하여 Field 타입의 오브젝트를 획득하여 객체 필드에 직접 접근할 수 있다.객체의 필드값에 접근하여 값을 수정 할 수도 있다. </li>
<li>메소드 (목록) 가져져올 수 있다 : 리플렉션을 사용하여 Method 타입의 오브젝트를 획득하여 객체 메소드에 직접 접근할 수 있다</li>
<li>어노테이션에 대한 정보 가져올 수 있다 : 클래스에 붙어있는 어노테이션을 가져올 수 있다. 어노테이션이 가지고 있는 필드에도 접근할 수 있다. </li>
</ol>
<p>이렇게만 알면 잘 와닿지가 않는다. 
생각해보면 내가 가장 궁금했던 것은 진짜 이걸 실무에서도 쓰긴 하는 거야? 어떻게 활용하는 거야? 였다. </p>
<h2 id="📌-리플랙션의-활용">📌 리플랙션의 활용</h2>
<p>결론은 은근히 사용했다. 경험이 쌓이면 더 많이 사용하는 모습이 보일 것 같다. 
내가 찾은 활용도는</p>
<p>1) 어노테이션 
자신의 어노테이션을 만들고, 해당 어노테이션이 선언되어 있는 클래스가 있다.</p>
<p><img src="https://velog.velcdn.com/images/sweet_sumin/post/6abd1732-7831-4b4f-a77a-2d82f9bcf2e4/image.png" alt=""></p>
<p>만약 해당 어노테이션이 선언된 클래스들을 모아다가 같은 비지니스 로직을 행하게 하고 싶으면?
<img src="https://velog.velcdn.com/images/sweet_sumin/post/cba47573-bac0-4815-a3cf-6522eda6627b/image.png" alt=""></p>
<ul>
<li>metadata.getEntityBindings(): 데이터베이스 매핑 정보에서 모든 엔터티를 가져오는 메서드. 이는 ORM에서 데이터베이스 테이블과 Java 클래스 간의 매핑 정보를 나타낸다.</li>
<li>persistent.getMappedClass(): 데이터베이스의 매핑된 엔터티에 해당하는 Java 클래스를 가져오는 메서드.</li>
<li>type.getDeclaredAnnotation(BsEntityListeners.class): Java 클래스에서 BsEntityListeners 어노테이션을 찾아 가져오는 메서드</li>
</ul>
<p>이런 식으로 활용한 경우도 있다. </p>
<p>2) 필드 비교
해당 코드는 나도 리플랙션을 사용해보고 싶어서 코드를 짜본 것이다.
기존 데이터와 데이터를 비교해서 만약 업데이트 된 컬럼이 존재한다면 해당 컬럼을 가져와야하는 요구사항이 있었다. </p>
<p><img src="https://velog.velcdn.com/images/sweet_sumin/post/a1ca52e2-93c5-40f8-8edf-35f67c00a395/image.png" alt=""></p>
<p>각 테이블마다 히스토리 테이블이 있었고 테이블의 모든 컬럼을 가져오고 일일히 다 비교하는 코드를 매 히스토리 테이블마다 하는 중복되는 코드를 짜고 싶지 않아서 리플랙션을 사용해서 요구사항을 풀었다. </p>
<p>위의 두 예시를 통해 단순히 실무에서도 종종 아주 가끔 사용은 한다~ 정도로만 참고하면 될 것 같다. </p>
<h2 id="📌-리플랙션의-단점">📌 리플랙션의 단점</h2>
<p>하지만 실무에서도 리플랙션 사용을 선호하지는 않는다. 크게 두가지 이유가 존재한다. </p>
<p>1) 성능 저하
리플렉션은 클래스의 바이트 코드를 이용하는 것이 아닌, 메모리에 해당 클래스의 특성을 투영한 데이터를 이용하므로 매 번 호출되는 메서드라도 최적화가 되지 않게 된다. 
즉,런타임에 클래스의 구조를 조사하고 메서드를 찾아야 하기 때문에 반복적으로 호출되는 작업에 대해서는 성능이 저하될 수 있다.</p>
<p>2) Private 필드 접근 가능
접근 제어자와 상관없이 다 접근 가능하기 때문에 보안적인 문제가 발생한다. 
invoke 메서드 호출 전에 setAccessible(true) 메서드를 호출해버리고 해당 값에 접근해버린다. </p>
<h3 id="참고">참고</h3>
<ul>
<li><a href="https://medium.com/@gksrlfw/%EB%A6%AC%ED%94%8C%EB%A0%89%EC%85%98-reflection-%EC%9D%B4-%EB%8A%90%EB%A6%B0-%EC%9D%B4%EC%9C%A0-a567edf80353">리플렉션reflection이-느린-이유</a></li>
<li><a href="https://jamesblog95.tistory.com/entry/%EB%A6%AC%ED%94%8C%EB%A0%89%EC%85%98Reflection">리플렉션Reflection</a></li>
<li><a href="https://ebabby.tistory.com/4">리플렉션 (reflection) 개념 이해하기</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Sealed class 란 무엇일까??]]></title>
            <link>https://velog.io/@sweet_sumin/Sealed-class-%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</link>
            <guid>https://velog.io/@sweet_sumin/Sealed-class-%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</guid>
            <pubDate>Tue, 16 Jan 2024 14:05:33 GMT</pubDate>
            <description><![CDATA[<p>어느날 갑자기 코드리뷰에 sealed class라는 것이 등장했다.🤔🤔</p>
<p><img src="https://velog.velcdn.com/images/sweet_sumin/post/ab1f43f3-2c44-4e5f-ad0d-6dd98a18d408/image.png" alt=""></p>
<p>처음 든 생각은 이게 뭐지?? 처음 본 것인데.. 내가 이렇게 공부를 안했던 거야? 언제 사용하는 것이지?? 바꿔서 달라지는 점이 뭐지?? 하위는 final?? 이게 무엇일까? 라는 의문을 가득 들게 만들었다. 의문점 해결 겸, 공부 겸, 다시 블로그를 쓰자는 올해의 다짐을 이행할 겸 컴퓨터 앞에 앉게 되었다. </p>
<h2 id="📌-sealed-class-가-무엇일까">📌 Sealed class 가 무엇일까?</h2>
<p>자바 17에 나타난 존재였다.. 수민아 공부하자 ㅎ
<img src="https://velog.velcdn.com/images/sweet_sumin/post/cb47b76a-b18c-4067-9ee5-a0dc222b64df/image.png" alt=""></p>
<p>개념 정리를 하자면</p>
<ul>
<li><strong>sealed type</strong> : 상속할 수 있는 것을 제한하는 것이다. 특정 타입이 가질 수 있는 종류를 제한한다는 관점으로 접근하면 이해하기 쉽다. 즉 무분별한 자식 클래스 생성을 방지하기 위해 봉인한다는 의미를 가지고 있다.
sealed type을 사용하기 위해서는 반드시</li>
<li><strong>permit</strong> : 허용하는 하위 타입 목록 지정 (sealed 클래스를 상속하는 클래스 지정)
를 같이 써줘야한다. </li>
</ul>
<p>개발자는 코드가 이해가 빠르쥬? (클래스명은 이해해줘요..이름 바꿀꺼다..이름짓는게 너무 어려워!)
<img src="https://velog.velcdn.com/images/sweet_sumin/post/f2814232-0544-4b52-8321-dc921c482848/image.png" alt=""></p>
<p>이렇게 써주면된다. 
단, sealed 타입을 상속한 타입은</p>
<ul>
<li>final : 상속 못받음</li>
<li>sealed : 상속을 다시 받을 수 있음</li>
<li>non-sealed : 봉인을 해제한다. 
중 하나를 지정해야 한다. 또한, sealed 타입과 같은 패키지/모듈에 위치해야 한다는 점을 명심해야 한다. </li>
</ul>
<p><img src="https://velog.velcdn.com/images/sweet_sumin/post/7561a900-ce8a-4725-9b89-ed829b26a5e2/image.png" alt=""></p>
<h2 id="📌-sealed-class은-그래서-언제-쓴다고">📌 Sealed class은 그래서 언제 쓴다고??</h2>
<p>Sealed class의 특징을 살펴보자면, </p>
<p>1) 허락하지 않으면 추상화된 클래스에 접근 할 수 없다
<img src="https://velog.velcdn.com/images/sweet_sumin/post/c4183507-364c-4163-a781-a632906e65e0/image.png" alt="">
2) 광범위하게 접근 가능하지만 광범위하게 확장 가능하지는 못한다.
<img src="https://velog.velcdn.com/images/sweet_sumin/post/7dfd92f2-31ff-42f4-a87d-9ed1ac4b5fa3/image.png" alt="">
라고 적혀져 있다. ㅎㅎ 너무 어렵게 표현되어있는데 
사실 간단하게 정리하자면, ** 📍추상화가 필요한 곳 중 구현체를 제한하고 싶을 때 sealed class를 활용하면 된다. **</p>
<blockquote>
<p><strong>Superclass Accessible, Not Extensible</strong></p>
</blockquote>
<p>이 말이 딱 적절한 표현인 듯 싶다. </p>
<h2 id="📌-그래서-현재-내-코드에-적용하는-것이-타당한가">📌 그래서 현재 내 코드에 적용하는 것이 타당한가?</h2>
<p>타당하다고 생각한다. 
현재 내가 진행하는 코드는 상위 추상 클래스를 두 클래스가 상속하도록 되어있다. 기존 코드는 이 두 클래스 이외에도 다른 클래스들도 무분별하게 해당 상위 추상클래스를 상속받을 수 있는 구조기 때문에 지정된 클래스만 상속할 수 있도록 제한할 필요성이 있었다. 그런 이유로 sealed 클래스를 적용하라고 리뷰를 달아주신 것 같다. 리뷰를 달아주신 steven님에게 감사의 마음을 표현한다. 문서에서도 sealed class를 사용하는 예시에 대해 잘 이야기하고 있다. 
<img src="https://velog.velcdn.com/images/sweet_sumin/post/d8c4f7db-7910-45d5-90a2-71c39a18d897/image.png" alt=""></p>
<ul>
<li><a href="https://www.baeldung.com/java-sealed-classes-interfaces">관련 참고 링크</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[PCB(Process Control Block) & TCB(Thread Control Block)]]></title>
            <link>https://velog.io/@sweet_sumin/PCBTCB</link>
            <guid>https://velog.io/@sweet_sumin/PCBTCB</guid>
            <pubDate>Fri, 11 Aug 2023 02:10:22 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-context-switching">📌 Context switching</h2>
<ul>
<li>CPU가 현재 작업중인 프로세스에서 다른 프로세스로 넘어갈 때, 이전의 프로세스 정보를 PCB에 저장하고 새롭게 실행할 프로세스의 정보를 PCB에서 읽어와 레지스터에 적재하는 과정을 말한다. 
<img src="https://velog.velcdn.com/images/sweet_sumin/post/4cbee609-52ba-4dc1-a16d-c6d6caa145fe/image.png" alt=""></li>
</ul>
<p> 1) 요청 발생 : 인터럽트나 트렙에 의해서 컨텍스트를 바꿔야 한다는 요청이 들어옴<br> ex) I/O 입출력, 예외, 시스템 콜 등 
 2) PCB 에 프로세스 정보 저장 : 기존에 실행중이던 프로세스 P0 와 관련된 정보들을 PCB0 에 저장함<br> running → waiting 
3) CPU 새롭게 할당    운영체제는 새롭게 실행할 프로세스 P1 에 대한 정보를 해당 PCB 에서 가져와 CPU 레지스터에 적재함
4) 요청 발생 : 인터럽트나 트렙에 의해서 컨텍스트를 바꿔야 한다는 요청이 들어옴 - P1의 상태를 PCB1에 저장
5) PCB0에 저장된 P0의 상태를 불러와 복구한다. 
6) P0 실행  </p>
<h2 id="📌-pcb--process-control-block-">📌 PCB ( Process control block )</h2>
<ul>
<li><p>운영체제가 프로세스를 제어하기 위해 정보(CPU 레지스터 값들)를 저장해 놓는 곳으로 프로세스의 상태 정보를 저장하는 구조체
운영체제가 프로세스 스케줄링을 위해 프로세스에 관한 모든 정보를 가지고 있는 데이터베이스</p>
</li>
<li><p>각 프로세스가 생성될 때마다 고유의 PCB가 생성되고, 프로세스가 완료되면 PCB는 제거된다. </p>
</li>
<li><p>프로세스는 CPU를 점유하여 작업을 처리하다가도 상태가 전이되면, 진행하던 작업 내용들을 모두 정리하고 CPU를 반환해야 하는데, 이때 진행하던 작업들을 모두 저장하지 않으면 다음에 자신의 순서가 왔을 때 어떠한 작업을 해야하는지 알 수 없는 사태가 발생한다.</p>
</li>
<li><blockquote>
<p>프로세스는 CPU가 처리하던 작업의 내용들을 자신의 PCB에 저장하고, 다음에 다시 CPU를 점유하여 작업을 수행해야 할 때 PCB로부터 해당 정보들을 CPU에 넘겨와서 계속해서 하던 작업을 진행할 수 있게 된다. </p>
</blockquote>
</li>
<li><p>PCB 는 프로세스의 중요한 정보들을 담고 있으므로 일반 사용자는 접근하지 못하는 보호된 메모리 영역에 존재</p>
</li>
<li><p>PCB에 저장되어 있는 정보
<img src="https://velog.velcdn.com/images/sweet_sumin/post/3b8b5583-e2a6-4246-a7db-bb7cb25d07ec/image.png" alt="">
1) 프로세스 식별자(Process ID) : 프로세스 고유 번호
2) 프로세스 상태(Process State) : 생성(create), 준비(ready), 실행 (running), 대기(waiting), 완료(terminated) 
3) 프로그램 계수기(Program Counter) : 프로그램 계수기는 이 프로세스가 다음에 실행할 명령어의 주소를 가리킵니다.
4) CPU 레지스터 및 일반 레지스터 : CPU(Central Processing Unit)가 요청을 처리하는 데 필요한 데이터를 일시적으로 저장하는 기억장치
5) CPU 스케줄링 정보 : 우선 순위, 최종 실행시각, CPU 점유시간 등
6) 메모리 관리 정보 : 해당 프로세스의 주소 공간 등
7) 프로세스 계정 정보 : 페이지 테이블, 스케줄링 큐 포인터, 소유자, 부모 등
8) 입출력 상태 정보 : 프로세스에 할당된 입출력장치 목록, 열린 파일 목록 등
9) 포인터 : 부모프로세스에 대한 포인터, 자식 프로세스에 대한 포인터, 프로세스가 위치한 메모리 주소에 대한 포인터, 할당된 자원에 대한 포인터 정보 등. 프로세스가 준비상태나 대기 상태일 경우에 큐로 운영이 되는데, 프로세스 제어 블록을 연결 시 큐로 구현하기 위해 포인터를 사용한다.
<img src="https://velog.velcdn.com/images/sweet_sumin/post/5c6c9d77-0b0f-4635-b6af-f126b5b8e610/image.png" alt=""></p>
</li>
</ul>
<ul>
<li><p>운영체제는 빠르게 PCB에 접근하기 위해 프로세스 테이블을 사용하여 각 프로세스의 PCB를 관리한다
<img src="https://velog.velcdn.com/images/sweet_sumin/post/74500c78-7f9d-4d09-b8a3-fd8960152cf1/image.png" alt=""></p>
</li>
<li><p>프로세스는 각각의 프로세스 이미지를 가진다. 프로세스 이미지는 데이터, 프로그램, 스택, PCB를 가진다.
<img src="https://velog.velcdn.com/images/sweet_sumin/post/173692d5-c136-4cbc-9541-d266dc9c41cc/image.png" alt=""></p>
</li>
</ul>
<h2 id="📌-tcb-thread-control-block">📌 TCB (Thread Control Block)</h2>
<ul>
<li><p>TCB는 PCB보다 적은 데이터를 가지는 자료구조이다.
참고) 스레드끼리는 사용자 수준 문맥의 text 영역과 data 영역은 공유, stack 영역은 따로 갖는다</p>
</li>
<li><p>구조
<img src="https://velog.velcdn.com/images/sweet_sumin/post/45f01312-f2dc-4cb9-b3f1-40f79fa03822/image.png" alt="">
1) <strong>스레드 ID</strong> : 스레드가 생성될 때 운영 체제에서 스레드에 할당하는 고유 식별자
2) <strong>스레드 상태</strong>: 스레드가 시스템을 통해 진행됨에 따라 변경되는 스레드의 상태
3) <strong>CPU 정보</strong>: 스레드가 얼마나 진행되었는지, 어떤 데이터가 사용되고 있는지 등 OS가 알아야 하는 모든 정보가 포함스레드 
4) <strong>우선 순위</strong>: 스레드 스케줄러가 READY 대기열에서 다음에 선택해야 하는 스레드를 결정하는 데 도움이 되는 다른 스레드에 대한 스레드의 가중치(또는 우선 순위)
5) <strong>PCB를 가리키는 포인터</strong> : 이 스레드 생성을 트리거한 프로세스를 가리키는 포인터
6) <strong>이 스레드가 생성한 스레드를 가리키는 포인터 **
7)</strong> stack 포인터** ( stack :  스레드가 함수 호출할때 저장하는 곳)</p>
</li>
<li><p>메모리영역 중 PCB는 커널 영역TCB는 유저영역과 커널 영역에 둘다 있을 수 있다. </p>
</li>
</ul>
<p>(1) 유저 영역 → 요즘은 거의 유저 영역은 거의 사용안함.
<img src="https://velog.velcdn.com/images/sweet_sumin/post/06adaa0a-31a1-43ad-8a2d-86d57463ae6f/image.png" alt="">
유저영역에 text, data, stack, heap 영역
스레드를 구현하고 관리하는 것이 유저영역에 heap에서 한다. 
스레드 관련 모든 동작이 사용자 영역</p>
<p>(2) 커널 영역
<img src="https://velog.velcdn.com/images/sweet_sumin/post/8e779fc8-d9df-4404-a34a-b88bd6583ee3/image.png" alt=""></p>
<p>커널에서 스레드 관련된 모든 작업(스케줄링, 실행) 을 관리한다.
사용자 스레드와 커널 스레드가 1:1 매핑이 되었있다.
커널이 전체 프로세스와 스레드 정보까지 유지한다. 
스케줄링과 동기화를 위해 더 많은 자원 필요하다
스레드 교환에 커널 개입으로 사용자영역에서 커널 영역으로 전환 필요하다
<img src="https://velog.velcdn.com/images/sweet_sumin/post/af524211-56b7-433c-b1e1-ada726dc395b/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로세스와 쓰레드]]></title>
            <link>https://velog.io/@sweet_sumin/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%99%80-%EC%93%B0%EB%A0%88%EB%93%9C</link>
            <guid>https://velog.io/@sweet_sumin/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%99%80-%EC%93%B0%EB%A0%88%EB%93%9C</guid>
            <pubDate>Fri, 11 Aug 2023 02:09:55 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-프로세스">📌 프로세스</h2>
<ul>
<li>프로그램을 구동하여 프로그램 자체와 프로그램의 상태가 메모리 상에서 실행되는 작업의 단위ex) 자바.class → 엔터 → JVM 시작 : 자바 프로세스 시작!
프로그램 : 일반적으로 하드 디스크 등에 저장되어 있는 실행 코드
<img src="https://velog.velcdn.com/images/sweet_sumin/post/82e1125c-f19e-4b45-81db-c0246bf11512/image.png" alt=""></li>
</ul>
<ul>
<li>하나의 프로세스는 운영체제로부터 각각 독립된 메모리 영역 (code, data, stack, heap)을 할당받는다. </li>
<li>한 프로세스는 다른 프로세스의 메모리에 직접 접근할 수 없다. </li>
<li>JVM 기준, 기본적으로 32MB ~ 64MB의 물리 메모리 점유  프로세스를 이용해 하나의 작업을 동시에 수행하려고 여러개의 프로세스를 띄워 각각 메모리를 할당해야 한다면? 
→ 쓰레드 등장 : 1MB 이내의 메모리 점유</li>
</ul>
<h2 id="쓰레드">쓰레드</h2>
<ul>
<li>프로세스 내에서 실행되는 흐름의 단위 혹은 CPU 스케줄링의 기본 단위</li>
<li>쓰레드는 각자 자신의 stack 영역을 보유한다. </li>
<li>쓰레드는 프로세스 내에서 Code, Data, Heap 영역을 공유한다.
<img src="https://velog.velcdn.com/images/sweet_sumin/post/05e55810-5031-4bfb-9318-0513a9e771f0/image.png" alt=""></li>
<li>프로세스 내에서 쓰레드들은 서로 주소 공간이나 자원들을 공유하며 실행할 수 있다.ex. 서로의 힙 메모리를 서로 읽고 쓸 수 있다. </li>
</ul>
<h2 id="멀티프로세스와-멀티-쓰레드">멀티프로세스와 멀티 쓰레드</h2>
<h3 id="멀티프로세스">멀티프로세스</h3>
<ul>
<li><p>한 애플리케이션에 여러개의 프로세스를 구성하여, 각 프로세스가 하나의 작업을 처리한다. </p>
</li>
<li><p>장점
1) 안정성이 좋다
2) 여러개의 자식 프로세스 중 하나에 문제가 생겨도, 다른 자식 프로세스에 영향이 확산되지 않는다
ex) 크롬 브라우저에 여러개의 탭을 띄우고 여러 웹사이트 방문시 한 탭이 잘못되어도, 해당 탭만 이용못하고 다른 탭은 별 문제가 없다. 
3) 각 프로세스들이 독립적으로 동작하여 자원이 서로 다르게 할당 된다. </p>
</li>
<li><p>단점
1) 메모리 사용량이 많다. 
2) 프로세스간 자원 공유가 필요할시 IPC(Inter-Process Commnuication)를 사용IPC : 프로세스 사이의 어렵고 복잡한 통신 기법, 파이프, 소켓, 메세지큐 등을 이용 
3) context switching에 의한 성능 저하의 우려가 존재한다. </p>
</li>
<li><ul>
<li>CPU 메모리 검색(다음 프로세스 정보 불러오기)</li>
</ul>
</li>
<li><ul>
<li>CPU 캐시 메모리 초기화</li>
</ul>
</li>
<li><ul>
<li>프로세스 상태 저장</li>
</ul>
</li>
<li><ul>
<li>등등..</li>
</ul>
</li>
</ul>
<h3 id="멀티-쓰레드">멀티 쓰레드</h3>
<ul>
<li><p>한 애플리케이션에 여러개의 쓰레드를 구성하여, 각 쓰레드가 하나의 작업을 처리한다. ex) 하나의 프로그램에서 두가지 이상의 동작을 동시에 처리하도록 하는 행위 . 웹서버
<img src="https://velog.velcdn.com/images/sweet_sumin/post/f676717c-98df-48c0-b24f-2ba99cbc898e/image.png" alt=""></p>
</li>
<li><p>웹 브라우저의 단일 탭/창 내에서 브라우저 네트워크 처리, I/O 및 기타 작업을 관리하고 처리</p>
</li>
<li><p>장점
1) 자원 공유가 쉽다. 
2) 시스템의 자원 소모가 감소하므로 프로세스를 할당하는 것보다 쓰레드를 할당하는 것이 비용이 적다
3) 프로세스 간 통신 방법에 비해 스레드 간의 통신 방법이 훨씬 간단하다-&gt; 프로세스 간의 전환 속도보다 쓰레드 간의 전환 속도가 빠르다.
4) 스레드가 프로세스보다 가벼움</p>
</li>
<li><ul>
<li>데이터 용량</li>
</ul>
</li>
<li><ul>
<li>생성 및 종료
5) 프로세스간 통신(IPC)를 사용하지 않고도 데이터 공유 가능
6) 컨텍스트 스위칭 오버헤드 존재하지만 상대적으로 프로세스의 컨텍스트 스위칭 오버해드보다 훨씬 비용이 낮다</li>
</ul>
</li>
<li><ul>
<li>스레드 간에 공유하는 자원을 제외한 스레드 정보(stack, register)만 교체
7) 응답 시간이 빠르다</li>
</ul>
</li>
<li><p>단점
1) 하나의 스레드에서 문제가 발생하면 다른 스레드들도 영향을 받아 전체 프로그램이 종료될 수 있다.
2) 쓰레드풀을 이용해 잔여 쓰레드를 가져와서 종료를 방지할 수 있지만, 새로운 쓰레드 생성이나 놀고 있는 쓰레드 가져오는 것에 대한 추가 비용 발생
3) 동기화 문제 발생 ( 공유 자원 )
4) 데드락 문제 발생
5) 그래도 context switching overhead 문제는 존재</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[객체 생성하는 방법, 장단점은 알고  적용하는 거야?]]></title>
            <link>https://velog.io/@sweet_sumin/%EB%B9%8C%EB%8D%94-%ED%8C%A8%ED%84%B4-%EC%9E%A5%EB%8B%A8%EC%A0%90%EC%9D%80-%EC%95%8C%EA%B3%A0-%EC%93%B0%EB%8A%94-%EA%B1%B0%EC%95%BC</link>
            <guid>https://velog.io/@sweet_sumin/%EB%B9%8C%EB%8D%94-%ED%8C%A8%ED%84%B4-%EC%9E%A5%EB%8B%A8%EC%A0%90%EC%9D%80-%EC%95%8C%EA%B3%A0-%EC%93%B0%EB%8A%94-%EA%B1%B0%EC%95%BC</guid>
            <pubDate>Tue, 01 Aug 2023 15:57:50 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-개요">📌 개요</h2>
<p>회사에서도, 친구들과의 대화에서 각각 한번씩 언급되었던 것이 빌더 패턴이었다. 대부분의 상황은 나는 주로 빌더패턴을 많이 사용했고, 이에 대해 반대하는 것이었다. 빌더 패턴은 객체를 생성하는 방법 중에 하나이다. 여러 대화를 하다보니, 내가 사실 그동안 빌더패턴을 사용한 것이 장점이 있어라기 보다는 습관적인 행동이었다는 것을 깨달았고, 이제라도 각각 어떤 장단점이 있는지 간단하게 정리해보려고 한다. </p>
<h2 id="📌-객체를-생성하는-방법">📌 객체를 생성하는 방법</h2>
<p>객체를 생성하는 방법은 보통 3가지로 알려져있다. </p>
<ol>
<li>점층적 생성자 패턴</li>
<li>자바빈즈 패턴</li>
<li>빌더 패턴</li>
</ol>
<p>각각의 생성 형태를 비교하자면,</p>
<h3 id="🐳-점층적-생성자-패턴">🐳 점층적 생성자 패턴</h3>
<pre><code>User user = new User(&quot;유저1&quot;, &quot;1234&quot;, &quot;email1&quot;, 0L);

userRepository.save(user);</code></pre><ul>
<li>필수 매개변수를 받는 생성자를 먼저 생성하고, 선택 매개변수 1개를 추가로 받는 생성자, 선택 매개변수 2개를 추가로 받는 생성자 등의 형태로 매개변수 개수만큼 생성자를 늘리는 방식이다. 마치 생성자가 점층적으로 성장하는 생성자를 가지도록 한 디자인 패턴이다.</li>
<li>생성자를 오버로딩 하는 방식이다.</li>
</ul>
<h3 id="🐳-자바-빈즈-패턴">🐳 자바 빈즈 패턴</h3>
<pre><code>   User user = new User();
   user.setUserId(&quot;유저1&quot;);
   user.setUserPassword(&quot;1234&quot;);
   user.setEmail(&quot;email1&quot;);
   user.setMoney(0L);

   userRepository.save(user);</code></pre><p> 매개 변수가 없는 생성자(기본 생성자)로 객체를 만든 후, setter 메서드를 호출하여 원하는 매개변수의 값을 설정하는 방식이다.</p>
<h3 id="🐳-빌더-패턴">🐳 빌더 패턴</h3>
<pre><code>    User user = User.builder()
                .userId(&quot;유저1&quot;)
                .userPassword(&quot;1234&quot;)
                .email(&quot;email1&quot;)
                .money(0L)
                .build();

    userRepository.save(user);
</code></pre><ul>
<li><p>복잡한 객체의 생성 과정과 표현 방법을 분리하여 다양한 구성의 인스턴스를 만드는 생성 패턴이다. </p>
</li>
<li><p>생성자에 들어갈 매개 변수를 메서드로 하나하나 받아들이고 마지막에 통합 빌드해서 객체를 생성하는 방식이다.
= 별도의 Builder 클래스를 만들어 메소드를 통해 step-by-step 으로 값을 입력받은 후에 최종적으로 build() 메소드로 하나의 인스턴스를 생성하여 리턴하는 패턴이다. </p>
<ul>
<li><p>메소드 체이닝을 통해 구현된다. </p>
<h2 id="📌-장단점">📌 장단점</h2>
<h3 id="🐳-점층적-생성자-패턴-1">🐳 점층적 생성자 패턴</h3>
</li>
<li><p>장점
구현이 간단하고 직관적이다</p>
</li>
<li><p>단점
1) 매개변수가 많아질수록 많은 조합이 만들어지고, 생성자의 수가 많아진다.
2) 초기화하고 싶은 필드만 포함한 생성자가 존재하지 않을 경우 원치 않는 필드까지 매개변수에 값을 지정해줘야 한다.</p>
</li>
</ul>
<h3 id="🐳-자바-빈즈-패턴-1">🐳 자바 빈즈 패턴</h3>
<ul>
<li>장점
1) 구현이 간단하고 가독성이 좋다
2) setter 메서드를 통해 유연하게 객체의 상태를 변경할 수 있다. </li>
<li>단점
1) 객체 생성과 초기화가 분리되어 객체의 무결성을 보장하기 어려울 수 있다. 
2) setter 메서드가 있기 때문에 객체가 불변성을 가지기 어렵다
3) 멀티스레드 환경에서 동기화 문제가 발생할 수 있다.
4) 하나의 객체를 완성하기까지 메서드를 여러 번 호출해야 한다.</li>
</ul>
<h3 id="🐳-빌더-패턴-1">🐳 빌더 패턴</h3>
<ul>
<li><p>장점
1) 객체의 불변성을 보장한다. (setter 메소드가 없으므로)
2) build() 함수가 잘못된 값이 입력되었는지 검증하게 할 수도 있다.
3) 한 번에 객체를 생성하므로 객체 일관성이 깨지지 않는다.</p>
</li>
<li><p>단점
1) 객체를 생성하기 위해 빌더 객체를 추가로 생성해야 하므로 오버헤드가 발생하다.
2) 일부 필드의 값을 안넣어도 필드가 Null로 들어가면서 객체가 생성가능하여, 개발자가 필수적으로 설정해야 하는 필드를 빠트릴 수 있다. 
예를 들어 , 위의 예시에서 userPassword와 money에 대한 값을 넣지 않아도 생성가능하다. </p>
<pre><code>User user = User.builder()
            .userId(&quot;유저1&quot;)
            .email(&quot;email1&quot;)
            .build();

userRepository.save(user);
</code></pre></li>
</ul>
<pre><code>

</code></pre></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>