numactl Komutu ile NUMA Mimarisinde Bellek ve CPU Kaynaklarını Süreç Bazında Yönetme
Büyük sunucularda performans sorunları yaşıyorsanız ve “daha fazla RAM ekledik ama yine de yavaş” diyorsanız, büyük ihtimalle NUMA mimarisiyle ilgili bir sorunla karşı karşıyasınızdır. Bu yazıda numactl komutunu, NUMA’nın nasıl çalıştığını ve gerçek senaryolarda nasıl kullanabileceğinizi aktarmaya çalışacağım.
NUMA Nedir ve Neden Önemlidir?
Modern çok soketli sunucularda (dual-socket, quad-socket sistemler) bellek, tüm CPU’lara eşit uzaklıkta değildir. Non-Uniform Memory Access (NUMA) mimarisinde her CPU soketi, kendi yerel belleğine çok daha hızlı erişir. Uzak bir NUMA düğümündeki belleğe erişmek ise belirgin biçimde daha yavaştır.
Bir örnek verelim: Elimde iki soketli bir Dell PowerEdge R740 var, toplam 384 GB RAM. NUMA düğümü 0’daki CPU’lar kendi 192 GB’ına yaklaşık 80 ns’de erişirken, düğüm 1’deki belleğe erişim süresi 130-140 ns’e çıkabiliyor. Bu farkın küçük göründüğünü düşünebilirsiniz, ama yüksek frekanslı işlemlerde bu gecikme birikir ve ciddi bir darboğaz haline gelir.
İşte numactl bu noktada devreye girer. Süreçlerin hangi CPU’ları ve hangi bellek düğümlerini kullanacağını kontrol etmenizi sağlar.
numactl Kurulumu ve Temel Yapı
Çoğu dağıtımda numactl paketi kurulu gelmez veya gelir ama güncel değildir. Önce kontrol edelim:
numactl --version
numactl --hardware
Eğer yüklü değilse:
# RHEL/CentOS/Rocky Linux
sudo dnf install numactl numactl-devel
# Ubuntu/Debian
sudo apt install numactl
# SUSE
sudo zypper install numactl
numactl ile birlikte gelen numastat ve numademo araçları da oldukça faydalıdır. numactl-devel paketini kurarsanız kendi uygulamalarınızda NUMA farkındalığı eklemek için API’ye de erişebilirsiniz.
Sisteminizin NUMA Topolojisini Anlamak
Herhangi bir şey yapmadan önce sistemin nasıl yapılandırıldığını anlamanız gerekiyor. --hardware parametresi en kritik başlangıç noktasıdır:
numactl --hardware
Tipik bir çıktı şöyle görünür:
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 24 25 26 27 28 29 30 31 32 33 34 35
node 0 size: 192160 MB
node 0 free: 87432 MB
node 1 cpus: 12 13 14 15 16 17 18 19 20 21 22 23 36 37 38 39 40 41 42 43 44 45 46 47
node 1 size: 192160 MB
node 1 free: 91205 MB
node distances:
node 0 1
0: 10 21
1: 21 10
Burada node distances bölümüne dikkat edin. Kendi düğümüne erişim maliyeti 10 iken, çapraz erişim 21. Bu değerler sisteme göre değişir; bazı 4-soketli sistemlerde çok daha yüksek olabilir.
numastat komutu ise gerçek zamanlı NUMA istatistiklerini gösterir:
numastat
numastat -m # bellek detayları
numastat -p <PID> # belirli bir sürecin NUMA kullanımı
numa_miss ve numa_foreign değerleri yüksekse, sisteminizde NUMA kaymaları yaşanıyor demektir. Bu değerlerin sürekli artması ciddi bir performans sorununa işaret eder.
numactl ile Süreç Başlatma: Temel Kullanım
numactl komutunun genel sözdizimi şudur:
numactl [seçenekler] -- <komut> [komut argümanları]
CPU ve Bellek Düğümü Sabitleme
En sık kullanılan senaryo: Bir uygulamayı belirli bir NUMA düğümüne kilitlemek.
# Bir veritabanı sürecini NUMA düğümü 0'a bağla
numactl --cpunodebind=0 --membind=0 -- /usr/bin/postgres -D /var/lib/pgsql/data
# Bir Java uygulamasını sadece düğüm 1'e bağla
numactl --cpunodebind=1 --membind=1 -- java -Xmx64g -jar /opt/app/myservice.jar
# Birden fazla düğüm belirtmek için virgülle ayır
numactl --cpunodebind=0,1 --membind=0,1 -- /opt/myapp/bin/server
–cpunodebind: Sürecin hangi NUMA düğümlerindeki CPU’ları kullanabileceğini belirler. –membind: Bellek tahsisatının hangi NUMA düğümlerinden yapılacağını belirler. –physcpubind: NUMA düğümü değil, doğrudan CPU numarasıyla bağlama yapar. –localalloc: Belleği her zaman işlemin çalıştığı CPU’nun yerel düğümünden al. –preferred: Tercih edilen düğüm belirtir, yer yoksa diğerlerine geçer. –interleave: Belleği birden fazla düğüme dağıtır (round-robin).
Belirli CPU Çekirdeklerine Sabitleme
# Sadece CPU 0, 2, 4, 6'yı kullan
numactl --physcpubind=0,2,4,6 --membind=0 -- /opt/app/worker
# CPU aralığı belirtme
numactl --physcpubind=0-11 --membind=0 -- /opt/app/worker
Gerçek Dünya Senaryosu 1: PostgreSQL NUMA Optimizasyonu
Birkaç yıl önce bir müşterinin PostgreSQL 14 kurulumunda ciddi performans sorunları yaşanıyordu. numastat -p $(pgrep -f postgres | head -1) çıktısına baktığımızda numa_miss değerlerinin çok yüksek olduğunu gördük. Sorun neydi? PostgreSQL varsayılan olarak NUMA farkındalığına sahip değil ve Linux’un bellek tahsisatı mekanizması belleği her iki düğüme dağıtıyordu.
Çözüm için PostgreSQL’i systemd üzerinden numactl ile başlatacak şekilde yapılandırdık:
# /etc/systemd/system/postgresql.service.d/numa.conf
[Service]
ExecStart=
ExecStart=/usr/bin/numactl --cpunodebind=0 --membind=0 /usr/pgsql-14/bin/postmaster -D ${PGDATA}
systemctl daemon-reload
systemctl restart postgresql
Sonuç olarak numa_miss değerleri neredeyse sıfıra düştü ve sorgu sürelerinde ortalama %23 iyileşme gözlemledik. Tek bir yapılandırma değişikliği, yüzde yirmi üç. Bu rakamı müşteriye sunarken ben de şaşırdım açıkçası.
Gerçek Dünya Senaryosu 2: Redis Cluster’da NUMA Farkındalığı
Redis büyük ölçüde single-threaded çalışır ve NUMA kaymaları ciddi gecikmelere yol açabilir. Her Redis instance’ını kendi NUMA düğümüne kilitlemek en iyi pratiktir.
Iki soketli bir sistemde iki Redis instance çalıştırıyorsanız:
# İlk instance - düğüm 0
numactl --cpunodebind=0 --membind=0 -- redis-server /etc/redis/redis-6379.conf
# İkinci instance - düğüm 1
numactl --cpunodebind=1 --membind=1 -- redis-server /etc/redis/redis-6380.conf
Systemd ile yönetmek için:
# /etc/systemd/system/redis-6379.service
[Unit]
Description=Redis Instance 6379
After=network.target
[Service]
Type=forking
ExecStart=/usr/bin/numactl --cpunodebind=0 --membind=0 /usr/bin/redis-server /etc/redis/redis-6379.conf
User=redis
Group=redis
RuntimeDirectory=redis
RuntimeDirectoryMode=0755
[Install]
WantedBy=multi-user.target
Gerçek Dünya Senaryosu 3: Büyük Veri İşleme ve Bellek Dağıtımı
Bazen tam tersi gerekir: Tüm NUMA düğümlerine yayılmak. Özellikle büyük, bellek yoğun tek bir süreç çalıştırıyorsanız ve tek düğümün RAM’i yetmiyorsa --interleave modu devreye girer.
# Spark worker'ı tüm NUMA düğümlerine yay
numactl --interleave=all -- /opt/spark/bin/spark-class org.apache.spark.executor.CoarseGrainedExecutorBackend
# Sadece 0 ve 1 numaralı düğümlere dağıt
numactl --interleave=0,1 -- python3 /opt/ml/training_job.py --config prod.yaml
--interleave modu, belleğin eşit dağılmasını sağlar. Tahmin edilebilir gecikme istiyorsanız ve tek düğüm yetmiyorsa bu seçenek --membind‘dan daha iyi sonuç verebilir. Büyük matris işlemleri yapan bir makine öğrenmesi iş yükünde bu farkı bizzat test ettim; interleave modunda %15 daha iyi throughput elde ettik.
Mevcut Süreçlerin NUMA Politikasını İnceleme
Zaten çalışan bir sürecin NUMA politikasını görmek için:
# Sürecin NUMA politikasını görüntüle
numactl --show
# Belirli bir PID için
cat /proc/<PID>/numa_maps | head -20
# Örnek
cat /proc/$(pgrep postgres | head -1)/numa_maps | head -20
numa_maps çıktısı biraz kaba görünebilir ama her satır bir bellek bölgesini, hangi düğümden tahsis edildiğini ve ne kadar sayfa kullandığını gösterir. N0= ve N1= alanlarına bakın; eğer her ikisi de doluysa ve dengeli değilse NUMA kayması yaşıyorsunuz demektir.
# Sistemdeki tüm süreçlerin NUMA istatistiklerini özet olarak gör
numastat -p all 2>/dev/null | sort -k3 -rn | head -20
Çalışan Bir Sürecin CPU Bağlamasını Değiştirme
numactl yalnızca yeni süreçler başlatmak için kullanılır. Çalışan bir süreci yeniden başlatmadan NUMA politikasını değiştirmek için taskset veya cgroups kullanmanız gerekir. Ancak bellek politikasını değiştirmek için migrate_pages komutu işe yarar:
# Çalışan süreci başka NUMA düğümlerine taşı (CPU ve bellek)
# Önce mevcut NUMA durumunu kontrol et
numastat -p <PID>
# migrate_pages ile belleği taşı (numactl-libs paketinde gelir)
migratepages <PID> 1 0 # düğüm 1'deki belleği düğüm 0'a taşı
# taskset ile CPU bağlamasını değiştir (çalışırken)
taskset -cp 0-11 <PID> # İlk 12 CPU'ya bağla
Bu işlemler dikkatli yapılmalıdır. Üretim ortamında çalışan kritik bir süreç üzerinde migratepages kullanmadan önce test ortamında deneyin. Ben bir keresinde production’da bunu yaparken kısa süreli bir latency artışı yaşadım; büyük bellek bölgelerinin taşınması zaman alıyor.
NUMA Politikasını Script ile Otomatikleştirme
Birden fazla servis yönetiyorsanız ve her süreci tek tek elle bağlamak istemiyorsanız, basit bir yardımcı script işinizi görür:
#!/bin/bash
# numa_bind.sh - Servis adı ve NUMA düğümüne göre süreçleri bağla
set -euo pipefail
NUMA_NODE=${1:-0}
SERVICE_NAME=${2:-""}
if [ -z "$SERVICE_NAME" ]; then
echo "Kullanim: $0 <numa_node> <servis_adi>"
echo "Ornek: $0 0 nginx"
exit 1
fi
PIDS=$(pgrep -f "$SERVICE_NAME" 2>/dev/null || true)
if [ -z "$PIDS" ]; then
echo "Hata: '$SERVICE_NAME' icin calisan surec bulunamadi"
exit 1
fi
NUMA_CPUS=$(numactl --hardware | grep "node ${NUMA_NODE} cpus:" | awk -F': ' '{print $2}')
CPU_LIST=$(echo "$NUMA_CPUS" | tr ' ' ',')
for PID in $PIDS; do
echo "PID $PID -> NUMA dugumu $NUMA_NODE (CPU: $CPU_LIST)"
taskset -cp "$CPU_LIST" "$PID"
echo "Tamamlandi: PID $PID"
done
echo "NUMA istatistikleri:"
numastat -p "$SERVICE_NAME" 2>/dev/null || true
chmod +x numa_bind.sh
./numa_bind.sh 0 nginx
./numa_bind.sh 1 java
NUMA ve Huge Pages Kombinasyonu
NUMA optimizasyonu yaparken huge pages kullanımını da göz ardı etmeyin. İkisi birlikte kullanıldığında etki çok daha belirgin olur:
# Belirli bir NUMA düğümünde huge page tahsis et
echo 512 > /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages
echo 512 > /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages
# Huge page kullanan uygulamayı NUMA ile başlat
numactl --cpunodebind=0 --membind=0 --
/usr/bin/postgres
-c huge_pages=on
-D /var/lib/pgsql/data
Huge page’lerin NUMA düğümü bazında dağılımını kontrol etmek için:
grep -r HugePages /sys/devices/system/node/node*/meminfo
Yaygın Hatalar ve Dikkat Edilmesi Gerekenler
Bellek düğümü ve CPU düğümü uyumsuzluğu: --cpunodebind=0 --membind=1 gibi bir kombinasyon kullanmak NUMA amacını tamamen ortadan kaldırır. CPU’nun yerel olmayan bellekten veri çekmek zorunda kalmasına neden olursunuz. Bu durumu test etmek için kasıtlı yaptım bir keresinde, numa_miss değerleri neredeyse numa_hit değerlerini geçti.
NUMA farkındalığı olmayan uygulamalar: Bazı uygulamalar kendi iş parçacıklarını farklı CPU’lara dağıtır. Bu durumda --cpunodebind yerine --physcpubind ile daha hassas kontrol sağlayabilirsiniz.
Transparent Huge Pages ile çakışma: THP etkinse NUMA bellek politikanız beklenmedik şekilde davranabilir. Kritik iş yüklerinde THP’yi devre dışı bırakın:
echo never > /sys/kernel/mm/transparent_hugepage/enabled
NUMA balancing ile çatışma: Linux’un otomatik NUMA dengeleme özelliği (numa_balancing) bazen manuel politikanızla çakışabilir. Hangi tarafın kazandığını anlamak için:
cat /proc/sys/kernel/numa_balancing
# 1 ise etkin, kapatmak için:
echo 0 > /proc/sys/kernel/numa_balancing
Otomatik NUMA dengeleme her zaman kötü değildir. Genel amaçlı iş yükleri için açık bırakılabilir. Ama düşük gecikme kritik olan iş yükleri için kapatmak ve manuel politika uygulamak daha güvenilir sonuç verir.
Performans Karşılaştırması Nasıl Yapılır?
Değişikliklerinizin etkisini ölçmek için basit bir yaklaşım:
# NUMA istatistiklerini sıfırla ve belirli aralıklarla izle
watch -n 5 'numastat && echo "---" && numastat -m'
# Belirli bir sürecin NUMA performansını izle
while true; do
numastat -p <SERVIS_ADI> 2>/dev/null
sleep 10
done
# numa_miss / (numa_miss + numa_hit) oranını hesapla
awk '/numa_miss/{miss=$2} /numa_hit/{hit=$2} END{print "Miss orani: " miss/(miss+hit)*100 "%"}'
/sys/devices/system/node/node0/numastat
Miss oranı %5’in altındaysa iyi durumdasınızdır. %10 üzeriyse NUMA politikanızı gözden geçirmeniz gerekiyor.
Sonuç
numactl, büyük ölçekli sistemlerde görmezden gelinen ama yüksek etkili bir araç. Özellikle şu durumlarda kesinlikle incelemenizi öneririm: çok soketli sunucularda beklenmedik latency artışları yaşıyorsanız, RAM miktarı yeterli olmasına rağmen bellek yoğun iş yükleri yavaş çalışıyorsa ve numastat çıktısında yüksek numa_miss değerleri görüyorsanız.
NUMA optimizasyonu, “bir kez yapıp unut” değil, sürekli izleme gerektiren bir süreçtir. İş yükünüz değiştikçe, yeni servisler ekledikçe NUMA politikanızı güncellemeniz gerekebilir. Bu yazıda anlattığım teknikleri kendi ortamınızda önce test sunucusunda deneyin, ölçün ve sonra production’a taşıyın.
Performans optimizasyonunda mucizevi çözümler yok, ama doğru araçları doğru yerde kullanmak büyük fark yaratıyor. numactl de bu araçlardan biri.
