<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Dev</title>
        <link>https://velog.io/</link>
        <description>성장하는 개발자가 되고싶어요</description>
        <lastBuildDate>Sat, 04 May 2024 08:58:34 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. Dev. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/youngmin-mo" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[취업 준비 관련 Tip]]></title>
            <link>https://velog.io/@youngmin-mo/%EC%B7%A8%EC%97%85-%EC%A4%80%EB%B9%84-%EA%B4%80%EB%A0%A8-Tip</link>
            <guid>https://velog.io/@youngmin-mo/%EC%B7%A8%EC%97%85-%EC%A4%80%EB%B9%84-%EA%B4%80%EB%A0%A8-Tip</guid>
            <pubDate>Sat, 04 May 2024 08:58:34 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>본 글은 취업 준비 과정에서 <strong>개인적인 팁</strong>을 정리한 포스트다.</p>
</blockquote>
<h2 id="1-자기-소개서">1. 자기 소개서</h2>
<blockquote>
<p>나는 <strong>강점A, 강점B …</strong> 를 보유한 지원자다. 강점의 근거는 나의 <strong>경험A, 경험B …</strong> 를 통해 습득했다. 나의 강점A, 강점B … 를 너희 회사 <strong>업무A, 업무B …</strong> 에 기여하기 위해 지원했다.</p>
</blockquote>
<ul>
<li>지원자 입장에서는 나는 &#39;책임감&#39;도 있고, &#39;협업&#39;도 잘하고, &#39;스피드&#39;하게 일 처리도 하고 등등 강점을 말한다. 하지만 누구나 강점이 있다고 <strong>말은</strong> 할수 있다. 강점의 <strong>근거</strong>를 회사측에 <strong>설득</strong>해야한다. 제일 좋은 설득 방법은 <strong>경험</strong>이다.</li>
<li>회사는 자선단체가 아니다. 회사는 사람을 뽑을 때 이 사람이 우리 팀 문화와 맞나? 우리 일은 잘할 수 있나? 등을 검토한다. 궁극적으로 이 사람이 회사의 <strong>업무에 기여할 수 있는지를</strong> 알고싶어한다.</li>
<li>그렇다고 <strong>업무와 무관한 강점으로</strong> 회사를 설득하기엔 매칭이 안된다. 회사에서 지원하는 업무를 파악하고, <strong>이 업무에 필요한 강점</strong>들은 뭐가 있는지 생각해보자. &#39;책임감&#39;, &#39;업무에 대한 열정&#39;, &#39;협업&#39;, &#39;희생정신&#39; 등등 여러가지 카테고리가 있다.</li>
<li>경험을 설명할 때 솔직하게 말할 수도 있고 어느정도 부풀려서 말하기도 한다. 해당 경험을 어느정도 부풀려서 설명하는건 괜찮다고 생각한다. <strong>다만 부풀려서 말할거면 해당 경험을 논리적으로, 구체적으로 정리해야한다.</strong> 취준의 끝은 자기소개서만 합격하면 되는 것은 아니다. <strong>자기소개서에 작성한건 면접때 확인할 가능성이 매우 크다.</strong> 따라서 자기소개서=면접이라고 생각하여, 면접 때 대답할 정도의 부풀림을 꼭 기억하자.</li>
</ul>
<h2 id="2-면접">2. 면접</h2>
<blockquote>
<p>자기소개서를 합격하고 코딩테스트/인적성 등 검사까지 합격하면 면접을 보게된다. 크게 &#39;팩트 체크&#39;, &#39;업무/전공 지식&#39;, &#39;인성 질문&#39;, &#39;회사 로열티&#39;를 검증한다.</p>
</blockquote>
<h3 id="1-자기소개서-팩트체크">[1] 자기소개서 팩트체크</h3>
<ul>
<li>자기소개서 및 기타 인적사항에 적은 내용이 진실인지 질문을 통해 파악한다.지원자가 적은 내용들이 진짜인가? 경험이 진짜인가?를 확인한다. 앞서 어느정도 경험을 부풀려도되지만 <strong>해당 경험을 논리적으로, 구체적으로 정리하라고 말했다.</strong> 팩트체크를 하기 때문이다.</li>
<li>내가 경험한 것도 말을 잘 못하면 혹은 앞뒤 다르게 말한다면 회사는 의심하기 시작한다. 이 사람 거짓말하는것 같은데? 라고 의심하기 시작하면 끝이 없다. 따라서 <strong>내가 실제 경험한 것, 그리고 어느정도 부풀려서 말한 것 모두 논리적으로 해당 상황을 꼭 정리하자.</strong> 실제 경험한건데 정리하지 않고 횡성수설해서 오해하면 너무 아깝다. 따라서 본인은 해당 상황을 <strong>시뮬레이션</strong> 돌리면서 정리했다. 해당 경험은 언제, 누구랑, 어디서, 어떻게, 그리고 어려웠던 일을 &#39;구체적&#39;으로 정리했다.</li>
</ul>
<h3 id="2-업무전공-지식">[2] 업무/전공 지식</h3>
<ul>
<li>지원 직무의 전공 기본기를 알고있나 체크한다. 혹은 진행한 프로젝트에 대해 전공 지식을 여쭤볼 수도 있다. 신입이라면 학교 프로젝트나, 전공지식을 여쭤볼 수 있고, 경력이라면 지난 회사에서 수행한 프로젝트를 여쭤볼 수 있다.</li>
<li>SW 개발 직무라면 보통 OS, Network, DataBase를 공통으로 확인했다. 이후 프로젝트에서 사용한 프레임워크, 언어 에 대한 기본 지식과 플로우를 구체적으로 물어봤다.</li>
</ul>
<h3 id="3-인성질문">[3] 인성질문</h3>
<ul>
<li>지원자가 우리 문화랑 잘 어울릴 수 있는지 물어본다. 인성질문에 대한 자료는 인터넷에 굉장히 많이 있다. 따라서 본인은 해당 질문 리스트를 쭉 적어보고 본인에 대한 생각을 하나하나 구체적으로 솔직하게 정리했다.</li>
<li>너무 잘 대답하려고 본인 fit에 맞지 않게 대답하다보면 뭔가 앞뒤가 다른 대답할 수도 있다. 물론 잘 대답하면 좋지만 본인은 인성질문에 있어서 모험은 하고 싶지 않아서 솔직하게 대답했다. 단, 너무 튀는 대답은 피했다.</li>
</ul>
<h3 id="4-회사-로열티">[4] 회사 로열티</h3>
<ul>
<li>회사/직무에 대한 관심도를 마지막으로 확인한다. 1~3번까지 이 사람의 능력치는 알겠는데 아무리 좋은 사람이어도 금방 나가면 어떡하지? 에 대한 내용을 검증한다.</li>
<li>참고로 지원자도 회사에 합격하기까지 정말 많은 리소스를 사용하지만 회사도 사람을 뽑는데 많은 비용(시간, 돈)이 든다. 따라서 회사도 사람을 뽑을 떄 금방 나갈 사람을 뽑고 싶어하진 않을거다.</li>
<li>회사에는 관심이 있는지, 직무에는 관심이 있는지를 확인한다.</li>
</ul>
<h2 id="3-개인적인-준비-방법">3. 개인적인 준비 방법</h2>
<ol>
<li>모든 경험을 정리한다.<ul>
<li>기간, 경험에 대한 핵심 키워드, 경험을 구체적으로 작성한다.</li>
<li>해당 경험을 A부터 Z까지 하나하나 곱씹어보며 시뮬레이션을 돌려본다.</li>
</ul>
</li>
<li>정리한 경험을 토대로 자기소개서를 작성한다.<ul>
<li>지원동기, 자기소개서, 어려웠던 프로젝트, 갈등있던 프로젝트 등 지원하고자하는 기업의 자소서 질문들을 미리 작성해본다.</li>
<li>공고가 뜨면 미리 작성한 자기소개서 기본 양식을 최대한 활용한다. 본인은 지원동기를 주로 수정하고 제출했다.</li>
<li>이 작업을 해두면 공고시즌이 몰릴 때 자소서에서 시간 뺏기는걸 최소화할 수 있다.</li>
</ul>
</li>
<li>면접 준비<ul>
<li>자기소개서에 쓴 내용들을 실제 면접관이라고 생각하고 물어볼 수 있는것들을 모두 시뮬레이션 돌려보면서 준비했다. 꼬리질문도 생각했다.</li>
<li>생각만 하는게 아니라, 실제 말을 해봐야한다. 본인은 긴장을 많이하는 성격이라 모의면접이나, 실제로 말하면서 준비했던게 도움됐다.</li>
<li>1분 자기소개서는 외우고, 모든 회사에서 공통으로 활용했다.</li>
<li>youtube에 모의 면접 관련해서 영상이 많은데 그걸 통해서 실제 면접본다고 생각하고 준비했다.</li>
</ul>
</li>
</ol>
<p>ㅎㅇㅌ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[서버는 얼마나 많은 요청을 처리할 수 있을까?]]></title>
            <link>https://velog.io/@youngmin-mo/%EC%9A%B0%EB%A6%AC%EC%9D%98-%EC%84%9C%EB%B2%84%EB%8A%94-%EC%96%BC%EB%A7%88%EB%82%98-%EC%9A%94%EC%B2%AD%EC%9D%84-%EC%B2%98%EB%A6%AC%ED%95%A0-%EC%88%98-%EC%9E%88%EC%A7%80</link>
            <guid>https://velog.io/@youngmin-mo/%EC%9A%B0%EB%A6%AC%EC%9D%98-%EC%84%9C%EB%B2%84%EB%8A%94-%EC%96%BC%EB%A7%88%EB%82%98-%EC%9A%94%EC%B2%AD%EC%9D%84-%EC%B2%98%EB%A6%AC%ED%95%A0-%EC%88%98-%EC%9E%88%EC%A7%80</guid>
            <pubDate>Sat, 13 Apr 2024 08:50:51 GMT</pubDate>
            <description><![CDATA[<h2 id="1-servlet-이란">1. Servlet 이란?</h2>
<ul>
<li><strong>Servlet</strong>이란?<ul>
<li>자바 기반의 <code>javax.servlet.Servlet</code> 인터페이스로, 사용자의 요청에 따라 동적인 데이터를 제공한다.</li>
<li>각 요청마다 프로세스가 아닌 쓰레드로 처리한다.</li>
<li>JVM에 의해 관리되기 때문에 Garbage Collection을 활용하여 Memory Leak에 대한 걱정을 덜 수 있다.</li>
</ul>
</li>
<li><strong>Web Container</strong>란?<ul>
<li>Servlet은 Web Container 안에 Load 되어있다.</li>
<li>Web Container는 Servlet의 생명주기를 관리한다. (Load -&gt; Init -&gt; Service -&gt; Destroy)</li>
<li>Web Container는 요청 온 URL을 올바른 서블릿에 맵핑해준다.</li>
</ul>
</li>
<li><strong>Web Container에서 Requset -&gt; Response 과정</strong><ol>
<li>HTTP 요청이 온다.</li>
<li>Web Container는 요청 URL을 처리하기 위해서 서블릿이 필요한지 확인한다. 서블릿이 필요 없다면 (ex 정적 데이터) 해당 데이터를 바로 respnose한다.</li>
<li>Web Container가 &#39;HttpServletRequest&#39;와 &#39;HttpServletResponse&#39;라는 객체를 생성한다.</li>
<li>Web Container가 요청 URL에 맵핑되는 Servlet을 찾고, 요청을 처리하기 위한 쓰레드를 생성(Thread Pool을 사용한다면 할당)한 후, 앞서 만든 request 와 response 객체를 Servlet Thread에 전달한다.</li>
<li>Web Container는 Servlet의 service() 메서드를 호출하고, 메서드 수행이 완료되면 컨테이너는 HTTP 응답을 반환한다. 이후 desctroy()를 수행하며 쓰레드를 반환한다</li>
</ol>
</li>
</ul>
<h2 id="2-tomcat">2. Tomcat</h2>
<blockquote>
<p><strong>Apache Tomcat</strong>은 Servlet Container를 포함하고 있는 대표적인 오픈소스 웹 서버이다.</p>
</blockquote>
<h3 id="1-thread를-얼마나-잡아야하지">[1] Thread를 얼마나 잡아야하지?</h3>
<ul>
<li>서버의 처리량이 좋다는건, 결국 동시에 얼마나 많은 요청을 처리하냐에 달려 있다. 톰캣은 요청을 처리할 때 Thread를 활용한다. 그렇다면 Thread가 많을수록 많은 요청을 처리할 수 있을까?</li>
<li>톰캣의 MaxThread 수는 200개가 기본 값이다. 1개의 요청 당 1개의 쓰레드를 할당한다고 가정할 때, 201개의 요청부터는 대기할텐데 무한정 스레드를 늘리면 안되나? 결론은 아니다. <strong>스레드를 무한정 늘리면 스레드에게 할당할 메모리가 작아지고, CPU Core 갯수에 따라 Context Switching이 빈번해질 수 있다.</strong><ul>
<li>스레드마다 각각의 &#39;Stack&#39; 영역을 가진다. 따라서 스레드를 무한정 늘리면 각각의 할당받는 메모리가 적을 수 밖에 없다.</li>
<li>Context Switching 작업은 JVM/OS 커널에 무거운 작업이다. 작업을 진행하다 메모리에 올리고 새롭게 작업을 대체해야하기 때문이다. 참고로 CPU Core가 8개면 최대 8개의 스레드만 동시에 처리할 수 있다.</li>
</ul>
</li>
<li>요청 Thread 뿐만 아니라 DB Connection Pool, 외부 API 호출(Ex RestTemplate) 등도 고려해야한다. 만약 DB Connection Pool에 가용할 스레드가 없다면 요청 온 Thread는 DB Connection Pool의 스레드를 사용할 수 있을때까지 대기하게 된다. 또한 동기방식으로 외부 서비스를 Call할 때, API의 응답속도도 느리고 timeout이 길다면 외부API의 응답이 올 때까지 스레드를 점유하게 된다. 결국 외부서비스를 호출할 때 사용하는 스레드 풀과 timeout 도 고려해야한다.</li>
</ul>
<blockquote>
<p>결국, 처리량을 높이기 위해선 Thread가 많이 확보 되있어야 하며, Thread를 늘리는데에 Memory와 CPU Core를 고려해야한다. 뿐만 아니라 외부 서비스를 활용하는데의 Thread Pool, timeout도 고려하자.</p>
</blockquote>
<h3 id="2-thread를-효율적으로-사용하기-위한-노력">[2] Thread를 효율적으로 사용하기 위한 노력</h3>
<ul>
<li>앞서 외부서비스를 call할 때 동기방식으로 처리하고, timeout이 길다면 가용할 스레드가 적어지는 문제가 있다. 다만 이러한 상황은 I/O Bound 인 상황으로 응답을 받고 처리하는 경우다.</li>
<li>스레드가 Request를 계속 점유하는게 아닌, Event 기반으로 스레드당 여러 요청을 처리하는 Reactive Programming(코틀린에서는 코루틴)라는 개념이 등장했다. 코루틴으로 비동기 처리가 용이해졌으며, 쓰레드를 효율적으로 사용하게 됐다. 쓰레드를 가성비 있게 사용하다보니, 많은 요청을 처리 할 수 있다.</li>
<li><a href="https://velog.io/@youngmin-mo/Coroutine">Coroutine 정리 포스트</a></li>
</ul>
<h3 id="3-그렇다면-thread-갯수를-얼마나-잡아야할까-by-tomcat">[3] 그렇다면 Thread 갯수를 얼마나 잡아야할까? by tomcat</h3>
<ul>
<li>적정 스레드 갯수를 아래의 공식을 참고할 수 있다. 적절한 쓰레드 갯수는 성능 테스트를 통해 확인하는게 좋다. 메모리는 효율적으로 사용하는지, Context Switching이 빈번한지 등을 모니터링을 통해 확인한다.
<img src="https://velog.velcdn.com/images/youngmin-mo/post/efad737b-cd5c-4906-bff4-6c53c965f762/image.png" alt=""></li>
<li>처리량을 계산할 때 MaxTreads, MaxConnections를 고려해야한다.<ul>
<li>우리의 Application이 CPU Bound인지, I/O Bound인지도 고려해야 한다.</li>
<li>예를 들어 외부 네트워크 호출 또는 I/O 작업으로 인해 상당한 대기 시간이 있는 시나리오에서는 coroutine을 활용하거나, 메모리를 적게 가져가고 스레드 수를 늘리는 것이 유리할 수 있다.(CPU 작업도 거의 없어 Context Switching 작업도 적게 일어난다.) 반대로 CPU, 메모리등의 리소스 제한이 있거나 애플리케이션이 일련의 순차적 계산 실행을 수행할 때는 경합이 발생하여 스레드 수를 늘리는 것이 불리할 수 있다.</li>
</ul>
</li>
<li><strong>MaxConnections</strong> : 서버가 수락하고 처리할 수 있는 최대 연결수다. Tomcat 7에서는 BIO가 기본이지만, 8.5 이상 버전에서는 NIO를 사용한다. Thread가 200개라면 7버전에서는 200개의 Connection이 사용되지만, 8.5이상 버전에서는 여러개의 Connection을 가질 수 있다.<ul>
<li>BIO : Blocking I/O (Thread당 하나의 Connection)</li>
<li>NIO : Non-Blocking I/O (Thread당 여러 개의 Connection)</li>
</ul>
</li>
<li>일반적으로는 Thread의 기본값은 200이고 maxConnections는 8192이기 때문에 Thread에서 병목이 발생할 가능성이 크다.</li>
<li>서버 어플리케이션의 품질은 동시에 처리할 수 있는 요청 개수와 관련있다. 잘못된 설정으로 생겨날 수 있는 시나리오는 2가지이다.<ol>
<li>요청 수에 비해 스레드를 너무 많이 설정 : 놀고 있는 스레드가 많아져 메모리,cpu 자원 비효율 증대</li>
<li>요청 수에 비해 스레드를 너무 적게 설정 : 동시 처리 요청수가 줄어든다. 평균응답시간, TPS 감소(대기)한다.</li>
</ol>
</li>
</ul>
<h4 id="tomcat-thread-connection-관련-설정-값">Tomcat Thread, Connection 관련 설정 값</h4>
<ul>
<li>server.tomcat.threads.max : Thread Pool에서 사용할 최대 스레드 개수, 기본값은 200</li>
<li>server.tomcat.threads.min-spare : Thread Pool에서 최소한으로 유지할 Thread 개수, 기본값은 10</li>
<li>server.tomcat.max-connections : 동시에 처리할 수 있는 최대 Connection 의 개수, 기본값은 8192다. 사실상 서버의 실질적인 동시 요청처리개수라고 생각할 수 있다.</li>
<li>server.tomcat.accept-count : max-connections 이상의 요청이 들어왔을 때 사용하는 요청 대기열 Queue 의 사이즈 기본값은 100이다.</li>
</ul>
<h2 id="3-tomcat에서의-처리량-정리">3. Tomcat에서의 처리량 정리</h2>
<blockquote>
<p>위에서 언급한 내용들을 정리한다.</p>
</blockquote>
<ul>
<li>Tomcat은 Servlet Container를 사용하며, Servlet은 기본적으로 Thread로 요청을 처리한다. 결국 얼마나 많은 요청을 처리할 수 있는지는 Thread 갯수와 관련있다.</li>
<li>Thread 갯수를 설정할 때는 Cpu Core(Context Switching 때문)와 메모리가(스레드마다 스택이라는 공간이 각각 가지게된다) 중요한 지표다.</li>
<li>또한 외부서비스를 Call할 때의 Thread Pool 영역과, timeout도 함께 고려가 필요하다.</li>
<li>서비스가 CPU Bound 작업인지, I/O Bound인지에 따라서도 스레드 갯수가 달라질 수 있으며, 성능 향상을 위해 비동기 방식의 코루틴도 고려한다. (코루틴의 장점 중 하나는 이벤트 방식으로 돌아가면서 I/O 작업시 스레드를 효율적으로 사용한다.)</li>
</ul>
<h2 id="4-실제-서버에서의-처리량">4. 실제 서버에서의 처리량</h2>
<ul>
<li>지금까지는 톰캣에서 얼마나 많은 요청을 처리하냐? 에대한 답으로 Thread 갯수와 관련이있다. 그리고 Thread 갯수를 설정하는데 CPU Core, 메모리가 중요하다. 그리고 I/O 작업이 많을 때 쓰레드를 가성비 있게 쓰기 위해 코루틴이라는 것도 잠시 소개했다.</li>
<li>서버 위에서 톰캣이 돌아가는데, 서버는 어떻게 설정하면 많은 요청을 처리할 수 있을까?</li>
<li>이전에는 서버 하나에 톰캣을 여러개 두고(톰캣을 여러개 두는 이유는 JVM 방식에서 Stop The World 때문이라고 본인은 생각한다.), Load Balancer 위에서 job을 분배했다. 최근에는 쿠버네티스를 활용해서 필요할 때 서버를 늘리고(결국 요청을 처리할 톰캣을 늘린것과 비슷하다.) 요청량이 줄어들면 서버도 제거하는 방식이 도입됐다. 관련 설명은 아래 링크로 대체한다.</li>
<li><a href="https://velog.io/@youngmin-mo/posts?tag=kubernetes">쿠버네티스 관련 링크</a></li>
</ul>
<h2 id="5-기타">5. 기타</h2>
<h4 id="q-nginx-proxy-server는-어떻게-많은-요청을-처리할-수-있지">[Q] Nginx, Proxy Server는 어떻게 많은 요청을 처리할 수 있지?</h4>
<ul>
<li>공통으로 CPU작업보다는 I/O 작업 즉, 외부에 call하고 응답을 받고 처리하는 형태라고 가정한다.</li>
<li>쓰레드를 활용<ul>
<li>CPU 작업이 많지 않기 때문에 Context Switching이 빈번하지 않고, 메모리 영역도 작게 가져가도 괜찮다고 생각한다. 따라서 Memory, CPU Resource를 작게한 여러개의 쓰레드를 생성한다. 이로써 많은 request를 커버할 수 있다.</li>
</ul>
</li>
<li>이벤트 방식 활용<ul>
<li>CPU 작업이 많지 않기 때문에, 공통 로직은 빠르게 처리되고 외부서비스에 call하고 쓰레드를 반환한다. 이후에 요청이 완료됐을 경우에 Event를 받아서 응답한다. 이로써 많은 request를 커버할 수 있다.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[시스템 디자인을 위한 조언]]></title>
            <link>https://velog.io/@youngmin-mo/%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%94%94%EC%9E%90%EC%9D%B8%EC%9D%84-%EC%9C%84%ED%95%9C-%EC%A1%B0%EC%96%B8</link>
            <guid>https://velog.io/@youngmin-mo/%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%94%94%EC%9E%90%EC%9D%B8%EC%9D%84-%EC%9C%84%ED%95%9C-%EC%A1%B0%EC%96%B8</guid>
            <pubDate>Sat, 09 Mar 2024 08:16:01 GMT</pubDate>
            <description><![CDATA[<h2 id="1-for-a-read-heavy-system---consider-using-a-cache">1. For a Read Heavy System - Consider using a Cache</h2>
<blockquote>
<p>읽기가 많은 시스템의 경우 캐시를 고려해보세요.</p>
</blockquote>
<ul>
<li>읽기 요청이 올 때마다 메인 저장소를 들리는게 아니라 메인 저장소의 자주 접근하는 데이터를 캐시에 올리면, 메인 저장소의 부하도 줄이고 읽기 속도도 향상된다.</li>
<li>캐시를 처리할 경우 데이터 용량도 고려해야하며, 어쩔 수 없이 메인 저장소 &lt;-&gt; 캐시 사이의 sync가 맞지 않는 케이스가 발생할 수 있다. 따라서 필요한 데이터만을 캐시에 올리고 데이터 갱신 주기를 고려해야한다.</li>
<li>저장소 속도 예시) 분산 서버 당 Local Cache(Memory) -&gt; External In-Memory(외부 서버의 Memory) -&gt; DISK 조회 시스템(MySQL, Oracle, MongoDB etc) -&gt; Hive(Hadoop)</li>
<li>결국 캐시에 데이터를 어떻게 올릴지가 고민된다. 복잡한 연산일 경우 배치로 올릴 수 있고 혹은 특정 이벤트를 받을 때 올릴 수도 있다. 이 부분은 상황에 맞게 판단하자.</li>
<li>몇가지 예시<ul>
<li>사용자 테이블에서 자주 조회하는 유저의 데이터는 Redis에 올린다. 특정 사용자의 정보 변경시 해당 레디스에도 데이터를 업데이트한다.</li>
<li>메인 홈페이지의 방문자 수를 획득하는 API가 있다. 해당 페이지의 접근이 많아 매번 레디스에 데이터를 업데이트하기엔 레디스에 부하가 있다. 방문자 수가 꼭 정확하지 않아도 된다면 분마다 메인 저장소에서 레디스에 데이터를 업로드 후, 방문자 수 획득 API는 레디스에서 처리한다.</li>
<li>자주 사용하는 데이터(직업 코드 값)를 LocalCache에 업로드 하고, 10분마다 데이터를 갱신한다.</li>
</ul>
</li>
</ul>
<h4 id="maindb-vs-read-db">MainDB vs Read DB</h4>
<ul>
<li>반드시 캐시가 아니어도 RDBMS에서 ReadDB를 구축함으로 읽기 작업을 향상시킬 수 있다. Create/Read/Update/Delete 작업을 Main DB에서 처리하되 Only Read DB들을 별도로 구축한다. </li>
<li>Read DB는 주기적으로 Main DB에서 데이터를 동기화 받고, 여러개로 Load Balancing하게 구축하면 읽기 작업을 여러 Read DB서버에서 처리하여 Main DB의 부하도 줄이고 읽기 작업도 크게 향상된다. </li>
<li>단, 데이터의 sync가 어느정도 부정확해도 괜찮은 시스템에서 허용한다.</li>
</ul>
<blockquote>
<p>대부분의 시스템은 읽기 작업이 많고, 자주 읽는 데이터를 빠른 저장소에 올려서 요기에 접근하자.</p>
</blockquote>
<h2 id="2-for-a-write-heavy-system---use-message-queues-for-async-processing">2. For a Write-Heavy System - Use Message Queues for async processing</h2>
<blockquote>
<p>쓰기가 많은 시스템의 경우 - 비동기 처리를 위해 메시지 큐 사용</p>
</blockquote>
<h4 id="write-작업을-비동기로-처리한다면">Write 작업을 비동기로 처리한다면?</h4>
<ul>
<li>메인 작업과 write 작업을 분리하면 메인 작업의 응답 시간이 개선된다. 무거운 write 작업을 큐에 던지고 응답하기 때문이다. write 작업과 메인 작업을 분리 했으니, 각각의 부하가 심한 시스템만 개별적으로 확장이 가능하다.</li>
<li>write 작업에 이슈가 발생해도 메인 작업에 영향이 없고, 메시지 큐에 저장된 것을 다시 consume 하여 write하면되니 데이터 손실 역시 최소화 할 수 있다.</li>
<li>write 작업 뿐만 아니라 전체 시스템에서 sync로 처리 하지 않아도 되는 무거운 작업(혹은 불안정한 시스템에 의존성 있는 것)은 별도로 비동기로 처리하는게 좋다. 하나의 작업에 무거운 여러 작업이 있다면 각 작업에 coupling이 강해 각 무거운 job에 의한 장애, 개별적인 확장 등에 어려움이 있다.</li>
</ul>
<h4 id="메시지-큐">메시지 큐</h4>
<ul>
<li>메시지 큐란 각 컴포넌트 간에 비 동기적으로 메시지를 교환하는 방식이다.</li>
<li>Producer, MessageQueue, Consumer로 구성된다.<ul>
<li>Producer : 메시지를 생성하고, 메시지 큐로 보내는 주체다.</li>
<li>MessageQueue : 메시지가 일시적으로 저장되는 큐다.</li>
<li>Consumer : Message Queue에서 메시지를 소비하는 주체다.</li>
</ul>
</li>
<li>Producer가 Consumer한테 메시지를 직접 보내느게 아니라 Message Queue를 거쳐서 보냄으로, <strong>Producer, Consumer가 느슨하게 결합된다.</strong><ul>
<li>Consumer에 이슈가 발생해도 Producer는 신경쓰지 않고 MessageQueue에 produce하면된다. 만약 producer에 트래픽이 폭주하더라도 Message Queue에 데이터가 쌓이지, consumer는 필요한만큼만 메시지를 consume하면된다. 이로써 각 컴포넌트는 독립적으로 확장할 수 있고, 변경할 수도 있다.</li>
<li>즉, producer/consumer는 서로의 내부 사정을 신경 쓰지 않고 메시지 큐만 바라본다. 특정 서비스에서 여러 서비스와 연동돼있고 coupling이 심하다면 유지보수, 확장 시 신경쓸게 너무 많다. 하지만 메시지 큐를 사용한다면 모든 연관부서는 데이터 연동시 메시지 큐만 바라보고 produce/consume하면 된다.</li>
</ul>
</li>
<li>그렇다고 메시지 큐가 은총알은 아니다. 데이터 복구 기한은 메시지 큐에 데이터가 얼마나 보관되냐에 달려있다. 또한 메시지큐에 무한정 데이터를 저장할 수 없기 때문에 lag가 쌓이면 고것도 어떻게 처리할지 고려해야한다. 또한, 비동기적으로 데이터를 consume하다보니 예상치 못한 시나리오도(대표적으로 트랜잭션 처리) 발생하고, 동기적으로 처리가 필요한 케이스에서는 consume 시점에 lock을 고려하기도 한다.</li>
<li>Ex) RabbitMQ, Kafka, Amazon SQS, ActiveMQ</li>
</ul>
<blockquote>
<p>결국 쓰기와 같이 무거운 작업 혹은 불안정한 시스템에 의존하는 모듈은 별도로 뺴서 비동기로 처리하는게 유지보수/확장 관점에서 유리하다. 예상치 못한 시나리오(ex 트랜잭션)가 발생할 수 있다. 이때, 비동기로 데이터를 보내는 방식 중 하나가 메시지 큐다.</p>
</blockquote>
<h2 id="3-for-a-low-latency-requirement---consider-using-a-cache-and-cdn">3. For a Low Latency Requirement - Consider using a Cache and CDN</h2>
<blockquote>
<p>짧은 지연 시간이 필요한 경우 - 캐시 및 CDN 사용을 고려하세요.</p>
</blockquote>
<h4 id="content-delivery-network">Content Delivery Network</h4>
<ul>
<li>CDN은 여러 지역 서버에 원본 서버의 컨텐츠를 캐시한다. 사용자가 컨텐츠르 요청하면 CDN은 해당 요청을 받아 지리적으로 가장 가까운 CDN 서버에서 캐시된 컨텐츠를 제공한다. 먼 원본 서버로 가지 않고 캐시된 데이터를 제공하니 응답 속도도 좋아지고 원본 서버의 네트워크 트래픽을 줄여준다.</li>
<li>이 역시 원본 서버의 캐시된 데이터를 사용하니 sync가 맞지 않다. original 서버의 컨텐츠 변경 시 데이터를 갱신하거나 일정 주기로 데이터를 갱싱해야한다.</li>
<li>Ex) image, css 등 자주 바뀌지 않으나 자주 접근하는 컨텐츠</li>
</ul>
<blockquote>
<p>이미지, 문구 와 같이 자주 바뀌지 않으나 여러 지역에서 자주 접근하는 컨텐츠는 CDN 서비스를 활용하자.</p>
</blockquote>
<h2 id="4-need-atomicity-consistency-isolation-durability-compliant-db---go-for-rdbmssql-database">4. Need Atomicity, Consistency, Isolation, Durability Compliant DB - Go for RDBMS/SQL Database</h2>
<blockquote>
<p>정합성, 일관성, 격리성, 내구성 준수 DB가 필요한 경우 - RDBMS로 이동합니다.</p>
</blockquote>
<ul>
<li>Atomicity(정합성) : 저장된 데이터가 규칙/제약 조건을 준수함을 의미한다. 즉, <strong>이상한 데이터 없이, 안전한 데이터</strong>다. RDBMS는 각 컬럼마다 하나의 데이터를 저장할 수 있는데 해당 컬럼의 &#39;제약조건&#39;, &#39;PK&#39;, &#39;외래키&#39;등으로 데이터 정합성을 유지할 수 있다. 예를 들어, data type, size, 특정 데이터만 insert, not null, 정규화를 통한 일관된 데이터 제공 등이 있다.</li>
<li>Consistency(일관성) : 트랜잭션으로 여러 작업을 묶어, 데이터의 일부만 변경됨을 방지한다. 만약 중간에 이슈가 발생하면 전체가 롤백되고, 모든 작업 성공시 커밋된다. ex) 은행 송금 처리</li>
<li>Isolation(격리성) : 여러 트랜잭션이 동시에 실행되고 하나의 테이블에서 CRUD할 때, 각 트랜잭션이 다른 트랜잭션에게 영향을 최소화 할 수 있다. Isolation Level에 따라 성능 &lt;-&gt; 격리성을 trade-off 할 수 있다.</li>
<li>Durability(내구성) : 시스템이 고장나더라도 데이터는 영구적으로 보관된다. data 이중화를 통해 데이터를 안전하게 보관한다.</li>
</ul>
<blockquote>
<p>잘못된 데이터가 들어가지 않게, 엄격한 데이터 관리가 필요할 경우 RDBMS를 고려하자. </p>
</blockquote>
<h2 id="5-have-unstructed-data---go-for-nosql-database">5. Have unstructed data - Go for NoSQL Database</h2>
<blockquote>
<p>비정형 데이터 보유 - NoSQL DB로 이동한다.</p>
</blockquote>
<ul>
<li>JSON 포맷과 같이 비정형 데이터는 NoSQL을 활용한다. 데이터 포맷이 달라지더라도 저장 포맷을 변경할 필요 없다. 키에 맞는 데이터를 노출하여 단순 insert, select 시 성능이 좋다.</li>
<li>정규화된 데이터를 제공하지 않아 일부 데이터만 update할 수 있어 신뢰성 있는 데이터 제공이 힘들다.</li>
</ul>
<blockquote>
<p>&#39;데이터 포맷&#39;이 자주 변경되는 비정형 데이터의 경우 &#39;엄격한 데이터&#39; 관리가 필요하지 않다면 NoSQL 제공도 훌륭한 방식이다.</p>
</blockquote>
<h2 id="6-have-complex-data-videos-images-files---go-for-blob--object-storage">6. Have Complex Data (Videos, Images, Files) - Go for Blob / Object Storage</h2>
<blockquote>
<p>복잡한 데이터 (동영상, 이미지, 파일)가 있는 경우 블롭/객체 스토리지로 이동한다.</p>
</blockquote>
<ul>
<li>Blob(Binary Large Object)은 주로 동영상, 이미지, 파일과 같이 크기가 크고 구조화된 파일/데이터를 의미한다.</li>
<li>blob 객체 스토리지는 데이터를 객체로 저장하며, 각 객체는 고유한 식별자, 데이터, 메타 데이터를 가진다. 이는 대량의 비정형 데이터를 저장하고 관리하기 위해 제작된 클라우드 기반 스토리지 서비스다.</li>
<li>예를 들어, 파일을 저장할 때 blob 객체 스토리지 서비스에 파일을 올리고 해당 url을 db 에 저장한다.</li>
</ul>
<blockquote>
<p>파일과 같이 크기가 큰 데이터를 blob 객체 스토리지 서비스를 이용하자.</p>
</blockquote>
<h2 id="7-complex-pre-computation---use-message-queue--cache">7. Complex Pre Computation - Use Message Queue &amp; Cache</h2>
<blockquote>
<p>복잡한 사전 계산이 필요한 경우 - 메시지 큐 및 캐시를 사용하세요</p>
</blockquote>
<ul>
<li>여기서 말하는 복잡한 사전 계산이란 시간이 오래 걸리거나 많은 자원이 필요로 하는 처리를 의미한다. ex) 대용량 데이터를 읽어서 처리하거나 복잡한 알고리즘을 처리한다. 이렇게 리소스가 많이 드는 작업을 기존 시스템에서 처리하면 영향도가 커지고 앞으로 복잡해지기만 한다. 따라서 부하가 심한 job을 백그라운드에서 처리하면 기존 시스템의 영향도를 최소화할 수 있다.</li>
<li>이렇게 계산한 결과를 캐시에 저장하여 이후에 동일한 계산이 요청됐을 때 다시 계산하지 않고 캐시된 데이터를 사용하여 성능을 향상 시킬 수 있다. 꼭 메시지 큐 뿐만 아니라 사전에 데이터 계산이 가능할 경우 일/월/년 배치로 미리 작업을 계산하고 캐시에 데이터를 넣어 시스템의 성능을 향상시킬 수 있다.</li>
</ul>
<blockquote>
<p>무거운 작업은 백그라운드에서 처리하고, 키를 잘 정의해서 캐시에 올리면 매번 계산할 필요가 없어진다.</p>
</blockquote>
<h2 id="8-high-volume-data-search---consider-search-index-tries-or-search-engine">8. High-Volume Data Search - Consider Search Index, Tries or Search Engine</h2>
<blockquote>
<p>대용량 데이터 검색 - RDBMS Index, Tries 자료구조, Search Engine 서비스를 고려하세요.</p>
</blockquote>
<h4 id="index">Index</h4>
<ul>
<li>RDBMS를 사용한다면 샤딩, 인덱스를 적용시켜 검색 속도를 향상시킬 수 있다. </li>
<li>&#39;책 목차&#39;처럼 인덱스를 적용시켜 검색을 위한 별도의 테이블을 생성할 수 있다. 물론 모든 검색에 우위가 있는 건 아니고, 데이터 중복도가 낮은것 / prefix가 적용된 케이스에서 좋다. 인덱스 특성에 따라 다르겠지만 내부적으로 B tree로 구현된 시스템이라면 비슷하다. 애매하면 query plan에서 인덱스가 잘 타는지확인하면 된다.</li>
<li>join절에서 자주 발생하는 컬럼에 인덱스를 추가하면 좋고, PK는 Clustered Index로 기적용된다.</li>
<li>인덱스라는건 결국 인덱스트 테이블을 생성하기 때문에, 데이터를 더 사용하고 대용량의 인덱스를 추가할 경우 오랜 시간이 걸리기도한다.</li>
</ul>
<h4 id="tries-자료구조">Tries 자료구조</h4>
<ul>
<li>문자열을 저장하고 효율적으로 탐색하기 위한 &#39;트리&#39;형태의 자료구조다.</li>
<li>우리가 검색할 때 볼 수 있는 자동완성 기능, 사전 검색 등 문자열을 탐색하는데 특화되어있는 자료구조라고 한다.</li>
</ul>
<h4 id="search-엔진">Search 엔진</h4>
<ul>
<li>대량의 데이터에서 원하는 정보를 검색하고 반환하는 SW다. 다양한 기술을 사용하여 데이터를 색인하고 검색 쿼리를 처리한다. ex) Elastic Search</li>
</ul>
<h2 id="9-scaling-sql-database---implement-database-sharding">9. Scaling SQL Database - Implement Database Sharding</h2>
<blockquote>
<p>SQL DB 확장 - DB 샤딩을 구현합니다.</p>
</blockquote>
<ul>
<li>샤딩은 대량의 데이터를 특정 규칙에 따라 &#39;수평&#39;으로 분할하여 여러 서버에 분산 저장하는 기술을 말한다. 샤딩을 통해 DB의 부하를 분산시키며, 수평으로 분할하여 확장에 용이하다. 다음은 사용 예시다.</li>
<li>로그성 데이터를 일자별로 분할하여 저장한다. 조회 일자만 알면(샤딩 키) 빠르게 데이터를 조회할 수 있고, 파티션별로 주기적으로 데이터를 삭제할 수 있다.</li>
<li>USER 라는 테이블에서 유저의 지역별로 분산 저장할 수 있다. 혹은 자주 조회하는 데이터를 분산 저장할 수 있다. ex) 자주 검색되는 연예인 정보</li>
<li>그렇다고 샤딩이 무조건 좋은건 아니다. 데이터를 분산 저장하여 추가 인프라 관리가 필요하다. 여러 샤드에 걸쳐 있는 데이터를 조회할 경우 데이터를 &#39;join&#39;하는데 복잡성이 늘어날 수 있다. 샤딩된 데이터 조회/조인시 반드시 샤딩 키를 지정해줘야 데이터 조회 속도가 향상된다.</li>
</ul>
<blockquote>
<p>SQL DB 사용시 &#39;부하 분산&#39;, &#39;확장&#39; 에 용이하기 위해 샤딩을 사용하자. 단, 샤딩된 데이터 조회시 샤딩 키를 반드시 지정해주자.</p>
</blockquote>
<h2 id="10-high-availability-performance--throughput-use-a-load-balancer">10. High Availability, Performance, &amp; Throughput - Use a Load Balancer</h2>
<blockquote>
<p>고가용성, 성능 및 처리량 - 로드 밸런서를 사용하세요.</p>
</blockquote>
<ul>
<li>로드밸런서란 요청 작업을 균등하게 분배하는 시스템이다. job을 균등하게 서버에 분배하여 각 서버의 트래픽을 관리한다. 만약 요청량이 많아지면 job을 처리할 서버를 추가 등록하여(수평확장에 용이) 처리량을 높일 수 있다. 만약 요청량이 적다면 로드밸런서에 등록한 서버를 제거하면 된다.</li>
<li>만약 특정 job에 문제가 있다면 로드밸런서에서 해당 서버에 job을 주지 않아 고가용성 역시 높일 수 있다.</li>
<li>DNS 서버, 프록시 서버, API Gateway 등에서 사용될 수 있다.</li>
</ul>
<blockquote>
<p>&#39;고가용성&#39;, &#39;분산 처리를 통한 높은 처리량&#39;을 위해 로드 밸런서를 활용하자.</p>
</blockquote>
<h2 id="11-global-data-delivery---consider-using-a-cdn">11. Global Data Delivery - Consider using a CDN</h2>
<blockquote>
<p>글로벌 데이터 전송 - CDN 사용을 고려하세요</p>
</blockquote>
<ul>
<li>전 세계 여러 지역에 위치한 서버를 통해 캐시된 데이터를 제공하여, 지리적으로 성능 향상을 이끌며 원본 서버의 부하를 분산시켜준다.</li>
<li>물론 캐시된 데이터를 제공하니 sync가 안맞는 문제는 여전히 발생할 수 있다.</li>
</ul>
<h2 id="12-graph-data-data-with-nodes-edges-and-relationships---utilize-graph-database">12. Graph Data (data with nodes, edges, and relationships) - Utilize Graph Database</h2>
<blockquote>
<p>그래프 데이터(노드, 에지 및 관계가 있는 데이터) - 그래프 DB를 활용하세요</p>
</blockquote>
<ul>
<li>Graph Data란 Node, Edge로 구성된 데이터로, Node는 객체를 나타내고 Edge는 Node 사이의 관계를 나타낸다. 예를 들어, SNS에서 사용자가 노드고, 친구 관계가 에지로 표현될 수 있다.</li>
<li>Graph DB는 노드와 에지의 관계를 효율적으로 저장하고, 노드간의 관계를 손쉽게 파악할 수 있다. 예를 들어, 특정 사용자의 친구 목록을 조회하거나, 두 사용자 간의 거리(친구를 통해 얼마나 떨어져있는지)를 계산하는 등의 작업을 수행할 수 있다.</li>
<li>EX) SNS 분석, 지리 정보 시스템, 네트워크 분석(Routing)</li>
</ul>
<h2 id="13-scaling-various-components---implement-horizontal-scaling">13. Scaling Various Components - Implement Horizontal Scaling</h2>
<blockquote>
<p>다양한 컴포넌트 스케일링 - 수평 스케일링을 구현합니다</p>
</blockquote>
<ul>
<li>애플리케이션의 성능을 향상 시키는데 수평 스케일링, 수직 스케일링이 있다. 컴포넌트를 스케일링할 때 비용, 고 가용성을 위해 수평 스케일링을 권장한다.</li>
</ul>
<h4 id="horizontal-scaling-수평">Horizontal Scaling (수평)</h4>
<ul>
<li>시스템의 성능을 높이기 위해 여러 인스턴스를 추가하여 부하를 분산하는 방법이다. 이는 단일 인스턴스의 성능 합계를(H/W 업그레이드) 초과하거나 시스템의 고가용성을 높이기 위해 사용된다. 이를 위해 새로운 서버 등록/삭제 수행 시 로드밸런서에 등록/삭제 하면 된다.</li>
<li>다만 요청 처리를 분리된 서버에서 진행하다보니 복잡성이 증가할 수 있다. 각 서버에서 로컬 캐시를 사용할 때 sync가 안맞는 문제, 각 서버의 모니터링 등의 복잡성 문제, 로드밸런서가 이중화되지 않았을 때의 단일지점 장애 포인트가된다.</li>
</ul>
<h4 id="vertical-scaling-수직">Vertical Scaling (수직)</h4>
<ul>
<li>시스템의 성능을 높이기 위해 단일 서버의 H/W 성능을 업그레이드 함을 의미한다. 서버의 CPU, 메모리, 디스크 등의 H/W를 업그레이드하여 단일 서버에서 더 많은 부하를 처리할 수 있다.</li>
<li>복잡하진 않지만, 물리적으로 업그레이드에 한계가 있고 서버 장애 시 시스템 전체가 영향을 받을 수 있다.</li>
</ul>
<blockquote>
<p>성능을 높이기 위해 &#39;고가용성&#39;, &#39;비용&#39; 측면에서 &#39;수평 스케일링&#39;을 활용하자.</p>
</blockquote>
<h2 id="14-high-performing-database-queries-use-database-indexes">14. High-Performing Database Queries - Use Database Indexes.</h2>
<blockquote>
<p>고성능 DB 쿼리 - DB 인덱스 사용하기</p>
</blockquote>
<ul>
<li>RDBMS에서 검색이 필요로하는 쿼리 사용시 &#39;인덱스&#39;를 활용한다. 쿼리 실행시 테이블당 하나의 인덱스가 사용되며, 복합 인덱스 설정 시 필드 데이터 중복도가 높은 순서로 설정함을 권장한다. ex) age &gt; name &gt; id 예측한데로 인덱스가 안탈 수 있어,  쿼리 플랜에서 인덱스가 잘 타는지 꼭 확인해보자.</li>
<li>인덱스는 내부 구현이 B+Tree로 구현되 있어 &#39;TEST%&#39;, 특정 필드의 join, column &gt;= 10, column = &#39;test&#39;과 같은 경우엔 인덱스가 탈 수 있지만 &#39;%TEST&#39;, 테이블 row 양에 따라 안타는 경우도 있다.</li>
<li>인덱스 생성시 인덱스를 위한 테이블이 별도로 생성되니 무조건 만들기 보단, 적절한 인덱스 테이블 생성이 필요하다. 또한, 테이블 양에 따라 컬럼, 인덱스 수정에 많은 시간이 소요될 수 있다.</li>
<li>어드민에서 여러 검색 조건을 조합할 때 여러개의 인덱스 생성이 필요한데(검색 필드가 5개면 최대 2^5=32개) 이렇게 많은 인덱스를 생성하기엔 부하가 있다. 따라서 필드의 중복도가 높은것 (성별)은 굳이 인덱스를 추가 안해도 되며, 몇개 검색조건은 하나의 인덱스를 태울수 있도록 설정해도 좋다. 혹은 필요시 검색 조건을 제한하여 성능 제한을 둘 수도 있다. (특정 검색조건은 몇개월치만 데이터 조회) 즉, 인덱스가 굉장히 좋은 기술이지만, 무조건 만들기보단 필요한 것 위주로, 안된다면 제한조건을 두는것도 하나의 방법이다.</li>
</ul>
<blockquote>
<p>RDBMS에서 검색을 위해 인덱스를 활용한다. 단, 무조건적으로 인덱스 테이블을 생성하기보단 &#39;중복도&#39;, &#39;제한조건&#39;도 함께 고려해보자.</p>
</blockquote>
<h2 id="15-bulk-job-processing---consider-batch-processing-and-message-queues">15. Bulk Job Processing - Consider Batch Processing and Message Queues</h2>
<blockquote>
<p>대량 작업 처리 - 배치 처리 및 메시지 큐를 고려합니다</p>
</blockquote>
<p>대량 작업이 필요한 경우 H/W 리소스도 많이 사용하고, 시간도 오래 걸릴 수 있다. 따라서 이러한 job들을 메인 서버가 아닌 백그라운드에서 처리한다.</p>
<h4 id="batch">Batch</h4>
<ul>
<li>주기적으로 대량 작업이 필요할 경우 메인 서버가 아닌 batch 서버에서 주기적으로 job을 처리한다. 예를 들어, 사용자의 하루 통계 데이터가 필요하다면 매일 새벽 1시에 스케줄러 job을 등록하고 job을 실행한다.</li>
<li>spring batch는 보통 Read -&gt; Process -&gt; Write 작업 순으로 실행되는데 MySQL에서 Read할 때 한번에 너무 많은 데이터를 조회하면 timeout exception이 발생할 수 있다. 혹은 쿼리 join시 한번에 너무 많은 양을 조회하면 성능 이슈가 발생할 수도 있다. 따라서 &#39;pagination&#39;으로 끊어서 데이터 조회가 가능하다. 다만 끊는 데이터 양이 너무 작다면 데이터 통신이 빈번하게 일어나기 때문에, 적절한 paging 갯수를 설정하자.</li>
<li>pagination을 구현할 때 limit으로 끊으면 limit의 번호가 끝으로 갈수록 성능 이슈가 발생할 수 있다. 따라서 WHERE 절에 이전에 읽은걸 방지하는 조건을 둠으로 성능을 향상시킬 수 있다.</li>
</ul>
<h4 id="message-queue">Message Queue</h4>
<ul>
<li>주기적으로 job을 처리하는게 아니라 특정 이벤트에 의해 대량 작업을 처리한다면 메시지 큐를 활용한다. 메인 서버에서 처리하지 않고, message queue에 메시지를 푸시하고, 별도의 백그라운드 서버에서 consume해서 작업을 처리할 수 있다.</li>
<li>메인시스템과 대용량 작업 처리가 동기적으로 처리될 필요가 없다면 coupling을 끊는게 &#39;영향도&#39;, &#39;확장&#39; 측면에서 좋다. 물론, 비동기적으로 처리되다보니 예상치 못한 시나리오가 발생할 수 있다. ex) 작업이 순서대로 실행되는게 아니라 뒤죽박죽 실행, 같은 트랜잭션으로 묶이지 않았기 때문에 일부만 롤백되는 케이스</li>
<li>만약 대용량의 데이터 작업이 필요하면 꼭 mysql이 아닌 하둡에 데이터를 적재하고 이를 mapreduce하는 spark-batch를 고려해 보는 것도 좋다.</li>
</ul>
<blockquote>
<p>대량 작업 처리시 백그라운드에서 처리하되, batch/message queue 시스템을 고려해보자.</p>
</blockquote>
<h2 id="16-server-load-management--preventing-dos-attacks---use-a-rate-limiter">16. Server Load Management &amp; Preventing DOS Attacks - Use a Rate Limiter</h2>
<blockquote>
<p>서버 부하 관리 및 DOS 공격 방지 - 속도 제한기를 사용하세요</p>
</blockquote>
<ul>
<li>속도 제한기는 서버에 대한 &#39;요청 속도&#39;(단위 시간 당 요청량)를 제한함으로써 과도한 트래픽으로 인한 부하를 완화하고, 악의적인 공격을 방지하는데 도움이 된다. 참고로 DOS란 특정 서버에 트래픽을 강제로 몰리게 하여 서버를 마비시키는 공격이다.</li>
<li>Rate Limiter 예시<ul>
<li>서버에 도착하는 요청 속도를 제한함으로, 허용 가능한 속도 이상의 요청을 거부한다. 이를 통해 서버가 과도한 부하에 시달리는 것을 방지한다.</li>
<li>특정 IP 주소에서 너무 많은 요청이 발생하는 경우 해당 IP 주소를 차단하거나 요청을 거부하는 것이 좋다.</li>
<li>User-Agent 헤더 또는 쿠키를 사용하여 클라이언트를 식별하고, 이를 통해 특정 사용자의 요청을 제한하는것이 가능하다.</li>
<li>사람이아닌 자동화된 스크립트로 DOS 공격이 올 수 있는데, &#39;CAPTCHA&#39; 라는 기술로 사람/시스템의 부하를 구별할 수 있다.</li>
<li>당연히 지속적으로 서버의 트래픽을 모니터링하고, 감사와 로깅을 주기적으로 체크해야한다.</li>
</ul>
</li>
<li>속도 제한기를 구현하는 방법은, 웹 서버에 내장된 기능으로 제한하거나 경우에 따라 특별한 애플리케이션을 추가 하기도한다. 혹은 특정 서비스 제공업체를 활용할수도 있다.</li>
</ul>
<h2 id="17-microservices-architecture-use-an-api-gateway">17. Microservices Architecture - Use an API Gateway</h2>
<blockquote>
<p>마이크로 서비스 아키텍처- API Gateway 사용 권장</p>
</blockquote>
<h4 id="microservice">Microservice</h4>
<ul>
<li>MSA는 시스템을 독립적인 서비스로 분해하여, 개발하는 방식이다. 큰 모듈에서 작은 모듈로 분해하니, 작은 모듈의 &#39;배포&#39;, &#39;확장&#39;, &#39;유지보수&#39;, &#39;R&amp;R이 명확하니 기획/개발간 협업&#39;의 장점이 있다.</li>
<li>이는 물론 각 서비스간 &#39;의존성&#39;과 &#39;서비스의 경계&#39;가 명확히 구별 했을 때의 장점이다. 이를 명확하지 않았을 경우 다른 팀의 &#39;배포&#39;에 영향 받고, 쉽게 유지 보수하기도 힘들다. 만약 분리된 서비스가 다른 팀이라면 협업도 힘들며 복잡성만 늘어난다.</li>
</ul>
<h4 id="api-gateway">API Gateway</h4>
<ul>
<li>Gateway란 네트워크에서 서로 다른 프로토콜 간의 통신을 중개하거나 변환하는 장치다. 이는 두 개 이상의 시스템 또는 네트워크 간 통신을 가능하게 한다. API Gateway란 클라이언트와 여러 백엔드 서비스 간의 통신을 관리하는 서비스로 MSA 아키텍처에서 많이 사용된다.</li>
<li>클라이언트 &lt;-&gt; 각 서비스의 결합도 완화<ul>
<li>클라이언트와 MSA 사이의 API Gateway를 둠으로써 분할된 MSA 정보를(ex 위치) 숨길 수 있다. 클라이언트는 모든 MSA에 대한 호출 대신 API Gateway에 대한 호출만을 관리한다. 또한 MSA 분할, 인스턴스 수 변화, 위치 등의 변화가 쉽다. 이는 Client와 MSA 간 결합도가 낮아 가능하다.</li>
</ul>
</li>
<li>프로토콜/응답값의 decoupling<ul>
<li>클라이언트와 MSA의 결합도가 낮아, 프로토콜이 달라도 API Gateway에서 변환할 수 있고, MSA의 응답값이 xml이여도 API Gateway에서 json으로 변환할 수 있다.</li>
</ul>
</li>
<li>공통 로직<ul>
<li>API Gateway는 MSA에서 &#39;단일 인증 포인트&#39;다. 따라서 공통 로직을 API Gateway에서 처리하면 때문에 서비스 전체에 &#39;표준화&#39;된 공통 로직을 제공하며, 각 서비스의 중복 로직을 제거한다.</li>
<li>대표적으로 인증/인가 서비스가 있다.</li>
<li>인증 : 해당 사용자가 본인이 맞는지를 확인하는 절차</li>
<li>인가 : 인증된 사용자가 요청한 자원에 접근 가능한지를 확인하는 절차</li>
</ul>
</li>
<li>메트릭 지표 수집<ul>
<li>모든 요청이 API Gateway를 거치기 때문에, 로깅 및 데이터 수집이 용이하다.</li>
</ul>
</li>
<li>API Gateway가 단일 지점 포인트기 때문에, 단일 실패 지점이 될 수 있다. 이를 위해 이중화 등 고가용성을 위한 설정이 필요하다. [Q] API Gateway에 모든 인입점이 되는데 요 녀석이 모든 트래픽을 감당할 수 있나? 인증/인가 처리하는 등 공통 로직을 단순히 하면 오래 안걸리나?</li>
</ul>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/80e87fa7-c2f7-42d8-b229-00969a6e684f/image.png" alt=""></p>
<ul>
<li>파드, 쓰레드 당 요청 얼마나 받을 수 있는지</li>
</ul>
<blockquote>
<p>클라이언트와 MSA간 통신이 일어날 때, &#39;결합도&#39;를 낮추고 &#39;표준화된 공통 로직&#39;을 위해 &#39;API Gateway&#39;를 활용하자.</p>
</blockquote>
<h2 id="18-for-single-point-of-failure---implement-redundancy">18. For Single Point Of Failure - Implement Redundancy</h2>
<blockquote>
<p>단일 장애 지점 - 이중화 구현</p>
</blockquote>
<h4 id="spofsingle-point-of-failure">SPOF(Single Point Of Failure)</h4>
<ul>
<li>시스템 구성 요소중에서 동작하지 않으면 전체 시스템이 중단되는 요소를 말한다.<ul>
<li>스위치/라우터와 같이 네트워크 통신에 의한 중단</li>
<li>데이터 센터의 전원 공급 장치에 의한 중단</li>
<li>단일 웹서버일 경우 해당 서버 인프라 장애 혹은 SW 버그에 의한 중단</li>
<li>DB 서버 전체 장애 발생 시 해당 DB를 이용하는 전체 서비스가 중단</li>
<li>클라우드 서비스에서 단일 지점 포인트 (ex- API Gateway)가 장애 발생할 경우 해당 클라우드를 이용하는 전체 서비스가 중단된다.</li>
</ul>
</li>
</ul>
<h4 id="이중화-redundancy--high-availabilityha">이중화 (Redundancy / High Availability=HA)</h4>
<ul>
<li>고가용성이란 장애가 생기더라도 빠르게 복구할 수 있는 시스템을 말한다. 완벽히 중단 시간을 없게는 못하겠지만, 최대한 중단 시간 없이 운영할 수 있는 성질을 말한다.</li>
<li>이중화는 각종 자원(하드웨어, OS, 미들웨어, DB 등)을 중복으로 구현하여 시스템의 가용성을 높이는 방식이다. 시스템에 문제가 있어도, 문제 없는 다른 시스템으로 대체하여 고가용성을 높일 수 있다.</li>
<li>장애 또는 재해시(서비스의 일시적 중단이 발생) <strong>재빠르게 교체</strong>하기 위해 사용한다. 혹은 일정량 이상의 트래픽을 처리할 경우 응답시간이 느려져, 원활한 서비스의 성능을 보장하기 위해 <strong>로드밸런싱</strong>을 목적으로 한다.</li>
<li>서버 이중화 전략 방식<ul>
<li><strong>Active-Standby</strong> : 평소엔 특정 서버에서 서비스를 운영하고, 해당 서버 장애 발생 시 대기 중이던 서버를 사용하여 대응하는 전략이다. Active 서버는 데이터의 업데이트가 있을 때마다 Standby서버로 전달하여 데이터를 동기화하고 장애 발생 시 그 즉시 투입 가능한 상태를 유지한다.</li>
<li><strong>Active-Active</strong>  : 서로 다른 서버가 모두 서비스를 운영하는데 어느 하나에 장애가 발생하면 나머지가 서비스를 제공하게 되는 구조다. Active Standby보다 높은 처리량과 고가용성을 제공하고, 비용이 더 많이 들어간다. (A-S는 하나가 down되있으나, A-A는 모두 ON 돼있음)</li>
</ul>
</li>
</ul>
<h4 id="완전한-다중화">완전한 다중화</h4>
<ul>
<li><strong>인프라</strong> : 인프라 영역은 전력, 네트워크 등 물리적인 공간으로 인터넷 서비스를 위한 영역이다. 예상치 못한 크고 작은 사고가 발생한다. ex) 화재, 포트가 빠진다거나 등. 그렇기 때문에 다양한 failover 설계를 해둬야한다. failover라는 건 문제가 생겼을 때 예비 시스템으로 자동 전환되는 기능을 말한다.</li>
<li><strong>데이터</strong> : DB와 같이 데이터를 다루는 영역이다. 실시간 이/삼중화를 통해 데이터가 유실되지 않게 백업해둔다.</li>
<li><strong>운영 및 관리 도구</strong> : 실제 서비스 트래픽을 받는 부분은 아니지만, 서비스 운영에 필요한 여러가지 기술적 도구들을 말한다. 배포, 모니터링, 권한관리 등을 위한 도구다. 이상적으론 다른 layer에서 다중화 구성만으로 장애 시 대응이 되어야 하지만 현실은 그렇지 않고, 예상치 못한 여러 상황이 벌어지기 때문에 배포 도구와 같은 운영관리 도구가 전체적인 서비스 복구 시간을 빠르게 도와준다.</li>
<li><strong>서비스 플랫폼</strong> : 서비스가 공통적으로 사용하는 플랫폼 영역다. 보안키 저장소, 레디스, 카프카 등 공용 솔루션을 말한다.</li>
<li><strong>어플리케이션</strong> : 실제 사용자에게 서비스하기 위한 소프트웨어 영역이다. 작은 장애가 다른 어플리케이션에 번질 수 있기 때문에 전략적으로 다중화를 진행해야한다.</li>
</ul>
<h4 id="dr-disaster-recovery">DR (Disaster Recovery)</h4>
<ul>
<li>이중화 요소를 갖췄다면 자연스레 장애 발생시 대처가 되지 않나 생각하지만, 만약 HA를 구성한 데이터 센터에 화재와 같은 큰 재해가 발생한다면  그 데이터 센터 내에 아무리 백업 서버를 두고 이중화를 잘 해놨다고 해도 아무 소용이 없다.</li>
<li>DR 센터란 서로 전혀 다른 지역에 재해 시 복구할 수 있도록 동일한 센터를 하나 더 만드는 것을 의미한다. 그리고 소산 백업이란 데이터만 백업하되, 그 백업 데이터가 같은 데이터 센터에 위치해 있으면 재해 시 유실되는 건 똑같으니까 물리적으로 다른 곳에 백업데이터를 두는 백업 방식이다.</li>
<li>이론적으론 아래의 복구 전략을 수립해야한다. 다만 별도의 DR센터를 구축했을 때 리소스(시간/비용/개발)에 많은 투자가 필요해서 적절한 HA/DR을 구축해야한다.<ul>
<li>서버가 장애났을 경우를 대비해서 HA 이중화 구성</li>
<li>백업 데이터까지 날라갈 경우를 대비해서 소산백업</li>
<li>재해 발생 시 빠르게 복구해야하는 경우 실시간으로 바로 서비스 재개가 가능한 DR 센터 구축</li>
</ul>
</li>
</ul>
<blockquote>
<p>장애를 빠르게 복구하기 위해 HA(이중화)를 구축하며, 재해에 대비하여 DR 센터를 별도로 구축하면 고가용성을 제공할 수 있다. 다만, 많은 리소스가 들기 때문에 상황게 맞게 판단하자.</p>
</blockquote>
<h2 id="19-for-fault-tolerance-and-durability---implement-data-replication">19. For Fault-Tolerance and Durability - Implement Data Replication</h2>
<blockquote>
<p>내결함성 및 내구성 - 데이터 복제를 구현하세요</p>
</blockquote>
<ul>
<li>시스템이 HW, SW 문제로부터 손상을 입었을 때, 시스템의 중단 없이 계속 작동되길 원한다. 특정 데이터 센터에 문제가 있거나 혹은 DB에 문제 발생 시 이러한 현상이 재현된다. 따라서 데이터 복제를 통해 문제 있는 DB가 다른 DB로 대체하여 시스템 중단 없이 계속 작동할 수 있게한다.</li>
<li>문제있던 DB에서 다른 DB로 대체되고, 문제있던 DB가 정상화되면 sync를 맞춰주는 작업이 추가적으로 필요하다. 뿐만 아니라 추가 장비를 통해 비용/복잡성 측면에서 리소스가 더 늘어난다. 하지만 고가용성이 중요한 서비스에서는 &#39;이중화&#39; 및 &#39;데이터 복제&#39;는 중요한 과제다.</li>
</ul>
<h2 id="20-for-user-to-user-fast-communication--use-websockets">20. For User-to-User fast communication — Use Websockets</h2>
<blockquote>
<p>사용자 간 빠른 통신을 위해 - 웹소켓 사용</p>
</blockquote>
<ul>
<li>기본적으로 HTTP 통신은 한번 연결을 맺고, 일정 시간 통신이 없을 경우 연결을 close한다. 하지만 웹소켓은 클라이언트와 서버 사이의 지속적인 연결을 제공한다. 이에 따라 웹소켓은 실시간 데이터를 양방향으로 전송할 수 있다.</li>
</ul>
<h2 id="21-failure-detection-in-distributed-systems---implement-a-heartbeat">21. Failure Detection in Distributed Systems - Implement a Heartbeat</h2>
<blockquote>
<p>분산 시스템에서의 장애 감지 - 하트비트 구현</p>
</blockquote>
<ul>
<li>분산 시스템의 다른 구성 요소 또는 노드 간에 주기적인 신호를 보내고 받아서 서로가 여전히 작동 중임을 확인하는 데 사용된다. 일종의 &quot;살아있다&quot; 메시지로 볼 수 있다. 만약 노드가 일정시간(timeout)안에 하트비트를 수신하지 못하면 다른 노드로부터 해당 노드의 장애를 감지할 수 있다.</li>
</ul>
<h2 id="22-data-integrity---use-checksum-algorithm">22. Data Integrity - Use Checksum Algorithm</h2>
<blockquote>
<p>데이터 무결성 - 체크섬 알고리즘 사용</p>
</blockquote>
<ul>
<li>체크섬 알고리즘은 데이터가 정확/안전한지 검증하는데 사용된다. 데이터가 전송되거나 저장될 때, 체크섬 알고리즘은 데이터의 일부분을 사용하여 고유한 체크섬 값을 생성한다. 이 체크섬 값은 데이터가 손상되었는지 여부를 나타내는 일종의 데이터 지문으로 볼 수 있다. 데이터를 수신하는 측에서 동일한 알고리즘을 사용하여 수신된 데이터의 체크섬 값을 계산하고, 이를 원본 체크섬 값과 비교하여 데이터의 무결성을 확인한다.</li>
<li>이러한 방식으로 체크섬 알고리즘은 데이터 전송 및 저장 과정에서 발생할 수 있는 오류를 탐지하고 보정하여 데이터 무결성을 유지한다. 이는 파일 전송, 테크워크 통신, 디비 관리 등에서 사용된다.</li>
</ul>
<h2 id="23-efficient-server-scaling---implement-consistent-hashing">23. Efficient Server Scaling - Implement Consistent Hashing</h2>
<blockquote>
<p>효율적인 서버 확장 - 일관성 해싱 구현</p>
</blockquote>
<ul>
<li>분산 시스템에서 해싱은 여러 스토리지 서버 간에 데이터를 분할하여 저장/탐색하기 위해 사용된다. 목표는 데이터가 각 서버에 균일하게 분포하고, 서버의 추가/제거시 데이터의 재분배가 많이 일어나지 않으면 좋겠다.</li>
<li>단순 해싱은 단순하고 직관적이지만, 서버의 추가 또는 제거 시에 데이터의 재분배가 많이 필요하다. 예를 들어, Data(String, Json etc) -&gt; integer 타입의 Key 획득 -&gt; Key % (n-1) -&gt; 해시값(서버 번호) 획득한다.</li>
<li>일관된 해싱은 서버 확장을 위한 고급 해싱 기술로, 서버의 추가/제거에 따른 데이터 재배치를 최소화한다. 해시 함수를 사용하여 데이터를 해시 값으로 변환한 다음, 해당 해시 값을 서버 집합 내의 원형 공간으로 맵핑한다. 각 서버는 이 원형 공간에서 고유한 범위를 가지고 있다. 데이터는 그 해시 값이 서버의 범위에 해당하는 서버에 할당된다.</li>
<li>서버가 추가되거나 제거될 때, 대부분의 데이터는 변경되지 않다. 대신, 새로운 서버의 범위가 추가되고, 일부 데이터가 이에 맞게 재배치된다. (근처에 있는 데이터들) 이 과정에서 전체 데이터의 재배치가 발생하지 않으므로, 일관된 해싱은 시스템의 확장성을 향상시키고 부하 분산을 효율적으로 처리할 수 있다.</li>
</ul>
<h2 id="24-decentralized-data-transfer---consider-gossip-protocol">24. Decentralized Data Transfer - Consider Gossip Protocol</h2>
<blockquote>
<p>분산 데이터 전송 - Gossip Protocol을 고려하자</p>
</blockquote>
<ul>
<li>가십 프로토콜은 분산 시스템에서 정보를 효율적으로 전파하는 방법 중 하나이다. 핵심 아이디어는 &quot;오는 사람이 있다면, 가는 사람이 있다&quot;는 개념이다. 즉, 노드가 다른 노드에게 정보를 전파하면서 동시에 정보를 받아들인다. 이러한 상호작용을 통해 시스템 내의 모든 노드가 비교적 짧은 시간 내에 동일한 정보를 보유하게 된다. 만약 중간에 통신이 안되더라도, 다른 노드에 의해 정보가 동기화된다.</li>
<li>분산 DB의 복제, 분산 시스템의 동기화, P2P 네트워크에서의 파일 공유 등에서 사용된다. 이는 중앙 집중식이 아닌 방식으로 데이터를 전송하고 동기화하는데 유용하며, 노드 간에 정보를 효과적으로 분산시키는데 도움이 된다.</li>
</ul>
<h2 id="25-location-based-functionality---use-quadtree-geohash-etc">25. Location-Based Functionality - Use Quadtree, Geohash, etc</h2>
<blockquote>
<p>위치 기반 기능 - Quadtree, Geohash 등 사용해라</p>
</blockquote>
<p><code>PASS</code></p>
<h2 id="26-avoid-specific-technology-names---use-generic-terms">26. Avoid Specific Technology Names - Use generic terms</h2>
<blockquote>
<p>특정 기술 이름을 피하고 일반적인 용어를 사용해라</p>
</blockquote>
<ul>
<li>구체적인 기술 대신 일반적인 용어 혹은 도메인 용어를 사용하는 것은 코드를 이해하는데 유용하다. 또한, 기술에 종속성이 없기 때문에 기술 컴포넌트를 쉽게 교체할 수 있다.</li>
</ul>
<h2 id="27-high-availability-and-consistency-trade-off---eventual-consistency">27. High Availability and Consistency Trade-Off - Eventual Consistency</h2>
<blockquote>
<p>고가용성과 일관성의 상충 관계 - 최종 일관성</p>
</blockquote>
<p>분산 시스템에서 일관성은 크게 두 가지 레벨로 나뉜다. 강한 일관성(Strong Consistency)과 최종 일관성(Eventually Consistency)이 있다.</p>
<h3 id="1-cap-이론">[1] CAP 이론</h3>
<ul>
<li>CAP 이론은 분산 시스템에서 일관성(Consistency), 가용성(Availability), 네트워크 분할 허용성(Partition Tolerance) 간의 trade-off를 설명하는 이론이다.<ul>
<li>Consistency : 모든 노드가 동일한 데이터를 동일한 시간에 볼 수 있는 것을 의미한다. 즉, 시스템의 모든 복제본이 항상 일관된 상태를 유지한다.</li>
<li>Availability : 시스템이 항상 작동 가능케 한다. 예를 들어, 데이터를 복제하여 main data에 이슈가 있어도 복제된 데이터를 활용한다.</li>
<li>Partition Tolerance : 시스템 내의 노드들 사이에 네트워크 분할이 발생했을 때에도 시스템이 정상적으로 동작할 수 있는 것을 의미한다. ex) Load balancer가 적절한 노드를 선택한다.</li>
</ul>
</li>
<li>일반적으로 CAP 이론은 Partition Tolerance은 반드시 보장되어야 하므로, 시스템은 서비스에 따라 일관성과 가용성 중에서 하나를 선택한다. Trade-Off</li>
</ul>
<h3 id="2-strong-consistency">[2] Strong Consistency</h3>
<ul>
<li>분산 시스템에서 데이터를 &#39;즉시&#39;, &#39;정확하게&#39; 업데이트하는 것을 말한다. 분산 시스템에서 데이터는 한 개가 아니라 가용성을 위해 복제 되있으며, 이를 위해 분산된 데이터를 대상으로 Atomic 업데이트가 필요하다. 이를 위해 각 서비스가 자신들의 데이터를 Atomic 업데이트하는 것을 기다려야한다.</li>
</ul>
<h3 id="3-eventually-consistency">[3] Eventually Consistency</h3>
<ul>
<li>고가용성을 확보하기 위해서는 여러 지점에 데이터를 복제하여 장애나 네트워크 문제로부터 시스템을 보호한다. 그러나 데이터를 여러 지점에 복제할 때 일관성을 유지하는 것은 어려운 문제다. 데이터를 복제한 후에도 모든 복제본이 동일한 값을 갖는 &#39;강한 일관성&#39;을 유지하는 것은 네트워크 지연이나 장애로 인해 비용이 많이 발생할 수 있다.</li>
<li>이에 대한 대안으로 &#39;최종 일관성&#39;이 제안되었다. 이 모델에서는 모든 복제본이 항상 동일한 값을 갖지 않을 수 있지만, 시감이 흐름에 따라 각 복제본이 최종적으로 동일한 값을 가질 것이라는 것이 보장된다. 즉, 일정 시간 동안 업데이트가 모든 복제본에 반영될 수 있도록 충분한 시간을 제공함으로써 최종적으로 일관성을 보장한다.</li>
<li>이러한 &#39;최종 일관성&#39; 모델은 고가용성을 유지하면서 일관성 문제를 완화하느 데 도움이 된다. 다만 일관성을 보장하는데 시간이 걸릴 수 있으므로, 상황에 따라 설정하자. Ex) EDA System</li>
</ul>
<h2 id="28-for-ip-resolution--domain-name-query---mention-dns">28. For IP resolution &amp; Domain Name Query - Mention DNS</h2>
<blockquote>
<p>Ip &lt;-&gt; Domain 이름을 변환할 때 DNS를 사용해라</p>
</blockquote>
<ul>
<li>DNS는 Domain Name System의 약자로, 인터넷에서 도메인 이름을 IP 주소로 변환하거나 반대로 IP 주소를 도메인 이름으로 변환하는 시스템이다.</li>
</ul>
<h2 id="29-handling-large-data-in-network-requests---implement-pagination">29. Handling Large Data in Network Requests - Implement Pagination</h2>
<blockquote>
<p>네트워크 요청의 대용량 데이터 처리 - 페이지 매김 구현</p>
</blockquote>
<ul>
<li>대량의 데이터를 가져올 때 pagination 기법이 사용된다. pagination은 데이터를 여러 페이지로 분할하여 각 페이지에 일정량의 데이터를 제공하는 방법이다.</li>
<li>구현 예시<ol>
<li>요청 시 시작 밑 끝 위치 지정 : 클라이언트가 데이터를 요청할 때, 요청에 시작 위치와(start) 가져올 데이터의 양을(일반적으로 페이지 크기) 지정한다.</li>
<li>서버에서 데이터 추출 : 서버는 해당 요청에 대해 시작 위치부터 일정량의 데이터를 추출한다. 만약 RDBMS를 사용한다면 limit으로 처리할 수 있으나, 페이지가 뒤로 갈수록 성능 저하가 있다. 따라서 특정 필드 기준으로 정렬되있고 where조건을 추가한다면 성능을 향상 시킬 수 있다.<ul>
<li><code>select * from TEST_DB where seq &gt;= &#39;start&#39; limit 0, 20</code></li>
</ul>
</li>
<li>다음 페이지 처리 : 클라이언트는 필요할 경우 다음 페이지에 대한 요청을 보낸다. 이때 이전 요청에서 받은 마지막 데이터의 위치를 시작 위치로 사용하여 데이터를 가져온다. 이러한 과정을 통해 전체 데이터를 순차적으로 검색할 수 있다.</li>
</ol>
</li>
</ul>
<h2 id="30-cache-eviction-policy--preferred-is-lru-least-recently-used-cache">30. Cache Eviction Policy — Preferred is LRU (Least Recently Used) Cache</h2>
<blockquote>
<p>캐시 제거 정책 - LRU(Least Recent Used) 캐시가 선호된다</p>
</blockquote>
<ul>
<li>LRU (Least Recently Used) : 가장 오랫동안 엑세스되지 않은 데이터를 방출하는 방식이다. 다음은 LRU가 가장 선호하는 사유다.<ul>
<li>Locality Of Reference : 대부분의 응용 프로그램에서는 데이터에 대한 엑세스 패턴이 지역성을 가진다. 즉, 최근에 엑세스된 데이터가 가장 높은 확률로 미래에 다시 엑세스될 가능성이 높다. LRU 방식은 이러한 지역성을 활용하여 캐시에 가장 최근에 엑세스된 데이터를 유지함으로써 캐시 히트 비율을 높일 수 있다.</li>
<li>각 데이터의 엑세스 시간을 추적하고, 가자아 오래전에 엑세스된 데이터를 방출하므로 구현이 쉽다.</li>
</ul>
</li>
<li>LFU (Least Frequently Used) : 가장 적게 엑세스된 데이터를 방출하는 방식이다. 이 방식은 얼마나 자주 엑세스되는지를 추적하여 방출할 대상을 결정한다. 자주 엑세스되지 않은 데이터가 캐시를 차지하지 않도록 하기 때문에 특정 데이터 패턴에 적합할 수 있다.</li>
<li>FIFO(First In, First Out) : 캐시에 추가된 순서대로 데이터를 방출한다. 가장 오래된 데이터가 먼저 방출되는 방식으로, 캐시에서 일정 시간 이상 보존되지 않아도 되는 데이터를 관리하는데 유용할 수 있다.</li>
</ul>
<h2 id="31-to-handle-traffic-spikes-implement-autoscaling-to-manage-resources-dynamically">31. To handle traffic spikes: Implement Autoscaling to manage resources dynamically</h2>
<blockquote>
<p>트래픽 급증 처리 : 자동 확장을 구현하여 리소스를 동적으로 관리하세요</p>
</blockquote>
<ul>
<li>Autoscaling(자동확장)은 시스템의 부하나 트래픽 변화에 따라 자동으로 컴퓨팅 리소스 (예 : 가상 머신 인스턴스)를 확장하거나 축소하여 애플리케이션의 성능을 최적화하고 가용성을 유지하는데 사용된다.</li>
<li>자동 확장은 시스템의 트래픽을 지속적으로 모니터링한다. 트래픽이 증가하거나 감소할 때 어떻게 대응할지 결정한다. 예를 들어, TPS, CPU/메모리 사용률 등 특정 임계값을 초과하면 인스턴스를 생성하고, 사용률이 다시 감소하면 인스턴스를 제거하는 정책을 세운다.</li>
<li>자동확장은 트리거가 발생하고 자원이 확장되거나 축소될 때 알람을 보내고, 해당 작업을 로깅하여 모니터링을 지원한다.</li>
<li>예를 들어, 배달 앱 서비스를 운영한다. 앱 특성상 점심/저녁 시간과 같이 특정 이벤트시점에 요청량이 몰릴 수 있다. 그렇다고 해당 서비스의 서버를 최대값으로 운영하기엔 비용이 많이든다. 따라서 필요한 시점에만 리소스를 많이 확보하고 싶다. 이럴때 autoscaling이 효과적일 수 있다.</li>
</ul>
<h2 id="32-need-analytics-and-audit-trails--consider-using-data-lakes-or-append-only-databases">32. Need analytics and audit trails — Consider using data lakes or append-only databases</h2>
<blockquote>
<p>분석 및 감사 추적 필요 — data lakes 혹은 append-only DB 사용을 고려하세요</p>
</blockquote>
<ul>
<li>감사추적 : 시스템의 사용이력, 변경이력, 데이터 생성/가공, 시스템 설정 이력 등 시스템 내에서 일어난 모든 이력에 대한 기록을 의미한다.</li>
</ul>
<h4 id="data-lake">Data Lake</h4>
<ul>
<li>Data Lake란 가공되지 않은 다양한 종류의 데이터를 한 곳에 모아둔 저장소의 집합이다.</li>
<li>빅데이터와 인공지능 기술의 중요성이 커지면서 다양한 영역의 다양한 데이터가 만나 새로운 가치를 만들어내기 시작했다. 이와 같이 빅데이터를 효율적으로 분석 및 사용하고자 다양한 영역의 가공되지 않은 데이터를 한 곳에 모아서 관리하고자 하는 것을 바로 Data Lake라 한다.</li>
<li>Data Lake라는 개념이 나오고 데이터를 한곳에 모으기 시작하였지만 데이터 사용자는 데이터 준비 과정에만 작업 시간의 80%를 소요했다. 이와 같은 문제점을 해결하고자 나온것이 바로 Data Lake Framework이다. 데이터 엔지니어가 데이터 사용자들의 데이터 준비 시간을 단축시켜 주는 것이다. Ex) Apache Hadoop/Spark/Hive, Amazon S3</li>
</ul>
<h4 id="append-only-db">Append Only DB</h4>
<ul>
<li>데이터를 수정하지 않고 새로운 데이터만 추가하는 방식으로 동작한다. 이러한 DB는 주로 감사추적, 금융 서비스 등에서 활용된다.</li>
<li>데이터가 추가되면 변경되지 않으므로 데이터의 &#39;무결성&#39;이 보장되며, 데이터 수정 및 삭제로 인한 문제가 발생하지 않는다.</li>
<li>감사 추적을 위해 모든 데이터 변경 이력을 저장하므로 데이터의 변화를 추적하고 검증할 수 있다.</li>
</ul>
<h2 id="33-handling-large-scale-simultaneous-connections--use-connection-pooling-and-consider-using-protobuf-to-minimize-data-payload">33. Handling Large-Scale Simultaneous Connections — Use Connection Pooling and consider using Protobuf to minimize data payload</h2>
<blockquote>
<p>대규모 동시 연결 처리 — 연결 풀링을 사용하고 Protobuf를 사용하여 데이터 페이로드를 최소화하는 것을 고려하세요.</p>
</blockquote>
<h4 id="connection-pooling">Connection Pooling</h4>
<ul>
<li>풀에 미리 여러 개의 연결을 생성해두고, 클라이언트 요청이 들어올 때마다 풀에서 사용 가능한 연결을 할당하여 사용한다. 연결을 재사용하여 연결을 매번 생성하고 해제하는 오버헤드를 줄인다.</li>
<li>최대한의 연결을 생성하여 풀에 저장해둔다. 이를 통해 시스템이 동시에 처리할 수 있는 최대 연결 수를 제어하고, 과도한 연결 요청이 들어왔을 때도 안정적으로 처리할 수 있다.</li>
<li>풀은 일정 시간 동안 사용되지 않은 연결을 자동으로 해제하거나 재생성하여 연결의 유효성을 유지한다. 이를 통해 불필요한 연결 리소스의 낭비를 방지하고, 시스템의 성능을 최적화한다.</li>
</ul>
<h4 id="protocol-buffer">Protocol Buffer</h4>
<ul>
<li>데이터 페이로드(payload)는 통신에서 전송되는 실제 데이터의 부분을 가리킨다.</li>
<li>Protocol Buffer는 구조화된 데이터를 직렬화하여 전송하는데 사용되는 바이너리 포맷이다. json, xml보다 더 작은 크기의 데이터를 생성하며, 빠르고 효율적인 데이터 전송이 가능하다.</li>
<li>대규모 동시 연결에서는 데이터 전송 속도와 크기가 중요하므로, 프로토콜 버퍼를 사용하여 데이터 페이로드를 최소화하고 네트워크 대역폭을 절약할 수 있다.</li>
</ul>
<hr>
<p>CQRS 패턴도 고려해보자.</p>
<p>_참조 : <a href="https://medium.com/@aqilzeka99/golden-rules-for-system-design-interview-c233295701e2">Golden Rules for System Design Interview</a>
_</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Test Driven Development]]></title>
            <link>https://velog.io/@youngmin-mo/Test-Driven-Development-By-Example</link>
            <guid>https://velog.io/@youngmin-mo/Test-Driven-Development-By-Example</guid>
            <pubDate>Sat, 29 Apr 2023 04:08:15 GMT</pubDate>
            <description><![CDATA[<h2 id="5-tdd">5. TDD</h2>
<blockquote>
<p>Test Driven Development는 <strong>매우 짧은 개발 사이클</strong>을 반복하는 소프트웨어 개발 프로세스 중 하나이다. 개발자는 먼저 요구사항을 검증하는 자동화된 테스트 케이스를 작성한다. 그런 후에, 그 테스트 케이스를 통과하기 위한 최소한의 코드를 생성한다. 마지막으로 작성한 코드를 표준에 맞도록 리팩토링한다.</p>
</blockquote>
<h3 id="1-tdd-cycle">[1] TDD Cycle</h3>
<ol>
<li>작은 테스트를 추가한다.</li>
<li>모든 테스트를 실행하고, 실패하는 것을 확인한다. 컴파일 되지 않을 수도 있다. 프로덕트 코드를 건드리지 않았으니 당연하다.</li>
<li>빨리 테스트가 성공하도록 코드를 개발한다. 어떤 죄악(함수가 무조건 특정 상수를 반환)을 저질러도 괜찮다. 빨리 테스트가 성공하도록 만들자.</li>
<li>모든 테스트를 실행하고, 성공하는 것을 확인한다.</li>
<li><strong>리팩토링한다.</strong><ul>
<li>3번단계에서 죄악을 저질렀다면 이제 모든 케이스에서 성공하도록 여러 테스트 코드를 추가해보자.</li>
<li>테스트는 당연히 실패했고, 이를 성공시키도록 죄악을 치워보자.</li>
<li>기능이 완성되면서 테스트가 성공이된다. 하지만, 코드의 중복이 있다. 코드가 안 이쁠 수 있다. 괜찮다. 테스트코드가 성공하는 내에서 이쁜 코드를 만들어보자. 맘대로 바꿔도된다. 테스트만 성공하면 되니 리팩토링의 두려움도 사라진다.</li>
</ul>
</li>
<li>개발 중 문뜩 &quot;아 이거해야되는데?&quot; 라는 생각이 든다. 이 때 바로 그 작업을 하지말고 메모장에 <strong>To-Do List</strong>를 추가하자. 이후에 하나하나 To-Do List를 제거하자. 이렇게 하는 이유는 <strong>현재 작업하는 부분에 집중</strong>하기 위해서다.</li>
</ol>
<h3 id="2-tdd는-두려움을-관리한다">[2] TDD는 두려움을 관리한다.</h3>
<ul>
<li>여기서 말하는 두려움이란 지금 작성중인 로직이 정상적으로 동작하나?에 대한 의구심이다.</li>
<li>복잡한 구현 알고리즘 문제를 풀 때 생각해보자. 처음부터 끝까지 한번에 프로덕트 코드를 만들고, 한번에 테스트를 하는 경우는 드물다. 물론 똑똑한 개발자라면 가능하지만 적어도 본인은 아니다. 큰 기능을 작은 기능으로 쪼개고, 다양한 데이터를 넣어보면서 테스트를 수행한다. 버그가 나와도 기능을 쪼갰기 때문에 버그 포인트를 쉽게 찾을 수 있다. 즉, <strong>단계를 나누고 디버깅을 통해 검증하면서 나아간다.</strong></li>
<li>TDD도 유사하다. <strong>TDD는 매 순간을 자동화된 테스트를 통해 확인받고 나아가는 개발 방법이다.</strong> 테스트는 테스트 코드가 될 수 있고, curl로 api를 호출할 수도 있고 자동화된 방법이라면 모든 괜찮다고 생각한다. 이전 단계의 기능을 마음 한켠에 신경쓰지 않고 &#39;현재 단계&#39; 로직만 집중하면된다. 개발 중 문뜩 이전 로직을 신경쓰거나 로직 오류가 발생할 때 이전 로직을 신경쓰지 않고 현재 로직에만 집중하면된다. <strong>심리적으로 &#39;이전 로직이 틀렸을까?&#39; 요런 불편한 감정을 최소화한다.</strong></li>
<li>만약 복잡한 프로덕트 코드를 한번에 짜고 테스트를 수행하는데 잘못된 점을 발견하면 어떨까? 아마 지금까지 힘들게 짠 프로덕트 코드를 다 수정해야한다. (수정하지 않더라도 정상적으로 동작하는지 머릿속으로 일일이 확인해야한다.) 아깝다. 하지만 TDD를 통해 이전에 점진적으로 검증받았다면, 이전에 개발한 로직은 신경쓰지 않아도된다.</li>
<li>마지막으로 리팩토링할 때 수월하다. 모든 케이스를 검증할 수 없겠지만 기존 테스트 코드가 있으니, 테스트를 만족하기만 한다면 마음 편히 리팩토링할 수 있다. 이건 테스트 코드의 장점이기도 하다.</li>
</ul>
<h3 id="3-현재-하는-일에-집중한다">[3] 현재 하는 일에 집중한다.</h3>
<ul>
<li>복잡한 기능을 한번에 짜기 힘들다. 따라서 작업을 분할하고 이를 합치는 방향으로 개발한다. 앞서 TDD는 각 단계마다 검증하면서 나아간다고 했다. 따라서 분할한 작업에서 오류가 나온다면 이전 단계의 오류는 신경쓰지 않고 현 단게만 생각하면된다.</li>
<li>앞서 <strong>To-Do List</strong>를 말했다. 작업 중 문뜬 여러 생각이 난다. 이거 고쳐야하고, 이거 추가해야하고 등등 여러가지다. 그런 것들은 필요한데에 메모하고, 원래 하던 작업에 집중한다. 본인은 막 컴퓨터처럼 스위칭이 자유롭지 않다. 원래 하던일만 잘하고 싶다.</li>
</ul>
<h3 id="4-tdd에-대한-개인적인-생각">[4] TDD에 대한 개인적인 생각</h3>
<h4 id="테스트-코드를-먼저짜나-프로덕트-코드를-먼저짜나-그게-무슨-차이가-있지">테스트 코드를 먼저짜나, 프로덕트 코드를 먼저짜나 그게 무슨 차이가 있지?</h4>
<ul>
<li>테스트 코드를 먼저 만들고, 프로덕트 코드를 만드나 순서를 바꾸는 거나 큰 차이는 없다고 생각한다. 다만, 테스트 코드를 먼저 생성한다면 <strong>자연스럽게 단계를 쪼갤 수 있고, 기능을 사용하는 관점에서 먼저 생각할 수 있다.</strong> 물론 객체지향 설계를 잘해서 적절히 코드의 책임을 분배하면 되겠지만, TDD를 통해 자연스럽게 기능을 쪼개기부터 시작할 수 있다.</li>
</ul>
<h4 id="testcode가-많다면-무조건-좋을까">TestCode가 많다면 무조건 좋을까?</h4>
<ul>
<li>테스트 코드가 많은건 edge case를 많이 잡아줄 수 있다는 측면에서 좋다. 하지만 프로덕트 코드보다 테스트 코드를 수정하는데 시간이 많이 걸릴 수도 있다.</li>
<li>결국 우리가 만든 로직의 결함을 잡아주기 위해 테스트 코드가 많으면 좋고, 그렇다고 유지보수할 때 테스트 코드에 너무 시간을 쓰기도 아깝다. 코드 한줄 바꿨는데 수 많은 테스트 코드를 고칠 수도 있다. 본인은 상황에 따라 테스트 코드를 추가하면된다고 생각한다. 즉, 테스트 커버리지를 높이기 위한 테스트는 굳이 필요 없다고 생각한다.</li>
<li>금융, 방산, 보안과 같이 절대 edge case가 나오면 안되는 도메인에서는 시간이 걸리더라도 엄격히 테스트 코드를 추가하는게 맞다고 생각한다.</li>
<li>핵심 로직이 담긴 부분은 나중에 유지보수시 실수로 로직이 깨지는걸 방지하고자 필수 로직에 대한 테스트 코드는 반드시 추가해야한다.</li>
<li>실제 서비스 중 예상치 못한 데이터 인입, 시나리오, 잘못된 함수 사용 등에 대한 테스트 코드는 추가하자.</li>
<li>관성적으로 테스트를 추가하는건 지양하고싶다. 유지보수가 너무 힘들다.</li>
</ul>
<h3 id="5-총평">[5] 총평</h3>
<blockquote>
<p>TDD는 작은 과정을 조금씩 쌓아올려나가는 개발 방법론이다. 각 과정마다 테스트를 통해 확신을 얻고, 현재 작업하는 부분에만 신경쓴다. 이를 위해 적절한 분량의 <strong>단계를 나누고, TO-DO List</strong>를 활용한다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[JDBC 동작원리]]></title>
            <link>https://velog.io/@youngmin-mo/JDBC-%EB%8F%99%EC%9E%91%EC%9B%90%EB%A6%AC</link>
            <guid>https://velog.io/@youngmin-mo/JDBC-%EB%8F%99%EC%9E%91%EC%9B%90%EB%A6%AC</guid>
            <pubDate>Sun, 05 Mar 2023 07:49:22 GMT</pubDate>
            <description><![CDATA[<h2 id="1-jdbc">1. JDBC</h2>
<ul>
<li>Java Database Connectivity 자바와 데이터베이스를 연결하기 위한 JAVA 표준 SQL 인터페이스다. DBMS의 종류(Mysql, MariaDB, Oracle 등)에 상관없이 JVM 위에서 동작하는 모든 애플리케이션들이 JDBC를 통해 <strong>표준 인터페이스</strong>로 DBMS에 연결할 수 있다. 이때, JDBC의 구현체는 각 DB의 Driver며 소켓을 통해 DBMS와 통신한다.</li>
<li>드라이버에 연결할 <code>Connection 객체</code> , 쿼리 수행을 위한 <code>PreparedStatement 객체</code> , DB Record 결과를 담을 <code>ResultSet 객체</code>로 구성되며, 각 객체는 close() 메서드를 통해 닫아줘야 메모리 누수가 생기지 않는다.</li>
<li>사용자가 많은 서비스에서 디비 연결이 있을 때마다 아래와같이 &#39;연결&#39;하고, &#39;해제&#39;하는 반복적인 작업을 한다면 번거롭고, 효율성이 굉장히 떨어지는 과정이다. 이러한 문제를 해결하고자 <code>Connection Pool</code>을 사용한다.<pre><code class="language-java">Connection conn = null;
PreparedStatement  pstmt = null;
ResultSet rs = null;
</code></pre>
</li>
</ul>
<p>try {
    sql = &quot;SELECT * FROM T_BOARD&quot;</p>
<pre><code>// 1. 드라이버 연결 DB 커넥션 객체를 얻음
connection = DriverManager.getConnection(DBURL, DBUSER, DBPASSWORD);

// 2. 쿼리 수행을 위한 PreparedStatement 객체 생성
// 참고로 Statement가 아닌 PreparedStatement를 사용하는 이유는 &#39;보안성&#39;과 &#39;캐시&#39;를 활용하기 때문
pstmt = conn.createStatement();

// 3. executeQuery: 쿼리 실행 후
// ResultSet: DB 레코드 ResultSet에 객체에 담김
rs = pstmt.executeQuery(sql);
} catch (Exception e) {
} finally {
    conn.close();
    pstmt.close();
    rs.close();
}</code></pre><p>}</p>
<p>```</p>
<h2 id="2-dbcp">2. DBCP</h2>
<ul>
<li>Database Connection Pool : Pool 속에 디비와 연결할 연결 객체(Connection)를 미리 여러 개 만들어둔다. 디비에 접근 요청이 왔을 때, 그 중 하나의 연결 객체(Connection)를 할당하여 사용하고, 사용이 종료되면 연결 객체를 반납하는 방식이다.</li>
<li>커넥션을 계속해서 재 사용하기 때문에 생성되는 최대 커넥션 수를 제한할 수 있으며, Connection Pool의 Connection이 없을 경우 사용자는 커넥션이 반활될 때까지 순서대로 대기한다. 이때 지정한 &#39;Timeout&#39; 시간이 만료되면 예외를 던진다.</li>
</ul>
<h2 id="3-was와-dbms의-통신-시-timeout-계층">3. WAS와 DBMS의 통신 시 Timeout 계층</h2>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/4eca7134-ab26-486d-9b8f-08603c8519c1/image.png" alt=""></p>
<ul>
<li>상위 레벨의 Timeout은 하위 레벨의 Timeout에 의존성을 가진다. 예를 들어, JDBC Driver SocketTimeout이 정상으로 동작하지 않으면, 그 보다 상위 레벨의 Timeout인 StatementTimeout, Transaction Timeout도 정상적으로 동작하지 않는다.</li>
</ul>
<h3 id="1-transactiontimeout">[1] TransactionTimeout</h3>
<ul>
<li>프레임워크(Spring, EJB Container etc)나 애플리케이션 레벨에서 유효한 타임아웃이다.</li>
<li>여러 쿼리를 트랜젝션으로 묶고 수행할 때, 지정된 시간을 초과하면 발생한다.</li>
<li>Spring에서 제공하는 TransactionTimeout은 매우 단순하다. 해당 Transaction의 시작 시간과 경과 시간을 기록하면서, 특정 이벤트 발생 시 경과 시간을 확인하여 타임아웃 이상일 경우 예외(Exception)를 발생하도록 하고 있다.</li>
</ul>
<h3 id="2-statementtimeout">[2] StatementTimeout</h3>
<ul>
<li>Statement 하나가 얼마나 오래 수행되어도 괜찮은지에 대한 한계 값이다. JDBC API인 Statement에 타임아웃 값을 설정하며, 이 값을 바탕으로 JDBC 드라이버가 StatementTimeout을 처리한다. JDBC API인 java.sql.Statement.setQueryTimeout(int timeout) 메서드로 설정한다. 즉, 쿼리 하나를 수행할 때, 지정된 시간을 초과하면 발생한다.</li>
<li>iBatis를 예로 들어 설명하자면 &quot;sql-map-config.xml&quot; 파일의 sqlMapConfig/settings에 @defaultStatementTimeout 값으로 기본값을 설정할 수 있다. 또한 &quot;sql-map.xml&quot; 파일의 statement, select, insert, update 구문마다 @timeout 값으로 개별적으로 설정할 수 있다.
<img src="https://velog.velcdn.com/images/youngmin-mo/post/1a97dcc2-0461-4d0e-9cb6-ece900f46b60/image.png" alt=""></li>
</ul>
<h4 id="mysql-jdbc-statement508-버전의-querytimeout">MySQL JDBC Statement(5.0.8 버전)의 QueryTimeout</h4>
<ol>
<li>Connection.createStatement() 메서드를 호출하여 Statement를 생성한다.</li>
<li>Statement.executeQuery() 메서드를 호출한다.</li>
<li>Statement는 내부 Connection을 사용하여 MySQL DBMS로 쿼리를 전송한다.</li>
<li>Statement는 타임아웃 처리를 위해 새로운 타임아웃 처리용 스레드를 생성한다. 5.1.x 버전에서는 Connection에 한 개의 스레드가 할당되는 것으로 변경되었다.</li>
<li>스레드에 타임아웃 처리를 등록한다.</li>
<li>타임아웃이 발생한다.</li>
<li>타임아웃 처리 스레드가 Statement와 동일한 설정의 Connection을 생성한다.</li>
<li>생성된 Connection을 사용하여 취소 쿼리(KILL QUERY &quot;connectionId&quot;)를 전송한다.</li>
</ol>
<h3 id="3-jdbc-driver-sockettimeout">[3] JDBC Driver SocketTimeout</h3>
<ul>
<li>JDBC 드라이버의 SocketTimeout 값은 DBMS가 비정상으로 종료되었거나 네트워크 장애(기기 장애 등)가 발생했을 때 필요한 값이다. TCP/IP의 구조상 소켓에는 네트워크의 장애를 감지할 수 있는 방법이 없다. 그렇기 때문에 애플리케이션은 DBMS와의 연결 끊김을 알 수 없다. 이럴 때 SocketTimeout이 설정되어 있지 않다면 애플리케이션은 DBMS로부터의 결과를 무한정 기다릴 수도 있다.</li>
<li>이러한 상태를 방지하기 위해 소켓에 타임아웃을 설정해야 한다. SocketTimeout은 JDBC 드라이버에서 설정할 수 있다. SocketTimeout을 설정하면 네트워크 장애 발생 시 무한 대기 상황을 방지하여 장애 시간을 단축할 수 있다.</li>
<li>단, SocketTimeout 값을 Statement의 수행 시간 제한을 위해 사용하는 것은 바람직하지 않다. 그러므로 SocketTimeout 값은 StatementTimeout 값보다는 크게 설정해야 한다. SocketTimeout값이 StatementTimeout보다 작으면, SocketTimeout이 먼저 동작하므로 StatementTimeout 값은 의미가 없게 되어 동작하지 않는다.</li>
<li>즉, 소켓을 이용해서 데이터를 주고받을 때, 지정된 시간을 초과하면 발생한다.</li>
</ul>
<h4 id="참고">참고</h4>
<ol>
<li>SocketTimeout에 의해 종료되더라도, DBMS에서 실행중인 쿼리가 종료되는 것은 아니다. 만약 쿼리의 timeout 값을 설정하고 싶다면 StatementTimeout를 사용하자. SocketTimeout 값보다 StatementTimeout 값이 더 작으면, SocketTimeout 발생 전에 StatementTimeout에 의하여 쿼리가 종료될 것이다. 그렇다면 Socket 통신이 중지되더라도 이미 StatementTimeout에 의하여 종료되기 때문에, 불필요한 쿼리 실행을 방지할 수 있다.</li>
<li>mysql의 long transaction exception은 mysql system 값 &#39;innodb_lock_wait_timeout&#39;보다 쿼리 실행 시간이 넘어설 경우 발생한다. 이를 없애고 싶다면 당연히 쿼리를 빠르게 처리하는게 맞긴하다. 하지만 최적화 했음에도 지속하여 발생하고, 이 에러를 없애고 싶다면 (가령 ddl lock(metadata lock)을 방지하고자) StatementTimeout를 값을 &#39;innodb_lock_wait_timeout&#39;보다 작게 가져가자.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Batch란?]]></title>
            <link>https://velog.io/@youngmin-mo/Spring-Batch%EB%9E%80</link>
            <guid>https://velog.io/@youngmin-mo/Spring-Batch%EB%9E%80</guid>
            <pubDate>Wed, 25 Jan 2023 13:31:36 GMT</pubDate>
            <description><![CDATA[<h2 id="1-spring-batch란">1. Spring Batch란?</h2>
<ul>
<li>Spring Batch는 로깅/추적, 트랜잭션 관리, 작업 처리 통계, 작업 재시작, 리소스 관리 등 대용량 레코드 처리에 필수적인 기능을 제공한다. 또한 최적화 및 파티셔닝 기술을 통해 대용량 및 고성능 배치 작업을 가능하게 하는 고급 기술 서비스 및 기능을 제공한다.</li>
<li>Spring Batch에서 배치가 실패하여 작업 재시작을 하게 된다면 처음부터가 아닌 실패한 지점부터 실행을 하게 된다.</li>
<li>또한 중복 실행을 막기 위해 성공한 이력이 있는 Batch는 동일한 Parameters로 실행 시 Exception이 발생하게 된다.</li>
</ul>
<h4 id="spring-batch-vs-scheduler">Spring Batch vs Scheduler?</h4>
<ul>
<li>Spring Batch는 Scheduler가 아니기에 비교 대상이 아니다.</li>
<li>Spring Batch는 Batch Job을 관리하지만 Job을 구동하거나 실행시키는 기능은 지원하고 있지 않는다. Spring에서 Batch Job을 실행시키기 위해서는 Quartz, Scheduler, Jenkins등 전용 Scheduler를 사용하여야 한다.</li>
</ul>
<h2 id="2-spring-batch-용어">2. Spring Batch 용어</h2>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/f5139cd5-d163-448d-9264-8557edb1c2d4/image.png" alt=""></p>
<h3 id="1-job">[1] Job</h3>
<ul>
<li>Job은 배치 처리 과정을 하나의 단위로 만들어 놓은 객체다. 또한 배치 처리 과정에 있어 전체 계층 최상단에 위치한다.</li>
</ul>
<h3 id="2-jobinstance">[2] JobInstance</h3>
<ul>
<li>JobInstance는 Job의 실행 단위를 나타낸다. Job을 실행시키면 하나의 JobInstance가 생성된다. 예를 들어 하나의 Job을 1월 1일 실행하고, 1월 2일에 실행을 하게 된다면, 각각의 JobInstance가 생성된다. 만약 1월 1일에 실행한 JobInstance가 실패하여 다시 실행을 시키더라도, 이 JobInstance는 1월 1일에 대한 데이터만 처리하게 된다.</li>
</ul>
<h3 id="3-jobparameters">[3] JobParameters</h3>
<ul>
<li>JobInstance는 Job의 실행 단위라고 했다. 그렇다면 각각의 JobInstance는 어떻게 구별할까? 이는 JobParameters 객체로 구분하게 된다. JobParameters는 JobInstance 구별 외에도 개발자 JonInstance에 전달하는 매개변수 역할도 하고 있다.</li>
<li>JobParameters는 String, Double, Long, Date 4가지 형식만을 지원하고있다.</li>
</ul>
<h3 id="4-jobexecution">[4] JobExecution</h3>
<ul>
<li>JobExecution은 JonInstance에 대한 실행 시도에 대한 객체다. 1월 1일에 실행한 JobInstance가 실패하여 재실행을 하여도 동일한 JobInstance를 실행시키지만 이 2번에 실행에 대한 JobExecution은 개별로 생기게 된다. JobExecution는 이러한 JobInstance 실행에 대한 상태, 시작시간, 종료시간, 생성시간 등의 정보를 담고있다.</li>
</ul>
<h3 id="5-step">[5] Step</h3>
<ul>
<li>Step은 Job의 배치 처리를 정의하고 순차적인 단계를 캡슐화한다.</li>
<li>Job은 최소 1개 이상의 Step을 가지며, Job의 실제 일괄 처리를 제어하는 모든 정보가 들어있다.</li>
</ul>
<h3 id="6-stepexecution">[6] StepExecution</h3>
<ul>
<li>StepExecution은 JobExecution과 동일하게 Step 실행 시도에 대한 객체를 나타낸다. 하지만 Job이 여러개의 Step으로 구성되어 있을 경우 이전 단계의 Step이 실패하게 되면 다음 단계가 실행되지 않음으로 실패 이후 StepExecution은 생성되지 않는다. StepExecution 또한 JobExecution과 동일하게 실제 시작이 될 떄만 생성된다.</li>
<li>StepExecution에는 JobExecution에 저장되는 정보 외에 read 수, write 수, commit 수, skip 수 등의 정보들도 저장된다.</li>
</ul>
<h3 id="7-executioncontext">[7] ExecutionContext</h3>
<ul>
<li>ExecutionContext란 Job에서 데이터를 공유 할 수 있는 데이터 저장소다. Spring Batch에서 제공하는 ExecutionContext는 JobExecutionContext, StepExecutionContext 2가지 종류가 있으나 이 두가지는 지정되는 범위가 다르다.</li>
<li>JobExecutionContext의 경우 Commit 시점에 저장되는 반면 StepExecutionContext는 실행 사이에 저장이 되게 된다.</li>
<li>ExecutionContext를 통해 Step간 Data 공유가 가능하며 Job 실패시 ExecutionContext를 통한 마지막 실행 값을 재구성 할 수 있다.</li>
</ul>
<h3 id="8-jobrepository">[8] JobRepository</h3>
<ul>
<li>JobRepository는 위에서 말한 모든 배치 처리 정보를 담고있는 매커니즘이다. Job이 실행되면 JobRepository에 JobExecution과 StepExecution을 생성하게 되며 JobRepository에서 Execution 정보들을 저장하고 조회하며 사용하게된다.</li>
</ul>
<h3 id="9-joblauncher">[9] JobLauncher</h3>
<ul>
<li>JobLauncher는 Job과 JobParameters를 사용하여 Job을 실행하는 객체다.</li>
</ul>
<h3 id="10-itemreader">[10] ItemReader</h3>
<ul>
<li>ItemReader는 Step에서 Item을 읽어오는 인터페이스다. ItemReader에 대한 다양한 인터페이스가 존재하며 다양한 방법으로 Item을 읽어 올 수 있다.</li>
</ul>
<h3 id="11-itemwriter">[11] ItemWriter</h3>
<ul>
<li>ItemWriter는 처리 된 Data를 Writer 할 때 사용한다. Writer는 처리 결과물에 따라 Insert가 될 수도 Update가 될 수도 Queue를 사용한다면 Send가 될 수도 있다. Writer 또한 Read와 동일하게 다양한 인터페이스가 존재한다. Writer는 기본적으로 Item을 Chunk로 묶어 처리하고 있다.</li>
</ul>
<h3 id="12-itemprocessor">[12] ItemProcessor</h3>
<ul>
<li>Item Processor는 Reader에서 읽어온 Item을 데이터를 처리하는 역할을 하고 있다. Processor는 배치를 처리하는데 필수 요소는 아니며 Reader, Writer, Processor 처리를 분리하여 각각의 역할을 명확하게 구분하고 있다.</li>
</ul>
<h2 id="3-spring-batch-사용하기">3. Spring Batch 사용하기</h2>
<ul>
<li>Spring Batch에서의 Job은 여러가지 Step의 모음으로 구성되어 있으며, Job은 순차적인 Step을 수행하며 Batch를 수행하게 된다.</li>
<li>Step은 Tasklet 처리 방식과 Chunk 지향 처리 방식을 지원하고 있다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/60825988-e105-41dc-855f-3290f1682ab1/image.png" alt=""></p>
<h3 id="2-tasklet">[2] Tasklet</h3>
<ul>
<li>Tasklet은 하나의 메서드로 구성 되어있는 간단한 인터페이스다.</li>
<li>이 메서드 는 실패를 알리기 위해 예외를 반환 하거나 throw할 때까지 execute를 반복적으로 호출하게된다 .</li>
<li>Taslket에서는 @BeforeStep, @AfterStep을 통해 execute 배치 실행 전 후에 Event를 등록하여 실행 시킬 수 있다.</li>
<li>일반적으로 리소스를 삭제하거나 쿼리를 실행하는 것과 같은 단일 태스크를 호출하는 시나리오에 사용된다.
<img src="https://velog.velcdn.com/images/youngmin-mo/post/5a295548-3b61-4421-aa46-38a9f168a004/image.png" alt=""></li>
</ul>
<h3 id="3-chunk">[3] Chunk</h3>
<ul>
<li>Spring Batch에서 Chunk란 처리 되는 커밋 row 수를 의미한다. Batch 처리에서 커밋 되는 row 수라는 건 chunk 단위로 Transaction을 수행하기 때문에 실패시 Chunk 단위만큼 rollback이 되게 된다.</li>
<li>Chunk 지향 처리에서는 다음과 같은 3가지 시나리오로 실행된다<ul>
<li>Read : Database에서 배치처리를 할 Data를 읽어온다</li>
<li>Processing : 읽어온 Data를 가공,처리를 한다 (필수사항X)</li>
<li>Write : 가공,처리한 데이터를 Database에 저장한다.</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/378fac3a-3deb-437c-8fb4-a870e53283e4/image.png" alt=""></p>
<pre><code class="language-java">List items = new Arraylist();
for(int i = 0; i &lt; commitInterval; i++){
    Object item = itemReader.read()
    Object processedItem = itemProcessor.process(item);
    items.add(processedItem);
}
itemWriter.write(items);</code></pre>
<h4 id="적절한-paging-size와-chunk-size에-관하여">적절한 Paging Size와 Chunk Size에 관하여..</h4>
<ul>
<li>Spring Batch에는 다양한 IremReader, ItemWriter가 조재한다. 대용량 배치 처리를 하게되면 Item을 읽어 올 때 Paging 처리를 하는게 효과적이다.</li>
<li>Spring Batch Reader에서는 이러한 Paging 처리를 지원하고 있다. 또한 적절한 Paging 처리와, Chunk Size(한번에 처리 될 트랜잭션)를 설정하여 더욱 효과적인 배치 처리를 할 수 있다.</li>
<li>Paging Size가 5이며 Chunk Size가 10일 경우 2번의 Read가 이루어진 후에 1번의 Transaction이 수행된다. 이는 한번의 Transaction을 위해 2번의 쿼리 수행이 발생하게 된다.</li>
<li>따라서 Read 쿼리 수행시 1번의 Transaction을 위해 두 설정의 값을 일치를 시키는게 가장 좋은 성능 향상 방법이며 특별한 이유가 없는 한 Paging Size 와 Chunk Size를 동일하게 설정하는 것을 추천한다.</li>
</ul>
<h4 id="pagingreader-사용-시-주의사항">PagingReader 사용 시 주의사항</h4>
<ul>
<li>페이징 처리 시 각 쿼리에 Offset과 Limit를 지정해 주어야 하는데 이는 PageSize를 지정하면 Batch에서 Offset과 Limit를 지정해준다. 하지만 페이징 처리를 할 때 마다 새로운 쿼리를 실행하기 때문에 데이터 순서가 보장 될 수 있도록 반드시 Order By를 사용하여야 한다.</li>
<li>참고로 Chunk Size는 한번에 처리될 트랜잭션 단위이며, Page Size는 한번에 조회할 Item 양을 의미한다.</li>
</ul>
<h2 id="4-spring-batch-관련-페이징-참고">4. Spring Batch 관련 페이징 참고</h2>
<ul>
<li>디비에서 데이터를 읽는데 20개씩 끊어서 읽어오고 싶다. 그렇다고 <code>limit</code> 문법으로 20개씩 끊어서 데이터를 읽는다면 처음에는 괜찮지만 후반부에는 성능이 풀스캔과 비슷하게 성능이 나빠진다. 따라서 Spring Batch에서 제공하는 paging 기법을 활용하여, 틀정 컬럼의<code>Where</code> 조건절로 데이터 조건절을 계속해서 줄이고 limit 0, 20으로 초반부 데이터만 읽게한다.</li>
<li>Spring Batch를 통해 페이지를 끊어서 읽는데, 이는 하나의 커넥션을 너무 길게 가져가는 걸 방지한다. 그런데 1억개의 데이터를 읽는데 Page를 2000개씩 끊는다면 I/O가 너무도 빈번할 것이다. 물론 BE개발자 입장에서 쿼리에 비즈니스 로직을 담는건 막고 싶지만 성능이 나쁜것은 어쩔 수 없다. 따라서 이러한 경우엔 group by 등 쿼리 단에서 처리가 필요할 것이다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Coroutine]]></title>
            <link>https://velog.io/@youngmin-mo/Coroutine</link>
            <guid>https://velog.io/@youngmin-mo/Coroutine</guid>
            <pubDate>Tue, 24 Jan 2023 05:57:45 GMT</pubDate>
            <description><![CDATA[<h2 id="1-coroutine이란">1. Coroutine이란?</h2>
<ul>
<li>어떠한 코루틴이 발동될 때 마다 해당 코루틴은 이전에 자신의 실행이 마지막으로 중단되었던 지점 다음의 장소에서 실행을 재개한다.</li>
<li>kotlin의 coroutine은 suspend 키워드로 마킹된 함수를 CPS(Continuation Passing Style)로 변환하고, 이를 Coroutine Builder를 통해 적절한 스레드 상에서 시나리오에 따라 동작하도록 구성된다.</li>
<li>주의할 점은 suspend function은 스레드와 스케줄의 관리를 수행하는 것이 아니라, 비동기 실행을 위한 중단(suspencion) 지점의 정의라는 점이다. 코루틴은 중단 지점까지 비선점형으로 동작하기 때문에 실행 스케줄이 os에 의해 온전히 제어되는 스레드와는 다른 관점에서 보아야 한다.</li>
<li>스레드는 잘 실행하다가 갑자기 os가 나오라면 제어권을 양도하고 비켜야 하는데, 코루틴은 중단 지점을 만나지 않는 한 제어권을 양도하지 않고 이어서 계속 실행한다.</li>
<li>코루틴은 스레드와 기능적으로 같지만, 스레드에 비교하면 좀 더 가볍고, 유연하게 동시성 프로그래밍을 지원한다. 즉, 하나의 스레드 내에서 여러 개의 코루틴이 실행되는 개념이다.</li>
<li>코루틴은 코틀린만의 것이 아니다. 이름이 비슷해서 코틀린의 것이라고 생각할 수 있지만 파이썬, C#, Go, Javascript 등 여러 언어에서 지원하고 있는 개념이다.</li>
</ul>
<h3 id="몇-가지-질문">몇 가지 질문</h3>
<ul>
<li><p>병렬 I/O를 위해서는 멀티스레드를 사용하거나, 이벤트큐 &amp; 큐를 사용하는 방법 뿐이었는데 코루틴 CPS를 이용하면 단일 스레드 하에서 병렬 작업이 가능하다.</p>
<ul>
<li>코루틴이 스레드를 대체할 수 있나?<ul>
<li>코루틴은 스레드 위에서 돌아가는 것이지만, 멀티스레드 대체 관점에서는 부분적으로는 가능. (1 스레드 위에 n개 코루틴)</li>
<li>I/O Bound 작업은 가능한데(단일 스레드에서도 소화 가능한 수준이라면), CPU Bound 작업은 코루틴이 돌아가는 기반 스레드 풀 수를 늘려줘야한다.</li>
</ul>
</li>
<li>다중 스레드 환경에서 코루틴은?<ul>
<li>코루틴의 A 부분은 1번 스레드에서 실행 -&gt; suspend 마주치고 대기하닥 실행 -&gt; B번은 2번 스레드에서 실행 요렇게!</li>
<li>이건 Dispatcher에 따라 다르다. unconfined vs confined</li>
</ul>
</li>
<li>Dispatchers.Default 스레드 풀은 CPU core 수와 동일하게 구성되며, core가 1개인 경우는 2개로 구성된다.</li>
</ul>
</li>
<li><p>Blocking Function을 사용해야 하는 경우, 어떻게 coroutine으로 wrapping해야되나요?</p>
<ul>
<li>Cpu bound 작업이면 Dispatchers.Default, I/O Bound 작업이면 Dispatchers.IO에 던진다.</li>
<li>아마 별도의 스레드로 던지겠지?</li>
</ul>
</li>
</ul>
<h2 id="2-특징">2. 특징</h2>
<h3 id="1-협력형-멀티-태스킹">[1] 협력형 멀티 태스킹</h3>
<ul>
<li>Co[협력/함께] + Routine[태스크/함수] : 협력하는 함수</li>
<li>코루틴도 routine이기 때문에, 하나의 함수로 생각하자. 그런데 이 함수에 진입할 수 있는 진입점도 여러개고, 함수를 빠져나갈 수 있는 탈출점도 여러개다. 즉, 코루틴 함수는 꼭 &#39;return&#39;문이나 마지막 닫은 괄호를 만나지 않더라도 언제든지 중간에 빠져나갈 수 있고, 언제든지 다시 나갔던 그지점으로 돌아올 수 있다.<img src="https://velog.velcdn.com/images/youngmin-mo/post/2dab4ae4-39f6-4e0b-aa99-0ab4bad771ab/image.png" width =50%/>

</li>
</ul>
<h4 id="예시">예시</h4>
<pre><code class="language-kotlin">fun drawPerson() {
    startCoroutine {
        drawHead()
        drawBody()
        drawLegs()
    }
}

suspend fun drawHead() {
    delay(2000)
}

suspend fun drawBody() {
    delay(2000)
}

suspend fun drawLegs() {
    delay(2000)
}</code></pre>
<ol>
<li>쓰레드의 Main함수가 drawPerson()을 호출하면 startCoroutine블럭을 만나 코루틴이 된다(정확하게는 하나의 코루틴을 만들어 시작한다). 위에도 말했듯이 이제 drawPerson()은 진입점과 탈출점이 여러개가 되는 자격이 주어진 것이다.</li>
<li>코루틴이 실행되었지만, suspend를 만나기 전까지는 그다지 특별한 점은 없다. suspend로 정의된 함수가 없다면 그냥 마지막 괄호를 만날 때 까지 계속 실행된다. 그러나 drawHead()는 suspend 키워드로 정의되어진 함수다. 따라서 drawHead() 부분에서 더 이상 아래 코드를 실행하지 않고 drawPerson()이라는 코루틴 함수를 (잠시)탈출한다.</li>
<li>메인 스레드가 해당 코루틴을 탈출했다. 그렇다고 쓰레드가 놀고 있을리는 없다. 우리가 짜 놓은 다른 코드들을 실행할 수도 있고, 안드로이드라면 UI 애니메이션을 처리 할 수도 있다. 그러나 Head는 어디선가 계속 그려지고 있다. drawHead()는 2초가 걸리는 suspend 함수였음을 기억해보자. drawHead()라는 suspend를 만나 코루틴을 탈출했지만, drawHead() 함수의 기능은 메인쓰레드에서 동시성 프로그래밍으로 작동하고 있을수도 있고, 다른 쓰레드에서 돌아가고 있을 수도 있다. 그것은 개발자가 자유롭게 선택할 수 있다.</li>
<li>그렇게 메인쓰레드가 다른 코드들을 실행하다가도, drawHead()가 제 역할을 다 끝내면 다시 아까 탈출했던 코루틴 drawPerson()으로 돌아온다. 아까 멈추어놓았던 drawHead() 아래인 drawBody()부터 재개(resume)된다.</li>
</ol>
<h3 id="2-동시성-프로그래밍-지원">[2] 동시성 프로그래밍 지원</h3>
<ul>
<li><p>함수를 중간에 빠져나왔다가, 다른 함수에 진입하고, 다시 원점으로 돌아와 멈추었던 부분부터 다시 시작하는 이 특성은 동시성 프로그래밍을 가능하게 한다. 여기서 어떻게 함수를 중간에 왔다 갔다 할수 있는거지에 대한 내용은 CPS(Continuation Passing Style)를 참고하자.</p>
<ul>
<li>동시성 프로그래밍이란 오른쪽 손에만 펜을 쥐고서 왼쪽 도화지에 사람 일부를 조금 그리고, 오른쪽 도화지에 가서 잠시 또 사람을 그리고, 다시 왼쪽 도화지에 사람을 찔끔 그리고… 이 행위를 아주 빨리 반복하는 것이다. 사실 내가 쥔 펜은 한 순간에 하나의 도화지에만 닿는다. 그러나 이 행위를 멀리서 본다면 마치 동시에 그림이 그려지고 있는 것 처럼 보일 것이다. 이것이 동시성 프로그래밍이다.</li>
<li>병렬성 프로그래밍은 이 것과 다르다. 병렬성은 실제로 양쪽 손에 펜을 하나씩 들고서 왼쪽과 오른쪽에 실제로 동시에 그리는 것이다. 같은 시간동안 두 개의 그림을 그리는 것이다.</li>
</ul>
</li>
<li><p>코루틴은 개념자체로만 보면 병렬성이아니라 동시성을 지원하는 개념이다</p>
</li>
<li><p>코루틴을 생성해서 동시성 프로그래밍을 하는 것은, 쓰레드를 사용해서 동시성 프로그래밍을 하는 것과 차원이 다른 효율성을 제공한다.</p>
</li>
<li><p>context-switching 비용, 스레드 경량화의 이유로 -&gt; 2,000개 미만의 스레드에는 1.5GB이상의 메모리가 필요한데 100만 개의 코루틴은 700MB 미만의 메모리가 필요하다. 결론은 코루틴은 매우 가볍다는 것이다.</p>
<h4 id="예시-1">예시</h4>
</li>
<li><p>코루틴도 루틴이다. 즉, 스레드가 아니라 일반 서브루틴과 비슷한 루틴이기 때문에 하나의 스레드에 여러개가 존재할 수 있다.</p>
</li>
</ul>
<pre><code class="language-kotlin">suspend fun drawPersonA() {
    startCoroutine {
        drawHead()
        drawBody()
        drawLegs()
    }
}

suspend fun drawPersonB() {
    startCoroutine {
        drawHead()
        drawBody()
        drawLegs()
    }
}

suspend fun drawHead() {
    delay(2000)
}

suspend fun drawBody() {
    delay(2000)
}

suspend fun drawLegs() {
    delay(2000)
}</code></pre>
<ol>
<li>메인 스레드에서 drawPersonA, drawPersonB 두개의 함수를 순서대로 호출한다고 해보자. (메인 스레드에 코루틴이 2개 있다.)</li>
<li>먼저 drawPersonA를 실행하면 startCoroutine {} 블럭으로 인해 코루틴이 되고, 함수를 중간에 나갔다가 다시 들어올 수 있는 힘을 얻게된다. 이후 suspend함수인 drawHead()를 만나게 되면 이 코루틴을 잠시 빠져나간다.</li>
<li>drawPersonA 코루틴을 빠져나갔지만 그렇다고 메인 스레드가 가만히 놀고있진 않는다. 다른 suspend 함수들을 찾거나 resume되어진 다른 코드들을 찾는다. drawPersonA 코루틴의 경우 2초 동안 drawHead()작업을 하게된다. 그러나 delay(2000)는 쓰레드를 블락시키지 않으므로 다른 일들을 할 수가 있다. 뿐만 아니라 drawHead() 함수 안에서 다른 스레드를 실행시킨다면 병행적으로 실행이 가능하다. drawPersonB() 함수를 만나게 되어 또 한번 suspend 함수를 만나게 되면 같은 현상이 발생한다. 즉, drawPersonA, drawPersonB에 대한 코루틴이 아주 빠르게 왔다 갔다하면서 실행된다. 이렇게 코루틴을 이요하면 하나의 스레드에서 동시성 프로그래밍이 가능해진다.</li>
</ol>
<h3 id="3-비동기-처리를-쉽게-도와준다">[3] 비동기 처리를 쉽게 도와준다.</h3>
<pre><code class="language-kotlin">
suspend goCompany(person: Person) {
   val wakeupPerson = wakeup(person)
   val takeShowerPerson = takeShower(wakeupPerson)
   val putOnShirtPerson = putOnShirt(takeShowerPerson)
   ...</code></pre>
<ul>
<li>언제 끝날지 모르는 비동기 작업들이지만 각자 함수들의 순서는 정확히 지켜진다. takeShower()함수는 wakeUp()함수가 끝나야만 실행되고, putOnShirt()함수는 takeShower()함수가 끝나야만 실행된다.</li>
<li>이게 가능한 이유는, goCompany라는 함수가 코루틴이기에 wakeUp을 만나면 wakeUp함수를 실행함과 동시에(여기서는 백그라운드 스레드에서 동시에 실행될 것이다.) 잠시 goCompay를 빠져나간다. 그러다가 wakeUp이 자신의 일을 끝마치면 다시 goCompany로 돌아올 수 있기 때문이다. 이게 코루틴으로 비동기 처리를 할 때 생기는 장점이다.</li>
<li>만약 코루틴이 아닌 callback이라면 &#39;callback hell&#39;현상이 나오고, 만약 rxkotlin을 활용한다면 우리가 알고자하는 것 이외에 여러 기능을 추가적으로 학습해야 코드 파악이 될 것이다.</li>
<li>추가로 비동기 처리의 궁극적인 모습은 마치 비동기 코드가 아닌것 처럼 짜는 모습이 아닐까 생각한다.</li>
</ul>
<p><a href="https://wooooooak.github.io/kotlin/2019/08/25/%EC%BD%94%ED%8B%80%EB%A6%B0-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EA%B0%9C%EB%85%90-%EC%9D%B5%ED%9E%88%EA%B8%B0/">https://wooooooak.github.io/kotlin/2019/08/25/%EC%BD%94%ED%8B%80%EB%A6%B0-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EA%B0%9C%EB%85%90-%EC%9D%B5%ED%9E%88%EA%B8%B0/</a></p>
<h2 id="3-coroutine-sope">3. Coroutine Sope</h2>
<ul>
<li>코루틴은 코루틴 스코프 안에서만 동작한다. 특히 일시 중단 함수 즉 await(), join(), delay(), suspend 메소드는 코루틴 스코프 안에서만 호출이 가능하다. 위 api를 코루틴 스코프가 아닌 다른 영역에서 사용하면 컴파일 에러가 발생한다.</li>
</ul>
<h3 id="1-globalscope">[1] GlobalScope</h3>
<ul>
<li>GlobalScope는 CoroutineScope의 한 종류로써 가장 큰 특징은 Application이 시작하고 종료될 때까지 계속 유지가 된다. Singletone 이기 때문에 따로 생성하지 않아도 되며 어디에서든 바로 접근이 가능하여 간단하게 사용하기 쉽다는 장점이 있다.</li>
<li>단점으로는 GlobalScope를 사용하면 메모리 누수의 원인이 될 수 있기 때문에 신중히 사용해야 한다. 즉 다시 말해 앱이 실행된 이 후 계속 수행이 되어야 한다면 GlobalScope 를 사용해야 하는 것이고 특정 Activity나 Service 에서만 잠깐 사용하는 것이라면 GlobalScope를 사용하면 안된다.</li>
<li>해당 스코프 내에 실행되는 것들은 비동기로 실행된다.</li>
</ul>
<pre><code class="language-kotlin">GlobalScope.launch {
    //run coroutine
}</code></pre>
<h3 id="2-runblocking">[2] RunBlocking</h3>
<ul>
<li>runBlocking 에 포함된 코루틴 로직을 모두 수행 한 후 넘어간다. (blocking) 다른 말로 하면 runBlocking의 코루틴이 모두 실행될 때까지 쓰레드가 대기하기 때문이다.</li>
<li>아래 예시에서는 runblocking이기 때문에 &#39;step 1&#39; -&gt; &#39;step 2&#39;가 출력될 것이다. 만약 RunBlocking 대신 GlobalScope.launch로 바꾸면 Step 2 만 출력이 된다. 왜나하면 GlobalScope.launch 는 메인쓰레드를 블록킹하지 않으며 코루틴과 main() 로직을 병렬적으로 실행이 된다. 다만 코루틴이 종료되기 전에 main() 이 먼저 종료되기 때문에 step1이 출력되지 않는다.</li>
<li>runBlocking() 같은 경우 코루틴의 장점인 병렬/비동기 처리에 대한 장점을 활용하지 못한다.</li>
</ul>
<pre><code class="language-kotlin">fun main() {
    runBlocking {
        delay(5000)
        println(&quot;step 1&quot;)
    }
    println(&quot;step 2&quot;)
}</code></pre>
<h3 id="3-coroutinescope">[3] CoroutineScope</h3>
<ul>
<li>runblocking과 달리 비동기로 couroutine scope를 생성한다.</li>
</ul>
<pre><code class="language-kotlin">coroutineScope {
    //이런식으로 필요할 때 coroutine scope를 생성할 수 있다.
}

CoroutineScope(Dispatchers.IO).async {
    // Coroutine Scope Context를 지정할 수 있다.
}</code></pre>
<h4 id="coroutinescope-context">CoroutineScope Context</h4>
<ul>
<li>Dispatchers.IO<ul>
<li>코루틴이 백그라운드 스레드에서 작동한다.</li>
<li>db, network, 파일 작업 등에서 사용된다.</li>
</ul>
</li>
<li>Dispatchers.Main<ul>
<li>코루틴이 메인 스레드에서 실행된다.</li>
<li>suspending 함수, 수정사항을 가져오는 함수 등 가벼운 작업들만 실행시킨다.</li>
</ul>
</li>
<li>Dispatchers.Default<ul>
<li>리스팅 정렬과 같이 CPU 부하가 많으 작업들을 할 때 사용한다.</li>
</ul>
</li>
<li>Dispatchers.Unconfined<ul>
<li>GlobalScope와 함께 사용되며, 코루틴이 현대 스레드에서 작동되다 중단되고 다시 시작하면 중단된 스레드에서 시작한다.</li>
</ul>
</li>
</ul>
<h3 id="4-coroutine-builder">[4] Coroutine Builder</h3>
<ul>
<li>하위 스코프를 만들지는 않지만 코루틴 스코프 내에서 비동기적으로 작업을 수행할 때 사용한다. 반환값을 이용해 완료될 때까지 대기가 가능하다.</li>
<li>launch : 비동기 실행 결과가 필요 없는 경우<ul>
<li>리턴 값 job, 대기는 join(), 다중 대기는 joinAll()</li>
</ul>
</li>
<li>async : 비동기 실행 결과가 외부에서 필요한 경우<ul>
<li>리턴 값 Deffered(Future, Promise와 같은 개념이다)</li>
<li>대기는 await(), 다중 대기는 awaitAll()</li>
<li>async 블럭을 만나자 마자 해당 코루틴을 비동기로 실행해버리는데, 원하는 시점에 실행하려면 start=LAZY 주고 나중에 start() 호출하면 된다.</li>
</ul>
</li>
<li>이런 coroutine builder로 작업을 감싸지 않고 그냥 사용하는 경우 해당 코루틴은 순차적으로 한 라인 씩 대기하면서 실행한다.</li>
</ul>
<h2 id="4-사용-코드-예시">4. 사용 코드 예시</h2>
<pre><code class="language-kotlin"># Controller
fun getUser(Param param) = RunBlocking {
    val user = userService.getUser(param)
    return @runBlocking user
}
// RunBlocking으로 Coroutine Scope 생성


# Service
suspend fun getUser(Param param) = coroutineScope {

    val user1 = async { user1Repository.getUser(param)}
    val user2 = async { user2Repository.getUser(param)}

    return @coroutineScope getUser(user1.await(), user2.await())
}
// 비동기로 코루틴을 실행하기 위해 coroutine scope를 만든 후, 각각 aysnc를 통해 비동기로 메서드를 호출한다. 이후 .await 부분에서 다른 코루킨을 실행할 것이 있으면 그것부터 실행한다.

# Repository

suspend fun getUser(Param param): User {
    return userApi.get()
            .uri {...}
            ...
            .awaitSingleOrNull()
}
// awaitSingleOrNull에서 대기하여 다른 코루틴으로 넘어간다.

/**
1. controlller로 요청이 오고, RunBlocking으로 scope를 만든 후, service로 넘어간다.
2. user1부터 차례대로 실행되며, Repository의 &#39;awaitSingleOrNull&#39;을 마주치면 다른 코루틴으로 넘어가야된다. 그럼 user2 부분이 실행되고 user1과 동일하게 &#39;awaitSingleOrNull&#39;에서 멈춘다.
3. user1 Repository가 완료되었으면 이제 다음 코드를 실행하는데, user2.await이 있어 다른 코루틴 실행할 것부터 처리한다.
4. user2 Repository가 완료되었으면 이제 결과물을 반환한다.
**/</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Logging, Kafka, Redis, Hadoop..?]]></title>
            <link>https://velog.io/@youngmin-mo/Microservice-Logging-Kafka-Redis-Hadoop</link>
            <guid>https://velog.io/@youngmin-mo/Microservice-Logging-Kafka-Redis-Hadoop</guid>
            <pubDate>Sat, 14 Jan 2023 05:52:39 GMT</pubDate>
            <description><![CDATA[<h2 id="1-loging">1. Loging</h2>
<h3 id="1-logging이란">[1] Logging이란</h3>
<ul>
<li>배포 환경에서 동작 상태를 확인하기 위해 로깅이 필요하다. 로깅이란 프로그램 동작시 발생하는 모든 일을 기록하는 행위로, 서비스 동작상태 및 장애를 표현한다.</li>
<li>I/O 작업 (Http 통신, DB 처리 등), Null Pointer Exception, Application 상태, 로직 에러 등이 있다.</li>
<li>로그를 기록하는 시점은 그때그때 다른데, 웬만하면 모두 남기는 것이 좋다. 다만 용도에 따라 level을 구분하여 남긴다.</li>
<li>실 서버 구동 같이 디버깅을 할 수 없는 상황에서는 로깅이 최선의 선택이다. 당연히 디버깅을 쓸 수 있다면 디버깅을 최대한 활용하는게 좋다.</li>
<li>Log Level : Fatal(매우 심각한 에러로, 프로그램이 종료되는 경우) -&gt; Error(프로그램이 종료되지 않지만, 의도치 않은 에러) -&gt; Warn(에러가 될 수 있는 잠재적 가능성이 있으며, 지속적으로 중첩될 경우 문제가 되어 알람과 같이 수신한다.) -&gt; Info(시스템 동작여부와 같이 정보를 확인하기 위해 사용) -&gt; Debug -&gt; Trace로 구성하며 환경(dev, alpha, stage, real)에 따라 로그 레벨을 달리 구성한다.</li>
</ul>
<h3 id="2-slf4j">[2] SLF4J</h3>
<ul>
<li>Simple Logging Facde For Java로 다양한 로깅 프레임 워크에 대한 추상화(인터페이스) 역할을 수행한다. 당연히 인터페이스니 단독으로 사용 불가능하고, 최종 사용자가 배포시 원하는 구현체를 선택해야한다. 구현체는 Logback, Log4J, java.util.logging 등이 있다.</li>
<li>Client -&gt; SLF4J API &lt;- (Logback, LOG4J 등 다양한 구현체)로 구성</li>
<li>클라이언트는 SLF4J만 알면 되므로 구현체 컴포넌트의 변경 및 상세 코드 변경에 대한 파급효과가 없다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/e013bcfc-58db-486f-aceb-090d3d41c883/image.png" alt=""></p>
<h3 id="3-logback-설정-요소">[3] Logback 설정 요소</h3>
<ul>
<li>local, dev, alpha, stage, real 등 배포 환경에 따라 다르게 설정할 수 있다.</li>
<li>configuration : 최상위에 위치하며, 구성 파일을 묶어준다.<ul>
<li>logger<ul>
<li>실제 로깅을 수행하는 구성요소로, 로그 객체를 등록한다.</li>
<li>name : 이후 LogFactory에서 이 부분에서 설정한 값으로 가져온다.</li>
<li>level : 로깅할 레벨을 지정한다.</li>
<li>appender-ref : appender에서 설정한 name을 넣으면 해당 로그 메시지로 write한다.</li>
<li>additivity : 상위 로거로부터의 상속 여부를 의미하며, default는 true(상속 o)이다.</li>
</ul>
</li>
<li>root<ul>
<li>loggerd의 네이밍에 맵핑이 안된 얘들은 모두 여길 탄다.</li>
</ul>
</li>
<li>appender<ul>
<li>설정해 놓은 위치에 로그 메시지를 write한다. default는 동기적으로 처리한다.</li>
<li>name : logger에서 호출할 이름</li>
<li>class : appender 구현한 클래스<ul>
<li>ConsoleAppender : 콘솔에 로그 기록</li>
<li>FileAppender : 지정된 파일에 로그 기록</li>
<li>RollingFileAppender : file appender확장판으로, 한 곳에 파일을 지속적으로 write하면 부담이되어 용량, 날짜에 따라 파일을 분리하여 저장하며, 지정 주기로 파일을 삭제하기도 한다.</li>
<li>SMTPAppender : 로그를 메일로 기록</li>
<li>DBAppender : 디비에 로그 기록</li>
</ul>
</li>
<li>encoder pattern : 로그 출력할 패턴</li>
<li>level : Log Level</li>
</ul>
</li>
</ul>
</li>
<li>LogFactory<ul>
<li>logger에서 지정한 로그 객체를 불러온 후 로그를 기록한다. 즉, 로그 객체를 생성한다.</li>
<li>@SLF4J로 로그를 기록하면 로거와 맵핑될 때 이름은 클래스 명으로 기록된다. 만약 logger에 맵핑되는것이 없다면 root로 기록된다.</li>
</ul>
</li>
</ul>
<h2 id="2-filebeat">2. Filebeat</h2>
<blockquote>
<p>경량화된 로그 파일 수집기로, VM&amp;컨테이너 등 호스트에 상관없이 로그(파일) 데이터를 수집 후 경량화된 방식으로 로그를 타겟서버로 전달하고 중앙 집중화 작업을 편리하게 만들어준다. 참고로 로그 같이 빠른시간내에 적재되는 데이터를 매번 보내는 것보다, 파일비트에 의해 보내는것이 더욱 효율적이다.</p>
</blockquote>
<h3 id="1-예시">[1] 예시</h3>
<ul>
<li>모니터링하려는 각 시스템에 Filebeat 설치 후, 로그 파일의 위치를 지정한다.</li>
<li>이후 로그 데이터를 필드로 파싱하고 target(ES, hadoop 등)으로 전송한다.</li>
<li>즉, 설치한 파일비트는 사용자가 지정한 로그 파일이나 위치를 모니터링하고 로그 이벤트를(데이터 발생) 수집하여 인덱싱을 위해 ES, Hadoop 등으로 전달한다.</li>
</ul>
<h3 id="2-로직-상세">[2] 로직 상세</h3>
<ol>
<li>파일비트를 시작하면 내가 로그 데이터 위치를 들여보도록 등록해놓은 하나 또는 그 이상의 &#39;input&#39;을 시작한다.</li>
<li>파일비트가 찾은 각 로그에 대해 harvester를(데이터 수확기) 시작한다.</li>
<li>각 harvester는 새 컨텐츠에 대한 로그 &#39;하나를&#39; 읽고 새 로그 데이터를 &#39;libbeat&#39;로 전송하며,</li>
<li>libbeat는 이벤트를 집계하고 지계된 데이터를 파일비트에 구성해 놓았던 &#39;output&#39;으로 전송한다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/8f85da5d-0c9b-44e4-9e85-f412ceb37067/image.png" alt=""></p>
<h2 id="3-streamset">3. Streamset</h2>
<h3 id="1-streamset이란">[1] Streamset이란</h3>
<ul>
<li>드래그앤드롭 방식으로 데이터 흐름을 관리하며, 파이프라인 생성으로 기능을 제정한다.</li>
<li>코드 작성을 최소화하며, 자동으로 인스턴스를 관리한다.</li>
<li>파이프라인을 시각화하며, 성능 및 이벤트를 모니터링할 수 있다.</li>
</ul>
<h3 id="2-component">[2] Component</h3>
<ul>
<li>Origin : 파이프라인 내 시작점을 의미하며, 파이프라인 내 하나만 생성할 수 있다. kafka, redis, tcp 등 data 근원지로 활용가능하다.</li>
<li>Processor : JSON Parster, Field Masker 등 데이터를 처리/가공하는 기능을 제공한다.</li>
<li>Destination : 파이프라인 목적지로 Redis, DB, Local File 등에 사용된다.</li>
<li>Executor : 이벤트를 수신했을 때 동작을 정의하며, Email Executor, Shell Executor 등의 기능을 제공한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/7ee0b664-5062-4cab-b0cb-07593f2c491b/image.png" alt=""></p>
<h2 id="4-kafka">4. Kafka</h2>
<blockquote>
<p>분산 이벤트 스트리밍 플랫폼</p>
</blockquote>
<h3 id="1-kafka-배경">[1] Kafka 배경</h3>
<ul>
<li>기존 서비스에선 source -&gt; destination application이 적어 전송라인이 복잡하지 않았다. 하지만 시간이 지나면서 application이 많아졌고, <strong>data 전송라인이 많아지면서</strong> <strong>배포</strong>, <strong>장애 대응</strong>이 힘들어졌다.</li>
<li>한 앱이 배포시 그와 연계된 서비스가 많아졌고 영향도가 높아지면서 &#39;배포&#39;가 고려할 상황이 늘어났고, 장애 대응시 연계된 서비스가 많아 어느 부분이 장애 포인트인지 파악이 힘들어졌다.</li>
<li>Apache Kafka는 source와 destination 사이의 coupling(결합도)을 약하게 하기 위해 나왔다.</li>
</ul>
<h3 id="2-구조">[2] 구조</h3>
<img src="https://velog.velcdn.com/images/youngmin-mo/post/a17a1c05-6e23-4814-9117-2153f076c327/image.png" width="50%"/>

<img src ="https://velog.velcdn.com/images/youngmin-mo/post/dc740c53-f68f-4580-b21c-cc213f0c8d73/image.png" width="70%"/>

<h3 id="3-특징">[3] 특징</h3>
<ul>
<li>복구가 가능하여 이슈 대응이 쉽고(Foult Tolerant가 용이), Latency 성능이 좋아 지연이 낮으며, Throughput이 좋아 높은 처리량을 갖는다. 즉, 데이터를 처리하기 매우 편리하다.</li>
<li>로그를 분석하고, 시각화하기 위해 ES에 저장하기도 하고, 로그를 백업하기 위해 하둡에 저장하기도 한다.</li>
</ul>
<h3 id="4-partition">[4] partition</h3>
<ul>
<li>하나의 토픽에 여러개의 파티션을 구성할 수 있다.</li>
<li>하나의 파티션에 큐와 같이, 파티션 끝에서부터 차곡차곡 채워넣는다.</li>
<li>consumer는 가장 오래된 순서부터 데이터를 가져간다.</li>
<li>consumer가 토픽 내부 파티션에서 데이터를 가져가도, 데이터는 삭제되지 않는다. 파티션 안에 데이터는 그대로 남아있어, 동일 데이터를 여러번 처리(소비)할 수 있다. 즉, 복구에 용이하다.</li>
<li>새로운 consumer가 오면 다시 파티션 안에 0번 데이터부터 가져갈 수 있다. 단, consumer group이 달라야하며, auto.offset.reset=earliest 셋팅되어야 한다.</li>
<li>파티션 안에 데이터가 삭제되는 시기는 <strong>레코드가 저장되는 최대시간과 크기로 제어</strong>할 수 있다. 단, <strong>파티션은 삭제 안되는데, 파티션 안에 데이터는 지정된 시간이 지나면 삭제된다.</strong><ul>
<li>log.retention.ms : 최대 레코드 보존 시간</li>
<li>log.retention.byte : 최대 레코드 보존 크기</li>
</ul>
</li>
<li>파티션이 여러개면 데이터를 어느 파티션에 넣어야될까?<ul>
<li>key, value로 데이터를 보내는데, key가 null이고 기본 파티셔너를 사용할 경우 round robin(순차적)으로 파티션을 할당한다. 만약 key가 있고, 기본 파티셔너를 사용할 경우 key의 hash 값을 구하고 특정 파티션에 할당한다.</li>
<li>key는 프로듀서가 메시지를 보내면 토픽의 파티션이 지정될 때 쓰인다.</li>
<li>파티션을 늘릴 수 있지만 줄일 수는 없으며, 토픽 안에 파티션을 늘리고 컨슈머 갯수를 늘려서 데이터 처리를 분산시킬 수 있다.</li>
<li>토픽에 파티션을 사용 중 중간에 추가하는 경우 해시를 쓰기 때문에 key, partition의 일관성이 깨진다. 따라서 key를 사용할 경우 초기 파티션 갯수 설정 후 추후에 변경하지 않음을 권장한다. -&gt; 이전에 할당되던 파티션이 아닌 다른 파티션에 데이터가 할당 될 수 있다.</li>
</ul>
</li>
</ul>
<h4 id="partional">Partional</h4>
<ul>
<li><p>프로듀서가 데이터를 보내면 무조건 파티셔너를 통해서 브로커로 데이터가 전송된다. 파티셔너는 데이터를 토픽의 어떤 파티션에 넣을지 결정하는 역할이다. 이떄 레코드에 포함된 메시지 &#39;키&#39; 또는 &#39;값&#39;에 따라서 파티션의 위치가 결정된다.</p>
</li>
<li><p>Uniformsticky Partitioner : 기본 옵션으로, 메시지 키 유무에 따라 다르게 동작한다.</p>
<ul>
<li>key 존재 o : 키가 같다면 동일한 해시 값을 만들기 때문에 항상 동일한 파티션에 들어가므로 &#39;순서&#39;를 보장한다.</li>
<li>key 존재 x : 라운드 로빈으로 동작하며, 프로듀서에서 배치로 모을 수 있는 최대한의 레코드를 모아서 파티션으로 데이터를 전송한다. 파티션에 적절히 데이터를 분배한다.</li>
</ul>
</li>
<li><p>Partitional Interface를 사용해서 커스텀 파티셔너 클래스를 만들면 메시지 키 또는 값 또는 토픽 이름에 따라서 레코드를 어떤 파티션에 보낼지 결정할 수 있다. 예를 들어 VIP 고객의 데이터를 좀 더 빨리 처리하고 싶다면 &#39;파티셔너&#39;를 통해 처리량을 늘릴 수 있다. 기본적으로 10개의 파티션이 있다고 가정할 때 우리가 커스텀 파티셔너를 만들어서 8개의 파티션에는 VIP 고객의 데이터를 그리고 2개의 파티션에는 일반 고객의 데이터를 넣어 처리량을 제어할 수 있다.</p>
<h3 id="5-kafka-producer">[5] Kafka Producer</h3>
</li>
<li><p>데이터를 카프카에 보내는(토픽 파티션에 데이터 생성) 역할로, 예를 들어 많은 양의 클릭 로그들을 대량으로 카프카에 적재할 때 사용한다.</p>
</li>
<li><p>kafka broker로 데이터를 전송할 때 전송 성공 여부를 알 수 있고 실패할 경우 재시도한다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/e54f6da6-95ed-454c-8ce8-583f0043f01e/image.png" alt=""></p>
<h3 id="6-데이터-유실을-막기-위한-노력">[6] 데이터 유실을 막기 위한 노력</h3>
<ul>
<li>용어<ol>
<li>Kafka Broker : 카프카가 설치되어 있는 서버 단위로, 보통 3개 이상의 브로커 사용을 권장한다.</li>
<li>Replication : 파티션의 복제를 뜻하며, 브로커 갯수보다 작아야한다. 예를 들어 partition이 1, replication이 3이라면 partition은 원본 1개와(leader partition) 복제본 2개(follower partition)로 구성된다.</li>
</ol>
</li>
<li>왜 replication을 쓸까?<ul>
<li>갑자기 브로커가 어떠한 이유로 사용 불가하다면 복제본이 없다면 파티션 안에 적재된 데이터를 복제할 수 없다. 즉, follow partition이 leader partition 역할을 승계하여 &#39;복구&#39;할 수 있다. 여기서 리더 파티션은 프로듀서가 토픽의 파티션에 데이터를 전달할 때 전달받는 주체다.</li>
<li>replication 갯수가 많아지면 그만큼 브로커 리소스 사용량도 늘어난다. 당연히 하나의 토픽에 저장된느 파티션이 많아지기 때문이다. 따라서 카프카에 들어오는 데이터 양과 retention date(저장시간)를 고려해서 지정하자. 보통 broker가 3개 이상일 때 replication은 3으로 설정을 권장한다.</li>
</ul>
</li>
<li>partition의 replication과 관련있는 &#39;ACK&#39; 옵션<ul>
<li>ACK 0 : 프로듀서가 리더 파티션에 데이터를 전송하고 응답값을 받지 않는다. 속도는 빠르지만 데이터 유실 가능성이 있다.</li>
<li>ACK 1 : 프로듀서가 리더 파티션에 데이터를 전송하고 응답값을 받는다. 0보단 아니지만 약간의 데이터 유실 가능성이 존재한다.</li>
<li>ACK 2 : 프로듀서가 리더 파티션에 데이터를 전송하고, 팔로워 파티션에 복제가 잘 이루어졌는지까지 응답을 받는다. 즉, 리더 파티션에 데이터를 보낸 후 나머지 팔로워 파티션에도 데이터가 저장되는 것을 확인한다. 다만, &#39;0&#39;, &#39;1&#39;에 비해 체크하는게 많아 속도가 현저히 느리다.</li>
</ul>
</li>
</ul>
<h3 id="7-kafka-consumer">[7] Kafka Consumer</h3>
<ul>
<li>카프카에서는 컨슈머가 데이터를 가져가더라도 데이터가 사라지지 않는다.</li>
<li>토픽의 파티션으로부터 데이터를 polling해서 가져간다.</li>
<li>데이터를 읽을 때 파티션의 offset 위치를 기록한다.<ul>
<li>offset : topic &gt; partition &gt; data의 고유 번호로, offset은 토픽별, 파티션 별로 별개로 지정된다.</li>
<li>즉, consumer가 데이터를 어느 지점까지 읽었는지 확인하는 용도로 사용된다.</li>
</ul>
</li>
<li>파티션 갯수에 따라 컨슈머를 여러 개 만들면 병렬처리가 가능하기 때문에 빠른 속도로 데이터를 처리할 수 있다.</li>
<li>컨슈머가 파티션에서 데이터를 읽을때마다 offset을 commit하게 되는데, 이는 _consumer_offset 토픽에 저장된다.</li>
<li>만약 불의의 사고로 컨슈머가 실행이 중지되어도 offset 정보를 토픽에 기록해있어, 중지된 위치부터 다시 데이터를 읽을 수 있다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/ba23ab39-090e-4939-826c-4d923b4f4738/image.png" alt="">
<img src="https://velog.velcdn.com/images/youngmin-mo/post/a867dd85-f6ed-4efb-a5b4-9ed08968548e/image.png" alt=""></p>
<h3 id="8-lag">[8] Lag</h3>
<ul>
<li>lag는 프로듀서의 오프셋과 컨슈머의 오프셋간의 차이다. 컨슈머가 성능이 안나오거나 비정상적으로 동작하면 lag가 필연적으로 발생하여 모니터링시 주의깊게 살펴봐야한다.</li>
<li>프로듀서가 데이터를 넣는 속도보다 컨슈머가 데이터를 가져가는 속도가 느리다면, 2개의 오프셋간의 차이가 발생한다. lag의 숫자를 통해 현재 해당 토픽에 대해 파이프라인으로 연계되있는 프로듀서와 컨슈머의 상태에 대한 유추가 가능하다.</li>
<li>토픽에 여러 파티션이 존재할 경우 lag가 여러개 존재할 수 있다.</li>
</ul>
<img src="https://velog.velcdn.com/images/youngmin-mo/post/410d01d4-bba7-47b0-a235-474bc5e6d2e6/image.png" width="50%"/>

<h2 id="5-hadoop">5. Hadoop</h2>
<h3 id="1-철학">[1] 철학</h3>
<ul>
<li>한 대의 고가 장비보다 여러 대의 장비가 낫다고 생각하여 데이터를 분산하여 저장한다.</li>
<li>시스템(H/W)은 언제든지 죽을 수 있다고 생각하여, 장애가 자동화되어야하며 &amp;&amp; 시스템 확장이 쉬워야한다고 생각한다.</li>
<li>따라서 다음과 같은 구성을 따른다.<ul>
<li>수천대 리눅스 기반 범용 서버로 구성된 Cluster로 구성한다.</li>
<li>Master-Slave 구조를 따른다.</li>
<li>File을 block 단위로 저장한다.</li>
<li>하둡 자체적으로 기본적으로 3개의 복제본을 구성한다. 즉, block data 복제본을 유지한다.</li>
<li>data 처리시 최대한 지역성을(비슷한 데이터는 물리적으로 근처에 위치한다.) 보장할려고 노력한다.</li>
</ul>
</li>
</ul>
<h3 id="2-hadoop-기본-개념">[2] Hadoop 기본 개념</h3>
<ul>
<li>하둡은 비정형 데이터를 포함한 빅데이터를 다루기 위한 가장 적합한 플랫폼으로 저렴한 비용으로 많은 비정형 데이터를 저장하며, 분석에 용이하다. RDBMS는 정형 데이터만을 다룬다.</li>
<li>&#39;빅 데이터&#39;의 &#39;저장&#39;과 &#39;분석&#39;을 위한 &#39;분산&#39; 컴퓨팅 솔루션<ul>
<li>빅데이터 : 한 대의 컴퓨터로 저장/연산하기 어려운 거대 데이터</li>
<li>저장 : 데이터 크기, 복구 관점에서 큰 규모의 데이터를 저장해야한다.</li>
<li>분석 : 쪼개진 데이터를 분석하고 그 결과를 합친다.</li>
<li>분산 : 여러대의 컴퓨터로 나눠서 작업을 처리한다.</li>
</ul>
</li>
<li>구성<ul>
<li>네임 노드 : 데이터를 쪼개고 위치를 기억한다.</li>
<li>데이터 노드 : 실 데이터를 저장한다.</li>
</ul>
</li>
<li>연계 컴포넌트<ul>
<li>hadoop : 대용량 데이터 접근을 위한 분산형 파일 시스템으로, 데이터를 블록 단위로 분할하여 여러 서버에 복제하여 저장하기 때문에 안전, 신뢰할 수 있게 저장하는 디비다. hadoop 안에 GFS라는 기능이 있으며, 이 기능 덕분에 파일을 &#39;분산형&#39;으로 저장한다.</li>
<li>yarn : 작업 일정 관리, 클러스터 자원 관리 툴</li>
<li>MapReduce : 분산 파일 시스템으로 저장된걸 병렬 처리한다.</li>
<li>Spark : 빅 데이터 처리를 위한 고속 엔진</li>
<li>hive : SQL을 활용하여 MapReduce를 실행할 수 있는 구조로, 파일 정보의 물리적 구조를 테이블 형태의 논리적 구조로 설명한다.</li>
</ul>
</li>
</ul>
<h2 id="6-redis">6. Redis</h2>
<blockquote>
<p>Not Disk, In Memory Data Structure</p>
</blockquote>
<h3 id="1-캐시란">[1] 캐시란</h3>
<ul>
<li>캐시란 나중에 요청 올 결과를 미리 저장하고 빠르게 서비스 해준다. 보통 많은 양의 데이터를 디비에 저장하고 이를 서비스해주는데, 사실 1000개 데이터가 있으면 그 중 20개의 데이터만 자주 사용한다고 알려져 있다.</li>
<li>읽기 작업이 빈번한 경우 디스크가 아닌 메모리 캐시를 이용하면 더 빠른 속도로 서비스를 제공할 수 있다.</li>
<li>DB에 삽입 쿼리 1번을 500번 날리는 것보다, 삽입 쿼리 500번을 1번에 날리는게 더 빠르다. 물론 캐시 장애시 동기화 안된 데이터가 다 날라가는 문제가 있지만, write 작업이 빈번한 경우(ex-log 적재) write-back 방식이 사용된다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/079cc841-321c-4272-ad6e-09e380db9979/image.png" alt=""></p>
<h3 id="2-redis-사용처">[2] Redis 사용처</h3>
<ul>
<li>remote data structure : A, B, C 서버에서 데이터를 공유하고 싶을 때</li>
<li>한 대의 서버에서는 전역변수가 괜찮지 않을까? 물론 맞는데, 레디스는 싱글 스레드라 자동으로 아토믹하게 지원</li>
<li>인증 토큰(Strings, Hash), 랭킹보드(Sorted, Set), 유저 API Limit, Job Queue(List), 자주 사용되는 값, 배치로 돌린 값 저장 등 다양한 곳에서 사용된다.</li>
</ul>
<h3 id="3-collection이란">[3] Collection이란</h3>
<ul>
<li>미리 잘 만들어 놓은 자료구조를 사용하여, 개발의 편이성이 증가한다. 즉, 컬렉션을 왜 사용할까에 대한 질문은 <strong>외부 요인은 신경 안쓰고 비지니스 로직에만 집중</strong>할 수 있기 때문이다.</li>
<li>랭킹 시스템 구현시 디비의 정렬로 구현한다면 속도가 현저히 느려진다. 따라서 in-memory 기반의 레디스를 활용한다. 또한, Atomic하지 않게 처리하다보면 특정 상황에서 race condition 상태가 발생하는데, redis collection은 이런 atomic 처리를 구성한다.</li>
<li>이러한 이유로 레디스에서 제공하는 컨렉션을 사용하며, 잘못 선택하면 서비스의 속도가 굉장히 느려져서 Collections을 잘 선택해야한다.</li>
<li>하나의 컬렉션에 너무 많은 아이템을 담으면 안된다. 10,000개 이하 몇천개 수준으로 유지하는게 좋으며, Expire Time은 Collection의 아이템 개별로 걸리지 않고 전체 컬렉션에 대해서만 걸린다.</li>
</ul>
<h4 id="collection-종류">Collection 종류</h4>
<ul>
<li>Strings : key, value 형태로 set(key, value), get(key), mset(multi set)으로 데이터를 저장 / 읽기 할 수 있다. 또한 키에 따라 데이터가 분산되기 때문에 어떻게 키를 잡을지 고민이 필요하다.</li>
<li>List : 중간에 값을 넣을 때 비효율적이며, 잡큐와 같이 데이터를 집어넣고, 하나씩 앞에서부터 가져올 때 사용된다. LPush, RPush, LPop, RPop 명령어를 이용한다.</li>
<li>Set : 데이터가 있는지 없는지만 체크하는 용도로 중복되지 않은 키를 넣을 때 사용한다. SADD, SMEMBERS, SISMEMBER 명령어를 사용하며, 친구 리스트와 같이 특정 유저를 팔로우 하는 목록을 저장할 때 사용한다.</li>
<li>Sorted Sets : 랭킹에(score 값) 따라 순서가 바껴서 set 형태로 저장한다. score는 double type이므로 부정확할 수 있으나, 유저 랭킹 보드로 사용한다. key, socre, value로 구성된다.</li>
<li>Hash : key 밑에 sub key가 존재하며, Hmset key subkey1 value1 subkey2 value2 등 명령어로 값을 삽입 및 Hgetall key, Hget key subkey1 등으로 조회할 수 있다.</li>
</ul>
<h3 id="4-redis-운영">[4] Redis 운영</h3>
<h4 id="메모리-관리를-잘하자">메모리 관리를 잘하자</h4>
<ul>
<li>레디스가 빠른 이유는 디스크가 아닌 <strong>In Memory Data Structure</strong> 때문인데, Physical Memory 이상을 사용하면, 메모리 -&gt; Disk Swap이 발생하며 속도가 현저히 느려진다.</li>
<li>데이터 삽입시 해당 데이터만큼만 메모리에 할당하는 것이 아닌 &#39;Page&#39; 단위로 데이터를 넣기 때문에 실제 사용 데이터보다 더 크게 메모리에 잡히는 경우가 존재한다. 따라서 모니터링은(실제 할당) 필수다.</li>
<li>큰 메모리를 쓰는 인스턴스 하나보다는 적은 메모리를 사용하는 인스턴스 여러개가 더 &#39;안전&#39;하다. 이유는 read는 괜찮은데 write시 현재 메모리의 <strong>fork</strong> 작업으로 2배를 사용하는 경우가 존재하기 때문이다. 따라서 관리는 귀찮지만 운영면에서는 더욱 안전하다. (24 gb instance (최대 48gb) 보다 8gb x 3개가(최대 32gb) 더 안전하다.)</li>
<li>따라서 다양한 데이터를 가지는 경우보다 유사한 크기의 데이터를 가지는것이 메모리 파편화가 덜 일어나 더 유리하다.</li>
<li>메모리가 부족하다면 좀 더 많은 메모리를 보유한 장비로 migration 해야 하는데, 메모리가 빡빡하다면 migration시 문제가 발생할 수 있다. 사용량 70~80%시 고민이 필요하며, 필요없는 victime 할 것을 지워줘야한다.</li>
</ul>
<h4 id="on-명령어는-주의하자">O(n) 명령어는 주의하자.</h4>
<ul>
<li>Redis는 Single Thread로 동시에 &#39;하나의&#39; 명령어만 처리할 수 있다.</li>
<li>TCP로 쪼개진 패킷이 들어오고, 쪼개진 패킷을 조합하여 차례로 명령어를 실행할 것이다. 단순 get, set 명령어는 금방 처리되지만 긴 시간을 소요하는 명령어를 수행하면 뒤이어 오는 명령들은 오랫동안 대기하여 전체가 대기하는 문제가 발생한다.</li>
<li>KEYS(모든 키 조회), FLUSH/DELETE Collections (삭제), Get All Collections 와 같이 뭔가 &#39;모든&#39; key, value를 조회하는 명령어를 주의하자.</li>
<li>Key가 백만개 이상인데 확인을 위해 Keys 명령어를 사용할 경우 문제가 발생하며, scan &amp; cursor 명령어를 활용하여 하나의 긴 명령어를 짧은 여러 명령어로 분할하여 사용하자.</li>
<li>아이템이 많은 상황에서 모든 데이터를 조회하는 것보다, collection의 일부만 가져오거나, 큰 컬렉션들을 여러개의 컬렉션으로 나누어 저장하여 오랜 연산비용을 줄이자.</li>
</ul>
<h3 id="5-redis-replication">[5] Redis Replication</h3>
<ul>
<li>레디스에서 데이터 안전을 위해 특정 서버로 데이터를 복제한다. 즉, A 서버(master) 데이터의 모든 변화를 B 서버에(slave) 반영하는데, 이때 &#39;Replication Lag&#39;가(잠시 동안 데이터의 불일치) 발생한다.</li>
<li>replication 과정에서 fork가 일어나므로 메모리 부족 현상이 발생할 수 있다. 또한, 많은 대수의 레디스 서버가 레플리카를 두면 네트워크 이슈가 발생할 수도 있다. (network bandwith보다 크게 데이터 전송시 문제가발생하여, 1대씩 migration 하도록 하기도한다.)</li>
<li>redis.conf 권장 설정 tip<ul>
<li>MaxClient 설정을 50,000으로 높이고</li>
<li>RDB, AOF (Memory -&gt; DB 저장) 설정을 off하고, master replica는 끄고 secondary replica만 켜서 레플리카에 의한 속도 이슈를 제어한다.</li>
<li>전체 장애의 90% 이상이 keys, save 설정(예를 들어 1분에 500개의 데이터 변경시 memory dump default 설정으로 disk, memory 사용량 부하)에 의하여 발생하므로 특정 명령어를 disable한다. (ex keys command)</li>
</ul>
</li>
</ul>
<h4 id="데이터-분산-설정">데이터 분산 설정</h4>
<ul>
<li>Consistent Hashing by Appcation단<ul>
<li>일반 해싱은 중간에 서버가 죽거나 증설되면 모든 데이터가 <strong>rearange</strong>하는 작업이 필요하다. consistent는 key의 일관된 해시 값을 주는 특성을 이용하여 모든 서버 해시 값에 근접한 값으로 이동하여, rearange해도 1/n 만큼의 변화가 일어난다.</li>
</ul>
</li>
<li>Sharding by Application단<ul>
<li>특정 range를 정의하고, 해당 range에 속하면 해당 키값에 데이터를 저장하는데, 상황에 따라 놀고있는 서버가 존재할 수 있다. 하지만 서버 확장이 용이하고, key를 쉽게 찾을 수 있다.</li>
<li>참고로 modulus로 처리하는건 서버 확장/감소시 안 좋지만 데이터를 &#39;균등히&#39; 분배할 수 있어 데이터 삽입/조회에 용이하다.</li>
</ul>
</li>
<li>Redis Cluster<ul>
<li>primray 서버, secondary 서버로 구성하며, primary가 죽으면 secondary가 승격한다.</li>
<li>해시 기반으로 slot 16,834로 구분한다.</li>
<li>일부 언어에서 라이브러리로 구현되있어, 사용이 편리하다. ex) 잘못된 slot으로 갈 때 처리 등</li>
</ul>
</li>
</ul>
<h4 id="redis-failover">Redis Failover</h4>
<ul>
<li>redis cluster의 경우 primary &amp; secondary로 구성되어 서버 장애시 자동 승격이 이루어진다.</li>
<li>Coordinate 방식<ul>
<li>API Server가 레디스에 접근 시 Coordinate를 통해 어떤 레디스가 Primary인지 알려준다.</li>
<li>Health Check가 Primary 서버가 죽으면 Redis가 죽으면 Coordinate 서버에게 어떤 서버가 Primary인지 알려준다.</li>
</ul>
</li>
<li>VIP 방식<ul>
<li>Api Server가 레디스 접근시 VIP로 접근하며, 레디스가 죽으면 Health Check가 VIP를 바꾼다.(Secondary 승격)</li>
</ul>
</li>
</ul>
<h3 id="6-redis-monitoring">[6] Redis Monitoring</h3>
<ul>
<li>infra(cpu, memory, disk)는 당연히 고려해야하며, 실제 데이터 사용량보다 메모리 사용량이 더 클 수도 있다.</li>
<li>Disk 기반의 DB보다 Memory 기반의 레디스는 좋은 툴이다. 하지만 메모리를 빡빡하게 쓰면 관리가 어렵다. 예를 들어, 32gb 장비에서 24gb 이상 사용하면 장비 증설을 고려해야한다. 당연히 write 작업이 heavy할 경우 migration을 주의하자.</li>
<li>캐시로 레디스를 사용할 경우 redis가 문제 있어도 db가 대체하여 괜찮지만, persistent storage 혹은 단독으로 사용할 경우 무조건 Primary/Secondary로 구성하자. 뿐만 아니라 주기적으로 secondary로 migration하여 fork가 주기적으로 일어나 메모리 사용을 여유롭게 구성하자.</li>
<li>RDB, AOF 작업은 Secondary에서 처리하도록 구성하자.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[어떻게 다른 서버와 통신할까요?]]></title>
            <link>https://velog.io/@youngmin-mo/%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%8B%A4%EB%A5%B8-%EC%84%9C%EB%B2%84%EC%99%80-%ED%86%B5%EC%8B%A0%ED%95%A0%EA%B9%8C%EC%9A%94</link>
            <guid>https://velog.io/@youngmin-mo/%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%8B%A4%EB%A5%B8-%EC%84%9C%EB%B2%84%EC%99%80-%ED%86%B5%EC%8B%A0%ED%95%A0%EA%B9%8C%EC%9A%94</guid>
            <pubDate>Sat, 07 Jan 2023 09:28:21 GMT</pubDate>
            <description><![CDATA[<h2 id="1-sync-async-blocking-non-blocking">1. Sync, Async, Blocking, Non-Blocking</h2>
<h3 id="1-sync-vs-async">[1] Sync vs Async</h3>
<ul>
<li>동기와 비동기의 차이는 <strong>호출되는 함수의 작업 완료 여부(리턴값)</strong>를 신경쓰는지의  여부의 차이다.</li>
<li>동기는 함수 A가 함수 B를 호출한 뒤, <strong>함수 B의 리턴 값을 계속 확인하면서 신경쓰는 것이다.</strong></li>
<li>비동기는 함수 A가 함수 B를 호출할 때 콜백 함수를 함께 전달해서, 함수 B의 작업이 완료되면 함께 보낸 콜백 함수를 실행한다. 즉, 함수 A는 함수 B를 호출한 후로 <strong>함수 B의 작업 완료 여부에는 신경쓰지 않는다.</strong></li>
</ul>
<h3 id="2-blocking-vs-non-blocking">[2] Blocking vs Non-Blocking</h3>
<ul>
<li>제어권은 자신(함수)의 코드를 실행할 권리 같은 것이다. 제어권을 가진 함수는 자신의 코드를 끝까지 실행한 후, 자신을 호출한 함수에게 돌려준다.</li>
<li>Blocking은 A 함수가 B 함수를 호출하면 <strong>제어권을 A가 호출한 B 함수에게 넘겨준다.</strong></li>
<li>Non-Blocking은 A 함수가 B 함수를 호출해도, <strong>제어권은 그대로 자신이 가지고 있는다.</strong>
<img src="https://velog.velcdn.com/images/youngmin-mo/post/571da859-ea00-4c3e-96e7-f9394b1cb484/image.png" alt=""></li>
</ul>
<h3 id="3-4가지-케이스">[3] 4가지 케이스</h3>
<h4 id="sync--blocking">Sync &amp;&amp; Blocking</h4>
<ul>
<li>함수 A는 함수 B의 <strong>리턴 값을 필요</strong>로한다.(sync) 그래서 <strong>제어권을 B에게 넘겨주고</strong>, 함수 B가 실행을 완료하여 제어권을 돌려줄때까지 <strong>함수A는 기다린다.</strong>(blocking)</li>
</ul>
<h4 id="sync--non-blocking">Sync &amp;&amp; Non-Blocking</h4>
<ul>
<li>함수 A는 함수 B를 호출한다. 이때 A 함수는 B 함수에게 <strong>제어권을 넘기지 않고, 자신의 코드를 계속 실행한다.</strong>(non-blocking) 그런데 A 함수는 B 함수의 <strong>리턴값이 필요하기 때문에, 중간중간 B 함수에게 실행이 완료되었는지 물어본다.</strong>(sync)</li>
</ul>
<h4 id="async--non-blocking">Async &amp;&amp; Non-Blocking</h4>
<ul>
<li>함수 A가 함수 B를 호출할 때, <strong>제어권을 넘기지 않고 자신이 계속 가지고있다.</strong>(Non-Blocking) 따라서 B 함수를 호출하더라도, 멈추지 않고 자신의 코드를 계속 실행한다.</li>
<li>또한, B 함수를 호출할 때 <strong>콜백함수를 넘겨준다.</strong>(A는 B의 작업여부를 신경쓰지 않는다.) B 함수는 자신의 작업이 끝나면 A 함수가 준 콜백함수를 실행한다.(Async)</li>
</ul>
<h4 id="async--blocking">Async &amp;&amp; Blocking</h4>
<ul>
<li>잘 마주치지 않는데, A 함수는 B 함수의 리턴값에 신경쓰지 않고, 콜백함수를 넘긴다.(Async) 그런데, B 함수의 작업에 관심이 없음에도 불구하고 A 함수는 제어권을 넘긴다. 따라서 A 함수는 자신과 관련 없는 B 함수의 작업이 끝날때까지 대기한다.</li>
</ul>
<h2 id="2-resttemplate">2. RestTemplate</h2>
<h3 id="1-resttemplate이란">[1] RestTemplate이란?</h3>
<ul>
<li>spring에서 지원하는 객체로, 간편하게 REST방식의 API를 호출할 수 있는 스프링 내장 클래스다.</li>
<li>spring 3.0부터 지원되었고, REST API를 호출 후 json, xml의 응답을 모두 받을 수있다.</li>
<li>HTTP 프로토콜의 메소드(ex. GET, POST, DELETE, PUT)들에 적합한 여러 메소드를 제공하며, Header, Content-Tpye등을 설정하여 외부 API 호출할 수 있다.</li>
<li>Blocking I/O 기반의 동기 방식을 활용한 통신방식이다.</li>
</ul>
<h3 id="2-resttemplate-동작원리">[2] RestTemplate 동작원리</h3>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/2c7daa8d-3a33-4677-85bc-c3b1c3752207/image.png" alt=""></p>
<ol>
<li>애플리케이션 내부에서 REST API에 요청하기 위해 RestTemplate의 메서드를 호출한다.</li>
<li>RestTemplate은 MessageConverter를 이용해 java object를 request body에 담을 message(JSON etc.)로 변환한다.</li>
<li>ClientHttpRequestFactory에서 ClientHttpRequest을 받아와 요청을 전달한다.</li>
<li>실질적으로 ClientHttpRequest가 HTTP 통신으로 요청을 수행한다.
5/6. ResponseHandler에 의해 에러핸들링을 한다.</li>
<li>MessageConverter를 이용해 response body의 message를 java object로 변환한다.</li>
<li>결과를 애플리케이션에 돌려준다.</li>
</ol>
<h3 id="3-사용-예시">[3] 사용 예시</h3>
<pre><code class="language-java">   RestTemplate restTemplate = new RestTemplate();
   HttpHeaders headers = new HttpHeaders();
   MediaType mediaType = new MediaType(&quot;application&quot;, &quot;json&quot;, Charset.forName(&quot;UTF-8&quot;));
   headers.setContentType(mediaType);
   HttpEntity&lt;String&gt; entity = new HttpEntity&lt;&gt;(jsonData, headers);

   String response = restTemplate.postForObject(url, entity, String.class); // restTemplate 함수가 완료된 후에 아래코드가 실행된다. block-I/O 방식이기 때문. 비동기로 처리하고 싶다면? 별도의 쓰레드에서 처리가 필요한데 그 부분은 다음 절에서 설명.
   ...
</code></pre>
<h2 id="3-비동기-통신">3. 비동기 통신</h2>
<h3 id="1-배경">[1] 배경</h3>
<ul>
<li>여러 API를 호출 후, 이 데이터를 합치는 기능을 만든다고 해보자. 각각의 외부 API를 호출할 때 순차적으로 호출하면 좋을까 아니면 병렬로 호출하면 좋을까? 물론 상황마다 다르다. 만약 1번 API를 호출한 값이 2번 API를 호출할 때 필요하다면 순차적으로 호출하는게 당연하다. 이런 경우가 아니라면 즉, 특정 API의 결과값이 다른 API를 호출할 때 상관 없다면 빠르게 다른 API를 호출하는게 성능적으로 매우 유리하다.</li>
<li>즉, 우리는 API를 호출 할 때 <strong>결과값을 기다리지 않고</strong> 처리하는 비동기 통신을 사용하고 싶다.</li>
</ul>
<h3 id="2-async">[2] @Async</h3>
<ul>
<li><code>@SpringBootApplication</code>에 <code>@EnableAsync</code>를 설정 후, 비동기 메소드에 <code>@Async</code>을 달면 끝이다.</li>
<li>스프링의 <code>@Async</code>는 AOP에 의해 동작하는데, <code>@Async</code>으로 선언된 메서드는 비동기 메서드로 동작한다. AOP 내부로직을 보면 반환 메서드의 리턴 타입에 따라 상이하게 구현된다.<ul>
<li>CompletableFuture</li>
<li>ListenableFuture</li>
<li>Future</li>
<li>Return이 없는 케이스</li>
</ul>
</li>
<li>비동기 메서드는 별도의 쓰레드를 생성 후 작업한다. 결과값은 <code>@Async</code> 메서드를 호출한쪽에서 blocking하거나 혹은 콜백 메서드를 통해 처리할 수 있다.</li>
<li><strong>멀티스레드와 Blocking</strong> 방식을 사용하기 때문에, Request는 먼저 Queue에 쌓이고, 가용한 스레드에 할당되어 처리한다. <strong>즉, 1개의 요청 당 하나의 스레드를 할당한다.</strong></li>
<li>따라서 요청을 처리할 쓰레드가 없다면 요청은 큐에서 대기하게된다. 따라서 외부서버와의 커넥션이 많고, 오래 걸릴 경우 가용한 스레드 수가 줄어들어 전체 서비스가 느려질 수 있다.</li>
</ul>
<h4 id="future">Future</h4>
<ul>
<li>메서드의 결과를 전달받아야 한다면 Future를 사용한다.</li>
<li>스프링에서 제공하는 <code>AsyncResult</code>는 Future의 구현체며, future의 <code>get</code>메서드는 메서드의 결과를 조회할 때까지 계속 기다린다. 예를 들어, A 함수에서 Future를 리턴하는 함수를 호출한다면, A함수는 호출한 함수의 <code>get()</code>을 마주치기 전까지 non-blocking으로 실행되다 <code>get</code>을 마주치면 blocking된다.</li>
</ul>
<pre><code class="language-java">public Future&lt;UserInfo&gt; getUserInfo(String id) {
   UserInfo userInfo = getUserInfo(id); // userInfo APi Call by RestTemplate

   return new AsyncResult&lt;&gt;(userInfo);
}

...

Future&lt;UserInfo&gt; userInfoFuture = userService.getUserInfo(id);
// non-blocking
System.out.println(&quot;UserInfo : &quot; + userInfoFuture.get());
// blocking</code></pre>
<h4 id="listenablefuture">ListenableFuture</h4>
<ul>
<li><code>future.addCallback</code> 메서드는 비동기 메서드의 내부 로직이 완료되면 수행되는 콜백 기능이다. Future를 사용했을 때는 <code>future.get</code>을 사용하여 메서드가 처리될 때까지 호출한 함수쪽에서 blocking 현상이 발생했지만, 콜백 메서드를 사용한다면 결과를 얻을때까지 무작정 기다릴 필요가 없다.</li>
<li>개인적으로 결과론적으로 본다면 future, ListenableFuture에 큰차이는 없다고 생각한다.(get 메서드의 위치가 잘 있다면) 다만, future의 get의 순서를 잘못 지정한다면 성능은 크게 떨어질 것이다. 하지만 콜백으로 처리하는 ListenableFuture의 경우 이러한 실수를 사전에 방지할 수 있다.</li>
</ul>
<pre><code class="language-java">ListenableFuture&lt;UserInfo&gt; userInfoFuture = userService.getUserInfo(id);
userInfoFuture.addCallback( ...callback 처리);</code></pre>
<h4 id="completablefuture">CompletableFuture</h4>
<ul>
<li><code>Future</code> + <code>@Async</code>를 합친다면 비동기로 외부 서버와 통신할 수 있다. <code>CompletableFuture</code>는 <code>@Async</code>를 추가할 필요 없이 자체적으로 별도의 쓰레드를 생성하여 API를 통신한다. 뿐만 아니라 콜백 등록, Future 조합, 예외 처리 등 다양한 기능을 제공한다.</li>
<li>개인적으로 비동기 통신할 때 람다형식으로 편하게 읽기 쉽게 외부 API와 통신할 수 있다.</li>
<li>비동기 작업 실행<ul>
<li>runAsync : 반환값이 없는 경우, 비동기로 작업 실행</li>
<li>sypplyAsync : 반환 값이 있는 경우, 비동기로 작업 실행</li>
</ul>
</li>
<li>작업 콜백<ul>
<li>thenApply : 반환 값을 받아서 다른 값을 반환함</li>
<li>thenAccpet : 반환 값을 받아 처리하고 값을 반환하지 않음</li>
<li>thenRun : 반환 값을 받지 않고 다른 작업을 실행함</li>
</ul>
</li>
<li>작업 조합<ul>
<li>thenCompose : 두 작업이 이어서 실행하도록 조합하며, 앞선 작업의 결과를 받아서 사용할 수 있음</li>
<li>thenCombine : 두 작업을 독립적으로 실행하고, 둘 다 완료되었을 때 콜백을 실행함</li>
<li>allOf : 여러 작업들을 동시에 실행하고, 모든 작업 결과에 콜백을 실행함</li>
</ul>
</li>
<li>예외 처리<ul>
<li>exeptionally : 발생한 에러를 받아서 예외를 처리함</li>
<li>handle, handleAsync : (결과값, 에러)를 반환받아 에러가 발생한 경우와 아닌 경우 모두를 처리할 수 있음</li>
</ul>
</li>
</ul>
<pre><code class="language-java">ThreadPoolTaskExecutor executor;

CompletableFuture&lt;Bank1&gt; back1Info = getBack1(param1);
CompletableFuture&lt;Bank2&gt; back2Info = getBack2(param2);
CompletableFuture&lt;Bank3&gt; back3Info = getBack3(param3);

CompletableFuture.allOf(bank1Info, back2Info, back3Info)
    .thenRun( () -&gt;
        bankTotalInfo.builder()
            .bank1Info(bank1Info.join())
            .bank2Info(bank2Info.join())
            .bank3Info(bank3Info.join())
            .build()
    ).join();

...

private CompletableFuture&lt;Bank1&gt; getBank1(BankParam param) {
    return CompletableFuture.supplyAsync(() -&gt; bankService.getBank(param, executor))
        .exceptionally(e -&gt; new Bank1());
}
...
</code></pre>
<h2 id="4-webclient">4. WebClient</h2>
<ul>
<li>Spring WebClient는 웹으로 API를 호출하기 위해 사용되는 Http Client 모듈 중 하나로, RestTemplate와 동일하게 &#39;HttpClient&#39; 모듈이다. 차이점은 통신방법이 RestTemplate은 Blocking방식이고, WebClient는 Non-Blocking방식이다.</li>
<li>Spring WebClient가 필요한 이유는, 다시 말하면 Non-blocking방식이 필요한 이유는 네트워킹의 병목현상을 줄이고 성능을 향상시키기 위함이다.</li>
</ul>
<h3 id="1-기존-resttemplate의-blocking-방식의-문제점">[1] 기존 RestTemplate의 Blocking 방식의 문제점</h3>
<ul>
<li>위에서 언급한 것처럼 RestTemplate은 Multi-Thread와 Blocking방식이다. &#39;Thread Pool&#39;에 가용할 Thread를 미리 만들어놓고, Request가 오면 먼저 Queue에 쌓이고 가용한 스레드가 있으면 그 스레드에 할당되어 처리된다. 즉, 1 요청 당 1 스레드가 할당된다. 이때 각 스레드에서는 Blocking방식으로 처리되어 응답이 처리될때 까지 그 스레드는 다른 요청에 할당될 수 없다.</li>
<li>요청을 처리할 스레드가 있으면 아무런 문제가 없지만, 스레드가 다 차는 경우 이후의 요청은 Queue에 대기하게 된다. 대부분의 문제는 네트워킹이나 DB와의 통신에서 생기는데 이런 문제가 여러 스레드에서 발생하면 가용한 스레드수가 현저하게 줄어들게 되고, 결국 전체 서비스는 매우 느려지게 된다.</li>
</ul>
<h3 id="2-webclient-동작원리">[2] WebClient 동작원리</h3>
<p>Spring WebClient는 Single Thread와 Non-Blocking방식을 사용한다. 즉, Core 당 1개의 Thread를 이용한다.</p>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/c37c92e1-65bf-4305-a707-8587efe2d7f5/image.png" alt="">
이미지 출처 : <a href="https://luminousmen.com/post/asynchronous-programming-blocking-and-non-blocking">https://luminousmen.com/post/asynchronous-programming-blocking-and-non-blocking</a></p>
<ol>
<li>각 요청은 &#39;Event Loop&#39; 내의 Job으로 등록된다.</li>
<li>&#39;Event Loop&#39;는 각 Job을 &#39;Worker&#39;에게 요청한 후, <strong>결과를 기다리지 않고 다른 Job</strong>을 처리한다.</li>
<li>&#39;Event Loop&#39;는 Worker로부터 <strong>Callback</strong>으로 응답이 오면 그 결과를 요청자에게 제공한다. 이렇게 WebClient는 이벤트에 반응형으로 동작하도록 설계되었다.</li>
</ol>
<h3 id="3-사용법">[3] 사용법</h3>
<pre><code class="language-kotlin">
 fun getWebClient(webClientBuilder: WebClient.Builder, url: String): WebClient {
            return webClientBuilder.baseUrl(url) // webclient base url
                .defaultHeaders { it.contentType = MediaType.APPLICATION_JSON }
                .clientConnector(ReactorClientHttpConnector(getHttpClient())) // HTTP CLIENT 설정
                .build()
 } // webclient 생성

fun getHttpClient(): HttpClient {
   val httpClient = ConnectionProvider.builder(&quot;connection-pool&quot;)
                .maxConnections(MAX_CONNECTIONS) // 유지할 Connection Pool 의 수
                .pendingAcquireMaxCount(MAX_PENDING_ACQUIRE_COUNT) // Connection 을 얻기 위해 대기하는 최대 수
                .pendingAcquireTimeout(Duration.ofMillis(PENDING_ACQUIRE_TIMEOUT)) // Connection 모두 사용중일때 Connection 을 얻기 위해 대기하는 시간
                .maxIdleTime(Duration.ofSeconds(MAX_IDLE_TIME)) // 사용하지 않는 상태의 Connection 이 유지되는 시간
                .maxLifeTime(Duration.ofSeconds(MAX_LIFE_TIME)) // Connection Pool 에서의 최대 수명 시간
                .build()

            return HttpClient.create(apiConnectionProvider)
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, CONNECT_TIMEOUT_MS)
                .responseTimeout(Duration.ofMillis(RESPONSE_TIME_OUT_MS)) // Default RESPONSE TIMEOUT
                .resolver(DefaultAddressResolverGroup.INSTANCE)
} // webclient 생성 시 필요한 Http Client

fun userCall() {
    client // 위에서 언급한 getWebClient 사용
        .get() // get or post etc.. method 설정
         .uri { it.path(path).queryParams(parameters).build() } // baseurl + path, parameters 설정
         .header(&quot;header&quot;, header) // cookie, header .. 설정
         .accept(MediaType.APPLICATION_JSON) // accept type 설정
         .httpResponseTimeout(Duration.of(timeout)) // default timeout이 아닌, 변경된 timeout 설정 가능.
         .retrieve() // body를 받아 디코딩하는 메서드로, exchange보다 memory leak의 단점 보안
         .bodyToMono&lt;UserInfoResponse&gt;() // 해당 클래스로 맵핑
         .doOnError { // 에러 수신시 처리 방식
             throw WebClientException()
         }
         .map { UserInfo(it) } // 최종 반환 클래스
         .awaitSingle() // 뒤이어 나올 코루틴과 관련된 내용
// 많은 API를 연동하다보면 중복된 코드가 많이 발생할 수 있는데 코틀린에서 제공하는 &#39;확장함수&#39;를 이용하면 코드 중복을 최소화할 수 있다.
}</code></pre>
<h3 id="4-timeout-관련-정리">[4] timeout 관련 정리</h3>
<ul>
<li>CONNECT_TIMEOUT_MILLIS : http client level이며, 서버와 커넥션 맺는데 기다리는 시간이다.</li>
<li>ReadTimeoutHandler/WriteTimeoutHandler : TCP Level에 적용되므로 TLS handshake 동안에도 적용된다. 따라서, HTTP 응답에 대해 원하는 것보다 timeout을 높게 설정해야한다. 심지어 HTTP request가 진행되지 않는 경우에도 ReadTimeoutHandler/WriteTimeoutHandler 핸들러가 작동된다. 예를 들어, 1000ms이라고 잡아도 실제로 TLS 등의 이유로 1000ms에 읽는/쓰는 시간 뿐만 아니라 다른 작업도 포함된다.</li>
<li>Reactive Stream의 timeout : 클라이언트가 응답을 받는 시간 뿐만 아니라, 커넥션 풀로부터 커넥션을 얻고 새로운 연결을 생성하는 것과 같은 것들을 수행한다. TLS의 handshake도 포함</li>
<li>response timeout : responseTimeout 은 순수 http 요청/응답 시간에 대한 timeout 이다.<blockquote>
<p>통신에서 사용되는 모든 시간을 통합한 response timeout 추천</p>
</blockquote>
</li>
</ul>
<h3 id="5-성능비교">[5] 성능비교</h3>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/cb81525f-ccfb-471e-8ac2-44963664bd33/image.png" alt="">
이미지 출처: <a href="https://alwayspr.tistory.com/44">https://alwayspr.tistory.com/44</a></p>
<ul>
<li>1,000명까지는 비슷하지만 동시 사용자가 늘수록 RestTemplate은 급격하게 느려지는것을 볼 수 있다.</li>
<li>Spring 커뮤니티에서는 RestTemplate을 이미 Depreciated시키고 WebClient를 사용할것을 강력히 권고하고 있다.</li>
<li>RestTemplate보다 훨씬 적은 Thread를 사용하며, Thread가 부족하여 요청이 대기해야하는 상황을 방지하여 더 많은 요청을 처리할 수 있다고 생각한다.</li>
</ul>
<h2 id="5-webflux">5. Webflux</h2>
<h3 id="1-spring-mvc-vs-webflux">[1] Spring MVC vs WebFlux</h3>
<h4 id="spring-mvc">Spring MVC</h4>
<blockquote>
<p>Spring MVC는 기본적으로 <strong>Blocking &amp;&amp; Sync</strong>방식을 사용한다.</p>
</blockquote>
<ul>
<li>하나의 요청에 대해 하나의 스레드가 사용된다(<strong>thread-per-request</strong>). 그렇기에 다수의 요청을 대비하여 미리 스레드 풀을 생성해놓으며, 각 요청마다 스레드를 할당하여 처리한다. 따라서 Spring MVC 같은 경우 요청이 들어오면 그 요청을 Queue에 쌓고 순서에 따라서 Thread를 하나 점유해 요청을 처리한다. 동시 다발적으로 스레드 수를 초과하는 요청이 발생한다면 계속해서 큐에 대기하게 되는 <strong>Thread Pool Hell</strong> 현상이 발생할 수 있다.</li>
<li>동작중인 스레드가 블로킹 상태가 되면 다른 스레드에서 CPU 사용을 위해 문맥 교환(<strong>context switch</strong>)이 일어나게 된다. 만약 서버의 대부분의 연산이 이러한 블로킹 콜(blocking call)로 구성되어 있다면, 특히 MSA 환경은 API 호출 또는 db connection으로 대부분의 비즈니스 로직이 구성되어 있으니 잦은 문맥 교환이 일어나게 된다. 내부 블로킹 콜이 차지하는 비중만큼 문맥 교환의 오버헤드가 있는 것이다.</li>
<li>요청량이 증가한다면 스레드 수도 이에 비례하여 증가한다. 64비트 JVM은 기본적으로 스레드 스택 메모리를 1MB 예약 할당하는데 스레드 수가 증가한다면 서버가 감당해내지 못할 만큼의 메모리를 먹을 수 있다. 1만개 요청이면 9.76GB, 100만 개면 976GB 메모리를 점유한다.</li>
</ul>
<h4 id="spring-webflux">Spring WebFlux</h4>
<blockquote>
<p> Spring Webflux는 <strong>Non-Blocking &amp;&amp; Async</strong> 방식을 사용한다.</p>
</blockquote>
<ul>
<li>Spring Webflux는 Spring5에 새롭게 추가되었으며, <strong>Non-Blocking &amp;&amp; Reactive Stream</strong>을 지원한다.</li>
<li>기존 Spring MVC의 Servlet API는 v3.1부터 Non-Blocking I/O를 위한 API를 제공했다. 하지만 Filter, Servlet과 같이 동기적으로 처리하는 모듈 및 Blocking 방식의 API들이 있기에 완벽한 Non-Blocking 환경의 개발을 할 수는 없었다. 또한, Async &amp;&amp; Non-Blocking 환경의 서버로 Netty가 부상하고 있으며 이 Netty와의 연동을 위해 Spring의 새로운 프레임워크가 필요했다. 이게 Webflux다.</li>
<li>서버는 스레드 한 개로 운영하며, 디폴트로 CPU 코어 수 개수의 스레드를 가진 워커 풀을 생성하여 해당 워커 풀 내의 스레드로 모든 요청을 처리한다. <strong>제약이 있다면 논블로킹으로 동작하여야만 하며</strong>, 블로킹 라이브러리가 필수적으로 사용되어야 한다면, 워커 스레드가 아닌 외부 별도의 스레드로 요청을 처리해야한다(Thread 갯수가 제한되있기 때문).</li>
<li>Event-Loop가 돌면서 요청이 발생할 경우 그것에 맞는 핸들러에게 처리를 위임하고 처리가 완료되면 <strong>Callback</strong> 메서드를 통해 응답을 반환한다.</li>
<li>요청이 처리될 때까지 기다리지 않기 때문에 Spring MVC에 비해 사용자의 요청을 대량으로 받아낼 수 있다는 장점이 있으며 상대적으로 cpu, memory, thread에 자원을 낭비하지 않는다. (context-switching이 적고 &amp;&amp; Blocking하지 않으며, 더 적은 쓰레드를 사용하기 때문) 이렇게 <strong>적은 양의 스레드와 최소한의 하드웨어 자원</strong>으로 <strong>동시성</strong>을 핸들링하는 장점이 있다.</li>
<li>함수형 스타일 지원 (이 부분은 좀 더 확인 필요)</li>
</ul>
<h3 id="2-spring-mvc---webflux-전환-주의점">[2] Spring MVC -&gt; WebFlux 전환 주의점</h3>
<h4 id="내부-작업-유형">내부 작업 유형</h4>
<ul>
<li>내부 hard한 연산들이 로직의 주를 이룰 경우 Spring MVC보다 성능적인 향상을 내지 못한다고 한다. 따라서 전환을 고려한다면 내부 로직들이 어떤 연산으로 이루어져 있는지 고려해야한다. 예를 들어, 기존 로직들이 내부 연산보다는 외부에 Blocking Call이 많다면 전환시 성능적 개선을 가져올 수 있다. 반면 내부 연산이 많다면 오히려 Thread가 많은 Spring MVC가 더 성능적 우위를 가져올 수 있다.</li>
</ul>
<h4 id="reactive-library-유무">Reactive Library 유무</h4>
<ul>
<li>사용하는 라이브러리들이 블로킹이라면 이는 쉽게 논블로킹으로 전환하기 어려울 수 있다. 워커풀이 아닌 별도의 외부 스레드풀을 생성하여 논블로킹으로 처리할 수 있겠지만, 스프링 가이드는 최대한 이러한 방법도 피하는 것을 권장한다.</li>
</ul>
<blockquote>
<p>Webflux 는 Asynchronous Non-blocking I/O 을 방식을 활용하여 성능을 끌어 올릴 수 있는 장점이 있다. 그런데 이 말은 즉, Non Blocking 기반으로 코드를 작성해야 한다. 만약 Blocking 코드가 있다면 Webflux를 사용하는 의미가 떨어지게 된다. 얼마 전까지는 Java 진영에 Non Blocking 을 지원하는 DB Driver가 없었지만, 최근에 R2DBC 가 릴리즈되어 이제는 Java 에서도 Non Blocking 으로 DB 를 접근할 수 있게 되었다.</p>
</blockquote>
<h3 id="3-netty">[3] Netty</h3>
<ul>
<li>Netty는 NIO(Non-Blocking Input/Output) 서버 프레임워크다. 즉, TCP/UDP 소켓 서버와 같은 네트워크 프로그래밍을 Non-Blocking 방식으로 쉽게 개발할 수 있다.</li>
<li>Netty는 NIO 방식으로 이벤트를 처리하기 때문에 자원이 스레드를 계속 점유하며 Block 상태를 유지 하지 않는다. 따라서 톰캣보다 더 많은 커넥션을 처리할 수 있는 장점이 있다.</li>
<li>Netty는 이벤트 기반 방식으로 동작하기 때문에  톰캣과달리 스레드Pool의 스레드 개수는 머신의 Core 개수의 두배다. 즉, 스레드수가 적어 스레드 경합이 잘 일어나지 않는다.</li>
<li>새부 내용은 하나의 EVENT Loop(single thread)에서 들어오는 incoming request에 대한 I/O를 담당하고, 해당 작업에 대한 처리는 worker thread에서 수행한다. Event Loop는 Non-Blocking이며 Worker Thread에서 작업 완료시 이벤트(콜백)로 받아 요청을 처리한다. 만약 Blocking 호출이 많다면 Worker Thread에서 해당 쓰레드를 점유해야되는데, 이를 방지하고자 별도의 스레드를 만들어서 처리하기도 한다.(비권장)
<img src="https://velog.velcdn.com/images/youngmin-mo/post/6c5d182b-4b82-4731-a339-57172da4d6e8/image.png" alt=""></li>
</ul>
<h3 id="4-reactive-programming">[4] Reactive Programming?</h3>
<ul>
<li>Reactive Programming이란 결국 Data Stream을 이용하여 전달하고, 데이터의 변경 시점을 이벤트로 하여(callback), 수신자와 송신자 사이에 데이터를 전달시키는 비동기 프로그래밍이다. 이로써 간결해진 Thread를 사용하며, 간단하게 비동기 연산을 처리할 수 있다. 또한 콜백 연산이 훨씬 자유로운 장점도 있다.</li>
<li>Spring Webflux에서 사용하는 reactive library가 Reactor이고 Reactor가 Reactive Streams의 구현체다. Flux와 Mono는 Reactor 객체이며, 차이점은 발행하는 데이터 갯수다.</li>
</ul>
<h3 id="1-mono-vs-flux">[1] Mono vs Flux</h3>
<ul>
<li>Flux vs Mono<ul>
<li>Flux : 0 ~ N 개의 데이터 전달</li>
<li>Mono : 0 ~ 1 개의 데이터 전달</li>
</ul>
</li>
<li>보통 여러 스트림을 하나의 결과를 모아줄 때 Mono를, 각각의 Mono를 합쳐서 하나의 여러 개의 값을 여러개의 값을 처리할 떄 Flux를 사용한다.</li>
<li>그런데 Flux도 0~1개의 데이터 전달이 가능한데, 굳이 한개까지만 데이터를 처리할 수 있는 Mono라는 타입이 필요할까? 데이터 설계를 할때 결과가 없거나 하나의 결과값만 받는 것이 명백한 경우,  List나 배열을 사용하지 않는 것처럼, Multi Result가 아닌 하나의 결과셋만 받게 될 경우에는 불필요하게 Flux를 사용하지 않고 Mono를 사용하는게 좋을 것이다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Architecture?]]></title>
            <link>https://velog.io/@youngmin-mo/archi</link>
            <guid>https://velog.io/@youngmin-mo/archi</guid>
            <pubDate>Sat, 31 Dec 2022 07:26:53 GMT</pubDate>
            <description><![CDATA[<h2 id="1-아키텍처가-필요한-이유">1. 아키텍처가 필요한 이유</h2>
<blockquote>
<p>설계의 핵심은 의존성이다.</p>
</blockquote>
<p>큰 계획 없이 개발하다 보면 얽히고설킨 스파게티 코드가 나온다. sw는 지속해서 변하고, 정돈되지 않은 코드를 유지보수한다면 개발 시간이 오래 걸릴 것이다. 예를 들어 하나의 기능을 수정하는데 여러 모듈을 건드린다면 당연히 개발비용이 증가한다. <strong>아키텍처가 필요한 이유는 sw를 쉽게 변경할 수 있는 구조를 설계하기 위함이다.</strong> 여기서 말하는 설계란 코드를 어떤 클래스, 패키지에 배치할지에 대한 의사결정이다.
<img src="https://velog.velcdn.com/images/youngmin-mo/post/a24a6828-0513-41e0-97e3-2fab442eea0b/image.png" alt=""></p>
<p>위 그림은 <code>마틴 파울러</code>가 언급한 내용으로, 아키텍처 없는 개발은 초반에는 빠른 생산성(개발 시간)을 가질 수 있지만 개발을 지속할수록 점차 생산성이 떨어지게 된다. 하지만 Good Design을 가진 아키텍처는 시간이 지나도 새로운 기능을 쉽게 반영할 수 있기 때문에 돈과 직결되는 생산성이 비약적으로 좋아진다.</p>
<p>프로젝트엔 여러 기능의 코드가 있다. db에 쿼리를 날리거나, 외부 요청을 받아들일 수 있고, 혹은 도메인 로직을 개발하기도 한다. 이러한 기능들 모두가 중요할까? 중요하다. 하지만 모듈 간 우선순위가 있다. 앞으로 이 기능을 유지보수할 팀을 위해 <strong>프로젝트의 핵심 모듈이 외부 인프라를 포함한 다른 모듈에 의존하지 않게 구성하고 싶다.</strong></p>
<p>A가 B에 의존한다는 의미는 B가 변경될 때 A도 함께 변경될 여지가 있다는 의미다. 핵심 로직이 담긴 모듈이 다른 서브 모듈에 의존한다면 어떻게 될까? 아마도 덜 중요한 모듈의 변경 때문에 핵심 로직을 계속해서 수정하고, 수정을 잘못했을까 봐 마음이 떨리기도 할 것이다.</p>
<h2 id="2-layered-architecture">2. Layered Architecture</h2>
<blockquote>
<p>목적이 같은 코드들을 계층으로 그룹화하여 관심사를 분리한 아키텍처</p>
</blockquote>
<p>다음으로 프로젝트에서 많이 사용하는 아키텍처 중 하나인 계층형 아키텍처를 살펴본다. 아키텍처 이해가 쉬워 간단한 서비스일 때 쉽게 적용할 수 있으며, 특정 기능을 담당할 코드를 어디에 배치할지 손쉽게 결정할 수 있다.</p>
<p>화면 표현/전환을 담당하는 Presentation Layer → 로직을 담당하는 Application 및 Domain Layer → 데이터 처리를 담당하는 Persistence Layer 흐름으로 구성된다. 이렇게 데이터 인입부터 DB 처리까지 시스템 플로우가 아키텍처에 그대로 적용되어 이해가 쉽다.</p>
<p>예를 들어, 회원 정보를 조회하는 API를 개발한다고 가정해보자. Request를 처리할 Controller는 Presentation 패키지, 회원 정보와 관련된 로직은 Application 패키지에 담는다. 마지막으로 DB로부터 조회할 Entity 및 쿼리는 각각 Domain, Persistence 패키지에 구성한다. 각각의 모듈을 어떤 패키지에 넣을지 굉장히 쉽게 떠올릴 수 있으며 나중에 코드를 어떤 패키지에서 찾을지 떠올릴 때도 유용하다.</p>
<pre><code class="language-yaml">package 구조 예시

Presentation Package
    - UserController.java
Application Package
    - UserUseCase.java
Domain Package
    - User.java
infrastructure Package
    - UserRepositoryImpl.java</code></pre>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/2d200cf5-782c-4885-8628-03fb81026152/image.png" alt=""></p>
<p>보통 애플리케이션은 도메인이(비즈니스 로직) 핵심인데, Domain Model이 Persistence 계층에 의존할 때 <strong>Persistence 영향으로 비즈니스 로직 역시 변경될 수도 있다.</strong> 현재 많은 서비스가 MSA 구조로 전환하고 redis, es 등 다양한 데이터 저장소가 나오고 있다. 이렇게 Persistence 계층의 변경이 자주 일어날 수 있는데, Persistence 계층 때문에 핵심 영역인 Domain이 변경되는 건 지양하고 싶다.</p>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/de519697-e7bf-4e11-a5c9-0a2f7afcc313/image.png" alt=""></p>
<p>이를 해결하고자 &#39;Dependency Inversion Principle&#39; 원리를 적용하기도 한다. <strong>Persistence Interface의 위치를 고수준 (비즈니스 계층 영역)으로 위치하고, Persistence Layer의 구현체를 저수준 모듈에 위치함을 말한다.</strong> 이로써 도메인 로직이 Persistence Layer의 영향을 받지 않게 구성할 수 있다. 개인적으로 이렇게 변경한 구조는 뒤에 나올 Hexagonal Architecture와 유사한 구조라고 생각한다.</p>
<p><em>다음으로 말할 내용은 매우 주관적인 생각이다. Layered Architecture의 단점으로 비즈니스 로직이 Persistence 계층에 의존함을 말했다. 만약 사업 초기 비즈니스 규칙이 자주 변하고, Entity 역시 자주 변화는 상황에서 모든 Layer가 도메인 로직에 의존한다면 어떻게 될까? 아마도 유지보수 시 많은 컴포넌트를 뜯어 고쳐야 할 것이다. 물론 도메인 로직이 중요하기 때문에 의존성을 도메인 로직으로 향하지 않게함은 동의한다. 하지만 편의상 사업 초기에 비즈니스 규칙이 자주 바뀔 때는 처음부터 뒤에 나올 클린 아키텍처가 아닌 Layered Architecture가 더 적합할 수도 있다고 생각한다.</em></p>
<h2 id="3-hexagonal-architecture란">3. Hexagonal Architecture란</h2>
<blockquote>
<p>핵심은 도메인이다. 도메인 개발이 쉽고, 외부 인프라를 포함한 다른 계층의 영향이 도메인으로 향하는 것을 최소화하는 방향으로 구성한다.</p>
</blockquote>
<p>마지막으로 Hexagonal Architecture를 얘기해본다. Hexagonal Architecture는 클린 아키텍처를 구현하는 방법을 &#39;구체화&#39;한 아키텍처다. 결국 두 아키텍처 모두 도메인(업무 규칙)을 제일 우선시하며, Infrastructure → Domain Layer에 영향을 최소화하도록 의존성을 관리한 아키텍처다.</p>
<h3 id="배경">배경</h3>
<ul>
<li>최근 다양한 데이터 저장소가 등장하고, 기존 서비스들도 MSA 구조로 전환하면서 하나의 서비스는 많은 외부 서비스를 호출하고 있다. 따라서 외부 서비스의 변경은 이전보다 빈번해졌다. 이때 도메인이 외부 인프라와 통신하는 로직에 의존한다면, 외부 서비스의 변경이 일어날 때마다 도메인을 수정해야 한다.</li>
<li>Hexagonal Architecture는 도메인을 보호하기 위해 도메인을 표현하는 내부 영역과 외부 서비스를 표현하는 외부 영역을 철저히 분리한다. 예를 들어 데이터베이스가 변경되거나, API를 교체하는 등 기술적인 세부 사항이 변경되더라도 도메인 로직 수정 없이 인프라 코드만 구현하면 된다.</li>
<li>근데 왜 다른 모듈보다 도메인을 우선시할까? 비즈니스 로직은 결국 시스템의 &#39;규칙&#39;이자 &#39;시스템이 존재하는 이유&#39;다. 따라서 시스템의 데이터가 어떻게 저장되고, 수정될지를 도메인이 결정한다. 당연히 프로젝트의 근간이자 핵심은 외부의 변화로부터 멀어져야 한다.</li>
</ul>
<h3 id="port--adapter">Port &amp; Adapter</h3>
<blockquote>
<p>port는 내부 비즈니스 영역을 외부 영역에 노출한 인터페이스, adapter는 외부 서비스와 포트 간 데이터 교환을 담당한다.</p>
</blockquote>
<h4 id="inbound-구조">Inbound 구조</h4>
<ul>
<li>Inbound Adapter : 외부에서 들어온 요청을 처리하며 Inbound Port를 호출함으로 도메인 로직에 접근할 수 있다. ex) Controller</li>
<li>Inbound Port : 도메인 로직 사용을 위해 노출한 포트로 Inbound Adapter가 Inbound Port를 통해 도메인 로직을 호출한다. ex) Service Interface</li>
</ul>
<h4 id="outbound-구조">Outbound 구조</h4>
<ul>
<li>Outbound Port : 도메인 로직에서 외부 영역에 있는 Outbound Adapter를 호출할 때 사용한다. Outbound Port는 Outbound Adapter의 추상화로, 도메인 로직에서 외부 서비스 호출 시 Outbound Adapter에 직접 의존하는 걸 막아준다. <strong>즉, 도메인 로직은 Outbound Adapter를 몰라도된다. Domain은 Outbound Port만 알면된다.</strong> 이러한 방향으로 개발한다면 , 외부 인프라에 의존하는 도메인이 줄어들어 도메인을 외부로부터 보호할 수 있다. ex) Repository Interface</li>
<li>Outbound Adapter : outbound port의 구현체로, 외부 서비스와 연계하여 내부 비즈니스 영역과 외부 서비스 간 데이터 교환을 담당한다. ex) RepositoyImpl</li>
</ul>
<p><em>다음으로 말할 내용은 개인적인 생각이다. Inbound Adapter(controller)에서 Inbound Port(service interface)를 <code>꼭</code> 사용할 이유는 없다고 생각한다. 서비스가 교체될 때 Inbound Adapter 단에서 변경이 일어나지, 도메인 로직에서 일어나는 건 아니기 때문이다. 하지만, 도메인 로직에서 외부 서비스를 호출할 때는 반드시 Outbound Adapter가 아닌 Outbound Port를 사용해야 한다. Inbound 구조와 달리 Outbound 구조는 추후 변경이 일어날 때 도메인 로직 단에서 변경이 일어날 수 있기 때문이다. 결국 port &amp; adapter 구조를 지키는 것보다 어떻게 하면 도메인 로직을 보호할 수 있을까에 대한 고민이 필요하다고 생각한다.</em></p>
<h3 id="hexagonal-architecture-시나리오">Hexagonal Architecture 시나리오</h3>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/72ce95a2-3e29-412d-a1f4-d09a0340a7ca/image.png" alt=""></p>
<ol>
<li>그림의 왼쪽 초록색 영역의 WebServer에서 요청이 들어오면Controller(Outbound Adapter)가 요청 로직을 처리하고 Service Interface(Outbound Port)를 통해 Service(Application Layer)로 전환한다.</li>
<li>Service에서 도메인 로직을 위해 필요한 작업(ex domain entity 변환)을 수행 후, Domain Layer로 전환한다.</li>
<li>도메인 로직을 처리하던 중 외부 서비스와 통신이 필요하다면 Repository Interface(Outbound Port)을 통해 요청한다.</li>
<li>RepositoyImpl(Outbound Adapter)가 실제로 외부 API와 통신하며 결괏값을 도메인 로직 쪽으로 전달한다.</li>
<li>그림의 오른쪽 상단에 SMS Server → Mailing Server로 외부 인프라를 교체한다고 가정해보자. 우리는 Outbound Port를 사용했기 때문에 도메인 영역에 영향 없이 Outbound Adapter만 교체하면 된다.</li>
</ol>
<pre><code>package 구조 예시

Presentation Package(Inbound Adapter)
    - UserController.java
Infrastructure Package(Outbound Adapter)
    - UserRepositoryImpl.java
Application Package(Service)
    - UserUseCase.java
Domain Package(Domain Logic, Outbound Port)
    - UserRule.java
    - UserRepository Interface</code></pre><h2 id="4-결론">4. 결론</h2>
<ul>
<li>먼저 살펴본 것은 아키텍처가 필요한 이유다. 코드를 특정 기준에 따라 분류한다면, 나중에 기능을 파악할 때 코드들이 어디 있는지 빠르게 파악할 수 있어 유지보수가 쉬울 것이다. 여기서 더 나아가 의존성의 중요성을 살펴봤었다. 결국 비즈니스는 계속 변하고, 이에 맞춰 서비스도 빠른 배포가 필요할 것이다. 이렇게 변화에 대응하기 위해 의존성을 고민하며 SW를 설계해야 됨을 깨달았다.</li>
<li>이후 아키텍처를 구분하는 방식에 대해 알아봤다. 플로우를 쉽게 파악하기 위한 &#39;Layered Architecture&#39;, 혹은 각각의 기능마다 패키지를 구성하는 방식, 혹은 도메인 로직을 보호하기 위한 &#39;Clean Architecture&#39;를 적용하는 사례가 있었다. 특정 아키텍처가 항상 좋음이 아닌 각각의 아키텍처마다의 장단점이 있고 상황에 맞게 적용이 필요함을 느꼈다.</li>
</ul>
<h4 id="참고-자료">참고 자료</h4>
<ul>
<li>[마틴 파울러] 소프트웨어 아키텍처의 중요성 (한글 자막)</li>
<li>우아한객체지향 by 우아한형제들 개발실장 조영호님</li>
<li><a href="https://www.youtube.com/watch?v=saxHxoUeeSw">https://www.youtube.com/watch?v=saxHxoUeeSw</a></li>
<li><a href="https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/">https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/</a></li>
<li><a href="https://engineering.linecorp.com/ko/blog/port-and-adapter-architecture/">https://engineering.linecorp.com/ko/blog/port-and-adapter-architecture/</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Kubernetes] 전체 시스템 플로우]]></title>
            <link>https://velog.io/@youngmin-mo/Kubernetes-%EC%A0%84%EC%B2%B4-%EC%8B%9C%EC%8A%A4%ED%85%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0</link>
            <guid>https://velog.io/@youngmin-mo/Kubernetes-%EC%A0%84%EC%B2%B4-%EC%8B%9C%EC%8A%A4%ED%85%9C-%ED%94%8C%EB%A1%9C%EC%9A%B0</guid>
            <pubDate>Thu, 29 Dec 2022 06:44:12 GMT</pubDate>
            <description><![CDATA[<h2 id="1-kubernetes-리소스-구조">1. Kubernetes 리소스 구조</h2>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/29059d03-b4c0-4a06-bd7d-c0f2d5d206af/image.png" alt=""></p>
<ul>
<li>Horizontal PodAutoScaler에 의해 트래픽이 늘어나거나, 시스템 리소스 사용량이 증가하면 유동적으로 파드를 늘린다. 이후 사용량이 줄어들면 파드 일부를 제거하기도한다.</li>
<li>Deployment를 통해 무중단 배포가 가능하며, 이 과정에서 ReplicaSet을 활용한다.</li>
<li>Pod Template에서 readness probe를 통해 health-check를 수행한다. 이를 통해 준비되지 않은 파드가 요청을 처리하는 불상사를 방지한다.</li>
<li>confingmap 및 secret을 통해 환경변수 및 보안 요소를 처리한다.</li>
<li>PVC, PV를 통해 디스크에 데이터를 유지하기도 한다.</li>
<li>stateless한 파드, stateful한 파드를 구별하여 이전 상태를 유지하기 위해 statefulset 리소스를 활용하기도한다.</li>
<li>Daemonset, Job, CronJob을 통해 주기적으로 실행해야되는 Job들을 처리한다.</li>
<li>ResourceQuota, LimitRange를 활용하여 cpu, memory, 요청 수에 따라 파드를 늘리거나/ 줄이는 등 사용량을 제어할 수 있다.</li>
<li>외부 요청을 처리하기 위해 Ingress, Service 리소스를 활용한다. 파드는 지속적으로 ip 주소가 변경되는데, 외부에 변경되지 않는 public ip 주소를 노출한다.</li>
<li>쿠버네티스 내의 리소스를 체계적으로 유지하기 위해 하나 이상의 레이블을 지정하기도 한다.</li>
</ul>
<h2 id="2-pod-lifecycle">2. Pod Lifecycle</h2>
<ul>
<li>파드에서 실행 중인 애플리케이션은 언제든지 종료될 수 있기 때문에 이를 고려해야 한다.</li>
<li>OOMKiller와 같은 크래시 등 다양한 이유로 컨테이너가 재시작될 수 있다. 이 경우 파드는 동일하지만 컨테이너 자체는 완전히 새로운 것이 된다. 컨테이너를 다시 사용하더라도 데이터를 보존하기 위해 최소한 파드 범위 이상의 볼륨을 사용하길 권장한다.</li>
<li>파드의 컨테이너가 계속해서 크래시 되면 kubelet은 최대 5분까지 파드를 재시작한다. 파드는 계속 CrashLoopBackOff 상태가 표시되고, 레플리카수와 레플리카가 일치하기 때문에 컨트롤러는 별도의 조치를 하지 않는다. 즉, 요청을 처리하지 않는 파드가 계속 띄어져 있다. 그러면 어떻게 해야할까? CrashLoopBackOff 관련 알람을 설정하고, 알람이 왔을 때 조치를 취하거나 예를 들어 재시작하거나 혹은 로그를 보고 문제있는 부분을 수정하면된다. 근데 로그를 따로 보관하지 않고 컨테이너단의 로그를 저장하면 아마 컨테이너 재시작시 해당 로그는 사라질 것이다. ㅠ</li>
</ul>
<h2 id="3-init-conatainer">3. Init Conatainer</h2>
<ul>
<li>파드의 주 컨테이너를 구동시키기 전에 초기화하는 것을 목적으로 사용한다.</li>
<li>파드는 여러 개의 초기화 컨테이너를 가질 수 있고, 이는 순차적으로 실행되며 마지막 컨테이너가 완료된 후에 주 컨테이너가 시작된다. 이를 통해 파드A와 파드B가 있을때 파드B의 초기화 컨테이너에서 A의 구동을 기다리도록 한다면 파드 A,B의 구동 순서를 보장할 수 있다. 예를 들어, A -&gt; B 파드 순으로 실행해야된다면, A 파드 주 컨테이너 구동 전 init-container로 B 파드에게 curl을 날려 응답이 정상일 때 까지 기다리는 방법도 있다.</li>
<li>물론 애플리케이션이 시작되기 전 준비 상태가 되기 위해 의존해야 할 서비스를 필요로 하지 않도록 애플리케이션을 만드는 것이 가장 좋다. 하지만 애플리케이션 의존성이 있다면 이를 위해 레디니스 프로브를 통해 의존성이 완료되어 정상 응답을 줄수 있을때 서비스에 투입되도록 하는것이 바람직하다.</li>
</ul>
<h2 id="4-모든-클라이언트-요청의-적절한-처리-보장">4. 모든 클라이언트 요청의 적절한 처리 보장</h2>
<ul>
<li>파드의 스케일업과 스케일다운 과정 중에 모든 클라이언트 요청이 올바르게 처리되어야 할 것이다. 이를 위해서는 연결이 끊어지지 않도록 애플리케이션에서 몇 가지 규칙을 잘 따라야 한다.</li>
<li>파드 스펙에 레디니스 프로브를 지정하지 않으면 곧바로 파드는 항상 준비된 것으로 간주되기 떄문에 이를 필수적으로 지정해야 한다. 레디니스 프로브에서는 요청을 올바르게 처리할 준비가 됐을 때에만 레디니스 프로브가 성공을 반환하도록 해야 한다.</li>
<li>파드 제거가 필요할 경우 pod를 stop하고 kube-proxy에서 삭제할려는 pod ip를 iptables에서 제거한다. 하지만 파드에 종료 신호를 보낸 후에도 클라이언트의 요청을 받을 수 있어 (iptables에서 아직 삭제되지 않거나 혹은 캐시 정책도 있을 수 있고), Connection Refused 에러가 발생할 수 있다. 따라서 파드 종료 요청을 보낸 후, iptables에서 제거 시간을 고려하여 파드 종료 요청 후 5~10초 후에 파드를 종료한다. (아마 자체적으로 처리되있을 것이다.)</li>
</ul>
<h2 id="5-애플리케이션을-쉽게-실행하고-관리">5. 애플리케이션을 쉽게 실행하고 관리</h2>
<ul>
<li>이미지를 만들때 OS 배포판의 모든 파일을 다 넣을 필요는 없다. 다 넣을 경우 파드가 노드에 처음 스케줄링할 때 필요 이상으로 대기해야 하는 경우가 생길 수 있다. (컨테이너 레지스트리에서 이미지 다운로드 시간이 증가할 수 있기 때문) 따라서 불필요한 것이 없는 작은 이미지로 만드는 것이 중요하다.</li>
<li>하지만 최소한의 이미지로는 디버깅이 어렵기 때문에 컨테이너 내에 ping,dig, curl 또는 이와 유사한 도구 등은 이미지에 포함시켜주는 것이 좋다.</li>
<li>파드 매니페스트에서 latest 태그를 참조하면 개별 파드 레플리카가 실행중인 이미지 버전을 알 수 없기 떄문에 이를 사용해서는 안된다. 그렇다고 imagePullPolicy가 always일 때는 새 파드가 배포될 떄마다 커네이너 런타임이 이미지 레지스트리에 접속해서 이미지를 가져와야 해서 파드 시작 속도가 약간 느려질 수 있다. 그리고 혹시 레지스트리에 연결할 수 없는 상황에서는 파드를 시작할 수 없게 된다. 결론은 상황에 맞게 잘 판단하자.(?)</li>
<li>최소한 리소스에는 리소스를 설명하는 어노테이션과 담당자 연락처 정보가 포함된 어노테이션을 포함시켜주는 것이 좋다.</li>
<li>컨테이너가 왜 종료되었는지를 추적하는 일은 무언가 해놓지 않았다면 굉장히 추적이 어렵다. 그렇기 때문에 로그 파일에 필요한 모든 디버그 정보를 포함시켜두는 것이 좋다. pod &gt; yaml -&gt; terminationMessagePath</li>
<li>파드로 각각 분리되있기 때문에 이를 합칠 방법이 필요하다. ELK 스택을 활용한다. ElasticSearch, LogStash, Kibana로 구성된 ELK 스택 또는 EFK 스택(ElasticSearch, FluentD, Kibana)을 통해 중앙 집중식 로깅을 활용하자.</li>
</ul>
<blockquote>
<p>끝</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Kubernetes] 파드의 컴퓨팅 리소스 관리]]></title>
            <link>https://velog.io/@youngmin-mo/Kubernetes-%ED%8C%8C%EB%93%9C%EC%9D%98-%EC%BB%B4%ED%93%A8%ED%8C%85-%EB%A6%AC%EC%86%8C%EC%8A%A4-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@youngmin-mo/Kubernetes-%ED%8C%8C%EB%93%9C%EC%9D%98-%EC%BB%B4%ED%93%A8%ED%8C%85-%EB%A6%AC%EC%86%8C%EC%8A%A4-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Thu, 29 Dec 2022 06:43:48 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>파드의 예상 소비량과 최대 소비량을 설정하는 것은 파드 정의에서 매우 중요하다. 이는 리소스를 공평하게 공유하고, 클러스터 전체에서 파드가 스케줄링되는 방식에도 영향을 미친다.</p>
</blockquote>
<h2 id="1-request와-limit">1. Request와 Limit</h2>
<ul>
<li>파드를 생성할 때 컨테이너가 필요로하는 cpu, memory에 엄격한 제한을 지정할 수 있다.</li>
<li>컨테이너에 개별적으로 지정되며 파드 전체에 지정되지 않는다. 이때 파드는 모든 컨테이너의 요청과 제한의 합으로 계산된다.</li>
<li>cpu 요청을 지정하지 않으면 컨테이너에 실행중인 프로세스에 할당되는 cpu 시간에 신경쓰지 않는다는 것과 같다. 하지만 최악의 경우 cpu 시간을 전혀 할당받지 못할수도 있다. 이는 다른 프로세스가 cpu 요청을 많이 한 상황에서 발생할 수 있다. 이러한 이유로 배치같은 경우 cpu를 지정하지 않을 수도 있으나, 웹 애플리케이션에서는 필수적으로 지정하는 것이 좋다.</li>
</ul>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
  name: requests-pod
spec:
  containers:
  - image: busybox
    command: [&quot;dd&quot;, &quot;if=/dev/zero&quot;, &quot;of=/dev/null&quot;]
    name: main
    resources:
      requests:              # 컨테이너 리소스 요청
        cpu: 200m         # cpu 200밀리코어 (1 core : 1000m 또는 1, 200밀리코어는 1/5 core 시간을 의미)
        memory: 10Mi   # 10Mi 메모리를 요청</code></pre>
<h2 id="2-request">2. Request</h2>
<h3 id="1-특징">[1] 특징</h3>
<ul>
<li>파드에 필요한 리소스의 최소량을 지정할 수 있다.</li>
<li>스케줄러는 파드를 스제줄링할 때 먼저 파드의 리소스 요청 사항을 만족하는 충분한 리소스를 가진 노드만을 고려한다.</li>
<li>스케줄러는 스케줄링하는 시점에 각 개별 리소스가 얼마나 사용되는지 보지 않고, 노드에 배포된 파드들의 리소스 요청량의 전체 합만을 기준으로 처리한다. 이는 실제 리소스 사용량에 기반해 다른 파드를 스케줄링한다는 것은 이미 배포된 파드에 대한 리소스 요청 보장을 깨뜨릴 수 있기 때문이다.</li>
</ul>
<h3 id="2-cpu-시간-공유">[2] CPU 시간 공유</h3>
<ul>
<li>CPU 요청은 단지 스케줄링에만 영향을 미칠 뿐만 아니라 남은 CPU 시간을 파드에 분배하는 방식도 결정한다. 미사용된 CPU는 두 파드 사이에 요청 비율에 맞게 나눠진다.</li>
<li>이외에도 한 컨테이너가 CPU를 최대로 사용하려는 순간 나머지 파드가 유휴상태에 있다면 전체 CPU를 사용할 수도 있다. (아무도 사용하지 않는다면 사용 가능한 모든 CPU를 사용 가능하다.)</li>
</ul>
<h2 id="3-limit">3. Limit</h2>
<h3 id="1-특징-1">[1] 특징</h3>
<ul>
<li>특정 컨테이너가 지정한 CPU 양보다 많은 cpu를 사용하는 것을 막고 싶을 수 있다.</li>
<li>cpu는 압축 가능한 리소스이므로 프로세스에 악영향을 주지 않으면서 컨테이너가 사용하는 cpu 양을 조절할 수 있다.</li>
<li>반면 메모리는 프로세스에 주어지면 프로세스가 메모리를 해제하지 않는 한 다른 프로세스에서 사용할 수 없다. 그래서 컨테이너에 할당되는 메모리의 최대량을 제한해야한다.</li>
<li>메모리를 제한하지 않으면 워커노드에 실행 중인 컨테이너는 사용 가능한 모든 메모리를 사용해서 노드에 있는 다른 모든 파드와 노드에 스케줄링되는 새 파드에 영향을 미칠 수 있다.</li>
<li>쿠버네티스는 모든 컨테이너의 리소스에 제한을 지정할 수 있으며, 리소스 요청을 지정하지 않고 제한만 지정한 경우 자동적으로 요청과 제한이 동일한 값으로 설정된다.</li>
<li>리소스 요청과 달리, 리소스 제한은 노드의 할당 가능한 리소스 양으로 제한하지 않고 오버커밋될 수 있다.(노드 용량 100%를 초과할 수 있다.)</li>
<li>여기서 노드 리소스의 100%가 다 소진되면 특정 컨테이너는 제거가 되어야한다. 쿠버네티스에서는 리소스 제한을 초과해 사용하려고 하거나 초과하는 경우 컨테이너를 제거한다.</li>
</ul>
<h3 id="2-리소스-제한-초과">[2] 리소스 제한 초과</h3>
<ul>
<li>CPU 제한이 설정돼 있으면 프로세스는 설정된 제한보다 많은 cpu 시간을 할당받을 수 없다.</li>
<li>메모리의 경우는 cpu와 달리 제한보다 많은 메모리를 할당받으려 시도하면 프로세스는 강제 종료된다. (컨테이너 OOMKilled)</li>
<li>파드의 재시작 정책(restart policy)이 Always 또는 Onfailure로 설정된 경우 프로세스는 즉시 다시 지작하므로 이를 알아차리지 못할 수도 있다.</li>
<li>메모리 제한 초과가 지속적으로 반복하면 쿠버네티스는 재시작 사이의 지연 시간을 증가시키면서 재시작한다. 이런 재시작의 경우 CrashLoopBackOff STATUS를 갖는다.</li>
</ul>
<h3 id="3-crashlookbackoff">[3] CrashLookBackOff</h3>
<ul>
<li>이 상태는 크래시 후 kubelet이 컨테이너를 다시 시작하기 전에 간격을 늘리는 상태를 의미한다.</li>
<li>첫번째 크래시 후에 kubelet은 컨테이너를 즉시 다시 시작하고, 다시 크래시가 발생하면 10초를 기다리고, 그이후에는 20초 40초 80초 지수 단위로 증가하다가 마지막 300초로 제한한다.</li>
<li>크래시된 이유는 로그를 보면되는데 대표적으로 OOMKilled가 있다.</li>
<li>컨테이너가 종료되는 것을 원하지 않는답면 메모리 제한을 너무 낮게 설정하지 않는 것이 중요하다. 하지만 컨테이너가 제한을 넘어서지 않아도 OOMKilled되는 경우도 있다.</li>
</ul>
<h3 id="4-컨테이너의-애플리케이션이-제한을-바라보는-방법">[4] 컨테이너의 애플리케이션이 제한을 바라보는 방법</h3>
<h4 id="컨테이너는-항상-컨테이너-메모리가-아닌-노드의-메모리를-바라본다">컨테이너는 항상 컨테이너 메모리가 아닌 노드의 메모리를 바라본다.</h4>
<ul>
<li>컨테이너 내부에서 top 명령을 수행해보면 컨테이너가 실행중인 전체 노드의 메모리양을 표시한다. (컨테이너 레벨에서 인식할 수 없다.)</li>
<li>JVM은 컨테이너에 사용 가능한 메모리 대신 호스트(노드)의 총 메모리를 기준으로 힙 크기를 설정하여 컨테이너 레벨에서 OOMKilled 위험이 있다. (최대 힙 크기를 지정하지 않은 경우 JVM GC가 돌기 전에 메모리 제한을 넘어서 OOMKilled 될 수 있음)
컨테이너는 노드의 모든 CPU 코어를 본다.</li>
</ul>
<h4 id="메모리와-마찬가지로-컨테이너에-설정된-cpu-제한과-관계없이-노드의-모든-cpu를-본다">메모리와 마찬가지로 컨테이너에 설정된 CPU 제한과 관계없이 노드의 모든 CPU를 본다.</h4>
<ul>
<li>CPU 제한이 하는 일은 컨테이너가 사용할 수 있는 CPU 시간의 양을 제한하는것 뿐이다.</li>
<li>CPU 제한이 1코어로 설정되더라도 컨테이너의 프로세스는 한 개 코어에서만 실행되는 것이 아니다.
쿠버네티스 클러스터에서 애플리케이션을 개발할때 주의할 점은 시스템의 CPU 코어수를 기준으로 작업 스레드를 결정하는 경우에 문제가 될 수 있다.</li>
</ul>
<h2 id="4-네임스페이스별-파드에-대한-기본-요청과-제한-설정">4. 네임스페이스별 파드에 대한 기본 요청과 제한 설정</h2>
<blockquote>
<p>컨테이너 리소스 요청과 제한을 설정하지 않으면 컨테이너는 이를 설정한 다른 컨테이너에 의해 좌지우지된다. 그래서 모든 컨테이에 리소스 요청과 제한을 설정하는 것이 좋다.</p>
</blockquote>
<h3 id="1-limitrange-리소스">[1] LimitRange 리소스</h3>
<ul>
<li>모든 컨테이너에 리소스 요청과 제한을 설정하는 대신 LimitRage 리소스를 생성해 이를 수행할 수 있다.</li>
<li>컨테이너의 각 리소스(네임스페이스별) 최소/최대 제한을 지정할 수 있고, 리소스 요청을 명시적으로 지정하지 않은 컨테이너의 기본 리소스 요청을 지정할 수 있다.</li>
</ul>
<h3 id="2-limitrange-오브젝트">[2] LimitRange 오브젝트</h3>
<ul>
<li>컨테이너 레벨에서 보면 최소값, 최대값 뿐만 아니라 리소스 요청과 제한을 명시적으로 지정하지 않은 각 컨테이너에 적용될 기본 요청(defaultRequest)와 기본 제한(default)을 설정할 수 있다.</li>
<li>또한 maxLimitREquestRatio을 통해 제한 대 요청 비율을 설정할 수 있다.(cpu 4의 의미는 cpu limit은 cpu request보다 4배 이상 큰 값이 될 수 없다.)</li>
<li>단일 PVC에서 요청 가능한 스토리지 양을 제한 할수도 있다.</li>
<li>하나의 LimitRange 리소스로 관리할수도 있고, 각 type별로 쪼개서 관리할수도 있다.</li>
<li>다른 쿠버네티스 매커니즘과 마찬가지로 나중에 수정된 사항은 기존 파드 및 PVC 등에 영향을 미치지 않는다.</li>
</ul>
<h3 id="3-resourcequota-리소스">[3] ResourceQuota 리소스</h3>
<ul>
<li>리소트쿼터는 LimitRange와 마찬가지로 네임스페이스 기준으로 동작한다.</li>
<li>파드가 사용할 수 있는 컴퓨팅 리소스 양과 퍼시스턴트볼륨클레임이 사용할 수 있는 스토리지 양 등을 제한할 수 있다.</li>
<li>네임스페이스 내에서 모든 파드들의 할 수 있는 요청과 제한의 최대값을 지정한다.</li>
<li>각 개별 파드나 컨테이너에 개별적으로 적용하지 않고 모든 파드의 리소스 요청과 제한의 총합에 적용된다.</li>
<li>리소스쿼터를 생성할 때 주의할 점은 LimitRange 오브젝트와 함꼐 생성해야 한다는 것이다.</li>
<li>LimitRange에 default가 없는 경우, 리소스 요청이나 제한도 명시하지 않은 파드는 아예 생성할 수 없게 된다.</li>
<li>또한, 퍼시스턴트 스토리지에 관한 쿼터 지정할 수도 있다.   </li>
</ul>
<blockquote>
<p>쿠버네티스 클러스터를 최대한 활용하기 위해 리소스 요청과 제한을 적절하게 설정하는 것은 매우 중요한 일이다. 하지만 적정 값을 찾으려면 모니터링이 필요하다. 예상되는 부하 수준에서 컨테이너의 실제 리소스 사용량을 모니터링하고, 필요한 경우 리소스 요청과 제한을 조정해야 한다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Kubernetes] Disk Storage]]></title>
            <link>https://velog.io/@youngmin-mo/Kubernetes-Disk-Storage</link>
            <guid>https://velog.io/@youngmin-mo/Kubernetes-Disk-Storage</guid>
            <pubDate>Thu, 29 Dec 2022 06:43:30 GMT</pubDate>
            <description><![CDATA[<h2 id="1-volume이-나온-배경">1. Volume이 나온 배경</h2>
<ul>
<li>파드는 내부에 프로세스가 실행되고 CPU, RAM, 네트워크 인터페이스 등의 리소스를 공유한다. 하지만 디스크는 공유되지 않는다. 파드 내부의 각 컨테이너는 고유하게 분리된 파일시스템을 가지기 때문이다.</li>
<li>새로 시작한 컨테이너는 이전에 실행했던 컨테이너에 쓰여진 파일 시스템의 어떤 것도 볼 수없다. 전체 파일 시스템이 유지될 필요는 없지만 실제 데이터를 가진 데렉터리를 보존하고 싶을 수도 있다.</li>
<li>이를 위해 쿠버테티스는 스토리지 볼륨 기능을 제공한다. 볼륨은 파드와 같은 최상위 리소스는 아니지만 파드의 일부분으로 정의되며 파드와 일반적으로는 동일한 라이프 사이클을 가진다.</li>
</ul>
<h2 id="2-volume이란">2. Volume이란</h2>
<ul>
<li>쿠버네티스 볼륨은 파드의 구성 요소로 컨테이너와 동일하게 파드 스펙에서 정의된다.</li>
<li>볼륨은 독립적인 쿠버네티스 오브젝트가 아니므로 자체적으로 생성, 삭제될 수 없다.</li>
<li>접근하려는 컨테이너에서 각각 마운트물리적인 장치(디스크)를 특정한 위치(디렉토리)에 연결시켜 주는 과정되어야 한다.</li>
<li>예를 들어, 볼륨 2개를 파드에 추가하고, 3개의 컨테이너 내부에 적절한 경로에 마운트했을 때 아래 사진과 같아진다. 이때, 마운트되지 않은 볼륨이 같은 파드안에 있더라도 접근할 수 없고, 접근하려면 volumeMount를 컨테이너 스펙에 추가해야한다.
<img src="https://velog.velcdn.com/images/youngmin-mo/post/9b142766-5a81-4519-aecc-b7a3434e3a31/image.png" alt=""></li>
</ul>
<h2 id="3-사용-가능한-volume">3. 사용 가능한 Volume</h2>
<ul>
<li>emptyDir : 일시적인 데이터를 저장하는데 사용하는 간단한 빈 디렉토리</li>
<li>gitRepo : 깃 레포지토리의 컨텐츠를 체크아웃해 초기화한 볼륨</li>
<li>hostPath : 워커 노드의 파일시스템을 파드의 디렉토리로 마운트</li>
<li>ntf : NFS 공유를 파드에 마운트</li>
<li>gcePersistentDisk, awsElasticBlockStore, azureDisk 등 : 클라우드 제공자의 전용 스토리지 마운트</li>
<li>cinder, cephfs, iscsi, flocker, glusterfs, quobyte, rdb, flexVolume, vsphereVolume, photonPersistentDisk, scaleIO : 다른 유형의 네트워크 스토리지 마운트</li>
<li>configMap, secret, downwardAPI : 쿠버네티스 리소스나 클러스터 정보를 파드에 노출하는 데 사용되는 특별한 유형의 볼륨</li>
<li>persistentVolumeClaim : 사전 혹은 동적으로 프로비저닝된 퍼시스턴트 스토리지를 사용하는 방법</li>
</ul>
<h3 id="1-emptydir">[1] EmptyDir</h3>
<ul>
<li>빈 디렉토리로 시작되며, 볼륨의 라이프사이클이 파드에 묶여 있으므로 파드가 삭제되면 볼륨의 콘텐츠도 사라진다.</li>
<li>컨테이너에서 가용한 메모리에 넣기에 큰 데이터 셋의 정렬 작업을 수행하는 것과 같은 임시 데이터를 디스크에 쓰는 목적인 경우 사용할 수 있다.</li>
</ul>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
  name: fortune
spec:
  containers:
  - image: luksa/fortune
    name: html-generator                          # 첫번째 컨테이너 html-generator
    volumeMounts:                                     # html이라는 이름의 볼륨을 컨테이너 /var/htdocs에 마운트
    - name: html
      mountPath: /var/htdocs
  - image: nginx:alpine
    name: web-server                               # 두번째 컨테이너 web-server
    volumeMounts:                                    # html이라는 이름의 볼륨을 컨테이너 /usr/share/nginx/html에 마운트
    - name: html
      mountPath: /usr/share/nginx/html
      readOnly: true
    ports:
    - containerPort: 80
      protocol: TCP
  volumes:                                                 # html이라는 단일 emptyDir 볼륨을 위의 컨테이너 2개에 마운트하기 위한 정의
  - name: html
    emptyDir: {}</code></pre>
<h3 id="2-gitrepo">[2] GitRepo</h3>
<ul>
<li>gitRepo 볼륨은 emptyDir base이고, 파드가 시작되면 깃 레포를 복제하여 데이터를 채운다.</li>
<li>볼륨이 생성된 후에 참조하는 레포지터리와 동기화되지 않는다. 파드가 삭제되고 새 파드가 생성되면 그 파드는 최신 커밋을 포함하게 된다.</li>
<li>파드가 삭제되면 볼륨과 콘텐츠는 모두 삭제된다.</li>
</ul>
<h3 id="3-host-path">[3] Host Path</h3>
<ul>
<li>hostpath 볼륨은 노드 파일 시스템의 특정 파일이나 디렉토리를 가리킨다. (persistent storage)</li>
<li>gitrepo, emptyDir 볼륨의 콘텐츠는 파드가 종료되면 삭제되지만, hostpath 볼륨의 콘텐츠는 삭제되지 않는다.</li>
<li>이전 파드와 동일한 노드에서 새롭게 스케줄링되는 새로운 파드는 이전 파드가 남긴 모든 항목을 볼 수 있다.</li>
<li>노드의 로그 파일이나 kubeconfig(쿠버네티스 구성 파일), CA 인증서를 접근하기 위한 데이터들을 hostpath로 구성되어있다.</li>
</ul>
<h3 id="4-gce-persistentdisk">[4] GCE PersistentDisk</h3>
<ul>
<li>파드에서 실행중인 애플리케이션이 디스크에 데이터를 유지해야 하고 파드가 다른 노드로 재스케줄링된 경우에도 동일한 데이터를 사용해야 하는 경우에 필요하다.</li>
</ul>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
  name: mongodb 
spec:
  volumes:
  - name: mongodb-data
    gcePersistentDisk: # 볼륨의 유형은 GCE 퍼시스턴트 디스크 이외에도 awsElasticBlockStore, azureFile, azureDisk, NFS 등 사용
      pdName: mongodb            
      fsType: ext4                         # 리눅스 파일시스템 유형
  containers:
  - image: mongo
    name: mongodb
    volumeMounts:
    - name: mongodb-data
      mountPath: /data/db           # 컨테이너 내 마운트 되는 path
    ports:
    - containerPort: 27017
      protocol: TCP</code></pre>
<ul>
<li>쿠버네티스에 앱을 배포하는 개발자는 어떤 종류의 스토리지 기술이 사용되는지 알 필요 없어야 하고, 동일한 방식으로 파드를 실행하기 위해 어떤 유형의 물리 서버가 사용되는지 알 필요 없어야한다. 즉, 파드의 볼륨이 실제 인프라스트럭처를 참조한다는 것은 쿠버네티스가 추구하는 방식이 아니다.</li>
</ul>
<h3 id="5-pvpersistent-volume--pvcpersistent-volume-claim">[5] PV(Persistent Volume) &amp;&amp; PVC(Persistent Volume Claim)</h3>
<ul>
<li>인프라스트럭처의 세부 사항을 처리하지 않고 앱이 스토리지를 요청할 수 있도록 하기 위한 리소스 유형</li>
<li>관리자는 네트워크 스토리지 유형을 정하고, PV 디스크립터를 게시하여 PV를 생성한다.</li>
<li>사용자는 PVC를 생성하면, 쿠버네티스가 적당한 크기와 접근모드의 PV를 찾아서 PVC를 PV에 바인딩시킨다.
<img src="https://velog.velcdn.com/images/youngmin-mo/post/9391c9d6-f7cf-4488-b6ee-a819c4df7c2a/image.png" alt=""></li>
</ul>
<pre><code class="language-yaml">apiVersion: v1
kind: PersistentVolume
metadata:
  name: mongodb-pv
spec:
  capacity: 
    storage: 1Gi
  accessModes:
    - ReadWriteOnce                                       # 단일 클라이언트의 읽기/쓰기용으로 마운트
    - ReadOnlyMany                                        # 여러 클라이언트의 읽기 전용으로 마운트
  persistentVolumeReclaimPolicy: Retain    # 클레임이 해제된 후 퍼시스턴트볼륨을 유지한다. (이 외에도 delete, recycle)
  hostPath:                                                     # ohstPath 볼륨 (minikube)
    path: /tmp/mongodb

# by admin</code></pre>
<pre><code class="language-yaml">apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mongodb-pvc 
spec:
  resources:
    requests:                         # 1GiB의 스토리지를 요청
      storage: 1Gi 
  accessModes:
  - ReadWriteOnce               # 단일 클라이언트를 지원하는 읽기/쓰기 스토리지
  storageClassName: &quot;&quot;

# by user 1 step</code></pre>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
  name: mongodb 
spec:
  containers:
  - image: mongo
    name: mongodb
    volumeMounts:
    - name: mongodb-data
      mountPath: /data/db
    ports:
    - containerPort: 27017
      protocol: TCP
  volumes:
  - name: mongodb-data                # 파드 볼륨에서 이름으로 퍼시스턴트볼륨클레임을 참조
    persistentVolumeClaim:
      claimName: mongodb-pvc

# by user 2 step</code></pre>
<p><strong>PVC의 장점</strong></p>
<ul>
<li>개발자가 직접 인프라스트럭처에서 스토리지를 가져오는 방식보다는 PV + PVC를 통해 직간접적으로 가져오는 방식이 더 간단하다. (개발자가 인프라스트럭처 상세 내용을 몰라도 됨)</li>
<li>동일한 파드와 PVC 메니페스트는 인프라스트럭처와는 관련된 어떤 것도 참조하지 않으므로 (PV만 참조), 다른 쿠버네티스 클러스터에서도 그대로 사용할 수 있다.</li>
<li>PVC는 x만큼의 스토리지가 필요하고, 한 번에 하나의 클라이언트에서 읽기와 쓰기만 할 수 있어라고만 명시하면된다.</li>
</ul>
<p><strong>PV의 재사용</strong></p>
<ul>
<li>persistentVolumeClaimPolicy를 Retain으로 설정하면 PVC가 해제되더라도 데이터가 남아있으면 상태가 Avaliable로 풀리지 않는다.</li>
<li>다른 리클레임 정책인 Recycle과 Delete가 있는데 Recycle은 볼륨의 콘텐츠를 삭제하고 다시 클레임될수 있도록 만드는 옵션이다.</li>
<li>Delete 정책은 쿠버네티스에서 퍼시스턴트볼륨 오브젝트와 외부 인프라(예: AWS EBS, GCE PD, Azure Disk 또는 Cinder 볼륨)의 관련 스토리지 자산을 모두 삭제한다.</li>
<li>Recycle과 Delete의 차이는 pvc가 삭제될때 pv까지 삭제하느냐 안하느냐에 대한 차이가 있음(Delete는 pvc를 삭제하면 pv까지 삭제함)</li>
</ul>
<h3 id="6-pv의-동적-프로비저닝">[6] PV의 동적 프로비저닝</h3>
<ul>
<li>클레임을 생성하면 fast(사용자 정의) 스토리지 클래스 리소스에 참조된 프로비저너가 PV를 생성한다.</li>
<li>PVC에서 존재하지 않는 스토리지 클래스를 참조하면 PV 프로비저닝은 실패한다.</li>
<li>이렇게 생성된 PV는 리클레임 정책 delete를 가지며, PVC가 삭제되면 PV도 삭제된다.</li>
<li>스토리지 클래스의 장점은 클레임이 클래스 이름으로 참조한다는 사실이다. 그래서 다른 클러스터에서 스토리지 클래스 이름을 동일하게 사용한다면 PVC정의를 다른 클러스터로 이식도 가능하다.</li>
<li>storageClassName 속성을 빈 문자열로 지정하지 않으면 미리 프로비저닝된 퍼시스턴트볼륨이 있다고 할지라도 동적 볼륨 프로비저너는 새로운 퍼시스턴트볼륨을 프로비저닝한다.</li>
<li>미리 프로비저닝된 PV에 바인딩하기 위해서는 (미리 만들어둔 PV에 바인딩하려면) 명시적으로 storageClassName을 &quot;&quot;로 지정해야한다.
즉, 빈 문자열을 스토리지 클래스 이름으로 지정하면 PVC가 새로운 PV를 동적 프로비저닝하지 않고 미리 프로비저닝된 PV에 바인딩된다.</li>
</ul>
<pre><code class="language-yaml">apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mongodb-pvc 
spec:
  storageClassName: fast           # PVC는 사용자 정의 스토리지 클래스를 요청. 만약, 이름을 지정하지 않고(이 항목 존재 x) PVC 생성시 구글 쿠버네티스 엔진에서는 pd-standard 유형의 퍼시스턴트 디스크가 프로비저닝된다.
  resources:
    requests:
      storage: 100Mi
  accessModes:
    - ReadWriteOnce</code></pre>
<p><strong>PV 동적 프로비저닝 플로우</strong></p>
<ul>
<li>클러스터 관리자는 퍼시스턴트볼륨 프로비저너를 설정하고, 하나 이상의 스토리지 클래스를 생성하고 기본값 정의</li>
<li>사용자는 스토리지클래스 중 하나를 참조해 PVC를 생성</li>
<li>PVC는 스토리지클래스와 거기서 참조된 프로비저너를 보고 PVC로 요청된 접근모드, 스토리지 크기, 파라미터를 기반으로 새 PV를 프로비저닝하도록 요청</li>
<li>프로비저너는 스토리지를 프로비저닝하고 PV를 생성한 후 PVC에 바인딩한다.</li>
<li>사용자는 PVC를 이름으로 참조하는 볼륨과 파드를 생성
<img src="https://velog.velcdn.com/images/youngmin-mo/post/b8ee2d95-1521-4126-9391-1e6ad7adeb7f/image.png" alt=""></li>
</ul>
<h2 id="4-config-map--secret">4. Config Map &amp; Secret</h2>
<ul>
<li>빌드된 애플리케이션 자체에 포함되지 말아야 하는 설정[배포된 인스턴스별로(dev, stage, real) 다른 셋팅, 외부 시스템 엑세스를 위한 자격증명 등]이 필요하다. 쿠버네티스는 이런 앱을 실행할 때 설정 옵션을 전달할 수 있는 방법을 제공한다.</li>
<li>설정 데이터를 최상위 레벨의 쿠버네티스 오브젝트에 저장하고, 이를 기타 다른 리소스 정의와 마찬가지로 깃 저장소 혹은 파일 스토리지에 저장할 수 있다.</li>
<li>대부분의 설정 옵션에서는 민감한 정보가 포함돼있지 않지만 자격증명, 암호화키, 보안을 유지해야 하는 유사 데이터들도 있다. 이를 위한 시크릿이라는 또다른 유형의 오브젝트도 제공한다.</li>
<li>애플리케이션에 설정을 전달하는 방법에는 명령줄 인자로 전달하거나, 환경변수 설정(.yaml)으로 전달할 수 있다.</li>
</ul>
<p><strong>하드코딩된 환경변수의 단점</strong></p>
<ul>
<li>하드코딩된 값을 가져오는게 효율적일 수 있지만, 프로덕션과 개발환경의 파드를 별도로 정의해야할 수 있다.</li>
<li>즉, 여러 환경에서 동일한 파드 정의를 재사용하려면 파드 정의에서 &#39;설정&#39;을 분리하는 것이 좋다.</li>
</ul>
<h3 id="2-컨피그맵으로-설정-분리">[2] 컨피그맵으로 설정 분리</h3>
<ul>
<li>환경에(local, alpha, real …) 따라 다르거나 자주 변경되는 설정 옵션을 애플리케이션 소스 코드와 별도로 유지하는 것</li>
<li>쿠버네티스에서는 설정 옵션을 컨피그맵이라 부르는 별도 오브젝트로 분리할 수 있다.</li>
<li>짧은 문자열에서 전체 설정 파일에 이르는 값을 가지는 키/값 쌍으로 구성된 맵이다.</li>
<li>파드는 컨피그맵 이름을 참조하여, 모든 환경에서 동일한 파드 정의를 사용하되 각 환경에서 서로 다른 설정을 사용할 수 있다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/38c7aa83-4318-477c-b2e0-06f1c0e72f53/image.png" alt=""></p>
<h2 id="5-statefulset">5. Statefulset</h2>
<h3 id="1-각각의-파드가-다른-pv와-맵핑하고-싶다면-어떻게-해야될까">[1] 각각의 파드가 다른 PV와 맵핑하고 싶다면 어떻게 해야될까?</h3>
<ul>
<li>하나의 Replicaset에 묶인 각각의 파드는 host 이름, ip가 다른 stateless하게 구성된다. 이때 각각의 파드는 모두 같은 PVC에 맵핑되며 하나의 PV를 공유하게 된다. 이때, 각각의 파드는 어떻게하면 PV를 다르게 맵핑할 수 있을까?</li>
<li>하나의 replicaset이 아닌 여러개의 Replicaset으로 구성하는 방식이 있다. 이는 별도의 PV를 맵핑할 수 있지만 의도한 Replicaset 수를 변경할 수 없으며 사실 Replicaset을 사용한 이유가 없다.</li>
<li>모든 파드가 동일 PV를 사용하지만 볼륨 내부에서 파일 디렉토리를 분리하는 방법도 있다. 인스턴스 생성 시점에 다른 인스턴스가 사용하지 않는 디렉토리를 선정해야하는데, 인스턴스간 조정이 필요하고 올바르게 수행되지 않을 수도 있다.(동기화 이슈/ 병목현상)</li>
</ul>
<h3 id="2-replicaset-vs-statefulset">[2] Replicaset vs Statefulset</h3>
<h4 id="replicaset">Replicaset</h4>
<ul>
<li>Stateless한 애플리케이션의 인스턴스는 이름을 정확히 알고 있을 필요가 없고 몇개가 있는지만 중요하다. 언제든 완전히 새로운 파드로 교체되어도 된다.</li>
</ul>
<h4 id="statefulset">Statefulset</h4>
<ul>
<li>각 인스턴스에 이름을 부여하고 개별적으로 관리한다.</li>
<li>Stateful한 파드가 종료되면 새로운 파드 인스턴스는 교체되는 파드와 동일한 이름, 네트워크 아이덴티티, 상태 그대로 다른 노드에서 되살아나야 한다. 또한, 각 파드는 다른 파드와 구별되는 자체 볼륨셋을 가진다.(PV)</li>
<li>새로운 파드 교체시 완전히 무작위가 아닌 예측가능한 아이덴티티를 가진다.</li>
</ul>
<h3 id="3-replicaset이란">[3] Replicaset이란?</h3>
<blockquote>
<p>statefulset은 애플리케이션의 인스턴스가 각각 안정적인 이름과 상태를 가지며 개별적으로 취급해야하는 애플리케이션에 알맞게 만들어졌다.</p>
</blockquote>
<ul>
<li>스테이트풀셋으로 생성된 파드는 서수인덱스(0부터시작해서 N-1까지 할당)가 할당되고 파드 이름, 호스트 이름, ip, 안정적인 스토리지를 붙인다. 즉, 각각의 파드가 누구인지 알며(인덱스 덕분) 스케이 업/다운시 예측 가능한 파드가 제거된다.</li>
<li>스테이트풀셋은 레플리카셋이 하는 것과 비슷하게 새로운 인스턴스로 교체되도록 한다. 하지만 교체되더라도 이전에 사라진 파드와 동일한 호스트 이름을 갖는다. 그렇기 떄문에 파드가 다른 노드로 재스케줄링되더라도 같은 클러스터 내에서 이전과 동일한 호스트 이름으로 접근이 가능하다.</li>
<li>스테이트풀셋의 스케일 다운의 좋은 점은 항상 어떤 파드가 제거될지 알수 있다는 점이다. 이때 스테이트풀셋의 스케일 다운은 항상 가장 높은 서수 인덱스의 파드를 먼저 제거한다.</li>
<li>Statefulset 파드들은 각각 별도의 PVC/PV가 맵핑되며 스토리지는 영구적으로 저장된다. 이는 DB와 같은 구조에서 사용될 수 있다. 스케일 다운시 파드는 삭제되지만 PVC 클레임은 남겨둔다. 클레임이 삭제된 후 바인딩됐던 PV를 재활용되거나 콘텐츠를 쉽게 복구할 수 있기 때문이다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Kubernetes] 무중단 업데이트 방법]]></title>
            <link>https://velog.io/@youngmin-mo/Kubernetes-%EB%AC%B4%EC%A4%91%EB%8B%A8-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@youngmin-mo/Kubernetes-%EB%AC%B4%EC%A4%91%EB%8B%A8-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Thu, 29 Dec 2022 06:43:18 GMT</pubDate>
            <description><![CDATA[<h2 id="1-application-update">1. Application Update</h2>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/78995465-7f10-4cee-90be-08b701cff5f4/image.png" alt=""></p>
<ul>
<li>클라이언트가 파드에 서비스를 통해 접근하며, 파드들은 ReplicaSet에 의해 관리된다.</li>
<li>파드에서 실행중인 애플리케이션의 버전을 바꾸고 싶을 때 기존 파드를 모두 삭제한 다음 새 파드를 시작하는 방식이 있다. 이는 간단해보이지만, 잠시동안 애플리케이션이 중단되는 문제가 있다.</li>
<li>혹은 새로운 파드를 모두 추가한 다음 한꺼번에 기존 파드를 삭제하는 방식도 있다. 리소스가 2배 더 소요되는 문제가 있다.</li>
<li>순차적으로 새로운 파드를 추가하고 기존 파드를 점진적으로 제거하는 방식도 있다. 이는 동시에 두가지 버전을 실행하기 때문에, 새로운 버전이 이전 버전을 손상시키는 케이스에선 사용하면 안된다.</li>
</ul>
<h2 id="2-업데이트-종류">2. 업데이트 종류</h2>
<h3 id="1-오래된-파드를-삭제하고-새-파드로-교체하는-수동-방식">[1] 오래된 파드를 삭제하고 새 파드로 교체하는 &#39;수동&#39; 방식</h3>
<ul>
<li>v1 pod set을 관리하는 &#39;Replication Controller&#39;가 있는 경우, v2를 참조하도록 pod template을 수정한 다음 이전 파드 인스턴스를(v1) 삭제해 쉽게 교체할 수 있다. 자연스럽게 RC는 v2를 참조하도록 변경했기 때문에 v2의 파드들을 생성한다.</li>
<li>이 방법은 이전 pod 가 삭제되고 새 pod 가 시작되는 동안 짧은 시간의 downtime 을 허용할 수 있어야 한다.
<img src="https://velog.velcdn.com/images/youngmin-mo/post/3ec2cb97-f88a-45ed-9619-d09eaea392c9/image.png" alt=""></li>
</ul>
<h3 id="2-blue-green-배포">[2] Blue Green 배포</h3>
<ul>
<li>새 버전을 실행하는 파드를 불러오는 동안 서비스는 파드의 이전 버전에 연결된다. 새 파드가 모두 실행되면 서비스의 레이블 셀렉터를 변경하고, 서비스를 새 파드로 전화한다. 즉, 한 번에 이전 버전에서 새 버전으로 전환하는 방식이다.</li>
<li>downtime 이 발생하지 않고 한번에 여러 version 의 application 이 실행되는 것을 지원하는 경우에 사용한다. 단, 잠시동안 두 배의 파드가 실행되므로 더 많은 리소스가 필요하다.
<img src="https://velog.velcdn.com/images/youngmin-mo/post/71144bd0-9dd1-440e-b5b0-ff9d74520f34/image.png" alt=""></li>
</ul>
<h3 id="3-rolling-배포">[3] Rolling 배포</h3>
<ul>
<li>이전 파드를 한번에 삭제하는 방법 대신 파드를 단계적으로 교체하는 롤링 업데이트를 수행할 수도 있다. 두 개의 레플리카셋을 이용해서 상태를 보면서 점진적으로 수행한다.</li>
<li>downtime 이 발생하지 않고 한번에 여러 version 의 application 이 실행되는 것을 지원하는 경우에 사용한다.
<img src="https://velog.velcdn.com/images/youngmin-mo/post/f5ae7161-af77-4df0-87a9-9e01bbc6b6a6/image.png" alt=""></li>
</ul>
<blockquote>
<p>쿠버네티스는 무중단 배포를 위해 &#39;Deployment Resource&#39;를 제공한다. (1) Label/ ReplicaSet을 관리할 필요가 없으며, (2)수동이 아닌 자동으로, (3) &#39;명령형&#39;이 &#39;선언형&#39;으로 처리할 수 있다.</p>
</blockquote>
<h2 id="3-deployment-resource">3. Deployment Resource</h2>
<ul>
<li>Deployment는 애플리케이션의 무중단 배포를 지원하는 리소스다.</li>
<li>Deployment를 생성하면 ReplicaSet 리소스가 그 아래 생성된다.</li>
<li>무중단 배포를 위해 추가 ReplicaSet 및 Label을 관리해야하는데, 이를 Deployment가 알아서 처리한다. 우리는 &#39;선언형&#39; 형태로 Deployment 리소스를 정의하기만 하면 된다.
<img src="https://velog.velcdn.com/images/youngmin-mo/post/8ee93d36-d967-4a4f-abd2-f9e5b836c984/image.png" alt=""></li>
</ul>
<h3 id="1-deployment-생성">[1] Deployment 생성</h3>
<ul>
<li>Deployment yaml파일은 레이블 셀렉터, 레플리카 수, 파드 템플릿으로 구성된다. 이때 업데이트 방식도 정의할 수 있다.</li>
<li>디플로이먼트는 파드 템플릿의 각 버전마다 하나씩 여러 개의 레플리카셋을 만든다.</li>
<li>파드 템플릿의 해시 값을 사용하면 디플로이먼트에서 지정된 버전의 파드 템플릿에 관해 항상 동일한 레플리카셋을 사용할 수 있다. 업데이트를 수행하더라도 ReplicaSet은 여전히 남아있다. 따라서 롤백시 RS를 재활용할 수 있다. RS에 revision 번호(버전)를 지정해 특정 버전으로 쉽게 롤백할 수 있다.</li>
</ul>
<h3 id="2-deployment-업데이트">[2] Deployment 업데이트</h3>
<ul>
<li>디플로이먼트 리소스에 정의된 파드 템플릿을 수정하기만 하면 쿠버네티스가 실제 시스템 상태를 리소스에 정의된 상태로 만드는 데 필요한 모든 단계를 수행한다. ex) rc를 추가로 생성하고 업데이트 등</li>
<li>기본적으로 RollingUpdate 전략을 사용한다. 이 방식은 이전 버전과 새 버전을 동시에 실행할 수 있는 경우에만 사용해야 한다.</li>
<li>대안으로 존재하는 Recreate 전략은 한 번에 기존 모든 파드를 삭제한 뒤 새로운 파드를 만드는 전략이다.</li>
</ul>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubia # 이전에는 RC가 특정 버전의 파드를 관리했기 떄문에 kubia-v1으로 했으나, Deployment 자체가 여러 버전을 실행할 수 있기 때문에 버전을 참조하진 않는다.
spec:
  replicas: 3
  template: # pod template
    metadata:
      name: kubia
      labels:
        app: kubia
    spec:
      containers:
      - image: luksa/kubia:v2
        name: nodejs</code></pre>
<h3 id="3-롤링-업데이트-속도-제어">[3] 롤링 업데이트 속도 제어</h3>
<ul>
<li>롤링 업데이트에서 두 가지 추가 속성을 통해 새 파드를 만들고 기존 파드를 삭제하는 과정에서 속도를 제어할 수 있다. &#39;maxSurge&#39;, &#39;maxUnavailable&#39; 두 가지가 있는데, 이 속성들을 통해 한 번에 몇개의 파드를 교체하지를 결정된다.</li>
<li><strong>maxSurge</strong> : 의도하는 replica 수 보다 얼마나 많은 pod instance 를 허용하는지 결정한다.</li>
<li><strong>maxUnavailable</strong> : update 중 의도하는 replica 수를 기준으로 사용할 수 없는 pod instance 수를 결정한다</li>
</ul>
<p>의도하는 replica 수가 3이고, 이러한 모든 속성이 default(25%) 라면 maxSurge 는 4까지 허용되고, maxUnavailable 은 사용할 수 없는 파드를 허용하지 않는다</p>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/52edeaa1-3ba8-401a-95fb-42c6c7633b56/image.png" alt=""></p>
<p>위 예제에서 maxUnavailable만 1로 변해보면 어떻게 동작할까? maxUnavailable=1로 인해서 2개까지만 가용한 상태면 되기 때문에 결과적으로는 롤링 업데이트가 더 빠르게 수행될수 있다.
<img src="https://velog.velcdn.com/images/youngmin-mo/post/fd07baea-ac43-4e4c-a0f8-c7d689f493a6/image.png" alt=""></p>
<h3 id="4-minreadyseconds-속성">[4] minReadySeconds 속성</h3>
<ul>
<li>minReadySeconds는 파드를 사용 가능한 것으로 취급하기 전에 새로 만든 파드를 준비할 시간을 지정하는 속성이다.</li>
<li>적절하게 구성된 레디니스 프로브와 적절한 minReadySeconds 설정으로 쿠버네티스는 버그가 있는 버전을 배포하지 못하게 할 수 있다.</li>
</ul>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: Deployment
metadata:
  name: kubia
spec:
  replicas: 3
  minReadySeconds: 10           # minReadySeconds를 10초로 설정
  selector:
    matchLabels:
      app: kubia
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0             # 디플로이먼트가 파드를 하나씩만 교체하도록 0으로 설정
    type: RollingUpdate
  template:
    metadata:
      name: kubia
      labels:
        app: kubia
    spec:
      containers:
      - image: luksa/kubia:v3
        name: nodejs
        readinessProbe:               
          periodSeconds: 1           # 매 초마다 레디니스 프로브 수행
          httpGet:
            path: /
            port: 8080</code></pre>
<ul>
<li>사용 가능한 것으로 간주되려면 10초 이상 준비돼 있어야 하기 때문에(초기에 이 값을 매우 늘려, 트래픽을 수용 가능한지 체크하기도 한다.) 해당 파드가 사용 가능할 때까지 새 파드를 만들지 않는다. 여기서 maxUnavailable 속성이 0으로 설정되었기 때문에 원래 파드도 제거되지 않는다.</li>
<li>만약 위 상황에서 minReadySeconds를 짧게 설정했더라면 레디니스 프로브의 첫 번째 호출이 성공한 후 즉시 새 파드가 사용 가능한것으로 간주해버릴 수도 있다.(트래픽, 오류 확인 없이) 그러면 잘못된 버전으로 롤아웃이 일어나기 때문에 이 값을 적절하게 잘 설정해야 한다.</li>
<li>만약 위 상황에서 minReadySeconds를 짧게 설정했더라면 레디니스 프로브의 결과값이 지속해서 실패할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Kubernetes] Pod, Service, Replica]]></title>
            <link>https://velog.io/@youngmin-mo/Kubernetes-Pod-Service-Replica</link>
            <guid>https://velog.io/@youngmin-mo/Kubernetes-Pod-Service-Replica</guid>
            <pubDate>Thu, 29 Dec 2022 06:43:01 GMT</pubDate>
            <description><![CDATA[<h2 id="1-pod-특징">1. Pod 특징</h2>
<blockquote>
<p>쿠버네티스에 존재하는 애플리케이션 실행 단위</p>
</blockquote>
<ul>
<li>모든 컨테이너는 항상 하나의 워커 노드에서 실행되며, 여러 워커 노드에 걸쳐 실행되지는 않는다.</li>
<li>컨테이너는 단일 프로세스를 실행하는 것을 목적으로 설계되었으며, 여러 프로세스를 단일 컨테이너로 묶지 않았기 때문에 컨테이너들을 함께 묶고 하나의 단위로 관리할 수 있는 또 다른 상위 구조가 필요하다. 이게 파드가 필요한 이유다.</li>
<li>파드의 모든 컨테이너는 동일한 IPC 네트워크 네임스페이스로 통신하며, 파일 시스템은 다른 컨테이너와 완전히 분리된다. 물론 &#39;볼륨&#39; 개념을 활용하면 각각의 컨테이너, 파드, 워커 노드 등 디스크 공간을 공유할 수 있다
각 파드는 고유한 IP를 가지며 통신한다.</li>
<li>파드 안에 존재하는 컨테이너들을 동일한 네트워크 네임스페이스에서 실행되기 때문에, 동일한 IP, Port를 공유한다.() 따라서 각각의 컨테이너들이 같은 포트 번호를 사용하지 않도록 해야한다.</li>
<li>한 호스트에 모든 유형의 앱을 넣었던 이전과 달리, 애플리케이션을 여러 파드로 구성하고 각 파드에는 밀접하게 관련있는 구성요소나 프로세스만 포함한다.</li>
<li>애플리케이션마다 사용할 리소스(메모리,cpu 등)가 다양하므로 이를 분리하면, 스케줄링 관점에서 인프라스트록처의 활용도를 향상시킬 수 있다.</li>
<li>쿠버네티스는 개별 컨테이너를 수평 확장할 수 없으며, 파드단위로 수평 확장한다. 따라서, 컨테이너너를 개별적으로 스케일링하고 싶다면, 별도 파드에 배포해야한다.</li>
<li>파드가 삭제되면 해당 로그도 같이 삭제돈다. 파드가 삭제된 후에도 파드의 로그를 보기 위해서는 모든 로그를 중앙 저장소에 저장하는 클러스터 전체의 중앙집중식 로깅을 설정해야한다.</li>
</ul>
<h2 id="2-label">2. Label</h2>
<blockquote>
<p>어플리케이션을 잘게 쪼개면서 수 많은 앱들이 존재하고, 이들을 그룹화하여 관리할 필요성이 생겼다. label, namespace, annotation 등을 활용하여 각각의 리소스를 묶어준다.</p>
</blockquote>
<ul>
<li>MSA의 경우 수백 개 파드가 생길 수 있다. 어떤 파드가 어떤 것인지 쉽게 알 수 있도록 임의의 기준에 따라 작은 그룹으로 조직하는 방법이 필요하다. 예를 들어, 각 파드에 대한 작업을 개별적으로 하기보단, 특정 그룹에 속한 모든 파드에 관해 한 번에 작업하길 원할것이다. 이를 레이블을 통해 파드와 기타 쿠버네티스 오브젝트의 조직화가 이뤄지게한다.</li>
<li>레이블은 파드와 모든 다른 쿠버네티스 리소스를 조직활할 수 있는 단순하면서 강력한 쿠버네티스 기능이다. 레이블은 리소스에 첨부하는 키-값 쌍으로, 이 쌍은 레이블 셀렉터를 사용해 리소스를 선택할 때 활용된다.</li>
</ul>
<h3 id="1-label-selector">[1] Label Selector</h3>
<ul>
<li>레이블 셀렉터는 특정 레이블로 태그된 파드의 부분 집합을 선택해 원하는 작업을 수행한다. &quot;특정한 키를 포함하거나 포함하지 않는 레이블&quot;, &quot;특정한 키와 값을 가진 레이블&quot;, &quot;특정한 키를 갖고 있지만, 다른 값을 가진 레이블&quot;</li>
</ul>
<pre><code class="language-yaml">kubectl get po -l key1=value1, kubectl get po -l &#39;!key1&#39;
kubectl get po -l env in (prod, devel) # env 레이블 값이 prod 또는 devel로 설정돼 있는 파드
kubectl get po -l env notin (prod, devel) # env 레이블 값이 prod, devel이 아닌 파드
kubectl get po -l app=pc 명령어</code></pre>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/4ee83206-64fb-4a01-9bc9-6024cc79f0e7/image.png" alt=""></p>
<h3 id="2-namespace">[2] Namespace</h3>
<ul>
<li>label은 파드와 다른 오브젝트를 그룹으로 묶는데 사용했다. 오브젝트는 여러개의 label을 가질 수 있기 때문에 이 그룹은 서로 &#39;겹칠&#39; 수 있다.</li>
<li>오브젝트를 겹치지 않고, 그룹으로 분할하고자 할 때 namespace를 사용한다. 즉, namespace 를 사용해 리소스를 겹치지 않는 그룹으로 분리할 수 있다.</li>
<li>여러 사용자 또는 그룹으로 분리하면 다른 사용자의 pod를 실수로 삭제하는 것을 방지하며, 리소스 이름에 관한 접근 범위를 제공하기 때문에 리소스 이름이 충돌하는 것을 걱정할 필요가 없다.</li>
</ul>
<h2 id="3-pod를-안정적으로-관리하는-방법">3. Pod를 안정적으로 관리하는 방법</h2>
<ul>
<li>컨테이너 주 프로세스에 크래시가 발생하면 kublet은 컨테이너를 재시작한다.</li>
<li>쿠버네티스는 probe를 통해 컨테이너가 살아있는지 3가지 매커니즘으로 확인할 수 있다. (1) 지정한 ip, port로 HTTP GET을 보내어 2xx, 3xx인지 확인한다. 이때, 응답 오류 코드이면 실패로 간주한다. (2) TCP 소켓 probe는 컨테이너의 지정된 포트에 TCP 연결을 시도한다. 연결되면 성공이다. (3) Exec는 명령의 종료 상태 코드가 0인지 확인한다.</li>
<li>아래는 probe 설정 yaml 파일 예시로, probe가 요청할 url/port를 명시하고, 초기 몇초간 probe가 확인을 안하도록 할 수 있다. 이런 설정을 해두지 않으면, 앱이 실행되자마자 restart가 1인 경우가 발생할 수 있다.</li>
</ul>
<pre><code class="language-yaml">livenessProbe:
     httpGet:
       path: /    # probe 가 요청해야 하는 경로
       port: 8080 # probe 가 연결해야 하는 포트
     initialDelaySeconds: 15</code></pre>
<ul>
<li>서버가 단순히 응답하냐 아니냐(200, 4xx, 5xx) 뿐만 아니라, 여기에 health check와 같이 내부 구성요소들이 살아 있는지 확인하는 API를 연동하면 더욱 효율적으로 컨테이너가 정상인지 확인할 수 있다. 특정 ACL이 뚤려있는지, 혹은 외부 커넥션을 맺을 수 있는지(보안체크) 등을 검사하는 예시가 있다.</li>
<li>결국 쿠버네티스는 probe를 통해 컨테이너를 재시작한다. 이 작업을 파드를 호스팅하는 노드의 kublet에서 수행한다. 그러나 노드 자체에 크래시가 발생한 경우 파드를 재시작해야 하는 것은 컨트롤러의 몫이다.</li>
</ul>
<h2 id="4-replication-controller">4. Replication Controller</h2>
<ul>
<li>RC는 쿠버네티스의 리소스로 항상 파드가 실행되도록 보장한다. 노드가 사라지거나 노드에서 파드가 제거된 경우 RS는 사라진 파드를 감지하고 스케줄링에 의해 괜찮은 노드로 교체하여 파드를 생성한다.</li>
<li>실행중인 파드 목록을 지속적으로 모니터링하고, 특정 타입의 실제 파드 수가 의도하는(명시한) 수와 일치하는지 항상 확인한다. 만약 더 많이 실행된다면 초과본을 제거한다. 문제가 있다면 의도한 수의 파드로 맞추기 위해 파드를 재생성한다.</li>
<li>(1) Label Selector : 컨트롤러의 범위 안에 있는 파드를 결정한다. / (2) Replica Count : 실행할 파드의 수 / (3) Pod Template : 새로운 파드 Replica를 만들 때 사용한다. Replication Controller는 이렇게 3가지로 구성한다.</li>
<li>Label Selector와 Pod Template를 변경해도 기본 파드에 영향을 미치지 않는다. Label Selector를 변경하면 기존 파드가 RC의 범위를 벗어나므로, 컨트롤러가 해당 파드에 대한 관리를 중지한다.</li>
</ul>
<pre><code class="language-yaml">apiVersion: v1
kind: ReplicationController # RC의 manifest 정의
metadata:
  name: kubia
spec:
  replicas: 3
  selector:
    app: kubia # 관리하는 pod 선택
  template: # 새 pod 에 사용할 pod template
    metadata:
      labels:
        app: kubia
    spec:
      containers:
      - name: kubia
        image: luksa/kubia
        ports:
        - containerPort: 8080</code></pre>
<ul>
<li>RS가 파드를 관리하는 대상은 오로지 Label Selector에 달려있다. 이 Label Selector와 일치하는 파드만을 관리한다.</li>
<li>만약 파드의 라벨을 변경해 RS와 일치하지 않는다면 해당 파드는 수동으로 만든 파드와 같다. 파드에 문제가 생기면 수동으로 대응해야한다.</li>
</ul>
<h2 id="5-replica-set">5. Replica Set</h2>
<ul>
<li>Replica Set은 차세대 RC로 동일한 기능의 추가로, Pod Selector의 좀 더 풍부한 표현식을 갖고있다. 즉, 여러 라벨을 하나의 그룹으로 매칭시킬 수 있다. 예를들어, 특정 라벨이 없는 파드, 라벨과 상관없이 특정 키를 갖는 파드 등이있다.</li>
</ul>
<pre><code class="language-yaml">apiVersion: apps/v1beta2
kind: ReplicaSet
metadata:
  name: kubia
spec:
  replicas: 3
  selector:
    matchExpressions:
      - key: app # pod 의 키는 app 이다.
        operator: In # 이외에도 NotIn, Exists, DoesNotExist 등의 표현식이 있다.
        values:
         - kubia
  template:
    metadata:
      labels:
        app: kubia
    spec:
      containers:
      - name: kubia
        image: luksa/kubia</code></pre>
<h2 id="6-demonset">6. DemonSet</h2>
<ul>
<li>RC, RS 모두 cluster 내 어딘가에서 pod 가 실행되길 원하지만, cluster 의 모든 node 에 1개의 pod 가 각각 실행되길 원할 때 daemon set 을 사용할 수 있다. 로그 수집기, 모니터링이 이에 해당하며, kube-proxy 가 해당한다.</li>
<li>target node 가 지정돼 있고, scheduler 를 건너뛰는 것을 제외하면 RC, RS 와 유사하다</li>
<li>복제본 수라는 개념이 없다 대신 node 수에 의존한다. 새 node 가 cluster 에 추가되면 daemon set은 즉시 새 pod instance 를 새 node 에 배포한다.
<img src="https://velog.velcdn.com/images/youngmin-mo/post/45e92401-a2b0-4793-ba9a-001a21c21547/image.png" alt=""></li>
</ul>
<h2 id="7-완료-가능한-단일-task를-수행하는-pod실행">7. 완료 가능한 단일 task를 수행하는 pod실행</h2>
<ul>
<li>지금까지 계속 실행하는 파드에 관해서만 다뤘는데, 하지만 작업이 완료된 후 종료되야 하는 경우도 있다.</li>
<li>이를 위해 쿠버네티스는 job이라는 리소스를 제공하며, 실행중인 프로세스가 성공적으로 완료되면 컨테이너를 다시 시작하지 않는다. 즉, 파드가 완료된 것으로 간주된다.</li>
<li>노드에 장애가 발생한다면 RC, RS와 같이 다른 노드로 스케줄링된다.</li>
<li>프로세스 자체에 장애가 발생한 경우 job에서 컨테이너를 다시 시작할 것인지 설정할 수 있다.</li>
<li>job은 작업이 제대로 완료된지 중요한 임시 작업에 유용하다.</li>
<li>job이 끝나면 일반적인 get 명령어로 조회가 안되어 --show--all 혹은 -a로 확인해야된다.</li>
</ul>
<pre><code class="language-yaml">apiVersion: batch/v1
kind: Job
metadata:
  name: batch-job
spec:
  template: # pod selector 를 지정하지 않는다.
    metadata:
      labels:
        app: batch-job
    spec:
      restartPolicy: OnFailure # job 은 무한정 실행하지 않으므로 기본 정책 사용이 불가능하다 따라서 restartPolicy 는 OnFailure 나 Never 로 설정해야 한다.
      containers:
      - name: main
        image: luksa/batch-job</code></pre>
<ul>
<li>linux, unix OS 에서 cron 작업과 같은 것도 k8s 가 제공한다 이것을 cronJob 이라 한다.</li>
<li>job 이나 pod 가 상대적으로 늦게 생성되고 실행될 수 있다. 예정된 시간을 너무 초과해 시작돼서는 안된다는 엄격한 요구 사항을 갖는 경우도 있다. startingDeadlineSeconds 필드를 이용해 데드라인을 설정할 수 있다.</li>
</ul>
<pre><code class="language-yaml">apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: batch-job-every-fifteen-minutes
spec:
  schedule: &quot;0,15,30,45 * * * *&quot; # 분 시 일 월 요일 
    startingDeadlineSecond: 15 #
  jobTemplate:
    spec:
      template:
        metadata:
          labels:
            app: periodic-batch-job
        spec:
          restartPolicy: OnFailure
          containers:
          - name: main
            image: luksa/batch-job
</code></pre>
<h2 id="8-클라이언트가-파드를-검색하고-통신하는-방법">8. 클라이언트가 파드를 검색하고 통신하는 방법</h2>
<ul>
<li>쿠버네티스는 노드에 파드를 스케줄링한 후 파드가 시작되기 바로 직전에 파드의 ip 주소를 할당하여, 클라이언트에서 특정 파드의 ip 주소를 미리 알 수 없다. 쿠버네티스의 파드는 일시적이고 ip가 언제든지 변경될 수 있다.</li>
<li>쿠버네티스의 서비스는 동일한 서비스를 제공하는 파드 그룹에 지속적인 단일 접점을 만들어주는 리소스다. 뒷단은 로드 밸런싱으로 처리되며, 여러 파드는 label, namespace, annotation 기능을 통해 그룹화한다.</li>
<li>서비스를 만들고, 클러스터 외부에서 엑세스 할 수 있도록 구성하면, 외부 클라이언트가 파드에 연결할 수 있는 하나의 고정 ip가 노출된다. 이때 서비스가 관리하는 파드들의 ip가 변경되더라도, 서비스의 ip 주소는 변경되지 않는다. 이에 따라 내부 클라이언트와 외부 클라이언트 모두 서비스 기능을 통해 파드에(자주변경되는ip) 접근할 수 있다.
<img src="https://velog.velcdn.com/images/youngmin-mo/post/1fc19c74-d29b-4a8d-8423-09be3dab578b/image.png" alt=""></li>
</ul>
<pre><code class="language-yaml">apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  ports:
  - port: 80 # 서비스가 사용할 포트
    targetPort: 8080# 서비스가 포워드할 컨테이너 포트
  selector:
    app: kubia # app=kubia 레이블이 있는 모든 파드가 이 서비스에 포함된다는것을 의미
  sessionAffinity: ClientIP # 동일한 클라이언트 IP의 모든 요청을 동일한 파드로 전달. 없으면 파드 상관없이 로드밸런싱</code></pre>
<h2 id="9-ingress-resource">9. Ingress Resource</h2>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/2af9640e-e580-49b4-bc66-2236f9a14a9f/image.png" alt=""></p>
<ul>
<li>인그레스는 한 IP 주소로 수십 개의 서비스에 접근이 가능하도록 지원한다.</li>
<li>애플리케이션 계층(HTTP)에서 작동하며, 서비스가 할 수 없는 쿠키 기반 세션 어피니티 등과 같은 기능 제공이 가능하다.</li>
</ul>
<pre><code class="language-yaml">apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: kubia
spec:
  rules:
  - host: kubia.example.com # 인그레스는 kubia.example.com 도메인 이름으로 서비스에 매핑된다.
    http:
      paths:
      - path: /
        backend:
          serviceName: kubia-nodeport # 모든 요청은 kubia-nodeport 서비스의 포트 80으로 전달된다.
          servicePort: 80</code></pre>
<h2 id="10-readiness-probe">10. Readiness Probe</h2>
<blockquote>
<p>파드가 연결을 수락할 준비가 됐을 때 신호 보내기</p>
</blockquote>
<ul>
<li>레디니스 프로브는 요청을 처리할 준비가 된 파드의 컨테이너만 요청을 수신하도록 한다.</li>
<li>컨테이너가 시작될 때 쿠버네티스는 첫 번째 레디니스 점검을 수행하기 전에 구성 가능한 시간이 경과하기를 기다릴 수 있도록 구성할 수 있다.</li>
<li>컨테이너가 준비 상태 점검에 실패하더라도 컨테이너가 종료되거나 다시 시작시키지 않는다.</li>
<li>예를 들어, 초기에 파드 그룹이 다른 파드에서 제공하는 서비스에 의존한다고 했을때(웹 앱 -&gt; 데이터베이스) 웹 앱 파드중 하나만 DB에 연결할 수 없는 경우, 재시작이 아니라 요청을 처리할 준비가 되지 않았다고 신호를 주는게 현명할 수 있다. (재시작이 아닌 대기가 맞을 수 있다)</li>
</ul>
<pre><code class="language-yaml">spec:
      containers:
        - name: kubia
          image: sungsu9022/kubia
          ports:
            - name: http
              containerPort: 8080
            readinessProbe: # 파드의 각 컨테이너에 레디니스 프로브를 정의
              exec: # ls /var/ready 명령어를 주기적으로 수행하여 존재하면 0(성공), 그렇지 않으면 다른 값(실패)
                command:
                  - ls
                  - /var/ready</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Kubernetes] Docker란?]]></title>
            <link>https://velog.io/@youngmin-mo/Kubernetes-Docker%EB%9E%80</link>
            <guid>https://velog.io/@youngmin-mo/Kubernetes-Docker%EB%9E%80</guid>
            <pubDate>Thu, 29 Dec 2022 06:42:45 GMT</pubDate>
            <description><![CDATA[<h2 id="1-vmware-vs-container">1. VMware vs Container</h2>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/f8d66248-c691-41ea-b267-e1dc3f2e957e/image.png" alt=""></p>
<h3 id="1-가상화란">[1] 가상화란</h3>
<ul>
<li>가상화란 하나의 물리 공간에서 여러 가상의 공간을 만들어주는 기술을 말한다.</li>
<li>가상화를 관리하는 소프트웨어(주로 Hypervisor)를 사용하여 하나의 물리적 머신에서 가상머신(VM)을 만들어, 하나의 컴퓨터지만 마치 여러 컴퓨터를 실행하듯 사용할 수 있다.</li>
<li>이와 동일한 맥락으로 한정된 메모리 공간에서 여러 프로세스가 독립적 공간을 사용하기 위해 가상 메모리라는 기술을 사용한다. 이는 메모리 뿐만 아니라 디스크 공간을 추가로 사용하여 마치 여러 가상의 공간을 확보하는 것처럼 보인다.</li>
</ul>
<h3 id="2-virtual-machine">[2] Virtual Machine</h3>
<ul>
<li>하나의 물리적 서버가 있고, VM을 가상화 시켜주는 하이퍼바이저(virtual box, vmware etc)에 의해 여러 VM들이 host os 위에 존재한다.</li>
<li>가상화된 VM들은 각각 독립된 실행 환경을 가지며, 데이터는 물론이고 코드도 전혀 공유하지 않는다.</li>
<li>Guest OS도 Host OS와 같이 여러 어플리케이션을 설치하고 포트로 구분하여 각각의 서비스를 만들 수 있다.</li>
<li>하지만 guest os, 커널 중복 등 VM마다 최소 GB 단위의 공간이 필요하며, VM 수에 비례해서 리소스가 늘어난다. 따라서 어플리케이션을 띄우는데 너무 많은 리소스가 필요하며 부팅시간 역시 지연되는 문제가 있다.</li>
</ul>
<h3 id="3-container">[3] Container</h3>
<ul>
<li>컨테이너는 무겁고 느린 Virtual Machine 가상화 방식을 해결하기 나왔다.</li>
<li>커널 하나에 격리된 여러 개의 인스턴스가 포함 될 수 있도록 애플리케이션 수준에서 이루어지는 가상화이며, 이러한 인스턴스를 컨테이너라 부른다.</li>
<li>VM은 OS Level에서 격리하며, Container는 리눅스 격리 기술에 의해 Application Level에서 격리한다.</li>
<li>Container Img는 컨테이너를 만들기 위해 필요한 모든걸 가지고 있는 파일인데, 이미지로 컨테이너를 생성하기 때문의 컨테이너를 이미지의 인스턴스라고도 할 수 있다. 이때 Img는 Immutable해서 롤백에 용이하다.</li>
<li>컨테이너는 모두 동일한 커널을 호출함으로 보안위험이 발생할 수 있다. 격리하려는 프로세스가 적다면 가상머신도 괜찮다. 반면, 동일한 시스템에서 더 많은 수의 격리된 프로세스를 실행하려면 컨테이너의 오버헤드가 낮기 때문에 컨테이너를 선택하는게 괜찮다.</li>
<li>각 가상머신은 자체 시스템 서비스를 실행하지만 컨테이너는 모두 동일한 os에서 실행되므로 컨테이너는 시스템서비스를 실행하지 않는다. 즉, 컨테이너를 실행하려면 가상머신처럼 부팅할(os&amp;kernel level) 필요 없이, 즉시 시작된다.</li>
</ul>
<h3 id="4-container-격리-방식">[4] Container 격리 방식</h3>
<h4 id="linux-namespace">linux namespace</h4>
<ul>
<li>각 프로세스가 시스템(파일, 프로세스, 네트워크 인터페이스, 호스트 이름 등)에 대한 독립된 뷰만 볼 수 있다.</li>
<li>프로세스는 여러 namespace에 속할 수 있고, 동일한 namespace 내에 있는 리소스만 볼 수 있다. 이때, 각 네임스페이스는 특정 리소스 그룹을 격리하느 데 사용된다.</li>
<li>각 컨테이너는 고유한 네트워크, 프로세스 등 인터페이스만 사용하므로 각 컨테이너는 고유한 해당 네임스페이스 세트만 볼 수 있다.</li>
</ul>
<h4 id="linux-control-cgroups">linux control cgroups</h4>
<ul>
<li>프로세스가 사용할 수 있는 리소스 (cpu, 메모리, 네트워크 대역폭 등)를 제한한다.</li>
<li>프로세스는 설정된 양 이상의 CPU, 메모리 등을 사용할 수 없다. 이런 방식으로 다른 프로세스용으로 예약된 리소스를 사용할 수 없다.</li>
</ul>
<h2 id="2-docker">2. Docker</h2>
<blockquote>
<p>원하는 개발 환경을 DockerFile에 저장하면, 도커는 이를 너가 원하는 어떤 머신에서든(local, dev, alpha, stage, real etc) 해당 &#39;환경을 동일하게 셋팅&#39;할 수 있고, 이러한 환경을 독립적으로 복제하여 환경셋팅을 &#39;모듈식&#39;으로 관리가 가능해진다.</p>
</blockquote>
<h3 id="1-docker란">[1] Docker란</h3>
<ul>
<li>컨테이너를 쉽게 실행시켜주는 프로그램</li>
<li>도커만 있으면 도커 허브에 한 번 업로드한 장비들을 내가 지정한 형태로 어디든 설치할 수 있다. 로컬, 알파, 리얼 등 어떤 환경에서도 동일하게 실행이 가능하다. 서버를 구성하고, 개발자마다 환경 셋팅 하는게 여간 귀찮은 작업인데 이를 도커 덕분에 편하게 구성할 수 있다.</li>
<li>각 요소들이 설치된 모습을 &#39;이미지&#39; 형태로 박제해서 자장하는데, 이미지는 공식 제품일 수 있고 &amp;&amp; 우리가 만들 수도 있다. 이렇게 이미지로 저장된 항목들이 함께 연결되서 동작하도록 dockerfile 문서 형태로 표현한다.</li>
<li>우리는 이 문서만 잘 보관하면 언제 어디서든 미리 지정된 서비스에 필요한 설정대로 dockerhub로부터 다운 받아 설치할 수 있다.</li>
<li>장비에 문제가 생겨도 도커허브에서 다시 불러오면되고, 도커에 의해 같은 서버의 다른 컨테이너들과 서로 방해없이 해당 작업만의 특정 공간(가상화)을 할당시켜준다.</li>
</ul>
<h3 id="2-docker-장점">[2] Docker 장점</h3>
<h4 id="environment-disparity">Environment Disparity</h4>
<ul>
<li>docker를 통해 다른 머신에서도 동일한 환경을 구축할 수 있다. 로컬, 개발, 알파, 리얼 등 모두 같은 개발 환경을 구축할 수 있고, 도커엔진만 있으면 &quot;내 컴퓨터에서는 되는데 왜 저기선 안되지?&quot;, &quot;새로운 팀원이 오고 환경 구축의 시간&quot;의 비용을 줄일 수 있다.</li>
<li>DockerFile에 필요한 파일을 ex) ubuntu, java 지정하면, 도커는 이걸 읽고 컴퓨터에 필요한 파일을 컨테이너에 설치함으로 동일한 환경을 구축할 수 있다.</li>
</ul>
<h4 id="independent">Independent</h4>
<ul>
<li>같은 서버에서 각기 다른 환경의 컨테이너를 설치할 수 있다. 예를 들어, 하나의 컴퓨터에 여러 서비스를 돌리고, 서비스마다 자바 버전이 달라도 괜찮다. A 서비스에선 java8을, B 서비스에선 java9를 사용해도 충돌 없이 진행이 가능하다.</li>
</ul>
<h4 id="환경-구축의-편리함">환경 구축의 편리함</h4>
<ul>
<li>서버를 구매하고, 환경셋팅하고, 시작하고 이러한 복잡한 과정을 도커를 통해 새로운 환경을 쉽게 구축할 수 있다. 이러한 특징으로 트래피에 따라 새로운 환경을 쉽게 설치할 수 있어 스케일 업/다운이 용이하다.</li>
</ul>
<h3 id="3-docker-container-layer">[3] Docker Container Layer</h3>
<ul>
<li>도커 기반 컨테이너 이미지와 가상머신 이미지의 큰 차이점은 컨테이너 이미지가 여러 이미지에서 공유되고 재사용될 수 있는 레이어로 구성돼 있다. 즉, 동일한 레이어를 포함하는 다른 컨테이너 이미지를 실행할 때 다른 레이어가 이미 다운로드 된 경우 이미지의 특정 레이어만 다운로드하면 된다.</li>
<li>도커 이미지는 레이어로 구성돼며, 모든 도커 이미지는 다른 이미지 위에서 빌드된다. 이러한 특징으로 네트워크로 이미지를 배포하는 속도가 빨라진다.</li>
<li>각 레이어는 동일 호스트에 한 번만 저장된다. 또한 컨테이너 이미지 레이어가 읽기 전용이기 때문에, 수정시 기존 이미지 위에 최상위 레이어로 작성된다.</li>
<li>이미지는 하나의 큰 바이너리 덩어리가 아니라, 서로 다른 이미지 여러개가 레이어로 구성한다. 다수의 이미지를 생성하더라도 기본 이미지를 구성하는 레이어는 단 한번만 저장되어, 필요한 부분만 pull 받고, push 할 수 있어 이미지 저장/전송에 효과적이다. DockerFile에 명시된 RUN, CMD 등 명령어를 수행할 때마다 레이어를 추가하고 최종적으로 latest 이미지를 저장한다.</li>
</ul>
<h3 id="4-dockerfile">[4] DockerFile</h3>
<blockquote>
<p>DockerFile은 Docker 이미지가 어떤 단계를 거쳐 빌드 되어야하는지를 담고있는 텍스트 파일로, Docker는 DockerFile에 나열된 명령문을 차례로 수행하여 이미지를 생성한다.</p>
</blockquote>
<h4 id="from">FROM</h4>
<ul>
<li>FROM &lt;이미지&gt;:&lt;태그&gt;</li>
<li>base 이미지를 지정할 때 사용한다.</li>
<li>FROM ubuntu:latest</li>
</ul>
<h4 id="workdir">WORKDIR</h4>
<ul>
<li>WORKDIR &lt;이동할 경로&gt;</li>
<li>Shell의 cd 명령문처럼 컨테이너 상에서 작업 디렉터리로 전환을 위해 사용된다. WORKDIR로 작업 디렉토리를 전환하면 그 이후에 등장하는 모든 RUN, CMD, ENTRYPOINT, COPY, ADD 명령문은 해당 디렉토리를 기준으로 실행한다.</li>
<li>WORKDIR /usr/app1</li>
</ul>
<h4 id="run">RUN</h4>
<ul>
<li>RUN [&quot;&lt;커맨드&gt;&quot;, &quot;&lt;파라미터1&gt;&quot;, &quot;&lt;파라미터2&gt;&quot;]</li>
<li>이미지 생성 과정에서 실행할 명령어로, 보통 이미지 안에 특정 소프트웨어를 설치하기 위해 많이 사용된다.</li>
</ul>
<pre><code class="language-yaml">RUN apk add curl #curl 도구설치
RUN npm install --silent # npm 패키지 설치
docker run busybox echo &quot;hello world&quot; #busybox라는 이미지를 다운받고, echod 명령어를(busybox에 포함) 통해 &quot;hello world&quot;를 출력한다. 보통 이렇게 실행 명령어는 이미지를 생성할 때 내부에 넣어 패키징한다.</code></pre>
<h4 id="entrypoint">ENTRYPOINT</h4>
<ul>
<li>ENTRYPOINT [&quot;&lt;커맨드&gt;&quot;, &quot;&lt;파라미터1&gt;&quot;, &quot;&lt;파라미터2&gt;&quot;]</li>
<li>이미지를 컨테이너로 띄울 때 항상 실행되야 하는 커맨드를 지정할 때 사용한다. 이 명령문은 docker img를 마치 하나의 실행 파일처럼 사용할 때 유용하다.</li>
</ul>
<pre><code class="language-yaml">ENTRYPOINT [&quot;npm&quot;, &quot;start&quot;] #npm start 스크립트 실행
ENTRYPOINT [&quot;python&quot;, &quot;manage.py&quot;, &quot;runserver&quot;] #Django 서버 실행</code></pre>
<h4 id="cmd">CMD</h4>
<ul>
<li>CMD [&quot;&lt;커맨드&gt;&quot;,&quot;&lt;파라미터1&gt;&quot;,&quot;&lt;파라미터2&gt;&quot;] or CMD [&quot;&lt;파라미터1&gt;&quot;,&quot;&lt;파라미터2&gt;&quot;]</li>
<li>CMD 명령문은 해당 이미지를 컨테이너로 띄울 때 디폴트로 실행할 커맨드나, ENTRYPOINT 명령문으로 지정된 커맨드에 디플트로 넘길 파라미터를 지정할 떄 사용한다. 예를 들어, ENTRYPOINT로 커맨드를 지정하고, CMD 명령문으로 디폴트 파라미터를 지정하면 매우 유연하게 이미지를 실행할 수 있다.</li>
<li>RUN 명령문은 이미지 빌드시 항상 실행되며, 한 dockerfile에 여러개의 RUN 명령문을 선언할 수 있다. 반면에 CMD 명령문은 이미지를 컨테이너로 띄울 때 딱 한번 실행 기회를 가지게 되며, 이 기회마저도 docker run 커맨드에 인자를 너길 경우 상실하게 된다.</li>
</ul>
<pre><code class="language-yaml">ENTRYPOINT [&quot;node&quot;]
CMD[&quot;index.js&quot;]

docker run test # node index.js 실행
docker run test main.js # node main.js 실행</code></pre>
<h4 id="copy">COPY</h4>
<ul>
<li>호스트 컴퓨터에 있는 디렉토리나 파일을 도커 이미지의 파일 시스템으로 복사하기 위해서 사용된다. 절대 경로와 상대 경로를 모두 지원하며 상대 경로 이용시, WORKDIR 명령문으로 작업 디렉토리를 어디로 전환을 했는지 고려해야한다.</li>
</ul>
<pre><code class="language-yaml">COPY package.json package.json # package.json 파일만 복사
WORKDIR app/
COPY . . # 이미지를 빌드한 디렉토리의 모든 파일을 컨테이너의 app/ 디렉토리로 복사</code></pre>
<h4 id="add">ADD</h4>
<ul>
<li>ADD 명령문은 좀 더 파워풀한 COPY 명령문인데, ADD 명령문은 일반 파일 뿐만 아니라 압출 파일이나 네트워크 상의 파일도 사용할 수 있다. 이렇게 특수한 파일을 다루는게 아니라면 COPY 명령문을 사용하는 것이 권장된다.</li>
</ul>
<h4 id="env-arg-expose">ENV, ARG, Expose</h4>
<ul>
<li>ENV : 환경 변수 설정 / ARG : 빌드시 넘어올 수 있는 인자 설정</li>
<li>Expose : 컨테이너가 리스닝할 포트 및 프로토콜 설정</li>
</ul>
<p>Example</p>
<pre><code class="language-yaml"># 이미지를 dockerhub에서 가져옴.
FROM python:3.8.5

# 이미지 생성 과정에서 실행할 명령어
RUN pip3 install flask flask-cors flask-mysql
# 이미지 내에서 명령어를 실행할(현 위치로 잡을) 디렉토리 설정
WORKDIR /usr/src/app
# 컨테이너 실행시 실행할 명령어
CMD [&quot;python3&quot;, &quot;backend.py&quot;]
# 도커환경에서 컨테이너 생성시 스크립트를 실행하는 폴더로, 미리 작성된 스크립트들을 이동
# RUN처럼 이미지를 생성하는 과정에서, 미리 해당 이미지 안에 특정 파일을 넣어둔다.
# COPY ./scripts/ /docker-entrypoint-initdb.d/
# 이미지 생성 명령어 (현 파일과 같은 디렉토리에서)
# docker build -t {이미지명} .
# 컨테이너 생성 &amp; 실행 명령어
# -v : volumn 명령어인데, 컨테이너와 특정 폴더를 공유한다.
# $(pwd) : 지금 위치한 이 폴더의 내용들이 각각의 컨테이너 /user/src/app~ 폴더에 들어간다의 의미
docker run --name {컨테이너명} -v $(pwd):/usr/src/app -p 5000:5000 {이미지명}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Kubernetes] Kubernetes란?]]></title>
            <link>https://velog.io/@youngmin-mo/Kubernetes-Kubernetes%EB%9E%80-lva0ggc8</link>
            <guid>https://velog.io/@youngmin-mo/Kubernetes-Kubernetes%EB%9E%80-lva0ggc8</guid>
            <pubDate>Thu, 29 Dec 2022 06:42:29 GMT</pubDate>
            <description><![CDATA[<h2 id="1-kubernetes가-나온-배경">1. Kubernetes가 나온 배경</h2>
<ul>
<li>몇 년 전만 해도 애플리케이션은 거대한 모놀리틱 시스템이었다. 업데이트가 자주 되지 않아 릴리스 주기가 느리고, 개발자는 개발 후 패키징 파일을 운영팀에게 넘기면, 운영팀이 이를 배포 후 모니터링 했다. 이후 운영팀은 하드웨어 장애가 발생하면 이를 사용 가능한 다른 서버로 마이그레이션 했다.</li>
<li>많은 서비스들이 Micro Service로 전환하면서 독립 개발 환경 구성, 잦은 배포, 수평 확장이 더욱 필요해졌다. 당연히 시스템에 배포 가능한 앱 구성 요소가 많아짐에 따라 시스템 관리자가 모든 구성 요소를 관리하는건 어려워졌다. 반면 잦은 배포가 필요해지면서 개발자는 시스템 관리자의 도움 없이 쉽게 배포도 필요해졌다.</li>
<li>즉, 개발자가 운영팀의 도움 없이 자신의 앱을 원하는 만큼 자주 배포하고 싶고, 운영팀은 하드웨어 장애 시 앱을 자동으로 모니터링 하면서 스케줄링을 통해 인프라를 쉽게 제어하고 싶어졌다.</li>
</ul>
<h2 id="2-kubernetes가-뭐지">2. Kubernetes가 뭐지?</h2>
<ul>
<li>컨테이너를 관리하는 S/W로, 개발자가 쿠버네티스에게 시스템이 의도하는 상태를 선언적으로 알려주고, 쿠버네티스가 실제 현재 상태를 지속적으로 검사해 의도한 상태로 조정한다.</li>
<li>클러스터에 노드가 몇개 있든 중요치 않고, 쿠버네티스에 앱을 배포하는 것은 항상 동일하며,(앱마다 상이한 케이스를 줄여줌) 클러스터 노드를 추가하는 것은 단순히 배포된 앱이 사용 가능한 리소스 양(cpu, memory, disk etc)을 추가한다는 의미다. 이때 기본 철학은 애플리케이션들이 어떤 노드에 배치되는지는 중요치 않다.</li>
<li>개발자가 원하는 상태를 &#39;선언형&#39;으로 나타내면, 쿠버네티스가 자체적으로 선언한 명령대로 스스로 관리된다. 따라서 개발자는 앱의 실제 기능을 구현하는데 집중하고, 앱을 인프라와 통합하는데 시간을 낭비하지 않는다.</li>
</ul>
<h2 id="3-kubernetes의-장점">3. Kubernetes의 장점</h2>
<ul>
<li>모놀리틱 서비스는 일부만 변경해도 전체 컴포넌트를 배포해야 되는데, 잘개 쪼개진 MSA에선 필요한 부분만 배포하면 된다. 쿠버네티스는 잘게 쪼개진 서비스를 쉽게 관리할 수 있어, 개발자는 쉽게 배포할 수 있다.</li>
<li>사용 가능한 리소스에 따라 스케줄링하여 하드웨어 활용도가 높은 노드로 선정하며, 노드 장애시 자동으로 실행 가능한 다른 노드로 대체하여 운영팀은 새벽 근무가 필요 없어진다. 또한 트래픽이 급증했을 때도 auto scaling하여 지속적인 모니터링이 필요없어졌다.</li>
<li>새로운 버전으로 업데이트할 때도 유용한다. 이전에는 운영중인 서버를 잠시 다운하고, 새로운 버전의 앱을 키는 과정을 점진 배포로 &#39;수동&#39;으로 했지만 쿠버네티스가 자동으로 컨테이너를 끄고 -&gt; 교체 -&gt; 키는 과정으로 배포 중 다운 없이 처리하게한다. 즉, &#39;수동&#39;이 아닌 &#39;자동&#39;으로 무 중단 업데이트를 제공한다.</li>
<li>컨테이너 기반이기 때문에 local, dev, real 등 개발환경 셋팅이 간편하다.</li>
</ul>
<h2 id="4-kubernetes-구성요소">4. Kubernetes 구성요소</h2>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/17913e27-9f4b-44bf-9b6f-c4a2f1b8bbb7/image.png" alt=""></p>
<h3 id="master-node">Master Node</h3>
<ul>
<li>etcd</li>
<li>scheduler</li>
<li>api server</li>
<li>controller manager</li>
</ul>
<h3 id="worker-node">Worker Node</h3>
<ul>
<li>kube-proxy</li>
<li>kublet</li>
<li>controller runtime</li>
</ul>
<h3 id="master-node-worker-node">Master Node, Worker Node</h3>
<ul>
<li>Master Node는 쿠버네티스 시스템을 관리하는 Controller Plain 역할로, 클러스터 상태를 저장하고 관리하는 요소이다. 이는 컨테이너를 직접 실행하지는 않는다. 반면 Worker Node에서 실제 컨테이너를 실행한다.</li>
</ul>
<h3 id="etcd-저장소">ETCD 저장소</h3>
<ul>
<li>쿠버네티스 오브젝트는 api 서버가 다시 시작되거나 실패하더라도 유지하기 위해 영구 저장이 필요하다. 이를 위해 쿠버네티스는 클러스터의 상태와 메타 데이터를 저장할 고가용성 구조를 가진 key/value 형태의 etcd 저장소를 사용한다.</li>
</ul>
<h3 id="api-server">API Server</h3>
<ul>
<li>API 서버는 다른 모든 구성 요소 및 클라이언트와 &#39;직접&#39; 통신한다. worker node의 kubelet을 통해 클러스터로 요청이 오면 &#39;인증&#39; -&gt; &#39;인가&#39; -&gt; &#39;컨트롤 플러그인&#39; -&gt; etcd 저장 및 클라이언트에 반환하는 작업을 처리한다.</li>
<li>여기서 말하는 인증이란 요청을 보낸 클라이언트를 인증함을 말하며, 인가는 인증된 사용자가 요청한 작업이 요청한 리소스를 대상으로 수행할 수 있는지 판별한다. 컨트롤 플러그인은 리소스 변경정책을 확인한다. 예를 들어 파드가 배포될 때마다 이미지를 강제로 가져오는 정책이 있다.</li>
<li>object가 갱신되면 api 서버는 etcd 정보를 (리소스 변경시 etcd 데이터가 업데이트됨) 참조해 클라이언트들에게 갱신된 정보를 전달한다. 즉, api 서버는 리소스 정보를 etcd에 저장하고, 변경 사항을 클라이언트에 통보하는 것 외에 다른 일을 하지 않는다.</li>
</ul>
<h3 id="scheduler">Scheduler</h3>
<ul>
<li>API 서버의 감시 매커니즘(변경 정보를 etcd에 업데이트하고, 쿠버네티스 리소스와 같은 클라이언트에게 이를 알림)을 통해 새로 생성될 파드를 기다리다, 할당된 노드가 없는 새로운 파드를 노드에 할당만하면된다.</li>
<li>H/W 리소스, 라벨, 볼륨 마운트 등 여러가지를 고려하여 스케줄링한다.</li>
</ul>
<h3 id="controller-manage">Controller Manage</h3>
<ul>
<li>리소스는 클러스터에 어떤 것을 실행해야 하는지 기술하는 반면, 컨트롤러 매니저는 배포된 리소스에 지정한대로 시스템을 원하는 상태로 수렴되도록 다른 &#39;활성&#39; 구성요소이다. replica, deployment, statefulset, service, volumn 등 여러가지 리소스가 있으며, 이를 컨트롤러 매니저가 관리한다.</li>
<li>어떤 컨트롤러도 kubelet과 직접 통신하지 않고, api 서버와 통신하며 리소스 정의와 현 상태를 비교하며 object를 제어한다.</li>
</ul>
<h3 id="kubelet">Kubelet</h3>
<ul>
<li>워커 노드에서 실행하는 모든 것을 담당하는 구성요소이다.</li>
<li>kubelet이 실행중인 노드를 노드 리소스로 만들어서 api 서버에 등록한다. 이후 api 서버를 지속적으로 모니터링해 해당 노드에 파드가 스케줄링되면, 파드의 컨테이너를 시작한다. 이후 설정된 컨테이너 런타임(docker)에 지정된 컨테이너 이미지로 컨테이너를 실행하도록 지시한다. 이후 실행중인 컨테이너를 계속 모니터링하면서 상태, 이벤트, 리소스 사용량을 api 서버에게 보고한다.</li>
</ul>
<h3 id="5-고가용성-클러스터">5. 고가용성 클러스터</h3>
<p><img src="https://velog.velcdn.com/images/youngmin-mo/post/e2ffbc14-8abc-409b-a89c-c3366e2a6ecd/image.png" alt=""></p>
<ul>
<li>애플리케이션의 고가용성을 위해, 디플로이먼트 리소스로 애플리케이션을 실행하고 적절한 수의 레플리카를 설정한다.</li>
<li>애플리케이션 수평확장이 힘들더라도, 레플리카 수를 1로 선정해야하는데 이는 문제 발생시 자동으로 재시작하기 때문이다. 하지만 파드 교체시 짧은 중단 시간이 발생하는데 이는 어쩔 수 없다.</li>
<li>짧은 중단 시간을 방지하고자 활성/비활성 복제본을 두고 처리하기도 한다. 단 하나만 활성 상태에 두다, 문제시 비활성 복제본을 사용하는 방법도 있다.</li>
</ul>
<h2 id="6-kubernetes-애플리케이션-실행">6. Kubernetes 애플리케이션 실행</h2>
<ol>
<li>앱을 하나 이상의 컨테이너 이미지로 패키징하고, 해당 이미지를 이미지 레지스트리로 푸시한다. 이후 디스크립션 파일에 어떤 이미지를 불러오고, 구성요소(리소스), 구성요소간 통신 방법 등이 담긴 정보를 쿠버네티스 API 서버로 전송한다.</li>
<li>먼저 API 서버가 디스크립션 파일을 읽는다. 이후 스케줄러에 의해 각 컨테이너에 필요한 리소스를 계산하고 워커노드에 각각의 컨테이너를 할당한다.</li>
<li>지정받은 워커 노드의 kubelet은 컨테이너 런타임에 필요한 이미지를 이미지레지스트리로부터 가져와 컨테이너를 실행하도록 지시한다.</li>
<li>앱이 실행되면 쿠버네티스는 애플리케이션의 실행 상태가 사용자가 제공한 디스크립션 내용과 일치하는지 지속적으로 확인한다. 예를 들어, 워커 노드가 중단되거나 특정 컨테이너가 이상이 있을 경우 해당 노드/컨테이너를 재스케줄링한다.</li>
<li>애플리케이션이 실행되는 동안 복제본 수를 늘릴지 줄일지를 결정할 수 있고, 이 작업은 쿠버네티스에 맡겨 cpu 부하/메모리 사용량, 초당 요청 수 등을 고려해서(디스크립션에 지정) 자동으로 조정할 수 있다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Deployment]]></title>
            <link>https://velog.io/@youngmin-mo/Building-Microservices-6%EC%9E%A5-%EB%B0%B0%ED%8F%AC-cezap3as</link>
            <guid>https://velog.io/@youngmin-mo/Building-Microservices-6%EC%9E%A5-%EB%B0%B0%ED%8F%AC-cezap3as</guid>
            <pubDate>Sat, 25 Jun 2022 03:25:39 GMT</pubDate>
            <description><![CDATA[<h2 id="1-jenkins란">1. Jenkins란</h2>
<ul>
<li>젠킨스란 소프트웨어 개발 시 지속적으로 통합 서비스를 제공하는 (Continuous Integration) 툴 이다.</li>
<li>다수의 개발자들이 하나의 프로그램을 개발할 때 버전 충돌을 방지하기 위해 각자 작업한 내용을 &#39;공유 저장소&#39;에 빈번히 업로드하는데, 젠킨스에 의해 지속적 통합을 가능케한다.</li>
<li>젠킨스와 같은 CI툴이 등장하기 전에는 일정시간마다 빌드를 실행하는 방식이 일반적이었다. 특히 개발자들이 당일 작성한 소스들의 커밋이 모드 끝난 심야 시간대에 이러한 빌드가 타이머에 의해 집중적으로 진행되었는데, 이를 nightly-build라 한다. 하지만, 젠킨스는 정기적인 빌드에서 한발 나아가 서브버전, Git 과 같은 버전관리시스템과 연동하여 소스의 커밋을 감지하면 자동적으로 자동화 테스트가 포함된 빌드가 작동되도록 설정할 수 있다.</li>
</ul>
<h3 id="젠킨스가-주는-이점">젠킨스가 주는 이점</h3>
<ul>
<li>프로젝트 표준 컴파일 환경에서의 컴파일 오류 검출</li>
<li>테스트 코드 자동 수행</li>
<li>정적 코드 분석에 의한 코딩 규약 준수여부 체크</li>
<li>프로파일링 툴을 이용한 소스 변경에 따른 성능 변화 감시</li>
<li>결합 테스트 환경에 대한 배포작업</li>
<li>각종 배치 작업의 간략화 (DB셋업, 환경설정, 배포, 라이브러리 릴리즈 등)<ul>
<li>보통 젠킨스 톰캣을 띄우고, 젠킨스 서버에서 스케줄러에 의해 해당 시간대에 배치 파일을 읽어서 실행한다.</li>
</ul>
</li>
<li>이 외에도 젠킨스는 500여가지가 넘는 플러그인을 온라인으로 간단히 인스톨 할 수 있는 기능을 제공하고 있으며 파이썬과 같은 스크립트를 이용해 손쉽게 자신에게 필요한 기능을 추가 할 수도 있다.</li>
</ul>
<h2 id="2-vm-배포-시스템">2. VM 배포 시스템</h2>
<ol>
<li>Build Jenkins Server에서<ul>
<li>[1] github에서 특정 브랜치 or 태그 or 릴리즈를 fetch하고 -&gt; 소스 코드를 가져오고</li>
<li>[2] build tool(maven, gradle 등)로 소스파일 빌드하고 -&gt; 실행 파일(war, jar etc)을 생성하고</li>
<li>[3] 결과물을 특정 저장소로 배치한다. -&gt; 공유 저장소에 푸시하고 (추후에 롤백시 이 파일로 바로 배포)</li>
</ul>
</li>
<li>Target Server에서<ul>
<li>[5] 타겟서버에서 빌드 [3] 결과물을 갖고온 후</li>
<li>[6] 지정된 스크립트로, 실행중인 프로세스 종료 후, 결과물을 start한다. (타겟 서버에 배포하는데 배포 방식에 따라 롤링, 블루그린 배포가 있다.)</li>
<li>[7] 새롭게 배포된 버전이 문제 있을 경우 빌드된 결과물을[3] 갖고와 재배포한다.</li>
</ul>
</li>
</ol>
<h2 id="3-kubernetes-배포-시스템">3. Kubernetes 배포 시스템</h2>
<ol>
<li>DockerFile에 작성된 내용을 토대로, 실행 파일 이미지를(컨테이너가 이해할) Docker Registry에 푸시한다.</li>
<li>배포할 Worker Node에서 [1]에서 푸시한 이미지를 가져온 후, 각각의 컨테이너에서 해당 이미지를 실행한다.</li>
<li>쿠버네티스의 리소스를 활용하여 다음과 같은 작업을 추가로 수행한다.<ul>
<li>Service, Ingress : 배포 후 각각의 파드는 IP 주소가 변경되는데, 외부에서 바라보는 IP 주소를 배포 후에도 변경없이 맞춰준다.</li>
<li>Deployment : Rolling, Blue-Green 등 개발자가 선언한 형식으로 무중단 배포를 지원한다.</li>
<li>Readness probe : VM 방식보다 배포 시간이 빠르다고 할지라도, 약간의 지연이 있을 수 있는데, 준비되지 않은 컨테이너는 요청을 처리하지 않도록 대기시킨다.</li>
<li>Statefulset : 기본적으로 파드는 Stateless하게 구성하는데, 특정 프로그램은 이전 내용을 공유할 필요가 있다. 즉, 이전 내용을 기억해야한다면 Statefulset 리소스에 의해 배포 전 파드의 내용을 배포 후의 파드에도 동일하게 기억한다.</li>
</ul>
</li>
</ol>
<h2 id="4-무중단-업데이트-배포-방법">4. 무중단 업데이트 배포 방법</h2>
<h3 id="1-rolling">[1] Rolling</h3>
<ul>
<li>구버전에서 신버전으로 트래픽을 점진적으로 전환하는 배포방식이다.</li>
<li>하나씩 옮기므로 롤백이 수월하지만, 구/신 버전이 동시에 실행되도 괜찮은 환경에서만 사용해야한다.<img src ="https://velog.velcdn.com/images/youngmin-mo/post/03b21807-4714-4203-b114-8e3d7052fb96/image.png" width="70%"/>

</li>
</ul>
<h3 id="2-blue-green">[2] Blue-Green</h3>
<ul>
<li>운영중인 구버전과 동일하게 신버전의 인스턴스를 구성한 뒤 로드밸런서를 통해 모든 트래픽을 한번에 신버전 쪽으로 전환하는 방식이다.</li>
<li>구버전의 인스턴스가 그대로 남아있어서 좀 더 손쉽게 롤백이 가능하며, 구버전의 환경을 다음 배포에 재사용할 수 있다. 또한 운영환경에 영향을 주지 않고 새 버전 테스트가 가능하다.</li>
<li>시스템 자원이 두배로 필요하며, 시스템 환경에 대한 테스트가 전제되어야한다.<img src ="https://velog.velcdn.com/images/youngmin-mo/post/9c3bbe29-6885-4fb5-abdd-931297ca83e2/image.png" width="70%"/>

</li>
</ul>
<h3 id="3-canary">[3] Canary</h3>
<ul>
<li>신버전을 소슈의 유저들에게만 배포를 해보고, 문제가 없는것을 확인해가며 점차 많은 유저들에게 배포하는 기법이다.</li>
<li>블루그린 배포와 유사하지만 블루그린처럼 트래픽을 한번에 확 바꾸는 거이 아니라 단계적으로 안전한지 체크하기 때문에, 부정적 영향을 최소화하고 상황에 따라 트래픽 양을 조절하며 롤백할 수 있다.</li>
<li>문제 상황을 빠르게 감지할 수 있지만, 네트워크 트래픽을 제어해야 되는 단점이 있다.<img src ="https://velog.velcdn.com/images/youngmin-mo/post/a13bd11f-a6c7-4433-980a-ec0feb621d0f/image.png" width="70%"/></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Microservice] MSA?]]></title>
            <link>https://velog.io/@youngmin-mo/Building-Microservices-1%EC%9E%A5-%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EC%84%9C%EB%B9%84%EC%8A%A4</link>
            <guid>https://velog.io/@youngmin-mo/Building-Microservices-1%EC%9E%A5-%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EC%84%9C%EB%B9%84%EC%8A%A4</guid>
            <pubDate>Thu, 26 May 2022 11:39:41 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>은총알은 없다. 마이크로 서비스 구조가 만능이 아니다. 마이크로서비스는 피치 못하게 모놀리틱 서비스보다 &#39;복잡성&#39;을 갖는다. 모놀리틱에서는 필요 없는 통신이 추가되고, 분리된 데이터 동기화와 같이 배포시 신경써야할게 늘어날 수도 있다.</p>
</blockquote>
<h2 id="1-적절한-msa-구조에서의-장점">1. 적절한 MSA 구조에서의 장점</h2>
<blockquote>
<p>분리된 서비스들이 <code>독립적</code>으로 나아간다.</p>
</blockquote>
<ul>
<li>조직부합성<ul>
<li>콘웨이 법칙과<code>(조직이 설계한 시스템 디자인은 해당 조직의 커뮤니케이션 구조를 따른다.)</code> 같이, 시스템 아키텍처가 조직에 맞게 구성되어 협업이 수월해진다.</li>
</ul>
</li>
<li>확장성<ul>
<li>모놀리틱 구조와 달리, 전체 서비스가 아닌 필요한 서비스만 확장이 용이한다.</li>
</ul>
</li>
<li>회복성<ul>
<li>다른 서비스와 독립적이라면, 다른 서비스에서 장애가 발생해도 장애가 전파되지 않게 할 수 있으며, 문제 발생시 롤백도 필요 부분만 수행하여 장애 영향도가 적다.</li>
</ul>
</li>
<li>배포 용이성<ul>
<li>다른 서비스와 독립적이라면, 다른 서비스 신경쓸 필요 없이 배포를 원하는대로 가능하다. 즉, 비지니스 요구사항에 맞게 빠르게 독립적으로 배포할 수 있다.</li>
</ul>
</li>
<li>대체 가능성 &amp; 조합성<ul>
<li>작은 서비스로 구성되니, 쉽게 다른 컴포넌트로 대체 가능하며(리팩토링도 용이) 이러한 컴포넌트끼리 조합도 쉽게 할 수 있다.</li>
</ul>
</li>
<li>기술 이기종성<ul>
<li>작은 서비스로 구성되니, 그 서비스(팀)에 맞는 기술 ex) 언어, db, infra 등을 핏하게 맞출 수 있다.</li>
</ul>
</li>
</ul>
<h2 id="2-적절치-않은-msa-구조에서의-단점">2. 적절치 않은 MSA 구조에서의 단점</h2>
<ul>
<li>독립적인 서비스들 사이에 협업을 통해 비지니스가 처리되다 보니 시스템이 기본적으로 복잡해진다. 운영 측면에서 한 비지니스 프로세스의 파악을 위해 여러 서비스들의 존재를 알아야하고 그들 사이에서 발생하는 협업들에 대해서도 파악해야 한다. 즉, 운영영의 어려움을 가져온다. 물론 독립적인 서비스라면 BEST </li>
<li>분리된 서비스들이 하나의 테이블을 건드리는 상황에서, 테이블에 이상한 데이터가 발생해도 어느 서비스 때문인지 파악이 어렵고, 하나의 트랜잭션으로 묶기 또한 힘들다. 즉 데이터 동기화의 어려움이 있다.</li>
<li>MSA 구조는 분산 환경이기 때문에 강력한 트랜잭션 일관성을 제공하기 힘들다. 최종적인 일관성 제공을 위해선 다른 매커니즘을 갖고와야한다.</li>
<li>하나의 기능이 여러 서비스에 분산되어, 하나의 기능을 유지보수하기 위해선 여러 서비스를 건드려야 하고, 이는 곧 개발 시간이 늘어남과 동시에 사람인지라 수정해야될 부분을 빠트리는 등 예상치 못한 버그로 이어진다.</li>
<li>의존성이 섞여 있는 컴포넌트는 여러 컴포넌트가 묶여 있으니 배포도 동시에 일어나야하는 상황이 발생하여, 관련된 여러 서비스간 배포 시간을 맞추는 등 불편함이 생긴다. MSA 구조에서 독립 배포의 장점을 잃어버리는 꼴이다.</li>
<li>분리된 서비스들간 테스트 또한 쉽지 않다. 테스트를 하기 위해선 의존성이 있는 서비스를 미리 확인해야 한다. 의존성있는 서비스가 정상적이지 않은 경우 테스트가 힘들다.</li>
<li>디버깅도 힘들 수 있다. 시스템 문제 발생시 모놀리틱 구조에선 서버 하나에 대한 문제 원인을 파악하면 되지만 MSA 구조는 관련된 모든 서비스를 일일히 살펴봐야되며, 통합 로그 관리 시스템이 없다면 하나하나 수동으로 서버 들어가서 파악해야된다.</li>
<li>이정도면 하나의 시스템을 억지로 찢은 느낌이다. 물론 처음엔 서비스들이 적절히 분리되있으나, 일정 압박으로 괜찮은 아키텍처 고려없이 의존성이 중구난방으로 펼쳐지며 복잡한 구조를 갖게되는 경우도 있다.</li>
<li>물론 모놀리틱 서비스가 가지는 한계도 있다. 장애의 퍼짐을 막기 힘들고, 수평 확장밖에 못하는 한계도 있다. 성능을 올릴 때 로드 밸런서를 두거나, 모든 서버를 증가시킬 수밖에 없다.</li>
<li>결국, <code>분산시스템이 가지는 복잡성을 해결</code>해야 MSA 구조의 장점을 끌어올릴 수 있다. 이것만 해결한다면 앞서 말한 강력한 장점을 갖고 올 수 있다. 흑백논리처럼 무조건 어디가 좋다고 생각하지말고 도메인/상황에 맞게 선택하자.</li>
</ul>
<h4 id="참고-모놀리스-구조">참고) 모놀리스 구조</h4>
<ul>
<li>모놀리틱 서비스는 단일 프로젝트에 모든 코드가 모여있는데,</li>
<li>시스템 구조가 간결하고, 빠르게 구축할 수 있다. 처음부터 MSA 구조를 선택하는 것이 아닌 모놀리틱 서비스로 나아가되, 추후 의존성을 분리하고, 모듈을 분리하는 방향으로 나아가자.</li>
<li>테스트 및 배포 파이프라인 모놀리틱보다 구성이 간단하며, 인프라스트럭처 구축과 운영이 용이하다.</li>
</ul>
<h2 id="3-코드의-응집과-결합">3. 코드의 응집과 결합</h2>
<ul>
<li>기능만 돌아가면 되지, 왜 SW 디자인(코드 구조)을 신경쓸까? 성능도 중요하지만, 결국 소비자에게 제공하는 비지니스 변경을 <strong><em>빠르게</em></strong> 반영하기 위해서다. 빠른 서비스 반영은 곧 수익으로 이어진다.</li>
<li>SW는 태생적으로 신경쓰지 않는다면 자연스럽게 구조는 복잡해진다. 이러한 복잡도를 잡는데 <strong><em>코드의 응집과 결합</em></strong>을 다스리는 것이 먼저다.</li>
<li>Monolitic -&gt; MSA 구조로 가는데 실패하는 가장 큰 이유 중 하나는 내부 구조가 복잡하기 때문이다. 따라서 코드의 복잡도를 잡는것이 첫번째이다.</li>
<li>변화에 성격과 비용(개발자 인력/시간)을 고려해야 하며, 밸런스에 맞춰 응집도를 높이고, 결합도를 낮추는것이 목표다. 만약 서비스를 작게 쪼개면 응집도는 높아지겠지만, 모듈간 결합도가 커지고 // 서비스를 크게 묶으면 모듈간 결합도는 낮아지지만, 응집도는 낮아진다. 결국 이들간의 밸런스를 맞출 필요성이 있다.<ul>
<li>코드의 응집<ul>
<li>시스템의 변경이 필요할 때, 하나의 요소에서 변하는 정도</li>
<li>어느 부분이 변경될 때, 해당 부분의 전체가 변한다면 응집도가 높은거고, 일부만 변경되면 요건 응집도가 낮다할 수 있다.</li>
</ul>
</li>
<li>코드의 결합<ul>
<li>두 개 이상의 요소가 있을 때, 하나의 변경이 또 다른 하나의 변경에 미치는 정도이다.</li>
<li>변경했을 때 다른 컴포넌트들도 많이 변경되면 결합도가 높다할 수 있다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>더 좋거나 나쁜 아키텍처는 없고, 시스템이 처한 환경에 따라 조건이 다르고, 이를 보고 유연하게 구조를 판단한다. 이때 시스템은 지속적으로 변하고, 이에 적응할 수 있는 아키텍처를 고려하자.</p>
</blockquote>
<h2 id="4-유연한-시스템을-위한-조건">4. 유연한 시스템을 위한 조건</h2>
<blockquote>
<p>좋은 아키텍처는 시스템이 모노리틱 구조로 태어나서 단일 파일로 배포되더라도, 이후에는 독립적으로 배포 가능한 단위들의 집합으로 자연스럽게 성장하고, 또 독립적인 서비스나 마이크로서비스 수준까지 바뀔 수 있도록 만들어져야 한다. 또한 좋은 아키텍처라면 나중에 상황이 바뀌었을 때 이 진행 방향을 거꾸로 돌려 원래 형태인 모노리틱 구조로 되돌릴 수도 있어야한다. by clean architecture 이말의 핵심은 결국 의존성을 잘 관리한 코드 구조는 Monolitic &lt;-&gt; MSA 의 전환이 자유로움을 의미한다.</p>
</blockquote>
<h3 id="1-모듈화">[1] 모듈화</h3>
<ul>
<li>단일 시스템이 커졌을 때 boundary context(domain의 경계) 와 같이 특정 조건에 맞게 경계를 두어 분리한다. 예를 들어 java에선 package로 경계를 구분한다.</li>
<li>private/public method로 외부에 공개하지 않은것들을 구분하고, 모듈화가 잘 되있으면 시스템을 볼 때 전체를 볼 필요가 없이 필요한 부분만을 확인하여 개발 생산성이 증가한다.</li>
<li>즉, 내부 서비스를 숨기고, 외부랑 통신할 때는 공개된 API로 통신한다.</li>
</ul>
<h3 id="2-아키텍처-구조">[2] 아키텍처 구조</h3>
<ul>
<li>Layered, Domain, Union, Hexagonal 아키텍처 등 다양하게 있다. 점차 아키텍처 구조 패러다임은 관심사의 분리를 통해 애플리케이션 핵심을 외부 연동 모듈과 분리시킨다. 이렇게 분리된 영역은 필요에 따라 어렵지 않게 &#39;변경&#39;이 가능해야한다.</li>
<li>즉, 애플리케이션 핵심은 코드의 영향을 최소화하고, 변경이 잦은 외부 컴포넌트는 쉽게 바꿔치기(통합/변경)할 수 있다. 이에 따라 테스트 하기 쉽고, 변경을 빠르게 수용할 수 있다. 이로써 고객에게 비지니스 변경을 빠르게 제공할 수 있는지 고민하자.</li>
<li>ex) Layered Architecture는 시스템 규모가 커지면 계층 내부에 복잡도가 증가하며 &amp;&amp; 또한, 모든 계층의 변경이 일어날 여지가 있다. 이에 따라 도메인을 기반으로 분리하고, 내부적으로 계층을 분리하는 아키텍처로 변경한 사례가 있다.</li>
<li>관심사에 따른 모듈화<ul>
<li>수직적 관심사 : 비지니스와 응용 프로그램에 특화된 기능(핵심 도메인)을 중점으로 관심사를 혹은 경계를 분리한다.</li>
<li>수평적 관심사 : 로깅, 보안 등 공통적인 저수준 기능으로 비지니스와 무관하게 공통 모듈을 제공한다. 재사용성이 목표다.</li>
</ul>
</li>
<li>잘 모듈화(ex-package 구분) 했다면 이제 모듈 사이의 경계를 넘어오지 못하게해야한다. ex) 컴파일러, 외부 의존성 관리 도구, private/protected/public, 수기, 수동 등 다양한 방법이 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Microservice] Monolithic To MSA ]]></title>
            <link>https://velog.io/@youngmin-mo/Building-Microservices-5%EC%9E%A5-%EB%AA%A8%EB%86%80%EB%A6%AC%EC%8A%A4-%EB%B6%84%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@youngmin-mo/Building-Microservices-5%EC%9E%A5-%EB%AA%A8%EB%86%80%EB%A6%AC%EC%8A%A4-%EB%B6%84%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 22 May 2022 07:41:39 GMT</pubDate>
            <description><![CDATA[<h2 id="1-모놀리틱-서비스-분해">1. 모놀리틱 서비스 분해</h2>
<blockquote>
<p>기존의 코드를 포함한 모놀리식 시스템을 어떻게 분해하면 좋을까?</p>
</blockquote>
<h3 id="1-코드의-경계를-찾자">[1] 코드의 &#39;경계&#39;를 찾자.</h3>
<ul>
<li>코드를 설계할 때 우리는 응집력이 높고, 각 컴포넌트간 느슨히 결합되는 서비스를 원한다. 하지만 모놀리틱 서비스는 함께 변결될 가능성이 높은것들을 함께 모으기 보단, 모든 종류의 코드를 가져와 이어 붙이는 경향이 있다. 물론 의존성을 잘 설계했더라면 응집력 높은 코드 베이스를 유지하지만 시간이 지나면서 이런 관리를 신경 안 쓸 가능성이 높다.</li>
<li>경계의 기준은 코드 베이스의 나머지 부분에 영향을 주지 않는 격리된 코드를 의미한다. 자바에선 package가 있다.</li>
<li>따라서 함께 변경될것같은 컴포넌트들을 한 패키지에 모아 놓는 작업부터 시작하는데, 한번에 모든 것들을 바꾸기 보단 한꺼번에 변경할 때 발생하는 피해를 최소화하기 위해 조금씩 점진적으로 변경하자.</li>
<li>막상 분리하면 각 패키지는 뒤엉킨 의존성을 가지며 cycle이 생긴다. 원인은 대부분 데이터베이스에 의존하기 때문이다.</li>
</ul>
<h3 id="2-데이터베이스와의-경계를-어떻게-끊을것인가">[2] 데이터베이스와의 경계를 어떻게 &#39;끊을것&#39;인가?</h3>
<ul>
<li>다른 컨텍스트의 테이블에 직접 접근하기보단, 해당 컴포넌트의 API를 통해 데이터를 노출하자. 물론 성능 이슈(rest api 통신)가 있을 수 있지만, 이건 도메인에 따라 수용 가능한지 판단해보자.</li>
<li>외부키 관계가 사라지면서 트랜잭션과 같은 제약조건 사라졌고, 데이터의 일관성을 보장하기 힘들어진다. 만약 강한 트랜잭션 결합이 중요한 경우엔 기존 서비스를 따르거나 트랜잭션을 보장할 새로운 방법을 기획하고 아닌 경우 분리하는걸 고려해보자.</li>
</ul>
<h4 id="참고-공유-데이터">참고) 공유 데이터</h4>
<ul>
<li>StringUtils, enum, 국가 테이블 등 공유 데이터 처리<ul>
<li>하드 코딩, 디비 테이블 관리가 있다.</li>
<li>테이블을 각 패키지에 복제하는 경우 당연히 일관성이 깨지고, 모든 컴포넌트에서 하나의 테이블에 접근하는 경우 의존성이 몰리는 현상이 발생한다. 이를 api를 두고 일관성은 유지하면서 의존성을 관리할 수 있다.</li>
</ul>
</li>
<li>두 개 이상의 서비스에서 하나의 테이블에 CURD한다면<ol>
<li>각각의 서비스에 맞게 테이블을 분리하거나</li>
<li>해당 테이블을 다른 추상화된 서비스를 분리하고, 이를 API(id)로 호출</li>
</ol>
</li>
</ul>
<h3 id="3-데이터베이스-분리">[3] 데이터베이스 분리</h3>
<ul>
<li>모놀리틱 서비스 안에 여러 서비스가 하나의 테이블 스키마를 바라보는 상황에서, 이제 각각의 테이블로 분리하고, 분리된 테이블에 따라 서비스도 분리한다.</li>
<li>스키마가 분리되면 단일 작업을 수행하는 <strong>디비의 호출 횟수가 증가할 수 있다. 또한, 트랜잭션의 일관성 역시 깨질 수 있다.</strong></li>
<li>서비스를 분리하는 순간 단일 트랜잭션이 제공하는 안전성이 깨진다. 즉, 데이터의 일관성이 분리 되는데, 이를 위해, 작업의 결과를 큐나 로그 파일에 푸시하고 나중에 재시도 하는 방법도 있다. 이렇게 향후 특정 시점에 시스템이 스스로 일관성을 유지하는 상태가 되로록 허용하는데, 이를 최종적 일관성이라한다. 이렇게 비지니스 작업들이 오래 걸리는 경우 유용하다.</li>
<li>다른 방안은 전체 연산 작업을 중지하는데(rollback) 이것마저 실패한다면 굉장히 복잡해진다. 일관성이 유지되기 바라는 작업이 한 두개가 아니라 많다면 굉장한 리소스가 소모될 것이다.</li>
<li>2단계 커밋으로 이루어진 분산 트랜잭션 도입한다. 분산 트랜잭션 매니저는 해당된 하위 트랜잭션으로부터 진행 가능 여부를 받고, 하나라도 안되면 롤백한다. 관련된 알고리즘이 많아 사용하면 되는데, 문제가 있다. 분산 트랜잭션 매니저가 다운되거나, 하나라도 투표 결과를 받지 못하면 모든것이차단된다.</li>
<li>정말 일관된 데이터가 필요하다면 처음부터 그 상태가 유지되도록 새로운 도메인을 만드는것도 고려해보자.</li>
</ul>
<h3 id="4-리포팅">[4] 리포팅</h3>
<ul>
<li>모놀리틱 서비스 아키텍처에서 모든 데이터는 거대한 단일 디비에 저장하여 이를 리포팅하기 굉장히 쉽다. 이때, 리포팅 쿼리가 메인 시스템에 영향을 끼치지 않게 하기 위해, replica 디비에서 처리한다.(relica 디비는 메인 디비와 주기적으로 sync 맞춤)</li>
<li>하지만 운영 디비 스키마를 그대로 사용하여 추출을 위한 리포팅 디비 스키마를 변경할 수 없다. 리포팅을 위해 운영 디비 스키마를 변경하는건 말이 안된다. RDB의 한계이기도 하면 로깅 처리도 힘들다. 당연히 리포팅을 위한 최적화도 어렵다.</li>
<li>리포팅시스템에서 각각의 원본 시스템으로 API를 통해 데이터를 추출하면, 많은 양의 데이터를 가져와야 되며, 서비스 트래픽이 폭파될 가능성이 있다.</li>
<li>따라서 주기적으로 배치 프로그램으로 데이터를 가져올 수 있는데, 이는 실시간성 처리가 힘든 단점이 있다. ex) 일주일간/한달간 디비 쿼리 성능 분석을 노티</li>
<li>따라서 이벤트 기반의 통신이 일어날 때 각 서비스에서 리포팅 시스템으로 데이터를 전달하는 방법도 있다. 즉, 매 통신마다 해당 스트림셋 혹은 하둡으로 직접 전송하거나 파일비트로 로그를 떨구는 등의 작업으로 처리한다. ex) 중요 데이터를 통신마다 적재하여 사용</li>
</ul>
<h2 id="2-배달의-민족-마이크로서비스-여행기">2. 배달의 민족 마이크로서비스 여행기</h2>
<blockquote>
<p>MSA로의 전환은 기술의 선택이 아닌, 생존의 전략.</p>
</blockquote>
<ul>
<li>이전엔 프론트 서버, 회원/인증, 리뷰, 주문, 정산, 쿠폰 등 한곳에서 관리하는 단일 서비스 형태였다. 하나의 디비에 약 700여개의 테이블, 스토어드 프로시저는 4,000개로 거대한 모놀리틱 시스템이었다.</li>
<li>그러던 중 리뷰 테이블에 어떤 문제가 생겨 DB 전체에 문제가 발생했으며, 사실 리뷰가 발생해도 다른 서비스엔 영향이 없어야한다.</li>
<li>따라서 점진적으로 각각의 서비스를 분리하느 작업(디비를 분리)을 시작하기로 한다.</li>
<li>또한, 기존엔 법적으로 IDC에서 서버를 관리하였는데, 이를 Cloud 시스템으로 AWS 형태로 이동하기 시작한다. 이게 왜 중요하냐면 서버의 증설이 매우 편리하며, 이러한 서버 관리를 어느정도 인프라 회사로 넘길 수 있어 트래픽을 유동적으로 처리할 수 있다.</li>
<li>조회쪽 처리 디비를 RDB -&gt; NoSQL로(용량관리) 이동했으며, write/read 데이터 싱크는 1~5분 주기로 배치 작업으로 처리하여 부하를 줄였다.</li>
<li>주문 시스템은 주문 완료시 리뷰, 광고 등 다양한 시스템의 api를 호출하는데 이걸 이벤트 기반 데이터 전달 방식으로 변경했다. 리뷰, 광고 시스템 등이 장애시 주문 시스템도 의존성이 요쪽에 있기 때문에 영향이 가하며 새로운 시스템과 연동할때마다 주문 시스템은 고통이된다. 따라서 주문 시스템 완료시 특정 토픽에 produce하고, 각각의 다른 팀들이 이를 consume하는 방향으로 이동한다. 또한, 특정 작업의 손실 없이 안정적인 점도 있다.</li>
<li>CURS 도입<ul>
<li>기존 시스템에선 명령 시스템을 사용하는 컴포넌트 문제시 다른 서비스에도(조회) 영향이 가해지며, 특정 컴포넌트에선 조회가 급격히 폭파할때가 있다. 즉, 대용량 트래픽을 대응해야하며, 특정 서비스에서 장애가 나도 다른 서비스는 유지해야 한다. 따라서 명령과 조회 서비스를 분리하여 이를 어느정도 해결할 수 있지만, 데이터 동기화 작업이 필요하다.</li>
<li>Command and Query Responsibility Segregation</li>
<li>사장님 사이트에 의해 데이터가 변경(명령)하는 작업과 조회하는 컴포넌트를 분리하고, 이를 Event-Driven 방식 Producer/Consumer 구조로 사용한다. 명령 데이터를 토픽에 넣고, 조회 서비스에서 이를 조회한다. 약간의 데이터 지연이 있지만 최종적으로 데이터의 sync가 맞는다.</li>
<li>이때 이벤트 드라이븐 전달시 id만을 전달하는데 이는 변경되는 데이터의 순서보단 데이터의 최종 결과물만 관심이 있기 때문이다. id에 해당하는 데이터는 그 이후에 각각 호출하여 데이터 싱크를 맞춘다.</li>
<li>조회쪽 디비는 ES, Redis, MongoDB와 같이 조회가 빠른 것 / 사장님 서비스는 안정성이 중요하므로 RDB로 사용한다.</li>
</ul>
</li>
</ul>
<blockquote>
<p>시스템이 커지고, 트래픽이 커지고 규모의 경제가 가능해지는 순간에 MSA가 필요하다. 단순하게 테이블을 조인하면 될걸 데이터 싱크하고 맞추는 과정에서 비용이 10배는 더 든다. 이를 상쇄하고도 남는 경우에만 MSA로 넘어간는것이 맞다는 조언.</p>
</blockquote>
<hr>
<h2 id="3-의존성-관리---우아한-객체지향">3. 의존성 관리 - 우아한 객체지향</h2>
<h3 id="1-의존성을-이용한-설계">[1] 의존성을 이용한 설계</h3>
<ul>
<li>설계란 코드를 어떻게 &#39;배치&#39;할 것인지에 대한 의사결정이다. 어떤 코드를 class, package, project 등에 배치할지 고민한다.</li>
<li>핵심은 &#39;변경&#39;이다. 같이 변경되는 얘들을 같은 클래스, 패키지에 모아 놓는다. 따라서 의존성에 따라 설계가 바뀌기 때문에 설계의 핵심은 의존성이다.</li>
<li>A가 B에 의존한다는 의미는, B가 변경될때 A도 함계 변경될 여지가 있다는 의미다. 즉, 변경에 의한 영향을 받을 수 있는 &#39;가능성&#39;이 있다와 같은 의미이다.</li>
</ul>
<h3 id="2-클래스-의존성-종류">[2] 클래스 의존성 종류</h3>
<h4 id="association">Association</h4>
<ul>
<li>강한 결합으로, A에서 B로 이동할 수 있다는 의미다.</li>
<li>연관관계는 빈번하게 협력할 때 사용하며, 영구적 관계를 맺는다. 구현 방법 중 하나는 객체 참조다.<pre><code class="language-java"> class A {
    private B b;
  }</code></pre>
</li>
</ul>
<h4 id="dependency">Dependency</h4>
<ul>
<li>파라미터, 리턴 타입, 메서드 안에 타입이 생성된다면 의존관계가 성립된다.</li>
<li>일시적 관계를 맺는다.<pre><code class="language-java"> public B method(B b) {
    return new B();
  }</code></pre>
<h4 id="inheritance">Inheritance</h4>
</li>
<li>상속관계는 B가 바뀔 때 A가 변경될 수 있는데, 구현이 변경되면 영향을 받는다.<pre><code class="language-java"> class A extends B { }</code></pre>
<h4 id="reliaization">Reliaization</h4>
</li>
<li>실체화 관계는(인터페이스) 인터페이스가 바뀌면 (구현x) 영향을 받는다.<pre><code class="language-java"> class A implements B { }</code></pre>
</li>
</ul>
<h4 id="패키지-의존성">패키지 의존성</h4>
<ul>
<li>package 레벨이며, 간단히 import에 다른 객체가 존재하면 의존성을 가진다고 생각할 수 있다.</li>
</ul>
<h3 id="3-설계-가이드">[3] 설계 가이드</h3>
<ul>
<li>양방향 의존성을 피하자.<ul>
<li>A &lt;-&gt; B : A가 바뀔 때 B가 바뀌고, A가 또 바뀔 수 있다.</li>
<li>이는 한 클래스를 억지로 분리한 것으로 생각할 수 있다.</li>
<li>성능 이슈 및 여러 문제점이 발생할 여지가 있다.</li>
</ul>
</li>
<li>다중성이 적은 방향을 선택하라.<ul>
<li>컬렉션을 인스턴스 변수로 갖지 않도록 한다. 이는 성능 이슈 등 다양한 이슈가 생길 수 있다.<pre><code class="language-java">class A { private Collection&lt;B&gt; bs; } + class B { } x
class A {} + class B { private A a; } o</code></pre>
</li>
</ul>
</li>
<li>의존성이 필요없다면 제거하자.</li>
<li>패키지 사이의 의존성 사이클을 제거하자.<ul>
<li>의존성이 양방향이면 같이 바뀌니까 하나의 패키지로 만드는 것이 낫다.</li>
<li>패키지 3개가 한 싸이클이면 하나의 패키지로 보는것이 맞다.</li>
</ul>
</li>
</ul>
<h3 id="3-설계-개선하기">[3] 설계 개선하기</h3>
<ul>
<li>설계를 개선하는 방법을 고민할 때는, 하나의 클래스를 파악하기 보단, 객체의 협력을 파악해야한다. 즉, 설계를 개선할려면 객체간 &#39;의존성&#39;을 봐야한다.</li>
<li>조영호님은 반드시 의존성을 그려본다고 한다. 초반에는 일단 구현해보고, 설계를 개선하는것도 괜찮은 방법이다.</li>
</ul>
<h4 id="1-추상화">1) 추상화</h4>
<ul>
<li>보통 추상화를 떠올리면 추상 클래스나 인터페이스를 떠올리는데, 개발에서 추상화는 &#39;잘 안변하는 것이다&#39;</li>
<li>A, B 패키지가 있을 때 서로 양방향 의존성을 가진다면 추상 객체를 두어 의존성을 끊을 수 있다.</li>
<li>A -&gt; B 에 특정 객체를 참조할 때 A 패키지에 추상 객체를 두고 B에서 A의 추상 객체로 convert하는 메소드를 둔 후, B에서 A로 넘길 때 convert한 값을 넘겨 의존성을 끊을 수 있다.</li>
<li>여기선 OptionGroup, Option으로 (잘 변하지 않을) 추상화한다.<img src="https://velog.velcdn.com/images/youngmin-mo/post/c3cced6d-de1a-4c0b-8c11-0b1d1fe1f942/image.png" width = "80%"/>

</li>
</ul>
<h4 id="2-강한-결합의-경계">2) 강한 결합의 경계</h4>
<ul>
<li>ORM 상황의 강한 결합의 경우 &#39;어디까지 조회할 것인가&#39;에 대한 문제가 생긴다. 대표적으로 lazy 로딩문제가 있다.</li>
<li>도메인 규칙을 함께 적용해야하는 객체의 범위는? 에대한 궁금증도 생기고, 이는 곧 &#39;트랜잭션의 경계는 어디까지인가?&#39;의 문제로 이어진다. 즉, &quot;어떤 테이블에서 어떤 테이블까지 하나의 단위로 잠금할 것인가?&quot; 고민된다.</li>
<li>객체 참조는 어디로든 갈 수있어 &#39;편리함&#39;은 있지만 이는 곧 모든 것들을 연결하는 문제가 있다. 어느 객체라도 접근 가능하다고 생각한다. 따라서 필요한 경우가 아니라면 객체 참조는 모두 끊어버려야한다.</li>
</ul>
<h4 id="3-다른-도메인의-데이터-조회">3) 다른 도메인의 데이터 조회</h4>
<ul>
<li>A 도메인에서 B 도메인의 내용이 궁금할 때 B도메인을 강한 결합으로 가져오기 보단, 인터페이스를 통해 B에 접근한다. 좀 더 결합을 약하게한다.</li>
<li>이때, 다른 도메인의 디비에 직접 접근하기 보단, id를 통해 접근한다. 즉, 한방 쿼리를 도메인 마다 분리하여 비지니스 로직을 쿼리에 모두 담지 않는다.</li>
<li>그렇다면 어떤 객체들을 묶고, 어떤 객체들을 분리할 것인가?<ul>
<li>함께 생성되고, 변경되고, 삭제되는 객체끼리 묶는다.</li>
<li>도메인 제약사항을 공유하는 객체끼리 묶는다. (객체를 묶는 기준은 서비스마다 다르다.)</li>
<li>트랜잭션 안에 있는 것은 같이 변경되는 것들이 있어야한다.</li>
<li>즉, 객체를 분리한 단위가 트랜잭션 단위가 되고 조회 단위가 된다. 같은 경계안에 있는 객체끼리는 한 번에 조회가 되고 Lazy 로딩을 할 수 있다.</li>
</ul>
</li>
</ul>
<h4 id="4-event-driven">4) Event Driven</h4>
<ul>
<li>실시간이 꼭 필요한 경우가 아니라면 producer / consumer 구조를 가져가 작업을 큐에 넣고 대상자가 이를 가져가는 방식을 활용한다.</li>
<li>이 경우 producer와 consumer간 결합력이 굉장히 낮아지는 효과가 발생한다.</li>
</ul>
<h3 id="4-의존성과-시스템-분리">[4] 의존성과 시스템 분리</h3>
<ul>
<li>레이어 단위로 아키텍처를 꾸리면, 초기에는 간단하지만 시간이 지나면서 하나의 변경이 여러 레이어를 이동하며 수정할 수 있다.</li>
<li>도메인 단위로 아키텍처를 꾸리면, 페키지간(도메인간) 의존성 관리가 초기에 어려움이(영역 침범) 있으나 이를 지킬 경우 레이어 아키텍처보단 의존성 관리가 좀 더 수월해진다.</li>
<li>추가로, 도메인 단위로 분리할 경우 도메인(비지니스)끼리 비동기적인 메시지를 가지고 통신하는 경우가 많다. 만약 동기적으로 한다면 시스템이 한번에 다 터질 수 있는 위험이 있기 때문에, 약간의 delay를 허용한다면 굉장히 비동기 방식을 생각해보자.</li>
</ul>
<img src ="https://velog.velcdn.com/images/youngmin-mo/post/8ea883cd-2776-498b-9cea-c09467c201a2/image.png" width ="100%"/>

<blockquote>
<p>시스템을 볼 때는 의존성을 보고, 의존성에 따라 시스템을 진화시켜라!</p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>