Kullanıcı F5'e basmadan yeni veriyi görmüyor: WebSocket, SSE ve yoklama arasında karar
Karar tek soruyla başlar: istemci, veriyi aldığı hızda sunucuya veri göndermek zorunda mı? Cevap evetse (sohbet, eşzamanlı düzenleme, imleç paylaşımı, canlı işlem ekranı) WebSocket gerekir. Sadece sunucu haber veriyorsa (sipariş durumu, bildirim sayacı, operasyon panosu, rapor ilerlemesi) sunucu gönderimli olaylar, yani SSE, daha az kod ve daha az bakım demektir. Ekranın on saniye eski kalması kimseyi rahatsız etmiyorsa zaten yoklama yeter ve en ucuz seçenek odur. Zor kısım protokol seçimi değil: bağlantı koptuğunda ne olacağı, kanala kimin abone olabileceği, olayın ikinci sunucuya nasıl ulaşacağı ve aradaki vekil sunucuların bağlantıyı kaç saniyede kestiği.
Önce ekranın bayatlama bütçesini yazın
"Canlı olsun" bir gereksinim değil. Gereksinim şudur: bu ekrandaki veri en fazla kaç saniye eski kalabilir? Sayıyı yazınca tartışmanın çoğu kapanır. Sipariş durumu ekranı için 10 ila 30 saniye neredeyse her zaman yeter. Kargo takip haritasında 5 saniye makul. Sohbet ve yazıyor göstergesi 300 milisaniye civarını ister. Başkasının imlecini ekranda görmek istiyorsanız 50 milisaniyeden söz ediyorsunuz. İç operasyon panosunda 60 saniye kimsenin dikkatini çekmez.
İki soru daha var. Kaç eşzamanlı bağlantı olacak: 30 kişilik bir depo ekibi mi, kampanya gününde aynı sayfada duran 50 bin ziyaretçi mi? Ve olay kaybı kabul edilebilir mi? Bildirim sayacının bir güncellemeyi kaçırması sorun değildir, sayfa yenilenince kendini düzeltir. Ödeme sonucunun kaçması sorundur, çünkü kullanıcı orada bekliyor ve ikinci kez ödemeyi deneyecek.
Yoklama hâlâ çoğu ekran için doğru cevap
Yoklamayı (polling) ilkel bulup atlamak yaygın bir hata. Matematiği yapalım: aynı anda açık 5.000 sekme, 5 saniyede bir istek, saniyede 1.000 istek eder. Kulağa çok geliyor, ama o isteklerin büyük kısmı "değişen yok" cevabı döner ve bu cevabı ucuzlatmanın standart yolları var.
Birincisi koşullu istek. Yanıta ETag koyun, istemci If-None-Match ile sorsun, değişmemişse 304 dönün. Gövde hiç üretilmez, hiç aktarılmaz. İkincisi değişiklik imleci: istemci gördüğü son sürümü (?since=...) gönderir, sunucu yalnızca o tarihten sonrasını döner. Üçüncüsü ve en çok atlanan: sekme arka plandayken yoklamayı durdurun. Page Visibility API ile üç satır iş, kazanç ise genelde trafiğin yarısı, çünkü insanlar sekmeleri açık bırakıp başka işe geçiyor. Önbellek başlıklarının ayrıntısı önbellek stratejisi yazısında duruyor.
Bir de aralığı nereden seçtiğinize dikkat edin. "3 saniyede bir" varsayılanı çoğu panoda gereksiz; bayatlama bütçesi 30 saniyeyse 30 saniyede bir sorun. Aynı anda açık bağlantı tutmadığınız için bu seçenek ölçeklenmesi en kolay olan da.
Uzun yoklama bir ara çözüm, ama bedeli SSE ile aynı
Uzun yoklamada (long polling) sunucu isteği hemen yanıtlamaz, veri gelene kadar bekletir. Socket.IO istemcisi varsayılan olarak bununla başlar ve sonra WebSocket'e yükseltmeye çalışır; Vercel'in dokümanı bile istemciyi doğrudan transports: ['websocket'] ile kurmanızı hatırlatıyor.
Mantık şu: uzun yoklamada da bağlantı açık tutuluyor, yani sunucu tarafındaki maliyet SSE ile aynı. Karşılığında yeniden bağlanmayı, sıra takibini ve zaman aşımı yönetimini kendiniz yazıyorsunuz. Eski tarayıcı desteği gerekmiyorsa uzun yoklamayı tercih etmenin pratik bir sebebi kalmıyor.
SSE: en az iş, en çok senaryoyu kapsayan seçenek
Sunucu gönderimli olaylar (Server-Sent Events) sıradan bir HTTP yanıtıdır. İçerik türü text/event-stream, bağlantı açık kalır, sunucu satır satır olay yazar. Tarayıcı tarafında EventSource var ve size iki şeyi hazır veriyor: bağlantı düştüğünde kendiliğinden yeniden bağlanır ve gördüğü son olayın kimliğini Last-Event-ID başlığıyla gönderir. Sunucuda kısa bir olay tamponu tutuyorsanız, kopma anında kaçırılan olayları geri oynatabilirsiniz. retry alanıyla yeniden bağlanma süresini sunucudan söylersiniz.
İki sınırı baştan bilin. HTTP/1.1 üzerinde tarayıcı aynı alan adına 6 bağlantıdan fazlasını açmıyor ve MDN'in belirttiği gibi bu sınır sekme başına değil tarayıcı başına. Kullanıcı yedinci sekmeyi açtığında o sekme sessizce asılı kalır. HTTP/2 üzerinde eşzamanlı akış sayısı sunucuyla pazarlanır ve varsayılan 100'dür, yani sorun pratikte ortadan kalkar. Sitenizi HTTP/2 ile sunuyorsanız bu maddeyi geçebilirsiniz.
İkinci sınır kimlik doğrulamada. EventSource yapıcısı yalnızca withCredentials alıyor, özel başlık göndermenin yolu yok. Yani Authorization: Bearer ... gönderemezsiniz. Kalan iki yol çerez ya da URL'de token. URL'ye koyduğunuz token erişim kayıtlarına, vekil sunucu loglarına ve bazı durumlarda Referer başlığına düşer; mecbursanız tek kullanımlık ve dakikalarla ölçülen ömürlü bir bilet üretin. Akışın tek yönlü olması genelde sorun değil: istemci veri göndermek istediğinde normal bir POST atar.
WebSocket gerçekten gerektiğinde
İki yönlü ve mesaj başına yükü çok az. Her yoklama isteğinde giden onlarca satır HTTP başlığı yerine küçük bir çerçeve başlığı var. Sohbet, eşzamanlı doküman düzenleme, varlık göstergesi (kim şu an bu sayfada), oyun ve saniyede birkaç kez iki yönlü mesaj akan işlem ekranları bu protokolün asıl yeri.
Karşılığında yazacaklarınız listesi uzun: yeniden bağlanma (üstel geri çekilmeli), yeniden abonelik, kopma sonrası durum tazeleme, kalp atışı mesajları ve tüketici yavaş kaldığında geri baskı yönetimi. Vercel'in kendi örnek istemcisi bile 1 saniyeden başlayıp 30 saniyeye çıkan bir geri çekilme döngüsü yazıyor. Bu kodu yazmayı planlamıyorsanız protokolü kullanmaya hazır değilsiniz demektir.
Altyapı katmanı bu işi sessizce kesiyor
Kalıcı bağlantıyı sizin uygulamanız değil, aradaki katmanlar bitirir. AWS Application Load Balancer'ın boşta kalma zaman aşımı varsayılan olarak 60 saniyedir ve 1 ile 4000 saniye arasında ayarlanabilir. Kalp atışını da 60 saniyeye kurduysanız bir yarış durumu kurmuş olursunuz, bağlantı arada bir kopar ve sebebini kimse bulamaz.
Nginx'te WebSocket için proxy_http_version 1.1 ve Upgrade / Connection başlıklarını elle geçirmek zorundasınız, yoksa yükseltme isteği hiç geçmez. proxy_read_timeout varsayılanı da 60 saniye. SSE'de ise ayrıca proxy_buffering off (ya da yanıtta X-Accel-Buffering: no) gerekir; aksi halde olaylar tamponda bekler ve geliştirici makinesinde çalışan ekran canlıda hiçbir şey göstermez. Bu ikincisi klasik bir arıza, çünkü kod tarafında hata yok.
Yönetilen servislerde limit yazılı: AWS API Gateway'in WebSocket API'sinde boşta kalma süresi 10 dakika, tek bağlantının ömrü en fazla 2 saat ve ikisi de yükseltilemiyor. Vercel'de WebSocket desteği 22 Haziran 2026'da genel betaya açıldı; bağlantı fonksiyonun maksimum süresine ulaştığında kapanıyor ve yeni bağlantının aynı fonksiyon örneğine düşeceği garanti edilmiyor. Hepsinin söylediği aynı şey: bağlantı kalıcı değildir. Kopmayı hata değil, normal akışın parçası sayın.
Tek sunucuda çalışır, ikincisini eklediğinizde bozulur
En sık rastlanan mimari hata bu. Bağlantı tek bir sunucu örneğinde yaşıyor, olayı üreten kod ise başka bir örnekte çalışıyor. Belleğe yazdığınız abonelik listesi diğer örnekte yok, dolayısıyla kullanıcıların bir kısmı bildirimi alıyor bir kısmı almıyor. Test ortamında tek örnek koştuğu için sorun canlıya çıkana kadar görünmüyor.
Çözüm bir yayın/abonelik katmanı: Redis, NATS ya da Postgres'in LISTEN/NOTIFY mekanizması. Postgres'i seçecekseniz dokümandaki üç uyarıyı okuyun. Bildirim yükü varsayılan yapılandırmada 8000 bayttan kısa olmak zorunda. Bildirim ancak işlem commit edildiğinde iletilir. Ve dinleyen oturum yoksa bildirim kaybolur, üstelik bir dinleyici geride kalıp kuyruk dolarsa (standart kurulumda 8 GB) NOTIFY çağıran işlemler commit anında hata vermeye başlar. Bu yüzden veriyi kanaldan taşımayın, sadece "şu kayıt değişti" sinyalini geçirin; istemci ayrıntıyı normal API'den okusun.
Bir de yapışkan oturum meselesi var. WebSocket'te bağlantıyı bir örneğe sabitlemeniz gerekir, SSE'de gerekmez çünkü standart HTTP ölçekleme düzeni yeterlidir. Kaç örnek ve hangi çalıştırma modeli sorusu için Kubernetes gerekli mi yazısına bakabilirsiniz.
Kullanıcının bildirdiği hata çoğunlukla şu: tünelden çıktı, ekran yanlış
Canlı güncelleme projelerinin gerçek arızası kopma anında yaşanıyor. Bağlantı 40 saniye düşüyor, o arada üç olay üretiliyor, bağlantı geri geliyor ve ekran o üç olaydan habersiz şekilde yanlış veriyi göstermeye devam ediyor. Kimse hata mesajı görmüyor, çünkü teknik olarak hata yok.
Tasarım kalıbı basit: her olaya monoton artan bir sıra numarası verin. İstemci gördüğü son numarayı saklar, yeniden bağlandığında onu gönderir. Sunucu ya o numaradan devam eder ya da "tamponum bu kadar geriye gitmiyor, baştan çek" der. İkinci cevabı da yazmak zorundasınız, yoksa uzun kopmalardan sonra sessiz veri kayması kalıcı olur. SSE bu işin taşıma yarısını Last-Event-ID ile hazır veriyor, tamponu yine siz tutacaksınız: son 200 olay ya da son 60 saniye gibi.
Kabul testi de bir cümle: ağı 60 saniye kesin, geri açın, ekrana dokunmadan bekleyin. Ekran kendini toparlıyorsa canlı güncelleme var. F5 gerekiyorsa elinizde canlı güncelleme değil, canlı güncelleme yanılsaması var.
Canlı kanal, yetkiyi en kolay atladığınız yer
WebSocket el sıkışması aynı köken politikasının kapsamında değil ve tarayıcı çerezleri bu isteğe de ekliyor. Sunucu Origin başlığını açık bir beyaz listeye karşı doğrulamazsa, başka bir site kullanıcının oturumuyla sizin kanalınıza bağlanabilir ve akan veriyi okuyabilir. Saldırının adı Cross-Site WebSocket Hijacking ve OWASP'ın konuya ayrı bir kontrol listesi var. CORS alışkanlığı burada yanıltıyor: tarayıcı, sunucu izin vermese bile bağlantıyı kuruyor, kararı sunucuya bırakıyor.
Daha sık gördüğümüz ikinci hata yetkinin yanlış yerde kontrol edilmesi. Bağlantı açılırken kullanıcı doğrulanıyor, sonra "order:12345 kanalına abone ol" isteği hiç denetlenmiyor. Kanal adının tahmin edilmesi zor olması yetkilendirme değildir. Her abonelik isteği sunucuda, oturumdaki kimlikle doğrulanmalı ve çok kiracılı bir uygulamada kiracı kimliği istemcinin gönderdiği mesajdan değil oturumdan okunmalı. Modelin kendisi için yetkilendirme yazısı, kiracı izolasyonu için çok kiracılı SaaS mimarisi, genel API tarafı için API güvenliği yazısı işinizi görür.
Üçüncü nokta: yetki değişince açık bağlantıya ne olacak? Kullanıcının rolü kaldırıldığında iki saat boyunca açık duran bir bağlantı veri akıtmaya devam edebilir. Ya bağlantı ömrünü kısa tutup düzenli yeniden yetkilendirin, ya yetki değişikliğinde ilgili kanalları kapatın.
Hazır servis mi, kendi kurmak mı
Ably, Pusher, Supabase Realtime ve Azure SignalR gibi servisler işi bağlantı ve mesaj sayısı üzerinden fiyatlıyor. Az bağlantı ile çok mesaj akan bir senaryonun matematiği, çok bağlantı ile seyrek mesaj akan senaryodan tamamen farklı çıkıyor, o yüzden karar vermeden kendi iki sayınızı (eşzamanlı bağlantı ve günlük mesaj) fiyat sayfasına koyun.
Kendi kurmanın gizli maliyeti sunucu kirası değil. Nöbet, eşzamanlı bağlantı testi, sürüm sırasında açık bağlantıların nasıl devredileceği ve kopma dalgası geldiğinde yeniden bağlanma fırtınasını karşılama işi size kalıyor. Pratik ölçüt şu: canlı güncelleme ürününüzün ana döngüsü değilse, yani müşteri bu özellik için para ödemiyorsa, satın almak neredeyse her zaman daha ucuza gelir. Ana döngüsüyse kendiniz kurun, ama o zaman bunun bir ekip işi olduğunu kabul edin. Aynı kararın kimlik tarafındaki karşılığı kendimiz mi yazalım hazır servis mi yazısında.
Ölçülmeyen canlı sistem sessizce bozulur
İzlenecek liste kısa: eşzamanlı bağlantı sayısı, yeniden bağlanma oranı, olay gecikmesi (üretildiği andan ekranda göründüğü ana kadar, p95), kaçırılan olay sayısı ve bağlantı başına bellek.
Alarmı bağlantı sayısına değil yeniden bağlanma oranına kurun. Araya giren yeni bir vekil sunucu, düşürülmüş bir zaman aşımı ayarı ya da kalp atışını bozan bir sürüm önce orada görünür; kullanıcı şikâyeti günler sonra ve "bazen güncellenmiyor" şeklinde gelir. Hedef bağlamanın mekaniği için SLO ve hata bütçesi yazısı var. Yük testi tarafında da bir ayrım yapın: eşzamanlı bağlantı testi, istek/saniye testiyle aynı şey değil. Ölçtüğünüz şey bağlantı sayısı, bellek ve dosya tanıtıcı sınırları olur. Senaryo kurmak için yük testi yazısı iyi bir başlangıç.
Bu haftaya sığan üç adım
Bir: canlı olmasını istediğiniz her ekranın yanına bayatlama bütçesini saniye olarak yazın. "Sipariş listesi: 30 saniye", "sohbet: 0,3 saniye" gibi. Bu tablo olmadan yapılan protokol tartışması sonuçsuz kalır.
İki: mevcut yoklamanızı ölçün. Kaç istek gidiyor, kaçı 304 dönüyor, kaçı boş sonuç getiriyor? Koşullu istek ve sekme gizliyken durdurma eklendiğinde bütçeyi tutuyorsanız iş bitmiştir, kalıcı bağlantıya hiç girmeyin.
Üç: tutmuyorsa tek bir ekranda SSE ile başlayın ve iki metrikle çıkın: yeniden bağlanma oranı ve olay gecikmesi. Sonra ağı 60 saniye kesip ekranın kendini toparlayıp toparlamadığına bakın. Arka planda uzun süren işlerin ilerlemesini bildirmek istiyorsanız, önce o işleri istek yolundan çıkarmak gerekir; o taraf arka plan işleri ve kuyruk yazısında.
Bu konuda yardıma mı ihtiyacınız var?