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?
- 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:
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.
// 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.
$ 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ü.
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.
- 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:
// 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.
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.
-
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ığı soru | Nereye götürür |
|---|---|---|
| Metrik | Bir sorun var mı? | “%99 gecikme 240 ms” → trace’e |
| Trace | Hangi adımda? | “kuyrukta 189 ms” → loga |
| Log | Tam 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ı?
- Correlation ID. Bugün. HTTP ve kuyruk dahil her yerde.
- Yapısal log. Düz metin yerine alanları olan log;
cidvetrace_idayrı alan olsun. - Kritik akışta trace. Bütün sistemi bir anda donatma; sadece emir akışına koy, faydasını gör.
- Kuyruk sonu örnekleme. Yavaş ve hatalı olanlar %100.
- 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
- Her istekte bir correlation ID var mı?
- Bu kimlik kuyruk mesajlarında da taşınıyor mu?
- Her log satırında
trace_id/cidalanı 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.