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.

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.
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.
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.
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.
Çoğu zaman zaman beklemekle geçer, hesaplama ile değil:
2 ms çalışan bir handler bile birkaç yerde beklerse uçtan uca 60–80 ms alabilir.
Yaygın jitter kaynakları şunlardır:
Hataları ayıklamak için duraklamaları tahsis oranı, context switch sayısı ve kuyruk derinliği ile ilişkilendirin.
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.
Sıcak döngüde nesneleri/buffer'ları önceden ayırın ve yeniden kullanın. Bu şunları azaltır:
Ayrıca olay verisini kompakt tutun ki CPU her olayda daha az bellek dokunsun (daha iyi cache davranışı).
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 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.
Ö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.