Observability in Distributed Systems — System Design

Observability di Distributed Systems Di monolith, debugging = buka satu log file. Di distributed system, request melintasi 10+ service. Observability adalah…

Observability adalah kemampuan menjawab pertanyaan baru tentang sistem yang sedang jalan hanya dari output-nya (metrics, traces, logs), tanpa menambah kode atau deploy ulang. Di distributed system ini penting, karena satu request melewati banyak service dan masalahnya sering muncul di tempat yang gak kamu duga. Bayangin jasa ekspedisi. Dashboard kantor pusat bilang "paket terlambat naik sejak jam 3 sore": itu metric. Lacak resi satu paket menunjukkan ia nyangkut 2 hari di gudang transit: itu trace. Catatan kurir bilang "gudang transit tutup, dialihkan": itu log. Yang menyambungkan ketiganya adalah nomor resi, dan di software namanya trace_id.

🧪 Tebakan cepat: mulai jam 19:30:30, p99 checkout TiketNada melompat dari 260-an ms ke 2.258 ms, tapi p50 tetap sekitar 180 ms. Berapa bagian pembeli yang kena: hampir semua, sekitar seperempat, atau cuma 1 dari 100? Tebak dulu, jawabannya ada di bagian "Pecah per field: siapa yang lambat".

Tiga sinyal, tiga pertanyaan

Lesson Monitoring & Logging sudah mengenalkan tiga pilar. Kuncinya: tiap sinyal menjawab pertanyaan yang berbeda.

Sinyal Menjawab Di kasus ini
Metrics apakah ada masalah? p99 checkout naik
Traces di mana masalahnya? span bayar yang menunggu provider
Logs kenapa terjadi? provider timeout, coba lagi

Ketiganya baru berguna bersama kalau disambung trace_id yang sama: dibawa header traceparent dari gateway ke tiap service. Cara memasangnya ada di lesson OpenTelemetry Fundamentals dan Distributed Tracing (track DevOps). Lesson ini tentang memakainya untuk menyelidiki.

Metric: lonjakan jam 19:30:30

Model contoh: satu menit checkout TiketNada, 19:30:00 sampai 19:31:00, 4 checkout per detik, 240 total. Metric checkout_duration_seconds per 10 detik:

Jam p50 p99
19:30:00 200 ms 267 ms
19:30:10 180 ms 273 ms
19:30:20 182 ms 260 ms
19:30:30 183 ms 2.258 ms
19:30:40 187 ms 2.272 ms
19:30:50 157 ms 2.253 ms

p50 tetap, p99 melompat hampir 9 kali. Artinya sebagian pembeli lambat sekali, sebagian besar normal. Metric menjawab "ada masalah", tapi belum bilang siapa yang kena dan kenapa.

Pecah per field: siapa yang lambat

Tiap checkout tercatat sebagai kejadian lengkap dengan field-nya: trace_id, user_id, region, metode bayar, dan versi app. Jadi kamu bisa memecah 120 checkout di jendela lonjakan (19:30:30 sampai 19:31:00) per field apa pun:

Dipecah per Hasil
region jakarta, surabaya, medan, makassar: semua p99 di atas 2.200 ms. Bukan pembedanya.
versi app keempat versi juga di atas 2.200 ms. Bukan pembedanya.
metode ewallet: 33 checkout, p50 2.240 ms, p99 2.272 ms; kartu p99 219 ms; va p99 150 ms

Jawaban tebakannya: sekitar seperempat. 33 dari 120 checkout (27,5%) lambat, dan semuanya pembayaran e-wallet. Itulah bedanya dengan monitoring: monitoring adalah dashboard dan alert yang disiapkan untuk masalah yang sudah kamu duga ("CPU naik? alert"). Observability adalah bisa mengajukan pertanyaan baru, misalnya "kenapa pembeli e-wallet lambat tapi pembeli kartu gak?", tanpa deploy ulang, karena data yang dibutuhkan sudah ada di tiap kejadian.

// 8 checkout pertama dari lonjakan 19:30:30 (data Lab)
const checkout = [
  { user: "u-7914", region: "jakarta", metode: "kartu", ms: 163 },
  { user: "u-3032", region: "surabaya", metode: "kartu", ms: 185 },
  { user: "u-5029", region: "jakarta", metode: "va", ms: 118 },
  { user: "u-8711", region: "surabaya", metode: "va", ms: 135 },
  { user: "u-8179", region: "jakarta", metode: "ewallet", ms: 2218 },
  { user: "u-9074", region: "jakarta", metode: "va", ms: 129 },
  { user: "u-7756", region: "jakarta", metode: "ewallet", ms: 2244 },
  { user: "u-8944", region: "surabaya", metode: "ewallet", ms: 2223 },
];

// persentil nearest rank: urutkan, ambil urutan ke ceil(p% × n)
const persen = (xs, p) => [...xs].sort((a, b) => a - b)[Math.ceil((p * xs.length) / 100) - 1];

function pecah(data, field) {
  const grup = new Map();
  for (const c of data) grup.set(c[field], [...(grup.get(c[field]) || []), c.ms]);
  for (const [nilai, xs] of grup) {
    console.log(field, nilai, "n", xs.length, "p50", persen(xs, 50), "p99", persen(xs, 99));
  }
}

pecah(checkout, "region"); // jakarta p99 2244, surabaya p99 2223: sama-sama lambat
pecah(checkout, "metode"); // kartu p99 185, va p99 135, ewallet p99 2244

High-cardinality: field di kejadian, bukan di label

Kenapa gak langsung pasang semua field itu sebagai label metric? Karena tiap nilai label jadi time series sendiri. Histogram checkout_duration_seconds di sini punya 7 bucket plus _sum dan _count, jadi 9 series per kombinasi label. Label metode (3 nilai) = 27 series, murah. Label user_id dengan 1.000.000 pengguna aktif harian = 9.000.000 series, dan penyimpanan metric meledak. Field yang nilainya sangat banyak disebut high-cardinality.

Aturannya: metric tetap kecil dengan label sedikit. Field high-cardinality seperti user_id dan trace_id ditaruh di span dan log, yang memang dicari per kejadian. Di sana field itu justru paling berharga: "kenapa checkout u-8177 lambat?" langsung terjawab.

Exemplar: dari titik di grafik ke satu trace

Bagaimana pindah dari batang p99 yang merah ke satu request yang sungguhan lambat? Lewat exemplar: contoh satu observasi yang ditempel ke bucket histogram, lengkap dengan trace_id-nya. Di format OpenMetrics (yang dibaca Prometheus), bentuknya:

checkout_duration_seconds_bucket{le="2.5"} 240 # {trace_id="5dffd064889e792b4ba982466d9637c9"} 2.253

Grafana menggambarnya sebagai titik di atas grafik. Klik titiknya, dan trace-nya terbuka:

Span Mulai Durasi
gateway POST /api/orders 0 ms 2.253 ms
akun: cek login 2 ms 6 ms
konser: TahanKursi 10 ms 14 ms
order: simpan pesanan 26 ms 13 ms
bayar: PotongKartu 41 ms 2.210 ms
dompetku POST /charge (percobaan 1) 41 ms 2.000 ms, timeout
dompetku POST /charge (percobaan 2) 2.041 ms 210 ms, 200

Trace menjawab di mana: dari 2.253 ms, 2.000 ms habis menunggu provider e-wallet dompetku di percobaan pertama. Pecah per field memberi tahu siapa yang kena, trace menunjukkan service mana yang membuatnya lambat.

Log dengan trace_id yang sama

Sekarang cari semua log dari semua service dengan trace_id itu. Dari ratusan baris, tersisa empat (skema dari lesson Structured Logging Patterns):

{"ts":"2026-10-09T19:30:57.250+07:00","level":"info","service":"order","trace_id":"5dffd064889e792b4ba982466d9637c9","user_id":"u-8177","metode":"ewallet","msg":"checkout dimulai"}
{"ts":"2026-10-09T19:30:59.291+07:00","level":"warn","service":"bayar","trace_id":"5dffd064889e792b4ba982466d9637c9","provider":"dompetku","attempt":1,"timeout_ms":2000,"msg":"provider timeout, coba lagi"}
{"ts":"2026-10-09T19:30:59.501+07:00","level":"info","service":"bayar","trace_id":"5dffd064889e792b4ba982466d9637c9","attempt":2,"duration_ms":210,"msg":"charge sukses"}
{"ts":"2026-10-09T19:30:59.503+07:00","level":"info","service":"order","trace_id":"5dffd064889e792b4ba982466d9637c9","status":201,"duration_ms":2253,"msg":"checkout selesai"}

Baris warn menjawab kenapa: dompetku gak menjawab percobaan pertama, bayar menunggu sampai timeout_ms 2000, lalu mencoba lagi dan sukses. Langkah berikutnya jelas: hubungi dompetku, dan pertimbangkan timeout yang lebih pendek atau circuit breaker (lesson Resilience Patterns).

RED untuk service, USE untuk resource

Untuk memasang alert, ada dua resep:

Jadi API order dipantau dengan RED, disk di node PostgreSQL dengan USE. Detail tiap angka ada di lesson Metrics & Monitoring.

🎯 SLO dan error budget

SLO (Service Level Objective) adalah target internal, misalnya "99% checkout selesai di bawah 1 detik, dihitung per 30 hari". Sisa 1%-nya disebut error budget: jatah yang boleh gagal. Di lonjakan tadi, 27,5% checkout melewati 1 detik, jadi budget-nya terbakar sekitar 27 kali lebih cepat dari jatahnya (burn rate 27,5). Alert pada burn rate berbunyi dalam hitungan menit. Alert pada CPU gak akan berbunyi: bayar cuma menunggu jaringan, CPU-nya santai.

⚠️ Jebakan yang sering ketemu

TL;DR: Observability = bisa menjawab pertanyaan baru tentang sistem dari output-nya tanpa deploy ulang; monitoring = dashboard dan alert untuk masalah yang sudah diduga. Metrics menjawab apakah ada masalah, traces di mana, logs kenapa, dan trace_id menyambungkan ketiganya. Di TiketNada, p99 checkout melompat ke 2.258 ms jam 19:30:30 sementara p50 tetap 180-an ms; dipecah per metode, cuma ewallet yang lambat (33 dari 120, 27,5%). Field high-cardinality (user_id, trace_id) disimpan di span dan log, bukan label metric (1.000.000 user × 9 series = 9.000.000 time series). Exemplar menempelkan trace_id ke bucket histogram, sehingga satu klik membuka trace: 2.000 ms habis menunggu dompetku sampai timeout. Log dengan trace_id yang sama menjelaskan kenapa. RED untuk service, USE untuk resource, dan alert pada SLO serta burn rate, bukan CPU.

Yang akan kamu pelajari