Category Archives: Hadoop

주키퍼(zookeeper) 설치 가이드 – 멀티 서버 구성

주키퍼를 멀티서버로 설치하여 실행해보자.

주키퍼는 정족수를 기준으로 동작하므로, 멀티서버로 구성된 정상적으로 동작중인 주키퍼의 서버 수가 [환경 설정 파일에 등록된 서버 대수 + 1] / 2보다 작으면 동작하지 않는다. 따라서 일반적으로 주키퍼는 3대로 구성하며, 2대 이상이 다운되어야만 서비스가 중지된다.

먼저 각 서버에 주키퍼 설치파일을 다운로드 한 후, 적당한 위치에 압축을 푼다.

그리고 ${ZOOKEPER_HOME}/conf 디렉토리에 환경 설정파일인 zoo.cfg 파일을 아래와 같이 생성한다.

dataDir은 주키퍼가 관리할 데이터를 저장할 디렉토리다. 당연히 주키퍼를 실행한 사용자가 해당 디렉토리에 접근권한이 있어야 한다.

server.#에는 멀티서버로 구성할 서버를 등록한다. 여기에서는 총 3대를 등록한다. 첫번째 포트(2888)는 주키퍼 클러스터의 리더에 접속하기 위한 포트이며, 두번째 포트(3888)는 리더를 선출하는데 사용한다.

이제 각 서버에서 주키퍼를 실행한다.

주키퍼는 관리를 위한 웹 UI는 따로 제공하지 않으며, 정상적으로 실행되었는지는 로그를 통해 확인할 수 있다. 리더로 선출된 서버는 아래와 같이 LEADING 메시지가 출력된다.

그리고 리더가 아닌 서버는 아래와 같이 FOLLOWING 메시지가 출력된다.

 

하이브(Hive) 사용하기

몇 년전에 한창 하이브로 쿼리 짤 때 썼던 글이다. 그때가 지금보다 더 많이 알았던 것 같다.

하이브 설치하기

설치하기

하이브를 다운로드 한 후 압축을 적당한 위치에 푼다. 하이브를 실행하려면 HADOOP_HOME을 환경변수로 추가해야 한다. $HIVE_HOME/conf/hive-env.sh 파일에 HADOOP_HOME 변수를 설정한다.

콘솔 실행하기

아래와 같이 하이브 콘솔을 실행할 수 있다.

테이블 목록을 조회해 본다.

명령어를 처음 실행하는 경우 로컬에 메타스토어를 구성해야 하므로 조금 오래 걸린다. 하이브는 메타 정보를 기본적으로 로컬에 임베디드 형태로 사용한다.

따라서 여러 사용자가 동시에 메타 정보를 동시에 사용해야 한다면 MySQL을 메타 스토어로 사용하는 모드로 설정해야 한다.

명령어 실행하기

테이블을 하나 만들어 보자

테이블이 정상적으로 생성되면 HDFS에 아래와 같은 경로에 테이블 이름과 동일한 디렉토리가 만들어진다.

hive_create_table

명령어가 실패한다면

만약 위의 명령어가 권한 문제로 실패했다면 hadoop 계정과 hive 계정을 따로 사용하는 경우일 것이다. 이때는 hive에서 사용하는 HDFS 디렉토리에 대해 hive에 사용권한을 아래와 같이 부여해야 한다.

하이브 metastore(메타스토어)의 유형 및 설정 방식

하이브는 테이블과 파티션과 관련된 메타정보를 모두 메타스토어에 저장한다.

Hive 메타스토어의 유형

하이브의 메타스토어 유형에는 임베디드 메타스토어(Embedded metastore), 로컬 메타스토어(Local metastore), 원격 메타스토어(Remote metastore) 세가지 유형이 있다.

hive_metastore

  • 임베디드 메타스토어(Embedded metastore)
    하이브를 설치하면 기본적으로 임베디드 메타스토어를 사용한다. 이 경우 메타스토어가 로컬 장비에 파일로 생성되므로 한번에 하나의 프로세스만 메타스토어에 접근할 수 있다. 따라서 실제 환경에서 사용해서는 안된다.
  • 로컬 메타스토어(Local metastore)
    로컬 메타스토어의 경우 메타데이터가 모두 원격(또는 로컬)의 데이터베이스에 저장된다.
  • 원격 메타스토어(Remote metastore)
    원격 메타스토어의 경우에도 메타데이터가 모두 원격(또는 로컬)의 데이터베이스에 저장된다. 하지만 로컬 메타스토어와는 달리 메타스토어를 서비스하는 별도의 서버가 기동되며, 클라이언트는 데이터베이스에 직접 쿼리문을 날리는 대신 메타스토어 서버의 중개를 받게 된다. 이때 클라이언트와 메타스토어 서버는 thrift 통신을 사용한다.

Hive 메타스토어의 설정 파라미터

하이브의 메타스토어를 설정하는 파라미터는 다음과 같다.

파라미터 설명
javax.jdo.option.ConnectionURL 메타데이터를 저장하는 DB의 접속 정보
javax.jdo.option.ConnectionDriverName DB에 연결할 때 사용할 JDBC 드라이버명
hive.metastore.uris 클라이언트에서 메타스토어 서버와 통신하는 URI
hive.metastore.local 임베디드 또는 로컬인 경우 true, 원격인 경우 false
hive.metastore.warehouse.dir  하이브 테이블이 저장되는 HDFS 상의 경로

Hive 메타스토어의 유형별 설정 방법

하이브의 메타스토어 유형별로 아래와 같이 설정한다. 기본적으로 아무 설정도 하지 않는다면 임베디드 메타스토어 방식을 사용한다. 아래의 파라미터를 설정하려면 hive-site.xml 파일을 만든 후, 기본 값을 변경할 파라미터만 설정한다.

임베디드 메타스토어(Embedded metastore)

파라미터 설명
javax.jdo.option.ConnectionURL jdbc:derby:;databaseName=../build/test/junit_metastore_db;create=true
javax.jdo.option.ConnectionDriverName org.apache.derby.jdbc.EmbeddedDriver
hive.metastore.local true
hive.metastore.warehouse.dir /user/hive/warehouse

로컬 메타스토어(Local metastore)

파라미터 설명
javax.jdo.option.ConnectionURL jdbc:mysql://<host name>/<database name>? createDatabaseIfNotExist=true
javax.jdo.option.ConnectionDriverName com.mysql.jdbc.Driver
javax.jdo.option.ConnectionUserName <user name>
javax.jdo.option.ConnectionPassword  <password>
hive.metastore.local true
hive.metastore.warehouse.dir /user/hive/warehouse

원격 메타스토어(Remote metastore)

서버는 아래와 같이 설정한다.

파라미터 설명
javax.jdo.option.ConnectionURL jdbc:mysql://<host name>/<database name>? createDatabaseIfNotExist=true
javax.jdo.option.ConnectionDriverName com.mysql.jdbc.Driver
javax.jdo.option.ConnectionUserName <user name>
javax.jdo.option.ConnectionPassword  <password>
hive.metastore.warehouse.dir /user/hive/warehouse

클라이언트는 아래와 같이 설정한다.

파라미터 설명
hive.metastore.uris thrift://<host_name>:<port>
hive.metastore.local false
hive.metastore.warehouse.dir /user/hive/warehouse

원격 메타스토어 사용하기

하이브를 원격 메타스토어로 사용하려면 우선 다음을 먼저 설치해야 한다.

  • 하둡 설치
  • 하이브(0.50 버전 이상) 임베디드 모드(default)로 설치
  • MySQL 설치

여기에서는 하나의 장비에 서버와 클라이언트를 모두 설치한다.

서버 설정하기

먼저 MySQL JDBC 커넥터 라이브러리를 $HIVE_HOME/lib에 추가해야 한다.

hive-site.xml을 아래와 같이 설정한다.

메타스토어 서비스를 기동한다. 메타스토어 서비스는 기본적으로 9083 포트로 기동된다. 만약 포트를 바꾸고 싶다면 -p 옵션을 사용한다. 자세한 옵션은 bin/hive –service metastore –help 명령어로 확인한다.

클라이언트 설정하기

hive-site.xml을 아래와 같이 설정한다.

클라이언트를 원격에서 접속하는 경우라면, 클라이언트에도 MySQL JDBC 커넥터 라이브러리를 $HIVE_HOME/lib에 추가해야 한다.

클라이언트 설정이 완료되면 이제 메타스토어 서비스에 접속해보자.

만약 show tables 명령어 실행시 아래와 같은 에러가 났다면,

하이브 메타스토어 서비스 접속 정보(hive.metastore.uris)가 잘못되지 않았는지 확인한다.

하이브 맵리듀스 로컬 모드

하둡이 분산 모드로 운영되더라도, 하이브 쿼리를 실행할 때 로컬 모드로 실행할 수 있다. 하이브 쿼리를 로컬 모드로 실행하게 되면, 하이브가 직접 데이터 파일을 읽고 맵리듀스 태스크를 관리하기 때문에, 더 빨리 실행된다. 단 입력 데이터가 작은 경우에만 로컬 모드로 실행해야 더 빠른 실행 속도를 얻을 수 있다.

일반적으로 하이브 쿼리문은 하이브 컴파일러에 의해 맵리듀스 잡으로 생성된다. 이렇게 생성된 잡은 mapred.job.tracker 변수에 설정된 맵리듀스 클러스터로 보내진다.

대개의 경우 mapred.job.tracker 변수는 다수의 노드로 구성된 맵리듀스 클러스터에 해당한다. 하지만 하둡에서는 맵리듀스 잡을 로컬 장비에서 간단히 실행할 수 있는 옵션을 제공한다. 작은 데이터셋에 대해 쿼리문을 실행할 경우, 이 옵션을 사용하면 꽤 효과적이다. 실제로 데이터셋이 작은 경우 맵리듀스 잡을 하둡 클러스터로 보내는 경우에 비해, 로컬 모드로 실행하는 편이 훨씬 더 빠른다. 이때 데이터는 HDFS로부터 투명하게 접근된다. 반대로 말하면, 로컬 모드에서는 단 하나의 리듀서만 실행하므로, 데이터셋이 큰 경우 훨씬 더 느려질 수 있다.

하이브 0.7 버전부터는 로컬 모드 실행을 완벽히 지원한다. 로컬 모드를 사용하려면 아래의 옵션을 설정해야 한다.

또한 mapred.local.dir이 로컬 장비에 실제로 위치한 경로를 가리키도록 설정해야 한다(예를 들어 /tmp/<username>/mapred/local와 같이). 만약 설정하지 않는다면 로컬 디스크 공간을 찾을 수 없다는 예외를 나타날 것이다.

뿐만 아니라 하이브 0.7 버전부터는 맵리듀스 잡을 자동으로 로컬 모드로 실행할 수 있는 옵션을 제공한다. 자동으로 실행하려면 아래와 같이 설정한다.

일반적으로 자동 로컬 모드는 비활성화되어 있다. 자동 로컬 모드를 활성화하면, 하이브는 각 쿼리문으부터 생성된 맵리듀스 잡의 크기를 분석한 후, 아래의 조건을 만족하면 자동으로 로컬모드로 실행하게 된다.

따라서 데이터셋이 작거나, 다수의 맵리듀스 잡이 실행되는 경우에도 연속된 잡의 입력이 상당히 작다면, 잡은 로컬 모드로 실행된다.

주의할 점은 하둡 서버 노드의 실행 환경과 하이브 클라이언트가 실행되는 장비의 환경은 다를 수 있다는 점이다(예를 들어 jvm 버전이 다르다거나, 다른 버전의 소프트웨어 라이브러리를 쓴다거나). 이로 인해 로컬 모드로 실행하는 경우 생각치 못한 방식으로 동작하거나 에러가 발생할 수도 있다. 또한 주의할 사항은 로컬 모드 실행은 별도의 (하이브 클라이언트의) 자식 jvm으로 실행된다는 점이다. 원한다면 자식 jvm이 사용가능한 최대 메모리 크기를 hive.mapred.local.mem 옵션을 통해 설정할 수 있다. 기본적으로 메모리 크기는 0으로 설정되어 있다. 기본값으로 설정된 경우, 자식 jvm이 사용가능한 메모리 한계를 하둡이 결정하도록 둔다.

하이브 결과 압축하기

하이브 퀴리를 실행하면 결국 맵리듀스 잡으로 변환되어 잡트래커에 제출되므로, 하이브 쿼리의 결과를 압축할 수 있는 부분은 총 2군데다. 맵의 중간결과를 압축하는 부분과 리듀스의 출력, 즉 잡의 최종 결과를 압축하는 부분이다.

맵리듀스 잡의 결과를 압축하게 되면 오히려 압축과정으로 인해 처리속도가 떨어지지 않을까 하는 우려도 있다. 하지만 하둡은 CPU보다는 I/O를 더 많이 쓰는 경향이 있다. 따라서 실행하려는 하이브 쿼리문이 CPU 연산보다는 입/출력에 더 많은 자원을 소모한다면 출력을 압축하는 편이 더 빨리 실행될 수 있겠다.

압축 코덱 선택

하둡 최신 버전은 기본적으로 GzipCodec, Bzip2Codec과 DefaultCodec을 지원한다. 현재 사용중인 하둡에서 지원하는 코덱을 확인하려면 아래의 명령을 실행한다. 별다른 설정을 하지 않았다면 ${HADOOP_HOME}/src/core/core-default.xml 파일에서(또는 core-site.xml과 같은 파일에서) io.compression.codecs설정을 확인한다.

물론 필요하다면 원하는 압축 코덱을 얼마든지 선택할 수 있다(단 하둡을 재기동해야 한다. 실시간으로 데이터를 수집중인 환경에서 네임노드가 이중화되지 않은 상황이라면 선택권이 없다).

이외에도최근에는 Snappy 압축 코덱과 LZO 압축 코델을 많이 사용한다. 각 압축 코덱의 장단점은 다음과 같다.

DefaultCodec GzipCodec Bzip2Codec Snappy LZO
압축률 높음 매우 높음 낮음 낮음
압축속도 중간 낮음 빠름 빠름
분합압축 X O X O

 

압축 코덱은 실행하려는 맵리듀스 잡의 성격에 따라 달리 선택해야 한다. 예를 들어 맵리듀스 잡의 결과를 다른 맵리듀스 잡의 결과로 활용하려면 반드시 분할 압축을 지원하는 코덱을 선택해야 한다.

반면 하이브 쿼리의 실행 결과는 영구적이기 보다는 임시용인 경우가 많으므로(CTAS문이 아닌 SELECT문인 경우에는 더더욱), 분할압축이 지원되지 않더라도 문제가 없다. 대신 압축률과 압축속도가 더 중요한 기준이 된다. 일반적으로 아래와 같이 선택해볼 수 있겠다.

위치 압축 코덱 이유
맵의 임시 결과 압축 Snappy 중간 과정 압축은 맵에서 리듀서로 보내는 데이터 량을 줄일 수 있다.
리듀서에서 다시 압축을 해제해야 하므로, 압축률보다는 압축속도가 빠른 코덱이 더 나은 선택이다.
잡의 최종 결과 압축 GzipCodec  Gzip 압축은 압축률이 높으므로 출력 압축으로 좋다.
하지만 압축된 결과를 하이브 CLI에서 직접 보기 위한 용도가 아니라면, 최종 결과는 압축하지 않는 편이 낫다.

압축 코덱 사용하기

하이브에서 압축 코덱을 사용하려면 아래와 같이 설정할 수 있다(사용중인 하둡 클러스터에 Snappy 코덱이 설치되어 있지 않은 관계로 임시 결과 압축에도 GzipCodec을 사용했다).

위치 설정
맵의 임시 결과 압축 mapred.map.output.compression.codec=org.apache.hadoop.io.compress.GzipCodec
hive.exec.compress.intermediate=true
잡의 최종 결과 압축  mapred.output.compression.codec=org.apache.hadoop.io.compress.GzipCodec
hive.exec.compress.output=true

실행하려는 하이브 쿼리문에 따라 압축할지 선택해야 하므로, CLI에서 매번 설정해야 한다. 이보다는 .hiverc. 파일을 사용하여 hive CLI에 대한 alias를 지정하여 사용하면 더욱 간편할 듯 하다.

우선 하이브 사용자의 홈 디렉토리에는 기본설정이 된 .hiverc를 만든다.

그리고 임시 결과 압축을 사용할 때 사용할 설정 파일과, 최종 결과까지 압축할 때 사용할 설정 파일을 .hive/.hiverc_map_zip과 .hive/.hiverc_out_zip 파일로 각각 만든다.

이제 .bashrc 파일에 각각의 파일에 대한 알리아스를 생성한다.

하이브 쿼리 성능 튜닝

하이브 쿼리 성능 향상을 위한 방법들을 정리한다.

병렬 처리

하이브는 쿼리문을 하나 이상의 스테이지로 변환한다. 각 스테이지 사이에는 의존관계가 있는 경우도 있다. 기본적으로 하이브는 스테이지를 한번에 하나씩 처리한다. 하지만 의존관계가 없는 스테이지가 있다면, 동시에 병렬로 수행하게 되면 더 빨리 처리할 수 있다.

병렬처리를 사용하려면 hive.exec.parallel을 true로 설정한다.

단 병렬처리를 사용하면 하둡 클러스터 자원을 더 많이 사용하게 된다.

LIMIT 설정

최적의 쿼리문을 만드는 중간 과정에서 빠른 결과를 보기 위하여 LIMIT 절을 사용하는 경우가 많다. 하지만 LIMIT절을 사용하더라도 데이터 전체에 대해 쿼리문을 수행 한후, LIMIT절에 기술된 수만큼 결과를 가져올 뿐이다. 따라서 성능 향상 효과는 미미하며, 오직 콘솔에 출력되는 시간만을 단축할 수 있을 뿐이다.

만약 LIMIT절을 이처럼 중간과정에서 사용하는 용도라면, 쿼리문이 전체 데이터가 아닌 일부 데이터만 적용하도록 설정할 수 있다.

설정 키 설정값 설명
hive.limit.optimize.enable true LIMIT절 사용시, 일부 데이터만 탐색하도록 설정할 것인지 여부
hive.limit.row.max.size 100000 LIMIT 최적화 사용시, 최대로 볼 수 있는 행의 개수
hive.limit.optimize.limit.file 20 LIMIT 최적화 사용시, 최대로 볼 파일의 개수

STRICT 모드 설정

사용자가 잘못된 쿼리문을 실행하여 잡트래커의 자원의 대부분을 낭비하게 되는 경우를 방지하기 위해, 하이브 실행 모드를 strict로 설정할 수 있다. 기본값은 nonstrict다.

STRICT 모드로 설정하면 아래의 쿼리문을 실행할 수 없다.

  • 파티션이 있는 테이블인 경우, 파티션 필터를 사용하지 않는 쿼리문
  • ORDER BY절이 있는 경우, LIMIT절이 없는 쿼리문
  • 카테시안 프로덕트를 생성하는 쿼리문

리듀서 수의 최적화

리듀스가 필요한 쿼리문을 실행한 경우 리듀스 개수를 설정하지 않았다면, 하이브는 입력 데이터의 크기를 기반으로 리듀스의 개수를 결정한다. 예를 들어 입력 데이터가 2GB인 경우, 리듀서의 개수는 2개가 되는데, 1GB당 1개의 리듀서가 생성되기 때문이다. 이러한 설정 값은 hive.exec.reducers.bytes.per.reducer로 설정할 수 있다.

대개의 경우는 기본값(1GB)으로도 충분하지만, 맵단계의 출력값, 즉 리듀서의 입력값이 맵의 입력값에 비해 훨씬 많은 경우라면 더 많은 리듀서가 필요하다. 또는 반대로 리듀서의 입력값이 맵의 입력값에 비해 훨씬 적은 경우라면, 더 적은 리듀서가 필요하다. 이러한 경우라면 mapred.reduce.tasks 옵션을 통해 직접 리듀서의 개수를 설정할 수 있다.

하지만 여러명의 사용자가 함께 사용중인 클러스터러면, 하나의 쿼리가 너무 많은 자원을 소모하지 않도록 방지할 필요가 있다. hive.exec.reducers.max를 임시가 아니라 hive-site.xml에 설정하여, 동시에 사용하는 사용자의 사용성을 보장해야 한다(물론 FairScheduler와 같이 맵리듀스 잡이 동시에 실행되도록 설정한 경우에만).

JVM 재사용

입력 파일이 작고 실행하려는 태스크가 단순하거나 또는 매퍼와 리듀서를 초기화하는데 시간이 오래 걸린다면, 태스크 자체의 실행시간보다 각 태스크를 위한 JVM을 생성하고 초기화하는데 더 많인 시간이 소모될 수도 있다. 하둡 0.19 버전부터 동일한 잡의 여러 태스크에서 하나의 JVM을 재사용할 수 있는 옵션이 추가되었다. mapred.job.reuse.jvm.num.tasks 속성에 재사용할 횟수를 설정하면 된다. 기본값을 1로, 전혀 재사용하지 않는다.

문제는 자바 가상 머신을 재사용하도록 설정하면, 해당 잡을 위해 예약된 태스크 슬롯은 잡이 종료되어야만 해제한다는 점이다. 만약 해당 잡의 특정 태스크가 다른 태스크보다 상당히 오래 걸려서 결국 잡 전체의 실행시간이 지연되는 경우라면, 해당 잡에 할당된 태스크 슬롯을 나머지 잡에서 사용하지 못하게 된다.

Group By 절의 성능 향상을 위한 맵 사이드 집계

하이브에서 count, avg와 같은 집계함수를 사용할 때, 성능 향상을 위한 옵션으로 hive.map.aggr 속성이 있다. 아래와 같이 이 속성을 true로 설정하면, 집계함수 사용시 맵 사이드에서 집계가 추가적으로 이루어진다.

기본적인 원리는 맵 단계의 임시 결과를 리듀서로 보내기 전에 컴바이너를 호출하는 방식과 동일하다. 즉 컴바이너를 호출함으로써 리듀서로 나가는 출력을 줄임으로써, I/O 측면에서 성능향상 효과를 볼 수 있다. 하지만 메모리 사용량이 추가적으로 늘어나게 된다

아래는 하이브 위키에서 설명하는 내용이다.

“Group By절을 위한 맵 사이드 집계”

hive.map.aggr 옵션은 집계를 하는 방식을 제어한다. 기본값은 false다. true로 설정하면, 하이브는 맵 태스크에서 직접 일단계 집계를 수행하게 된다.(현재 사용중인 0.8.1 버전에서는 true가 기본 값이다.)

이를 통해 약간의 성능을 향상시킬 수 있지만, 성공적으로 실행하려면 더 많은 메모리를 필요로 한다.

아래는 대런 리(Darren Lee)가 bizo 블로그에서 설명하는 내용이다.

“아파치 하이브: 맵 사이드 집계”

하이브에서 대용량의 데이터에 대해 집계 함수를 실행할 때, 흔히 마주치게 되는 예러는 다음과 같다.

  • 에러:
    맵사이드 집계에서 사용하는 해시 맵으로 인한 Out of memory
  • 해결책:
    hive.map.aggr.hash.percentmemory는 기본적으로 0.5로 설정된다. 0.5보다 작은 값으로 설정한다.
    예를 들어 ‘set hive.map.aggr.hash.percentmemory = 0.25;’와 같이 설정한다.

이 문제는 하이브가 맵사이드 집계를 실행하여 쿼리를 최적화하려고 할 때 발생한다. 맵사이드 집계란 매퍼 내부에서 집계를 부분적으로 실행하여 맵 단계에서 최적화를 하는 과정으로, 이를 통해 매퍼는 더 적은 수의 행을 생성하게 된다. 결과적으로 하둡에서 정렬하거나 리듀서에 분배해야 하는 정보의 량이 줄어드는 효과를 만들어 낸다.

흔히 사용하는 “단어 세기(word count)” 예제를 통해 하둡 잡이 어떻게 실행되는지 살펴보자.

단어 세기 예제의 경우, 기본적으로 매퍼는 입력에서 각 행을 토큰으로 분리한 후, (#{token}, 1)와 같이 키-값 쌍의 형태로 출력을 만들어낸다. 하둡 프레임워크는 이러한 키-값 쌍을 토큰별로 정렬한 후, 리듀서에서는 각 토큰에 대한 전체 갯수를 출력하기 위해 값 값에 대한 합계를 계산한다.

반면 맵사이드 집계를 사용하게 되면, 매퍼에서는 각 행을 토큰 단위로 분리할 뿐만 아니라 메모리에 해시 맵을 생성하여 각 토큰에 대한 부분 합계를 저장해 둔다. (더 정확히 말하자면, 매퍼는 키와 대응하는 부분 집계값, 즉 키의 개수를 저장한다.) 그런 후 매퍼는 (#{token}, #{token_count}) 쌍을 출력으로 만들어낸다. 이경우에도 마찬가지로 하둡 프레임워크에서는 매퍼의 출력값을 정렬하며, 리듀서에서는 각 토큰별로 합계를 계산하여 전쳬 합계를 출력으로 만들어낸다. 따라서 매퍼는 각 토큰이 나타날 때마다 매번 한 행을 출력하는 게 아니라, 토큰 하나당 오직 한 행만이 출력으로 만들어진다. 이로 인해 모든 토큰에 대한 맵을 메모리에 유지해야 한다는 단점이 있다.

기본적으로 하이브는 맵사이드 집계를 사용하려고 시도한다. 맵사이드 집계 중에 사용중인 해시 맵이 메모리를 너무 많이 차지하게 되면, 맵사이드 집계를 중단하고 기본적인 방식으로 실행되게 된다. 좀더 자세히 말하면, 1000,000행(hive.groupby.mapaggr.checkinterval를 통해 설정 가능)을 처리한 후 하이브는 해시맵에 있는 항목 수를 검사한다. 만약 항목 수가 현재까지 읽은 행수의 50%(hive.map.aggr.hash.min.reduction로 설정 가능)보다 많으면, 맵사이드 집계가 중단된다.

뿐만 아니라 하이브는 해시맵의 각 항목마다 요구하는 메모리 량을 측정한 후, 맵의 사이즈가 매퍼에서 사용가능한 메모리 총량의 50%(hive.map.aggr.hash.percentmemory로 설정 가능)보다 많아지면, 맵의 결과를 리듀셔로 바로 보낸다. 하지만 이러한 과정은 행의 개수와 각 행의 사이즈를 하이브에서 추정한 결과에 따른 것이므로, 각 행별로 실제로 차지하는 메모리 사용량이 추정치보다 높아지면, 해시 맵의 데이터가 리듀서로 보내지기도 전에 매퍼에서 Out of Memory 에러가 발생하게 된다.

특히 count distinct 집계를 호출하는 쿼리를 사용하는 경우에는, 부분 집계는 실제로 모든 값에 대한 목록을 포함하게 된다. 유니크한(즉 distinct한) 키에 대해 값이 많이 있다면, 해시맵의 행의 개수는 늘지 않더라도 맵에서 사용하는 메모리 총량은 증가하게 된다. 하지만 앞서 말했듯이 부분적인 집계 결과를 리듀서로 출력할지 결정하는 기준은 해시맵의 행의 개수다.

맵사이드 집계가 true로 설정된 상태에서 group by절을 포함하는 쿼리문을 실행할 때, 매퍼에서 메모리가 부족해지는 현상이 발생하면, 하이브에서는 메모리 부족 현상을 피하기 위해 리듀서로 출력하는 기준을 더 낮출 것을 제안하는 도움말을 표시해준다. 따라서 하이브가 맵의 출력을 리듀서로 보내는 기준(행의 개수)를 낮출 수 있다. 하지만 이렇게 하더라도 행의 개수와는 무관하게 맵의 사이즈(바이트 단위)가 증가하게 되는 문제를 해결할 수는 없다.

대안으로는 맵사이드 집계를 아예 사용하지 않을 수도 있다(set hive.map.aggr = false). 또는 하둡 설정을 통해 매퍼에서 사용할 메모리를 좀더 증가시키거나, 하이브가 쿼리 실행 계획을 다르게 세우도록 쿼리문을 달리 작성할 수도 있다.

예를 들어 아래과 같은 단둔한 쿼리문은

아래와 같이 작성할 수 있다.

이렇게 하면 count distinct 집계를사용하지 않으므로, 더 효율적으로 실행될 수도 있다.

하이브 CLI 활용 팁

CLI 프롬프트와 .hiverc 파일

하이브를 사용하다 보면 기본 프롬프트가 아니라, 현재 사용중인 데이터베이스 명이 나오면 더 편하지 않을까 생각해 본적 있다면 이 글이 도움이 될 것이다.

아무런 설정도 하진 않는 경우 하이브 CLI를 실행하면 아래와 같인 기본 프롬프트가 나타난다.

만약 기본 프롬프트가 아니라, 현재 사용중인 데이터베이스가 프롬프토에 보이게 하고 싶다면 아래와 같이 설정한다.

또한 하이브 쿼리를 실행한 경우, 헤더가 표시되지 않아 제대로 실행이 된것인지 의문이 될 때가 많다. 하이브 쿼리문의 실행 결과에 헤더를 표시하고 싶다면 아래와 같이 설정한다.

물론 이렇게 설정하더라도 필드 단위의 정렬은 되지 않으므로 보기에는 약간 번거롭기는 하다.

하지만 이렇게 설정하면 매번 접속할 때마다 설정해야 하는 번거로움이 있다. 이때 .hiverc 파일을 활용한다. 하이브는 CLI를 실행할 때마다 자동으로 $HIVE_HOME/.hiverc 파일에 설정된 명령을 실행하게 된다. 따라서 .hiverc 파일에 기본적으로 필요한 옵션을 필요한 명령을 설정하면 된다.

쿼리문 편집시 이동 단축키

shell 명령어와 같이 이동 단축키를 제공한다.

  • Ctrl + A : 맨 앞으로
  • Ctrl + E : 맨 뒤로
  • TAB : 예약어 및 함수 자동완성

쿼리문 편집시 TAB키가 자동완성으로 사용되기 때문에, 쿼리문에 TAB키가 포함된 경우 쿼리문이 정상적으로 실행되지 않는다. 대개의 경우 TAB키를 쿼리 포맷팅용으로 많이 활용하기 때문에 간혹 번거로울 때가 있다.

하이브 CLI에서 쉘 명령어 실행하기

명령문 앞에 ! 를 입력한 후, 세미콜론으로 마무리한다.

주의할 점은 쉘 파이프와 파일 글로빙은 지원하지 않는다.

하이브에서 하둡 dfs 명령어 실행하기

명령문 앞에 dfs 를 입력한 후, 세미콜론으로 마무리한다.

쉘에서 하둡 dfs 명령문을 실행하는 것에 비해 하이브 CLI에서 하둡 dfs 명령문을 실행하면 별도의 JVM 인스턴스를 구동하지 않으므로 좀 더 빨리 실행된다.