Ajuste de swappiness y page cache en Linux para servidores de bases de datos
Introducción
En el ecosistema de servidores de bases de datos, el rendimiento predecible y la baja latencia son requisitos no negociables. A diferencia de un servidor web o de archivos, una base de datos (ya sea MySQL, PostgreSQL o MariaDB) depende críticamente de que sus estructuras de datos más utilizadas residan en memoria RAM. Linux, por defecto, aplica una política de gestión de memoria genérica que no distingue entre un proceso mysqld y un proceso apache2. Esta política, controlada por los parámetros vm.swappiness y la gestión del page cache, puede ser contraproducente para cargas de trabajo de bases de datos.
En este artículo, exploraremos en profundidad cómo ajustar estos dos mecanismos para transformar un servidor Linux genérico en una plataforma optimizada para bases de datos, minimizando la E/S de disco innecesaria y maximizando la caché del motor de base de datos.
Comprendiendo el Problema: Swap vs. Page Cache
Para tomar decisiones informadas, debemos entender qué ocurre bajo el capó.
El Dilema del Kernel: ¿Qué liberar?
La memoria RAM se divide en dos grandes categorías en uso por el kernel:
- Memoria de procesos (Anonymous Pages): Páginas de memoria asociadas a datos de aplicaciones (heap, stack). Estas páginas no tienen un respaldo directo en un archivo del sistema de archivos. Si se mueven a swap, se almacenan en la partición/archivo de swap.
- Page Cache (File-backed Pages): Páginas de memoria que contienen datos leídos o escritos desde archivos en disco (bloques de tablas de bases de datos, logs, binarios). Liberar esta memoria es más eficiente, ya que si se necesita de nuevo, se puede re-leer del disco.
El kernel utiliza un demonio kswapd que se activa cuando la memoria libre es escasa. Su trabajo es liberar páginas. Aquí está el núcleo del problema: ¿qué tipo de páginas debe liberar primero? El parámetro vm.swappiness controla esta decisión.
vm.swappiness: La Balanza de la Liberación
El valor de vm.swappiness (rango 0-100) le indica al kernel la tendencia a intercambiar páginas anónimas (swap) en lugar de desalojar páginas del page cache.
- Valores altos (cercanos a 100): El kernel preferirá mover páginas anónimas a swap para liberar RAM, manteniendo un page cache grande. Esto es útil en estaciones de trabajo o servidores de archivos donde la recarga de un archivo desde disco es costosa y la latencia de swap es aceptable.
- Valores bajos (cercanos a 0): El kernel preferirá desalojar el page cache antes que mover páginas anónimas a swap. Esto es crítico para bases de datos, donde las páginas anónimas corresponden a la memoria del proceso
mysqld(buffers, conexiones, etc.) y moverlas a swap causaría una degradación masiva del rendimiento.
Nota crítica: Un valor de
swappiness=0no deshabilita el swap. Solo indica que el kernel no usará swap como primera opción hasta que la presión de memoria sea extrema. En kernels modernos (5.x+),swappiness=0puede no ser suficiente y se recomiendaswappiness=1para evitar un comportamiento agresivo de OOM Killer.
Page Cache: El Amigo y el Enemigo
El page cache es una bendición para la mayoría de las cargas de trabajo. Acelera la lectura de archivos. Sin embargo, para una base de datos, el page cache puede ser un "ladrón de RAM". La base de datos ya tiene su propio caché interno extremadamente eficiente (el buffer pool de InnoDB, el shared buffers de PostgreSQL) que sabe qué páginas de datos son las más calientes. El page cache del kernel, al ser genérico, puede terminar cacheando datos que la base de datos ya tiene en su propio buffer, o peor aún, puede ocupar memoria que la base de datos podría usar para expandir su propio caché.
El objetivo no es eliminar el page cache, sino gestionarlo de forma inteligente para que no compita agresivamente con el proceso de la base de datos.
Ajuste Fino de vm.swappiness para Bases de Datos
El ajuste de swappiness es el primer paso y el más simple, pero con un impacto inmediato.
Paso 1: Verificar el Valor Actual
# Consultar el valor actual de swappiness
cat /proc/sys/vm/swappiness
# También se puede ver con sysctl
sysctl vm.swappiness
Paso 2: Aplicar el Cambio en Caliente (Temporal)
Para un servidor de base de datos dedicado, el valor recomendado es 1. En sistemas muy antiguos (kernel < 3.x) o con RAM extremadamente limitada, se podría usar 10, pero 1 es el estándar moderno.
# Establecer swappiness a 1 (efecto inmediato, no persistente)
sudo sysctl -w vm.swappiness=1
# Verificar el cambio
sysctl vm.swappiness
Paso 3: Hacer el Cambio Persistente
Para que el cambio sobreviva a un reinicio, debemos agregarlo al archivo de configuración de sysctl.
# Editar el archivo de configuración (o crear un archivo dedicado)
echo 'vm.swappiness = 1' | sudo tee -a /etc/sysctl.d/99-database-tuning.conf
# Aplicar la configuración desde el archivo
sudo sysctl --system
# Verificar que el valor se ha cargado correctamente
sysctl vm.swappiness
Nota importante sobre
swappiness=0: En kernels modernos (5.8+), el comportamiento deswappiness=0cambió para evitar el OOM (Out Of Memory) en sistemas con mucha presión de memoria. Ahora, conswappiness=0, el kernel puede desalojar page cache de forma más agresiva, pero sigue sin usar swap hasta el último momento. Para bases de datos,swappiness=1es la opción más segura y recomendada por la mayoría de los expertos.
¿Por qué swappiness=1 y no 0?
La razón es sutil pero crucial. Con swappiness=0, el kernel puede entrar en un estado en el que se niega a usar swap incluso cuando está bajo una presión de memoria extrema, lo que puede llevar a que el OOM Killer mate un proceso (¡tu base de datos!). Con swappiness=1, el kernel sigue prefiriendo fuertemente el page cache para liberar, pero si la presión es muy alta, usará el swap como una válvula de escape controlada, evitando un OOM Killer catastrófico. Es una red de seguridad.
Gestión Avanzada del Page Cache
Una vez que el swap está controlado, el siguiente paso es gestionar cómo el kernel escribe los datos sucios del page cache de vuelta al disco. Esto se controla con los parámetros dirty_ratio y dirty_background_ratio.
Los Parámetros Dirty: Control de la Escritura Diferida
Linux no escribe inmediatamente cada cambio en disco. Acumula escrituras en el page cache (páginas "sucias") y las vacía periódicamente. Para una base de datos que ya tiene su propio transaction log y doublewrite buffer, estas escrituras diferidas del kernel pueden ser contraproducentes. Una acumulación excesiva de páginas sucias puede provocar:
- Picos de latencia de E/S: Cuando el kernel decide vaciar una gran cantidad de páginas sucias de golpe.
- Pérdida de datos en caso de caída: Aunque la base de datos usa fsync para garantizar la durabilidad, el page cache puede almacenar datos que la base de datos considera escritos, pero que aún no han llegado al disco.
dirty_ratio y dirty_background_ratio
vm.dirty_background_ratio(por defecto 10): Porcentaje de memoria total que puede estar ocupada por páginas sucias antes de que un demonio en segundo plano (pdflush/flusher) comience a escribirlas en disco. Es una operación asíncrona y no bloqueante para el proceso.vm.dirty_ratio(por defecto 20): Porcentaje de memoria total que puede estar ocupada por páginas sucias antes de que un proceso que genera escrituras (ej. mysqld) se bloquee forzosamente hasta que se hayan vaciado suficientes páginas al disco. Esto es síncrono y causa latencia.
Ajuste para Bases de Datos
Para un servidor de base de datos, queremos que las escrituras sucias se vacíen al disco de forma temprana y suave, evitando bloqueos repentinos. La base de datos ya gestiona sus propios flushes (ej. innodb_flush_log_at_trx_commit = 1). Por lo tanto, debemos reducir estos ratios.
Configuración recomendada para servidores de bases de datos dedicados (con mucha RAM):
# Reducir el umbral para que el kernel empiece a vaciar pronto
vm.dirty_background_ratio = 5
# Reducir drásticamente el umbral de bloqueo para evitar picos de latencia
vm.dirty_ratio = 10
Nota: En sistemas con mucha RAM (por ejemplo, 256 GB), incluso un 5% son 12.8 GB de páginas sucias. Para bases de datos con cargas de escritura intensivas (OLTP), se pueden usar valores absolutos (
dirty_background_bytesydirty_bytes) para un control más preciso. Sin embargo, los ratios son más portables y recomendados para la mayoría de los casos.
Configuración con Bytes (Alternativa para Alta Precisión)
Si prefieres usar valores absolutos, puedes hacerlo. Por ejemplo, para limitar a 2 GB de páginas sucias antes del vaciado en segundo plano:
# Establecer límites en bytes (descomentar los de ratio primero)
# vm.dirty_background_bytes = 2147483648 # 2 GB
# vm.dirty_ratio = 0 (Deshabilitar el ratio)
# vm.dirty_bytes = 4294967296 # 4 GB (bloqueo)
Importante: No puedes mezclar _ratio y _bytes para el mismo parámetro. Si usas _bytes, debes poner el otro a 0.
Paso a Paso: Aplicar la Configuración
-
Añadir al archivo de sysctl dedicado:
cat >> /etc/sysctl.d/99-database-tuning.conf << EOF # Tuning de page cache para bases de datos vm.dirty_background_ratio = 5 vm.dirty_ratio = 10 EOF -
Aplicar los cambios:
sudo sysctl --system -
Verificar los nuevos valores:
sysctl vm.dirty_background_ratio vm.dirty_ratio
El Parámetro vm.vfs_cache_pressure
Este parámetro controla la tendencia del kernel a reclamar memoria utilizada para los metadatos del sistema de archivos (dentries e inodes). Un valor alto (ej. 200) hace que el kernel sea más agresivo al reclamar estos metadatos. Un valor bajo (ej. 50) hace que el kernel los mantenga en caché por más tiempo.
Para bases de datos que realizan muchas operaciones de apertura/cierre de archivos (tablas, índices), mantener los metadatos en caché es beneficioso para el rendimiento.
# Reducir la presión sobre la caché de metadatos (menos reclamación)
vm.vfs_cache_pressure = 50
Monitoreo del Impacto de los Cambios
Ajustar estos parámetros sin monitoreo es ciego. Debemos verificar que los cambios están teniendo el efecto deseado.
Monitoreo de Swap
# Ver el uso de swap
free -h
# Ver la actividad de swap en tiempo real
vmstat 1 10
# Ver el uso de swap por proceso (ordenado por uso)
for file in /proc/*/status ; do awk '/VmSwap|Name/{printf $2 " " $3}END{ print ""}' $file; done | sort -k 2 -n -r | head -20
Monitoreo de Page Cache y Dirty Pages
# Ver el tamaño del page cache y las páginas sucias
cat /proc/meminfo | grep -E "^(Dirty|Writeback|NFS_Unstable|PageTables|Cached|SwapCached)"
# Monitoreo continuo de páginas sucias
watch -n 1 'grep -E "^(Dirty|Writeback|NFS_Unstable)" /proc/meminfo'
Monitoreo de Latencia de Disco (El indicador más importante)
# Usar iostat para ver los tiempos de espera de E/S
iostat -x 1 10
# Prestar atención a las columnas:
# %util: Porcentaje de tiempo que el dispositivo estuvo ocupado (alto no siempre es malo, pero si es constante >90% y hay latencia, hay problema).
# await: Tiempo promedio de respuesta de las solicitudes de E/S (debe ser lo más bajo posible, idealmente < 5ms para SSD, < 1ms para NVMe).
# svctm: Tiempo de servicio promedio (despreciado en discos modernos).
Estrategias Avanzadas: numa_balancing y zone_reclaim_mode
En servidores con múltiples nodos NUMA (Non-Uniform Memory Access), el comportamiento de la memoria puede ser aún más complejo.
Deshabilitar NUMA Balancing
Para cargas de trabajo de bases de datos, el NUMA balancing del kernel puede mover páginas de memoria entre nodos para "equilibrar" la carga, lo que introduce latencia. Es mejor fijar el proceso de la base de datos a un nodo NUMA específico.
# Deshabilitar NUMA balancing
echo 'kernel.numa_balancing = 0' | sudo tee -a /etc/sysctl.d/99-database-tuning.conf
sudo sysctl --system
Zone Reclaim Mode
Si tienes un desequilibrio de memoria entre nodos NUMA, el kernel puede empezar a reclamar memoria del nodo local antes de usar memoria de un nodo remoto. Esto puede ser bueno o malo. Para bases de datos, a menudo es mejor permitir el acceso a memoria remota para evitar un reclamación agresiva.
# Permitir que las páginas se alojen en nodos NUMA remotos si el local está lleno
# (1 = preferir local, 0 = permitir remoto)
echo 'vm.zone_reclaim_mode = 0' | sudo tee -a /etc/sysctl.d/99-database-tuning.conf
Ejemplo de Configuración Completa para un Servidor MySQL/MariaDB
A continuación, un archivo /etc/sysctl.d/99-database-tuning.conf completo para un servidor de base de datos dedicado con 64 GB de RAM y discos SSD NVMe.
# ============================================
# Tuning de Kernel para Servidor de Base de Datos
# ============================================
# --- Gestión de Swap ---
# Minimizar el intercambio. Preferir liberar page cache.
# 1 es más seguro que 0 en kernels modernos (evita OOM killer).
vm.swappiness = 1
# --- Gestión de Page Cache (Escrituras Diferidas) ---
# Reducir la cantidad de páginas sucias antes de empezar a vaciar en segundo plano.
# Evita que se acumulen grandes cantidades de escrituras.
vm.dirty_background_ratio = 5
# Reducir el umbral de bloqueo forzoso. Evita picos de latencia.
vm.dirty_ratio = 10
# --- Caché de Metadatos (VFS) ---
# Mantener los metadatos del sistema de archivos (inodos, dentries) en caché.
# Beneficioso para bases de datos que abren/cierran muchos archivos.
vm.vfs_cache_pressure = 50
# --- Gestión de Memoria (NUMA) ---
# Deshabilitar el balanceo automático de páginas NUMA.
# La base de datos debe fijarse manualmente a un nodo.
kernel.numa_balancing = 0
# Permitir que el kernel use memoria de nodos NUMA remotos
# antes de reclamar agresivamente memoria local.
vm.zone_reclaim_mode = 0
# --- Ajustes de Red (Opcional pero recomendado) ---
# Aumentar el backlog de conexiones para manejar picos de conexiones.
net.core.somaxconn = 65535
# Reducir el tiempo de espera de las conexiones TCP.
net.ipv4.tcp
