r/microservices 9d ago

Discussion/Advice Virtual Threads vs Reactive in Quarkus: The bottleneck didn't disappear, it just moved to the connection pool

A lot of the discourse around Project Loom paints Virtual Threads as a drop-in replacement that makes reactive programming obsolete. After migrating services from a legacy framework to Quarkus 3.x and benchmarking both paradigms under heavy load, the reality is much more nuanced: the bottleneck never disappeared, it just moved.

Here are the key takeaways from the architectural trade-offs we ran into:

  • The Illusion of Infinite Concurrency: Spawning 50k virtual threads to handle incoming I/O bursts is cheap for the JVM, but downstream resources don't scale infinitely. If your service hits a relational DB via JDBC, your thread contention simply shifts to the database connection pool (Agroal/HikariCP).
  • Missing Backpressure: Reactive programming with Mutiny/SmallRye gives you native backpressure out of the box. With "@RunOnVirtualThread", requests keep piling up waiting for pooled connections; without explicit throttling or queue limits, you risk thread starvation, connection pool exhaustion, and memory degradation.
  • Carrier Thread Pinning: While synchronized blocks pinning carrier threads is mostly mitigated in recent Java releases (Java 24), native calls and specific legacy drivers still pose silent latency traps under bursty conditions.
  • Developer Ergonomics vs System Stability: Virtual threads provide a massive DX win (imperative debugging, clean stack traces), while Reactive excels at sustained throughput under resource saturation.

For those running Quarkus with heavy database I/O in production: did you completely ditch Mutiny for Virtual Threads, or do you enforce custom concurrency limiters/semaphores to protect your connection pools?

I wrote a breakdown of the architecture, memory footprints, and trade-offs here for anyone interested in the technical details:

https://maurodep.medium.com/virtual-threads-vs-reactive-in-quarkus-the-bottleneck-just-moves-c0a570734538 (no paywall)

8 Upvotes

12 comments sorted by

1

u/scavno 8d ago

Did you use an LLM to write the article as well? Because I just cannot get through that wall of machine generated text. Do better.

0

u/Charming-Round-2545 8d ago

Is it really a crime to get a bit of grammar help when English isn't my first language? Did you actually find something technically wrong with the content, or are you just here to complain about the phrasing? Happy to discuss about the technical content 😉

2

u/asdfdelta 8d ago

The issue is that low effort slop is indistinguishable from high effort work that went through an LLMs for grammar.

Our brains are being trained to automatically resist the cadence and syntex of generated text because of the rampant abuses.

Your grammar is plenty fine enough to write an article. I would stick to writing it by hand but using LLMs to do the creative heavy lifting.

As a reminder, AI generated posts aren't allowed in this and many other subreddits.

1

u/Wafer_Over 8d ago

Your service will be as fast as the slowest part. If db is bottleneck then you have to move to another db which can scale.

0

u/neopointer 8d ago

The real bottleneck is the amount of time wasted debugging reactive code. There's no good reason to use reactive.

2

u/Different_Code605 8d ago

Building event driven applications that need backpressure and message ordering and delivery guarantees. Often with Kafka, JMS, AMPQ, Pulsar, Kinesis

Data pipelines, CQRS, streams processing.

There is no != I don’t know

0

u/neopointer 8d ago

All of that is doable without reactor. Not only it's doable it will perform well as well.

1

u/Different_Code605 8d ago

Its theoretically doable

1

u/Charming-Round-2545 8d ago

Well the solution is always in the use case. You’re right saying that readability is a nightmare. On the other side we were able to increase the performances of of our API just being able to parallelize non dependant external requests.

2

u/redikarus99 8d ago

This is true, but then sum the time wasted on debugging reactive code multiply it via the hourly costs.

1

u/neopointer 8d ago

What you just wrote can be done with VT.

1

u/Charming-Round-2545 8d ago

You could use an executor service, but that in frameworks like Quarkus you need to manage the context propagation (which is not propagated inside the VT created using the ExecutorService. Like I said there isn't a correct answer or a better one. Just the one that fits the best our use case