Introduction

This article tests three distinct Spring Boot application architectures:

  1. Classic Servlet App (Servlet)
  2. Reactive
  3. RSocket

Motivation

HTTP is a widely used transport for APIs, and it has pros and cons. It’s old and has big performance issues, but it is still widely used because of its ease of use.

Assumptions

  • Basic applications without best practices implementation
  • Tests may not be identical in every aspect
  • JVM parameters remain unoptimized for each application type

Requirements

  • Accept messages, receive lists/streams, save to database with empty response
  • List messages by author with return of all messages
  • Identical data feeds across all tests
  • Measure requests per second (RPS)

Message Format:

{
    "id": "UUID",
    "text": "String",
    "author": "String",
    "createdAt": "ZonedDateTime"
}

Test Scenario

Create Messages

  • Iterate concurrent threads (4, 8, 16, 64)
  • Test message sizes (1, 10, 100, 1000) in single request
  • Run test, clean database, sleep, repeat

Get Messages

  • Iterate concurrent threads (4, 8, 16, 64)
  • Generate test messages
  • Run test, clean data, sleep, repeat

Execution Environment

  • Java Version: OpenJDK 21.0.5
  • Processor: Intel Core i9-14900HX
  • Memory: 32540280 kB

Performance Results

Send Messages (RPS)

Threads Servlet Reactive RSocket
4 3691.5 18668.4 20862.3
8 5088.7 15821.7 20362.9
16 4497.7 13346.9 21043.3
64 4501 12443.3 20881.3

Get Messages (RPS)

Threads Servlet Reactive RSocket
4 1786 7270 20862.3
8 3600 5367 20362.9
16 3502 2914 21043.3
64 3535 2064 20881.3

Analysis

Servlet

Servlet is a legacy interface that is not good for asynchronous calls. With the latest API it provided an opportunity for async dispatch, but even with it had bad performance.

Servlet Results

Servlet workflow issues:

  • Requires collecting messages on sender side
  • JSON serialization of entire collection
  • Server must accept complete HTTP request before processing
  • Requires deserialization of entire payload
  • Memory demands lead to GC pauses and potential OOM

Reactive

Spring Boot Reactive uses Netty instead of traditional servlet containers, enabling async operations.

Reactive Results

Key advantage: application/x-ndjson allows us to send/receive messages in a more convenient way for faster processing without blocking and awaiting all HTTP request/response.

Payload comparison:

  • application/json: [{..}, {..}]
  • application/x-ndjson: {}\n{}\n

TCP Dump - application/json:

TCP Dump - application/json

TCP Dump - application/x-ndjson:

TCP Dump - application/x-ndjson

Reactive implementation example:

class Controller {
    public Mono<Void> addMessages(@RequestBody Flux<Message> messages) {
        return messages.flatMap(message -> r2dbc.insert(Message.class).using(message))
                       .then();
    }
}

Benefits: Non-blocking HTTP processing, streaming support, incremental message transmission.

Limitations: Database becomes bottleneck; stream failures affect entire request; lacks full backpressure.

RSocket

RSocket is reactive binary protocol that builds over TCP.

RSocket Results

Key advantages:

  • Operates over TCP, not HTTP
  • Implements backpressure mechanism
  • Consumer could ask publisher to stop producing more messages if it overloaded or give more if it have enough capacity
  • Provides superior linear scalability

RSocket demonstrates consistent high performance across all concurrency levels.

Summary

Servlet bottlenecks: Blocking operations, HTTP protocol limitations, memory accumulation.

Reactive improvements: Non-blocking APIs, streaming between client/server, efficient I/O handling.

RSocket advantages: Complete reactive paradigm implementation, backpressure support, binary protocol efficiency.

References