0. 시작하며
정답이 없다는 것은 참 어려운 것 같습니다. 마치 이 도입부를 쓰는 데에 제가 1시간 30분이 넘는 시간 동안 하얀색 화면을 보며 어떠한 말로 이 포스팅을 시작하면 좋을지 고민한 것처럼요. 본문은 쉽습니다. 제가 이 포스팅에서 쓰고자 한 내용을 전달하면 되니까요. 마무리 역시 모든 내용을 정리하며 끝마치는 내용을 적으면 되니 어려울 것도 없습니다. 그러나 도입부는 그러한 가이드라인이 없어서 어떻게 해야 잘 쓴 도입부일까 하는 고민을 하게 되고야 맙니다.
ETL을 개발하시는 분이라면 작업하면서 위에서 제가 한 것과 같은 고민을 많이 하실 것입니다. “이렇게 설계하는 게 맞을까?”, “이게 가장 효율적인 구조일까?” 혹은 “ETL 작업을 하면서 지켜야하는 주의사항 같은 것은 없을까?” 등등…(일단 저는이런 생각을 꽤 자주 했습니다) ETL 작업 역시도 정답이랄 게 없으니 말이죠. 어떤 것이 잘 쓴 도입부인지 딱 정의내리기 어려운 것처럼 ETL 역시 어떻게 해야 현 상태에서 내가 해낼 수 있는 최고로 효율적이고, 완벽하며, 다른 사람이 보아도 깔끔하게 보이는 결과물을 만들어낼 수 있을지 파악하기가 어렵습니다.
이번 포스팅에서는 저런 고민을 하고 계시는 분들에게 조금이나마 도움이 될 만한 ETL에서 지켜야하는 원칙들에 대해서 다뤄볼까 합니다. 물론 이 원칙들을 지킨다고 해서 최고로 효율적이고, 완벽하며, 다른 사람이 보아도 깔끔하게 보이는 결과물을 만들어낼 수 있을거라고 장담하기는 어렵습니다만, 해당 원칙들을 지키고 고려해보는 것만으로도 기존보다는 훨씬 나은 모습의 ETL 작업이 가능하리라고 생각합니다.
1. ETL의 개념
ETL의 원칙에 들어가기 전에, 먼저 정확한 ETL의 개념에 대해 정리해보았습니다. 해당 파트에서는 ETL이 정확히 어떤 것을 의미하는지, ETL의 역할이 어떤 것인지, 정확히 어떤 부분부터 어디까지를 아우르는지에 대해 설명하고자 했습니다.
1-1. 역할
ETL에서는 데이터 웨어하우스를 설계, 구축하고 유지 관리하는 일을 합니다. 여러 시스템의 데이터를 단일 데이터베이스, 데이터 저장소 등에 결합하여 데이터 웨어하우스를 구축하고, 기존 데이터를 저장하거나 집계, 분석하여 이를 비즈니스 결정에 활용할 수 있도록 하는 것이죠. 이와 같은 ETL의 역할을 한 문장으로 정리하자면 아래와 같습니다.
“수많은 팀에서 관리하는 구조화된 데이터와 구조화되지 않은 데이터를 비롯한 전체 데이터를 가져와 비즈니스 목적에 실질적으로 유용한 상태로 변환하는 프로세스”
1-2. ETL 프로세스
ETL은 총 세 단계로 이루어져있습니다. 바로 추출(Extract), 변환(Transform), 적재(Load) 단계 입니다. ETL이라는 약어 역시 각 단계의 첫 글자를 따와 만들어졌습니다.
- 추출
- 다양한 소스들로부터 데이터를 추출합니다. 이 때 추출 대상들은 같은 데이터베이스일 수도, 다른 데이터 베이스일 수도 있습니다.
- 이렇게 추출된 데이터는 결과적으로 변환 단계를 거쳐 단일 데이터베이스(데이터 웨어하우스)에 적재됩니다.
- 변환
- 추출한 데이터를 구체적인 비즈니스 로직에 의해 수정하고 변형하는 단계입니다.
- 재구성만 필요한 경우도 있으나, 중복을 제거하고 일관성을 확보하는 등의 정제 과정 역시 해당 단계에 포함되며, 각 데이터 필드를 검사하고 규칙을 적용합니다. (e.g. 컬럼 도메인 적용)
- 결과적으로 적재되었으면 하는 데이터의 형태가 되도록 로직을 적용하는 과정이 이루어집니다.
- 적재
- 위의 두 단계를 거쳐 추출/변형된 데이터를 타겟 데이터베이스 내에 저장하는 단계입니다.
1-3. 특징
세 단계로 이루어져 있기 때문에 ETL은 연속적이고 지속적인 프로세스라는 특징을 가집니다. 동종(혹은 이종)의 데이터 소스에서 데이터를 추출하여 스테이징 영역에 추출한 데이터를 임시 보관한 후, 스테이징 영역에서 데이터를 필터링, Reshaping하는 등의 프로세스를 거쳐 데이터웨어하우스에 저장되기까지의 단계들이 연속적으로 이루어져야 한다는 것이죠. 해당 단계들을 진행하는 데에 있어서 데이터 엔지니어 및 개발자의 계획, 감독 및 코딩 역시 필요합니다.
덧붙여, 최신 ETL 솔루션의 경우는 쉽고 빠르다는 특징 또한 가지고 있습니다. 아래의 예시는 클라우드 기반 ETL 솔루션인 Xplenty입니다. 프로그래밍 전문 지식이 없어도 다양한 소스에서 ETL을 수행할 수 있도록 설계되어 있는 것을 볼 수 있습니다.
2. ETL 원칙
해당 파트에서는 ETL 작업을 진행하면서 지켜야 하는 원칙들에 대해서 정리해보았습니다. 해당 원칙들의 목적은 데이터의 품질 및 보안을 유지하는 것으로 데이터를 사용하는 사용자가 올바른 장소에서 적절한 시간에 올바른 정보를 사용할 수 있도록 합니다. 이렇게 올바른 데이터를 제공함으로써 사용자가 적절한 의사결정을 할 수 있도록 합니다.
2-1. 멱등성 제약
멱등성이란 같은 작업을 실행할 때마다, 동일한 결과를 보장한다는 것을 의미합니다. 즉, 프로세스가 다른 날짜/시간/조건에서 동일한 매개변수로 실행되는 경우 결과가 동일하게 유지된다는 것이죠.
일반적으로 모든 ETL 실행의 결과는 항상 멱등성 특정을 가져야 합니다. 이러한 멱등성의 개념을 ETL에 실제로 적용해보면, 다음과 같은 경우가 될 수 있습니다.
- ETL 프로세스를 재실행하여 재적재를 진행했을 때, 데이터가 중복 적재되어서는 안됨
- ETL 프로세스가 중단되었을 경우, 다시 실행했을 때 기존 실행이 중단되지 않은 것처럼 동일한 결과가 보장되어야 함
- 과거 어느 날의 ETL 프로세스를 재실행했을 때, 당시의 실행결과와 동일한 결과가 보장되어야 함
- 예약된 ETL 프로세스를 수동으로 실행했을 경우, 둘 간의 차이가 없어야 함
아래는 멱등성이 보장되지 않는 경우의 상황입니다. ETL 프로세스가 왼쪽 그림처럼 진행되던 중 예기치 못한 상황으로 인해 중단되는 경우 재실행했다고 가정해보죠. 그랬을 때 멱등성이 보장되지 않는다면, 오른쪽 그림과 같이 중단된 시점부터 다시 데이터가 적재될 것입니다. 이 경우, 데이터가 중복 적재되는 것은 물론 해당 ETL 프로세스로부터 원헸던 결과를 얻을 수 없게 되는 것이죠.
그렇다면 멱등성을 보장함으로써 해결되는 문제상황은 어떤 것들이 있을까요? 다음과 같이 정리해 볼 수 있습니다.
- ETL 실행 실패
- ETL은 때때로 실패하여 실행의 누락이 발생할 수 있습니다. 이 때 ETL의 결과가 실행 타이밍에 따라 달라진다면, ETL 실패의 경우 재실행을 했을 때 원래 의도했던 것과는 다른 결과가 도출될 수 있습니다.
- 멱등성이 보장된다면, 재실행의 경우에도 실패가 발생하지 않은 것처럼 데이터 적재가 가능합니다.
- 비즈니스 로직 변경
- 비즈니스 로직은 시간이 지남에 따라 변경될 가능성이 있습니다. 이러한 경우, 멱등성이 보장되어야만 과거의 데이터를 수정된 비즈니스 로직으로 재적재를 진행할 때도 원하는 결과를 얻을 수 있습니다.
- 개발 환경 구축
- 데이터 변환 결과가 실행 타이밍에 다라 달라진다면, 라이브 환경을 미러링하는 개발 환경을 구축하는 것이 제한적입니다. 개발 환경에서 제대로 작동하는 로직도 라이브 환경에서 (실행하는 시점이 달라졌기 때문에) 결과가 다르게 나오면서 제대로 작동하지 않을 수 있습니다.
- 멱등성이 보장된다면, 해당 경우에도 동일한 결과를 보장하기 때문에 라이브 환경을 미러링하는 개발 환경 구축이 가능해집니다.
위에서 정리한 것처럼 멱등성을 보장함으로써 ETL 작업 상황에서 발생할 수 있는 상당히 크리티컬한 문제들이 해결이 됩니다. 이처럼 중요한 멱등성을 보장하기 위해서는 어떠한 방법들을 사용할 수 있을까요? 아래는 참고용으로 간단히 정리한 멱등성을 보장하기 위해 사용하는 방법들입니다.
- 비즈니스 로직의 기본 키 찾아 해당 키를 기준으로 OVERWRITE 하는 방식
- 다른 실행 시간에 ETL이 실행되어도 유지될 수 있는 비즈니스 로직의 고유 키를 판별해야 합니다. 이는 테이블의 기본 키와는 다를 수도, 같을 수도 있습니다.
- 해당 키를 기준으로 데이터를 적재하고, 필요한 경우에는 파티셔닝에도 해당 키를 사용합니다.
- 특정 키를 가진 데이터 전체를 덮어씌우는 방식이기 때문에 데이터의 중복 적재를 방지할 수 있습니다.
- MERGE INTO 커맨드 사용
- 테이블에 데이터가 이미 존재한다면 업데이트를 하고 존재하지 않으면 입력하는 방식으로 동작하는 커맨드입니다. 해당 커맨드로도 데이터의 중복 적재를 미연에 방지할 수 있습니다.
- 다만, 많은 데이터를 다룰 경우 성능이 저하될 가능성도 있기 때문에 그런 경우는 위의 비즈니스 로직의 기본키를 사용하여 OVERWRITE를 하는 방식이 성능 상 유리합니다.
2-2. 점진적 데이터 로드
점진적으로 데이터를 적재한다는 것은 각 ETL 실행에서 전채 팩트 테이블을 스캔하는 것이 아니라 이전 날짜 파티션만 스캔하여 데이터를 적재하는 방식을 말합니다. 테이블이 작은 크기일 때는 전체 팩트 테이블을 가져다가 쓰는 것도 별 무리가 없지만, 테이블이나 데이터 셋의 크기와 복잡성이 증가할 수록 전체 팩트 테이블을 사용하는 것이 부담이 되기 때문에 되도록이면 해당 원칙을 지켜주는 것이 좋습니다.
아래 예시의 두 가지 쿼리를 보면 점진적으로 데이터를 적재한다는 것이 어떤 것인지 훨씬 더 이해가 쉬울 것 같습니다. 첫번째 쿼리가 기존에 작성하던 전체 팩트 테이블을 스캔하는 쿼리고, 두 번째 쿼리가 해당 쿼리를 점진적으로 데이터를 적재하도록 수정한 쿼리입니다.
### as-is
INSERT OVERWRITE TABLE sample_summary_table PARTITION (pdt = '20210716')
SELECT customer
, COUNT(1) AS buy_cnt
FROM sample_fact_table
WHERE pdt <= '20210716'
GROUP BY customer
### to-be
INSERT OVERWRITE TABLE sample_summary_table PARTITION (pdt = '20210716')
SELECT customer
, SUM(buy_cnt) AS buy_cnt
FROM (
SELECT customer
, buy_cnt
FROM sample_summary_table
WHERE pdt = '20210715'
UNION
SELECT customer
, COUNT(1) AS buy_cnt
FROM sample_fact_table
WHERE pdt = '20210716'
GROUP BY customer
)
GROUP BY customer
2-3. 효율적인 과거 데이터 적재
비즈니스 로직이 바뀌는 등의 경우, 이전의 날짜로 돌아가서 과거 데이터를 처리해야 하는 경우가 발생하게 됩니다. 이런 과거 데이터를 효율적으로 적재하기 위해서는 시작 매개변수를 자율적으로 변경할 수 있어야 합니다. 즉, 비즈니스 로직을 실행시키는 날짜 또는 시간에 관계없이 시작 매개변수만 해당 과거 날짜로 지정된다면 과거 데이터를 다시 적재할 수 있도록 해야한다는 것이죠.
2-4. 데이터 분할
해당 원칙은 상당한 과거 데이터가 계속해서 적재되어 온 대형 테이블을 다룰 때 유용한 원칙입니다. 대부분의 경우는 날짜 스탬프를 사용하여 데이터를 분할하며 해당 방식으로 분할하게 되면 기존 데이터를 적재하는 경우, 여러 날짜를 병렬화하여 진행할 수 있다는 장점이 있습니다.
특히 UPDATE, APPEND 및 DELETE와 같은 DML 작업은 데이터의 변형 가능성이 있기 때문에 파티션으로 데이터를 분할하여 특정 파티션을 덮어쓰는 것이 데이터 베이스 이상현상을 방지해주는 방법이기도 합니다.
이때 파티션은 날짜 뿐만 아니라 주로 불변하는 객체를 사용하여 지정해주면 됩니다.
2-5. 중간 데이터 저장
여러 후속 Task에서 사용해야 하는 데이터의 경우는, 임시 로컬 파일을 만들어 사용하는 것을 지양해야 합니다. 해당 소스를 사용해야 하는 다른 Task가 다른 Task에서 생성한 임시 로컬 파일에 접근할 수 없는 경우가 생기기 때문입니다. 위의 경우를 방지하기 위해서는 모든 Task가 액세스할 수 있는 영역에 중간 데이터를 적재하는 것이 중요합니다. 저희 팀에서는 중간 데이터만 저장하는 DB를 따로 두어 관리하고 있습니다.
2-6. 시간에 따른 비즈니스 로직 관리
해당 원칙은 비즈니스 로직이 시간에 따라 소급 적용되는 경우에 고려해야 합니다. (소급이 아닌 경우=변경된 비즈니스 로직이 전체 데이터에 적용되는 경우는 과거 데이터 적재로 처리할 수 있기 때문에 해당 원칙의 고려 대상이 아님)
아래 예시를 보시면 비즈니스 로직의 소급적용이라는 게 어떤 건지 이해가 쉬울 것 같습니다. 예를 들어, 2021년에 새로운 세금 계산 방법으로 인해 비즈니스 로직의 변경이 생겼다고 가정해 봅시다. 앞으로 이 새로운 비즈니스 로직을 반영하도록 ETL 프로세스 자체를 업데이트 해버리면 어떻게 될까요? 2019년도의 과거 데이터를 재적재할 때, 2021년도의 새로운 세금 계산 방법이 반영된 비즈니스 로직을 적용하게 됩니다.
위처럼 시간에 따라서 각각 다른 비즈니스 로직이 적용되어야 하는 경우 유효 날짜를 사용하여 매개변수 테이블에 저장하고 프로세스를 처리할 때 올바른 매개변수를 결합하는 방식이 가장 바람직합니다.(하드코딩 지양) 2021년도 이후의 데이터를 적재하는 경우에는 새로운 비즈니스 로직을 적용하고, 그 이전의 데이터를 적재하는 경우에는 기존 로직을 적용하도록 말이죠.
2-7. 작업 단위의 명확성
작업단위의 명확성이 지켜진다는 것은 작업단위가 각각의 단일 파티션으로 출력되어야 함을 의미합니다. 해당 원칙이 지켜져야지만, 각 논리 테이블을 작업에 매핑하고 각 파티션을 작업 인스턴스에 매핑하는 것이 간단해집니다.
위의 그림은 Airflow에서 각 작업을 나타내는 방법입니다. 각 행이 테이블에 해당하는 작업을 의미하고, 그 행 안에서의 각 셀이 하나의 작업 인스턴스를 의미하죠. 이렇게 표시함으로써 개발자는 특정 파티션에 해당하는 로그파일을 셀 하나만 선택함으로써 쉽게 추적할 수 있게 됩니다.
위의 그림처럼 작업단위가 명확해지면, 작업의 재실행이 필요한 경우 전체 작업을 재실행하는 것이 아니라 원하는 셀만을 선택하여 다시 실행시킬 수 있습니다. (이를 위해서는 멱등성이 보장되어야 합니다.)
2-8. 데이터 검사(테스트)
데이터의 품질을 보장하기 위해 꼭 지켜야 하는 원칙입니다. 주로 데이터를 처리할 때 테스트 환경에서 충분히 데이터의 품질을 확인한 후, 라이브 환경에 적용시키도록 합니다. 특히나 ETL 로직을 라이브 환경에 적용시키기 전에 아래의 항목들을 충분히 검사/테스트/확인 해보아야 합니다.
- 메타데이터 검사
- 설계했던 스키마의 데이터 타입과 결과물의 데이터 타입이 동일한지
- 각 테이블마다 적절한 제약조건이 적용되어 있는지
- DB, 테이블, 컬럼 등이 정의된 도메인에 맞게 적용되어 있는지
- 대상 DB에 적재된 데이터가 예상했던 결과 레코드의 수와 일치하는지
- 예상했던 것보다 훨씬 많은 레코드가 적재되는 경우 JOIN등의 과정에서 오류가 발생했을 가능성이 있습니다. 반대로 예상했던 것보다 훨씬 적은 레코드가 적재되는 경우에는 로직이 원하는 대로 작동하지 않았을 가능성이 있겠죠.
- 적재된 결과 데이터들이 손실된 데이터 없이 대상 DB에 적재되고 의도대로 잘 변환되었는지
- 각각의 데이터가 잘못된 문자와 패턴 혹은 NULL을 포함하는 등의 부적절한 레코드의 형태로 적재되지는 않았는지
- 대상 테이블들에 모든 결과 데이터들이 올바르게 적재되었는지…등등
2-9. ETL 모니터링
ETL 작업은 실행하는데 있어서 짧게는 수 분이내로 끝날 수도 있지만, 길게는 몇 시간까지 걸리는 경우도 있습니다. 이런 경우는 개발자가 몇 시간 동안 작업이 정상적으로 진행되는지 지속적으로 체크를 할 수 없기 때문에 모니터링 방법이 필요합니다. 모니터링 방법을 따로 둠으로서, 작업의 진행상황 등을 지속적으로 확인할 수도 있습니다. 대표적으로 Airflow를 사용하는 경우는 빌트인 되어있는 시각화 차트를 이용하여 확인이 가능하고, 따로 메일링 시스템을 두는 경우도 있습니다.
Airflow에서 제공하는 다양한 시각화 차트들을 예시로 가져와보았습니다. 먼저 왼쪽 사진은 Airflow에 접속하면 가장 먼저 볼 수 있는 DAGs 탭 입니다. 해당 화면을 통해 우리는 각각의 DAG가 현재 어떤 상태인지, 오류가 발생한 DAG는 없는지 등을 확인할 수 있습니다. 오른쪽 사진은 각 DAG를 이루고 있는 Task들의 상태를 보여주는 Graph View 입니다. 해당 차트에서는 만약 DAG가 작업 실패 상태로 처리가 되었다면 해당 DAG안의 어떤 Task로 인해 작업이 실패했는지를 한눈에 파악할 수 있습니다.
위의 두 화면에서는 전체적은 DAG와 Task의 상태를 알 수 있었다면, 아래의 두 차트는 조금 더 세부적인 정보들을 제공합니다. 왼쪽의 Gantt Chart 에서는 각각의 Task 당 얼마만큼의 시간이 소요되었는지를 보여줍니다. 비정상적으로 긴 러닝타입을 가지는 Task를 캐치할 수 있겠죠. 오른쪽의 Task Tries 차트는 일자별로 어떤 Task를 몇 회정도 시도했는지를 차트로 볼 수 있습니다. 해당 차트를 통해 주로 실패하여 Retry가 실행된 Task가 어떤 Task인지를 확인할 수 있습니다.
3. 추가로 고려해야 하는 사항들
해당 파트에서는 위에서 말한 ETL 원칙 뿐만 아니라 추가로 고려해야 하는 사항들 4가지를 정리해보았습니다. 위의 원칙들처럼 ETL 작업을 하는 것에 있어서 필수적으로 지켜야 하는 것은 아니지만, 단발적인 ETL 작업이 아닌 지속적으로 ETL을 개발하고 관리하는 작업을 한다면 언젠가는 해당 사항들을 고려해야 할 때가 올 것입니다.
3-1. 리소스 사용
※Airflow DAG란?
Directed Acyclic Graph(유향 비순환 그래프)의 형태를 가지는 Task들의 모음입니다.
Task들을 어떤 순서와 어떤 dependency로 실행할지, 어떤 스케줄로 실행할지 등의 정보를 가지고 있습니다.
리소스의 ‘유연하고 효율적인’ 사용은 ETL 뿐만 아니라 다른 어떤 작업을 하더라도 필수적으로 고려해야 하는 부분입니다. 데이터와 서비스가 추가되고 또 성장할 수록 ETL 작업에서 실행시켜야 하는 DAG의 수도 늘어나기 마련입니다. 한정된 리소스 안에서 DAG 수의 증가는 각 DAG의 실행 속도를 점점 느려지게 할 것이고, 이는 곧 DAG의 대기시간 증가로 이어질 것입니다.
배치 주기가 긴 DAG이라면 대기시간의 증가가 치명적이지 않을 수 있지만 만일 해당 DAG이 1시간마다 돌아야 하는 작업이라면? 그리고 대기시간이 2시간을 넘어간다면? 해당 DAG는 개발자가 목적했던 시간에 동작하지 않을 가능성이 높습니다. 해당 예시에서 알 수 있듯이 DAG의 대기시간 증가는 배치주기가 짧은 DAG일수록 크리티컬합니다. 이를 방지하기 위해서는 끊임없이 한정된 리소스를 어떻게 효율적으로, 유연하게 사용할 수 있을 것인가에 대한 고민이 계속되어야 합니다.
3-2. 작명규칙 관리
두번째 고려해야 할 사항은 ETL 작업의 컨벤션을 관리하는 것입니다. 여기서 말하는 컨벤션이란 DAG 내에 작성된 코드의 컨벤션 혹은 DAG의 네이밍 룰, 컬럼 네이밍 룰 등의 모든 규칙을 이릅니다.
이 부분이 고려되지 않는 경우, 규칙 없이 제각각으로 코드가 작성되어 컨벤션이 무너지거나 유지보수가 까다로워질 수 있습니다. 같은 데이터를 저장하는 컬럼인데도 테이블마다 각각 컬럼명이 다른 경우를 가정해봅시다. 작업을 할 때마다 해당 테이블을 열어보고 컬럼명을 확인해야 하는 불필요한 단계가 계속해서 추가되겠죠. 혹은 컬럼명이 매치되지 않아 오류가 발생하는 상황이 빈번히 생길 것입니다.
또는, DAG 모듈의 이름을 정하는 네이밍 룰이 존재하지 않아 모니터링 로그를 봤을 때 에러를 발생시킨 DAG가 실제로 어떤 작업에 영향을 끼치는 DAG인지 파악하기 어려워지는 문제가 생길 수도 있을 것입니다. 또는 문제의 DAG에 포함되어 있는 파일들이 구체적으로 어떤 것인지 모든 코드를 열어봐야 하는 그런 끔찍한 일(…) 도 생길 수 있으니 미리미리 규칙을 정하는 것도 좋은 방법입니다.
3-3. 의존성
ETL을 실행시키는 Tool과 DAG코드의 의존성, DAG를 실행시키는 컨테이너와 스크립트에서 사용하는 라이브러리 간의 의존성, Airflow의 경우에는 Airflow의 버전과 파이썬 버전의 의존성 모두 고려 대상에 포함됩니다. 처음 설정부터 이러한 의존관계를 명확히 하거나 분리하지 않으면 서비스가 커질 수록, 여러 DAG가 추가될 수록 의존성의 늪에서 빠져나오지 못할 수 있습니다.
3-4. 운영과 테스트의 분리
네 가지 중에 어쩌면 가장 먼저 고려가 되어야 하는 부분입니다. 어떤 로직을 ETL에 올리기 위해서는 반드시 테스트를 거쳐야 하기 때문이죠. 이 때 테스트를 하기 위해 테스트용 코드를 실행하거나, 테스트용 테이블을 따로 만들텐데 그런 상황에서는 반드시 운영과 테스트 환경을 분리하는 것이 고려되어야 합니다. 해당 부분을 고려하지 않았을 때 일어날 수 있는 상황을 나열해보면 다음과 같습니다.
- 테스트를 하는 과정에서 실제 운영 중인 테이블에 직접 접근하는 로직을 실행시키는 경우
- 테스트를 하는 과정에서 테스트용 테이블 이름을 사용하는 것을 깜빡하는 경우
- 실제 운영되는 테이블을 덮어쓰거나, 과거 데이터를 건드릴 수 있음 (제일 절망적)
4. 자동화 가능한 워크플로우 패턴들
ETL 업무를 진행하다 보면, 여러 유형의 워크플로우를 개발하게 됩니다. 그 중에서는 입력 값 혹은 사용하는 테이블만 다르고 기조가 되는 로직은 같은 경우가 종종 있습니다. 이렇게 유사하거나 동일한 워크플로우에 대한 작업을 진행할 때마다 로직을 새로 개발하지 않기 위해서는 같은 유형의 워크플로우를 하나로 묶어 자동화하는 방법이 있습니다.
워크플로우를 자동화한다는 것은 곧, 워크플로우를 동적으로 만들어 입력만 주어져도 개발자가 원하는 작업이 수행될 수 있도록 한다는 것을 의미합니다. 즉, 비슷한 유형의 워크플로우를 실행시킬 때 입력값만 다르게 준다면 내가 원했던 워크플로우의 결과가 나와야 한다는 것이죠. 이를 위해서 워크플로우를 자동화할 때는 일반적으로 워크플로우를 입력, 데이터 처리, 출력 세 단계로 구분하여 설계한 뒤 자동화를 합니다. 각 단계가 명확히 구분되도록 설계되어야 동적으로 워크플로우를 실행시키기 수월하기 때문입니다.
아래는 대표적으로 자동화가 가능한 세 가지 워크플로우 패턴입니다. 자동화가 필요할 만큼 자주 쓰이는 워크플로우 패턴들이기도 하니, 참고하시면 좋을 것 같습니다.
4-1. 증분 계산
증분 계산이란 계속해서 누적값을 집계하는 워크플로우를 의미합니다. 보통 누적 합계 또는 특정 이벤트 이후 시간이 지남에 따른 집약적인 지표를 계산하는 경우에 많이 이용하는 워크플로우이기도 합니다. 예를 들어, “새로운 제품 출시 이후에 출시일부터 현재까지 해당 제품을 사용한 적이 있는 총 사용자 수” 같은 집계 데이터를 생산할 때 많이 사용하는 워크플로우죠.
위의 ETL 원칙 파트에서 말씀드렸던 것처럼 이 경우, 팩트 테이블에서 모든 날짜 파티션에 대해 SUM, MAX 또는 MIN 함수 등을 사용하여 데이터를 집계하는 것은 ETL 원칙에 위반됩니다. (효율적인 데이터 적재를 위해 ETL에서는 데이터를 점진적으로 적재해야 하기 때문이죠.) 이 경우의 이상적인 솔루션은 요약 테이블의 어제 날짜 파티션의 데이터와 팩트 테이블의 오늘 날짜 파티션의 데이터만을 가지고 집계하는 것입니다.
해당 워크플로우를 자동화하기 위한 입력, 데이터 처리, 출력 단계는 다음과 같이 설계될 수 있습니다.
- 입력 : 사용자가 사전 계산할 데이터, 혹은 쿼리할 팩트 테이블 등
- 데이터 처리 : 데이터를 점진적으로 로드(ETL 원칙에 적합하도록)하는 스크립트
- 출력 : 누적 합계가 저장된 요약 테이블
4-2. 과거 데이터 적재
ETL 작업을 진행하면서 가장 자주 만난다고 할 수 있는 워크플로우입니다. 대부분의 경우, ETL 파이프 라인을 구축하고 나서 히스토리를 재구성하는 작업이 필요하기 때문이죠. 이 경우, 현재 시점을 기준으로 과거의 데이터들을 적재해야 합니다. 또는, 기존 로직의 오류를 발견하였을 때도 해당 워크플로우가 필요합니다. 오류를 수정한 새로운 로직을 가지고 잘못된 로직에 따라 적재되었던 데이터를 새로 소급하여 적재해야 하니까요.
해당 워크플로우의 데이터 처리 단계에서 고려해야 하는 것은 바로 적재하려는 기간입니다. 데이터를 백필할 때 무작정 순차적으로 적재하는 것보다 미니 백필로 분할 후, 병렬화하여 적재 작업을 진행하는 것이 훨씬 더 효율적이기 때문입니다. 단기간의 데이터를 적재할 때는 병렬화의 필요성이 느껴지지 않을 수도 있지만, 몇 달 혹은 몇 년 분량의 데이터를 적재해야 하는 상황이라면 병렬화만큼 현명한 친구가 없습니다.
또, 데이터 처리 단계에 포함되어야 하는 작업은 바로 데이터 검사 작업입니다. 백필을 통해 적재되는 데이터가 내가 원하는 데이터의 모양이 맞는지, 잘못된 값이 들어가지는 않는지, 데이터의 누락이 발생하지는 않는지 등의 작업 또한 이루어져야 해당 워크플로우를 자동화하였을 때 데이터의 품질이 보장될 수 있습니다.
해당 워크플로우를 자동화하기 위한 입력, 데이터 처리, 출력 단계는 다음과 같이 설계될 수 있습니다.
- 입력 : 작업 이름, 시작 날짜, 종료 날짜, 병렬화 할 프로세스 수 등
- 데이터 처리 : 백필 작업 병렬화, 데이터 검사 수행, 백필한 테이블과 실제 테이블을 교체하는 프로세스
- 출력 : 과거 데이터가 완전히 적재된 테이블
4-3. 비정규화 작업
대시보드 ETL등을 구축할 때 자주 접하게 되는 워크플로우입니다. 대시보드에서 보여주고 싶은 차트, 표에 맞추어서 데이터를 적재할 때, 팩트 테이블에서 원하는 컬럼들만을 가져와 다른 팩트 테이블들과 조인하여 최종적으로는 비정규화된 테이블의 상태로 적재를 합니다.
해당 워크플로우를 자동화하기 위한 입력, 데이터 처리, 출력 단계는 다음과 같이 설계될 수 있습니다.
- 입력 : 하나 이상의 팩트 테이블, 최종 테이블에 포함하려는 컬럼 집합, 조인에 사용할 키 등
- 데이터 처리 : 필요한 컬럼들을 식별하고 조인하여 비정규화된 테이블을 자동으로 생성하는 프로세스
- 출력 : 하나 이상의 비정규화된 테이블
5. 마치며
지금까지 ETL이란 무엇인지, 어디까지의 역할을 말하는지와 ETL 작업을 할 때 고려해야하는 원칙들에 대해서 소개드렸습니다.
더 효율적인 ETL, 더 나은 ETL 작업을 위해서는 위에 작성해둔 원칙들과 고려해야 하는 사항들보다 더 많은 것들을 염두에 두어야 합니다. 위의 원칙들 중에서도 평소에 무의식적으로 지키고 있던 원칙이 있는가 하면, 이런 걸 고려해야 하는구나 처음 깨닫게 되는 원칙들이 있을 수 있겠죠. 너무 많은 것을 고려해야 하는 것이 아닌지 하는 의문점이 들 수도 있지만, 게시글을 훑어보면 알 수 있듯이 이유 없는 원칙은 없는 것 같습니다.
아래에 해당 게시글을 작성하며 참고한 자료 리스트를 첨부할테니, 시간 나실 때 다들 읽어보시면 좋을 것 같습니다. (제가 생략한 부분이 많거든요)
그럼 또 다음 게시글로 찾아뵙겠습니다. 감사합니다.
Appendix.
- https://airflow.apache.org/docs/
- https://www.confessionsofadataguy.com/the-elusive-idempotent-data-load-etl/
- https://fivetran.com/blog/idempotence-failure-proofs-data-pipeline
- https://blog.koresoftware.com/blog/etl-principles
- https://gtoonstra.github.io/etl-with-airflow/principles.html
- https://maximebeauchemin.medium.com/functional-data-engineering-a-modern-paradigm-for-batch-data-processing-2327ec32c42a
- https://tech.socarcorp.kr/data/2021/06/01/data-engineering-with-airflow.html
- https://medium.com/hashmapinc/etl-understanding-it-and-effectively-using-it-f827a5b3e54d
- https://medium.com/@rchang/a-beginners-guide-to-data-engineering-the-series-finale-2cc92ff14b0