NFS v3 kontra v4

11

Zastanawiam się, dlaczego NFS v4 byłby o wiele szybszy niż NFS v3 i czy istnieją jakieś parametry na v3, które można by poprawić.

Montuję system plików

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

a następnie biegnij

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

Mogę odczytać 200-400 MB / s, ale kiedy zmienię wersję na vers=3, ponownie zamontuję i uruchom ponownie dd, dostaję tylko 90 MB / s . Plik, z którego czytam, jest plikiem w pamięci na serwerze NFS. Obie strony połączenia to Solaris i karta sieciowa 10GbE. Unikam buforowania po stronie klienta przez ponowne podłączanie między wszystkimi testami. Kiedyś dtracewidziałem na serwerze, aby zmierzyć, jak szybko dane są podawane przez NFS. Zarówno w wersji v3, jak i v4 zmieniłem:

 nfs4_bsize
 nfs3_bsize

od domyślnego 32K do 1M (na v4 osiągałem maks. 150 MB / s przy 32K) Próbowałem podkręcić

  • nfs3_max_threads
  • clnt_max_conns
  • nfs3_async_clusters

aby poprawić wydajność v3, ale nie ma mowy.

W wersji v3, jeśli uruchomię cztery równoległe dd, przepustowość spada z 90 MB / s do 70-80 MB, co prowadzi mnie do wniosku, że problemem jest jakiś wspólny zasób, a jeśli tak, to zastanawiam się, co to jest i czy mogę go zwiększyć ratunek.

Kod dtrace, aby uzyskać rozmiary okien:

#!/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;
}

Wyjście wygląda następująco (nie z tej konkretnej sytuacji):

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

niektóre nagłówki

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

planuje wziąć snoopa z dd na v3 i v4 i porównać. Zrobiłem już to, ale ruch był zbyt duży i użyłem pliku dysku zamiast pliku z pamięci podręcznej, co sprawiło, że porównywanie czasów nie miało sensu. Będzie uruchamiał inne snoopy z buforowanymi danymi i bez żadnego innego ruchu między urządzeniami. TBD

Ponadto faceci sieci twierdzą, że na połączeniach nie ma kształtowania ruchu ani ograniczeń przepustowości.

Kyle Hailey
źródło
2
Po pierwsze nfsv4 domyślnie działa na tcp zamiast udp.
Phil Hollenback,
3
AFAIK, solaris, inaczej niż Linux, domyślnie montuje tcp nawet na v3. W przypadku testów v3 również wyraźnie „proto = tcp” w niektórych testach, ale miałem taką samą wydajność na v3 z włączeniem „proto = tcp” lub bez niego
Kyle Hailey
Czy masz już włączone duże ramki w infrastrukturze przełączania i kartach sieciowych serwera?
wielomian
tak, duże ramki są konfigurowane i weryfikowane. Dzięki dtrace widzę rozmiary pakietów.
Kyle Hailey
1
W rzeczywistości Linux również domyślnie instaluje się przy pomocy tcp
janneb

Odpowiedzi:

4

NFS 4.1 (mniejszy 1) ma być szybszym i wydajniejszym protokołem i jest zalecany w stosunku do poprzednich wersji, zwłaszcza 4.0.

Obejmuje to buforowanie po stronie klienta i chociaż nie jest istotne w tym scenariuszu, równoległy NFS (pNFS) . Główną zmianą jest to, że protokół jest teraz stanowy.

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

Myślę, że jest to zalecany protokół podczas korzystania z NetApps, sądząc po ich dokumentacji wydajności. Technologia jest podobna do blokowania oportunistycznego w systemie Windows Vista +.

NFSv4 różni się od poprzednich wersji NFS tym, że pozwala serwerowi delegować określone działania na pliku do klienta, aby umożliwić bardziej agresywne buforowanie danych przez klienta i umożliwić buforowanie stanu zablokowania. Serwer przekazuje kontrolę aktualizacji plików i stanu blokowania klientowi za pośrednictwem delegacji. Zmniejsza to opóźnienia, umożliwiając klientowi wykonywanie różnych operacji i buforowanie danych lokalnie. Obecnie istnieją dwa rodzaje delegacji: odczyt i zapis. Serwer ma możliwość oddzwonienia od klienta w przypadku rywalizacji o plik. Gdy klient przechowuje delegację, może wykonywać operacje na plikach, których dane zostały buforowane lokalnie, aby uniknąć opóźnień sieci i zoptymalizować operacje we / wy. Bardziej agresywne buforowanie wynikające z delegacji może być dużą pomocą w środowiskach o następujących cechach:

  • Częste otwieranie i zamykanie
  • Częste GETATTR
  • Blokowanie plików
  • Udostępnianie tylko do odczytu
  • Duże opóźnienie
  • Szybcy klienci
  • Mocno załadowany serwer z wieloma klientami
Steve-o
źródło
Dzięki za wskazówki na NFS 4.1, chociaż AFAIK, że jesteśmy na 4.0
Kyle Hailey
1
W rzeczywistości zmiany w pamięci podręcznej po stronie klienta pojawiły się w wersji 4.0 i mogą być największą różnicą wydajności, jeśli chodzi o pisanie, jak widać z wyciągu v4 - „NFSv4 ... deleguj ... do klienta”. Właśnie zauważyłem, że pytanie dotyczyło czytania. Nie jestem pewien, jak ważna jest większość tego przypadku.
Peter