Jekyll2023-03-15T00:42:47+00:00https://seonkyukim.github.io/feed.xmlGONNABEDeveloper김선규hopsprings2ternal@gmail.comJava 동시성에 대하여2021-07-09T00:00:00+00:002021-07-09T00:00:00+00:00https://seonkyukim.github.io/java-concurrent<p>멀티 쓰레딩에 대해 고민하는 시기가 오게 됩니다.
하지만 처음 구글링을 하다보면 생각보다 매우 다양한 정보에 휩쓸리게 됩니다.
Thread, ExecutorService, Future, Parallel Stream 등등..
이 글에서는 이론적인 내용부터 어플리케이션 레벨, 그리고 하드웨어 레벨까지 동시성에 대한 개념들을 포괄적으로 설명할 예정입니다.
어떻게 보면 제가 공부한 레퍼런스들의 총 집합이라고 볼 수 있겠습니다.</p>
<ul>
<li>“쓰레드”란 무엇이고, 어떻게 동시성 작업을 진행하는지 간단하게 살펴볼 것입니다.</li>
<li>Java 에서 제공하는 동시성 프로그래밍 방법들을 알아볼 것입니다.</li>
<li>동시성을 언제 사용하는지, 동시성 설계 시 주의점, 발생할 수 있는 문제점들을 알아볼 것입니다.</li>
</ul>
<h3 id="프로세스와-쓰레드">프로세스와 쓰레드</h3>
<p>어떤 프로그램을 실행시킬 때, 운영체제는 시스템에서 그 <em>한 개의 프로그램만 실행되는 것 같은 착각</em>에 빠지게 해줍니다.
그 프로그램의 내용들이 아무런 방해 없이 순차적으로 이뤄지고 있는 것으로 보이지요.
이러한 환상은 “프로세스”라는 개념으로 만들어집니다.</p>
<p><strong>프로세스란 실행 중인 프로그램에 대한 운영체제의 추상화입니다.</strong> 간단하게는 실행중인 프로그램이라고 생각하셔도 됩니다.</p>
<p>보통 우리는 하나의 컴퓨터에서 여러 프로세스를 동시에 실행시킵니다.
하지만 실제로 단일 코어 환경에서, 운영체제는 어느 한 순간에 하나의 프로세스 코드만을 실행할 수 있습니다.
그렇다면 어떻게 동시에 프로세스들이 실행되는 것처럼 보이게 하는 걸까요?</p>
<p>운영체제는 <strong>문맥 전환(context switching)</strong> 이라는 방법을 사용해 이를 가능하게 합니다.
여러 개의 프로세스를 빠르게 번갈아가며 교차 실행하는 방식이죠.</p>
<p>운영체제는 프로세스가 실행되는데 필요한 모든 상태정보의 변화를 추적합니다.
<strong>문맥, 컨텍스트(context)</strong> 라고 부르는 상태정보는 PC, 레지스터 파일, 메인 메모리의 현재 값을 포함하고 있습니다.
운영체제는 현재 프로세스에서 다른 새로운 프로세스로 제어를 옮기려고 할 때 현재 프로세스의 컨텍스트를 저장하고,
새로운 프로세스의 컨텍스트를 복원시키는 <em>문맥전환</em>을 사용하여 제어권을 새 프로세스에게 넘겨줍니다.</p>
<p><strong>쓰레드는 프로세스에 존재하는 실행 유닛, 혹은 제어 흐름입니다.</strong>
프로세스와 마찬가지로 추상적인 단위이며, 실제로 CPU 자원을 할당받아 실행되는 단위입니다.
프로세스는 하나의 쓰레드만 가질 수도 있고, 여러 개의 쓰레드를 가질 수도 있습니다.
여러 개의 쓰레드를 갖고 있을 경우 위에서 설명한 문맥전환과 비슷한 방법으로 CPU에 스케줄링 됩니다.</p>
<p>쓰레드라는 개념은 하드웨어 레벨과 어플리케이션 레벨로 분리해서 생각하는 것이 이해가 쉽습니다.
프로그램에서 사용하는 <strong>쓰레드는 하나의 제어 흐름 단위</strong>라고 생각하시면 됩니다.
반면, 하드웨어에서 사용하는 <strong>쓰레드는 CPU에 할당되어 실제로 일을 처리</strong>하는 단위입니다.</p>
<p>쓰레드도 앞서 설명한 문맥전환과 같은 방법을 이용하여 교차적으로 CPU에 할당되기 때문에,
프로그램에서는 하드웨어의 쓰레드보다 많은 수의 쓰레드를 이용할 수 있습니다.</p>
<p><em>출처: 컴퓨터 시스템 Chapter1</em></p>
<h3 id="멀티-쓰레딩은-언제-필요한가---처리량-높이기">멀티 쓰레딩은 언제 필요한가? - 처리량 높이기</h3>
<p>주로 멀티쓰레딩은 처리량을 높이기 위해 사용할 수 있습니다.
하지만 멀티 쓰레딩은 항상 처리량을 높여주지는 않습니다.
일반적으로 <strong>1. 대기 시간이 긴 작업</strong>이 있거나, 여러 프로세서가 동시에 처리할 <strong>2. 독립적인 계산이 충분히 많은</strong> 경우에 성능이 높아집니다.
왜 그런지 다음 예시를 통해 살펴보겠습니다.</p>
<p>대기 시간이 긴 작업은 주로 네트워크 응답을 기다리거나 I/O를 기다리는 시간입니다.
다음과 같이 페이지를 읽어오고 처리하는 작업이 있다고 가정해봅시다:</p>
<ul>
<li>페이지를 읽어오는 평균 I/O 시간: 1초</li>
<li>페이지를 분석하는 평균 처리 시간: 0.5초</li>
<li>처리는 CPU 100% 사용, I/O는 CPU 0% 사용</li>
</ul>
<p>1개의 쓰레드로 3개의 페이지를 처리하는데 걸리는 시간은 1.5초 X N 입니다.</p>
<p>반면, 3개의 쓰레드로 처리할 경우, I/O 를 기다리는 1초 동안 두 개의 페이지를 처리 할 수 있습니다.
그러므로 처리율은 1개의 쓰레드를 이용할 때의 3배가 될 것입니다.</p>
<p><em>출처: 클린코드, 부록 A 동시성</em></p>
<p>이제 Java 에서 어떻게 멀티 쓰레딩을 활용할 수 있는지 확인해봅시다.</p>
<h3 id="thread">Thread</h3>
<p>구글에 “Java 멀티 쓰레드 구현”이라고 검색하면 처음에 가장 많이 나오는 방법입니다.
보통 <code class="language-plaintext highlighter-rouge">Runnable</code> 한 태스크를 만든 뒤, 새로운 쓰레드를 만들고 해당 쓰레드를 <code class="language-plaintext highlighter-rouge">start()</code> 합니다.</p>
<p><a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Thread.html"><code class="language-plaintext highlighter-rouge">Thread</code></a> 를 사용하여 병렬로 <code class="language-plaintext highlighter-rouge">f()</code>, <code class="language-plaintext highlighter-rouge">g()</code> 함수를 실행하는 간단한 예시입니다:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Thread</span> <span class="n">t1</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Thread</span><span class="o">(()</span> <span class="o">-></span> <span class="o">{</span> <span class="n">result</span><span class="o">.</span><span class="na">left</span> <span class="o">=</span> <span class="n">f</span><span class="o">(</span><span class="n">x</span><span class="o">);</span> <span class="o">});</span>
<span class="nc">Thread</span> <span class="n">t2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Thread</span><span class="o">(()</span> <span class="o">-></span> <span class="o">{</span> <span class="n">result</span><span class="o">.</span><span class="na">right</span> <span class="o">=</span> <span class="n">g</span><span class="o">(</span><span class="n">x</span><span class="o">);</span> <span class="o">});</span>
<span class="n">t1</span><span class="o">.</span><span class="na">start</span><span class="o">();</span>
<span class="n">t2</span><span class="o">.</span><span class="na">start</span><span class="o">();</span>
<span class="n">t1</span><span class="o">.</span><span class="na">join</span><span class="o">();</span>
<span class="n">t2</span><span class="o">.</span><span class="na">join</span><span class="o">();</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">result</span><span class="o">.</span><span class="na">left</span> <span class="o">+</span> <span class="n">result</span><span class="o">.</span><span class="na">right</span><span class="o">);</span>
</code></pre></div></div>
<p><em>출처: 모던 자바 인 액션</em></p>
<h3 id="executorservice">ExecutorService</h3>
<p><a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ExecutorService.html"><code class="language-plaintext highlighter-rouge">ExecutorService</code></a> 는 비동기 작업들을 쓰레드를 직접 관리하지 않고 실행 할 수 있는 인터페이스를 제공합니다.
Task 를 실행할 수 있는 <code class="language-plaintext highlighter-rouge">submit</code> 메서드가 존재합니다.</p>
<h3 id="쓰레드-풀과-threadpoolexecutor">쓰레드 풀과 ThreadPoolExecutor</h3>
<p>쓰레드 풀은 말 그대로 미리 생성해둔 쓰레드들이 모여 있는 곳입니다.
태스크가 제출되면 차례대로 작업 큐에 들어가서 쓰레드를 할당받을 때까지 기다립니다.
쓰레드는 작업 큐에서 태스크를 하나씩 가져가 처리합니다.</p>
<p><img src="https://seonkyuKim.github.io/assets/images/2021-07-09-java-concurrent/thread-pool.png" alt="" class="align-center" /></p>
<p>출처: <a href="https://limkydev.tistory.com/55">https://limkydev.tistory.com/55</a></p>
<p><a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ThreadPoolExecutor.html"><code class="language-plaintext highlighter-rouge">ThreadPoolExecutor</code></a> 는 <code class="language-plaintext highlighter-rouge">ExecutorService</code>의 구현체이며 앞서 설명한 쓰레드 풀을 이용하여 비동기로 작업들을 실행합니다.
주로 <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Executors.html"><code class="language-plaintext highlighter-rouge">Executors</code></a> 의 펙토리 메서드들을 사용하여 생성합니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">ExecutorService</span> <span class="n">executorService</span> <span class="o">=</span> <span class="nc">Executors</span><span class="o">.</span><span class="na">newFixedThreadPool</span><span class="o">(</span><span class="mi">2</span><span class="o">);</span>
<span class="nc">Future</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">y</span> <span class="o">=</span> <span class="n">executorService</span><span class="o">.</span><span class="na">submit</span><span class="o">(()</span> <span class="o">-></span> <span class="n">f</span><span class="o">(</span><span class="n">x</span><span class="o">));</span>
<span class="nc">Future</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">z</span> <span class="o">=</span> <span class="n">executorService</span><span class="o">.</span><span class="na">submit</span><span class="o">(()</span> <span class="o">-></span> <span class="n">g</span><span class="o">(</span><span class="n">x</span><span class="o">));</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">y</span><span class="o">.</span><span class="na">get</span><span class="o">()</span> <span class="o">+</span> <span class="n">z</span><span class="o">.</span><span class="na">get</span><span class="o">());</span>
</code></pre></div></div>
<p><strong>쓰레드풀 주의사항</strong></p>
<ol>
<li>
<p><strong>잠을 자거나 I/O, 네트워크 연결을 기다리는 테스크가 있다면 주의.</strong></p>
<p>n개의 쓰레드를 가진 쓰레드 풀은 오직 n개의 쓰레드를 동시에 실행시킬 수 있습니다.
예를 들어 5개의 쓰레드 풀에서 3개의 쓰레드에서 기다리는 작업이 있다면
그동안은 두 개의 쓰레드에서만 작업 처리가 가능합니다.
따라서 적당한 쓰레드 수를 설정해줍시다.</p>
</li>
<li>
<p><strong>모든 쓰레드 풀을 종료하는 습관을 갖자.</strong></p>
<p>다음 태스크를 기다리면서 쓰레드 풀이 종료가 안될 수 있습니다.
이런 상황을 해결하기 위해서 <code class="language-plaintext highlighter-rouge">Thread.setDaemon</code> 메서드를 이용해 데몬 쓰레드로 설정할 수 있습니다.
데몬 쓰레드는 어플리케이션이 종료될 때 강제 종료됩니다.
(main() 메서드는 모든 비데몬 쓰레드가 종료되기까지 기다립니다.)</p>
</li>
</ol>
<p><em>출처: 모던 자바 인 액션</em></p>
<h3 id="future">Future</h3>
<p>위 예시에서 <code class="language-plaintext highlighter-rouge">ExecutorService</code> 의 <code class="language-plaintext highlighter-rouge">submit()</code> 메서드를 보면 <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Future.html"><code class="language-plaintext highlighter-rouge">Future</code></a> 타입을 반환합니다.
이는 무엇일까요?</p>
<p>문서를 살펴보면 비동기 작업의 결과를 나타낸다고 합니다.
<code class="language-plaintext highlighter-rouge">isDone()</code>, <code class="language-plaintext highlighter-rouge">isCancelled()</code> 등의 메서드로 태스크의 상태를 확인할 수 있고,
<code class="language-plaintext highlighter-rouge">get()</code> 메서드를 통해 태스크의 결과를 가져올 수 있습니다.
다만, <code class="language-plaintext highlighter-rouge">get()</code> 함수를 호출할 때 결과가 다 계산되지 않았을 경우 해당 위치에서 기다립니다 (block).</p>
<p>Future를 실제로 사용하는 예시들을 소개해드릴 예정입니다.
그 전에 앞서 <code class="language-plaintext highlighter-rouge">Future</code> 의 구현체인 <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/CompletableFuture.html"><code class="language-plaintext highlighter-rouge">CompletableFuture</code></a> 클래스의
<code class="language-plaintext highlighter-rouge">complete</code>, <code class="language-plaintext highlighter-rouge">completeExceptionally</code>, <code class="language-plaintext highlighter-rouge">supplyAsync</code> 메서드들도 한 번 둘러보시길 바랍니다.</p>
<h3 id="비동기-실제-사용-예시">비동기 실제 사용 예시</h3>
<p>카프카 클라이언트 코드(2.0.0)를 보면서 실제로 사용된 예시를 하나 살펴보겠습니다.</p>
<p><code class="language-plaintext highlighter-rouge">KafkaAdminClient</code> 에는 카프카 브로커와 네트워크를 통해 통신하는 부분이 있습니다.
예를 들어 <code class="language-plaintext highlighter-rouge">listConsumerGroupOffsets</code> 함수를 보면 다음과 같은 방식으로 응답을 반환합니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Future 객체 생성</span>
<span class="nc">TopicPartition</span><span class="o">,</span> <span class="nc">OffsetAndMetadata</span><span class="o">>></span> <span class="n">groupOffsetListingFuture</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">KafkaFutureImpl</span><span class="o"><>();</span>
<span class="o">...</span>
<span class="k">if</span> <span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">hasError</span><span class="o">())</span> <span class="o">{</span>
<span class="c1">// 예외 처리</span>
<span class="n">groupOffsetListingFuture</span><span class="o">.</span><span class="na">completeExceptionally</span><span class="o">(</span><span class="n">response</span><span class="o">.</span><span class="na">error</span><span class="o">().</span><span class="na">exception</span><span class="o">());</span>
<span class="o">}</span> <span class="k">else</span><span class="o">{</span>
<span class="o">...</span>
<span class="c1">// 응답 처리</span>
<span class="n">groupOffsetListingFuture</span><span class="o">.</span><span class="na">complete</span><span class="o">(</span><span class="n">groupOffsetsListing</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">// Future 객체 반환</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">ListConsumerGroupOffsetsResult</span><span class="o">(</span><span class="n">groupOffsetListingFuture</span><span class="o">);</span>
</code></pre></div></div>
<p>생략을 많이 해서 그렇지만 <code class="language-plaintext highlighter-rouge">groupOffsetsListing</code> 값 역시 콜백 형식으로 비동기적으로 할당됩니다.</p>
<p>책에 있는 다른 예시를 하나 더 살펴보겠습니다.
다음 함수는 상품을 인자로 받고 가격을 <code class="language-plaintext highlighter-rouge">Future</code> 를 반환합니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="nc">Future</span><span class="o"><</span><span class="nc">Double</span><span class="o">></span> <span class="nf">getPriceAsync</span><span class="o">(</span><span class="nc">String</span> <span class="n">product</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">CompletableFuture</span><span class="o">.</span><span class="na">supplyAsync</span><span class="o">(()</span> <span class="o">-></span> <span class="n">calculatePrice</span><span class="o">(</span><span class="n">product</span><span class="o">));</span>
<span class="o">}</span>
</code></pre></div></div>
<p><em>출처: 모던 자바 인 액션</em></p>
<h3 id="forkjoinpool">ForkJoinPool</h3>
<p><a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ForkJoinPool.html"><code class="language-plaintext highlighter-rouge">ForkJoinPool</code></a> 은 앞서 살펴보았던 쓰레드 풀 방식에서 알고리즘을 추가하여 병렬적으로 태스크를 실행할 수 있는 <code class="language-plaintext highlighter-rouge">ExecutorService</code> 의 구현체입니다.</p>
<p>포크/조인 프레임워크라고 부르는데요, 이를 사용하기 위해서는 <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/RecursiveTask.html"><code class="language-plaintext highlighter-rouge">RecursiveTask</code></a>,
<a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/RecursiveAction.html"><code class="language-plaintext highlighter-rouge">RecursiveAction</code></a> 등을 상속받아 구현하셔야 합니다.</p>
<p>포크 조인 프레임워크는 하나의 큰 작업을 여러개의 작은 작업들로 나눕니다.
적당히 작은 크기까지 나눠지게 되면 해당 작업을 처리하고 결과를 반환합니다.
각각의 작업 결과들을 다시 합쳐서 최종 결과를 반환하게 됩니다.
어떻게 보면 divide and conquer 알고리즘과 비슷합니다.</p>
<p><img src="https://seonkyuKim.github.io/assets/images/2021-07-09-java-concurrent/fork-join-1.png" alt="" class="align-center" /></p>
<p>그렇기 때문에 저희가 주로 구현해야할 부분의 의사코드는 다음과 같습니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// compute 메서드</span>
<span class="k">if</span> <span class="o">(</span><span class="n">태스크가</span> <span class="n">충분히</span> <span class="n">작거나</span> <span class="n">더</span> <span class="n">이상</span> <span class="n">분할할</span> <span class="n">수</span> <span class="n">없으면</span><span class="o">)</span> <span class="o">{</span>
<span class="n">순차적으로</span> <span class="n">태스크</span> <span class="n">계산</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">태스크를</span> <span class="n">두</span> <span class="n">서브태스크로</span> <span class="n">분할</span>
<span class="n">태스크가</span> <span class="n">다시</span> <span class="n">서브태스크로</span> <span class="n">분할되도록</span> <span class="n">이</span> <span class="n">메서드를</span> <span class="n">재귀적으로</span> <span class="n">호출</span>
<span class="n">모든</span> <span class="n">서브태스크의</span> <span class="n">연산이</span> <span class="n">완료될</span> <span class="n">때까지</span> <span class="n">기다림</span>
<span class="n">각</span> <span class="n">서브태스크의</span> <span class="n">결과를</span> <span class="n">합침</span>
<span class="o">}</span>
</code></pre></div></div>
<p><em>출처: 모던 자바 인 액션</em></p>
<p>그렇다면 쓰레드 풀 방식보다 이 방식이 좋은 점은 무엇일까요?
포크/조인 프레임워크는 각각의 쓰레드가 더 이상 배정된 태스크가 없을 때, 다른 쓰레드로부터 <code class="language-plaintext highlighter-rouge">work-stealing</code>을 합니다.</p>
<p>포크/조인 프레임워크를 사용하지 않고 4개의 쓰레드에 작업 시간이 다른 태스크 4개를 제출했을 때 모습입니다:</p>
<p><img src="https://seonkyuKim.github.io/assets/images/2021-07-09-java-concurrent/fork-join-2.png" alt="" class="align-center" /></p>
<p>포크/조인 프레임워크를 사용하여 태스크를 서브태스크로 충분히 작게 나눈 후 <code class="language-plaintext highlighter-rouge">work-stealing</code> 과정이 일어날 때의 모습입니다:</p>
<p><img src="https://seonkyuKim.github.io/assets/images/2021-07-09-java-concurrent/fork-join-3.png" alt="" class="align-center" /></p>
<p>하나의 태스크를 여러 개의 서브 태스크로 나누는데도 비용이 들어갑니다.
따라서 서브태스크로 나누는데 드는 비용과 서브태스크를 처리하는데 드는 비용을 잘 고려하여 충분히 작은 서브태스크 크기를 결정하시기 바랍니다.</p>
<p>포크/조인 프레임워크를 살펴본 이유는 다음에 나올 Parallel Stream 에서 포크/조인 프레임워크를 사용하기 때문입니다.</p>
<h3 id="parallel-stream">Parallel Stream</h3>
<p>Java <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/stream/Stream.html"><code class="language-plaintext highlighter-rouge">Stream</code></a> 을 사용하다보면 <code class="language-plaintext highlighter-rouge">parallel()</code> 함수를 이용해 매우 손쉽게 병렬로 처리할 수 있습니다.
병렬로 처리하면 막연히 빨라질 것이라는 기대를 하게 합니다.</p>
<p>그러나 이제는 parallel stream 이 어떤 방식으로 동작하는지 알고 있으므로 어느 부분에 주의해야 할지 생각해볼 수 있습니다.</p>
<p><strong>분할</strong></p>
<p>Parallel Stream 을 이용하게 되면 내부에서 포크/조인 프레임워크에 따라 Stream 들이 작은 크기의 Stream 들로 분할 될 것입니다.
분할 알고리즘은 <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Spliterator.html"><code class="language-plaintext highlighter-rouge">Spliterator</code></a> 인터페이스에서 담당하며,
기본 자료구조들은 미리 정의된 Spliterator 를 반환하는 메서드들을 포함하고 있습니다.
<code class="language-plaintext highlighter-rouge">LinkedList</code> 같은 경우 iterate 하면서 분할하지만, <code class="language-plaintext highlighter-rouge">ArrayList</code> 의 경우 index를 통해 요소들을 탐색하지 않아도 분할할 수 있으므로 분할 속도가 더 빠릅니다.
비슷한 이유로 분할 시 iterate 를 해야하는 자료구조는 분할 속도가 느립니다.</p>
<p>기본 Spliterator 를 사용할 경우에는 매우 작은 크기로 분할되기 때문에 요소 하나하나의 처리 시간이 오래 걸릴 수록 병렬 스트림을 효과적으로 사용할 여지가 높습니다.
요소 하나하나의 처리 시간이 짧을 경우 분할 과정에서 생기는 비용이 더 클 수 있기 때문이죠.</p>
<p>소량의 데이터를 병렬로 작업하는 경우에는 분할 과정에서 생기는 추가 비용을 상쇄할 수 있을 만큼의 이득을 얻지 못할 가능성이 높습니다.</p>
<p>Custom Spliterator 를 이용하여 분할 과정을 제어할 수도 있습니다.</p>
<p><strong>병합</strong></p>
<p>최종 연산의 병합 과정에 드는 비용도 고려해봐야합니다.
<a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/stream/Collectors.html"><code class="language-plaintext highlighter-rouge">Collectors</code></a> 의 combiner 메서드들의 비용도 한 번 살펴보시기 바랍니다.</p>
<p><strong>순서</strong></p>
<p>분할과 병합의 과정이 있으므로 요소 순서가 중요하지 않은 태스크를 병렬 스트림으로 실행할 때 더욱 효과적입니다.</p>
<p><strong>쓰레드 풀</strong></p>
<p>병렬 스트림은 내부적으로 <code class="language-plaintext highlighter-rouge">ForkJoinPool</code> 을 사용합니다.
기본적으로는 프로세서 수에 따라 쓰레드 풀 수가 결정됩니다.
이를 변경할 수도 있고, 원한다면 custom <code class="language-plaintext highlighter-rouge">ForkJoinPool</code> 을 생성하여 parallel stream 을 실행할 수도 있습니다.</p>
<p><strong>가장 중요한 것</strong></p>
<p>순차 스트림을 사용할지, 병렬 스트림을 사용할지 고민된다면 가장 중요한 것은 측정입니다.</p>
<p><em>출처: 모던 자바 인 액션</em></p>
<h3 id="공유-객체">공유 객체</h3>
<p>멀티 쓰레딩을 이용하면, 동일한 객체에 여러 쓰레드가 동시에 접근하여 알 수 없는 일들이 벌어지곤 합니다.
아래와 같은 클래스를 통해 간단한 예를 보여주고자 합니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">IdGenerator</span><span class="o">{</span>
<span class="kt">int</span> <span class="n">lastId</span><span class="o">;</span>
<span class="kd">public</span> <span class="kt">int</span> <span class="nf">incrementValue</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="o">++</span><span class="n">lastId</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">lastId</code> 의 초기값이 93이라고 가정하고 두 개의 쓰레드가 동일한 객체에 접근하여 <code class="language-plaintext highlighter-rouge">incrementValue()</code> 메서드를 호출할 때,
다음과 같이 세 가지 경우가 모두 가능합니다.
특히 마지막 경우도 가능하다는 점을 눈여겨 보시기 바랍니다.</p>
<ul>
<li>쓰레드 1이 94를 얻고, 쓰레드 2가 95를 얻고, lastId 가 95가 된다.</li>
<li>쓰레드 1이 95를 얻고, 쓰레드 2가 94를 얻고, lastId 가 95가 된다.</li>
<li>쓰레드 1이 94를 얻고, 쓰레드 2가 94를 얻고, lastId 가 94가 된다.</li>
</ul>
<p>그 이유는 ++ 가 원자적 연산이 아니기 때문입니다.</p>
<p>보통 이런 경우, <code class="language-plaintext highlighter-rouge">synchronized</code> 키워드를 통해 해결할 수 있습니다.
<code class="language-plaintext highlighter-rouge">synchronized</code>로 선언된 부분은 한 쓰레드가 접근할 때 lock 을 걸어 다른 쓰레드에서 접근하지 못하도록 합니다.
하지만, 그런 이유로 성능상 퍼포먼스가 떨어질 수 있습니다.</p>
<p>자바는 위와 같은 상황에서 사용할 수 있는 AtomicInteger, AtomicBoolean 등의 클래스들을 지원합니다.
해당 클래스들은 <code class="language-plaintext highlighter-rouge">synchronized</code> 키워드를 사용할 때보다 거의 더 빠릅니다.
(엇비슷한 경우는 있어도 느린 경우는 없습니다.)
위 클래스들은 현대 프로세서에서 제공하는 CAS 라는 연산을 통해 미리 lock 을 걸기 보다는, 문제를 감지하는 쪽으로 동작하기 때문입니다.</p>
<p><em>출처: 클린 코드, 부록 A 동시성2</em></p>김선규hopsprings2ternal@gmail.com멀티 쓰레딩에 대해 고민하는 시기가 오게 됩니다. 하지만 처음 구글링을 하다보면 생각보다 매우 다양한 정보에 휩쓸리게 됩니다. Thread, ExecutorService, Future, Parallel Stream 등등.. 이 글에서는 이론적인 내용부터 어플리케이션 레벨, 그리고 하드웨어 레벨까지 동시성에 대한 개념들을 포괄적으로 설명할 예정입니다. 어떻게 보면 제가 공부한 레퍼런스들의 총 집합이라고 볼 수 있겠습니다.카프카 리벨런싱에 대하여 (Kafka Rebalancing)2021-03-28T00:00:00+00:002021-03-28T00:00:00+00:00https://seonkyukim.github.io/kafka-rebalancing<blockquote>
<p>이 글은 <a href="https://medium.com/streamthoughts/apache-kafka-rebalance-protocol-or-the-magic-behind-your-streams-applications-e94baf68e4f2">Apache Kafka Rebalance Protocol, or the magic behind your streams applications</a> 을 일부 번역한 것입니다.</p>
</blockquote>
<p>카프카 리벨런싱은 <strong>Group Membership Protocol</strong> 과 <strong>Client Embedded Protocol</strong> 이라는 두 가지 프로토콜을 기반으로 이루어집니다.</p>
<h2 id="joingroup">JoinGroup</h2>
<p>컨슈머가 새로 시작할 때, 자신의 그룹을 관리하는 카프카 브로커의 coordinator를 찾기 위해 <code class="language-plaintext highlighter-rouge">FindCoordinator</code> 요청을 보냅니다.
그리고 나서, <code class="language-plaintext highlighter-rouge">JoinGroup</code> 요청을 통해 리벨런싱 프로토콜을 시작합니다.</p>
<p><img src="https://seonkyuKim.github.io/assets/images/2021-03-28-kafka-rebalancing/image1.jpeg" alt="" class="align-center" /></p>
<p><code class="language-plaintext highlighter-rouge">JoinGroup</code> 요청은 <code class="language-plaintext highlighter-rouge">session.timeout.ms</code>와 <code class="language-plaintext highlighter-rouge">max.poll.interval.ms</code> 등의 클라이언트 설정들을 포함합니다.
coordinator는 이 설정들을 그룹에서 컨슈머들로부터 응답이 없을 경우 제외시키기 위한 기준으로 사용합니다.</p>
<p>추가로, 이 요청은 두 가지 중요한 필드들을 포함합니다: 사용 가능한 client protocol의 목록, 그리고 프로토콜을 실행시킬 때 필요한 메타데이터들을 보냅니다.
client-protocol 은 <code class="language-plaintext highlighter-rouge">partition.assignment.strategy</code> 와 같은 컨슈머의 partition assignor 의 목록입니다.
메타 데이터들은 컨슈머가 구독하고 있는 topic 들입니다.</p>
<p><code class="language-plaintext highlighter-rouge">JoinGroup</code> 은 일종의 베리어 역할을 합니다.
즉, coordinator는 모든 컨슈머로부터 해당 요청을 받기 전(<code class="language-plaintext highlighter-rouge">group.initial.rebalance.delay.ms</code>)까지, 혹은 rebalance timeout 이 발생하기 전까지 응답하지 않습니다.</p>
<p><img src="https://seonkyuKim.github.io/assets/images/2021-03-28-kafka-rebalancing/image2.jpeg" alt="" class="align-center" /></p>
<p>그룹 안의 첫 번째 컨슈머는 <strong>group leader</strong>로서 활동하며, 정상 작동중인 구성원들의 목록, 그리고 선택된 assignment strategy를 응답받습니다.
group leader는 파티션 배정의 책임을 지게 됩니다.</p>
<h2 id="syncgroup">SyncGroup</h2>
<p>다음으로 모든 구성원들은 coordinator에게 <code class="language-plaintext highlighter-rouge">SyncGroup</code> 요청을 보냅니다.
그룹 리더는 계산한 파티션 배치를 함께 보내며, 다른 컨슈머들은 그냥 빈 요청을 보냅니다.</p>
<p><img src="https://seonkyuKim.github.io/assets/images/2021-03-28-kafka-rebalancing/image3.jpeg" alt="" class="align-center" /></p>
<p>coordinator는 모든 <code class="language-plaintext highlighter-rouge">SyncGroup</code> 요청에 응답을 보내고, 각각의 컨슈머들은 그들에게 배정된 파티션들을 응답받습니다.
그리고 <code class="language-plaintext highlighter-rouge">onPartitionsAssignmedMethod</code> 가 실행되며, 메시지들을 fetch 해오기 시작합니다.</p>
<p><img src="https://seonkyuKim.github.io/assets/images/2021-03-28-kafka-rebalancing/image4.jpeg" alt="" class="align-center" /></p>
<h2 id="heartbeat">Heartbeat</h2>
<p>마지막으로 가장 중요한 것은, 각각의 컨슈머들은 session을 유지하기 위해 주기적으로 <code class="language-plaintext highlighter-rouge">Heartbeat</code> 요청을 브로커 coordinator에게 보내는다는 것입니다.(<code class="language-plaintext highlighter-rouge">heartbeat.interval.ms</code>)</p>
<p>만약 리벨런싱이 진행중인 경우, coordinator 는 <code class="language-plaintext highlighter-rouge">Heartbeat</code> 응답으로 컨슈머에게 그룹 rejoin이 필요하다고 알려줍니다.</p>
<p><img src="https://seonkyuKim.github.io/assets/images/2021-03-28-kafka-rebalancing/image5.jpeg" alt="" class="align-center" /></p>
<h2 id="몇-가지-주의점">몇 가지 주의점</h2>
<p>리벨런싱 프로토콜의 첫 번째 한계점은 전체를 멈추지 않고는 리벨런싱을 완료할 수 없다는 것입니다. (<em>stop-the-world effect</em>)</p>
<p>예룰 들어, 하나의 인스턴스를 멈춰봅시다.
첫 번째 리벨런싱은 컨슈머가 coordinator에게 멈추기 전 <code class="language-plaintext highlighter-rouge">LeaveGroup</code> 요청을 보내면서 시작됩니다.</p>
<p><img src="https://seonkyuKim.github.io/assets/images/2021-03-28-kafka-rebalancing/image6.jpeg" alt="" class="align-center" /></p>
<p>남아 있는 컨슈머들은 다음 번 <code class="language-plaintext highlighter-rouge">Heartbeat</code> 때에야 리벨런싱이 필요하다는 사실을 알게 되고, 파티션 재배치를 위해 새로운 <code class="language-plaintext highlighter-rouge">JoinGroup/SyncGroup</code> 을 시작하게 될겁니다.</p>
<p><img src="https://seonkyuKim.github.io/assets/images/2021-03-28-kafka-rebalancing/image7.jpeg" alt="" class="align-center" /></p>
<p>파티션 재조정이 끝나기 전까지는, 컨슈머들은 아무런 데이터도 처리할 수 없습니다.
기본적으로 rebalance timeout은 5분으로 설정되어 있으며 상황에 따라서는 매우 큰 consumer-lag을 유발할 수 있습니다.</p>
<p>문제는 컨슈머가 다시 시작되면 어떤 일이 발생할까요? 컨슈머는 다시 그룹에 참여하기 위해 새로운 리벨런싱을 유발할 것이고, 또다시 전체 컨슈머가 멈추는 일이 발생할 것입니다.</p>
<p><img src="https://seonkyuKim.github.io/assets/images/2021-03-28-kafka-rebalancing/image8.jpeg" alt="" class="align-center" /></p>
<p>이와 같은 재시작은 rolling update 시에도 발생할 수 있습니다.
이런 시나리오는 컨슈머들에게는 매우 안 좋습니다.
실제로 세 개의 컨슈머들이 재시작 되면 총 6번의 파티션 재조정이 발생합니다.</p>
<p>최종적으로 카프카 컨슈머에서 가장 흔히 발생하는 문제는 네트워크 장애, 긴 GC 시간 등으로 heartbeat 요청이 실패한 경우와
데이터를 처리하는데 오래 걸려 poll 요청을 못하는 경우입니다.
첫 번째 경우, coordinator 는 <code class="language-plaintext highlighter-rouge">session.timeout.ms</code> 만큼의 시간동안 heartbeat 요청을 못받으면 컨슈머가 죽었다고 생각합니다.
두 번째의 경우, 데이터를 처리하는데 걸리는 시간은 <code class="language-plaintext highlighter-rouge">max.poll.interval.ms</code> 시간보다 적어야 합니다.</p>
<p><img src="https://seonkyuKim.github.io/assets/images/2021-03-28-kafka-rebalancing/image9.jpeg" alt="" class="align-center" /></p>
<p>리벨런싱을 효율적으로 하기 위한 몇 가지 방안들이 카프카 2.3 이후로 나왔습니다.
대표적으로는 static membership, incremental cooperative rebalancing 이 있습니다.
이와 같은 방법에 대해서는 <a href="https://medium.com/streamthoughts/apache-kafka-rebalance-protocol-or-the-magic-behind-your-streams-applications-e94baf68e4f2">Apache Kafka Rebalance Protocol, or the magic behind your streams applications</a>의 글 뒷부분을 참조해주시기 바랍니다.</p>김선규hopsprings2ternal@gmail.com이 글은 Apache Kafka Rebalance Protocol, or the magic behind your streams applications 을 일부 번역한 것입니다.파리에서 도시락을 파는 여자2021-03-11T00:00:00+00:002021-03-11T00:00:00+00:00https://seonkyukim.github.io/%ED%8C%8C%EB%A6%AC%EC%97%90%EC%84%9C%20%EB%8F%84%EC%8B%9C%EB%9D%BD%EC%9D%84%20%ED%8C%8C%EB%8A%94%20%EC%97%AC%EC%9E%90<blockquote>
<p>반드시 철저한 조사와 준비가 선행되어야 한다. 초심을 지키자. 기준을 세우자.</p>
</blockquote>
<p>‘자만심’, ‘경험 부족’, ‘공부 부족’. 즉, 스스로를 과대평가 했던 것. (p.53)</p>
<ul>
<li>‘공부 부족’ -> 이미 누군가는 비슷한 것을 이유로 실패했다. 물어보자.</li>
</ul>
<p>어느 정도 자리가 잡히면 통찰력을 갖고 미래를 내다보는 데 시간을 써야한다. (p.58)</p>
<p>수익을 올리지 못한 프로젝트에서 시간과 에너지를 써놓고는 ‘재미있었으니까 됐어’라는 식의 합리화부터 잘못되었다. (p.60)</p>
<p>누군가를 돕는 데도 현명함이 필요하다. (p.76)</p>
<p>체면과 자존심 때문에 자신을 계속해서 불행에 빠뜨리는 선택을 한다. (p.85)</p>
<p>행복의 다른 말은 흥분. 누구나 가슴이 뛰고 흥분되고 설레는 일을 할 때 행복을 느끼게 마련이다. (p.93)</p>
<p>사업가는 만들어진다. (p.95)</p>
<p>언젠가부터 창업자들 사이에서 실행력과 추진력이 사업가의 첫 번째 덕목인 것처럼 은연중에 퍼져가는 것 같다.
하지만 명심해야 한다.
준비가 뒷받침되지 않은 상황에서 실행력과 추진력만 발휘하는 건 눈을 감고 시속 200킬로미터로 차량을 모는 것과 같다.
따라서 사업을 하면서 오랫동안 돈도 벌고 행복하고 싶다면, <strong>반드시 철저한 조사와 준비가 선행</strong>되어야 한다.
단, 책상에 앉아서 인터넷으로 검색하는 게 아니라, 직접 발로 뛰고 눈으로 보면서 하는 준비여야 한다는 점을 꼭 강조하고 싶다. (p.98)</p>
<p>기준 없는 사업은 모르는 사람과 하는 결혼과 같다. (p.99)</p>
<p>준비는 철저히 하되, 그 시작은 미루지 말아야 한다. (p.109)</p>
<p>책은 몇 권 파다보면 그 책에 나온 것들을 나도 모르게 따라 하게 된다. (p.116)</p>
<p>누군가의 도움을 받고 싶으면 일단 전화번호부에서 그 사람의 번호를 찾아내세요. 그리고 전화를 걸고, 도와달라고 하세요. - 스티브 잡스 (p.132)</p>
<p>멘토를 찾는 기준에도 여러 가지가 있을 것이다 (p.134) - 아무에게나 묻지 말자.</p>
<p>그럼에도 그는 여전히 일주일에 한두 번은 회사에 나와서 우리에게 많은 조언을 해주고 있다.
심지어 그는 1년에 한 번씩 일본에 갈 때마다 새로운 메뉴나 트렌드를 공부해 와서 우리에게 전수해 준다.
이 역시 대가를 바라지 않고 하는 것이지만, 나로서는 최소한 그에 맞는 대가를 조금이라도 지불하고 싶었다. (p.140)</p>
<ul>
<li>왜 도와줄까?</li>
<li>진심이 중요</li>
</ul>
<p>사업적으로 도움을 요청할 때 반드시 기억해야 하는 점이 있다.
그 사람이 추구하는 가치, 비전, 철학, 전략 등이 나와 회사가 추구하는 그것과 잘 맞아떨어져야만 한다.
누군가에게 도움을 청하기 전에 반드시 그 사람의 철학과 비전 등에 대해서 조사해야 한다. (p.149)</p>
<p>왜 이 사업을 하는 사람이 켈리 최여야만 하는가 (p.152)</p>
<p>그 사람과 함께하는 ‘현재’가 좋아야 하는게 먼저다.
누군가를 설득할 때는 나의 마음가짐이나 열정, 진심과 철학이 어떠한지, 나는 좋은 사람인지 등에 대해서 진지하게 생각해볼 필요가 있다.
너무 당연한 말이지만, 먼저 호감과 믿음을 주는 사람이 되어야 하는 것이다. (p.168)</p>
<p>사람을 설득할 때 이런 차이를 보이는 것은 ‘얼마나 확신에 차 있는가’의 차이라고 본다.
나부터 믿음과 확신을 갖고 상대방을 위하는 진심을 담아 비전을 제시해보자.
아마도 상대는 오히려 당신에게 고마워하며 기꺼이 함께하고자 할 것이다.</p>
<p>성공이 행복의 열쇠가 아니라, 행복이 성공의 열쇠다.
만약 당신이 지금 하고 있는 일을 사랑한다면
당신은 성공한 것이다. - 알베르트 슈바이처</p>
<p>가족의 얼굴 볼 시간조차 없는 사업가는 되기 싫었다. (p.179)</p>
<p>사장이 일하지 않는 회사.
좋은 기업문화를 바탕으로 훌륭한 인재들이 알아서 회사를 성장시키고 발전시키는 회사,
사장이 장시간 자리를 비워도 아무런 타격이 없는 회사가 바로 내가 만들고자 하는 회사다. (p.193)</p>
<p>사장은 통찰력을 발휘해 멀리 바라보고 목적지를 정하는 사람이어야 한다. (p.194)</p>
<p>직원의 가족도 소중히 여긴다. (p.200)</p>
<p>고객들이 진정한 행복을 느낀다면 돈은 저절로 따라오게 되어 있기 때문이다. (p.215)</p>
<p>프렌차이즈의 경우 가맹점주들의 만족도가 상승하자 고객 서비스가 좋아졌다. (p.219)</p>
<p>윈-윈 관계가 유지되기 위해서는 절대로 감정적으로 대처해서는 안 된다. (p.227)</p>
<p>초심을 지키자.</p>
<p>어느 정도 자신감이 붙으면 이때부터 길이 나뉜다.
어떤 사람은 그럼에도 여전히 초심자의 태도를 유지하지만, 어떤 사람은 조금씩 스스로를 과신하기 시작한다.</p>
<p>허나 ‘마음가짐’만은 바뀌지 말아야 한다. 특히 열정과 겸손함만큼은 끝까지 지켜야 한다. (p.249)</p>
<p>‘검소함’ 역시 중요하다.
‘돈을 버는 것보다 어떻게 쓰느냐가 더 중요하다’는 점이다. (p.250)</p>
<p>사장인 내가 비용을 아낄 생각조차 하지 않는데 직원들이라고 비용을 아끼겠는가? (p.256)</p>
<p>‘지금 당장’보다 ‘장기적으로’ 좋은 길을 택한다. (p.258)</p>
<p>오너는 새로운 아이디어에 최대한 피드백하지 않는 게 좋다. (p.270)</p>
<p>직원들이 새로운 아이디어를 내면 격려해주고 그 아이디어를 테스트해볼 수 있도록 지원할 뿐, 결코 평가나 피드백을 하지 않는다. (p.273)</p>
<p>직원들이 마음껏 도전할 수 있는 환경은 ‘실패를 장려하는 문화’에서만 가능하다. (p.275)</p>김선규hopsprings2ternal@gmail.com반드시 철저한 조사와 준비가 선행되어야 한다. 초심을 지키자. 기준을 세우자.ulimit 확인 및 설정 방법2021-02-09T00:00:00+00:002021-02-09T00:00:00+00:00https://seonkyukim.github.io/ulimit<h2 id="ulimit-이란">ulimit 이란?</h2>
<p>프로세스가 사용하는 자원에 대한 제어(user limit)를 관리할 수 있게 도와줍니다.
<code class="language-plaintext highlighter-rouge">OutOfMemoryError</code> 가 발생했는데 쓰레드를 더 이상 생성할 수 없다는 메시지가 나온다면 이 설정을 의심해 볼 필요가 있습니다.</p>
<p>ulimit에서 자주 설정하게 되는 값은 <code class="language-plaintext highlighter-rouge">open file</code>과 <code class="language-plaintext highlighter-rouge">max user process</code> 설정입니다.
이 값에 따라 프로그램에서 소켓 커넥션 수, 혹은 생성 가능한 쓰레드 수가 제한됩니다.</p>
<p>Java를 사용하신다면 우아한형제들 블로그의 ‘<a href="https://woowabros.github.io/experience/2018/04/17/linux-maxuserprocess-openfiles.html">Java, max user processes, open files</a>’ 글을 읽어보시기 바랍니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>- Java에서 동시에 생성 가능한 쓰레드 수는 max user processes를 따라간다.
- Java에서 소켓 통신(HTTP API, JDBC 커넥션 등)은 open file 옵션을 따라간다.
- 단, JDK 내부 코드상에서 hard limit 값이 soft limit에 update된다.
- 파이썬의 경우는 soft limit 값 따라간다.
</code></pre></div></div>
<h2 id="ulimit-확인-방법">ulimit 확인 방법</h2>
<p>다음과 같은 명령어를 통해 ulimit을 확인하실 수 있습니다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 하드 설정 확인</span>
<span class="nv">$ </span><span class="nb">ulimit</span> <span class="nt">-a</span> <span class="nt">-H</span>
<span class="c"># 소프트 설정 확인</span>
<span class="nv">$ </span><span class="nb">ulimit</span> <span class="nt">-a</span> <span class="nt">-S</span>
</code></pre></div></div>
<p>example output:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">ulimit</span> <span class="nt">-a</span>
core file size <span class="o">(</span>blocks, <span class="nt">-c</span><span class="o">)</span> 0
data seg size <span class="o">(</span>kbytes, <span class="nt">-d</span><span class="o">)</span> unlimited
scheduling priority <span class="o">(</span><span class="nt">-e</span><span class="o">)</span> 0
file size <span class="o">(</span>blocks, <span class="nt">-f</span><span class="o">)</span> unlimited
pending signals <span class="o">(</span><span class="nt">-i</span><span class="o">)</span> 63195
max locked memory <span class="o">(</span>kbytes, <span class="nt">-l</span><span class="o">)</span> 64
max memory size <span class="o">(</span>kbytes, <span class="nt">-m</span><span class="o">)</span> unlimited
open files <span class="o">(</span><span class="nt">-n</span><span class="o">)</span> 1024
pipe size <span class="o">(</span>512 bytes, <span class="nt">-p</span><span class="o">)</span> 8
POSIX message queues <span class="o">(</span>bytes, <span class="nt">-q</span><span class="o">)</span> 819200
real-time priority <span class="o">(</span><span class="nt">-r</span><span class="o">)</span> 0
stack size <span class="o">(</span>kbytes, <span class="nt">-s</span><span class="o">)</span> 8192
cpu <span class="nb">time</span> <span class="o">(</span>seconds, <span class="nt">-t</span><span class="o">)</span> unlimited
max user processes <span class="o">(</span><span class="nt">-u</span><span class="o">)</span> 1024
virtual memory <span class="o">(</span>kbytes, <span class="nt">-v</span><span class="o">)</span> unlimited
file locks <span class="o">(</span><span class="nt">-x</span><span class="o">)</span> unlimited
</code></pre></div></div>
<h3 id="소프트-설정과-하드-설정">소프트 설정과 하드 설정</h3>
<p>ulimit 에서 설정할 수 있는 타입에는 두 가지가 있습니다. 바로 <code class="language-plaintext highlighter-rouge">soft</code>와 <code class="language-plaintext highlighter-rouge">hard</code> 설정입니다.</p>
<p>소프트 limit 역시 리소스를 제한하는 최대값 입니다.
어떤 유저가 할당된 제한 값 이상의 리소스를 사용할 수는 없습니다.</p>
<p>소프트 값은 0과 하드 limit 사이의 값을 가질 수 있습니다. (0 <= soft limit <= hard limit)</p>
<p>즉, 하드 limit은 소프트 limit들의 최대값 역할을 합니다.</p>
<p>하드 limit 은 root 사용자만 값을 증가시킬 수 있습니다.</p>
<h2 id="ulimit-설정-방법">ulimit 설정 방법</h2>
<p>ulimit 설정에는 명령어를 이용할 수도 있고, 설정 파일을 변경할 수도 있습니다.
명령어를 이용할 경우 세션이 끊어지면 설정이 초기화되므로 선호하지 않습니다.</p>
<h3 id="명령어">명령어</h3>
<p>위에서 <code class="language-plaintext highlighter-rouge">ulimit -a</code> 명령어 output 결과를 보시면 옵션들이 괄호 안에 있습니다.
ulimit 명령어에 옵션과 값을 주시면 해당 값이 변합니다.</p>
<p>다음은 max user process(-u) 값을 2048로 바꾸는 예제입니다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">ulimit</span> <span class="nt">-u</span> 2048
</code></pre></div></div>
<h3 id="설정파일">설정파일</h3>
<p>기본 파일은 <code class="language-plaintext highlighter-rouge">/etc/security/limits.conf</code> 입니다.</p>
<p>다음과 같이 생겼습니다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># /etc/security/limits.conf</span>
<span class="c">#</span>
...
<span class="c">#</span>
<span class="c">#<domain> <type> <item> <value></span>
<span class="c">#</span>
<span class="c">#Where:</span>
<span class="c">#<domain> can be:</span>
<span class="c"># - a user name</span>
<span class="c"># - a group name, with @group syntax</span>
<span class="c"># - the wildcard *, for default entry</span>
<span class="c"># - the wildcard %, can be also used with %group syntax,</span>
<span class="c"># for maxlogin limit</span>
<span class="c">#</span>
<span class="c">#<type> can have the two values:</span>
<span class="c"># - "soft" for enforcing the soft limits</span>
<span class="c"># - "hard" for enforcing hard limits</span>
<span class="c">#</span>
<span class="c">#<item> can be one of the following:</span>
<span class="c"># - core - limits the core file size (KB)</span>
<span class="c"># - data - max data size (KB)</span>
<span class="c"># - fsize - maximum filesize (KB)</span>
<span class="c"># - memlock - max locked-in-memory address space (KB)</span>
<span class="c"># - nofile - max number of open file descriptors</span>
<span class="c"># - rss - max resident set size (KB)</span>
<span class="c"># - stack - max stack size (KB)</span>
<span class="c"># - cpu - max CPU time (MIN)</span>
<span class="c"># - nproc - max number of processes</span>
<span class="c"># - as - address space limit (KB)</span>
<span class="c"># - maxlogins - max number of logins for this user</span>
<span class="c"># - maxsyslogins - max number of logins on the system</span>
<span class="c"># - priority - the priority to run user process with</span>
<span class="c"># - locks - max number of file locks the user can hold</span>
<span class="c"># - sigpending - max number of pending signals</span>
<span class="c"># - msgqueue - max memory used by POSIX message queues (bytes)</span>
<span class="c"># - nice - max nice priority allowed to raise to values: [-20, 19]</span>
<span class="c"># - rtprio - max realtime priority</span>
<span class="c">#</span>
<span class="c">#<domain> <type> <item> <value></span>
<span class="c">#</span>
<span class="c">#* soft core 0</span>
<span class="c">#* hard rss 10000</span>
<span class="c">#@student hard nproc 20</span>
<span class="c">#@faculty soft nproc 20</span>
<span class="c">#@faculty hard nproc 50</span>
<span class="c">#ftp hard nproc 0</span>
<span class="c">#@student - maxlogins 4</span>
<span class="c"># End of file</span>
</code></pre></div></div>
<p>살짝만 읽어보시면 친절하게 설정 방법을 알려줍니다.
한 row 에 domain, type, item, value 를 적어주시면 됩니다.</p>
<p>또한 주의해서 살펴보아야 할 파일은 <code class="language-plaintext highlighter-rouge">/etc/security/limits.d/</code> 아래에 있는 <code class="language-plaintext highlighter-rouge">.conf</code> 파일들입니다.
제가 위 파일의 주석에서 생략한 부분에는 <code class="language-plaintext highlighter-rouge">limits.d</code> 아래에 있는 <code class="language-plaintext highlighter-rouge">.conf</code> 파일들을 알파벳 순서로 읽으며, 기존 값이 있을 경우 override 합니다.</p>
<p>관리하게 편하게끔 <code class="language-plaintext highlighter-rouge">/etc/security/limits.d/</code> 아래에 role 별로 관리하시길 추천합니다.</p>
<h2 id="참고한-글">참고한 글</h2>
<ul>
<li><a href="https://woowabros.github.io/experience/2018/04/17/linux-maxuserprocess-openfiles.html">Java, max user processes, open files</a></li>
<li><a href="https://access.redhat.com/solutions/384633">What does soft, hard, and unlimited stand for in ulimit command?</a></li>
<li><a href="https://m.blog.naver.com/hanajava/221002536826">ulimit 설정</a></li>
</ul>김선규hopsprings2ternal@gmail.comulimit 이란?Linux 완전 기초! 파일 시스템, 프로세스 및 서비스, 로그파일 관리까지!2019-07-08T00:00:00+00:002019-07-08T00:00:00+00:00https://seonkyukim.github.io/tech/Linux%20%EC%99%84%EC%A0%84%20%EA%B8%B0%EC%B4%88!%20%ED%8C%8C%EC%9D%BC%20%EC%8B%9C%EC%8A%A4%ED%85%9C,%20%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%20%EB%B0%8F%20%EC%84%9C%EB%B9%84%EC%8A%A4,%20%EB%A1%9C%EA%B7%B8%ED%8C%8C%EC%9D%BC%20%EA%B4%80%EB%A6%AC%EA%B9%8C%EC%A7%80!<p>Linux를 잘 모르고 다루다보니 항상 stackoverflow를 활용해 주먹구구식으로 문제점들을 해쳐나갔다. 이제는 linux를 공부해 기본적인 것들을 알아야겠다고 느껴 공부하게 되었다.</p>
<h2 id="why-linux">Why Linux?</h2>
<h3 id="일관적인-운영-모델">일관적인 운영 모델</h3>
<p>어떤 버전의 Linux 배포판(CentOS, Debian 등등)이든 상관없이 기본적인 command line 문법, 프로세스 관리, 네트워크 관리 등 기본적인 조작법이 같다. 따라서 효율적인 개발을 통해 많은 비용을 절감할 수 있다.</p>
<h3 id="광범위성">광범위성</h3>
<p>Linux 운영체제는 매우 광범위하게 이용된다. 슈퍼 컴퓨터에서 초소형 기기까지 모든 분야에 걸쳐 사용되고 있다.</p>
<h3 id="오픈소스와-커뮤니티">오픈소스와 커뮤니티</h3>
<p>매우 활발하게 오픈소스가 개발되고 있으며 커뮤니티도 운영되고 있다.</p>
<h3 id="모든-네트워크-기능">모든 네트워크 기능</h3>
<p>매우 오랜 기간동안 강력한 네트워크 기능들이 만들어졌다. 따라서 routing, briding, DNS, DHCP, virtual networking 등 거의 모든 네트워크 기능들을 사용할 수 있다.</p>
<h3 id="package-management">Package management</h3>
<p>몇 가지 명령어로 매우 쉽게 새로운 서비스들을 설치할 수 있다. 간단하게 Apple의 App store, 안드로이드의 Play Store라고 비유할 수 있다. 패키지 매니저의 예시로는 <strong>apt, rpm, yum</strong> 등이 있다.</p>
<h2 id="리눅스-사용하기">리눅스 사용하기</h2>
<h3 id="ssh-서버-접속">SSH 서버 접속</h3>
<h3 id="리눅스-버전-확인하기">리눅스 버전 확인하기</h3>
<p>리눅스에는 매우 많은 버전이 있기 때문에 때때로 자신의 리눅스 버전을 확인해야 할 때가 있다. 이때 <strong>uname</strong> 이라는 명령어를 사용한다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ uname -a
</code></pre></div></div>
<p>또한 hostname, machine ID 등을 확인하기 위해서는 <strong>hostnamectl</strong> 이라는 명령어를 사용한다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ hostnamectl
Static hostname: debian
Icon name:
...
Machine ID:
Operating System:
Kernel:
Architecture:
</code></pre></div></div>
<p class="notice--info"><strong>hostname이란?</strong><br /><br />쉽게 말해 IP주소 대신 사람이 읽기 쉬운 형식으로 지어지는 고유 id 값이다<br />자세한 사항은 다음 링크를 잠고하여라: <a href="https://ko.wikipedia.org/wiki/%ED%98%B8%EC%8A%A4%ED%8A%B8%EB%AA%85">https://ko.wikipedia.org/wiki/트명</a></p>
<h2 id="리눅스-파일-시스템">리눅스 파일 시스템</h2>
<p>파일 시스템은 directory, 혹은 folder라는 개념을 사용해 이뤄진다. 윈도우에서의 ‘파일 탐색기’를 생각하면 된다. 윈도우에서는 저장소의 가장 상위 지점이 ‘C: drive’와 같은 특정 드라이브 명이다. 하지만 Linux의 파일 시스템 가장 상위 지점은 “/”, 혹은 “root”이다. 전체적인 구조는 다음과 같다.</p>
<p><img src="https://seonkyuKim.github.io/assets/images/2019-07-08-Linux 완전 기초! 파일 시스템, 프로세스 및 서비스, 로그파일 관리까지!/01.png" alt="" class="align-center" /></p>
<p>출처: David M. Davis. <em>Linux Networking 101</em> ActualTech Media.</p>
<h3 id="기본적인-파일-시스템-관련-명령어">기본적인 파일 시스템 관련 명령어</h3>
<ul>
<li><strong>pwd</strong></li>
<li><strong>ls</strong>. 파일에 관한 메타 데이터를 같이 확인하고 싶다면, ls -la</li>
<li><strong>cd</strong></li>
<li><strong>rm</strong></li>
<li><strong>mkdir, rmdir</strong></li>
</ul>
<h3 id="linux-파일-시스템의-중요한-디렉토리들">Linux 파일 시스템의 중요한 디렉토리들</h3>
<ul>
<li><strong>/bin, /sbin, /usr/bin,</strong> and <strong>/usr/sbin</strong> : 실행 가능한 프로그램들이 저장되는 곳.</li>
<li><strong>/dev</strong> : 하드웨어 기기들을 나타내는 파일들이 저장되는 곳. 예를 들어 플로피 디스크를 갖고 있다면, <strong>/dev/fd0</strong> 디렉토리의 <strong>fd0</strong></li>
<li><strong>/etc</strong> : Configuration 파일이 저장되는 곳.
<ul>
<li><strong>/home</strong> : 각각의 사용자에 대해 사용자 디렉토리가 저장되는 곳.</li>
<li><strong>/var</strong> : 로그 파일과 같은 variable-length 파일들이 저장되는 곳.</li>
</ul>
</li>
</ul>
<p class="notice--info">더 자세한 사항을 알고 싶다면 <a href="https://ko.wikipedia.org/wiki/%ED%8C%8C%EC%9D%BC%EC%8B%9C%EC%8A%A4%ED%85%9C_%EA%B3%84%EC%B8%B5%EA%B5%AC%EC%A1%B0_%ED%91%9C%EC%A4%80">FHS</a>(<em>file system Hierarchy Standard</em>)를 살펴보십시오</p>
<h2 id="프로그램-실행을-어떻게-하는가">프로그램 실행을 어떻게 하는가?</h2>
<p>터미널에서 작업을 해봤다면 <strong>cat, cp, ps</strong>와 같은 명령어를 사용했을 것이다. 놀라운 사실은 이런 명령어조차 하나의 응용 프로그램이라는 것이다. 이와 같은 시스템 응용 프로그램은 /sbin, 혹은 /usr/sbin 과 같은 디렉토리 안에 있다. 하지만 위의 명령어를 사용하기 위해서 해당 디렉토리로 이동할 필요가 없다. 이를 가능하게 하는 것이 <strong>$PATH</strong> 변수이다.</p>
<p>CLI로 명령어를 치게 되면, $PATH 변수에 저장된 디렉토리 경로들을 검색하여 해당하는 프로그램을 실행시킨다.</p>
<p>$PATH 변수는 다음과 같이 확인할 수 있다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
</code></pre></div></div>
<p>만약 실행시키고자 하는 프로그램이 $PATH 안에 없다면, 직접 해당 프로그램이 존재하는 디렉토리로 이동하여 실행을 시키거나, 실행시키고자 하는 프로그램의 전체 경로를 입력해주어야 한다.</p>
<h3 id="사용하는-프로그램-경로-확인하기">사용하는 프로그램 경로 확인하기</h3>
<p>실행하고자 하는 프로그램이 어떤 경로에 있는지 확인하기 위해서는 <strong>which</strong> 명령어를 사용하면 된다. 예를 들어 python에는 다양한 버전이 있으며, 사용하고 있는 python이 어느 경로의 python인지 확인하기 위해 사용한다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ which python
/usr/bin/python
</code></pre></div></div>
<h3 id="프로그램-설치">프로그램 설치</h3>
<ul>
<li><strong>apt update</strong> : 패키지 목록 업데이트</li>
<li><strong>apt install (프로그램명)</strong> : 프로그램 다운로드</li>
<li><strong>apt show (프로그램명)</strong> : 프로그램 인스톨 확인</li>
</ul>
<h2 id="linux-processes-programs-and-services">Linux Processes, Programs, and Services</h2>
<p>프로그램을 실행시킬 때 터미널에서 정보를 주고받을 것이다. 하지만, 이 프로그램을 background에서 실행하기 원할 수 있다(service라 부른다). 이 경우, background에서 실행을 한 뒤, 완료되면 알림을 받게 된다.</p>
<p>하지만 background에서 진행중인 process 를 어떻게 확인할 수 있을까? 주로 <strong>-ef</strong>라는 flag와 함께 <strong>ps</strong> 명령어를 사용해 확인할 수 있다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ **ps -ef**
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Jul05 ? 00:00:02 /sbin/init
root 2 0 0 Jul05 ? 00:00:00 [kthreadd]
root 4 2 0 Jul05 ? 00:00:00 [kworker/0:0H]
...
</code></pre></div></div>
<ul>
<li>UID : user identifier. 사용자 식별자</li>
<li>PID : process identifier. 프로세스 식별자</li>
</ul>
<p>만약 여러분이 실행 중인 프로세스들을 보고 싶다면 <strong>ps</strong> 명령어를 사용해라.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ps
PID TTY TIME CMD
11637 pts/1 00:00:00 bash
11649 pts/1 00:00:00 ps
</code></pre></div></div>
<p>위 예시는 사용자가 <strong>bash</strong> shell을 쓰고있고, <strong>ps</strong> 명령어를 사용하고 있다.</p>
<p>Linux는 <em>system services</em>의 개념을 사용하고 있다. <em>system services</em>란 주로 시스템 사용자 대신 서비스를 제공하는 백그라운드에서 실행되는 프로그램들이다. <strong>systemctl</strong> 명령어로 확인할 수 있다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ systemctl
- debian
State: running
Jobs: 0 queued
Failed: 0 units
Since: Fri 2019-07-05 07:48:09 UTC; 2 days ago
CGroup: /
├─user.slice
│ └─user-1000.slice
│ ├─user@1000.service
...
</code></pre></div></div>
<h2 id="로그-파일-확인하기">로그 파일 확인하기</h2>
<p>시스템의 로그 파일을 확인하는 것은 매우 중요한데, 대부분의 시스템 로그 파일들은 /var/log에 있다. /var/log 디렉토리로 이동한 후 ls -l 명령어로 시스템 로그 파일들을 확인할 수 있다.</p>
<ul>
<li><strong>syslog :</strong> 제일 중심적인 로깅 시스템이다. 여기서는 kernel, application 등과 관련된 메시지들을 확인할 수 있다. 조금 더 확장하면 데이터 센터의 모든 로그 파일들을 저장할 수 있다.</li>
<li><strong>auth.log</strong> : 인증(authentication)의 실패와 성공 기록이 담겨있다.</li>
<li><strong>messages</strong> : 모든 종류의 일반적인 시스템 메시지가 담겨있다.</li>
</ul>
<p>파일을 확인하는 몇 가지 유용한 명령어들이다.</p>
<ul>
<li><strong>cat</strong></li>
<li><strong>less</strong> : pagination과 scroll을 통해 파일을 보여준다.</li>
<li><strong>grep</strong> : 파일 안에 특정 문자열을 찾고 싶을 때 사용한다. <strong>grep PATTERN [FILE]</strong> 과 같이 사용한다.</li>
<li><strong>head</strong> : 파일의 첫 줄을 보여준다.</li>
<li><strong>tail</strong> : 파일의 마지막 줄을 보여준다. 주로 로그 파일을 확인할 때 <strong>tail -f /var/log/syslog</strong> 와 같이 사용한다.</li>
</ul>
<h2 id="users-and-superusers">Users and Superusers</h2>
<p>리눅스에서 사용자를 생성하고 수정하고 삭제하는 것이 가능하다.</p>
<p>리눅스에서 관리자 권한을 <em>supersuer</em> 권한이라 하며 이는 root 사용자의 권한과 같다. root 의 UID는 0이다.</p>
<p>사용자와 관련된 명령어가 몇 가지 있다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ id
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),108(lxd),114(netdev)
$ whoami
ubuntu
$ sudo id
uid=0(root) gid=0(root) groups=0(root)
$ sudo whoami
root
</code></pre></div></div>
<ul>
<li><strong>id</strong> : UID를 확인할 수 있다.</li>
<li><strong>whoami</strong> : 사용자 이름을 반환한다.</li>
</ul>
<h2 id="참고-문헌">참고 문헌</h2>
<ul>
<li>David M. Davis. <em>Linux Networking 101</em> ActualTech Media. ActualTechMedia</li>
</ul>김선규hopsprings2ternal@gmail.comLinux를 잘 모르고 다루다보니 항상 stackoverflow를 활용해 주먹구구식으로 문제점들을 해쳐나갔다. 이제는 linux를 공부해 기본적인 것들을 알아야겠다고 느껴 공부하게 되었다.maria DB 기초: 설치부터 유저 생성까지2019-04-01T00:00:00+00:002019-04-01T00:00:00+00:00https://seonkyukim.github.io/tech/Maria-DB-basics<h3 id="mariadb-설치하기mac-기준">mariaDB 설치하기(mac 기준)</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew install mariadb
</code></pre></div></div>
<h3 id="mariadb-서버-실행하기">mariaDB 서버 실행하기</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql.server start
</code></pre></div></div>
<h3 id="mariadb-로그인하기">mariaDB 로그인하기</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql -u root
</code></pre></div></div>
<h3 id="데이터베이스-확인하기">데이터베이스 확인하기</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SHOW databases;
</code></pre></div></div>
<h3 id="데이터베이스-만들기">데이터베이스 만들기</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CREATE DATABASE bookstoredb;
</code></pre></div></div>
<h3 id="데이터베이스-사용하기">데이터베이스 사용하기</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>USE bookstoredb;
</code></pre></div></div>
<h2 id="테이블-생성하기">테이블 생성하기</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CREATE TABLE authorstbl;
</code></pre></div></div>
<p>다음과 같은 에러:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ERROR 1113 (42000): A table must have at least 1 column
</code></pre></div></div>
<p>따라서 데이터베이스 칼럼과 같이 생성:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CREATE TABLE authorstbl (
-> AuthorID INT NOT NULL AUTO_INCREMENT,
-> AuthorName VARCHAR(100),
-> PRIMARY KEY(AuthorID)
-> );
CREATE TABLE bookstbl (
-> BookID INT NOT NULL AUTO_INCREMENT,
-> BookName VARCHAR(100) NOT NULL,
-> AuthorID INT NOT NULL,
-> BookPrice DECIMAL(6,2) NOT NULL,
-> BookLastUpdated TIMESTAMP,
-> BookIsAvailable BOOLEAN,
-> PRIMARY KEY (BookID),
-> FOREIGN KEY (AuthorID) REFERENCES authorstbl(AuthorID)
-> );
</code></pre></div></div>
<p><a href="http://sqlmvp.kr/220340062504">여기</a>에서 다양한 데이터 타입을 살펴볼 수 있음.</p>
<h2 id="레코드-삽입하기">레코드 삽입하기</h2>
<p><strong>특정 칼럼만 삽입</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>INSERT INTO authorstbl (AuthorName)
-> VALUES ('Agatha Christie'), ('Stephen King'), ('John Clancy');
</code></pre></div></div>
<p><strong>전체 칼럼 삽입</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>INSERT INTO bookstbl (BookName, AuthorID, BookPrice, BookIsAvailable) VALUES
-> ('And Then There were None', 1, 14.95, 1),
-> ('The Man in the Brown Suit', 1, 23.99, 1),
-> ('The Stand', 2, 35.99, 1),
-> ('The Green Mile', 2, 23.99, 1),
-> ('Jack Ryan - The Preque', 3, 24.99, 1);
</code></pre></div></div>
<h2 id="레코드-확인하기">레코드 확인하기</h2>
<p><strong>모든 레코드</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SELECT * FROM authorstbl;
</code></pre></div></div>
<p><strong>특정 레코드</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SELECT * FROM authorstbl WHERE AuthorName='Agatha Christie';
</code></pre></div></div>
<p><strong>특정 칼럼</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SELECT authorstbl.AuthorName FROM authorstbl;
</code></pre></div></div>
<h3 id="레코드-수정하기">레코드 수정하기</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>UPDATE bookstbl SET BookPrice=14.99 WHERE AuthorID=2;
</code></pre></div></div>
<h3 id="레코드-삭제하기">레코드 삭제하기</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>DELETE FROM bookstbl WHERE BookID=4;
</code></pre></div></div>
<h2 id="advanced-select-statement">Advanced Select Statement</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SELECT CONCAT(bookstbl.BookName, ' (', authorstbl.AuthorName, ')') AS Description,
-> bookstbl.BookPrice FROM authorstbl
-> JOIN bookstbl ON authorstbl.AuthorID = bookstbl.AuthorID;
</code></pre></div></div>
<p>Concat은 ( )안의 항목들을 이어서 보여줌.</p>
<p>JOIN에 대한 자세한 설명은 <a href="https://postitforhooney.tistory.com/entry/DBMARIADB-SQL-%EC%98%88%EC%A0%9C%EB%A5%BC-%ED%86%B5%ED%95%9C-JOIN%EC%9D%98-%EC%A2%85%EB%A5%98-%ED%8C%8C%EC%95%85">여기</a>를 참조.</p>
<h2 id="user-생성하기">User 생성하기</h2>
<p>Note: 유저를 생성하기 전, 원하는 데이터베이스에 접근해 있는지 확인할 것!</p>
<p>비밀번호를 통해 유저 확인</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CREATE USER bookstoredbadmin@localhost IDENTIFIED BY 'PASSWORD';
</code></pre></div></div>
<p>권한 부여</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GRANT ALL PRIVILEGES ON bookstoredb.* TO bookstoredbadmin@localhost;
</code></pre></div></div>
<p>새로고침</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FLUSH PRIVILEGES;
</code></pre></div></div>
<p><strong>참고 :</strong></p>
<ul>
<li>
<p>유튜브 강의 : <a href="https://www.youtube.com/watch?v=RZa3VYVohXg">https://www.youtube.com/watch?v=RZa3VYVohXg</a></p>
</li>
<li>
<p>[Mac] 마리아DB!! mariaDB - 세팅하기(초보는 힘드렁)출처: <a href="https://devuryu.tistory.com/41">https://devuryu.tistory.com/41</a></p>
</li>
</ul>김선규hopsprings2ternal@gmail.commariaDB 설치하기(mac 기준)17: 브랜치 워크플로우(branch workflow)2019-03-20T00:00:00+00:002019-03-20T00:00:00+00:00https://seonkyukim.github.io/git-tutorial/git-branch-workflow<p>지금까지 먼 길을 달려오시느랴 수고하셨습니다. 이번 장에서는 여러분이 열심히 Git을 배워오신 궁극적인 이유와 관련이 깊다고 생각을 합니다. 이제 프로젝트를 진행할 때 브랜치를 사용한 어떤 워크플로우(<em>workflow</em>)들이 있는지를 배우고, 다음 장에서 브랜치 관리법에 대해 배우도록 하겠습니다. 이 부분에는 명확한 답변이 없기 때문에 다양한 방법들을 소개해드리도록 하겠습니다.</p>
<h2 id="로컬-환경에서의-워크플로우">로컬 환경에서의 워크플로우</h2>
<p>개인 로컬 환경에서 어떤 식으로 작업을 하면 좋은지에 대한 설명입니다.</p>
<p>‘<a href="https://git-scm.com/book/ko/v2/Git-%EB%B8%8C%EB%9E%9C%EC%B9%98-%EB%B8%8C%EB%9E%9C%EC%B9%98-%EC%9B%8C%ED%81%AC%ED%94%8C%EB%A1%9C">3.4 Git 브랜치 - 브랜치 워크플로</a>‘의 <strong>Long-Running 브랜치</strong>와 <strong>토픽 브랜치</strong>에 대한 설명을 읽고 오시기 바랍니다.</p>
<h2 id="분산-환경에서의-워크플로우">분산 환경에서의 워크플로우</h2>
<p>‘<a href="https://developer.ibm.com/kr/developer-%EA%B8%B0%EC%88%A0-%ED%8F%AC%EB%9F%BC/2018/02/05/github-collaboration/">GitHub를 이용하여 협력하기</a>’</p>
<p>‘<a href="http://woowabros.github.io/experience/2017/10/30/baemin-mobile-git-branch-strategy.html">우린 Git-flow를 사용하고 있어요 - 우아한 형제들 기술블로그</a>’</p>
<p>‘<a href="https://ujuc.github.io/2015/12/16/git-flow-github-flow-gitlab-flow/">Git flow, GitHub flow, GitLab flow</a>’ : work flow에 다양한 이름이 붙어있습니다. 참고해보세요!</p>
<p>‘<a href="https://lhy.kr/git-workflow">Git을 이용한 협업 워크플로우</a>’</p>
<p>‘<a href="https://medium.freecodecamp.org/how-to-use-git-efficiently-54320a236369">How to use Git efficiently</a>’</p>
<p>‘<a href="https://git-scm.com/book/ko/v2/%EB%B6%84%EC%82%B0-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C%EC%9D%98-Git-%EB%B6%84%EC%82%B0-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C%EC%9D%98-%EC%9B%8C%ED%81%AC%ED%94%8C%EB%A1%9C">5.1 분산 환경에서의 Git - 분산 환경에서의 워크플로</a>’ : git book 5단원 내용이 모두 분산 환경을 다루고 있습니다.</p>김선규hopsprings2ternal@gmail.com지금까지 먼 길을 달려오시느랴 수고하셨습니다. 이번 장에서는 여러분이 열심히 Git을 배워오신 궁극적인 이유와 관련이 깊다고 생각을 합니다. 이제 프로젝트를 진행할 때 브랜치를 사용한 어떤 워크플로우(workflow)들이 있는지를 배우고, 다음 장에서 브랜치 관리법에 대해 배우도록 하겠습니다. 이 부분에는 명확한 답변이 없기 때문에 다양한 방법들을 소개해드리도록 하겠습니다.16: 리모트 브랜치 만들기2019-03-18T00:00:00+00:002019-03-18T00:00:00+00:00https://seonkyukim.github.io/git-tutorial/git-remote-branch-2<p>저번 시간에는 리모트 브랜치, 트래킹 브랜치들의 개념을 살펴보았습니다. 이번에는 각각의 브랜치를 어떻게 만드는지 알아보도록 하겠습니다.</p>
<h2 id="리모트-브랜치-만들기">리모트 브랜치 만들기</h2>
<p>로컬에서 생성한 브랜치들은 <code class="language-plaintext highlighter-rouge">git push</code>를 할 경우 자동으로 리모트 저장소로 push 되지 않습니다. 다음과 같이 명시적으로 코드를 작성해주어야 합니다:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git push <remote> <localBranch>
</code></pre></div></div>
<p>위는 리모트 브랜치로 해당 로컬 브랜치를 push합니다. 리모트 브랜치에서는 로컬 브랜치와 다른 이름을 쓰고 싶다면 다음과 같이 명령어를 작성해야 합니다:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git push <remote> <localBranch>:<branchName>
</code></pre></div></div>
<h2 id="트래킹-브랜치-만들기">트래킹 브랜치 만들기</h2>
<p>트래킹 브랜치를 만드는 방법에는 여러 가지가 있습니다. 다음 방법들은 브랜치의 이름과 관련되어 있을 뿐, 본질적으로 같다고 생각하시면 됩니다. 예시를 들어가며 설명하겠습니다.</p>
<p>1.<strong>origin/serverfix</strong>라는 리모트 브랜치를 추적하는 <strong>iss10</strong> 이름의 트래킹 브랜치 만들기</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git checkout -b iss10 origin/serverfix
Branch 'iss10' set up to track remote branch 'serverfix' from 'origin'.
Switched to a new branch 'iss10'
</code></pre></div></div>
<p><br /></p>
<p>2.리모트 브랜치와 같은 이름의 트래킹 브랜치 만들기</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git checkout --track origin/serverfix
Branch 'serverfix' set up to track remote branch 'serverfix' from 'origin'.
Switched to a new branch 'serverfix'
</code></pre></div></div>
<p><br /></p>
<p>3.위 방법 shortcut</p>
<p>만약 입력한 브랜치가 있는 리모트가 딱 하나 있고, 로컬에는 없으면 다음과 같이 줄여 쓸 수 있습니다:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git checkout serverfix
Branch 'serverfix' set up to track remote branch 'serverfix' from 'origin'.
Switched to a new branch 'serverfix'
</code></pre></div></div>
<p><br /></p>
<p>4.이미 로컬에 존재하는 브랜치가 리모트 브랜치를 추적하게 하기</p>
<p><code class="language-plaintext highlighter-rouge">-u</code> 혹은 <code class="language-plaintext highlighter-rouge">--set-upstream-to</code> 옵션을 사용하시면 됩니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git branch -u origin/serverfix
</code></pre></div></div>
<p><br /></p>
<h2 id="트래킹-브랜치와-리모트-브랜치-비교하기">트래킹 브랜치와 리모트 브랜치 비교하기</h2>
<p>로컬로 가져온 리모트 브랜치는 fetch를 하지 않고서는 수정할 수 없다고 배웠습니다. 따라서 트래킹 브랜치를 만들어 로컬에서 작업을 진행합니다. 이때, 리모트 브랜치와 트래킹 브랜치의 커밋 히스토리를 다음과 같이 비교할 수 있습니다. (비교는 가장 최근 fetch와 비교합니다)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git branch -vv
iss53 7e424c3 [origin/iss53: ahead 2] forgot the brackets
master 1ae2a45 [origin/master] deploying index fix
* serverfix f8674d9 [teamone/server-fix-good: ahead 3, behind 1] this should do it
testing 5ea463a trying something new
</code></pre></div></div>
<p>왼쪽에 표시된 내용은 로컬 브랜치의 이름입니다. 그 옆에는 해당 브랜치가 가리키고 있는 커밋입니다. [ ] 사이에 표시된 내용은 upstream 브랜치입니다. 그 옆에는 커밋 메세지가 표시됩니다.</p>
<p>[ ] 사이에 ahead는 로컬 브랜가 리모트 브랜치보다 커밋이 두 개가 앞서있다는 뜻입니다. (즉, fetch를 한 후 두 개의 커밋을 새로 만들었다고 볼 수 있습니다.) behind라고 표시되면 반대로 뒤쳐져 있다는 뜻입니다. 만약 ahead와 behind 두 개가 동시에 표시된다면, 이는 브랜치가 갈라졌다는 의미입니다. 즉, 영희의 master 브랜치는 ahead 2, behind 2 라고 표기될 것입니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># 영희의 로컬 저장소
$ git branch -vv
master C5' [origin/master: ahead 2, behind 2] <Commit message>
</code></pre></div></div>
<p>주의할 점은 리모트 저장소와의 비교를 마지막 fetch를 기준으로 진행합니다. 따라서 꼭 fetch를 한 후 비교해주시기 바랍니다. 따라서 다음과 같이 사용할 수 있습니다:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git fetch -all; git branch -vv
</code></pre></div></div>
<h2 id="브랜치-합치기">브랜치 합치기</h2>
<p>영희와 같이 트래킹 브랜치와 upstream 브랜치가 갈라졌을 경우, 두 브랜치를 앞에서 배운대로 merge해주시면 됩니다:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git merge
</code></pre></div></div>
<p>혹은 <code class="language-plaintext highlighter-rouge">git pull</code> 명령어를 사용할 수도 있습니다. 사실, 앞에서 배운 <code class="language-plaintext highlighter-rouge">git pull</code>의 의미는 fetch와 merge를 동시에 진행하는 것입니다. 일반적으로 fetch와 merge 명령을 명시적으로 사용하는 것이 pull 명령으로 한 번에 하는 것보다 좋습니다.</p>김선규hopsprings2ternal@gmail.com저번 시간에는 리모트 브랜치, 트래킹 브랜치들의 개념을 살펴보았습니다. 이번에는 각각의 브랜치를 어떻게 만드는지 알아보도록 하겠습니다.15: 리모트 브랜치 개념2019-03-16T00:00:00+00:002019-03-16T00:00:00+00:00https://seonkyukim.github.io/git-tutorial/git-remote-branch<p>저번 시간에 영희의 로컬 저장소가 다음과 같이 변한 것을 확인했습니다:</p>
<p><img src="https://seonkyuKim.github.io/assets/images/2019-03-16-git-remote-branch/01.png" alt="" class="align-center" /></p>
<p>이때, 위 사진에서 <strong>origin/master를 리모트 브랜치</strong>라고 합니다. 리모트 브랜치는 <code class="language-plaintext highlighter-rouge">git fetch</code> 명령어를 통해 로컬 저장소로 가져올 수 있습니다.</p>
<p>리모트 저장소를 여러 개 저장할 수 있는 것처럼, 리모트 브랜치 역시 여러 개 저장할 수 있습니다. 이해를 돕기 위해 <strong>teamA</strong>라는 리모트 저장소를 하나 더 만들고, 해당 리모트 브랜치를 <code class="language-plaintext highlighter-rouge">git fetch</code>해보도록 하겠습니다. 다음은 <strong>teamA</strong>라는 리모트 저장소와, 그것을 fetch한 영희의 로컬 저장소의 모습입니다:</p>
<ul>
<li>
<p>리모트 저장소
<img src="https://seonkyuKim.github.io/assets/images/2019-03-16-git-remote-branch/02.png" alt="" class="align-center" /></p>
</li>
<li>
<p>영희 로컬 저장소</p>
</li>
</ul>
<p><img src="https://seonkyuKim.github.io/assets/images/2019-03-16-git-remote-branch/03.png" alt="" class="align-center" /></p>
<p>teamA의 리모트 저장소의 커밋 히스토리가 영희의 커밋 히스토리안에 포함되므로 단순히 리모트 브랜치 하나가 더 생기게 됩니다.</p>
<h2 id="리모트-브랜치-리모트-트래킹-브랜치-upstream-브랜치">리모트 브랜치, 리모트 트래킹 브랜치, Upstream 브랜치</h2>
<p>리모트 브랜치는 위에서 살펴본 <strong>origin/master</strong> 혹은 <strong>teamA/master</strong>입니다. 이들은 앞서 배운 로컬 브랜치들과 달리 수정을 할 수 없습니다. 단지 해당 커밋을 가리키고 있는 포인터입니다.</p>
<p>리모트 브랜치를 사용하기 위해서는 리모트 브랜치를 추적(tracking)하고 있는 <strong>트래킹 브랜치</strong>를 로컬에서 만들어 사용합니다. 그리고 트래킹 브랜치가 가리키고 있는 리모트 브랜치를 <strong>upstream 브랜치</strong>라고 합니다. clone했을 때 생기는 <strong>master</strong> 브랜치는 자동적으로 <strong>origin/master</strong> 브랜치를 추적하게 됩니다.</p>
<p class="notice--warning"><strong>Note</strong><br /><br />로컬 저장소의 리모트 브랜치를 직접 수정할 수는 없지만 <code class="language-plaintext highlighter-rouge">git fetch</code>를 통해 업데이트 할 수 있습니다.</p>
<h3 id="정리">정리</h3>
<p><strong>리모트 브랜치</strong></p>
<ul>
<li>리모트 저장소에 있는 브랜치.</li>
<li><code class="language-plaintext highlighter-rouge">git fetch</code> 명령어를 통해 로컬로 가져올 수 있음.</li>
<li>로컬에서는 해당 커밋에 대한 포인터의 역할만 할뿐, 수정할 수 없음</li>
<li>예시: origin/master</li>
</ul>
<p><strong>트래킹 브랜치</strong></p>
<ul>
<li>로컬로 가져온 리모트 브랜치를 추적(tracking)하는 브랜치</li>
<li>이를 이용하여 변경사항을 리모트 브랜치에 반영</li>
<li>예시: clone했을 경우, <strong>origin/master</strong>를 추적하고 있는 <strong>master</strong> 브랜치</li>
</ul>
<p><strong>Upstream 브랜치</strong></p>
<ul>
<li>트래킹 브랜치가 추적하고 있는 브랜치</li>
<li>예시: clone했을 경우, <strong>master</strong> 브랜치의 upstream 브랜치는 <strong>origin/master</strong></li>
</ul>김선규hopsprings2ternal@gmail.com저번 시간에 영희의 로컬 저장소가 다음과 같이 변한 것을 확인했습니다:14: 리모트 저장소 데이터 가져오기(git fetch)2019-03-11T00:00:00+00:002019-03-11T00:00:00+00:00https://seonkyukim.github.io/git-tutorial/git-fetch<p>이전까지의 작업은 거의 local에서 진행되었다고 볼 수 있습니다. 이제 다른 사람들과 협업을 하기 위해서는 리모트 저장소를 활용해야 합니다. 이때, 발생할 수 있는 상황 한 가지를 살펴보도록 하겠습니다.</p>
<h2 id="상황-요약">상황 요약</h2>
<p>요약해드리면 철수와 영희가 프로젝트를 진행하기 위해 <strong>origin</strong>이라는 리모트 저장소를 clone했습니다. 철수와 영희는 각자의 로컬 저장소에서 작업을 마친 후 commit을 하였습니다. 이때, 두 사람의 커밋 히스토리가 달라졌기 때문에 리모트 저장소에 push할 때 에러가 발생하고 말았습니다. 이를 해결하기 위해 <code class="language-plaintext highlighter-rouge">git fetch</code> 명령어를 사용해 로컬 저장소의 내용을 업데이트 해야합니다.</p>
<blockquote>
<p>로컬 저장소, 리모트 저장소, clone에 대해 헷갈리시다면 ‘<a href="https://seonkyukim.github.io/git-tutorial/git-remote/">09: 리모트 저장소(git remote)</a>‘의 내용을 다시 한 번 보고 오시기 바랍니다.</p>
</blockquote>
<h2 id="자세한-설명">자세한 설명</h2>
<p>철수와 영희가 <strong>origin</strong>이라는 리모트 저장소를 이용하여 프로젝트를 진행하고 있다고 가정합시다. 리모트 저장소 <strong>origin</strong>의 커밋 히스토리는 다음과 같습니다:</p>
<p><img src="https://seonkyuKim.github.io/assets/images/2019-03-11-git-fetch/01.png" alt="" class="align-center" /></p>
<p>또한 철수와 영희가 <code class="language-plaintext highlighter-rouge">git clone</code>을 하여 각각의 로컬 저장소에 다음과 같이 저장했습니다:</p>
<p><img src="https://seonkyuKim.github.io/assets/images/2019-03-11-git-fetch/02.png" alt="" class="align-center" /></p>
<p>이후 철수가 두 개의 커밋을 만들어 push를 했습니다. 그럼 원격 저장소와 각각의 저장소는 다음과 같은 모습일 것입니다:</p>
<ul>
<li>
<p>리모트 저장소
<img src="https://seonkyuKim.github.io/assets/images/2019-03-11-git-fetch/03.png" alt="" class="align-center" /></p>
</li>
<li>
<p>로컬 저장소
<img src="https://seonkyuKim.github.io/assets/images/2019-03-11-git-fetch/04.png" alt="" class="align-center" /></p>
</li>
</ul>
<p>이때, 영희도 자신의 작업을 마쳐 두 개의 커밋을 만들었습니다. 그럼 로컬 저장소의 커밋 히스토리와 리모트 저장소의 커밋 히스토리가 달라지게 됩니다. 또한, 영희의 커밋 히스토리와 철수의 커밋 히스토리도 달라지게 됩니다:</p>
<ul>
<li>로컬 저장소
<img src="https://seonkyuKim.github.io/assets/images/2019-03-11-git-fetch/05.png" alt="" class="align-center" /></li>
</ul>
<p><strong>이때 영희가 push를 하려한다면 다음과 비슷한 에러가 발생할 것입니다.</strong></p>
<p><img src="https://seonkyuKim.github.io/assets/images/2019-03-11-git-fetch/06.png" alt="" class="align-center" /></p>
<p>그 이유는 리모트 저장소의 커밋 히스토리와 영희의 커밋 히스토리가 다르기 때문입니다. 따라서 리모트 저장소에서 데이터를 받아와 로컬 저장소를 업데이트 해주어야 합니다. 이를 <code class="language-plaintext highlighter-rouge">git fetch <remote></code> 명령어를 이용하여 할 수 있습니다. 영희는 다음과 같은 명령어를 사용합니다:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git fetch origin
</code></pre></div></div>
<p>이후 영희의 로컬 히스토리는 다음과 같이 업데이트 됩니다:</p>
<p><img src="https://seonkyuKim.github.io/assets/images/2019-03-11-git-fetch/07.png" alt="" class="align-center" /></p>
<p>이후 앞에서 배운 것처럼 <code class="language-plaintext highlighter-rouge">git merge</code> 등으로 합친 후 push 해주면 됩니다. 위 그림의 <strong>origin/master</strong>의 정체는 다음 장에서 배우도록 하겠습니다.</p>김선규hopsprings2ternal@gmail.com이전까지의 작업은 거의 local에서 진행되었다고 볼 수 있습니다. 이제 다른 사람들과 협업을 하기 위해서는 리모트 저장소를 활용해야 합니다. 이때, 발생할 수 있는 상황 한 가지를 살펴보도록 하겠습니다.