다양한 메모리, 디스크, 저장소 등 데이터를 저장할 방법은 넘쳐나고 있고, 멀티코어를 갖는 중앙처리장치(CPU)도 아주 저렴한 비용으로 엄청나게 많이 소유할 수도 있고, GPU도 이제 주류로 편입되고 있으며, 네트워크도 CPU와 메모리간 공유메모리, 메모리와 디스크 사이에 네트워크, 그리고 인터넷 통신망으로 연결된 컴퓨터 등 거의 무한에 가까운 컴퓨팅 자원을 갖출 수 있는 환경이 되었다.
그럼 문제는 어떻게 다양하고 엄청난 저장공간, 멀티코어를 갖춘 수많은 중앙처리장치, 그리고 컴퓨터를 연결하는 네트워크 통신을 어떻게 최적화할 수 있을까?
첫번째 접근법은 컴퓨터 한대를 점점더 강력하게 만들어 나가는 방법으로 1990년대를 풍미한 접근법이다. 하지만, 한대의 강력한 컴퓨터를 제작하는 기술보다 훨씬 더 많은 데이터가 더욱 빠르게 생성되어 가는 환경이 2000년대 출현하였다.
범용 장비를 갖춘 컴퓨터를 네트워크로 연결하여 데이터를 분산해서 처리하는 아키텍처가 가장 현실적인 대안으로 제시되었고, 이와 관련된 기술이 사회경제적인 요청에 맞추어 발달해 나갔다.
범용 장비를 갖춘 컴퓨터를 네트워크로 연결하여 대용량의 데이터를 정말 빠른 시간내에 처리한다는 방향에는 모두 동의하지만, 모든 것을 컴퓨터 한대에 때려넣는 방법에 비해 예상치 못한 다양한 난제에 봉착했다.
난제영역 | 고장 통계자료 |
---|---|
서버 컴퓨터 고장 | 3년마다 서버컴퓨터 교체 → 10,000대 운영, 10대/일 |
하드 디스크 | 1 – 5 % / 년 고장 (구글) |
메모리(DIMM) | 0.2 % / 년 고장 (구글) |
네트워크 속도 | 공유메모리 << 디스크 << 네트워크 지연속도 |
하지만 더큰 문제는 고장은 아닌데 엄청 느리게 동작하는 범용장비가 다수 존재하는데 이를 어떻게 인지하고 식별해서 교체해주느냐도 난제에 속한다.
HDFS에서 데이터를 전통적인 맵리듀스(Map Reduce, MR) 방식으로 처리하면 디스크 입출력(Disk I/O) 때문에 병렬처리를 하든 배치방식으로 연속처리를 하든 문제가 성능에 심각한 문제가 발생된다. 그렇다고 데이터를 저장하고 중앙처리장치에서 처리하는 패러다임을 바꿀 수는 없는 노릇이다.
그런데, 메모리 가격이 지속적으로 떨어져 물론 하드디스크나 기타 보조 저장장치보다는 여전히 비싸지만, 적당한 가격에 빅데이터를 처리하는데 문제가 적을만큼 가격이 떨어졌다. 따라서, 기존 주기억장치로 사용되던 메모리를 보조기억장치에 사용하여 빅데이터를 처리할 경우 맵리듀스가 갖는 성능문제를 극복하면서 대용량 데이터를 다루는 것이 가능해졌고, 이렇게 구현된 것이 스파크 RDD다. 즉 메모리 위에서 데이터 공유를 해서 성능문제를 극복한다.
지속적으로 하락하는 메모리 가격에 대한 정보는 Graph of Memory Prices Decreasing with Time (1957-2015)을 참조한다.
구분 | 하둡 맵리듀스 | 스파크 RDD |
---|---|---|
저장소 | 디스크 | 메모리 혹은 디스크 |
연산작업 | 맵(Map)과 리듀스(Reduce) | 맵, 리듀스, 표본추출, 합병… |
실행모형 | 배치(Batch) | 배치, 인터랙티브, 스트리밍 |
프로그래밍 언어 | 자바 | 자바, R, 파이썬, 스칼라 |
빅데이터를 담을 그릇으로 과거에는 하둡, 현재는 스파크(spark)가 많이 회자된다. 동질적인 컴퓨팅 자원을 분산해서 관리하는 방법도 함께 이해해보자.
2015년을 기점으로 하둡 분산형 파일 시스템(Hadoop Distributed File System, HDFS)은 빅데이터에 적합한 스토리지 플랫폼이며, YARN은 빅데이터 환경에 도입할 수 있는 자원 할당 및 관리 프레임워크라는 사실이 많은 공감을 얻고 있다. 또한, 맵 리듀스는 놀라운 기술이지만 모든 문제를 해결 해결하는 하나의 프로세싱 프레임워크는 존재하지 않는다는 사실이다.
하둡, YARN으로도 해결되지 못하는 아니, 정확하게는 하둡, YARN 도구로 기술적 분석(Descriptive analysis), 검색, 예측 분석, 기계학습과 그래프 프로세싱 등 고급 분석에는 적합하지 못하다는 점이다.
이제빅데이터 분석에 스파크를 이용하는 5가지 이유를 살펴보자 1
스파크 프로그램 실행 모형은 다음 두가지로 구성된다.
스파크 응용프로그램을 생성하고 분산데이터에 작업을 실행시키기 위해 sc
변수를 통해 SparkContext
를 생성시켜야 된다. SparkContext
가 생성되면, RDD( resilient distributed dataset)이 생성되어 분산처리할 데이터가 준비된다. 그리고 난 후에 SparkContext
에 마스터 모수설정 을 하여 작업 유형과 방식을 지정한다.
일꾼 프로그램을 로컬 컴퓨터 노드 혹은 로컬 쓰레드로 동작시키는 방식과 스파크 클러스터 혹은 메쏘스 클러스터를 통해 원격으로 일꾼 프로그램에 명령을 주어 작업을 처리하는 방식으로 나뉜다.
작업 영역 | 마스터 모수설정 | 설명 |
---|---|---|
로컬 | local |
병렬작업 없이 작업자 하나 쓰레드로 로컬에서 스파크 실행 |
로컬 | local[K] |
병렬작업 (멀티코어 숫자) K개 작업자를 쓰레드로 로컬에서 스파크 실행 |
원격 | spark://HOST:PORT |
기본디폴트 7077 포트로 원격에서 스파크 클러스터에 연결시켜 작업 |
원격 | mesos://HOST:PORT |
기본디폴트 5050 포트로 원격에서 아파치 메소스(Mesos) 클러스터에 연결시켜 작업 |
RDD는 스파크의 핵심이 되는 데이터 추상화로 한번 생성되면 변경이 불가능한 Immutable 자료형이다. RDD를 생성하는 방법은 다음과 같은 세가지 방식이 있자.
응용프로그램 개발자가 RDD에 대한 분할 갯수를 지정하는데, 더 많이 분할하면 할수록 더 많은 병렬성을 내포하게 된다. RDD를 6개로 쪼개 일꾼 3개에 병렬처리를 시킨다. 작업을 하나만 받은 일꾼도 있고, 2개를 받은 일꾼도 있고, 3개를 처리해야 하는 일꾼도 있다.
RDD에 대한 두가지 연산유형
RDD를 캐쉬형태로 메모리 혹은 디스크에 넣고 작업하는 것도 가능하다.
데이터 구조 분류
printf
문으로 생성되는 웹로그 등.RDD와 데이터프레임 성능을 비교하면, 단순히 RDD를 사용하는 것에 비해 데이터프레임을 사용한 것이 성능이 파이썬의 경우 더 잘 나오는 것이 확인된다. R 데이터프레임도 확인이 되고 있지 않지만 유사할 것으로 판단된다.
스파크 RDD 변환은 기존 데이터에서 새로운 데이터를 생성시키지만, 바로 새로운 데이터가 생성되는 것이 아니다. 지연연산(lazy evaluation) 을 사용해서 기초 데이터에 적용될 변환연산을 기억하고 있고 동작(Action) 이 실행될 때 한번에 실행된다. 이런 과정을 통해 스파크가 자동으로 최적화 및 장애나 느리게 작업하는 일꾼을 깔끔하게 처리한다. 어떻게 보면 최종결과값을 생성해내는 방안 혹은 음식조리하는 요리법으로 간주할 수도 있다.
변환 작업 | 상세설명 |
---|---|
map(func) | func 함수를 통해 인자로 전달하고, 결과로 새로운 분산 데이터셋이 반환된다. |
filter(func) | func 함수를 통해 참으로 선택된 것만 인자로 전달하고, 결과로 새로운 분산 데이터셋이 반환된다. |
distinct([작업갯수]) | 유일무이한 작업만 선택해서 인자로 전달하고, 결과로 새로운 분산 데이터셋이 반환된다. |
flatmap(func) | map 과 유사하지만, 각 입력항목이 0 혹은 그 이상으로 func 함수가 단일 항목을 반환하는 것이 아니라 순열을 반환한다. |
# coding: utf-8
# 1. map 예제
>>> rdd = sc.parallelize([1,2,3,4])
>>> rdd.map(lambda x: x*2)
# [1,2,3,4] --> [1,4,9,16]
# 2. filter 예제
>>> rdd.filter(lambda x: x %2 == 0)
# [1,2,3,4] --> [2,4]
# 3. distinct 예제
>>> rdd2 = sc.parallelize([3,5,5,2,1,2,2,3])
>>> rdd2.distinct()
# [3,5,5,2,1,2,2,3] --> [1,2,3,5]
동작 작업 | 상세설명 |
---|---|
reduce(func) | func 함수를 사용해서 데이터셋 원소를 총합요약. func 함수는 인자를 두개 받아 하나를 반환. |
take(n) | 첫 n 개 원소를 뽑아낸 배열을 생성. |
collect() | 배열로 모든 원소를 뽑아냄, 드라이버 프로그램 메모리가 데이터를 담을 수 있는 사전확인 |
takeOrdered(n, key=func) | key=func 에 지정된 방식 혹은 오름차순으로 n 개 원소를 추출하여 반환 |
RDD 동작방식은 최종 결과를 얻기 위해서 모으기(collect) 동작을 일으키면, 병렬화(parallelize), 여과과정(filter), 매핑(map) 작업이 원 데이터 파일 혹은 리스트에 가해져 RDD에 순차적으로 진행된다.
좀더 구체적으로 살펴보면 다음과 같다.