10. Batch Processing

이전 파트들에서는 요청과 쿼리, 그에 대응하는 응답이나 결과에 대해 알아보았다. 온라인 시스템에서는 그것이 페이지를 요청하는 웹 브라우저거나 원격 API를 호출하는 서비스이건 간에 요청은 사람에 의해 유발되고 그 사람이 반응을 기다리는 것으로 가정된다. 하지만 요청/대응 스타일의 상호작용만이 시스템을 만드는 법은 아니며 다른 접근법들도 장점이 존재한다.

  • 서비스(온라인 시스템) : 서비스는 도달할 클라이언트로부터 요청이나 지시를 받는다.
  • 배치 프로세싱 시스템(오프라인 시스템): 배치 프로세싱 시스템은 대량의 입력 데이터를 받아 그것을 처리하는 작업을 하고 어떠한 출력 데이터를 낸다.
  • 스트림 프로세싱 시스템(근 리얼타임 시스템): 스트림 프로세싱 시스템은 온라인과 오프라인/배치 프로세싱 중간 어딘가에 있다 (그래서 근 리얼타임 또는 니어라인 프로세싱이라고도 한다)

배치 프로세싱은 신뢰성 있고 확장성 있고 유지보수 가능한 애플리케이션을 만들기 위한 중요한 벽돌이다. MapReduces는 여러 해 이전에 데이터 웨어하우스를 위해 개발된 병렬 처리 시스템과 비교하면 상당히 저수준의 프로그래밍 모델이지만, 상품 하드웨어에서 얻을 수 있는 범위의 처리에서는 큰 발전을 이루었다. 사실, 배치 프로세싱은 대단히 오래된 형태의 연산이다. 이 장에서는 MapReduce와 다른 여러 배치 프로세싱 알고리즘과 프레임워크를 알아보고 그것이 현대 데이터 시스템에서 어떻게 쓰이는지를 살펴볼 것이다.

Batch Processing with Unix Tools

간단한 예를 알아보자.

Simple Log Analysis

여러 도구들은 이 로그 파일들을 받아 웹사이트에 대한 깔끔한 보고를 해 주지만, 연습용으로 기본 Unix 툴을 사용해 보자. 이는 단순해 보일 수 있지만 매우 강력하다. Unix 도구들은 익힐만한 가치가 있다.

Chain of commands versus custom program

Unix 명령어 여러 개를 쓰는 대신 단순한 루비 프로그램으로 같은 일을 할 수 있다.

Sorting versus in-memory aggregation

루비 프로그램과 Unix 프로그램 중 어느 것이 낫나? 서로 다른 URL을 몇 개 가졌는지에 따라 다르다. 작업하는 집합이 가용 메모리보다 크면 정렬하는 것이 낫다. 아니면 인-메모리 집합이 낫다.

The Unix Philosophy

이것은 사실 Unix의 핵심 설계 아이디어 중 하나이며 현재까지도 크게 연관이 있다. Unix의 설계 철학은 다음과 같다: 자동화, 빠른 프로토타이핑, 점진적 반복, 실험에 대한 친화성, 큰 프로젝트를 관리 가능한 조각들로 줄이는 것. sort 도구는 이것을 만족시키는 좋은 예이다. bash 같은 Unix 쉘은 이런 작은 프로그램들을 쉽게 조립해 강력한 데이터 처리 작업을 할 수 있게 해 준다.

A uniform interface

한 프로그램의 출력이 다른 프로그램의 입력이 되려면 이 프로그램들은 같은 데이터 포맷, 즉 호환가능한 인터페이스를 사용해야 한다. Unix에서는 그 인터페이스는 파일이다. 전통적으로 많은 Unix 프로그램들은 이 바이트 서열들을 ASCII 텍스트로 다룬다. 각 기록들에 대한 파싱은 조금 더 모호하다. ASCII 텍스트의 균일한 인터페이스는 거의 작동하지만 그렇게 아름답지는 않다. 완벽하지는 않지만, 수십 년이 지나더라도, Unix의 균일한 인터페이스는 눈여겨볼만 하다. 같은 데이터 모델을 가진 데이터베이스도 서로간 호환이 쉽지 않기 때문이다.

Separation of logic and wiring

Unix 도구의 또 다른 특징은 표준 입출력의 사용이다. 프로그램은 필요하면 파일을 직접 읽고 쓸 수 있지만, Unix의 접근법은 프로그램이 특정 파일 경로를 신경 써야 할 필요가 없이 stdin과 stdout을 쓰기만 하면 될 때 가장 효과적이다. 심지어 직접 프로그램을 쓴 뒤 운영 체제에서 제공되는 도구들로 이들을 병합할 수도 있다. 하지만 stdin과 stdout으로 모든 것을 할 수 있는 건 아니다.

Transparency and experimentation

Unix 도구를 성공적으로 만드는 것의 일부분은 무엇이 이루어지는지 보기 쉽다는 것에 있다. Unix 도구들이 관계형 데이터베이스의 쿼리 최적화자에 비하면 뭉툭하고 원시적일 수 있지만, 이들은 특히 실험에 대해서는 매우 유용하다. 하지만 Unix 도구의 가장 큰 한계는 단일 기계에서만 동작한다는 것이며 이 때문에 Hadoop 같은 도구들이 도입되었다.

MapReduce and Distributed Filesystems

MapReduce는 Unix 도구와 같지만 수천 개의 기기까지도 분산되어 있다. 대부분의 Unix 도구와 비슷하게, MapReduce 작업을 수행하는 것은 대개 입력을 수정하지 않으며 출력을 만드는 것 외에 다른 부가 효과를 갖지 않는다. Unix 도구가 stdin과 stdout을 입력/출력으로 사용하듯, MapReduce는 분산형 파일시스템 내의 파일을 읽고 쓴다. HDFS 외에도 여러 많은 분산형 파일 시스템이 존재한다. HDFS는 네트워크 부착 저장소(NAS)와 저장소 영역 네트워크(SAN) 구조의 공유 디스크 접근과는 달리 공유 없음 원리에 기반한다. HDFS는 각 기기에서 동작하는 데몬 프로세스로 이루어지고 다른 노드들이 그 기기에 저장된 파일에 접근하는 것을 허용하는 네트워크 서비스를 노출시킨다. 이는 데이터 센터 내 모든 범용 기기가 그에 부착된 디스크가 존재한다는 것을 가정한다. 기기와 디스크 고장을 대처하기 위해, 파일 블록은 복수 기기에 복제된다. HDFS는 잘 확장된다.

MapReduce Job Execution

MapReduce는 HDFS와 같은 분산형 파일 시스템 내의 큰 데이터셋을 처리하는 코드를 쓰기 위한 프로그래밍 프레임워크이다. 이는 입력 파일의 집합을 읽어 기록들로 분해하고, 매퍼 함수를 호출해 각 입력 기록에서 키와 값을 추출하고, 이 쌍을 키로 정렬한 뒤, 리듀서 함수를 호출해 정렬된 키-값 쌍에 대해 반복한다. MapReduce 작업을 생성하기 위해서는 매퍼와 리듀서 두 가지 함수를 구현해야 한다. 매퍼는 각 입력 기록마다 한 번씩 호출되어 그 입력 기록으로부터 키와 값을 추출한다. 리듀서는 값의 모임에 대한 반복자에 대해 호출되는 함수이다. MapReduce에서는 두 번째 정렬 작업이 필요하다면 두 번째 MapReduce 작업을 쓴 뒤에 첫 번째 작업의 출력을 두 번째 작업의 입력으로 쓰면 된다.

Distributed execution of MapReduce

Unix 명령어 파이프라인과의 핵심 차이점은 MapReduce는 병렬화를 명시적으로 다루는 코드를 쓸 필요 없이 여러 기기간 연산을 병렬화할 수 있다는 것이다. 표준 Unix 도구를 분산형 연산에서 매퍼와 리듀서로 사용하는 것도 가능하지만 대개 이들은 전통적 프로그래밍 언어의 함수들로 구현된다. 이 병렬화는 파티셔닝에 기반한다. 각 입력 파일은 대개 수백 메가바이트의 크기를 갖는다. 대부분의 경우에 맵 작업에서 동작해야 하는 애플리케이션 코드는 수행될 작업이 배정된 기계에 아직 존재하지 않으므로 MapReduce 프레임워크는 우선 코드를 적절한 기기에 복사한다. 연산의 리듀스 쪽도 파티션된다. 키-값 쌍들은 정렬되어야 하며, 데이터 셋은 단일 기기에서의 전통적인 정렬 알고리즘으로 정렬되기에는 너무 클 수 있다. 매퍼가 입력 파일을 읽는 것을 끝내고 정렬된 출력 파일을 쓸 때, MapReduce 스케쥴러는 리듀서들에게 그 매퍼로부터의 출력 파일을 가져오는 것을 시작할 수 있다는 것을 알린다. 리듀스 작업은 매퍼로부터 파일들을 받아 정렬된 순서를 보존하면서 이들을 병합한다. 리듀서는 키와 그 키에 대한 모든 기록을 순차적으로 순회하는 반복자에 대해 호출된다.

MapReduce workflows

단일 MapReduce 작업으로 해결할 수 있는 문제의 범위는 제한되어 있다. 그러므로 MapReduce 작업들이 여러 개 연결되어 워크플로우가 되는 것은 흔하다. 이 경우 한 작업의 출력이 다음 작업의 입력이 된다. 연결된 MapReduce 작업들은 Unix 명령어의 파이프라인 (한 프로세스의 출력을 작은 메모리 상주 버퍼만을 사용해 다른 프로세스의 입력으로 직접 전달하는)은 덜 닮았으며 그것보다는 각 명령의 출력이 임시 파일에 써지고 다음 명령어가 그 임시 파일을 읽는 연속된 명령어들의 나열에 더 가깝다. 배치 작업의 출력은 그 작업이 성공적으로 완료되었을 때만 올바르다고 간주된다 (MapReduce는 실패한 작업의 부분적 출력은 버린다). 이 스케쥴러들은 배치 작업의 큰 모임을 유지하는 데 쓸모 있는 관리 특성들도 갖고 있다. Haddp에 대한 여러 고수준 도구들도 복수의 MapReduce 단계들을 적절하게 묶어 워크플로우를 셋업한다.

Reduce-Side Joins and Grouping

연결 연산은 어떻게 구현될까? 많은 데이터셋에서 한 기록은 다른 기록에 대한 연관을 가진 경우가 많다. 데이터베이스에서 작은 수의 기록만을 포함하는 쿼리만 실행한다면 데이터베이스는 대개 인덱스를 사용해서 관련된 기록들을 빠르게 위치시킬 것이다. MapReduce에는 인덱스가 없기 때문에 파일의 집합을 입력으로 받으면 이 파일들의 내용 전부를 읽는다. 배치 처리의 관점에서 연결을 말할 때에는 데이터셋 내에서의 연관의 모든 출현을 해결하는 것을 말한다.

Example: analysis of user activity events

배치 작업의 연결의 예로는 웹사이트에서 로그인된 사용자가 한 이벤트 로그들과 사용자 데이터베이스를 연관짓는 것이 있다. 분석적 작업은 사용자 활동을 사용자 프로필 정보와 연관지을 필요가 있다. 이 연결 연산의 가장 간단한 구현은 활동 이벤트를 하나씩 순회한 뒤 그것이 마주하는 모든 사용자 ID에 대해 사용자 데이터베이스를 쿼리할 것이다. 배치 과정에서 좋은 처리량을 얻기 위해서는 이 연산은 가능한 한 한 기기에 대해 로컬이어야 한다. 그러므로 더 나은 접근법은 사용자 데이터베이스에 대한 복제를 받은 뒤 이를 사용자 활동 이벤트의 로그로서 같은 분산형 파일 시스템 내에 놓는 것이다.

Sort-merge joins

매퍼의 일은 각 입력 기록으로부터 키와 값을 추출하는 것이라는 것을 상기시켜 보자. MapReduce 프레임워크가 매퍼 출력을 키로 분할하고 키-값 쌍들을 정렬할 때, 그 효과는 같은 사용자 ID에 대한 모든 활동 이벤트와 사용자 기록이 리듀서 입력 내에서 서로 인접하게 된다는 것이다. 그러면 리듀서는 각 연결 로직을 쉽게 수행할 수 있다. 리듀서는 특정 사용자 ID에 대한 모든 기록들을 한 번에 처리할 수 있으므로, 한 사용자 기록을 한 시점에만 메모리에 담고 있기만 하면 되고, 네트워크 전체에 대한 요청은 할 필요가 없다.

Bringing related data together in the same place

정렬-병합 연결에서는, 매퍼와 정렬 프로세스가 특정한 사용자 ID에 대한 연결 연산을 수행하기 위한 모든 필요한 데이터가 리듀서의 단일 호출에 대한 같은 위치로 모이는 것을 보장한다. 이 구조를 보는 하나의 방식은 매퍼가 리듀서에 메시지를 전송하는 것으로 보는 것이다. MapReduce 프로그래밍을 사용하는 것은 연산의 물리적 네트워크 통신 부분 (데이터를 올바른 기기로부터 얻는 것)을 애플리케이션 로직 (데이터를 얻은 뒤 처리하는 것)과 분리시켰다.

GROUP BY

연결 이외에도, 관계된 데이터를 같은 위치로 놓는 것의 흔한 다른 사용처는 어떤 키에 대해 기록을 그룹핑하는 것이다. 이런 그룹핑 연산을 MapReduce로 구현하는 가장 간단한 방법은 매퍼를 셋업해 이들이 만드는 키-값 쌍들이 원하는 그룹핑 키를 쓰도록 하는 것이다. 그룹핑의 또 다른 흔한 용례는 특정 사용자 세션의 모든 활동 이벤트를 모아 그 사용자가 행한 모든 동작의 나열을 추적하는 것 (세션화)이다. 사용자 요청을 다루는 복수의 웹 서버가 있다면 특정 사용자의 활동 이벤트는 여러 다른 서버의 로그 파일에 분산되어 있을 가능성이 높다.

Handling skew

같은 키의 모든 기록을 같은 장소로 모으는 패턴은 단일 키에 관계된 데이터의 양이 아주 많다면 실패하게 된다. 이런 핫 키에 대한 모든 활동을 단일 리듀서에 모으는 것은 심한 쏠림을 낳을 수 있다. 연결 입력이 핫 키를 갖고 있다면 이를 보정하기 위한 몇 개의 알고리즘이 있다. 이런 기법들은 핫 키를 다루는 작업을 여러 리듀서로 분산해 병렬화가 더 잘 되게 하지만, 다른 연결 입력들을 복수의 리듀서로 복제하는 비용을 감수해야 한다. Hive의 쏠린 연결 최적화는 다른 접근법을 쓴다. 핫 키의 기록들을 그룹핑하고 모을 때에는 두 단계의 그룹핑을 할 수 있다.

Map-Side Joins

직전 섹션에서 다루었던 연결 알고리즘은 실제 연결 로직을 리듀서에서 수행하므로 리듀스 쪽 연결으로 알려져 있다. 리듀스 쪽 접근은 입력 데이터에 대한 가정을 할 필요가 없다는 장점이 있다. 그에 반면, 입력 데이터에 대한 특정한 가정을 할 수 있다면 맵 쪽 연결을 함으로써 연결을 더 빠르게 할 수 있다.

Broadcast hash joins

맵 쪽 연결을 수행하는 가장 간단한 방법은 큰 데이터셋이 작은 데이터셋과 연결될 때 적용된다. 이 경우 매퍼가 시작될 때 분산형 파일 시스템의 사용자 데이터베이스를 메모리 상주 해시 테이블로 읽어들일 수 있다. 이 때에도 매퍼 작업은 여러 개 있을 수 있다. 이 단순하지만 효과적인 알고리즘은 방송 해시 연결이라 불린다. 작은 연결 입력을 메모리 상주 해시 테이블로 불러오는 대신, 그 대안은 작은 연결 입력을 로컬 디스크 내 읽기 전용 인덱스로 저장하는 것이다.

Partitioned hash joins

맵 쪽 연결의 입력이 같은 방식으로 분할되어 있다면 해시 연결 접근법은 각 파티션에 독립적으로 적용될 수 있다. 파티셔닝이 올바르게 적용된다면 연결시키고자 하는 모든 기록이 같은 번호의 파티션에 위치함을 보장할 수 있으며 각 매퍼들이 입력 데이터셋 각각에서 하나의 파티션만을 읽는 것으로 충분하다. 이 방법은 양 쪽 연결의 입력들이 같은 수의 파티션을 가지면서 파티션에 할당된 기록들이 같은 키와 같은 해시 함수에 기반했을 때만 사용이 가능하다. 파티션된 해시 연결은 Hive에서는 버킷 맵 연결으로 불린다.

Map-side merge joins

맵 쪽 연결의 또 다른 변형은 입력 데이터 셋이 같은 방식으로 파티션되어 있을 뿐만 아니라 같은 키를 기반으로 정렬되었을 때 적용될 수 있다. 맵 쪽 병합 연결이 가능할 때에는 이전 MapReduce 작업들이 입력 데이터셋을 처음 분할되고 정렬된 형태로 만들었음을 뜻한다.

MapReduce workflows with map-side joins

MapReduce 연결의 출력이 아래쪽 작업에 의해 소모될 때, 연결이 맵 쪽인지 리듀스 쪽인지에 대한 선택은 출력의 구조에 영향을 미친다. 맵 쪽 연결은 입력 데이터셋의 크기, 정렬, 파티셔닝에 대해 더 많은 가정을 한다. Hadoop 생태계에서 데이터셋의 파티셔닝에 대한 이런 종류의 메타데이터는 HCatalog나 Hive 메타스토어에 의해 유지되고는 한다.

The Output of Batch Workflows

배치 워크플로우의 결과물은 어떻게 되나? 데이터베이스 쿼리들의 경우에는 트랜잭션 처리(OLTP) 목적은 분석적 목적들과는 구분된다. 배치 프로세싱은 트랜잭션 처리도 아니고 분석도 아니지만 분석에 가깝다.

Building search indexes

Google의 MapReduce의 원 사용처는 검색 엔진을 위한 인덱스를 만들기 위한 것이었으며 이는 5~10 MapReduce 작업으로 이루어진 워크플로우로 구현되었다. 완전 텍스트 검색 인덱스는 특정 키워드를 효율적으로 검색할 수 있고 그 키워드를 포함하는 모든 문서 ID의 목록을 ㅛ율적으로 찾을 수 있는 파일로 구현된다. 고정된 문서 집합에 대해 완전 텍스트 탐색을 수행한다면 배치 처리는 인덱스를 만드는 매우 효율적인 방법이다. 문서의 인덱스된 집합이 변경된다면 한 방법은 문서 집합 전체에 대해 전체 인덱싱 워크플로우를 주기적으로 재수행해 작업이 끝나면 이전의 인덱스 파일 전체를 대체하는 것이다. 대안으로는 인덱스를 점진적으로 만드는 방법도 있다.

Key-value stores as batch process output

검색 인덱스는 배치 처리 워크플로우의 가능한 출력의 하나의 예일 뿐이다. 이런 배치 작업의 출력은 종종 데이터베이스의 종류이기도 하다. 이런 데이터베이스는 사용자 요청을 처리하는 웹 애플리케이션으로부터 쿼리되어야 하고 이는 대개 Hadoop 인프라로부터 분리되엉 ㅣㅆ다. 이런 때 가장 흔한 선택은 당신의 데이터베이스에 대한 클라이언트 라이브러리를 매퍼나 리듀서 내에서 직접 사용한 뒤 데이터베이스 서버에 직접 배치 작업으로부터 한 번에 한 기록씩 쓰는 것이다. 하지만 각 기록에 대한 네트워크 요청을 하는 것은 느리고, 데이터베이스에 과부하가 걸릴 수 있고, 부분적으로 완료된 작업들이 다른 시스템에 보여진다는 문제점이 있다. 더 좋은 해법은 배치 작업 내부에서 새 데이터베이스를 만들고 분산된 파일시스템 내 작업의 출력 디렉토리 내 파일로 쓰는 것이다 – 직전 절에서 검색 인덱스처럼. 이런 데이터베이스 파일을 만드는 것은 MapReduce의 좋은 사용례이다. Voldemort로 데이터를 불라올 때 서버는 새 데이터 파일이 분산 파일 시스템으로부터 서버의 로컬 디스크로 복사될 때 낡은 데이터 파일에 대해 요청을 처리하는 것을 계속한다.

Philosophy of batch process outputs

이전에 다루었던 Unix 철학은 실험이 데이터 흐름에 대해 매우 명시적일 것을 유도한다. MapReduce 작업의 출력 처리는 같은 철학을 따른다. 코드에 버그가 생겼고 출력이 오염되면 코드를 이전 버전으로 롤백해 작업을 다시 수행하면 출력은 다시 교정된다. 롤백이 쉽기 때문에 특성 개발은 실수가 비가역적인 피해를 입히는 환경에 비해서는 훨씬 빠르게 이루어진다. 맵이나 리듀스 작업이 실패하면 MapReduce 프레임워크는 자동적으로 재 스케쥴링한 뒤 같은 입력에 대해 다시 수행된다. 같은 파일 집합은 여러 다른 작업에 대해 입력으로 쓰일 수 있다 – 지표를 계산하는 작업에 대한 모니터링과 작업의 출력이 예상한 특성을 가졌는지를 평가하는 것. Unix 도구와 같이, MapReduce 작업은 논리와 연결을 분리하고 이는 고려 사항을 분리하고 코드의 재사용을 가능케 한다. 이런 영역들에서, Unix에 잘 작동한 설계 철학은 Hadoop에 대해서도 잘 작동한 것으로 보인다. 하지만 Unix와 Hadoop은 어떤 면에서는 다르다.

Comparing Hadoop to Distributed Databases

Hadoop은 어떻게 보면 Unix의 분산형 버전으로도 볼 수 있고 HDFS는 파일시스템, MapReduce는 Unix 프로세스의 변형으로 볼 수 있다. MapReduce 논문이 출판되었을 때 그것은 그다지 새로운 것이 아니었다. 가장 큰 차이점은 MPP 데이터베이스는 분석적 SQL 쿼리들의 병렬 실행을 기기의 클러스터에 대해 초점을 맞추었지만 MapReduce와 분산형 파일 시스템의 조합은 임의의 프로그램을 실행할 수 있는 범용 운영 체제와 같은 것을 제공했다는 것이다.

Diversity of storage

데이터베이스는 특정 모델에 따라 데이터를 구조화할 것을 요구한다. 반면에 분산 파일시스템 내 파일은 그저 바이트 서열이며 어느 데이터 모델이나 인코딩으로도 쓰일 수 있다. Hadoop은 데이터를 무비판적으로 HDFS에 덤핑하는 가능성을 열어 놓았고 나중에 그것을 어떻게 더 처리할지를 계산해 내었다. 그 반면에 MPP 데이터베이스는 데이터와 쿼리 패턴에 대한 주의 깊은 사전 모델링을 데이터베이스의 저장소 포맷으로 데이터를 임포트하기 이전 요구한다. 이것은 바람직해보일지 모르지만 데이터를 빠르게 가용가능하게 만드는 것이 더 중요하다. 이런 무차별적 데이터 덤핑은 부담을 데이터를 표현하는 것으로 옮겨놓는다. 그래서 Hadoop은 ETL 프로세스를 구현하는 데 종종 쓰였다.

Diversity of processing models

MPP 데이터베이스는 단일체의 단단히 통합된 소프트웨어 조각들로서 디스크 내 저장소 레이아웃, 쿼리 계획, 스케쥴링, 실행에 있어 주의를 요한다. 반면, 모든 종류의 처리가 SQL 쿼리로 분별 있게 처리될 수 있는 것은 아니다. MapReduce는 엔지니어들에게 큰 데이터셋에 대해 자신의 코드를 쉽게 수행할 수 있게 한다. 이후에, 사람들은 MapReduce는 너무 제한적이고 어떤 종류의 처리에 대해서는 너무 성능이 나쁜 것을 발견했으므로, Hadoop 위에서 여러 다른 처리 모델들이 개발되었다. 결정적으로, 이런 여러 처리 모델은 전부 단일 공유 사용 기기 클러스터에 대해, 모든 기기들이 분산 파일 시스템의 같은 파일들을 접근하면서 사용될 수 있다. Hadoop 생태계는 HBase와 같은 무작위 접근 OLTP 데이터베이스와 Impala와 같은 MPP 스타일 분석적 데이터베이스를 모두 포함한다.

Designing for frequent faults

MapReduce를 MPP 데이터베이스와 비교할 때에는 설계 접근에 있어 두 가지 차이점이 드러난다. 실패에 대한 처리와 메모리/디스크의 사용. 쿼리가 실행 중 노드가 크래시되면 MPP 데이터베이스는 대부분 전체 쿼리를 중단하고 사용자가 쿼리를 재제출하도록 하거나 자동적으로 이를 재수행한다. 반면 MapReduce는 맵이나 리듀스 작업의 실패를 작업의 재시도를 각 작업의 단계에서 수행함으로써 전체 작업에 영향을 끼치지 않는 방식으로 대응한다. MapReduce 접근 방식은 큰 작업들에 대해 더 적합하다. 하지만 이런 가정들이 현실적인 것일까? MapReduce의 인색한 메모리 사용과 작업 레벨 복구의 이유를 이해하기 위해서는 MapReduce가 본래 설계된 대상 환경을 이해할 필요가 있다. 이런 혼용 데이터베이스의 구조는 생산 중이지 않은 (저 우선도의) 연산 자원들이 능력 이상으로 할당되도록 허용하는데, 시스템은 필요 시에 자원들을 되찾을 수 있음을 알기 때문이다. Google에서는, 한 시간동안 동작하는 MapReduce 작업은 약 5%의 확률로 더 우선도 높은 프로세스에 의해 선점될 위험성을 갖고 있다. 그리고 이것이 MapReduce가 예상치 못한 잦은 작업 중단에 대처하는 방식으로 설계되었기 때문이다. 오픈 소스 클러스터 스케쥴러에 대해서는 선점은 덜 폭넓게 쓰인다.

Beyond MapReduce

MapReduce가 2000년대 후반에 매우 높은 명성을 얻고 인기를 많이 얻기는 했지만, 단지 분산 시스템에 대한 많은 가능한 프로그래밍 모델 중 하나일 뿐이다. 그럼에도 불구하고 MapReduce를 많이 언급한 이유는 좋은 학습 도구이면서 분산형 파일 시스템 위의 매우 깔끔하고 단순한 추상화이기 때문이다. MapReduce를 직접적으로 쓰는 데 있어서의 어려움에 대응해, 여러 고수준 프로그래밍 모델들이 MapReduce 위의 추상화로서 만들어졌다. 그러나 MapReduce 실행 모델 그 자체에도 문제가 있는데, 이는 다른 추상화 층을 더함으로서도 고쳐지지 않았으며 어떤 처리에서는 나쁜 성능을 드러내었다. 배치 처리에서 다른 대안들을 알아보자.

Materialization of Intermediate State

모든 MapReduce 작업은 다른 모든 작업과 독립적이다. 이 세팅은 첫 작업의 출력이 조직 내에서 넓게 출판되는 데이터셋일 경우 적합하다. 하지만 한 작업의 출력이 다른 하나의 작업의 입력으로만 쓰이고 같은 팀에 의해 유지보수될 수 있다. 이 경우에는 중간 상태를 물질화하지 않는 과정이 더 적합할 수 있다. 반면에, Unix 파이프는 완전히 중간 상태를 물질화하지 않았고 한 명령의 출력을 다음 명령의 입력으로 스트림화했다. MapReduce의 중간 상태를 완전히 물질화하는 과정은 Unix 파이프에 대해 단점을 갖고 있다. MapReduce 작업은 이전 작업들이 전부 끝난 상태에서만 시작될 수 있다. 매퍼들은 때로는 불필요하다. 임시 데이터들이 분산형 파일 시스템에서 복제되는 것은 불필요할 수 있다.

Dataflow engines

MapReduce의 이러한 문제들을 고치기 위해, 분산 배치 연산을 위한 여러 새 실행 엔진들이 개발되었으며, 그 중 가장 잘 알려진 것은 Spark, Tez, Flink 등이다. 이들은 데이터의 흐름을 여러 처리 단계를 통해 명시적으로 모델링하기 때문에, 이 시스템들은 데이터 흐름 엔진이라고도 불린다. MapReduce와는 다르게, 이 함수들은 맵과 리듀스를 교대로 반복하는 강한 규칙을 적용하지 않고, 더 유연한 방식으로 조합될 수 있다. 재파티션하고 기록들을 키로 정렬할 수도 있고, 여러 입력들을 받아 같은 방식으로 파티셔닝하되 정렬을 스킵할 수도 있고, 한 연산자로부터의 같은 출력이 연결 연산의 모든 파티션으로 보내질 수 있다. 이 처리 엔진 스타일은 Dryad와 Nephele와 같은 연구 시스템에 기반했으며 MapReduce에 비해 여러 이점을 갖고 있다. 정렬 같은 비싼 연산이 필요할 때만 사용되며, 불필요한 매핑 작업이 없으며, 모든 워크플로우 내 연결과 데이터 의존성이 명시적으로 선언되므로, 스케쥴러는 어떤 데이터가 어디에 필요한지 알 수 있고 국소적 최적화를 할 수 있다. 또한 중간 상태들이 메모리나 국소적 디스크에 써지므로, HDFS에 쓰는 것보다 적은 입출력을 필요로 한다. 연산자들은 입력이 준비되자마자 바로 동작할 수 있다. 존재하는 자바 가상 기계 프로세스들은 새 연산자를 수행하기 위해 재사용됨으로써 MapReduce에 비해 시작 오버헤드를 줄일 수 있다. 데이터 흐름 엔진을 사용해 MapReduce 워크플로우와 같은 연산들을 구현할 수 있으며, 이들은 여기서 묘사된 최적화들로 인해 더 빠르게 동작할 수 있다. 연산자들은 맵과 리듀스의 일반화이므로 같은 처리 코드는 데이터 흐름 엔진에서도 똑같이 동작할 수 있다.

Fault tolerance

분산형 파일 시스템의 중간 상태를 완전히 물질화하는 것의 이득은 MapReduce에서 실패에 대한 대처를 매우 쉽게 만들 정도로 시스템이 견고해진다는 것이다. 이에 반해 Spark, Flink, Tez는 HDFS에 중간 상태를 쓰지 않으므로 실패에 대한 대처를 다른 접근법을 쓴다. 이 재연산을 가능케 하기 위해, 프레임워크는 주어진 데이터가 어떻게 계산되었는지를 추적해야 한다. 어떤 입력 파티션을 썼는지, 어떤 연산자가 적용되었는지. 데이터를 재계산할 때, 해당 연산이 결정론적인지를 아는 것이 중요하다. 연속적인 실패를 피하기 위해, 연산자들을 결정론적으로 만드는 것이 좋다. 데이터를 재계산해서 실패로부터 복구하는 것은 항상 정답이 되는 것은 아니다. 중간 데이터를 물질화하는 것이 더 나을 수도 있다.

Discussion of materialization

Unix와의 유사점으로 돌아가 보면, MapReduce는 각 명령어의 출력을 임시 파일에 쓰는 것과 비슷하고, 데이터 흐름 엔진은 Unix 파이프와 비슷하다. 정렬 연산은 출력을 내기 전 전체 입력을 소모해야 할 수밖에 없는데, 마지막 입력이 가장 낮은 키여서 맨 처음 출력이 되어야 할 수도 있기 때문이다. 작업이 끝나면, 출력은 내구성이 있는 어디인가로 보내져서 사용자들이 찾을 수 있고 사용할 수 있어야 한다. 대개는 이는 분산형 파일 시스템에 다시 쓰인다.

Graphs and Iterative Processing

데이터를 모델링하는 데 있어 그래프를 사용하는 법을 알아보았다. 배치 처리 맥락에서 그래프를 알아보는 것도 흥미로운데, 여기서의 목표는 전체 그래프에 대한 분석이나 일종의 오프라인 처리를 하는 것이다. 많은 그래프 알고리즘은 한 번에 한 간선을 순회하고, 한 정점을 다른 정점과 연결해 어떤 정보를 전달하고, 이를 어떤 조건이 이뤄질 때까지 반복하는 것 등으로 표현된다. 그래프를 분산 파일 시스템에 저장하는 것도 가능하지만, 이런 “완료될 때까지 반복하는” 발상은 일반 MapReduce에는 표현될 수 없다. 이는 데이터에 대한 단일 패스만 수행하기 때문이다. 이런 알고리즘들은 그래서 반복적으로 구현되고는 한다. 이를 MapReduce로 구현하는 것은 매우 비효율적이다.

The Pregel processing model

그래프를 배치 처리하는 데 있어 최적화하는 것은, 대규모 동기 병렬화 (BSP) 모델의 연산이 인기를 얻었다. MapReduce에서, 매퍼들은 특정한 리듀서 호출에 개념적으로 메시지를 보내는데 프레임워크가 같은 키에 대한 모든 매퍼의 출력을 모으기 때문이다. 각 반복에서, 각 정점마다 함수가 호출되고, 그 정점으로 보내진 모든 메시지를 함수로 보낸다. 이는 리듀서에 대한 호출과 비슷하다. 다른 것은 정점들이 한 반복에서 다른 반복으로 갈 때 메모리의 상태를 기억하기 때문에 함수는 들어오는 새 메시지를 처리하기만 하면 된다는 것이다.

Fault tolerance

정점들이 메시지 전달을 통해서만 통신한다는 사실은 Pregel 작업의 성능을 개선하는 데 도움을 준다. 메시지가 배치화될 수 있고 통신할 필요가 줄어들기 때문이다. 기반하는 네트워크가 메시지를 유실하거나, 중복시키거나, 또는 임의로 지연시키더라도, Pregel 구현들은 메시지가 도착 정점에 정확히 한 번 처리됨을 보장한다. 이런 실패에 대한 처리는 모든 정점의 상태를 반복의 끝마다 주기적으로 체크포인트화하는, 즉 전체 상태를 견고한 저장소에 쓰는 것으로 얻어진다.

Parallel execution

정점들은 그것이 어떤 물리적 기계를 실행 중인지 알 필요가 없다. 프로그래밍 모델은 한 번에 한 정점만 다루기 때문에, 프레임워크는 그래프를 임의로 분할할 수 있다. 그 결과로, 그래프 알고리즘은 종종 기계간 큰 통신 오버헤드를 겪으며, 중간 상태는 원본 그래프보다 커지기도 한다. 이러한 이유로, 그래프가 단일 컴퓨터의 메모리에 들어간다면, 단일 기기 알고리즘이 분산 배치 처리를 성능 면에서 압도할 가능성이 높다.

High-Level APIs and Languages

MapReduce가 처음 인기를 얻었을 때부터, 분산형 배치 처리에 대한 실행 엔진은 숙성되었다. 이전에 논한 대로, Hive, Pig, Cascading, Crunch와 같은 고수준 언어와 API들은 인기를 얻었는데 MapReduce 프로그래밍 작업을 직접 하는 것은 매우 수고가 많이 들기 때문이다. 이런 데이터 흐름 API는 일반적으로 관계형 부분을 사용해 연산을 표현한다. 더 적은 코드를 요구하는 명백한 이점 외에도, 이런 고수준 인터페이스는 상호작용적 사용을 가능케 한다. 즉, 쉘에서 분석 코드를 점진적으로 쓰고 그것을 자주 실행해 무엇을 하는지를 볼 수 있다. 또한, 이러한 고수준 인터페이스는 시스템을 사용하는 사람들을 더 생산적으로 만들 뿐만 아니라, 기계 수준에서의 작업 수행 효율성도 개선시킨다.

The move toward declarative query languages

연결들을 관계형 연산자로 특정하는 이점은, 연결을 수행하는 코드를 직접 쓰는 것과 비교하면, 프레임워크가 연결 입력들의 특성을 분석할 수 있고 이전에 언급된 연결 알고리즘의 어떤 부분이 주어진 작업에 적합한지를 자동적으로 분석할 수 있다는 것이다. 연결 알고리즘의 선택은 배치 작업의 성능에서 큰 차이를 만들 수 있으며, 이 장에서 이야기한 여러 연결 아고리즘을 전부 이해하고 기억할 필요가 없게 된다는 점은 좋은 일이다. 하지만, 다른 방면으로, MapReduce와 그 데이터 흐름 후계자들은 SQL의 완전 선언적인 쿼리 모델과는 매우 다르다. 임의의 코드를 쉽게 가동할 수 있는 자유는 MapReduce의 배치 처리 시스템을 MPP 데이터베이스와 오랜 기간동안 구별지은 것이었다. 하지만, 데이터 흐름 엔진의 개발자들은 연결 이외에도 더 선언적인 특성을 포함하는 것이 이득이 될 때가 있다는 것을 발견했다. 고수준 API의 선언적 방면들을 포함하고, 실행 중 이들의 이득을 취할 수 있는 쿼리 최적화자들을 가짐으로써, 배치 처리 프레임워크는 MPP 데이터베이스처럼 보이게 되었다.

Specialization for different domains

임의의 코드를 실행할 수 있는 확장성은 유용하지만, 표준적 처리 패턴이 계속 재발생하는 많은 범용 용례가 존재한다. 그러므로 공통된 부분에 대해 재사용 가능한 구현을 가지는 것은 가치가 있다. 중요도가 증가하고 있는 다른 영역은 통계적/수치적 알고리즘으로, 이는 분류나 추천 시스템과 같은 기계 학습 애플리케이션에 필요하다. 또 유용한 것은 k-최근접 근방과 같은 공간적 알고리즘으로, 이는 어떠한 다차원 공간 내에 주어진 아이템에서 가장 가까운 아이템을 탐색하는 것으로, 일종의 유사성 탐색이다. 배치 처리 엔진은 폭넓게 증가하고 있는 넓은 영역으로부터 알고리즘의 분산된 실행에 대해 쓰이고 있다.

Summary

이 장에서는 배치 처리의 주제를 알아보았다. awk, grep, sort와 같은 Unix 도구를 알아보는 것으로 시작해서, 이런 도구의 설계 철학이 어떻게 MapReduce와 더 최근의 데이터 흐름 엔진으로 이끌어져 왔는지를 알아보았다. 이러한 설계 철학 중 일부는 입력은 변경 불가능해야 하고, 출력은 다른 프로그램의 입력이 되어야 하며, 복잡한 문제들은 하나의 작업을 잘 하는 작은 도구들을 조합함으로써 풀릴 수 있다는 것이다.

Unix 세계에서는, 한 프로그램이 다른 것들을 조합해 만들어질 수 있게 하는 균일한 인터페이스는 파일과 파이프이다. MapReduce에서, 이 인터페이스는 분산된 파일시스템이다. 데이터 흐름 엔진은 그들의 고유한 파이프 형태의 데이터 전송 메커니즘을 추가해 중간 상태를 분산형 파일 시스템 내로 물질화하는 것을 피하는 것을 보았지만, 작업의 최초 입력과 마지막 출력은 대개 여전히 HDFS 안의 파일들이다.

분산된 배치 처리 프레임워크가 풀어야 하는 두 가지 문제는 다음과 같다:

  • 파티셔닝. MapReduce에서는 매퍼가 입력 파일 블록에 따라 파티션된다. 매퍼의 출력은 재파티션되고, 정렬되고, 병합되어 설정 가능한 숫자의 리듀서 파티션이 된다. 이 과정의 목적은 모든 관계된 데이터 – ex. 같은 키의 모든 기록들 – 을 같은 위치로 모으는 것이다.
    MapReduce 이후 데이터 흐름 엔진은 정렬을 필요하지 않는 한 피하려고 하지만, 이외에는 파티셔닝에 대해 넓게는 비슷한 접근법을 사용한다.
  • 실패에 대한 처리. MapReduce는 빈번하게 디스크에 씀으로써, 각각 실패한 처리로부터 전체 작업을 재시작하지 않고서도 복구가 쉽도록 하지만 실패가 없는 경우에는 실행을 느리게 한다. 데이터 흐름 엔진은 중간 상태에 대해 물질화를 더 적게 하고 메모리에 더 많이 쓴다. 이는 그들이 노드가 실패했을 때 더 많은 데이터를 재계산해야 함을 의미한다. 결정론적 연산자는 재계산되어야 할 데이터의 양을 줄인다.

MapReduce에 대한 여러 연결 알고리즘도 알아보았다. 이들 중 대부분은 MPP 데이터베이스와 데이터 흐름 엔진들에 대해서도 내부적으로 쓰인다. 이들은 파티션된 알고리즘들이 어떻게 동작하는지 좋은 예제를 제공해 준다.

  • 정렬-병합 연결. 연결되는 입력 각각은 매퍼를 통해 이동해서 연결 키가 추출된다. 파티셔닝, 정렬, 병합에 따라, 같은 키의 모든 기록들은 리듀서의 같은 호출에 위치하게 된다. 이 함수는 연결된 기록들을 출력하게 된다.
  • 방송 해시 연결. 두 연결 입력 중 하나가 작다면, 이것은 파티션되지 않고 해시 테이블로 전체로서 불러와질 수 있다. 즉, 큰 연결 입력의 각 파티션으로부터 매퍼를 시작해서, 작은 입력에 대한 해시 테이블을 각 매퍼로 불러와서, 큰 입력에 대해 한 번에 한 기록씩 스캐닝할 수 있으며, 각 기록에 대한 해시 테이블을 쿼리할 수 있다.
  • 파티션된 해시 연결. 두 연결 입력이 같은 방식으로 파티션되었다면 (같은 키를 쓰고, 같은 해시 함수를 쓰고, 같은 수의 파티션을 쓴다면) 해시 테이블 접근법은 각 파티션마다 독립적으로 쓰일 수 있다.

분산된 배치 처리 엔진은 의도적으로 제한된 프로그래밍 모델을 갖는다. 콜백 함수 (매퍼와 리듀서 같은) 들은 상태가 없는 것으로 가정되며 지정된 출력 이외에는 외부에서 보이는 부가 효과가 없어야 한다. 이 제한은 프레임워크가 분산형 시스템들이 갖는 어떤 강한 문제들을 추상화 뒤에 숨긴다. 크래시나 네트워크 문제를 마주했을 때, 작업들은 안전하게 재시도될 수 있으며, 실패한 작업으로부터의 출력은 버려진다. 파티션의 여러 작업이 성공한다면, 그 중 하나만이 그 출력을 실제로 보여직 ㅔ만든다.

프레임워크로 인해, 배치 처리 작업을 하는 코드는 실패에 대처하는 메커니즘을 구현하는 데 있어 걱정할 필요가 없다. 프레임워크는 작업의 최종 출력은 실패가 발생하지 않은 것과 똑같음을 보장한다 – 심지어 실제로는 여러 작업이 재시도되었어야 할지라도. 이러한 신뢰성 있는 시맨틱들은 사용자 요청을 다뤄 요청을 처리하는 부가 효과로 데이터 베이스에 쓰는 온라인 서비스에서 가질 수 있는 시맨틱들보단 훨씬 강한 것이다.

배치 처리 작업의 구별되는 특성은 입력 데이터를 읽어 출력 데이터를 출력하는데, 이 때 입력을 수정하지 않는다는 것이다. 즉, 출력은 입력으로부터 파생된다. 핵심적으로, 입력 데이터는 한계를 갖는다. 이는 알려진, 고정 크기를 가진다. (예를 들면, 이는 특정 시점에서의 로그 파일의 집합으로 이루어졌거나, 데이터베이스의 내용에 대한 스냅샷으로 이루어진다) 이것은 크기에 한계가 있기 때문에, 작업은 전체 입력을 읽는 것이 언제 끝날지를 알고, 작업이 언제 완료되는지도 알 수 있다.

다음 장에서는 스트림 처리를 알아본다. 이 때 입력은 한계가 없다. 즉, 작업이 있지만, 입력은 끝나지 않는 데이터 스트림이다. 이 경우, 작업은 절대 완료 상태가 아닌데, 어떤 시점이든 들어오고 있는 작업이 있을 수 있기 때문이다. 스트림과 배치 처리는 어떤 부분에서는 비슷하지만, 한계가 없는 스트림에 대한 가정은 시스템을 어떻게 만들지에 대해 많은 것을 바꾼다.

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google photo

Google의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중