IIS Performans Sorunlarını Tespit ve Giderme

Yıllar önce, bir müşterinin IIS sunucusu gece yarısı çöktüğünde aldığım o panik dolu telefonu hâlâ hatırlıyorum. “Site açılmıyor, müşteriler bağlanamıyor, ne yapacağız?” sorusunun cevabını bulmak için üç saat oturup logları incelemiştik. O geceden sonra IIS performans sorunlarına yaklaşımım köklü biçimde değişti. Artık sorun çıkmadan önce izliyorum, sorun çıktığında ise sistematik bir yol izliyorum. Bu yazıda o deneyimleri sizinle paylaşacağım.

Önce Durumu Anlamak: Semptomları Doğru Okumak

IIS performans sorunları genellikle birkaç farklı belirtiden kendini gösterir. Yavaş sayfa yüklemeleri, 503 hataları, uygulama havuzlarının beklenmedik biçimde kapanması, CPU veya bellek kullanımının tavana vurması… Bunların her biri farklı bir kök nedene işaret edebilir. Doğrudan çözüme atlamadan önce semptomları doğru okumak, sizi çok fazla gereksiz işten kurtarır.

İlk işim her zaman Event Viewer‘ı açmak olur. Windows Event Log, IIS ile ilgili önemli hataları kayıt altına alır. Özellikle Application ve System loglarına bakın.

# PowerShell ile son 50 IIS ile ilgili event'i çekmek için
Get-EventLog -LogName Application -Source "W3SVC" -Newest 50 | Select-Object TimeGenerated, EntryType, Message | Format-List

Buradan aldığınız bilgilerle sorunun uygulama katmanında mı, IIS katmanında mı, yoksa işletim sistemi katmanında mı olduğunu anlayabilirsiniz.

IIS Loglarını Gerçek Anlamda Analiz Etmek

IIS logları varsayılan olarak C:inetpublogsLogFilesW3SVC1 dizininde tutulur. Çoğu sistem yöneticisi bu logların varlığından haberdardır ama içini düzgünce analiz etmez. Halbuki bu loglar altın madeni gibidir.

Log dosyalarındaki alanları anlayabilmek için önce W3C Extended log formatını bilmeniz gerekiyor. sc-status başarı kodunu, time-taken ise milisaniye cinsinden yanıt süresini verir. 10.000ms üzerindeki istekler ciddi bir sorunun habercisidir.

# PowerShell ile yavaş istekleri (5 saniyeden uzun) bulmak
Get-Content "C:inetpublogsLogFilesW3SVC1u_ex231015.log" |
Where-Object { $_ -notmatch "^#" } |
ConvertFrom-Csv -Delimiter " " -Header "date","time","s-ip","cs-method","cs-uri-stem","cs-uri-query","s-port","cs-username","c-ip","cs-useragent","cs-referer","sc-status","sc-substatus","sc-win32-status","time-taken" |
Where-Object { [int]$_."time-taken" -gt 5000 } |
Select-Object date, time, "cs-uri-stem", "sc-status", "time-taken" |
Sort-Object "time-taken" -Descending |
Select-Object -First 20

Bu sorgu size en yavaş 20 isteği listeler. Hangi endpoint’in sorun çıkardığını bir anda görebilirsiniz. Bir keresinde bir müşteride bu sorguyu çalıştırdığımda, /api/report/generate endpoint’inin her seferinde 45-60 saniye sürdüğünü gördüm. Sorun IIS’de değil, veritabanı sorgusundaydı. Ama bunu loglardan tespit etmeden önce IIS konfigürasyonunu karıştırarak iki saat harcamıştım.

# En çok hata veren URL'leri bulmak (500 hataları)
Get-Content "C:inetpublogsLogFilesW3SVC1u_ex231015.log" |
Where-Object { $_ -notmatch "^#" -and $_ -match " 500 " } |
Group-Object { ($_ -split " ")[4] } |
Sort-Object Count -Descending |
Select-Object -First 10 Name, Count

Performance Monitor ile Derinlemesine İzleme

Windows Performance Monitor (PerfMon), IIS sorunlarını tespit etmede en güçlü araçlardan biridir. GUI’den açabilirsiniz ama ben her zaman komut satırından çalışmayı tercih ederim, özellikle uzak sunucularda.

IIS için takip etmeniz gereken kritik performans sayaçları şunlardır:

  • Web ServiceCurrent Connections: Anlık bağlantı sayısı
  • Web ServiceTotal Method Requests/sec: Saniye başına istek sayısı
  • ASP.NETRequests Queued: Kuyrukta bekleyen istek sayısı
  • ASP.NETRequests Rejected: Reddedilen istek sayısı
  • Process% Processor Time (w3wp): Worker process CPU kullanımı
  • ProcessPrivate Bytes (w3wp): Worker process bellek kullanımı
  • APP_POOL_WASCurrent Application Pool State: Uygulama havuzu durumu
# PowerShell ile anlık IIS performans verilerini almak
$counters = @(
    "Web Service(_Total)Current Connections",
    "Web Service(_Total)Total Method Requests/sec",
    "ASP.NETRequests Queued",
    "ASP.NETRequests Rejected",
    "Process(w3wp*)% Processor Time",
    "Process(w3wp*)Private Bytes"
)

Get-Counter -Counter $counters -SampleInterval 5 -MaxSamples 12 |
ForEach-Object {
    $_.CounterSamples | ForEach-Object {
        [PSCustomObject]@{
            Timestamp = $_.Timestamp
            Counter = $_.Path
            Value = [Math]::Round($_.CookedValue, 2)
        }
    }
} | Format-Table -AutoSize

Bu scripti 1 dakika boyunca 5 saniyelik aralıklarla çalıştırın. Anlık yük altında sistemin nasıl davrandığını göreceksiniz. Requests Queued değeri sürekli artıyorsa, IIS istekleri yetiştiremiyordur. Bu ya thread pool tükenmesi ya da uygulama kodunun yavaşlığına işaret eder.

Failed Request Tracing: Gizli Sorunları Gün Yüzüne Çıkarmak

IIS’in en az kullanılan ama en güçlü özelliklerinden biri Failed Request Tracing (FREB)‘dir. Belirli bir durum kodu, belirli bir gecikme süresi veya belirli bir URL için detaylı istek izleme kayıtları oluşturur. Performans sorunlarının kaynağını bulmada paha biçilmezdir.

# PowerShell ile Failed Request Tracing'i etkinleştirmek
# Önce modülün yüklü olduğundan emin olun
Import-Module WebAdministration

# Belirli bir site için FREB'i etkinleştirin
$siteName = "Default Web Site"
Set-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST/$siteName" `
    -filter "system.webServer/tracing" `
    -name "enabled" `
    -value $true

# 5 saniyeden uzun süren istekler için tracing kuralı ekleyin
Add-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST/$siteName" `
    -filter "system.webServer/tracing/traceFailedRequests" `
    -name "." `
    -value @{path="*"}

# Zaman aşımı kuralını ekleyin (5000ms = 5 saniye)
Add-WebConfigurationProperty `
    -pspath "MACHINE/WEBROOT/APPHOST/$siteName" `
    -filter "system.webServer/tracing/traceFailedRequests/add[@path='*']/failureDefinitions" `
    -name "timeTaken" `
    -value "00:00:05"

FREB logları C:inetpublogsFailedReqLogFiles altında XML formatında oluşur ve IIS Manager’dan doğrudan HTML olarak görüntülenebilir. Bu logda her IIS modülünün ne kadar zaman harcadığını görebilirsiniz. Yetkilendirme mi yavaş, statik dosya sunumu mu, ASP.NET pipeline mı? Hepsi orada.

Uygulama Havuzu Sorunları ve Çözümleri

Uygulama havuzlarının beklenmedik biçimde kapanması, IIS’te en sık karşılaşılan sorunlardan biridir. Bunu genellikle 503 Service Unavailable hatası olarak görürsünüz.

# Tüm uygulama havuzlarının durumunu kontrol edin
Get-WebConfiguration -Filter "system.applicationHost/applicationPools/add" |
Select-Object name, state, @{Name="PipelineMode"; Expression={$_.managedPipelineMode}} |
Format-Table -AutoSize

# Durdurulmuş havuzları yeniden başlatın
Get-ChildItem IIS:AppPools | Where-Object { $_.state -eq "Stopped" } |
ForEach-Object {
    Write-Host "Başlatılıyor: $($_.name)"
    Start-WebAppPool -Name $_.name
}

Uygulama havuzu sürekli kapanıyorsa, memory limit’e ulaşıyor olabilir. Varsayılan private memory limit genellikle 1.843.200 KB (1.8 GB) civarındadır. Bunu Application Pool’un Recycling ayarlarından değiştirebilirsiniz.

# Memory limiti ve recycling ayarlarını görüntüleyin
$poolName = "DefaultAppPool"
$pool = Get-Item "IIS:AppPools$poolName"

Write-Host "Private Memory Limit: $($pool.recycling.periodicRestart.privateMemory) KB"
Write-Host "Regular Time Interval: $($pool.recycling.periodicRestart.time)"
Write-Host "Request Limit: $($pool.recycling.periodicRestart.requests)"

# Memory limitini 2GB olarak ayarlayın
Set-ItemProperty "IIS:AppPools$poolName" `
    -Name "recycling.periodicRestart.privateMemory" `
    -Value 2097152

Burada dikkat etmeniz gereken bir nokta var: Memory limitini sonsuza kadar artırmak çözüm değildir. Eğer uygulama bellek sızıntısı yapıyorsa, limiti artırmak sadece sorunu ertelemek demektir. Asıl sorun uygulama kodunda yatıyordur.

Thread Pool ve Request Queue Optimizasyonu

ASP.NET uygulamalarında sıkça karşılaşılan bir senaryo şudur: Sunucu CPU kullanımı düşük, bellek yeterli, ağ trafiği normal… Ama yine de istekler yığılıyor ve yanıt süreleri artıyor. Bu durumun en yaygın sebebi thread pool tükenmesidir.

machine.config veya web.config dosyasında thread pool ayarlarını kontrol edin:

# Thread pool ayarlarını kontrol etmek için PowerShell
[System.Threading.ThreadPool]::GetMaxThreads([ref]$maxWorkerThreads, [ref]$maxIoThreads)
[System.Threading.ThreadPool]::GetMinThreads([ref]$minWorkerThreads, [ref]$minIoThreads)

Write-Host "Max Worker Threads: $maxWorkerThreads"
Write-Host "Max IO Threads: $maxIoThreads"
Write-Host "Min Worker Threads: $minWorkerThreads"
Write-Host "Min IO Threads: $minIoThreads"

Web.config’de processModel ayarlarını düzenleyerek minimum thread sayısını artırabilirsiniz. Özellikle yüksek eşzamanlı istek alan uygulamalarda bu kritik önem taşır.

# aspnet.config veya machine.config'de processModel düzenlemesi
# C:WindowsMicrosoft.NETFramework64v4.0.30319aspnet.config
$configPath = "C:WindowsMicrosoft.NETFramework64v4.0.30319aspnet.config"
$config = [xml](Get-Content $configPath)

# Mevcut processModel ayarlarını görüntüle
$processModel = $config.configuration.runtime.appDomainManagerType
Write-Host $config.configuration.system.web.processModel.OuterXml

Thread pool sorununu tespit etmek için şu anlık sorguyu çalıştırabilirsiniz:

# IIS worker process thread sayısını anlık görüntüleyin
$w3wpProcesses = Get-Process -Name "w3wp" -ErrorAction SilentlyContinue

foreach ($proc in $w3wpProcesses) {
    $appPool = (Get-WmiObject -Query "SELECT * FROM Win32_Process WHERE ProcessId = $($proc.Id)").CommandLine
    Write-Host "PID: $($proc.Id) | Threads: $($proc.Threads.Count) | CPU: $($proc.CPU) | Memory: $([Math]::Round($proc.WorkingSet64/1MB,2)) MB"
    Write-Host "Command: $appPool"
    Write-Host "---"
}

HTTP.sys Kuyruk Uzunluğu ve Kernel-Mode Önbelleği

IIS, HTTP.sys adlı bir kernel-mode sürücüsü üzerinde çalışır. İstekler önce bu sürücüye gelir, oradan uygulama havuzuna iletilir. HTTP.sys kuyruğu dolduğunda, yeni istekler doğrudan reddedilir. Bu durumu “503 Service Unavailable” hatası olarak görürsünüz.

# HTTP.sys kuyruk uzunluğunu ayarlamak
# Varsayılan değer 1000'dir, yüksek trafikli sitelerde artırabilirsiniz
Set-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST" `
    -filter "system.applicationHost/applicationPools/add[@name='DefaultAppPool']/processModel" `
    -name "requestQueueLimit" `
    -value 5000

# Kernel-mode cache ayarlarını kontrol edin
netsh http show cachestate

Kernel-mode önbelleği etkin olduğunda, statik içerik için IIS worker process’e bile ihtiyaç duyulmaz. Doğrudan çekirdek katmanından yanıt verilir. Bu, statik içerik yoğun sitelerde dramatik performans artışı sağlar.

Gerçek Dünya Senaryosu: Memory Leak Tespiti

Bir e-ticaret müşterisinde şöyle bir durumla karşılaştık: Sunucu her sabah güzel çalışıyor, öğleden sonra yavaşlıyor, akşam çöküyor. Classic memory leak senaryosu.

Önce bunu doğrulamak için basit bir monitoring scripti yazdım:

# Her 5 dakikada bir w3wp bellek kullanımını log dosyasına yaz
$logFile = "C:Logsmemory_monitor.csv"
Add-Content $logFile "Timestamp,PID,AppPool,MemoryMB,Threads"

while ($true) {
    $processes = Get-Process -Name "w3wp" -ErrorAction SilentlyContinue
    foreach ($proc in $processes) {
        $cmdLine = (Get-WmiObject Win32_Process -Filter "ProcessId=$($proc.Id)").CommandLine
        $poolName = if ($cmdLine -match "-aps+""([^""]+)""") { $matches[1] } else { "Unknown" }
        $memMB = [Math]::Round($proc.WorkingSet64/1MB, 2)
        $line = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss'),$($proc.Id),$poolName,$memMB,$($proc.Threads.Count)"
        Add-Content $logFile $line
        Write-Host $line
    }
    Start-Sleep -Seconds 300
}

Bu scripti bir gün boyunca çalıştırdım. CSV dosyasını Excel’de açtığımda, belleğin her saat yaklaşık 150MB artığını gördüm. Sızdıran kod bulunup düzeltilene kadar geçici çözüm olarak uygulama havuzunu her 6 saatte bir recycle etmeye ayarladık.

Statik İçerik Optimizasyonu ve Sıkıştırma

IIS’te statik ve dinamik sıkıştırma özelliği hem bant genişliği tüketimini hem de yanıt sürelerini önemli ölçüde azaltır. Ama dikkat edin: Sıkıştırma CPU kullanımını artırır. Bant genişliği pahalıysa açın, CPU darboğazı varsa kapayın.

# Sıkıştırma durumunu kontrol edin
Get-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST" `
    -filter "system.webServer/httpCompression" `
    -name "."

# Statik sıkıştırmayı etkinleştirin
Set-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST" `
    -filter "system.webServer/urlCompression" `
    -name "doStaticCompression" `
    -value $true

# Dinamik sıkıştırmayı etkinleştirin
Set-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST" `
    -filter "system.webServer/urlCompression" `
    -name "doDynamicCompression" `
    -value $true

# Sıkıştırılmış dosyaların önbelleğe alınacağı dizini ayarlayın
Set-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST" `
    -filter "system.webServer/httpCompression" `
    -name "directory" `
    -value "D:inetpubtempIIS Temporary Compressed Files"

Statik içerik için ETag ve Cache-Control başlıklarını doğru ayarlamak da önemlidir. Tarayıcı önbelleği iyi yapılandırılmış bir sitede, tekrarlayan ziyaretçiler için sunucu yükü dramatik biçimde düşer.

Connection Timeout ve Keep-Alive Ayarları

Uzun süre açık kalan bağlantılar, IIS’te ciddi bir kaynak tüketimine yol açabilir. Keep-Alive bağlantıları performansı artırır ama yanlış timeout değerleriyle birleşince aksi sonuç doğurabilir.

# Site bazlı timeout ayarlarını görüntüleyin ve düzenleyin
$siteName = "Default Web Site"

# Mevcut limits ayarlarını görüntüle
Get-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST/$siteName" `
    -filter "system.webServer/asp/limits" `
    -name "."

# Connection timeout'u 120 saniyeye ayarlayın (varsayılan 120'dir, gerekirse düşürün)
Set-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST" `
    -filter "system.applicationHost/sites/site[@name='$siteName']/limits" `
    -name "connectionTimeout" `
    -value "00:02:00"

# Maximum bandwidth ve maximum connections limitlerini ayarlayın
Set-WebConfigurationProperty -pspath "MACHINE/WEBROOT/APPHOST" `
    -filter "system.applicationHost/sites/site[@name='$siteName']/limits" `
    -name "maxConnections" `
    -value 4294967295

Sonuç

IIS performans sorunları genellikle tek bir kaynaktan kaynaklanmaz. Sorun araştırması yaparken katmanlı bir yaklaşım benimsemek kritiktir: Önce loglar, sonra performans sayaçları, ardından FREB ile detaylı izleme. Bu üç adımın büyük çoğunluğu sorunun kaynağını ortaya çıkarır.

Deneyimlerimden çıkardığım en önemli ders şu: Sorun çıkmadan önce baseline almak hayat kurtarır. Normal çalışma koşullarında CPU, bellek, thread sayısı ve yanıt sürelerini kayıt altına alın. Sorun çıktığında bu değerlerle kıyasladığınızda çok daha hızlı sonuca ulaşırsınız. Bunu otomatikleştirmek için yukarıdaki monitoring scriptlerini bir Windows Task Scheduler görevi olarak çalıştırabilirsiniz.

IIS üretim ortamında değişiklik yapmadan önce mutlaka bir test ortamında deneyin. processModel veya thread pool değişiklikleri gibi düşük seviyeli ayarlar, beklenmedik sonuçlar doğurabilir. Küçük değişiklikler, iyi gözlem, sistematik yaklaşım: IIS yönetiminin özeti budur.

Bir yanıt yazın

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