0. 시작하며

개발자라면 누구나 성능 향상에 대한 고민을 해 본 적이 있을 것입니다. 다른 분야도 마찬가지겠지만, ETL에 있어서도 성능을 향상시키는 것은 굉장히 중요한 부분입니다. 성능이 저하됨에 따라서 원하는 시간 내에 ETL 작업이 완료되지 못할 수도 있고, 특정 작업이 자원을 많이 차지함에 따라서 다른 작업의 수행 속도에 안 좋은 영향을 끼칠 수도 있으니 말이에요. 이번 게시글에서는 Hive측면과 RDBMS 측면에서 쿼리 수정을 통해 성능을 향상시키는 방법과 ETL 작업을 개발할 때 효율적으로 진행하는 방법에 대해서 이야기 해보려고 합니다.




1. HiveQL 측면에서 성능 향상하기

첫 번째 파트로는 HiveQL 측면에서 성능을 향상하는 세 가지 방법에 대해 소개하려 합니다. 딥하게 Hive의 구조를 뜯어보고, 실행 옵션을 수정할 필요 없이 쿼리를 일부분 수정하는 것만으로 성능을 향상시킬 수 있는 방법들입니다. 생각했던 것보다 HiveQL로 짜여진 ETL 로직의 실행 시간이 오래 걸릴 때, 아래 방법들을 먼저 적용해보는 것도 좋은 선택이 되겠죠. (생각보다 큰 효과를 볼 수 있을지도 모르구요!)


1-1. 조건절 내의 사용자 정의 함수 제거

사용자 정의 함수(UDF)는 Hive 옵티마이저가 제대로 인식할 수 없는 함수입니다. 제대로 인식할 수 없다는 것은 옵티마이저가 쿼리에 대한 실행 계획을 수립할 때 해당 함수는 비용 계산에서 제외되게 된다는 것을 뜻하죠. 즉, 사용자 정의 함수를 WHERE 조건 절에 사용하는 경우에는 해당 UDF 조건을 통해 추출 데이터의 건수가 줄어들 수 있음에도 불구하고 쿼리 수행 비용 계산 시에는 반영이 되지 않습니다. 옵티마이저 입장에서는 없는 함수니까요.

특히, 파티션 키 컬럼의 필터링 조건으로 UDF 가 사용되는 경우는 성능 상의 이슈가 조금 더 심각해집니다. 쿼리 수행 비용 계산에 함수가 반영되지 않는 것보다 큰 문제가 발생하기 때문이죠. 분명히 내가 짠 쿼리에는 파티션 키 컬럼의 필터링 조건이 존재하지만 Partitions Pruning이 일어나지 않게 됩니다.

Partitions Pruning이 무엇이길래 성능에 영향을 끼치는 것일까요? Partitions Pruning이란 말 그대로 파티션 가지치기를 의미합니다. 실행 시점에서 쿼리의 조건 절을 분석하여 읽지 않아도 되는 파티션 세그먼트를 액세스 대상에서 제외하는 기능인데, 쉽게 말하면 SQL 수행 시 필요 없는 파티션에 대해서는 가지를 치고 필요한 파티션만을 읽게 하는 기능입니다.

이제 상황을 정리해봅시다. 원하는 값만 필터링해서 읽어오고자 UDF로 파티션 키 컬럼에 조건을 주었지만, 옵티마이저 입장에서는 없는 필터링이었고 결국에는 Partitions Pruning 없이 전체 테이블의 데이터를 읽는 방식으로 쿼리가 실행됩니다. 특정 파티션의 데이터 만을 읽어오는 것과, 전체 테이블의 데이터를 읽어와서 필터링 하는 것. 전자가 훨씬 더 퍼포먼스가 좋을 것이 명백하죠.

따라서, 최대한 조건 절 내에서는 사용자 정의 함수의 사용을 피하고 제공되는 기본 함수를 사용해야 합니다.

SELECT p_date
	, COUNT(DISTINCT column_name)
FROM table_name
WHERE p_date BETWEEN nc_to_char('20211006', 'yyyy-MM-dd') AND nc_to_char('20211008', 'yyyy-MM-dd')
GROUP BY p_date

위의 쿼리에서 p_date는 파티션 키 컬럼이고 nc_to_char(a, b)라는 함수는 a값을 b의 포맷에 맞게 변환시켜주는 UDF 입니다. 이 때, 해당 쿼리의 성능을 생각해본다면 해당 UDF 대신 다른 기본 제공 함수를 써야하겠죠. 이 경우, 동일한 기능을 하는 DATE_FORMAT(a, b) 라는 함수를 사용하여 아래처럼 쿼리를 수정할 수 있습니다.

SELECT p_date
	, COUNT(DISTINCT column_name)
FROM table_name
WHERE p_date BETWEEN DATE_FORMAT('20211006', 'yyyy-MM-dd') AND DATE_FORMAT('20211008', 'yyyy-MM-dd')
GROUP BY p_date


1-2. DISTINCT COUNT 연산 피하기

COUNT(DISTINCT 컬럼명) 함수는 컬럼 내 중복을 제외한 데이터의 건수를 제외하기 위해 사용합니다. 이 때, 전체 데이터에서 중복이 제거된 건수를 계산해야 하므로 단 하나의 리듀스 테스크가 해당 작업을 맡아서 처리하게 되죠. 즉, 데이터의 건수가 얼마나 많은지와는 상관없이 1개의 리듀스 테스크만이 할당되게 됩니다. 이 경우, 하둡의 장점인 분산처리의 이점을 전혀 활용하지 못하게 됩니다.

SELECT p_date
	, COUNT(DISTINCT column_name)
FROM table_name
WHERE p_date BETWEEN DATE_FORMAT('20211006', 'yyyy-MM-dd') AND DATE_FORMAT('20211008', 'yyyy-MM-dd')
GROUP BY p_date

위의 예시 쿼리는 설명한 것처럼 COUNT 함수 안에서 DISTINCT 작업이 이루어지는 형태이기 때문에 단 1개의 리듀서만을 할당 받게 됩니다. 이를 방지하기 위해서 아래처럼 쿼리를 수정할 수 있습니다.

SELECT p_date
	, COUNT(column_name)
FROM (
	SELECT p_date
	    , column_name
	FROM table_name
	WHERE p_date BETWEEN DATE_FORMAT('20211006', 'yyyy-MM-dd') AND DATE_FORMAT('20211008', 'yyyy-MM-dd')
	GROUP BY p_date, column_name
) tmp
GROUP BY p_date

수정된 쿼리를 보면, 서브 쿼리 안에서 GROUP BY를 통해 중복이 제거된 중간 데이터 결과 셋을 생성하고 있습니다. COUNT(DISTINCT 컬럼명)의 방법과 달리 GROUP BY 연산은 다수의 리듀스가 작업을 분배하여 처리하게 됩니다. 결과적으로 제일 바깥에 있는 COUNT 연산은 크기가 감소된 중간 데이터 결과 셋을 입력으로 사용하게 될 것입니다.


1-3. JOIN 사용 시 고려사항

HiveQL에서 조인을 사용할 때는 몇 가지 고려해야 할 사항들이 있습니다.

첫번째로 가장 큰 테이블이 가장 마지막 순서로 오도록 해야 합니다. 이는 HIVE 옵티마이저는 쿼리의 마지막 테이블이 가장 크다고 가정하기 때문입니다. 마지막 테이블을 가장 큰 테이블로 가정하고 있기 때문에 상대적으로 작은 크기라고 생각되는 앞의 테이블들을 버퍼링하려고 시도하고, 각 레코드에 대해서 조인을 수행한 후, 제일 마지막 테이블로 보내는 방식으로 조인 연산이 일어나게 됩니다. 즉, 가장 큰 테이블이 마지막에 위치하지 않을 경우에는 해당 테이블이 버퍼에 올라가기 때문에 성능 저하가 발생할 수 있겠죠.

하지만, 때로는 쿼리의 가독성 측면에서 아니면 다른 이유로 가장 큰 테이블을 쿼리의 마지막에 위치시키지 못하는 경우가 있습니다. 이럴 때는 다음과 같이 처리 가능합니다.

SELECT /*+STREAMTABLE(a)*/
	a.*, b.*
FROM a
JOIN b ON (a.column_name = b.column_name)
WHERE a.column_name IS NOT NULL

위의 쿼리에서처럼 /*+STREAMTABLE(a) 구문은 쿼리상 a 테이블이 제일 마지막에 위치하지는 않았지만, HIVE 옵티마이저에게 a 테이블을 가장 마지막에 사용하게끔 합니다.

두번째는 OUTER JOIN과 파티션 필터의 순서를 이해하는 것입니다. 쿼리를 짤 때, 검색 최적화를 위해 우리는 파티션 필터를 종종 채택합니다. 하지만, OUTER JOIN 연산은 WHERE 조건 절의 파티션 필터를 무시한 채 진행됩니다. 이유는 바로 조인을 수행한 후에 WHERE 절을 평가하기 때문입니다. 즉 순서상으로 JOIN 이후에 WHERE 조건절을 진행하기 때문에 파티션 조건이 적용되지 않은 상태에서 JOIN 연산이 먼저 수행되는 것이죠. 이 경우, 당연히 성능이 저하될 수 밖에 없습니다.

SELECT a.column_name
	, b.column_name
FROM a
LEFT OUTER JOIN b ON (a.condition = b.condition)
WHERE a.partition_column = 'a_value'
  AND b.partition_column = 'b_value'

이 때, 아래 쿼리와 같이 모든 조인에서 중첩 SELECT 문을 사용하여 파티션 필터를 사용하도록 만들 수 있습니다.

SELECT a.column_name
	, b.column_name
FROM (
	SELECT *
	FROM a
	WHERE partition_column = 'a_value'
) a
LEFT OUTER JOIN (
	SELECT *
	FROM b
	WHERE partition_column = 'b_value'
) b
ON a.condition = b.condition


1-4. SELECT 사용 시 고려사항

SELECT 절에서도 성능을 위해서 고려해야 하는 부분이 있습니다. 바로 필요한 컬럼만을 가져와서 사용하게 하는 것이죠. ETL에서 사용하는 테이블들은 대부분 대규모의 테이블입니다. 많게는 수 십개의 컬럼을 가지는 테이블도 있죠. 이렇게 많은 갯수의 컬럼들로 이루어져 있을 때 SELECT * 대신 SELECT a, b, c와 같이 필요한 컬럼만을 명시하여 사용하는 것이 훨씬 좋습니다.

특히 Parquet 형식의 데이터의 경우는 컬럼 단위로 파일이 적재됩니다. 즉, 컬럼을 명시하지 않으면 모든 데이터를 불러와서 사용해야 하지만 SELECT 절에서 컬럼명을 명시해주는 것만으로도 필요한 파일만을 불러와 사용할 수 있게 되는 것입니다.

SELECT a.column_name
	, b.column_name
FROM (
	SELECT *
	FROM a
	WHERE partition_column = 'a_value'
) a
LEFT OUTER JOIN (
	SELECT *
	FROM b
	WHERE partition_column = 'b_value'
) b
ON a.condition = b.condition

앞서 사용한 쿼리입니다. 중접 SELECT 문을 사용하여 JOIN 연산 전에 파티션 필터를 거치기 때문에 원래의 쿼리보다 성능은 향상되었지만 여전히 중첩 SELECT 문 안에서는 *를 통해 모든 컬럼을 불러오고 있는 상황입니다. 이 경우 필요한 컬럼만을 SELECT 해오도록 아래와 같이 수정한다면 성능이 훨씬 향상될 것입니다.

SELECT a.column_name
	, b.column_name
FROM (
	SELECT column_name, condition
	FROM a
	WHERE partition_column = 'a_value'
) a
LEFT OUTER JOIN (
	SELECT column_name, condition
	FROM b
	WHERE partition_column = 'b_value'
) b
ON a.condition = b.condition


2. RDBMS 쿼리 측면에서 성능 향상하기

앞서 HiveQL에 대해서 이야기하였으니, 이번에는 RDBMS 쿼리 측면에서 어떻게 성능을 향상시킬 수 있을지에 대해 이야기해 볼 차례입니다.


2-1. 불필요한 인덱스 줄이기

평소 인지하지 못할 수 있지만, 데이터 웨어하우스가 과도하게 인덱싱되어 있는 경우는 상당히 빈번하게 발생합니다. 인덱스 생성은 성능 문제를 해결하기 위해 주로 채택되는 접근 방식이기 때문이죠. 하지만, ETL 작업의 경우에는 일반적으로 성능 향상에 인덱싱은 도움이 되지 않을 뿐더러 로드 시간도 늘어나게 됩니다. 모든 추가 인덱스는 INSERT, UPDATE 또는 MERGE 문의 DML 성능을 낮추며, 최악의 경우에는 옵티마이저가 조인의 방식으로 Nested Loops Join을 사용하도록 합니다.

옵티마이저가 조인의 방식으로 Nested Loops Join을 채택하는 것이 왜 좋지 않은 것일까요? Nested Loops Join 이란 바깥 테이블의 처리 범위를 하나씩 액세스하면서 추출된 값으로 안 쪽 테이블을 조인하는 방식입니다. 흔히 사용하는 이중 FOR문을 도는 방식으로 조인 연산을 처리한다고 생각하면 이해하기가 더 쉬울 것 같습니다. 이러한 Nested Loops Join의 특징과 사용 경우는 다음과 같습니다.

  • 특징
    • 순차적으로 처리
    • 순차적으로 돌기 때문에 안 쪽 테이블에 인덱스 필요
    • 메모리 사용량이 가장 적음
    • 두 테이블의 랜덤 I/O가 높게 나옴
    • 많은 양의 데이터를 조회하는 경우의 성능은 낮음
  • 사용 경우
    • 외부 테이블로 사용되는 테이블의 양이 작고 내부 테이블로 사용되는 테이블의 양이 많을 때
    • 내부 테이블에 인덱스가 생성되어 있을 때
    • 적은 양의 행에 대해서 일어나는 트랜잭션이 다수 발생하는 경우

ETL 작업들은 주로 대량의 데이터를 대상으로 합니다. 이 경우에 Nested Loops Join은 적합한 조인 방식이 아닌 것이죠. 또한 큰 데이터 집합을 사용하는 ETL 작업의 특성 상 테이블의 작은 비율만 반환하도록 설계된 인덱스는 성능 향상에 있어서 당연히 적절한 방법이 아닙니다.

따라서, 가능한 한 적은 수의 인덱스를 사용해야 합니다. 기본키, 고유 제약 조건에 대해서만 인덱스를 두는 것이 베스트겠죠?


2-2. WHERE 조건에서 함수 피하기

WHERE 조건에서 표현식 또는 함수 호출을 하는 것을 피해야 합니다. 필터 조건에서 UPPER, SUBSTR 등과 같은 SQL 함수를 사용하면 수정되지 않은 열만 사용하는 경우보다 옵티마이저가 카디널리티를 추정하기 훨씬 어려워집니다. 당연히, 원하는 모양의 데이터를 얻어야 하니 모든 필터 조건에서 표현식 또는 함수를 제거할 수는 없지만, 때로는 이전 단계에서 변환을 수행하는 것이 성능에 있어서 유리합니다.

이해하기 쉽게 예시를 들어볼게요. 백만 개의 행이 있는 테이블에 대한 쿼리를 가정해 봅시다.

  • 쿼리에는 AND 연산자로 이루어진 SQL 함수를 호출하는 필터 조건 3개가 포함되어 있습니다.
  • 옵티마이저는 각 필터 조건에 대해 1%의 선택도를 가정합니다.
  • 해당 쿼리의 카디널리티는 0.01 x 0.01 x 0.01 x 1000000 = 1(행)이 됩니다.

즉, 해당 필터 조건들이 실제로는 저렇게 낮은 선택도를 가지지 않더라도 함수로 이루어진 조건들에 대해서 옵티마이저가 실제보다 낮은 선택도를 가정하게 되고 카디널리티는 아주 작게 계산되게 됩니다. 이처럼 작은 카디널리티일 때, 옵티마이저가 Nested Loops Join을 선택할 확률은 매우 높아집니다. (Nested Loops Join의 단점은 위에서 설명했으니 넘어가겠습니다.)


2-3. WHERE절 안에 있는 OR 조건에 주의하기

옵티마이저는 OR로 결합된 복잡한 WHERE 조건에 대해서 최적화 되지 않은 실행계획을 만들 가능성이 높습니다. column = 1111 OR column IS NULL 과 같은 구문은 IFNULL(column, 1111) = 1111 로 대체할 수 있듯이, 최대한 WHERE 조건에서 OR 조건으로 결합되는 상황을 피하는 것이 좋습니다. 혹은, OR 이 있는 SQL 문을 두 개의 개별 쿼리문으로 분할하고 UNION 처리하는 것이 원래의 SQL 문보다 훨씬 빠르게 처리됩니다.




3. ETL 작업 측면에서 성능 향상하기

지금까지는 HiveQL과 RDBMS 쿼리를 수정하는 것으로 ETL 자체의 성능을 향상시키는 방법에 대해서 소개했다면, 이번 파트에서는 ETL 작업 설계를 어떻게 하면 수월하게 할 수 있을지, ETL 수정 빈도를 어떻게 하면 줄일 수 있을지와 같은 ETL 작업을 하는 과정에서의 성능 향상에 대해 이야기 해보려 합니다.


3-1. 가능한 빨리 테스트 데이터로 채우기

ETL 프로세스의 핵심 단계는 최종적으로 적재되는 데이터의 형태를 이해하는 것입니다. 결과적으로 내가 얻고 싶은 데이터 모델이 어떤 형태인지, 어떤 모양의 데이터를 채울 지 이해하는 것이 가장 중요합니다.

샘플 데이터로 데이터 웨어하우스의 팩트 및 디멘젼 테이블을 채우면, 그 이후로 이어질 모든 작업들이 최종적으로 우리가 목표하고 있는 결과물과 일치하는지 확인하는 것에 많은 도움이 됩니다. 이 때 다수의 데이터가 아니라 소수의 행만 채워도 ETL 개발자는 실제 로직을 통해 목표 테이블의 데이터를 채우는 데 사용할 ETL 모델의 구조를 수월하게 설계할 수 있습니다.


3-2. 소스 데이터 검증 및 키 정의

ETL 작업을 개발하기 전에 주요 데이터 관계, 값의 범위 열 이상 등의 식별은 꼭 이루어져야 하는 단계입니다. 특히, 모든 소스 테이블에서 기본 키 정의와 해당 키와 관련된 정보들을 식별하는 것이 중요합니다. 해당 테이블의 기본 키가 의미하는 바가 무엇인지, 자료형은 무엇인지, 이 기본키와 다른 테이블의 컬럼과는 어떤 관계가 형성되어있는지 등이 모두 파악되어야 하는 정보에 포함되겠죠.

소스 데이터에 누락으로 인한 구멍이 있는지도 확인이 필요한 부분입니다. 현재 소스 테이블의 모든 데이터가 정상적으로 적재되고 있는지, 누락되고 있는 데이터가 있다면 누락의 원인이 무엇인지, 해당 데이터의 누락이 이번 ETL 작업에 영향을 얼마나 끼칠지 등이 파악되어야 합니다.

이처럼 소스 데이터에 대한 이해와 검증이 이루어지지 않았다면 올바르게 해당 소스 데이터를 사용할 수 없습니다. 데이터 유형을 미리 확인하지 않아, 다 완성한 ETL 작업에서 후에 데이터 유형에 따른 문제가 발생할 수도 있고 소스 데이터의 의미를 제대로 파악하지 못해서 처음 의도와는 다른 ETL이 만들어져 전체적인 로직을 수정해야 할 수도 있습니다. 이런 불상사를 막기 위해서 소스 데이터 검증은 ETL 개발을 시작하는 단계에서 필수적으로 이루어져야 합니다.


3-3. ETL 로그

비단 ETL 뿐만이 아니라 모든 개발 작업에서 로그 유지는 상당히 중요합니다. ETL 로그 역시 매우 중요한데요. 과거의 로그를 기반으로 현재 ETL 작업의 문제점을 파악하고 해결할 수 있기 때문입니다. 유지해야 하는 로그의 종류는 다음과 같습니다.

  • ETL 작업 시작 시간 및 실행에 걸린 시간
    • 평소 ETL 작업 시간에 대한 로그를 통해 유달리 빨리 끝났다던가, 아니면 이상하게 오래 걸린 경우를 판별하여 ETL 오류를 잡아낼 수 있습니다.
  • 모든 오류 로그
    • 동일 오류 발생 시 해결 과정을 찾기에 매우 유리합니다.
  • 삽입/변경/삭제가 일어난 데이터 행 수
    • 해당 로그를 통해 정상적으로 ETL이 동작하였는지 확인 가능합니다.
  • 이번 ETL 작업으로 인하여 생성된 키
    • 목표했던 데이터가 이번 ETL로 잘 적재되었는지 확인할 수 있습니다.


3-4. 소스 시스템 검증

앞서 말했던 소스 데이터 검증이 소스 데이터에 누락된 데이터가 있는지, 어떤 자료형을 가지고 있는지 등의 내용을 검증하는 것이라면, 시스템 검증은 소스 데이터를 적재하는 시스템에 관한 검증입니다. 평소에 추출되던 소스 데이터 양보다 급격하게 적은 행 수의 소스 데이터가 적재되고 있는지, 소스 데이터가 적재되는 로직에 변경사항이 있었는지 혹은 같은 로직이 적용되는 소스 데이터가 동일한 데이터 값을 가지고 있는지 등이 이에 포함됩니다.

그렇다면 소스 시스템 검증은 어떻게 할 수 있을까요? 총 두 가지 위치에서 검증을 진행할 수 있습니다. 먼저, 처음 해당 소스 데이터를 로드하는 부분입니다. 이 부분에서 불일치가 일어났다면 소스 데이터를 추출하는 시스템에 문제가 있다는 의미입니다. 두 번째는 결과적으로 추출된 소스 데이터를 적재하는 부분입니다. 이 부분에서 불일치가 일어났다면, 소스 추출은 성공적으로 작동했지만 최종 데이터 모델에 적재할 때 데이터가 누락되거나 또는 이중 계산이 이루어지고 있는 것입니다. 이렇게 어느 부분에서 문제가 있는지 확인하면 훨씬 수월하게 소스 시스템 문제의 원인을 파악할 수 있겠죠?


3-5. 스케줄링

ETL 작업에서 스케줄링은 매우 중요한 역할을 합니다. 스케줄링을 어떻게 하느냐에 따라서 작업의 속도와 효율이 향상될 수도 저하될 수도 있기 때문이죠. 여기서는 이해하기 쉽게 스케줄링을 크게 세 가지로 세분화하여 설명해보겠습니다.

먼저, 작업을 실행해야 하는 시기에 대한 스케줄링입니다. 대부분의 ETL 작업이 그렇듯이, 기존의 데이터를 가지고 최종 데이터 모델에 적재할 데이터를 추출하는 경우를 말합니다. 전 날의 데이터를 가지고 작업을 실행해야 할 때, 오늘의 데이터로 실행되는 ETL 작업의 스케줄 일자는 내일이 될 것입니다. 같은 원리로 한 시간 전의 데이터를 사용할 때는 현재의 데이터를 사용하여 1시간 후의 ETL 스케줄을 돌리게 될 것입니다.

두 번째는 이벤트에 따른 스케줄링입니다. 다른 작업이 완료될 때까지 특정 작업을 시작할 수 없거나, 혹은 소스 데이터가 적재되기 전까지 작업을 시작할 수 없는 경우가 이에 해당합니다. 이렇게 소스 데이터 혹은 앞 쪽의 작업에 대해 의존성이 생기는 경우, 해당 작업의 완료 여부를 알아야 하겠죠. Airflow를 사용한다고 가정했을 때, 해당 문제는 Sensor를 사용하여 쉽게 해결이 가능합니다. 앞의 작업에 영향을 받는 경우라면 해당 작업이 완료되었는지를 체크하는 Sensor를, 소스 데이터에 영향을 받는 경우라면 해당 데이터가 적재되었는지를 체크하는 Sensor를 둔다면 보다 깔끔한 스케줄링이 가능합니다.

마지막은 병렬처리에 따른 스케줄링입니다. 연속적으로 로직이 적용되는 경우가 아니라면 병렬로 ETL 작업을 실행하는 게 훨씬 더 성능에 도움이 됩니다. 전 일, 전 시간의 데이터가 현재 데이터에 영향을 미치지 않고 독립적으로 적재되는 경우, 굳이 순서대로 ETL 작업을 실행할 필요가 없겠죠? 이럴 때는 병렬로 동시에 여러 작업을 실행하여 성능을 향상시킬 수 있습니다.


3-6. 용량계획

실제 늘어날 용량을 미리 정확하게 에측할 수는 없지만, 대략적으로라도 데이터베이스의 예상 성장을 추측하는 것이 앞으로의 작업을 대비하기 위해서는 필수적입니다.

데이터베이스의 예상 성장을 결정할 때 고려해야 하는 항목은 다음과 같습니다.

  • 새롭게 추가될 것이라고 예상되는 데이터
    • 앞으로 추가될 예정인 데이터에 대한 정보가 있다면 더욱 세밀한 용량 계획이 가능합니다.
  • 백업 데이터
    • 백업 데이터의 생성주기와 너무 오래된 백업 데이터의 삭제 주기, 크기 등이 해당됩니다.
  • 정기 스냅샷
    • 복구를 위해 정기적으로 저장하는 스냅샷에 대해서도 당연히 고려가 되어야 합니다.
  • 운영 데이터 혹은 중간 영역 데이터
    • 현재 스케줄링이 되어 돌아가고 있는 ETL 작업과정에서 생겨나는 중간 영역 데이터, 그를 관리하기 위한 운영데이터 등이 해당됩니다.

위와 같이 다양한 항목들을 고려하고 팀과 함께 주기적으로 데이터베이스의 증가 패턴 등을 공유하고 검토하는 것이 필요합니다. 더 많은 항목들을 고려할 수록 더욱 디테일한 용량 계획이 가능하겠죠? 주로 분기별로 용량 계획을 검토하는 것이 적절하다고 합니다.




4. 마치며

지금까지 HiveQL, RDBMS 쿼리 측면에서 성능을 향상시킬 수 있는 간단한 방법들과 ETL 작업을 하는 것에 있어서 효율적인 작업을 위해 도움이 될 만한 팁들을 몇 가지 소개드렸습니다.

원하는 결과 데이터를 추출 및 적재하는 것도 물론 중요하지만, 그 과정에서 성능까지 고려하여 작업한다면 금상첨화겠지요. 저도 작성하면서 그동안 너무 기능에만 치중한 쿼리 작성과 ETL 작업을 해오지 않았나 하는 생각이 많이 들었습니다.

아래에 해당 게시글을 작성하며 참고한 자료들을 첨부할 테니, 시간 나실 때 다들 읽어보시면 좋을 것 같습니다.

그럼 또 다음 게시글로 찾아뵙겠습니다. 감사합니다.




Appendix.