Linux gestiona la memoria en páginas de 4 KB por defecto. Una base de datos con 16 GB de shared_buffers necesita más de cuatro millones de esas páginas, y cada acceso a memoria depende de que su traducción esté en la TLB de la CPU, que es pequeña. Las huge pages de 2 MB reducen el número de entradas 512 veces, lo que baja los fallos de TLB y el tamaño de las tablas de páginas de cada proceso. En este tutorial reservarás huge pages en Ubuntu 24.04, configurarás PostgreSQL 16 (y opcionalmente MySQL 8.0) para usarlas y verificarás que la memoria compartida está realmente en huge pages.

Requisitos previos

Para seguir esta guía necesitas:

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS o un servidor dedicado de CubePath, con un usuario no root con privilegios sudo.
  • PostgreSQL 16 instalado desde los repositorios de Ubuntu (sudo apt install postgresql) o MySQL 8.0 (sudo apt install mysql-server).
  • Memoria suficiente: la ganancia se nota con buffers de varios GB. Con menos de 4 GB de RAM el beneficio es muy pequeño.

Paso 1: Revisar el estado actual de la memoria

Comprueba el tamaño de huge page por defecto y cuántas hay reservadas:

grep -i huge /proc/meminfo
AnonHugePages:         0 kB
ShmemHugePages:        0 kB
FileHugePages:         0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
Hugetlb:               0 kB

Hugepagesize: 2048 kB indica páginas de 2 MB, que es lo que usarás en esta guía. HugePages_Total: 0 significa que aún no hay ninguna reservada.

Mira también el modo de las Transparent Huge Pages (THP), que son un mecanismo distinto que el kernel aplica de forma automática:

cat /sys/kernel/mm/transparent_hugepage/enabled
always [madvise] never

Ubuntu usa madvise por defecto: el kernel solo usa THP en la memoria que la aplicación marca expresamente. Es el valor adecuado para un servidor de bases de datos, así que no lo cambies. Las huge pages que vas a configurar aquí son explícitas (hugetlbfs), se reservan por adelantado y no se ven afectadas por este ajuste.

Paso 2: Calcular cuántas huge pages necesita PostgreSQL

PostgreSQL coloca en huge pages su segmento principal de memoria compartida, cuyo tamaño depende sobre todo de shared_buffers. Un punto de partida habitual es el 25 % de la RAM. Por ejemplo, en un servidor con 16 GB:

sudo -u postgres psql -c "ALTER SYSTEM SET shared_buffers = '4GB';"

Reinicia PostgreSQL para que el nuevo tamaño se aplique:

sudo systemctl restart postgresql

Desde PostgreSQL 15 el propio servidor calcula cuántas huge pages necesita para su memoria compartida:

sudo -u postgres psql -c "SHOW shared_memory_size_in_huge_pages;"
 shared_memory_size_in_huge_pages
----------------------------------
 2138
(1 row)

Apunta este número. Reserva un pequeño margen por encima (un 2 o 3 %) para no tener que recalcular si cambias algún parámetro menor. En el ejemplo, 2200 páginas de 2 MB son unos 4,3 GB.

Paso 3: Reservar las huge pages en el kernel

Crea un archivo de sysctl propio para que la reserva se mantenga tras un reinicio:

sudo nano /etc/sysctl.d/60-hugepages.conf

Añade la línea con el número de páginas calculado:

vm.nr_hugepages = 2200

Aplica la configuración:

sudo sysctl -p /etc/sysctl.d/60-hugepages.conf
vm.nr_hugepages = 2200

Comprueba que el kernel ha podido reservarlas todas:

grep -E 'HugePages_(Total|Free)' /proc/meminfo
HugePages_Total:    2200
HugePages_Free:     2200

Si HugePages_Total es menor que el valor pedido, la memoria está fragmentada y el kernel no encuentra bloques contiguos de 2 MB. Consulta la sección de solución de problemas.

Paso 4: Obligar a PostgreSQL a usar huge pages

El valor por defecto de huge_pages es try: PostgreSQL intenta usarlas y, si no puede, arranca en silencio con páginas normales. Ponlo en on para que falle al arrancar en lugar de degradar el rendimiento sin avisar:

sudo -u postgres psql -c "ALTER SYSTEM SET huge_pages = 'on';"

Reinicia el servicio:

sudo systemctl restart postgresql

Comprueba que el clúster está en marcha:

sudo systemctl status postgresql@16-main --no-pager
● [email protected] - PostgreSQL Cluster 16-main
     Loaded: loaded (/usr/lib/systemd/system/[email protected]; enabled-runtime; preset: enabled)
     Active: active (running) since ...

Si el servicio no arranca, revisa el log. El error típico cuando faltan páginas es este:

sudo tail -n 20 /var/log/postgresql/postgresql-16-main.log
FATAL:  could not map anonymous shared memory: Cannot allocate memory
HINT:  This error usually means that PostgreSQL's request for a shared memory segment exceeded available memory, swap space, or huge pages.

En ese caso aumenta vm.nr_hugepages en /etc/sysctl.d/60-hugepages.conf, vuelve a aplicarlo y reinicia PostgreSQL.

Paso 5: Verificar que la memoria compartida está en huge pages

Con PostgreSQL arrancado, vuelve a mirar los contadores:

grep -E 'HugePages_(Total|Free|Rsvd)' /proc/meminfo
HugePages_Total:    2200
HugePages_Free:     2105
HugePages_Rsvd:     2043

Estos números confirman que funciona:

  • HugePages_Rsvd pasa de 0 a casi el valor calculado en el paso 2: PostgreSQL ha reservado sus páginas.
  • HugePages_Free baja a medida que los backends tocan shared_buffers. Tras un rato de carga real, Free menos Rsvd queda cerca del margen que dejaste.

Si HugePages_Rsvd sigue en 0, PostgreSQL no está usando huge pages: comprueba que SHOW huge_pages; devuelve on y que reiniciaste el servicio.

Paso 6 (opcional): Configurar MySQL 8.0 con huge pages

MySQL usa huge pages para el buffer pool de InnoDB a través de memoria compartida System V. Para eso el usuario mysql tiene que pertenecer al grupo autorizado por el kernel en vm.hugetlb_shm_group.

Calcula las páginas a partir de innodb_buffer_pool_size más un 10 % de margen. Con un buffer pool de 4 GB: 4096 MB / 2 MB = 2048 páginas, más el margen, unas 2250. Si PostgreSQL y MySQL comparten servidor, suma ambas cifras en vm.nr_hugepages.

Obtén el GID del usuario mysql:

id -g mysql
110

Edita el archivo del paso 3:

sudo nano /etc/sysctl.d/60-hugepages.conf

Deja el número de páginas y el grupo, usando el GID que obtuviste:

vm.nr_hugepages = 2250
vm.hugetlb_shm_group = 110

Aplica los cambios:

sudo sysctl -p /etc/sysctl.d/60-hugepages.conf

La documentación de MySQL recomienda además quitar el límite de memoria bloqueada del proceso. Crea un override de systemd:

sudo systemctl edit mysql

Añade estas líneas en la zona editable del archivo:

[Service]
LimitMEMLOCK=infinity

Activa las huge pages en la configuración de MySQL:

sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf

Añade estas líneas dentro de la sección [mysqld]:

innodb_buffer_pool_size = 4G
large_pages = ON

Reinicia MySQL y comprueba la variable:

sudo systemctl restart mysql
sudo mysql -e "SHOW VARIABLES LIKE 'large_pages';"
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| large_pages   | ON    |
+---------------+-------+

La variable solo refleja la configuración. Para confirmar que la asignación ha funcionado, revisa que el log de errores no contiene avisos de HugeTLB y que HugePages_Rsvd o HugePages_Free han cambiado en /proc/meminfo:

sudo grep -i hugetlb /var/log/mysql/error.log

Si no devuelve nada y los contadores se han movido, InnoDB está usando huge pages. Si ves Failed to allocate, faltan páginas o el GID de vm.hugetlb_shm_group no es el del usuario mysql.

Paso 7 (opcional): Usar huge pages de 1 GB

En servidores con mucha RAM (por ejemplo, shared_buffers de 64 GB o más), las páginas de 1 GB reducen todavía más la presión sobre la TLB. Primero comprueba que la CPU las admite:

grep -m1 -o pdpe1gb /proc/cpuinfo
pdpe1gb

Si no aparece nada, la CPU (o la CPU virtual que expone el hipervisor) no las soporta y debes quedarte con páginas de 2 MB. Las páginas de 1 GB se deben reservar al arrancar, cuando la memoria aún no está fragmentada. Edita GRUB:

sudo nano /etc/default/grub

Añade los parámetros a la línea existente, ajustando el número de páginas a tu caso:

GRUB_CMDLINE_LINUX_DEFAULT="hugepagesz=1G hugepages=16"

Si esa línea ya tenía otros valores (por ejemplo quiet splash), mantenlos y añade los nuevos separados por un espacio. Regenera la configuración de GRUB y reinicia:

sudo update-grub
sudo reboot

Tras el reinicio, comprueba la reserva en el directorio del tamaño de 1 GB:

cat /sys/kernel/mm/hugepages/hugepages-1048576kB/nr_hugepages
16

Por último, indica a PostgreSQL ese tamaño y reinícialo:

sudo -u postgres psql -c "ALTER SYSTEM SET huge_page_size = '1GB';"
sudo systemctl restart postgresql

Con páginas de 1 GB, elimina o reduce vm.nr_hugepages en /etc/sysctl.d/60-hugepages.conf para no reservar también páginas de 2 MB que nadie va a usar.

Solución de problemas

  • HugePages_Total se queda por debajo de lo pedido. La memoria está fragmentada. Pide al kernel que compacte la memoria con echo 1 | sudo tee /proc/sys/vm/compact_memory y vuelve a aplicar el sysctl. Si sigue sin llegar, reinicia: el archivo de /etc/sysctl.d/ se aplica al principio del arranque, cuando hay bloques libres de sobra.
  • El sistema se queda sin memoria tras reservar las páginas. Has reservado demasiadas. Las huge pages no se pueden usar para procesos normales ni para la caché de disco. Baja vm.nr_hugepages, deja al menos un 20-25 % de la RAM fuera de la reserva, porque PostgreSQL también depende de la caché de disco del sistema.
  • PostgreSQL arranca pero HugePages_Rsvd es 0. huge_pages sigue en try o el cambio no se aplicó. Ejecuta SHOW huge_pages; y reinicia el servicio (no basta con recargarlo).
  • Has cambiado shared_buffers. Repite el paso 2: el número de páginas necesario cambia con él.

Conclusión

Has reservado huge pages en Ubuntu 24.04, has calculado la cantidad exacta a partir de shared_memory_size_in_huge_pages y has configurado PostgreSQL para que no arranque sin ellas, con MySQL como alternativa. Para medir el efecto, ejecuta una carga representativa con pgbench antes y después del cambio, vigila HugePages_Free en tu sistema de monitorización (node_exporter publica node_memory_HugePages_Free) y revisa el cálculo cada vez que cambies el tamaño de los buffers.