A second, faster path was added between a Proxmox VE guest and a
Synology NAS. The mount was pointed at the new 10GbE address. Routing confirmed the new
interface. mount showed exactly what it should. Throughput didn't move a single megabyte per
second — because NFSv4 recognised the server it was already talking to and quietly kept
using the old connection.
NFSv4.1 and later can use multiple network paths to the same server. To do that, the client has to work
out whether two addresses are in fact the same server — which it does 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 and reuses the existing transport instead
of establishing a new one.
That's a sensible feature. It is also exactly why your new fast path can carry nothing: you mounted a different IP, the client asked who lives there, the answer was "the server you're already connected to", and it carried on using the connection it had — over the old, slow interface.
This is the part that makes it eat 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 was unchanged at 105 MB/s — suspiciously close to what a saturated 1 GbE link gives you, which was the only clue that anything was wrong.
Ask the interface how many bytes it has 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 was going out of the old interface the whole time. Throughput numbers and
mount output are not evidence about which link is carrying traffic; counters are.
After the fix, the same test showed roughly 4,028 MB across that interface for a 4 GB read —
which is what "it's actually using this path" looks like.
nfsstat -m and ss -tnp | grep :2049 are useful supporting checks: the latter
shows you the local address of the actual TCP connection, which is the thing in dispute.
NFSv3 has no trunking detection. It has no concept of server identity across addresses, so the address you ask for is the address it uses. For a fixed storage path where you have decided which link the traffic should take, 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 will find it, because vers=3 looks like
somebody was being lazy or hadn't upgraded. It isn't an oversight — it's the mechanism. If you later move
back to v4 for its other benefits, re-verify with interface counters, not with
mount output or a throughput number.
If you want to stay on v4, then the honest options are to make the two addresses genuinely distinct servers from the client's point of view, or to accept that path selection is the client's decision rather than 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 reached 299 MB/s, while the hypervisor itself managed 579–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, which spreads
that 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 reasonably modern kernel on both
ends and is per server-address, so mounts to the same address share the connections. Raising the guest
NIC's queue count (queues=8 on a virtio NIC) attacks the same overhead from the other side
and stacks with it, at the cost of a guest restart.
mount, ping, even throughput — can
look right while the traffic is elsewhere.vers=3
without a reason attached will be "helpfully" upgraded by someone later, and the regression will be
silent and slow rather than loud./sys/class/net/<iface>/statistics/rx_bytes,
pin storage mounts to vers=3 when you need to choose the path yourself, and add
nconnect before concluding the link is the limit.