본문 바로가기
Big Data

맵/리듀스 (Map/Reduce) 이해하기

by csk 2015. 3. 6.

빅데이터를 접하기 시작하면서 자주듣게 되는 용어가 있습니다. 맵/리듀스 라는 용어인데요, MR이라고도 많이 쓰구요, 빅데이터 처리에는 늘 맵리듀스 개념이 들어가죠.


그럼, 빅데이터 처리의 기본이되는 맵리듀스란 무엇인지 자세히 알아볼께요. 


일단 맵(Map) 이라는 것은 지도? 아니구요, :) 데이터를 담아두는 자료 구조 중의 하나입니다. 맵은 키와 밸류라는 두개의 값을 쌍으로 가지고 있는 형태입니다. 수학시간에 좌표를 표시할때 순서쌍이라고 하죠, (x,y) 이렇게 하던 바로 그 개념입니다. 여기서 x가 키이고, y가 밸류 즉 값인거죠. 그리고 함수 f(x) => y 도  생각나시죠? x를 알면 y를 알 수 있는 구조로 관리 됩니다.   



리듀스(Reduce)는 이 맵을 정리해 나가는(줄여나가는) 방법이라고 할 수 있습니다. 키를 기준으로 (같은 키 값을 가진 맵들의) 개수를 센다든지, 같은 키를 기준으로 밸류를 모두 더하거나, 평균을 내거나 하는 것들이 있습니다. 글 마지막 부분에 더 많은 예제가 있으니까 끝까지 꼭 읽어보세요. :)


그럼... 맵리듀스의 가장 쉬운 예제를 살펴 볼께요.


말로만은 이해가 어려우니 예를 들어볼께요. 맵리듀스 예제로 가장많이 등장하는 wordcount를 가지고 설명할께요. 

wordcount는 어떤 글에 등장하는 단어들의 개수를 세는 작업이구요, java 프로그래밍을 처음 배울때 "hello world"를 찍어보듯 맵리듀스를 처음 배울때 짜보는 프로그램 입니다.


단어 수를 세기 위한 맵은 일단 대상이 되는 글이 담긴 파일을 한 줄 한 줄 읽어가면서 단어를 잘라 만듭니다. 그리고 밸류는 무조건 1로 해서 차곡차곡 나열만 하는거죠. 


그리고 나면 하둡 같은 빅데이터 처리 프레임워크에서 suffling이라는 작업을 통해서 비슷한것끼리 모아가면서 정리를 해줍니다.


이어서 리듀스 프로그램이 돌아가게 되는데요. 이때 키가 같은 경우(단어가 같은)의 밸류(1로 입력한)를 더하라고 프로그래밍 하면 단어별로 출현 빈도가 집계되는 것입니다. 


그런데,,, 도대체 왜 빅데이터에서 맵리듀스의 개념을 사용하는 걸까요? 


빅데이터는 양이 워낙 많기 때문에 처리 프로세스는 최대한 단순하게 만들어야 합니다. 그래야 수많은 서버에 흩어서 병렬로 처리할 수 있거든요. 처리의 순서가 중요하다든지 실패했을때 다시하려면 몇단계 전으로 돌아가야 한 다든지 하면 대략난감해지니까요. 

이를 위해서는 기준이 되는 값은 하나!이어야 프로세스가 단순합니다. 그래서 기준이되는 값인 키가 하나인 맵 구조를 선택한거구요. 

키에 대한 실제 처리는 밸류(값)를 가지고 하게 되는데 여기에 수행 하는 연산도 역시 단순해야 합니다. 어떤식이냐면 교환법칙과 결합법칙이 성립해야 하죠. 임의의 서버에서 임의의 순서로 두 개의 맵을 선택해서 연산을 수행하더라도 최종결과는 늘 같아야 병렬처리가 가능 하니까요.




이런 조건을 만족하기 위해서 모든 사용자들의 처리를 하나의 틀 안에서 단순화 하는거죠. 맵 만들고, 리듀스 하고... 다시 맵 만들고 리듀스하고... 하는 식으로요. 이 틀을 어기지만 않는다면 수백대의 서버가 일을 나누어 해도 오류없이 처리가 가능하게 됩니다. 

그리고 또 신기한것이 이런 틀 안에서도 맵리듀스를 잘 이용하거나, 여러단계를 반복 시키면, 참 다양하고 꽤 복잡한 작업도 처리가 가능하게 되니, 충분히 분할만 해나간다면 본질적으로 복잡한 작업이란 없는건지도 모르겠습니다. :) 


개념을 이해하기 위해서, 맵리듀스를 사용하는 패턴을 조금 더 자세하게 살펴볼께요.


예를 들어 쇼핑몰 사용자의 사용 로그가 아래와 같이 남는다고 가정 해볼께요.

사용일자:사용자아이디:행동유형:관련금액

-----------------------------------------

20150305 0930:chulsoo:addToCart:0

20150305 1002:chulsoo:buy:33000

...


쇼핑몰을 방문한 사용자를 집계하고 싶을때는 

로그를 한 줄씩 읽으면서 (사용자아이디, 1) 형태로 맵을 만듭니다.

리듀스 할때 밸류를 서로 더해 주셔도 되고, key의 개수를 세어주는 함수가 제공된다면 그걸 그냥 호출하면 됩니다. 


대략 적으로 코드 형태를 보여드리자면 이런 식입니다. 일종의 pseudo code랄 수 있겠네요. :)

map(사용자아이디, 1)

reduceByKey((a,b) => a+b)

맵을 만들때는 로그의 한 줄을 읽어서 적당한 부분을 잘라내는 파싱 작업이 먼저 이루어져야 하죠. 그래야 map에 저렇게 사용자 아이디를 넣어줄 수 있으니까요. 

그리고 reduceByKey에 나오는 a,b는 두 개의 맵을 들고 연산을 수행할때, 각각의 밸류값 입니다. 키는 reduceByKey라는 이름이 알려주듯 같은 키 값을 가진 맵들끼리 계속해서 연산을 해서 맵의 숫자를 줄이는 거죠. 최종적으로는 그 키를 가지는 맵이 한 개 남을때 까지요. 그러니까 키는 정해줄 필요가 없고 첫번째 맵의 밸류 a와 두번째 맵의 밸류 b를 가지고 어떤 연산을 수행할 것인지 (위 예제에서는 더하기(+) 네요) 만 정해 주면 됩니다. 


  • 특정행동이 일어난 건수를 행동 유형별로 알고 싶을때는

로그를 한 줄씩 읽으면서 (행동유형, 1) 형태로 맵을 만들구요,

리듀스처리에서는 키가 같은 두개의 맵이 만날때마다 밸류 값을 서로 더해주는 함수를 선언하면 됩니다.


map(행동유형, 1)

reduceByKey((a,b) => a+b)


  • 당일에 특정행동에 관련된 금액의 총계를 뽑고 싶을때는

사용일시가 당일인 건을 한 줄씩 읽으면서 (행동유형, 관련금액) 형태로 맵을 만들고,

리듀스 처리에서는 키가 같은 두 개의 맵에 만날때마나 밸류값을 서로 더해주는 함수를 선언하면 됩니다.

map(행동유형, 관련금액)

reduceByKey((a,b) => a+b)


  • 사용자별 행동별 건수와 같이 두가지 이상의 조건을 조합하고 싶은 경우에는요,

그 조건을 적절하게 이어붙여서 키 값을 만드세요.

한 줄씩 읽으면서 (사용자아이디_행동유형, 1) 형태로 맵을 만드는 것 처럼요.

그리고 나서 리듀스 처리를 하고, 

이후에 이걸 다시 파싱해서 DB에 넣어서 조회하거나 하는 방식으로 사용할 수 있습니다. 

map (사용자아이디_행동유형, 1)

reduceByKey((a,b) => a+b)


패턴이 보이시나요? :)

로그에서 원하는 조건 부분을 잘라서 맵에 넣으시구요, 밸류에는 개수를 셀때는 1을 원하는 값이 있을때는 그 값을 넣어줍니다. 그리고 나서 키를 기준으로 리듀스를 하시면 됩니다. 


아 오늘은 좀 글이 길었나요? 다음에 또 시간날때 정리할께요~~~ :)