본문 바로가기
프로그래밍/Erlang

얼랭(erlang)으로 구현하는 다중서버 기반의 분산처리 플랫폼

by o테리o 2013. 8. 26.

얼랭(erlang)으로 구현하는 다중서버 기반의 분산처리 플랫폼 

 

최근 대용량 데이터를 처리하는 플랫폼이 큰 관심을 끌고 있고 중심에는 hadoop이 있다. 또한 hadoop 내부 알고리즘인 map/reduce 알고리즘은 분산처리 플랫폼을 언급할 때 빠지지 않고 나오는 효율적인 분산 프로그래밍 모델이다. 스페셜 2부에서는 얼랭이 map/reduce 분산 프로그래밍 모델과 얼마나 궁합이 잘 맞는지 알아본다. 


map/reduce 란 무엇인가?

최근 몇 년간 구글의 면접에서 가장 흔하게 나온 질문이다.

수 백 메가의 데이터를 소팅(sorting)하려고 하는데 당신의 컴퓨터 사양은 아쉽게도 램이 64메가에 디스크는 딱 데이터를 담을 정도인 수 백 메가 밖에 없습니다. 하지만 운 좋게도 당신에게는 네트워크로 연결된 비슷한 사양의 컴퓨터 n대가 있습니다. 어떻게 하면 데이터 소팅을 효율적으로 할 수 있을까요?

구글이 이 같은 문제를 내는 이유는 해당 엔지니어는 항상 대용량 데이터를 잘 다뤄야 된다는 전재가 깔려 있기 때문이다. 당장 해결 방법을 생각하기 힘든 문제일지 모르겠지만 다행스럽게도 위 문제의 답은 앞으로 설명할 map/reduce와 크게 관련이 있다. 위 문제의 가장 큰 문제는 데이터가 있는 pc에서 메모리나 디스크의 부족 때문에 단독으로 소팅하는 게 어렵다. 문제에 힌트가 있는데, 다수의 컴퓨터가 네트워크로 연결돼 있다는 게 바로 힌트다(이 힌트를 무시하고 파일 기반 분산 소팅을 한다고 우긴다면 100점을 받기는 힘들 것이다).

그렇다! 데이터를 n대의 컴퓨터에 적절하게 분산시켜(꽤 괜찮은 hash 알고리즘을 써야겠지만) 각 pc에서 소팅을 한 후 다시 내 pc로 가져올 때, n대 pc의 소팅된 데이터 중 가장 큰 값들을 비교해 순서대로 가져오면 된다. map/reduce 알고리즘에 익숙하다면 이런 방법은 쉽게 풀 수가 있다. 이 프로그래밍 모델이 데이터를 잘게 잘라 분산 시켜서 각자 프로세싱을 한 후 그 데이터를 다시 조합(merge)하는 과정을 거치기 때문이다.


<그림1>map/reduce(출처:http://hadoop.apache.org)

 

이걸 컴퓨터 클러스터의 관점에서 보면 <그림 1>과 같다. 데이터를 컴퓨터 클러스터에 있는 서버에 분산시키고 map 함수를 이용해 잘게 자른 데이터를 쌍으로 만든다. 그리고 그 처리된 데이터들에서 같은 key를 가진 데이터를 누적해서 value에 대한 작업을 해주면 된다. 여기서 누적해서 value값들에 대한 작업을 해주는 역할을 reduce가 해준다. 독자들은 이게 소팅하고 무슨 관계가 있냐고 반문할지도 모르지만 매우 관계가 많다. map작업을 하고 나온 데이터들이 수많은 노드에 분산 있는데, 이들을 같은 key값으로 누적시켜 주는 reduce를 태우기 전 각 노드에서 같은 key를 가지는 데이터를 모두 가져와야 한다.

key를 찾으려면 각 노드가 소팅돼 있어야 시간이 덜 걸린다. 그래서 각 노드별로 key를 기반으로 소팅 작업이 이뤄진다. 만일 소팅되지 않았다면 각 노드별로 최악의 상황에는 데이터 개수만큼의 서치(search)가 이루어 질 것이다. 예를 들어 각 노드가 내림차순 정렬로 소팅돼 있다면 각 노드에서 가장 큰 값을 팝(pop)을 해와 뽑아온 데이터들 간에 소팅한 후 가장 큰 키를 찾아서 reduce를 태우는 과정을 거치면 여타 다른 노드에서 해당 key의 데이터가 없다는 걸 보장 받는다. 만약 구글 문제의 답을 map/reduce로 구한다면 map에서 쌍으로 만들어 주고 내부 노드에서 소팅할 때 원하는 소팅 방법으로 소팅 후 그대로 reduce 함수에서 map에서 출력된 값을 출력해 주면 된다(hadoop에서 이런 방식으로 소팅이 가능하다). 위 원리는 후에 소개할 예제 프로그램에서도 중요한 원리로 작용하니 반드시 이해하기 바란다. map/reduce 프로그래밍 모델 설명이 나올 때 가장 단골 메뉴로 나오는 word count 예제를 소개한다. word count 예제는 필자가 후에 소개할 프로그램의 테스트로도 쓰인다(<그림 2> 참조).

 

<그림 2>가 쉽게 도식화 돼 있어서 이해하기 쉬울 것이다. map/reduce 프로그래밍 모델의 효율성은 위의 작업을 수 테라바이트의 데이터를 갖고 카운팅 한다고 가정했을 때 어떻게 할지 고민해 보면 이 알고리즘의 효율성을 간접적으로나마 이해할 수 있다. 하지만 상당수 복잡도가 큰 알고리즘들이 map/reduce 모델로 쉽게 변환 가능하지 않은 단점이 있다. 따라서 알고리즘에 따라 <그림 2>와 같이 1번씩의 map/reduce에 끝날 수도 있고, 다수의 map/reduce를 거쳐서 결과가 나올 수도 있다.


분산 얼랭  


필자는 얼랭을 공부하면서 map/reduce 같은 분산 알고리즘을 이용한 애플리케이션 개발에 최적화된 언어라고 언소개한 적이 있었다. 그 중심에는 spawn()함수의 편리성과 spawn()함수를 통해 나온 process id 값이면 어느 노드에 있든 프로세스건 핸들링이 가능한 분산 얼랭(distributed erlang)환경이 존재했다. 분산 얼랭 환경은 최적화돼 있으므로 같은 머신은 물론이고 개방된 네트워크 간의 노드에서도 쉽게 운영이 가능하다. 분산 얼랭을 이용하면 다수의 머신에서 운영하지만 마치 하나의 머신에서 돌리는 것 같은 착각을 일으킨다. map/reduce 모델하고 어울리는 게 바로 이점이다.

다수의 노드를 관리하기 쉽고, 수십에서 수백 개의 map 프로세스와 reduce 프로세스를 쉽게 생성하고 죽일 수 있기 때문이다. 또한 얼랭의 경량 프로세스는 os레벨의 프로세스와 비교를 불허할 정도로 가볍고 빠르다. 메시지 전달을 기반으로 한 함수형 언어의 특징을 갖춰서 입력 값이 같으면 동일한 결과값을 보장한다. 즉, 중간에 스레드 같은 게 들어와서 변수를 몰래 고치거나 그런 일이 절대 일어나지 않는다는 것인데, 이로써 얼랭 프로세스 한 개가 정상적으로 수행되면 동일한 수백 개의 얼랭 프로세스도 잘 수행된다는 것을 보장한다. 마지막으로 에러에 강건한(fault tolerant) 시스템을 만들 수 있다. 생성된 프로세스는 다른 프로세스와 쉽게 링크돼 서로 프로세스 작동 여부를 감시할 수 있다. 즉, 어디서든 충돌이 일어나 프로세스가 죽으면 즉시 프로세스를 되살릴 수 있다.

얼랭을 이용한 다중 서버 기반 map/reduce 설계 

그럼 본격적으로 구현을 해보겠다. <그림 3>은 구현하게 될 map/reduce 프레임워크이다.

<그림3>프로세스 구조도

 


<그관점)

물론 broker를 제외한 모든 프로세스 및 노드의 개수는 가변적이다. 각 노드에 reducer 프로세스가 위치할 수 있으나 일단 편의상 <그림 3>과 같이 구성했다. 각 프로세스가 하는 일에 대해 간략하게 설명해 보겠다.

● broker process

map/reduce 전체 프로세스를 스케줄링 하기 위한 프로세스로 실제 각 노드의 프로세스 정보를 가졌다(reducer 프로세스 정보도 가졌다). 모든 노드에 map 프로세싱 작업의 진행 여부를 체크하고 각 노드에 job을 할당하며, map 단계가 끝나고 나서는 각 reduce 프로세스에 데이터를 할당한다.

● node process

broker 프로세스로부터 생성되며, broker 프로세스로부터 데이터를 받아 휘하 map 프로세스들에게 job을 할당한다. 데이터 처리 진행 여부를 체크하며 이를 broker 프로세스에 리포팅 한다. map 프로세싱 이외에 combine이라는 최적화 작업이 각 node 프로세스에서 수행된다.

● map processes

node 프로세스로부터 생성되며 실제 map작업을 하는 프로세스이다. map 프로세스의 생성 시 할당되는 map 함수에 따라 다양한 작업이 가능하다.

● reduce processes

broker 프로세스로부터 생성/파괴가 되고 각 노드에서 결과로 나오는 데이터를 받아 최종 reduce 프로세싱을 한다. 역시 reduce 함수를 교체해줌으로써 다양한 작업이 가능하다. 얼랭의 프로세스 자체가 메시지 전달(message passing)에 의해 이뤄지기 때문에 이해를 위해 msd (message sequence diagram)을 그려봤다. 사실 전체적인 메커니즘은 <그림 4>의 msd에 다 나와 있다. 예제 소스코드와 메시지를 잘 매칭해서 보면 전체적인 윤곽을 파악하는데 큰 도움이 된다. 지면 관계상 자세한 설명 보다는 반드시 이해할 구현 원리를 설명하겠다.



broker process에서 각 노드에 모듈 분산하기 


위에서부터 시간 순으로 보자면 broker 프로세스가 main 프로세스로부터 생성되고 node 프로세스 및 reduce 프로세스를 생성시킨다. 그리고 init 명령을 각 node 프로세스에 날리면 노드는 데이터를 프로세싱 할 준비와 약정된 연산을 하는 map 프로세스들을 생성한다. 이 부분 broker 프로세스에 대한 코드는 <리스트 1>과 같다.

<리스트 1> broker_process() 함수의 init 부분

broker_process(parentid, nodeinfo, reduceprocesses, mappable) ->

  receive

    {init, {nodelist, mapfunc, mappern, reducefun,  reducern}} ->

      %각 노드를 초기화 한다.

      activenode = lists:filter(fun(x) -> net_adm:ping(x) == pong end, nodelist),

      io:format("~b of ~b is avalible.~n",  [length(nodelist), length(activenode)]),

      c:nl(?module),

      pid = self(),

      newnodeinfo = map(fun(node) ->

         nodepid = spawn(node, ?module, node_process, [pid, [],[], [],  false]),

        nodepid ! {init_node, {mapfunc, mappern}},

        {nodepid, [node, init, 0]}

        end, activenode),

      %reducer들을 만든다.

      newreduceprocesses = map(fun(_) ->

        spawn(fun() -> reducer(pid, reducefun) end)

        end, lists:seq(0, reducern-1)),

      broker_process(parentid, newnodeinfo, newreduceprocesses, mappable)

  ...............................

  end.

<리스트 1>에서 {init, .. } 명령으로 들어가는 부분을 보면 내가 node 프로세스를 만들고 싶은 노드에 대한 정보들이 nodelist에 들어 있는데 이들 중에 가용한 노드가 어떤 것인지 net_adm:ping()함수로 확인한다. 그리고 nl()명령을 수행하게 되는데, 이 함수가 없다면 각 노드에 들어가서 소스 컴파일하고 동일한 모듈이 각 노드에 로딩 됐다는 것을 보장할 수 있는 특정 작업을 해줘야 한다. 하지만 단지 nl()만으로 백 개의 노드이건 천 개의 노드이건 모듈 로딩이 간편하게 수행된다. <리스트 1>에서 주의할 부분은 broker 프로세스의 pid를 먼저 구하고 이를 spawn 인자로 넘겨야 된다는 것이다. 만일 spawn의 인자에 self()를 직접 넣어버리면 spawn으로 생성된 프로세스의 pid가 들어간다. 이점 주의하길 바란다.


얼랭 프로세스에서 꼬리재귀란?

앞에서 얼랭은 메시지 전달에 의해 프로세스가 동작한다고 했다. 프로세스 내에서 전역적으로 쓰이는 변수는 반드시 꼬리재귀(tail recursion)를 통해서 전달된다. 데이터 누적도 이를 통해서 전달된다. 만일 receive 패턴에 맞더라도 꼬리재귀에 들어가지 않을 경우 프로세스는 종료된다. 그리고 얼랭의 모든 프로세스는 메시지 드리븐(message driven)방식이어서 메시지가 오지 않고서는 자동으로 진행되기 힘들다. 따라서 <그림 4>의 msd처럼 간단하게 메시지 전달에 대한 디자인을 세심하게 할 필요가 있다. 간단하게 broker 프로세스의 receive 패턴과 꼬리 재귀의 얼개를 살펴보자(<리스트 2> 참조).

<리스트 2>에서 보듯이 이 부분 함수는 mappable 애텀(atom)을 가지고 있어서 반드시 map 단계에서 실행되는 부분이다. {statuses, {nodepid, combine_completed, datalen}} 패턴의 메시지를 받을 때마다 모든 node의 상태가 combine_completed로 되어 있는지 확인하게 되는데 이때 모든 node의 상태가 combine_completed로 되었다는 게 확인되면 reduceable 단계로 넘어간다(<리스트 2> 16라인 참조). 이 reduceable 패턴이 매칭되는 위 함수 이후의 나머지 부분이 reduce 단계를 수행한다.

보시다시피 각 패턴 매칭 단계의 마지막에는 꼬리 재귀를 위한 broker_process 함수 호출이 있다. 유일하게 {clear_all_process}부분에서는 꼬리재귀를 하지 않는다. 모든 node 프로세스들을 종료시키고 자신도 종료하는 부분이기 때문이다. 그럼 꼭 꼬리재귀를 해야 하는가? 반드시 그런 건 아니다. 재귀를 모듈 중간에 했더라도 그 모듈이 무한으로 실행되는 것이 아니라는 보장만 있으면 사용해도 무방하지만 <리스트 2>와 같이 언제 종료될지 모르는 프로세스에서 쓰는 것은 스택 오버플로우(stack overflow)를 일으킬 위험이 많으니 삼가야 한다. 그래서 얼랭에서는 꼬리재귀를 사용하는 것이 정석이다.

<리스트 2> broker_process() 함수에서 map 단계를 처리하는 코드 얼개

broker_process(parentid, nodeinfo, reduceprocesses, mappable) ->

  receive

    {init, {nodelist, mapfunc, mappern, reducefun, reducern}} ->

      broker_process(parentid, newnodeinfo, newreduceprocesses, mappable);

    {clear_all_process} ->

      _;

    {distribute_data_to_node, {chunk}} ->

      broker_process(parentid, nodeinfo, reduceprocesses, mappable);

    {end_of_distribute_data} ->

      broker_process(parentid, newnodeinfo, reduceprocesses, mappable);

    {statuses, {nodepid, map_completed}} -> 

      broker_process(parentid, newnodeinfo, reduceprocesses, mappable);

    {statuses, {nodepid, combine_completed, datalen}} ->

      case is_all_of_status(newnodeinfo, combine_completed) of

        true ->

          broker_process(parentid, newnodeinfo, reduce processes, pidlist, [{in,0}, {out,0}] ,reduceable);

        false ->

          broker_process(parentid, newnodeinfo, reduceprocesses, mappable)

      end

  end;

각 노드에서의 map과 combine  

 

각 노드에서 map 프로세싱과 combine 프로세싱이 어떻게 이뤄지는지 알아보자. map 프로세싱은 map 프로세스에 데이터를 던지고 받는 과정으로 끝난다. 처음 map 프로세스가 생성될 때 map 프로세스에 쓰일 fp퍼런스 함수를 하나 받아 가는데 이를 사용해서 들어온 데이터를 파싱한다. 예제로 추가된 word count map 함수를 사용하면 다음과 같은 리스트 객체가 리턴된다. 

"i am a boy." -> [ {"i", 1}, {"am", 1}, {"a", 1}, {"boy.", 1} ]

map 작업 후에 효율적으로 map 프로세싱 된 결과물을 보내기 위해 같은 해당 노드에서 key를 갖는 데이터 단위에 대해 묶어주는(merge)작업을 한다. 바로 이러한 작업이 combine 프로세싱이다.

이렇게 combine 프로세싱을 하는 이유는 reduce 프로세싱을 하기 전에 같은 key의 데이터에 대해 모든 노드에서 데이터를 효율적으로 가져오기 위한 준비과정이기 때문이다. 또한 여러 번 보내야 되는 데이터들에 대한 압축 프로세싱의 의미도 있다. 이렇게 압축돼 있으면 데이터 전송 효율도 좋아진다. 간단하게는 각 노드에서 일어나는 일종의 reduce 프로세싱이라고 생각해도 큰 무리는 없을 것이다. 이 부분에 대한 코드를 보자!

<리스트 3> node_process() 함수에서 map 결과물을 가지고 combine 작업을 하는 부분

node_process(brokerpid, mapprocesses, keyvaluedict, status, islastdata) ->

  receive

    .............

    {map_result, {keyvalues}} ->

      ...........

      newkeyvaluedict = lists:foldl(fun(x, accu) -> insert(accu, x) end, keyvaluedict, keyvalues),   

      ...........

      node_process(brokerpid, mapprocesses, newkeyvaluedict, newstatus, islastdata);

    ...........

  end.


insert(dict, {key, value}) ->

  case dict:is_key(key, dict) of

    true -> 

      dict:append(key, value, dict);

    false ->

      dict:store(key, [value], dict)

  end.

broker에서 각 reduce 프로세스에 중복 없는 데이터를 보내는 방법

 

우여곡절 끝에 각 노드에 map/combine 작업을 수행해 데이터를 잘 쌓아 두었다. 이젠 각 노드에 흩어진 데이터를 reduce 프로세싱에 할당하는 마지막 작업이 남았다. 이를 위해서 broker 프로세스는 아주 중요한 일을 한다. 그 과정은 다음과 같다.

1. 전체 노드에서 첫 번째 데이터를 하나씩 가져온다(이는 key로 소팅되고 combine 된 데이터들이다).

2. 이렇게 가져온 데이터들을 key로 소팅하고 가장 큰 key값을 가지는 것들만 조합(merge)한다.

3. 그 조합된 데이터를 key로 해싱한 결과로 선택된 reduce 프로세스에 할당한다.

4. reduce 프로세스로 보내진 key를 가지고 있던 노드에서만 새로운 데이터를 받아와서 2 ~ 4번 과정을 반복한다.

위 과정은 모든 node 프로세스에 데이터가 남아 있지 않을 때까지 수행해야 한다. 과정을 반복하면 reduce 프로세스에 중복 없는 데이터들이 빠르게 공급된다. 관련 함수가 바로 아래 두 함수이다.

preprocess_data_before_reduce_reduce(reduceprocesses, piddata).
broker_process(parentid, nodeinfo, reduceprocesses, piddata,[{in,in}, {out,out}] , reduceable).

예제 코드 테스트

예제로 쓰일 것은 map/reduce 프로그래밍의 “hello world!” 격인 word count 예제이다. 주어진 문서에서 tab이나 공백 단위로 쪼개진 단어를 카운팅하는 게 예제의 목적이다. 이를 위해 map 함수와 reduce 함수를 제작해보면 다음과 같다.

map_function(line) ->

  tokenlist = string:tokens(line, "\t\s"),

  [ {tok, 1} || tok <- tokenlist].


reduce_function(valuelist) ->

  lists:foldl(fun(x, accu) -> x + accu end, 0, valuelist).

map 함수는 파일의 라인을 받고 이를 [ {key, 1}, {key2, 1} ] 리스트로 반환한다. 그리고 reduce 함수는 값들의 리스트만을 받아서 리스트 내부의 값들을 순회하며 합한 결과를 리턴한다. 이를 실험하기 위해 필자가 미리 main()함수를 만들어 두었다. 가장 먼저 해야 될 것이 대상이 되는 머신에서 분산 얼랭 쉘을 띄워 두는 것이다. 필자는 동일 서버에서 세 개의 터미널을 띄우고 테스트를 수행했다. 아마 서로 각기 다른 서버에 띄운다고 해도 크게 다르지 않을 것이다. 우선 node 프로세스와 map 프로세스가 띄워질 두 개의 얼랭 쉘을 띄운다.


gogamza@freesearch:~$ erl ?sname gamza

gogamza@freesearch:~$ erl ?sname goguma

마지막으로 컴파일 된 코드가 존재하는 디렉터리로 이동해 broker 프로세스와 reduce 프로세스들이 띄워질 한 개의 얼랭 쉘을 띄운다.


gogamza@freesearch:~$ erl ?sname gogamza

위 쉘에서 모듈을 로딩하고 main 함수를 실행 시킨다. 입력 파일은 예제 소스 파일 자체를 사용하겠다.


(gogamza@freesearch)1> c(distmapreduce).

(gogamza@freesearch)1> distmapreduce:main(["distmapreduce.erl", "goguma@freesearch", "gamza@freesearch"]).

main 함수에 들어가는 첫 번째 인자는 프로세싱 한 파일명이고, 나머지는 쉘을 띄운 node 정보이다. 이렇게 실행 시키면 distmapreduce.erl_word_count 라는 파일이 생성되는데 이게 바로 word count 된 결과 파일이다. 만일 동일 네트워크 상의 다른 머신에서 돌린다면 얼랭 쉘을 띄울시 다음과 같이 한다.


gogamza@gogamza.freesearch:~$ erl ?name gogamza ?setcookie abc

아래는 필자가 3대의 서로 다른 머신에서 돌려봤을 때 출력된 로그이다. 독자들이 직접 돌려본다면 다음과 유사한 로그를 볼 수 있다.


now number of 2 nodes is avalible.

<8278.113.0> node init : 5 mapper processes is aviable!

<0.44.0> broker init : 3 reducer processes isaviable!

<8279.112.0> node init : 5 mapper processes is aviable!

<8279.112.0> node map process is done!

<8279.112.0> node combine process is completedmap processing on

<8279.112.0> node was completed

<8278.113.0> node map process is done!

<8278.113.0> node combine process is completedmap processing on

<8278.113.0> node was completedreduce completed!

cleaning process!


all process was completed!

<0.44.0> broker was killed

        check distmapreduce.erl_word_count file!

<0.54.0> reducer was killed!

...............

<0.56.0> reducer was killed!

ok

<8278.113.0> node process was killed

....................................

<8278.117.0> map process was killed!

 

실제 성능 비교를 위해 비교 대상이 되는 프로세싱 작업이 있었으면 좋았을 텐데 이 부분에 대해서는 독자 여러분에게 숙제로 남길까 한다. 또 다른 숙제는 각 노드에 있는 map 프로세스에 일을 할당하는 함수를 좀 더 효율적인 것으로 짜볼 수 있는데 한 번씩 생각해 보길 권한다. 단순히 랜덤하게 선택해서 일을 할당하기 보다는 놀고 있는 프로세스에 할당을 하는 게 가장 좋은 방법이다. 이를 위해 각 프로세스의 메시지 큐를 확인 할 수 있으면 될 것이다. 한번 찾아보고 바꿔보고 테스트해보길 바란다.

또한 예제와 같이 메모리 기반으로도 충분히 처리할 수 있는 데이터가 아닌 반드시 디스크를 써서 해야 될 정도로 큰 데이터를 처리한다면 얼랭에 포함된 디스크 기반 해쉬테이블인 dets나 mnesia를 사용하는 것도 좋은 방법이다. 재미있게도 mnesia는 복제가 가능하다(hadoop과 같은 데이터 복제가 간편하게 가능할지도 모르겠다).

만일 이 섹션을 이해하기 힘든 독자들은 ?g프로그래밍 얼랭(programming erlang)?h의 10장과 20장을 참고하길 바란다. 필자는 이런 분산얼랭의 특징 때문에 기계학습(machine lear ning) 애플리케이션 구현에 얼랭을 사용했다. 물론 지금까지 수십 대의 노드를 사용해야 할 정도로 큰 데이터를 학습시켜 보지 않았지만 얼랭은 잠재력 있는 최적의 선택이었다고 생각한다. 멀티코어, 대용량 데이터의 시대를 맞이하여 앞으로도 얼랭은 아마도 최적의 선택일 것이다.


필자소개

 

전희원 gogamza@freesearch.pe.kr, www.freesearch.pe.kr |고려대학교 컴퓨터공학 석사 졸업, 학교에서는 정보검색 및 machine learning을 공부했으며, 5년간의 검색엔진 개발 및 데이터마이닝 경험을 가지고 있다. 대용량 데이터 처리 및 데이터 마이닝에 많은 관심을 가지고 있고, 현재 yahoo! korea search eng r&d 파트에서 방대한 양의 웹 데이터를 들여다보며 재미있는 시간을 보내고 있다.

 

제공 : db포탈사이트 dbguide.net