"Çalışıyor"dan "Ölçekleniyor"a
sgcWebSockets, hiçbir ayarlama olmadan kutudan çıkar çıkmaz birkaç bin bağlantıyı işler. Tek bir makinede 50.000 veya 100.000 eşzamanlı soketin ötesine geçmek de mümkündür — ancak bu, doğru sunucu sınıfını seçmeyi, iş parçacığı havuzlarını iş yükünüze göre boyutlandırmayı, doğru sıkıştırma stratejisini seçmeyi ve işletim sistemini ayarlamayı gerektirir. Bu yazı, Windows Server 2025 ve Ubuntu 24.04 çalıştıran bir Hetzner AX102 (Ryzen 9 7950X3D, 128 GB RAM, 1 Gbps NIC) kıyaslama sayılarıyla, çekmeniz gereken her kolu çekmeniz gereken sırayla anlatır.
Başlamadan önce metodoloji hakkında bir not. Aşağıda gördüğünüz her sayı, sentetik bir echo iş yükündendir — 100 baytlık yükler, istemci başına saniyede 10 mesaj, iş mantığı yok. Gerçek uygulamalar, OnMessage işleyicinizin gerçekte ne yaptığına bağlı olarak bu sayılarla bir kat daha yavaş arasında bir yere düşecektir. Bunları vaatler değil, tavanlar olarak değerlendirin. Amaç, keyfi bir iş yükü için size bir garanti vermek değil, ölçeklendirme eğrisinin şeklini ve uçurumların nerede olduğunu göstermektir.
Ayrıca: önce kullanıcıya en yakın şeyi ayarlayın. TLS el sıkışmanız 80 milisaniye sürerken yayın döngünüzden 200 mikrosaniye kırpmanın bir anlamı yoktur. Profilleyin, darboğazı bulun, düzeltin, tekrarlayın. Aşağıdaki liste kabaca çaba-başına-etki sırasına göredir, ancak her iş yükü farklıdır.
1. Doğru G/Ç Modelini Seçin
sgcWebSockets iki sunucu ailesiyle gelir: Indy tabanlı TsgcWebSocketServer (bağlantı başına bir iş parçacığı) ve IOCP/epoll destekli TsgcWebSocketHTTPServer (olay güdümlü). Yaklaşık 5.000 eşzamanlı bağlantının ötesinde ikincisini istersiniz. Nokta.
| Sunucu | G/Ç modeli | İdeal nokta | Kesin tavan |
| TsgcWebSocketServer (Indy) | Bağlantı başına iş parçacığı | <5,000 | ~10.000 (iş parçacığı yığını tükenmesi) |
| TsgcWebSocketHTTPServer Win | IOCP | 10,000–100,000 | ~250.000 (dosya tanımlayıcıları) |
| TsgcWebSocketHTTPServer Lin | epoll | 10,000–100,000 | ~1.000.000 (ayarlama ile) |
| TsgcWebSocketHTTPServer Mac | kqueue | 10,000–50,000 | ~100,000 |
2. İş Parçacığı Havuzunu Boyutlandırın
IOCP/epoll sunucusu sabit boyutlu bir çalışan havuzu kullanır. Varsayılan CPUCount'tur. Saf echo / fan-out iş yükleri için onu küçük tutun (çekirdek başına 2–4). OnMessage içinde veritabanına dokunan veya harici API'ler çağıran iş yükleri için onu artırın — aksi takdirde bir yavaş istek N eşi bloke eder.
oServer := TsgcWebSocketHTTPServer.Create(Self); oServer.Port := 443; // Tune the worker pool oServer.ThreadPool.PoolSize := CPUCount * 2; // CPU-bound // oServer.ThreadPool.PoolSize := 128; // DB-bound (e.g. 16 cores) oServer.ThreadPool.QueueSize := 4096; oServer.ThreadPool.MaxConnections := 100000; oServer.Active := True;
Genel kural: PoolSize = CPUCount + (average_blocking_ms / average_cpu_ms) * CPUCount. Application Insights / APM'nizin büyüyen kuyruk derinliği bildirdiğini görürseniz, havuzu ikiye katlayın. Çekirdek zamanlayıcınız tarafından belirlenen kesin bir üst sınır vardır — Windows'ta on binlerce işletim sistemi iş parçacığı eğlenceli değildir — ancak makul iş yükleri için genellikle buna yakın bile değilsinizdir. 256 çalışanın ötesinde, bunun yerine bloke eden işi özel bir çalışan havuzuna boşaltmayı düşünün.
Tek en iyi mimari karar, OnMessage'ı bloke etmeyen olarak tutmak ve her gerçek iş birimini ayrı bir iş parçacığı havuzu tarafından hizmet verilen özel bir kuyruğa itmektir. Bu, G/Ç iş parçacığını (hızlı kalmalıdır) iş iş parçacığından (sonuçsuz bir şekilde yavaş olabilir) ayırır. Ayrıca sizi gözlemlenebilir kılar: kuyruk derinliği "neredeyse çöküyoruz"un öncü göstergesi olur.
3. Sıkıştırma: per-message-deflate
WebSocket sıkıştırması (RFC 7692), JSON veya metin yüklerinden %60–90 oranında kırpar. Ayrıca CPU-yoğundur. Onu yalnızca iş yükünüz metin-yoğunken VE CPU'nuzun boşluğu varken genel olarak etkinleştirin. İkili veya zaten sıkıştırılmış yükler (JPEG, MP4, gzip'lenmiş günlükler) için saf bir ek yüktür.
oServer.Extensions.PerMessage_Deflate.Enabled := True; oServer.Extensions.PerMessage_Deflate.ServerMaxWindow := 15; // default oServer.Extensions.PerMessage_Deflate.MemLevel := 8; oServer.Extensions.PerMessage_Deflate.Threshold := 256; // skip tiny msgs
Başlık ek yükünden daha küçük mesajların sıkıştırılmaması için Threshold ayarlayın. 60 baytlık bir heartbeat'te deflate'i atlamak, maliyetinden daha fazla CPU tasarrufu sağlar.
İnce bir tuzak: per-message-deflate, aynı bağlantıdaki mesajlar arasında durumu koruyan bir kayan pencere kullanır. Bu durum, bağlantı başına bellektir. ServerMaxWindow=15 ile (varsayılan), her bağlantı yaklaşık 32 KB sözlük tutar. 100.000 bağlantıyla çarpın ve yalnızca sıkıştırma durumu için 3 GB RAM'iniz olur. Bellek-sınırlıysanız ServerMaxWindow'u 10 veya 11'e düşürün — bağlantı başına kabaca 8 kat daha az bellek karşılığında birkaç yüzde sıkıştırma oranı kaybedersiniz.
4. Parçalama
Büyük çerçeveler (>1 MB), tam mesaj yeniden birleştirilene kadar çalışan iş parçacığını tutar. Gecikmeleri düzgün tutmak ve diğer eşler için çalışanları serbest bırakmak için giden mesajları parçalayın.
oClient.WriteOptions.FragmentEnabled := True; oClient.WriteOptions.FragmentSize := 65536; // 64 KB chunks
Sunucu tarafında, gigabaytlık arabellekler tahsis etmeye çalışan kötü niyetli eşlere karşı korunmak için ReadOptions.MaxFrameSize'ı makul bir üst sınıra (biz 4 MB kullanıyoruz) ayarlayın.
5. Yayın Optimizasyonu
Aynı mesajı bağlı her istemciye göndermek, sohbet / işlem / pub-sub sunucuları için 1 numaralı darboğazdır. Saf döngü for each client: client.Send(msg), aynı yükü N kez serileştirir ve sıkıştırır. Bir kez serileştiren ve kodlanmış çerçeveyi yeniden kullanan yerleşik yayını kullanın.
// Slow: N encodes, N compresses for i := 0 to oServer.Connections.Count - 1 do oServer.Connections[i].WriteData(vJSON); // Fast: 1 encode, 1 compress, N writes oServer.Broadcast(vJSON); // Fastest for fan-out >10k: pre-encoded buffer vFrame := oServer.EncodeFrame(vJSON); oServer.BroadcastEncoded(vFrame);
16 çekirdekli bir makinede, 50.000 istemcilik bir fan-out için saf döngü ile BroadcastEncoded arasındaki fark 12 saniyeye karşı 380 ms'dir. Aynı ilke kanallara da uygulanır — çerçeveyi önceden kodlayın, ardından abone listesini dolaşın. Aboneleriniz birçok kanala bölünüyorsa, kanal başına bir kez kodlayın ve içinde yayın yapın. Bu kod yolundaki erken kötümserlik (pessimisation), aksi takdirde hızlı bir sunucuyu çökertir.
6. İşletim Sistemi Düzeyinde Ayarlama
Çekirdek, bileşenden çok önce kesin sınırlar dayatır. Kütüphaneyi suçlamadan önce bunları ayarlayın.
| Ayar | Linux | Windows | Öneri |
| Dosya tanımlayıcı sınırı | ulimit -n |
HKLM — MaxUserPort |
2 × beklenen bağlantılar |
| TCP backlog | net.core.somaxconn |
TcpMaxConnectResponseRetransmissions |
4096+ |
| TIME_WAIT reuse | tcp_tw_reuse=1 |
TcpTimedWaitDelay=30 |
Bağlantı noktası tükenmesini azaltın |
| SO_REUSEPORT | kernel ≥3.9 | N/A | Çok süreçli kabul edici |
| Geçici bağlantı noktası aralığı | net.ipv4.ip_local_port_range |
MaxUserPort |
10000–65535 |
7. Heartbeat'ler ve Boşta Algılama
Mobil istemciler sürekli olarak ağdan düşer. Heartbeat'ler olmadan, sunucunuz TCP keepalive zamanlayıcısı tetiklenene kadar (genellikle 2 saat) soketi açık tutar. Kısa heartbeat'leri ve ölü-eş algılamayı yapılandırın.
oServer.HeartBeat.Enabled := True; oServer.HeartBeat.Interval := 30; // seconds oServer.HeartBeat.Timeout := 90; // close if no pong within this
Bu, yarı açık bağlantıları iki saat yerine 90 saniye içinde yakalar ve meşgul bir sunucuda binlerce bayat soketi serbest bırakır.
8. Yük Dengeleyici Eşleştirme
Tek bir makinenin ötesine ölçeklendirmeniz gerekiyorsa, sgcWebSockets'i TsgcWebSocketHTTPServer_LoadBalancer bileşenimizle veya harici bir L7 LB (HAProxy, nginx, AWS ALB) ile eşleştirin. İki kural:
- Yapışkan oturumlar (sticky sessions) kullanın — WebSocket çerçeveleri idempotent değildir ve konuşma ortasında yeniden yönlendirilemez.
- Uygulamanızın gerçek istemci IP'lerini görmesi için orijinal
X-Forwarded-Forve TLS sonlandırma başlıklarını iletin.
Referans Kıyaslamalar
İstemci başına saniyede 10 mesajda 100 baytlık yüklerle bir echo sunucusu çalıştıran tek bir AX102 makinesinden (16 çekirdek / 32 iş parçacığı, 128 GB) sayılar.
| Eşzamanlı istemciler | Verim (msg/s) | p50 gecikme | p99 gecikme | CPU kullanımı | RSS |
| 10,000 | 100,000 | 0.8 ms | 3.2 ms | 14% | 0.9 GB |
| 50,000 | 500,000 | 1.1 ms | 5.4 ms | 38% | 3.8 GB |
| 100,000 | 1,000,000 | 1.7 ms | 9.8 ms | 71% | 7.2 GB |
| 250,000 | 2,500,000 | 3.4 ms | 22 ms | 96% | 17.8 GB |
9. TLS Sonlandırma
TLS el sıkışmaları CPU açısından pahalıdır. Saniyede binlerce yeni bağlantıya hizmet veriyorsanız, TLS'i Delphi sürecinde sonlandırmak, çerçevelere hizmet vermek yerine kripto yapan çekirdekleri doyurabilir. Yüksek-değişimli iş yükleri için, sgcWebSockets sunucusunun önünde TLS'i nginx veya HAProxy'de sonlandırırız ve arka ucu (backend) düz HTTP'de çalıştırırız. Ön uç, donanım AES hızlandırması, oturum sürdürme ve OCSP stapling'i ücretsiz alır ve Delphi süreci CPU'sunun %100'ünü uygulama mantığına harcayabilir.
Kalıcı, uzun ömürlü bağlantılara sahip iş yükleri için (tipik bir sohbet / işlem senaryosu), süreç-içi TLS sorun değildir çünkü el sıkışma saatler veya günler boyunca amortize edilir. Bağlan-bağlantıyı kes-yeniden bağlan patlamaları için (istikrarsız ağlardaki mobil istemciler), öne bir ters proxy koyun.
10. NIC ve Ağ Ayarlaması
1 Gbps'de NIC'i doyurmanız olası değildir. 10 Gbps'nin üzerinde, kesme birleştirme (interrupt coalescing), alma-tarafı ölçeklendirme (RSS) ve sgcWebSockets çalışan iş parçacıklarını NUMA-yerel çekirdeklere sabitleme hakkında düşünmeniz gerekir. Linux'ta ethtool -L ve set_irq_affinity.sh dostlarınızdır. Windows'ta, NIC özelliklerinde RSS Profile'ı NUMAScaling olarak ayarlayın ve Get-NetAdapterRss ile doğrulayın. Yalnızca izlemeniz çekirdeğin softirq veya DPC'lerde gerçek zaman harcadığını söylüyorsa ayarlamaya değer.
Profil-Güdümlü Ayarlama Döngüsü
Ayarlama yinelemelidir. Varsayılanlarla başlayın, temsili bir yük çalıştırın ve şunlara bakın: çalışan başına CPU, GC / tahsis oranı, fan-out altında p99 gecikme ve işletim sistemi düzeyinde bağlantı sayaçları. Bir şeyi değiştirin, yeniden çalıştırın, karşılaştırın. En yaygın sürprizler:
- Zaten sıkıştırılmış yüklerde sıkıştırma etkin → sıfır bant genişliği kazancı için CPU ani artışları.
OnMessageiçinde senkron DB çağrıları → çalışan havuzu <%1 CPU'da doymuş.- Yayın gruplama yok → piyasa açılış ani artışları sırasında satır başı bloke etme (head-of-line blocking).
- 64 çekirdekli bir makinede varsayılan iş parçacığı havuzu → 256'nın 4× verim açacağı yerde işi 64 çalışana serileştirme.
Daha Fazla Okuma
Henüz doğru sunucu sınıfını seçmediyseniz, Hangi Sürüm'den başlayın. Ardından çok-makineli ölçeklendirme için Load Balancer bileşenine geçin. Kütüphanede yeni misiniz? Başlarken merkezi, kurulumu beş dakikada size adım adım anlatır.