Satu sore, tim backend mengumumkan perubahan kolom di tabel raw_pesanan mulai besok. Pertanyaan pertama tim data bukan “bagaimana cara mengubah kode”, melainkan “tabel dan dashboard mana saja yang terdampak”. Tanpa peta dependensi, jawabannya hanya tebakan, karena pipeline TokoKita sudah terdiri dari belasan tabel staging dan mart yang saling menumpuk. Data lineage menjawab pertanyaan tersebut dengan memetakan asal-usul setiap tabel — dari data mentah sampai mart laporan — sehingga dampak sebuah perubahan bisa dihitung sebelum kode diganti. Chapter ini membangun graf lineage langsung dari kode pipeline, lalu memakai graf tersebut untuk analisis dampak.
Apa Itu Data Lineage #
Data lineage adalah catatan dependensi antar artefak data: tabel mana yang dibaca, tabel mana yang ditulis, dan bagaimana nilai mengalir di antaranya. Ada dua tingkat ketelitian yang perlu kita bedakan:
- Table-level lineage memetakan relasi antar tabel, misalnya
mart_penjualan_hariandibangun daristg_pesanandanstg_pelanggan. - Column-level lineage memetakan relasi antar kolom, misalnya kolom
gmvdi mart dihitung dari kolomtotaldistg_pesanan.
Table-level lineage cukup untuk menjawab “apa yang terdampak jika tabel X berubah”. Column-level lineage dibutuhkan saat kita harus mengaudit rumus: ketika angka gmv mencurigakan, kita menelusuri bahwa nilainya berasal dari total yang difilter kolom status. Untuk analisis dampak, table-level sudah cukup dan jauh lebih mudah dibangun, sehingga kita mulai dari sana.
Membangun Graf Lineage dari Kode Pipeline #
Sumber lineage paling jujur adalah kode pipeline itu sendiri. Setiap langkah SQL menyatakan upstream secara eksplisit lewat klausa FROM dan JOIN, sedangkan nama langkah menyatakan tabel target. Kita definisikan mini-pipeline TokoKita sebagai kamus target -> SQL, plus satu langkah pandas yang ditulis manual:
import re
LANGKAH_SQL = {
"stg_pesanan": """
SELECT id_pesanan, id_pelanggan, total, status
FROM raw_pesanan
WHERE status = 'lunas'
""",
"stg_pelanggan": """
SELECT id_pelanggan, nama, kota
FROM raw_pelanggan
""",
"mart_penjualan_harian": """
SELECT DATE(p.tanggal) AS tanggal, c.kota, SUM(p.total) AS gmv
FROM stg_pesanan p
JOIN stg_pelanggan c ON p.id_pelanggan = c.id_pelanggan
GROUP BY 1, 2
""",
}
LANGKAH_PANDAS = {
"mart_gmv_bulanan": ["mart_penjualan_harian"],
}
POLA_TABEL = re.compile(r"\b(?:FROM|JOIN)\s+([a-z_][a-z0-9_]*)", re.IGNORECASE)
def ekstrak_upstream(sql):
"""Ambil nama tabel setelah FROM atau JOIN."""
temuan = POLA_TABEL.findall(sql)
return sorted({t.lower() for t in temuan})
def bangun_lineage():
lineage = {}
for target, sql in LANGKAH_SQL.items():
lineage[target] = ekstrak_upstream(sql)
for target, sumber in LANGKAH_PANDAS.items():
lineage[target] = sorted(sumber)
return lineage
lineage = bangun_lineage()
print("== Graf lineage (tabel -> upstream) ==")
for target in sorted(lineage):
print(f"{target:24s} <- {', '.join(lineage[target])}")Output yang Dihasilkan:
== Graf lineage (tabel -> upstream) ==
mart_gmv_bulanan <- mart_penjualan_harian
mart_penjualan_harian <- stg_pelanggan, stg_pesanan
stg_pelanggan <- raw_pelanggan
stg_pesanan <- raw_pesananMari kita telaah baris-baris kode di atas:
- Baris 3-19 (
LANGKAH_SQL): tiga langkah SQL didefinisikan sebagai kamus; kunci adalah tabel target, nilai adalah query yang membangun tabel tersebut. - Baris 21-23 (
LANGKAH_PANDAS): langkah non-SQL dideklarasikan manual karena kode pandas tidak bisa diparsing dengan regex yang sama. - Baris 25 (
POLA_TABEL): regex menangkap identifier pertama setelah kataFROMatauJOIN; alias sepertipdancberada setelah nama tabel sehingga tidak ikut tertangkap, dan\bmemastikanFROMhanya cocok sebagai kata utuh. - Baris 28-31 (
ekstrak_upstream):findallmengembalikan semua kecocokan, lalusetmembuang duplikat dansortedmenjaga urutan stabil. - Baris 34-40 (
bangun_lineage): graf disimpan sebagai kamustabel -> daftar upstream; tabelraw_pesanantidak pernah menjadi kunci karena tabel tersebut tidak dibangun pipeline ini — tabel tersebut adalah sumber.
Analisis Dampak saat Tabel Berubah #
Graf lineage menjawab pertanyaan praktis: jika kolom di stg_pesanan berubah, apa yang harus dicek ulang? Kita balik arah graf menjadi relasi upstream -> pemakai, lalu menjalankan BFS dari tabel yang berubah untuk mengumpulkan semua downstream transitif:
from collections import deque
def downstream(lineage, mulai):
"""BFS: semua tabel yang (secara transitif) bergantung pada `mulai`."""
pemakai = {}
for target, sumber in lineage.items():
for s in sumber:
pemakai.setdefault(s, []).append(target)
terdampak = []
antrean = deque([mulai])
dikunjungi = {mulai}
while antrean:
tabel = antrean.popleft()
for p in pemakai.get(tabel, []):
if p not in dikunjungi:
dikunjungi.add(p)
terdampak.append(p)
antrean.append(p)
return terdampak
print("== Analisis dampak: kolom di stg_pesanan berubah ==")
print("Tabel downstream yang perlu dicek ulang:")
for t in downstream(lineage, "stg_pesanan"):
print(f" - {t}")
print()
print("== Analisis dampak: raw_pesanan berubah ==")
for t in downstream(lineage, "raw_pesanan"):
print(f" - {t}")Output yang Dihasilkan:
== Analisis dampak: kolom di stg_pesanan berubah ==
Tabel downstream yang perlu dicek ulang:
- mart_penjualan_harian
- mart_gmv_bulanan
== Analisis dampak: raw_pesanan berubah ==
- stg_pesanan
- mart_penjualan_harian
- mart_gmv_bulananFungsi downstream bekerja dalam dua tahap. Pertama, loop for di awal membalik graf: kamus pemakai memetakan setiap tabel ke daftar tabel yang membacanya. Kedua, BFS mengunjungi pemakai secara berlapis — stg_pesanan dulu, lalu mart_penjualan_harian, lalu mart_gmv_bulanan — sehingga hasilnya sudah terurut menurut jarak dari tabel yang berubah. Himpunan dikunjungi mencegah tabel tercatat dua kali bila graf bercabang lalu menyatu kembali.
Visualisasi Graf Lineage #
Graf yang sama mudah dirender sebagai diagram. Node menyatakan tabel, panah menyatakan arah aliran data:
Diagram ini adalah versi visual dari kamus lineage: setiap baris sumber --> target berasal dari satu pasangan di graf. Pada pipeline nyata dengan puluhan tabel, diagram seperti ini menjadi peta navigasi utama saat onboarding engineer baru maupun saat menilai dampak perubahan.
Lineage dari Kode versus Lineage Otomatis #
| Aspek | Lineage dari kode | Lineage otomatis |
|---|---|---|
| Sumber kebenaran | SQL dan kode transform di repository | event lineage yang dikirim runtime pipeline |
| Pemeliharaan | ikut berubah saat kode diubah | butuh integrasi di setiap tool |
| Cakupan | hanya langkah yang tertulis di kode | termasuk query ad-hoc dan layer BI |
| Cocok untuk | tim kecil, pipeline batch sederhana | organisasi dengan banyak tim dan tool |
Pendekatan dari kode menuntut disiplin: setiap langkah harus terdaftar di satu tempat. Begitu ada tabel yang dibangun lewat query manual di notebook, graf langsung bohong — dan lineage yang bohong lebih berbahaya daripada tidak ada lineage sama sekali.
Lineage memberi tahu kita apa yang terdampak ketika sesuatu berubah. Namun untuk mengetahui apa yang terjadi di dalam pipeline setiap kali pipeline dijalankan — tahap mana yang lambat, berapa baris yang diproses, kapan kegagalan terjadi — kita butuh catatan eksekusi yang terstruktur. Materi tersebut dibahas di chapter Pipeline Logging.