IRQ Dengeleme ile Ağ Performansını İyileştirme

Yüksek trafikli bir sunucuda ağ performansı sorunlarıyla boğuşurken çoğu zaman ilk akla gelen şeyler buffer boyutları, TCP parametreleri ya da NIC sürücü ayarları olur. Ama işin özüne indiğinde, IRQ (Interrupt Request) dağılımının ne kadar kritik bir rol oynadığını fark etmek sizi şaşırtabilir. Özellikle çok çekirdekli modern sunucularda, tüm ağ kesmelerinin tek bir CPU çekirdeğine yığılması hem o çekirdeği boğar hem de diğer çekirdekleri boşta bırakır. Bu yazıda IRQ dengelemenin ne olduğunu, neden önemli olduğunu ve bunu nasıl düzgün yapılandıracağınızı gerçek dünya senaryolarıyla ele alacağız.

IRQ Dengeleme Nedir ve Neden Önemlidir?

Her ağ paketi işlendiğinde, NIC bir donanım kesmesi üretir ve bu kesmeyi hangi CPU çekirdeğinin işleyeceği IRQ atama tablosuyla belirlenir. Varsayılan Linux kurulumlarında bu atama genellikle CPU0’a yığılır. 10GbE veya 25GbE gibi yüksek bant genişlikli arayüzlerle çalışıyorsanız, saniyede milyonlarca kesme oluşabilir ve bunların tamamı tek bir çekirdekten geçmeye çalışır.

Softirq mekanizması bu yükü biraz dağıtsa da yetersiz kalır. top çıktısında %si sütununu izlediğinizde tek bir çekirdekte sürekli yüksek softirq kullanımı görüyorsanız, IRQ dengeleme probleminiz var demektir.

Pratik sonuçları şöyle sıralayabiliriz:

  • Yüksek paket kaybı ve gecikme artışı
  • Ağ yoğun iş yüklerinde CPU darboğazı oluşması
  • Diğer çekirdeklerin atıl kalması nedeniyle kaynak israfı
  • Latency-sensitive uygulamalarda tutarsız performans

Mevcut IRQ Durumunu Analiz Etmek

Önce nerede durduğumuzu anlamak gerekiyor. Sistem üzerindeki IRQ dağılımını görmek için:

cat /proc/interrupts

Bu komutun çıktısı oldukça uzun olabilir. Sadece ağ arayüzüne ait IRQ’ları filtrelemek için:

grep -E "eth|ens|enp|bond" /proc/interrupts

Bir 10GbE kartında tipik bir çıktı şöyle görünebilir:

 120:  847392103          0          0          0   PCI-MSI  eth0-rx-0
 121:       1243          0          0          0   PCI-MSI  eth0-rx-1
 122:        892          0          0          0   PCI-MSI  eth0-rx-2
 123:        741          0          0          0   PCI-MSI  eth0-rx-3

Soldaki sayılar her CPU çekirdeğinin işlediği kesme sayısını gösterir. Yukarıdaki örnekte tüm yük CPU0’da birikmiş, diğer üç çekirdek neredeyse hiç iş yapmıyor.

Hangi IRQ numarasının hangi CPU’ya atandığını görmek için:

for irq in $(grep -E "eth0|ens3" /proc/interrupts | awk -F: '{print $1}' | tr -d ' '); do
    echo "IRQ $irq --> CPU affinity: $(cat /proc/irq/$irq/smp_affinity_list)"
done

Ağ arayüzünüzdeki kuyruk sayısını öğrenmek de önemli:

ethtool -l eth0

Çıktıda Combined veya RX/TX satırlarına bakın. Bu, kaç tane bağımsız kuyruk (ve dolayısıyla kaç IRQ) kullanabileceğinizi gösterir.

irqbalance Servisi: Dost mu Düşman mı?

irqbalance, IRQ’ları otomatik olarak çekirdekler arasında dağıtmaya çalışan bir daemon’dur. Çoğu dağıtımda varsayılan olarak çalışır. Genel kullanım senaryoları için makul sonuçlar verir ama yüksek performans gerektiren ağ senaryolarında yetersiz kalabilir hatta kontraproduktif olabilir.

Durumunu kontrol edelim:

systemctl status irqbalance

irqbalance’ın bir an için ne yaptığını görmek için:

irqbalance --oneshot --debug 2>&1 | grep -i "eth|irq"

Yüksek performans senaryoları için genellikle irqbalance’ı devre dışı bırakıp manuel atama yapmanızı öneririm. Bunun nedeni irqbalance’ın dinamik kararlar alması ve zaman zaman kesmelerinizi NUMA node sınırları dışında dağıtmasıdır.

irqbalance’ı devre dışı bırakmak için:

systemctl stop irqbalance
systemctl disable irqbalance

Eğer irqbalance’ı tamamen kapatmak istemiyorsanız, belirli IRQ’ları irqbalance’ın elinden almanın yolu şöyledir. /etc/sysconfig/irqbalance veya /etc/default/irqbalance dosyasını düzenleyip:

IRQBALANCE_BANNED_CPUS="0"

Bu satır CPU0’ı irqbalance’ın kullanım alanı dışına alır. Kritik IRQ’ları belirli çekirdeklere kendiniz atadıktan sonra bu korumayı eklemek mantıklıdır.

Manuel IRQ Atama: Temel Yöntem

IRQ affinitysi /proc/irq//smp_affinity dosyası üzerinden hexadecimal bitmask ile veya smp_affinity_list üzerinden okunabilir liste formatında ayarlanır.

Diyelim ki eth0 için IRQ 120, 121, 122, 123 numaralı kesmeler mevcut ve bunları sırasıyla CPU 0, 1, 2, 3’e atamak istiyoruz:

# CPU0 = hex 1, CPU1 = hex 2, CPU2 = hex 4, CPU3 = hex 8
echo 1 > /proc/irq/120/smp_affinity
echo 2 > /proc/irq/121/smp_affinity
echo 4 > /proc/irq/122/smp_affinity
echo 8 > /proc/irq/123/smp_affinity

Ya da daha okunabilir smp_affinity_list yöntemiyle:

echo 0 > /proc/irq/120/smp_affinity_list
echo 1 > /proc/irq/121/smp_affinity_list
echo 2 > /proc/irq/122/smp_affinity_list
echo 3 > /proc/irq/123/smp_affinity_list

Otomatik IRQ Atama Script’i

Elle yapmak çok sayıda kuyruk olduğunda pratik değil. Aşağıdaki script, belirttiğiniz NIC’in IRQ’larını otomatik olarak çekirdeklere dağıtır:

#!/bin/bash
# irq_set_affinity.sh
# Kullanim: ./irq_set_affinity.sh eth0

NIC=${1:-eth0}
CPU_COUNT=$(nproc)

echo "NIC: $NIC icin IRQ affinity ayarlaniyor..."
echo "Kullanilabilir CPU sayisi: $CPU_COUNT"

# irqbalance'i durdur
systemctl stop irqbalance 2>/dev/null

# NIC'e ait IRQ'lari bul
IRQ_LIST=$(grep "$NIC" /proc/interrupts | awk -F: '{print $1}' | tr -d ' ')

if [ -z "$IRQ_LIST" ]; then
    echo "HATA: $NIC icin IRQ bulunamadi!"
    exit 1
fi

CPU_IDX=0
for IRQ in $IRQ_LIST; do
    if [ -f "/proc/irq/$IRQ/smp_affinity_list" ]; then
        echo $CPU_IDX > /proc/irq/$IRQ/smp_affinity_list
        echo "IRQ $IRQ --> CPU$CPU_IDX atandi"
        CPU_IDX=$(( (CPU_IDX + 1) % CPU_COUNT ))
    fi
done

echo "Atama tamamlandi. Mevcut durum:"
for IRQ in $IRQ_LIST; do
    echo "IRQ $IRQ: CPU $(cat /proc/irq/$IRQ/smp_affinity_list)"
done

Bu script’i çalıştırdıktan sonra /proc/interrupts çıktısını izleyerek dağılımın düzeldiğini doğrulayabilirsiniz.

NUMA Topolojisini Hesaba Katmak

Çift soketli ya da büyük çok çekirdekli sunucularda NUMA (Non-Uniform Memory Access) topolojisini göz ardı etmek performans kazanımlarını boşa çıkarabilir. NIC’in hangi NUMA node’una bağlı olduğunu şöyle öğrenebilirsiniz:

cat /sys/class/net/eth0/device/numa_node

Bu komut genellikle 0 ya da 1 döndürür. Çıktı 0 ise, IRQ’larınızı NUMA node 0’a ait CPU çekirdeklerine atamanız bellek erişim gecikmesini minimize eder.

Hangi CPU’ların hangi NUMA node’una ait olduğunu görmek için:

numactl --hardware

ya da daha detaylı:

lscpu | grep -E "NUMA|CPU(s)"

Örneğin 0-7 arası CPU’lar NUMA node 0’da ve NIC de node 0’a bağlıysa, IRQ’larınızı sadece bu 8 çekirdeğe dağıtın. Şöyle:

#!/bin/bash
NIC="eth0"
NUMA_NODE=$(cat /sys/class/net/$NIC/device/numa_node)

# NUMA node'una ait CPU listesini al
NUMA_CPUS=$(numactl --hardware | grep "node $NUMA_NODE cpus:" | awk -F: '{print $2}' | tr ' ' 'n' | grep -v '^$')

IRQ_LIST=$(grep "$NIC" /proc/interrupts | awk -F: '{print $1}' | tr -d ' ')
CPU_ARRAY=($NUMA_CPUS)
CPU_COUNT=${#CPU_ARRAY[@]}
IDX=0

for IRQ in $IRQ_LIST; do
    TARGET_CPU=${CPU_ARRAY[$IDX]}
    echo $TARGET_CPU > /proc/irq/$IRQ/smp_affinity_list
    echo "IRQ $IRQ --> CPU$TARGET_CPU (NUMA$NUMA_NODE)"
    IDX=$(( (IDX + 1) % CPU_COUNT ))
done

RSS ve RPS ile Yazılım Tarafında Dengeleme

RSS (Receive Side Scaling) donanım düzeyinde çalışır ve NIC’in birden fazla kuyruk desteklemesi gerektirir. RPS (Receive Packet Steering) ise yazılım düzeyinde çalışarak RSS’in yapamadığı durumlarda devreye girer.

RSS için NIC kuyruk sayısını optimize etmek:

# Mevcut aktif kuyruk sayisini goster
ethtool -l eth0

# Kuyruk sayisini CPU sayisina esitlemek
ethtool -L eth0 combined 8

RPS aktifleştirmek için tüm çekirdeklerin kullanılmasını sağlayan bitmask değerini yazıyoruz. 8 çekirdekli sistemde ff değeri tüm çekirdekleri kapsar:

for rx in /sys/class/net/eth0/queues/rx-*/rps_cpus; do
    echo ff > $rx
    echo "RPS ayarlandi: $rx"
done

16 çekirdekli sistemde ffff, 32 çekirdekli sistemde ffffffff kullanırsınız. Çekirdek sayınıza göre hesaplamak için:

CPU_COUNT=$(nproc)
BITMASK=$(python3 -c "print(hex(2**$CPU_COUNT - 1)[2:])")
echo "Kullanilacak bitmask: $BITMASK"

for rx in /sys/class/net/eth0/queues/rx-*/rps_cpus; do
    echo $BITMASK > $rx
done

RFS (Receive Flow Steering) ise paketi doğrudan ilgili uygulamanın çalıştığı CPU’ya yönlendirir, böylece cache miss’leri azaltır:

# Globel RFS tablosu boyutu
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries

# Her kuyruk icin akis tablosu boyutu
for rx in /sys/class/net/eth0/queues/rx-*/rps_flow_cnt; do
    echo 4096 > $rx
done

Gerçek Dünya Senaryosu: Web Sunucu Performans Sorunu

Bir e-ticaret platformunun backend sunucusunda yaşanan gerçek bir durumu ele alalım. Sunucu özellikleri: 2x Intel Xeon 16 çekirdek (toplam 32 çekirdek, 64 thread), 25GbE NIC, yoğun dönemlerde 500K+ req/s hedefi.

Sorun tespiti:

# CPU kullanim durumuna bak
mpstat -P ALL 1 5 | grep -v "^$" | awk '$3 > 80 {print "YUKSEK: CPU"$2, "si="$8"%"}'

Bu komutun çıktısında CPU0 üzerinde %si (softirq) değerinin %78’e çıktığını, diğer CPU’ların ise %5-10 civarında kaldığını gördük.

Çözüm adımları:

# 1. NIC kuyruk sayisini arttir
ethtool -L eth0 combined 16

# 2. irqbalance'i kapat
systemctl stop irqbalance && systemctl disable irqbalance

# 3. IRQ'lari NUMA-aware dagit
NIC="eth0"
NUMA_NODE=0
# Xeon E5 ailesi: CPU 0-15 node0, 16-31 node1
for I in $(seq 0 15); do
    IRQ=$(grep "${NIC}-TxRx-${I}" /proc/interrupts | awk -F: '{print $1}' | tr -d ' ')
    [ -n "$IRQ" ] && echo $I > /proc/irq/$IRQ/smp_affinity_list
done

# 4. XPS (Transmit Packet Steering) ayarla
for I in $(seq 0 15); do
    MASK=$(printf "%x" $((1 << $I)))
    echo $MASK > /sys/class/net/eth0/queues/tx-${I}/xps_cpus 2>/dev/null
done

Bu adımların ardından aynı yük altında CPU0’ın %si değeri %78’den %12’ye düşerken toplam throughput yaklaşık %35 artış gösterdi. Daha önemlisi, P99 latency değerleri önemli ölçüde iyileşti.

Değişiklikleri Kalıcı Hale Getirmek

/proc üzerinden yapılan değişiklikler yeniden başlatmada kaybolur. Bunu kalıcı hale getirmenin en temiz yolu systemd service oluşturmaktır:

cat > /etc/systemd/system/irq-affinity.service << 'EOF'
[Unit]
Description=IRQ Affinity Configuration
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/irq_set_affinity.sh eth0
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable irq-affinity.service
systemctl start irq-affinity.service

Script dosyasını da doğru konuma kopyalayıp çalıştırılabilir yapın:

cp irq_set_affinity.sh /usr/local/bin/irq_set_affinity.sh
chmod +x /usr/local/bin/irq_set_affinity.sh

Sonuçların İzlenmesi

Yaptığınız değişikliklerin etkisini ölçmek için birkaç temel metriği izleyin:

# IRQ dagilimini canli izle
watch -n 1 "cat /proc/interrupts | grep eth0"

# CPU bazi softirq istatistikleri
sar -I ALL 1 10

# Paket basina CPU kullanimi
sar -n DEV 1 5

# Kuyruk basi paket istatistikleri
ethtool -S eth0 | grep -E "rx_queue|tx_queue|missed|dropped"

Uzun vadeli izleme için Prometheus + node_exporter kombinasyonu node_softnet_* metriklerini toplar ve Grafana dashboard üzerinden görselleştirmenizi sağlar. node_softnet_dropped_total metriği özellikle dikkat edilmesi gereken bir göstergedir; bu değerin artması IRQ işleme kapasitesinin yetersiz kaldığına işaret eder.

Sık Yapılan Hatalar

IRQ dengeleme ayarlarken karşılaşılan yaygın tuzaklar şunlardır:

  • NUMA boundary ihlali: NIC’in bağlı olduğu NUMA node dışındaki CPU’lara IRQ atamak, bellek erişim gecikmesini artırır ve kazanılan performansı geri verir
  • Hyperthreading karmaşası: Aynı fiziksel çekirdeğin iki logical thread’ine aynı IRQ’ları atamak beklenen faydayı vermez; mümkünse fiziksel çekirdek başına bir IRQ kuyrukta tutun
  • Kuyruk sayısı ve IRQ uyumsuzluğu: ethtool ile artırdığınız kuyruk sayısının gerçekten IRQ oluşturup oluşturmadığını /proc/interrupts üzerinden doğrulamadan atama yapmak anlamsızdır
  • irqbalance çakışması: Manuel atama yaptıktan sonra irqbalance’ı kapatmayı unutursanız, daemon kısa sürede ayarlarınızı ezebilir
  • Yanlış bitmask hesabı: Özellikle 32+ çekirdekli sistemlerde hex bitmask değerini hesaplarken hata yapmak kolaydır; smp_affinity_list yöntemini tercih edin

Sonuç

IRQ dengeleme, ağ performansı optimizasyonunun görünmez ama son derece etkili bir bileşenidir. Yüksek trafikli ortamlarda tek bir CPU çekirdeğine yığılan kesmeler hem darboğaz oluşturur hem de sistemin geri kalan kaynaklarını atıl bırakır. Bu yazıda incelediğimiz yöntemleri kısaca özetlersek:

  • /proc/interrupts ile mevcut durumu analiz edin
  • irqbalance yetersiz kalıyorsa devre dışı bırakıp manuel atama yapın
  • NUMA topolojisini mutlaka hesaba katın
  • RSS/RPS/RFS mekanizmalarını katmanlı olarak kullanın
  • XPS ile transmit tarafını da ihmal etmeyin
  • Değişikliklerinizi systemd service ile kalıcı hale getirin
  • Sonuçları metriklerle ölçün ve doğrulayın

Her sunucu ortamı farklıdır; burada anlattıklarımı kendi altyapınıza uygularken kademeli ilerleyin ve her adımın etkisini ölçün. Production ortamında kör bir şekilde uygulanan optimizasyonlar bazen iyileştirmek yerine durumu kötüleştirebilir. Test edin, ölçün, uygulayın.

Similar Posts

Bir yanıt yazın

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