From: Sagi Grimberg <sagi@grimberg.me>
To: Nilay Shroff <nilay@linux.ibm.com>, linux-nvme@lists.infradead.org
Cc: hare@suse.de, kbusch@kernel.org, hch@lst.de, dwagner@suse.de,
kanie@linux.alibaba.com, jmeneghi@redhat.com,
randyj@purestorage.com, martin.petersen@oracle.com,
john.g.garry@oracle.com, gjoyce@linux.ibm.com
Subject: Re: [PATCH v8 00/10] nvme-multipath: introduce latency I/O policy
Date: Mon, 31 Aug 2026 00:53:06 +0300 [thread overview]
Message-ID: <2a03f032-3fed-454c-a960-ac332b4c3fed@grimberg.me> (raw)
In-Reply-To: <8ecbc5aa-0a5d-4780-9c34-bf433f5e6221@linux.ibm.com>
On 25/08/2026 8:05, Nilay Shroff wrote:
> On 8/23/26 4:17 AM, Sagi Grimberg wrote:
>>
>>
>> On 15/08/2026 20:34, Nilay Shroff wrote:
>>> Hi,
>>>
>>> This series introduces a new latency I/O policy for NVMe native
>>> multipath. Existing policies such as numa, round-robin, and queue-depth
>>> are static and do not adapt to real-time transport performance. The
>>> numa
>>> selects the path closest to the NUMA node of the current CPU,
>>> optimizing
>>> memory and path locality, but ignores actual path performance. The
>>> round-robin distributes I/O evenly across all paths, providing fairness
>>> but not performance awareness. The queue-depth reacts to instantaneous
>>> queue occupancy, avoiding heavily loaded paths, but does not account
>>> for
>>> actual latency, throughput, or link speed.
>>>
>>> The new latency policy addresses these gaps selecting paths dynamically
>>> based on measured I/O latency for both PCIe and fabrics. Latency is
>>> derived by passively sampling I/O completions. Each path is assigned a
>>> weight proportional to its latency score, and I/Os are then forwarded
>>> accordingly. As condition changes (e.g. latency spikes, bandwidth
>>> differences), path weights are updated, automatically steering traffic
>>> toward better-performing paths.
>>>
>>> Early results show reduced tail latency under mixed workloads and
>>> improved throughput by exploiting higher-speed links more effectively.
>>> For example, with NVMf/TCP using two paths (one throttled with ~30 ms
>>> delay), fio results with random read/write/rw workloads (direct I/O)
>>> showed:
>>
>> TBH, I do not know if this measurement represent any real-life
>> scenario. I do think that occasional packet drops are a real-life
>> scenario, and
>> it would be a worthy use-case to optimize for. Can you perhaps measure
>> how the path selectors compare in this case?
>>
>
> Okay so I have now measured another workload where I simulated packet
> loss/drops
> which are occasional and compared different path selectors. In this
> measurement
> I have a shared NVMe namespace configured which is reachable over two
> tcp paths.
> Now to simulate the occasional packet loss/drop I have configured one
> of the paths
> to experience occasional packet loss/drop as shown below over the
> period of
> 480 seconds:
>
> 0 120 240 360 480
> |-------------|-------------|-------------|-------------|
> |<--5% drop-->|<--no drop-->|<--3% drop-->|<--no drop-->|
>
> As shown above for the first 120 seconds the path experiences 5%
> packet drop,
> for the next 120 seconds path sees no packet drop and again for
> subsequent 120
> seconds path experiences 3% packet drop and for the rest of the
> duration (during
> last 120 seconds) there's no packet drop observed by the path. With
> this simulation,
> I ran fio workload for 480 seconds leveraging direct I/O, bs=4k,
> iodepth=64,
> numjobs=32 and ioengine=io_uring. Shown below is bw observed running
> fio test
> using different I/O policies:
>
> numa round-robin queue-depth latency
> (MiB/s) (MiB/s) (MiB/s) (MiB/s)
> ------- ----------- ----------- ---------
> randread: 1288 1202 1464 1642
> randwrite: 1456 1493 1779 1944
> randrw: R:623 R:594 R:750 R:822
> W:623 W:594 W:750 W:822
>
>>>
>>> numa round-robin queue-depth adaptive
>>> ----------- ----------- ----------- ---------
>>> READ: 50.0 MiB/s 105 MiB/s 230 MiB/s 350 MiB/s
>>> WRITE: 65.9 MiB/s 125 MiB/s 385 MiB/s 446 MiB/s
>>> RW: R:30.6 MiB/s R:56.5 MiB/s R:122 MiB/s R:175 MiB/s
>>> W:30.7 MiB/s W:56.5 MiB/s W:122 MiB/s W:175 MiB/s
>>
>> And I'm assuming there are zero downsides for the normal
>> case?
>>
> For the normal case where all paths are symmetric I saw
> queue-depth, latency and round-robin policies yielding
> nearly same bandwidth. However for numa policy, it depends
> on CPU/numa locality.
>
>>>
>>> This pathcset includes totla 8 patches:
>>> [PATCH 1/10] block: expose blk_stat_{enable,disable}_accounting()
>>> - Make blk_stat APIs available to block drivers.
>>> - Needed for per-path latency measurement.
>>>
>>> [PATCH 2/10] block: record I/O request start time for passthru request
>>> - Record I/O start time for I/O passthru requests.
>>> - This is prep patch which allows measuring I/O completion latency
>>> for passthru requests.
>>>
>>> [PATCH 3/10] block: support nesting for blk-mq flag
>>> QUEUE_FLAG_SAME_FORCE
>>> - Support nesting for QUEUE_FLAG_SAME_FORCE as multiple users
>>> could toggle QUEUE_FLAG_SAME_FORCE.
>>>
>>> [PATCH 4/10] nvme-multipath: pass I/O type to nvme_find_path()
>>> - This is the prep patch which updates nvme_find_path() signature
>>> [PATCH 5/10] nvme-multipath: add latency I/O policy
>>> - Implement path scoring based on latency (EWMA).
>>> - Distribute I/O proportionally to per-path weights.
>>>
>>> [PATCH 6/10] nvme: add generic debugfs support
>>> - Introduce generic debugfs support for NVMe module
>>>
>>> [PATCH 7/10] nvme-multipath: add debugfs attribute latency_ewma_shift
>>> - Adds a debugfs attribute to control ewma shift
>>>
>>> [PATCH 8/10] nvme-multipath: add debugfs attribute
>>> latency_batch_timeout
>>> - Adds a debugfs attribute to control latency batch window interval
>>>
>>> [PATCH 9/10] nvme-multipath: add debugfs attribute latency_stat
>>> - Add “latency_stat” under per-path and head debugfs directories to
>>> expose latency policy state and statistics.
>>>
>>> [PATCH 10/10] nvme-multipath: add documentation for latency I/O policy
>>> - Includes documentation for latency I/O multipath policy.
>>>
>>> LSFMM discussion:
>>> =================
>>> During lsfmm 2026, it was decided to rename this I/O policy from
>>> "adaptive" to "latency". This series reflects that rename.
>>>
>>> The discussion at lsfmm also focused extensively on the latency
>>> measurement model, including whether latency should be tracked
>>> per-CPU or per-NUMA, and whether separate I/O-size buckets should
>>> be maintained for different request sizes.
>>>
>>> After detailed discussion and evaluation of throughput results, the
>>> consensus was to initially measure I/O completion latency on a
>>> per-CPU basis. The available performance data showed that the
>>> per-CPU implementation already provides sufficient averaging across
>>> CPUs while keeping the design relatively simple.
>>>
>>> The use of additional I/O-size buckets did not demonstrate meaningful
>>> throughput improvement in the general case and would introduce extra
>>> complexity into the fast path and accounting logic. As a result, the
>>> consensus was to avoid I/O-size bucketing for now and keep the policy
>>> focused on per-CPU latency measurement.
>>>
>>> If future real-world workloads demonstrate a clear benefit from
>>> I/O-size-aware latency accounting, the policy can be extended later
>>> to support it.
>>>
>>> As ususal, feedback and suggestions are most welcome!
>>
>> Nilay, do we have evidence that round-robin/queue-depth are better
>> for any workload? As a user, I would be very confused with the amount
>> of path selectors I have available and which should I choose.
>
> From my experiments, when the paths are symmetric, both round-robin and
> queue-depth (and for that matter latency) exhibit similar behavior, with
> the workload being distributed roughly equally across the active paths.
>
> When the paths are asymmetric, I found queue-depth to perform better than
> round-robin. Queue-depth tries to steer I/O toward the less-loaded path
> based on the number of in-flight I/Os on each path, whereas round-robin
> continues to distribute I/O evenly across all active paths.
>
> However, queue-depth still has a limitation in this scenario. It uses
> the number of in-flight I/Os as an indirect indication of path
> performance, it does not have a direct signal of the actual I/O
> completion latency. For example, if one path starts experiencing packet
> loss, I/O completion on that path can become significantly slower.
> Queue-depth can react to this as the path accumulates more outstanding
> I/Os, but it can still continue sending I/O to the degraded path as long
> as its queue depth remains comparable to the healthy path. In other
> words, it can reduce the amount of I/O sent to the degraded path, but it
> cannot directly account for how much slower that path has become.
>
> The latency policy uses I/O completion latency as the signal instead.
> When one path becomes degraded, its observed latency increases and its
> path score/weight decreases. Consequently, the policy shifts more I/O
> towards
> the healthy path. This allows the healthy path to sustain a higher queue
> depth while the degraded path receives substantially less I/O, rather
> than trying to maintain a similar queue depth across both paths.
>
> This is also reflected in the packet-loss experiment above. Round-robin
> continues to distribute I/O across both paths, while queue-depth does a
> better job by reacting to the increased queue occupancy of the degraded
> path. The latency policy goes one step further by directly using the
> increased completion latency as a signal and therefore steers more I/O
> toward the healthy path, resulting in higher throughput.
>
> So based on the results I have so far, I would characterize the existing
> policies as follows: round-robin is useful when paths are symmetric and
> equal distribution is desired. The queue-depth is preferable when
> paths are
> asymmetric and queue occupancy provides a useful indication of path
> load and the latency policy is intended for cases where path performance
> can vary dynamically and we want the policy to adapt based on actual
> observed latency.
If you have policies A, B, and C and you say:
- In certain conditions policy C > A, B
- In some conditions C = B > A
- In all other cases C = B = A
This means that C should always be used, and A, B should never be used.
Hence I ask, should we really have this as an option? or should we deprecate
round-robin/queue-depth and have only numa|latency?
I would like to avoid introducing this as a config knob if it is always
behaves
better.
What do others think?
next prev parent reply other threads:[~2026-08-30 21:53 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-15 17:34 [PATCH v8 00/10] nvme-multipath: introduce latency I/O policy Nilay Shroff
2026-08-15 17:34 ` [PATCH v8 01/10] block: expose blk_stat_{enable,disable}_accounting() to drivers Nilay Shroff
2026-08-15 17:34 ` [PATCH v8 02/10] block: record I/O request start time for passthru request Nilay Shroff
2026-08-15 17:34 ` [PATCH v8 03/10] block: support nesting for blk-mq flag QUEUE_FLAG_SAME_FORCE Nilay Shroff
2026-08-18 9:58 ` Hannes Reinecke
2026-08-18 11:59 ` Nilay Shroff
2026-08-19 13:51 ` Hannes Reinecke
2026-08-20 6:15 ` Nilay Shroff
2026-08-15 17:34 ` [PATCH v8 04/10] nvme-multipath: pass I/O type to nvme_find_path() Nilay Shroff
2026-08-15 17:34 ` [PATCH v8 05/10] nvme-multipath: add support for latency I/O policy Nilay Shroff
2026-08-15 17:34 ` [PATCH v8 06/10] nvme: add generic debugfs support Nilay Shroff
2026-08-15 17:34 ` [PATCH v8 07/10] nvme-multipath: add debugfs attribute latency_ewma_shift Nilay Shroff
2026-08-15 17:34 ` [PATCH v8 08/10] nvme-multipath: add debugfs attribute latency_batch_timeout Nilay Shroff
2026-08-15 17:34 ` [PATCH v8 09/10] nvme-multipath: add debugfs attribute latency_stat Nilay Shroff
2026-08-15 17:34 ` [PATCH v8 10/10] nvme-multipath: add documentation for latency I/O policy Nilay Shroff
2026-08-22 22:47 ` [PATCH v8 00/10] nvme-multipath: introduce " Sagi Grimberg
2026-08-25 5:05 ` Nilay Shroff
2026-08-30 21:53 ` Sagi Grimberg [this message]
2026-08-31 6:31 ` Nilay Shroff
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=2a03f032-3fed-454c-a960-ac332b4c3fed@grimberg.me \
--to=sagi@grimberg.me \
--cc=dwagner@suse.de \
--cc=gjoyce@linux.ibm.com \
--cc=hare@suse.de \
--cc=hch@lst.de \
--cc=jmeneghi@redhat.com \
--cc=john.g.garry@oracle.com \
--cc=kanie@linux.alibaba.com \
--cc=kbusch@kernel.org \
--cc=linux-nvme@lists.infradead.org \
--cc=martin.petersen@oracle.com \
--cc=nilay@linux.ibm.com \
--cc=randyj@purestorage.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox