Nginx Performans Optimizasyonu: Worker ve Buffer Ayarları
Yoğun trafik altında Nginx’in yavaşladığını, bağlantıların kuyrukta beklediğini ya da sunucunun gereksiz yere boş işlem kapasitesi harcadığını görüyorsanız, büyük ihtimalle worker ve buffer ayarlarınız varsayılan değerlerde kalmış demektir. Nginx kurulumdan hemen sonra çalışıyor olması, onu optimize edilmiş hale getirmiyor. Bu yazıda, gerçek dünya senaryoları üzerinden Nginx’in worker süreçlerini, bağlantı yönetimini ve buffer mekanizmalarını nasıl doğru şekilde yapılandıracağınızı anlatacağım.
Neden Worker Ayarları Bu Kadar Önemli?
Nginx, Apache’nin aksine event-driven (olay güdümlü) bir mimari kullanır. Bu sayede çok sayıda bağlantıyı az sayıda süreçle yönetebilir. Ama bu mimariyi doğru kullanabilmek için worker süreçlerinin sistem kaynaklarıyla uyumlu olması gerekiyor.
Varsayılan kurulumda Nginx genellikle tek bir worker süreci çalıştırır. Modern sunucularda bu, işlemci çekirdeklerinin büyük bir kısmının boşta kalması anlamına gelir. Öte yandan, worker sayısını gereğinden fazla artırmak da bağlam değiştirme (context switching) maliyetini artırıp performansı düşürür.
Önce mevcut durumu analiz etmekle başlayalım.
# Kaç CPU çekirdeğiniz var?
nproc
# Ya da daha detaylı bilgi için
grep -c ^processor /proc/cpuinfo
# Nginx'in şu an kaç worker çalıştırdığını görün
ps aux | grep nginx
# Nginx'in mevcut konfigürasyonunu kontrol edin
nginx -T | grep worker
Çıktıda kaç fiziksel veya sanal çekirdek gördüğünüzü not edin. Bu sayı, worker_processes değerinizin temelini oluşturacak.
worker_processes ve worker_connections Ayarları
/etc/nginx/nginx.conf dosyasını açın ve şu bloğu inceleyin:
# Mevcut nginx.conf durumunu görüntüle
cat /etc/nginx/nginx.conf | head -30
Şimdi bu iki kritik parametreyi anlamak gerekiyor:
worker_processes: Nginx’in kaç bağımsız worker süreci çalıştıracağını belirler. auto değeri, Nginx’in mevcut CPU çekirdek sayısını otomatik algılamasını sağlar.
worker_connections: Her worker sürecinin aynı anda kaç bağlantıyı yönetebileceğini belirler. Bu değer, sistem limitiyle doğrudan ilişkilidir.
Toplam eşzamanlı bağlantı kapasitesi = worker_processes x worker_connections
Örneğin 4 çekirdekli bir sunucuda worker_processes 4 ve worker_connections 1024 ile 4096 eşzamanlı bağlantı kapasitesine ulaşırsınız.
# /etc/nginx/nginx.conf temel optimizasyon
user www-data;
worker_processes auto;
worker_cpu_affinity auto;
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
events {
worker_connections 4096;
multi_accept on;
use epoll;
}
Buradaki multi_accept on ayarı önemli: varsayılan olarak kapalıdır ve her worker süreci aynı anda sadece bir yeni bağlantı kabul eder. multi_accept on ile worker, bekleyen tüm bağlantıları aynı anda alır. Yoğun trafik senaryolarında bu farkı hissedilir biçimde etkiliyor.
use epoll ise Linux’un epoll mekanizmasını kullanır. Nginx çoğu zaman bunu otomatik seçer ama açıkça belirtmek konfigürasyonun okunurluğunu artırır.
Sistem Limitleriyle Uyum: ulimit Ayarları
worker_connections değerini 4096 ya da daha yükseğe çekseniz bile, sistem dosya tanımlayıcı (file descriptor) limiti buna izin vermiyorsa Nginx bu kapasiteyi kullanamaz. Her bağlantı bir dosya tanımlayıcısı tüketir.
# Mevcut sistem limitini kontrol edin
ulimit -n
# Nginx kullanıcısı için limiti kontrol edin
su -s /bin/bash www-data -c "ulimit -n"
# Sistemdeki genel limitleri görüntüleyin
cat /proc/sys/fs/file-max
Limiti kalıcı olarak artırmak için:
# /etc/security/limits.conf dosyasına ekleyin
echo "www-data soft nofile 65536" >> /etc/security/limits.conf
echo "www-data hard nofile 65536" >> /etc/security/limits.conf
# Nginx konfigürasyonunda da belirtin
# nginx.conf içinde main context'e ekleyin:
# worker_rlimit_nofile 65536;
nginx.conf içindeki tam hali şöyle görünmeli:
user www-data;
worker_processes auto;
worker_rlimit_nofile 65536;
worker_cpu_affinity auto;
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
events {
worker_connections 8192;
multi_accept on;
use epoll;
}
worker_rlimit_nofile parametresi, Nginx worker süreçlerinin açabileceği maksimum dosya tanımlayıcısı sayısını belirler. Bu değeri worker_connections değerinin en az iki katı yapmanızı öneririm, çünkü her upstream bağlantısı da ayrı bir descriptor kullanır.
Buffer Ayarları: Bellek ile Disk Arasındaki Denge
Buffer ayarları, Nginx’in istek ve yanıtları nasıl bellekte tutacağını belirler. Yanlış yapılandırılmış buffer’lar iki farklı probleme yol açar: çok küçük buffer’lar gereksiz disk I/O üretir, çok büyük buffer’lar ise bellek tüketimini patlatır.
client_body_buffer_size ve client_header_buffer_size
İstemcilerden gelen POST isteklerinin body kısmı önce buffer’a alınır. Bu buffer dolduğunda Nginx geçici dosyaya yazar. Dosya upload eden, form gönderen ya da API payload’ları alan bir servis işletiyorsanız bu ayar kritik.
http {
# İstemci istek body buffer'ı
# POST verileri için bellek buffer'ı (varsayılan: 8k|16k)
client_body_buffer_size 128k;
# İstek header'ları için buffer boyutu
client_header_buffer_size 4k;
# Büyük header'lar için (cookie yoğun uygulamalar)
large_client_header_buffers 4 16k;
# Maksimum istek body boyutu (0 = sınırsız)
client_max_body_size 50m;
# Buffer timeout süreleri
client_body_timeout 30s;
client_header_timeout 30s;
}
Gerçek dünya senaryosu: E-ticaret platformunda çalışırken kullanıcıların ürün görseli yüklerken 413 hatası aldığını fark ettim. client_max_body_size varsayılan 1m’de kalmıştı. Görseller 2-5MB arasında değiştiğinden tüm yüklemeler başarısız oluyordu. client_max_body_size 20m ile sorun çözüldü ama aynı zamanda client_body_buffer_size 256k yaparak disk yazma işlemlerini de azalttık.
proxy_buffer Ayarları
Nginx’i reverse proxy olarak kullanıyorsanız (ve büyük ihtimalle kullanıyorsunuzdur) proxy buffer ayarları çok daha kritik hale geliyor. Bu buffer’lar, upstream sunucudan (örneğin Node.js, PHP-FPM, Gunicorn) gelen yanıtları tutmak için kullanılır.
http {
# Proxy buffer ayarları
proxy_buffering on;
# Her upstream yanıtı için buffer sayısı ve boyutu
proxy_buffers 16 32k;
# İlk yanıt parçası için buffer boyutu
proxy_buffer_size 64k;
# Busy buffer eşiği (proxy_buffers toplamının yarısını geçemez)
proxy_busy_buffers_size 128k;
# Geçici dosyaya yazılacak maksimum boyut
proxy_temp_file_write_size 128k;
# Proxy bağlantı timeout'ları
proxy_connect_timeout 30s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
proxy_buffering on durumunda Nginx, upstream yanıtını tamamen alır ve istemciye gönderir. Bu sayede upstream bağlantısı hızla serbest kalır. Streaming içerik veya server-sent events kullanıyorsanız bu özelliği ilgili location bloğunda proxy_buffering off ile devre dışı bırakın.
proxy_buffers 16 32k değeri 16 adet 32KB buffer demek, toplamda 512KB. Ortalama API yanıtınız 50-100KB arasındaysa bu değer yeterli. Büyük JSON payload’ları dönüyorsanız artırmanız gerekebilir.
Keepalive Optimizasyonu
Her HTTP isteği için yeni TCP bağlantısı açmak masraflıdır. Keepalive, mevcut TCP bağlantısını yeniden kullanarak bu maliyeti ortadan kaldırır.
http {
# İstemci keepalive timeout'u
keepalive_timeout 65s;
# Bir keepalive bağlantısı üzerinden işlenecek maksimum istek sayısı
keepalive_requests 1000;
# Upstream bağlantı havuzu (her worker için)
upstream backend_app {
server 127.0.0.1:8080;
server 127.0.0.1:8081;
keepalive 64;
keepalive_requests 1000;
keepalive_timeout 60s;
}
}
Upstream keepalive ayarı çoğu zaman gözden kaçıyor. Her istek için upstream’e yeni TCP bağlantısı açmak, özellikle yüksek trafikte ciddi gecikme yaratıyor. keepalive 64 ile her worker için 64 adet upstream bağlantısını açık tutuyorsunuz.
Upstream keepalive kullanırken proxy header’larına dikkat edin:
location /api/ {
proxy_pass http://backend_app;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
proxy_http_version 1.1 ve proxy_set_header Connection "" olmadan keepalive çalışmaz. HTTP/1.0 varsayılan olarak her istekten sonra bağlantıyı kapatır.
Gzip Compression ve Sendfile
Buffer optimizasyonunun tamamlayıcısı olarak compression ve sendfile ayarlarına da değinmek gerekiyor.
http {
# Statik dosya gönderimi için kernel optimizasyonu
sendfile on;
tcp_nopush on;
tcp_nodelay on;
# Gzip ayarları
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 256;
gzip_types
application/json
application/javascript
text/css
text/plain
text/html
image/svg+xml
application/xml;
# Gzip buffer
gzip_buffers 16 8k;
}
sendfile on: Kernel’ın dosyaları doğrudan disk’ten network soketine kopyalamasını sağlar. Userspace’e geçiş olmadan çalıştığı için statik dosya servisi önemli ölçüde hızlanır.
tcp_nopush on: sendfile ile birlikte kullanılır. Yanıt header’ları ile ilk veri parçasını tek pakette gönderir.
tcp_nodelay on: Küçük veri parçacıklarını geciktirmeden hemen gönderir. Nagle algoritmasını devre dışı bırakır. Gerçek zamanlı uygulamalarda önemli.
gzip_comp_level 6: 1 ile 9 arasında değer alır. 6, CPU kullanımı ile sıkıştırma oranı arasındaki ideal noktadır. 9’a çıkarmak CPU’yu yorar ama boyutsal kazanım marginal kalır.
Rate Limiting ile Worker’ları Korumak
Worker sayısını ve buffer’ları ne kadar optimize ederseniz edin, kötü niyetli ya da kontrolsüz istemciler tüm kaynakları tüketebilir. Rate limiting, worker süreçlerini korur.
http {
# Rate limit zone tanımlamaları
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/m;
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m;
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
server {
# API endpoint'leri için rate limiting
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
limit_conn conn_limit 20;
proxy_pass http://backend_app;
}
# Login endpoint'i için daha sıkı kısıtlama
location /auth/login {
limit_req zone=login_limit burst=3 nodelay;
limit_conn conn_limit 5;
proxy_pass http://backend_app;
}
}
}
burst=20 nodelay: Normal hızın üzerinde gelen 20 isteğe kadar kuyruğa almadan işle, 21. isteği reddet. nodelay olmadan burst istekleri kuyruğa alınır ve yavaşlatılır.
limit_conn conn_limit 20: Tek IP’den aynı anda maksimum 20 aktif bağlantı. DDoS senaryolarında hayat kurtarıcı.
Performans Testi ve İzleme
Yaptığınız değişikliklerin etkisini ölçmeden optimizasyon yarım kalır. Nginx stub_status modülünü aktif edin:
# Nginx stub_status modülünün var olduğunu kontrol edin
nginx -V 2>&1 | grep stub_status
server {
listen 127.0.0.1:8080;
location /nginx_status {
stub_status;
allow 127.0.0.1;
deny all;
}
}
# Status çıktısını görüntüleyin
curl http://127.0.0.1:8080/nginx_status
# Örnek çıktı:
# Active connections: 847
# server accepts handled requests
# 1234567 1234567 4891234
# Reading: 12 Writing: 234 Waiting: 601
Bu çıktıyı yorumlamak için:
- Active connections: Şu an aktif olan toplam bağlantı
- Reading: Nginx’in istemci isteğini okuduğu bağlantı sayısı
- Writing: Nginx’in yanıt yazdığı bağlantı sayısı
- Waiting: Keepalive bağlantılarının yeni istek beklediği sayı (idle)
Waiting değeri çok yüksekse keepalive_timeout değerini düşürebilirsiniz. Writing değeri sürekli yüksekse upstream’den yanıt bekleniyordur, bu durumda asıl sorun backend’dedir.
Yük testi için:
# wrk ile yük testi
wrk -t4 -c400 -d30s --latency http://sunucunuz.com/
# ab (ApacheBench) ile test
ab -n 10000 -c 200 http://sunucunuz.com/
# Nginx log'larından ortalama yanıt süresini hesaplayın
awk '{sum += $NF; count++} END {print "Ortalama:", sum/count, "ms"}' /var/log/nginx/access.log
Gerçek Dünya Optimizasyon Senaryosu: Yüksek Trafikli API Gateway
Bir müşterinin API gateway’inde saniyede 2000-3000 istek işlenmesi gerekiyordu. Başlangıçta sunucu 8 çekirdekli olmasına rağmen Nginx tek worker ile çalışıyordu ve yanıt süreleri 200ms’nin üzerindeydi. Uyguladığımız konfigürasyon:
user www-data;
worker_processes auto;
worker_rlimit_nofile 65536;
events {
worker_connections 8192;
multi_accept on;
use epoll;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 30s;
keepalive_requests 2000;
client_body_buffer_size 64k;
client_header_buffer_size 2k;
large_client_header_buffers 4 8k;
client_max_body_size 10m;
proxy_buffering on;
proxy_buffers 32 16k;
proxy_buffer_size 32k;
proxy_busy_buffers_size 64k;
proxy_connect_timeout 10s;
proxy_read_timeout 20s;
gzip on;
gzip_comp_level 5;
gzip_min_length 512;
gzip_types application/json text/plain;
limit_req_zone $binary_remote_addr zone=api:20m rate=500r/s;
upstream api_backend {
least_conn;
server 10.0.0.10:3000;
server 10.0.0.11:3000;
server 10.0.0.12:3000;
keepalive 128;
}
server {
listen 80;
location / {
limit_req zone=api burst=100 nodelay;
proxy_pass http://api_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
}
Bu değişikliklerin ardından ortalama yanıt süresi 200ms’den 45ms’ye düştü. CPU kullanımı 8 çekirdeğe yayılırken toplam throughput neredeyse 4 katına çıktı. Özellikle upstream keepalive’ı aktif etmek (her istek için yeni TCP bağlantısı açma maliyetini ortadan kaldırmak) tek başına yanıt süresinde 40ms kazandırdı.
Konfigürasyon Değişikliklerini Uygulama
Her değişiklikten sonra konfigürasyonu test etmeden uygulamak production’da hata riskini artırır:
# Konfigürasyon sözdizimini kontrol edin
nginx -t
# Ya da daha ayrıntılı çıktı için
nginx -T
# Nginx'i durdurmadan konfigürasyonu yeniden yükleyin
systemctl reload nginx
# Ya da doğrudan sinyal göndererek
nginx -s reload
# Worker süreçlerini izleyin
watch -n 1 'ps aux | grep nginx'
systemctl reload nginx ile mevcut bağlantılar kesilmez. Nginx yeni worker süreçlerini başlatır, eski worker’lar mevcut bağlantıları tamamladıktan sonra kapanır. Production ortamında her zaman restart yerine reload kullanın.
Sonuç
Nginx optimizasyonu katmanlı bir süreçtir. Worker süreçlerini CPU çekirdek sayısına uygun yapılandırmak, eşzamanlı bağlantı kapasitesini sistem limitleriyle uyumlu hale getirmek ve buffer boyutlarını trafik profilinize göre ayarlamak birbirini tamamlayan adımlardır.
En kritik noktaları özetlemek gerekirse: worker_processes auto ile başlayın ve worker_rlimit_nofile ile worker_connections değerlerini uyumlu tutun. Reverse proxy kullanıyorsanız upstream keepalive’ı mutlaka aktif edin. Proxy buffer’larını ortalama upstream yanıt boyutuna göre boyutlandırın ve stub_status ile değişikliklerinizin etkisini ölçün.
Tüm bu ayarlar kağıt üzerinde doğru görünse de her uygulamanın trafik profili farklıdır. Yaptığınız her değişikliği yük testi ile doğrulayın, production’a almadan önce staging ortamında deneyin ve metrikleri izlemeye devam edin. Nginx’i bir kez kurup unutmak değil, çalışan sistemi anlamak ve sürekli iyileştirmek, gerçek anlamda iyi bir sysadmin’i ayıran şeydir.
