mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Naman Jain <namjain@linux.microsoft.com>
To: Luigi Rizzo <lrizzo@google.com>
Cc: Michael Kelley <mhklinux@outlook.com>,
	"linux-nvme@lists.infradead.org" <linux-nvme@lists.infradead.org>,
	Keith Busch <kbusch@kernel.org>, Jens Axboe <axboe@kernel.dk>,
	Christoph Hellwig <hch@lst.de>, Sagi Grimberg <sagi@grimberg.me>,
	"linux-hyperv@vger.kernel.org" <linux-hyperv@vger.kernel.org>,
	"linux-kernel@vger.kernel.org" <linux-kernel@vger.kernel.org>,
	changfengnan@bytedance.com
Subject: Re: [RFC PATCH 0/3] nvme-pci: yield completions under scheduler pressure
Date: Tue, 13 Oct 2026 11:51:50 +0530	[thread overview]
Message-ID: <1d12109d-2f47-4ca5-af7b-66c032a379a7@linux.microsoft.com> (raw)
In-Reply-To: <CAMOZA0JrxakyxxYPu72hk-jWQpW33-0BGFkZX-n_3D+28eDuGA@mail.gmail.com>



On 10/12/2026 2:47 PM, Luigi Rizzo wrote:
> On Mon, Oct 12, 2026 at 10:19 AM Naman Jain <namjain@linux.microsoft.com> wrote:
>>
>>
>>
>> On 10/9/2026 8:28 PM, Luigi Rizzo wrote:
>>> On Fri, Oct 9, 2026 at 1:27 PM Luigi Rizzo <lrizzo@google.com> wrote:
>>>>
>>>> On Fri, Oct 9, 2026 at 12:17 PM Naman Jain <namjain@linux.microsoft.com> wrote:
>>>>>
>>>>>
>>>>>
>>>>> On 10/9/2026 12:21 PM, Naman Jain wrote:
>>>>>>
>>>>>>
>>>>>> On 10/9/2026 11:24 AM, Michael Kelley wrote:
>>>>>>> From: Naman Jain <namjain@linux.microsoft.com> Sent: Thursday, October
>>>>>>> 8, 2026 10:06 PM
>>>>>>>>
>>>>>>>> On systems with several fast NVMe controllers, completion interrupts can
>>>>>>>> keep returning to the same CPUs faster than scheduled work can run. Each
>>>>>>>> handler may drain only a small number of completions, but the combined
>>>>>>>> interrupt stream can still prevent scheduler and watchdog progress.
>>>>>>>
>>>>>>> See this recent proposal [1] that sounds like it is addressing the
>>>>>>> same or a
>>>>>>> similar issue. And there is this [2] more global approach. It's
>>>>>>> worthwhile to read
>>>>>>> through the discussion on both threads. I haven't done a detailed
>>>>>>> comparison
>>>>>>> of either vs. your proposal.
>>>>>>>
>>>>>>> Michael
>>>>>>>
>>>>>>> [1] https://lore.kernel.org/linux-nvme/20260818033846.53790-1-
>>>>>>> changfengnan@bytedance.com/
>>>>>>> [2] https://lore.kernel.org/lkml/20260819124341.4185621-1-
>>>>>>> lrizzo@google.com/
>>>>>>>
>>>>>>
>>>>>>
>>>>>> Thanks for sharing these Michael. I'll check more on these, and try it out.
>>>>>>
>>>>>> Regards,
>>>>>> Naman
>>>>>
>>>>> ++ authors of these two series, for awareness and if there is some
>>>>> configuration in their patches I should be trying to fix these lockup
>>>>> issues.
>>>>
>>
>> Hi Luigi,
>> Thanks for looking into it.
> ...
>> GSIM was creating gaps for individual sources, but the other NVMe
>> sources sharing that CPU were filling most of them.
>>
>> I traced irq_handler_entry/irq_handler_exit with trace-cmd for the four
>> NVMe queue vectors routed to CPU0. The test used GSIM fixed at 10 us
>> with all 56 data-NVMe I/O vectors opted in.
>>
>> Without timer push, I recorded 37,218 NVMe handler invocations in three
>> seconds. The median handler duration was about 2 us, but the median gap
>> between NVMe handlers was only 4 us. The p99 gap was 872 us.
> 
> (medians are not a good metric in an environment where there might be
> a lot of idle runs because a previous, delayed run already consumed all CQs.
> You may want to look at the full distribution, I expect to see a step at some
> point, the exact percentile depends on how many queues share the same CPU)
> 
> Just a clarification, the pattern I expect on CPU0 is something like this
> NVME0 NVME1 NVME2 NVME3 TIMER_PUSH NVME0 ...
> what is "mediam gap between handlers", is it the time between NVME0 and
> the next NVME0, or between any two NVMEx and NVMEy ?

The earlier term was ambiguous. I now use two separate definitions:

   any-source gap:
     previous NVMe handler exit to the next NVMe handler entry for any of
     the four CPU0 NVMe vectors, i.e. NVMEx -> NVMEy

   same-source recurrence:
     previous exit to the next entry of the same vector, e.g.
     NVME0 -> next NVME0

The earlier 4-us push-off and 517-us push-on medians were the any-source 
gaps, not same-source recurrence.

I reran the tests, and the distribution confirms two effects:

1. Without push, many handler runs remain very short, but different NVMe 
sources can run almost back-to-back.

2. With push, the CPU gets much larger gaps, but CQ work accumulates and 
the replayed NVMe hardirq commonly runs for several milliseconds.

The multi source pattern you mentioned, is correct.
NVME0 NVME1 NVME2 NVME3 TIMER_PUSH NVME0 ...

> 
>> With timer push enabled, the same three-second CPU0 trace contained only
>> 531 NVMe handler invocations, and the median gap increased from 4 us to
>> 517 us. This confirms your diagnosis, adding one delay per additional
>> source restores a real CPU-wide gap.
>>
>> The full QD256 workload then completed without a soft lockup or kernel
>> taint. There was a significant cost, though. The delayed completions
>> accumulated into much larger CQ batches:
>>
>>     - median NVMe handler duration increased from 2 us to 3.1 ms
>>     - p99 handler duration increased from 106 us to 7.1 ms
>>
>> For comparison on the same workload:
>>
>>     fixed 10-us GSIM + timer push: 1.906M IOPS
>>     adaptive GSIM + timer push:    1.712M IOPS
>>     threaded NVMe IRQs:            3.165M IOPS
>>     NVMe IRQ-poll fallback:        4.986M IOPS
>>
>> So the timer-push idea does prevent the lockup, and the trace confirms
>> the multi-source gap issue. On this NVMe topology, however, delaying all
>> sources creates very large completion batches, and the NVMe handler
>> still drains each batch without a bound in hardirq context. That causes
>> the large throughput and tail-latency cost above.
> 
> Yes, without touching the nvme handler to limit the number
> of entries processed in each run, there is only so much
> a generic solution like GSIM can do.
> This definitely warrants some NVME-specific changes
> such as those proposed by Fengnan, and they are in line
> with what NAPI already does by using a bounded batch
> and rescheduling the handler.

Agreed. Timer-push GSIM changes when the NVMe hardirq runs, but it does 
not move NVMe completion processing out of hardirq or bound the amount 
drained after replay, which is required to avoid lockups.

> 
> I am a bit surprised by your numbers regarding the differences with
> and without timer push, so I would like to understand
> the numbers better.
> (As an aside, 10us is really a bit too short for GSIM delay, because
> almost 50% of the time gets eaten by the context switching.
> At least for an initial assessment I would start testing with a fixed 30-50us)

The push-off/push-on IRQ trace was an earlier short experiment with 
fixed 10-us GSIM:

                                   push off       push on
   short-run IOPS                    2.164M         1.944M
   CPU0 handlers / 3 seconds        37,219            531
   eventual full-test lockup            yes             no

Those short-run IOPS are not completed 90-second results, as all the 
long duration timer push off experiments eventually soft-locked.

The latest controlled three-run matrix used timer_push=1 for every GSIM row.

> 
> Can you clarify the following, specifically for the "full QD256 workload"
> - how many vectors are sharing one interrupt CPU (the previous test says 4),
>    and how many interrupt CPUs are used in total ?

There are four data NVMe controllers, each with 14 interrupt-driven I/O 
queues.

Corresponding queue vectors from the four controllers are routed to the 
same CPU. Therefore:

   vectors per affected CPU:       4
   affected interrupt CPUs:       14
   total data-NVMe I/O vectors:   56

The affected CPUs are:

   0, 10, 19, 28, 37, 46, 55,
   64, 74, 83, 92, 101, 110, 119

The matched unmodified control reproduced soft lockups on exactly those 
14 CPUs. The first watchdog report occurred 25 seconds after fio launch.

The production fio command is equivalent to:

   --filename=dev1:dev2:dev3:dev4
   --numjobs=128
   --iodepth=256
   --group_reporting

so the potential total is:
   128 x 256 = 32,768 requests

The NVMe hardware queue depth is 1,024, with 1,023 usable entries.

I tested fixed 30 us and 50 us as you suggested.

Every passing mode was tested three times, with a fresh boot before each 
sample. The workload uses a 10-second ramp followed by a 90-second 
measured runtime.

NVMe fallback: this series
threaded NVMe IRQs: Default kernel with nvme.use_threaded_interrupts=1

High-load results are medians of three runs, you can find complete 
minimum-to-maximum range:

                               IOPS median       complete range
   NVMe fallback                 5.004M          5.002-5.004M
   threaded NVMe IRQs            3.140M          3.100-3.169M
   GSIM fixed 10 us + push       1.962M          1.935-2.025M
   GSIM fixed 30 us + push       1.790M          1.775-1.822M
   GSIM fixed 50 us + push       1.744M          1.736-1.746M

Completion latency:
                               mean        p99          p99.9
   NVMe fallback              6.533 ms    14.352 ms     16.908 ms
   threaded NVMe IRQs        10.421 ms    22.151 ms     24.510 ms
   GSIM fixed 10 us + push   16.570 ms    38.011 ms    270.533 ms
   GSIM fixed 30 us + push   18.283 ms    40.108 ms    492.831 ms
   GSIM fixed 50 us + push   18.743 ms    40.632 ms    541.065 ms

Relative to the fallback median, threaded IRQs are 37.24% lower, while 
fixed 10/30/50-us timer-push GSIM is 60.78%/64.22%/65.15% lower.

QD1 results were:
                               IOPS median       complete range
   unmodified hardirq           25,518          one run
   NVMe fallback                25,520          23,212-25,552
   threaded NVMe IRQs           22,253          22,035-22,466
   GSIM fixed 10 us + push      21,080          21,032-21,931
   GSIM fixed 30 us + push       5,496           5,259-5,608
   GSIM fixed 50 us + push       5,492           5,224-5,589

The 30-us and 50-us settings on this VM, do not improve high-load 
performance relative to fixed 10 us and they reduce QD1 performance 
substantially.

I also traced fixed 30 us plus timer push on CPU0:

   handler count:                    550 in three seconds
   handler busy fraction:            53.05%
   handler duration p50:             2.888 ms
   handler duration p90:             5.187 ms
   handler duration p99:             6.655 ms
   handler duration p99.9:           7.461 ms
   handler duration maximum:         7.805 ms
   any-source gap p50:               439 us
   any-source gap p90:               8.073 ms
   any-source gap p99:               13.319 ms
   same-source recurrence p50:       18.782 ms
   same-source recurrence p99:       35.551 ms

> - From my experience (different environments), I have measured
>    < 1 queue - 1 interrupt CPU - 1 user thread > saturating at 400-600KIOPS
>    (I don't remember whether the bottleneck is the user or interrupt CPU).
>    At those rates, 256 outstanding entries may take some 500us to be processed.
>    so it seems strange that the median NVME handler (per queue?) takes 3ms.
> - these times will be scaled down if each drive uses many queues so the
>    average throughput per queue will be lower, and such to keep the total
>    rate for each interrupt CPU below its 400K cap.
> 
> My expectations is something along these lines:
> - the outstanding CQ queue cannot exceed QD ie 256 in your test
> - worst case each handler should run at most some 500us
> - four queues will create a total QD=1024 so the interrupt CPU
>    will take at least 2ms to drain them
> 

At approximately 5M aggregate IOPS, 56 queues (4 devices), an 
even-distribution estimate is:

   5,000,000 / 56
     = approximately 89,286 completions/s/queue

or:
   1,000,000 us / 89,286
     = approximately 11.2 us/completion/queue

The measured queue occupancy is more important here i.e. despite the 
lower average throughput per hardware queue, timer-push batching allowed 
all four traced queues to reach 1,023 outstanding commands.

I also ran a separate experiment matching a true total outstanding depth 
of 256:

   four jobs
   one job per disk
   iodepth=64 per job
   all jobs pinned to CPU0

Therefore: 4 x 64 = 256 total potential outstanding requests

With fixed 30-us GSIM:

                               push off       push on
   total IOPS                    15.52K         15.22K
   handler duration p50           3 us           3 us
   handler duration p99           5 us           5 us
   any-source gap p50           267 us         270 us

Timer push had negligible effect in this sparse case because the 
workload already had large gaps.

Fixed 30-us moderation heavily throttled that control, so I repeated it
without GSIM moderation. It reached 263.6K IOPS total. Handler duration was:

   p50:       1 us
   p99:       4 us
   p99.9:     5 us
   maximum: 106 us

Therefore, your expectation of short handlers applies when total 
outstanding depth really is 256. The millisecond handlers in the 
production reproducer come from the much deeper 128-job workload and the 
accumulation caused by timer push.

Regards,
Naman

  reply	other threads:[~2026-10-13  6:22 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-09  5:05 Naman Jain
2026-10-09  5:05 ` [RFC PATCH 1/3] nvme-pci: select IRQ_POLL Naman Jain
2026-10-09  5:05 ` [RFC PATCH 2/3] nvme-pci: make completion queue polling softirq-safe Naman Jain
2026-10-09  5:05 ` [RFC PATCH 3/3] nvme-pci: defer completions when rescheduling is needed Naman Jain
2026-10-09 15:30   ` Keith Busch
2026-10-12  6:47     ` Naman Jain
2026-10-12 14:28       ` Keith Busch
2026-10-12 16:24         ` Naman Jain
2026-10-09  5:54 ` [RFC PATCH 0/3] nvme-pci: yield completions under scheduler pressure Michael Kelley
2026-10-09  6:51   ` Naman Jain
2026-10-09 10:16     ` Naman Jain
2026-10-09 11:27       ` Luigi Rizzo
2026-10-09 14:58         ` Luigi Rizzo
2026-10-12  8:19           ` Naman Jain
2026-10-12  9:17             ` Luigi Rizzo
2026-10-13  6:21               ` Naman Jain [this message]
2026-10-09 11:36       ` Fengnan
2026-10-12  6:48         ` Naman Jain
2026-10-12  7:00           ` Fengnan
2026-10-12  8:51             ` Naman Jain

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=1d12109d-2f47-4ca5-af7b-66c032a379a7@linux.microsoft.com \
    --to=namjain@linux.microsoft.com \
    --cc=axboe@kernel.dk \
    --cc=changfengnan@bytedance.com \
    --cc=hch@lst.de \
    --cc=kbusch@kernel.org \
    --cc=linux-hyperv@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-nvme@lists.infradead.org \
    --cc=lrizzo@google.com \
    --cc=mhklinux@outlook.com \
    --cc=sagi@grimberg.me \
    /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

all inboxes | Powered by JetHome®