Debian 13: NFS más lento de lo esperado — demuestra que es sync/rsize/wsize y arréglalo (caso #23)

¿Te fue útil?

No hay nada tan desmoralizador como una máquina nueva con Debian 13, una NIC rápida, una matriz de almacenamiento sana y un NFS que rinde como si viajara por fax. Las lecturas son “más o menos aceptables”. Las escrituras son una tragedia. Y todo el mundo tiene una teoría, ninguna con evidencia.

Este es el caso en el que dejas de adivinar y empiezas a demostrar. Mostraremos cómo demostrar —usando estadísticas, trazas y pruebas controladas— que tu cuello de botella suele ser uno de tres culpables: semántica síncrona (sync/async más comportamiento de commit), rsize/wsize demasiado pequeñas, o un desajuste entre lo que espera el cliente y la realidad del almacenamiento del servidor. Luego lo arreglamos sin engañarnos sobre la durabilidad.

Guía rápida de diagnóstico

Si estás de guardia y alguien está respirando en el canal de incidentes, este es el camino más corto hacia la verdad. No empieces cambiando opciones de montaje. Empieza midiendo una cosa a la vez.

Primero: confirma qué montaste y qué exportaste

  1. Cliente: confirma la versión de NFS y las opciones de montaje (nfs4 vs nfs, rsize/wsize, comportamiento sync vía hard/soft y timeouts).
  2. Servidor: confirma las opciones de export y si el servidor está forzando comportamiento sync (por defecto) y si puede cachar de forma segura.

Segundo: determina si estás limitado por latencia (sync) o por ancho de banda (tamaño de E/S)

  1. Ejecuta una prueba de lectura en buffer y una prueba de escritura directa desde el cliente. Si las lecturas alcanzan la tasa de línea pero las escrituras se arrastran, sospecha commits síncronos.
  2. Revisa nfsstat -c buscando la tasa de llamadas COMMIT. Una alta tasa de COMMIT es un letrero luminoso.

Tercero: aisla red vs disco del servidor

  1. Red: verifica que puedes empujar paquetes con una prueba TCP simple (no “ping”, no esperanzas).
  2. Disco del servidor: comprueba si NFSd está esperando flushes de almacenamiento (común con HDDs, controladoras RAID con cache de escritura apagada o almacenamiento virtualizado).

Cuarto: sólo entonces afina

  • Si pruebas que los commits son el limitador: arregla la ruta de durabilidad (cache de almacenamiento del servidor, SLOG, ajustes del controlador) o acepta riesgo controlado (async solo si la carga lo permite).
  • Si pruebas que el límite es el tamaño de E/S: aumenta rsize/wsize, verifica que MTU y offloads no te saboteen, y confirma que el servidor soporta esos tamaños.

Modelo de rendimiento: dónde pasa realmente el tiempo NFS

Los debates sobre rendimiento de NFS suelen ser emocionales porque la gente confunde “mi aplicación está lenta” con “la red está lenta” con “el almacenamiento está lento”. NFS es las tres cosas, pegadas con un contrato sobre consistencia.

A grandes rasgos, una escritura NFS puede:

  • Ser enviada desde el cliente al servidor en fragmentos del tamaño de wsize.
  • Ser reconocida por el servidor ya sea como “lo recibí” o como “está en almacenamiento estable”, dependiendo de la versión NFS, opciones de export y la implementación del servidor.
  • Ser comprometida (explícitamente vía operaciones COMMIT en NFSv3 y NFSv4 cuando sea necesario) en almacenamiento estable.

Si el servidor debe hacer flush al almacenamiento estable con frecuencia, estás limitado por latencia. Si cada flush toma 2–10 ms y lo haces miles de veces, tu rendimiento está matemáticamente condenado. Puedes tener 100 GbE y aún escribir a “unas pocas decenas de MB/s” porque la física no negocia.

Si rsize/wsize son pequeñas (8K, 16K, 32K), estás limitado por sobrecarga. El coste de CPU del manejo de RPC, la latencia por llamada y la contabilidad del kernel se vuelven el cuello de botella incluso si el almacenamiento es rápido.

Debian 13 no cambia estas leyes. Solo te da kernels más nuevos, comportamiento cliente NFS actualizado y defaults ligeramente diferentes que pueden exponer lo que antes la suerte ocultaba.

Una cita que vale la pena recordar mientras afinas algo en producción:

«La esperanza no es una estrategia.» — General Gordon R. Sullivan

Hechos e historia interesantes (que sí ayudan)

  • NFS se diseñó en los años 80 para hacer que archivos remotos se sintieran locales en LANs que eran lentas según estándares actuales. Muchas de sus semánticas asumen que la latencia es aceptable si se preserva la consistencia.
  • NFSv3 introdujo el concepto COMMIT para que los clientes pudieran canalizar escrituras pero aun así pedir un commit duradero más tarde, por eso las “tormentas de COMMIT” son un modo de falla reconocible.
  • NFSv4 integró el “mount” en el protocolo e introdujo bloqueo con estado y sesiones; los contadores de rendimiento y los modos de fallo difieren de v3 en formas que aparecen en nfsstat.
  • sync históricamente es el valor seguro por defecto en muchos servidores NFS porque coincide con la expectativa del usuario: “cuando una escritura retorna, probablemente está segura.” Esa seguridad tiene un precio.
  • async puede ser muy rápido porque permite al servidor reconocer escrituras antes de que lleguen al almacenamiento estable. También puede convertir una pérdida de energía en pérdida de datos de manera “perfectamente consistente” con el contrato que violaste.
  • rsize/wsize solían estar limitados por NICs antiguas, MTUs y restricciones del kernel. Linux moderno puede usar tamaños grandes, pero aún necesitas compatibilidad end-to-end.
  • “Jumbo frames” no son un hechizo mágico. Ayudan en algunos casos CPU-limitados, pero también crean agujeros de path-MTU y pérdidas extrañas si un puerto de switch queda en 1500.
  • El cliente NFS de Linux ha tenido múltiples generaciones; “funcionaba en Debian 10” no prueba que tu carga esté bien—prueba que tu configuración antigua lo toleraba.
  • El writeback caching es un problema de almacenamiento disfrazado de problema de sistema de archivos. Un controlador RAID con la cache de escritura desactivada puede hacer que sync se sienta como melaza incluso en SSDs.

Broma #1: Tunear NFS es como ajustar el mezclador de la ducha en un hotel viejo—un milímetro de más y o estás congelado o te estás quemando.

Demostrar que es comportamiento sync/commit

No demuestras que “sync es el problema” declarándolo en Slack. Lo demuestras correlacionando latencia de escritura, frecuencia de commits y tiempo de flush del almacenamiento del servidor.

Qué significa realmente “sync” aquí

En el lado del servidor, el comportamiento de export sync generalmente significa que el servidor no responderá “hecho” hasta que pueda garantizar que los datos están en almacenamiento estable (o al menos tan estables como la pila de almacenamiento del servidor afirma). Eso a menudo implica un flush de caché o una barrera equivalente.

En muchas configuraciones, el asesino no es la transferencia por la red. Es la garantía de almacenamiento estable. Si tu almacenamiento subyacente no puede completar flushes rápidamente, cada escritura “segura” se vuelve una micro-transacción esperando a que un disco termine de meditar.

La firma del dolor por sync

  • El rendimiento de lectura grande se ve bien; el rendimiento de escritura grande es bajo y plano.
  • El rendimiento de escritura no aumenta mucho con archivos más grandes o múltiples hilos (o aumenta ligeramente y luego se estabiliza).
  • nfsstat muestra una tasa no trivial de llamadas COMMIT.
  • Métricas de almacenamiento del servidor muestran alta latencia en flush o barreras (incluso cuando IOPS brutas parecen “aceptables”).

Qué hacer con esa prueba

Tienes tres opciones honestas:

  1. Hacer el almacenamiento estable rápido (SSDs adecuados, cache de controlador con protección por batería/flash, ZFS SLOG, etc.).
  2. Cambiar la carga de trabajo (escrituras por lotes, reducir frecuencia de fsync, escribir localmente y luego mover, usar almacenamiento de objetos para logs, etc.).
  3. Relajar el contrato de seguridad (async, o durabilidad a nivel aplicación) con los ojos bien abiertos y por escrito.

Demostrar que es rsize/wsize

rsize y wsize definen el tamaño máximo de carga útil para operaciones NFS READ y WRITE. Si son demasiado pequeñas, te ahogas en overhead de RPC. Lo suficientemente grandes, y aprovechas el ancho de banda por el que pagaste.

Pero hay una trampa: los tamaños grandes solo ayudan si no estás ya limitado por latencia de commit y si el camino (cliente, servidor, red) los maneja correctamente.

Firma del dolor por rsize/wsize

  • Uso de CPU en cliente o servidor crece rápidamente con el throughput.
  • Muchas operaciones NFS por segundo aparecen en nfsstat.
  • El rendimiento mejora drásticamente al aumentar rsize/wsize.
  • La red está infrautilizada a pesar de latencia de disco baja y CPU suficiente.

Qué tamaños “buenos” parecen en Linux moderno

En redes modernas típicas (1/10/25 GbE) y kernels, valores como 262144 (256 KiB) son puntos de partida comunes para NFSv4. Algunas entornos van más alto, pero perseguir valores máximos es menos útil que verificar que el tamaño negociado coincida con lo que crees.

Tareas prácticas: comandos, salidas, qué significan y qué decides

Estas son las tareas que realmente ejecuto cuando alguien dice “NFS está lento” y espera una solución. Cada tarea tiene un punto de decisión. Si no cambias decisiones en base a la salida, no estás solucionando problemas; estás de turismo.

Tarea 1: Confirma qué NFS montaste (versión, rsize/wsize, sec, proto)

cr0x@client:~$ nfsstat -m
/mnt/shared from nfs1:/export/shared
 Flags: rw,relatime,vers=4.2,rsize=262144,wsize=262144,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=10.10.10.21,local_lock=none

Significado: Esta es la verdad de suelo sobre lo que el kernel negoció. Si esperabas NFSv3 o tamaños diferentes, la realidad discrepa.

Decisión: Si rsize/wsize son pequeñas (por ejemplo, 32768), planifica probar tamaños mayores. Si la versión no es la esperada, arregla eso primero.

Tarea 2: Confirma las opciones de export del servidor (sync/async, fsid, subtree, security)

cr0x@nfs1:~$ sudo exportfs -v
/export/shared
	10.10.10.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,ro,secure,root_squash,no_all_squash)
/export/shared
	10.10.10.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,root_squash,no_all_squash)

Significado: El export es sync. Eso es seguro, y puede ser lento dependiendo del rendimiento de flush del almacenamiento.

Decisión: No cambies a async todavía. Primero demuestra que sync es el cuello de botella con evidencia de commit/latencia.

Tarea 3: Revisa básicos de carga de hilos NFS y RPC en el servidor

cr0x@nfs1:~$ sudo nfsstat -s
Server rpc stats:
calls      badcalls   badfmt     badauth    badclnt
10293841   0          0          0          0

Server nfs v4:
null         compound     open         close        read         write
0            854123      1102         1102         302194       181004
commit       getattr      setattr      fsinfo       renew        fsstat
42001        701223      0            21           12944        98

Significado: Si commit es no trivial relativo a write, puede que estés pagando por almacenamiento estable con frecuencia.

Decisión: Si las llamadas commit son altas, prioriza la investigación de sync/flush. Si commit es casi cero, enfócate en dimensionamiento de I/O o red.

Tarea 4: Busca tormentas de COMMIT también en el cliente

cr0x@client:~$ nfsstat -c
Client rpc stats:
calls      retrans    authrefrsh
221004     12         0

Client nfs v4:
null         compound     read         write        commit       getattr
0            199321      100112       40123        9800         32119

Significado: Existen llamadas commit y retrans son bajas. Eso no es un problema de pérdida de red; es probablemente un coste de durabilidad/flush.

Decisión: Avanza hacia medir latencia de flush en la pila de almacenamiento del servidor.

Tarea 5: Comprueba si usas por accidente comportamiento tipo “sync” en la app (fsync intenso)

cr0x@client:~$ sudo strace -f -tt -T -e trace=fdatasync,fsync,openat,write -p 23817
15:12:09.441201 fsync(7)                 = 0 <0.012341>
15:12:09.453901 write(7, "....", 4096)   = 4096 <0.000221>
15:12:09.454301 fsync(7)                 = 0 <0.010998>

Significado: La app llama mucho a fsync. En NFS eso puede traducirse en commits/flushes. Puedes afinar NFS todo el día y aún perder.

Decisión: Si la carga es fsync-intensa (bases de datos, loggers con journaling), debes hacer el almacenamiento estable del servidor rápido o cambiar la estrategia de durabilidad de la app.

Tarea 6: Ejecuta una prueba controlada de escritura que evite la caché de páginas del cliente

cr0x@client:~$ dd if=/dev/zero of=/mnt/shared/ddtest.bin bs=1M count=4096 oflag=direct status=progress
3229614080 bytes (3.2 GB, 3.0 GiB) copied, 78 s, 41.4 MB/s
4096+0 records in
4096+0 records out
4294967296 bytes (4.3 GB, 4.0 GiB) copied, 104.2 s, 41.2 MB/s

Significado: ~41 MB/s en un enlace presumiblemente más rápido es sospechoso. oflag=direct hace esto más sensible al comportamiento de commit del servidor.

Decisión: Si las escrituras directas son lentas mientras las pruebas de red son rápidas, enfoca en flush del almacenamiento del servidor y semánticas sync.

Tarea 7: Ejecuta una prueba de lectura en buffer para comparar

cr0x@client:~$ dd if=/mnt/shared/ddtest.bin of=/dev/null bs=4M status=progress
4173336576 bytes (4.2 GB, 3.9 GiB) copied, 6 s, 695 MB/s
4294967296 bytes (4.3 GB, 4.0 GiB) copied, 6.2 s, 693 MB/s

Significado: Las lecturas están cerca de la tasa de línea; las escrituras no. Esta asimetría grita “ruta de commit/flush/durabilidad”.

Decisión: Deja de culpar a la red.

Tarea 8: Verifica la capacidad de la red de forma independiente (throughput TCP simple)

cr0x@nfs1:~$ iperf3 -s
-----------------------------------------------------------
Server listening on 5201 (test #1)
-----------------------------------------------------------
cr0x@client:~$ iperf3 -c 10.10.10.11 -P 4
[SUM]   0.00-10.00  sec  36.8 GBytes  31.6 Gbits/sec  0             sender
[SUM]   0.00-10.00  sec  36.7 GBytes  31.5 Gbits/sec                  receiver

Significado: La red está bien. Tus escrituras NFS de 40 MB/s no son un problema de cable.

Decisión: Invierte tiempo en latencia de flush de almacenamiento y semánticas de escritura NFS.

Tarea 9: Comprueba la consistencia de MTU (porque alguien siempre “ayudó”)

cr0x@client:~$ ip -d link show dev enp65s0
2: enp65s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9000 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 3c:fd:fe:aa:bb:cc brd ff:ff:ff:ff:ff:ff
cr0x@nfs1:~$ ip -d link show dev enp65s0
2: enp65s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 3c:fd:fe:11:22:33 brd ff:ff:ff:ff:ff:ff

Significado: Incompatibilidad de MTU. Esto puede causar fragmentación, pérdidas y rarezas de rendimiento—especialmente si el equipo intermedio es inconsistente.

Decisión: O establece MTU 1500 en todas partes (aburrido, fiable) u habilita jumbo frames de extremo a extremo y demuéstralo con capture de paquetes y configuración de switch.

Tarea 10: Observa latencia por operación con nfsiostat

cr0x@client:~$ nfsiostat 2 3
10.10.10.11:/export/shared mounted on /mnt/shared:

read:            ops/s    kB/s   kB/op  retrans  avg RTT (ms)  avg exe (ms)
                 85.00  696320  8192.00   0.00       1.20         1.35
write:           ops/s    kB/s   kB/op  retrans  avg RTT (ms)  avg exe (ms)
                 40.00   40960  1024.00   0.00      10.80        24.50

Significado: Las escrituras tienen tiempo de ejecución mucho mayor que el RTT. Eso suele indicar espera en el lado del servidor (flush/commit), no retraso de red.

Decisión: Enfoca en el servidor: latencia de almacenamiento, cache de escritura, comportamiento de sync del sistema de archivos y NFSd threads.

Tarea 11: Observa la latencia de flush del disco del servidor en tiempo real

cr0x@nfs1:~$ iostat -x 2 3
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          6.23    0.00    4.12   21.44    0.00   68.21

Device            r/s     w/s   rkB/s   wkB/s  aqu-sz  await  svctm  %util
nvme0n1         12.00  420.00   896.0  43008.0   8.42  19.8   1.1   48.0

Significado: Alto await con utilización moderada implica que la latencia viene de flush/barriers o comportamiento de la pila de almacenamiento, no de saturación de ancho de banda.

Decisión: Investiga políticas de cache de escritura del almacenamiento, el sistema de archivos y si hay una “promesa” de almacenamiento estable (o su ausencia).

Tarea 12: Verifica si el servidor se queda sin hilos NFS o alcanza límites de CPU

cr0x@nfs1:~$ ps -eo pid,comm,psr,pcpu,stat | grep -E 'nfsd|rpc'
  812 nfsd              2  6.5 S
  813 nfsd              3  6.2 S
  814 nfsd              5  6.1 S
  815 nfsd              7  6.4 S
  501 rpc.svcgssd       1  0.1 S
  476 rpc.mountd        0  0.0 S
cr0x@nfs1:~$ cat /proc/fs/nfsd/threads
8

Significado: 8 hilos pueden estar bien o ser insuficientes según la carga y CPU. Si nfsd está saturado mientras el almacenamiento está inactivo, estás limitado por CPU/hilos.

Decisión: Si estás limitado por CPU: aumenta hilos y afina la red; si estás limitado por almacenamiento: los hilos no te salvarán.

Tarea 13: Confirma el rsize/wsize negociado remontando explícitamente (prueba, no asumas)

cr0x@client:~$ sudo mount -o remount,vers=4.2,rsize=1048576,wsize=1048576 nfs1:/export/shared /mnt/shared
cr0x@client:~$ nfsstat -m | sed -n '1,3p'
/mnt/shared from nfs1:/export/shared
 Flags: rw,relatime,vers=4.2,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=10.10.10.21,local_lock=none

Significado: El kernel aceptó tamaños de 1 MiB. Si los degrada silenciosamente, lo verás aquí.

Decisión: Si el rendimiento mejora y las tasas de commit se mantienen, conserva tamaños mayores. Si ves errores o retransmisiones, vuelve atrás y examina red/MTU/offloads.

Tarea 14: Revisa retransmisiones y timeouts (el “lento” que en realidad es pérdida)

cr0x@client:~$ nfsstat -rc
Client rpc stats:
calls      retrans    authrefrsh
221004     932        0

Significado: Retransmisiones altas. Eso no es “NFS siendo NFS”; es congestión, pérdidas, problemas de MTU o un camino de servidor saturado.

Decisión: Pausa el afinado. Arregla la pérdida de paquetes primero: contadores del switch, estadísticas de NIC, driver/firmware, consistencia MTU y QoS.

Tarea 15: Observa NFSd en kernel del servidor (sanity rápida)

cr0x@nfs1:~$ sudo cat /proc/fs/nfsd/pool_stats
# pool packets arrived sockets enqueued woken queued timers
cpu0  120391  0 0 0 0
cpu1  118220  0 0 0 0
cpu2  119004  0 0 0 0
cpu3  121188  0 0 0 0

Significado: Si ves colas grandes o problemas de wakeups, puedes estar limitado por hilos o enfrentando contención.

Decisión: Si las estadísticas de pool sugieren acumulación de colas, aumenta hilos y confirma planificación de CPU/ubicación NUMA.

Tarea 16: Valida el sistema de archivos del servidor y opciones de montaje (barriers y journaling importan)

cr0x@nfs1:~$ findmnt -no SOURCE,FSTYPE,OPTIONS /export/shared
/dev/md0 ext4 rw,relatime,errors=remount-ro

Significado: El tipo de sistema de archivos y opciones influyen en el comportamiento de flush. Ext4 está bien, pero tu dispositivo de bloque subyacente importa más que la folklore de Internet.

Decisión: Si el bloque es una controladora RAID, verifica la política de cache y protección por batería. Si está virtualizado, verifica la pila de almacenamiento del hipervisor y el modo de cache.

Soluciones que funcionan (cliente, servidor y almacenamiento)

Una vez que hayas probado el cuello de botella, las soluciones se vuelven directas. No siempre fáciles. Directas.

Categoría A: rsize/wsize bien hecho

Si demostraste comportamiento bound por overhead (tamaños pequeños de E/S), ajusta el montaje. Mantenlo aburrido y reproducible.

  • Comienza con vers=4.2,proto=tcp,hard,timeo=600.
  • Establece rsize=262144,wsize=262144 (o 1 MiB si lo validaste).
  • Usa noatime solo si entiendes las implicaciones; suele estar bien para datos compartidos pero puede sorprender algunos flujos de trabajo.
cr0x@client:~$ sudo mount -t nfs4 -o vers=4.2,proto=tcp,hard,timeo=600,retrans=2,rsize=262144,wsize=262144 nfs1:/export/shared /mnt/shared
cr0x@client:~$ nfsstat -m | head -n 2
/mnt/shared from nfs1:/export/shared
 Flags: rw,relatime,vers=4.2,rsize=262144,wsize=262144,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=10.10.10.21,local_lock=none

Qué evitar: mezclas aleatorias de opciones legacy copiadas de un post de 2009. Si no puedes explicar por qué existe una opción, no la publiques.

Categoría B: la forma honesta de hacer rápido el sync

Cuando sync es el cuello de botella, necesitas reducir el coste de commits duraderos.

1) Arregla la política de cache de escritura del almacenamiento (común en RAID / SAN)

Si la cache de escritura del controlador RAID está deshabilitada, las escrituras sync se arrastrarán. Muchos controladores la desactivan cuando falta el módulo de batería/flash o está degradado. Eso es correcto, pero también un evento de rendimiento.

Acción: verifica salud y política de cache del controlador con herramientas del proveedor. Luego vuelve a probar dd oflag=direct y observa la frecuencia de commits.

2) Usa un intent log / dispositivo de journal rápido cuando corresponda

En sistemas de archivos que lo soportan (notablemente ZFS con SLOG), puedes descargar escrituras síncronas a un dispositivo de baja latencia. Esto no es “más SSD”; es “el SSD correcto en el lugar correcto”.

3) Asegúrate de que tu stack de virtualización no mienta

Discos virtuales con “writeback cache” en una capa y expectativas de “sync” en otra pueden llevar a seguridad falsa o lentitud falsa. Decide qué es verdad y alinea ajustes end-to-end.

Categoría C: exportar async (solo si puedes tolerarlo)

Seamos claros: async puede hacer que las escrituras NFS sean veloces. También puede convertir una pérdida de energía en datos corruptos de forma que “parece consistente” hasta que no lo es. Si haces esto, hazlo porque la carga es prescindible (artefactos de build, caches, scratch) o porque la durabilidad se maneja en otra parte.

cr0x@nfs1:~$ sudoedit /etc/exports
cr0x@nfs1:~$ sudo exportfs -ra
cr0x@nfs1:~$ sudo exportfs -v | grep -A1 '/export/shared'
/export/shared
	10.10.10.0/24(async,wdelay,hide,no_subtree_check,sec=sys,rw,secure,root_squash,no_all_squash)

Decisión: Solo publica async si está aprobado explícitamente por el propietario de los datos y la historia de durabilidad está documentada. Si la historia es “probablemente está bien”, no es una historia; es un incidente futuro.

Broma #2: async es como quitarte el cinturón de seguridad para llegar más rápido a la reunión—llegarás temprano hasta que no lo hagas.

Categoría D: afina concurrencia del servidor NFS (cuando probaste límites de CPU/hilos)

Si tus pruebas muestran que el almacenamiento es rápido pero NFSd está limitado por CPU o hilos, aumenta hilos y confirma que no creas contención de locks.

cr0x@nfs1:~$ sudo systemctl edit nfs-server
cr0x@nfs1:~$ sudo systemctl show -p ExecStart nfs-server
ExecStart={ path=/usr/sbin/rpc.nfsd ; argv[]=/usr/sbin/rpc.nfsd 32 ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }

Significado: Aquí el servidor está configurado para 32 hilos. Eso puede ayudar cargas paralelas.

Decisión: Si aumentar hilos mejora throughput sin aumentar latencia o retransmisiones, mantenlo. Si aumenta contención o CPU steal (en VMs), retrocede.

Categoría E: deja de sabotearte con MTU inconsistentes y offloads

La consistencia MTU de extremo a extremo importa más que los slogans “9000 everywhere”. Si no puedes garantizarlo, usa 1500 y sigue con tu vida.

cr0x@client:~$ sudo ip link set dev enp65s0 mtu 1500
cr0x@client:~$ ip link show dev enp65s0 | grep mtu
2: enp65s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000

Decisión: Si las retransmisiones caen y NFS se estabiliza, el plan de “jumbo frames” no estaba listo para producción.

Tres mini-historias del mundo corporativo

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

La empresa tenía un farm de build escribiendo artefactos a un share NFS. Las actualizaciones de Debian se desplegaron y el equipo celebró: kernel más nuevo, NFS más nuevo. Luego las builds se ralentizaron. No fallaban—simplemente eran dolorosamente lentas. La cola de trabajo creció, los ingenieros empezaron a relanzar jobs, lo que empeoró todo. Un clásico slowdown auto-amplificante.

La suposición errónea fue simple: “el rendimiento NFS es mayormente red”. Pasaron un día entero cambiando cables, moviéndose a otro switch top-of-rack y discutiendo hashing de LACP. Los gráficos de red estaban limpios. Los contadores del switch también. Lo único que no estaba limpio era el canal de incidentes.

Cuando alguien finalmente ejecutó una prueba de escritura directa (dd oflag=direct) y la comparó con iperf3, la forma del problema quedó clara. Las escrituras estaban bound por latencia. Las lecturas estaban bien. El equipo sacó nfsstat y vio COMMIT calls. En el servidor, el almacenamiento era un volumen RAID cuya cache de escritura había sido desactivada tras un módulo de batería entrar en un “ciclo de aprendizaje” y no volver a la normalidad sin intervención manual.

La solución no fue exótica: restaurar la protección de la cache de escritura, verificar salud de la batería y volver a probar. El rendimiento regresó. El postmortem fue aún más aburrido: siempre separa throughput de red del coste de durabilidad de almacenamiento. Añadieron un paso estándar “iperf + dd direct-write” al runbook, y el siguiente incidente fue más corto y menos teatral.

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

Otra organización tenía un cluster de data science. Alguien leyó que “async hace rápido NFS” y cambió exports para el dataset compartido principal. Sí, se volvió rápido. Todos aplaudieron. Los jobs terminaron antes. Los dashboards se veían bien. Lo desplegaron más ampliamente.

Dos semanas después, un evento de energía afectó a un rack. No catastrófico—solo lo suficiente para reiniciar varios nodos incluido el servidor NFS. El dataset no parecía corrupto al principio. Luego las ejecuciones de entrenamiento empezaron a producir resultados inconsistentes. Algunas ejecuciones fallaron con errores de formato de archivo raros. Otras produjeron modelos sutilmente incorrectos. Ese es el tipo de fallo divertido: pasa CI y falla en realidad.

La investigación fue fea porque nada gritaba “corrupción”. Los archivos estaban presentes. Los permisos normales. Los checksums no coincidían con valores históricos, pero solo para ciertos shards. La causa raíz fue el export async: el servidor había reconocido escrituras que nunca llegaron a almacenamiento estable antes de la pérdida de energía. La app había asumido que “la escritura retornó” significaba durable. Esa suposición era razonable—hasta que alguien cambió el contrato.

Revirtieron a sync para datasets duraderos y crearon un export separado async para scratch. El golpe de rendimiento fue aceptado porque la alternativa era caos epistemológico. También introdujeron checks de integridad periódicos para datasets críticos. No los hizo más rápidos, pero los hizo correctos.

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

Un equipo fintech usaba NFS para bundles de configuración y artefactos de despliegue. No era almacenamiento glamoroso. La salvación fue que trataron NFS como una dependencia de producción: montajes documentados, versiones fijadas y un único perfil de montaje “conocido bueno” para clientes Linux.

Durante un despliegue de Debian 13, un equipo de aplicaciones se quejó de arranque lento, culpando a NFS. En lugar de discutir, el SRE de turno sacó el runbook estándar: confirmar negociación de montaje, ejecutar nfsiostat, comparar con baseline. El baseline importaba—porque lo tenían.

La salida mostró algo sutil: las retransmisiones subían durante horas pico. No era exagerado, pero no era cero. El equipo de red encontró un puerto de switch con errores intermitentes. Porque el equipo de almacenamiento pudo demostrar “esto es pérdida, no coste de sync”, la solución fue un reemplazo de hardware simple, no una semana de afinado.

La práctica aburrida fue “mantener un baseline y un perfil de montaje conocido-bueno”. Les ahorró días de especulación entre equipos y evitó el ritual habitual de sacrificar opciones de montaje al azar.

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

1) Escrituras estancadas entre 20–80 MB/s en enlaces rápidos

Síntoma: Lecturas saturan el enlace, escrituras son planas y bajas.

Causa raíz: Latencia de sync/commit dominada por tiempo de flush del almacenamiento del servidor (cache de escritura apagada, journal lento, ruta sync respaldada en HDD).

Solución: Mide tasa de COMMIT y await del servidor; arregla la ruta de durabilidad del almacenamiento (protección de cache del controlador, dispositivo log más rápido), o aísla cargas scratch a async.

2) El throughput aumenta con más clientes, pero cada cliente sigue lento

Síntoma: Throughput agregado sube, flujo único sigue decepcionante.

Causa raíz: wsize/rsize pequeños, overhead de flujo único, o app haciendo escrituras pequeñas con sync.

Solución: Aumenta rsize/wsize; prueba con dd bs=1M y valida valores negociados; arregla patrón de escritura de la app si es fsync-intenso.

3) Congelamientos aleatorios, “servidor no responde”, luego recuperación

Síntoma: Colgas periódicas, luego todo continúa.

Causa raíz: Pérdida de paquetes/retransmisiones, incompatibilidad MTU, bugs de offload de NIC, o hilos del servidor sobrecargados causando timeouts.

Solución: Revisa nfsstat -rc retrans; verifica MTU end-to-end; revisa contadores de error de NIC; aumenta hilos del servidor solo después de descartar pérdida.

4) Afinar rsize/wsize empeora las cosas

Síntoma: Tamaños mayores reducen throughput o aumentan latencia.

Causa raíz: Problemas de path MTU, fragmentación, o un servidor que no maneja bien E/S grandes (CPU limitado, presión de memoria).

Solución: Arregla consistencia MTU; confirma offloads de NIC; elige un tamaño moderado (256 KiB) y valida con nfsstat -m.

5) “Async lo arregló” y luego rarezas de datos después

Síntoma: Escrituras rápidas, luego corrupción o faltan actualizaciones recientes tras crash/pérdida de energía.

Causa raíz: Cambiaste el contrato de durabilidad. El sistema se comportó en consecuencia.

Solución: Usa sync para datos duraderos; aísla exports scratch; documenta expectativas de durabilidad y prueba comportamiento ante crash si debes desviarte.

6) Picos de CPU en el servidor NFS con throughput moderado

Síntoma: El almacenamiento está medio inactivo, la red no saturada, pero CPU del servidor está alta.

Causa raíz: Muchas operaciones pequeñas (tamaños pequeños de I/O), hilos nfsd insuficientes, o cifrado/krb overhead.

Solución: Aumenta rsize/wsize, incrementa hilos y mide de nuevo; si usas Kerberos, presupuestá CPU en consecuencia.

Listas de verificación / plan paso a paso

Paso a paso: demuestra que sync es el limitador

  1. En el cliente: captura la negociación de montaje con nfsstat -m.
  2. En el cliente: ejecuta iperf3 hacia el servidor para confirmar capacidad de red.
  3. En el cliente: ejecuta dd oflag=direct para escritura y dd para lectura; compara throughput.
  4. En cliente y servidor: revisa nfsstat por tasa de llamadas COMMIT.
  5. En cliente: usa nfsiostat para comparar RTT vs tiempo de ejecución en escrituras.
  6. En servidor: usa iostat -x para encontrar await/iowait altos durante escrituras.
  7. Decisión: si las escrituras están bound por latencia con commits, arregla la ruta de durabilidad del almacenamiento o acepta async controlado para cargas no duraderas.

Paso a paso: demuestra que rsize/wsize es el limitador

  1. Registra los actuales rsize/wsize desde nfsstat -m.
  2. Ejecuta prueba lectura/escritura con bloques grandes (bs=4M lectura, bs=1M oflag=direct escritura).
  3. Remonta temporalmente con rsize/wsize mayores y verifica que la negociación no degradó los valores.
  4. Vuelve a ejecutar las mismas pruebas y compara throughput y uso de CPU.
  5. Decisión: conserva el tamaño más pequeño que logre el throughput necesario sin aumentar retransmisiones/latencia.

Checklist de despliegue en producción (para no “afinar” hasta provocar un outage)

  • Elige un cliente y un share como canario.
  • Mide baseline: nfsstat -m, nfsstat -c, nfsiostat, iostat -x en el servidor durante carga.
  • Cambia una variable a la vez: o rsize/wsize o comportamiento de export sync, no ambos.
  • Mantén listo un comando de rollback (viejas opciones de montaje, línea de export anterior).
  • Vigila retransmisiones y latencia, no solo MB/s.
  • Escribe el contrato de durabilidad para cada export (durable vs scratch).

Preguntas frecuentes

1) ¿Debería usar NFSv3 o NFSv4.2 en Debian 13?

Por defecto usa NFSv4.2 salvo que tengas una razón de compatibilidad específica. v4 tiene mejor integración (modelo de conexión TCP única, statefulness). Pero no esperes que el cambio de versión por sí solo arregle la latencia de flush.

2) Si pongo rsize/wsize a 1 MiB, ¿siempre será más rápido?

No. Ayuda cuando estás bound por overhead. Si estás limitado por latencia de commit/flush, un wsize mayor no arreglará el coste fundamental de “esperar al almacenamiento estable”. Además, si la ruta de red tiene problemas de MTU o pérdidas, operaciones mayores pueden amplificar el dolor.

3) ¿Cuál es la diferencia entre “el servidor es lento” y “el almacenamiento es lento”?

Para escrituras NFS, “el servidor es lento” a menudo significa “el servidor está esperando flush al almacenamiento”. Usa nfsiostat: tiempo de ejecución alto con RTT bajo apunta a espera en el servidor, usualmente almacenamiento.

4) ¿Es async siempre inseguro?

Es inseguro para cargas que asumen que la finalización de escritura implica durabilidad. Para caches, artefactos de build, scratch, puede ser aceptable. No lo haces “seguro” por esperanza; lo haces “riesgo aceptado” escogiendo los datos correctos.

5) ¿Por qué veo llamadas COMMIT incluso en NFSv4?

NFSv4 aún puede requerir comportamiento tipo commit dependiendo de cómo el cliente y el servidor gestionen garantías de almacenamiento estable. Si el servidor reconoce datos como inestables, los clientes emitirán commits para hacerlos estables.

6) Mi app es lenta solo en NFS, pero en disco local va bien. ¿Cuál es la causa más común?

Patrones fsync-intensos. El disco local puede tener cache de escritura rápida y flush de baja latencia, mientras que la ruta de almacenamiento estable del servidor NFS es más lenta. Demuéstralo con strace en la app y iostat en el servidor.

7) ¿Debo deshabilitar atime o usar noatime para acelerar?

Puede reducir escrituras de metadata, pero rara vez es el cuello de botella principal en incidentes de “escrituras a 40 MB/s”. Arregla latencia de commit y dimensionamiento de I/O primero. Luego considera atime si la carga de metadata es un problema medido.

8) Aumenté hilos del servidor NFS y nada cambió. ¿Por qué?

Porque estabas limitado por latencia de almacenamiento, no por hilos. Más hilos solo te dan más espera concurrente. Usa nfsiostat y iostat -x del servidor para ver si estás esperando flush de disco.

9) ¿Pueden las jumbo frames arreglar NFS lento?

A veces, para casos CPU-bound y alto throughput. Pero la inconsistencia de MTU crea pérdidas y retransmisiones que parecen colgas aleatorias. Si no puedes garantizar MTU end-to-end, usa 1500 y céntrate en el limitador real.

Próximos pasos que puedes tomar

NFS lento en Debian 13 rara vez es un misterio. Normalmente es una de tres cosas: coste de sync/commit, rsize/wsize subdimensionados, o pérdida de red escondida detrás de “probablemente está bien”. El truco es negarse a tratar suposiciones como datos.

Haz esto siguiente, en orden:

  1. Captura la negociación de montaje del cliente (nfsstat -m) y las opciones de export del servidor (exportfs -v).
  2. Demuestra la red con una prueba de throughput TCP, y luego deja de hablar de cables.
  3. Ejecuta pruebas emparejadas de escritura directa y lectura. Si las escrituras son lentas y las lecturas rápidas, pivota a evidencia de commit/flush.
  4. Usa nfsstat y nfsiostat para decidir si estás bound por latencia o por overhead.
  5. Arregla el limitador probado: ruta de almacenamiento durable para cargas sync, o dimensionamiento de montaje para cargas bound por overhead.
  6. Si eliges async, hazlo solo para datos que puedan perderse, y documenta esa elección como un adulto.

Si sigues esta disciplina, “NFS está lento” deja de ser una queja vaga y se convierte en un comportamiento del sistema medible con una solución real. Ese es el trabajo.

← Anterior
IA en GPUs: cómo tu tarjeta gráfica se convirtió en una supercomputadora doméstica
Siguiente →
Ubuntu 24.04: endurecimiento SSH que no te dejará fuera — lista pragmática

Deja un comentario