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.

markdown
# 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:

KolomTipeDeskripsiContoh
pesanan_idINTEGERID unik pesanan dari sistem kasir, kunci utama1
waktuTEXTWaktu pesanan dibuat, format ISO 86012026-02-01T09:10:00
pelangganTEXTNama pelanggan pembeliBudi
produkTEXTNama produk yang dibeliKemeja Batik
qtyINTEGERJumlah unit yang dibeli, selalu lebih dari 02
hargaINTEGERHarga satuan produk dalam Rupiah240000
totalINTEGERHasil perhitungan qty x harga dikurangi diskon460000

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.

python
"""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:

text
=== 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 > 0

Mari kita telaah baris-baris kode di atas:

  1. 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.
  2. Baris 25-28 (memuat kamus): kamus dibaca dari JSON, bukan disalin manual ke dalam skrip, sehingga sumber kebenarannya tunggal.
  3. Baris 31-32 (PRAGMA table_info): SQLite melaporkan skema nyata tabel — nama dan tipe setiap kolom — tanpa perlu membuka berkas definisi tabel.
  4. 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.
  5. 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.