Observability & Performans Mühendisliği 6 dk okuma 3 okunma

Distributed Tracing ile Performans Analizi

Distributed Tracing, dağıtık sistemlerde bir isteğin servisler arasındaki tüm yolculuğunu izleyerek performans darboğazlarını, yavaş servisleri ve kritik yolları tespit etmeyi sağlar. Bu yazıda, trace/span kavramları, critical path analizi, span sürelerinin yorumlanması, örnekleme stratejileri, OpenTelemetry Collector ile tail-based sampling ve .NET'te performans analizi teknikleri ele alınır.

Distributed Tracing ile Performans Analizi

Distributed Tracing ile Performans Analizi

Mikroservis mimarisinde, bir isteğin yavaşlaması durumunda sorunun hangi serviste olduğunu bulmak, log'lar arasında kaybolmak demektir. Distributed Tracing (Dağıtık İzleme), bir isteğin tüm servisler arasındaki yolculuğunu tek bir görselleştirmede birleştirerek performans analizini bilimsel bir disipline dönüştürür. Bu yazıda, distributed tracing verilerini kullanarak performans darboğazlarını nasıl tespit edeceğinizi, yorumlayacağınızı ve gidereceğinizi detaylıca ele alacağız.


1. Trace ve Span: Performansın Yapı Taşları

Trace (İz): Bir isteğin başlangıcından sonuna kadar tüm yolculuğunu temsil eder. Benzersiz bir Trace ID ile tanımlanır.

Span (Aralık): Trace içindeki her bir işlem birimidir. Her span şunları içerir:

  • Span ID: Benzersiz kimlik

  • Parent Span ID: Üst span (hiyerarşi)

  • Start Time / End Time: Başlangıç ve bitiş zamanı

  • Duration: Süre (End - Start)

  • Attributes: HTTP metodu, URL, DB sorgusu, durum kodu

  • Status: OK, Error

Kritik Metrik: duration. Her span'in süresi, o işlemin performansını gösterir.


2. Performans Analizinin Temel Soruları

Distributed tracing verileriyle şu soruları yanıtlayabilirsiniz:

Soru Cevap İçin Bakılacak Yer
İstek toplam ne kadar sürdü? Root span duration
Hangi servis en yavaş? Span sürelerini servis bazında karşılaştır
Veritabanı mı, ağ mı yavaş? DB span vs HTTP client span süreleri
N+1 sorgusu var mı? Aynı parent altında çok sayıda DB span
Kritik yol (critical path) neresi? Parent-child zincirinde en uzun süren yol
Hata nerede başladı? Status=Error olan span'ler
Yavaşlık yeniden mi ediyor? Trace'leri zaman içinde karşılaştır

3. Critical Path Analizi (Kritik Yol Analizi)

Critical Path, bir trace'te isteğin tamamlanmasını belirleyen en uzun süren span zinciridir. Paralel çalışan span'ler arasında, toplam süreyi belirleyen yol kritik yoldur.

Örnek Trace:

text

Root: GET /api/order/123 (Toplam: 1250ms)
├── OrderService.GetOrder (200ms)
├── CustomerService.GetCustomer (300ms)  ← Paralel
├── ProductService.GetItems (400ms)      ← Paralel
└── PaymentService.ProcessPayment (800ms) ← KRİTİK YOL
    └── DB Query (750ms)                  ← ASIL DARBOĞAZ

Analiz:

  • Toplam süre: 1250ms

  • Paralel çalışan 3 servis: en uzunu 400ms

  • PaymentService: 800ms (kritik yol)

  • DB Query: 750ms (asıl darboğaz)

Çıkarım: Diğer servisleri optimize etmek anlamsız; asıl odaklanılması gereken PaymentService'teki DB sorgusu.

Formül:

text

Kritik Yol Süresi = max(paralel dalların süreleri) + sıralı adımlar
Toplam İyileştirme Potansiyeli = Kritik Yol Süresi - İkinci En Uzun Yol

4. Span Sürelerinin Yorumlanması

A. Self-Time vs. Total-Time

  • Total Time: Span'in başlangıcından bitişine kadar geçen süre (child span'ler dahil).

  • Self Time: Span'in kendi işlem süresi (child span'ler hariç).

text

Span A: 500ms (Total)
├── Span B: 300ms (Child)
└── Span C: 150ms (Child)
Self Time (A) = 500 - 300 - 150 = 50ms

Yorum: Span A kendi içinde yalnızca 50ms harcıyor; asıl yük child span'lerde. Optimize edilecek yer Span B.

B. Gap Analizi (Boşluk Analizi)

Child span'ler arasında boşluklar varsa, bu, uygulamanın işlem yapmadan beklediği anlamına gelir (CPU hesaplama, kilit bekleme, GC).

text

Parent Span: 1000ms
├── Child 1: 0-200ms
├── [BOŞLUK: 200-600ms]  ← 400ms açıklanamayan süre
└── Child 2: 600-800ms

Yorum: 400ms'lik boşluk, in-process hesaplama, kilit bekleme veya GC duraklaması olabilir. Detaylı profiling gerekir.

C. N+1 Sorgu Tespiti

Aynı parent span altında çok sayıda benzer DB span görüyorsanız, N+1 problemi vardır.

text

Parent: GetOrders (1200ms)
├── DB Query: SELECT * FROM Orders (50ms)
├── DB Query: SELECT * FROM Customers WHERE Id=1 (20ms)
├── DB Query: SELECT * FROM Customers WHERE Id=2 (20ms)
├── DB Query: SELECT * FROM Customers WHERE Id=3 (20ms)
... (100 kez tekrar)  ← N+1 problemi!

Çözüm: Eager loading (Include), batch sorgu veya DataLoader.


5. OpenTelemetry ile Performans Verisi Toplama

.NET'te Manuel Span ile Detaylı Ölçüm:

csharp

using System.Diagnostics;

public class OrderService
{
    private static readonly ActivitySource ActivitySource = new("OrderService");

    public async Task<Order> GetOrderAsync(int orderId)
    {
        using var activity = ActivitySource.StartActivity("GetOrder");
        activity?.SetTag("order.id", orderId);

        // 1. Veritabanı sorgusu - ayrı span
        using (var dbActivity = ActivitySource.StartActivity("DB.GetOrder"))
        {
            dbActivity?.SetTag("db.system", "postgresql");
            dbActivity?.SetTag("db.statement", "SELECT * FROM Orders WHERE Id = @Id");
            order = await _dbContext.Orders.FindAsync(orderId);
        }

        // 2. Harici servis çağrısı - ayrı span
        using (var httpActivity = ActivitySource.StartActivity("HTTP.GetCustomer"))
        {
            httpActivity?.SetTag("http.method", "GET");
            httpActivity?.SetTag("http.url", $"https://customer-service/api/{order.CustomerId}");
            customer = await _httpClient.GetFromJsonAsync<Customer>(...);
        }

        return order;
    }
}

Bu sayede trace'te her adımın süresi ayrı ayrı görünür ve darboğaz net şekilde tespit edilir.


6. Örnekleme (Sampling) Stratejileri

Yüksek trafikli sistemlerde tüm trace'leri toplamak maliyetlidir. Ancak performans analizi için yavaş trace'leri kaçırmamak kritiktir.

A. Head-Based Sampling (Basit)

csharp

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .SetSampler(new TraceIdRatioBasedSampler(0.1))  // %10
    );

Dezavantaj: Yavaş trace'ler örnekleme dışında kalabilir.

B. Tail-Based Sampling (Önerilen)
Trace tamamlandıktan sonra karar verilir. Yavaş veya hatalı trace'ler her zaman örneklenir.

yaml

# OpenTelemetry Collector yapılandırması
processors:
  tail_sampling:
    decision_wait: 10s
    policies:
      # 1. Hatalı trace'ler - her zaman al
      - name: errors-policy
        type: status_code
        status_code: { status_codes: [ERROR] }
      
      # 2. Yavaş trace'ler (>1 saniye) - her zaman al
      - name: slow-traces-policy
        type: latency
        latency: { threshold_ms: 1000 }
      
      # 3. Diğerleri - %5 örnekle
      - name: random-policy
        type: probabilistic
        probabilistic: { sampling_percentage: 5 }

Avantaj: Performans sorunlarını asla kaçırmazsınız.


7. .NET'te Performans Analizi Araçları

Araç Kullanım Alanı
OpenTelemetry + Jaeger Trace toplama ve görselleştirme
Grafana Tempo Ucuz trace depolama, Grafana entegrasyonu
Azure Application Insights Otomatik enstrümantasyon, application map
dotnet-trace .NET runtime trace toplama (GC, JIT, thread)
dotnet-counters Canlı metrik izleme (CPU, GC, ThreadPool)
BenchmarkDotNet Mikro-benchmark
PerfView Derinlemesine CPU/bellek analizi

dotnet-trace ile Runtime Performans Analizi:

bash

# Çalışan uygulamadan trace al
dotnet-trace collect --process-id 12345 --providers Microsoft-DotNETCore-SampleProfiler

# Trace'i analiz et
dotnet-trace report trace.nettrace topN

8. Gerçek Dünya Performans Analizi Senaryosu

Senaryo: E-ticaret sipariş sayfası 3 saniyede yükleniyor. Kullanıcılar şikayetçi.

Adım 1: Trace'i İncele

text

Root: GET /api/order/123 (3200ms)
├── OrderService.GetOrder (150ms)
├── CustomerService.GetCustomer (200ms)
├── ProductService.GetItems (2800ms)  ← ŞÜPHELİ
│   ├── DB.GetProduct (20ms) × 100 kez = 2000ms  ← N+1!
│   └── HTTP.GetImage (8ms) × 100 kez = 800ms
└── PaymentService.GetStatus (50ms)

Adım 2: Darboğazı Tespit Et

  • Kritik yol: ProductService (2800ms)

  • Asıl sorun: 100 kez tekrarlanan DB.GetProduct sorgusu (N+1 problemi)

  • İkincil sorun: 100 kez HTTP.GetImage çağrısı

Adım 3: Çözüm Uygula

csharp

// ❌ Önce (N+1)
foreach (var item in order.Items)
{
    var product = await _db.Products.FindAsync(item.ProductId);  // 100 kez
}

// ✅ Sonra (Tek sorgu)
var productIds = order.Items.Select(i => i.ProductId).ToList();
var products = await _db.Products
    .Where(p => productIds.Contains(p.Id))
    .ToDictionaryAsync(p => p.Id);  // 1 kez

// ✅ Görselleri toplu al
var images = await _imageService.GetBatchAsync(productIds);  // 1 kez

Adım 4: Sonucu Doğrula

text

Root: GET /api/order/123 (450ms)  ← 7x iyileşme!
├── OrderService.GetOrder (150ms)
├── CustomerService.GetCustomer (200ms)
├── ProductService.GetItems (150ms)  ← 2800ms → 150ms
└── PaymentService.GetStatus (50ms)

9. En İyi Pratikler

Pratik Açıklama
Tail-Based Sampling Kullanın Yavaş ve hatalı trace'leri asla kaçırmayın.
Anlamlı Span İsimleri DB.GetProduct gibi; Span1 gibi değil.
Kritik Yolu Analiz Edin Paralel dallar arasında en uzun olanı bulun.
N+1 Sorgularını Tespit Edin Aynı parent altında tekrarlayan DB span'lerini izleyin.
Gap Analizi Yapın Child span'ler arasındaki boşlukları araştırın.
Self-Time Hesaplayın Span'in kendi işlem süresini ölçün.
Baseline Oluşturun Normal performansı ölçün; sapmaları tespit edin.
Alerting Kurun p99 latency eşiği aşıldığında uyarı gönderin.
Trace'leri Loglarla İlişkilendirin trace_id ile log-trace korelasyonu sağlayın.

10. Trace'ten Aksiyona: Optimizasyon Döngüsü

text

1. Ölç (Trace Topla)
      ↓
2. Analiz Et (Kritik Yol, Darboğaz)
      ↓
3. Optimize Et (N+1, Cache, Parallel)
      ↓
4. Doğrula (Yeni Trace ile Karşılaştır)
      ↓
5. İzle (Sürekli Monitoring)
      ↓
   (1'e dön)

Sonuç

Distributed Tracing, mikroservis mimarisinde performans analizini tahminden bilime dönüştürür. Trace ve span yapıları sayesinde bir isteğin tüm yolculuğunu görür; critical path analizi ile asıl darboğazı tespit eder; self-time ve gap analizi ile yavaşlığın kaynağını bulur; N+1 sorgu tespiti ile veritabanı problemlerini ortaya çıkarırsınız. Tail-based sampling ile yavaş trace'leri asla kaçırmaz; OpenTelemetry ve Jaeger/Grafana Tempo ile bu verileri görselleştirirsiniz. Unutmayın: Performans optimizasyonu, ölçmeden yapılmaz. Distributed Tracing, ölçümün en güçlü aracıdır; onu doğru kullanan ekipler, sorunları kullanıcılar fark etmeden çözer.

Tüm yazılar Soru ya da yorumun mu var? Yaz