You add a 10GbE path between a Proxmox VE guest and a
Synology NAS. You point the mount at the new address. Routing confirms the new interface.
mount shows exactly what it should. Throughput does not move one megabyte per second.
NFSv4 recognised the server it was already talking to and quietly kept using the old
connection.
NFSv4.1 and later can use several network paths to the same server. To do that the client works out whether
two addresses are really the same server. It asks with an EXCHANGE_ID call. If the server
answers with an identity the client already has a session with, the client treats the new address as another
path to a known server. Then it reuses the existing transport instead of building a new
one.
That is a sensible feature. It is also why your new fast path carries nothing. You mounted a different IP. The client asked who lives there. The answer was "the server you are already connected to". So it carried on over the old, slow interface.
This is the part that eats an afternoon. All of these look correct while the problem is happening:
$ mount | grep movies
10.0.1.31:/volume1/movies on /mnt/movies type nfs4 (rw,...) # the new address ✓
$ ip route get 10.0.1.31
10.0.1.31 dev ens20 src 10.0.1.44 # the new interface ✓
$ ping -c3 10.0.1.31
3 packets transmitted, 3 received # reachable ✓
Throughput sat unchanged at 105 MB/s. That is suspiciously close to a saturated 1 GbE link. It was the only clue anything was wrong.
Ask the interface how many bytes it moved. It cannot be talked into a wrong answer:
# before
$ cat /sys/class/net/ens20/statistics/rx_bytes
0
# read ~4 GB off the mount
$ dd if=/mnt/movies/some.large.file of=/dev/null bs=1M count=4000
# after
$ cat /sys/class/net/ens20/statistics/rx_bytes
0 # <- the "10G" interface received nothing at all
Zero. The traffic went out the old interface the whole time. Throughput numbers and
mount output are not evidence about which link carries traffic. Counters are. After the
fix the same test showed about 4,028 MB across that interface for a 4 GB read. That is what
actually using the path looks like.
nfsstat -m and ss -tnp | grep :2049 are good supporting checks. The second one
shows the local address of the real TCP connection, which is the thing in dispute.
NFSv3 has no trunking detection. It has no concept of server identity across addresses. The address you ask for is the address it uses. For a fixed storage path where you have already decided which link the traffic takes, that predictability is the feature:
# /etc/fstab
10.0.1.31:/volume1/movies /mnt/movies nfs vers=3,nconnect=8,_netdev 0 0
Write that choice down where the next person finds it. vers=3 looks like somebody was lazy or
never upgraded. It is not an oversight. It is the mechanism. If you move back to v4 later,
re-verify with interface counters. Not with mount output, and not with a
throughput number.
To stay on v4 you have two honest options. Make the two addresses genuinely distinct servers from the client's point of view. Or accept that path selection is the client's call, not yours. There is no "please use this NIC" flag that beats trunking.
Once traffic was genuinely on the fast link, a single NFS connection from the guest still only hit 299 MB/s. The hypervisor itself managed 579 to 633 MB/s over the same link. That gap is virtio single-queue overhead, not the network. One queue, one CPU doing the work.
nconnect tells the client to open several TCP connections for the same mount. That spreads the
work:
| Configuration | Throughput |
|---|---|
| 1G path (before) | 105 MB/s |
| 10G, single connection | 299 MB/s |
10G, nconnect=8 | 553 MB/s |
10G, nconnect=8, live library under load | 397 MB/s |
Nearly double, from one mount option. nconnect needs a modern kernel on both ends. It is per
server-address, so mounts to the same address share the connections. Raising the guest NIC's queue count with
queues=8 on a virtio NIC attacks the same overhead from the other side. It stacks with
nconnect, at the cost of a guest restart.
mount, ping and throughput can all look right while the traffic
is somewhere else.vers=3 with no
reason attached gets "helpfully" upgraded by somebody later. That regression will be silent and slow, not
loud./sys/class/net/<iface>/statistics/rx_bytes. Pin storage
mounts to vers=3 when you need to pick the path yourself. Add nconnect before you
conclude the link is the limit.