Dimensionierung und Konfiguration des jadice web toolkit bei Inbetriebnahme

Dimensionierung und Konfiguration des jadice web toolkit bei Inbetriebnahme

Gültig ab jadice web toolkit 5.11

Zur Inbetriebnahme einer Installation des jadice web toolkit sind verschiedene Einstellungen vorzunehmen.

Die meisten Werte sind bereits sinnvoll voreingestellt - jedoch mindestens die grundsätzliche Dimensionierung der Server sowie die Konfiguration der Caches müssen anhand der erwarteten Last passend eingestellt werden - gerne auch in Rücksprache mit levigo.

In der nachfolgenden Tabelle sind die verschiedenen Parameter zusammengestellt.

Stellgrößen für Dimensionierung und Performance im jadice web toolkit

Bereich

Details

Bereich

Details

Pods/VM

  • Erfahrungswerte für 500-1000 Concurrent User:

    • 2-4 Server-Instanzen (mit 2 Instanzen starten, bei Bedarf hochskalieren)

    • 4 logische Kerne/Prozessoren → bitte Abschnitt "Docker, Kubernetes & OpenShift" in Thread Pools and Concurrency beachten

    • (6-)8 GB RAM,

    • (4-)6 GB max. Heap

      • z.B. auch über -XX:MaxRAMPercentage=75.0 möglich

    • Region Size des Garbage-Collectors unbedingt erhöhen

      • z.B. -XX:G1HeapRegionSize=8m bei 8 GB RAM

      • Dadurch wird der RAM wesentlich besser ausgereizt

  • Java 21 

  • Geeignete Dimensionierung ist abhängig von Dokumenten:

    • PDF kostet gegenüber anderen Formaten mehr Rechenzeit → weniger User bzw. mehr Prozessoren pro Pod

    • große Dokumente (PDFs mit vielen Seiten, in großen Formaten (DIN A0), mit hoher Auflösung) erfordern größeren serverseitigen Heap Space 

Tile-Rendering

PNG statt WebP im Container: 

  • Im Container-Betrieb empfehlen wir (bis Version 5.10 und ab Version 6 Default-Einstellung) empfehlen wir den "Tile-Compression-Type" auf IMAGEIO (→ PNG) zu stellen

  • In Version 5.10 des jadice web toolkit wurde eingeführt, dass Tiles standardmäßig als WebP-Images und nicht mehr als PNG an den Client geliefert werden. Dies bringt Vorteile im Bezug auf Geschwindigkeit und Dateigröße. Jedoch hat sich herausgestellt, dass dies im Containerumfeld weniger gut geeignet ist. Die Erzeugung von WebP-Bildern erfordert Off-Heap-Speicher, was im Monitoring nicht gut überwachbar und steuerbar ist. Die Erzeugung von PNG ermöglicht es jadice auch besser mit dem Cache umzugehen, zudem kann so die Memory-Lücke zwischen Container- und JVM-Speicherlimit deutlich kleiner ausfallen, ohne dass es zu OOM-Killed-Fehlern seitens des Containers kommt.

     

     

  • Weitere Informationen zur Wahl des Tile-Compression-Types sind hier zu finden.

    Um die Einstellung zu ändern:
    In der application.yml folgendes einstellen:
    webtoolkit:
      tile-compression-type: IAMGEIO
    alternativ programmatisch
    ConfigurationManager.getServerConfiguration().setTileCompressionType(ServerConfiguration.TileCompressionType.IMAGEIO);

Connection

Verbindungstyp setzen: Websocket, Server Sent Events oder Longpoll (siehe Connection Framework

  • falls möglich, ist Websocket empfohlen

  • programmatisch über die ServerConfiguration :
    z.B. new ServerConnectionBuilder().setWebSocketEnabled(false).setServerSentEventsEnabled(false).setLongpollEnabled(true).build();

  • alternativ in der application.yml bei Nutzung von Spring Boot

Deployment

  • Wenn das webtoolkit als Spring-Boot Anwendung gebaut wird, sollte das packaging unbedingt als JAR erfolgen und nicht als WAR (WAR sollte nur bei "Traditional Deployment", also wenn das WAR auf einem App-Server läuft, gewählt werden). Hintergrund ist, dass Spring wohl eine Unterscheidung beim Classpath-Scanning macht (dieser wird bei jedem PDF-Dokument durchgeführt, vermutlich da DefaultMetadataFactory intern auf Xerces setzt, welches dynamisch Klassen sucht)

Performance 

  • Tile-Preloading-Range: beschleunigte Anzeige benachbarter Seiten (empfohlen: 0-3)
    pageView.setPreloadingPageRange(new PreloadingPageRange(2));

  • Generierung der Tiles als PNG/WEBP-Images (Tradeoff zwischen Rechengeschwindigkeit und Bandbreite): → siehe Tile-images file-formats
    ConfigurationManager.getServerConfiguration().setTileCompressionType(...)

  • Thread Pools - sind auf Basis der Prozessorzahl sinnvoll voreingestellt (siehe Thread Pools and Concurrency)

    • ServerConfiguration.setTileRendererPoolCoreSize(int)

    • ServerConfiguration.setTileRendererPoolMaxSize()

    • ServerConfiguration.setTileRendererMaxParallelRenderingPerClient() 

    • ServerConfiguration.setGeneralPoolCoreSize(int)

    • ServerConfiguration.setGeneralPoolMaxSize(int)

Cache

Serverseitiges Tile-Caching: an/aus
Stellt man fest, dass der Server-Cache einen Engpass darstellt (also Dokumente zu häufig aus dem Cache expired werden und erneut geladen werden müssen), empfiehlt sich das Ausschalten des serverseitigen Tile-Cachings.
ConfigurationManager.getServerConfiguration().setTileCachingEnabled(false), Default: false

Clientseitiger Cache (Browser): immer aktiv 

Serverseitiger Cache - typische Richtwerte:



LRUCache

CompositeKeyCache
(=Standard im JWT)

empfohlene Werte

jadice.viewer.cache.maxNumberOfCacheEntries

3000

3000
(in JWT 90000)

90000 → siehe Berechnung

Berechnung:

  • je Dokument wird ca. 2,5-4,5 MB im Cache belegt (JWT-Dokument, JWT-PageSegmente, Strukturinformationen aus der docpv) - haben wir empirisch ermittelt mit der Enterprise Demo und versch. Dokumenten

  • im Mittel werden 10 Dokumente pro Minute geladen (Annahme)

  • wir wollen erreichen, dass die Dokumente möglichst 30min im Cache bleiben
    --> 10Doc*30min*2,5 MB = 750 MB bis 10*30*4,5 MB = 1350 MB

  • im Cache werden ca. 150-300 Entries pro Dokument erzeugt (aus Stichproben ermittelt) -> 10Doc*30min*300 = 90.000 Entries

    • Hier würde ich tendenziell sogar höher gehen, so dass der Speicher der limitierende Faktor ist und nicht die Anzahl der Elemente

"jadice.viewer.cache.maximumExpiryObjectCountRatio"

0.1f

0.1f für LRUExpiryStrategy
10 für TileExpiryStrategy

0.1f bzw. 10 (Achtung: Die TileExpiryStrategy im jadice web toolkit erwartet eine Prozentangabe!)

"jadice.viewer.cache.minimumExpiryAge"

-1

-1
(LRUExpiryStrategy +TileExpiryStrategy)

(in JWT 60000)

60000 // lifetime for cache entries. (in milliseconds)

jadice.viewer.cache.sizeHighwaterMark"

-1

-1

Nicht empfohlen. Stattdessen lieber sizeHighwaterMarkPercent nutzen.

jadice.viewer.cache.sizeHighwaterMarkPercent"

-1

-1

(in JWT 25)

25 // entspricht 2 GB bei 8 GB Heap

// configure an adaptive size management strategy with default parameters
cache.setSizeManagementStrategy(new AdaptiveSizeManagementStrategy(cache));



jadice.viewer.cache.maxAgeExpiryEnabled=true

true

expires all entries which are older than the defined age (age = latest access)

jadice.viewer.cache.maxAge=30

60



jadice.viewer.cache.maxAge.timeUnit=MINUTES

MINUTES



jadice.viewer.cache.type=CompositeKeyCache

-

-

Irrelevant im JWT, da hart codiert!

Empfehlung: Serverseitig ggf. auf LRUCache umstellen, falls man sieht, dass trotz geringer Belegung des Caches häufig aufgeräumt wird. Das macht man wie folgt:

Überschreiben des Caches im ConfigurationContextListener
// Im WebtoolkitServerContext wurde folgendes bereits statisch vorgenommen: // CacheManager.setDefault(DefaultCacheProvider.getDefaultServerCache()); // Das überschreiben wir hier: LRUCache cache = new LRUCache(120000); cache.setSizeHighwaterMark(1000 * 1024 * 1024); CacheManager.setDefault(cache); // ConfigurationManager.getServerConfiguration().setTileCachingEnabled(false);

Formatinterpretation der jadice document platform

Format-Einstellungen:

  • Abhängig vom Dokumentenbestand ggf. auf den Lesemodus LENIENT_ON_ERROR umstellen, um PDFs tolerant zu lesen, so dass Dokumente trotz gewisser syntaktischer Fehler angezeigt werden. → Zu Risiken und Nebenwirkungen siehe PDF-Struktur Lesestrategien

    Einstellung im DocumentDataProvider, direkt vor reader.read(inputStream)

    PDFStructureReaderSettings settings = reader.getSettings(PDFStructureReaderSettings.class); settings.setPDFStructureReadStrategy(PDFStructureReadStrategy.LENIENT_ON_ERROR);
  • Embedded MO:DCA-Annotationen anzeigen:
    jadice.viewer.afp-show-inline-annotation=true

UX

  • Qualität der Client-Icons: ClientConfigurationManager.getClientConfiguration().setIsIconFontUsed(true);

 

JVM-Parameter für Spring Boot mit Java 25 (Docker, 8GB RAM)

Garbage Collection

-XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=16m -XX:InitiatingHeapOccupancyPercent=45 -XX:G1ReservePercent=10

Erklärung:

  • -XX:+UseG1GC - Aktiviert den G1 Garbage Collector (Standard in Java 25, für niedrige Latenz optimiert)

  • -XX:MaxGCPauseMillis=200 - Ziel-Pausenzeit von 200ms für GC-Zyklen (Balance zwischen Durchsatz und Latenz)

  • -XX:G1HeapRegionSize=16m - Größe der Heap-Regionen auf 16MB (gut für 6GB Heap)

  • -XX:InitiatingHeapOccupancyPercent=45 - Startet Concurrent Marking bei 45% Heap-Belegung (früher = weniger Full GCs)

  • -XX:G1ReservePercent=10 - Reserviert 10% des Heaps als Puffer für Allocation Spikes

Heap-Konfiguration

-XX:InitialRAMPercentage=75.0 -XX:MaxRAMPercentage=75.0 -XX:MinRAMPercentage=75.0 -XX:+AlwaysPreTouch

Erklärung:

  • -XX:InitialRAMPercentage=75.0 - Initiale Heap-Größe auf 75% des Container-RAMs (~6GB bei 8GB)

  • -XX:MaxRAMPercentage=75.0 - Maximale Heap-Größe auf 75% begrenzt

  • -XX:MinRAMPercentage=75.0 - Minimale Heap-Größe (verhindert dynamisches Resizing)

  • -XX:+AlwaysPreTouch - Alloziert den gesamten Heap beim Start (vermeidet Latenz-Spikes später, bessere Performance)

Metaspace

-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m

Erklärung:

  • -XX:MetaspaceSize=256m - Initiale Metaspace-Größe (für Klassen-Metadaten)

  • -XX:MaxMetaspaceSize=512m - Maximale Metaspace-Größe (Spring Boot lädt viele Klassen, daher großzügig bemessen)

Java 25 spezifische Optimierungen

-XX:+UnlockExperimentalVMOptions -XX:+UseCompactObjectHeaders

Erklärung:

  • -XX:+UnlockExperimentalVMOptions - Aktiviert experimentelle Features

  • -XX:+UseCompactObjectHeaders - Neu in Java 25! Reduziert Object-Header von 12→8 Bytes (10-20% weniger Heap-Verbrauch, bessere Cache-Lokalität)

Performance-Optimierungen

-XX:+UseStringDeduplication -XX:+OptimizeStringConcat -XX:+UseCompressedOops -XX:+UseCompressedClassPointers

Erklärung:

  • -XX:+UseStringDeduplication - Dedupliziert identische Strings im Heap (spart Speicher bei vielen gleichen Strings)

  • -XX:+OptimizeStringConcat - Optimiert String-Konkatenation zur Laufzeit

  • -XX:+UseCompressedOops - Komprimiert Object-Pointer auf 32 Bit (bei Heaps <32GB, spart Speicher)

  • -XX:+UseCompressedClassPointers - Komprimiert Class-Pointer (zusätzliche Speicherersparnis)

Container-Support

-XX:+UseContainerSupport

Erklärung:

  • -XX:+UseContainerSupport - Erkennt Container-Limits (cgroups) automatisch (in Java 25 standardmäßig aktiv, aber explizit ist sicherer)

Diagnostik & Monitoring

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof -Xlog:gc*:file=/tmp/gc.log:time,uptime,level,tags:filecount=5,filesize=10M -XX:+ExitOnOutOfMemoryError

Erklärung:

  • -XX:+HeapDumpOnOutOfMemoryError - Erstellt Heap-Dump bei OutOfMemoryError (für Post-Mortem-Analyse)

  • -XX:HeapDumpPath=/tmp/heapdump.hprof - Speicherort für Heap-Dumps

  • -Xlog:gc*:file=/tmp/gc.log:... - GC-Logging in rotierenden Dateien (max 5 Dateien à 10MB)

  • -XX:+ExitOnOutOfMemoryError - Beendet die JVM bei OOM (Container-Orchestrierung kann neu starten)

Sonstiges

-Djava.security.egd=file:/dev/./urandom

Erklärung:

  • -Djava.security.egd=file:/dev/./urandom - Nutzt /dev/urandom für Zufallszahlen (verhindert Blocking bei /dev/random, wichtig für schnellen Start)


Speicher-Verteilung bei 8GB Container

  • Heap: ~6GB (75%)

  • Metaspace: max 512MB

  • Direct Memory, Thread Stacks, Native Memory: ~1.5GB

  • Container-Overhead: minimal

Hinweise

  • Compact Object Headers ist experimentell → unbedingt testen vor Produktion

  • Bei hoher Last ggf. MaxRAMPercentage auf 80% erhöhen

  • GC-Logs mit Tools wie GCViewer oder GCEasy analysieren

  • Bei sehr niedriger Latenz-Anforderung: ZGC als Alternative zu G1GC erwägen

Weiterführende Informationen