<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Reactive on tsvinc</title>
    <link>https://tsvinc.dev/tags/reactive/</link>
    <description>Recent content in Reactive on tsvinc</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <lastBuildDate>Tue, 07 Jul 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://tsvinc.dev/tags/reactive/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Eight Connections, Zero Available</title>
      <link>https://tsvinc.dev/posts/eight-connections-zero-available/</link>
      <pubDate>Tue, 07 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://tsvinc.dev/posts/eight-connections-zero-available/</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 18 of &lt;a href=&#34;https://tsvinc.dev/series/micronaut-256mb/&#34;&gt;the Micronaut native image series&lt;/a&gt;. Follows &lt;a href=&#34;https://tsvinc.dev/posts/28-apis-same-throughput/&#34;&gt;Part 17&lt;/a&gt;, which found that reactive and imperative produced identical throughput on the 28-API workload. Assumes familiarity with R2DBC and Project Reactor.&lt;/em&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;608-and-599&#34;&gt;60.8 and 59.9&lt;/h2&gt;&#xA;&lt;p&gt;Two frameworks. Different HTTP servers, different dependency trees, different teams maintaining them. Under load, both reactive stacks land at almost the same throughput:&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Framework&lt;/th&gt;&#xA;          &lt;th&gt;Model&lt;/th&gt;&#xA;          &lt;th&gt;Runtime&lt;/th&gt;&#xA;          &lt;th&gt;Mem&lt;/th&gt;&#xA;          &lt;th&gt;rps&lt;/th&gt;&#xA;          &lt;th&gt;p95&lt;/th&gt;&#xA;          &lt;th&gt;k6 errors&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Micronaut&lt;/td&gt;&#xA;          &lt;td&gt;Reactive&lt;/td&gt;&#xA;          &lt;td&gt;Native&lt;/td&gt;&#xA;          &lt;td&gt;128 MB&lt;/td&gt;&#xA;          &lt;td&gt;60.8&lt;/td&gt;&#xA;          &lt;td&gt;15000.6 ms&lt;/td&gt;&#xA;          &lt;td&gt;14.1%&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Spring Boot&lt;/td&gt;&#xA;          &lt;td&gt;Reactive&lt;/td&gt;&#xA;          &lt;td&gt;Native&lt;/td&gt;&#xA;          &lt;td&gt;128 MB&lt;/td&gt;&#xA;          &lt;td&gt;59.9&lt;/td&gt;&#xA;          &lt;td&gt;13548.7 ms&lt;/td&gt;&#xA;          &lt;td&gt;2.0%&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Micronaut&lt;/td&gt;&#xA;          &lt;td&gt;Imperative&lt;/td&gt;&#xA;          &lt;td&gt;Native&lt;/td&gt;&#xA;          &lt;td&gt;256 MB&lt;/td&gt;&#xA;          &lt;td&gt;251.9&lt;/td&gt;&#xA;          &lt;td&gt;2554.8 ms&lt;/td&gt;&#xA;          &lt;td&gt;0%&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Spring Boot&lt;/td&gt;&#xA;          &lt;td&gt;Imperative&lt;/td&gt;&#xA;          &lt;td&gt;Native&lt;/td&gt;&#xA;          &lt;td&gt;256 MB&lt;/td&gt;&#xA;          &lt;td&gt;220.6&lt;/td&gt;&#xA;          &lt;td&gt;2588.9 ms&lt;/td&gt;&#xA;          &lt;td&gt;0%&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Quarkus&lt;/td&gt;&#xA;          &lt;td&gt;Reactive&lt;/td&gt;&#xA;          &lt;td&gt;Native&lt;/td&gt;&#xA;          &lt;td&gt;256 MB&lt;/td&gt;&#xA;          &lt;td&gt;258.7&lt;/td&gt;&#xA;          &lt;td&gt;—&lt;/td&gt;&#xA;          &lt;td&gt;0%&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Imperative hits 220–252. Quarkus reactive hits 259. Micronaut and Spring Boot reactive: 60.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Every Route Registered. Every Request 404&#39;d.</title>
      <link>https://tsvinc.dev/posts/mono-zip-empty-404/</link>
      <pubDate>Mon, 06 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://tsvinc.dev/posts/mono-zip-empty-404/</guid>
      <description>&lt;p&gt;Thirty-three routes registered. Startup log confirms it. The &lt;code&gt;/routes&lt;/code&gt; management endpoint lists every one. And under load, every single request returns 404.&lt;/p&gt;&#xA;&lt;p&gt;This is the reactive Micronaut branch of the &lt;a href=&#34;https://tsvinc.dev/series/micronaut-256mb/&#34;&gt;space observatory benchmark&lt;/a&gt; — 28 upstream API calls, 10 HTTP clients, all pointed at a Go mock server that deliberately injects failures (HTTP 500, 504-after-delay, abrupt connection close). The imperative sibling branch works. The lighter synth variant works. This one: 100% 404.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Three Front Doors, One Engine</title>
      <link>https://tsvinc.dev/posts/three-front-doors-one-engine/</link>
      <pubDate>Sun, 05 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://tsvinc.dev/posts/three-front-doors-one-engine/</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 22 of &lt;a href=&#34;https://tsvinc.dev/series/micronaut-256mb/&#34;&gt;the Micronaut native image series&lt;/a&gt;. Follows &lt;a href=&#34;https://tsvinc.dev/posts/two-ways-mistranslate-retry/&#34;&gt;Part 21&lt;/a&gt;, where a retry was mistranslated two ways. Assumes familiarity with reactive Java (Mono/Flux/Uni) and at least one of the three frameworks.&lt;/em&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-experiment&#34;&gt;The experiment&lt;/h2&gt;&#xA;&lt;p&gt;Same app. Same Java 25. Same GraalVM CE 25. Same Postgres. Same Liquibase schema. Same benchmark harness. Three frameworks.&lt;/p&gt;&#xA;&lt;p&gt;The app fans out to 28 HTTP APIs concurrently, computes risk scores across seismic, atmospheric, and space weather data, and writes one row to Postgres. Six endpoints. The heaviest hits all 28 APIs in four parallel blocks. Built on Micronaut 5.0.0 with reactive R2DBC. Ported to Spring Boot 4.1 and Quarkus 3.33.&lt;/p&gt;</description>
    </item>
    <item>
      <title>1% CPU and 6-Second Latency</title>
      <link>https://tsvinc.dev/posts/quarkus-two-stacked-bottlenecks/</link>
      <pubDate>Sat, 04 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://tsvinc.dev/posts/quarkus-two-stacked-bottlenecks/</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 20 of &lt;a href=&#34;https://tsvinc.dev/series/micronaut-256mb/&#34;&gt;the Micronaut native image series&lt;/a&gt;. Follows &lt;a href=&#34;https://tsvinc.dev/posts/xmx-wont-save-native-image/&#34;&gt;Part 19&lt;/a&gt;, where a native image OOM&amp;rsquo;d at 256MB. Assumes familiarity with reactive Java and connection pooling.&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;Quarkus 3.33. Hibernate Reactive Panache. Mutiny. GraalVM native image in a 256MB container. Six endpoints, each fanning out to 28 mock APIs via &lt;code&gt;Uni.combine&lt;/code&gt;, computing risk scores, persisting one row. The near-identical Spring WebFlux port did 100 req/s.&lt;/p&gt;&#xA;&lt;p&gt;Quarkus did 18.8.&lt;/p&gt;&#xA;&lt;p&gt;Average latency: 6.4 seconds. p95: 16 seconds. 33% of k6 checks failing. And the CPU was at 1%.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Two ways to mistranslate a retry</title>
      <link>https://tsvinc.dev/posts/two-ways-mistranslate-retry/</link>
      <pubDate>Sat, 04 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://tsvinc.dev/posts/two-ways-mistranslate-retry/</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 21 of &lt;a href=&#34;https://tsvinc.dev/series/micronaut-256mb/&#34;&gt;the Micronaut native image series&lt;/a&gt;. Follows &lt;a href=&#34;https://tsvinc.dev/posts/quarkus-two-stacked-bottlenecks/&#34;&gt;Part 20&lt;/a&gt;, where two stacked bottlenecks throttled Quarkus. Assumes familiarity with declarative HTTP clients and retry semantics.&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;Porting 10 HTTP clients from Micronaut to Quarkus and Spring Boot produced two retry bugs. One framework over-retried by ~50%. The other silently never retried HTTP 5xx at all. Both ports passed every functional test. Both shipped code comments claiming parity with the original.&lt;/p&gt;&#xA;&lt;p&gt;The bugs were mirror images, caused by completely different mechanisms, and invisible without a benchmark that injects failures and counts upstream calls.&lt;/p&gt;</description>
    </item>
    <item>
      <title>The Throughput Was Always 115</title>
      <link>https://tsvinc.dev/posts/28-apis-same-throughput/</link>
      <pubDate>Thu, 02 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://tsvinc.dev/posts/28-apis-same-throughput/</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 17 of &lt;a href=&#34;https://tsvinc.dev/series/micronaut-256mb/&#34;&gt;the Micronaut native image series&lt;/a&gt;. Follows &lt;a href=&#34;https://tsvinc.dev/posts/designed-reactive-benchmark/&#34;&gt;Part 16&lt;/a&gt;, which compared reactive vs imperative on a lighter workload. Assumes familiarity with Project Reactor and virtual threads.&lt;/em&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;115&#34;&gt;115&lt;/h2&gt;&#xA;&lt;p&gt;That&amp;rsquo;s the number. Requests per second. For all twelve scenarios.&lt;/p&gt;&#xA;&lt;p&gt;Reactive R2DBC on native: 115. Imperative JDBC on JVM: 115. Virtual threads with loom-carrier: 114.5. Reactive on JVM with 512 MB memory limit: 115.2. Imperative on JVM unlimited: 115.1.&lt;/p&gt;&#xA;&lt;p&gt;Twelve benchmark runs. Two codebases. Three runtime configurations. Six memory/container permutations each. 0% failure rate across the board. And the same number, over and over, like a metronome that doesn&amp;rsquo;t care what&amp;rsquo;s playing.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Designed a Reactive Benchmark. Imperative Won.</title>
      <link>https://tsvinc.dev/posts/designed-reactive-benchmark/</link>
      <pubDate>Tue, 30 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://tsvinc.dev/posts/designed-reactive-benchmark/</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 16 of &lt;a href=&#34;https://tsvinc.dev/series/micronaut-256mb/&#34;&gt;the Micronaut native image series&lt;/a&gt;. Follows &lt;a href=&#34;https://tsvinc.dev/posts/seven-scenarios-one-table/&#34;&gt;Part 15&lt;/a&gt;, which benchmarked the reactive service alone. Assumes familiarity with Project Reactor and virtual threads.&lt;/em&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-hypothesis&#34;&gt;The Hypothesis&lt;/h2&gt;&#xA;&lt;p&gt;The workload was designed to make reactive shine. Six endpoints exercising parallel fan-out, scatter-gather, bounded concurrency, and backpressure. A Go mock server injecting realistic latency jitter — ISS at 10-50ms with 3% failures, NEO at 30-150ms, connection resets via &lt;code&gt;Hijack()&lt;/code&gt; + &lt;code&gt;conn.Close()&lt;/code&gt;. k6 ramping to 200 VUs then sustaining 500 req/s constant arrival. If reactive Micronaut had a home-court advantage anywhere, it was here.&lt;/p&gt;</description>
    </item>
    <item>
      <title>265 req/s on All Three Runtimes (Until You Add a Memory Limit)</title>
      <link>https://tsvinc.dev/posts/seven-scenarios-one-table/</link>
      <pubDate>Sat, 27 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://tsvinc.dev/posts/seven-scenarios-one-table/</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 15 of &lt;a href=&#34;https://tsvinc.dev/series/micronaut-256mb/&#34;&gt;the Micronaut native image series&lt;/a&gt;. Assumes familiarity with reactive Java (Mono/Flux) and container memory limits.&lt;/em&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-draw&#34;&gt;The Draw&lt;/h2&gt;&#xA;&lt;p&gt;Native image, JVM, and virtual threads walk into a benchmark. Same reactive Micronaut service. Same k6 load profile. Same PostgreSQL. Same mock APIs with realistic latency jitter.&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Runtime&lt;/th&gt;&#xA;          &lt;th&gt;Req/s&lt;/th&gt;&#xA;          &lt;th&gt;Errors&lt;/th&gt;&#xA;          &lt;th&gt;RSS (avg)&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Native (unlimited)&lt;/td&gt;&#xA;          &lt;td&gt;264&lt;/td&gt;&#xA;          &lt;td&gt;1.0%&lt;/td&gt;&#xA;          &lt;td&gt;273 MB&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;JVM (unlimited)&lt;/td&gt;&#xA;          &lt;td&gt;266&lt;/td&gt;&#xA;          &lt;td&gt;1.0%&lt;/td&gt;&#xA;          &lt;td&gt;551 MB&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Virtual threads (unlimited)&lt;/td&gt;&#xA;          &lt;td&gt;265&lt;/td&gt;&#xA;          &lt;td&gt;1.0%&lt;/td&gt;&#xA;          &lt;td&gt;543 MB&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;265 req/s. All three. The error rates match the mock server&amp;rsquo;s configured failure rate — the application itself isn&amp;rsquo;t failing. Reactive code runs identically on native image, HotSpot, and loom-carrier Netty.&lt;/p&gt;</description>
    </item>
    <item>
      <title>We Removed Reactor. Nothing Got Slower.</title>
      <link>https://tsvinc.dev/posts/reactor-removal-benchmark/</link>
      <pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://tsvinc.dev/posts/reactor-removal-benchmark/</guid>
      <description>&lt;p&gt;We took the entire reactive stack out. R2DBC, Mono, Flux, Reactor Core, loom-carrier, &lt;code&gt;flatMap&lt;/code&gt; with its carefully-tuned concurrency arguments. Replaced it with JDBC, HikariCP, and blocking method calls. Same endpoints. Same Netty HTTP server. Same PostgreSQL. Same k6 load profile: 10 to 200 VUs, 500 req/s constant arrival rate, seven scenarios each.&lt;/p&gt;&#xA;&lt;p&gt;Throughput didn&amp;rsquo;t move. Latency dropped. Memory dropped. GC pauses dropped 63% at 256 MB.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-scoreboard&#34;&gt;The Scoreboard&lt;/h2&gt;&#xA;&lt;p&gt;This is &lt;a href=&#34;https://tsvinc.dev/series/micronaut-256mb/&#34;&gt;part of the series&lt;/a&gt; benchmarking a Micronaut 5.0.0 app under memory constraints. The reactive version is the one from &lt;a href=&#34;https://tsvinc.dev/posts/flatmap-backpressure/&#34;&gt;Part 10&lt;/a&gt; and &lt;a href=&#34;https://tsvinc.dev/posts/loom-carrier-reactive/&#34;&gt;Part 11&lt;/a&gt; — fully tuned, &lt;code&gt;flatMap&lt;/code&gt; concurrency bounded, R2DBC pool sized to match, loom-carrier enabled for virtual threads. The imperative version: JDBC/HikariCP, &lt;code&gt;THREAD_PER_TASK&lt;/code&gt; executor for virtual threads, same pool size.&lt;/p&gt;</description>
    </item>
    <item>
      <title>flatMap&#39;s Second Argument Saved 1.7 Million Requests</title>
      <link>https://tsvinc.dev/posts/flatmap-backpressure/</link>
      <pubDate>Mon, 15 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://tsvinc.dev/posts/flatmap-backpressure/</guid>
      <description>&lt;p&gt;Midway through a code review of the reactive pipeline, staring at this line in &lt;code&gt;NeoService.java&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;na&#34;&gt;flatMap&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;neo&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;saveAsteroidWithApproaches&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sessionId&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;neo&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s &lt;code&gt;Flux.flatMap()&lt;/code&gt; with one argument. The function. No concurrency limit. Reactor&amp;rsquo;s default: 256 concurrent inner subscriptions.&lt;/p&gt;&#xA;&lt;p&gt;The R2DBC connection pool has 20 connections. Netty has 4 worker threads. &lt;code&gt;flatMap&lt;/code&gt; will cheerfully create 256 concurrent database save operations on 20 connections and 4 threads, queuing 236 of them in memory. Every one holds a &lt;code&gt;NeoAsteroidRecord&lt;/code&gt; entity, a &lt;code&gt;NeoCloseApproach&lt;/code&gt; list, and whatever intermediate state Reactor needs for the inner publisher chain.&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
