<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jay_be.log</title>
        <link>https://velog.io/</link>
        <description>성장하는, 나눌 줄 아는 개발자</description>
        <lastBuildDate>Fri, 16 Jan 2026 06:35:56 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jay_be.log</title>
            <url>https://velog.velcdn.com/images/jay_be/profile/a7465f66-91b6-44f7-8239-186d1a301d28/image.PNG</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jay_be.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jay_be" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[오픈소스 기여하기 spring-cloud/openfeign]]></title>
            <link>https://velog.io/@jay_be/%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4-%EA%B8%B0%EC%97%AC%ED%95%98%EA%B8%B0-spring-cloudopenfeign</link>
            <guid>https://velog.io/@jay_be/%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4-%EA%B8%B0%EC%97%AC%ED%95%98%EA%B8%B0-spring-cloudopenfeign</guid>
            <pubDate>Fri, 16 Jan 2026 06:35:56 GMT</pubDate>
            <description><![CDATA[<p>2026년에 목표 중 하나인 Spring 관련 오픈소스에 기여하기에 성공하였습니다! 🎉🎉🎉🎉</p>
<p>Spring Team과 소통하면서 많은 사람들이 사용하는 라이브러리에 제가 짠 코드가 들어갈 수 있다는게 신기하기도 하고 기분도 좋았습니다. 👍👍👍 그 과정을 잊기 전에 글로 작성해보려고 합니다.</p>
<h2 id="0-인트로">0. 인트로</h2>
<p>저는 오픈소스에 항상 기여하고 싶고 싶은 마음이 있었습니다. 그러나 막연하게 도전하는 것에 두려움이 있었던 것 같습니다. 2026년이 되어 개발자로써의 목표를 세웠는데 그 중 하나가 오픈소스 기여 였습니다.</p>
<p><img src="https://velog.velcdn.com/images/jay_be/post/7a6b05f7-dc4e-445a-9018-cedd70177507/image.png" alt=""></p>
<p>링크드인에서 게시글들을 구경하던 와중 <a href="https://kr.linkedin.com/posts/injae-kim-dev_%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4-%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4%EA%B8%B0%EC%97%AC%EB%AA%A8%EC%9E%84-opensource-activity-7408814788635717634-a021">링크드인 - 오픈소스 기여모임</a> 게시글을 보게 되었습니다.</p>
<p>오픈소스에 기여하는 첫걸음에 도움을 받을 수 있을 것 같아 
&quot;오픈소스 기여모임 10기&quot;에 참여하게 되었습니다.</p>
<blockquote>
<p>해당 과정은 <a href="https://medium.com/opensource-contributors/%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4%EC%9D%98-%ED%8C%90%EB%8F%84%EB%A5%BC-%EB%B0%94%EA%BF%80-ai%EB%A1%9C-%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4-%EA%B8%B0%EC%97%AC-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C%EC%99%80-%ED%94%84%EB%A1%AC%ED%94%84%ED%8A%B8-%EA%B3%B5%EC%9C%A0-2db85bf736b8">Medium - AI 활용 오픈소스 기여 가이드 — 오픈소스 기여의 진입 장벽을 낮추고 판도를 바꿉니다!</a> 를 참고하여 진행되었습니다.</p>
</blockquote>
<h2 id="1-오픈소스-선정">1. 오픈소스 선정</h2>
<p>Java/Spring Boot를 실무에서 사용하기 때문에 먼저 spring-projects에 어떤 라이브러리들이 있는지 확인을 해보았습니다.
<img src="https://velog.velcdn.com/images/jay_be/post/17cd5545-fba7-4cd1-b10f-5b0c695a6c82/image.png" alt=""></p>
<p>평소에 작업하는 프로젝트들의 build.gradle을 보며 어떤 라이브러리들이 있는지 찬찬히 살펴보았습니다. 그러다보니 몰랐던 라이브러리들도 알 수 있게 되었다는게 좋았습니다. 
<img src="https://velog.velcdn.com/images/jay_be/post/c224c667-d8bf-4612-9eef-e53518953b4e/image.png" alt=""></p>
<p>특히나 Spring Session은 세션 상태를 서버에서 분리해 분산 환경에서도 일관된 세션을 제공하기 위해 쓰는 라이브러리로, 추후 회사에 도입해볼 수 있겠다는 생각이 들었습니다.</p>
<p>이슈를 선정할 때 저는 퍼블릭시티 코멧을 활용하였습니다. 코멧은 ai가 탑재된 브라우저로 프롬프트를 작성하면 직접 url에 접근이 가능하고 화면을 읽고 직접 처리까지 가능합니다.</p>
<p><img src="https://velog.velcdn.com/images/jay_be/post/502b7f94-4474-42b5-8602-0ab53499f706/image.png" alt=""></p>
<p>이슈 번호를 추천 받고, 직접 이슈를 들어가서 처리 가능한 상태인지 확인을 합니다. 누군가가 작업중이거나 메인테이너가 작업을 원하지 않는 상태일 수 있으니 꼼꼼히 확인이 필요합니다.</p>
<p>추가적으로 spring-cloud 라이브러리도 살펴보았고, 하나의 이슈를 선정하게 되었습니다.
<img src="https://velog.velcdn.com/images/jay_be/post/b5212121-17ae-4b6d-b3e3-cb57f3f271cb/image.png" alt=""></p>
<p>해당 이슈의 내용은 Spring Cloud OpenFeign의 OAuth2 설정에서 프로퍼티 이름 바인딩이 일관되지 않다는 내용입니다.</p>
<p><a href="https://rwinch.github.io/spring-boot/features/external-config/typesafe-configuration-properties/relaxed-binding.html?utm_source=chatgpt.com">스프링 공식문서 - Relaxed Binding</a></p>
<p>공식문서 기준으로 Spring에서는 Relaxed Bindng을 지원합니다.
<img src="https://velog.velcdn.com/images/jay_be/post/568a1522-8cb6-4fee-86a9-29962f02d5c3/image.png" alt=""></p>
<h3 id="✔-허용되는-변환">✔ 허용되는 변환</h3>
<ul>
<li>kebab-case (<code>client-registration-id</code>)</li>
<li>camelCase (<code>clientRegistrationId</code>)</li>
<li>snake_case (<code>client_registration_id</code>)</li>
<li>UPPER_SNAKE_CASE (<code>CLIENT_REGISTRATION_ID</code>)</li>
<li>점 표기 (<code>client.registration-id</code>)</li>
</ul>
<p>이 규칙은:</p>
<ul>
<li><code>application.yml</code></li>
<li><code>application.properties</code></li>
<li>환경 변수</li>
<li>JVM system properties</li>
</ul>
<p><strong>모두 동일하게 적용</strong>됩니다. 현재는 camelCase만 지원된다고 하니 한번 소스를 분석을 해봤습니다. 그 전에! 메인테이너를 호출해서 작업을 해도 되는지 물어봅니다.
<img src="https://velog.velcdn.com/images/jay_be/post/6d84d6a4-b6bb-4d1c-aabb-70060fa2706b/image.png" alt=""></p>
<p>허락을 받았으니 이제 오픈소스를 분석해봅니다!</p>
<h2 id="2-오픈소스-분석--수정">2. 오픈소스 분석 &amp; 수정</h2>
<p>오픈소스를 수정하기 위해서는 아래 순서를 지킵니다.</p>
<h3 id="1-프로젝트-fork">1. 프로젝트 Fork</h3>
<ul>
<li>GitHub에서 대상 오픈소스 프로젝트를 내 계정으로 fork</li>
<li>원본 저장소(upstream)는 그대로 두고, 내 fork 저장소(origin)에서 작업)</li>
</ul>
<h3 id="2-pc에-프로젝트-clone">2. PC에 프로젝트 clone</h3>
<pre><code>git clone https://github.com/{my-account}/{repo}.git
cd {repo}
</code></pre><p>추가로 upstream 저장소 등록</p>
<pre><code>git remote add upstream https://github.com/{original-org}/{repo}.git
git remote -v</code></pre><p>-&gt; 이후 최신 변경사항을 반영하기 위함입니다.</p>
<h3 id="3-작업용-브랜치-생성">3. 작업용 브랜치 생성</h3>
<ul>
<li>main/master 에서 직접 작업 X</li>
<li>이슈 단위 브랜치 생성<pre><code>git checkout -b gh-1270-oauth2-dash-case-support</code></pre></li>
<li><blockquote>
<p>브랜치명를 다양하게 만들지만 저는 github-이슈번호-간략한 이슈 내용 이렇게 생성하였습니다.</p>
</blockquote>
</li>
</ul>
<h3 id="4-개발-환경-세팅">4. 개발 환경 세팅</h3>
<ul>
<li>코드 포멧팅 설정</li>
<li><blockquote>
<p>Github에 가이드 문서대로 설정하면 됩니다.</p>
</blockquote>
</li>
<li>의존 관계 확인 후 다운로드</li>
<li>Spring Cloud OpenFeign은 단독 프로젝트가 아니라 Spring Cloud Commons와 Spring Cloud Build에 의존하는 구조이므로 해당 프로젝트들도 함께 준비합니다.
<img src="https://velog.velcdn.com/images/jay_be/post/9cb2a7c4-786a-454a-aa7a-f251f7aeecb1/image.png" alt=""></li>
</ul>
<p>Spring Cloud OpenFeign은 Spring Cloud 생태계의 일부이기 때문에, 공통 기능을 담당하는 Commons와 공통 빌드 기준을 제공하는 Build 프로젝트를 구성해야 로컬에서 정상적인 개발과 테스트가 가능합니다.</p>
<p>이런 경험을 하면서 오픈소스와 더 가까워지는 느낌을 받았습니다. 👍👍</p>
<h3 id="5-소스코드-수정">5. 소스코드 수정</h3>
<p>오픈소스를 처음 수정하려고 하면,
어느 부분의 코드를 수정해야 하는지 막연하게 느껴질 수 있습니다.
프로젝트 규모가 크고 구조가 복잡할수록 진입 장벽은 더 높아집니다.</p>
<p>하지만 최근에는 AI의 도움을 받아 문제의 원인이 될 가능성이 높은 위치를 빠르게 파악할 수 있습니다.
이슈 설명과 증상을 기반으로 관련 모듈이나 클래스를 추려내고,
실제로 코드를 확인해야 할 범위를 크게 줄일 수 있었습니다.</p>
<p>또한 이번에 선택한 이슈의 경우,
이미 어느 클래스를 수정하면 될지에 대한 제안이 이슈에 포함되어 있었기 때문에,
해당 클래스를 중심으로 코드를 빠르게 탐색할 수 있었습니다.
그 덕분에 전체 코드를 무작정 살펴보는 대신,
문제와 직접적으로 연관된 부분에 집중하여 효율적으로 수정 작업을 진행할 수 있었습니다.</p>
<h4 id="51문제-원인-분석">5.1.문제 원인 분석</h4>
<p>먼저 기존 코드가 왜 dash-case를 지원하지 못하는지 파악했습니다.</p>
<p>기존 코드 (문제) :</p>
<pre><code class="language-java">@Bean
@ConditionalOnBean(OAuth2AuthorizedClientManager.class)
public OAuth2AccessTokenInterceptor defaultOAuth2AccessTokenInterceptor(
        @Value(&quot;${spring.cloud.openfeign.oauth2.clientRegistrationId:}&quot;) String clientRegistrationId,
        OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager) {
    return new OAuth2AccessTokenInterceptor(clientRegistrationId, oAuth2AuthorizedClientManager);
}</code></pre>
<p>문제점:</p>
<ul>
<li>@Value 어노테이션은 정확한 프로퍼티 이름만 매칭합니다.</li>
<li>즉, clientRegistrationId는 작동하지만, client-registration-id는 인식하지 못합니다.</li>
<li>Spring Boot의 Relaxed Binding을 활용하지 못하고 있습니다.</li>
</ul>
<p>프로젝트 내에 다른 설정 클래스들을 확인해보니, 대부분 @ConfigurationProperties를 사용하고 있었습니다.</p>
<pre><code class="language-java">@ConfigurationProperties(&quot;spring.cloud.openfeign.client&quot;)
public class FeignClientProperties { ... }

@ConfigurationProperties(prefix = &quot;spring.cloud.openfeign.httpclient&quot;)
public class FeignHttpClientProperties { ... }</code></pre>
<h4 id="52-해결-방안-설계">5.2 해결 방안 설계</h4>
<p>문제를 해결하기 위해 다음과 같은 접근 방식을 선택했습니다.</p>
<ol>
<li>@ConfigurationProperties를 사용하는 Properties 클래스 생성<ul>
<li>다른 설정 클래스들과 동일한 패턴 적용</li>
<li>Spring Boot의 Relaxed Binding 활용</li>
</ul>
</li>
<li>기존 @Value 방식을 Properties 클래스로 대체<ul>
<li>일관성 있는 설정 방식 유지</li>
<li>타입 안정성 확보</li>
</ul>
</li>
<li>테스트 추가<ul>
<li>dash-case가 정상적으로 작동하는지 검증</li>
</ul>
</li>
</ol>
<h4 id="53-변경사항-상세">5.3 변경사항 상세</h4>
<p>변경한 클래스는 총 3개입니다:</p>
<h4 id="1-feignoauth2propertiesjava-신규-추가">1. FeignOAuth2Properties.java (신규 추가)</h4>
<pre><code class="language-java">package org.springframework.cloud.openfeign;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * Configuration properties for Feign OAuth2 support.
 *
 * @author jaehun lee
 */
@ConfigurationProperties(prefix = &quot;spring.cloud.openfeign.oauth2&quot;)
public class FeignOAuth2Properties {

    /**
     * Enables feign interceptor for managing oauth2 access token.
     */
    private boolean enabled = false;

    /**
     * Client registration id to be used to retrieve the OAuth2 access token. If not
     * specified, the {@code serviceId} retrieved from the {@code url} host segment will
     * be used. This is convenient for load-balanced Feign clients. For non-load-balanced
     * clients, specifying the {@code clientRegistrationId} is recommended.
     */
    private String clientRegistrationId = &quot;&quot;;

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public String getClientRegistrationId() {
        return clientRegistrationId;
    }

    public void setClientRegistrationId(String clientRegistrationId) {
        this.clientRegistrationId = clientRegistrationId;
    }
}</code></pre>
<h4 id="핵심-포인트">핵심 포인트</h4>
<ul>
<li>@ConfigurationProperties 어노테이션으로 Relaxed Binding 활성화</li>
<li>prefix = &quot;spring.cloud.openfeign.oauth2&quot;로 설정 경로 지정</li>
<li>Getter/Setter를 통해 Spring Boot가 자동으로 값을 바인딩</li>
<li>기존 FeignClientProperties, FeignHttpClientProperties와 동일한 패턴</li>
</ul>
<blockquote>
<h4 id="relaxed-binding이란">Relaxed Binding이란?</h4>
<p>Spring Boot는 다양한 형식의 프로퍼티 이름을 자동으로 변환해줍니다:</p>
</blockquote>
<ul>
<li>client-registration-id (dash-case) ✅</li>
<li>clientRegistrationId (camelCase) ✅</li>
<li>client_registration_id (snake_case) ✅</li>
<li>CLIENTREGISTRATIONID (대문자) ✅</li>
<li><blockquote>
<p>모두 clientRegistrationId 필드로 매핑됩니다!</p>
</blockquote>
</li>
</ul>
<h4 id="2-feginautoconfigurationjava-수정">2. FeginAutoConfiguration.java (수정)</h4>
<p>변경 1: @EnableConfigurationProperties에 추가</p>
<pre><code class="language-java">@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ 
    FeignClientProperties.class, 
    FeignHttpClientProperties.class,
    FeignEncoderProperties.class, 
    FeignOAuth2Properties.class  // ← 추가!
})
public class FeignAutoConfiguration {
    // ...
}</code></pre>
<p>변경2: Bean 메서드 파라미터 수정</p>
<pre><code class="language-java">    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(OAuth2AuthorizedClientManager.class)
    @ConditionalOnProperty(&quot;spring.cloud.openfeign.oauth2.enabled&quot;)
    protected static class Oauth2FeignConfiguration {

        @Bean
        @ConditionalOnBean({ OAuth2AuthorizedClientService.class, ClientRegistrationRepository.class })
        @ConditionalOnMissingBean
        OAuth2AuthorizedClientManager feignOAuth2AuthorizedClientManager(
                ClientRegistrationRepository clientRegistrationRepository,
                OAuth2AuthorizedClientService oAuth2AuthorizedClientService) {
            return new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository,
                    oAuth2AuthorizedClientService);

        }

        @Bean
        @ConditionalOnBean(OAuth2AuthorizedClientManager.class)
        public OAuth2AccessTokenInterceptor defaultOAuth2AccessTokenInterceptor(FeignOAuth2Properties oAuth2Properties,
                OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager) {
            return new OAuth2AccessTokenInterceptor(oAuth2Properties.getClientRegistrationId(),
                    oAuth2AuthorizedClientManager);
        }

    }</code></pre>
<h4 id="3-feignautoconfigurationtestsjava-테스트-추가">3. FeignAutoConfigurationTests.java (테스트 추가)</h4>
<p>dash-case가 정상적으로 작동하는지 검증하는 테스트를 추가했습니다:</p>
<pre><code class="language-java">@Test
void shouldInstantiateFeignOAuth2FeignRequestInterceptorWithDashCaseProperty() {
    runner
        .withPropertyValues(&quot;spring.cloud.openfeign.oauth2.enabled=true&quot;,
                &quot;spring.cloud.openfeign.oauth2.client-registration-id=feign-client&quot;)  // dash-case!
        .withBean(OAuth2AuthorizedClientService.class, () -&gt; mock(OAuth2AuthorizedClientService.class))
        .withBean(ClientRegistrationRepository.class, () -&gt; mock(ClientRegistrationRepository.class))
        .run(ctx -&gt; {
            assertOauth2AccessTokenInterceptorExists(ctx);
            assertThatOauth2AccessTokenInterceptorHasSpecifiedIdsPropertyWithValue(ctx, &quot;feign-client&quot;);
        });
}</code></pre>
<p>테스트 목적</p>
<ul>
<li>client-registration-id (dash-case) 형식이 정상적으로 인식되는지 확인</li>
<li>기존 camelCase 테스트는 유지하여 하위 호환성 보장</li>
</ul>
<h4 id="54-동작-원리">5.4 동작 원리</h4>
<p>Spring Boot의 프로퍼티 바인딩 과정:</p>
<pre><code>1. application.yml 파일 읽기
   spring.cloud.openfeign.oauth2.client-registration-id: fancy

2. Spring Boot가 프로퍼티 정규화
   client-registration-id → clientregistrationid (소문자, 구분자 제거)

3. Java 필드명도 정규화
   clientRegistrationId → clientregistrationid

4. 매칭 성공!
   clientregistrationid == clientregistrationid ✅

5. Setter 호출
   setClientRegistrationId(&quot;fancy&quot;)

6. Bean으로 등록
   FeignOAuth2Properties 객체가 Spring 컨텍스트에 등록됨

7. 의존성 주입
   defaultOAuth2AccessTokenInterceptor 메서드의 파라미터로 자동 주입</code></pre><h4 id="55-사용자-관점에서의-변화">5.5 사용자 관점에서의 변화</h4>
<p>변경 전 (문제):</p>
<pre><code>spring:
  cloud:
    openfeign:
      oauth2:
        clientRegistrationId: fancy      # ✅ 작동
        client-registration-id: fancy    # ❌ 작동 안 함!</code></pre><p>변경 후 (해결):</p>
<pre><code>spring:
  cloud:
    openfeign:
      oauth2:
        clientRegistrationId: fancy      # ✅ 작동 (하위 호환성 유지)
        client-registration-id: fancy    # ✅ 작동! (새로 지원)
        client_registration_id: fancy    # ✅ 작동! (보너스)</code></pre><p>이제 사용자들은 Spring Boot의 표준 규칙에 따라 자유롭게 프로퍼티를 작성할 수 있습니다.</p>
<h4 id="56-빌드-및-테스트">5.6 빌드 및 테스트</h4>
<p>수정 완료 후 다음 명령어로 빌드와 테스트를 진행했습니다.</p>
<pre><code># 전체 빌드 (모든 테스트 포함)
./mvnw clean install</code></pre><p>모든 테스트가 통과하여 기존 기능에 영향을 주지 않으면서 새로운 기능이 정상적으로 작동함을 확인했습니다.</p>
<h3 id="6-pull-request-제출">6. Pull Request 제출</h3>
<p>PR도 Spring Cloud 규칙에 맞게 제출했습니다.</p>
<p><img src="https://velog.velcdn.com/images/jay_be/post/4e909f9a-4967-4476-952a-897cec7c4fba/image.png" alt=""></p>
<h3 id="7-메인테이너의-merged">7. 메인테이너의 Merged</h3>
<p><img src="https://velog.velcdn.com/images/jay_be/post/07fb6a16-d157-4bc4-b338-ba6b3a06e15d/image.png" alt=""></p>
<p>메인테이너의 테스트 프로세스가 끝나면 머지를 시켜줍니다! 
고맙다는 인사도 받아보는 값진 경험을 해보았습니다. 🎉🎉🎉
<img src="https://velog.velcdn.com/images/jay_be/post/084b82ce-98d0-4159-a579-601f51b66568/image.png" alt=""></p>
<h2 id="첫-오픈소스-기여를-마치며">첫 오픈소스 기여를 마치며</h2>
<p><img src="https://velog.velcdn.com/images/jay_be/post/4504b4ec-794e-491a-aba1-fae45189a4c4/image.png" alt=""></p>
<p>Spring Cloud 오픈소스에 기여하면서 많은 것을 느끼고 배웠습니다.
처음에는
“내가 Spring Cloud 같은 대형 프로젝트에 기여할 수 있을까?”
라는 걱정이 앞섰지만, 지금 돌아보면 그런 생각은 기우였습니다.</p>
<p>오픈소스 기여 모임 운영진분들의 도움과 AI 활용, 그리고 꾸준한 노력만 있다면 오픈소스 기여는 생각보다 어렵지 않다는 것을 느꼈습니다. 무엇보다 그 과정에서 정말 많은 것을 배울 수 있었습니다.</p>
<p>스프링 공식 문서를 직접 찾아 읽고, 포맷과 규칙에 맞게 코드를 작성하기 위해 가이드 문서를 참고하며 환경을 세팅했습니다. 또한 다른 개발자들의 코드를 살펴보며 자연스럽게 프로젝트의 구조와 철학을 이해할 수 있었습니다.</p>
<p>Spring Cloud 팀의 반응도 예상보다 훨씬 긍정적이었습니다. 리뷰어들의 친절하고 건설적인 피드백 덕분에, 큰 오픈소스 프로젝트에 성공적으로 기여할 수 있었다고 생각합니다.</p>
<p>앞으로는 종종 취미 삼아 오픈소스에 기여해보려고 합니다. 제가 생각했던 것 이상으로 이 시간이 즐거웠기 때문입니다.</p>
<p>오픈소스 기여 경험이 없더라도, 의지만 있다면 충분히 도전해볼 만하다고 생각합니다. 이 글이 누군가에게 작은 계기가 되었으면 좋겠습니다. 감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[지금 내가 만드는 백엔드 서비스 개선하기 1]]></title>
            <link>https://velog.io/@jay_be/%EC%A7%80%EA%B8%88-%EB%82%B4%EA%B0%80-%EB%A7%8C%EB%93%9C%EB%8A%94-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0-1</link>
            <guid>https://velog.io/@jay_be/%EC%A7%80%EA%B8%88-%EB%82%B4%EA%B0%80-%EB%A7%8C%EB%93%9C%EB%8A%94-%EB%B0%B1%EC%97%94%EB%93%9C-%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0-1</guid>
            <pubDate>Fri, 19 Dec 2025 08:27:02 GMT</pubDate>
            <description><![CDATA[<p>백엔드 개발자로서 항상 자신이 만들고 있는 서비스가 안정적인지, 이게 최선인지 고민해 보아야 한다. </p>
<p>나 역시 곧 1차 릴리즈를 앞두고 있다. 많은 고민 끝에 기술을 결정하고 서비스를 만들었지만, 예상치 못한 문제가 발생할 수도 있다는 것을 알고 있다. ChatGPT에게 현재 내가 만들고 있는 서비스를 설명해주고 더 안정적인 운영을 하기 위해 어떻게 해야할지 같이 고민해달라고 요청했다.</p>
<h3 id="현재-만들고-있는-서비스">현재 만들고 있는 서비스</h3>
<ul>
<li>웹으로 접속하여 해당 서버의 네트워크, 시스템, 방화벽 등 설정을 할 수 있는 서비스.</li>
<li>백엔드와 실질적인 명령어를 내리는 서비스들이 존재.</li>
</ul>
<h1 id="gpt-답변">GPT 답변</h1>
<h2 id="개요">개요</h2>
<p>현재 운영 중이거나 개발 중인 서비스는 <strong>웹 화면을 통해 서버의 네트워크 설정, 방화벽 정책, 시스템 설정, IPSec VPN 설정 등을 적용하는 관리형 서비스</strong>이다.</p>
<p>기술 스택은 다음과 같다.</p>
<ul>
<li>Frontend: React</li>
<li>Backend: Spring Boot</li>
<li>Database: PostgreSQL</li>
<li>Inter-service communication: gRPC</li>
<li>Domain services: Go 기반 모듈</li>
<li>Logging: journald → Vector → OpenObserve</li>
</ul>
<p>이 글에서는 <strong>백엔드 시니어 관점에서 안정적인 서비스 운영을 위해 반드시 고려해야 할 사항과 임팩트 있는 개선 포인트</strong>를 정리한다.</p>
<hr>
<h2 id="1-이-서비스의-본질-설정을-바꾸는-서비스">1. 이 서비스의 본질: “설정을 바꾸는 서비스”</h2>
<p>이 서비스는 일반적인 CRUD 웹 서비스와 본질적으로 다르다.</p>
<ul>
<li>설정 변경은 <strong>즉시 장애로 이어질 수 있음</strong></li>
<li>잘못된 정책 적용은 <strong>네트워크 단절, 보안 사고</strong>로 직결</li>
<li>부분 실패, 중복 적용, 상태 불일치가 치명적</li>
</ul>
<p>따라서 모든 설계는 다음 질문을 중심으로 이루어져야 한다.</p>
<blockquote>
<p>이 버튼을 잘못 누르거나, 적용 도중 실패하면 어떻게 되는가?</p>
</blockquote>
<hr>
<h2 id="2-정책-적용apply-흐름-설계">2. 정책 적용(Apply) 흐름 설계</h2>
<h3 id="21-정책-상태-머신-정의">2.1 정책 상태 머신 정의</h3>
<p>정책은 반드시 <strong>상태 기반으로 관리</strong>되어야 한다.</p>
<pre><code>DRAFT
 → READY
 → APPLYING
 → APPLIED
 → FAILED
 → ROLLBACKING
 → ROLLED_BACK
</code></pre><h3 id="반드시-db에-저장해야-할-정보">반드시 DB에 저장해야 할 정보</h3>
<ul>
<li>policy_id</li>
<li>대상 서버 또는 장비</li>
<li>요청자</li>
<li>요청 시각</li>
<li>현재 상태</li>
<li>실패 사유 (error code + message)</li>
<li>재시도 횟수</li>
</ul>
<p>DB만 보고도 다음 질문에 답할 수 있어야 한다.</p>
<ul>
<li>지금 이 서버는 어떤 상태인가?</li>
<li>마지막 성공 적용은 언제인가?</li>
<li>실패한 이유는 무엇인가?</li>
</ul>
<hr>
<h3 id="22-grpc-호출-실패-시나리오-분해">2.2 gRPC 호출 실패 시나리오 분해</h3>
<p>gRPC 통신은 “성공/실패”만으로는 부족하다.</p>
<table>
<thead>
<tr>
<th>상황</th>
<th>실제 의미</th>
<th>대응</th>
</tr>
</thead>
<tbody><tr>
<td>gRPC timeout</td>
<td>적용 중인지, 미수신인지 불명</td>
<td>Idempotency 필수</td>
</tr>
<tr>
<td>일부 모듈 성공</td>
<td>반쪽짜리 적용</td>
<td>Rollback 또는 보정</td>
</tr>
<tr>
<td>Go 모듈 panic</td>
<td>상태 불명</td>
<td>상태 조회 API 필요</td>
</tr>
<tr>
<td>네트워크 단절</td>
<td>결과 미확인</td>
<td>재조회 기반 판단</td>
</tr>
</tbody></table>
<p>👉 <strong>Go 모듈에는 반드시 “현재 적용 상태 조회 API”가 존재해야 한다.</strong></p>
<hr>
<h3 id="23-idempotency-중복-적용-방지">2.3 Idempotency (중복 적용 방지)</h3>
<p>정책 적용 요청은 반드시 <strong>멱등성(Idempotency)</strong> 을 가져야 한다.</p>
<ul>
<li>모든 Apply 요청에 <code>request_id (UUID)</code> 부여</li>
<li>Go 모듈은 <code>request_id</code> 기준으로 처리<ul>
<li>이미 처리한 요청 → 즉시 성공 응답</li>
<li>신규 요청 → 실제 적용</li>
</ul>
</li>
</ul>
<p>Idempotency가 없으면 재시도는 곧 <strong>중복 설정</strong>이 된다.</p>
<p>특히 방화벽, VPN 설정에서는 치명적이다.</p>
<hr>
<h2 id="3-rollback-전략-없으면-운영-불가">3. Rollback 전략 (없으면 운영 불가)</h2>
<h3 id="31-이전-설정은-어디에-있는가">3.1 이전 설정은 어디에 있는가?</h3>
<p>Rollback이 가능하려면 다음 중 하나는 반드시 존재해야 한다.</p>
<ul>
<li>DB에 정책 버전 이력 저장</li>
<li>Go 모듈에서 적용 전 설정 스냅샷 보관</li>
<li>최소한 diff 기반 복원 전략</li>
</ul>
<hr>
<h3 id="32-rollback-트리거">3.2 Rollback 트리거</h3>
<p>Rollback은 명확한 조건을 가져야 한다.</p>
<ul>
<li>정책 적용 실패 시 자동 실행</li>
<li>운영자 수동 Rollback 버튼</li>
<li>APPLYING 상태가 일정 시간 이상 지속될 경우</li>
</ul>
<p>Rollback 없는 Apply는 <strong>운영 서비스로서 매우 위험</strong>하다.</p>
<hr>
<h2 id="4-트랜잭션-경계-재설계">4. 트랜잭션 경계 재설계</h2>
<h3 id="41-db-트랜잭션과-실세계는-다르다">4.1 DB 트랜잭션과 실세계는 다르다</h3>
<p>다음과 같은 코드는 매우 위험하다.</p>
<pre><code class="language-java">@Transactional
savePolicy();
grpc.apply();
</code></pre>
<h3 id="권장-패턴-saga-스타일">권장 패턴 (Saga 스타일)</h3>
<ol>
<li>정책 상태를 APPLYING으로 저장 (DB commit)</li>
<li>gRPC 호출</li>
<li>결과 수신</li>
<li>상태를 APPLIED 또는 FAILED로 갱신</li>
</ol>
<p>DB는 <strong>의도(intent)</strong> 를 기록하고,</p>
<p>실제 적용은 <strong>외부 세계</strong>에서 이루어진다.</p>
<hr>
<h2 id="5-관측-가능성observability-강화">5. 관측 가능성(Observability) 강화</h2>
<h3 id="51-로그-표준화">5.1 로그 표준화</h3>
<p>이미 OpenObserve를 사용 중이라면, 로그 필드 표준화가 핵심이다.</p>
<h3 id="공통-로그-필드">공통 로그 필드</h3>
<ul>
<li>request_id</li>
<li>policy_id</li>
<li>target_host</li>
<li>domain (firewall, vpn, network 등)</li>
<li>result_code</li>
</ul>
<p>Spring → gRPC → Go → journald → Vector → OpenObserve</p>
<p>전 구간에서 <strong>동일한 request_id 유지</strong>가 필수다.</p>
<hr>
<h3 id="52-메트릭은-로그보다-중요하다">5.2 메트릭은 로그보다 중요하다</h3>
<p>다음 메트릭이 없으면 “감으로 운영”하게 된다.</p>
<ul>
<li>정책 적용 성공/실패 횟수</li>
<li>평균 적용 시간</li>
<li>APPLYING 상태 체류 시간</li>
<li>Rollback 발생 빈도</li>
<li>Go 모듈별 오류율</li>
</ul>
<p>Prometheus + Grafana 조합을 강력히 권장한다.</p>
<p>(OpenObserve는 로그, 메트릭은 분리하는 것이 안정적)</p>
<hr>
<h2 id="6-장애-대응-관점-체크리스트">6. 장애 대응 관점 체크리스트</h2>
<h3 id="61-자동-감지-가능한-이상-상태">6.1 자동 감지 가능한 이상 상태</h3>
<p>다음 상황은 반드시 감지되어야 한다.</p>
<ul>
<li>APPLYING 상태가 일정 시간 이상 지속</li>
<li>동일 서버에 동시 정책 적용 시도</li>
<li>특정 시간대 실패율 급증</li>
</ul>
<p>최소한 Alert는 떠야 하며, 가능하면 자동 조치도 고려한다.</p>
<hr>
<h3 id="62-운영자를-위한-필수-화면">6.2 운영자를 위한 필수 화면</h3>
<p>운영 관점에서 다음 정보는 반드시 제공되어야 한다.</p>
<ul>
<li>서버별 마지막 성공 적용 시점</li>
<li>현재 적용 중인 정책</li>
<li>실패 이력 타임라인</li>
<li>재시도 / Rollback 버튼</li>
</ul>
<hr>
<h2 id="7-보안-관점-고려사항">7. 보안 관점 고려사항</h2>
<h3 id="71-권한-분리">7.1 권한 분리</h3>
<p>API 권한은 반드시 분리한다.</p>
<ul>
<li>조회 권한</li>
<li>정책 수정 권한</li>
<li>실제 Apply 권한</li>
</ul>
<p>Apply 권한은 관리자 중에서도 제한해야 한다.</p>
<hr>
<h3 id="72-audit-log">7.2 Audit Log</h3>
<p>삭제 불가능한 감사 로그는 필수다.</p>
<ul>
<li>누가</li>
<li>언제</li>
<li>무엇을</li>
<li>어떤 서버에</li>
<li>어떤 결과로</li>
</ul>
<p>보안 사고 및 감사 대응을 위해 반드시 필요하다.</p>
<hr>
<h2 id="8-임팩트-큰-개선-우선순위-top-5">8. 임팩트 큰 개선 우선순위 TOP 5</h2>
<ol>
<li>정책 적용 상태 머신 + DB 기록</li>
<li>Idempotent gRPC 설계</li>
<li>Rollback 전략 명문화</li>
<li>request_id 기반 전 구간 트레이싱</li>
<li>APPLYING 상태 감지 및 알림</li>
</ol>
<p>이 다섯 가지만 제대로 갖춰도</p>
<p><strong>“운영 가능한 서비스” → “신뢰 가능한 서비스”</strong> 로 도약한다.</p>
<hr>
<h2 id="9-마무리">9. 마무리</h2>
<p>이 서비스는 단순한 백엔드 API가 아니라 <strong>Control Plane</strong>에 가깝다.</p>
<p>이 영역을 제대로 경험하면:</p>
<ul>
<li>SRE</li>
<li>플랫폼 엔지니어</li>
<li>아키텍트</li>
</ul>
<p>로 성장하는 데 매우 큰 자산이 된다.</p>
<hr>
<p>정말 다양한 내용이 들어있다. 해당 내용을 가지고 현재 내가 진행하고 있는 프로젝트에 잘 적용되어있는지 체크를 해보았다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[PostgreSQL 데이터 마이그레이션 간단하게 하기]]></title>
            <link>https://velog.io/@jay_be/PostgreSQL-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%A7%88%EC%9D%B4%EA%B7%B8%EB%A0%88%EC%9D%B4%EC%85%98-%EA%B0%84%EB%8B%A8%ED%95%98%EA%B2%8C-%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jay_be/PostgreSQL-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%A7%88%EC%9D%B4%EA%B7%B8%EB%A0%88%EC%9D%B4%EC%85%98-%EA%B0%84%EB%8B%A8%ED%95%98%EA%B2%8C-%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 19 Dec 2025 06:39:57 GMT</pubDate>
            <description><![CDATA[<p>실무 중 간단하게 서버 구축 중 다른 테스트 서버의 DB 데이터를 마이그레이션 하는 과정을 담았다.</p>
<p>기존 서버에는 docker 컨테이너로 PostgreSQL이 실행중이고, 새로 구축할 서버에는 직접 설치를 하였다.</p>
<h3 id="1-데이터-dump">1. 데이터 dump</h3>
<p>먼저 db dump 파일을 만들었다. sql 파일을 만들어도 되지만, dump 파일을 사용하면 아무래도 복원 속도가 빠르고, 병렬 처리가 가능하다. 테스트 서버 경우 데이터가 많지 않지만 먼저 익숙한 db dump를 떴다.</p>
<pre><code>docker exec -t {container_id} pg_dump -U {id} -Fc {database_name} &gt; dump.dump</code></pre><p>-Fc 옵션은 Format을 custom으로 지정하는 옵션이다. 즉, 일반 텍스트가 아닌 PostgreSQL이 이해하는 바이너리 형식으로 저장을 한다.</p>
<h3 id="2-dump-파일-적용">2. dump 파일 적용</h3>
<p>dump 파일을 만든 후에 새로 구축한 서버에 적용을 해보았다.</p>
<pre><code>pg_restore -U {id} -d {database_name} --no-owner --no-privileges dump.dump</code></pre><p>원본 소유자 정보 무시, GRANT/REVOKE 무시 옵션을 주고 적용을 하였다.</p>
<h3 id="3-그리고-에러">3. 그리고 에러</h3>
<pre><code>Segmentation fault (core dumped)</code></pre><p>결과로 해당 에러가 나왔다. 해당 에러는 새로 만든 서버의 pg_restore 버전과 pg_dump를 사용한 서버의 버전 불일치 문제가 발생했기 때문에 이런 에러가 나온다.</p>
<p>역시나 16버전과 18버전의 차이로 에러가 나는 것이었다. 해결하는 방법은 여러가지가 있지만, 가장 빠르고, 간단하게 처리를 하였다.</p>
<h3 id="4-덤프-새로-뜨기-sql">4. 덤프 새로 뜨기 .sql</h3>
<p>sql 파일로 만들어서 직접 적용하기로 하였다. sql을 직접 사용하는 것이기 때문에 버전 호환성이 높기 때문에 문제 없이 작동할 것이라고 생각했다.</p>
<pre><code>docker exec -t {container_id} pg_dump -U postgres -Fp {database_name} &gt; dump.sql</code></pre><p>-Fp Format을 plan (plain SQL, 일반 텍스트), 즉 사람이 읽을 수 있는 SQL 스크립트로 덤프를 했다.</p>
<p>vi로 sql 파일을 열어보면 안에 읽을 수 있는 DDL 등이 있다.</p>
<h3 id="5-새로운-서버에-적용">5. 새로운 서버에 적용</h3>
<p>리눅스 유저명을 먼저 변경한다. (postgresql의 유저명으로 변경한다.)</p>
<pre><code>sudo -i -u {username}</code></pre><p>이제 적용을 해준다.</p>
<pre><code>psql -d {database_name} &lt; /home/dump/dump.sql</code></pre><p>로그가 쭈욱 뜨면서 작업이 되는 것을 확인할 수 있다. 이렇게 간단하게 데이터 마이그레이션을 마무리 했다.</p>
<h3 id="버전이-다르면-sql-덤프만-사용해야-할까">버전이 다르면 sql 덤프만 사용해야 할까?</h3>
<p>아니다.</p>
<pre><code>/usr/lib/postgresql/16/pg_restore -U {id} -d {database_name} --no-owner --no-privileges dump.dump</code></pre><p>postgreSQL에서는 다른 버전의 라이브러리도 제공하고 있다. 이렇게 하는 방법도 있다 :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[토스 Learner's High 1일차 기록]]></title>
            <link>https://velog.io/@jay_be/%ED%86%A0%EC%8A%A4-Learners-High-1%EC%9D%BC%EC%B0%A8-%EA%B8%B0%EB%A1%9D</link>
            <guid>https://velog.io/@jay_be/%ED%86%A0%EC%8A%A4-Learners-High-1%EC%9D%BC%EC%B0%A8-%EA%B8%B0%EB%A1%9D</guid>
            <pubDate>Tue, 16 Dec 2025 12:56:44 GMT</pubDate>
            <description><![CDATA[<h3 id="가이드-세션-후기">가이드 세션 후기</h3>
<p>토스 이항령 리드 개발자는 스스로 배움의 가치를 강조했다. 각자의 회사를 다니고 있고 다양한 환경에 맞는 최적의 해답을 스스로 탐색해 나가는 시간을 가지기를 원하고 있다.</p>
<p>해당 과정에서는 명확한 목표를 제시하지 않는다. 목표가 너무 명확하면, 오히려 한계가 생길 수 있다. 목표가 없을 때, 더 다양한 방향성이 열리고 결과의 차이도 뚜렷하다.</p>
<p>해당 과정은 단기적인 과정이 아닌 성장을 위한 장기형 여정이다.
참가자들을 단순 채용 지원이 아니라 성장이 목적인 것이다.</p>
<p>토스의 내부 성과 지표의 내용을 보며, 현재 내가 다니고 있는 회사에 어떻게 성과를 내볼지 생각을 해보고 있다. 짧은 한달 기간동안 임팩트 있는 결과를 만들어 낸다면, 나와 회사의 성장과, 동료들의 신뢰와 얻어낼 수 있을 것 같다.</p>
<p>현재 내가 다니고 있는 회사에서는 자유도가 꽤나 높고 현재 프로덕트의 백엔드 총괄을 맡고 있는 나는 이번 기회에 다양한 시도를 해볼 수 있을 것 같다. </p>
<blockquote>
<p>이직을 위한 스펙쌓기, 이력서보다 진정한 나의 성장을 위해 몰입하는 경험이 현재의 회사에도, 당신의 향후 커리어에도 강력한 무기가 됩니다.</p>
</blockquote>
<p>위의 문장이 굉장히 크게 와닿았고 Learner&#39;s High에 열심히 참여해볼 생각이다.</p>
<h2 id="한달-간의-목표">한달 간의 목표</h2>
<p>한달 간의 짧은 과정이기에 현재의 상황을 정리 해보고 어떤 큰 임팩트를 만들어 볼 수 있을지 작성해본다.</p>
<h3 id="현재-회사-업무-상황">현재 회사 업무 상황</h3>
<p>현재 회사에서는 네트워크 설정과 각종 보안 설정, 원격 접속을 웹으로 가능하게 하는 프로젝트를 진행하고 있고, 1차 릴리즈를 앞두고 있다. 백엔드 개발자로서 어떤 성과를 남길 수 있을까?</p>
<h4 id="백엔드의-안정성-확보-운영">백엔드의 안정성 확보 (운영)</h4>
<h4 id="동료들과의-성장">동료들과의 성장</h4>
<h4 id="비지니스-도메인-학습">비지니스 도메인 학습</h4>
]]></description>
        </item>
        <item>
            <title><![CDATA[토스 Learner's High 2기 스트레칭]]></title>
            <link>https://velog.io/@jay_be/%ED%86%A0%EC%8A%A4-Learners-High-2%EA%B8%B0-%EC%8A%A4%ED%8A%B8%EB%A0%88%EC%B9%AD</link>
            <guid>https://velog.io/@jay_be/%ED%86%A0%EC%8A%A4-Learners-High-2%EA%B8%B0-%EC%8A%A4%ED%8A%B8%EB%A0%88%EC%B9%AD</guid>
            <pubDate>Mon, 15 Dec 2025 07:53:22 GMT</pubDate>
            <description><![CDATA[<p>토스 Learner&#39;s High 2기에 신청했고, 운이 좋게 참여하게 되었다. 패스트캠퍼스의 이너서클 후에 오랜만에 이런 과정을 참여하게 되었다.</p>
<p>토스 Learner&#39;s High 1기 과정을 꼼꼼히 서칭해 보면서 참여해야겠다는 마음을 먹었었다. 커리큘럼은 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/jay_be/post/4c593e7a-e998-41c9-972a-4f8dbe9b0051/image.png" alt=""></p>
<p>토스 Learner&#39;s High는 단순한 교육이 아니다. 성공적인 비즈니스를 만드는 토스라는 회사에서 어떻게 일을 하는지, 인재상과 필수 역량을 알 수 있고, 토스의 개발자 마인드 셋으로 한달동안 살아볼 수 있다고 생각했다.</p>
<p>그 과정에서 나의 성장과, 회사의 성장을 같이 할 수 있다는 생각이 들었다. 성장하는 행복한 연말을 보낼 수 있지 않을까 생각한다.</p>
<p><img src="https://velog.velcdn.com/images/jay_be/post/79724a4a-0d29-43e5-8276-8eedea882812/image.png" alt=""></p>
<p>뛰어난 개발자와 함께 시간을 보내는 것은 정말 값지다. 토스의 이항령님과 함께 해당 과정을 함께할 수 있어 영광이다.</p>
<h2 id="미리-알아보는-토스-인재상">미리 알아보는 토스 인재상</h2>
<p><a href="https://toss.oopy.io/a201e419-e243-4401-983f-1ac3cad02900">토스 핵심 가치 3.1</a></p>
<h3 id="misson-over-individual-개인의-목표보다-토스팀의-미션을-우선한다">Misson over Individual 개인의 목표보다 토스팀의 미션을 우선한다.</h3>
<ul>
<li>탁월한 동료들과 같은 목표를 추구할 때 더 강력한 조직이 된다.</li>
</ul>
<h3 id="aim-higher-더-높은-수준을-추구한다">Aim Higher 더 높은 수준을 추구한다.</h3>
<ul>
<li>주어진 일을 잘 수행, 업무 퀄리티와 판단력, 성과의 새로운 스탠다드를 만들어내는 것이다.</li>
<li>주어진 일을 잘 수행하기 위해 단순히 1시간을 더 일하는 것보다, 새로운 스탠다드를 창조하는 것이 Aim Higher이다.</li>
</ul>
<h3 id="focus-on-impact-하면-좋을-10가지보다-임팩트를-만드는-데-집중한다">Focus on Impact 하면 좋을 10가지보다, 임팩트를 만드는 데 집중한다.</h3>
<ul>
<li>가장 중요한 일 한가지를 의도적으로 정하고 집중하라.</li>
<li>멀티태스킹, 바쁜 삶은 뿌듯함을 안겨줄 수 있지만 임팩트를 대변하지는 못한다.</li>
</ul>
<h3 id="question-every-assumption-모든-기본-가정에-근원적-물음을-제기한다">Question Every Assumption 모든 기본 가정에 근원적 물음을 제기한다.</h3>
<ul>
<li>기본적으로 가정하고 실행 중인 안이라도, 그것을 바꾸면 어떨지 더나은 길은 없을지 끊임없이 추구한다.</li>
<li>이때 다른 레퍼런스나 유추가 창의적 사고를 가로막지 않도록 주의한다.</li>
</ul>
<h3 id="execution-over-perfection-완벽해지려-하기보다-실행에-집중하라">Execution over perfection 완벽해지려 하기보다 실행에 집중하라</h3>
<ul>
<li>빠른 실행과 실험이 많은 회의와 전략을 이긴다.</li>
<li>한번에 완벽한 기획은 없다.</li>
<li>완벽보다는 빠른 실행을, 논쟁보다는 실험을 우선시 하자.</li>
<li>고객에게 가치를 전달하는 활동은 오직 실행이다.</li>
</ul>
<h3 id="learn-proactively-주도적으로-학습한다">Learn Proactively 주도적으로 학습한다.</h3>
<ul>
<li>업무에 관한 지식, 좋은 판단 등 회사의 정보를 주도적으로 학습하고 흡수한다.</li>
<li>우선 내가 무엇을 모르는지를 명확하고 객관적으로 판단한다.</li>
<li>내가 모르는 점을 주변에 용기있게 드러내고 이를 채울 수단과 방법을 가리지 않고 찾는다.</li>
<li>동료는 가장 좋은 학습 수단이다.</li>
</ul>
<h3 id="move-with-urgency-신속한-속도로-움직인다">Move with Urgency 신속한 속도로 움직인다.</h3>
<ul>
<li>새로운 혁신은 사용자들에게 금세 최소한의 기준이 된다.</li>
<li>실패하더라도 신속하게 시도하는게 언제나 낫다.</li>
<li>임팩트가 클 것으로 예상되면 더 빠르게 실험해본다.</li>
</ul>
<h3 id="ask-for-feedback-피드백을-자주-구하라">Ask for feedback 피드백을 자주 구하라</h3>
<ul>
<li>솔직한 피드백을 구하는 것은 용기가 필요한 일이지만, 그 불편함을 피하지 않고 더 많이, 자주 피드백을 구하는 것이 성장을 위한 길이다.</li>
<li>모든 영역에 걸친 피드백을 주기적으로 요청하고 그 피드백에 대해 진정으로 받아들이는 태도를 가지자.</li>
</ul>
<p>이런 태도를 가지고 앞으로 토스 러너하이 과정을 진행해 볼 예정이다. 토스 러너하이 스트레칭 끝!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JVM Heap Area (Java8)]]></title>
            <link>https://velog.io/@jay_be/JVM-Heap-Area-Java8</link>
            <guid>https://velog.io/@jay_be/JVM-Heap-Area-Java8</guid>
            <pubDate>Thu, 23 Jan 2025 07:48:49 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>최근 고객사에서 outOfMemoryError가 발생하였다. JVM의 GC 방법과 JVM의 Heap Area에 대해 알아야 해당 문제의 발생 원인과 해결책을 마련할 수 있다.</p>
</blockquote>
<p>참조 : 인프런 - <a href="https://www.inflearn.com/course/%EB%A9%B4%EC%A0%91-%EC%8B%A0%EC%9E%85-java-%EB%B0%B1%EC%95%A4%EB%93%9C-%EA%B0%9C%EB%B0%9C%EC%9E%90">면접 전에 알고 가면 좋을 것들 - 신입 Java 백엔드 개발자편</a></p>
<p><img src="https://velog.velcdn.com/images/jay_be/post/30aef1f2-c7e0-4cc9-82b7-acfc3a90ac60/image.png" alt=""></p>
<h2 id="runtime-data-area-total-size">Runtime Data Area (Total Size)</h2>
<p>Runtime Data Area란, JVM이 프로그램을 수행하기 위해 OS로부터 할당 받은 메모리 영역이다. 여기서 중요한 점은 (Total Size) 이다.  톰캣이나 jar를 실행할 때 옵션(파라미터)으로 Total size를 지정해준다. 결국은 유한한 자원을 가지고 있다는 뜻이다. </p>
<p>Runtime Data Area의 크기를 초과하면 메모리 할당 오류가 발생하고, 이는 프로그램이 예상치 못한 방식으로 종료될 수 있음을 의미한다. 이를 방지하려면 메모리 용량을 적절하게 관리하고, 애플리케이션에서 불필요한 객체를 정리하는 등 메모리 최적화가 필요하다.</p>
<h2 id="heap-space">Heap Space</h2>
<h3 id="young-generation">Young generation</h3>
<p>새로 생성한 객체가 사용되는 영역</p>
<ul>
<li>Minor GC 대상 영역</li>
<li>Eden, From, To 요소로 구성</li>
</ul>
<h3 id="eden">Eden</h3>
<ul>
<li>객체 생성 직후 저장되는 영역</li>
<li>Minor GC 발생 시 Survivor 영역으로 이동</li>
<li>Copy &amp; Scavenge 알고리즘 사용</li>
</ul>
<h3 id="survivor-0-1">Survivor 0, 1</h3>
<ul>
<li>Minor GC 발생 시 Eden, S0에서 살아남은 객체는 S1으로 이동</li>
<li>S1에서 살아남은 객체는 Old영역으로 이동</li>
</ul>
<h3 id="old-old-generation">Old (Old generation)</h3>
<ul>
<li>Young generation 영역에서 소멸하지 않고 남은 개체들이 사용하는 영역</li>
<li>Full GC 발생 시 개체 회수 → 지연이 됌 → 지연이 길어질 시 장애 발생</li>
<li>Mark &amp; Compact 알고리즘 사용</li>
</ul>
<h2 id="알아야-하는-내용">알아야 하는 내용</h2>
<p>객체를 생성하면 Eden에 객체가 저장되고 알고리즘에 의해 survivor 영역으로 객체가 넘어간다.</p>
<p>survivor 0과 1은 from 과 to로 번갈아가면서 변경되면서 Ninor GC가 발생한다. Minor GC는 짧은 시간 동안 수행되며, 프로그램 전체 성능에 큰 영향을 주지 않는 경우가 많다.</p>
<p>문제는 Old generation으로 간 객체들이 문제이다. young generation에서 살아남은 객체들 (Age Counter가 임계치를 넘었거나, 크기가 크거나, Survivor 영역에 공간이 부족한 경우 Full(Major) GC에 의해 주로 관리된다.</p>
<p>Full GC는 힙 메모리 전체를 대상으로 수행되는 GC 이벤트이다. Young generation, Old generation, 필요 시 Metaspace까지 정리한다. 이는 Stop-the-World 이벤트를 동반하며, 애플리케이션의 모든 스레드가 중단된다. 빈번한 Full GC가 일어나게 되면 애플리케이션이 멈추는 시간이 늘어나고 성능이 저하되는 문제가 발생한다. 결론적으로 Full GC가 자주 일어나는 코드는 좋은 코드가 아니다.</p>
<p>알아야 하는 내용</p>
<p>객체를 생성하면 Eden에 객체가 저장되고 알고리즘에 의해 survivor 영역으로 객체가 넘어간다.</p>
<p>survivor 0과 1은 from 과 to로 번갈아가면서 변경되면서 Ninor GC가 발생한다. Minor GC는 짧은 시간 동안 수행되며, 프로그램 전체 성능에 큰 영향을 주지 않는 경우가 많다.</p>
<p>문제는 Old generation으로 간 객체들이 문제이다. young generation에서 살아남은 객체들 (Age Counter가 임계치를 넘었거나, 크기가 크거나, Survivor 영역에 공간이 부족한 경우 Full(Major) GC에 의해 주로 관리된다.</p>
<p>Full GC는 힙 메모리 전체를 대상으로 수행되는 GC 이벤트이다. Young generation, Old generation, 필요 시 Metaspace까지 정리한다. 이는 Stop-the-World 이벤트를 동반하며, 애플리케이션의 모든 스레드가 중단된다. 빈번한 Full GC가 일어나게 되면 애플리케이션이 멈추는 시간이 늘어나고 성능이 저하되는 문제가 발생한다. 결론적으로 Full GC가 자주 일어나는 코드는 좋은 코드가 아니다.</p>
<h2 id="metaspace-java8">Metaspace (Java8)</h2>
<ul>
<li>문자열 상수</li>
<li>로드되는 클래스, 메서드 등의 관한 메타 정보를 저장.</li>
<li>Java heap이 아닌 Native 메모리 영역을 사용 (속도 문제)</li>
<li>Spring 애플리케이션에서 Metaspace를 많이 사용하는 경향이 있음 (속도 문제)</li>
<li>리플랙션 클래스 로드 시 사용 (Spring)</li>
<li>어떤 경우에는 Heap Space 보다 Metaspace 영역의 크기가 커야하는 경우도 있다.</li>
<li>TPS가 많으면 Metaspace영역을 많이 사용한다.</li>
</ul>
<p>알아야 하는 내용</p>
<p>객체를 생성하면 Eden에 객체가 저장되고 알고리즘에 의해 survivor 영역으로 객체가 넘어간다.</p>
<p>survivor 0과 1은 from 과 to로 번갈아가면서 변경되면서 Ninor GC가 발생한다. Minor GC는 짧은 시간 동안 수행되며, 프로그램 전체 성능에 큰 영향을 주지 않는 경우가 많다.</p>
<p>문제는 Old generation으로 간 객체들이 문제이다. young generation에서 살아남은 객체들 (Age Counter가 임계치를 넘었거나, 크기가 크거나, Survivor 영역에 공간이 부족한 경우 Full(Major) GC에 의해 주로 관리된다.</p>
<p>Full GC는 힙 메모리 전체를 대상으로 수행되는 GC 이벤트이다. Young generation, Old generation, 필요 시 Metaspace까지 정리한다. 이는 Stop-the-World 이벤트를 동반하며, 애플리케이션의 모든 스레드가 중단된다. 빈번한 Full GC가 일어나게 되면 애플리케이션이 멈추는 시간이 늘어나고 성능이 저하되는 문제가 발생한다. 결론적으로 Full GC가 자주 일어나는 코드는 좋은 코드가 아니다.</p>
<h2 id="metaspace-java8-1">Metaspace (Java8)</h2>
<ul>
<li>문자열 상수</li>
<li>로드되는 클래스, 메서드 등의 관한 메타 정보를 저장.</li>
<li>Java heap이 아닌 Native 메모리 영역을 사용 (속도 문제)</li>
<li>Spring 애플리케이션에서 Metaspace를 많이 사용하는 경향이 있음 (속도 문제)</li>
<li>리플랙션 클래스 로드 시 사용 (Spring)</li>
<li>어떤 경우에는 Heap Space 보다 Metaspace 영역의 크기가 커야하는 경우도 있다.<ul>
<li>TPS가 많으면 Metaspace영역을 많이 사용한다.</li>
</ul>
</li>
</ul>
<h2 id="native-area">Native Area</h2>
<p>JVM에서 운영체제의 메모리를 직접 사용하는 영역으로, Java 애플리케이션의 동작과 관련된 다양한 네이티브 작업이 수행된다. JVM은 Java 객체를 관리하는  Heap과는 별도로, 네이티브 메모리 영역을 활용하여 JVM 자체 및 네이티브 라이브러리의 동작을 지원한다.</p>
<h3 id="heap-메모리와-독립적">Heap 메모리와 독립적</h3>
<ul>
<li>Native Area는 Java Heap 과는 별도의 영역</li>
<li>GC의 영향을 받지 않으며, 직접 관리되어야 한다.</li>
</ul>
<h3 id="jni-및-네이티브-코드">JNI 및 네이티브 코드</h3>
<ul>
<li>JNI를 통해 호출된 네이티브 메서드가 메모리를 할당하거나 사용하는 경우 이 영역을 사용한다.</li>
</ul>
<h3 id="jvm-내부의-네이티브-작업">JVM 내부의 네이티브 작업</h3>
<ul>
<li>ClassLoader, Thread, JIT 컴파일러, 메서드 호출 스택 등 JVM 내부 동작과 관련된 리소스가 저장된다.</li>
</ul>
<h3 id="메모리-관리">메모리 관리</h3>
<ul>
<li>Native Area 메모리는 Java에서 GC의 영향을 받지 않으므로 명시적으로 해제해야 한다.</li>
<li>누수가 발생하면 OutOfMemoryError 또는 시스템 메모리 부족 상태를 유발할 수 있다.</li>
</ul>
<h2 id="runtime-data-area-주요-에러">Runtime Data Area 주요 에러</h2>
<h3 id="outofmemoryerror-java-heap-space">OutofMemoryError: Java heap space</h3>
<ul>
<li>Heap 영역에 할당할 수 있는 메모리 공간이 부족할 떄 발생하는 오류. 객체를 더 이상 생성할 수 없으며, JVM이 동적으로 할당하려는 메모리가 부족하다는 신호이다.</li>
<li>해결 방법 : JVM의 메모리 할당 크기를 늘리거나 메모리 누수를 해결해야 한다.</li>
<li>예: -Xmx 옵션으로 힙 메모리 크기를 늘릴 수 있다.</li>
</ul>
<h3 id="outofmemoryerror-permgen-space--metaspace">OutOfMemoryError: PermGen space / Metaspace</h3>
<ul>
<li>Method Area 또는 metaspace(Java8 이후)에 클래스 메타 데이터나 JIT 컴파일된 코드가 저장된다. 이 공간이 초과되면 해당 오류가 발생한다.</li>
<li>해결 방법 : -XX:MaxMetaspaceSize 옵션으로 메타스페이스의 크기를 설정할 수 있다.</li>
</ul>
<h3 id="stackoverflowerror">StackOverflowError</h3>
<ul>
<li>Stack 영역의 크기를 초과할 때 발생할 수 있는 오류이다. 주로 재귀 호출로 인해 스택 메모리가 초과될 수 있다.</li>
<li>해결 방법 : 재귀 호출을 줄이거나 JVM 스택 크기를 늘릴 수 있다.(-Xss 옵션)</li>
</ul>
<h2 id="어떻게-해결할까-">어떻게 해결할까 ?</h2>
<p>운영 중에 outofMemoryError를 만난다면 당황할 수 밖에 없다. 결론은 코드에서 찾아야 한다. 코드에서 Young generation에서 오래 살아남은 객체들이 Old generation에 과도하게 많이 올라가는 경우, Metaspace 영역을 많이 사용하는 경우 빈번한 Full GC가 일어나면서 Stop-the-World가 이뤄지고 이것은 장애로 이루어질 수 있다.</p>
<p>하지만 JVM 설정과 GC 종류를 변경하여 해결하는 방법도 있다. 또한 APM (Application Performance Management) 을 사용하여 애플리케이션을 모니터링하고 부하 테스트를 통해 원인을 찾아야 한다.
이런 경우에는 JVM에 다양한 옵션을 통해서 Heap, Stack, Metaspace 영역을 늘리고, 그것에 맞게 GC를 선택할 수 있다.</p>
<p>이런 경우에는 JVM에 다양한 옵션을 통해서 Heap, Stack, Metaspace 영역을 늘리고, 그것에 맞게 GC를 선택할 수 있다.</p>
<h2 id="gc-종류">GC 종류</h2>
<h3 id="serial-gc">Serial GC</h3>
<ul>
<li>단일 스레드 환경 및 소규모 응용 프로그램을 위한 간단한 GC</li>
<li>Minor GC에서 Copy &amp; Scavenge 알고리즘 적용</li>
<li>Full GC에서 Mark &amp; Compact 알고리즘 적용</li>
</ul>
<h3 id="parallel-gc">Parallel GC</h3>
<ul>
<li>JVM 기본 옵션(Java 8 기본)</li>
<li>멀티 스레드 기반 (개수 지정 가능)으로 작동해 효율을 높임</li>
<li>Low-pause (응용 프로그램 중단 최소화)</li>
<li>Throughput (Mark &amp; Compact 알고리즘 기반으로 신속성 최대화)</li>
</ul>
<h3 id="concurrnet-gc">Concurrnet GC</h3>
<ul>
<li>Low-pause와 유사하며 응용 프로그램 실행 중 GC 실행</li>
<li>동작 중지 최소화</li>
</ul>
<h3 id="incremental-gc-train-gc">Incremental GC (Train GC)</h3>
<ul>
<li>Concurrent GC와 유사하나 Minor GC 발생 시 Full GC를 일부 병행</li>
<li>경우에 따라 오히려 더 느려지는 부작용</li>
</ul>
<h3 id="g1-garbage-first-gb">G1 (Garbage First) GB</h3>
<ul>
<li>4GB 이상 대용량 Heap 메모리를 사용하는 멀티스레드 기반 응용 프로그램에 특화된 GC</li>
<li>Heap을 영역(1~32MB)단위로 분할한 후 멀티스레드로 스캔</li>
<li>가비지가 가장 많은 영역부터 수집 실시
<img src="https://velog.velcdn.com/images/jay_be/post/9b290982-78cd-44a9-95bc-589ea4c081fc/image.png" alt="">
보통 Heap 메모리를 4G 이상으로 변경한 후 G1GB 설정하는 것으로 GC 튜닝 할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Tomcat vs Undertow 성능 측정]]></title>
            <link>https://velog.io/@jay_be/Tomcat-vs-Undertow-%EC%84%B1%EB%8A%A5-%EC%B8%A1%EC%A0%95</link>
            <guid>https://velog.io/@jay_be/Tomcat-vs-Undertow-%EC%84%B1%EB%8A%A5-%EC%B8%A1%EC%A0%95</guid>
            <pubDate>Thu, 23 Jan 2025 07:38:25 GMT</pubDate>
            <description><![CDATA[<h2 id="빌드된-jar-파일-사이즈-비교">빌드된 jar 파일 사이즈 비교</h2>
<h3 id="tomcat">Tomcat</h3>
<p><img src="https://velog.velcdn.com/images/jay_be/post/326d3202-f549-4416-9cde-1d8189a9ef78/image.png" alt=""></p>
<h3 id="undertow">Undertow</h3>
<p><img src="https://velog.velcdn.com/images/jay_be/post/b15b4c5a-4b45-4830-9769-d7711cc0a6bd/image.png" alt=""></p>
<p>빌드한 JAR 파일을 확인해 보니, Undertow를 사용한 경우 예상외로 용량이 더 큰 것으로 나타났다. 이것은 의존성의 크기 차이 때문이다. Undertow를 사용하면 Tomcat과는 다른 의존성이 포함된다. Undertow 관련 라이브러리나 플러그인이 Tomcat 보다 더 많은 용량을 차지할 가능성이 있기 때문이다. 필요하지 않는 라이브러리를 제거하여 더 줄일 수 있다. 하지만 약 1MB 차이는 크게 문제되지 않기 때문에 변경하지는 않았다. 
단, 필요한 경우 일부 라이브러리를 제거할 수 있다는 점을 알아야 한다.</p>
<h2 id="성능-테스트---ngrinder">성능 테스트 - ngrinder</h2>
<p>간단한 String을 반환하는 기능에 부하 테스트를 진행하였다.</p>
<pre><code class="language-java">@RestController
public class TestController {

    @GetMapping(&quot;/test1&quot;)
    public String testResponse() {
        for (int i = 0; i &lt; 10000; i++) {
            System.out.println(&quot;hello world&quot;);
        }
        return &quot;response&quot;;
    }
    // 생략</code></pre>
<h2 id="테스트-조건">테스트 조건</h2>
<p><img src="https://velog.velcdn.com/images/jay_be/post/bcc86bd3-a601-43e9-ba25-cc09d13d5641/image.png" alt=""></p>
<p>에이전트별 가상 사용자를 1000 과 3000 두 상황을 나눠 테스트를 진행하였다.</p>
<h2 id="가상-사용자---1000">가상 사용자 - 1000</h2>
<h3 id="tomcat-1">Tomcat</h3>
<p><img src="https://velog.velcdn.com/images/jay_be/post/9cfea909-a8d5-4b4c-9eb2-f58c0e08c1a1/image.png" alt=""></p>
<h3 id="undertow-1">Undertow</h3>
<p><img src="https://velog.velcdn.com/images/jay_be/post/019308d7-65d9-455f-8853-62bf6eab6679/image.png" alt=""></p>
<h2 id="가상-사용자---1000-결과">가상 사용자 - 1000 결과</h2>
<h3 id="tps-transaction-per-second">TPS (Transaction Per Second)</h3>
<p>초당 트랜잭션 횟수. 즉, 초당 몇 개의 트랜잭션을 처리할 수 있는가 (높을 수록 좋음)</p>
<ul>
<li>Tomcat &lt; Undertow</li>
<li>큰 차이는 아니지만 Undertow가 더 많이 처리한 것을 확인할 수 있다.</li>
</ul>
<h3 id="평균-테스트-시간">평균 테스트 시간</h3>
<p>서버가 요청에 대해 응답하는데 걸린 시간 평균 (낮을 수록 좋음)</p>
<ul>
<li>Tomcat &gt; Undertow</li>
<li>평균 응답 시간도 Undertow가 더 빠르다는 것을 알 수 있다.</li>
</ul>
<h2 id="가상-사용자---3000">가상 사용자 - 3000</h2>
<h3 id="tomcat-2">Tomcat</h3>
<p><img src="https://velog.velcdn.com/images/jay_be/post/e3c780b8-7be1-4db3-a1d3-75e7a281f305/image.png" alt=""></p>
<h3 id="undertow-2">Undertow</h3>
<p><img src="https://velog.velcdn.com/images/jay_be/post/8369c67f-f6fa-4cb1-93f0-5bfa39075cfc/image.png" alt=""></p>
<h2 id="가상-사용자---3000-결과">가상 사용자 - 3000 결과</h2>
<h3 id="tps-transaction-per-second-1">TPS (Transaction Per Second)</h3>
<p>초당 트랜잭션 횟수. 즉, 초당 몇 개의 트랜잭션을 처리할 수 있는가 (높을 수록 좋음)</p>
<ul>
<li>Tomcat &lt; Undertow</li>
<li>가상 사용자가 늘어남에 따라 Tomcat은 TPS가 떨어진 반면 Undertow는 TPS가 늘어난 것을 확인할 수 있다.</li>
</ul>
<h3 id="평균-테스트-시간-1">평균 테스트 시간</h3>
<p>서버가 요청에 대해 응답하는데 걸린 시간 평균 (낮을 수록 좋음)</p>
<ul>
<li>Tomcat &gt; Undertow</li>
<li>이 역시 사용자가 늘어남에 따라 평균 응답 시간이 많이 차이는 것을 확인할 수 있다.</li>
</ul>
<h2 id="결론">결론</h2>
<p>이번 테스트를 통해 간단히 WAS 서버를 Tomcat에서 Undertow로 변경했을 뿐인데, 두 서버 간의 성능 차이가 상당히 크다는 것을 확인할 수 있었다. 가상 사용자를 3000으로 설정한 테스트 환경에서 Undertow는 TPS 면에서 Tomcat을 크게 상회하였으며, 사용자 수가 늘어남에 따라 그 격차는 더 두드러졌다. 반면, 평균 응답 시간 측면에서도 Undertow는 사용자 증가에 따라 안정적인 응답 속도를 유지하는데 뛰어난 성능을 보여주었다.</p>
<p>이러한 결과는 Undertow가 고성능과 경량화를 목표로 설계된 비동기 기반 WAS라는 점에서 비롯된 것으로 보인다. 특히 Tomcat은 기본적으로 스레드 기반 처리 모델을 사용하는 반면, Undertow는 비동기 I/O를 사용하여 리소스 효율성을 극대화한다는 차이가 주요 요인이라고 생각한다.</p>
<p>이번 테스트를 통해 Undertow가 높은 부하 환경에서도 안정적이고 뛰어난 성능을 제공한다는 것을 직접 증명하였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[WEB 서버 vs WAS 서버]]></title>
            <link>https://velog.io/@jay_be/WEB-%EC%84%9C%EB%B2%84-vs-WAS-%EC%84%9C%EB%B2%84</link>
            <guid>https://velog.io/@jay_be/WEB-%EC%84%9C%EB%B2%84-vs-WAS-%EC%84%9C%EB%B2%84</guid>
            <pubDate>Thu, 23 Jan 2025 07:30:17 GMT</pubDate>
            <description><![CDATA[<h1 id="웹-서버web-server와-wasweb-application-server란-">웹 서버(Web Server)와 WAS(Web Application Server)란 ?</h1>
<h2 id="웹-서버web-server란">웹 서버(Web Server)란?</h2>
<p>웹 서버는 클라이언트(주로 웹 브라우저)로 부터 요청을 받아 정적 컨텐츠(HTML, CSS, JavaScript, 이미지 등)를 제공하는 역할이다.</p>
<h3 id="주요-기능">주요 기능</h3>
<ul>
<li>HTTP 요청 처리</li>
<li>정적 리소스 제공</li>
<li>요청에 따라 적절한 컨텐츠 반환</li>
</ul>
<h3 id="예시">예시</h3>
<ul>
<li>Apache HTTP Server</li>
<li>Nginx</li>
<li>IIS(Internet Information Service)</li>
</ul>
<h3 id="한계점">한계점</h3>
<p>동적 컨텐츠(예 : 사용자 입력을 처리하거나 데이터베이스와 상호작용) 제공이 어렵다.</p>
<h2 id="wasweb-application-server란">WAS(Web Application Server)란?</h2>
<p>WAS는 동적인 컨텐츠를 처리하는 서버로, 애플리케이션 로직 실행을 담당한다. 클라이언트의 요청을 처리하기 위해 데이터베이스와 상호작용하거나 비즈니스 로직을 수행한 뒤 결과를 반환한다.</p>
<h3 id="주요-기능-1">주요 기능</h3>
<ul>
<li>동적 컨텐츠 생성</li>
<li>애플리케이션 로직 실행</li>
<li>데이터베이스와의 통신</li>
</ul>
<h3 id="예시-1">예시</h3>
<ul>
<li>Apache Tomcat</li>
<li>JBoss/WildFly</li>
<li>WebSphere</li>
<li>Spring Boot 내장 WAS (예: 톰캣)</li>
</ul>
<h3 id="추가-기능">추가 기능</h3>
<ul>
<li>트랜잭션 관리</li>
<li>세션 관리</li>
<li>애플리케이션 보안</li>
</ul>
<h2 id="웹-서버와-was의-차이">웹 서버와 WAS의 차이</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>웹 서버</th>
<th>WAS</th>
</tr>
</thead>
<tbody><tr>
<td>제공 컨텐츠</td>
<td>정적 컨텐츠 (HTML, CSS 등)</td>
<td>동적 컨텐츠 (애플리케이션 로직 처리)</td>
</tr>
<tr>
<td>주요 역할</td>
<td>요청 분배 및 정적 리소스 제공</td>
<td>비즈니스 로직 처리 및 데이터 연동</td>
</tr>
<tr>
<td>대표 서버</td>
<td>NginX, Apache</td>
<td>Tomcat, WebLogic</td>
</tr>
<tr>
<td>주요사용 사례</td>
<td>정적 웹 사이트, 리버스 프록시 서버</td>
<td>API 서버</td>
</tr>
</tbody></table>
<h3 id="웹-서버와-was의-협업-구조">웹 서버와 WAS의 협업 구조</h3>
<p>웹 서버와 WAS는 함께 사용되는 경우가 많다.</p>
<ol>
<li>클라이언트 : 웹 브라우저를 통해 서버로 요청을 보낸다.</li>
<li>서버 : 정적 리소스를 제공하거나, 동적 요청은 WAS로 전달한다.</li>
<li>WAS : 비즈니스 로직을 처리하고, 필요한 데이터를 데이터베이스에서 가져온다.</li>
<li>웹 서버 : WAS로부터 받은 응답을 클라이언트에 반환한다.</li>
</ol>
<h3 id="예시-아키텍처">예시 아키텍처</h3>
<ul>
<li>Nginx(웹 서버) + Tomcat(WAS)</li>
<li>Apache(웹 서버) + Spring Boot 내장 WAS</li>
</ul>
<h2 id="웹-서버와-was를-분리하는-이유">웹 서버와 WAS를 분리하는 이유</h2>
<h3 id="성능-향상">성능 향상</h3>
<p>정적 컨텐츠는 웹 서버에서 처리하고, 동적 컨텐츠는 WAS가 처리하도록 분리하면 효율적이다.</p>
<h3 id="확장성">확장성</h3>
<p>트래픽이 증가할 경우 웹 서버와 WAS를 각각 독립적으로 확장 가능하다.</p>
<h3 id="보안-강화">보안 강화</h3>
<p>웹 서버를 리버스 프록시로 활용하여 WAS를 외부에 직접 노출하지 않도록 설정할 수 있다.</p>
<h2 id="결론">결론</h2>
<p>웹 서버와 WAS는 각자의 장점을 가지고 있으며, 현대 웹 환경에서 주로 사용된다. 적절한 설정과 아키텍처 설계를 통해 성능과 안정성을 극대화할 수 있다.</p>
<h2 id="나는-웹서버를-따로-사용하지-않는데-">나는 웹서버를 따로 사용하지 않는데 ?</h2>
<p>Spring Boot와 같은 내장 톰캣(Web Application Server)를 사용하는 경우, 웹 서버와 WAS가 하나의 프로세스내에서 동작한다. 이 경우 별도의 외부 웹 서버(Nginx, Apache 등)를 사용하지 않아도 웹 요청을 처리할 수 있다.</p>
<h2 id="내장-톰캣spring-boot에서의-동작-방식">내장 톰캣(Spring Boot)에서의 동작 방식</h2>
<h3 id="내장-톰캣-역할">내장 톰캣 역할</h3>
<p>Spring Boot는 애플리케이션에 톰캣(WAS)를 내장하고 있어, 정적 리소스와 동적 리소스 모두 처리할 수 있다.</p>
<h3 id="정적-리소스-처리">정적 리소스 처리</h3>
<p>/static, /public, /resources 디렉토리에 있는 정적 파일(HTML, CSS, JavaScript 등)을 직접 클라이언트에 전달한다.</p>
<p>정적 컨텐츠 제공 역할은 외부 웹 서버가 아닌 내장 톰캣이 수행한다.</p>
<h3 id="동적-리소스-처리">동적 리소스 처리</h3>
<p>REST API 요청 또는 데이터베이스 연동과 같은 동적 작업은 Spring MVC와 같은 애플리케이션 로직을 통해 처리된다.</p>
<p>내장 톰캣이 HTTP 요청을 수신하고 이를 DispatcherServlet(Spring MVC)으로 전달한다.</p>
<h2 id="내장-톰캣-사용-시-요청-흐름">내장 톰캣 사용 시 요청 흐름</h2>
<h3 id="요청-흐름">요청 흐름</h3>
<ol>
<li><p>클라이언트 요청</p>
<ul>
<li>클라이언트(브라우저, REST 클라이언트 등)가 HTTP 요청을 전송한다.</li>
</ul>
</li>
<li><p>톰캣이 요청 수신</p>
<ul>
<li>내장 톰캣이 기본적으로 설정된 포트(예 : 8080)에서 요청을 수신한다.</li>
</ul>
</li>
<li><p>정적 리소스 처리</p>
<ul>
<li>요청한 리소스가 /static, /public, /resources 등에 존재하면, 톰캣이 해당 정적 파일을 직접 반환한다.</li>
<li>예 : /index.html 요청 시 /src/main/resources/static/index.html 파일을 반환</li>
</ul>
</li>
<li><p>동적 요청 처리</p>
<ul>
<li>요청이 정적 리소스가 아닌 경우, 톰캣이 DispatcherServlet(Spring MVC 핵심 컴포넌트)으로 요청을 전달한다.</li>
<li>DispatcherServlet은 Controller와 Service를 거쳐 비즈니스 로직을 실행하고 응답을 생성한다.</li>
<li>예 : /api/products 요청 시 Controller에서 데이터를 조회하여 JSON 응답 반환</li>
</ul>
</li>
<li><p>응답 반환</p>
<ul>
<li>톰캣이 처리 결과를 HTTP 응답으로 클라이언트에 반환</li>
</ul>
</li>
</ol>
<h2 id="외부-웹-서버-없이-내장-톰캣만-사용하는-이유">외부 웹 서버 없이 내장 톰캣만 사용하는 이유</h2>
<h3 id="장점">장점</h3>
<ol>
<li><p>단순한 배포</p>
<ul>
<li>애플리케이션과 톰캣이 하나의 실행 파일로 통합되어 별도 웹 서버 설정이 필요 없다.</li>
<li>JAR 파일만 실행하면 애플리케이션과 웹 서버가 함께 작동.</li>
</ul>
</li>
<li><p>개발 편의성</p>
<ul>
<li>내장 톰캣은 Spring Boot에서 자동으로 설정되므로, 별도 WAS 설치 및 설정 작업이 필요 없다.</li>
</ul>
</li>
<li><p>효율적인 리소스 사용</p>
<ul>
<li>내장 톰캣은 애플리케이션과 동일한 JVM에서 실행되어 리소스를 공유한다.</li>
</ul>
</li>
<li><p>배포 아키텍처 단순화</p>
<ul>
<li>별도의 외부 웹 서버를 구성할 필요가 없어 배포 아키텍처가 단순하다.</li>
</ul>
</li>
</ol>
<h3 id="단점">단점</h3>
<ol>
<li><p>정적 컨텐츠 처리 성능 제한</p>
<ul>
<li>Nginx, Apache 같은 전문 웹 서버에 비해 정적 컨텐츠 처리 성능이 떨어질 수 있다.</li>
<li>특히, 많은 정적 리소스를 제공하거나 CDN(Content Delivery Network)을 사용하는 경우 한계가 있을 수 있다.</li>
</ul>
</li>
<li><p>확장성 부족</p>
<ul>
<li>내장 톰캣은 단일 프로세스이기 때문에 트래픽이 급증하면 부하 분산이 어렵다.</li>
<li>리버스 프록시(Nginx, Apache)를 추가하면 해결 가능.</li>
</ul>
</li>
</ol>
<h3 id="결론-1">결론</h3>
<p>내장 톰캣은 작은 규모의 애플리케이션이나 빠른 개발에 적합하다. 외부 웹 서버를 추가하지 않고도 간단히 작동하므로 개발 초기 단계에서 유용하다.</p>
<p>그러나 대규모 트래픽 처리나 복잡한 정적 리소스 관리가 필요한 경우, 외부 웹 서버를 추가해 리버스 프록시와 같은 구조로 확장하는 것이 좋다.</p>
<h2 id="리버스-프록시">리버스 프록시</h2>
<p>리버스 프록시는 클라이언트와 서버 사이에 위치해 요청과 응답을 중계하는 역할을 하는 서버이다. 이를 통해 클라이언트는 실제 서버와 직접 통신하지 않고 리버스 프록시를 통해 간접적으로 통신하게 된다.</p>
<h2 id="왜-리버스-프록시를-쓰는가">왜 리버스 프록시를 쓰는가</h2>
<h3 id="1-보안-강화">1. 보안 강화</h3>
<ul>
<li>클라이언트는 실제 서버의 IP 주소를 알지 못하므로, 서브를 직접 공격하기 어렵다.</li>
<li>리버스 프록시가 방패 역할을 한다.</li>
</ul>
<h3 id="2-로드-밸런싱">2. 로드 밸런싱</h3>
<ul>
<li>리버스 프록시가 여러 서버로 요청을 분산시켜서, 하나의 서버에 과부하가 걸리지 않게 한다.</li>
<li>예를 들어, Nginx 리버스 프록시는 여러 서버에 트래픽을 나누어 분산 처리한다.</li>
</ul>
<h3 id="3-정적-리소스-처리">3. 정적 리소스 처리</h3>
<ul>
<li>HTML, CSS, JavaScript 같은 정적 파일은 리버스 프록시(Nginx, Apache)가 빠르게 처리하고, 동적 요청(API 등)만 실제 서버로 보낸다.</li>
</ul>
<h3 id="4-sslhttps-처리">4. SSL(HTTPS) 처리</h3>
<ul>
<li>리버스 프록시가 HTTPS 인증서를 관리하고 암호화된 통신을 처리한다. 실제 서버는 HTTP만 처리하므로 더 간단해진다.</li>
</ul>
<h3 id="5-캐싱">5. 캐싱</h3>
<ul>
<li>자주 요청되는 데이터를 리버스 프록시에 저장해 클라이언트 요청이 더 빠르게 응답한다.(예 : 이미지 파일)</li>
</ul>
<h2 id="리버스-프록시-vs-포워드-프록시">리버스 프록시 vs 포워드 프록시</h2>
<ul>
<li>리버스 프록시 : 클라이언트를 대신해 서버와 통신 (서버를 보호)</li>
<li>포워드 프록시 : 서버를 대신해 클라이언트가 통신 (클라이언트를 보호)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[패스트캠퍼스 재직자 과정 참여 후 달라진 점]]></title>
            <link>https://velog.io/@jay_be/%ED%8C%A8%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%8D%BC%EC%8A%A4-%EC%9E%AC%EC%A7%81%EC%9E%90-%EA%B3%BC%EC%A0%95-%EC%B0%B8%EC%97%AC-%ED%9B%84-%EB%8B%AC%EB%9D%BC%EC%A7%84-%EC%A0%90</link>
            <guid>https://velog.io/@jay_be/%ED%8C%A8%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%8D%BC%EC%8A%A4-%EC%9E%AC%EC%A7%81%EC%9E%90-%EA%B3%BC%EC%A0%95-%EC%B0%B8%EC%97%AC-%ED%9B%84-%EB%8B%AC%EB%9D%BC%EC%A7%84-%EC%A0%90</guid>
            <pubDate>Sun, 19 Jan 2025 13:35:52 GMT</pubDate>
            <description><![CDATA[<p>나를 포함해서 업무에는 익숙해진 많은 <strong>주니어 개발자</strong>들이 하는 고민이 있다.</p>
<blockquote>
<p>일은 잘하고 있는 것 같은데.. 내가 과연 &#39;<strong>성장</strong>&#39;한 것일까??</p>
</blockquote>
<blockquote>
<p>내가 정말 &#39;<strong>경쟁력</strong>&#39; 있는 개발자일까?</p>
</blockquote>
<blockquote>
<p>&#39;<strong>이직</strong>&#39;을 잘 할 수 있을까?</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jay_be/post/62162a57-e462-4365-bdc5-b6942124dd4c/image.png" alt=""></p>
<p>나의 개발 자신감 그래프를 그려 보았다.</p>
<ul>
<li><strong>취업 직후</strong> : 무엇이든 다 만들 수 있는 개발자라고 생각</li>
<li><strong>1년 차</strong> : 업무를 쳐내느라 바쁜 시기</li>
<li><strong>2년 차</strong> : 업무에 슬슬 익숙해지는 시기</li>
<li><strong>3년 차</strong> : 내가 물경력자가 되고 있나 라는 불안감이 생기는 시기</li>
<li><strong>패스트캠퍼스 재직자 과정 수료 후</strong> : 업무에서, 이직 시장에서 자신감이 생김</li>
<li><strong>현재</strong> : 다양한 경험을 바탕으로 나만의 개발 방향성을 찾고 나아가고 있음</li>
</ul>
<p>3년 차 개발자가 되면서 회사 업무에는 익숙해졌지만, 반복되는 업무와 바뀌지 않는 기술 스택, 항상 같은 방식으로 작성하는 코드 스타일에 지치고, 조언을 구할 선배 개발자가 없는 상황에서 고민이 많았다. 내가 작성하는 코드가 과연 좋은 코드인지 확신이 없었고, 책과 강의를 통해 이론을 쌓았지만, 실제로 피드백을 주고받으며 성장할 수 있는 기회가 필요하다고 느꼈다.</p>
<p>그래서 <strong>패스트캠퍼스 캠퍼스 재직자 과정</strong>에 참여하게 되었고, 그 과정에서 많은 성장을 이루었다. 수료 후에는 회사에서 인정받아 <strong>신규 개발팀</strong>에 합류하는 기회를 얻었다.</p>
<p><img src="https://velog.velcdn.com/images/jay_be/post/5f959775-c7d5-443e-8e71-fbf7ddcefeaf/image.png" alt=""></p>
<p><strong>패스트캠퍼스 재직자 과정</strong> 수료 후 나는 명확한 방향성과 목표를 설정할 수 있었다. 더 큰 서비스 기업으로 이직하기 위해 준비를 시작했으며, 동시에 현재 재직 중인 회사에서의 업무 목표도 새롭게 정립하였다.</p>
<p>과정에 참여하면서 스스로 학습하는 습관을 갖추게 되었고, 문제 해결 능력 또한 크게 향상되었다. 특히, 고민이나 난관에 부딪혔을 때 언제든지 함께 논의하고 조언을 구할 수 있는 ** 동료와 멘토**를 만나게 된 점은 큰 자산이 되었다.</p>
<p><strong>패스트캠퍼스 재직자 과정</strong>에서 배운 내용을 활용해 회사 제품의 품질 향상을 위해 노력하고 있다. 또한, 팀원들과 활발히 <strong>소통</strong>하며 이전보다 더 나은 회사 생활을 만들어가고 있다. 과정을 같이 들었던 동료들 역시 누군가는 성공적인 이직을, 누군가는 회사에서 더 큰 역할을 담당하게 되었다.</p>
<p>성장의 욕심이 있지만 길잡이가 필요하다면 <strong>패스트캠퍼스 재직자과정</strong>은 좋은 방법 중 하나라고 생각한다. </p>
<p>해당 과정에 참여하면서 강사와의 교류, 개인 프로젝트, 팀 프로젝트 진행 경험에 대해 작성해 보겠다.</p>
<h2 id="강사와의-공유">강사와의 공유</h2>
<p>패스트 캠퍼스 재직자 과정을 참여하면서 매주 볼타코퍼레이션 CTO 진태양 리더와, 대기업 이커머스 현직자 멘토의 피드백, 업무 노하우를 들을 수 있었다.</p>
<p>패스트캠퍼스 프로젝트뿐만 아니라, 실무 관련 질문, 개발자 커리어, 그리고 회사에서의 후임 개발자에 대한 고민까지도 리더님과 멘토님께서 귀 기울여 들어주시고, 진심 어린 조언을 해주셨다. 
<img src="https://velog.velcdn.com/images/jay_be/post/01ff8271-e5c7-419a-adfa-5f3c7470cc00/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jay_be/post/7065dc46-f11d-49f8-871f-751659ad9f83/image.png" alt=""></p>
<p>특히, 이커머스 프로젝트를 맡아주신 멘토님께서는 좋은 마인드와 깊은 지식을 보유하신 분이라 지금도 굉장히 감사함을 느끼고 있다.. 이런 멘토님도 퇴근하고 개인 공부를 열심히 하는 모습을 보고 나도 굉장히 자극을 받았다.
<img src="https://velog.velcdn.com/images/jay_be/post/ae6afdd3-c9ac-42c6-bf0c-8da3f9560b71/image.png" alt=""></p>
<p>지금도 가끔 서로의 안부를 묻고 실무에서 고민중인 내용을 공유하며 소통하고 있다. 이렇게 좋은 멘토님을 만난 것도 <strong>패스트캠퍼스 재직자 과정</strong>에 참여했기 때문이라고 생각한다.</p>
<h2 id="개인-프로젝트-경험">개인 프로젝트 경험</h2>
<h3 id="오픈소스-라이브러리-개발">오픈소스 라이브러리 개발</h3>
<p>오픈소스를 직접 만들어보고 배포하는 경험을 하였다. 평소 JD를 보게 되면 우대사항에 적혀 있는 문구가 있다.</p>
<p>&quot;오픈소스에 참여한 이력이 있으신 분 우대&quot;</p>
<p>나 역시 컨트리뷰터에 도전하기 위해 여러 유명 프레임워크를 포크해보았지만, 방대한 패키지 구조 속에서 어떤 부분을 봐야 할지, 그리고 어떻게 수정 요청(PR)을 해야 할지 막막함을 느꼈다.</p>
<p>이번 과정에서는 평소 필요하다고 생각했던 오픈소스 라이브러리를 직접 설계하고 구현한 뒤, 배포까지 진행하는 소중한 경험을 할 수 있었다. 더불어 교육생들끼리 서로의 라이브러리를 사용하고 피드백을 주고받는 시간을 가지며, 실제로 오픈소스를 개선하는 과정을 체험할 수 있었다.</p>
<p>이를 통해 오픈소스 생태계에 대한 이해도가 높아졌고, 앞으로는 유명한 오픈소스 프로젝트의 컨트리뷰터로 활발히 활동할 수 있을 것이라는 자신감을 얻게 되었다.</p>
<h3 id="구글-폼-분석-및-개발">구글 폼 분석 및 개발</h3>
<p>구글에서 제공하는 구글 투표 시스템을 참고하여 프로젝트를 설계하고 개발해보는 경험을 했다. 데이터가 동적으로 들어올 때 어떻게 저장하고 수정할지, 쿼리 성능을 높이기 위해 엔티티를 어떻게 설계할지 고민했다. 또한, 테스트 코드를 작성해 소프트웨어의 안정성을 높이기 위해 노력했다.</p>
<p>이 과정에서는 성능 개선을 위해 어떤 부분을 고려해야 하는지에 대한 피드백을 받았고, 진태양 리더님의 1:1 피드백을 통해 실질적인 도움을 받을 수 있었다. 이 경험 덕분에 프로젝트의 성능과 소프트웨어 품질을 향상시키는 방법을 배웠다.</p>
<h2 id="팀-프로젝트-경험">팀 프로젝트 경험</h2>
<p>두 개의 개인 프로젝트 이후 <strong>패스트캠퍼스 재직자 과정의 꽃</strong> 팀 프로젝트가 진행되었다. 원하는 도메인을 선택하고 팀이 배정되었다. 1기에서는 총 3개의 도메인 프로젝트가 있었다.</p>
<h4 id="팀프로젝트-주제">팀프로젝트 주제</h4>
<ul>
<li>O2O</li>
<li>핀테크</li>
<li>커머스</li>
</ul>
<p>나는 평소 커머스 도메인에 관심이 많아 커머스 팀에 들어가게 되었다.
각 팀별로 MVP 개발 요청서를 받았고, 여기에는 기능적 요구사항과 함께 기술적 도전 과제도 포함되어 있었다. 모든 요구사항은 실제 실무에서 사용하는 수준의 현실적이고 구체적인 내용들로, 이커머스 실무 감각을 익힐 수 있다고 생각했다.
<img src="https://velog.velcdn.com/images/jay_be/post/8636cdd7-a237-4fcf-85c9-1b51642390f1/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jay_be/post/00cc569d-c49f-4cf1-b261-56fa3c811eaf/image.png" alt=""></p>
<p>프로젝트의 과정은 순탄치 않았다. 팀 프로젝트 시작과 동시에 회사 업무로 인해 아쉽지만 참여하지 못하는 팀원들도 발생했고, 야근 등 각자의 사정으로 인해 프로젝트 기능 구현의 기간을 지키지 못하는 문제도 발생하였다. </p>
<p>그럼에도 불구하고, 괜찮았다. 프로젝트의 모든 기능적 요구사항과 기술적 도전 과제를 완벽히 해결하려는 데에만 집착하지 않았다. 대신, 나 자신의 성장을 목표로 삼아 최선을 다해 좋은 소프트웨어를 만드는 데 집중했다.</p>
<p>그 결과, 프로젝트는 성공적으로 마무리되었고, 이 과정에서 얻은 경험과 배움은 앞으로 나아가는 데 큰 자산이 되었다. </p>
<p>그 과정에서 좋은 동료들을 만나게 되었고 또 좋은 결과를 만들게 되었다.
이커머스 팀은 청중, 리더, 멘토진에 좋은 평가를 받은 1등팀이 되었다. 아래는 멋진 팀원들과 찍은 사진이다 :)
<img src="https://velog.velcdn.com/images/jay_be/post/05e48750-c4e2-42c9-8315-d11355c81e47/image.png" alt=""></p>
<h2 id="프로젝트-내용">프로젝트 내용</h2>
<h3 id="이너북스--도서-판매-사이트">이너북스 / 도서 판매 사이트</h3>
<ul>
<li>프로젝트 기간 : 2024.08.17 ~2024.11.02 (총 77일)</li>
<li>사용 기술 : Kotlin, Spring Boot, MySQL, Spring Data JPA, Kotlin JDSL, Spring Rest Docs, ArchUnit, Docker, AWS ECS, AWS Fargate, AWS API Gateway, AWS ALB, AWS CloudFront</li>
<li>소프트웨어 설계 패턴 : MSA, 클린 아키텍처</li>
<li>인프라 구성<figure>
    <img src="https://velog.velcdn.com/images/jay_be/post/ecad256c-b056-4550-aa9f-887f3bcf4931/image.png" alt="인프라 구성">
</figure></li>
<li>인프라 구성 - 배포<figure>
    <img src="https://velog.velcdn.com/images/jay_be/post/1752c11a-6174-4403-be7f-00dea0f1c2f1/image.png" alt="인프라 - 배포">
</figure>  </li>
<li>서버 비용 절감<figure>
    <img src="https://velog.velcdn.com/images/jay_be/post/279eb528-4d41-4b1b-92e3-f3ea389a432f/image.png" alt="비용 절감">
</figure></li>
<li>화면 (일부)<figure>
    <img src="https://velog.velcdn.com/images/jay_be/post/d61cadb5-c452-4c05-90d8-84768177a383/image.png" alt="화면">
</figure>

</li>
</ul>
<h3 id="팀-프로젝트-후기">팀 프로젝트 후기</h3>
<p>평소에 사용하고 싶었던 다양한 기술을 마음껏 활용할 수 있는 소중한 기회였으며, 훌륭한 팀원들과 멘토를 만나 많은 것을 배우고 성장할 수 있었던 프로젝트였다. 이커머스 도메인에 대해 깊이 고민하며 실무적인 문제를 해결해본 경험은 매우 뜻깊었고, 팀원들과 함께 안정적이고 확장 가능한 소프트웨어를 구축하기 위해 열띤 토의를 나눈 시간도 값진 배움의 기회였다.</p>
<h3 id="패스트-캠퍼스-교육기관-후기">패스트 캠퍼스 교육기관 후기</h3>
<p><img src="https://velog.velcdn.com/images/jay_be/post/df0ae2ea-1bb6-47ee-b57d-d4d018f496a7/image.png" alt=""></p>
<p>수많은 교육기관 중에서도, 패스트캠퍼스 재직자 과정을 추천하는 이유 중 하나는 열정적인 매니저님들의 세심한 케어다. 적극적인 지원과 꼼꼼한 관리 덕분에 학습에 더욱 몰입할 수 있었고, 끝까지 수료할 수 있게 많은 도움을 주셨다. 
<strong>&#39;이렇게 까지 적극적으로 지원해주시나..?&#39;</strong> 싶을 정도로 도움을 많이 주셨다. 수료 후에도 네트워킹 그룹을 만들어 주시고, 운영해주고 계신다. 패스트캠퍼스가 교육에 진심이구나.. 라고 느끼는 부분이다.</p>
<ul>
<li>주기적인 외부 강사 초청</li>
<li>매주 만족도 조사</li>
<li>네트워킹 주최</li>
<li>AWS 서버 비용 지원</li>
<li>다양한 이벤트</li>
<li>패스트캠퍼스 강의 제공</li>
<li>멘탈 케어 및 응원</li>
</ul>
<h2 id="결론">결론</h2>
<p>커리어에 자신이 없거나 고민이 많은 주니어 개발자들에게 패스트캠퍼스 재직자 과정을 강력히 추천한다. 나 역시 이 과정을 통해 많은 것을 배웠고, 그 가치를 믿기에 이미 주변 주니어 개발자들에게도 적극적으로 추천하고 있다.</p>
<p>나는 1기 수료자이지만, 패스트캠퍼스 측에서는 2기, 3기, 나아가 100기까지 수료자들이 서로 교류하고 네트워킹할 수 있는 환경을 만들겠다는 목표를 가지고 있다고 한다. 만약 이 글을 읽고 패스트캠퍼스 재직자 과정 참여를 결심한다면, 언젠가 함께 즐거운 개발 이야기를 나눌 수 있기를 기대한다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[gRPC ]]></title>
            <link>https://velog.io/@jay_be/gRPC</link>
            <guid>https://velog.io/@jay_be/gRPC</guid>
            <pubDate>Fri, 03 Jan 2025 06:18:51 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>해당 글은 gRPC - 알고 나면 쉬움 - <a href="https://www.youtube.com/watch?v=uwrR5e5_FH8">https://www.youtube.com/watch?v=uwrR5e5_FH8</a> 를 보고 정리한 글 입니다.</p>
</blockquote>
<h2 id="grpc">gRPC</h2>
<ul>
<li>구글에서 만든 RPC 프레임워크.</li>
</ul>
<h2 id="rpc">RPC</h2>
<ul>
<li>remote procedure call의 약자.</li>
<li>다른 컴퓨터에 있는 어떤 기능을 자기 기능인 것처럼 실행할 수 있도록 하는 프로토콜.</li>
</ul>
<h2 id="grpc-1">gRPC</h2>
<ul>
<li>여러 RPC 프레임워크 중 하나.</li>
<li>높은 성능.</li>
<li>강력한 개발자 커뮤니티.</li>
<li>풍부한 도구, 라이브러리.</li>
<li>최근에 가장 널리 사용되는 RPC.</li>
</ul>
<h3 id="rest-api-vs-grpc">REST API vs gRPC</h3>
<p>예시)
도서 대여 서비스의 서버에서 마이크로서비스로 도서 관리, 대여 및 반납, 리뷰 관리, 검색, 알림 등으로 구성됩니다.
이런 마이크로서비스들 사이에서도 요청과 응답을 통한 지속적인 소통이 필요합니다. REST API 에서는 보통 JSON 데이터로 통신합니다.</p>
<pre><code class="language-java">{
    &quot;available&quot;: true,
    &quot;location&quot;: &quot;Main Library, Aisle 4&quot;,
    &quot;rentalPrice&quot;: 2.99,
    &quot;rentalDays&quot;: 14,
    &quot;additionInfo&quot;: &quot;Last Copy available!&quot;
}</code></pre>
<p>JSON은 SOAP에서 사용하는 XML 보다는 훨씬 간결하지만 같은 종류 요청이 자주 반복된다면 key 값이 반복되어 중복에 대한 낭비가 생기게 됩니다.</p>
<pre><code class="language-java">{
    &quot;available&quot;: true,
    &quot;location&quot;: &quot;Main Library, Aisle 4&quot;,
    &quot;rentalPrice&quot;: 2.99,
    &quot;rentalDays&quot;: 14,
    &quot;additionInfo&quot;: &quot;Last Copy available!&quot;
},
{
    &quot;available&quot;: true,
    &quot;location&quot;: &quot;Main Library, Aisle 4&quot;,
    &quot;rentalPrice&quot;: 2.99,
    &quot;rentalDays&quot;: 14,
    &quot;additionInfo&quot;: &quot;Last Copy available!&quot;
},
{
    &quot;available&quot;: true,
    &quot;location&quot;: &quot;Main Library, Aisle 4&quot;,
    &quot;rentalPrice&quot;: 2.99,
    &quot;rentalDays&quot;: 14,
    &quot;additionInfo&quot;: &quot;Last Copy available!&quot;
},
{
    &quot;available&quot;: true,
    &quot;location&quot;: &quot;Main Library, Aisle 4&quot;,
    &quot;rentalPrice&quot;: 2.99,
    &quot;rentalDays&quot;: 14,
    &quot;additionInfo&quot;: &quot;Last Copy available!&quot;
},</code></pre>
<p>하지만 JSON에서 key를 없앤다면 해당 값이 무엇을 나타내느지 구분하기 어렵습니다.</p>
<p>gPRC는 이를 위해 protocol buffer라는 것을 사용합니다.</p>
<h4 id="proto">*.proto</h4>
<pre><code>
syntax = &quot;proto3&quot;;

pacage loan;

service LoanService {
    rpc CheckBookAvaliability(BookRequest) returns (BookResponse) {}
}

message BookRequest {
    string book_id = 1;
    int32 quantity = 2;
}

message BookResponse {
    bool available = 1;
    string location = 2;
    float rentalPrice = 3;
    int32 rentalDays = 4;
    string additionInfo = 5;
}</code></pre><p>.proto 파일은 각 메시지가 어떻게 작성될지에 관한 약속으로, 서버와 클라이언트 모두에게 공유됩니다. 이를 기준으로 클라이언트와 서버는 서로 주고받는 메시지를 작성하고 해독합니다.</p>
<p>이처럼 프로토콜 버퍼를 사용해서 합의된 사양을 기준으로, 언어와 환경이 다른 주체들끼리도 매끄럽게 소통할 수 있습니다.</p>
<p>프로토콜 버퍼는 단지 메시지의 키만 간소화하는게 아니라 이를 바이너리 형태로 직렬화해서 전송합니다. 이는 JSON과 같은 텍스트 기반 방식에 비해 용량이 작으므로 훨씬 빠르게 전송할 수 있습니다. 때문에 gRPC는 REST API와 같은 다른 방식들에 비해 효율적으로 요청과 응답을 주고 받을 수 있습니다.</p>
<h3 id="http2-기반">HTTP/2 기반</h3>
<p>gRPC의 또 다른 강점은 HTTP/2 기반이라는 점입니다.
RESTful API에서 주로 사용되는 HTTP/1.1는 편지로 요청과 응답을 주고 받는 것과 같습니다. </p>
<p>클라이언트가 편지들을 적어보내면 서버는 이를 하나씩 받아서 순서대로 답장을 돌려보내는 식으로만 소통이 이루어집니다. 만약 먼저 보낸 편지의 처리가 늦어지면, 그 뒤의 편지들은 앞의 편지가 처리될 때까지 기다리게 됩니다.</p>
<p>반면, HTTP/2는 전화로 대화를 주고 받는 것과 같습니다. 한번 통화가 연결되면 서버와 클라이언트는 양방향에서 동시에 대화를 주고 받을 수 있습니다. 여러 메시지를 한 번에 보낼 수도 있고 이를 순서와 상관없이 빠르게 처리해서 답을 보낼 수도 있습니다.
서로 동시에 메시지를 보낼 수도 있고, 서버가 클라이언트에게 능동적으로 메시지를 보낼 수도 있습니다.
gRPC에서 주고 받아지는 바이너리 데이터도 HTTP/2에서 효율적으로 처리됩니다.</p>
<h3 id="tls를-통해-데이터-암호화">TLS를 통해 데이터 암호화</h3>
<p>다른 누군가가 메시지를 훔쳐보거나, 내용을 변경하거나 클라이언트 또는 서버를 사칭하지 못하도록 합니다. TLS를 통해 중요한 데이터가 오고 가는 곳에서 gRPC는 안전한 통신을 보장합니다.</p>
<p>아래 링크에서 grpc에 대해 더 알아볼 수 있습니다.
<a href="https://grpc.io">https://grpc.io</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[기본 지식] 데드락, 트랜잭션 격리 레벨, 트랜잭션 락]]></title>
            <link>https://velog.io/@jay_be/%EA%B8%B0%EB%B3%B8-%EC%A7%80%EC%8B%9D-%EB%8D%B0%EB%93%9C%EB%9D%BD-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EA%B2%A9%EB%A6%AC-%EB%A0%88%EB%B2%A8-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EB%9D%BD</link>
            <guid>https://velog.io/@jay_be/%EA%B8%B0%EB%B3%B8-%EC%A7%80%EC%8B%9D-%EB%8D%B0%EB%93%9C%EB%9D%BD-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EA%B2%A9%EB%A6%AC-%EB%A0%88%EB%B2%A8-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EB%9D%BD</guid>
            <pubDate>Mon, 09 Dec 2024 12:19:13 GMT</pubDate>
            <description><![CDATA[<h2 id="1-db-데드락-deadlock">1. DB 데드락 (Deadlock)</h2>
<p>데드락이란 두 개 이상의 트랜잭션이 서로 상대방이 갖고 있는 자원을 기다리며 무한히 대기하는 상황을 말한다.</p>
<h3 id="예시">예시</h3>
<ul>
<li>트랜잭션 A는 테이블 1에 대한 락을 얻고, 동시에 테이블 2에 대한 락을 요청한다.</li>
<li>트랜잭션 B는 테이블 2에 대한 락을 얻고, 동시에 테이블 1에 대한 락을 요청한다.</li>
</ul>
<p>이 경우 트랜잭션 A는 트랜잭션 B가 가진 테이블 2 락을 기다리고, 트랜잭션 B는 트랜잭션 A가 가진 테이블 1 락을 가지게 된다. 결국 두 트랜잭션은 서로 상대방의 락을 기다리면서 진행되지 않으며, 데드락 상태에 빠진다.</p>
<h3 id="해결-방법">해결 방법:</h3>
<ul>
<li>타임아웃 설정 : 일정 시간이 지나면 자동으로 트랜잭션을 취소하고, 해당 트랜잭션을 롤백하여 데드락을 해결할 수 있다.</li>
<li>데드락 탐지 : 데이터베이스는 주기적으로 데드락 상태를 검사하고, 이를 감지하면 트랜잭션을 강제로 롤백한다.</li>
<li>트랜잭션 설계 개선 : 락을 순차적으로 획득하는 방식으로 트랜잭션을 설계하여 데드락을 방지할 수 있다.</li>
</ul>
<h2 id="2-트랜잭션-격리-레벨-transaction-isolation-levels">2. 트랜잭션 격리 레벨 (Transaction Isolation Levels)</h2>
<p>트랜잭션 격리 레벨은 데이터베이스에서 여러 트랜잭션이 동시에 실행될 때, 한 트랜잭션 다른 트랜잭션에 의해 영향을 받지 않도록 보장하는 정도를 설정하는 것이다. 트랜잭션 격리 수준은 크게 4가지로 나뉜다.</p>
<h3 id="1-read-uncommitted-가장-낮은-격리-레벨">1. READ UNCOMMITTED (가장 낮은 격리 레벨)</h3>
<ul>
<li>다른 트랜잭션이 아직 커밋하지 않은 데이터를 읽을 수 있다.</li>
<li>문제점 : &quot;더티 리드&quot;가 발생할 수 있다. 이는 다른 트랜잭션이 아직 커밋하지 않은 데이터를 읽는 상황으로, 데이터가 잘못된 상태일 수 있다.</li>
</ul>
<h3 id="2-read-committed">2. READ COMMITTED</h3>
<ul>
<li>다른 트랜잭션이 커밋한 데이터만 읽을 수 있다.</li>
<li>문제점 : &quot;비반복적 읽기&quot;가 발생할 수 있다. 한 트랜잭션 내에서 동일한 쿼리를 두 번 실행했을 때 결과가 달라질 수 있다. 예를 들어, 다른 트랜잭션이 데이터를 수정하고 커밋했기 때문에, 두 번째 조회에서 다른 값이 반환될 수 있다.</li>
</ul>
<h3 id="3-repeatable-read">3. REPEATABLE READ</h3>
<ul>
<li>한 트랜잭션 내에서 읽은 데이터는 다른 트랜잭션이 수정할 수 없다. 즉, 같은 데이터를 여러 번 읽을 때 같은 결과가 보장된다.</li>
<li>문제점 : &quot;팬텀 리드&quot;가 발생할 수 있다. 트랜잭션이 데이터를 읽을 때, 다른 트랜잭션이 새로운 데이터를 추가하는 경우, 첫번째 트랜잭션에서 두번째 조회 시 새로운 데이터가 포함될 수 있다.</li>
</ul>
<h3 id="4-serializable-가장-높은-격리-레벨">4. SERIALIZABLE (가장 높은 격리 레벨)</h3>
<ul>
<li>모든 트랜잭션이 직렬화된 방식으로 처리되어, 하나의 트랜잭션이 완료될 때까지 다른 트랜잭션이 실행되지 않는다.</li>
<li>장점 : 완벽하게 동시성 문제가 해결된다.</li>
<li>단점 : 성능이 저하된다. 동시에 실행되는 트랜잭션이 많을 경우 처리 속도가 느려질 수 있다.</li>
</ul>
<h2 id="3-트랜잭션-락-transaction-locks">3. 트랜잭션 락 (Transaction Locks)</h2>
<p>트랜잭션 락은 데이터베이스에서 여러 트랜잭션이 동시에 같은 데이터를 변경하지 않도록 보호하는 매커니즘이다. 락은 데이터가 동시성을 처리할 수 있게 하며, 동시에 여러 트랜잭션이 동일한 데이터를 수정하려고 할 때 충돌을 방지한다.</p>
<h3 id="락-종류">락 종류</h3>
<h3 id="공유-락-shared-lock">공유 락 (Shared Lock)</h3>
<ul>
<li>데이터를 읽을 수는 있지만, 다른 트랜잭션이 해당 데이터를 수정할 수 없도록 한다.여러 트랜잭션이 데이터를 동시에 읽을 수는 있지만, 수정은 불가능하다.</li>
</ul>
<h3 id="배타적-락-exclusive-lock">배타적 락 (Exclusive Lock)</h3>
<ul>
<li>데이터를 읽고 수정할 수 있으며, 다른 트랜잭션은 이 데이터를 읽거나 수정할 수 없다. 트랜잭션이 완료될 때까지 해당 자원에 대한 모든 접근이 차단된다.</li>
</ul>
<h3 id="낙관적-락-optimistic-locking">낙관적 락 (Optimistic Locking)</h3>
<ul>
<li>데이터를 읽은 후, 트랜잭션을 커밋할 때 다른 트랜잭션이 데이터를 수정하지 않았는지 확인한다. 만약 수정이 있었다면, 충돌을 해결하고 다시 시도할 수 있다.</li>
<li>주로 version 컬럼을 사용하여 트랜잭션 충돌을 관리한다.</li>
</ul>
<h2 id="정리">정리</h2>
<ul>
<li>&quot;데드락&quot;은 두 트랜잭션이 서로 상대방의 락을 기다리며 무한히 대기하는 상태를 의미한다. 이를 해결하려면 타임아웃 설정이나 데드락 탐지 기능을 활용해야 한다.</li>
<li>&quot;트랜잭션 격리 레벨&quot;은 여러 트랜잭션이 동시에 실행될 때 데이터 일관성을 유지하는 방법을 정의한다. 높은 격리 레벨일수록 성능이 저하될 수 있지만, 더 강한 일관성을 제공한다.</li>
<li>&quot;트랜잭션 락&quot;은 트랜잭션 간 충돌을 방지하기 위한 방법이다. 락을 적절히 관리하지 않으면 성능 문제가 발생할 수 있다.</li>
</ul>
<blockquote>
<p>보통의 트랜잭션 격리 레벨은 &quot;READ COMMITTED&quot;로 설정하는 경우가 많다. 이는 대부분의 데이터베이스에서 기본값으로 설정되는 격리 레벨이기도 하며, 일반적으로 적절한 성능과 데이터 일관성의 균형을 맞추기 때문이다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[패스트캠퍼스 이너서클 수료 후기 [1기 백엔드]]]></title>
            <link>https://velog.io/@jay_be/%ED%8C%A8%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%8D%BC%EC%8A%A4-%EC%9D%B4%EB%84%88%EC%84%9C%ED%81%B4-%ED%9B%84%EA%B8%B0-1%EA%B8%B0-%EB%B0%B1%EC%97%94%EB%93%9C</link>
            <guid>https://velog.io/@jay_be/%ED%8C%A8%EC%8A%A4%ED%8A%B8%EC%BA%A0%ED%8D%BC%EC%8A%A4-%EC%9D%B4%EB%84%88%EC%84%9C%ED%81%B4-%ED%9B%84%EA%B8%B0-1%EA%B8%B0-%EB%B0%B1%EC%97%94%EB%93%9C</guid>
            <pubDate>Mon, 02 Dec 2024 11:05:38 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>INNER CIRCLE: 풀스택 개발 Course 1기 : 2024.07.20 ~ 2024.11.09(256시간)</p>
</blockquote>
<p>회사일로 바빠 이제야 후기를 작성한다. 이번 글은 수료 후의 느낀점을 주관적으로 작성한 글이다.</p>
<p><img src="https://velog.velcdn.com/images/jay_be/post/ddab729b-02a8-4e98-9d35-adfe09a78a14/image.png" alt="">
일단 전우애가 생긴 이커머스팀 팀원들과 사진 투척. 군대 동기급의 끈끈한 정이 생겼다. 수료 후에도 꾸준히 연락하면서 잘 지내고 있다. &quot;이너서클&quot;의 의도답게 계속해서 서로에게 긍정적인 영향을 줄 수 있기를 기대하고 있다.</p>
<p><img src="https://velog.velcdn.com/images/jay_be/post/ae4c60c5-a992-4c92-80b2-f05f310ab2bf/image.png" alt=""></p>
<p>[패스트캠퍼스 이너서클 과정]을 듣기 위해서는 자기소개서와 사전 역량 검사를 응시하고 리더님들이 직접 뽑아 교육 과정에 참여할 수 있게 된다. 이런 것들을 함으로써 성장하려고 하는 의지를 가진 개발자들과 함께 교육 과정에 참여할 수 있게 되었던 것 같다. </p>
<p><img src="https://velog.velcdn.com/images/jay_be/post/15422d48-6254-4226-9f27-48ba879f19b2/image.png" alt="">
그럼에도 불구하고 평일 월, 화, 목 3시간, 토요일 9시간을 필수로 참여해야한다는 것은 쉽지는 않은 것 같다. 그렇다고 엄청난 각오를 해야하는 것은 아닌 것 같고, 그저 습관처럼 퇴근 후에 자연스럽게(?) 컴퓨터를 키고 코딩을 하면 된다. 필자는 수료 후에 강제성이 없어지니 살짝 루즈해진 것 같아 회사 근처 독서실을 등록하고 다니고 있다. 좋은 개발자가 되기 위해서 그냥 매일매일 조금씩 무언가를 하려고 한다.</p>
<p><img src="https://velog.velcdn.com/images/jay_be/post/19bc5397-a318-40e8-80d5-05f918636e9b/image.png" alt="">
뛰어난 개발자와 얘기할 수 있는 기회가 있다는 것이 해당 과정의 큰 장점인 것 같다. 많은 경험과 깊은 지식을 바탕으로 프로젝트나 개발자로써의 고민을 들어주시고 그에 대한 방향을 잡아주셨다.</p>
<p><img src="https://velog.velcdn.com/images/jay_be/post/5edf3ea2-7f4b-4689-bc7d-0e7c05fa8d0e/image.png" alt="">
팀 프로젝트를 진행하게 되면 도메인에 따라 팀마다 멘토님 매칭된다. 이커머스 팀을 맡았던 최00 멘토님은 현재 이커머스 도메인에서 재직중이신 고수 개발자셨다. 
이커머스에 대한 도메인 지식도 얻을 수 있었고, 막히는 부분에 대해서 조언도 주셨고 코드 리뷰도 해주셨다.</p>
<p><img src="https://velog.velcdn.com/images/jay_be/post/afd232e8-a94b-4113-8b76-92aee3abc458/image.png" alt=""></p>
<h3 id="q-그래서-패스트캠퍼스-이너서클-과정을-추천하는가-">Q. 그래서 패스트캠퍼스 이너서클 과정을 추천하는가 ?</h3>
<p>A. 추천한다. 단, 성장하고 싶은 개발자만</p>
<h3 id="패스트캠퍼스-이너서클-장점">패스트캠퍼스 이너서클 장점</h3>
<ol>
<li>강제로 그 시간에 코드 한 줄 더 치게 된다.</li>
<li>좋은 동료 개발자를 얻게 된다.</li>
<li>좋은 멘토 개발자를 알게 된다.</li>
<li>팀 프로젝트를 진행할 수 있다.</li>
</ol>
<p>크게 4가지로 정리해보았다.</p>
<p>주변에 성장을 하고 싶은 개발자가 있다면 필자는 해당 과정을 추천해줄 것이다. (이미 많이 했다)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MySQL vs PostgreSQL]]></title>
            <link>https://velog.io/@jay_be/MySQL-vs-PostgreSQL</link>
            <guid>https://velog.io/@jay_be/MySQL-vs-PostgreSQL</guid>
            <pubDate>Sat, 12 Oct 2024 07:50:50 GMT</pubDate>
            <description><![CDATA[<p>회사에서 신규 서비스를 만들기 위해서 기술 스택을 정하는 기간이다. 나는 데이터베이스를 조사하는 역할을 맡아 아래와 같이 정리하였고 PostgreSQL을 선택 후 현재 hammerDB를 사용하여 데이터베이스 부하테스트 진행중에 있다.</p>
<p>해당 내용은 AWS의 문서를 작성한 내용이다.
<a href="https://aws.amazon.com/ko/compare/the-difference-between-mysql-vs-postgresql/">AWS - MySQL과 PostgreSQL의 차이점은 무엇인가요?</a></p>
<h3 id="mysql">MySQL</h3>
<p>데이터를 행과 열이 있는 테이블로 저장할 수 있는 관계형 데이터베이스 관리 시스템이다. 많은 웹 애플리케이션에서 널리 사용된다.</p>
<h3 id="postgresql">PostgreSQL</h3>
<p>MySQL보다 더 많은 기능을 제공하는 객체 관계형 데이터베이스 관리 시스템이다. 데이터 유형, 확장성, 동시성 및 데이터 무결성에 있어 유연성이 더 뛰어나다.</p>
<h3 id="postgresql과-mysql의-유사점">PostgreSQL과 MySQL의 유사점?</h3>
<p>PostgreSQL과 MySQL은 모두 관계형 데이터베이스 관리 시스템이다. 공통 열 값을 통해 서로 관련된 테이블에 데이터를 저장한다.</p>
<p>둘 다 구조화된 쿼리 언어(SQL)를 인터페이스로 사용하여 데이터를 읽고 편집 가능</p>
<p>둘 다 오픈 소스이며, 강력한 개발자 커뮤니티 지원 제공</p>
<p>두 제품 모두 데이터 백업, 복제 및 액세스 제어 기능이 내장되어 있음</p>
<h3 id="주요-차이점">주요 차이점</h3>
<h4 id="acid-규정-준수">ACID 규정 준수</h4>
<p>원자성, 일관성, 격리성, 지속성(ACID)는 예상치 못한 오류가 발생한 후에도 데이터베이스를 유효한 상태로 유지하는 데이터베이스 속성이다.</p>
<p>MySQL은 InnoDB 및 NDB 클러스터 스토리지 엔진 또는 소프트웨어 모듈과 함께 사용하는 경우에만 ACID 규정 준수를 제공한다. PostgreSQL은 모든 구성에서 ACID와 완벽하게 호환된다.</p>
<h4 id="동시성-제어">동시성 제어</h4>
<p>다중 버전 동시성 제어(MVCC)를 사용하면 여러 사용자가 데이터 무결성을 손상시키지 않고 동일한 데이터를 동시에 읽고 수정할 수 있다.</p>
<p>MySQL은 MVCC를 제공하지 않지만 PostgreSQL은 이 기능을 제공한다.</p>
<h4 id="인덱스">인덱스</h4>
<p>데이터베이스는 인덱스를 사용하여 데이터를 더 빠르게 검색한다. 자주 액세스하는 데이터를 다른 데이터와 다르게 정렬 및 저장하도록 데이터베이스 관리 시스템을 구성하여 자주 액세스하는 데이터를 인덱싱할 수 있다.</p>
<p>MySQL은 계층적으로 인덱싱된 데이터를 저장하는 B-tree, R-tree 인덱싱을 지원한다. PostgreSQL 인덱스 유형에는 트리, 표현식, 부분 인덱스 및 해시 인덱스가 포함된다. 크기를 확장할 때 데이터베이스 성능 요구 사항을 세밀하게 조정할 수 있는 더 많은 옵션이 있다.</p>
<h4 id="데이터-유형">데이터 유형</h4>
<p>MySQL은 순수 관계현 데이터베이스이다. 반면 PostgreSQL은 객체 관계형 데이터베이스이다. 즉, PostgreSQL에서는 데이터를 속성을 가진 객체로 저장할 수 있다. PostgreSQL을 사용하는 것은 데이터베이스 개발자에게 더 작관적이다. PostgreSQL은 배열 및 XML과 같은 다른 추가 데이터 유형도 지원한다.</p>
<h4 id="view">View</h4>
<p>View는 데이터베이스 시스템이 여러 테이블에서 관련 테이블을 가져와서 만드는 데이터 하위 집합이다.</p>
<p>MySQL은 View를 지원하고, PostgreSQL은 고급 보기 옵션을 제공한다. ex) 특정 기간 동안 모든 주문의 합계 금액과 같은 일부 값을 미리 계산하여 구체화된 뷰를 생성할 수 있다. 구체화된 뷰는 복잡한 쿼리의 데이터베이스 성능을 향상시킨다.</p>
<h4 id="저장-프로시저">저장 프로시저</h4>
<p>저장 프로시저는 미리 작성하고 저장할 수 있는 구조화된 쿼리 언어 (SQL) 쿼리 또는 코드 명령문이다. 동일한 코드를 반복해서 재사용할 수 있으므로 데이터베이스 관리 작업에 효율적이다.</p>
<p>MySQL과 PostgreSQL 모두 저장 프로시저를 지원하지만 PostgreSQL을 사용하면 SQL 이외의 언어로 작성된 프로시저를 호출할 수 있다.</p>
<h4 id="트리거">트리거</h4>
<p>트리거는 데이터베이스 관리 시스템에서 관련 이벤트가 발생할 때 자동으로 실행되는 저장 프로시저이다.</p>
<p>MySQL 데이터베이스에서는 SQL Insert, Update, Delete 문에 Alter, Before 트리거만 사용할 수 있다. 즉, 사용자가 데이터를 수정하거나 전이나 후에 프로시저가 자동으로 실행된다. 반대로 PostgreSQL은 Insted of 트리거를 지원하므로 함수를 사용하여 복잡한 SQL문을 실행할 수 있다.</p>
<h4 id="postgresql과-mysql-중-하나를-선택하는-방법">PostgreSQL과 MySQL 중 하나를 선택하는 방법</h4>
<p>두 관계형 데이터베이스는 모두 대부분의 사용 사례에 적합하다.</p>
<h4 id="애플리케이션-범위">애플리케이션 범위</h4>
<p>PostgreSQL은 쓰기 작업이 빈번하고 쿼리가 복잡한 엔터프라이즈급 애플리케이션에 더 적합하다.</p>
<p>사용자 수가 적은 내부 애플리케이션을 만들거나, 읽기 횟수가 많고 데이터 업데이트가 자주 이루어지지 않는 정보 스토리지 엔진을 만들고 싶다면 MySQL 이 적합하다.</p>
<h4 id="데이터베이스-개발-경험">데이터베이스 개발 경험</h4>
<p>MySQL은 초보자에게 적합하며 학습 기간이 짧다. 반면 Postgresql이 훨씬 더 어려울 수 있다. 일반적으로 복잡한 인프라 설정 및 문제 해결 경험이 필요하다.</p>
<h4 id="성능-요구-사항">성능 요구 사항</h4>
<p>애플리케이션에 잦은 데이터 업데이트가 필요한 경우 PostgreSQL이 더 나은 선택이다. 그러나 데이터베이스를 자주 읽어야 하는 경우에는 MySQL을 사용하는 것이 좋다.</p>
<h4 id="쓰기-성능">쓰기 성능</h4>
<p>MySQL은 쓰기 잠금을 사용하여 실제 동시성을 구현한다. ex) 한 사용자가 테이블을 편집하는 경우 다른 사용자가 테이블을 변경하려면 작업이 완료될 때 까지 기다려야 할 수 있다.</p>
<p>그러나 PostgreSQL에는 읽기-쓰기 잠금이 없는 다중 버전 동시성 제어 (MVCC) 지원이 내장되어 있다. 따라서 쓰기 작업이 빈번하고 동시에 수행되는 경우 PostgreSQL 데이터베이스가 더 잘 작동한다.</p>
<h4 id="읽기-성능">읽기 성능</h4>
<p>PostgreSQL은 데이터베이스에 연결된 모든 사용자에 대해 상당한 메모리 할당량(약 10MB)을 포함하는 새로운 시스템 프로세스를 생성한다. 여러 사용자를 위해 확장하려면 메모리 집약적 리소스가 필요하다.</p>
<p>반면 MySQL은 여러 사용자를 위해 단일 프로세스를 사용한다. 따라서 MySQL 데이터베이스는 주로 데이터를 읽고 사용자에게 표시하는 애플리케이션에서 PostgreSQL보다 성능이 뛰어나다.</p>
<p><img src="https://velog.velcdn.com/images/jay_be/post/8d4ae56b-e4ab-4691-bf91-54cfb1d42180/image.png" alt=""></p>
<h3 id="무조건-postgresql이-더-좋을까-">무조건 PostgreSQL이 더 좋을까 ?</h3>
<p>gpt의 답변</p>
<p>PostgreSQL은 강력한 기능을 제공하지만, 몇 가지 단점도 있습니다. 아래는 PostgreSQL의 주요 단점들입니다:</p>
<h4 id="복잡한-설정과-유지보수">복잡한 설정과 유지보수</h4>
<p>PostgreSQL은 고급 기능을 많이 제공하기 때문에 설정과 유지 관리가 MySQL보다 더 복잡할 수 있습니다. 이를 처음 다루는 사용자에게는 학습 곡선이 가파를 수 있습니다.</p>
<h4 id="메모리-사용량">메모리 사용량</h4>
<p>PostgreSQL은 데이터를 효율적으로 처리하기 위해 더 많은 메모리를 사용할 수 있는데, 이는 고사양의 서버가 필요할 수 있다는 것을 의미합니다. 따라서 소규모 서버에서는 MySQL이 더 적합할 수도 있습니다.</p>
<h4 id="쓰기-성능-1">쓰기 성능</h4>
<p>PostgreSQL은 쓰기 성능에서 MySQL보다 상대적으로 느릴 수 있습니다. 특히 트랜잭션이 많이 발생하는 시스템에서는 성능에 차이가 날 수 있습니다.</p>
<h4 id="mysql에-비해-덜-친숙한-사용자층">MySQL에 비해 덜 친숙한 사용자층</h4>
<p>MySQL은 웹 호스팅, 워드프레스 같은 대중적인 환경에서 많이 쓰이기 때문에 MySQL 관련 정보와 커뮤니티 지원이 PostgreSQL에 비해 더 활발할 수 있습니다.</p>
<h4 id="윈도우에서의-성능-이슈">윈도우에서의 성능 이슈</h4>
<p>PostgreSQL은 리눅스 환경에서 매우 안정적이지만, 윈도우 운영체제에서 사용할 때는 성능이나 안정성 면에서 문제가 발생할 수 있다는 사용자 보고가 있습니다.</p>
<h4 id="대규모-트래픽-처리-성능">대규모 트래픽 처리 성능</h4>
<p>PostgreSQL이 기능 면에서 우수하지만, 아주 대규모 트래픽 환경에서는 MySQL이나 다른 DB 시스템에 비해 쓰기 성능에서 약간 뒤처질 수 있습니다.</p>
<p>이런 단점들이 특정 상황에서 중요하게 작용할 수 있지만, PostgreSQL은 여전히 기능성, 확장성 측면에서 매우 강력한 선택입니다.</p>
<h3 id="결론---개인적-의견">결론 - 개인적 의견</h3>
<p>DB를 어떤 용도로 쓸 것인지 확인 필요</p>
<ul>
<li>단순 CRUD가 많다 → MySQL</li>
<li>복잡한 쿼리가 많다 → PosgreSQL</li>
<li>PostgreSQL을 사용하므로써 장비의 성능을 올려야 할 수 있음 → 비용 증가</li>
<li>PostgreSQL를 사용한다면 학습 필요</li>
<li>그럼에도 불구하고 PostgreSQL는 매력적이다!</li>
</ul>
<h3 id="용어-정리">용어 정리</h3>
<h4 id="원자성-atomicity">원자성 (Atomicity)</h4>
<p>트랜잭션은 분해가 불가능한 최소의 단위인 하나의 원자처럼 동작한다는 의미. 트랜잭션 내의 모든 연산은 반드시 한꺼번에 완전하게 정체가 정상적으로 수행이 완료 되거나 아니면 어떠한 연산도 수행되지 않은 all or nothing</p>
<h4 id="일관성-consistency">일관성 (Consistency)</h4>
<p>데이터가 항상 일관된 규칙을 따라야 한다는 의미. 트랜잭션이 끝나면, 데이터는 항상 정해진 규칙에 맞게 변해야 한다. ex) 은행 이체 시, 계좌의 잔액이 음수가 되면 안된다. 규칙에 맞게 데이터가 유지되는 것</p>
<h4 id="고립성-isolation">고립성 (Isolation)</h4>
<p>여러 트랜잭션이 동시에 실행돼도 서로에게 영향을 주면 안된다는 개념이다. ex) 두 사람이 동시에 같은 계좌에서 돈을 인출할 때, 한 사람의 트랜잭션이 끝나기 전에 다른 사람의 트랜잭션이 영향을 받지 않게 해야 한다. 서로 독립적으로 동작하도록 하는 것이다.</p>
<h4 id="지속성-durability">지속성 (Durability)</h4>
<p>트랜잭션이 완료된 후에 시스템이 멈추거나 문제가 생겨도 그 결과는 계속 유지돼야 한다. ex) 은행에서 돈을 송금한 후에 시스템이 갑자기 멈춰도, 그 돈은 여전히 정상적으로 이체된 상태로 남아 있어야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SOLID를 나만의 언어로 표현해보자!
- 인프런 워밍업 클럽 2기]]></title>
            <link>https://velog.io/@jay_be/SOLID%EB%A5%BC-%EB%82%98%EB%A7%8C%EC%9D%98-%EC%96%B8%EC%96%B4%EB%A1%9C-%ED%91%9C%ED%98%84%ED%95%B4%EB%B3%B4%EC%9E%90-%EC%9D%B8%ED%94%84%EB%9F%B0-%EC%9B%8C%EB%B0%8D%EC%97%85-%ED%81%B4%EB%9F%BD-2%EA%B8%B0</link>
            <guid>https://velog.io/@jay_be/SOLID%EB%A5%BC-%EB%82%98%EB%A7%8C%EC%9D%98-%EC%96%B8%EC%96%B4%EB%A1%9C-%ED%91%9C%ED%98%84%ED%95%B4%EB%B3%B4%EC%9E%90-%EC%9D%B8%ED%94%84%EB%9F%B0-%EC%9B%8C%EB%B0%8D%EC%97%85-%ED%81%B4%EB%9F%BD-2%EA%B8%B0</guid>
            <pubDate>Thu, 03 Oct 2024 14:54:05 GMT</pubDate>
            <description><![CDATA[<h2 id="solid가-뭔데-">SOLID가 뭔데 ?</h2>
<p>객체 지향 설계 5원칙이다. </p>
<ul>
<li>SRP : 단일 책임 원칙</li>
<li>OCP : 개방 폐쇄 원칙</li>
<li>LSP : 리스코프 치환 원칙</li>
<li>ISP : 인터페이스 분리 원칙</li>
<li>DIP : 의존 역전 원칙</li>
</ul>
<p>정말 정말 쉽게 이 5원칙을 설명하자면 유지보수 하기 쉽고, 추가 기능 개발 요청이 왔을 때 최대한 개발 시간을 줄이고 사이드 이펙트를 줄일 수 있게 하는 <strong>꿀팁의 모음</strong>이라고 생각을 한다.</p>
<h3 id="srp---단일-책임-원칙">SRP - 단일 책임 원칙</h3>
<blockquote>
<p>어떤 클래스를 변경해야 하는 이유는 오직 하나 뿐이여야 한다. - 로버트 C. 마틴</p>
</blockquote>
<p>하나의 클래스는 하나의 책임을 가져야 한다. 예를 들어 학교에 선생님을 클래스로 표현해보겠다. (정말 간단하게)</p>
<pre><code class="language-java">public class Teacher {
    private String name; // 이름
    private String subject; // 과목
}</code></pre>
<p>근데 이 선생님이 퇴근 후에 은행에 가게 되었다. 그러면 이 클래스는 아래와 같이 변한다.</p>
<pre><code class="language-java">public class Teacher {
    private String name;
    private String subject;
    private Money money;
    private String residentRegistrationNumber;
}</code></pre>
<p>자 이제 이 클래스는 두가지의 역할을 가지게 되었다. 선생님과 은행 고객 두 가지이다. </p>
<p>이제 은행에서 은행 고객에게 추가로 어떤 정보를 요청할 때도 해당 클래스가 변경되고, 선생님에게 새로운 룰이 추가가 되어도 해당 클래스가 변경이 된다. 이럴 때는 Teacher class 와 BankCustomer class를 분리하자!!</p>
<pre><code class="language-java">public class Teacher {
    private String name;
    private String subject;
}</code></pre>
<pre><code class="language-java">public class Teacher {
    private String name;
    private Money money;
    private String residentRegistrationNumber;
}</code></pre>
<h3 id="ocp--개방-폐쇄-원칙">OCP : 개방 폐쇄 원칙</h3>
<blockquote>
<p>소프트웨어 엔티티(클래스, 모듈, 함수) 등은 확장에 대해서는 열려 있어야 하지만 변경에 대해서는 닫혀 있어야 한다 - 로버트 C.마틴</p>
</blockquote>
<p>자신의 확장에는 열려 있고, 주변의 변화에는 닫혀 있어야 한다.
자동차의 타이어를 예로 들 수 있다. 자동차 타이어 회사는 몇 개가 있을까?
<img src="https://velog.velcdn.com/images/jay_be/post/0a6e8ab3-6fd0-4f53-9baf-c8bbbd68c05c/image.png" alt="">
간단하게 검색만 해도 이렇게 많이 나온다. 만약 타이어 인터페이스가 없다면 ? 차를 클래스로 표현해 보겠다.</p>
<pre><code class="language-java">public class Car {
    private String name;
    private KumhoTire tire; // 금호 타이어
}</code></pre>
<p>타이어를 잘 사용하다가 다른 회사의 타이어를 사용하고 싶다면 ??
차 클래스의 코드가 변경 되어야 한다. 이것은 OCP를 위반한다.</p>
<pre><code class="language-java">public class Car {
    private String name;
    private KoreanTire tire;
}</code></pre>
<p>어떻게 해결해야 할까? Car 클래스가 바퀴를 인터페이스로 받으면 해결된다.</p>
<pre><code class="language-java">public class Car {
    private String name;
    private Tire tire;
}</code></pre>
<p>이제 Car 클래스는 타이어가 바뀌더라도 변경되지 않는다. OCP를 지키게 된 것이다.</p>
<h3 id="lsp--리스코프-치환-원칙">LSP : 리스코프 치환 원칙</h3>
<blockquote>
<p>서브 타입은 언제나 자신의 기반 타입(base type)으로 교체할 수 있어야 한다. - 로버트 마틴</p>
</blockquote>
<p>객체 지향의 상속 조건</p>
<ul>
<li>하위 클래스 is kind of 상위 클래스 - 하위 분류는 상위 분류의 한 종류이다.</li>
<li>구현 클래스 is able to 인터페이스 - 구현 분류는 인터페이스 할 수 있어야 한다.
위 두 개의 문장대로 구현된 프로그램이라면 이미 LSP를 잘 지키고 있는 것이다.</li>
</ul>
<p>쉽게 말하면 아래와 같다.</p>
<ul>
<li>자식은 부모의 종류 중 하나이다 -&gt; (X)</li>
<li>고래는 포유류의 종류 중 하나이다. -&gt; (O)</li>
</ul>
<p>부모 클래스와 자식 클래스라고 불리는 것은 잘못된 말이다. 
자식이 부모의 역할을 할 수 있어야 하는데 그건 아닌 것 같다..</p>
<p>결국 리스코프 치환 원칙은 객체 지향에서 상속이라는 특성을 올바르게 활용한다면 자연스럽게 얻게 되는 것이다.</p>
<h3 id="isp--인터페이스-분리-원칙">ISP : 인터페이스 분리 원칙</h3>
<blockquote>
<p>클라이언트는 자신이 사용하지 않는 메서드의 의존 관계를 맺으면 안된다.</p>
</blockquote>
<p>쉽게 얘기를 하자면 클래스가 인터페이스를 구현할 때 필요 없는 메서드까지 구현해야 한다면 그것은 ISP를 위반한 것이다. 그럴 때는 인터페이스를 분리하자 !! ISP는 인터페이스의 단일 책임 원칙을 강조한다.
SRP와 ISP를 동시에 만족하지 못할 상황이 발생하기도 한다.
<img src="https://velog.velcdn.com/images/jay_be/post/4671acec-44bd-451e-8e2c-b74fe9b02611/image.png" alt="">
그럴 때는 상황에 맞춰서 결정하도록 하자!</p>
<h3 id="dip--의존-역전-원칙">DIP : 의존 역전 원칙</h3>
<blockquote>
<ul>
<li>고차원 모듈은 저차원 모듈에 의존하면 안된다. 이 두 모듈은 다른 추상화된 것에 의존해야 한다.</li>
</ul>
</blockquote>
<ul>
<li>추상화 된 것은 구체적인 것에 의존하면 안된다. 구체적인 것이 추상화된 것에 의존해야 한다.</li>
</ul>
<blockquote>
<p>&quot;자주 변경되는 클래스에 의존하지 마라. - 로버트 C.마틴&quot;</p>
</blockquote>
<p>OCP에 작성했던 예시와 같게 들 수 있다.
<img src="https://velog.velcdn.com/images/jay_be/post/d219d116-cee5-46cc-82c7-5f0b6d3f7dee/image.png" alt=""></p>
<p>Car는 Tire가 자주 변경된다면 Car는 Tire를 의존하면 안된다. 이 때 DIP를 적용하여 타이어 인터페이스에 의존하게 하여 결합도를 낮출 수 있고, DIP를 지킬 수 있다.</p>
<h3 id="워밍업-day4-미션">워밍업 day4 미션</h3>
<p>아래 코드를 리팩토링 해보시오</p>
<pre><code class="language-java">public boolean validateOrder(Order order) {
    if (order.getItems().size() == 0) {
        log.info(&quot;주문 항목이 없습니다.&quot;);
        return false;
    } else {
        if (order.getTotalPrice() &gt; 0) {
            if (!order.hasCustomerInfo()) {
                log.info(&quot;사용자 정보가 없습니다.&quot;);
                return false;
            } else {
                return true;
            }
        } else if (!(order.getTotalPrice() &gt; 0)) {
            log.info(&quot;올바르지 않은 총 가격입니다.&quot;);
            return false;
        }
    }
    return true;
}</code></pre>
<p>나는 아래와 같이 코드를 리팩토링 해보았다.</p>
<pre><code class="language-java">public void validateOrder(Order order) {

    if (order.hasNotCustomerInfo()) {
        throw new IllegalArgumentException(&quot;사용자 정보가 없습니다.&quot;);
    }

    if (order.isNoItem()) {
        throw new IllegalArgumentException(&quot;주문 항목이 없습니다.&quot;);
    }

    if (order.isTotalPriceisNegative()) {
        throw new IllegalArgumentException(&quot;올바르지 않은 총 가격입니다.&quot;);
    }
}
</code></pre>
<p>validateOrder는 boolean을 굳이 반환이 필요 없다고 생각한다. void로 바꾼 후 문제가 있을 시 익셉션을 던지게 하였다. 또한 order의 상태를 order에게 직접 물어봄으로써 좀 더 가독성이 좋고, 응집도 높은 코드로 변경 하였다.</p>
<p>많은 개발자들이 객체 지향 언어라고 불리는 Java를 사용하지만 내가 정말 객체 지향적으로 잘 사용하고 있냐는 질문에 &#39;예&#39; 라고 대답할 수 있는 개발자는 흔하지 않을 것이라고 생각한다. 나 또한 객체 지향 언어를 제대로 활용하고 있다고 말하지 못한다. 객체 지향 언어를 사용하면서 객체 지향 설계를 못한다는 것은 많이 아쉬운 부분이라고 생각해서, 이번에 인프런 워밍업 클럽 2기 (백엔드 클린 코드, 테스트코드)에 참여하게 되었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[도메인 분석 - SDP]]></title>
            <link>https://velog.io/@jay_be/%EB%8F%84%EB%A9%94%EC%9D%B8-%EB%B6%84%EC%84%9D-SDP</link>
            <guid>https://velog.io/@jay_be/%EB%8F%84%EB%A9%94%EC%9D%B8-%EB%B6%84%EC%84%9D-SDP</guid>
            <pubDate>Mon, 30 Sep 2024 10:12:59 GMT</pubDate>
            <description><![CDATA[<p>2024년 10월 부터 새로운 팀으로 발령을 받게 되었다. 새로운 팀에 들어가게 되면 항상 무엇을 하는 팀인지 파악하는데 시간이 걸린다.
내가 어떤 기능을 개발해야하고 왜 필요한지를 알려면 도메인 분석은 필수이다. 업무에 적응하는 시간을 최소화하고, 더 나은 소프트웨어를 만들기 위해 팀이 변경 되기 전에 새로 들어가는 팀의 도메인을 스스로 분석해 보고 내가 회사에 어떻게 기여할 수 있는지에 대해 고민해 보았다. 이 글을 보는 여러 개발자들도 일을 하기 전에 도메인 분석이 필요하다는 것을 알았으면 좋겠다.</p>
<h2 id="sdp-개념">SDP 개념</h2>
<p>Software Defined Perimeter(SDP)는 보안 프레임워크로, 네트워크 자산에 대한 접근을 제한하고 인증된 사용자나 기기만이 접근할 수 있도록 설계된 보안 모델입니다. SDP는 전통적인 네트워크 보안 모델과 달리 네트워크 인프라를 숨기고, 네트워크 경계를 정의하여 미리 인증된 사용자에게만 네트워크에 접속할 수 있는 권한을 부여합니다.</p>
<p>SDP는 주로 제로 트러스트 보안 원칙을 따르며, 다음과 같은 핵심 개념을 포함합니다.</p>
<ol>
<li><p>제로 트러스트 : 네트워크 내부와 외부를 구분하지 않고, 모든 사용자와 기기를 신뢰하지 않으며, 지속적인 인증 및 권한 부여를 요구합니다.</p>
</li>
<li><p>인증 후 접근 : 사용자나 기기가 먼저 인증 되어야만 네트워크 자산에 접근할 수 있으며, 접근 권한이 부여된 자산 외에는 숨겨집니다.</p>
</li>
<li><p>동적 터널링 : 사용자가 인증이 되면, 네트워크 자산에 대한 임시적이고 동적인 터널링을 통해 필요한 자원에만 접근할 수 있습니다.</p>
</li>
<li><p>네트워크 숨김 : 네트워크 자산과 서비스가 외부에 노출되지 않으며, 인증되지 않는 사용자는 네트워크 자체를 볼 수 없습니다.</p>
</li>
</ol>
<p>SDP는 주로 원격 근무 환경에서 안전한 접속, 클라우드 보안, 분산 네트워크 보안 등에 사용되며 VPN(가산 사설망)이나 방화벽에 비해 더 정교하고 세분화된 보안 제어를 제공합니다.</p>
<h2 id="왜-sdp-라는-이름을-가지게-되었을까">왜 SDP 라는 이름을 가지게 되었을까?</h2>
<p>SDP 라는 약어가 만들어진 이유는, 이 기술이 네트워크 보안 경계를 전통적인 방식과는 다르게 스포트웨어적으로 정의하고, 관리하는 개념을 표현하기 위해서 입니다. 약어가 만들어진 이유는 아래와 같은 배경에 있습니다.</p>
<ol>
<li><p>전통적 네트워크 보안 모델의 한계
기존의 네트워크 보안 모델은 물리적 장치(방화벽, VPN 등)에 의해 네트워크 경계(Perimeter)를 정의했습니다. 이 모델은 네트워크 내부와 외부를 구분하여 내부는 신뢰할 수 있고, 외부는 신뢰할 수 없다는 가정 하에 작동하였습니다. 하지만 오늘날의 분산형 클라우드 환경, 원격 근무 등의 환경에서는 이 가정이 유효하지 않으며, 네트워크 경계 자체가 모호해졌습니다.</p>
</li>
<li><p>소프트웨어 정의 네트워크 발전
SDN(Software Defined Networking)과 같은 기술의 발전으로 네트워크 관리 및 제어가 하드웨어가 아닌 소프트웨어에서 이루어지게 되면서, 보안의 경계를 하드웨어가 아닌 소프트웨어적으로 유연하게 정의할 필요성이 생겼습니다.  SDP는 이러한 SDN 개념을 보안에 적용한 것으로, 네트워크 자산에 대한 접근을 소프트웨어적으로 정의하고 관리합니다.</p>
</li>
<li><p>Perimeter의 중요성
Perimeter 라는 단어는 전통적인 네트워크 보안 개념에서 경계를 의미합니다. SDP는 경계가 더 이상 물리적이지 않으며, 소프트웨어를 통해 동적으로 정의되고 관리될 수 있음을 나타내고자 합니다. 즉, 네트워크 자산과 그 접근 경계는 소프트웨어적으로 제어되며, 접근 권한이 없는 사용자에게는 네트워크 자산이 보이지 않거나 숨겨진 상태로 유지됩니다.</p>
</li>
<li><p>소프트웨어에 의한 제어
SDP는 보안을 하드웨어에 의존하지 않고 소프트웨어로 정의하고 관리하기 때문에 Software Defined 라는 이름을 붙였습니다. 소프트웨어는 기존 하드웨어 기반 보안 솔루션보다 유연하고 확장 가능하며, 클라우드 환경에서도 쉽게 적용할 수 있습니다.</p>
</li>
</ol>
<p>이러한 배경에서 “Software Defiend Perimeter(SDP)“라는 이름이 탄생했고, 이는 소프트웨어를 통해 동적으로 보안 경계를 정의하고, 사용자 접근을 통제하는 방식을 정확히 설명하는 용어 입니다.</p>
<h2 id="왜-sdp를-사용해야-할까">왜 SDP를 사용해야 할까?</h2>
<p>SDP와 VPN 둘 다 원격지 사용자가 네트워크 리소스에 안전하게 접근할 수 있도록 해주는 기술이지만, 그 접근 방식과 모델에서 큰 차이를 보입니다.</p>
<ol>
<li>보안 모델</li>
</ol>
<ul>
<li><p>VPN : 전통적인 보안 모델로, 사용자가 한 번 인증되면 네트워크 전체에 대한 접근 권한을 부여하는 방식입니다. 즉 신뢰와 비신뢰 경계를 네트워크 자체에 설정하고, VPN에 접속하면 내부망 리소스에 접근이 가능합니다.</p>
</li>
<li><p>SDP : 제로 트러스트 보안 모델을 따릅니다. 모든 사용자는 처음부터 신뢰하지 않으며, 사용자가 먼저 인증을 받은 후에야 특정 리소스에만 접근 권한이 부여됩니다. 네트워크 자체는 사용자가 인증되기 전까지 숨겨져 있으며, 인증 후에 필요한 자원에만 제한적으로 접근할 수 있습니다.</p>
</li>
</ul>
<ol start="2">
<li>네트워크 접근성</li>
</ol>
<ul>
<li><p>VPN : 사용자는 VPN 서버에 연결된 후, 전체 네트워크 내부 자원에 접근할 수 있게 됩니다. 이로 인해 불필요한 네트워크 리소스에도 접근할 수 있는 과도한 권한이 부여될 수 있습니다.</p>
</li>
<li><p>SDP : 사용자는 인증 후에 필요한 리소스에만 접근할 수 있습니다. 즉, 필요한 자원에만 연결이 가능하고 다른 리소스는 숨겨져 있어 접근 자체가 불가능합니다.</p>
</li>
</ul>
<ol start="3">
<li>보안 경계 설정</li>
</ol>
<ul>
<li><p>VPN : 네트워크 경계는 고정적입니다. 즉, 내부 네트워크와 외부 네트워크가 구분되며, VPN 연결을 통해 외부 사용자가 내부 네트워크에 접근하는 구조입니다. VPN은 네트워크를 통째로 노출할 위험이 있습니다.</p>
</li>
<li><p>SDP :  동적 경계를 설정합니다. 네트워크의 경계는 고정되지 않고 사용자의 인증 상태와 필요에 따라 동적으로 변합니다. 인증되지 않은 사용자에게는 네트워크 리소스 자체가 보이지 않게 숨겨지며, 인증 후에만 필요한 자산에 접근할 수 있습니다.</p>
</li>
</ul>
<h2 id="4-네트워크-가시성">4. 네트워크 가시성</h2>
<ul>
<li><p>VPN : 한 번 접속되면 사용자는 네트워크의 모든 자산에 대해 가시성을 갖습니다. 즉, 사용자가 내부 네트워크를 탐색할 수 있습니다.</p>
</li>
<li><p>SDP : 네트워크 자산은 사용자가 인증 받기 전에는 완전히 숨겨진 상태에 있습니다. 즉, 인증 받지 않은 사용자는 네트워크의 존재 자체를 인식하지 못합니다.</p>
</li>
</ul>
<ol start="5">
<li>세분화된 접근 제어</li>
</ol>
<ul>
<li><p>VPN  : 접근 제어가 상대적으로 덜 세분화됩니다. 사용자는 VPN 연결이 허용된 후 네트워크 자산 전체에 접근할 수 있으며, 세밀한 제어가 어렵습니다.</p>
</li>
<li><p>SDP : 세밀한 접근 제어를 지원합니다. 각 사용자는 인증 절차를 통해 개별 리소스에만 접근 권한을 부여받기 때문에, 접근 제어가 더 유연하고 구체적입니다.</p>
</li>
</ul>
<ol start="6">
<li>제로 트러스트 적용</li>
</ol>
<ul>
<li><p>VPN : VPN은 전통적으로 내부 네트워크를 신뢰하고 외부 네트워크에만 비신뢰로 간주하는 모델을 사용합니다. 이는 제로트러스트 원칙과는 맞지 않으며, 내부자 위협에 취약할 수 있습니다.</p>
</li>
<li><p>SDP : 제로 트러스트 모델을 기반으로 하여 네트워크 내부와 외부를 구분하지 않고, 모든 접근 요청에 대해 인증과 권한 부여를 요구합니다. 이는 더 안전한 보안 모델로, 내부자 위협에도 대응할 수 있습니다.</p>
</li>
</ul>
<ol start="7">
<li>보안 정책 적용</li>
</ol>
<ul>
<li><p>VPN : 보안 정책은 주로 VPN 연결 전에 설정되며, 연결 후에는 상대적으로 제한적인 정책 적용만 가능합니다. 사용자의 권한이나 접근을 실시간으로 동적으로 조정하기 어렵습니다.</p>
</li>
<li><p>SDP : 동적 보안 정책을 적용할 수 있습니다. 사용자의 역할, 위치, 장치 상태 등에 따라 실시간으로 정책을 조정할 수 있으며, 접속 중에도 필요에 따라 접근 권한을 변경할 수 있습니다.</p>
</li>
</ul>
<ol start="8">
<li>배포 및 확장성</li>
</ol>
<ul>
<li><p>VPN : VPN은 물리적 네트워크에 의존하기 때문에 대규모 네트워크나 분산된 환경에서 확장하기 어렵고, 사용자가 많을수록 성능 저하나 관리 복잡성이 커질 수 있습니다.</p>
</li>
<li><p>SDP : 클라우드 친화적이며, 분산된 네트워크 환경에서 확장성이 뛰어납니다. 네트워크 트래픽을 중앙화 하지 않고 사용자와 자원 간 직접적 연결을 설정할 수 있어, 보다 효율적입니다. (정말 그러한가 ?)</p>
</li>
</ul>
<ol start="9">
<li>클라우드 및 원격 근무에 대한 적합성</li>
</ol>
<ul>
<li><p>VPN : VPN은 모든 트래픽을 본사 네트워크로 라우팅하여 보안 문제를 해결하는 방식인데, 이는 클라우드 기반 애플리케이션에 적합하지 않고, 네트워크 성능을 저하시킬 수 있습니다.</p>
</li>
<li><p>SDP : 클라우드 및 원격 근무 환경에 적합한 방식으로, 사용자는 위치에 상관없이 인증된 자원에만 접근할 수 있고, 클라우드 기반 애플리케이션과의 호환성이 뛰어납니다.</p>
</li>
</ul>
<ol start="10">
<li>배포 간소화</li>
</ol>
<ul>
<li><p>VPN : VPN 설정과 배포에는 고정된 네트워크 인프라와 복잡한 설정이 필요하며, 특히 분산된 환경에서 배포가 번거로울 수 있습니다.</p>
</li>
<li><p>SDP : SDP는 소프트웨어 기반으로 동작하며, 빠른 배포와 설정이 가능하고, 확장성과 유연성이 높습니다.</p>
</li>
</ul>
<h2 id="결론">결론</h2>
<p>VPN은 전통적인 네트워크 보안 모델로, 사용자가 네트워크에 한 번 연결되면 전체 네트워크에 대한 접근이 가능하다는 점에서 보안에 취약할 수 있습니다.</p>
<p>SDP는 더 발전된 보안 모델로, 제로 트러스트 원칙을 따르고, 사용자에게 필요한 자원만 접근할 수 있도록 하는 세분화된 제어와 동적 네트워크 경계를 제공합니다.</p>
<h2 id="sdp-팀에서-내가-할-수-있는-일--하고-싶은-일---백엔드">SDP 팀에서 내가 할 수 있는 일 &amp; 하고 싶은 일 - 백엔드</h2>
<ol>
<li>지속 성장 가능한 소프트웨어 개발
비지니스 로직 관리</li>
</ol>
<ul>
<li>모듈화</li>
<li>테스트 코드 작성</li>
<li>커스텀 최소화하기 (안하기 ?)</li>
<li>CI/CD 파이프 라인 구축</li>
<li>좋은 개발 문화를 가진 회사가 되도록 기여하기
<a href="https://geminikims.medium.com/%EC%A7%80%EC%86%8D-%EC%84%B1%EC%9E%A5-%EA%B0%80%EB%8A%A5%ED%95%9C-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4%EB%A5%BC-%EB%A7%8C%EB%93%A4%EC%96%B4%EA%B0%80%EB%8A%94-%EB%B0%A9%EB%B2%95-97844c5dab63">지속 성장 가능한 소프트웨어 만들어 가는 방법</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[회사 프로젝트 마이그레이션 중간 기록 -끄적끄적]]></title>
            <link>https://velog.io/@jay_be/%ED%9A%8C%EC%82%AC-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%A7%88%EC%9D%B4%EA%B7%B8%EB%A0%88%EC%9D%B4%EC%85%98-%EC%A4%91%EA%B0%84-%EA%B8%B0%EB%A1%9D-%EB%81%84%EC%A0%81%EB%81%84%EC%A0%81</link>
            <guid>https://velog.io/@jay_be/%ED%9A%8C%EC%82%AC-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%A7%88%EC%9D%B4%EA%B7%B8%EB%A0%88%EC%9D%B4%EC%85%98-%EC%A4%91%EA%B0%84-%EA%B8%B0%EB%A1%9D-%EB%81%84%EC%A0%81%EB%81%84%EC%A0%81</guid>
            <pubDate>Thu, 07 Mar 2024 16:01:32 GMT</pubDate>
            <description><![CDATA[<p>현재 회사에서는 기술 스택 변경을 위해 힘든 나날들을 보내고 있습니다.
10년이 넘는 기간동안 많은 사람들 손을 거쳐 있던 프로젝트는 생각보다 많이 복잡하였고,
쓸모없는 코드도 꽤나 존재하였습니다.</p>
<p>하지만 가장 큰 문제는 Spring 과 Ext.js는 굉장히 강한 결합성을 가지고 있습니다.
이제 프론트와 백엔드의 결합을 끊고 RestApi 형태의 백엔드 프로젝트로 변경하려고 한합니다.</p>
<h3 id="기존-기술-스택">기존 기술 스택</h3>
<ul>
<li>Java7 or 8</li>
<li>Spring 3.XX</li>
<li>Ext.js : 아는 사람이 있을까..</li>
<li>Elastic-Search</li>
<li>ibatis</li>
<li>Mysql</li>
</ul>
<h3 id="변경-기술-스택">변경 기술 스택</h3>
<ul>
<li>Java8</li>
<li>Spring boot : 2.XX</li>
<li>Elastic-Search</li>
<li>mybatis</li>
<li>Mysql</li>
</ul>
<blockquote>
<p>Spring 3점대 프로젝트를 Spring boot 2점대로 옮기는 작업은 생각만큼 쉽지 않습니다..</p>
</blockquote>
<h4 id="마이그레이션-순서는-다음과-같습니다">마이그레이션 순서는 다음과 같습니다.</h4>
<ol>
<li>Spring boot 프로젝트 생성</li>
<li>프로젝트 구조 잡기</li>
<li>디펜던시 마이그레이션</li>
<li>소스 이관 + 휴먼 테스트 + 문서화 ### 현재 단계</li>
<li>리펙토링 + 테스트코드 작성</li>
<li>CI/CD 구축</li>
</ol>
<p>이번 포스팅에서는 DTO 리펙토링에 대해 다루도록 하겠습니다.</p>
<p>1,2,3 작업을 마친 후 프로젝트 구조는 아래와 같습니다. (백엔드)</p>
<p><img src="https://velog.velcdn.com/images/jay_be/post/b833e506-978b-4112-957f-3e8803884df3/image.png" alt=""></p>
<p>기존 코드에서 마이그레이션을 할 때 먼저 서비스 코드는 변경하지 않기로 결정하였습니다. 기존 서비스 코드에서는 MAP을
사용하고 있었기 때문에 자유로운 포맷으로 데이터를 주고 받을 수 있었으나 마이그레이션 과정에서는 굉장히 큰 리스크로 다가왔습니다.</p>
<p>RequestDTO와 ResponseDTO를 분리하여 코드를 작성하기로 하였고 그 과정에서 사용했던 코드를 생각나는대로 작성해보려고 합니다.</p>
<p>DTO 에서 Map으로 바꾸는 코드와 Map에서 DTO로 바꾸는 코드는 모두 DTO 클래스 안에 static 함수로 작성하였습니다.</p>
<p>코드의 재사용성을 높히기 위해 ResponseService 클래스를 만들어 동일한 포팻으로 리턴할 수 있게 하였습니다. 각기 다른 ResponseDTO 내부에 있는 toModel 메서드의 예시입니다.</p>
<pre><code class="language-java">public class PolicyResponseDto {
    private String name;
    private String content;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    // 컨버팅
    public static PolicyResponseDto toModel(Map&lt;String, String&gt; map) {
        PolicyResponseDto policyResponseDto = new PolicyResponseDto();
        policyResponseDto.setName(map.get(&quot;name&quot;));
        policyResponseDto.setContent(map.get(&quot;content&quot;));
        return policyResponseDto;
    }
}</code></pre>
<p>여기서 제가 고민한 부분은 하나의 함수를 거쳐서 map을 각각의 클래스의 toModel을 호출하여 컨버팅 되어 리턴되는 함수를 만드는 것이였습니다.</p>
<pre><code class="language-java">@FunctionalInterface
public interface DataConverter&lt;T&gt; {
    T toModel(Map map);
}
</code></pre>
<p>인터페이스를 하나 만든 후 ResponseDTO에 인터페이스를 구현시켰습니다.</p>
<pre><code class="language-java">public class PolicyResponseDto implements DataConverter&lt;PolicyResponseDto&gt;{
    private String name;
    private String content;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public PolicyResponseDto toModel(Map map) {
        PolicyResponseDto policyResponseDto = new PolicyResponseDto();
        policyResponseDto.setName((String) map.get(&quot;name&quot;));
        policyResponseDto.setContent((String) map.get(&quot;content&quot;));
        return policyResponseDto;
    }
}</code></pre>
<p>ResponseService 클래스에는 DataConverter 클래스를 구현한 클래스의 객체를 생성시킨 후 그 안에 toModel 함수를 호출 시킵니다.
그리고 그것을 리스트에 담고 페이징 정보를 담는 역할을 합니다.</p>
<pre><code class="language-java">public class ResponseService {

    public &lt;T extends DataConverter&lt;T&gt;&gt; PagingListResponse&lt;T&gt; getPagingListResult(List&lt;Map&gt; list, Class&lt;T&gt; dtoClass) {
        PagingListResponse&lt;T&gt; result = new PagingListResponse&lt;&gt;();

        result.setList(
                list.stream().map(item -&gt; mapToDto((Map&lt;String, Object&gt;) item, dtoClass))
                        .collect(Collectors.toList())
        );
        // 생략
        return result;
    }


    private static final&lt;T extends DataConverter&lt;T&gt;&gt; T mapToDto(Map map, Class&lt;T&gt; dtoClass) {
        try {
            return dtoClass.getDeclaredConstructor().newInstance().toModel(map);
        } catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) {
            e.printStackTrace();
            throw new IllegalStateException(&quot;Failed to map to DTO&quot;, e);
        }
    }
}</code></pre>
<p>이제 서비스에서 컨트롤러로 데이터를 돌려줄 때 ResponseService에 컨퍼팅 함수에 넣어 리턴시켜줍니다.</p>
<pre><code class="language-java">
public PagingListResponse getPolicyList(Map map) {
    // 생략
    return responseService.getPagingListResult(list, PolicyResponseDto.class);
}
</code></pre>
<p>이제 해당 함수를 통해 컨트롤러로 보내주면 알맞은 DTO로 변경되어 컨트롤러로 넘어가게 됩니다.
더 좋은 방법이 존재할 것 같습니다. 최근에 자바 공부를 또 열심히 하고 있는데 제네릭을 활용하여 하나의 함수로
컨버팅을 할 수 있게 만든 것 같아 뿌듯합니다. :) 더 공부하여 나중에 더 좋은 방법을 찾을 수 있도록 하겠습니다 :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[docker] spring boot와 redis 통신 삽질 -docker network]]></title>
            <link>https://velog.io/@jay_be/docker-spring-boot%EC%99%80-redis-%ED%86%B5%EC%8B%A0-%EC%82%BD%EC%A7%88-docker-network</link>
            <guid>https://velog.io/@jay_be/docker-spring-boot%EC%99%80-redis-%ED%86%B5%EC%8B%A0-%EC%82%BD%EC%A7%88-docker-network</guid>
            <pubDate>Sun, 25 Feb 2024 15:25:51 GMT</pubDate>
            <description><![CDATA[<p>GCP에 docker를 사용해서 Spring boot, mysql, redis를 올려 docker run을 시켰습니다.</p>
<h1 id="하지만">하지만....</h1>
<p>Spring boot에서는 mysql과 redis와 커넥션이 되지 않는다는 에러를 뱉어냈습니다..</p>
<p>삽질을 하다보니 결국 &quot;docker network&quot; 라는 키워드를 얻었고 도커 네트워크를 동일하게 설정해야 컨테이너 간의 통신이 가능해진다는 것을 알았습니다.</p>
<h2 id="docker-network-생성">docker network 생성</h2>
<pre><code>$ docker network create docker-network</code></pre><pre><code>$ docker network list
NETWORK ID     NAME             DRIVER    SCOPE
f39965968f55   bridge           bridge    local
4d78319bd446   docker-network   bridge    local
76d4b90c010b   host             host      local
20e3f2dcc9cf   none             null      local
</code></pre><p>잘 생성된 것을 확인할 수 있습니다. 이제 도커 컨테이너 실행 시에 옵션을 주어 도커 네트워크에 포함되도록 합니다. (spring boot, mysql, redis)</p>
<pre><code>docker run -d -p 8080:8080 --network docker-network -v /home/uploadedImage:/home/uploadedImage -e SPRING_PROFILES_ACTIVE=prod [project]
</code></pre><p>이제 컨테이너들 간에 통신이 잘 되는 것을 확인할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[GCP SSL, Nginx, Https]]></title>
            <link>https://velog.io/@jay_be/GCP-SSL-Nginx-Https</link>
            <guid>https://velog.io/@jay_be/GCP-SSL-Nginx-Https</guid>
            <pubDate>Sun, 25 Feb 2024 15:10:22 GMT</pubDate>
            <description><![CDATA[<p>위 글은 기록용이므로 흐름 파악용으로만 보실 것을 권장드립니다. :)</p>
<p>백엔드 API를 프론트 분들에게 GCP에 프로젝트를 배포하여 Swagger API를 제공하려고 합니다. </p>
<ul>
<li>GCP ubuntu 22.04</li>
<li>nginx</li>
<li>SSL</li>
<li>docker</li>
</ul>
<p>GCP 서버는 이미 만들어져 있고, 도메인도 구매했다는 가정하에 진행합니다.
<img src="https://velog.velcdn.com/images/jay_be/post/9083aa3f-60bf-4fc5-95ea-ede2f69a7cdf/image.png" alt=""></p>
<h2 id="1-인스턴스-그룹-만들기">1. 인스턴스 그룹 만들기</h2>
<p>인스턴스 그룹은 단일 항목으로 관리할 수 있는 가상머신 인스턴스의 모음입니다. 
New unmanaged instance Group(새로운 비관리형 인스턴스 그룹으로 만들고 VM 인스턴스를 추가해줍니다.</p>
<p><img src="https://velog.velcdn.com/images/jay_be/post/0568ed69-1857-4262-86d5-752360f31566/image.png" alt=""></p>
<h2 id="2-로드밸런서-만들기">2. 로드밸런서 만들기</h2>
<p>GCP에서 제공하는 부하분산기를 사용하요 사용자의 모든 요청은 해당 부하분산기를 통해 내부로 들어오게 됩니다.
<img src="https://velog.velcdn.com/images/jay_be/post/ec2a5b43-c5bf-4f7b-a998-156e13bd1017/image.png" alt="">
<img src="https://velog.velcdn.com/images/jay_be/post/b8c93cfe-430b-49df-a547-d38f66c87acf/image.png" alt="">
<img src="https://velog.velcdn.com/images/jay_be/post/fdb64c33-5e1d-450c-87a2-e6a36d6c997c/image.png" alt="">
<img src="https://velog.velcdn.com/images/jay_be/post/860c9648-8dcb-4b5a-b813-5bb61756931b/image.png" alt="">
여기서 프론트엔드는 요청을 받는 앞단의 로드밸런서이고 백엔드는 실행하려는 서비스가 돌고 있는 서버입니다. 백엔드에는 https를 적용할 필요가 없습니다. </p>
<p>프론트에는 인증서가 필요하므로 GCP에서 제공하는 SSL 인증서를 만들어서 넣어주었습니다.</p>
<p><img src="https://velog.velcdn.com/images/jay_be/post/cdeb87a9-18c6-413e-be1b-fea89a999a2d/image.png" alt=""></p>
<p>부하분산기가 잘 작동되는 것을 확인할 수 있습니다.
<img src="https://velog.velcdn.com/images/jay_be/post/141ec453-819a-4b8c-86b1-3d23d12534a9/image.png" alt="">
Cloud DNS도 위와같이 설정해주었습니다. 가비아에 네임서버에도 아래와 같이 설정을 해주었습니다.
<img src="https://velog.velcdn.com/images/jay_be/post/8dfe87a6-2559-491a-8639-e7bd83ad7562/image.png" alt=""></p>
<p>이제 해당 도메인에 들어오는 요청은 GCP 프론트엔드를 거쳐 GCP 백엔드(서비스가 돌아가고 있는 서버)로 넘어가게 됩니다. 443 -&gt; 80 포트로 왔기 때문에 GCP 백엔드에는 Nginx를 설치하고 80 포트로 들어오는 요청을 8080포트로 리다이렉트 시켜주는 설정을 해줍니다.</p>
<pre><code>vi /etc/nginx/sites-enabled/default</code></pre><pre><code>server {
        listen 80 default_server;
        listen [::]:80 default_server;
        server_name [ip입력];

        # SSL configuration
        #       # listen 443 ssl default_server;
        # listen [::]:443 ssl default_server;
        #
        # Note: You should disable gzip for SSL traffic.
        # See: https://bugs.debian.org/773332
        #
        # Read up on ssl_ciphers to ensure a secure configuration.
        # See: https://bugs.debian.org/765782
        #
        # Self signed certs generated by the ssl-cert package
        # Don&#39;t use them in a production server!
        #
        # include snippets/snakeoil.conf;

        root /var/www/html;

        # Add index.php to the list if you are using PHP
        index index.html index.htm index.nginx-debian.html;

        location / {
                 proxy_set_header    HOST $http_host;
                proxy_set_header    X-Real-IP $remote_addr;
                proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header    X-Forwarded-Proto $scheme;
                proxy_set_header    X-NginX-Proxy true;
                proxy_pass http://localhost:8080;
                proxy_redirect  off;
                charset utf-8;


                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.

</code></pre><p>이제 백엔드 서버에 들어오는 요청을 80포트에서 8080포트로 리다이렉트를 하여 스웨거 페이지를 제공할 수 있게 하였습니다.</p>
<p>참조 블로그
<a href="https://keyhyuk-kim.medium.com/%EA%B5%AC%EA%B8%80-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%ED%94%8C%EB%9E%AB%ED%8F%BC-gcp-compute-engine%EC%97%90-https-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0-e74489d2abf2">https://keyhyuk-kim.medium.com/%EA%B5%AC%EA%B8%80-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%ED%94%8C%EB%9E%AB%ED%8F%BC-gcp-compute-engine%EC%97%90-https-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0-e74489d2abf2</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ELK 스택 찍먹하기 (ElasticSearch, Logstash, Kibana) - 1편 ElasticSearch]]></title>
            <link>https://velog.io/@jay_be/ELK-%EC%8A%A4%ED%83%9D-%EC%B0%8D%EB%A8%B9%ED%95%98%EA%B8%B0-ElasticSearch-Logstash-Kibana-1%ED%8E%B8</link>
            <guid>https://velog.io/@jay_be/ELK-%EC%8A%A4%ED%83%9D-%EC%B0%8D%EB%A8%B9%ED%95%98%EA%B8%B0-ElasticSearch-Logstash-Kibana-1%ED%8E%B8</guid>
            <pubDate>Tue, 20 Feb 2024 04:46:35 GMT</pubDate>
            <description><![CDATA[<p>해당 시리즈는 인프런 강의를 듣고 정리한 내용입니다.
<a href="https://www.inflearn.com/course/elk-%EC%8A%A4%ED%83%9D-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%84%EC%84%9D/dashboard">ELK 스택 (ElasticSearch, Logstash, Kibana)으로 데이터 분석</a></p>
<h2 id="학습-목표--아래-그림의-내용을-이해하고-찍먹해보자">학습 목표 : 아래 그림의 내용을 이해하고, 찍먹해보자</h2>
<p><img src="https://velog.velcdn.com/images/jay_be/post/b71c05d4-abed-4ff8-b114-aacdd80e7685/image.png" alt="">
데이터를 수집하여 elasticsearch에 넣어주는 logstash
elasticsearch 데이터를 보기 좋게 보여주는 kibana를 사용합니다.
elasticsearch의 빠른 검색으로 빅데이터도 kibana로 빠르게 시각화 할 수 있게 됩니다.</p>
<h2 id="1-우분투에-엘라스틱-서치-설치하기">1. 우분투에 엘라스틱 서치 설치하기</h2>
<p>Ubuntu 22.04 LTS 버전을 기준입니다.</p>
<blockquote>
<p>JDK는 알아서 설치해주세요!!!</p>
</blockquote>
<ol>
<li><p>보안키 서명</p>
<pre><code>curl -fsSL https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -</code></pre></li>
<li><p>apt 리스트 추가</p>
<pre><code>echo &quot;deb https://artifacts.elastic.co/packages/7.x/apt stable main&quot; | sudo tee -a /etc/apt/sources.list.d/elastic-7.x.list</code></pre></li>
<li><p>apt update</p>
<pre><code>sudo apt update</code></pre></li>
<li><p>ElasticSearch 서치 설치</p>
<pre><code>sudo apt install elasticsearch</code></pre></li>
<li><p>ElasticSearch 서치 실행</p>
<pre><code>service elasticsearch start</code></pre></li>
<li><p>잘 설치되었는지 확인</p>
<pre><code>service elasticsearch status</code></pre></li>
<li><p>버전 확인</p>
<pre><code>curl localhost:9200</code></pre><p><img src="https://velog.velcdn.com/images/jay_be/post/b6feb50f-c281-4a8b-87ff-5bd11564748e/image.png" alt=""></p>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/jay_be/post/d4cefbda-87f4-4950-b7bd-98a7c7f00861/image.png" alt=""></p>
<h2 id="2-elasticsearch-기본-개념-정리">2. elasticsearch 기본 개념 정리</h2>
<h3 id="용어1">용어1</h3>
<table>
<thead>
<tr>
<th>Elastic Search</th>
<th>RDB</th>
</tr>
</thead>
<tbody><tr>
<td>Index</td>
<td>Database</td>
</tr>
<tr>
<td>Type</td>
<td>Table</td>
</tr>
<tr>
<td>Document</td>
<td>Row</td>
</tr>
<tr>
<td>Field</td>
<td>Column</td>
</tr>
<tr>
<td>Mapping</td>
<td>Schema</td>
</tr>
</tbody></table>
<h3 id="용어2">용어2</h3>
<table>
<thead>
<tr>
<th>Elastic Search</th>
<th>RDB</th>
</tr>
</thead>
<tbody><tr>
<td>GET</td>
<td>Select</td>
</tr>
<tr>
<td>PUT</td>
<td>Update</td>
</tr>
<tr>
<td>POST</td>
<td>Insert</td>
</tr>
<tr>
<td>DELETE</td>
<td>Delete</td>
</tr>
</tbody></table>
<h4 id="예시">예시</h4>
<pre><code>curl -XGET localhost:9200/classes/class/1
-&gt; select * from class where id = 1

curl -XPOST localhost:9200/classes/class/1 -d &#39;{xxx}&#39;
-&gt; Insert into class values (xxx)

curl -XPUT localhost:9200/classes/class/1 -d &#39; {xxx}&#39;
-&gt; update class set xxx where id = 1

curl -XDELETE localhost:9200/classes/class/1
-&gt; delete from class where id = 1</code></pre><blockquote>
<p>여기서 classes 는 index, class는 테이블, 1은 id를 뜻합니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jay_be/post/87580a42-9ea1-4435-8595-fc07653b7032/image.png" alt="">
엘라스틱 서치 결과값을 눈에 보기 좋게 하기 위해 뒤에 파라미터로 pretty를 붙히면 보기 좋게 출력됩니다.</p>
<h4 id="post--content-type-에러-잘못된-예시">POST / content-type 에러 (잘못된 예시)</h4>
<pre><code>curl -XPOST localhost:9200/classes/class/1 -d &#39;{&quot;titile&quot;: &quot;me&quot;, &quot;professor&quot; : &quot;홍길동&quot;}&#39;</code></pre><p><img src="https://velog.velcdn.com/images/jay_be/post/b3bf3327-8a87-401c-a39e-866286c063eb/image.png" alt="">
ES6.0 이후 부터는 content-type을 기재해주어야 합니다. (아... 귀찮아)</p>
<h4 id="post--content-type-삽입-후-성공">POST / content-type 삽입 후 성공</h4>
<pre><code>curl -XPOST localhost:9200/classes/class/1 -d &#39;{&quot;titile&quot;: &quot;me&quot;, &quot;professor&quot; : &quot;홍길동&quot;}&#39; -H &quot;Content-Type: application/json&quot;</code></pre><p><img src="https://velog.velcdn.com/images/jay_be/post/c7df19bf-cb7a-4b97-a320-93d1838731b1/image.png" alt="">
데이터가 잘 들어가진 것을 확인할 수 있습니다.
<img src="https://velog.velcdn.com/images/jay_be/post/2ab5193f-1716-445d-ae62-ede82a8b0170/image.png" alt=""></p>
<h4 id="post--json-파일-insert-하기">POST / .json 파일 insert 하기</h4>
<pre><code>curl -XPOST http://localhost:9200/classes/class/1/ -d @oneclass.json -H &quot;Content-Type: application/json&quot;</code></pre><h4 id="bulk-post">BULK POST</h4>
<pre><code>curl -XPOST http://localhost:9200/_bulk?pretty --data-binary @class.json -H &quot;Content-Type: application/json&quot;</code></pre><h2 id="3-mapping">3. Mapping</h2>
<pre><code>curl -XPUT http://localhost:9200/classes/class/_mapping -d @classRating_mapping.json -H &quot;Content-Type: application/json&quot;</code></pre><p>매핑을 추가하게 되면 데이터를 뽑아낼 때 도움이 됩니다. 아래에 예제가 존재합니다!</p>
<h2 id="4-데이터-조회-search">4. 데이터 조회 (search)</h2>
<h4 id="record-document-전체-조회">record document 전체 조회</h4>
<pre><code>crul -XGET localhost:9200/basketball/record/_search?pretty</code></pre><h4 id="points가-30인-데이터만-출력">points가 30인 데이터만 출력</h4>
<pre><code>curl -XGET localhost:9200/basketball/record/_search?q=points:30&amp;pretty

curl -XGET localhost:9200/basketball/record/_search -d &#39;
{
    &quot;query&quot; : {
        &quot;term&quot; : {&quot;points&quot; : 30}
    }
}&#39;</code></pre><h2 id="5-메트릭-어그리게이션">5. 메트릭 어그리게이션</h2>
<p>어그리게이션 : 엘라스틱 서치의 도큐먼트에서 값을 도출할 때 쓰이는 방법입니다. 그 중에서 메트릭 어그리게이션은 산수값을 구할 때 사용합니다.</p>
<p>simple_basketball.json</p>
<pre><code>{&quot;index&quot; : { &quot;_index&quot; : &quot;basketball&quot;, &quot;_type&quot; : &quot;record&quot;, &quot;_id&quot; : &quot;1&quot;}}
{&quot;team&quot;: &quot;chicago bulls&quot;, &quot;name&quot;: &quot;micheal Jordan&quot;, &quot;points&quot;: 30, &quot;rebound&quot;: 3, &quot;assists&quot;: 4, &quot;submit_date&quot;: &quot;1996-10-11&quot;}
{&quot;index&quot; : { &quot;_index&quot; : &quot;basketball&quot;, &quot;_type&quot; : &quot;record&quot;, &quot;_id&quot; : &quot;2&quot;}}
{&quot;team&quot;: &quot;chicago bulls&quot;, &quot;name&quot;: &quot;micheal Jordan&quot;, &quot;points&quot;: 20, &quot;rebound&quot;: 5, &quot;assists&quot;: 8, &quot;submit_date&quot;: &quot;1996-10-10&quot;}</code></pre><p>해당 데이터를 먼저 elasticsearch에 넣도록 하겠습니다.</p>
<pre><code>curl -XPOST localhost:9200/_bulk --data-binary @simple_basketball.json -H &quot;Content-Type: application/json&quot;</code></pre><p><img src="https://velog.velcdn.com/images/jay_be/post/52b39a7f-3269-4bea-b33d-eb5d83a3689a/image.png" alt=""></p>
<p>avg_points_aggs.json</p>
<pre><code>{
        &quot;size&quot;: 0,
        &quot;aggs&quot;: {
                &quot;avg_score&quot;: {
                        &quot;avg&quot;: {
                                &quot;field&quot;: &quot;points&quot;
                        }
                }
        }
}
</code></pre><p>간단한 어그리게이션 파일을 작성했습니다. 
size: 0 -&gt; 결과값에 원하는 값만 보겠다.
aggs -&gt; 어그리게이션 파일이다.
avg_score -&gt; 어그리게이션 이름
avg -&gt; 평균값을 구하겠다.
field -&gt; points의 평균값을 구하겠다.</p>
<h4 id="어그리게이션-파일-실행">어그리게이션 파일 실행</h4>
<pre><code>curl -XGET localhost:9200/_search?pretty --data-binary @avg_points.json -H &quot;Content-Type: application/json&quot;</code></pre><p>결과가 잘 나오는 것을 확인할 수 있습니다.</p>
<pre><code>{
  &quot;took&quot; : 100,
  &quot;timed_out&quot; : false,
  &quot;_shards&quot; : {
    &quot;total&quot; : 2,
    &quot;successful&quot; : 2,
    &quot;skipped&quot; : 0,
    &quot;failed&quot; : 0
  },
  &quot;hits&quot; : {
    &quot;total&quot; : {
      &quot;value&quot; : 3,
      &quot;relation&quot; : &quot;eq&quot;
    },
    &quot;max_score&quot; : null,
    &quot;hits&quot; : [ ]
  },
  &quot;aggregations&quot; : {
    &quot;avg_score&quot; : {
      &quot;value&quot; : 25.0
    }
  }
}</code></pre><p>max_points.json</p>
<pre><code>{
        &quot;size&quot;: 0,
        &quot;aggs&quot;: {
                &quot;max_score&quot; : {
                        &quot;max&quot; : {
                                &quot;field&quot; : &quot;points&quot;
                        }
                }
        }
}
</code></pre><h4 id="어그리게이션-파일-실행-1">어그리게이션 파일 실행</h4>
<pre><code>curl -XGET localhost:9200/_search?pretty --data-binary @max_points.json -H &quot;Content-Type: application/json&quot;</code></pre><p>결과가 잘 나오는 것을 확인할 수 있습니다.</p>
<pre><code>{
  &quot;took&quot; : 7,
  &quot;timed_out&quot; : false,
  &quot;_shards&quot; : {
    &quot;total&quot; : 2,
    &quot;successful&quot; : 2,
    &quot;skipped&quot; : 0,
    &quot;failed&quot; : 0
  },
  &quot;hits&quot; : {
    &quot;total&quot; : {
      &quot;value&quot; : 3,
      &quot;relation&quot; : &quot;eq&quot;
    },
    &quot;max_score&quot; : null,
    &quot;hits&quot; : [ ]
  },
  &quot;aggregations&quot; : {
    &quot;max_score&quot; : {
      &quot;value&quot; : 30.0
    }
  }
}</code></pre><blockquote>
<p>min, sum, stats도 가능합니다.
stats는 count, min, max, avg, sum을 한번에 보여줍니다.</p>
</blockquote>
<p>stats_points.json</p>
<pre><code>{
        &quot;size&quot;: 0,
        &quot;aggs&quot;: {
                &quot;max_score&quot; : {
                        &quot;stats&quot; : {
                                &quot;field&quot; : &quot;points&quot;
                        }
                }
        }
}
</code></pre><h4 id="어그리게이션-파일-실행-2">어그리게이션 파일 실행</h4>
<pre><code>curl -XGET localhost:9200/_search?pretty --data-binary @stats_points.json -H &quot;Content-Type: application/json&quot;
</code></pre><p>결과가 잘 나오는 것을 확인할 수 있습니다.</p>
<pre><code>{
  &quot;took&quot; : 23,
  &quot;timed_out&quot; : false,
  &quot;_shards&quot; : {
    &quot;total&quot; : 2,
    &quot;successful&quot; : 2,
    &quot;skipped&quot; : 0,
    &quot;failed&quot; : 0
  },
  &quot;hits&quot; : {
    &quot;total&quot; : {
      &quot;value&quot; : 3,
      &quot;relation&quot; : &quot;eq&quot;
    },
    &quot;max_score&quot; : null,
    &quot;hits&quot; : [ ]
  },
  &quot;aggregations&quot; : {
    &quot;max_score&quot; : {
      &quot;count&quot; : 2,
      &quot;min&quot; : 20.0,
      &quot;max&quot; : 30.0,
      &quot;avg&quot; : 25.0,
      &quot;sum&quot; : 50.0
    }
  }
}</code></pre><h2 id="6-버킷-어그리게이션">6. 버킷 어그리게이션</h2>
<p>RDB에 GROUP BY라고 보면 됩니다.</p>
<h3 id="1-기존-인덱스-삭제">1. 기존 인덱스 삭제</h3>
<pre><code>curl -XDELETE localhost:9200/basketball</code></pre><h3 id="2-인덱스-추가">2. 인덱스 추가</h3>
<pre><code>curl -XPUT localhost:9200/basketball?pretty</code></pre><h3 id="3-매핑-추가">3. 매핑 추가</h3>
<p>basketball_mapping.json</p>
<pre><code>{
    &quot;record&quot; : {
        &quot;properties&quot; : {
            &quot;team&quot; : {
                &quot;type&quot; : &quot;text&quot;,
                &quot;fielddata&quot; : true
            },
            &quot;name&quot; : {
                &quot;type&quot; : &quot;text&quot;,
                &quot;fielddata&quot; : true
            },
            &quot;points&quot; : {
                &quot;type&quot; : &quot;long&quot;
            },
            &quot;rebounds&quot; : {
                &quot;type&quot; : &quot;long&quot;
            },            
            &quot;assists&quot; : {
                &quot;type&quot; : &quot;long&quot;
            },
            &quot;blocks&quot; : {
                &quot;type&quot; : &quot;long&quot;
            },
            &quot;submit_date&quot; : {
                &quot;type&quot; : &quot;date&quot;,
                &quot;format&quot; : &quot;yyyy-MM-dd&quot;
            }               
        }
    }
}</code></pre><pre><code>curl -XPUT &#39;localhost:9200/basketball/record/_mapping?include_type_name=true&amp;pretty&#39; -d @basketball_mapping.json -H &quot;Content-Type:application/json&quot;
</code></pre><p><img src="https://velog.velcdn.com/images/jay_be/post/36ef0424-bc78-4d91-a36e-4f6ff3422085/image.png" alt=""></p>
<h3 id="4-도큐먼트-추가">4. 도큐먼트 추가</h3>
<p>twoteam_basketball.json</p>
<pre><code>{&quot;index&quot; : { &quot;_index&quot; : &quot;basketball&quot;, &quot;_type&quot; : &quot;record&quot;, &quot;_id&quot; : &quot;1&quot;}}
{&quot;team&quot;: &quot;chicago&quot;, &quot;name&quot;: &quot;micheal Jordan&quot;, &quot;points&quot;: 30, &quot;rebound&quot;: 3, &quot;assists&quot;: 4, &quot;submit_date&quot;: &quot;1996-10-11&quot;}
{&quot;index&quot; : { &quot;_index&quot; : &quot;basketball&quot;, &quot;_type&quot; : &quot;record&quot;, &quot;_id&quot; : &quot;2&quot;}}
{&quot;team&quot;: &quot;chicago&quot;, &quot;name&quot;: &quot;micheal Jordan&quot;, &quot;points&quot;: 20, &quot;rebound&quot;: 5, &quot;assists&quot;: 8, &quot;submit_date&quot;: &quot;1996-10-10&quot;}
{&quot;index&quot; : { &quot;_index&quot; : &quot;basketball&quot;, &quot;_type&quot; : &quot;record&quot;, &quot;_id&quot; : &quot;1&quot;}}
{&quot;team&quot;: &quot;LA&quot;, &quot;name&quot;: &quot;kobe&quot;, &quot;points&quot;: 30, &quot;rebound&quot;: 3, &quot;assists&quot;: 4, &quot;submit_date&quot;: &quot;1996-10-11&quot;}
{&quot;index&quot; : { &quot;_index&quot; : &quot;basketball&quot;, &quot;_type&quot; : &quot;record&quot;, &quot;_id&quot; : &quot;2&quot;}}
{&quot;team&quot;: &quot;LA&quot;, &quot;name&quot;: &quot;kobe&quot;, &quot;points&quot;: 40, &quot;rebound&quot;: 5, &quot;assists&quot;: 8, &quot;submit_date&quot;: &quot;1996-10-10&quot;}
</code></pre><h3 id="5-데이터-추가">5. 데이터 추가</h3>
<pre><code>curl -XPOST localhost:9200/_bulk?pretty --data-binary @twoteam_basketball.json -H &quot;Content-Type:application/json&quot;</code></pre><p>term_aggs.json</p>
<pre><code>{
    &quot;size&quot; : 0,
    &quot;aggs&quot; : {
        &quot;players&quot; : {
            &quot;terms&quot; : {
                &quot;field&quot; : &quot;team&quot;
            }
        }
    }
}</code></pre><p>size : 0 -&gt; 어그리게이션한 정보만 볼 수 있게
aggs -&gt; 어그리게이션 
players -&gt; 어그리게이션 내용
terms -&gt; terms 어그리게이션을 사용하겠다.
field : team -&gt; 팀별로 묶겠다.</p>
<h3 id="6-aggs-group-by-team-팀으로-묶어서-출력">6. AGGS (group by team) 팀으로 묶어서 출력</h3>
<pre><code>curl -XGET localhost:9200/_search?pretty --data-binary @term_aggs.json -H &#39;Content-Type:application/json&#39;</code></pre><h4 id="결과">결과</h4>
<p>결과가 잘 나온 것을 확인할 수 있습니다.
<img src="https://velog.velcdn.com/images/jay_be/post/e51b4599-646a-4283-8245-bad4e8d68bfe/image.png" alt="">
사실 이렇게 심플한 예제 보다는. 각 팀의 점수 통계 데이터를 뽑아내는 것을 많이 사용할 것 같습니다.</p>
<h3 id="7-aggs-stats-group-by-team-팀으로-묶어서-점수-톰계">7. AGGS (stats group by team) 팀으로 묶어서 점수 톰계</h3>
<p>각 팀에 대한 데이터를 보여주기 위한 예제입니다. 
stats_by_team.json</p>
<pre><code>{
    &quot;size&quot; : 0,
    &quot;aggs&quot; : {
        &quot;team_stats&quot; : {
            &quot;terms&quot; : {
                &quot;field&quot; : &quot;team&quot;
            },
            &quot;aggs&quot; : {
                &quot;stats_score&quot; : {
                    &quot;stats&quot; : {
                        &quot;field&quot; : &quot;points&quot;
                    }
                }
            }
        }
    }
}</code></pre><h4 id="aggs-group-by-team">AGGS (group by team)</h4>
<pre><code>curl -XGET localhost:9200/_search?pretty --data-binary @stats_by_team.json -H &#39;Content-Type:application/json&#39;</code></pre><p><img src="https://velog.velcdn.com/images/jay_be/post/16b303c9-89e6-4894-9a87-ae05d13e2c7e/image.png" alt=""></p>
<h3 id="간단한-예제로-엘라스틱-서치를-찍먹해봤습니다-다음-게시글에서는-키바나를-찍먹해보도록-하겠습니다">간단한 예제로 엘라스틱 서치를 찍먹해봤습니다. 다음 게시글에서는 키바나를 찍먹해보도록 하겠습니다.</h3>
<h1 id="어우-맛있따🔥🔥🔥🔥">어우 맛있따!🔥🔥🔥🔥</h1>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Deep in java] 자바 지식 - 3]]></title>
            <link>https://velog.io/@jay_be/Deep-in-java-%EC%9E%90%EB%B0%94-%EC%A7%80%EC%8B%9D-3</link>
            <guid>https://velog.io/@jay_be/Deep-in-java-%EC%9E%90%EB%B0%94-%EC%A7%80%EC%8B%9D-3</guid>
            <pubDate>Tue, 21 Nov 2023 05:30:47 GMT</pubDate>
            <description><![CDATA[<p>사내에서 진행하는 자바 스터디 3주차 주제입니다.</p>
<ul>
<li>쓰레드</li>
<li>리플렉션</li>
<li>직렬화, 역직렬화</li>
<li>Java 동기 vs 비동기</li>
<li>클래스, 객체, 인스턴스</li>
</ul>
<h1 id="쓰레드">쓰레드</h1>
<h2 id="프로그램--프로세스--쓰레드">프로그램 &amp; 프로세스 &amp; 쓰레드</h2>
<p><a href="https://www.youtube.com/watch?app=desktop&amp;v=4rLW7zg21gI"><img src="https://velog.velcdn.com/images/jay_be/post/1c4be790-5841-41f7-be7f-03e1e8328f02/image.png" alt="&#39;process vs threads&#39;"></a><a href="https://www.youtube.com/watch?app=desktop&amp;v=4rLW7zg21gI">https://www.youtube.com/watch?app=desktop&amp;v=4rLW7zg21gI</a></p>
<p>프로그램은 실행 파일입니다. 여기에는 디스크에 파일로 저장되는 코드 또는 프로세서 명령 세트가 포함되어 있습니다. 프로그램의 코드가 메모리에 로드되고 프로세서에 의해 실행되면 프로세스가 됩니다. 활성 프로세스(실행중인 프로그램)에는 프로그램을 실행하는데 필요한 리소스도 포함됩니다. 
<img src="https://velog.velcdn.com/images/jay_be/post/8ebb5a77-bb91-4a16-9a71-5bf86c1b3048/image.png" alt=""></p>
<p>이러한 리소스들은 운영체제에 의해 관리됩니다. 예를 들어 프로세서 레지스터, 프로그램 카운터, 스택 포인터, 힙 및 스택용 프로세스에 할당된 메모리 페이지 등이 있습니다.
<img src="https://velog.velcdn.com/images/jay_be/post/0c9e64a2-1c52-429c-a3b7-5f7b6cd80859/image.png" alt=""></p>
<p>각 프로세스에는 자체 메모리 주소 공간이 있습니다. 한 프로세스는 다른 프로세스의 메모리 공간을 손상시킬 수 없습니다. 즉 한 프로세스가 오작동 해도, 다른 프로세스는 문제없이 계속 실행됩니다.
<img src="https://velog.velcdn.com/images/jay_be/post/3ae8720b-6c0e-4f42-86bc-197422b716d2/image.png" alt=""></p>
<p>Chrome은 프로세스의 대표적인 예시입니다. 각 탭을 자체 프로세스에서 실행하며 프로세스의 격리를 활용하는 것으로 유명합니다. 버그나 악의적인 공격으로 인해 탭 하나가 오작동하더라도 다른 탭은 영향을 받지 않습니다.</p>
<p><img src="https://velog.velcdn.com/images/jay_be/post/8bc0f3eb-3e04-46f5-a779-c571adca0bf7/image.png" alt=""></p>
<p>스레드는 프로세스 내 실행 단위입니다. 프로세스에는 하나 이상의 스레드가 있습니다. 이를 메인 스레드라고 합니다. 프로세스에는 스레드가 많이 존재할 수 있습니다. 
각 스레드에는 자체 스택이 있습니다. 앞에서 레지스터, 프로그램 카운터, 스택 포인터가 프로세스의 일부라고 하였지만, 이것은 스레드에 속한다고 말하는 것이 더 정확합니다. 
<img src="https://velog.velcdn.com/images/jay_be/post/d7498ab2-0aa0-4de8-8d58-6a482e12c7df/image.png" alt=""></p>
<p>프로세스 내의 스레드는 메모리 주소 공간을 공유합니다. 해당 공유 메모리 공간을 사용하여 스레드 간에 통신이 가능합니다. 공유된 공간을 사용하기 때문에 하나의 잘못된 스레드로 인해 전체 프로세스가 중단될 수도 있습니다.
<img src="https://velog.velcdn.com/images/jay_be/post/efe8b4f0-ced4-40fa-98d5-c522b856e3d6/image.png" alt=""></p>
<h3 id="운영체제는-cpu에서-스레드나-프로세스를-어떻게-실행할까">운영체제는 CPU에서 스레드나 프로세스를 어떻게 실행할까?</h3>
<p>컨텍스트 전환을 통해 처리됩니다. 컨텍스트 전환 후에 한 프로세스가 CPU에서 전환되며 다른 프로세스가 실행될 수 있습니다. 운영체제는 현재 실행중인 프로세스의 상태를 저장하므로 프로세스를 복원하고 나중에 실행을 재개할 수 있습니다. 그런 다음 이전에 저장된 다른 프로세스의 상태를 복원하고, 해당 프로세스의 실행을 재개합니다. 
<img src="https://velog.velcdn.com/images/jay_be/post/c8acca93-f0f2-4cf4-a50a-266c53bcfdd0/image.png" alt=""></p>
<p>컨텍스트 전환은 많은 비용이 듭니다. 여기에는 레지스터 저장, 로드, 메모리 페이지 전환, 다양한 커널 데이터 구조 업데이트가 포함됩니다. 스레드 간 실행을 전환할 떄에도 컨텍스트 전환이 필요합니다.
<img src="https://velog.velcdn.com/images/jay_be/post/4077d473-a4ae-40c0-81af-d67603342cad/image.png" alt=""></p>
<p>일반적으로 프로세스 간 컨텍스트 전환보다 스레드 간에 컨텍스트 전환하는 것이 더 빠릅니다. 추적해야하는 상태가 적고, 더 중요한 것은 스레드가 동일한 메모리 주소 공간을 공유하기 때문에 컨텍스트 전환 중 가장 비용이 많이 드는 작업 중 하나인 가상 메모리 페이지 전환할 필요가 없다는 것입니다. 컨텍스트 전환은 비용이 많이 들기 때문에 이를 최소화 하기 위한 다른 메커니즘이 있습니다. 이 부분에 관심이 있으신 분들은 더 찾아보시면 될 것 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/jay_be/post/52f27e3d-f126-4428-9376-635b909c0043/image.png" alt=""></p>
<h2 id="용어-정리">용어 정리</h2>
<h3 id="프로세서">프로세서</h3>
<p>중앙처리장치(CPU)라고도 하는 프로세서는 기본적인 산술, 논리, 제어, 입출력(I/O) 연산을 수행하여 컴퓨터 프로그램의 명령을 수행하는 컴퓨터 핵심 구성요소입니다. 흔히 컴퓨터의 두뇌로 간주됩니다.</p>
<h3 id="프로세서-레지스터">프로세서 레지스터</h3>
<p>프로세서 레지스터는 프로그램 실행 중에 데이터를 일시적으로 저장하는 CPU내의 작고 빠른 저장 위치입니다. CPU 작업에 대한 중간 결과와 피연산자를 저장하는데 사용됩니다. 레지스터는 데이터에 대한 빠른 액세스를 제공하여 계산 속도를 향상시키는데 도움이 됩니다.</p>
<h3 id="프로그램-카운터">프로그램 카운터</h3>
<p>프로그램 카운터는 다음에 실행될 명령어의 메모리 주소를 추적하는 CPU의 레지스터입니다. 프로그램 실행 중에 PC는 순차적으로 다음 명령어를 가르키도록 증가됩니다.</p>
<h3 id="스택-포인터">스택 포인터</h3>
<p>스택 포인터는 컴퓨터 메모리의 스택 상단을 가르키는 레지스터입니다. 스택은 함수 호출 정보, 지역 변수, 반환 주소 등 임시 데이터를 저장하는데 사용되는 메모리 영역입니다. 스택 포인터는 데이터가 스택에 푸시되거나 스택에서 팝될 때 조정됩니다.</p>
<h3 id="가상-메모리">가상 메모리</h3>
<p>크기가 다른 물리 메모리에서도 일관되게 프로세스를 실행할 수 있도록 도와주는 것이 가상 메모리 기술입니다. 물리 메모리 크기와 상관없이 메모리를 이용할 수 있도록 지원해주는 기술입니다.
3GB인 프로세스를 2GB의 메모리에도 실행 가능하게 하는 기술입니다.</p>
<h3 id="프로세스">프로세스</h3>
<p>프로세스는 CPU에 의해 메모리에 올려져 실행중인 프로그램을 말하며, 자신만의 메모리 공간을 포함한 독립적인 실행 환경을 가지고 있습니다. 자바 JVM은 주로 하나의 프로세스로 실행되며, 여러 작업을 수행하기 위해서 멀티 스레드를 지원하고 있습니다.</p>
<h3 id="스레드">스레드</h3>
<p>스레드는 프로세스 안에서 실질적인 작업을 수행하는 단위를 말하며, 자바에서 JVM에 의해 관리됩니다. 프로세스는 적어도 한개 이상의 스레드가 있으며, Main 스레드 하나로 시작하여 스레드를 추가 생성하게 되면 멀티 스레드 환경이 됩니다. 이러한 스레드들은 프로세스의 리소스를 공유하기 때문에 효율적이긴 하지만 잠재적인 문제점에 노출될 수 있습니다.</p>
<h2 id="자바-프로세스-vs-자바-쓰레드">자바 프로세스 vs 자바 쓰레드</h2>
<h3 id="자바-프로세스">자바 프로세스</h3>
<p>자바에서 프로세스란 실행중인 프로그램입니다. JVM은 OS로 부터 실행에 필요한 자원(메모리)를 할당 받아 프로세스를 실행시킵니다. 프로세스는 프로그램을 수행하는데 필요한 데이터, 메모리 등의 자원 그리고 스레드로 구성되어 있으며 프로세스의 자원을 이용하여 실제로 작업을 수행하는 것이 스레드입니다.</p>
<h3 id="자바-스레드">자바 스레드</h3>
<p>자바는 다중 스레드 프로그래밍 언어입니다. 단인 프로그램 내에서 여러 스레드의 동시 실행을 지원합니다. 이 기능을 통해 개발자는 여러 작업을 동시에 수행하여 성능과 응답성을 향상 시킬 수 있는 프로그램을 작성할 수 있습니다.</p>
<h3 id="스레드-생성">스레드 생성</h3>
<ol>
<li>Thread 클래스 상속</li>
<li>Runnable 인터페이스 구현</li>
</ol>
<p>자바는 다중 상속을 허용하지 않기 때문에 Thread 클래스를 상속 받게 되면 다른 클래스를 상속 받을 수 없기 떄문에 Runnable 인터페이스를 구현하는 것을 보통으로 합니다.</p>
<p>스레드를 생성하고 동작시킴에 있어서 알아두어야 할 점은 사용자가 스레드 객체를 생성하고 실행 요청을 하더라도 스레드가 실행되는 것은 전적으로 JVM에 의한 스케줄러를 따른다는 것입니다.</p>
<pre><code class="language-java">public class MyThread implements Runnable{
    @Override
    public void run() {
        System.out.println(&quot;myThread start&quot;);
        System.out.println(Thread.currentThread().getName());
        System.out.println(&quot;myThread finish&quot;);
    }
}</code></pre>
<p>Runnable 인터페이스를 구현한 클래스입니다.</p>
<pre><code class="language-java">public class ThreadTest {
    public static void main(String[] args) {
        System.out.println(&quot;main start&quot;);
        System.out.println(Thread.currentThread().getName());

        Runnable runnable = new MyThread();
        Thread thread = new Thread(runnable);
        thread.start();

        System.out.println(&quot;main finish&quot;);
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/jay_be/post/bb471132-cd56-434c-8190-da4981e1cc06/image.png" alt="">
결과적으로 main 함수와 run 함수는 다른 스레드를 사용하는 것을 알 수 있습니다. 실무에서 이렇게 스레드를 생성하여 코드를 작성하는 일은 드물었을 것이라고 생각이 듭니다.</p>
<p>최근에 인프런 강의 <a href="https://www.inflearn.com/course/%EC%84%A0%EC%B0%A9%EC%88%9C-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%8B%A4%EC%8A%B5">&quot;실습으로 배우는 선착순 이벤트 시스템&quot;</a> 를 학습하면서 테스트 코드에 멀티스레드를 활용하여 1000명이 멀티스레드를 사용하여 쿠폰 발급을 요청한다는 가정으로 코드를 작성해보았습니다.</p>
<pre><code class="language-java"> @Test
    void 여러명_응모() throws InterruptedException {
        int threadCount = 1000; // thread 수

        ExecutorService executorService = Executors.newFixedThreadPool(32); // thread pool 32개 생성

        CountDownLatch latch = new CountDownLatch(threadCount); // 모든 작업자 스레드와 메인스레드를 동기화 하는 대 사용


        for (int i = 0; i &lt; threadCount; i++) {
            long userId = i;
            executorService.submit(() -&gt; {
                try {
                    applyService.apply(userId); // 쿠폰 발급 로직
                } finally {
                    latch.countDown();
                }
            });
        }

        latch.await(); // 래치 수가 0이 될 때까지 차단

        long count = couponRepository.count();

        assertThat(count).isEqualTo(100);
    }</code></pre>
<p>간단하게 코드 설명을 해보자면 32개의 스레드 풀을 생성하고 생성된 스레드로 1000개의 쿠폰 생성 요청을 합니다. CountDownLatch를 사용하여 작업 스레드들이 작업을 완료할 때까지 기다리게 하여 메인 스레드와 작업자 스레드를 동기화 한 후 결과를 테스트하는 코드입니다.</p>
<p>멀티 스레드를 개발자가 직접 컨트롤 하는 것은 굉장히 어렵습니다. 현재 이 코드에도 많은 문제가 발생할 수 있기 때문에 동시성에 대한 것은 웹 개발자로서 깊게 공부해야하는 분야라고 생각이 듭니다.</p>
<h3 id="추가-키워드">추가 키워드</h3>
<ul>
<li>레이스 컨디션</li>
<li>레디스</li>
<li>카프카</li>
<li>DB Lock</li>
</ul>
<h1 id="리플렉션">리플렉션</h1>
<h2 id="리플렉션이란-">리플렉션이란 ?</h2>
<p>런타임에 클래스와 인터페이스 등을 검사하고 조작할 수 있는 기능입니다.
런타임 시점에 어떻게 클래스와 인터페이스를 검사하고 조작할 수 있을까요?
이것을 알기 위해서는 JVM 동작 방식을 이해해야 합니다.
<img src="https://velog.velcdn.com/images/jay_be/post/b908a714-34d0-457a-bcf7-754926a60c9b/image.png" alt="">
JVM의 클래스 로더는 Runtime Data Area에 올려주게 됩니다. 그중 Method Area에 클래스 수준의 메타 정보를 올려주게 됩니다. 리플렉션은 그 Method Area에 접근합니다. </p>
<h2 id="class-클래스">Class 클래스</h2>
<p>클래스는 메서드 영역의 클래스 및 인터페이스 정보를 가져오는 클래스입니다.</p>
<pre><code class="language-java">public class Main {

    public static void main(String[] args) throws ClassNotFoundException {

        final Class&lt;Main&gt; class1 = Main.class;

        final Main mainObj = new Main();
        Class&lt;? extends Main&gt; class2 = mainObj.getClass();

        final Class&lt;?&gt; class3 = Class.forName(&quot;클래스 풀 패키지 경로&quot;);
    }
}</code></pre>
<h2 id="리플렉션으로-가져올-수-있는-정보">리플렉션으로 가져올 수 있는 정보</h2>
<ol>
<li>필드</li>
<li>메서드</li>
<li>생성자</li>
<li>Enum</li>
<li>Annotation</li>
<li>배열</li>
<li>부모 클래스와 인터페이스 </li>
</ol>
<p>등이 있습니다.</p>
<h2 id="사용-예시">사용 예시</h2>
<ul>
<li>DI</li>
<li>Proxy</li>
<li>ModelMapper</li>
<li>MVC : view에서 넘어오는 데이터를 객체에 바인딩 할 때 사용</li>
<li>Hibernate : @Entitiy 클래스에 setter가 없으면 해당 필드에 값을 바로 주입</li>
<li>JUnit ReflectionUtils라는 클래스를 내부적으로 정의하여 사용</li>
<li>Eclipse : 메소드 자동완성</li>
<li>Tomcat : web.xml 파일에 있는 클래스 이름을 가지고 웹의 요청을 처리할 서블릿</li>
</ul>
<h2 id="리플렉션을-언제-사용해야-할까">리플렉션을 언제 사용해야 할까?</h2>
<p>런타임에 지금 실행되고 있는 클래스를 가져와서 실행해야 하는 경우에 사용을 합니다.</p>
<h2 id="리플렉션-단점">리플렉션 단점</h2>
<ul>
<li>보안 취약점</li>
<li>코드 복잡도 증가</li>
<li>성능 저하</li>
<li>최적화 방해</li>
<li>타입 안정성 X</li>
<li>호환성</li>
</ul>
<h2 id="왜-사용할까">왜 사용할까?</h2>
<p>자바는 정적인 언어라 동적인 문제를 해결하기 위해 리플렉션을 사용합니다. 일반적인 사용 사례 중 하나는 런타임까지 클래스 구조를 알 수 없는 프레임워크, 라이브러리 또는 시나리오를 처리할 때 사용됩니다.</p>
<p>사실 리플렉션은 일반 개발자들은 되도록이면 사용을 지양해야 합니다. 하지만 필요한 경우에 적절하게 사용하는 것은 강력한 도구가 될 수 있습니다.</p>
<p>프레임워크 및 라이브러리 제공자들은 런타임에 사용자와 상호작용하면서 기능을 제공하고 싶은 경우에 한정적으로 사용합니다.</p>
<h1 id="직렬화-역직렬화">직렬화, 역직렬화</h1>
<h2 id="자바-직렬화란">자바 직렬화란?</h2>
<ul>
<li>자바 직렬화란 자바 시스템 내부에서 사용되는 객체 또는 데이터를 외부의 자바 시스템에도 사용할 수 있도록 바이트(byte) 형태로 데이터를 변환하는 기술과 바이트로 변환된 데이터를 다시 객체로 변환하는 기술(역직렬화)를 말합니다.</li>
<li>시스템적으로 설명하자면 JVM의 메모리의 상주되어 있는 객체 데이터를 바이트 형태로 변환하는 기술과 직렬화된 바이트 형태의 데이터를 객체로 변환하여 JVM으로 상주시키는 형태를 말합니다.</li>
</ul>
<h2 id="자바-직렬화-조건">자바 직렬화 조건</h2>
<p>자바 기본타입과 java.io.Serializable 인터페이스를 상속 받은 객체는 직렬화를 할 수 있습니다.</p>
<pre><code class="language-java">public class User implements Serializable {
    private final String name;
    private final int age;

    public User(final String name, final int age) {
        this.name = name;
        this.age = age;
    }
}</code></pre>
<h2 id="직렬화-방법">직렬화 방법</h2>
<p>자바 직렬화 방법은 java.io.ObjectOutputStream 객체를 사용합니다.</p>
<pre><code class="language-java">public class SerializeTest {
    public static void main(String[] args) throws IOException {

        User user = new User(&quot;jjj&quot;, 10);
        byte[] serializeUser;

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);

        oos.writeObject(user);

        serializeUser = baos.toByteArray();

        System.out.println(Arrays.toString(serializeUser));
//           [-84, -19, 0, 5, 115, 114, 0, 14, 115, 101, 114, 105, 97,
//        108, 105, 122, 101, 46, 85, 115, 101, 114, -33, 8, 17, 81, -124, 110,
//        -92, -7, 2, 0, 2, 73, 0, 3, 97, 103, 101, 76, 0, 4, 110, 97, 109, 101,
//        116, 0, 18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 
//          110, 103, 59, 120, 112, 0, 0, 0, 10, 116, 0, 3, 106, 106, 106]

        oos.close();
        baos.close();
    }
}</code></pre>
<p>만약 User 클래스에 참조형 필드가 추가된다면 해당 클래스 또한 Serializable를 구현해야 에러가 발생하지 않습니다.
<img src="https://velog.velcdn.com/images/jay_be/post/85e24d5e-ca80-4f77-9b9a-7ddacc6d391f/image.png" alt=""></p>
<h2 id="역직렬화-조건">역직렬화 조건</h2>
<ul>
<li><p>직렬화 대상이 된 객체의 클래스가 class path에 존재해야하고 import 되어야 합니다.</p>
</li>
<li><p>자바 직렬화 대상 객체는 동일한 serialVersionUID를 가지고 있어야 합니다. </p>
<pre><code class="language-java">public class TableName implements Serializable {

  /**
   * This field was generated by Apache iBATIS ibator. This field corresponds to the database column
   * @ibatorgenerated  Mon Nov 20 12:31:29 KST 2023
   */
  private String name;
  /**
   * This field was generated by Apache iBATIS ibator. This field corresponds to the database column 
   * @ibatorgenerated  Mon Nov 20 12:31:29 KST 2023
   */
  private String org;

  /**
   * This field was generated by Apache iBATIS ibator. This field corresponds to the database table 
   * @ibatorgenerated  Mon Nov 20 12:31:29 KST 2023
   */
  private static final long serialVersionUID = 1L;
// getter setter 생략   
}    </code></pre>
<p>현재 실무에서 사용중인 Apache iBATIS ibator의 결과 모델을 가져와봤습니다.</p>
</li>
</ul>
<pre><code class="language-java">    /**
     * This field was generated by Apache iBATIS ibator. This field corresponds to the database table 
     * @ibatorgenerated  Mon Nov 20 12:31:29 KST 2023
     */
    private static final long serialVersionUID = 1L;</code></pre>
<p>Apache iBATIS ibator는 serialVersionUID를 1L 고정값을 사용하고 있습니다. 그 이유는 serialVersionUID를 따로 설정해주지 않으면 클래스의 기본 해쉬값을 사용하기 때문에 값이 변경됩니다. </p>
<p>자바 직렬화는 상당히 타입의 변화에 엄격하기 때문에 필드의 변경이 일어나면 serialVersionUID는 변경되므로 타입 에러가 발생할 수 있습니다.</p>
<ul>
<li>특별한 문제가 없으면 자바 직렬화 버전 serialVersionUID는 개발자가 직접 관리해야 합니다.</li>
<li>serialVersionUID가 동일하다면 멤버 변수 및 메서드 추가는 크게 문제가 없습니다. 멤버 변수 제거 및 이름 변경은 오류가 발생하지 않지만 데이터는 누락됩니다.</li>
</ul>
<h2 id="역직렬화-예제">역직렬화 예제</h2>
<pre><code class="language-java">public class SerializeTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {

        User user = new User(&quot;jjj&quot;, 10);
        byte[] serializeUser = serialize(user);

        ByteArrayInputStream bais = new ByteArrayInputStream(serializeUser);
        ObjectInputStream ois = new ObjectInputStream(bais);

        User newUser = (User) ois.readObject();
        System.out.println(newUser.toString());
        System.out.println(user == newUser);
        ois.close();
        bais.close();
    }

    private static byte[] serialize(User user) throws IOException{
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);

        oos.writeObject(user);

        return baos.toByteArray();
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/jay_be/post/c41a27c6-f073-47d2-ac10-6afcc01db520/image.png" alt=""></p>
<h2 id="왜-자바-직렬화가-있을까">왜 자바 직렬화가 있을까?</h2>
<p>직렬화에는 많은 종류가 있습니다.</p>
<h4 id="문자열-직렬화">문자열 직렬화</h4>
<ul>
<li>CSV</li>
<li>JSON</li>
<li>XML 등</li>
</ul>
<h4 id="이진-직렬화">이진 직렬화</h4>
<ul>
<li>Protocol Buffer</li>
<li>Apache Avro 등</li>
</ul>
<p>위의 직렬화들은 시스템의 고유 특성과 상관 없는 대부분의 시스템에서 데이터 교환 시 많이 사용됩니다. 자바 직렬화는 &quot;자바 시스템 간의 데이터 교환을 위해 존재한다&quot; 라고 생각하면 됩니다.
<img src="https://velog.velcdn.com/images/jay_be/post/8d8ea865-ade5-4aad-85a8-a95e6b6930bf/image.png" alt=""></p>
<h2 id="자바-직렬화-장단점">자바 직렬화 장단점</h2>
<p>자바 직렬화의 장점은 아래와 같습니다.</p>
<ol>
<li>플랫폼 독립성 : Java를 지원하는 모든 플랫폼에서 역직렬화 가능</li>
<li>사용 편의성 : 자바에서 직렬화는 사용하기 쉽습니다.
등등</li>
</ol>
<p>자바 직렬화의 단점은 상당히 많습니다.</p>
<ul>
<li>보안</li>
<li>유지보수성</li>
<li>테스트</li>
<li>싱글톤 문제
등등</li>
</ul>
<h2 id="결론">결론</h2>
<p>필요에 따라 잘 사용하면 되지만, 커스텀 직렬화, 직렬화 프록시, 역직렬화 필터링, 직렬 버전관리 등등 해야할 일이 굉장히 많기 때문에 장단점을 잘 생각하여 사용해야 합니다.</p>
<h1 id="java-동기-vs-비동기">Java 동기 vs 비동기</h1>
<h2 id="동기">동기</h2>
<p>작업이 순차적인 방식으로 하나씩 실행됩니다. 각 작업은 시작되기 전에 이전 작업이 완료될 때까지 기다려야 합니다. 자바는 기본적으로 동기식 호출을 사용합니다.</p>
<pre><code class="language-java">public class Test {

    public static void main(String[] args) {
        int result1 = add(1, 3);
        System.out.println(result1); // 4

        int result2 = add(result1, 3);
        System.out.println(result2); // 7
    }

    private static int add(int a, int b) {
        return a + b;
    }
}</code></pre>
<p>이 코드는 실행시켜 보지 않더라도 결과값을 예상할 수 있습니다. 바로 코드가 순차적으로 진행되기 때문입니다. </p>
<h2 id="비동기">비동기</h2>
<p>비동기 작업에서는 작업이 기본 프로그램 흐름과 독립적으로 실행될 수 있습니다. 한 작업을 시작하기 전에 다른 작업이 완료될 때까지 기다릴 필요가 없습니다. 비동기 작업은 성능과 응답성을 향상하기 위해서 자주 사용됩니다.</p>
<pre><code class="language-java">public class Test {

    public static void main(String[] args) {

        ExecutorService executorService = Executors.newFixedThreadPool(32);

        for (int i = 0; i &lt; 10; i++) {
            final int taskId = i;
            executorService.submit(() -&gt; {
                System.out.println(taskId);
            });
        }
    }
}</code></pre>
<p>이 코드의 결과를 예상할 수 있을까요? 없습니다. 해당 코드의 결과는 순서 보장도 안되고, 실행 할 때마다 달라지는 것을 알 수 있습니다.
<img src="https://velog.velcdn.com/images/jay_be/post/baf8a306-3a03-4d36-ac7f-4942f19fcb01/image.png" alt=""></p>
<p>실무에서 이렇게 스레드를 생성해서 로직을 수행하는 일은 많지 않을 것이라고 생각이 듭니다. 그렇다면 실무에서는 자바를 어떻게 비동기로 사용하는 경우가 많을까요?</p>
<ul>
<li>파일 I/O :Java New I/O API 사용시 메인 스레드를 차단하지 않고 파일 작업을 수행 할 수 있습니다. (스프링에서 제공하는 Webflux와 Netty 서버는 자바의 NIO  기반으로 만들어져 있습니다.)</li>
<li>네트워크 통신 : HTTP 요청 만들기 또는 소켓 처리와 같은 네트워킹 작업은 비동기 프로그램의 이점을 누릴 수 있습니다. Netty와 같은 라이브러리는 확장 가능하고 성능이 뛰어난 네트워크 어플리케이션을 구축하기 위한 네트워킹 기능을 제공합니다.</li>
<li>메시징 프로그램 : JMS(Java Message Service)와 같은 비동기 메시징 시스템은 메시지를 비동기적으로 보내고 받는 작업을 포함합니다.</li>
<li>스케줄러 및 타이머 : Java ScheduledExecutorService를 사용하면 작업이 주기적으로 또는 지정된 지연 후에 실행되도록 예약할 수 있습니다.</li>
<li>미들웨어 : 미들웨어 또는 타사 서비스와 통합하는 경우 기본 애플리케이션 스레드가 차단하지 않도록 비동기 통신을 사용합니다. 메시지큐, 기타 분산 시스템 등이 포함됩니다.</li>
</ul>
<h2 id="정리">정리</h2>
<p>기본적으로 웹 개발을 하다보면 동기적으로 코드를 작성하게 됩니다. 상황에 맞게 비동기 코드를 작성한다면 성능적 이점을 가져올 수 있습니다. 추후에 webflex, Netty 등을 공부하고 싶어지네요.</p>
<h1 id="클래스-객체">클래스, 객체</h1>
<h2 id="클래스">클래스</h2>
<p>객체 생성을 위한 설계도, 템플릿 입니다. 생성될 객체를 특성화하는 일련의 속성, 메서드를 정의합니다.</p>
<h2 id="객체">객체</h2>
<p>객체는 클래스의 인스턴스입니다. 클래스가 정의한 것들을 실제로 구현한 것입니다. 동일한 하나의 클래스로 여러 개의 객체를 만들 수 있습니다.</p>
<p>클래스를 객체로 만드는 것을 &quot;인스턴스화 한다&quot;라고 합니다.</p>
<h2 id="메모리적-관점에서의-클래스-객체">메모리적 관점에서의 클래스, 객체</h2>
<pre><code class="language-java">public class Main {
    public static void main(String[] args) {
        Car myCar = new Car(&quot;Toyota Camry&quot;, 2022);
        Car anotherCar = new Car(&quot;Honda Accord&quot;, 2021);

        // ...
    }
}</code></pre>
<ol>
<li>Car 클래스의 메타 데이터 정보는 프로그램이 시작될 때 Calss Loader에 의해 JVM 메모리 (메서드 영역)에 올라가게 됩니다.</li>
<li>myCar, anotherCar 객체를 생성하면 메모리가 힙 영역에 동적으로 할당되게 됩니다.</li>
</ol>
<hr>
<p>참조 블로그
<a href="https://kadosholy.tistory.com/121">https://kadosholy.tistory.com/121</a>
<a href="https://velog.io/@p1atina/%EC%9E%90%EB%B0%94%EC%9D%98-%EC%A0%95%EC%84%9D-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%99%80-%EC%93%B0%EB%A0%88%EB%93%9C">https://velog.io/@p1atina/%EC%9E%90%EB%B0%94%EC%9D%98-%EC%A0%95%EC%84%9D-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%99%80-%EC%93%B0%EB%A0%88%EB%93%9C</a>
<a href="https://kadosholy.tistory.com/121">https://kadosholy.tistory.com/121</a>
<a href="https://techblog.woowahan.com/2550/">https://techblog.woowahan.com/2550/</a>
<a href="https://techblog.woowahan.com/2551/">https://techblog.woowahan.com/2551/</a></p>
<p>참고 영상
<a href="https://www.youtube.com/watch?app=desktop&amp;v=4rLW7zg21gI">https://www.youtube.com/watch?app=desktop&amp;v=4rLW7zg21gI</a>
<a href="https://www.youtube.com/watch?v=RZB7_6sAtC4">https://www.youtube.com/watch?v=RZB7_6sAtC4</a>
<a href="https://www.youtube.com/watch?v=3iypR-1Glm0&amp;t=653s">https://www.youtube.com/watch?v=3iypR-1Glm0&amp;t=653s</a></p>
]]></description>
        </item>
    </channel>
</rss>