MySQL vs MariaDB: innodb_buffer_pool_size — el error de tuning por copiar y pegar que mata el rendimiento

¿Te fue útil?

No hay nada que arruine un turno de on-call tranquilo como “la base de datos está lenta” sin contexto adicional, seguido de un gráfico que parece un sismógrafo. Revisas la CPU: bien. Revisas las consultas: mayormente normales. Entonces notas que el host está haciendo swap, la E/S está saturada y la latencia de la base de datos acaba de aprender a viajar en el tiempo.

Una cantidad deprimente de esto viene de una línea bienintencionada y copiada: innodb_buffer_pool_size. Mismo nombre de parámetro en MySQL y MariaDB. Comportamiento por defecto similar. Ecosistema distinto, realidades de contabilización de memoria distintas y modos de fallo diferentes cuando te pasas. Es una perilla de tuning que puede hacerte parecer un genio —o como alguien que “optimizó” producción hasta convertirla en un cráter.

Qué es realmente el buffer pool (y qué no es)

El buffer pool de InnoDB es la caché primaria para datos e índices de InnoDB. Cuando todo está sano, es de donde provienen las lecturas y donde reposan las páginas sucias antes de ser volcadas al disco. Cuando las cosas están mal, es donde tu presupuesto de memoria va a morir.

Qué almacena en caché

  • Páginas de datos (filas de tabla almacenadas en páginas)
  • Páginas de índices (nodos B-tree)
  • Entradas del insert buffer / change buffer (dependiendo de la carga)
  • Estructuras del adaptive hash index (si están habilitadas, depende de la carga)

Lo que no cubre

Aquí es donde empieza el error de tuning por copiar y pegar. La gente fija el buffer pool al “80% de la RAM” y se declara vencedora, olvidando que InnoDB no es lo único en memoria.

  • Memoria por conexión (sort buffers, join buffers, tablas temporales) puede superar tus expectativas bajo concurrencia.
  • Binary log cache, buffers de replicación y pilas de hilos existen aunque no los pienses.
  • InnoDB log buffer y estructuras internas de memoria no son “gratis”.
  • La caché de páginas del SO importa, incluso con InnoDB. Si la dejas sin recursos, puedes penalizar la E/S de metadatos, la E/S de binlog y cualquier cosa que no se sirva desde el buffer pool.
  • Otros demonios (agentes de backup, monitoring, sidecars) no dejan de consumir memoria porque escribiste un archivo de configuración confiado.

Si quieres un modelo mental: innodb_buffer_pool_size es el cubo predecible más grande, no la fiesta de piscina completa.

Broma #1: Copiar configuraciones de bases de datos es como tomar prestadas las gafas recetadas de otra persona: definitivamente verás algo, pero no necesariamente lo que necesitas.

MySQL vs MariaDB: por qué copiar y pegar duele

A nivel de parámetros, MySQL y MariaDB suelen parecer intercambiables. A nivel operativo, son hermanos que crecieron en casas distintas y ahora tienen hábitos diferentes.

Mismo control, distintos valores por defecto alrededor

En ambos productos, el buffer pool es el consumidor dominante de memoria de InnoDB. Pero el resultado de rendimiento de “ponerlo alto” depende de:

  • Comportamientos específicos de versión (especialmente en torno al flushing, hilos de limpieza de páginas y cómo se exponen las métricas)
  • Valores por defecto del empaquetado de la distribución (systemd limits, cgroup memory ceilings, tmpdir locations)
  • Otras características del motor (MariaDB y MySQL se han separado en varios subsistemas; incluso cuando InnoDB es “InnoDB-ish”, los puntos de integración difieren)
  • Diferencias de observabilidad (lo que puedes medir fácilmente cambia lo que puedes ajustar con seguridad)

El modo de fallo por copiar y pegar

El error clásico va así:

  1. Tienes un servidor MySQL donde el buffer pool está ajustado al 75–80% de la RAM. La vida está más o menos bien.
  2. Despliegas MariaDB (o viceversa) en hosts con “la misma RAM”.
  3. Reutilizas el mismo fragmento de my.cnf con el mismo tamaño de buffer pool.
  4. El nuevo entorno tiene requisitos de margen distintos: más conexiones, comportamiento distinto de tablas temporales, necesidades distintas de caché de sistema de archivos, límites de kernel diferentes, límites de cgroup distintos o simplemente una carga distinta.
  5. Entra la presión de memoria. El kernel hace swap. La latencia explota. Tu subsistema de E/S se convierte en una lámpara de calor.

El parámetro no cambió. La física sí.

Por qué sobredimensionar mata el rendimiento de una manera muy específica

Cuando fijas innodb_buffer_pool_size demasiado grande, no obtienes “un poco peor”. Obtienes “otro sistema”. El host empieza a reclamar memoria agresivamente, potencialmente intercambiando memoria de la base de datos que asumías caliente. Ahora lecturas que eran aciertos en memoria se convierten en fallos mayores. Las escrituras se atascan detrás de la E/S. El comportamiento de flushing de InnoDB puede volverse errático porque el SO se convierte en el programador de tu dolor.

Incluso sin swap, privar a la caché de páginas del SO puede perjudicar: binlogs, relay logs, metadatos de tablas y journaling del sistema de archivos compiten por las sobras. Tu base de datos empieza a comportarse como si estuviera en un disco en red de 2009.

Datos interesantes y contexto histórico (breve, útil, no trivia)

  1. InnoDB no siempre fue el predeterminado. Históricamente MySQL tenía MyISAM por defecto; la expectativa moderna de que “todo es InnoDB” es relativamente reciente en la cultura de producción.
  2. El buffer pool era más sencillo de razonar antes. Las versiones antiguas tenían menos piezas móviles (y menos superficies de observabilidad), así que “sólo hazlo grande” parecía más seguro de lo que realmente era.
  3. MariaDB se bifurcó de MySQL tras los cambios de Sun/Oracle. Las ramas preservaron muchos nombres de variables, lo cual es genial para familiaridad y terrible para la sobreconfianza al copiar y pegar.
  4. Se introdujeron instancias del buffer pool para reducir la contención. Dividir el buffer pool en instancias redujo la contención de mutex en sistemas multi-core, pero también creó otra dimensión de tuning que la gente adivina.
  5. El comportamiento de swapping en Linux cambió operativamente con el tiempo. Los valores por defecto del kernel y el uso de cgroup en despliegues containerizados hicieron que “memoria” fuera una promesa menos estable que “RAM instalada”.
  6. Performance Schema cambió cómo diagnosticamos memoria y esperas. Mejor instrumentación creó mejor tuning—si realmente la habilitas y la consultas.
  7. La adopción de SSD cambió la curva de penalización. En discos giratorios, un fallo de caché era lento; en SSD es “menos lento”, lo que tentó a equipos a infrainvertir en caché correcta hasta que la concurrencia los corrigió.
  8. Los tipos de instancia en cloud reconfiguraron expectativas. Las proporciones RAM-to-IO, créditos burst y almacenamiento conectado en red hicieron que el mismo tamaño de buffer pool se comporte distinto según el entorno.
  9. Transparent Huge Pages (THP) se convirtió en un reincidente. No es una configuración de base de datos, pero interactúa con los patrones de asignación de memoria de formas que se manifiestan como paradas y picos de latencia.

Guía rápida de diagnóstico

Esto es lo que haces cuando tu equipo de aplicación está gritando y tienes 15 minutos para encontrar el cuello de botella. El objetivo no es un tuning perfecto. El objetivo es identificar si innodb_buffer_pool_size está involucrado y si estás muriendo por presión de memoria, saturación de E/S o contención de bloqueos.

Primero: confirma que el host no te está mintiendo sobre memoria

  1. Revisa la actividad de swap y los fallos mayores. Si el swapping está activo, deja de tratar el tuning de consultas como el primer paso.
  2. Revisa límites de cgroup. En contenedores o slices de systemd, “free -h” puede parecer bien mientras la base de datos está encerrada.
  3. Revisa logs del OOM killer. Si el kernel está matando procesos, tu tamaño de buffer pool es ahora escena del crimen.

Segundo: determina si las lecturas son aciertos de caché o E/S de disco

  1. Tasa de aciertos del buffer pool y lecturas. Busca un aumento de lecturas físicas y una baja relación de aciertos bajo carga estable.
  2. Espera de E/S y saturación del dispositivo. Si los discos están al máximo, tu “lentitud de base de datos” podría ser “el almacenamiento es el cuello de botella”.

Tercero: verifica si las escrituras se están atascando por flushing

  1. Porcentaje de páginas sucias. Si es alto y no disminuye, estás limitado por flushing.
  2. Edad del checkpoint / presión en el log. Si la presión en el redo log es alta, las escrituras se atascarán independientemente del tamaño del buffer pool.

Árbol de decisión (rápido y directo)

  • ¿Swap o OOM? Reduce uso de memoria primero. Normalmente eso significa bajar el buffer pool, controlar la memoria por conexión o ambas cosas.
  • No hay swap, pero E/S saturada y baja tasa de aciertos? El buffer pool puede ser demasiado pequeño o la carga no cabe. Aumenta con cuidado si tienes margen.
  • E/S saturada con alta tasa de aciertos? Probablemente presión de escritura, configuración de flushing, fsync, binlog o latencia de almacenamiento. No “soluciones” inflando el buffer pool.

Tareas prácticas: comandos, salidas y decisiones (12+)

Estos son comandos reales que puedes ejecutar en un host Linux y en un servidor MySQL/MariaDB. Cada tarea incluye (1) el comando, (2) qué significa la salida típica y (3) la decisión que tomas.

Task 1: Confirmar qué servidor estás ejecutando realmente

cr0x@server:~$ mysql --version
mysql  Ver 15.1 Distrib 10.11.6-MariaDB, for Linux (x86_64) using readline 5.1

Significado: Esto es MariaDB 10.11.x. Si esperabas Oracle MySQL 8.0, tu runbook acaba de convertirse en fanfiction.

Decisión: Usa instrumentación específica de MariaDB y ten cuidado al asumir comportamientos o valores por defecto de MySQL 8.0.

Task 2: Verificar el tamaño actual del buffer pool y knobs relacionados

cr0x@server:~$ mysql -NBe "SHOW VARIABLES WHERE Variable_name IN ('innodb_buffer_pool_size','innodb_buffer_pool_instances','innodb_page_size','innodb_flush_method','innodb_flush_neighbors')"
innodb_buffer_pool_size	25769803776
innodb_buffer_pool_instances	8
innodb_page_size	16384
innodb_flush_method	O_DIRECT
innodb_flush_neighbors	0

Significado: Buffer pool de 24 GiB, 8 instancias, páginas de 16 KiB, IO directo habilitado. O_DIRECT reduce el doble almacenado en caché pero aumenta tu dependencia de que el buffer pool esté correctamente dimensionado.

Decisión: Compara este tamaño contra los límites reales de memoria y el footprint total de MySQL. No asumas que “24 GiB está bien” porque la máquina tiene “32 GiB instalados”.

Task 3: Revisar memoria a nivel OS (y si swap se está usando)

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:            31Gi        29Gi       512Mi       1.2Gi       1.6Gi       1.1Gi
Swap:          4.0Gi       2.6Gi       1.4Gi

Significado: Swap está activo y es significativo. La memoria “available” es baja. El kernel está bajo presión.

Decisión: Deja de afinar consultas primero. Planea reducir el uso de memoria de MySQL (buffer pool y/o buffers por conexión), añadir RAM o bajar la concurrencia.

Task 4: Verificar que el swapping es actual, no histórico

cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 3  1 2700000 480000  24000 820000  12   45  1800  2400  900 1400 18  6 48 28  0
 4  2 2702000 470000  24000 810000  20   60  2000  2600 1100 1600 20  7 41 32  0
 2  1 2705000 460000  24000 800000  10   30  1700  2100  980 1500 17  5 52 26  0

Significado: Valores no cero en si/so indican swap-in y swap-out ocurriendo ahora. CPU wa (IO wait) es alta.

Decisión: Trata esto como una emergencia: reduce la presión de memoria rápido o espera latencia en cascada.

Task 5: Confirmar las restricciones de memoria del servicio (systemd)

cr0x@server:~$ systemctl show mariadb -p MemoryMax -p MemoryHigh -p TasksMax
MemoryMax=0
MemoryHigh=0
TasksMax=3096

Significado: No hay un tope explícito de memoria en systemd aquí. Si estás containerizado, esto no te dirá toda la historia.

Decisión: Si esto está en Kubernetes/Docker, revisa límites de cgroup a continuación. Si es bare metal/VM, procede a la memoria del proceso MySQL.

Task 6: Comprobar el límite de memoria del cgroup (contenedores o slices)

cr0x@server:~$ cat /sys/fs/cgroup/memory.max
21474836480

Significado: El grupo de procesos está limitado a 20 GiB. Si tu buffer pool es de 24 GiB, has diseñado un evento OOM con papeleo.

Decisión: Reduce innodb_buffer_pool_size por debajo del límite de cgroup dejando margen para conexiones y sobrecarga del SO, o aumenta el límite.

Task 7: Encontrar el RSS real del proceso MySQL/MariaDB y comportamiento de mapeo

cr0x@server:~$ ps -o pid,cmd,rss,vsz --sort=-rss -C mysqld | head -n 5
 1423 /usr/sbin/mariadbd 28765432 32298124

Significado: RSS ~27.4 GiB, VSZ ~30.8 GiB. El proceso ya está cerca de la RAM. Esto no incluye toda la memoria del kernel ni la caché de páginas.

Decisión: Si existe swapping/presión, reduce buffer pool y/o otros buffers; considera límites de conexión.

Task 8: Comprobar estado de MySQL: uso del buffer pool, lecturas y páginas sucias

cr0x@server:~$ mysql -NBe "SHOW GLOBAL STATUS WHERE Variable_name IN ('Innodb_buffer_pool_pages_total','Innodb_buffer_pool_pages_free','Innodb_buffer_pool_read_requests','Innodb_buffer_pool_reads','Innodb_buffer_pool_pages_dirty','Innodb_buffer_pool_wait_free')"
Innodb_buffer_pool_pages_total	1572864
Innodb_buffer_pool_pages_free	1024
Innodb_buffer_pool_read_requests	9842331123
Innodb_buffer_pool_reads	83422119
Innodb_buffer_pool_pages_dirty	210432
Innodb_buffer_pool_wait_free	9821

Significado: El buffer pool está básicamente lleno (solo 1024 páginas libres). Las lecturas físicas son sustanciales. wait_free indica hilos esperando páginas libres — señal clásica de presión y/o flushing pobre.

Decisión: Si la memoria del host está justa, puedes estar sobredimensionado y haciendo swap. Si la memoria del host está bien, aumenta el buffer pool (con cautela) o arregla la E/S/flushing. No adivines: correlaciona con métricas del SO.

Task 9: Calcular la tasa de aciertos del buffer pool (aproximación rápida)

cr0x@server:~$ mysql -NBe "SELECT ROUND((1- (SELECT VARIABLE_VALUE FROM information_schema.GLOBAL_STATUS WHERE VARIABLE_NAME='Innodb_buffer_pool_reads')/(SELECT VARIABLE_VALUE FROM information_schema.GLOBAL_STATUS WHERE VARIABLE_NAME='Innodb_buffer_pool_read_requests'))*100,2) AS hit_ratio_percent;"
99.15

Significado: Tasa de aciertos ~99.15%. Suena bien, pero aún puede ser insuficiente a gran escala. 0.85% de misses en miles de millones de peticiones son muchas lecturas de disco.

Decisión: Si la E/S está saturada y los misses correlacionan con la carga, el buffer pool podría ser demasiado pequeño. Si la E/S está saturada pero los misses no suben, mira escrituras/binlogs/fsync/flush.

Task 10: Verificar porcentaje de páginas sucias y presión de flushing

cr0x@server:~$ mysql -NBe "SELECT ROUND(100*(SELECT VARIABLE_VALUE FROM information_schema.GLOBAL_STATUS WHERE VARIABLE_NAME='Innodb_buffer_pool_pages_dirty')/(SELECT VARIABLE_VALUE FROM information_schema.GLOBAL_STATUS WHERE VARIABLE_NAME='Innodb_buffer_pool_pages_total'),2) AS dirty_pct;"
13.38

Significado: Páginas sucias ~13%. No es una locura por sí sola. Pero si sube y se mantiene alta con stalls, vas retrasado en flushing.

Decisión: Si la latencia de escritura sube y el porcentaje sucio se mantiene elevado, examina la capacidad de E/S, ajustes de flush y dimensionamiento/presión del redo log. No lo “soluciones” con un buffer pool más grande salvo que tengas margen de E/S.

Task 11: Comprobar contador actual de conexiones y max_connections

cr0x@server:~$ mysql -NBe "SHOW GLOBAL STATUS LIKE 'Threads_connected'; SHOW VARIABLES LIKE 'max_connections';"
Threads_connected	482
max_connections	2000

Significado: 482 conexiones vivas. Con un max_connections permisivo, puedes llegar a la muerte por memoria por miles de pilas de hilos y buffers por sesión.

Decisión: Si hay presión de memoria, limita conexiones, usa pooling y audita buffers por conexión. Dimensionar el buffer pool sin disciplina de conexiones es deseo, no ingeniería.

Task 12: Inspeccionar valores por conexión que suelen explotar

cr0x@server:~$ mysql -NBe "SHOW VARIABLES WHERE Variable_name IN ('sort_buffer_size','join_buffer_size','read_buffer_size','read_rnd_buffer_size','tmp_table_size','max_heap_table_size','thread_stack')"
sort_buffer_size	262144
join_buffer_size	262144
read_buffer_size	131072
read_rnd_buffer_size	262144
tmp_table_size	16777216
max_heap_table_size	16777216
thread_stack	299008

Significado: Los valores por defecto son modestos aquí, pero “modesto × cientos de conexiones” suma, y las tablas temporales pueden desbordarse a disco o consumir memoria según patrones de consulta.

Decisión: Si ves presión de memoria con alta concurrencia, no sólo reduzcas el buffer pool: también arregla el pooling de conexiones y las consultas que crean tablas temporales grandes.

Task 13: Comprobar si las tablas temporales están yendo a disco

cr0x@server:~$ mysql -NBe "SHOW GLOBAL STATUS LIKE 'Created_tmp%tables';"
Created_tmp_disk_tables	1284921
Created_tmp_tables	4422103

Significado: Una fracción significativa de tablas temporales están en disco. Eso añade E/S extra, a menudo mal diagnosticada como “buffer pool demasiado pequeño”.

Decisión: Revisa consultas e índices; considera aumentar límites de tmp tables sólo si has probado margen de memoria y la carga se beneficia.

Task 14: Identificar saturación de E/S y latencia en el volumen de la base de datos

cr0x@server:~$ iostat -x 1 3
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          18.22    0.00    6.11   31.45    0.00   44.22

Device            r/s     w/s   rkB/s   wkB/s  await  aqu-sz  %util
nvme0n1         620.0  1140.0 49600.0 88400.0   9.80    3.10  96.00

Significado: Dispositivo ~96% utilizado, await ~10ms. Si esto es NVMe, 10ms es sospechosamente alto y suele indicar encolamiento y contención.

Decisión: Si la E/S está cerca de saturación, un buffer pool mayor podría ayudar lecturas si tienes margen de RAM, pero no arreglará stalls de escritura causados por fsync/binlog/flush. Confirma qué tipo de E/S domina.

Task 15: Comprobar los mayores consumidores de E/S (¿es mysqld o algo más?)

cr0x@server:~$ sudo iotop -o -b -n 3
Total DISK READ: 45.21 M/s | Total DISK WRITE: 86.73 M/s
  PID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO>    COMMAND
 1423 be/4 mysql     42.10 M/s   80.22 M/s   12.00 %  92.00 % mariadbd
 2211 be/4 root       0.10 M/s    6.20 M/s    0.00 %   4.00 % backup-agent

Significado: MariaDB es el mayor consumidor de E/S y está haciendo swap-in (SWAPIN%). Esa es la combinación de pesadilla: bound por E/S y thrashing por swap.

Decisión: Mitigación inmediata: reduce footprint de memoria y detén el swap; considera bajar temporalmente la concurrencia o descargar carga. Luego afina.

Task 16: Revisar el estado del motor InnoDB para la historia detrás de las métricas

cr0x@server:~$ mysql -NBe "SHOW ENGINE INNODB STATUS\G" | sed -n '1,120p'
*************************** 1. row ***************************
  Type: InnoDB
  Name: 
Status: 
=====================================
2025-12-29 10:12:19 0x7f3c9c2fe700 INNODB MONITOR OUTPUT
=====================================
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 27487790694
Dictionary memory allocated 612345678
Buffer pool size   1572864
Free buffers       1024
Database pages     1559000
Old database pages 574000
Modified db pages  210432
Pending reads 0
Pending writes: LRU 0, flush list 48, single page 0
Pages made young 112334455, not young 99887766
...

Significado: Puedes ver writes pendientes y páginas modificadas. Writes pendientes en la lista de flush indican presión de flushing; dictionary memory indica huella adicional más allá del buffer pool.

Decisión: Si los writes pendientes permanecen no nulos y crecen, investiga la capacidad de E/S y la configuración de flushing; si dictionary memory es enorme, considera inflación de esquema/índices y presión de caching de metadatos.

Tres micro-historias corporativas desde el frente

Micro-historia 1: El incidente causado por una suposición errónea

Migraron un servicio orientado al cliente de MySQL a MariaDB porque procurement gustó la historia de licencias y los ingenieros gustaron de la idea de “compatibilidad drop-in”. El archivo de configuración vino con el viaje, incluyendo un orgulloso innodb_buffer_pool_size fijado a un porcentaje grueso de la RAM.

El despliegue se vio bien en staging. Claro que sí—staging tenía menor concurrencia y menos jobs en segundo plano. En producción, los nuevos nodos MariaDB vivían dentro de un runtime de contenedores con un límite de memoria del que nadie del equipo de bases de datos había sido informado. El host tenía mucha RAM. El contenedor no.

Dos horas después del corte, la curva de latencia empezó a subir en una línea suave y horrorosa. Luego se rompió en picos. Al principio culparon las consultas. Luego el balanceador de carga. Luego “la nube”. Eventualmente alguien ejecutó un único comando en el lugar correcto y vio el límite del cgroup.

El buffer pool era mayor que el límite de memoria. El kernel hizo lo que hacen los kernels: OOM kills o swap-thrashing hasta que el servicio parece poseído. Redujeron el buffer pool, limitaron conexiones y el sistema se recuperó—sin cambiar una sola consulta.

La suposición equivocada no fue sobre MySQL vs MariaDB. Fue creer que “RAM en la máquina” equivale a “RAM disponible para mysqld”. En 2025, eso no es una suposición. Es un bug.

Micro-historia 2: La optimización que rebotó

Un equipo diferente tenía una carga intensiva en escrituras y vio lecturas de disco subir. Alguien propuso la solución clásica: hacer el buffer pool enorme para mantener más páginas calientes. Lo aumentaron agresivamente durante una ventana de mantenimiento, vieron mejorar la tasa de aciertos y celebraron.

Luego el lag de réplicas empezó. No inmediatamente. Unos días después, durante tráfico pico, la replicación se quedó atrás y nunca recuperó. El primario “estaba bien”, pero las réplicas se estaban ahogando. El tráfico de lectura de la aplicación se enrutaba a réplicas, así que los usuarios aún sintieron el dolor.

La causa raíz no era mística. Con el buffer pool inflado, la caché de páginas del SO se apretó. Los binary logs y relay logs dejaron de estar cacheados eficazmente. El hilo de I/O y el hilo SQL de la réplica se convirtieron en un desfile constante de misses de caché y esperas de fsync. Mientras tanto, el buffer pool más grande también aumentó la cantidad de datos sucios que podían acumularse, elevando el costo de ponerse al día durante ráfagas.

Reducieron el buffer pool ligeramente, se aseguraron de que la E/S de redo/binlog tuviera margen y usaron métricas para confirmar el intercambio. La “optimización” no estaba equivocada en principio—estaba equivocada en contexto. El rendimiento es un presupuesto, no un deseo.

Micro-historia 3: La práctica aburrida pero correcta que salvó el día

Esta es poco glamurosa, por eso funcionó. Una empresa con flotas mixtas MySQL y MariaDB tenía la práctica estándar: cada host de base de datos reportaba (1) límites efectivos de memoria, (2) mysqld RSS, (3) actividad de swap y (4) métricas del buffer pool en el mismo dashboard. No como “agradable de tener”. Como puerta de despliegue.

Cuando un equipo pidió un buffer pool mayor “porque las lecturas son lentas”, SRE requirió una hoja de cálculo de capacidad: RSS actual, conteo de conexiones esperado, topes de buffers por conexión y un perfil de E/S medido. Sin hoja, no hay cambio.

Un viernes, un nuevo servicio se lanzó y accidentalmente abrió muchas más conexiones de las esperadas. La base de datos no murió. Se volvió más lenta, pero se mantuvo arriba. ¿Por qué? Porque habían dejado margen intencional y habían puesto topes sensatos de conexión. El buffer pool no estaba fijado en algún porcentaje machista; estaba fijado según lo que el sistema podía permitirse.

Arreglaron la tormenta de conexiones, el rendimiento volvió y nadie tuvo que hacer el ritual de las 2 a.m. de “apágalo y enciéndelo” en una base de datos que se estaba paginando hasta morir.

Cómo dimensionar innodb_buffer_pool_size de forma segura

Aquí va la guía opinada: deja de usar una regla de porcentaje única en todas las flotas. Usa un método. Si quieres una regla empírica, vale—gánatela midiendo.

Paso 1: Conoce tu modelo de despliegue

  • Bare metal / VM: Normalmente tienes RAM predecible, pero aún compartes con la caché de páginas del SO y servicios de fondo.
  • Contenedor / limitado por cgroup: El límite es la verdad. Todo lo demás es storytelling.

Paso 2: Presupuesta la memoria explícitamente (no digas “otros” de forma vaga)

Un presupuesto práctico de producción se ve así:

  • Buffer pool: el trozo grande
  • Memoria por conexión: estimación en peor caso, o imponer límites para que el peor caso no ocurra
  • Sobrecarga de InnoDB: diccionario de datos, estructuras internas, adaptive hash (si se usa), purge, etc.
  • SO y caché de sistema de archivos: especialmente importante para binlogs/relay logs y metadatos
  • Margen operativo: backups, cambios de esquema, picos de carga y el hecho de que tu yo futuro hará algo riesgoso

Paso 3: Trata el swap como señal de que perdiste

En el mundo de bases de datos, el swap rara vez es “aceptable”. Es un precipicio de rendimiento. Si ves swap activo en un host de base de datos, no es “usar todos los recursos”. Es ahogarse.

Broma #2: Si tu base de datos está haciendo swap, no está “multitarea”. Sólo intenta recordar dónde dejó las llaves.

Paso 4: Usa instancias de buffer pool correctamente

Las instancias del buffer pool ayudan a reducir la contención, pero hay un rango sensato:

  • Muy pocas instancias en pools grandes pueden aumentar contención.
  • Demasiadas instancias pueden aumentar la sobrecarga y reducir la eficiencia. También vuelves algunas métricas más ruidosas.

Elige un número razonable según tu tamaño y CPU, luego mide. No importes ciegamente “8” porque lo leíste en un blog de la era de los SSD SATA.

Paso 5: No asfixies subsistemas de E/S al asfixiar la caché del SO

Especialmente en entornos con mucha replicación, la E/S de binlog y relay log puede decidir tu destino. Incluso si las lecturas de InnoDB se sirven desde el buffer pool, tu realidad operativa involucra logs, metadatos y otra E/S de archivos. Si le das todo al buffer pool, fuerzas al resto del sistema a ir al disco en cada paso. Entonces culpas a InnoDB. Clásico.

Una cita sobre fiabilidad (idea parafraseada)

Idea parafraseada: la fiabilidad viene de diseñar pensando en el fallo, no de asumir que no ocurrirá. — John Allspaw

Errores comunes: síntoma → causa raíz → solución

Esta sección debería sentirse incómodamente familiar.

1) Síntoma: picos de latencia súbitos tras “tuning de memoria”

Causa raíz: Buffer pool fijado demasiado alto, el host empieza a hacer swap o a reclamar memoria agresivamente.

Solución: Reduce innodb_buffer_pool_size para restaurar margen; limita conexiones; confirma que swap está deshabilitado o no usado; verifica límites de cgroup.

2) Síntoma: alta utilización de E/S, pero la tasa de aciertos del buffer pool parece “buena”

Causa raíz: La E/S está dominada por escrituras (redo, flushing, binlogs) o tablas temporales, no por lecturas de InnoDB.

Solución: Inspecciona páginas sucias, flushes pendientes, comportamiento de fsync de binlog, uso de tablas temporales; ajusta flushing y almacenamiento; no aumentes el buffer pool a ciegas.

3) Síntoma: las réplicas se retrasan más después de aumentar el buffer pool

Causa raíz: La caché de páginas del SO se comprimió; I/O de relay log/binlog deja de estar cacheado; las réplicas quedan ligadas por latencia de E/S.

Solución: Reduce el buffer pool ligeramente; asegúrate de que la E/S de logs tenga margen de caché; considera separar logs en dispositivos distintos donde sea apropiado.

4) Síntoma: el rendimiento empeora sólo durante picos de conexiones

Causa raíz: Picos de memoria por conexión; demasiados hilos; tablas temporales generan presión de E/S y memoria.

Solución: Usa pooling de conexiones; baja max_connections; revisa tamaños de buffers por sesión; arregla consultas que vierten a disco.

5) Síntoma: el OOM killer elimina mysqld después de un reinicio

Causa raíz: El buffer pool y otras asignaciones ocurren rápidamente al arrancar; combinado con límites de cgroup o poco margen, disparas OOM.

Solución: Dimensiona correctamente el buffer pool al límite real; asegúrate de que el sistema tenga margen durante el warm-up; considera reinicios escalonados y preflight checks.

6) Síntoma: “Aumentamos el buffer pool pero no mejoró nada”

Causa raíz: El working set no cabe de todos modos, o el cuello de botella está en otro lado (locks, CPU, latencia de almacenamiento, red, replicación).

Solución: Mide el volumen de misses del buffer pool, no sólo la ratio; identifica los principales eventos de espera; analiza latencia de E/S; ajusta consultas e índices según cuellos reales.

Listas de verificación / plan paso a paso

Checklist A: comprobaciones de seguridad previas al cambio (haz esto antes de tocar el buffer pool)

  1. Confirma el sabor y versión del servidor (mysql --version).
  2. Confirma límites de memoria (systemd y cgroups).
  3. Registra métricas actuales del buffer pool: tamaño, páginas libres, lecturas, wait_free, páginas sucias.
  4. Registra métricas del SO: uso de swap, fallos mayores, utilización de E/S, latencia de E/S.
  5. Registra métricas de conexiones: Threads_connected, max_connections y picos.
  6. Revisa uso de tablas temporales y creación de tablas temporales en disco.

Checklist B: plan de tuning paso a paso que no arruinará tu fin de semana

  1. Decide el objetivo. ¿Intentas reducir E/S de lectura? ¿Reducir picos de latencia? ¿Detener swap? Son problemas distintos.
  2. Fija un objetivo de margen. Elige un número concreto (por ejemplo, mantener varios GiB libres/available y mantener swap inactivo).
  3. Ajusta el buffer pool en incrementos pequeños. Saltos grandes ocultan causalidad.
  4. Observa las métricas correctas por al menos un ciclo de carga. No cinco minutos. Un ciclo.
  5. Si aparece swap, revierte inmediatamente. No “esperes y veas”. El swap no se cura solo durante carga pico.
  6. Tras la estabilización, reevalúa patrones de consulta. Si estás vertiendo tablas temporales o haciendo escaneos completos, el buffer pool no es tu única palanca.

Checklist C: mitigación de emergencia cuando ya estás haciendo swap

  1. Reduce la carga entrante (rate limiting, shedding de lecturas, deshabilitar jobs no críticos).
  2. Disminuye la concurrencia de conexiones rápidamente (límites en pool de la app, límites en proxy).
  3. Reduce el tamaño del buffer pool en la siguiente oportunidad segura (puede requerir reinicio según versión y configuración).
  4. Detén tareas de fondo hambrientas de memoria (consultas analíticas ad-hoc, backups si están thrashing E/S).
  5. Confirma que la actividad de swap se detiene (vmstat, iotop).

FAQ

1) ¿Puedo usar “80% de la RAM” para innodb_buffer_pool_size en MySQL y MariaDB?

Puedes, pero estás eligiendo superstición sobre ingeniería. Empieza con un tamaño conservador que deje margen real y luego ajusta según actividad de swap, métricas de E/S y comportamiento de la carga.

2) ¿Por qué aumentar innodb_buffer_pool_size empeoró el rendimiento?

Más a menudo: indujiste swapping o exprimiste tanto la caché de páginas del SO que la E/S no relacionada con InnoDB (logs, metadatos, archivos temporales) quedó dependiente del disco. Una caché más grande no es mejor si hace que el kernel expulse las cosas equivocadas.

3) ¿Es el swapping siempre malo para MySQL/MariaDB?

El swapping activo casi siempre es malo. Una pequeña cantidad de swap que nunca cambia puede ser residuo histórico; swap-in/swap-out continuo durante carga es una emergencia de rendimiento.

4) ¿Cómo sé si las lecturas son el problema o las escrituras?

Correlaciona misses del buffer pool (Innodb_buffer_pool_reads), utilización/await del dispositivo (iostat -x) y flushes pendientes/páginas sucias de InnoDB (SHOW ENGINE INNODB STATUS). Si la E/S está saturada pero los misses no suben, sospecha escrituras o tablas temporales.

5) ¿Usar O_DIRECT cambia cómo debo dimensionar el buffer pool?

Sí. Con O_DIRECT reduces el double-buffering y dependes más del cache de InnoDB. Eso puede mejorar la previsibilidad, pero también castiga el subdimensionamiento y hace más crítico planear el margen correctamente.

6) ¿Debo fijar innodb_buffer_pool_instances manualmente?

Sólo si tienes una razón y estás midiendo contención y throughput. Muy pocas instancias pueden crear un cuello de botella en mutex; demasiadas añaden sobrecarga. Elige un valor sensato y revísalo si ves síntomas de contención.

7) ¿Por qué mi tasa de aciertos del buffer pool es alta pero la latencia sigue siendo mala?

Porque la tasa de aciertos no es throughput, y oculta el número absoluto de misses. Además, la latencia puede venir de escrituras, comportamiento de fsync, esperas por locks, replicación, red o latencia de almacenamiento incluso cuando lecturas pegan en caché.

8) ¿Cuál es la manera más segura de cambiar innodb_buffer_pool_size?

Lo más seguro significa: ventana de cambio planeada, incrementos pequeños, plan de rollback y monitorización. Si es dinámico depende de la versión y comportamiento del motor; no asumas que el resize online será indoloro bajo carga.

9) ¿MariaDB es “peor” o “mejor” que MySQL para el comportamiento del buffer pool?

Ninguno de forma universal. El problema es asumir que son idénticos. Operativamente, diferencias en empaquetado, valores por defecto y contexto de carga importan más que la marca.

10) Si tengo mucha RAM, ¿debería simplemente maximizar el buffer pool?

No. Aún necesitas caché del SO, margen para conexiones y espacio para picos operativos (backups, cambios de esquema, failovers). Usa la RAM para comprar estabilidad, no para ganar una captura de benchmark.

Próximos pasos (qué hacer esta semana)

Haz estas cosas en orden. Son aburridas. Funcionan.

  1. Inventario de la realidad: En cada nodo de base de datos, registra versión MySQL/MariaDB, límites cgroup/systemd, RAM, ajustes de swap y tamaño actual del buffer pool.
  2. Construye un dashboard mínimo: actividad de swap, mysqld RSS, device await/%util, lecturas del buffer pool vs read requests, porcentaje de páginas sucias, Threads_connected.
  3. Elige una política de margen: Decide qué significa “seguro” (no swap activo; una memoria available mínima; tope de conexiones).
  4. Arregla la disciplina de conexiones: Si no puedes predecir la concurrencia, no puedes predecir la memoria. Usa pooling; baja max_connections a algo que puedas soportar.
  5. Ajusta el buffer pool midiendo: Aumenta o reduce según la E/S observada y el comportamiento de swap, no por porcentajes heredados.
  6. Escribe el runbook que desearías tener: Incluye la guía rápida de diagnóstico y los 12+ comandos arriba. Tu yo futuro estará cansado y no impresionado por orientación vaga.

Si solo aprendes una lección: innodb_buffer_pool_size no es un número “poner y olvidar”. Es un contrato entre InnoDB, el SO, tu carga de trabajo y tu disciplina operativa. Rompe el contrato y producción aplicará la cláusula penal.

← Anterior
rDNS/PTR ausente: la corrección aburrida de DNS que salva la entregabilidad del correo
Siguiente →
Proxmox: VMs sin Internet — errores comunes en vmbr0 y soluciones rápidas

Deja un comentario