Los volúmenes CIFS en Docker son lentos: la verdad (y mejores alternativas)

¿Te fue útil?

Empieza con una queja que suena vaga e inofensiva: “El contenedor está lento.” Entonces miras más de cerca y siempre tiene la misma forma: un volumen Docker respaldado por un recurso compartido CIFS/SMB, y una aplicación que toca archivos como si le pagaran por cada syscall.

En teoría, CIFS es “simplemente un sistema de archivos en red.” En producción, es un amplificador de latencia envuelto en metadatos, mapeo de permisos, límites de caché y la optimista creencia de que tu carga de trabajo es principalmente lecturas secuenciales. Spoiler: tu carga de trabajo no lo es.

Qué significa realmente “volumen CIFS lento”

Cuando la gente dice “CIFS es lento”, normalmente está comprimiendo varios modos de fallo distintos en una sola frase gruñona:

  • Alta latencia por operación: cada stat(), open, close, chmod, rename se convierte en un viaje de ida y vuelta por la red (o varios).
  • Cargas de trabajo con muchos metadatos: gestores de paquetes de lenguajes, checkouts de git, aplicaciones web con muchos archivos pequeños, tormentas de import en Python, autoloaders de PHP, Node node_modules, escaneo del classpath de Java. Estos son los depredadores naturales de CIFS.
  • Semánticas de durabilidad de escritura: SMB intenta proporcionar semánticas amigables con Windows alrededor de locking y consistencia. Eso no es gratis.
  • Limitaciones de caché en el cliente: Linux CIFS a menudo debe sacrificar agresividad de caché por corrección, especialmente con acceso por múltiples clientes.
  • Mapeo de permisos y traducción de identidad: no es glamuroso, pero añade sobrecarga y causa reintentos y sorpresas.

Además: Docker no hace que CIFS sea mágicamente más rápido. Los contenedores solo facilitan equivocarse al poner una base de datos en un recurso compartido de red y luego sorprenderse cuando se comporta como una base de datos en un recurso compartido de red.

Una regla firme: si tu aplicación realiza muchas E/S pequeñas aleatorias o llamadas de metadatos al sistema de archivos, CIFS dentro de un contenedor es un impuesto de rendimiento que pagas en cada petición. Tu “throughput” puede parecer correcto en una prueba de copia de archivos grandes y aun así ser inusable en cargas reales.

Hechos e historia: por qué SMB se comporta así

Un poco de contexto ayuda, porque SMB/CIFS no es lento porque sea “malo”. Es lento porque está haciendo trabajo que no pediste, para clientes que no invitaste, a través de redes que no pretendías.

8 hechos interesantes (breves, concretos y útiles)

  1. “CIFS” es efectivamente el dialecto SMB más antiguo (era SMB1). La mayoría de las configuraciones modernas usan SMB2/SMB3, pero la gente aún dice “CIFS” como si fuera una marca de pañuelos.
  2. SMB1 era conversador por diseño. SMB2 redujo drásticamente los viajes de ida y vuelta, pero las cargas con muchos metadatos siguen sufriendo porque el cliente todavía necesita respuestas.
  3. SMB3 añadió cifrado y multichannel. Genial para seguridad y resiliencia, a veces no tan bueno para CPU y latencia si está mal configurado.
  4. Los opportunistic locks (oplocks) y leases existen para mejorar la caché, pero introducen tráfico de invalidación y semánticas complicadas bajo concurrencia.
  5. Las semánticas de Windows importan: SMB está construido para preservar el bloqueo y los modos de compartición de Windows. Las aplicaciones Linux que asumen comportamiento POSIX pueden desencadenar comprobaciones adicionales.
  6. Linux CIFS usa el cliente en el kernel (cifs.ko) y ha ido mejorando, pero las restricciones de corrección siguen limitando la caché agresiva en escenarios con múltiples escritores.
  7. El coste de metadatos domina en enlaces de alta latencia: incluso 2–5 ms de RTT pueden hacer que “miles de operaciones pequeñas” se sientan como melaza, independientemente del ancho de banda gigabit.
  8. Los volúmenes Docker no son un milagro de abstracción de almacenamiento: el kernel sigue haciendo el mismo montaje/E/S. Docker solo facilita desplegar el error de forma consistente.

Las verdaderas causas raíz (no los mitos)

Mito: “Es ancho de banda. Necesitamos red más rápida.”

El ancho de banda importa para lecturas/escrituras secuenciales grandes. El dolor de los volúmenes CIFS en contenedores suele ser IOPS y latencia más amplificación de metadatos. Puedes tener un enlace de 10 GbE y aún ser destrozado por 3 ms de RTT y 20k llamadas stat() por petición. Tu enlace estará aburrido mientras tu app está de rodillas.

Mito: “Es sobrecarga de Docker.”

Docker añade algo de sobrecarga en casos específicos (overlay filesystems, user namespaces, peculiaridades de propagación de montajes). Pero para un bind mount o volumen con nombre respaldado por CIFS, el coste dominante son las semánticas del sistema de archivos en red. Docker es en su mayoría un espectador inocente sosteniendo la bolsa.

Realidad #1: La latencia vuelve no lineales las cargas de trabajo de metadatos

Si una petición dispara 500 operaciones de metadatos y cada una cuesta un viaje de ida y vuelta por la red, las matemáticas son brutales. CIFS puede pipelinear y agrupar algunas operaciones, pero no lo suficiente para salvar una carga de trabajo conversadora de sí misma.

Realidad #2: La caché está limitada por la corrección

Linux CIFS puede cachar atributos y entradas de directorio, pero si múltiples clientes pueden modificar el mismo árbol, la caché se vuelve una responsabilidad. Puedes ajustar la caché (más adelante hablaremos), pero siempre estás intercambiando coherencia entre clientes por velocidad.

Realidad #3: Los contenedores ocultan desajustes de identidad y permisos

Dentro de un contenedor, UID/GID puede no coincidir con lo que el servidor SMB espera. Eso conduce a comprobaciones de permisos, escrituras fallidas y, a veces, comportamientos de respaldo. Incluso cuando “funciona”, puedes terminar con intentos extra de chown/chmod que golpean fuertemente las rutas de metadatos.

Realidad #4: El firmado/cifrado SMB puede ser un impuesto silencioso a la CPU

El firmado y el cifrado SMB son buenos, a menudo necesarios, y a veces costosos. Si alguno de los lados carece de aceleración AES o fijas los contenedores a CPU muy limitadas, tu “problema de almacenamiento” es en realidad “la criptografía te está comiendo el rendimiento”.

Realidad #5: Las semánticas de bloqueo muerden a bases de datos y herramientas de compilación

SQLite, muchos sistemas de build y algunos servidores de aplicaciones dependen de bloqueos de archivos y patrones fsync que están bien en ext4/xfs locales. Sobre SMB, las semánticas pueden ser más lentas, o peor, sutilmente diferentes. Ahí es donde obtienes “lento” y “raro” en el mismo ticket.

Parafraseando una idea de Werner Vogels: “Todo falla, todo el tiempo—diseña para eso.” CIFS sobre red fallará de maneras más creativas que tu disco local jamás había tenido tiempo de practicar.

Guía rápida de diagnóstico

Este es el orden que uso cuando alguien me dice “volumen CIFS lento” y quiero una respuesta antes del almuerzo.

1) Prueba que sea el montaje (no la app)

  • Compara la misma operación en CIFS vs disco local: creación de archivos, tormentas de stat, escrituras pequeñas.
  • Comprueba si la ralentización se correlaciona con operaciones de metadatos en lugar de throughput.

2) Mide latencia, no solo throughput

  • Ping RTT al servidor.
  • Busca retransmisiones, congestión, problemas de duplex, sorpresas de Wi-Fi “enterprise”.

3) Identifica el dialecto SMB y las características de seguridad

  • Confirma SMB3 vs SMB2 vs caída accidental a SMB1.
  • Revisa el estado de firmado/cifrado; verifica saturación de CPU en cualquiera de los lados.

4) Inspecciona la caché y las opciones de montaje

  • Caché de atributos (actimeo), caché del cliente (cache=), tamaños de lectura/escritura y si las opciones se están ignorando.

5) Determina si tienes escritores múltiples

  • Si múltiples nodos/contenedores escriben en el mismo árbol, tus perillas de caché son limitadas.
  • Si es un solo escritor, puedes ser más audaz.

6) Revisa el servidor y la ruta

  • ¿El servidor SMB es un equipo Windows, Samba o un appliance NAS?
  • ¿El almacenamiento subyacente es lento (discos, RAID sobrecargado, disco cloud thin-provisioned)?

Chiste #1: Los sistemas de archivos en red son como las impresoras de oficina—cuando funcionan, nadie se da cuenta; cuando no, todo el mundo de repente tiene opiniones sobre la infraestructura.

Tareas prácticas: comandos, salidas, decisiones

A continuación hay tareas reales que puedes ejecutar en un host Linux con Docker. Cada una incluye: comando, salida de ejemplo, qué significa y qué decisión tomar.

Task 1: Confirmar el tipo de montaje y las opciones realmente en uso

cr0x@server:~$ findmnt -T /var/lib/docker/volumes/appdata/_data -o TARGET,SOURCE,FSTYPE,OPTIONS
TARGET                              SOURCE                FSTYPE OPTIONS
/var/lib/docker/volumes/appdata/_data //nas01/share        cifs   rw,relatime,vers=3.1.1,cache=strict,username=svc_app,uid=1000,gid=1000,actimeo=1

Qué significa: Esto es un montaje CIFS (SMB) con SMB 3.1.1 y caché estricta. El timeout de caché de atributos es de 1 segundo.

Decisión: Si la carga es intensiva en metadatos y de un solo escritor, considera aumentar actimeo y/o cambiar cache= para mejorar la velocidad. Si hay varios escritores, procede con cautela.

Task 2: Verificar dialecto SMB y capacidades vía logs del kernel

cr0x@server:~$ dmesg | grep -i cifs | tail -n 5
[ 9342.112233] CIFS: VFS: \\nas01 negotiated SMB3.1.1 dialect
[ 9342.112240] CIFS: VFS: cifs_mount failed w/return code = -13
[ 9410.445566] CIFS: VFS: \\nas01 Server supports multichannel
[ 9410.445577] CIFS: VFS: \\nas01 requires packet signing

Qué significa: Se negoció SMB3.1.1, se soporta multichannel y se requiere firmado. Hubo una falla de permisos antes (-13).

Decisión: Asegúrate de que no estés cayendo silenciosamente a SMB1. También investiga el coste de firmado y ese error de auth anterior (puede indicar reintentos/backoff).

Task 3: Medir RTT de red y jitter

cr0x@server:~$ ping -c 20 nas01
PING nas01 (10.20.1.50) 56(84) bytes of data.
64 bytes from 10.20.1.50: icmp_seq=1 ttl=63 time=1.92 ms
64 bytes from 10.20.1.50: icmp_seq=2 ttl=63 time=2.08 ms
...
--- nas01 ping statistics ---
20 packets transmitted, 20 received, 0% packet loss, time 19024ms
rtt min/avg/max/mdev = 1.71/2.05/2.61/0.21 ms

Qué significa: RTT ~2 ms. Eso está “bien” para humanos y “caro” para miles de llamadas de metadatos.

Decisión: Si tu carga es pesada en metadatos, necesitas caché, cambios en la carga o un backend de almacenamiento diferente—no solo más ancho de banda.

Task 4: Buscar retransmisiones y dolor TCP

cr0x@server:~$ ss -ti dst 10.20.1.50:445 | head -n 20
ESTAB 0 0 10.20.1.10:52144 10.20.1.50:445
	 cubic wscale:7,7 rto:204 rtt:2.3/0.4 ato:40 mss:1448 pmtu:1500 rcvmss:1448 advmss:1448 cwnd:10 bytes_acked:1289341 segs_out:10873 segs_in:10122 send 50.3Mbps lastsnd:12 lastrcv:12 lastack:12 pacing_rate 100.6Mbps retrans:0/12

Qué significa: RTT bajo, algo de historial de retransmisiones pero actualmente cero activas. Si ves retransmisiones creciendo, la red está saboteando tu almacenamiento.

Decisión: Si las retransmisiones son significativas, arregla la red antes de afinar SMB. Ninguna opción de montaje vence a la pérdida de paquetes.

Task 5: Micro-benchmark rápido de metadatos (tormenta de creación de archivos)

cr0x@server:~$ time bash -c 'd=/var/lib/docker/volumes/appdata/_data/testmeta; rm -rf "$d"; mkdir -p "$d"; for i in $(seq 1 5000); do : > "$d/f_$i"; done'
real	0m38.412s
user	0m0.311s
sys	0m5.992s

Qué significa: 5.000 pequeñas creaciones tardaron 38 segundos. Eso no es “un poco lento”, eso es “tu app va a agotar el tiempo”.

Decisión: Es un problema de latencia de metadatos. Considera mover caminos calientes a disco local, usar NFS con afinamiento apropiado o una capa de caché local.

Task 6: Comparar la misma prueba en disco local

cr0x@server:~$ time bash -c 'd=/tmp/testmeta; rm -rf "$d"; mkdir -p "$d"; for i in $(seq 1 5000); do : > "$d/f_$i"; done'
real	0m0.486s
user	0m0.169s
sys	0m0.301s

Qué significa: La misma carga es ~80× más rápida localmente. Por eso la gente piensa “Docker es lento” cuando en realidad es “metadatos remotos son lentos”.

Decisión: No pongas cargas intensivas en metadatos en CIFS salvo que aceptes el coste o rediseñes la carga.

Task 7: Inspeccionar estadísticas CIFS (pistas del lado cliente)

cr0x@server:~$ cat /proc/fs/cifs/Stats
Resources in use
CIFS Session: 2
Share (unique mount targets): 1
SMB Request/Response Buffer: 1 Pool size: 5
SMB Small Req/Resp Buffer: 1 Pool size: 30
Operations (MIDs): 0
Total vfs operations: 482109
Total ops: 612990
Total reconnects: 3

Qué significa: Existen reconexiones. Incluso unas pocas reconexiones pueden crear “pausas aleatorias” que parecen fallos de la app.

Decisión: Si las reconexiones aumentan, revisa la estabilidad del servidor SMB, timeouts de inactividad, state tracking de firewalls e interrupciones de red.

Task 8: Confirmar si el cifrado SMB está activado (y costando CPU)

cr0x@server:~$ grep -iE 'Encryption|Signing|Dialect' /proc/fs/cifs/DebugData | head -n 20
Dialect: 3.1.1
Security: NTLMSSP
Signing: Enabled
SMB3 encryption: Enabled

Qué significa: El cifrado está activado. Excelente para seguridad; posiblemente costoso para la CPU.

Decisión: Si las CPUs están saturadas durante la E/S, considera si el cifrado es obligatorio de extremo a extremo. Si lo es, asegúrate de CPUs modernas y evita cargas de I/O minúsculas.

Task 9: Vigilar steal/saturación de CPU durante la E/S

cr0x@server:~$ mpstat -P ALL 1 5
Linux 6.5.0 (server) 	01/03/2026 	_x86_64_	(16 CPU)

12:03:10 AM  CPU   %usr %nice %sys %iowait %irq %soft %steal %idle
12:03:11 AM  all   22.10  0.00  18.44   9.33 0.00  0.88   0.00 49.25
12:03:12 AM  all   25.20  0.00  21.77  10.12 0.00  1.01   0.00 41.90

Qué significa: Alto %sys y %iowait sugiere que el kernel está realizando mucho trabajo de sistema de archivos/red. Si %usr no es alto pero %sys sí, la sobrecarga SMB es plausible.

Decisión: Si la CPU es el cuello de botella, afinar opciones de montaje no te salvará. Necesitas cambios en la carga, menos operaciones pequeñas o un almacenamiento distinto.

Task 10: Identificar los peores culpables (metadatos vs datos)

cr0x@server:~$ strace -f -tt -T -o /tmp/trace.txt docker exec app sh -c 'php -r "for($i=0;$i<2000;$i++){stat(\"/data/smallfile_$i\");}"'
cr0x@server:~$ tail -n 5 /tmp/trace.txt
12:04:21.553221 stat("/data/smallfile_1998", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0 <0.006842>
12:04:21.560219 stat("/data/smallfile_1999", {st_mode=S_IFREG|0644, st_size=0, ...}) = 0 <0.006991>

Qué significa: Cada stat() toma ~7 ms. Multiplica eso por miles y obtienes tristeza con número de ticket.

Decisión: O bien aumentas la caché (si es seguro), reduces los stat (config de la app) o mueves esos archivos a local.

Task 11: Benchmark de throughput SMB con una escritura secuencial grande (para no engañarte)

cr0x@server:~$ dd if=/dev/zero of=/var/lib/docker/volumes/appdata/_data/dd.test bs=8M count=256 oflag=direct status=progress
2147483648 bytes (2.1 GB, 2.0 GiB) copied, 13 s, 165 MB/s
256+0 records in
256+0 records out
2147483648 bytes (2.1 GB, 2.0 GiB) copied, 13.0256 s, 165 MB/s

Qué significa: El throughput es correcto. Esta es la trampa clásica: “165 MB/s, así que el almacenamiento es rápido.” Mientras tanto tu app muere en llamadas de metadatos.

Decisión: Deja de usar solo pruebas de throughput. Siempre haz también una prueba de metadatos.

Task 12: Comprobar la propagación de montajes de Docker y si estás apilando sistemas de archivos

cr0x@server:~$ docker inspect app | grep -n '"Type"\|"Source"\|"Destination"' | head -n 20
145:            "Type": "volume",
146:            "Source": "/var/lib/docker/volumes/appdata/_data",
147:            "Destination": "/data",

Qué significa: El contenedor ve un volumen montado en /data. Si tu contenedor también usa overlayfs para otras rutas, eso es normal. La clave es que tu camino caliente está golpeando CIFS directamente.

Decisión: Si la app escribe archivos temporales, redirige directorios temporales a almacenamiento local (tmpfs o volumen local) y deja CIFS solo para datos fríos/compartidos.

Task 13: Confirmar la versión del módulo CIFS del kernel y parámetros cargados

cr0x@server:~$ modinfo cifs | egrep 'version:|parm:|filename:' | head -n 15
filename:       /lib/modules/6.5.0/kernel/fs/smb/client/cifs.ko
version:        2.45
parm:           CIFSMaxBufSize:Network buffer size (int)
parm:           enable_oplocks:Enable or disable oplocks (int)
parm:           linux_ext:Enable Linux CIFS Extensions (int)

Qué significa: Estás usando el cliente SMB en el kernel. La versión y las características varían entre versiones de kernel; las actualizaciones pueden cambiar el comportamiento.

Decisión: Si estás en un kernel antiguo, considera actualizar. El rendimiento y la corrección del cliente SMB han mejorado materialmente con el tiempo.

Task 14: Validar que DNS y resolución de nombres no estén añadiendo latencia

cr0x@server:~$ time getent hosts nas01
10.20.1.50      nas01
real	0m0.006s
user	0m0.002s
sys	0m0.003s

Qué significa: La resolución de nombres es rápida. Si esto toma cientos de milisegundos por DNS roto, las reconexiones y montados SMB se vuelven lentos e inestables.

Decisión: Arregla DNS o usa IPs estables en las definiciones de montaje (con conciencia de failover).

Opciones de montaje que ayudan (y las que engañan)

Las opciones de montaje no son hechizos mágicos. Son compensaciones, por lo general entre rendimiento y coherencia. Si tienes múltiples clientes escribiendo en el mismo árbol, tu margen de ajuste es pequeño.

Empieza con valores sensatos

Para servidores SMB modernos, usa SMB3 explícitamente. No dejes que la negociación “lo averigüe”. Cuando “lo averigua” mal, pasarás una semana haciendo benchmarks con fantasmas.

cr0x@server:~$ sudo mount -t cifs //nas01/share /mnt/share \
  -o vers=3.1.1,username=svc_app,uid=1000,gid=1000,serverino,rw

Opciones que suelen importar

  • vers=3.1.1: fija un dialecto moderno. Si el servidor no puede, quieres saberlo.
  • actimeo=: tiempo de caché de atributos. Valores mayores pueden acelerar enormemente lecturas pesadas en metadatos; también pueden ocultar cambios entre clientes.
  • cache= (strict, loose, none): controla la política de caché del cliente. “Loose” puede ser más rápido y menos correcto.
  • rsize=, wsize=: ajusta tamaños de lectura/escritura. Ayuda al throughput, menos útil para tormentas de metadatos.
  • noserverino vs serverino: comportamiento de inode. Los desajustes pueden romper apps que dependen de números de inode estables; el impacto en rendimiento varía pero el impacto en corrección puede ser real.

Opciones que se malentienden frecuentemente

  • nounix/unix: afecta extensiones Unix, permisos y comportamiento. Úsalo deliberadamente, no copiando un fragmento de blog de 2014.
  • soft/hard: eso es un concepto de NFS; las semánticas de fallo de SMB son distintas. Aún necesitas pensar en timeouts y reintentos, pero la perilla no es la misma.
  • “Solo aumenten wsize y se arregla”: si tu problema es metadatos, buffers I/O más grandes no importarán.

Un ejemplo realista de afinado «single-writer, mostly-read»

Si tienes un contenedor escribiendo y el resto leyendo (o un solo nodo), puedes asumir más riesgos:

cr0x@server:~$ sudo mount -t cifs //nas01/share /mnt/share \
  -o vers=3.1.1,username=svc_app,uid=1000,gid=1000,rw,cache=loose,actimeo=30

Qué obtienes: Menos viajes de ida y vuelta para stats y trayectos de directorio.

Qué pagas: Otro cliente podría no ver actualizaciones inmediatamente. Si tienes múltiples escritores, acabas de crear una lotería de coherencia.

Peculiaridades específicas de Docker con volúmenes CIFS

Volúmenes nombrados con el driver local y CIFS

Un patrón común es el driver de volúmenes local de Docker con opciones CIFS. Es conveniente, reproducible y también fácil de configurar mal.

cr0x@server:~$ docker volume create \
  --driver local \
  --opt type=cifs \
  --opt device=//nas01/share \
  --opt o=vers=3.1.1,username=svc_app,password=REDACTED,uid=1000,gid=1000,rw \
  appdata

Eso funciona, pero ahora has esparcido credenciales en lugares que podrías lamentar. Usa un archivo de credenciales en el host cuando sea posible y vigila los permisos de ese archivo.

Overlayfs no es el villano, pero puede ser daño colateral

Cuando el sistema de archivos del contenedor usa overlayfs y tu app mezcla rutas de overlay con montajes CIFS, puedes obtener patrones extraños: lecturas rápidas desde la capa de imagen, lecturas lentas desde datos montados y trazas de strace confusas que te hacen perseguir lo incorrecto.

Espacios de nombres de usuario y desajustes UID/GID

Si ejecutas Docker con userns-remap o en modo rootless, el mapeo UID/GID de CIFS puede volverse incómodo rápidamente. El “uid 1000” del contenedor puede mapear a otra cosa en el host. Entonces obtienes permission-denied con reintentos, comportamientos de fallback y acantilados de rendimiento que desaparecen cuando ejecutas como root (lo cual es otro acantilado, solo que distinto).

Los healthchecks pueden convertirse en pruebas de carga accidentales

Los healthchecks que tocan archivos en CIFS cada pocos segundos a través de muchos contenedores se convierten en un goteo constante de metadatos. Multiplica por docenas de contenedores y te has montado un pequeño DDoS contra tu propio NAS, lentamente, educadamente y continuamente.

Chiste #2: Poner una base de datos en CIFS es como remolcar un coche de carreras con un carrito de la compra—técnicamente se mueve, pero todos se ven incómodos.

Tres mini-historias corporativas desde el terreno

Mini-historia 1: El incidente causado por una suposición incorrecta

En una empresa mediana, un equipo migró una app legada a contenedores. Querían “almacenamiento compartido” entre dos hosts Docker para uploads y thumbnails generados. El camino más rápido para una demo fue un recurso CIFS desde un servidor Windows existente. Funcionó. Todos aplaudieron. El ticket se cerró con una sonrisa.

Dos semanas después, llegó una campaña de marketing. La app no se cayó inmediatamente. Simplemente empezó a agotar tiempos de espera en olas desparejas. La CPU estaba bien. La memoria estaba bien. Las gráficas de red estaban tranquilas. Los logs de la app estaban llenos de advertencias de requests lentas y errores ocasionales de archivo no encontrado que no tenían sentido.

La suposición incorrecta fue sutil: “Si un archivo existe en el share, todos los contenedores lo verán inmediatamente.” En realidad, el cacheo de atributos más la enumeración de directorios y las escrituras concurrentes produjeron vistas obsoletas. Un contenedor creó un thumbnail y otro no lo vio aún, así que lo volvió a generar. A veces competían. A veces sobrescribían. Ocasionalmente un lector obtuvo un archivo parcialmente escrito porque la app asumía atomicidad local que no estaba garantizada de la forma en que lo usaban.

La solución no fue heroica. Dejaron de compartir el camino caliente. Los uploads aterrizaban primero en disco local, luego un job asíncrono subía los artefactos finalizados al almacenamiento compartido. El almacenamiento compartido pasó a ser un punto de distribución, no un patio de trabajo en vivo. Los errores desaparecieron. El rendimiento se estabilizó. La frase del postmortem fue directa: “Tratamos un sistema de archivos en red como un sistema de archivos local bajo concurrencia.”

Mini-historia 2: La optimización que salió mal

En otro lugar, otra “solución ingeniosa.” Un equipo tenía un servicio Node.js con árboles enormes de dependencias. El arranque era dolorosamente lento porque leía miles de archivos pequeños desde un volumen CIFS. Alguien encontró una sugerencia de afinamiento: aumentar la caché agresivamente. Pusieron cache=loose y subieron mucho actimeo. El arranque en frío mejoró dramáticamente. Todos volvieron a aplaudir. Diferente sala de reuniones, mismo aplauso.

Luego vino el reves: los despliegues empezaron a fallar de una nueva manera. El servicio arrancaba con una versión vieja de un archivo de configuración, aun cuando el archivo había sido actualizado en el share por el job de despliegue. Los rollouts se volvieron no determinísticos. La mitad del fleet se comportaba como la nueva versión, la otra mitad como la vieja. Depurar fue un circo porque “simplemente reiniciarlo” a veces lo arreglaba y a veces no.

La causa raíz fue predecible en retrospectiva: cambiaron coherencia por velocidad sin modificar el flujo de trabajo. Un recurso compartido en red usado para configuración compartida y código en vivo necesita garantías de frescura. Cachés “loose” es básicamente un acuerdo con el universo sobre eventual incoherencia.

La solución fue dejar de usar CIFS para ese propósito. Construyeron imágenes inmutables que contenían dependencias y valores por defecto de configuración, y usaron un servicio de configuración para ajustes en tiempo de ejecución. CIFS permaneció, pero solo para artefactos grandes donde la obsolescencia de caché no causaba bugs de corrección.

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

Una organización más grande tenía volúmenes CIFS en producción por restricciones corporativas: equipo de almacenamiento basado en Windows, controles de acceso existentes y auditores a los que les gustaba la palabra “SMB” porque sonaba familiar. El equipo SRE no lo amaba, pero fueron realistas: algunas peleas son batallas presupuestarias, no técnicas.

Así que hicieron lo aburrido. Crearon un estándar: montajes CIFS permitidos solo para datos fríos y logs mayormente append-only, nunca para bases de datos, nunca para árboles de dependencias, nunca para scratch de build, nunca para directorios temporales de alta rotación. Cada carga respaldada por CIFS necesitaba un micro-benchmark en CI que ejecutara tanto una prueba de throughput como una de metadatos, con umbrales.

También añadieron chequeos rutinarios: alertas en reconexiones CIFS, seguimiento de CPU del servidor SMB y medición de latencia p99 de operaciones de archivo desde contenedores representativos. Nadie celebró esos dashboards. No eran sexys. Eran correctos.

Seis meses después, el equipo de almacenamiento empujó un cambio que alteró la configuración de firmado SMB. El rendimiento degradó ligeramente. Los dashboards lo captaron rápido y el rollback ocurrió antes de impacto al cliente. El “estándar aburrido” previno un incidente porque restringió donde CIFS podía hacer daño, y la monitorización hizo obvio el modo de fallo.

Mejores alternativas (y cuándo elegir cada una)

Si no lees nada más: deja de usar CIFS como backend de almacenamiento por defecto para cargas sensibles al rendimiento en contenedores. Úsalo cuando necesites semánticas nativas de Windows o estés forzado. De lo contrario, elige la herramienta que corresponda a la carga.

1) Disco local (ext4/xfs) + replicación (preferido para bases de datos)

Si los datos son propiedad de un servicio (Postgres, MySQL, Elasticsearch, persistencia de Redis), usa almacenamiento local y maneja replicación a nivel de aplicación. Tendrás latencias predecibles, comportamiento fsync correcto y menos bloqueos extraños.

2) NFSv4.1+ para almacenamiento POSIX compartido

NFS no es automáticamente “más rápido”, pero tiende a alinearse mejor con semánticas y herramientas Linux. Puede rendir mejor en cargas intensivas en metadatos, dependiendo de la configuración del servidor/cliente. También falla de forma distinta. A veces eso es precisamente el punto.

3) Almacenamiento de objetos para artefactos (APIs compatibles S3)

Si tus contenedores necesitan leer/escribir blobs, deja de pretender que es un sistema de archivos. Usa almacenamiento de objetos. Cambias semánticas POSIX por escalabilidad y una experiencia mucho mejor bajo concurrencia. Tu app puede necesitar cambios, pero tu yo futuro te dará las gracias.

4) Almacenamiento en bloque (iSCSI, Fibre Channel, volúmenes cloud) + un sistema de archivos

Si necesitas semánticas de sistema de archivos y rendimiento, crea el sistema de archivos sobre almacenamiento en bloque y móntalo localmente. Luego comparte vía mecanismos a nivel de aplicación, no dejando que múltiples clientes golpeen el mismo sistema de archivos a menos que uses intencionalmente un sistema de archivos clusterizado.

5) Una capa de caché local delante de CIFS

Si la realidad corporativa dicta que CIFS se queda, pon una caché delante. Opciones incluyen:

  • Sincronizar a local en el arranque (tipo rsync) y refresco periódico.
  • Patrones write-back para logs/artefactos (cola local, subida asíncrona).
  • Caché explícita en la aplicación (en memoria, Redis, CDN para contenido estático).

Esto funciona porque cambia la forma de I/O: menos viajes de red, menos operaciones de metadatos, menos escrituras síncronas.

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

Esta sección evita repetir incidentes.

1) Síntoma: “El throughput está bien pero la app aún agota tiempo”

Causa raíz: La latencia de metadatos domina (tormentas de stat/open/close). Las pruebas con archivos grandes mienten.

Solución: Ejecuta micro-benchmarks de metadatos; aumenta actimeo solo si es seguro; mueve árboles de dependencias y archivos temporales a disco local; rediseña la carga para usar menos llamadas al sistema de archivos.

2) Síntoma: “Congelamientos aleatorios de 5–30 segundos”

Causa raíz: Reconexiones SMB, fallos del servidor, timeouts de firewall/NAT o retrasos DNS durante la reconexión.

Solución: Revisa /proc/fs/cifs/Stats para reconexiones, retransmisiones TCP, dmesg; estabiliza la ruta de red; asegúrate de keepalives; reduce timeouts de inactividad; arregla DNS.

3) Síntoma: “Funciona en un host, lento en otro”

Causa raíz: Diferentes opciones de montaje, distinto dialecto SMB negociado, versiones de kernel distintas o diferencias en capacidades criptográficas de la CPU.

Solución: Compara salida de findmnt; fija vers=; estandariza kernel y ajustes CIFS; verifica cifrado/signing y uso de CPU.

4) Síntoma: “Permiso denegado” mezclado con lentitud

Causa raíz: Desajuste UID/GID, espacios de nombres de usuario o evaluación de ACLs en el servidor que causa fallos repetidos.

Solución: Alinea identidades; usa uid=/gid= consistentes; considera usar Samba con extensiones Unix si necesitas permisos POSIX; evita tormentas de chown al iniciar contenedores.

5) Síntoma: “El archivo existe, pero la app no lo ve todavía”

Causa raíz: Caché del cliente o de entradas de directorio; demoras en coherencia entre clientes.

Solución: Reduce la agresividad de caché para rutas compartidas de config/código; no uses CIFS como mecanismo de coordinación; usa un servicio de registro/configuración o sincronización explícita.

6) Síntoma: “Corrupción de base de datos / errores de locking / comportamiento fsync raro”

Causa raíz: Las semánticas del sistema de archivos en red no coinciden con lo que la base de datos espera ante fallos, locking o requisitos de durabilidad.

Solución: No ejecutes bases de datos en CIFS. Usa disco local o almacenamiento clusterizado diseñado con semánticas conocidas para esa base de datos.

7) Síntoma: “Picos de CPU durante copias de archivos”

Causa raíz: Coste de cifrado/signing SMB, posiblemente con I/O pequeño.

Solución: Confirma cifrado/signing; asegura aceleración AES-NI/CPU; incrementa tamaños de I/O donde sea posible; considera una ruta de almacenamiento alternativa si la CPU es el cuello de botella.

Listas de verificación / plan paso a paso

Paso a paso: estabilizar una carga lenta respaldada por CIFS en Docker (plan práctico)

  1. Identifica el camino caliente: qué directorios están en CIFS y cuáles son sensibles a latencia (config, deps, temp, cache, archivos DB).
  2. Ejecuta dos benchmarks: uno de throughput (dd) y uno de metadatos (create/stat storm). Registra tiempos.
  3. Mide RTT y retransmisiones: si la red está sucia, detente y arregla eso primero.
  4. Confirma dialecto/seguridad: SMB3.1.1 y si firmado/cifrado están activados.
  5. Revisa reconexiones: si hay reconexiones, trátalo como un problema de fiabilidad, no solo de rendimiento.
  6. Mueve directorios temporales a local: configura TMPDIR, directorios de caché de la app, salidas de build a disco local o tmpfs.
  7. Quita bases de datos de CIFS: migra a volúmenes locales con replicación/backups.
  8. Reduce la presión de metadatos: incorpora dependencias en las imágenes; evita instalaciones en tiempo de ejecución desde CIFS; minimiza escaneos de directorio.
  9. Ajusta la caché solo cuando sea seguro: si hay single-writer, aumenta actimeo y considera cache=loose para árboles de solo lectura.
  10. Estandariza las definiciones de montaje: mismas opciones en todos los hosts, vers= fijado, credenciales gestionadas de forma segura.
  11. Instrumenta latencia filesystem p95/p99: muestreo con strace, tiempos en la aplicación y stats CIFS.
  12. Planea una salida: elige un diseño de almacenamiento alternativo para el próximo trimestre; no dejes que “CIFS temporal” se haga inmortal.

Lista operativa: cuando CIFS es inevitable

  • Fija el dialecto SMB (vers=3.1.1 o uno conocido bueno).
  • Documenta si el share es single-writer o multi-writer.
  • Mantén CIFS para archivos grandes y datos fríos, no para tormentas de metadatos.
  • Alerta sobre reconexiones y retransmisiones elevadas.
  • Mantén opciones de montaje consistentes entre hosts.
  • Prueba después de actualizaciones del kernel; el cliente SMB cambia con el tiempo.

Preguntas frecuentes

1) ¿“CIFS” es lo mismo que SMB?

En la jerga operativa diaria, sí. Técnicamente, CIFS suele referirse a los dialectos más antiguos de la era SMB1. En Linux a menudo montas con -t cifs incluso cuando se negocia SMB3.

2) ¿Por qué CIFS es especialmente doloroso en contenedores?

Los contenedores fomentan patrones como “montar un share y ejecutar todo ahí”, incluyendo árboles de dependencias, caches y a veces bases de datos. Los contenedores también multiplican el número de clientes concurrentes y operaciones de metadatos.

3) ¿Cuál es el mayor predictor de “CIFS es lento”?

Cargas intensivas en metadatos más RTT no trivial. Si tu app hace muchas llamadas pequeñas al sistema de archivos, sentirás cada milisegundo.

4) ¿Las opciones de montaje pueden arreglarlo realmente?

Pueden mejorarlo, a veces dramáticamente, pero no cambian la física. Si tu carga necesita latencia de disco local y estás haciendo metadatos sobre la red, las opciones solo eligen qué esquina quieres recortar.

5) ¿Debería usar cache=loose?

Sólo cuando entiendas el trade-off de coherencia y el share no se usa para coordinación en vivo entre escritores. Es una palanca de rendimiento con precio de corrección.

6) ¿NFS es siempre más rápido que SMB?

No. Pero NFS a menudo se alinea mejor con cargas Linux y puede rendir mejor en patrones de metadatos, dependiendo de la configuración del servidor/cliente y del almacenamiento detrás.

7) ¿El multichannel de SMB me acelerará?

Puedes ayudar al throughput y a la resiliencia cuando está bien configurado en cliente y servidor con múltiples NICs. No arreglará mágicamente la latencia de metadatos y puede añadir complejidad.

8) ¿Por qué las copias de archivos grandes se ven bien mientras la app va a paso de tortuga?

El I/O secuencial puede transmitirse bien sobre SMB. Las apps suelen hacer I/O mixto con muchas opens/stats/escrituras pequeñas. Es un universo de rendimiento distinto.

9) ¿Ejecutar Postgres/MySQL en CIFS está soportado?

Aun cuando “funciona”, es una apuesta de fiabilidad y rendimiento. Usa disco local o almacenamiento diseñado para semánticas de base de datos. CIFS es un protocolo de compartición de archivos, no un sustrato de base de datos.

10) ¿Cuál es un patrón seguro si debo compartir datos entre contenedores?

Comparte artefactos finalizados, no conjuntos de trabajo vivos. Escribe localmente y luego publica. Trata CIFS como una capa de distribución, no como un espacio de trabajo transaccional.

Próximos pasos que puedes hacer esta semana

Si ya ejecutas volúmenes CIFS en Docker y están lentos, no empieces reescribiendo el mundo. Haz esto:

  1. Ejecuta el micro-benchmark de metadatos en el montaje CIFS y localmente. Si la brecha es grande, tienes tu diagnóstico.
  2. Mueve el churn caliente (temp, caches, instalaciones de dependencias, salidas de build) a almacenamiento local. Esto solo suele reducir el dolor por un orden de magnitud.
  3. Confirma dialecto SMB y ajustes de seguridad para no negociar algo antiguo o caro sin darte cuenta.
  4. Estandariza las opciones de montaje y mide antes/después. No afines a ciegas.
  5. Elige una alternativa para la próxima iteración: disco local + replicación para bases de datos, NFS para árboles POSIX compartidos, almacenamiento de objetos para blobs, o un patrón de caché por delante si CIFS está mandatado.

Luego escríbelo como regla: CIFS es aceptable para datos compartidos fríos. No es el hogar por defecto para contenedores de producción. Tu futura rotación on-call agradecerá silenciosamente tu falta de creatividad.

← Anterior
Tablas amigables para código: diseño fijo vs automático, ajuste y alineación numérica
Siguiente →
PCIe y GPUs: cuándo x8 es suficiente y cuándo perjudica

Deja un comentario