<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>CHANG's LOG</title>
        <link>https://velog.io/</link>
        <description>모르면 알고 넘어가자</description>
        <lastBuildDate>Mon, 07 Apr 2025 13:16:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>CHANG's LOG</title>
            <url>https://velog.velcdn.com/images/joo-chang/profile/e7e7574a-3be3-4ba0-a5fc-f7e4b0f87e72/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. CHANG's LOG. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/joo-chang" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[JS] Currying 기법]]></title>
            <link>https://velog.io/@joo-chang/JS-Currying-%EA%B8%B0%EB%B2%95</link>
            <guid>https://velog.io/@joo-chang/JS-Currying-%EA%B8%B0%EB%B2%95</guid>
            <pubDate>Mon, 07 Apr 2025 13:16:00 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>커링(Currying)은 함수형 프로그래밍의 핵심 기법 중 하나로, 여러 개의 인자를 가진 함수를 인자 하나만 받는 함수들의 체인으로 변환하는 과정이다. 이 기법은 함수의 재사용성과 유연성을 높이는 데 큰 도움을 준다.</p>
</blockquote>
<h2 id="기본-개념">기본 개념</h2>
<p>커링은 n개의 인자를 받는 함수를 각각 하나의 인자만 받는 n개의 함수로 분리하는 기법이다. 각 단계에서 인자 하나를 받아 처리하고, 다음 인자를 기다리는 새로운 함수를 반환한다.</p>
<p>예를 들어, 세 개의 인자를 받아 더하는 함수를 커링으로 변환해보자:</p>
<pre><code class="language-js">// 일반적인 함수
function add(a, b, c) {
  return a + b + c;
}

// 커링된 함수
function curriedAdd(a) {
  return function(b) {
    return function(c) {
      return a + b + c;
    };
  };
}

// 사용 예시
const result1 = add(1, 2, 3); // 6
const result2 = curriedAdd(1)(2)(3); // 6</code></pre>
<p>화살표 함수를 사용하면 더 간결하게 표현할 수 있다:</p>
<pre><code class="language-js">const curriedAdd = a =&gt; b =&gt; c =&gt; a + b + c;</code></pre>
<h2 id="커링-활용-이유">커링 활용 이유</h2>
<ul>
<li><strong>부분 적용(Partial Application)</strong>: 일부 인자만 미리 적용하여 재사용 가능한 함수를 만들 수 있다.</li>
<li><strong>함수 조합성 향상</strong>: 작은 단위의 함수를 조합하여 복잡한, 고차원 함수를 구성할 수 있다.</li>
<li><strong>지연 실행</strong>: 모든 인자가 제공될 때까지 함수 실행을 지연시킬 수 있다.</li>
<li><strong>코드 가독성 향상</strong>: 복잡한 로직을 작은 단위로 분리하여 더 명확하고 읽기 쉬운 코드를 작성할 수 있다.</li>
</ul>
<h2 id="커링-활용-예시">커링 활용 예시</h2>
<h3 id="범용-커링-함수-만들기">범용 커링 함수 만들기</h3>
<p>일반 함수를 커링 함수로 변환하는 유틸리티 함수를 구현할 수 있다:</p>
<pre><code class="language-js">function curry(fn) {
  return function curried(...args) {
    // 1. 충분한 인자가 전달되었는지 확인
    if (args.length &gt;= fn.length) {
      // 2. 충분한 인자가 있으면 원래 함수 실행
      return fn.apply(this, args);
    } else {
      // 3. 인자가 부족하면 나머지 인자를 기다리는 새 함수 반환
      return function(...nextArgs) {
        // 4. 이전 인자와 새 인자를 합쳐서 다시 curried 함수 호출
        return curried.apply(this, args.concat(nextArgs));
      };
    }
  };
}
// 사용 예시
function sum(a, b, c) {
  return a + b + c;
}

const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // 6
console.log(curriedSum(1, 2)(3)); // 6
console.log(curriedSum(1)(2, 3)); // 6</code></pre>
<h3 id="부분-적용을-통한-함수-재사용">부분 적용을 통한 함수 재사용</h3>
<p>커링을 이용해 특정 인자를 고정한 새로운 함수를 만들 수 있다:</p>
<pre><code class="language-js">const multiply = a =&gt; b =&gt; a * b;

// 2를 곱하는 함수
const double = multiply(2);
// 3을 곱하는 함수
const triple = multiply(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15</code></pre>
<h3 id="이벤트-핸들러-최적화">이벤트 핸들러 최적화</h3>
<p>커링은 이벤트 핸들러에서도 유용하게 활용될 수 있다:</p>
<pre><code class="language-js">const handleEvent = eventType =&gt; element =&gt; callback =&gt; {
  element.addEventListener(eventType, callback);
  return () =&gt; element.removeEventListener(eventType, callback);
};

// 클릭 이벤트 전용 핸들러
const handleClick = handleEvent(&#39;click&#39;);

// 특정 버튼에 대한 핸들러
const buttonClickHandler = handleClick(document.getElementById(&#39;myButton&#39;));

// 이벤트 등록
const removeListener = buttonClickHandler(e =&gt; console.log(&#39;Button clicked!&#39;));

// 나중에 필요하면 이벤트 제거
// removeListener();</code></pre>
<h3 id="함수형-프로그래밍-유틸리티">함수형 프로그래밍 유틸리티</h3>
<p>배열 메서드와 함께 사용하면 더 강력한 기능을 구현할 수 있다:</p>
<pre><code class="language-js">const map = fn =&gt; array =&gt; array.map(fn);
const filter = predicate =&gt; array =&gt; array.filter(predicate);
const reduce = (fn, initial) =&gt; array =&gt; array.reduce(fn, initial);

// 숫자 배열의 각 요소를 제곱하는 함수
const square = x =&gt; x * x;
const squareAll = map(square);

// 짝수만 필터링하는 함수
const isEven = x =&gt; x % 2 === 0;
const filterEven = filter(isEven);

// 함수 조합
const numbers = [1, 2, 3, 4, 5];
const result = filterEven(squareAll(numbers)); // [4, 16]</code></pre>
<h2 id="결론">결론</h2>
<p>커링은 JavaScript에서 함수형 프로그래밍을 구현하는 강력한 도구다.</p>
<ol>
<li>함수의 재사용성을 높이고 코드의 중복을 줄여준다.</li>
<li>함수 조합을 통해 복잡한 로직을 더 간결하고 명확하게 표현할 수 있다.</li>
<li>함수의 실행을 지연시켜 필요한 시점에 필요한 인자만으로 함수를 호출할 수 있다.</li>
<li>부분 적용을 통해 특정 상황에 맞는 특화된 함수를 쉽게 생성할 수 있다.</li>
</ol>
<p>단, 과도한 커링은 코드의 복잡성을 증가시킬 수 있으므로, 적절한 상황에서 균형 있게 사용하는 것이 중요하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[제네릭 (Generics)]]></title>
            <link>https://velog.io/@joo-chang/%EC%A0%9C%EB%84%A4%EB%A6%AD-Generics</link>
            <guid>https://velog.io/@joo-chang/%EC%A0%9C%EB%84%A4%EB%A6%AD-Generics</guid>
            <pubDate>Mon, 17 Mar 2025 13:51:20 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>제네릭은 코드의 재사용성과 타입 안정성을 동시에 확보할 수 있도록 도와주는 기능이다. 즉, 구체적인 타입을 미리 정하지 않고, 필요할 때마다 유연하게 타입을 지정할 수 있게 해준다.</p>
</blockquote>
<h2 id="기본-개념">기본 개념</h2>
<p>함수를 작성할 때 특정 타입에 의존하지 않고, 여러 타입에 대해 동작하도록 만들고 싶을 때 제네릭을 사용한다. </p>
<p>예를 들어, 입력받은 값을 그대로 반환하는 함수</p>
<pre><code class="language-ts">function identity&lt;T&gt;(arg: T): T {
    return arg;
}

const result1 = identity&lt;string&gt;(&quot;Hello&quot;); // string type
const result2 = identity&lt;number&gt;(123); // number type
</code></pre>
<p>여기서  <code>&lt;T&gt;</code> 는 타입 변수로, 함수를 호출할 때 실제 타입으로 대체된다. 이를 통해 함수가 어떤 타입을 받아도 그에 맞는 타입을 반환할 수 있게 된다.
<br></p>
<h2 id="제네릭-활용-이유">제네릭 활용 이유</h2>
<ul>
<li>재사용성 증가 : 동일한 로직을 다양한 데이터 타입에 대해 사용할 수 있다.</li>
<li>타입 안정성 보장 : 코드 작성 시점에 타입을 체크해 오류를 줄일 수 있으며, 런타임 이전에 문제를 발견할 수 있다.</li>
<li>유연한 설계 : 구체적인 타입에 얽매이지 않고, 필요한 시점에 타입을 결정할 수 있어 모듈성과 확장성이 뛰어나다.<br>

</li>
</ul>
<h2 id="제네릭-활용-예시">제네릭 활용 예시</h2>
<h3 id="제네릭-인터페이스">제네릭 인터페이스</h3>
<pre><code class="language-ts">interface ApiResponse&lt;T&gt; {
    data: T;
    status: number;
    message: string;
}

const userReesponse: ApiResponse&lt;{ name: string; age: number }&gt; = {
    data: { name: &quot;joo&quot;, age: 29},
    status: 200,
    message: &quot;성공&quot;
};</code></pre>
<p>여기서 T는 사용 시점에 구체적인 타입(객체)을 지정하게 된다.</p>
<h3 id="제네릭-클래스">제네릭 클래스</h3>
<p>클래스에서 제네릭을 사용하면 여러 타입을 처리하는 범용 클래스를 만들 수 있다.</p>
<pre><code class="language-ts">class Container&lt;T&gt; {
    private _value: T;

    constructor(value: T) {
        this._value = value;
    }

    get value(): T {
        return this._value;
    }

    set value(newValue: T) {
        this._value = newValue;
    }
}

const stringContainer = new Container&lt;string&gt;(&quot;문자열&quot;);
console.log(stringContainer.value); // &quot;문자열&quot;</code></pre>
<h3 id="제네릭-제약조건-constraints">제네릭 제약조건 (Constraints)</h3>
<p>모든 타입이 아닌, 특정 조건을 만족하는 타입만 받도록 제한할 수 있다.</p>
<pre><code class="language-ts">interface Lengthwise {
    length: number;
}

function logLength&lt;T extends Lengthwise&gt;(item: T): void {
    console.log(item.length);
}

logLength(&quot;Hello&quot;);     // 문자열은 length 속성을 가짐
logLength([1, 2, 3]);   // 배열도 length 속성이 있음
// logLength(42);       // 숫자는 length 속성이 없으므로 오류 발생</code></pre>
<p>위에서는 제네릭 타입 T가 Lengthwise 인터페이스를 반드시 만족하도록 제한했다.</p>
<h3 id="여러-개의-제네릭-사용">여러 개의 제네릭 사용</h3>
<p>함수나 클래스에 두 개 이상의 제네릭 타입 변수를 사용하여 복합적인 타입 관계를 정의할 수 있다.</p>
<pre><code class="language-ts">function swap&lt;K, V&gt;(key: K, value: V): [V, K] {
    return [value, key];
}
const swapped = swap(&quot;age&quot;, 30); // [30, &quot;age&quot;]</code></pre>
<p>두 개의 타입 매개변수를 사용해 입력 순서를 바꾸거나, 타입 간의 관계를 명확하게 표현할 수 있다.
<br></p>
<h2 id="결론">결론</h2>
<p>제네릭은 함수, 인터페이스, 클래스 등 다양한 구조에서 사용 가능하며, 코드의 재사용성과 유지보수성을 크게 향상시킨다.</p>
<ol>
<li>구체적인 타입을 나중에 지정할 수 있어 유연한 코드 설계가 가능하다.</li>
<li>타입 안전성을 보장하여 컴파일 시점에서 오류를 사전에 예방할 수 있다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트 동작 방식 (1)]]></title>
            <link>https://velog.io/@joo-chang/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%8F%99%EC%9E%91-%EB%B0%A9%EC%8B%9D-1</link>
            <guid>https://velog.io/@joo-chang/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%8F%99%EC%9E%91-%EB%B0%A9%EC%8B%9D-1</guid>
            <pubDate>Tue, 04 Feb 2025 14:58:14 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>리액트는 선언적 UI 라이브러리로, 사용자 인터페이스를 효율적으로 구성하고 관리하는데 초점이 맞춰져 있다. 리액트 동작 방식을 이해하라면 핵심 개념인 가상 DOM, 컴포넌트 기반 아키텍처, 단방향 데이터 흐름 등을 알아야 한다.</p>
</blockquote>
<p>선언적 UI 라이브러리는 <code>어떤 상태일 때 어떻게 보여야 하는지를 선언하는 방식</code>으로 개발하는 것을 의미한다.</p>
<h2 id="가상-dom-virtual-dom">가상 DOM (Virtual DOM)</h2>
<p>리액트의 가장 큰 특징 중 하나는 가상 DOM을 사용한다는 것이다. 가상 DOM은 실제 DOM의 가벼운 복사본으로, 리액트는 가상 DOM을 통해 UI 업데이트를 최적화한다.</p>
<h3 id="가상-dom-동작-과정">가상 DOM 동작 과정</h3>
<ol>
<li><strong>초기 렌더링</strong> : 리액트는 컴포넌트를 기반으로 가상 DOM 트리를 생성한다.</li>
<li><strong>상태 변경</strong> : 컴포넌트의 상태(state)나 속성(props)가 변경되면, 리액트는 새로운 가상 DOM 트리를 생성한다.</li>
<li><strong>Diffing 알고리즘</strong> : 리액트는 이전 가상 DOM과 새로운 가상 DOM을 비교하여 변경된 부분만 찾아낸다.</li>
<li><strong>실제 DOM 업데이트</strong> : 변경된 부분만 실제 DOM에 반영한다. 이 과정을 재조정(Reconciliation)이라고 한다.</li>
</ol>
<h3 id="가상-dom-장점">가상 DOM 장점</h3>
<ul>
<li><strong>성능 향상</strong> : 실제 DOM을 직접 조작하는 것은 비용이 많이 드는 작업이다. 가상 DOM을 통해 최소한의 DOM 업데이트만 수행하므로 성능이 개선된다.</li>
<li><strong>선언적 UI</strong> : 개발자는 UI가 어떻게 업데이트될지 신경 쓰지 않고, <code>어떤 상태일 때 UI가 어떻게 보여야 하는지</code>만 선언하면 된다.<br>

</li>
</ul>
<h2 id="컨포넌트-기반-아키텍처">컨포넌트 기반 아키텍처</h2>
<p>리액트는 컴포넌트 단위로 UI를 구성한다. 컴포넌트는 독립적이고 재사용 가능한 UI 조각이다.</p>
<h3 id="특징">특징</h3>
<ul>
<li><strong>재사용성</strong> : 같은 컴포넌트를 여러 곳에서 재사용할 수 있다.</li>
<li><strong>캡슐화</strong> : 컴포넌트는 자신의 상태와 로직을 내부에 캡슐화한다.</li>
<li><strong>계층 구조</strong> : 컴포넌트는 부모-자식 관계로 구성되며, 트리 구조를 형성한다.</li>
</ul>
<h3 id="종류">종류</h3>
<ul>
<li><strong>함수형 컴포넌트</strong> : 간단하고 가볍게 작성할 수 있다. <code>useState</code>, <code>useEffect</code> 등의 훅을 사용해 상태와 생명주기를 관리한다.</li>
<li><strong>클래스형 컴포넌트</strong> : <code>state</code>와 생명주기 메서드를 사용해 복잡한 로직을 처리한다.<br>

</li>
</ul>
<h2 id="단방향-데이터-흐름">단방향 데이터 흐름</h2>
<p>리액트는 데이터가 단방향으로 흐르도록 설계되었다. 이는 데이터의 흐름을 예측 가능하게 하고, 디버깅을 쉽게 만든다.</p>
<h3 id="특징-1">특징</h3>
<ol>
<li><strong>상위 -&gt; 하위 컴포넌트로 데이터 전달</strong> : 부모 컴포넌트는 <code>props</code>를 자식 컨포넌트로 데이터를 전달한다.</li>
</ol>
<pre><code class="language-js">function Parent() {
  const message = &quot;Hello from Parent&quot;;
  return &lt;Child message={message} /&gt;;
}

function Child({ message }) {
  return &lt;p&gt;{message}&lt;/p&gt;;
}</code></pre>
<ol start="2">
<li><strong>하위 -&gt; 상위 컴포넌트로 이벤트 전달</strong> : 자식 컴포넌트는 부모 컴포넌트로부터 전달받은 콜백 함수를 통해 이벤트를 전달한다.</li>
</ol>
<pre><code class="language-js">function Parent() {
  const handleClick = () =&gt; {
    alert(&#39;Button clicked in Child&#39;);
  };
  return &lt;Child onClick={handleClick} /&gt;;
}

function Child({ onClick }) {
  return &lt;button onClick={onClick}&gt;Click&lt;/button&gt;;
}</code></pre>
<br>]]></description>
        </item>
        <item>
            <title><![CDATA[DispatcherServlet, Interceptor, Filter, AOP 정리]]></title>
            <link>https://velog.io/@joo-chang/DispatcherServlet-Interceptor-Filter-AOP-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@joo-chang/DispatcherServlet-Interceptor-Filter-AOP-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Wed, 22 Jan 2025 14:32:04 GMT</pubDate>
            <description><![CDATA[<h2 id="dispatcherservlet">DispatcherServlet</h2>
<ul>
<li>스프링 MVC의 프론트 컨트롤러 역할을 수행하는 서블릿 클래스이다.</li>
<li>모든 HTTP 요청이 DispatcherServlet을 거쳐서, 적절한 Controller와 핸들러로 매핑되어 처리된다.</li>
</ul>
<h3 id="동작-흐름">동작 흐름</h3>
<ol>
<li>웹 서버에서 HTTP 요청을 DispatcherServlet에 전달한다.</li>
<li>DispatcherServlet은 스프링의 HandlerMapping을 통해 어떤 컨트롤러 메서드가 이 요청을 처리할지 찾는다.</li>
<li>연결된 HandlerAdapter가 실제 컨트롤러 메서드를 호출해 로직을 수행한다.</li>
<li>컨트롤러가 반환하는 뷰나 응답을 ViewResolver나 메세지 컨버터를 통해 최종 HTTP 응답 형태로 변환한다.</li>
<li>DispatcherServlet이 이 결과를 웹 서버에게 넘기고, 클라이언트에게 응답이 전송된다.</li>
</ol>
<h3 id="핵심-포인트">핵심 포인트</h3>
<ul>
<li>DispatcherServlet은 스프링 MVC의 핵심이다. 요청 분배, 컨트롤러 호출, 뷰 렌더링 등 전반적인 제어를 담당한다.</li>
<li>스프링 부트에서 <code>/ (루트 경로)</code> 매핑으로 DispatcherServlet이 자동 등록된다.</li>
</ul>
<br>

<h2 id="interceptor-handlerinterceptor">Interceptor (HandlerInterceptor)</h2>
<ul>
<li>스프링 MVC 에서 제공하는 핸들러 인터셉터로, 컨트롤러(핸들러) 호출 전후 혹은 뷰 렌더링 전후에 추가 처리를 하고 싶을 때 사용한다. ex) 인증/권한 체크, 로깅, 공통 세션 처리</li>
</ul>
<h3 id="동작-흐름-1">동작 흐름</h3>
<ul>
<li>DispatcherServlet이 *&quot;이 요청은 A 컨트롤러가 처리해야겠다.&quot;* 라고 결정하기 전에, HandlerInterceptor를 preHandle 순서대로 실행</li>
<li>컨트롤러 로직이 끝난 후, postHandle과 afterCompletion 메서드를 순서대로 실행. (응답 반환 전후)</li>
<li>preHandle에서 <code>return false;</code> 하면 요청 처리를 중단하고 직접 응답을 보낼 수 있다. (컨트롤러로 넘어가지 않는다.)</li>
</ul>
<h3 id="주요-메서드">주요 메서드</h3>
<ul>
<li>preHandle : 컨트롤러 호출 전. true/false 반환 (false면 흐름 중단)</li>
<li>postHandle : 컨트롤러 처리 후, 뷰 렌더링 전</li>
<li>afterCompletion : 뷰 렌더링까지 끝난 뒤(완료 후) 호출</li>
</ul>
<h3 id="등록-방식">등록 방식</h3>
<ul>
<li>주로 <strong>WebMvcConfigurer</strong>를 구현한 설정 클래스에서 <code>addInterceptors(InterceptorRegistry registry)</code> 메서드로 등록</li>
</ul>
<br>

<h2 id="filter-javaxservletfilter">Filter (javax.servlet.Filter)</h2>
<ul>
<li>서블릿 스펙의 Filter이며, 스프링 MVC의 DispatcherServlet보다 더 앞에서 동작 가능(서블릿 컨테이너 레벨)</li>
<li>모든 요청/응답 흐름에 대해 공통 전처리, 후처리를 할 수 있는 기능</li>
<li>ex) 인코딩 설정, 보안 검증, 로깅, CORS 처리 등</li>
</ul>
<h3 id="동작-흐름-2">동작 흐름</h3>
<ul>
<li>HTTP 요청이 웹 서버에 들어오면, Filter Chain의 등록 순서대로 각 Filter가 doFilter()를 실행한다.</li>
<li>각 Filter는 다음 Filter로 넘길지 결정할 수 있고, 다음 Filter 까지 다 끝난 후에 응답에 대해서도 후처리 할 수 있다.</li>
<li>최종적으로 DispatcherServlet(또는 다른 서블릿)에 도달하기 전까지 여러 Filter가 chaining 된다.</li>
</ul>
<h3 id="주요-메서드-1">주요 메서드</h3>
<ul>
<li>init(FilterConfig) : 초기화 시점(서버 시작)</li>
<li>doFilter(SevletRequest, ServletResponse, FilterChain) : 요청-응답 처리의 핵심</li>
<li>destroy() : 종료 시점(서버 종료)</li>
</ul>
<h3 id="등록-방식-1">등록 방식</h3>
<ul>
<li>web.xml에 필터 태그로 설정하거나, Spring Boot 에서는 @WebFilter + FilterRegistrationBean으로 등록 가능</li>
<li>순서를 설정해 여러 Filter를 체인으로 구성한다.</li>
</ul>
<h2 id="aop-aspect-oriented-promramming">AOP (Aspect-Oriented Promramming)</h2>
<ul>
<li>AOP는 관점 지향 프로그래밍이라는 뜻으로, 공통 기능을 분리해서 애플리케이션의 핵심 로직과 횡단 관심사를 분리하는 기법이다.</li>
<li>스프링에서 @Aspect + pointcut/advice 설정으로 메서드 호출 전후/예외 등에 로직을 삽입할 수 있다.</li>
</ul>
<h3 id="동작-시점">동작 시점</h3>
<ul>
<li>런타임에 프록시 객체를 생성하거나, 컴파일/클래스로드 시점에 코드 변환(스프링은 보통 런타임 프록시) 하여 특정 메서드 실행 전후에 어드바이스를 실행한다.</li>
</ul>
<h3 id="하는-일">하는 일</h3>
<ul>
<li>트랜잭션 처리(@Transactional 내부도 AOP 원리), 로깅, 예외 처리, 보안 체크, 캐싱, 모니터링 등</li>
<li>Interceptor나 Filter로도 할 수 있지만, <strong>메서드</strong> 단위나 <strong>클래스</strong> 단위의 공통 기능을 AOP 로 처리하면 더 정교하고 깔끔하게 분리 가능하다.</li>
</ul>
<br>

<h2 id="차이점-요약">차이점 요약</h2>
<h3 id="동작-계층">동작 계층</h3>
<ul>
<li><strong>Filter</strong> : 서블릿 컨테이너 레벨(DispatcherServlet보다 앞). 모든 서블릿에 대해 동작할 수 있음</li>
<li><strong>DispatcherServlet</strong> : Spring MVC 프론트 컨트롤러. 요청이 필터 체인을 거친 뒤, DispatcherServlet으로 들어옴</li>
<li><strong>Interceptor</strong> : Spring MVC(DispatcherServlet) 내부에서 컨트롤러 전후 흐름 제어</li>
<li><strong>AOP</strong> : 스프링 빈의 메서드를 호출할 때, 프록시를 통해 공통 로직 삽입(주로 비즈니스 로직 전후)</li>
</ul>
<h3 id="사용-목적">사용 목적</h3>
<ul>
<li><strong>Filter</strong> : 인코딩, CORS, 공통 보안 체크, 로깅 등 전역적이고 서블릿 수준의 처리가 필요할 때</li>
<li><strong>DispatcherServlet</strong> : 스프링 Request -&gt; Contorller -&gt; View 로 이어지는 메인 흐름 담당</li>
<li><strong>Interceptor</strong> : 스프링 MVC 전용으로, 컨트롤러 진입 전후 로직, 공통 모델 추가, 인증/인가, 로깅, postHandle 등 핸들러 레벨의 처리</li>
<li><strong>AOP</strong> : 트랜잭션, 로깅, 캐싱, 예외 처리 등 메서드 단위 횡단 관심사</li>
</ul>
<br>

<h2 id="결론">결론</h2>
<ul>
<li><strong>Filter</strong> : 서블릿 레벨에서 요청/응답을 전역적으로 가로채는 표준 메커니즘. 인코딩, 보안 등에 사용</li>
<li><strong>DispatcherServlet</strong> : 스프링 MVC 핵심 서블릿, 요청을 컨트롤러로 연결하고 응답 처리.</li>
<li><strong>Interceptor</strong> : 스프링 MVC에서 컨트롤러 전후 로직(특정 path)에 집중. 인증, 권한, 로깅 등.</li>
<li><strong>AOP</strong> : 비즈니스 로직(메서드) 전후/예외 시점에 횡단 기능 삽입. 트랜잭션, 로깅, 캐싱 등에 자주 사용.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP 통신 과정]]></title>
            <link>https://velog.io/@joo-chang/HTTP-%ED%86%B5%EC%8B%A0-%EA%B3%BC%EC%A0%95</link>
            <guid>https://velog.io/@joo-chang/HTTP-%ED%86%B5%EC%8B%A0-%EA%B3%BC%EC%A0%95</guid>
            <pubDate>Mon, 20 Jan 2025 13:18:36 GMT</pubDate>
            <description><![CDATA[<h2 id="http">HTTP</h2>
<p>HTTP(Hyper Text Transfer Protocol)이란 클라이언트와 서버 간 데이터를 주고 받기 위한 프로토콜이다.</p>
<p>HTTP 종류는 TCP와 UDP 방식이 있으며, 80 포트를 사용한다.</p>
<h3 id="tcp">TCP</h3>
<p>TCP는 연결형 서비스로 3-way handshaking 과정을 통해 연결을 설정하기 때문에 높은 신뢰성을 보장하지만, 속도가 비교적 느리다는 단점이 있다.</p>
<h3 id="udp">UDP</h3>
<p>UDP는 비연결형 서비스로 3-way handshaking을 사용하지 않기 때문에 신뢰성이 떨이지는 단점이 있지만, 데이터 수신 여부를 확인하지 않기 때문에 속도가 빠르다는 장점이 있다.</p>
<p>TCP는 신뢰성이 중요한 파일 교환과 같은 경우에 쓰이고, UDP는 실시간성이 중요한 스트리밍에 자주 사용된다.</p>
<table>
<thead>
<tr>
<th><strong>프로토콜 종류</strong></th>
<th><strong>TCP</strong></th>
<th><strong>UDP</strong></th>
</tr>
</thead>
<tbody><tr>
<td>연결 방식</td>
<td>연결형 서비스</td>
<td>비연결형 서비스</td>
</tr>
<tr>
<td>패킷 교환 방식</td>
<td>가상 회선 방식</td>
<td>데이터그램 방식</td>
</tr>
<tr>
<td>전송 순서</td>
<td>전송 순서 보장</td>
<td>전송 순서가 바뀔 수 있음</td>
</tr>
<tr>
<td>수신 여부 확인</td>
<td>수신 여부를 확인함</td>
<td>수신 여부를 확인하지 않음</td>
</tr>
<tr>
<td>통신 방식</td>
<td>1:1 통신</td>
<td>1:1 OR 1:N OR N:N 통신</td>
</tr>
<tr>
<td>신뢰성</td>
<td>높다</td>
<td>낮다</td>
</tr>
<tr>
<td>속도</td>
<td>느리다</td>
<td>빠르다</td>
</tr>
<tr>
<td>사용처</td>
<td>통신의 안정성 및 순차적인 전달이 필요한 경우</td>
<td>오류 검사 및 수정이 필요 없는 경우 (DNS, 스트리밍, 온라인 게임)</td>
</tr>
</tbody></table>
<blockquote>
<p>DNS (Domain Name System)란?
인터넷에서는 컴퓨터를 식별하기 위해 IP 주소를 사용하는데, 호스트의 도메인 이름을 호스트의 IP 주소로 바꾸거나 그 반대의 변환을 수행하는 시스템이다.</p>
</blockquote>
<h3 id="dns-동작-원리">DNS 동작 원리</h3>
<ol>
<li>브라우저에 도메인을 입력한다.</li>
<li>컴퓨터는 컴퓨터 내부에 등록되어 있는 DNS 서버로 도메인에 해당되는 IP 주소를 물어본다.</li>
<li>DNS 서버는 해당 도메인의 IP를 알려준다.</li>
<li>컴퓨터는 IP 주소에 해당하는 컴퓨터에 접속하게 된다.</li>
</ol>
<br>

<h2 id="http-통신-과정">HTTP 통신 과정</h2>
<ol>
<li>주소창에 URL을 입력 후 엔터를 치면 URL을 해석</li>
<li>DNS 서버에 조회하여 IP 주소로 변환</li>
<li>IP를 찾아 해당 IP가 존재하는 서버로 이동</li>
<li>ARP (Address Resolution Protocol)을 이용하여 MAC 주소로 변환</li>
<li>웹 서버와 TCP 연결 시도</li>
<li>서버에 요청을 하고 응답 반환</li>
<li>연결 종료</li>
</ol>
<h3 id="1-url-입력">1. URL 입력</h3>
<p><code>http://www.google.com/path</code> 를 치게 되면, 먼저 URL을 해석하는 과정을 거친다.</p>
<ul>
<li><code>http://</code> : 통신에 사용된 프로토콜</li>
<li><code>google.com</code> : 서버의 도메인</li>
<li><code>www</code> : 서버의 서브 도메인</li>
<li><code>path</code> : 요청 경로</li>
</ul>
<h3 id="2-dns-서버에-조회하여-ip-주소로-변환">2. DNS 서버에 조회하여 IP 주소로 변환</h3>
<p>URL을 입력하면, 가장 먼저 URL과 연결된 DNS 서버로 이동하여 IP 주소를 찾는다.
만약 IP가 로컬 DNS에 캐시로 남아 있다면, DNS 서버에 접근하지 않고, 바로 요청을 보낼 수 있다.</p>
<h3 id="3-ip를-찾아-해당-ip가-존재하는-서버로-이동">3. IP를 찾아 해당 IP가 존재하는 서버로 이동</h3>
<p>정확한 좌표값을 얻기 위해 고유 값인 MAC 주소를 활용하여 이동한다. 해당 IP를 찾아 이동하는 과정은 여러 라우터를 거쳐서 호스트를 찾게 되는데, 이 과정에서 동적 라우팅 프로토콜이 적용되어 라우팅 테이블에서 현재 경로를 따라 자동으로 경로를 조절하는 과정을 거친다. 여러 대의 컴퓨터와 네트워크 기기를 중계해서 상대방에게 도착한다. 그렇게 중계하는 동안 다음으로 중계할 곳의 MAC 주소를 사용하여 목적지를 찾아가게 된다.</p>
<h3 id="4-arpaddress-resolution-protocol을-이용하여-mac-주소-반환">4. ARP(Address Resolution Protocol)을 이용하여 MAC 주소 반환</h3>
<blockquote>
<p>ARP(Address Resolution Protocol)란, 주소 결정 프로토콜로 네트워크 상에서 IP 주소를 물리적 네트워크(MAC) 주소로 대응시키기 위해 사용되는 프로토콜이다.</p>
</blockquote>
<p>IP주소는 컴퓨터 네트워크에서 장치들이 서로 인식하고 통신하기 위해 사용 되는 번호이다. 규칙에 의해 만들어진 값으로 언제든지 변할 수 있는 값이다.
반면 MAC 주소는 네트워크 세그먼트 데이터 링크 계층에서 통신을 위한 인터페이스에 할당된 고유 식별자이다. MAC 주소는 IP 주소와 달리 고유 주소이기 때문에 서버의 실제 위치를 알기 위해서 MAC 주소가 필요하다.</p>
<h3 id="5-웹-서버와-tcp-연결-시도">5. 웹 서버와 TCP 연결 시도</h3>
<blockquote>
<p>TCP(Trasmission Control Protocol)는 컴퓨터와 데이터 통신을 위한 규약이다.</p>
</blockquote>
<p>클라이언트와 서버가 TCP 연결을 하려면 3-way handshake 과정을 거친다. 연결을 성공하면 현재 통신이 연결되어 있음을 보장한다.</p>
<h4 id="3-way-handshake">3-way handshake</h4>
<p>TCP 네트워크에서 통신하는 장치가 서로 연결이 잘 됐는지 확인하는 방법이다. 송신자와 수신자는 총 3번에 걸쳐 데이터를 주고 받으며 통신이 가능한 상태인지 확인한다.</p>
<ol>
<li>SYN(synchronize sequence numbers): 클라이언트가 서버로 임의의 시퀀스 번호를 전달  </li>
<li>SYN-ACK: 서버는 클라이언트가 서버로 전달한 시퀀스에 1을 더하여 클라이언트로 전달  </li>
<li>ACK(acknowledgement): 클라이언트는 서버에서 전달해준 시퀀스 + 1하여 다시 서버로 전달</li>
</ol>
<h3 id="6-서버에-요청을-하고-응답-반환">6. 서버에 요청을 하고 응답 반환</h3>
<p>클라이언트가 요청을 보내면, 서버는 그에 맞는 데이터와 상태를 응답한다.</p>
<h3 id="7-연결-종료">7. 연결 종료</h3>
<p>TCP 통신 종료를 위해 4-way handshake 과정을 거친다.</p>
<h4 id="4-way-handshake">4-way handshake</h4>
<p>TCP 네트워크에서 통신하는 장치의 연결을 해제하는 방법이다.  총 4번에 걸쳐 데이터를 주고 받으며 연결을 끊는다</p>
<ol>
<li>클라이언트가 연결을 종료하겠다는 FIN플래그를 전송한다.</li>
<li>서버는 일단 확인메시지를 보내고 자신의 통신이 끝날때까지 기다리는데 이 상태가 TIME_WAIT 상태다.</li>
<li>서버가 통신이 끝났으면 연결이 종료되었다고 클라이언트에게 FIN플래그를 전송한다.</li>
<li>클라이언트는 확인했다는 메시지를 보낸다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Redis 캐싱 전략]]></title>
            <link>https://velog.io/@joo-chang/Redis-%EC%BA%90%EC%8B%B1-%EC%A0%84%EB%9E%B5</link>
            <guid>https://velog.io/@joo-chang/Redis-%EC%BA%90%EC%8B%B1-%EC%A0%84%EB%9E%B5</guid>
            <pubDate>Wed, 08 Jan 2025 08:34:37 GMT</pubDate>
            <description><![CDATA[<p>Redis를 활용하여 애플리케이션 성능과 확장성을 높이려면, 단순히 &#39;어떤 데이터를 캐싱할지&#39; 결정하는 것을 넘어, 캐시 전략을 체계적으로 수립해야 된다.</p>
<p>Redis 캐싱 전략을 구성할 때 주로 고려하는 요소와 대표적인 패턴 및 기법들을 알아보자.</p>
<h2 id="캐싱-기본-개념">캐싱 기본 개념</h2>
<h3 id="핫-데이터">핫 데이터</h3>
<ul>
<li>자주 조회되는 데이터를 캐시에 저장해 DB 부담을 덜어주고, 응답 속도를 개선한다.</li>
<li>캐시 적중률이 높을수록 이점이 커지며, 적중률이 낮다면 불필요한 메모리만 차지하게 될 수 있다.</li>
</ul>
<h3 id="key-value-저장">Key-Value 저장</h3>
<ul>
<li>Redis는 문자열, 해시, 리스트, 셋 등 다양한 구조를 지원하지만, 기본적으로 Key-Value 기반으로 동작한다.</li>
</ul>
<h3 id="ttl-time-to-live">TTL (Time To Live)</h3>
<ul>
<li>캐시된 데이터의 만료 시간을 설정해두면, 유효 기간이 지난 데이터는 자동으로 제거된다.</li>
<li>이를 통해 오래된 데이터의 불일치 및 메모리 누적 문제를 방지한다.</li>
</ul>
<blockquote>
<p>Cache Hit : 캐시 스토어(Redis)에 데이터가 있을 경우 바로 가져온다.
Cache Miss : 캐시 스토어에 데이터가 없을 경우 DB에서 가져온다.</p>
</blockquote>
<hr>
<h2 id="캐싱-전략">캐싱 전략</h2>
<h3 id="cache-aside">Cache Aside</h3>
<blockquote>
<p>가장 흔히 쓰이는 캐싱 패턴이다. 
애플리케이션이 먼저 캐시에 접근하고, 데이터가 없으면 DB에서 읽어온 뒤 캐시에 넣는다. 
쓰기 시에는 DB를 갱신하고, 캐시를 무효화(delete)하는 방법이 주로 사용된다.</p>
</blockquote>
<ul>
<li>반복적인 읽기가 많은 호출에 적합하다.</li>
<li>캐시와 DB가 분리되어 있기 때문에 원하는 데이터만 별도로 구성하여 캐시에 저장할 수 있고, 캐시 장애 대비 구성이 되어 있다.</li>
<li>Redis가 다운되더라도 DB에서 데이터를 가져올 수 있으므로 서비스에 문제가 없다.</li>
</ul>
<h3 id="read-through">Read-Through</h3>
<ul>
<li>캐시에 요청이 들어왔을 때 캐시가 DB에 직접 접근하여 값을 가져와서 캐시에 저장 후 반환한다. (읽기에 초점)</li>
<li>사용자가 직접 DB를 조회하지 않고, 캐시 계층이 DB 연동 로직을 책임진다.</li>
<li>따라서 데이터를 조회하는데 전체적으로 속도가 느리다.</li>
<li>또한, 데이터 조회를 전적으로 캐시에 의지하므로, Redis가 다운될 경우 서비스 이용에 차질이 생길 수 있다.</li>
</ul>
<h3 id="write-through">Write-Through</h3>
<ul>
<li>캐시에 쓰기를 하면, 캐시가 DB에도 동기적으로 쓰기를 하는 방식 (쓰기에 초점)</li>
<li>Read-Though 와 마찬가지로 DB 동기화 작업을 캐시에 위임한다.</li>
<li>데이터 유실이 발생하면 안되는 상황에 적합하다.</li>
<li>매 요청마다 두번의 Write가 발생하게 됨으로써 빈번한 생성, 수정이 발생하는 서비스에서는 성능 이슈가 발생할 수 있다.</li>
</ul>
<h3 id="write-behind-write-back">Write-Behind (Write-Back)</h3>
<blockquote>
<p>데이터 변경 시 즉시 DB에 반영하지 않고, 캐시에 먼저 쓰고 일정 시간이 지난 후 비동기로 DB에 반영</p>
</blockquote>
<ul>
<li>빈번한 쓰기를 묶어서 한번에 DB에 적용 가능(배치), 높은 쓰기 성능을 기대할 수 있다.</li>
<li>Redis가 장애나면 아직 DB에 반영되지 않은 데이터가 유실될 위험이 있다.</li>
<li>분산 환경에서 동시성 이슈가 복잡해질 수 있다.</li>
</ul>
<h3 id="write-around">Write Around</h3>
<ul>
<li>쓰기 로직을 처리할 때, 쓰기 연산을 캐시에 직접 반영하지 않고, 오직 DB에만 데이터를 기록</li>
<li>캐시에 들어있는 내용은 무효화(delete or 아무 조치 안함)</li>
<li>쓰기는 DB 위주로 처리하고, 캐시는 주로 읽기를 가속하기 위한 용도로만 사용한다는 것이 핵심.</li>
</ul>
<h2 id="cache-aside-와-write-around-차이">Cache Aside 와 Write Around 차이</h2>
<ul>
<li>Cache Aside는 &quot;읽기 시 캐시가 없으면 DB에서 가져와 적재, 쓰기 시 DB를 업데이트하고 캐시 무효화&quot;.</li>
<li>Write Around는 &quot;쓰기 시 DB에만 쓰고, 캐시를 무효화할지 말지 선택&quot;</li>
</ul>
<h3 id="결론">결론</h3>
<ul>
<li>Write Around는 캐시를 건드리지 않음으로써 쓰기 성능을 높이고, 대신 재조회 시 캐시 미스를 허용하므로 실시간 일관성은 다소 낮을 수 있다.</li>
<li>Cache Asdie는 쓰기 시점에 캐시도 무효화/갱신 하기 때문에 일관성을 좀 더 강조한다.</li>
<li>프로젝트 상황(데이터 변경 후 즉시 재조회 빈도, 쓰기 부하, 캐시 히트율 목표, 전체 트래픽 구조 등)에 따라, 어느 쪽을 택할지(혹은 혼합할지) 결정하면 된다.</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[정렬 알고리즘 종류와 가장 좋은(?) 정렬은 무엇일까?]]></title>
            <link>https://velog.io/@joo-chang/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%A2%85%EB%A5%98%EC%99%80-%EA%B0%80%EC%9E%A5-%EC%A2%8B%EC%9D%80-%EC%A0%95%EB%A0%AC%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</link>
            <guid>https://velog.io/@joo-chang/%EC%A0%95%EB%A0%AC-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%A2%85%EB%A5%98%EC%99%80-%EA%B0%80%EC%9E%A5-%EC%A2%8B%EC%9D%80-%EC%A0%95%EB%A0%AC%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</guid>
            <pubDate>Tue, 07 Jan 2025 09:21:38 GMT</pubDate>
            <description><![CDATA[<p>정렬 알고리즘은 데이터 집합을 오름차순 또는 내림차순으로 재배열하는 과정에서 사용되며, <strong>시간 복잡도</strong>, <strong>메모리 사용량</strong>, <strong>정렬 안정성</strong> 등의 기준에 따라 여러 가지 종류가 존재한다.</p>
<p>가장 중요한 퀵 정렬, 병합 정렬, 힙 정렬만 보자.</p>
<h2 id="대표적인-정렬-알고리즘">대표적인 정렬 알고리즘</h2>
<h3 id="퀵-정렬-quick-sort">퀵 정렬 (Quick Sort)</h3>
<blockquote>
<p>피벗을 중심으로 작은 값과 큰 값을 분할하고, 이를 재귀적으로 반복</p>
</blockquote>
<ul>
<li>시간 복잡도 : 평균 O(nlogn), 최악의 경우 O(n^2)</li>
<li>장점 : 평균적으로 매우 빠르며, 구현이 비교적 간단하다.</li>
<li>단점 : 피벗 선택에 따라 최악의 경우가 발생할 수 있다.</li>
</ul>
<h3 id="병합-정렬-merge-sort">병합 정렬 (Merge Sort)</h3>
<blockquote>
<p>리스트를 절반으로 분할하고, 각각을 재귀적으로 정렬한 뒤 병합</p>
</blockquote>
<ul>
<li>시간 복잡도 : 최선, 평균, 최악 모두 O(nlogn)</li>
<li>장점 : 안정적 정렬이며, 시간 복잡도도 일정하다.</li>
<li>단점 : 추가 메모리 공간이 필요 (일반적으로 O(n))</li>
</ul>
<h3 id="힙-정렬heap-sort">힙 정렬(Heap Sort)</h3>
<blockquote>
<p>최대 힙(or 최소 힙)을 구성해 가장 큰(or 작은) 원소부터 차례대로 추출</p>
</blockquote>
<ul>
<li>시간 복잡도 : 평균, 최악 모두 O(nlogn)</li>
<li>장점 : 추가 메모리 공간 없이 일정한 시간 복잡도를 보인다.</li>
<li>단점 : 내부 구현이 다소 복잡하며, 대부분의 경우 퀵 정렬보다 실제 수행 시간이 느린 편이다.</li>
</ul>
<hr>
<h2 id="가장-좋은-정렬-알고리즘은">가장 좋은 정렬 알고리즘은?</h2>
<h3 id="좋다의-기준은-뭘까">좋다의 기준은 뭘까?</h3>
<ul>
<li>시간 복잡도 : 평균적으로 빠른 알고리즘을 선호하지만, 데이터 패턴에 따라 달라짐.</li>
<li>공간 복잡도 : 정렬 과정에서 추가적인 메모리 사용이 큰 문제가 되는 환경이라면, 제자리 정렬(In-Place Sort)이 필요할 수 있음.</li>
<li>안정성 : 정렬 시 같은 값의 상대적 순서를 유지해야 한다면, 병합 정렬처럼 안정적인 정렬 알고리즘이 유리함.</li>
<li>데이터 패턴 및 특성 : 이미 거의 정렬된 상태인지, 무작위인지, 중복 값이 많은지 등에 따라 적합한 알고리즘이 달라짐.</li>
</ul>
<h3 id="일반적인-권장-사항">일반적인 권장 사항</h3>
<ul>
<li>무작위 데이터, 평균적으로 빠른 속도 : 퀵 정렬<ul>
<li>평균적으로 O(nlogn)이라는 빠른 속도를 갖고, 구현 난이도도 적당히 낮음.</li>
</ul>
</li>
<li>안정성과 일정한 성능 : 병합 정렬<ul>
<li>최선·평균·최악이 O(nlogn)으로 일정하고, 정렬 안정성이 필요한 경우에 적합.</li>
</ul>
</li>
<li>추가 공간 절감 : 힙 정렬<ul>
<li>제자리 정렬로, 외부 공간을 최소화해야 할 때 활용.</li>
</ul>
</li>
</ul>
<h2 id="결론">결론</h2>
<p>데이터 크기, 메모리 사용 제약, 정렬 안정성 요구사항, 데이터의 분포 특성 등 다양한 요소를 종합적으로 고려해야 한다. 예를 들어, 이미 거의 정렬된 상태에서는 삽입 정렬이 퀵 정렬보다 빠를 수 있다. 또한, 서버 환경에서 대규모 데이터를 처리한다면, 병합 정렬이나 힙 정렬 등 O(nlogn) 알고리즘이 기본적으로 안전하고 빠른 결과를 보장할 수 있다.</p>
<p>결론적으로, 특정 정렬 알고리즘이 무조건 최고라고 단정 지을 수는 없으며, 데이터의 특성과 환경에 따라 최적의 알고리즘을 선택하는 것이 중요하다. 필요하다면, 작은 규모 데이터에 대해 테스트나 실제 성능 측정을 통해 어떤 알고리즘이 가장 효율적인지를 판단하는 접근이 권장된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[서비스 디스커버리 (Eureka)]]></title>
            <link>https://velog.io/@joo-chang/%EC%84%9C%EB%B9%84%EC%8A%A4-%EB%94%94%EC%8A%A4%EC%BB%A4%EB%B2%84%EB%A6%AC-Eureka</link>
            <guid>https://velog.io/@joo-chang/%EC%84%9C%EB%B9%84%EC%8A%A4-%EB%94%94%EC%8A%A4%EC%BB%A4%EB%B2%84%EB%A6%AC-Eureka</guid>
            <pubDate>Mon, 16 Dec 2024 02:54:24 GMT</pubDate>
            <description><![CDATA[<h3 id="서비스-디스커버리란">서비스 디스커버리란?</h3>
<ul>
<li>MSA에서 각 서비스의 위치를 동적으로 관리하고 찾아주는 기능</li>
<li>각 서비스는 등록 서버(Eureka)에 자신의 위치를 등록하고, 이를 조회하여 통신</li>
<li>서비스 등록, 서비스 조회, 헬스 체크 등의 기능</li>
</ul>
<br>

<h3 id="eureka란">Eureka란?</h3>
<ul>
<li>넷플릭스가 개발한 서비스 디스커버리 서버로, MSA에서 각 서비스의 위치를 동적으로 관리</li>
<li>모든 서비스 인스턴스의 위치를 저장하는 중앙 저장소 역할, 서비스의 상태를 주기적으로 확인하여 가용성 보장</li>
<li>여러 인스턴스를 지원하여 고가용성을 유지할 수 있음</li>
</ul>
<h4 id="eureka-서버-설정">Eureka 서버 설정</h4>
<ul>
<li>Eureka 서버는 서비스 레지스트리를 구성하는 중앙 서버</li>
</ul>
<pre><code class="language-yml">server:
  port: 8761

eureka:
  client:
    register-with-eureka: false  # 다른 Eureka 서버에 이 서버를 등록하지 않음
    fetch-registry: false  # 다른 Eureka 서버의 레지스트리를 가져오지 않음
  server:
    enable-self-preservation: false  # 자기 보호 모드 비활성화</code></pre>
<ul>
<li>위 설정을 통해 Eureka 서버 구성, 클라이언트가 등록할 수 있도록 준비</li>
</ul>
<h4 id="eureka-클라이언트-설정">Eureka 클라이언트 설정</h4>
<ul>
<li>각 서비스는 Eureka 서버에 자신을 등록해야 함</li>
</ul>
<pre><code class="language-yml">spring:
  application:
    name: my-service

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/  # Eureka 서버 URL
    register-with-eureka: true  # Eureka 서버에 등록
    fetch-registry: true  # Eureka 서버로부터 레지스트리 정보 가져오기
  instance:
    hostname: localhost  # 클라이언트 호스트 이름
    prefer-ip-address: true  # IP 주소 사용 선호
    lease-renewal-interval-in-seconds: 30  # 리스 갱신 간격
    lease-expiration-duration-in-seconds: 90  # 리스 만료 기간</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Cloud Gateway]]></title>
            <link>https://velog.io/@joo-chang/Spring-Cloud-Gateway</link>
            <guid>https://velog.io/@joo-chang/Spring-Cloud-Gateway</guid>
            <pubDate>Mon, 09 Dec 2024 06:43:48 GMT</pubDate>
            <description><![CDATA[<h2 id="spring-cloud-gateway란">Spring Cloud Gateway란?</h2>
<ul>
<li>Spring Cloud Gateway는 Spring 프로젝트의 일환으로 개발된 API 게이트웨이로, 클라이언트 요청을 적절한 서비스로 라우팅하고 다양한 필터링 기능을 제공한다.</li>
<li>Spring Cloud Netflix 패키지의 일부로, MSA에서 널리 사용된다.</li>
</ul>
<h2 id="특징">특징</h2>
<ul>
<li>동적 라우팅 : 요청의 URL 패턴에 따라 동적으로 라우팅</li>
<li>필터링 : 요청 전후에 다양한 작업을 수행할 수 있는 필터 체인 제공</li>
<li>모니터링 : 요청 로그 및 메트릭을 통해 서비스 상태 모니터링</li>
<li>보안 : 요청의 인증 및 권한 검증</li>
</ul>
<h2 id="사용-사례">사용 사례</h2>
<ul>
<li>API 집합 관리 : 여러 마이크로서비스의 엔드포인트를 단일 진입점으로 통합하여 관리</li>
<li>보안 강화 : 인증 및 인가를 중앙 집중화하여 각 마이크로서비스의 보안 부담을 줄임</li>
<li>트래픽 제어 : 요청률 제한, 회로 차단기 등의 기능을 통해 시스템 안정성 유지</li>
<li>모니터링 및 로깅 : 통합된 로깅 및 모니터링을 통해 시스템 상태를 실시간으로 파악</li>
</ul>
<h2 id="spring-cloud-gateway-vs-netflix-zuul">Spring Cloud Gateway vs Netflix Zuul</h2>
<ul>
<li>성능 : Spring Cloud Gateway는 비동기적이고 논블로킹 방식으로 설계되어 Zuul1보다 더 높은 성능을 제공한다.</li>
<li>기능 : Spring Gateway는 WebFlux를 기반으로 하여 리액티브 스트림을 지원하며, Zuul 2는 아직 널리 사용되지 않는다.</li>
<li>커뮤니티 및 지원 : Spring Cloud Gateway는 Spring 생태계와 긴밀하게 통합되어 있어 Spring 개발자에게 친숙한다.</li>
</ul>
<h2 id="spring-cloud-gateway-vs-kong-nginx-등">Spring Cloud Gateway vs Kong, NGINX 등</h2>
<ul>
<li>통합성 : Spring Cloud Gateway는 Spring 생태계와의 통합성이 뛰어나지만, Kong이나 NGINX 는 독립적인 API 게이트웨이 솔루션으로 더 다양한 언어와 프레임워크와의 호환성을 가진다.</li>
<li>확장성 : Kong과 NGINX는 고성능의 트래픽 처리를 위해 최적화되어 있으며, 대규모 분산 환경에서 유리할 수 있다.</li>
</ul>
<h2 id="주요-구성-요소">주요 구성 요소</h2>
<ol>
<li>Route<ul>
<li>특정 조건에 맞는 요청을 지정된 목적지로 전달하는 규칙</li>
<li>각 라우트는 ID, 대상 URI, 필터 등의 속성을 가진다.</li>
</ul>
</li>
<li>Filter<ul>
<li>요청 또는 응답을 가로채 처리하는 컴포넌트</li>
<li>Pre-filter와 Post-filter로 나뉜다.</li>
</ul>
</li>
<li>Predicate<ul>
<li>라우팅 조건을 정의하는 논리</li>
<li>예) 특정 경로, 헤더 값, 쿼리 파라미터 등이 일치하는지 검사</li>
</ul>
</li>
</ol>
<h2 id="결론">결론</h2>
<p>Spring Cloud Gateway는 MSA에서 유연하고 강력한 API 게이트웨이를 구축할 수 있는 도구로, Spring 생태계와의 뛰어난 통합성을 바탕으로 다양한 기능을 제공한다. 비동기적이고 논블로킹 방식의 처리 모델을 통해 높은 성능과 확장성을 제공하며, 라우팅, 필터링, 보안, 부하 분산 등 API 게이트웨이에 필요한 핵심 기능을 손쉽게 구현할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Redis Sentinel, Cluster 차이]]></title>
            <link>https://velog.io/@joo-chang/Redis-Sentinel-Cluster-%EC%B0%A8%EC%9D%B4</link>
            <guid>https://velog.io/@joo-chang/Redis-Sentinel-Cluster-%EC%B0%A8%EC%9D%B4</guid>
            <pubDate>Tue, 03 Dec 2024 08:52:23 GMT</pubDate>
            <description><![CDATA[<p>Redis는 단일 인스턴스로도 운영 가능하지만 물리 머신이 가진 메모리 한계를 초과하는 데이터를 저장하고 싶거나, failover에 대한 처리를 통해 고가용성을 보장하려면 Sentinel이나 Cluster 등의 운영 방식을 선택해서 사용해야 한다.</p>
<blockquote>
<p>Failover : 장애 조치 기능으로 시스템 장애 이벤트 발생 시 하나의 예비 백업 시스템으로 자동 전환되는 것을 말한다.</p>
</blockquote>
<h2 id="sentinel">Sentinel</h2>
<ul>
<li>모니터링 : Master/Slave 동작 감시</li>
<li>자동 장애 조치 (Automatic Failover)</li>
<li>알림 : failover 됐을 때, client에게 알릴 수 있다.</li>
</ul>
<h3 id="구성-요소">구성 요소</h3>
<ul>
<li>최소 3개 이상의 Sentinel 인스턴스를 권장하여 장애 발생 시 합의를 통해 안정적인 장애 조치가 이루어지도록 한다.</li>
<li>Master-Slave 구조 : 하나의 마스터와 다수의 슬레이브로 구성되며, Sentinel이 마스터의 상태를 관리한다.</li>
</ul>
<h3 id="동작-방식">동작 방식</h3>
<ul>
<li>Sentinel 인스턴스 과반수 이상이 Master 장애를 감지하면 Slave 중 하나를 Master로 승격시키고 기존의 Master는 Slave로 강등시킨다.</li>
<li>Slave가 여러 개 있을 경우 Slave가 새로운 Master로부터 데이터를 받을 수 있도록 재구성 된다.</li>
</ul>
<h3 id="장점">장점</h3>
<ul>
<li>고가용성 보장 : 마스터 서버 장애 시 자동으로 슬레이브를 마스터로 승격시켜 서비스 중단 시간을 최소화한다.</li>
<li>유연성 : 기존의 Master-Slave 구조를 활용하여 고가용성을 강화할 수 있다.</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li>확장성 한계 : Sentinel 자체를 클러스터링을 지원하지 않기 때문에, 수평적 확장에 한계가 있다.</li>
<li>복잡한 관리 : Sentinel 인스턴스 수와 구성을 관리하는 데 추가적인 노력이 필요하다.</li>
</ul>
<hr>
<h2 id="cluster">Cluster</h2>
<p>Redis Cluster는 Redis의 수평적 확장성과 고가용성을 동시에 제공하기 위한 분산 시스템이다. 클러스터는 데이터를 여러 노드에 분산 저장하고, 각 노드가 마스터와 슬레이브 역할을 수행하여 데이터의 복제와 장애 조치를 관리한다.</p>
<h3 id="주요-기능">주요 기능</h3>
<ul>
<li>데이터 분산</li>
<li>고가용성 : 각 마스터 노드는 하나 이상의 슬레이브 노드를 가지며, 마스터 노드 장애 시 슬레이브노드 중 하나를 새로운 마스터로 승격시킨다.</li>
<li>자동 장애 조치(Failover) : 클러스터 내에서 마스터 노드의 장애를 감지하고, 자동으로 슬레이브 노드를 마스터로 승격시켜 클러스터의 안정성을 유지한다.</li>
<li>데이터 재분배 : 노드 추가/제거 시 데이터 슬롯을 재분배하여 클러스터의 균형을 맞춘다.</li>
</ul>
<h3 id="구성-요소-1">구성 요소</h3>
<ul>
<li>노드 : 클러스터를 구성하는 개별 Redis 인스턴스로 마스터와 슬레이브 역할을 수행한다.</li>
<li>Master-Slave : 각 마스터 노드는 하나 이상의 슬레이브 노드를 가지며, 데이터 복제를 통해 고가용성을 보장한다.</li>
</ul>
<h3 id="장점-1">장점</h3>
<ul>
<li>수평적 확장 : 클러스터링을 통해 데이터와 트래픽을 여러 노드에 분산시켜 성능을 향상시킬 수 있다.</li>
<li>고가용성</li>
<li>자체적인 데이터 분산 : 데이터 분산과 복제를 자동으로 관리한다.</li>
</ul>
<h3 id="단점-1">단점</h3>
<ul>
<li>복잡한 설정</li>
<li>제한된 명령어 : 특정 명령어는 제한될 수 있다.</li>
<li>데이터 일관성 : 여러 노드에 데이터가 분산 저장되기 때문에, 일부 상황에서 데이터 일관성 관리가 복잡해질 수 있다.</li>
</ul>
<hr>
<h2 id="그래서-뭘-써야-될까">그래서 뭘 써야 될까?</h2>
<h3 id="sentinel-1">Sentinel</h3>
<ul>
<li>고가용성만이 주요 요구 사항일 때 : 단일 마스터-슬레이브 구조로도 충분한 고가용성 제공</li>
<li>데이터 분산이 필요 없을 때 : 데이터가 한 노드에 집중돼도 문제 없는 경우</li>
<li>소규모 or 중간 규모</li>
</ul>
<h3 id="cluster-1">Cluster</h3>
<ul>
<li>수평적 확장과 높은 처리량이 필요할 때 : 데이터와 트래픽을 여러 노드로 분산시켜 성능 향상이 필요한 경우</li>
<li>대규모 데이터 저장 : 단일 노드에 저장하기 어려운 경우</li>
<li>자동 데이터 분산이 필요할 때 : 별도 데이터 분산 로직 없이 Redis가 자체적으로 데이터 분산 관리하고 싶은 경우</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[쿼리 조회 성능 개선]]></title>
            <link>https://velog.io/@joo-chang/%EC%BF%BC%EB%A6%AC-%EC%A1%B0%ED%9A%8C-%EC%84%B1%EB%8A%A5-%EA%B0%9C%EC%84%A0</link>
            <guid>https://velog.io/@joo-chang/%EC%BF%BC%EB%A6%AC-%EC%A1%B0%ED%9A%8C-%EC%84%B1%EB%8A%A5-%EA%B0%9C%EC%84%A0</guid>
            <pubDate>Tue, 12 Nov 2024 03:13:32 GMT</pubDate>
            <description><![CDATA[<h2 id="쿼리-조회-시-여러-번-조회와-한번에-조회하는-것-차이">쿼리 조회 시 여러 번 조회와 한번에 조회하는 것 차이</h2>
<h3 id="여러-번-개별-조회">여러 번 개별 조회</h3>
<ul>
<li>쿼리 횟수 증가 : 데이터가 많을 수록 쿼리 횟수가 기하급수적으로 증가. 데이터베이스 부하 증가</li>
<li>네트워크 지연 : 각 쿼리마다 네트워크 왕복 시간이 소요되어 전체 응답 시간이 길어짐</li>
<li>트랜잭션 관리 오버헤드 : 각 쿼리에 대해 트랜잭션을 관리해야 하므로 오버헤드 발생</li>
</ul>
<h3 id="한번에-조회">한번에 조회</h3>
<ul>
<li>쿼리 횟수 감소 : 단일 쿼리로 다수의 쿼리를 조회하여 데이터베이스 부하 감소</li>
<li>응답 시간 단축 : 네트워크 왕복 횟수가 줄어들어 전체 응답 시간 개선</li>
<li>트랜잭션 효율성 : 하나의 트랜잭션으로 여러 작업을 처리하여 시스템 자원을 효율적으로 사용</li>
</ul>
<h2 id="쿼리-수를-줄이는게-무조건-좋은가">쿼리 수를 줄이는게 무조건 좋은가?</h2>
<h3 id="장점">장점</h3>
<ul>
<li>네트워크 비용 절감</li>
<li>DB 부하 감소</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li>쿼리 복잡성 증가</li>
<li>데이터 양이 많을 경우 메모리 사용량이 급증하여 성능이 저하될 수 있음 (페이징 처리하면 어느정도 해결 가능)</li>
<li>여러 쿼리를 결합해서 하나의 트랜잭션으로 처리하면 원자성을 보장할 수 있지만, 실패 시 롤백 비용이 커질 수 있음</li>
</ul>
<hr>
<h2 id="1n-문제-개선">1+N 문제 개선</h2>
<p>결제 내역 조회 시</p>
<p>결제 내역 건당 해당하는 concert 정보를 가져와야 하므로 concert 서비스로 건당 요청을 보내게 됨</p>
<p><img src="https://velog.velcdn.com/images/joo-chang/post/39c43311-b12e-4992-93c2-c91143a9d6d1/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/joo-chang/post/0372a17f-8824-40c5-a9f9-27ac32d92982/image.png" alt=""></p>
<p>결제 row 하나당 concert 조회 쿼리가 한 개 발생하기 때문에 매우 비효율적임</p>
<p>조회할 concertId를 뽑아서 한번에 조회해오는 방식으로 변경</p>
<p><img src="https://velog.velcdn.com/images/joo-chang/post/f1da02be-34ce-40b2-9c1b-a23c770fd967/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/joo-chang/post/992c93e3-1f88-4369-b2b9-e6636ff862c8/image.png" alt=""></p>
<p>쿼리가 한개만 나가는 것을 확인</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[보상 트랜잭션]]></title>
            <link>https://velog.io/@joo-chang/%EB%B3%B4%EC%83%81-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98</link>
            <guid>https://velog.io/@joo-chang/%EB%B3%B4%EC%83%81-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98</guid>
            <pubDate>Fri, 01 Nov 2024 07:44:45 GMT</pubDate>
            <description><![CDATA[<h2 id="보상-트랜잭션이란">보상 트랜잭션이란?</h2>
<p>MSA에서 보상 트랜잭션은 분산 시스템에서 여러 서비스 간의 데이터 일관성을 유지하기 위한 기법이다.
일반적으로 MSA는 각 서비스가 독립적으로 배포되고 운영될 수 있도록 설계되어 있기 때문에, 하나의 서비스에서 트랜잭션이 발생하더라도 다른 서비스에는 영향을 미치지 않는 경우가 많다.
이로 인해 데이터 일관성을 유지하기 위한 방법이 필요하다. 보상 트랜잭션은 이런 요구를 해결하기 위한 방법이다.</p>
<h3 id="정의">정의</h3>
<ul>
<li>보상 트랜잭션은 이전에 수행된 트랜잭션의 영향을 취소하기 위해 실행되는 추가적인 트랜잭션이다.</li>
<li>원래 트랜잭션이 실패하거나, 후속 프로세스에서 롤백이 필요할 때 사용된다.</li>
</ul>
<h3 id="사용-상황">사용 상황</h3>
<ul>
<li>MSA 환경에서 여러 서비스가 순차적으로 호출되는 과정에서 하나의 서비스에서 오류가 발생했을 때, 이미 성공한 서비스의 작업을 취소하거나 보상해야할 때 사용한다.</li>
</ul>
<h2 id="구현-방식">구현 방식</h2>
<h3 id="사전에-정의된-보상-트랜잭션">사전에 정의된 보상 트랜잭션</h3>
<ul>
<li>각 서비스는 명시적으로 보상 트랜잭션을 정의해야 하며, 성공적으로 수행되도록 구현해야 된다.</li>
</ul>
<h3 id="오케스트레이션">오케스트레이션</h3>
<ul>
<li>보상 트랜잭션을 관리하기 위해 오케스트레이터를 사용할 수 있다. 오케스트레이터는 각 서비스의 상태를 추적하고, 필요할 때 보상 트랜잭션을 호출한다.</li>
</ul>
<h3 id="상태-관리">상태 관리</h3>
<ul>
<li>각 서비스는 자신의 상태를 관리해야 하며, 성공적인 트랜잭션과 보상 트랜잭션의 실행 여부를 명확하게 정의해야 한다.</li>
</ul>
<h2 id="장단점">장단점</h2>
<h3 id="장점">장점</h3>
<ul>
<li>데이터 일관성 유지 : 여러 서비스 간의 데이터 일관성을 효과적으로 유지할 수 있다.</li>
<li>비즈니스 로직 반영 : 실제 비즈니스 프로세스에 맞춘 복잡한 트랜잭션 처리가 가능하다.</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li>복잡성 증가 : 보상 트랜잭션을 구현하는 데 있어 복잡성이 증가</li>
<li>성능 문제 : 보상 트랜잭션이 추가적인 처리를 요구하므로 성능 저하가 발생할 수 있다.</li>
</ul>
<h2 id="saga">SAGA</h2>
<ul>
<li>MSA 환경에서 분산 트랜잭션을 관리하는 데 유용하게 사용된다.</li>
<li>여러 서비스 간의 트랜잭션을 관리하기 위해 각 서비스의 로컬 트랜잭션을 순차적으로 실행하고, 만약 중간에 실패가 발생하면 이를 보상하기 위해 이전 단계의 트랜잭션을 롤백하는 방식이다.</li>
</ul>
<h3 id="접근-방식">접근 방식</h3>
<h4 id="오케스트레이션-1">오케스트레이션</h4>
<ul>
<li>중앙 오케스트레이터가 모든 트랜잭션을 관리하고 조정한다. 트랜잭션이 순차적으로 진행되며, 오류 발생 시 보상 트랜잭션을 호출한다.</li>
<li>ex) 주문 처리 -&gt; 결제 -&gt; 배송 요청 등이 순차적으로 실행되는 구조.</li>
</ul>
<h4 id="코레오그래피">코레오그래피</h4>
<ul>
<li>각 서비스가 자신의 로컬 트랜잭션을 수행하고, 성공 여부에 따라 다음 서비스에 이벤트를 전파한다. 각 서비스는 이벤트를 수신하면 보상 트랜잭션을 정의하여 처리한다.</li>
<li>ex) 주문 서비스가 결제 서비스에 이벤트를 발송하고, 결제 서비스가 성공적으로 처리하면 배송 서비스에 이벤트를 발송하는 구조</li>
</ul>
<p>오케스트레이션은 로컬 트랜잭션의 실패 시 모든 연관된 트랜잭션을 명확하게 롤백하는 구조이며, 코레오그래피는 각 서비스가 독립적으로 보상 트랜잭션을 처리하여 롤백하는 구조이다. 
이러한 차이로 인해 각 방식은 특정 비즈니스 요구 사항에 따라 적합하게 선택해야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[권한 오류 발생 시 500 Error 반환되는 문제]]></title>
            <link>https://velog.io/@joo-chang/%EA%B6%8C%ED%95%9C-%EC%98%A4%EB%A5%98-%EB%B0%9C%EC%83%9D-%EC%8B%9C-500-Error-%EB%B0%98%ED%99%98%EB%90%98%EB%8A%94-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@joo-chang/%EA%B6%8C%ED%95%9C-%EC%98%A4%EB%A5%98-%EB%B0%9C%EC%83%9D-%EC%8B%9C-500-Error-%EB%B0%98%ED%99%98%EB%90%98%EB%8A%94-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Mon, 14 Oct 2024 03:21:14 GMT</pubDate>
            <description><![CDATA[<h3 id="문제-상황">문제 상황</h3>
<p>권한이 없는 API 요청 시 권한 오류 403이 반환 돼야 되는데 500 error가 발생</p>
<h3 id="해결-과정">해결 과정</h3>
<ol>
<li>권한 오류가 mvc 계층에서 난 오류여서 GlobalException 핸들러가 security exceptionhandler보다 먼저 exception을 가로채게 돼서 500 error 발생</li>
<li>Global Exception에 AccessDeniedException Handler 추가해줬으나 정상 작동하지 않음</li>
<li>500 error Handler에서 Security ExceptionHandler를 탈 수 있도록 다시 AccessDeniedException을 던져줌</li>
<li>그 과정에서 Security 의존성이 필요함을 깨달음</li>
<li>기존 AccessDeniedException Handler를 다시 보니 다른 라이브러리의 AccessDeniedException.class를 보고 있었음</li>
<li>Security를 굳이 가지 않고 AccessDeniedException Handler 추가하여 해결</li>
</ol>
<h3 id="해결-포인트">해결 포인트</h3>
<ul>
<li>Security 의존성 추가..</li>
</ul>
<p><img src="https://velog.velcdn.com/images/joo-chang/post/dbed5cf7-e163-478f-b06e-645bc0abc5a9/image.png" alt=""></p>
<hr>
<h3 id="추가-문제-발생">추가 문제 발생</h3>
<p><img src="https://velog.velcdn.com/images/joo-chang/post/a4cb05d5-284e-43ad-9107-068ab6ce469a/image.png" alt=""></p>
<ul>
<li>갑자기 요청에서 CSRF token cannot be found 오류 발생</li>
</ul>
<h3 id="원인">원인</h3>
<p>Spring Security가 의존성에 포함되면 기본적으로 CSRF 보호 기능이 활성화되기 때문</p>
<h3 id="해결-방법">해결 방법</h3>
<ul>
<li>security 모듈과 excetpion 모듈을 합쳐, security 의존성을 같이 사용하도록 변경</li>
<li>security와 exception 모듈을 분해하여 각 서비스에서 각각 처리하도록 변경</li>
</ul>
<p>###</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[대기열 시스템 Kafka & Redis]]></title>
            <link>https://velog.io/@joo-chang/%EB%8C%80%EA%B8%B0%EC%97%B4-%EC%8B%9C%EC%8A%A4%ED%85%9C-Kafka-Redis-9bnfmiwj</link>
            <guid>https://velog.io/@joo-chang/%EB%8C%80%EA%B8%B0%EC%97%B4-%EC%8B%9C%EC%8A%A4%ED%85%9C-Kafka-Redis-9bnfmiwj</guid>
            <pubDate>Mon, 07 Oct 2024 11:43:24 GMT</pubDate>
            <description><![CDATA[<h2 id="대기열">대기열</h2>
<ul>
<li>대기열은 서버에 대용량 트래픽이 몰릴 때를 대비하여 서버의 부하를 일정 수준으로 유지하기 위해 만든다.</li>
<li>티켓팅 시간에 맞춰 많은 요청이 한번에 들어올 경우 서버가 부하를 이기지 못하고 장애가 발생할 수 있다.</li>
<li>따라 요청들에 대기열을 부여하여 하나씩 처리할 수 있도록 하는 대기열 시스템을 도입한다.</li>
</ul>
<h2 id="대기열-처리-기술">대기열 처리 기술</h2>
<h3 id="kafka">Kafka</h3>
<h4 id="장점">장점</h4>
<ul>
<li><strong>높은 처리량과 확장성</strong> : 분산 아키텍처와 파티셔닝을 토앻 초당 수백만 건의 메세지를 처리할 수 있으며, 대규모 트래픽에 매우 적합</li>
<li><strong>데이터 내구성</strong> : 디스크에 데이터를 저장하고 복제하기 떄문에 메시지 손실을 방지하고, 장애 시 데이터를 복구 할 수 있음</li>
<li><strong>Offset 관리</strong> : 메시지를 원하는 시점부터 다시 읽을 수 있어 재처리와 재실행이 가능</li>
<li><strong>순서 보장</strong> : 동일한 파티션 내에서 메세지 순서 보장</li>
<li><strong>신뢰성</strong> : 데이터 중복 없이 정확하게 한 번만 처리할 수 있어 신뢰성 제공</li>
</ul>
<h4 id="단점">단점</h4>
<ul>
<li><strong>지연 시간</strong> : Redis 보다 지연 시간이 길 수 있어, 즉각적인 응답이 필요한 실시간 처리에는 약간 부족함이 있을 수 있음</li>
<li><strong>복잡한 설정과 운영</strong> : 설정과 운영이 복잡하여, 관리 비용이 높을 수 있음</li>
</ul>
<h3 id="redis">Redis</h3>
<h4 id="장점-1">장점</h4>
<ul>
<li><strong>빠른 속도</strong> : 인메모리 데이터 저장소이기 때문에 매우 낮은 지연 시간과 빠른 응답 속도를 제공</li>
<li><strong>간단한 사용법</strong> : 설정과 운영이 간단하고, 구현이 용이</li>
<li>실시간 처리에 적합</li>
</ul>
<h4 id="단점-1">단점</h4>
<ul>
<li><strong>데이터 손실 위험</strong> : Redis는 기본적으로 인메모리 데이터베이스이기 떄문에 시스템 장애에 데이터가 손실될 위험이 있음. 지속성을 위해 추가적인 설정이 필요</li>
<li><strong>확장성 한계</strong> : 대규모 트래픽 처리에 있어 kafka만큼 확장성이 뛰어나지 않음</li>
<li><strong>순서 보장 부족</strong> : 높은 동시성에서 메시지 순서를 보장하기 어렵고, 메세지 내구성이 약할 수 있음</li>
</ul>
<h3 id="kafka--redis">Kafka &amp; Redis</h3>
<h4 id="장점-2">장점</h4>
<p>• <strong>유연성</strong> : Redis를 통해 빠른 응답이 필요한 실시간 작업을 처리하고, Kafka를 통해 대규모 데이터 처리를 담당하는 구조로 구성할 수 있다.</p>
<p>• <strong>장점 극대화</strong>: Redis의 빠른 속도와 Kafka의 높은 처리량 및 데이터 내구성을 동시에 활용하여 다양한 상황에 적응할 수 있다.</p>
<p>• <strong>복구 및 재처리</strong>: Redis에서 실패한 메시지를 Kafka로 전달하여 안정적으로 재처리할 수 있어 장애 대응이 유연</p>
<h4 id="단점-2">단점</h4>
<p>• <strong>구성 복잡성</strong>: 두 가지 시스템을 모두 운영해야 하기 때문에 설정 및 관리가 복잡해질 수 있으며, 운영 비용이 증가</p>
<p>• <strong>중복 처리 비용</strong>: 두 시스템 간의 메시지 전송과 처리가 중복으로 발생할 수 있어 리소스 사용 효율성이 떨어질 수 있다.</p>
<h3 id="결론">결론</h3>
<ul>
<li>Kafka를 사용하는 것으로 결정!</li>
<li>Kafka를 선택하는 주된 이유는 대기열 시스템의 핵심 요구 사항인 <strong>확장성, 데이터 내구성, 메시지 순서 보장, 유연한 소비 모델, 그리고 운영 편의성</strong>을 Redis보다 더 잘 충족시키기 때문이다. </li>
<li>Kafka는 대규모 데이터 처리와 장애 복구 능력에서 특히 강점을 가지며, 여러 Consumer 그룹이 독립적으로 작업을 처리할 수 있는 구조를 제공하여 대기열 관리의 효율성을 극대화</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Exception Handler 적용 이슈]]></title>
            <link>https://velog.io/@joo-chang/Exception-Handler-%EC%A0%81%EC%9A%A9-%EC%9D%B4%EC%8A%88</link>
            <guid>https://velog.io/@joo-chang/Exception-Handler-%EC%A0%81%EC%9A%A9-%EC%9D%B4%EC%8A%88</guid>
            <pubDate>Mon, 07 Oct 2024 03:50:45 GMT</pubDate>
            <description><![CDATA[<h2 id="문제-상황-분석">문제 상황 분석</h2>
<ul>
<li>각 서비스에서 Exception 모듈 의존성을 추가하여 GlobalException은 잘 동작하지만, 모듈 내에 정의한 예외 핸들러가 제대로 동작하지 않는 문제가 발생</li>
<li>예외 핸들러 클래스가 Spring 애플리케이션 컨텍스트에 포함되지 않아서 발생하는 것</li>
</ul>
<h3 id="문제-상황">문제 상황</h3>
<ul>
<li>Exception 모듈은 컴포넌트로 등록되어 있으나, 해당 서비스가 이 모듈을 스캔하지 않아 예외 핸들러를 사용할 수 없는 상황</li>
<li>Spring의 예외 핸들러는 @RestControllerAdvice나 @ControllerAdvice 같은 어노테이션을 사용해 예외를 처리하며, 이러한 핸들러가 정상적으로 동작하려면 Spring 애플리케이션 컨텍스트에 포함되어 있어야 한다.</li>
</ul>
<h3 id="해결-방안">해결 방안</h3>
<p>이 문제를 해결하기 위해, 각 서비스에서 Spring의 @ComponentScan을 사용해 예외 핸들러가 있는 패키지를 스캔하고, 이를 Spring 애플리케이션 컨텍스트에 포함시켜야 한다. </p>
<p>이를 통해 예외 핸들러가 정상적으로 동작할 수 있도록 한다.</p>
<pre><code class="language-java">@Configuration
@ComponentScan(basePackages = {
    &quot;com.fortickets.exception&quot; // Exception 모듈이 위치한 패키지 경로
})
public class ComponentConfig {
}</code></pre>
<ul>
<li>@ComponentScan: basePackages 속성을 통해 지정된 패키지 내의 모든 클래스를 스캔해 Spring 컨텍스트에 등록</li>
<li>이 경우, com.fortickets.exception 패키지에 위치한 예외 핸들러 클래스가 포함</li>
</ul>
<h3 id="중요한-점">중요한 점</h3>
<ul>
<li>모듈 구조: MSA 또는 다중 모듈 구조에서는 각 모듈이 독립적으로 동작하기 때문에, 예외 핸들러가 포함된 패키지를 명시적으로 스캔하지 않으면 해당 서비스에서 예외 핸들러를 인식하지 못함.</li>
<li>범용성: 위와 같은 설정을 각 서비스에 적용하면, 모든 서비스에서 Exception 모듈의 예외 핸들러를 사용할 수 있음.</li>
</ul>
<h3 id="결론">결론</h3>
<p>Spring 애플리케이션에서 예외 핸들러가 제대로 동작하지 않는 문제는 주로 해당 핸들러가 Spring 컨텍스트에 포함되지 않아서 발생함. 이를 해결하기 위해 각 서비스에 @ComponentScan을 사용해 예외 핸들러가 있는 패키지를 명시적으로 스캔하도록 설정함으로써, 모든 서비스에서 동일한 예외 핸들링 로직을 사용할 수 있게 됨.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Elastic Search]]></title>
            <link>https://velog.io/@joo-chang/Elastic-Search</link>
            <guid>https://velog.io/@joo-chang/Elastic-Search</guid>
            <pubDate>Wed, 04 Sep 2024 14:09:46 GMT</pubDate>
            <description><![CDATA[<h2 id="elastic-search-란">Elastic Search 란?</h2>
<p>엘라스틱 서치는 분산형 오픈 소스 검색 엔진으로, Apach Lucene를 기반으로 개발되었다.</p>
<p>주로 로그 분석, 검색 엔진, 데이터 분석 등에 활용되며, 빠르고 확장성 있는 검색 기능을 제공</p>
<p>엘라스틱 서치는 텍스트 검색뿐만 아니라 구조화된 데이터와 비정형 데이터의 저장, 검색, 분석에도 탁훨한 성능을 발휘</p>
<p>프로젝트를 진행하다 보면 서버 성능, DB 설계의 문제나 혹은 DB 최적화가 되어있지 않아 서비스가 느려지는 형상을 경험하게 된다.
그 결과 관계형 데이터베이스에 최적화를 위해 인덱싱을 하게 되고 어떻게 인덱싱을 하냐에 따라 퍼포먼스가 달라지게 된다.</p>
<p>엘라스틱 서치 역시 데이터에 다양한 규칙으로 최적화된 인덱싱을 처리할 수 있어 검색에 빠른 성능을 보인다.</p>
<p>또한, 관계형 데이터베이스와 엘라스틱 서치의 데이터 저장되는 구조 자체가 다르다.</p>
<p><img src="https://velog.velcdn.com/images/joo-chang/post/77000b38-b985-4f49-86b3-092374948f43/image.png" alt=""></p>
<p>관계형 데이터베이스는 Document 중심이면 Elastic Search는 텍스트 중심이다.
RDBMS는 사용자가 봉준호를 검색하는 순간 doc1, doc3을 하나하나 확인하여 봉준호를 찾고, Elastic Search는 검색하는 순간 데이터를 찾을 수 있다.</p>
<p>RDBMS는 관계형 테이블을 조합하여 볼 수 있으며, 데이터의 ACID 원칙에 의해 관리되는 목적
Elastic Search는 데이터를 다른 table과 조합할 수 없으며, 데이터 트랜젝션을 지원하지 않는다. 엘라스틱 서치만으로 서비스를 개발하기에는 무리가 있다.</p>
<p>가장 좋은 방법은 관계형 DB를 베이스로 하고 검색에 필요한 부분만 엘라스틱 서치로 진행하는 것이 보통이다.</p>
<br>

<hr>
<h2 id="특징">특징</h2>
<h3 id="1-분산형-구조">1. 분산형 구조</h3>
<ul>
<li>클러스터로 구성되며, 데이터를 여러 노드에 분산하여 저장</li>
<li>분산 환경에서 데이터를 처리하기 때문에 대규모 데이터도 빠르고 안정적으로 처리</li>
</ul>
<h3 id="2-실시간-검색">2. 실시간 검색</h3>
<ul>
<li>데이터를 거의 실시간으로 색인할 수 있어, 최신 데이터에 대한 즉각적인 검색 결과 제공</li>
</ul>
<h3 id="3-restful-api-기반">3. RESTful API 기반</h3>
<ul>
<li>HTTP 기반으로 동작하며, JSON 형식으로 데이터를 전송하고 조회할 수 있는 RESTful API를 제공</li>
<li>사용하기 쉽고, 다양한 언어에서 통합하기 용이</li>
</ul>
<h3 id="4-schemaless-모델">4. Schemaless 모델</h3>
<ul>
<li>스키마리스 데이터 모델을 사용하여, 데이터를 저장할 때 미리 스키마를 정의하지 않아도 됨</li>
<li>필요에 따라 매핑을 설정하여 데이터 구조를 정의할 수도 있음</li>
</ul>
<h3 id="5-풀텍스트-검색-full-text-search">5. 풀텍스트 검색 (Full-text Search)</h3>
<ul>
<li>Apache Lucene 기반으로 텍스트 분석, 색인, 검색이 매우 뛰어남</li>
<li>특히 자연어 처리와 같은 복잡한 검색 기능을 쉽게 구현 가능</li>
</ul>
<h3 id="6-분석">6. 분석</h3>
<ul>
<li>데이터 검색 외에도 집계를 통해 데이터 분석 기능 제공</li>
</ul>
<h3 id="7-확장성">7. 확장성</h3>
<ul>
<li>수평 확장 가능</li>
<li>데이터가 많아지면 클러스터에 새로운 노드를 추가하여 성능을 유지</li>
<li>대규모 데이터 처리에 매우 유용</li>
</ul>
<br>

<hr>
<h2 id="장단점">장단점</h2>
<h3 id="장점">장점</h3>
<ul>
<li><strong>빠른 검색 성능</strong> : 대규모 데이터에서도 매우 빠르게 텍스트 검색 및 데이터 분석 처리</li>
<li><strong>확장성</strong> : 노드를 추가하여 성능 향상, 데이터 분산 처리로 대용량 데이터 처리 가능</li>
<li><strong>RESTful API</strong> : RESTful API를 통해 다양한 프로그래밍 언어와 쉽게 통합</li>
<li>강력한 집계 기능 : 데이터 집계 및 분석 기능이 뛰어남</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li><strong>복잡한 설정</strong> : 기본적인 사용은 간단하나, 대규모 클러스터에서의 운영은 복잡할 수 있으며, 성능 최적화나 장애 대응 등을 고려해야 함</li>
<li><strong>메모리 사용량</strong> : 데이터 양이 많아지면 메모리 사용량이 증가할 수 있어, 대용량 시스템에서는 메모리 관리에 주의해야 함</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[풍선 딜리버리 시스템 아키텍처 설계]]></title>
            <link>https://velog.io/@joo-chang/%ED%92%8D%EC%84%A0-%EB%94%9C%EB%A6%AC%EB%B2%84%EB%A6%AC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@joo-chang/%ED%92%8D%EC%84%A0-%EB%94%9C%EB%A6%AC%EB%B2%84%EB%A6%AC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Fri, 23 Aug 2024 01:59:32 GMT</pubDate>
            <description><![CDATA[<h2 id="api-명세서">API 명세서</h2>
<p><a href="https://tidal-property-d16.notion.site/API-27f203be83994af283b65dcb6271d78e?pvs=74">https://tidal-property-d16.notion.site/API-27f203be83994af283b65dcb6271d78e?pvs=74</a></p>
<h2 id="테이블-설계서">테이블 설계서</h2>
<ul>
<li>사용자 테이블 (p_users)</li>
</ul>
<pre><code>| 필드 이름 | 데이터 타입 | 설명 |
| --- | --- | --- |
| `user_id` | Long | 사용자 ID, Primary Key |
| `email` | VARCHAR(255) | 사용자 이메일, Unique |
| `nickname` | VARCHAR(100) | 사용자 닉네임 |
| `password` | VARCHAR(255) | 사용자 비밀번호 (암호화) |
| `role` | role_type | 사용자 권한 (USER, OWNER, ADMIN) |
| `address_id(fk)` |  | 주소 ID |</code></pre><ul>
<li>메뉴(p_menus)</li>
</ul>
<pre><code>| 필드 이름 | 데이터 타입 | 설명 |
| --- | --- | --- |
| `menu_id` | Long  | 메뉴 ID, Primary Key |
| `name` | VARCHAR(255)  | 메뉴 이름 |
| `price` | Integer  | 메뉴 가격 |
| `content` | VARCHAR(255) | 메뉴 설명 |
| `restaurant_id (fk)` |  | 가게 ID |</code></pre><ul>
<li>가게 (p_restaurants)</li>
</ul>
<pre><code>| 필드 이름 | 데이터 타입 | 설명 |
| --- | --- | --- |
| `restaurant_id` | Long | 가게 ID, Primary Key |
| `name` | VARCHAR(255) | 가게 이름 |
| `content` | VARCHAR(255) | 가게 설명 |
| `phone` | VARCHAR(100) | 가게 번호 |
| `address_id (fk)` |  | 주소 ID |
| `user_id (fk)` |  | 사용자 ID |
| `category_id (fk)` |  | 카테고리 ID |</code></pre><ul>
<li>장바구니 (p_carts)</li>
</ul>
<pre><code>| 필드 이름 | 데이터 타입 | 설명 |
| --- | --- | --- |
| `cart_id` | Long | 장바구니 ID, Primary Key |
| `quantity` | VARCHAR(255) | 수량 |
| `price` | VARCHAR(255) | 가격 |
| `user_id (fk)` |  | 사용자 ID |
| `menu_id (fk)` |  | 메뉴 ID |</code></pre><ul>
<li>주문 테이블 (p_orders)</li>
</ul>
<pre><code>| 필드 이름 | 데이터 타입 | 설명 |
| --- | --- | --- |
| `order_id` | Long | 주문 ID, Primary Key |
| `status` | enum | 주문 상태 (결제 대기, 주문 취소, 주문 대기, 조리중, 주문 완료) |
| `order_type` | enum | 주문 유형 (온라인, 오프라인) |
| `request` | VARCHAR(255) | 요청 사항 |
| `total_price` | Long | 총 가격 |
| `restaurant_id (fk)` |  | 가게 ID |
| `payment_id (fk)` |  | 결제 ID |
| `user_id (fk)` |  | 사용자 ID |</code></pre><ul>
<li>주문 상세 (p_order_details)</li>
</ul>
<pre><code>| 필드 이름 | 데이터 타입 | 설명 |
| --- | --- | --- |
| `order_detail_id` | Long | 주문 상세 ID, Primary Key |
| `quantity` | Integer | 수량 |
| `price` | Long | 총 가격 |
| `order_id (fk)` |  | 주문 ID |
| `menu_id (fk)` |  | 메뉴 ID |</code></pre><ul>
<li>결제 (p_payments)</li>
</ul>
<pre><code>| 필드 이름 | 데이터 타입 | 설명 |
| --- | --- | --- |
| `payment_id` | Long | 결제 ID, Primary Key |
| `status` | enum | 결제 상태 (결제 요청, 결제 완료, 결제 취소) |
| `price` | Long | 가격 |
| `user_id (fk)` |  | 사용자 ID |</code></pre><ul>
<li>주소 (p_address)</li>
</ul>
<pre><code>| 필드 이름 | 데이터 타입 | 설명 |
| --- | --- | --- |
| `address_id` | Long | 주소 ID, Primary Key |
| `address1` | VARCHAR(10) | 우편번호 |
| `address2` | VARCHAR(255) | 주소 |
| `address3` | VARCHAR(255) | 상세 주소 |</code></pre><ul>
<li>카테고리 (p_categorys)</li>
</ul>
<pre><code>| 필드 이름 | 데이터 타입 | 설명 |
| --- | --- | --- |
| `category_id` | Long | 카테고리 ID, Primary Key |
| `name` | enum | 카테고리 (한식, 중식 …) |</code></pre><ul>
<li>CHATBOT (p_chatbot)</li>
</ul>
<pre><code>| 필드 이름 | 데이터 타입 | 설명 |
| --- | --- | --- |
| `chatbot_id` | Long | 챗봇 ID, Primary Key |
| `request` | TEXT | 요청 데이터 |
| `response` | TEXT | 응답 데이터 |</code></pre><ul>
<li>공통 Audit</li>
</ul>
<h2 id="erd">ERD</h2>
<p><img src="https://velog.velcdn.com/images/joo-chang/post/a2cde052-685d-4a31-bc51-1fd80aa691be/image.png" alt=""></p>
<h2 id="시스템-설계서">시스템 설계서</h2>
<p><img src="https://velog.velcdn.com/images/joo-chang/post/1cc64575-0150-4a42-a1ca-56674220067c/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA N+1 문제]]></title>
            <link>https://velog.io/@joo-chang/JPA-N1-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@joo-chang/JPA-N1-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Wed, 21 Aug 2024 03:08:56 GMT</pubDate>
            <description><![CDATA[<ul>
<li>JPA N+1 문제는 데이터베이스 성능 저하를 유발하는 대표적인 문제 중 하나이다.</li>
<li>주로 <code>OneToMany</code> 나 <code>ManyToMany</code> 같은 다대일, 일대다 관계를 조회할 때 발생</li>
</ul>
<br>

<h2 id="상황">상황</h2>
<p>예를들어, Customer 와 Order가 일대다 관계를 가지고 있다고 가정</p>
<pre><code class="language-java">@Entity
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = &quot;customer&quot;, fetch = FetchType.LAZY)
    private List&lt;Order&gt; orders = new ArrayList&lt;&gt;();
}

@Entity
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String product;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;customer_id&quot;)
    private Customer customer;
}</code></pre>
<br>

<h2 id="문제-발생-예시">문제 발생 예시</h2>
<ul>
<li>고객과 관련된 모든 주문을 조회한다고 가정</li>
<li>고객 리스트를 조회할 때, 각 고객의 주문도 함께 조회하려고 할 때 문제가 발생할 수 있다.</li>
</ul>
<pre><code class="language-java">public interface CustomerRepository extends JpaRepository&lt;Customer, Long&gt; {
}

@Service
@RequiredArgsConstructor
public class CustomerService {
    private final CustomerRepository customerRepository;

    public List&lt;Customer&gt; getAllCustomersWithOrders() {
        List&lt;Customer&gt; customers = customerRepository.findAll(); // 1. 모든 고객을 조회
        for (Customer customer : customers) {
            System.out.println(customer.getOrders().size()); // 2. 각 고객의 주문 수를 출력
        }
        return customers;
    }
}</code></pre>
<h3 id="n1-문제-발생">N+1 문제 발생</h3>
<ul>
<li>위 코드에서 <code>customerRepository.findAll();</code> 은 모든 고객을 조회하는 SQL 쿼리를 한 번 실행</li>
</ul>
<pre><code class="language-sql">SELECT * FROM customer;</code></pre>
<ul>
<li>하지만 <code>customer.getOrder()</code> 를 호출할 때마다 해당 고객의 주문을 조회하는 추가적인 SQL 쿼리가 실행</li>
</ul>
<pre><code class="language-sql">SELECT * FROM orders WHERE customer_id = ?;</code></pre>
<p>만약 DB에 10명의 고객이 있다면, 총 11개의 SQL 쿼리 실행 (고객 조회 1번 + 주문 조회 10번)
고객 수가 증가할수록 쿼리 수도 증가하게 되어, 성능에 큰 영향을 미친다.</p>
<br>

<h2 id="n1-문제-해결-방법">N+1 문제 해결 방법</h2>
<h3 id="fetch-join-사용">Fetch Join 사용</h3>
<ul>
<li>QueryDSL 을 사용하여 <code>fetchJoin()</code> 을 활용하면 고객과 주믄을 한 번의 쿼리로 가져올 수 있다.</li>
</ul>
<pre><code class="language-java">import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.stereotype.Repository;
import java.util.List;

@Repository
public class CustomerRepositoryImpl implements CustomerRepositoryCustom {

    private final JPAQueryFactory queryFactory;

    public CustomerRepositoryImpl(JPAQueryFactory queryFactory) {
        this.queryFactory = queryFactory;
    }

    @Override
    public List&lt;Customer&gt; findAllWithOrders() {
        QCustomer customer = QCustomer.customer;
        QOrder order = QOrder.order;

        return queryFactory
            .selectFrom(customer)
            .leftJoin(customer.orders, order).fetchJoin() // 페치 조인
            .fetch();
    }
}</code></pre>
<h3 id="batch-size-설정">Batch Size 설정</h3>
<p>대량의 연관 데이터를 처리할 때, <code>@BatchSize</code> 를 설정하여, 지정된 크기만큼의 데이터를 한 번에 가져오도록 설정</p>
<pre><code class="language-java">@Entity
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = &quot;customer&quot;, fetch = FetchType.LAZY)
    @BatchSize(size = 10)
    private List&lt;Order&gt; orders = new ArrayList&lt;&gt;();

    // Getters and Setters
}</code></pre>
<h3 id="entitygraph-사용">@EntityGraph 사용</h3>
<p><code>@EntityGraph</code> 를 사용하면 특정 쿼리에서 관련 엔티티를 즉시 로딩 (Eager Loading)으로 가져올 수 있다.</p>
<pre><code class="language-java">public interface CustomerRepository extends JpaRepository&lt;Customer, Long&gt;, QuerydslPredicateExecutor&lt;Customer&gt; {
    @EntityGraph(attributePaths = {&quot;orders&quot;})
    List&lt;Customer&gt; findAll(Predicate predicate);
}</code></pre>
<br>

<h2 id="결론">결론</h2>
<p>Spring Data JPA에서 N+1 문제는 연관된 엔티티 조회 시 발생할 수 있는 일반적인 문제이며, 성능 저하가 발생할 수 있다.</p>
<p>이를 해결하기 위해 <code>FETCH JOIN</code> , <code>@EntityGraph</code> , <code>@BatchSize</code> 등을 적절히 사용해 쿼리 수를 줄이고, 성능을 최적화하는 것이 중요하다.</p>
<p>미리 N+1 문제를 인지하고 해결하는 것이 애플리케이션 성능을 유지하는 데 큰 도움이 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Entity 설계 연습]]></title>
            <link>https://velog.io/@joo-chang/Entity-%EC%84%A4%EA%B3%84-%EC%97%B0%EC%8A%B5</link>
            <guid>https://velog.io/@joo-chang/Entity-%EC%84%A4%EA%B3%84-%EC%97%B0%EC%8A%B5</guid>
            <pubDate>Tue, 20 Aug 2024 05:27:33 GMT</pubDate>
            <description><![CDATA[<h2 id="상황">상황</h2>
<p>쇼핑몰에서 고객은 여러 개의 상품을 주문할 수 있다.
각 주문은 여러 상품을 포함하며, 각 상품은 하나의 주문에 속한다.
이 상황에서 주문과 상품의 관계는 다대다 관계이다. 
또한, 주문에는 고객 정보가 포함되며, 고객과 주문의 관계는 일대다 관계이다.</p>
<h2 id="설계">설계</h2>
<h3 id="고객-엔티티">고객 엔티티</h3>
<ul>
<li>고객은 여러 주문을 가질 수 있다.</li>
<li>Customer와 Order는 일대다 관계</li>
</ul>
<pre><code class="language-java">@Entity
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String email;

    @OneToMany(mappedBy = &quot;customer&quot;, cascade = CascadeType.ALL)
    private List&lt;Order&gt; orders = new ArrayList&lt;&gt;();

    // Getters and Setters
}</code></pre>
<h3 id="주문-엔티티">주문 엔티티</h3>
<ul>
<li>주문은 여러 상품을 포함하며, 각 상품은 주문에 속한다.</li>
<li>Order와 OrderItem은 일대다 관계</li>
<li>주문은 하나의 고객과 연관된다.</li>
<li>Order와 Customer는 다대일 관계</li>
</ul>
<pre><code class="language-java">@Entity
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;customer_id&quot;)
    private Customer customer;

    @OneToMany(mappedBy = &quot;order&quot;, cascade = CascadeType.ALL)
    private List&lt;OrderItem&gt; orderItems = new ArrayList&lt;&gt;();

    // Getters and Setters
}</code></pre>
<h3 id="상품-엔티티">상품 엔티티</h3>
<ul>
<li>각각의 상품을 나타내며, 여러 <code>OrderItem</code>과 연관될 수 있다.</li>
</ul>
<pre><code class="language-java">@Entity
public class Item {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private int price;

    @OneToMany(mappedBy = &quot;item&quot;)
    private List&lt;OrderItem&gt; orderItems = new ArrayList&lt;&gt;();

    // Getters and Setters
}</code></pre>
<h3 id="주문-상품-엔티티">주문 상품 엔티티</h3>
<p>\</p>
<ul>
<li>주문 내의 개별 항목을 나타내며, 하나의 Order와 하나의 Item에 연관된다.</li>
</ul>
<pre><code class="language-java">@Entity
public class OrderItem {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;order_id&quot;)
    private Order order;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;item_id&quot;)
    private Item item;

    private int quantity;
    private int orderPrice;

    // Getters and Setters
}
</code></pre>
<h2 id="테이블-구조">테이블 구조</h2>
<h3 id="customer">Customer</h3>
<pre><code class="language-sql">CREATE TABLE customer (
    id BIGINT NOT NULL AUTO_INCREMENT, -- 고객의 고유 ID
    name VARCHAR(255), -- 고객 이름
    email VARCHAR(255), -- 고객 이메일
    PRIMARY KEY (id) -- 기본 키
);</code></pre>
<h3 id="item">Item</h3>
<pre><code class="language-sql">CREATE TABLE item (
    id BIGINT NOT NULL AUTO_INCREMENT, -- 상품의 고유 ID
    name VARCHAR(255), -- 상품 이름
    price INT, -- 상품 가격
    PRIMARY KEY (id) -- 기본 키
);</code></pre>
<h3 id="order">Order</h3>
<pre><code class="language-sql">CREATE TABLE orders (
    id BIGINT NOT NULL AUTO_INCREMENT, -- 주문의 고유 ID
    customer_id BIGINT, -- 고객 ID (외래 키)
    order_date TIMESTAMP, -- 주문 일시
    status VARCHAR(255), -- 주문 상태
    PRIMARY KEY (id), -- 기본 키
    FOREIGN KEY (customer_id) REFERENCES customer(id) -- 외래 키 제약 조건
);</code></pre>
<h3 id="orderitem">OrderItem</h3>
<pre><code class="language-sql">CREATE TABLE order_item (
    id BIGINT NOT NULL AUTO_INCREMENT, -- 주문 항목의 고유 ID
    order_id BIGINT, -- 주문 ID (외래 키)
    item_id BIGINT, -- 상품 ID (외래 키)
    quantity INT, -- 주문된 상품 수량
    order_price INT, -- 해당 상품의 주문 가격
    PRIMARY KEY (id), -- 기본 키
    FOREIGN KEY (order_id) REFERENCES orders(id), -- 외래 키 제약 조건
    FOREIGN KEY (item_id) REFERENCES item(id) -- 외래 키 제약 조건
);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kafka]]></title>
            <link>https://velog.io/@joo-chang/Kafka-8z8l2976</link>
            <guid>https://velog.io/@joo-chang/Kafka-8z8l2976</guid>
            <pubDate>Mon, 19 Aug 2024 02:07:24 GMT</pubDate>
            <description><![CDATA[<h2 id="kafka란">Kafka란?</h2>
<ul>
<li>카프카는 분산 이벤트 스트리밍 플랫폼이다. 대용량 데이터 스트리밍을 처리하고 저장할 수 있는 오픈 소스 솔루션이다. 주로 이벤트 스트리밍, 데이터 파이프라인 및 실시간 데이터 피드를 구축하는데 사용한다.</li>
<li>메시지 큐와 유사하지만, 대용량 데이터 스트림을 저장하고 실시간으로 분석하거나 처리하는 데 중점을 둔다.</li>
</ul>
<br>

<h2 id="역할">역할</h2>
<ul>
<li>실시간 데이터 처리 : 대용량 데이터를 실시간 처리, 분석</li>
<li>데이터 통합 : 다양한 소스에서 데이터를 수집하고 이를 통합하여 분석</li>
<li>내결함성 : 데이터 손실 없이 안정적으로 데이터를 저장, 전송</li>
</ul>
<br>

<h2 id="장단점">장단점</h2>
<h3 id="장점">장점</h3>
<ul>
<li>신뢰성<ul>
<li>데이터 복제 : 데이터를 여러 브로커에 복제하여 저장하므로, 단일 브로커 장애 시에도 데이터 손실 방지</li>
<li>확인 매커니즘 : 데이터가 소비자에게 성공적으로 전달되었는지 확인하는 기능 제공</li>
</ul>
</li>
<li>유연성<ul>
<li>다양한 소비자 패턴 : 여러 소비자가 동시에 데이터를 구독할 수 있음</li>
<li>프로토콜 지원 : 기본적으로 Kafka 프로토콜을 사용하지만, 다양한 클라이언트를 통해 다른 언어에서도 사용 가능</li>
</ul>
</li>
<li>확장성<ul>
<li>분산 시스템 : 클러스터링을 통해 여러 노드에서 데이터 분산 처리</li>
<li>수평 확장 : 브로커와 파티션을 추가하여 쉽게 확장</li>
</ul>
</li>
<li>성능<ul>
<li>높은 처리량 : 대용량 데이터를 실시간으로 빠르게 처리</li>
<li>저지연 : 데이터 전송 지연을 최소화하여 실시간 처리</li>
</ul>
</li>
<li>관리 및 모니터링<ul>
<li>관리 도구 : 다양한 관리 도구를 통해 클러스터를 모니터링하고 관리</li>
<li>플러그인 시스템 : 다양한 플러그인을 통해 기능 확장</li>
</ul>
</li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li>설정 및 운영 복잡성<ul>
<li>복잡한 설정 : 초기 설정이 다소 복잡할 수 있으며, 클러스터링 및 분산 환경에서는 더 많은 설정이 필요</li>
<li>운영 관리 : 대규모 환경에서 운영, 관리하는 데 추가적인 노력이 필요</li>
</ul>
</li>
<li>성능 문제<ul>
<li>브로커 오버헤드 : 높은 트래픽 상황에서는 브로커의 오버헤드가 발생할 수 있음</li>
<li>대규모 메시지 처리 : 매우 대규모의 메시지를 처리할 때 성능 저하가 발생할 수 있으며, 적절한 클러스터링 및 최적화가 필요</li>
</ul>
</li>
<li>운영 비용<ul>
<li>리소스 소비 : 메모리와 CPU 자원을 많이 소비할 수 있어, 충분한 리소스를 제공해야 함</li>
<li>모니터링 및 유지보수 : 지속적인 모니터링과 유지보수가 필요하며, 추가적인 인력과 비용이 발생할 수 있음</li>
</ul>
</li>
<li>러닝 커브<ul>
<li>학습 필요성 : 개념과 설정을 이해하는 데 시간이 걸릴 수 있음</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/joo-chang/post/fa192c6d-9446-45f5-963f-f79196173bf0/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/joo-chang/post/c16efdf2-b2ce-49a7-b0ed-811e0ab2053f/image.png" alt=""></p>
<p>데이터가 어떤 시스템에 저장되는지 관계하지 않고 무조건 Kafka만 상대하면 된다.
카프카에 쌓인 데이터 메세지들도, 서비스들에 데이터를 전달해줄 때 카프카에서 받아오면 되기 때문에 단일 포맷을 사용할 수 있다.</p>
<h2 id="기본-구성-요소">기본 구성 요소</h2>
<h3 id="message">Message</h3>
<ul>
<li>메시지는 Kafka를 통해 전달되는 데이터 단위</li>
<li>메시지는 key, value, timestamp, 몇 가지 메타데이터로 구성</li>
</ul>
<h3 id="kafka-cluster">Kafka Cluster</h3>
<ul>
<li>메세지를 저장하는 저장소이다.</li>
<li>하나의 카프카 클러스터는 여러 개 브로커(서버로 보면 됨)로 구성</li>
</ul>
<h3 id="producer">Producer</h3>
<ul>
<li>카프카 클러스터에 메세지를 보내는 역할</li>
<li>프로듀서는 특정 토픽에 메시지를 보냄</li>
</ul>
<h3 id="topic">Topic</h3>
<ul>
<li>메시지를 저장하는 장소</li>
<li>메시지는 토픽에 저장되었다가 소비자에게 전달</li>
<li>토픽은 여러 파티션으로 나누어질 수 있으며, 파티션은 메시지를 순서대로 저장</li>
<li>파티션을 통해 병렬 처리가 가능</li>
</ul>
<h3 id="partition">Partition</h3>
<ul>
<li>파티션은 토픽을 물리적으로 나눈 단위</li>
<li>각 파티션은 독립적으로 메시지를 저장하고 관리</li>
<li>파티션을 통해 데이터를 병렬로 처리할 수 있으며, 클러스터 내의 여러 브로커에 분산시켜 저장 가능</li>
</ul>
<h3 id="key">Key</h3>
<ul>
<li>키는 메시지를 특정 파티션에 할당하는 데 사용되는 값</li>
<li>동일한 키를 가진 메시지는 항상 동일한 파티션에 저장</li>
</ul>
<h3 id="consumer">Consumer</h3>
<ul>
<li>토픽에서 메시지를 가져와 처리하는 역할</li>
<li>컨슈머는 특정 컨슈머 그룹에 속하며, 같은 그룹에 속한 컨슈머들은 토픽의 파티션을 분산 처리</li>
<li>기본적으로 컨슈머는 스티키 파티셔닝을 사용한다. 특정 컨슈머가 특정 파티션에 붙어서 계속해서 데이터를 처리하는 방식으로, 데이터 지역성을 높여 캐시 히트율을 증가시키고 전반적인 처리 성능을 향상</li>
</ul>
<h3 id="broker">Broker</h3>
<ul>
<li><p>Kafka 클러스터의 각 서버를 의미. 메시지를 저장하고 전송하는 역할</p>
</li>
<li><p>하나의 Kafka 클러스터는 여러 브로커로 구성될 수 있으며, 각 브로커는 하나 이상의 토픽 파티션을 관리</p>
<h3 id="zookeeper">Zookeeper</h3>
</li>
<li><p>카프카 클러스터를 관리, 조정하는 데 사용되는 분산 코디네이션 서비스</p>
</li>
<li><p>브로커의 메타데이터를 저장하고, 브로커 간 상호작용을 조정</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/joo-chang/post/8747546a-631e-44de-9ab9-58210b03801d/image.png" alt=""></p>
<br>

<h2 id="토픽과-파티션">토픽과 파티션</h2>
<ul>
<li>토픽은 메세지를 구분하는 단위 (파일 시스템의 폴더와 유사함)</li>
<li>한 개의 토픽은 한 개 이상의 파티션으로 구성<ul>
<li>파티션은 메세지를 저장하는 물리적인 파일</li>
</ul>
</li>
</ul>
<p>프로듀서는 메세지를 저장할 때 어떤 토픽에 저장
컨슈머는 어떤 토픽에서 메세지를 읽어옴</p>
<p><img src="https://velog.velcdn.com/images/joo-chang/post/f6df07f5-fdd2-4b39-8eac-01ef99d3a1f5/image.png" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>