<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Smart Tiger's blog</title>
    <link>https://seamless.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Mon, 29 Jun 2026 17:11:29 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Data Engineer</managingEditor>
    <image>
      <title>Smart Tiger's blog</title>
      <url>https://tistory1.daumcdn.net/tistory/385015/attach/fe3a4162a581475abaa163a1330739e9</url>
      <link>https://seamless.tistory.com</link>
    </image>
    <item>
      <title>gcc로 컴파일 하는 방법</title>
      <link>https://seamless.tistory.com/2</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. gcc 동작 과정&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;gcc 란?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;원래는 GNU C Compiler를 의미 했지만 1999년부터 GNU Compiler Collection을 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;gcc가 실행시키는 프로그램&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;전처리기 : cpp&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;컴파일러 : cc1&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;어셈블러 : as&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;링커 : ld&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;단계별 처리 방법&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1) 전처리 단계&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;소스 파일(file.c)에 gcc를 동작시키면 가장 먼저 전처리기인 cpp가 동작한다. cpp는 소스 파일의 #include와 #define과 같은 #으로 시작되는 전처리기 부분을 처리 한다. 즉, 필요한 헤더 파일을 삽입하고 실행 문장의 매크로를 상수로 변환한다. 소스 파일 file.c가 전처리기를 거치면 file.i라는 이름의 파일이 생성되지만 디스크에는 저장되지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2) 컴파일 단계&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;컴파일러가 전처리된 파일(file.i)로부터 어셈블리어로 된 파일(file.s)을 생성한다. 그런데 일반적으로 다음 단계인 어셈블 단계를 바로 실행하므로 file.s 파일은 디스크에 저장되지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3) 어셈블 단계&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;어셈블리어로 된 파일(file.s)을 기계가 직접 이해할 수 있는 기계어로 된 오브젝트 파일(file.o)로 변환한다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;4) 링크 단계&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;오브젝트 파일(file.o)은 printf, scanf와 같은 라이브러리 함수에 해당하는 코드가 없기 때문에 실행될 수 없다. 또한 여러 파일로 이루어진 프로그램의 경우에도 파일 간에 연결이 이루어지지 않아 실행될 수 없는데, 이러한 라이브러리 함수와 오브젝트파일들을 연결해 실행 파일을 생성하는 단계가 링크 단계이다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;컴파일을 할 때는 각 단계별로 옵션을 사용해 얼마나 진행되었는지 그 상황을 볼 수 있게 수동으로 제어할 수 있는데, 이에 대한 자세한 내용은 gcc 옵션에서 다룬다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;5) 파일 확장자에 따른 처리 방법&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;gcc는 파일 확장자에 따라 다음과 같이 처리 방법을 달리하는데, 한 가지만 설명하면 확장자가 .c인 경우 gcc로 전처리기, 컴파일, 어셈블, 링크 과정을 거쳐야 실행 파일이 완성된다는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;확장자&lt;/span&gt;&lt;/th&gt;
&lt;th&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;종류&lt;/span&gt;&lt;/th&gt;
&lt;th&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;처리방법&lt;/span&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;.c&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;C 소스 파일&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;gcc로 전처리, 컴파일, 어셈블, 링크&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;.C, .CC&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;C++ 소스 파일&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;g++로 전처리, 컴파일, 어셈블, 링크&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;.i&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;전처리된 C 파일&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;gcc로 컴파일, 어셈블, 링크&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;.ii&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;전처리된 C++ 파일&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;g++로 컴파일, 어셈블, 링크&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;.s&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;어셈블리어로 된 파일&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;어셈블, 링크&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;.S&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;어셈블리어로 된 파일&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;전처리, 어셈블, 링크&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;.o&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;오브젝트 파일&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;링크&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;.a, .so&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;컴파일된 라이브러리 파일&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;링크&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. gcc 실행하기&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;gcc를 이용해 컴파일 하는 방법은 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;$ gcc 소스파일 이름&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;gcc를 이용해 컴파일에 성공하면 a.out이라는 파일이 생성된 것을 확인할 수 있는데 여기서 그냥 명령어로 a.out을 입력하면 명령어를 발견할 수 없다는 오류 메시지가 나온다. 이는 a.out가 저장된 디렉토리를 path로 설정하지 않았기 때문이다. path로 설정되지 않는 디렉토리에 있는 명령어는 디렉토리 위치를 지정해주지 않는 한 실행되지 않는다. 그러므로 a.out를 실행하려면 다음과 같이 해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;$ ./a.out&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;즉, 프로그램 이름 앞에 디렉토리 위치 정보인 ./를 추가해야 현재 디렉토리에 있는 명령어가 실행되는데, 여기서 마침표(.)는 현재 디렉토리를 의미하고, 슬래시(/)는 디렉토리를 구분하는 문자다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3. gcc 옵션&lt;/span&gt;&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 291px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 19px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;옵션&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 19px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;의미&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-E&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;전처리를 실행하고 컴파일을 중단하게 한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-c&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;소스 파일을 컴파일만 하고 링크를 수행하지 않으며, 오브젝트 파일을 생성한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-o&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;바이너리 형식의 출력 파일 이름을 지정하는데, 지정하지 않으면 a.out라는 기본 이름이 적용된다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-I&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;헤더 파일을 검색하는 디렉토리 목록을 추가한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-L&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;라이브러리 파일을 검색하는 디렉토리 목록을 추가한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-l&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;라이브러리 파일을 컴파일 시 링크한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-g&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;바이너리 파일에 표준 디버깅 정보를 포함시킨다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-ggdb&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;바이너리 파일에 GNU 디버거인 gdb만이 이해할 수 있는 많은 디버깅 정보를 포함시킨다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-O&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;컴파일 코드를 최적화시킨다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-ON&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;최적화 N 단계를 지정한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-DFOO=RAR&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;명령라인에서 BAR의 값을 가지는 FOO라는 선행 처리기 매크로를 정의한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-static&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;정적 라이브러리에 링크한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-ansi&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;표준과 충돌하는 GNU 확장안을 취소하며, ANSI/ISO C 표준을 지원한다. 이 옵션은 ANSI 호환 코드를 보장하지 않는다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-traditional&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;과서 스타일의 함수 정의 형식과 같이 전통적인 K&amp;amp;R(Kernighan and Ritchie) C 언어 형식을 지원한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-MM&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;make 호환의 의존성 목록을 출력한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-V&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;컴파일의 각 단계에서 사용되는 명령어를 보여준다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1)&amp;nbsp;자주&amp;nbsp;사용되는&amp;nbsp;옵션&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-o 옵션&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;C&amp;nbsp;소스&amp;nbsp;코드를&amp;nbsp;컴파일&amp;nbsp;할&amp;nbsp;때&amp;nbsp;생성되는&amp;nbsp;출력&amp;nbsp;파일&amp;nbsp;이름을&amp;nbsp;지정하는&amp;nbsp;옵션으로&amp;nbsp;사용법은&amp;nbsp;다음과&amp;nbsp;같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671159013566&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# -o 옵션 기능 : 생성되는 출력 파일 이름을 지정한다.
$ gcc -o 출력파일이름 소스파일이름&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;$&amp;nbsp;gcc&amp;nbsp;-o&amp;nbsp;file&amp;nbsp;file.c&amp;nbsp;또는&amp;nbsp;$&amp;nbsp;gcc&amp;nbsp;file.c&amp;nbsp;-o&amp;nbsp;file을&amp;nbsp;입력한다.&amp;nbsp;그럼&amp;nbsp;출력파일이&amp;nbsp;file이라는&amp;nbsp;이름으로&amp;nbsp;생성이&amp;nbsp;된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이를&amp;nbsp;실행하려면&amp;nbsp;$&amp;nbsp;./file&amp;nbsp;이라고&amp;nbsp;실행하면&amp;nbsp;된다.&amp;nbsp;-o&amp;nbsp;옵션을&amp;nbsp;생략하고&amp;nbsp;컴파일을&amp;nbsp;하면&amp;nbsp;실행&amp;nbsp;파일&amp;nbsp;이름은&amp;nbsp;a.out가&amp;nbsp;된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그런데&amp;nbsp;이럴&amp;nbsp;경우&amp;nbsp;두&amp;nbsp;가지&amp;nbsp;다른&amp;nbsp;소스를&amp;nbsp;차례로&amp;nbsp;컴파일할&amp;nbsp;때&amp;nbsp;먼저&amp;nbsp;생성된&amp;nbsp;실행&amp;nbsp;파일&amp;nbsp;a.out를&amp;nbsp;나중에&amp;nbsp;생성된&amp;nbsp;a.out가&amp;nbsp;경고없이&amp;nbsp;덮어쓰므로&amp;nbsp;주의해야&amp;nbsp;한다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-E&amp;nbsp;옵션&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;컴파일의&amp;nbsp;첫&amp;nbsp;단계인&amp;nbsp;전처리까지만&amp;nbsp;실행한&amp;nbsp;결과를&amp;nbsp;화면에&amp;nbsp;출력한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671159243573&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# -E 옵션 기능 : 전처리까지만 실행하고 결과를 화면에 출력한다. 
$ gcc -E 소스파일이름&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;$ gcc -E file.c로 컴파일을 하면 매우 방대한 내용이 소스파일 위에 붙는 것을 확인할 수 있다. 이처럼 -E 옵션만 주면 전처리된 결과가 화면에만 출력되고 파일로 저장되지는 않는다. 그러므로 파일을 저장하려면 -o 옵션을 함께 주어야 하고, 그러면 file.i라는 전처리된 파일이 디스크에 저장된다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-c&amp;nbsp;옵션&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-c&amp;nbsp;옵션은&amp;nbsp;전처리,&amp;nbsp;컴파일,&amp;nbsp;어셈블까지&amp;nbsp;실행하여&amp;nbsp;오브젝트&amp;nbsp;파일(.o)을&amp;nbsp;생성한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671159308574&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# -c 옵션 기능 : 전처리, 컴파일, 어셈블까지 실행하여 오브젝트 파일을 생성한다. 
$ gcc -c 소스파일이름&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;file.c를&amp;nbsp;-c&amp;nbsp;옵션을&amp;nbsp;사용해&amp;nbsp;컴파일&amp;nbsp;하면&amp;nbsp;file.o라는&amp;nbsp;오브젝트&amp;nbsp;파일이&amp;nbsp;생성된다.&amp;nbsp;즉,&amp;nbsp;-c&amp;nbsp;옵션을&amp;nbsp;주어&amp;nbsp;&amp;lsquo;소스파일이름.c'를&amp;nbsp;컴파일하면&amp;nbsp;오브젝트&amp;nbsp;파일&amp;nbsp;이름은&amp;nbsp;&amp;rsquo;소스파일이름.o'가&amp;nbsp;된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671159334582&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ gcc -c file.c&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고&amp;nbsp;이&amp;nbsp;오브젝트&amp;nbsp;파일을&amp;nbsp;이용해&amp;nbsp;실행&amp;nbsp;파일을&amp;nbsp;생성하려면&amp;nbsp;다음과&amp;nbsp;같이&amp;nbsp;gcc를&amp;nbsp;이용하면&amp;nbsp;된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671159351694&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ gcc file.o&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그럼&amp;nbsp;a.out이라는&amp;nbsp;기본&amp;nbsp;출력&amp;nbsp;파일이&amp;nbsp;생성된다.&amp;nbsp;여기서&amp;nbsp;file&amp;nbsp;이라는&amp;nbsp;이름의&amp;nbsp;실행파일을&amp;nbsp;생성하려면&amp;nbsp;-o&amp;nbsp;옵션만&amp;nbsp;주면&amp;nbsp;된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671159373457&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ gcc file.o -o file&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-c 옵션은 하나의 프로그램을 여러 파일로 분리해 작성한 다음, 함께 컴파일 하는 &lt;b&gt;분리 컴파일 시&lt;/b&gt; 많이 사용된다. 예를 들어 main.c와 hi.c 두 개의 소스로 구성된 프로그램을 살펴보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671159511814&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// main.c 
extern void hi();  
main() {
    hi(); 
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1671159539131&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// hi.c
#include &amp;lt;stdio.h&amp;gt;  

void hi() {
    printf(&quot;Hi\n&quot;); 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이&amp;nbsp;둘을&amp;nbsp;함께&amp;nbsp;컴파일&amp;nbsp;하는&amp;nbsp;방법은&amp;nbsp;다음과&amp;nbsp;같으며,&amp;nbsp;이를&amp;nbsp;분리&amp;nbsp;컴파일이라&amp;nbsp;한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671159560718&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ gcc main.c hi.c -o test 
$ ./test Hi&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;만약&amp;nbsp;다음과&amp;nbsp;같이&amp;nbsp;파일별로&amp;nbsp;컴파일&amp;nbsp;하면&amp;nbsp;컴파일&amp;nbsp;오류가&amp;nbsp;발생하는데,&amp;nbsp;main.c&amp;nbsp;파일에&amp;nbsp;대한&amp;nbsp;오류는&amp;nbsp;호출하는&amp;nbsp;함수&amp;nbsp;hi라는&amp;nbsp;함수가&amp;nbsp;정의되지&amp;nbsp;않았기&amp;nbsp;때문이고,&amp;nbsp;hi.c&amp;nbsp;파일에대한&amp;nbsp;오류는&amp;nbsp;main&amp;nbsp;함수가&amp;nbsp;없기&amp;nbsp;때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671159602568&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 오류가 난다. 오류의 종류는 생략한다. 
$ gcc main.c -o test 

# 오류가 난다. 오류의 종류는 생략한다. 
$ gcc hi.c -o hi.c&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그러나&amp;nbsp;다음과&amp;nbsp;같이&amp;nbsp;각&amp;nbsp;파일별로&amp;nbsp;오브젝트&amp;nbsp;파일을&amp;nbsp;만들고&amp;nbsp;나중에&amp;nbsp;링크하는&amp;nbsp;것은&amp;nbsp;가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671159620394&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ gcc -c main.c $ gcc -c hi.c 
$ gcc main.o hi.o -o test&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이런 식으로 분리 컴파일을 하게 되면 만일 hi.c 파일이 수정되면 main.c와 hi.c를 모두 컴파일할 필요 없이 hi.c 파일에 대한 오브젝트 파일만을 생성하고 링크하면 원하는 실행 파일이 생성된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671159640999&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# hi.c를 수정하고 나서 다시 컴파일 
$ gcc -c hi.c 
$ gcc main.o hi.o -o test&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-I&amp;nbsp;옵션&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-I&amp;nbsp;옵션은&amp;nbsp;C&amp;nbsp;소스가&amp;nbsp;표준&amp;nbsp;디렉토리가&amp;nbsp;아닌&amp;nbsp;위치에&amp;nbsp;있는&amp;nbsp;헤더&amp;nbsp;파일을&amp;nbsp;가질&amp;nbsp;때&amp;nbsp;그&amp;nbsp;디렉토리&amp;nbsp;위치를&amp;nbsp;지정해준다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671159690221&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-I 옵션 기능 : 표준 디렉토리가 아닌 위치에 있는 헤더 파일의 디렉토리를 지정한다. 
$ gcc 소스파일이름 -I디렉토리 이름&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저 myheader.h 파일이 age.c 파일이 있는 디렉토리의 하위 디렉토리인 mydir에 있다고 하자.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671159743431&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// age.c 
#include &amp;lt;stdio.h&amp;gt; 
#include &quot;myheader.h&quot; 

main() {
    printf(&quot;%d\n&quot;, AGE); 
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1671159759689&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// myheader.h 
#define AGE 20&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;age.c&amp;nbsp;파일을&amp;nbsp;컴파일하면&amp;nbsp;다음과&amp;nbsp;같이&amp;nbsp;myheader.h&amp;nbsp;파일이&amp;nbsp;없다는&amp;nbsp;메시지의&amp;nbsp;컴파일&amp;nbsp;오류가&amp;nbsp;발생한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671159776303&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 컴파일 오류가 발생한다. 
$ gcc age.c&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;myheader.h&amp;nbsp;파일이&amp;nbsp;표준&amp;nbsp;디렉토리에&amp;nbsp;없기&amp;nbsp;때문에&amp;nbsp;오류가&amp;nbsp;발생하는&amp;nbsp;것이다.&amp;nbsp;이와&amp;nbsp;같이&amp;nbsp;표준&amp;nbsp;디렉토리가&amp;nbsp;아닌&amp;nbsp;디렉토리에&amp;nbsp;있는&amp;nbsp;헤더&amp;nbsp;파일을&amp;nbsp;이용하려면&amp;nbsp;-I&amp;nbsp;옵션으로&amp;nbsp;디렉토리&amp;nbsp;위치를&amp;nbsp;지정해야&amp;nbsp;한다.&amp;nbsp;즉,&amp;nbsp;myheader.h&amp;nbsp;파일이&amp;nbsp;있는&amp;nbsp;mydir&amp;nbsp;디렉토리를&amp;nbsp;다음과&amp;nbsp;같이&amp;nbsp;지정해&amp;nbsp;컴파일하면&amp;nbsp;성공한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671159794837&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ gcc age.c -Imydir 
$ ./a.out 20&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2) 라이브러리 지정 옵션&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;라이브러리란?&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;자주&amp;nbsp;사용되는&amp;nbsp;유용한&amp;nbsp;함수에&amp;nbsp;대한&amp;nbsp;오브젝트&amp;nbsp;파일을&amp;nbsp;모아둔&amp;nbsp;것이&amp;nbsp;라이브러리로,&amp;nbsp;사실&amp;nbsp;라이브러리에는&amp;nbsp;함수&amp;nbsp;목록(index)도&amp;nbsp;포함된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;시스템에서 제공하는 라이브러리는 /usr/lib 디렉토리에 있으니 확인해보면 된다. 아주 많은 라이브러리가 있는데, 이름은 lib로 시작하고 ar 명령어에 의해 생성되므로 확장자는 .a다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이 중에서 libc.a는 표준 라이브러리고 libm.a는 수치 연산 라이브러리다. ar 명령어를 이용하면 libc.a가 어떠한 오브젝트 파일로 이루어졌는지 알 수 있다. ar 명령어의 t 옵션은 .a 파일의 내용을 표시해주는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671159899510&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ar t libc.a&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;라이브러리를&amp;nbsp;직접&amp;nbsp;만들어&amp;nbsp;보기&amp;nbsp;위해&amp;nbsp;우선&amp;nbsp;다음과&amp;nbsp;같이&amp;nbsp;plus.c와&amp;nbsp;minus.c&amp;nbsp;두&amp;nbsp;개의&amp;nbsp;파일을&amp;nbsp;mylib&amp;nbsp;디렉토리에&amp;nbsp;만들자.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671159922190&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// plus.c 
int plus(int x, int y) {   
    return x+y; 
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1671159937746&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// minus.c 
int minus(int x, int y) {   
    return x-y; 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;plus.c와&amp;nbsp;minus.c&amp;nbsp;파일을&amp;nbsp;생성했으면&amp;nbsp;이들&amp;nbsp;파일에&amp;nbsp;대한&amp;nbsp;오브젝트&amp;nbsp;파일인&amp;nbsp;plus.o와&amp;nbsp;minus.o를&amp;nbsp;다음과&amp;nbsp;같이&amp;nbsp;생성한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671163937386&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ gcc -c plus.c minus.c 
$ ls minus.c minus.o plus.c plus.o&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그러면&amp;nbsp;라이브러리&amp;nbsp;파일을&amp;nbsp;생성할&amp;nbsp;준비가&amp;nbsp;끝났다.&amp;nbsp;다음과&amp;nbsp;같이&amp;nbsp;ar&amp;nbsp;명령어를&amp;nbsp;이용하면&amp;nbsp;plus.o와&amp;nbsp;minus.o에&amp;nbsp;대한&amp;nbsp;libmy.a가&amp;nbsp;생성되는데,&amp;nbsp;r옵션은&amp;nbsp;.a&amp;nbsp;파일을&amp;nbsp;생성한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671163961045&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ar r libmy.a plus.o minus.o&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고&amp;nbsp;라이브러리&amp;nbsp;파일에&amp;nbsp;목록을&amp;nbsp;추가해야&amp;nbsp;하는데,&amp;nbsp;다음과&amp;nbsp;같이&amp;nbsp;s&amp;nbsp;옵션을&amp;nbsp;주어&amp;nbsp;ar&amp;nbsp;명령어를&amp;nbsp;실행하면&amp;nbsp;라이브러리&amp;nbsp;파일이&amp;nbsp;생성된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671163975415&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ar s libmy.a&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-l&amp;nbsp;옵션&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-l&amp;nbsp;옵션은&amp;nbsp;표준&amp;nbsp;라이브러리가&amp;nbsp;아닌&amp;nbsp;라이브러리를&amp;nbsp;사용하고자&amp;nbsp;할&amp;nbsp;때&amp;nbsp;그&amp;nbsp;라이브러리를&amp;nbsp;지정해준다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671164011902&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# -l 옵션 기능 : 표준 라이브러리가 아닌 라이브러리를 지정한다. 
$ gcc 소스파일이름 -l라이브러리이름&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그럼 -l 옵션의 예를 살펴보면 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671164074645&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// test1.c 
#include &amp;lt;stdio.h&amp;gt; 
#include &amp;lt;math.h&amp;gt;  

main() {   
    //pow(x, y)는 x^y를 구하는 함수
    printf(&quot;%g\n&quot;, pow(2, 3)); 
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1671164173627&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 오류가 난다. 
$ gcc test1.c&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;수치&amp;nbsp;연산&amp;nbsp;라이브러리의&amp;nbsp;이름은&amp;nbsp;libm.a다.&amp;nbsp;그러므로&amp;nbsp;이&amp;nbsp;라이브러리&amp;nbsp;이름을&amp;nbsp;-l&amp;nbsp;옵션&amp;nbsp;뒤에&amp;nbsp;지정해야&amp;nbsp;하는데,&amp;nbsp;lib와&amp;nbsp;.a를&amp;nbsp;제외한&amp;nbsp;m만&amp;nbsp;쓴다.&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;즉,&amp;nbsp;다음과&amp;nbsp;같이&amp;nbsp;설정하고&amp;nbsp;컴파일하면&amp;nbsp;성공한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671164196414&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ gcc test1.c -lm 
$ ./a.out&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제&amp;nbsp;앞서&amp;nbsp;직접&amp;nbsp;만든&amp;nbsp;libmy.a는&amp;nbsp;표준&amp;nbsp;라이브러리가&amp;nbsp;아니므로&amp;nbsp;-l&amp;nbsp;옵션을&amp;nbsp;주어&amp;nbsp;지정해야&amp;nbsp;한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671164236482&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// test2.c 
#include &amp;lt;stdio.h&amp;gt; 

int plus(int x, int y) 
int minus(int x, int y)  

main() {   
    printf(&quot;%d %d\n&quot;, plus(2, 3), minus(2, 3)); 
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1671164248151&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 컴파일 오류가 난다. 
$ gcc test2.c -lmy&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;오류가 발생하는 이유는 링커인 ld가 라이브러리를 찾을 때 /lib, /usr/lib와 같이 정해진 디렉토리만 찾기 때문이다. libmy.a 라이브러리는 현재 작업 디렉토리의 하위 디렉토리인 mylib에 있으므로 링커가 찾지 못하는데, 이는 -L 옵션을 사용해 해결할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-L&amp;nbsp;옵션&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-L&amp;nbsp;옵션은&amp;nbsp;사용할&amp;nbsp;라이브러리의&amp;nbsp;위치를&amp;nbsp;지정해주므로&amp;nbsp;사용자가&amp;nbsp;라이브러리&amp;nbsp;파일을&amp;nbsp;직접&amp;nbsp;만들어&amp;nbsp;사용하거나&amp;nbsp;새&amp;nbsp;라이브러리를&amp;nbsp;내려&amp;nbsp;받아&amp;nbsp;사용할&amp;nbsp;때&amp;nbsp;이용된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671164329840&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# -L 옵션 기능 : 사용할 라이브러리의 위치를 지정한다. 
$ gcc 소스파일이름 -L라이브러리위치&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;컴파일 오류가 발생했던 test2.c를 다음과 같이 -L 옵션을 주어 libmy.a 라이브러리가 있는 디렉토리인 mylib를 지정하면 성공적으로 컴파일이 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671164401148&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ gcc test2.c -lmy -Lmylib 
$ ./a.out&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3)&amp;nbsp;디버깅&amp;nbsp;관련&amp;nbsp;옵션&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1671164430893&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# -g, -ggdb 옵션 기능 : 디버깅 정보를 삽입한다. 
$ gcc -g 소스파일이름 gcc -ggdb 소스파일이름&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-g&amp;nbsp;옵션에는&amp;nbsp;실행파일에&amp;nbsp;삽입될&amp;nbsp;디버깅&amp;nbsp;정보의&amp;nbsp;양에&amp;nbsp;따라&amp;nbsp;-g1,&amp;nbsp;-g2,&amp;nbsp;-g3와&amp;nbsp;같이&amp;nbsp;세&amp;nbsp;가지&amp;nbsp;단계가&amp;nbsp;있는데,&amp;nbsp;숫자&amp;nbsp;없이&amp;nbsp;-g&amp;nbsp;옵션을&amp;nbsp;주면&amp;nbsp;기본적으로&amp;nbsp;-g2의&amp;nbsp;디버깅&amp;nbsp;정보가&amp;nbsp;삽입된다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-g&amp;nbsp;옵션의&amp;nbsp;단계&lt;/span&gt;&lt;/h4&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 114px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 19px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;옵션&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 19px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;의미&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 38px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-g1&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 38px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;역추적&amp;nbsp;스택&amp;nbsp;덤프&amp;nbsp;생성에&amp;nbsp;필요한&amp;nbsp;정보를&amp;nbsp;포함하지만&amp;nbsp;지역변수,&amp;nbsp;문장&amp;nbsp;번호를&amp;nbsp;위한&amp;nbsp;디버깅&amp;nbsp;정보는&amp;nbsp;삽입하지&amp;nbsp;않는다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 38px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-g2&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 38px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;확장&amp;nbsp;기호&amp;nbsp;테이블,&amp;nbsp;문장&amp;nbsp;번호,&amp;nbsp;지역과&amp;nbsp;외부&amp;nbsp;변수에&amp;nbsp;대한&amp;nbsp;디버깅&amp;nbsp;정보를&amp;nbsp;삽입한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 19px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-g3&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 19px;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-g2&amp;nbsp;옵션의&amp;nbsp;디버깅&amp;nbsp;정보와&amp;nbsp;모든&amp;nbsp;매크로&amp;nbsp;정의를&amp;nbsp;삽입한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-g&amp;nbsp;옵션&amp;nbsp;없이&amp;nbsp;컴파일&amp;nbsp;할때와&amp;nbsp;-g&amp;nbsp;옵션을&amp;nbsp;주었을&amp;nbsp;때&amp;nbsp;생성된&amp;nbsp;실행&amp;nbsp;파일의&amp;nbsp;크기가&amp;nbsp;훨씬&amp;nbsp;커지는데,&amp;nbsp;그&amp;nbsp;이유는&amp;nbsp;디버깅&amp;nbsp;정보가&amp;nbsp;추가되었기&amp;nbsp;때문이다.&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-ggdb 옵션도 -g와 거의 유사하나 특별히 다른 점이 있다면 디버깅 작업을 도와주는 추가 정보가 필요하고, gdb 외의 다른 종류의 디버거에서는 사용이 불가능하다는 것이다.&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-g, -ggdb 옵션에 의한 디버킹 코드 삽입은 파일의 크기를 크게하기 때문에 최종 실행 파일을 생성할 때는 이러한 옵션을 주지 말고 컴파일하는 것이 바람직하다. 또 최적화된 코드는 디버깅을 어렵게 만들기 때문에 최적화 수행 전에 디버깅을 하는 것이 좋다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;4)&amp;nbsp;최적화&amp;nbsp;옵션&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;성능을&amp;nbsp;개선시키기&amp;nbsp;위해&amp;nbsp;코드를&amp;nbsp;최적화하면&amp;nbsp;불필요하거나&amp;nbsp;비효율적인&amp;nbsp;계산&amp;nbsp;과정이&amp;nbsp;효율적&amp;nbsp;게산&amp;nbsp;과정으로&amp;nbsp;대체되어&amp;nbsp;코드의&amp;nbsp;크기와&amp;nbsp;실행&amp;nbsp;시간을&amp;nbsp;줄일&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;대신,&amp;nbsp;컴파일&amp;nbsp;시간이&amp;nbsp;늘고&amp;nbsp;컴파일&amp;nbsp;과정에서&amp;nbsp;메모리&amp;nbsp;사용량이&amp;nbsp;늘어나는&amp;nbsp;단점이&amp;nbsp;있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1671164582396&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# -O 옵션 기능 : 코드를 최적화 시킨다. 
$ gcc -O 소스파일이름&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-O&amp;nbsp;옵션은&amp;nbsp;-ON(숫자)을&amp;nbsp;써줌으로써&amp;nbsp;최적화&amp;nbsp;단계를&amp;nbsp;구분할&amp;nbsp;수&amp;nbsp;있는데,&amp;nbsp;N&amp;nbsp;값은&amp;nbsp;gcc&amp;nbsp;버전마다&amp;nbsp;차이가&amp;nbsp;나며&amp;nbsp;값이&amp;nbsp;커질수록&amp;nbsp;더욱&amp;nbsp;최적화된&amp;nbsp;코드가&amp;nbsp;나온다.&amp;nbsp;일반적으로,&amp;nbsp;-O1,&amp;nbsp;-O2를&amp;nbsp;많이&amp;nbsp;사용하며,&amp;nbsp;-O1,&amp;nbsp;-O2,&amp;nbsp;-O3에&amp;nbsp;의한&amp;nbsp;최적화&amp;nbsp;내용은&amp;nbsp;다음과&amp;nbsp;같다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;옵션&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;의미&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-O1&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-O&amp;nbsp;옵션과&amp;nbsp;같은&amp;nbsp;단계의&amp;nbsp;옵션으로&amp;nbsp;최소한의&amp;nbsp;스레드&amp;nbsp;분기&amp;nbsp;동작&amp;nbsp;횟수를&amp;nbsp;줄이고,&amp;nbsp;호출된&amp;nbsp;각&amp;nbsp;함수&amp;nbsp;반환&amp;nbsp;시&amp;nbsp;스택에&amp;nbsp;인수를&amp;nbsp;모아&amp;nbsp;두었다&amp;nbsp;동시에&amp;nbsp;꺼내게&amp;nbsp;해준다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-O2&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-O1&amp;nbsp;단계의&amp;nbsp;최적화와&amp;nbsp;함께&amp;nbsp;프로세서가&amp;nbsp;다른&amp;nbsp;명령어의&amp;nbsp;결과나&amp;nbsp;캐시&amp;nbsp;메모리&amp;nbsp;또는&amp;nbsp;메모리의&amp;nbsp;데이터를&amp;nbsp;기다리는&amp;nbsp;동안&amp;nbsp;컴파일러가&amp;nbsp;다른&amp;nbsp;명령어를&amp;nbsp;실행하도록&amp;nbsp;한다.&amp;nbsp;컴파일&amp;nbsp;시간이&amp;nbsp;더&amp;nbsp;오래&amp;nbsp;걸리지만,&amp;nbsp;수정된&amp;nbsp;코드는&amp;nbsp;더&amp;nbsp;최적화되어&amp;nbsp;실행이&amp;nbsp;빨라진다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-O3&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;-O2&amp;nbsp;단계의&amp;nbsp;모든&amp;nbsp;최적화와&amp;nbsp;루프&amp;nbsp;해체,&amp;nbsp;그&amp;nbsp;밖의&amp;nbsp;프로세서&amp;nbsp;전용&amp;nbsp;특징을&amp;nbsp;포함한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Tools</category>
      <category>gcc</category>
      <category>gcc 옵션</category>
      <category>gcc 컴파일 방법</category>
      <category>컴파일</category>
      <author>Data Engineer</author>
      <guid isPermaLink="true">https://seamless.tistory.com/2</guid>
      <comments>https://seamless.tistory.com/2#entry2comment</comments>
      <pubDate>Tue, 22 Nov 2022 07:34:57 +0900</pubDate>
    </item>
    <item>
      <title>데이터베이스 소개 및 훑어보기</title>
      <link>https://seamless.tistory.com/107</link>
      <description>&lt;p&gt;데이터베이스 시스템의 선택은 중요합니다. 성능, 일관성 문제, 운영의 어려움과 같은 이유로 데이터베이스를 변경하게 되는 경우가 발생할 수 있습니다. 데이터베이스를 변경할 때 마이그레이션이 쉽지 않을 수도 있기 때문에 초기 설계 단계에서 애플리케이션의 특성에 알맞는 데이터베이스를 선택해야 합니다. 데이터베이스를 선택함에 있어 다음과 같은 변수를 통해서 어떤 데이터베이스를 선택할지 예측할 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;스키마와 레코드 크기&lt;/li&gt;
&lt;li&gt;클라이언트 수&lt;/li&gt;
&lt;li&gt;쿼리 형식과 접근 패턴&lt;/li&gt;
&lt;li&gt;읽기와 쓰기 쿼리 비율&lt;/li&gt;
&lt;li&gt;위 변수들의 변동폭&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그리고 위의 변수들을 기준으로 다음과 같은 체크리스트에 관한 답을 내려서 선택할 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;요청된 쿼리를 수행할 수 있는가?&lt;/li&gt;
&lt;li&gt;데이터를 모두 저장할 수 있는가?&lt;/li&gt;
&lt;li&gt;단일 노드는 몇 건의 읽기와 쓰기 요청을 처리할 수 있는가?&lt;/li&gt;
&lt;li&gt;시스템에는 몇 개의 노드가 필요한가?&lt;/li&gt;
&lt;li&gt;데이터 증가 추세에 맞춰 클러스터를 확장할 수 있는가?&lt;/li&gt;
&lt;li&gt;유지보수는 어떻게 할 것인가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;위의 변수들과 질문들을 통해서 애플리케이션에서 적절한 데이터베이스를 선택할 수 있을 것입니다.&lt;/p&gt;
&lt;h2&gt;DBMS 아키텍처&lt;/h2&gt;
&lt;p&gt;모든 데이터베이스가 동일한 아키텍처를 갖는 것은 아닙니다. 데이터베이스마다 다른 아키텍처를 가지고 있으며 내부 컴포넌트의 경계를 명확히 나누는 것이 어렵습니다. 그러나 몇몇 공통된 부분들을 모아서 나타낸 아키텍처는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9993F13360568BA306&quot; alt=&quot;&quot; width=&quot;324&quot; height=&quot;515&quot; /&gt;&lt;/p&gt;
&lt;p&gt;DBMS는 클라이언트/서버 모델을 기반으로 합니다. 데이터베이스와 애플리케이션은 각각 서버와 클라이언트의 역할을 합니다. 위 그림에서처럼 사용자가 쿼리를 요청하면 쿼리를 쿼리 프로세서에 전달합니다. 쿼리 프로세서에서는 쿼리를 분석하고 분석된 쿼리를 쿼리 옵티마이저에 전달합니다. 쿼리 옵티마이저에서는 쿼리의 실행 계획(Execution plan)을 최적화합니다. 그리고 선택된 실행 계획을 실행 엔진에서 실행합니다.&lt;br /&gt;스토리지 엔진은 그림과 같이 다양한 역할을 수행하는 컴포넌트들로 구성되어 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;트랜잭션 매니저 : 트랜잭션을 스케줄링하고 데이터베이스 상태의 논리적 일관성을 보장&lt;/li&gt;
&lt;li&gt;락 매니저 : 트랜잭션에서 접근하는 데이터베이스 객체에 대한 잠금 제어&lt;/li&gt;
&lt;li&gt;엑세스 메소드 : 디스크에 저장된 데이터에 대한 접근 및 저장 방식 정의&lt;/li&gt;
&lt;li&gt;버퍼 매니저 : 데이터 페이지를 메모리에 캐시&lt;/li&gt;
&lt;li&gt;리커버리 매니저 : 로그를 유지 관리하고 장애 발생 시 시스템 복구&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;인메모리 DBMS vs 디스크 기반 DBMS&lt;/h2&gt;
&lt;p&gt;인메모리 데이터베이스는 메모리에 데이터를 저장하고 디스크는 복구와 로그 저장 용도로 사용합니다. 반면에 디스크 기반 데이터베이스는 대부분의 데이터를 디스크에 저장하고 캐시로 메모리를 사용합니다. 인메모리 데이터베이스는 데이터 접근 성능이 디스크 기반보다 높습니다. 하지만 RAM은 휘발성이기 때문에 데이터를 영구적으로 저장할 수 없습니다. 그래서 장애 발생 시 데이터가 손실될 수 있습니다. 그래서 인메모리 데이터베이스에서는 데이터 손실을 방지하기 위해 데이터를 디스크에 백업합니다. 보통 write-ahead log(WAL)을 사용합니다.&lt;/p&gt;
&lt;h2&gt;컬럼형 DBMS vs 로우형 DBMS&lt;/h2&gt;
&lt;p&gt;데이터는 디스크에 저장하는 레이아웃 방식에 따라 컬럼형과 로우형 데이터베이스로 분류할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;로우형 데이터 레이아웃&lt;/h3&gt;
&lt;p&gt;로우형 DBMS는 데이터 레코드를 로우 형식으로 저장합니다. 이러한 방식은 한 개의 로우에 접근하는 경우에 적합합니다. 디스크에 저장된 데이터는 블록 단위(디스크에 엑세스하는 최소 단위)로 접근하는데 한 블록에 모든 컬럼 값이 들어갑니다. 이러한 접근 방식은 특정 사용자의 모든 정보를 읽을 때는 효율적이지만, 여러 사용자의 하나의 필드를 읽을 때는 비효율적입니다. 왜냐하면 요청하지 않은 필드까지 페이징하기 때문입니다.&lt;/p&gt;
&lt;h3&gt;컬럼형 데이터 레이아웃&lt;/h3&gt;
&lt;p&gt;컬럼형 DBMS는 데이터를 컬럼 단위로 저장합니다. 로우형과 달리 컬럼끼리 디스크에 연속해서 저장하는 방식입니다. 컬럼형 데이터베이스는 데이터의 평균과 같은 집계 분석 작업을 수행하는데 적합합니다. 컬럼형 데이터베이스는 조인이나 필터링, 다중 로우 집계 등을 위해 튜플을 재구성하기 위해서는 컬럼 사이의 관계를 정의하는 메타데이터가 추가로 필요합니다. 그래서 각 값마다 추가적인 값이 중복되서 저장되므로 데이터 양이 증가합니다. 컬럼형 데이터베이스에서는 아래와 같이 데이터를 저장합니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Symbol: 1:DOW; 2:DOW; 3:S&amp;amp;P; 4:S&amp;amp;P
Date:   1:08 Aug 2018; 2:09 Aug 2018; 3:08 Aug 2018; 4:09 Aug 2018
Price:  1:24,314.65; 2:24,136.16; 3:2,414.45; 4:2,232.32&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;와이드 컬럼 스토어&lt;/h3&gt;
&lt;p&gt;구글의 빅테이블이나 HBase는 와이드 컬럼 스토어입니다. 일반적인 컬럼형 데이터베이스와는 다른 특성을 갖고 있기 때문에 와이드 컬럼 스토어로 다르게 구분합니다. 와이드 컬럼 스토어는 데이터를 다차원 맵의 형태로 표현하고 여러 컬럼을 컬럼 패밀리 단위로 저장합니다. 컬럼 패밀리의 데이터는 로우 형식으로 저장합니다. 이러한 방식은 키 단위 엑세스 패턴에 적합합니다.&lt;/p&gt;
&lt;h2&gt;데이터 파일과 인덱스 파일&lt;/h2&gt;
&lt;p&gt;데이터베이스는 여러 필드로 구성된 레코드를 테이블 형식으로 저장하는데 일반적으로 각 테이블은 별도의 파일에 저장합니다. 그리고 테이블의 각 레코드는 검색 키로 찾을 수 있습니다. 주로 이러한 레코드의 위치를 찾는데 인덱스를 사용합니다. 데이터베이스 시스템은 데이터 파일과 인덱스 파일을 분리합니다. 데이터 파일에는 데이터의 레코드가 저장되며 인덱스 파일에는 레코드에 대한 메타데이터를 저장하고 이를 통해 데이터 파일에서 레코드를 찾습니다.&lt;/p&gt;
&lt;h3&gt;데이터 파일&lt;/h3&gt;
&lt;p&gt;데이터 파일은 Heap-organized table, Hash-organized table, Index-organized table와 같은 종류가 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Heap-organized table : 레코드를 삽입 순서대로 저장하고 데이터 검색 시 데이터 실제 위치를 가리키는 인덱스를 필요로 합니다.&lt;/li&gt;
&lt;li&gt;Hash-organized table : 레코드를 각 키의 해시 값에 해당하는 버킷에 저장합니다. 버킷 내 레코드는 삽입 순서대로 저장하거나 키 순서로 정렬하면 조회 속도를 향상시킬 수 있습니다.&lt;/li&gt;
&lt;li&gt;Index-organized table : 인덱스에 데이터 레코드를 저장합니다. 데이터는 키 순서로 정렬됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;인덱스 파일&lt;/h3&gt;
&lt;p&gt;인덱스틑 디스크에 저장된 데이터 레코드를 효율적으로 검색할 수 있는 자료 구조입니다. 인덱스 파일은 데이터 레코드를 식별할 수 있는 키나 기본 키를 데이터 파일의 해당 레코드 위치에 매핑하는 특별한 구조로 구성되어 있습니다.&lt;br /&gt;프라이머리 인덱스는 기본 키나 기본키 역할을 할 수 있는 여러 키의 조합에 대한 인덱스입니다. 다른 인덱스는 모두 세컨더리 인덱스입니다.&lt;/p&gt;
&lt;p&gt;데이터베이스에서 데이터 레코드를 직접 참조할지 프라이머리 키 인덱스를 통해 접근할 지 의견이 갈립니다. 각각의 방식은 장단점이 존재합니다. 데이터를 직접 참조하면 디스크 탐색 오버헤드가 줄지만 레코드를 갱신하거나 위치를 변경할 때 포인터를 수정해야 합니다. 반면 프라이머리 인덱스를 통해 간접 참조하면 포인트 갱신 비용은 줄어들지만 레코드 위치를 찾는 과정이 추가됩니다.&lt;/p&gt;
&lt;h2&gt;버퍼링(Buffering), 불변성(Immutability), 순서(Ordering)&lt;/h2&gt;
&lt;p&gt;데이터베이스 자료 구조에 고려해야 할 대표적인 속성 3가지가 있습니다. 이 3가지 특정을 조합하여 데이터베이스에 가진 특징에 맞도록 사용하거나 사용하지 않는 방식으로 자료 구조를 설계할 수 있습니다. 각 속성의 특징은 다음과 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;버퍼링
&lt;ul&gt;
&lt;li&gt;데이터를 디스크에 쓰기전 일부를 메모리에 저장할지 여부를 정의&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;불변성
&lt;ul&gt;
&lt;li&gt;파일 일부를 읽고 갱신한 뒤 똑같은 자리에 다시 쓸지에 대한 여부&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;순서
&lt;ul&gt;
&lt;li&gt;디스크 페이지에 데이터 레코드를 키 순서로 저장할지에 대한 것&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;p&gt;이번 포스트에서는 DBMS의 아키텍처와 주요 컴포넌트에 관해 살펴보았습니다. 인메모리 기반과 디스크 기반의 DBMS의 차이에 대해 알아보았고, 컬럼형과 로우형 데이터베이스에 차이도 알아보았습니다. 그리고 데이터베이스에서 데이터를 저장하는 방식에 대해 알아보았습니다. 그리고 마지막에 데이터베이스 자료 구조의 중요한 3가지 속성인 버퍼링, 불변성, 순서에 관해 살펴보았습니다. 이번 포스트를 통해서 데이터베이스의 기본 개념에 대해 청사진을 그릴 수 있었을 것이라고 생각합니다.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://book.naver.com/bookdb/book_detail.nhn?bid=17852468&quot;&gt;데이터베이스 인터널스&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Big Data/Database</category>
      <category>NoSQL</category>
      <category>RDB</category>
      <category>데이터베이스</category>
      <category>로우형 데이터베이스</category>
      <category>인덱스</category>
      <category>인메모리 DB</category>
      <category>컬럼형 데이터베이스</category>
      <author>Data Engineer</author>
      <guid isPermaLink="true">https://seamless.tistory.com/107</guid>
      <comments>https://seamless.tistory.com/107#entry107comment</comments>
      <pubDate>Sun, 21 Mar 2021 08:57:26 +0900</pubDate>
    </item>
    <item>
      <title>Spark에서 groupByKey 대신 reduceByKey 사용하기</title>
      <link>https://seamless.tistory.com/101</link>
      <description>&lt;p&gt;이번 포스트에서는 스파크에서 빈번히 사용되는 transformation인 &lt;code&gt;reduceByKey&lt;/code&gt;와 &lt;code&gt;groupByKey&lt;/code&gt;의 동작에 대해 살펴보겠습니다.&lt;br /&gt;먼저 스파크에서 &lt;code&gt;reduceByKey&lt;/code&gt;와 &lt;code&gt;groupByKey&lt;/code&gt;를 사용하여 단어 세기 예제를 작성해보도록 하겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scala&quot;&gt;val words = Array(&quot;one&quot;, &quot;two&quot;, &quot;two&quot;, &quot;three&quot;, &quot;three&quot;, &quot;three&quot;)
val wordPairsRDD = sc.parallelize(words).map(word =&amp;gt; (word, 1))

val wordCountsWithReduce = wordPairsRDD
  .reduceByKey(_ + _)
  .collect()

val wordCountsWithGroup = wordPairsRDD
  .groupByKey()
  .map(t =&amp;gt; (t._1, t._2.sum))
  .collect()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;두 방법 모두 정확한 답을 출력합니다. 그러나 &lt;code&gt;reduceByKey&lt;/code&gt;가 큰 데이터 셋에서는 더 효율적으로 동작합니다. 그 이유는 &lt;code&gt;reduceByKey&lt;/code&gt;의 내부적인 동작의 차이 때문입니다. &lt;code&gt;reduceByKey&lt;/code&gt;는 키를 기준으로 셔플링을 하기 전에 미리 각 파티션 내에 있는 데이터들을 먼저 combine을 수행합니다. 맵리듀스의 combiner를 사용하는 것과 동일한 역학을 수행하는 것입니다.&lt;/p&gt;
&lt;p&gt;아래의 그림을 보면 &lt;code&gt;reduceByKey&lt;/code&gt;가 어떻게 동작하는지 더 손쉽게 이해할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9994F24A5FDDA2BD23&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;반면에 &lt;code&gt;groupByKey&lt;/code&gt;는 모든 키-값 페어에 대해 셔플을 수행합니다. 이러한 동작은 단어 세기 예제와 같은 경우 불필요한 셔플을 발생하여 네트워크 자원을 더 많이 소모하게 됩니다.&lt;/p&gt;
&lt;p&gt;스파크는 하나의 executor가 가진 메모리보다 더 많은 셔플 데이터를 처리해야 하는 경우에는 데이터를 디스크로 저장하게 됩니다. 만일 하나의 키가 가진 키-값 데이터가 executor의 메모리를 넘어가게 되는 경우에는 out of memory 예외가 발생하게 됩니다. 이러한 상황이 발생하지 않도록 애플리케이션을 작성해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;groupByKey&lt;/code&gt;의 동작은 아래의 그림과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99C4953D5FDDA2C520&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;훨씬 큰 데이터 셋에서 &lt;code&gt;reduceByKey&lt;/code&gt;와 &lt;code&gt;groupByKey&lt;/code&gt;는 셔플링하는 데이터 차이가 극명합니다. &lt;code&gt;groupByKey&lt;/code&gt; 대신 사용할 수 있는 다른 함수들도 존재합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;combineByKey&lt;/code&gt;는 키를 가진 값들을 하나로 병합하는 기능을 수행하지만 병합을 수행하는 과정에서 값의 타입이 바뀔 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;foldByKey&lt;/code&gt;는 &lt;code&gt;reduceByKey&lt;/code&gt;와 다르게 초기값인 &lt;code&gt;zeroValue&lt;/code&gt;를 전달해서 병합 시 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Reference&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://databricks.gitbooks.io/databricks-spark-knowledge-base/content/best_practices/prefer_reducebykey_over_groupbykey.html&quot;&gt;https://databricks.gitbooks.io/databricks-spark-knowledge-base/content/best_practices/prefer_reducebykey_over_groupbykey.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Big Data/Apache Spark</category>
      <category>combinebykey</category>
      <category>Combiner</category>
      <category>groupbykey</category>
      <category>pair-rdd</category>
      <category>reducebykey</category>
      <category>Spark</category>
      <category>스파크</category>
      <author>Data Engineer</author>
      <guid isPermaLink="true">https://seamless.tistory.com/101</guid>
      <comments>https://seamless.tistory.com/101#entry101comment</comments>
      <pubDate>Sat, 19 Dec 2020 15:52:20 +0900</pubDate>
    </item>
    <item>
      <title>아파치 카프카 소개</title>
      <link>https://seamless.tistory.com/100</link>
      <description>&lt;h1&gt;1. Apache Kafka&lt;/h1&gt;
&lt;p&gt;아파치 카프카(이하 카프카)는 여러 대의 분산 서버에서 대량의 데이터를 처리하는 분산 메시징 시스템입니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9970863F5FDDA1231D&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;카프카는 여러 시스템과 장치를 연결하는 중요한 역할을 수행합니다. 카프카는 높은 처리량과 실시간 처리를 할 수 있습니다. 이러한 카프카는 다음의 4가지 특징을 가지고 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;확장성 : 여러 서버로 scale out 구성이 가능합니다.&lt;/li&gt;
&lt;li&gt;영속성 : 수신한 데이터를 디스크에 유지하므로 데이터의 영속성을 유지합니다.&lt;/li&gt;
&lt;li&gt;유연성 : 다양한 제품들을 연결할 수 있어서 시스템을 연결하는 허브 역할을 수행할 수 있습니다.&lt;/li&gt;
&lt;li&gt;신뢰성 : 메시지 전달을 보증하므로 데이터 분실을 걱정하지 않아도 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;2. 카프카 설계 목표&lt;/h1&gt;
&lt;p&gt;카프카는 2011년 링크드인에서 출발하였습니다. 처음의 카프카는 링크드인 웹사이트에서 생성되는 로그를 처리하기 위해 개발하였습니다. 카프카를 설계할 당시 카프카의 실현 목표는 다음과 같습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;높은 처리량으로 실시간 처리&lt;/li&gt;
&lt;li&gt;임의의 타이밍에 데이터를 읽음&lt;/li&gt;
&lt;li&gt;다양한 프로덕트와 쉽게 연동&lt;/li&gt;
&lt;li&gt;메시지를 잃지 않음&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;2.1 카프카 이전 제품&lt;/h2&gt;
&lt;p&gt;물론 이전에도 이러한 카프카의 요구사항을 충족하는 제품들이 있었지만 부분적으로 충족하는 경우가 대부분이고 포괄적으로 해결할 수 있는 제품은 없었습니다. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;메시지 큐&lt;/p&gt;
&lt;p&gt;기존의 메시지 큐를 활용할 수 있었지만 기존의 메시지의 경우는 위의 요구사항을 만족하지 않았습니다. 기존의 메시지 큐의 경우는 메시지를 정확히 한번만 전송(exactly once)되는 것을 보증할 수 있었습니다. 그러나 엄격한 트랜잭션 관리는 다소 오버 스펙이었으며 높은 처리량이 우선순위가 더 높았습니다. 또한 기존의 메시지큐는 스케일 아웃 기능을 대부분이 지원하지 않았습니다. 또한 메시지가 대량으로 쌓이는 것을 예상하지 않았고 배치로 처리하는 것을 고려하지 않았기 때문에 메시지 큐로는 한계가 있었습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;로그 수집 시스템&lt;/p&gt;
&lt;p&gt;기존의 로그 수집 시스템의 경우 Flume이 대표적인 제품이었는데 HDFS로 데이터 축적하는 것과 배치 처리만 고려하였습니다. &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2.2 카프카의 요구사항 실현&lt;/h2&gt;
&lt;p&gt;위에서 작성된 요구사항을 카프카에서 어떻게 실현했는지 살펴보겠습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;높은 처리량으로 실시간 처리 =&amp;gt; 메시징 모델과 스케일 아웃형 아키텍처&lt;/li&gt;
&lt;li&gt;임의의 타이밍에 데이터를 읽음 =&amp;gt; 디스크로 데이터 저장&lt;/li&gt;
&lt;li&gt;다양한 프로덕트와 쉽게 연동 =&amp;gt; 이해하기 쉬운 API 제공&lt;/li&gt;
&lt;li&gt;메시지를 잃지 않음 =&amp;gt; 전달 보증 메커니즘&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;물론 요구 사항과 실현 수단이 일대일로 대응되지 않지만 핵심은 &lt;code&gt;메시징 모델과 스케일 아웃형 아키텍처&lt;/code&gt;와 &lt;code&gt;디스크로 데이터 영속화&lt;/code&gt;라고 할수 있습니다.&lt;/p&gt;
&lt;h3&gt;2.2.1 카프카 메시징 모델&lt;/h3&gt;
&lt;p&gt;카프카 메시징 모델은 다음과 같은 3가지 요소로 구성됩니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9920D1345FDDA12F1E&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;프로듀서 : 메시지 생산자&lt;/li&gt;
&lt;li&gt;브로커 : 메시지 수집/전달 역할&lt;/li&gt;
&lt;li&gt;컨슈머 : 메시지 소비자&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;카프카에서는 여러 컨슈머가 분산 처리로 메시지를 소비하는 큐잉 모델과 여러 서브스크라이버에서 동일한 메시지를 전달하고, 토픽 기반으로 전달 내용을 변경하는 펍/섭 모델로 메시징 모델을 구현하였으며, 컨슈머 그룹이라는 개념을 도입하여 컨슈머를 확장 구성할 수 있도록 설계하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99558E485FDDA13922&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;여러 컨슈머가 동일 토픽을 분산하여 메시지를 읽음으로써 처리 확장성을 제공합니다.&lt;/p&gt;
&lt;h3&gt;2.2.2 디스크로 데이터 영속화&lt;/h3&gt;
&lt;p&gt;카프카는 위에서 설명한대로 임의의 타이밍에 데이터를 읽고 메시지를 잃지 않기 위해서 메시지를 디스크에 저장합니다. 기존의 메시지 큐의 경우는 데이터를 메모리에 유지하였지만 실시간 처리에 중점을 두고 있는 경우가 많고 장기 보존을 가정하지 않았습니다. 그러나 카프카의 경우 데이터를 기간마다 모아 배치 처리도 고려했기 때문에 데이터를 메모리에 유지하는 것은 어려움이 있었습니다. 그래서 카프카의 메시지는 디스크에 저장하도록 구성하였습니다. 카프카의 특징은 디스크에 데이터를 저장함에도 불구하고 높은 처리량을 제공합니다. 후에 기회가 되면 디스크에 저장하면서 어떻게 높은 처리량을 제공할 수 있었는지를 포스트하도록 하겠습니다.&lt;/p&gt;
&lt;h3&gt;2.2.3 다양한 커넥터 제공&lt;/h3&gt;
&lt;p&gt;위의 특징에서 살펴보았듯이 카프카는 프로듀서와 컨슈머를 쉽게 접속할 수 있는 &lt;code&gt;Connect API&lt;/code&gt;를 제공합니다. 이 API를 기반으로 카프카에 접속하기 위한 Kafka Connect를 제공하고 있습니다. 현재 컨플루언트에서는 다양한 제품을 지원하는 커넥터를 제공합니다. 또한 커뮤니티에서 제공하는 커넥터도 다양합니다. 이와같이 다양한 제품을 지원하므로써 데이터를 쉽게 연결할 수 있는 특징을 가지고 있습니다.&lt;/p&gt;
&lt;h3&gt;2.2.4 메시지 전달 보장&lt;/h3&gt;
&lt;p&gt;메시지 전달 방식에는 At Most Once, At Least Once, Exactly Once가 있으며 카프카에서는 3가지 수준으로 전달을 보장합니다. 각각의 특징은 아래의 표와 같습니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;center&quot;&gt;종류&lt;/th&gt;
&lt;th&gt;개요&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;재전송유무&lt;/th&gt;
&lt;th align=&quot;center&quot;&gt;중복 삭제 유무&lt;/th&gt;
&lt;th&gt;비고&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;At Most Once&lt;/td&gt;
&lt;td&gt;1회 전달 시도&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;td&gt;메시지는 중복되지 않지만 상실될 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;At Least once&lt;/td&gt;
&lt;td&gt;적어도 1회는 전달&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;X&lt;/td&gt;
&lt;td&gt;메시지가 중복될 가능성은 있지만, 상실되지는 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;center&quot;&gt;Exactly Once&lt;/td&gt;
&lt;td&gt;1회만 전달&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td align=&quot;center&quot;&gt;O&lt;/td&gt;
&lt;td&gt;중복되거나 상실되지도 않고 확실하게 메시지가 도달하지만, 성능이 나오지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h4&gt;At Least Once&lt;/h4&gt;
&lt;p&gt;일반적으로 메시지 큐에서는 Exactly Once 수준을 제공하는 것을 목적으로 합니다. 카프카의 개발 초기에는 높은 처리량을 목적으로 했기 때문에 At Least Once 수준의 전달의 보장했습니다.&lt;br /&gt;At Least Once의 경우 &lt;code&gt;Ack&lt;/code&gt;와 &lt;code&gt;Offset Commit&lt;/code&gt;을 통해서 제공했습니다. Ack는 프로듀서에게 수신에 대한 응답을 뜻합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9923BC4B5FDDA14622&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;마찬가지로 컨슈머가 브로커로 메시지를 받을 때 어디까지 메시지를 받았는지 관리하기 위한 오프셋이 존재합니다. 이를 이용한 전달 보장 구조를 오프셋 커밋이라고 합니다. Ack와 비슷한 역할을 수행하고 있는 것이죠.&lt;/p&gt;
&lt;h4&gt;Exactly Once&lt;/h4&gt;
&lt;p&gt;카프카의 사용자가 늘어나면서 Exactly Once의 요구사항이 많아졌습니다. 그래서 카프카에서는 트랜잭션 개념을 도입하여 Exactly Once를 보장합니다. Exactly Once는 (프로듀서와 브로커), (브로커와 컨슈머) 사이의 서로 구현이 필요합니다. 프로듀서와 브로커 사이에 양쪽 모두에서 시퀀스 번호를 관리해 중복되는 실행을 제거하는 방법을 사용합니다. 멱등성을 유지하는 프로듀서라는 의미에서 Idempotent Producer라고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9970953F5FDDA14D1D&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;브로커와 컨슈머 간에는 컨슈머에 대해 트랜잭션 관리 메커니즘을 제공해서 Exactly Once를 제공합니다. 이와 같이 Exactly Once를 보장하려면 카프카 뿐만 아니라 프로듀서와 컨슈머에서도 상태 관리가 필요합니다.&lt;/p&gt;
&lt;br /&gt;

&lt;h2&gt;Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.hanbit.co.kr/media/books/book_view.html?p_code=B8503179529&quot;&gt;실전 아파치 카프카&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Big Data/Apache Kafka</category>
      <category>Broker</category>
      <category>consumer</category>
      <category>Kafka</category>
      <category>messeage queue</category>
      <category>Producer</category>
      <category>pubsub</category>
      <category>streaming</category>
      <category>메세지큐</category>
      <category>카프카</category>
      <author>Data Engineer</author>
      <guid isPermaLink="true">https://seamless.tistory.com/100</guid>
      <comments>https://seamless.tistory.com/100#entry100comment</comments>
      <pubDate>Sat, 19 Dec 2020 15:46:13 +0900</pubDate>
    </item>
    <item>
      <title>이벤트 시간 처리(Event Time Processing)와 워터마크(Watermark) - (feat. Apache Flink)</title>
      <link>https://seamless.tistory.com/99</link>
      <description>&lt;h1&gt;이벤트 시간 처리(Event Time Processing)와 워터마크(Watermark)&lt;/h1&gt;
&lt;p&gt;스트림 처리에서 바라보는 시간적 측면 중에 &lt;strong&gt;이벤트 시간(Event time)&lt;/strong&gt; 기반으로 처리하는 방식에 대해 살펴보겠습니다. 최근에 데이터 처리 분야에서 스트리밍 애플리케이션 개발의 중요성이 더욱 커지고 있습니다. 만약 스트리밍 애플리케이션을 개발하게 되는 경우 애플리케이션의 목적에 따라 이벤트 시간(Event time)을 기준으로 처리할 것인지 처리 시간(Processing time) 기준으로 처리할 것인지 선택을 해야 할 것입니다. 각각의 시간이 갖는 특성을 이해하고 있으면 스트리밍 애플리케이션의 요구사항에 알맞는 시간을 선택하여 개발할 수 있을 것입니다.&lt;/p&gt;
&lt;h2&gt;스트림 처리에서 바라보는 시간&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;이벤트 시간(event time)&lt;ul&gt;
&lt;li&gt;이벤트 시간이란 데이터에 의존적인 타임스탬프입니다. 즉, 데이터 내에 존재하는 데이터 발생 시간과 같은 것입니다. 데이터에 의존적이기 때문에 어떠한 값을 타임스탬프로 넣을 것인지 다르겠지만 주로 이벤트가 발생한 시간을 많이 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;처리 시간(processing time)&lt;ul&gt;
&lt;li&gt;처리 시간이란 실제 스트림 처리 엔진에서 데이터를 처리하는 시간입니다. 즉, 해당 데이터를 받아 처리할 때 처리하는 서버의 시간입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;수집 시간(ingestion time)&lt;ul&gt;
&lt;li&gt;스트림 처리 엔진에 데이터가 처음으로 수집된, 즉, 들어온 시간을 나타냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;글로만 보았을 때는 헷깔리는 부분이 있습니다. 다음 그림을 보면 이해하기 쉬울 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99EE33435FC398630B&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;스트림에서 바라보는 시간들이 잘 구별되어 있습니다. 이 중에 이벤트 시간과 처리 시간이 가장 많이 사용됩니다. 스토리지 시간(Storage time)의 경우 많이 사용되진 않습니다.&lt;/p&gt;
&lt;p&gt;이벤트 시간은 위에서 설명했듯이 실세계에서 발생한 이벤트의 시간이고 처리 시간은 스트림 시스템에서 이벤트를 처리한 시간입니다. 포스트의 제목처럼 이벤트 시간 처리의 이해도를 높이기 위해서 먼저 처리 시간 기반의 시스템에 대해 살펴보겠습니다. 이 포스트에선 따로 스트림 처리에서 사용하는 윈도우의 개념에 대해서는 설명하지 않습니다. 윈도우에 대한 개념은 해당 블로그 내에 있는 &lt;a href=&quot;https://eoriented.github.io/post/stream-processing-2/&quot;&gt;스트림 프로세싱&lt;/a&gt; 포스트에서 확인하실 수 있습니다.&lt;/p&gt;
&lt;h2&gt;처리 시간 기반 시스템&lt;/h2&gt;
&lt;p&gt;처리 시간 기반 시스템의 예제는 윈도우 사이즈가 10이고 5초마다 슬라이딩 되어 윈도우가 처리됩니다. 본 예제는 간단히 윈도우 내에서 키를 기준으로 워드 카운트를 하는 예제입니다. 플링크 기반의 예제로 설명을 하는데 대부분의 스트림 처리 엔진에서도 비슷한 방식의 연산들을 제공합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;val senv = StreamExecutionEnvironment.getExecutionEnvironment
val text = env.socketTextStream(&amp;quot;localhost&amp;quot;, 9999)

val counts = text.map {(m: String) =&amp;gt; (m.split(&amp;quot;,&amp;quot;)(0), 1) }
    .keyBy(0)
    .timeWindow(Time.seconds(10), Time.seconds(5))
    .sum(1)

counts.print
senv.execute(&amp;quot;ProcessingTime example&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 예제에서 메시지가 정시에 도착하는 경우와 메시지가 지연이 발생해서 늦게 도착하는 경우를 나누어서 사례를 살펴보겠습니다. 본 예제에서는 아래의 그림과 같이 3개의 메세지에 대한 처리를 합니다. 메시지는 CSV 형태이며 &lt;code&gt;value,eventTime&lt;/code&gt;의 문자열입니다. 첫 번째는 13초에 &lt;code&gt;a&lt;/code&gt;라는 키를 가진 이벤트가 2개 발생하고 16초에 1개 발생합니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99D94A385FC3988E0E&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;메시지가 정시에 도착하는 경우&lt;/h3&gt;
&lt;p&gt;위의 소스 코드에서 처럼 윈도우 사이즈는 10이며 5초마다 슬라이딩 됩니다. 메시지 지연이 없는 경우 아래의 그림과 같이 처리가 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99B78E3F5FC398A305&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;일반적으로 개발자는 메시지를 위의 그림과 같이 처리하기를 기대합니다. 그러나 메시지는 네트워크 끊김과 같은 상황으로 인해 지연될 수 있습니다. 그럼 메시지가 시스템에 지연되어 도착하는 경우 처리 시간 기반 시스템에선 어떻게 처리되는지 살펴보겠습니다.&lt;/p&gt;
&lt;h3&gt;메시지가 지연이 발생해서 늦게 도착하는 경우&lt;/h3&gt;
&lt;p&gt;13초에 발생한 메시지가 6초의 지연이 발생하여 19초에 도착했다라고 가정해봅시다. 그러면 처리 시간 기반 시스템에서는 어떻게 처리될까요? 바로 다음과 같이 처리가 될 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/992D4D3B5FC398B306&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;위의 그림에서 처럼 첫 번째 윈도우의 키 합의 결과가 1이되고 윈도우 3의 결과가 2가 되어 우리가 기대했던 결과와 다른 형태로 처리가 되었습니다. 이와 같이 메시지 지연이 발생하는 경우 우리가 기대했던 결과값과 다른 결과가 나올 수 있습니다. 그럼 이 문제는 어떻게 해결해야 할까요? 이러한 문제를 해결하기 위해서는 처리 시간을 사용하는 것이 아닌 이벤트 시간 기준으로 처리해야 합니다.&lt;/p&gt;
&lt;h2&gt;이벤트 시간 기반 처리 시스템&lt;/h2&gt;
&lt;p&gt;이벤트 시간은 메시지가 생성된 시간이 대표적입니다. 그러나 스트림 처리 시스템에서는 데이터에서 어떤 것이 이벤트 시간인지 알 수 없습니다. 그래서 데이터 내에 시간 부분을 추출하는 방법을 알려줘야 합니다. 플링크에서는 이벤트 시간을 추출하는 클래스를 정의해주어야 합니다. 정의하는 방법은 다음과 같습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class TimestampExtractor extends AssignerWithPeriodicWatermarks[String] with Serializable {
  override def extractTimestamp(e: String, prevElementTimestamp: Long) = {
    e.split(&amp;quot;,&amp;quot;)(1).toLong 
  }

  override def getCurrentWatermark(): Watermark = { 
    new Watermark(System.currentTimeMillis)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 코드와 같이 이벤트 시간 추출기를 정의해줍니다. 실제 이벤트 시간 추출은 &lt;code&gt;extractTimestamp&lt;/code&gt;라는 메소드에서 처리되서 나온 결과로 사용합니다. &lt;code&gt;getCurrentWatermark&lt;/code&gt; 메소드는 뒤에서 설명드리도록 하겠습니다. 이제 이벤트 시간을 기반으로 하는 스트림 애플리케이션을 작성해보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;val senv = StreamExecutionEnvironment.getExecutionEnvironment
senv.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)

val text = senv.socketTextStream(&amp;quot;localhost&amp;quot;, 9999)
                .assignTimestampsAndWatermarks(new TimestampExtractor) 

val counts = text.map {(m: String) =&amp;gt; (m.split(&amp;quot;,&amp;quot;)(0), 1) }
      .keyBy(0)
      .timeWindow(Time.seconds(10), Time.seconds(5))
      .sum(1)

counts.print
senv.execute(&amp;quot;EventTime example&amp;quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이벤트 시간을 기반으로 처리하는 경우 &lt;code&gt;TimeCharacteristic&lt;/code&gt;을 &lt;code&gt;EventTime&lt;/code&gt;으로 설정합니다. 그리고 스트림 소스에 이벤트 시간 추출기를 셋팅해주었습니다. 그 외의 로직 코드는 동일합니다. 자, 그럼 위의 코드를 사용하는 경우 메세지 지연이 발생했을 때 어떻게 처리가 되는지 한번 살펴보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99ADDC345FC398D60E&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;메세지 지연이 발생하는 경우 처리 시간 기반 시스템과의 결과가 다릅니다. 이벤트 시간 기반 처리에서는 윈도우 3에서 지연된 메시지의 이벤트 시간이 맞지 않기 때문에 포함되지 않은 결과를 올바르게 내보냅니다. 하지만 여전히 윈도우 1의 결과는 우리가 기대하는 결과값과 다릅니다. 윈도우 1의 완료 시간은 15초인데 메시지는 이미 윈도우가 완료된 후인 19초에 들어왔기 때문입니다. 이렇게 메시지 지연으로 윈도우 1에서 처리해야 할 메시지를 처리하지 못한 것입니다. 이러한 문제를 해결하기 위해서 워터마크(Watermark)라는 기능을 사용합니다.&lt;/p&gt;
&lt;h2&gt;워터마크(Watermark)&lt;/h2&gt;
&lt;p&gt;워터마크는 이러한 지연된 메세지를 처리하기 위한 흥미로운 아이디어입니다. 워터마크는 하나의 타임스탬프입니다. 이 워터마크를 통해 플링크에서는 워터마크보다 지연된 메시지는 도착하지 않을 것이라고 가정하고 결과를 처리합니다. 이러한 워터마크의 개념은 플링크 뿐만 아니라 다른 스트림 시스템에서도 이와 같이 지연된 메시지를 처리하는 방법으로 제공하고 있습니다.&lt;br&gt;우리는 이미 위의 예제에서 워터마크를 설정하는 것을 확인하였습니다. 바로 이벤트 시간 추출기에서 워터마크에 관한 정보도 함께 지정해주었습니다. 이제 워터마크를 통해서 위에서 지연된 메시지를 정상적으로 처리하는 방법에 관해 살펴보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;override def getCurrentWatermark(): Watermark = { 
  new Watermark(System.currentTimeMillis - 5000)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;시간 추출기 클래스에서 워터마크 설정을 &lt;code&gt;(현재 시간 - 5초)&lt;/code&gt;로 설정하겠습니다. 이렇게 설정하게 되면 시스템에게 메시지의 지연 시간을 5초까지는 허용하겠다고 알려주는 것입니다. 이와 같이 설정이 되면 위에서 윈도우 1 &lt;code&gt;[5초 - 15초]&lt;/code&gt;의 결과는 20초에 나오게 됩니다. 마찬가지로 윈도우 2 &lt;code&gt;[10초 - 20초]&lt;/code&gt;는 5초 뒤인 25초에 결과가 나오게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/9948654E5FC398F009&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;이와 같이 워터마크를 사용하여 지연된 메시지를 받은 경우에도 정상적으로 결과를 만들어 내도록 처리하였습니다.&lt;/p&gt;
&lt;p&gt;이번 포스트에서 스트림 시스템에서 사용되는 시간의 종류에 대해 살펴보았습니다. 그리고 그 시간을 기반으로 메시지가 어떻게 처리되는지도 함께 알아보았습니다. 스트림 시스템에서 메세지 지연은 어쩔 수 없이 발생합니다. 이러한 메세지 지연을 처리하기 위해서 워터마크라는 기능을 제공합니다. 스트림 애플리케이션을 개발할 때 애플리케이션에서 메시지 지연을 허용할 지, 혹은 지연된 메시지를 그냥 무시할 것인지는 요구사항에 따라 다릅니다. 그래서 개발하려는 애플리케이션의 특성을 정확히 파악하고 특성에 맞도록 메시지를 처리하도록 해야 합니다.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=3UfZN59Nsk8&quot;&gt;Dataflow: A Unified Model for Batch and Streaming Data Processing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://vishnuviswanath.com/flink_eventtime.html&quot;&gt;http://vishnuviswanath.com/flink_eventtime.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.slideshare.net/dataArtisans/apache-flink-training-time-and-watermarks&quot;&gt;https://www.slideshare.net/dataArtisans/apache-flink-training-time-and-watermarks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://softwaremill.com/windowing-in-big-data-streams-spark-flink-kafka-akka/&quot;&gt;https://softwaremill.com/windowing-in-big-data-streams-spark-flink-kafka-akka/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Big Data/Apache Flink</category>
      <category>Event time</category>
      <category>flink</category>
      <category>ingestion time</category>
      <category>lateness</category>
      <category>processing time</category>
      <category>Sliding window</category>
      <category>tumbling window</category>
      <category>watermark</category>
      <category>window</category>
      <category>워터마크</category>
      <category>플링크</category>
      <author>Data Engineer</author>
      <guid isPermaLink="true">https://seamless.tistory.com/99</guid>
      <comments>https://seamless.tistory.com/99#entry99comment</comments>
      <pubDate>Sun, 29 Nov 2020 21:44:41 +0900</pubDate>
    </item>
    <item>
      <title>06. 파티셔닝(Partitioning) - 2</title>
      <link>https://seamless.tistory.com/98</link>
      <description>&lt;h1&gt;파티셔닝(Partitioning)&lt;/h1&gt;
&lt;hr&gt;
&lt;p&gt;이전 포스트에 이어서 파티셔닝에서 사용하는 리밸런싱 기법에 관해 살펴보고, 클라이언트에서 질의 요청을 어떻게 처리할 것인지에 관해 알아보겠습니다.&lt;/p&gt;
&lt;h2&gt;파티션 리밸런싱&lt;/h2&gt;
&lt;p&gt;리밸런싱이란 클러스터에서 한 노드가 담당하던 부하를 다른 노드를 옮기는 과정입니다. 이러한 리밸런싱이 필요한 경우는 시간이 지나면서 데이터베이스에 변화가 생기기 때문입니다. 예를 들어 질의 처리량이 증가하여 부하를 늘리기 위해 CPU를 추가하거나, 데이터 셋의 크기가 증가하여 디스크와 램을 추가 하는 등의 변화입니다. 이러한 리밸런싱을 위한 전략이 몇가지가 있습니다.&lt;/p&gt;
&lt;h3&gt;리밸런싱 전략&lt;/h3&gt;
&lt;h4&gt;쓰면 안되는 방법: 해시값 mod N&lt;/h4&gt;
&lt;p&gt;mod 연산을 사용하면 쉽게 각 키를 노드에 할당하는 것이 쉽다. 예를 들어 노드가 10대가 있고 hash(key) mod 10을 통해 0-9 사이의 숫자를 기반으로 해당 노드에 할당하는 방식이다. 처음엔 문제가 되지 않는다 그러나 노드가 1대 늘어나서 11대가 되는 경우 대부분의 키값들은 다른 노드로 옮겨져야 하기 때문에 리밸런싱 비용이 지나치게 커지는 문제가 발생한다.&lt;/p&gt;
&lt;h4&gt;고정 파티션 수&lt;/h4&gt;
&lt;p&gt;간단한 해결책으로 파티션의 개수를 고정하는 방법이 있습니다. 즉, 총 파티션의 개수를 고정해놓고 새로운 노드가 추가되는 경우에 파티션만 다시 분배시키는 방법입니다. 클러스터에서 노드가 제거 되면 이 과정이 반대로 수행됩니다. 이러한 과정을 나타내는 그림은 아래와 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99976D435FBE61A913&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;고정 파티션을 사용할 때 처음 설정된 파티션 개수가 사용가능한 노드 대수의 최대치가 되므로 미래에 충분히 수용가능하도록 설정해야 합니다. 너무 크거나 작은 경우에는 오버헤드가 발생할 수 있습니다. 사실 적당히 선택해야 하지만 적절한 크기를 찾기는 쉽지 않습니다. 결국 애플리케이션의 특성을 잘 파악하고 후에 얼마나 변할 것인지 잘 예측해서 정해야 하는 어려움이 있습니다.&lt;/p&gt;
&lt;h4&gt;동적 파티셔닝&lt;/h4&gt;
&lt;p&gt;범위 파티셔닝의 경우 파티션 경계를 잘못 지정하면 모든 데이터가 한 파티션에 저장되고 나머지 파티션은 빌 수 있습니다. 이 경우에 파티션 경계를 수동으로 재설정해야 하는데 고된 작업입니다.&lt;br&gt;이러한 이유 때문에 범위 파티셔닝을 사용하는 데이터베이스에서는 파티션을 동적으로 만듭니다. 예를 들어 파티션 크기가 설정된 값을 넘어서면 파티션을 두개로 쪼개고 반대로 데이터가 많이 삭제되어 파티션 크기가 임계값 아래로 떨어지면 인접한 파티션과 합쳐질 수 있습니다.&lt;br&gt;그러나 이러한 방식은 빈 데이터베이스부터 시작하는 경우 파티션 경계에 관한 사전 정보가 없으므로 시작할 때 파티션이 한개로 시작합니다. 파티션이 한개로 시작하면 나머지 노드들은 유휴 상태로 존재합니다. 이 문제를 해결하기 위해 HBase와 몽고DB에서는 빈 데이터베이스에 초기 파티션 집합을 설정할 수 있습니다. 이 방식을 pre-splitting이라고 합니다.&lt;/p&gt;
&lt;h4&gt;노드 비례 파티셔닝&lt;/h4&gt;
&lt;p&gt;파티션 개수가 노드 수에 비례하도록 하는 방법입니다. 즉, 노드당 할당되는 파티션 개수를 고정하는 방식입니다. 노드 수가 변함 없는 동안 개별 파티션 크기가 데이터셋의 크기에 비례해서 증가 하지만, 노드 수를 늘리면 파티션 크기는 작아지도록 하는 방식입니다. 이 방식을 채택하고 있는 대표적인 데이터베이스는 카산드라입니다.&lt;/p&gt;
&lt;h2&gt;라우팅 요청&lt;/h2&gt;
&lt;p&gt;클라이언트에서 요청을 보내려고 할 때 어느 노드에 접속해야 하는지 어떻게 알 수 있을까요? 아래의 그림과 같이 3가지 방식이 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99E9123E5FBE61CC1A&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;클라이언트가 아무 노드에 접속하고 해당 노드에 파티션이 존재하면 직접 처리하고 아니면 요청을 올바른 노드로 전달해서 응답을 받아서 돌려주는 방법&lt;/li&gt;
&lt;li&gt;클라이언트가 모든 요청을 라우팅 계층으로 보내는 방법&lt;/li&gt;
&lt;li&gt;클라이언트가 파티셔닝 방법과 파티션이 어떤 노드에 할당됐는지 알고 있어서 직접 처리하는 방법(partition-aware)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;여기서 가장 중요한 문제는 라우팅 결정을 내리는 구성요소가 노드에 할당된 파티션의 변경 사항을 어떻게 알 수 있느냐 입니다. 이와 같은 문제는 라우팅을 요청 받는 곳에서 정보가 일치해야 합니다. 주로 분산 데이터 시스템은 클러스터의 메타데이터 추석을 위해 주키퍼와 같은 코디네이션 서비스를 이용합니다. 파티션 소유자가 변경되거나 노드가 추가, 삭제 되는 경우 주키퍼는 라우팅 계층에 이를 알려줘서 라우팅 정보를 최신으로 유지합니다.&lt;/p&gt;
&lt;h3&gt;정리&lt;/h3&gt;
&lt;p&gt;이번 포스트에서는 대용량 데이터셋을 파티셔닝하는 다양한 방법을 살펴보았습니다. 먼저 주요 파티셔닝 방식인 범위 파티셔닝과 해시 파티셔닝에 관해 살펴보았습니다. 그리고 파티셔닝과 세컨더리 인덱스 사이의 상호 작용에 관해 알아보았습니다. 끝으로 라우팅 기법도 살펴보았습니다. 이러한 파티셔닝 방법을 통해서 대용량 데이터를 여러 장비에 처리를 할 수 있습니다. 핫스팟이 생기지 않고 질의 부하를 여러 장비에 균등하게 분배할 수 있도록 데이터에 적합한 파티셔닝 방식을 선택하여 사용해야 합니다.&lt;/p&gt;</description>
      <category>Big Data/Designing Data-Intensive Applicatiosn</category>
      <category>partitioning</category>
      <category>데이터라우팅</category>
      <category>보조색인</category>
      <category>색인</category>
      <category>파티셔닝</category>
      <category>파티션</category>
      <author>Data Engineer</author>
      <guid isPermaLink="true">https://seamless.tistory.com/98</guid>
      <comments>https://seamless.tistory.com/98#entry98comment</comments>
      <pubDate>Wed, 25 Nov 2020 22:54:33 +0900</pubDate>
    </item>
    <item>
      <title>06. 파티셔닝(Partitioning) - 1</title>
      <link>https://seamless.tistory.com/97</link>
      <description>&lt;p&gt;데이터셋이 매우 크거나 질의 처리량이 매우 높은 경우 데이터를 파티션으로 쪼개야 합니다. 이번 포스트에서 이야기하는 파티셔닝은 대용량 데이터베이스에서 데이터를 작은 단위로 쪼개는 방법을 말합니다. 몽고DB, 엘라스틱서치, 솔라에서는 샤드라고 하며 HBase에서는 리전, 빅테이블에서는 태블릿(tablet), 카산드라와 리악에서는 vnode, 카우치베이스에서 vBucker이라고 부릅니다.&lt;br&gt;데이터 파티셔닝의 가장 큰 목적은 확장성을 갖기 위함입니다. 대용량 데이터셋을 파티셔닝하여 여러 디스크에 분산시킬 수 있고, 질의 부하를 분산시킬 수 있습니다.&lt;/p&gt;
&lt;p&gt;주로 파티셔닝은 복제(Replication)와 함께 적용해 파티션의 복사본을 여러 노드에 저장합니다. 복제에 관한 설명은 이전 포스트에서 확인할 수 있습니다. 파티셔닝된 데이터를 복제함으로써 내결함성을 보장할 수 있게 됩니다. 일반적인 리더-팔로워 복제 모델을 사용하면 파티셔닝과 복제의 조합은 다음 그림과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99A4384E5FBD119115&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;키-값 데이터 파티셔닝&lt;/h3&gt;
&lt;p&gt;키-값 데이터 모델을 사용한다고 가정했을 때, 이 모델에서는 기본키를 통해 레코드에 접근합니다.&lt;/p&gt;
&lt;h4&gt;범위 키 파티셔닝&lt;/h4&gt;
&lt;p&gt;각 파티션에 연속된 범위를 키에 할당하는 방법입니다. 가장 대표적인 예가 아래의 그림과 같이 종이 백과사전이 되겠습니다. 각 범위들 사이의 경계를 알면 어떤 키가 어느 파티션에 속하고 있는지 쉽게 찾을 수 있습니다. 또한 어떤 파티션이 어느 노드에 할당되어 있는지 알면 적절한 노드로 요청을 직접 보낼 수 있습니다. 백과사전의 경우 책장에서 알맞는 책을 꺼낼 수 있는 것과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/99B9A3425FBD119E14&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;키 범위의 크기가 동일할 필요는 없습니다. 데이터가 고르게 분포가 되지 않을 경우도 있기 때문입니다. 위 그림의 예에서 보듯이 1권의 경우 A-B까지 범위이지만 마지막 권의 경우 T-Z로 범위가 더 넓습니다. 이와 같이 데이터를 고르게 분산시키려면 파티션 경계를 데이터에 맞게 조정해야 합니다.&lt;br&gt;파티션 내에서 키를 기준으로 정렬된 순서로 저장할 수 있는데 이렇게 하면 범위 스캔이 쉬워지는 장점을 갖습니다. 또한 키를 연결된 인덱스로 간주해서 질의 하나로 관련 레코드를 여러개를 읽어오는데 사용할 수 있습니다.&lt;br&gt;키 범위 파티셔닝의 경우 핫스팟(불균형하게 부하가 높은 파티션)이 발생할 수 있는 단점이 있습니다. 이 경우에는 애플리케이션의 특성에 맞춰서 파티셔닝의 키 선택을 해야 합니다.&lt;/p&gt;
&lt;h4&gt;해시 키 파티셔닝&lt;/h4&gt;
&lt;p&gt;주로 분산 데이터스토어는 쏠림과 핫스팟 문제 때문에 키의 파티션을 정하는데 해시 함수를 많이 사용합니다. 이 기법은 키를 파티션 사이에 균일하게 분산시킬 수 있는 장점을 갖습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/991909435FBD11AB10&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;해시 파티셔닝의 경우 범위 파티셔닝에서 범위 질의를 효율적으로 실행할 수 있는 장점을 잃어 버립니다. 범위 파티셔닝에서 인접했던 키들이 모든 파티션에 흩어져서 정렬 순서가 유지되지 않기 때문입니다.&lt;/p&gt;
&lt;h4&gt;쏠린 작업 부하와 핫스팟 완화&lt;/h4&gt;
&lt;p&gt;해시 기반의 파티셔닝을 하면 핫스팟을 줄일 수 있지만 완전히 제거할 수 없습니다. 만약 항상 동일한 키를 읽고 쓰는 극단적인 상황에서는 모든 요청이 동일한 파티션으로만 쏠리게 됩니다. 이러한 경우 애플리케이션에서 쏠림을 완화해야 하는데 주로 간단한 해결책은 각 키의 시작이나 끝에 임의의 숫자를 붙여서 해당 키를 여러 파티션으로 분산시키는 방법입니다. 대신에 이와 같이 하나의 키를 쪼개서 쓰게 되면 읽기를 실행할 때 추가적인 작업이 필요합니다. 예를 들어 하나의 키를 100개로 나누었을 때 나누어진 데이터를 모아서 처리해야 하기 때문입니다. 그리고 추가적으로 저장해야 할 정보도 있기 때문에 요청이 몰리는 소수의 키에서만 사용해야 합니다. 낮은 쓰기 처리량을 가진 키에도 적용하면 불필요한 오버헤드가 발생하기 때문입니다. &lt;/p&gt;
&lt;h3&gt;파티셔닝과 세컨더리 인덱스&lt;/h3&gt;
&lt;p&gt;세컨더리 인덱스는 레코드를 유일하게 식별하는 용도가 아닌 특정한 값이 발생한 항목을 검색하는 수단입니다. 키-값 데이터 모델에서는 레코드를 기본키를 통해서만 접근하므로 키를 기준으로 파티션을 결정할 수 있습니다. 그러나 세컨더리 인덱스는 파티션에 깔끔하게 대응되지 않는 문제점을 갖습니다. 세컨더리 인덱스 기능을 제공하는 데이터베이스에서 파티셔닝하는데 널리 쓰이는 2가지 방법이 있습니다. 바로 문서 기반 파티셔닝과 용어 기반 파티셔닝입니다.&lt;/p&gt;
&lt;h4&gt;문서 기반 세컨더리 인덱스 파티셔닝&lt;/h4&gt;
&lt;p&gt;한가지 예를 통해 살펴보겠습니다. 중고차를 판매하는 웹사이트를 운영하고 있습니다. 차마다 문서 ID라는 고유의 ID가 있으며, 데이터베이스는 문서 ID를 기준으로 파티셔닝을 합니다. 사용자들이 차를 검색할 때 색상과 제조사로 필터링하는 기능을 제공하려면, color와 make(관계형 데이터베이스는 칼럼이라고 하고 문서 데이터베이스에서는 필드라고 함)에 세컨더리 인덱스를 만들어야 합니다. 인덱스로 설정을 하면 데이터베이스가 데이터가 추가될 때 자동으로 인덱스를 생성할 수 있습니다. 아래의 그림이 이와 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/990A69475FBD11B514&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;이러한 인덱싱 방법을 사용하면 각 파티션 별로 독립적으로 동작이 가능합니다. 각 파티션 별로 자신의 세컨더리 인덱스를 유지하며 그 파티션에 속하는 문서만 담당합니다. 그러한 까닭에 문서 파티셔닝 인덱스는 지역 인덱스(local)라고도 합니다.&lt;/p&gt;
&lt;p&gt;일반적으로 특정한 색상이나 특정한 제조사가 만든 자동차가 동일한 파티션에 저장되지 않을 수 있으므로 빨간색 자동차를 찾고 싶다면 모든 파티션으로 질의를 보내서 결과를 모두 모아야 합니다. 파티셔닝된 데이터베이스에서 이런 식으로 질의를 보내는 방법을 scatter/gather라고 합니다. 이런 질의는 읽는 질의에 큰 비용이 발생할 수 있습니다. 그럼에도 불구하고 세컨더리 인덱스를 문서로 기준으로 파티셔닝하는 경우가 많습니다. 대표적으로 몽고DB, 리악, 카산드라, 엘라스틱서치, 볼트DB 등 입니다.&lt;/p&gt;
&lt;h4&gt;단어(term) 기반 세컨더리 인덱스 파티셔닝&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/994D3B4F5FBD11BD11&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;위의 그림을 통해 예를 살펴보겠습니다. 찾고자 하는 단어에 따라 인덱스의 파티션이 결정되므로 단어 기준으로 파티셔닝됩니다(term-partitioned). 색깔 인덱스의 경우 a-r 까지의 글자로 시작하는 색깔은 파티션 0에 s-z까지의 글자로 시작하는 색깔은 파티션 1에 저장되도록 파티셔닝되는 것입니다. 제조사도 마찬가지입니다. 여기서 단어는 전문 색인(full-text index)에서 온 것인데 단어(term)란 문서에 등장하는 모든 단어를 이야기합니다.&lt;/p&gt;
&lt;p&gt;인덱싱을 할 때 단어 자체를 사용할 수 있고 단어의 해시값을 사용할 수 있습니다. 차이점은 앞에서 살펴봤듯이 단어 자체로 파티셔닝하면 범위 스캔이 가능합니다. 반면에 해시값을 사용하면 부하가 좀 더 고르게 분산되는 장점을 갖게 됩니다.&lt;/p&gt;
&lt;p&gt;문서 기반 세컨더리 인덱스에 비해 전역 인덱스(단어 파티셔닝)이 갖는 이점은 읽기가 효율적이라는 것입니다. 용어 파티셔닝의 경우 모든 파티션에 scatter/gather를 실행할 필요 없이 원하는 단어를 포함하는 파티션으로만 요청을 보냅니다. 하지만 쓰기가 느리고 복잡하다는 단점을 갖습니다. 단일 문서를 쓸 때 문서에 있는 모든 단어가 다른 노드의 파티션에 속할 수 있기 때문입니다.&lt;/p&gt;
&lt;h3&gt;정리&lt;/h3&gt;
&lt;p&gt;이번 포스트에서는 두가지 주요 파티셔닝 기법인 범위와 해시 방식의 파티셔닝에 대해 알아보았습니다. 그리고 파티셔닝과 세컨더리 인덱스의 상호 작용에 관해 살펴보았습니다. 다음 포스트에서는 이어서 파티션 리밸런싱 기법과 라우팅 처리하는 방식에 관해 살펴보도록 하겠습니다.&lt;/p&gt;</description>
      <category>Big Data/Designing Data-Intensive Applicatiosn</category>
      <category>Index</category>
      <category>partitioning</category>
      <category>데이터 파티션</category>
      <category>보조색인</category>
      <category>색인</category>
      <category>파티셔닝</category>
      <category>파티션</category>
      <author>Data Engineer</author>
      <guid isPermaLink="true">https://seamless.tistory.com/97</guid>
      <comments>https://seamless.tistory.com/97#entry97comment</comments>
      <pubDate>Tue, 24 Nov 2020 23:00:36 +0900</pubDate>
    </item>
    <item>
      <title>프로그래밍과 알고리즘 공부 방법 - 김창준님</title>
      <link>https://seamless.tistory.com/96</link>
      <description>&lt;h3&gt;퍼온이의 생각&lt;/h3&gt;
&lt;p&gt;이 글은 월간 마소 2002년에 기고된 김창준 님의 &amp;quot;어떻게 공부할까: 프로그래머를 위한 공부론&amp;quot;이라는 기사 내용입니다. 해당 글을 읽고 나서 많은 생각을 하였고 개발자로 성장하는데 많은 길잡이가 된 글이었습니다. 그래서 이 좋을 글을 퍼와서 포스팅합니다.&lt;/p&gt;
&lt;h3&gt;프로그래밍과 알고리즘 공부 방법&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;우리 프로그래머들은 항상 공부해야 합니다. 우리는 지식을 중요하게 여깁니다. 하지만 지식에 대한 지식, 즉 내가 그 지식을 얻은 과정이나 방법 같은 것은 소홀히 여기기 쉽습니다. 따라서 지식의 축적과 공유는 있어도 방법론의 축적과 공유는 매우 드문 편입니다. 저는 평소에 이런 생각에서 학교 후배들을 위해 제 자신의 공부 경험을 짬짬이 글로 옮겨놓았고, 이번 기회에 그 글들을 취합, 정리하게 되었습니다. 그 결실이 바로 이 글입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이 글은 공부하는 방법과 과정에 관한 글입니다. 이 글은 제가 공부한 성공/실패 경험을 기본 토대로 했고, 지난 몇 년간 주변에서 저보다 먼저 공부한 사람들의 경험을 관찰, 분석한 것에 제가 다시 직접 실험한 것과 그밖에 오랫동안 꾸준히 모아온 자료들을 더했습니다. &amp;#39;만약 다시 공부한다면&amp;#39; 저는 이렇게 공부할 것입니다.&lt;/p&gt;
&lt;p&gt;부디 독자 제현께서 이 글을 씨앗으로 삼아 자신만의 나무를 키우고 거기서 열매를 얻고, 또 그 열매의 씨앗이 다시 누군가에게 전해질 수 있다면 더 이상 바랄 것이 없겠습니다.&lt;/p&gt;
&lt;p&gt;이 글은 특정 주제들의 학습/교수법에 대한 문제점과 제가 경험한 좋은 공부법을 소개하는 식으로 구성됐습니다. 여기에 선택된 과목은 리팩토링, 알고리즘·자료구조, 디자인패턴, 익스트림 프로그래밍(Extreme Programming 혹은 XP) 네 가지입니다.&lt;/p&gt;
&lt;p&gt;이 네 가지가 선택된 이유는 필자가 관심있게 공부했던 것이기 때문만은 아니고, 모든 프로그래머에게 어떻게든 널리 도움이 될만한 교양과목이라 생각하여 선택한 것입니다. 그런데 이 네 가지의 순서가 겉보기와는 달리 어떤 단계적 발전을 함의하는 것은 아닙니다. 수신(修身)이 끝나면 더 이상 수신은 하지 않고 제가(齊家)를 한다는 것이 어불성설인 것과 같습니다.&lt;/p&gt;
&lt;p&gt;원래는 글 후미에 일반론으로서의 공부 패턴들을 쓰려고 했습니다. 하지만 지면의 제약도 있고, 독자 스스로 이 글에서 그런 패턴을 추출하는 것도 의미가 있을 것이기에 생략했습니다. 그런 일반론이 여기 저기 숨어있기 때문에 알고리즘 공부에 나온 방법 대부분이 리팩토링 공부에도 적용할 수 있고, 적용되어야 한다는 점을 꼭 기억해 주셨으면 합니다. 다음에 기회가 닿는다면 제가 평소 사용하는 (컴퓨터) 공부패턴들을 소개하겠습니다.&lt;/p&gt;
&lt;h3&gt;알고리즘·자료구조 학습에서의 문제&lt;/h3&gt;
&lt;p&gt;우리는 알고리즘 카탈로그를 배웁니다. 이미 그러한 해법이 존재하고, 그것이 최고이며, 따라서 그것을 달달 외우고 이해해야 합니다. 좀 똑똑한 친구들은 종종 &amp;quot;이야 이거 정말 기가 막힌 해법이군!&amp;quot;하고 감탄할지도 모릅니다. 대부분의 나머지 학생들은 그 해법을 이해하려고 머리를 쥐어짜고 한참을 씨름한 후에야 어렴풋이 왜 이 해법이 그 문제를 해결하는지 납득하게 됩니다.&lt;/p&gt;
&lt;p&gt;그리고는 그 &amp;#39;증명&amp;#39;은 책 속에 덮어두고 까맣게 사라져버립니다. 앞으로는 그냥 &amp;#39;사용&amp;#39;하면 되는 것입니다. 더 많은 대다수의 학생은 이 과정이 무의미하다는 것을 알기 때문에 왜 이 해법이 이 문제를 문제없이 해결하는지의 증명은 간단히 건너뜁니다.&lt;/p&gt;
&lt;p&gt;이런 학생들은 이미 주어진 알고리즘을 사용하는 일종의 객관식 혹은 문제 출제자가 존재하는 시험장 상황에서는 뛰어난 성적을 보일 것임은 자명합니다. 하지만 스스로가 문제와 해답을 모두 만들어내야 하는 상황이라면, 또는 해답이 존재하지 않을 가능성이 있는 상황이라면, 혹은 최적해를 구하는 것이 불가능에 가깝다면, 혹은 알고리즘을 완전히 새로 고안해내야 하거나 기존 알고리즘을 변형해야 하는 상황이라면 어떨까요?&lt;/p&gt;
&lt;p&gt;교육은 물고기를 잡는 방법을 가르쳐야 합니다. 어떤 알고리즘을 배운다면 그 알고리즘을 고안해낸 사람이 어떤 사고 과정을 거쳐 그 해법에 도달했는지를 구경할 수 있어야 하고, 학생은 각자 스스로만의 해법을 차근차근 &amp;#39;구성&amp;#39;(construct)할 수 있어야 합니다(이를 교육철학에서 구성주의라고 합니다. 교육철학자 삐아제(Jean Piaget)의 제자이자, 마빈 민스키와 함께 MIT 미디어랩의 선구자인 세이머 페퍼트 박사가 주창했습니다). 전문가가 하는 것을 배우지 말고, 그들이 어떻게 전문가가 되었는지를 배우고 흉내 내야 합니다.&lt;/p&gt;
&lt;p&gt;결국은 소크라테스적인 대화법입니다. 해답을 가르쳐 주지 않으면서도 초등학교 학생이 자신이 가진 지식만으로 스스로 퀵소트를 유도할 수 있도록 옆에서 도와줄 수 있습니까? 이것이 우리 스스로와 교사들에게 물어야 할 질문입니다.&lt;/p&gt;
&lt;p&gt;왜 우리는 학교에서 &amp;#39;프로그래밍을 하는 과정&amp;#39;이나 &amp;#39;디자인 과정&amp;#39;(소프트웨어 공학에서 말하는 개발 프로세스가 아니라 몇 시간이나 몇 십 분 단위의, 개인적인 차원의 사고 과정 등을 일컫습니다)을 명시적으로 배운 적이 없을까요? 왜 해답에 이르는 과정을 가르쳐주는 사람이 없나요? 우리가 보는 것은 모조리 이미 훌륭히 완성된, 종적 상태의 결과물로서의 프로그램뿐입니다. 어느 날 문득 하늘에서 완성된 프로그램이 뚝 떨어지는 경우는 없는데 말입니다.&lt;/p&gt;
&lt;p&gt;교수가 어떤 알고리즘 문제의 해답을 가르칠 때, &amp;quot;교수님, 교수님께서는 어떤 사고 과정을 거쳐, 그리고 어떤 디자인 과정과 프로그래밍 과정을 거쳐서 그 프로그램을 만드셨습니까?&amp;quot;하고 물어봅시다. 만약 여기에 어떤 체계적인 답변도 할 수 없는 분이라면 그 분은 자신의 사고에 대해 &amp;#39;사고&amp;#39;해 본 적이 없거나 문제 해결에 어떤 효율적 체계를 갖추지 못한 분이며, 따라서 아직 남을 가르칠 준비가 되어있지 않은 분일 것입니다. 만약 정말 그렇다면 우리는 어떻게 해야 할까요?&lt;/p&gt;
&lt;h3&gt;자료구조와 알고리즘 공부&lt;/h3&gt;
&lt;p&gt;제가 생각건대, 교육적인 목적에서는 자료구조나 알고리즘을 처음 공부할 때 우선은 특정 언어로 구현된 것을 보지 않는 것이 좋을 때가 많습니다. 대신 말로 된 설명이나 의사코드(pseudo-code) 등으로 그 개념까지만 이해하는 것이죠. 그 아이디어를 절차형(C, 어셈블리어)이나 함수형(LISP, Scheme, Haskell), 객체지향(자바, 스몰토크) 언어 등으로 직접 구현해 보는 겁니다. 그 다음에는 다른 사람이나 다른 책의 코드와 비교합니다. 이 경험을 애초에 박탈당한 사람은 귀중한 배움과 깨달음의 기회를 잃은 셈입니다.&lt;/p&gt;
&lt;p&gt;만약 여러 사람이 함께 공부한다면 각자 동일한 아이디어를 같은 언어로 혹은 다른 언어로 어떻게 다르게 표현했는지를 서로 비교해 보면 배우는 것이 무척 많습니다.&lt;/p&gt;
&lt;p&gt;우리가 자료구조나 알고리즘을 공부하는 이유는, 특정 &amp;#39;실세계의 문제&amp;#39;를 어떠한 &amp;#39;수학적 아이디어&amp;#39;로 매핑시켜 해결할 수 있는지, 그것이 효율적인지, 또 이를 컴퓨터에 어떻게 효율적으로 구현할 수 있는지 따지고, 그것을 실제로 구현하기 위해서입니다. 따라서 이 과정에 있어 실세계의 문제를 수학 문제로, 그리고 수학적 개념을 프로그래밍 언어로 효율적으로 표현해내는 것은 아주 중요한 능력이 됩니다.&lt;/p&gt;
&lt;h4&gt;알고리즘 공부에서 중요한 것&lt;/h4&gt;
&lt;p&gt;개별 알고리즘의 목록을 이해, 암기하며 익히는 것도 중요하지만 더 중요한 것은 다음 네 가지입니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;알고리즘을 스스로 생각해낼 수 있는 능력&lt;/li&gt;
&lt;li&gt;다른 알고리즘과 효율을 비교할 수 있는 능력&lt;/li&gt;
&lt;li&gt;알고리즘을 컴퓨터와 다른 사람이 이해할 수 있는 언어로 표현해낼 수 있는 능력&lt;/li&gt;
&lt;li&gt;이것의 정상작동(correctness) 여부를 검증해 내는 능력&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;첫 번째가 제대로 훈련되지 못한 사람은 알고리즘 목록의 스테레오 타입에만 길들여져 있어서 모든 문제를 자신이 아는 알고리즘 목록에 끼워 맞추려고 합니다. 디자인패턴을 잘못 공부한 사람과 비슷합니다. 이런 사람들은 마치 과거에 수학 정석만 수십 번 공부해 문제를 하나 던져주기만 하면, 생각해보지도 않고 자신이 풀었던 문제들의 패턴 중 가장 비슷한 것 하나를 기계적·무의식적으로 풀어제끼는 문제 풀이 기계와 비슷합니다. 그들에게 도중에 물어보십시오. &amp;quot;너 지금 무슨 문제 풀고 있는 거니?&amp;quot; 열심히 연습장에 뭔가 풀어나가고는 있지만 그들은 자신이 뭘 풀고 있는지도 제대로 인식하지 못 하는 경우가 많습니다.&lt;/p&gt;
&lt;p&gt;머리가 푸는 게 아니고 손이 푸는 것이죠. 이렇게 되면 도구에 종속되는 &amp;#39;망치의 오류&amp;#39;에 빠지기 쉽습니다. 새로운 알고리즘을 고안해야 하는 상황에서도 기존 알고리즘에 계속 매달릴 뿐입니다. 알고리즘을 새로 고안해 내건 혹은 기존의 것을 조합하건 스스로 생각해 내는 훈련이 필요합니다.&lt;/p&gt;
&lt;p&gt;두 번째가 제대로 훈련되지 못한 사람은 일일이 구현해 보고 실험해 봐야만 알고리즘 간의 효율을 비교할 수 있습니다. 특히 자신이 가진 카탈로그를 벗어난 알고리즘을 만나면 이 문제가 생깁니다. 이건 상당한 대가를 치르게 합니다.&lt;/p&gt;
&lt;p&gt;세 번째가 제대로 훈련되지 못한 사람은, 문제를 보면 &amp;quot;아, 이건 이렇게 저렇게 해결하면 됩니다&amp;quot;하는 말은 곧잘 할 수 있지만 막상 컴퓨터 앞에 앉혀 놓으면 아무 것도 하지 못합니다. 심지어 자신이 생각해낸 그 구체적 알고리즘을 남에게 설명해 줄 수는 있지만, 그걸 &amp;#39;컴퓨터에게&amp;#39; 설명하는 데는 실패합니다. 뭔가 생각해낼 수 있다는 것과 그걸 컴퓨터가 이해할 수 있게 설명할 수 있다는 것은 다른 차원의 능력을 필요로 합니다.&lt;/p&gt;
&lt;p&gt;네 번째가 제대로 훈련되지 못한 사람은, 알고리즘을 특정 언어로 구현해도, 그것이 옳다는 확신을 할 수 없습니다. 임시변통(ad hoc)의 아슬아슬한 코드가 되거나 이것저것 덧붙인 누더기 코드가 되기 쉽습니다. 이걸 피하려면 두 가지 훈련이 필요합니다.&lt;/p&gt;
&lt;p&gt;하나는 수학적·논리학적 증명의 훈련이고, 다른 하나는 테스트 훈련입니다. 전자가 이론적이라면 후자는 실용적인 면이 강합니다. 양자는 상보적인 관계입니다. 특수한 경우들을 개별적으로 테스트해서는 검증해야 할 것이 너무 많고, 또 모든 경우에 대해 확신할 수 없습니다. 테스트가 버그의 부재를 보장할 수는 없습니다. 하지만 수학적 증명을 통하면 그것이 가능합니다. 또, 어떤 경우에는 수학적 증명을 굳이 할 필요 없이 단순히 테스트 케이스 몇 개만으로도 충분히 안정성이 보장되는 경우가 있습니다. 이럴 때는 그냥 테스트만으로 만족할 수 있습니다.&lt;/p&gt;
&lt;h4&gt;실질적이고 구체적인 문제를 함께 다루라&lt;/h4&gt;
&lt;p&gt;자료구조와 알고리즘 공부를 할 때에는 가능하면 실질적이고 구체적인 실세계의 문제를 함께 다루는 것이 큰 도움이 됩니다. 모든 학습에 있어 이는 똑같이 적용됩니다. 인류의 지성사를 봐도, 구상(concrete) 다음에 추상(abstract)이 옵니다. 인간 개체 하나의 성장을 봐도 그러합니다. &amp;#39;be-동사 더하기 to-부정사&amp;#39;가 예정으로 해석될 수 있다는 룰만 외우는 것보다 다양한 예문을 실제 문맥 속에서 여러 번 보는 것이 훨씬 나을 것은 자명합니다. 알고리즘과 자료구조를 공부할 때 여러 친구들과 함께 연습문제(특히 우리가 경험하는 실세계의 대상들과 관련이 있는 것)를 풀어보기도 하고, ACM의 ICPC(International Collegiate Programming Contest: 세계 대학생 프로그래밍 경진 대회) 등의 프로그래밍 경진 대회 문제 중 해당 알고리즘·자료구조가 사용될 수 있는 문제를 같이 풀어보는 것도 아주 좋습니다. 이게 가능하려면 &amp;quot;이 알고리즘이 쓰이는 문제는 이거다&amp;quot;하고 가이드를 해줄 사람이 있으면 좋겠죠. 이것은 그 구체적 알고리즘·자료구조를 훈련하는 것이고, 이와 동시에 어떤 알고리즘을 써야 할지 선택, 조합하는 것과 새로운 알고리즘을 만들어내는 훈련도 무척 중요합니다.&lt;/p&gt;
&lt;h4&gt;알고리즘 디자인 과정의 중요성&lt;/h4&gt;
&lt;p&gt;알고리즘을 좀더 수월하게, 또 잘 만들려면 알고리즘 디자인 과정에 대해 생각해 봐야 합니다. 그냥 밑도 끝도 없이 문제를 쳐다본다고 해서 알고리즘이 튀어나오진 않습니다. 체계적이고 효율적인 접근법을 사용해야 합니다. 대표적인 것으로 다익스트라(E. W. Dijkstra)와 워스(N. Wirth)의 &amp;#39;조금씩 개선하기&amp;#39;(Stepwise Refinement)가 있습니다. 워스의 「Program Development by Stepwise Refinement」(1971, CACM 14.4, &lt;a href=&quot;http://www.acm.org/classics/dec95&quot;&gt;http://www.acm.org/classics/dec95&lt;/a&gt;)를 꼭 읽어보길 바랍니다. 여기 소개된 조금씩 개선하기는 구조적 프로그래밍에서 핵심적 역할을 했습니다(구조적 프로그래밍을 &amp;#39;goto 문 제거&amp;#39; 정도로 생각하면 안 됩니다). 다익스트라의 「Stepwise Program Construction」 (Selected Writings on Computing: A Personal Perspective, Springer-Verlag, 1982, &lt;a href=&quot;http://www.cs.utexas.edu/users/EWD/ewd02xx/EWD227.PDF&quot;&gt;http://www.cs.utexas.edu/users/EWD/ewd02xx/EWD227.PDF&lt;/a&gt;) 추천합니다.&lt;/p&gt;
&lt;p&gt;알고리즘 검증은 알고리즘 디자인과 함께 갑니다. 새로운 알고리즘을 고안할 때 검증해 가면서 디자인하기 때문입니다. 물론 가장 큰 역할은 고안이 끝났을 때의 검증입니다. 알고리즘 검증에는 루프 불변식(loop invariant) 같은 것이 아주 유용합니다. 아주 막강한 무기입니다. 익혀 두면 두고두고 가치를 발휘할 것입니다. 맨버(Udi Manber)의 알고리즘 서적(『Introduction to Algorithms: A Creative Approach』)이 알고리즘 검증과 디자인이 함께 진행해 가는 예로 자주 추천됩니다. 많은 계발을 얻을 것입니다. 고전으로는 다익스트라의 『A Discipline of Programming』과 그라이스(Gries)의 『The Science of Programming』이 있습니다. 특히 전자를 추천합니다. 프로그래밍에 대한 관을 뒤흔들어 놓을 것입니다.&lt;/p&gt;
&lt;h4&gt;알고리즘과 패러다임&lt;/h4&gt;
&lt;p&gt;알고리즘을 공부하면 큰 줄기들을 알아야 합니다. 개별 테크닉도 중요하지만 &amp;#39;패러다임&amp;#39;이라고 할만한 것들을 알아야 합니다. 이것에 대해서는 튜링상을 수상한 로버트 플로이드(Robert Floyd)의 튜링상 수상 강연(The Paradigms of Programming, 1978)을 추천합니다. 패러다임을 알아야 알고리즘을 상황에 맞게 마음대로 변통할 수 있습니다. 그리고 자신만의 분류법을 만들어야 합니다. 구체적인 문제들을 케이스 바이 케이스로 여럿 접하는 동안 그냥 지나쳐 버리면 개별자는 영원히 개별자로 남을 뿐입니다. 비슷한 문제들을 서로 묶어서 일반화해야 합니다.&lt;/p&gt;
&lt;p&gt;이런 패러다임을 발견하려면 &amp;#39;다시 하기&amp;#39;가 아주 좋습니다. 다른 것들과 마찬가지로, 이 다시 하기는 알고리즘에서만이 아니고 모든 공부에 적용할 수 있습니다. 같은 것을 다시 해보는 것에서 우리는 얼마나 많은 것을 배울 수 있을까요. 대단히 많습니다. 왜 동일한 문제를 여러 번 풀고, 왜 같은 내용의 세미나에 또 다시 참석하고, 같은 프로그램을 거듭 작성할까요? 훨씬 더 많이 배울 수 있기 때문입니다. 화술 교육에서는 같은 주제에 대해 한 번 말해본 연사와 두 번 말해본 연사는 천지 차이가 있다고 말합니다. 같은 일에 대해 두 번의 기회가 주어지면 두 번째에는 첫 번째보다 잘 할 기회가 있습니다. 게다가 첫 번째 경험했던 것을 &amp;#39;터널을 벗어나서&amp;#39; 다소 객관적으로 볼 수 있게 됩니다. 왜 자신이 저번에 이걸 잘 못 했고, 저걸 잘 했는지 알게 되고, 어떻게 하면 그걸 더 잘할 수 있을는지 깨닫게 됩니다. 저는 똑같은 문제를 여러 번 풀더라도 매번 조금씩 다른 해답을 얻습니다. 그러면서 정말 엄청나게 많은 것을 배웁니다. &amp;#39;비슷한 문제&amp;#39;를 모두 풀 능력이 생깁니다.&lt;/p&gt;
&lt;p&gt;제가 개인적으로 존경하는 전산학자 로버트 플로이드(Robert W. Floyd)는 1978년도 튜링상 강연에서 다음과 같은 말을 합니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;제가 어려운 알고리즘을 디자인하는 경험을 생각해 볼 때, 제 능력을 넓히는 데 가장 도움이 되는 특정한 테크닉이 있습니다. 어려운 문제를 푼 후에, 저는 그것을 처음부터 완전히 새로 풉니다. 좀 전에 얻은 해법의 통찰(insight)만을 유지하면서 말이죠. 해법이 제가 희망하는 만큼 명료하고 직접적인 것이 될 때까지 반복합니다. 그런 다음, 비슷한 문제들을 공략할 어떤 일반적인 룰을 찾습니다. 아까 주어진 문제를 아예 처음부터 최고로 효율적인 방향에서 접근하도록 이끌어줬을 그런 룰을 찾는 거죠. 많은 경우에 그런 룰은 불변의 가치가 있습니다. … 포트란의 룰은 몇 시간 내에 배울 수 있습니다. 하지만 관련 패러다임은 훨씬 더 많은 시간이 걸립니다. 배우거나(learn) 배운 것을 잊거나(unlearn) 하는 데 모두.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;수학자와 프로그래머를 포함한 모든 문제 해결자들의 고전이 된 죠지 폴리야(George Polya)의 『How to Solve it』에는 이런 말이 있습니다:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;심지어는 꽤나 훌륭한 학생들도, 문제의 해법을 얻고 논증을 깨끗하게 적은 다음에는 책을 덮어버리고 뭔가 다른 것을 찾는다. 그렇게 하는 동안 그들은 그 노력의 중요하고도 교육적인 측면을 잃어버리게 된다. … 훌륭한 선생은 어떠한 문제이건 간에 완전히 바닥이 드러나는 경우는 없다는 관점을 스스로 이해하고 또 학생들에게 명심시켜야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;저는 ACM의 ICPC 문제 중에 어떤 문제를 이제까지 열 번도 넘게 풀었습니다. 대부분 짝 프로그래밍이나 세미나를 통해 프로그래밍 시연을 했던 것인데, 제 세미나에 여러 번 참석한 분이 농담조로 웃으며 물었습니다. &amp;quot;신기해요. 창준 씨는 그 문제를 풀 때마다 다른 프로그램을 짜는 것 같아요. 설마 준비를 안 해 와서 그냥 내키는 대로 하는 건 아니죠?&amp;quot; 저는 카오스 시스템과 비슷하게 초기치 민감도가 프로그래밍에도 작용하는 것 같다고 대답했습니다. 저 스스로 다른 해법을 시도하고 싶은 마음이 있으면 출발이 조금 다르고, 또 거기서 나오는 진행 방향도 다르게 됩니다. 그런데 중요한 것은 이렇게 같은 문제를 매번 다르게 풀면서 배우는 것이 엄청나게 많다는 점입니다. 저는 매번, 전보다 개선할 것을 찾아내게 되고, 또 새로운 것을 배웁니다. 마치 마르지 않는 샘물처럼 계속 생각할 거리를 준다는 점이 참 놀랍습니다.&lt;/p&gt;
&lt;p&gt;알고리즘 개론 교재로는 CLR(Introduction to Algorithms, Thomas H. Cormen, Charles E. Leiserson, and Ronald L. Rivest)을 추천합니다. 이와 함께 혹은 이 책을 읽기 전에 존 벤틀리(Jon Bentley)의 『Programming Pearls』도 강력 추천합니다. 세계적인 짱짱한 프로그래머와 전산학자들이 함께 꼽은 위대한 책 목록에서 몇 손가락 안에 드는 책입니다. 아직 이 책을 본 적이 없는 사람은 축하합니다. 아마 몇 주간은 감동 속에 하루하루를 보내게 될 겁니다.&lt;/p&gt;
&lt;h3&gt;리팩토링 학습에서의 문제&lt;/h3&gt;
&lt;p&gt;먼저, 본지 2001년 11월호에 제가 썼던 마틴 파울러의 책을 추천하는 글을 인용하겠습니다.&lt;/p&gt;
&lt;p&gt;OOP를 하건 안 하건 프로그래밍이란 업을 하는 사람이라면 이 책은 자신의 공력을 서너 단계 레벨업시켜줄 수 있다. 자질구레한 술기를 익히는 것이 아니고 기감과 내공을 증강하는 것이다.&lt;/p&gt;
&lt;p&gt;혹자는 DP(Design pattern) 이전에 RF(Refactoring)를 봐야 한다고도 한다. 이 말이 어느 정도 일리가 있는 것이, 효과적인 학습은 문제의식이 선행해야 하기 때문이다. DP는 거시적 차원에서 해결안을 모아놓은 것이다. RF를 보고 나쁜 냄새(Bad Smell)를 맡을 수 있는 후각을 발달시켜야 한다. RF의 목록을 모두 외우는 것은 큰 의미가 없다. 그것보다 냄새나는 코드를 느낄 수 있는 감수성을 키우는 것이 더 중요하다. 필자는 일주일에 한 가지씩 나쁜 냄새를 정해놓고 그 기간 동안에는 자신이 접하는 모든 코드에서 그 냄새만이라도 확실히 맡도록 집중하는 방법을 권한다. 일명 일취집중후각법. 패턴 개념을 만든 건축가 크리스토퍼 알렉산더나 GoF의 랄프 존슨은 좋은 디자인이란 나쁜 것이 없는 상태라고 한다. 무색 무미 무취의 무위(無爲)적 자연(自然) 코드가 되는 그 날을 위해 오늘도 우리는 리팩토링이라는 유위(有爲)를 익힌다.&lt;/p&gt;
&lt;p&gt;주변에서 리팩토링을 잘못 공부하는 경우를 종종 접합니다. 어떤 의미에서 잘못 공부한다고 할까요? &amp;#39;실체화&amp;#39;가 문제입니다. 리팩토링은 도구이고 방편일 뿐인데, 그것에 매달리는 것은 달은 보지 않고 손을 보는 것과 같습니다. 저는 리팩토링 책이 또 하나의 (이미 그 병폐가 많이 드러난) GoF 책이 되는 현상이 매우 걱정됩니다.&lt;/p&gt;
&lt;h4&gt;리팩토링 공부&lt;/h4&gt;
&lt;p&gt;사람들이 일반적으로 생각하는 바와는 달리 리팩토링 학습에 있어 어떤 리팩토링이 있는지, 구체적으로 무엇인지 등의 리팩토링 목록에 대한 지식과 각각에 대한 메카닉스(Mechanics: 해당 리팩토링을 해나가는 구체적 단계)는 오히려 덜 중요할 수 있습니다. 더 기본적이고 유용한 것은 코드 냄새(Code Smell)와 짧은 테스트-코드 싸이클입니다. 이것만 제대로 되면 리팩토링 책의 모든 리팩토링을 스스로 구성해낼 수 있으며, 다른 종류의 리팩토링까지 직접 발견해낼 수 있습니다.&lt;/p&gt;
&lt;p&gt;그 책에서는 테스트의 중요성이 충분히 강조되지 않았습니다. 하지만 테스트 코드 없는 리팩토링은 안전벨트 없는 자동차 경주와 같습니다. 그리고 테스트 코드가 리팩토링의 방향을 결정하기도 합니다. 양자는 음과 양처럼 서로 엮여 있습니다. 이런 의미에서 리팩토링은 TDD(Test Driven Development)와 함께 수련하는 것이 좋습니다. 훨씬 더 빨리, 훨씬 더 많은 것을 배울 수 있을 겁니다.&lt;/p&gt;
&lt;p&gt;리팩토링을 공부할 때는 우선 코드 냄새의 종류를 알고, 왜 그것이 나쁜 냄새가 될 수 있는지 이해하고(그게 불가하다면 리팩토링 공부를 미뤄야 합니다) 거기에 동의할 수 있어야 합니다. 그런 다음, 대충 어떤 종류의 리팩토링이 가능한지 죽 훑어봅니다. 그 중 몇 개는 메카닉스를 따라가면서 실험해 봅니다. 이제는 책을 덮습니다. 그리고 실제 코드를 접하고, 만약 거기에서 냄새를 맡는다면 이 냄새를 없애기 위해 어떻게 해야 할지 스스로 고민합니다. 리팩토링 책의 목록은 일단 무시하십시오. 그 냄새를 없애는 것이 목표이지, 어떤 리팩토링을 여기에 &amp;#39;써먹는 것&amp;#39;이 목표가 되어선 안 됩니다. 이 때, 반드시 테스트 코드가 있어야 합니다. 그래야 &amp;#39;좋은&amp;#39; 리팩토링을 할 수 있습니다. 책을 떠나 있으면서도 책에서 떠나지 않는 방법입니다.&lt;/p&gt;
&lt;p&gt;리팩토링을 하기 전에 초록색 불(테스트가 모두 통과)이 들어온 시점에서 코드를 일부 고치면 빨간 불(테스트가 하나라도 실패)로 바뀔 겁니다. 이게 다시 초록색 불이 될 때까지 최소한도의 시간이 걸리도록 하십시오. 현 초록색에서 다음 초록색까지 최소한의 시간을 소비하도록 하면서 코드와 테스팅을 오가게 되면 자기도 모르는 사이에 훌륭한 리팩토링이 자발공으로 터져 나옵니다. 여기서 목적지는 우선은 OAOO(Once And Only Once: 모든 것은 오로지 한 번만 말해야 한다)를 지키는 쪽으로 합니다. 그러면 OAOO와 짧은 테스트-코드 싸이클을 지키는 사이 어느새 탁월한 디자인이 튀어나옵니다. 참고로 저는 &amp;#39;모래시계 프로그래밍&amp;#39;이란 걸 가끔 합니다. 모래시계나 알람 프로그램으로 테스트-코드 사이클의 시간을 재는 것입니다. 그래서 가급적이면 한 사이클이 3분 이내(대부분의 모래시계는 단위가 3분입니다)에 끝나도록 노력합니다. 여기서 성공을 하건 실패를 하건 많은 걸 얻습니다.&lt;/p&gt;
&lt;h4&gt;리팩토링 수련법&lt;/h4&gt;
&lt;p&gt;제가 고안, 사용한 몇 가지 리팩토링 수련법을 알려드립니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;일취집중후각법: 앞에 소개한 본지 2001년 11월호에서 인용된 글 참조&lt;/li&gt;
&lt;li&gt;주석 최소화: 주석을 최소화하되 코드의 가독성이 떨어지지 않도록(혹은 오히려 올라가도록) 노력합니다. 이렇게 하면 자동으로 리팩토링이 이뤄지는 경우가 많습니다.&lt;/li&gt;
&lt;li&gt;OAOO 따르기: OAOO 법칙을 가능하면 최대한, 엄격하게 따르려고 합니다. 역시 자동으로 좋은 리팩토링이 이뤄집니다. 여기서 디자인패턴이 창발하기도 합니다. GoF 책을 한번도 보지 못한 사람이 디자인패턴을 자유자재로 부리는 경우를 보게 됩니다.&lt;/li&gt;
&lt;li&gt;디미터 법칙(Law of Demeter) 따르기: 디미터 법칙을 가능하면 지키려고 합니다. 어떤 리팩토링이 저절로 이뤄지거나 혹은 필요 없어지는가요?&lt;/li&gt;
&lt;li&gt;짝(Pair) 리팩토링: 함께 리팩토링합니다. 혼자 하는 것보다 더 빨리, 더 많은 걸 배우게 됩니다. 특히, 각자 작성했던 코드를 함께 리팩토링하고, 제3자의 코드를 함께 리팩토링해 봅니다. 사람이 많다면 다른 짝이 리팩토링한 것과 서로 비교하고 토론합니다.&lt;/li&gt;
&lt;li&gt;&amp;#39;무엇&amp;#39;과 &amp;#39;어떻게&amp;#39;를 분리: 어떻게에서 무엇을 분리해 내도록 합니다. 어떤 리팩토링이 창발합니까?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;여기서 번, 짝 리팩토링은 아주 효과적인 방법입니다. 저는 이것을 협동적 학습(Collaborative Learning)이라고 부릅니다. 상대가 나보다 더 많이 아는 경우만이 아니고, 서로 아는 것이 비슷해도 많은 양의 학습이 발생합니다. 특히, 전문가와 함께 짝 프로그래밍을 하면 무서울 만큼 빠른 학습을 경험할 수 있습니다. 저와 짝 프로그래밍을 한 사람이 학습하는 속도에서 경이감을 느낀 적이 한두 번이 아닙니다. 문화는 사회적으로 학습되는 것입니다. 우리가 지식이라고 하는 것의 상당 부분은 문화처럼 암묵적인 지식(Tacit Knowledge)입니다. 전문가가 문제가 생겼을 때 어떻게 사고하고, 어떤 과정을 거쳐 접근하며, 어떻게 디버깅하고, 키보드를 어떤 식으로 누르는지, 사고 도구로 무엇을 사용하는지, 일 계획과 관리를 어떻게 하는지, 동료와 어떻게 대화하는지 등은 성문화되어 있지 않습니다. 그러나 이런 것들은 아주 중요합니다. 프로페셔널의 하루 일과의 대부분을 이루고 있기 때문입니다. 이런 것들은 전문가 바로 옆에서 조금씩 일을 도와주면서 배워야 합니다. 도제 살이(Apprenticeship)입니다. 진정한 전문가는 모든 동작이 우아합니다. 마치 춤을 추는 것 같습니다. 이 모습을 바로 곁에서 지켜보게 되면, 주니어는 한마디로 엄청난 충격을 받습니다. 그리고 스펀지처럼 빨아들이게 됩니다. 도대체 이 경험을 책이나 공장화한 학교가 어떻게 대신하겠습니까. 이와 관련해서는 레이브와 웽거(Jean Lave, Etienne Wenger)의 『Situated Learning : Legitimate Peripheral Participation』을 강력 추천합니다. 이 글을 보는 모든 교육 종사자들이 꼭 읽어봤으면 합니다. 이 협동적 학습은 두 사람만이 아니고 그룹 단위로도 가능합니다. 스터디에 이용하면 재미와 유익함 일석이조를 얻습니다.&lt;/p&gt;
&lt;p&gt;이 외에, 특히(어쩌면 가장) 중요한 것은 일취집중후각법 등을 이용해 자신의 코드 후각의 민감도를 높이는 것입니다. 코드 후각의 메타포 말고도 유사한 메타포가 몇 가지 더 있습니다. 켄트 벡은 코드의 소리를 들으라고 하고, 저는 코드를 향해 대화하라고 합니다. 코드의 소리를 듣는 것은 코드가 원하는 것에 귀를 기울이는 것을 말합니다. 코드는 단순해지려는 욕망이 있습니다. 그걸 이뤄주는 것이 프로그래머입니다. 그리고 짝 프로그래밍을 할 때 두 사람의 대화를 코드에 남기도록 합니다. 주석이 아닙니다. 왜 이것이 중요한가는 본지 2001년 12월호 「허실문답 XP 강화」를 참고하기 바랍니다&lt;/p&gt;
&lt;p&gt;기학으로 우리 사상사에 큰 획을 그은 철학자요, &amp;#39;서울서 책만 사다 망한 사람&amp;#39;으로 이름을 날릴 정도로 엄청난 지식욕을 과시하던 조선시대 사상가 혜강 최한기는 그의 저술 『신기통』(神氣通)에서 &amp;#39;눈에 통하는 법(目通), 귀에 통하는 법(耳通), 코에 통하는 법(鼻通)&amp;#39; 등을 이야기하고 있습니다. 어떻게 하면 우리는 코드에 도통할 수 있을까요? 리팩토링을 공부하거나 혹은 했던 사람들에게 많은 영감과 메타포를 주는 책으로 일독을 권합니다. 필자가 기회가 닿는다면 프로그래밍을 혜강의 사상적 측면에서 조망한 글을 써보고 싶습니다.&lt;/p&gt;
&lt;p&gt;앞서의 것들이 어느 정도 이뤄지고, 리팩토링에 대한 감이 오게 되면 그 때 비로소 리팩토링 책을 하나 하나 파헤치고 또 거기서 제대로 된 비판을 할 수 있게 됩니다.&lt;/p&gt;
&lt;h3&gt;디자인패턴 학습에서의 문제&lt;/h3&gt;
&lt;p&gt;잡지에 연재되거나 서적으로 출간된 혹은 세미나에서 진행되었던 디자인패턴 &amp;#39;강의&amp;#39;를 몇 가지 접했습니다. 훌륭한 강의도 많았지만 그렇지 못한 것도 있었습니다. 몇 가지 문제점을 지적하겠습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;패턴을 지나치게 실체화, 정형화해 설명한다.&lt;/li&gt;
&lt;li&gt;컨텍스트와 문제 상황에 대한 설명이 없거나 부족하다. 결과적으로 문제를 해결하기 위해 패턴이 도입된 것이 아니라 패턴을 써먹기 위해 패턴이 도입된 느낌을 준다.&lt;/li&gt;
&lt;li&gt;문제의식을 먼저 형성하게 하지 않고 답을 먼저 보여준 뒤 그걸 어디에 써먹을지 가르친다. 왜 이걸 쓰는 게 좋은지는 일언반구 언급이 없다. 독자는 &amp;#39;어린아이가 망치를 들고 있는 오류&amp;#39;에 빠질 것이다.&lt;/li&gt;
&lt;li&gt;패턴이 어떻게 생성되었는지 그 과정을 보여주지 못한다. 즉, 스스로 패턴을 만들어내는 데 도움이 전혀 되지 않는다.&lt;/li&gt;
&lt;li&gt;해당 패턴이 현실적으로 가장 자주 쓰이는 맥락을 보여주지 못한다. 대부분 장난감 문제(Toy Problem)에서 끝난다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그런 패턴 강의를 하는 분들이 알렉산더(Christopher Alexander, 패턴언어 창시자)의 저작을 충실히 읽어봤다면 이런 병폐는 없을 것이라 생각합니다. 알렉산더의 저작을 접해보지 못 하고서 패턴을 가르치는 사람은 성경을 읽어보지 않은 전도사와 같을 것입니다. 알렉산더가 『The Timeless Way of Building』의 마지막에서 무슨 말을 하는가요?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;이 마지막 단계에는 더 이상 패턴은 중요하지 않다. … 패턴은 당신이 현실적인 것에 대해 수용적이 되는 것을 가르쳐줬다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;패턴 역시 도구요, 방편일 뿐입니다. 패턴은 현실적인 것에 대해 수용적이 되도록 가르친다는 말은 결국 우리가 궁극적으로 추구하는 것은 패턴이 아니라 현실이어야 한다는 이야기입니다. 물론 처음 단계에는 교육적인 목적에서, 어느 정도 패턴에 얽매여도 괜찮다고는 해도, 나중에 패턴을 잊고 패턴에서 자유로워지려면 처음부터 패턴에 대해 도구적·방편적인 인식을 가져야 합니다.&lt;/p&gt;
&lt;p&gt;미국 캘리포니아 주립대학의 교수 베티 에드워즈(Betty Edwards)가 쓴 책 중에 『Drawing on the Right Side of the Brain』이라는 유명한 베스트셀러가 있습니다. 사람의 뇌와 그림 그리기의 관계에 대한 탁월한 책입니다. 에드워즈는 자신의 그리기 실력을 향상시키기 위해 우뇌를 적극적으로 사용하는 구체적인 방법들을 가르쳐줍니다. 그 중 대표적인 것 하나가 대상을 뒤집어 놓고 그리는 것입니다. 지금 실험해 보길 바랍니다. 1000원권 지폐를 바로 놓고 그걸 비슷하게 그려보고, 이번에는 그걸 위아래가 거꾸로 되게 놓고 따라 그려보십시오. 아마 무척 놀랐을 겁니다. &amp;quot;아니 내가 이렇게 그림을 잘 그리다니! 그것도 거꾸로!&amp;quot; 그렇습니다. 우리는 자신의 머리 속 패턴에 얽매여 세상을 제대로 보지 못 할 때가 많습니다. 실체가 코에 약간이라도 비슷하게 보이면 우리는 그것을 이미 우리 머리 속에 추상적으로 갖고 있던 기하학적 &amp;#39;코&amp;#39;의 패턴으로 대체해버리는 것입니다.&lt;/p&gt;
&lt;h3&gt;디자인 패턴 공부&lt;/h3&gt;
&lt;p&gt;우선은 제 교육철학과 언어교습론, 그리고 공부론 이야기를 잠깐 하겠습니다.&lt;/p&gt;
&lt;p&gt;기본적으로 교육은 교육자가 피교육자가에게 지식을 그대로 전달하는 행위가 아닙니다. 진정한 교육은 피교육자의 개인적 체험에 기반한 전폭적 동의에서 출발합니다. 저는 이를 동의에 의한 교육이라고 합니다.&lt;/p&gt;
&lt;p&gt;제가 &amp;quot;주석문을 가능하면 쓰지 않는 것이 더 좋다&amp;quot;는 이야기를 했을 때 이 문장을 하나의 사실로 받아들이고 기억하면 당장 그 시점에는 학습이 일어나지 않는다고 봅니다. 대신 여러분이 차후에 여러 가지 경험을 하면서도 이 화두를 놓치지 않고 있다가 어느 순간엔가, &amp;quot;아! 그래, 주석문을 쓰지 않는 게 좋겠구나!&amp;quot;하고 자각하는 순간, 바로 그 시점에 학습과 교육이 이뤄지는 것입니다. 이는 기본적으로 삐아제와 비갓스키(Lev Vygotsky)의 구성주의를 따르는 것이죠. 지식이란 외부에서 입력받는 것이 아니고, 그것에 대한 모델을 학습자 스스로가 내부에서 구축할 때 획득할 수 있는 것이라는 사상이죠.&lt;/p&gt;
&lt;p&gt;권법에서 주먹에 대해 달통한 도사가 &amp;#39;권을 내지르는 법&amp;#39;에 대한 규칙들을 정리해서 애제자의 머리 속에 아무리 쑤셔 넣는 데 성공한들 그 제자가 도사만큼 주먹이 나갈리는 만무합니다. 권을 내지르는 법을 유추해 내기까지 그 스승이 겪은 과정을 제자는 완전히 쏙 빼먹고 있기 때문입니다. 소위 &amp;#39;몸&amp;#39;이 만들어지지 않은 것이지요. 제자는 마당 쓸기부터, 물 긷기 등의 수련 과정을 겪어야만 하고 스승이 정리한 그 일련의 규칙에 손뼉을 치고 춤을 추며 기쁨의 동의를 할 수 있을 정도로 수련 과정이 축적된 이후에야 비로소 진정한 &amp;#39;가르침&amp;#39;이 이뤄지는 것이며, 청출어람의 가능성도 생각해 볼 수 있는 것입니다.&lt;/p&gt;
&lt;p&gt;이런 동의라는 것은 학습자 자신만의 컨텍스트와 문제의식을 바탕으로 한 것입니다. 우리는 많은 경우, 어떤 지식과 동시에 그 지식의 필요성까지도 지식화해서 외부에서 주입을 받습니다. 하지만 진정 체화된 지식을 위해서는 스스로가 이미 문제의식을 갖고 있어야 합니다.&lt;/p&gt;
&lt;p&gt;패턴도 마찬가지인데, 대부분 그 패턴의 필요성을 체감하지 못한 채 그냥 도식적 구조를 외우기에만 주력하는 사람이 많습니다만, 사실 그렇게 되면 어떤 경우에 이 패턴이 필요하고 어떤 경우에는 사용하면 안 되는지(GoF는 패턴을 정말 안다는 것은 그 패턴을 쓰면 안 될 때를 아는 것이라 했습니다) 등을 알기 힘듭니다. 설령 책에 나온 가이드를 암기했더라도요. 자신의 삶 속에서 문제의식이 구체적으로 실제 경험으로 형성되지 않았기 때문입니다.&lt;/p&gt;
&lt;p&gt;GoF 중 한 명인 랄프 존슨(Ralph Johnson)은 다음과 같이 말합니다.&lt;/p&gt;
&lt;p&gt;우리[GoF]는 책에서, 정말 그 패턴들이 필요하다는 것을 알만큼 충분한 경험을 갖기 전에는 그것을 [시스템 속에] 집어넣는 것을 피해야 한다고 말할 만큼 대담하진 못했다. 하지만 우리 모두는 그 사실을 믿었다. 패턴은 프로그램의 초기 버전이 아니고 프로그램 생애의 훨씬 나중에 가서야 비로소 등장해야 한다고 나는 늘 생각해 왔다.&lt;/p&gt;
&lt;p&gt;결국은 어떤 패턴의 필요성을 자신의 경험 속에서 절감하지 못한다면 그 패턴을 제대로 아는 것이 아니라고 말할 수 있을 겁니다. 따라서 패턴 하나를 공부할 때는 가능한 한 실제 예를 많이 접해야 합니다. 그리고 패턴을 적용하지 않은 경우에서 그 필요를 느끼고 설명할 수 있게끔 다양한 코드를 접해야 합니다.&lt;/p&gt;
&lt;h3&gt;소프트웨어 개발에 푹 빠지기&lt;/h3&gt;
&lt;p&gt;패턴 중에 보면 서로 비슷비슷한 것이 상당히 많습니다. 그 구조로는 완전히 동일한 것도 있죠. 초보자를 괴롭히는 것 중 하나입니다. 이것은 외국어를 공부할 때 문법 중심적인 학습을 하면서 부딪치는 문제와 비슷합니다. &amp;#39;주어+동사+목적어&amp;#39;라는 구조로는 동일한 두 개의 문장, 즉 &amp;#39;I love you&amp;#39;와 &amp;#39;I hate you&amp;#39;가 구조적으로는 동일할지라도 의미론적으로는 완전히 반대가 될 수 있는 겁니다. 패턴을 공부할 때는 그 구조보다 의미와 의도를 우선해야 하며, 이는 다양한 실례를 케이스 바이 케이스로 접하면서 추론화 및 자신만의 모델화라는 작업을 통해 하는 것이 최선입니다. 스스로 문법을 발견하고 체득하는 것이라고 할까요.&lt;/p&gt;
&lt;p&gt;DP는 사전과 같습니다. 이 책은 순서대로 소설 읽듯이 읽어나가라고 집필된 것이 아니고, 일종의 패턴 레퍼런스로 쓰인 것입니다. 역시 GoF의 한 명인 존 블리스사이즈(John Vlissides)는 다음과 같이 말합니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;여기서 내가 강조하고 싶은 것은 디자인패턴, 즉 GoF 책을 어떻게 읽느냐는 것이다. 많은 사람들은 그 내용을 온전히 이해하기 위해서는 순서대로 읽어야 한다고 느낀다. 하지만 GoF는 소설이 아니라 레퍼런스 북이다. 독일어를 배우기 위해 독영 사전의 처음부터 끝까지 하나하나 읽으려고 하는 경우를 생각해 보라. 그렇게는 결코 배울 수 없을 것이다! 독일어를 마스터하기 위해서는 독일어 문화에 자기 자신을 푹 담궈야(immerse) 한다. 독일어로 살아야 하는 것이다. 디자인패턴도 똑같다. 그걸 마스터하기 이전에 소프트웨어 개발에 자신을 푹 담궈야 한다. 패턴으로 살아야 하는 것이다.&lt;/p&gt;
&lt;p&gt;만약 꼭 그래야 한다면 소설 읽듯이 디자인패턴 책을 읽어라. 하지만 거의 아무도 그 방식으로 유창해지지 못한다. 소프트웨어 개발 프로젝트의 열기 속에서 패턴이 동작하게 하라. 실제 디자인 문제를 직면했을 때 그 패턴들의 통찰을 이용하라. 이것이 GoF 패턴들을 자신의 것으로 만드는 가장 효율적인 방법이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;어떤 지식을 체화하기 위해선 그 지식으로 살아야 한다는 말을 확인할 수 있습니다. 영어를 배우려면 영어로 살고, DP를 배우려면 DP로 살라는 단순하면서도 아주 강력한 말입니다.&lt;/p&gt;
&lt;p&gt;어떤 특정 문장 구조를 학습하는 데 최선은 그 문장 구조를 이용한 실제 문장을 나에게 의미 있는 실제 컨텍스트 속에서 많이 접하고 스스로 나름의 모델을 구축하여 교과서의 법칙에 &amp;#39;기쁨에 찬 동의&amp;#39;를 하는 것입니다.&lt;/p&gt;
&lt;p&gt;주변에서 특정 패턴이 구현된 코드를 구하기가 힘들다면 이 패턴을 자신이 만지고 있는 코드에 적용해 보려고 노력해 볼 수 있습니다. 이렇게 해보고 저렇게도 해보고, 그러다가 오히려 복잡도만 증가하면 &amp;quot;아 이 경우에는 이 패턴을 쓰면 안 되겠구나&amp;quot;하는 걸 학습할 수도 있죠. GoF는 패턴을 배울 때는 한결 같이 &amp;quot;이 패턴이 적합한 상황과 동시에 이 패턴이 악용, 오용될 수 있는 상황&amp;quot;을 함께 공부하라고 합니다.&lt;/p&gt;
&lt;p&gt;이런 식의 &amp;#39;사례 중심&amp;#39;의 공부를 위해서는 스터디 그룹을 조직하는 것이 좋습니다. 혼자 공부를 하건, 그룹으로 하건 커리프스키(Joshua Kerievsky)의 「A Learning Guide To Design Patterns」(&lt;a href=&quot;http://www.industriallogic.com/papers/learning.html&quot;&gt;http://www.industriallogic.com/papers/learning.html&lt;/a&gt;)를 참고하세요. 그리고 스터디 그룹을 효과적으로 꾸려 나가는 데는 스터디 그룹의 패턴 언어를 서술한 「Knowledge Hydrant」(&lt;a href=&quot;http://www.industriallogic.com/papers/khdraft.pdf&quot;&gt;http://www.industriallogic.com/papers/khdraft.pdf&lt;/a&gt;)를 참고하면 많은 도움이 될 겁니다. 이 문서는 뭐든지 간에 그룹 스터디를 한다면 적용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;LG2DP(「A Learning Guide To Design Patterns」) 뒷부분에 보면 DP를 공부하는 순서와 각 패턴에서 던질만한 질문이 같이 정리되어 있습니다. DP는 순차적으로 공부해야만 하는 것은 아닙니다. 효과적인 공부의 순서가 있습니다. sorry라는 단어를 모르면서 remorseful이라는 단어를 공부하는 학생을 연상해 보세요. 외국어 공부에서는 자기 몸에 가까운 쉬운 단어부터 공략하는 것이 필수적입니다. 이런 걸 근접 학습(Proximal Learning)이라고도 하죠. 등급별 어휘 목록 같은 게 있으면 좋죠. LG2DP에서 제안하는 순서가 그런 것 중 하나입니다.&lt;/p&gt;
&lt;p&gt;랄프 존슨은 이런 순서의 중요성에 관해 다음과 같은 말을 했습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;… 하지만 나는 언제나 싱글톤 패턴을 가르치기 전에 콤포짓, 스트래터지, 템플릿 메쏘드, 팩토리 메쏘드 패턴을 가르친다. 이것이 훨씬 더 일반적인 것들이며, 대다수의 사람들은 아마도 이것들 중 마지막 두 가지를 이미 사용하고 있을 것이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;제대로 된 패턴 구현을 위한 다양한 접근 시도하기&lt;/h3&gt;
&lt;p&gt;우리의 지식이라는 것은 한 가지 표현양상(representation)으로만 이뤄져 있지 않습니다. 사과라는 대상을 음식으로도, 그림의 대상으로도 이해할 수 있어야 합니다. 실제 패턴이 적용된 &amp;#39;다양한 경우&amp;#39;를 접하도록 하라는 것이 이런 겁니다. 동일 대상에 대한 다양한 접근을 시도하라는 것이죠. 자바로 구현된 코드도 보고, C++로 된 것도 보고, 스몰토크로 된 것도 봐야 합니다. 설령 &amp;#39;오로지 자바족&amp;#39;이라고 할지라도요(전 이런 사람들을 자바리안(Javarian)이라고 부릅니다. 자바와 바바리안(barbarian)을 합성해서 만든 조어지요). 이런 &amp;#39;오로지 하나만 공부하는 것&amp;#39;의 병폐에 대해서는 존 블리스사이즈가 쓴 「Diversify」(&lt;a href=&quot;http://www.research.ibm.com/people/v/vlis/pubs/gurus-99.pdf&quot;&gt;http://www.research.ibm.com/people/v/vlis/pubs/gurus-99.pdf&lt;/a&gt;)라는 글을 읽어보세요. 이렇게 다양화를 해야 비로소 자바로도 &amp;#39;상황에 맞는&amp;#39; 제대로 된 패턴을 구현할 수 있습니다. 패턴은 그 구현(implementation)보다 의도(intent)가 더 중요하다는 사실을 꼭 잊지 말고, 설명을 위한 방편으로 채용된 한 가지 도식에 자신의 사고를 구속하는 우를 범하지 않기를 빕니다.&lt;/p&gt;
&lt;p&gt;이런 맥락에서 저는 DP를 공부할 때 GoF와 동시에 『Design Patterns Smalltalk Companion』을 필수적으로 읽기를 권합니다. 두 권은 말하자면 양날개입니다. 하나는 정적언어로 구현되었고(간간이 스몰토크 구현이 있긴 합니다), 다른 하나는 동적언어로 구현되어 있습니다. 언어와 패턴의 고리를 느슨하게 하고, 패턴을 여러 관점에서 신선하게 볼 수 있는 계기가 될 것입니다. 또, 한 쪽을 보고 이해가 잘 되지 않을 때 다른 쪽을 보면 쉽게 이해됩니다. 서로 상보적인 것이죠.&lt;/p&gt;
&lt;p&gt;패턴도 결국 &amp;#39;문제해결&amp;#39;을 위한 한 가지 방편에 지나지 않습니다. 주변에서 &amp;quot;이 경우에는 무조건 이 패턴을 써야 합니다&amp;quot;하고 생떼를 쓰는 사람을 보면 씁쓸한 기분을 감출 수 없습니다.&lt;/p&gt;
&lt;h3&gt;디자인패턴 추천서&lt;/h3&gt;
&lt;p&gt;디자인패턴 책 중에 중요한 서적을 몇 권 소개하겠습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;『Design Patterns Explained』(Shalloway, Trott): 최근 DP 입문서로 급부상하고 있는 명저&lt;/li&gt;
&lt;li&gt;『Design Patterns Java Workbook』(Steven John Metsker): DPE 다음으로 볼만한 책으로 쏟아져 나오는 시중의 조악한 자바 패턴 책들과는 엄연히 다르다. 워크북 형식을 채용해서 연습문제를 풀고 뒷부분의 답과 대조해 볼 수 있는 등 독학자가 공부하기에 좋다.&lt;/li&gt;
&lt;li&gt;『Refactoring』(Martin Fowler): DP 공부 이전에 봐서 문제의식 형성하기(망치를 들면 모든 것이 못으로 보이는 오류 탈피)&lt;/li&gt;
&lt;li&gt;『Design Patterns』: 바이블.&lt;/li&gt;
&lt;li&gt;『Design Patterns Smalltalk Companion』: GoF가 오른쪽 날개라면 DPSC는 왼쪽 날개&lt;/li&gt;
&lt;li&gt;『Pattern Hatching』(John Vlissides): DP 심화학습. 얇지만 밀도 높은 책.&lt;/li&gt;
&lt;li&gt;『Smalltalk Best Practice Patterns』(Kent Beck): 마이크로 패턴. 개발자의 탈무드. 감동의 연속.&lt;/li&gt;
&lt;li&gt;『Pattern Languages of Program Design』 1,2,3,4: 패턴 컨퍼런스 논문 모음집으로 대부분은 인터넷에서 구할 수 있음&lt;/li&gt;
&lt;li&gt;『Pattern-Oriented Software Architecture』 1,2: 아키텍처 패턴 모음. 2권은 네트워크 애플리케이션의 아키텍처 모음.&lt;/li&gt;
&lt;li&gt;『Concurrent Programming in Java』(Doug Lea): 컨커런트 프로그래밍에 대한 최고의 서적.&lt;/li&gt;
&lt;li&gt;『Patterns of Software』(Richard Gabriel): 패턴에 관한 중요한 에세이 모음.&lt;/li&gt;
&lt;li&gt;『Analysis Patterns』(Martin Fowler): 비즈니스 분석 패턴 목록. 비즈니스 애플리케이션 개발자에게 많은 도움이 됨.&lt;/li&gt;
&lt;li&gt;『A Timeless Way of Building』(Christopher Alexander): 프로그래머들이 가장 많이 본 건축 서적. 패턴의 철학적·이론적 배경. &amp;#39;구약&amp;#39;(&amp;#39;신약&amp;#39;은 올해 안에 출간 예정인 동저자의 『The Nature of Order』).&lt;/li&gt;
&lt;li&gt;『A Pattern Language』(Christopher Alexander): 알렉산더의 건축 패턴 모음집.&lt;/li&gt;
&lt;li&gt;『Problem Frames』(Michael Jackson): DP의 해결(solution) 지향식의 문제점과 극복 방안으로서의 문제 지향식 방법. 마이클 잭슨은 요구 사항 분석 쪽에서 동명의 가수만큼이나 유명.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;DP를 처음 공부한다면, DPE와 DPJW를 RF와 함께 보면서 앞서의 두 권을 RF적으로 독해해 나가기를 권합니다(하버드 대학의 뚜웨이밍 교수는 요즘 칸트를 유교적으로 독해하고 있다고 합니다. 하나의 책을 다른 각도에서 독해하는 것, 여기서 배우는 것이 많습니다). 이게 된 후에는 GoF와 DPSC를 함께 볼 수 있습니다. 양자는 상호 보완적인 면이 강합니다. 이쯤 되어 SBPP를 보면 상당히 충격을 받을 수 있습니다. 스스로가 생각하기에 코딩 경험이 많다면 다른 DP 책 이전에 SBPP를 먼저 봐도 좋습니다.&lt;/p&gt;
&lt;p&gt;이 정도의 책을 봤다면 POSA와 PLOPD 등에서 자신이 관심이 가는 패턴들을 찾아 읽는 것이 좋습니다. 그리고 동시에 알렉산더의 원저들을 꼭 읽기를 권합니다. 가브리엘의 책이 알렉산더의 사상 이해에 많은 도움이 될 것입니다.&lt;/p&gt;
&lt;p&gt;패턴 공부를 해나가면서 남을 가르치는 것이 공부에 많은 도움이 됩니다(사실 자바 패턴 책 중에 어떤 것은 &amp;quot;내가 패턴을 처음 공부하면서 같이 쓴 것이다&amp;quot;라고 저자가 고백한 것도 있습니다). 보이스카웃에서는 보통 다음 과정을 통해 뭔가를 &amp;#39;학습&amp;#39;하게 한다고 합니다. 처음은 어떻게 하는지를 보여주고, 다음은 스스로 그것을 해보게 하고, 다음으로 그걸 남에게 가르치게 합니다. 이 때 중요한 것은 상대가 이해하지 못 하면 그 이유를 자기 자신에게서 찾는 것이 나에게 더 이득이 된다는 것입니다. &amp;quot;내가 설명을 잘못 했군&amp;quot;하고 생각하는 것이죠. 그러면 다음번에는 설명을 좀더 잘 할 수 있게 되고, 동시에 자기의 이해도 더욱 명료해지게 됩니다. 저는 &amp;#39;OOP 개념을 한 시간 만에 가르치기&amp;#39;나 &amp;#39;특정 언어 문법을 한 시간 만에 가르치기&amp;#39; 등을 하나의 도전으로 생각하고 즐깁니다. 가르치면서 동시에 배운다는 것은 정말 놀라운 경험입니다.&lt;/p&gt;
&lt;h3&gt;익스트림 프로그래밍 학습에서의 문제&lt;/h3&gt;
&lt;p&gt;앞의 경우와 비슷합니다. 익스트림 프로그래밍을 공부하는 사람들은 실제로 행해보지 않고 책만 들여다보면 안 됩니다. 그렇다고 책이 중요하지 않다는 말은 아닙니다. 하지만 자전거 타기는 자기 몸으로 직접 굴려봐야 합니다.&lt;/p&gt;
&lt;p&gt;게다가 켄트 벡 스스로가 『XP Explained』를 만약 다시 쓴다면 뜯어고치고 싶은 부분이 상당히 된다고 말하는 것을 봐도 알 수 있듯이 초기 XP 이후 바뀌고 보완된 점이 상당수 있습니다. 따라서 책만으로 XP를 공부하기는 힘듭니다. 지금은 책 속의 XP가 사람들의 머리 속 XP에 한참 뒤쳐져 있습니다.&lt;/p&gt;
&lt;p&gt;어찌 되었든 XP에는 무술이나 춤, 혹은 악기 연주 등과 유사한 면이 많습니다. 따라서 글을 보고 그것을 익히기는 쉽지 않습니다. 그나마 메일링 리스트 같은 &amp;#39;대화&amp;#39;를 보면 훨씬 더 많은 것을 얻을 수 있기는 하지만, 태권도 정권 찌르기를 말로 설명하는 것이 불가능에 가깝듯이 XP를 언어를 통해 익히기는 정말 어렵습니다. 우리의 언어는 너무도 성글은 미디어입니다(XP는 매 초, 매 순간 벌어지는 &amp;#39;일상적&amp;#39; 장면의 연속이 매우 중요합니다).&lt;/p&gt;
&lt;h3&gt;익스트림 프로그래밍 공부&lt;/h3&gt;
&lt;p&gt;XP를 이해하려면 다음 기본 자료에 대한 이해가 우선해야 합니다(본지 2001년 12월호 「허실문답 XP 강화」 참조).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;『XP Explained』(Kent Beck): XP 선언서&lt;/li&gt;
&lt;li&gt;『XP Installed』(Ron Jeffries et al): C3 프로젝트에 적용한 예, 얻은 교훈 등&lt;/li&gt;
&lt;li&gt;『Planning XP』(Kent Beck, Martin Fowler): 계획 부분 설명(관리자, 코치용)&lt;/li&gt;
&lt;li&gt;『Refactoring』(Martin Fowler): 리팩토링에 대한 최고의 책&lt;/li&gt;
&lt;li&gt;『XP Applied』: 유즈넷과 메일링 리스트의 논의 등 최근 자료를 반영&lt;/li&gt;
&lt;li&gt;『XP Explored』: 가장 쉽고 구체적인 XP 안내서&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 중에서 XPI나 XPX를 먼저 권합니다. XPE는 좀 추상적인 서술이 많아서 봐도 느낌이 별로 없을 수 있습니다(2001년 본지 11월호에 제가 쓴 리뷰 참고). 여유가 되면 다음 자료를 더 참고합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;『The Timeless Way of Building』: 패턴 운동을 일으킨 알렉산더의 저작. 현장 고객(On-site Customer), 점진 성장(Piecemeal Growth), 소통(Communication) 등의 아이디어가 여기에서 왔음.&lt;/li&gt;
&lt;li&gt;『XP in Practice』(Robert C. Martin 외): 두세 사람이 짧은 기간 동안 간단한 프로젝트를 XP로 진행한 것을 기록. 자바 사용(중요한 문헌은 아님).&lt;/li&gt;
&lt;li&gt;『XP Examined』: XP 컨퍼런스에 발표된 논문 모음&lt;/li&gt;
&lt;li&gt;『Peopleware』(Tom DeMarco): 개발에 있어 인간 중심적 시각의 고전&lt;/li&gt;
&lt;li&gt;『Adaptive Software Development』(Jim Highsmith): 복잡계 이론을 개발에 적용. 졸트상 수상.&lt;/li&gt;
&lt;li&gt;『Surviving Object-Oriented Projects』(Alistair Cockburn): 얇고 포괄적인 OO 프로젝트 가이드라인&lt;/li&gt;
&lt;li&gt;『Software Project Survival Guide』(Steve McConnell): 조금 더 전통적인 SE 시각.&lt;/li&gt;
&lt;li&gt;『The Psychology of Computer Programming』(Gerald M. Weinberg): 프로그래밍에 심리학을 적용한 고전. 코드 공유와 짝 프로그래밍에 필수인 비자아적 프로그래밍(Egoless Programming)이 여기서 나왔다.&lt;/li&gt;
&lt;li&gt;『Agile Software Development』(Alistair Cockburn): 전반적 애자일 방법론에 대한 개론서&lt;/li&gt;
&lt;li&gt;『Software Craftsmanship』(Pete McBreen): 장인으로서의 새로운 프로그래머 상&lt;/li&gt;
&lt;li&gt;『Agile Software Development with SCRUM』(Schwaber Ken): 최근 확장성(Scalability)을 위해 XP+SCRUM의 시도가 애자일 쪽의 큰 화두임.&lt;/li&gt;
&lt;li&gt;『A Practical Guide to eXtreme Programming』(David Astels 외): 저자들이 직접 참여한 프로젝트를 따라가 보면서 배움. 자바로 구현. XPP보다는 스케일이 큼.&lt;/li&gt;
&lt;li&gt;『Agile Modeling』(Scott Ambler): 애자일 쪽에서 모델링이 무시되는 느낌이 있을 수 있는데, 그쪽으로 깊게 천착한 사람이 앰블러임.&lt;/li&gt;
&lt;li&gt;『Agile Software Development Ecosystems』(Jim Highsmith): 각각의 애자일 방법론에 대한 소개와 동시에 각 방법론의 대표자들 인터뷰가 백미.&lt;/li&gt;
&lt;li&gt;『Test Driven Development』(Kent Beck): 곧(아마 올해 내에) 출간 예정인 최초의 TDD 서적. TDD를 모르면 XP도 모르는 것(TDD를 실제 적용하려면 적어도 반년 정도는 계속 훈련해야 함)&lt;/li&gt;
&lt;li&gt;IEEE Software/Computer, CACM, Software Development Magazine 등에 실린 기사&lt;/li&gt;
&lt;li&gt;『XP Conference, XP Universe 등의 논문들(특히 최근 것들)&lt;/li&gt;
&lt;li&gt;유즈넷, 메일링 리스트, 오리지널 위키(&lt;a href=&quot;http://c2.com&quot;&gt;http://c2.com&lt;/a&gt;)의 논의들&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;특히 유즈넷, 메일링 리스트, 오리지널 위키는 늘 가까이 하고 있어야 합니다. 이 세 곳을 살필 때, 특히 다음 사람들의 글은 꼭 읽어보고 항상 레이더를 열어두면 좋습니다(이 외에도 개발 경력 10년, 20년이 넘는 짱짱한 사람이 많으므로 눈 여겨 관찰하세요. 모든 글을 읽는 것은 무리이므로 그들의 대화를 일차적으로 읽어야 합니다).&lt;/p&gt;
&lt;p&gt;켄트 벡, 론 제프리즈(Ron Jeffries), 워드 커닝엄(Ward Cunningham), 앨리스테어 코번(Alistair Cockburn), 마틴 파울러, 로버트 마틴 혹은 엉클 밥(Robert C. Martin aka Uncle Bob), 마이클 페더즈(Michael Feathers), 켄 아우어(Ken Auer), 윌리엄 웨이크(William Wake), 로이 밀러(Roy Miller), 데이브 토마스(Dave Thomas), 앤디 헌트(Andy Hunt), 랄프 존슨, 스카트 앰블러(Scott Ambler), 짐 하이스미스(Jim Highsmith), 조슈아 커리프스키(Joshua Kerievsky), 로렌트 보사빗(Laurent Bossavit), 존 브루어(John Brewer) 등&lt;/p&gt;
&lt;p&gt;이런 자료들 외에, 기회가 된다면 주변에서 XP를 직접 사용하는 곳을 방문해서 하루만 같이 생활해 보기를 권합니다. 반년 공부를 앞당길 수 있습니다. 제가 공부할 때는 주변에 XP를 아는 사람이 없어서 혼자 공부했는데, 그것에 비해 XP를 직접 사용하는 상황에 처한 사람은 (제가 억울하리만큼) 더 적은 노력으로 몇 배 이상 빨리 몸에 익히는 것 같았습니다.&lt;/p&gt;
&lt;p&gt;이게 힘들면 같이 공부하는 방법이 있습니다(앞서 언급된 스터디 그룹에 관한 패턴 참고). 이 때 같이 책을 공부하거나 하는 것은 시간 낭비가 많습니다. 차라리 공부는 미리 다 해오고 만나서 토론을 하거나 아니면 직접 실험을 해보는 것이 훨씬 좋습니다. 두 사람 당 한 대의 컴퓨터와 커대란 화이트보드를 옆에 두고 말이죠. 제 경우 스터디 팀과 함께 저녁 시간마다 가상 XP 프로젝트를 많이 진행했고, 짤막짤막하게 프로그래밍 세션도 많이 가졌습니다. 나중에 회사에서 직접 XP를 사용할 때 많은 도움이 되었습니다.&lt;/p&gt;
&lt;h3&gt;Refactor Me&lt;/h3&gt;
&lt;p&gt;제가 하고 싶은 말은 더 있지만 이 글은 일단 여기서 끝이 납니다. 서두에서 말씀드렸듯이 애초 쓰려던 &amp;#39;일반론&amp;#39;은 생략하고, 대신 독자들의 몫으로 남겨두려 합니다. 이 글 자체가 여러분의 리팩토링 수련의 연장(延長)이 되었으면 하는 바램입니다. 이 글과 함께 실생활에서 직접 실험을 해보면서 - 이 때 욕심 부리지 않고 한 가지씩 지긋이 해보는 느긋함과 음미의 정신이 필요할지도 모르겠습니다 - 자신의 경험을 축적해 나가고, 동시에 이 글을 적절히 리팩토링해서 자신만의 패턴을 차근히 만들어 나가길 바랍니다. 그렇습니다. 리팩토링은 대상에 대한 이해가 깊고 경험이 많을수록 더 잘할 수 있습니다.&lt;/p&gt;
&lt;p&gt;이 글에 소개된 제 공부론은 어찌 보면 상당히 진부해 보이기도 할 것입니다. 하지만 저는 이런 상식적이고 일상적이며 심지어는 소소해 보이는 것들에서 많은 감동을 받아왔습니다. 이 글도 사실 제 감동의 개인사입니다. 저는 &amp;quot;만약 오늘 어떤 것에라도 감동한 것이 없었다면, 오늘은 뭔가 잘못 산 것이다&amp;quot;라는 신조를 갖고 있습니다. 그것이 컴퓨터이건 대화이건 상관없이 말이죠. 저는 날마다 감동하며 살려고 노력합니다. 그러나 이 감동에 뭔가 꼭 대단한 것이 필요한 것은 아닙니다. 저는 오히려 감동 받기 위해 스스로 대단해져야 할 필요를 느끼기도 합니다. &amp;#39;감동&amp;#39;이라는 것은 주어지는 것이 아닙니다. 나와 타자가 공조하여 만드는 대화입니다.&lt;/p&gt;
&lt;p&gt;감동해야 체득할 수 있다고 생각합니다. 그리고 이 감동은 개인적 삶 속에서 자기가, 자신의 몸으로, 직접 얻는 것입니다. 工夫 열심히 합시다.&lt;/p&gt;</description>
      <category>Programming</category>
      <category>XP</category>
      <category>공부법</category>
      <category>디자인패턴</category>
      <category>리팩토링</category>
      <category>알고리즘 공부</category>
      <category>프로그래밍 공부</category>
      <category>프로그래밍 수련법</category>
      <author>Data Engineer</author>
      <guid isPermaLink="true">https://seamless.tistory.com/96</guid>
      <comments>https://seamless.tistory.com/96#entry96comment</comments>
      <pubDate>Mon, 23 Nov 2020 22:54:03 +0900</pubDate>
    </item>
    <item>
      <title>Docker and Container</title>
      <link>https://seamless.tistory.com/59</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Container&lt;/h2&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 컨테이너란 무엇일까요? 컨테이너는 Docker의 정식 홈페이지에서 다음과 같이 정의하고 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot; data-ke-style=&quot;style2&quot;&gt;Package Software into Standardized Units for Development, Shipment and Deployment&lt;/blockquote&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;컨테이너는 애플리케이션에게 격리된 실행 환경을 제공해주는 가상화 기술입니다. 컨테이너 단위로 OS, Library, Application을 패키징 할 수 있습니다. 컨테이너 기술을 통해 하나의 OS 상에서 여러 애플리케이션을 독립적으로 실행시킬 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;650&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tVpOt/btqBZr94tsu/YJaK0dZF5KtOM23UXJTmuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tVpOt/btqBZr94tsu/YJaK0dZF5KtOM23UXJTmuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tVpOt/btqBZr94tsu/YJaK0dZF5KtOM23UXJTmuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtVpOt%2FbtqBZr94tsu%2FYJaK0dZF5KtOM23UXJTmuK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;650&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Why container?&lt;/h2&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;그러면 왜 컨테이너 기술을 사용할까요? 그 이유는 다음과 같습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;- 컨테이너 이미지를 서비스 별 배포 단위로 만들어서 사용&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 컨테이너 이미지는 불변&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 배포와 롤백이 용이&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;- 리소스 사용의 효율성 증대&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;- MSA(Micro Service Architecture)에 적합&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nLYGJ/btqB2uKMuNM/3H4KUsNIpKv039vDQoY2lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nLYGJ/btqB2uKMuNM/3H4KUsNIpKv039vDQoY2lk/img.png&quot; data-alt=&quot;이미지 출처 :&amp;amp;amp;nbsp;https://www.redhat.com/ko/topics/containers/whats-a-linux-container&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nLYGJ/btqB2uKMuNM/3H4KUsNIpKv039vDQoY2lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnLYGJ%2FbtqB2uKMuNM%2F3H4KUsNIpKv039vDQoY2lk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지 출처 :&amp;nbsp;https://www.redhat.com/ko/topics/containers/whats-a-linux-container&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Technology&lt;/h2&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 컨테이너 기술을 가능하게 하는 기반 기술들이 존재합니다. 그러한 기반 기술들에 대해 살펴보도록 하겠습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Namespace&lt;/h4&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;프로세스에게 격리된 실행 환경을 제공하고 서로가 충돌 나지 않게 하는 기능입니다. 리눅스 커널에선 6가지 namespace 지원하고 있습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;- mnt(파일시스템 마운트) : 호스트 파일 시스템과 상관없이 독립적으로 파일 시스템을 마운트하거나 언마운트 가능&lt;br /&gt;- pid(프로세스) : 독립적인 프로세스 공간 할당&lt;br /&gt;- net(네트워크) : namespace 간 network 충돌 방지&lt;br /&gt;- ipc(SystemV IPC) : 프로세스간 독립적인 통신 통로 할당&lt;br /&gt;- uts(hostname) : 독립적인 hostname 할당&lt;br /&gt;- user(UID) : 독립적인 사용자 할당&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9oQJ2/btqB1bFtboY/tL154cafE0s4Wv3kdW3Akk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9oQJ2/btqB1bFtboY/tL154cafE0s4Wv3kdW3Akk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9oQJ2/btqB1bFtboY/tL154cafE0s4Wv3kdW3Akk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9oQJ2%2FbtqB1bFtboY%2FtL154cafE0s4Wv3kdW3Akk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;cgroups(Control Groups)&lt;/h4&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;cgroups는 자원에 대한 제어를 가능하게 해주는 리눅스 커널의 기능입니다. 제어 가능한 자원은 아래와 같습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp;메모리&lt;br /&gt;-&amp;nbsp;CPU&lt;br /&gt;-&amp;nbsp;I/O&lt;br /&gt;-&amp;nbsp;Network&lt;br /&gt;-&amp;nbsp;device&amp;nbsp;노드(/dev)&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;cgroup으로 그룹핑된 프로세스들은 리소스별로 정해진 양만 사용 가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oHw0C/btqB2I9R52S/2IB0kll9nlinI9k7k4XwJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oHw0C/btqB2I9R52S/2IB0kll9nlinI9k7k4XwJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oHw0C/btqB2I9R52S/2IB0kll9nlinI9k7k4XwJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoHw0C%2FbtqB2I9R52S%2F2IB0kll9nlinI9k7k4XwJ1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Union Filesystem&lt;/h4&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;서로 다른 파일 시스템이나 디렉터리를 합쳐서 하나의 논리적인 파일 시스템으로 컨테이너에게 제공됩니다. Union Filesystem을 살펴보기 전에 먼저 Union mount에 대해서 알아야 합니다. Union mount는 다음과 같습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;- Union mount&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 복수의 파일 시스템을 하나의 파일 시스템으로 마운트 하는 기능&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 두 파일 시스템에서 동일한 파일이 있다면 나중에 마운트된 파일 시스템을 오버레이함&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 하위 파일시스템에 대한 쓰기 작업은 CoW(Copy on Write) 전략에 따라 복사본을 생성하여 수행하므로 원본&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 파일시스템은 변하지 않음&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdZZWQ/btqBZr94S4S/PrcLnY1BrL3UZqk0tl2opK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdZZWQ/btqBZr94S4S/PrcLnY1BrL3UZqk0tl2opK/img.png&quot; data-alt=&quot;이미지 출처 :&amp;amp;amp;nbsp;https://blog.naver.com/PostView.nhn?blogId=alice_k106&amp;amp;amp;amp;logNo=221530340759&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdZZWQ/btqBZr94S4S/PrcLnY1BrL3UZqk0tl2opK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdZZWQ%2FbtqBZr94S4S%2FPrcLnY1BrL3UZqk0tl2opK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지 출처 :&amp;nbsp;https://blog.naver.com/PostView.nhn?blogId=alice_k106&amp;amp;logNo=221530340759&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 Union Filesystem에 대해 살펴보도록 하겠습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;- Union Filesystem&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - UnionFS : Linux, FreeBSD, NetBSD를 위해 초기에 구현된 Union Filesystem&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;- AUFS(Advanced Union File System)&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - UnionFS를 완전히 재작성하여 신뢰성과 성능을 개선하고 Writable Branch Balancing과 같은 새로운 개념을 도입&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 그러나 주류 리눅스 커널에는 반영이 안 됨&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;- Overlay&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 주류 리눅스 커널에 통합된 버전&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Docker&lt;/h2&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;Docker는 컨테이너를 이미지 파일로 빌드하고 배포하여 어디서나 실행할 수 있게 해 줄 수 있는 오픈소스 프로젝트입니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;Docker는 클라이언트(docker)와 서버(dockerd)로 구성되어 있습니다. 클라이언트에서 명령어를 서버로 전송해서 실행합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O0YDD/btqB1oqXCKJ/EvgzhyUcwsHL0umlWHK2aK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O0YDD/btqB1oqXCKJ/EvgzhyUcwsHL0umlWHK2aK/img.png&quot; data-alt=&quot;이미지 출처 :&amp;amp;amp;nbsp;https://docs.docker.com/engine/docker-overview/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O0YDD/btqB1oqXCKJ/EvgzhyUcwsHL0umlWHK2aK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO0YDD%2FbtqB1oqXCKJ%2FEvgzhyUcwsHL0umlWHK2aK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지 출처 :&amp;nbsp;https://docs.docker.com/engine/docker-overview/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;docker의 동작 방식은 다음과 같습니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;- Docker 이미지 빌드를 위해 Dockerfile 작성&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;- Docker 이미지 빌드를 dockerd에 요청&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;- dockerd에서 이미지를 빌드하고 로컬에 저장&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;- push 명령어를 요청하면 로컬에 저장되어 있는 빌드된 이미지를 docker registry에 전송&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;- run 명령어를 서버에 전송하면 docker 서버는 registry에 있는 이미지를 로컬에 다운로드하고 컨테이너를 시작&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 docker와 docker의 기반 기술이 되는 container에 대해 알아보았습니다. 다음 시간에는 이러한 container들을 orchestration 할 수 있는 kubernetes에 대해 알아보도록 하겠습니다.&lt;/p&gt;</description>
      <category>Cloud Computing/Docker &amp;amp; Kubernetes</category>
      <category>CD</category>
      <category>CI</category>
      <category>cloud</category>
      <category>container</category>
      <category>DevOps</category>
      <category>docker</category>
      <category>kubenetes</category>
      <category>lxc</category>
      <category>orchestration</category>
      <author>Data Engineer</author>
      <guid isPermaLink="true">https://seamless.tistory.com/59</guid>
      <comments>https://seamless.tistory.com/59#entry59comment</comments>
      <pubDate>Sun, 16 Feb 2020 15:21:11 +0900</pubDate>
    </item>
    <item>
      <title>05. 복제(Replication) - 2</title>
      <link>https://seamless.tistory.com/58</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;동기식 복제와 비동기식 복제&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://seamless.tistory.com/54&quot;&gt;이전 포스트&lt;/a&gt;에 이어서 복제에 관해 살펴보겠습니다. 복제는 동기 또는 비동기적으로 이루어집니다. 동기식 복제는 리더가 해당 팔로워가 쓰기를 수신했는지 확인해줄 때까지 기다리는 방식입니다. 아래의 그림에서는 Follwer1의 복제는 동기식으로 동작합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvfImf/btqABR9z3bz/Ak3kKmoJlD9xNUW8f0Ecsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvfImf/btqABR9z3bz/Ak3kKmoJlD9xNUW8f0Ecsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvfImf/btqABR9z3bz/Ak3kKmoJlD9xNUW8f0Ecsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvfImf%2FbtqABR9z3bz%2FAk3kKmoJlD9xNUW8f0Ecsk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;동기식 복제의 장점은 팔로워가 리더와 일관성 있게 최신 데이터 복사본을 갖는 것을 보장합니다. 동기식의 경우 리더가 동작하지 않아도 데이터는 팔로워에서 계속 사용할 수 있습니다. 그러나 동기적으로 동작하는 팔로워가 응답하지 않는다면 쓰기는 처리될 수 없는 단점이 존재합니다. 즉, 리더는 해당 팔로워가 다시 동작할 때까지 기다려야 하는 문제가 발생합니다. 그렇기 때문에 모든 팔로워가 동기식으로 복제하는 방법은 사용되지 않습니다. 현실적으로 데이터베이스에서 동기식 복제를 사용하려면 팔로워 하나는 동기식 다른 팔로워는 비동기식 복제를 하도록 사용합니다.&lt;/p&gt;
&lt;p&gt;보통 리더 기반 복제의 경우 완전히 비동기식으로 구성합니다. 리더가 잘못돼서 복구할 수 없는 경우 데이터가 유실될 수 있습니다. 하지만 모든 팔로워가 잘못되더라도 리더는 쓰기 처리를 계속할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리더 기반 복제의 고가용성&lt;/h2&gt;
&lt;p style=&quot;font-size: 1.25em;&quot; data-ke-size=&quot;size18&quot;&gt;팔로워 장애&lt;/p&gt;
&lt;p&gt;각 팔로워는 리더로부터 수신한 데이터 변경 로그를 로컬 디스크에 보관합니다. 팔로워에 문제가 발생하는 경우 문제 발생 전에 처리한 마지막 트랜잭션을 알아낸 후 이후에 발생한 데이터 변경을 리더에게 요청해서 복구합니다.&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot; data-ke-size=&quot;size18&quot;&gt;리더 장애&lt;/p&gt;
&lt;p&gt;리더가 장애가 나면 팔로워 중 하나를 새로운 리더로 승격시키고 클라이언트는 승격된 리더로 쓰도록 재설정을 해야 합니다. 그리고 다른 팔로워들이 새로운 리더로부터 데이터 복제를 하도록 합니다.&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;복제 지연 문제&lt;/h2&gt;
&lt;p&gt;리더 기반 복제는 모든 쓰기가 단일 노드를 거쳐야 하지만 읽기 전용 질의는 어떤 복제 서버에서도 가능합니다. 애플리케이션에서 비동기 팔로워에 데이터를 읽을 때 복제가 지연되어 이전 정보를 읽을 수도 있습니다. 이러한 복제 지연이 있을 때 발생할 수 있는 문제에 대해 살펴보도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot; data-ke-size=&quot;size18&quot;&gt;자신이 쓴 내용 읽기&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;사용자가 쓰기를 수행한 직후 데이터를 보는 경우 아직 복제 서버에는 반영이 안될 수도 있습니다. 물론 최종적으로는 반영될 것이지만 (이를 eventual consistency라고 합니다) 데이터 변경 직후에는 사용자가 보기엔 데이터가 유실된 것처럼 보이기 때문에 문제가 있습니다. 이러한 문제를 방지하기 위해 read-after-write 일관성이 필요합니다. 이 일관성은 자신이 제출한 모든 갱신은 보장하지만 다른 사용자에 대해서는 보장하지 않는 방식입니다. 이러한 방식은 사용자 자신의 입력이 올바르게 저장됐음을 보장할 수 있습니다. 그럼 이러한 방식은 어떻게 구현할까요?&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;소셜 네트워크에서 사용자 프로필 정보는 리더에서 읽고 다른 사용자 프로필은 팔로워에서 읽도록 하는 방법&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;마지막 갱신 시각을 찾아서 1분 내에 있는 갱신은 리더에서 읽고 복제 지연이 1분이 넘는 경우 팔로워에 질의를 금지하도록 하는 방법&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;클라이언트의 타임스탬프를 통해 복제 서버가 타임스탬프까지 따라잡을 때 까지 질의를 대기&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;font-size: 1.25em;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;단조(Monotonic) 읽기&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;단조 읽기는 각 사용자의 읽기는 동일한 복제 서버에서 읽도록 하는 방법입니다. 사용자 ID를 해시 기반으로 복제 서버를 선택하여 읽도록 하는 방식입니다. 이 방식은 최종적 일관성(eventual consistency)보다는 더 강한 보장 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;애플리케이션 개발자가 이러한 미묘한 복제 문제를 걱정하지 않고 항상 데이터베이스를 신뢰할 수 있다면 훨씬 좋을 것입니다. 그것이 트랜잭션이 있는 이유입니다. 오랫동안 단일 노드 트랜잭션은 존재했습니다. 그러나 분산 데이터베이스로 전환하는 과정에서 많은 시스템들이 트랜잭션을 포기했습니다. 트랜잭션이 성능과 가용성 측면에서 너무 비싸고 확장 가능한 시스템에서는 어쩔 수 없는 부분이 있을 수도 있겠죠. 그렇기 때문에 이와 같은 문제점들을 인지하고 어떻게 해결할 것인지 애플리케이션 개발자가 고민해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이번 포스트는 복제가 이루어지는 방식에 대해 알아보았습니다. 복제가 동기인지 비동기인지에 따라 장애가 발생하는 경우 시스템 작동에 중요한 영향을 끼칩니다. 또한 복제 지연으로 인해 발생할 수 있는 문제점을 살펴보았습니다. 그리고 애플리케이션이 복제 지연 시 어떻게 동작해야 하는지 결정하는데 도움이 되는 일관성 모델에 대해 알아보았고 이러한 일관성 모델을 잘 이해해서 애플리케이션이 잘 동작하도록 해야 할 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Big Data/Designing Data-Intensive Applicatiosn</category>
      <category>Datastore</category>
      <category>DDIA</category>
      <category>REPLICA</category>
      <category>replication</category>
      <category>고가용성</category>
      <category>데이터중심애플리케이션</category>
      <category>복제</category>
      <category>비동기복제</category>
      <category>일관성</category>
      <category>일관성모델</category>
      <author>Data Engineer</author>
      <guid isPermaLink="true">https://seamless.tistory.com/58</guid>
      <comments>https://seamless.tistory.com/58#entry58comment</comments>
      <pubDate>Fri, 20 Dec 2019 22:19:04 +0900</pubDate>
    </item>
  </channel>
</rss>