Tu base de datos funcionaba bien en hardware físico. Luego la containerizaste, apuntaste el directorio de datos a “algo persistente” y de repente los gráficos de latencia parecen un sismógrafo.
Las escrituras se atascan. Los checkpoints se disparan. Alguien dice “usa volúmenes, los bind mounts son lentos”, y otra persona afirma lo contrario con la confianza de un hilo nocturno en Twitter.
Aquí está la verdad desde las trincheras de producción: “bind mount vs volumen” no es una afirmación de rendimiento. Es una ruta hacia un sistema de archivos, más unas cuantas capas de indirección que pueden —o no— importar.
Lo que importa es lo que hay detrás de esa ruta: el kernel, el sistema de archivos, la pila de almacenamiento, el runtime de contenedores y las decisiones de durabilidad de tu base de datos.
Las decisiones reales que estás tomando (no las que la gente discute)
Cuando alguien pregunta “¿bind mount o volumen para MariaDB/Postgres?”, en realidad está planteando un conjunto de preguntas que no se da cuenta que formula:
- ¿Estoy escribiendo en la capa escribible del contenedor (OverlayFS) o no? Ese es el mayor “no lo hagas” para bases de datos.
- ¿Cuál es el sistema de archivos subyacente real? ext4, XFS, ZFS, btrfs y “almacenamiento en bloque de red misterioso” se comportan de forma diferente bajo presión de fsync.
- ¿El almacenamiento es local o en red? NVMe local y un volumen CSI en red pueden ser ambos “un volumen”, y se comportarán como especies diferentes.
- ¿Cuáles son mis ajustes de durabilidad? PostgreSQL
fsync,synchronous_commit, ubicación del WAL; MariaDB InnoDBinnodb_flush_log_at_trx_commity doublewrite. No son perillas cosméticas. - ¿Cuál es mi amplificación de escritura? Tamaños de página, WAL/binlog, doublewrite, checkpoints, autovacuum/purge. Estás pagando por escrituras que no sabías que pediste.
- ¿Estoy limitado por IOPS, latencia, CPU o writeback de páginas sucias? Si no lo sabes, optimizarás lo equivocado con gran entusiasmo.
Para decirlo sin rodeos: un bind mount a un XFS local rápido en NVMe barrerá el suelo con un “volumen Docker” apoyado en almacenamiento en red congestionado.
A la inversa, un volumen gestionado por Docker respaldado por almacenamiento en bloque local puede ser tan rápido como un bind mount. La etiqueta no es el cuello de botella.
Broma #1: Ejecutar una base de datos en la capa escribible del contenedor es como guardar tu pasaporte en una bolsa de plástico: técnicamente posible, emocionalmente imprudente.
Hechos interesantes y contexto histórico (por qué estamos aquí)
Algunos hechos cortos y concretos explican por qué los debates sobre almacenamiento en contenedores se vuelven extraños:
- El WAL de PostgreSQL existe desde mediados de los 90 (cuando PostgreSQL evolucionó de POSTGRES), diseñado para seguridad ante fallos en discos imperfectos—mucho antes de que existieran “volúmenes en la nube”.
- InnoDB se convirtió en el motor por defecto de MySQL en 5.5 (era 2010), y MariaDB heredó esa línea; la historia de durabilidad de InnoDB depende mucho de logs de redo y del comportamiento de doublewrite.
- La cache de páginas de Linux es la maquinaria de rendimiento para ambas bases de datos; “I/O directo por todas partes” no es la estrategia por defecto y luchar contra la cache a menudo perjudica.
- Las semánticas de fsync varían según sistema de archivos y opciones de montaje; la misma configuración de BD puede ser segura en un entorno y “más o menos segura” en otro, lo cual es una forma educada de decir “pérdida de datos sorpresa”.
- OverlayFS se popularizó con el driver overlay2 de Docker porque es eficiente para el versionado de imágenes, no porque ame las cargas de trabajo de bases de datos que hacen fsync y escrituras aleatorias.
- Históricamente, los sistemas de archivos en red fueron una trampa común para bases de datos; los sistemas modernos son mejores, pero el triángulo “latencia + fsync + jitter” aún arruina fines de semana.
- Los volúmenes nombrados de Docker se popularizaron por portabilidad (“mueve la app sin preocuparte dónde vive los datos”), pero a las bases de datos les importa mucho dónde aterrizan físicamente los bytes.
- Las abstracciones de PV en Kubernetes facilitaron solicitar almacenamiento y lo hicieron más difícil de entender; “pedí 100Gi” no implica “recibí escrituras síncronas y baja latencia”.
MariaDB vs PostgreSQL: cómo sus patrones de E/S castigan el almacenamiento
PostgreSQL: primero WAL, luego archivos de datos, luego drama de checkpoints
PostgreSQL escribe cambios en el WAL (Write-Ahead Log) y más tarde vacía buffers sucios hacia los archivos de datos, con checkpoints que coordinan “hasta dónde es seguro reciclar el WAL”.
La ruta de fsync del WAL es tu latido de durabilidad. Si se vuelve lenta, tus commits se vuelven lentos. Y se vuelve lenta de una manera muy honesta: la latencia del almacenamiento aparece como latencia de transacción.
Puntos de dolor típicos en contenedores:
- WAL en almacenamiento lento (o peor: en la capa escribible del contenedor): los commits se vuelven más lentos.
- Picos de checkpoints: ves tormentas de escritura periódicas y acantilados de latencia.
- E/S de autovacuum: lecturas/escrituras aleatorias sostenidas, que revelan límites de IOPS rápidamente.
- fsync y barreras: si tu “volumen” miente sobre durabilidad, PostgreSQL no lo sabrá hasta que ocurra.
MariaDB (InnoDB): redo logs, doublewrite y flushing en background
El motor InnoDB de MariaDB se apoya en redo logs para hacer commits durables (dependiendo de innodb_flush_log_at_trx_commit) y usa un mecanismo de doublewrite para reducir la corrupción por escrituras parciales de página.
En la práctica, esto significa más amplificación de escritura a cambio de seguridad ante fallos.
Puntos de dolor típicos:
- Cadencia de fsync del redo log: con durabilidad estricta, la latencia del dispositivo de logs controla el rendimiento.
- Doublewrite + páginas de datos: puedes pagar por escribir la misma página dos veces (o más), lo que castiga medios lentos.
- Flushing en background: si tu almacenamiento no puede seguir el ritmo, InnoDB puede suspender trabajo en primer plano.
- Binary logs (si están activados): escrituras secuenciales extra que parecen “baratas” hasta que la política de fsync pega.
Qué significa esto para “bind mount vs volumen”
PostgreSQL suele ser más sensible a la latencia por commit (la ruta WAL fsync es visible de inmediato), mientras que InnoDB puede amortiguar y diluir el dolor hasta que no puede más y entonces se atasca con más dramatismo.
A ambos les desagrada la latencia impredecible. A ambos les molestan las limitaciones por picos. A ambos les disgusta que “tu fsync sea en realidad una sugerencia”.
Bind mounts vs volúmenes Docker: lo que realmente difiere
Dejemos de tratar estos como objetos místicos.
- Bind mount: la ruta del contenedor se mapea a una ruta existente del host. Tú controlas el directorio del host, el sistema de archivos, las opciones de montaje, las etiquetas SELinux y el ciclo de vida.
- Volumen nombrado de Docker: Docker gestiona un directorio (normalmente bajo
/var/lib/docker/volumes) y lo monta en el contenedor. El almacenamiento subyacente sigue siendo el sistema de archivos del host a menos que un driver de volumen lo cambie.
Las diferencias de rendimiento suelen ser indirectas:
- Valores por defecto operativos: los volúmenes tienden a ubicarse en el mismo disco que la raíz de Docker, que a menudo no es el disco que pensabas para las bases de datos.
- Opciones de montaje y elección del sistema de archivos: los bind mounts hacen más natural elegir sistemas de archivos y opciones “amigables con BD”.
- Sobrecarga de etiquetado de seguridad: en sistemas con SELinux, el relabel y el contexto pueden añadir sobrecarga o causar denegación; usualmente problemas de corrección primero, rendimiento después.
- Flujos de trabajo de backup/restore: los bind mounts se integran con herramientas del host; los volúmenes con herramientas de Docker. Ambos pueden estar bien; ambos pueden ser mal utilizados.
Si usas un sistema de archivos local simple en el host, tanto bind mounts como volúmenes evitan OverlayFS. Eso es clave. Los archivos de tu base de datos no deberían vivir en la capa escribible del overlay.
OverlayFS/overlay2: el villano con una coartada razonable
OverlayFS está diseñado para el versionado de imágenes. Es excelente en lo que fue construido: muchos archivos mayormente de solo lectura con cambios ocasionales por copy-on-write.
Las bases de datos son lo opuesto: reescriben los mismos archivos, los sincronizan, lo hacen por siempre y se quejan ruidosamente cuando añades latencia.
El modo de fallo se ve así:
- Pequeñas escrituras aleatorias se convierten en operaciones CoW más complejas.
- El churn de metadatos aumenta: atributos de archivos, entradas de directorio, copy-ups.
- El comportamiento de fsync se vuelve más costoso, especialmente bajo presión.
Si tu BD está sobre overlay2, estás midiendo el driver de almacenamiento de Docker, no MariaDB o PostgreSQL.
Ángulo Kubernetes: HostPath, PV locales, PV en red y por qué tu BD los odia de forma distinta
Kubernetes toma tu pregunta simple y la convierte en un bufé de abstracciones:
- HostPath: básicamente un bind mount hacia una ruta del nodo. Rápido y simple. También ata tu pod a un nodo y puede ser una trampa de disponibilidad.
- Persistent Volumes locales: como HostPath, pero con semánticas de scheduling y ciclo de vida. A menudo la mejor opción de “disco local” si aceptas afinidad de nodo.
- Volúmenes en bloque en red: iSCSI, NVMe-oF, dispositivos en bloque en la nube—a menudo buena latencia, aún con jitter y posibilidades de contención multi-tenant.
- Sistemas de archivos en red: semánticas tipo NFS o sistemas distribuídos. Pueden funcionar, pero la “pared de fsync” es real y aparece como latencia en los commits.
Para bases de datos con estado, no trates “PVC enlazado” como “problema resuelto”. Necesitas saber qué obtuviste: perfil de latencia, comportamiento de fsync, throughput con escritores concurrentes y cómo falla.
Cómo hacer benchmarking sin engañarte a ti mismo
Si quieres la verdad de rendimiento, prueba todo el recorrido: BD → libc → kernel → sistema de archivos → capa de bloque → dispositivo.
No hagas pruebas que eviten fsync si tu durabilidad de producción requiere fsync.
Además, no te limites a ejecutar una herramienta una vez y declarar victoria. Los sistemas en contenedores tienen vecinos ruidosos, throttling de CPU, tormentas de writeback y compactación en background.
Quieres capturar la varianza, no solo los promedios.
Una idea parafraseada de Werner Vogels: “Todo falla, todo el tiempo—diseña para poder operar a través de ello.” (idea parafraseada)
Guión rápido de diagnóstico
Cuando alguien dice “Postgres en Docker está lento” o “MariaDB en Kubernetes es errático”, haz esto en orden. No improvises.
-
Primero: confirma dónde viven realmente los datos.
Si están en overlay2, detente y corrígelo antes de benchmarkear cualquier otra cosa. -
Segundo: mide la latencia de fsync/ruta de commit.
PostgreSQL: revisa métricas de latencia WAL/commit; MariaDB: comportamiento de flush de redo log y stalls.
Si los commits son lentos, el almacenamiento es culpable hasta que se demuestre lo contrario. -
Tercero: identifica el tipo de limitación.
Escrituras aleatorias limitadas por IOPS vs escrituras secuenciales limitadas por throughput vs throttling de CPU vs presión de memoria que causa tormentas de writeback. -
Cuarto: revisa el sistema de archivos y las opciones de montaje.
ext4 vs XFS vs ZFS, barreras, discard, atime, modo de journaling y si estás sobre un archivo loopback. -
Quinto: busca asesinos de I/O en background.
Checkpoints, autovacuum, purge, compaction, trabajos de backup, envío de logs a nodo, antivirus/agentes de escaneo. -
Sexto: solo entonces compara bind mount vs volumen.
Si ambos apuntan al mismo sistema de archivos subyacente, la diferencia de rendimiento suele ser un error de redondeo—a menos que hayas introducido opciones distintas o dispositivos diferentes.
Tareas prácticas: comandos, salidas y qué decisión tomar
Estas son tareas reales de operaciones. Cada una incluye comandos, qué significa la salida y la decisión que tomas a continuación.
Ejecútalas en el host y dentro de contenedores según proceda.
Task 1: Probar si la BD está en overlay2 o en un montaje real
cr0x@server:~$ docker inspect -f '{{.GraphDriver.Name}} {{json .Mounts}}' pg01
overlay2 [{"Type":"volume","Name":"pgdata","Source":"/var/lib/docker/volumes/pgdata/_data","Destination":"/var/lib/postgresql/data","Driver":"local","Mode":"z","RW":true,"Propagation":""}]
Qué significa: GraphDriver es overlay2 para la capa del contenedor, pero el directorio de datos es un montaje de volumen hacia una ruta del host bajo el directorio de volúmenes de Docker. Eso es bueno: los datos de la BD eluden overlay2.
Decisión: Si ves que el datadir de la BD no aparece bajo .Mounts (o apunta al sistema de archivos del contenedor), estás en overlay2. Mueve el datadir a un bind mount o a un volumen inmediatamente.
Task 2: Encontrar el tipo real de sistema de archivos que respalda tu bind mount/volumen
cr0x@server:~$ df -T /var/lib/docker/volumes/pgdata/_data
Filesystem Type 1K-blocks Used Available Use% Mounted on
/dev/nvme0n1p2 xfs 468587520 122884164 345703356 27% /
Qué significa: Tus volúmenes Docker residen en XFS en /. Eso podría ser NVMe (bueno) o un disco raíz compartido (a menudo malo).
Decisión: Si este no es el disco que pretendías, mueve el almacenamiento de BD a un montaje dedicado (bind mount o un driver de volumen). Los discos raíz se vuelven ruidosos rápido.
Task 3: Comprobar si tu “volumen” es en realidad un archivo loopback (desastre silencioso)
cr0x@server:~$ mount | grep -E '/var/lib/docker|/var/lib/kubelet|loop'
/dev/loop0 on /var/lib/docker type ext4 (rw,relatime)
/dev/nvme0n1p2 on / type xfs (rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota)
Qué significa: Docker está en un dispositivo loop. Eso a menudo significa “un dispositivo de bloque respaldado por un archivo” (como devicemapper en un archivo, o algún almacenamiento anidado).
El loopback añade sobrecarga y puede convertir fsync en una broma de rendimiento.
Decisión: No ejecutes bases de datos serias sobre almacenamiento de Docker respaldado por loopback. Arregla primero el layout de almacenamiento del host.
Task 4: Confirmar opciones de montaje que afectan durabilidad y latencia
cr0x@server:~$ findmnt -no TARGET,FSTYPE,OPTIONS /var/lib/docker/volumes/pgdata/_data
/ xfs rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota
Qué significa: Puedes ver si tienes opciones sospechosas como nobarrier (peligro), ajustes raros de journaling o opciones de metadatos pesadas.
Decisión: Si encuentras opciones de montaje que reducen la seguridad para perseguir rendimiento, detente. Arregla el rendimiento mejorando el dispositivo y el layout, no quitando los cinturones de seguridad.
Task 5: Verificar ajustes de PostgreSQL que se traducen directamente en comportamiento de almacenamiento
cr0x@server:~$ docker exec -it pg01 psql -U postgres -c "SHOW data_directory; SHOW wal_level; SHOW synchronous_commit; SHOW full_page_writes; SHOW checkpoint_timeout;"
data_directory
-------------------------------
/var/lib/postgresql/data
(1 row)
wal_level
-----------
replica
(1 row)
synchronous_commit
-------------------
on
(1 row)
full_page_writes
------------------
on
(1 row)
checkpoint_timeout
--------------------
5min
(1 row)
Qué significa: Esta configuración prioriza la durabilidad. WAL es necesario para réplicas, los commits son síncronos y full-page writes están activados (seguridad frente a páginas partidas).
Decisión: Si el rendimiento es malo, no empieces por desactivar la durabilidad. Comienza moviendo el WAL a almacenamiento más rápido y arreglando la latencia.
Task 6: Revisar los ajustes de durabilidad de MariaDB/InnoDB que afectan fsync
cr0x@server:~$ docker exec -it mariadb01 mariadb -uroot -psecret -e "SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit'; SHOW VARIABLES LIKE 'sync_binlog'; SHOW VARIABLES LIKE 'innodb_doublewrite';"
+--------------------------------+-------+
| Variable_name | Value |
+--------------------------------+-------+
| innodb_flush_log_at_trx_commit | 1 |
+--------------------------------+-------+
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| sync_binlog | 1 |
+---------------+-------+
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| innodb_doublewrite| ON |
+-------------------+-------+
Qué significa: Estás pagando la factura de “durabilidad real”. Cada commit hace flush del redo log; el binlog se sincroniza; doublewrite está activado.
Decisión: Si necesitas esta durabilidad, debes comprar almacenamiento que pueda manejarla. Si no la necesitas (raro en producción), sé explícito sobre el riesgo y documéntalo.
Task 7: Observar latencia por dispositivo y profundidad de cola (vista del host)
cr0x@server:~$ iostat -x 1 3
Linux 6.5.0 (server) 12/31/2025 _x86_64_ (16 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
6.12 0.00 2.31 8.44 0.00 83.13
Device r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
nvme0n1 221.0 842.0 7824.0 29812.0 74.1 9.82 11.6 4.2 13.5 0.6 64.3
Qué significa: await ~11.6ms y avgqu-sz ~9.8 sugiere escrituras en cola. Para una BD ocupada, 10–15ms de latencia de escritura puede aparecer absolutamente como commits lentos.
Decisión: Si await es alto y %util es alto, estás limitado por el almacenamiento. Arregla el layout de almacenamiento o migra a medios más rápidos antes de tocar la optimización de consultas.
Task 8: Detectar tormentas de writeback del sistema de archivos (presión de memoria que se hace pasar por almacenamiento)
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
2 0 0 81232 65488 9231440 0 0 124 980 2840 6120 7 2 86 5 0
3 1 0 42112 65284 9150020 0 0 118 8450 3120 7450 6 3 63 28 0
4 2 0 23840 65110 9121000 0 0 102 16820 3401 8012 6 4 52 38 0
Qué significa: Aumento de b (bloqueados), aumento de bo (bloques salientes) y aumento de wa sugieren páginas sucias siendo forzadas a flush. Esto puede parecer “el almacenamiento es lento” pero el desencadenante es presión de memoria.
Decisión: Si la memoria es escasa, da más RAM al nodo, reduce cargas competidoras o ajusta la memoria de la BD para que el kernel no se vea obligado a flush masivo.
Task 9: Verificar throttling de CPU por cgroups (el falso cuello de botella de almacenamiento)
cr0x@server:~$ docker inspect -f '{{.HostConfig.NanoCpus}} {{.HostConfig.CpuQuota}} {{.HostConfig.CpuPeriod}}' pg01
0 200000 100000
Qué significa: La cuota de CPU es 2 cores (200000/100000). Si alcanzas ese techo, los workers en background (checkpoints, flushing, vacuum) se ralentizan y el almacenamiento parece “raro”.
Decisión: Si la BD está limitada por CPU, sube los límites antes de migrar almacenamiento. Una BD con CPU hambrienta no puede impulsar I/O eficientemente.
Task 10: Medir comportamiento de commits y checkpoints de PostgreSQL desde estadísticas
cr0x@server:~$ docker exec -it pg01 psql -U postgres -c "SELECT checkpoints_timed, checkpoints_req, buffers_checkpoint, buffers_backend, maxwritten_clean FROM pg_stat_bgwriter;"
checkpoints_timed | checkpoints_req | buffers_checkpoint | buffers_backend | maxwritten_clean
------------------+-----------------+-------------------+----------------+------------------
118 | 43 | 9823412 | 221034 | 1279
(1 row)
Qué significa: Un alto buffers_checkpoint indica escrituras impulsadas por checkpoints. Si los picos de latencia coinciden con checkpoints, el throughput de almacenamiento y el comportamiento de writeback están implicados.
Decisión: Considera repartir la I/O de checkpoints (ajusta pacing de checkpoints), pero solo después de confirmar que el almacenamiento no está fundamentalmente subdimensionado.
Task 11: Verificar la ubicación del WAL de PostgreSQL y considerar aislarlo
cr0x@server:~$ docker exec -it pg01 bash -lc "ls -ld /var/lib/postgresql/data/pg_wal && df -T /var/lib/postgresql/data/pg_wal"
drwx------ 1 postgres postgres 4096 Dec 31 09:41 /var/lib/postgresql/data/pg_wal
Filesystem Type 1K-blocks Used Available Use% Mounted on
/dev/nvme0n1p2 xfs 468587520 122884164 345703356 27% /
Qué significa: El WAL comparte el mismo sistema de archivos que los datos. Eso es común, pero no siempre ideal.
Decisión: Si la latencia de commit es tu dolor, monta el WAL en un dispositivo más rápido o menos contendido (volumen/ bind mount separado). El WAL es pequeño pero exigente.
Task 12: Confirmar ubicación del datadir de MariaDB y sistema de archivos
cr0x@server:~$ docker exec -it mariadb01 bash -lc "mariadb -uroot -psecret -e \"SHOW VARIABLES LIKE 'datadir';\" && df -T /var/lib/mysql"
+---------------+----------------+
| Variable_name | Value |
+---------------+----------------+
| datadir | /var/lib/mysql/|
+---------------+----------------+
Filesystem Type 1K-blocks Used Available Use% Mounted on
/dev/nvme0n1p2 xfs 468587520 122884164 345703356 27% /
Qué significa: Los datos de MariaDB están en XFS en la raíz. Bueno si la raíz es rápida y dedicada; malo si la raíz se comparte con todo lo demás.
Decisión: Si ves que esto cae en un disco raíz genérico, reubica a un montaje dedicado con características de rendimiento conocidas.
Task 13: Chequeo rápido y crudo de la realidad de fsync con fio (ruta host usada por bind/volume)
cr0x@server:~$ fio --name=fsync-test --directory=/var/lib/docker/volumes/pgdata/_data --rw=write --bs=4k --size=256m --ioengine=sync --fsync=1 --numjobs=1 --runtime=20 --time_based --group_reporting
fsync-test: (g=0): rw=write, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=sync, iodepth=1
...
write: IOPS=820, BW=3280KiB/s (3360kB/s)(64.0MiB/20003msec)
clat (usec): min=220, max=42000, avg=1216.55, stdev=3100.12
Qué significa: Esto aproxima “pequeñas escrituras con fsync”. Latencia promedio ~1.2ms pero máximo 42ms: esa latencia de cola es exactamente lo que se convierte en “commits aleatorios lentos”.
Decisión: Si las latencias máximas son feas, arregla la contención del almacenamiento o muévete a mejores medios. La afinación de BD no puede negociar con la física.
Task 14: Comprobar si tu base de datos está alcanzando límites silenciosos del sistema de archivos (inodos, espacio, bloques reservados)
cr0x@server:~$ df -h /var/lib/docker/volumes/pgdata/_data && df -i /var/lib/docker/volumes/pgdata/_data
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p2 447G 118G 330G 27% /
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p2 224M 3.2M 221M 2% /
Qué significa: Espacio e inodos están bien. Cuando estos se vuelven escasos, las BD se comportan de forma extraña: autovacuum falla, archivos temporales fallan, crash al instalar extensiones, y más.
Decisión: Si el espacio libre es bajo o el uso de inodos es alto, arregla eso antes del trabajo de rendimiento. Discos llenos hacen el rendimiento “interesante” de la peor manera.
Task 15: Confirmar que nadie está “ayudando” haciendo rsync del datadir en vivo
cr0x@server:~$ ps aux | grep -E 'rsync|tar|pg_basebackup|mariabackup' | grep -v grep
root 19344 8.2 0.1 54360 9820 ? Rs 09:44 0:21 rsync -aH --delete /var/lib/docker/volumes/pgdata/_data/ /mnt/backup/pgdata/
Qué significa: Alguien está haciendo rsync del directorio de datos en vivo. Eso es tanto un impuesto de rendimiento como (para PostgreSQL) no es un método de backup consistente a menos que se haga correctamente.
Decisión: Deja de hacer copias a nivel de sistema de archivos de una BD en vivo a menos que uses semánticas de snapshot apropiadas y métodos de backup coordinados por la BD.
Errores comunes: síntoma → causa raíz → solución
1) Síntoma: “La BD en contenedor es lenta, pero el host es rápido”
Causa raíz: Los datos de la BD están en overlay2/capa escribible del contenedor, no en un montaje.
Solución: Monta el datadir como bind mount o volumen nombrado en un sistema de archivos real del host. Confirma con docker inspect los mounts.
2) Síntoma: Picos aleatorios de commit de 200–2000ms
Causa raíz: Latencia de cola en el almacenamiento por vecinos ruidosos, jitter en PV de red o comportamiento de flush del cache del dispositivo. A menudo aparece como stalls de fsync.
Solución: Mueve WAL/redo logs a almacenamiento de menor latencia, aísla discos para la BD y mide distribuciones de latencia (no solo throughput promedio).
3) Síntoma: Cliffs de latencia periódicos en PostgreSQL cada pocos minutos
Causa raíz: Checkpoints que causan tormentas de escritura; buffers sucios se acumulan y luego se vacían en ráfagas.
Solución: Ajusta el pacing de checkpoints y asegúrate de que el almacenamiento pueda absorber escrituras sostenidas. Si el almacenamiento es marginal, la afinación es cosmética frente a un paginador.
4) Síntoma: MariaDB se detiene bajo carga, luego se recupera
Causa raíz: El flushing en background de InnoDB no puede mantenerse al día; el redo log o doublewrite amplifican escrituras; ocurren stalls eventualmente.
Solución: Mejora la latencia/IOPS del almacenamiento, valida ajustes de durabilidad y evita compartir el disco con servicios no relacionados que escriban mucho.
5) Síntoma: “Cambiar a volúmenes Docker lo hizo más lento”
Causa raíz: El volumen vive en el filesystem raíz con opciones de montaje diferentes, posiblemente en medios más lentos, o contendidos por pulls de imágenes y logs de Docker.
Solución: Coloca volúmenes en almacenamiento dedicado (bind mount a un punto de montaje dedicado, o configura data-root de Docker a un disco más rápido).
6) Síntoma: “Los bind mounts son lentos en mi laptop Mac/Windows”
Causa raíz: Docker Desktop comparte archivos cruzando una frontera de virtualización; las semánticas del sistema de archivos del host se emulan y son más lentas.
Solución: Para desarrollo local, prefiere volúmenes Docker dentro de la VM Linux. Para producción Linux, este problema específico desaparece.
7) Síntoma: Corrupción de BD tras crash del host, a pesar de “fsync activado”
Causa raíz: La pila de almacenamiento mintió sobre escrituras durables (cache de escritura sin protección contra pérdida de energía, opciones de montaje inseguras, controlador RAID mal configurado).
Solución: Usa almacenamiento de nivel empresarial con protección contra pérdida de energía; verifica políticas de cache; no desactives barreras; prueba el comportamiento de recuperación ante fallos.
8) Síntoma: Alto iowait pero bajo %util del disco
Causa raíz: A menudo latencia en almacenamiento en red, throttling por cgroups o locks a nivel de sistema de archivos. El disco no está “ocupado”, está “lejos”.
Solución: Mide latencia de extremo a extremo (stats del dispositivo de bloque, métricas del PV en red) y revisa throttling de CPU y presión de memoria.
Tres mini-historias del mundo corporativo
Mini-historia 1: El incidente causado por una suposición equivocada
Una empresa mediana migró un servicio de pagos de VMs a Kubernetes. El equipo hizo lo sensato en papel: cargas con estado obtuvieron PVCs, cargas sin estado obtuvieron emptyDir.
Asumieron que PVC significaba “disco real”. También asumieron que la clase de almacenamiento llamada “fast” significaba “baja latencia”.
El servicio usaba PostgreSQL con commits síncronos. Durante la ventana de migración, todo pasó pruebas funcionales. Luego, el lunes por la mañana, el tráfico golpeó y la latencia p99 de transacciones se disparó.
Los pods de BD no estaban limitados por CPU. No estaban limitados por memoria. Los nodos parecían sanos. El equipo de la app culpó a Postgres “en contenedores”.
La historia real: la clase de almacenamiento “fast” era una solución respaldada por un sistema de archivos en red que estaba bien para uploads web y logs, pero tenía una latencia de cola desagradable bajo cargas intensas de fsync.
El PVC hizo exactamente lo que prometía: almacenamiento persistente. No prometía latencia de commit predecible.
La solución fue aburrida y estructural: mover el WAL a una clase de bloques de menor latencia, mantener los archivos de datos en la clase existente (o moverlos también, según costo), y fijar los pods de BD a nodos con mejores rutas de red.
La mayor mejora vino de reconocer que “persistente” no es lo mismo que “grado de base de datos”.
Mini-historia 2: La optimización que salió mal
Otra organización ejecutaba MariaDB en Docker en una flota de hosts Linux. El rendimiento estaba bien, pero había stalls ocasionales de escritura.
Alguien notó que el subsistema de disco tenía cache de escritura y pensó: “Podemos hacer que fsync sea más barato.”
Cambiaron opciones de montaje y configuraciones del controlador para reducir barreras y comportamiento de flush. Los benchmarks mejoraron. Todos celebraron.
Dos semanas después, un host se reinició inesperadamente. MariaDB se recuperó, pero un puñado de tablas tenía corrupción sutil: no inmediatamente catastrófica, pero suficiente para producir resultados incorrectos.
El postmortem fue doloroso porque nada estaba “evidentemente mal” en las métricas. El fallo vivía en la brecha entre lo que la base de datos pedía (fsync significa durable) y lo que el almacenamiento entregaba (reconocimiento rápido, persistencia posterior).
El incidente no apareció en un benchmark de camino feliz. Apareció en la física más un evento de energía no planificado.
Revirtieron las opciones inseguras, implementaron backups adecuados con ejercicios de recuperación y mejoraron controladores de almacenamiento con protección contra pérdida de energía.
El rendimiento bajó ligeramente. El negocio lo aceptó. Datos incorrectos cuestan más que commits más lentos, y los contadores entienden ese lenguaje.
Mini-historia 3: La práctica aburrida pero correcta que salvó el día
Un tercer equipo tenía un hábito que parecía casi pintoresco: antes de cualquier cambio de almacenamiento, ejecutaban la misma pequeña suite de pruebas y registraban resultados.
No era sofisticado. Simplemente consistente. Unos perfiles fio, una carga de escritura específica de base de datos y un ensayo de snapshot/restore.
Estaban desplegando una nueva configuración del runtime de contenedores y moviendo el data-root de Docker a otro disco.
Alguien sugirió “son solo metadatos de Docker, los volúmenes no importan”. El equipo probó de todos modos.
Las pruebas mostraron una regresión en la latencia de cola de fsync tras el movimiento. No grandes promedios—solo outliers peores.
El culpable resultó ser la contención de disco: el nuevo disco también alojaba logs del sistema y un agente de monitorización que ocasionalmente generaba picos de escritura.
Como probaron antes del despliegue, lo detectaron temprano, movieron los volúmenes de BD a montajes dedicados y evitaron un incidente de lenta combustión.
No fue heroico. Fue supervisión adulta.
Broma #2: Lo único más persistente que un volumen de base de datos es un ingeniero insistiendo en que su último benchmark “definitivamente fue representativo”.
Listas de verificación / plan paso a paso
Paso a paso: elegir almacenamiento para Postgres o MariaDB en contenedores
- Decide qué necesitas: durabilidad estricta vs “podemos perder el último segundo”. Escríbelo. Haz que el negocio lo firme (o al menos quede registrado en un ticket).
- Coloca los datos de BD fuera de overlay2: usa bind mount o volumen nombrado, pero asegúrate de conocer e intencionar el sistema de archivos de respaldo.
- Elige el sistema de archivos de respaldo intencionalmente: ext4 o XFS son defaults comunes. ZFS puede ser excelente con cuidado, pero no lo trates como magia.
- Usa almacenamiento dedicado para BD: punto de montaje separado. Evita el disco raíz compartido. Evita dispositivos loopback.
- Aísla WAL/redo logs si la latencia de commit importa: dispositivo/volumen separado si es necesario. Suele ser la victoria más barata y significativa.
- Haz benchmarking del camino que vas a usar: incluye fsync. Captura latencia de cola. Ejecuta bajo concurrencia realista.
- Confirma flujos operativos: backups, restores, ensayos de recuperación y cómo migras/redimensionas volúmenes.
- Implementa monitorización ligada a modos de fallo: latencia de commits, duración de checkpoints, tiempos de fsync, disk await y presión en el nodo.
- Documenta puntos de montaje y propiedad: permisos, etiquetas SELinux y exactamente qué ruta del host se mapea a qué ruta del contenedor.
- Re-prueba después de cada cambio de plataforma: actualización de kernel, cambio de clase de almacenamiento, cambio de runtime o actualización de imagen del nodo.
Lista de verificación: decisión “bind mount vs volumen”
- Usa bind mount cuando quieras control explícito sobre la elección del disco, opciones de montaje y herramientas operativas en el host.
- Usa volumen nombrado cuando quieras ciclo de vida gestionado por Docker y confíes en dónde vive data-root de Docker (o uses un driver de volumen apropiado).
- Evita ambos si caen sobre almacenamiento en red con latencia impredecible y necesitas rendimiento estricto de commits; en su lugar elige una clase/dispositivo mejor.
Preguntas frecuentes
1) ¿Los volúmenes Docker son más rápidos que los bind mounts en Linux?
Normalmente son iguales si están en el mismo sistema de archivos y dispositivo subyacente. Las diferencias de rendimiento vienen de dónde aterrizan y qué los respalda, no del tipo de montaje.
2) ¿Alguna vez está bien ejecutar Postgres/MariaDB en overlay2?
Para desarrollo desechable y pruebas, sí. Para cualquier cosa que te importe, no. OverlayFS está optimizado para versionado de imágenes, no para durabilidad y patrones de escritura de bases de datos.
3) ¿Por qué PostgreSQL se siente más sensible a la latencia de almacenamiento que MariaDB?
La ruta de commit de PostgreSQL suele golpear fsync del WAL inmediatamente (con commits síncronos). Si la latencia del almacenamiento se dispara, tus commits se disparan. InnoDB puede amortiguar de otra forma y luego atascar más tarde.
4) Si desactivo fsync o activo async commit, ¿se “arregla” el rendimiento?
Será más rápido y menos correcto. Podrías comprar velocidad a costa de corrupción futura o transacciones perdidas tras un crash. Si eliges ese trade-off, documéntalo como si fuera una demolición controlada.
5) ¿Debo separar WAL y datos en diferentes volúmenes?
A menudo sí cuando la latencia de commit importa y tienes I/O mixto. El WAL es secuencial-ish y sensible a la latencia; las escrituras de datos pueden ser bruscas en checkpoints. La separación reduce contención.
6) En Kubernetes, ¿HostPath es buena idea para bases de datos?
Puede ser rápido y predecible porque es local. Pero ata tu pod a un nodo y complica failover. Los PV locales son la versión más estructurada de esta idea.
7) ¿Por qué los bind mounts fueron terribles en mi laptop?
Docker Desktop usa una VM. Los bind mounts cruzan la frontera host/VM y pueden ser lentos. Los volúmenes dentro de la VM suelen ser más rápidos para desarrollo.
8) ¿Cuál es la trampa de rendimiento de volúmenes más común en producción?
Asumir que un volumen persistente implica baja latencia y buen comportamiento de fsync. Persistencia trata sobre sobrevivir, no sobre velocidad. Mide latencia y comportamiento de cola.
9) ext4 vs XFS para bases de datos en contenedores?
Ambos pueden ser excelentes. Lo que importa más es la calidad del dispositivo, opciones de montaje, versión del kernel y evitar capas patológicas (loopback, overlay para datos, disco raíz contendido).
10) ¿Cuál es la forma más rápida de zanjar la discusión entre “bind mount es más rápido” y “volumen es más rápido”?
Pon ambos en el mismo sistema de archivos/dispositivo subyacente y ejecuta una carga que incluya fsync. Si difieren significativamente, has descubierto una diferencia ambiental, no una verdad filosófica.
Siguientes pasos que puedes hacer esta semana
- Auditoría de ubicación: lista todos los contenedores/pods de BD y prueba que sus datadirs estén en montajes, no en overlay2.
- Mapear la realidad del almacenamiento: para cada BD, registra tipo de sistema de archivos, dispositivo, opciones de montaje y si es local o en red.
- Medir latencia de cola de fsync: ejecuta una pequeña prueba fio fsync en la ruta exacta usada por datadir/WAL/redo.
- Separar logs si es necesario: aísla WAL de PostgreSQL o logs de MariaDB en almacenamiento más rápido cuando la latencia de commit sea el problema.
- Estabilizar el nodo: elimina servicios con escrituras intensivas del disco de BD y verifica que límites de CPU/memoria no estén forzando patrones extraños de I/O.
- Escribe tu postura de durabilidad: Postgres y MariaDB permiten intercambiar seguridad por velocidad. Haz esa decisión explícita y revísala trimestralmente.
La verdad de rendimiento no es “bind mount vs volumen”. Es “qué camino de almacenamiento construiste realmente y respeta fsync bajo presión?”.
Haz eso bien, y MariaDB y PostgreSQL se comportarán como los sistemas maduros que son: predecibles, lo suficientemente rápidos y sin disculpas frente a la física.