Tracing #

Dalam pengembangan perangkat lunak monolitik tradisional, melacak kegagalan transaksi atau penurunan performa relatif mudah dilakukan karena seluruh eksekusi kode berjalan di dalam satu proses memori yang sama. Jika terjadi kesalahan, kita dapat dengan mudah membaca tumpukan memori (stack trace) untuk menemukan baris kode yang rusak. Namun, ketika kita beralih ke arsitektur layanan mikro (microservices), satu permintaan (request) sederhana dari pengguna (seperti menekan tombol “Checkout”) dapat melintasi 5, 10, hingga puluhan microservices independen yang berjalan di berbagai kontainer terpisah, ditulis dalam bahasa pemrograman berbeda, dan dikelola oleh tim pengembang yang terpisah pula. Stack trace dari satu layanan tidak akan pernah cukup untuk mendiagnosis masalah end-to-end. Kita membutuhkan teknologi Distributed Tracing (Runut Terdistribusi) untuk memetakan dan memvisualisasikan perjalanan lengkap request melintasi seluruh batas jaringan antar-layanan. Artikel ini akan membahas tantangan diagnosa microservices, konsep trace dan span, mekanisme propagasi konteks, serta pemanfaatan standar industri OpenTelemetry.

Tantangan Diagnosa pada Arsitektur Microservices #

Bayangkan skenario klasik di sebuah platform e-commerce: Pengguna melaporkan bahwa transaksi checkout mereka gagal secara berkala dengan error HTTP 500.

1. Skenario Tanpa Distributed Tracing #

Tanpa distributed tracing, tim engineer harus membuka sistem log terpusat dan berupaya menyatukan kepingan teka-teki secara manual:

  • Melihat log API Gateway: Request masuk, response 500 terkirim ke klien.
  • Melihat log Order Service: Berhasil membuat draf order.
  • Melihat log Payment Service: Mengembalikan status error koneksi API bank.
  • Masalah: Tanpa adanya pengenal unik yang mengikat semua log ini secara eksplisit, tim engineer terpaksa menebak-nebak kecocokan transaksi berdasarkan stempel waktu (timestamp) yang sering kali tidak sinkron presisinya di antara server-server yang berbeda. Kita tidak dapat menjawab dengan pasti: Berapa lama waktu tunggu di setiap service? Layanan mana yang menjadi bottleneck utama? Dan apakah ada operasi yang di-retry secara tidak efisien?

2. Skenario Dengan Distributed Tracing #

Distributed tracing menyatukan seluruh riwayat perjalanan panggilan API lintas jaringan tersebut menjadi satu diagram alir visual tunggal (Gantt Chart) yang memetakan hubungan hierarkis antar-layanan secara presisi dari ujung ke ujung.

flowchart TD
    subgraph TraceTree ["Trace ID: 4bf92f3577b34da6a3ce929d0e0e4736 (Total: 2.1s)"]
        GW["API Gateway (Span 1: 0 - 2.1s)"]
        Order["Order Service (Span 2: 10 - 250ms)"]
        Inventory["Inventory Check (Span 3: 20 - 110ms)"]
        Payment["Payment Service (Span 4: 250ms - 2.1s)"]
        BankAPI["Bank External API (Span 5: 300ms - 2.0s - BOTTLENECK)"]
        
        GW --> Order
        Order --> Inventory
        GW --> Payment
        Payment --> BankAPI
    end
    
    style BankAPI stroke:#d32f2f,stroke-width:2px

Konsep Fundamental: Trace dan Span #

Distributed tracing didasarkan pada dua konsep data logis utama:

1. Trace #

Trace adalah representasi alur kerja lengkap dari satu request tunggal yang berjalan melewati seluruh sistem terdistribusi kita. Trace bertindak sebagai wadah (container) yang memiliki pengenal unik global bernama Trace ID (sebuah string hexadecimal 16-byte). Seluruh layanan yang berpartisipasi dalam melayani request tersebut akan menandai datanya dengan Trace ID yang sama.

2. Span #

Span adalah unit kerja individual (blok bangunan) di dalam sebuah trace. Satu span merepresentasikan operasi kerja spesifik dengan waktu mulai (start_time) dan durasi eksekusi tertentu.

  • Contoh Span: Satu query SELECT ke database, satu panggilan HTTP client ke API pihak ketiga, atau proses verifikasi token JWT.

Anatomi Atribut di Dalam Span #

Nama Atribut Tipe Data Deskripsi Fungsi Contoh Nilai
trace_id String Hex (16-byte) Menghubungkan span ini ke satu trace induk global. 4bf92f3577b34da6a3ce929d0e0e4736
span_id String Hex (8-byte) Pengenal unik untuk span ini sendiri. 00f067aa0ba902b7
parent_span_id String Hex (8-byte) Menunjuk ke ID span yang memicu operasi ini (kosong untuk Root Span). 00f067aa0ba902b6
name String Nama operasi yang deskriptif. SELECT FROM users
attributes Key-Value Pairs Metadata kontekstual untuk analisis pencarian. http.status_code: 200, db.system: postgresql
status Enum Status hasil eksekusi (Unset, Ok, Error). Error

Context Propagation: Benang Merah Lintas Layanan #

Agar layanan hilir (downstream service) mengetahui bahwa mereka adalah bagian dari trace yang dimulai oleh layanan hulu (upstream service), kita harus mengirimkan metadata pelacakan melewati batas jaringan. Proses ini disebut Context Propagation (Propagasi Konteks).

Context propagation dilakukan dengan menyisipkan detail Trace ID dan Span ID ke dalam header protokol komunikasi (seperti HTTP Header atau metadata pesan gRPC).

sequenceDiagram
    autonumber
    participant GW as API Gateway
    participant Ord as Order Service
    
    Note over GW: 1. Terima HTTP Request dari Klien<br>Generate Trace ID: 4bf92f35...<br>Generate Span ID: 00f067aa...
    
    GW->>Ord: 2. Kirim HTTP POST /orders<br>Header: traceparent: 00-4bf92f35...-00f067aa...-01
    
    Note over Ord: 3. Ekstrak header traceparent<br>Warisi Trace ID: 4bf92f35...<br>Set Parent Span ID: 00f067aa...<br>Buat Span Baru: 8b2a1a8c...

Standar Format Header: W3C Trace Context #

Dahulu setiap vendor monitoring memiliki format header proprietary sendiri (seperti B3 dari Zipkin atau header X-Amzn-Trace-Id dari AWS). Saat ini, industri telah sepakat menggunakan standar W3C Trace Context dengan format header bernama traceparent:

Format traceparent:
00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
│  │                                │                └─ Trace Flags (e.g., 01 = Sampel Aktif)
│  │                                └─ Parent Span ID (8-byte hex)
│  └─ Trace ID (16-byte hex)
└─ Version (saat ini selalu 00)

Ketika Order Service menerima request HTTP dengan header traceparent tersebut, pustaka tracing di dalam Order Service akan mengekstrak nilai tersebut, menetapkannya sebagai parent span, dan meluncurkan span baru di bawah koordinat trace yang sama.


OpenTelemetry sebagai Standar Observabilitas Agnostik #

OpenTelemetry (OTel) menyediakan satu API dan SDK standar untuk menulis instrumentasi distributed tracing tanpa ketergantungan pada vendor monitoring tertentu (vendor-agnostic).

Komponen OpenTelemetry Tracing: #

  1. Tracer Provider: Pabrik (factory) yang mengelola siklus hidup pembuatan objek Tracer.
  2. Sampler: Menentukan apakah sebuah trace harus dicatat secara penuh atau dibuang untuk menghemat bandwidth penyimpanan.
  3. Span Processor: Menentukan bagaimana data span yang telah selesai diproses dikirimkan (misal: dikirimkan secara asinkron dalam kelompok batch).
  4. Exporter: Mengirimkan data span yang telah dikumpulkan ke database pusat (seperti Jaeger, Zipkin, Datadog, atau Grafana Tempo).

Berikut adalah contoh lengkap instrumentasi manual pembuatan span kustom menggunakan bahasa Go dengan OpenTelemetry SDK:

// Contoh instrumentasi manual span kustom di Go menggunakan OpenTelemetry SDK (BENAR)
package main

import (
	"context"
	"errors"
	"time"

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/codes"
)

var tracer = otel.Tracer("payment-processor-tracer")

func ProcessPayment(ctx context.Context, orderID string, amount float64) error {
	// 1. Mulai span baru di bawah context berjalan (otomatis mewarisi parent jika ada)
	ctx, span := tracer.Start(ctx, "ProcessPaymentTransaction")
	defer span.End() // ✓ BENAR: Pastikan span diakhiri saat fungsi selesai

	// 2. Set atribut metadata untuk kebutuhan pencarian di dashboard
	span.SetAttributes(
		attribute.String("payment.order_id", orderID),
		attribute.Float64("payment.amount", amount),
		attribute.String("payment.gateway", "Stripe"),
	)

	// Simulasi logika bisnis pemrosesan pembayaran
	err := executeBankTransaction(amount)
	if err != nil {
		// 3. Catat kesalahan ke dalam span jika operasi gagal
		span.RecordError(err)
		span.SetStatus(codes.Error, err.Error()) // Set status span menjadi ERROR
		return err
	}

	span.SetStatus(codes.Ok, "Pembayaran Berhasil")
	return nil
}

func executeBankTransaction(amount float64) error {
	time.Sleep(100 * time.Millisecond) // Simulasi network call
	if amount > 1000000 {
		return errors.New("limit_exceeded: Transaksi melebihi limit bank")
	}
	return nil
}

Analisis Gantt Chart Tracing untuk Mendiagnosis Bottleneck #

Setelah data trace dikumpulkan ke backend, data tersebut divisualisasikan dalam bentuk Gantt Chart. Saat melakukan investigasi performa lambat, kita harus mencari beberapa pola kritis berikut:

Visualisasi Gantt Chart (Masalah Bottleneck):
API-Gateway   [============================================================] (2.1s)
  Order-Service   [====] (120ms)
    DB-Insert         [==] (60ms)
  Payment-Service        [=================================================] (1.98s)
    Card-Validate        [==] (120ms)
    Bank-API-Call          [=============================================] (1.8s)  <-- BOTTLENECK!

1. Menemukan Jalur Kritis (Critical Path) & Bottleneck #

Carilah span terpanjang yang mendominasi sebagian besar durasi total root span. Pada contoh visualisasi di atas, total waktu transaksi adalah 2.1 detik. Kita dapat melihat bahwa panggilan ke API eksternal bank (Bank-API-Call) memakan waktu 1.8 detik (85% dari total waktu). Ini mengidentifikasi secara pasti bahwa bottleneck performa kita berada di sisi integrasi pihak ketiga, bukan pada database internal kita.

2. Panggilan Sekuensial vs Paralel #

Jika kita melihat ada dua span child (misal: Card-Validate dan Check-Account-Balance) yang berjalan secara berurutan (sequential), tanyakan pada tim arsitektur: “Apakah kedua operasi ini saling bergantung? Jika tidak, jalankan secara paralel menggunakan goroutine/async await untuk memotong durasi latensi total.”

3. Jeda Antar Span (Overhead Gap) #

Jika Span A selesai di milidetik ke-100, namun Span B baru mulai di milidetik ke-150 tanpa ada aktivitas di antaranya, ini mengindikasikan adanya jeda tersembunyi (gap overhead). Hal ini biasanya dipicu oleh overhead waktu deserialisasi payload JSON yang terlalu besar, atau waktu tunggu antrean (queue wait time) di server sebelum request diproses.


Strategi Sampling Metrik Tracing #

Mengirimkan 100% data tracing untuk aplikasi dengan kapasitas jutaan request per detik akan melipatgandakan biaya tagihan penyimpanan cloud kita secara drastis. Kita harus menerapkan metode Sampling (Penyaringan):

1. Head-based Sampling #

Keputusan apakah sebuah request akan di-trace atau dibuang diambil di awal alur kerja (misal di tingkat API Gateway) sebelum request diproses lebih lanjut.

  • Metode: Menggunakan modul probabilitas (misal: hanya rekam 5% dari total trafik secara acak).
  • Kelebihan: Sangat hemat biaya komputasi dan bandwidth jaringan.
  • Kekurangan: Jika terjadi error langka pada request yang tidak terpilih untuk di-trace, kita akan kehilangan data diagnosa tersebut.

2. Tail-based Sampling #

Seluruh span dikumpulkan terlebih dahulu ke dalam memori sementara (buffer) di tingkat kolektor (OpenTelemetry Collector). Keputusan untuk menyimpan atau membuang data trace baru diambil setelah seluruh request selesai dieksekusi secara utuh.

  • Aturan Kebijakan:
    • Jika trace berakhir dengan status ERROR (HTTP 5xx) ────> Simpan 100%.
    • Jika durasi trace melampaui 1.5 detik (lambat) ────> Simpan 100%.
    • Jika trace berjalan sukses dan cepat (normal) ────> Hanya simpan 1% (sebagai pembanding baseline).
  • Kelebihan: Menjamin kita selalu memiliki data diagnosa lengkap untuk setiap insiden error dan kelambatan, tanpa membuang biaya untuk menyimpan data transaksi normal yang tidak menarik.

Ringkasan #

  • Distributed tracing memetakan alur request end-to-end melintasi batas jaringan di dalam arsitektur microservices terdistribusi.
  • Trace bertindak sebagai pohon (tree) yang merangkai sekumpulan Span (unit kerja terkecil) menggunakan koordinasi Trace ID yang unik secara global.
  • Context propagation menggunakan HTTP header traceparent (W3C standard) menyambungkan informasi trace di antara layanan hulu dan hilir.
  • OpenTelemetry adalah standar industri observabilitas agnostik yang membebaskan kita dari keterikatan (lock-in) vendor monitoring tertentu.
  • Analisis Gantt Chart trace secara visual untuk mendeteksi bottleneck jalur kritis, jeda overhead jaringan, dan pemrosesan sekuensial yang tidak efisien.
  • Terapkan Tail-based Sampling di tingkat OTel Collector untuk memastikan 100% log error tersimpan dengan aman tanpa membuang anggaran untuk menyimpan log sukses.

← Sebelumnya: Metrics   Berikutnya: Alerting →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact