<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>kooks.log</title>
        <link>https://velog.io/</link>
        <description>I'm kooks</description>
        <lastBuildDate>Thu, 07 May 2026 14:52:57 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>kooks.log</title>
            <url>https://velog.velcdn.com/images/kooks-dev/profile/766072c4-1bfb-4969-b1d6-aa6119f8656d/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. kooks.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/kooks-dev" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[빈 스코프]]></title>
            <link>https://velog.io/@kooks-dev/%EB%B9%88-%EC%8A%A4%EC%BD%94%ED%94%84</link>
            <guid>https://velog.io/@kooks-dev/%EB%B9%88-%EC%8A%A4%EC%BD%94%ED%94%84</guid>
            <pubDate>Thu, 07 May 2026 14:52:57 GMT</pubDate>
            <description><![CDATA[<p>스프링 빈이 스프링 컨테이너의 시작과 함께 생성되어 스프링 컨테이너가 종료될 때 까지 유지된다. 이것은 스프링 빈이 기본적으로 싱글톤 스코프로 생성되기 때문이다. </p>
<blockquote>
<p>스코프는 번역 그대로 빈이 존재할 수 있는 범위를 뜻한다.</p>
</blockquote>
<h3 id="스프링이-지원하는-스코프">스프링이 지원하는 스코프</h3>
<ul>
<li><code>싱글톤</code>: 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프</li>
<li><code>프로토타입</code>: 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프<h4 id="웹-관련-스코프">웹 관련 스코프</h4>
</li>
<li><code>reqeust</code>: 웹 요청이 들어오고 나갈때 까지 유지되는 스코프</li>
<li><code>session</code>: 웹 세션이 생성되고 종료될 때 까지 유지되는 스코프</li>
<li><code>application</code>: 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스포크</li>
<li><code>websocket</code>: 웹 소켓과 동일한 생명주기를 가지는 스코프</li>
</ul>
<h2 id="프로토타입-스코프">프로토타입 스코프</h2>
<p>싱글톤 스코프의 빈을 조회하면 스프링 컨테이너는 항상 같은 인스턴스의 스프링 빈을 반환하다.
반면에 프로토타입 스코프는 스프링 컨테이너에 조회하면 스프링 컨테이너는 항상 새로운 인스턴스를 생성해서 반환한다.</p>
<p>핵심은 스프링 컨테이너는 프로토타입 빈을 생성하고, 의존관계 주입, 초기화까지만 처리한다는 것이다. 클라이언트에 빈을 반환하고, 이후 스프링 컨테이너는 생성된 프로토타입 빈을 관리하지 않는다. 프로토타입 빈을 관리할 책임은 프로토타입 빈을 받은 클라이언트에 있다. 그래서 <code>@PreDestroy</code>같은 종료 메서드가  호출되지 않는다.</p>
<h4 id="singleton">Singleton</h4>
<pre><code class="language-bash">SingletonBean.init
singletonBean1 = hello.core.scope.PrototypeTest$SingletonBean@54504ecd
singletonBean2 = hello.core.scope.PrototypeTest$SingletonBean@54504ecd
org.springframework.context.annotation.AnnotationConfigApplicationContext -
Closing SingletonBean.destroy</code></pre>
<h4 id="prototype">Prototype</h4>
<pre><code class="language-bash">find prototypeBean1
PrototypeBean.init
find prototypeBean2
PrototypeBean.init
prototypeBean1 = hello.core.scope.PrototypeTest$PrototypeBean@13d4992d
prototypeBean2 = hello.core.scope.PrototypeTest$PrototypeBean@302f7971
org.springframework.context.annotation.AnnotationConfigApplicationContext -
Closing </code></pre>
<p>프로토타입 스코프를 보면 <code>.destroy</code> 종료 문구가 없다.  생성과 의존관계 주입 그리고 초기화까지만 관여한다는 것을 알 수 있다.</p>
<h3 id="프로토타입-스코프와-싱글톤-빈과-함께-사용시-문제점">프로토타입 스코프와 싱글톤 빈과 함께 사용시 문제점</h3>
<p>스프링은 일반적으로 싱글톤 빈을 사용하므로, 싱글톤 빈이 프로토타입 빈을 사용하게 된다. 그런데 싱글톤 빈은 생성시점엠나 의존관계 주입을 받기 때문에 프로토타입 빈이 새로 생성되기는 하지만, 싱글톤 빈과 함께 계속 유지되는 것이 문제다.</p>
<blockquote>
<p>매번 사용할 때 마다 의존관계 주입이 완료된 새로운 객체가 필요하면 사용하면 된다. 그런데 실무에서 웹 애플리케이션을 개발해보면, 싱글톤 빈으로 대부분의 문제를 해결할 수 있기 때문에 프로토타입 빈을 직접적으로 사용하는 일은 드물다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[빈 생명 주기 콜백]]></title>
            <link>https://velog.io/@kooks-dev/%EB%B9%88-%EC%83%9D%EB%AA%85-%EC%A3%BC%EA%B8%B0-%EC%BD%9C%EB%B0%B1</link>
            <guid>https://velog.io/@kooks-dev/%EB%B9%88-%EC%83%9D%EB%AA%85-%EC%A3%BC%EA%B8%B0-%EC%BD%9C%EB%B0%B1</guid>
            <pubDate>Wed, 06 May 2026 17:46:05 GMT</pubDate>
            <description><![CDATA[<p>데이터베이스 커넥션 풀, 네트워크 소켓 처럼 애플리케이션 시작 시점에 필요한 연결을 미리 해두고, 애플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면 객체의 초기화와 종료 작업이 필요하다.</p>
<p>스프링 빈은 객체를 생성하고, 의존관계 주입이 다 끝난 다음에 필요한 데이터를 사용할 수 있는 준비가 완료된다. 따라서 초기화 작업은 의존관계 주입이 모두 완료되고 난 다음에 호출해야 한다.</p>
<blockquote>
<p>객체 생성 -&gt; 의존관계 주입</p>
</blockquote>
<p>스프링 의존관계 주입이 완료되면 스프링 빈에게 콜백 메서드를 통해서 초기화 시점을 알려주는 다양한 기능을 제공한다. 또한 스프링은 스프링 컨테이너가 종료되기 직전에 소멸 콜백을 준다. 따라서 안전하게 종료 작업을 진행할 수 있다.</p>
<h4 id="스프링-빈의-이벤트-라이프-사이클">스프링 빈의 이벤트 라이프 사이클</h4>
<blockquote>
<p>스프링 컨테이너 생성 -&gt; 스프링 빈 생성 -&gt; 의존관계 주입 -&gt; 초기화 콜백 -&gt; 사용 -&gt; 소멸전 콜백 -&gt; 스프링 종료</p>
</blockquote>
<ul>
<li>초기화 콜백 : 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출</li>
<li>소멸전 콜백 : 빈이 소멸되기 직전에 호출</li>
</ul>
<p>생성자는 필수 정보를 받고, 메모리를 할당해서 객체를 생성하는 책임을 가진다. 반면에 초기화는 이렇게 생성된 값들을 활용해서 외부 커넥션을 연결하는등 무거운 동작을 수행한다.
따라서 생성자 안에서 무거운 초기화 작업을 함께 하는 것 보다 객체를 생성하는 부분과 초기화 하는 부분을 명확하게 나누는 것이 유지보수 관점에서 좋다. 물론 초기화 작업이 내부 값들만 약간 변경하는 정도로 단순한 경우에는 생성자에서 한번에 다 처리하는게 더 나을 수 있다.</p>
<blockquote>
<p>싱글톤 빈들은 스프링 컨테이너가 종료될 때 싱글톤빈들도 함께 종료되기 때문에 스프링 컨테이너가 종료되기 직전에 소멸전 콜백이 일어난다. 싱글톤 처럼 컨테이너의 시작과 종료까지 생존하는 빈도 있지만, 생명주기가 짧은 빈들도 있는데 이 빈들은 컨테이너와 무관하게 해당 빈이 종료되기 직전에 소멸전 콜백이 일어난다.</p>
</blockquote>
<blockquote>
<p>간단하게 요약
생성자
→ 필수 정보를 받아 객체를 생성하고 기본 상태를 만드는 책임
초기화 콜백
→ 객체 생성과 의존관계 주입이 끝난 뒤, 외부 연결 같은 무거운 초기화 작업을 수행
종료 콜백
→ 빈이 제거되기 직전, 외부 연결 해제나 자원 정리 작업을 수행</p>
</blockquote>
<h2 id="인터페이스-initializing-disposablebean">인터페이스 Initializing, DisposableBean</h2>
<pre><code class="language-java">public class NetworkClient implements InitializingBean, DisposableBean {

    @Override
    public void afterPropertiesSet() throws Exception {
        connect();
    }

    @Override 
    public void destroy throws Exception {
        disConnect();
    }
}</code></pre>
<ul>
<li><code>InitializingBEan</code>은 <code>afterPropertiesSet()</code> 메서드로 초기화를 지원한다.</li>
<li><code>DisposableBean</code>은 <code>destroy</code> 메서드로 소멸을 지원한다.<blockquote>
<p>참고
인터페이스를 사용하는 초기화, 종료 방법은 스프링 초창기에 나온 방법들이고, 지금은 더 나은 방법들이 있어서 거의 사용하지 않는다.</p>
</blockquote>
</li>
</ul>
<h2 id="설정-정보에-초기화-메서드-종료-메서드-지정">설정 정보에 초기화 메서드, 종료 메서드 지정</h2>
<p>설정 정보에 <code>@Bean(initMEthod = &quot;init&quot;, destroyMethod = &quot;close&quot;)</code>처럼 초기화, 소멸 메서드를 지정할 수 있다.</p>
<h2 id="postconsturct-predestroy-어노테이션-지원">@PostConsturct, @PreDestroy 어노테이션 지원</h2>
<h4 id="postconstruct-predestroy-어노테이션-특징">@PostConstruct, @PreDestroy 어노테이션 특징</h4>
<ul>
<li>최신 스프링에서 가장 권장하는 방법이다.</li>
<li>어노테이션 하나만 붙이면 되므로 편리하다.</li>
<li>패키지를 보면 <code>javax.annotation.PostConstruct</code>이다. 스프링에서 종속적인 기술이 아니라 JSR-250라는 자바 표준이다. 따라서 스프링이 아닌 다른 컨테이너에서도 동작한다.</li>
<li>컴포넌트 스캔과 잘 어울린다.</li>
<li>유일한 단점은외부 라이브러리에는 적용하지 못한다는 것이다. 외부 라이브러리를 초기화, 종료해야 하면 <code>@Bean</code>의 기능을 사용하자.</li>
</ul>
<h3 id="정리">정리</h3>
<ul>
<li>@PostConstruct, @PreDestroy 어노테이션을 사용하자</li>
<li>코드를 고칠 수 없는 외부 라이브러리를 초기화, 종료해야 하면 <code>@Bean</code>의 <code>initMethod</code>, <code>destroyMethod</code>를 사용하자.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[의존관계 자동 주입]]></title>
            <link>https://velog.io/@kooks-dev/%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84-%EC%9E%90%EB%8F%99-%EC%A3%BC%EC%9E%85</link>
            <guid>https://velog.io/@kooks-dev/%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84-%EC%9E%90%EB%8F%99-%EC%A3%BC%EC%9E%85</guid>
            <pubDate>Fri, 01 May 2026 16:08:29 GMT</pubDate>
            <description><![CDATA[<h3 id="생성자-주입">생성자 주입</h3>
<ul>
<li>생성자 호출 시점에 1번만 호출되는 것이 보장된다.</li>
<li>불편, 필수 의존관계에 사용</li>
</ul>
<pre><code class="language-java">@Component
public class OrderServiceImpl implements OrderService {
    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    @Autowired
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
           this.memberRepository = memberRepository;
           this.discountPolicy = discountPolicy;
    }   
}</code></pre>
<blockquote>
<p>생성자가 1개만 존재하면 <code>@Autowired</code>를 생략해도 자동 주입된다.</p>
</blockquote>
<h3 id="수정자-주입">수정자 주입</h3>
<ul>
<li><code>setter</code> 수정자 메서드를 통해서 의존관계를 주입하는 방법</li>
<li>선택, 변경 가능성이 있는 의존관계에 사용</li>
<li>자바빈 프로퍼티 규약의 수정자 메서드 방식을 사용하는 방법<pre><code class="language-java">@Autowired
public void setDiscountPolicy(DiscountPolicy discounPolicy) {
  this.discuntPolicy = discountPolicy;
}</code></pre>
</li>
</ul>
<h3 id="필드-주입">필드 주입</h3>
<ul>
<li><p>이름 그대로 필드에 바로 주입하는 방법</p>
</li>
<li><p>코드가 간결해서 많이 사용되지만, 외부에서 변경이 불가능해서 테스트 하기 힘든 치명적인 단점이 있다.</p>
</li>
<li><p>DI 프레임워크가 없으면 아무것도 할 수 없다.</p>
</li>
<li><p>사용하지 않는 것을 추천</p>
<pre><code class="language-java">@Component
public class OrderServiceImpl implements OrderService{

  @Autowired
  private MemberRepository memberRepository;
  @Autowired
  private DiscountPolicy discountPolicy;
}</code></pre>
</li>
<li><p>순수한 자바 테스트 코드에는 다연히 <code>@Autowired</code>가 동작하지 않는다. <code>@SpringBootTest</code>처럼 스프링 컨테이너를 테스트에 통합한 경우에만 가능하다.</p>
</li>
</ul>
<h3 id="일반-메서드-주입">일반 메서드 주입</h3>
<ul>
<li><p>일반 메서드를 통해서 주입 받을 수 있다.</p>
</li>
<li><p>한번에 여러 필드를 주입 받을 수 있다.</p>
</li>
<li><p>일반적으로 사용하지 않는다.</p>
<pre><code class="language-java">@Component
public class OrderServiceImpl implements OrderService {
  private final MemberRepository memberRepository;
  private final DiscountPolicy discountPolicy;

  @Autowired
  public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
         this.memberRepository = memberRepository;
         this.discountPolicy = discountPolicy;
  }   
}</code></pre>
</li>
</ul>
<h3 id="옵션처리">옵션처리</h3>
<ul>
<li><code>@Autowired(required=false)</code> : 자동 주입할 대상이 수정자 메서드 자체가 호출 안됨</li>
<li><code>org.springfreamework.lang.@Nullalbe</code>: 자동 주입할 대상이 없으면 <code>null</code>이 입력된다.</li>
<li><code>Optional&lt;&gt;</code> : 자동 주입할 대상이 없으면 <code>Optional.empty</code>가 입력된다.</li>
</ul>
<h2 id="생성자-주입을-선택해라">생성자 주입을 선택해라</h2>
<ul>
<li>생성자 주입을 권장한다.</li>
</ul>
<p><strong>불변</strong></p>
<ul>
<li>생성자 주입은 객체를 생성할 때 1번만 호출되므로 이후에 호출되는 일이 없다. 따라서 불변하게 설계할 수 있다.</li>
</ul>
<p><strong>누락</strong></p>
<ul>
<li><code>@Autowired</code>가 프레임워크 안에서 동작할 때는 의존관계가 없으면 오류가 발생하지만, 지금은 프레임워크 없이 순수한 자바 코드로만 단위 테스트를 수행하고 있다.</li>
</ul>
<p><strong>final</strong>
생성자 주입을 사용하면 필드에 <code>final</code>키워드를 사용할 수 있다. 그래서 생성자에서 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에 막아준다.</p>
<ul>
<li>수정자 주입을 포함한 나머지 주입 방싯은 모두 생성자 이후에 호추되므로, 필드에 <code>final</code> 키워드를 사용할 수 없다. 오직 생성자 주입 방식만 <code>final</code>키워드를 사용할 수 있다.</li>
</ul>
<h3 id="정리">정리</h3>
<ul>
<li>생성자 주입 방식을 선택하는 이유는 여러가지 있지만, 프레임워크에 의존하지 않고, 순수한 자바 언어의 특징을 잘 살리는 방법이기도 한다.</li>
<li>기본으로 생성자 주입을 사용하고, 필수 값이 아닌 경우에는 수정자 주입 방식을 옵션으로 부여하면된다. 생성자 주입과 수정자 주입을 동시에 사용할 수 있다.</li>
</ul>
<h2 id="조회-빈이-2개-이상">조회 빈이 2개 이상</h2>
<ul>
<li><code>@Autowired</code>는 타입으로 조회한다.</li>
<li>타입으로 조회하면 선택된 빈이 2개 이상일 때 문제가 발생한다.</li>
<li>DiscounPolicy의 하위 타입인 <code>FixDiscountPolicy</code>, <code>RateDiscountPolicy</code> 둘다 스프링 빈으로 선언하게 되면 <code>NoUniqueBeanDefinitionException</code> 오류가 발생한다.<pre><code class="language-bash">NoUniqueBeanDefinitionException: No qualifying bean of type
&#39;hello.core.discount.DiscountPolicy&#39; available: expected single matching bean
but found 2: fixDiscountPolicy,rateDiscountPolicy</code></pre>
</li>
</ul>
<h2 id="autowired-필드명-qualifier-primary">@Autowired 필드명, @Qualifier, @Primary</h2>
<h3 id="autowired-필드-명-매칭">@Autowired 필드 명 매칭</h3>
<ul>
<li><code>@Autowired</code>는 타입 매칭을 시도하고, 이때 여러 빈이 있으면 필드 이름, 파라미터 이름으로 빈 이름을 추가 매칭한다.</li>
<li>필드 명 매칭은 먼저 타입 매칭을 시도 하고 그 결과에 여러 빈이 있을 때 추가로 동작하는 기능이다.</li>
<li>타입 매칭의 결과가 2개 이상일 때 필드 명, 파라미터 명으로 빈 이름 매칭</li>
</ul>
<h3 id="qualifier">@Qualifier</h3>
<ul>
<li><code>@Qualifier</code>는 추가 구분자를 붙여주는 방법이다. 주입시 추가적인 방법을 제공하는 것이지 빈 이름을 변경하는 것은 아니다.</li>
</ul>
<pre><code class="language-java">@Component
@Qualifier(&quot;mainDiscountPolicy&quot;)
public class RateDiscountPolict implements DiscountPolicy{}

@Component
@Qualifier(&quot;fixDiscountPolicy&quot;)
public class FixDiscountPolict implements DiscountPolicy{}

---
//생성자 자동 주입 예시
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
                        @Qualifier(&quot;mainDiscountPolicy&quot;) DiscountPolicy discountPolicy){
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
}

//수정자 자동 주입 예시
@Autowired
public DiscountPolicy setDiscountPolicy(@Qualifier(&quot;mainDiscountPolicy&quot;) DiscountPolicy discountPolicy){
    this.discountPolicy = discountPolicy;
}</code></pre>
<ul>
<li><code>@Qualifier</code>끼리 매칭</li>
<li>빈 이름 매칭</li>
<li><code>NosuchBeanfinitionException</code> 예외 발생</li>
</ul>
<h3 id="primary">@Primary</h3>
<ul>
<li><code>@Priamry</code>는 우선순위를 정하는 방법이다. <code>@Autowired</code>시에 여러 빈이 매칭되면 <code>@Primary</code>가 우선권을 가진다.<pre><code class="language-java">@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy{}
</code></pre>
</li>
</ul>
<p>@Component
public class FixDiscountPolicy implements DiscointPolicy{}</p>
<hr>
<p>@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
                        DiscountPolicy discountPolicy){
    this.memberRepository = memberRepository;
    this.dicountPolicy = discountPolicy;
}</p>
<pre><code>
## 수동 빈 등록은 언제?
- **업무 로직 빈** -&gt; 웹을 지원하는 컨트롤러, 비즈니스 로직이 있는 서비스, 데이터 계층의 로직을 처리하는 피로지토리등이 모두 업무 로직이다. 보통 비즈니스 요구사항을 개발할 때 추가되거나 변경된다.
- **기술 지원 빈** -&gt; 기술적인 문제나 공통 관심사(AOP)를 처리할 떄 주로 사용된다. 데이터베이스 연결이나, 공통 로그처리 처럼 업무 로직을 지원하기 위한 하부 기술이나 공통 기술이다.
- 편리한 자동 기능을 기본적으로 사용하자
- 직접 등록하는 기술 지원 객체는 수동으로 등록













</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[컴포넌트 스캔]]></title>
            <link>https://velog.io/@kooks-dev/%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%8A%A4%EC%BA%94</link>
            <guid>https://velog.io/@kooks-dev/%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EC%8A%A4%EC%BA%94</guid>
            <pubDate>Thu, 30 Apr 2026 09:12:22 GMT</pubDate>
            <description><![CDATA[<ul>
<li>스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능을 제공한다.</li>
<li>의존관계도 자동으로 주입하는 <code>@Autowired</code>라는 기능도 제공한다.</li>
</ul>
<h4 id="1-componentscan">1. @ComponentScan</h4>
<ul>
<li><p>@ComponentScan은 @Component가 붙은 모든 클래스를 스프링 빈으로 등록한다.</p>
</li>
<li><p>이때 스프링 빈의 기본 이름은 클래스명을 사용하되 맨 앞글자만 소문자를 사용한다.</p>
<h4 id="2-autowired">2. Autowired</h4>
</li>
<li><p>생성자에 @Autowired를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입한다.</p>
</li>
<li><p>생성자에 파라미터가 많아도 다시 찾아서 자동으로 주입한다.</p>
<pre><code class="language-java">@Component
public class OrderServiceImpl implements OrderService {
  private final MemberRepository memberRepository;
  private final DiscountPolicy discountPolicy;

  @Autowired
  public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy){
      this.memberRepository = memberRepository;
      this.discountPolicy = discountPolicy;
  }
}</code></pre>
</li>
</ul>
<h2 id="탐색-위치와-기본-스캔">탐색 위치와 기본 스캔</h2>
<pre><code class="language-java">@ComponentScan(
    basePackage = &quot;hello.core&quot;,
)</code></pre>
<ul>
<li>basePackages : 탐색할 패키지의 시작 위치를 지정, 이 패키지를 포함해서 하위 패키지를 모두 탐색한다.</li>
<li><code>basePackages = {&quot;hello.core&quot;, &quot;hello.service&quot;}</code> 여러 시작 위치를 지정할 수도 있다.</li>
<li>basePackageClasses : 지정한 클래스의 패키지를 탐색 시작 위치로 지정한다.</li>
<li>지정하지 않으면 @ComponentScan이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다.<blockquote>
<p>스프링 부트를 사용하면 스프링 부트의 대표 시작 정보인 <code>@SpringBootApplication</code>을 이 프로젝트 시작 루트 위치에 두는 것이 관례이다.(그리고 이 설정 안에 <code>@ComponentScan</code>이 들어있다.)</p>
</blockquote>
</li>
</ul>
<h2 id="컴포넌트-스캔-기본-대상">컴포넌트 스캔 기본 대상</h2>
<ul>
<li><code>@Compnent</code> : 컴포넌트 스캔에 사용</li>
<li><code>@Controller</code> : 스프링 MVC 컨트롤러에 사용</li>
<li><code>@Service</code> : 스프링 비즈니스 로직에 사용, 특별한 기능은 따로 존재하지 않지만, 개발자들이 핵심 비즈니스 로직이 여기에 존재한다고 인식 가능</li>
<li><code>@Repository</code> : 스프링 데이터 접근 계층에 사용, 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환해준다.</li>
<li><code>@Configuration</code> : 스프링 설정 정보에서 사용<blockquote>
<p>해당 클래스의 소스 코드를 보면 <code>@Component</code>를 포함하고 있다.</p>
</blockquote>
</li>
</ul>
<h2 id="filter">Filter</h2>
<ul>
<li><code>includeFilters</code> : 컴포넌트 스캔 대상을 추가로 지정한다.</li>
<li><code>excludeFilters</code> : 컴포넌트 스캔에서 제외할 대상을 지정한다.</li>
</ul>
<h4 id="filtertype">FilterType</h4>
<ul>
<li><code>ANNOTATION</code> : 기본값, 애노테이션을 인식해서 동장
ex) <code>org.example.SomeAnnotation</code></li>
<li><code>ASSIGNABLE_TYPE</code> : 지정한 타입과 자식 타입을 인식해서 동작한다.
ex) <code>org.example.SomeClass</code></li>
<li><code>ASPECTJ</code> : AspectJ 패턴 사용
ex) <code>org.example..*Service</code></li>
<li><code>REGEX</code> : 정규표현식
ex) <code>org\.example\.Default.*</code></li>
<li><code>CUSTOM</code> : TypeFilter 이라는 인터페이스를 구현해서 처리
ex) <code>org.example.MyTypeFilter</code></li>
</ul>
<pre><code class="language-java">@ComponentScan(
    includeFilters = {
        @Filter(type = FilterType.ANNOTATION, classes = MyIncludeCompnont.class),
    },
    excludeFilters = {
        @Filter(type = FilterType.ANNOTATION, classes = MyExclueComponent.class),
        @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = BeanA.class
    }
)
</code></pre>
<blockquote>
<p>@Component 면 충분하기 때문에 거의 사용될 일은 거의 없다.
여러가지 이유로 간혹 사용할 때가 있지만 많지는 않다.
특히 스프링 부트는 컴포넌트 스캔을 기본적으로 제공하는데, 개인적으로 옵션을 변경하면서 사용하기 보다는 스프링의 기본 설정에 최대한 맞추어 사용하는 것을 권장하고, 선호하는 편이다.</p>
</blockquote>
<h2 id="중복-등록과-충돌">중복 등록과 충돌</h2>
<h4 id="자동-vs-자동-출동">자동 vs 자동 출동</h4>
<ul>
<li>컴포넌트 스캔에 의해 자동으로 빈으로 동록되는데, 그 이름이 같은 경우 스프링은 오류를 발생시킨다.</li>
<li><code>ConflictingBeanDefinitionException</code> 예외 발생</li>
</ul>
<h4 id="수동-vs-자동-출동">수동 vs 자동 출동</h4>
<ul>
<li>수동 빈과 자동 빈이 출동하게되면 수동 빈 등록이 우선권을 가진다.</li>
<li>수동 빈이 자동 빈을 오버라이딩 한다.<pre><code class="language-bash">Overriding bean definition for bean &#39;memoryMemberRepository&#39; with a different
definition: replacing</code></pre>
<blockquote>
<p>의도적으로 했다면 상관없지만, 여러 설정들이 꼬여서 이런 결과가 만들어지는 경우가 대부분이다.
그래서 최근 스프링 부트에서는 수동 빈 등록과 자동 빈 등록이 충돌나면 오류가 발생하도록 기본 값을 바꾸었다.</p>
</blockquote>
</li>
</ul>
<pre><code class="language-bash">Consider renaming one of the beans or enabling overriding by setting
spring.main.allow-bean-definition-overriding=true</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[싱글톤 컨테이너]]></title>
            <link>https://velog.io/@kooks-dev/%EC%8B%B1%EA%B8%80%ED%86%A4-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88</link>
            <guid>https://velog.io/@kooks-dev/%EC%8B%B1%EA%B8%80%ED%86%A4-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88</guid>
            <pubDate>Wed, 29 Apr 2026 14:51:30 GMT</pubDate>
            <description><![CDATA[<h2 id="싱글톤-패턴">싱글톤 패턴</h2>
<ul>
<li>클래스의 인스턴스가 1개만 생성되는 것을 보장하는 디자인 패턴</li>
<li>객체 인스턴스를 2개 이상 생성하지 못하도록 막아야 한다.</li>
<li>private 생성자를 사용해서 외부에서 임의로 new 키워드를 사용하지 못하도록 막아야한다.</li>
</ul>
<pre><code class="language-java">public class SingletonService{

    //1. static 영역에 객체를 1개만 생성
    private static final SingletonService instance = new SingletonService();

    //2. public으로 열어서 객체 인스턴스가 필요하면 이 static 메서드를 통해서만 조회하도록 허용
    public static SingletonService getInstance(){
        return instance;
    }

    //3. 생성자를 private로 선언해서 외부에서 new 키워드를 사용한 객체 생성을 못하게 막는다.
    private SingletonService(){}

    public void logic(){
        System.out.println(&quot;싱글톤 객체 로직 호출&quot;);
    }

}</code></pre>
<ol>
<li>static 영역에 객체 instance를 미리 하나 생성해서 올려둔다.</li>
<li>객체 인스턴스가 필요하면 오직 getInstance() 메서드를 통해서만 조회할 수 있다. 이 메서드를 호출하면 항상 같은 인스턴스를 반환한다.</li>
<li>1개의 객체 인스턴스만 존재하므로, 생성자를 private로 막아서 혹시라도 외부에서 new 키워드로 객체 인스턴스가 생성되는 것을 막는다.</li>
</ol>
<blockquote>
<p>싱글톤 패턴을 적용하면 고객의 요청이 올 때 마다 객체를 생성하는 것이 아니라, 이미 만들어진 객체를 공유해서 효율적으로 사용할 수 있다. </p>
</blockquote>
<h3 id="싱글톤패턴-문제점">싱글톤패턴 문제점</h3>
<ul>
<li>싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다.</li>
<li>의존관계상 클라이언트가 구체 클래스에 의존한다. -&gt; DIP를 위반한다.</li>
<li>클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높다.</li>
<li>테스트하기 어렵다.</li>
<li>내부 속성을 변경하거나 초기화하기 어렵다.</li>
<li>private 생성자로 자식 클래스를 만들기 어렵고, 유연성이 떨어진다.</li>
<li>안티패턴으로 불리기도 한다.</li>
</ul>
<hr>
<h2 id="싱글톤-컨테이너">싱글톤 컨테이너</h2>
<p>스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서, 객체 인스턴스를 싱글톤으로 관리한다.</p>
<ul>
<li>스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리한다.</li>
<li>스프링 컨테이너는 싱글톤 컨테이너 역할을 한다. 싱글톤 객체를 생성하고 관리하는 기능을 <strong>싱글톤 레지스트리</strong>라 한다.</li>
<li>싱글톤 패턴의 모든 단점을 해결하면서 객체를 싱글톤으로 유지할 수 있다.</li>
</ul>
<h3 id="주의점">주의점</h3>
<ul>
<li>객체 인스턴스를  하나만 생성해서 공유하는 싱글톤 방식은 여러 클라이언트가 하나의 같은 객체의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 상태를 유지(stateful)하게 설계하면 안된다.</li>
<li>무상태(stateless)로 설계해야 한다.</li>
<li>의존적인 필드, 값을 변경할 수 있는 필드가 있으면 안된다.</li>
</ul>
<blockquote>
<ol>
<li>무상태로 만들라는 건 필드를 아예 쓰지 말라는 뜻이 아니라, 요청마다 달라지는 값을 필드에 저장하지 말라는 뜻</li>
<li>Repository, Service, Client 같은 의존 객체를 final 필드로 주입받는 것은 괜찮</li>
<li>userId, price, currentOrder처럼 사용자별로 달라지는 값은 필드가 아니라 파라미터, 지역 변수, 반환값으로 처리해야 함</li>
</ol>
</blockquote>
<h2 id="configuration과-바이트코드-조작">@Configuration과 바이트코드 조작</h2>
<p>스프링 컨테이너는 싱글톤 레지스트리다. 따라서 스프링 빈이 싱글톤이 되도록 보장해주어야 한다. 그런데 스프링이 자바 코드까지 어떻게 하기는 어렵다. 자바 코드는 여러번 호출될  수 있다. 그래서 스프링은 클래스의 바이트코드를 조작하는 라이브러리를 사용한다.
모든 비밀은 <code>@Configutation</code>을 적용한 <code>AppConfig</code>에 있다.</p>
<ul>
<li><code>AnnotationConfigApplicationContext</code>에 파라미터로 넘긴 값은 스프링 빈으로 등록된다. 그래서 <code>AppConfig</code>도 스프링 빈이 된다.</li>
</ul>
<blockquote>
<p>프록시 적용 대상</p>
</blockquote>
<ul>
<li>&quot;스프링이 메서드 호출을 가로채야 하는 이유가 있는지&quot;로 정한다.</li>
<li>비즈니스 빈 -&gt; 객체 하나 만들고 주입하면 싱글톤 보장 끝, 기본 프록시 불필요</li>
<li><code>@Configuration</code>, <code>@Transactional</code>, <code>@Cacheable</code>, <code>@Async</code> &lt;- 프록시 대상</li>
</ul>
<h4 id="정리">정리</h4>
<ul>
<li>비즈니스 빈은 스프링 컨테이너가 객체를 한 번 생성하고, 등록된 빈을 의존성으로 주입하면 되므로 싱글톤 보장을 위한 프록시가 필요없다.</li>
<li>반면 @Configuration은 @Bean 메서드가 여러 번 직접 호출될 수 있으므로, 스프링이 프록시로 호출을 가로채 기존 싱글톤 빈을 반환</li>
<li>@Configuration이 적용된 설정 클래스의 @Bean 메서드라면, 여러 번 생성하는 코드처럼 보여도 프록시가 가로채서 이미 등록된 빈을 반환</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring ]]></title>
            <link>https://velog.io/@kooks-dev/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%EA%B3%BC-Spring</link>
            <guid>https://velog.io/@kooks-dev/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%EA%B3%BC-Spring</guid>
            <pubDate>Tue, 28 Apr 2026 12:53:13 GMT</pubDate>
            <description><![CDATA[<h2 id="spring-fremework">Spring Fremework</h2>
<ul>
<li>핵심 : Spring DI 컨테이너, AOP, 이벤트, 기타</li>
<li>웹 : Spring MVC, Spring WebFlux</li>
<li>데이터 접근 기술 : Transaction, JDBC, ORM, XML</li>
<li>기술 통합 : 캐시, 이메일, 원격접근, 스케줄링</li>
<li>테스트 : 스프링 기반 테스트 지원</li>
<li>언어 : Kotlin, Groovy</li>
</ul>
<h2 id="스프링-핵심">스프링 핵심</h2>
<ul>
<li>스프링은 자바 언어 기반의 프레임워크 </li>
<li>자바 언어의 가장 큰 특징 - 객체지향</li>
<li>스프링은 객체 지향 언어가 가진 강력한 특징을 살려내는 프레임워크</li>
<li>좋은 객체 지향 애플리케이션을 개발할 수 있게 도화주는 프레임워크</li>
</ul>
<blockquote>
<p><strong>객체 지향</strong></p>
</blockquote>
<ul>
<li><code>추상화</code>, <code>캡슐화</code>, <code>상속</code>, <code>다형성</code></li>
<li>객체 지향 프로그래밍은 여러개의 독립된 단위, 즉 &quot;객체&quot;들의 모임으로 파악하고자 하는 것이다.</li>
<li>객체는 메시지를 주고 받고, 데이터를 처리할 수 있다.</li>
<li>유연하고 용이하게 만들기 때문에 대규모 소프트웨어 개발에 많이 사용된다.</li>
</ul>
<h3 id="좋은-객체지향-설계-5가지-원칙solid">좋은 객체지향 설계 5가지 원칙(SOLID)</h3>
<ul>
<li><code>SRP</code> : 단일 책임 원칙 (Single Responsibility Principle)</li>
<li><code>OCP</code> : 개방-폐쇄 원칙 (Open-Close Principle)</li>
<li><code>LSP</code> : 리스코프 치환 원칙 (Liskov Substution Principle)</li>
<li><code>ISP</code> : 인터페이스 분리 원칙 (Interface Segregation Principle)</li>
<li><code>DIP</code> : 의존관계 역전 원칙 (Dependency Inversion Principle)</li>
</ul>
<h4 id="srp">SRP</h4>
<ul>
<li>한 클래스는 하나의 책임만을 가져야 한다.</li>
<li>변경이 있을 때 파급 효과가 적으면 단일 책임 원칙을 잘 따른 것</li>
</ul>
<h4 id="ocp">OCP</h4>
<ul>
<li>소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.</li>
<li>즉, 새로운 구현체를 추가하거나 교체할 때 기존 클라이언트 코드는 수정하지 않아야 한다는 뜻입니다.</li>
</ul>
<h4 id="lsp">LSP</h4>
<ul>
<li>객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야한다.</li>
<li>부모 타입이나 인터페이스 타입으로 사용하던 코드를, 자식 타입이나 구현체로 바꿔도 프로그램이 정상적으로 동작해야 한다.</li>
</ul>
<h4 id="isp">ISP</h4>
<ul>
<li>특정 클라이언트를 위한 여러개의 인터페이스가, 범용 인터페이스 하나보다 좋다.</li>
<li>인터페이스가 명확해지고, 대체 가능성이 높아진다.</li>
<li>클라이언트가 사용하지 않는 기능에 의존하지 않게 인터페이스를 작게 나누는것</li>
<li>하나의 큰 인터페이스에 모든 기능을 몰아넣기보다, 역할별로 인터페이스를 분리하는 것이 좋다.</li>
</ul>
<h4 id="dip">DIP</h4>
<ul>
<li>프로그래머는 &quot;추상화에 의존해야지, 구체화에 의존하면 안된다.&quot; 의존성 주입은 이 원칙을 따르는 방법중 하나다.</li>
<li>구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻</li>
</ul>
<h3 id="정리">정리</h3>
<p>스프링 이야기에 왜 객체지향 이야기가 나오나?</p>
<ul>
<li>스프링은 다음 기술 -&gt; 다형성, OCP, DIP를 가능하게 지원</li>
<li>DI 컨테이너 제공</li>
<li>클라이언트 코드의 변경없이 기능 확장</li>
<li>쉽게 부품을 교체하듯이 개발</li>
<li>모든 설계에 역할과 구현을 분리</li>
<li>애플리케이션을 언제든지 유연하게 변경할 수 있도록 만드는 것이 좋은 객체지향 설계</li>
</ul>
<hr>
<h2 id="spring-container">Spring Container</h2>
<pre><code>ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);</code></pre><ul>
<li><code>ApplicationContext</code>를 스프링 컨테이너라 하고, 인터페이스다.</li>
<li>스프링 컨테이너는 <code>XML</code>을 기반으로 만들 수 있고, 어노테이션 기반의 자바 설정 클래스로 만들 수 있다.</li>
<li>더 정확히 <code>Spring Container</code>를 부를 때 <code>BeanFactory</code>, <code>ApplicationContext</code>로 구분해서 이야기 한다. </li>
<li><code>BeanFactory</code>를 직접 사용하는 경우는 거의 없으므로 일반적으로 <code>ApplicationContext</code>를 스프링 컨테이너라고 한다.</li>
</ul>
<h3 id="spring-container-생성-과정">Spring Container 생성 과정</h3>
<ol>
<li>스프링 컨테이너 생성</li>
</ol>
<ul>
<li>생성할 때는 구성 정보를 지정해줘야 한다.</li>
</ul>
<ol start="2">
<li>스프링 빈 등록</li>
</ol>
<ul>
<li>스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보를 사용해서 스프링 빈으로 등록한다.</li>
<li>빈 이름은 메서드 이름을 사용하고 직접 부여할 수 도 있다.<blockquote>
<p>주의
빈 이름은 항상 다른 이름으로 부여해야 한다. 같은 이름을 부여하면서 다른 빈이 무시되거나, 기존 빈을 덮어버리거나 설정에 따라 오류가 발생한다.</p>
</blockquote>
</li>
</ul>
<ol start="3">
<li>스프링 빈의존관계 설정 - 준비</li>
<li>스프링 빈 의존관계 설정 - 완료</li>
</ol>
<ul>
<li>설정 정보를 참고해서 의존관계를 주입(DI)한다.</li>
</ul>
<h2 id="beanfactory와-applicationcontext">BeanFactory와 ApplicationContext</h2>
<p><code>BeanFactory</code> :</p>
<ul>
<li>스프링 컨테이너의 최상위 인터페이스</li>
<li>스프링 빈을 관리하고 조회하는 역할 담당</li>
<li>위의 대부분의 기능은 BeanFactory가 제공하는 기능</li>
</ul>
<p><code>ApplicationContext</code> :</p>
<ul>
<li><code>BeanFactory</code> 기능을 모두 상속받아서 제공</li>
<li>메시지소스를 활용한 국제화 기능</li>
<li>환경변수</li>
<li>애플리케이션 이벤트</li>
</ul>
<h3 id="다양한-설정-지원">다양한 설정 지원</h3>
<ol>
<li><p>애노테이션 기반 자바 코드 설정
AnnotationConfigApplicationContext 클래스를 사용하면서 자바 코드로된 설정 정보를 넘긴다.</p>
</li>
<li><p>XML 설정
부트로 넘거가면서 XML기반의 설정은 잘 사용하지 않는다.
아직 많은  레거시 프로젝트들이 XML로 되어 있고, 또 XML을 사용하면서 컴파일 없이 빈 설정 정보를 변경할 수 있는 장점도 있다.</p>
</li>
<li><p>Xxx 설정</p>
</li>
</ol>
<ul>
<li>기타...</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kafka Cluster]]></title>
            <link>https://velog.io/@kooks-dev/Kafka-Cluster</link>
            <guid>https://velog.io/@kooks-dev/Kafka-Cluster</guid>
            <pubDate>Fri, 28 Nov 2025 08:28:21 GMT</pubDate>
            <description><![CDATA[<h2 id="kafka">Kafka</h2>
<ul>
<li>분산 이벤트 스트리밍 플랫폼</li>
<li>대규모 데이터를 실시간으로 처리하기 위해 사용</li>
<li>고성능, 확장성, 내구성 가용성</li>
</ul>
<p><a href="https://mermaid.live/edit#pako:eNo9UMtugzAQ_BW0Z4KIsQH7kAs9JlLVY-MeLOwAamyjja0-EP9eQ6XsaWZnZ1aaBXqvDQi43f1XPyoM2flNuizN63VGr2Nv8CM7HE5Zd-28e0SbuHTbCeQw4KRB3NT9YXJIilUbh2WTJYTRWCNBJKgVfkqQbk2mWbl37y2IgDHZ0MdhfIbEWatgXiY1oLLPLRqnDXY-ugDiWLI9BMQC34mypmAVq5uGlzwB0ubwk9acFYQySpqKtpzTZs3hd39bFnXLSEVoWbacHuuUZvQUPF7-q9gbWf8A1wRWAQ"><img src="https://mermaid.ink/img/pako:eNo9UMtugzAQ_BW0Z4KIsQH7kAs9JlLVY-MeLOwAamyjja0-EP9eQ6XsaWZnZ1aaBXqvDQi43f1XPyoM2flNuizN63VGr2Nv8CM7HE5Zd-28e0SbuHTbCeQw4KRB3NT9YXJIilUbh2WTJYTRWCNBJKgVfkqQbk2mWbl37y2IgDHZ0MdhfIbEWatgXiY1oLLPLRqnDXY-ugDiWLI9BMQC34mypmAVq5uGlzwB0ubwk9acFYQySpqKtpzTZs3hd39bFnXLSEVoWbacHuuUZvQUPF7-q9gbWf8A1wRWAQ?type=png" alt=""></a>
Producer: 데이터 생산자
Comsumer: 데이터 소비자</p>
<h1 id="통신-방법">통신 방법</h1>
<h2 id="api">API</h2>
<p><a href="https://mermaid.live/edit#pako:eNo9UE1rhDAQ_SsyZxVXE2NyKBR7WdjC0mM3PQSTVemayGxCP8T_3mhh5_Q-5r2BWaBz2oCA6819dYNCn5zepE3inC8zOh06gx9JliXP52OWPSXtpXX2HqaoSrstQgo9jhrEVd3uJoXoTGrjsGy2BD-YyUgQEWqFnxKkXWNoVvbduQmExxBj6EI_PErCrJU3L6PqUU0PFY3VBlsXrAdxqNheAmKB70gpy2lFa8Z4wSMomxR-osxpXhJKSlaRhnPC1hR-97NFXje0rEpSFA0nh5qmYPToHb7-P2T_y_oHbm5XWg"><img src="https://mermaid.ink/img/pako:eNo9UE1rhDAQ_SsyZxVXE2NyKBR7WdjC0mM3PQSTVemayGxCP8T_3mhh5_Q-5r2BWaBz2oCA6819dYNCn5zepE3inC8zOh06gx9JliXP52OWPSXtpXX2HqaoSrstQgo9jhrEVd3uJoXoTGrjsGy2BD-YyUgQEWqFnxKkXWNoVvbduQmExxBj6EI_PErCrJU3L6PqUU0PFY3VBlsXrAdxqNheAmKB70gpy2lFa8Z4wSMomxR-osxpXhJKSlaRhnPC1hR-97NFXje0rEpSFA0nh5qmYPToHb7-P2T_y_oHbm5XWg?type=png" alt=""></a>
직접 통신, 단순하고 간단한 방법이다. 하지만 Concumer에 장애가 발생한다면 직접 호출하기 때문에 장애가 전파될 수도 있고, 전달되지 않는다면, 데이터 유실 가능성이 있다.
대규모 데이터를 안전하게, 고성능으로 처리하는데 한계가 있다.</p>
<h2 id="message-queue">Message Queue</h2>
<p><a href="https://mermaid.live/edit#pako:eNo9kM1uhDAMhF8F-cwiNiQEcuiFHovU9ljoISJeQF0SFBL1B_HuzcJqfRp_9owlr9AZhSDgcjXf3SCti17eWx2Fem1ma5Tv0H5GB6mayujFT4EcoG5qXBbZY_Tm0WOgd2t0Oj1F9X1pb6pjBjH0dlQgLvK6YAwha5K3HtbbuAU34IQtiCCVtF8ttHoLplnqD2MmEM76YLPG98MjxM9KOnweZW_l9KAWtUJbGa8dCML5HgJihR8QZ8YTlrGc8zItgyBFDL8BlywhlFHCM1qUJeVbDH_72TTJC0YyQtO0KOk5ZzGgGp2x9fG-_YvbP5nhZBE"><img src="https://mermaid.ink/img/pako:eNo9kM1uhDAMhF8F-cwiNiQEcuiFHovU9ljoISJeQF0SFBL1B_HuzcJqfRp_9owlr9AZhSDgcjXf3SCti17eWx2Fem1ma5Tv0H5GB6mayujFT4EcoG5qXBbZY_Tm0WOgd2t0Oj1F9X1pb6pjBjH0dlQgLvK6YAwha5K3HtbbuAU34IQtiCCVtF8ttHoLplnqD2MmEM76YLPG98MjxM9KOnweZW_l9KAWtUJbGa8dCML5HgJihR8QZ8YTlrGc8zItgyBFDL8BlywhlFHCM1qUJeVbDH_72TTJC0YyQtO0KOk5ZzGgGp2x9fG-_YvbP5nhZBE?type=png" alt=""></a>
Producer와 Consumer 사이에 Message Queue에 데이터 전송을 위힘해볼 수 있다. 직접적으로 결합되지 않기 때문에 장애 전파될 위험은 감소될 수도 있고, 데이터 유실 위험을 낮출 수도 있고, 데이터에 대한 처리가 비동기로 수행될 수도 있다.</p>
<blockquote>
<p>Producer와 Consumer가 많아진다면? 
처리해야될 데이터가 늘어난다면? 
Message Queue 장애가 발생한다면?
데이터에 대해 복잡한 라우팅이 필요하다면?
단일 Message Queue로 대규모 데이터를  처리하기 어려울 수 있다.</p>
</blockquote>
<p><a href="https://mermaid.live/edit#pako:eNp1kctugzAQRX8FzZogzDtedBG6LBLtstCFiycQJWBksPqI8u8dSEIjpZ2Nr8_VPOw5QqUkAoftQX1UjdCj9fRSdhZFzopcK2kq1G_WBXl3KGVFqrrBtIQuxLslZzaY91qLvrGyTZHhMIgaN1rtl5wpMna1rGeDBm8t73_L_9PCTk69c2atVg_UlaS3yGwzy5T9Su866fkEG2q9k8C34jCgDfSWVkx3OE52CWODLZbASUqh9yWU3YmSetG9KtUCH7WhNK1M3SxFTC_FiI87QV_RLlTTqKhTZboReBKFcxHgR_gEzlzXCRM3DvwoSgK2DiMbvgiHobOmCALfT0IvYfHJhu-5r-skMZVAuRuVzs6rnTd8-gH6ppQe"><img src="https://mermaid.ink/img/pako:eNp1kctugzAQRX8FzZogzDtedBG6LBLtstCFiycQJWBksPqI8u8dSEIjpZ2Nr8_VPOw5QqUkAoftQX1UjdCj9fRSdhZFzopcK2kq1G_WBXl3KGVFqrrBtIQuxLslZzaY91qLvrGyTZHhMIgaN1rtl5wpMna1rGeDBm8t73_L_9PCTk69c2atVg_UlaS3yGwzy5T9Su866fkEG2q9k8C34jCgDfSWVkx3OE52CWODLZbASUqh9yWU3YmSetG9KtUCH7WhNK1M3SxFTC_FiI87QV_RLlTTqKhTZboReBKFcxHgR_gEzlzXCRM3DvwoSgK2DiMbvgiHobOmCALfT0IvYfHJhu-5r-skMZVAuRuVzs6rnTd8-gH6ppQe?type=png" alt=""></a></p>
<p>여러 Message Queue를 만들고, 이를 처리하는 시스템을 고려할 수 있다. 
Message Queue(메시지 중개자)가 여러 Message Queue를 관리함으로써 대규모 데이터를 병렬로 처리하며, 복잡한 데이터 요구사항을 처리한다.</p>
<p>만약 Producer가 생산하는 데이터가 Broker나 Consumer의 처리향을 넘어가면, 리소스 부족으로 장애가 전파될 수도 있고, 데이터 유실될 수 있다.</p>
<p><a href="https://mermaid.live/edit#pako:eNp1kUlOwzAUhq9ivXUaxS4Z6gWLhiWRCktqFiZ-Tao2duTEYqh6A67A5TgJTgppJeBt_Pv_3uDhAKVRCBw2e_Nc1tL25PZeaOJjRdcra5Qr0T6Sb4v9soQ-rZ17qqxsa1Is1wV2naxwac3OJ574EAX9QeTOocNLxP5H8z8RajXMXlEym137qV6ySRZL4jX5_HgfrZwKndNLegmZh-fSIXOdG925Zjp8zs4OBFDZrQK-kfsOA_BeI4c9HIZkAX2NDQrgXippdwKEPvqiVuoHYxrgvXW-zBpX1VMT1yrZ481W-jdsJtf6O6LNjdM9cJrRsQnwA7wAT1k4T68WCUtpnCwWURrA65CUhEkWJVkcxSzNaHwM4G2cGoVZGgeAatsbW5z-fPz64xf7FJwR"><img src="https://mermaid.ink/img/pako:eNp1kUlOwzAUhq9ivXUaxS4Z6gWLhiWRCktqFiZ-Tao2duTEYqh6A67A5TgJTgppJeBt_Pv_3uDhAKVRCBw2e_Nc1tL25PZeaOJjRdcra5Qr0T6Sb4v9soQ-rZ17qqxsa1Is1wV2naxwac3OJ574EAX9QeTOocNLxP5H8z8RajXMXlEym137qV6ySRZL4jX5_HgfrZwKndNLegmZh-fSIXOdG925Zjp8zs4OBFDZrQK-kfsOA_BeI4c9HIZkAX2NDQrgXippdwKEPvqiVuoHYxrgvXW-zBpX1VMT1yrZ481W-jdsJtf6O6LNjdM9cJrRsQnwA7wAT1k4T68WCUtpnCwWURrA65CUhEkWJVkcxSzNaHwM4G2cGoVZGgeAatsbW5z-fPz64xf7FJwR?type=png" alt=""></a></p>
<p>Consumer가 Message Broker에서 데이터를 push받는게 아니라, Consumer가 Message Broker에서 데이터를 pull 해온다. 자신의 처리향에 따라서 조절할 수 있다.</p>
<blockquote>
<p>Producer가 데이터 생산(publish), Consumer는 데이터 구독(subscribe) 
<code>pub/sub 패턴</code></p>
</blockquote>
<p>그렇다면 이러한 시스템을 직접 구축해야되나? 고성능, 안전성, 가용성을 위한 분산 시스템을 구축하는 것은 쉽지 않다.</p>
<p>직접 구축할 필요 없이 <code>Kafka</code>를 사용하면된다.</p>
<h1 id="kafka-1">Kafka</h1>
<p><a href="https://mermaid.live/edit#pako:eNplkUtOwzAQhq9izTqtYpc86gWLhh1EqlgSszCx20Rt7Mi1xaPqDbgCl-MkTFKUFjEbj79_5vdjjlBbpYHDZm9f60Y6Tx4ehSEYa1qtnVWh1u6Z_CL2DwlzXg_hZetk35ByVd3LzU6SlbM7rDvLQ5S08rZv62vE_iBt1OC3pmQ2u0UnTNmUliuCOfn--hxRQYUp6LV6LTIUL61DZVVYcwjddKOCXQhEsHWtAr6R-4OOAFknhz0ch2IBvtGdFsAxVdLtBAhzwqZemidrO-DeBWxzNmybyST0Snp910r8l26iDt-oXWGD8cAXLBlNgB_hDXjG5ovsZpmyjCbpchlnEbwDp3k6T_M4zZM4YVlOk1MEH-Op8TzP0ECr1ltXnuc4jvP0A7F8jcg"><img src="https://mermaid.ink/img/pako:eNplkUtOwzAQhq9izTqtYpc86gWLhh1EqlgSszCx20Rt7Mi1xaPqDbgCl-MkTFKUFjEbj79_5vdjjlBbpYHDZm9f60Y6Tx4ehSEYa1qtnVWh1u6Z_CL2DwlzXg_hZetk35ByVd3LzU6SlbM7rDvLQ5S08rZv62vE_iBt1OC3pmQ2u0UnTNmUliuCOfn--hxRQYUp6LV6LTIUL61DZVVYcwjddKOCXQhEsHWtAr6R-4OOAFknhz0ch2IBvtGdFsAxVdLtBAhzwqZemidrO-DeBWxzNmybyST0Snp910r8l26iDt-oXWGD8cAXLBlNgB_hDXjG5ovsZpmyjCbpchlnEbwDp3k6T_M4zZM4YVlOk1MEH-Op8TzP0ECr1ltXnuc4jvP0A7F8jcg?type=png" alt=""></a></p>
<blockquote>
<p>머메이드 문법상 &lt;-- 없으면 방향이 틀어지기 때문에 방향 제거 불가</p>
</blockquote>
<p>kafka는 데이터를 구분하기 위해 topic이라는 단위를 사용한다.</p>
<ul>
<li>Producer는 topic 단위로 이벤트를 생산 및 전송</li>
<li>Consumer는 topic 단위로 이벤트를 구독 및 소비한다.</li>
<li>topic: Kafka에서 생산 및 소비되는 데이터를 논리적으로 구분하는 단위</li>
</ul>
<p><a href="https://mermaid.live/edit#pako:eNqllEtugzAURbdivTGJsAk_DzoInaWVog4LHbjgBJRgI8eonyg76Ba6ua6kDjQElAqpqSd-fvf64oPAe0hlxoHCaitf0pwpje4eEoHMWOJ4qWRWp1w9oZ8WuWglop139fNasSpHiyhesNWGobmSG-Nr5YFljuNWRLgnDyxaVkVq47iZL2zNWYxamfMWupDiwsFFNppMfpLJr8mzXjIZSx4sznzkxEdG-cgoHxnwob8BklFAMgBEVxA6J0JnlNAZJXT-Q-iMEjpXEDZFIpYYTSY35js2JenKRYRMjb4-P5pWhBMR4b7aF4kRz1uPzjiSYleX3f8QkXMHLFirIgO6Ytsdt8D0SnZcw_5oTkDnvOQJUFNmTG0SSMTBbKqYeJSyBKpVbbYpWa_zLqSuMqb5bcHMKyu7rjKMXEWyFhpoGLhNCNA9vAL1ydTxZ6FHfOx6YWj7FrwBxYE39QLbC1zbJX6A3YMF781T7Wngu3ZvYAt4Vmip7tsrpblZDt_BajcA"><img src="https://mermaid.ink/img/pako:eNqllEtugzAURbdivTGJsAk_DzoInaWVog4LHbjgBJRgI8eonyg76Ba6ua6kDjQElAqpqSd-fvf64oPAe0hlxoHCaitf0pwpje4eEoHMWOJ4qWRWp1w9oZ8WuWglop139fNasSpHiyhesNWGobmSG-Nr5YFljuNWRLgnDyxaVkVq47iZL2zNWYxamfMWupDiwsFFNppMfpLJr8mzXjIZSx4sznzkxEdG-cgoHxnwob8BklFAMgBEVxA6J0JnlNAZJXT-Q-iMEjpXEDZFIpYYTSY35js2JenKRYRMjb4-P5pWhBMR4b7aF4kRz1uPzjiSYleX3f8QkXMHLFirIgO6Ytsdt8D0SnZcw_5oTkDnvOQJUFNmTG0SSMTBbKqYeJSyBKpVbbYpWa_zLqSuMqb5bcHMKyu7rjKMXEWyFhpoGLhNCNA9vAL1ydTxZ6FHfOx6YWj7FrwBxYE39QLbC1zbJX6A3YMF781T7Wngu3ZvYAt4Vmip7tsrpblZDt_BajcA?type=png" alt=""></a></p>
<ul>
<li><code>Broker</code>: kafka에서 데이터 중개 및 처리해주는 애플리케이션 실행 단위</li>
<li><code>Kafka Cluter</code>: 여러개의 Broker가 모여서 하나의 분산형 시스템을 구성한 것</li>
<li><code>Partition</code>: Topic이 분산되는 단위, 각 Topic은 여러 개의 Partition으로 분산 저장 및 병렬 처리된다.</li>
<li><code>Offset</code>: 각 데이터에 대해 고유한 위치, Consumer Group은 각 그룹이 처리한 Offset을 관리한다.</li>
<li><code>Consumer Group</code>: 각 Topic의 Partition 단위로 Offset을 관리한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Article] 조회수, 어뷰징]]></title>
            <link>https://velog.io/@kooks-dev/Article-%EC%A1%B0%ED%9A%8C%EC%88%98-%EC%96%B4%EB%B7%B0%EC%A7%95</link>
            <guid>https://velog.io/@kooks-dev/Article-%EC%A1%B0%ED%9A%8C%EC%88%98-%EC%96%B4%EB%B7%B0%EC%A7%95</guid>
            <pubDate>Thu, 27 Nov 2025 16:52:29 GMT</pubDate>
            <description><![CDATA[<p>조회수는 게시글이 조회된 횟수만 저장하면 된다.</p>
<h4 id="데이터-일관성">데이터 일관성</h4>
<ul>
<li>비교적 덜 중요하다.</li>
<li>모든 조회 내역을 보여주진 않는다. 단순히 조회된 횟수만 보여주면 된다.</li>
<li>불일치가 발생하더라도 사용자가 인지하기 어렵다.</li>
</ul>
<h4 id="쓰기-트래픽">쓰기 트래픽</h4>
<ul>
<li>비교적 많다.</li>
<li>단순히 게시글 조회만 해도 쓰기 작업이 필요하다.</li>
</ul>
<p>데이터 일관성이 덜 중요하고, 다른 데이터에 의해 파생되는 데이터가 아니므로, 트랜잭션이나 안전한 저장소가 반드시 필요하진 않다. 또한 디스크는 접근 비용이 비싸다. 디스크보다 더 빠른 저장소를 고려해 볼 수 있다.</p>
<p>그렇다면 조회수 관리를 위해 <code>In-memory Database</code>를 사용해볼 수 있다. <code>Redis</code></p>
<p>Redis는 휘발성이다. 종료되면 저장되어 있던 데이터는 사라진다. 그렇다면 Redis에는 백업이 필요하다.
약간의 데이터 유실을 허용했기 때문에 모든 데이터를 백업할 필요는 없다. </p>
<ul>
<li>시간 단위 백업 X</li>
<li>개수 단위 백업 O</li>
</ul>
<h2 id="조회수-어뷰징-방지">조회수 어뷰징 방지</h2>
<p>게시글을 조회하면 조회수가 올라간다. 어뷰저는 특정 게시글을 여러 번 조회해서 데이터를 조작할 수 있다. 조회수 기반으로 인기글이 산정되는 경우, 올바르지 않는 어뷰징으로 인기글이 선정될 수 있다.</p>
<p>어뷰징을 방지하기 위한 조회 여부는 </p>
<ul>
<li>로그인 사용자
사용자 식별</li>
<li>비로그인 사용자
IP, User-Agen, 브라우저 쿠키, 토큰 등</li>
</ul>
<p>로그인 사용자만 고려하고 설계하게 한다.</p>
<ul>
<li>조회수 증가 요청이 오면, Redis에 TTL = 10분으로 데이터를 저장</li>
<li><code>key</code>는 <code>? + userId</code>가 된다.</li>
<li>이미 저장된 데이터가 있으면 저장에 실패하는 명령어를 사용</li>
<li>데이터 저장 성공 여부에 따라, 증가 및 증가하지 않는다.</li>
</ul>
<table>
<thead>
<tr>
<th align="center">요청 1</th>
<th align="center">Redis</th>
<th align="center">요청 2</th>
</tr>
</thead>
<tbody><tr>
<td align="center"></td>
<td align="center">조회수=3</td>
<td align="center"></td>
</tr>
<tr>
<td align="center">조회수 증가 분산 락 요청 key=articleId+userId, TTL=10분</td>
<td align="center">조회수=3 lock=(key=articleId+userId, TTL=10분)</td>
<td align="center"></td>
</tr>
<tr>
<td align="center">setIfAbsent -&gt; True 응답</td>
<td align="center">조회수=3 lock=(key=articleId+userId, TTL=10분)</td>
<td align="center"></td>
</tr>
<tr>
<td align="center"></td>
<td align="center">조회수=3 lock=(key=articleId+userId, TTL=10분)</td>
<td align="center">조회수 증가 분산 락 요청 key=articleId+userId, TTL=10분</td>
</tr>
<tr>
<td align="center"></td>
<td align="center">조회수=4 lock=(key=articleId+userId, TTL=10분)</td>
<td align="center">setIfAbsent -&gt; False 응답</td>
</tr>
<tr>
<td align="center">조회수 증가</td>
<td align="center">조회수=4 lock=(key=articleId+userId, TTL=10분)</td>
<td align="center">종료</td>
</tr>
<tr>
<td align="center">동일 사용자가 2번의 조회수 증가 요청이 있을 때의 상황이다.</td>
<td align="center"></td>
<td align="center"></td>
</tr>
</tbody></table>
<h2 id="code">code</h2>
<pre><code class="language-java">@Service
@RequiredArgsConstructor
public class ArticleViewService {

    private final ArticleViewCountRepository articleViewCountRepository;
    private final ArticleViewCountBackUpProcessor articleViewCountBackUpProcessor;
    private final ArticleViewDistributedLockRepository articleViewDistributedLockRepository;

    private static final int BACK_UP_BACH_SIZE = 100;
    private static final Duration TTL = Duration.ofMinutes(10);

    public Long increase(Long articleId, Long userId) {
        if (!articleViewDistributedLockRepository.lock(articleId, userId, TTL)) {
            return articleViewCountRepository.read(articleId);
        }

        Long count = articleViewCountRepository.increase(articleId);

        if(count % BACK_UP_BACH_SIZE == 0){
            articleViewCountBackUpProcessor.backUp(articleId, userId);
        }

        return count;
    }</code></pre>
<p>로그인 사용자 기준으로 조회수를 관리하기로 했고, 조회수 증가에서 articleId, userId로 락의 여부를 확인한다. </p>
<ul>
<li><code>.lock(articleId, userId, TTL)</code> 에서 사용자 A가 해당 게시글을 조회한 여부가 있는지 확인한다. 처음 조회한다면 락을 획득하고 아니면 락 획득에 실패해 해당 게시글의 조회수를 반환한다.</li>
<li>락 획득에 성공했다면 조회수를 증가한다.</li>
<li>개수 단위로 백업을 하기로 했었다. 100단위로 백업 여부를 체크해 조회수를 저장한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Article] 무한 depth]]></title>
            <link>https://velog.io/@kooks-dev/Article-%EB%AC%B4%ED%95%9C-depth</link>
            <guid>https://velog.io/@kooks-dev/Article-%EB%AC%B4%ED%95%9C-depth</guid>
            <pubDate>Thu, 27 Nov 2025 08:35:30 GMT</pubDate>
            <description><![CDATA[<h2 id="무한-depth">무한 depth</h2>
<pre><code>XXXXX    |  XXXXX  |  XXXXX  | ...........
1depth    2depth    3depth    4depth</code></pre><p>depth 별로 5개의 문자열 경로 정보를 갖는다. </p>
<ul>
<li>1depth : 5개의 문자열</li>
<li>2depth : 10개의 문자열</li>
<li>3depth : 15개의 문자열<blockquote>
<p>N depth = (N * 5)</p>
</blockquote>
</li>
</ul>
<p>5개의 문자열로 나타내므로, 표현 범위가 제한된다. 10개의 숫자로 나타내면 표현 수는 
10 ^ 5 = 100,000개로 제한된다. (00000 ~ 99999)</p>
<p>하지만 문자열이기 떄문에 0<del>9, A</del>Z, a~z 62개의 문자를 사용할 수 있다.
62 ^ 5 - 916,132,832개로 제한된다. (00000 ~ zzzzz)</p>
<h2 id="데이터베이스-collation">데이터베이스 collation</h2>
<p>문자열을 정렬하고 비교하는 방법을 정의하는 설정</p>
<pre><code class="language-sql">SELECT table_name, table_colation 
FROM information_schema.TABLES WHERE table_schema = &#39;comment&#39;;</code></pre>
<p><code>utf8mb4_0900_ai_ci</code> 설정인 것을 확인할 수 있다.
<code>_ci</code>는 대소문자의 순서를 비교할 수 없다. 그래서 <code>utf8mb4_bin</code> 을 사용한다.</p>
<pre><code class="language-sql">create table comment(
    path varcher(25) character set utf8md4 collate utf8md4_bin not null
...
);</code></pre>
<pre><code class="language-sql">create unique index inx_article_id_path on comment(article_id asc, path asc);</code></pre>
<h2 id="무한-depth-1">무한 depth</h2>
<pre><code class="language-bash">00a0z  &lt;- parentPath
├── 00a0z 00000
├── 00a0z 00001
├── 00a0z 00002 &lt;- childernTopPath
└── ?          └── 00a0z 00002 00000 &lt;- descendantsTopPath</code></pre>
<p>descendantsTopPath 찾아 신규 댓글의 <code>depth * 5</code>까지만 남기고 잘라내면 된다. ex) <code>00a0z 00002 00000</code> 에서 <code>(신규 댓글의 depth * 2) * 5 = 10</code>개의 문자만 남기고 잘라내면 <code>00a0z 00002</code>를 찾을 수 있다.</p>
<pre><code class="language-sql">select path from comment
 where article_id = {article_id}
   and path &gt; {parentPath} 
   and path like {parentPath}% 
order by path desc 
 limit 1; </code></pre>
<p>descendantsTopPath를 구할 수 있다.</p>
<h2 id="code">code</h2>
<pre><code class="language-java">public class CommentPath {
    private String path;

    private static final String CHARSET = &quot;0123456789ABCDEFGHIJKMNLOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz&quot;;

    private static final int DEPTH_CHUNK_SIZE = 5;
    private static final int MAX_DEPTH = 5;

    // MIN_CHUNK = &quot;00000&quot; MAX_CHUNK = &quot;zzzzz&quot;
    private static final String MIN_CHUNK = String.valueOf(CHARSET.charAt(0)).repeat(DEPTH_CHUNK_SIZE);
    private static final String MAX_CHUNK = String.valueOf(CHARSET.charAt(CHARSET.length() - 1)).repeat(DEPTH_CHUNK_SIZE);</code></pre>
<ul>
<li><code>CHARSET</code>에 62개의 문자열 정의</li>
<li>최대 Depth 깊이와 Depth 길이 정의</li>
<li>시작과 끝 Depth 정의<pre><code class="language-java">  public static CommentPath create(String path) {
      if(isDepthOverflow(path)){
          throw new IllegalStateException(&quot;depth overflowed&quot;);
      }
      CommentPath commentPath = new CommentPath();
      commentPath.path = path;
      return commentPath;
  }</code></pre>
</li>
<li>팩토리 메서드</li>
</ul>
<pre><code class="language-java">    public CommentPath createChildCommentPath(String descendantsTopPath) {
        if (descendantsTopPath == null) {
            return CommentPath.create(path + MIN_CHUNK);
        }
        String childrenTopPath = findChildrenToPath(descendantsTopPath);
        return CommentPath.create(increase(childrenTopPath));
    }</code></pre>
<ul>
<li>자식 여부를 체크해 자식이 없다면 depth 자식 생성, <code>null</code>아니라면 자식 또는 형제 생성</li>
<li>path가 빈 값이고 <code>descendantsTopPath</code>이 <code>00000</code> 라면 <code>00001</code>을 형제 생성</li>
<li><code>00000</code>이 자식을 생성하려 할때 <code>00000 00000</code> 자식이 존재한다. <code>descendantsTopPath</code>이 <code>00000 00000</code>에서 형제이고 <code>00000</code> 자식인 <code>00000 00001</code>을 생성 한다.</li>
</ul>
<pre><code class="language-java">private String findChildrenToPath(String descendantsTopPath) {
        return descendantsTopPath.substring(0, (getDepth() + 1) * DEPTH_CHUNK_SIZE);
}</code></pre>
<ul>
<li>루트에서 형제를 생성한다면 <code>path = &quot;&quot;</code> 뎁스는 0이고, <code>00000</code>을 반환</li>
<li>자식을 생성한다면 <code>path = 00000</code>뎁스는 1이고 <code>00000 00000</code>을 반환</li>
</ul>
<pre><code class="language-java">private String increase(String path) {
        // 00000 00000
        String lastChunk = path.substring(path.length() - DEPTH_CHUNK_SIZE);
        if (isChunkOverflow(lastChunk)) {
            throw new IllegalStateException(&quot;chunk overflowed&quot;);
        }
        int charsetLength = CHARSET.length();

        int value = 0;
        for(char ch : lastChunk.toCharArray()) {
            value = value * charsetLength + CHARSET.indexOf(ch);
        }

        value = value + 1;

        String result = &quot;&quot;;
        for(int i = 0 ; i &lt; DEPTH_CHUNK_SIZE ; i++) {
            result = CHARSET.charAt(value % charsetLength) + result;
            value /= charsetLength;
        }

        return path.substring(0, path.length() - DEPTH_CHUNK_SIZE) + result;
    }</code></pre>
<ul>
<li>루트가 형제를 생성하기 위해<code>00000</code>을 받았다면 <code>00001</code>을 반환하고, 자식을 만들기 위해 <code>00000 00000</code>을 받았다면 <code>00000 00001</code>을 반환하게 된다.</li>
<li>문자열 청크를 62진수 숫자로 변환하고 1을 증가시킨다.</li>
<li>다시 62진수 숫자를 문자열 청크(길이 5)로 변환</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Git] git label 템플릿]]></title>
            <link>https://velog.io/@kooks-dev/Git-git-label-%ED%85%9C%ED%94%8C%EB%A6%BF</link>
            <guid>https://velog.io/@kooks-dev/Git-git-label-%ED%85%9C%ED%94%8C%EB%A6%BF</guid>
            <pubDate>Tue, 25 Nov 2025 10:31:46 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Github 라벨을 하나하나 설정하지 않고 템플릿을 사용해서 적용하는 방법</p>
</blockquote>
<h3 id="사전-작업">사전 작업</h3>
<ul>
<li>Node.js 설치 여부
npx 명령어를 사용하기 위해 설치해야 됨</li>
<li>Git Access Token 발급
토큰 생성시 repo권한 부여 후 발급</li>
<li>라벨 템플릿 준비
(해당 게시글 하단에 템플릿 존재)</li>
</ul>
<h3 id="적용-명령어">적용 명령어</h3>
<pre><code class="language-bash"> npx github-label-sync --access-token &lt;Git Access Token&gt; --labels ./labels.json &lt;Git Name&gt;/&lt;Git Repository Name&gt;</code></pre>
<ul>
<li>Git Access Token -&gt; 발급 받은 토큰(repo 권한 O) </li>
<li>Git Name -&gt; 사용하는 깃허브 이름</li>
<li>Git Repository Name -&gt; 적용할 레포지토리</li>
</ul>
<h3 id="적용-결과">적용 결과</h3>
<p><img src="https://velog.velcdn.com/images/kooks-dev/post/6f1d032b-b935-4d0a-bf2e-57492010c090/image.png" alt=""></p>
<h3 id="라벨-템플릿">라벨 템플릿</h3>
<pre><code class="language-bash">[
{
    &quot;name&quot;: &quot;⚙ Setting&quot;,
    &quot;color&quot;: &quot;e3dede&quot;,
    &quot;description&quot;: &quot;개발 환경 세팅&quot;
},
{
    &quot;name&quot;: &quot;✨ Feature&quot;,
    &quot;color&quot;: &quot;a2eeef&quot;,
    &quot;description&quot;: &quot;기능 개발&quot;
},
{
    &quot;name&quot;: &quot;🌏 Deploy&quot;,
    &quot;color&quot;: &quot;C2E0C6&quot;,
    &quot;description&quot;: &quot;배포 관련&quot;
},
{
    &quot;name&quot;: &quot;🎨 Html&amp;css&quot;,
    &quot;color&quot;: &quot;FEF2C0&quot;,
    &quot;description&quot;: &quot;마크업 &amp; 스타일링&quot;
},
{
    &quot;name&quot;: &quot;🐞 BugFix&quot;,
    &quot;color&quot;: &quot;d73a4a&quot;,
    &quot;description&quot;: &quot;Something isn&#39;t working&quot;
},
{
    &quot;name&quot;: &quot;💻 CrossBrowsing&quot;,
    &quot;color&quot;: &quot;C5DEF5&quot;,
    &quot;description&quot;: &quot;브라우저 호환성&quot;
},
{
    &quot;name&quot;: &quot;📃 Docs&quot;,
    &quot;color&quot;: &quot;1D76DB&quot;,
    &quot;description&quot;: &quot;문서 작성 및 수정 (README.md 등)&quot;
},
{
    &quot;name&quot;: &quot;📬 API&quot;,
    &quot;color&quot;: &quot;D4C5F9&quot;,
    &quot;description&quot;: &quot;서버 API 통신&quot;
},
{
    &quot;name&quot;: &quot;🔨 Refactor&quot;,
    &quot;color&quot;: &quot;f29a4e&quot;,
    &quot;description&quot;: &quot;코드 리팩토링&quot;
},
{
    &quot;name&quot;: &quot;🙋‍♂️ Question&quot;,
    &quot;color&quot;: &quot;9ED447&quot;,
    &quot;description&quot;: &quot;Further information is requested&quot;
},
{
    &quot;name&quot;: &quot;🥰 Accessibility&quot;,
    &quot;color&quot;: &quot;facfcf&quot;,
    &quot;description&quot;: &quot;웹접근성 관련&quot;
},
{
    &quot;name&quot;: &quot;✅ Test&quot;,
    &quot;color&quot;: &quot;ccffc4&quot;,
    &quot;description&quot;: &quot;test 관련(storybook, jest...)&quot;
}
]
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Git] 우선 빠르게 사용해야될 때 바로 보는 명령어]]></title>
            <link>https://velog.io/@kooks-dev/Git-CLI</link>
            <guid>https://velog.io/@kooks-dev/Git-CLI</guid>
            <pubDate>Fri, 21 Nov 2025 17:38:56 GMT</pubDate>
            <description><![CDATA[<h1 id="git-시작">Git 시작</h1>
<pre><code class="language-bash">git init                     # 현재 디렉토리를 Git 저장소로 초기화
git add .                    # 모든 파일 스테이지에 올리기
git commit -m &quot;init&quot;         # 초기 커밋 생성
git branch -M main           # 브랜치 이름을 main 으로 변경
git remote add origin &lt;URL&gt;  # 원격 저장소(origin) 등록
git push -u origin main      # 원격 저장소에 첫 푸시

#-----------------------------

git clone &lt;URL&gt;              # 원격 저장소를 로컬로 복제</code></pre>
<blockquote>
<p><code>-u</code> 를  붙이는 이유는 로컬의 브랜치와 원격의 브랜치를 연결해주는 옵셥
이후 <code>git pull</code>, <code>git push</code> 를 브랜치명 없이 가능 하지만 <code>git pull main</code>, <code>git push main</code> 을 이름을 명시해준다면 않해도 됨</p>
</blockquote>
<h1 id="git-pr">Git PR</h1>
<pre><code class="language-bash">git checkout -b feature/my-task            # 작업용 브랜치 생성 및 이동

# --- 여기서 코드 수정 작업 ---

git add .                                  # 변경 파일 스테이지
git commit -m &quot;작업 내용&quot;                    # 작업 커밋
git push -u origin feature/my-task         # 작업 브랜치를 원격에 업로드

# 이후 GitHub 웹 화면에서 &quot;Compare &amp; pull request&quot; 클릭하여 PR 생성</code></pre>
<h1 id="git-branch">Git branch</h1>
<blockquote>
<p>브랜치 목록 확인/생성/삭제</p>
</blockquote>
<pre><code class="language-bash">git branch          # 목록
git branch -d feat  # 삭제
git branch -M main  # 이름 변경</code></pre>
<h1 id="git-switch">Git switch</h1>
<blockquote>
<p><code>git switch</code>는 브랜치 이동을 직관적으로 만들기 위해 만들어진 명령어
기존의 <code>git checkout</code>이 너무 많은 기능을 한꺼번에 담당해서 혼란을 줄이기 위해 분리된 명령어</p>
</blockquote>
<pre><code class="language-bash">git switch main                 # main 브랜치로 이동
git switch -c feature/my-task   # 브랜치를 만들고 바로 이동</code></pre>
<blockquote>
<p><code>git checkout</code> 을 하게 되면 파일이 변경되어 과거 버전을 보고 개발하게 될 수 있음 하지만 <code>git switch</code> 는 브랜치만 변경되기 때문에 그런일이 줄어듬</p>
</blockquote>
<h1 id="git-checkout">Git Checkout</h1>
<blockquote>
<p>checkout은 “브랜치 이동, 파일 복원”을 한 명령으로 처리하는 바람에 실수 위험이 크다.
작업 중이던 파일이 다른 브랜치 기준으로 덮어쓰여서 변경 중이던 코드가 사라지는 사고가 자주 발생
그래도, 특정 상황에서는 필요함</p>
</blockquote>
<pre><code class="language-bash"># --- 브랜치 이동 ---
git checkout main
git checkout feature/login
#대신 추천
git switch main                 # main 브랜치로 이동

# --- 새 브랜치 생성 + 이동 ---
git checkout -b feature/login
#대신 추천
git switch -c feature/login

# --- 파일을 특정 커밋 상태로 되돌리는 기능 ---
git checkout HEAD src/App.java
git checkout -- src/App.java
#대신 추천
git restore src/App.java</code></pre>
<h1 id="git-status">Git status</h1>
<blockquote>
<p>현재 작업 디렉토리에서 어떤 파일이 변경되었는지 확인하고 싶을 때
스테이지(<code>add</code>) 되었는지, 아직 <code>unstaged</code> 인지 확인하고 싶을 때
어떤 파일이 커밋 대산인지 보고싶을 때</p>
</blockquote>
<pre><code class="language-bash">git status
git add .
git status
git commit -m &quot;msg&quot;

# --- Untracked files (Git에 없는 파일) ---
Untracked files:
  (use &quot;git add &lt;file&gt;...&quot; to include in what will be committed)
        src/test/Main.java


# --- Changes not staged for commit (변경됐지만 add 안 됨) ---
Changes not staged for commit:
  modified: src/app/UserService.java


# --- Changes to be committed (커밋될 준비가 된 파일) ---  
Changes to be committed:
  new file: src/app/OrderService.java
  modified: src/app/CartService.java


# --- 현재 브랜치 정보 / 리모트 상태 ---  
On branch feature/login
Your branch is ahead of &#39;origin/feature/login&#39; by 2 commits.

# --- 충돌(conflict) 상황도 알려줌 ---  
Unmerged paths:
  both modified: src/app/UserService.java</code></pre>
<h1 id="git-merge">Git merge</h1>
<blockquote>
<p>두 브랜치의 변경사항을 하나의 브랜치로 합치는 것</p>
</blockquote>
<p>예를 들어:</p>
<ul>
<li><code>main branch</code></li>
<li><code>feature/login branch</code></li>
<li><code>feature</code> 브랜치에서 작업을 끝내고 → <code>main</code>에 합칠 때 merger를 사용</li>
</ul>
<pre><code class="language-bash">git switch main               # 합쳐질(받아들일) 브랜치로 이동
git merge feature/login       # feature의 변경사항을 main에 합치기</code></pre>
<pre><code class="language-bash">main:      A — B — C — F
               \
feature:         D — E

# --- git merge ---

main:      A — B — C — F — M
               \           /
feature:         D — E ——</code></pre>
<h2 id="merge-conflict">merge conflict</h2>
<pre><code class="language-bash">git merge feature/login
# &lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD
# =======
# &gt;&gt;&gt;&gt;&gt;&gt;&gt; feature/login

# --- 해당 파일을 직접 수정 후 ---

git add 파일명
git commit</code></pre>
<h2 id="merge는-언제-사용">merge는 언제 사용?</h2>
<p>PR(풀 리퀘스트) 병합시</p>
<p>GitHub에서 PR을 만들고 → 리뷰 후 → <code>Merge Pull Reqeust</code> (내부적으로 merge를 수행)</p>
<p><code>main</code> ← <code>feature</code> 작업 완료시</p>
<h1 id="git-rebase">Git rebase</h1>
<blockquote>
<p>rebase = 내 브랜치 출발점을 (main 최신 지점)으로 옮기는 것</p>
</blockquote>
<ul>
<li>커밋을 다시 만들어서 직선을 만듦</li>
<li>공유 브랜치에서는 절대 사용하면 안됨(커밋 재작성되니 충돌과 히스토리 꼬임)</li>
</ul>
<pre><code class="language-bash">git switch feature
git rebase main

# rebase 후 원격 브랜치 업데이트
git push -f 
# or
git push -f &lt;브랜치명&gt;

# 더 안전하게는 
git push --force-with-lease
# or
git push --force-with-lease &lt;브랜치명&gt;</code></pre>
<pre><code class="language-bash">main:      A — B — C — F
               \
feature:         D — E

# --- git merge ---

main:      A — B — C — F
                        \
feature:                 D&#39; — E&#39;</code></pre>
<h2 id="rebase-conflict">rebase conflict</h2>
<pre><code class="language-bash">error: could not apply D
hint: Resolve conflicts and run `git rebase --continue`

#-----------------------------

&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD
main 버전의 코드
=======
feature 버전의 코드
&gt;&gt;&gt;&gt;&gt;&gt;&gt; D

#-----------------------------

git add &lt;충돌난파일&gt;
git rebase --continue</code></pre>
<h2 id="rebase는-언제-사용">rebase는 언제 사용?</h2>
<ol>
<li><code>main</code> 브랜치가 업데이트된 상태에서 <code>feature</code> 브랜치를 최신으로 맞추고 싶을 때</li>
<li>feature 브랜치의 커밋을 깔끔하게 정리하고 싶을 때</li>
<li>PR 보내직 전에 히스토리를 직선으로 만들고 싶을 때</li>
<li>merge coimmit 이 싫을 때</li>
</ol>
<h2 id="feature-브랜치의-커밋을-깔끔하게-정리하고-싶을-때">feature 브랜치의 커밋을 깔끔하게 정리하고 싶을 때</h2>
<pre><code class="language-bash">D  (작업 커밋)
E  (오타 수정)
F  (테스트 로그 삭제)
G  (실제 기능 추가)

#-----------------------------

$ git log --oneline
g7f3abc G
c9b312f F
a4e129b E
d1f2c9a D

#-----------------------------

git rebase -i HEAD~4

# 아래 명령어 사용 가능:
# pick (그대로)
# squash(commit 합치기)
# reword(메시지 수정)
# drop(커밋 삭제)
#-----------------------------

pick d1f2c9a D
pick a4e129b E
pick c9b312f F
pick g7f3abc G

#-----------------------------

pick d1f2c9a D
squash a4e129b E
squash c9b312f F
pick g7f3abc G

#-----------------------------

D
# E
# F

#-----------------------------

기능 구현 + 오타 수정 + 로그 정리

#-----------------------------

new_hash2 G
new_hash1 기능 구현 + 오타 수정 + 로그 정리</code></pre>
<h1 id="git-restore">Git restore</h1>
<blockquote>
<p>수정한 <a href="http://App.java">App.java</a> 파일을 마지막 커밋 상태(HEAD)로 되돌림</p>
</blockquote>
<ul>
<li>수정했던 내용 → 모두 사라짐</li>
<li>파일은 <strong>커밋된 기준 버전으로 리셋됨</strong></li>
<li>add 되지 않은 변경사항(staged 전 변경)만 리셋됨</li>
</ul>
<p>개발하다가 “이거 그냥 다시 처음부터 하자” 싶을 때 쓰는 명령어</p>
<pre><code class="language-bash">src/App.java  ← 내가 수정함
# 근데 “아… 이거 아니네… 그냥 돌아가자” 하면
git restore src/App.java
# → 마지막 커밋 시점으로 돌아감</code></pre>
<p>한번 더</p>
<pre><code class="language-bash"># --- 파일 수정함 (아직 add 안 함) ---
git status

Changes not staged for commit:
    modified: src/App.java
# --- 복구 ---
git restore src/App.java

# --- 복구 후 상태 ---
git status
# nothing to commit, working tree clean
# 변경 내용 전부 사라짐</code></pre>
<p><strong>🟥 중요한 점!</strong></p>
<pre><code class="language-bash">git add src/App.java
git restore src/App.java  ❌ 안 됨</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Snowflake]]></title>
            <link>https://velog.io/@kooks-dev/Snowflake</link>
            <guid>https://velog.io/@kooks-dev/Snowflake</guid>
            <pubDate>Tue, 18 Nov 2025 10:31:45 GMT</pubDate>
            <description><![CDATA[<h1 id="snowflake란">Snowflake란?</h1>
<p>단일 DB Auto Increment에 의존하지 않고 다중 서버(멀티 노드)에서 동시 생성해도 충돌없는 고유 ID를 만드는 알고리즘</p>
<ul>
<li>DB INSERT 전에 ID를 만들어도 됨(DB 부하 감소)</li>
<li>분산환경에서도 충돌 없음</li>
<li>시간 순서대로 정렬 가능(정렬 성능 ↑) </li>
<li>64 비트의 long값 하나로 표현 -&gt; 빠르고 메모리 효율적</li>
</ul>
<pre><code class="language-bash">1  비트  : UNUSED  
41 비트  : Timestamp (시간)
10 비트  : Node ID (서버 ID)
12 비트  : Sequence (동일 시간 내 증가값)
-----------------------------------------
총 64비트 = long
</code></pre>
<table>
<thead>
<tr>
<th align="center">비트</th>
<th align="center">필드</th>
<th align="center">저장 내용</th>
</tr>
</thead>
<tbody><tr>
<td align="center">1</td>
<td align="center">UNUSED</td>
<td align="center">항상 0 (long 부호 비트)</td>
</tr>
<tr>
<td align="center">41</td>
<td align="center">Timestamp</td>
<td align="center">기준 시각부터 몇 ms 지났는지</td>
</tr>
<tr>
<td align="center">10</td>
<td align="center">Node ID</td>
<td align="center">서버 ID(0~1023)</td>
</tr>
<tr>
<td align="center">12</td>
<td align="center">Sequence</td>
<td align="center">같은 ms 안에서 생성된 번호(0~4095)</td>
</tr>
</tbody></table>
<hr>
<h2 id="code">Code</h2>
<pre><code class="language-java">public class Snowflake {
    private static final int UNUSED_BITS = 1;
    private static final int EPOCH_BITS = 41;
    private static final int NODE_ID_BITS = 10;
    private static final int SEQUENCE_BITS = 12;

    private static final long maxNodeId = (1L &lt;&lt; NODE_ID_BITS) - 1;
    private static final long maxSequence = (1L &lt;&lt; SEQUENCE_BITS) - 1;

    private final long nodeId = RandomGenerator.getDefault().nextLong(maxNodeId + 1);

    private final long startTimeMillis = 1704067200000L;

    private long lastTimeMillis = startTimeMillis;
    private long sequence = 0L;

    public synchronized long nextId() {
        long currentTimeMillis = System.currentTimeMillis();

        if (currentTimeMillis &lt; lastTimeMillis) {
            throw new IllegalStateException(&quot;Invalid Time&quot;);
        }

        if (currentTimeMillis == lastTimeMillis) {
            sequence = (sequence + 1) &amp; maxSequence;
            if (sequence == 0) {
                currentTimeMillis = waitNextMillis(currentTimeMillis);
            }
        } else {
            sequence = 0;
        }

        lastTimeMillis = currentTimeMillis;

        return ((currentTimeMillis - startTimeMillis) &lt;&lt; (NODE_ID_BITS + SEQUENCE_BITS))
            | (nodeId &lt;&lt; SEQUENCE_BITS)
            | sequence;
    }

    private long waitNextMillis(long currentTimestamp) {
        while (currentTimestamp &lt;= lastTimeMillis) {
            currentTimestamp = System.currentTimeMillis();
        }
        return currentTimestamp;
    }
}</code></pre>
<pre><code class="language-java">Snowflake idGen = new Snowflake();

long id = idGen.nextId();
System.out.println(id); // 전 세계 고유 ID
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Primary Key 생성 전략]]></title>
            <link>https://velog.io/@kooks-dev/Primary-Key-%EC%83%9D%EC%84%B1-%EC%A0%84%EB%9E%B5</link>
            <guid>https://velog.io/@kooks-dev/Primary-Key-%EC%83%9D%EC%84%B1-%EC%A0%84%EB%9E%B5</guid>
            <pubDate>Tue, 18 Nov 2025 10:05:21 GMT</pubDate>
            <description><![CDATA[<ul>
<li>DB auto_increment</li>
<li>유니크 문자열 또는 숫자</li>
<li>유니크 정렬 문자열</li>
<li>유니크 정렬 숫자</li>
</ul>
<h1 id="db-auto_increment">DB auto_increment</h1>
<ul>
<li>분산 데이터베이스 환경에서는 PK가 중복될 수 있기 때문에, 식별자의 유일성이 보장되지 않는다.</li>
<li>클라이언트 측에 노출하면 보안 문제
user_id = 1000 라면 1,000명의 사용자가 있다는 사실을 유추</li>
<li>간단하기 때문에 다음 상황에서 유리할 수 있다.</li>
</ul>
<p>1.보안적인 문제를 크게 고려하지 않는 상황
2.단일 DB를 사용하거나 애플리케이션에서 PK의 중복을 직접 구분하는 상황</p>
<ul>
<li>보안 적인 문제가 염려된다면 PK는 데이터베이스 내에서의 식별자로만 사용하고, 애플리케이션에서의 식별자를 위해 별도 유니크 인덱스를 사용할 수도 있다.
PK = id(DB auto_increment)
unique index = article_id(UUID 등)
Client는 article_id만 식별자로서 노출 및 사용</li>
</ul>
<h1 id="유니크-문자열-또는-숫자">유니크 문자열 또는 숫자</h1>
<ul>
<li>UUID 또는 난수를 생성하여 PK를 지정할 수 있다. </li>
<li>정렬 데이터가 아니라 랜덤 데이터를 삽입하는 것이다. </li>
<li>하지만, 랜덤 데이터로 인해 성능 저하가 발생할 수 있다.
Clustered Index는 정렬된 상태를 유지한다.
데이터 삽입 필요한 인덱스 페이지가 가득 찼다면, B+tree 재구성 및 페이지 분할로 디스크 I/O증가
PK를 이용한 범위 조회가 필요하다면, 디스크에서 랜덤 I/O가 발생하기 때문에, 순차 I/O보다 성능 저하</li>
</ul>
<h1 id="유니크-정렬-문자열">유니크 정렬 문자열</h1>
<ul>
<li>분산 환경에 대한 PK 중복 문제 해결</li>
<li>보안 문제 해결</li>
<li>랜덤 데이터에 의한 성능 문제 해결</li>
<li>UUID v7, ULID 등의 알고리즘
일반적으로 알려진 알고리즘음 128비트를 사용한다.</li>
<li>데이터 크기에 따라 공간 및 성능 효율이 달라진다.
Clustered Index는 PK를 기준으로 만들어지고, Secondary Index는 데이터에 접근할 수 있는 포인터를 가진다. 즉, PK를 가지고 있다. 
PK가 크면 클수록, 데이터는 더 많은 공간을 차지, 비교 연산에 의한 정렬/조회에 더 많은 비용 소모</li>
</ul>
<h1 id="유니크-정렬-숫자">유니크 정렬 숫자</h1>
<ul>
<li>분산 환경에 대한 PK 중복 문제 해결</li>
<li>보안 문제 해결</li>
<li>랜덤 데이터에 의한 성능 문제 해결</li>
<li>Snowflake, TSID 등의 알고리즘 
64비트를 사용한다. (BIGINT)
정렬을 위해 타임스탬프를 나타내는 비트 수의 제한으로, 키 생성을 위한 시간적인 한계가 있을 수있다. 문자열 알고리즘에서도 동일한 문제가 있으나 비트 수가 많을수록 제한이 덜할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Article] 게시판 페이징]]></title>
            <link>https://velog.io/@kooks-dev/Article-%EA%B2%8C%EC%8B%9C%ED%8C%90</link>
            <guid>https://velog.io/@kooks-dev/Article-%EA%B2%8C%EC%8B%9C%ED%8C%90</guid>
            <pubDate>Mon, 10 Nov 2025 15:27:13 GMT</pubDate>
            <description><![CDATA[<h1 id="게시판-설계">게시판 설계</h1>
<table>
<thead>
<tr>
<th align="center"></th>
<th align="center"></th>
</tr>
</thead>
<tbody><tr>
<td align="center">articleId</td>
<td align="center">Primary Key</td>
</tr>
<tr>
<td align="center">title</td>
<td align="center">제목</td>
</tr>
<tr>
<td align="center">content</td>
<td align="center">내용</td>
</tr>
<tr>
<td align="center">board_id</td>
<td align="center">게시판 ID(Shard Key)</td>
</tr>
<tr>
<td align="center">writer_id</td>
<td align="center">작성자 ID</td>
</tr>
<tr>
<td align="center">created_id</td>
<td align="center">생성 시간</td>
</tr>
<tr>
<td align="center">modified_at</td>
<td align="center">수정 시간</td>
</tr>
</tbody></table>
<p>분산 데이터베이스를 사용을 고려해 게시글이 N개의 Shard로 분산되는 상황을 고려한다.</p>
<blockquote>
<p>Shrad Key = board_id</p>
</blockquote>
<p>❓Shard Key는 왜 게시판 ID일까? 
❗️게시글 서비스는 <strong>게시판 단위</strong>로 서비스를 이용한다. 게시판 단위로 게시글 목록이 조회되기 때문 </p>
<p>만약 Shard Key가 article_id 일 경우 동일한 게시판에 속한 게시글이 별도의 샤드에 저장될 수 있다. 
각 게시판의 게시글 목록을 조회하는 경우 게시글이 속한 모든 샤드에 조회를 요청해야 한다. 
N개의 샤드라면, 게시글의 N개의 샤드로 분산되어 있으므로 게시글 목록 조회를 위해 N개의 쿼리를 사용해 목록을 조회하는 복잡한 처리가 필요하다.</p>
<h2 id="primary-key">Primary Key</h2>
<ul>
<li>오름차순 유니크 숫자를 애플리케이션에 직접 생성
AUTO_INCREMENT 또는 UUID를 사용하지 않는다.</li>
<li>오름차순 유니크 숫자를 만들기 위한 알고리즘</li>
<li><em>Snowflake*</em>, TSID 등</li>
</ul>
<hr>
<h1 id="게시글-api-및-test-data">게시글 API 및 Test Data</h1>
<ul>
<li>1200만 건 데이터 삽입
1번 게시판에 1200만 건 게시글</li>
</ul>
<p>❓대규모 데이터에서 게시글 목록 조회는 왜 복잡할까?
모든 데이터를 한 번에 보여줄 수 없다. 메모리, 네트워크, 성능 등의 제약...페이징이 필요하다.</p>
<p>서버 애플리케이션 내의 메모리로 디스크에 저장된 모든 데이터를 가져오고, 특정 페이지만 추출하는 것은 비효율적이다. 데이터베이스에서 특정 페이지의 데이터만 바로 추출하는 방법이 <strong>페이징 쿼리</strong>다.</p>
<h2 id="페이징">페이징</h2>
<ul>
<li><p>페이지 번호
이동할 페이지 번호가 명시적으로 지정, 원하는 페이지로 즉시 이동 가능</p>
</li>
<li><p>무한 스크롤
스크롤을 내리면 다음 데이터가 조회, 주로 모바일 환경에서 사용</p>
</li>
</ul>
<h2 id="페이지-번호">페이지 번호</h2>
<p>페이지 번호 방식에는 두 가지 정보가 필요하다.
페이지 당 30개의 게시글을 보여주고, 총 94개의 게시글이 있다면, 사용자는 클라이언트 화면에서 4번 페이지까지 이동할 수 있다. 페이지 번호 활성화에 필요</p>
<p>N번 페이지에서 M개의 게시글을 조회하려면?
SQL offset, limit을 활용하여 페이지 쿼리를 할 수 있다. offset지점 부터 limit개의 데이터 조회</p>
<pre><code class="language-sql">select * from article
where board_id = {board_id}
order by create_at desc 
limit 30 offset 90;</code></pre>
<p>그런데 1,200만 건의 데이터를 조회하는데 4초가 소요되었다. 정상적으로 서비스하기 어려운 상황이다. Query Plan으로 확인해보고 인덱스를 사용한다.</p>
<pre><code class="language-sql">create index idx_board_id_article_id on article(board_id asc, article_id);</code></pre>
<p>여기서 created_at 이 아닌 article_id로 사용했나?
현재 게시판은 분산 환경을 가정하여 만들고 있는데 여러 서버에서 동시에 처리될 수 있기 때문이다. 게시글이 동시에 생성될 수 있다. 또한, 테스트 데이터를 멀티스레드에서 동시에 생성했기 때문에 동일 시간을 갖는 데이터가 많다.</p>
<p>article_id는 분산 시스템에서 고유한 오름찬순을 위해 고안된 알고리즘을 사용해 데이터를 저장한다.</p>
<pre><code class="language-sql">select * from article
where board_id = 1
order by aricle_id desc 
limit 30 offset 90;</code></pre>
<p>이전에는 약 4초가 소요되었다면 0초대에 수행되는 것을 확인해 볼 수 있다.
또한, Query Plan를 살펴보면 key = idx_board_id_article_id 를 사용하는 것도 확인해 볼 수 있을 것이다.</p>
<p>그렇다면 모든 문제는 해결되었는가??
만약 50,000페이지를 조회한다면 다시 4초의 시간이 걸릴 것이다. 인덱스를 생성하고 사용하게 했지만 느려진다.</p>
<p>이런 경우에는 Secondary Index(보조 인덱스 or Non-clustered Index)가 Clustered Index에 접근하게 하여 데이터를 가져오면 된다. 
Secondary Index에더 데이터에 접근하기 위한 포인터를 찾은 뒤, Clustered Indexd에서 데이터를 찾는 방식이다.</p>
<pre><code class="language-sql">select * from (
    select article_id from article
    where board_id = 1
    order by article_id desc
    limit 30 offset 1499970
) t left join article on t.article_id = article.article_id;</code></pre>
<p>여기서 서브쿼리는 Covering Index로 동작한다. 데이터를 읽지 않고 인덱스에 포함된 정보만으로 쿼리가 가능하기 때문에 <strong>1499970</strong> 건이 있어도 빠르게 조회할 수 있다.</p>
<p>만약 여기서 더 높은 페이지를 조회할려 한다면??? 여기서 생각해 볼 수 있는건 그 조회가 정상적인 조회인지를 생각해야 한다. 데이터 수집을 목적으로 하는 어뷰저일 수도 있기 때문에 정책으로 풀어내 특정 페이지까지만 제한해야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Distributed Relational Database]]></title>
            <link>https://velog.io/@kooks-dev/Distributed-Relational-Database</link>
            <guid>https://velog.io/@kooks-dev/Distributed-Relational-Database</guid>
            <pubDate>Fri, 07 Nov 2025 18:07:39 GMT</pubDate>
            <description><![CDATA[<p><a href="https://mermaid.live/edit#pako:eNo9ULtuhDAQ_BW0NYewMQa7iBSOMmlSBqdwzj5AARv5jPJA_HsMd2KqndHO7GoWuFilgcN1sN-XTjofvbwJI0wU8Nych14b_3Gnt_mzdXLqorpq6uohbqia4Osvg35o2qgjIjqdnoJBGIihdb0CfpXDTccwajfKjcOyrQrwnR61AB5GJd2XAGHWYJqkebd2BO7dHGzOzm13hMyTkl7XvQyPjYfqwgPane1sPHBM0B4CfIEf4IiQhKaUIYSykmKakxh-g1zQhDGWppgRQlmG1xj-9rNpUpCM5DgnJSvTAmEWg1a9t-713txe4PoPLtdiHQ"><img src="https://mermaid.ink/img/pako:eNo9ULtuhDAQ_BW0NYewMQa7iBSOMmlSBqdwzj5AARv5jPJA_HsMd2KqndHO7GoWuFilgcN1sN-XTjofvbwJI0wU8Nych14b_3Gnt_mzdXLqorpq6uohbqia4Osvg35o2qgjIjqdnoJBGIihdb0CfpXDTccwajfKjcOyrQrwnR61AB5GJd2XAGHWYJqkebd2BO7dHGzOzm13hMyTkl7XvQyPjYfqwgPane1sPHBM0B4CfIEf4IiQhKaUIYSykmKakxh-g1zQhDGWppgRQlmG1xj-9rNpUpCM5DgnJSvTAmEWg1a9t-713txe4PoPLtdiHQ?type=png" alt=""></a></p>
<p>Clinet는 초기에 단일 데이터베이스를 사용한다. </p>
<p>❓서비스가 활성화되어 단일 DB로 처리하기엔 큰 부담이 생기게 되었을 때, 저장해야 할 데이터와 트래픽이 많아졌다면???</p>
<p>❗️가장 간단한 방법으로 Scale-Up을 고려해볼 수 있다.</p>
<p>❓얼마 후 서비스가 더욱 활성화되면서 단일 DB로 처리하기엔 큰 부담이 생기게  되었다. 저장해야 할 데이터와 트래픽이 계속 많아 진다면???</p>
<p>❗️이번에도 Scale-Up을 고려해볼 수 있지만, 장비를 무한정 Scale-Up하는 것은 한계가 있다. 한계로 인해 Scale-Out을 고려해볼 수 있다. 장비를 어러 대 사용하여 수평확장하는 것이다.</p>
<p><a href="https://mermaid.live/edit#pako:eNp1kc1ugzAQhF8F7Zkg8w8-VErCsbn02LgHF28AFWzkGKUt4t1rII3SSJ2D5Vn7m11pRyiVQKBwatWlrLk2zvMLk0w6Vtvjvm1QmrfVrud5eK8072un2PnHYnd9m2W95ZuyxWsRpfiNuqOCByr4n9o6m83T3OivDZgEFyrdCKAn3p7RhQ51x2cP4_yXgamxQwbUXgXXHwyYnCzUc_mqVAfU6MFiWg1VfQsZesENFg23o3a3qrYDod6rQRqgPomXEKAjfALNUo9kJI3DPInzLAsTF76ApsTLrZIwCiISJtHkwvfSlHhZGpM7-S6gaIzSh3ULyzKmH334dnA"><img src="https://mermaid.ink/img/pako:eNp1kc1ugzAQhF8F7Zkg8w8-VErCsbn02LgHF28AFWzkGKUt4t1rII3SSJ2D5Vn7m11pRyiVQKBwatWlrLk2zvMLk0w6Vtvjvm1QmrfVrud5eK8072un2PnHYnd9m2W95ZuyxWsRpfiNuqOCByr4n9o6m83T3OivDZgEFyrdCKAn3p7RhQ51x2cP4_yXgamxQwbUXgXXHwyYnCzUc_mqVAfU6MFiWg1VfQsZesENFg23o3a3qrYDod6rQRqgPomXEKAjfALNUo9kJI3DPInzLAsTF76ApsTLrZIwCiISJtHkwvfSlHhZGpM7-S6gaIzSh3ULyzKmH334dnA?type=png" alt=""></a></p>
<p>2대의 DB로 운용되어 Client의 요청은 각 DB로 분살될 수 있고, 데이터베이스에 대한 하나의 분산 시스템이 구성된 것이다.</p>
<p>❓그렇다면 데이터는 각 DB에 어떻게 분산될 수 있을까?</p>
<p>샤딩을 이용하여 데이터를 여러 DB에 분산할 수 있다. 그리고 샤딩된 각각의 데이터 단위를 샤드라고 부른다.</p>
<blockquote>
<p><strong>샤딩(Sharding)</strong>: 데이터를 여러 데이터베이스에 분산하여 저장하는 기술
<strong>샤드(Shard)</strong>: 샤딩된 각각의 데이터 단위</p>
</blockquote>
<h1 id="sharding">Sharding</h1>
<h2 id="vertical-sharding">Vertical Sharding</h2>
<p><a href="https://mermaid.live/edit#pako:eNqFkslugzAQhl8FzZkgggmLD5UScmwv7a2mihzsBKtgI8eoS5R3r4GIEJWqc7Bm-8efRnOGQjEOGA6V-ihKqo3z-JzLXDrW1iSrBJfmbQiH99Tuj5o2pbPdLMmLVbBr-a5oB4mi4ktydSY9mzG5E1PtJiBGmPtWRAolzQ2hMy7ZLRi8PvUL71-6YIaus-wPwr4WkL2yY2cqFlZzajjb0VneCeXaWSwexiXN5IKuE1w4asEAH2h14i7UXNe0i-HcSXIwJa95Dti6jOr3HHJ5saKGylelasBGt1amVXssxyFtwyzjVlC7inrMagvHdaZaaQCnCPVDAJ_hE3ASe37ixyuURqs0SVDkwhfg2PdSaxEKg9BHUXhx4bv_1PeSeOUCZ8Io_TRcV39klx_IaLgA"><img src="https://mermaid.ink/img/pako:eNqFkslugzAQhl8FzZkgggmLD5UScmwv7a2mihzsBKtgI8eoS5R3r4GIEJWqc7Bm-8efRnOGQjEOGA6V-ihKqo3z-JzLXDrW1iSrBJfmbQiH99Tuj5o2pbPdLMmLVbBr-a5oB4mi4ktydSY9mzG5E1PtJiBGmPtWRAolzQ2hMy7ZLRi8PvUL71-6YIaus-wPwr4WkL2yY2cqFlZzajjb0VneCeXaWSwexiXN5IKuE1w4asEAH2h14i7UXNe0i-HcSXIwJa95Dti6jOr3HHJ5saKGylelasBGt1amVXssxyFtwyzjVlC7inrMagvHdaZaaQCnCPVDAJ_hE3ASe37ixyuURqs0SVDkwhfg2PdSaxEKg9BHUXhx4bv_1PeSeOUCZ8Io_TRcV39klx_IaLgA?type=png" alt=""></a></p>
<p>수직 샤딩(Vertical Sharding): 데이터를 수직으로 분할하는 방식(컬럼 단위) article_id 식별자를 이용하여 분산 저장</p>
<ul>
<li>상위 샤드 -&gt; board_id, created_at</li>
<li>하위 샤드 -&gt; title, content</li>
</ul>
<p>각 샤드가 적은 수의 컬럼을 저장하므로, 성능 및 공간 이점이 생긴다. 
하지만, 데이터의 분리로 인해 조인 또는 트랜잭션 관리가 복잡해질 수 있다. 수직으로 분할되므로 수평적 확장에 제한이 있다.</p>
<h2 id="horizontal-sharding">Horizontal Sharding</h2>
<p><a href="https://mermaid.live/edit#pako:eNqFkslugzAQhl8FzZkgmz2WWikhx_bS3gpV5cYOoIKNHKMuEXn2GoggUdN2DtbMP4s_W3OArWQcCOwq-b4tqNLW3UMmMmEZW6VJVXKhn8dwPPfta65oU1ibNU4fTQc7pS-SZlC5rThOT85ZzXoSZ-0kvJTsBh8DhNBcPxdxweZg9AbpB5f7L5d7hau35G-2nuyI0QXfb5BnaCtrsbidvuSK5vaVYEOuSgZkR6s9t6HmqqZ9DIe-JQNd8JpnQIzLqHrLIBOdaWqoeJKyBqJVa9qUbPNiGtI2jGq-Kal5fj2pysBxlchWaCAY42iYAuQAH0DiyEExigJvGQbLOPZCGz6BRMhZGgs93_WRF_qdDV_DrciJo8AGzkot1f24TMNOdd-gmbCW"><img src="https://mermaid.ink/img/pako:eNqFkslugzAQhl8FzZkgmz2WWikhx_bS3gpV5cYOoIKNHKMuEXn2GoggUdN2DtbMP4s_W3OArWQcCOwq-b4tqNLW3UMmMmEZW6VJVXKhn8dwPPfta65oU1ibNU4fTQc7pS-SZlC5rThOT85ZzXoSZ-0kvJTsBh8DhNBcPxdxweZg9AbpB5f7L5d7hau35G-2nuyI0QXfb5BnaCtrsbidvuSK5vaVYEOuSgZkR6s9t6HmqqZ9DIe-JQNd8JpnQIzLqHrLIBOdaWqoeJKyBqJVa9qUbPNiGtI2jGq-Kal5fj2pysBxlchWaCAY42iYAuQAH0DiyEExigJvGQbLOPZCGz6BRMhZGgs93_WRF_qdDV_DrciJo8AGzkot1f24TMNOdd-gmbCW?type=png" alt=""></a>
수평 샤딩(Horizontal Sharding): 데이터를 수평으로 분할하는 방식(행 단위) 으로 분산 저장 </p>
<ul>
<li>하위 샤드 -&gt; article_id 1 ~ 5000</li>
<li>상위 샤드 -&gt; article_id 5000 ~ 10000</li>
</ul>
<p>각 샤드에 데이터가 분산 저장되므로 성능 및 공간 이점이 생긴다.
하지만, 데이터의 분리로 인해 조인 또는 트랜잭션 관리가 복잡해질 수 있다. 수평으로 분할되므로 수평적 확장에 용이하다.</p>
<table>
<thead>
<tr>
<th align="center">항목</th>
<th align="center">수직 샤딩</th>
<th align="center">수평 샤딩</th>
</tr>
</thead>
<tbody><tr>
<td align="center">샤딩</td>
<td align="center">수직으로 분할(컬럼 단위)</td>
<td align="center">수평으로 분할(행 단위)</td>
</tr>
<tr>
<td align="center">확장성</td>
<td align="center">제한</td>
<td align="center">용이</td>
</tr>
<tr>
<td align="center">장점</td>
<td align="center">각 샤드로 데이터 분산되므로, 성능 및 공간 이점</td>
<td align="center">(동일)</td>
</tr>
<tr>
<td align="center">단점</td>
<td align="center">조인 또는 트랜잭션 관리 등의 어려움</td>
<td align="center">(동일)</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[System Architecture]]></title>
            <link>https://velog.io/@kooks-dev/System-Architecture</link>
            <guid>https://velog.io/@kooks-dev/System-Architecture</guid>
            <pubDate>Fri, 07 Nov 2025 16:36:12 GMT</pubDate>
            <description><![CDATA[<h2 id="시스템-아키텍처란">시스템 아키텍처란?</h2>
<ul>
<li>시스템의 구조나 설계 방식</li>
<li>확장성, 유지보수성, 서능 등 큰 영향</li>
</ul>
<h4 id="대표적인-아키텍처">대표적인 아키텍처</h4>
<ul>
<li>Monolithic Architecture</li>
<li>Micorservice Architecture</li>
</ul>
<h2 id="monolithic-architecture">Monolithic Architecture</h2>
<p>Monolithic: 단일의, 일체형의, 하나의 큰 덩어리로 된
Monolithic Architecture:애플리케이션의 모든 기능이 하나로 통합된 아키텍처
<a href="https://mermaid.live/edit#pako:eNpdUT1vwjAQ_SvRzRFKggnYazu2S8fWHQw2BJXEyCT9QkgMrVSJbAWpQkWiHTp1QFUHhv4iMP-hNpFo4RbfvXfvPcnXh4bkAgg0O_KmETGVOidnNHFM9bJ6S7Fu5PSEuhbqgoKezDfjfP3xqX-GevatH18oXBbLtphZWX3levS6Wg73mLph1vnzIdywnu8jPXnQ0_Eewy3ztthMc_20HyIsM1uulotDt-b_eKdQ_y0Uj0g4TWwLLrRUmwNpsk5PuBALFTM7Q9_SFNJIxIICMS1n6ooCTQZG1GXJuZQxkFRlRqZk1op2JlmXs1Qct5n5t3iHKhMq1JHMkhQIQnhrAqQPt0B8hEqhF2Lf98u1MAgryIU7A1fDEsbY8wKMUIjLwcCF-22sV6qiMqoEFVTDNa_qB8ZO8HYq1Wlxx-05B78dLKUc"><img src="https://mermaid.ink/img/pako:eNpdUT1vwjAQ_SvRzRFKggnYazu2S8fWHQw2BJXEyCT9QkgMrVSJbAWpQkWiHTp1QFUHhv4iMP-hNpFo4RbfvXfvPcnXh4bkAgg0O_KmETGVOidnNHFM9bJ6S7Fu5PSEuhbqgoKezDfjfP3xqX-GevatH18oXBbLtphZWX3levS6Wg73mLph1vnzIdywnu8jPXnQ0_Eewy3ztthMc_20HyIsM1uulotDt-b_eKdQ_y0Uj0g4TWwLLrRUmwNpsk5PuBALFTM7Q9_SFNJIxIICMS1n6ooCTQZG1GXJuZQxkFRlRqZk1op2JlmXs1Qct5n5t3iHKhMq1JHMkhQIQnhrAqQPt0B8hEqhF2Lf98u1MAgryIU7A1fDEsbY8wKMUIjLwcCF-22sV6qiMqoEFVTDNa_qB8ZO8HYq1Wlxx-05B78dLKUc?type=png" alt=""></a>
위 애플리케이션은 게시판 서비스의 모든 기능을 포함하고 있다. 간단한 구조를 가지기 때문에 초기에 쉽고 빠르게 개발할 수 있다.</p>
<blockquote>
<p>이러한 애플리케이션을 1대의 서버에 배포하여 서비스하는 상황을 가정</p>
</blockquote>
<p>Monolithic Architecture 로 구성된 애플리케이션 서버 한 대에 배포했을 때 단일 코드베이스로 관리하기 때문에, 각 애플리케이션에는 모든 기능이 포함되어 있다. 
서비스가 활성화되면, 게시글 조회 수와 작성 트래픽이 급등했다고 가정했을 때,
게시글 기능만 트래픽이 많아 진 것이다. </p>
<p>부하를 감당하기 위해 Scale-Up하여 단일 서버의 성능을 향상시키는 것이다.</p>
<p>그렇다면 트래픽이 서버에서 감당할 수 없을 정도로 많아질 때 마다 Scale-Up을 하면 되는 것일까? 하지만 서버를 무한정 Scale-Up하는 것은 한계가 있고 가격도 점점 비싸진다. </p>
<p>수직으로 확장하는 것은 한계가 있으므로, 수평으로 확장하는 방법을 고려해 볼 수 있다. 서버를 여러대의 부하를 분산하여 성능을 높이는 Scale-Out</p>
<p><a href="https://mermaid.live/edit#pako:eNqNk01PwkAQhv9KM-eGtLv9YHvVo1486npY6EKJtCVL6xch4aCJCdyEhBBJ1IMnD8R44OAvgvIf3EJCLLLROc3OO_NOnk2mA9XY5-BBrRlfVQMmEu3ohEaajHZaqQvWCrQ2F5dcmGcUstHzajhYvr1nX71s-pndjzWTwvmmPw8mmxYfg6z_tJj3CkpFKsvB4265mru-9rPRXTYZFhQ_V15mq8kgexgXFJ4r0_liPtt1q_1cr22mtw088mmUpztgaD8YKoIhNRlSoCE1G1LDITUd-hfeL0C8HxAXAbEaECsAsRoQqwGxGhD_AQg61EXDB6_Gmm2uQ8hFyPI3dPIeCknAQ07Bk6nPxAUFGnXlUItFp3EcgpeIVI6JOK0HW5O05bOEHzaY_LNwWxVyHRcHcRol4NmuuzYBrwPX4JmWVXIMh5imicsOcmxLhxtZdp0SIcQwELEsh2DU1eF2vdYouRa2bGRbZVI2XBMRHbjfSGJxvLm-9RF2vwFOr1fT"><img src="https://mermaid.ink/img/pako:eNqNk01PwkAQhv9KM-eGtLv9YHvVo1486npY6EKJtCVL6xch4aCJCdyEhBBJ1IMnD8R44OAvgvIf3EJCLLLROc3OO_NOnk2mA9XY5-BBrRlfVQMmEu3ohEaajHZaqQvWCrQ2F5dcmGcUstHzajhYvr1nX71s-pndjzWTwvmmPw8mmxYfg6z_tJj3CkpFKsvB4265mru-9rPRXTYZFhQ_V15mq8kgexgXFJ4r0_liPtt1q_1cr22mtw088mmUpztgaD8YKoIhNRlSoCE1G1LDITUd-hfeL0C8HxAXAbEaECsAsRoQqwGxGhD_AQg61EXDB6_Gmm2uQ8hFyPI3dPIeCknAQ07Bk6nPxAUFGnXlUItFp3EcgpeIVI6JOK0HW5O05bOEHzaY_LNwWxVyHRcHcRol4NmuuzYBrwPX4JmWVXIMh5imicsOcmxLhxtZdp0SIcQwELEsh2DU1eF2vdYouRa2bGRbZVI2XBMRHbjfSGJxvLm-9RF2vwFOr1fT?type=png" alt=""></a>
서버가 1대에서 3대로 확장되었고, 3개의 서버에서 게시글 트래픽을 처리하므로, 1대에서 처리되는 부하가 3대로 분산되는 것이다.이제 게시글 기능은 문제 없이 처리될 수 있다.</p>
<p>하지만, 위 구성은 리소스 낭비되는 지점이 있다. 분명 게시글 기능에 대한 부담만 대응하면 충분했다. Monolithic Architecture에서 모든 기능이 단일 애플리케이션에서 함께 배보되므로, 게시글과 관련 없는 기능들도 같이 배푀되어서 리소스를 점유하고 있는 것이다.
<a href="https://mermaid.live/edit#pako:eNpdUT1PwkAY_ivNOxfSL9peBxOioy6Oeg4HPSiRtuRo_SIkDGpM6AYkhkCiDk4ORCVh0D8Ex3_wriREeKe75_NybweqsU_Bg1ozvq4GhCXK8SmOFDHttFJnpBUobcquKNPPMfDRy3qYrd4_-E-PT7_5wzOGi41aDhGS5WfG-5PlorfDVASzygb7cFVmvvX56J6PhzuML5nX2Xqc8afdEiqZ6WK5mO2n1f7XKxv3VkAjH0c4KkvN13z9OFf473A1mOQCqhQKB0pZKkGFOmv44NVIs01VCCkLibxDR9IYkoCGFIMnjj5hlxhw1BWmFonO4jgEL2GpsLE4rQfbkLTlk4QeNYj40HCLMvEmyg7jNErAc00nDwGvAzfg6ZZVtDUb6bpuurZhlywVbgXs2EWEkKYZyLJsZBpdFe7yWq3oWKZVMkqWi1zN0Q2kAvUbScxONgvO99z9A6gzsdw"><img src="https://mermaid.ink/img/pako:eNpdUT1PwkAY_ivNOxfSL9peBxOioy6Oeg4HPSiRtuRo_SIkDGpM6AYkhkCiDk4ORCVh0D8Ex3_wriREeKe75_NybweqsU_Bg1ozvq4GhCXK8SmOFDHttFJnpBUobcquKNPPMfDRy3qYrd4_-E-PT7_5wzOGi41aDhGS5WfG-5PlorfDVASzygb7cFVmvvX56J6PhzuML5nX2Xqc8afdEiqZ6WK5mO2n1f7XKxv3VkAjH0c4KkvN13z9OFf473A1mOQCqhQKB0pZKkGFOmv44NVIs01VCCkLibxDR9IYkoCGFIMnjj5hlxhw1BWmFonO4jgEL2GpsLE4rQfbkLTlk4QeNYj40HCLMvEmyg7jNErAc00nDwGvAzfg6ZZVtDUb6bpuurZhlywVbgXs2EWEkKYZyLJsZBpdFe7yWq3oWKZVMkqWi1zN0Q2kAvUbScxONgvO99z9A6gzsdw?type=png" alt=""></a></p>
<p>직접 코드를 개발하는 상황에서의 문제점도 있다.
흔히 개발 편의와 생산성을 위해 중복을 제거하고, 공통 코드를 관리한다. 위 그림에서 인기글 기능에서 공통 코드를 사용한다.</p>
<p>서비스의 활성화를 위해 인기글 선정 정책에 변화가 필요해졌을 때 인기글의 코드 변경이 필요한상황이라고 가정한다.
<a href="https://mermaid.live/edit#pako:eNqdUsFq20AQ_ZVlQqAF2UiyLFsbKJj22Fx6bJXDRlpZIpLWrKUmqTG4xC0N8S02uMEBN4f00kOa1OBD-kP26h-6slI3zrFzmpn35r1ldjrgMJcCBi9kh45PeIJev7FjJKOd7jc5afmoTfl7yrV3NojRNBsOltc_xH1PXP4Sn8Y27BXsPIikLG4H4myymPc2kH2JLAfnT9tOrnl1JkZ9cTHcQNwc-XaTXQzEl00TmiOX88X85qma99geFdNrAo1dOy7SRs67m2WfZ0j8Hi7PJ_9IqFR6gRp_idvbSJycipOPSHzviekIPdulPCKBi7LxPPs6Fv2fSMwmi7spWt72xbT__GFzyXFIpZgXhCHe8jxHhtJOODugeanKeChLh4Gb-FhvHSkOCxnHWxLbeazS-G8VUKDJAxewR8I2VSAq3i7_upMb2JD4NKI2YJm6hB_YYMddOdQi8VvGIsAJT-UYZ2nTX4ukLZck9FVA5GlE6y6X26X8JUvjBHB1JQG4A0eANcMom6ppaZpWqZu6WTUUOJbtmlm2LEtVdcswTKuidxX4sDJVyzWjYlT1qlG36mpN0y0FqBskjO8Wh7q61-4f6U_8tA"><img src="https://mermaid.ink/img/pako:eNqdUsFq20AQ_ZVlQqAF2UiyLFsbKJj22Fx6bJXDRlpZIpLWrKUmqTG4xC0N8S02uMEBN4f00kOa1OBD-kP26h-6slI3zrFzmpn35r1ldjrgMJcCBi9kh45PeIJev7FjJKOd7jc5afmoTfl7yrV3NojRNBsOltc_xH1PXP4Sn8Y27BXsPIikLG4H4myymPc2kH2JLAfnT9tOrnl1JkZ9cTHcQNwc-XaTXQzEl00TmiOX88X85qma99geFdNrAo1dOy7SRs67m2WfZ0j8Hi7PJ_9IqFR6gRp_idvbSJycipOPSHzviekIPdulPCKBi7LxPPs6Fv2fSMwmi7spWt72xbT__GFzyXFIpZgXhCHe8jxHhtJOODugeanKeChLh4Gb-FhvHSkOCxnHWxLbeazS-G8VUKDJAxewR8I2VSAq3i7_upMb2JD4NKI2YJm6hB_YYMddOdQi8VvGIsAJT-UYZ2nTX4ukLZck9FVA5GlE6y6X26X8JUvjBHB1JQG4A0eANcMom6ppaZpWqZu6WTUUOJbtmlm2LEtVdcswTKuidxX4sDJVyzWjYlT1qlG36mpN0y0FqBskjO8Wh7q61-4f6U_8tA?type=png" alt=""></a>
인기글 코드와 인기글 개발 편의를 위해 사용하던 공통 코드에 변경이 생겼다.
코드에서 인기글과 공통 코드만 변경이 있었고, 인기글 기능에 문제가 없음을 확인했다. 그리고 개발자는 변경 사항을 반영하기 위해 재배포를 진행한다.</p>
<p><a href="https://mermaid.live/edit#pako:eNqlU01v00AQ_SurqXpzIn_FiRcJKYIjXDiCOTj2OrZqe6ONTVuiSEGliKqRADURKEpF4ACXHiqgkg_wZzjGG_EXWCfFaSJxgTntzHvz3uxXDxzqEsDghXTf8W2WoHsPrBiJ6KatNrM7PuoS9oQw5ZEFfDxbjIb5pwv-fcDPv_HjdxY8XrGLsAXl52T2K3uF5l-G_HQ6zwYbhNaakA_PtlGncPh4ysfP-WS0gbgF8uFyMRnyl5uWpEDOs3l2ua3mCaScAq26SwKJXSteLZsF7-vV4sUV4j9G-dl0TUKVym3U_EPc3UX86IQfPUP884DPxtenlByGRFC9IAzxjuc5IqRuwugeKVJZxHVa2Q_cxMdq50ByaEgZ3hHYrZsqzf9QWc_49nX-_gIt3kzF1lF-nBVbumFilyaktmFiGNsm2t9Hbf2zCkjQZoEL2LPDLpEgIiyyixx6hYEFiU8iYgEWS9dmexZYcV80dez4IaUR4ISloo3RtO2XImnHtRNyN7DFi43KKhPXTNgdmsYJYE1bagDuwQFgRderhmyYiqJoDUM1aroEh6JcN6qmacqyauq6YWpqX4KnS1e5Wtc1vabW9IbZkOuKakpA3CCh7P7qAy3_Uf83S6EwVA"><img src="https://mermaid.ink/img/pako:eNqlU01v00AQ_SurqXpzIn_FiRcJKYIjXDiCOTj2OrZqe6ONTVuiSEGliKqRADURKEpF4ACXHiqgkg_wZzjGG_EXWCfFaSJxgTntzHvz3uxXDxzqEsDghXTf8W2WoHsPrBiJ6KatNrM7PuoS9oQw5ZEFfDxbjIb5pwv-fcDPv_HjdxY8XrGLsAXl52T2K3uF5l-G_HQ6zwYbhNaakA_PtlGncPh4ysfP-WS0gbgF8uFyMRnyl5uWpEDOs3l2ua3mCaScAq26SwKJXSteLZsF7-vV4sUV4j9G-dl0TUKVym3U_EPc3UX86IQfPUP884DPxtenlByGRFC9IAzxjuc5IqRuwugeKVJZxHVa2Q_cxMdq50ByaEgZ3hHYrZsqzf9QWc_49nX-_gIt3kzF1lF-nBVbumFilyaktmFiGNsm2t9Hbf2zCkjQZoEL2LPDLpEgIiyyixx6hYEFiU8iYgEWS9dmexZYcV80dez4IaUR4ISloo3RtO2XImnHtRNyN7DFi43KKhPXTNgdmsYJYE1bagDuwQFgRderhmyYiqJoDUM1aroEh6JcN6qmacqyauq6YWpqX4KnS1e5Wtc1vabW9IbZkOuKakpA3CCh7P7qAy3_Uf83S6EwVA?type=png" alt=""></a>
그런데 문제가 발생한다. 건드린적 없던 게시글과 댓글 기능이 동작하지 않는 것이다. </p>
<p>공통 코드의 사용처를 살펴보니, 게시글과 댓글 기능에서도 함께 사용되고 있었다.
인기글을 위한 변경 사항이 게시글과 댓글 기능에선 호환되지 않던 것이다.</p>
<p>단일 코드베이스에서 통합 관리하다보면, 공통 코드 관리 등으로 변경 사항이 타 기능에 예기치 않게 전파될 수 있다. 전파되는 범위는 시스템이 커질수록 넓어지고 복잡해진다. 
공통 코드가 아니더라도, 연관된 기능에 대해 서로의 코드를 침범하며, 코드 간 결합도가 높아지고, 각 기능의 응딥도가 낮아질 수 있다. 이는 유지보수를 점점 어렵게 만든다.</p>
<p><a href="https://mermaid.live/edit#pako:eNqdUsFq20AQ_ZVlQqAF2UiyLFsbKJj22Fx6bJXDRlpZIpLWrKUmqTG4xC0N8S02uMEBN4f00kOa1OBD-kP26h-6slI3zrFzmpn35r1ldjrgMJcCBi9kh45PeIJev7FjJKOd7jc5afmoTfl7yrV3NojRNBsOltc_xH1PXP4Sn8Y27BXsPIikLG4H4myymPc2kH2JLAfnT9tOrnl1JkZ9cTHcQNwc-XaTXQzEl00TmiOX88X85qma99geFdNrAo1dOy7SRs67m2WfZ0j8Hi7PJ_9IqFR6gRp_idvbSJycipOPSHzviekIPdulPCKBi7LxPPs6Fv2fSMwmi7spWt72xbT__GFzyXFIpZgXhCHe8jxHhtJOODugeanKeChLh4Gb-FhvHSkOCxnHWxLbeazS-G8VUKDJAxewR8I2VSAq3i7_upMb2JD4NKI2YJm6hB_YYMddOdQi8VvGIsAJT-UYZ2nTX4ukLZck9FVA5GlE6y6X26X8JUvjBHB1JQG4A0eANcMom6ppaZpWqZu6WTUUOJbtmlm2LEtVdcswTKuidxX4sDJVyzWjYlT1qlG36mpN0y0FqBskjO8Wh7q61-4f6U_8tA"><img src="https://mermaid.ink/img/pako:eNqdUsFq20AQ_ZVlQqAF2UiyLFsbKJj22Fx6bJXDRlpZIpLWrKUmqTG4xC0N8S02uMEBN4f00kOa1OBD-kP26h-6slI3zrFzmpn35r1ldjrgMJcCBi9kh45PeIJev7FjJKOd7jc5afmoTfl7yrV3NojRNBsOltc_xH1PXP4Sn8Y27BXsPIikLG4H4myymPc2kH2JLAfnT9tOrnl1JkZ9cTHcQNwc-XaTXQzEl00TmiOX88X85qma99geFdNrAo1dOy7SRs67m2WfZ0j8Hi7PJ_9IqFR6gRp_idvbSJycipOPSHzviekIPdulPCKBi7LxPPs6Fv2fSMwmi7spWt72xbT__GFzyXFIpZgXhCHe8jxHhtJOODugeanKeChLh4Gb-FhvHSkOCxnHWxLbeazS-G8VUKDJAxewR8I2VSAq3i7_upMb2JD4NKI2YJm6hB_YYMddOdQi8VvGIsAJT-UYZ2nTX4ukLZck9FVA5GlE6y6X26X8JUvjBHB1JQG4A0eANcMom6ppaZpWqZu6WTUUOJbtmlm2LEtVdcswTKuidxX4sDJVyzWjYlT1qlG36mpN0y0FqBskjO8Wh7q61-4f6U_8tA?type=png" alt=""></a>
또한, 인기글 기능만 변경하려고 했음에도 모든 기능이 다시 배포되어야 했다. 모든 기능이 단일 애플리케이션에 함계 관리되기 때문이다. 애플리케이션이 무거워질수록, 빌드 및 배포 시간은 늘어난다.</p>
<p>만약 인기글 기능 변경 사항에 오류가 남아있어서 장애가 발생했다면? 인기글 기능만 사용할 수 없다면 다행이다.
또는, 변경이 전파된 게시글, 댓글까지만 사용할 수 없어도 다행일 것이다.
최악의 경우 모든 기능이 단일 애플리케이션에서 함께 배포 및 관리되므로, 시스템의 모든 기능으로 장애가 전파될 수 있다.</p>
<h4 id="정리">정리</h4>
<ul>
<li>모든 기능이 단일 코드베이스로 결합된 아키텍처</li>
<li>소규모 시스템에서 개발 및 배포가 간단하기 때문에 많이 선택</li>
<li>빠르고 효율적으로 개발할 수 있음<h5 id="하지만">하지만</h5>
</li>
<li>특정 부분만 확장하기 어려움</li>
<li>변경 사항이 시스템 전체에 영향을 미침</li>
<li>대규모 시스템에서 복잡도가 커지고 개발의 어려움</li>
</ul>
<hr>
<h2 id="micorservice-architecture">Micorservice Architecture</h2>
<p><a href="https://mermaid.live/edit#pako:eNp1kb1OwzAUhV8lunNUxYmT1l5hhIURzODWbhPRxJWb8FdVYuiAaDdAQlUZQDxAxRReqTXvgJNKlSqRO9nH3z3nyncCPSUkUOgP1U0v5jp3Ts5Y5tgaF92B5qPYGUt9LTW6YOAwuNw9VsWtsvlemPlqUz44Zrba_szM09cB07XMdvHcDPQsYD7n5nVmli8NjKiYj_XvcmEe3xoYWTHv5aZcN2f1DweuHf9BZSZYBi4MdCKA9vlwLF1IpU55dYdJRTHIY5lKBtQeBddXDFg2tU0jnp0rlQLNdWHbtCoG8d6kGAmey-OE249N96q2gVIfqSLLgfoY1SZAJ3ALFGHciryIIISCTuRHIXbhzsrtqEUI8TyfYByRwJ-6cF_Heq02DnDoh7hDOl4bBS5IkeRKn-72XK97-gfnFbxy"><img src="https://mermaid.ink/img/pako:eNp1kb1OwzAUhV8lunNUxYmT1l5hhIURzODWbhPRxJWb8FdVYuiAaDdAQlUZQDxAxRReqTXvgJNKlSqRO9nH3z3nyncCPSUkUOgP1U0v5jp3Ts5Y5tgaF92B5qPYGUt9LTW6YOAwuNw9VsWtsvlemPlqUz44Zrba_szM09cB07XMdvHcDPQsYD7n5nVmli8NjKiYj_XvcmEe3xoYWTHv5aZcN2f1DweuHf9BZSZYBi4MdCKA9vlwLF1IpU55dYdJRTHIY5lKBtQeBddXDFg2tU0jnp0rlQLNdWHbtCoG8d6kGAmey-OE249N96q2gVIfqSLLgfoY1SZAJ3ALFGHciryIIISCTuRHIXbhzsrtqEUI8TyfYByRwJ-6cF_Heq02DnDoh7hDOl4bBS5IkeRKn-72XK97-gfnFbxy?type=png" alt=""></a></p>
<p>Micorservice Architecture에서는 시스템이 작고 독립적인 서비스(Microservice)로 구성된다. 
단일 애플리케이션에 모두 포함 됐던 기능이 각각의 마이크로서비스로 분리되고, 하나의 애플리케이션에 만들어진 기능이 6개의 애플리케이션으로 분리된 것이다.</p>
<blockquote>
<p>각각의 마이크로 서비스는 독립적으로 배포될 수 있다. 6개의 마이크로 서비스가 배포되는 상황을 가정</p>
</blockquote>
<p><a href="https://mermaid.live/edit#pako:eNqdlL1OwzAQx18lujmq8uGkcVYYYWEEM7ix01Q0SeUmfFWVGDIg2g2QUFUGEA9QdSqv1IZ3IE6looIwojfZ97-_f5eL5QEEKePgQ9hNL4KIikw7OCKJVkU_b7UF7UVan4tzLswTAhqB07Uo42v1rdSQtcv5uBxNl4sbrSymq_eivHvbcsugslDK88L8KVob0doSecJI8jtc2lbjezW5pSK3diTb0vY6Kh-LcvKggAcqeKCE_z17JO0vs4_JuLx9UjTBVE2wHSfgSNvzYrmYqcfPVXC-I9zdvnX1DBQ9hKoewn_8hToFOrRFh4Ef0m6f6xBzEVO5h4GsIpBFPOYE_GrJqDgjQJJhZerR5DhNY_AzkVc2kebtaHNI3mM04_sdWn1nvMmKCsjFXponGfie59SHgD-AS_BNhBqu4WLTNG3PtVwH6XBVpZtuA2NsGBZGyMW2NdThusYajSaykWM5yMOe0TQtrANnnSwVh-uHoX4fhp9GA2cX"><img src="https://mermaid.ink/img/pako:eNqdlL1OwzAQx18lujmq8uGkcVYYYWEEM7ix01Q0SeUmfFWVGDIg2g2QUFUGEA9QdSqv1IZ3IE6looIwojfZ97-_f5eL5QEEKePgQ9hNL4KIikw7OCKJVkU_b7UF7UVan4tzLswTAhqB07Uo42v1rdSQtcv5uBxNl4sbrSymq_eivHvbcsugslDK88L8KVob0doSecJI8jtc2lbjezW5pSK3diTb0vY6Kh-LcvKggAcqeKCE_z17JO0vs4_JuLx9UjTBVE2wHSfgSNvzYrmYqcfPVXC-I9zdvnX1DBQ9hKoewn_8hToFOrRFh4Ef0m6f6xBzEVO5h4GsIpBFPOYE_GrJqDgjQJJhZerR5DhNY_AzkVc2kebtaHNI3mM04_sdWn1nvMmKCsjFXponGfie59SHgD-AS_BNhBqu4WLTNG3PtVwH6XBVpZtuA2NsGBZGyMW2NdThusYajSaykWM5yMOe0TQtrANnnSwVh-uHoX4fhp9GA2cX?type=png" alt=""></a>
각 서비스가 독립적인 애플리케이션으로 배포되었고, 배포되는 서버도 독립적으로 구성될 수 있다. 각 서비스는 유기적으로 연결되어 통신하며 하나의 시스템을 이룬다.</p>
<p>각 서비스의 코드는 독립적으로 관리 및 배포되므로, 개별 서비스 내에서는 복잡도가 낮고, 빠르게 빌드 및 배포할 수 있다.</p>
<p>게시글에 대한 트래픽에 대응해야 한다면??
<a href="https://mermaid.live/edit#pako:eNqdVEFv0zAU_ivR2zVUieOkta9w3C4cIRxc22miJXHlOmyjqsShB8R6AyQ0jQOIHzDtVP5SF_4DccYK7TQj5pP9vvfe933PlufAlZBAISvVCc-ZNt7h87T2ujVrxhPNprk3k_q11OHLFLwUXt2Cdv3Z7aUGNndzvWrPLzfrt167vLz5sWzff9-ptovZRAtfL8P7INqC6D4YbcFoB5S1SOu_lJmzUnbZXlaUJT3gPMs492dGq2NJD5Acigj9Pj45KYTJKZqe-lyVSt_BO-32jFqJN6sPbpdjl8vxQy73jewx9_6_nbeflu3FRwc5d5FzJ_m_7xnb8q9XPy9W7bvPDhHCJUI8cgKxLfuy3qyv3OOXLnL5SPJk94X3M3BoyFwasv-4hT4EPkx0IYBmrJxJHyqpK2bPMLdZKZhcVjIF2m0F08cppPWiK5qy-oVSFVCjm65Mq2aSb5s0U8GMfFawzme1jeqOUOqnqqkNUILjvgnQOZwCDTEeJEFCwjCMRglKYuzDWRceJgNCSBAggnFCIrTw4U1PGwyGOMIxivGIjIJhiIgPUhRG6aPbT6j_ixa_AIUjiVc"><img src="https://mermaid.ink/img/pako:eNqdVEFv0zAU_ivR2zVUieOkta9w3C4cIRxc22miJXHlOmyjqsShB8R6AyQ0jQOIHzDtVP5SF_4DccYK7TQj5pP9vvfe933PlufAlZBAISvVCc-ZNt7h87T2ujVrxhPNprk3k_q11OHLFLwUXt2Cdv3Z7aUGNndzvWrPLzfrt167vLz5sWzff9-ptovZRAtfL8P7INqC6D4YbcFoB5S1SOu_lJmzUnbZXlaUJT3gPMs492dGq2NJD5Acigj9Pj45KYTJKZqe-lyVSt_BO-32jFqJN6sPbpdjl8vxQy73jewx9_6_nbeflu3FRwc5d5FzJ_m_7xnb8q9XPy9W7bvPDhHCJUI8cgKxLfuy3qyv3OOXLnL5SPJk94X3M3BoyFwasv-4hT4EPkx0IYBmrJxJHyqpK2bPMLdZKZhcVjIF2m0F08cppPWiK5qy-oVSFVCjm65Mq2aSb5s0U8GMfFawzme1jeqOUOqnqqkNUILjvgnQOZwCDTEeJEFCwjCMRglKYuzDWRceJgNCSBAggnFCIrTw4U1PGwyGOMIxivGIjIJhiIgPUhRG6aPbT6j_ixa_AIUjiVc?type=png" alt=""></a>
모든 기능이 함께 배포되어야 했던 Monolithic Architecture와 달리 Microservice Archtecture에서는 게시글 서비스의 서버만 증설해볼 수 있다. 다른 서비스들과 독립적으로 배포되기 때문에, 게시글에 대한 리소스만 추가로 점유하게 된다.</p>
<p><a href="https://mermaid.live/edit#pako:eNqdVM1O4zAQfpVouGarJHXSxhzhCJc97oaDGztNRBJXjrPAVpU49ICgt92VVggOu-IBEKfySiW8A3ZABYpq2J2T5-eb75ux5THEnDLAkOT8IE6JkNbO56i0lFX1YCjIKLUqJr4x4X6NwIpg7zGp7fm0Uuro2sXNrDm7WMyPrWZ6cXc7bU6vXqG1EV2o0zdT923SWya9NbyspFH5MrFekm52N_th1jMw6Rms07OiYpW5q2F_z5pf0-b8p4E8NpHHRvL3bwRp-J_r-_NZc_LbIIKaRND_3ICvYZfzxfzavH5mImcf2kAlj3K25LWSLM_xRpLEyuxKCr7PtOsoe3I_HWRUptgbHdoxz7nAGyq3-ZGVBq8febtcw3CJabjkH663DYENQ5FRwAnJK2ZDwURBtA9jXRWBTFnBIsDqSInYjyAqJwo0IuUXzgvAUtQKJng9TJdN6hElkm1nRM1ZLKNCETKxxetSAnYd5LddAI_hUPkIdQInCF3X7fYDL_CRDUcq3As6YRg6jhciFIRdb2LD95bX6fRQF_mej_ph3-m5XmgDo5nkYvfxI2r_o8kDlzyGsg"><img src="https://mermaid.ink/img/pako:eNqdVM1O4zAQfpVouGarJHXSxhzhCJc97oaDGztNRBJXjrPAVpU49ICgt92VVggOu-IBEKfySiW8A3ZABYpq2J2T5-eb75ux5THEnDLAkOT8IE6JkNbO56i0lFX1YCjIKLUqJr4x4X6NwIpg7zGp7fm0Uuro2sXNrDm7WMyPrWZ6cXc7bU6vXqG1EV2o0zdT923SWya9NbyspFH5MrFekm52N_th1jMw6Rms07OiYpW5q2F_z5pf0-b8p4E8NpHHRvL3bwRp-J_r-_NZc_LbIIKaRND_3ICvYZfzxfzavH5mImcf2kAlj3K25LWSLM_xRpLEyuxKCr7PtOsoe3I_HWRUptgbHdoxz7nAGyq3-ZGVBq8febtcw3CJabjkH663DYENQ5FRwAnJK2ZDwURBtA9jXRWBTFnBIsDqSInYjyAqJwo0IuUXzgvAUtQKJng9TJdN6hElkm1nRM1ZLKNCETKxxetSAnYd5LddAI_hUPkIdQInCF3X7fYDL_CRDUcq3As6YRg6jhciFIRdb2LD95bX6fRQF_mej_ph3-m5XmgDo5nkYvfxI2r_o8kDlzyGsg?type=png" alt=""></a>
인기글에 대한 코드 변경 또는 장애가 발행하더라도 다른 서비스의 코드는 물리적으로 분리되어 있기 때문에 전파 범위가 줄어든다.</p>
<p>얼핏 보면 Monolithic의 문제를 모두 해결하는 만능 아키텍처인 것 같지만, 서비스 간 통신 비용, 트랜잭션 관리, 서비스 분리 기준, 모니터링, 개발 비용, 테스트, 설계 등 고려해야할 부분이 엄청 많다.</p>
<h4 id="정리-1">정리</h4>
<ul>
<li>시스템이 작도 독립전이 서비스(Microservice)로 구성</li>
<li>각 서비스는 단일 기능을 담당하며, 독립적 배포가능</li>
<li>서비스 단위로 유연한 확장 가능<h5 id="하지만-1">하지만</h5>
</li>
<li>서비스 간 복잡한 통신 및 모니터링 필요</li>
<li>데이터 일관성 및 트랜잭션 관리의 어려움</li>
<li>여러운 개발 난이도</li>
</ul>
<hr>
<table>
<thead>
<tr>
<th align="center">구분</th>
<th align="center">Monolithic Architecture</th>
<th align="center">Microservice Architecture</th>
</tr>
</thead>
<tbody><tr>
<td align="center">구조</td>
<td align="center">통합</td>
<td align="center">분산</td>
</tr>
<tr>
<td align="center">결합도</td>
<td align="center">높음</td>
<td align="center">낮음</td>
</tr>
<tr>
<td align="center">확장성</td>
<td align="center">개별 확장 어려움</td>
<td align="center">개별 확장 쉬움</td>
</tr>
<tr>
<td align="center">배포</td>
<td align="center">통합 배포</td>
<td align="center">독립 배포</td>
</tr>
<tr>
<td align="center">기술 스택</td>
<td align="center">단일 기술 스택</td>
<td align="center">여러 기술 스택 가능</td>
</tr>
<tr>
<td align="center">변경 전파 범위</td>
<td align="center">시스템 전체</td>
<td align="center">서비스 또는 전파 범위 이내</td>
</tr>
<tr>
<td align="center">통신 비용</td>
<td align="center">낮음</td>
<td align="center">높음</td>
</tr>
<tr>
<td align="center">개발 난이도</td>
<td align="center">쉬움(소규모 일수록)</td>
<td align="center">어려움</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[대규모 시스템 서버 ]]></title>
            <link>https://velog.io/@kooks-dev/%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%9C%EB%B2%84</link>
            <guid>https://velog.io/@kooks-dev/%EB%8C%80%EA%B7%9C%EB%AA%A8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%9C%EB%B2%84</guid>
            <pubDate>Fri, 07 Nov 2025 12:39:41 GMT</pubDate>
            <description><![CDATA[<p><a href="https://mermaid.live/edit#pako:eNpFUMtugzAQ_BW0Z4IwGIN9qNQmx_bS3hrnYMUbQAU72pi-EP9eQ6V2TzualzQznL1FUHAZ_Me5MxSSx2ftknj3x_3QowunZLe7Sx6OL0jvSCftVhpSaKm3oC5muGEKI9JoVgzzSmsIHY6oQcXXGnrToN0STVfjXr0fQQWaoo381HZ_IdPVmoCH3rRk_iXoLNLeTy6AYvUWAWqGz4g4z0QuJGOsbEQhKp7C1yoSmZQyzwvJuZBlsaTwvZXmWc1LXhUVb2ST16yQKaDtg6en3xW2MZYffHBTxg"><img src="https://mermaid.ink/img/pako:eNpFUMtugzAQ_BW0Z4IwGIN9qNQmx_bS3hrnYMUbQAU72pi-EP9eQ6V2TzualzQznL1FUHAZ_Me5MxSSx2ftknj3x_3QowunZLe7Sx6OL0jvSCftVhpSaKm3oC5muGEKI9JoVgzzSmsIHY6oQcXXGnrToN0STVfjXr0fQQWaoo381HZ_IdPVmoCH3rRk_iXoLNLeTy6AYvUWAWqGz4g4z0QuJGOsbEQhKp7C1yoSmZQyzwvJuZBlsaTwvZXmWc1LXhUVb2ST16yQKaDtg6en3xW2MZYffHBTxg?type=png" alt=""></a>
Client는 Server로 요청을 보내고 Server는 요청에 대해 필요한 작업을 처리한다.</p>
<blockquote>
<p>Server -&gt; Spring Boot</p>
</blockquote>
<p><a href="https://mermaid.live/edit#pako:eNo9UMtugzAQ_BW0Z4KIsQH7UKklx-TS3opzcOINoIKNHKM-EP9eQ6vsaXZGMyPNDFerEQTcevt5bZXz0fFVmijcc131HRp_jna7p-ilfhtdZ5roYu0_VdUH5dVF3fEszeqBGBrXaRA31d8xhgHdoNYf5lWW4FscUIIIUCv3IUGaJZhGZd6tHUB4NwWbs1PTPkKmUSuPh041Tg0P1qHR6Co7GQ-CsHILATHDF4g9KxKWsbwoeMoDIEH9DjRnCaGMkiKjJee0WGL42WrTJC8ZyQhN05LTfc5iQN15605_22wTLb9sSFrY"><img src="https://mermaid.ink/img/pako:eNo9UMtugzAQ_BW0Z4KIsQH7UKklx-TS3opzcOINoIKNHKM-EP9eQ6vsaXZGMyPNDFerEQTcevt5bZXz0fFVmijcc131HRp_jna7p-ilfhtdZ5roYu0_VdUH5dVF3fEszeqBGBrXaRA31d8xhgHdoNYf5lWW4FscUIIIUCv3IUGaJZhGZd6tHUB4NwWbs1PTPkKmUSuPh041Tg0P1qHR6Co7GQ-CsHILATHDF4g9KxKWsbwoeMoDIEH9DjRnCaGMkiKjJee0WGL42WrTJC8ZyQhN05LTfc5iQN15605_22wTLb9sSFrY?type=png" alt=""></a>
Spring Boot는 Client의 요청을 처리하면서 어딘가에 상태를 관리해야 할 수 있다. 데이터는 데이터베이스에 의해 안전하게 괸리 될 수 있다.</p>
<p>서비스가 활성되면서 Client의 요청이 많아졌다고 가정했을 때, 서버의 리소스 부족으로 인해, 단일 서버, 단일 애플리케이션에서 감당할 수 없는 지경이 된다.</p>
<p><a href="https://mermaid.live/edit#pako:eNp1UclugzAQ_RU0ZxIRY7P4UKklx_bS3Ao9OGECqMFGg-kW5d9roE0TqZ3DaN57ns1zhJ0pESTsD-ZtVyuy3v1joT1nt3l2aFDb5xnOvh-2Famu9jZIr0gzOdrdKt901OjK2xrzkzMJ7D8h_ENAXX539xaLG1f1CrErFF4ONs8zCdkvm-VrZdVW9XjeotDgQ0VNCXKvDj360CK1asRwHB8VYGtssQDpwlLRSwGFPrmkTuknY1qQlgaXRmao6nORoSuVxXWj3Pe0Z5bcPkiZGbQFmYR8KgLyCO8gVyJeilBEcZwGqQtY4sOHo1OxZFxwFoc8SVMen3z4nNoGyygRLGQ8CJKUryLhA5aNNfQwn3C65OkLT6KGJQ"><img src="https://mermaid.ink/img/pako:eNp1UclugzAQ_RU0ZxIRY7P4UKklx_bS3Ao9OGECqMFGg-kW5d9roE0TqZ3DaN57ns1zhJ0pESTsD-ZtVyuy3v1joT1nt3l2aFDb5xnOvh-2Famu9jZIr0gzOdrdKt901OjK2xrzkzMJ7D8h_ENAXX539xaLG1f1CrErFF4ONs8zCdkvm-VrZdVW9XjeotDgQ0VNCXKvDj360CK1asRwHB8VYGtssQDpwlLRSwGFPrmkTuknY1qQlgaXRmao6nORoSuVxXWj3Pe0Z5bcPkiZGbQFmYR8KgLyCO8gVyJeilBEcZwGqQtY4sOHo1OxZFxwFoc8SVMen3z4nNoGyygRLGQ8CJKUryLhA5aNNfQwn3C65OkLT6KGJQ?type=png" alt=""></a></p>
<p>단순히 Scale-Up을 고려할 수 있지만 한계는 존재한다. 애플리케이션을 여러 서버에서 동시에 실행하여 처리를 분산할 수 있다. <strong>Scale-Out</strong></p>
<blockquote>
<p>Scale-Up : 단일 서버의 성능을 향상시키는 것 (수직확장)
Scale-Out : 서버를 추가하여 성능을 향상시키는 것(수평 확장)</p>
</blockquote>
<p>Client는 애플리케이션으로 요청을 분산하여 처리할 수 있다. 하지만 서버가 증설될 때 마다 Client가 이러한 정보를 모두 알 수 없고, 알아야 할 필요도 없다.</p>
<p><a href="https://mermaid.live/edit#pako:eNp1UU1zgjAQ_SuZPaMDgQjh0BmFo73UW8HDaiIwhYSJoV-O_70RtK0z7R529r23X8meYK-FhBQOrX7b12gsWT-VijjLi6xtpLLbCS6LtUZBVtii2ktzZXMymz2Q5QQmfxx2lcG-JhtpXqWZyIutgmLTm0ZVZKf1re0o0P-E8A9BKnHdaJy9Cu4QvUPh78WmfUYh-2GzIkeLOzzK7S25VOBBZRoB6QHbo_Sgk6bDC4bTJakEW8tOlpC6UKB5KaFUZ1fUo3rWuoPUmsGVGT1U9XeToRdoZd6g-57umzXuPdJkelAWUh77YxNIT_AOacDiOQvZIo65z11AEw8-HM3ZnEYsonEYJZxH8dmDz3GsP18kjIY08v2ER8GCeSBFY7V5nK48Hvv8Bdtyj8s"><img src="https://mermaid.ink/img/pako:eNp1UU1zgjAQ_SuZPaMDgQjh0BmFo73UW8HDaiIwhYSJoV-O_70RtK0z7R529r23X8meYK-FhBQOrX7b12gsWT-VijjLi6xtpLLbCS6LtUZBVtii2ktzZXMymz2Q5QQmfxx2lcG-JhtpXqWZyIutgmLTm0ZVZKf1re0o0P-E8A9BKnHdaJy9Cu4QvUPh78WmfUYh-2GzIkeLOzzK7S25VOBBZRoB6QHbo_Sgk6bDC4bTJakEW8tOlpC6UKB5KaFUZ1fUo3rWuoPUmsGVGT1U9XeToRdoZd6g-57umzXuPdJkelAWUh77YxNIT_AOacDiOQvZIo65z11AEw8-HM3ZnEYsonEYJZxH8dmDz3GsP18kjIY08v2ER8GCeSBFY7V5nK48Hvv8Bdtyj8s?type=png" alt=""></a>
트래픽을 라우팅 및 분산하기 위한 도구로 Load Balancer를 활용할 수 있다. 
Client는 Load Balancer로 요청을 보내면, Load Balancer는 요청을 적절히 분산하여 서버로 전달한다.</p>
<p>Scale-Out은 Load Balancer, Database에 대해서도 처리될 수 있지만, Client가 요청을 처리하는 과정이 복잡해 질 수록 응답은 느려질 수 있다.
그래서빠른 성능을 위해 캐시(Cache)를 사용한다.</p>
<p><a href="https://mermaid.live/edit#pako:eNp1Uk1zgjAQ_SvMntGBAPJx6EzBo73UW6GHlURgCgkTQ7_U_94AxarVPeW9t3n7NpM95IIyiGBbi4-8RKmM1XPGDV3LNKkrxtXrCH9JYzZ7MFYCaYw18pzJkd91m0JiW15IRtojY4K_Rn092vclcl9ybkqM04xfxVgz-T5l6yu203UrK14YGyHUmWdM7gnODWEYNaYcHiK2z-EhwbxkB-152eRM8cZUAzn1LlHhBnfs3wZ_wpQosdOJPMuZkJusc82eoveTwIRCVhSiLdY7ZkLDZIM9hn3flIEqWcMyiPSRonzLIONHfalF_iJEA5GSnb4mRVeUJ5OupajYskK9QHNipR7MZCI6riCyXTsYXCDaw6fGnj_3HG_h-6EV6gPR6pemQ29OXM8lvuMGYej6RxO-h7nWfBF4xCGuZQWhay88ExitlJBP4zcefvPxBxtd1qU"><img src="https://mermaid.ink/img/pako:eNp1Uk1zgjAQ_SvMntGBAPJx6EzBo73UW6GHlURgCgkTQ7_U_94AxarVPeW9t3n7NpM95IIyiGBbi4-8RKmM1XPGDV3LNKkrxtXrCH9JYzZ7MFYCaYw18pzJkd91m0JiW15IRtojY4K_Rn092vclcl9ybkqM04xfxVgz-T5l6yu203UrK14YGyHUmWdM7gnODWEYNaYcHiK2z-EhwbxkB-152eRM8cZUAzn1LlHhBnfs3wZ_wpQosdOJPMuZkJusc82eoveTwIRCVhSiLdY7ZkLDZIM9hn3flIEqWcMyiPSRonzLIONHfalF_iJEA5GSnb4mRVeUJ5OupajYskK9QHNipR7MZCI6riCyXTsYXCDaw6fGnj_3HG_h-6EV6gPR6pemQ29OXM8lvuMGYej6RxO-h7nWfBF4xCGuZQWhay88ExitlJBP4zcefvPxBxtd1qU?type=png" alt=""></a>
캐시는 각 구간마다 적절히 적용 해 브라우저에서 자제적인 캐시를 활용, DNS 쿼리 결과를 캐시, 데이터베이스보다 빠르게 데이터에 접근, CDN 기술에 활용, 캐시라는 개념을 다양한 구간에 적용될 수 있다.
Cache도 필요하면 Scale-Out을 적용하여 단일 서버의 부하를 줄여줄 수 있다.</p>
<p>안전성에 대한 문제도 있다. 네트워크는 안전하지 않기 때문에 언제든 순단 가능성이 있고, 서버와 데이터는 시스템 오류 또는 자연 재해  등으로 언제든 중단 및 유실될 수 있다. 또, 지리적으로 서버와 멀리 떨어져 있다면, 응답 시간에 큰 지연이 생길 수 있다.</p>
<p>문제를 방지하기 위해, 서버를 지리적으로 여러 장소에 구설 할 수도 있다. 서버가 설치된 데이터센터를 다중화하는 것이다. 분산된 각 서버에 대한 Client의 라우팅을 위해 DNS를 활용할 수도 있고, 더욱 정밀하고 복잡한 라우팅이 필요한 경우 GSLB 등 다양한 방법을 활용할 수도 있다.</p>
<p><a href="https://mermaid.live/edit#pako:eNqNVctuqzAQ_RU0axrhBwazqNTCst20uwt34QY3RA0QOdDHTfPvl4QkwmHS4gWyzxyfY88M8hbmda4hgtdV_TEvlGmch6escrpxl8arpa6av_2y_27al4VR68J5qFV-r1aqmmtDUudI2o97kg6Dwwi9GmFYRFd5Vv3gS21fflXdvxoRv_jeOTc3t853rOaF_rYv_RuBjs7-rM37ZbZikvbwEKMIxmzscEZE3c5JzBElH8HENHVmqweIUohgcpo6v8iMh0gRLF-EIgb91CrZvlanUh2LMYlFxyw6ZrFJLD5qi0Q16kVttN0YCUlPgSFKUZRdouMUnwh2gyQc1fNRVEx3sRslCVC9EEXldBd-kTEPFSR4JglFjfrpsTusn_tcpyGFohQ6pDCUwoYUjlI4uLAwyxyiV7XaaBdKbUq1X8N2vzuDptClziDqprkybxlk1a7btFbVn7ouIWpM220zdbsoziLtOleNTpaqy2V5Rk13eW3iuq0aiKgIg4MKRFv4hIhwPhOekIQQFgoqfO7CVwcHYial9DwqOReS0Z0L_w6-3izgjPvU56EMvYBQ6YLOl01tHvvn5vDq7P4DxVvxLQ"><img src="https://mermaid.ink/img/pako:eNqNVctuqzAQ_RU0axrhBwazqNTCst20uwt34QY3RA0QOdDHTfPvl4QkwmHS4gWyzxyfY88M8hbmda4hgtdV_TEvlGmch6escrpxl8arpa6av_2y_27al4VR68J5qFV-r1aqmmtDUudI2o97kg6Dwwi9GmFYRFd5Vv3gS21fflXdvxoRv_jeOTc3t853rOaF_rYv_RuBjs7-rM37ZbZikvbwEKMIxmzscEZE3c5JzBElH8HENHVmqweIUohgcpo6v8iMh0gRLF-EIgb91CrZvlanUh2LMYlFxyw6ZrFJLD5qi0Q16kVttN0YCUlPgSFKUZRdouMUnwh2gyQc1fNRVEx3sRslCVC9EEXldBd-kTEPFSR4JglFjfrpsTusn_tcpyGFohQ6pDCUwoYUjlI4uLAwyxyiV7XaaBdKbUq1X8N2vzuDptClziDqprkybxlk1a7btFbVn7ouIWpM220zdbsoziLtOleNTpaqy2V5Rk13eW3iuq0aiKgIg4MKRFv4hIhwPhOekIQQFgoqfO7CVwcHYial9DwqOReS0Z0L_w6-3izgjPvU56EMvYBQ6YLOl01tHvvn5vDq7P4DxVvxLQ?type=png" alt=""></a></p>
<p>단일 애플리케이션은 커질수록 관리가 어려워질 수있다. 서버가 처리해야할 트래픽이 너무 많아지면, 리소스가 부족할 수도 있고, 시스템이 커질수록 애플리케이션의 유지보수가 어려워질 수도 있기 때문이다. 
단일 애플리케이션을 기능에 맞는 여러 개의 애플리케이션으로 분리하여 관리할 수 있다. 각 애플리케이션은 담당한 기능에 대해 개별 작업만 처리할 수있는 것이다.</p>
<p>예를 들어 A 애플리케이션은 게시글 기능만, B 애플리케이션은 댓글 기능만 처리한다. 필요하면, 데이터베이스도 각 기능 별로 분리하여 구성할 수 있다.</p>
<p>분리된 웹 애플리케이션이 하나의 서비스를 이루려면 서로 네트워크 통신이 필요할 수 있다. API를 통해 직접적으로 통신할 수도 있지만, <strong>메시지(이벤트)</strong> 를 통해 간접적으로 통신할 수도 있다. </p>
<p>이처럼 시스템은 성능, 안전성, 확장성이라는 세 가지 축을 균형있게 고려해야 한다. 
단순히 서버를 늘리거나 기능을 분리하는 것만으로 충분하지 ㅇ낳다. 서비스의 특성과 트래픽 패턴에 따라 적절한 캐시 전략, 데이터 분산 방식, 통신 구조(API 또는 메시징 기반)를 설계해야 한다.
또한 각 구성 요소 간의 장애 격리와 복구 절자, 모니터링 체계를 갖추는 것이 중요하다.</p>
<p>마지막으로 이러한 요소들이 조화롭게 결합된 <strong>시스템 아키텍처</strong>를 살펴보면, 서비스가 어떤 원리로 대규모 트래픽을 안정적으로 처리하고 빠르게 확장할 수 있는지 한눈에 이해할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker Compose]]></title>
            <link>https://velog.io/@kooks-dev/Docker-Compose-r6fwq0pt</link>
            <guid>https://velog.io/@kooks-dev/Docker-Compose-r6fwq0pt</guid>
            <pubDate>Tue, 04 Nov 2025 16:45:28 GMT</pubDate>
            <description><![CDATA[<h2 id="docker-compose">Docker Compose</h2>
<ul>
<li>여러 개의 Docker 컨테이너들을 하나의 서비스로 정의하고 구성해 하나의 묶음으로 관리할 수 있게 해 준다.</li>
<li>여러 개의 컨테이너를 관리하는데 용이</li>
<li>복잡한 명령어로 실행시키던걸 간소화시킬 수 있음</li>
</ul>
<h2 id="docker-compose-흐름nginx">Docker Compose 흐름(Nginx)</h2>
<h3 id="docker-cli로-컨테이너-실행">Docker CLI로 컨테이너 실행</h3>
<pre><code class="language-bash">$ docker run --name webserver -d -p 80:80 nginx
</code></pre>
<h3 id="docker-compose로-컨테이너-실행">Docker Compose로 컨테이너 실행</h3>
<ol>
<li>compose.yml 파일 작성</li>
</ol>
<pre><code class="language-bash">services:
 my-web-server:
  container_name: webserever
  image: nginx
  ports:
   - 80:80
</code></pre>
<ul>
<li><strong>services: my-web-server</strong> -&gt; Docker Compose에서 하나의 컨테이너를 서비스(service)라 부른다. 이 옵션은 서비스에 이름을 붙이는 기능이다.</li>
<li><strong>container_name: web-server</strong> -&gt; 컨테이너를 뛰울 때 붙이는 별칭이다. CLI에서 --name web-server와 동일하다.</li>
<li><strong>image: nginx</strong> -&gt; 컨테이너를 실행시킬 때 어떤 이미지를 사용할지 정의하는 명령어다. docker run [이미지명]과 동일</li>
<li><strong>ports</strong> -&gt; 포트 매핑은 어떻게 할지를 설정하는 옵션이다. CLI -p 80:80과 동일하다.</li>
</ul>
<ol>
<li>compose 파일 실행시키기</li>
</ol>
<pre><code class="language-bash">$ docker compose up -d
</code></pre>
<ol>
<li>compose 실행 현황 보기</li>
</ol>
<pre><code class="language-bash">$ docker compose ps
$ docker ps
</code></pre>
<ol>
<li>localhost:80 확인</li>
<li>compose로 실행된 컨테이너 삭제</li>
</ol>
<pre><code class="language-bash">$ docker compose down
</code></pre>
<h2 id="docker-compose로-mysql-실행">Docker Compose로 MySQL 실행</h2>
<h3 id="docker-cli-로-컨테이너-실행">Docker CLI 로 컨테이너 실행</h3>
<pre><code class="language-bash">$ docker run -e MYSQL_ROOT_PASSWORD=pwd123 -p 3306:3306 -v /User/docker/docker-mysql:/var/lib/mysql -d mysql
</code></pre>
<h3 id="docker-compose로-mysql-실행-1">Docker Compose로 MySQL 실행</h3>
<p>1. compose 파일 작성</p>
<pre><code class="language-bash">service:
 my-db:
  image: mysql
  enviroment:
   MYSQL_ROOT_PASSWORD: pwd123
  volumnes:
   - ./mysql_data:/var/lib/mysql
      ports:
   - 3306:3306
</code></pre>
<ul>
<li><strong>enviroment: ...</strong> -&gt; CLI에서 <strong>-e MYSQL_ROOT_PASSWORD=password</strong> 역할과 동알하다.</li>
<li><strong>volumnes: ...</strong> -&gt; CLI 에서 <strong>-v {호스트 경로}:/var/lib/mysql</strong> 역할과 동일하다.</li>
</ul>
<p>2. compose 파일 실행</p>
<pre><code class="language-bash">$ docker compose up -d
</code></pre>
<p>3. compose 실행 현황</p>
<pre><code class="language-bash">$ docker compose ps
$ docker ps
</code></pre>
<p>4. 잘 작동하는 지 DBeaver 연결 확인</p>
<p>5. volumne의 경로에 데이터 저장되고 있는지 확인</p>
<p>6. compose로 실행된 컨테이너 삭제</p>
<pre><code class="language-bash">$ docker compose down
</code></pre>
<h2 id="docker-compose로-spring-boot-실행">Docker Compose로 Spring Boot 실행</h2>
<p>1. 프로젝트 셋팅</p>
<p>Java17 버전을 기준으로 진행</p>
<p>2. 간단한 코드 작성</p>
<pre><code class="language-java">@RestController
public class AppController {
  @GetMapping(&quot;/&quot;)
  public String home() {
    return &quot;Hello, World!&quot;;
  }
}
</code></pre>
<p>3. Dockerfile 작성</p>
<pre><code class="language-bash">FROM openjdk:17-jdk

COPY build/lib/*SNAPSHOT.jar /app.jar

ENTRYPOINT [&quot;java&quot;,&quot;-jar&quot;,&quot;/app.jar&quot;]
bash
</code></pre>
<p>4. Spring Boot 프로젝트 빌드</p>
<pre><code class="language-bash">$ ./gradlew clean build
</code></pre>
<p>5. compose 파일 작성</p>
<p><strong>compose.yml</strong></p>
<pre><code class="language-bash">services:
 my-server:
  build: .
  ports:
   - 8080:8080
</code></pre>
<ul>
<li><strong>build: .</strong> -&gt; compose.yml 이 존재하는 디렉토리( . )에 있는 Dockerfile로 이미지를 생성해 컨테이너를 뛰우겠다는 의미</li>
</ul>
<p>참고) Docker CLI 로 실행</p>
<pre><code class="language-bash">$ docker build -t hello-server .
$ docker run -d -p 8080:8080 hello-server
</code></pre>
<p>6. compose 파일 실행시키기</p>
<pre><code class="language-bash">$ docker compose up -d --build
</code></pre>
<p>7. compose 실행 현황 보기</p>
<pre><code class="language-bash">$ docker compose ps
$ docker ps
</code></pre>
<p>8. localhost :8080으로 들어가보기</p>
<p>9. compose로 실행된 컨테이너 삭제</p>
<pre><code class="language-bash">$ docker compose down
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker File]]></title>
            <link>https://velog.io/@kooks-dev/Docker-File</link>
            <guid>https://velog.io/@kooks-dev/Docker-File</guid>
            <pubDate>Tue, 04 Nov 2025 16:44:13 GMT</pubDate>
            <description><![CDATA[<p>Docker 이미지는 Dockerhub을 통해 다운받아서 사용할 수 있는데 올려져 있는 이미지들도 누군가 만들어서 Dockerhub에 올려놓은 것이다.</p>
<p>Dockerfile이라는 걸 활용해서 Docker 이미지를 만들 수 있다.</p>
<p>Dockerhub에 올려놓은 Docker 이미지가 아닌 나만의 Docker 이미지를 만들고 싶을 수 있다.</p>
<p>예로 Spring Boot 프로젝트가 있을 때 프로젝트 자체를 Docker 이미지로 만들고 싶다면 이럴때 Dockerfile을 활용하면 나만의 Docker 이미지를 만들고 사용할 수 있다.</p>
<h2 id="from">FROM</h2>
<p>FROM 은 베이스 이미지를 생성하는 역할을 한다. Docker 컨테이너 특정 초기 이미지를 기반으로 추가적인 셋팅을 할 수 있다. 여기서 말한 특정 초기 이미지가 곧 <strong>베이스 이미지</strong>다.</p>
<pre><code class="language-bash">FROM [이미지명]
FROM [이미지명]:[태그명]
</code></pre>
<p><strong>태그명</strong>을 적지 않으면 해당 이미지의 최신(latest)버전을 사용한다.</p>
<h2 id="from-실습">FROM 실습</h2>
<pre><code class="language-bash"># Docker file
FROM openjdk:17-jdk

-------------------------------------------------------------------------

# Docker로 이미지 생성하는 문법
$ docker build -t my-jdk17-serever

# 이미지를 기반으로 컨테이너 뛰우기
$ docker run -d my-jdk17-server
</code></pre>
<h2 id="copy">COPY</h2>
<p>COPY는 호스트 컴퓨터에 있는 파일을 복사해서 컨테이너로 전달한다.</p>
<pre><code class="language-bash">COPY [호스트 컴퓨터에 있는 복사할 파일의 경로] [컨테이너에서 파일이 위치할 경로]

# 예시 (파일)
COPY app.txt /app.txt

# 예시 (폴더)
COPY my-app /my-app/

# 예시 (와일드 카드)
COPY *.txt /text-files/
</code></pre>
<h2 id="dockerignore">.dockerignore</h2>
<p>특정 파일 또는 폴더만 COPY를 하고 싶지 않을 수 있다. 그럴때 <strong>.dockerignore</strong>를 활용한다.</p>
<p>.dockeignore</p>
<pre><code class="language-bash">readme.txt
</code></pre>
<p>dockerfile을 만들고 이미지 생성 및 컨테이너를 실행하면</p>
<p>readme.txt 을 제외하고 COPY된 결과를 볼 수 있다.</p>
<h2 id="entrypoint">ENTRYPOINT</h2>
<p><strong>ENTRYPOINT</strong>컨테이너가 생성되고 최초로 실핼할 때 수행되는 명령어를 뜻한다. 쉽게 설명하자면 컴퓨터의 전원을 키고나서 실행시키고 싶은 명령어를 적으면 된다.</p>
<pre><code class="language-bash">ENTRYPOINT [명령문...]
ENTRYPOINT [&quot;/bin/bash&quot;,&quot;-c&quot;,&quot;echo hello&quot;]
</code></pre>
<h2 id="spring-boot를-docker-실행">Spring Boot를 Docker 실행</h2>
<h3 id="1-springboot간단한-코드-작성">1. SpringBoot간단한 코드 작성</h3>
<pre><code class="language-bash">@RestController
public class AppController {
 @GetMapping(&quot;/&quot;)
 public String home() {
     return &quot;Hello World&quot;;
 }
}
</code></pre>
<h3 id="2-dockerfile">2. Dockerfile</h3>
<pre><code class="language-bash">FROM openjdk:17-jdk

COPY build/libs/*SNAPSHOT.jar app.jar

ENTRYPOINT [&quot;java&quot;, &quot;-jar&quot;, &quot;/app.jar&quot;]
</code></pre>
<h3 id="3-spring-boot-build">3. Spring Boot Build</h3>
<pre><code class="language-bash">$ ./gradlew clean build
</code></pre>
<h3 id="4-dockerfile을-이미지-빌드">4. Dockerfile을 이미지 빌드</h3>
<pre><code class="language-bash">$ docker build -t hello-server .
</code></pre>
<h3 id="5-생성-이미지-컨테이너-실행">5. 생성 이미지 컨테이너 실행</h3>
<pre><code class="language-bash">$ docker run -d -p 8080:8080 hello-server
</code></pre>
<h2 id="run">RUN</h2>
<p><strong>RUN</strong> 은 이미지를 생성 과정에서 명령어를 실행시켜야 할 때 사용한다.</p>
<pre><code class="language-bash">RUN [명령문]
RUN npm install
</code></pre>
<h2 id="run-vs-entrypoint"><strong>RUN</strong> VS <strong>ENTRYPOINT</strong></h2>
<p><strong>RUN</strong>, <strong>ENTRYPOINT</strong>를 사용하다보면 헷갈리는 경우가 있다. 둘 다 같이 명령어를 실행시키기 때문이다.</p>
<p>하지만 둘의 사용 용도는 다르다. <strong>RUN</strong>은 이미지 생성 과정에서 필요한 명렬어를 실행시킬 때 사용하고 <strong>ENTRYPOINT</strong>는 생성된 이미지를 기반으로 컨테이너를 생성한 직후에 명령어를 실행시킬 때 사용한다.</p>
<pre><code class="language-bash">FROM ubuntu

RUN apt update &amp;&amp; apt install -y git

ENTRYPOINT [&quot;/bin/bash&quot;,&quot;-c&quot;,&quot;sleep 500&quot;]
</code></pre>
<h2 id="workdir">WORKDIR</h2>
<p><strong>WORKDIR</strong> 으로 작업 디렉터리를 전환하면 그 이후에 등장하는 모든 <strong>RUN</strong>, <strong>CMD</strong>, <strong>ENTRYPOINT</strong>, <strong>COPY</strong>, <strong>ADD</strong> 명령문은 해당 디렉터리를 기준으로 실행된다. 작업 디렉터리를 굳이 지정해주는 이유는 컨테이너 내부의 폴더를 깔끔하게 관리하기 위해서이다.</p>
<pre><code class="language-bash">WORKDIR [작업 디렉토리로 사용할 절대 경로]
WORKDIR /usr/src/app
</code></pre>
<h2 id="expose">EXPOSE</h2>
<p><strong>EXPOSE</strong>는 컨테이너 내부에서 어떤포트에 프로그램이 실행되는 지를 문서화하는 역할을 한다.</p>
<p>-p의 옵션과 같은 역할은 아니다. 쉽게 표현하자면 EXPOSE 명령어를 쓰나 안 쓰나 작동하는 방식의 영향을 미치지 않는다.</p>
<p>권장사항 일 뿐 실제 뜨는 포트랑 달라도 전형 상관없다.</p>
<pre><code class="language-bash">EXPOSE [포트 번호]
EXPOSE 8080
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker Volume(도커 볼룸)]]></title>
            <link>https://velog.io/@kooks-dev/Docker-Volume%EB%8F%84%EC%BB%A4-%EB%B3%BC%EB%A3%B8-8cnfxzc3</link>
            <guid>https://velog.io/@kooks-dev/Docker-Volume%EB%8F%84%EC%BB%A4-%EB%B3%BC%EB%A3%B8-8cnfxzc3</guid>
            <pubDate>Tue, 04 Nov 2025 16:43:00 GMT</pubDate>
            <description><![CDATA[<h2 id="컨테이너가-가진-문제점">컨테이너가 가진 문제점</h2>
<p>Docker를 활용하면 특정 프로그램을 컨테이너로 뛰울 수 있다.</p>
<p>프로그램에 기능이 추가되면 새로운 이미지를 만들어 컨테이너를 실행시켜야 한다.</p>
<p>이때 Docker는 기존 컨테이너에서 변경된 부분을 수정하지 않고 새로운 컨테이너를 만들어서 통째로 갈아 끼우는 방식으로 교체한다.</p>
<p>이런 특징 때문에 기존 컨테이너를 새로운 컨테이너로 교체하면 기존 컨테이너 내부에 있던 데이터도 같이 삭제된다.</p>
<p>만약 이컨테이너가 데이터베이스를 실행시키는 컨테이너였다면 저장된 데이터도 같이 삭제 된다.</p>
<p>따라서 컨테이너 내부에 저장된 데이터가 삭제되면 안되는 경우에는 <strong>볼룸(Volume)</strong> 이라는 개념을 사용한다.</p>
<h2 id="docker-volume도커-볼룸">Docker Volume(도커 볼룸)</h2>
<p>도커의 볼룸(Volume)이란 도커 컨테이너에서 데이터를 영속적으로 저장하기 위한 방법이다.</p>
<p>볼룸은 컨테이너 자체의 저장 공간을 사용하지 않고 호스트 자체의 저장 공간을 고유해서 사용하는 형태이다.</p>
<h2 id="볼룸volumne을-사용하는-명령어">볼룸(Volumne)을 사용하는 명령어</h2>
<pre><code class="language-bash">$ docker run -v [호스트의 디렉토리 절대경로]:[컨테이너의 디렉톤리 절대경로] [이미지]:[태그명]
</code></pre>
<ul>
<li>[호스트의 디렉토리 절대경로]에 디렉토리 이미 존재할 경우 호스트의 디렉터리가 컨테이너의 디렉터리를 덮어 쒸운다.</li>
<li>[호스트의 디렉토리 절대경로]에 디렉토리가 존재하지 않을 경우 호스트의 데렉터리 절대 경로에 디렉터리를 새로 만들고 컨테이너의 디렉터리에 있는 파일들을 호스트의 티렉터리로 복사해온다.</li>
</ul>
<h2 id="dokcer로-mysql-실행시키기">Dokcer로 MySQL 실행시키기</h2>
<ol>
<li>MySQL 이미지를 바탕으로 컨테이너 실행시키기</li>
</ol>
<pre><code class="language-bash">$ docker run -e MYSQL_ROOT_PASSWORD=password123 -p 3306:3306 -d mysql
$ echo $MYSQL_ROOT_PASSWORD
또는
$ exprot
</code></pre>
<ul>
<li><strong>-e</strong> : 해당 옵셩은 컨테이너의 환경 변수를 설정하는 옵션</li>
<li>Dockerhub의 MySQL 공신 문서를 보면 환경 변수로 <strong>MYSQL_ROOT_PASSWORD</strong>를 정해야만 정상적으로 컨테이너가 실행된다고 적혀있다.</li>
<li>아래의 명령어로 컨테이너로 들어가서 환경 변수를 직접 확인할 수 있다.</li>
</ul>
<pre><code class="language-bash">$ docker exec -it [MySQL 컨테이너 ID] bash
</code></pre>
<ol>
<li>컨에티너가 잘 실행되고 있는지 체크</li>
<li>컨테이너 실행시킬 때 에러 없이 잘 실행되는지 로그 체크</li>
<li>DBeaver에도 연결 시켜 체크</li>
</ol>
<p><img src="https://velog.velcdn.com/images/kooks-dev/post/bf5573d9-6d05-4c11-b99f-d898481916de/image.png" alt=""></p>
<h2 id="볼룸volumne을-활용해서-mysql-컨테이너-뛰우기">볼룸(Volumne)을 활용해서 MySQL 컨테이너 뛰우기</h2>
<ol>
<li>MySQL 컨테이너 뛰우기</li>
</ol>
<pre><code class="language-bash">$ cd /User/test $ mkdir docekr-mysql
# docker run -e MYSQL_ROOT_PASSWORD=password123 -p 3306:3306 -v {호스트의 절대경로}/mysql-sql:/var/lib/mysql -d mysql
$ docker run -e MYSQL_ROOT_PASSWORD=password123 -p 3306:3306 -v /User/test/docker-mysql:/var/lib/mysql -d mysql`
</code></pre>
<ol>
<li>MySQL 컨테이너에 접속해서 데이터베이스 만들기</li>
</ol>
<pre><code class="language-bash">$ docker exec -it [MySQL 컨테이너 ID] bash
$ mysql -u root -p
mysql&gt; show database;
mysql&gt; create databases mydb;
mysql&gt; show datavbases;
</code></pre>
<ol>
<li>컨테이너 종료 후 다시 생성해보기</li>
</ol>
<pre><code class="language-bash"># 컨테이너 종료 후 삭제하고 다시 생성

$ docker stop [MySQL 컨테이너 ID]
$ docker rm [MySQL 컨테이너 ID]
$ docker run -e MYSQL_ROOT_PASSWORD=password123 -p 3306:3306 -v /User/test/docker-mysql:/var/lib/mysql -d mysql

$ docker exec -it [MySQL 컨테이너 ID] bash
$ mysql -u root -p

mysql&gt; show database;
mysql&gt; show datavbases;
</code></pre>
<ul>
<li>결과를 보면 삭제 전에 만들었던 데이터베이스 <strong>mydb</strong>의 정보가 남아 있는걸 확인할 수 있다.</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>