Web Sitesi Neden Yavaş Açılıyor: Temel Nedenler ve Çözümler

Kulağa basit geliyor, değil mi? “Site yavaş açılıyor” diyor kullanıcı. Sen de sunucuya bağlanıyorsun, top çekiyorsun, her şey normal görünüyor. Ama site hâlâ yavaş. İşte bu an, sysadmin’lerin en çok vakit kaybettiği andır. Çünkü yavaşlık, tek bir yerde değil onlarca farklı katmanda gizlenebilir. Bu yazıda o katmanları tek tek açacağız, gerçek dünyada karşılaştığım senaryolarla.

Önce Ölçmeden Tanı Koyma

Yavaşlığın nedenini aramadan önce neyin yavaş olduğunu anlamak gerekiyor. “Site yavaş” ifadesi aslında çok muğlak. İlk byte geç mi geliyor? Sayfa içeriği mi yavaş yükleniyor? Belirli bir endpoint mi, yoksa tüm site mi yavaş?

Sunucu tarafında ilk yapacağım şey şu:

curl -o /dev/null -s -w "DNS: %{time_namelookup}nBağlantı: %{time_connect}nTLS: %{time_appconnect}nİlk Byte: %{time_starttransfer}nToplam: %{time_total}n" https://orneksite.com

Bu komut sana DNS çözümleme, TCP bağlantısı, TLS el sıkışması ve ilk byte süresini ayrı ayrı verir. Eğer time_namelookup yüksekse sorun DNS’te, time_connect yüksekse ağda ya da sunucuda, time_starttransfer yüksekse backend’de demektir. Tanıyı doğru katmana koymak, saatlerce yanlış yerde arama yapmaktan seni kurtarır.

Birden fazla noktadan ölçmek için ise şunu kullanıyorum:

for i in {1..10}; do
  curl -o /dev/null -s -w "%{time_starttransfer}n" https://orneksite.com
done | awk '{sum+=$1; count++} END {print "Ortalama:", sum/count, "saniye"}'

On istek at, ortalamasına bak. Tek istek seni yanıltabilir.

DNS Kaynaklı Gecikmeler

DNS sorunları genellikle gözden kaçar çünkü “site açılıyor, sadece yavaş” gibi görünür. Ama TTL değeri düşük tutulmuş bir domain, her istekte DNS sorgusunu tekrarlıyor olabilir.

dig orneksite.com +stats | grep "Query time"
# Query time: 1 msec -> iyi
# Query time: 340 msec -> sorun var

Bir de şunu kontrol et:

dig orneksite.com +trace

Bu komut DNS çözümleme zincirinin tamamını gösterir. Root sunuculardan başlayarak hangi adımın ne kadar sürdüğünü görürsün. Yurt dışındaki bir NS sunucusundan yanıt bekliyorsan, sadece DNS için 200-300ms harcıyor olabilirsin.

Gerçek bir senaryodan bahsedeyim: Bir e-ticaret sitesinin DNS TTL değeri 60 saniye olarak ayarlanmıştı. Bunu bir migration sırasında düşürmüşler, sonra unutmuşlar. Her kullanıcı oturumunda defalarca DNS sorgusu atılıyordu. TTL’yi 3600’e çıkarınca sayfa açılış süresi ortalama 180ms iyileşti. Hiçbir koda dokunmadan.

TCP Bağlantı Sorunları ve Keep-Alive

Her HTTP isteği için yeni bir TCP bağlantısı açmak ciddi bir yük yaratır. Three-way handshake (SYN, SYN-ACK, ACK) her seferinde tekrarlanır. HTTPS kullanıyorsan üstüne bir de TLS el sıkışması eklenir.

Nginx’te keep-alive ayarları:

http {
    keepalive_timeout 65;
    keepalive_requests 1000;

    upstream backend {
        server 127.0.0.1:8080;
        keepalive 32;
    }
}

keepalive 32 satırı çok önemli: Nginx’in upstream (backend) tarafıyla da kalıcı bağlantı kurmasını sağlar. Bunu kaçıran çok sysadmin gördüm. Nginx ile kullanıcı arasında keep-alive var, ama Nginx ile uygulama sunucusu arasında her seferinde yeni bağlantı kuruluyor.

Mevcut bağlantı durumuna bakmak için:

ss -s
# veya
netstat -an | awk '/tcp/ {print $6}' | sort | uniq -c | sort -rn

Eğer TIME_WAIT durumundaki bağlantı sayısı binleri geçiyorsa, bu bir işaret. net.ipv4.tcp_tw_reuse ve net.ipv4.tcp_fin_timeout parametrelerini gözden geçirmen gerekebilir.

sysctl net.ipv4.tcp_fin_timeout
sysctl net.ipv4.tcp_tw_reuse

Tipik production değerleri:

# /etc/sysctl.conf
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_tw_reuse = 1
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535

Sunucu Kaynak Doygunluğu

Klasik senaryo: CPU %100, load average tavan yapmış, her istek kuyrukta bekliyor. Ama bu o kadar bariz ki genellikle hemen fark edilir. Daha sinsi olan kaynak sorunları şunlardır:

Memory pressure ve swap kullanımı: Sunucu yeterli RAM’e sahip görünebilir ama uygulamalar swap’a yazıp okuyorsa gecikme dramatik şekilde artar.

vmstat 1 5
# si ve so sütunlarına bak (swap in / swap out)
# Sürekli 0 olmayan değerler varsa sorun var

free -h
cat /proc/meminfo | grep -E "MemTotal|MemFree|MemAvailable|SwapTotal|SwapFree|Cached"

I/O wait: CPU idle görünebilir ama diskten yanıt bekliyordur.

iostat -x 1 5
# %await sütunu ortalama I/O bekleme süresini ms cinsinden verir
# 10ms altı genellikle iyi, SSD'de 1-2ms beklenmeli
# HDD'de 50ms+ görüyorsan ciddi sorun var

iotop -ao
# Hangi process en çok I/O yapıyor?

Bir zamanlar üzerinde çalıştığım bir sunucuda MySQL log dosyaları aynı disk üzerindeydi ve innodb_flush_log_at_trx_commit = 1 ayarıyla her transaction’da sync yazma yapıyordu. %await değeri 80ms’e çıkmıştı. Log dosyalarını ayrı bir diske taşımak ve innodb_flush_log_at_trx_commit = 2 yapmak yeterli oldu.

Veritabanı: En Sık Suçlanan Katman

Deneyimlerime göre yavaş sitelerin %60-70’inde asıl sorun veritabanında. Yavaş sorgular, eksik indexler, connection pool sorunları…

Önce yavaş sorguları tespit et:

# MySQL için yavaş sorgu logu aç
mysql -u root -p -e "
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
"

# Sonra analiz et
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log

PostgreSQL için:

# postgresql.conf'a ekle
# log_min_duration_statement = 1000  (1 saniyeden uzun sorguları logla)

# Çalışan yavaş sorguları anlık gör
psql -c "SELECT pid, now() - pg_stat_activity.query_start AS duration, query, state
FROM pg_stat_activity
WHERE (now() - pg_stat_activity.query_start) > interval '5 seconds';"

Connection pool konusunda da bir not düşeyim: Uygulaman her istek için yeni bir veritabanı bağlantısı açıp kapatıyorsa, bu ciddi bir overhead yaratır. PHP + MySQL ikilisinde PDO persistent connection ya da bir connection pooler (PgBouncer, ProxySQL) bu farkı dramatik şekilde kapatır.

PHP-FPM ve Uygulama Katmanı Darboğazları

PHP kullanan sitelerde en sık gözden kaçan şey FPM havuzu boyutudur:

# PHP-FPM havuz durumunu kontrol et
curl http://127.0.0.1/fpm-status?full 2>/dev/null | grep -E "active processes|idle processes|max children reached"

# ya da doğrudan
systemctl status php8.1-fpm

Eğer max children reached değeri sürekli artıyorsa, FPM tüm worker’larını kullanmış ve yeni istekleri kuyrukta bekletiyordur. www.conf içindeki değerleri gözden geçir:

; /etc/php/8.1/fpm/pool.d/www.conf
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500

pm.max_children değerini kafaya göre artırma. Her worker bellek tüketir. Önce bir worker’ın ortalama bellek kullanımına bak:

ps aux | grep php-fpm | grep -v master | awk '{sum += $6; count++} END {print "Ortalama RSS:", sum/count/1024, "MB"}'

Diyelim ki her worker 50MB kullanıyor ve sunucunda 4GB RAM var. Sistem ve diğer servisler için 1GB ayırsak, PHP-FPM için 3GB kalır. Bu da max 60 worker demek. Bunun üzerine çıkarsan swap başlar, bütün hesap değişir.

Caching Eksikliği veya Hatalı Cache Yapılandırması

Bu başlık altında konuyu üç seviyede ele almak gerekiyor:

Opcode cache (PHP): OPcache açık mı?

php -r "echo opcache_get_status()['opcache_enabled'] ? 'Açık' : 'Kapalı';"

# php.ini içinde
# opcache.enable=1
# opcache.memory_consumption=256
# opcache.max_accelerated_files=20000
# opcache.validate_timestamps=0  (production'da)

Sayfa/nesne cache: Redis veya Memcached kullanıyor musun? Kullanıyorsan hit rate’i ne?

redis-cli info stats | grep -E "keyspace_hits|keyspace_misses"
# hit rate = hits / (hits + misses)
# %80 altına düşüyorsa TTL veya cache stratejini gözden geçir

HTTP cache başlıkları: Statik dosyalar tarayıcı tarafında cache’leniyor mu?

curl -I https://orneksite.com/static/app.js | grep -E "Cache-Control|Expires|ETag|Last-Modified"

Eğer statik dosyalarda Cache-Control: no-cache veya hiç cache başlığı yoksa, her ziyaretçi her sayfa açışında aynı dosyaları tekrar tekrar çekiyor demektir. Nginx’te:

location ~* .(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    access_log off;
}

Ağ ve Coğrafi Konum Faktörleri

Sunucu İstanbul’da, kullanıcılar Ankara’da, peki ya kullandığın üçüncü parti servisler? Google Fonts, harici analytics scriptleri, ödeme sistemi widget’ları… Bunların her biri ayrı bir DNS sorgusu, ayrı bir TCP bağlantısı demek.

Sayfanın yüklenmesi sırasında yapılan tüm dış bağlantıları görmek için:

# Apache/Nginx access log'larından değil, tcpdump ile gerçek trafiği yakala
tcpdump -i eth0 -n 'tcp[tcpflags] & tcp-syn != 0' 2>/dev/null | head -50

# Veya web sunucusu üzerinden hangi domainlere istek gidiyor
ss -tp | grep nginx | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn

CDN kullanıyorsan doğru yapılandırılmış mı? Türk kullanıcılara İstanbul PoP’undan mı yoksa Frankfurt’tan mı servis veriliyor?

# CDN edge lokasyonunu kontrol et
curl -sI https://orneksite.com | grep -i "x-served-by|x-cache|cf-ray|x-amz-cf-pop"

Bir müşteri projesi: Site Cloudflare kullanıyordu ama Cloudflare’in önbelleğe almadığı HTML sayfalar için Türk kullanıcılar Frankfurt origin’ine gidiyordu. Origin sunucu İstanbul’daydı ama Cloudflare routing’i Türkiye’deki edge’den direkt origin’e değil, Frankfurt üzerinden gönderiyordu. Cloudflare Argo veya doğru routing ayarıyla bu gecikme %40 azaldı.

TLS/SSL El Sıkışma Maliyeti

TLS 1.2 ile tam el sıkışma 2 round-trip ister. TLS 1.3 bunu 1 round-trip’e indirdi. Session resumption ise bunu neredeyse sıfıra yaklaştırır.

# TLS versiyonunu kontrol et
openssl s_client -connect orneksite.com:443 2>/dev/null | grep -E "Protocol|Session-ID|TLSv"

# TLS session ticket açık mı?
openssl s_client -connect orneksite.com:443 -reconnect 2>/dev/null | grep -i "reused"

Nginx’te TLS optimizasyonu:

ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets on;
ssl_stapling on;
ssl_stapling_verify on;

OCSP stapling açık değilse, tarayıcı sertifika geçerliliğini kontrol etmek için ayrıca bir istek atar. Bu özellikle mobil bağlantılarda fark edilir düzeyde gecikme yaratır.

Load Balancer Arkasındaki Sorunlar

Yük dengeleyici varsa, sağlıklı görünen bir sistemde bile gizli sorunlar olabilir.

Health check aralıkları çok sık ayarlanmışsa ve backend’ler buna yetişemiyorsa, sağlıklı node’lar geçici olarak devre dışı kalıp tüm yük tek node’a yığılabilir. Yük dengeleyici log’larını mutlaka incele:

# HAProxy istatistikleri
echo "show stat" | socat /var/run/haproxy/admin.sock stdio | cut -d',' -f1,2,18,19,47,48 | column -t -s','

# veya HAProxy stats sayfasından
curl http://localhost:8404/stats

Backend response time dağılımına bak. Bir node diğerlerinden sürekli yavaş yanıt veriyorsa, round-robin algoritması bu node’a da istek göndermeye devam eder. leastconn veya response-time tabanlı bir algoritma daha iyi sonuç verir.

Sistematik Yaklaşım: Hepsini Bir Araya Getirmek

Tüm bu kontrolleri yaparken bir checklist kafasıyla değil, hiyerarşik bir yaklaşımla ilerle. Ağ gecikmeleri varsa uygulamayı optimize etmenin anlamı yoktur. Veritabanı connection pool dolu taşıyorsa OPcache ayarlamak işe yaramaz.

Genel sıralama şöyle ilerler:

  • Önce ölç: curl ile katmanlı timing al
  • DNS ve ağ: Çözümleme süresi, TTL değerleri, coğrafi konum
  • Bağlantı katmanı: TCP keep-alive, TIME_WAIT birikimi, TLS versiyonu
  • Sunucu kaynakları: CPU, memory, I/O wait, swap kullanımı
  • Web sunucusu: Worker sayısı, connection limitleri, cache başlıkları
  • Uygulama katmanı: FPM havuzu, opcode cache, kod düzeyinde profiling
  • Veritabanı: Yavaş sorgular, eksik indexler, connection pooling

Her katmanı methodolojik şekilde geçmek yerine “herhalde veritabanıdır” diye direkt oraya atlamak, hem zaman kaybettirir hem de asıl sorunu ıskalatır.

Sonuç

Web sitesi yavaşlığı, çoğu zaman tek bir nedene bağlanamaz. Ben genellikle şunu görürüm: Birkaç küçük sorun bir araya gelir ve toplam etkisi büyük olur. DNS’te 50ms, TLS’te 80ms, bir yavaş sorgu 200ms, FPM bekleme 150ms… Hepsi toplanınca kullanıcı “site çok yavaş” der, ama tek bir fail point yoktur.

Bu yüzden sabır ve metodoloji şart. Ölç, teşhis koy, düzelt, tekrar ölç. Değişikliği izole et ki neyin işe yaradığını bilesin. “Her şeyi aynı anda optimize ettim, şimdi daha hızlı” diyenler, bir sonraki sorunda ne yapacaklarını bilemezler.

Ve son bir not: Bu tür sorunları production patlarken değil, düzenli periyodik kontroller sırasında bulmak çok daha az stresli. Haftada bir curl timing almak, aylık bir slow query log incelemesi yapmak, sistematik izleme araçları kurmak; bunlar sizi “site neden yavaş?” krizinden korur. Ya da en azından kriz anında elinizde baseline veriniz olur.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir