
이 글은 하이브 완벽 가이드 책을 읽고 그 중 일부 내용을 정리한 글입니다.
날짜별 테이블
날짜별 테이블 패턴은 supply_2022_10_05, supply_2022_10_06과 같이 타임스탬프를 추가하여 테이블을 생성하는 패턴입니다. 하이브에서는 날짜별 테이블 패턴 대신 파티셔닝된 테이블을 사용하는 방법으로 이 문제를 해결할 수 있습니다.
파티션 설계 시 고려사항
HDFS는 작은 파일보다는 아주 큰 파일을 위해 설계되었습니다. 너무 많은 파티션을 가지고 있다는 것은 파일과 디렉터리를 불필요하게 많이 생성한다는 단점이 있습니다. 맵리듀스는 잡을 여러 개의 태스크로 변환합니다. 기본적으로 각 태스크는 시작과 종료 시에 부하가 걸리는 새로운 JVM 위에서 동작합니다. 작은 파일에 대해서도 각 파일마다 개별 태스크를 사용하기 때문에 작은 파일이 과도하게 많다면 시작과 종료에 대한 부하가 실제 수행 시간을 압도할 수도 있습니다.
따라서 이상적인 파티션 설계는 너무 많은 파티션과 디렉터리를 만들지 않고 각 디렉터리에 있는 파일은 파일시스템 블록 크기보다 몇 배 정도 크게 하는 것입니다. 예를 들어 시간 범위 파티션을 만드는 좋은 전략은 서로 다른 시간 granularity에서 각각 누적되는 대략적인 데이터 크기를 확인하고 시간이 지남에 따라 몇 개 파티션에서 비슷한 크기로 데이터가 증가하는 granularity를 선택하는 것입니다. 이런 균형 작업은 파티션을 크게 유지하여 일반적인 쿼리의 처리량을 최적화합니다. 균일한 크기의 파티션을 찾지 못한다면 버킷팅을 고려할 수 있습니다.
고유키와 정규화
하이브는 관계형 데이터베이스와 다르게 정규화가 지양됩니다. 이는 외래키 관계를 찾아다니면서 생기는 디스크 탐색을 최소화하기 위해서입니다.
동일 데이터에 대한 다중 패스 만들기
하이브에는 한 번의 소스 데이터로 반복 스캔 없이 여러 번 집계를 할 수 있는 특별한 문법이 있습니다. 이 다중 패스 기능은 대규모 입력 데이터를 처리할 때 상당한 시간을 절약합니다.
예를 들어 다음 두 개의 쿼리는 각각 history라는 동일한 테이블로부터 테이블을 생성합니다.
-- 다중 패스 기능 사용 X
INSERT OVERWRITE TABLE sales
SELECT * FROM history WHERE action = 'purchase';
INSERT OVERWRITE TABLE sales
SELECT * FROM history WHERE action = 'return';
-- 다중 패스 기능 사용 O
FROM history
INSERT OVERWRITE TABLE sales WHERE action = 'purchase'
INSERT OVERWRITE TABLE sales WHERE action = 'return';
임시 테이블 파티셔닝하기
많은 ETL 처리는 다수의 처리 단계를 수반합니다. 쿼리나 소스 데이터의 실수로 여러 날의 입력에 대해서 ETL 과정을 강제로 다시 동작시켜야 할 때 임시 테이블을 덮어쓰지 않도록 하기 위해서는 임시 테이블을 파티셔닝해야 합니다.
테이블 저장소 버킷팅하기
파티션은 데이터를 나누고 쿼리를 최적화하는 편리한 방법이지만, 모든 데이터에 대해서 그런 것은 아니며 특히 적절한 파티션 크기를 결정하지 못하는 경우에는 더더욱 그렇습니다. 예를 들어 최상위 파티션으로 날짜 컬럼 dt를, 그다음 파티션으로 user_id를 사용하는 테이블이 작은 파티션을 너무 많이 만드는 상황이 그렇습니다. 만약 이런 파티션을 동적으로 생성한다면 하이브는 파일시스템을 보호하기 위해서 동적 생성되는 파티션의 최대값을 제한합니다. 따라서 다음 명령은 실패합니다.
CREATE TABLE weblog (url STRING, source_ip STRING)
PARTITIONED BY (dt STRING, user_id INT);
FROM raw_weblog
INSERT OVERWRITE TABLE page_view PARTITION (dt='2022-10-06', user_id)
SELECT server_name, url, source_ip, dt, user_id;
대신에 weblog 테이블을 버킷팅하고 user_id를 버킷 컬럼으로 사용한다면 이 컬럼의 값을 사용자가 정한 버킷 수로 해시한 후 해당 버킷으로 보냅니다.
CREATE TABLE weblog (user_id INT, url STRING, source_ip STRING)
PARTITIONED BY (dt STRING)
CLUSTERED BY (user_id) INTO 96 BUCKETS;
그러나 실제 데이터를 테이블에 정확하게 넣는 일은 사용자의 몫입니다. CREATE TABLE 문에서는 메타 데이터를 정의만 했지 실제로 테이블의 데이터를 채우는 명령에 대해서는 아무런 영향을 미치지 않습니다.
이는 INSERT ... TABLE 문장을 사용할 때 테이블의 데이터를 정확히 어떻게 채워가는지 보여줍니다. 우선 하이브가 대상 테이블의 버킷팅 설정에 해당하는 정확한 리듀스 개수를 결정하기 위해 속성을 결정합니다. 그런 다음 파티션에 데이터를 넣기 위해서 쿼리를 수행합니다. 예제는 다음과 같습니다. 만약 hive.enforce.bucketing 속성을 사용하지 않는다면 mapred.reduce.tasks 속성에 버킷과 동일한 개수의 리듀스 개수를 설정해야 합니다.
SET hive.enforce.bucketing=true;
-- 또는 SET mapred.reduce.tasks=96;
FROM raw_logs
INSERT OVERWRITE TABLE weblog PARTITION (dt='2022-10-05')
SELECT user_id, url, source_ip WHERE dt = '2022-10-05';
버킷을 사용하면 여러 이점이 존재합니다. 버킷 수가 고정되면 데이터의 변동은 심하지 않습니다.
테이블에 컬럼 추가하기
많은 데이터베이스가 특정 포맷으로 강제로 변환하거나 가져오기를 요구하는 반면 하이브는 원시 데이터 파일에 따라 스키마를 정의할 수 있도록 허용합니다. 이러한 특징으로 인해 데이터 파일에 새로운 컬럼을 추가하고자 할 때 테이블 정의를 이에 맞추는 것이 가능합니다.
하이브는 입력으로부터 데이터를 추출할 수 있게 하는 SerDe라는 추상화를 제공합니다. 물론 데이터 출력에는 SerDe를 사용할 수 있지만 하이브는 주로 쿼리를 수행하는 데 사용하기 때문에 출력 데이터에 대해서는 자주 사용하지 않습니다. SerDe는 일반적으로 왼쪽에서 오른쪽으로 파싱해가면서 특정 컬럼 구분자를 사용하여 로우를 나누며 매우 유연하게 동작합니다. 예를 들어 테이블 로우가 생각보다 더 적은 컬럼을 가지고 있다면 빠진 컬럼에 대해서는 NULL을 반환하고 로우가 생각보다 더 많은 테이블을 가지고 있다면 무시합니다. 따라서 스키마에 새로운 컬럼을 추가하기 위해 ALTER TABLE ADD COLUMN 명령을 사용하면 됩니다. 로그 포맷처럼 로그 메시지에 추가 정보만을 늘리려 할 때 유용합니다.
컬럼 기반 테이블 사용하기
하이브는 전형적으로 로우 지향 저장소를 사용하지만 로우와 컬럼을 둘 다 지향하는 하이브리드 형태로 정보를 저장하는 컬럼 기반의 SerDe 또한 가지고 있습니다. 이를 위해서는 RCFile이라는 포맷을 참고합니다.
압축하기
일반적으로, 압축하면 디스크 상에서 차지하는 크기가 작아져 I/O 부담이 줄어들고 결과적으로 쿼리를 좀 더 빠르게 수행할 수 있습니다. 그런데 외부 시스템에 사용되는 데이터는 텍스트와 같이 압축되지 않은 포맷이 가장 호환성이 좋기 때문에 압축을 하지 않는 것이 좋습니다.
'Hadoop Ecosystem > Hive' 카테고리의 다른 글
| [하이브 완벽 가이드] Ch11. 기타 파일 포맷과 압축 (0) | 2022.10.24 |
|---|---|
| [하이브 완벽 가이드] Ch10. 튜닝 (0) | 2022.10.10 |
| [하이브 완벽 가이드] Ch8. HiveQL : 색인(Index) (0) | 2022.10.04 |
| [하이브 완벽 가이드] Ch6. HiveQL : 쿼리 (0) | 2022.09.19 |
| [하이브 완벽 가이드] Ch5. HiveQL : 데이터 조작(DML) (0) | 2022.09.16 |