Tracing #
Ketika sebuah request gagal atau lambat di aplikasi monolitik, kamu bisa lihat stack trace dan langsung tahu di mana masalahnya. Di arsitektur microservices, satu request dari user mungkin melewati 5, 10, atau 20 service berbeda — masing-masing berjalan di container berbeda, ditulis dalam bahasa berbeda, dikelola oleh tim berbeda. Stack trace satu service tidak cukup. Kamu perlu melihat perjalanan lengkap request tersebut dari ujung ke ujung. Itulah yang diselesaikan oleh distributed tracing.
Masalah yang Dipecahkan Tracing #
Request user: "Checkout gagal"
Tanpa Tracing:
→ Lihat log API Gateway: request masuk, response 500
→ Lihat log Order Service: order dibuat
→ Lihat log Payment Service: ada error
→ Lihat log Inventory Service: tidak ada error
→ Lihat log Notification Service: tidak dipanggil
Pertanyaan:
✗ Berapa lama di setiap service?
✗ Service mana yang paling lambat?
✗ Error di Payment Service terjadi setelah berapa ms dari awal?
✗ Apakah ada retry yang terjadi?
→ Harus korelasi manual dari banyak log berbeda
Dengan Distributed Tracing:
──── Trace: checkout request (total: 2.3 detik) ────
API Gateway [ 23ms ]
Order Service [ 120ms ]
├── Inventory Check [ 45ms ]
├── DB Write [ 60ms ]
└── Payment Service [ 2.1 DETIK ← BOTTLENECK! ]
├── Card Validation [ 120ms ]
├── Bank API Call [ 1.8 DETIK ← LAMBAT ]
└── DB Write [ 80ms ]
Langsung terlihat: Bank API Call memakan 1.8 detik dari total 2.3 detik
Konsep: Trace dan Span #
Trace:
→ Representasi perjalanan lengkap satu request dari ujung ke ujung
→ Punya Trace ID unik yang sama di semua service
→ Berisi kumpulan Span yang membentuk pohon (tree)
Span:
→ Unit kerja individual dalam sebuah trace
→ Merepresentasikan operasi tunggal (request ke service, query DB, dll)
→ Setiap span punya:
span_id: identifier unik span ini
parent_span_id: identifier span yang memanggil span ini
trace_id: identifier trace keseluruhan (sama untuk semua span)
service_name: service mana yang menghasilkan span ini
operation_name: nama operasi (misalnya: "GET /api/users")
start_time: kapan mulai
duration: berapa lama
status: OK / ERROR
attributes: metadata tambahan (http.method, db.statement, dll)
Contoh struktur Trace sebagai pohon:
Trace ID: abc123
[API Gateway: handle_request] 2300ms (root span)
├── [Order Service: create_order] 120ms
│ ├── [Inventory Service: check_stock] 45ms
│ └── [DB: INSERT orders] 60ms
└── [Payment Service: process_payment] 2100ms
├── [Card Validator: validate] 120ms
├── [Bank API: charge] 1800ms ← MASALAH DI SINI
└── [DB: INSERT transactions] 80ms
Context Propagation — Benang Merah antar Service #
Untuk menyambungkan span dari service yang berbeda menjadi satu trace, context harus diteruskan dari satu service ke service berikutnya.
Cara Context Propagation Bekerja:
API Gateway menerima request dari user
→ Generate Trace ID: "abc123", Span ID: "span-001"
→ Proses request, mulai catat span
API Gateway memanggil Order Service:
→ Tambahkan header ke HTTP request:
traceparent: 00-abc123-span-001-01
(format W3C Trace Context standar)
→ Order Service menerima header ini
Order Service:
→ Baca header: trace_id = "abc123", parent_span_id = "span-001"
→ Buat span baru dengan parent tersebut, span_id: "span-002"
→ Proses request
Order Service memanggil Payment Service:
→ Teruskan header dengan trace_id sama: "abc123"
→ Payment Service buat span baru sebagai child
Hasilnya: semua span dari semua service punya trace_id yang sama
→ Bisa dilihat sebagai satu pohon lengkap di tracing backend
Propagation format yang umum:
→ W3C Trace Context (standar, direkomendasikan)
→ B3 (Zipkin) — legacy tapi masih banyak digunakan
→ Jaeger format
OpenTelemetry otomatis menangani propagation ini
OpenTelemetry — Standar Industri #
OpenTelemetry (OTel) adalah project open-source yang menyediakan SDK, API, dan tooling untuk instrumentasi observabilitas — termasuk tracing — yang vendor-agnostic.
Kenapa OpenTelemetry penting:
Tanpa standar:
→ Pakai Datadog → harus pakai Datadog SDK
→ Pindah ke Jaeger → harus ganti semua instrumentasi
→ Setiap vendor punya SDK sendiri → vendor lock-in
Dengan OpenTelemetry:
→ Instrumentasi sekali dengan OTel SDK
→ Kirim ke backend mana pun:
Jaeger, Zipkin, Datadog, Grafana Tempo, AWS X-Ray, dll
→ Pindah backend = ganti konfigurasi exporter saja,
bukan ganti semua instrumentasi
OTel menyediakan:
✓ API: interface standar untuk emit telemetry
✓ SDK: implementasi yang bisa dikonfigurasi
✓ Auto-instrumentation: tanpa ubah kode (untuk framework umum)
✓ Collector: agent yang menerima, proses, dan forward telemetry
✓ Exporter: komponen untuk kirim ke backend tertentu
Auto-Instrumentation #
Auto-instrumentation otomatis menambahkan tracing ke library umum
tanpa perlu modifikasi kode:
Framework yang biasanya didukung auto-instrumentation:
✓ HTTP client/server (Flask, FastAPI, Express, Spring)
✓ Database client (SQLAlchemy, JDBC, pg driver)
✓ Message queue client (Kafka, RabbitMQ, SQS)
✓ Cache client (Redis, Memcached)
✓ gRPC
Cara mengaktifkan (Python sebagai contoh):
# Tanpa ubah kode aplikasi sama sekali:
opentelemetry-instrument python app.py
→ Secara otomatis:
Setiap request HTTP masuk = satu span
Setiap query database = satu span (child)
Setiap HTTP outbound call = satu span (child)
Context propagation di-handle otomatis
Manual instrumentation untuk custom spans:
from opentelemetry import trace
tracer = trace.get_tracer("my-service")
def process_payment(order_id, amount):
with tracer.start_as_current_span("process_payment") as span:
span.set_attribute("order_id", order_id)
span.set_attribute("amount", amount)
# ... logic
Cara Membaca Trace untuk Diagnosis #
Gantt chart trace — cara membaca:
Request mulai: T=0ms
───────────────────────────────────────────────────── T=2300ms
[API Gateway]───────────────────────────────────────────────
└──[Order Service]──────────────────
├──[Inventory]────
└──[DB Insert]──────
└──[Payment Service]──────────────────────────────────
├──[Card Validator]────────
├──[Bank API]─────────────────────────────────
└──[DB Insert]──────
Yang perlu diperhatikan:
1. Span terpanjang = kandidat bottleneck
Bank API: 1800ms dari 2300ms total = 78% waktu
2. Span yang overlap vs sequential:
Inventory Check dan DB Insert berjalan sequential di Order Service
→ Bisa jadi paralel? Pengoptimalan potensial
3. Error dalam trace:
Span dengan status ERROR ditampilkan dengan warna berbeda
Bisa ada error yang di-retry dan akhirnya sukses
→ Latency tinggi karena retry yang tidak terlihat dari log sukses akhir
4. Gap antar span:
[Service A] selesai → [Service B] baru mulai = ada gap
Gap = overhead serialization, network, atau antrian yang tersembunyi
Sampling — Tidak Perlu Trace Semua Request #
Tracing setiap request di sistem dengan ribuan request/detik
akan menghasilkan biaya yang sangat besar.
Sampling adalah solusi: hanya trace sebagian request.
Head-based Sampling (keputusan di awal):
→ Saat request mulai, putuskan: trace ini atau tidak
→ Misal: 10% request di-trace (sample rate 10%)
→ Sederhana dan efisien
Masalah: request yang di-skip bisa jadi yang bermasalah
Tail-based Sampling (keputusan di akhir):
→ Collect semua span dulu, keputusan setelah request selesai
→ "Jika trace ini punya error atau latensi > 1 detik → simpan"
→ "Jika trace normal → buang 95% diantaranya"
→ Pastikan trace yang interessant selalu disimpan
Lebih mahal (butuh buffer semua span), tapi jauh lebih berguna
Strategi yang umum dipakai:
✓ Selalu trace 100% request dengan ERROR atau status 5xx
✓ Selalu trace request yang latensinya di atas threshold
✓ Sample 1-5% request yang normal (untuk baseline)
✓ Tambahkan trace manual untuk fitur baru yang sedang di-monitor
Ringkasan #
- Distributed tracing memetakan perjalanan satu request melintasi banyak service — satu Trace ID yang sama hadir di semua span dari semua service yang terlibat.
- Trace = pohon span; span = unit kerja individual — setiap span punya parent, sehingga membentuk hierarki yang merepresentasikan urutan pemanggilan.
- Context propagation via HTTP header menyambungkan span antar service — format W3C Trace Context (
traceparent) adalah standar yang direkomendasikan.- OpenTelemetry adalah standar industri untuk instrumentasi yang vendor-agnostic — instrumentasi sekali, kirim ke backend mana pun.
- Auto-instrumentation menambah tracing tanpa ubah kode — framework HTTP, database client, dan message queue bisa di-trace secara otomatis.
- Tail-based sampling untuk memastikan trace bermasalah selalu disimpan — error dan request lambat selalu di-trace penuh, request normal di-sample sebagian kecil.