dbt를 통한 데이터 웨어하우스 개발 후기

Joshua Kim
IOTRUST : Team Blog
24 min readJun 16, 2024

CONTENTS

  • 들어가는 글
  • OVERVIEW
  • dbt 기능 톺아보기
  • dbt로 어떤 것들을 써먹었는가
  • dbt 활용에 관한 팁
  • 나가는 글

들어가는 글

dbt 공식 웹사이트

dbt(Data Build Tool)는 데이터 Transformation 및 Modeling 작업을 자동화하고 관리하는 오픈 소스 프레임워크이며, 데이터 분석 및 데이터 엔지니어링 팀에서 널리 사용됩니다. dbt는 SQL과 Jinja 템플릿을 사용하여 데이터 Transformation 작업을 정의하여 이를 실행할 수 있도록 지원하고 있습니다.

저는 아이오트러스트 위핀 워크스페이스의 개발 과정에서 데이터 웨어하우스 구축 업무를 맡았으며, 이를 위해 dbt를 적극적으로 활용했습니다. 더 나아가, 향후 위핀 워크스페이스의 기능 추가에도 꾸준히 활용해볼 예정이며, 사내 데이터 분석 환경에도 dbt를 통해 많은 과제들을 풀어갈 계획을 가지고 있습니다.

이번 아티클에서는 dbt 프레임워크가 정확히 어떤 작업에 필요한지 파헤쳐본 후, dbt의 필요성, 그리고 제가 진행하며 얻은 노하우들을 정리해보도록 하겠습니다. 향후 dbt 활용을 계획 중이신 데이터 엔지니어, 애널리틱스 엔지니어 분들께 도움을 드릴 수 있길 바랍니다.

OVERVIEW

위핀 워크스페이스 중 “사용자 통계” 기능은 ETL 툴을 통해 개발되었습니다. 이 과정에서 저는 Transformation과 Orchestration에 중점을 두어 업무를 진행했어요.

위핀 워크스페이스 공식 웹사이트

DB부터 UI까지의 전체 파이프라인은 아래 그림과 같습니다.

필자 작성

(1) 운영 DB를 분산형 데이터 처리 서비스를 통해 물리적으로 분리된 또 다른 DB에 Near Real-time으로 스트리밍합니다.

(2) 스트리밍된 소스 테이블로부터 Core, Mart, Access Layer를 거치며 순차적으로 Transform을 진행합니다. 이 과정은 별도의 VM 인스턴스 내에 저장된 dbt 프로젝트에 정의된 방식으로 진행되며, Orchestration 툴에 의해 특정 주기마다 진행합니다.

  • Core Layer: 칼럼 이름, 칼럼 유형, Cardinality 변환, Null Handling 등 주로 Data Cleansing 등의 절차를 이 Layer에서 진행합니다. 또한 확장 가능성과 쿼리 최적화를 위해 Star Schema 혹은 Snowflake Schema로 설계했습니다.
  • Mart Layer: 비즈니스 로직에 필요한 지표들을 유형화하여 각 Mart에서 관리할 수 있도록 분리합니다. 테이블의 생성보다 테이블의 참조가 더욱 잦은 영역인 만큼, 쿼리 속도를 위해 반드시 Star Schema로 설계하는 것을 원칙으로 삼았습니다.
  • Access Layer: 백엔드가 응답 받기 용이한 구조를 만들기 위해 Mart Layer의 최종 테이블들을 한 번 더 가공한 영역입니다. 가령, date, interval, country 등의 칼럼이 sequential하게 빠짐 없이 존재할 수 있도록 함으로써, 백엔드와 프론트엔드 단에서 가공 절차를 사전에 최소화할 수 있도록 작업했습니다.

(3) 백엔드에서 GraphQL 프레임워크를 사용하여 Access Layer에 존재하는 각 테이블에 쿼리를 요청합니다.

  • 기존의 API 방식은 프론트엔드가 요청해야 할 수많은 케이스에 따라 개별적인 엔드포인트를 생성해야 합니다. 이는 프로덕트 고도화나 수정 발생시 수시로 엔드포인트를 생성하고 수정해야 하는 번거로움이 발생할 것입니다.
  • 그러나 GraphQL 방식을 사용하면 하나의 엔드포인트에서 다양한 케이스를 객체화하여 요청할 수 있어, 프로덕트 고도화 및 수정 발생시 시간을 단축시키고 더욱 유연하게 대응할 수 있을 것입니다.

(4) 최종 사용자가 UI 상에서 각기 다른 액션을 취할 때마다 프론트엔드에서 GraphQL에 요청하여 데이터를 불러온 후, 이를 차트 상에 표현합니다.

dbt 기능 톺아보기

전술했듯이, dbt는 ETL 프로세스 중 Transformation에 특화된 자동화 프레임워크입니다. Transformation 과정은 수많은 테이블마다 Dependency를 확인해야 하고, 개별적인 Transform 실행 절차를 꿰뚫고 있어야 한다는 관리의 부담감이 매우 큽니다.

그러나 dbt를 사용하면 이 작업들을 자동화함으로써 개발, 유지보수, 고도화, 그리고 인수인계 과정에서 발생하는 큰 부담을 상당한 수준으로 경감시킬 수 있습니다. 아래 그림은 dbt가 기여할 수 있는 대표적인 다섯 가지 Tasks가 뭔지 요약하고 있습니다.

Complete dbt(Data Build Tool) Bootcamp (Zero to Hero)

(1) Snapshot

많은 분들이 잘 아시듯, Fact Tables는 다음과 같은 유형으로 분류됩니다.

  • Transactional Fact Tables
  • Periodic Snapshot Fact Tables
  • Accumulating Snapshot Fact Tables
  • Factless Fact Tables

이중 Periodic Snapshot과 Accumulating Snapshot 유형의 Fact Tables를 dbt가 지원하는 것입니다. 가령, 일간, 주간, 월간마다 수익, 비용, 재고, 잔액 등 현황을 스냅샷을 찍듯 저장하여 관리하기 위한 테이블이 대표적인데요. dbt에서는 이러한 유형을 Snapshot 디렉토리에서 별도로 자동화할 수 있도록 지원합니다.

(2) Transform

수많은 Transform Tasks를 각 Dependency를 고려하여 안정적으로 실행할 수 있습니다. dbt는 테이블간 선후 관계나 의존 관계를 자동으로 분석한 후 마치 하나의 DAG 형태로 전체 Transform이 실행될 수 있도록 하는데요. 이러한 장점 덕분에, 애널리틱스 엔지니어나 데이터 분석가는 안정적으로 신규 Mart를 추가하거나, 특정 Mart에 대해서만 유지보수를 진행할 수 있는 것이죠.

(3) Test

dbt는 데이터 정합성 테스트의 자동화를 지원합니다. 개인적으로 dbt의 장점 중 가장 위대한 기능이라고 생각하고 있는데요. Built-in 테스트가 존재할 뿐만 아니라, 사용자가 직접 자유롭게 테스트를 정의할 수도 있기 때문에, 각 칼럼이나 테이블을 안정적으로 테스트하여 데이터 정합성 점검 시간을 극적으로 절약할 수 있습니다.

(4) Deploy

dbt 프로젝트는 Python 기반으로 만들어진 프레임워크이지만, 사용자는 SQL과 Jinja 템플릿을 중심으로 작성을 해야 합니다. dbt를 실행하면 각 모델에 작성된 내용을 연결된 DB에 해당하는 순수 SQL로 디코딩하여 DDL, DML 등의 명령을 실행합니다.

  • DDL: Create, Alter, Drop, Truncate 등
  • DML: Select, Insert, Update, Delete 등

(5) Document

각 테이블과 칼럼의 Description, Dependency, Test 목록 등을 자동으로 문서화해주는 기능을 지원하기도 합니다. 이는 애널리틱스 엔지니어 본인의 업무 F/up 뿐만 아니라 다른 동료들에게 인수인계할 때도 굉장히 많은 시간을 절약해줍니다.

dbt로 어떤 것들을 써먹었는가

1. Transform되어야 할 테이블을 일목요연하게 디렉토리로 관리했습니다.

테이블별 Dependency와 별도로, 아래의 기준을 통해 각 테이블 모델을 분류하는 것이 중요할 것입니다.

  • 어떤 Layer에서 가공되는 테이블인가?
  • Fact와 Dimension 중 어느 유형에 해당하는 테이블인가?
  • Mart Layer라면 어떤 Mart에 속해있는 테이블인가?

저는 이를 정확히 분류하여 관리하는 것이 유지보수와 확장 작업시 매우 중요하다는 생각을 했고, 아래와 같이 각 테이블을 최대한 상세하게 디렉토리로 분류했습니다. 이를 통해 시행착오가 생겨 특정 모델들에 한해서만 수정이 필요할 때 작업 속도가 크게 향상했습니다.

필자 작성

2. 각 테이블과 칼럼의 데이터 정합성 테스트를 자동화했습니다.

dbt에서 크게 세 가지 유형의 데이터 정합성 테스트를 실행할 수 있습니다.

  • Built-in Generic Test
  • Custom Singular Test
  • Custom Generic Test

(1) Built-in Generic Test

  • unique: 테이블 내에서 Unique한 식별자로 관리해야 하는 칼럼
  • not_null: 테이블 내에서 Not Null하게 관리해야 하는 칼럼
  • accepted_values: 특정 값만 존재해야 하는 Cardinal 유형의 칼럼
  • relationships: 다른 테이블의 칼럼 값에 의존해야 하는 칼럼 (Foriegn Key 등)

(2) Custom Singular Test

  • 아래와 같이, 특정 테이블이나 칼럼을 대상으로 사용자가 직접 Test를 만들 수 있습니다. dbt가 Test 실행 시 1개라도 Row가 출력되면 Test를 통과하지 못하는 것으로 간주합니다.
SELECT
*
FROM
{{ ref('fct_balances') }}
WHERE
balance < 0
LIMIT
1

(3) Custom Generic Test

  • 다양한 테이블과 칼럼에 적용될 수 있도록, 테이블이나 칼럼을 동적 파라미터로 두어 작성한 Test를 의미합니다. 가령 아래와 같은 사례가 있습니다.
{% test is_even(model, column_name) %}

WITH
validation AS (
SELECT {{ column_name }} as even_field
FROM {{ model }}
),
validation_errors AS (
SELECT even_field
FROM validation
-- if this is true, then even_field is actually odd!
WHERE (even_field % 2) = 1
)

SELECT *
FROM validation_errors

{% endtest %}

정리하면, 가령 각 테이블 모델의 yaml 파일에 다음과 같이 Test 목록을 작성할 수 있습니다.

version: 2

models:

- name: dim_users
description: Users Info

columns:
- name: user_id
description: Primary key
tests:
- unique
- not_null
- name: country_id
description: The country id
tests:
- not_null
- relationships:
to: ref('dim_countries')
field: country_id
- name: cookie_areement
description: Cookie Agreement
tests:
- accepted_values:
values: [0, 1]
- name: purchased_cnt
description: Number of Purchase so far
tests:
- positive_value

3. Docs 자동 생성을 위한 Meta Data를 작성했습니다.

Docs를 잘 생성하기 위해서는 각 yaml 파일을 잘 작성해야 합니다. 이 Docs를 잘 생성한다면, 특정 테이블들을 대상으로 한 유지 보수가 필요한 업무가 생겼을 때, Dependency가 존재하는 다른 테이블들을 염두에 두어 안정적인 코드 개선 및 실행 작업을 진행할 수 있습니다. (이 기능이 길을 잃지 않도록 방지해줘서 정말 편합니다.)

  • node_color: 각 테이블 모델이 Docs 상에서 나타날 색상을 Color Hex Code로 정의할 수 있습니다. 이를 통해 성격이 유사한 모델들이 무었인지 시각적으로 한 눈에 파악할 수 있습니다.
  • table description: 각 테이블이 어떤 정보를 담고 있는지 정의를 작성할 수 있습니다.
  • table meta: 가령 owner 정보를 작성하여, 이 테이블을 정의한 동료가 누구인지 파악할 수 있습니다. 특히, 사내의 각 데이터 분석가가 자신만의 Data Mart를 생성할 경우, 오너십을 확인해야 할 때 유용합니다.
  • column description: 각 칼럼이 어떤 정보를 담고 있는지 정의를 작성할 수 있습니다.
  • column tests: 각 칼럼이 어떤 Test를 통과해야 하는지 작성할 수 있습니다. 실제 Test 실행을 위한 작성이지만, 동시에 Docs에서도 이를 일목요연하게 확인할 수 있습니다.

4. 각 테이블마다 Tags를 정의하여 객체화했습니다.

아래 사례와 같이, 각 모델의 yaml 파일에 tags 리스트를 적어주었습니다.

config:
tags: ["core", "fact", "incremental"]
materialized: incremental
incremental_strategy: append

즉, 각 모델마다 Layer 및 Mart 이름, 테이블 유형이나 토픽 등의 정보를 tag로 입력하면, 두 가지 측면에서 도움을 줍니다.

  • dbt Docs 생성시 특정 tag에 해당하는 모델들만 추려내어 시각화할 수 있습니다.
  • 특정 tag에 해당하는 모델들에 대해서만 Transform과 Test를 실행할 수 있으므로, 유지보수나 고도화 과정에서 안정적인 과정을 진행할 수 있도록 도와줍니다.

5. 테이블 별로 Table Materialization 전략을 설정하고, 특히 Incremental 전략을 진행할 때는 Jinja 템플릿을 통해 조건을 입력해주었습니다.

Complete dbt(Data Build Tool) Bootcamp (Zero to Hero)

위 그림과 같이, dbt는 크게 총 4개의 Table Materialization 방식을 지원합니다.

(1) View

  config:
materialized: view
  • 말 그대로 Table View를 생성하는 방식입니다. 이 방식을 적용한 테이블의 경우, 실제 테이블 생성 작업이 발생하지는 않고, DB의 View 내에 쿼리문만 저장됩니다.

(2) Table

config:
materialized: table
  • Transform이 실행될 때마다 테이블 전체를 다시 새로 생성하는 방식입니다. 즉, 기존에 테이블 자체가 없을 경우 당연히 전체 테이블을 생성하고, 기존에 테이블이 존재할 경우에도 테이블을 한 번 삭제한 후 다시 전체 테이블을 생성하게 됩니다. 이는 쿼리 속도에 악영향을 끼치기 때문에, Fact보다는 Dimension 유형의 테이블에 주로 사용됩니다.

(3) Incremental

  config:
materialized: incremental
incremental_strategy: append
  config:
materialized: incremental
incremental_strategy: merge
unique_key: [user_id]
merge_update_columns: [country]
  • 기존에 테이블이 존재할 경우 이를 삭제하지 않고, 행을 증분적으로 추가해가는 방식입니다. Fact 유형의 모델에 흔히 사용됩니다.
  • 크게 append 유형과 merge 유형으로 옵션을 둘 수 있습니다.
  • append: Transform을 실행했을 때, 증분해야 하는 행들을 기존 테이블에 단순히 추가해주는 방식입니다.
  • merge: Transform을 실행했을 때, 증분해야 하는 행들을 기존 테이블과 비교하여 특정 칼럼의 값만 업데이트하는 방식입니다.
  • merge는 중복 행 생성을 사전에 차단하여 데이터 정합성을 안정적으로 유지할 수 있다는 장점이 있지만, 기존 테이블의 전체 행들과 일일이 비교하는 로직이 추가되어 있어 속도가 느려진다는 단점이 있습니다.
  • 개인적으로 SCD Type 1 유형의 테이블의 경우에만 merge 방식을 택하고, 나머지는 가능한 한 append 옵션을 적용하고자 노력하고 있습니다. 특정 칼럼 값을 최신화해주는 것이 중요한 경우에만 사용하고, 나머지는 쿼리 속도를 위해 사용을 자제하는 것이죠. (SCD 유형에 대한 학습이 필요하시다면 이 아티클을 추천합니다.)

(4) Ephemeral

  • View 방식과 달리, Ephemeral은 쿼리문 자체를 DB에 저장하는 것조차 하지 않습니다. 즉, dbt 프로젝트 내에서만 임시로 활용할 뿐, DB에서는 어디에서도 확인할 수 없는 것입니다.
  • Staging Layer의 테이블일 경우 이 방식을 사용할 수 있습니다만, 개인적으로 전체 테이블을 Full Scan하는 것을 피하려고 애쓰다보니 이 유형을 실제로 적용할 만한 동기가 없었습니다.

6. DB 클라이언트 상에서 편하게 조회할 수 있도록, 각 테이블의 스키마를 분류하여 작성했습니다.

아래는 제가 직접 작성한 dbt_project.yaml 파일의 일부입니다.

models:
wepin_workspace_dbt:

01_core:
dim:
+schema: 01_core.dim
fct:
+schema: 01_core.fct

02_mart:
01_users_cnt:
00_users_events_daily:
+schema: 02_mart.01_users_cnt.00_users_events_daily

dbt는 각 테이블이 생성될 스키마를 직접 정의해줄 수 있는데요. 아무리 dbt 프로젝트 내에서 각 테이블을 디렉토리로 잘 구분한다고 하더라도, 스키마를 정의하지 않으면 DB 클라이언트 상에서는 테이블이 뒤죽박죽 섞여진 채 나타나게 됩니다. 그럼, 테이블을 참고하는 수많은 개발자 분들이 큰 혼동을 느끼겠죠.

따라서 각 테이블의 스키마를 정의하여, dbt 프로젝트를 확인할 필요는 없지만 DB 클라이언트를 통해 테이블 명세를 확인해야 하는 다른 개발자 분들의 가독성을 향상 시키고자 노력했습니다.

dbt 활용에 관한 팁

다음은 제가 dbt 프로젝트를 개발하며 느꼈던 것들을 정리해본 내용입니다. “사전에 미리 알았더라면 시행 착오를 줄일 수 있었을텐데" 하고 느낀 것들을 중심으로 말씀 드리겠습니다.

1. git pull, dbt debug, dbt deps, dbt run, dbt docs generate, dbt test, slack alert 순으로 DAG를 만들었습니다.

필자 작성

(1) git pull: dbt 프로젝트 Repo를 최신으로 업데이트합니다.

(2) dbt debug: dbt의 profiles.yamldbt_project.yaml이 기본적으로 잘 작성되었는지 확인할 수 있고, 특히 DB Connection이 정상 작동하는지 확인할 수 있습니다.

(3) dbt deps: dbt 실행에 필요한 최신 Packages를 모두 불러옵니다.

(4) dbt run: 본격적으로 Transform을 실행합니다.

(5) dbt docs generate: 현재 dbt 프로젝트의 최신 상태에 맞게 Docs를 재생성합니다.

(6) dbt test: 사용자가 정의한 모든 Test를 실행하며 데이터 정합성 여부를 확인합니다.

(7) slack alert: 각 단계마다 미리 변수화해둔 Result 내용을 슬랙 알람으로 보내줍니다.

2. profiles.yaml 파일을 프로젝트 별로 폐쇄화하여 관리했습니다.

dbt를 처음 인스톨하면 기본적으로 사용자 root 디렉토리에 profiles.yaml 파일이 생성되는데요. 저는 profiles.yaml 파일이 이렇게 별도로 존재하면 아래 두 가지 측면에서 단점이 존재한다고 생각했습니다.

  • 한 기업 내에 다양한 dbt 프로젝트가 생성될 것인데, 이를 모조리 하나의 profiles.yaml 파일에 관리하는 것은 안정성 관리의 취약점이 발생할 수 있을 것입니다. (특히, DB Connection 정보를 모두 하나의 파일에 의존하기 때문)
  • 하나의 DB Connection 정보만 포함된 profiles 정보를 다른 구성원 간에 전달을 해야 하는 경우에도 폐쇄적으로 분리 관리하는 것이 차라리 좋다고 생각합니다.

그러나 dbt debug 등을 실행하면 기본적으로 dbt는 사용자 root 디렉토리에서 profiles.yaml 파일을 탐색하는데요. 이는 다음과 같이 환경변수를 변경함으로써 경로를 수정할 수 있습니다. 자세한 문서는 여기에 있습니다.

$ export DBT_PROFILES_DIR=path/to/directory

3. Incremental Strategy를 Jinja 템플릿으로 정의하는 작업이 가장 높은 진입 장벽이며, 익숙해질 때까지 시간이 오래 걸립니다.

저는 다음과 같은 Jinja 템플릿을 매우 빈번하게 작성해주었습니다. 즉, 매 배치 실행시마다 Full Scan을 통해 테이블 전체를 생성하는 게 아니라, Incremental하게 불러와야 할 케이스의 조건을 작성하는 것인데요.

WHERE
...
-- Incremental Strategy: 본 테이블의 datetime 보다 큰 createdTime만 추출한다.
{% if is_incremental() %}
AND (SELECT MAX(datetime) FROM {{ this }}) < "createdTime"
{% endif %}

이 부분을 작성하는 것이 생각보다 훨씬 어렵고, 착오가 빈번하게 발생하는 곳입니다. 개인적으로 두 가지를 알고 계시면 시행착오를 줄이실 수 있을 것 같다고 생각합니다.

(1) 하루에 dbt run을 1번 실행하든, 1,000번 실행하든 중복 행이 생성되지 않도록 설계하면 된다.

(2) 특히, Core Layer라면 date 칼럼과 datetime 칼럼 두 가지를 모두 가지고 있는 것이 유리할 때가 많다.

  • date 칼럼만 존재할 경우: Incremental 전략 실행시, 어떤 행들을 추가로 불러와야 할지 기준이 존재하지 않아 어려움이 발생합니다.
  • datetime 칼럼만 존재할 경우: 추후 테이블 Partition과 Index 생성시 어려움이 발생하여 규모가 커질수록 이에 대응하기 어려워집니다.

4. Source Tables를 일목요연하게 네이밍하여 관리했습니다.

보통 운영 DB를 설계한 동료의 네이밍 관점과 애널리틱스 엔지니어 입장에서의 네이밍 관점이 많이 다릅니다. 따라서 데이터 웨어하우스를 설계하는 과정에서 운영 DB의 네이밍에 대해 큰 혼란을 느끼기 쉬운데요. 저는 dbt 내에서 소스 테이블의 가상 스키마와 가상 이름을 sources.yaml 파일에서 정의한 후 작업을 진행했습니다. 이렇게 정의하고 난 후, Core Layer를 설계할 때 혼란감을 크게 줄일 수 있었습니다.

sources:

- name: users
description: "사용자 관련 테이블"
database: ...
schema: ...
meta:
owner: Joshua
tables:
- name: fct_events
identifier: ...
description: 이벤트 테이블
tags: ["source", "users", "fact"]
- name: dim_users
identifier: ...
description: 사용자 Info 테이블
tags: ["source", "users", "dim"]
- name: dim_ips_to_countries
identifier: ...
description: IP 주소별 국가 Dimension 테이블
tags: ["source", "users", "dim"]

그러면 아래와 같이 소스 테이블을 참조할 때 다음과 같이 작성하면 됩니다.

SELECT
...
FROM
{{ source('users', 'dim_users') }}
...

5. dbt가 Docs를 생성해주긴 하지만, 처음 데이터 웨어하우스 구조를 계획할 때는 직접 Table Dependency를 스케치하여 시작하는 것이 좋습니다.

아래와 같이 dbt docs generate로 사전 디코딩이 완료된 후, dbt docs serve를 통해 로컬 호스팅을 해주면 Tables Dependency를 확인하기 용이합니다. 그러나 dbt Docs는 프로젝트 내 모델들에 대해 어느 정도 완성된 상태에서 확인이 가능하므로, 처음 설계할 때는 제대로 활용하기 어렵습니다.

dbt Documentation

저는 이러한 단점을 극복하고, 설계의 시행착오를 최소화하고자 별도로 Table Dependency를 스케치했습니다. 아래 사례와 같이, draw.io를 활용하여 스케치를 진행했고, 설계가 완성된 후 자연스럽게 dbt Docs에 의존하게 되었습니다.

필자 작성

6. Core Layer를 확실하게 잘 완성한 후에 Mart Layer로 들어가는 것이 좋았습니다.

Core Layer의 각 모델 정의와 Materialization 설계까지 모두 확실하게 끝내지 않은 상태에서 급하게 Mart Layer 설계로 들어가게 되면, 시행착오 등으로 인해 엄청나게 큰 혼동이 생길 수 있습니다. 따라서 저는 Core Layer의 완벽한 설계 후에 Mart Layer 작업으로 넘어갔습니다.

물론, 이 과정에서 작업 속도가 더뎌진다는 무언의 압박에 빠질 수는 있지만, Core Layer에서 가장 많은 시간을 할애해야만 추후 Mart Layer 설계 속도가 훨씬 빨라질 수 있다는 사실을 망각하면 안 될 것입니다.

또한, Core Layer에서 모호한 정의를 하고 나면, Mart Layer 작업시 다시 Core Layer에서 조건을 명확화하는 Bottom-up 작업 시도가 많아지게 될 것인데, 이 때 Core Layer 자체의 일관적인 조건 설정을 오히려 더럽히는 효과가 발생하기 쉬워 전반적으로 굉장히 큰 혼란감에 빠지기 쉽습니다.

따라서 데이터 웨어하우스의 확장성과 유연성 등을 사전에 모두 고려하여, Core Layer 자체에 가장 많은 시간을 할애하여 완벽한 모습을 갖춘 후에 Mart Layer로 넘어가는 것이 작업 속도 측면과 추후 확장 가능성 측면에서 모두 바람직한 작업 방향이 될 것입니다.

7. dbt test에서는 중복 행 생성 여부가 가장 중요했습니다.

Materialization 전략 중 Incremental 방식을 적용하는 것이 가장 까다롭고, 주로 이 방식에 대한 착오로 인해 데이터 정합성에 오류가 생기는 경우가 많았습니다. 즉, 한 번 Insert가 완료된 행과 동일한 행을 중복하여 재차 Insert하는 과정이 생기기 때문인데요.

저는 이 문제를 사전에 잘 탐지하기 위해 아래와 같은 쿼리문을 작성하여 Custom Generic Test에 포함시켰습니다. 즉, 중복 생성 행을 출력하는 쿼리문입니다.

WITH CTE AS (
SELECT
a, b, c,
ROW_NUMBER() OVER (PARTITION BY a, b, c) AS row_num
FROM
table
)
SELECT * FROM CTE WHERE row_num > 1

나가는 글

지금까지 위핀 워크스페이스 기능을 개발하기 위한 데이터 웨어하우스 설계부터, dbt 기능과 활용 후기에 대해 정리해봤습니다.

개인적으로 데이터 웨어하우스에 관한 이론을 습득하고 dbt 활용법을 익히기 위한 여러 가지 Udemy 강의를 수강했고, 러닝커브의 기울기를 높이고 진입장벽을 극복하기 위해 여러모로 재미있게 배우고 적용했습니다.

사실 데이터 웨어하우스를 구축하기 위해 비즈니스 로직과 인프라에 따라 dbt가 빛을 낼 수도 있고, 혹은 다른 툴이 빛을 낼 수도 있을 것입니다. 그러나 다른 포지션에 비해 데이터 엔지니어링과 애널리틱스 엔지니어링 역할을 맡은 사람이 저 1명에 불과하므로 Docs를 통한 작업 문서화와 Test를 통한 빠른 데이터 정합성 점검이 워낙 중요했습니다. 따라서 dbt를 적극적으로 도입하기로 결정했습니다.

이 과정에서 Google Analytics, Amplitude, Mixpanel 등과 같은 데이터 분석 플랫폼들은 본인들의 데이터 웨어하우스를 어떤 구조로 구축했는지 매우 큰 궁금점이 자라기 시작했습니다. 그리고 그 이면에 “데이터 엔지니어와 애널리틱스 엔지니어들의 엄청난 고충이 있겠구나”하는 동질감이 느껴지기도 합니다.

하나의 프레임워크를 이렇게 깊이 파고나니, 데이터 엔지니어링에 필요한 여러 가지 툴들이 어떤 역할을 맡고 있는지 예전보다 훨씬 감이 많이 잡히게 되었습니다. 하나하나 잘 익히고 적용하여, 스스로 더욱 성장하는 것은 물론, 회사에도 많은 일들을 수행하여 더 좋은 제품을 만드는 데 기여하고 싶어집니다. 읽어주셔서 감사합니다.

--

--