์ด์์ ์ธ ๋ฐ์ดํฐ ์จ์ดํ์ฐ์ค๋ ์ด๋ค ์์น์ ๋ฐ๋ผ ๊ตฌ์ถ๋๋์?
์์ฉ๊ตฌ ์ฝ๋๊ฐ ์๋ ๊ฒฝ์ฐ ๋น์ฆ๋์ค ๊ฐ์น์ ๋ถ์์ ์ง์คํ์ธ์. DWH๋ฅผ ์ฝ๋๋ฒ ์ด์ค๋ก ๊ด๋ฆฌ: ๋ฒ์ ๊ด๋ฆฌ, ๊ฒํ , ์๋ํ๋ ํ ์คํธ ๋ฐ CI. ๋ชจ๋์, ํ์ฅ ๊ฐ๋ฅํ ์คํ ์์ค ๋ฐ ์ปค๋ฎค๋ํฐ์ ๋๋ค. ์ฌ์ฉ์ ์นํ์ ์ธ ๋ฌธ์ํ ๋ฐ ์ข ์์ฑ ์๊ฐํ(Data Lineage).
์ด์ ๋ํ ์์ธํ ๋ด์ฉ๊ณผ ๋น
๋ฐ์ดํฐ ๋ฐ ๋ถ์ ์ํ๊ณ์์ DBT์ ์ญํ ์ ๋ํด ์์ธํ ์์๋ณด์ธ์. cat์ ์ค์ ๊ฒ์ ํ์ํฉ๋๋ค.
์๋ ํ์ธ์.
Artemy Kozyr๋์ด ์ฐ๋ฝ๋๋ฆฝ๋๋ค. ์ ๋ 5๋
๋๊ฒ ๋ฐ์ดํฐ ์จ์ดํ์ฐ์ค ์์
, ETL/ELT ๊ตฌ์ถ, ๋ฐ์ดํฐ ๋ถ์ ๋ฐ ์๊ฐํ ์์
์ ํด์์ต๋๋ค. ๋๋ ํ์ฌ ์ผํ๊ณ ์์ต๋๋ค
๊ฐ์
DBT ํ๋ ์์ํฌ๋ ELT(Extract - Transform - Load) ์ฝ์ด์ T์ ๊ดํ ๊ฒ์ ๋๋ค.
BigQuery, Redshift, Snowflake์ ๊ฐ์ ์์ฐ์ ์ด๊ณ ํ์ฅ ๊ฐ๋ฅํ ๋ถ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ถํ์ผ๋ก ์ธํด ๋ฐ์ดํฐ ์จ์ดํ์ฐ์ค ์ธ๋ถ์์ ๋ณํ์ ์ํํ ํ์๊ฐ ์๊ฒ ๋์์ต๋๋ค.
DBT๋ ์์ค์์ ๋ฐ์ดํฐ๋ฅผ ๋ค์ด๋ก๋ํ์ง ์์ง๋ง ์ ์ฅ์(๋ด๋ถ ๋๋ ์ธ๋ถ ์ ์ฅ์)์ ์ด๋ฏธ ๋ก๋๋ ๋ฐ์ดํฐ๋ก ์์ ํ ์ ์๋ ์ข์ ๊ธฐํ๋ฅผ ์ ๊ณตํฉ๋๋ค.
DBT์ ์ฃผ์ ๋ชฉ์ ์ ์ฝ๋๋ฅผ ๊ฐ์ ธ์ SQL๋ก ์ปดํ์ผํ๊ณ ๋ฆฌํฌ์งํ ๋ฆฌ์์ ์ฌ๋ฐ๋ฅธ ์์๋ก ๋ช
๋ น์ ์คํํ๋ ๊ฒ์
๋๋ค.
DBT ํ๋ก์ ํธ ๊ตฌ์กฐ
ํ๋ก์ ํธ๋ ๋ค์ ๋ ๊ฐ์ง ์ ํ์ ๋๋ ํฐ๋ฆฌ์ ํ์ผ๋ก๋ง ๊ตฌ์ฑ๋ฉ๋๋ค.
- ๋ชจ๋ธ(.sql) - SELECT ์ฟผ๋ฆฌ๋ก ํํ๋๋ ๋ณํ ๋จ์
- ๊ตฌ์ฑ ํ์ผ(.yml) - ๋งค๊ฐ๋ณ์, ์ค์ , ํ ์คํธ, ๋ฌธ์
๊ธฐ๋ณธ์ ์ผ๋ก ์์ ์ ๋ค์๊ณผ ๊ฐ์ด ๊ตฌ์ฑ๋ฉ๋๋ค.
- ์ฌ์ฉ์๋ ํธ๋ฆฌํ IDE์์ ๋ชจ๋ธ ์ฝ๋๋ฅผ ์ค๋นํฉ๋๋ค.
- CLI๋ฅผ ์ฌ์ฉํ์ฌ ๋ชจ๋ธ์ด ์์๋๊ณ DBT๊ฐ ๋ชจ๋ธ ์ฝ๋๋ฅผ SQL๋ก ์ปดํ์ผํฉ๋๋ค.
- ์ปดํ์ผ๋ SQL ์ฝ๋๋ ์ง์ ๋ ์์(๊ทธ๋ํ)๋ก Storage์์ ์คํ๋ฉ๋๋ค.
CLI์์ ์คํํ๋ ๋ชจ์ต์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
๋ชจ๋ ๊ฒ์ด SELECT์ ๋๋ค
์ด๋ Data Build Tool ํ๋ ์์ํฌ์ ํต์ฌ ๊ธฐ๋ฅ์ ๋๋ค. ์ฆ, DBT๋ ์ฟผ๋ฆฌ๋ฅผ ์ ์ฅ์๋ก ๊ตฌ์ฒดํํ๋ ๊ฒ๊ณผ ๊ด๋ จ๋ ๋ชจ๋ ์ฝ๋(CREATE, INSERT, UPDATE, DELETE ALTER, GRANT, ... ๋ช ๋ น์ ๋ณํ)๋ฅผ ์ถ์ํํฉ๋๋ค.
๋ชจ๋ ๋ชจ๋ธ์๋ ๊ฒฐ๊ณผ ๋ฐ์ดํฐ ์ธํธ๋ฅผ ์ ์ํ๋ ํ๋์ SELECT ์ฟผ๋ฆฌ ์์ฑ์ด ํฌํจ๋ฉ๋๋ค.
์ด ๊ฒฝ์ฐ ๋ณํ ๋ ผ๋ฆฌ๋ ๋ค์ค ๋ ๋ฒจ์ผ ์ ์์ผ๋ฉฐ ์ฌ๋ฌ ๋ค๋ฅธ ๋ชจ๋ธ์ ๋ฐ์ดํฐ๋ฅผ ํตํฉํ ์ ์์ต๋๋ค. ์ฃผ๋ฌธ ์ผ์ผ์ด์ค(f_orders)๋ฅผ ๊ตฌ์ถํ ๋ชจ๋ธ์ ์:
{% set payment_methods = ['credit_card', 'coupon', 'bank_transfer', 'gift_card'] %}
with orders as (
select * from {{ ref('stg_orders') }}
),
order_payments as (
select * from {{ ref('order_payments') }}
),
final as (
select
orders.order_id,
orders.customer_id,
orders.order_date,
orders.status,
{% for payment_method in payment_methods -%}
order_payments.{{payment_method}}_amount,
{% endfor -%}
order_payments.total_amount as amount
from orders
left join order_payments using (order_id)
)
select * from final
์ฌ๊ธฐ์ ์ด๋ค ํฅ๋ฏธ๋ก์ด ์ ์ ๋ณผ ์ ์๋์?
์ฒซ ๋ฒ์งธ: CTE(Common Table Expressions) ์ฌ์ฉ - ๋ง์ ๋ณํ๊ณผ ๋น์ฆ๋์ค ๋ ผ๋ฆฌ๊ฐ ํฌํจ๋ ์ฝ๋๋ฅผ ๊ตฌ์ฑํ๊ณ ์ดํดํฉ๋๋ค.
๋์งธ: ๋ชจ๋ธ ์ฝ๋๋ SQL๊ณผ ์ธ์ด์ ํผํฉ์
๋๋ค.
์์ ์์๋ ๋ฃจํ๋ฅผ ์ฌ์ฉํฉ๋๋ค. for ํํ์์ ์ง์ ๋ ๊ฐ ๊ฒฐ์ ๋ฐฉ๋ฒ์ ๋ํ ๊ธ์ก์ ์์ฑํฉ๋๋ค. ์ธํธ. ๊ธฐ๋ฅ๋ ์ฌ์ฉ๋ฉ๋๋ค ์ฌํ โ ์ฝ๋ ๋ด์์ ๋ค๋ฅธ ๋ชจ๋ธ์ ์ฐธ์กฐํ๋ ๊ธฐ๋ฅ:
- ์ปดํ์ผ ์ค ์ฌํ Storage์ ํ ์ด๋ธ์ด๋ ๋ทฐ์ ๋ํ ๋์ ํฌ์ธํฐ๋ก ๋ณํ๋ฉ๋๋ค.
- ์ฌํ ๋ชจ๋ธ ์ข ์์ฑ ๊ทธ๋ํ๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
๊ทธ๊ฒ์
- If / else ๋ฌธ - ๋ถ๊ธฐ ๋ฌธ
- For ๋ฃจํ - ์ฌ์ดํด
- ๋ณ์
- ๋งคํฌ๋ก - ๋งคํฌ๋ก ์์ฑ
๊ตฌ์ฒดํ: ํ ์ด๋ธ, ๋ทฐ, ์ฆ๋ถ
๊ตฌ์ฒดํ ์ ๋ต์ ๊ฒฐ๊ณผ ๋ชจ๋ธ ๋ฐ์ดํฐ ์ธํธ๊ฐ ์ ์ฅ์์ ์ ์ฅ๋๋ ์ ๊ทผ ๋ฐฉ์์ ๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ํ ์ด๋ธ - ์ ์ฅ์์ ๋ฌผ๋ฆฌ์ ํ ์ด๋ธ
- ๋ณด๊ธฐ - ์ ์ฅ์์ ๋ณด๊ธฐ, ๊ฐ์ ํ ์ด๋ธ
๋ ๋ณต์กํ ๊ตฌ์ฒดํ ์ ๋ต๋ ์์ต๋๋ค.
- ์ฆ๋ถ - ์ฆ๋ถ ๋ก๋(๋ํ ํฉํธ ํ ์ด๋ธ) ์ ์ค์ด ์ถ๊ฐ๋๊ณ , ๋ณ๊ฒฝ๋ ์ค์ด ์ ๋ฐ์ดํธ๋๊ณ , ์ญ์ ๋ ์ค์ด ์ง์์ง๋๋ค.
- ์์ - ๋ชจ๋ธ์ด ์ง์ ์ ์ผ๋ก ๊ตฌ์ฒดํ๋์ง๋ ์์ง๋ง ๋ค๋ฅธ ๋ชจ๋ธ์ CTE๋ก ์ฐธ์ฌํฉ๋๋ค.
- ์ง์ ์ถ๊ฐํ ์ ์๋ ๋ค๋ฅธ ์ ๋ต
๊ตฌ์ฒดํ ์ ๋ต ์ธ์๋ ํน์ ์ ์ฅ์์ ๋ํ ์ต์ ํ ๊ธฐํ๊ฐ ์์ต๋๋ค. ์๋ฅผ ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ๋์ก์ด: ์์ ํ ์ด๋ธ, ๋ณํฉ ๋์, ํ ์ด๋ธ ํด๋ฌ์คํฐ๋ง, ๊ถํ ๋ณต์ฌ, ๋ณด์ ๋ทฐ
- ์ ์ ํธ์ด: Distkey, Sortkey(์ธํฐ๋ฆฌ๋ธ, ๋ณตํฉ), ํ๊ธฐ ๋ฐ์ธ๋ฉ ๋ทฐ
- BigQuery: ํ ์ด๋ธ ํํฐ์ ๋ ๋ฐ ํด๋ฌ์คํฐ๋ง, ๋ณํฉ ๋์, KMS ์ํธํ, ๋ ์ด๋ธ ๋ฐ ํ๊ทธ
- ๋ถ๊ฝ: ํ์ผ ํ์(parquet, csv, json, orc, delta), partition_by, Clustered_by, buckets, Incremental_strategy
ํ์ฌ ์ง์๋๋ ์ ์ฅ์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ํฌ์คํธ๊ทธ๋ ์ค
- ์ ์ ํธ์ด
- BigQuery
- ๋์ก์ด
- ํ๋ ์คํ (์ผ๋ถ)
- ์คํํฌ(์ผ๋ถ)
- Microsoft SQL Server(์ปค๋ฎค๋ํฐ ์ด๋ํฐ)
๋ชจ๋ธ์ ๊ฐ์ ํด ๋ณด๊ฒ ์ต๋๋ค.
- ์ฑ์ฐ๊ธฐ๋ฅผ ์ฆ๋ถ์ ์ผ๋ก ๋ง๋ค์ด ๋ณด๊ฒ ์ต๋๋ค. (์ฆ๋ถ)
- Redshift์ ๋ํ ๋ถํ ๋ฐ ์ ๋ ฌ ํค๋ฅผ ์ถ๊ฐํด ๋ณด๊ฒ ์ต๋๋ค.
-- ะะพะฝัะธะณััะฐัะธั ะผะพะดะตะปะธ:
-- ะะฝะบัะตะผะตะฝัะฐะปัะฝะพะต ะฝะฐะฟะพะปะฝะตะฝะธะต, ัะฝะธะบะฐะปัะฝัะน ะบะปัั ะดะปั ะพะฑะฝะพะฒะปะตะฝะธั ะทะฐะฟะธัะตะน (unique_key)
-- ะะปัั ัะตะณะผะตะฝัะฐัะธะธ (dist), ะบะปัั ัะพััะธัะพะฒะบะธ (sort)
{{
config(
materialized='incremental',
unique_key='order_id',
dist="customer_id",
sort="order_date"
)
}}
{% set payment_methods = ['credit_card', 'coupon', 'bank_transfer', 'gift_card'] %}
with orders as (
select * from {{ ref('stg_orders') }}
where 1=1
{% if is_incremental() -%}
-- ะญัะพั ัะธะปััั ะฑัะดะตั ะฟัะธะผะตะฝะตะฝ ัะพะปัะบะพ ะดะปั ะธะฝะบัะตะผะตะฝัะฐะปัะฝะพะณะพ ะทะฐะฟััะบะฐ
and order_date >= (select max(order_date) from {{ this }})
{%- endif %}
),
order_payments as (
select * from {{ ref('order_payments') }}
),
final as (
select
orders.order_id,
orders.customer_id,
orders.order_date,
orders.status,
{% for payment_method in payment_methods -%}
order_payments.{{payment_method}}_amount,
{% endfor -%}
order_payments.total_amount as amount
from orders
left join order_payments using (order_id)
)
select * from final
๋ชจ๋ธ ์ข ์์ฑ ๊ทธ๋ํ
๋ํ ์ข ์์ฑ ํธ๋ฆฌ์ด๊ธฐ๋ ํฉ๋๋ค. DAG(๋ฐฉํฅ์ฑ ๋น์ํ ๊ทธ๋ํ)๋ผ๊ณ ๋ ํฉ๋๋ค.
DBT๋ ๋ชจ๋ ํ๋ก์ ํธ ๋ชจ๋ธ์ ๊ตฌ์ฑ์ ๊ธฐ๋ฐ์ผ๋ก ๊ทธ๋ํ๋ฅผ ์์ฑํ๊ฑฐ๋ ์คํ๋ ค ๋ชจ๋ธ ๋ด์ ref() ๋งํฌ๋ฅผ ๋ค๋ฅธ ๋ชจ๋ธ์ ์ฐ๊ฒฐํฉ๋๋ค. ๊ทธ๋ํ๋ฅผ ์ฌ์ฉํ๋ฉด ๋ค์ ์์ ์ ์ํํ ์ ์์ต๋๋ค.
- ์ฌ๋ฐ๋ฅธ ์์๋ก ๋ชจ๋ธ ์คํ
- ์ ํฌ ํ์ฑ์ ๋ณ๋ ฌํ
- ์์์ ํ์ ๊ทธ๋ํ ์คํ
๊ทธ๋ํ ์๊ฐํ์ ์:
๊ทธ๋ํ์ ๊ฐ ๋
ธ๋๋ ๋ชจ๋ธ์ด๋ฉฐ, ๊ทธ๋ํ์ ๊ฐ์ฅ์๋ฆฌ๋ ref ํํ์์ผ๋ก ์ง์ ๋ฉ๋๋ค.
๋ฐ์ดํฐ ํ์ง ๋ฐ ๋ฌธ์ํ
๋ชจ๋ธ ์์ฒด๋ฅผ ์์ฑํ๋ ๊ฒ ์ธ์๋ DBT๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฒฐ๊ณผ ๋ฐ์ดํฐ ์ธํธ์ ๋ํด ๋ค์๊ณผ ๊ฐ์ ๋ค์ํ ๊ฐ์ ์ ํ ์คํธํ ์ ์์ต๋๋ค.
- Null ์๋
- ์ ์ผํ
- ์ฐธ์กฐ ๋ฌด๊ฒฐ์ฑ - ์ฐธ์กฐ ๋ฌด๊ฒฐ์ฑ(์: ์ฃผ๋ฌธ ํ ์ด๋ธ์ customer_id๋ ๊ณ ๊ฐ ํ ์ด๋ธ์ id์ ํด๋นํจ)
- ํ์ฉ๋๋ ๊ฐ ๋ชฉ๋ก ์ผ์น
์๋ฅผ ๋ค์ด ํ๋ฃจ, ์ผ์ฃผ์ผ, ํ ๋ฌ ์ ์งํ๋ฅผ ์ฌ์ฉํ ์์ต % ํธ์ฐจ์ ๊ฐ์ ์์ฒด ํ ์คํธ(๋ง์ถคํ ๋ฐ์ดํฐ ํ ์คํธ)๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค. SQL ์ฟผ๋ฆฌ๋ก ๊ณต์ํ๋ ๋ชจ๋ ๊ฐ์ ์ ํ ์คํธ๊ฐ ๋ ์ ์์ต๋๋ค.
์ด๋ฌํ ๋ฐฉ์์ผ๋ก ์ฐฝ๊ณ ์ฐฝ์ ๋ฐ์ดํฐ์์ ์์น ์๋ ํธ์ฐจ์ ์ค๋ฅ๋ฅผ ํฌ์ฐฉํ ์ ์์ต๋๋ค.
๋ฌธ์ํ ์ธก๋ฉด์์ DBT๋ ๋ชจ๋ธ ๋ฐ ์์ฑ ์์ค์์ ๋ฉํ๋ฐ์ดํฐ์ ์ค๋ช ์ ์ถ๊ฐ, ๋ฒ์ ๊ด๋ฆฌ ๋ฐ ๋ฐฐํฌํ๊ธฐ ์ํ ๋ฉ์ปค๋์ฆ์ ์ ๊ณตํฉ๋๋ค.
๊ตฌ์ฑ ํ์ผ ์์ค์์ ํ ์คํธ ๋ฐ ๋ฌธ์๋ฅผ ์ถ๊ฐํ๋ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- name: fct_orders
description: This table has basic information about orders, as well as some derived facts based on payments
columns:
- name: order_id
tests:
- unique # ะฟัะพะฒะตัะบะฐ ะฝะฐ ัะฝะธะบะฐะปัะฝะพััั ะทะฝะฐัะตะฝะธะน
- not_null # ะฟัะพะฒะตัะบะฐ ะฝะฐ ะฝะฐะปะธัะธะต null
description: This is a unique identifier for an order
- name: customer_id
description: Foreign key to the customers table
tests:
- not_null
- relationships: # ะฟัะพะฒะตัะบะฐ ัััะปะพัะฝะพะน ัะตะปะพััะฝะพััะธ
to: ref('dim_customers')
field: customer_id
- name: order_date
description: Date (UTC) that the order was placed
- name: status
description: '{{ doc("orders_status") }}'
tests:
- accepted_values: # ะฟัะพะฒะตัะบะฐ ะฝะฐ ะดะพะฟัััะธะผัะต ะทะฝะฐัะตะฝะธั
values: ['placed', 'shipped', 'completed', 'return_pending', 'returned']
์์ฑ๋ ์น์ฌ์ดํธ์์ ์ด ๋ฌธ์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
๋งคํฌ๋ก ๋ฐ ๋ชจ๋
DBT์ ๋ชฉ์ ์ SQL ์คํฌ๋ฆฝํธ ์ธํธ๊ฐ ๋๋ ๊ฒ์ด ์๋๋ผ ์ฌ์ฉ์์๊ฒ ์์ฒด ๋ณํ์ ๊ตฌ์ถํ๊ณ ์ด๋ฌํ ๋ชจ๋์ ๋ฐฐํฌํ ์ ์๋ ๊ฐ๋ ฅํ๊ณ ๊ธฐ๋ฅ์ด ํ๋ถํ ์๋จ์ ์ ๊ณตํ๋ ๊ฒ์ ๋๋ค.
๋งคํฌ๋ก๋ ๋ชจ๋ธ ๋ด์์ ํจ์๋ก ํธ์ถํ ์ ์๋ ๊ตฌ๋ฌธ ๋ฐ ํํ์ ์ธํธ์ ๋๋ค. ๋งคํฌ๋ก๋ฅผ ์ฌ์ฉํ๋ฉด DRY(Don't Repeat Yourself) ์์ง๋์ด๋ง ์์น์ ๋ฐ๋ผ ๋ชจ๋ธ๊ณผ ํ๋ก์ ํธ ๊ฐ์ SQL์ ์ฌ์ฌ์ฉํ ์ ์์ต๋๋ค.
๋งคํฌ๋ก ์:
{% macro rename_category(column_name) %}
case
when {{ column_name }} ilike '%osx%' then 'osx'
when {{ column_name }} ilike '%android%' then 'android'
when {{ column_name }} ilike '%ios%' then 'ios'
else 'other'
end as renamed_product
{% endmacro %}
์ฌ์ฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
{% set column_name = 'product' %}
select
product,
{{ rename_category(column_name) }} -- ะฒัะทะพะฒ ะผะฐะบัะพัะฐ
from my_table
DBT์๋ ์ฌ์ฉ์๊ฐ ๊ฐ๋ณ ๋ชจ๋๊ณผ ๋งคํฌ๋ก๋ฅผ ๊ฒ์ํ๊ณ ์ฌ์ฌ์ฉํ ์ ์๋ ํจํค์ง ๊ด๋ฆฌ์๊ฐ ํจ๊ป ์ ๊ณต๋ฉ๋๋ค.
์ด๋ ๋ค์๊ณผ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ก๋ํ๊ณ ์ฌ์ฉํ ์ ์์์ ์๋ฏธํฉ๋๋ค.
dbt_utils : ๋ ์ง/์๊ฐ, ๋๋ฆฌ ํค, ์คํค๋ง ํ ์คํธ, ํผ๋ฒ/ํผ๋ฒ ํด์ ๋ฐ ๊ธฐํ ์์- ๋ค์๊ณผ ๊ฐ์ ์๋น์ค๋ฅผ ์ํ ๊ธฐ์ฑํ ์ผ์ผ์ด์ค ํ
ํ๋ฆฟ
์ ์ค๊ธฐ ะธ์คํธ๋ผ์ดํ - ํน์ ๋ฐ์ดํฐ ์ ์ฅ์๋ฅผ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(์:
์ ์ ํธ์ด ๋ก๊น โ DBT ์์ ์ ๋ก๊น ํ๋ ๋ชจ๋
์ ์ฒด ํจํค์ง ๋ชฉ๋ก์ ๋ค์์์ ํ์ธํ ์ ์์ต๋๋ค.
๋ ๋ง์ ๊ธฐ๋ฅ
์ฌ๊ธฐ์์๋ ํ๊ณผ ์ ๊ฐ ๋ฐ์ดํฐ ์จ์ดํ์ฐ์ค๋ฅผ ๊ตฌ์ถํ๋ ๋ฐ ์ฌ์ฉํ๋ ๋ช ๊ฐ์ง ๋ค๋ฅธ ํฅ๋ฏธ๋ก์ด ๊ธฐ๋ฅ๊ณผ ๊ตฌํ์ ๋ํด ์ค๋ช
ํ๊ฒ ์ต๋๋ค.
๋ฐํ์ ํ๊ฒฝ ๋ถ๋ฆฌ DEV - TEST - PROD
๋์ผํ DWH ํด๋ฌ์คํฐ ๋ด์์๋(๋ค๋ฅธ ๊ตฌ์ฑํ ๋ด์์). ์๋ฅผ ๋ค์ด ๋ค์ ํํ์์ ์ฌ์ฉํฉ๋๋ค.
with source as (
select * from {{ source('salesforce', 'users') }}
where 1=1
{%- if target.name in ['dev', 'test', 'ci'] -%}
where timestamp >= dateadd(day, -3, current_date)
{%- endif -%}
)
์ด ์ฝ๋๋ ๋ฌธ์ ๊ทธ๋๋ก ๋ค์๊ณผ ๊ฐ์ด ๋งํฉ๋๋ค. ๊ฐ๋ฐ, ํ ์คํธ, CI ์ง๋ 3์ผ ๋์์ ๋ฐ์ดํฐ๋ง ๊ฐ์ ธ์ค๊ณ ๊ทธ ์ด์์ ๊ฐ์ ธ์ค์ง ์์ต๋๋ค. ์ฆ, ์ด๋ฌํ ํ๊ฒฝ์์ ์คํํ๋ฉด ํจ์ฌ ๋ ๋น ๋ฅด๊ณ ๋ ์ ์ ๋ฆฌ์์ค๊ฐ ํ์ํฉ๋๋ค. ํ๊ฒฝ์์ ์คํํ ๋ ์ฐ๋ฅด๋ค ํํฐ ์กฐ๊ฑด์ ๋ฌด์๋ฉ๋๋ค.
๋์ฒด ์ด ์ธ์ฝ๋ฉ์ ํตํ ๊ตฌ์ฒดํ
Redshift๋ ๊ฐ ๊ฐ๋ณ ์ด์ ๋ํ ๋ฐ์ดํฐ ์์ถ ์๊ณ ๋ฆฌ์ฆ์ ์ค์ ํ ์ ์๋ ์ด ๊ธฐ๋ฐ DBMS์ ๋๋ค. ์ต์ ์ ์๊ณ ๋ฆฌ์ฆ์ ์ ํํ๋ฉด ๋์คํฌ ๊ณต๊ฐ์ 20~50%๊น์ง ์ค์ผ ์ ์์ต๋๋ค.
๋งคํฌ๋ก
๋งคํฌ๋ก ์๋ช :
{{ compress_table(schema, table,
drop_backup=False,
comprows=none|Integer,
sort_style=none|compound|interleaved,
sort_keys=none|List<String>,
dist_style=none|all|even,
dist_key=none|String) }}
๋ก๊น ๋ชจ๋ธ ์คํ
๋ชจ๋ธ์ ๊ฐ ์คํ์ ํํฌ๋ฅผ ์ฐ๊ฒฐํ ์ ์์ผ๋ฉฐ, ์ด๋ ์ถ์ ์ ์ด๋ ๋ชจ๋ธ ์์ฑ์ด ์๋ฃ๋ ์งํ์ ์คํ๋ฉ๋๋ค.
pre-hook: "{{ logging.log_model_start_event() }}"
post-hook: "{{ logging.log_model_end_event() }}"
๋ก๊น ๋ชจ๋์ ์ฌ์ฉํ๋ฉด ํ์ํ ๋ชจ๋ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ๋ณ๋์ ํ ์ด๋ธ์ ๊ธฐ๋กํ ์ ์์ผ๋ฉฐ, ์ดํ์ ๋ณ๋ชฉ ํ์์ ๊ฐ์ฌํ๊ณ ๋ถ์ํ๋ ๋ฐ ์ฌ์ฉํ ์ ์์ต๋๋ค.
Looker์ ๋ก๊น ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ ๋์๋ณด๋๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
์คํ ๋ฆฌ์ง ์ ์ง ๊ด๋ฆฌ ์๋ํ
UDF(์ฌ์ฉ์ ์ ์ ํจ์)์ ๊ฐ์ ์ฌ์ฉ๋ ๋ฆฌํฌ์งํ ๋ฆฌ ๊ธฐ๋ฅ์ ์ผ๋ถ ํ์ฅ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ์ด๋ฌํ ๊ธฐ๋ฅ์ ๋ฒ์ ๊ด๋ฆฌ, ์ก์ธ์ค ์ ์ด ๋ฐ ์ ๋ฆด๋ฆฌ์ค์ ์๋ ๋กค์์์ด DBT์์ ๋งค์ฐ ํธ๋ฆฌํฉ๋๋ค.
์ฐ๋ฆฌ๋ Python์์ UDF๋ฅผ ์ฌ์ฉํ์ฌ ํด์, ์ด๋ฉ์ผ ๋๋ฉ์ธ ๋ฐ ๋นํธ๋ง์คํฌ ๋์ฝ๋ฉ์ ๊ณ์ฐํฉ๋๋ค.
๋ชจ๋ ์คํ ํ๊ฒฝ(dev, test, prod)์์ UDF๋ฅผ ์์ฑํ๋ ๋งคํฌ๋ก์ ์:
{% macro create_udf() -%}
{% set sql %}
CREATE OR REPLACE FUNCTION {{ target.schema }}.f_sha256(mes "varchar")
RETURNS varchar
LANGUAGE plpythonu
STABLE
AS $$
import hashlib
return hashlib.sha256(mes).hexdigest()
$$
;
{% endset %}
{% set table = run_query(sql) %}
{%- endmacro %}
Wheely์์๋ PostgreSQL์ ๊ธฐ๋ฐ์ผ๋ก ํ๋ Amazon Redshift๋ฅผ ์ฌ์ฉํฉ๋๋ค. Redshift์ ๊ฒฝ์ฐ ์ ๊ธฐ์ ์ผ๋ก ํ ์ด๋ธ์ ๋ํ ํต๊ณ๋ฅผ ์์งํ๊ณ ๋์คํฌ ๊ณต๊ฐ์ ํ๋ณดํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค(๊ฐ๊ฐ ANALYZE ๋ฐ VACUUM ๋ช ๋ น).
์ด๋ฅผ ์ํด redshift_maintenance ๋งคํฌ๋ก์ ๋ช ๋ น์ด ๋งค์ผ ๋ฐค ์คํ๋ฉ๋๋ค.
{% macro redshift_maintenance() %}
{% set vacuumable_tables=run_query(vacuumable_tables_sql) %}
{% for row in vacuumable_tables %}
{% set message_prefix=loop.index ~ " of " ~ loop.length %}
{%- set relation_to_vacuum = adapter.get_relation(
database=row['table_database'],
schema=row['table_schema'],
identifier=row['table_name']
) -%}
{% do run_query("commit") %}
{% if relation_to_vacuum %}
{% set start=modules.datetime.datetime.now() %}
{{ dbt_utils.log_info(message_prefix ~ " Vacuuming " ~ relation_to_vacuum) }}
{% do run_query("VACUUM " ~ relation_to_vacuum ~ " BOOST") %}
{{ dbt_utils.log_info(message_prefix ~ " Analyzing " ~ relation_to_vacuum) }}
{% do run_query("ANALYZE " ~ relation_to_vacuum) %}
{% set end=modules.datetime.datetime.now() %}
{% set total_seconds = (end - start).total_seconds() | round(2) %}
{{ dbt_utils.log_info(message_prefix ~ " Finished " ~ relation_to_vacuum ~ " in " ~ total_seconds ~ "s") }}
{% else %}
{{ dbt_utils.log_info(message_prefix ~ ' Skipping relation "' ~ row.values() | join ('"."') ~ '" as it does not exist') }}
{% endif %}
{% endfor %}
{% endmacro %}
DBT ํด๋ผ์ฐ๋
DBT๋ฅผ ์๋น์ค(Managed Service)๋ก ํ์ฉํ๋ ๊ฒ์ด ๊ฐ๋ฅํฉ๋๋ค. ํฌํจ:
- ํ๋ก์ ํธ ๋ฐ ๋ชจ๋ธ ๊ฐ๋ฐ์ ์ํ Web IDE
- ์์ ๊ตฌ์ฑ ๋ฐ ์์ฝ
- ๊ฐ๋จํ๊ณ ํธ๋ฆฌํ ๋ก๊ทธ ์ก์ธ์ค
- ํ๋ก์ ํธ ๋ฌธ์๊ฐ ํฌํจ๋ ์น์ฌ์ดํธ
- CI ์ฐ๊ฒฐ(์ง์์ ํตํฉ)
๊ฒฐ๋ก
DWH๋ฅผ ์ค๋นํ๊ณ ์ญ์ทจํ๋ ๊ฒ์ ์ค๋ฌด๋๋ฅผ ๋ง์๋ ๊ฒ๋งํผ ์ฆ๊ฒ๊ณ ์ ์ตํฉ๋๋ค. DBT๋ Jinja, ์ฌ์ฉ์ ํ์ฅ(๋ชจ๋), ์ปดํ์ผ๋ฌ, ์คํ๊ธฐ, ํจํค์ง ๊ด๋ฆฌ์๋ก ๊ตฌ์ฑ๋ฉ๋๋ค. ์ด๋ฌํ ์์๋ฅผ ๊ฒฐํฉํ๋ฉด ๋ฐ์ดํฐ ์จ์ดํ์ฐ์ค๋ฅผ ์ํ ์๋ฒฝํ ์์ ํ๊ฒฝ์ ์ป์ ์ ์์ต๋๋ค. ์ค๋๋ DWH ๋ด์์ ํ์ ์ ๊ด๋ฆฌํ๋ ๋ฐ ์ด๋ณด๋ค ๋ ์ข์ ๋ฐฉ๋ฒ์ ์์ต๋๋ค.
DBT ๊ฐ๋ฐ์๊ฐ ๋ฐ๋ฅด๋ ์ ๋ ์ ๋ค์๊ณผ ๊ฐ์ด ๊ณต์ํ๋ฉ๋๋ค.
- GUI๊ฐ ์๋ ์ฝ๋๋ ๋ณต์กํ ๋ถ์ ๋ ผ๋ฆฌ๋ฅผ ํํํ๋ ๋ฐ ๊ฐ์ฅ ์ ํฉํ ์ถ์ํ์ ๋๋ค.
- ๋ฐ์ดํฐ ์์ ์ ์ํํธ์จ์ด ์์ง๋์ด๋ง(์ํํธ์จ์ด ์์ง๋์ด๋ง)์ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ์ ์ฉํด์ผ ํฉ๋๋ค.
- ์ค์ํ ๋ฐ์ดํฐ ์ธํ๋ผ๋ ์คํ ์์ค ์ํํธ์จ์ด๋ก์ ์ฌ์ฉ์ ์ปค๋ฎค๋ํฐ์ ์ํด ์ ์ด๋์ด์ผ ํฉ๋๋ค.
- ๋ถ์ ๋๊ตฌ๋ฟ๋ง ์๋๋ผ ์ฝ๋๋ ์ ์ฐจ ์คํ ์์ค ์ปค๋ฎค๋ํฐ์ ์์ฐ์ด ๋ ๊ฒ์ ๋๋ค.
์ด๋ฌํ ํต์ฌ ์ ๋ ์ ์ค๋๋ 850๊ฐ ์ด์์ ํ์ฌ์์ ์ฌ์ฉ๋๋ ์ ํ์ ํ์์์ผฐ์ผ๋ฉฐ, ๋ฏธ๋์ ๋ง๋ค์ด์ง ๋ง์ ํฅ๋ฏธ๋ก์ด ํ์ฅ์ ๊ธฐ์ด๋ฅผ ํ์ฑํฉ๋๋ค.
๊ด์ฌ ์๋ ๋ถ๋ค์ ์ํด ์ ๊ฐ ๋ช ๋ฌ ์ OTUS์์ ์ด๋ฆฐ ๊ณต๊ฐ ๋ ์จ์ ์ผํ์ผ๋ก ์งํํ ๊ณต๊ฐ ๋ ์จ ์์์ด ์์ต๋๋ค.
DBT ๋ฐ ๋ฐ์ดํฐ ์จ์ดํ์ฐ์ง ์ธ์๋ OTUS ํ๋ซํผ์ ๋ฐ์ดํฐ ์์ง๋์ด ๊ณผ์ ์ ์ผํ์ผ๋ก ๋๋ฃ์ ์ ๋ ๊ธฐํ ๊ด๋ จ ์๊ณ ํ๋์ ์ธ ์ฃผ์ ์ ๋ํด ๊ฐ์ํฉ๋๋ค.
- ๋น ๋ฐ์ดํฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ ์ํคํ ์ฒ ๊ฐ๋
- Spark ๋ฐ Spark Streaming ์ค์ต
- ๋ฐ์ดํฐ ์์ค ๋ก๋ ๋ฐฉ๋ฒ ๋ฐ ๋๊ตฌ ํ์
- DWH์์ ๋ถ์ ์ผ์ผ์ด์ค ๊ตฌ์ถ
- NoSQL ๊ฐ๋ : HBase, Cassandra, ElasticSearch
- ๋ชจ๋ํฐ๋ง ๋ฐ ์กฐ์ ์ ์์น
- ์ต์ข ํ๋ก์ ํธ: ๋ฉํ ๋ง ์ง์์ ํตํด ๋ชจ๋ ๊ธฐ์ ์ ํ๋๋ก ๋ชจ์ผ๊ธฐ
๋งํฌ :
DBT ๋ฌธ์ - ์๊ฐ โ ๊ณต์ ๋ฌธ์์ ํํ dbt๋ ๋ฌด์์ธ๊ฐ์? โ DBT ์ ์ ์ค ํ ์ฌ๋์ ๋ฆฌ๋ทฐ ๊ธฐ์ฌAmazon Redshift ์คํ ๋ฆฌ์ง์ฉ ๋ฐ์ดํฐ ๊ตฌ์ถ ๋๊ตฌ โ YouTube, OTUS ์คํ ๋ ์จ ๋ นํ๊ทธ๋ฆฐํ๋ผ ์์๋ณด๊ธฐ โ ๋ค์ ๊ณต๊ฐ ๊ฐ์๋ 15๋ 2020์ XNUMX์ผ์ ๋๋ค.๋ฐ์ดํฐ ์์ง๋์ด๋ง ์ฝ์ค โOTUS์ฑ์ํ ๋ถ์ ์ํฌํ๋ก ๊ตฌ์ถ โ ๋ฐ์ดํฐ ๋ฐ ๋ถ์์ ๋ฏธ๋ ์ดํด๋ณด๊ธฐ์ด์ ์คํ ์์ค ๋ถ์์ด ํ์ํ ๋์ ๋๋ค. โ ๋ถ์์ ์งํ์ ์คํ ์์ค์ ์ํฅdbtCloud๋ฅผ ์ฌ์ฉํ ์ง์์ ์ธ ํตํฉ ๋ฐ ์๋ํ๋ ๋น๋ ํ ์คํธ โ DBT๋ฅผ ํ์ฉํ CI ๊ตฌ์ถ ์์นDBT ํํ ๋ฆฌ์ผ ์์ํ๊ธฐ โ ์ฐ์ต, ๋ ๋ฆฝ์ ์ธ ์์ ์ ์ํ ๋จ๊ณ๋ณ ์ง์นจJaffle ์์ โ Github DBT Tutorial โ Github, ๊ต์ก ํ๋ก์ ํธ ์ฝ๋
์ถ์ฒ : habr.com