10. 일괄 처리
- 앞 장에서는 요청(request), 응답(response), 질의(query), 결과(result) 에 대해 다뤘습니다.
- 온라인 시스템에서는 사용자의 응답 시간 단축에 많은 노력을 합니다.
- 시스템을 세가지 유형으로 분리하면 다음과 같습니다.
- 서비스(온라인 시스템) , 서비스는 클라이언트로부터 요청이나 지시가 올때까지 기다립니다.
- 일괄 처리 시스템(오프라인 시스템) , 매우 큰 입력의 데이터를 받아 데이터를 처리하는 작업을 수행하고 결과 데이터를 생산합니다.
- 스트림 처리 시스템(준실시간 시스템) , 온라인과 오프라인 사이의 어딘가에 위치하기에 준실시간 처리라고 부릅니다. 일처리 작업은 정해진 크기의 입력 데이터를 대상으로 작동하지만 스트림 처리는 입력 이벤트가 발생한 직후 바로 작동합니다.
- 일괄 처리는 신뢰할 수 있고 확장 가능하며 유지보수하기 쉬운 애플리케이션을 구축하는 데 중요한 구성요소 입니다.
#
유닉스 도구로 일괄 처리하기#
단순 로그 분석다양한 도구를 통해서 트래픽에 대한 보고서를 생성할 수 있습니다.
로그를 읽고, 공백으로 분리된 줄의 7번째 필드만 출력하고, 요청 URL을 정렬하고, 중복을 제거하고,...
Tip. 개발하면서 로그 분석한거 이야기 주기.
- 이러한 방식은 상당히 강력합니다.
#
연쇄 명령 대 맞춤형 프로그램- 표면적인 문법의 차이를 빼고 두가지 방법은 실행 흐름이 크게 다릅니다.
#
정렬 대 인메모리 집계- 루비 스크립트는 URL 해시 테이블을 메모리에 유지합니다.
- 유닉스 파이프라인 예제는 이러한 해시 테이블이 없습니다.
허용 메모리보다 작업 세트가 크다면 정렬 접근법을 사용하는 것이 중요합니다.
#
유닉스 철학유닉스 철학은 다음과 같습니다.
- 각 프로그램이 한 가지 일만 하도록 작성합니다.
- 모든 프로그램의 출력은 아직 알려지지 않은 다른 프로그램의 입력으로 쓰일 수 있다고 생각하라.
- 소프트웨어를 빠르게 써볼 수 있게 설계하고 구축하라.
- 프로그래밍 작업을 줄이려면 미숙한 도움보단 도구를 사용하라.
bash나 shell을 통해 데이터 처리 작업을 쉽게 구성할 수 있습니다.
#
동일 인터페이스- 어떤 프로그램의 출력을 다른 프로그램의 입력으로 쓰고자 한다면 이들 프로그램은 같은 데이터 형식을 사용해야 합니다.
- 즉, 특정 프로그램이 다른 어떤 프로그램과도 연결 가능하려면 프로그램 모두가 같은 입출력 인터페이스를 사용해야 한다는 의미입니다.
- 동일한 데이터 모델인 데이터베이스 간에도 한쪽에서 다른 쪽으로 데이터를 옮기는 게 쉽지 않습니다. 이처럼 데이터가 발칸화되는 이유는 유닉스 도구와 같은 통합이 부족했기 때문입니다.
#
로직과 연결의 분리- 유닉스 도구의 다른 특징 중 하나는 표준 입력(stidn)과 표준 출력(stdout)을 사용한다는 점입니다.
- 이러한 경우와 같이 입력이 어디서부터 들어오는지, 출력이 어디로 나가는지 신경을 쓰거나 알 필요가 없습니다.
- 이러한 형태를 느슨한 결합(loose coupling) , 지연 바인딩(late binding), 또는 제어 반전(inversion of control) 이라고 합니다.
#
투명성과 실험- 유닉스 도구가 성공적인 이유 중 하나는 진행 사항을 파악하기가 상당히 쉽습니다.
- 유닉스 명령에 들어가는 입력 파일은 일반적으로 불변으로 처리됩니다.
- 어느 시점이든 파이프라인을 중단하고 출력을 파이프를 통해 less로 보내 원하는 형태의 출력이 나오는지 확인할 수 있습니다.
- 특정 파이프라인 단계의 출력을 파일에 쓰고 그 파일의 다음 단계의 입력으로 사용할 수 있습니다.
유닉스 도구는 일부 불친절하지만 단순하고 유용합니다. 이러한 가장 큰 제약은 단일 장비에서만 실행되며 이러한 이유로 하둡 같은 도구가 필요합니다.
#
맵리듀스와 분산 파일 시스템- 맵리듀스는 유닉스 도구와 비슷한 면이 있지만 수천 대의 장비로 분산해서 실행이 가능하다는 점에서 차이가 있습니다.
- 유닉스 도구와 마찬가지로 맵리듀스 작업은 입력을 수정하지 않기 때문에 출력을 생산하는 것 외에 다른 부수 효과는 없습니다.
- 유닉스 도구는 stdin과 stdout을 사용하는데, 맵리듀스 작업은 분산 파일 시스템상의 파일을 입력과 출력으로 사용합니다. 이를 HDFS(Hadoop Distributed File System) 이라고 합니다.
- HDFS는 비공유 원칙을 기반으로 하며, NAS(Network Attached Storage) 나 SAN(Storage Area Network) 아키텍처에서 사용하는 공유 디스크 방식과는 반대입니다.
- HDFS는 각 장비에서 실행되는 데몬 프로세스로 구성됩니다.
- 데몬 프로세스는 다른 노드가 해당 장비에 저장된 파일에 접근 가능하게끔 네트워크 서비스를 제공합니다.
- 네임노드(NameNode) 라고 부르는 중앙 서버는 특정 파일 블록이 어떤 장비에 저장됐는지 추적합니다.
- 즉 , HDFS는 개념적으로는 매우 큰 하나의 파일 시스템이고 데몬이 실행중인 모든 장비의 디스크를 활용할 수 있습니다.
- 장비가 죽거나 디스크가 실패하는 경우에 대비하기 위해 파일 블록은 여러 장비에 복제됩니다.
- HDFS는 확장성이 뛰어납니다.
#
맵리듀스 작업 실행하기- 맵리듀스는 HDFS와 같은 분산 파일 시스템 위에서 대용량 데이터셋을 처리하는 코드를 작성하는 프로그래밍 프레임워크입니다.
- 맵리듀스 작업 하나는 4가지 단계로 구성됩니다.
- 1단계 : 파일을 나누어 레코드를 만드는 데 입력 형식 파서를 씁니다.
- 2단계 : 맵(Map)
- 3단계 : 정렬 단계
- 4단계 : 리듀스
- 맵리듀스 작업을 생성하려면 다음과 같이 동작하는 매퍼와 리듀서라는 콜백 함수를 구현해야 합니다.
- 매퍼(Mapper) : 매퍼는 모든 입력 레코드마다 한 번씩만 호출됩니다.
- 리듀서(Reducer) : 맵리듀스 프레임워크는 매퍼가 생성한 키- 값 쌍을 받아 같은 키를 가진 레코드를 모으고 해당 값의 집합을 반복해 리듀서 함수를 호출합니다.
- 매퍼는 정렬에 적합한 형태로 데이터를 준비하는 역할을 하고, 리듀서는 정렬된 데이터를 가공하는 역할을 합니다.
#
맵리듀스의 분산 실행- 유닉스 명령어 파이프라인과 가장 큰 차이점은 맵리듀스가 병렬로 수행하는 코드를 직접 작성하지 않고도 여러 장비에서 동시에 처리가 가능합니다.
- 매퍼와 리듀서는 한 번에 하나의 레코드만 처리하고 입력이 어디서, 출력이 어디로 가는지 신경쓰지 않습니다.
- 분산 연산에서 매퍼와 리듀서로 표준 유닉스 도구를 사용하는 것도 가능합니다.
- 하둡 맵리듀스 작업에서 데이터플로를 보여줍니다. 맵리듀스 작업의 병렬 실행은 파티셔닝을 기반으로 합니다.
- RAM과 CPU에 여유가 있다면 맵리듀스 스케줄러가 입력 파일이 있는 장비에서 작업을 수행하는 것이 좋은 데 이 원리를 데이터 가까이에서 연산하기라고 합니다.
- 대표적인 예시로, 자바 프로그램을 예로 들면 JAR 파일을 복사한 후 각 장비에서 매퍼 태스크가 시작됩니다. 입력 파일을 읽기 시작하면 입력 파일에서 한 번에 레코드 하나씩 읽어 매퍼 콜백 함수로 전달합니다.
- 리듀서 측 연산도 파티셔닝됩니다. (태스크 수는 사용자가 설정합니다.)
- 키-값 쌍은 반드시 정렬돼야 하지만 대개 데이터셋이 매우 크기 때문에 단게를 나눠서 정렬을 수행합니다.
- 매퍼가 입력 파일을 읽어서 정렬된 출력 파일을 기록하기를 완료하면 맴 리듀스 스케줄러는 그 매퍼에서 출력 파일을 가져올 수 있다고 리듀서에게 알려줍니다.
- 리듀서를 기준으로 파티셔닝하고 정렬한 뒤 매퍼에게 데이터 파티션을 복사하는 과정을 셔플(shuffle) 라고 합니다.
- 리듀서 태스크는 매퍼로부터 파일을 가져와 정렬된 순서를 유지하면서 병합합니다. (그래서 리듀서의 입력으로 들어갈 때는 서로 인전해서 들어가게 됩니다.)
- 리듀서는 키와 반복자(iterator)를 인자로 호출하는 데 이 반복자로 전달된 키와 동일한 키를 가진 레코드를 모두 훑을 수 있습니다.
#
맵리듀스 워크플로- 맵리듀스 작업 하나로 해결할 수 있는 문제의 범위는 제한적입니다.
- 즉, 맵리듀스 작업을 연결해 워크폴로(workflow) 로 구성하는 방식이 꽤 일반적입니다.
- 일괄 처리 작업의 출력은 작업이 성공적으로 끝났을 때만 유효합니다. (맵리듀스는 작업이 실패하고 남은 부분 출력은 제거합니다.)
- 이런 스케줄러에는 많은 일괄 처리 작업의 집합을 유지보수할 때 유용한 관리 기능이 있습니다.
#
리듀스 사이드 조인과 그룹화- 여러 데이터넷에서 한 레코드가 다른 레코드와 연관이 있는 것은 일반적입니다.
- 관계형 모델에서는 외래키(foreign key), 문서 모델에서는 문서 참조(document reference), 그래프 모델에서는 간선(edge) 라고 합니다.
- 연관된 레코드 양쪽 모두에 접근해야 하는 코드가 있다면 조인은 필수적입니다.
- 데이터베이스에서 적은 수의 레코드만 관련된 질의를 실행한다면 데이터베이스는 일반적으로 색인(index)을 사용합니다.
- 그러나, 맵리듀스에서는 색인 개념이 없습니다.
- 맵리듀스는 파일을 전체 읽으며 (데이터베이스에는 이 연산을 전체 테이블 스캔, full table scan), 이는 비용이 비싸보이지만 병렬처리가 가능하기 대문에 합리적입니다.
- 일괄 처리 맥락에서의 조인은 데이터셋 내 모든 연관 관계를 다룬다는 뜻입니다.
#
사용자 활동 이벤트 분석 예제- 일괄 처리에서는 이벤트 로그(활동 이벤트(activity event) 또는 클릭스트림 데이터(clickstream data))고 오른쪽은 사용자 데이터베이스입니다.
- 그러나 이러한 처리에서 사용자 데이터베이스가 원격에 있으면 나쁜 성능이 됩니다.
- 일괄 처리에서는 처리량을 높이기 위해서는 가능한 한 장비에서 연산을 수행해야 하므로, 데이터베이스의 사본을 가져와 사용자 활동 이벤트 로그가 저장된 분산 파일 시스템에 넣는 것이 좋습니다.
#
정렬 병합 조인- 매퍼는 입력 레코드로부터 키와 값을 추출하는 것이 목적입니다.
- 맵리듀스 프레임워크에서 키로 매퍼의 출력을 파티셔닝해 키-값 쌍으로 정렬한다면 같은 사용자의 활동 이벤트와 사용자 레코드는 리듀서의 입력으로 서로 인접해서 들어갑니다.
- 리듀서가 항상 사용자 데이터베이스를 먼저 보고 활동 이벤트를 시간 순으로 보게 하는 식으로 맵리듀스에서 작업 레코드를 재배열하기도 합니다. 이를 보조 정렬(secondary sort) 라고 합니다.
- 보조 정렬후, 리듀서 함수는 모든 사용자 ID당 한번만 호출되고 보조 정렬 덕분에 첫 번째 값은 사용자 데이터베이스의 생년월일 레코드로 예상할 수 있습니다.
- 리듀서는 본 URL(viewed-url) 과 본 사람의 연령의 쌍을 출력합니다.
- 이를 통해 각 URL마다 본 사람의 연령 분호를 계산하고 연령대별로 클러스터링할 수 있습니다.
- 리듀서는 특정 사용자 ID의 모든 레코드를 한 번에 처리하므로 한 번에 사용자 한 명의 레코드만 메모리에 유지하면 되고 네트워크로 아무 요청도 보낼 필요가 없습니다. 이를 정렬 병합 조인(sort-merge join) 이라고 합니다.
- 매퍼 출력이 키로 정렬된 후에 리듀서가 조인의 양측의 정렬된 레코드 목록을 병합합니다.
#
같은 곳으로 연관된 데이터 가져오기- 병합 정렬 조인 중 매퍼와 정렬 프로세스는 특정 사용자 ID로 조인 연산을 할 때 필요한 모든 데이터베이스를 한 곳으로 모읍니다.
- 필요한 데이터를 사전에 줄을 세웠기 때문에 단 리듀서는 단일 스레드로 동작하는 간단한 코드 조각이 될 수 있으며 레코드를 휘젓고 다닐 때 처리량은 높게 유지하면서 메모리 부담을 줄일 수 있습니다.
- 같은 키를 가진 키-값 쌍은 같은 리듀서를 호출합니다.
- 맵리듀스 프로그래밍 모델은 올바른 장비로 데이터를 모으는 연산의 물리적 네트워크 통신 측면과 받은 데이터를 처리하는 애플리케이션 로직을 분리합니다.
- 이는 데이터베이스 사용 유형과 대조적입니다.
- 맵리듀스는 애플리케이션 로직에서 영향이 가지 않게 실패한 태스크는 확실하게 재시도합니다.
#
그룹화- 조인 외에도 "같은 곳으로 관련 데이터를 모으는" 일반적인 사용 유형은 SQL에서
GROUP BY
절과 같이 특정 키로 레코드를 그룹화하는 것입니다. - 맵리듀스로 그룹화 연산을 구현하는 가장 간단한 방법은 매퍼가 키-값 쌍을 생성할 때 그룹화할 대상을 키로 하는 것입니다.
- 맵리듀스 위에서 그룹화와 조인의 구현은 상당히 유사합니다.
- 특정 사용자가 취한 일련의 활동을 찾기 위해 사용자 세션별 활동 이벤트를 수집 분석할 때도 일반적으로 그룹화를 사용합니다. 이 과정을 세션화(sessionization) 이라고 합니다.
- 사용자 요청을 받는 웹 서버가 여러 개라면 특정 사용자의 활동 이벤트는 여러 서버로 분산돼 각각 다른 로그 파일에 저장됩니다.
- 세션 쿠키, 사용자 ID나 유사한 식별자를 그룹화 키로 사용해 특정 사용자 활동 이벤트를 모두 한 곳으로 모으면 세션화를 구현할 수 있습니다.
- 이때 서로 다른 사용자의 이벤트는 다른 파티션으로 골고루 분산됩니다.
#
쏠림 다루기- 키 하나에 너무 많은 데이터가 연관된다면 (ex. 소셜 네트워크) 불균형한 활성 데이터베이스 레코드를 가지게 되는데 이를 린치핀 객체(linchpin object) 또는 핫 키(hot key) 라고 합니다.
- 유명 인사 한 사람에 관련된 모든 활동을 리듀서 한 개에서 모은다면 상당한 쏠림 현상이 생깁니다. 이 현상을 핫스팟이라고 합니다.
- 즉, 한 리듀서가 다른 리듀서보다 엄청나게 많은 레코드를 처리해야 합니다.
- 조인 입력에 핫 키가 존재하는 경우 핫스팟을 완화시킬 몇 가지 알고리즘이 있습니다.
- 쏠린 조인(skewed join) 메서드는 어떤 키가 핫 키인지 결정하기 위해 샘플링 작업을 수행합니다. 이 경우 핫키로 조인할 다른 입력은 핫 키가 전송된 모든 리듀서에 복제합니다.
- 공유 조인(shared join) 메서드는 이 기법과 비슷하나 핫 키를 명시적으로 지정합니다.
- 핫 키로 레코드를 그룹화하고 집계하는 작업은 두 단계로 수행됩니다.
- 첫 번째 맵리듀스 단게는 레코드를 임의의 리듀서로 보냅니다. 각 리듀서는 핫 키 레코드의 일부를 그룹화하고 키별로 집계해 간소화한 값을 출력합니다.
- 두 번째 맵리듀스 작업은 첫 단계 모든 리듀서에서 나온 값을 키별로 모두 결합해 하나의 값으로 만듭니다.
Map Reducer 구현, 파이썬
단어 빈도수 세기 예제
- mapper의 아웃풋을 '키'인 단어 기준으로 정렬
- 아웃풋을 reducer로 전달한후, 내림차순으로 정리한 경우
#
맵 사이드 조인- 앞의 여러 조인 알고리즘은 실제 조인 로직을 리듀서에서 수행하기 때문에 리듀스 사이드 조인(reduce-side join) 이라고 합니다.
- 매퍼는 입력 데이터를 준비하는 역할을 합니다.
- 리듀스 사이드 접근법의 장점은 이벽 데이터에 대한 특정 가정이 필요없다는 점입니다.
- 그러나 정렬 후 리듀서로 복사한 뒤 리듀서 입력을 병합하는 모든 과정에 비용이 상당한 단점이 있습니다.
- 입력 데이터에 대해 특정 가정이 가능하다면 맵사이드 조인(map-side join) 으로 불리는 기법을 사용해 조인을 더 빠르게 수행할 수 있습니다.
#
브로드캐스트 해시 조인- 맵 사이드 조인은 작은 데이터셋과 매우 큰 데이터셋을 조인하는 경우에 간단하게 적용가능합니다
- 작은 데이터셋은 전체를 각 매퍼 메모리에 적재 가능할 정도로 충분히 작아야 합니다.
- 이러한 매퍼 태스크를 여러개 사용할 수도 있습니다.
- 위처럼 간단하고 효율적인 알고리즘으을 브로드캐스트 해시 조인(broadcast hash join) 이라 합니다.
- 브로드캐스트라는 단어는 큰 입력의 파티션 하나를 담당하는 각 매퍼는 작은 입력 전체를 읽는다는 것입니다.
- 해시라는 이름에서 알 수 있듯이, 해시 테이블을 사용합니다.
- 작은 조인 입력을 인메모리 해시 테이블로 적재하는 대신 로컬 디스크에 읽기 전용 색인으로 작은 조인 입력을 저장하기도 합니다.
- 즉,이 방법을 사용하면 데이터셋 전체가 메모리 안에 들어오지 않더라도 거의 인메모리 해시 테이블만큼 빠르게 임의 접근 조회가 가능합니다.
#
파티션 해시 조인- 같은 방식으로 맵 사이드 조인의 입력을 파티셔닝한다면 해시 조인 접근법을 각 파티션에 독립적으로 적용할 수 있습니다.
- 제대로 파티셔닝이 작동했다면 조인할 레코드 모두가 같은 번호의 파티션에 위치합니다.
- 각 매퍼는 각 입력 데이터셋 중 파티션 한 개만 읽어도 충분합니다. 이러한 방법은 각 매퍼의 해시 테이블에 적재해야 할 데이터의 양을 줄일 수 있다는 점이 장점입니다.
- 파티션 해시 조인은 조인할 두 입력 모두를 같은 키와 같은 해시 함수를 기반으로 같은 수로 파티셔닝해야 작동합니다.
- 파티션 해시 조인(partitioned hash join)을 하이브에서는 버킷 맵 조인(bucketed map join) 이라고 합니다.
#
맵 사이드 병합 조인- 입력 데이터셋이 같은 방식으로 파티셔닝됐을 뿐 아니라 같은 키를 기준으로 정렬됐다면 변형된 맵 사이드 조인을 적용할 수 있습니다.
- 맵 사이드 병합 정렬(map-side merge join)이 가능하다면 선행 맵리듀스 작업이 이미 입력 데이터셋을 파티셔닝하고 정렬해 놓았다는 뜻입니다.
#
맵 사이드 조인을 사용하는 맵리듀스 워크플로- 맵리듀스 조인의 출력을 하위 작업에서 입력으로 사용하거나, 맴 사이드 조인을 사용하거나, 리듀스 사이드 조인을 사용할지에 따라 그 출력 구조가 달라집니다.
- 리듀스 사이드 조인은 조인 키로 파티셔닝하고 정렬해서 출력합니다.
- 반면 맵 사이드 조인은 큰 입력과 동일한 방법으로 파티셔닝하고 정렬합니다.
- 맵 사이드 조인을 수행하기 위해서는 크기, 정렬, 입력 데이터의 파티셔닝 같은 제약 사항이 따릅니다.
- 조인 전략을 최적화할 때는 물리적 레이아웃(파티션 수, 데이터가 어떤 키를 기준으로 파티셔닝되고 정렬됐는지) 파악이 중요합니다.
- 하둡 생태계에서는 데이터셋 파티셔닝 관련 메타데이터를 관리하는데 H카탈로그(HCatalog)나 하이브(Hive) 메타스토어를 사용하기도 합니다.
#
일괄 처리 워크플로의 출력맵리듀스 작업의 결과와 이 작업의 목적에 대해서 이야기합니다.
- 데이터베이스 질의의 경우 트랜잭션 처리(OLTP)를 분석 목적과 구별했습니다.
- OLTP 질의는 색인을 사용해 사용자에게 보여줄 소량의 레코드만 특정 키로 조회하는 것이 일반적입니다.
- 분석 질의는 대량의 레코드를 스캔해 그룹화와 집계 연산을 수행하고 그 결과를 보고서 형태로 출력합니다.
- 일괄 처리는 위의 트랜잭션 처리나 분석이 아니지만, 일반적으로 입력 데이터셋 대부분을 스캔하는 것이 일반적이라 분석에 더 가깝습니다.
- 다만 일괄 처리의 워크플로는 분석 목적으로 사용하는 SQL 질의와는 다릅니다.
#
검색 색인 구축- 구글이 색인을 구축하는 목적으로 맵리듀스를 더이상 사요하지 않지 않지만, 검색 색인을 구축하는 과정을 자세히 살펴보면 맵리듀스를 이해하하는데 도움이 많이됩니다.
- 정해진 문서 집합을 대상으로 전문 검색이 필요하다면 일괄 처리가 색인을 구축하는데 매우 효율적입니다.
- 매퍼는 필요에 따라 문서 집합을 파티셔닝하고 각 리듀서가 해당 파티션에 대한 색인을 구축하고 색인 파일은 분산 파일 시스템에 저장됩니다.
- 색인된 문서 집합이 변한다면 전체 문서 집합을 대상으로 주기적으로 전체 색인 워크플로를 재수행하고 수행이 끝나면 이전 색인 파일을 서로 생성된 색인으로 바꾸는 것이 한 가지 방법입니다.
- 다른 방법으로 증분 색인을 구축하는 것도 가능합니다.
#
일괄 처리의 출력으로 키-값을 저장- 검색 색인은 일괄 처리 워크플로 출력의 한가지 예이니다.
- 이러한 일괄 처리 작업의 출력은 흔히 일종의 데이터베이스가 됩니다.
- 데이터베이스는 하둡 인프라와 별도로 사용자 요청을 받는 웹 애플리케이션에서 질의해야 합니다.
- 가장 확실한 방법은 직접 매퍼와 리듀서 내에서 선호하는 데이터베이스 클라이언트 라이브러리를 사용해 일괄 처리 작업이 한번에 레코드 하나씩 데이터베이스 서버로 직접 요청을 보내는 것입니다. 그러나 이는 잘못된 아이디어입니다.
- 클라이언트 라이브러리가 일괄 처리 방식을 지원해도 성능은 떨어집니다.
- 맵리듀스 작업은 대개 많은 태스크를 동시에 실행합니다. 모든 매퍼나 리듀서가 일괄 처리에서 기대하는 속도로 동시에 같은 출력 데이터베이스에 기록한다면 데이터베이스가 과부하 상태에 빠지기 쉽고 덩달아 질의 성능도 나빠집니다.
- 일반적으로 맵리듀스 작업은 성공한 경우에만 출력을 생성하는 것을 보장합니다. 즉, 작업이 성공하면 출력은 각 태스크가 정확하게 한번만 실행한 결과과 같습니다. 그러나 작업 내부에서 외부 시스템에 기록한다면 이런 방식에서는 감출 수 없는 외부에 드러나는 부수효과를 만들어냅니다.
- 훨씬 좋은 해결책은 일괄 처리 작업 내부에 완전히 새로운 데이터베이스를 구축해 분산 파일 시스템의 작업 출력 디텍터리에 저장하는 방법입니다.
- 검색 색인과 유사한 구조로 저장합니다.
- 다양한 키-값 저장소가 맵리듀스 작업 내에서 데이터베이스 파일을 구축하는 기능을 지원합니다.
- 데이터베이스 파일을 생성하는 작업은 굉장히 좋은 맵리듀스 활용법입니다. 매퍼로 키를 추출한 다음, 키로 정렬하는 과정은 색인을 만들 때도 꼭 필요한 작업입니다.
- 볼드모트에 데이터를 적재할 때 분산 파일 시스템에서 서버의 로컬 디스크로 새 데이터 파일을 복사하는 동안 서버는 기존 데이터 파일로 요청을 계속 처리합니다.
#
일괄 처리 출력에 관한 철학- 맵리듀스 작업도 마찬가지로 철학으로 출력을 취급합니다. 입력을 불변으로 처리하고 외부 데이터베이스에 기록하는 등의 부수 효과를 피하기 때문에 일괄 처리 작업은 좋은 성능을 내면서도 유지보수가 훨씬 간단합니다.
- 코드에 버그가 있어 출력이 잘못되거나 오염됐다면 코드를 이전 버전으로 돌리고 작업을 재수행해 간단하게 출력을 고칠 수 있습니다. 혹은 다른 디렉터리에 이전 출력을 기록했다면 간단하게 출력의 위치만 바꿔 복구를 할 수 있습니다. 버그 있는 코드로부터 복원할 수 있느냐에 관한 개념을 인적 내결함성(human fault tolerance) 라고 합니다.
- 쉽게 되돌릴 수 있는 속성의 결과로 실수를 하면 손상을 되돌릴 수 없는 환경에서보다 기능 개발을 빠르게 진행할 수 있습니다. 비가역성 최소화(minimizing irreversibility) 원리는 애자일 소프트웨어 개발에 이롭습니다.
- 맵이나 리듀스 태스크가 실패하면 맵리듀스 프레임워크는 일시적 문제로 발생한 실패는 충분히 견딜 수 있습니다.
- 구조화 형식은 효율적인 스키마 기반 부호화를 제공하고 시간이 지남에 따라 스키마를 발전시킬 수 있습니다.
#
하둡과 분산 데이터베이스의 비교- 하둡은 유닉스의 분산 버전과 다소 비슷합니다.
- 앞장의 처리 알고리즘과 병렬 조인 알고리즘은 대규모 병렬 처리(massively parallel processing, MVP) 데이터베이스라 불리는 것에 모두 구현되었습니다.
- 맵리듀스와의 가장 큰 차이는 MPP 데이터베이스는 장비 클러스터에서 분석 SQL 질의를 병렬로 수행하는 것에 초점을 두지만 맵리듀스와 분산 파일 시스템의 조합은 아무 프로그램이나 실행할 수 있는 운영체제와 비슷한 속성을 제공합니다.
#
저장소의 다양성- 데이터베이스는 특정 모델을 따라 데이터를 구조화해야합니다.
- 극단적으로 이야기하자면 하둡은 데이터가 어떤 형태라도 상관없이 HDFS로 덤프할 수 잇는 가능성을 열어주었습니다.
- 현실적으로 이상적인 데이터 보다는 데이터를 빨리 사용 가능하게 만드는 것이 더 가치가 있습니다.
- 이러한 아이디어는 데이터 웨어하우스의 개념과 유사합니다.
- 제약없는 데이터 덤핑(data dumping)은 데이터를 해석하는 부담을 이전시킵니다.
- 이러한 접근법을 초밥 원리(sushi principle) 이라 부릅니다. 즉, "원시 데이터가 더 좋다"의 의미입니다.
- 하둡은 ETL 프소세스를 구현하는 데 종종 사용됩니다.
#
처리 모델의 다양성- MPP 데이터베이스는 일체식 구조로서 디스크 저장소 레이아웃과 질의 계획, 스케줄링과 실행을 다루는 소프트웨ㅓ 조각들이 긴밀하게 통합됩니다.
- 반면 SQL 질의로 모든 종류의 처리를 표현하지는 못합니다.
- 맵리듀스를 이용하면 엔지니어는 자신이 작성한 코드를 대용량 데이터셋 상에서 쉽게 실행할 수 있습니다.
- HDFS와 맵리듀스가 있으면 그 위에 SQL 질의 실행 엔진을 구축할 수 있는데 하이브 프로젝트가 그러한 역할을 합니다.
- 시간이 지나 맵리듀스가 너무 제한적이고 어떤 형태의 처리에서는 성능도 나쁘다는 점을 알게 됭ㅆ습니다.
- 그래서 하둡 위에서 다른 다양한 처리 모델이 개발되었습니다.
- 결정적으로 이런 다양한 처리 모델은 모두 단일 공유 클러스터 장비에서 실행되고 분산 파일 시스템상에 존재하는 동일한 파일들에 접근 가능합니다.
- 하둡 접근법에는 다른 종류의 처리를 하기 위해 여러 다른 특정 시스템으로 데이터를 보낼 필요가 없습니다.
- 하둡 시스템은 동일한 클러스터 내에서 다양한 작업부하를 함께 지원할 수 있을 정도로 충분히 유연합니다.
- 하둡 생태계는 HBase 같은 임의 접근 가능한 OLTP 데이터베이스와 임팔라 같은 MPP 스타일의 분석 데이터베이스를 포함합니다.
#
빈번하게 발생하는 결함을 줄이는 설계- 맵리듀스와 MPP 데이터베이스를 비교할 때 설계 방식에서 큰 차이점 두 가지가 두드러집니다.
- 결합을 다루는 방식과 메모리 및 디스크를 사용하는 방식이 그 두가지 방식입니다.
- 일괄 처리는 온라인 시스템에 비해 결함에 덜 민감합니다.
- 질의 실행 중 한 장비만 죽어도 MPP 데이터베이스 대부분은 전체 질의가 중단됩니다.
- 맵리듀스는 맵 또는 리듀스 태스크의 실패를 견딜 수 잇습니다.
- 맵리듀스 접근법은 대용량 작업에 더 적합합니다.
- 그러나 대부분의 상황은 장비 장애가 자주 발생하징는 않습니다.
- 모든 태스크는 우선순위가 있고, 우선순위가 높은 태스크에 더 많은 자원이 필요하다면 자원 확보를 위해 같은 장비에서 수행되던 우선순위가 낮은 태스크를 종료할 수 있습니다.
- 이 아키텍처를 사용하면 비프로덕션 연산 자원을 초과 할당할 수 있습니다.
- 맵리듀스는 태스크 종료가 예상치 못하게 자주 발생하더라도 견딜 수 있게 설계되었습니다. 즉, 하드우에어를 신뢰할 수 없기 때문이 아닌 프로세스를 임의로 종료할 수 있으면 연산 클러스터에서 자원 활용도를 높일 수 있기 때문입니다.
#
맵리듀스를 넘어- 맵리듀스는 2000년대 후반 인기가 있었으나 이는 그저 분산 시스템에서 가능한 여러 프로그래밍 모델 중 단지 하나일 뿐입니다.
- 맵리듀스는 단순 명료하게 추상화된 모델이기에 의의를 가집니다. (즉, 무엇을 하고 있는지 이해하기 쉽다는 뜻입니다.)
- 맵리듀스를 직접 사용하는 일은 어렵기 때문에 맵리듀스 상에 추상화된 다양한 고수준 프로그래밍 모델(피그, 하이브, 캐스캐이딩, 크런치 등)이 등장했습니다.
- 이러한 고수준 프로그래밍 모델은 맵리듀스가 어떻게 동작하는지 이해하고 있으면 확실히 배우기 쉬울 뿐 아니라 일반적인 일괄 처리 태스크를 구현하기 상당히 편리합니다.
- 그러나 맵리듀스 실행 모델 자체에도 문제가 있으며 이는 추상화 단계를 올린다고 해결되지 않고 일부 유형의 처리에 대해서는 성능 저하를 유발합니다.
- 맵리듀스는 견고합니다.
- 즉, 맵리듀스 작업은 느릴지언정 성공하나 특정 유형의 처리는 다른 도구들이 더 빠를 수 있습니다.
아래에서는 일괄 처리 방법의 대안을 찾아봅니다.
#
중간 상태 구체화- 맵리듀스 작업은 다른 작업과 모두 독립적입니다.
- 주요 접점은 분산 파일 시스템 상의 입력과 출력 디렉터리입니다.
- 첫 번째 작업의 출력을 두 번째 작업의 입력으로 사용하려면 두 번째 작업의 입력 디렉터리를 첫 번째 작업 출력 디렉터리와 같게 설정해야 합니다.
- 외부 워크플로 스케줄러에서 반드시 첫번째 작업을 완료한 후 두 번째 작업을 수행해야 합니다.
- 첫 번째 작업의 출력이 조직 내에 널리 공개하는 데이터셋이라면 이러한 설정은 합리적입니다.
- 그러나 대개 한 작업의 출력은 같은 팀 내에서 유지보수하는 다른 특정 작업의 입력으로만 사용됩니다.
- 분산 파일 시스템 상에 있는 파일들은 단순히 한 작업에서 다른 작업으로 데이터를 옮기는 수단, 즉 중간 상태(Intermediate state) 가 됩니다.
- 중간 상태를 파일로 기록하는 과정을 구체화(materialization) 이라 합니다.
- 이전 장에서는 유닉스 파이프를 사용했는데, 파이프는 중간 상태를 완전히 구체화하는 대신 작은 인메모리 버퍼만을 사용해 점진적으로 출력을 입력으로 스트리밍 합니다.
- 중간 상태를 완전히 구체화하는 맵리듀스 접근법은 유닉스 파이프에 비해 여러 단점이 있습니다.
- 맵리듀스 작업은 입력을 생성하는 모든 선행 작업이 완료됐을 때만 시작 가능합니다. 그러나 파이프는 동시에 시작되고 출력은 생상되는 즉시 소비됩니다.
- 매퍼는 종종 중복되기도 합니다.
- 분산 파일 시스템에서 중간 상태를 저장하는 것은 중간 상태 파일들이 여러 장비에 걸쳐 복제됐다는 의미이며 이런 임시 데이터에게는 과잉조치입니다.
#
데이터플로 엔진- 맵리듀스의 여러 문제를 해갈하기 위해 분산 일괄 처리 연산을 수행하는 엔진이 여러개 개발되었습니다. (스파크, 테스, 플링크 등)
- 이러한 엔진들의 공통점은 전체 워크플로를 독립된 하위 작업으로 나누지 않고 작업 하나로서 다룬다는 점입니다.
- 위 엔진들은 여러 처리 단계를 통해 데이터 흐름을 명시적으로 모델링하기 때문에 이 시스템을 데이터플로 엔진(dataflow engine) 이라고 부릅니다.
- 맵리듀스와 달리 이 함수들은 맵과 리듀스를 번갈아 수행하는 식의 규칙을 엄격하게 지킬 필요가 없으며 더 유연하게 함수들을 조합할 수 있습니다.
- 이러한 함수를 연산자(operator) 라고 부릅니다.
- 정렬, 정렬을 건너뛰는 선택지, 브로드캐스트 해시 조인 등
- 데이터플로 엔진은 맵리듀스 모델과 비교했을 때 몇가지 장점이 있습니다.
- 정렬과 같은 값비싼 작업은 실제로 필요할 때만 수행합니다. 맵리듀스 모델은 기본적으로 모든 맵과 리듀스 사이에서 정렬 작업이 항상 발생합니다.
- 필요없는 맵 태스크는 없습니다. 매퍼가 수행한 작업은 종종 선행 리듀스 연산자로 통합할 수 있기 때문입니다.
- 워크플로에 모든 조인과 데이터 의존 관계를 명시적으로 선언하기 때문에 스케줄러가 어느 데이터가 어디에서 필요한지에 대한 개요를 가져서 지역성 최적화가 가능합니다.
- 워크플로에 모든 조인과 데이터 의존 관계를 명시적으로 선언하기 때문에 스케줄러가 어느 데이터가 어디에 필요한지에 대한 개요를 자겨서 지역성 최적화가 가능합니다.
- 연산자 간 중간 상태는 대개 메모리나 로컬디스크에 기록하는 것으로 충분한데 HDFS에 중간 상태를 기록할 때 보다 I/O가 훨씬 적게 듭니다.
- 연산자들은 입력이 준비되는 즉시 실행을 시작할 수 있습니다.
- 새로운 연산자를 실행할 때 이미 존재하는 자바 가상 머신(JVM)을 재활용할 수 있어 각 태스크마다 새로운 JVM을 구동하는 맵리듀스에 비해 시작 부담이 적습니다.
- 맵리듀스 워크플로와 동일한 연산을 데이터플로 엔진을 사용해 구현할 수 있습니다. 또한 앞의 최적화로 인해 수행 속도가 훨씬 더 빠릅니다.
- 테즈는 노드 간에 데이터를 실제로 복사하는 YARN 셔플 서비스에 의존하는 상당히 가벼운 라이브러리이며 스파크와 플링크는 자체 네트워크 통신 게층과 스케줄러, 사용자 측 API를 가진 대형 프레임워크입니다.
#
내결함성- 분산 파일 시스템에 중간 상태를 모두 구체화할 때 생기는 이점은 내구성입니다.
- 맵리듀스는 중간 상태를 모두 구체화하기 때문에 쉽게 내결함성을 확보합니다.
- 스파크와 플링크, 테즈는 HDFS에 중간 상태를 쓰지 않기 때문에 내결함성 확보를 위해 다른 접근법을 사용합니다.
- 장비가 죽어서 장비에 있던 상태까지 잃게 되면 아직 유효한 데이터로부터 계산을 다시해서 복구합니다.
- 재계산이 가능하려면 프레임워크에서 주어진 데이터가 어떻게 연산되는지 추적해야합니다.
- 데이터를 재연산할 때 중요한 점은 해당 연산이 결정적인지 아닌지 파악하는 것입니다.
- 즉, 동일한 입력 데이터가 주어졌을 때 연산자들이 항상 같은 출력을 생산할지에 대한 내용은 중요합니다.
- 아래로 전파되는 결함을 피하려면 연산자를 결정적으로 만드는 것이 좋습니다.
- 결함에서 복구할 때 데이터를 재연산하는 방식이 항상 정답은 아닙니다. 중간 데이터가 원천 데이터보다 훨씬 작거나 연산이 CPU 중심적이라면 재연산보다 중간 데이터를 파일로 구체화하는 방식이 더 효과적입니다.
#
구체화에 대한 논의- 유닉스에 비유하자면 맵리듀스는 각 명령의 출력을 임시 파일에 기록하는 것과 유사한 반면 데이터플로 엔진은 유닉스 파이프와 매우 비슷합니다.
- 정렬 연산자는 출력을 생산하기 전에 전체 입력을 소비해야 합니다.
- 작업을 완료하고 출력을 다른 사용자가 찾아 사용할 수 있게 지속성 있는 어떤 곳으로 보내야합니다.
- 일반적으로 출력을 분산 파일 시스템에 다시 기록합니다. 즉, 데이터플로 엔진을 사용할 때 HDFS상에 구체화된 데이터셋은 보통 작업의 입력과 최종 출력입니다.
#
그래프와 반복 처리- 스파크와 플링크, 테즈 같은 데이터플로 엔진은 일반적으로 작업에 있는 연산자를 비순환 방향 그래프로 배열합니다. 이는 그래프 처리와 다릅니다.
- 데이텁플로 엔진에서는 데이터 자체는 전형적으로 관계형 튜플의 형태로 구성된 채로 한 연사자로부터 다른 연산자로 가는 데이터 흐름이 그래프로 구성됩니다.
- 그래프 처리에서는 데이터 자체가 그래프 형식입니다.
- 어떤 지역이 어느 다른 지역 내에 속하는지 가리키는 간선을 반복적으로 따라가는 방식을 이형적 폐쇄(transitive closure) 라고 합니다.
- 맵리듀스는 데이터를 일회성으로만 처리하며, 이런 알고리즘은 대개 반복적 스타일로 구현됩니다.
- 외부 스케줄러가 이 알고리즘의 한 단계를 연산하기 위해 일괄 처리를 수행합니다.
- 해당 일괄 처리가 완료되면 스케줄러는 종료 조건을 기반으로 완료됐는지 확인합니다.
- 아직 끝나지 않았다면 스케줄러는 1단계로 돌아가서 다음 일괄 처리를 수행합니다.
- 위의 접근법은 맵리듀스로 구현해도 동작하나 비효율적입니다 .맵리듀스는 알고리즘의 반복적 속성을 고려하지 않기 때문입니다.
#
프리글 처리 모델- 일괄 처리 그래프를 최적화하는 방법으로 벌크 동기식 병렬(bulk synchronous parallel, BSP) 연산 모델이 널리 사용됩니다.
- 맵리듀스에서는 개념적으로 매퍼가 특정 리듀서를 호출해 "메시지를 전달"합니다.
- 맵리듀스 프레임워크가 매퍼의 출력을 동일한 키를 기준으로 수집하기 때문입니다.
- 반복할 때마다 개별 정점에서 함수를 호출해 그 정점으로 보내진 모든 메시지를 전달하는데 이는 프리글과 리듀서를 호출하는 방식이 비슷합니다.
- 맵리듀스와 프리글 모델의 차이점은 정점에서 반복에서 사용한 메모리 상태를 기억하고 있다는 점입니다.
- 프리글 모델은 액터 모델과 살짝 비슷하며, 각 정점은 액터로 본다면 정점 상태를 제외하고 정점 사이의 메시지는 내결함성과 지속성이 있습니다.
#
내결함성- 정점이 서로 질의하는 방식이 아니라 메시지 전달로 통신한다는 점은 프리글 작업 성능 향상에 도움을 줍니다.
- 네트워크 상의 문제로 메시지가 사라지거나 중복, 지연되더라도 프리글 구현상 다음 반복에서 메시지는 목적지 정점에서 정확히 한 번만 처리되빈다.
- 이러한 내결함성은 반복이 끝나는 시점에 모든 정점의 상태를 주기적으로 체크포인트로 저장함으로써 보장됩니다.
#
병렬 실행- 정점은 어떤 물리 장비에서 실행되는지 알 필요가 없기 때문에 메시지를 다른 정점으로 보낼 때 단순히 정점 ID를 사용해 메시지를 전달합니다.
- 프로그래밍 모델은 한 번에 정점 한 개를 다루기 때문에 프리글 프레임워크가 임의의 방법으로 그래프를 파티셔닝할 수 있습니다.
- 결과적으로 그래프 알고리즘은 장비간 통신 오버헤드가 많이 발생합니다.
- 이러한 이유로 그래프가 단일 컴퓨터 메모리에 넣즐 수 있는 크기라면 단일 장비 알고리즘이 분산 일괄 처리보다 훨씬 성능이 좋을 가능성이 있습니다.
#
고수준 API와 언어- 10000대가 넘는 장비로 구성된 클러스터 상에서 페타바이트급의 데이터를 충분히 저장하고 처리할 정도로, 일괄 처리를 운영하는 문제가 거의 해결했으며 다른 분야에 더 집중할 수 있게 되었습니다.
- 직접 맵리듀스 작업을 작성하는 일은 상당히 어렵기 때문에 하이브, 피그, 캐스캐이딩, 크런치와 같은 고수준 언어나 API가 인기를 끌었습니다.
- 이렇게 데이터플로 API는 일반적으로 관계형 스타일의 빌딩 블록을 사용해 연산을 표현합니다.
- 고수준 인터페이스는 코드를 적게 작성해도 되는 명백한 이점뿐만 아니라 대화식 사용도 지원합니다.
- 고수준 인터페이스를 사용하면 사용자가 시스템의 생산성 높게 사용할 수 있을 뿐 아니라 장비 수준에서도 작업을 더욱 효율적으로 수행할 수 있습니다.
#
선언형 질의 언어로 전환- 조인을 수행하는 코드를 작성하는 방식에 비해 관계형 연산자로 조인을 나타내면 프레임워크가 조인 입력의 속성을 분석해 자동으로 앞서 기술한 조인 알고리즘 중에 어떤 방법이 적절한지 자동으로 결정할 수 있다는 장점이 있습니다.
- 어떤 조인 알고리즘을 선택하느냐에 따라 일괄 처리 작업의 성능이 크게 달라집니다.
- 선언적인 방법으로 조인을 지정하면 가능합니다.
- 맵리듀스와 맵리듀스의 데이터플로 계승자들은 SQL의 완전한 선언형 질의 모델과는 다릅니다.
- 맵리듀스는 함수 콜백 개념으로 각 레코드 또는 레코드 그룹을 입력으로 사용자 정의 함수를 호출합니다.
- 코드를 임의로 실행할 수 있다는 점은 MPP 데이터베이스와 맵리듀스를 계승하는 일괄 처리 시스템을 오랜 세월 동안 구별해준 특성입니다.
- 데이터플로 엔진도 조인 외에 좀 더 선언적인 기능을 통합하면 이점이 있습니다.
- 디스크에서 필요한 칼럼만 읽을 수 있습니다.
- 고수준 API에 선언적 측면을 포함하면서 실행 중에 이용할 수 있는 질의 최적화기를 가진다면 일괄 처리 프레임워크는 MPP 데이터베이스와 한승 비슷해집니다. (성능적으로도)
- 동시에 일괄처리 프레임워크는 임의의 코드를 실행하고 임의 형식의 데이터를 읽을 수 있는 확장성을 지녀 일괄처리 프레임워크의 장점인 유연성은 그대로 유지합니다.
#
다양한 분야를 지원하기 위한 전문화- 재사용 가능한 공통 빌딩 블록을 구현하는 일은 가치가 있습니다.
- 이러한 분야로 비즈니스 정보 분석, 통계학, 수치 알고리즘, 염기 서열 분석 등이 있습니다.
- 일괄 처리 엔진은 광범위한 영역에서 필요한 알고리즘을 분산 수행하는데 사용됩니다.
- 일괄 처리 시스템은 내장 기능과 고수준 선언적 연산자를 모두가 가지고 있고 MPP 데이터베이스는 유연해져서 비슷해 보입니다.
- 일괄 처리 시스템과 MPP 데이터베이스 모두 결국에는 데이터를 저장하고 처리하는 시스템이기 때문입니다.
#
정리- 맵리듀스의 설계 원리는 입력은 불변이고 출력은 다른 프로그램의 입력으로 사용됩니다.
- 유닉스 환경에서 프로그램과 다른 프로그램을 연결하는 단일 인터페이스는 파일과 파이프입니다. 맵리듀스의 인터페이스는 분산 파일 시스템입니다.
- 분산 일괄 처리 프레임워크는 두가지 중요한 문제를 고려해야합니다.
- 파티셔닝
- 내결함성
- 맵리듀스에서 사용하는 몇 가지 조인 알고리즘은 다음과 같습니다.
- 정렬 병합 조인
- 브로드캐스트 해시 조인
- 파티션 해시 조인
- 분산 일괄 처리 엔진은 의도적으로 제한된 프로그래밍 모델을 제공합니다.
- 프레임워크는 작업의 최종 출력이 결함이 생기지 않을 때와 동일하게 보장됩니다.
- 이러한 신뢰성의 시맨틱은 온라인 서비스에서 사용자 요청을 처리하고 그 부수 효과로 데이터베이스에 기록하는 것에 비해 훨씬 강력합니다.
- 일괄 처리 작업의 차별화된 특징은 입력을 수정하지 않고 입력을 읽어 출력을 생산한다는 점입니다. (즉, 출력은 입력으로부터 파생됩니다.)
- 결정적으로 입력 데이터는 고정된 크기로 한정됩니다.
다음의 장에서는 스트림 처리를 다루며 스트림 처리는 입력이 한정되지 않습니다.