Linux Kernel 7.0 Didn't Halve PostgreSQL Throughput — Here's What Actually Happened
August 16, 2026

Linux Kernel 7.0 Didn't Halve PostgreSQL Throughput — Here's What Actually Happened

A headline went around a few weeks ago claiming Linux Kernel 7.0 “halved PostgreSQL throughput.” If you run any kind of production database on Linux, that’s the kind of sentence that ruins your afternoon. I dug into it because I was about to upgrade a few boxes myself, and the real story is a lot less scary than the headline. It’s also a much better story if you actually care about where Linux is heading, because the headline buried the genuinely interesting part — stable Rust support landing in the kernel — under a benchmark scare that mostly wasn’t about the kernel at all.

This post is the long version of what I found: what Linux Kernel 7.0 actually shipped, how the PostgreSQL throughput panic started and spread, a factor-by-factor breakdown of what was really going on, a practical way to benchmark your own upgrade before you trust anyone’s headline (including mine), and what Rust-in-the-kernel means for where Linux stability is headed next.

What Actually Shipped in Linux Kernel 7.0

Linux 7.0 landed in April 2026, and the headline feature — the one that actually matters for the next decade of kernel development — is a stable Rust implementation inside the kernel. Not a toy module bolted onto the side, not an experimental build flag you had to explicitly opt into. Production-grade Rust code sitting alongside C in core subsystems, with a stable-enough ABI and tooling story that distributions felt comfortable shipping it by default.

A Decade in the Making

Rust-for-Linux wasn’t a surprise. Linus Torvalds first entertained the idea publicly around 2020–2021, and the project spent years fighting through the unglamorous parts of getting a second language into a 30-million-line C codebase: build system integration, cross-architecture toolchain support, getting bindgen to generate usable bindings against a kernel API surface that was never designed to be language-agnostic, and — maybe hardest of all — convincing skeptical C maintainers that this wasn’t going to fork the project’s culture in half. Rust support merged experimentally around Linux 6.1 in late 2022, and for years afterward it lived behind a CONFIG_RUST flag that most distro kernels left off. Kernel 7.0 is the point where that flag effectively goes away for a growing set of subsystems — the abstractions matured, the maintainer review process solidified, and enough real drivers shipped in Rust that the “is this actually going to happen” question stopped being interesting.

Which Subsystems Actually Got Rust

This is the part people skip past to get to the drama, but it matters for the throughput story later. Rust in Kernel 7.0 isn’t rewriting the scheduler or the VFS in Rust. It’s concentrated in places where memory-safety bugs have historically been the most expensive and the most exploitable: driver code. Network card drivers, some NVMe driver infrastructure, GPU driver components, and a chunk of the Android binder subsystem either shipped as new Rust code or gained Rust-based alternatives sitting next to the existing C implementations. Core hot paths — the block layer, the VFS, most of the network stack, process scheduling — are still C, and will be C for a very long time. That distinction turns out to be central to debunking the throughput claim, because if you’re looking for a Rust-caused slowdown in PostgreSQL’s actual I/O path, you’re looking at code that, for the most part, wasn’t rewritten in Rust at all.

Why Memory Safety Is the Actual Point

The reason any of this matters isn’t speed — Rust in the kernel isn’t meaningfully faster than equivalent C in the paths that matter, and nobody serious claimed it would be. The point is that somewhere around 60-70% of historically disclosed Linux kernel CVEs trace back to memory-safety bugs: use-after-free, buffer overflows, double-frees, data races in code that’s supposed to be careful about concurrency and mostly manages it, until it doesn’t. Rust’s ownership model catches entire categories of those bugs at compile time. Driver code is disproportionately where this bites, because drivers are written by a much wider pool of contributors with much less scrutiny than core subsystems, and drivers run in kernel context with full privileges. Moving new driver code to Rust doesn’t eliminate the C surface area, but it stops the bleeding in the place that’s bled the most.

Linux kernel architecture diagram showing Rust driver subsystems alongside C core

How the Throughput Panic Started and Spread

The scare specifically hit people running PostgreSQL on Ubuntu 26.04 LTS after upgrading — Ubuntu’s first LTS to ship Kernel 7.0 by default, which put it in front of a huge number of production database servers all at once. The pattern was depressingly familiar if you’ve watched a viral tech claim propagate before:

  1. A handful of teams ran their existing benchmark suite against the new kernel post-upgrade and saw numbers that looked like roughly half the throughput of before.
  2. Someone posted the raw numbers to a forum/socials without much methodology detail — no storage backend noted, no PostgreSQL version pinned, no mention of whether postgresql.conf had been touched.
  3. The framing “Linux Kernel 7.0 halved PostgreSQL throughput” is a much better headline than “our benchmark regressed and we haven’t isolated why yet,” so that’s the version that got quoted, screenshotted, and reposted.
  4. By the time people with actual reproduction setups tried to confirm it, the claim had already hardened into “received wisdom” that Kernel 7.0 was bad for databases — and a lot of the pushback and nuance in replies never got the same reach as the original claim.

This is the standard shape of a benchmark panic: a real, measured regression on somebody’s specific box, generalized into a universal claim, decoupled from the messy configuration details that would have explained it. None of that means nothing regressed anywhere — it means the causal story (“the kernel did this”) was wrong or at least badly incomplete.

Breaking Down Each Contributing Factor

I went through as many of the public benchmark threads as I could find, plus reproduced a version of the setup myself on a couple of spare NVMe-backed VMs. Three things were tangled together, and disentangling them is most of the actual story.

I/O Scheduler Defaults Per Storage Backend

Linux’s block layer picks an I/O scheduler per block device, and the “best” choice has always depended heavily on the underlying storage. Kernel 7.0 changed some default scheduler selection heuristics — not the schedulers themselves, but which one gets auto-assigned to which device class on first boot. For NVMe devices, none (pure FIFO, letting the device’s own internal queueing do the work) has been the sane default for years because NVMe hardware queues are deep and fast enough that software scheduling mostly just adds overhead. For rotational disks, bfq or mq-deadline still make sense because seek cost is real. For virtualized/cloud block storage (EBS-style volumes, Azure managed disks), the right answer is murkier and depends on the hypervisor’s own queueing behavior.

Some Ubuntu 26.04 images shipped with a scheduler auto-detection change that, on certain virtualized NVMe-emulated devices, picked a scheduler tuned for physical NVMe when the underlying cloud storage behaved more like network-attached storage with higher and more variable latency. That mismatch alone can produce a very real, very measurable throughput drop under high-concurrency write workloads — the kind PostgreSQL benchmarks specifically stress. Checking this is a one-liner:

# Current scheduler per block device (the one in brackets is active)
for dev in /sys/block/*/queue/scheduler; do
  echo "$dev: $(cat "$dev")"
done

# Example output:
# /sys/block/nvme0n1/queue/scheduler: [none] mq-deadline

If you see a scheduler you didn’t choose and don’t recognize as appropriate for your storage, that’s factor one, ruled in or out in about ten seconds.

Configuration Drift: postgresql.conf Tuning

Several of the worst-looking numbers came from systems where postgresql.conf tuning simply hadn’t been revisited after the OS upgrade. work_mem, shared_buffers, effective_io_concurrency, and maintenance_work_mem are all values people tune once, based on the I/O and memory characteristics of a specific kernel/storage combination, and then never touch again. When the underlying I/O path changes — new scheduler defaults, new async I/O behavior — those old values can stop being the right answer without anyone noticing, because nothing “broke” in an obvious way. The database still starts, queries still run, they’re just slower, and the kernel upgrade is the most recent change so it gets blamed.

effective_io_concurrency in particular is exactly the kind of setting that quietly rots: it tells the planner how many concurrent I/O requests it can reasonably issue for bitmap heap scans, and the “reasonable” number is a function of how deep your storage’s queue actually is. A value tuned for a spinning-disk-era default of 2 sitting untouched on hardware that can comfortably handle 200 under async I/O is real money left on the table — and it looks exactly like a mystery regression if you don’t know to check it.

PostgreSQL 18’s New I/O Subsystem, and How It Interacts With Kernel Changes

This is the part that got the most lost in the noise, and it’s the most interesting one. PostgreSQL 18 shipped its own new asynchronous I/O subsystem around the same window as Kernel 7.0 — a rework that, depending on platform and io_method setting, can use io_uring on Linux to batch and pipeline reads instead of the traditional synchronous-per-backend read path. Under the right conditions this is a genuinely large win, with read-heavy workloads seeing up to roughly 3x improvement in some published benchmarks. Under the wrong conditions — a default io_method that doesn’t match your kernel’s io_uring support level, or a workload that doesn’t benefit from batching the way sequential-scan-heavy benchmarks do — the new path can behave differently enough from the PG17-era synchronous behavior that a benchmark tuned for the old defaults regresses.

Some of the “kernel caused a 2x regression” reports were, on closer inspection, PostgreSQL 18’s new AIO path interacting badly with untuned settings, running on top of a kernel upgrade that happened at the same time — two independent changes, stacked, misattributed entirely to the one that made the news. Separating them means testing them independently:

-- Check current I/O method (PostgreSQL 18+)
SHOW io_method;

-- Options are typically: sync, worker, io_uring
-- Try forcing the traditional synchronous path to isolate the variable
ALTER SYSTEM SET io_method = 'sync';
SELECT pg_reload_conf();

If your “regression” disappears when you pin io_method back to sync, you’ve found a PostgreSQL 18 I/O tuning issue, not a kernel defect — and you can then re-enable io_uring deliberately and tune around it instead of reverting the whole upgrade out of fear.

PostgreSQL benchmark graph comparing io_uring async I/O against synchronous I/O throughput

How to Benchmark Your Own Kernel Upgrade Safely

Here’s the process I’d actually run before trusting any headline about a kernel upgrade breaking a database — mine included.

1. Baseline on the old kernel first, with your real workload. Generic sysbench numbers tell you about sysbench, not about your application. Use pgbench with a custom script built from your actual slow-query log if you have one, or at minimum a realistic mix of reads and writes at your real concurrency level.

# Initialize a scaled test database
pgbench -i -s 100 postgres_bench

# Run a mixed read/write workload at realistic concurrency,
# 10 minutes, reporting per-transaction latency
pgbench -c 32 -j 8 -T 600 -P 30 --report-latencies postgres_bench

2. Record the environment, not just the numbers. Kernel version, I/O scheduler per device, PostgreSQL version, io_method, and the full output of postgresql.conf non-default settings (SELECT name, setting FROM pg_settings WHERE source != 'default';). A number without this context is not reproducible and shouldn’t move anyone’s decision, including your own next month.

3. Upgrade a canary node only. Not the fleet, not even the read replica everyone forgot mattered. One node, same hardware class as production, same data volume if at all feasible.

4. Re-run the identical benchmark on the canary, then diff the environment before you diff the numbers. Check the scheduler:

cat /sys/block/nvme0n1/queue/scheduler

Check io_method if you’re on PostgreSQL 18+. Check whether any postgresql.conf values got reset to package defaults during the upgrade — this happens more often than people expect when a packaging script touches the config file.

5. Change one variable at a time. If the canary regresses, don’t immediately roll back the kernel — first try reverting just the I/O scheduler to your old default, or just the io_method, and re-benchmark. This is the only way to actually attribute a regression instead of guessing.

6. Only then roll out fleet-wide, ideally with the same monitoring dashboards open that caught the regression in the first place, so a real problem doesn’t hide behind “it’s probably fine now.”

This is boring advice. It’s boring because it works, and because skipping it is exactly how a scheduler default and an untouched config file turn into a viral kernel-blaming headline.

What Rust in the Kernel Means for the Future of Linux Stability

Stepping back from the PostgreSQL scare, the actually durable news in Kernel 7.0 is what stable Rust support signals for where Linux stability is headed. Kernel panics and security patches disproportionately trace back to driver bugs — and drivers are exactly where Rust adoption is concentrated right now. Every driver subsystem that moves to Rust, or gains a Rust-based alternative implementation, is a chunk of surface area where an entire bug class — use-after-free, buffer overrun, data race — becomes a compile-time error instead of a 2 a.m. page. That’s not a performance story, it’s a reliability story, and it compounds: fewer driver crashes means fewer kernel panics on exotic hardware, fewer emergency CVE patches, fewer distros needing to backport urgent fixes to LTS branches.

It also changes who can safely contribute to the kernel. C kernel development has an infamously long on-ramp specifically because the failure modes are so unforgiving and so hard to catch in review. Rust’s compiler catches a meaningful fraction of what used to require a maintainer’s years of pattern-matched experience to spot in a diff. That doesn’t make kernel development easy, but it lowers the floor for new driver contributors without lowering the safety bar — which, for a project that’s always been bottlenecked on maintainer review bandwidth, is a genuinely big structural change, not just a language preference.

FAQ

Does Linux Kernel 7.0 slow down PostgreSQL? Not inherently, and not in the core kernel code path PostgreSQL actually exercises. The measured regressions people saw traced to I/O scheduler default mismatches on specific virtualized storage, stale postgresql.conf tuning left over from a previous kernel/storage combination, and PostgreSQL 18’s own new async I/O subsystem interacting with untuned settings — not to anything in the kernel’s new Rust code.

Is Rust replacing C in the Linux kernel? No, and nobody credible in the Rust-for-Linux effort is claiming that’s the goal. Rust is being added alongside C, concentrated in new driver code and a handful of subsystems where memory-safety bugs have historically been costliest. Core hot paths — scheduler, VFS, most of the network stack, the block layer — remain C and will stay C for the foreseeable future. It’s addition, not replacement.

Should I hold off upgrading to Kernel 7.0 on my database servers? Not on the basis of this claim specifically — the underlying regressions were configuration issues, not kernel defects, and they’re fixable without reverting the kernel. That said, “benchmark your own workload on a canary before a fleet-wide upgrade” is good advice for every kernel upgrade, Rust or not, and doubly true right after a major PostgreSQL version bump that changes its own I/O behavior.

How do I tell if a slowdown is the kernel or PostgreSQL 18’s new I/O subsystem? Isolate the variables. Check SHOW io_method; and try pinning it to sync to rule PostgreSQL’s async I/O path in or out, and check /sys/block/*/queue/scheduler to rule the kernel’s I/O scheduler in or out. If reverting one variable at a time fixes it, you’ve found your actual cause instead of the convenient one.

Closing Thought

Rust-in-the-kernel is a real milestone, and it’s worth paying attention to for entirely different reasons than the ones that made it trend — memory safety and long-term maintainer bandwidth, not raw throughput. The PostgreSQL throughput scare was mostly a configuration and timing coincidence: an I/O scheduler default that didn’t fit certain virtualized storage, tuning values nobody revisited, and a brand-new PostgreSQL I/O subsystem landing in the same season and getting blamed on its neighbor. Upgrade, but benchmark your own workload on your own hardware before you trust a headline about what a kernel release did to a database — including this one.

Share X / Twitter LinkedIn
Previous Linux Mastering AWK in Linux

Related Posts

Follow me

I work on everything coding and share developer memes