Yirmi yıllık sistem ve network yöneticiliği kariyerimde öğrendiğim en net şey şudur: Yüksek trafik sistemleri nasıl düşer sorusunun cevabı hiçbir zaman “yetersiz sunucu kaynağı” değildir. Sistemleri öldüren şey genellikle saniyede yüz binlerce istek alan devasa makineler değil, o makinelerin arkasında unutulmuş küçücük bir timeout parametresidir. Bir keresinde, büyük bir yerli e-ticaret altyapısında tam olarak bu yüzden uzun süren bir felaket yaşadık.
O gün yaşadığımız sorun, ne veritabanı sunucusunun yetersizliğiydi ne de network bant genişliğinin dolmasıydı. Her şey, bir mikroservisin diğerine attığı istekte varsayılan (default) timeout değerinin 30 saniye olarak kalmasıyla başladı. Tek bir servis yavaşlayınca, tüm sistem bir anda domino taşları gibi devrildi.
Zincirleme Reaksiyon: Sağlık Kontrolü (Health Check) Nasıl Tetikçi Olur?
Yüksek trafikli bir sistem tasarlarken her düğümün (node) durumunu izlemek için load balancer arkasına health check mekanizmaları koyarız. Ancak bu mekanizmayı yanlış kurgularsanız, kendi topuğunuza sıkmış olursunuz. Benim de dahil olduğum bir projede, her 5 saniyede bir çalışan /health endpoint’i arka planda veritabanına basit bir SELECT 1 sorgusu atıyordu.
Sistem normal çalışırken hiçbir sorun yoktu. Fakat veritabanında ağır bir raporlama sorgusu (ki bunu yazan arkadaşa o gün çok kızmıştım) indeks kullanmadığı için PostgreSQL üzerindeki disk I/O limitlerini zorlamaya başladı. Bu yavaşlama anında health check sorgularını da etkiledi.
Süreç tam olarak şöyle işledi:
- Veritabanı yavaşladı,
/healthendpoint’leri timeout vermeye başladı. - Load balancer, yanıt alamadığı için sağlıklı çalışan 10 uygulama sunucusunu tek tek “unhealthy” olarak işaretleyip trafik dışı bıraktı.
- Ayakta kalan son birkaç sunucuya bir anda tüm trafik bindi.
- Bu son sunucular anında OOM (Out of Memory) yiyerek çöktü ve tüm sistem tamamen karanlığa gömüldü.
Bağlantı Havuzları (Connection Pools) ve Yalancı Güven Hissi
Yüksek trafik sistemleri nasıl düşer sorusunun bir diğer popüler cevabı da veritabanı bağlantı limitleridir. Birçok yazılımcı, uygulamanın performansını artırmak için connection pool limitini olabildiğince yüksek tutmak ister. “Postgres güçlü makine, verelim 500 bağlantı” mantığıyla yola çıkarlar.
Ancak unuttukları şey şudur: PostgreSQL’de her aktif bağlantı (backend process) RAM ve CPU tüketir. Saniyede binlerce isteğin geldiği bir anda, pool limitiniz çok yüksekse ve sorgularınız milisaniyeler yerine saniyeler sürmeye başlarsa, veritabanı sunucusu bir anda yüzlerce aktif bağlantıyı yönetmeye çalışırken kilitlenir.
Aşağıdaki tabloda, kendi projelerimde test ettiğim connection pool stratejilerinin yüksek trafik altındaki davranışlarını özetledim:
| Strateji | Trafik Altındaki Davranış | Risk Seviyesi | Çözüm Önerisi |
|---|---|---|---|
| Sınırsız / Çok Yüksek Limit | CPU spike, OOM, kilitlenme | Çok Yüksek | PgBouncer gibi bir connection pooler kullanmak |
| Dar / Küçük Limit | Kuyrukta bekleme, istek zaman aşımları | Orta | Kuyruk sürelerini (queue timeout) optimize etmek |
| Dinamik Ölçekleme | Bağlantı açma-kapama maliyeti, latency | Yüksek | Sabit ve optimize edilmiş pool boyutu belirlemek |
Ben bu hatayı bir üretim ERP’sinin gerçek zamanlı izleme ekranını yazarken yaptım. PostgreSQL connection limitini yüksek tutup, Nginx reverse proxy arkasındaki FastAPI uygulamalarını kontrolsüzce ölçekledim. Sonuç? Veritabanı CPU’su tavan yaptı ve üretim bir süre durdu. O günden beri PgBouncer’ı yanımdan ayırmam.
Sistemlerin Çöküşünü Engellemek İçin Ne Yapmalıyız?
Yüksek trafiği yönetmek sadece daha büyük sunucular kiralamakla çözülmez. Altyapıyı tasarlarken “bu sistem kesinlikle çökecek, peki nasıl çökecek?” sorusunu sormanız gerekir. Zararı minimize etmek (graceful degradation) ana hedefimiz olmalıdır.
Kendi sistemlerimde uygulayığım ve hayat kurtaran üç temel kuralı buraya bırakıyorum:
- Circuit Breaker Kalıbını Uygulayın: Eğer bağımlı olduğunuz bir dış servis (örneğin bir ödeme geçidi) yavaşladıysa, ona istek atmaya devam edip kendi kaynaklarınızı tüketmeyin. Devreyi açın, hatayı anında dönün ve sistemin diğer kısımlarını kurtarın.
- Agresif Timeout Değerleri Kullanın: Varsayılan (default) timeout değerleri bir sistemin en büyük düşmanıdır. 30 saniyelik timeout olmaz. İç servisler arası iletişimde timeout değerleriniz milisaniyeler mertebesinde olmalıdır.
- Rate Limiting ve Shedding: Gelen trafiğin tamamını kabul etmek zorunda değilsiniz. Kapasitenizin üzerindeki istekleri 429 (Too Many Requests) koduyla hızlıca reddedin ki, içerideki mevcut işlemler başarıyla tamamlansın.
Sistem mimarisi, sadece kod yazmaktan ziyade bir organizasyon ve limitleri yönetme sanatıdır. “Bizim sistem asla düşmez” diyen bir mühendis görürseniz, muhtemelen henüz yeterli trafikle karşılaşmamıştır.
Peki, sizin sisteminiz en son ne zaman ve hangi küçük parametre yüzünden çöktü? Yorumlarda benimle paylaşın, üzerine konuşalım.