Huge Pages

Huge Pages

Pe un sistem modern cu 16GB RAM și pagini de 4KB, kernelul gestionează peste 4 milioane de pagini. Fiecare pagină are o intrare în page table, iar procesorul menține un cache de traduceri recente în TLB. Când o aplicație accesează date răspândite pe mii de pagini - cum fac bazele de date mari - TLB-ul se umple rapid și procesorul pierde timp traducând adrese în loc să proceseze date. Huge Pages rezolvă această problemă oferind pagini de dimensiuni mult mai mari: 2MB sau chiar 1GB în loc de 4KB standard.

De ce contează dimensiunea paginii

TLB-ul (Translation Lookaside Buffer) este un cache hardware mic și extrem de rapid - de obicei între 64 și 1024 intrări pe arhitecturile moderne. Fiecare intrare acoperă o pagină de memorie. Cu pagini de 4KB, 1024 intrări în TLB acoperă doar 4MB de memorie. Cu pagini de 2MB, aceleași 1024 intrări acoperă 2GB.

Când un proces accesează o adresă care nu e în TLB, procesorul trebuie să parcurgă page table-ul în RAM - operație numită TLB miss sau page table walk - care costă zeci până la sute de cicluri de procesor. Pe o aplicație care face milioane de accese de memorie pe secundă, TLB miss-urile frecvente se adună și devin un bottleneck vizibil.

Bazele de date sunt cel mai bun exemplu: PostgreSQL, MariaDB, MySQL sau Oracle accesează constant zone mari de memorie pentru buffer pool, sort buffers și index scans. Trecerea la Huge Pages reduce dramatic numărul de TLB miss-uri și poate îmbunătăți performanța cu 10-30% în scenarii intensive.

Tipuri de Huge Pages pe Linux

Linux oferă două implementări distincte cu filozofii diferite.

Static Huge Pages

Huge Pages statice sunt rezervate la pornirea sistemului (sau manual) și rămân alocate permanent, indiferent dacă sunt folosite sau nu. Avantajul este performanța maximă - paginile sunt contigue în RAM și garantat disponibile. Dezavantajul este inflexibilitatea - memoria rezervată nu poate fi folosită pentru altceva, chiar dacă aplicația care le folosește nu rulează.

Dimensiunile disponibile depind de arhitectură: pe x86_64 sunt disponibile pagini de 2MB (cel mai comun) și 1GB (necesită suport explicit în kernel și CPU).

Transparent Huge Pages (THP)

Transparent Huge Pages este un mecanism automat prin care kernelul încearcă să folosească pagini mari fără intervenție manuală. Kernelul monitorizează alocările de memorie și, când detectează că un proces folosește zone contigue mari de memorie cu pagini de 4KB, le combină automat în pagini de 2MB.

Avantajul evident este că funcționează fără configurare și fără modificări în aplicații. Dezavantajul este că procesul de combinare (compaction) și desfacere (splitting) a paginilor provoacă uneori latențe imprevizibile - procesul se poate bloca câteva milisecunde în timp ce kernelul reorganizează memoria. Din acest motiv, unele aplicații (Redis, MongoDB, multe baze de date) recomandă explicit dezactivarea THP.

Verificarea stării Huge Pages

Verifici câte Huge Pages statice sunt configurate și câte sunt folosite:

cat /proc/meminfo | grep -i huge

Rezultatul arată ceva de genul:

AnonHugePages:    614400 kB
ShmemHugePages:        0 kB
HugePages_Total:     128
HugePages_Free:       96
HugePages_Rsvd:       32
HugePages_Surp:        0
Hugepagesize:       2048 kB
Hugetlb:          262144 kB

HugePages_Total - numărul total de pagini de 2MB rezervate, HugePages_Free - câte sunt disponibile, HugePages_Rsvd - câte sunt rezervate dar nu încă folosite efectiv, AnonHugePages - memoria folosită de THP.

Verifici starea THP:

cat /sys/kernel/mm/transparent_hugepage/enabled

Valoarea între paranteze drepte este cea activă: always, madvise sau never.

Configurarea Huge Pages statice

Calculul necesarului

Înainte de a configura Huge Pages pentru o bază de date, trebuie să știi câtă memorie alocă aplicația pentru structurile care beneficiază de pe urma lor. Pentru PostgreSQL, valoarea relevantă este shared_buffers. Pentru MariaDB/MySQL este innodb_buffer_pool_size.

Formula pentru numărul de pagini necesare:

număr_pagini = (dimensiune_în_bytes / 2097152) + rezervă_10%

Exemplu pentru PostgreSQL cu shared_buffers = 4GB:

4GB = 4294967296 bytes
4294967296 / 2097152 = 2048 pagini
+ 10% rezervă = ~2250 pagini

Rezervarea paginilor

Rezervi Huge Pages temporar (se pierde la repornire):

sysctl vm.nr_hugepages=2250

Permanent, în /etc/sysctl.conf sau /etc/sysctl.d/99-hugepages.conf:

vm.nr_hugepages=2250

Aplici fără repornire:

sysctl -p

Verifici că au fost alocate:

grep HugePages /proc/meminfo

Dacă HugePages_Free e mai mic decât ai cerut, kernelul nu a putut găsi suficientă memorie contiguă. În acest caz, fie reduci numărul, fie repornești sistemul și configurezi Huge Pages devreme în procesul de boot, înainte ca memoria să fie fragmentată.

Rezervarea la boot

Pentru a garanta alocarea paginilor contigue înainte de fragmentarea memoriei, adaugi parametrul direct în linia de boot a kernelului. În /etc/default/grub:

GRUB_CMDLINE_LINUX_DEFAULT="quiet splash hugepages=2250"

Actualizezi GRUB:

# Ubuntu/Debian
update-grub
 
# Fedora/RHEL
grub2-mkconfig -o /boot/grub2/grub.cfg
 
# Arch Linux
grub-mkconfig -o /boot/grub/grub.cfg

Hugepages de 1GB

Paginile de 1GB sunt disponibile doar dacă CPU-ul suportă extensia pdpe1gb (verifici cu grep pdpe1gb /proc/cpuinfo) și trebuie configurate exclusiv la boot - nu pot fi alocate dinamic după pornirea sistemului:

GRUB_CMDLINE_LINUX_DEFAULT="quiet splash hugepages=4 default_hugepagesz=1G hugepagesz=1G"

Paginile de 1GB sunt utile doar pentru aplicații cu structuri de memorie foarte mari (zeci de GB) unde chiar și paginile de 2MB generează prea multe intrări în page table.

Configurarea THP

Modurile disponibile

THP are trei moduri de funcționare:

always - kernelul încearcă agresiv să folosească pagini mari pentru orice alocare anonimă. Poate provoca latențe din cauza compaction-ului.

madvise - THP este folosit doar pentru zonele de memorie unde aplicația a cerut explicit prin apelul madvise(MADV_HUGEPAGE). Oferă control fin fără latențele din modul always.

never - THP dezactivat complet.

Schimbi modul temporar:

echo madvise > /sys/kernel/mm/transparent_hugepage/enabled

Permanent, prin parametru de kernel în /etc/default/grub:

GRUB_CMDLINE_LINUX_DEFAULT="quiet splash transparent_hugepage=madvise"

Dezactivarea THP pentru aplicații specifice

Unele aplicații - Redis, MongoDB, Cassandra - recomandă dezactivarea THP pentru că latențele de compaction interferează cu timpii de răspuns. Dacă nu vrei să dezactivezi THP global, poți dezactiva pentru un singur proces după pornire:

# Dezactivezi THP pentru un proces cu PID cunoscut
echo madvise > /proc/PID/numa_maps  # nu funcționează direct astfel

O abordare mai practică este să folosești systemd pentru a seta mediul procesului. Adaugi în fișierul de serviciu:

[Service]
ExecStartPost=/bin/sh -c 'echo madvise > /sys/kernel/mm/transparent_hugepage/enabled'

Sau mai elegant, prin numactl sau wrapper scripts specifice aplicației - Redis de exemplu are în documentație o secțiune dedicată acestui subiect.

Configurarea PostgreSQL pentru Huge Pages

PostgreSQL suportă Huge Pages nativ din versiunea 9.4. Configurezi în postgresql.conf:

huge_pages = on          # on, off, sau try
shared_buffers = 4GB

Cu huge_pages = try, PostgreSQL încearcă să folosească Huge Pages și continuă cu pagini normale dacă nu sunt disponibile - util în medii unde Huge Pages pot lipsi. Cu on, serverul refuză să pornească dacă nu găsește Huge Pages suficiente.

PostgreSQL are nevoie și de permisiuni pentru a accesa Huge Pages. Verifici și setezi limita pentru utilizatorul postgres:

# Verifici limita curentă
grep -i hugepages /etc/security/limits.conf
 
# Adaugi dacă lipsește
echo "postgres soft memlock unlimited" >> /etc/security/limits.conf
echo "postgres hard memlock unlimited" >> /etc/security/limits.conf

Configurarea MariaDB/MySQL pentru Huge Pages

MariaDB și MySQL suportă Huge Pages prin parametrul large-pages în fișierul de configurare:

[mysqld]
large-pages
innodb_buffer_pool_size = 4G

La fel ca PostgreSQL, procesul mysqld trebuie să aibă permisiuni memlock. Pe sisteme cu systemd, adaugi în fișierul de serviciu:

[Service]
LimitMEMLOCK=infinity

Monitorizarea utilizării Huge Pages

Urmărești utilizarea în timp real:

watch -n 2 'grep -E "HugePages|AnonHuge" /proc/meminfo'

Pentru a vedea care procese folosesc Huge Pages:

grep -l "AnonHugePages" /proc/[0-9]*/smaps | while read f; do
  pid=$(echo $f | cut -d/ -f3)
  size=$(grep AnonHugePages $f | awk '{sum+=$2} END {print sum}')
  name=$(cat /proc/$pid/comm 2>/dev/null)
  [ "$size" -gt "0" ] 2>/dev/null && echo "$size kB - $pid ($name)"
done | sort -rn

Statisticile THP sunt disponibile în:

cat /sys/kernel/mm/transparent_hugepage/khugepaged/pages_collapsed
cat /sys/kernel/mm/transparent_hugepage/khugepaged/full_scans

pages_collapsed arată câte pagini de 4KB au fost combinate în pagini mari - o valoare mare indică THP activ. full_scans arată de câte ori khugepaged (daemonul kernel care face compaction) a scanat memoria.

Când să folosești Huge Pages

Huge Pages aduc beneficii clare în scenarii specifice și pot fi contraproductive în altele.

Beneficii clare:

  • Baze de date cu buffer pool mare (PostgreSQL, MariaDB, Oracle)
  • Aplicații de calcul intensiv cu structuri de date mari (simulări, machine learning)
  • Servere cu mulți GB de RAM și câteva procese mari

Fără beneficii sau contraproductive:

  • Sisteme desktop cu multe aplicații mici
  • Servere cu zeci de procese mici
  • Aplicații cu acces aleatoriu la zone mici de memorie
  • Sisteme cu RAM limitat - Huge Pages rezervă memorie permanent

Pe un VPS mic cu 4GB RAM care rulează multe servicii, Huge Pages pot face mai mult rău decât bine prin blocarea memoriei. Pe un server dedicat cu 64GB RAM și PostgreSQL ca serviciu principal, pot aduce îmbunătățiri semnificative de performanță.

Resurse suplimentare