Tutorial dbt: Analytics Engineering dan Transformasi Data Modern

# Analytics Engineering dengan dbt: Panduan Praktis dbt (data build tool) telah menjadi bagian standar dari modern data stack karena membawa disiplin software engineering ke transformasi data berbasi...

By Ruby Abdullah · · tutorial
dbtAnalytics EngineeringData TransformationSQLData WarehouseELT

Analytics Engineering dengan dbt: Panduan Praktis

dbt (data build tool) telah menjadi bagian standar dari modern data stack karena membawa disiplin software engineering ke transformasi data berbasis SQL. Tutorial ini membahas apa itu dbt, cara menyiapkannya, dan cara membangun proyek transformasi yang mudah dirawat, lengkap dengan contoh SQL dan YAML realistis yang bisa Anda sesuaikan dengan warehouse Anda sendiri.

Apa Itu dbt dan Alur Kerja Analytics Engineering

dbt adalah alat transformasi yang memungkinkan Anda mengekspresikan transformasi data sebagai pernyataan SELECT SQL. dbt tidak melakukan ekstraksi atau pemuatan data; ia berada di bagian T dari ELT, berjalan di dalam data warehouse Anda (Snowflake, BigQuery, Redshift, Postgres, Databricks, dan lainnya). Anda menulis model, lalu dbt mengompilasinya menjadi SQL dan menjalankannya sesuai urutan dependensi.

Peran yang muncul seiring alur kerja ini adalah analytics engineer: seseorang yang menerapkan praktik engineering, version control, pengujian, code review, dokumentasi, dan desain modular, pada kode analitik yang dahulu ditulis sebagai query ad-hoc.

Alur kerja dbt yang umum terlihat seperti ini:

  • Data mentah masuk ke warehouse melalui alat ingesti (Fivetran, Airbyte, pipeline kustom).
  • Anda mendefinisikan sources yang menunjuk ke tabel mentah tersebut.
  • Anda membangun staging model yang membersihkan dan mengganti nama kolom.
  • Anda membangun model intermediate dan mart yang melakukan join dan agregasi.
  • Anda menambahkan tests dan dokumentasi.
  • Anda menjalankan dbt build secara terjadwal, di CI, atau melalui dbt Cloud.
  • Manfaat utamanya adalah keterulangan (repeatability), pelacakan lineage, dan kemampuan menguji transformasi layaknya kode aplikasi.

    Instalasi

    dbt didistribusikan sebagai dbt-core ditambah paket adapter untuk warehouse spesifik Anda. Pasang hanya adapter yang Anda butuhkan.

    # Buat environment terisolasi terlebih dahulu
    

    python -m venv .venv

    source .venv/bin/activate

    Postgres

    pip install dbt-postgres

    Atau BigQuery

    pip install dbt-bigquery

    dbt-core terpasang otomatis sebagai dependensi

    dbt --version

    dbt membaca pengaturan koneksi dari file profiles.yml. Secara default file ini berada di ~/.dbt/profiles.yml, terpisah dari proyek Anda agar kredensial tidak masuk ke version control.

    # ~/.dbt/profiles.yml
    

    jaffleshop:

    target: dev

    outputs:

    dev:

    type: postgres

    host: localhost

    port: 5432

    user: analytics

    password: "{{ envvar('DBTPASSWORD') }}"

    dbname: analytics

    schema: dbtdev

    threads: 4

    Menggunakan envvar() menjaga rahasia tetap di luar file. Uji koneksi dengan:

    dbt debug
    

    Struktur Proyek

    Buat proyek baru dengan dbt init, lalu telusuri struktur yang dihasilkan.

    dbt init jaffleshop
    

    Proyek dbt berpusat pada dbtproject.yml dan sekumpulan direktori konvensional:

    jaffleshop/
    

    dbtproject.yml # konfigurasi proyek

    models/ # pernyataan SELECT (transformasi Anda)

    staging/

    marts/

    seeds/ # file CSV statis yang dimuat sebagai tabel

    snapshots/ # pelacakan SCD type 2

    macros/ # Jinja yang dapat dipakai ulang

    tests/ # singular test (kustom)

    analyses/ # query ad-hoc yang dikompilasi namun tidak dijalankan

    File dbtproject.yml mengikat semuanya dan menetapkan nilai default:

    name: 'jaffleshop'
    

    version: '1.0.0'

    profile: 'jaffleshop'

    model-paths: ["models"]

    seed-paths: ["seeds"]

    snapshot-paths: ["snapshots"]

    macro-paths: ["macros"]

    models:

    jaffleshop:

    staging:

    +materialized: view

    marts:

    +materialized: table

    Kunci +materialized menetapkan default per folder. Awalan + menandai sebuah konfigurasi, bukan jalur model bersarang.

    Sebuah model adalah satu file .sql yang berisi satu pernyataan SELECT. Nama file menjadi nama relasi di warehouse. Tidak ada boilerplate CREATE TABLE, dbt membungkus query Anda berdasarkan materialisasi.

    -- models/staging/stgorders.sql
    

    select

    id as orderid,

    userid as customerid,

    orderdate,

    status,

    amount / 100.0 as amountusd

    from {{ source('jaffle', 'raworders') }}

    -- models/marts/orderspercustomer.sql
    

    select

    customerid,

    count() as ordercount,

    sum(amountusd) as lifetimevalue

    from {{ ref('stgorders') }}

    group by 1

    Materialisasi

    Materialisasi mengontrol bagaimana dbt menyimpan hasil sebuah model. Anda mengaturnya secara inline dengan blok config() atau di dbtproject.yml.

    • view (default): membuat database view. Tanpa biaya penyimpanan, selalu segar, dihitung ulang saat dibaca.
    • table: membangun ulang tabel penuh pada setiap run. Cepat untuk di-query, mudah dipahami.
    • incremental: menyisipkan atau memperbarui hanya baris baru/berubah. Dipakai untuk tabel besar yang banyak penambahan.
    • ephemeral: tidak dibangun di warehouse sama sekali; disisipkan sebagai CTE ke model downstream.

    -- konfigurasi inline
    

    {{ config(materialized='table') }}

    select from {{ ref('stgorders') }}

    Pilih view untuk staging yang ringan, table untuk mart yang sering di-query, incremental ketika rebuild penuh menjadi terlalu lambat atau mahal, dan ephemeral untuk transformasi pembantu kecil yang tidak perlu di-query langsung.

    ref() dan source()

    Kedua fungsi Jinja ini adalah inti dari dbt. Jangan pernah menulis nama tabel secara hard-code; gunakan ref() dan source() agar dbt dapat membangun graf dependensi dan mengelola schema antar environment.

    ref('model
    name') mereferensikan model lain. dbt menggunakan pemanggilan ini untuk menentukan urutan run dan menghasilkan lineage.
    from {{ ref('stgorders') }}
    

    source('sourcename', 'tablename') mereferensikan data mentah yang didefinisikan di file YAML. Ini memisahkan model Anda dari lokasi fisik tabel.
    from {{ source('jaffle', 'raworders') }}
    

    Karena dbt menyelesaikan ini saat kompilasi, beralih dari schema dev ke schema prod tidak memerlukan perubahan kode, cukup target yang berbeda.

    Sources dan Freshness

    Deklarasikan sources di file YAML agar dapat direferensikan, diuji, dan dipantau.

    # models/staging/sources.yml
    

    version: 2

    sources:

    • name: jaffle
    database: raw

    schema: public

    tables:

    • name: raworders
    loadedatfield: loadedat

    freshness:

    warnafter: {count: 12, period: hour}

    errorafter: {count: 24, period: hour}

    • name: rawcustomers

    Blok freshness memungkinkan dbt memeriksa apakah data mentah datang tepat waktu. Jalankan pemeriksaan dengan:

    dbt source freshness
    

    Jika nilai loadedat terbaru lebih lama dari ambang batas, dbt mengeluarkan peringatan atau error, yang berguna sebagai sinyal awal bahwa pipeline upstream macet.

    Tests

    Tests memastikan data Anda memenuhi ekspektasi. dbt memiliki dua jenis: tes generic yang diterapkan via YAML, dan tes singular yang ditulis sebagai SQL.

    Empat tes bawaan mencakup sebagian besar kebutuhan:

    # models/staging/models.yml
    

    version: 2

    models:

    • name: stgorders
    columns:

    • name: orderid
    tests:

    • unique
    • notnull
    • name: status
    tests:

    • acceptedvalues:
    values: ['placed', 'shipped', 'completed', 'returned']

    • name: customerid
    tests:

    • relationships:
    to: ref('stg
    customers')

    field: customerid

    • unique: tidak ada nilai duplikat.
    • notnull: tidak ada nilai yang hilang.
    • acceptedvalues: hanya nilai dari himpunan yang dikenal.
    • relationships: setiap nilai ada di model yang direferensikan (integritas referensial).

    Sebuah tes singular adalah query SQL apa pun yang seharusnya mengembalikan nol baris. Jika mengembalikan baris, tes gagal.

    -- tests/assertamountispositive.sql
    

    select

    orderid,

    amountusd

    from {{ ref('stgorders') }}

    where amountusd < 0

    Jalankan tes dengan:

    dbt test
    

    dbt test --select stgorders

    Dokumentasi dan dbt docs generate

    dbt menghasilkan situs dokumentasi yang dapat ditelusuri dari deskripsi YAML dan lineage model Anda. Tambahkan deskripsi di samping tes Anda.

    version: 2
    
    

    models:

    • name: stgorders
    description: "Satu baris per order, dibersihkan dari raworders."

    columns:

    • name: orderid
    description: "Primary key untuk order."

    tests:

    • unique
    • notnull

    Anda juga dapat menulis deskripsi yang lebih panjang di file .md dan mereferensikannya dengan fungsi doc():

        description: "{{ doc('stgordersdoc') }}"
    

    Bangun dan sajikan dokumentasi:

    dbt docs generate
    

    dbt docs serve

    Situs yang dihasilkan mencakup katalog model yang dapat dicari dan graf lineage interaktif (DAG), yang memudahkan menelusuri bagaimana sebuah kolom mengalir dari sumber mentah ke mart akhir.

    Jinja dan Macros

    dbt membuat template SQL dengan Jinja, yang menambahkan variabel, perulangan, kondisional, dan fungsi yang dapat dipakai ulang yang disebut macros. Ini menjaga SQL tetap DRY tanpa harus copy-paste.

    -- perulangan untuk mem-pivot status menjadi kolom
    

    select

    customerid,

    {% for status in ['placed', 'shipped', 'completed'] %}

    sum(case when status = '{{ status }}' then 1 else 0 end) as {{ status }}count

    {%- if not loop.last %},{% endif %}

    {% endfor %}

    from {{ ref('stgorders') }}

    group by 1

    Sebuah macro adalah blok bernama yang dapat dipakai ulang, didefinisikan di macros/:

    -- macros/centstodollars.sql
    

    {% macro centstodollars(columnname, precision=2) %}

    round({{ columnname }} / 100.0, {{ precision }})

    {% endmacro %}

    Panggil dari model mana pun:

    select
    

    orderid,

    {{ centstodollars('amount') }} as amountusd

    from {{ source('jaffle', 'raworders') }}

    Macros adalah cara paket seperti dbtutils mendistribusikan logika yang dapat dipakai ulang. Pasang paket dengan mencantumkannya di packages.yml lalu menjalankan dbt deps.

    Seeds

    Seeds adalah file CSV kecil di direktori seeds/ yang dimuat dbt sebagai tabel. Cocok untuk data referensi statis seperti kode negara, pemetaan status, atau daftar akun uji yang perlu dikecualikan.

    seeds/
    

    countrycodes.csv

    dbt seed
    

    Setelah dimuat, referensikan sebuah seed dengan ref() persis seperti model:

    select
    

    o.orderid,

    c.countryname

    from {{ ref('stgorders') }} o

    left join {{ ref('countrycodes') }} c

    on o.countrycode = c.code

    Seeds berada di bawah version control, sehingga data referensi hidup berdampingan dengan logika transformasi Anda. Hindari menggunakan seeds untuk dataset besar atau yang sering berubah, seeds ditujukan untuk lookup kecil yang stabil.

    Snapshots (SCD Type 2)

    Sistem sumber sering menimpa record di tempat, sehingga keadaan historis hilang. Snapshots menangkap perubahan dari waktu ke waktu menggunakan logika slowly changing dimension (SCD) type 2, mencatat kapan setiap versi baris valid.

    -- snapshots/orderssnapshot.sql
    

    {% snapshot orderssnapshot %}

    {{

    config(

    targetschema='snapshots',

    uniquekey='orderid',

    strategy='timestamp',

    updatedat='updatedat'

    )

    }}

    select * from {{ source('jaffle', 'raworders') }}

    {% endsnapshot %}

    Dengan strategi timestamp, dbt membandingkan kolom updatedat pada setiap run. Strategi check membandingkan kolom yang ditentukan, berguna ketika sumber tidak memiliki timestamp yang andal.

    dbt snapshot
    

    dbt menambahkan kolom dbtvalidfrom dan dbtvalidto. Nilai dbtvalidto yang null menandai versi baris saat ini, yang membuat analisis point-in-time menjadi mudah.

    Incremental Model dengan isincremental()

    Incremental model memproses hanya record baru atau berubah alih-alih membangun ulang seluruh tabel pada setiap run. Ini adalah jawaban ketika materialisasi table menjadi terlalu lambat atau mahal pada tabel fakta yang besar.

    -- models/marts/fctevents.sql
    

    {{

    config(

    materialized='incremental',

    uniquekey='eventid'

    )

    }}

    select

    eventid,

    userid,

    eventtype,

    eventat

    from {{ source('app', 'rawevents') }}

    {% if isincremental() %}

    -- hanya pindai baris yang lebih baru dari yang sudah dimuat

    where eventat > (select max(eventat) from {{ this }})

    {% endif %}

    isincremental() mengembalikan true hanya ketika tabel sudah ada dan Anda tidak sedang menjalankan full refresh. {{ this }} merujuk pada relasi model saat ini. Dengan uniquekey diset, dbt melakukan merge pada baris yang cocok alih-alih menggandakannya.

    Paksa rebuild penuh ketika logika berubah:

    dbt run --select fctevents --full-refresh
    

    Menjalankan dbt: run, test, dan build

    Perintah dbt beroperasi pada seleksi resource. Gunakan --select untuk menargetkan subset.

    # bangun semua model
    

    dbt run

    bangun satu model dan semua downstream-nya

    dbt run --select stgorders+

    jalankan tes saja

    dbt test

    build: menjalankan model, tes, snapshot, dan seed bersama-sama sesuai urutan DAG

    dbt build

    dbt build adalah perintah yang direkomendasikan untuk produksi karena menyisipkan model dan tesnya sesuai urutan dependensi, mencegah model downstream dibangun di atas data yang gagal tesnya. Operator graf patut dipelajari: model+ memilih turunan, +model memilih leluhur, dan tag:nightly memilih berdasarkan tag.

    Catatan Deployment: dbt Cloud vs CI

    Anda memiliki dua jalur umum menuju produksi.

    dbt Cloud adalah layanan terkelola yang menyediakan scheduler, IDE berbasis browser, dokumentasi terhosting, dan CI bawaan yang berjalan terhadap pull request. Ini adalah opsi dengan perawatan lebih rendah dan menangani orkestrasi untuk Anda. CI yang dikelola sendiri menjalankan dbt-core di pipeline Anda sendiri (GitHub Actions, GitLab CI, Airflow). Contoh job GitHub Actions:
    # .github/workflows/dbt.yml
    

    name: dbt build

    on: [pullrequest]

    jobs:

    dbt:

    runs-on: ubuntu-latest

    steps:

    • uses: actions/checkout@v4
    • uses: actions/setup-python@v5
    with:

    python-version: '3.11'

    • run: pip install dbt-postgres
    • run: dbt deps
    • run: dbt build --target ci
    env:

    DBTPASSWORD: ${{ secrets.DBTPASSWORD }}

    Untuk CI yang efisien, pelajari Slim CI menggunakan state:modified+ dengan deferral, yang hanya membangun model yang berubah dalam sebuah pull request alih-alih seluruh proyek. Apa pun caranya, tujuannya sama: setiap perubahan diuji sebelum mencapai data produksi.

    Praktik Terbaik

    • Susun model berlapis. Gunakan staging (satu model per tabel sumber, pembersihan ringan), intermediate (join yang dapat dipakai ulang), dan marts (model menghadap bisnis). Ini menjaga logika tetap modular dan mudah di-review.
    • Selalu gunakan ref() dan source(). Jangan pernah menulis nama schema atau tabel secara hard-code; biarkan dbt mengelola lineage dan environment.
    • Satu staging model per tabel sumber, dinamai stg, hanya melakukan penamaan ulang, type casting, dan pembersihan ringan.
    • Uji asumsi Anda. Minimal, tambahkan unique dan notnull pada setiap primary key. Tambahkan tes relationships pada foreign key.
    • Jaga kredensial di luar repo. Gunakan env_var() di profiles.yml.
    • Utamakan view untuk staging dan table untuk marts. Gunakan incremental hanya ketika waktu run atau biaya menuntutnya.
    • Dokumentasikan sambil berjalan. Model dengan deskripsi dan kolom yang teruji jauh lebih mudah dipercaya oleh analis berikutnya.
    • Jalankan dbt build di CI agar perubahan yang belum teruji tidak pernah mencapai produksi.
    • Kesimpulan dan Poin Penting

      dbt membawa praktik software engineering, version control, modularitas, pengujian, dan dokumentasi, ke lapisan transformasi data stack Anda. Dengan menulis model sebagai pernyataan SELECT biasa dan menghubungkannya dengan ref() dan source(), Anda memperoleh graf dependensi yang dikelola otomatis, portabilitas environment, dan lineage yang jelas.

      Poin penting untuk diingat:

      • dbt menangani hanya transformasi (huruf T pada ELT); ia menjalankan SQL di dalam warehouse Anda.
      • Materialisasi (view, table, incremental, ephemeral) memungkinkan Anda menyetel trade-off biaya/kesegaran per model.
      • ref() dan source() wajib untuk lineage dan pengelolaan environment.
      • Tests (generic dan singular) serta dbt build mencegah data buruk menyebar ke downstream.
      • Snapshots menangkap riwayat (SCD type 2); incremental model menskalakan tabel besar.
      • Dokumentasi dan DAG membuat proyek dapat dipahami oleh seluruh tim.

      Mulailah dari yang kecil dengan beberapa staging model dan sedikit tes, lalu kembangkan proyek lapis demi lapis. Disiplin ini terbayar seiring transformasi dan tim Anda bertumbuh.

      Artikel Terkait