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.

Similar Posts

Bir yanıt yazın

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