Quarkus Reactive
in Action

Stefan Trenkel, Matthias Bremer | team neusta

Wir

  • > 10 Jahre bei neusta sd 👴

  • Java-/Jakarta-Entwickler ☕

  • lange beim gleichen Kunden (vit w.V.) 🤠

Stefan Trenkel, Matthias Bremer | team neusta

Warum Quarkus?

  • Kunde hat Jakarta/JEE-Erfahrung
  • Entwickler finden Wildfly schwerfällig (Testing, kein Hot-Reload)
  • Setup eines neuen Projekts nicht ganz einfach
  • Wildfly Updates

Quarkus = Supersonic 🚀 + Subatomic 🔬 + Java ❤️

Stefan Trenkel, Matthias Bremer | team neusta

Historie

11/2019   1.0         Red Hat

 6/2021   2.0 

 4/2023   3.0         Jakarta 10, MicroProfile 6, Hibernate 6

 7/2023   3.2 LTS

 2/2024   3.8 LTS

 4/2024   3.10

 7/2024               Commonhaus Foundation

 9/2024   3.15 LTS

CDI (ArC)

  • implementiert CDI Lite specification 💉
  • Reflections vermeiden (teuer) 🪞

Non-standard Features:

  • Removing Unused Beans
  • Lazy Bean Creation
  • Qualified Injected Fields

siehe: https://quarkus.io/guides/cdi-reference#nonstandard_features

Dev Mode

__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2024-09-15 12:54:51,134 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2024-09-15 12:54:51,135 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: 
[agroal, cdi, hibernate-orm, hibernate-orm-panache, hibernate-validator, jdbc-postgresql, narayana-jta, resteasy, resteasy-jsonb, smallrye-context-propagation, vertx]

--
Tests paused
Press [e] to edit command line args (currently ''), [r] to resume testing, [o] Toggle test output, [:] for the terminal, [h] for more options>

  • Live Reload
  • Continuous Testing
  • Dev Services - zero Configuration

Hibernate Panache (Repository-Pattern)

@Entity
public class Cattle { ...}
@ApplicationScoped
public class CattleRepository implements PanacheRepository<Cattle> { ... }
@Inject
CattleRepository cattleRepository;

cattleRepository.persist(cattle);

long count = cattleRepository.count("birthday", LocalDate.of(1940, 3, 10));

Hibernate Panache (Active-Record Pattern)

@Entity
public class Cattle extends PanacheEntity { ...}
cattle.persist();

long count = Cattle.count("birthday", LocalDate.of(1940, 3, 10));

Quarkus Test

  • JUnit5

  • Mockito

  • @QuarkusTest + RESTassured = ❤️

    einfache Endpoint-Tests

Quarkus Test

@QuarkusTest
@TestHTTPEndpoint(CattleResource.class)
class CattleResourceTest {

  @Test
  void testGeCattle() {
    given().pathParam("lom", 276000400000001L)
      .when().get("{lom}")
      .then().log().ifValidationFails()
      .statusCode(200)
      .body(is("{\"birthday\":\"1940-03-10\",\"lom\":276000400000001}"));
  }

Reactive

Was ist Reactive?

Reactive is a set of principles to build robust, efficient, and concurrent applications and systems.

Was bedeutet das?

These principles let you handle more load than traditional approaches while using the resources (CPU and memory) more efficiently while also reacting to failures gracefully.

Quelle: https://quarkus.io/guides/getting-started-reactive

Imperativ

  • jeder Request bekommt einen Worker-Thread aus dem Threadpool

  • der Thread ist blockiert für die Zeit des Requests

  • ein Request entspricht damit genau einem Thread

  • Code ist straight forward

Quelle: Davi Vieira - Designing Hexagonal Architecture with Java

Reactive

  • ein Thread kann mehrere Requests bearbeiten

  • non-blocking I/O-Threads
    DB-Operationen etc. blockieren die Threads nicht

  • arbeitet mit Callbacks

  • während eine I/O-Option noch läuft, kann der Thread schon den nächsten Request annehmen

Quelle: Davi Vieira - Designing Hexagonal Architecture with Java

Reactive und Quarkus

  • innere Quarkus-Architektur
  • intern non-Blocking I/O-Threads
  • Eclipse Vert.x, Netty
  • als User: reactive Programming mit Mutiny
  • @Blocking erlaubt imperativen Code

Quelle: https://quarkus.io/guides/quarkus-reactive-architecture

(Klassische) Jakarta-Implementierung

  @Inject
  CattleRepository cattleRepository;


  CattleDto findCattle(Long lom) {
    Cattle cattle;
    try {
      cattle = cattleRepository.find("lom", lom).singleResult();
    } catch (NoResultException e) {
      return null;
    }
    return CattleDto.from(cattle);
  }

Reactive mit SmallRye Mutiny

  @Inject
  CattleRepository cattleRepository;


  @WithTransaction
  Uni<CattleDto> findCattle(Long lom) {
    return cattleRepository.find("lom", lom).singleResult()
      .onItem().transform(CattleDto::from)
      .onFailure(NoResultException.class).recoverWithNull();
  }

Performance

  • Wildfly

  • Quarkus imperativ

  • Quarkus reactive

Umgebung

  • Lokal auf Notebook: 8 CPUs, i7 von 2018

  • Ubuntu (Performance Setting)

  • PostgreSQL

  • Docker-Container (CPU und RAM begrenzt)

  • Quarkus 3.8 LTS + Wildfly 31

Startup

Quarkus imperativ:

CONTAINER ID   NAME                                   CPU %     MEM USAGE / LIMIT   MEM %     NET I/O           BLOCK I/O       PIDS
ee5217f76198   cattle-quarkus-classic-quarkus-app-1   0.34%     130.8MiB / 512MiB   25.54%    9.91kB / 12kB     0B / 119kB    23

🚀 Startup: 6.0 s

Quarkus reactive:

CONTAINER ID   NAME                                    CPU %     MEM USAGE / LIMIT   MEM %     NET I/O          BLOCK I/O    PIDS
1a346ce10311   cattle-quarkus-reactive-quarkus-app-1   0.27%     136.8MiB / 512MiB   26.72%    173kB / 8.01kB   0B / 111kB    21

🚀 Startup: 6.2 s

Startup

Wildfly:

CONTAINER ID   NAME                       CPU %     MEM USAGE / LIMIT   MEM %     NET I/O           BLOCK I/O       PIDS
374a381d75b6   cattle-wildfly-wildfly-1   0.06%     504.6MiB / 512MiB   98.55%    19.5kB / 19.5kB   471MB / 778MB   54
CONTAINER ID   NAME                       CPU %     MEM USAGE / LIMIT   MEM %     NET I/O           BLOCK I/O       PIDS
7d54f15b952e   cattle-wildfly-wildfly-1   0.05%     557.7MiB / 1GiB     54.46%    19.4kB / 19.5kB   10.9MB / 2.79MB   54

🐘 + 🐌 Startup ca. 30 s

POST 1000 Tiere

/ curl -X POST ... & curl -X POST ...
Quarkus imperativ 12 14
Quarkus reactive 11 14
Wildfly 16 17

GET

          /\      |‾‾| /‾‾/   /‾‾/   
     /\  /  \     |  |/  /   /  /    
    /  \/    \    |     (   /   ‾‾\  
   /          \   |  |\  \ |  (‾)  | 
  / __________ \  |__| \__\ \_____/ .io
  • 1000 VUs
  • 1 Request pro Sekunde
  • Random Tier 1..1000
  • 20 Iterationen
          /\      |‾‾| /‾‾/   /‾‾/   
     /\  /  \     |  |/  /   /  /    
    /  \/    \    |     (   /   ‾‾\  
   /          \   |  |\  \ |  (‾)  | 
  / __________ \  |__| \__\ \_____/ .io

     execution: local
        script: k6/script.qclassic.js
        output: -

     scenarios: (100.00%) 1 scenario, 1000 max VUs, 10m30s max duration (incl. graceful stop):
              * default: 20000 iterations shared among 1000 VUs (maxDuration: 10m0s, gracefulStop: 30s)


     data_received..................: 2.1 MB 45 kB/s
     data_sent......................: 1.8 MB 39 kB/s
     http_req_blocked...............: avg=3ms      min=1.37µs  med=3.46µs   max=235.01ms p(90)=6.01µs   p(95)=487.95µs
     http_req_connecting............: avg=2.9ms    min=0s      med=0s       max=234.95ms p(90)=0s       p(95)=94.45µs 
     http_req_duration..............: avg=1.29s    min=1.56ms  med=590.48ms max=10.45s   p(90)=3.19s    p(95)=4.82s   
       { expected_response:true }...: avg=1.29s    min=1.56ms  med=590.48ms max=10.45s   p(90)=3.19s    p(95)=4.82s   
     http_req_failed................: 0.00%  ✓ 0          ✗ 20000 
     http_req_receiving.............: avg=52.19µs  min=14.46µs med=36.75µs  max=10.79ms  p(90)=66.06µs  p(95)=84.88µs 
     http_req_sending...............: avg=702.66µs min=5.36µs  med=15.07µs  max=200.94ms p(90)=172.89µs p(95)=812.23µs
     http_req_tls_handshaking.......: avg=0s       min=0s      med=0s       max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=1.29s    min=1.48ms  med=590.39ms max=10.35s   p(90)=3.19s    p(95)=4.82s   
     http_reqs......................: 20000  429.218332/s
     iteration_duration.............: avg=2.29s    min=1s      med=1.59s    max=11.55s   p(90)=4.19s    p(95)=5.82s   
     iterations.....................: 20000  429.218332/s
     vus............................: 752    min=752      max=1000
     vus_max........................: 1000   min=1000     max=1000


running (00m46.6s), 0000/1000 VUs, 20000 complete and 0 interrupted iterations
default ✓ [======================================] 1000 VUs  00m46.6s/10m0s  20000/20000 shared iters   

10 VUs - 1 Request pro Sekunde

Quarkus imperativ:

http_req_duration..............: avg=15.08ms  min=3.45ms  med=5.4ms   max=206.75ms p(90)=13.64ms  p(95)=97.5ms  

Quarkus reactive:

http_req_duration..............: avg=10.85ms min=2.61ms  med=5.14ms  max=169.34ms p(90)=10.68ms p(95)=46.89ms 

Wildfly:

http_req_duration..............: avg=7.22ms  min=4.08ms  med=6.72ms  max=20.25ms  p(90)=10.47ms p(95)=11.92ms 

1000 VUs - 1 Request pro Sekunde

Quarkus imperativ:

http_req_duration..............: avg=905.76ms min=1.64ms  med=392.46ms max=5.25s    p(90)=2.2s     p(95)=2.88s   

Quarkus reactive:

http_req_duration..............: avg=1.05s   min=205ms    med=723.27ms max=6.65s    p(90)=2.09s   p(95)=3.12s

Wildfly:

http_req_duration..............: avg=2.87s    min=1.2s    med=2.41s   max=7.77s    p(90)=4.4s     p(95)=4.8s

1000 VUs - 1 Request pro Sekunde - nach 5 Iterationen

Quarkus imperativ:

http_req_duration..............: avg=30.64ms  min=594.27µs med=2.35ms  max=590.12ms p(90)=44.5ms  p(95)=168.4ms

Quarkus reactive:

http_req_duration..............: avg=41.78ms  min=874.91µs med=7.39ms  max=850.99ms p(90)=67.72ms p(95)=93.8ms  

Wildfly:

http_req_duration..............: avg=1.08s    min=45.08ms med=1.09s   max=2.1s     p(90)=1.2s    p(95)=1.28s

2000 VUs - 1 Request pro Sekunde

Quarkus imperativ:

http_req_duration..............: avg=2.43s    min=14.37ms med=1.5s    max=12.71s   p(90)=5.18s   p(95)=8.89s   

Quarkus reactive:

http_req_duration..............: avg=2.78s    min=102.01ms med=2.19s   max=13.64s   p(90)=4.42s   p(95)=7.07s   

Wildfly:

http_req_duration..............: avg=6.58s    min=75.54ms med=5.98s   max=17.65s   p(90)=9.51s    p(95)=12.28s  

Vorläufiges Fazit - Quarkus Reactive

  • schneller Start 🚀

  • sparsam mit Speicher 📊

  • performanter als Wildfly 🚀

  • Reactive bis zum 'Enterprise-Developer' muss nicht sein?

  • ... aber auf Framework-Ebene ⚛️

Code und Slides

https://gitlab.com/quarkus-ck/quarkus-reactive-in-action/

Schön, dass unser Thema euer Interesse geweckt hat. Matthias "Quarkus" Stefan "Reactive" in Action ??? Hat jemand Erfahrungen mit Quarkus? Mit Quarkus und Reactive?

seit xxx Software-Entwickler bei neusta SD. Seit xxx Jahren beim gleichen Kunden... in unterschiedlichen Projekten Schwerpunkt Java / JakartaEE, früher auf Tomcat und Wildfly jetzt immer mehr Quarkus

1. Kunde traditionell - setzt auf Standards 2. Backendlastig 3. Quarkus-Standards: CDI/ArC, Hibernate, RestEasy, JsonB, Microprofile SmallRye 4. leichtgewichtiger: leichtes Setup, schnelles Startup, QuarkusTest, Dev-Mode 5. Webservices, CLI- bzw. Batch-Programme 6. seit ca. 1,5 Jahren Docker-Swarm und Quarkus = Standard fürs Backend

1. Red Hats Antwort auf Spring Boot (lt. Wikipedia 10 Jahre alt) 2. Red Hat hatte schon erfahrungen mit JBoss bzw. Wildfly 3. rasante Entwicklung (für ein Java-Framework) 4. nächste 3.15 LTS am 25.9.2024 5. 12 Monate Support für LTS-Versionen

0. Optimierung für HotSpot-Compiler und GraalVM 1. Annotation-Scanning und Optimierung zur Compilezeit 2. Reflections ist damit nur eingeschränkt erlaubt 3. Ungenutzte Klassen werden zur Buildzeit entfernt werden 4. Beans werden erzeugt, wenn sie gebraucht werden (z.B. RequestScoped) 5. Inject-Annotation kann entfallen, Bsp. RestClient, ConfigProperty

Dev UI

Quarkus Persistence

einfach im Vergleich zu klassischem Jakarta-Appserver

erstmal schauen, was das Gegenteil von Reactive eigentlich ist

* Eclipse Vert.x: asynchrones Programmier-Framework * Netty: asynchrones Netzwerk-Framework * @NonBlocking ist ein Hint an das Backend

* mit Panache-Repository * Wildfly so ähnlich

eher eventbasiert

Quarkus ist im Backend immer reactive

Quarkus und Wildfly mit gleichem Stand Hibernate

Startup - docker compose mit Postgres 1 GET Request

Startup + 1 GET Request

* vorher 1 GET * leere DB * auf Sekunde gerundet

* Simples Scenario * Skript dazu ist im Repo * Performance-Messung ist schwierig * HotSpot-Compiler (JIT) + Caching

* Was man bekommt sieht etwa so aus * http_req_duration * median und 90. oder 95. Perzentil

* mit warmem Compiler / Cache

* im Vergleich mit klassischem Jakarta App-Server * Quarkus macht auch noch Spaß * reactive ist wahrscheinlich ein Grund für die gute Quarkus-Performance