<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jae_cheol.log</title>
        <link>https://velog.io/</link>
        <description>Hi</description>
        <lastBuildDate>Wed, 20 Apr 2022 14:52:30 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jae_cheol.log</title>
            <url>https://images.velog.io/images/jae_cheol/profile/f505f0ad-6c41-443a-9c69-40491da2ef98/social.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jae_cheol.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jae_cheol" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Aws Ubuntu EC2 볼륨 축소하기]]></title>
            <link>https://velog.io/@jae_cheol/Aws-Ubuntu-EC2-%EB%B3%BC%EB%A5%A8-%EC%B6%95%EC%86%8C%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jae_cheol/Aws-Ubuntu-EC2-%EB%B3%BC%EB%A5%A8-%EC%B6%95%EC%86%8C%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 20 Apr 2022 14:52:30 GMT</pubDate>
            <description><![CDATA[<h2 id="환경">환경</h2>
<ul>
<li>AWS EC2 Ubuntu</li>
<li>가용 영역 : 모든 볼륨의 가용 영역이 같아야 한다.</li>
</ul>
<h2 id="인스턴스준비">인스턴스(준비)</h2>
<ol>
<li><p>Origin 인스턴스(테스트용)</p>
<ul>
<li><p>간단히 MySQL을 설치하고 test db 생성 후 값을 넣어 두었다.</p>
<pre><code>create database test;
use test;
create table test(
id varchar(24),
text varchar(20)
);

select * from test;
insert into test values(&#39;1&#39;, &#39;1&#39;),(&#39;2&#39;, &#39;2&#39;),(&#39;3&#39;, &#39;3&#39;);</code></pre><pre><code>// 아래 명령어를 통해 해당 인스턴스가 ext인지 확인
df -TH</code></pre></li>
</ul>
</li>
<li><p>Resize 인스턴스</p>
<ul>
<li>축소할 볼륨을 추출하기 인스턴스이다.</li>
<li>볼륨을 생성하고 삭제한다.</li>
</ul>
</li>
<li><p>Temp 인스턴스 </p>
<ul>
<li>Origin의 볼륨의 데이터를 Resize 볼륨에 복사하기 위한 작업을 진행하기 위한 인스턴스이다.</li>
</ul>
</li>
</ol>
<h2 id="ec2-볼륨-축소-작업">EC2 볼륨 축소 작업</h2>
<ol>
<li><p>Origin 인스턴스(원본)를 준비 후 인스턴스 정지하고 볼륨을 추출한다.
(필자는 테스트로 진행하기 때문에 원본 인스턴스를 생성 후, Origin 인스턴스의 볼륨을 추출한다.)
(실제 사용하는 인스턴스를 사용할 경우 해당 인스턴스의 볼륨을 스냅샷을 생성 후, 생성된 스냅샷을 통해 볼륨을 생성한다.)</p>
</li>
<li><p>Resize 인스턴스를 생성 후 인스턴스 중지하고 볼륨 탭에 들어가서 Resize의 볼륨을 분리합니다.
<img src="https://velog.velcdn.com/images/jae_cheol/post/f39c19f9-e7ee-4ffe-a400-14b9c7261c5d/image.png" alt=""></p>
</li>
<li><p>Temp 인스턴스 생성 후 Origin, Resize 볼륨을 연결해준다. 
아래의 이미지는 Origin, Resize 볼륨을 연결 후 해당 인스턴스의 스토리지 탭에서 제대로 연결되었는지 확인할 수 있다.
<img src="https://velog.velcdn.com/images/jae_cheol/post/b3348273-70b1-44fa-9987-fa4c5b18d630/image.png" alt=""></p>
</li>
<li><p>Temp 인스턴스에 SSH 연결하고 lsblk를 통해 연결된 볼륨을 확인한다.
이때, xvdf 가 Origin 볼륨(30G), xvdg가 Resize 볼륨(20G)이다.</p>
<pre><code>lsblk</code></pre><p><img src="https://velog.velcdn.com/images/jae_cheol/post/01da12f2-98ac-44b3-ad52-82b1efe4bc69/image.png" alt="">
위의 이미지에서 MOUNTPOINT 가 작업할 파티션(xvdf1, xvdg1)에 &#39;/&#39;로 지정되어 있다면 잘못되었기 때문에 잘 체크해준다.!! (파일 시스템을 체크할 때 마운트되어있으면 안되기 때문이다.)
아래의 작업을 진행할 때 볼륨의 타입이 disk가 아닌 part(파티션)에 작업을 진행한다.</p>
</li>
<li><p>다음으로 e2fsck를 통해서 파일 시스템을 체크한다.</p>
<pre><code>e2fsck -f /dev/xvdf1</code></pre><p><img src="https://velog.velcdn.com/images/jae_cheol/post/0d4213e5-d322-4d11-866b-3430eba9aeff/image.png" alt=""></p>
</li>
<li><p>resize2fs 명령어를 통해 ext2/ext3/ext4 파일 시스템 크기를 최소한의 크기로 줄인다.</p>
<pre><code>resize2fs -M -p /dev/xvdf1</code></pre><blockquote>
<h3 id="resize2fs-옵션">resize2fs 옵션</h3>
<p> -M : 파일 시스템을 최소한의 크기로 줄임
-p : 실행이 완료되는 상태를 퍼센트 비율로 출력
<img src="https://velog.velcdn.com/images/jae_cheol/post/09382326-4d92-4ef0-a2c4-5e431d361381/image.png" alt="">
위의 이미지에서 빨간 박스는 4K단위의 블록의 974729개 이다.</p>
</blockquote>
</li>
</ol>
<ol start="7">
<li>6번의 결과 이미지에 빨간 박스 부분이 blockcount이다.
다음 작업으로 Resize 볼륨에 Origin 볼륨을 복사하기 위해서 dd 명령어를 통해 복사하기 위해 count를 계산해야 한다. 이때 bs=16M으로 진행한다. <blockquote>
<h3 id="계산식">계산식</h3>
<p>blockcount * 4 / (16 * 1024)
위의 공식대로 계산하면
974729 * 4 / (16 * 1024) = 237.97094...이 나오는데 올림해서 238이다.</p>
</blockquote>
</li>
</ol>
<ol start="8">
<li><p>이제 dd 명령어를 통해 블록단위로 디스크(Resize 볼륨)를 복사를 해준다.</p>
<pre><code>dd if=/dev/xvdf1 of=/dev/xvdg1 bs=16M count=238 status=progress
// bs는 한번에 읽고 쓸 사이즈를 의미하고, count는 횟수
// status=progress는 진행 상태를 표시</code></pre><p><img src="https://velog.velcdn.com/images/jae_cheol/post/6463282d-1f3b-46d5-9ab0-9ec0a1324274/image.png" alt=""></p>
</li>
<li><p>디스크 복사가 끝나면 Resize 볼륨의 파일 시스템 크기를 조절한다.</p>
<pre><code>resize2fs -p /dev/xvdg1</code></pre><p><img src="https://velog.velcdn.com/images/jae_cheol/post/1d3df1d3-38ca-4f4d-ac47-da6fdfa8c7b5/image.png" alt=""></p>
</li>
<li><p>e2fsck를 통해 파일 시스템을 체크한다. 파일 시스템 체크가 완료되면 Temp 인스턴스에서 수행해야 하는 작업이 완료되었다.</p>
<pre><code>e2fsck -f /dev/xvdg1</code></pre><p><img src="https://velog.velcdn.com/images/jae_cheol/post/530aed62-9134-473b-8c2c-f1cc0b85a71e/image.png" alt=""></p>
</li>
</ol>
<ol start="11">
<li><p>다시 aws console로 돌아가서 Temp 인스턴스를 중지하고, Origin, Resize 볼륨을 분리하고,
Origin 인스턴스에 Resize 볼륨을 연결시킨다.
이때, Origin 인스턴스가 루트 디바이스로 마운트 되어야하기 때문에 Origin 인스턴스의 루트 디바이스 이름과 동일하게 입력한다. 볼륨을 연결 후 Origin 인스턴스를 다시 시작한다.
<img src="https://velog.velcdn.com/images/jae_cheol/post/c49a3cb1-ab42-471e-804c-e5ad7321ded1/image.png" alt="">
Origin 인스턴스의 스토리지 탭에서 축소된 볼륨크기를 확인 할 수 있다.
<img src="https://velog.velcdn.com/images/jae_cheol/post/04879e55-a77e-4127-94df-817f106b63d9/image.png" alt=""></p>
</li>
<li><p>다시 MySQL에 접속하여 이전 볼륨의 데이터와 동일한지 한번 확인하자</p>
</li>
</ol>
<h3 id="참고">참고</h3>
<ul>
<li><a href="https://1mini2.tistory.com/90">https://1mini2.tistory.com/90</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[카프카 기본 개념]]></title>
            <link>https://velog.io/@jae_cheol/%EC%B9%B4%ED%94%84%EC%B9%B4-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@jae_cheol/%EC%B9%B4%ED%94%84%EC%B9%B4-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90</guid>
            <pubDate>Sat, 26 Feb 2022 08:39:10 GMT</pubDate>
            <description><![CDATA[<h1 id="카프카-브로커-클러스터-주키퍼">카프카 브로커, 클러스터, 주키퍼</h1>
<h2 id="카프카-브로커">카프카 브로커</h2>
<p>카프카 클라이언트와 데이터를 주고받기 위해 사용하는 주체이자 데이터를 분산 저장하여 장애가 발생하더라도 안전하게 사용할 수 있도록 도와주는 애플리케이션</p>
<ul>
<li><p>데이터 저장, 전송</p>
<ul>
<li>프로듀서로 부터 데이터를 전달받으면 카프카 브로커는 프로듀서가 요청한 토픽의 파티션에 데이터를 저장</li>
<li>컨슈머가 데이터를 요청하면 파티션에 저장된 데이터를 전달</li>
<li>카프카는 메모리나 데이터베이스에 저장하지 않으며 따로 캐시메모리를 구현하여 사용하지도 않는다.</li>
<li>카프카는 페이지 캐시를 사용하여 디스크 입출력 속도를 높였음</li>
<li>페이지 캐시란 OS에서 파일 입출력의 성능 향상을 위해 만들어 놓은 메모리 영역을 말한다.</li>
<li>한번 읽은 내용은 메모리의 페이지 캐시 영역에 저장시킨다.</li>
</ul>
</li>
<li><p>데이터 복제, 싱크</p>
<ul>
<li>데이터 복제는 카프카를 장애 허용 시스템으로 동작하도록 하는 원동력</li>
<li>파티션 단위로 이루어짐</li>
<li>복제된 파티션은 리더와 팔로워로 구성</li>
<li>프로듀서와 컨슈머에서 직접 통신하는 파티션은 리더, 나머지를 팔로워라 부름</li>
<li>운영할때 2이상의 복제 개수를 정하는 것이 중요</li>
</ul>
</li>
<li><p>컨트롤러</p>
<ul>
<li>클러스터의 다수 브로커 중 한대가 컨트롤러 역할을 함</li>
<li>다른 브로커들의 상태를 체크하고 브로커가 클러스터에서 빠지는 경우 해당 브로커에 존재하는 리더 파티션을 재분배함</li>
<li>컨트롤러역할을 하는 브로커가 장애가 생기면 다른 브로커가 컨트롤러 역할을 수행함</li>
</ul>
</li>
<li><p>데이터 삭제</p>
<ul>
<li>다른 컨슈머가 데이터를 가져가더라도 삭제되지 않음</li>
<li>컨슈머나 프로듀서가 데이터 삭제를 요청 할수 없음</li>
<li>오직 브로커만이 데이터 삭제 가능</li>
<li>삭제는 파일 단위로 이루어짐 = 이 단위를 로그 <strong>세그먼트</strong>라고 부름</li>
</ul>
</li>
<li><p>컨슈머 오프셋 저장</p>
<ul>
<li>컨슈머 그룹은 토픽이 특정 파티션으로부터 데이터를 가져가서 처리하고 이 파티션의 어느 레코드까지 가져갔는지 확인하기 위해 오프셋을 커밋함</li>
</ul>
</li>
<li><p>코디네이터</p>
<ul>
<li>클러스터의 다수 브로커 중 한대가 코디네이터 역할 수행</li>
<li>컨슈머 그룹의 상태를 체크하고 파티션을 컨슈머와 매칭되도록 분배하는 역할을 함</li>
<li>파티션을 컨슈머로 재할당하는 과정을 <strong>리밸런스</strong>라고 함</li>
</ul>
</li>
</ul>
<h2 id="주키퍼">주키퍼</h2>
<ul>
<li>카프카의 메타데이터를 관리하는데 사용</li>
<li>카프카 클러스터로 묶인 브로커들은 동일한 경로의 주키퍼 경로로 선언해야 같은 카프카 브로커 묶음이 됨</li>
<li>한개의 주키퍼에 다수의 카프카 클러스터를 연결해서 사용할 수 있음</li>
</ul>
<h2 id="토픽과-파티션">토픽과 파티션</h2>
<ul>
<li><p>토픽</p>
<ul>
<li>카프카에서 데이터를 구분하기 위해 사용하는 단위</li>
<li>토픽은 1개 이상의 피티션을 소유</li>
</ul>
</li>
<li><p>파티션</p>
<ul>
<li>프로듀서가 보낸 데이터들이 들어가 저장되는데 이 데이터를 <strong>레코드</strong>라 부름</li>
<li>카프카의 병렬처리의 핵심</li>
<li>그룹으로 묶인 컨슈머들이 레코드를 병렬로 처리할 수있도록 매칭됨</li>
<li>자료구조에서 접하는 큐와 비슷한 구조 FIFO</li>
<li>다만 큐는 데이터를 가져가면 삭제되지만 카프카는 삭제되지 않는다.</li>
</ul>
</li>
</ul>
<h2 id="레코드">레코드</h2>
<ul>
<li>타임스탬프, 메시지 키, 메시지 값, 오프셋, 헤더로 구성</li>
<li>메시지 카는 메시지 값을 순서대로 처리하거나 메시지 값의 종류를 나타내기 위해 사용</li>
<li>메시지 키를 사용하면 프로듀서가 토픽에 레코드를 전송할 때 메시지 키의 해시값을 토대로 파티션을 지정하게 됨</li>
<li>메시지 값에는 실질적으로 처리할 데이터가 들어있음</li>
<li>오프셋은 0이상의 숫자로 이루어져 있으면 직접 지정할 수 없다</li>
<li>헤더는 레코드의 추가적인 정보를 담는 메타데이터 저장소 용도로 사용</li>
<li>헤더는 키/값 형태로 데이터를 추가하여 레코드의 속성을 저장하여 컨슈머에 참조할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[카프카에 대해]]></title>
            <link>https://velog.io/@jae_cheol/%EC%B9%B4%ED%94%84%EC%B9%B4</link>
            <guid>https://velog.io/@jae_cheol/%EC%B9%B4%ED%94%84%EC%B9%B4</guid>
            <pubDate>Sat, 26 Feb 2022 07:56:16 GMT</pubDate>
            <description><![CDATA[<h1 id="카프카">카프카</h1>
<ul>
<li>카프카 내부에 데이터가 저장되는 파티션의 동작은 FIFIO방식의 큐 자료와 유사함</li>
<li>큐에 데이터를 보내는 것이 프로듀서</li>
<li>큐에서 데이터를 가져가는 것이 컨슈머</li>
<li>데이터 포멧은 제한 없음 =&gt; 직렬화, 역직렬화를 통해 ByteArray로 통신하기 때문</li>
<li>카프카는 상용환경에서는 최소 3대 이상의 서버에서 부산 운영하여 프로듀서를 통해 전송받은 데이터를 파일 시스템에 안전하게 기록함. </li>
<li>서버 3대 이상으로 이루어진 카프카 클러스터 중 일부 서버에 장애가 발생하더라도 데이터를 지속적으로 복제하기 때문에 안전하게 운영이 가능</li>
<li>데이터를 묶음 단위로 처리하는 배치 전송을 통해 낮은 지연과 높은 데이터 처리량도 가지게 됨.
<img src="https://images.velog.io/images/jae_cheol/post/0ee8cf89-7d28-4049-afc3-8e8fd2fceb9a/22C24FA8-93CB-40B7-9B94-8E46D8CA7570.jpeg" alt=""></li>
</ul>
<h1 id="빅데이터-파이프라인에서-카프카의-역할">빅데이터 파이프라인에서 카프카의 역할</h1>
<ul>
<li>데이터 레이크 : 데이터가 모이는 저장 공간</li>
<li>높은 처리량<ul>
<li>카프카는 프로듀서가 브로커로 데이터를 보낼 때와 컨슈머가 브로커로부터 데이터를 받을 때 모두 묶어서 전송</li>
<li>많은 양의 데이터를 묶음 단위로 처리하는 배치도 빠르게 처리할 수 있기 때문에 대용량의 실시간 로그데이터를 처리하는 데에 적합하다.</li>
</ul>
</li>
<li>확장성<ul>
<li>가변적인 환경에서 안정적으로 확장 가능하도록 설계되었음</li>
<li>스케일 아웃, 이케일 인</li>
<li>무중단 운영</li>
</ul>
</li>
<li>영속성<ul>
<li>데이터를 생성한 프로그램이 종료되더라도 사라지지 않은 데이터 특성을 뜻함</li>
<li>전송받은 데이터를 메모리가 아닌 파일 시스템에 저장</li>
<li>I/O 성능 향상을 위해 페이지 캐시 영역을 메모리에 따로 생성하여 사용</li>
<li>페이지 캐시 메모리 영역을 사용하여 한번 읽은 파일 내용은 메모리에 저장시켰다가 다시 사용하는 방식이기에 철히량이 높다</li>
</ul>
</li>
<li>고가용성<ul>
<li>3개 이상의 서버들로 운영되는 카프카 클러스터는 일부 서버가 장애가 발생하더라도 무중단으로 안전하고 지속적으로 데이터를 처리할 수 있음</li>
<li>프로듀서로 부터 전송받은 데이터를 다른 서버에서 데이터를 복제함</li>
</ul>
</li>
</ul>
<h2 id="왜-3대-이상의-브로커들로-구성해야하는가">왜 3대 이상의 브로커들로 구성해야하는가?</h2>
<ul>
<li>1대<ul>
<li>테스트 목적으로 사용</li>
</ul>
</li>
<li>2대<ul>
<li>브로커 간에 데이터가 복제되는 시간차이로 인해 일부 데이터가 유실될 가능성이 있음.</li>
</ul>
</li>
<li>3대<ul>
<li>3개 중 1개의 브로커가 장애가 나더라도 지속적으로 데이터를 처리 가능</li>
</ul>
</li>
</ul>
<h1 id="데이터-레이크-아키텍처와-카프카의-미래">데이터 레이크 아키텍처와 카프카의 미래</h1>
<p>데이터레이크 아키텍처는 람다아키텍처와 카파 아키텍처가 있다</p>
<ul>
<li>람다 아키텍처<ul>
<li>레거시 데이터 수집 플랫폼을 개선하기 위해 구성한 아키텍처</li>
<li>3가지 레이어로 나뉨<ul>
<li>배치 레이어 : 배치 데이터를 모아서 특정 시간, 타이밍마다 일괄 처리</li>
<li>서빙 레이어 : 가공된 데이터를 데이터 사용자, 서비스 애플리케이션에서 사용할 수 있도록 데이터가 저장된 공간</li>
<li>스피드 레이어 : 서비스에서 생성되는 원천 데이터를 실시간으로 분석하는 용도로 사용</li>
</ul>
</li>
</ul>
</li>
<li>카파 아키텍처<ul>
<li>람다 아키텍처의 단점을 해소하기 위함</li>
<li>람다 아키텍처와 유사하지만 배치 레이어를 제거하고 모든 데이터를 스피드 레이어에 넣어서 처리함.</li>
<li>로그는 배치 데이터를 스트림으로 표현하기 적합</li>
<li>배치 데이터를 로그로 표현헐 때는 각 시점의 배치 데이터의 변환기록을 시간 순서대로 기록함으로써  각 시점의 모든 스냅샷 데이터를 저장하지 않고도 배치 데이터를 표현할 수 있게 됨</li>
</ul>
</li>
</ul>
<h2 id="배치데이터와-스트림-데이터">배치데이터와 스트림 데이터</h2>
<ul>
<li><p>배치데이터</p>
<ul>
<li>초, 분, 시간, 일 등으로 한정된 기간 단위 데이터</li>
<li>데이터를 일괄 처리하는 것이 특징</li>
</ul>
</li>
<li><p>스트림 데이터</p>
<ul>
<li>한정되지 않은 데이터로 시작 데이터와 끝 데이터가 명확히 정해지지 않은 데이터</li>
<li>사용자의 클릭 로그, 주식 정보, 사물인터넷의 센서 데이터를 스트림 데이터라 볼 수 있음</li>
</ul>
</li>
<li><p>데이터 레이크</p>
<ul>
<li>카파 아키텍처에서 서빙 레이어를 제거한 아키텍처</li>
<li></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[젠킨스 블루오션]]></title>
            <link>https://velog.io/@jae_cheol/%EC%A0%A0%ED%82%A8%EC%8A%A4-%EB%B8%94%EB%A3%A8%EC%98%A4%EC%85%98</link>
            <guid>https://velog.io/@jae_cheol/%EC%A0%A0%ED%82%A8%EC%8A%A4-%EB%B8%94%EB%A3%A8%EC%98%A4%EC%85%98</guid>
            <pubDate>Sat, 06 Nov 2021 14:21:05 GMT</pubDate>
            <description><![CDATA[<h2 id="젠킨스-블루오션">젠킨스 블루오션</h2>
<ul>
<li>젠킨스의 주요 애플리케이션에 대한 UI 보조 기능<ul>
<li>향상된 시각화</li>
<li>파이프라인 에디터</li>
<li>개인화</li>
<li>깃과 깃허브를 위한 쉽고 빠른 파이프라인 설정 마법사</li>
</ul>
</li>
</ul>
<h2 id="설치">설치</h2>
<ul>
<li>Jenkins 관리 &gt; 플러그인 관리 &gt; 설치가능(Available) &gt; Blue Ocean검색 후 설치</li>
<li>설치가 끝나면 대시보드에 블루오션이 생긴것을 볼 수 있다.
<img src="https://images.velog.io/images/jae_cheol/post/57259230-62b3-41e8-8f2f-f8c640e9278b/image.png" alt=""></li>
</ul>
<h2 id="블루오션-살펴보기">블루오션 살펴보기</h2>
<ol>
<li>블루오션 대시보드
<img src="https://images.velog.io/images/jae_cheol/post/ecdd33d9-10e8-4633-925f-2c190e00b780/image.png" alt=""></li>
<li>멀티브랜치 파이프라인
<img src="https://images.velog.io/images/jae_cheol/post/5cedf25c-46fe-4935-bb9e-ee7dda56de74/image.png" alt=""></li>
</ol>
<h2 id="블루오션에서-파이프라인-생성">블루오션에서 파이프라인 생성</h2>
<ul>
<li>블루오션 대시보드에서 새로운 파이프라인 버튼을 클릭하고 단계에 따라 진행하면 된다.
아래 이미지에서는 토큰 입력이 없지만 있다면 토큰 생성 링크 클릭 후 생성하면 된다.
<img src="https://images.velog.io/images/jae_cheol/post/013f7300-9c90-4ef2-accb-9b105f38f80a/image.png" alt="">
위 이미지처럼 선택 후 파이프라인 생성 클릭하면 다음 이미지와 같다.
<img src="https://images.velog.io/images/jae_cheol/post/b64f2280-4a52-40cb-a5a7-dbad92df398a/image.png" alt="">
에이전트 부분을 다음과 같이 선택하고 입력한다.
<img src="https://images.velog.io/images/jae_cheol/post/0bc8c6f9-5424-44a1-bbd4-a633c5460696/image.png" alt="">
다음으로 가운데 + 버튼을 클릭하면 Build Stage를 만들 수 있다. 스테이지 이름 입력칸에 Build라고 작성 후 스텝 추가 버튼 클릭
<img src="https://images.velog.io/images/jae_cheol/post/dcfee143-b22d-42ff-b395-2efa1d9cbc54/image.png" alt="">
provide를 검색해서 Provide Maven environment 선택
<img src="https://images.velog.io/images/jae_cheol/post/a2af972b-0a91-4dcd-b0d6-85b71362330e/image.png" alt="">
Maven 영역에 M3 입력 후 나머지는 그대로 놔둔다.
<img src="https://images.velog.io/images/jae_cheol/post/327febea-ea16-472b-8899-63961cf19355/image.png" alt="">
스텝 추가 클릭 후 Shell Script 선택 후 mvn clean install 입력
<img src="https://images.velog.io/images/jae_cheol/post/594612c4-fe3b-442c-b524-26493ac11fa7/image.png" alt="">
그리고 뒤로가기 화살표를 클릭해 이전 메뉴로 돌아가자(아래 이미지는 Build 스탭 다 입력 시 아래와 같음)
<img src="https://images.velog.io/images/jae_cheol/post/361df112-6c2c-417b-87f4-09ad09858a31/image.png" alt="">
다음 Build 다음의 + 버튼을 클릭하고 Results라고 입력한다
<img src="https://images.velog.io/images/jae_cheol/post/4c15c1a4-a84d-49c5-8cf8-2280a46ed397/image.png" alt="">
스텝 추가 후 Junit 검색 후 Archive JUnit-formatted test results 선택 하고 TestResults 칸에 **/target/surefire-reports/TEST-<em>.xml 입력한다.
<img src="https://images.velog.io/images/jae_cheol/post/c748346d-1ccf-42ba-8527-d52786b25f96/image.png" alt="">
뒤로가기 클릭 후 스텝 추가 클릭 후 archive 검색 &gt; Archive the artifacts선택 &gt; Artifacts 영역에 target/</em>.jar 작성
<img src="https://images.velog.io/images/jae_cheol/post/a716953b-bf68-49e1-ad03-c8e48ac4892d/image.png" alt="">
뒤로가기 클릭 후 우측 상단 저장버튼 클릭 
<img src="https://images.velog.io/images/jae_cheol/post/4f426079-2885-44e8-8ea9-36b025346f33/image.png" alt="">
<img src="https://images.velog.io/images/jae_cheol/post/95a73056-0441-4825-ab75-55e86a51a714/image.png" alt="">
Save &amp; run 버튼 클릭하면 레포지토리에 Jenkinsfile이 생성된 것을 확인 할 수있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[멀티브랜치 파이프라인]]></title>
            <link>https://velog.io/@jae_cheol/%EB%A9%80%ED%8B%B0%EB%B8%8C%EB%9E%9C%EC%B9%98-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8</link>
            <guid>https://velog.io/@jae_cheol/%EB%A9%80%ED%8B%B0%EB%B8%8C%EB%9E%9C%EC%B9%98-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8</guid>
            <pubDate>Sat, 06 Nov 2021 13:02:50 GMT</pubDate>
            <description><![CDATA[<ul>
<li>사용자가 소스코드 저장소의 모든 브랜치에 대해 파이프라인을 자동으로 생성하게 해준다.</li>
<li>깃이나 깃허브 저장소의 브랜치 중 어떤 곳에서 변경이 발생하면 자동으로 빌드를 시작시키기 위해 설계됐다.</li>
</ul>
<h2 id="1-깃허브-인증을-젠킨스에-추가">1. 깃허브 인증을 젠킨스에 추가</h2>
<ul>
<li>Credentials &gt; System &gt; Global credentials<ol>
<li>Add Credentials 클릭
<img src="https://images.velog.io/images/jae_cheol/post/da9c4ed8-93d2-4f63-8ead-3ec19ddcf223/image.png" alt=""></li>
<li>Kind &gt; Secret text 선택
<img src="https://images.velog.io/images/jae_cheol/post/e73a3481-7434-4725-9323-355cabd1cf87/image.png" alt=""></li>
<li>위처럼 값 입력 후 OK 클릭<br></li>
</ol>
</li>
<li>깃허브에서 시크릿 키 발급<ol>
<li>Setting &gt; Developer settings &gt; Personal access tokens</li>
<li>다음과 같이 체크박스 체크 후 발급받기
<img src="https://images.velog.io/images/jae_cheol/post/dead5ac7-c54b-4cd0-9c00-d646bd9ae7e5/image.png" alt=""></li>
</ol>
</li>
</ul>
<h2 id="2-젠킨스에-깃허브-webhooks-설정">2. 젠킨스에 깃허브 Webhooks 설정</h2>
<ul>
<li>Jenkins 관리 &gt; 시스템 설정 &gt; GitHub &gt; Add GitHub Server
Credentials 부분은 위에서 만든 Credentials를 선택하고 테스트 버튼 클릭
<img src="https://images.velog.io/images/jae_cheol/post/3ef84820-d57f-404d-aefa-84e29a74fd68/image.png" alt=""></li>
<li>테스트가 성공하면 저장 버튼 클릭</li>
</ul>
<h2 id="3-새로운-깃허브-저장소-만들기">3. 새로운 깃허브 저장소 만들기</h2>
<ul>
<li><a href="https://github.com/jglick/simple-maven-project-with-tests">https://github.com/jglick/simple-maven-project-with-tests</a></li>
<li>위의 저장소를 Fork 할것임!!
<img src="https://images.velog.io/images/jae_cheol/post/14d40bff-7276-435a-be48-5cdc59044449/image.png" alt=""></li>
</ul>
<h2 id="4-jenkinsfiles-이용하기">4. Jenkinsfiles 이용하기</h2>
<ul>
<li>위 저장소에서 포크한 레포지토리에서 Jenkinsfiles를 다음과 같이 작성 후 커밋!<pre><code>node(&#39;master&#39;) {
checkout scm
stage(&#39;Build&#39;) {
  withMaven(maven: &#39;M3&#39;){
    if(isUnix()){
      sh &#39;mvn -Dmaven.test.failure.ignore clean package&#39;
    }
    else {
      bat &#39;mvn -Dmaven.test.failure.ignore clean package&#39;
    }
  }
}
stage(&#39;Results&#39;) {
  junit &#39;**/target/surefire-reports/TEST-*.xml&#39;
  archive &#39;target/*.jar&#39;
}
}</code></pre></li>
</ul>
<h2 id="5-젠킨스에서-멀티브랜치-파이프라인-생성하기">5. 젠킨스에서 멀티브랜치 파이프라인 생성하기</h2>
<ul>
<li>대시보드 &gt; New Item &gt; multibranch-pipeline 선택
<img src="https://images.velog.io/images/jae_cheol/post/09f9e866-764e-45b8-9823-28966e74b77d/image.png" alt=""></li>
<li>Branch Sources 탭 클릭 &gt; Add Source 클릭 &gt; Github을 선택하면 아래 이미지처럼 새로운 영역이 생김
<img src="https://images.velog.io/images/jae_cheol/post/b9c081a9-9662-4478-ac87-2d52b4f52335/image.png" alt=""></li>
<li>Credentials 는 만들어 둔것을 선택하고
(Secret text는 목록에서 안뜸. 이거를 해결해야할 듯.. 그래야 7번 테스트가 잘될듯함.) 
Repository는 아래 이미지처럼 입력 및 선택한다
<img src="https://images.velog.io/images/jae_cheol/post/22261822-c926-4e74-8c45-ac9401ef8451/image.png" alt=""></li>
<li>저장 클릭!</li>
</ul>
<h2 id="6-webhooks-재등록">6. Webhooks 재등록</h2>
<ol>
<li>Jenkins 관리 &gt; 시스템 설정 &gt; Github 영역에서 두번째 고급 버튼 클릭
<img src="https://images.velog.io/images/jae_cheol/post/ad60b720-87ad-46b9-9574-6f7c8ff2a44d/image.png" alt=""></li>
<li>Re-register hooks for all jobs 버튼 클릭
<img src="https://images.velog.io/images/jae_cheol/post/21468c8f-9435-4f93-9ce0-aa67490d8e80/image.png" alt=""></li>
<li>해당 레포지토리 &gt; Settings &gt; Webhooks 탭
아래의 이미지처럼  웹훅스가 등록된 것을 볼 수 있다.
<img src="https://images.velog.io/images/jae_cheol/post/a756b1c5-67aa-426b-922c-8053a26bd319/image.png" alt=""></li>
</ol>
<h2 id="7-멀티브랜치-파이프라인-테스트">7. 멀티브랜치 파이프라인 테스트</h2>
<ul>
<li>레포지토리에서 feature라는 브랜치 생성하면 젠킨스 파이프라인은 즉시 실행함.
<img src="https://images.velog.io/images/jae_cheol/post/9aea3cad-2233-4248-93d7-ba5b649f0702/image.png" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[서술적 파이프라인 문법]]></title>
            <link>https://velog.io/@jae_cheol/%EC%84%9C%EC%88%A0%EC%A0%81-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%EB%AC%B8%EB%B2%95</link>
            <guid>https://velog.io/@jae_cheol/%EC%84%9C%EC%88%A0%EC%A0%81-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%EB%AC%B8%EB%B2%95</guid>
            <pubDate>Sat, 30 Oct 2021 14:07:47 GMT</pubDate>
            <description><![CDATA[<h2 id="기본-구조">기본 구조</h2>
<h3 id="1-노드-블록">1. 노드 블록</h3>
<ul>
<li>스테이지 블록, 디렉티브, 스텝이 실행될 젠킨스 에이전트를 정의한다.<pre><code>node(&#39;&lt;parameter&gt;&#39;) {&lt;constituents&gt;}</code></pre><h3 id="2-스테이지-블록">2. 스테이지 블록</h3>
</li>
<li>스테이지 블록은 같은 목적을 가진 스텝과 디렉티브의 모음이다.<pre><code>stage(&#39;&lt;parameter&gt;&#39;) {&lt;constituents&gt;}</code></pre><h3 id="3-디렉티브">3. 디렉티브</h3>
</li>
<li>디렉티브의 가장 큰 목적은 환경 변수, 옵션, 파라미터, 트리거, 툴을 제공해 노드 블록과 스테이지 블록, 스텝을 지원하는 것이다.</li>
</ul>
<h3 id="4-스텝">4. 스텝</h3>
<ul>
<li><p>스텝은 서술적 파이프라인을 구성하는 가장 중요한 요소</p>
</li>
<li><p>젠킨스에서 무엇을 할지 명령을 내리는 것</p>
<pre><code>node(&#39;master&#39;) {
  // Directive 1
  def mvnHome

  // Stage block 1
  stage(&#39;Preparation&#39;) { 
      // Step 1
      git &#39;https://github.com/jglick/simple-maven-project-with-tests.git&#39;
      // Directive 2
      mvnHome = tool &#39;M3&#39;
  }

  // Staage block 2
  stage(&#39;Build&#39;) {
      // Step 2
      withEnv([&quot;MVN_HOME=$mvnHome&quot;]) {
          if (isUnix()) {
              sh &#39;&quot;$MVN_HOME/bin/mvn&quot; -Dmaven.test.failure.ignore clean package&#39;
          } else {
              bat(/&quot;%MVN_HOME%\bin\mvn&quot; -Dmaven.test.failure.ignore clean package/)
          }
      }
  }

  // Stage block 3
  stage(&#39;Results&#39;) {
      // Step 3
      junit &#39;**/target/surefire-reports/TEST-*.xml&#39;
      // Step 4
      archiveArtifacts &#39;target/*.jar&#39;
  }
}</code></pre></li>
<li><p>any를 파라미터로 사용하면 모든 스테이지 노드와 스텝, 디렉티브는 임의의 젠킨스 슬레이브 중 하나에서 수행된다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[젠킨스 파이프라인 잡 만들기]]></title>
            <link>https://velog.io/@jae_cheol/%EC%A0%A0%ED%82%A8%EC%8A%A4-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%EC%9E%A1-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@jae_cheol/%EC%A0%A0%ED%82%A8%EC%8A%A4-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%EC%9E%A1-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Sat, 30 Oct 2021 12:22:00 GMT</pubDate>
            <description><![CDATA[<ol>
<li><p>New Item 클릭</p>
</li>
<li><p>다음과 같이 파이프라인 입력후 OK 클릭
<img src="https://images.velog.io/images/jae_cheol/post/825a0a03-9669-46a1-9d04-04068c2a5550/image.png" alt=""></p>
</li>
<li><p>파이프라인 탭을 클릭 후 스크립투 부분에 try sample pipeline이라는 셀랙트 박스에서 Scripted Pipleline 선택 후 저장!!
<img src="https://images.velog.io/images/jae_cheol/post/a6505f6b-79a2-489e-8902-d8cfeaec2ff2/image.png" alt=""></p>
<ul>
<li>간단히 스크립트
node {} 영역 : 메인 컨테이너로 젠킨스 마스터에서 파이프라인 스크립트 영역 전체를 실행하라는 의미
stage(&#39;Preparation&#39;) : 파이프라인을 실행하기 전에 이 단계를 수행해야 한다.
stage(&#39;Build&#39;) : 프로젝트를 빌드함.
stage(&#39;Results&#39;) : 빌드 결과물을 Junit테스트 결과와 함께 묶어낸다.</li>
</ul>
</li>
<li><p>파이프라인을 실행하기 전에 전역 도구 환결 설정에서 파이프라인에서 사용될 도구를 설정해야함 (ex) 깃, 메이블, 자바, 등등)
위치 : Jenkins 관리 -&gt; Global Tool Configuration 로 이동 후 다음과 같이 적용
<img src="https://images.velog.io/images/jae_cheol/post/05e31cd0-0a80-4072-8499-ce6203c785bf/image.png" alt=""></p>
</li>
<li><p>젠킨스 대시보드로 가면 다음과 같이 처음에 만든 파이프라인이 리스트에 나온다
<img src="https://images.velog.io/images/jae_cheol/post/78e6d24b-1a7a-4709-8072-904625e8d871/image.png" alt="">
위의 이미지처럼 빨간 박스부분을 클릭하면 파이프라인이 실행 된다!!</p>
</li>
<li><p>5번의 이미지에서 해당 파이프라인 이름을 클릭하면 해당 파이프라인상세보기로 간다
<img src="https://images.velog.io/images/jae_cheol/post/1ac64631-393d-487f-b9b1-65845556a080/image.png" alt=""></p>
</li>
<li><p>빌드한 콘솔을 보기 위해서 빌드 히스토리에 마우스를 올리면 다음과 같이 드롭다운 메뉴가 나타남
<img src="https://images.velog.io/images/jae_cheol/post/58e5260f-f58d-47bf-87ba-9ecc941f676b/image.png" alt="">
<img src="https://images.velog.io/images/jae_cheol/post/30f22c04-4688-4b13-bc32-3c3055ace013/image.png" alt=""></p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Jenkins-EC2 도커에서 젠킨스 실행하기]]></title>
            <link>https://velog.io/@jae_cheol/Jenkins-EC2-%EB%8F%84%EC%BB%A4%EC%97%90%EC%84%9C-%EC%A0%A0%ED%82%A8%EC%8A%A4-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jae_cheol/Jenkins-EC2-%EB%8F%84%EC%BB%A4%EC%97%90%EC%84%9C-%EC%A0%A0%ED%82%A8%EC%8A%A4-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 25 Oct 2021 12:11:57 GMT</pubDate>
            <description><![CDATA[<pre><code> chmod 400 /Users/lee/Desktop/jenkins.pem
  ssh -i ~/Desktop/jenkins.pem ubuntu@[IP Address]</code></pre><ol>
<li><p>apt가 저장소를 사용할 수 있게 함</p>
<pre><code>sudo apt-get install apt-transport-https ca-certificates</code></pre></li>
<li><p>도커 공식 GPG 키 등록</p>
<pre><code>curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -</code></pre><p><img src="https://images.velog.io/images/jae_cheol/post/33d72c3d-f7a0-410b-9a65-99481e3eb1d3/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-25%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%208.27.48.png" alt=""></p>
</li>
<li><p>공식 저장소 추가</p>
<pre><code>sudo add-apt-repository &quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable&quot;</code></pre></li>
<li><p>apt 업데이트</p>
<pre><code>sudo apt update</code></pre></li>
<li><p>도커 설치</p>
<pre><code>sudo apt-get install docker-ce</code></pre></li>
<li><p>도커 확인</p>
<pre><code>sudo docker info</code></pre><p><img src="https://images.velog.io/images/jae_cheol/post/c31ce9fe-1ad7-4c1a-b98e-cd4cc3567a0d/image.png" alt=""></p>
</li>
<li><p>젠킨스 설치 및 실행</p>
<pre><code>docker run -d --name jenkins_dev -p 8080:8080  jenkins/jenkins:lts</code></pre></li>
<li><p>젠킨스 컨테이너 접속 </p>
<pre><code>docker exec -it jenkins_dev bash</code></pre></li>
</ol>
<ul>
<li>[IP ADDRESS]:8080 접속 시 초기 패스워드 위치를 가르쳐줌
<img src="https://images.velog.io/images/jae_cheol/post/16184340-d49a-440c-be3d-f41e86ad75f1/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-25%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%208.45.42.png" alt=""></li>
</ul>
<ol start="9">
<li>젠킨스 초기 비밀번호<pre><code>cat /var/jenkins_home/secrets/initialAdminPassword</code></pre><img src="https://images.velog.io/images/jae_cheol/post/396dfc6f-2651-4e7a-ad3d-2f4aaf4e760a/image.png" alt=""></li>
</ol>
<h2 id="데이터-볼륨을-이용한-젠킨스-컨테이너-실행">데이터 볼륨을 이용한 젠킨스 컨테이너 실행</h2>
<ul>
<li>컨테이너를 삭제하게 되면 jenkins_home 폴더도 같이 삭제가 된다.</li>
<li>데이터 볼륨을 이용해 젠킨스를 도커 위에서 수행하는데 더 좋은 방식이 있음.</li>
<li>데이터 볼륨이란? 데이터가 컨테이너의 라이프 사이클에 상관없이 영구적으로 저장하는 특정 폴더이다.</li>
</ul>
<ol>
<li>젠킨스 컨테이너 실행<pre><code>docker run -d --name jenkins_prod -p 8080:8080 -p 50000:50000 -v jenkins-home-prod:/var/jenkins_home jenkins/jenkins:lts</code></pre></li>
</ol>
<ul>
<li>-v jenkins-home-prod:/var/jenkins_home 옵션은 jenkins-home-prod라는 이름으로 데이터 볼륨을 만들어 /var/jenkins_home 폴더에 연결한다.</li>
</ul>
<ol start="2">
<li><p>jenkins_prod 컨테이너 내의 /var/jenkins_home 폴더의 내용을 보기 위해 다음 명령어를 실행</p>
<pre><code>docker exec -it jenkins_prod ls -lrt /var/jenkins_home</code></pre><p><img src="https://images.velog.io/images/jae_cheol/post/112ef278-cef3-4a3b-bd55-1ef687ff1d22/image.png" alt=""></p>
</li>
<li><p>도커 볼륨 목록 확인</p>
<pre><code>docker volume ls</code></pre><p><img src="https://images.velog.io/images/jae_cheol/post/3c494191-273d-4a1f-8a4b-00dc35c20e69/image.png" alt=""></p>
</li>
<li><p>이제 영구적인 jenkins_home 폴더를 가진 젠킨스 도커 컨테이너가 생성됬음.</p>
</li>
<li><p>도커 젠킨스에 접속 후 최소 비밀번호를 가져온다</p>
<pre><code>docker exec -it jenkins_prod bash
cat /var/jenkins_home/secrets/initialAdminPassword</code></pre><p><img src="https://images.velog.io/images/jae_cheol/post/555cba8d-f29e-4052-89e4-96c2fe652881/image.png" alt=""></p>
</li>
<li><p>초기 접속시 화면
<img src="https://images.velog.io/images/jae_cheol/post/fcc4dc69-53ba-45b0-928a-46343cf61f17/image.png" alt=""></p>
</li>
</ol>
<ul>
<li>위에서 확인한 비밀번호 입력 후 Continue 클릭</li>
</ul>
<ol start="7">
<li>다음 페이지로 Custimize Jenkins 화면이 나옴(Install suggested plugins 클릭) 
<img src="https://images.velog.io/images/jae_cheol/post/779dcf3d-5f93-4b3a-8491-38e0922d8009/image.png" alt=""></li>
<li>위 과정에서 설치가 끝나면 어드민 계정을 만들라는 페이지가 나옴
<img src="https://images.velog.io/images/jae_cheol/post/33ac945c-b759-4a5e-9765-30d3d7dfbf22/image.png" alt=""></li>
<li>다음 페이지로 그냥 Save And Finish 버튼 클릭
<img src="https://images.velog.io/images/jae_cheol/post/9a59fe9d-e96d-4518-ba76-6ae4ba14084f/image.png" alt=""></li>
<li>환경 설정 끝!!</li>
<li>/var/jenkins_home/users 폴더내에 모든 사용자 정보가 있음</li>
<li>이제 jenkins_prod 컨테이너를 삭제해보자<pre><code>docker kill jenkins_prod
docker rm jenkins_prod</code></pre></li>
<li>도커 목록확인<pre><code>docker ps -a</code></pre><img src="https://images.velog.io/images/jae_cheol/post/88557bb6-9081-4e72-9521-4b7fc2c8b53f/image.png" alt=""></li>
</ol>
<ul>
<li>컨테이너가 존재하지 않는 것을 볼 수 있음.</li>
</ul>
<ol start="14">
<li>볼륨 확인
<img src="https://images.velog.io/images/jae_cheol/post/179fa0d1-67ab-4ed3-8075-67b52b564846/image.png" alt=""></li>
</ol>
<ul>
<li>컨테이너는 삭제됬지만 볼륨은 남아있다.</li>
</ul>
<ol start="15">
<li>jenkins-home-prod 볼륨을 사용하는 새로운 젠킨스 컨테이너를 생성하자<pre><code>docker run -d --name jenkins_prod -p 8080:8080 -p 50000:50000 -v jenkins-home-prod:/var/jenkins_home jenkins/jenkins:lts</code></pre></li>
<li>다시 해당 페이지로 접속을 해보면 세팅페이지가 아닌 로그인페이지로 이동을 한다.
<img src="https://images.velog.io/images/jae_cheol/post/516b81ee-e702-4b59-af06-cb3b21de127a/image.png" alt=""></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker]]></title>
            <link>https://velog.io/@jae_cheol/Docker</link>
            <guid>https://velog.io/@jae_cheol/Docker</guid>
            <pubDate>Sun, 24 Oct 2021 08:38:51 GMT</pubDate>
            <description><![CDATA[<h2 id="이미지-관련">이미지 관련</h2>
<h3 id="이미지-검색">이미지 검색</h3>
<pre><code>docker search [IMAGE NAME]</code></pre><h3 id="이미지-다운로드">이미지 다운로드</h3>
<pre><code>docker pull [IMAGE NAME]:[TAG]</code></pre><h3 id="이미지-확인">이미지 확인</h3>
<pre><code>docker images</code></pre><h3 id="이미지-삭제">이미지 삭제</h3>
<pre><code>docker rmi [IMAGE NAME] -f(사용 중인 이미지 일 경우 -f를 붙이면 지워짐)</code></pre><h3 id="이미지-정보-확인">이미지 정보 확인</h3>
<pre><code>docker inspect [IMAGE NAME]</code></pre><h3 id="이미지-저장소-위치-확인">이미지 저장소 위치 확인</h3>
<pre><code>docker info</code></pre><h3 id="도커-용량-확인">도커 용량 확인</h3>
<pre><code>du -sh /var/lib/docker/ #도커가 설치된 환경 용량 확인
명령어 실행 결과 : 2.0G /var/lib/docker/

du -sh /var/lib/docker/image/ # 도커 이미지에 대한 정보 저장 디렉토리
명령어 실행 결과 : 2.7M /var/lib/docker/image/

du -sh /var/lib/docker/overlay2/ # 도커 이미지의 파일 시스템이 사용되는 실제 디렉토리
명령어 실행 결과 : 2.0G /var/lib/docker/overlay2/

du -sh /var/lib/docker/containers/ # 도커 컨테이너 정보 저장 디렉토리
명령어 실행 결과 : 136K /var/lib/docker/containers/</code></pre><ul>
<li>image 안에는 imageDB와 LayerDB가 있는데</li>
</ul>
<p>imageDB는 LayerDB에 대한 정보를 가지고 있고 LayerDB는 overlay2라는 정보를 가지고 있음
overlay2라는 변경사항 내용은 /l에 저장되어 있음.</p>
<h2 id="컨테이너-관련">컨테이너 관련</h2>
<h3 id="다운로드한-컨테이너-생성">다운로드한 컨테이너 생성</h3>
<pre><code>docker create -p 80:80 --name [CONTAINER NAME][IMAGE]</code></pre><h3 id="컨테이너-생성-및-실행">컨테이너 생성 및 실행</h3>
<pre><code>docker run [options] image[:TAG|@DIGEST] [COMMAND] [ARG...]</code></pre><ul>
<li>옵션</li>
</ul>
<table>
<thead>
<tr>
<th align="center">옵션</th>
<th align="left">설명</th>
</tr>
</thead>
<tbody><tr>
<td align="center">-d</td>
<td align="left">detached mode 흔히 말하는 백그라운드 모드</td>
</tr>
<tr>
<td align="center">-p</td>
<td align="left">호스트와 컨테이너의 포트를 연결 (포워딩)</td>
</tr>
<tr>
<td align="center">-v</td>
<td align="left">호스트와 컨테이너의 디렉토리를 연결 (마운트)</td>
</tr>
<tr>
<td align="center">-e</td>
<td align="left">컨테이너 내에서 사용할 환경변수 설정</td>
</tr>
<tr>
<td align="center">--name</td>
<td align="left">컨테이너 이름 설정</td>
</tr>
<tr>
<td align="center">--it</td>
<td align="left">-i와 -t를 동시에 사용한 것으로 터미널 입력을 위한 옵션 (컨테이너의 표준 입력과 로컬 컴퓨터의 키보드 입력을 연결)</td>
</tr>
<tr>
<td align="center">--rm</td>
<td align="left">프로세스 종료시 컨테이너 자동 제거</td>
</tr>
<tr>
<td align="center">--link</td>
<td align="left">nk    컨테이너 연결 [컨테이너 명:별칭]</td>
</tr>
</tbody></table>
<h3 id="컨테이너-시작">컨테이너 시작</h3>
<pre><code>docker start [컨테이너 id 또는 name]</code></pre><h3 id="컨테이너-재시작">컨테이너 재시작</h3>
<pre><code>docker restart [컨테이너 id 또는 name]</code></pre><h3 id="컨테이너-접속">컨테이너 접속</h3>
<pre><code>docker attach [컨테이너 id 또는 name]</code></pre><h3 id="컨테이너-정지">컨테이너 정지</h3>
<pre><code>docker stop [컨테이너 id 또는 name]</code></pre><h3 id="컨테이너-삭제">컨테이너 삭제</h3>
<pre><code>docker rm [컨테이너 id 또는 name]</code></pre><h3 id="실행중인-컨테이너-확인">실행중인 컨테이너 확인</h3>
<pre><code>docker ps (-a를 주면 모든 컨테이너 확인)</code></pre><h2 id="유용하게-쓰이는-명령어image--tomcat">유용하게 쓰이는 명령어(image : tomcat)</h2>
<h3 id="포트포워딩으로-실행">포트포워딩으로 실행</h3>
<pre><code>docker run -d --name tc -p 80:8080 tomcat</code></pre><h3 id="컨테이너-내부-셸-실행">컨테이너 내부 셸 실행</h3>
<pre><code>docker exec -it [container name] /bin/bash</code></pre><h3 id="컨테이너-로그-확인">컨테이너 로그 확인</h3>
<pre><code>docker logs tc</code></pre><h3 id="호스트-및-컨테이너-간-파일-복사">호스트 및 컨테이너 간 파일 복사</h3>
<pre><code>docker cp &lt;path&gt; &lt;to container&gt;:&lt;path&gt;
docker cp test.txt tc:/

docker cp &lt;from container&gt;:&lt;path&gt; &lt;path&gt;
docker cp tc:/test.txt ./test.txt

docker cp &lt;from container&gt;:&lt;path&gt; &lt;to container&gt;:&lt;path&gt;</code></pre><h3 id="도커-컨테이너-모두-삭제">도커 컨테이너 모두 삭제</h3>
<pre><code>docker stop `docker ps -a -q`
docker rm `docker ps -a -q`</code></pre><h3 id="임시-컨테이너-생성">임시 컨테이너 생성</h3>
<pre><code>docker run -d -p 80:8080 —rm --name tc tomcat</code></pre><h2 id="빌드">빌드</h2>
<ol>
<li>이미지 내려받기<pre><code>docker pull ubuntu</code></pre></li>
<li>도커 파일 생성<pre><code>mkdir test
cd test
vi dockerfile</code></pre></li>
</ol>
<ul>
<li>아래 코드 작성<pre><code>FROM ubuntu:latest [이미지 명]
</code></pre></li>
</ul>
<p>RUN apt-get update [명령어 실행, 이미지를 만들 때 실행]
RUN apt-get install -y net-tools [명령어 실행]
RUN apt-get install -y nano [명령어 실행]</p>
<p>CMD [&quot;echo&quot;, &quot;image created”] [명령을 실행, 컨테이너가 실행 될 때 실행]</p>
<pre><code>3. 빌드 하기</code></pre><p>docker build -t my-ubuntu .</p>
<pre><code>
4. 이미지 확인</code></pre><p>docker images</p>
<pre><code>5. 실행</code></pre><p>docker run -it my-ubuntu bash</p>
<pre><code>
## 도커 이미지 푸시</code></pre><p>docker login
docker tag my-ubuntu [docker id]/my-ubuntu[:tag] ex) :v2.0   (대괄호는 뺴고 작성)
docker images
docker push [docker id]/my-ubuntu</p>
<pre><code>## 이미지 히스토리 확인</code></pre><p>docker history [docker id]/my-ubuntu﻿</p>
<pre><code></code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Prometheus + Grafana]]></title>
            <link>https://velog.io/@jae_cheol/Prometheus-Grafana</link>
            <guid>https://velog.io/@jae_cheol/Prometheus-Grafana</guid>
            <pubDate>Sun, 24 Oct 2021 06:20:44 GMT</pubDate>
            <description><![CDATA[<h2 id="prometheus">Prometheus</h2>
<ul>
<li>Metrics를 수집하고 모니터링 및 알람에 사용되는 오픈소스 애플리케이션</li>
<li>Pull 방식의 구조와 다양한 Metrics Exporter 제공</li>
<li>시계열 DB에 Metrics 저장 -&gt; 조회 가능(쿼리)</li>
</ul>
<h3 id="간단히-사용해보기">간단히 사용해보기</h3>
<ol>
<li><p>설치 아래링크에서  os에 맞게 설치</p>
<ul>
<li><a href="https://prometheus.io/download/">https://prometheus.io/download/</a> </li>
</ul>
</li>
<li><p>압축 풀기</p>
<ul>
<li>tar xvzf prometheus-2.31.0-rc.0.darwin-amd64.tar.gz</li>
</ul>
</li>
<li><p>prometheus.yml 수정 (scrape_configs 아래에 다음 내용 추가)</p>
<pre><code>scrape_configs:
# The job name is added as a label `job=&lt;job_name&gt;` to any timeseries scraped from this config.
...

- job_name: &quot;user-service&quot;
 scrape_interval: 15s
 metrics_path: &quot;/user-service/actuator/prometheus&quot;
 static_configs:
   - targets: [&quot;localhost:8000&quot;]

- job_name: &quot;apigateway-service&quot;
 scrape_interval: 15s
 metrics_path: &quot;/actuator/prometheus&quot;
 static_configs:
   - targets: [&quot;localhost:8000&quot;]

- job_name: &quot;order-service&quot;
 scrape_interval: 15s
 metrics_path: &quot;/order-service/actuator/prometheus&quot;
 static_configs:
   - targets: [&quot;localhost:8000&quot;]</code></pre></li>
<li><p>실행 (포트 9090)</p>
<ul>
<li>./prometheus --config.file=prometheus.yml
<img src="https://images.velog.io/images/jae_cheol/post/711d991b-ee18-4fdd-947d-0e4d37e3a87e/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-24%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%202.52.55.png" alt="">실행 시 대시보드에서 http_server_requests_seconds_count를 검색</li>
</ul>
</li>
</ol>
<h2 id="grafana">Grafana</h2>
<ul>
<li>데이터 시각화, 모니터링 및 분석을 위한 오프소스 애플리케이션</li>
<li>시계열 데이터를 시각화하기 위한 대시보드 제공</li>
</ul>
<h3 id="간단히-사용해보기-1">간단히 사용해보기</h3>
<ol>
<li>설치
<a href="https://grafana.com/grafana/download?edition=oss&amp;pg=get&amp;plcmt=selfmanaged-box1-cta1&amp;platform=mac">https://grafana.com/grafana/download?edition=oss&amp;pg=get&amp;plcmt=selfmanaged-box1-cta1&amp;platform=mac</a></li>
<li>실행 (포트 3000, 계정/비밀번호 : admin/admin) 
./bin/grafana-server</li>
<li>접속 후 Configuration에서 Prometheus 추가</li>
<li>Create -&gt; import 로 DashBoard생성 
<a href="https://grafana.com/grafana/dashboards/?orderBy=downloads&amp;direction=desc">https://grafana.com/grafana/dashboards/?orderBy=downloads&amp;direction=desc</a></li>
<li>위의 링크에서 맞는 대시보드를 찾아서 id값을 임포트한다</li>
<li>기본설정과 다른 값들을 다 맞게 바꿔준다
ex) gateway_requests_seconds_count -&gt; spring_cloud_gateway_requests_seconds_count
or
서비스명 변경하면 됨</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Micrometer]]></title>
            <link>https://velog.io/@jae_cheol/Micrometer</link>
            <guid>https://velog.io/@jae_cheol/Micrometer</guid>
            <pubDate>Sun, 24 Oct 2021 05:15:43 GMT</pubDate>
            <description><![CDATA[<h2 id="turbine-server">Turbine Server</h2>
<ul>
<li>마이크로 서비스에 설치된 Hystrix 클라이언트의 스트림을 통합<ul>
<li>마이크로 서비스에서 생성되는 Hystrix 클라이언트 스트림메시지를 터빈 서버로 수집</li>
</ul>
</li>
</ul>
<h2 id="hystrix-dashboard">Hystrix Dashboard</h2>
<ul>
<li>Hystrix 클라이언트에서 생성하는 스트림을 시각화 (Web Dashboard)</li>
<li>Hystrix Dashboard / Turbine -&gt; Micrometer + Monitoring System</li>
</ul>
<h2 id="micrometer">Micrometer</h2>
<ul>
<li><a href="https://micrometer.io/">https://micrometer.io/</a></li>
<li>JVM 기반의 애플리케이션의 Metrics 제공</li>
<li>Prometheus등의 다양한 모니터링 시스템 지원</li>
</ul>
<h2 id="timer">Timer</h2>
<ul>
<li>짧은 지연시간, 이벤트의 사용 빈도를 측정</li>
<li>시계열로 이벤트의 시간, 호출 빈도 등을 제공</li>
<li>@Timed 제공</li>
</ul>
<h2 id="사용해보기">사용해보기</h2>
<ul>
<li><p>pom.xml</p>
<pre><code class="language-xml">      &lt;dependency&gt;
          &lt;groupId&gt;io.micrometer&lt;/groupId&gt;
          &lt;artifactId&gt;micrometer-registry-prometheus&lt;/artifactId&gt;
      &lt;/dependency&gt;</code></pre>
</li>
<li><p>application.yml (info, metrics, prometheus 추가)</p>
<pre><code>management:
endpoints:
  web:
    exposure:
      include: refresh, health, beans, busrefresh, info, metrics, prometheus</code></pre></li>
<li><p>controller (@Timed 추가)</p>
<pre><code>@GetMapping(&quot;/health_check&quot;)
@Timed(value = &quot;users.status&quot;, longTask = true)
public String status(){
  return String.format(&quot;It&#39;s Working in User Service&quot;
    + &quot;, port(local.server.port)=&quot; + env.getProperty(&quot;local.server.port&quot;)
    + &quot;, port(server.port)=&quot; + env.getProperty(&quot;server.port&quot;)
    + &quot;, token secret=&quot; + env.getProperty(&quot;token.secret&quot;)
    + &quot;, token expiration time=&quot; + env.getProperty(&quot;token.expiration_time&quot;)
  );
}
@GetMapping(&quot;/welcome&quot;)
@Timed(value = &quot;users.welcome&quot;, longTask = true)
public String welcome(){
//    return env.getProperty(&quot;greeting.message&quot;);
  return greeting.getMessage();
}</code></pre></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Zipkin - 분산 추적]]></title>
            <link>https://velog.io/@jae_cheol/Zipkin-%EB%B6%84%EC%82%B0-%EC%B6%94%EC%A0%81</link>
            <guid>https://velog.io/@jae_cheol/Zipkin-%EB%B6%84%EC%82%B0-%EC%B6%94%EC%A0%81</guid>
            <pubDate>Sat, 23 Oct 2021 06:14:30 GMT</pubDate>
            <description><![CDATA[<ul>
<li><a href="https://zipkin.io/">https://zipkin.io/</a></li>
<li>분산 환경에서의 시스템 병목 현상 파악</li>
<li>Twitter에서 사용하는 분산 환경의 Timing 데이터 수집, 추적 시스템(오픈 소스)</li>
<li>Collector, Query Service, Databasem WebUi 로 구성</li>
<li>Span<ul>
<li>하나의 요청에 사용되는 작업의 단위</li>
<li>64bit unique ID</li>
</ul>
</li>
<li>Trace<ul>
<li>트리 구조로 이루어진 Span 셋</li>
<li>하나의 요청에 대한 같은 Trace ID 발급
<img src="https://images.velog.io/images/jae_cheol/post/d6b20d95-0718-421d-b192-8ff8ee8d1b94/image.png" alt=""></li>
</ul>
</li>
</ul>
<h2 id="spring-cloud-sleuth">Spring Cloud Sleuth</h2>
<ul>
<li>스프링 부트 애플리케이션을 Zipkin과 연동</li>
<li>요청 값에 따른 TraceID, SpanId 부여</li>
<li>Trace와 Span Ids를 로그에 추가 가능<ul>
<li>servlet filter</li>
<li>rest templete</li>
<li>scheduled actions</li>
<li>message channels</li>
<li>feign client
<img src="https://images.velog.io/images/jae_cheol/post/02beda4b-63e8-439f-bf2e-31053c2ea4f7/image.png" alt=""></li>
</ul>
</li>
</ul>
<h2 id="설치">설치</h2>
<ul>
<li><a href="https://zipkin.io/pages/quickstart.html">https://zipkin.io/pages/quickstart.html</a><pre><code>curl -sSL https://zipkin.io/quickstart.sh | bash -s
java -jar zipkin.jar</code></pre></li>
<li><a href="http://127.0.0.1:9411/">http://127.0.0.1:9411/</a>
<img src="https://images.velog.io/images/jae_cheol/post/f85884a5-65bc-46f6-961f-1cb252f1b479/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-23%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%203.14.23.png" alt=""></li>
</ul>
<h2 id="사용해보기">사용해보기</h2>
<h3 id="user-service">user-service</h3>
<ul>
<li>pom.xml<pre><code>      &lt;dependency&gt;
          &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
          &lt;artifactId&gt;spring-cloud-starter-sleuth&lt;/artifactId&gt;
      &lt;/dependency&gt;
      &lt;dependency&gt;
          &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
          &lt;artifactId&gt;spring-cloud-starter-zipkin&lt;/artifactId&gt;
      &lt;/dependency&gt;</code></pre></li>
<li>application.yml<pre><code>spring:
zipkin:
  base-url: http://127.0.0.1:9411
  enabled: true
sleuth:
  sampler:
    probability: 1.0
</code></pre></li>
</ul>
<pre><code></code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[CircuitBreaker]]></title>
            <link>https://velog.io/@jae_cheol/CircuitBreaker</link>
            <guid>https://velog.io/@jae_cheol/CircuitBreaker</guid>
            <pubDate>Sat, 23 Oct 2021 04:10:43 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p>장애가 발생하는 서비스에 반복적인 호출이 되지 못하게 차단</p>
</li>
<li><p>특정 서비스가 정상적으로 동작하지 않을 경우 다른 기능으로 대체 수행 -&gt; 장애 회피
<img src="https://images.velog.io/images/jae_cheol/post/032b7e41-9bab-4714-be2b-3435d0b5303d/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-23%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2012.57.35.png" alt="">
<img src="https://images.velog.io/images/jae_cheol/post/bf12849e-1b31-4a2c-bfce-e8bdbd56d9de/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-23%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%2012.59.06.png" alt=""></p>
</li>
<li><p>Hystrix -&gt; Resilience4j 로 대체</p>
</li>
</ul>
<h2 id="resilience4j">Resilience4j</h2>
<ul>
<li>resilience4j-circuitbreaker: Circuit breaking</li>
<li>resilience4j-ratelimiter: Rate limiting</li>
<li>resilience4j-bulkhead: Bulkheading</li>
<li>resilience4j-retry: Automatic retrying (sync and async)</li>
<li>resilience4j-timelimiter: Timeout handling</li>
<li>resilience4j-cache: Result caching</li>
</ul>
<h2 id="간단히-사용해보기">간단히 사용해보기</h2>
<ul>
<li><p>pom.xml</p>
<pre><code>      &lt;dependency&gt;
          &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
          &lt;artifactId&gt;spring-cloud-starter-circuitbreaker-resilience4j&lt;/artifactId&gt;
      &lt;/dependency&gt;</code></pre></li>
<li><p>user-service getUserByUserId() 함수 order-service 호출하는 부분</p>
<pre><code>
private final CircuitBreakerFactory circuitBreakerFactory;

...
public UserDto getUserByUserId() {
...
</code></pre></li>
</ul>
<p>// 주석
//  List<ResponseOrder> orderList = orderServiceClient.getOrders(userId); </p>
<p>  CircuitBreaker circuitBreaker = circuitBreakerFactory.create(&quot;circuitbreaker&quot;);
  List<ResponseOrder> orderList = circuitBreaker.run(() -&gt; orderServiceClient.getOrders(userId),
            throwable -&gt; new ArrayList&lt;&gt;());</p>
<p>...
}</p>
<pre><code>
## Custom 해보기</code></pre><p>package com.example.userservice.config;</p>
<p>import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.timelimiter.TimeLimiterConfig;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JCircuitBreakerFactory;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JConfigBuilder;
import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;</p>
<p>import java.time.Duration;</p>
<p>@Configuration
public class Resilience4jConfig {
  @Bean
  public Customizer<Resilience4JCircuitBreakerFactory> globalCustomConfiguration() {
    CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
      .failureRateThreshold(4)
      .waitDurationInOpenState(Duration.ofMillis(1000))
      .slidingWindowType(CircuitBreakerConfig.SlidingWindowType.COUNT_BASED)
      .slidingWindowSize(2)
      .build();</p>
<pre><code>TimeLimiterConfig timeLimiterConfig = TimeLimiterConfig.custom()
  .timeoutDuration(Duration.ofSeconds(4))
  .build();

return factory -&gt; factory.configureDefault(id -&gt; new Resilience4JConfigBuilder(id)
  .timeLimiterConfig(timeLimiterConfig)
  .circuitBreakerConfig(circuitBreakerConfig)
  .build()
);</code></pre><p>  }
}</p>
<p>```
<img src="https://images.velog.io/images/jae_cheol/post/4e2479eb-2ed7-443f-8fa8-9375a6f97341/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-23%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%201.09.44.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[kafka 사용해보기]]></title>
            <link>https://velog.io/@jae_cheol/kafka-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@jae_cheol/kafka-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sun, 17 Oct 2021 06:09:36 GMT</pubDate>
            <description><![CDATA[<h2 id="catalog-service-consumer">Catalog-Service (Consumer)</h2>
<ul>
<li><p>pom.xml</p>
<pre><code class="language-xml">      &lt;dependency&gt;
          &lt;groupId&gt;org.springframework.kafka&lt;/groupId&gt;
          &lt;artifactId&gt;spring-kafka&lt;/artifactId&gt;
      &lt;/dependency&gt;</code></pre>
</li>
<li><p>KafkaConsumerConfig</p>
<pre><code class="language-java">@EnableKafka
@Configuration
public class KafkaConsumerConfig {

// 접속 정보
@Bean
public ConsumerFactory&lt;String, String&gt; consumerFactory(){
  Map&lt;String, Object&gt; properties = new HashMap&lt;&gt;();

  properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, &quot;127.0.0.1:9092&quot;);
  properties.put(ConsumerConfig.GROUP_ID_CONFIG, &quot;consumerGroupId&quot;);
  /*
    그룹 아이디는 카프카에서 토픽에 쌓여있는 메세지를 가져가는 Consumer 들을 그룹핑할 수 있음.
    나중에 여러개의 Consumer가 데이터를 가져갈 때 특정한 Consumer 그룹을 만들어 놓고 전달하고자 하는 그룹을 지정할 수 있음.
   */
  properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
  properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);

</code></pre>
</li>
</ul>
<pre><code>return new DefaultKafkaConsumerFactory&lt;&gt;(properties);</code></pre><p>  }</p>
<p>  @Bean
  public ConcurrentKafkaListenerContainerFactory&lt;String, String&gt; kafkaListenerContainerFactory(){
    ConcurrentKafkaListenerContainerFactory&lt;String, String&gt; kafkaListenerContainerFactory
      = new ConcurrentKafkaListenerContainerFactory&lt;&gt;();</p>
<pre><code>kafkaListenerContainerFactory.setConsumerFactory(consumerFactory());

return kafkaListenerContainerFactory;</code></pre><p>  }</p>
<p>}</p>
<pre><code>
- KafkaConsumer
```java
// 리스너를 이용해서 데이터를 가져오고 그 데이터를 데이터베이스에 업데이트 할려 함
@Slf4j
@Service
@RequiredArgsConstructor
public class KafkaConsumer {
  private final CatalogRepository catalogRepository;

  @KafkaListener(topics = &quot;example-catalog-topic&quot;)
  public void updateQty(String kafkaMessage) {
    log.info(&quot;Kafka Message : -&gt; {}&quot;, kafkaMessage);

    Map&lt;Object, Object&gt; map = new HashMap&lt;&gt;();
    ObjectMapper mapper = new ObjectMapper();

    try {
      map = mapper.readValue(kafkaMessage, new TypeReference&lt;Map&lt;Object, Object&gt;&gt;() {});
    }catch (JsonProcessingException e){
      e.printStackTrace();;
    }
    CatalogEntity entity = catalogRepository.findByProductId((String)map.get(&quot;productId&quot;));

    if(entity != null){
      entity.setStock(entity.getStock() - (Integer) map.get(&quot;qty&quot;));
      catalogRepository.save(entity);
    }
  }
}</code></pre><br>

<h2 id="order-serviceproducer">Order-Service(Producer)</h2>
<ul>
<li><p>pom.xml 동일</p>
</li>
<li><p>KafkaProducerConfig</p>
<pre><code class="language-java">@EnableKafka
@Configuration
public class KafkaProducerConfig {

// 접속 정보
@Bean
public ProducerFactory&lt;String, String&gt; producerFactory(){
  Map&lt;String, Object&gt; properties = new HashMap&lt;&gt;();

  properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, &quot;127.0.0.1:9092&quot;);
  properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
  properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);

  return new DefaultKafkaProducerFactory&lt;&gt;(properties);
}

@Bean
public KafkaTemplate&lt;String, String&gt; kafkaTemplate(){
  return new KafkaTemplate&lt;&gt;(producerFactory());
}
</code></pre>
</li>
</ul>
<p>}</p>
<pre><code>- KafkaProducer
```java
@Slf4j
@Service
@RequiredArgsConstructor
public class KafkaProducer {
  private final KafkaTemplate&lt;String, String&gt; kafkaTemplate;

  public OrderDto send(String topic, OrderDto orderDto) {
    ObjectMapper mapper = new ObjectMapper();
    String jsonInString = &quot;&quot;;

    try {
      jsonInString = mapper.writeValueAsString(orderDto);
    } catch (JsonProcessingException e) {
      e.printStackTrace();
    }

    kafkaTemplate.send(topic, jsonInString);
    log.info(&quot;Kafka Producer sent data from Order Micro Service : {}&quot;, orderDto);

    return orderDto;
  }
}</code></pre><h2 id="order-service-가-두-개-이상일-경우">Order-Service 가 두 개 이상일 경우</h2>
<ul>
<li>각각의 오더 서비스가 한번씩 실행(라운드 로빈)을 한다. -&gt; 데이터가 동기화 되지 않는다</li>
<li>Order Service에 요청된 주문 정보를 DB가 아니라 Kafka의 Topic으로 전송하고, Topic에 설정된 Kafka Sink Connect를 사용해 단일 DB에 저장</li>
</ul>
<ul>
<li>Field<pre><code class="language-java">@Data
@AllArgsConstructor
public class Field {
private String type;
private boolean optional;
private String field;
}</code></pre>
</li>
<li>Schema<pre><code class="language-java">@Data
@Builder
public class Schema {
private String type;
private List&lt;Field&gt; fields;
private boolean optional;
private String name;
}</code></pre>
</li>
<li>Payload<pre><code class="language-java">@Data
@Builder
public class Payload {
private String order_id;
private String user_id;
private String product_id;
private int qty;
private int unit_price;
private int total_price;
}</code></pre>
</li>
<li>KafkaOrderDto<pre><code class="language-java">@Data
@AllArgsConstructor
public class KafkaOrderDto implements Serializable {
private Schema schema;
private Payload payload;
}
</code></pre>
</li>
</ul>
<pre><code>- OrderProducer
```java

@Slf4j
@Service
@RequiredArgsConstructor
public class OrderProducer {
  private final KafkaTemplate&lt;String, String&gt; kafkaTemplate;

  List&lt;Field&gt; fields = Arrays .asList(
    new Field(&quot;string&quot;, true, &quot;order_id&quot;),
    new Field(&quot;string&quot;, true, &quot;user_id&quot;),
    new Field(&quot;string&quot;, true, &quot;product_id&quot;),
    new Field(&quot;int32&quot;, true, &quot;qty&quot;),
    new Field(&quot;int32&quot;, true, &quot;unit_price&quot;),
    new Field(&quot;int32&quot;, true, &quot;total_price&quot;)
  );
  Schema schema = Schema.builder()
                        .type(&quot;struct&quot;)
                        .fields(fields)
                        .optional(false)
                        .name(&quot;orders&quot;)
                        .build();

  public OrderDto send(String topic, OrderDto orderDto) {
    Payload payload = Payload.builder()
                              .order_id(orderDto.getOrderId())
                              .user_id(orderDto.getUserId())
                              .product_id(orderDto.getProductId())
                              .qty(orderDto.getQty())
                              .unit_price(orderDto.getUnitPrice())
                              .total_price(orderDto.getTotalPrice())
                              .build();

    KafkaOrderDto kafkaOrderDto = new KafkaOrderDto(schema, payload);

    ObjectMapper mapper = new ObjectMapper();
    String jsonInString = &quot;&quot;;

    try {
      jsonInString = mapper.writeValueAsString(kafkaOrderDto);
    } catch (JsonProcessingException e) {
      e.printStackTrace();
    }

    kafkaTemplate.send(topic, jsonInString);
    log.info(&quot;Order Producer sent data from Order Micro Service : {}&quot;, kafkaOrderDto);

    return orderDto;
  }
}</code></pre><hr>
<h2 id="결론">결론</h2>
<ul>
<li>Order-Service 하고 Catalog-Service는 각각의 데이터를 사용하면서 수량 부분에 대해서 동기화시켜 주는 부분을 Kafka Topic을 사용</li>
<li>Order-Service가 두개 이상일 때 디비가 서로 달라서 데이터가 동기화 되지 않는다. 
그래서 주문이 들어오면 카프카로 메세지를 보내고 카프카에 저장된(토픽에 저장되어 있는) 값을 
단일 데이터베이스로 읽어 오기 위해서싱크 커낵트를 연결 해서 데이터를 동기화 했음</li>
<li>응용해서 확장하고 싶으면<ul>
<li>데이터베이스에 저장되는 메시지 큐잉 서버를 이벤트 소싱이라고 해서 데이터가 전달되는 부분을 저장하는 파트와 저장된 데이터를 읽어오는 파트를 두개로 분리해서 만드는 cqrs라는 패턴을 이용하면 좀더 효율적으로 메시지징 기반의 시스템을 이용하고 시간 순서에 의해 메시지가 기록된 것을 데이터베이스에 업데이트구현할 수 있음</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[해시 조인]]></title>
            <link>https://velog.io/@jae_cheol/%ED%95%B4%EC%8B%9C-%EC%A1%B0%EC%9D%B8-ufp183ou</link>
            <guid>https://velog.io/@jae_cheol/%ED%95%B4%EC%8B%9C-%EC%A1%B0%EC%9D%B8-ufp183ou</guid>
            <pubDate>Sat, 16 Oct 2021 16:03:25 GMT</pubDate>
            <description><![CDATA[<ul>
<li>트 머지 조인은 항상 양쪽 테이블을 정렬하는 부담이 있는데, 해시 조인은 그런 부담도 없다.</li>
</ul>
<h2 id="기본-메커니즘">기본 메커니즘</h2>
<pre><code class="language-sql">select /* ordered use_hash(c) */
    e.사원번호, e.사원명, e.입사일자
    c.고객번호 , c.고객명, c.전화번호, c.최종주문금액
from 사원 e, 고객 c
where c.관리사원번호 = e.사원번호
and e.입사일자 &gt;= &#39;19960101&#39;
and e.부서코드 = &#39;Z123&#39;
and c.최종주문금액 &gt;= 20000</code></pre>
<ol>
<li><p>Build 단계</p>
<ul>
<li>작은 쪽 테이블을 읽어 해시 테이블을 생성한다.<blockquote>
<p>아래 조건에 해당하는 사원 데이터를 읽어 해시 테이블을 생성한다.
사원번호를 해시 함수에 입력해서 반환된 값으로 해시 체인을 찾고, 그 해시 체인에 데이터를 연결한다.
해시 테이블은 PGA 영역에 할당된 Hash Area에 저장한다.</p>
<pre><code class="language-sql">select e.사원번호, e.사원명, e.입사일자
from 사원 e
where e.입사일자 &gt;= &#39;19960101&#39;
and e.부서코드 = &#39;Z123&#39;</code></pre>
</blockquote>
</li>
</ul>
</li>
<li><p>Probe 단계</p>
<ul>
<li>큰 쪽 테이블을 읽어 해시 테이블을 탐색하면서 조인한다.  <blockquote>
<ul>
<li>아래 조건에 해당하는 고객 데이터를 하나씩 읽어 앞서 생성한 해시 테이블을 탐색한다.</li>
<li>즉, 관리사원번호를 해시 함수에 입력해서 반환된 값으로 해시 체인을 찾고, 그 해시 체인을 스캔해서 값이 같은 사원번호를 찾는다.<pre><code class="language-sql">select c.고객번호 , c.고객명, c.전화번호, c.최종주문금액, c.관리사원번호
from 고객 c
where c.최종주문금액 &gt;= 20000</code></pre>
</li>
<li>Build 단계에서 사용한 해시 함수를 Probe 단계에서도 사용하므로 같은 사원번호를 입력하면 같은 해시 값을 반환한다.</li>
<li>NL 조인과 다르지 않다.</li>
</ul>
</blockquote>
</li>
</ul>
</li>
</ol>
<ul>
<li>use_hash 힌트 사용</li>
</ul>
<h2 id="해시-조인이-빠른-이유">해시 조인이 빠른 이유</h2>
<ul>
<li>Hash Area에 생성한 해시 테이블을 이용한다는 점만 다를 뿐 해시 조인도 조인 프로세싱 자체는 NL 조인과 같다.</li>
<li>NL 조인보다 빠른 이유<ul>
<li>해시 테이블을 PGA 영역에 할당하기 때문이다.</li>
<li>래치 획득 과정 없이 PGA에서 빠르게 데이터를 탐색하고 조인한다.</li>
</ul>
</li>
<li>해시 조인과 소트 머지 조인의 성능 차이는 조인 오퍼레이션을 시작하기 전, 사전 준비 작업에 기인한다.<ul>
<li>소트 머지 조인에서 사전 준비작업은 &#39;양쪽&#39; 집합을 모두 정렬해서 PGA에 담는 작업</li>
<li>해시 조인에서 사전 준비작업은 양쪽 집합 중 어느 &#39;한쪽&#39;을 읽어 해시 맵을 만드는 작업</li>
</ul>
</li>
<li>해시 조인은 NL조인처럼 조인 과정에서 발생하는 랜덤 액세스 부하가 없고, 소트 머지 조인처럼 양쪽 집합을 미리 정렬하는 부하도 없다.</li>
</ul>
<h2 id="대용량-buil-input-처리">대용량 Buil Input 처리</h2>
<ul>
<li>분할 정복 방식</li>
</ul>
<ol>
<li>파티션 단계<ul>
<li>조인하는 양쪽 집합(조인 이외 조건절을 만족하는 레코드)의 조인 컬럼에 해시 함수를 적용하고, 반환된 해시 값에 따라 동적으로 파티셔닝한다.</li>
<li>독립적으로 처리할 수 있는 여러 개의 작은 서브 집합으로 분할함으로써 파티션 짝을 생성하는 단계다.</li>
</ul>
</li>
<li>조인 단계<ul>
<li>파티션 단계를 완료하면 각 파티션 짝에 대해 하나씩 조인을 수행한다.</li>
<li>각각에 대한 Build Input과 Probe Input은 독립적으로 결정된다.</li>
<li>각 파티션 짝별로 작은 쪽을 Build Input으로 선택하고 해시 테이블을 생성한다.</li>
<li>해시 테이블을 생성하고 나면 반대쪽 파티션 로우를 하나씩 읽으면서 해시 테이블을 탐색한다. </li>
<li>모든 파티션 짝에 대한 처리를 마칠 때까지 이 과정을 반복한다.</li>
</ul>
</li>
</ol>
<h2 id="해시-조인-실행꼐획-제어">해시 조인 실행꼐획 제어</h2>
<ul>
<li>해시 조인 실행계획을 제어할 때 <strong>use_hash</strong> 를 사용</li>
<li><strong>use_hash</strong> 만 사용할 경우 옵티마이저가 Build Input을 선택하는데, 일반적으로 둘 중 카디널리티가 작은 테이블을 선택한다.</li>
<li>조인 대상 테이블이 두 개라면 <strong>leading</strong>이나 <strong>ordered</strong> 힌트를 사용하여 Build Input을 직접 선택할 수 있다.</li>
<li><strong>swap_join_inputs</strong> 힌트로 Build Input을 명시적으로 선택할 수도 있다.<h3 id="세-개-이상-테이블-해시-조인">세 개 이상 테이블 해시 조인</h3>
</li>
<li>조인하는 테이블이 몇 개든, 조인 연결고리를 따라 순방향 또는 역방향으로 leading 힌트에 기술한 후, Build Input으로 선택하고 싶은 테이블을 swap_join_inputs 힌트에 지정해 주면 된다.</li>
<li>Build Input으로 선택하고 싶은 테이블이 조인된 결과 집합이어서 swap_join_inputs 힌트로 지정하기 어렵다면, no_swap_join_inputs 힌트로 반대쪽 Probe Input을 선택해 주면 된다.</li>
</ul>
<h2 id="조인-메소드-선택-기준">조인 메소드 선택 기준</h2>
<ul>
<li><p>일반적인 조인 메소드 선택 기준</p>
<blockquote>
<p>소량 데이터 조인할 때 : NL 조인
대량 데이터 조인할 때 : 해시 조인
대량 데이터 조인인데 해시 조인으로 처리할 수 없을 때, 즉 조인 조건식이 등치 조건이 아닐 때 : 소트 머지 조인</p>
</blockquote>
</li>
<li><p>수행빈도가 매우 높은 쿼리에 대한 기준</p>
<blockquote>
<p>(최적화된) NL 조인과 해시 조인 성능이 같으면: NL 조인
해시 조인이 약간 더 빨라도: NL 조인
NL 조인보다 해시 조인이 매우 빠른 경우: 해시 조인</p>
</blockquote>
</li>
<li><p>왜 NL 조인을 선택해야 할까?</p>
<blockquote>
<p>NL 조인에 사용하는 인덱스는 영구적으로 유지하면서 다양한 쿼리를 위해 공유 및 재사용하는 자료구조다.
반면, 해시 테이블은 단 하나의 쿼리를 위해 생성하고 조인이 끝나면 곧바로 소멸하는 자료구조다.
따라서 수행시간이 짧으면서 수행빈도가 매우 높은 쿼리를 해시 조인으로 처리하면 CPU와 메모리 사용률이 크게 중가한다. 해시 맵을 만드는 과정에 여러 가지 래치 경합도 발생한다.</p>
</blockquote>
</li>
<li><p>해시 조인은 아래 세가지 조건을 만족하는 SQL문에 주로 사용</p>
<blockquote>
<ol>
<li>수행 빈도가 낮고</li>
<li>쿼리 수행시간이 오래 걸리는</li>
<li>대량 데이터 조인할 때</li>
</ol>
</blockquote>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[서브쿼리 조인]]></title>
            <link>https://velog.io/@jae_cheol/%EC%84%9C%EB%B8%8C%EC%BF%BC%EB%A6%AC-%EC%A1%B0%EC%9D%B8</link>
            <guid>https://velog.io/@jae_cheol/%EC%84%9C%EB%B8%8C%EC%BF%BC%EB%A6%AC-%EC%A1%B0%EC%9D%B8</guid>
            <pubDate>Sat, 16 Oct 2021 15:33:47 GMT</pubDate>
            <description><![CDATA[<h2 id="서브쿼리">서브쿼리</h2>
<ul>
<li>하나의 SQL문 안에 괄호로 묶은 별도의 쿼리 블록을 말한다.</li>
</ul>
<ol>
<li><p>인라인 뷰</p>
<ul>
<li>From 절에 사용한 서브쿼리</li>
</ul>
</li>
<li><p>중첩된 서브쿼리</p>
<ul>
<li>WHERE 절에 사용한 서브쿼리</li>
<li>서브쿼리가 메인쿼리 컬럼을 참조하는 형태를 &#39;상관관계 있는 서브쿼리&#39;라 함</li>
</ul>
</li>
<li><p>스칼라 서브쿼리</p>
<ul>
<li>한 레코드당 정확히 하나의 값을 반환하는 서브쿼리</li>
<li>SELECT-LIST에서 사용하지만 몇 가지 예외사항을 제외하면 컬럼이 올 수 있는 대부분 위치에서 사용할 수 있다.</li>
</ul>
</li>
</ol>
<h2 id="서브쿼리-변환이-필요한-이유">서브쿼리 변환이 필요한 이유</h2>
<ul>
<li>옵티마이저는 사용자로부터 전달받은 SQL을 최적화에 유리한 형태로 변환하는 작업, 즉 쿼리 변환부터 진행한다.</li>
<li>쿼리 변환 : 옵티마이저가 SQL을 분석해 의미적으로 동일하면서도 더 나은 성능이 기대되는 형태로 재작성하는 것을 말함.</li>
<li>서브쿼리를 참조하는 메인쿼리도 하나의 쿼리 블록이며, 옵티마이저는 쿼리 블록 단위로 최적화를 수행한다.</li>
<li>SQL을 최적화할 때도 옵티마이저가 나무가 아닌 숲 전체를 바라보는 관점에서 쿼리를 이해하려면 먼저 서브쿼리를 풀어내야만 한다!</li>
</ul>
<h2 id="서브쿼리와-조인">서브쿼리와 조인</h2>
<blockquote>
<p>메인쿼리와 서브쿼리 간에는 부모와 자식이라는 종속적이고 계층적인 관계가 존재한다.
서브쿼리는 메인쿼리에 종속되므로 단독으로 실행할 수 없다.</p>
</blockquote>
<ul>
<li><h4 id="필터-오퍼레이션">필터 오퍼레이션</h4>
<ul>
<li>no_unnest : 서브쿼리를 풀어내지 말고 그대로 수행하라고 옵티마이저에 지시하는 힌트</li>
<li>기본적으로 NL 조인과 처리 루틴이 같다.(NL 조인처럼 부분 범위 처리도 가능)</li>
<li>차이점<ol>
<li>필터는 메인쿼리의 한 로우가 서브쿼리의 한 로우와 조인에 성공하는 순간 진행을 멈추고, 메인쿼리의 다음 로우를 계속 처리한다는 점</li>
<li>필터는 캐싱기능을 갖는다는 점<ul>
<li>서브쿼리 입력 값에 따른 반환 값(true or false)을 캐싱하는 기능</li>
<li>이 기능이 작동하므로 서브쿼리를 수행하기 전에 항상 캐시부터 확인한다.</li>
</ul>
</li>
<li>필터 서브쿼리는 일반 NL 조인과 달리 메인쿼리에 종속되므로 조인 순서가 고정된다.(항상 메인쿼리가 드라이빙 집합)</li>
</ol>
</li>
</ul>
</li>
<li><h4 id="서브쿼리-unnesting--서브쿼리-flattening">서브쿼리 Unnesting (= 서브쿼리 Flattening)</h4>
<ul>
<li>unnest 힌트 사용</li>
<li>메인과 서브쿼리 간 계층 구조를 풀어 서로 같은 레벨로 만들어 준다.</li>
<li>서브쿼리를 그대로 두면 필터 방식을 사용</li>
<li>Unnesting 하고 나면 일반 조인문처럼 다양한 최적화 기법을 사용할 수 있다.</li>
<li>NL 세미 조인<ul>
<li>unnest, nl_sj 힌트 사용</li>
<li>NL 조인과 같은 프로세스</li>
<li>조인에 성공하는 순간 진행을 멈추고 메인 쿼리의 다음 로우를 계속 처리한다는 점만 다르다.</li>
</ul>
</li>
<li>Unnesting된 서브쿼리는 NL 세미조인 외에도 다양한 방식으로 실행될 수 있다.</li>
<li>필터방식은 항상 메인쿼리가 드라이빙 집합이지만,</li>
<li>Unnesting된 서브쿼리는 메인 쿼리 집합보다 먼저 처리될 수 있다.</li>
</ul>
</li>
</ul>
<blockquote>
<h3 id="rownum---잘-쓰면-약-잘못-쓰면-독">ROWNUM - 잘 쓰면 약, 잘못 쓰면 독</h3>
<ul>
<li>서브쿼리에 rownum을 쓰면 옵티마이저에게 &quot;이 서브쿼리 블록은 손대지 말라&quot;고  선언하는 것과 다름없다.</li>
<li>서브쿼리 Unnesting을 방지하려는 목적이 아니면 서브쿼리에 함부로 쓰지 말자.</li>
</ul>
</blockquote>
<ul>
<li><h4 id="서브쿼리-pushing">서브쿼리 Pushing</h4>
<ul>
<li>서브쿼리 필터링을 가능한 한 앞 단계에서 처리하도록 강제하는 기능</li>
<li>push_subq, no_push_subq 힌트로 제어</li>
<li>이 기능은 Unnesting 되지 않은 서브쿼리에만 작동</li>
<li>따라서 push_subq 힌트는 항상 no_unnest 힌트와 같이 기술해야 한다.</li>
</ul>
</li>
</ul>
<h2 id="뷰와-조인">뷰와 조인</h2>
<ul>
<li>최적화 단위가 쿼리 블록이므로 옵티마이저가 뷰 쿼리를 변환하지 않으면 뷰 쿼리 블록을 독립적으로 최적화한다.</li>
<li><strong>merge</strong> 힌트를 이용해 뷰를 메인 쿼리와 머징하도록 함.</li>
<li>부분처리가 불가능한 상황에서 NL 조인은 좋은 선택이 아니다. (이런 상황에선 보통 해시 조인이 빠름)</li>
</ul>
<h3 id="조인-조건-pushdown">조인 조건 Pushdown</h3>
<ul>
<li>메인쿼리를 실행하면서 조인 조건절 값을 건건이 뷰 안으로 밀어 넣는 기능이다.</li>
<li><strong>&#39;VIEW PUSHED PREDICATE&#39;</strong>오퍼레이션을 통해 이 기능의 작동 여부를 알 수 있음.</li>
<li>이 방식을 사용하면 &#39;건건이&#39; 데이터만 읽어서 조인하고 Group By를 수행할 수 있다.</li>
<li>부분범위 처리가 가능하다.</li>
<li><strong>push_pred</strong> 힌트를 사용</li>
<li>옵티마이저가 머징하면 힌트가 작동하지 않으니 <strong>no_merge</strong> 힌트와 함께 사용하는 습관이 필요</li>
</ul>
<h2 id="스칼라-서브쿼리-조인">스칼라 서브쿼리 조인</h2>
<h3 id="스칼라-서브쿼리의-특징">스칼라 서브쿼리의 특징</h3>
<ul>
<li>Outer 조인문처럼 NL 조인 방식으로 실행된다.</li>
<li>처리과정에서 캐싱 작용이 일어난다.<h3 id="스칼라-서브쿼리-캐싱-효과">스칼라 서브쿼리 캐싱 효과</h3>
</li>
<li>필터 서브쿼리 캐싱과 같은 기능이다.(조인 성능을 높이는 데 큰 도움)</li>
<li>캐싱은 쿼리 단위로 이루어진다.<blockquote>
<p>쿼리를 시작할 때 PGA 메모리에 공간을 할당하고, 
쿼리를 수행하면서 공간을 채워나가며, 
쿼리를 마치는 순간 공간을 반환한다.</p>
</blockquote>
</li>
<li>많이 활용되는 튜닝 기법<ul>
<li>SELECT-LIST에 사용한 함수는 메인쿼리 결과 건수만큼 반복 수행되는데, 스칼라 서브쿼리를 덧씌워서 호출 횟수를 최소화할 수 있다.</li>
</ul>
</li>
</ul>
<h3 id="스칼라-서브쿼리-캐싱-부작용">스칼라 서브쿼리 캐싱 부작용</h3>
<ul>
<li>스칼라 서브쿼리 캐싱 효과는 입력 값의 종류가 소수여서 해시 충돌 가능성이 작을 때 효과가 있다.</li>
<li>반대의 경우라면 캐시를 매번 확인하는 비용 때문에 오히려 성능이 나빠지고 CPU 사용률만 높게 만들며 메모리도 더 사용한다.</li>
</ul>
<h3 id="두-개-이상의-값-반환">두 개 이상의 값 반환</h3>
<ul>
<li>인라인 뷰 사용<ul>
<li>뷰를 사용하면, 전체를 읽어야 하거나, 뷰가 머징될 때 Group By 때문에 부분범위 처리가 안 되는 문제가 있다.</li>
</ul>
</li>
</ul>
<h3 id="스칼라-서브쿼리-unnesting">스칼라 서브쿼리 Unnesting</h3>
<ul>
<li>스칼라 서브쿼리도 NL 방식으로 조인하므로 캐싱 효과가 크지 않으면 랜덤 I/O 부담이 있다.</li>
<li>그래서 다른 조인 방식을 선택하기 위해 스칼라 서브쿼리를 일반 조인문으로 변환하고 싶을 때가 있다.</li>
<li>특히, 병렬 쿼리에선 될 수 있으면 스칼라 서브쿼리를 사용하지 않아야 한다.<ul>
<li>대량 데이터를 처리하는 병렬 쿼리는 해시 조인으로 처리해야 효과적이기 때문</li>
</ul>
</li>
<li>_optimizer_unnest_scalar_sq 파라미터<ul>
<li>true로 설정<ul>
<li>스칼라 서브쿼리를 Unnesting 할지 여부를 옵티마이저가 결정</li>
</ul>
</li>
<li>false로 설정<ul>
<li>옵티마이저가 이 기능을 사용하지 않지만, 사용자가 unnest 힌트로 유도할 수 있다.</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[시놀로지 Docker Https 적용하기]]></title>
            <link>https://velog.io/@jae_cheol/%EC%8B%9C%EB%86%80%EB%A1%9C%EC%A7%80-Docker-Https-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jae_cheol/%EC%8B%9C%EB%86%80%EB%A1%9C%EC%A7%80-Docker-Https-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 11 Oct 2021 04:28:29 GMT</pubDate>
            <description><![CDATA[<p>일단 간단하게 할 수 있는 역방향 프록시를 설정하면 된다고 하는데 필자는 적용안된다 ㅎㅎ</p>
<p>필자는 패키지 센터에 있는 깃랩을 설치였다.</p>
<h2 id="https-설정">Https 설정</h2>
<p>우선 실행 중인 깃랩을 멈추고 다음과 같이 환경 변수를 세팅한다.</p>
<pre><code>SSL_KEY_PATH=/home/git/data/certs/gitlab.key
SSL_DHPARAM_PATH=/home/git/data/certs/dhparam.pem
SSL_CERTIFICATES_PATH=/home/git/data/certs/gitlab.crt
SSL_SELF_SIGNED=false
GITLAB_HTTPS=true
GITLAB_HOST=domain.com
GITLAB_PORT=(your container port)</code></pre><p>다음으로 포트 설정을 80 -&gt; 443으로 변경
<img src="https://images.velog.io/images/jae_cheol/post/2ce1da28-5594-404e-8b2f-0a5d7bf78d41/image.png" alt=""></p>
<p>ssh 접속으로 시놀로지에 접속한다. (관리자로)</p>
<pre><code>cd /usr/syno/etc/certificate/_archive/</code></pre><p>위의 경로로 이동하면 6자리의 인증서 폴더가 있다.
인증서가 없다면 제어판 -&gt; 인증서 에서 하나 생성해 준다.</p>
<pre><code>ls -al</code></pre><p>위의 명령어를 실행하면 언제 생성됬는지 확인 할 수있음.</p>
<pre><code>cd xxxxxx</code></pre><p>인증서 폴더로 이동하고 해당 인증서를 도커쪽 폴더로 복사한다.</p>
<pre><code># 해당 폴더가 없으면 생성해주자
mkdir /volume1/docker/gitlab/gitlab/certs
# 인증서 복사
cp -f privkey.pem /volume1/docker/gitlab/gitlab/certs/gitlab.key;\
cp -f fullchain.pem /volume1/docker/gitlab/gitlab/certs/gitlab.crt;</code></pre><p>복사가 끝나면 복사한 폴더 쪽으로 이동하여 dhparam.pem를 생성한다</p>
<pre><code>cd /volume1/docker/gitlab/gitlab/certs
openssl dhparam -out dhparam.pem 4096</code></pre><p>dhparam.pem 생성은 엄청 오래 걸린다. 필자는 켜두고 잤음 ㅋㅋㅋ</p>
<p>dhparam.pem 생성이 완료 되면 깃랩을 실행 하면 https 적용이 완료 됨!!</p>
<h2 id="인증서-자동-갱신">인증서 자동 갱신</h2>
<p>제어판 -&gt; 작업 스케줄러 -&gt; 예약된 작업 -&gt; 사용자 정의 스크립트
작업 설정 탭에서 스크립트를 다음과 같이 작성</p>
<pre><code># 인증서 폴더 명 
ID=&quot;xxxxxx&quot;

#/usr/syno/bin/synopkg stop Docker-GitLab
cd /usr/syno/etc/certificate/_archive/${ID}
SYNOLOGY_CERT=$(sudo openssl x509 -checkend 0 -in fullchain.pem)
GITLAB_CERT=$(openssl x509 -checkend 0 -in /volume1/docker/gitlab/gitlab/certs/gitlab.crt)
echo &quot;synology cert status: ${SYNOLOGY_CERT}&quot;
echo &quot;gitLab cert status: ${GITLAB_CERT}&quot;

if [ &quot;${SYNOLOGY_CERT}&quot; != &quot;${GITLAB_CERT}&quot; ]
then
echo &quot;Action Required&quot;
sudo \cp -f privkey.pem /volume1/docker/gitlab/gitlab/certs/gitlab.key;
sudo \cp -f fullchain.pem /volume1/docker/gitlab/gitlab/certs/gitlab.crt;
echo &quot;GitLab restarting..&quot;
/usr/syno/bin/synopkg restart Docker-GitLab
else
echo &quot;no action required.&quot;
fi

echo &quot;done.&quot;</code></pre><p>스케줄 텝에서 다음과 같이 설정 후 확인 클릭(일요일마다 실행)
<img src="https://images.velog.io/images/jae_cheol/post/4d8827da-976a-4fb0-8ee3-e38687b73614/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-11%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%201.27.59.png" alt=""></p>
<p>참고</p>
<ul>
<li><a href="https://github.com/mschadev/synology-gitlab-ssl">https://github.com/mschadev/synology-gitlab-ssl</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kafka Conncet]]></title>
            <link>https://velog.io/@jae_cheol/Kafka-Conncet</link>
            <guid>https://velog.io/@jae_cheol/Kafka-Conncet</guid>
            <pubDate>Sun, 10 Oct 2021 08:07:32 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p>Kafka Conncet를 통해 Data를 Import/Export 가능</p>
</li>
<li><p>코드 없이 Configuration으로 데이터를 이동</p>
</li>
<li><p>Standalone mode, Distribution Mode 지원</p>
<ul>
<li>RESTful API 통해 지원</li>
<li>Stream 또는 Batch 형태로 데이터 전송 가능</li>
<li>커스텀 Connector를 통한 다양한 플러그인 제공 (File, S3, Hive, MySQL...)</li>
</ul>
</li>
<li><p>Kafka Conncet Source : 데이터를 가져오는 쪽</p>
</li>
<li><p>Kafka Conncet Sink : 데이터를 보내는 쪽</p>
<ul>
<li>토픽에 등록된 데이터 파일을 타겟 시스템쪽으로 옮겨주는 역할
<img src="https://images.velog.io/images/jae_cheol/post/3b9f3377-1404-44c9-bf22-e4695c4e7375/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-10%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%204.13.02.png" alt=""></li>
</ul>
</li>
</ul>
<h2 id="kafka-conncet-설치">Kafka Conncet 설치</h2>
<ul>
<li>필자는 모든 설치파일을 /Users/lee/development 경로 안에 저장했음.<pre><code>curl -O http://packages.confluent.io/archive/5.5/confluent-community-5.5.2-2.12.tar.gz
or
curl -O http://packages.confluent.io/archive/6.1/confluent-community-6.1.0.tar.gz
tar xvf confluent-community-6.1.0.tar.gz
cd  confluent-community-6.1.0
</code></pre></li>
</ul>
<p>실행
./bin/connect-distributed ./etc/kafka/connect-distributed.properties</p>
<pre><code>- Install the JDBC Connector
  - https://docs.confluent.io/5.5.1/connect/kafka-connect-jdbc/index.html
  - 위 링크에서 Download and extract the ZIP file 클릭 후 다운로드 버튼 클릭!
- plugin 정보 추가</code></pre><ul>
<li>confluent-community-6.1.0 폴더 내에서
code ./etc/kafka/connect-distributed.properties
```
<img src="https://images.velog.io/images/jae_cheol/post/351df0ca-4a03-4b82-851b-39c3df6c1f20/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-10%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%205.42.55.png" alt=""></li>
<li>JdbcSourceConnector에서 mysql 사용하기 위해 드라이버 복사</li>
</ul>
<pre><code>mvn 의 빌드된 jar들은 .m에 다있음.
cd /Users/lee/.m2/repository/mysql/mysql-connector-java/8.0.26

 cp ./mysql-connector-java-8.0.26.jar /Users/lee/development/kafka/confluent-6.1.0/share/java/kafka</code></pre><h2 id="kafka-conncet-사용">Kafka Conncet 사용</h2>
<h3 id="1-kafka-connce-source-등록">1. Kafka Connce Source 등록</h3>
<ul>
<li><p>Kafka Source Conncet 등록 (포스트맨을 사용)</p>
</li>
<li><p>등록</p>
<pre><code>POST / http://localhost:8083/connectors
Type application/json
data
{
  &quot;name&quot; : &quot;my-source-connect&quot;,
  &quot;config&quot; : {
  &quot;connector.class&quot; : &quot;io.confluent.connect.jdbc.JdbcSourceConnector&quot;,
  &quot;connection.url&quot;:&quot;jdbc:mysql://localhost:3306/mydb&quot;,
  &quot;connection.user&quot;:&quot;root&quot;,
  &quot;connection.password&quot;:&quot;1234&quot;,
  &quot;mode&quot;: &quot;incrementing&quot;,
  &quot;incrementing.column.name&quot; : &quot;id&quot;,
  &quot;table.whitelist&quot;:&quot;users&quot;,
  &quot;topic.prefix&quot; : &quot;my_topic_&quot;,
  &quot;tasks.max&quot; : &quot;1&quot;
  }
}</code></pre></li>
<li><p>목록</p>
<pre><code>GET / http://localhost:8083/connectors</code></pre><p><img src="https://images.velog.io/images/jae_cheol/post/fc65c60c-08d8-4a3d-8b85-c0ed2cc58c7d/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-10%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.16.08.png" alt=""></p>
</li>
<li><p>상세 확인</p>
<pre><code>http://localhost:8083/connectors/my-source-connect/status</code></pre><p><img src="https://images.velog.io/images/jae_cheol/post/20f6e2af-fe25-4309-8b16-9e258cb563c5/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-10%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.16.49.png" alt=""></p>
</li>
<li><p>이제 테이블에 값을 넣을 때마다 메시지를 보내는 것을 볼 수 있음.
(처음에는 바뀐 것이 없기 때문에 my_topic_users 가 없음)
<img src="https://images.velog.io/images/jae_cheol/post/4fba4544-691b-43bb-807d-f2833d7933df/image.png" alt=""></p>
<pre><code>insert into users(user_id, pwd, name) values(&#39;user&#39;, &#39;1234&#39;, &#39;name&#39;);
insert into users(user_id, pwd, name) values(&#39;user2&#39;, &#39;1234&#39;, &#39;name2&#39;);</code></pre><p><img src="https://images.velog.io/images/jae_cheol/post/24f73dd9-71c4-48d6-9bc9-0395737ebf71/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-10%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.17.36.png" alt=""></p>
</li>
</ul>
<h3 id="2-kafka-connce-sink-등록">2. Kafka Connce Sink 등록</h3>
<ul>
<li>Kafka Sink Conncet 등록 (포스트맨을 사용)</li>
<li>등록<pre><code>POST / http://localhost:8083/connectors
Type application/json
data
{
  &quot;name&quot;:&quot;my-sink-connect&quot;,
  &quot;config&quot;:{
  &quot;connector.class&quot;:&quot;io.confluent.connect.jdbc.JdbcSinkConnector&quot;,
  &quot;connection.url&quot;:&quot;jdbc:mysql://localhost:3306/mydb&quot;,
  &quot;connection.user&quot;:&quot;root&quot;,
  &quot;connection.password&quot;:&quot;1234&quot;,
  &quot;auto.create&quot;:&quot;true&quot;,
  &quot;auto.evolve&quot;:&quot;true&quot;,
  &quot;delete.enabled&quot;:&quot;false&quot;,
  &quot;tasks.max&quot;:&quot;1&quot;,
  &quot;topics&quot;:&quot;my_topic_users&quot;
  }
}</code></pre></li>
<li>목록 및 상세 확인은 동일함.</li>
<li>Sink 등록 후 DB를 보면 테이블 하나가 생성된 것과 데이터가 들어가 있는 것을 볼 수있음.
<img src="https://images.velog.io/images/jae_cheol/post/b881afbd-ab7b-44a5-b0db-e6f6c7f17181/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-10%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.31.57.png" alt="">테이블 생성이 됨
<img src="https://images.velog.io/images/jae_cheol/post/6fcd0415-3509-4f8f-8650-52d70b6da808/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-10%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.32.37.png" alt="user">users 테이블 값
<img src="https://images.velog.io/images/jae_cheol/post/940a6b9a-a5e1-477c-96f1-cdaff3b52a7c/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-10%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.32.14.png" alt="">생성된 테이블 값</li>
<li>위의 이미지를 보면 users와 my_topic_users 가 동일한 데이터가 있는 것을 볼 수 있음.</li>
<li>producer에서 값 보내기 <pre><code>{&quot;schema&quot;:{&quot;type&quot;:&quot;struct&quot;,&quot;fields&quot;:[{&quot;type&quot;:&quot;int32&quot;,&quot;optional&quot;:false,&quot;field&quot;:&quot;id&quot;},{&quot;type&quot;:&quot;string&quot;,&quot;optional&quot;:true,&quot;field&quot;:&quot;user_id&quot;},{&quot;type&quot;:&quot;string&quot;,&quot;optional&quot;:true,&quot;field&quot;:&quot;pwd&quot;},{&quot;type&quot;:&quot;string&quot;,&quot;optional&quot;:true,&quot;field&quot;:&quot;name&quot;},{&quot;type&quot;:&quot;int64&quot;,&quot;optional&quot;:true,&quot;name&quot;:&quot;org.apache.kafka.connect.data.Timestamp&quot;,&quot;version&quot;:1,&quot;field&quot;:&quot;created_at&quot;}],&quot;optional&quot;:false,&quot;name&quot;:&quot;users&quot;},&quot;payload&quot;:{&quot;id&quot;:7,&quot;user_id&quot;:&quot;user5&quot;,&quot;pwd&quot;:&quot;7777&quot;,&quot;name&quot;:&quot;name7&quot;,&quot;created_at&quot;:1633890976000}}</code></pre><img src="https://images.velog.io/images/jae_cheol/post/c85dce5f-af40-4060-a25e-3bd4716069e0/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-10%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%207.58.27.png" alt="">
<img src="https://images.velog.io/images/jae_cheol/post/b6e036b4-215c-452b-8d4f-9fd390035380/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-10%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%207.58.49.png" alt=""></li>
<li>producer에서 값을 보내면 위의 이미지처럼 users에는 값이 들어가지 않고 my_topic_users에만 값을 들어 가는 것을 볼 수 있음</li>
<li>이유 : user 토픽으로 부터 데이터를 가져오는 게 아니라 자신이 가지고 있는 데이터를 my_topic_users에 데이터를 넣는 역할임</li>
</ul>
<hr>
<h2 id="에러">에러</h2>
<ul>
<li>만약 producer에서 값을 잘못 보내면 sink가 에러로 죽음!!</li>
<li>해결 방안으로 토픽을 삭제 해주고 다시 실행! 또는 토픽 초기화를 해주면 됨.<h3 id="토픽-삭제">토픽 삭제</h3>
</li>
<li>config/server.properties 에서 다음을 추가<pre><code>delete.topic.enable=true</code></pre></li>
<li>카프카 재실행 후 다음 커맨드 실행<pre><code>./bin/kafka-topics.sh --delete --bootstrap-server localhost:9092  --topic my_topic_users</code></pre></li>
<li>토픽 초기화는 아래 메소드를 통해서 할 수 있는 듯 하다 </li>
</ul>
<h3 id="kafka-conncet-관련-메소드">Kafka Conncet 관련 메소드</h3>
<pre><code>- GET /connectors - return a list of active connectors
- POST /connectors - create a new connector; the request body should be a JSON object containing a string name field and an object config field with the connector configuration parameters
- GET /connectors/{name} - get information about a specific connector
- GET /connectors/{name}/config - get the configuration parameters for a specific connector
- PUT /connectors/{name}/config - update the configuration parameters for a specific connector
- GET /connectors/{name}/status - get current status of the connector, including if it is running, failed, paused, etc., which worker it is assigned to, error information if it has failed, and the state of all its tasks
- GET /connectors/{name}/tasks - get a list of tasks currently running for a connector
- GET /connectors/{name}/tasks/{taskid}/status - get current status of the task, including if it is running, failed, paused, etc., which worker it is assigned to, and error information if it has failed
- PUT /connectors/{name}/pause - pause the connector and its tasks, which stops message processing until the connector is resumed
- PUT /connectors/{name}/resume - resume a paused connector (or do nothing if the connector is not paused)
- POST /connectors/{name}/restart - restart a connector (typically because it has failed)
- POST /connectors/{name}/tasks/{taskId}/restart - restart an individual task (typically because it has failed)
- DELETE /connectors/{name} - delete a connector, halting all tasks and deleting its configuration
- GET /connectors/{name}/topics - get the set of topics that a specific connector is using since the connector was created or since a request to reset its set of active topics was issued
- PUT /connectors/{name}/topics/reset - send a request to empty the set of active topics of a connector</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Kafka]]></title>
            <link>https://velog.io/@jae_cheol/Kafka</link>
            <guid>https://velog.io/@jae_cheol/Kafka</guid>
            <pubDate>Sun, 10 Oct 2021 05:42:18 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p>Apache Software Foundation의 Scala 언어로 된 오픈 소스 메시지 브로커 프로젝트</p>
</li>
<li><p>실시간 데이터 피드를 관리하기 위해 통일된 높은 처리량, 낮은 지연 시간을 지닌 플랫폼 제공</p>
</li>
<li><p>모든 시스템으로 부터 데이터를 실시간으로 전송하여 처할 수 있는 시스템</p>
</li>
<li><p>데이터가 많아지더라도 확장이 용이한 시스템</p>
</li>
<li><p>Producer/Consumer 분리</p>
</li>
<li><p>메시지를 여러 Consumer에게 허용</p>
</li>
<li><p>높은 처리량을 위한 메시지 최적화</p>
</li>
<li><p>Scale-out 가능(여러개의 서버로 구성해서 작동이 가능)</p>
</li>
<li><p>Eco-system</p>
</li>
</ul>
<h2 id="kafka-broker">Kafka Broker</h2>
<ul>
<li>메시지를 주고 받을 때 저장되는 공간으로 Kafka Broker를 사용</li>
<li>실행 된 Kafka 애플리케이션 서버</li>
<li>3대 이상의 Broker Cluster 구성</li>
<li>Zookeeper 연동 (Broker를 컨트롤해줌)<ul>
<li>역할 : 메타데이터 저장</li>
<li>Controller 정보 저장</li>
</ul>
</li>
<li>n개 Broker 중 1대는 Controller 기능 수행<ul>
<li>Controller 역할 <ul>
<li>각 Broker에게 담당 파티션 할당 수행</li>
<li>Broker 정상 동작 모니터링 관리</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="kafka-설치">Kafka 설치</h2>
<ul>
<li><p><a href="https://kafka.apache.org/downloads">https://kafka.apache.org/downloads</a></p>
</li>
<li><p>tar xvf 압출을 풀 압축파일</p>
</li>
<li><p>실제환경에서는 멀티클러스터링을 통해서 다양하게 브로커를 구성한 다음에 안정성있는 메시지 전달할 수 구조로 구축하는 게 좋음</p>
</li>
<li><p>예제는 싱글 주키퍼, 싱글 카프카 두가지만 구성</p>
</li>
</ul>
<h2 id="kafka-실행">Kafka 실행</h2>
<ul>
<li>주키퍼 실행 (port : 2181)<pre><code>./bin/zookeeper-server-start.sh ./config/zookeeper.properties</code></pre></li>
<li>Kafka 실행 (port : 9092)<pre><code>bin/kafka-server-start.sh config/server.properties</code></pre></li>
</ul>
<h2 id="topic">Topic</h2>
<ul>
<li><p>Producer가 메시지를 전송하면 Topic에 저장이 된다.</p>
</li>
<li><p>Topic을 구독을 한 Consumer쪽으로 일괄적으로 메시지를 받음</p>
</li>
<li><p>Topic 생성</p>
<pre><code>./bin/kafka-topics.sh --bootstrap-server localhost:9092 --create --topic quickstart-events --partitions 1 --replication-factor 1
</code></pre></li>
</ul>
<p>--create : 생성 
--topic : topic 명 
--bootstrap-server : 연결한 카프카 서버 주소 
--partitions : 생성하는 토픽의 파티션 수 
--replication-factor : 생성하는 토픽의 각 파티션의 replication-factor 개수</p>
<pre><code>- Topic 목록 확인</code></pre><p>./bin/kafka-topics.sh --bootstrap-server localhost:9092 --list</p>
<pre><code>![](https://images.velog.io/images/jae_cheol/post/71cc4f64-e31d-48fa-911f-3dbe4269a282/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-10%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%203.44.58.png)
- Topic 정보 확인</code></pre><p>./bin/kafka-topics.sh --bootstrap-server localhost:9092 --describe</p>
<pre><code>![](https://images.velog.io/images/jae_cheol/post/6d7753fa-9a8a-4312-880c-703bf0dac4c2/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-10%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%203.45.25.png)
- 메시지 생상</code></pre><p>./bin/kafka-console-producer.sh --broker-list localhost:9092 --topic quickstart-events</p>
<pre><code>![](https://images.velog.io/images/jae_cheol/post/dd0b24ee-9fa7-499c-9d70-6314e9a798f7/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-10%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%203.45.45.png)
- 메시지 소비</code></pre><p>./bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic quickstart-events \ --from-beginning</p>
<pre><code>![](https://images.velog.io/images/jae_cheol/post/74f01099-4ab3-47cd-ad25-b886a5fa8952/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-10-10%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%203.46.23.png)


---
## 에러 처리
- kafka.common.KafkaException: Failed to acquire lock on file .lock in /tmp/kafka-logs.</code></pre><p>/tmp/kafka-logs 삭제
9092 포트 찾아서 죽이기
/tmp/kafka-logs 재생성하기 </p>
<pre><code></code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[소트 머지 조인]]></title>
            <link>https://velog.io/@jae_cheol/%EC%86%8C%ED%8A%B8-%EB%A8%B8%EC%A7%80-%EC%A1%B0%EC%9D%B8</link>
            <guid>https://velog.io/@jae_cheol/%EC%86%8C%ED%8A%B8-%EB%A8%B8%EC%A7%80-%EC%A1%B0%EC%9D%B8</guid>
            <pubDate>Sat, 09 Oct 2021 12:05:32 GMT</pubDate>
            <description><![CDATA[<p>조인 컬럼에 인덱스가 없을 때, 대량의 데이터 조인이어서 인덱스가 효과적이지 않을 때, 옵티마이저는 NL조인 대신 소트 머지 조인이나 해시 조인을 선택한다.</p>
<h2 id="sga-vs-pga">SGA vs PGA</h2>
<ul>
<li><p>SGA</p>
<ul>
<li>공유 메모리 영역인 SGA에 캐시된 데이터는 여러 프로세스가 공유할 수 있다.</li>
<li>여러 프로세스가 공유할 수 있지만, 동시에 액세스할 수 없다.</li>
<li>동시에 액세스하려는 프로세스 간 액세스를 직렬화하기 위한 Lock 메커니즘으로 래치(Latch)가 존재</li>
<li>데이터 블록과 인덱스 블록을 캐싱하는 DB버퍼캐시는 SGA의 가장 핵심적인 구성요소</li>
<li>여기서 블록을 읽으려면 버퍼 Lock도 얻어야 한다.</li>
</ul>
</li>
<li><p>PGA</p>
<ul>
<li>오라클 서버 프로세스는 SGA에 공유된 데이터를 읽고 쓰면서, 동시에 자신만의 고유 메모리 영역을 갖는다.</li>
<li>각 오라클 서버 프로세스에 할당된 메모리 영역을 PGA라 부름</li>
<li>프로세스에 종속적인 고유 데이터를 저장하는 용도로 사용</li>
<li>다른 프로세스와 공유하지 않는 독립적인 메모리 공간</li>
<li>래치 메커니즘이 불필요</li>
<li>같은 양의 데이터를 읽더라도 SGA 버퍼캐시에서 읽을 때보다 훨씬 빠름</li>
</ul>
</li>
</ul>
<h2 id="기본-매커니즘">기본 매커니즘</h2>
<ul>
<li>소트 머지 조인<ol>
<li>소트 단계 : 양쪽 집합을 조인 컬럼 기준으로 정렬</li>
<li>머지 단계 : 정렬한 양쪽 집합을 서로 머지 </li>
</ol>
</li>
<li>SortArea에 저장할 데이터 자체가 인덱스 역할을 하므로 소트 머지 조인은 조인 컬럼에 인덱스가 없어도 사용할 수 있는 조인 방식이다.</li>
</ul>
<h2 id="소트-머지-조인이-빠른-이유">소트 머지 조인이 빠른 이유</h2>
<ul>
<li>NL 조인의 치명적인 단점은 대량 데이터 조인할 때 성능이 매우 느림</li>
<li>소트 머지 조인은 Sort Area에 미리 정렬해 둔 자료구조를 이용한다는 점만 다를 ㅃ분 조인 프로세싱 자체는 NL 조인과 같다.</li>
<li>소트 머지 조인은 양쪽 테이블로 부터 조인 대상 집합을 일괄적으로 읽어 PGA에 저장한 후 조인한다.</li>
<li>PGA는 프로세스만을 위한 독립적인 메모리 공간이므로 데이터를 읽을 때 래치 획득 과정이 없다.</li>
</ul>
<h2 id="소트-머지-조인의-주용도">소트 머지 조인의 주용도</h2>
<ul>
<li>소트 머지 조인 사용 <ol>
<li>조인 조건식이 등치(=)조건이 아닌 대량 데이터 조인</li>
<li>조인 조건식이 아예 없는 조인(Cross Join, 카테시안 곱)</li>
</ol>
</li>
</ul>
<h2 id="소트-머지-조인-제어하기">소트 머지 조인 제어하기</h2>
<ul>
<li>use_merge()</li>
</ul>
<h2 id="소트-머지-조인-특징-요약">소트 머지 조인 특징 요약</h2>
<ul>
<li>조인을 위해 실시간으로 인덱스를 생성하는 것과 다름 없다.</li>
<li>조인 컬럼에 인덱스가 없는 상황에서 두 테이블을 각각 읽어 조인 대상 집합을 줄일 수 있을 떄 유리</li>
<li>스캔 위주의 액세스 방식을 사용한다.</li>
<li>하지만 모든 처리가 스캔 방식으로 이루어 지지 않음</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>