출처: https://www.ngxstorage.com/improving-nfs-read-performance-overcoming-protocol-stack-challenges/
네트워크 파일 시스템(NFS) 성능을 최적화하는 것은 프로토콜 역학에 대한 섬세한 이해가 필요한 까다로운 작업일 수 있습니다. 가장 큰 과제는 클라이언트와 서버 간 통신에 표준 TCP 소켓을 사용하는 것인데, 이는 프로토콜 스택에 복잡성을 더합니다.
네트워크 링크에 상당한 대역폭 여유가 있어도 NFS 클라이언트 성능은 정체되는 경향이 있습니다. 읽기 작업은 약 2GB/초의 처리량을 달성하는 반면 쓰기 작업은 단일 마운트에서 약 1.2GB/초의 처리량을 달성합니다. 성능 병목 현상은 운영 체제나 CPU 제한과 같은 기존의 의심 사항으로 인해 발생하는 것이 아니라 NFS 프로토콜 스택에 내재된 복잡성으로 인해 발생합니다.
Linux에는 이제 ‘nconnect’라는 새로운 기능이 있는데, 이를 통해 단일 NFS 마운트에 대해 여러 TCP 연결을 설정할 수 있습니다. nconnect를 마운트 옵션으로 설정하면 NFS 클라이언트는 동일한 호스트에 대해 여러 전송 연결을 열 수 있습니다. 이 기능은 Linux 커널 버전 5.3 이상에 포함되어 있습니다.
‘nconnect’를 사용하는 것은 다른 옵션과 같습니다.
mount -t nfs -o rw,nconnect=16 192.168.1.1:/exports/ngxpool/share /mnt
NGX 스토리지를 사용하는 단일 호스트의 성능 비교를 살펴보겠습니다.
테스트 설정에는 NFS 테스트를 위해 스위치에 연결된 40Gbit 이더넷 연결이 있는 단일 클라이언트가 포함됩니다. 이 구성은 간단한 방식으로 시스템 성능과 데이터 교환 기능을 평가하는 것을 목표로 합니다.
사례 1: “nconnect” 없이
root@proxmox:~# fio --rw=read --ioengine=libaio --name=test_nconncect-1 --size=20g --direct=1 --invalidate=1 --fsync_on_close=1 --norandommap --group_reporting --exitall --runtime=60 --time_based --iodepth=128 --numjobs=1 --bs=128k --filename=/mnt/test
test_nconncect-1: (g=0): rw=read, bs=(R) 128KiB-128KiB, (W) 128KiB-128KiB, (T) 128KiB-128KiB, ioengine=libaio, iodepth=128
fio-3.25
Starting 1 process
Jobs: 1 (f=1): [R(1)][100.0%][r=1221MiB/s][r=9767 IOPS][eta 00m:00s]
test_nconncect-1: (groupid=0, jobs=1): err= 0: pid=60345: Fri Feb 2 17:04:57 2024
read: IOPS=11.0k, BW=1496MiB/s (1568MB/s)(87.7GiB/60012msec)
slat (usec): min=12, max=286, avg=24.59, stdev= 9.19
clat (usec): min=2824, max=28405, avg=10670.42, stdev=1509.28
lat (usec): min=2892, max=28439, avg=10695.08, stdev=1509.38
clat percentiles (usec):
| 1.00th=[ 8717], 5.00th=[ 8979], 10.00th=[ 9110], 20.00th=[ 9372],
| 30.00th=[ 9634], 40.00th=[ 9896], 50.00th=[10159], 60.00th=[10552],
| 70.00th=[11207], 80.00th=[12125], 90.00th=[13173], 95.00th=[13304],
| 99.00th=[14615], 99.50th=[15139], 99.90th=[16581], 99.95th=[17433],
| 99.99th=[23462]
bw ( MiB/s): min= 1189, max= 1577, per=100.00%, avg=1495.81, stdev=119.18, samples=120
iops : min= 9518, max=12618, avg=11966.47, stdev=953.42, samples=120
lat (msec) : 4=0.01%, 10=42.86%, 20=57.11%, 50=0.03%
cpu : usr=3.26%, sys=31.89%, ctx=715792, majf=0, minf=4112
IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=100.0%
submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.1%
issued rwts: total=718115,0,0,0 short=0,0,0,0 dropped=0,0,0,0
latency : target=0, window=0, percentile=100.00%, depth=128
실행 상태 그룹 0(모든 작업):
읽기: bw=1496MiB/s(1568MB/s) , 1496MiB/s-1496MiB/s(1568MB/s-1568MB/s), io=87.7GiB(94.1GB), 실행=60012-60012msec
사례 2: “nconnect”를 사용하는 경우
root@proxmox:~# fio --rw=read --ioengine=libaio --name=test_nconncect-16 --size=20g --direct=1 --invalidate=1 --fsync_on_close=1 --norandommap --group_reporting --exitall --runtime=60 --time_based --iodepth=128 --numjobs=2 --bs=128k --filename=/mnt/test
test_nconncect-16: (g=0): rw=read, bs=(R) 128KiB-128KiB, (W) 128KiB-128KiB, (T) 128KiB-128KiB, ioengine=libaio, iodepth=128
...
fio-3.25
Starting 2 processes
Jobs: 2 (f=2): [R(2)][100.0%][r=4707MiB/s][r=37.7k IOPS][eta 00m:00s]
test_nconncect-1: (groupid=0, jobs=2): err= 0: pid=66333: Fri Feb 2 17:45:55 2024
read: IOPS=37.6k, BW=4705MiB/s (4934MB/s)(276GiB/60007msec)
slat (usec): min=9, max=849, avg=25.58, stdev=11.04
clat (usec): min=2452, max=18476, avg=6774.13, stdev=1787.08
lat (usec): min=2470, max=18507, avg=6799.76, stdev=1787.05
clat percentiles (usec):
| 1.00th=[ 5276], 5.00th=[ 5473], 10.00th=[ 5538], 20.00th=[ 5604],
| 30.00th=[ 5735], 40.00th=[ 5800], 50.00th=[ 5866], 60.00th=[ 6063],
| 70.00th=[ 7308], 80.00th=[ 7570], 90.00th=[11076], 95.00th=[11338],
| 99.00th=[11600], 99.50th=[11731], 99.90th=[11863], 99.95th=[11994],
| 99.99th=[12125]
bw ( MiB/s): min= 4683, max= 4712, per=100.00%, avg=4706.41, stdev= 1.68, samples=238
iops : min=37466, max=37700, avg=37651.29, stdev=13.48, samples=238
lat (msec) : 4=0.01%, 10=88.73%, 20=11.26%
cpu : usr=2.67%, sys=50.41%, ctx=994400, majf=0, minf=20609
IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=100.0%
submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.1%
issued rwts: total=2258833,0,0,0 short=0,0,0,0 dropped=0,0,0,0
latency : target=0, window=0, percentile=100.00%, depth=128
실행 상태 그룹 0(모든 작업):
읽기: bw=4705MiB/s(4934MB/s) , 4705MiB/s-4705MiB/s(4934MB/s-4934MB/s), io=276GiB(296GB), 실행=60007-60007msec
결론적으로, Linux 커널 5.3 이상에서 ‘nconnect’ 기능을 구현하는 것은 기능적 솔루션으로 작용하여 단일 NFS 마운트에 대한 여러 TCP 연결을 허용합니다. fio 테스트와 NGX 스토리지 메트릭을 기반으로 한 평가는 ‘nconnect’를 성능 향상을 위한 실용적인 도구로 강조합니다.
참고: 이 기능을 맹목적으로 활성화하지 마세요! NFS 클라이언트 설명서를 읽어 클라이언트 버전에서 nconnect가 지원되는지와 모범 사례를 확인하세요.