โ† semua materi
05:00 Arsitektur ยท 30 Oct 2026 ยท 05:02

๐Ÿ“š Topik: Observability โ€” metric vs log vs trace, dan masalah cardinality

๐ŸŽฏ Tujuan Belajar Setelah sesi ini, keputusan ini jadi lebih baik: - Pilih sinyal mana (metric/log/trace) untuk pertanyaan operasional mana โ€” bukan "pasang semua". - Desain label/nama metric yang tidak meledakkan biaya storage & query. - Tentukan batas antara agregasi di aplikasi vs di backend observability. - Hitung konsekuensi sampling sebelum memilih head vs tail sampling.


๐Ÿง  Penjelasan Inti

Observability bukan "punya dashboard". Tiga sinyal punya bentuk data berbeda, dan bentuk itu menentukan pertanyaan apa yang bisa dijawab.

Sinyal Bentuk Kuat untuk Lemah untuk
Metric agregat numerik per interval, berdimensi terbatas deteksi & alerting, trend, SLO, kapasitas sebab per-request, nilai individual
Log event diskrit, teks/JSON, per-request detail konteks, audit, error spesifik agregasi lintas volume besar, biaya per byte tinggi
Trace DAG span dengan parent-child, per-request latency breakdown, dependency, titik kegagalan lintas service akurasi statistik kalau di-sample, biaya kalau 100%

Alasan metric murah: pre-aggregation. Aplikasi menghitung counter/histogram, backend hanya simpan hasil. 10.000 request jadi 1 titik data per 15 detik. Log/trace sebaliknya: satu baris per kejadian, biaya linear terhadap traffic.

Masalah yang diselesaikan metric: "apakah sistem sehat sekarang" harus dijawab dalam milidetik dari data kecil. Log/trace menjawab "kenapa tepatnya request X gagal" โ€” pertanyaan yang frekuensinya rendah tapi nilainya tinggi saat insiden.

Kesalahan struktural paling umum: memakai log untuk pertanyaan agregat ("berapa p99 latency per endpoint per jam") โ†’ query scan miliaran baris, mahal dan lambat. Atau memakai metric untuk pertanyaan per-entitas ("kenapa user 8123 lambat") โ†’ itu mendorong label user_id, dan di situlah cardinality meledak.


โš™๏ธ Cara Kerja Internal

1. Metric pipeline - Instrumentasi menghasilkan instrument: Counter (monoton naik), Gauge (naik-turun), Histogram (distribusi), UpDownCounter. - SDK melakukan agregasi lokal per (nama metric, atribut) lalu ekspor periodik (pull via scrape, atau push OTLP). - Time series identity = nama metric + set label. Kardinalitas = jumlah kombinasi label unik yang pernah aktif.

2. Histogram vs Summary (keputusan yang sering salah) - Histogram: bucket batas tetap, dapat diagregasi lintas instance (bucket dijumlahkan). p99 dihitung di query (histogram_quantile), hasilnya aproksimasi terhadap batas bucket. - Summary: kuantil dihitung di klien. Tidak bisa diagregasi โ€” rata-rata p99 dari 10 pod bukan p99 sistem. Ini sumber dashboard bohong. - Native histogram (Prometheus โ‰ฅ v3.8.0 stable; sebelumnya feature flag --enable-feature=native-histograms sejak v2.40) memperbaiki dua kelemahan klasik histogram: bucket harus didefinisikan di muka, dan satu bucket = satu series. Native histogram = satu sample terstruktur, bucket sparse, resolusi dinamis, cakupan penuh float64. Di v3.8 scraping tetap harus diaktifkan via scrape_native_histograms; v3.9+ feature flag jadi no-op; v4 kedua setting default true. Padanan di OpenTelemetry: ExponentialHistogram, base = 2**(2**(-scale)).

3. Log pipeline - Dari "teks bebas" ke "structured event" adalah lompatan kualitas terbesar. log.info("failed") tidak bisa difilter; {"event":"payment_failed","order_id":...,"gateway":"x","latency_ms":842,"err":"timeout"} bisa. - Backend modern mengindeks subset field (biasanya yang low-cardinality) dan menyimpan sisanya sebagai blob terkompresi. Query pada field tak terindeks = scan.

4. Trace pipeline - Span = (trace_id, span_id, parent_span_id, start, duration, atribut, status). - Propagasi konteks lintas proses (W3C traceparent) yang membuat trace berguna โ€” tanpa propagasi, tiap service menghasilkan trace terpisah yang tak tersambung. - Sampling: head sampling (putuskan di awal, murah, buta terhadap hasil), tail sampling (buffer semua span, putuskan setelah trace selesai, bisa simpan yang error/lambat, butuh memory besar di collector). - Exemplar menautkan metric โ†’ trace: satu sample histogram membawa trace_id dari request nyata. Ini jembatan "grafik naik" โ†’ "request mana".

5. Cardinality โ€” mekanika biaya Series aktif disimpan di head block (in-memory + WAL), di-flush ke blok persisten. Biaya naik di: RAM, ukuran index, waktu query, waktu kompaksi. Rule of thumb kasar: orde kilobyte per series aktif di head (bukan angka presisi โ€” tergantung label & sample rate). 1 juta series aktif = kelas RAM puluhan GB.

Hitungan contoh (DIKETAHUI, aritmetika):

http_requests_total{method, route, status, instance, version}
5 ร— 200 ร— 15 ร— 20 ร— 3 = 900.000 series

Tambah user_id (100.000 user): 900.000 ร— 100.000 = 9ร—10ยนโฐ. Sistem mati, bukan lambat.

Sumber ledakan nyata: - ID unik sebagai label: user_id, request_id, trace_id, session_id, order_id. - URL mentah sebagai label (/user/8123/orders) โ†’ pakai route template /user/:id/orders. - Pesan error sebagai label (err="dial tcp 10.0.3.7:5432: timeout"). - Status code granular (semua 4xx/5xx individual) โ†’ biasanya cukup status_class. - Path file, versi build, pod name (pod name = high churn, series mati terus tiap deploy).


๐Ÿ—๏ธ Diagram / Pseudocode

Metric (agregasi hilang detail, murah)
  request โ†’ counter++ โ†’ [15s] โ†’ 1 titik โ†’ TSDB
             โ”‚
             โ””โ”€ exemplar(trace_id) โ”€โ”€โ”
                                     โ–ผ
Trace (per-request, biaya per span)  span: GET /checkout 842ms
  request โ†’ span(root) โ†’ span(db) 610ms  โ† bottleneck ketemu
                          span(http) 200ms
Log (per-request, biaya per byte)
  request โ†’ {"trace_id":"abc","event":"db_slow","ms":610}

Alur investigasi yang benar:

1. SLO breach?            โ†’ metric (SLO burn rate)
2. Endpoint mana?         โ†’ metric (per-route histogram)
3. Request mana?          โ†’ exemplar โ†’ trace_id
4. Kenapa?                โ†’ trace (span terlambat)
5. Detail payload?        โ†’ log difilter trace_id

Tiap langkah mempersempit. Kalau langkah 1 butuh log, desain sinyalnya salah.

Pseudocode desain label (batas kardinalitas eksplisit):

labels:
  route    = route_template      # bukan path mentah
  status   = status_class        # 2xx/4xx/5xx, bukan 15 nilai
  version  = major.minor         # bukan git sha lengkap
FORBIDDEN sebagai label: user_id, request_id, trace_id, err_msg
โ†’ taruh di log/trace, bukan metric

โš–๏ธ Trade-off & Kapan JANGAN dipakai

Metric - JANGAN pakai untuk: analisis per-entitas, audit trail, debugging payload. Label high-cardinality di metric = anti-pattern struktural, bukan sekadar mahal. - JANGAN pakai Summary kalau butuh agregasi lintas instance. Kalau tidak yakin butuh kuantil global โ†’ Histogram. - Batas: agregasi membuang detail permanen. Tidak bisa "zoom in" setelah data diagregasi.

Log - JANGAN jadikan log sebagai sumber metric ("kita hitung error dari log saja"). Bisa, tapi biayanya 10โ€“100ร— lebih besar per pertanyaan yang sama, dan latency query tinggi. - JANGAN log 100% request body / response body di produksi. PII + biaya. - Logging sinkron di hot path (I/O blocking) menambah latency nyata; buffered async logging mengorbankan durability saat crash. Ini trade-off, bukan pilihan bebas.

Trace - JANGAN 100% sampling kalau volume tinggi โ€” biaya collector & storage meledak. - Head sampling 1%: p99 dari sampel 1% bias terhadap error langka, karena error sering bukan sampel acak yang tersebar merata. - Tail sampling menyelesaikan itu tapi butuh buffer seluruh trace di collector: memory โˆ (trace/s ร— durasi trace). Trace dengan span banyak memperburuk. - Sampling rate tetap juga menyembunyikan kejadian langka: 0,001% error pada 10 rps โ‰ˆ 1 kejadian/3 jam โ€” sampel 1% bisa tak pernah menangkapnya. - JANGAN sampling tanpa aturan "selalu simpan": error, latency > threshold, request tertentu (canary/tenant besar).

Kapan menunda observability mahal Sistem dengan traffic < ~10 rps dan < 5 service: metric dasar + structured log + trace_id di log sudah cukup. Distributed tracing penuh baru bernilai saat ada >3 hop jaringan. Menambah trace backend sebelum ada masalah lintas-service = biaya tanpa pertanyaan.


๐Ÿญ Contoh Produksi & Failure Mode

  1. Prometheus OOM karena label user_id. Tim menambah label untuk "debug satu user". Traffic naik โ†’ series naik linear terhadap user aktif โ†’ head block melampaui RAM โ†’ OOM loop. Pemulihan butuh drop label + restart + kehilangan data selama WAL replay. Pencegahan: metric_relabel_configs drop label, atau batas kardinalitas di SDK.

  2. Summary tidak bisa diagregasi. Dashboard menghitung avg(rate(http_duration{quantile="0.99"}[5m])) โ†’ angka terlihat bagus sementara sebagian pod menderita. Insiden ditemukan dari user, bukan dashboard. Ini false confidence โ€” lebih berbahaya daripada tidak punya dashboard.

  3. Bucket histogram salah pilih. Bucket [0.1, 1, 10] detik untuk API yang p99-nya 300ms โ†’ 99% sample masuk bucket pertama, p99 tak bisa dibedakan. Native histogram menghapus masalah ini; histogram klasik tidak.

  4. Log volume sebagai biaya tersembunyi. Service menambah log.debug payload penuh untuk investigasi; lupa dimatikan. Volume naik 20ร—, biaya bulanan naik, retensi dipotong, dan justru log yang dibutuhkan saat insiden sudah terhapus karena retensi pendek.

  5. Trace tanpa propagasi. Hanya service edge yang instrumented; hop internal tak meneruskan traceparent. Trace terlihat sebagai satu span 900ms tanpa isi โ€” persis informasi yang sudah ada di metric, nol nilai tambah.

  6. Tail sampling collector jadi bottleneck. Semua trace di-buffer untuk keputusan tail; saat traffic spike, memory collector penuh โ†’ drop trace acak, termasuk yang error. Ironi: sampel hilang tepat saat paling dibutuhkan.

  7. Cardinality churn. Pod name / container ID sebagai label: tiap deploy menghasilkan set series baru, series lama tetap "aktif" sampai stale marker (~5 menit default), tapi index & query tetap membengkak. Churn tinggi = biaya tanpa pertumbuhan traffic.


โš ๏ธ Miskonsepsi Umum


โœ… Cek Pemahaman

  1. Dashboard butuh p99 latency global lintas 12 pod. Kenapa Summary salah dan Histogram benar? Apa yang berubah kalau memakai native histogram / OTel ExponentialHistogram?
  2. Traffic 500 rps, 40 endpoint, 6 region, 30 versi aktif. Hitung kardinalitas http_requests_total{route, region, version, status} dengan status = 12 nilai. Berapa kalau route diganti path mentah dengan 3.000 path unik?
  3. Kamu punya budget trace 2% dari total. Error rate 0,02%. Jelaskan kenapa head sampling 2% acak hampir pasti melewatkan error, dan rancang aturan sampling yang menjamin error tertangkap tanpa menaikkan budget.

๐Ÿ”‘ Poin Kunci


โžก๏ธ Eksplorasi Lanjut

๐Ÿ”— Sumber 1. Prometheus. "Native Histograms" โ€” prometheus.io/docs/specs/native_histograms/ (diakses 2026; stable sejak v3.8.0, scrape_native_histograms wajib v3.9+, default true di v4) 2. OpenTelemetry. "Metrics Data Model" โ€” opentelemetry.io/docs/specs/otel/metrics/data-model/ (ExponentialHistogram, base = 2**(2**(-scale)), temporal/spatial reaggregation) 3. OpenTelemetry. "Metrics SDK" โ€” opentelemetry.io/docs/specs/otel/metrics/sdk/

โš ๏ธ Catatan Angka "orde kilobyte per series di head" adalah rule of thumb, bukan spesifikasi โ€” bervariasi dengan jumlah label, sample rate, dan versi TSDB. Klaim versi Prometheus di atas terverifikasi dari dokumentasi resmi. Perilaku tail sampling & biaya collector dijelaskan secara mekanis; angka konkret bergantung implementasi (OTel Collector tailsamplingprocessor, vendor lain) โ€” BELUM diverifikasi untuk rilis spesifik.