Senin pagi Siti membuka dashboard TokoKita dan menemukan angka penjualan berhenti di Jumat sore — padahal orkestrator menampilkan semua task berstatus sukses. Pipeline memang berjalan, tetapi tidak ada data baru yang sampai; kegagalan senyap seperti ini tidak mengirim pesan galat apa pun. Data freshness adalah dimensi kualitas yang menjawab persis masalah ini: berapa umur data terbaru di setiap tabel, dan apakah umur tersebut masih memenuhi janji layanan. Chapter ini menghitung freshness dari dua sinyal waktu yang berbeda, membandingkannya dengan SLA per tabel, dan membedakan kasus data terlambat dari kasus pipeline yang benar-benar berhenti.
Segar Itu Berapa Jam #
Tidak ada angka freshness yang benar untuk semua tabel — “segar” didefinisikan oleh kebutuhan konsumen. Tabel penjualan untuk operasional harian kehilangan nilai jauh lebih cepat daripada tabel katalog produk yang jarang berubah:
| Tabel | Konsumen dan Kebutuhan | SLA Freshness |
|---|---|---|
pesanan_harian | dashboard penjualan, dipantau pagi hari | maksimal 24 jam |
stok_gudang | peringatan stok menipis, keputusan siang hari | maksimal 6 jam |
katalog_produk | halaman produk, perubahan tidak mendesak | maksimal 48 jam |
Angka SLA inilah yang kita jumpai sebagai klausul sla_freshness_jam pada data contract di chapter sebelumnya — freshness yang diukur tanpa ambang hanyalah informasi, sedangkan freshness yang dibandingkan dengan SLA menjadi alarm.
Dua Sinyal Waktu: Umur Data dan Umur Berkas #
Freshness dapat dihitung dari dua sumber waktu yang berbeda. Umur data dihitung dari nilai timestamp terbaru di dalam data — selisih antara titik pemeriksaan dan MAX(dibuat_pada). Umur berkas dihitung dari waktu modifikasi berkas partisi (mtime) — selisih antara titik pemeriksaan dan terakhir kali pipeline menulis sesuatu.
Keduanya mendeteksi masalah yang berbeda. Umur data yang tua dengan umur berkas yang muda berarti berkas baru saja ditulis tetapi isinya lama — gejala backfill atau data yang tertahan di upstream lalu tiba sekaligus. Umur berkas yang tua berarti pipeline sama sekali tidak menulis — gejala jadwal gagal atau proses macet. Pembacaan ganda ini adalah saudara dekat konsep Event Time vs Processing Time pada pipeline streaming: waktu kejadian di data dan waktu pemrosesan oleh pipeline memang dua hal berbeda.
Pemeriksa Freshness untuk Berkas Partisi #
Skrip berikut memeriksa tiga tabel yang datanya tersimpan sebagai berkas partisi. Waktu tulis berkas diatur dengan os.utime supaya contoh deterministik; di produksi, mtime datang dari berkas sungguhan dan titik pemeriksaan memakai waktu saat ini:
"""Pemeriksa freshness tabel dari berkas partisi: umur data vs umur berkas vs SLA."""
import datetime as dt
import os
from pathlib import Path
SEKARANG = dt.datetime(2026, 10, 20, 9, 0)
# (tabel, SLA jam, [(berkas, timestamp data terbaru, waktu berkas ditulis)])
TABEL = [
("pesanan_harian", 24, [
("pesanan-2026-10-18.csv", dt.datetime(2026, 10, 18, 22, 3), dt.datetime(2026, 10, 19, 1, 55)),
("pesanan-2026-10-19.csv", dt.datetime(2026, 10, 19, 23, 58), dt.datetime(2026, 10, 20, 8, 40)),
]),
("stok_gudang", 6, [
("stok-2026-10-20.csv", dt.datetime(2026, 10, 20, 2, 0), dt.datetime(2026, 10, 20, 2, 15)),
]),
("katalog_produk", 48, [
("katalog-lengkap.csv", dt.datetime(2026, 10, 17, 4, 0), dt.datetime(2026, 10, 20, 8, 5)),
]),
]
folder = Path("partisi")
folder.mkdir(exist_ok=True)
for _, _, daftar in TABEL:
for nama, terbaru, ditulis in daftar:
berkas = folder / nama
berkas.write_text(f"id,diperbarui\n1,{terbaru.isoformat()}\n", encoding="utf-8")
os.utime(berkas, (ditulis.timestamp(), ditulis.timestamp()))
def umur_jam(waktu):
return (SEKARANG - waktu).total_seconds() / 3600
def cek_tabel(nama_tabel, sla, daftar):
data_terbaru = max(terbaru for _, terbaru, _ in daftar)
berkas_terbaru = max(
dt.datetime.fromtimestamp((folder / nama).stat().st_mtime) for nama, _, _ in daftar
)
umur_data, umur_berkas = umur_jam(data_terbaru), umur_jam(berkas_terbaru)
status = "SEGAR" if umur_data <= sla else "MELEBIHI SLA"
print(f"{nama_tabel:16}{umur_data:>9.1f}j{umur_berkas:>11.1f}j{sla:>6} {status}")
return umur_data, umur_berkas, status
print(f"{'tabel':16}{'umur_data':>11}{'umur_berkas':>13}{'SLA':>6} status")
for nama_tabel, sla, daftar in TABEL:
umur_data, umur_berkas, status = cek_tabel(nama_tabel, sla, daftar)
if status != "SEGAR":
catatan = ""
if umur_berkas <= 1:
catatan = " (berkas baru saja ditulis: cek backfill atau data terlambat)"
print(f" -> ALERT {nama_tabel}: umur data {umur_data:.1f} jam > SLA {sla} jam{catatan}")Output yang Dihasilkan:
tabel umur_data umur_berkas SLA status
pesanan_harian 9.0j 0.3j 24 SEGAR
stok_gudang 7.0j 6.8j 6 MELEBIHI SLA
-> ALERT stok_gudang: umur data 7.0 jam > SLA 6 jam
katalog_produk 77.0j 0.9j 48 MELEBIHI SLA
-> ALERT katalog_produk: umur data 77.0 jam > SLA 48 jam (berkas baru saja ditulis: cek backfill atau data terlambat)Mari kita telaah baris-baris kode di atas:
- Baris 9-20 (
TABEL): setiap entri membawa SLA dan daftar berkas beserta dua waktu — timestamp data terbaru di dalam berkas dan waktu berkas ditulis. - Baris 22-28 (penyiapan berkas): berkas partisi dibuat di folder lokal lalu
os.utimemengaturmtime-nya ke waktu tulis yang kita tentukan; tanpa langkah ini,mtimemengikuti jam eksekusi skrip. - Baris 36-39 (dua sinyal):
max()atas seluruh berkas partisi menentukan data terbaru dan berkas terbaru — sebuah tabel terdiri dari banyak partisi, jadi keduanya wajib dihitung lintas berkas. - Baris 41 (status): perbandingan memakai
<=— umur tepat sama dengan SLA masih dianggap segar; ambang batas selalu perlu keputusan inklusif atau eksklusif. - Baris 49-53 (alert bercatatan): alert diperkaya diagnosis awal — bila umur berkas di bawah satu jam sementara umur data tua, dugaan pertama adalah backfill atau keterlambatan upstream, bukan pipeline yang berhenti.
Dua alert pada output menceritakan dua penyakit berbeda: stok_gudang memiliki umur berkas 6,8 jam — pipeline stok memang tidak menulis apa pun sejak dini hari; katalog_produk justru ditulis 0,9 jam lalu tetapi isinya 77 jam — data lama tiba terlambat. Tanpa kolom umur berkas, kedua kasus ini terlihat sama dan investigasi salah arah.
Alur dari Pengukuran ke Tindakan #
Alur ini sederhana karena keputusannya memang biner per tabel; kerumitannya ada di pengelolaan ambang. SLA yang terlalu ketat menghasilkan alert palsu setiap akhir pekan, sedangkan SLA yang terlalu longgar membuat dashboard menipu — aturan praktisnya: mulai dari kebutuhan konsumen, lalu kencangkan berdasarkan pola operasional yang terukur. Di ekosistem tool, dbt bahkan menyediakan perintah source freshness bawaan dengan ambang warn_after dan error_after untuk kasus tabel yang dimuat dari sumber eksternal (dokumentasi di docs.getdbt.com
).
Freshness menjawab “apakah data masih baru” — tetapi data yang baru saja tiba belum tentu utuh. Partisi bisa hadir dengan separuh barisnya saja, dan kelengkapan itulah topik chapter berikutnya: Data Completeness.