Pukul 03:00 pipeline pesanan harian gagal, dan engineer yang sedang on-call membuka repositori dengan satu pertanyaan di kepala: apa yang harus saya lakukan sekarang? Tanpa dokumentasi, satu jam berikutnya habis untuk membaca kode, menebak sumber data, dan mencari pemilik sistem sumber — sementara tenggat laporan pukul 06:00 terus mendekat. Dokumentasi yang baik memangkas waktu tersebut menjadi lima menit, dan karena itu dokumentasi adalah bagian dari pipeline, bukan tempelan yang dikerjakan saat senggang.
Lima Hal yang Wajib Didokumentasikan #
Dokumentasi pipeline minimal menjawab lima pertanyaan. Pertama, sumber dan kontrak input: dari mana data berasal, formatnya apa, kolom mana yang wajib, dan kapan data tersedia. Kedua, kontrak output: struktur tabel hasil, lokasinya, dan jaminan yang diberikan — misalnya aman dibaca setelah status sukses, dan aman dijalankan ulang. Ketiga, jadwal dan dependensi: kapan pipeline berjalan, stage apa yang harus tuntas lebih dulu, dan berapa durasi normal. Keempat, owner: siapa yang bertanggung jawab, lewat kanal apa dihubungi, bagaimana eskalasi. Kelima, runbook kegagalan: langkah bernomor yang bisa diikuti orang yang panik pada pukul tiga pagi.
Template README Pipeline #
Kelimanya paling praktis ditampung dalam satu berkas README.md di akar direktori pipeline. Template berikut memakai domain toko online kita dan merangkum konsep-konsep dari chapter-chapter sebelumnya: checkpoint, status run, dan smoke test.
# Pipeline pesanan-harian
Pipeline batch harian toko online: membaca ekspor pesanan dari sistem kasir
(berkas JSONL), menghitung kolom `total`, lalu memuat hasilnya ke tabel
`pesanan` di basisdata `toko.db` (SQLite).
## Sumber
- Sistem kasir cabang, berupa ekspor JSONL harian di `/data/kasir/`,
tersedia pukul 01:00.
- Kontrak input: `pesanan_id` (integer), `waktu` (ISO 8601), `pelanggan`,
`produk`, `qty` (integer > 0), `harga` (integer > 0), `diskon`
(opsional, default 0).
## Kontrak output
Tabel `pesanan` di `toko.db`. Muatan memakai `INSERT OR REPLACE` berdasarkan
`pesanan_id`, sehingga pipeline aman dijalankan ulang (idempotent). Daftar
kolom, tipe, dan artinya ada di `kamus_data.json`.
## Jadwal
- Setiap hari pukul 02:00 WIB melalui cron: `0 2 * * *`.
- Durasi normal 3 menit, batas waktu 15 menit.
## Owner
Tim Data Platform. Kanal `#data-platform`, jadwal on-call bergiliran
mingguan, eskalasi setelah dua kegagalan beruntun.
## Runbook kegagalan
1. Periksa tabel `status_run` di `toko.db`, cari baris berstatus `gagal`
terbaru; kolom `partisi_terpengaruh` menunjukkan partisi bermasalah.
2. Baca kolom `pesan`. Galat parsing JSON berarti berkas sumber rusak:
minta ekspor ulang ke tim kasir, lalu jalankan ulang pipeline.
3. Pipeline aman dijalankan ulang: partisi bercheckpoint `selesai` akan
divalidasi lalu dilewati.
4. Bila gagal dua kali berturut-turut, eskalasi ke on-call berikutnya dan
beri tahu pemilik dashboard bahwa data hari itu tertunda.
## Perintah
```bash
python3 pipeline.py # jalankan pipeline
python3 cek_dokumentasi.py # verifikasi kamus data terhadap skema nyata
```
## Smoke test setelah load
```sql
-- Harus mengembalikan jumlah baris > 0 dan total > 0 setelah run sukses.
SELECT COUNT(*), SUM(total) FROM pesanan;
```Data Dictionary #
README menjelaskan pipeline dari sudut pandang operator; data dictionary menjelaskan isi tabel dari sudut pandang pemakai data. Untuk tabel pesanan, kamusnya terlihat seperti ini:
| Kolom | Tipe | Deskripsi | Contoh |
|---|---|---|---|
pesanan_id | INTEGER | ID unik pesanan dari sistem kasir, kunci utama | 1 |
waktu | TEXT | Waktu pesanan dibuat, format ISO 8601 | 2026-02-01T09:10:00 |
pelanggan | TEXT | Nama pelanggan pembeli | Budi |
produk | TEXT | Nama produk yang dibeli | Kemeja Batik |
qty | INTEGER | Jumlah unit yang dibeli, selalu lebih dari 0 | 2 |
harga | INTEGER | Harga satuan produk dalam Rupiah | 240000 |
total | INTEGER | Hasil perhitungan qty x harga dikurangi diskon | 460000 |
Kamus yang sama sebaiknya disimpan juga dalam bentuk yang bisa dibaca mesin — contoh kita memakai kamus_data.json dengan satu entri per kolom berisi nama, tipe, deskripsi, dan contoh. Bentuk JSON membuat kamus bisa diperiksa otomatis, dan di ekosistem yang lebih besar, dbt menyediakan tempat khusus untuk deskripsi kolom serta pengujian kontrak model — rinciannya ada di dokumentasi dbt
.
Dokumentasi yang Bisa Diverifikasi #
Dokumentasi usang lebih menyesatkan daripada tidak ada dokumentasi: runbook yang menyebut tabel yang sudah berganti nama mengarahkan on-call ke jalan buntu. Penawarnya adalah verifikasi otomatis. Skrip berikut membaca kamus_data.json, membandingkannya dengan skema nyata lewat PRAGMA table_info — rujukan resmi ada di dokumentasi SQLite
— lalu menjalankan smoke query yang dijanjikan README.
"""Verifikasi dokumentasi: bandingkan kamus_data.json dengan skema nyata."""
import json
import sqlite3
from pathlib import Path
DB = Path("toko.db")
if DB.exists():
DB.unlink()
# Siapkan output pipeline persis seperti yang didokumentasikan README.
koneksi = sqlite3.connect(DB)
koneksi.executescript("""
CREATE TABLE pesanan (
pesanan_id INTEGER PRIMARY KEY, waktu TEXT NOT NULL,
pelanggan TEXT NOT NULL, produk TEXT NOT NULL,
qty INTEGER NOT NULL, harga INTEGER NOT NULL, total INTEGER NOT NULL);
""")
koneksi.executemany(
"INSERT INTO pesanan VALUES (?, ?, ?, ?, ?, ?, ?)",
[(1, "2026-02-01T09:10:00", "Budi", "Kemeja Batik", 2, 240000, 460000),
(2, "2026-02-01T11:30:00", "Siti", "Kain Songket", 1, 850000, 850000),
(3, "2026-02-01T15:45:00", "Dewi", "Rok Tenun", 1, 310000, 310000)])
koneksi.commit()
kamus = json.loads(Path("kamus_data.json").read_text())
print(f"=== Memuat kamus data: {kamus['basisdata']}.{kamus['tabel']} ===")
terdokumentasi = {kolom["nama"]: kolom["tipe"] for kolom in kamus["kolom"]}
print(f" {len(terdokumentasi)} kolom terdokumentasi")
print("=== Membandingkan dengan skema nyata (PRAGMA table_info) ===")
di_db = {baris[1]: baris[2].upper() for baris in koneksi.execute(
f"PRAGMA table_info({kamus['tabel']})")}
cocok = 0
for nama, tipe in terdokumentasi.items():
if nama not in di_db:
print(f" {nama} {tipe}: HILANG di basisdata")
elif di_db[nama] != tipe:
print(f" {nama}: BEDA (dok: {tipe}, db: {di_db[nama]})")
else:
print(f" {nama} {tipe}: cocok")
cocok += 1
tak_terdokumentasi = [nama for nama in di_db if nama not in terdokumentasi]
for nama in tak_terdokumentasi:
print(f" {nama} {di_db[nama]}: TIDAK ADA di kamus")
print(f" hasil: {cocok}/{len(terdokumentasi)} kolom cocok, "
f"{len(tak_terdokumentasi)} kolom tak terdokumentasi")
print("=== Smoke query dari README ===")
(jumlah, total) = koneksi.execute(
"SELECT COUNT(*), SUM(total) FROM pesanan").fetchone()
print(f" SELECT COUNT(*), SUM(total) FROM pesanan -> ({jumlah}, {total})")
if jumlah > 0 and total > 0:
print(" sesuai kontrak README: jumlah baris > 0 dan total > 0")
else:
print(" TIDAK SESUAI kontrak README")
koneksi.close()Output yang Dihasilkan:
=== Memuat kamus data: toko.db.pesanan ===
7 kolom terdokumentasi
=== Membandingkan dengan skema nyata (PRAGMA table_info) ===
pesanan_id INTEGER: cocok
waktu TEXT: cocok
pelanggan TEXT: cocok
produk TEXT: cocok
qty INTEGER: cocok
harga INTEGER: cocok
total INTEGER: cocok
hasil: 7/7 kolom cocok, 0 kolom tak terdokumentasi
=== Smoke query dari README ===
SELECT COUNT(*), SUM(total) FROM pesanan -> (3, 1620000)
sesuai kontrak README: jumlah baris > 0 dan total > 0Mari kita telaah baris-baris kode di atas:
- Baris 10-23 (penyiapan): bagian ini menyiapkan basisdata persis seperti kontrak README. Pada pemakaian nyata, bagian tersebut digantikan oleh pipeline itu sendiri — skrip verifikasi cukup membuka basisdata hasil run terakhir.
- Baris 25-28 (memuat kamus): kamus dibaca dari JSON, bukan disalin manual ke dalam skrip, sehingga sumber kebenarannya tunggal.
- Baris 31-32 (
PRAGMA table_info): SQLite melaporkan skema nyata tabel — nama dan tipe setiap kolom — tanpa perlu membuka berkas definisi tabel. - Baris 34-47 (perbandingan tiga arah): kolom yang hilang di basisdata, kolom yang tipe-nya berbeda, dan kolom yang ada di basisdata namun tidak terdokumentasi — ketiganya dilaporkan eksplisit, bukan hanya yang cocok.
- Baris 49-56 (smoke query): janji README diuji langsung — query yang sama dengan yang tertulis di dokumentasi benar-benar dijalankan dan hasilnya diperiksa terhadap klaim kontrak.
Skrip seperti ini layak masuk ke test suite dari Chapter Pipeline Testing dan dijalankan setiap kali kode berubah. Begitu seorang engineer menambah kolom baru tanpa memperbarui kamus, verifikasi gagal keras — dokumentasi tidak bisa kedaluwarsa dalam diam.
Dengan dokumentasi, pipeline kita akhirnya lengkap: dirancang sadar, diuji bertingkat, mampu pulih, berversi jelas, reusable, terukur performanya, dan bisa dioperasikan orang lain. Perjalanan kursus ini berlanjut ke lapisan di bawahnya — infrastruktur yang menjalankan data dalam skala besar — dimulai dari Chapter Dasar Distributed Systems.