<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>공부하자 오세팡</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Tue, 04 Mar 2025 12:05:17 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>공부하자 오세팡</title>
            <url>https://velog.velcdn.com/images/sepang-pang/profile/59d5d823-0993-430c-b154-f44a8d99cde8/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 공부하자 오세팡. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/sepang-pang" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[ OOP ] 객체 지향 설계의 핵심, SOLID 원칙 이해하기]]></title>
            <link>https://velog.io/@sepang-pang/OOP-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84%EC%9D%98-%ED%95%B5%EC%8B%AC-SOLID-%EC%9B%90%EC%B9%99-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sepang-pang/OOP-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%EC%84%A4%EA%B3%84%EC%9D%98-%ED%95%B5%EC%8B%AC-SOLID-%EC%9B%90%EC%B9%99-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 04 Mar 2025 12:05:17 GMT</pubDate>
            <description><![CDATA[<h1 id="what-is-solid-">What is S.O.L.I.D ?</h1>
<h2 id="단일-책임-원칙-srp-single-responsibility-principle">단일 책임 원칙 SRP (Single Responsibility Principle)</h2>
<p>☑️ 단일 책임 원칙은 클래스(객체)는 단 하나의 책임만 가져야 한다는 원칙
☑️ 여기서 <code>책임</code> 이란 하나의 <code>기능 담당</code>  혹은 <code>변경의 이유</code> 를 의미한다.
☑️ 즉, <strong>하나의 클래스는 하나의 기능에 집중</strong>하도록 설계하고, <strong>오직 하나의 이유로 변경</strong>되어야 한다는 것이다.
☑️ 만약 하나의 클래스가 여러 가지 기능(책임)을 갖고 있다면, 특정 기능을 변경할 때 연쇄적으로 다른 코드까지 수정해야 하는 문제가 발생할 수 있다. 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;예를 들어, A 기능을 수정하면 B도 변경해야 하고, B를 수정하면 다시 C를 손봐야 하며, 심지어 C를 수정한 후 다시 A로 돌아가야 하는 상황이 발생할 수 있다. 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong>이는 책임이 순환되면서 유지보수가 어려워지는 원인이 된다.</strong>
☑️ 이에 <strong>SRP 원칙을 따르면</strong> 한 기능의 변경이 다른 기능에 미치는 영향을 최소화할 수 있어, <strong>유지보수성과 확장성이 향상</strong>된다. 
☑️ 다만, 책임의 범위는 개발하는 프로그램의 성격에 따라 달라질 수 있으며, 정해진 정답이 존재하는 것은 아니다. 
☑️ 따라서 개발자는 프로젝트의 요구사항을 고려하여 적절한 기준을 세우는 것이 중요하다.</p>
<blockquote>
<p>SRP 원칙을 적용하여 클래스를 세세하게 나눔으로써 전체 코드 길이가 길어졌다 하더라도, 하나의 클래스를 사용하는 것보다 여러 개의 클래스를 사용하는 것이 더 효율적이다. 그래야 각 클래스의 의미를 파악하기도 쉽고 유지보수에 용이하기 때문이다.</p>
</blockquote>
<hr>
<h2 id="개방-폐쇄-원칙-ocp">개방-폐쇄 원칙 (OCP)</h2>
<p>☑️ 개방-폐쇄 원칙는 <strong>클래스는 확장에는 열려 있어야 하고, 수정에는 닫혀 있어야 한다</strong>는 원칙
☑️ <strong>이는 새로운 기능을 추가할 때 기존 코드를 수정하지 않고 확장을 통해 구현하는 설계 기법</strong>이다.
☑️ <strong>확장에 열려 있다는 것은 새로운 기능이나 변경 사항이 발생했을 때, 기존 코드를 크게 손대지 않고 유연하게 확장할 수 있어야 한다는 의미이다.</strong>
☑️ <strong>수정에 닫혀 있다는 것은 기존의 클래스나 객체를 직접적으로 수정하는 것을 제한한다는 것이다.</strong>
☑️ 기존 코드의 일부 요소를 삭제하거나 새로운 요소를 추가하는 것은 문제가 되지 않지만, <strong>기존 요소의 내용을 변경하면 의도치 않은 사이드 이펙트가 발생할 수 있기 때문에 변경을 최소화하는 것이 중요하다.</strong>
☑️ <strong>OCP 원칙은 추상화를 사용해 클래스 간의 관계를 구축하고, 다형성과 확장을 가능하게 하는 객체지향의 장점을 극대화하는 설계 원칙이다.</strong>
☑️ 즉, <strong>시스템에 새로운 기능을 추가할 때 기존 코드를 변경하지 않도록 보장하는 방식</strong>이다.</p>
<hr>
<h3 id="ocp-예제">OCP 예제</h3>
<h4 id="ocp-를-따르지-않는-경우">OCP 를 따르지 않는 경우</h4>
<pre><code class="language-java">public class PaymentProcessor {
    public void processPayment(String paymentType, double amount) {
        if (paymentType.equals(&quot;신용카드&quot;)) {
            System.out.println(&quot;신용카드 결제 처리: &quot; + amount + &quot;원&quot;);
        } else if (paymentType.equals(&quot;페이팔&quot;)) {
            System.out.println(&quot;페이팔 결제 처리: &quot; + amount + &quot;원&quot;);
        } else {
            System.out.println(&quot;알 수 없는 결제 방식&quot;);
        }
    }
}</code></pre>
<p>결제 방식마다 조건문을 통해 결제를 처리한다고 가정해보자. 이때 새로운 결제 방식이 추가될 때마다 이 클래스를 수정해야 한다.
예를 들어, “비트코인” 결제 방식이 추가된다면 <code>processPayment</code> 메소드에 조건문을 추가해야 하므로 기존 코드를 변경하게 되어 OCP 원칙을 위배하게 된다.</p>
<h4 id="ocp-원칙을-적용한-경우">OCP 원칙을 적용한 경우</h4>
<pre><code class="language-java">public interface PaymentMethod {
    void processPayment(double amount);
}</code></pre>
<pre><code class="language-java">// 신용카드 결제 처리 클래스
public class CreditCardPayment implements PaymentMethod {
    @Override
    public void processPayment(double amount) {
        System.out.println(&quot;신용카드 결제 처리: &quot; + amount + &quot;원&quot;);
    }
}</code></pre>
<pre><code class="language-java">// 페이팔 결제 처리 클래스
public class PayPalPayment implements PaymentMethod {
    @Override
    public void processPayment(double amount) {
        System.out.println(&quot;페이팔 결제 처리: &quot; + amount + &quot;원&quot;);
    }
}</code></pre>
<pre><code class="language-java">// 비트코인 결제 처리 클래스
public class BitcoinPayment implements PaymentMethod {
    @Override
    public void processPayment(double amount) {
        System.out.println(&quot;비트코인 결제 처리: &quot; + amount + &quot;원&quot;);
    }
}</code></pre>
<pre><code class="language-java">// 결제 처리 클래스
public class PaymentProcessor {

    private final PaymentMethod paymentMethod;

    public void processPayment(PaymentMethod paymentMethod, double amount) {
        paymentMethod.processPayment(amount);
    }</code></pre>
<p>이렇게 구현하면 새로운 결제 방식이 추가될 때마다 PaymentProcessor 클래스를 수정할 필요 없이, PaymentMethod 인터페이스를 구현하는 새로운 클래스를 추가하는 방식으로 확장이 가능하다. </p>
<hr>
<h2 id="리스코프-치환-원칙-lsp">리스코프 치환 원칙 (LSP)</h2>
<p>☑️ 서브 타입은 언제나 기반(부모) 타입으로 교체할 수 있어야 한다는 원칙이다.
☑️ 쉽게 말해, LSP는 <strong>다형성을 올바르게 활용하기 위한 기본 원칙</strong>으로 볼 수 있다.
☑️ 부모 클래스 타입으로 선언된 객체에 <strong>하위 클래스의 인스턴스를 업캐스팅 했을 때</strong>, <strong>부모의 메서드를 그대로 사용하더라도 정상적으로 동작</strong>해야 한다는 것이 핵심이다.
☑️ 여기서 &quot;정상적으로 동작&quot; 한다는 표현이 모호할 수 있다.
☑️ 더 쉽게 말하자면, <strong>부모 클래스의 메서드가 의도한 대로 일관되게 동작</strong>해야 한다는 것이다 !!!
☑️ 만약 부모의 메서드를 호출했을 때, <strong>예외가 발생하거나, 예상과 다른 동작을 하면 LSP를 위배한 것</strong>이다.</p>
<hr>
<h3 id="lsp-예제">LSP 예제</h3>
<h4 id="lsp-위반">LSP 위반</h4>
<pre><code class="language-java">public class Rectangle {

    public int width;
    public int height;

    // 너비 반환
    public int getWidth() {
        return width;
    }

    // 높이 반환
    public int getHeight() {
        return height;
    }

    // 너비 할당
    public void setWidth(int width) {
        this.width = width;
    }

    // 높이 할당
    public void setHeight(int height) {
        this.height = height;
    }

    //직사각형 넓이 반환 함수
    public int getArea() {
        return width * height;
    }
}</code></pre>
<p>위 코드와 같이 직사각형 클래스가 있다고 하자.
너비와 높이를 설정할 수 있도록 <code>setter</code> 메서드를 제공한다.
또한, <code>getArea()</code> 메서드를 통해 직사각형의 넓이를 구할 수 있다.</p>
<pre><code class="language-java">public class Square extends Rectangle {

    @Override
    public void setWidth(int Width) {
        super.setWidth(width);
        super.setHeight(getWidth());
    }

    @Override
    public void setHeight(int height) {
        super.setHeight(height);
        super.setWidth(getHeight());
    }
}</code></pre>
<p>정사각형 클래스는 <code>Rectangle</code> 을 상속받으며, 너비와 높이를 동일하게 유지하기 위해 <code>setter</code> 메서드를 오버라이딩한다.
정사각형은 너비와 높이가 항상 같아야 하므로, <code>setWidth()</code> 와 <code>setHeight()</code> 를 호출할 때 상대 값을 자동으로 변경하도록 구현한다.</p>
<hr>
<p>직사각형과 이를 상속받는 정사각형을 잘 작성하였으니, 메서드가 잘 수행되는지 확인해보자.</p>
<pre><code class="language-java">public class Main {
    public static void main(String[] args) {
        Rectangle rectangle = new Rectangle();

        rectangle.setWidth(5);
        rectangle.setHeight(10);

        System.out.println(rectangle.getArea());
    }
}</code></pre>
<blockquote>
<p>50</p>
</blockquote>
<p>정상적으로 넓이가 잘 구해진 것을 확인할 수 있다 !!</p>
<pre><code class="language-java">public class Main {
    public static void main(String[] args) {
        Rectangle rectangle = new Square();

        rectangle.setWidth(5);
        rectangle.setHeight(10);

        System.out.println(rectangle.getArea());
    }
}</code></pre>
<blockquote>
<p>100</p>
</blockquote>
<p>다음으로 정사각형을 직사각형을 업캐스팅을 하여, 메서드를 수행하면 어떻게 될까 ?</p>
<p>위에 출력된 것처럼 <code>100</code> 이라는 값이 나온다.</p>
<p>&quot;너비나 높이든 값이 입력되면 동일하게 설정되도록 작성했으니까 정상적으로 작동된 거 아니야 ?&quot;</p>
<p>맞다. 애초에 위에서 정사각형의 특징을 살려 너비나 높이든 특정 값을 삽입하면, 동일하게 설정되도록 코드를 구현하였다.</p>
<p>이에, <code>rectangle.setHeight(10);</code> 을 통해 넓이는 <code>100</code> 이 나오는 게 맞다.</p>
<p>다만, 현제 예제에서 이러한 결과값은 명백히 LSP 를 위반한 것이다.</p>
<p>왜냐하면, 정사각형은 부모 클래스인 직사각형으로 업캐스팅 되었고, 업캐스팅을 했으면 부모 클래스의 동작 방식대로 유지되어야 하는데, 자식 클래스의 오버라이딩된 로직 때문에 예상과 다른 결과가 나온다.</p>
<p>즉, <code>50</code> 이 출력되어야 하는데 <code>100</code> 이 출력되었고, 이는 자식 클래스가 부모 클래스를 완전히 대체하지 못한다는 것을 반증한다.</p>
<hr>
<h4 id="lsp-준수">LSP 준수</h4>
<p>해결책은 <strong>“정사각형(Square)은 직사각형(Rectangle)의 하위 타입이 아니다!”</strong> 라는 점을 인정하고 반영하는 것이다.</p>
<p>즉, 정사각형과 직사각형을 올바른 상속관계가 형성될 수 없다는 것을 인지하고, 올바르게 성립하는 상속 관계를 구현해야 하다는 것이다.</p>
<pre><code class="language-java">public class Shape {

    public int width;
    public int height;

    // 너비 반환
    public int getWidth() {
        return width;
    }

    // 높이 반환
    public int getHeight() {
        return height;
    }

    // 너비 할당
    public void setWidth(int width) {
        this.width = width;
    }

    // 높이 할당
    public void setHeight(int height) {
        this.height = height;
    }

    // 사각형 넓이 반환
    public int getArea() {
        return width * height;
    }
}
</code></pre>
<pre><code class="language-java">//직사각형 클래스
public class Rectangle extends Shape {

    public Rectangle(int width, int height) {
        setWidth(width);
        setHeight(height);
    }

}</code></pre>
<pre><code class="language-java">//정사각형 클래스
public class Square extends Shape {

    public Square(int length) {
        setWidth(length);
        setHeight(length);
    }

}</code></pre>
<p>직사각형과 정사각형 모두 <code>Shape</code> 클래스를 상속 받으면서, 서로에게 영향을 주지 않도록 설계가 되었다.</p>
<pre><code class="language-java">public class Main {
    public static void main(String[] args) {

        Shape rectangle = new Rectangle(10, 5);
        Shape square = new Square(5);

        System.out.println(rectangle.getArea());
        System.out.println(square.getArea());
    }
}</code></pre>
<blockquote>
<p>50</p>
</blockquote>
<blockquote>
<p>25</p>
</blockquote>
<p>이를 통해 정사각형과 직사각형이 각각의 특성을 유지하면서도, 부모 타입으로 업캐스팅되었을 때 의도한 대로 동작함을 확인할 수 있다.</p>
<hr>
<h2 id="인터페이스-분리-원칙-isp">인터페이스 분리 원칙 (ISP)</h2>
<p>☑️ ISP 원칙은 <strong>인터페이스를 사용 목적에 맞게 잘게 분리해야 한다는 설계 원칙</strong>이다.
☑️ <strong>SRP가 클래스의 단일 책임</strong>을 강조한다면, <strong>ISP는 인터페이스의 단일 책임</strong>을 강조한다고 볼 수 있다.
☑️ ISP 원칙의 목표는** 인터페이스를 사용하는 클라이언트의 목적과 용도에 맞게 인터페이스를 분리<strong>하는 것이다.
☑️ 이때 **한번 분리한 인터페이스는 추후 가급적 변경하지 않는 것이 바람직</strong>하다.</p>
<hr>
<h3 id="isp-예제">ISP 예제</h3>
<h4 id="isp-위반">ISP 위반</h4>
<pre><code class="language-java">public interface SmartPhone {
    void call(String number); // 통화 기능
    void message(String number, String text); // 문제 메세지 전송 기능
    void wirelessCharge(); // 무선 충전 기능
    void AR(); // 증강 현실(AR) 기능
    void biometrics(); // 생체 인식 기능
}</code></pre>
<p>다양한 스마트폰을 구현하기 위해, 스마트폰의 공통적으로 들어갈만한 기능들을 위와 같이 추상화하였다.</p>
<p>인터페이스를 이용하여, 갤럭시 S24 이나 S25 를 구현한다고 해보자. </p>
<pre><code class="language-java">public class S24 implements SmartPhone {
    public void call(String number) {
    }

    public void message(String number, String text) {
    }

    public void wirelessCharge() {
    }

    public void AR() {
    }

    public void biometrics() {
    }
}

public class S25 implements SmartPhone {
    public void call(String number) {
    }

    public void message(String number, String text) {
    }

    public void wirelessCharge() {
    }

    public void AR() {
    }

    public void biometrics() {
    }
}</code></pre>
<p>위 두 기종은 최신 모델들이기에 앞서 작성한 인터페이스의 기능들이 모두 존재할 것이다.</p>
<p>그런데 최신 스마트폰 뿐 아니라 과거 스마트폰도 다뤄여 한다면 ?</p>
<p>구형 모델인 갤럭시S3 의 경우에는 무선충전이나 생체인식 기능 들은 존재하지 않을 것이다.</p>
<p>이를 코드로 작성해보자면, 다음과 같다.</p>
<pre><code class="language-java">public class S3 implements SmartPhone {
    public void call(String number) {
    }

    public void message(String number, String text) {
    }

    public void wirelessCharge() {
        System.out.println(&quot;지원 하지 않는 기능 입니다.&quot;);
    }

    public void AR() {
        System.out.println(&quot;지원 하지 않는 기능 입니다.&quot;);
    }

    public void biometrics() {
        System.out.println(&quot;지원 하지 않는 기능 입니다.&quot;);
    }
}
</code></pre>
<p>해당 모델은 필요하지 않은 기능들이기에, 오버라이딩은 필수로 해야겠지만 null 을 반환토록 하거나, 예외를 발생시키거나 해야할 것이다.</p>
<p>이러나 저러나 불필요한 기능들을 구현해야 하는 낭비가 발생한다.</p>
<hr>
<h4 id="isp-준수">ISP 준수</h4>
<p>앞서 ISP 를 위반한 코드를 개선하기 위해서, 각각의 기능에 맞게 인터페이스를 잘게 분리하도록 구성한다.</p>
<p>그리고 잘게 분리된 인터페이스를 클래스가 지원되는 기능만을 선별하여 implements 하면 ISP 원칙이 지켜지게 된다.</p>
<pre><code class="language-java">// 통화 기능 인터페이스
public interface CallFunction {
    void call(String number);
}

// 메시지 기능 인터페이스
public interface MessageFunction {
    void message(String number, String text);
}

// 무선 충전 기능 인터페이스
public interface WirelessCharging {
    void wirelessCharge();
}

// 증강 현실(AR) 기능 인터페이스
public interface ARFunction {
    void AR();
}

// 생체 인식 기능 인터페이스
public interface Biometrics {
    void biometrics();
}</code></pre>
<pre><code class="language-java">// 최신 스마트폰 (모든 기능을 지원)
public class S24 implements CallFunction, MessageFunction, WirelessCharging, ARFunction, Biometrics {
    public void call(String number) {

    }

    public void message(String number, String text) {

    }

    public void wirelessCharge() {

    }

    public void AR() {

    }

    public void biometrics() {

    }
}

// 구형 스마트폰 (무선충전, AR, 생체인식 기능 없음)
public class S3 implements CallFunction, MessageFunction {
    public void call(String number) {

    }

    public void message(String number, String text) {

    }
}</code></pre>
<p>인터페이스를 역할별로 분리하면 불필요한 기능을 강제로 구현할 필요가 없으며, 클라이언트(S3, S24)는 필요한 인터페이스만 선택적으로 구현할 수 있어 유지보수가 용이하다. 또한, 새로운 기능이 추가되더라도 기존 코드에 영향을 주지 않고 확장할 수 있다.</p>
<p>다만, ISP를 준수하려면 <strong>초기 설계 단계에서 기능의 변화를 미리 고려하여 인터페이스를 설계</strong>해야 하며, 이는 현실적으로 쉽지 않은 작업이다. 결국, 이러한 설계 능력은 개발자의 역량에 달려 있다.</p>
<blockquote>
<p>Q : SRP 를 준수하면, ISP 는 준수되는가 ?</p>
</blockquote>
<blockquote>
<p>A : 그렇지 않다. 예를 들어, <strong>게시글 작성, 수정, 삭제 기능</strong>을 하나의 인터페이스로 추상화했다고 가정하자. 이는 SRP를 준수한 예가 될 수 있지만, 사용자는 강제 삭제 기능을 구현할 필요가 없고, 관리자는 구현해야 할 수도 있다. 이렇게 되면 사용자는 필요 없는 기능을 강제로 구현해야 하므로 ISP를 위반하게 된다. <strong>즉, SRP와 ISP는 별개의 원칙이기 때문에, SRP를 준수한다고 해서 ISP도 반드시 준수되는 것은 아니다.</strong></p>
</blockquote>
<hr>
<h2 id="의존성-역전-원칙-dip">의존성 역전 원칙 (DIP)</h2>
<p>☑️ DIP 원칙은 어떤 Class를 참조해서 사용해야하는 상황이 생긴다면, 그 Class를 직접 참조하는 것이 아니라 그 대상의 상위 요소(추상 클래스 or 인터페이스)로 참조하라는 원칙
☑️ 쉽게 이야기해서 구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻
☑️ 의존 관계를 맺을 때 변화하기 쉬운 것 또는 자주 변화하는 것보다는, 변화하기 어려운 것 거의 변화가 없는 것에 의존하라는 것</p>
<h3 id="dip-예제">DIP 예제</h3>
<h4 id="dip-위반">DIP 위반</h4>
<pre><code class="language-java">public class OrderService {
    private final EmailNotification emailNotification;

    public OrderService() {
        this.emailNotification = new EmailNotification();
    }

    public void placeOrder() {
        System.out.println(&quot;주문이 완료되었습니다.&quot;);
        emailNotification.send(&quot;주문이 완료되었습니다.&quot;);
    }
}</code></pre>
<p>OrderService는 EmailNotification이라는 구체적인 클래스에 직접 의존하고 있다.</p>
<p>만약 EmailNotification 대신 SMSNotification을 사용하려면 OrderService를 직접 수정해야 할 것이다.</p>
<hr>
<h4 id="dip-준수">DIP 준수</h4>
<pre><code class="language-java">public interface Notification {
    void send(String message);
}</code></pre>
<pre><code class="language-java">public class EmailNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println(&quot;이메일 발송: &quot; + message);
    }
}

public class SMSNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println(&quot;SMS 발송: &quot; + message);
    }
}</code></pre>
<pre><code class="language-java">public class OrderService {
    private final Notification notification;

    public OrderService(Notification notification) {
        this.notification = notification;
    }

    public void placeOrder() {
        System.out.println(&quot;주문이 완료되었습니다.&quot;);
        notification.send(&quot;주문이 완료되었습니다.&quot;);
    }
}</code></pre>
<p>이제 OrderService는 Notification 인터페이스에 의존하기 때문에 EmailNotification이든 SMSNotification이든 쉽게 교체할 수 있게된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ OOP ] 상속(Inheritance) 에 대하여]]></title>
            <link>https://velog.io/@sepang-pang/OOP-%EC%83%81%EC%86%8DInheritance-%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC</link>
            <guid>https://velog.io/@sepang-pang/OOP-%EC%83%81%EC%86%8DInheritance-%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC</guid>
            <pubDate>Wed, 26 Feb 2025 13:09:25 GMT</pubDate>
            <description><![CDATA[<h1 id="상속inheritance-이란-">상속(Inheritance) 이란 ?</h1>
<p><strong>상속(Inheritance)</strong> 은 상위 클래스의 특성을 하위 클래스가 이어받아서 재사용하거나 추가, 확장하는 것을 말한다.
이 개념은 코드의 재사용 측면, 계층적인 관계 생성, 유지 보수성 측면에서 중요하다.</p>
<blockquote>
<p>Q : &quot;계층적인 관계 생성&quot; 이 정확히 무슨 의미일까요 ?</p>
</blockquote>
<blockquote>
<p>A : 상속을 통해 클래스들 간에 <strong>부모-자식 관계를 형성</strong>하여, <strong>상위 클래스는 공통적인 기능을 제공</strong>하고, <strong>하위 클래스는 그 기능을 확장</strong>하거나 <strong>특화</strong>하는 구조를 구축하는 것을 의미합니다 !</p>
</blockquote>
<hr>
<h2 id="상속의-필요성">상속의 필요성</h2>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/60f4185b-e30f-40df-abfc-2058a31318e9/image.png" alt=""></p>
<p>상속이 필요한 이유는 프로그래밍에서 자주 발생하는 <strong>&quot;중복 문제&quot;</strong>를 해결하기 위해서이다.</p>
<p>예를 들어, 다양한 직업을 클래스로 표현해야 한다고 가정해 보자. 의사, 선생님, 엔지니어 등 1만 개의 직업 클래스를 각각 정의해야 한다면, <strong>이름, 전화번호, 이메일</strong>과 같은 공통적인 정보가 매번 반복될 것이다. 
이는 마치 <strong>1만 개의 붕어빵 틀을 만들 때</strong> 매번 같은 형태의 <strong>머리, 몸통, 꼬리</strong>를 새로 설계하는 것과 같다. 이처럼 중복된 코드는 유지보수에 불편을 주고 코드가 길어지기만 한다.</p>
<p>하지만 <strong>상속</strong>을 사용하면, <strong>공통된 특성</strong>을 가진 부모 클래스를 설계하고, 이를 바탕으로 각 직업 클래스를 정의할 수 있다. 이는 마치 <strong>기본 틀을 만들어 놓고</strong>, 각 직업별로 추가적인 특징(의사: 전문 분야, 선생님: 교과목 등)을 덧붙이는 방식이다. 이렇게 하면 코드 중복을 줄일 수 있을 뿐만 아니라, 직업 클래스를 유지보수하거나 확장할 때 훨씬 효율적이다.</p>
<p>결국 <strong>상속</strong>은 <strong>공통된 특성을 가진 객체를 체계적으로 관리하고 재사용성 높은 구조</strong>를 설계하기 위해 필요한 중요한 개념이다. 이를 통해 프로그래머는 반복 작업을 줄이고, 더 효율적이고 가독성 높은 코드를 작성할 수 있다.</p>
<hr>
<h2 id="상속의-특징">상속의 특징</h2>
<p>자바는 <strong>다중 상속을 지원하지 않기 때문에</strong>, 하나의 클래스는 <strong>오직 하나의 부모 클래스를 가질 수 있다</strong>. 
하지만 <strong>하나의 부모 클래스는 여러 자식 클래스를 가질 수 있다</strong>. 또한, <strong>부모 클래스가 다른 부모 클래스를 가질 수 있다는 점에서 계층 구조를 확장하는 데 제한이 없다</strong>.</p>
<hr>
<h2 id="상속시-메모리-구조">상속시 메모리 구조</h2>
<p>인스턴스를 생성하면, 실제로 인스턴스 변수에 담기는 것은 해당 인스턴스의 <strong>메모리 주소</strong>이다. 일반적으로 메모리 주소를 통해 해당 인스턴스를 찾아가면, 그 인스턴스에 대한 정보들이 포함된 메모리가 존재한다.</p>
<p>하지만 <strong>상속</strong> 구조에서 인스턴스를 생성하면 두 가지 인스턴스가 존재하게 된다. 첫 번째는 <strong>자식 클래스의 인스턴스</strong>에 대한 정보이고, 두 번째는 <strong>부모 클래스의 인스턴스</strong>에 대한 정보이다. 즉, <strong>상속 관계를 가진 인스턴스는 부모 클래스의 인스턴스 정보도 함께 생성된다.</strong></p>
<p>만약 <strong>부모 클래스</strong>의 메서드를 호출했다고 가정해 보자. <strong>자바는 우선 자식 클래스에서 해당 메서드를 먼저 찾고</strong>, 만약 자식 클래스에 해당 메서드가 없다면 <strong>부모 클래스에서 메서드를 찾는다</strong>. 부모 클래스에도 해당 메서드가 없다면, <strong>컴파일 에러가 발생</strong>한다.</p>
<hr>
<h2 id="상속-사용법">상속 사용법</h2>
<p>상속을 구현하려면 부모 클래스를 정의하고, 자식 클래스는 부모 클래스를 <code>extends</code>하여 상속받는다. 
자식 클래스는 부모 클래스의 <strong>필드</strong>와 <strong>메서드</strong>를 그대로 사용할 수 있으며, 필요한 경우 <strong>오버라이딩</strong>을 통해 메서드를 변경할 수 있다.</p>
<pre><code class="language-java">// 부모 클래스 - Product
public class Product {
    private String name;  
    private double price; 

    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public double getPrice() {
        return price;
    }

    // 상품 정보 출력 메서드
    public void displayInfo() {
        System.out.println(&quot;상품명: &quot; + name);
        System.out.println(&quot;가격: &quot; + price + &quot;원&quot;);
    }
}</code></pre>
<pre><code class="language-java">// 자식 클래스 - Book
public class Book extends Product {
    private String author; 

    public Book(String name, double price, String author) {
        super(name, price);  // 부모 클래스의 생성자 호출
        this.author = author;
    }

    public String getAuthor() {
        return author;
    }

    @Override
    public void displayInfo() {
        super.displayInfo();  // 부모 클래스의 displayInfo() 메서드 호출
        System.out.println(&quot;저자: &quot; + author);
    }
}</code></pre>
<pre><code class="language-java">// 자식 클래스 - Electronics
public class Electronics extends Product {
    private String brand;

    public Electronics(String name, double price, String brand) {
        super(name, price);  // 부모 클래스의 생성자 호출
        this.brand = brand;
    }

    public String getBrand() {
        return brand;
    }

    @Override
    public void displayInfo() {
        super.displayInfo();  // 부모 클래스의 displayInfo() 메서드 호출
        System.out.println(&quot;브랜드: &quot; + brand);
    }
}</code></pre>
<pre><code class="language-java">public class Main {
    public static void main(String[] args) {
        // Book 객체 생성
        Book book = new Book(&quot;자바의 정석&quot;, 25000, &quot;남궁성&quot;);
        book.displayInfo();
        System.out.println();

        // Electronics 객체 생성
        Electronics electronics = new Electronics(&quot;스마트폰&quot;, 500000, &quot;삼성&quot;);
        electronics.displayInfo();
    }
}</code></pre>
<pre><code class="language-java">상품명: 자바의 정석
가격: 25000원
저자: 남궁성

상품명: 스마트폰
가격: 500000원
브랜드: 삼성</code></pre>
<hr>
<h3 id="오버라이딩">오버라이딩</h3>
<p><strong>오버라이딩</strong>은 부모 클래스의 메서드를 자식 클래스에서 재정의하는 방식이다. 부모 클래스의 메서드는 보통 일반적인 기능을 수행하지만, 특정 자식 클래스에서 이 기능을 수정할 필요가 있을 때 <strong>오버라이딩</strong>을 사용하여 부모 메서드와는 다른 내부 로직을 가진 동일한 이름의 메서드를 정의할 수 있다.</p>
<p>오버라이딩을 할 때는 특별히 <code>@Override</code>라는 <strong>Annotation</strong>을 사용한다.</p>
<pre><code class="language-java">public class Product {

    ...

    // 상품 정보 출력 메서드
    public void displayInfo() {
        System.out.println(&quot;상품명: &quot; + name);
        System.out.println(&quot;가격: &quot; + price + &quot;원&quot;);
    }
}</code></pre>
<pre><code class="language-java">// 자식 클래스 - Book
public class Book extends Product {

...

    @Override
    public void displayInfo() {
        super.displayInfo();  // 부모 클래스의 displayInfo() 메서드 호출
        System.out.println(&quot;저자: &quot; + author);
    }
}</code></pre>
<p>위의 코드에서 부모 클래스의 <code>displayInfo()</code> 메서드는 상품명과 가격을 출력하는 기본적인 기능을 담당한다. 반면, 자식 클래스인 <code>Book</code>에서는 <code>displayInfo()</code> 메서드를 <strong>오버라이딩</strong>하여 <strong>저자 정보</strong>를 추가로 출력하는 기능을 추가했다.</p>
<blockquote>
<p><strong>Override Annotation은 꼭 필요할까 ?</strong>
실제로 위 코드에서 @Override를 적지 않아도 displayInfo() 는 자식의 displayInfo() 로 실행된다. 그 이유는 자식 인스턴스에서 displayInfo() 를 먼저 찾기 때문이다. 하지만 @Override를 쓰지 않았을 경우, 자바는 이 메서드가 오버라이드의 목적이라는 것을 모르기 때문에 혹여나 실수로 인한 displayInfo() 를 displayInfoooo() 로 적어도 설계과정에서 에러경고를 띄우지 않는다. 그리고나서 displayInfo() 를 호출한다면, 자식에 displayInfo() 가 없기에 부모의 displayInfo() 를 가져오게 된다. 흔한 실수는 아니겠지만 프로그래밍시 의도하지 않은 문제를 발생시킬 확률을 원천 차단할 수 있으므로 오버라이딩의 의도로 코딩한다면 해당 annotation은 그냥 습관처럼 추가하는 것이 좋을 것 같다.</p>
</blockquote>
<p>오버라이딩 시, 부모 클래스의 메서드보다 자식 클래스의 메서드 접근 제어자가 더 제한적일 수는 없다. 예를 들어, 부모 클래스에서 <code>protected</code>로 정의된 메서드는 자식 클래스에서 <code>private</code>으로 오버라이드할 수 없다.</p>
<p>또한, <code>static</code>, <code>final</code>, <code>private</code> 메서드는 오버라이딩할 수 없으며, <strong>생성자</strong> 또한 오버라이드할 수 없다.</p>
<p>오버라이딩할 때는 부모 클래스와 자식 클래스의 메서드가 <strong>매개변수의 타입</strong>과 <strong>개수</strong>가 동일해야 한다.</p>
<hr>
<h3 id="super">super</h3>
<p><code>super</code>는 <strong>superclass의 super</strong>를 나타내며, 말 그대로 <strong>super 인스턴스</strong>를 가리킨다.</p>
<p>자신의 인스턴스를 가리키는 것은 <code>this</code>이다. <code>this()</code>가 생성자를 호출하는 것처럼, <code>super()</code>는 <strong>부모 클래스의 생성자</strong>를 호출한다.</p>
<p>부모 클래스와 <code>extends</code>된 자식 클래스로 생성된 인스턴스는 메모리에 <strong>(자식 인스턴스 + 부모 인스턴스)</strong>의 메모리 주소를 담는다. 부모 인스턴스도 생성되어야 자식 클래스에서 사용할 수 있기 때문에 메모리에는 두 정보가 모두 생성된다. 자바는 우리가 <code>this</code>나 <code>super</code>로 호출하지 않는 한, 메서드나 필드를 찾기 위해 <strong>자식 인스턴스를 먼저 방문하고</strong>, 그 후 부모를 방문한다.</p>
<ul>
<li><code>super.메서드()</code>를 호출하면 <strong>자식을 거쳐서 부모로 바로 이동</strong>한다.</li>
<li><code>this.메서드()</code>를 호출하면 부모 클래스에 대한 검색은 이루어지지 않는다.</li>
</ul>
<p>부모 클래스의 생성자가 <strong>기본 생성자(no parameter)</strong>라면, 자식 클래스의 생성자는 <code>super()</code>를 통해 부모 생성자를 자동으로 호출한다.</p>
<p>하지만 기본 생성자가 아닌 경우, 자식 생성자에서 <strong><code>super(파라미터);</code>를 작성해 주어야</strong> 부모 생성자를 호출할 수 있다.</p>
<hr>
<h2 id="상속의-단점">상속의 단점</h2>
<p>지금까지 알아본 상속의 장점을 정리하자면 다음과 같다.</p>
<pre><code>- 코드의 확장성, 재사용성 상승
- 중복된 코드 제거가능
- 객체지향 프로그래밍에서의 다형성</code></pre><p>다만, 이러한 상속에도 단점이 존재하는데...</p>
<blockquote>
<p>상속은 코드를 재사용하는 강력한 수단이지만, 항상 최선은 아니다. 잘못 사용하면 오류를 내기 쉬운 소프트웨어를 만들게 된다.
-이펙티브 자바 3판</p>
</blockquote>
<h3 id="1-캡슐화를-깨뜨린다">1. 캡슐화를 깨뜨린다.</h3>
<p>부모 클래스가 어떻게 구현되느냐에 따라 하위 클래스의 동작에 이상이 생길 수 있다.</p>
<p>이를 해결하려면 부모 클래스의 구현 내용을 알아야 하위 클래스에서 해결할 수 있고, 이러한 불필요한 구현 내용 노출은 캡슐화를 깨뜨리는 행위이다.</p>
<h3 id="2-부모-클래스의-필드와-메서드를-찾는데-불편함이-있다">2. 부모 클래스의 필드와 메서드를 찾는데 불편함이 있다.</h3>
<p>자식 클래스에서는 부모 클래스의 필드와 메소드는 보이지 않는다.</p>
<p>그러나 첫 번째 단점에서 설명했듯이 부모 클래스의 내부 구현을 알아야 할 일이 생긴다면, 부모 클래스에 가서 확인을 해야한다.</p>
<p>또한 부모의 필드를 이용할 때에도, 자식 클래스에는 해당 필드가 선언되어 있지 않은 형태이다보니 확인이 필요한 경우 불편함을 겪을 수 있다.</p>
<h3 id="3-부모-클래스의-변경이-자식-클래스에-영향을-미친다">3. 부모 클래스의 변경이 자식 클래스에 영향을 미친다.</h3>
<p>상속은 부모 클래스의 구현에 의존하기 때문에, 부모 클래스의 변경이 자식 클래스에 영향을 미칠 수 있다.</p>
<p>예를 들어, 부모 클래스에서 메서드를 수정하거나 새로운 메서드를 추가하거나 필드를 변경하면, 자식 클래스가 이를 상속받아 사용하는 경우 예상치 못한 동작이나 오류가 발생할 수 있다. </p>
<p>이러한 의존성은 시스템의 유지보수를 어렵게 만들고, 부모 클래스의 변경이 자식 클래스에 불필요한 영향을 미치게 된다.</p>
<pre><code class="language-java">// 부모 클래스 - Product
public class Product {
    private String name;
    private double price;

    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public void displayInfo() {
        System.out.println(&quot;상품명: &quot; + name);
        System.out.println(&quot;가격: &quot; + price + &quot;원&quot;);
    }

    // 부모 클래스에 새로운 메서드를 추가
    public void updatePrice(double newPrice) {
        this.price = newPrice;
    }
}</code></pre>
<pre><code class="language-java">
// 자식 클래스 - Book
public class Book extends Product {
    private String author;

    public Book(String name, double price, String author) {
        super(name, price);
        this.author = author;
    }

    @Override
    public void displayInfo() {
        super.displayInfo();
        System.out.println(&quot;저자: &quot; + author);
    }
}
</code></pre>
<p>이때, Product 클래스에서 updatePrice 메서드를 추가했다고 가정해보자. </p>
<p>Book 클래스는 Product를 상속받았기 때문에, updatePrice 메서드를 자동으로 상속받는다. </p>
<pre><code class="language-java">// 자식 클래스에서 부모의 메서드를 오버라이드하지 않으면
Book book = new Book(&quot;자바의 정석&quot;, 25000, &quot;남궁성&quot;);
book.updatePrice(20000);  // 예상치 못한 가격 업데이트가 일어날 수 있음</code></pre>
<p>이처럼 book 클래스는 원치않은 가격 변경 메서드를 상속 받는 바람에 예상치 못한 상황이 발생할 수 있다는 것이다.</p>
<hr>
<h2 id="결론">결론</h2>
<p>상속은 코드 재사용성과 확장성을 높여주는 개념이지만 잘못 사용하면 캡슐화의 훼손, 부모 클래스 변경에 따른 자식 클래스의 예상치 못한 영향 등 문제를 일으킬 수 있다. </p>
<p>따라서 상속을 사용할 때는 이러한 단점들을 충분히 고려하고, 필요에 따라 상속 대신 다른 설계 패턴을 사용하는 것도 좋은 선택이 될 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ OOP ] 캡슐화(Encapsulation)란?]]></title>
            <link>https://velog.io/@sepang-pang/OOP-%EC%BA%A1%EC%8A%90%ED%99%94Encapsulation%EB%9E%80</link>
            <guid>https://velog.io/@sepang-pang/OOP-%EC%BA%A1%EC%8A%90%ED%99%94Encapsulation%EB%9E%80</guid>
            <pubDate>Wed, 26 Feb 2025 11:27:34 GMT</pubDate>
            <description><![CDATA[<h1 id="캡슐화란-">캡슐화란 ?</h1>
<p><strong>캡슐화</strong>란, 데이터와 해당 데이터를 처리하는 메서드를 하나로 묶고, <strong>외부에서의 직접적인 접근을 제한하는 개념</strong>이다.<br>이를 통해 <strong>데이터의 변경을 안전하게 관리하고, 무분별한 접근을 차단하여 안정성을 높일 수 있다.</strong></p>
<p>쉽게 말해, <strong>객체의 속성과 기능을 하나로 묶고, 외부에는 꼭 필요한 기능만 노출하며 나머지는 내부로 감추는 것</strong>이다.</p>
<hr>
<h2 id="접근-제어자">접근 제어자</h2>
<p>캡슐화를 설명하려면 먼저 접근 제어자에 대한 이해가 선행되어야 한다.</p>
<p>자바는 총 4 가지의 접근 제어자를 제공하며, <code>private</code> <code>default</code> <code>protected</code> <code>public</code> 등이 그것이다.</p>
<p>접근 제어자의 핵심은 <strong>속성과 기능을 외부로부터 숨기는 것</strong>이다.</p>
<pre><code class="language-java">private 은 나의 클래스 안으로 속성과 기능을 숨길 때 사용, 외부 클래스에서 해당 기능을 호출할 수 없다.
default 는 나의 패키지 안으로 속성과 기능을 숨길 때 사용, 외부 패키지에서 해당 기능을 호출할 수 없다.
protected 는 상속 관계로 속성과 기능을 숨길 때 사용, 상속 관계가 아닌 곳에서 해당 기능을 호출할 수 없다.
public 은 기능을 숨기지 않고 어디서든 호출할 수 있게 공개한다.</code></pre>
<hr>
<h2 id="캡슐화를-적용하는-이유">캡슐화를 적용하는 이유</h2>
<p>캡슐화에는 크게 두 가지 측면이 있다.</p>
<blockquote>
<ol>
<li>외부로부터 필드와 메서드에 대한 불필요한 접근을 막는다.</li>
<li>외부로부터 클래스의 구체적인 구현 내용을 감춘다.</li>
</ol>
</blockquote>
<p>이러한 캡슐화의 개념을 적용시키지 않았을 때 무슨 문제가 발생하는지 예시를 통해 알아보도록 하자</p>
<hr>
<h3 id="a-씨와-b-씨의-이야기">A 씨와 B 씨의 이야기</h3>
<p>A 씨는 간단한 <code>BankAccount</code> 클래스를 만들었다. 계좌의 잔액을 나타내는 <code>balance</code> 변수를 <code>public</code>으로 선언하여 누구나 직접 접근할 수 있도록 했다.
A 씨는 <code>BankAccount</code> 클래스를 작성한 후, 동료 B 씨에게 사용하도록 했다. 하지만 B 씨는 실수로 잔액을 직접 변경하는 코드를 작성했다.</p>
<pre><code class="language-java">class BankAccount {
    public int balance; // 외부에서 직접 접근 가능

    public BankAccount(int balance) {
        this.balance = balance;
    }
}

public class Main {
    public static void main(String[] args) {
        BankAccount account = new BankAccount(1000);

        // B 씨가 실수로 잔액을 마음대로 변경함
        account.balance = -500; // 말도 안 되는 음수 잔액 발생!

        System.out.println(&quot;잔액: &quot; + account.balance); // 잔액: -500 (비정상적인 값)
    }
}</code></pre>
<p>위 코드를 살펴보면 B 씨가 실수로 <code>balance</code> 값을 음수(-5000)로 설정해버렸다.
정상적인 은행 시스템에서는 잔액이 음수가 되면 안 됨에도 불구하고, A 씨는 이를 막지 못했다.</p>
<p>해당 사례를 통해 캡슐화를 신경 쓰지 않는다면 다음과 같은 문제점이 발생한다.</p>
<blockquote>
<ol>
<li>필드와 메서드의 불필요한 접근은 예상치 못한 오류를 야기할 수 있다.
-&gt; ex ) <code>balance</code> 에 -500 과 같은 비정상적인 값이 설정될 수 있음.</li>
<li>필드가 여러 곳에서 수정될 수 있기 때문에 유지보수성이 저하된다.
-&gt; ex ) 다른 개발자가 balance를 잘못 수정하는 경우, 어디에서 문제가 발생했는지 추적하기 어려움</li>
<li>내부 구현이 그대로 노출되기 때문에, 클래스 내부 로직을 보호할 방법이 없다.
-&gt; ex ) <code>balance</code> 를 외부에서 직접 변경하면, 은행 시스템에서 잔액 변경 로그를 기록하는 기능을 우회할 수도 있음</li>
<li>객체의 일관성이 깨진다.
-&gt; ex ) 특정 로직에서는 <code>balance</code> 를 음수로 설정할 수 없도록 구현했지만, 외부에서 <code>balance = -5000;</code> 처럼 직접 변경하면 무용지물이 됨</li>
</ol>
</blockquote>
<hr>
<p>A 씨는 실수를 깨닫고, <code>balance</code> 변수를 <code>private</code> 으로 감추고 잔액을 안전하게 변경할 수 있는 메서드를 추가했다.</p>
<pre><code class="language-java">class BankAccount {
    private int balance; // 외부에서 직접 접근하지 못하도록 변경

    public BankAccount(int balance) {
        if (balance &lt; 0) {
            throw new IllegalArgumentException(&quot;초기 잔액은 0 이상이어야 합니다.&quot;);
        }
        this.balance = balance;
    }

    // 현재 잔액을 조회하는 메서드
    public int getBalance() {
        return balance;
    }

    // 잔액을 변경할 때 유효성 검사 추가
    public void deposit(int amount) {
        if (amount &lt;= 0) {
            throw new IllegalArgumentException(&quot;입금 금액은 0보다 커야 합니다.&quot;);
        }
        balance += amount;
    }

    public void withdraw(int amount) {
        if (amount &gt; balance) {
            throw new IllegalArgumentException(&quot;잔액이 부족합니다.&quot;);
        }
        balance -= amount;
    }
}</code></pre>
<pre><code class="language-java">public class Main {
    public static void main(String[] args) {
        BankAccount account = new BankAccount(1000);

        account.deposit(500); // ✅ 정상 입금
        System.out.println(&quot;현재 잔액: &quot; + account.getBalance()); // 1500 출력

        account.withdraw(2000); // ❌ 예외 발생 (잔액 부족)
    }
}</code></pre>
<p>이제 <code>BankAccount</code> 클래스에 캡슐화를 적용하여 <code>balance</code> 값을 외부에서 임의로 변경하는 것을 막을 수 있게 되었다.
B 씨는 더 이상 <code>balance</code> 변수를 직접 조작할 수 없으며, <code>deposit()</code> 과 <code>withdraw()</code> 메서드를 통해서만 잔액을 변경할 수 있다.
또한, 이 두 메서드에서 유효성 검사를 수행하여 잘못된 값의 입력을 방지함으로써 데이터의 무결성을 유지할 수 있게 되었다.</p>
<p>즉, <strong>잔액을 어떻게 저장하고 변경하는지는 숨기고, 외부에서는 정해진 메서드(deposit(), withdraw())만 사용하게 강제</strong>하는 구조가 된 것이다 !</p>
<hr>
<h2 id="결론">결론</h2>
<p>캡슐화를 적용하지 않으면 <strong>데이터 보호가 어렵고</strong>, <strong>시스템의 안정성이 떨어지며, 유지보수가 복잡해지는 문제가 발생한다.</strong>
따라서, 중요한 데이터는 반드시 <code>private</code> 으로 보호하고, 필요한 경우 <code>getter/setter</code> 또는 특정 메서드를 통해 안전하게 접근하도록 하자 !</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP 웹 기본 지식 ( 8 ) - HTTP 헤더2/캐시와 조건부 요청]]></title>
            <link>https://velog.io/@sepang-pang/HTTP-%EC%9B%B9-%EA%B8%B0%EB%B3%B8-%EC%A7%80%EC%8B%9D-8-HTTP-%ED%97%A4%EB%8D%942%EC%BA%90%EC%8B%9C%EC%99%80-%EC%A1%B0%EA%B1%B4%EB%B6%80-%EC%9A%94%EC%B2%AD</link>
            <guid>https://velog.io/@sepang-pang/HTTP-%EC%9B%B9-%EA%B8%B0%EB%B3%B8-%EC%A7%80%EC%8B%9D-8-HTTP-%ED%97%A4%EB%8D%942%EC%BA%90%EC%8B%9C%EC%99%80-%EC%A1%B0%EA%B1%B4%EB%B6%80-%EC%9A%94%EC%B2%AD</guid>
            <pubDate>Mon, 24 Feb 2025 12:28:52 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>본 게시글은 인프런의 모든 개발자를 위한 HTTP 웹 기본 지식 (김영한) 강의를 듣고 정리한 내용입니다.</p>
</blockquote>
<h2 id="캐시-기본-동작">캐시 기본 동작</h2>
<h3 id="캐시가-없을-때">캐시가 없을 때</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/07a1aba7-2a98-46c5-8884-1fa22ec8844a/image.png" alt=""></p>
<ul>
<li>데이터가 변경되지 않아도 네트워크를 통해 다시 다운로드해야 함 → 브라우저 로딩 속도 저하</li>
<li>인터넷 네트워크는 메모리나 하드디스크보다 상대적으로 비용이 높음</li>
</ul>
<h3 id="캐시가-적용되었을-때">캐시가 적용되었을 때</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/331f3b6b-e18e-4dfa-9718-3bb4f97b0038/image.png" alt=""></p>
<ul>
<li><code>Cache-Control</code>을 통해 캐시 유효시간 설정 가능</li>
<li>유효시간 동안 네트워크를 사용하지 않음 → 브라우저 로딩 속도 향상</li>
<li>인터넷 네트워크 사용량 감소</li>
</ul>
<h3 id="캐시-시간이-초과되었을-때">캐시 시간이 초과되었을 때</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/e8e49966-7199-4208-a0f4-a62092c8de6c/image.png" alt=""></p>
<ul>
<li>서버에서 데이터를 다시 조회하고, 캐시를 갱신</li>
<li>네트워크 다운로드가 다시 발생</li>
<li>클라이언트와 서버의 데이터가 같아도 변경되지 않았다면 다시 다운로드 발생</li>
</ul>
<h3 id="캐시-시간-초과-시-로컬-캐시-재사용">캐시 시간 초과 시 로컬 캐시 재사용</h3>
<ul>
<li>기존 데이터가 변경되지 않았을 경우, 검증 헤더를 사용하여 로컬 캐시를 재사용할 수 있음</li>
<li>클라이언트와 서버가 가진 데이터가 동일함이 검증된 경우에만 가능</li>
</ul>
<h2 id="검증-헤더와-조건부-요청">검증 헤더와 조건부 요청</h2>
<h3 id="첫-번째-요청-시---검증-헤더-추가">첫 번째 요청 시 - 검증 헤더 추가</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/1b69bcee-aa28-4331-959f-cfc017ac9479/image.png" alt=""></p>
<ul>
<li><code>Last-Modified</code>(데이터 최종 수정일)를 응답 헤더에 포함</li>
<li>클라이언트는 <code>Last-Modified</code> 값을 캐시에 저장</li>
</ul>
<h3 id="두-번째-요청-시---검증-헤더-추가--조건부-요청">두 번째 요청 시 - 검증 헤더 추가 + 조건부 요청</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/7b2905ae-3c80-4847-9efe-aade99e17d1d/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/f820d6ac-f4bf-47e5-8174-fb0ebe69e91d/image.png" alt=""></p>
<ul>
<li><code>Last-Modified</code> 비교 시 변경되지 않았다면 <code>304 Not Modified</code> 응답</li>
<li>클라이언트는 기존 캐시된 데이터를 재사용</li>
</ul>
<blockquote>
<p><strong>Q: 캐시에 저장된 데이터를 재활용한다는 것은 만료된 데이터가 남아 있다는 의미인가?</strong><br><strong>A:</strong> 캐시된 데이터가 만료되었더라도 검증 과정을 거쳐 변경되지 않았음을 확인하면 재활용할 수 있음. 즉, 일부 데이터가 남아있다는 것.</p>
</blockquote>
<h3 id="검증-헤더">검증 헤더</h3>
<ul>
<li><code>Last-Modified</code>, <code>ETag</code>를 활용하여 캐시 데이터와 서버 데이터의 일치 여부를 검증</li>
</ul>
<h3 id="조건부-요청-헤더">조건부 요청 헤더</h3>
<ul>
<li><code>If-Modified-Since</code>: <code>Last-Modified</code> 값 사용</li>
<li><code>If-None-Match</code>: <code>ETag</code> 값 사용</li>
<li>조건이 만족하면 <code>200 OK</code>, 만족하지 않으면 <code>304 Not Modified</code></li>
</ul>
<h3 id="if-modified-since">If-Modified-Since</h3>
<ul>
<li>이후 데이터가 수정되었을 경우 새로운 데이터 제공</li>
<li>1초 미만 단위로 캐시 조정 불가능</li>
<li>날짜 기반 로직 사용 (예: <code>2020년 11월 10일 10:00:00</code>)</li>
<li>데이터를 수정했지만 동일한 내용을 다시 저장하는 경우 날짜는 변경되지만 데이터는 같을 수 있음</li>
</ul>
<blockquote>
<p><strong>Q: 데이터를 복사 붙여넣기 하면 수정된 날짜가 다르지만 데이터는 같은 건가?</strong><br><strong>A:</strong> 맞음. 내용이 변하지 않아도 저장 시점이 다르면 <code>Last-Modified</code> 값은 달라질 수 있음.</p>
</blockquote>
<h3 id="etag-if-none-match">ETag, If-None-Match</h3>
<ul>
<li>캐시 데이터에 해시 기반의 고유한 식별자를 부여<ul>
<li>예) <code>ETag: &quot;v1.0&quot;</code>, <code>ETag: &quot;a2jiodwjekjl3&quot;</code></li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/5ee7ddad-9741-4fc9-80d2-4ba1791c77d6/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/07c6c27a-14cc-4cc8-84fc-162812e1b817/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/f5e204c9-3715-41da-91f1-42f85f7d7806/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/84151419-ac50-45b5-8685-a2059a3770c4/image.png" alt=""></p>
<ul>
<li>파일의 콘텐츠가 변경될 경우 해시값이 달라지므로 새로운 캐시로 갱신됨</li>
</ul>
<blockquote>
<p><strong>Q: Last-Modified의 단점을 보완하기 위해 ETag를 활용한다고 하는데, 어떻게 동작하는가?</strong><br><strong>A:</strong> <code>Last-Modified</code>는 날짜 기반으로 변경을 감지하지만, <code>ETag</code>는 파일의 내용 기반으로 변경을 감지함.<br>즉, <code>Last-Modified</code>는 파일의 내용이 변하지 않아도 수정 시간이 갱신되면 다르게 판단하지만, <code>ETag</code>는 파일의 내용이 같다면 같은 해시값을 유지하므로 불필요한 다운로드를 줄일 수 있음.</p>
</blockquote>
<h2 id="캐시-제어-헤더">캐시 제어 헤더</h2>
<ul>
<li><code>Cache-Control</code>: 캐시 제어</li>
<li><code>Pragma</code>: HTTP 1.0 하위 호환용 캐시 제어</li>
<li><code>Expires</code>: HTTP 1.0 캐시 유효 기간</li>
</ul>
<h3 id="cache-control---캐시-지시어-directives">Cache-Control - 캐시 지시어 (Directives)</h3>
<ul>
<li><code>max-age</code>: 캐시 유효 시간(초 단위)</li>
<li><code>no-cache</code>: 캐시 데이터를 사용하기 전에 항상 원 서버에서 검증 필요</li>
<li><code>no-store</code>: 민감한 정보가 포함된 데이터는 캐시 저장 불가 (즉시 삭제)</li>
</ul>
<h2 id="프록시-캐시">프록시 캐시</h2>
<h3 id="프록시란">프록시란?</h3>
<ul>
<li>사용자의 요청을 대신 처리하여 서버로 전달하는 역할 (중간 역할)</li>
<li>원 서버에서 직접 데이터를 가져오는 대신 프록시 캐시 서버를 통해 가져옴</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/428b9c25-9b8e-42df-b0f8-6c9c30fe112d/image.png" alt=""></p>
<h3 id="cache-control---기타-캐시-지시어">Cache-Control - 기타 캐시 지시어</h3>
<ul>
<li><code>public</code>: 응답이 공용 캐시에 저장 가능</li>
<li><code>private</code>: 응답이 특정 사용자 전용 (기본값)</li>
<li><code>s-maxage</code>: 프록시 캐시에만 적용되는 <code>max-age</code></li>
<li><code>Age</code>: 프록시 캐시에 머문 시간 (초 단위)</li>
</ul>
<h2 id="캐시-무효화">캐시 무효화</h2>
<h3 id="cache-control---확실한-캐시-무효화">Cache-Control - 확실한 캐시 무효화</h3>
<ul>
<li><code>no-cache</code>: 캐시 데이터를 사용하기 전에 항상 원 서버 검증 필요</li>
<li><code>no-store</code>: 민감한 데이터이므로 캐시 저장 불가 (즉시 삭제)</li>
<li><code>must-revalidate</code>: 캐시 만료 후 반드시 원 서버 검증 필요, 실패 시 오류 (<code>504 Gateway Timeout</code>)</li>
<li><code>Pragma: no-cache</code>: HTTP 1.0 하위 호환</li>
</ul>
<blockquote>
<p><strong>Q: <code>no-cache</code>는 캐시를 사용할 수 있는데, 원 서버 검증이 필요하다는 것이 무슨 의미인가?</strong><br><strong>A:</strong> 캐시를 보관하지만 사용 전에 원 서버의 검증을 거쳐야 함. 변경되지 않았다는 확인이 되면 캐시를 사용함.</p>
</blockquote>
<h3 id="no-cache-vs-must-revalidate">no-cache vs must-revalidate</h3>
<h4 id="no-cache">no-cache</h4>
<ul>
<li>기본적으로 원 서버의 검증을 받아 캐시 데이터를 사용</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/83efbba7-b642-4952-a17d-c494c2e0f2f9/image.png" alt=""></p>
<ul>
<li>네트워크 단절 시 검증을 받지 못하면 프록시 서버에서 기존 데이터를 반환할 수도 있음</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/e24ab73d-be47-4965-a972-ca477f7748f4/image.png" alt=""></p>
<blockquote>
<p><strong>Q: 웹 브라우저 → 프록시 캐시 서버 → 원 서버 → 프록시 캐시 서버 → 브라우저 캐시를 거쳐 클라이언트에 반환되는 흐름이 맞는가?</strong><br><strong>A:</strong> 맞음. 프록시 캐시 서버가 상태 코드(<code>304 Not Modified</code>)만 반환하면 브라우저는 기존 캐시된 데이터를 사용함.</p>
</blockquote>
<h4 id="must-revalidate">must-revalidate</h4>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/d6c7347c-b057-4bf8-9788-035fa111e4d1/image.png" alt=""></p>
<ul>
<li>네트워크 단절 시 검증이 불가능하면 오류를 반환함 (504 Gateway Timeout)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[ HTTP 웹 기본 지식 ( 7 ) - HTTP 헤더1/일반 헤더]]></title>
            <link>https://velog.io/@sepang-pang/HTTP-%EC%9B%B9-%EA%B8%B0%EB%B3%B8-%EC%A7%80%EC%8B%9D-6-HTTP-%ED%97%A4%EB%8D%941%EC%9D%BC%EB%B0%98-%ED%97%A4%EB%8D%94</link>
            <guid>https://velog.io/@sepang-pang/HTTP-%EC%9B%B9-%EA%B8%B0%EB%B3%B8-%EC%A7%80%EC%8B%9D-6-HTTP-%ED%97%A4%EB%8D%941%EC%9D%BC%EB%B0%98-%ED%97%A4%EB%8D%94</guid>
            <pubDate>Mon, 24 Feb 2025 12:16:10 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>본 게시글은 인프런의 모든 개발자를 위한 HTTP 웹 기본 지식 (김영한) 강의를 듣고 정리한 내용입니다.</p>
</blockquote>
<h1 id="http-헤더">HTTP 헤더</h1>
<hr>
<h2 id="http-헤더-용도">HTTP 헤더 용도</h2>
<p>HTTP 전송에 필요한 모든 부가 정보를 말한다. </p>
<blockquote>
<p>EX ) 메시지 바디의 내용, 메시지 바디의 크기, 압축, 인증, 요청 클라이언트, 서버 정보, 캐시 관리 정보 ...</p>
</blockquote>
<p>이러한 표준 헤더는 정말 맣고, 필요시 개발자가 임의로 추가 가능하다.</p>
<h3 id="rfc7230">RFC7230</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/73222a5f-7f8b-4f45-9079-f9b0d3de839c/image.png" alt=""></p>
<p>현재 HTTP 최신 버전은 RFC7230 버전이다.</p>
<p>해당 버전은 메시지 본문(=payload) 을 통해 표현 데이터를 전달한다.</p>
<blockquote>
<p>표현 = 표현 헤더 + 표현 데이터</p>
</blockquote>
<p>이때 표현 헤더는 데이터 유형, 길이 압축 정보 등등 표현 데이터를 해석할 수 있는 정보를 제공한다.</p>
<blockquote>
<p>Q : 왜 표현이라고 얘기하는 걸까 ?</p>
</blockquote>
<blockquote>
<p>A : 예를 들어, 회원 데이터를 조회한다고 가정해 보자. 실제로 DB에서 회원 정보를 조회한 후 HTTP를 통해 클라이언트에 전달할 때, 이 데이터를 HTML로 “표현”할 수도 있고, JSON으로 “표현”할 수도 있다. 즉, 같은 리소스라도 다양한 방식으로 전달될 수 있기 때문에, 이를 명확하게 구분하기 위해 “표현”이라는 용어를 사용하는 것이다.</p>
</blockquote>
<hr>
<h2 id="표현">표현</h2>
<p>표현 헤더는 요청, 응답 모두 사용한다.</p>
<h3 id="content-type">Content-Type</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/104bb533-5b3b-41d7-8262-826855f0e3b5/image.png" alt=""></p>
<p><code>Content-Type</code>  은 쉽게 말해서 &quot;body 에 들어가는 내용이 뭐야 ?&quot; 라는 것이다.</p>
<p>즉, <code>Content-Type</code> 은 표현 데이터의 형식을 설명한다. 보통 미디어 타입, 문자 인코딩 등을 여기에 표시한다.</p>
<blockquote>
<p>ex )</p>
</blockquote>
<ul>
<li>text/html; charset=utf-8</li>
<li>application/json</li>
<li>image/png</li>
</ul>
<h3 id="content-encoding">Content-Encoding</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/7e598e5e-c9c8-44c3-83be-d9de721153e3/image.png" alt=""></p>
<p><code>Content-Encoding</code> 은 표현 데이터를 압축하는 방식이다.
데이터를 전송하는 측에서 메시지 바디를 압축하면, 수신 측은 이를 해제해야 한다.
따라서 클라이언트가 압축 형식을 인식할 수 있도록 <code>Content-Encoding</code> 에 압축 방식을 명시한다.</p>
<blockquote>
<p>ex&gt; gzip, deflate, identity</p>
</blockquote>
<h3 id="content-language">Content-Language</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/07c529cf-a290-4f6e-b09a-3aefb2af9b2c/image.png" alt=""></p>
<p>Content-Language는 표현 데이터의 자연 언어를 표시한다.</p>
<blockquote>
<p>ex&gt; ko, en, en-US</p>
</blockquote>
<h3 id="content-length">Content-Length</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/804b6d74-27ee-410f-8443-fb94b37aeabd/image.png" alt=""></p>
<p>Content-Length는 표현 데이터의 길이를 의미한다.
길이는 바이트 단위이다.</p>
<hr>
<h2 id="콘텐츠-협상">콘텐츠 협상</h2>
<p>협상은 <strong>클라이언트가 선호하는 표현을 서버에 요청하는 것</strong>이다.
 
클라이언트가 선호하는 표현을 서버에 제시하고, 서버는 환경이 되는 한 그 클라이언트의 선호도를 맞춰주려고 노력한다.
이러한 과정이 일상의 협상 처럼 보인다.
 
협상은 클라이언트의 선호를 서버로 요청하기 때문에, 요청 시에만 사용이 가능하다.</p>
<table>
<thead>
<tr>
<th>협상 종류</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>Accept</code></td>
<td>클라이언트가 선호하는 미디어 타입</td>
</tr>
<tr>
<td><code>Accept-Charset</code></td>
<td>클라이언트가 선호하는 문자 인코딩</td>
</tr>
<tr>
<td><code>Accept-Encoding</code></td>
<td>클라이언트가 선호하는 압축 인코딩</td>
</tr>
<tr>
<td><code>Accept-Language</code></td>
<td>클라이언트가 선호하는 자연 언어</td>
</tr>
</tbody></table>
<h3 id="accept-language-작동-방식">Accept-Language 작동 방식</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/86cfc2b0-d702-4699-b5ab-ced45cf9e578/image.png" alt=""></p>
<p>위 예시처럼 클라이언트가 한국어를 선호한다고 서버에 알리면, 서버는 자신이 지원하는 언어 목록을 확인한 후, 한국어를 지원할 경우 해당 언어로 데이터를 반환한다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/f238a96d-bea6-4c1d-8a26-037bf7fe7c29/image.png" alt=""></p>
<p>하지만, 세상은 녹록지 않다. 모든 것이 뜻대로 되지 않는다.
클라이언트가 요청한 언어를 안타깝게도 서버에서 지원하지 않으면, 서버는 default 언어로 응답을 반환한다.</p>
<p>그렇다면 이 문제를 어떻게 해결할 수 있을까 ?</p>
<h3 id="협상과-우선순위">협상과 우선순위</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/5f019cab-d57b-4683-aa8e-1e5065eac956/image.png" alt=""></p>
<p>이를 해결하기 위해 <strong>Quality Values(q)</strong> 를 사용한다.</p>
<p><strong>Quality Value(q)란?</strong></p>
<ul>
<li>0과 1 사이의 유리수로, 우선순위를 나타낸다.</li>
<li>값이 1에 가까울수록 우선순위가 높다.</li>
<li>생략하면 기본값으로 1이 지정된다.</li>
</ul>
<p><strong>Quality Value를 사용하면</strong> 위와 같은 문제를 어느 정도 해결할 수 있다.</p>
<p>이번엔 Accept-Language 대신 Accept를 보자.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/cacdd6f3-4f21-4f52-b2e2-699e8ed9b2df/image.png" alt=""></p>
<p>위의 경우(Quality Value 사용 X)에는 구체적으로 요구할수록 우선순위가 높아진다.
따라서 우선순위는 아래와 같다.</p>
<blockquote>
<ol>
<li>text/plain;format=flowed</li>
<li>text/plain</li>
<li>text/*</li>
<li><em>/</em></li>
</ol>
</blockquote>
<p>이때 Quality Value를 사용하면 더욱 명확해진다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/aa00e931-fa27-4361-8c24-77a596b196d0/image.png" alt=""></p>
<hr>
<h2 id="전송-방식">전송 방식</h2>
<h3 id="단순-전송">단순 전송</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/a172cce2-48db-49a1-9203-00f3e69982d5/image.png" alt=""></p>
<p>단순 전송은 <strong>요청하면 다 전송해주는 방식으로 전송 콘텐츠에 대한 길이값을 단순히 요청하고 다 받는 것</strong>이다.</p>
<h3 id="분할-전송">분할 전송</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/82083fee-f9d6-4130-a383-fcd5371a3f37/image.png" alt=""></p>
<p>하지만 <strong>전송해야 할 데이터 크기를 미리 알 수 없는 경우</strong>가 있을 것이다. 이때 <strong>Transfer-Encoding: chunked</strong> 방식을 사용하는 분할 전송 방식을 사용할 수 있다. </p>
<p>서버가 분할해서 데이터를 보내주면, 클라이언트는 오는 순서대로 데이터를 받고 바로바로 화면에 보여준다.</p>
<p>이떄 분할 전송은 <code>Content-Length</code> 를 넣으면 안된다. 왜냐하면 각각 쪼개진 길이를 모르기 때문 !!</p>
<blockquote>
<p>ex)  스트리밍 데이터, 동적으로 생성되는 콘텐츠 등</p>
</blockquote>
<h3 id="압축-전송">압축 전송</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/b2bf6bd2-f5d4-4f75-965e-5c9e90515313/image.png" alt=""></p>
<p>압축 전송은 <strong>서버에서 메시지를 압축해서 전송하는 것</strong>을 말한다.
이 때 압축 방식을 표기하면, 서버에서 표기된 압축 방식을 확인하고 알맞은 방식으로 압축 해제를 진행할 수 있다.</p>
<h3 id="범위-전송">범위 전송</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/3d85a396-4d23-4a2e-8741-c5326d9f6de1/image.png" alt=""></p>
<p>범위 전송은 <strong>메시지의 범위를 바이트 단위 기준으로 정하여 그 부분만 전송하는 방식</strong>이다.</p>
<p>예를 들어 원래 보내던 전송이 중간에 끊겼을 때, 다시 처음부터 보내면 낭비가 있으므로 끊긴 부분부터 범위를 설정하여 재전송한다.</p>
<hr>
<h2 id="일반-정보">일반 정보</h2>
<p>HTTP 헤더의 일반 정보에는 <code>From</code> <code>Referer</code> <code>User-Agent</code> <code>Server</code> <code>Date</code> 와 같은 속성들이 있다.</p>
<h3 id="from">From</h3>
<p><code>From</code> 은 <strong>유저의 이메일 정보</strong>를 말한다.</p>
<p>일반적으로 잘 사용되지는 않고, 검색 엔진 같은 곳에서 사용한다.</p>
<p><strong>요청에서만</strong> 사용한다</p>
<h3 id="referer">Referer</h3>
<p><code>Referer</code> 은 <strong>현재 요청된 페이지의 이전 웹 페이지 주소</strong>를 말한다.</p>
<p>이 <code>Referer</code> 을 사용해서 유입 경로를 분석할 수 있다.</p>
<p><strong>요청에서만</strong> 사용한다.</p>
<blockquote>
<p>ex) google -&gt; wiki =&gt; 이때 goole 의 Referer 은 wiki.</p>
</blockquote>
<h3 id="user-agent">User-Agent</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/b39d273e-6cb7-4756-a181-502fec728c52/image.png" alt=""></p>
<p><code>User-Agent</code> 는 <strong>클라이언트의 애플리케이션 정보</strong>를 말한다. (웹 브라우저 정보 등)
 
<code>User-Agent</code> 를 통해 통계 정보를 내거나, 어떤 종류의 브라우저에서 장애가 발생하는지 파악할 수 있다.
 
<strong>요청에서만</strong> 사용한다.</p>
<h3 id="server">Server</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/9c1fd817-81bf-4b96-9435-1d827cb2361a/image.png" alt=""></p>
<p><code>Server</code> 는 <strong>요청을 처리하는 ORIGIN 서버의 소프트웨어 정보</strong>를 말한다.</p>
<blockquote>
<p>Q : ORIGIN 서버가 뭔가요 ?</p>
</blockquote>
<blockquote>
<p>A : HTTP 통신에서는 요청이 프록시 서버, 캐시 서버 등 여러 중간 서버를 거칠 수 있다. 하지만 이러한 중간 서버가 아닌, 실제 요청을 처리하는 원본 서버를 ORIGIN 서버라고 한다. 즉, ORIGIN 서버는 HTTP 요청에 최종적으로 응답하는 진짜 서버이다.</p>
</blockquote>
<p><strong>응답에서만</strong> 사용한다.</p>
<h3 id="date">Date</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/d865c0f1-2278-4667-9aea-ec06dd081eba/image.png" alt=""></p>
<p><code>Date</code> 는** 메시지가 발생한 날짜와 시간<strong>을 말한다.
 
**응답에서만</strong> 사용한다. (과거에는 요청에서도 사용)</p>
<hr>
<h2 id="특별한-정보">특별한 정보</h2>
<p>HTTP 헤더의 특별한 정보에는 <code>Host</code> <code>Location</code> <code>Allow</code> <code>Retry-After</code> 와 같은 속성들이 있다.</p>
<h3 id="host">Host</h3>
<p><code>Host</code> 는 <strong>요청한 호스트 정보(도메인)</strong>을 말한다.</p>
<p>HTTP 통신에서 이런 경우가 있다.</p>
<ul>
<li>하나의 서버가 여러 도메인을 처리해야 할 때</li>
<li>하나의 IP 주소에 여러 도메인이 적용되어 있을 때</li>
</ul>
<p>이러한 경우, Host를 활용해서 해결할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/fe3bd0ea-ab17-4e60-b574-17cfbcf75fb1/image.png" alt=""></p>
<p><strong>요청에서 필수</strong>로 사용해야 한다.</p>
<h3 id="location">Location</h3>
<p><code>Location</code> 은 페이지 리다이렉션 위치를 말한다.</p>
<p>웹 브라우저는 3xx 응답의 결과에 <code>Location</code> 헤더가 있으면, <code>Location</code> 위치로 자동 이동한다.</p>
<p>또한 201(Created) 코드에서도 요청에 의해 생성된 리소스 URI를 반환할 때 사용한다.</p>
<h3 id="allow">Allow</h3>
<p> <code>Allow</code> 는 <strong>허용 가능한 HTTP 메서드</strong>를 말한다.</p>
<p>URI 경로는 있지만 지원하지 않는 HTTP 메서드로 요청할 경우, 405 (Method Not Allowed) 응답 코드와 함께 허용된 메서드 목록을 함께 전달한다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/6ba1d2b6-5e80-4f37-97c0-d56e3dc79368/image.png" alt=""></p>
<h3 id="retry-after">Retry-After</h3>
<p><code>Retry-After</code> 는 유저 에이전트가 다음 요청을 하기까지 기다려야 하는 시간을 말한다.
<code>Retry-After</code> 을 통해 서비스가 언제까지 불능인지를 알 수 있다.</p>
<blockquote>
<p>ex ) </p>
<ul>
<li>서버 과부하 (503 Service Unavailable)</li>
<li>서버 유지보수 (503 Service Unavailable)</li>
<li>Rate Limiting (429 Too Many Requests)</li>
</ul>
</blockquote>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/ad54f7d0-c58a-4740-a61a-81a0dfb39c05/image.png" alt=""></p>
<p>위 사진처럼 날짜, 초 단위 표기 두 가지 방식이 있다.</p>
<hr>
<h2 id="인증">인증</h2>
<p>HTTP 헤더의 인증 정보에는 <code>Authorization</code> <code>WWW-Authenticate</code> 와 같은 속성들이 있다.</p>
<h3 id="authorization">Authorization</h3>
<p><code>Authorization</code> 은 <strong>클라이언트 인증 정보를 서버에 전달하는 것</strong>을 말한다.</p>
<blockquote>
<p>! 인증 방식마다 들어가는 값은 모두 다르다.</p>
</blockquote>
<h3 id="www-authenticate">WWW-Authenticate</h3>
<p><code>WWW-Authenticate</code> 는 <strong>리소스 접근 시 필요한 인증 방법을 정의한 것</strong>을 말한다.</p>
<p>만약 접근했는데 인증에 문제가 있다면, 401 Unauthorized 응답을 남기고 함께 사용한다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/e2d8e35e-2178-47ef-8fb7-0ae2470ce095/image.png" alt=""></p>
<p>&quot; 위 정보들을 참고해서 인증을 다시 해줘 ~ &quot; 라는 뜻이다.</p>
<hr>
<h2 id="쿠키">쿠키</h2>
<p>쿠키란 웹 서버에서 생성하여, 브라우저에 전송 &amp; 저장되는 작은 텍스트 파일이다.</p>
<p>쿠키를 사용할 때는 set-cookie, cookie 두 가지를 사용함</p>
<blockquote>
<p>Set-Cookie : 서버가 클라이언트로 쿠키 전달 (응답)
Cookie: 클라이언트가 서버에서 받은 쿠키를 저장하고, HTTP 요청시 서버로 전달</p>
</blockquote>
<h3 id="쿠키-활용">쿠키 활용</h3>
<p>HTTP 는 무상태(Stateless) 프로토콜이라고 배웠다.
그래서 우리는 매번 데이터를 HTTP 요청에 같이 보냈어야 했다.
<img src="https://velog.velcdn.com/images/sepang-pang/post/6a7c30ca-d2b7-43d1-b5ae-7458031dc85a/image.png" alt=""></p>
<p>HTTP의 무상태 특성상, 각 요청은 독립적이며 서버는 이전 요청의 상태를 기억하지 않는다. 따라서 인증 정보를 함께 전송하지 않으면, 서버는 매 요청마다 우리가 새로울 것이다. 이로 인해 서버의 기억을 되살려주기 위해 매 요청 시마다 “홍길동”과 같은 사용자 정보를 함께 보내야 한다.</p>
<p>이거 낭비가 크다.. 보안적 이슈도 있고 여러모로 문제가 많다.</p>
<p>이러한 문제를 쿠키가 해결해준다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/868ba0ab-ee3b-4e40-9804-48dd359c5518/image.png" alt=""></p>
<p>이처럼 처음 로그인할때 서버에서 유저 관련 정보를 Set-Cookie를 통해 쿠키로 만들어 전달한다. </p>
<p>이후 브라우저에서는 해당 정보를 쿠키 저장소에 저장해둔다.</p>
<p>그리고 아래처럼 활용한다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/f4a1df62-9fdd-4962-bf6c-8af79ffd0f21/image.png" alt=""></p>
<p>클라이언트는 매 HTTP 요청마다 쿠키 저장조에서 쿠리르 조회하여 자동으로 해당 유저 정보를 포함한 쿠키를 서버에 전달한다.
 
그런데 쿠키도 조심해야할 게 있다.
아무래도 노출이 높은 편이다 보니, <strong>보안에 민감한 데이터는 쿠키에 저장하면 안된다.</strong>
 
또한 쿠키는 네트워크 트래픽을 추가로 유발한다.
그러므로 <strong>최소한의 정보</strong>만 활용하자.
만약 서버에 전송하지 않고, 웹 브라우저 내부에서만 데이터를 저장하고 싶다면, 웹 스토리지(LocalStorage, SessionStorage)를 활용하자.</p>
<blockquote>
<p>쿠키 사용처 : 사용자 인증 세션 관리, 광고 정보 트래킹 등</p>
</blockquote>
<h3 id="쿠키의-생명주기">쿠키의 생명주기</h3>
<p>Set-Cookie에는 생명주기와 관련된 옵션 두가지가 있다.</p>
<ul>
<li>expires : 만료일이 되면 쿠키 자동 삭제 (GMT 기준으로 설정)</li>
<li>max-age : 지정 시간 후 쿠키 자동 삭제 (0 또는 음수로 설정하면 즉시 쿠키 삭제)</li>
</ul>
<blockquote>
<p>Q :  expires 와 max-age 는 무슨 차이일까 ?</p>
</blockquote>
<blockquote>
<p>A : expires 는 쿠키의 만료 시간을 절대 시간으로 설정한다. 특정 날짜와 시간을 GMT(UTC) 형식으로 지정하는 것이다. max-age 는 쿠키의 만료 시간을 상대적인 시간으로 설정한다. 설정된 값은 쿠키가 생성된 시간부터 경과한 초 단위로 계산되는 것이다.</p>
</blockquote>
<h3 id="쿠키의-종류">쿠키의 종류</h3>
<ul>
<li>세션 쿠키 : 만료 날짜 생략하면 브라우저 종료시 까지만 유지</li>
<li>영속 쿠키 : 만료 날짜 입력하면 해당 날짜까지 오래 유지</li>
</ul>
<h3 id="쿠키--도메인">쿠키 : 도메인</h3>
<p>쿠키는 특정 도메인에만 적용되도록 설정할 수 있다. 만약 쿠키가 모든 사이트에서 자동으로 생성된다면 보안상 문제가 발생할 수 있으니, 쿠키가 사용할 도메인을 정확히 지정해야 한다.</p>
<p>예를 들어, domain=example.or g로 설정하면 example.org 뿐만 아니라 dev.example.org 같은 서브 도메인도 쿠키에 접근할 수 있다.</p>
<p>반면, 별다른 설정을 하지 않으면 쿠키는 example.org 에서만 유효하고 서브 도메인에서는 접근할 수 없다.</p>
<h3 id="쿠키--경로">쿠키 : 경로</h3>
<p>쿠키에 경로 옵션을 설정하면, <strong>해당 경로를 포함한 하위 경로에서만 쿠키에 접근</strong>할 수 있다. 
즉, 도메인뿐만 아니라 경로도 지정할 수 있다는 뜻이다.</p>
<h4 id="예시">예시:</h4>
<ul>
<li><code>path=/home</code>으로 설정한 경우:<ul>
<li><code>/home</code> -&gt; 쿠키 접근 가능</li>
<li><code>/home/level1</code> -&gt; 쿠키 접근 가능</li>
<li><code>/home/level1/level2</code> -&gt; 쿠키 접근 가능</li>
<li><code>/hello</code> -&gt; 쿠키 접근 불가능</li>
</ul>
</li>
</ul>
<p>일반적으로는 <code>path=/</code>로 설정하여 루트 경로부터 모든 페이지에서 쿠키에 접근할 수 있도록 설정한다.</p>
<h3 id="쿠키--보안">쿠키 : 보안</h3>
<p>쿠키는 <code>Secure</code>, <code>HttpOnly</code>, <code>SameSite</code> 속성으로 보안을 강화할 수 있다.</p>
<ul>
<li><p><strong>Secure</strong><br><code>Secure</code> 속성을 적용하면, 쿠키는 오직 HTTPS 연결을 통해서만 전송된다. 기본적으로 쿠키는 HTTP와 HTTPS 모두에서 전송되지만, <code>Secure</code>를 설정하면 보안이 강화된다.</p>
</li>
<li><p><strong>HttpOnly</strong><br><code>HttpOnly</code> 는 XSS 공격을 방지하기 위해 사용된다. 기본적으로 JavaScript에서는 쿠키에 접근할 수 있지만, <code>HttpOnly</code>를 설정하면 JavaScript에서 쿠키에 접근할 수 없게 된다. 이렇게 하면 쿠키는 오직 HTTP 전송에서만 사용되어 보안이 강화된다.</p>
</li>
<li><p><strong>SameSite</strong><br><code>SameSite</code> 는 XSRF 공격을 방지하는 데 사용된다. 쿠키에 설정된 도메인과 요청을 보내는 도메인이 같은 경우에만 쿠키를 전송하도록 제한한다. 이를 통해 다른 사이트에서 오는 요청에 대해 쿠키가 자동으로 전송되는 것을 방지할 수 있다.</p>
</li>
</ul>
<hr>
<h2 id="번외--xss-와-xsrf-란-">번외 : XSS 와 XSRF 란 ?</h2>
<h3 id="xss-cross-site-scripting">XSS (Cross-Site Scripting)</h3>
<p>XSS는 공격자가 웹사이트에 악성 코드(주로 JavaScript)를 넣어서, 사용자의 브라우저에서 그 코드가 실행되도록 하는 공격이다.</p>
<blockquote>
<p>ex )</p>
<ul>
<li>사용자가 댓글을 사이트에 남긴다.</li>
<li>공격자는 그 사이트의 댓글에 악성 JavaScript 코드를 삽입한다.</li>
<li>다른 사람이 그 댓글을 읽으면, 댓글 안의 악성 코드가 사용자의 브라우저에서 실행된다.</li>
<li>이 코드가 사용자의 쿠키를 훔쳐가면, 공격자가 그 쿠키를 사용해서 사용자의 로그인 상태를 탈취할 수 있다.</li>
</ul>
</blockquote>
<p><strong>결국, XSS는 웹페이지에 악성 스크립트를 삽입해서 사용자의 브라우저에서 이를 실행시키는 공격이다.</strong></p>
<h3 id="xsrf-cross-site-request-forgery">XSRF (Cross-Site Request Forgery)</h3>
<p>XSRF는 공격자가 사용자의 로그인 상태를 악용해서, 사용자가 원하지 않는 요청을 다른 사이트로 보내게 만드는 공격이다.</p>
<blockquote>
<p>ex )</p>
<ul>
<li>사용자가 온라인 뱅킹 사이트에 로그인한다.</li>
<li>그때 악성 사이트에 접속했는데, 그 사이트가 자동으로 사용자의 은행 계좌에서 돈을 이체하는 요청을 보낸다.</li>
<li>사용자가 로그인된 상태에서, 브라우저는 이미 로그인 정보가 들어있는 쿠키를 자동으로 포함시켜서 은행에 요청을 보내버린다.</li>
<li>그래서 공격자는 사용자의 의도와 상관없이 사용자의 계좌에서 돈을 이체할 수 있게 되는 것이다.</li>
</ul>
</blockquote>
<p><strong>결국, XSRF는 공격자가 사용자가 의도하지 않은 요청을 다른 사이트에 보내게 만드는 공격이다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP 웹 기본 지식 (6) - HTTP 상태코드]]></title>
            <link>https://velog.io/@sepang-pang/HTTP-%EC%9B%B9-%EA%B8%B0%EB%B3%B8-%EC%A7%80%EC%8B%9D-6-HTTP-%EC%83%81%ED%83%9C%EC%BD%94%EB%93%9C</link>
            <guid>https://velog.io/@sepang-pang/HTTP-%EC%9B%B9-%EA%B8%B0%EB%B3%B8-%EC%A7%80%EC%8B%9D-6-HTTP-%EC%83%81%ED%83%9C%EC%BD%94%EB%93%9C</guid>
            <pubDate>Mon, 24 Feb 2025 12:15:46 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>본 게시글은 인프런의 모든 개발자를 위한 HTTP 웹 기본 지식 (김영한) 강의를 듣고 정리한 내용입니다.</p>
</blockquote>
<h1 id="http-상태코드">HTTP 상태코드</h1>
<p>클라이언트가 보낸 요청의 처리 상태를 응답에서 알려주는 기능</p>
<hr>
<h2 id="http-상태코드의-종류">HTTP 상태코드의 종류</h2>
<table>
<thead>
<tr>
<th>범주</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>1xx (Informational)</td>
<td>요청이 수신되어 처리 중 ( 잘 사용 안함 )</td>
</tr>
<tr>
<td>2xx (Successful)</td>
<td>요청 정상 처리</td>
</tr>
<tr>
<td>3xx (Redirection)</td>
<td>요청을 완료하려면 추가 행동 필요</td>
</tr>
<tr>
<td>4xx (Client Error)</td>
<td>클라이언트 오류로 인해 요청 수행 불가</td>
</tr>
<tr>
<td>5xx (Server Error)</td>
<td>서버 오류로 인해 요청 처리 불가</td>
</tr>
</tbody></table>
<blockquote>
<p>만약 모르는 상태 코드가 나타나면, 클라이언트는 상위 상태코드로 해석해서 처리</p>
</blockquote>
<ul>
<li>299 ??? -&gt; 2xx (Successful)</li>
<li>451 ??? -&gt; 4xx (Client Error)</li>
<li>599 ??? -&gt; 5xx (Server Error)</li>
</ul>
<hr>
<h2 id="2xx-successful">2xx (Successful)</h2>
<p>클라이언트의 요청을 성공적으로 처리 했을 때 사용하는 상태코드</p>
<h3 id="200-ok">200 OK</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/e6264c65-a8dd-4e6e-962e-26075cd395b7/image.png" alt=""></p>
<h3 id="201-created">201 Created</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/c2c147a9-0090-4dec-bead-3f4ecb7bf3c8/image.png" alt=""></p>
<p>응답에 Location 포함하여 리소스의 경로를 제공한다.</p>
<h3 id="202-accepted">202 Accepted</h3>
<p>요청이 접수되었지만, 처리는 완료되지 않음. 배치 처리 등에 사용된다.</p>
<blockquote>
<p>ex ) 예약 요청이 승인 대기 상태일 때</p>
</blockquote>
<h3 id="204-no-content">204 No Content</h3>
<p>서버가 요청을 성공적으로 수행했지만, 응답 페이로드 본문에 보낼 데이터가 없음</p>
<blockquote>
<p>ex ) velog 의 임시저장 같은 기능이 있다.</p>
</blockquote>
<hr>
<h2 id="3xx-redirection">3xx (Redirection)</h2>
<p>요청을 완료하기 위해 유저 에이전트(웹 브라우저)의 추가 조치 필요할 때 사용하는 상태코드</p>
<h3 id="리다이렉션이란-">리다이렉션이란 ?</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/3b71b713-b7eb-470b-b739-8e7720e384ff/image.png" alt=""></p>
<p>웹 브라우저는 3xx 응답의 결과에 Location 헤더가 있으면, Location 위치로 자동 이동한다.</p>
<pre><code>예전에 /event 사용
-&gt; 이후에 /new-event로 uri변경
-&gt; /event를 통해 들어온 유저들에게 301 Moved Permanently 보냄
-&gt; 경로를 자동으로 /new-event를 바꾸고 다시 요청</code></pre><h3 id="영구-리다이렉션-permanent-redirection">영구 리다이렉션 (Permanent Redirection)</h3>
<p>특정 리소스의 URI가 영구적으로 이동하는 것을 의미한다.</p>
<blockquote>
<p>ex ) <a href="http://example.com/old-page">http://example.com/old-page</a> → <a href="http://example.com/new-page">http://example.com/new-page</a></p>
</blockquote>
<h4 id="301-moved-permanently">301 Moved Permanently</h4>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/8c3ca9db-7016-41ad-bb3b-56ca3d384217/image.png" alt=""></p>
<p>리다이렉트시 요청 메서드가 GET으로 변하고, 본문이 제거될 수 있음</p>
<p>예를 들어, <strong>POST</strong> 메서드가 <strong>GET</strong> 메서드로 변경된다.
메시지 바디는 제거되며, 사용자는 <strong>처음부터 폼을 다시 작성해야</strong> 한다.
<strong>하지만</strong> 모든 경우에서 메시지 바디가 무조건 제거되는 것은 아니다.</p>
<h4 id="308-permanent-redirect">308 Permanent Redirect</h4>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/0f23195e-1d05-4b2c-997f-3e92406269e3/image.png" alt=""></p>
<p>01과 기능은 같지만, 리다이렉트 시 요청 메서드와 본문을 유지함.</p>
<blockquote>
<p>! 요청 메서드 유지 : POST -&gt; POST</p>
</blockquote>
<h3 id="일시-리다이렉션-temporary-redirection">일시 리다이렉션 (Temporary Redirection)</h3>
<p> 리소스가 <strong>일시적으로 이동</strong>되었음을 나타낸다.</p>
<p><strong>PRG (Post/Redirect/Get)</strong> 패턴을 적용하면 중복 요청을 방지할 수 있다. 이 패턴은 사용자가 <strong>POST</strong> 요청 후 리다이렉션을 <strong>GET</strong> 요청으로 처리하여 <strong>중복 제출</strong>을 방지하는 방식이다.</p>
<h4 id="302-found">302 Found</h4>
<ul>
<li><strong>일시적인 리다이렉션</strong>을 나타내며, 서버가 요청을 다른 URL로 임시로 리다이렉트한다.</li>
<li>리다이렉션 시, 요청 메서드는 <strong>GET</strong>으로 변경되며, <strong>본문이 제거</strong>될 수 있다.</li>
</ul>
<h4 id="307-temporary-redirect">307 Temporary Redirect</h4>
<ul>
<li><strong>302 Found</strong>와 기능은 비슷하지만, 중요한 차이점은 요청 메서드와 본문을 <strong>유지</strong>한다는 것이다. 즉, 리다이렉션 후에도 <strong>원래의 요청 메서드</strong>와 <strong>본문</strong>을 그대로 사용한다.</li>
</ul>
<h4 id="303-see-other">303 See Other</h4>
<ul>
<li><strong>302 Found</strong>와 유사하지만, 리다이렉션 시 <strong>요청 메서드</strong>가 <strong>GET</strong>으로 변경된다.</li>
<li>이 응답은 <strong>POST</strong>와 같은 <strong>메서드에 의한 리소스 생성 후</strong>, 클라이언트가 리다이렉션을 통해 다른 리소스를 조회할 때 사용된다.</li>
</ul>
<h4 id="300-304-기타-리다이렉션">300, 304 기타 리다이렉션</h4>
<p>300 Multiple Choices
    - 요청에 대해 <strong>하나 이상의 응답</strong>이 가능함을 나타낸다.
    - 이 응답은 실제로 거의 사용되지 않는다.</p>
<p>304 Not Modified
    - 요청된 리소스를 <strong>재전송할 필요가 없음</strong>을 나타낸다.
    - <strong>캐시</strong>를 목적으로 사용되며, <strong>캐시의 암묵적인 리다이렉트</strong>로 볼 수 있다.
    - 서버는 클라이언트에게 리소스가 <strong>수정되지 않았음</strong>을 알려주며, 클라이언트는 <strong>로컬에 저장된 캐시</strong>를 재사용한다.
    - <strong>304 응답</strong>은 응답 본문을 포함할 수 없으며, 로컬 캐시를 사용해야 한다.
    - <strong>조건부 GET, HEAD 요청</strong> 시 사용된다.</p>
<hr>
<h3 id="prg-패턴-postredirectget">PRG 패턴 (Post/Redirect/Get)</h3>
<p>POST로 주문한 후 웹 브라우저를 새로고침하면, 다시 요청이 가서 <strong>중복 주문</strong>이 발생할 수 있다. 이러한 현상을 방지하기 위해 <strong>PRG (Post/Redirect/Get)</strong> 패턴을 활용할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/f19030ed-2fc4-41a7-b368-b0f9da4704f3/image.png" alt=""></p>
<p>PRG 패턴을 사용하면 POST로 주문한 후 새로 고침으로 인한 <strong>중복 주문</strong>을 방지할 수 있다. 주문 후, <strong>결과 화면</strong>을 <strong>GET</strong> 메서드로 리다이렉트하기 때문에, 새로 고침을 해도 결과 화면을 <strong>GET</strong> 방식으로 조회하게 된다. 결과적으로 중복 주문이 발생하는 대신, 결과 화면만 <strong>GET</strong>으로 다시 요청된다.</p>
<hr>
<h2 id="4xx-client-error">4xx (Client Error)</h2>
<p>오류의 원인이 <strong>클라이언트</strong>에게 있으며, 클라이언트가 잘못된 요청이나 데이터를 보냈기 때문에, 동일한 요청을 반복해도 실패한다.</p>
<h3 id="400-bad-request">400 Bad Request</h3>
<ul>
<li><strong>클라이언트</strong>가 잘못된 요청을 해서 서버가 요청을 처리할 수 없을 때 발생한다.</li>
<li>요청 구문이나 메시지 등에 오류가 있을 때 발생한다.</li>
<li>클라이언트는 요청 내용을 <strong>다시 검토</strong>하고 수정한 후 전송해야 한다.<blockquote>
<p>ex ) 요청 파라미터 오류, API 스펙 불일치</p>
</blockquote>
</li>
</ul>
<h3 id="401-unauthorized">401 Unauthorized</h3>
<ul>
<li><strong>클라이언트</strong>가 해당 리소스에 대한 <strong>인증</strong>을 거치지 않았을 때 발생한다.</li>
<li><strong>인증되지 않음</strong>을 의미하며, 응답에는 인증 방법을 설명하는 <strong>WWW-Authenticate</strong> 헤더가 포함된다.</li>
</ul>
<blockquote>
<p><strong>Authentication</strong>(인증): 본인이 누구인지 확인 (로그인 등)
<strong>Authorization</strong>(인가): 권한 부여 (admin 권한 등, 인증이 있어야 인가 가능)</p>
</blockquote>
<h3 id="403-forbidden">403 Forbidden</h3>
<ul>
<li><strong>서버</strong>가 요청을 이해했지만 <strong>승인</strong>을 거부했을 때 발생한다.</li>
<li>주로 인증 자격 증명은 있지만, 접근 권한이 <strong>불충분한 경우</strong> 발생한다.<blockquote>
<p>ex ) 일반 사용자가 admin 등급의 리소스에 접근하려고 할 때</p>
</blockquote>
</li>
</ul>
<h3 id="404-not-found">404 Not Found</h3>
<ul>
<li>요청된 리소스를 <strong>찾을 수 없을 때</strong> 발생한다.</li>
<li>또한, 클라이언트가 <strong>권한이 부족한 리소스</strong>에 접근하려고 할 때, 해당 리소스를 숨기기 위해 사용되기도 한다.</li>
</ul>
<hr>
<h2 id="5xx-server-error">5xx (Server Error)</h2>
<p>오류의 원인이 <strong>서버</strong>에게 있으며, 이에 <strong>재시도 시 성공할 수 있다</strong>.</p>
<h3 id="500-internal-server-error">500 Internal Server Error</h3>
<ul>
<li><strong>서버</strong> 문제로 오류가 발생했을 때 발생하며, 애매한 오류일 경우 사용된다.</li>
<li>최대한 이 오류는 발생시키지 않도록 하며, 정말로 서버에 문제가 있을 때만 사용한다.</li>
</ul>
<h3 id="503-service-unavailable">503 Service Unavailable</h3>
<ul>
<li><strong>서버가 일시적인 과부하</strong> 또는 <strong>예정된 작업</strong>으로 인해 요청을 처리할 수 없을 때 발생한다.</li>
<li>서버는 잠시 후 복구될 수 있으며, 이때 <strong>Retry-After</strong> 헤더 필드를 통해 복구 시간을 알려줄 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP 웹 기본 지식 (5) - HTTP 메서드 활용]]></title>
            <link>https://velog.io/@sepang-pang/HTTP-%EC%9B%B9-%EA%B8%B0%EB%B3%B8-%EC%A7%80%EC%8B%9D-5-HTTP-%EB%A9%94%EC%84%9C%EB%93%9C-%ED%99%9C%EC%9A%A9</link>
            <guid>https://velog.io/@sepang-pang/HTTP-%EC%9B%B9-%EA%B8%B0%EB%B3%B8-%EC%A7%80%EC%8B%9D-5-HTTP-%EB%A9%94%EC%84%9C%EB%93%9C-%ED%99%9C%EC%9A%A9</guid>
            <pubDate>Mon, 24 Feb 2025 10:43:39 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>본 게시글은 인프런의 모든 개발자를 위한 HTTP 웹 기본 지식 (김영한) 강의를 듣고 정리한 내용입니다.</p>
</blockquote>
<h1 id="http-메서드-활용">HTTP 메서드 활용</h1>
<hr>
<h2 id="클라이언트에서-서버로의-데이터를-전달하는-방식">클라이언트에서 서버로의 데이터를 전달하는 방식</h2>
<blockquote>
<ol>
<li>쿼리 파라미터를 통한 데이터 전송<ul>
<li>GET</li>
<li>검색어 정렬 필터<ol start="2">
<li>메시지 바디를 통한 데이터 전송</li>
</ol>
<ul>
<li>POST, PUT, PATCH</li>
</ul>
</li>
<li>회원가입, 상품 주문, 리소스 등록, 리소스 변경</li>
</ul>
</li>
</ol>
</blockquote>
<hr>
<h2 id="클라이언트에서-서버로-데이터를-전송하는-예시">클라이언트에서 서버로 데이터를 전송하는 예시</h2>
<h3 id="정적-데이터-조회">정적 데이터 조회</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/0eadf918-74ca-4bb8-9963-8f48bbd21c47/image.png" alt=""></p>
<p>정적 데이터는 일반적으로 쿼리 파라미터 없이 리스소 경로로 단순하게 조회가 가능하다 !</p>
<h3 id="동적-데이터-조회">동적 데이터 조회</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/f4346414-c9bf-495a-8b1e-508445eae8ea/image.png" alt=""></p>
<p>검색어(필터), 정렬조건 등 추가적인 조건을 통해 조회하는 경우 쿼리파라미터를 이용한다. 
서버는 쿼리파라미터의 key, value값을 적절히 이용해 조건에 맞는 데이터를 반환한다.</p>
<blockquote>
<p>! HTTP 스펙상 GET 요청 간 Message Body 를 사용해 메시지를 전송할 수 있지만, 실무에서는 권장되지 않는다.</p>
</blockquote>
<h3 id="html-form-데이터-전송">HTML Form 데이터 전송</h3>
<h4 id="post-전송">POST 전송</h4>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/a36bff00-7c9e-48cc-9c0c-9c5faf40e86a/image.png" alt=""></p>
<p><code>&lt;form&gt;</code> 태그를 사용해 요청을 보내면, 지정한 action URL과 method 속성에 따라 HTTP 요청이 생성된다.
사용자가 입력한 <code>&lt;input&gt;</code> 필드 값은 application/x-www-form-urlencoded 형식으로 인코딩되어 요청의 본문(body) 에 포함된다.
이때, 이때, HTTP 요청의 Content-Type 헤더는 application/x-www-form-urlencoded로 설정되며, 브라우저는 username=kim&amp;age=20과 같은 형식으로 데이터를 전송한다.</p>
<blockquote>
<p>Q : x-www-form-urlencoded 가 뭔가요 ?
A:  application/x-www-form-urlencoded는 HTML 폼 데이터를 key=value 형태로 변환해 HTTP 요청 본문(body)에 포함하는 인코딩 방식이다.
일반적으로 POST 요청에서 사용되며, GET 요청이 데이터를 Query String 형태로 보내는 것과 대비된다.</p>
</blockquote>
<h4 id="get-전송">GET 전송</h4>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/efce519c-9f2b-442f-8fa4-c6aeb294e4fc/image.png" alt=""></p>
<p>사용자가 입력한 <code>&lt;input&gt;</code> 필드 값은 Query String 형태로 변환되어 URL에 포함된다.
이때, HTTP 요청의 Content-Type 헤더는 따로 설정되지 않으며, 브라우저는 GET /members?username=kim&amp;age=20과 같은 형식으로 데이터를 전송한다.</p>
<h4 id="mulitipartform-data">mulitipart/form-data</h4>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/ab1bb1f1-185e-4fc9-ba52-b3654905fe15/image.png" alt="">
username과 age 같은 텍스트 데이터뿐만 아니라 바이너리 파일까지 전송할 때는 multipart/form-data 형식을 사용해야 한다.
웹 브라우저는 자동으로 boundary를 사용해 파일과 텍스트 데이터를 구분하며, 이를 통해 여러 개의 Content-Type을 포함한 요청을 보낼 수 있다.
주로 바이너리 데이터 전송에 활용된다.</p>
<h3 id="http-api-데이터-전송">HTTP API 데이터 전송</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/a46a7513-310b-4041-8517-d2c3b0c2964a/image.png" alt=""></p>
<p>HTTP API를 통한 데이터 전송은 서버 간 통신, 모바일 앱(iOS, Android) 및 웹 클라이언트(React, Vue.js 등)와의 데이터 교환에 활용되며,POST, PUT, PATCH는 요청 본문(Body)을 통해 데이터를 전송하고, GET은 Query String 형식으로 데이터를 전달하며, 일반적으로 application/json이 표준으로 사용된다.</p>
<hr>
<h2 id="http-api-설계">HTTP API 설계</h2>
<h3 id="api-설계---post-기반-등록">API 설계 - POST 기반 등록</h3>
<pre><code class="language-java">- 회원 목록 /members -&gt; GET
- 회원 등록 /members -&gt; POST
- 회원 조회 /members/{id} -&gt; GET
- 회원 수정 /members/{id} -&gt; PATCH, PUT, POST
- 회원 삭제 /members/{id} -&gt; DELETE</code></pre>
<h3 id="api-설계---put-기반-등록">API 설계 - PUT 기반 등록</h3>
<pre><code class="language-java">- 파일 목록 /files -&gt; GET
- 파일 조회 /files/{filename} -&gt; GET
- 파일 등록 /files/{filename} -&gt; PUT
- 파일 삭제 /files/{filename} -&gt; DELETE
- 파일 대량 등록 /files -&gt; POST</code></pre>
<h3 id="post와-put-기반-등록의-차이---컬렉션-vs-스토어">POST와 PUT 기반 등록의 차이 - 컬렉션 vs 스토어</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>POST</th>
<th>PUT</th>
</tr>
</thead>
<tbody><tr>
<td><strong>URI 지정 주체</strong></td>
<td>서버가 URI 생성</td>
<td>클라이언트가 URI 지정</td>
</tr>
<tr>
<td><strong>예제</strong></td>
<td><code>POST /members</code></td>
<td><code>PUT /files/star.jpg</code></td>
</tr>
<tr>
<td><strong>리소스 관리 방식</strong></td>
<td>컬렉션(Collection)</td>
<td>스토어(Store)</td>
</tr>
<tr>
<td><strong>URI 관리 주체</strong></td>
<td>서버가 관리</td>
<td>클라이언트가 관리</td>
</tr>
<tr>
<td><strong>응답 코드</strong></td>
<td><code>201 Created</code> + <code>Location</code> 헤더</td>
<td><code>201 Created</code> (신규 생성) 또는 <code>200 OK</code> (업데이트)</td>
</tr>
<tr>
<td><strong>사용 예시</strong></td>
<td>회원 등록 (<code>/members</code>)</td>
<td>파일 업로드 (<code>/files/{filename}</code>)</td>
</tr>
</tbody></table>
<p>POST를 이용해 데이터를 등록할 때는 클라이언트의 요청을 서버에서 받아 리소스를 만들어준다. 이 때 클라이언트는 등록될 리소스의 URI를 모른다. 서버가 관리하는 리소스 디렉토리 또는 서버가 리소스 URI를 생성하고 관리하는 형태를 <strong>컬렉션이라고한다.</strong></p>
<p>PUT기반 등록은 클라이언트가 리소스 URI를 알고 있다. 클라이언트가 직접 리소스의 URI를 지정한다는 것은 클라이언트가 리소스 URI를 관리한다는 뜻이다. 클라이언트가 관리하는 리소스 저장소 또는 클라이언트가 리소스의 URI를 알고 관리하는 형태를 <strong>스토어라고 한다.</strong></p>
<blockquote>
<p>대부분 POST 기반의 컬렉션을 사용한다.</p>
</blockquote>
<hr>
<h3 id="html-form-사용">HTML FORM 사용</h3>
<p>순수 HTML FROM만을 이용할 때는 <strong>GET, POST만을 지원</strong>한다. 이러한 제약사항을 해결하기 위해 리소스 경로에 동사를 사용하는 컨트롤 URI를 이용한다.</p>
<blockquote>
<p>주의: 최대한 Resource 중심으로 URI를 설계하되, 필요할 경우에만 컨트롤 URI를 사용하도록 하자.</p>
</blockquote>
<pre><code class="language-java">회원 목록      /members (GET)
회원 등록 폼 /members/new (GET)
회원 등록      /members/new, /members (POST)
회원 조회      /members/{id} (GET)
회원 수정 폼 /members/{id}/edit (GET)
회원 수정      /members/{id}/edit, /members/{id} (POST)
회원 삭제      /members/{id}/delete (POST)</code></pre>
<p>POST 를 통해 회원 등록 시 폼(GET)과 URI를 맞추거나, 별도의 컬렉션 형태(/members)로 URI를 생성할 수 있다. 이때, URI를 동일하게 맞추면, 등록 중 문제가 발생해 폼으로 돌아갈 때 경로를 변경할 필요가 없어 편리하다.
회원 수정 폼은 조회 기능이므로 GET을 사용하며, 수정 요청 시에도 폼의 URI와 맞추거나 컬렉션 형태로 URI를 생성할 수 있다.
또한, HTML FORM은 DELETE 메서드를 지원하지 않으므로, 회원 삭제 시 POST를 사용하고, delete를 포함한 컨트롤 URI를 활용해야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP 웹 기본 지식 (4) - HTTP 메서드]]></title>
            <link>https://velog.io/@sepang-pang/HTTP-%EC%9B%B9-%EA%B8%B0%EB%B3%B8-%EC%A7%80%EC%8B%9D-4-%EC%9D%B8%ED%84%B0%EB%84%B7-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC</link>
            <guid>https://velog.io/@sepang-pang/HTTP-%EC%9B%B9-%EA%B8%B0%EB%B3%B8-%EC%A7%80%EC%8B%9D-4-%EC%9D%B8%ED%84%B0%EB%84%B7-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC</guid>
            <pubDate>Mon, 24 Feb 2025 10:42:50 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>본 게시글은 인프런의 모든 개발자를 위한 HTTP 웹 기본 지식 (김영한) 강의를 듣고 정리한 내용입니다.</p>
</blockquote>
<h1 id="http-메서드">HTTP 메서드</h1>
<hr>
<h2 id="http-api를-만들어보자">HTTP API를 만들어보자</h2>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/f5e3ca64-4002-4b2b-b89a-91eb5523bf9d/image.png" alt=""></p>
<p>API 설계를 할 때 가장 중요한 것은 <strong>리소스를 식별하는 것</strong>이다.</p>
<p>예를 들어, 회원 등록, 수정, 삭제와 관련된 API를 설계할 때, <strong>회원 자체가 리소스</strong>가 된다. URI 설계는 리소스를 중심으로 정의해야 한다.</p>
<p>여기서 중요한 점은 행위를 URI에 포함하지 않고, HTTP 메서드를 활용하여 의미를 전달하는 것이다.</p>
<hr>
<h2 id="http-메서드---get">HTTP 메서드 - GET</h2>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/bcaee48c-f723-4014-bc9e-aba2f7088fba/image.png" alt=""></p>
<p>GET 메서드는 리소스를 조회하는 용도로 사용된다. 서버에 전달하고자 하는 데이터는 주로 쿼리 파라미터를 통해 전달한다. 이때 메세지 바디를 함께 전달할 수는 있지만, 많은 서버가 이를 지원하지 않으므로 가능한 지양하는 것이 좋다.</p>
<h3 id="get-메시지-전달-과정">GET 메시지 전달 과정</h3>
<h4 id="1-메시지-전달">1. 메시지 전달</h4>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/a1e555fc-8821-4787-8609-cb5de8ca915f/image.png" alt=""></p>
<h4 id="2-서버-도착">2. 서버 도착</h4>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/6f906b4c-2244-4776-ab64-509b76d6ea35/image.png" alt=""></p>
<h4 id="3-응답-데이터">3. 응답 데이터</h4>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/f1ab923d-92f1-4d39-be56-9f323213c35c/image.png" alt=""></p>
<hr>
<h2 id="http-메서드---post">HTTP 메서드 - POST</h2>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/3bb8d687-a14b-4249-b54a-43872b15eb79/image.png" alt=""></p>
<p>POST 메서드는 <strong>요청 데이터 처리</strong>를 위해 사용된다. 요청 데이터는 <strong>메시지 바디</strong>를 통해 서버로 전달된다. 서버는 전달된 요청 데이터를 처리하며, 주로 <strong>신규 데이터 등록</strong>이나 <strong>프로세스 처리</strong>에 사용된다.</p>
<blockquote>
<p>POST는 단순히 리소스를 생성하는 것이 아니다!
<strong>컨트롤 URI</strong>를 사용하는 경우, 예를 들어 <code>POST /orders/{orderId}/start-delivery</code>와 같은 형태로, 리소스를 생성하지 않고 <strong>프로세스를 처리</strong>할 수도 있다. 또한 JSON 으로 조회 데이터를 넘겨야 하는데, GET 메서드를 사용하기 어려운 경우 POST 를 사용하기도 한다 !</p>
</blockquote>
<h3 id="post-메시지-전달-과정">POST 메시지 전달 과정</h3>
<h4 id="1-메시지-전달-1">1. 메시지 전달</h4>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/93166a07-738a-4219-81bb-09f1a504b213/image.png" alt=""></p>
<h4 id="2-신규-리소스-생성">2. 신규 리소스 생성</h4>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/2d4eeaea-4c30-4ddb-94f1-0db5d3742cf3/image.png" alt=""></p>
<h4 id="3-응답-데이터-1">3. 응답 데이터</h4>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/0d6e1f7e-d89c-4088-8c17-cf9ebdcd553c/image.png" alt=""></p>
<hr>
<h2 id="http-메서드---put-patch-delete">HTTP 메서드 - PUT, PATCH, DELETE</h2>
<h3 id="put-메서드">PUT 메서드</h3>
<p><strong>리소스를 수정할 때</strong> 사용하며, <strong>리소스를 대체</strong>하는 메서드이다.  </p>
<ul>
<li><strong>리소스가 있으면 대체</strong>, 없으면 <strong>생성</strong>된다 (덮어 씌움).</li>
<li><strong>동일 리소스가 있는 경우</strong>: PUT 요청 → 리소스 대체</li>
<li><strong>동일 리소스가 없는 경우</strong>: PUT 요청 → 리소스 생성</li>
<li><strong>중요</strong>: 클라이언트가 <strong>리소스를 식별</strong>해야 한다 (POST와 다름).</li>
<li>클라이언트가 <strong>리소스 위치</strong>를 알고 <strong>URI</strong>를 지정해야 한다.</li>
</ul>
<h3 id="patch-메서드">PATCH 메서드</h3>
<p><strong>리소스를 부분 변경</strong>할 때 사용된다.  </p>
<ul>
<li>PATCH가 지원되지 않는 서버도 있을 수 있다. 이 경우, <strong>POST</strong>를 사용해도 된다.</li>
</ul>
<h3 id="delete-메서드">DELETE 메서드</h3>
<p><strong>리소스를 제거</strong>할 때 사용된다.</p>
<hr>
<h2 id="http-메서드의-속성">HTTP 메서드의 속성</h2>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/c2c0788a-08c1-421e-892c-c4a417d87de6/image.png" alt=""></p>
<h3 id="안전-safe">안전 (Safe)</h3>
<p><strong>안전</strong>하다는 것은 호출해도 <strong>리소스가 변경되지 않음</strong>을 의미한다.</p>
<blockquote>
<p><strong>GET</strong>: 안전 (조회만 하므로 리소스를 변경하지 않음)
<strong>POST, PATCH, DELETE</strong>: 변경, <strong>안전하지 않음</strong></p>
</blockquote>
<h3 id="멱등-idempotent">멱등 (Idempotent)</h3>
<p><strong>멱등성</strong>은 한 번 호출하든 여러 번 호출하든 <strong>결과가 동일해야</strong> 한다는 의미이다.</p>
<h4 id="멱등-메서드">멱등 메서드</h4>
<ul>
<li><strong>GET</strong>: 한 번 조회하든 여러 번 조회하든 <strong>같은 결과</strong>를 조회한다.</li>
<li><strong>PUT</strong>: 리소스를 <strong>대체</strong>하므로 여러 번 호출하든 결과는 같다.</li>
<li><strong>DELETE</strong>: 리소스를 <strong>삭제</strong>하므로 여러 번 호출하든 삭제된 상태로 남는다.</li>
<li><strong>POST</strong>: 두 번 호출하면 <strong>중복된 결과</strong>가 발생할 수 있다.</li>
</ul>
<h4 id="활용">활용</h4>
<ul>
<li>서버에서 응답이 없을 경우, 멱등 메서드를 <strong>다시 호출</strong>해도 동일한 결과를 얻을 수 있다.</li>
<li>이를 통해 <strong>자동 복구 메커니즘</strong>을 구현할 수 있다.</li>
</ul>
<h3 id="캐시-가능-cacheable">캐시 가능 (Cacheable)</h3>
<ul>
<li>응답 결과 리소스를 <strong>캐시</strong>하여 재사용할 수 있는지 여부.</li>
<li><strong>캐시</strong>란 인터넷에서 요청한 리소스를 <strong>저장</strong>해두고, 이후에 <strong>재사용</strong>하는 것이다.</li>
<li><strong>캐시 가능 메서드</strong>:<ul>
<li><strong>GET, HEAD, POST, PATCH</strong>는 캐시가 가능하다.</li>
<li>실제로는 <strong>GET, HEAD</strong>만 주로 캐시로 사용된다.</li>
<li><strong>POST, PATCH</strong>는 본문 내용까지 캐시 키로 고려해야 하므로 <strong>구현이 어려울 수 있다.</strong></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP 웹 기본 지식 (3) - HTTP 기본]]></title>
            <link>https://velog.io/@sepang-pang/HTTP-%EC%9B%B9-%EA%B8%B0%EB%B3%B8-%EC%A7%80%EC%8B%9D-2-HTTP-%EA%B8%B0%EB%B3%B8</link>
            <guid>https://velog.io/@sepang-pang/HTTP-%EC%9B%B9-%EA%B8%B0%EB%B3%B8-%EC%A7%80%EC%8B%9D-2-HTTP-%EA%B8%B0%EB%B3%B8</guid>
            <pubDate>Mon, 24 Feb 2025 09:46:10 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>본 게시글은 인프런의 모든 개발자를 위한 HTTP 웹 기본 지식 (김영한) 강의를 듣고 정리한 내용입니다.</p>
</blockquote>
<h1 id="http-기본">HTTP 기본</h1>
<p>HTTP 는 hypertext transfer protocol 의 약자로 애플리케이션 계층으로서 웹 서비스 통신에 사용된다.</p>
<p>초기에는 HTML 등 링크를 통해 연결 할 수 있는 문서 (하이퍼텍스트)를 전송하는 프로토콜로 시작하였다. 다만, 현재는 모든 형태의 데이터를 전송하는 프로토콜로 사용중이며, HTML 뿐만 아니라, TEXT, image, 음성, 영상, 파일, JSON, XML 형식의 데이터를 모두 전송할 수 있다. 또한, 서버간 통신을 할 때도 대부분 http 프로토콜로 연결하고 있다.</p>
<hr>
<h2 id="http-역사">HTTP 역사</h2>
<blockquote>
<ul>
<li>HTTP/0.9 1991년: GET 메서드만 지원, HTTP 헤더X</li>
</ul>
</blockquote>
<ul>
<li>HTTP/1.0 1996년: 메서드, 헤더 추가</li>
<li>HTTP/1.1 1997년: 가장 많이 사용, 우리에게 가장 중요한 버전<ul>
<li>RFC2068 (1997) -&gt; RFC2616 (1999) -&gt; RFC7230~7235 (2014) </li>
</ul>
</li>
<li>HTTP/2 - 2015년: 성능 개선</li>
<li>HTTP/3 진행중: TCP 대신에 UDP 사용, 성능 개선</li>
</ul>
<p>지금 우리가 가장 많이 사용하고, 가장 중요한 버전은 HTTP/1.1이다. 이 버전에는 웬만한 모든 기능이 포함되어 있으며, 이후에 등장한 HTTP/2와 HTTP/3은 성능 개선에 중점을 두고 있다.</p>
<hr>
<h2 id="기반-프로토콜">기반 프로토콜</h2>
<blockquote>
<p>• TCP: HTTP/1.1, HTTP/2
• UDP: HTTP/3
• 현재 HTTP/1.1 주로 사용
• HTTP/2, HTTP/3 도 점점 증가</p>
</blockquote>
<p>HTTP는 버전마다 사용하는 프로토콜이 다르다. HTTP/1.1과 HTTP/2는 TCP 기반으로 작동하는데, TCP는 3-way handshake 같은 연결 설정 과정이 필요하고, 기본적으로 속도가 빠른 방식이 아니다. </p>
<p>반면, HTTP/3는 성능을 개선하기 위해 UDP를 기반으로 설계되었으며, 이를 통해 애플리케이션 레벨에서 성능 최적화가 이루어졌다. </p>
<p>다만, 아직까지는 주로 HTTP/1.1을 쓰고 있지만, 각 HTTP 버전은 개발자 도구의 네트워크 탭에서 ‘protocol’ 항목을 열어 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/ddecae5f-93b1-4b4e-8697-112fb0cd0bdb/image.png" alt=""></p>
<hr>
<h2 id="http-특징">HTTP 특징</h2>
<h3 id="클라이언트-서버-구조">클라이언트-서버 구조</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/d7ca8940-6571-4ee1-90fb-181b5da19b3e/image.png" alt=""></p>
<p>HTTP는 클라이언트가 서버에 요청을 보내고, 서버는 그 요청에 대한 결과를 만들어 응답하는 클라이언트-서버 구조로 동작한다. </p>
<p>중요한 점은 클라이언트와 서버 개념을 분리하는 것인데, HTTP 덕분에 비즈니스 로직과 데이터 로직은 서버에서 처리되고, 클라이언트는 UI에 집중할 수 있게 되었다. </p>
<p>이 구조 덕분에 프론트엔드와 백엔드가 독립적으로 동작할 수 있게 되었고, 역할 분담이 명확해져 효율적인 개발이 가능해졌다.</p>
<hr>
<h3 id="무상태-프로토콜--stateless">무상태 프로토콜 : Stateless</h3>
<p>HTTP는 무상태 프로토콜(stateless)을 지향한다. 즉, 서버는 각 요청을 독립적으로 처리하며, 이전 요청에 대한 정보를 저장하지 않는다. 이를 통해 서버의 부담을 줄이고, 요청마다 빠르고 효율적인 처리가 가능해진다</p>
<blockquote>
<p>Q : 그래서 무상태 프로토콜이 뭔데요 ?</p>
</blockquote>
<blockquote>
<p>A : 무상태 프로토콜은 서버가 클라이언트의 상태를 저장하거나 기억하지 않는 프로토콜</p>
</blockquote>
<ul>
<li>장점 : 서버 확장성 높음 (스케일 아웃)</li>
<li>단점 : 클라이언트가 추가 데이터를 전송해야 함</li>
</ul>
<hr>
<h3 id="stateful-vs-stateless">Stateful vs Stateless</h3>
<p><strong>상태 유지 - Stateful</strong></p>
<pre><code>고객: 이 노트북 얼마인가요?
점원: 100만원 입니다.

고객: 2개 구매하겠습니다.
점원: 200만원 입니다. 신용카드, 현금중에 어떤 걸로 구매 하시겠어요?

고객: 신용카드로 구매하겠습니다.
점원: 200만원 결제 완료되었습니다.</code></pre><p><strong>상태 유지 - Stateful, 점원이 중간에 바뀌면?</strong></p>
<pre><code>고객: 이 노트북 얼마인가요?
점원A: 100만원 입니다.

고객: 2개 구매하겠습니다.
점원B: ? 무엇을 2개 구매하시겠어요?

고객: 신용카드로 구매하겠습니다.
점원C: ? 무슨 제품을 몇 개 신용카드로 구매하시겠어요?</code></pre><p><strong>상태 유지 - Stateful, 정리</strong></p>
<pre><code>고객: 이 노트북 얼마인가요?
점원: 100만원 입니다. (노트북 상태 유지)

고객: 2개 구매하겠습니다.
점원: 200만원 입니다. 신용카드, 현금중에 어떤 걸로 구매 하시겠어요?
    (노트북, 2개 상태 유지)

고객: 신용카드로 구매하겠습니다.
점원: 200만원 결제 완료되었습니다. (노트북, 2개, 신용카드 상태 유지)</code></pre><p><strong>무상태 - Stateless, 점원이 중간에 바뀌면?</strong></p>
<pre><code>고객: 이 노트북 얼마인가요?
점원A: 100만원 입니다.

고객: 노트북 2개 구매하겠습니다.
점원B: 노트북 2개는 200만원 입니다. 신용카드, 현금중에 어떤 걸로 구매 하시겠어요?

고객: 노트북 2개를 신용카드로 구매하겠습니다.
점원C: 200만원 결제 완료되었습니다.</code></pre><p>위 사례처럼 Stateful 의 경우, 중간에 점원이 바뀌면 새로운 점원은 이전 점원의 인수인계를 받지 못해 고객의 요청이 이해되지 않을 수 있다.</p>
<p>반면, Stateless의 경우에는 점원이 바뀌어도 고객이 필요한 정보를 모두 전달하면 새로운 점원이 문제 없이 다음 행동을 수행할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/4e9f9b47-5284-451a-a079-3ce353e4a9b0/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/8f8a7ce3-2f21-4ecd-b1a1-6581ff290fc0/image.png" alt=""></p>
<p>사진과 같이 Stateless 의 경우, 통신 간 서버 장애가 발생했을 때 다른 서버로 전달을 하면 될 뿐이고 고객은 해당 장애를 인지하지 못한다. 즉, 고가용성이 보장된다는 것이다.</p>
<p>다만, Stateless 에도 현실적인 한계가 존재한다. 실무에서는 모든 것을 무상태로 설계할 수 없는 경우가 있는데, 예를 들어 로그인한 사용자는 로그인 상태를 서버에 유지해야 한다. 그렇지 않으면 매 요청마다 사용자가 로그인을 다시 해야 하는 상황이 발생한다. 그래서 일반적으로는 브라우저의 쿠키나 서버 세션 등을 이용해 상태를 유지한다.</p>
<blockquote>
<p>물론 JWT 토큰 인증 방식을 채택하면 이 또한 Stateless 하게 처리할 수 있다 !</p>
</blockquote>
<hr>
<h3 id="비-연결성--connectionless">비 연결성 : Connectionless</h3>
<p>HTTP는 Connectionless 라는 특징을 가진다. 이는 기본적으로 연결을 유지해야 하는 TCP/IP와는 반대되는 개념으로, HTTP는 각 요청마다 새로운 연결을 설정하고 요청이 끝나면 연결을 종료한다. TCP/IP는 서버와의 연결을 계속 유지해야 하므로 서버 자원이 소모되는 단점이 있지만, HTTP는 연결을 유지하지 않아 자원을 최소화할 수 있다는 장점이 있다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/5d91d92b-ab6b-4242-b64e-10bd7f1f183f/image.png" alt=""></p>
<p>이처럼 TCP/IP로 연결을 유지하면, 클라이언트 1이 통신을 주고 받는 동안 클라이언트 2와 클라이언트 3은 대기 상태에 있게 되어 자원이 낭비된다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/308f64d2-6e63-4d29-86fc-9b0191152644/image.png" alt=""></p>
<p>반면, HTTP는 한 번 서버와 통신을 수행한 후 연결을 유지하지 않기 때문에 자원을 효율적으로 사용할 수 있다. 이 덕분에 초 단위 이하의 빠른 속도로 응답할 수 있으며, 1시간 동안 수천 명이 서비스를 사용해도 실제 서버의 동시 처리 요청은 수십 개 이하로 매우 적다.</p>
<p>결과적으로, HTTP는 서버 자원을 매우 효율적으로 사용할 수 있는 프로토콜이다.</p>
<p>다만, 비연결성의 한계점도 존재한다. HTTP는 클라이언트와 서버가 서로 연결을 유지하지 않기 때문에, TCP/IP 프로토콜에 따라 연결과 종료를 반복할때 요청에 대한 응답을 여러번 반복하게 되면서 서버의 자원과 시간(3 way handshake 시간)이 낭비된다. </p>
<p>또한, 웹 브라우저 요청 시 HTML 하나만 받아오는게 아니라, 자바스크립트, css, 이미지등 수많은 파일들을 불러오는데, 연결이 되지 않는다면 파일 하나하나당 매번 요청을 보내야한다는 불편함이 있다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/0208e526-61e4-4b0d-ad76-07b0b1ce90d2/image.png" alt=""></p>
<p>이러한 문제점을 개선하기 위해, HTTP/1.1부터는 Keep-Alive 기능을 도입하여 지속 연결(Persistent Connection) 방식을 적용했다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/d9a29cc0-eb97-4d09-8d11-5f57849e7496/image.png" alt=""></p>
<p>이처럼 HTML, JS, CSS 등의 데이터를 모두 내려받을 때까지 연결을 유지함으로써, 여러 번 연결을 설정하는 비용을 줄이고 더 효율적으로 작업을 수행할 수 있게 된다.</p>
<h1 id="http-메시지">HTTP 메시지</h1>
<p><img src="blob:https://velog.io/b6a76f22-5da0-48aa-aa31-b1c0ad60bcf9" alt="업로드중.."></p>
<p>HTTP 메세지는 요청 메세지와 응답 메세지 크게 두 종류로 나뉘어져 있으며, 각각 형태가 조금씩 다르다. 기본적인 구조는 아래와 같다.</p>
<pre><code>시작 라인 (start-line)
헤더 (header)
공백 라인 (CRLF, empty line)
바디 (message body)</code></pre><h2 id="start-line">start-line</h2>
<p>시작 라인은 크개 reqeust-line / status-line 으로 구분된다. 
요청 메시지는 reqeust-line, 응답 메시지는 status-line 로 구성되는 것이다.</p>
<h3 id="요청-메시지">요청 메시지</h3>
<pre><code class="language-java">method SP(공백) request-target SP HTTP-version CRLF(엔터)</code></pre>
<p>method : GET, POST, PUT, DELETE 등
target : 절대 경로와 쿼리
version : HTTP 버전 정보</p>
<h3 id="응답-메시지">응답 메시지</h3>
<pre><code class="language-java">HTTP-version SP status-code SP reason-phrase CRLF</code></pre>
<p>status-code : HTTP 상태코드
reason-phrase : 상태 코드 설명 글</p>
<h2 id="header">Header</h2>
<p>HTTP 헤더는 HTTP 전송에 필요한 모든 부가 정보를 담기 위해 생성된다.
ex) 인증,웹 브라우저 정보, 압축 등등</p>
<p><strong>메세지 바디 빼고 모든 메타데이터 정보가 다 들어있음</strong></p>
<h2 id="body">Body</h2>
<p>마지막으로 HTTP 메세지 바디는 실제 전송할 데이터를 담고있다. 예를 들어, HTML 문서, 이미지, 영상, JSON 등 byte로 표현할 수 있는 모든 데이터가 바디가 될 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP 웹 기본 지식 (2) - URI와 웹 브라우저 요청 흐름]]></title>
            <link>https://velog.io/@sepang-pang/HTTP-%EC%9B%B9-%EA%B8%B0%EB%B3%B8-%EC%A7%80%EC%8B%9D-2-URI%EC%99%80-%EC%9B%B9-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EC%9A%94%EC%B2%AD-%ED%9D%90%EB%A6%84</link>
            <guid>https://velog.io/@sepang-pang/HTTP-%EC%9B%B9-%EA%B8%B0%EB%B3%B8-%EC%A7%80%EC%8B%9D-2-URI%EC%99%80-%EC%9B%B9-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EC%9A%94%EC%B2%AD-%ED%9D%90%EB%A6%84</guid>
            <pubDate>Mon, 24 Feb 2025 09:45:30 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>본 게시글은 인프런의 모든 개발자를 위한 HTTP 웹 기본 지식 (김영한) 강의를 듣고 정리한 내용입니다.</p>
</blockquote>
<h1 id="uri">URI</h1>
<hr>
<h2 id="uri-란-">URI 란 ?</h2>
<blockquote>
<p>&quot;URI는 로케이터(locator), 이름(name) 또는 둘 다 추가로 분류될 수 있다&quot;</p>
</blockquote>
<p>URI는 <strong>Uniform Resource Identifier</strong>의 약자로, 자원을 식별하는 통일된 방법이다. 이를 통해 웹상의 리소스를 고유하게 구분할 수 있다. URI, URL, URN은 비슷한 개념이지만 약간씩 다르다.</p>
<h3 id="uri-url-urn의-차이점">URI, URL, URN의 차이점</h3>
<ul>
<li><strong>URI (Uniform Resource Identifier)</strong>: 리소스를 식별하는 통합된 방법으로, 위치와 이름을 포함한 포괄적인 개념이다.</li>
<li><strong>URL (Uniform Resource Locator)</strong>: URI의 한 종류로, 자원의 위치를 나타낸다. 웹 브라우저에서 주로 사용하는 형태이다.</li>
<li><strong>URN (Uniform Resource Name)</strong>: URI의 또 다른 종류로, 자원의 이름을 나타낸다. 위치와는 무관하게 자원을 고유하게 식별한다.</li>
</ul>
<p>즉, <strong>URL</strong>은 위치를, <strong>URN</strong>은 이름을 나타내는 형태이다. 웹 브라우저에서 입력하는 것은 주로 URL이다.</p>
<h3 id="uri-각-단어의-의미">URI 각 단어의 의미</h3>
<ul>
<li><strong>Uniform</strong>: 리소스를 식별하는 통일된 방식이다.</li>
<li><strong>Resource</strong>: 웹 상의 자원(예: HTML 파일, 이미지, 실시간 데이터 등)이다.</li>
<li><strong>Identifier</strong>: 고유하게 식별할 수 있는 값이다.</li>
</ul>
<p>이처럼 URI는 웹 자원을 식별하는 고유한 방법을 제공하며, URL은 그 중 위치를 나타내는 방식이다.</p>
<hr>
<h2 id="url-문법">URL 문법</h2>
<h3 id="예시-url">예시 URL</h3>
<blockquote>
<p><a href="https://www.google.com:443/search?q=hello&amp;hl=ko">https://www.google.com:443/search?q=hello&amp;hl=ko</a></p>
</blockquote>
<h3 id="url-문법-규격화">URL 문법 규격화</h3>
<blockquote>
<p>scheme://[userinfo@]host[:port][/path][?query][#fragment]</p>
</blockquote>
<h3 id="url-분석">URL 분석</h3>
<table>
<thead>
<tr>
<th><strong>요소</strong></th>
<th><strong>설명</strong></th>
<th><strong>예시</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>scheme</strong></td>
<td>프로토콜 (자원 접근 방식)</td>
<td><code>https</code> (HyperText Transfer Protocol Secure)</td>
</tr>
<tr>
<td><strong>userinfo@</strong></td>
<td>사용자 정보 (거의 사용하지 않음)</td>
<td><code>admin:password@</code></td>
</tr>
<tr>
<td><strong>host</strong></td>
<td>호스트명, 도메인 또는 IP 주소</td>
<td><code>www.google.com</code></td>
</tr>
<tr>
<td><strong>port</strong></td>
<td>접속하려는 포트 (일반적으로 생략됨)</td>
<td><code>443</code> (HTTPS 기본 포트)</td>
</tr>
<tr>
<td><strong>path</strong></td>
<td>리소스 경로</td>
<td><code>/search</code></td>
</tr>
<tr>
<td><strong>query</strong></td>
<td>쿼리 파라미터 (key=value)</td>
<td><code>?q=hello&amp;hl=ko</code></td>
</tr>
<tr>
<td><strong>fragment</strong></td>
<td>HTML 내부 북마크 (서버로 전송되지 않음)</td>
<td><code>#section1</code></td>
</tr>
</tbody></table>
<hr>
<h1 id="웹-브라우저의-요청-흐름">웹 브라우저의 요청 흐름</h1>
<p>웹 브라우저가 URL을 입력받아 서버에 요청을 보내고, 응답을 받아 렌더링하는 과정을 살표보자.</p>
<h3 id="1-dns-조회">1. DNS 조회</h3>
<blockquote>
<p><a href="https://www.google.com/search?q=hello&amp;hl=ko">https://www.google.com/search?q=hello&amp;hl=ko</a> </p>
</blockquote>
<p>위 URL 을 브라우저에 검색했다고 하자.</p>
<p>이때 웹 브라우저는 DNS 서버에 <code>google.com</code> 이라는 호스트를 조회해서 IP 주소를 조회한다.</p>
<h3 id="2-http-요청-생성">2. HTTP 요청 생성</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/3bffc8ea-c450-4797-a867-ece468835b6e/image.png" alt=""></p>
<p>브라우저는 위와 같은 형태로 HTTP 요청 메시지를 생성한다.</p>
<h3 id="3-socket-라이브러리-통해서-애플리케이션---os-계층으로-전달">3. Socket 라이브러리 통해서 애플리케이션 -&gt; OS 계층으로 전달</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/ff411f28-dd76-44d5-bc6d-5f3c02077d39/image.png" alt=""></p>
<p>웹 브라우저에서 생성된 HTTP 요청 메세지는 Socket 라이브러리를 통해 애플리케이션에서 OS 계층으로 전달이 된다.</p>
<h3 id="4-tcpip-패킷-생성">4. TCP/IP 패킷 생성</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/4349350d-d163-4403-87f6-93ea95a87fb6/image.png" alt=""></p>
<p>전송 데이터에 HTTP 메시지를 추가한 후, TCP 3-way handshake를 통해 서버와의 연결을 확인한다.</p>
<h3 id="5-패킷-전달">5. 패킷 전달</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/45610b19-3914-4bea-aec8-9010b14b2d6b/image.png" alt=""></p>
<p>패킷이 도착하면 TCP/IP 패킷을 모두 제거한 후 HTTP 메시지를 해석한다.</p>
<h3 id="6-http-응답-메시지-생성">6. HTTP 응답 메시지 생성</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/562ab3f4-3696-43ec-a19c-eb90b4ac665c/image.png" alt=""></p>
<ul>
<li>200 OK : 정상 응답</li>
<li>Content-Type : 응답한 데이터 형식(html), 문자 인코딩(UTF-8)</li>
<li>Content-Length : 실제 HTML 데이터의 크기</li>
</ul>
<h3 id="7-http-응답-메시지-전송">7. HTTP 응답 메시지 전송</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/5611083e-5ade-4548-9bca-ff6a5633d240/image.png" alt=""></p>
<p>해당 패킷은 똑같은 방식으로 클라이언트로 전달이 된다.</p>
<h3 id="8-웹-브라우저-html-렌더링">8. 웹 브라우저 HTML 렌더링</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/28427933-9e5a-4041-9fc6-bb9956c93dac/image.png" alt=""></p>
<p>브라우저는 받은 HTML, CSS, JavaScript 등을 해석하여 사용자 화면에 웹페이지를 렌더링한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP 웹 기본 지식 (1) - 인터넷 네트워크]]></title>
            <link>https://velog.io/@sepang-pang/HTTP-%EC%9B%B9-%EA%B8%B0%EB%B3%B8-%EC%A7%80%EC%8B%9D-1-%EC%9D%B8%ED%84%B0%EB%84%B7-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-URI%EC%99%80-%EC%9B%B9-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EC%9A%94%EC%B2%AD-%ED%9D%90%EB%A6%84</link>
            <guid>https://velog.io/@sepang-pang/HTTP-%EC%9B%B9-%EA%B8%B0%EB%B3%B8-%EC%A7%80%EC%8B%9D-1-%EC%9D%B8%ED%84%B0%EB%84%B7-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-URI%EC%99%80-%EC%9B%B9-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EC%9A%94%EC%B2%AD-%ED%9D%90%EB%A6%84</guid>
            <pubDate>Mon, 24 Feb 2025 09:21:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>본 게시글은 인프런의 모든 개발자를 위한 HTTP 웹 기본 지식 (김영한) 강의를 듣고 정리한 내용입니다.</p>
</blockquote>
<h1 id="인터넷-네트워크">인터넷 네트워크</h1>
<hr>
<h2 id="인터넷-통신">인터넷 통신</h2>
<p>인터넷 통신이란 무엇일까 ?
내가 한국에서 메시지를 보내면, 미국에 있는 친구의 PC가 이를 받게 된다. 이때 메시지는 인터넷망을 통해 전달된다. 인터넷에는 수많은 <strong>노드</strong>가 존재하며, 우리가 보낸 메시지는 이 노드들을 거쳐 최종 목적지까지 도달하게 된다.</p>
<hr>
<h2 id="ip">IP</h2>
<p>이 복잡한 인터넷망에서 메시지를 원활하게 전달하려면 일정한 규칙이 필요하다. 이를 <strong>통신 규약(Protocol)</strong>이라고 하며, 대표적인 예가 <strong>IP(Internet Protocol)</strong>이다. 
IP는 클라이언트의 IP 주소에서 서버의 IP 주소로 데이터를 전송하는 방식으로 인터넷 통신을 수행한다. 데이터는 <strong>패킷</strong>이라는 전송 단위로 나누어지며, 각 패킷에는 출발지 IP와 목적지 IP 정보가 포함된다. 
이렇게 만들어진 IP 패킷은 인터넷망을 구성하는 여러 노드를 거치며, 마치 파도에 떠밀리듯 여기저기 경로를 변경하면서 최종 목적지까지 도달하게 된다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/0a22323e-02b3-49a5-85c8-3e885dbc58b6/image.png" alt=""></p>
<p>위 사진처럼 패킷이라는 단위 안에 출발지 IP 주소와 목적지 IP 주소, 그리고 전송 데이터 등을 담아서 전송한다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/9e0313e1-0c61-449a-b71a-9124b7eef0e0/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/5ff48f4f-a4d8-4080-83f0-ea894dc429b1/image.png" alt=""></p>
<p>응답할 때도 마찬가지로 패킷에다가 여러 정보를 담아서 전송한다.</p>
<p>하지만 이러한 IP 에는 한계점이 존재하는데..</p>
<blockquote>
<p>IP 의 한계</p>
</blockquote>
<ul>
<li>비연결성<ul>
<li>패킷을 받을 대상이 없거나, 서비스 불능 상태여도 그 상태를 확인하지 않고 무작정 패킷을 전송한다.</li>
</ul>
</li>
<li>비신뢰성<ul>
<li>통신 도중 패킷이 사라졌을 때의 대안책이 없다.</li>
<li>패킷이 전송한 순서대로 도착하지 않았을 때의 대안책이 없다.</li>
</ul>
</li>
<li>프로그램 구분<ul>
<li>같은 IP 주소를 사용하는 서버에서 통신하고자 하는 애플리케이션이 여러 개일 때에도 문제가 있다.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="tcp--udp">TCP / UDP</h2>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/d7a84400-6940-485a-ad04-47648d47b3f2/image.png" alt=""></p>
<p>TCP/UDP는 각각 Transfer Control Protocol/User Datagram Protocol의 약자로, 인터넷 프로토콜 4계층중 세 번째 계층인 전송 계층이다. IP 계층 위에서 보완해주는 용도라고 생각하면 된다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/e21fd530-fa76-41e2-9256-8e92531390cc/image.png" alt=""></p>
<p>위 그림과 같이 메시지가 생성되는데, 이 과정을 자세히 살펴보자면 다음과 같다.</p>
<ol>
<li><p><strong>애플리케이션 계층</strong></p>
<ul>
<li>프로그램이 <code>&quot;Hello World&quot;</code> 메시지를 생성함.</li>
<li>Socket 라이브러리를 통해 운영체제(OS)로 메시지를 전달함.</li>
</ul>
</li>
<li><p><strong>전송 계층 (TCP)</strong></p>
<ul>
<li><code>&quot;Hello World&quot;</code> 메시지에 TCP 헤더를 추가하여 <strong>TCP 세그먼트</strong>를 생성함.</li>
<li>TCP 헤더에는 <strong>송수신 포트 정보, 순서 번호, 오류 검사 정보</strong> 등이 포함됨.</li>
</ul>
</li>
<li><p><strong>인터넷 계층 (IP)</strong></p>
<ul>
<li>TCP 세그먼트에 IP 헤더를 추가하여 <strong>IP 패킷</strong>을 생성함.</li>
<li>IP 헤더에는 <strong>송신 IP, 목적지 IP, 패킷 식별 정보</strong> 등이 포함됨.</li>
</ul>
</li>
<li><p><strong>네트워크 액세스 계층</strong></p>
<ul>
<li>IP 패킷에 Ethernet 프레임을 추가하여 <strong>Ethernet 프레임</strong>을 생성함.</li>
<li>프레임에는 <strong>송신 MAC, 목적지 MAC, CRC 체크섬</strong> 등의 정보가 포함됨.</li>
<li>LAN 카드에서 신호 변환 후, 네트워크를 통해 데이터가 전송됨.</li>
</ul>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/e6050850-1fb4-4c53-8584-b6bfd18b43a8/image.png" alt=""></p>
<hr>
<h2 id="tcp-특징">TCP 특징</h2>
<p>TCP의 주요 특징은 <strong>연결 지향, 데이터 전달 보증, 순서 보장</strong>이다.</p>
<p>TCP는 3-Way Handshake 방식을 통해 가상 연결을 수립하며, 클라이언트와 서버가 통신 가능 상태인지 확인한 후 연결을 진행하여 신뢰성을 높인다. 또한, 데이터 누락을 감지할 수 있어 데이터 전달이 보증된다. 마지막으로, TCP 패킷에는 순서 정보가 포함되어 있어 패킷의 순서를 보장할 수 있다.</p>
<h3 id="tcp-3-way-handshake">TCP 3 way handshake</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/2120104a-e56a-4b79-9712-7d5dfc3ebebc/image.png" alt=""></p>
<p>TCP 3 way handshake는 TCP/IP 프로토콜을 이용해 통신을 하기 전, 정확한 전송을 보장하기 위해 상대 컴퓨터와 사전에 연결이 잘 되는지 확인하는 과정이다.</p>
<p>만약에 서버가 연결이 안되었을 경우에는 ?</p>
<ol>
<li>SYN을 보냈는데 응답이 없음</li>
<li>ACK이 돌아오지 않으므로 연결이 안됨을 확인</li>
<li>데이터를 전송하지 않음</li>
</ol>
<h3 id="데이터-전달-보증">데이터 전달 보증</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/dd95e9c0-38f4-4d8b-bc99-e94ca5f25207/image.png" alt=""></p>
<p>TCP를 통해 데이터 전송이 성공하면, 서버는 데이터를 정상적으로 수신했다는 의미로 ACK 패킷을 보낸다. 이를 통해 데이터가 올바르게 전달되었음을 보증한다.</p>
<h3 id="데이터-순서-보장">데이터 순서 보장</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/a5029ee4-07ae-4d79-b318-896183988cc3/image.png" alt=""></p>
<p>TCP는 헤더에 포함된 전송 제어 정보를 이용하여 데이터의 순서를 보장한다.
수신 측은 순서가 어긋난 패킷을 재정렬하고, 손실된 패킷에 대해 송신 측에 재전송을 요청한다.</p>
<hr>
<h2 id="udp-특징">UDP 특징</h2>
<p>UDP는 TCP에 비해 기능이 거의 없는 비연결성, 비신뢰성 전송 프로토콜로, 3-way handshake 과정이 없으며 데이터 전달과 순서를 보장하지 않는다.</p>
<p>IP 프로토콜과 유사하지만, 포트 번호와 체크섬 기능이 추가되어 있어 애플리케이션 간 데이터 구분과 간단한 오류 검사가 가능하다.</p>
<p>이러한 UDP는 기능이 간단하기 때문에 개발자가 필요한 기능을 추가하여 커스터마이즈할 수 있는 장점이 있다. 최근 HTTP/3에서는 성능 최적화를 위해 3-way handshake 과정을 줄이고, 빠르고 효율적인 연결을 제공하기 위해 UDP를 기본 프로토콜로 사용하고 있다.</p>
<hr>
<h2 id="port">PORT</h2>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/5abc9030-28fc-46ea-8ebd-3bce3d5f83f8/image.png" alt=""></p>
<p>포트(Port)는 배가 도착하는 ‘항구’를 의미한다. 두 개 이상의 서버와 동시에 연결할 때, IP만으로는 패킷의 출발지나 목적지를 구분할 수 없다. 이를 해결하기 위해 출발지와 목적지에 포트 번호를 추가하여 구분할 수 있다. 하나의 컴퓨터에서도 각 연결마다 다른 포트를 사용하여 구분할 수 있기 때문이다. 비유하자면, IP는 아파트 주소이고, 포트는 동/호수 번호이다.</p>
<blockquote>
<p> FTP는 포트 20, 21을 사용하고 HTTP는 포트 80, HTTPS는 포트 443을 사용한다.</p>
</blockquote>
<hr>
<h2 id="dns">DNS</h2>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/3bb5c3b0-6b40-4187-8f13-7ca8a379da1a/image.png" alt=""></p>
<p>DNS 는 도메인 이름을 IP 주소에 매핑해주는 시스템이다. IP 주소는 기억하기 어렵고, 변경될 수 있어 접근이 불편하다. 따라서 DNS는 전화번호부처럼 도메인 이름에 해당하는 IP 주소를 등록하고 관리하여, 사용자가 쉽게 도메인으로 접속할 수 있도록 돕는다. 클라이언트가 특정 도메인을 검색하면, DNS 서버는 해당 도메인의 IP 주소를 반환하고, 클라이언트는 이를 통해 해당 서버에 접속한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ T I L ] 24.12.28]]></title>
            <link>https://velog.io/@sepang-pang/T-I-L-24.12.28</link>
            <guid>https://velog.io/@sepang-pang/T-I-L-24.12.28</guid>
            <pubDate>Fri, 27 Dec 2024 17:32:40 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p>MSA 아키텍처를 학습하면서 문득 의문이 들었다.
게이트웨이는 왜 존재해야 하는데 ?</p>
<h1 id="해결">해결</h1>
<p>스프링 <strong>MVC 서비스</strong>는 <strong>Tomcat</strong> 기반으로 <strong>쓰레드풀</strong>을 사용하여 동기 블로킹 방식으로 운영된다. 이때, 쓰레드풀이 고갈되면 새로운 요청을 처리할 수 없고, 기존 요청이 완료될 때까지 대기해야 한다. 결국, <strong>병목현상</strong>이 발생하여 서비스 성능이 저하된다.</p>
<p>반면, <strong>게이트웨이</strong>는 <strong>WebFlux</strong> 구조와 <strong>Netty</strong> 서버를 사용하여 <strong>비동기 논블로킹 방식</strong>으로 운영된다. 이 방식에서는 <strong>하나의 쓰레드</strong>가 여러 요청을 동시에 처리할 수 있으며, 요청이 완료되지 않아도 다른 요청을 계속 처리할 수 있다. 따라서, 하나의 쓰레드풀로 <strong>더 많은 요청을 처리</strong>할 수 있게 된다. 그러나 <strong>리소스</strong>에는 한계가 있기 때문에, <strong>쓰레드풀</strong> 고갈의 가능성은 여전히 존재한다.</p>
<p>게이트웨이는 <strong>부하 분산</strong>의 핵심적인 역할을 한다. 게이트웨이는 서비스로 전달하기 전에 <strong>부하를 여러 서버로 분산</strong>시킨다. 이를 위해 <strong>로드밸런서</strong>와 연동하여 요청을 분배하거나, <strong>Rate Limiting</strong> 을 설정하여 지나치게 많은 요청이 한 번에 들어오는 것을 방지할 수 있다.</p>
<p>게이트웨이를 통해, 뒤에 전달될 서비스들이 대량의 트래픽을 감당할 수 있도록 다양한 전략을 사용할 수 있다. 이러한 전략을 통해, 서비스가 많은 트래픽에 놀라지 않도록 처리할 수 있게 된다.</p>
<h1 id="정리">정리</h1>
<p>게이트웨이의 존재 의의</p>
<ol>
<li><p>보안 ⇒ 외부에 노출되는 API 는 하나로 정해진다.</p>
<p> 내부 port 는 막아놓고, 게이트웨 port 만 열어두니까 내부는 안전한 장소가 된다.</p>
</li>
</ol>
<ol start="2">
<li><p>로깅 ⇒ 원래는 각 서비스에서 로그를 모아서 정리를 해야되는데, 모든 요청이 게이트웨이에서 이루어지니까 그냥 여기서 로그를 쭉쭉 뽑아내면 된다.</p>
<p> 전처리, 후처리 필터를 걸어서 응답을 감싸서 처리하기가 좋다.</p>
</li>
</ol>
<ol start="3">
<li>로드밸런싱 ⇒ 로드밸런싱이 이제 제일 후순위다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ T I L ] 2024.12.02]]></title>
            <link>https://velog.io/@sepang-pang/T-I-L-2024.12.03</link>
            <guid>https://velog.io/@sepang-pang/T-I-L-2024.12.03</guid>
            <pubDate>Mon, 02 Dec 2024 16:27:29 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>이번 MSA 아키텍처 기반 과제를 수행함에 앞서, 단순히 기능 구현뿐 아니라 각 기능이 어떻게 상호작용하며 동작하는지를 명확히 이해하는 것이 중요하다고 생각했다. </p>
<p>이러한 이해의 차이는 과제 수행에서 얻어가는 것에 대해 분명 큰 영향을 미칠 것이다.</p>
<p>이에 과제 수행에 앞서 <code>Spring Cloud</code> 의 전반적인 흐름을 이해하는 시간을 가졌다.</p>
<h1 id="알게된-점">알게된 점</h1>
<pre><code class="language-java">// ==== 예시코드 === //
@FeignClient(name = &quot;product-service&quot;)
public interface ProductClient {

    @GetMapping(&quot;/product/{id}&quot;)
    String getProduct(@PathVariable(&quot;id&quot;) String id);
}</code></pre>
<p>위 코드를 예시로 들어 Spring cloud 의 흐름을 짚어보자면 다음과 같다.</p>
<ol>
<li><p><strong>Feign</strong> 이 <code>porduct-service</code> 를 호출.</p>
</li>
<li><p><strong>Eureka</strong> 서버가 <code>porduct-service</code> 인스턴스 목록을 반환함
 ex. <code>product-service</code> 의 8080, 8081, 8082 등의 인스턴스들을 나열함</p>
</li>
<li><p><strong>Ribbon</strong> 이 로드밸런싱 알고리즘을 통해 인스턴스 목록 중 하나를 선택.</p>
</li>
<li><p><strong>Fegin</strong> 이 선택된 인스턴스의 <code>/product/{id}</code> 에 대해 HTTP 요청을 함</p>
</li>
<li><p>해당 인스턴스는 응답을 반환</p>
</li>
<li><p><strong>Feign</strong> 이 클라이언트에게 결과 반환</p>
</li>
</ol>
<p>전반적으로 이러한 상호작용을 하며 동작하는 것으로 이해하였다.</p>
<p>막연히 어렵기만 했던 MSA 아키텍쳐였는데, 하나하나 이해한 후 전체적으로 바라보니, 비로소 감을 잡을 수 있게 되었다.</p>
<p>이제 이러한 기반 위에서 과제를 수행한다면 분명 좋은 결과가 있을 거라는 생각과 기대감이 든다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ T I L ] 2024.11.28]]></title>
            <link>https://velog.io/@sepang-pang/T-I-L-2024.11.28-qj57203d</link>
            <guid>https://velog.io/@sepang-pang/T-I-L-2024.11.28-qj57203d</guid>
            <pubDate>Thu, 28 Nov 2024 16:07:51 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p>The steps of the <strong>insertion sort</strong> algorithm:</p>

<ol>
    <li>Insertion sort iterates, consuming one input element each repetition and growing a sorted output list.</li>
    <li>At each iteration, insertion sort removes one element from the input data, finds the location it belongs within the sorted list and inserts it there.</li>
    <li>It repeats until no input elements remain.</li>
</ol>

<p>The following is a graphical example of the insertion sort algorithm. The partially sorted list (black) initially contains only the first element in the list. One element (red) is removed from the input data and inserted in-place into the sorted list with each iteration.</p>
<p><strong class="example">Example 1:</strong></p>
<img alt="" src="https://assets.leetcode.com/uploads/2021/03/04/sort1linked-list.jpg" style="width: 422px; height: 222px;" />
<pre>
<strong>Input:</strong> head = [4,2,1,3]
<strong>Output:</strong> [1,2,3,4]
</pre>


<p>삽입정렬 학습 간에 위와 같은 문제를 맞닥뜨리게 되었다.</p>
<p>요약하면 <code>LinkedList</code> 의 노드들을 오름차순으로 정렬 시키는 문제였다.</p>
<p>이외 같은 유형의 문제는 처음 접하기에 30분 넘게 끙끙 앓다가 그냥 <code>GPT</code> 선생님께 답을 여쭤보고, 해당 답을 이해하는 방향으로 틀었다.</p>
<h2 id="답안">답안</h2>
<pre><code class="language-java">public static ListNode solution(ListNode head) {
        if (head == null) {
            return head;
        }

        ListNode dummy = new ListNode(0);
        ListNode curr = head;

        while (curr != null) {
            ListNode prev = dummy; --- [1]
            ListNode next = curr.next; --- [2]

            while (prev.next != null &amp;&amp; prev.next.val &lt; curr.val) { --- [3]
                prev = prev.next; --- [4]
            }

            curr.next = prev.next; --- [5]
            prev.next = curr; --- [6]

            curr = next; --- [7]
        }

        return dummy.next;
    }</code></pre>
<p>이와 같이 문제에 대한 답을 도출하는 <code>GPT</code> 선생님이었다.</p>
<p>이제는 해당 코드를 분석하고 이해하는 일만 남았다.</p>
<p>그런데 이 과정에서 문제가 발생한다.</p>
<p>하나하나 값들을 대입해보며 코드를 이해해보고자 했는데, 도저히 이해가 안가는 것이다.</p>
<h3 id="초기값">초기값</h3>
<pre><code class="language-java">dummy = 0 -&gt; null
curr = 4 -&gt; 2 -&gt; 1 -&gt; 3 -&gt; null</code></pre>
<h3 id="첫-번째-반복">첫 번째 반복</h3>
<pre><code class="language-java">[1] prev = 0 -&gt; null
[2] next = 2 -&gt; 1 -&gt; 3 -&gt; null

[3] prev.next = null -&gt; fail
[4] skip

[5] curr.next = null
    curr = 4 -&gt; null
[6] prev.next = curr
    prev = 0 -&gt; 4 -&gt; null

[7] curr = 2 -&gt; 1 -&gt; 3 -&gt; null</code></pre>
<h3 id="두-번째-반복">두 번째 반복</h3>
<pre><code class="language-java">curr = 2 -&gt; 1 -&gt; 3 -&gt; null

[1] prev = 0 -&gt; 4 -&gt; null
[2] next = 1 -&gt; 3 -&gt; null

[3] prev.next.val = 4 &lt; curr.val = 2 =&gt; false
[4] skip

[5] curr.next = 4 -&gt; null
    curr = 2 -&gt; 4 -&gt; null
[6] prev.next = curr
    prev = 0 -&gt; 2 -&gt; 4 -&gt; null

[7] curr = 1 -&gt; 3 -&gt; null</code></pre>
<h3 id="세-번째-반복">세 번째 반복</h3>
<pre><code class="language-java">curr = 1 -&gt; 3 -&gt; null

[1] prev = 0 -&gt; 2 -&gt; 4 -&gt; null
[2] next = 3 -&gt; null

[3] prev.next.val = 2 &lt; curr.val = 1 =&gt; false
[4] skip

[5] curr.next = 2 -&gt; 4 -&gt; null
    curr = 1 -&gt; 2 -&gt; 4 -&gt; null
[6] prev.next = curr
    prev = 0 -&gt; 1 -&gt; 2 -&gt; 4 -&gt; null

[7] curr = 3 -&gt; null</code></pre>
<h4 id="네-번째-반복">네 번째 반복</h4>
<pre><code class="language-java">curr = 3 -&gt; null

[1] prev = 0 -&gt; 1 -&gt; 2 -&gt; 4 -&gt; null
[2] next = null

[3] prev.next.val = 1 &lt; curr.val = 3 =&gt; true
[4-1] prev = 1 -&gt; 2 -&gt; 4 -&gt; null
[4-2] prev.next.val = 2 &lt; curr.val = 3 =&gt; true
[4-3] prev = 2 -&gt; 4 -&gt; null
[4-4] prev.next.val = 4 &lt; curr.val = 3 =&gt; false
[4-5] skip

[5] curr.next = 4 -&gt; null
    curr = 3 -&gt; 4 -&gt; null
[6] prev.next = curr
    prev = 2 -&gt; 3 -&gt; 4 -&gt; null

[7] curr = null</code></pre>
<p>위와 같이 네 번의 반복 끝에</p>
<pre><code class="language-java"> return dummy.next;</code></pre>
<p> 더미를 반환하게 된다.</p>
<p> 이때 데이터는 다음과 같다.</p>
<pre><code class="language-java"> expected - 내가 예상한 값
 : 3 -&gt; 4

 actually - 실제 값
 : 1 -&gt; 2 -&gt; 3 -&gt; 4</code></pre>
<p>아니 분면 <code>네 번째 반복</code> 과정의 [6] 에서 <code>prev = 2 -&gt; 3 -&gt; 4 -&gt; null</code> 이 되었지 않은가.</p>
<p><code>0 -&gt; 1</code> 해당 노드는 대체 어디서 생겨나서 <code>dummy.next</code> 를 하면 <code>1 -&gt; 2 -&gt; 3-&gt; 4</code> 노드가 나오는 것인가 도통 이해가 안갔다.</p>
<h1 id="해결">해결</h1>
<p>위 물음에 대한 대답은 <code>dummy</code> 에 있었다.</p>
<pre><code class="language-java"> ListNode dummy = new ListNode(0); --- dummy : MainListNode@417
        ListNode curr = head;

        while (curr != null) { --- curr : MainListNode@415
            ListNode prev = dummy; --- dummy : MainListNode@417   prev : MainListNode@417
            ListNode next = curr.next;

            while (prev.next != null &amp;&amp; prev.next.val &lt; curr.val) {
                prev = prev.next;
            }

            curr.next = prev.next; --- curr : MainListNode@415   prev : MainListNode@417
            prev.next = curr; --- curr : MainListNode@415   prev : MainListNode@417 

            curr = next; --- next : MainListNode@418 
        }</code></pre>
<p>디버깅을 하면 위와 같이 주소값들이 할당되는 것을 볼 수 있다.
1 ~ 3 번째 반복을 위와 같이 수행하면 <code>dummy(0)</code> 은 <code>1 -&gt; 2 -&gt; 4</code> 를 참조하는 <code>LinkedList</code> 가 형성되는 것이다.</p>
<pre><code class="language-java">while (prev.next != null &amp;&amp; prev.next.val &lt; curr.val) {
     prev = prev.next;
}</code></pre>
<p>내가 헷갈렸던 문제의 코드는 이 부분이었다.</p>
<pre><code class="language-java">prev = 1 -&gt; 2 -&gt; 4 -&gt; null
prev = 2 -&gt; 4 -&gt; null</code></pre>
<p>이런식으로 <code>prev</code> 의 변화가 <code>dummy</code> 내의 노드들에 영향을 준다고 생각했던 것이다.
하지만 이런 생각은 잘못된 것이었고, 위 변화는 <code>prev</code> 라는 커서의 위치가 변한 것일 뿐이다.
즉, <code>prev</code> 가 가리키는 대상이 최초 <code>1</code> 에서 <code>2</code> 로 변화했다 이런 말인 것이다.</p>
<p>다시 말해서</p>
<pre><code class="language-java">prev = 2 -&gt; 4 -&gt; null =&gt; dummy = 2 -&gt; 4 -&gt; null</code></pre>
<p>위 과정이 아니라는 것이다.</p>
<p><code>dummy</code> 는 그저 <code>0</code> 이라는 헤드노드를 가지고 있을 뿐이고, 해당 헤드노드 <code>0</code> 은 <code>1</code> 을 여전히 참조하고 있다.
<code>1</code> 은 여전히 <code>2</code> 를 참조하고 있기도 한 것이다.</p>
<p>이에 이후에</p>
<pre><code class="language-java">[5] curr.next = 4 -&gt; null
    curr = 3 -&gt; 4 -&gt; null
[6] prev.next = curr
    prev = 2 -&gt; 3 -&gt; 4 -&gt; null</code></pre>
<p><code>prev</code> 의 헤드, <code>2</code> 가 <code>3 -&gt; 4 -&gt; null</code> 을 참조하게 변경된 것이다.</p>
<p>이후에 <code>dummy</code> 를 반환하게 되면,</p>
<p><code>dummy</code> 의 헤드는 <code>0</code> 이니까 <code>0</code> 은 현재 <code>1</code> 을 참조하고 있고, <code>1</code> 은 <code>2</code> 를 참조하고 있으며,  앞서 언급했던 거처럼 <code>2</code> 는 <code>3 -&gt; 4 -&gt; null</code> 를 참조하고 있으니
최종적으로 <code>dummy = 0 -&gt; 1 -&gt; 2 -&gt; 3 -&gt; 4 -&gt; null</code> 과 같은 <code>LinkedList</code> 를 구현하개 되는 것이다.</p>
<p>정리하자면, 위 코드는 <code>dummy</code> 노드를 활용해 각 노드를 정렬된 위치에 삽입하여 연결 리스트를 오름차순으로 정렬하는 코드인 것이다.</p>
<h1 id="알게된-점">알게된 점</h1>
<p>이번 문제를 통해 <code>LinkedList</code> 자료구조에 대해 더 깊이 이해할 수 있었다. 
단순히 개념만 알고 있었던 <code>LinkedList</code> 를 실제 코드로 구현하고 다뤄보며, 리스트의 동작 원리와 참조 관계에 대해 명확히 알게 되었다.</p>
<p>특히, <strong>포인터(참조값)</strong>의 개념에서 혼란을 겪었지만, 이번 학습을 통해 포인터가 변한다고 해서 객체 자체가 변하는 것이 아니라, 참조하는 위치만 바뀐다는 것을 이해했다. 
이러한 개념이 삽입 정렬의 동작 원리를 파악하는 데 큰 도움이 되었다.</p>
<p>결과적으로, 삽입 정렬과 연결 리스트의 동작을 코드로 직접 다뤄보며 보다 깊이 있는 학습을 할 수 있었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ T I L ] 2024.11.14]]></title>
            <link>https://velog.io/@sepang-pang/T-I-L-2024.11.14</link>
            <guid>https://velog.io/@sepang-pang/T-I-L-2024.11.14</guid>
            <pubDate>Thu, 14 Nov 2024 03:42:23 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<pre><code class="language-java">@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class OrderMenuDTO {

    @JsonIgnore
    private Long orderId;
    private Long orderMenuId;
    private UUID menuId;
    private String menuName;
    private int menuPrice;
    private int count;

    public static Map&lt;Long, List&lt;OrderMenuDTO&gt;&gt; from(List&lt;OrderMenu&gt; orderMenus) {
        List&lt;OrderMenuDTO&gt; list = getOrderMenuDTOS(orderMenus);

        return list.stream()
                .collect(Collectors.groupingBy(orderMenuDTO -&gt; orderMenuDTO.orderId));
    }

    private static List&lt;OrderMenuDTO&gt; getOrderMenuDTOS(List&lt;OrderMenu&gt; orderMenus) {
        return orderMenus.stream()
                .map(OrderMenuDTO::from)
                .toList();
    }

    public static OrderMenuDTO from(OrderMenu orderMenu) {
        return OrderMenuDTO.builder()
                .orderId(orderMenu.getOrder().getId())
                .orderMenuId(orderMenu.getId())
                .menuId(orderMenu.getMenu().getMenuId())
                .menuName(orderMenu.getMenu().getMenuName())
                .menuPrice(orderMenu.getOrderPrice())
                .count(orderMenu.getOrderCount())
                .build();
    }
}</code></pre>
<p>위와 같이 <code>OrderMen</code> 엔티티를 <code>DTO</code> 로 변환 후 <code>JsonIgnore</code> 처리 된 <code>orderId</code> 를 이용하여 <code>Map&lt;Long, List&lt;OrderMenuDTO&gt;&gt;</code> 를 생성하는 로직을 구현하였다.</p>
<p>이에 대해, <code>OrderMen</code> 에서 <code>Map&lt;Long, List&lt;OrderMenuDTO&gt;&gt;</code> 로 변환되는 과정을 하나로 줄이고, 이 과정을 통해 <code>orderId</code> 필드도 굳이 사용하지 않아도 되니, 해당 방향으로 리팩토링을 하는 게 어떻냐는 제안을 받았다.</p>
<h1 id="시도">시도</h1>
<pre><code class="language-java">private static Map&lt;Long, List&lt;OrderMenuDTO&gt;&gt; from(List&lt;OrderMenu&gt; orderMenus) {
        return orderMenus.stream()
                .collect(Collectors.groupingBy(
                        orderMenu -&gt; orderMenu.getOrder().getId(),
                        Collectors.mapping(
                                OrderMenuDTO::from,
                                Collectors.toList()
                        )
                ));
    }</code></pre>
<p>리팩토링은 간단했다.</p>
<p><code>OrderMenu</code> 의 리스트에 <code>grouping</code> 메서드를 통해 <code>orderId</code> 를 추출하면서 <code>Key</code> 로 가지게 하고, <code>value</code> 에는 <code>OrderDTO</code> 로 변환하는 메서드를 이용해 즉각적으로 <code>Map</code> 을 생성하게 하였다.</p>
<p>이렇게 개선하니 메서드도 간결해지고, 불필요한 필드값도 제거할 수 있게 되었다.</p>
<h1 id="결과">결과</h1>
<pre><code class="language-java">@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class OrderMenuDTO {

    private Long orderMenuId;
    private UUID menuId;
    private String menuName;
    private int menuPrice;
    private int count;

    private static Map&lt;Long, List&lt;OrderMenuDTO&gt;&gt; from(List&lt;OrderMenu&gt; orderMenus) {
        return orderMenus.stream()
                .collect(Collectors.groupingBy(
                        orderMenu -&gt; orderMenu.getOrder().getId(),
                        Collectors.mapping(
                                OrderMenuDTO::from,
                                Collectors.toList()
                        )
                ));
    }

    public static OrderMenuDTO from(OrderMenu orderMenu) {
        return OrderMenuDTO.builder()
                .orderMenuId(orderMenu.getId())
                .menuId(orderMenu.getMenu().getMenuId())
                .menuName(orderMenu.getMenu().getMenuName())
                .menuPrice(orderMenu.getOrderPrice())
                .count(orderMenu.getOrderCount())
                .build();
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ CS ] 프로세스의 메모리 구조와 PCB]]></title>
            <link>https://velog.io/@sepang-pang/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%9D%98-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B5%AC%EC%A1%B0%EC%99%80-PCB</link>
            <guid>https://velog.io/@sepang-pang/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%9D%98-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B5%AC%EC%A1%B0%EC%99%80-PCB</guid>
            <pubDate>Tue, 08 Oct 2024 14:44:25 GMT</pubDate>
            <description><![CDATA[<h1 id="프로세스">프로세스</h1>
<hr>
<h3 id="프로세스의-메모리-구조">프로세스의 메모리 구조</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/6b27c846-65e1-4f5b-9cc3-9fe32551ca5d/image.png" alt=""></p>
<p>운영체제는 위에서부터 스택, 힙, 데이터 영역, 코드 영역으로 나눌 수 있다.</p>
<ul>
<li><p><strong>스택</strong> : 지연변수, 매개변수, 리턴값 등 잠시 사용되었다가 사라지는 데이터를 저장하는 영역이고,  <code>LIFO(Last In, First Out)</code> 방식으로 운영된다.</p>
<p>함수 호출 시 할당되고 함수 반환 시 소멸되며, 컴파일 시 크기가 할당된다.</p>
</li>
</ul>
<ul>
<li><p><strong>힙</strong> : 힙 영역은 프로그래머가 직접 공간을 할당, 해제하는 메모리 공간이다.</p>
<p>힙 영역에서 <code>malloc()</code> 또는 <code>new</code> 연산자를 통해 메모리를 할당하고, <code>free()</code> 또는 <code>delete</code> 연산자를 통해 메모리를 해제한다.</p>
<p>힙 영역은 <code>선입선출(FIFO, First-In First-Out)</code> 의 방식으로, 가장 먼저 들어온 데이터가 가장 먼저 인출 된다.</p>
<p>이는 힙 영역이 <strong>메모리의 낮은 주소에서 높은 주소의 방향으로 할당되기 때문</strong>이다.</p>
<ul>
<li><p><strong>데이터 영역</strong> : BSS 영역과 Data 영역으로 나뉘고 정적할당에 관한 부분을 담당한다.</p>
<ul>
<li><strong>BSS 영역</strong> : 전역변수,static, const로 선언되어있는 변수 중 0으로 초기화 또는 <code>초기화가 어떠한 값으로도 되어 있지 않은 변수</code> 들이 이 메모리 영역에 할당된다.</li>
<li><strong>데이터 영역</strong> :  전역변수, static, const로 선언되어있는 변수 중 0이 아닌 값으로 <code>초기화된 변수</code> 가 이 메모리 영역에 할당된다.</li>
</ul>
</li>
<li><p><strong>코드 영역</strong> : 소스코드가 들어간다.</p>
</li>
</ul>
</li>
</ul>
<h3 id="프로세스의-상태">프로세스의 상태</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/04ddf331-dc30-4c21-ba85-fd4d0c720ab4/image.png" alt=""></p>
<ul>
<li><p><strong>생성 상태</strong> : 프로그램을 메모리에 가져와 실행 준비가 완료된 상태이다.</p>
</li>
<li><p><strong>준비 상태</strong> : 실행을 기다리는 모든 프로세스가 자기 차례를 기다리는 상태이다. </p>
<p>프로세스 제어 블록은 준비 큐(ready Queue) 에서 기다리며 cpu스케줄러에 의해 관리된다.</p>
<p>cpu 스케줄러가 <strong>dispatch(PID)</strong> 명령을 실행하여 준비상태에서 실행상태로 바뀌는 작업이 이루어진다.</p>
<ul>
<li><strong>실행 상태</strong> : 선택된 프로세스가 타임 슬라이스를 얻어 cpu를 사용하는 상태이다. </li>
</ul>
<p>실행상태에 들어가는 프로세스는 cpu의 개수 만큼이다.</p>
<ul>
<li><p>실행 상태에 있는 프로세스가 주어진 시간을 다 사용하면 <code>timeout(PID)</code> 가 실행되어 준비 상태로 전환</p>
</li>
<li><p>만약 실행상태 동안 작업이 완료되면 <code>exit(PID)</code> 가 실행되어 프로세스 정상 종료</p>
</li>
<li><p>프로세스가 입출력 요청시 CPU는 입출력 관리자에게 입출력을 요청 후 <code>block(PID)</code> 을 실행하는데 <code>block(PID)</code> 는 입출력이 완료될때까지 작업을 진행할 수 없기 떄문에 해당 프로세스를 대기 상태로 옮긴다.</p>
</li>
</ul>
</li>
<li><p><strong>휴식 상태</strong> : 프로세스가 작업을 일시적으로 쉬고 있는 상태이다. </p>
<p>사용하던 데이터가 메모리에 그대로 있고 PCB도 유지되므로 프로세스는 멈춘 지점에서 부터 재시작할 수 있다.</p>
<ul>
<li><strong>보류 상태</strong> : 프로세스가 메모리에서 잠시 쫓겨난 상태로 <code>일시 정지 상태</code> 라고도 불린다. </li>
</ul>
<p>보류 상태의 프로세스는 메모리 밖으로 쫓겨나 스왑 영역에 보관된다.</p>
</li>
<li><p><strong>완료 상태</strong> : 코드와 사용했던 데이터를 메모리에서 삭제하고 PCB 를 폐기한다.</p>
<p>정상적인 종료는 <code>exit()</code> 로 처리하고 만약 오류나 다른 프로세스에 의해 비정상적으로 종료되는 강제 종료를 만나면 디버깅 하기 위해 강제 종료 직전의 메모리 상태를 저장장치로 옮기는데 이를 <code>코어 덤프</code> 라고한다.</p>
</li>
</ul>
<h1 id="pcb-와-컨텍스트-스위칭">PCB 와 컨텍스트 스위칭</h1>
<hr>
<h3 id="pcb-란">PCB 란</h3>
<blockquote>
<p>PCB(Process Control Block)는 운영체제에서 관리하는 프로세스에 대한 메타데이터를 저장한 데이터블록</p>
</blockquote>
<h3 id="pcb-의-구조">PCB 의 구조</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/79180777-0daf-4bf2-b07e-232dd01abb2c/image.png" alt=""></p>
<ul>
<li><strong>프로세스 상태</strong> – 대기중, 실행 중 등 프로세스의 상태</li>
<li><strong>프로세스 번호(PID)</strong> – 각 프로세스의 고유 식별 번호(프로세스 ID)</li>
<li><strong>프로그램 카운터(PC)</strong> – 이 프로세스에 대해 실행될 다음 명령의 주소에 대한 포인터.</li>
<li><strong>레지스터</strong> – 레지스터관련 정보</li>
<li><strong>메모리 제한</strong> – 프로세스의 메모리 관련정보</li>
<li><strong>열린 파일 정보</strong> - 프로세스를 위해 열린 파일 목록들</li>
</ul>
<h3 id="컨텍스트-스위칭이란">컨텍스트 스위칭이란</h3>
<p>컨텍스트 스위칭이란 CPU 를 차지하던 프로세스가 나가고 새로운 프로세스를 받아들이는 작업을 말한다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/129730cc-8fc5-42e1-b483-972dffa03ccb/image.png" alt=""></p>
<p><code>프로세스1</code> 이 작업을 수행하다가, 인터럽트나 시스템 콜이 발생했을 때 <code>프로세스1</code> 은 지금까지 작업하던 내용을 <strong>PCB 에 저장</strong>할 것이다.</p>
<p>이후 대기하고 있던 <code>프로세스2</code> 가 작업을 수행하고, <code>프로세스</code> 도 작업이 일시중지되면, PCB 에 저장 후 <code>프로세스 1</code> 은 상태 복구 후 작업을 다시 수행한다. </p>
<h3 id="컨텍스트-스위칭-비용">컨텍스트 스위칭 비용</h3>
<ol>
<li><p><strong>유후시간 발생</strong> : <code>컨텍스트 스위칭</code> 을 할 때마다 유후시간이 발생한다.</p>
</li>
<li><p><strong>캐시 미스</strong> : <code>프로세스1</code> 이 실행 중일 때, 해당 프로세스의 페이지 테이블 매핑 정보는 <code>TLB</code> 에 캐싱된다.</p>
<p>그러나 <code>컨텍스트 스위칭</code> 이 발생하면, <code>TLB</code> 에 저장된 <code>프로세스1</code> 의 매핑 정보는 지워지고, <code>프로세스 2</code> 의 매핑 정보로 대체된다.</p>
<p>이후 다시 <code>프로세스1</code> 으로 전환될 때, <code>TLB</code> 에는 <code>프로세스1</code> 의 정보가 없으므로 <code>캐시 미스</code> 가 발생하고, 시스템은 페이지 테이블을 다시 참조해 데이터를 로드해야 한다.</p>
<p>이 과정에서 캐시의 이점을 잃고, 더 느린 메모리 접근으로 인해 성능 저하가 발생하는 것이다.</p>
<ol start="3">
<li><strong>오버헤드</strong> : CPU는 중단된 작업의 상태(레지스터, 프로그램 카운터, 메모리 매핑 등)를 저장하고, 새 작업의 상태를 로드해야 한다. </li>
</ol>
<p>이때 일정한 <code>오버헤드</code> 가 발생하게 되며 이 <code>오버헤드</code> 가 자주 발생하면 전체적으로 시스템 성능이 저하될 수 있으며, CPU의 유효 가동 시간에 영향을 미칠 수 있다.</p>
</li>
</ol>
<h1 id="참고자료">참고자료</h1>
<hr>
<p><a href="https://www.inflearn.com/course/%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%A9%B4%EC%A0%91-cs-%ED%8A%B9%EA%B0%95/dashboard">https://www.inflearn.com/course/%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%A9%B4%EC%A0%91-cs-%ED%8A%B9%EA%B0%95/dashboard</a></p>
<p><a href="https://all-young.tistory.com/17">https://all-young.tistory.com/17</a></p>
<p><a href="https://www.youtube.com/watch?v=a2GDsaReFEA">https://www.youtube.com/watch?v=a2GDsaReFEA</a></p>
<p><a href="https://dream-and-develop.tistory.com/168">https://dream-and-develop.tistory.com/168</a></p>
<p><a href="https://velog.io/@kimtaehyeun/OS-CHAPTER03.-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%99%80-%EC%8A%A4%EB%A0%88%EB%93%9C-01.-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%9D%98-%EA%B0%9C%EC%9A%94">https://velog.io/@kimtaehyeun/OS-CHAPTER03.-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%99%80-%EC%8A%A4%EB%A0%88%EB%93%9C-01.-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%9D%98-%EA%B0%9C%EC%9A%94</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ CS ] 가상메모리와 스와핑]]></title>
            <link>https://velog.io/@sepang-pang/CS-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B0%80%EC%83%81%EB%A9%94%EB%AA%A8%EB%A6%AC</link>
            <guid>https://velog.io/@sepang-pang/CS-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B0%80%EC%83%81%EB%A9%94%EB%AA%A8%EB%A6%AC</guid>
            <pubDate>Tue, 08 Oct 2024 12:40:58 GMT</pubDate>
            <description><![CDATA[<h1 id="가상메모리">가상메모리</h1>
<hr>
<h3 id="가상메모리란">가상메모리란</h3>
<blockquote>
<p>가상 메모리(virtual memory)는 OS에서 사용되는 메모리 관리 기법의 하나로 컴퓨터가
실제로 이용가능한 메모리 자원(실제주소, physical address)을 추상화하여 이를 사용하는
사용자들에게 매우 큰 메모리로 보이게 만드는 것을 말한다.</p>
</blockquote>
<h3 id="가상메모리의-원리">가상메모리의 원리</h3>
<ol>
<li><p>프로세스가 실행될 때 운영체제는 해당 프로세스에게 가상 주소 공간을 제공한다.</p>
</li>
<li><p>32bit 운영체제 기준으로 가상 주소 공간은 최대 4GB 의 크기로 제공한다.</p>
<span style="color:gray; font-size:15px;">
이때 일반적으로 유저모드 프로세스에 2GB 커널모드 공간에 2GB 로 분할되며, 유저모드의 2GB 중에서 OS 가 점유하는 부분을 제외하면, 실질적으로 1.7 ~ 1.8 GB 정도된다.
</span>
</li>
<li><p>프로세스 내의 가상 주고 공간은 페이지 단위로 나눠지고, 논리주소가 부여되며, <code>stack</code> 이나 <code>heap</code>, <code>code</code> 등이 해당 공간에 할당된다.</p>
</li>
<li><p>이때 사용되는 페이지만이 주 기억장치의 프레임과 매핑이 되며, 사용되지 않는 페이지는 보조기억장치에 저장된다.</p>
</li>
<li><p>이에 프로세스의 가상 주소 공간의 크기는 32bit 기준 4GB지만, 실제로 운용되는 것은 1mb ~ 2mb 이다. (예를 들어)</p>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/6fbe0a05-00d6-42fa-a0d3-613c8180ca53/image.png" alt="">
그림을 보면  가상 주소 공간의 <code>논리 주소</code>를 MMU 에서 관리하는 <code>페이지 테이블</code>이라는 공간이 <code>실제 물리 주소와 매핑</code> 시켜주는 것을 확인할 수 있다.</p>
<p>이때 속도 향상을 위해서 캐시 계층인 TLB 라는 녀석도 사용한다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/3c5f2e36-0d1e-4b5c-b91e-ffed50c568ae/image.png" alt=""></p>
<p>가상 주소에서 바로 페이지 테이블로 가는 것이 아니라, TLB 에 매핑된 물리 주소가 있는지 확인하고, 없으면 이후에 페이지 테이블로 향하는 것이다.</p>
<h3 id="가상메모리-기법-사용-이유">가상메모리 기법 사용 이유</h3>
<p>프로세스의 크기가 4GB 일때, 불필요한 부분까지 RAM 위에 올라가 있다면 소수의 프로세스 밖에 운용하지 못할 것이다.</p>
<p>이에 가상 메모리 기법을 통해 프로세스의 필요한 부분만 RAM 에 올림으로써 자원의 낭비를 줄이고, 많은 프로세스를 운용하게 할 수 있는 것이다.</p>
<p><strong>&quot;가상 메모리는 실제 메모리보다 더 크게 보이게 하는 기술이다&quot;</strong> 라는 말도 앞선 이유 때문이다.</p>
<p>또한 가장 큰 이유로써, <code>관리적 용이함</code> 이 있다.</p>
<ol>
<li>임의의 프로세스가 죽었을 때 OS 에서 이를 감지하여, 해당 프로세스의 페이지와 매핑된 프레임을 반환한다.</li>
<li>이를 통해 다른 프로세스는 반환된 공간을 사용할 수 있게 된다.</li>
<li><del>만일 가상메모리 기법이 존재하지 않는다면, 프로세스가 죽어도 RAM 위에서 자리를 차지할 것이며, 또 다른 프로세스들이 계속해서 죽어갈 때 끝내 RAM 에는 자리가 존재하지 않을 것이다. ~</del></li>
</ol>
<h1 id="페이지-폴트와-스와핑">페이지 폴트와 스와핑</h1>
<hr>
<h3 id="페이지-폴트란">페이지 폴트란</h3>
<blockquote>
<p>메모리에 적재된 페이지중에 사용 페이지가 없을 때를 말한다.</p>
</blockquote>
<h3 id="페이지-폴트의-원인">페이지 폴트의 원인</h3>
<ol>
<li><p><strong>Major Page Fault</strong>
요청한 페이지가 페이지 테이블에는 존재하지만, 물리 주소에는 존재하지 않는 경우를 말한다.</p>
<p>이는 최초에는 주 기억장치에 존재하던 페이지가 <code>페이지 교체 알고리즘</code> 에 의해 보조 기억장치(스왑 공간)로 <code>Swap-out</code> 됐을 것이고, 이후 프로세스가 해당 페이지에 다시 접근하려고 하는 것이 원인인 것이다.</p>
<p>이로 인해 운영체제는 해당 페이지를 다시 <code>Swap-in</code> 하여 주 기억장치로 복원 시켜야 하며, 이때  Disk I/O가 발생하게 된다.</p>
</li>
<li><p><strong>Minor Page Fault</strong>
요청한 페이지가 물리 메모리에는 로드되어 있지만 MMU에는 로드되지 않았다고 표시된 경우 이를 Minor page fault라고 한다.</p>
<p>프로세스의 스레드1이 페이지 1를 사용하고 있다고 할 때, 같은 프로세스의 스레드2가 페이지 1을 요청하는 경우를 예시로 들 수 있다. </p>
</li>
<li><p><strong>Invalid Page Fault</strong>
블루스크린 원인 중 하나이다.
요청한 페이지가 스왑 영역의 범위를 초과하거나, 페이지를 쓰기 불가능한 영역에 쓰려고 할 때 발생하는 페이지 폴트이다. 이 경우에는 페이지 폴트 핸들러가 <strong>세그멘테이션 폴트(Segmentation Fault)</strong>를 발생시킨다. 결과는 보통 커널 패닉(블루스크린)으로 나타난다.</p>
</li>
</ol>
<h3 id="스와핑">스와핑</h3>
<p><strong>Major Page Fault</strong> 가 발생하면 스와핑을 시도한다.</p>
<p>페이지 테이블에 존재하지만, RAM 에는 존재하지 않는 페이지를 하드 디스크의 스왑 영역에서 찾아와 RAM 으로 불러온다. 이를 <code>Swap-in</code> 이라고 한다.</p>
<p>그런데, RAM 에 빈 공간이 존재하지 않다면, 기존에 존재한 다른 페이지를 하드 디스크의 스왑 영역으로 빼내어 빈 공간을 창출한다. 이를 <code>Swap-out</code> 이라고 한다.</p>
<p>이러한 스와핑 과정은 <code>페이지 교체 알고리즘</code> 에 의해 동작한다.</p>
<h3 id="페이지-교체-알고리즘">페이지 교체 알고리즘</h3>
<ol>
<li><p><strong>FIFO (First In First Out)</strong>
<img src="https://velog.velcdn.com/images/sepang-pang/post/d90b656f-678d-4876-89bf-8db897f81e6f/image.png" alt="">
<code>FIFO(First In First Out)</code> 는 가장 먼저 온 페이지부터 교체하는 방법을 말한다.</p>
<p>앞의 그림처럼 1, 3, 0 순서대로 바뀌는 것을 볼 수 있다.</p>
</li>
<li><p><strong>LRU (Least Recently Used)</strong></p>
<p><code>LRU(Least Recently Used)</code> 는 최근에 사용되지 않은 페이지를 바꾸는 방법이다.</p>
<p>예를 들어 7 0 1 2 0 3 0 4 로 페이지 요청이 들어온다고 가정하고 4개의 페이지만 담는다고 가정하면 다음과 같이된다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/15082b8c-212f-494c-841e-ea05744e8ce1/image.png" alt=""></p>
<ul>
<li>처음 7 0 1 2 를 담고 있고, 그 다음 0이 들어온다면 스와핑이 일어나지 않는다. </li>
<li>이후에 3이 들어왔을 때 가장 참조가 오래된 페이지는 7이기 때문에 해당 페이지가 교체된다. 왜냐하면 7 -&gt; 0 -&gt; 1 -&gt; 2 순으로 페이지가 들어왔기 때문이다.</li>
<li>그리고 4가 들어왔을 때는 가장 오래된 1이 교체가 되는 것이다.</li>
</ul>
</li>
</ol>
<ol start="3">
<li><p><strong>NUR (Not Used recently)</strong>
<img src="https://velog.velcdn.com/images/sepang-pang/post/35ad481a-30ed-4dd1-83c3-0a45e416a29e/image.png" alt=""></p>
<p>clock 알고리즘이라고 부르기도 하는데, 0은 참조되지 않았음을 1은 최근에 참조됐음을 의미한다.</p>
<p>한 바퀴를 돌 동안 참조가 되지 않은 부분은 0으로 바뀌게되고, 참조가 된 부분은 1로 바뀌게 되는 알고리즘이다.</p>
</li>
<li><p><strong>LFU (Least Frequently Used)</strong></p>
<p><code>LFU(Least Frequently Used)</code> 알고리즘은 가장 참조 횟수가 적은 페이지를 교체하는 알고리즘이다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/eb98ad3f-2285-4b79-a7b9-07e7da55225c/image.png" alt=""></p>
<p>만약에 0 -&gt; 1 -&gt; 2 -&gt; 0 -&gt; 0 -&gt; 1 -&gt; 2 -&gt; 3 으로 요청이 들어온다고 해보자.</p>
<p>표에서 보는 거 처럼 먼저 0, 1, 2 가 쌓일 것이다.</p>
<p>이후 </p>
<ul>
<li>0 이 참조된다. 0 참조 횟수 : 2</li>
</ul>
</li>
</ol>
<ul>
<li>0 이 참조된다. 0 참조 횟수 : 3</li>
<li>1 이 참조된다. 1 참조 횟수 : 2</li>
<li>2 가 참조된다. 2 참조 횟수 : 2</li>
<li>3 이  참조된다. 1 과 2 가 횟수가 동일하므로 가장 먼저 들어왔었던 1 과 스와핑된다.</li>
</ul>
<h1 id="참고자료">참고자료</h1>
<hr>
<p><a href="https://www.inflearn.com/course/%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%A9%B4%EC%A0%91-cs-%ED%8A%B9%EA%B0%95/dashboard">https://www.inflearn.com/course/%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%A9%B4%EC%A0%91-cs-%ED%8A%B9%EA%B0%95/dashboard</a></p>
<p><a href="https://hooni-playground.com/939/">https://hooni-playground.com/939/</a></p>
<p><a href="https://www.youtube.com/watch?v=_SyFgWccEs8&amp;t=181s">https://www.youtube.com/watch?v=_SyFgWccEs8&amp;t=181s</a></p>
<p><a href="https://youtu.be/-jlzaslp-w4?t=1339">https://youtu.be/-jlzaslp-w4?t=1339</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ CS ] 인터럽트와 시스템콜]]></title>
            <link>https://velog.io/@sepang-pang/C-S-%EC%9D%B8%ED%84%B0%EB%9F%BD%ED%8A%B8%EC%99%80-%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%BD%9C</link>
            <guid>https://velog.io/@sepang-pang/C-S-%EC%9D%B8%ED%84%B0%EB%9F%BD%ED%8A%B8%EC%99%80-%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%BD%9C</guid>
            <pubDate>Mon, 07 Oct 2024 17:48:59 GMT</pubDate>
            <description><![CDATA[<h1 id="인터럽트">인터럽트</h1>
<hr>
<h3 id="인터럽트란">인터럽트란</h3>
<blockquote>
<p>인터럽트는 어떤 신호가 들어왔을 때 CPU 를 잠깐 정지시키는 것을 말한다.
산술 연산 오류, 프로세스 오류 등으로 발생하며, 뿐만 아니라 키보드, 마우스 등 IO 디바이스를 사용할 때의 인터럽트, 우선순위가 높은 프로세스의 발생 등으로 발생한다.</p>
</blockquote>
<p>인터럽트는 <code>하드웨어 인터럽트</code>, <code>소프트웨어 인터럽트</code> 두 가지로 나뉜다.</p>
<p><code>하드웨어 인터럽트</code> 는 IO 디바이스 등 하드웨어에서 발생하는 인터럽트이며, 마우스를 클릭하거나, 디스크에서 파일을 읽는 작업 등에서 발동된다.</p>
<p><code>소프트웨어 인터럽트</code> 는 <code>트랩(Trap)</code> 이라고도 하며, 프로세스의 오류나 프로세스의 시작 및 종료 등을 기반으로 발생한다.</p>
<h3 id="인터럽트-발생-과정">인터럽트 발생 과정</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/dfc75223-3e44-4f18-b831-cd05551ccc15/image.png" alt=""></p>
<ol>
<li>인터럽트 발생</li>
<li>프로세스 실행 중단</li>
<li>현재의 프로세스 상태 PCB 에 보존</li>
<li>인터럽트 벡터로 이동</li>
<li>인터럽트 서비스 루틴 실행</li>
<li>저장해둔 프로세스 상태 복구</li>
<li>중단된 프로세스 실행 재개</li>
</ol>
<p>인터럽트의 발생 과정은 대략적으로 위와 같이 정리할 수 있다.</p>
<p>인터럽트가 발생하면, 해당 프로세스는 <code>모든 작업을 일시정지</code> 하고, 작업 내용들을 <code>PCB</code> 에 백업을 한다.</p>
<p>이후 <code>인터럽트 벡터로 이동</code> 하는데, 여기에는 <strong>&quot;이때는 이렇게 하면돼~&quot;</strong> 라며, 인터럽트를 어떻게 처리하는지 알려주는 함수들이 존재한다.</p>
<p>이에 해당 함수들을 통해 인터럽트를 처리하고, <code>프로세스의 상태를 복구 후 다시 실행 재개</code> 하는 것이다.</p>
<h1 id="시스템콜">시스템콜</h1>
<hr>
<h3 id="시스템-콜이란">시스템 콜이란</h3>
<blockquote>
<p>시스템 콜(System Call)은 운영체제 커널이 제공하는 서비스에 접근하기 위해 사용하는 인터페이스다.</p>
</blockquote>
<h3 id="시스템-콜의-과정">시스템 콜의 과정</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/569b97b5-3c70-41c8-bba7-b3a6d443c2f2/image.png" alt=""></p>
<ol>
<li><code>시스템 콜</code> 호출</li>
<li>사용자 모드에서 커널 모드로 전환 : modebit 1 -&gt; 0</li>
<li>요청받은 <code>시스템 콜</code> 을 처리</li>
<li>사용자 모드로 전환 : modebit 0 -&gt; 1</li>
</ol>
<p><code>시스템 콜</code> 과정 사진을 살펴보면 <code>trap</code> 이 발생하는 것을 확인할 수 있다.</p>
<p><code>시스템 콜</code> 을 수행할 때도 인터럽트가 발생한다는 것이다.</p>
<p>이에 따르면 다음과 같이 정리할 수 있다.</p>
<blockquote>
<p>모든 인터럽트는 시스템 콜로 이어지지 않는다. 
하지만 모든 시스템콜은 인터럽트로 처리된다.</p>
</blockquote>
<h3 id="시스템-콜-사용-이유">시스템 콜 사용 이유</h3>
<p>위 내용을 토대로 유저 모드에서 어떤 파일을 읽거나 쓸 때, 혹은 열 때 시스템콜이 호출되어 커널에서 수행되는 것은 알겠다.</p>
<p>그렇다면 이러한 <code>시스템 콜</code> 을 대체 왜 사용하는 것일까 ?</p>
<p>만약에 유저모드에서 아무런 제약 없이 커널모드에 접근할 수 있다면, 악의적 공격자가 내 시스템 자원에 제한 없이 접근 가능하여 보안적 위험이 발생할 것이다.</p>
<p>이에 <code>시스템 콜</code> 을 통한 제한된 접근은 시스템의 안정성과 보안을 지키는 핵심요소이다.</p>
<h3 id="시스템-콜만으로-안전할까-">시스템 콜만으로 안전할까 ?</h3>
<p>유저모드와 커널모드의 분리를 통한 제한전 접근이 시스템의 안정성을 높이는 것은 이해했다.</p>
<p>하지만, 공격자가 <code>시스템 콜</code> 을 호출하면서 접근할 수 있는 거 아닌가 ? 라는 생각이 들었다.</p>
<blockquote>
<p>시스템 콜을 통해 요청된 작업은 항상 운영체제에 의해 검사된다. 이 과정에서 사용자 권한, 요청의 유효성, 작업의 안전성 등이 검토된다. 만약 요청이 시스템 안전성을 해칠 가능성이 있다 판단되면 요청을 거부한다.</p>
</blockquote>
<p>운영체제는 사용하지 않는 <code>시스템 콜</code> 을 빈번하게 호출하거나, 일반적이지 않은 순서로 시스템 콜을 사용하는 경우 의심할 수 있다.</p>
<p><code>시스템 콜</code> 의 순서와 패턴을 분석해 정상적인 동작과 비교해서 의심스러운지 탐지하기도 하며, 특정 규칙이나 행위 패턴을 정의하고, 이 규칙에 위배되는 <code>시스템 콜</code> 의 사용을 감지한다고 한다.</p>
<p>이처럼 <code>시스템 콜</code> 을 통한 악의적 접근이 시도될 수 있지만, 운영체제가 다양한 방식으로 이를 탐지하고 방어하는 역할을 수행한다.</p>
<h1 id="참고자료">참고자료</h1>
<hr>
<p><a href="https://www.inflearn.com/course/%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%A9%B4%EC%A0%91-cs-%ED%8A%B9%EA%B0%95/dashboard">https://www.inflearn.com/course/%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%A9%B4%EC%A0%91-cs-%ED%8A%B9%EA%B0%95/dashboard</a></p>
<p><a href="https://youtu.be/V4lp6iGoUFY">https://youtu.be/V4lp6iGoUFY</a></p>
<p><a href="https://youtu.be/r_kmB2wlqGI?t=993">https://youtu.be/r_kmB2wlqGI?t=993</a></p>
<p><a href="https://brightstarit.tistory.com/13">https://brightstarit.tistory.com/13</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[ CS ] 운영체제와 컴퓨터 시스템의 구조]]></title>
            <link>https://velog.io/@sepang-pang/C-S-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C%EC%99%80-%EC%BB%B4%ED%93%A8%ED%84%B0-%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9D%98-%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@sepang-pang/C-S-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C%EC%99%80-%EC%BB%B4%ED%93%A8%ED%84%B0-%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%9D%98-%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Mon, 07 Oct 2024 13:39:13 GMT</pubDate>
            <description><![CDATA[<h1 id="운영체제-개요">운영체제 개요</h1>
<hr>
<h3 id="운영체제의-역할">운영체제의 역할</h3>
<ul>
<li>CPU 스케줄링과 프로세스 상태관리</li>
<li>메모리 관리</li>
<li>디스크 파일 관리</li>
<li>I/O 디바이스 관리</li>
</ul>
<p align="center" style="color:gray; margin-top:40px">
  <img src="https://velog.velcdn.com/images/sepang-pang/post/65ab35bb-65d7-4816-bc25-5766c89cd2e3/image.png" style="padding: 0;margin:10px;">
  운영체제가 실행될 때의 과정
</p>

<p>그림과 같이 운영체제의 커널은 SSD 나 보조기억장치 등에 설치되어 있는 프로그램을 실행시면, 해당 프로그램을 주 기억장치 위에 올려둔다.</p>
<p>이때 프로그램은 프로세스가 된다. 이후 CPU 가 왔다 갔다하면서, 메모리에 있는 어떠한 명령어들을 수행하며, 프로그램을 실행시키는 것이다. (이와 관련해서 추후 포스팅에 상세히 작성하겠음)</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/1028543e-d613-476e-825d-47d2f6de743e/image.png" alt=""></p>
<p>이처럼 주 기억장치에는 여러 프로세스가 작업을 수행한다.</p>
<p>사진처럼 주 기억장치에 더이상 프로세스가 운용될 자리가 부족하다면, 운영체제는 스와핑이라는 메모리 관리 기법을 수행하기도 하며, 또한 우선순위를 기반으로 프로세스를 종료시키거나 생성하는 과정도 수행한다.</p>
<p>추가적으로 인터럽트라는 것도 필요한데, 이 모든 것들을 운영체제가 수행한다는 것이다.</p>
<p>정리하자면, 운영체제는 어떤 프로세스를 우선순위로 수행할 것인가에 대한 <code>CPU 스케줄링 및 프로세스 상태관리</code>, 작은 메모리를 기반으로 어떤 프로세스를 실행시킬 것인가에 대한 <code>메모리 관리</code>. SSD 나 HDD 에 저장할 때 어떻게 저장할 것인가에 대한 <code>디스크 파일 관리</code>, 키보드나 마우스 등을 이용할 때 설치하는 드라이버를 기반으로 해당 장치들을 컨트롤 하는 <code>I/O 디바이스 관리</code> 등을 수행한다는 것이다.</p>
<h3 id="운영체제의-구조">운영체제의 구조</h3>
<ul>
<li>유저프로그램</li>
<li><strong>인터페이스</strong></li>
<li><strong>시스템콜</strong></li>
<li><strong>커널</strong></li>
<li>하드웨어</li>
</ul>
<p>이때 인터페이스, 시스템콜, 커널 부분이 OS 에 해당한다.</p>
<p>인터페이스는 <code>GUI</code> 나 <code>CLI</code> 등이 있을 것이고, 현대 OS 는 <code>GUI</code> 를 기반한다.</p>
<p>커널 안에는 <code>I/O 디바이스</code>, <code>드라이버</code>, <code>파일 시스템</code> 등이 들어있으며, 이때 <code>드라이버</code> 같은 경우에는 우리도 많이 접했을 것이다.</p>
<p>키보드나 마우스 등을 설치할 때 그냥 사용할 수도 있겠지만, 어떤 드라이버를 설치하여 사용한 경우가 있다.</p>
<p>이때 해당 드라이버를 기반으로 OS 를 통해 하드웨어와 통신하여 사용하는 것이다.</p>
<h1 id="컴퓨터-시스템-구조">컴퓨터 시스템 구조</h1>
<hr>
<h3 id="컴퓨터의-요소">컴퓨터의 요소</h3>
<ul>
<li><strong>CPU</strong> : 메모리에서 레지스터로 올라간 값을 읽는 일꾼</li>
<li><strong>DMA 컨트롤러</strong> : CPU 의 일을 보조하는 일꾼</li>
<li><strong>메모리</strong> : 전자회로에서 데이터, 상태 등을 기록하는 장치 (작업장)</li>
<li><strong>타이머</strong> : 특정 프로그램에 시간을 다는 역할</li>
<li><strong>디바이스 컨트롤러</strong> : IO 디바이스들의 작은 CPU</li>
<li><strong>로컬버퍼</strong> : 디바이스에 달려있는 작은 메모리</li>
</ul>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/91c8767e-619e-4c5b-9092-ae7d0b457348/image.png" alt=""></p>
<p>사진과 같이 컴퓨터 시스템의 구조를 파악할 수 있다.</p>
<p>CPU 가 해야할 일이 많다보니 이를 보조하는 <code>DMA 컨트롤러</code> 가 존재하며, <code>타이머</code> 는 몇 초 안에는 작업이 끝나야 한다는 것을 정하고 특정 프로그램에 시간 제한을 다는 역할을 한다. 이를 통해 특정 프로그램의 무한 루프를 막을 수 있는 것이다.</p>
<p>디바이스 컨트롤러와 로컬 버퍼의 경우에는, 예를 들어 우리가 프린트를 한다고 가정해보자.</p>
<p>해당 작업은 CPU가 담당하지 않고 <code>디바이스 컨트롤러</code> 라는 각 디바이스에 붙어있는 컨트롤러에게 그 일을 전담하게 한다. I/O 디바이스에게 입출력을 하라는 명령이 들어오면 진행하며 나타나는 데이터들을 자신의 <code>로컬 버퍼</code> 에다가 저장을 시킨다. 물론 그 와중에 CPU는 계속해서 다른 일을 하게 되는 것이다.</p>
<h3 id="cpu">CPU</h3>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/0a8a3e1e-b9bd-4855-bdf2-f2598ec71561/image.png" alt=""></p>
<ul>
<li>*<em>ALU (산술논리장치) *</em>: 산술연산과 논리연산을 하는 회로장치</li>
<li><strong>CU ( 제어장치 )</strong> : 프로세스의 조작을 지시하며, 명령어들을 읽고 해석하며 데이터 처리를 위한 순서를 결정</li>
<li><strong>레지스터</strong> : CPU 안에 매우 빠른 기억장치</li>
</ul>
<p>만약에 어떤 작업을 수행한다고 하면, 먼저 <code>제어장치</code> 가 메모리에 있던 일을 <code>레지스터</code> 로 옮긴다.</p>
<p>이후 다시 <code>제어장치</code> 는 <code>산술논리연산장치</code> 에게 <code>레지스터</code> 에 올라온 값을 계산하라고 지시를 한다. </p>
<p>그렇게 <code>산술논리연산장치</code> 는 해당 값을 계산 후 <code>레지스터</code> 에 반영한다.</p>
<p>제어장치는 해당 계산값을 <code>레지스터</code> 에서 메모리로 옮기게 된다.</p>
<p>이렇게 해서 프로그램이 종료되고 나서 보조기억장치 등에 저장될 수도 있는 것이다.</p>
<p>이처럼 CPU 를 정의할 때는 <strong>&quot;메모리에 올라온 명령어를 읽는 일꾼&quot;</strong> 이라는 표현 보다는, <strong>&quot;메모리에서 레지스터로 올라간 명령어를 읽는 일꾼&quot;</strong> 이라는 표현이 더 정확한 것이다.</p>
<h1 id="참고자료">참고자료</h1>
<hr>
<p><a href="https://www.inflearn.com/course/%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%A9%B4%EC%A0%91-cs-%ED%8A%B9%EA%B0%95/dashboard">https://www.inflearn.com/course/%EA%B0%9C%EB%B0%9C%EC%9E%90-%EB%A9%B4%EC%A0%91-cs-%ED%8A%B9%EA%B0%95/dashboard</a></p>
<p><a href="https://m.yes24.com/Goods/Detail/108887922">https://m.yes24.com/Goods/Detail/108887922</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[선착순 쿠폰 발급 시 재고 감소의 동시성 문제 해결 방안 (3/3)]]></title>
            <link>https://velog.io/@sepang-pang/%EC%84%A0%EC%B0%A9%EC%88%9C-%EC%BF%A0%ED%8F%B0-%EB%B0%9C%EA%B8%89-%EC%8B%9C-%EC%9E%AC%EA%B3%A0-%EA%B0%90%EC%86%8C%EC%9D%98-%EB%8F%99%EC%8B%9C%EC%84%B1-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EC%95%88-33</link>
            <guid>https://velog.io/@sepang-pang/%EC%84%A0%EC%B0%A9%EC%88%9C-%EC%BF%A0%ED%8F%B0-%EB%B0%9C%EA%B8%89-%EC%8B%9C-%EC%9E%AC%EA%B3%A0-%EA%B0%90%EC%86%8C%EC%9D%98-%EB%8F%99%EC%8B%9C%EC%84%B1-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EC%95%88-33</guid>
            <pubDate>Tue, 21 May 2024 14:26:50 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>이번 포스팅에는 데드락, 레이스 컨디션 등의 문제를 다양한 Lock 전략을 사용하여 해소하는 과정을 다뤄보도록 하겠다.</p>
<p>주요 Lock 전략으로 <code>비관적 락</code> <code>낙관적 락</code> <code>네임드 락</code> <code>Redisson 분산 락</code> 등을 다룰 예정이다.</p>
<h1 id="비관적-락">비관적 락</h1>
<h3 id="정의">정의</h3>
<blockquote>
<p>트랜잭션들은 무조건 충돌할 수 있다. 이 데이터는 중요하니까 우선 락부터 걸자</p>
</blockquote>
<p><code>비관적 락</code> 이란 트랜잭션이 시작될 때 <code>s - lock</code> 또는 <code>x - lock</code> 을 걸고 시작하는 방법이다. </p>
<p>즉, <code>s - lock</code> 을 걸게 되면 write를 하기위해서 <code>x - lock</code> 을 얻어야하는데 <code>s - lock</code> 이 이미 다른 트랜잭션에 의해서 걸려 있으면 해당 <code>x - lock</code> 을 얻지 못해서 업데이트를 할 수 없다. </p>
<p>수정을 하기 위해서는 해당 트랜잭션을 제외한 모든 트랜잭션이 종료(commit) 되어야한다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/f6410281-30bb-48bb-a5a0-709c4291f3e0/image.png" alt=""></p>
<p>위의 표를 보며 비관적락에 대한 이해를 제대로 해보자.</p>
<blockquote>
<ol>
<li>TX 1 에서 <code>Coupon id = 1</code> 을 읽음 </li>
<li>TX 2 에서 <code>Coupon id = 1</code> 을 조회하고자 하지만 앞서 TX 1 에서 <code>Lock</code> 이 걸렸기에 대기</li>
<li>TX 1 에서 작업 완료 후 트랜잭션 해제 (commit)</li>
<li>TX 2 에서 <code>Coupon id = 1</code> 을 조회 후 작업 수행</li>
</ol>
</blockquote>
<p>이와 같이 데이터에는 락을 가진 트랜잭션만 접근이 가능하도록 제어하는 방법이 <code>비관적 락</code> 이다.</p>
<h3 id="구현">구현</h3>
<h4 id="couponservicejava">CouponService.java</h4>
<pre><code class="language-java">public void issueCoupon(CouponIssueParam param, User user) {
        // 쿠폰 조회
        Coupon coupon = getCoupon(param.getCouponId());

        // 쿠폰 발급
        UserCoupon userCoupon = UserCoupon.CreateUserCoupon(coupon, user);

        userCouponQueryService.saveUserCoupon(userCoupon);
    }</code></pre>
<p>위와 같이 쿠폰을 발급하는 로직이 있을 때, 충돌이 발생하는 지점에 <code>비관적 락</code> 을 걸어주면 된다.</p>
<p>여기서는 Coupon 에서 충돌이 발생하기에 해당 조회 메서드에서 수행하겠다.</p>
<h4 id="couponrepositoryjava">CouponRepository.java</h4>
<pre><code class="language-java">    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query(&quot;select c from Coupon c where c.id = :couponId and c.isDeleted = false&quot;)
    Optional&lt;Coupon&gt; findOneCouponByCouponId(@Param(&quot;couponId&quot;) Long couponId);</code></pre>
<p>쿼리문 위에 <code>@Lock(LockModeType.PESSIMISTIC_WRITE)</code> 와 같이 어노테이션을 달아주면 <code>비관적 락</code> 설정을 끝이다.</p>
<p>해당 어노테이션은 <code>x - lock</code> 을 의미하며, 이 락이 설정된 데이터에 대해서는 해당 트랜잭션이 완료될 때까지 다른 트랜잭션이 해당 데이터를 읽거나 쓰는 것을 방지한다.</p>
<h3 id="테스트">테스트</h3>
<h4 id="couponconcurrencytestjava">CouponConcurrencyTest.java</h4>
<pre><code class="language-java">    @Test
    @DisplayName(&quot;쿠폰 여러 명 발급&quot;)
    void 쿠폰_여러_명_발급() throws InterruptedException {
        int threadCount = 1000;
        ExecutorService executorService = Executors.newFixedThreadPool(32);
        CountDownLatch latch = new CountDownLatch(threadCount);

        for (int i = 0; i &lt; threadCount; i++) {
            final int threadNumber = i + 1;
            int key = i;
            executorService.submit(() -&gt; {
                try {
                    couponService.issueCoupon(param, users.get(key));
                    System.out.println(&quot;Thread &quot; + threadNumber + &quot; - 성공&quot;);

                } catch (PessimisticLockingFailureException e) {
                    System.out.println(&quot;Thread &quot; + threadNumber + &quot; - 락 충돌 감지&quot;);

                } catch (Exception e) {
                    System.out.println(&quot;Thread &quot; + threadNumber + &quot; - &quot; + e.getMessage());

                } finally {
                    latch.countDown();
                }
            });
        }

        latch.await();
        executorService.shutdown();

        Long count = userCouponRepository.countByCouponId(param.getCouponId());

        assertThat(count).isEqualTo(100);
    }</code></pre>
<p>32 개의 스레드 환경에서 1,000 명의 유저가 총 재고 100 장인 쿠폰에 대해 발급을 요청한다면 100 장만 발급이 되어야할 것이다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/d40a6857-8f58-4df9-a129-dd65a5ab07d1/image.png" alt=""></p>
<p>테스트가 통과됐다 !!!</p>
<h4 id="query-log">Query Log</h4>
<pre><code class="language-java">Hibernate: 
    select
        c1_0.id,
        c1_0.coupon_image,
        c1_0.coupon_name,
        c1_0.created_at,
        c1_0.expired_at,
        c1_0.is_deleted,
        c1_0.modified_at,
        c1_0.remain_quantity,
        c1_0.stock_status,
        c1_0.total_quantity 
    from
        coupon c1_0 
    where
        c1_0.id=? 
        and c1_0.is_deleted=0 for update
Hibernate: 
    insert 
    into
        user_coupon
        (coupon_id, user_id, id) 
    values
        (?, ?, ?)
Hibernate: 
    update
        coupon 
    set
        coupon_image=?,
        coupon_name=?,
        expired_at=?,
        is_deleted=?,
        modified_at=?,
        remain_quantity=?,
        stock_status=?,
        total_quantity=? 
    where
        id=?
Thread 32 - 성공

Hibernate: 
    select
        c1_0.id,
        c1_0.coupon_image,
        c1_0.coupon_name,
        c1_0.created_at,
        c1_0.expired_at,
        c1_0.is_deleted,
        c1_0.modified_at,
        c1_0.remain_quantity,
        c1_0.stock_status,
        c1_0.total_quantity 
    from
        coupon c1_0 
    where
        c1_0.id=? 
        and c1_0.is_deleted=0 for update
Hibernate: 
    insert 
    into
        user_coupon
        (coupon_id, user_id, id) 
    values
        (?, ?, ?)
Hibernate: 
    update
        coupon 
    set
        coupon_image=?,
        coupon_name=?,
        expired_at=?,
        is_deleted=?,
        modified_at=?,
        remain_quantity=?,
        stock_status=?,
        total_quantity=? 
    where
        id=?
Thread 10 - 성공

Hibernate: 
    select
        c1_0.id,
        c1_0.coupon_image,
        c1_0.coupon_name,
        c1_0.created_at,
        c1_0.expired_at,
        c1_0.is_deleted,
        c1_0.modified_at,
        c1_0.remain_quantity,
        c1_0.stock_status,
        c1_0.total_quantity 
    from
        coupon c1_0 
    where
        c1_0.id=? 
        and c1_0.is_deleted=0 for update
Hibernate: 
    insert 
    into
        user_coupon
        (coupon_id, user_id, id) 
    values
        (?, ?, ?)
Hibernate: 
    update
        coupon 
    set
        coupon_image=?,
        coupon_name=?,
        expired_at=?,
        is_deleted=?,
        modified_at=?,
        remain_quantity=?,
        stock_status=?,
        total_quantity=? 
    where
        id=?
Thread 2 - 성공

...</code></pre>
<p>출려된 쿼리를 보면 각 스레드마다 <code>select</code> -&gt; <code>insert</code> -&gt; <code>update</code> 순서대로 딱딱 DML 명령어가 수행된 것을 확인할 수 있다.</p>
<p>여기서 주목할 점이 <code>selcet</code> 명령어에 나타나있는 <code>for update</code> 문인데, 이는 <code>&quot;데이터 수정하려고 찾았으니까 다른 사람들은 사용하지 마세요&quot;</code> 정도의 의미로 이해하면 될 거 같다.</p>
<h1 id="낙관적-락">낙관적 락</h1>
<h3 id="정의-1">정의</h3>
<blockquote>
<p>트랜잭션들은 충돌하지 않을 거야 ~</p>
</blockquote>
<p><code>낙관적 락</code> 이란 <code>비관적 락</code> 과는 다르게 DB 에서 락을 잡는 게 아닌 <code>Application Level</code> 에서 잡아주는 락이다.</p>
<p>특정 테이블을 읽어올 때의 <code>version</code> 과 <code>commit</code> 될 때의 <code>version</code> 을 비교하여, 서로 상이하다면 트랜잭션의 <code>rollback</code> 이 발생하고 다시 재수행하는 방식인 것이다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/d2d5fae3-54c1-447f-b798-11b49c446038/image.png" alt=""></p>
<p>위의 표를 보며 <code>낙관적 락</code> 에 대한 이해를 제대로 해보자.</p>
<ol>
<li>TX 1 에서 Coupon id = 1 을 읽음 <code>version = 1</code></li>
<li>TX 2 에서 Coupon id = 1 을 읽음 <code>version = 1</code></li>
<li>TX 1 에서 <code>vesrion = 2</code> 로 상승 시킨 후 작업 종료</li>
<li>TX 2 에서 <code>vesrsion = 1</code> 에 대해 <code>UPDATE</code>를 수행하고자 하지만, <code>vesrsion</code> 불일치로 <code>OptimisticLockException</code> 발생 후 <code>rollback</code></li>
<li>다시 최신 <code>vesrsion</code> 엔티티를 조회 후 작업 수행</li>
</ol>
<h3 id="구현-1">구현</h3>
<h4 id="couponjava">Coupon.java</h4>
<pre><code class="language-java">@Entity
@Getter
@Table(name = &quot;coupon&quot;)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Coupon extends Timestamped {

    ....

    @Version
    private Long version;

    }</code></pre>
<p>우선 <code>낙관적 락</code> 으로 관리할 엔티티에 <code>@Version</code> 어노테이션을 달아주어 필드값을 추가한다.</p>
<p><code>@Version</code> 어노테이션은 엔티티의 버전 관리를 위해 사용되며, 이 필드는 엔티티가 변경될 때마다 자동으로 증가한다.</p>
<h4 id="couponrepositoryjava-1">CouponRepository.java</h4>
<pre><code class="language-java">    @Lock(LockModeType.OPTIMISTIC)
    @Query(&quot;select c from Coupon c where c.id = :couponId and c.isDeleted = false&quot;)
    Optional&lt;Coupon&gt; findOneCouponByCouponId(@Param(&quot;couponId&quot;) Long couponId);</code></pre>
<p><code>@Lock(LockModeType.OPTIMISTIC)</code> 어노테이션은 해당 엔티티를 조회할 때 <code>낙관적 락</code> 을 사용하여 데이터의 동시성을 관리하고, 트랜잭션 커밋 시점에 <code>version</code> 필드의 변경 여부를 검사하여 다른 트랜잭션에 의해 데이터가 변경된 경우 <code>OptimisticLockException</code> 을 발생시켜 충돌을 처리한다.</p>
<h4 id="optimisticlockfacadejava">OptimisticLockFacade.Java</h4>
<pre><code class="language-java">@Component
@RequiredArgsConstructor
public class OptimisticLockFacade {

    private final CouponService couponService;

    public void issueCoupon(CouponIssueParam param, User user) throws InterruptedException {
        while (true) {
            try {
                couponService.issueCoupon(param, user);
                break;
            } catch (IllegalArgumentException e) {
                throw e;
            } catch (Exception e) {
                Thread.sleep(50);
            }
        }
    }
}</code></pre>
<p>실패했을 때 재시도를 위해 <code>Facade</code> 패턴을 도입하여 이를 처리하도록 하였다.</p>
<ol>
<li>재시도를 수행해야 하므로 <code>while</code> 문으로 감싸준다.</li>
<li>발급이 완료 되거나, 혹은 쿠폰 재고가 소진되었을 경우 <code>while</code> 문을 탈출한다.</li>
<li>이외 발급 실패 시 <code>50ms</code> 대기 후 재시도 한다.</li>
</ol>
<p>굳이 <code>Facade Class</code> 를 만들지 않아도 동작가능하게 만들 수는 있지만, 이럴 경우에는 <code>CouponService</code> 에서 너무 많은 책임을 가지게된다.</p>
<blockquote>
<ol>
<li>쿠폰 발급 책임</li>
<li>재시도 책임</li>
</ol>
</blockquote>
<p>이렇게 각각의 책임을 한 클래스에서 한 가지만 가지게 의도하였다.</p>
<p>이에 재시도를 해야하는 책임을 <code>Facade</code> 로 옮김으로써 <code>CouponService</code> 에서는 쿠폰 발급에 대한 책임만을 가지게 한 것이다.</p>
<h3 id="테스트-1">테스트</h3>
<h4 id="couponconcurrencytestjava-1">CouponConcurrencyTest.java</h4>
<pre><code class="language-java">    @Test
    @DisplayName(&quot;쿠폰 여러 명 발급&quot;)
    void 쿠폰_여러_명_발급() throws InterruptedException {
        int threadCount = 1000;
        ExecutorService executorService = Executors.newFixedThreadPool(32);
        CountDownLatch latch = new CountDownLatch(threadCount);

        for (int i = 0; i &lt; threadCount; i++) {
            final int threadNumber = i + 1;
            int key = i;
            executorService.submit(() -&gt; {
                try {
                    optimisticLockFacade.issueCoupon(param, users.get(key));
                    System.out.println(&quot;Thread &quot; + threadNumber + &quot; - 성공&quot;);

                } catch (PessimisticLockingFailureException e) {
                    System.out.println(&quot;Thread &quot; + threadNumber + &quot; - 락 충돌 감지&quot;);

                } catch (Exception e) {
                    System.out.println(&quot;Thread &quot; + threadNumber + &quot; - &quot; + e.getMessage());

                } finally {
                    latch.countDown();
                }
            });
        }

        latch.await();
        executorService.shutdown();

        Long count = userCouponRepository.countByCouponId(param.getCouponId());

        assertThat(count).isEqualTo(100);
    }</code></pre>
<p>요구사항은 앞서 진행했던 것과 동일하게 32 개의 스레드 환경에서 1,000 명의 유저가 총 재고 100 장인 쿠폰에 대해 발급을 요청한다면 100 장만 발급이 되어야할 것이다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/051d259a-080d-457b-97f2-d75dcab04c54/image.png" alt=""></p>
<p>테스트가 무사히 통과됐다.</p>
<p>이전 <code>비관적 락</code> 을 통해 테스트를 수행했을 대는 <code>4sec 759ms</code> 가 걸렸다면, 이번에는 실패할 때 재시도하는 로직이 존재하기에 총 <code>6sec 620ms</code> 로, <code>39.1%</code> 정도 더 소요되는 것을 확인할 수 있었다.</p>
<h4 id="query-log-1">Query Log</h4>
<pre><code class="language-java">...

Hibernate: 
    update
        coupon 
    set
        coupon_image=?,
        coupon_name=?,
        expired_at=?,
        is_deleted=?,
        modified_at=?,
        remain_quantity=?,
        stock_status=?,
        total_quantity=?,
        version=? 
    where
        id=? 
        and version=?

...</code></pre>
<p>출력된 쿼리를 보면 특정 <code>version</code> 의 엔티티에 대하여 <code>version</code> 을 포함한 다른 요소들도 <code>UPDATE</code> 를 수행하는 것을 확인할 수 있었다.</p>
<h1 id="네임드-락">네임드 락</h1>
<h3 id="정의-2">정의</h3>
<blockquote>
<p>이 자원은 중요하니, 사용하기 전에 이름을 가진 키로 잠그자. 누군가 이미 사용 중이면 기다릴게!</p>
</blockquote>
<p><code>네임드 락</code> 은 중요한 자원에 이름을 붙여서 그 자원을 사용할 때 다른 누구도 못 쓰게 잠그는 방법이다. </p>
<p>이름이 있는 키를 사용해서 잠그는 것인데, 예를 들어, <code>문서1</code> 이라는 중요한 파일을 여러 트랜잭션이 동시에 수정하려고 할 때, 첫 번째 트랜잭션이 <code>문서1</code> 이라는 키로 잠그면 다른 트랜잭션은 첫 번째 트랜잭션이 끝날 때까지 기다려야 하는 것이다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/289d1f5e-1bea-425d-a875-ff842bd96419/image.png" alt=""></p>
<blockquote>
<ol>
<li>TX 1 에서 &quot;1&quot; 이라는 이름의 <code>Lock</code> 을 획득한다</li>
<li>TX 1 은 이제 비즈니스 로직을 수행할 수 있는 권한을 얻었으며, 이에 비즈니스 로직을 수행한다.</li>
<li>TX 2 는 &quot;1&quot; 이라는 이름의 <code>Lock</code> 을 획득하고자 하지만, 이미 TX 1 이 사용중이기에 대기한다.</li>
<li>TX 1 이 작업 종료 후 <code>Lock</code> 을 해제하고, TX 2 가 다시 요청해 <code>Lock</code> 을 획득한다.</li>
</ol>
</blockquote>
<p>이와 같이 <code>네임드 락</code> 이란 데이터 자체에 락을 거는 게 아니라, 특정 데이터에 접근할 수 있는 <code>Key</code> 또는 <code>관문 ?</code> 이라고 생각하면 편할 거 같다.</p>
<h3 id="구현-2">구현</h3>
<p><code>네임드 락</code> 을 사용할 때는 데이터 소스를 분리하는 게 좋다고 한다.</p>
<blockquote>
<p><code>네임드 락</code> 은 lock 획득에 필요한 커넥션 1개, transaction (로직) 에 필요한 커넥션 1개와 같이 커넥션을 2개를 사용한다.
이렇게 두 개의 커넥션을 사용하게 되면, 커넥션 풀의 사용률이 높아져 커넥션 부족 현상이 발생할 가능성이 있다.
이에 <code>네임드 락</code> 은  데이터소스를 분리하는 것이 좋다. </p>
</blockquote>
<p>이에 데이터 소스를 분리하려고 하였으며, <code>jdbcTemplate</code> 을 이용하여 <code>네임드 락</code> 전용 리포지토리를 구현하고자 하였다.</p>
<p>하지만 데이터 소스 분리는 아직,,, 어떻게 하는 건지 이해가 안가서 추후에 도전해보기로 보류했다.</p>
<p>우선 아쉽게나마 <code>jdbcTemplate</code> 을 이용하여 <code>네임드 락</code> 전용 리포지토리를 구현해보았다.</p>
<p>데이터 소스를 분리하지 않은 환경에서 굳이 <code>jdbcTemplate</code> 을 이용할 필요는 없어보이긴 하는데 일단 그냥 구현해봤다.</p>
<h4 id="namedlockrepositoryjava">NamedLockRepository.java</h4>
<pre><code class="language-java">@Repository
@RequiredArgsConstructor
public class NamedLockRepository {

    private final NamedParameterJdbcTemplate jdbcTemplate;

    public void getLock(String key) {
        MapSqlParameterSource params = new MapSqlParameterSource().addValue(&quot;key&quot;, key);

        String sql = &quot;SELECT GET_LOCK(:key , 3000)&quot;;

        Integer result = jdbcTemplate.queryForObject(sql, params, Integer.class);

        checkResult(result);
    }

    public void releaseLock(String key) {
        MapSqlParameterSource params = new MapSqlParameterSource().addValue(&quot;key&quot;, key);

        String sql = &quot;SELECT RELEASE_LOCK(:key)&quot;;

        Integer result = jdbcTemplate.queryForObject(sql, params, Integer.class);

        checkResult(result);
    }

    private void checkResult(Integer result) {
        if (result != 1) {
            throw new RuntimeException(&quot;LOCK 을 수행하는 중에 오류가 발생하였습니다.&quot;);
        }
    }
}</code></pre>
<ol>
<li><p><code>SELECT GET_LOCK(:key, 3000)</code> SQL 문을 실행하여 특정 키에 대한 락을 시도한다. 여기서 3000은 락 획득 시도를 위해 대기할 최대 시간(밀리초)을 나타낸다.</p>
</li>
<li><p><code>SELECT RELEASE_LOCK(:key)</code> SQL 문을 실행하여 특정 키에 대한 락을 해제한다.</p>
</li>
<li><p><code>jdbcTemplate.queryForObject</code> 를 사용하여 실행하고 결과를 Integer로 받는다. </p>
</li>
<li><p><code>checkResult</code> 를 통해 락 해제 성공 여부를 확인하며, 락 획득 및 해제 성공 시 결과는 1 이다.</p>
</li>
</ol>
<h4 id="namedlockfacadejava">NamedLockFacade.java</h4>
<pre><code class="language-java">@Component
@Transactional
@RequiredArgsConstructor
public class NamedLockFacade {

    private final CouponService couponService;
    private final NamedLockRepository namedLockRepository;

    public void issueCoupon(CouponIssueParam param, User user) {
        try {
            // 쿠폰 ID를 이용해 락을 획득
            namedLockRepository.getLock(param.getCouponId().toString());
            couponService.issueCoupon(param, user); // 비즈니스 로직 수행
        } finally {
            // 락 해제
            namedLockRepository.releaseLock(param.getCouponId().toString());
        }
    }
}</code></pre>
<p><code>네임드 락</code> 의 습득과 해제는 동일한 세션에서 이루어져야 한다.</p>
<p>이에 동일한 세션을 유지하고자 <code>NamedLockFacade</code> 에 <code>@Transactional</code> 어노테이션을 붙였다.</p>
<p>또한 쿠폰의 Id 를 이용해 <code>네임드 락</code> 의 Key 를 설정해주었으며, 이를 통해 여러 트랜잭션의 <code>네임드 락</code> 에 대한 접근을 제어할 수 있다.</p>
<h4 id="couponservicejava-1">CouponService.java</h4>
<pre><code class="language-java">   @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void issueCoupon(CouponIssueParam param, User user) {
        // 쿠폰 조회
        Coupon coupon = getCoupon(param.getCouponId());

        // 쿠폰 발급
        UserCoupon userCoupon = UserCoupon.CreateUserCoupon(coupon, user);

        userCouponQueryService.saveUserCoupon(userCoupon);
    }</code></pre>
<p>쿠폰을 발급하는 비즈니스 로직에는 <code>@Transactional(propagation = Propagation.REQUIRES_NEW)</code> 어노테이션을 설정하였다. </p>
<p>이처럼 부모 메서드와 자식 메서드 간의 트랜잭션은 각각 독립적으로 수행되어야 한다.</p>
<p>만약에 이 둘을 독립적으로 수행하지 않는다면 어떻게 될까 ?</p>
<p>만약에 <code>Thread 1</code> 과 <code>Thread 2</code> 가 있고 재고가 100이라고 해보자.</p>
<ol>
<li><code>Thread 1</code> 트랜잭션 시작 (총 재고 100)</li>
<li><code>Thread 1 네임드 락 획득 (get lock)</code> 후 쿠폰의 재고 1 감소</li>
<li><code>Thread 1 네임드 락 해제 (release lock)</code> -&gt; 아직 데이터 <code>commit</code> 전</li>
<li><code>Thread 2</code> 트랜잭션 시작 및 <code>네임드 락 획득 (get lock)</code> -&gt; 재고가 감소되기 전 데이터를 <code>SELECT</code> (총 재고 100)</li>
<li><code>Thread 1</code> 의 재고 감소 1 데이터 <code>commit</code> (총 재고 99)</li>
<li><code>Thread 2 네임드 락 해제 (release lock)</code> 및 <code>commit</code> (총 재고 99)</li>
</ol>
<p>이와 같이 트랜잭션을 서로 분리해주지 않는다면 <code>갱신 손실(Lost Update)</code> 문제가 발생할 수 있는 것이다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/045aef73-3f41-4a2d-a24f-803ac8d15670/image.png" alt=""></p>
<p>이는 앞서 설명한 시나리오를 도식화한 것이다.</p>
<p>다시 말해서 <code>release lock</code> 이 호출되어 락이 해제된 후, 다른 트랜잭션이 해당 락을 즉시 획득할 수 있는 상황이 발생할 수 있다. 만약 이 때 아직 <code>첫 번째 트랜잭션</code> 이 데이터베이스에 완전히 <code>커밋</code> 되지 않았다면, <code>새로운 트랜잭션</code> 은 아직 완전히 확정되지 않은 변경사항에 접근하게 될 가능성이 있는 것이다.</p>
<h3 id="테스트-2">테스트</h3>
<h4 id="couponconcurrencytestjava-2">CouponConcurrencyTest.java</h4>
<pre><code class="language-java">    @Test
    @DisplayName(&quot;쿠폰 여러 명 발급&quot;)
    void 쿠폰_여러_명_발급() throws InterruptedException {
        int threadCount = 1000;
        ExecutorService executorService = Executors.newFixedThreadPool(32);
        CountDownLatch latch = new CountDownLatch(threadCount);

        for (int i = 0; i &lt; threadCount; i++) {
            final int threadNumber = i + 1;
            int key = i;
            executorService.submit(() -&gt; {
                try {
                    namedLockFacade.issueCoupon(param, users.get(key));
                    System.out.println(&quot;Thread &quot; + threadNumber + &quot; - 성공&quot;);

                } catch (PessimisticLockingFailureException e) {
                    System.out.println(&quot;Thread &quot; + threadNumber + &quot; - 락 충돌 감지&quot;);

                } catch (Exception e) {
                    System.out.println(&quot;Thread &quot; + threadNumber + &quot; - &quot; + e.getMessage());

                } finally {
                    latch.countDown();
                }
            });
        }

        latch.await();
        executorService.shutdown();

        Long count = userCouponRepository.countByCouponId(param.getCouponId());

        assertThat(count).isEqualTo(100);
    }</code></pre>
<p>앞선 테스트 코드와 크게 차이는 없고, <code>issueCoupon</code> 메서드를 <code>namedLockFacade</code> 에서 호출했다는 것만 달라졌다.
<img src="https://velog.velcdn.com/images/sepang-pang/post/ec90d1a9-c50e-460b-a5cf-b7ef8064bbd9/image.png" alt=""></p>
<p>수행시간은 <code>4sec 521ms</code> 정도 측정됐으며, 이는 <code>비관적 락</code> 에 비해 약 <code>5.00%</code> 더 빠르고, <code>낙관적 락</code> 에 비해는 약 <code>31.71%</code> 더 빠른 것으로 확인된다.</p>
<h4 id="query-log-2">Query Log</h4>
<pre><code class="language-java">...

2024-05-21T23:10:36.078+09:00 DEBUG 16963 --- [Issue-Coupon-Service] [ool-2-thread-17] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [SELECT GET_LOCK(? , 3000)]
Hibernate: 
    select
        c1_0.id,
        c1_0.coupon_image,
        c1_0.coupon_name,
        c1_0.created_at,
        c1_0.expired_at,
        c1_0.is_deleted,
        c1_0.modified_at,
        c1_0.remain_quantity,
        c1_0.stock_status,
        c1_0.total_quantity 
    from
        coupon c1_0 
    where
        c1_0.id=? 
        and c1_0.is_deleted=0
Hibernate: 
    insert 
    into
        user_coupon
        (coupon_id, user_id, id) 
    values
        (?, ?, ?)
Hibernate: 
    update
        coupon 
    set
        coupon_image=?,
        coupon_name=?,
        expired_at=?,
        is_deleted=?,
        modified_at=?,
        remain_quantity=?,
        stock_status=?,
        total_quantity=? 
    where
        id=?
2024-05-21T23:10:36.081+09:00 DEBUG 16963 --- [Issue-Coupon-Service] [ool-2-thread-10] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL query
2024-05-21T23:10:36.081+09:00 DEBUG 16963 --- [Issue-Coupon-Service] [ool-2-thread-10] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [SELECT RELEASE_LOCK(?)]
Thread 56 - 성공

...</code></pre>
<p><code>네임드 락 획득 (get lock)</code> 후 비즈니스 로직이 수행되고 <code>UPDATE</code> 까지 완료 후에 <code>네임드 락 해제 (release lock)</code> 가 수행되는 것을 확인할 수 있다.</p>
<p>그렇다면 만약에 앞서 언급했던 부모 메서드와 자식 메서드 간 트랜잭션 분리를 하지 않는다면 어떻게 될까.</p>
<pre><code class="language-java">...

2024-05-21T23:19:20.573+09:00 DEBUG 17081 --- [Issue-Coupon-Service] [pool-2-thread-3] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [SELECT GET_LOCK(? , 3000)]
2024-05-21T23:19:20.574+09:00 DEBUG 17081 --- [Issue-Coupon-Service] [ool-2-thread-18] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL query
2024-05-21T23:19:20.574+09:00 DEBUG 17081 --- [Issue-Coupon-Service] [ool-2-thread-18] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [SELECT GET_LOCK(? , 3000)]
2024-05-21T23:19:20.574+09:00 DEBUG 17081 --- [Issue-Coupon-Service] [pool-2-thread-4] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL query
2024-05-21T23:19:20.574+09:00 DEBUG 17081 --- [Issue-Coupon-Service] [pool-2-thread-4] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [SELECT RELEASE_LOCK(?)]
2024-05-21T23:19:20.573+09:00  WARN 17081 --- [Issue-Coupon-Service] [ool-2-thread-11] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 1213, SQLState: 40001
2024-05-21T23:19:20.575+09:00 ERROR 17081 --- [Issue-Coupon-Service] [ool-2-thread-11] o.h.engine.jdbc.spi.SqlExceptionHelper   : Deadlock found when trying to get lock; try restarting transaction
Hibernate: 
    insert 
    into
        user_coupon
        (coupon_id, user_id, id) 
    values
        (?, ?, ?)
2024-05-21T23:19:20.573+09:00  WARN 17081 --- [Issue-Coupon-Service] [ool-2-thread-21] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 1213, SQLState: 40001
Hibernate: 
    select
        c1_0.id,
        c1_0.coupon_image,
        c1_0.coupon_name,
        c1_0.created_at,
        c1_0.expired_at,
        c1_0.is_deleted,
        c1_0.modified_at,
        c1_0.remain_quantity,
        c1_0.stock_status,
        c1_0.total_quantity 
    from
        coupon c1_0 
    where
        c1_0.id=? 
        and c1_0.is_deleted=0
2024-05-21T23:19:20.575+09:00 ERROR 17081 --- [Issue-Coupon-Service] [ool-2-thread-21] o.h.engine.jdbc.spi.SqlExceptionHelper   : Deadlock found when trying to get lock; try restarting transaction
2024-05-21T23:19:20.573+09:00  WARN 17081 --- [Issue-Coupon-Service] [ool-2-thread-30] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 1213, SQLState: 40001
2024-05-21T23:19:20.576+09:00 ERROR 17081 --- [Issue-Coupon-Service] [ool-2-thread-30] o.h.engine.jdbc.spi.SqlExceptionHelper   : Deadlock found when trying to get lock; try restarting transaction

...
</code></pre>
<p><code>thread-3</code> 과 <code>thread-18</code> 이 함께 <code>네임드 락 획득 (get lock)</code>  을 시도하고 있으며, <code>thread-4</code> 가 <code>네임드 락 해제 (release lock)</code> 를 수행하고 있는 것이 확인되고, <code>Dead Lock</code> 문제도 발생하는 것을 볼 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/9c6dcf3c-cc27-4b15-a6bc-d8a665814194/image.png" alt=""></p>
<p>테스트는 당연히 실패했으며, 예상된 값 보다 훨씬 많은 쿠폰이 발급된 것을 확인할 수 있다.</p>
<p>이처럼 <code>네임드 락</code> 을 이용할 때 트랜잭션을 분리하지 않는다면, 데이터 정합성에 문제를 야기할 수 있다는 사실을 직접 확인할 수 있었다.</p>
<h1 id="redisson">Redisson</h1>
<h3 id="정의-3">정의</h3>
<blockquote>
<p>자원을 구독하고 있으면, 내가 다 쓰고 나서 알려줄게 ~ 그때 자원을 사용하도록해 !</p>
</blockquote>
<p><code>Redisson</code>은 Redis의 <code>PUB/SUB</code> 기능을 활용하여 <code>Lock</code> 의 상태 변화를 실시간으로 구독하는 것이 가능하게 한다. </p>
<p>이를 통해, <code>Lock</code> 을 획득하려는 다른 프로세스나 스레드가 <code>Lock</code> 의 상태 변경을 신속하게 인지하고, <code>Lock</code> 이 해제되는 순간 즉시 반응할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/f5d54a56-dca5-44e5-a098-95fd4d6e1c95/image.png" alt=""></p>
<ol>
<li>각 <code>Thread</code> 가 <code>Lock</code> 획득을 시도한다.</li>
<li><code>Lock</code> 을 먼저 점유한 <code>Thread</code> 는 작업을 수행한다.</li>
<li><code>Lock</code> 을 점유하지 못한 <code>Thread</code> 는 <code>Lock</code> 의 <code>Key</code> 를 이름으로 가지는 <code>채널</code> 을 구독한다.</li>
<li>TX 1 의 작업이 끝나면 채널이 <code>Lock</code> 해제 알림을 발행한다.</li>
<li>이를 구독 중인 TX 2 는 <code>Lock</code> 점유를 시도하고, 획득 후 작업을 수행한다.</li>
</ol>
<p>이처럼 <code>Redisson</code> 은 <code>PUB/SUB</code> 기능을 이용해 동시성 문제를 제어하는 것을 볼 수 있다.</p>
<h3 id="구현-3">구현</h3>
<h4 id="redissonlockfacadejava">RedissonLockFacade.java</h4>
<pre><code class="language-java">@Component
@Slf4j(topic = &quot;RedissonLockFacade&quot;)
@RequiredArgsConstructor
public class RedissonLockFacade {

    private final RedissonClient redissonClient;

    private final CouponService couponService;

    public void executeLock(CouponIssueParam param, User user) {
        RLock lock = redissonClient.getLock(param.getCouponId().toString());

        try {
            boolean available = lock.tryLock(10, 1, TimeUnit.SECONDS);

            if (!available) {
                log.info(&quot;Lock 획득 실패&quot;);
                return;
            }

            couponService.issueCoupon(param, user);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }
}</code></pre>
<p><code>Lcok</code> 을 생성하고 점유를 시도하는 로직을 위해 <code>Facade</code> 클래스를 생성한다.</p>
<ol>
<li><code>getLock()</code> 을 통해 쿠폰의 Id 를 <code>Key</code> 로 가지는 <code>Lock</code> 을 생성한다.</li>
<li><code>tryLock()</code> 를 통해 <code>Lock</code> 의 점유를 시도한다.</li>
<li>이때 파라미터 값으로 <code>대기시간</code> <code>점유시간</code> 을 전달한다.</li>
<li>코드에 작성된 내용을 기준으로 <code>Lock</code> 을 점유하기 위해 <code>10초</code> 기다릴 거고, 점유하면 <code>1초</code> 만에 끝내겠다는 의미다.</li>
<li>이후 비즈니스 로직을 수행하고 최종적으로 <code>Lock</code> 을 해제한다.</li>
</ol>
<h3 id="테스트-3">테스트</h3>
<h4 id="couponconcurrencytestjava-3">CouponConcurrencyTest.java</h4>
<pre><code class="language-java">@Test
    @DisplayName(&quot;쿠폰 여러 명 발급&quot;)
    void 쿠폰_여러_명_발급() throws InterruptedException {
        int threadCount = 1000;
        ExecutorService executorService = Executors.newFixedThreadPool(32);
        CountDownLatch latch = new CountDownLatch(threadCount);

        for (int i = 0; i &lt; threadCount; i++) {
            final int threadNumber = i + 1;
            int key = i;
            executorService.submit(() -&gt; {
                try {
                    redissonLockFacade.executeLock(param, users.get(key));
                    System.out.println(&quot;Thread &quot; + threadNumber + &quot; - 성공&quot;);

                } catch (PessimisticLockingFailureException e) {
                    System.out.println(&quot;Thread &quot; + threadNumber + &quot; - 락 충돌 감지&quot;);

                } catch (Exception e) {
                    System.out.println(&quot;Thread &quot; + threadNumber + &quot; - &quot; + e.getMessage());

                } finally {
                    latch.countDown();
                }
            });
        }

        latch.await();
        executorService.shutdown();

        Long count = userCouponRepository.countByCouponId(param.getCouponId());

        assertThat(count).isEqualTo(100);
    }</code></pre>
<p><code>redissonLockFacade</code> 를 주입받고, <code>executeLock</code> 을 호출한다.
쿠폰은 100 개가 발급되어야 할 것이다.</p>
<p><img src="https://velog.velcdn.com/images/sepang-pang/post/0771ca8f-15ee-467e-92af-11a614c88ac4/image.png" alt=""></p>
<p>테스트가 무사히 통과됐다 !</p>
<p>비관적 락에 비해 약 <code>27.26%</code>, 네임드 락에 비해 약 <code>33.94%</code> 느리며 낙관적 락에 비해 약 <code>8.53%</code> 빠른 것을 확인할 수 있었다.</p>
<h1 id="정리">정리</h1>
<p>지금까지 동시성 제어를 위한 다양한 <code>Lock</code> 전략을 소개하고 각각의 특징과 적용 시나리오를 심층적으로 다뤄봤다. </p>
<p>선택할 수 있는 <code>Lock</code> 전략은 많지만, 각각의 방식은 특정 상황에서 더 효과적일 수 있으므로, 서비스의 특성과 요구사항에 맞게 적절한 전략을 선택하는 것이 중요한 거 같다.</p>
<blockquote>
<p><strong>비관적 락</strong>
활용도: 데이터 충돌이 빈번하게 예상되는 환경에서 유용함
장점: 충돌을 미리 방지하여 데이터 일관성을 확보.
단점: 락으로 인해 발생하는 대기 시간이 시스템의 응답성을 저하시킬 수 있.</p>
</blockquote>
<blockquote>
<p><strong>낙관적 락</strong>
활용도: 충돌이 드물게 발생하는 환경에서 성능 향상을 기대할 수 있음.
장점: 락을 걸지 않기 때문에 시스템의 응답성이 비교적 높음.
단점: 충돌 발생 시 재시도 비용이 발생하며, 재시도 로직을 관리해야 함.</p>
</blockquote>
<blockquote>
<p><strong>네임드 락</strong>
활용도: 데이터베이스 내에서 자원을 순차적으로 접근해야 할 때 유용함.
장점: MySQL 같은 데이터베이스의 기능을 사용하여 간단하게 구현할 수 있음.
단점: MySQL 환경에서만 구축 가능하며, 데이터 소스를 분리하지 않으면 커넥션 풀에 부담을 줄 수 있음.</p>
</blockquote>
<blockquote>
<p><strong>Redisson</strong>
활용도: 분산 시스템 환경에서 여러 인스턴스가 공통 자원에 접근할 때 사용.
장점: Redis의 PUB/SUB 기능을 활용하여 락의 상태 변경을 실시간으로 감지하고 반응함.
단점: Redis 환경이 구축되어 있어야 하며, 외부 시스템에 의존하게 됨.</p>
</blockquote>
<p>각각의 <code>Lock</code> 전략을 선택할 때는 환경, 요구 사항, 사용 가능한 리소스를 고려하여 가장 적합한 방법을 선택하는 것이 중요할 것이다.</p>
]]></description>
        </item>
    </channel>
</rss>