#MySQL#OpenSearch#KVM#NUMA#Sysctl
Kurumsal veri merkezlerinde en sık karşılaşılan paradokslardan biri şudur: 256GB veya 512GB RAM’e sahip bir veritabanı (PostgreSQL, Percona/MySQL, OpenSearch) veya KVM sanallaştırma sunucusunda free -h çıktısına baktığınızda onlarca gigabayt bellek available olarak görünür; ancak sunucu periyodik olarak 3-5 saniye boyunca tamamen donar (stall), CPU sys (kernel) veya iowait kullanımı %100’e vurur ve ardından her şey normale döner.
Bu anlık “mikro-donmaların” sebebi bellek yetersizliği değil; Linux çekirdeğinin varsayılan sanal bellek yöneticisinin (vm) masaüstü bilgisayarlar veya küçük ölçekli sunucular için tasarlanmış olmasıdır. Yüzlerce gigabayt RAM üzerinde varsayılan yüzdelik oranlarla çalışan THP (Transparent HugePages), Dirty Page Flush ve NUMA Rebalancing mekanizmaları, büyük bellekli sunucularda saatli birer bombaya dönüşür.
Bugünkü yazımızda, yüksek bellekli Linux sunucularda bu görünmez duvarları nasıl ortadan kaldıracağımızı adım adım inceliyoruz.
1. Transparent HugePages (THP) Kabusu vs. Explicit HugePages (hugetlbfs)
Linux’ta standart bellek sayfası (page size) 4 KB boyutundadır. 256 GB RAM kullanan bir veritabanı sunucusunda çekirdeğin yönetmesi gereken tam 67.108.864 adet 4KB’lık sayfa vardır. İşlemcinin Sanal-Fiziksel adres dönüşüm önbelleği olan TLB (Translation Lookaside Buffer) bu kadar büyük bir tabloyu tutamaz ve sürekli TLB Miss yaşanır.
Çözüm 2 MB (veya 1 GB) boyutundaki HugePages kullanmaktır. Ancak Linux burada iki farklı yol sunar ve ikisini birbirine karıştırmak felakete yol açar:
A. Transparent HugePages (THP) Neden Kapatılmalı?
THP, arka planda çalışan khugepaged adlı çekirdek süreciyle 4KB’lık sayfaları dinamik olarak birleştirip 2MB’lık bloklar haline getirmeye çalışır. Bellek parçalandığında (fragmentation), bir veritabanı bellek istediği anda çekirdek tüm I/O ve sorgu işlemlerini durdurup bellekte Direct Compaction (Doğrudan Sıkıştırma/Birleştirme) başlatır. Percona MySQL, PostgreSQL, Redis, MongoDB ve OpenSearch/Elasticsearch ortamlarındaki anlık CPU %100 sıçramalarının bir numaralı sorumlusu budur.
THP durumunu kontrol etmek ve devre dışı bırakmak:
# Mevcut durumu kontrol edin (Köşeli parantez aktif olanı gösterir)
cat /sys/kernel/mm/transparent_hugepage/enabled
cat /sys/kernel/mm/transparent_hugepage/defrag
# Anlık olarak kapatmak için (Canlı sistemde kesinti yapmaz):
echo never | sudo tee /sys/kernel/mm/transparent_hugepage/enabled
echo never | sudo tee /sys/kernel/mm/transparent_hugepage/defrag
Kalıcı olması için /etc/default/grub içindeki GRUB_CMDLINE_LINUX satırına transparent_hugepage=never ekleyip update-grub (veya RHEL’de grub2-mkconfig) çalıştırın.
B. Explicit (Statik) HugePages (vm.nr_hugepages) Ne Zaman Açılmalı?
THP’yi kapattıktan sonra, büyük bir shared_buffers (PostgreSQL), innodb_buffer_pool_size (MySQL) veya KVM sanal makine belleğini önceden ayrılmış, parçalanmayan ve asla Swap’a atılamayan (locked) 2MB’lık statik sayfalarla beslemek için Explicit HugePages kullanılır.
Örneğin 64 GB shared_buffers kullanan bir PostgreSQL sunucusu için gereken 2MB’lık sayfa sayısını hesaplayalım (Güvenlik payı olarak +%5 eklenir):
$$\frac{64 \times 1024 \text{ MB}}{2 \text{ MB}} = 32768 \text{ sayfa} \rightarrow \sim 34000 \text{ sayfa}$$
# /etc/sysctl.d/99-vmninja-memory.conf
vm.nr_hugepages = 34000
Böylece PostgreSQL (huge_pages = on) veya KVM (<memoryBacking><hugepages/></memoryBacking>) belleği doğrudan TLB dostu 2MB’lık bloklardan sıfır CPU birleştirme yüküyle kullanır.
2. “Dirty Pages” Tuzağı: Varsayılan %20 Oranı Neden Diskleri Kilitler?
Linux’ta uygulamalar diske veri yazdığında, veri önce RAM’deki Page Cache (Dirty Pages) alanına yazılır ve uygulama işine devam eder. Çekirdeğin pdflush / flush iş parçacıkları bu kirli sayfaları arka planda diske yazar.
Varsayılan Linux ayarları şunlardır:
vm.dirty_background_ratio = 10(RAM’in %10’u kirlenince arka planda diske yazmaya başla)vm.dirty_ratio = 20(RAM’in %20’si kirlenirse tüm uygulamaların I/O işlemlerini durdur ve senkron yazmaya geç)
Matematiksel Felaket: 512 GB RAM’li bir sunucuda %10, 51.2 GB demektir! Yani Linux, RAM’de 51 GB yazılmamış veri birikene kadar diske neredeyse hiçbir şey yazmaz; sonra bir anda SAN/NVMe katmanına 51 GB’lık devasa bir yazma tsunamisi (I/O Storm) gönderir. Bu sırada disk kuyrukları (aqu-sz) patlar ve veritabanı kilitlenir.
Çözüm: Yüzdelik (ratio) Yerine Sabit Boyut (bytes) Kullanmak
Büyük bellekli sunucularda yüzdelik oranları iptal edip doğrudan byte cinsinden sabit ve küçük eşikler tanımlamalısınız:
# Arka plan yazma işlemini RAM'de 256 MB kirli veri biriktiğinde hemen başlat:
vm.dirty_background_bytes = 268435456
# Uygulamaları durduran (throttling) üst sınırı 1 GB (1073741824 byte) olarak sabitle:
vm.dirty_bytes = 1073741824
Not: dirty_bytes ayarlandığında çekirdek dirty_ratio değerini otomatik olarak 0 yapar. Bu ayar sayesinde yazma trafiği dev dalgalar halinde değil, kurumsal NVMe/SAN önbelleklerinin en sevdiği şekilde sürekli ve pürüzsüz (smooth stream) olarak diske akar.
3. vm.swappiness = 0 vs 1: Çekirdek Sürümü Farkı!
Yıllarca ezbere uygulanan vm.swappiness = 0 kuralı, modern Linux çekirdeklerinde (Kernel 3.5+ ve RHEL 8/9, Ubuntu 20.04+) davranış değiştirdi:
- vm.swappiness = 0: Çekirdeğe, dosya önbelleği (page cache) tamamen bitene kadar hiçbir şekilde anonim bellek sayfalarını swap’a atmamasını söyler. Bu durum, ani bellek dalgalanmalarında çekirdeğin kurtarma manevrası yapamayıp doğrudan OOM Killer’ı tetikleyerek
mysqld,postgresveyaqemu-system-x86_64sürecini öldürmesiyle sonuçlanır. - vm.swappiness = 1 (Kurumsal Standart): Normal şartlarda swap yapmaz, ancak OOM Killer tetiklenmeden hemen önce gerçekten hiç kullanılmayan (atıl) bellek sayfalarını swap’a taşıyarak kritik servisin çökmesini engeller.
(İstisna: Kubernetes Node’larında ve OpenSearch/Elasticsearch sunucularında JVM Garbage Collection’ın swap’a dokunup saniyelerce donmaması için swap tamamen kapatılır: swapoff -a).
4. Çok Soketli Sunucularda NUMA Tuzağı (“Swap Insanity”) ve zone_reclaim_mode
Çift soketli (Dual-Socket) sunucularda (Örn: 2 x Intel Xeon / AMD EPYC, toplam 256 GB RAM) bellek ikiye bölünür: NUMA Node 0 (128 GB) ve NUMA Node 1 (128 GB).
Eğer tek bir büyük MySQL veya OpenSearch süreci 160 GB RAM talep ederse, Node 0 üzerindeki 128 GB tamamen dolar. Eğer vm.zone_reclaim_mode = 1 ise veya süreç sadece Node 0’a sabitlenmişse; sunucuda Node 1 üzerinde 128 GB bomboş RAM dururken, çekirdek Node 0 içindeki belleği çılgınca Swap’a atmaya başlar (Jeremy Cole tarafından tanımlanan meşhur MySQL Swap Insanity durumu).
NUMA Dengesizliğini Kontrol Etmek ve Çözmek:
# NUMA düğümlerindeki bellek dağılımını ve 'numa_miss' (uzak düğümden bellek okuma) sayaçlarını görün
numastat -m
numastat -c postgres
Kurumsal veritabanı ve KVM sunucuları için /etc/sysctl.d/99-vmninja-memory.conf altın şablonu:
# 1. OOM Killer tetiklemeden minimum swap davranışı
vm.swappiness = 1
# 2. Düğüm (NUMA Node) dolduğunda yerel belleği zorla silmek yerine diğer NUMA düğümünden bellek almasına izin ver
vm.zone_reclaim_mode = 0
# 3. Otomatik NUMA sayfa taşımasının (CPU yükü ve anlık gecikme yaratır) önüne geçmek için (Özellikle KVM ve DB'lerde):
kernel.numa_balancing = 0
# 4. Dizin ve dosya (inode/dentry) meta verilerini RAM'de daha uzun süre tut (Çok dosyası olan DB ve Storage sunucuları için):
vm.vfs_cache_pressure = 50
# 5. OpenSearch / Elasticsearch mmap limitini artır (Varsayılan: 65530 -> Önerilen: 262144)
vm.max_map_count = 262144
# 6. Çekirdeğin ani ağ ve disk kesmeleri (interrupt) için her zaman kenarda tutacağı acil durum RAM miktarı (1 GB):
vm.min_free_kbytes = 1048576
MySQL / Percona İçin Ekstra İpucu: Büyük bellekli çift soketli sunucularda MySQL’in belleği sadece Node 0’dan doldurmaya başlamasını önlemek için mysqld servisini numactl --interleave=all parametresiyle başlatın veya my.cnf içinde innodb_numa_interleave = ON ayarını aktif edin. Böylece bellek sayfaları her iki sokete eşit (round-robin) dağıtılır.