나도 git 좀 써 보자 – 설치하기

이 글은 크몽 재능인, socurites님이 원고를 기고하셨습니다.

이 글은 총 4부로 구성됩니다.

CVS에서 SVN으로 넘어온지도 꽤 지났다. 그러면서 ant, hudson, jenkins, anthill.. 이제는 이름도 생각나지 않은 Cruise Control 등의 빌드 툴들을 사용해 봤다. 그렇게 시간이 지났고, 이제 git의 시대가 되었다.

관심 없었다. 서브버전(Subversion)으로도 충분히 큰 불편함이 없었기에. 이제 사람들이 모두 “짓허브”이 아니라 “깃허브”이라고 부르는 Github에 많은 프로젝트와 코드들이 쌓여간다.

이제는 더 뒤쳐질 수 없기에, 몇년 전에 사두었던 책, “Git, 분산 버전 관리 시스템”을 꺼내 들었다.

git_book

이제 나도 Github에서 단순히 ZIP 파일을 다운로드 하는게 아니라, 제대로 좀 써보자!!!!!!
는 생각으로 git 사용 이력을 여기에 남겨 본다.

먼저 git을 설치해야 한다. 맥북에는 이미 설치가 되어 있다. 이미 설치되었는지 궁금하다면, 버전을 확인해 보자.

island:~ socurites$ git --version
git version 1.8.1.2

윈도우에 git 설치하기

git은 리눅스를 기반으로 만들어졌기 때문에, 윈도우는 공식적으로 지원하지 않는다. 하지만 구글 코드에서 Git on MSys라는 이름의 프로젝트가 있다. Git on MSys는 git을 윈도우에 설치하여 쉽게 사용할 수 있도록 만들어졌다.

https://code.google.com/p/msysgit/‎에서 다운로드하여 쉽게 설치가 가능하다. 설치 과정에서 아래 창이 뜨면 2번째 옵션을 선택해야만, 커맨드 창에서도 git을 쉽게 이용할 수 있다.

git_window_install

이제 환경 설정을 해야 한다. 로컬 시스템의 전역 값 중에서 user.name과 user.email은 반드시 설정해야 한다.

island:~ socurites$ git config --global uer.name "socurites"
island:~ socurites$ git config --global user.name "socurites"
island:~ socurites$ git config --global user.email "socurites@gmail.com"
island:~ socurites$ git config --global --list
uer.name=socurites
user.name=socurites
user.email=socurites@gmail.com

잘못해서 오타가 났다. user.name을 입력해야 하는데, user.name을 입력했다. 불필요한 설정 값이므로 삭제하자.

island:~ socurites$ git config --global --unset uer.name
island:~ socurites$ git config --global --list
user.name=socurites
user.email=socurites@gmail.com

여기까지가 필수 전역 설정이다.

윈도우 사용자를 위한 추가 설정

git의 커밋 메시지의 기본 인코딩은 utf-8이다. 윈도우는 명령 프롬프트의 기본 인코딩이 cp949이므로, 커밋 메시지와 로그 메시지의 인코딩을 cp949로 변경해야 한다.

prompt>git config --global i18n.commitEncoding cp949
prompt>git config --global i18n.logOutputEncoding cp949

또한 mSysGit에서 로그 메시지를 콘솔로 출력할 때 less 명령어를 사용하는데, less 명령어가 한글을 제대로 보여주려면 마찬가지로 문자셋을 변경해 주어야 한다.

prompt> set LESSCHARSET=latin1

만약 환경 변수 값을 영구적으로 설정하고자 한다면, 시스템 환경 변수에 LESSCHARSET를 추가하고, 값으로는 latin1을 설정한다.

git_window_lesscharset

설정 파일은 사용자 홈디렉토리에 .gitconfig라는 이름으로로 저장된다.

크몽 재능

PHP 프레임워크 검토하기

이 글은 크몽 재능인, socurites님이 원고를 기고하셨습니다.

아래는 http://www.phpframeworks.com/index.php 에서 설명하는 프레임워크 목록이다.

php-framework-list

분류 기준은 다음과 같다.

  • MVC
    Model-View-Controller 패턴을 내부적으로 지원 하느냐
  • Multiple DB’s
    여러개의 데이터베이스를 지원하느냐
  • ORM
    Object-Record Mapper를 지원하느냐(ActiveRecord 패턴)
  • DB Objects
    TableGateWay(또는 Table Data Gateway)와 같은 데이터베이스 객체를 지원하느냐
  • Templates
    내부적으로 템플릿 엔진을 포함하느냐
  • Caching
    캐싱 객체를 지원하느냐 또는 다른 방식으로도 캐싱을 지원하느냐
  • Validation
    검증(validation) 또는 필터링(Filtering) 컴포넌트를 지원하느냐
  • Ajax
    Ajax를 지원하느냐
  • Auth Module
    사용자 인증을 처리하기 위한 모듈을 내부적으로 포함하느냐
  • Modules
  • RSS 피드 파서, PDF 모듈 등과 같이 유용한 모듈을 포함하느냐
  • EDP
    이벤트 주도 개발(Event Driven Programming)을 지원하느냐

ActiveRecord 패턴, Table Data Gateway 패턴 등에 대한 설명은 마틴 파울러(Martin Folwer)가 쓴 PoEAA(Pattern of Enterprise Application Architecture)를 참고하세요.

이 중에서 반드시 필요로 하는 속성은 다음과 같다고 볼 수 있다.

  • MVC
    개발 생산성 및 유지보수성을 위해 반드시 필요한 속성이다.
  • Multiple DB’s
    하나의 데이터베이스를 사용하는 경우는 거의 없다.
  • ORM
    데이터베이스 접속을 추상화하기 위해서 반드시 필요하다.
  • DB Objects
    ORM만으로 처리하기 임든 경우가 많으므로, 이 또한 반드시 필요하다.
  • Caching
    처리 속도 향상을 위해 필수적이다.
  • Ajax
    처리 속도 향상 및 사용자 경험 향상을 위해 필수적이다.

중요하지만, 꼭 필요하지 않은 속성은 다음과 같다. 꼭 필요하지 않다는 말은, 웹 어플리케이션에서 필요로 하지 않은 속성이 아니라, 다른 외부 모듈을 통해서 충분히 가져다 쓸 수 있다는 뜻이다.

예를 들어 템플릿 지원 속성의 경우, 프레임워크에서 지원하지 않더라도 전용 템플릿 엔진과 결합해서 사용할 수 있다.

  • Templates
    다른 템플릿 엔진을 연동하면 된다
  • Validation
    검증의 경우, 도메인에 따라 특화되어야 하므로, in-house 방식으로 개발되어야 한다.
  • Auth Module
    외부 인증 모듈(페이스북, 구글 등)과 연동해서 사용할 수 있다.
  • Modules
    부수적

결국, 필수 속성을 만족하는 프레임워크는 다음과 같이 7개로 추려진다.

  •  Akelos
  • CakePHP
  • Prado
  • Seagull
  • Symfony
  • Yii
  • Zend

PHP 프레임워크 사용자 평가

프레임워크를 선택할 때 다음으로 중요한 속성으로는 프레임워크가 가벼운지, 그리고 사용이 편한지다.

아무리 좋은 속성을 제공하더라도, 무겁다면 로컬환경에서 개발하기가 쉽지 않다. 또한 제공하는 API가 유연하지 못해 사용하기가 쉽지 않다면, 이 역시도 개발 생산성 및 확장성 면에서 좋지 않다.

아래는 http://www.phpframeworks.com/top-10-php-frameworks/ 에서 제공하는 프레임워크 랭킹이다. 이중에서, 필수 속성을 만족하는 프레임워크 랭킹은 다음과 같다.

php-framework-ranking

구글 트렌드(Google Trend)

이런게 신통방통한 툴이 있는 줄 알았다면! 아래는 PHP 프레임워크에 대한 트렌드 비교 입니다.

php-framework-google-trend

크몽 재능

PHP – 값에 의한 전달 vs. 참조에 의한 전달

이 글은 크몽 재능인, socurites님이 원고를 기고하셨습니다.

새로운 프로그래밍 언어를 배울 때마다, 함수나 메서드에 파라미터를 전달할 때 값에 의한 전달(pass by value)인지 아니면 참조에 의한 전달 방식(pass by reference)을 취하는지가 중요한 요소가 되곤 했다.

예를 들어 자바에서는 모두 값에 의한 전달이다. 자바랜치(http://www.javaranch.com)에서 알기 쉽게 설명하였으니 참고하기를 바란다.

파이썬도 크게 다르지 않으며, 값에 의한 전달로 볼 수 있다.

결국 자바와 파이썬은 모두 값에 의한 전달만 있을 뿐, 참조에 의한 전달은 없다고 봐야 한다. 참조에 의한 전달은 언어를 사용하는 방식을 복잡하게 만들며, 대다수의 최신 언어에서는 지원하지 않는 편이다.

반면 PHP는 값에 의한 전달과 참조에 의한 전달을 구분한다. 예를 들어 파라미터로 넘어온 값을 1만큼 증가시키는 아래의 함수를 보자.

function increment($value, $amount=1) {
    $value = $value + $amount;
}

즉 $value의 값을 $amount(1)만큼 증가시키는 함수다. 이제 함수를 아래와 같이 호출해보자.

$value = 10;
increment($value);
echo $value."\n";

호출한 결과, $value의 값은 변하지 않은 채 10으로 출력된다. 이는 변수의 유효범위가 함수로 한정되기 때문에, 함수 밖에 $value가 있더라도, 함수 파라미터에 선언된 $value는 지역변수로 선언되며 결국 함수 밖의 $value를 가리게 된다.

그렇다면 함수 밖의 $value를 변경할 수 있으려면 어떻게 해야 하는가?
함수를 아래와 같이 변경해 보자.

function increment2(&$value, $amount=1) {
    $value = $value + $amount;
}

차이점은 변수 앞에 &를 붙였다는 점이다. 이는 값에 의한 전달이 아닌, 참조에 의한 전달을 하겠다는 뜻이다. 따라서 아래와 같이 호출하면

$value2 = 10;
increment2($value2);
echo $value."\n";

$value의 값이 11로 변경된 것을 알 수 있다.

하지만 질문 자체가 위험한 질문이다. 왜 함수 내부에서 함수 밖의 변수 값을 수정할 수 있어야 하느냐다. 이는 함수가 호출되기 전과 호출된 후의 컨텍스트가 바뀌게 되는 결과를 낳기 때문에, 옳은 사용법은 아니다. 코드 라인은 줄겠지만, 그에 따른 부작용 역시 감수해야 한다. 동일한 결과를 낳기 위해서는 아래와 같이 사용할 수 있으며, 차라리 아래 방식이 더 나아 보인다.

function increment3($value, $amount=1) {
    $value = $value + $amount;
    return $value;
}

$value3 = 10;
$value3 = increment3($value3);
echo $value3."\n";

이렇게 사용하게 되면, 값을 바꾸겠다는 의도를 더 명확히 표현할 수 있다.

그렇다면 기본형(primitive type)이 아닌 경우에는 어떨까? 배열을 한번 보자. 아래는 배열을 받아서, 해당 배열의 요소의 배수를 계산하는 함수다.

function multiply($arr, $factor) {
    for ($i = 0; $i < count($arr); $i++ ) {
        $arr[$i] *= $factor;
    }
}

$arr = array(1, 2, 3);
multiply($arr, 2);

하지만 이 역시도 원하는대로 동작하지 않는데, 마찬가지로 값에 의한 전달이기 때문이다. 함수를 참조에 의한 전달을 사용하도록 수정해 보자.

function multiply2(&$arr, $factor) {
    for ($i = 0; $i < count($arr); $i++ ) {
        $arr[$i] *= $factor;
    }
}

$arr = array(1, 2, 3);
multiply2($arr, 2);

이제는 원하는 방식대로 동작한다. 하지만 함수 내부에서 함수 밖의 객체를 수정하는 일은 그렇게 달가운 방식은 아니다. 같은 일을 아래와 같이 처리할 수도 있다.

function multiply3($arr, $factor) {
    for ($i = 0; $i < count($arr); $i++ ) {
        $arr[$i] *= $factor;
    }

    return $arr;
}

$arr = array(1, 2, 3);
$arr = multiply3($arr, 2);

하지만 이마저도 사실 더욱 헷갈릴 뿐이다. 사실 자바를 사용하는 경우라면, 첫 번째 multiply 함수를 사용하면 함수 밖의 배열의 값이 바뀌게 된다.

결국 언어간의 이러한 전달 방식의 사소한 차이는 큰 에러로 치닫을 수 있다는 점에서 유심히 살펴보지 않되는 부분이다.

참고로 PHP 5.3 이전 버전에서는 참조에 의한 전달을 사용하는 경우 아래와 같이 호출하는 코드를 볼 수도 있다.

$arr = array(1, 2, 3);
multiply2(&$arr, 2);

하지만 5.3 이상 버전에서는 이렇게 사용하는 경우 아래와 같은 에러가 난다.
Fatal error: Call-time pass-by-reference has been removed….

크몽 재능

스무디(smoothie)를 사용하여 실시간 차트 그리기

이 글은 크몽 재능인, socurites님이 원고를 기고하셨습니다.

스무디(smoothie.js)를 사용하여 스트리밍 데이터에 대한 실시간 차트를 그려보자. 먼저 실시간 차트가 무엇인지 먼저 보고 싶다면, 아래의 링크를 살펴보자.

http://smoothiecharts.org/examples/example1.html

스무디 차트(Smoothie Chart)는  라인 차트를 실시간으로 부드럽게 표시할 수 있는 자바스크립트(javascript) 라이브러리로, 사용법이 매우 간단한다.

먼저 smoothie.js를 다운로드 하여 자신의 웹 프로젝트로 추가하자.

smoothie.js를 자바스크립트에 포함한다

<script type=”text/javascript” src=”/javascript/smoothie.js”></script>

canvas 객체를 생성한다

<canvas id=”mycanvas” width=”400″ height=”100″></canvas>

canvas에 SmoothieChart 객체를 연결한다

var smoothie = new SmoothieChart();
smoothie.streamTo(document.getElementById(“mycanvas”));

데이터를 추가한다

// 두개의 라인 데이터 추가
var line1 = new TimeSeries();
var line2 = new TimeSeries();

// 매 초마다, 각 라인에 랜덤 숫자값을 할당
setInterval(function() {
line1.append(new Date().getTime(), Math.random());
line2.append(new Date().getTime(), Math.random());
}, 1000);

// 스무디 차트 객체에 라인 등록
smoothie.addTimeSeries(line1);
smoothie.addTimeSeries(line2);

TimeSeries.append(timestamp, value) 함수

  • timestamp
    데이터를 표시할 시간(X축)
  • value
    데이터의 값(Y축). 임의의 숫자값. value의 최소값과 최대값을 기반으로 차트를 자동으로 축소/확대한다.

지연시간을 추가한다

지금까지 만든 차트에는 약간의 문제가 있다. 다음 번 데이터의 값을 알기 전까지는 차트에 표시할 수 없다. 따라서 차트가 연속적으로 움직인다기 보다는, 약간 점프하는 것 처럼 보인다.

이 문제를 해결하기 위해, 차트에 약간의 지연시간을 추가하자. 따라서 라인을 다음 번 데이터가 전달된 후 라인을 그리도록 만들어 보자.

지연 시간은 SmoothieChart.streamTo()를 호출할 때 추가할 수 있다.

smoothie.streamTo(document.getElementById(“mycanvas”), 1000 /delay/);

차트 색상을 지정한다

스무디는 라인과 배경의 스타일을 변경할 수 있는 API 또한 제공한다.

var smoothie = new SmoothieChart({ grid: { strokeStyle:’rgb(125, 0, 0)’, fillStyle:’rgb(60, 0, 0)’, lineWidth: 1, millisPerLine: 250, verticalSections: 6, }, labels: { fillStyle:’rgb(60, 0, 0)’ }
});

smoothie.addTimeSeries(line1, { strokeStyle:’rgb(0, 255, 0)’, fillStyle:’rgba(0, 255, 0, 0.4)’, lineWidth:3 });

smoothie.addTimeSeries(line2, { strokeStyle:’rgb(255, 0, 255)’, fillStyle:’rgba(255, 0, 255, 0.3)’, lineWidth:3 });

실행하기

여기에서는 표시할 수 없지만. 실행 해보면 아래와 같이 차트가 움직이는 것을 확인할 수 있다.

smothie-chart

참고자료

크몽 재능

[독후감] 거침없이 하둡 활용해 보기

이 문서는 [거침없이 배우는 하둡(Chuck Lam 지음 / 이현남, 강택현 옮김 / 지앤선 출판사)]을 읽고 쓴 독후감이다.

hadoop_in_action_face

가장 기본적인 내용은 간단히 정리해보고, 지금까지 미처 활용하지 못했던 몇가지 팁을 작성하고자 한다.

하둡이란

하둡(Hadoop)은 빅데이터를 처리할 수 있는 분산 응용 프로그램을 작성하고, 실행하기 위한 오픈 소스 프레임워크다. 분산 컴퓨팅 플랫폼은 하둡 외에도 다양하게 존재하지만, 하둡은 아래와 같은 차이점을 갖는다.

  • 접근성(Accessibility)
    하둡은 다수의 범용 컴퓨터를 이용하여 쉽게 클러스터를 구성할 수 있다. 또는 아마존의 EC2(Elastic Compute Cloud)와 같은 클라우드 인프라를 간단히 활용할 수도 있다.
  • 견고함(Robust)
    하둡은 범용 컴퓨터로 구성된 클러스터상에서 실행되도록 설계되었기 때문에, 하드웨어가 고장이 나더라도 쉽게 복구할 수 있다.
  • 확장가능성(Scalability)
    처리 속도나 저장 용량을 늘리고자 할 때, 단순히 컴퓨터를 더 추가함으로써 선형적으로 확장이 가능하다.
  • 단순함(Simplicity)
    분산된 컴퓨터에서 병렬적으로 처리되는 프로그램을 손쉽게 개발할 수 있다.

<하둡의 설계 철학>

프로그램은 일반적으로 데이터를 프로그램이 있는 컴퓨터에 가져와서 처리하게 된다. 반면 하둡의 경우, 데이터가 있는 컴퓨터에 프로그램을 전송해서 그 곳에서 데이터를 처리한다. 따라서 실제 데이터가 저장되어 있는 컴퓨터에서 데이터가 처리된다. 데이터가 대용량이라는 점을 감안해 볼 때, 데이터를 옮기는 것보다 프로그램을 옮기는 것이 당연하며, 하둡은 이러한 철학을 따라 만들어졌다.

MapReduce 살펴보기

MapReduce 프로그램은 map과 reduce, 2단계로 이루어진다. map 단계에서는 입력 데이터를 필터링하여 reduce 단계에서 처리할 수 있는 형태로 변경한다. reduce는 map의 출력 값을 입력 값으로 받은 후, 데이터를 통합한다.

하둡의 구성 요소

하둡은 아래의 데몬들로 구성된다.

  • NameNode(네임노드)
  • DataNode(데이터노드)
  • Secondary NameNode(세컨데리 네임노드)
  • Job Tracker(잡 트래커)
  • TaskTracker(태스크 트래커)

hadoop_diagram

  •  NameNode
    하둡은 마스터/슬레이브 구조를 가진다. 네임노드는 HDFS에서 마스터 역할을 하며, 슬레이브 역할을 하는 데이터 노드에게 I/O 작업을 할당한다. 네임노드는 현재 이중화 구성을 할 수 없어서, 하둡 클러스터를 운영할 때 단일 장애 지점이 된다.
    * 네임노드 백업 및 이중화에 대해서는 조금 후에 살펴보도록 하겠다.
  •  DataNode
    실제 데이터는 데이터노드에 저장된다. 클라이언트가 HDFS에 파일을 읽거나 쓰기 위해 네임노드에게 요청을 날리면, 네임노드는 어느 데이터노드의 어디 블록에 파일이 있는지(또는 쓸지)를 알려준다. 그러면 클라이언트는 데이터노드와 직접 통신하여, 파일을 읽거나 쓰게된다. 다시 말해, 데이터노드와 블록 위치가 정해지면 클라이언트는 네임노드와는 전혀 통신하지 않고, 해당 데이터 노드와 직접 통신한다.
    클러스터가 처음 시작될 때, 각 데이터 노드에서 자신의 블록 정보를 네임노드에게 알려준다. 그런 후 데이터 노드는 자신의 로컬 디스크에 변경사항이 발생할 때마다 네임노드에게 변경사항을 알려주게된다.
  •  Secondary Name Node
    세컨데리 네임노드는 HDFS의 블록이 변경되더라도 실시간으로 변경정보를 갱신하지 않는다. 세컨데리 네임노드는 네임노드의 블록정보를 병합할 때 사용하기 위한 노드이며, 백업 노드도 아니고 stand by 네임 노드는 더더욱 아니다.
    하지만 네임노드에 장애가 발생할 경우를 대비해서 백업용 노드로 활용할 수 있다. 그리고 네임노드가 장애가 난 경우, 세컨데리 네임노드를 네임노드로 활용할 수도 있다. 이러한 작업은 모두 자동화 되지 않으며, 수작업으로 처리해야 한다.
    * 네임노드 백업 및 이중화에 대해서는 조금 후에 살펴보도록 하겠다.
  •  JobTracker
    클라이언트가 요청한 작업(job)에 대한 실행을 관리한다. 일반적으로 잡트래커는 마스터 노드, 즉 네임노드가 설치된 노드에서 실행한다.
  •  TaskTracker
    작업을 실제로 처리하는 일은 태스크트래커가 맡는다. 슬레이브 노드, 즉 데이터 노드에는 하나의 태스크트래커만이 존재하며, 여러개의 JVM을 생성해서 다수의 map과 reduce를 실행하게 된다.
    mapreduce

Job Chaining

복잡한 문제가 있을 때 하나의 프로그램으로 처리할 수도 있지만, 작은 책임만을 가지도록 프로그램을 나누면 프로그램을 유지보수하기가 쉬워진다. MapReduce에서도 여러개의 작은 Job을 연결하여 복잡한 문제를 처리할 수 있다.

  • Job Pipeline(잡 파이프라인)
    Job A와 Job B가 있고, A 작업이 끝난 후 A 작업의 출력을 B 작업의 입력으로 사용해야 한다고 해보자. 수작업으로 A 작업이 끝난뒤, B 작업을 실행할 수도 있지만, 파이프라인을 이용하면 여러개의 Job을 서로 연결할 수 있다.
    job_A | job_B위와 같이 실행한 경우, job_A의 드라이버가 실행 된 후, job_B의 드라이버가 실행된다. 그리고 job_A의 결과 파일이 job_B의 입력파일로 설정된다.
  • Job Dependency(잡 의존성)
    여러가지 Job이 있고, 각 Job이 모두 순차적으로 실행되지는 않을 경우 단순히 파이프라인만으로는 실행 순서를 결정할 수 없다. 예를 들어 job_A, job_B, job_C가 있고, job_A와 job_B는 job_C보다 먼저 실행되어야 하지만, job_A와 job_B는 어느 순서로 실행되어도 되는(또는 동시에 실행되어도 관계 없는) 경우를 가정해보자. 이 경우 아래와 같이
    job_A | job_B | job_C
    또는
    job_B | job_A | job_C와 같이 실행해도 원하는 결과를 얻을 수 있다. 하지만 처리속도는 최적화되지 않는데, job_A와 job_B는 동시에 실행하는 편이 낫기 때문이다. 이러한 경우 의존성을 직접 설정하여 처리 순서를 결정할 수 있다.예를 들어 job_C에 대해 아래와 같이 설정할 수 있다.
    job_C.addDependingJob(job_A);
    job_C.addDependingJob(job_B);이렇게 설정하면, job_A와 job_B가 종료된 후 job_C가 실행되는 것을 보장한다.
    이처럼 의존성이 결정된 job은 모두 JobControl 객체에 등록해야하며, 이때 addJob() 메서드를 이용한다.
    의존성과 job 등록이 완료되면, JobControl.run() 메서드를 실행하여 job을 실행할 수 있다.
    JobControl에는 allFinished() 메서드와, getFailedJobs() 메서드가 있어 job들의 실행상태를 모니터링할 수 있다.
  • ChainMapper와 ChainReducer
    데이터를 처리할 때 두가지 책임을 갖는 매퍼를 만드는 경우가 있다. 예를 들어 웹로그를 읽어서 사용자 접근 유형을 분석하는 MapReduce를 개발한다고 해보자. 이경우 아래와 같은 처리흐름을 따라 처리할 수 있다.
    – 로그 정규화 및 유효하지 않은 라인 제거 : 입력 파일의 로그 중에서, 정해진 로그 포맷에 맞지 않는 라인을 걸러낸다.
    – 클라이언트 Ip별로 매핑 : 클라이언트 IP를 키로, 상태가 200인 접근 로그를 값으로 매핑한다.
    – 클라이언트 IP별 접근 유형 리듀싱 : 클라이언트 IP별로 접근 로그를 분석하여 결과파일로 리듀싱한다.
    – 리듀싱된 결과 DB에 로드 : 리듀싱된 결과를 DB에 로드한다.하나의 Job으로도 위의 로직을 모두 구현할 수 있으나, 첫번째 유효성 검사 작업과 마지막 DB 로드 작업은 다른 Job에서도 공통적으로 필요한 모듈이므로 별도의 모듈로 관리하면 재사용이 용이하다. 따라서 아래와 같이 Job을 구조화해볼 수도 있다.
    Job_A | Job_B | Job_CJob_A
    – mapper : 웹로그의 입력을 읽어 정규화한다. 키는 라인번호, 값은 로그 라인 전체를 매핑한다.
    – reducer : IdentityReducer를 사용한다.Job_B
    – mapper : 클라이언트 Ip별로 매핑. 키는 클라이언트 IP, 값은 접근 로그
    – reducer : 클라이언트 IP별 접근 유형 리듀싱. 키는 클라이언트 IP, 값은 분석 결과Job_C
    – mapper : 분석 결과를 DB로 로드한다. 출력은 없음
    – reducer : 없음
    이처럼 파이프라인을 활용하면 책임에 따라 Job을 하위 모듈로 구조화할 수 있지만, 각 단계마다 중간 결과를 처리해야 하므로 I/O가 발생하게 된다.이와는 달리 아래와 같이 구조화할 수도 있다.
    Job_A_mapper | job_B | job_C_mapper
    (Job_A_mapper | job_B_mapper | job_B_reducer | job_C_mapper)즉, 전처리 단계는 모두 매퍼에서 실행하고, 후처리 단계는 리듀서 이후에 실행하는 방식이다. 이때 ChainMapper와 ChainReducer를 사용할 수 있다. 위의 로직을 의사 코드로 작성해보면 아래와 같다.

    ChainMapper.addMapper(Job_A_mapper);
    ChainMapper.addMapper(Job_B_mapper);
    ChainReducer.setReducer(Job_B_reducer);
    ChainReducer.addMapper(Job_C_mapper);

IsolationRunner을 활용한 태스크 재실행

TDD를 바탕으로 MapReduce를 개발하더라도, 실행단계에서는 예외가 발생하기 마련이다. 이러한 경우 스택 트레이스를 눈으로 확인해서 디버깅을 하게 되는데, 그 과정이 상당히 번거로울 뿐만 아니라 버그를 찾기도 쉽지 않다. 이를 위해 하둡에서는 ISolationRunner라는 유틸리티를 제공한다. ISolationRunner를 사용하면 실패한 태스크에 대해, 실패할 당시의 데이터를 사용하여 해당 태스크만 독립적으로 실행할 수 있다.

ISolationRunner를 사용하려면 keep.failed.tasks.files 속성을 true로 설정해야 한다. true로 설정한 후 Job을 실행하면, 태스크가 실패한 경우 해당 데이터를 보관하게 된다.

자세한 사용법은 하둡 ISolationRunner튜토리얼을 참고한다.

MapReduce 성능 향상을 위한 팁

컴바이너(Combiner)의 활용

매퍼의 결과값이 큰 경우, 매퍼의 결과를 모두 리듀서로 전달할 때 네트워크 I/O에서 병목이 발생할 수 있다. 이러한 경우에는 컴바이너를 활용하면 성능이 향상될 수 있다. 별도의 컴바이너 프로그램을 구현하기보다는, 리듀서를 매퍼 단계에서 적용하여 매핑의 결과를 줄이는 효과를 내기 위해서 사용한다.

압축하기

컴바이너를 활용하더라도, 매퍼의 결과인 중간 단계의 데이터가 클 수 있다. 이러한 경우 중간 데이터를 압축하면 성능을 개선할 수 있다. 매퍼의 결과를 압축하기 위해서는 아래의 속성을 설정해야 한다.

속성 설명

  • mapred.compress.map.output
    매퍼의 출력을 압축하지 여부
  • mapred.map.output.compression.codec
    매퍼의 출력을 압축할 때 사용할 코덱 클래스

이 두 속성은 설정파일에 전역 속성으로 설정하기보다는, Job의 성격에 따라 JobConf 객체에 설정하는 편이 낫다.

압축할 때 사용할 수 있는 코덱에는 DefaultCodec, GzipCodec, BZip2Codec 등이 있다.

리듀스의 출력 데이터는 중간단계의 데이터가 아닌 최종 결과물인 경우가 많다. 따라서 리듀스의 출력을 압축하는 경우 문제가 된다. 출력값이 하둡의 블록 사이즈(보통 64MB)보다 큰 경우, 압축된 출력 결과가 2개 이상의 블록으로 분할되게 된다. 대다수의 압축형식에서 이처럼 분할된 압축 파일을 개별적으로 압축 해제할 수 없다. 결국 리듀서의 출력 값을 입력 값으로 사용하는 또다른 Job이 있는 경우, 각 블록마다 태스크가 실행되는 대신 하나의 태스크만 실행되므로 병렬 처리라는 장점을 잃게 된다.

이를 위해 하둡에서는 SequenceFile이라는 특별한 형태의 파일을 지원한다.

JVM의 재사용

태스크트래커는 매퍼와 리듀서에 대해, 별도의 JVM을 새로 할당하여 자식 프로세스로 실행한다. 따라서 초기화 작업이 복잡한 경우, JVM을 실행하는데 시간이 오래 걸린다. 이처럼 JVM을 새로 시작하는데 시간이 오래 걸린다면, Job을 실행하는 전체 시간도 오래 걸린다.

하둡 0.19 버전에서는 동일한 Job의 여러 태스크에 대해 JVM을 재사용할 수 있다. JVM을 재사용하면 태스크의 시작 비용을 줄일 수 있다. JVM을 재사용하려면, mapred.job.reuse.jvm.num.tasks 속성을 변경한다. 또는 JobConf 객체의 setNumTasksToExecutePerJvm(int) 메서드를 활용할 수도 있다.

추론적 실행(Speculative Execution)

맵리듀스 프레임워크는 맵 태스크나 리듀스 태스크가 실패한 경우, 자동으로 태스크를 재실행한다. 이처럼 재실행할 수 있는 이유는 동일한 태스크가 여러번 실행되더라도 동일한 결과가 나오도록 보장하기 때문이다. 이는 수학적 용어로 “멱등성이 보장된다”라고 부른다.

태스크가 실패하지 않더라도 동일한 태스크가 여러번 실행될 수 있다. 예를 들어 특정 노드에서만 태스크가 느리게 진행될 수 있다. 이는 자원 상황(CPU, I/O 등)에 따라 해당 시점에만 발생할 수 있는 경우다. 이 경우 하둡은 동일한 태스크를 다른 노드에서도 실행하게 되며, 가장 빨리 완료된 태스크의 결과를 최종 결과로 사용하며 종료되지 않은 나머지 태스크는 종료시킨다. 이러한 기능을 추론적 실행이라고 부른다.

기본적으로 추론적 실행은 true로 설정되어 있으며, 맵과 리듀서에 대해 개별적으로 설정할 수 있다.

  • mapred.map.tasks.speculative.execution : 매퍼에 대한 설정
  • mapred.reduce.tasks.speculative.execution : 리듀서에 대한 설정

태스크가 모두 완료되어야만 Job이 완료되므로, 특정 태스크가 느리게 처리되는 경우 Job의 전체 실행시간 또한 느려지게된다. 따라서 추론적 실행을 사용하면, 특정 상황에 종속되지 않고 Job의 실행시간이 평균적으로 빠르게 처리된다는 점을 보장한다.

하지만 일반적으로는 추론적 실행을 사용하지 않는다. 이는 태스크가 멱등성을 가지지 않는 경우가 많기 때문이다. 예를 들어 분석 결과를 DB에 저장하는 경우, 추론적 실행을 사용하면 동일한 데이터가 DB에 저장되므로 중복 저장되는 결과를 낳게 된다. 따라서 실행하려는 Job의 특성에 따라 추론적 실행을 설정해야 한다.

DB와의 상호작용

맵리듀스 프로그램의 입력 데이터가 DB에 있거나, 맵리듀스의 결과를 DB에 저장해야 하는 경우가 있을 수 있다. 이 중에서 입력 데이터를 DB에서 가져와야 하는 경우에는 매퍼에서 DB 커넥션을 얻어서 처리하는 방식은 지양해야 한다. 입력 데이터가 많은 경우, 매퍼의 개수는 그에 맞게 늘어나게 되며 결국 DB 커넥션의 수도 그만큼 증가하게 된다. 이 경우 DB에 과부하가 걸리며 전체 Job의 실행 속도가 늦어지거나, DB 자체에 장애가 발생할 수도 있다.

따라서 입력 데이터를 DB로부터 읽어야하는 경우에는 드라이버 클래스에서 DB 커넥션을 하나만 얻어와서 입력 데이터를 파일로 만든 후, 각 맵에서는 해당 파일을 사용하도록 구현해야 한다.

반대로 맵 리듀스의 결과를 DB에 저장해야하는 경우에는 각 리듀서에서 DB 커넥션을 얻어와서 처리할 수 있다. 리듀스는 매퍼에 비해 동시에 실행되는 수가 아주 적으므로, DB 커넥션 또한 많이 사용하지 않기 때문이다. 하둡에서는 이를 위해 DBOutputFormat 클래스를 제공한다.

DBOutputFormat

먼저 드라이버에서 출력 포맷을 DBOutputFormat으로 설정한 후, DB 커넥션 정보는 DBConfiguration 클래스의 configureDB() 메서드를 활용해서 설정한다. 그리고 출력할 데이터를 DBOutputFormat 클래스의 setOutput() 메서드를 활용해서 설정한다.


conf.setOutputFormat(DBOutputFormat.class);
DBConfiguration.configureDB(job, drvierClass, dbUrl, userId, userPass);
DBOutputFormat.setOutput(job, tableName, col1, col2, col3, ...);

태스크 개수 할당 규칙

태스크트래커는 동시에 실행할 수 있는 맵과 리듀서의 최대 개수를 설정할 수 있으며, 기본값은 4개다(맵 2개, 리듀서 2개). 일반적으로 코어 하나당 2개의 태스크를 할당한다. 따라서 쿼드 코어라면 총 6개의 태스크를 설정할 수 있다(맵 3개, 태스크 3개). 태스크 트래커와 데이터노드가 각각 하나의 태스크를 사용하므로, 결국 총 8개의 태스크가 실행되게 된다. 이 경우 전제사항은 해당 태스크에서 CPU를 많이 사용하기 보다는 I/O 작업이 많은 경우다. 만약 CPU 사용이 많은 태스크라면, 설정은 조금 낮추어야 한다.

동시에 실행할 맵과 리듀서의 개수는 각 Job별로 설정할 수도 있다. 맵의 경우 입력 데이터의 크기에 따라 자동으로 설정되며, 리듀서의 개수는 직접 설정이 가능하다. 별도로 설정하지 않는 경우 각 Job별로 하나의 reduce 태스크만 할당된다. 일반적으로 클러스터에서 리듀서 태스크트래커 최대 개수에 0.95 또는 1.75를 곱한 값을 사용한다.

fsck

하둡은 파일 시스템의 상태를 검사할 수 있는 fsck 유틸리티를 제공한다.

bin/hadoop fsck /path/to/directory

fsck

over-replicated 블록 또는 under-replicated 블록의 경우, 시간이 지나면 자동으로 정상으로 돌아온다. 하지만 mis-replicated 블록, corrupt 블록, missing replicas의 경우에는 자동으로 복구가 불가능하다. 이 경우 문제가 된 블록을 삭제하려면, fsck 명령어에 -delete 옵션을 사용한다. 또는 -move 옵션을 사용해 일단 /lost-found로 옮긴 후, 이후에 복구작업을 수행할 수도 있다.

권한 관리

하둡은 일반적으로 하둡이 실행된 시스템의 사용자 권한 체계를 그대로 따른다. 주의할 점은 하둡을 실행한 사용자는 하둡 파일 시스템에서 슈퍼유저로 분류된다. 만약 슈퍼유저 권한을 또다른 사용자에게 주고자한다면, dfs.permissions.supergroup 파라미터를 설정한다.

데이터노드 제거 및 추가

특정 데이터노드가 장애가 난 경우, 단순히 노드를 클러스터에서 네트워크 연결을 끊더라도 HDFS은 정상적으로 운영된다. 그리고 장애가 난 데이터노드에 있던 블록을 복제 계수를 맞추기 위해 자동으로 복제작업을 시작한다. 하지만 좀더 안정적으로 제거하고자 한다면 디커미션(decommsion) 기능을 사용해야 한다.

먼저 네임노드가 설치된 노드에서 dfs.hosts.exclude 파일에 제거할 노드를 “호스트명 IP” 형태로 추가한다. 그런후 네임노드에서 아래의 명령어를 실행한다.

bin/hadoop dfsadmin -refreshNodes

그러면 네임노드는 디커미션 작업을 실행하여 해당 노드를 제거하게 된다.

데이터노드 추가 작업 또한 간단하다. 단순히 데이터노드 데몬과 태스크트래커 데몬을 실행하면 된다. 네임노드가 재실행될 경우를 대비해서, 네임노드의 conf/slaves 파일에 추가할 데이터노드를 추가하는 것도 잊지 말자.

이보다 중요한 작업은 데이터를 옮기는 일이다. 추가된 새로운 노드에는 디스크가 비어 있으므로, 기존의 데이터노드에서 블록을 복제하여 새로운 노드에 복사해야 한다. 이를 위해 밸런서를 실행해야 한다.

bin/start-balancer.sh

위의 명령어는 백그라운드에서 실행되며, 데이터 분포가 균형을 이룰때까지 실행된다.

클러스터가 균형을 이룬다는 말은 각 데이터노드의 사용률이 시스템의 평균 사용률의 임계치 내에 들어왔다는 말이다 일반적으로 임계치는 10%이다. 밸런서를 실행할 때, 임계치를 설정할 수 있다. 아래는 임계치를 5%로 설정하여 밸런서를 실행하는 경우다.

bin/start-balancer.sh -threshold 5

네임노드와 세컨데리 네임노드

네임노드는 시스템의 메타데이터와 블록에 대한 위치정보를 메모리에 유지한다. 네임노드는 드라이브 문제로 인한 데이터 손실을 막기 위해서 RAID 드라이브를 사용해야 한다.

네임노드는 HDFS에 저장된 파일의 블록에 대한 매핑정보를 메모리에 유지하므로, 블록이 많아질수록 성능이 느려질 수 있다. 따라서 블록 사이즈를 크게 하면 매핑정보가 그만큼 줄어들게 된다. 반면에 블록 사이즈가 커지면, 맵리듀스에서 병렬적으로 처리할 수 있는 태스크의 개수가 작아지므로, 입력 파일의 사이즈가 작은 경우 Job 프로그램의 실행속도가 느려질 수도 있다.

기본적으로 블록 사이즈는 64MB이며, 경우에 따라 128MB로 설정하기도 한다. 블록 사이즈는 dfs.block.size 속성을 설정하면 된다.

세컨데리 네임노드로 구동할 노드는 반드시 네임노드와 동일한 하드웨어 스펙을 사용해야 한다. 이를 이해하려면 네임노드와 세컨데리 네임노드 사이의 관계에 대해 이해하여야 한다.

secodary-name-node

네임노드는 fsimage와 edlits 로그 파일을 이용하여 파일 시스템의 상태정보를 관리한다. fsimage 파일은 파일 시스템 전체의 특정 시점에 대한 스냅샷이며, edits 로그 파일은 해당 시점 이후의 변경사항을 기록한 로그 파일이다. 따라서 두 개의 파일을 이용하면 파일 시스템의 현재 상태를 정확히 알 수 있다.

네임노드가 구동될 때 네임노드는 두 파일을 병합하여 현재 시점에 대한 스냅샷을 fsimage 파일에 기록한다. 그리고 비어 있는 edits 로그 파일이 만들어진다. 이후의 변경사항은 모두 edits 로그 파일에 기록된다. 네임노드는 파일 시스템의 상태 정보에 대한 복사본(fsimage와 edits 로그 파일이 병합된 정보)을 메모리에 상주시켜, 클라이언트 요청에 빠르게 응답한다.

이때 문제는 edits 로그 파일이 너무 커질 경우, 네임노드를 재기동할 때 병합하는 시간이 오래 걸리므로 재기동하는 시간 또한 오래 걸리게 된다. 이를 위해 fsimage 파일과 edits 로그 파일을 병합하는 작업을 주기적으로 실행하게 된다. 네임노드의 경우 파일 시스템 전체에 대한 마스터 역할을 수행하므로, 병합 작업과 같이 자원을 많이 차지하는 작업은 별도의 노드에서 수행하게 되는데, 바로 이 서버가 세컨데리 네임 노드다. 즉 세컨데리 네임노드는 네임 노드로부터 fsimage 파일과 edits 로그 파일을 fs.checkpoint.dir 디렉토리로 주기적으로 가져와서 병합하여, 해당 시점의 스냅샷을 기록한다. 그리고 병합된 fsimage 파일은 다시 네임노드로 복사하며, 네임노드는 병합된 fsimage 파일을 저장하게 된다.

따라서 세컨데리 네임노드는 백업용 노드가 아닌, 현재 상태의 파일 시스템 정보를 기록하기 위한 체크포인트 서버로 이해해야 한다. 결국 세컨데리 네임노드는 네임 노드와 동일한 파일을 관리해야 하므로 최소한 동일한 크기의 메모리를 보장받아야 한다.

네임노드 장애복구

현재 네임노드는 이중화 구성을 할 수 없다. 네임노드는 하둡 파일 시스템에서 단일 장애지점에 해당한다. 일반적으로 세컨데리 네임노드를 백업용 노드로 활용하여, 네임노드에 장애가 발생했을 때를 대비한다. 물론 장애가 발생한다고 해서 세컨데리 네임노드가 자동으로 네임노드의 역할을 수행하는 것은 아니며, 약간의 수고를 들여야한다. 하지만 데이터는 손실되지 않도록 보장할 수 있게 된다.

다수의 디스크를 활용한 장애복구

네임노드에 디스크가 2개 이상인 경우, dfs.name.dir 속성에 해당 디스크를 모두 설정한다. 따라서 첫번째 디스크에 장애가 발생한다면, 네임노드는 두번째로 지정된 디스크를 자동으로 사용하게 된다.

세컨데리 네임노드를 활용한 장애복구

디스크 뿐만 아니라 네임노드가 설치된 머신 자체가 장애가 난 경우를 대비해서 세컨데리 네임노드를 백업 노드로 활용할 수 있다. 마찬가지로 dfs.name.dir 속성에 세컨데리 네임노드의 디스크를 추가한다. 그리고 네임노드에 장애가 발생한 경우, 세컨데리 네임노드의 IP의 네임노드의 IP로 변경한 후 네임노드를 재실행한다. 각 데이터 노드는 IP를 기반으로 네임노드와 통신하므로 반드시 IP를 변경해야만 한다.

네임노드 이중화

현재 네임노드를 Active / Stand-by 형태로 이중화할 수 있는 작업이 진행중인 듯하다.

Job 스케쥴링

기본적으로 하둡은 FIFO 스케쥴러를 사용한다. 즉 모든 Job은 실행된 순서대로 차례차례 실행되게 된다. 따라서 한번에 한개의 Job만을 실행할 수 있다.

하지만 경우에 따라 2개 이상의 Job을 동시에 실행하길 원할 수도 있다. 이를 위해 페이스북에서는 Fair 스케쥴러를, 야후에서는 Capacity 스케쥴러를 구현했다.

페어(Fair) 스케쥴러

페어 스케쥴러에서 중요한 개념은 태스크 풀(task pool)이다. 각 Job은 모두 특정 풀에 속하며, 각 풀은 모두 최소한의 맵과 리듀스 개수를 보장받는다. 또한 Job마다 우선순위를 설정할 수 있으며, 우순 선위가 더 높은 Job은 더 많은 태스크 개수를 할당받는다.

페어 스케쥴러를 사용하려면 설정파일을 아래와 같이 수정해야 한다.


<property>
    <name>mapred.jobtracker.taskScheduler</name>
    <value>org.apache.hadoop.mapred.FairScheduler</value>

</property>
<property>
    <name>mapred.fairscheduler.allocation.file</name>
    <value>HADOOP_CONF_DIR/pools.xml</value>

</property>
<property>
    <name>mapred.fairscheduler.assignmultiple</name>
    <value>true</value>

</property>
<property>
    <name>mapred.fairscheduler.poolnameproperty</name>
    <value>pool.name</value>

</property>
<property>
    <name>pool.name</name>
    <value>${user.name}</value>

</property>

위의 설정 파일을 좀더 자세히 살펴보자. 먼저 사용할 스케쥴러의 클래스명을 수정한다.

<property>
    <name>mapred.jobtracker.taskScheduler</name>
    <value>org.apache.hadoop.mapred.FairScheduler</value>
</property>

태스크 풀에 대한 설정정보를 저장한 파일의 경로를 설정한다.

<property>
    <name>mapred.fairscheduler.allocation.file</name>
    <value>HADOOP_CONF_DIR/pools.xml</value>
</property>

Job이 실행될 때 어느 풀을 사용할지 결정하는 기준을 설정한다. 여기에서는 pool.name, 즉 풀의 이름을 기준으로 선택하도록 설정한다.

<property>
    <name>mapred.fairscheduler.poolnameproperty</name>
    <value>pool.name</value>
</property>

pool.name의 기본값을 ${user.name}으로 설정하면, job을 실행한 사용자 계정을 기준으로 사용할 풀을 선택하게 된다.

아래는 풀에 대한 설정을 담고 있는 pools.xml 파일이다.

<?xml version="1.0">
<allocations>
    <pool name="hadoop">
        <minMaps>2</minMaps>
        <minReduces>2</minReduces>
    </pool>

    <pool name="hive">
        <minMaps>2</minMaps>
        <minReduces>2</minReduces>
        <maxRunningJobs>2</maxRunningJobs>
    </pool>
    <user name="bulk">
        <maxRunningJobs>5</maxRunningJobs>
    </user>
    <userMaJobsDefault>3</userMaxJobsDefault>
</allocations>

pools.xml 파일은 매 15초마다 자동으로 다시 읽히므로, 변경사항은 바로바로 반영되게 된다. 이 파일에 정의되지 않은 pool은 최소한의 태스크 수를 보장받지 못하게 된다.

페어 스케쥴러를 사용한 경우 최대 이점은 웹 모니터링 UI 화면이다. http://masternode:50030/scheduler 로 접속해보면, 페어스케쥴러에서만 사용할 수 있는 기능을 확인할 수 있다.

hadoop_mapreduce_fair_scheduler

웹 UI 상에서 풀에 대한 정보 및, Job의 우선순위를 바꿀수도 있다.

 

크몽 재능

[번역 후기] 고객과 함께 하는 도메인 특화 언어 DSL

이 글은 크몽 재능인, socurites님이 원고를 기고하셨습니다.

드디어 나왔습니다.

“고객과 함께 하는 도메인 특화 언어 DSL”

dsl_face

번역을 부탁 받고, 처음으로 원서를 접했을 때의 느낌은 두려움이었습니다. 두께도 두껍거니와, 내용 자체도 상당히 어려웠기 때문입니다.

지금까지는 접하지 못했던 새로운 내용들과,  현장에서는 거의 쓰이지 못하던 기술들 때문에 번역은 커녕 내용을 이해하는데도 거의 3달이 넘게 걸렸습니다.

그렇게 책을 다 읽은 후, 초벌 번역을 진행하면서 두번째로 읽었을 때의 느낌은 놀라움이었습니다.

아! 이런 일도 가능하구나!
이렇게 쉬운 방법이 있구나!

한 장, 한장을 넘기면서 새로운 패러다임에 눈을 뜨기 시작했습니다.

초벌 번역을 다듬으면서 세번째로 읽었을 때의 느낌은 막막함이었습니다. 내용은 신선하지만, 과연 어떤 분야에 적용할 수 있을지, DSL을 적용하기에는 한글과 영어의 격차가 너무 큰 건 아닌지, 과연 현실성이 있는지 조금도 확신할 수 없었습니다.

퇴고를 하면서 네번쨰로 읽었을 때의 느낌은 즐거움이었습니다.
웹로그를 파싱하면서, ANTLR 파서 생성기를 실제로 적용해보면서
이전에는 시도하지도 못했던 일들을 해낼 수 있었습니다.
이를 통해 프로그래밍의 즐거움을 새삼 느낄 수 있었습니다.

이 책을 접하는 독자분들도 저와 같은 감정 기복을 분명히 느낄 것입니다. 또한 저와 같은 시행 착오를 이겨낸다면, 이 책은 독자 여러분에게 프로그래밍에 대한 새로운 시각을 열어줄 것입니다.

크몽 재능

jsch 라이브러리를 사용해서 sftp / ssh 클라이언트 구현하기

이 글은 크몽 재능인, socurites님이 원고를 기고하셨습니다.

apache-commons에 있는 ftp/telnet 라이브러리로는 sftp/ssh 클라이언트를 구현할 수 없다. jsch 라이브러리를 사용하면, sftp/ssh 클라이언트를 직접 구현할 수 있다.

sftp 클라이언트 구현하기

sftp 접속하기 예제

</pre>
private void connect() {
 System.out.println("==> Connecting to " + host);
 Session session = null;
 Channel channel = null;

 // 1. JSch 객체를 생성한다.
 JSch jsch = new JSch();
 try {
 // 2. 세션 객체를 생성한다(사용자 이름, 접속할 호스트, 포트를 인자로 전달한다.)
 session = jsch.getSession(username, host, port);

 // 3. 패스워드를 설정한다.
 session.setPassword(password);

 // 4. 세션과 관련된 정보를 설정한다.
 java.util.Properties config = new java.util.Properties();
 // 4-1. 호스트 정보를 검사하지 않는다.
 config.put("StrictHostKeyChecking", "no");
 session.setConfig(config);

 // 5. 접속한다.
 session.connect();

 // 6. sftp 채널을 연다.
 channel = session.openChannel("sftp");

 // 7. 채널에 연결한다.
 channel.connect();
 } catch (JSchException e) {
 e.printStackTrace();
 }

 // 8. 채널을 FTP용 채널 객체로 캐스팅한다.
 channelSftp = (ChannelSftp) channel;
 System.out.println("==> Connected to " + host);
}

sftp 업로드하기 예제

public void upload(String catalinaHome, File file) throws SocketException, IOException {
    // 앞서 만든 접속 메서드를 사용해 접속한다.
    connect();
    System.out.println("==> Uploading: " + file.getPath() + " at " + host);
    FileInputStream in = null;
    try {
        // 입력 파일을 가져온다.
        in = new FileInputStream(file);

        // 업로드하려는 위치르 디렉토리를 변경한다.
        channelSftp.cd(catalinaHome + "/webapps");

        // 파일을 업로드한다.
        channelSftp.put(in, file.getName());
    } catch (SftpException e) {
        e.printStackTrace();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        try {
            // 업로드된 파일을 닫는다.
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    System.out.println("==> Uploaded: " + file.getPath() + " at " + host);
}

ssh 클라이언트 구현하기

ssh 접속하기  예제

private void connect() throws Exception {
    System.out.println("==> Connecting to" + host);
    Session session = null;

    // 1. JSch 객체를 생성한다.
    JSch jsch = new JSch();

    // 2. 세션 객체를 생성한다 (사용자 이름, 접속할 호스트, 포트를 인자로 준다.)
    session = jsch.getSession(username, host, port);

    // 3. 패스워드를 설정한다.
    session.setPassword(password);

    // 4. 세션과 관련된 정보를 설정한다.
    java.util.Properties config = new java.util.Properties();
    // 4-1. 호스트 정보를 검사하지 않는다.
    config.put("StrictHostKeyChecking", "no");
    session.setConfig(config);

    // 5. 접속한다.
    session.connect();

    // 6. sftp 채널을 연다.
    Channel channel = session.openChannel("exec");

    // 8. 채널을 SSH용 채널 객체로 캐스팅한다
    channelExec = (ChannelExec) channel;

    System.out.println("==> Connected to" + host);

}

명령어 실행하기 예제

public void stopTomcat() throws Exception {
    // 앞서 만든 접속 메서드를 사용해 접속한다.
    connect();

    System.out.println("==> Stopping Tomcat @" + host);
    // 실행할 명령어를 설정한다.
    channelExec.setCommand(catalinaHome + "/bin/shutdown.sh");

    // 명령어를 실행한다.
    channelExec.connect();

    System.out.println("==> Stopped Tomcat @" + host);
}

크몽 재능

Envers를 사용해 스프링에서 하이버네이트 엔티티 이력 관리하기

이 글은 크몽 재능인, socurites님이 원고를 기고하셨습니다.

웹 어플리케이션에서 특정 도메인 엔티티는 이력관리를 해야할 때가 있다. 예를 들어 상품 시스템이라면, 상품이 생성되고 변경되며, 삭제된 이력을 관리해야 한다. 이때, 이력 관리 기능을 직접 구현할 수도 있지만, Envers를 사용하면 해당 엔티티가 변경될 때마다 자동으로 이력을 남기도록 만들 수 있다.

이 글에서는 하이버네이트(hibernate)의 hbm 매핑 파일이 아니라, 어노테이션(annotation) 기반으로 매핑을 하고 여기에 이력관리 기능을 추가하는 과정을 설명한다.

라이브러리

스프링(Spring)을 적용한 프로젝트에 아래와 같은 라이브러리가 추가적으로 필요하다.

  • hibernate-annotations : 3.5 이상
  • hibernate-core : 3.5 이상
  • slf4j-log4j12
  • javaassist
  • cglib
  • hibernate-envers : 3.5 이상

스프링 환경설정하기

sessionFactory 빈을 설정한 부분을 아래와 같이 설정한다. 각 값은 자신의 환경에 맞게 수정한다.

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://33.33.108.108:3306/dbtest" />
    <property name="username" value="test" />
    <property name="password" value="test00" />
    <property name="maxActive" value="1" />
    <property name="maxIdle" value="1" />
</bean>

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="annotatedClasses">
        <list>
            <value>com.sds.secu.ccams.user.domain.Manager</value>
            <value>com.sds.secu.ccams.code.domain.Code</value>
            <value>com.sds.secu.ccams.code.domain.CodeGroup</value>
        </list>
    </property>
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
            <prop key="hibernate.show_sql">true</prop>
            <prop key="hibernate.hbm2ddl.auto">update</prop>

        </props>
     </property>
     <property name="eventListeners">
         <map>
             <entry key="post-insert" value-ref="enversListener" />
             <entry key="post-update" value-ref="enversListener" />
             <entry key="post-delete" value-ref="enversListener" />
             <entry key="pre-collection-update" value-ref="enversListener" />
             <entry key="pre-collection-remove" value-ref="enversListener" />
             <entry key="post-collection-recreate" value-ref="enversListener" />

         </map>
     </property>
</bean>

<bean id = "enversListener" class="org.hibernate.envers.event.AuditEventListener" />

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

이 설정에서 주의 깊게 봐야할 부분은 자동으로 이력을 남기도록 이벤트 리스너를 등록하는 부분이다.

엔티티 설정하기

엔티티 클래스에는 @Audited를 붙인다.

@Entity
@Table(name = "CTRL_URL")
@Audited
public class CtrlUrl implements Serializable {
    private static final long serialVersionUID = 8655803398383521275L;
    @Id
    @Column(name = "CTRL_URL_ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @ManyToOne
    @JoinColumn(name = "LOC_GB_CD")
    @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
    private Code locGbCd; /* 위치구분 코드 */

이때 연관 관계가 있는 프로퍼티의 경우, 해당 연관 클래스도 반드시 @Audited를 붙여서 이력관리 대상이 되도록 만들어야 한다. 만약 연관 클래스를 이력 대상에서 제외하고자 한다면, 해당 프로퍼티를 반드시 @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED) 로 설정해야 한다.

테스트하기

이와 같이 설정한 후, 엔티티 객체를 추가하면, 매핑된 클래스인 CTRL_URL 테이블 뿐만 아니라, 이력 정보를 담고 잇는 CTRL_URL_AUD 테이블이 함께 생성되며 이력 정보가 추가된다.

이력정보를 담은 테이블에서 REVTYPE이 엔티티의 변경상태이며, 1(추가), 2(수정), 3(삭제)로 구분된다.

참고자료

Hibernate Envers – Easy Entity Auditing

크몽 재능

 

리눅스에서 mysql 사용하기

이 글은 크몽 재능인, socurites님이 원고를 기고하셨습니다.

설치하기

mysql 서버와 클라이언트를 설치한다

$ sudo apt-get install mysql-server mysql-client

설치하는 중 root 사용자 패스워드를 입력하라는 화면이 나오면, 사용할 root 패스워드를 입력한다.

설치가 끝나면, mysql 설정파일(/etc/mysql/my.cnf)에서 bind-address 부분을 주석처리한다.

# bind-address = 127.0.0.1

데이터베이스 생성하기

create 명령어를 사용해서 데이터베이스를 생성한다. 여기에서는 springbook이라는 이름으로 데이터베이스를 생성한다.

$ mysqladmin -u root create springbook -p

데이터베이스에 접속하기

앞에서 생성한 데이터베이스에 접속한다.

$ mysql -u root -p springbook

현재 만들어진 테이블을 살펴본다. 아직 테이블을 만드지 않았으므로 아무런 테이블도 표시되지 않는다.

mysql> show tables;
Empty set (0.000 sec)

캐릭터 셋(Character Set) 설정하기

테이블을 생성하기에 앞서 클라이언트, 서버, 데이터베이스 등의 캐릭터 셋을 설정해야 한다.

지원되는 캐릭터 셋 확인하기

현재 머신에 설치된 mysql에서 사용가능한 Charater Set을 확인한다.

mysql> show character set;

현재 설정된 캐릭터 셋 확인하기

현재 설치된 캐릭터 셋을 확인한다.

mysql> show variables like 'char%';

mysql_charaterset

결과에서 보는 바와 같이, client, connection, database, result, server의 캐릭터 셋이 기본적으로 latin1로 되어 있음을 확인할 수 있다.

utf8로 캐릭터 셋 변경하기

캐릭터 셋을 변경하는 방법에는 여러가지가 있으나, mysql 데몬이 재시작된 후에도 캐릭터 셋 정보를 그대로 유지하려면 설정파일(my.cnf)을 편집해야 한다. 아래와 같이 utf8로 된 부분을 [client], [mysqld], [mysql] 영역에 추가한다.

port    = 3306
socket    = /var/run/mysqld/mysqld.sock

# character set to utf8 #####################################
default-character-set=utf8
###################################################
.....

[mysqld]
# character set to utf8 #####################################
init_connect=SET collation_connection = utf8_general_ci
init_connect=SET NAMES utf8
default-character-set=utf8
character-set-server=utf8
collation-server=utf8_general_ci
###################################################
....

[mysql]
# character set to utf8 ###################################
default-character-set=utf8
###################################################

mysql 서버 데몬을 재시작한다.

$ sudo /etc/init.d/mysql restart

springbook 데이터베이스에 새로 접속해 보면, 아래와 같이 캐릭터 셋이 utf8로 모두 변경되었음을 확인할 수 있다.

mysql_characterset_utf8

테이블 생성하기

create table 문을 사용해서 새로운 테이블을 생성한다.

mysql> create table users (
id varchar(10) primary key,
name varchar(20) not null,
password varchar(10) not null
);

show tables 명령을 실행해보면 users 테이블이 생성되었음을 확인할 수 있다.

사용자 추가하기

사용자는 GRANT 명령어를 사용해서 추가할 수 있다. 여기에서는 아이디가 “spring”이고, 패스워드가 “book”인 사용자를 추가하고, 동시에 springbook 데이터베이스에 대한 권한을 모두 주고자 한다.

mysql> GRANT ALL PRIVILEGES ON springbook.* TO spring@localhost IDENTIFIED BY 'book' WITH GRANT OPTION;

데이터베이스를 mysql로 이동한다.

mysql> use mysql;

이제 user 테이블을 조회해보면 방금 등록한 spring 사용자가 등록되었음을 확인할 수 있다. root 계정도 함께 보이며, 패스워드는 암호화되어 저장되므로 조회 결과가 순식간에 스크롤된다.

mysql> select * from user;

크몽 재능

트렐로(Trello)를 사용해 온라인으로 스크럼(Scrum) 하기

이 글은 크몽 재능인, socurites님이 원고를 기고하셨습니다.

지금까지는 현황판을 사용해서 스크럼을 해 왔는데, 거리상의 이유로 오프라인으로 스크럼을 하기 힘든 팀원들이 생겼다. 프로젝트 현황판을 통해 데일리 스크럼을 하다

그래서 온라인으로 사용할 수 있는 현황판을 지인에게 추천받았는데, 그 중 하나가 바로 트렐로(Trello)다.

이 글에서는 내가 트렐로를 사용한 경험을 간단하게 정리하였다.

회원가입

먼저 트렐로 사이트에 접속한다. 구글 계정이 있다면 따로 계정을 등록할 필요 없이 구글 계정으로 간편히 회원가입할 수 있다.

첫 로그인 후 할 일

처음 사용한다면 2가지 작업을 먼저 해두면 쓸모가 있다.

먼저 [Account] 메뉴에서, Full Name과 Initials를 자신을 알아볼 수 있는 이름으로 수정해두면 좋다. 각 Board별로 멤버를 사진만으로 구분하기 힘들다. 따라서 이름을 한글로 설정해두면, 사진의 툴팁으로 누가 누구인지 쉽게 구분할 수 있게 된다.

그리고 사진을 등록해야 한다. 트렐로 사이트에서 사진을 직접 등록하는 게 아니라 Gravatar 사이트에서 등록한 사진을 풀링해오는 방식이다. [Account] 메뉴에 접속하면 gravatar.사이트가 링크되어 있으므로, 접속해서 로그인한 후 원하는 사진을 등록한다. Gravatar에서 등록한 사진이 트렐로 사이트에 반영되려면 10분~20분 정도가 걸린다.

Organization 만들기

프로젝트를 소유할 Organization을 우선 만든다.

Board 만들기

앞에서 만든 Organization에서 사용할 보드(Board)를 만든다. 보드는 오프라인에서 만들었던 프로젝트 현황판과 동일한 개념이다. Board 이름을 입력하고 생성을 하면, 아래 그림과 같은 기본 Board가 만들어진다.

트렐로 보드

멤버 추가하기

[Add Members] 메뉴로 프로젝트를 함께 꾸려갈 멤버를 추가한다. 사실 나는 보드를 먼저 만들기 전에, Organization을 만든 후 Organization에 멤버를 추가했다. 그러고 나서 보드를 만들었는데, Organization의 멤버가 해당 보드로 상속되지는 않았다. 그래서 멤버를 따로 추가했다.(Organization에 등록된 멤버를 바로 추가할 수 있는 기능이 있는지 더 찾아봐야 할 듯 하다). 멤버를 추가하면 해당 멤버가 바로 등록되는게 아니라, 메일로 초청 메일이 전달된다. 초청메일을 받은 사용자가 트렐로 사이트에 접속해서 초청을 수락해야만 보드의 멤버가 되는 구조다.

구조 변경하기

기본적으로 만들어진 구조를 자신의 프로젝트에 맞게 변경할 수 있다. 여기에서는 아래와 같이 수정했다. 최상위의 항목은 List라고 부른다. 기존 프로젝트 현황판에서 스토리를 상태에 따라 나누는 분류정도로 이해하면 된다. 나는 기존 항목을 [Product Backlog], [To Do], [On Going]으로 수정했다. 그리고 여기에 [Finish] 리스트를 하나 추가했다. 리스트 추가는 [Add List] 버튼을 이용한다.

board_new

스토리 카드 추가하기

스토리 카드는 각 리스트에서 [Add Card]를 선택해서 추가할 수 있다.

태스크 추가하기

각 스토리 카드에는 태스크에 해당하는 체크리스트를 추가할 수 있다. 앞에서 추가한 카드를 클릭하면 각 카드의 상세 정보를 설정할 수 있다. 여기에서는 [Add checklist]를 클릭해서 체크리스트를 하나 추가한 후, 태스크를 등록했다. 태스크로는 “형상서버 설치하기”와 “hudson 설치하기”를 등록했다.

트렐로-사용자 스토리에 태스크 추가하기

라벨 사용하기

라벨은 태그와 같은 것으로, 각 스토리 카드마다 라벨을 설정할 수 있다. 기본적으로 라벨에는 이름이 부여되어 있지 않으므로, 프로젝트에 맞게 라벨에 이름을 부여한 후 사용한다. 아쉬운 점은 라벨은 총 6개만 제공되며 더이상 추가할 수 없다는 점이다.

부족한 점

사용법은 생각하는대로 움직이므로 충분히 쉽게 사용할 수 있다. 하지만 가장 아쉬운 점은 스토리 포인트를 부여할 수 없다는 점이다. 그래서 프로젝트의 현재 속도를 측정할 수가 없다. 바라건대, 각 스토리마다 포인트를 부여할 수 있게 하고, 현재 시점에 총 스토리 포인트 중에서 얼마나 진행했는지 주기적으로 볼 수 있는 소멸 그래프가 추가되었으면 한다.

크몽 재능