NFS v3 versus v4

11

Me pregunto por qué NFS v4 sería mucho más rápido que NFS v3 y si hay algún parámetro en v3 que pueda modificarse.

Monto un sistema de archivos

sudo mount  -o  'rw,bg,hard,nointr,rsize=1048576,wsize=1048576,vers=4'  toto:/test /test

y luego correr

 dd if=/test/file  of=/dev/null bs=1024k

Puedo leer 200-400MB / s, pero cuando cambio la versión vers=3, vuelvo a montar y vuelvo a ejecutar el dd, solo obtengo 90MB / s . El archivo del que estoy leyendo es un archivo en memoria en el servidor NFS. Ambos lados de la conexión son Solaris y tienen NIC de 10 GbE. Evito el almacenamiento en caché del lado del cliente al volver a montar entre todas las pruebas. Solía dtracever en el servidor para medir qué tan rápido se sirven los datos a través de NFS. Para ambos v3 y v4 cambié:

 nfs4_bsize
 nfs3_bsize

de 32K por defecto a 1M (en v4 llegué al máximo a 150MB / s con 32K) He intentado ajustar

  • nfs3_max_threads
  • clnt_max_conns
  • nfs3_async_clusters

para mejorar el rendimiento de v3, pero no vayas.

En la v3, si ejecuto cuatro paralelos dd, el rendimiento disminuye de 90 MB / sa 70-80 MB, lo que me lleva a creer que el problema es un recurso compartido y, de ser así, me pregunto qué es y si puedo aumentarlo. recurso.

Código dtrace para obtener tamaños de ventana:

#!/usr/sbin/dtrace -s
#pragma D option quiet
#pragma D option defaultargs

inline string ADDR=$$1;

dtrace:::BEGIN
{
       TITLE = 10;
       title = 0;
       printf("starting up ...\n");
       self->start = 0;
}

tcp:::send, tcp:::receive
/   self->start == 0  /
{
     walltime[args[1]->cs_cid]= timestamp;
     self->start = 1;
}

tcp:::send, tcp:::receive
/   title == 0  &&
     ( ADDR == NULL || args[3]->tcps_raddr == ADDR  ) /
{
      printf("%4s %15s %6s %6s %6s %8s %8s %8s %8s %8s  %8s %8s %8s  %8s %8s\n",
        "cid",
        "ip",
        "usend"    ,
        "urecd" ,
        "delta"  ,
        "send"  ,
        "recd"  ,
        "ssz"  ,
        "sscal"  ,
        "rsz",
        "rscal",
        "congw",
        "conthr",
        "flags",
        "retran"
      );
      title = TITLE ;
}

tcp:::send
/     ( ADDR == NULL || args[3]->tcps_raddr == ADDR ) /
{
    nfs[args[1]->cs_cid]=1; /* this is an NFS thread */
    this->delta= timestamp-walltime[args[1]->cs_cid];
    walltime[args[1]->cs_cid]=timestamp;
    this->flags="";
    this->flags= strjoin((( args[4]->tcp_flags & TH_FIN ) ? "FIN|" : ""),this->flags);
    this->flags= strjoin((( args[4]->tcp_flags & TH_SYN ) ? "SYN|" : ""),this->flags);
    this->flags= strjoin((( args[4]->tcp_flags & TH_RST ) ? "RST|" : ""),this->flags);
    this->flags= strjoin((( args[4]->tcp_flags & TH_PUSH ) ? "PUSH|" : ""),this->flags);
    this->flags= strjoin((( args[4]->tcp_flags & TH_ACK ) ? "ACK|" : ""),this->flags);
    this->flags= strjoin((( args[4]->tcp_flags & TH_URG ) ? "URG|" : ""),this->flags);
    this->flags= strjoin((( args[4]->tcp_flags & TH_ECE ) ? "ECE|" : ""),this->flags);
    this->flags= strjoin((( args[4]->tcp_flags & TH_CWR ) ? "CWR|" : ""),this->flags);
    this->flags= strjoin((( args[4]->tcp_flags == 0 ) ? "null " : ""),this->flags);
    printf("%5d %14s %6d %6d %6d %8d \ %-8s %8d %6d %8d  %8d %8d %12d %s %d  \n",
        args[1]->cs_cid%1000,
        args[3]->tcps_raddr  ,
        args[3]->tcps_snxt - args[3]->tcps_suna ,
        args[3]->tcps_rnxt - args[3]->tcps_rack,
        this->delta/1000,
        args[2]->ip_plength - args[4]->tcp_offset,
        "",
        args[3]->tcps_swnd,
        args[3]->tcps_snd_ws,
        args[3]->tcps_rwnd,
        args[3]->tcps_rcv_ws,
        args[3]->tcps_cwnd,
        args[3]->tcps_cwnd_ssthresh,
        this->flags,
        args[3]->tcps_retransmit
      );
    this->flags=0;
    title--;
    this->delta=0;
}

tcp:::receive
/ nfs[args[1]->cs_cid] &&  ( ADDR == NULL || args[3]->tcps_raddr == ADDR ) /
{
    this->delta= timestamp-walltime[args[1]->cs_cid];
    walltime[args[1]->cs_cid]=timestamp;
    this->flags="";
    this->flags= strjoin((( args[4]->tcp_flags & TH_FIN ) ? "FIN|" : ""),this->flags);
    this->flags= strjoin((( args[4]->tcp_flags & TH_SYN ) ? "SYN|" : ""),this->flags);
    this->flags= strjoin((( args[4]->tcp_flags & TH_RST ) ? "RST|" : ""),this->flags);
    this->flags= strjoin((( args[4]->tcp_flags & TH_PUSH ) ? "PUSH|" : ""),this->flags);
    this->flags= strjoin((( args[4]->tcp_flags & TH_ACK ) ? "ACK|" : ""),this->flags);
    this->flags= strjoin((( args[4]->tcp_flags & TH_URG ) ? "URG|" : ""),this->flags);
    this->flags= strjoin((( args[4]->tcp_flags & TH_ECE ) ? "ECE|" : ""),this->flags);
    this->flags= strjoin((( args[4]->tcp_flags & TH_CWR ) ? "CWR|" : ""),this->flags);
    this->flags= strjoin((( args[4]->tcp_flags == 0 ) ? "null " : ""),this->flags);
    printf("%5d %14s %6d %6d %6d %8s / %-8d %8d %6d %8d  %8d %8d %12d %s %d  \n",
        args[1]->cs_cid%1000,
        args[3]->tcps_raddr  ,
        args[3]->tcps_snxt - args[3]->tcps_suna ,
        args[3]->tcps_rnxt - args[3]->tcps_rack,
        this->delta/1000,
        "",
        args[2]->ip_plength - args[4]->tcp_offset,
        args[3]->tcps_swnd,
        args[3]->tcps_snd_ws,
        args[3]->tcps_rwnd,
        args[3]->tcps_rcv_ws,
        args[3]->tcps_cwnd,
        args[3]->tcps_cwnd_ssthresh,
        this->flags,
        args[3]->tcps_retransmit
      );
    this->flags=0;
    title--;
    this->delta=0;
}

La salida se ve así (no desde esta situación particular):

cid              ip  usend  urecd  delta     send     recd      ssz    sscal      rsz     rscal    congw   conthr     flags   retran
  320 192.168.100.186    240      0    272      240 \             49232      0  1049800         5  1049800         2896 ACK|PUSH| 0
  320 192.168.100.186    240      0    196          / 68          49232      0  1049800         5  1049800         2896 ACK|PUSH| 0
  320 192.168.100.186      0      0  27445        0 \             49232      0  1049800         5  1049800         2896 ACK| 0
   24 192.168.100.177      0      0 255562          / 52          64060      0    64240         0    91980         2920 ACK|PUSH| 0
   24 192.168.100.177     52      0    301       52 \             64060      0    64240         0    91980         2920 ACK|PUSH| 0

algunos encabezados

usend - unacknowledged send bytes
urecd - unacknowledged received bytes
ssz - send window
rsz - receive window
congw - congestion window

planea tomar snoop's de los dd's sobre v3 y v4 y comparar. Ya lo hice, pero había demasiado tráfico y utilicé un archivo de disco en lugar de un archivo en caché, lo que hizo que la comparación de tiempos no tuviera sentido. Ejecutará otros snoop's con datos en caché y ningún otro tráfico entre cajas. TBD

Además, los chicos de la red dicen que no hay forma de tráfico o limitadores de ancho de banda en las conexiones.

Kyle Hailey
fuente
2
Bueno, para empezar, nfsv4 se ejecuta en tcp de forma predeterminada en lugar de udp.
Phil Hollenback
3
AFAIK, solaris, a diferencia de Linux, monta tcp por defecto incluso en v3. Para las pruebas de v3 también explícitamente "proto = tcp" en algunas de las pruebas, pero tuve el mismo rendimiento en v3 con o sin incluir "proto = tcp"
Kyle Hailey
¿Ya ha habilitado las tramas gigantes en la infraestructura de conmutación y las NIC del servidor?
polinomio
Sí, los marcos gigantes están configurados y verificados. Con dtrace puedo ver los tamaños de los paquetes.
Kyle Hailey
1
En realidad, Linux también se monta de manera predeterminada con tcp
enero

Respuestas:

4

NFS 4.1 (menor 1) está diseñado para ser un protocolo más rápido y más eficiente y se recomienda sobre versiones anteriores, especialmente 4.0.

Esto incluye el almacenamiento en caché del lado del cliente y , aunque no es relevante en este escenario, NFS paralelo (pNFS) . El cambio principal es que el protocolo ahora tiene estado.

http://www.netapp.com/us/communities/tech-ontap/nfsv4-0408.html

Creo que es el protocolo recomendado cuando se usa NetApps, a juzgar por su documentación de rendimiento. La tecnología es similar al bloqueo oportunista de Windows Vista +.

NFSv4 difiere de las versiones anteriores de NFS al permitir que un servidor delegue acciones específicas en un archivo a un cliente para permitir un almacenamiento en caché de datos más agresivo y permitir el almacenamiento en caché del estado de bloqueo. Un servidor cede el control de las actualizaciones de archivos y el estado de bloqueo a un cliente a través de una delegación. Esto reduce la latencia al permitir que el cliente realice varias operaciones y almacene datos localmente. Actualmente existen dos tipos de delegaciones: lectura y escritura. El servidor tiene la capacidad de volver a llamar a una delegación de un cliente en caso de que haya una disputa por un archivo. Una vez que un cliente tiene una delegación, puede realizar operaciones en archivos cuyos datos se han almacenado en caché localmente para evitar la latencia de la red y optimizar la E / S. El almacenamiento en caché más agresivo que resulta de las delegaciones puede ser de gran ayuda en entornos con las siguientes características:

  • Abre y cierra frecuentemente
  • GETATTR frecuentes
  • Bloqueo de archivos
  • Uso compartido de solo lectura
  • Alta latencia
  • Clientes rápidos
  • Servidor muy cargado con muchos clientes.
Steve-o
fuente
Gracias por los punteros en NFS 4.1, aunque AFAIK están en 4.0
Kyle Hailey
1
En realidad, los cambios de almacenamiento en caché del lado del cliente llegaron con 4.0, y pueden ser la mayor diferencia de rendimiento, para escribir, como se puede ver en el extracto de v4 - "NFSv4 ... delegate ... to a client". Acabo de notar que la pregunta era sobre la lectura. No estoy seguro de cuán relevante es la mayor parte de esto para ese caso.
Peter