필자가 생각하는 Hive에서의 데이터 아키텍처 최적화 방법은 아래와 같이 3가지로 구분될 수 있습니다.

Hive에서 데이터 아키텍처 최적화 방법

 

  • 많은 데이터는 적게 만들어라
  • 적은 데이터를 많게 만들어라
  • Skew는 분할처리하라.

 

  이 포스트에서는 첫 번째 Case인 '많은 데이터는 적게 만들기' 방법으로 튜닝한 예제에 대해 설명하고자 합니다.

  먼저 튜닝 결과는 아래와 같습니다.

  리듀스의 수가 크게 증가한 것에 의문을 가지시는 분들이 계실 수도 있는데요. 이 부분에 대해서는 아래에서 궁금증을 풀어보도록 하겠습니다.

 

 

 

[ 튜닝 전 ]

 

  먼저 튜닝 전 쿼리를 살펴보도록 하겠습니다.

SELECT BASE_DT
     , MAX(ORG_CNT)                AS ORG_CNT
     , MAX(CAL_CNT)                AS CAL_CNT
     , MAX(ORG_CNT) - MAX(CAL_CNT) AS GAP_CNT
  FROM (
        SELECT '${BASE_DT}'        AS BASE_DT
             , COUNT(DISTINCT UID) AS ORG_CNT
             , 0                   AS CAL_CNT
          FROM ACT_TXN_DD
         WHERE BASE_DT BETWEEN TO_CHAR(DATE_ADD(FROM_UNIXTIME(UNIX_TIMESTAMP('${BASE_DT}', 'yyyyMMdd'), 'yyyy-MM-dd'), -179), 'yyyyMMdd') 
                           AND '${BASE_DT}'
         UNION ALL
        SELECT BASE_DT
             , 0                   AS ORG_CNT
             , COUNT(DISTINCT UID) AS CAL_CNT
          FROM ACT_TXN_180_SUM
         WHERE BASE_DT = '${BASE_DT}'
         GROUP BY BASE_DT
     ) A
 GROUP BY BASE_DT
;

  * 참고
   - BASE_DT는 파티션 키 컬럼입니다.
   - 위 조건절에 사용된 TO_CHAR 함수는 Hive 내장 UDF가 아니라 시스템 운영상 필요해 의해 만들어진 진짜 사용자 정의 UDF입니다.

 

  위 쿼리의 튜닝 포인트는 2가지가 존재합니다.

* Tuning Point #1 : 조건절 내 사용자 정의 UDF 제거

  사용자 정의 UDF는 Hive의 옵티마이저가 제대로 인식할 수 없는 UDF라고 볼 수 있습니다. 따라서 옵티마이저가 실행 계획을 수립할 때 사용자 정의 UDF는 비용 계산에서 제외됩니다.
  이러한 사용자 정의 UDF가 where 조건절에 사용되어 추출 데이터의 건수를 상당히 줄인다 해도 옵티마이저의 쿼리 수행 비용 계산 시에는 반영되지 않는 겁니다.

  특히 위 쿼리에서처럼 파티션 키 컬럼(BASE_DT)의 필터링 조건으로 사용자 정의 UDF가 사용되는 경우에 성능상의 큰 이슈가 발생할 수 있습니다. 파티션 키 컬럼의 필터링 조건이 존재하지만 Partition pruning을 시도하지 못하고 전체 테이블의 데이터를 읽어야(Full table scan) 하기 때문입니다.

  위 쿼리에서는 Partition pruning을 위해서 where 조건절에 사용된 사용자 정의 UDF를 제거하는 것이 필요합니다. 

 

  참고로 Hive에서 Partition pruning을 판단할 수 있는 방법은 아래 포스트에서 확인하실 수 있습니다.

 

Hive 튜닝 기본 -실행계획에서 Partition pruning 확인하기

Hive에서도 아래와 같이 'EXPLAIN' 명령문으로 쿼리 실행계획을 확인할 수 있습니다. hive> EXPLAIN 쿼리문; Hive의 실행계획은 다른 DBMS에 비해 실행계획의 가독성이 떨어진다는 점이 참으로 안타까운데

sparkdia.tistory.com

 

 

* Tuning Point #2 : DISTINCT COUNT 연산 부하

  COUNT(DISTINCT 컬럼) 함수는 '컬럼'내 중복을 제외한 데이터의 건수를 계산합니다.
  전체 데이터에서 중복이 제거된 건수를 계산해야 하므로 이 작업은 단 하나의 리듀스 태스크가 처리하게 됩니다.

  위 쿼리의 리듀스 태스크 수가 3개였습니다.

  - 서브쿼리 내 UNION ALL 절 상단의 쿼리
  - 서브쿼리 내 UNION ALL 절 하단의 쿼리
  - 최상위 쿼리

  데이터의 건수와는 상관없이 각 쿼리를 수행하는 Staage 별로 단 1개의 리듀스 태스크가 할당이 되어 집계 함수 연산을 수행하는 것입니다.

  큰 작업을 여러 개의 맵과 리듀스가 나눠서 빠른 시간 내에 처리하는 것이 하둡의 기본 사상이자 장점입니다.
  1개의 리듀스로 집계 함수 연산을 수행한다는 것은 하둡의 장점인 분산 처리의 이점을 전혀 활용하지 못하는 경우입니다. 

  위 쿼리에서는 다수의 리듀스가 연산을 처리할 수 있도록 COUNT(DISTINCT 컬럼) 연산의 부하 감소가 필요합니다.

 

 

 

 

[ 튜닝 후 ]

 

  아래는 튜닝 전 쿼리에서 부하를 유발한 사용자 정의 UDF를 제거하고 COUNT DISTINCT의 부하를 감소시킨 쿼리입니다.

SELECT BASE_DT
     , MAX(ORG_CNT)                AS ORG_CNT
     , MAX(CAL_CNT)                AS CAL_CNT
     , MAX(ORG_CNT) - MAX(CAL_CNT) AS GAP_CNT
  FROM (
        SELECT '${BASE_DT}'        AS BASE_DT
             , COUNT(DISTINCT UID) AS ORG_CNT
             , 0                   AS CAL_CNT
          FROM (
                SELECT BASE_DT
                     , UID
                  FROM ACT_TXN_DD
                 WHERE BASE_DT BETWEEN DATE_FORMAT(DATE_ADD(FROM_UNIXTIME(UNIX_TIMESTAMP('${BASE_DT}', 'yyyyMMdd'), 'yyyy-MM-dd'), -179), 'yyyyMMdd') 
                                   AND '${BASE_DT}'
                 GROUP BY BASE_DT, UID
             ) SUB
         UNION ALL
        SELECT BASE_DT
             , 0                   AS ORG_CNT
             , COUNT(DISTINCT UID) AS CAL_CNT
          FROM ACT_TXN_180_SUM
         WHERE BASE_DT = '${BASE_DT}'
         GROUP BY BASE_DT
     ) A
 GROUP BY BASE_DT
;

 

 

* Tuning Point #1 : 조건절 내 사용자 정의 UDF 제거

  파티션 키 컬럼인 BASE_DT의 필터링 조건을 사용자 정의 UDF가 아닌 Hive 내장 UDF인 DATE_FORMAT으로 변경하였습니다.

WHERE BASE_DT BETWEEN TO_CHAR(DATE_ADD(FROM_UNIXTIME(UNIX_TIMESTAMP('${BASE_DT}', 'yyyyMMdd'), 'yyyy-MM-dd'), -179), 'yyyyMMdd') AND '${BASE_DT}'
WHERE BASE_DT BETWEEN DATE_FORMAT(DATE_ADD(FROM_UNIXTIME(UNIX_TIMESTAMP('${BASE_DT}', 'yyyyMMdd'), 'yyyy-MM-dd'), -179), 'yyyyMMdd') AND '${BASE_DT}'

  Hive 내장 UDF를 사용하였기에 옵티마이저는 BASE_DT 컬럼의 필터링 연산을 실행 계획에 반영할 수 있습니다.
  필터링 연산이 적용되므로 Partition pruning이 발생하여 액세스 대상 데이터의 건수의 감소로 쿼리 성능이 향상되게 됩니다.

 

 

* Tuning Point #2 : DISTINCT COUNT 연산 부하

  앞서 DISTINCT COUNT 연산은 오직 하나의 리듀스가 처리한다고 했습니다.
  분산 처리가 불가능한 상황이므로, 리듀스 연산 시간을 단축하는 방법은 리듀스의 입력 데이터 건수를 줄이는 것입니다.

  튜닝 전 쿼리를 다시 한번 살펴보겠습니다.

 

  중복된 UID가 존재하는 180일간의 데이터를 읽어 중복되지 않은 UID의 건수를 계산합니다. 하나의 리듀스가 180일간의 데이터를 모두 처리므로 실행 속도가 느려지게 됩니다.

 

  DISTINCT COUNT 연산을 수행하는 리듀스의 입력 데이터를 줄이는 방법사전에 입력 데이터의 중복을 제거하는 것입니다.

  서브 쿼리 안에서 GROUP BY BASE_DT, UID 집계 연산으로 중복이 제거된 중간 데이터 결과 셋이 생성됩니다. 
  DISTINCT COUNT 연산을 처리하는 리듀스는 입력으로 크기가 감소된 중간 데이터 결과 셋을 사용하게 됩니다.

  위 GROUP BY 연산은 다수의 리듀스가 작업을 분배하여 처리합니다.
  하나의 리듀스가 처리하던 중복제거 작업을 다수의 리듀스가 처리하게 되는 겁니다. 작업의 분산 처리가 가능하니 당연히 연산 속도는 빨라지게 됩니다.

 

 

 

다시 한번 첫 번째 Hive에서의 데이터 아키텍처 최적화 방법을 요약 정리하면 아래와 같습니다.

최적화 방법 첫번째 : 많은 데이터는 적게 만들어라.

1. 액세스 대상 테이블 데이터 건수 감소 (Partition Pruring이 적용)
2. 집계 함수 연산 대상 데이터 건수 감소 (사전 Group by 연산 수행)
 

 

+ Recent posts