Hive 파티션, 버킷

파티션과 버킷

하이브는 테이블을 파티션으로 구조화할 수 있다. 파티션이란 테이블의 데이터를 날짜와 같은 파티션 컬럼의 값을 기반으로 큰 단위로 분할하는 방식이다. 파티션을 사용하면 데이터의 일부를 매우 빠르게 질의할 수 있다.

테이블과 파티션은 효율적인 쿼리를 위해 데이터에 추가된 구조인 버킷으로 더욱 세분화될 수 있다. 예를 들어 사용자 ID를 기준으로 버킷을 생성하면 전체 사용자 중에서 무작위 데이터 샘플을 뽑아 사용자가 작성한 쿼리가 제대로 실행되는지 빠르게 평가할 수 있다.


파티션

파티션이 주로 사용되는 예를 들기 위해 각 레코드가 타임스탬프를 포함하고 있는 로그파일이 있다고 가정. 만약 날짜 기반으로 파티션을 했다면 같은 날짜의 레코드는 동일한 파티션에 저장된다. 특정 날짜나 기간으로 국한된 쿼리는 오직 관련된 파티션의 파일만 읽기 때문에 매우 효율적이다.

테이블은 다중 차원으로 파티션될 수 있다. 예를 들어 먼저 날짜를 기준으로 로그를 파티션하고 그 다음에 지역별로 효율적인 쿼리를 수행하기 위해 각 날짜별 파티션에 국가별 서브파티션을 추가할 수 있다.

CREATE TABLE logs (ts BIGINT, line STRING)
PARTITIONED BY (dt STRING, country STRING);
LOAD DATA LOCAL INPATH 'hdfs path'
INTO TABLE logs
PARTITION (dt='2001-01-01', country='GB');


디렉터리 구조

/usr/hive/warehouse/logs
|- dt=2001-01-01/
|   |- country=GB/
|   |   |- file1
|   |   L  file2
|   L  country=US/
|       L  fil3
L dt=2001-01-02/
    |- country=GB/
    |   L  file4
    L  country=US/
        |- file5
        L  file6


SHOW PARTITIONS 로 테이블에 포함된 파티션의 목록을 하이브에 요청할 수 있다.

SHOW PARTITIONS logs;

PARTITIONED BY 절의 컬럼 정의는 파티션 컬럼이라 불리는 온전한 테이블 컬럼이라는 점을 염두에 두어야 한다. 파티션 컬럼에 대한 값은 디렉터리 이름으로 유추할 수 있기 때문에 데이터 파일 자체에는 포함되어 있지 않다.

일반적으로 파티션 컬럼은 SELECT 문에서 사용될 수 있다. 하이브는 해당 파티션만 읽기 위해 입력 가지치기를 수행한다.

SEELCT ts, dt, line
FROM logs
WHERE country='GB';



버킷

테이블을 버킷으로 구조화하려는 이유는 2가지

  • 매우 효율적인 쿼리가 가능하다.

버킷팅은 테이블에 대한 추가 구조를 부여하고, 하이브는 어떤 쿼리를 수행할 때 이 추가 구조를 이용할 수 있다. 특히 동일한 컬럼(조인할 컬럼)에 대한 버킷을 가진 두 테이블을 조인할 때 맵 조인을 구현하면 매우 효율적이다.)

  • 효율적인 샘플링에 유리하다.

매우 큰 데이터셋을 대상으로 개발하거나 개선하는 과정에서 데이터셋의 일부만으로 쿼리를 수행할 수 있으면 매우 편리하다.

테이블 버킷팅 방법(CLUSTERED BY)

-- 사용자 ID 기준으로 버킷팅
CREATE TABLE bucketed_users (id INT, name STRING)
CLUSTERED BY (id) INTO 4 BUCKETS;

하이브는 사용자 ID의 값을 해싱하고, 그 값을 버킷 수로 나눈 나머지를 버킷의 번호로 선택한다. 그러면 각 버킷은 사용자의 무작위 집합을 포함하는 효과가 있다.

동일한 방식으로 버킷된 두 개의 테이블에 맵 조인을 수행하면 왼쪽 테이블의 버킷을 처리하는 매퍼는 상응하는 행이 오른쪽 테이블의 버킷에 있다는 것을 사전에 인지하고 있기 때문에 조인을 수행할 때 오른쪽 테이블에 저장된 전체 데이터의 작은 일부만 추출한다. 두 테이블의 버킷 수가 정확하게 같으면 좋겠지만 서로 달라도 최적화가 가능하다.

버킷에 포함된 데이터는 하나 이상의 컬럼으로 정렬될 수 있다. 이렇게 하면 각 버킷을 조인할 때 효율적인 병합 정렬이 가능하므로 맵 조인의 속도를 더 향상시킬 수 있다.

-- 정렬 버킷
CREATE TABLE buketed_users (id INT, name STRING)
CLUSTERED BY (id) SORTED BY (id ASC) INTO 4 BUCKETS;

하이브는 디스크에 저장된 데이터 파일에 있는 버킷이 테이블 정의(버킷 컬럼이나 개수)에 기반한 버킷과 정확히 일치하는지 검증하지 않는다. 일치하지 않으면 에러가 발생하거나 쿼리 시점에 예측하지 못한 결과가 발생할 수 있다. 이러한 이유로 하이브가 직접 버킷팅을 수행하도록 권고하고 있다.

기존의 테이블을 버킷된 테이블로 변경하려면 먼저 hive.enforce.bucketing 속성을 true로 설정하여 테이블 정의에 선언된 개수만큼 버킷을 생성하도록 하이브에 알려주어야 한다. 이 후 버킷 테이블로 Insert 한다.

INSERT OVERWRITE TABLE bucketed_users
SELECT * FROM users;

물리적으로 각 버킷은 테이블이나 파티션 디렉터리 안에 있는 하나의 파일이다. 파일의 이름은 중요하지 않지만 사전순으로 배치될 때 버킷 n은 n번째 파일일다. 사실 버킷은 맵리듀스 출력 파일 파티션에 상응한다. job은 reduce 태스크와 동일한 개수의 버킷(출력 파일)을 생성한다.

dfs -ls /usr/hive/warehouse/bucketed_users;
000000_0
000001_0
000002_0
000003_0

첫 번째 버킷은 사용자 ID가 0과 4인 레코드를 가진다. INT의 해시는 정수 그 자체고, 버킷 수인 4로 나눈 나머지가 바로 버킷의 번호다.

전체 테이블이 아니라 테이블의 일부 버킷만 사용하는 쿼리롤 제한하는 TABLESAMPLE 절을 이용하여 해당 테이블을 샘플링하면 동일한 결과를 얻을 수 있다.

SELECT * FROM bucketed_users
TABLESAMPLE(BUKET 1 OUT OF 4 ON id);

여기서 버킷 번호는 1이 기준이다. 따라서 이 쿼리는 4 개의 버킷 중 첫 번째 버킷의 사용자만 검색한다. 데이터가 고르게 분포된 매우 큰 데이터셋에서는 테이블에 있는 전체 행의 4분의 1가량이 반환될 것이다. 비율을 다르게 지정하면 더 많은 버킷도 샘플링할 수 있다. 어차피 샘플링 자체가 정밀한 연산을 의미하지 않기 때문에 버킷 수를 엄격하게 지정할 필요는 없다. 예를 들어 다음 쿼리는 버킷의 절반(1/2)을 반환한다.

SELECT * FROM bucketed_users
TABLESAMPLE(BUCKET 1 OUT OF 2 ON id);

버킷된 테이블의 샘플링은 TABLESAMPLE 절에 해당되는 버킷만 읽으면 되기 때문에 매우 효율적이다. 버킷이 없는 테이블을 RAND() 함수로 샘플링할 때와 한 번 비교해본다면 매우 작은 샘플만 필요하지만 전체 입력 데이터셋을 읽어야하는 낭비가 생긴다.

SELECT * FROM users
TABLESAMPLE(BUCKET 1 OUT OF 4 ON rand());