KoderKoder.ai
FiyatlandırmaKurumsalEğitimYatırımcılar için
Giriş YapBaşla

Ürün

FiyatlandırmaKurumsalYatırımcılar için

Kaynaklar

Bize UlaşınDestekEğitimBlog

Yasal

Gizlilik PolitikasıKullanım KoşullarıGüvenlikKabul Edilebilir Kullanım PolitikasıKötüye Kullanımı Bildir

Sosyal

LinkedInTwitter
Koder.ai
Dil

© 2026 Koder.ai. Tüm hakları saklıdır.

Ana Sayfa›Blog›Düşük gecikme için Disruptor deseni: öngörülebilir gerçek zamanlı tasarım
21 Eyl 2025·6 dk

Düşük gecikme için Disruptor deseni: öngörülebilir gerçek zamanlı tasarım

Disruptor desenini düşük gecikme için öğrenin ve kuyruklar, bellek ve mimari seçimlerle öngörülebilir yanıt sürelerine sahip gerçek zamanlı sistemleri nasıl tasarlayacağınızı keşfedin.

Düşük gecikme için Disruptor deseni: öngörülebilir gerçek zamanlı tasarım

Gerçek zamanlı uygulamalar kod hızlı olsa neden yavaş hissedilir?\n\nHızın iki yüzü vardır: throughput ve latency. Throughput saniyede ne kadar iş bitirdiğinizdir (istekler, mesajlar, kareler). Latency ise tek bir iş biriminin baştan sona ne kadar sürdüğüdür.\n\nBir sistem müthiş throughput'a sahip olabilir ama bazı istekler çok daha uzun sürüyorsa yine yavaş hissedilir. Bu yüzden ortalamalar yanıltır. Eğer 99 işlem 5 ms sürerken bir işlem 80 ms sürüyorsa, ortalama iyi görünür; ama 80 ms'i yaşayan kullanıcı takılmayı hisseder. Gerçek zamanlı sistemlerde nadir zirveler bütün hikayedir çünkü ritmi bozarlar.\n\nÖngörülebilir gecikme demek sadece düşük ortalamayı hedeflemek değil, çoğu işlemin dar bir aralıkta tamamlanmasını sağlamaktır. Bu yüzden ekipler tail'i (p95, p99) izler. Duraklamalar genellikle orada saklanır.\n\n50 ms'lik bir sıçrama ses ve video uygulamalarında (ses patlamaları), çok oyunculu oyunlarda (rubber-banding), gerçek zamanlı alım-satımda (fiyat kaçırma), endüstriyel izlemede (geç alarm) ve canlı panolarda (sayılarda sıçrama, güvenilmez uyarılar) önemli olabilir.\n\nBasit bir örnek: bir sohbet uygulaması çoğu zaman mesajları hızlı teslim eder. Ama arka planda bir duraksama tek bir mesajın 60 ms gecikmesine neden olursa, yazıyor göstergeleri titrer ve konuşma gecikmeli hissedilir; sunucu ortalamada “hızlı” görünse bile.\n\nGerçek zamanlı hissetmek istiyorsanız daha az sürpriz gerekir, sadece daha hızlı kod değil.\n\n## Gecikme temelleri: zaman gerçekten nerede harcanıyor\n\nÇoğu gerçek zamanlı sistem CPU'nun zorlanmasından yavaş değildir. Yavaş hissetmelerinin nedeni işin çoğunun beklemede geçmesidir: çalıştırılmayı beklemek, bir kuyruğun arkasında beklemek, ağda beklemek veya depolamayı beklemek.\n\nUçtan uca gecikme “bir şey oldu”dan “kullanıcı sonucu gördü”ye kadar geçen tam süredir. Handler'ınız 2 ms çalışsa bile istek beş farklı yerde duraklarsa yine 80 ms alabilir.\n\nYolu şu şekilde bölmek faydalıdır: \n\n- Ağ zamanı (istemciden kenara, servisler arası, yeniden denemeler)\n- Zamanlama (thread'in çalışmayı beklemesi)\n- Kuyruk zamanı (işin diğer işlerin arkasında beklemesi)\n- Depolama zamanı (disk, veritabanı kilitleri, cache miss'ler)\n- Seri hale getirme zamanı (veriyi kodlama/çözme)\n\nBu beklemeler üst üste biner. Burada birkaç milisaniye toplandığında “hızlı” bir yol yavaş bir deneyime dönüşür.\n\nTail gecikme kullanıcıların şikayet etmeye başladığı yerdir. Ortalama gecikme iyi görünse bile p95 veya p99, en yavaş %5 veya %1'i gösterir. Aykırılıklar genellikle nadir duraklamalardan gelir: bir GC döngüsü, host üzerindeki gürültülü bir komşu, kısa süreli kilit içerme, bir cache doldurma veya kuyruğun oluştuğu bir patlama.\n\nSomut örnek: fiyat güncellemesi ağda 5 ms içinde gelir, meşgul bir worker için 10 ms bekler, diğer olayların arkasında 15 ms geçirir, sonra veritabanında 30 ms'lik bir duraklama yaşar. Kodunuz hâlâ 2 ms çalışmış olabilir, ama kullanıcı 62 ms beklemiştir. Amaç her adımı tahmin edilebilir kılmaktır, sadece hesaplamayı hızlı yapmak değil.\n\n## Kod hızının ötesindeki jitter kaynakları\n\nHızlı bir algoritma bile zaman başına düşen süreler dalgalanıyorsa yine yavaş hissedilebilir. Kullanıcılar ortalamayı değil zirveleri fark eder. Bu dalgalanma jitter'dır ve genellikle kodunuzun tamamen kontrolünde olmayan şeylerden gelir.\n\nCPU önbellekleri ve bellek davranışı gizli maliyetlerdir. Sık erişilen veri cache'e sığmazsa CPU RAM'i bekler. Nesne-ağır yapılar, dağınık bellek ve “bir kez daha bir bak” durumları tekrarlayan cache miss'lere dönüşebilir.\n\nBellek tahsisi kendi rastgeleliğini ekler. Kısa ömürlü çok sayıda nesne ayırmak heap üzerinde basınç yaratır; bu daha sonra duraklamalar (garbage collection) veya allocator çatışması olarak görünür. GC olmasa bile sık tahsisler bellek parçalanmasına ve locality kaybına neden olabilir.\n\nThread zamanlaması başka bir yaygın kaynaktır. Bir thread deschedule edildiğinde context switch maliyeti ve cache sıcaklığının kaybı ortaya çıkar. Yoğun bir makinede “gerçek zamanlı” thread'iniz alakasız işlerin arkasında bekleyebilir.\n\nKilit içerme, öngörülebilir sistemlerin sıklıkla çöktüğü yerdir. “Genelde boş” olan bir kilit konvoya dönüşebilir: thread'ler uyanır, kilit için mücadele eder ve birbirlerini tekrar uyuturlar. İş yine yapılır, ancak tail gecikme uzar.\n\nI/O beklemeleri her şeyi gölgede bırakabilir. Tek bir syscall, dolu bir ağ tamponu, TLS el sıkışması, disk flush veya yavaş DNS sorgusu keskin bir sıçrama yaratabilir; hiçbir mikro-optimizasyon bunu tamamen düzeltmez.\n\nJitter avına çıktıysanız, cache miss'lerini (çoğunlukla pointer-ağır yapılar ve rastgele erişim neden olur), sık tahsisleri, çok fazla thread veya gürültülü komşulardan kaynaklanan context switch'leri, kilit içerme ve sıcak yolda bloklayan herhangi bir I/O'yu arayın.\n\nÖrnek: bir fiyat-ticker servisi mikrosaniyelerde güncelleme hesaplayabilir, ama tek bir senkronize logger çağrısı veya içerilmiş bir metrik kilidi ara sıra onlarca milisaniye ekleyebilir.\n\n## Martin Thompson ve Disruptor deseni nedir\n\nMartin Thompson, düşük gecikme mühendisliğinde sadece ortalama hız değil, baskı altındaki sistem davranışına odaklanmasıyla bilinir. LMAX ekibiyle birlikte Disruptor desenini popülerleştirmeye katkıda bulundu; bu desen, olayları küçük ve tutarlı gecikmelerle sistemde taşımak için referans bir yaklaşımdır.\n\nDisruptor yaklaşımı, birçok “hızlı” uygulamayı öngörülemez yapan şeye yanıt olarak ortaya çıktı: içerme ve koordinasyon. Tipik kuyruklar genellikle kilitlere veya yoğun atomik operasyonlara dayanır, thread'leri açıp kapatır ve üreticiler ile tüketiciler paylaşılan yapılar üzerinde rekabet ettiğinde bekleme patlamaları yaratır.\n\nKuyruk yerine Disruptor bir ring buffer kullanır: slotlarda olayları tutan sabit boyutlu bir döngüsel dizi. Üreticiler bir sonraki slotu talep eder, veriyi yazar, sonra bir sıra numarası yayınlar. Tüketiciler bu sırayı takip ederek sıralı şekilde okur. Buffer önceden ayrıldığı için sık tahsislardan kaçınır ve garbage collector baskısını azaltırsınız.\n\nAna fikirlerden biri tek-yazar ilkesidir: paylaşılan bir durum parçasından sorumlu tek bir bileşen tutun (ör. ring boyunca ilerleyen cursor). Daha az yazar “sırada kim var?” anlarını azaltır.\n\nGeri basınç açık ve nettir. Tüketiciler geride kaldığında, üreticiler hâlâ kullanımda olan bir slota ulaşır. O noktada sistem beklemek, düşürmek veya yavaşlamak zorundadır; ama bunu kontrol edilen, görünür bir şekilde yapar, sonsuz büyüyen bir kuyruğun arkasına gizlemek yerine.\n\n## Gecikmeyi tutarlı kılan temel tasarım fikirleri\n\nDisruptor tarzı tasarımları hızlı yapan şey bir mikro-optimizasyon değil; sistemin kendi hareketli parçalarıyla kavga ettiğinde ortaya çıkan öngörülemez duraklamaları ortadan kaldırmaktır: tahsisler, cache miss'ler, kilit içerme ve sıcak yolda karışan ağır işler.\n\nKullanışlı bir zihinsel model montaj hattıdır. Olaylar sabit rota boyunca net el değişimleriyle hareket eder. Bu paylaşılan durumu azaltır ve her adımı basit ve ölçülebilir tutmayı kolaylaştırır.\n\n### Belleği ve veriyi tahmin edilebilir tutun\n\nHızlı sistemler sürpriz tahsislerden kaçınır. Buffer'ları önceden ayırıp mesaj nesnelerini yeniden kullanırsanız, garbage collection, heap büyümesi ve allocator kilitlerinin neden olduğu “bazen” spike'larından kurtulursunuz.\n\nAyrıca mesajları küçük ve sabit tutmak yardımcı olur. Her olayda dokunduğunuz veri CPU cache'ine sığarsa bellek beklemelerinde daha az zaman harcarsınız.\n\nPratikte en önemli alışkanlıklar şunlardır: her olay için yeni nesneler yaratmak yerine nesneleri yeniden kullanın, olay verisini kompakt tutun, paylaşılan durum için tek yazarı tercih edin ve koordinasyon maliyetini daha az sıklıkta ödeyecek şekilde dikkatli batching yapın.\n\n### Yavaş yolları bariz kılın\n\nGerçek zamanlı uygulamalar genellikle loglama, metrikler, yeniden denemeler veya veritabanı yazmaları gibi ekstra ihtiyaçlara sahiptir. Disruptor zihniyeti, bunları çekirdek döngüden izole etmektir, böylece onları engelleyemezler.\n\nCanlı bir fiyat akışında sıcak yol yalnızca bir tick'i doğrulayıp bir sonraki fiyat anlık görüntüsünü yayınlamak olabilir. Disk, ağ çağrıları veya ağır serileştirme gibi duraklatabilecek her şey ayrı bir tüketiciye veya yan kanala taşınır, böylece öngörülebilir yol öngörülebilir kalır.\n\n## Öngörülebilir gecikme için mimari seçimler\n\nÖngörülebilir gecikme büyük ölçüde bir mimari sorunudur. Çok sayıda thread aynı veriyi paylaşırsa veya mesajlar gereksiz yere ağ üzerinde sekip duruyorsa hızlı kodunuzda bile zirveler oluşur.\n\nÖnce kaç yazar ve okuyucunun aynı kuyruk veya buffer'ı etkilediğine karar verin. Tek bir üretici daha düzgün tutmak daha kolaydır çünkü koordinasyondan kaçınırsınız. Çoklu üretici düzenleri throughput'u artırabilir, ancak genellikle içerme ekleyip en kötü durum zamanlamasını daha öngörülemez kılar. Birden çok üreticiye ihtiyaç varsa, paylaşılan yazmaları azaltmak için olayları anahtara göre shard'layın (ör. userId veya instrumentId) böylece her shard'ın kendi sıcak yolu olur.\n\nTüketici tarafında, sıralama önemliyse tek bir tüketici en stabil zamanlamayı verir çünkü durum yerel bir thread'te kalır. İşçi havuzları görevler gerçekten bağımsızsa yardımcı olur, ama zamanlama gecikmeleri ekler ve dikkatli olmazsanız işi yeniden sıralayabilir.\n\nBatching bir başka takastır. Küçük batch'ler overhead'i keser (daha az uyanma, daha az cache miss), ama batch için olayları bekletmek beklemeyi artırabilir. Gerçek zamanlı bir sistemde batch yapıyorsanız bekleme süresini sınırlandırın (örneğin “en fazla 16 olay veya 200 mikro saniye, hangisi önce gerçekleşirse”).\n\nServis sınırları da önemlidir. Sıkı gecikme gerektiğinde işlem içi (in-process) mesajlaşma genellikle en iyisidir. Ölçek için ağ hop'ları gerekliyse karşılığında her hop kuyruklar, yeniden denemeler ve değişken gecikme ekler. Hop gerekiyorsa protokolü basit tutun ve sıcak yolda fan-out'tan kaçının.\n\nPratik bir kural seti: mümkün olduğunda shard başına tek-yazar yolu tutun, bir sıcak kuyruğu paylaşmak yerine anahtarlarla shard'layarak ölçekleyin, batch yaparken kesin bir zaman limiti koyun, sadece paralel ve bağımsız işler için işçi havuzları ekleyin ve her ağ hop'unu ölçene kadar potansiyel bir jitter kaynağı olarak değerlendirin.\n\n## Adım adım: düşük-jitter bir boru hattı tasarlamak\n\nKod yazmaya başlamadan önce yazılı bir gecikme bütçesiyle başlayın. Bir hedef seçin (iyi hissettiren nedir) ve bir p99 belirleyin (altında kalmanız gereken sınır). Bu sayıyı girdi, doğrulama, eşleştirme, kalıcılık ve dışa güncellemeler gibi aşamalara bölün. Bir aşamanın bütçesi yoksa sınırı yoktur.\n\nSonra tam veri akışını çizin ve her el değişimini işaretleyin: thread sınırları, kuyruklar, ağ hop'ları ve depolama çağrıları. Her el değişimi jitter'in saklanabileceği yerdir. Gördüğünüzde, azaltabilirsiniz.\n\nTasarımı dürüst tutacak bir iş akışı:

  • Aşama başına bir gecikme bütçesi yazın (hedef ve p99), artı küçük bir rezerv.

  • Boru hattını haritalayın ve kuyrukları, kilitleri, tahsisleri ve engelleyici çağrıları etiketleyin.

  • Mantıklı bir eşzamanlılık modeli seçin (tek yazar, anahtara göre partition edilmiş worker'lar veya adanmış I/O thread'i).

  • Mesaj biçimini erken belirleyin: sabit şemalar, kompakt yükler ve minimum kopyalama.

  • Geri basınç kurallarını baştan karara bağlayın: düşürme, geciktirme, bozulma veya yükü atma. Bunu görünür ve ölçülebilir yapın.\n\nSonra neyin asenkron olabileceğine karar verin. Basit kural: kullanıcıya “şimdi” gözüken her şey kritik patikada kalır. Diğer her şey dışarı taşınır.\n\nAnalitik, denetim logları ve ikincil indeksleme genelde sıcak yoldan daha güvenle taşınabilir. Doğrulama, sıralama ve bir sonraki durumu üretmek için gereken adımlar genellikle kritik patikada kalmalıdır.\n\n## Çalışma zamanı ve OS seçimleri tail gecikmeyi etkiler\n\nHızlı kod, çalışma zamanı veya OS işinizi yanlış anda durdurduğunda yine yavaş hissedebilir. Amaç sadece yüksek throughput değil; en yavaş %1'de daha az sürprizdir.\n\nGarbage collected runtimeler (JVM, Go, .NET) üretkenlik için çok iyi olabilir, ama bellek temizliği gerektiğinde duraklamalar ekleyebilir. Modern collector'lar eskisine göre çok daha iyi olsa da, yük altındayken çok sayıda kısa ömürlü nesne yaratmak tail gecikmeyi zıplatabilir. GC olmayan diller (Rust, C, C++) GC duraklamalarından kaçınır ama maliyeti manuel sahiplik ve tahsis disiplini olarak öne iter. Her iki durumda da bellek davranışı CPU hızından en az onun kadar önemlidir.\n\nPratik alışkanlık basittir: tahsislerin olduğu yerleri bulup onları sıradan yapın. Nesneleri yeniden kullanın, buffer'ları önceden boyutlandırın ve sıcak yoldaki veriyi geçici string'ler veya map'ler haline getirmekten kaçının.\n\nThread seçimleri de jitter olarak görünür. Her ekstra kuyruk, async hop veya thread pool el değişimi beklemeyi artırır ve varyansı yükseltir. Az sayıda uzun ömürlü thread tercih edin, üretici-tüketici sınırlarını net tutun ve sıcak yolda engelleyici çağrılardan kaçının.\n\nBazı OS ve konteyner ayarları tail'in temiz mi yoksa dalgalı mı olacağını belirler: sıkı limitlerden CPU kısıtlaması, paylaşılan host'taki gürültülü komşular ve kötü yerleştirilmiş loglama/metrikler ani yavaşlamalara neden olabilir. Sadece bir şeyi değiştirecekseniz, gecikme sıçramaları sırasında tahsis oranını ve context switch sayısını ölçmeye başlayın.\n\n## Veri, depolama ve servis sınırları: sürpriz duraklamalar olmadan\n\nBirçok gecikme sıçraması “yavaş kod” değildir. Planlamadığınız beklemelerdir: veritabanı kilidi, yeniden denemeler fırtınası, takılan bir servis çağrısı veya bir cache miss'in tam bir round-trip'e dönüşmesi.\n\nKritik patikayı kısa tutun. Her ekstra hop zamanlama, serileştirme, ağ kuyrukları ve engellenme yerleri ekler. Bir isteğe tek bir işlem ve tek bir veri deposundan cevap verebiliyorsanız önce onu yapın. Her çağrı opsiyonel veya sıkı sınırlı olmadıkça servislere bölün.\n\nSınırlı bekleme hızlı ortalamalar ile öngörülebilir gecikme arasındaki farktır. Uzak çağrulara sert zaman aşımı koyun ve bir bağımlılık sağlıksızsa hızlıca başarısız olun. Circuit breaker'lar sadece sunucuları korumak için değil; kullanıcıların ne kadar süreyle takılabileceğini de sınırlamak içindir.\n\nVeri erişimi engellendiğinde yolları ayırın. Okumalar genellikle indekslenmiş, denormalize ve cache-dostu şekiller ister. Yazmalar ise kalıcılık ve sıralama ister. Ayrıştırma içerme kaldırabilir ve kilit süresini azaltabilir. Tutarlılık izin veriyorsa, ekleme-odaklı kayıtlar (event log) genellikle sıcak satır kilitleme veya arka plan bakımı tetikleyen yerinde güncellemelere göre daha öngörülebilir davranır.\n\nGerçek zamanlı uygulamalar için basit bir kural: kalıcılık kritik patikada olmamalıdır, eğer doğru sonuç için gerçekten gerekiyorsa hariç. Genellikle daha iyi şekil şu olur: hafızada güncelle, cevap ver, sonra asenkron olarak kalıcılaştır ve replay mekanizması kullan (outbox veya write-ahead log gibi).\n\nBirçok ring-buffer boru hattında bu şu olur: bellekte bir buffer'a yayınla, durumu güncelle, cevap ver, sonra ayrı bir tüketici PostgreSQL'e yazmaları batch'leyerek yapar.\n\n## Gerçekçi bir örnek: öngörülebilir gecikme ile gerçek zamanlı güncellemeler\n\nBir canlı işbirliği uygulamasını (veya küçük bir çok oyunculu oyunu) düşünün: her 16 ms'de bir (yaklaşık saniyede 60 kez) güncelleme gönderiyor. Amaç ortalamada hızlı olmak değil; çoğunlukla 16 ms altında kalmak, bir kullanıcının bağlantısı kötü olsa bile.\n\nBasit bir Disruptor-tarzı akış şöyle görünür: kullanıcı girdisi küçük bir olay olur, önceden ayrılmış bir ring buffer'a yayınlanır, sonra sıralı olarak sabit bir dizi handler tarafından işlenir (validate -> apply -> prepare outbound messages) ve sonunda istemcilere yayınlanır.\n\nBatching kenarlarda yardımcı olabilir. Örneğin, her istemci için outbound yazıları bir tick başına batchleyin ki ağ katmanını daha az çağırın. Ama sıcak yol içinde “biraz daha bekleyip” daha fazla olay için batch yapmak tick'i kaçırmanıza neden olur—beklemek tick'i kaçırmanın yoludur.\n\nBir şey yavaşladığında onu bir containment (izolasyon) sorunu gibi ele alın. Bir handler yavaşlarsa, onu kendi buffer'ının arkasına izole edin ve ana döngüyü engellemek yerine hafif bir iş öğesi yayınlayın. Bir istemci yavaşsa, yayıncıyı tıkamasına izin vermeyin; her istemciye küçük bir gönderim kuyruğu verin ve eski güncellemeleri düşürün veya birleştirin ki en son durumu koruyun. Buffer derinliği büyürse, kenarda geri basınç uygulayın (o tick için ekstra giriş kabul etmeyi durdurun veya özellikleri bozulun).\n\nİşler yolunda gidiyorsa sayılar sıkıcı kalır: backlog derinliği sıfıra yakın dalgalanır, düşürülen/birleştirilen olaylar nadir ve açıklanabilir olur ve p99 gerçekçi yük altında tick bütçenizin altında kalır.\n\n## Gecikme sıçramalarına yol açan yaygın hatalar\n\nÇoğu gecikme sıçraması kendimiz tarafından yaratılır. Kod hızlı olabilir ama sistem diğer thread'leri, OS'yi veya CPU önbelleği dışındaki her şeyi beklediğinde durur.\n\nTekrarlayan hatalar şunlardır:

  • Basit geldiği için her yerde paylaşılan kilitler kullanmak. Bir içerilmiş kilit birçok isteği bekletebilir.

  • Sıcak yolda yavaş I/O karıştırmak: senkron loglama, veritabanı yazmaları veya uzak çağrılar.

  • Sınırsız kuyruklar tutmak. Bu aşırı yükü gizler ta ki saniyelerce backlog oluşana kadar.

  • Ortalamaları izlemek yerine p95 ve p99'u görmezden gelmek.

  • Erken aşırı tuning yapmak. Thread pinleme, gecikmeler GC, içerme veya soket beklemelerinden geliyorsa yardımcı olmaz.\n\nSıçramaları azaltmanın hızlı yolu beklemeleri görünür ve sınırlı yapmak: yavaş işleri ayrı yollara koyun, kuyrukları sınırlandırın ve dolduğunuzda ne olacağını (düşür, yükü at, birleştir veya geri basınç) baştan kararlaştırın.\n\n## Öngörülebilir gecikme için hızlı kontrol listesi\n\nÖngörülebilir gecikmeyi bir özellik gibi ele alın, kazara ortaya çıkan bir durum gibi değil. Kodu ayarlamadan önce sistemin net hedefleri ve guardrail'ları olduğundan emin olun.\n\n- Açık bir p99 hedefi belirleyin (gerekirse p99.9 da), sonra aşama başına gecikme bütçesi yazın.

  • Sıcak yolu engelleyici I/O'dan arındırın. I/O olması gerekiyorsa, onu yan yola taşıyın ve geciktiğinde ne yapacağınızı kararlaştırın.

  • Sınırlı kuyruklar kullanın ve aşırı yük davranışını tanımlayın (düşür, yükü at, birleştir veya geri basınç).

  • Sürekli ölçün: backlog derinliği, aşama başına süre ve tail gecikme.

  • Sıcak döngüde tahsisi minimize edin ve profillerde kolayca görünür kılın.\n\nBasit bir test: bir patlamayı simüle edin (normal trafiğin 10 katı, 30 saniye). Eğer p99 patlarsa, beklemenin nerede olduğunu sorun: büyüyen kuyruklar, yavaş bir tüketici, GC duraklaması veya paylaşılan bir kaynak mı?\n\n## Sonraki adımlar: bunu kendi uygulamanıza nasıl uygularsınız\n\nDisruptor desenini bir kütüphane seçimi olarak değil, bir iş akışı olarak görün. Özellik eklemeden önce küçük bir dilim ile öngörülebilir gecikmeyi kanıtlayın.\n\nAnında hissetmesi gereken tek bir kullanıcı eylemi seçin (ör. “yeni fiyat geldi, UI güncelleniyor”). Uçtan uca bütçeyi yazın ve baştan itibaren p50, p95 ve p99'u ölçün.\n\nGenellikle işe yarayan sıra şudur:

  • Bir giriş, bir çekirdek döngü ve bir çıktı içeren ince bir boru hattı inşa edin. Yük altında erken p99'u doğrulayın.

  • Sorumlulukları açıkça belirleyin (kim durumu sahibi, kim yayınlıyor, kim tüketiyor) ve paylaşılan durumu küçük tutun.

  • Eşzamanlılık ve tamponlamayı küçük adımlarla ekleyin ve değişiklikleri geri alınabilir tutun.

  • Bütçe dar olduğunda kullanıcılara yakın dağıtın, sonra gerçekçi yük altında tekrar ölçün (aynı yük boyutları, aynı patlama desenleri).\n\nEğer Koder.ai (koder.ai) üzerinde geliştiriyorsanız, önce olay akışını Planning Mode'da haritalamak, kuyrukların, kilitlerin ve servis sınırlarının kazara ortaya çıkmasını engellemede yardımcı olabilir. Snapshots ve rollback, throughput'u artırıp p99'u kötüleştiren değişiklikleri geri almak için tekrar eden gecikme deneylerini daha güvenli kılar.\n\nÖlçümleri dürüst tutun. Sabit bir test senaryosu kullanın, sistemi ısıtın ve hem throughput hem gecikmeyi kaydedin. Yük altında p99 sıçradığında hemen "kod optimize edelim"e başlamayın. Önce GC, gürültülü komşular, log patlamaları, thread zamanlaması veya gizli bloklayan çağruları arayın.

SSS

Ortalama gecikme iyi görünürken uygulamam neden takılıyor?

Ortalamalar nadir duranmaları gizler. Çoğu işlem hızlı olsa da birkaç tanesi çok daha uzun sürüyorsa, kullanıcılar bunu takılma veya “gecikme” olarak hisseder; özellikle ritmin önemli olduğu gerçek zamanlı akışlarda.

Tail gecikmeyi (ör. p95/p99) izleyin; fark edilir duraklamalar orada yaşanır.

Gerçek zamanlı sistemlerde throughput ile latency arasındaki fark nedir?

Throughput (verim) saniyede ne kadar iş tamamladığınızdır. Latency (gecikme) ise tek bir işlemin uçtan uca ne kadar sürdüğüdür.

Yüksek verime sahip olabilirsiniz ama arada uzun süren istekler olursa gerçek zamanlı uygulamalar yine de yavaş hissedilir.

p95/p99 gecikme bana ne söylüyor, neden umursamalıyım?

Tail gecikme (p95/p99) en yavaş istekleri ölçer, tipik olanı değil. p99 demek, işlemlerin %1'inin bu değerden daha uzun sürdüğüdür.

Gerçek zamanlı uygulamalarda bu %1, duyulan titreşimlere yol açar: ses patlamaları, rubber-banding, gösterge titremeleri veya kaçırılan tick'ler.

Kodum hızlıysa uçtan uca gecikme genelde nereden geliyor?

Çoğu zaman zaman beklemekle geçer, hesaplama ile değil:

  • Ağ gecikmeleri ve yeniden denemeler
  • Diğer işlerin arkasında kuyrukta bekleme
  • Thread zamanlaması ve context switch'ler
  • Depolama duraklamaları (kilitler, cache miss'ler, disk flush)
  • Seri hale getirme ve kopyalama

2 ms çalışan bir handler bile birkaç yerde beklerse uçtan uca 60–80 ms alabilir.

Algoritmaların ötesinde gecikme sıçramalarına (jitter) en çok ne sebep olur?

Yaygın jitter kaynakları şunlardır:

  • Garbage collection veya tahsisçi çatışmaları
  • Kilit içerme (genellikle boş olan kilitlerde konvoylar)
  • Pointer-ağırlıklı veya dağınık veri nedeniyle cache miss'ler
  • Sıcak yol içinde engelleyici I/O (loglama, DNS, disk, senkron çağrılar)
  • Çok fazla thread el değişimi ve kuyruklar

Hataları ayıklamak için duraklamaları tahsis oranı, context switch sayısı ve kuyruk derinliği ile ilişkilendirin.

Disruptor deseni sadedeleştirilmiş olarak nedir?

Disruptor, olayları küçük ve tutarlı gecikmelerle bir boru hattından geçirmek için kullanılan bir desendir. Öncelikle önceden ayrılmış bir ring buffer ve sıra numaraları kullanır; tipik paylaşılan kuyrukların neden olduğu beklemeleri, tahsisleri ve uyanma/uyutma döngülerini azaltmayı hedefler.

Amaç, gecikmenin “sıkıcı” (tutarlı) olmasını sağlamaktır; sadece ortalama hızlı değil.

Önceden ayırma ve nesne yeniden kullanımı öngörülebilir gecikmeye nasıl yardımcı olur?

Sıcak döngüde nesneleri/buffer'ları önceden ayırın ve yeniden kullanın. Bu şunları azaltır:

  • Garbage collection baskısı
  • Beklenmedik heap büyümeleri
  • Rastgele tahsis gecikmeleri

Ayrıca olay verisini kompakt tutun ki CPU her olayda daha az bellek dokunsun (daha iyi cache davranışı).

Gerçek zamanlı işlem için tek iş parçacıklı döngü, sharding mi yoksa worker pool mu kullanılmalı?

Mümkünse her parça için tek yazıcı yolu kullanın (single-writer). Bu, reasoning'i kolaylaştırır ve içeriği paylaşırken koordinasyon ihtiyacını azaltır.

Ölçeklemek gerektiğinde, aynı sıcak kuyruğu paylaşmaktansa kullanıcıId veya enstrümanId gibi anahtarlarla sharding yapın; böylece her shard'ın kendi sıcak yolu olur.

Batching ne zaman yardımcı olur, ne zaman zarar verir?

Batching overhead'i azaltır ama beklemeye neden olursa gecikmeyi kötüleştirir.

Pratik kural: batch'i hem boyut hem de süre ile sınırlandırın (ör. “en fazla N olay veya en fazla T mikro saniye, hangisi önce gerçekleşirse”). Böylece batch, gizlice gecikme bütçenizi bozmaz.

Düşük jitterlı bir boru hattını tasarlamak için pratik adım adım yol nedir?

Önce uçtan uca bir gecikme bütçesi yazın (hedef ve p99), sonra bunu aşamalara bölün. Her el değişimini (kuyruklar, thread sınırları, ağ hop'ları, depolama çağrıları) haritalayın ve beklemeyi görünür kılın: kuyruk derinliği, aşama süreleri vb.

Sıcak yol dışında bırakılabilecek işleri asenkron yapın; sıcak yol genelde kullanıcıya anında görünen değişiklikleri üretir.

İçindekiler
Gerçek zamanlı uygulamalar kod hızlı olsa neden yavaş hissedilir?\n\nHızın iki yüzü vardır: throughput ve latency. Throughput saniyede ne kadar iş bitirdiğinizdir (istekler, mesajlar, kareler). Latency ise tek bir iş biriminin baştan sona ne kadar sürdüğüdür.\n\nBir sistem müthiş throughput'a sahip olabilir ama bazı istekler çok daha uzun sürüyorsa yine yavaş hissedilir. Bu yüzden ortalamalar yanıltır. Eğer 99 işlem 5 ms sürerken bir işlem 80 ms sürüyorsa, ortalama iyi görünür; ama 80 ms'i yaşayan kullanıcı takılmayı hisseder. Gerçek zamanlı sistemlerde nadir zirveler bütün hikayedir çünkü ritmi bozarlar.\n\nÖngörülebilir gecikme demek sadece düşük ortalamayı hedeflemek değil, çoğu işlemin dar bir aralıkta tamamlanmasını sağlamaktır. Bu yüzden ekipler tail'i (p95, p99) izler. Duraklamalar genellikle orada saklanır.\n\n50 ms'lik bir sıçrama ses ve video uygulamalarında (ses patlamaları), çok oyunculu oyunlarda (rubber-banding), gerçek zamanlı alım-satımda (fiyat kaçırma), endüstriyel izlemede (geç alarm) ve canlı panolarda (sayılarda sıçrama, güvenilmez uyarılar) önemli olabilir.\n\nBasit bir örnek: bir sohbet uygulaması çoğu zaman mesajları hızlı teslim eder. Ama arka planda bir duraksama tek bir mesajın 60 ms gecikmesine neden olursa, yazıyor göstergeleri titrer ve konuşma gecikmeli hissedilir; sunucu ortalamada “hızlı” görünse bile.\n\nGerçek zamanlı hissetmek istiyorsanız daha az sürpriz gerekir, sadece daha hızlı kod değil.\n\n## Gecikme temelleri: zaman gerçekten nerede harcanıyor\n\nÇoğu gerçek zamanlı sistem CPU'nun zorlanmasından yavaş değildir. Yavaş hissetmelerinin nedeni işin çoğunun beklemede geçmesidir: çalıştırılmayı beklemek, bir kuyruğun arkasında beklemek, ağda beklemek veya depolamayı beklemek.\n\nUçtan uca gecikme “bir şey oldu”dan “kullanıcı sonucu gördü”ye kadar geçen tam süredir. Handler'ınız 2 ms çalışsa bile istek beş farklı yerde duraklarsa yine 80 ms alabilir.\n\nYolu şu şekilde bölmek faydalıdır: \n\n- Ağ zamanı (istemciden kenara, servisler arası, yeniden denemeler)\n- Zamanlama (thread'in çalışmayı beklemesi)\n- Kuyruk zamanı (işin diğer işlerin arkasında beklemesi)\n- Depolama zamanı (disk, veritabanı kilitleri, cache miss'ler)\n- Seri hale getirme zamanı (veriyi kodlama/çözme)\n\nBu beklemeler üst üste biner. Burada birkaç milisaniye toplandığında “hızlı” bir yol yavaş bir deneyime dönüşür.\n\nTail gecikme kullanıcıların şikayet etmeye başladığı yerdir. Ortalama gecikme iyi görünse bile p95 veya p99, en yavaş %5 veya %1'i gösterir. Aykırılıklar genellikle nadir duraklamalardan gelir: bir GC döngüsü, host üzerindeki gürültülü bir komşu, kısa süreli kilit içerme, bir cache doldurma veya kuyruğun oluştuğu bir patlama.\n\nSomut örnek: fiyat güncellemesi ağda 5 ms içinde gelir, meşgul bir worker için 10 ms bekler, diğer olayların arkasında 15 ms geçirir, sonra veritabanında 30 ms'lik bir duraklama yaşar. Kodunuz hâlâ 2 ms çalışmış olabilir, ama kullanıcı 62 ms beklemiştir. Amaç her adımı tahmin edilebilir kılmaktır, sadece hesaplamayı hızlı yapmak değil.\n\n## Kod hızının ötesindeki jitter kaynakları\n\nHızlı bir algoritma bile zaman başına düşen süreler dalgalanıyorsa yine yavaş hissedilebilir. Kullanıcılar ortalamayı değil zirveleri fark eder. Bu dalgalanma jitter'dır ve genellikle kodunuzun tamamen kontrolünde olmayan şeylerden gelir.\n\nCPU önbellekleri ve bellek davranışı gizli maliyetlerdir. Sık erişilen veri cache'e sığmazsa CPU RAM'i bekler. Nesne-ağır yapılar, dağınık bellek ve “bir kez daha bir bak” durumları tekrarlayan cache miss'lere dönüşebilir.\n\nBellek tahsisi kendi rastgeleliğini ekler. Kısa ömürlü çok sayıda nesne ayırmak heap üzerinde basınç yaratır; bu daha sonra duraklamalar (garbage collection) veya allocator çatışması olarak görünür. GC olmasa bile sık tahsisler bellek parçalanmasına ve locality kaybına neden olabilir.\n\nThread zamanlaması başka bir yaygın kaynaktır. Bir thread deschedule edildiğinde context switch maliyeti ve cache sıcaklığının kaybı ortaya çıkar. Yoğun bir makinede “gerçek zamanlı” thread'iniz alakasız işlerin arkasında bekleyebilir.\n\nKilit içerme, öngörülebilir sistemlerin sıklıkla çöktüğü yerdir. “Genelde boş” olan bir kilit konvoya dönüşebilir: thread'ler uyanır, kilit için mücadele eder ve birbirlerini tekrar uyuturlar. İş yine yapılır, ancak tail gecikme uzar.\n\nI/O beklemeleri her şeyi gölgede bırakabilir. Tek bir syscall, dolu bir ağ tamponu, TLS el sıkışması, disk flush veya yavaş DNS sorgusu keskin bir sıçrama yaratabilir; hiçbir mikro-optimizasyon bunu tamamen düzeltmez.\n\nJitter avına çıktıysanız, cache miss'lerini (çoğunlukla pointer-ağır yapılar ve rastgele erişim neden olur), sık tahsisleri, çok fazla thread veya gürültülü komşulardan kaynaklanan context switch'leri, kilit içerme ve sıcak yolda bloklayan herhangi bir I/O'yu arayın.\n\nÖrnek: bir fiyat-ticker servisi mikrosaniyelerde güncelleme hesaplayabilir, ama tek bir senkronize logger çağrısı veya içerilmiş bir metrik kilidi ara sıra onlarca milisaniye ekleyebilir.\n\n## Martin Thompson ve Disruptor deseni nedir\n\nMartin Thompson, düşük gecikme mühendisliğinde sadece ortalama hız değil, baskı altındaki sistem davranışına odaklanmasıyla bilinir. LMAX ekibiyle birlikte Disruptor desenini popülerleştirmeye katkıda bulundu; bu desen, olayları küçük ve tutarlı gecikmelerle sistemde taşımak için referans bir yaklaşımdır.\n\nDisruptor yaklaşımı, birçok “hızlı” uygulamayı öngörülemez yapan şeye yanıt olarak ortaya çıktı: içerme ve koordinasyon. Tipik kuyruklar genellikle kilitlere veya yoğun atomik operasyonlara dayanır, thread'leri açıp kapatır ve üreticiler ile tüketiciler paylaşılan yapılar üzerinde rekabet ettiğinde bekleme patlamaları yaratır.\n\nKuyruk yerine Disruptor bir ring buffer kullanır: slotlarda olayları tutan sabit boyutlu bir döngüsel dizi. Üreticiler bir sonraki slotu talep eder, veriyi yazar, sonra bir sıra numarası yayınlar. Tüketiciler bu sırayı takip ederek sıralı şekilde okur. Buffer önceden ayrıldığı için sık tahsislardan kaçınır ve garbage collector baskısını azaltırsınız.\n\nAna fikirlerden biri tek-yazar ilkesidir: paylaşılan bir durum parçasından sorumlu tek bir bileşen tutun (ör. ring boyunca ilerleyen cursor). Daha az yazar “sırada kim var?” anlarını azaltır.\n\nGeri basınç açık ve nettir. Tüketiciler geride kaldığında, üreticiler hâlâ kullanımda olan bir slota ulaşır. O noktada sistem beklemek, düşürmek veya yavaşlamak zorundadır; ama bunu kontrol edilen, görünür bir şekilde yapar, sonsuz büyüyen bir kuyruğun arkasına gizlemek yerine.\n\n## Gecikmeyi tutarlı kılan temel tasarım fikirleri\n\nDisruptor tarzı tasarımları hızlı yapan şey bir mikro-optimizasyon değil; sistemin kendi hareketli parçalarıyla kavga ettiğinde ortaya çıkan öngörülemez duraklamaları ortadan kaldırmaktır: tahsisler, cache miss'ler, kilit içerme ve sıcak yolda karışan ağır işler.\n\nKullanışlı bir zihinsel model montaj hattıdır. Olaylar sabit rota boyunca net el değişimleriyle hareket eder. Bu paylaşılan durumu azaltır ve her adımı basit ve ölçülebilir tutmayı kolaylaştırır.\n\n### Belleği ve veriyi tahmin edilebilir tutun\n\nHızlı sistemler sürpriz tahsislerden kaçınır. Buffer'ları önceden ayırıp mesaj nesnelerini yeniden kullanırsanız, garbage collection, heap büyümesi ve allocator kilitlerinin neden olduğu “bazen” spike'larından kurtulursunuz.\n\nAyrıca mesajları küçük ve sabit tutmak yardımcı olur. Her olayda dokunduğunuz veri CPU cache'ine sığarsa bellek beklemelerinde daha az zaman harcarsınız.\n\nPratikte en önemli alışkanlıklar şunlardır: her olay için yeni nesneler yaratmak yerine nesneleri yeniden kullanın, olay verisini kompakt tutun, paylaşılan durum için tek yazarı tercih edin ve koordinasyon maliyetini daha az sıklıkta ödeyecek şekilde dikkatli batching yapın.\n\n### Yavaş yolları bariz kılın\n\nGerçek zamanlı uygulamalar genellikle loglama, metrikler, yeniden denemeler veya veritabanı yazmaları gibi ekstra ihtiyaçlara sahiptir. Disruptor zihniyeti, bunları çekirdek döngüden izole etmektir, böylece onları engelleyemezler.\n\nCanlı bir fiyat akışında sıcak yol yalnızca bir tick'i doğrulayıp bir sonraki fiyat anlık görüntüsünü yayınlamak olabilir. Disk, ağ çağrıları veya ağır serileştirme gibi duraklatabilecek her şey ayrı bir tüketiciye veya yan kanala taşınır, böylece öngörülebilir yol öngörülebilir kalır.\n\n## Öngörülebilir gecikme için mimari seçimler\n\nÖngörülebilir gecikme büyük ölçüde bir mimari sorunudur. Çok sayıda thread aynı veriyi paylaşırsa veya mesajlar gereksiz yere ağ üzerinde sekip duruyorsa hızlı kodunuzda bile zirveler oluşur.\n\nÖnce kaç yazar ve okuyucunun aynı kuyruk veya buffer'ı etkilediğine karar verin. Tek bir üretici daha düzgün tutmak daha kolaydır çünkü koordinasyondan kaçınırsınız. Çoklu üretici düzenleri throughput'u artırabilir, ancak genellikle içerme ekleyip en kötü durum zamanlamasını daha öngörülemez kılar. Birden çok üreticiye ihtiyaç varsa, paylaşılan yazmaları azaltmak için olayları anahtara göre shard'layın (ör. userId veya instrumentId) böylece her shard'ın kendi sıcak yolu olur.\n\nTüketici tarafında, sıralama önemliyse tek bir tüketici en stabil zamanlamayı verir çünkü durum yerel bir thread'te kalır. İşçi havuzları görevler gerçekten bağımsızsa yardımcı olur, ama zamanlama gecikmeleri ekler ve dikkatli olmazsanız işi yeniden sıralayabilir.\n\nBatching bir başka takastır. Küçük batch'ler overhead'i keser (daha az uyanma, daha az cache miss), ama batch için olayları bekletmek beklemeyi artırabilir. Gerçek zamanlı bir sistemde batch yapıyorsanız bekleme süresini sınırlandırın (örneğin “en fazla 16 olay veya 200 mikro saniye, hangisi önce gerçekleşirse”).\n\nServis sınırları da önemlidir. Sıkı gecikme gerektiğinde işlem içi (in-process) mesajlaşma genellikle en iyisidir. Ölçek için ağ hop'ları gerekliyse karşılığında her hop kuyruklar, yeniden denemeler ve değişken gecikme ekler. Hop gerekiyorsa protokolü basit tutun ve sıcak yolda fan-out'tan kaçının.\n\nPratik bir kural seti: mümkün olduğunda shard başına tek-yazar yolu tutun, bir sıcak kuyruğu paylaşmak yerine anahtarlarla shard'layarak ölçekleyin, batch yaparken kesin bir zaman limiti koyun, sadece paralel ve bağımsız işler için işçi havuzları ekleyin ve her ağ hop'unu ölçene kadar potansiyel bir jitter kaynağı olarak değerlendirin.\n\n## Adım adım: düşük-jitter bir boru hattı tasarlamak\n\nKod yazmaya başlamadan önce yazılı bir gecikme bütçesiyle başlayın. Bir hedef seçin (iyi hissettiren nedir) ve bir p99 belirleyin (altında kalmanız gereken sınır). Bu sayıyı girdi, doğrulama, eşleştirme, kalıcılık ve dışa güncellemeler gibi aşamalara bölün. Bir aşamanın bütçesi yoksa sınırı yoktur.\n\nSonra tam veri akışını çizin ve her el değişimini işaretleyin: thread sınırları, kuyruklar, ağ hop'ları ve depolama çağrıları. Her el değişimi jitter'in saklanabileceği yerdir. Gördüğünüzde, azaltabilirsiniz.\n\nTasarımı dürüst tutacak bir iş akışı:SSS
Paylaş