Merhaba arkadaşlar, bugün sizlere özellikle yüksek bellek kapasiteli sunucularınızda MySQL performansını ciddi anlamda etkileyebilen bir konudan, NUMA dengesizliğinden ve bunu nasıl çözeceğimizden bahsedeceğim. Çoklu CPU soketine sahip sunucularda, her CPU kendine özel bellek kanalına sahiptir (NUMA node). Eğer işletim sistemi veya uygulamanız bu mimariyi doğru yönetmezse, bir CPU diğer CPU'nun belleğine erişmek zorunda kalır ve bu da "uzak bellek erişimi" denen ciddi bir performans cezasına yol açar. Bugün bu sorunu nasıl tespit edip, MySQL'ı nasıl doğru node'lara sabitleyeceğimizi (pinning) adım adım göreceğiz.
NUMA Dengesizliği Nedir ve Neden Önemli?
Öncelikle basitçe anlatayım. Modern sunucularınızda 2 veya daha fazla fiziksel CPU (socket) olur. Her CPU, kendine fiziksel olarak en yakın RAM slotlarına daha hızlı erişir. İşte bu her bir "CPU + ona yakın RAM" grubuna bir NUMA Nodu diyoruz. İşletim sistemi varsayılan olarak bellek taleplerini tüm node'lara yaymaya çalışabilir. Bu, tek soketli sistemlerde sorun değil ama MySQL gibi yoğun bellek ve CPU kullanan bir uygulamada, prosesler bir node'da çalışırken belleği başka bir node'dan alırsa performans %30-50'ye varan oranlarda düşebilir. Buna NUMA dengesizliği diyoruz.
NUMA Durumunu ve Dengesizliği `numastat` ile İzleme
Sorunu çözmenin ilk adımı, onu gözlemlemektir. `numastat` aracı, her NUMA node'unun ne kadar bellek kullandığını ve "numa_miss" (uzak bellek erişimi) istatistiklerini gösterir. Hemen terminali açıp bakalım.
Çıktı şuna benzer bir tablo olacak:
Burada dikkat etmemiz gereken en kritik satırlar `Numa_Hit` ve `Numa_Miss`. `Numa_Hit`, bir prosesin kendi yerel node'undan başarılı bellek aldığını, `Numa_Miss` ise başka bir node'dan (uzaktan) bellek almak zorunda kaldığını gösterir. Eğer `Numa_Miss` değerleri, `Numa_Hit` değerlerine kıyasla çok yüksekse (örneğin %10-20'den fazlaysa), ciddi bir NUMA dengesizliği ve performans kaybı yaşıyorsunuz demektir. MySQL çalışırken bu komutu düzenli çalıştırıp durumu gözlemleyin.
MySQL'ı `numactl` ile Belirli NUMA Node'larına Bağlama (Pinning)
Sorunu tespit ettik. Şimdi çözüm zamanı. Amacımız, MySQL sunucusunun (mysqld) tüm proseslerini ve bellek taleplerini, mümkün olduğunca aynı NUMA node'u içinde tutmak. Bunun için `numactl` aracını kullanacağız. Benim genelde tercihim, MySQL'ı tüm node'lara yaymak yerine, özellikle yoğun iş yükü altında, belirli node'lara sabitlemektir.
Öncelikle sunucumuzdaki NUMA topolojisini öğrenelim:
Veya daha detaylı:
Bu komut, kaç tane node olduğunu ve her birinde ne kadar bellek bulunduğunu gösterecek. Diyelim ki 2 node'lu (Node 0 ve Node 1) bir sistemimiz var.
Şimdi, MySQL servisini durduralım ve mevcut systemd servis dosyasını düzenleyelim. En temiz ve kalıcı yöntem budur.
MySQL'ın systemd servis dosyasını bulup düzenleyeceğiz. Dosya genellikle /lib/systemd/system/mysql.service veya /usr/lib/systemd/system/mysqld.service yolundadır.
Bu komut, servis dosyasını düzenlemek için bir editör açacaktır. `[Service]` bölümünü bulun ve `ExecStart` satırını değiştirin. Değişiklik öncesi satır şuna benzer:
`ExecStart=/usr/sbin/mysqld --daemonize --pid-file=/run/mysqld/mysqld.pid`
Bu satırı, `numactl` komutu ile sararak değiştireceğiz. Örnek 1: MySQL'ı sadece Node 0'a sabitlemek:
Örnek 2 (Daha İyi Bir Yaklaşım - Benim Tercihim): MySQL'ı hem Node 0 hem Node 1'e yaymak, ancak bellek ayırmasını sadece bu node'larla sınırlamak (böylece işletim sistemi diğer uygulamalar için bellek ayırabilir). Bu yöntemde `--interleave` kullanırız:
`--interleave` parametresi, bellek ayırmalarını belirtilen node'lar arasında sırayla (round-robin) dağıtır. Bu, NUMA dengesizliğini önlerken, uygulamanın tüm belleğe erişebilmesini sağlar ve genellikle MySQL için en iyi genel performansı verir.
Değişikliği yaptıktan sonra, systemd'yi ve servisi yeniden yükleyip başlatalım:
Dikkat Edilmesi Gerekenler ve Test
Önce Test Edin: Bu ayarı yapmadan önce, mutlaka bir test/uygulama ortamında deneyin. Yanlış pinleme performansı daha da kötüleştirebilir.
Donanımınızı Tanıyın: Sunucunuzun NUMA topolojisini (`numactl --hardware`) iyi anlayın. Bazen sanal makinelerde veya bulut sunucularda NUMA yapısı farklı olabilir.
MySQL Ayarları: `innodb_buffer_pool_size` gibi büyük bellek havuzlarınızın, pinlediğiniz node'ların toplam fiziksel belleğini aşmamasına dikkat edin. Aşarsa takas (swap) kullanmaya başlar ve performans çöker.
Diğer Uygulamalar: Sunucuda sadece MySQL çalışmıyorsa, diğer kritik uygulamalar için de benzer pinleme stratejileri düşünebilirsiniz. Kaynakları izole etmek stabilite sağlar.
Değişiklik sonrası, sunucunuzu bir süre gerçek iş yükü altında çalıştırın ve `numastat` komutuyla `Numa_Miss` değerlerinin nasıl değiştiğini gözlemleyin. Ayrıca MySQL sorgu performansınızı ve genel sistem tepkisini de izleyin.
Sonuç
NUMA farkındalığı, özellikle yüksek yoğunluklu veritabanı sunucularında "bedavaya" performans kazanmanın en önemli yollarından biridir. Basit bir `numastat` kontrolü ve `numactl` ile yapılan bir konfigürasyon, gizli bir performans cezasını ortadan kaldırabilir.
Öncelikle basitçe anlatayım. Modern sunucularınızda 2 veya daha fazla fiziksel CPU (socket) olur. Her CPU, kendine fiziksel olarak en yakın RAM slotlarına daha hızlı erişir. İşte bu her bir "CPU + ona yakın RAM" grubuna bir NUMA Nodu diyoruz. İşletim sistemi varsayılan olarak bellek taleplerini tüm node'lara yaymaya çalışabilir. Bu, tek soketli sistemlerde sorun değil ama MySQL gibi yoğun bellek ve CPU kullanan bir uygulamada, prosesler bir node'da çalışırken belleği başka bir node'dan alırsa performans %30-50'ye varan oranlarda düşebilir. Buna NUMA dengesizliği diyoruz.
Sorunu çözmenin ilk adımı, onu gözlemlemektir. `numastat` aracı, her NUMA node'unun ne kadar bellek kullandığını ve "numa_miss" (uzak bellek erişimi) istatistiklerini gösterir. Hemen terminali açıp bakalım.
Bash:
numastat
Çıktı şuna benzer bir tablo olacak:
Kod:
Per-node numastat info (in MBs):
Node 0 Node 1
Numastat_MemTotal [B][/B][B][/B][B] [/B][B][/B][B][/B]
Numastat_MemFree [B][/B][B][/B][B] [/B][B][/B][B][/B]
Numastat_MemUsed [B][/B][B][/B][B] [/B][B][/B][B][/B]
Numa_Hit 12345678 9876543
Numa_Miss 1500000 2500000
Numa_Foreign 2500000 1500000
Burada dikkat etmemiz gereken en kritik satırlar `Numa_Hit` ve `Numa_Miss`. `Numa_Hit`, bir prosesin kendi yerel node'undan başarılı bellek aldığını, `Numa_Miss` ise başka bir node'dan (uzaktan) bellek almak zorunda kaldığını gösterir. Eğer `Numa_Miss` değerleri, `Numa_Hit` değerlerine kıyasla çok yüksekse (örneğin %10-20'den fazlaysa), ciddi bir NUMA dengesizliği ve performans kaybı yaşıyorsunuz demektir. MySQL çalışırken bu komutu düzenli çalıştırıp durumu gözlemleyin.
Sorunu tespit ettik. Şimdi çözüm zamanı. Amacımız, MySQL sunucusunun (mysqld) tüm proseslerini ve bellek taleplerini, mümkün olduğunca aynı NUMA node'u içinde tutmak. Bunun için `numactl` aracını kullanacağız. Benim genelde tercihim, MySQL'ı tüm node'lara yaymak yerine, özellikle yoğun iş yükü altında, belirli node'lara sabitlemektir.
Öncelikle sunucumuzdaki NUMA topolojisini öğrenelim:
Bash:
lscpu | grep -i numa
Veya daha detaylı:
Bash:
numactl --hardware
Bu komut, kaç tane node olduğunu ve her birinde ne kadar bellek bulunduğunu gösterecek. Diyelim ki 2 node'lu (Node 0 ve Node 1) bir sistemimiz var.
Şimdi, MySQL servisini durduralım ve mevcut systemd servis dosyasını düzenleyelim. En temiz ve kalıcı yöntem budur.
Bash:
sudo systemctl stop mysql
MySQL'ın systemd servis dosyasını bulup düzenleyeceğiz. Dosya genellikle /lib/systemd/system/mysql.service veya /usr/lib/systemd/system/mysqld.service yolundadır.
Bash:
sudo systemctl edit mysql --full
Bu komut, servis dosyasını düzenlemek için bir editör açacaktır. `[Service]` bölümünü bulun ve `ExecStart` satırını değiştirin. Değişiklik öncesi satır şuna benzer:
`ExecStart=/usr/sbin/mysqld --daemonize --pid-file=/run/mysqld/mysqld.pid`
Bu satırı, `numactl` komutu ile sararak değiştireceğiz. Örnek 1: MySQL'ı sadece Node 0'a sabitlemek:
INI:
ExecStart=/usr/bin/numactl --cpunodebind=0 --membind=0 /usr/sbin/mysqld --daemonize --pid-file=/run/mysqld/mysqld.pid
Örnek 2 (Daha İyi Bir Yaklaşım - Benim Tercihim): MySQL'ı hem Node 0 hem Node 1'e yaymak, ancak bellek ayırmasını sadece bu node'larla sınırlamak (böylece işletim sistemi diğer uygulamalar için bellek ayırabilir). Bu yöntemde `--interleave` kullanırız:
INI:
ExecStart=/usr/bin/numactl --interleave=0,1 /usr/sbin/mysqld --daemonize --pid-file=/run/mysqld/mysqld.pid
`--interleave` parametresi, bellek ayırmalarını belirtilen node'lar arasında sırayla (round-robin) dağıtır. Bu, NUMA dengesizliğini önlerken, uygulamanın tüm belleğe erişebilmesini sağlar ve genellikle MySQL için en iyi genel performansı verir.
Değişikliği yaptıktan sonra, systemd'yi ve servisi yeniden yükleyip başlatalım:
Bash:
sudo systemctl daemon-reload
sudo systemctl start mysql
sudo systemctl status mysql
Önce Test Edin: Bu ayarı yapmadan önce, mutlaka bir test/uygulama ortamında deneyin. Yanlış pinleme performansı daha da kötüleştirebilir.
Donanımınızı Tanıyın: Sunucunuzun NUMA topolojisini (`numactl --hardware`) iyi anlayın. Bazen sanal makinelerde veya bulut sunucularda NUMA yapısı farklı olabilir.
MySQL Ayarları: `innodb_buffer_pool_size` gibi büyük bellek havuzlarınızın, pinlediğiniz node'ların toplam fiziksel belleğini aşmamasına dikkat edin. Aşarsa takas (swap) kullanmaya başlar ve performans çöker.
Diğer Uygulamalar: Sunucuda sadece MySQL çalışmıyorsa, diğer kritik uygulamalar için de benzer pinleme stratejileri düşünebilirsiniz. Kaynakları izole etmek stabilite sağlar.
Değişiklik sonrası, sunucunuzu bir süre gerçek iş yükü altında çalıştırın ve `numastat` komutuyla `Numa_Miss` değerlerinin nasıl değiştiğini gözlemleyin. Ayrıca MySQL sorgu performansınızı ve genel sistem tepkisini de izleyin.
NUMA farkındalığı, özellikle yüksek yoğunluklu veritabanı sunucularında "bedavaya" performans kazanmanın en önemli yollarından biridir. Basit bir `numastat` kontrolü ve `numactl` ile yapılan bir konfigürasyon, gizli bir performans cezasını ortadan kaldırabilir.