Dosya Sistemi Performansı: Linux I/O Scheduler Seçimi ve Optimizasyonu
Sunucunuzda disk I/O performansı aniden düştüğünde, ilk bakacağınız yer genellikle donanım olur. Daha hızlı diskler almayı düşünürsünüz, RAID yapılandırmasını gözden geçirirsiniz ya da storage ekibini arayıp şikayet edersiniz. Ama çoğu zaman cevap çok daha yakındadır: Linux çekirdeğinin I/O zamanlayıcısı yanlış yapılandırılmıştır. Doğru I/O scheduler seçimi, hiçbir kuruş harcamadan disk performansını dramatik biçimde artırabilir. Bu yazıda, I/O scheduler’ların nasıl çalıştığını, hangi senaryoda hangisini kullanmanız gerektiğini ve bunu production ortamında nasıl uygulayacağınızı ele alacağız.
I/O Scheduler Nedir ve Neden Önemlidir?
Linux çekirdeği, uygulamalardan gelen disk okuma/yazma isteklerini doğrudan donanıma iletmez. Bu istekleri önce bir kuyruğa alır, sıralar, birleştirir ve optimize eder. Bu işi yapan bileşene I/O scheduler (ya da I/O elevator) denir.
Mekanik diskler (HDD) döneminde bu optimizasyon hayati önem taşıyordu çünkü disk kafasının fiziksel hareketi ciddi gecikmelere yol açıyordu. Rastgele gelen istekleri geometrik olarak sıralayıp disk kafasının daha az hareket etmesini sağlamak, performansı katlarca artırabiliyordu. SSD’lerin hayatımıza girmesiyle tablo değişti ama scheduler seçimi hala kritik bir konu olmaya devam ediyor.
Peki neden hala önemli? Çünkü farklı iş yükleri için farklı optimizasyonlar gerekiyor:
- Veritabanı sunucuları düşük gecikme ister, yüksek throughput ikincil plandadır
- Dosya sunucuları yüksek throughput ister, bireysel istek gecikmesi daha az önemlidir
- Web sunucuları mixed I/O profili gösterir
- Sanal makine hostları çok sayıda eş zamanlı iş yükü barındırır
Linux’ta Mevcut I/O Scheduler’lar
Kernel sürümüne ve donanım tipine göre kullanabileceğiniz birkaç farklı scheduler vardır.
CFQ (Completely Fair Queuing)
Kernel 5.0 öncesinde default scheduler olan CFQ, her işlem için ayrı bir kuyruk oluşturur ve round-robin mantığıyla bu işlemlere adil bir şekilde disk erişimi sağlar. İnteraktif masaüstü sistemler için iyi çalışır ama server ortamlarında genellikle suboptimal kalır. Kernel 5.0 ile birlikte CFQ kaldırılmıştır.
Deadline
Deadline scheduler, her I/O isteğine bir son kullanma tarihi atar. Okuma isteklerine 500ms, yazma isteklerine 5000ms deadline tanır ve bu süre dolmadan istek işlenmelidir. Bu sayede istek açlığı (starvation) önlenir. Düşük gecikme gerektiren veritabanı iş yükleri için ideal bir seçimdir.
NOOP
En basit scheduler olan NOOP, herhangi bir sıralama veya birleştirme yapmadan istekleri FIFO sırasıyla işler. SSD’lerde ya da zaten kendi içinde optimizasyon yapan akıllı RAID kontrolcülerinde kullanılması önerilir. Sanal makinelerde de guest işletim sistemi için NOOP tercih edilebilir çünkü optimizasyonu hypervisor seviyesinde yapılmaktadır.
BFQ (Budget Fair Queuing)
Modern kernellerde (4.12+) gelen BFQ, CFQ’nun gelişmiş halefidir. Her işleme sabit zaman dilimi yerine bütçe (işlem sayısı bazlı) tahsis eder. Özellikle masaüstü ve mixed iş yükleri için geliştirilmiştir. Ancak bazı yüksek yoğunluklu server senaryolarında overhead yaratabilir.
MQ-Deadline
Multi-queue mimarisine uygun deadline scheduler implementasyonudur. NVMe diskler gibi çok kuyruklu depolama aygıtlarında kullanılır ve modern sistemlerde Deadline’ın yerini almıştır.
Kyber
Yüksek performanslı, düşük gecikme odaklı bir scheduler’dır. NVMe diskler için optimize edilmiştir. Latency hedefleri doğrultusunda otomatik olarak okuma/yazma oranlarını ayarlar.
None (no-op for NVMe)
Modern NVMe diskler ve bazı yüksek performanslı storage sistemleri için, scheduler overhead’ini tamamen ortadan kaldırmak adına “none” seçilebilir. Donanımın kendi iç optimizasyonu yeterince iyiyse bu yaklaşım en iyi performansı verir.
Mevcut Durumu Tespit Etmek
Önce neyle çalıştığınızı bilmeniz gerekir. Sisteminizdeki blok aygıtları ve kullandıkları scheduler’ları görüntülemek için:
# Tüm blok aygıtları için aktif scheduler'ı göster
for disk in /sys/block/*/queue/scheduler; do
echo "$disk: $(cat $disk)"
done
Daha okunabilir bir çıktı için:
# Spesifik bir disk için
cat /sys/block/sda/queue/scheduler
# Örnek çıktı:
# noop deadline [cfq]
# Köşeli parantez içindeki aktif scheduler'dır
Disk tipini (HDD mi SSD mi) anlamak da scheduler seçiminde kritiktir:
# 1 = HDD (rotational), 0 = SSD/NVMe
cat /sys/block/sda/queue/rotational
# Tüm diskler için toplu kontrol
for disk in /sys/block/sd*; do
echo "$(basename $disk): rotational=$(cat $disk/queue/rotational)"
done
Kernel sürümünüzü ve mevcut scheduler seçeneklerinizi kontrol edin:
uname -r
# NVMe diskler genellikle şu konumda görünür
ls /sys/block/ | grep nvme
cat /sys/block/nvme0n1/queue/scheduler
# Örnek çıktı: [none] mq-deadline kyber bfq
I/O Performansını Ölçmek
Scheduler değiştirmeden önce bir baseline ölçümü almanız şart. Bunun için fio aracı production-grade benchmark için en güvenilir seçenektir:
# fio kurulumu
apt install fio # Debian/Ubuntu
yum install fio # RHEL/CentOS
# Sequential read testi
fio --name=seq-read
--filename=/tmp/fio-test
--rw=read
--bs=128k
--size=1G
--numjobs=4
--time_based
--runtime=60
--group_reporting
# Random read/write (IOPS odaklı, veritabanı simülasyonu)
fio --name=random-rw
--filename=/tmp/fio-test
--rw=randrw
--rwmixread=70
--bs=4k
--size=1G
--numjobs=8
--iodepth=32
--time_based
--runtime=60
--group_reporting
--ioengine=libaio
--direct=1
Daha hızlı bir genel bakış için iostat kullanabilirsiniz:
# iostat ile gerçek zamanlı I/O izleme
iostat -xz 1
# Önemli metrikler:
# r/s, w/s: Saniyedeki okuma/yazma işlemi
# rMB/s, wMB/s: Saniyedeki MB cinsinden throughput
# await: Ortalama istek bekleme süresi (ms)
# %util: Disk kullanım yüzdesi
# r_await, w_await: Okuma/yazma için ayrı ayrı gecikme
# 5 saniyelik örneklerle izle
iostat -xz 5 /dev/sda
Scheduler’ı Değiştirmek
Geçici Olarak Değiştirme (Test İçin)
Sistemi yeniden başlatmadan, anında etkili olacak şekilde scheduler değiştirmek için:
# sda için deadline scheduler'a geç
echo deadline > /sys/block/sda/queue/scheduler
# NVMe disk için mq-deadline
echo mq-deadline > /sys/block/nvme0n1/queue/scheduler
# Değişikliği doğrula
cat /sys/block/sda/queue/scheduler
Bu yöntemle A/B testi yapabilirsiniz: Önce bir scheduler ile 5 dakika fio testi çalıştırın, sonra diğerine geçip aynı testi tekrarlayın. Farkı siyah beyaz görürsünüz.
Kalıcı Olarak Değiştirme
Geçici değişiklikler sistem yeniden başladığında kaybolur. Kalıcı hale getirmek için birkaç farklı yöntem vardır.
GRUB ile (tüm diskler için):
/etc/default/grub dosyasını düzenleyin:
# Mevcut satırı bul ve düzenle
GRUB_CMDLINE_LINUX="elevator=deadline"
# GRUB'u güncelle
update-grub # Debian/Ubuntu
grub2-mkconfig -o /boot/grub2/grub.cfg # RHEL/CentOS
udev kuralı ile (disk tipine göre):
Bu yöntem daha esnektir; SSD ve HDD için farklı scheduler kullanmanızı sağlar:
# /etc/udev/rules.d/60-ioschedulers.rules dosyası oluştur
cat > /etc/udev/rules.d/60-ioschedulers.rules << 'EOF'
# HDD için deadline
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="deadline"
# SSD için noop veya none
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="noop"
# NVMe için none veya mq-deadline
ACTION=="add|change", KERNEL=="nvme[0-9]*", ATTR{queue/scheduler}="none"
EOF
# Kuralları yeniden yükle
udevadm control --reload-rules
udevadm trigger --type=devices --action=change
systemd service ile:
# /etc/systemd/system/ioscheduler.service
cat > /etc/systemd/system/ioscheduler.service << 'EOF'
[Unit]
Description=Set I/O Scheduler
After=sysinit.target
[Service]
Type=oneshot
ExecStart=/bin/bash -c 'for disk in /sys/block/sd*; do echo deadline > $disk/queue/scheduler; done'
ExecStart=/bin/bash -c 'for disk in /sys/block/nvme*; do echo none > $disk/queue/scheduler; done'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
systemctl enable ioscheduler.service
systemctl start ioscheduler.service
Gerçek Dünya Senaryoları
Senaryo 1: PostgreSQL Veritabanı Sunucusu (SSD)
Bir müşterimizin PostgreSQL sunucusu, peak saatlerde query latency patlamaları yaşıyordu. Diskler SSD olmasına rağmen scheduler hala CFQ’da (eski kernel) kalıyordu. Önce durumu analiz ettik:
# PostgreSQL'in I/O beklemelerini gözlemle
iostat -xz 1 | grep -E "Device|sda"
# pg_stat_bgwriter ile PostgreSQL tarafını izle
psql -c "SELECT * FROM pg_stat_bgwriter;"
# Aktif scheduler
cat /sys/block/sda/queue/scheduler
# Çıktı: noop [cfq] deadline
Deadline’a geçtik ve queue depth’i ayarladık:
echo deadline > /sys/block/sda/queue/scheduler
echo 1 > /sys/block/sda/queue/iosched/fifo_batch
# Deadline parametrelerini veritabanı için optimize et
echo 100 > /sys/block/sda/queue/iosched/read_expire # ms
echo 500 > /sys/block/sda/queue/iosched/write_expire # ms
echo 1 > /sys/block/sda/queue/iosched/writes_starved
Sonuç: Ortalama query latency %35 düştü, p99 latency neredeyse yarıya indi.
Senaryo 2: Yoğun Yazma Yapan Log Sunucusu (HDD)
Merkezi log sunucusu için onlarca uygulama log akışı gönderiyordu. Diskler mekanikti ve NOOP scheduler kullanıyordu. Bu durumda NOOP tam bir felaket çünkü HDD’lerde rastgele I/O’nun neden olduğu seek time’ı optimize etmiyordu.
# Mevcut durum analizi
iostat -xz 5 /dev/sdb
# await değeri 80ms'in üzerindeydi, kabul edilemez
# CFQ yerine deadline deneyelim
echo deadline > /sys/block/sdb/queue/scheduler
# Write-heavy iş yükü için parametreler
echo 2000 > /sys/block/sdb/queue/iosched/write_expire
echo 0 > /sys/block/sdb/queue/iosched/writes_starved # Yazmaları önceliklendir
# Sonucu kontrol et
iostat -xz 5 /dev/sdb
# await 80ms'den 22ms'ye düştü
Senaryo 3: KVM/QEMU Sanal Makine Hostu
Sanallaştırma ortamlarında hem host hem guest seviyesinde scheduler yapılandırması yapmak gerekir. Guest, aslında bir blok aygıtına eriştiğini “sanır” ama gerçekte hypervisor katmanı zaten optimizasyon yapmaktadır. Bu yüzden guest’e NOOP koymak mantıklıdır:
# Host üzerinde, QEMU disk imajları için
# /dev/sda üzerinde oturan VM'ler için host'ta deadline kullanalım
echo deadline > /sys/block/sda/queue/scheduler
# Guest VM'in içinde (virtio disk için)
# /etc/udev/rules.d/60-ioschedulers.rules
echo 'ACTION=="add|change", KERNEL=="vd[a-z]", ATTR{queue/scheduler}="noop"'
> /etc/udev/rules.d/60-ioschedulers.rules
# Aynı şey libvirt XML'inde
# <driver name="qemu" type="raw" cache="none" io="native"/>
I/O Queue Derinliği ve Scheduler Parametreleri
Sadece scheduler seçmek yeterli değil; parametrelerini de ayarlamanız gerekebilir:
# Mevcut queue derinliğini gör
cat /sys/block/sda/queue/nr_requests
# NVMe için daha yüksek değer uygundur
echo 1024 > /sys/block/nvme0n1/queue/nr_requests
# Read-ahead değeri: Sequential workload için artır
cat /sys/block/sda/queue/read_ahead_kb
echo 2048 > /sys/block/sda/queue/read_ahead_kb
# Deadline scheduler parametreleri
ls /sys/block/sda/queue/iosched/
# fifo_batch front_merges read_expire write_expire writes_starved
# Parametrelerin anlamları:
# read_expire: Okuma isteğinin maximum bekleme süresi (ms, default 500)
# write_expire: Yazma isteğinin maximum bekleme süresi (ms, default 5000)
# writes_starved: Okuma önceliğinden önce kaç yazma turu
# fifo_batch: Bir turda işlenecek maximum istek sayısı
Monitoring ve Alarm Kurulumu
Scheduler değişikliğinin gerçekten işe yarayıp yaramadığını, ve zamanla performansın bozulup bozulmadığını izlemek şart:
#!/bin/bash
# io-monitor.sh - Basit I/O performans izleme scripti
THRESHOLD_AWAIT=50 # ms cinsinden alarm eşiği
DISK="sda"
while true; do
AWAIT=$(iostat -x 1 1 /dev/$DISK | awk '/^'"$DISK"'/{print $10}')
if (( $(echo "$AWAIT > $THRESHOLD_AWAIT" | bc -l) )); then
echo "UYARI: $DISK disk await süresi ${AWAIT}ms, eşik: ${THRESHOLD_AWAIT}ms"
echo "Aktif scheduler: $(cat /sys/block/$DISK/queue/scheduler)"
# Buraya alerting entegrasyonu ekleyebilirsiniz
fi
sleep 30
done
Prometheus ve node_exporter kullanıyorsanız, node_disk_io_time_weighted_seconds_total ve node_disk_read_time_seconds_total metrikleri size zaten detaylı I/O istatistikleri sağlar. Grafana üzerinde scheduler’ı değiştirdiğiniz anı bir annotation olarak işaretleyin; before/after karşılaştırması çok daha kolay olur.
Sık Yapılan Hatalar
- Production’da test yapmadan değiştirmek: Mutlaka staging ortamında deneyin ya da en azından off-peak saatte uygulayın
- Sanal makinede yanlış scheduler: VM guest’inde deadline ya da bfq kullanmak, çift optimizasyon overhead’i yaratır
- NVMe’de eski scheduler kullanmak: CFQ veya eski Deadline, multi-queue NVMe’nin kapasitesini tam kullanamaz; mq-deadline veya none tercih edin
- Scheduler değiştirip parametrelerini unutmak: Default parametreler her iş yükü için optimal değildir
- Read-ahead’i görmezden gelmek: Sequential workload’larda read-ahead değeri throughput üzerinde büyük etkiye sahiptir
- Sadece await’e bakmak: Düşük await her zaman iyi performans anlamına gelmez; throughput ile birlikte değerlendirin
Sonuç
I/O scheduler seçimi, sysadmin araç çantasındaki en değerli ama en az kullanılan optimizasyon araçlarından biridir. Doğru scheduler ile yanlış scheduler arasındaki fark, aynı donanım üzerinde %30-50’ye varan performans farkı yaratabilir. Genel rehber olarak şunu söyleyebilirim: NVMe diskler için none veya mq-deadline, SSD’ler için deadline veya noop, HDD’ler için deadline, VM guest’leri için noop. Ama bunlar başlangıç noktası; asıl optimizasyon kendi iş yükünüzü ölçerek gelir.
Her değişikliği önce izole bir ortamda test edin, fio ile net sayılar alın, production’a udevadm kuralları veya systemd servisi ile deploy edin ve mutlaka monitoring koyun. Disk reformu yapmadan, donanım yatırımı yapmadan bu kadar net bir performans kazanımı elde etmek, sysadmin’in en tatmin edici anlarından biridir.
