Re: [RFC] knfsd: per-client fair scheduling to prevent single-client starvation

Benjamin Coddington <ben.coddington-F/[email protected]>
Newsgroups gmane.linux.nfs
Message-ID <[email protected]>
On 19 May 2026, at 20:29, Chuck Lever wrote:

> On Tue, May 19, 2026, at 6:02 PM, Benjamin Coddington wrote:
>> On 19 May 2026, at 14:44, Chuck Lever wrote:
>>
>>> On Tue, May 19, 2026, at 5:08 PM, Benjamin Coddington wrote:
>>>> Just to be clear - the issue I'm exploring isn't the same as when all the
>>>> kNFSD threads are slow due to their workload.  This is very much a
>>>> multi-client dynamic where one client (or a group of automated client
>>>> instances) are able to easily starve another simply because they create the
>>>> most connections.
>>>>
>>>> That's different from the other problem that we've discussed a bunch at
>>>> bakeathon and on the list previously.
>>>>
>>>> This is not so much a deadlock issue as it is an issue
>>>> of per-client fairness.  I think this problem is in a different class.
>>>
>>> Does dynamic svc thread creation have any impact?
>>
>> I haven't tested it - I think it would just pin to max-threads for the
>> workload in question.
>
> If the aggregate workload consumes all the threads, then that doesn’t
> sound like xprt scheduling is the bottleneck. But I should look at
> numbers instead of speculating.

Chuck,

Here are the numbers I promised. Short version: under saturation a client's
share of nfsd service tracks its transport count, not its identity -- a
one-connection client gets ~1/(K+1) of the pool when another client drives K
connections.

Method

To measure the dispatch path in isolation I used a small debug patch to nfsd:
a request bearing a "magic" filehandle is answered after a fixed,
caller-specified delay, at the top of fh_verify() before any export/dentry
work -- so the thread is held for a known time D and does zero filesystem
work. With T nfsd threads that fixes the pool ceiling at ~T/D ops/s and makes
nfsd thread-time the only contended resource: no I/O, no caching, no backing
filesystem, just inbound request scheduling.

A userspace load client (single host, loopback) then runs two clients at
once, both offering more than the server can serve:
  - greedy:      K connections
  - interactive: 1 connection
The v3 probe is GETATTR; the v4.1 probe is COMPOUND{SEQUENCE, PUTFH}, with
the greedy client's K connections bound to one session via
BIND_CONN_TO_SESSION. The magic fh carries D in both cases.

Findings

1. Share tracks connection count. NFSv3, 8 nfsd threads, D=10ms, greedy
   connections K swept, interactive fixed at 1:

     K    interactive ops/s   intr share   greedy share
     1         240.6            50.4%         49.6%
     2         216.0            33.8%         66.2%
     4         129.4            20.1%         79.9%
     8          72.2            11.2%         88.8%
    16          38.0             5.9%         94.1%

   interactive is ~1/(K+1), greedy ~K/(K+1). Total stays pinned at the ~648
   ops/s pool ceiling throughout, and every op completes in bounded time --
   nothing is stuck.

2. The interactive client is actively harmed, not just out-numbered. Alone it
   sustains 229 ops/s; with the K=16 greedy client present it gets 38 (6x
   less) and its median latency rises from 16ms to 105ms.

3. Same result for v4.1. With 8 greedy connections bound to a single session,
   the split is 89% / 11%, identical to v3. The session is a clear client
   identity, but the dispatch path ignores it -- the unfairness is in sunrpc,
   below the version.

4. It is structural, not a small-pool artifact. Sweeping the thread count,
   the ratio is unchanged while only the absolute ceiling moves:

     K    share@T=4   share@T=8   share@T=16
     1      50.0%       50.7%       50.5%
     4      20.1%       20.0%       20.2%
    16       5.9%        5.9%        5.9%

   (K=16 total throughput: 321 / 649 / 1299 ops/s at T = 4 / 8 / 16.)

On your earlier point -- "if the aggregate workload consumes all the threads,
that doesn't sound like xprt scheduling is the bottleneck": the pool is
saturated here, that's the precondition. The finding is that the saturated
capacity is divided per-transport, so each client's share is set by how many
sockets it opened. Saturation is the setup; the per-xprt split is the problem.

I kept this to the pure scheduling case -- fixed delay, no filesystem work --
on purpose, because it's the most conservative: a real workload with heavier
ops only saturates the pool sooner and hits the same split at lower connection
counts. pool_stats agrees, with threads-woken collapsing once the pool
saturates.

The harness (synthetic clients plus the debug latency patch) is standalone;
happy to share it or run other configurations -- different fairness units,
per-connection caps, and so on.

Ben
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.