Sertaç Yıldırımsaha notları

Ana sayfa → Teknik

Nerede 200 ms Gitti?

Müşteri şikayetçi: emirler bazen geç açılıyor. Ölçtük — uçtan uca 240 ms. Her servis kendi logunda “ben 8 ms’de bitirdim” diyor. Altı servisin toplamı 40 ms. Kalan 200 ms nerede?

Özet
  • Servislerin kendi süreleri toplamı, gerçek süreyi vermez. Zaman aradaki boşluklarda kayboluyor.
  • Correlation ID en ucuz kazanç. Tam tracing kuramıyorsan bile bunu bugün ekle.
  • Trace, kuyruklardan da geçmeli. Yoksa izin yarısında kopuyor.
  • Her isteği örnekleme. Ama yavaş ve hatalı olanları mutlaka örnekle.

Loglar neden yetmiyor?

Elimizde altı servisin logu vardı ve hepsi kendince doğruydu:

Altı ayrı log, tek bir gerçek yok
gateway     10:30:00.104  emir alindi, dogrulandi (8 ms)
risk        10:30:00.147  risk kontrolu tamam (5 ms)
teminat     10:30:00.201  teminat ayrildi (11 ms)
kopru       10:30:00.298  MT5'e gonderildi (9 ms)
kopru       10:30:00.331  fill alindi
gateway     10:30:00.344  musteriye dondu

Toplam "islem" suresi: 33 ms
Musterinin bekledigi:  240 ms
Aradaki 207 ms: ???

Kayıp zaman servislerin içinde değil, aralarında: kuyrukta bekleme, bağlantı havuzunda (connection pool) sıra bekleme, ağ, serileştirme, ve en sinsisi — iş parçacığı havuzunda (thread pool) sıraya girme.

Bunu loglarla bulmaya çalışmak, altı ayrı zaman damgasını elle karşılaştırmak demek. Üstelik sunucu saatleri milisaniyeler düzeyinde kayıyor, dolayısıyla çıkardığın fark bazen negatif bile olabiliyor.

Önce en ucuzu: correlation ID

Tam bir tracing altyapısı kurmak zaman alır. Ama tek bir kimliği bütün sisteme taşımak bir öğleden sonralık iş ve faydasının çoğunu hemen veriyor.

Kural: giriş noktasında üret, her yere taşı
// 1) Gateway: istek girerken
String correlationId = istek.header("X-Correlation-Id");
if (correlationId == null) correlationId = UUID.randomUUID().toString();
MDC.put("cid", correlationId);          // log altyapisina koy

// 2) HTTP cagrilarinda tasi
httpClient.header("X-Correlation-Id", correlationId);

// 3) KUYRUK MESAJLARINDA DA TASI  ← en cok atlanan
redis.xadd("emirler", Map.of(
    "cid", correlationId,
    "govde", json
));

// 4) Tuketici tarafta geri al
MDC.put("cid", mesaj.get("cid"));

Üçüncü madde kritik. Çoğu ekip HTTP başlıklarında taşıyor ama iş kuyruğa girince zinciri bırakıyor. Oysa asenkron sistemlerde zamanın çoğu tam da orada geçiyor — yani izi tam kaybetmen gereken yerde kaybediyorsun.

Sonuç: tek bir aramayla bütün yolculuğu görüyorsun.

Artık mümkün
$ grep 'cid=7f3a-991b' /var/log/*/app.log | sort -k1
# ya da log platformunda:  cid:"7f3a-991b"

10:30:00.104  gateway   emir alindi
10:30:00.112  gateway   redis'e yazildi
10:30:00.301  risk      mesaj alindi        ← 189 ms BOSLUK!
10:30:00.306  risk      kontrol tamam

İşte 200 ms. Emir Redis stream’ine yazıldıktan sonra risk servisinin onu alması 189 ms sürmüş. Sebep: risk servisi tek tüketiciyle çalışıyordu ve önünde büyük bir toplu emir vardı — klasik sıra başı tıkanması (head-of-line blocking).

Bu bulguya loglara bakarak ulaşmak günler alırdı. Correlation ID ile on beş dakika sürdü.

Correlation ID, gözlemlenebilirlik yatırımları içinde en yüksek getirili olanı. Bir günlük iş, ilk kullanışta kendini ödüyor.

Sonra asıl olan: trace

Correlation ID sana “nerede” sorusunu cevaplatıyor. Trace ise bunu bir adım öteye taşıyor: her adımın ne kadar sürdüğünü ve hangisinin hangisinin içinde olduğunu gösteriyor.

İki kavram, hepsi bu
  • Trace: bir isteğin tamamı. Tek bir trace_id.
  • Span: o istek içindeki tek bir iş parçası. Her span’in bir başlangıcı, süresi ve bir ebeveyni var.
trace_id: 7f3a...  "emir ac"                        240 ms
├─ gateway: dogrulama                                 8 ms
├─ gateway: redis xadd                                3 ms
├─ [KUYRUKTA BEKLEME]                               189 ms   ← iste burada
├─ risk: kontrol                                      5 ms
│  └─ risk: postgres sorgusu                          3 ms
├─ teminat: ayirma                                   11 ms
└─ kopru: MT5 gonderimi                              24 ms
   └─ kopru: TCP yazma                                9 ms

Bu görünüm, altı log dosyasını yan yana koymanın yapamayacağı şeyi tek bakışta yapıyor: boşluğu görünür kılıyor.

Kuyruğun iki yakasını birleştirmek

Trace’in asenkron sistemlerdeki en kritik detayı bu. Mesajı yazarken trace bağlamını mesajın içine koyman, okurken de oradan devam etmen gerekiyor:

Bağlamı mesajla taşı
// Uretici tarafi
Map<String,String> tasiyici = new HashMap<>();
propagator.inject(Context.current(), tasiyici, Map::put);
redis.xadd("emirler", birlestir(govde, tasiyici));   // traceparent mesajda

// Tuketici tarafi
Context ust = propagator.extract(Context.root(), mesaj, GETTER);
Span span = tracer.spanBuilder("risk kontrolu")
                  .setParent(ust)                    // ← zincir kopmuyor
                  .startSpan();

Bunu yapmazsan her tüketici yeni bir trace başlatır ve elinde birbiriyle ilgisiz yüzlerce kısa trace olur. Teknik olarak tracing kurmuş olursun, pratikte hiçbir soruyu cevaplayamazsın.

Örnekleme: her isteği izleme

Saniyede 50 bin işlem yapan bir sistemde her isteği kaydetmek ne mantıklı ne de ucuz. Ama rastgele %1 örneklemek de bir işe yaramıyor — çünkü ilgilendiğin şey ortalama istek değil, kötü olan istek.

Kuyruk sonu örnekleme

Doğru yaklaşım, kararı istek bittikten sonra vermek:

  • Hata aldıysa → %100 sakla
  • Süre 200 ms’yi aştıysa → %100 sakla
  • Belirli müşteriler / VIP hesaplar → %100 sakla
  • Geri kalan normal istekler → %1 sakla (temel çizgi için)

Böylece hem maliyet düşük kalıyor hem de sorun yaşanan her istek elinde oluyor. “Müşteri şikayet etti ama o isteğin trace’i örneklenmemişti” cümlesi, tracing kurmuş olmanın en can sıkıcı hali.

Üç yaygın hata
  • Span’a büyük veri koymak. Emrin tamamını JSON olarak span’a eklemek, trace deposunu şişiriyor ve pahalıya patlıyor. Sadece kimlikleri koy: order_id, account_id, symbol.
  • Trace göndermeyi istek yolunda senkron yapmak. Toplayıcı yavaşladığında uygulaman da yavaşlıyor. Gönderim her zaman arka planda ve tampon dolduğunda düşürerek olmalı — gözlem aracı asıl işi bekletmemeli.
  • Kişisel veriyi span’a yazmak. Müşteri adı, e-posta, hesap numarası — trace deposu genelde loglardan daha az korunuyor.

Üçünü bir arada tutmak

Metrik, log ve trace aynı sorunun üç farklı görüntüsü. Asıl güç, aralarında geçiş yapabilmekten geliyor:

AraçCevapladığı soruNereye götürür
MetrikBir sorun var mı?“%99 gecikme 240 ms” → trace’e
TraceHangi adımda?“kuyrukta 189 ms” → loga
LogTam olarak ne oldu?“tüketici tek, batch önde”

Bu zincirin çalışması için tek bir şey gerekiyor: her log satırında trace_id olmalı. Trace’te bir yavaşlık gördüğünde tek tıkla o isteğin loglarına gidebiliyorsan sistem işini yapıyor demektir; kimliği elle kopyalayıp aramak zorunda kalıyorsan yarısı eksik.

Nereden başlamalı?

  1. Correlation ID. Bugün. HTTP ve kuyruk dahil her yerde.
  2. Yapısal log. Düz metin yerine alanları olan log; cid ve trace_id ayrı alan olsun.
  3. Kritik akışta trace. Bütün sistemi bir anda donatma; sadece emir akışına koy, faydasını gör.
  4. Kuyruk sonu örnekleme. Yavaş ve hatalı olanlar %100.
  5. Yayılmayı genişlet. Değeri kanıtlandıktan sonra diğer akışlara.

İlk iki adım, tracing altyapısı hiç kurmasan bile problemlerinin çoğunu çözüyor. “Önce OpenTelemetry kuralım” diye başlayan projeler genelde altı ay sonra hâlâ kurulum aşamasında oluyor; correlation ID ise ertesi gün işe yarıyor.

Kontrol listesi AI agent görev listesi

Gözlemlenebilirlik
  • Her istekte bir correlation ID var mı?
  • Bu kimlik kuyruk mesajlarında da taşınıyor mu?
  • Her log satırında trace_id / cid alanı var mı?
  • Trace zinciri kuyruğun iki yakasını birleştiriyor mu?
  • Yavaş ve hatalı istekler %100 örnekleniyor mu?
  • Span’lara büyük gövde ya da kişisel veri konuyor mu?
  • Trace gönderimi asıl isteği bekletiyor mu?
  • Metrikten trace’e, trace’ten loga tıklayarak geçebiliyor musun?

Sonuç

Dağıtık bir sistemde en pahalı şey, sorunun nerede olduğunu bilmeden geçirilen saatler. Kod düzeltmesi genelde on dakika; onu bulmak günler sürüyor.

Baştaki 200 ms bir hata değildi; tek tüketicili bir kuyruktu. Kimse yanlış kod yazmamıştı, kimse yanlış bir şey yapmamıştı — sadece hiç kimse bütünü göremiyordu. Tracing’in yaptığı tek şey de bu: parçaların arasındaki boşluğu görünür kılmak.