<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>과정을 즐기자</title>
    <link>https://320hwany.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sun, 9 Aug 2026 13:13:59 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>320Hwany</managingEditor>
    <item>
      <title>객체 지향 SOLID 원칙중 D (DIP)에 대해 알아보자</title>
      <link>https://320hwany.tistory.com/129</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;객체 지향 언어를 사용하다보면 SOLID 원칙에 대해 많이 들어보셨을 겁니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SOLID는 유지보수성과 확장성이 높은 코드를 작성하기 위한 다섯 가지 설계 원칙을 의미합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;221&quot; data-start=&quot;200&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그 다섯 가지 원칙은 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;--single-responsibility-principle단일-책임-원칙&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. Single Responsibility Principle - 단일 책임 원칙&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;--open-closed-principle개방-폐쇄-원칙&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. Open-Closed Principle - 개방-폐쇄 원칙&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;--liskov-substitution-principle리스코프-치환-원칙&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. Liskov Substitution Principle - 리스코프 치환 원칙&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;--interface-segregation-principle인터페이스-분리-원칙&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4. Interface Segregation Principle - 인터페이스 분리 원칙&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;--dependency-inversion-principle의존성-역전-원칙&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;5. Dependency Inversion Principle - 의존성 역전 원칙&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번 글에서는 5번째 원칙인 DIP 에 대해 집중적으로 알아보겠습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  DIP의 정의를 알아보자&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;먼저 DIP는 Dependency Inversion Principle의 약자입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;236&quot; data-start=&quot;146&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;비슷한 약자로 DI(Dependency Injection) 가 있는데 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 두 가지는 이름이 비슷해 혼동하기 쉽지만 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;완전히 다른 개념&lt;/b&gt;&lt;/span&gt;입니다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;236&quot; data-start=&quot;146&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DIP의 I는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Inversion(역전)&lt;/b&gt;&lt;/span&gt; 으로 의존 관계의 방향을 역전시키는 원칙을 의미합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;236&quot; data-start=&quot;146&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DI의 I는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Injection(주입)&lt;/b&gt;&lt;/span&gt; 으로 의존 대상을 외부에서 주입받는 행위를 의미합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;의존성 역전 원칙이라고만 하면 잘 와닿지 않으니 자세한 설명을 살펴보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;상위 수준 모듈(High-level Module)은 하위 수준 모듈(Low-level Module)에 의존해서는 안 된다.&lt;br /&gt;두 모듈 모두 추상화(Abstraction)에 의존해야 한다. 또한 추상화는 세부 사항(Details)에 의존해서는 안 되며,&lt;br /&gt;세부 사항이 추상화에 의존해야 한다.&amp;rdquo;&lt;/b&gt;&lt;br /&gt;&amp;mdash; Robert C. Martin&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;요약하면 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;추상화에 의존해야하지 구체화에 의존하면 안된다는 것&lt;/span&gt;&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번 글에서는 해당 설명의 의미와 역전이라는 단어가 왜 등장했는지를 코드와 그림을 통해 살펴보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  DIP 예제로 직접 살펴보자&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단순한 레이어드 아키텍쳐를 생각해보겠습니다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Service 에서 Repository 를 사용하는 구조는 아래 그림과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-09 오전 10.48.37.png&quot; data-origin-width=&quot;1138&quot; data-origin-height=&quot;676&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dXnHCy/btsQ48yTDsV/TtFCwcvxXhwBxNkEX7bGS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dXnHCy/btsQ48yTDsV/TtFCwcvxXhwBxNkEX7bGS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dXnHCy/btsQ48yTDsV/TtFCwcvxXhwBxNkEX7bGS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdXnHCy%2FbtsQ48yTDsV%2FTtFCwcvxXhwBxNkEX7bGS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;327&quot; data-filename=&quot;스크린샷 2025-10-09 오전 10.48.37.png&quot; data-origin-width=&quot;1138&quot; data-origin-height=&quot;676&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;golang 코드로도 살펴보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;application layer&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;go&quot; data-ke-language=&quot;go&quot;&gt;&lt;code&gt;package application

import (
    &quot;go-dip-principle/persistence&quot;
)

type MemberService struct {
    MemberRepository persistence.MemberRepository
}

func NewMemberService(memberRepository persistence.MemberRepository) *MemberService {
    return &amp;amp;MemberService{
       MemberRepository: memberRepository,
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;persistence layer&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;go&quot; data-ke-language=&quot;go&quot;&gt;&lt;code&gt;package persistence

import (
    &quot;gorm.io/gorm&quot;
)

type MemberRepository struct {
    db *gorm.DB
}

func NewMemberRepository(db *gorm.DB) *MemberRepository {
    return &amp;amp;MemberRepository{db: db}
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;import 부분을 살펴보면 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;상위 수준 모듈인 application layer 에서 하위 수준 모듈인 persistence layer 에 의존&lt;/span&gt;&lt;/b&gt;하고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또한 여기서 MemberRepository는 추상화가 아닌 구체화된 구현체로 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;상위 모듈이 추상화에 의존하고 있지도 않습니다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이제 해당 코드를 DIP 를 만족할 수 있도록 수정해보겠습니다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;수정된 그림은 아래와 같습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;MemberRepository 를 인터페이스로 만들고 application layer 로 올렸으며 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;MemberService는 MemberRepository 에&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 의존하고 MemberRepository 의 구현체를 persistence layer에 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;생성하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-09 오전 10.59.16.png&quot; data-origin-width=&quot;1812&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BadQa/btsQ5SvCaTA/nMmJ7pjctH6hSRcRP3xJx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BadQa/btsQ5SvCaTA/nMmJ7pjctH6hSRcRP3xJx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BadQa/btsQ5SvCaTA/nMmJ7pjctH6hSRcRP3xJx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBadQa%2FbtsQ5SvCaTA%2FnMmJ7pjctH6hSRcRP3xJx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;253&quot; data-filename=&quot;스크린샷 2025-10-09 오전 10.59.16.png&quot; data-origin-width=&quot;1812&quot; data-origin-height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;application layer&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;go&quot; data-ke-language=&quot;go&quot;&gt;&lt;code&gt;package application

type MemberService struct {
    MemberRepository MemberRepository
}

func NewMemberService(memberRepository MemberRepository) *MemberService {
    return &amp;amp;MemberService{
       MemberRepository: memberRepository,
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;go&quot; data-ke-language=&quot;go&quot;&gt;&lt;code&gt;package application

import (
    &quot;go-dip-principle/domain&quot;
)

type MemberRepository interface {
    Save(member *domain.Member) error
    GetById(memberId int) (*domain.Member, error)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;persistence layer&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;go&quot; data-ke-language=&quot;go&quot;&gt;&lt;code&gt;package persistence

import (
    &quot;go-dip-principle/application&quot;
    &quot;go-dip-principle/domain&quot;
    &quot;gorm.io/gorm&quot;
)

// 컴파일 타임 인터페이스 컴플라이언스 검증
var _ application.MemberRepository = (*MemberRepositoryImpl)(nil)

type MemberRepositoryImpl struct {
    db *gorm.DB
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;수정된 코드의 import 부분을 다시 살펴보면 application layer 에서 persistence layer 에 의존하지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또한 persistence layer 에서 application layer 에 의존하지만 추상화된 상위 모듈에 의존합니다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  역전(Inversion) 이라는 단어를 사용하는 이유&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 예제 코드가 SOLID 에서 말하는 DIP 를 적용했다고 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그렇다면 왜 해당 구조로 변경하는 원칙에서 Inversion 이라는 단어를 사용하는 것일까요?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이전 그림을 다시 한번 살펴보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Before&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-09 오전 10.48.37.png&quot; data-origin-width=&quot;1138&quot; data-origin-height=&quot;676&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dXnHCy/btsQ48yTDsV/TtFCwcvxXhwBxNkEX7bGS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dXnHCy/btsQ48yTDsV/TtFCwcvxXhwBxNkEX7bGS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dXnHCy/btsQ48yTDsV/TtFCwcvxXhwBxNkEX7bGS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdXnHCy%2FbtsQ48yTDsV%2FTtFCwcvxXhwBxNkEX7bGS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;450&quot; height=&quot;267&quot; data-filename=&quot;스크린샷 2025-10-09 오전 10.48.37.png&quot; data-origin-width=&quot;1138&quot; data-origin-height=&quot;676&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;After&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-10-09 오전 10.59.16.png&quot; data-origin-width=&quot;1812&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BadQa/btsQ5SvCaTA/nMmJ7pjctH6hSRcRP3xJx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BadQa/btsQ5SvCaTA/nMmJ7pjctH6hSRcRP3xJx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BadQa/btsQ5SvCaTA/nMmJ7pjctH6hSRcRP3xJx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBadQa%2FbtsQ5SvCaTA%2FnMmJ7pjctH6hSRcRP3xJx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;217&quot; data-filename=&quot;스크린샷 2025-10-09 오전 10.59.16.png&quot; data-origin-width=&quot;1812&quot; data-origin-height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Before의 MemberRepository와 After의 MemberRepositoryImpl은 동일한 코드/역할을 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;의존성 방향을 보면 들어오는 방향에서 나가는 방향으로 변경&lt;/span&gt;&lt;/b&gt;되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그래서 의존성 역전 법칙(&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Dependency Inversion Principle)&lt;/span&gt;이라고 부릅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  의존성 역전을 사용하는 이유&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그렇다면 상위 모듈이 추상화에 의존하도록 하는 이유는 뭘까요?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;구체화에 의존하게 되면 내부 구현이 바뀌었을 때 상위 모듈에도 영향&lt;/span&gt;&lt;/b&gt;을 주게됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;go&quot; data-ke-language=&quot;go&quot;&gt;&lt;code&gt;package persistence

import (
	&quot;go-dip-principle/application&quot;
	&quot;gorm.io/gorm&quot;
)

// 컴파일 타임 인터페이스 컴플라이언스 검증
var _ application.MemberRepository = (*MemberRepositoryImpl)(nil)

type MemberRepositoryImpl struct {
	db *gorm.DB
}

func NewMemberRepository(db *gorm.DB) *MemberRepositoryImpl {
	return &amp;amp;MemberRepositoryImpl{db: db}
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 코드에서 gorm 이 아닌 다른 방식을 사용, RDB가 아닌 NoSQL 사용, RDB가 아닌 캐시 사용 등등..&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여러가지 이유로 Repository 구현체는 얼마든지 변경될 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 위와 같이 의존성 역전을 한다면 application layer 에 영향을 주지 않고 구현체를 변경할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;복잡한 문제를 해결하는 실무의 코드에서 요구 사항의 변경으로 인한 코드 변경을 최소화 할 수 있는 중요한 방법이라고 생각합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또한 이러한 구조는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;테스트하기 쉬운 코드&lt;/b&gt;&lt;/span&gt;를 만드는 데 도움이 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;239&quot; data-start=&quot;96&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예를 들어 MemberService를 테스트하려고 할 때 실제 MemberRepository 구현체가 외부 시스템과의 복잡한 연동을&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;239&quot; data-start=&quot;96&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 포함하고 있다고 가정해보겠습니다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 경우 MemberService 역시 그 영향을 받아 테스트가 어려워집니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;358&quot; data-start=&quot;241&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 MemberRepository의 테스트용 구현체를 따로 만든다면 외부 연동에 의존하지 않고도 MemberService의 로직을&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;358&quot; data-start=&quot;241&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 독립적으로 검증할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;// 테스트를 위한 구현체
type FakeMemberRepositoryImpl struct {
}

func NewFakeMemberRepositoryImpl() *FakeMemberRepositoryImpl {
    return &amp;amp;FakeMemberRepositoryImpl{}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;</description>
      <category>아키텍쳐</category>
      <author>320Hwany</author>
      <guid isPermaLink="true">https://320hwany.tistory.com/129</guid>
      <comments>https://320hwany.tistory.com/129#entry129comment</comments>
      <pubDate>Fri, 29 Aug 2025 23:10:03 +0900</pubDate>
    </item>
    <item>
      <title>트랜잭션 아웃 박스 패턴 알아보기</title>
      <link>https://320hwany.tistory.com/128</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번 글에서는 트랜잭션 아웃 박스 패턴에 대해 알아보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;트랜잭션&amp;nbsp;아웃박스&amp;nbsp;패턴은&amp;nbsp;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;로컬&amp;nbsp;트랜잭션&amp;nbsp;내에서&amp;nbsp;비즈니스&amp;nbsp;데이터와&amp;nbsp;이벤트를&amp;nbsp;함께&amp;nbsp;저장&lt;/span&gt;&lt;/b&gt;하여&amp;nbsp;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;분산&amp;nbsp;시스템에서의&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;데이터&amp;nbsp;일관성을&amp;nbsp;보장&lt;/span&gt;&lt;/b&gt;합니다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;라고만.. 설명하면 붕 뜨는 느낌이 있으니 필요한 배경부터 예제 코드까지 한 번 살펴보도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  마이크로서비스 아키텍쳐(MSA) 에서 발생할 수 있는 문제&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;트랜잭션 아웃박스 패턴이 필요한 배경은 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;MSA&amp;nbsp;환경에서의 데이터 일관성과 메시지 송신의 원자성 보장 문제&lt;/span&gt;&lt;/b&gt;에서 출발합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;문제 상황을 생각해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;주문 관련 로직, 결제 관련 로직 각각이 복잡하여 별도의 서버로 분리하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이때 주문을 생성하는 서버와 관련된 해당 주문에 대한 결제를 진행하는 서버가 다릅니다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이때 크게 아래 2가지 문제가 발생할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;1. 주문 DB에는 저장 성공, 메시지 브로커 전송 실패 (&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;이벤트 미전송 &amp;rarr;&lt;span&gt;&amp;nbsp;&lt;b&gt;결제 서버&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;가 상태를 모름)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;메시지 브로커에는 전송 성공, 주문 DB 저장 실패 (&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;잘못된 이벤트 발생 &amp;rarr;&lt;span&gt;&amp;nbsp;&lt;b&gt;결제 서버가&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&amp;nbsp;존재하지 않는 주문을 참조)&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위와 같은 문제를 막기위해 서로 다른 서버의 로직을 같은 하나의 트랜잭션으로 묶는다면 성능 문제가 발생할 수 있으며&lt;br /&gt;굳이 서버를 분리한 이유가 사라지게 됩니다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ChatGPT Image 2025년 8월 3일 오후 05_18_52.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vxTqO/btsPE97xg8p/9KKtyuppsBXMjQQqKBLMKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vxTqO/btsPE97xg8p/9KKtyuppsBXMjQQqKBLMKk/img.png&quot; data-alt=&quot;이런 방식이라면 MSA를 사용하는 이유가 없다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vxTqO/btsPE97xg8p/9KKtyuppsBXMjQQqKBLMKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvxTqO%2FbtsPE97xg8p%2F9KKtyuppsBXMjQQqKBLMKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;267&quot; data-filename=&quot;ChatGPT Image 2025년 8월 3일 오후 05_18_52.png&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이런 방식이라면 MSA를 사용하는 이유가 없다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  트랜잭션 아웃 박스 패턴 적용해보기&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위에서 발생할 수 있는 문제를 해결하기 위한 트랜잭션 아웃 박스 패턴 예제 코드를 작성해보겠습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(예제는 Golang으로 작성하였습니다)&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  주문 &amp;amp; 이벤트 생성&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1.&amp;nbsp; 클라이언트 요청&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;클라이언트가 주문 생성 요청을 보냅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;서버는 Controller 에서 요청을 받고 처리합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;go&quot; data-ke-language=&quot;go&quot;&gt;&lt;code&gt;func (c *OrderController) CreateOrder(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
       http.Error(w, &quot;Method not allowed&quot;, http.StatusMethodNotAllowed)
       return
    }

    var req dto.CreateOrderRequest
    if err := json.NewDecoder(r.Body).Decode(&amp;amp;req); err != nil {
       http.Error(w, &quot;Invalid request body&quot;, http.StatusBadRequest)
       return
    }

    if req.ProductName == &quot;&quot; || req.Quantity &amp;lt; 1 || req.Price &amp;lt; 0 {
       http.Error(w, &quot;Invalid request: product_name, quantity (min 1), and price (min 0) are required&quot;, http.StatusBadRequest)
       return
    }

    response, err := c.orderService.CreateOrder(&amp;amp;req)
    if err != nil {
       http.Error(w, &quot;Failed to create order&quot;, http.StatusInternalServerError)
       return
    }

    w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(response)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 트랜잭션 시작&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;OrderService에서 주문 생성에 대한 데이터베이스 트랜잭션을 시작합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;go&quot; data-ke-language=&quot;go&quot;&gt;&lt;code&gt;func (s *OrderService) CreateOrder(req *dto.CreateOrderRequest) (*dto.CreateOrderResponse, error) {
    var response *dto.CreateOrderResponse
    
    err := s.db.Transaction(func(tx *gorm.DB) error {
       order := &amp;amp;models.Order{
          ProductName: req.ProductName,
          Quantity:    req.Quantity,
          Price:       req.Price,
          Status:      OrderStatusPending,
       }
       
       if err := s.orderRepo.Create(tx, order); err != nil {
          return fmt.Errorf(&quot;failed to create order: %w&quot;, err)
       }

       if err := s.createOrderCreatedEvent(tx, order); err != nil {
          return fmt.Errorf(&quot;failed to create outbox event: %w&quot;, err)
       }

       response = &amp;amp;dto.CreateOrderResponse{
          ID:          order.ID,
          ProductName: order.ProductName,
          Quantity:    order.Quantity,
          Price:       order.Price,
          Status:      order.Status,
       }
       
       return nil
    })
    
    if err != nil {
       return nil, err
    }
    
    return response, nil
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. 주문 데이터 저장&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2번 로직 중에서 Order를 생성하는 로직이 있습니다.&lt;br /&gt;orderRepo는 gorm을 통해서 주문 테이블에 새로운 주문 정보를 INSERT합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;order := &amp;amp;models.Order{
    ProductName: req.ProductName,
    Quantity:    req.Quantity,
    Price:       req.Price,
    Status:      OrderStatusPending,
}

if err := s.orderRepo.Create(tx, order); err != nil {
    return fmt.Errorf(&quot;failed to create order: %w&quot;, err)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4. 아웃박스 이벤트 저장&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3번과 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;동일한 트랜잭션 내에서&lt;/span&gt; &lt;/b&gt;outbox_events 테이블에 이벤트를 INSERT합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이벤트는 pending 상태로 저장되어 나중에 처리될 준비를 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;go&quot; data-ke-language=&quot;go&quot;&gt;&lt;code&gt;if err := s.createOrderCreatedEvent(tx, order); err != nil {
    return fmt.Errorf(&quot;failed to create outbox event: %w&quot;, err)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;5. 트랜잭션 커밋&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모든 작업이 성공하면 트랜잭션을 커밋합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;주문과 이벤트가 동시에 저장되어 데이터 일관성이 보장&lt;/span&gt;&lt;/b&gt;됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  백그라운드 이벤트 발행&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;5초마다 한번씩 polling을 통해 OutboxProcessor가 동작합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;처리가 필요한 이벤트를 DB 에서 조회한 후&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 각 이벤트마다 아래 순서로 진행합니다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;Kafka&amp;nbsp;메시지&amp;nbsp;생성&amp;nbsp;(Key,&amp;nbsp;Value,&amp;nbsp;Headers)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;토픽으로&amp;nbsp;전송&amp;nbsp;(order-events)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;응답&amp;nbsp;대기&amp;nbsp;(offset&amp;nbsp;확인)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;- DB 상태 업데이트 (pending &amp;rarr; processed)&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실패 시 이벤트는 pending 상태 유지하고 다음 폴링에서 재시도할 수 있도록 합니다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;6. 아웃박스 폴링&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;백그라운드 프로세서 시작&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;func (p *OutboxProcessor) Start() {
    ticker := time.NewTicker(p.interval)
    defer ticker.Stop()

    log.Println(&quot;Outbox processor started&quot;)

    for {
       select {
       case &amp;lt;-ticker.C:
          p.processOutboxEvents()
       case &amp;lt;-p.stopCh:
          log.Println(&quot;Outbox processor stopped&quot;)
          return
       }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대기 중인 이벤트 조회&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;go&quot; data-ke-language=&quot;go&quot;&gt;&lt;code&gt;func (p *OutboxProcessor) processOutboxEvents() {
    events, err := p.outboxEventRepo.FindPendingEvents(p.batchSize)
    if err != nil {
       log.Printf(&quot;Error fetching outbox events: %v&quot;, err)
       return
    }

    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;7. Kafka 메세지 전송&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;go&quot; data-ke-language=&quot;go&quot;&gt;&lt;code&gt;func (p *OutboxProcessor) processEvent(event *models.OutboxEvent) error {
    log.Printf(&quot;Processing event: ID=%d, Type=%s, AggregateID=%s&quot;,
       event.ID, event.EventType, event.AggregateID)

    // Kafka로 이벤트 전송
    topic := p.kafkaTopic
    // 이벤트 타입에 따라 다른 토픽 사용 가능
    if strings.HasPrefix(event.EventType, &quot;Order&quot;) {
       topic = &quot;order-events&quot;
    }

    deliveryChan := make(chan kafka.Event, 1)
    err := p.kafkaProducer.Produce(&amp;amp;kafka.Message{
       TopicPartition: kafka.TopicPartition{
          Topic:     &amp;amp;topic,
          Partition: kafka.PartitionAny,
       },
       Key:   []byte(event.AggregateID),
       Value: []byte(event.Payload),
       Headers: []kafka.Header{
          {Key: &quot;event_id&quot;, Value: []byte(fmt.Sprintf(&quot;%d&quot;, event.ID))},
          {Key: &quot;event_type&quot;, Value: []byte(event.EventType)},
          {Key: &quot;timestamp&quot;, Value: []byte(event.CreatedAt.Format(time.RFC3339))},
       },
    }, deliveryChan)

    if err != nil {
       return fmt.Errorf(&quot;failed to produce message to Kafka: %w&quot;, err)
    }

    // 전송 결과 대기
    e := &amp;lt;-deliveryChan
    m := e.(*kafka.Message)

    if m.TopicPartition.Error != nil {
       return fmt.Errorf(&quot;delivery failed: %w&quot;, m.TopicPartition.Error)
    }

    log.Printf(&quot;Event delivered to topic %s [%d] at offset %v&quot;,
       *m.TopicPartition.Topic, m.TopicPartition.Partition, m.TopicPartition.Offset)

    // 성공적으로 전송되면 processed로 표시
    return p.outboxEventRepo.MarkAsProcessed(event)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  정리&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번 글에서는 MSA 환경에서 발생할 수 있는 데이터 일관성 문제를 해결하는 트랜잭션 아웃박스 패턴에 대해 알아보았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;핵심은 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;로컬 트랜잭션 내에서 비즈니스 데이터와 이벤트를 함께 저장&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하여 해당 이벤트를 이용하여 다른 서버와의 데이터 일관성을&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;유지하는 것입니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이외에도 데이터 일관성 유지를 위해 SAGA 패턴 (분산 트랜잭션 보상 처리), CDC (Change Data Capture, DB 로그 기반 변경 감지)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;등등이 존재합니다. 해당 내용에 대해서는 다음 글에서 정리해보도록 하겠습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Database</category>
      <author>320Hwany</author>
      <guid isPermaLink="true">https://320hwany.tistory.com/128</guid>
      <comments>https://320hwany.tistory.com/128#entry128comment</comments>
      <pubDate>Wed, 30 Jul 2025 21:49:02 +0900</pubDate>
    </item>
    <item>
      <title>DRF로 로그인 &amp;amp; CRUD 만들어보기</title>
      <link>https://320hwany.tistory.com/127</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번 글에서는 DRF (Django REST Framework)로 회원 로그인 및 간단한 CRUD를 만들어보려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;회사를 다니게 된 이후로 go, python을 사용하게 되었는데 python의 경우 잘 동작하던 프로젝트에서 개발을 했지&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;처음부터 만들어본 경험은 없는 것 같아서 글을 써보게 되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  요청의 흐름&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용자의 요청이 들어오면 urls.py 를 통해 매핑되는 view를 확인합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그 다음으로 views.py 에서 사용자의 요청/응답을 처리합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이때 비즈니스 관련 로직은 service를 통해 처리하고 service는 DB 관련 로직이 들어있는 repository를 참조합니다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;views -&amp;gt; service -&amp;gt; repository 순으로 참조를 하는 코드라고 봐주시면 될 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  코드 살펴보기&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;urls.py&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;urlpatterns = [
    path(&quot;members&quot;, MemberView.as_view(), name=&quot;member-view&quot;),
    path(&quot;login&quot;, MemberLoginView.as_view(), name=&quot;login&quot;),
]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;http://127.0.0.1:8000/crud-app/members&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;로 들어온 요청은 MemberView 와 매핑되고&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; text-align: left;&quot;&gt;http://127.0.0.1:8000/crud-app/login&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; text-align: left;&quot;&gt; 로 들어온 요청은 MemberLoginView와 매핑이 됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;views.py&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;class MemberView(APIView):

    def __init__(self, **kwargs: Any):
        super().__init__(**kwargs)
        self.member_service = container.member_service

    @handle_exception
    def post(self, request: Request) -&amp;gt; Response:
        member_create_request: MemberCreateRequest = MemberCreateRequest(**request.data)
        self.member_service.create_member(member_create_request)
        return Response(&quot;OK&quot;, status=201)

    @handle_exception
    @authentication
    def get(self, request: Request, member_id: int) -&amp;gt; Response:
        member_response: MemberResponse = self.member_service.get_member(member_id)
        return Response(asdict(member_response), status=200)

    @handle_exception
    @authentication
    def patch(self, request: Request, member_id: int) -&amp;gt; Response:
        member_update_request: MemberUpdateRequest = MemberUpdateRequest(**request.data)
        self.member_service.update_member(member_id, member_update_request)
        return Response(&quot;OK&quot;, status=200)

    @handle_exception
    @authentication
    def delete(self, request: Request, member_id: int) -&amp;gt; Response:
        self.member_service.delete_member(member_id)
        return Response(&quot;OK&quot;, status=200)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;MemberView는 APIView를 상속하여 생성하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;APIView는 DRF에서 제공하는 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;RESTful API를 구현하기 위한 뷰의 기본 동작&lt;/span&gt;&lt;/b&gt;(HTTP 메소드, Request/Response 객체)을 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;특이한 것은 메소드의 이름이 HTTP method와 매핑된다는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또한 예외에 대한 공통 처리를 위해 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;handle_exception 데코레이터&lt;/span&gt;&lt;/b&gt;를 이용하였습니다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;아래와 같이 내부 로직에서 예외가 발생하면 처리할 수 있도록 하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;def handle_exception(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except ValueError as e:
            return Response({&quot;message&quot;: str(e)}, status=400)
        except AuthenticationFailed as e:
            return Response({&quot;message&quot;: str(e)}, status=401)
        except Exception as e:
            logger.error(f&quot;An error occurred: {e}&quot;)
            return Response({&quot;message&quot;: &quot;An error occurred&quot;}, status=500)
    return wrapper&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;인증 관련 authentication 데코레이터&lt;/span&gt;&lt;/b&gt;도 만들었는데 jwt token을 바탕으로 회원 id 를 반환하는 로직입니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;def authentication(func):
    @wraps(func)
    def wrapper(self, request: Request, *args, **kwargs):
        secret_key: str = &quot;GZ8x8-Dl2FZAClDee7aOZ5rHnPDO1BtYK8wH6P5wf_k&quot;
        algorithm: str = &quot;HS256&quot;

        access_token: str = request.headers.get(&quot;AccessToken&quot;)
        refresh_token: str = request.headers.get(&quot;RefreshToken&quot;)

        try:
            access_payload = jwt.decode(access_token, secret_key, algorithms=[algorithm])
            member_id = access_payload.get(&quot;member_id&quot;)
        except jwt.ExpiredSignatureError:
            try:
                refresh_payload = jwt.decode(refresh_token, secret_key, algorithms=[algorithm])
                member_id = refresh_payload.get(&quot;member_id&quot;)
                member_response: MemberResponse = self.member_service.get_member(member_id)

                access_payload = {
                    &quot;member_id&quot;: member_response.member_id,
                    &quot;name&quot;: member_response.name,
                    &quot;email&quot;: member_response.email,
                    &quot;age&quot;: member_response.age,
                    &quot;exp&quot;: datetime.utcnow() + timedelta(hours=1)  # access_token 만료 시간 (1시간)
                }

                access_token: str = jwt.encode(access_payload, secret_key, algorithm=[algorithm])
                request.headers.__setattr__(&quot;AccessToken&quot;, access_token)

            except jwt.ExpiredSignatureError:
                raise AuthenticationFailed(&quot;Refresh token has expired&quot;)
            except jwt.InvalidTokenError:
                raise AuthenticationFailed(&quot;Invalid refresh token&quot;)
        except jwt.InvalidTokenError:
            raise AuthenticationFailed(&quot;Invalid access token&quot;)

        return func(self, request, member_id=member_id, *args, **kwargs)
    return wrapper&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;확실히 어떤 언어든 단순 반복되는 코드들을 줄이기 위한 방법은 존재하는 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;요청에 대한 예외 처리, 인증 관련 부분에서 Python 데코레이터를 이용하여 해결해보았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 또 주목해볼 점이 있다면 MemberView를 생성할 때 container를 통해 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;member_service를 주입 받도록 하였습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;ruby&quot;&gt;&lt;code&gt;def __init__(self, **kwargs: Any):
    super().__init__(**kwargs)
    self.member_service = container.member_service&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;depedency_container.py&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Java Spring 진영에서는 각 객체들을 스프링 컨테이너를 통해 의존 관계 주입을 해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 Python에서도 일부 관리해주는 프레임워크가 있는 것 같지만 이번에는 직접 관리를 해주도록 하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;중요한건 컴포넌트니 bean 주입과 같은 것이 아니고 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Stateless한 싱글톤으로 관리되는 객체에 대한 의존 관계 주입&lt;/span&gt;&lt;/b&gt;이라고 생각합니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;class MetaSingleton(type):
    _instances: dict = {}

    def __call__(cls, *args: Any, **kwargs: Any) -&amp;gt; object:
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

class DependencyContainer(metaclass=MetaSingleton):
    def __init__(self):
        self._member_repository = MemberRepository()
        self._member_service = MemberService(self._member_repository)

    @property
    def member_repository(self) -&amp;gt; MemberRepository:
        return self._member_repository

    @property
    def member_service(self) -&amp;gt; MemberService:
        return self._member_service

container = DependencyContainer()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;member_service.py&lt;/b&gt;&lt;/h4&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;ruby&quot;&gt;&lt;code&gt;class MemberService:

    def __init__(self, member_repository: MemberRepository):
        self.member_repository = member_repository

    def create_member(self, dto: MemberCreateRequest) -&amp;gt; None:
        self.member_repository.create_member(dto)

    def login(self, dto: MemberLoginRequest) -&amp;gt; JwtToken:
        return self.member_repository.login(dto)

    def get_member(self, member_id: int) -&amp;gt; MemberResponse:
        member: Member = self.member_repository.get_member(member_id)
        return MemberResponse.from_model(member)

    def update_member(self, member_id: int, dto: MemberUpdateRequest) -&amp;gt; None:
        self.member_repository.update_member(member_id, dto)

    def delete_member(self, member_id: int) -&amp;gt; None:
        self.member_repository.delete_member(member_id)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;member_repository.py&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Django ORM을 통해 직접 쿼리를 작성하지 않고 get, update_or_create, save, delete 를 통해&lt;br /&gt;간단한 CRUD 로직을 수행할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;class MemberRepository:

    @transaction.atomic
    def create_member(self, dto: MemberCreateRequest) -&amp;gt; Member:
        member = dto.to_model()
        member.save()
        return member

    @transaction.atomic
    def login(self, dto: MemberLoginRequest) -&amp;gt; JwtToken:
        try:
            member = Member.objects.get(email=dto.email, password=dto.password)

            access_payload = {
                &quot;member_id&quot;: member.member_id,
                &quot;name&quot;: member.name,
                &quot;email&quot;: member.email,
                &quot;age&quot;: member.age,
                &quot;exp&quot;: datetime.utcnow() + timedelta(hours=1)  # access_token 만료 시간 (1시간)
            }
            refresh_payload = {
                &quot;member_id&quot;: member.member_id,
                &quot;exp&quot;: datetime.utcnow() + timedelta(hours=24)  # refresh_token 만료 시간 (24시간)
            }
            secret_key: str = &quot;GZ8x8-Dl2FZAClDee7aOZ5rHnPDO1BtYK8wH6P5wf_k&quot;
            access_token: str = jwt.encode(access_payload, secret_key, algorithm=&quot;HS256&quot;)
            refresh_token: str = jwt.encode(refresh_payload, secret_key, algorithm=&quot;HS256&quot;)

            JwtRefreshToken.objects.update_or_create(
                member_id=member.member_id,
                defaults={&quot;refresh_token&quot;: refresh_token}
            )

            return JwtToken(access_token=access_token, refresh_token=refresh_token)

        except Member.DoesNotExist:
            raise ValueError(&quot;회원 정보가 존재하지 않습니다.&quot;)

    @transaction.atomic
    def get_member(self, member_id: int) -&amp;gt; Member:
        try :
            return Member.objects.get(member_id=member_id)
        except Member.DoesNotExist:
            raise ValueError(f&quot;회원 id가 {member_id}인 회원이 존재하지 않습니다.&quot;)

    @transaction.atomic
    def update_member(self, member_id: int, dto: MemberUpdateRequest) -&amp;gt; None:
        try:
            member = Member.objects.get(member_id=member_id)
            for field, value in asdict(dto).items():
                setattr(member, field, value)
            member.save()
        except Member.DoesNotExist:
            raise ValueError(f&quot;회원 id가 {member_id}인 회원이 존재하지 않아 수정할 수 없습니다.&quot;)

    @transaction.atomic
    def delete_member(self, member_id) -&amp;gt; None:
        try:
            member = Member.objects.get(member_id=member_id)
            member.delete()
        except Member.DoesNotExist:
            raise ValueError(f&quot;회원 id가 {member_id}인 회원이 존재하지 않아 삭제할 수 없습니다.&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;models.py&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;django.db 의 models 를 상속 받아 Member, Order, JwtRefreshToken을 생성해주었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이때 models는 DB 엔티티로 DB의 테이블과 매핑됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;class Member(models.Model):
    member_id: models.BigAutoField = models.BigAutoField(primary_key=True)
    name: models.CharField = models.CharField(max_length=100)
    email: models.EmailField = models.EmailField()
    password: models.CharField = models.CharField(max_length=100)
    age: models.IntegerField = models.IntegerField()

    created_at: models.DateTimeField = models.DateTimeField(auto_now_add=True)
    updated_at: models.DateTimeField = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'members'


class Order(models.Model):
    order_id: models.BigAutoField = models.BigAutoField(primary_key=True)
    member_id: models.BigIntegerField = models.BigIntegerField()
    price: models.IntegerField = models.IntegerField()

    created_at: models.DateTimeField = models.DateTimeField(auto_now_add=True)
    updated_at: models.DateTimeField = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'orders'


class JwtRefreshToken(models.Model):
    jwt_refresh_token_id: models.BigAutoField = models.BigAutoField(primary_key=True)
    member_id: models.BigIntegerField = models.BigIntegerField()
    refresh_token: models.CharField = models.CharField(max_length=500)
    created_at: models.DateTimeField = models.DateTimeField(auto_now_add=True)
    updated_at: models.DateTimeField = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'jwt_refresh_token'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  정리하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Java만 하던 대학생이 입사를 한 후 회사에서 Go, Python을 사용하게 되었는데&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;철학은 조금 다른 것 같기는 하지만 각 언어마다 부딪히는 문제는 비슷하고 각각의 해결책도 비슷한 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;특히 의존 관계 주입을 하는 방법, 공통 예외 처리, 인증/인가 관련 로직 등등..&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;코드 보러가기&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1746522953878&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - 320Hwany/django-crud-practice&quot; data-og-description=&quot;Contribute to 320Hwany/django-crud-practice development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/320Hwany/django-crud-practice&quot; data-og-url=&quot;https://github.com/320Hwany/django-crud-practice&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/J4z3H/hyYPfi888q/jtgkgZSO61vsoaA6Qf4ZL1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/b71tsk/hyYPrw7K5l/MxLhGGUQKGxKAF0zKKaMwK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/320Hwany/django-crud-practice&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/320Hwany/django-crud-practice&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/J4z3H/hyYPfi888q/jtgkgZSO61vsoaA6Qf4ZL1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/b71tsk/hyYPrw7K5l/MxLhGGUQKGxKAF0zKKaMwK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - 320Hwany/django-crud-practice&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to 320Hwany/django-crud-practice development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Python</category>
      <author>320Hwany</author>
      <guid isPermaLink="true">https://320hwany.tistory.com/127</guid>
      <comments>https://320hwany.tistory.com/127#entry127comment</comments>
      <pubDate>Tue, 6 May 2025 18:16:06 +0900</pubDate>
    </item>
    <item>
      <title>Golang 에서의 예외 처리</title>
      <link>https://320hwany.tistory.com/126</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번 글에서는 Golang에서의 예외 처리 방법에 대해 알아보려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Java Spring에 익숙한 개발자가 Golang을 배우게 되면서 느낀점을 위주로 작성해보려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  익숙한 Java Spring 방식의 예외 처리&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Java에서는 Checked Exception, Unchecked Exception이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1739685623135&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;TIL/java/예외처리.md at main &amp;middot; 320Hwany/TIL&quot; data-og-description=&quot;Today I Learned. Contribute to 320Hwany/TIL development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/320Hwany/TIL/blob/main/java/%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC.md&quot; data-og-url=&quot;https://github.com/320Hwany/TIL/blob/main/java/%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC.md&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/t8lwF/hyYfERANzN/FEKq2EsuzSVz8jkyV8ZiPK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/caIXwa/hyYfWxY6Es/OKikYWnyldMfPuSXPx2pN1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/8zwxP/hyYb8sGiNJ/A7iKkhlzK5Ad0tKQfd8bo1/img.png?width=1442&amp;amp;height=310&amp;amp;face=0_0_1442_310&quot;&gt;&lt;a href=&quot;https://github.com/320Hwany/TIL/blob/main/java/%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/320Hwany/TIL/blob/main/java/%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC.md&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/t8lwF/hyYfERANzN/FEKq2EsuzSVz8jkyV8ZiPK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/caIXwa/hyYfWxY6Es/OKikYWnyldMfPuSXPx2pN1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/8zwxP/hyYb8sGiNJ/A7iKkhlzK5Ad0tKQfd8bo1/img.png?width=1442&amp;amp;height=310&amp;amp;face=0_0_1442_310');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;TIL/java/예외처리.md at main &amp;middot; 320Hwany/TIL&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Today I Learned. Contribute to 320Hwany/TIL development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;간단하게 요약하면 개발하면서 발생하는 예외들을 처리할 때 최근에는 Unchecked Exception으로 처리하는 경향이 많은데&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이유는 Checked Exception을 사용하면 예외를 반드시 외부로 던져야 하기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉, 레이어드 아키텍쳐에서 생각을 해본다면 Repository에서 발생한 예외를 Service, Controller 등 상위 계층까지 전파가 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이로 인해 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;불필요한 의존 관계가 발생&lt;/span&gt;&lt;/b&gt;한다는 문제가 생깁니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 기존에 개발을 해올 때는 커스텀 예외에 대해서 모두 Unchecked Exception으로 만든 후 해당 메소드 안에서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;throw 하는 구조로 만들었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이렇게 발생한 예외를 Spring에서는 Controller Advice를 이용하여 공통 예외 처리를 해주어서 throw로 던진 예외를&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;처리할 수 있도록 하였습니다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739686111007&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
@RestControllerAdvice
public class ApiRestControllerAdvice {

    /**
     * 커스텀 예외 처리
     */

    // 400
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BadRequestException.class)
    public ErrorResponse handleException(final BadRequestException e) {
        log.info(&quot;BadRequestException={}&quot;, e.getMessage());
        return ErrorResponse.of(e.getStatusCode(), e.getMessage());
    }

    // 401
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    @ExceptionHandler(UnauthorizedException.class)
    public ErrorResponse handleException(final UnauthorizedException e) {
        log.info(&quot;UnauthorizedException Exception={}&quot;, e.getMessage());
        return ErrorResponse.of(e.getStatusCode(), e.getMessage());
    }

    // 403
    @ResponseStatus(HttpStatus.FORBIDDEN)
    @ExceptionHandler(ForbiddenException.class)
    public ErrorResponse handleException(final ForbiddenException e) {
        log.info(&quot;ForbiddenException Exception={}&quot;, e.getMessage());
        return ErrorResponse.of(e.getStatusCode(), e.getMessage());
    }

    // 404
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(NotFoundException.class)
    public ErrorResponse handleException(final NotFoundException e) {
        log.info(&quot;NotFoundException Exception={}&quot;, e.getMessage());
        return ErrorResponse.of(e.getStatusCode(), e.getMessage());
    }
 	
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이렇게 개발을 해오는 것이 익숙하였고 딱히 별다른 문제없이 당연(?)하다고 생각해왔습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 Golang에서의 예외 처리를 접하면서 조금 다른 시각을 가지게 되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  Golang 에서는 공통 예외 처리가 없다?&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Golang에서는 위 Java Spring 예시와 같이 공통 예외 처리를 해주는 방법이 따로 없습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Repository에서 발생한 예외는 Service, Controller 상위 레이어까지 전달한 다음에 상위 레이어에서 예외 처리를 해주도록 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;왜 이러한 방식으로 처리를 할까요? &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Golang의 철학이라고 생각합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; Golang은 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;개발자에게 많은 자유도를 주면서&lt;/span&gt; &lt;span style=&quot;color: #ee2323;&quot;&gt;실제 코드의 내부 동작을 개발자가 파악하기 쉽도록 &lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;하는 것&lt;/span&gt;&lt;/b&gt;에 집중한다고 생각합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Repository&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1739688858449&quot; class=&quot;go&quot; data-ke-language=&quot;go&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func (r *memberRepository) GetById(memberId int) (*entity.Member, error) {
	var member entity.Member

	if err := r.db.First(&amp;amp;member, memberId).Error; err != nil {
		return nil, err
	}

	return &amp;amp;member, nil
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Service&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;func (s *memberService) GetById(memberId int) (*entity.Member, error) {
    return s.memberRepository.GetById(memberId)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Controller&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;func (c *memberController) GetMember(w http.ResponseWriter, r *http.Request) {
    memberId, isFail := validator.ValidatePathVariable(w, r, &quot;/members/&quot;)

    if isFail {
       return
    }

    member, err := c.memberService.GetById(memberId)

    if validator.ValidateGormError(w, r, err) {
       return
    }

    memberResponse := dto.ToMemberResponse(member)
    json.NewEncoder(w).Encode(memberResponse)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이렇게 Controller의 코드만 보고도 이 코드를 실행하면서 내부적으로 어떠한 에러가 발생할 수 있는지를 한 번에 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기에 Controller가 너무 길어져 가독성이 떨어지는 문제가 있는 것 같아 validator를 만들어서 예외처리를 해주도록 하였습니다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  &lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;정리&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;코드가 시작하는 지점에서 어떤 에러가 발생할 수 있는지를 미리 확인할 수 있는 점은 Golang에서의 장점이라고 생각합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 Java Spring 방식으로 공통 예외 처리를 하는 방식이 조금 더 가독성이 있다고 생각합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;정답은 없다고 생각합니다. 각 언어의 철학이 있는 것이고 Golang은 개발자에게 자유도를 주면서 내부 동작을 쉽게&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;확인하도록 해주는 언어입니다. DI와 같은 자주 사용하는 패턴도 프레임워크에서 지원해주는 것을 쓰지 않고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;직접 구현해서 사용하는 경우가 많은 것을 보니 굳이 이런 것도 직접 구현해야 하나 싶은 생각이 들면서도 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;내부 동작에 대한 개발자의 이해도가 올라간다는 건 또 좋다는 생각도 듭니다. 개발에 정답은 없는 것 같습니다...!&lt;/span&gt;&lt;/p&gt;</description>
      <category>Go</category>
      <author>320Hwany</author>
      <guid isPermaLink="true">https://320hwany.tistory.com/126</guid>
      <comments>https://320hwany.tistory.com/126#entry126comment</comments>
      <pubDate>Sun, 16 Feb 2025 16:53:29 +0900</pubDate>
    </item>
    <item>
      <title>Goroutine 과 Java virtual thread 비교해보기</title>
      <link>https://320hwany.tistory.com/125</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Java 21 LTS 버전부터 정식으로 Virtual Thread가 포함되었고 이전 글에서 알아본 적이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1734678359885&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Java Virtual Thread 에 대해 알아보자&quot; data-og-description=&quot;Java 21 LTS 버전부터 정식으로 Virtual Thread가 포함되었습니다.Virtual Thread 란 무엇일까요?&amp;nbsp;오라클 공식 문서에 있는 표현은 다음과 같습니다.&amp;quot;Virtual threads are lightweight threads that reduce the effort of writing&quot; data-og-host=&quot;320hwany.tistory.com&quot; data-og-source-url=&quot;https://320hwany.tistory.com/121&quot; data-og-url=&quot;https://320hwany.tistory.com/121&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/0AcPT/hyXOgDVqTi/dmKBLa4q04DLDGGyP2nyj1/img.png?width=800&amp;amp;height=296&amp;amp;face=0_0_800_296,https://scrap.kakaocdn.net/dn/bXCkTi/hyXOk7nf7Q/kXBphP2FbkcaPeDeOPlkz0/img.png?width=800&amp;amp;height=296&amp;amp;face=0_0_800_296,https://scrap.kakaocdn.net/dn/Q1n0L/hyXOcVNRXm/9diD3QIf5KR2P8e77cTPtk/img.png?width=1032&amp;amp;height=1298&amp;amp;face=0_0_1032_1298&quot;&gt;&lt;a href=&quot;https://320hwany.tistory.com/121&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://320hwany.tistory.com/121&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/0AcPT/hyXOgDVqTi/dmKBLa4q04DLDGGyP2nyj1/img.png?width=800&amp;amp;height=296&amp;amp;face=0_0_800_296,https://scrap.kakaocdn.net/dn/bXCkTi/hyXOk7nf7Q/kXBphP2FbkcaPeDeOPlkz0/img.png?width=800&amp;amp;height=296&amp;amp;face=0_0_800_296,https://scrap.kakaocdn.net/dn/Q1n0L/hyXOcVNRXm/9diD3QIf5KR2P8e77cTPtk/img.png?width=1032&amp;amp;height=1298&amp;amp;face=0_0_1032_1298');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Java Virtual Thread 에 대해 알아보자&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Java 21 LTS 버전부터 정식으로 Virtual Thread가 포함되었습니다.Virtual Thread 란 무엇일까요?&amp;nbsp;오라클 공식 문서에 있는 표현은 다음과 같습니다.&quot;Virtual threads are lightweight threads that reduce the effort of writing&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;320hwany.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번 글에서는 Java Virtual Thread와 비교해보면서 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Golang에 있는 Goroutine&lt;/span&gt;&lt;/b&gt;에 대해 알아보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; &amp;nbsp; Goroutine과 Virtual Thread&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 경량 쓰레드 모델&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Goroutine과 Java의 Virtual Thread 모두 OS 쓰레드에 직접 매핑되지 않는 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;경량 쓰레드&lt;/span&gt;&lt;/b&gt;입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 비동기 처리 지원&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일반적으로 유저 쓰레드와 OS 쓰레드가 1대1로 매핑된다면 블로킹 작업이 있을 때 OS 쓰레드도 블로킹됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 두 모델 모두 각각의 런타임(Goroutine은 Go 런타임, Virtual Thread는 JVM)에서 관리하여&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;비동기 방식으로 처리할 수 있도록 도와줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;3. 스케줄링 관리&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;두 모델 모두 컨텍스트 스위칭이 OS 레벨에서 일어나지 않습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Goroutine은 Go 런타임, Virtual Thread는 JVM 수준에서 스케줄링합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;즉 위와 같은 특징들로 인해 두 모델 모두 처리량이 높은 애플리케이션에서 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;높은 동시성 처리&lt;/span&gt;&lt;/b&gt;가 필요할 때 사용할 수 있습니다.&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Goroutine은 Virtual Thread 보다 조금 더 세밀한 제어를 할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;직접 예시를 만들어보면서 살펴보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; &amp;nbsp; Goroutine 사용 예시&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Hello World를 출력하는 함수를 만들고 이 함수를 10번 실행하도록 해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이때 함수의 각 실행은 약 1초가 걸리도록 하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;go&quot; data-ke-language=&quot;go&quot;&gt;&lt;code&gt;package main

import (
	&quot;fmt&quot;
	&quot;time&quot;
)

func main() {
	start := time.Now()

	for i := 0; i &amp;lt; 10; i++ {
		printHello()
	}

	elapsed := time.Since(start)
	fmt.Printf(&quot;걸린 시간: %s\n&quot;, elapsed)
}

func printHello() {
	fmt.Println(&quot;hello world&quot;)
	time.Sleep(time.Second)
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;걸린 시간: 10.010707292s 의 결과가 나왔습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;main 함수를 실행하고 하나의 실행 흐름에서 printHello()를 순서대로 10번을 실행했기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 만약에 이 작업을 병렬로 실행해도 문제가 없는 경우라면 어떨까요?&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이때 goroutine을 사용해볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;package main

import (
    &quot;fmt&quot;
    &quot;sync&quot;
    &quot;time&quot;
)

var waitGroup sync.WaitGroup

func main() {
    start := time.Now()
    waitGroup.Add(10)

    for i := 0; i &amp;lt; 10; i++ {
       go printHelloWithGoroutine()
    }

    waitGroup.Wait()
    elapsed := time.Since(start)
    fmt.Printf(&quot;걸린 시간: %s\n&quot;, elapsed)
}

func printHelloWithGoroutine() {
    fmt.Println(&quot;hello world&quot;)
    time.Sleep(time.Second)
    waitGroup.Done()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;걸린 시간: 1.001426875s 의 결과가 나왔습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;print 함수 앞에 go 키워드를 사용하였으며 waitGroup 을 사용하여 해당 작업이 모두 완료될 때까지 기다려주었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;main 함수에서 시작된 쓰레드 흐름이 goroutine을 만나면 새로운 경량 쓰레드를 생성하여 해당 작업을 넘긴 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 10번의 함수 실행을 거의 동시에 병렬로 실행할 수 있게되어 약 10초 -&amp;gt; 약 1초로 성능을 향상 시킬 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  Goroutine 사용시 주의할 점&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이렇게 Goroutine은 경량 쓰레드로 동시 처리 성능을 향상 시킬 수 있다는 것을 확인하였습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;두 모델 모두 비슷한 메커니즘으로 동작하지만 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Goroutine은 조금 더 세밀한 제어&lt;/span&gt;&lt;/b&gt;가 가능합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 Goroutine은 사용할 때 주의할 점이 있습니다. 단순하게 동시 처리 성능을 높인만큼 공유 자원에 대한&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;동시성 문제에 대해 고려해야 합니다. 한 가지 예시를 만들어보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1000번 동시에 1000원씩 입금을 한다면 최종적으로 100만원이라는 금액이 있어야 합니다&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;package main

import (
    &quot;fmt&quot;
    &quot;sync&quot;
)

var waitGroup1 sync.WaitGroup
var money1 = 0

func main() {
    waitGroup1.Add(1000)

    for i := 0; i &amp;lt; 1000; i++ {
       go deposit(1000)
    }

    waitGroup1.Wait()
    fmt.Println(money1)
}

func deposit(depositAmount int) {
    money1 += depositAmount
    waitGroup1.Done()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해당 로직을 실행한 결과 956000, 945000, 938000으로 100만원이 아닐 뿐만 아니라 매번 결과가 달랐습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여러 경량 쓰레드가 동시에 money1 이라는 공유 자원에 접근하면서 문제가 발생한 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;뮤텍스를 사용하여 공유 자원에 접근할 때 문제가 발생하지 않도록 수정해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #080808;&quot;&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;package main

import (
    &quot;fmt&quot;
    &quot;sync&quot;
)

var waitGroup2 sync.WaitGroup
var money2 = 0
var lock = sync.Mutex{}

func main() {
    waitGroup2.Add(1000)

    for i := 0; i &amp;lt; 1000; i++ {
       go depositWithLock(1000)
    }

    waitGroup2.Wait()
    fmt.Println(money2)
}

func depositWithLock(depositAmount int) {
    lock.Lock()
    defer lock.Unlock()

    money2 += depositAmount
    waitGroup2.Done()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해당 로직을 실행한 결과 100만원이라는 정확한 결과가 나왔습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; &amp;nbsp; 정리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이전에는 Java를 주로 했어서 Virtual Thread의 동작 원리에 대해서만 알고 있었는데 Golang을 배우기 시작하면서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Goroutine에 대해 알게되었습니다. &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;각각 언어마다 마주치는 문제도 비슷한 것 같고 해결하는 방법도 비슷&lt;/span&gt;&lt;/b&gt;하다는 것을 느꼈습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;경량 쓰레드, 비동기 처리, 스케줄링 방식&lt;/span&gt;&lt;/b&gt; 등등 다양한 면에서 비슷하다고 느꼈습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 Goroutine은 조금 더 세밀한 제어가 가능한 것 같고 Goroutine 간 메세지를 전달하는 메세지 큐 역할을 하는&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt; 채널&lt;/span&gt;&lt;/b&gt;이라는&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;개념도 있는 것 같아서 조금 더 학습한 뒤 다시 정리해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예제 코드&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1734684829995&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - 320Hwany/goroutine-test&quot; data-og-description=&quot;Contribute to 320Hwany/goroutine-test development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/320Hwany/goroutine-test&quot; data-og-url=&quot;https://github.com/320Hwany/goroutine-test&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://github.com/320Hwany/goroutine-test&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/320Hwany/goroutine-test&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - 320Hwany/goroutine-test&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to 320Hwany/goroutine-test development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Go</category>
      <author>320Hwany</author>
      <guid isPermaLink="true">https://320hwany.tistory.com/125</guid>
      <comments>https://320hwany.tistory.com/125#entry125comment</comments>
      <pubDate>Fri, 20 Dec 2024 17:53:57 +0900</pubDate>
    </item>
    <item>
      <title>UUID 충돌 발생이 예상된다면 어떻게 대처를 해야 할까?</title>
      <link>https://320hwany.tistory.com/124</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로젝트를 진행하면서 UUID로 임의의 문자열을 생성하는 로직이 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;128비트의 32글자 UUID 버전 4를 사용했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;매초 10억개를 100년동안 만들더라도 단 하나의 중복이 발생할 확률이 약 50% 정도라고 알려져 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 따로 고려를 해본적은 없었지만 서비스가 정말 어마어마하게 커져 충돌 발생 가능성을 고려해야 한다면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어떠한 식으로 대처를 해야할 지에 대해 작성해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  대략적인 규모 추정하기&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;16바이트의 UUID를 매초 10억개씩 100년동안 만들었다고 가정해본다면 기가, 테라, 페타를 넘어&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;약 50 엑사바이트 정도의 데이터입니다. 이는 하나의 데이터베이스에서 저장하기에는 너무나도 큰 수치입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;우선은 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;하나의 데이터베이스에 저장하는 경우&lt;/span&gt;&lt;/b&gt;를 생각해보고 추가적으로 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;스케일 아웃했을 경우&lt;/span&gt;&lt;/b&gt;를 고려해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  충돌 감지하기&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;우선 UUID를 데이터베이스에 insert 하기 전에 이미 해당 문자열이 있는지 확인을 해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이를 확인하기 위해서 전체 테이블 풀스캔을 하는 것은 너무나도 큰 성능 저하가 예상됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 우선은 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;인덱스&lt;/span&gt;&lt;/b&gt;를 활용해야겠다는 생각이 듭니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일반적인 B+Tree를 사용한다면 조회 성능이&amp;nbsp;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;O(logn) 정도 나오는 것을 보장할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 일반적으로 인덱스는 조회 성능을 높이며 삽입/수정/삭제의 성능은 조금 포기해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;매초 10억건은 매우 많은 insert 작업입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일반적인 B+Tree 인덱스를 이용하여 해당 문자열이 있는지 확인을 하더라도 대량의 insert 작업으로 인해서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인덱스 자료구조를 관리하는 비용이 커질 것으로 예상됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;물론 생성하는 임의의 문자열을 UUID 버전 7과 같이 항상 정렬된 순서를 보장할 수 있다면 이에 대한 비용은&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;줄어들게 할 수도 있습니다. 하지만 더 나은 대안을 생각해보겠습니다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;B+Tree가 아닌&lt;/span&gt;&lt;/b&gt; &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;해시 인덱스&lt;/span&gt;&lt;/b&gt;를 생각해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;B+Tree가 효과적인 인덱스 자료구조였던 이유중 하나가 범위 검색에 특화되어 있다는 점이 컸었는데&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;특정 임의의 문자열의 존재 여부를 판단하기에는 해시 자료구조가 조금 더 적합해 보입니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  예상되는 구조 설계&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;UUIDCreator 클래스를 생성하고 uuids를 해시를 이용한 Set 자료구조로 관리하도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1731302178823&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UUIDCreator {

    static Set&amp;lt;UUID&amp;gt; uuids = ConcurrentHashMap.newKeySet();

    public UUID create() {
        UUID uuid;

        do {
            uuid = UUID.randomUUID();
        } while (!uuids.add(uuid));

        return uuid;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;uuids에 새로 생성한 문자열이 포함되어 있다면 새롭게 생성하여 재시도하고 없다면 uuids에 추가한 후 리턴하도록 하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이러한 방식으로 새롭게 생성한 문자열의 충돌 여부 확인을 O(1)의 시간만에 할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또한 insert 할 때도 O(1)의 성능을 보장할 수 있습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이제 &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;충돌 여부 확인, 문자열 insert 모두 O(1)로 빠른 성능을 보장&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;했지만 하나의 데이터베이스를 기준으로 하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여러 데이터베이스에 분산되어 저장되어 있는 경우를 생각해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 경우에는 UUID 버전 7과 같이 시간을 기준으로 정렬을 보장할 수 있다면 그 정보를 기반으로 어떠한 데이터베이스에&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;저장되어 있는지를&amp;nbsp;판단하기 때문에 분산 시스템에서 적합할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 완전히 충돌 발생 가능성이 0인 것은 아닌 이유는 동일 밀리초 내에서 생성된 경우는 아주 낮은 확률로&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;충돌이 발생할 수도 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다만 이 경우에는 굳이 정렬이 아니더라도 db1-{UUID 문자열}, db2-{UUID 문자열}, db3-{UUID 문자열} 와 같은 방식으로&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;앞에 db에 대한 정보를 적어놓고 같은 서버 안에서만 충돌 여부를 확인한 후 앞에 db 정보를 붙인 UUID 문자열을&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사용한다면 전체 서비스 내에서 충돌 없이 사용해볼 수 있을 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  정리&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. 매초 10억건의 insert의 UUID를 생성한다면 인덱스는 B+Tree 보다는 해시가 적합할 수도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 해시 방식으로 인덱스를 관리하여 충돌 여부 확인, 문자열 insert를 O(1) 성능 보장&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. 스케일 아웃을 한 경우에는 UUID 버전 7이나 각 DB 마다 DB 정보를 붙여 전체 서비스에서는 충돌 방지&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4. 한 번에 insert를 할 때 batch 처리를 고려&lt;/span&gt;&lt;/p&gt;</description>
      <category>아키텍쳐</category>
      <author>320Hwany</author>
      <guid isPermaLink="true">https://320hwany.tistory.com/124</guid>
      <comments>https://320hwany.tistory.com/124#entry124comment</comments>
      <pubDate>Mon, 11 Nov 2024 14:50:26 +0900</pubDate>
    </item>
    <item>
      <title>MVCC 만으로 팬텀 리드를 막을 수 있을까?</title>
      <link>https://320hwany.tistory.com/123</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번 글에서는 RDBMS의 MVCC가 팬텀 리드를 막을 수 있는지 알아보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;먼저 MVCC, 팬텀 리드에 대한 용어부터 정리해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;MVCC&lt;/b&gt;&lt;/span&gt;는 Multi Version Concurrency Control의 약자로 레코드 값을 여러 버전으로 관리한다는 것을 의미합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;MVCC를 사용함으로써 잠금 없는 읽기를 제공하면서 여러 트랜잭션의 동시 처리 성능을 높였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;팬텀 리드&lt;/b&gt;&lt;/span&gt;는 같은 트랜잭션 내부에서 같은 쿼리를 실행했을 때 새로운 레코드가 보일 수 있는 경우를 말합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;같은 레코드의 데이터 자체는 바뀌지 않지만 새로운 데이터가 추가되거나 삭제된 경우는 쿼리의 결과값이 동일하게&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;나오지 않을 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  예제 만들어보기&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기서 한가지 의문점이 들었습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DB 격리 레벨에서 Serializable을 제외한 나머지는 모두 팬텀 리드가 발생할 수 있다는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;MVCC로 각 레코드 데이터를 버전별로 관리하고 해당 트랜잭션의 버전에 맞게 새로운 데이터가 추가되거나 삭제된 경우는&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;보여주지 않으면 될 것 같은데 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;왜 팬텀 리드가 발생&lt;/span&gt;&lt;/b&gt;하는 것일까요?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;트랜잭션 2개를 실행해보면서 알아보겠습니다. (일반적인 RDBMS의 MVCC를 사용하는 Repeatable Read 수준이라고 가정)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1730014867993&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- Transaction 1
BEGIN;
SELECT COUNT(*) FROM member WHERE age BETWEEN 20 AND 30;  -- 결과: 10

-- Transaction 2 (동시 실행)
BEGIN;
INSERT INTO member (name, age) VALUES ('홍길동', 25);
COMMIT;

-- Transaction 1
SELECT COUNT(*) FROM member WHERE age BETWEEN 20 AND 30;  -- 결과: 10
COMMIT;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 쿼리에서는 Transaction1 에서 같은 쿼리를 2번 실행하였는데 모두 결과가 10 이 나왔습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Transaction2의 커밋을 통해 새로운 레코드가 추가되었지만 그 결과가 Transaction1에 영향을 주지는 않았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Transaction1의 시작 시점의 버전을 1이라고 하고 Transaction2의 시작 시점을 버전 2라고 하면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Transacion1에서 두 번째 쿼리를 시작할 때 시작 시점의 버전인 1 버전에 대한 데이터를 읽어옵니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 새롭게 추가된 (&quot;홍길동&quot;, 25) 회원의 정보는 조회하지 않아 결과가 그대로 10이 나오게 되는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 경우에는 팬텀 리드가 발생하지 않은 것입니다. 그러면 MVCC가 팬텀 리드를 막을 수 있는 것일까요?&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  문제는 Lock을 사용할 때 발생&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위와 같이 Lock을 사용하지 않고 SELECT, SELECT 쿼리를 실행한다면 항상 트랜잭션의 시작 시점의 데이터를 읽어오기 때문에&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;MVCC 만으로도 팬텀 리드를 막을 수 있습니다.&amp;nbsp;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;문제는 Lock을 사용할 때 발생&lt;/span&gt;&lt;/b&gt;합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위 예시를 조금 변형해서 SELECT 쿼리에 write lock을 사용해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1730015389945&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- Transaction 1 시작
BEGIN;
SELECT COUNT(*) FROM member WHERE age BETWEEN 20 AND 30 FOR UPDATE;  -- 결과: 10

-- Transaction 2 시작 (동시 실행)
BEGIN;
INSERT INTO member (name, age) VALUES ('홍길동', 25);
COMMIT;

-- Transaction 1 
SELECT COUNT(*) FROM member WHERE age BETWEEN 20 AND 30 FOR UPDATE;  -- 결과: 11
COMMIT;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번에는 Transaction1 에서 같은 쿼리를 2번 실행했지만 결과가 다르게 나왔습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;마찬가지로 Transaction1의 시작 시점의 버전을 1이라고 하고 Transaction2의 시작 시점을 버전 2라고 하면&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Transacion1에서 두 번째 쿼리를 시작할 때 1 버전에 대한 데이터가 아니라 해당 레코드의 가장 최신 버전에 대한 데이터를&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;읽어오게 됩니다. 따라서 새롭게 삽입된 (&quot;홍길동&quot;, 25) 회원의 정보를 추가로 조회하여 결과가 11이 나오게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;즉 MVCC를 사용하더라도 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Lock을 사용한다면 트랜잭션 시작 버전이 아닌 가장 최신 버전&lt;/span&gt;&lt;/b&gt;을 가져오기 때문에&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;팬텀 리드가 발생할 수 있게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  MySQL의 갭락 (Gap Lock)&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 대부분의 RDBMS의 격리 레벨 Repeatable Read 수준에서는 MVCC를 사용하더라도 팬텀 리드가 발생할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 MySQL의 경우에는 대부분의 경우 팬텀 리드가 발생하지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;MySQL은 MVCC를 제공하면서도 갭락이라는 Lock 또한 제공하기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;MySQL은 갭락을 이용해서 레코드와 레코드의 사이의 간격을 잠금&lt;/span&gt;&lt;/b&gt;으로써 레코드의 생성, 수정, 삭제를 제어합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;다른 RDBMS에서 막을 수 없었던 팬텀 리드를 어떠한 방식으로 막을 수 있는지 이전 예제를 다시 살펴보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1730015941659&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- Transaction 1 시작
BEGIN;
SELECT COUNT(*) FROM member WHERE age BETWEEN 20 AND 30 FOR UPDATE;  -- 결과: 10

-- Transaction 2 시작 (동시 실행)
BEGIN;
INSERT INTO member (name, age) VALUES ('홍길동', 25); 
-- Transaction 2는 BLOCKING

-- Transaction 1 
SELECT COUNT(*) FROM member WHERE age BETWEEN 20 AND 30 FOR UPDATE;  -- 결과: 10
COMMIT;

-- Transaction 1이 COMMIT된 후에야
-- Transaction 2의 INSERT가 실행됨
-- (Transaction 2의 COMMIT 실행)
COMMIT;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Transaction1의 첫 번째 쿼리를 실행할 때 age 20 ~ 30 범위의 기본 레코드에 대한 락 뿐만 아니라 그 사이의 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;빈 공간에 대한 락을 획득합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이때 Transaction 2가 age 25라는 새로운 데이터를 추가하려고 하지만 이미 Transaction 1이 age 20 ~ 30 범위에는&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;갭락을 걸어둔 상태입니다. 따라서 해당 insert 문은 Transaction 1이 커밋되거나 롤백될 때까지 Blocking 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결과적으로 Transaction1의 두 번째 쿼리의 결과도 마찬가지로 10이라는 결과를 반환하게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;   갭락으로도 막을 수 없는 팬텀 리드의 발생 상황&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;지금까지 같은 트랜잭션 내부에서 SELECT, SELECT를 실행한 경우 SELECT FOR UPDATE, SELECT FOR UPDATE를 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;실행한 2가지 경우를 살펴보았습니다. 기존 내용을 다시 한번 정리하면서 추가적으로 &lt;span style=&quot;text-align: start;&quot;&gt;조금 더 다양한 케이스를 살펴보겠습니다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;편의상 Transaction1, Transaction2를 각각 T1, T2 라고 표현하며 마찬가지로 Repeatable Read 격리 레벨에 대한 케이스입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1. SELECT (T1)&amp;nbsp; -&amp;gt; INSERT (T2) -&amp;gt; SELECT (T1)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일반적인 RDBMS 라면 MVCC를 사용해서 팬텀 리드를 막을 수 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;트랜잭션 시작 시점의 버전에 대한 데이터를 일관적으로 읽어올 수 있기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;2. SELECT FOR UPDATE (T1) -&amp;gt; INSERT (T2) -&amp;gt;&amp;nbsp; SELECT FOR UPDATE (T1)&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 경우에는 Lock을 사용하여 최신 버전의 데이터를 읽어 오기 때문에 MVCC로만으로는 팬텀 리드를 막을 수 없습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 특별히 MySQL에서만 갭락을 사용하여 팬텀 리드를 막을 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;3. SELECT FOR UPDATE (T1) -&amp;gt; INSERT (T2) -&amp;gt; SELECT (T1)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;마찬가지로 MVCC 만으로는 팬텀리드를 막을 수 없고 T1의 첫 번째 쿼리를 실행할 때 갭락이 걸려 T1의 두 번째 쿼리도&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;같은 결과를 반환할 수 있도록 해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;4. SELECT (T1) -&amp;gt; INSERT (T2) -&amp;gt; SELECT FOR UPDATE (T1)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;바로 4번의 경우가 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;MySQL을 사용하더라도 문제가 발생할 수 있는 경우&lt;/span&gt;&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;T1의 첫 번째 쿼리를 실행할 때 갭락이 사용되지 않았는데 T1의 두 번째 쿼리를 실행할 때는 최신 데이터를 읽어오게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 T1에서의 쿼리 결과가 달라지는 경우가 발생합니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  정리&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번 글에서는 MVCC, 팬텀 리드에 대해 정리해보고 MVCC 만으로 팬텀 리드를 막을 수 없는 이유에 대해 알아보았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;MVCC 만으로는 막을 수 없었지만 MySQL은 갭락을 이용하여 대부분의 경우 팬텀 리드를 막을 수 있다는 것을&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여러 가지 케이스로 나눠서 살펴보았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다른 여러 RDBMS는 대부분 Read Commited을 기본 격리 레벨로 설정하는데 MySQL InnoDB 엔진은 기본적으로&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Repeatable Read를 기본 격리 레벨로 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;왜 Repeatable Read를 기본 레벨로 설정했을까를 생각해보면 MySQL의 갭락이 큰 이유라고 생각합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;갭락을 사용하면서 MVCC 만으로는 막을 수 없었던 팬텀 리드의&amp;nbsp;대부분의 경우를 막을 수 있게 되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또한 Read Commited 과 비교하여 조금 더 관리하는 버전이 많아져 언두 로그의 데이터가 더 많이 쌓일 수도 있고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;갭락으로 인해 데드락이 발생할 수 있는 가능성이 많아졌지만 성능상 큰 차이점이 없다고 알려진 것으로 보아&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&quot;성능도 챙기고 데이터 일관성을 더 챙길 수 있는 Repeatable Read 수준을 채택하지 않았을까&quot;&lt;/span&gt;&lt;/b&gt; 라는 생각을 해봅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;출처&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1730017835593&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[MySQL] 스토리지 엔진 수준의 락의 종류(레코드 락, 갭 락, 넥스트 키 락, 자동 증가 락)&quot; data-og-description=&quot;이번에는 스토리지 엔진 수준의 락의 종류에 대해 알아보도록 하겠습니다. 아래의 내용은 RealMySQL과 MySQL 공식 문서 등을 참고하여 작성하였으며, 모든 내용은 InnoDB를 기준으로 설명합니다. 1. 스&quot; data-og-host=&quot;mangkyu.tistory.com&quot; data-og-source-url=&quot;https://mangkyu.tistory.com/298&quot; data-og-url=&quot;https://mangkyu.tistory.com/298&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bp7I4Y/hyXlSxwxke/fseWvwHriHpQn6BlNfwj5k/img.png?width=800&amp;amp;height=324&amp;amp;face=0_0_800_324,https://scrap.kakaocdn.net/dn/UVeOJ/hyXpBHDYCQ/s3H2Bk40nqcYR5JopZ1z51/img.png?width=800&amp;amp;height=324&amp;amp;face=0_0_800_324,https://scrap.kakaocdn.net/dn/cTxD26/hyXlKfab6W/QtK8WOdMnaLSt8PvIzZVo1/img.png?width=1290&amp;amp;height=523&amp;amp;face=0_0_1290_523&quot;&gt;&lt;a href=&quot;https://mangkyu.tistory.com/298&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://mangkyu.tistory.com/298&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bp7I4Y/hyXlSxwxke/fseWvwHriHpQn6BlNfwj5k/img.png?width=800&amp;amp;height=324&amp;amp;face=0_0_800_324,https://scrap.kakaocdn.net/dn/UVeOJ/hyXpBHDYCQ/s3H2Bk40nqcYR5JopZ1z51/img.png?width=800&amp;amp;height=324&amp;amp;face=0_0_800_324,https://scrap.kakaocdn.net/dn/cTxD26/hyXlKfab6W/QtK8WOdMnaLSt8PvIzZVo1/img.png?width=1290&amp;amp;height=523&amp;amp;face=0_0_1290_523');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[MySQL] 스토리지 엔진 수준의 락의 종류(레코드 락, 갭 락, 넥스트 키 락, 자동 증가 락)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이번에는 스토리지 엔진 수준의 락의 종류에 대해 알아보도록 하겠습니다. 아래의 내용은 RealMySQL과 MySQL 공식 문서 등을 참고하여 작성하였으며, 모든 내용은 InnoDB를 기준으로 설명합니다. 1. 스&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;mangkyu.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database</category>
      <author>320Hwany</author>
      <guid isPermaLink="true">https://320hwany.tistory.com/123</guid>
      <comments>https://320hwany.tistory.com/123#entry123comment</comments>
      <pubDate>Sun, 27 Oct 2024 17:27:00 +0900</pubDate>
    </item>
    <item>
      <title>필터링 검색 기능은 어떻게 구현되는 것일까? (feat. inverted index)</title>
      <link>https://320hwany.tistory.com/122</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여러 서비스를 이용하다보면 다음과 같은 필터링 기능을 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-02 오후 2.32.33.png&quot; data-origin-width=&quot;2996&quot; data-origin-height=&quot;1786&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bA6hCU/btsJSF5Yrx8/f8GM4lAF2EZOLXeRKz8jQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bA6hCU/btsJSF5Yrx8/f8GM4lAF2EZOLXeRKz8jQ0/img.png&quot; data-alt=&quot;인프런 온라인 강의 사이트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bA6hCU/btsJSF5Yrx8/f8GM4lAF2EZOLXeRKz8jQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbA6hCU%2FbtsJSF5Yrx8%2Ff8GM4lAF2EZOLXeRKz8jQ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2996&quot; height=&quot;1786&quot; data-filename=&quot;스크린샷 2024-10-02 오후 2.32.33.png&quot; data-origin-width=&quot;2996&quot; data-origin-height=&quot;1786&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;인프런 온라인 강의 사이트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;수많은 강의들이 있는데 Spring, MySQL, Node.js, React, SQL과 같은 키워드를 바탕으로 해당되는 강의들을 필터링해서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;보여주는 기능입니다. 또 이러한&amp;nbsp;기술 검색 필터링 외에도 검색 키워드를 바탕으로 해당 키워드를 제목으로 포함한 강의를&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 보여주는 기능도 있을 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;검색 기능 구현을 위해서는 3가지 방법 정도가 생각납니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. RDBMS의 like 키워드 사용&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. RDBMS의 Fulltext index 사용&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. Elasticsearch와 같은 검색 엔진 사용&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기술적인 내용은 위와 같지만 이번 글에서는 이러한 기술은 조금 제쳐두고&amp;nbsp;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;조금 더 원리에 집중해서&lt;/span&gt;&lt;/b&gt; 어떻게 기능을 구현하면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 좋을지 생각해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  역 인덱스 (Inverted index)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;조회 성능을 높이는 인덱스에 대해서는 많이 사용하고 있지만 역 인덱스 (inverted index)에 대해서는 조금 낯설 수도 있을 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;inverted index는 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;키워드를 통해 정보를 찾아내는 방식&lt;/span&gt;&lt;/b&gt;입니다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인덱스와 역 인덱스의 기가 막힌 비유가 하나 있는데 인덱스는 책의 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;목차라고 생각하면 되고 역 인덱스는 책 뒷 부분의 찾아보기로 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;역 인덱스는 기본적으로 key-value 형식이라고 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;key 값에 특정 키워드를 넣고 value에 해당 문서에 대한 참조와 같은 내용을 넣을 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  예제 만들어보기&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예제를 하나 직접 만들어보며 역 인덱스에 대해 더 자세히 알아보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예제 요구 사항은 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. 인프런 강의 사이트와 같이 여러 키워드로 필터링을 할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 선택한 키워드를 제목으로 포함하는 강의 정보를 반환한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. 키워드는 30개 정도로 하고 강의는 100만 건의 데이터를 넣어 결과를 비교해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;우선 100만 건의 데이터를 먼저 넣어보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;하나의 강의에 1 ~ 3개의 키워드를 포함하도록 제목을 구성하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-02 오후 2.53.41.png&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;898&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTm3X6/btsJTI1K4v2/n2BCwb2CW1menYhyFNjpe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTm3X6/btsJTI1K4v2/n2BCwb2CW1menYhyFNjpe1/img.png&quot; data-alt=&quot;키워드 데이터&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTm3X6/btsJTI1K4v2/n2BCwb2CW1menYhyFNjpe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTm3X6%2FbtsJTI1K4v2%2Fn2BCwb2CW1menYhyFNjpe1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;405&quot; data-filename=&quot;스크린샷 2024-10-02 오후 2.53.41.png&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;898&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;키워드 데이터&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-02 오후 2.54.26.png&quot; data-origin-width=&quot;1626&quot; data-origin-height=&quot;972&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUqXeZ/btsJSqgSsa0/uDkxwIkTkaHES7SQgFHUXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUqXeZ/btsJSqgSsa0/uDkxwIkTkaHES7SQgFHUXk/img.png&quot; data-alt=&quot;테스트 데이터 저장&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUqXeZ/btsJSqgSsa0/uDkxwIkTkaHES7SQgFHUXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUqXeZ%2FbtsJSqgSsa0%2FuDkxwIkTkaHES7SQgFHUXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;359&quot; data-filename=&quot;스크린샷 2024-10-02 오후 2.54.26.png&quot; data-origin-width=&quot;1626&quot; data-origin-height=&quot;972&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;테스트 데이터 저장&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;역 인덱스를 사용하기 전에 &quot;%like%&quot; 연산자를 이용한 예제부터 살펴보겠습니다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-02 오후 2.56.22.png&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/60H6W/btsJUboXxBS/gVhDWI8M4X4CaHKp3EWh3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/60H6W/btsJUboXxBS/gVhDWI8M4X4CaHKp3EWh3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/60H6W/btsJUboXxBS/gVhDWI8M4X4CaHKp3EWh3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F60H6W%2FbtsJUboXxBS%2FgVhDWI8M4X4CaHKp3EWh3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1382&quot; height=&quot;114&quot; data-filename=&quot;스크린샷 2024-10-02 오후 2.56.22.png&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;114&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-02 오후 2.57.59.png&quot; data-origin-width=&quot;1436&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RNa8w/btsJUcajFA4/mEpdqkk0XKOfU1iFRpIk0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RNa8w/btsJUcajFA4/mEpdqkk0XKOfU1iFRpIk0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RNa8w/btsJUcajFA4/mEpdqkk0XKOfU1iFRpIk0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRNa8w%2FbtsJUcajFA4%2FmEpdqkk0XKOfU1iFRpIk0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1436&quot; height=&quot;284&quot; data-filename=&quot;스크린샷 2024-10-02 오후 2.57.59.png&quot; data-origin-width=&quot;1436&quot; data-origin-height=&quot;284&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;간단하게 Postman을 이용해 API 요청을 1번 날려보았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-02 오후 3.09.04.png&quot; data-origin-width=&quot;2598&quot; data-origin-height=&quot;1534&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kBtxh/btsJScXGBwn/i1SoLKX183kCOIHUDgItIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kBtxh/btsJScXGBwn/i1SoLKX183kCOIHUDgItIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kBtxh/btsJScXGBwn/i1SoLKX183kCOIHUDgItIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkBtxh%2FbtsJScXGBwn%2Fi1SoLKX183kCOIHUDgItIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;800&quot; height=&quot;472&quot; data-filename=&quot;스크린샷 2024-10-02 오후 3.09.04.png&quot; data-origin-width=&quot;2598&quot; data-origin-height=&quot;1534&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해당 요청의 응답 수는 100만 건중에서 66468 건 이었고 746ms가 걸렸습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;사실 이 쿼리는 title에 인덱스가 걸려있어도 인덱스를 활용하지 못하고 테이블 풀 스캔으로 실행됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;약 6만 건의 데이터를 읽어오기 위해 테이블에 있는 100만 건의 데이터를 모두 읽어온다는 이야기입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이제 역 인덱스를 활용한 코드를 작성해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;LectureRepository 뿐만 아니라 key-value 형식의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Map인 keyworldInvertedIndex도 생성하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-02 오후 3.16.10.png&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJ8Vl4/btsJTAJIyVZ/mASwNd5iap469kqKy7b4I1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJ8Vl4/btsJTAJIyVZ/mASwNd5iap469kqKy7b4I1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJ8Vl4/btsJTAJIyVZ/mASwNd5iap469kqKy7b4I1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJ8Vl4%2FbtsJTAJIyVZ%2FmASwNd5iap469kqKy7b4I1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1568&quot; height=&quot;108&quot; data-filename=&quot;스크린샷 2024-10-02 오후 3.16.10.png&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;108&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;원활한 예제 진행을 키워드 30개는 미리 key 값으로 넣어놓았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-02 오후 3.17.08.png&quot; data-origin-width=&quot;974&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdoA81/btsJTMiPLhN/2kip0ATHX1sACXsfYrpf1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdoA81/btsJTMiPLhN/2kip0ATHX1sACXsfYrpf1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdoA81/btsJTMiPLhN/2kip0ATHX1sACXsfYrpf1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdoA81%2FbtsJTMiPLhN%2F2kip0ATHX1sACXsfYrpf1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;196&quot; data-filename=&quot;스크린샷 2024-10-02 오후 3.17.08.png&quot; data-origin-width=&quot;974&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또 putKeyword() 라는 메소드를 통해 위에서 100만 건의 테스트 데이터를 저장할 때 lectureId, title, teacher 정보를&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;담는 LectureResponse 객체도 value 값으로 넣어주었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-02 오후 3.17.58.png&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;490&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyqMJC/btsJUj8hgP4/OgNXkonFAz56jrBVOEIqU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyqMJC/btsJUj8hgP4/OgNXkonFAz56jrBVOEIqU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyqMJC/btsJUj8hgP4/OgNXkonFAz56jrBVOEIqU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyqMJC%2FbtsJUj8hgP4%2FOgNXkonFAz56jrBVOEIqU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1338&quot; height=&quot;490&quot; data-filename=&quot;스크린샷 2024-10-02 오후 3.17.58.png&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;490&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이제 해당 키워드 값이 들어오면 key 값에 맞는 value를 가져오기만 하면 됩니다!&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-02 오후 3.19.03.png&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;140&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dn0hIG/btsJTGQysNY/dIywskiRiuA7y46GGUVdrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dn0hIG/btsJTGQysNY/dIywskiRiuA7y46GGUVdrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dn0hIG/btsJTGQysNY/dIywskiRiuA7y46GGUVdrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdn0hIG%2FbtsJTGQysNY%2FdIywskiRiuA7y46GGUVdrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1360&quot; height=&quot;140&quot; data-filename=&quot;스크린샷 2024-10-02 오후 3.19.03.png&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;140&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다시 Postman을 이용해 이제 역 인덱스를 적용한 조회 API를 1번 날려보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-02 오후 3.13.41.png&quot; data-origin-width=&quot;2562&quot; data-origin-height=&quot;1528&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lysJa/btsJTHBVMsD/NZDpJOatdJiqeKecfskWF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lysJa/btsJTHBVMsD/NZDpJOatdJiqeKecfskWF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lysJa/btsJTHBVMsD/NZDpJOatdJiqeKecfskWF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlysJa%2FbtsJTHBVMsD%2FNZDpJOatdJiqeKecfskWF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2562&quot; height=&quot;1528&quot; data-filename=&quot;스크린샷 2024-10-02 오후 3.13.41.png&quot; data-origin-width=&quot;2562&quot; data-origin-height=&quot;1528&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;마찬가지로 100만 건의 데이터 중에서 &lt;span style=&quot;text-align: start;&quot;&gt;66468 건의 데이터를 가져왔으며 조회에 걸린 시간은 127ms로 like 연산자를 사용했을 때보다&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;약 6배정도 빠른 성능을 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  역 인덱스의 장단점&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;역 인덱스를 사용하면 키워드 기반으로 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;정보를 검색할 때 아주 빠르다는 장점&lt;/span&gt;&lt;/b&gt;이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 역 인덱스도 인덱스와 마찬가지로 단점이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 바로 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;조회 성능&lt;/span&gt;&lt;/b&gt;을 높이기 위해 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;삽입/수정/삭제의 성능&lt;/span&gt;&lt;/b&gt;은 어느정도 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;포기한다는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;특히 역 인덱스는 위와 같이 몇 개의 정해진 키워드를 바탕으로 검색하는 것이 아니고 많은 키워드를 다뤄야 할 경우도 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 경우에는 문서를 띄어쓰기로 분리하고 대/소문자를 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;구분하며, 한글/영어를 구분하고, 불용어를 처리하고, 중복된 단어를 처리하는 등&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여러 추가적인 많은 작업이 필요합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;  정리&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번 글에서는 역 인덱스를 통해 필터링 검색 기능이 어떻게 구현되었는 지 그 내부 방식을 알아보려고 하였습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;물론 위와 같은 작업을 애플리케이션 개발자가 일일이 하지 않고 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Elasticsearch&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;와 같은 검색엔진을 사용하는 경우가 훨씬 많을 것 같지만&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;그래도 알고 써야 더 잘 쓴다는 마음으로.. 정리해 보았습니다!&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예제 코드&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1727850947829&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - 320Hwany/inverted-index: 역 인덱스 (inverted index) 방식 테스트&quot; data-og-description=&quot;역 인덱스 (inverted index) 방식 테스트. Contribute to 320Hwany/inverted-index development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/320Hwany/inverted-index&quot; data-og-url=&quot;https://github.com/320Hwany/inverted-index&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dwyPHc/hyXayFpGR2/dqzr4UBkpW9CqLntbxVeC1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bw0tyO/hyXaDzTVpA/5CJuxjeKQYze7LCYLBZLlk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/320Hwany/inverted-index&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/320Hwany/inverted-index&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dwyPHc/hyXayFpGR2/dqzr4UBkpW9CqLntbxVeC1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/bw0tyO/hyXaDzTVpA/5CJuxjeKQYze7LCYLBZLlk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - 320Hwany/inverted-index: 역 인덱스 (inverted index) 방식 테스트&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;역 인덱스 (inverted index) 방식 테스트. Contribute to 320Hwany/inverted-index development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database</category>
      <author>320Hwany</author>
      <guid isPermaLink="true">https://320hwany.tistory.com/122</guid>
      <comments>https://320hwany.tistory.com/122#entry122comment</comments>
      <pubDate>Wed, 2 Oct 2024 15:31:17 +0900</pubDate>
    </item>
    <item>
      <title>Java Virtual Thread 에 대해 알아보자</title>
      <link>https://320hwany.tistory.com/121</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Java 21 LTS 버전부터 정식으로 Virtual Thread가 포함되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Virtual Thread 란 무엇일까요?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;오라클 공식 문서에 있는 표현은 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;&quot;Virtual threads are lightweight threads that reduce the effort of writing, maintaining, and debugging &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;high-throughput concurrent applications.&quot;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;간단하게 해석해보면 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;처리량이 높은 애플리케이션&lt;/span&gt;&lt;/b&gt;을 만들 때 유지 관리 및 디버깅의 노력을 줄여주는 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;경량 쓰레드&lt;/span&gt;&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;경량 쓰레드라는 말을 처음 보았을 때 CPU 코어를 더 잘 활용하기 위해 쓰레드 개념이 나온 것처럼 경량 쓰레드가 나온 이유도&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;뭔가 비슷한 이유가 아닐까 라는 생각만 들었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번 글에서는 다음과 같은 순서로 다루겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. Virtual Thread의 특징 - 생성/실행 속도 빠름, 스케줄링 방식, Non-Blocking I/O&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 기존 쓰레드 풀 방식의 병목 지점과 이를 개선한 방식&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. 성능 테스트&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;4. Virtual Thread의 단점&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  &lt;b&gt;Virtual Thread는 생성/실행 속도가 빠름&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오전 9.44.22.png&quot; data-origin-width=&quot;1074&quot; data-origin-height=&quot;756&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2DVkz/btsJhknGh4c/yBxF19O5uk9RRtaq6iiRj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2DVkz/btsJhknGh4c/yBxF19O5uk9RRtaq6iiRj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2DVkz/btsJhknGh4c/yBxF19O5uk9RRtaq6iiRj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2DVkz%2FbtsJhknGh4c%2FyBxF19O5uk9RRtaq6iiRj1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;352&quot; data-filename=&quot;스크린샷 2024-08-28 오전 9.44.22.png&quot; data-origin-width=&quot;1074&quot; data-origin-height=&quot;756&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일반 쓰레드를 10만 개 생성할 때는 4005ms 시간이 걸렸습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오전 9.45.13.png&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;746&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgGFWq/btsJgC3xzqw/fHnjXNYRUzznJqcoXnsd0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgGFWq/btsJgC3xzqw/fHnjXNYRUzznJqcoXnsd0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgGFWq/btsJgC3xzqw/fHnjXNYRUzznJqcoXnsd0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgGFWq%2FbtsJgC3xzqw%2FfHnjXNYRUzznJqcoXnsd0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;353&quot; data-filename=&quot;스크린샷 2024-08-28 오전 9.45.13.png&quot; data-origin-width=&quot;1058&quot; data-origin-height=&quot;746&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가상 쓰레드를 10만 개 생성할 때는 166ms 시간이 걸렸습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;생성/실행 속도에서 약 20배 이상의 큰 차이를 보입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;왜 이렇게 차이가 나는 것일까요?&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Thread의 start() 메소드와 관련이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오전 9.56.07.png&quot; data-origin-width=&quot;834&quot; data-origin-height=&quot;366&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B4PsJ/btsJiLxtYa7/pppjyca6JkTXqtaAD7OBb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B4PsJ/btsJiLxtYa7/pppjyca6JkTXqtaAD7OBb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B4PsJ/btsJiLxtYa7/pppjyca6JkTXqtaAD7OBb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB4PsJ%2FbtsJiLxtYa7%2Fpppjyca6JkTXqtaAD7OBb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;219&quot; data-filename=&quot;스크린샷 2024-08-28 오전 9.56.07.png&quot; data-origin-width=&quot;834&quot; data-origin-height=&quot;366&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오전 9.56.23.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;88&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GRsai/btsJhLSNjUQ/7Ft0XrbaAsBfnOtTMFB7y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GRsai/btsJhLSNjUQ/7Ft0XrbaAsBfnOtTMFB7y0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GRsai/btsJhLSNjUQ/7Ft0XrbaAsBfnOtTMFB7y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGRsai%2FbtsJhLSNjUQ%2F7Ft0XrbaAsBfnOtTMFB7y0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;53&quot; data-filename=&quot;스크린샷 2024-08-28 오전 9.56.23.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;88&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일반 쓰레드는 실행할 때 start0() JNI 메소드를 호출하게 됩니다. 이 부분은 자바 코드가 아니라 C나 C++로 되어 있는 코드로&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;시스템 콜&lt;/span&gt;&lt;/b&gt;을 호출할 때 사용됩니다. 이 과정에서 커널 영역에 접근하여 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;운영체제 수준&lt;/span&gt;&lt;/b&gt;에서 관리되는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;바로 이 부분에서 많은 시간이 소요되는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Virtual Thread의 start() 메소드는 어떨까요? (Virtual Thread의 상속을 따라가보면 Thread 클래스가 존재)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오전 10.01.23.png&quot; data-origin-width=&quot;1032&quot; data-origin-height=&quot;1298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DqN1l/btsJgRe5ZMu/7gPFdWcWQ0njxCigKEuKkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DqN1l/btsJgRe5ZMu/7gPFdWcWQ0njxCigKEuKkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DqN1l/btsJgRe5ZMu/7gPFdWcWQ0njxCigKEuKkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDqN1l%2FbtsJgRe5ZMu%2F7gPFdWcWQ0njxCigKEuKkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;629&quot; data-filename=&quot;스크린샷 2024-08-28 오전 10.01.23.png&quot; data-origin-width=&quot;1032&quot; data-origin-height=&quot;1298&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;뭔가 보이는 코드는 더 복잡해진 것 같지만 JNI를 호출하는 코드는 없습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;대신에 submitRunContinuation() 이라는 메소드를 볼 수 있는데 이 메소드를 살펴보면 &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오전 10.03.24.png&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;364&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5mBTW/btsJgzzaybC/Bdbnhm01Y2LKEaJhzlp5N1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5mBTW/btsJgzzaybC/Bdbnhm01Y2LKEaJhzlp5N1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5mBTW/btsJgzzaybC/Bdbnhm01Y2LKEaJhzlp5N1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5mBTW%2FbtsJgzzaybC%2FBdbnhm01Y2LKEaJhzlp5N1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;243&quot; data-filename=&quot;스크린샷 2024-08-28 오전 10.03.24.png&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;364&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;어떤 스케줄러에 continuation을 전달해줍니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;(continuation은 현재 실행 상태를 저장하고 이후에 그 지점에서 다시 실행할 수 있도록 해주는 프로그래밍 개념)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기서 알 수 있는 점은 Virtual Thread는 생성/실행에 OS가 개입하지 않고 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;JVM 수준&lt;/span&gt;&lt;/b&gt;에서 관리된다는 것을 알 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그렇기 때문에 생성/실행 속도에서 큰 차이를 보였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또한 생성/실행 속도 뿐만 아니라 차지하는 메모리 크기도 약 100배 정도 차이난다고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  &lt;b&gt;Virtual Thread의 컨텍스트 스위칭은 OS 수준에서 발생하지 않음&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1번에 이어서 스케줄러에 대해 알아보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2024-08-28 오전 10.12.57.png&quot; data-origin-width=&quot;1746&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cia2ZX/btsJg9GAzZG/j7BYkoDKLF4ip7aEI3yuz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cia2ZX/btsJg9GAzZG/j7BYkoDKLF4ip7aEI3yuz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cia2ZX/btsJg9GAzZG/j7BYkoDKLF4ip7aEI3yuz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcia2ZX%2FbtsJg9GAzZG%2Fj7BYkoDKLF4ip7aEI3yuz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;338&quot; data-filename=&quot;edited_스크린샷 2024-08-28 오전 10.12.57.png&quot; data-origin-width=&quot;1746&quot; data-origin-height=&quot;844&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;필드가 되게 많은데 스케줄러와 관련된 scheduler 필드와 DEFAULT_SCHEDULER를 살펴보면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Executor 인터페이스의 구현체인 ForkJoinPool로 이루어져 있음을 알 수 있고 또한 static 변수라는 점에서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;모든 Virtual Thread가 하나의 스케줄러를 사용한다는 것을 알 수 있습니다.&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ForkJoinPool은 플랫폼 쓰레드(우리가 알고 있는 쓰레드)를 관리하고 Virtual 쓰레드에게 작업을 분배하는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오전 10.29.52.png&quot; data-origin-width=&quot;1390&quot; data-origin-height=&quot;516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/92zBs/btsJigxR0UO/5QefBcM5uOIJY9hEMafVKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/92zBs/btsJigxR0UO/5QefBcM5uOIJY9hEMafVKK/img.png&quot; data-alt=&quot;우아한 기술 블로그 - Java의 미래, Virtual Thread&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/92zBs/btsJigxR0UO/5QefBcM5uOIJY9hEMafVKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F92zBs%2FbtsJigxR0UO%2F5QefBcM5uOIJY9hEMafVKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1390&quot; height=&quot;516&quot; data-filename=&quot;스크린샷 2024-08-28 오전 10.29.52.png&quot; data-origin-width=&quot;1390&quot; data-origin-height=&quot;516&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;우아한 기술 블로그 - Java의 미래, Virtual Thread&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;기존 쓰레드 방식에서 OS 쓰레드는 플랫폼쓰레드와 1 : 1로 대응되며 OS 쓰레드는 OS 레벨에서 스케줄링됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Virtual 쓰레드 방식은 &lt;span style=&quot;text-align: start;&quot;&gt;OS 쓰레드는 플랫폼쓰레드와 1 : 1로 대응되지만 플랫폼쓰레드와 Virtual 쓰레드는&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;1 : N 으로 대응되며 이러한 스케줄링은 OS 레벨이 아닌 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;JVM 레벨에서 ForkJoinPool로 구현된 스케줄러&lt;/span&gt;&lt;/b&gt;가 해줍니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;따라서 컨텍스트 스위칭 속도도 약 10배 정도 차이납니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;조금 더 들어가 Virtual Thread 클래스에서 DEFAULT_SCHEDULER를 생성하는 메소드는 다음과 같습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오전 10.23.02.png&quot; data-origin-width=&quot;1576&quot; data-origin-height=&quot;1596&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nmtWq/btsJhYxyH14/Y2uXk8ySAdS2KYhHA3OJv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nmtWq/btsJhYxyH14/Y2uXk8ySAdS2KYhHA3OJv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nmtWq/btsJhYxyH14/Y2uXk8ySAdS2KYhHA3OJv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnmtWq%2FbtsJhYxyH14%2FY2uXk8ySAdS2KYhHA3OJv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1576&quot; height=&quot;1596&quot; data-filename=&quot;스크린샷 2024-08-28 오전 10.23.02.png&quot; data-origin-width=&quot;1576&quot; data-origin-height=&quot;1596&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;간단하게 요약하면 property로 설정한 설정값이 있다면 사용하고 없으면 새롭게 설정해주는 코드입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기서 또 주목해볼 점이 parallelism는 일반적으로 동시에 병렬로 실행될 수 있는 작업 수를 의미합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Virtual Thread 방식은 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;CPU 코어의 개수와 근접하게 플랫폼 쓰레드&lt;/span&gt;&lt;/b&gt;를 만든다고 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  &lt;span style=&quot;text-align: start;&quot;&gt;Virtual Thread는 Non-Blocking I/O 방식으로 동작한다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Virtual Thread는 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Non-Blocking I/O&lt;/span&gt; &lt;/b&gt;방식으로 동작합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;우선 코드를 통해 확인해본 후 어떻게 동작하는 지를 알아보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;1초 대기하는 간단한 API 입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오전 10.53.20.png&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;414&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAcqPm/btsJigR9Ux7/4MYakiktkAlTf4EwFoZvU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAcqPm/btsJigR9Ux7/4MYakiktkAlTf4EwFoZvU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAcqPm/btsJigR9Ux7/4MYakiktkAlTf4EwFoZvU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAcqPm%2FbtsJigR9Ux7%2F4MYakiktkAlTf4EwFoZvU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;226&quot; data-filename=&quot;스크린샷 2024-08-28 오전 10.53.20.png&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;414&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1. Blocking I/O 로 동작하는 기존 쓰레드 방식&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;쓰레드 풀에 10개의 쓰레드를 만들어 놓고, 1초에 동시에 100개의 요청을 전송해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오전 10.54.11.png&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;366&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5Yyai/btsJg88OKlK/1buyFPskG2eP84EoxUqEQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5Yyai/btsJg88OKlK/1buyFPskG2eP84EoxUqEQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5Yyai/btsJg88OKlK/1buyFPskG2eP84EoxUqEQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5Yyai%2FbtsJg88OKlK%2F1buyFPskG2eP84EoxUqEQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;228&quot; data-filename=&quot;스크린샷 2024-08-28 오전 10.54.11.png&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;366&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;성능 테스트 툴로는 Artilley를 활용했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오전 10.54.58.png&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/liTsd/btsJiI1XKCp/Z7nAUxcxAkGAGCX5ns36Ok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/liTsd/btsJiI1XKCp/Z7nAUxcxAkGAGCX5ns36Ok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/liTsd/btsJiI1XKCp/Z7nAUxcxAkGAGCX5ns36Ok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FliTsd%2FbtsJiI1XKCp%2FZ7nAUxcxAkGAGCX5ns36Ok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;268&quot; data-filename=&quot;스크린샷 2024-08-28 오전 10.54.58.png&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;506&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오전 10.59.57.png&quot; data-origin-width=&quot;384&quot; data-origin-height=&quot;458&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6wG2N/btsJhLSOJhF/sQNzq2UJQoTgVcEtsStAA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6wG2N/btsJhLSOJhF/sQNzq2UJQoTgVcEtsStAA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6wG2N/btsJhLSOJhF/sQNzq2UJQoTgVcEtsStAA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6wG2N%2FbtsJhLSOJhF%2FsQNzq2UJQoTgVcEtsStAA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;298&quot; data-filename=&quot;스크린샷 2024-08-28 오전 10.59.57.png&quot; data-origin-width=&quot;384&quot; data-origin-height=&quot;458&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;10개의 쓰레드로 100개의 요청을 처리해야 합니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가장 빠르게 처리된 쓰레드는 1006ms 였지만 가장 오래 걸린 쓰레드는 9250ms 이고 중간값은 5123.5ms로 나왔습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기서 알 수 있는 점은 기존 쓰레드 방식은 Blocking I/O 방식으로 동작한다는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;2. Non-Blocking I/O 로 동작하는 Virtual 쓰레드 방식&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Virtual Thread 활용하기 위한 설정을 추가하고 적용되지 않는 max, min-spare 설정은 지웠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오후 12.30.42.png&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/daty7e/btsJi1NH2Ly/K6mEFgmnFKOgAKIzAaPkd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/daty7e/btsJi1NH2Ly/K6mEFgmnFKOgAKIzAaPkd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/daty7e/btsJi1NH2Ly/K6mEFgmnFKOgAKIzAaPkd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdaty7e%2FbtsJi1NH2Ly%2FK6mEFgmnFKOgAKIzAaPkd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;284&quot; data-filename=&quot;스크린샷 2024-08-28 오후 12.30.42.png&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오전 11.06.14.png&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rHymS/btsJiIuaQ1E/2IchLfej9hQLq0JyqzdB7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rHymS/btsJiIuaQ1E/2IchLfej9hQLq0JyqzdB7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rHymS/btsJiIuaQ1E/2IchLfej9hQLq0JyqzdB7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrHymS%2FbtsJiIuaQ1E%2F2IchLfej9hQLq0JyqzdB7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;321&quot; data-filename=&quot;스크린샷 2024-08-28 오전 11.06.14.png&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;마찬가지로 10개의 쓰레드로 100개의 요청을 처리해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;가장 빠르게 처리된 쓰레드는 1001ms 였고 가장 오래 걸린 쓰레드는 1096ms 이고 중간값은 1005ms로 나왔습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;여기서 알 수 있는 점은  Virtual Thread 방식은 Non-Blocking I/O 방식으로 동작한다는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  &lt;span style=&quot;text-align: start;&quot;&gt;기존 쓰레드 풀 방식의 병목 지점&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오전 11.34.27.png&quot; data-origin-width=&quot;3452&quot; data-origin-height=&quot;1156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGD3Pi/btsJhkOFiUg/mwjku08Sukj61g0LXNTp1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGD3Pi/btsJhkOFiUg/mwjku08Sukj61g0LXNTp1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGD3Pi/btsJhkOFiUg/mwjku08Sukj61g0LXNTp1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGD3Pi%2FbtsJhkOFiUg%2Fmwjku08Sukj61g0LXNTp1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;860&quot; height=&quot;288&quot; data-filename=&quot;스크린샷 2024-08-28 오전 11.34.27.png&quot; data-origin-width=&quot;3452&quot; data-origin-height=&quot;1156&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Acceptor: 클라이언트의 소켓 연결을 받아들임.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;PollerEventQueue: Acceptor가 받아들인 소켓 연결을 큐에 저장.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Poller: 큐에서 소켓을 가져와, 요청을 처리할 준비가 되었는지 확인.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;TaskQueue: 요청을 처리하기 위한 작업을 대기열에 저장.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Thread Pool: 대기 중인 작업을 처리하여 클라이언트의 요청에 응답.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그림의 톰캣 8.5 이후 버전부터는 BIO Connector가 아닌 NIO Connector 입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Acceptor와 Poller 쓰레드는 Non-Blocking 방식으로 동작하여 Connection per Request가 아닌 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Thread per Request 방식&lt;/span&gt;&lt;/b&gt;으로&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;동작할 수 있게 하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그림에서 초록색 부분은 성능에 크게 문제되는 부분이 없습니다. 병목 지점은 빨간색 부분입니다. (사실 이것도 크게 문제는 되지 않음)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;플랫폼 쓰레드와 OS 쓰레드가 1 : 1로 대응되는데 OS 쓰레드는 CPU를 할당 받아 실행됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만약에 I/O 작업이 발생하면 해당 OS 쓰레드는 CPU를 할당 받을 수 없게 되고 다른 OS 쓰레드에게 CPU 소유권이 넘어가게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 시점에 플랫폼 쓰레드, OS 쓰레드가 같이 Blocking 되는 것입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;즉, 쓰레드가 작업을 실행하는 DB I/O나 네트워크 I/O 가 발생하게 되면 쓰레드는 Blocking 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  Virtual Thread 가 개선한 병목 지점&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오후 12.08.59.png&quot; data-origin-width=&quot;2344&quot; data-origin-height=&quot;868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8Z18r/btsJhDAFNxp/eeu3OutDjgF0IKtjbyFqV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8Z18r/btsJhDAFNxp/eeu3OutDjgF0IKtjbyFqV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8Z18r/btsJhDAFNxp/eeu3OutDjgF0IKtjbyFqV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8Z18r%2FbtsJhDAFNxp%2Feeu3OutDjgF0IKtjbyFqV0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2344&quot; height=&quot;868&quot; data-filename=&quot;스크린샷 2024-08-28 오후 12.08.59.png&quot; data-origin-width=&quot;2344&quot; data-origin-height=&quot;868&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Virtual Thread 방식은 이러한 I/O 작업을 진행할 때 플랫폼 쓰레드가 Blocking 되지 않게 해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그림처럼 Virtual Thread 1, Virtual Thread 2가 플랫폼 Thread 1과 대응되고 있다고 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Virtual Thread 1이 작업을 수행하다가 DB I/O나 네트워크 I/O가 발생하면 Virtual Thread 1는 work steal queue에서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; pop 된 후 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;park() 메소드에 의해 힙 메모리로 돌아갑니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;I/O 작업이 끝나면 다시 unpark() 메소드를 호출하여 work steal queue에 들어간 후 플랫폼 쓰레드에 할당되어 사용될 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또 park() 메소드를 호출한 후 힙 메모리에서 더 이상 사용되지 않으면 GC에 의해 삭제됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이렇게 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Virtual Thread가 I/O 작업을 수행할 때 플랫폼 쓰레드와 OS 쓰레드는 Blocking 되지 않고&lt;/span&gt;&lt;/b&gt; 다른 Virtual Thread의&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;작업을 처리할 수 있게 됩니다. 즉 Non-Blocking I/O 방식으로 작업을 처리할 수 있게 되는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt; &amp;nbsp; 성능 테스트 해보자&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;테스트 해볼 API는 단순합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;쓰레드 정보를 출력하고 0.3초 sleep 하도록 하여 각각의 방식이 얼마만큼 처리할 수 있는지 확인해 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오후 12.26.29.png&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;456&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b85Bgw/btsJiGwoNvS/3qETCVKgYT1LyzNC3HCWA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b85Bgw/btsJiGwoNvS/3qETCVKgYT1LyzNC3HCWA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b85Bgw/btsJiGwoNvS/3qETCVKgYT1LyzNC3HCWA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb85Bgw%2FbtsJiGwoNvS%2F3qETCVKgYT1LyzNC3HCWA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;238&quot; data-filename=&quot;스크린샷 2024-08-28 오후 12.26.29.png&quot; data-origin-width=&quot;958&quot; data-origin-height=&quot;456&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;해당 API를 1초에 1000번씩 10초동안 총 10000번을 호출해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1. Virtual Thread 방식&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오후 12.30.42.png&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;454&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/daty7e/btsJi1NH2Ly/K6mEFgmnFKOgAKIzAaPkd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/daty7e/btsJi1NH2Ly/K6mEFgmnFKOgAKIzAaPkd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/daty7e/btsJi1NH2Ly/K6mEFgmnFKOgAKIzAaPkd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdaty7e%2FbtsJi1NH2Ly%2FK6mEFgmnFKOgAKIzAaPkd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;284&quot; data-filename=&quot;스크린샷 2024-08-28 오후 12.30.42.png&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;454&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오후 12.28.45.png&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgkbjS/btsJiaq9SBu/k4hnD1HtJ1tvU61Yyl5V7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgkbjS/btsJiaq9SBu/k4hnD1HtJ1tvU61Yyl5V7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgkbjS/btsJiaq9SBu/k4hnD1HtJ1tvU61Yyl5V7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgkbjS%2FbtsJiaq9SBu%2Fk4hnD1HtJ1tvU61Yyl5V7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;287&quot; data-filename=&quot;스크린샷 2024-08-28 오후 12.28.45.png&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;448&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;10000개의 요청 모두 성공했고 p99가 461ms 정도 나옵니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또 쓰레드 정보를 출력해보았을 때 Virtual Thread는 매 요청마다 새로 생성되는 것으로 보이지만 플랫폼 쓰레드는 총 8개만 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;존재하였습니다. 아마도 저의 M1 맥북의 CPU 코어가 8개여서 그런 것 같다고 유추하고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;2. 기존 쓰레드 풀 방식&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;마찬가지로&amp;nbsp;&lt;span style=&quot;text-align: start;&quot;&gt;해당 API를 1초에 1000번씩 10초동안 총 10000번을 호출해보겠습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그런데 이번에는 기존 쓰레드 풀 방식을 사용하는데 Virtual Thread가 사용한 만큼의 플랫폼 쓰레드를 사용하기 위해&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;8개로 설정해주었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오후 12.38.31.png&quot; data-origin-width=&quot;468&quot; data-origin-height=&quot;352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AFFAK/btsJh96SGhH/zpUMuyhLR1WFKmw2TJXkk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AFFAK/btsJh96SGhH/zpUMuyhLR1WFKmw2TJXkk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AFFAK/btsJh96SGhH/zpUMuyhLR1WFKmw2TJXkk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAFFAK%2FbtsJh96SGhH%2FzpUMuyhLR1WFKmw2TJXkk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;226&quot; data-filename=&quot;스크린샷 2024-08-28 오후 12.38.31.png&quot; data-origin-width=&quot;468&quot; data-origin-height=&quot;352&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오후 12.42.03.png&quot; data-origin-width=&quot;352&quot; data-origin-height=&quot;538&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpuaEa/btsJiJfCZpo/CEQZKFGnE4XvnKdeXaQQe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpuaEa/btsJiJfCZpo/CEQZKFGnE4XvnKdeXaQQe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpuaEa/btsJiJfCZpo/CEQZKFGnE4XvnKdeXaQQe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpuaEa%2FbtsJiJfCZpo%2FCEQZKFGnE4XvnKdeXaQQe1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;382&quot; data-filename=&quot;스크린샷 2024-08-28 오후 12.42.03.png&quot; data-origin-width=&quot;352&quot; data-origin-height=&quot;538&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;처리 시간이 오래 걸릴 뿐만 아니라 10000개 요청 중에서 264개만 성공하였습니다. accept-count의 수가 문제일 수도 있어서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 늘려보았지만 비슷한 결과가 나왔습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;내장 톰캣에 HTTP 연결 타임아웃은 설정하였지만 운영체제, 네트워크 스택에서 설정하는 TCP 소켓&amp;nbsp;&lt;/span&gt;커넥션 타임 아웃에 걸려서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;나머지 요청들이 실패되는 것으로 유추하였습니다. 같은 수의 플랫폼 쓰레드를 사용한다면 차이가 많이 나는군요...&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;쓰레드 개수를 100개로 늘렸지만 여전히 10000개의 요청 중에서 4800개만 성공하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오후 12.46.15.png&quot; data-origin-width=&quot;366&quot; data-origin-height=&quot;540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAXfl1/btsJihjm5Rp/2bQVxB7SFJERyAptckYxB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAXfl1/btsJihjm5Rp/2bQVxB7SFJERyAptckYxB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAXfl1/btsJihjm5Rp/2bQVxB7SFJERyAptckYxB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAXfl1%2FbtsJihjm5Rp%2F2bQVxB7SFJERyAptckYxB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;369&quot; data-filename=&quot;스크린샷 2024-08-28 오후 12.46.15.png&quot; data-origin-width=&quot;366&quot; data-origin-height=&quot;540&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;쓰레드 개수를 200개로 늘렸을 때는 10000개의 요청이 모두 성공하였고 p99는 5600ms가 나왔습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오후 12.55.14.png&quot; data-origin-width=&quot;352&quot; data-origin-height=&quot;452&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxB2KN/btsJiKrYhQi/WSyQ9m6AqUPBdyTHjKnP11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxB2KN/btsJiKrYhQi/WSyQ9m6AqUPBdyTHjKnP11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxB2KN/btsJiKrYhQi/WSyQ9m6AqUPBdyTHjKnP11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxB2KN%2FbtsJiKrYhQi%2FWSyQ9m6AqUPBdyTHjKnP11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;321&quot; data-filename=&quot;스크린샷 2024-08-28 오후 12.55.14.png&quot; data-origin-width=&quot;352&quot; data-origin-height=&quot;452&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;쓰레드 개수를 300개로 늘렸을 때는 10000개 요청이 모두 성공하였고 p99가 821ms가 나왔습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오후 12.59.37.png&quot; data-origin-width=&quot;356&quot; data-origin-height=&quot;456&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b30uIv/btsJgSkVXAF/f1gp7Q0K6HBkaKcYJ74ZGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b30uIv/btsJgSkVXAF/f1gp7Q0K6HBkaKcYJ74ZGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b30uIv/btsJgSkVXAF/f1gp7Q0K6HBkaKcYJ74ZGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb30uIv%2FbtsJgSkVXAF%2Ff1gp7Q0K6HBkaKcYJ74ZGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;320&quot; data-filename=&quot;스크린샷 2024-08-28 오후 12.59.37.png&quot; data-origin-width=&quot;356&quot; data-origin-height=&quot;456&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;쓰레드가 300개여도 Virtual Thread가 2배정도 빠른 성능을 보였습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;쓰레드 풀에 쓰레드 개수를 무한정 늘릴 수가 없습니다. 쓰레드를 생성하는데 필요한 메모리가 증가할 뿐만 아니라&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;컨텍스트 스위칭 비용도 증가합니다. 요청이 많아지면 소켓 연결만 해도 CPU 리소스를 꽤 많이 먹었는데 이러한 &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;컨텍스트 스위칭&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;비용도 증가하면 CPU 리소스를 더 많이 먹게됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;따라서 적절한 모니터링을 통해 쓰레드 개수를 튜닝해야 할 것 같습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;Virtual Thread 방식은 어느 정도의 요청을 더 받을 수 있는 지 알아보기 위해 10초동안 20000개의 요청을 보내보았습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오후 1.05.17.png&quot; data-origin-width=&quot;368&quot; data-origin-height=&quot;586&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OGdK0/btsJgSFipr2/MwPW09kZ3cPqIPmD15GOi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OGdK0/btsJgSFipr2/MwPW09kZ3cPqIPmD15GOi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OGdK0/btsJgSFipr2/MwPW09kZ3cPqIPmD15GOi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOGdK0%2FbtsJgSFipr2%2FMwPW09kZ3cPqIPmD15GOi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;398&quot; data-filename=&quot;스크린샷 2024-08-28 오후 1.05.17.png&quot; data-origin-width=&quot;368&quot; data-origin-height=&quot;586&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;20000개 중에 16782개를 처리하였고 p99는 611.7ms이 나왔습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그리고 CPU 사용량 경고가 뜬 것으로 보아 확실히 요청이 많아지면 소켓 연결만 해도 CPU 리소스를 많이 먹는 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-28 오후 1.06.12.png&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K8lU8/btsJi9Zg53o/YoEBkUhMyMAkpvX4UIFA3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K8lU8/btsJi9Zg53o/YoEBkUhMyMAkpvX4UIFA3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K8lU8/btsJi9Zg53o/YoEBkUhMyMAkpvX4UIFA3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK8lU8%2FbtsJi9Zg53o%2FYoEBkUhMyMAkpvX4UIFA3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;97&quot; data-filename=&quot;스크린샷 2024-08-28 오후 1.06.12.png&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;208&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  Virtual Thread 단점은 없을까?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;지금까지의 이야기만 보면 Virtual Thread는 무조건 써야할 것 같지만 항상 좋은 것은 아닙니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;바로 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;CPU bound 작업의 경우에는 적합하지 않다는 것&lt;/span&gt;&lt;/b&gt;입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;오라클 공식 문서에는 다음과 같이 언급하고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&quot;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;Virtual threads are not faster threads; they do not run code any faster than platform threads. &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;They exist to provide scale (higher throughput), not speed (lower latency).&quot;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;정리하면 Virtual Threads는 빠른 쓰레드가 아니라 처리량이 많은 상황에 적합한 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이유를 생각해보면 플랫폼 쓰레드와 Virtual 쓰레드가 연결되는 과정이 추가되기 때문에 조금 더 느려질 수 있는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;그래서 CPU를 많이 사용하는 작업의 경우에는 Virtual 쓰레드를 사용하지 않고 그냥 플랫폼 쓰레드와 OS 쓰레드만 1 : 1로 연결하여&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;사용하는 것이 더 빠를 수 있는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;또 Virtual Thread는 아직 한계가 있는데 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;synchronized&lt;/span&gt;&lt;/b&gt;와 같은 키워드를 사용할 때 Virtual Thread가 플랫폼 쓰레드, OS 쓰레드와&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;함께 같이 Blocking 되어 성능이 제대로 나오지 않을 수 있습니다. 위에서 진행한 성능 테스트는 해당 키워드가 없었기 때문에 높은 성능이&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;나왔지만 사용하는 DB의 JDBC driver가 내부적으로 &lt;span style=&quot;text-align: start;&quot;&gt;synchronized을 사용하면 성능이 제대로 나오지 않게됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서 &lt;span style=&quot;text-align: start;&quot;&gt;synchronized 키워드가 아닌 Lock을 사용할 필요가 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;최근에 나온 MySQL 9.xx 버전 부터는 제공하는 JDBC driver가 &lt;span style=&quot;text-align: start;&quot;&gt;synchronized에서 ReentrantLock을 사용하는 방식으로 수정하여&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;이 부분을 개선한 것으로 알고 있습니다. 이제 Spring Boot에도 적용이 된다면 Virtual Thread를 더 많이 사용하게 될 것 같습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;마지막으로 Virtual Thread 방식은 기존 쓰레드 풀 방식과 다르게 쓰레드 생성에 한계를 두지 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하드웨어 성능이 받쳐주는 것이 보장된다면 계속 생성됩니다. 따라서 하드웨어 성능과 관련지어 모니터링 해야 하며&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DB 커넥션 수와 같이 다른 이유로 병목이 될 수도 있으며 이러한 부분도 같이 체크해야 할 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;참고한 자료&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://docs.oracle.com/en/java/javase/20/core/virtual-threads.html#GUID-DC4306FC-D6C1-4BCC-AECE-48C32C1A8DAA&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.oracle.com/en/java/javase/20/core/virtual-threads.html#GUID-DC4306FC-D6C1-4BCC-AECE-48C32C1A8DAA&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://techblog.woowahan.com/15398/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://techblog.woowahan.com/15398/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://findstar.pe.kr/2023/04/17/java-virtual-threads-1/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://findstar.pe.kr/2023/04/17/java-virtual-threads-1/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://www.youtube.com/watch?v=BZMZIM-n4C0&amp;amp;t=2555s&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=BZMZIM-n4C0&amp;amp;t=2555s&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://velog.io/@sihyung92/how-does-springboot-handle-multiple-requests&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@sihyung92/how-does-springboot-handle-multiple-requests&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://www.youtube.com/watch?v=srpOD6WIasM&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=srpOD6WIasM&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <author>320Hwany</author>
      <guid isPermaLink="true">https://320hwany.tistory.com/121</guid>
      <comments>https://320hwany.tistory.com/121#entry121comment</comments>
      <pubDate>Wed, 28 Aug 2024 12:56:42 +0900</pubDate>
    </item>
    <item>
      <title>스프링 빈을 싱글톤으로 유지하는 방법과 이유</title>
      <link>https://320hwany.tistory.com/120</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;싱글톤이란 클래스의 인스턴스를 하나만 생성하는 것을 말합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스프링 컨테이너가 관리하는 스프링 빈들은 기본적으로 싱글톤으로 관리됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번 글에서는 스프링에서&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt; 객체를 싱글톤으로 등록하는 방법, 싱글톤으로 관리하는 이유&lt;/span&gt;, 싱글톤 사용시 주의할 점,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;싱글톤을 직접 만드는 것의 단점과 스프링이 관리해주는 이유에 대해 알아보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  싱글톤으로 등록하는 방법&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스프링 프레임워크를 사용하다보면 크게 2가지 방법으로 스프링 빈을 등록합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;등록하는 방법부터 간단하게 살펴 보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;1. @Component 어노테이션 기반&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-21 오전 9.39.23.png&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRIwqX/btsI91VD181/mXtMvpTn58kxRjqccU9f11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRIwqX/btsI91VD181/mXtMvpTn58kxRjqccU9f11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRIwqX/btsI91VD181/mXtMvpTn58kxRjqccU9f11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRIwqX%2FbtsI91VD181%2FmXtMvpTn58kxRjqccU9f11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;124&quot; data-filename=&quot;스크린샷 2024-08-21 오전 9.39.23.png&quot; data-origin-width=&quot;542&quot; data-origin-height=&quot;168&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@Component, @Controller, @Service, @Repository와 같은 어노테이션으로 등록하고 가장 많이 사용하는 방법입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-21 오전 9.42.30.png&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZTEtb/btsJarM3aAR/RMWwsHKZig2k4UcdklYyA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZTEtb/btsJarM3aAR/RMWwsHKZig2k4UcdklYyA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZTEtb/btsJarM3aAR/RMWwsHKZig2k4UcdklYyA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZTEtb%2FbtsJarM3aAR%2FRMWwsHKZig2k4UcdklYyA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;227&quot; data-filename=&quot;스크린샷 2024-08-21 오전 9.42.30.png&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;318&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;@Component의 메타 어노테이션 중에서 @Target을 보면 대상이 클래스, 인터페이스, Enum에 붙일 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;이러한 방식으로 등록한 클래스는 컴포넌트 스캔의 대상이 되며 기본적으로 런타임 시점에 하나의 객체만 생성되고&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;스프링 컨테이너에 의해 관리됩니다.&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 개발자가 직접 생성한 클래스&lt;/b&gt;&lt;/span&gt;를 스프링 빈으로 관리하고 싶을 때 자주 사용하는 방법입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;2. @Configuration, @Bean 사용하여 등록하기&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-21 오전 9.51.43.png&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dHouZD/btsJaqtRdDB/JZBUJ46wcK0xc9dWUCd0A1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dHouZD/btsJaqtRdDB/JZBUJ46wcK0xc9dWUCd0A1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dHouZD/btsJaqtRdDB/JZBUJ46wcK0xc9dWUCd0A1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdHouZD%2FbtsJaqtRdDB%2FJZBUJ46wcK0xc9dWUCd0A1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;233&quot; data-filename=&quot;스크린샷 2024-08-21 오전 9.51.43.png&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;@Configuration, @Bean을 사용해서 스프링 빈으로 등록할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-21 오전 9.53.42.png&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nx4mm/btsI9ReF8gW/KoLfDkKbPF1wR5gkjETac1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nx4mm/btsI9ReF8gW/KoLfDkKbPF1wR5gkjETac1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nx4mm/btsI9ReF8gW/KoLfDkKbPF1wR5gkjETac1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnx4mm%2FbtsI9ReF8gW%2FKoLfDkKbPF1wR5gkjETac1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;311&quot; data-filename=&quot;스크린샷 2024-08-21 오전 9.53.42.png&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;@Tartget을 보면 메소드와 어노테이션에 적용할 수 있다는 것을 알 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 방식은 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;개발자가 직접 조정할 수 없는&lt;/span&gt;&lt;/b&gt; 외부 라이브러리와 같이 이미 등록되어 있는 클래스를 사용하기 위해 적용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;물론 보통 이렇다는 것이고 개발자가 생성한 클래스도 이러한 방식으로 생성할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  싱글톤으로 관리하는 이유&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그렇다면 왜 기본적으로 싱글톤으로 관리하는 것일까요?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;만약 싱글톤으로 생성하지 않고 매 요청마다 객체를 생성한다면 어떨까요?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;요청을 처리하는 비용보다 객체를 생성하고 파괴하는 비용이 더 클수도 있으며 많은 객체들이 생성되면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;불필요하게 메모리를 많이 사용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;요청을 처리하기 위한 생성한 클래스들을 스프링 빈으로 관리하여 싱글톤으로 생성한다면 여러 요청이 오더라도&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;같은 객체를 사용하여 처리합니다. 이렇게 하면 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;적은 메모리를 사용하여 많은 요청을 처리&lt;/span&gt;&lt;/b&gt;할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  싱글톤 객체 사용시 주의할 점&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;싱글톤은 적은 메모리를 사용하여 많은 요청을 처리할 수 있도록 하였지만 주의해야할 점이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;멀티 쓰레드 환경에서 싱글톤 객체를 사용하게 되므로 동시성 문제에 대해 항상 주의해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-21 오전 10.19.42.png&quot; data-origin-width=&quot;986&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dVJp5L/btsJamLKJDn/UlLquPHTD6JKnPbwsWFOBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dVJp5L/btsJamLKJDn/UlLquPHTD6JKnPbwsWFOBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dVJp5L/btsJamLKJDn/UlLquPHTD6JKnPbwsWFOBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdVJp5L%2FbtsJamLKJDn%2FUlLquPHTD6JKnPbwsWFOBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;245&quot; data-filename=&quot;스크린샷 2024-08-21 오전 10.19.42.png&quot; data-origin-width=&quot;986&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스프링 컨테이너는 SingletonBadExample 클래스에 대한 싱글톤 객체를 생성하고 관리합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이때 많은 요청이 동시에 들어온다면 numbers 필드를 여러 쓰레드가 동시에 접근하게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;ArrayList는 내부적으로 동기화를 보장하지 않기 때문에 이러한 경우 원하는 대로 동작하지 않을 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;그렇기 때문에 싱글톤 객체를 사용할 때는 되도록이면 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;필드 값을 가지지 않도록&lt;/span&gt;&lt;/b&gt; 해야합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이것을 싱글톤 패턴에서 Stateless (무상태) 하도록&amp;nbsp; 설계해야 한다고도 말합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;물론 java.util.concurrent 패키지의 클래스들을 활용하여 동기화를 보장할 수도 있지만 이것도 역시 한 서버 내에서만&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 적용되는 것이므로 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;가능하면 &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Stateless 하도록 하고&lt;/span&gt;&lt;/b&gt; 상태는 DB를 통해서 유지하도록 하는 것이 좋을 것 같습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;  싱글톤을 직접 만드는 것의 단점과 스프링이 관리해주는 이유&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;싱글톤으로 등록하는 방법, 이유, 주의할 점에 대해 알아보았습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;조금만 더 나아가 싱글톤을 직접 만드는 것의 단점과 스프링에서 관리해주는 이유에 대해 알아보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;싱글톤 객체를 아래와 같은 다양한 방식으로 개발자가 직접 만들 수도 있습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;- 이른 초기화를 하는 가장 단순한 방식&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-21 오전 10.48.53.png&quot; data-origin-width=&quot;1230&quot; data-origin-height=&quot;486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkhaaD/btsJaJ00jF7/lD4eYqvP8qxSriIh5VgmpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkhaaD/btsJaJ00jF7/lD4eYqvP8qxSriIh5VgmpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkhaaD/btsJaJ00jF7/lD4eYqvP8qxSriIh5VgmpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkhaaD%2FbtsJaJ00jF7%2FlD4eYqvP8qxSriIh5VgmpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;450&quot; height=&quot;178&quot; data-filename=&quot;스크린샷 2024-08-21 오전 10.48.53.png&quot; data-origin-width=&quot;1230&quot; data-origin-height=&quot;486&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 이른 초기화를 하지 않고 멀티 쓰레드 환경에서 안전한 방식&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-21 오전 10.47.05.png&quot; data-origin-width=&quot;1026&quot; data-origin-height=&quot;798&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rDcqe/btsJaRds1ng/9o61UkXHWUiPuN3GvkZqt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rDcqe/btsJaRds1ng/9o61UkXHWUiPuN3GvkZqt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rDcqe/btsJaRds1ng/9o61UkXHWUiPuN3GvkZqt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrDcqe%2FbtsJaRds1ng%2F9o61UkXHWUiPuN3GvkZqt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;311&quot; data-filename=&quot;스크린샷 2024-08-21 오전 10.47.05.png&quot; data-origin-width=&quot;1026&quot; data-origin-height=&quot;798&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- Enum을 사용하여 완전한 싱글톤을 보장하는 방식&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-21 오전 10.49.49.png&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7Yi8f/btsI84eFWHK/jtoX4vKxn2RMuGewbrS6pk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7Yi8f/btsI84eFWHK/jtoX4vKxn2RMuGewbrS6pk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7Yi8f/btsI84eFWHK/jtoX4vKxn2RMuGewbrS6pk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7Yi8f%2FbtsI84eFWHK%2FjtoX4vKxn2RMuGewbrS6pk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;124&quot; data-filename=&quot;스크린샷 2024-08-21 오전 10.49.49.png&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;182&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이렇게 만들었을 때의 문제점은 무엇일까요? 일단 코드 자체가 많아지게 됩니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이것보다 더 큰 2가지 문제점이 있는데 바로 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;의존관계 주입이 어렵다는 점&lt;/b&gt;&lt;/span&gt;과 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;테스트가 어려워진다는 점&lt;/span&gt;&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;의존 관계 주입은 스프링 프레임워크를 사용하는 가장 큰 이유 중 하나로 런타임 시점에 객체들의 관계를 주입해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SingleTon1 클래스, SingleTon2Interface 인터페이스, SingleTon2 클래스가 있다고 해봅시다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;SingleTon1 클래스에 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;SingleTon2Interface 인터페이스를 주입하고 런타임 시점에 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;SingleTon2를 사용하고 싶더라도&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;이러한 방식으로는 인터페이스에 의존하지 않고 구체적인 클래스인 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;SingleTon2에 의존할 수 밖에 없습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;- SingleTon1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-21 오전 11.04.48.png&quot; data-origin-width=&quot;1332&quot; data-origin-height=&quot;668&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uippM/btsJar0AOiv/Uu4fKlfv3bzIrm0FXFhOWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uippM/btsJar0AOiv/Uu4fKlfv3bzIrm0FXFhOWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uippM/btsJar0AOiv/Uu4fKlfv3bzIrm0FXFhOWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuippM%2FbtsJar0AOiv%2FUu4fKlfv3bzIrm0FXFhOWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;251&quot; data-filename=&quot;스크린샷 2024-08-21 오전 11.04.48.png&quot; data-origin-width=&quot;1332&quot; data-origin-height=&quot;668&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;- SingleTon2 인터페이스&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-21 오전 11.03.53.png&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;84&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZXQiZ/btsI9tkQQSj/gaNmiP3qEFYDFbcMhKHWlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZXQiZ/btsI9tkQQSj/gaNmiP3qEFYDFbcMhKHWlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZXQiZ/btsI9tkQQSj/gaNmiP3qEFYDFbcMhKHWlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZXQiZ%2FbtsI9tkQQSj%2FgaNmiP3qEFYDFbcMhKHWlK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;450&quot; height=&quot;61&quot; data-filename=&quot;스크린샷 2024-08-21 오전 11.03.53.png&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;84&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;- SingleTon2&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-21 오전 11.04.15.png&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tpk1u/btsJafzoY22/MUGk34Ib3GtrkmEOWqBrg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tpk1u/btsJafzoY22/MUGk34Ib3GtrkmEOWqBrg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tpk1u/btsJafzoY22/MUGk34Ib3GtrkmEOWqBrg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftpk1u%2FbtsJafzoY22%2FMUGk34Ib3GtrkmEOWqBrg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;218&quot; data-filename=&quot;스크린샷 2024-08-21 오전 11.04.15.png&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;498&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;싱글톤 패턴은 테스트를 하기 어렵다는 단점도 있는데 싱글톤이어서 객체를 따로 생성할 수 없기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;물론 생성자가 private이어도 리플렉션을 활용하면 객체를 생성할 수는 있지만 바람직한 방법은 아닌 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또 Enum의 경우는 리플렉션으로도 객체를 생성할 수 없는 완전한 싱글톤입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이러한 이유로 인해 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;스프링 컨테이너가 직접 객체를 관리하고 싱글톤을 유지&lt;/span&gt;&lt;/b&gt;해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스프링 컨테이너 내부에는 싱글톤 레지스트리라는 데이터 구조가 존재하며 싱글톤 빈 인스턴스를 저장하는 Map과 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;같은 형태로 존재합니다. 이 레지스트리는 각 빈의 이름, ID와 그에 해당하는 인스턴스를 매핑하여 관리합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이러한 방식으로 private 생성자로 객체 생성을 막지 않고 스프링 컨테이너를 활용할 때는 싱글톤으로 관리되도록 해주는 것입니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Spring</category>
      <author>320Hwany</author>
      <guid isPermaLink="true">https://320hwany.tistory.com/120</guid>
      <comments>https://320hwany.tistory.com/120#entry120comment</comments>
      <pubDate>Wed, 21 Aug 2024 11:29:15 +0900</pubDate>
    </item>
  </channel>
</rss>