% !TeX root = ../../thesis.tex

\chapter{Performance-Untersuchung der Anwendung}
\label{ch:performance-investigation-application}

Nun werden die unterschiedlichen Schichten betrachtet und mögliche Performance"=Verbesserungen untersucht und deren
Vor"= und Nachteile herausgearbeitet.

Für die Tests wird ein aktuelles Manjaro"=System mit frisch installierten Payara als Serverhost und der IntelliJ IDEA
als Entwicklungsumgebung verwendet. Der Computer ist mit einer Intel CPU i7-12700K, 32 GB Arbeitsspeicher und einer SSD
als Systemfestplatte ausgestattet.

Zur ersten Untersuchung und der Bestimmung der Basis-Linie wurde das Script ohne eine Änderung am Code und der
Konfiguration mehrfach aufgerufen. Hierbei hat sich gezeigt, dass der erste Aufruf nach dem Deployment circa 1500 ms
gedauert hat. Die weiteren Aufrufe benötigen im Durchschnitt noch 600 ms. Beim achten Aufruf des Scripts hat der
Server nicht mehr reagiert und im Log ist ein \textit{OutOfMemoryError} protokolliert worden.

Nach einem Neustart des Servers konnte das gleiche Verhalten wieder reproduziert werden. Daraufhin ist eine Erweiterung
des Test"=Scripts notwendig, um die aktuellen Speichernutzung des Payara"=Servers darzustellen und auszuwerten. Diese
Auswertung zeigte, dass der Server mit circa 1500 MB RSS Nutzung an seine Grenzen stößt. Diese Grenze wird durch die
Konfigurationsänderung im Payara-Server von \texttt{-Xmx512m} auf \texttt{-Xmx4096m} nach oben verschoben. Nun werden
circa 60 Aufrufe des Scripts benötigt, damit der Server nicht mehr reagiert. Hierbei wird aber kein \textit{OutOfMemoryError}
in der Log-Datei protokolliert und der Server verwendet nun circa 4700 MB RSS. Bei allen Tests war noch mehr als die
Hälfte des verfügbaren Arbeitsspeichers des Computers ungenutzt.

Mit der Konfiguration \texttt{-Xmx} wird der maximal verwendbare Heap"=Speicher in der \ac{JVM} definiert.
Dies zeigt direkt, dass es ein Problem in der Freigabe der Objekte gibt, da dass Erhöhen des verwendbaren
Arbeitsspeicher das Problem nicht löst, sondern nur verlagert.

Für alle nachfolgenden Messungen wird das Skript im \autoref{ap:calling_script} verwendet, welches die einzelnen
Aufrufe steuert. Die Ergebnisse werden in eine Tabelle überführt, wie in \autoref{tbl:measure-without-cache}.
Hierbei werden die Aufrufzeiten der Webseite aus dem Skript für die Zeitmessung mit Mindest"~, Durchschnitt"~ und
Maximalzeit aufgenommen, hierbei ist eine kürzere Zeit besser. Zusätzlich wird die Anzahl der aufgerufenen \ac{SQL} Abfragen
ermittelt, auch hier gilt, je weniger Aufrufe desto besser. Als letztes wird noch der verwendete Arbeitsspeicher
vom \textit{Payara}"=Server vor und nach dem Aufruf ermittelt und die Differenz gebildet, hierbei sollte im idealen
Fall die Differenz bei 0 liegen. Dieser Aufbau gilt für alle weiteren Messungen. Zusätzlich werden noch die Laufzeiten
der \ac{JSF} ermittelt und die durchschnittlichen Zeiten mit in der Tabelle dargestellt, und auch hier ist es besser,
wenn die Zeiten kürzer sind.

Als Grundlage für die Vergleiche wurde eine Messung durchgeführt, bei welcher alle Caches deaktiviert wurden und keine
Änderung am Code vorgenommen wurde. Das Ergebnis dieser Messung ist in \autoref{tbl:measure-without-cache} zu finden.
Diese zeigen auch direkt ein zu erwartendes Ergebnis, dass der erste Aufruf bedeutend länger dauert als die Nachfolgenden.
Ebenfalls sieht man eindeutig, dass die Anzahl der Anfragen nach dem ersten Aufruf immer die gleiche Anzahl besitzen.
Der Speicherbedarf steigt relativ gleichmässig, was nicht recht ins Bild passt, da hier keine Objekte im Cache
gehalten werden sollten.

\begin{table}[H]
    \centering
    \begin{tabular}{|r|r|r|r|r|r|r|r|}
        \hline
        & \multicolumn{3}{c|}{Aufrufzeit (ms)} & & \multicolumn{3}{c|}{RSS (MB)} \\
        \hline
        \# & min & avg & max & Queries & davor & danach & diff \\
        \hline
        1 & 395 & 578 & 1312 & 12237 &  747.15 &  924.88 & 177.73 \\
        2 & 353 & 375 &  464 & 12080 &  924.51 & 1027.75 & 103,24 \\
        3 & 286 & 345 &  535 & 12080 & 1018.21 & 1145.36 & 127.15 \\
        4 & 291 & 307 &  340 & 12080 & 1129.91 & 1239.75 & 109,84 \\
        \hline
    \end{tabular}
    \caption{Messung ohne Caches}
    \label{tbl:measure-without-cache}
\end{table}

Vor jedem weiteren Test"=Lauf wird die Domain beendet und komplett neugestartet, um mit einer frischen Instanz zu
beginnen. Hierbei ist aufgefallen, dass fast immer 62 Abfragen zur Startup-Phase dazugehören, unabhängig von den
konfigurierten Cache Einstellungen. Einige dieser Abfragen sind durch das Erstellen der \textit{Materialized View}
\textit{searchreference} und \textit{searchfulltext} erklärbar. Zusätzlich ist noch ein zyklischer Dienst
\textit{SearchEntityService} vorhanden, der zum Start und alle sechs Stunden den Datenbestand für die Suche aufbereitet
und entsprechend einige Abfragen an die Datenbank absetzt. Da weder die Sichten noch der Dienst für die Dokumentenliste
benötigt werden, ist der Dienst und das Erstellen im Code für die weiteren Tests deaktiviert.

Da die Abfragezeiten auf der Datenbank zu gering sind, um eine Verbesserung feststellen zu können, wird für den
PostgreSQL und den Payara"=Server ein Docker"=Container erzeugt und diese limitiert. Die Konfiguration ist in 
\autoref{ap:docker_config} beschrieben.

Mit dem neuen Aufbau ergibt sich eine neue Messung. Für den Speicherbedarf wird nun nicht mehr der benutzte
Speicher der Anwendung beobachtet, sondern die Speichernutzung des Docker-Containers für den Payara-Server. Auch hier
ist es besser, wenn es keine oder nur geringe Änderungen vor und nach dem Aufruf der Webseite gibt, ein steigender Wert
zeigt an, dass der verwendete Speicher nicht sauber freigegeben werden kann.

Für die Ausführungszeiten der \ac{SQL}"=Abfragen werden nur die sechs Abfragen für die Darstellung der Tabelle beachtet.
Hierzu zählt die Hauptabfrage der Dokumenten"=Tabelle, die Ermittlung des letzten und ersten Eintrags in der Tabelle,
die Ermittlung der Adressen des Autors, die Ermittlung der Koautoren, die Ermittlung der Faksimile, sowie die Ermittlung
der Anzahl aller vorhandenen Dokumente.

Zusätzlich wird die Zeit des Rendern der Sicht gemessen. Die erste Messung erfasst die komplette Laufzeit die fürs Rendern
notwendig ist. Die zweite Messung ermittelt die Zeit für das Laden der Daten aus der Datenbank und das Erstellen
der Java"=Objekte inklusive dem Befüllen mit den geladenen Daten.

\begin{table}[H]
    \centering
    \resizebox{\textwidth}{!}{
    \begin{tabular}{|r|r|r|r|r|r|r|r|r|r|r|r|r|r|r|}
        \hline
        & \multicolumn{3}{c|}{Aufrufzeit (ms)} & \multicolumn{2}{c|}{Queries (ms)} & \multicolumn{3}{c|}{Memory (MB)} & \multicolumn{3}{c|}{Render (ms)} & \multicolumn{3}{c|}{DB-load (ms)} \\
        \hline
        \# & min & avg &  max & \#"=avg &   avg &  start &   stop &  diff & min & avg &  max & min & avg & max \\
        \hline
        1 &  451 & 682 & 1931 &  1223.0 &  30.3 &  931.3 &  986.1 &  54.8 & 440 & 666 & 1859 & 290 & 399 & 710 \\ % 12230 -  303 ms (135+ 79+ 39+ 22+17+11) (#2-6,8)
        2 &  341 & 389 &  478 &  1208.0 &  31.2 &  986.5 & 1159.0 & 172.5 & 331 & 378 &  468 & 235 & 282 & 367 \\ % 24310 -  615 ms (270+156+ 78+ 56+34+21) (#2-7)
        3 &  299 & 407 &  682 &  1208.0 &  33.5 & 1163.0 & 1273.0 & 110.0 & 290 & 398 &  672 & 207 & 307 & 579 \\ % 36390 -  950 ms (406+256+118+ 79+55+36) (#2-7)
        4 &  278 & 359 &  424 &  1208.0 &  33.7 & 1272.0 & 1465.0 & 193.0 & 270 & 351 &  415 & 198 & 269 & 342 \\ % 48470 - 1287 ms (564+334+167+105+72+45) (#2-7)
        5 &  264 & 317 &  356 &  1208.0 &  32.9 & 1467.0 & 1574.0 & 107.0 & 256 & 309 &  348 & 184 & 235 & 276 \\ % 60560 - 1616 ms (699+428+210+128+92+59) (#2-7)
        \hline
    \end{tabular}
    }
    \caption{Messung ohne Caches im Docker}
    \label{tbl:measure-without-cache-docker}
\end{table}

\section{Caching im OpenJPA}
\label{sec:performance-investigation-application:caching-openjpa}

Die Cache"=Einstellung von OpenJPA werden über die zwei Einstellungen \texttt{openjpa.DataCache} und
\texttt{openjpa.QueryCache} konfiguriert. Bei beiden Einstellungen kann zuerst einmal über ein einfaches Flag
\textit{true} und \textit{false} entschieden werden, ob der Cache aktiv ist. Zusätzlich kann über das Schlüsselwort
\textit{CacheSize} die Anzahl der Elemente im Cache gesteuert werden. Wird diese Anzahl erreicht, werden zufällige
Objekte aus dem Cache entfernt und in eine SoftReferenceMap übertragen. Bei der Berechnung der Anzahl der Elemente werden
angeheftete Objekte nicht beachtet.

Die Anzahl der Soft References kann ebenfalls über eine Einstellung gesteuert werden. Hierfür wird die Anzahl der
Elemente über \textit{SoftReferenceSize} gesetzt, dessen Wert im Standard auf \textit{unbegrenzt} steht. Mit dem Wert
\textit{0} werden die Soft Referenzen komplett deaktiviert. Über die Attribute an den Entitätsklassen können diese
Referenzen ebenfalls gesteuert werden, hierzu muss eine Überwachungszeit angegeben werden. Diese Zeit gibt in ms an,
wie lange ein Objekt gültig bleibt. Mit dem Wert \textit{-1} wird das Objekt nie ungültig, was ebenfalls der
Standardwert ist.

Zuerst wird mit aktiviertem Cache mit einer Cache-Größe von 1000 Elemente getestet. Wie in
\autoref{tbl:measure-ojpa-active} zu sehen, dauert auch hier der erste Aufruf minimal länger als ohne aktiviertem 
Cache. Alle nachfolgenden Aufrufe wiederrum sind um 100ms schneller in der Verarbeitung. Auch bei der Anzahl der
Anfragen an die Datenbank kann der Rückgang der Anfragen sehr gut gesehen werden. Aktuell kann die Verringerung des 
wachsenden Speicherbedarfs nur nicht erklärt werden.

\begin{table}[H]
    \centering
    \resizebox{\textwidth}{!}{
    \begin{tabular}{|r|r|r|r|r|r|r|r|r|r|r|r|r|r|r|}
        \hline
        & \multicolumn{3}{c|}{Aufrufzeit (ms)} & \multicolumn{2}{c|}{Queries (ms)} & \multicolumn{3}{c|}{Memory (MB)} & \multicolumn{3}{c|}{Render (ms)} & \multicolumn{3}{c|}{DB-load (ms)} \\
        \hline
        \# & min & avg &  max & \#"=avg &   avg &  start &   stop &  diff & min & avg &  max & min & avg & max \\
        \hline
        1 &  291 & 611 & 2347 &   730.2 &  28.8 &  852.7 &  891.9 &  39.2 & 282 & 595 & 2286 & 172 & 284 & 770 \\ %  7302 -  288 ms (145+ 42+ 40+ 24+18+ 8+ 7+ 4) (#2-8,12)
        2 &  278 & 319 &  422 &   667.3 &  25.8 &  892.7 & 1010.0 & 117.3 & 266 & 309 &  411 & 173 & 195 & 220 \\ % 13975 -  546 ms (282+ 81+ 70+ 47+33+14+11+ 8) (#2-9)
        3 &  229 & 281 &  329 &   680.6 &  27.6 & 1011.0 & 1067.0 &  56.0 & 220 & 271 &  313 & 134 & 180 & 222 \\ % 20781 -  822 ms (430+120+ 99+ 77+49+20+16+11) (#2-9)
        4 &  222 & 280 &  321 &   671.3 &  27.6 & 1067.0 & 1122.0 &  55.0 & 213 & 271 &  310 & 131 & 189 & 238 \\ % 27494 - 1098 ms (569+160+137+ 99+68+26+22+17) (#2-9)
        5 &  206 & 272 &  388 &   683.6 &  27.6 & 1122.0 & 1219.0 &  97.0 & 199 & 264 &  380 & 122 & 175 & 291 \\ % 34330 - 1374 ms (704+202+171+128+86+34+27+22) (#2-9)
        \hline
    \end{tabular}
    }
    \caption{Messung mit OpenJPA-Cache und Größe auf 1000}
    \label{tbl:measure-ojpa-active}
\end{table}

Bei einer erhöhten Cache"=Größe, von 1000 auf 10000, zeigt sich auf den ersten Blick ein noch besseres Bild ab, wie in
\autoref{tbl:measure-ojpa-active-bigger} ersichtlich ist. Der erste Aufruf entspricht der Laufzeit mit geringerer 
Cache"=Größe, aber schon die Anfragen an die Datenbank gehen drastisch zurück. Bei den weiteren Aufrufen werden im 
Schnitt nun nur noch 6 Anfragen pro Seitenaufruf an die Datenbank gestellt, wodurch die Laufzeit im Schnitt nochmal 
um 100 ms beschleunigt werden kann.

\begin{table}[H]
    \centering
    \resizebox{\textwidth}{!}{
    \begin{tabular}{|r|r|r|r|r|r|r|r|r|r|r|r|r|r|r|}
        \hline
        & \multicolumn{3}{c|}{Aufrufzeit (ms)} & \multicolumn{2}{c|}{Queries (ms)} & \multicolumn{3}{c|}{Memory (MB)} & \multicolumn{3}{c|}{Render (ms)} & \multicolumn{3}{c|}{DB-load (ms)} \\
        \hline
        \# & min & avg &  max & \#"=avg &   avg &  start &   stop & diff & min & avg &  max & min & avg & max \\
        \hline
        1 &  151 & 368 & 1904 &  141.2 &    20.8 & 906.3 &  936.8 & 30.5 & 164 & 404 & 2232 &  39 & 124 & 847 \\ % 1412 -  208 ms (133+ 40+ 23+9+2+1) (#2,4-6,10,12)
        2 &  133 & 143 &  159 &    6.0 &    20.5 & 935.7 &  939.3 &  3.6 & 121 & 136 &  146 &  32 &  36 &  44 \\ % 1472 -  413 ms (274+ 80+ 47+9+2+1) (#2-3,5,6,10,12)
        3 &  120 & 126 &  132 &    6.0 &    19.9 & 939.4 &  942.7 &  3.3 & 116 & 136 &  256 &  32 &  47 & 167 \\ % 1532 -  612 ms (412+119+ 69+9+2+1) (#2,3,5,6,10,12)
        4 &  120 & 124 &  128 &    6.0 &    21.4 & 944.3 &  945.4 &  1.1 & 105 & 113 &  125 &  30 &  32 &  39 \\ % 1592 -  826 ms (550+168+ 96+9+2+1) (#2-4,6,10,12)
        5 &  109 & 114 &  131 &    6.0 &    19.7 & 945.5 &  946.7 &  1.2 & 101 & 107 &  112 &  30 &  32 &  35 \\ % 1652 - 1023 ms (683+209+119+9+2+1) (#2-4,6,10,12)
        \hline
    \end{tabular}
    }
    \caption{Messung mit OpenJPA-Cache und Größe auf 10000}
    \label{tbl:measure-ojpa-active-bigger}
\end{table}

Bei dem deaktivieren der \textit{SoftReference} und dem kleineren Cache zeigt sich keine große Differenz, somit scheint
die \textit{SoftReference} nicht das Problem für den steigenden Arbeitsspeicher zu sein, wie in 
\autoref{tbl:measure-ojpa-active-bigger-no-softref} ersichtlich.

% document, documentaddresseeperson, first/last, documentcoauthorperson, count und documentfacsimile
\begin{table}[H]
    \centering
    \resizebox{\textwidth}{!}{
    \begin{tabular}{|r|r|r|r|r|r|r|r|r|r|r|r|r|r|r|}
        \hline
        & \multicolumn{3}{c|}{Aufrufzeit (ms)} & \multicolumn{2}{c|}{Queries (ms)} & \multicolumn{3}{c|}{Memory (MB)} & \multicolumn{3}{c|}{Render (ms)} & \multicolumn{3}{c|}{DB-load (ms)} \\
        \hline
        \# & min & avg &  max & \#"=avg &   avg &  start &   stop &  diff & min & avg &  max & min & avg & max \\
        \hline
        1 &  339 & 659 & 2435 &   880.8 &  33.2 &  909.6 &  960.2 &  50.6 & 330 & 644 & 2375 & 218 & 343 & 815 \\ %  8808 -  332 ms (168+ 63+ 44+ 32+21+ 4) (#1,3-6,10)
        2 &  267 & 332 &  388 &   832.1 &  28.1 &  959.7 & 1000.0 &  40.3 & 259 & 323 &  377 & 178 & 229 & 280 \\ % 17129 -  613 ms (313+111+ 82+ 48+42+17) (#1-3,5-6,8)
        3 &  265 & 397 &  350 &   830.3 &  27.3 & 1001.0 & 1107.0 & 106.0 & 256 & 288 &  241 & 172 & 204 & 252 \\ % 25432 -  886 ms (455+156+125+ 64+63+23) (#1-3,5-6,8)
        4 &  249 & 311 &  401 &   727.8 &  27.1 & 1108.0 & 1234.0 & 126.0 & 240 & 303 &  392 & 165 & 225 & 317 \\ % 32710 - 1157 ms (594+205+163+ 85+80+30) (#1-5,8)
        5 &  268 & 296 &  325 &   931.9 &  28.0 & 1236.0 & 1239.0 &   3.0 & 260 & 288 &  318 & 192 & 217 & 244 \\ % 42029 - 1437 ms (738+254+204+106+97+38) (#1-5,8)
        \hline
    \end{tabular}
    }
    \caption{Messung mit OpenJPA-Cache und Größe auf 1000 und 0 SoftReference}
    \label{tbl:measure-ojpa-active-bigger-no-softref}
\end{table}

Der Vergleich zeigt, dass der Cache eine gute Optimierung bringt, aber dies kann nur dann gut funktionieren, wenn immer
wieder die gleichen Objekte ermittelt werden. Sobald die Anfragen im Wechsel gerufen werden oder einfach nur die Menge
der Objekte den Cache übersteigt, fällt die Verbesserung geringer aus.

\section{Cached Queries}
\label{sec:performance-investigation-application:cached-query}

Über die Einstellung \textit{openjpa.""jdbc.""QuerySQLCache} wird der Cache für Abfragen aktiviert. Hierbei können 
Abfragen angeben werden, die aus dem Cache ausgeschlossen werden. Der QueryCache wiederrum beachtet aber nur Abfragen,
die keine Parameter verwenden. Das sieht man auch entsprechend der Auswertung der Aufrufe in 
\autoref{tbl:measure-cached-queries}, dass hier keine Veränderung der Aufrufzeiten stattgefunden hat, gleich ob  
mit \ac{JPQL} oder Criteria API abfragt wird.

% document, documentaddresseeperson, first/last, documentcoauthorperson, count und documentfacsimile
\begin{table}[H]
    \centering
    \resizebox{\textwidth}{!}{
    \begin{tabular}{|r|r|r|r|r|r|r|r|r|r|r|r|r|r|r|}
        \hline
        & \multicolumn{3}{c|}{Aufrufzeit (ms)} & \multicolumn{2}{c|}{Queries (ms)} & \multicolumn{3}{c|}{Memory (MB)} & \multicolumn{3}{c|}{Render (ms)} & \multicolumn{3}{c|}{DB-load (ms)} \\
        \hline
        \# & min & avg &  max & \#"=avg &   avg &  start &   stop &  diff & min & avg &  max & min & avg & max \\
        \hline
        1 &  409 & 771 & 2660 &  1222.4 &  32.8 &  850.4 &  982.8 & 132.4 & 366 & 633 & 2019 & 254 & 364 & 758 \\ % 12224 -  328 ms (140+ 90+ 43+ 22+20+13) (#1-6)
        2 &  336 & 387 &  504 &  1208.0 &  31.2 &  982.9 & 1113.0 & 130.1 & 310 & 374 &  433 & 221 & 268 & 345 \\ % 24304 -  640 ms (280+174+ 80+ 41+39+26) (#1-6)
        3 &  312 & 373 &  422 &  1208.0 &  31.1 & 1114.0 & 1221.0 & 107.0 & 295 & 401 &  658 & 216 & 320 & 570 \\ % 36384 -  951 ms (417+258+119+ 62+57+38) (#1-6)
        4 &  288 & 363 &  471 &  1208.0 &  31.3 & 1239.0 & 1474.0 & 235.0 & 269 & 356 &  486 & 200 & 279 & 405 \\ % 48464 - 1264 ms (557+341+159+ 82+75+49) (#1-6)
        5 &  325 & 398 &  535 &  1208.0 &  33.5 & 1474.0 & 1666.0 & 192.0 & 280 & 466 &  804 & 208 & 390 & 725 \\ % 60544 - 1599 ms (698+436+198+109+96+62) (#1-6)
        \hline
    \end{tabular}
    }
    \caption{Messung mit aktiviertem Cached Queries}
    \label{tbl:measure-cached-queries}
\end{table}

\section{Caching mit Ehcache}
\label{sec:performance-investigation-application:caching-ehcache}

Der Ehcache ist ein L2"=Cache, den man direkt in OpenJPA mit integrieren kann. Hierfür sind einige Punkte zu beachten.
Zum einen muss die Referenz auf das \textit{ehcache} und das \textit{ehcache"=openjpa} Paket hinzugefügt werden.
Zusätzlich dazu sind die Konfiguration \textit{openjpa.""QueryCache}, \textit{openjpa.""DataCache} und
\textit{openjpa.""DataCacheManager} auf \textit{ehcache} anzupassen. Anhand der Annotation \texttt{@DataCache} kann
an jeder Klasse die Benennung des Caches sowie die Verwendung selbst gesteuert werden. Es wird für jede Klasse ein 
eigener Cache angelegt und der Name auf den vollen Klassennamen gesetzt. Die Verwendung ist für alle Klassen
aktiviert und muss explizit deaktiviert werden, wenn dies nicht gewünscht ist. Als letztes muss noch der
Cache"=Manager aktiviert werden, dieser kann entweder durch Code programmiert werden oder über eine Konfiguration
in der \textit{ehcache.xml}.

Anhand der Auswertung von \autoref{tbl:measure-ehcache-active} sieht man, dass der Ehcache eine starke Performance
Verbesserung aufbringt. Über die Performance"=Statistik"=Webseite kann beobachtet werden, dass bei gleichen Aufrufen
der Webseite nur die Treffer in Cache steigen, aber die Misses nicht. Ebenfalls erhöht sich die Anzahl der Objekte
im Cache nicht. Zusätzlich steigt in diesem Fall der Speicherverbrauch nur gering bis gar nicht. Des Weiteren zeigt sich,
dass die Abfragezeiten in der Datenbank nur gering verkürzt wurden, aber die Laufzeit der Webseite sich stark
verbessert hat. Dies lässt auch hier den Schluss zu, dass die Erstellung der Objekte im OpenJPA die meiste Zeit
benötigt.

% document, documentaddresseeperson, first/last, documentcoauthorperson, count und documentfacsimile
\begin{table}[H]
    \centering
    \resizebox{\textwidth}{!}{
    \begin{tabular}{|r|r|r|r|r|r|r|r|r|r|r|r|r|r|r|}
        \hline
        & \multicolumn{3}{c|}{Aufrufzeit (ms)} & \multicolumn{2}{c|}{Queries (ms)} & \multicolumn{3}{c|}{Memory (MB)} & \multicolumn{3}{c|}{Render (ms)} & \multicolumn{3}{c|}{DB-load (ms)} \\
        \hline
        \# & min & avg &  max & \#"=avg &   avg &  start &   stop &  diff & min & avg &  max & min & avg &  max \\
        \hline
        %- &  151 & 368 & 1904 &  141.2 &    20.8 &  906.3 &  936.8 &  30.5 & 164 & 404 & 2232 &  39 & 124 & 847  \\ %  1412 -  208 ms (133+ 40+ 23+9+2+1) (#2,4-6,10,12)
        1 &  156 & 488 & 2820 &   135.2 &  20.7 &  981.6 & 1006.0 &  24.4 & 147 & 490 & 2809 &  39 & 175 & 1186 \\ % 1352 -  207 ms (136+ 35+ 19+12+3+2) (#1-4,7-8)
        2 &  135 & 144 &  166 &     6.0 &  20.1 & 1006.0 & 1007.0 &   1.0 & 124 & 136 &  157 &  33 &  38 &   47 \\ % 1412 -  408 ms (272+ 77+ 42+12+3+2) (#1-4,7-8)
        3 &  121 & 129 &  136 &     6.0 &  19.4 & 1008.0 & 1009.0 &   1.0 & 113 & 121 &  126 &  32 &  34 &   33 \\ % 1472 -  602 ms (407+115+ 63+12+3+2) (#1-4,7-8)
        4 &  116 & 123 &  133 &     6.0 &  19.7 & 1008.0 & 1016.0 &   8.0 & 108 & 116 &  125 &  31 &  33 &   34 \\ % 1532 -  799 ms (542+155+ 85+12+3+2) (#1-4,7-8)
        5 &  111 & 118 &  127 &     6.0 &  12.7 & 1016.0 & 1012.0 &  -4.0 & 104 & 111 &  119 &  32 &  34 &   38 \\ % 1592 -  926 ms (608+194+107+12+3+2) (#1-4,7-8)
        \hline
    \end{tabular}
    }
    \caption{Messung mit aktiviertem Ehcache}
    \label{tbl:measure-ehcache-active}
\end{table}

\section{Caching in EJB}
\label{sec:performance-investigation-application:caching-ejb}

Die Cache"=Einstellungen des \ac{EJB} sind in der Admin-Oberfläche des Payara-Servers zu erreichen. Unter dem Punkt
Configurations $\Rightarrow$ server"=config $\Rightarrow$ \ac{EJB} Container wird zum einen die minimalen und maximalen 
Größen des Pools definiert. Zum anderen wird an dieser Stelle die maximale Größe des Caches und die Größe der
Erweiterung definiert.

Anhand der Auswertung der \autoref{tbl:measure-ejb-cache-active} ist ersichtlich, dass der \ac{EJB}"=Cache keine
Auswirkung auf die Performance hat. Ebenso ist es ersichtlich, dass die Anzahl der Datenbankabfragen nicht reduziert
werden. Dies ist dadurch zu erklären, dass im \ac{EJB} die Provider gelagert sind, die über Dependency Injection
dem Controller bereitgestellt werden. Die Objekte selbst sind nicht im \ac{EJB}"=Cache hinterlegt.

% document, documentaddresseeperson, first/last, documentcoauthorperson, count und documentfacsimile
\begin{table}[H]
    \centering
    \resizebox{\textwidth}{!}{
    \begin{tabular}{|r|r|r|r|r|r|r|r|r|r|r|r|r|r|r|}
        \hline
        & \multicolumn{3}{c|}{Aufrufzeit (ms)} & \multicolumn{2}{c|}{Queries (ms)} & \multicolumn{3}{c|}{Memory (MB)} & \multicolumn{3}{c|}{Render (ms)} & \multicolumn{3}{c|}{DB-load (ms)} \\
        \hline
        \# & min & avg &  max & \#"=avg &   avg &  start &   stop &  diff & min & avg & max & min & avg & max \\
        \hline
        1 &  364 & 741 & 2962 &  1222.1 &    29.4 &  880.6 &  991.7 & 111.1 & 353 & 725 & 2902 & 248 & 366 & 689  \\ % 12221 -  294 ms (135+ 73+ 41+ 20+16+ 9) (#1,2,4-7)
        2 &  318 & 378 &  460 &  1208.0 &    31.0 &  992.4 & 1099.0 & 106.6 & 310 & 370 &  451 & 225 & 275 & 362  \\ % 24301 -  604 ms (274+154+ 80+ 42+34+20) (#1-3,5-7)
        3 &  314 & 397 &  528 &  1208.0 &    32.5 & 1109.0 & 1308.0 & 199.0 & 306 & 388 &  519 & 227 & 307 & 434  \\ % 36381 -  929 ms (411+245+122+ 63+54+34) (#1-3,5-7)
        4 &  334 & 371 &  420 &  1208.0 &    32.7 & 1308.0 & 1528.0 & 220.0 & 326 & 363 &  412 & 246 & 289 & 333  \\ % 48461 - 1256 ms (557+333+163+ 84+73+46) (#1-5,7)
        5 &  304 & 392 &  562 &  1208.0 &    33.3 & 1518.0 & 1662.0 & 144.0 & 297 & 385 &  555 & 229 & 311 & 478  \\ % 60541 - 1589 ms (697+431+202+104+95+60) (#1-5,7)
        \hline
    \end{tabular}
    }
    \caption{Messung mit EJB"=Cache}
    \label{tbl:measure-ejb-cache-active}
\end{table}

\section{Abfragen JPQL}
\label{sec:performance-investigation-application:query-jpql}

Für die \ac{JPQL} wird ein \ac{SQL} ähnlicher Syntax verwendet, um die Abfragen an die Datenbank durchzuführen. Der 
nachfolgende Code für die Dokumentenliste, welche in \ac{JPQL} geschrieben ist, ist aus dem DFG"=Projekt 
\citep{DFGProje43:online} entnommen. Das \autoref{lst:jpql-document-list-jpql} zeigt den \ac{JPQL}"=Code welcher unter
dem Namen \textit{Document.""findAll} hinterlegt ist, um die Daten aus dem \textit{PostgreSQL}"=Server zu ermitteln. 
Die enthaltenen Namen mit vorangestellten Doppelpunkt sind Übergabevariablen, um die Auswahl einzuschränken.

\begin{lstlisting}[language=Java,caption={JPQL Dokumentenliste},label=lst:jpql-document-list-jpql]
SELECT DISTINCT d FROM Document d
LEFT   JOIN FETCH d.authorPerson
LEFT   JOIN FETCH d.coauthorPersonSet
LEFT   JOIN FETCH d.addresseePersonSet
WHERE  d.validUntil > :now
AND    d.isPublishedInDb = :published
ORDER  BY d.documentId ASC
\end{lstlisting}

Der dazugehörige Code im Server ist in \autoref{lst:jpql-document-list} zu finden und zeigt wie die benannte Anfrage 
aufgerufen und die ermittelt Daten übernimmt. Zusätzlich wird an dieser Stelle die Parameter versorgt, die Grenzwerte
der Paginierung und die Hints hinterlegt.

\begin{lstlisting}[language=Java,caption={Java JPQL Dokumentenliste},label=lst:jpql-document-list]
List<Document> myResultList = createNamedTypedQuery("Document.findAll")
   .setParameter("now", _IncludeDeleted ? new Date(0) : Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant()))
   .setParameter("published", true)
   .setFirstResult(_Start)
   .setMaxResults(_Size)
   .setHint("javax.persistence.query.fetchSize", _Size)
   .getResultList();

// Uebergabe der Ergebnisliste
if(myResultList != null && !myResultList.isEmpty()) {
    myResult.addAll(myResultList);
}
\end{lstlisting}

Da dieser Code direkt so aus dem Projekt stammt, wird hierfür keine gesonderte Zeitmessung durchgeführt, da diese der
Messung aus \autoref{tbl:measure-without-cache} entspricht.

Für die Optimierung wurden noch zusätzlich die Hints \texttt{openjpa.""hint.""OptimizeResultCount},
\texttt{javax.""persistence.""query.""fetchSize} und \texttt{openjpa.""FetchPlan.""FetchBatchSize} gesetzt. Hierbei
konnten je nach gesetzten Wert, keine relevanten Unterschiede festgestellt werden. Hierbei wurde der Wert auf zwei 
gesetzt, welcher viel zu gering ist. Als weiterer Test wurde der Wert auf die angefragte Größe und den 
20"=fachen Wert dieser Größe gestellt.

Ebenso bringt der Hint \texttt{openjpa.""FetchPlan.""ReadLockMode} auch keinen Unterschied bei der Geschwindigkeit.
Dies ist dadurch erklärbar, dass im Standard bei einer reinen Selektion eine Lesesperre aktiv sein muss.
Bei \texttt{openjpa.""FetchPlan.""Isolation} wird gesteuert, auf welche Sperren beim Laden geachtet wird. Damit könnte
man lediglich Schreibsperren umgehen und würde damit die Anfrage nicht mehr blockieren lassen, jedoch führt es unweigerlich
zu sogenannten >>Dirty"=Reads<<, wodurch die Ausgabe verfälscht werden könnte. Daher ist diese Einstellung 
mit Vorsicht zu verwenden.

Mit dem Hint \texttt{openjpa.""FetchPlan.""EagerFetchMode} wird definiert, wie zusammengehörige Objekte abgefragt werden.
Bei dem Wert \textit{none} werden nur die Basis"=Daten abgefragt und jedes weitere Objekt wird in einem eigenen
Statement abgefragt. Mit \textit{join} wird definiert, dass abhängige Objekte, die als >>to-one<<"=Relation
definiert sind, in der Abfrage über einen Join verknüpft und damit direkt mitgeladen werden. Bei reinen 
>>to-one<<"=Relation funktioniert das Rekursiv und spart sich damit einige einzelne Abfragen.
Bei der Einstellung \textit{parallel} wird zwar für jede abhängige Objektdefinition eine Abfrage ausgeführt und
diese werden direkt auf die Hauptobjekte gefiltert und die Verknüpfung im OpenJPA"=Framework durchgeführt.
Somit muss in diesem Beispiel nicht für jedes Dokument eine einzelne Abfrage für die Koautoren durchgeführt werden, 
es wird lediglich eine Abfrage für alle Dokumente, welche ermittelt wurden, abgesetzt. Technisch gesehen wird die gleiche 
WHERE"=Abfrage nochmal durchgeführt und um die JOINS ergänzt, um die Daten der Unterobjekte zu ermitteln.
Mit dem Hint \texttt{openjpa.""FetchPlan.""SubclassFetchMode} ist die Konfiguration für Unterklassen definiert. Die
Möglichkeiten entsprechen der vom \texttt{openjpa.""FetchPlan.""EagerFetchMode}.

Beim Umstellen der 2 Hints auf \textit{parallel} wird die Bearbeitungszeit fast halbiert und Anzahl der Datenbankaufrufe
wurde fast geviertelt. Dies zeigt, dass die einzelnen Aufrufe je Dokument aufwendiger sind als eine komplette Abfrage
der abhängigen Daten und das zusammensetzen in der OpenJPA-Schicht.

Der letzte Hint \texttt{openjpa.""FetchPlan.""MaxFetchDepth} schränkt die rekursive Tiefe ein, für die abhängige
Objekte mitgeladen werden. Lediglich auf Grund fehlender Datenbestände wird die Abfrage beschleunigt und ist daher 
in diesem Fall nicht zu verwenden.

\section{Abfragen Criteria API}
\label{sec:performance-investigation-application:query-criteria-api}

Für die Criteria API wird die Abfrage nicht in einem \ac{SQL}"=Dialekt beschrieben, hierbei werden über Attribute die
Verlinkung zur Datenbank durchgeführt. An der Klasse selbst wird der Tabellenname definiert und an den Attributen die
Spaltennamen. Um die Anfrage durchzuführen, muss nun nur noch die Datenklasse angegeben und mit den Parametern
versorgt werden, wie es in \autoref{lst:criteria-api} gezeigt wird. 

\begin{lstlisting}[language=Java,caption={Criteria API Dokumentenliste},label=lst:criteria-api]
CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
CriteriaQuery<Document> cq = cb.createQuery(Document.class);
Root<Document> from = cq.from(Document.class);
ParameterExpression<Boolean> includedPara = cb.parameter(Boolean.class, "published");
ParameterExpression<Date> validPart = cb.parameter(Date.class, "now");

CriteriaQuery<Document> select = cq.select(from)
    .where(cb.and(
        cb.equal(from.get("isPublishedInDb"), includedPara),
        cb.greaterThan(from.get("validUntil"), validPart)
   ));
TypedQuery<Document> typedQuery = getEntityManager().createQuery(select)
    .setParameter("now", _IncludeDeleted ? new Date(0) : Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant()))
    .setParameter("published", true)
    .setFirstResult(_Start)
    .setMaxResults(_Size)
    .setHint("javax.persistence.query.fetchSize", _Size);
List<Document> myResultList = typedQuery.getResultList();

// Uebergabe der Ergebnisliste
if (myResultList != null && !myResultList.isEmpty()) {
    myResult.addAll(myResultList);
}
\end{lstlisting}

Wie in der Messung in \autoref{tbl:measure-criteria-api} zu sehen, unterscheiden sich die Abfragezeiten nur marginal 
von denen mit \ac{JPQL}. Wenn man sich den Code im Debugger betrachtet, lässt sich erkennen, dass die zusammengesetzten 
Abfragen in den Java-Objekten fast identisch sind. In der Datenbank sind die Anfragen identisch zu denen über JPQL.

\begin{table}[H]
    \centering
    \resizebox{\textwidth}{!}{
    \begin{tabular}{|r|r|r|r|r|r|r|r|r|r|r|r|r|r|r|}
        \hline
        & \multicolumn{3}{c|}{Aufrufzeit (ms)} & \multicolumn{2}{c|}{Queries (ms)} & \multicolumn{3}{c|}{Memory (MB)} & \multicolumn{3}{c|}{Render (ms)} & \multicolumn{3}{c|}{DB-load (ms)} \\
        \hline
        \# & min & avg &  max & \#"=avg &   avg &  start &   stop &  diff & min & avg &  max & min & avg & max \\
        \hline
        1 &  429 & 704 & 2472 &  1224.4 &  27.0 &  848.5 &  928.2 &  79.7 & 419 & 687 & 2400 & 276 & 368 & 732 \\ % 12244 -  270 ms (120+ 66+ 41+ 20+12+11) (#1-6)
        2 &  327 & 396 &  482 &  1208.0 &  30.1 &  929.3 & 1151.0 & 221.7 & 318 & 383 &  472 & 216 & 284 & 339 \\ % 24324 -  571 ms (257+138+ 82+ 44+26+24) (#1-6)
        3 &  322 & 397 &  507 &  1208.0 &  28.6 & 1151.0 & 1304.0 & 153.0 & 312 & 389 &  498 & 232 & 308 & 420 \\ % 36404 -  857 ms (370+219+123+ 66+41+38) (#1-6)
        4 &  306 & 351 &  416 &  1208.0 &  27.1 & 1303.0 & 1439.0 & 136.0 & 298 & 341 &  401 & 218 & 261 & 323 \\ % 48484 - 1128 ms (489+284+163+ 91+53+48) (#1-6)
        5 &  288 & 357 &  448 &  1208.0 &  27.1 & 1440.0 & 1580.0 & 140.0 & 279 & 348 &  441 & 201 & 271 & 360 \\ % 60564 - 1399 ms (603+354+205+113+65+59) (#1-6)
        \hline
    \end{tabular}
    }
    \caption{Messung mit Criteria-API ohne Cache}
    \label{tbl:measure-criteria-api}
\end{table}

Daher bringt die Criteria API keinen Performance Vorteil gegenüber der JPQL"=Implementierung. Somit können beide 
Implementierungen ohne Bedenken gegeneinander ausgetauscht werden, und die verwendet werden, die für den Anwendungsfall
einfacher umzusetzen ist.

Bei den Hints ist es das Gleiche wie bei \ac{JPQL}. Auch hier haben die meisten Hints keinen merkbaren Einfluss. Die
Einstellung \texttt{openjpa.""FetchPlan.""EagerFetchMode} liefert auch hier Optimierungen, wenn der Wert auf
\textit{parallel} gestellt wird. Hier wird ebenfalls die Anzahl der Anfragen reduziert und damit auch die
Geschwindigkeit optimiert.

\section{Materialized Views}
\label{sec:performance-investigation-application:materialized-views}

\textit{Materialized Views} sind Sichten in der Datenbank, die beim Erstellen der Sicht den aktuellen Zustand ermitteln 
und zwischenspeichern. Somit wird beim Zugriff auf diese Sichten nicht die hinterlegte Abfrage ausgeführt, sondern auf
die gespeicherten Daten zugegriffen. Dies ist gerade bei vielen Joins von Vorteil. Zusätzlich können auf solchen
Sichten auch Indexe erstellt werden, um noch effektiver die Abfragen bearbeiten zu können.

Der größte Nachteil dieser Sichten ist, dass sie zyklisch oder bei Datenänderungen aktualisiert werden müssen, sonst
läuft der Datenbestand der Sicht und der zugrundeliegenden Abfrage auseinander. Da die Hauptarbeiten auf der Webseite
die Abfrage der Daten ist und nicht das Editieren, kann dieser Nachteil bei entsprechender Optimierung ignoriert werden.

In diesem Test wurde die aktuelle Implementierung aus dem Wedekind"=Projekt der \textit{Materialized View} inklusive 
der Trigger und der \textit{SearchDocument}"=Klasse übernommen \citep{Dokument53:online}. Wie in 
\autoref{lst:sql-materialized-view} zu sehen, wurden zur Standard"=Abfrage die sonst zusätzlichen Abfragen als 
direkte Sub"=Selects mit integriert. Der Datenbestand dieser Sub"=Selects wird im Json"=Format angegeben, damit bei 
den Koautoren und den Adressen mehrere Datensätze in einer Zeile zurückgegeben werden können. Ohne diese Technik würde 
sich die Anzahl der Dokumente vervielfachen.

\begin{lstlisting}[language=SQL,caption={SQL Materialized View},label=lst:sql-materialized-view]
CREATE MATERIALIZED VIEW searchdocument AS
SELECT
    d.id, d.documentid, d.datetype, d.startdatestatus, d.startyear,
    d.startmonth, d.startday, d.enddatestatus, d.endyear, d.endmonth,
    d.endday,
    (
        SELECT
            jsonb_build_object(
                'personId', hp.personid,
                'surname', hp.surname,
                'firstname', hp.firstname,
                'dateBirth', json_build_object(
                    'year', hp.birthstartyear,
                    'month', hp.birthstartmonth,
                    'day', hp.birthstartday
                ),
                'dateDeath', json_build_object(
                    'year', hp.deathstartyear,
                    'month', hp.deathstartmonth,
                    'day', hp.deathstartday
                )
            )
        FROM  historicalperson hp
        WHERE hp.id = d.authorperson_id
        AND   hp.validuntil > NOW()
    ) as author,
    (
        SELECT
            jsonb_agg(jsonb_build_object(
                'personId', hcap.personid,
                'surname', hcap.surname,
                'firstname', hcap.firstname,
                'dateBirth', json_build_object(
                    'year', hcap.birthstartyear,
                    'month', hcap.birthstartmonth,
                    'day', hcap.birthstartday
                ), 
                'dateDeath', json_build_object(
                    'year', hcap.deathstartyear,
                    'month', hcap.deathstartmonth,
                    'day', hcap.deathstartday
                )
            ))
        FROM  documentcoauthorperson dcap
        JOIN  historicalperson hcap
                    ON  hcap.id = dcap.authorperson_id
                    AND dcap.validuntil > NOW()
                    AND hcap.validuntil > NOW()
        WHERE dcap.document_id = d.id
    ) AS coauthors,
    (
        SELECT
            jsonb_agg(jsonb_build_object(
                'personId', hap.personid,
                'surname', hap.surname,
                'firstname', hap.firstname,
                'dateBirth', json_build_object(
                    'year', hap.birthstartyear,
                    'month', hap.birthstartmonth,
                    'day', hap.birthstartday
                ),
                'dateDeath', json_build_object(
                    'year', hap.deathstartyear,
                    'month', hap.deathstartmonth,
                    'day', hap.deathstartday
                )
            ))
        FROM  documentaddresseeperson dap
        JOIN  historicalperson hap
                    ON  hap.id = dap.addresseeperson_id
                    AND dap.validuntil > NOW()
                    AND hap.validuntil > NOW()
        WHERE dap.document_id = d.id
    ) AS addressees,
    sc.city, d.documentcategory, d.ispublishedindb, d.createdat,
    d.modifiedat, d.validuntil
FROM document d
LEFT JOIN sitecity sc ON sc.id = d.city_id;
\end{lstlisting}

Zusätzlich zur View, werden noch die Indexe aus \autoref{lst:sql-materialized-view-index} erstellt. Diese werden für
eine bessere Performance der Abfrage benötigt.

\begin{lstlisting}[language=SQL,caption={SQL Materialized View},label=lst:sql-materialized-view-index]
CREATE INDEX idx_searchdocument_documentid
    ON searchdocument (documentid);

CREATE INDEX idx_searchdocument_author_surname_firstname
    ON searchdocument ((author->>'surname'), (author->>'firstname'));

CREATE INDEX idx_searchdocument_startdate
    ON searchdocument (startyear, startmonth, startday);

CREATE INDEX idx_searchdocument_addressees_first_entry
    ON searchdocument
        ( ((addressees->0->>'surname')::text)
        , ((addressees->0->>'firstname')::text));

CREATE INDEX idx_searchdocument_city
    ON searchdocument (city);

CREATE INDEX idx_searchdocument_documentcategory
    ON searchdocument (documentcategory);
\end{lstlisting}

Für die Datenermittlung wurden die notwendigen Teile aus dem Wedekind"=Projekt kopiert. Bei der Darstellung wurden die
vorhanden Elemente, welche die Liste der Dokumente anzeigt, kopiert und auf die \texttt{SearchDocument}"=Klasse angepasst.

% document, first/last, documentaddresseeperson, documentcoauthorperson, documentfacsimile und count
% document, count, first/last
\begin{table}[H]
    \centering
    \resizebox{\textwidth}{!}{
    \begin{tabular}{|r|r|r|r|r|r|r|r|r|r|r|r|r|r|r|}
        \hline
        & \multicolumn{3}{c|}{Aufrufzeit (ms)} & \multicolumn{2}{c|}{Queries (ms)} & \multicolumn{3}{c|}{Memory (MB)} & \multicolumn{3}{c|}{Render (ms)} & \multicolumn{3}{c|}{DB-load (ms)} \\
        \hline
        \# & min & avg &  max & \#"=avg &   avg & start &   stop &  diff & min & avg &  max & min & avg & max \\
        \hline
        1 &  232 & 424 & 1486 &    14.3 &   1.4 & 828.2 &  929.3 & 101.1 & 222 & 408 & 1404 & 138 & 208 & 393 \\ % 143 -  14 ms (13+1+0) (#1,3,6)
        2 &  154 & 182 &  219 &     7.0 &   1.2 & 939.9 &  941.2 &   1.3 & 145 & 174 &  209 &  81 & 103 & 132 \\ % 213 -  26 ms (25+1+0) (#1,3,5)
        3 &  139 & 147 &  163 &     7.0 &   1.3 & 941.1 &  949.2 &   8.1 & 131 & 140 &  156 &  76 &  80 &  88 \\ % 283 -  39 ms (38+1+0) (#1,4,5)
        4 &  128 & 134 &  141 &     7.0 &   1.3 & 946.0 &  946.6 &   0.6 & 121 & 127 &  133 &  72 &  75 &  78 \\ % 353 -  52 ms (51+1+0) (#1,4,5)
        5 &  123 & 129 &  134 &     7.0 &   1.5 & 946.7 &  947.8 &   1.1 & 116 & 122 &  127 &  65 &  68 &  72 \\ % 423 -  67 ms (66+1+0) (#1,4,5)
        \hline
    \end{tabular}
    }
    \caption{Messung mit Materialized View}
    \label{tbl:measure-materialized-view}
\end{table}

Wie in \autoref{tbl:measure-materialized-view} zu sehen ist, bringt die Verwendung der \textit{Materialized View} eine 
Verbesserung in verschiedenen Punkten. Zum einen ist eine Verbesserung der Aufrufzeiten zu erkennen, zum anderen fällt 
der Speicheranstieg weniger stark aus. Die Verbesserung der Aufrufzeiten lässt sich zusätzlich erklären, dass hier nun
nur noch vier statt der sechs Abfragen an die Datenbank gestellt werden, da die Einzelabfragen für die Adressen der 
Personen und der Koautoren komplett entfallen.

Nach einer weiteren Untersuchung des Quellcodes wird festgestellt, dass bei jeder Anfrage die gleichen Bedingungen
benötigt werden. Da die Sicht nun explizit für diese Anfrage geschaffen ist, werden die Bedingungen nun direkt in die 
Sicht mit integriert. Dies bedeutet eine Erweiterung der Sicht aus \autoref{lst:sql-materialized-view} um
\autoref{lst:sql-materialized-view-ext} und das Entfernen der Parameter aus dem \ac{SQL}"=Anfragen im Java"=Code.

\begin{lstlisting}[language=SQL,caption={SQL Materialized View Erweiterung},label=lst:sql-materialized-view-ext]
WHERE d.validuntil > NOW()
AND   d.ispublishedindb = true;
\end{lstlisting}

Nach den Anpassungen haben sich dann die Werte aus \autoref{tbl:measure-materialized-view-ext} ergeben. Diese Werte
zeigen nur minimale Unterschiede in den Zeiten, diese sind auf Messtoleranzen zurückzuführen.

\begin{table}[H]
    \centering
    \resizebox{\textwidth}{!}{
    \begin{tabular}{|r|r|r|r|r|r|r|r|r|r|r|r|r|r|r|}
        \hline
        & \multicolumn{3}{c|}{Aufrufzeit (ms)} & \multicolumn{2}{c|}{Queries (ms)} & \multicolumn{3}{c|}{Memory (MB)} & \multicolumn{3}{c|}{Render (ms)} & \multicolumn{3}{c|}{DB-load (ms)} \\
        \hline
        \# & min & avg & max & \#"=avg &   avg &  start &   stop &  diff & min & avg & max & min & avg & max \\
        \hline
        1 &  241 & 348 & 859 &    16.8 &   2.5 &  896.0 &  932.4 &  36.4 & 232 & 331 & 803 & 132 & 174 & 334 \\ % 168 -  25 ms (14+11+0) (#1,2,8)
        2 &  164 & 194 & 225 &     9.0 &   2.4 &  933.3 &  935.9 &   2.6 & 154 & 185 & 215 &  79 &  99 & 117 \\ % 258 -  49 ms (30+18+1) (#1,2,8)
        3 &  147 & 161 & 179 &     9.0 &   2.4 &  935.8 &  938.8 &   3.0 & 139 & 152 & 167 &  68 &  77 &  86 \\ % 348 -  73 ms (45+27+1) (#1,2,7)
        4 &  135 & 145 & 183 &     9.0 &   2.4 &  939.4 &  936.0 &  -3.4 & 127 & 137 & 174 &  70 &  73 &  75 \\ % 438 -  97 ms (61+35+1) (#1,2,7)
        5 &  126 & 137 & 154 &     9.0 &   2.4 &  936.1 &  939.1 &   3.0 & 118 & 129 & 143 &  66 &  72 &  79 \\ % 528 - 121 ms (76+44+1) (#1,2,7)
        \hline
    \end{tabular}
    }
    \caption{Messung mit erweiterter Materialized View}
    \label{tbl:measure-materialized-view-ext}
\end{table}

Da bei der \textit{Materialized View} das Laden der Daten und das Wandeln in die Java"=Objekte getrennt programmiert 
wurde, können hier eigene Zeitmessungen für die zwei Schritte eingebaut werden. Hierfür wird die Zeit vor dem
\texttt{map}"=Aufruf und der \texttt{map}"=Aufruf gemessen. Für die erste Messung wurde vor der Datenbankabfrage ein 
\texttt{SearchDocument} Objekt erzeugt und dieses in jedem \texttt{map}"=Aufruf zurückgegeben. Mit diesem Aufbau wurde
die Zeit ermittelt, um die Daten aus der Datenbank zu laden und dieses Ergebnis einmalig zu durchlaufen, ohne ein
Objekt zu erstellen. Hierbei lagen die Zeiten bei circa 1 ms für das reine Laden der Daten und 3 ms
für den Aufruf der \texttt{map}"=Funktion. Sobald innerhalb der \texttt{map}"=Funktion pro Eintrag ein Objekt
erzeugt wird, ohne die Konvertierung der ermittelten Daten in das Objekt, steigt die Laufzeit bereits auf 54 ms.
Wenn man nun noch die Konvertierung der Daten mit einbaut, erhöht sich die Laufzeit nochmals, auf nun 82 ms.
Alleine für das Erzeugen der Objekte und der Json"=Parse Aufrufe wird die meiste Zeit aufgewendet.

Bei der Verwendung des Hints \texttt{openjpa.""FetchPlan.""FetchBatchSize} kann sich die Abfrage enorm verschlechtern. Wenn
dieser Wert zu klein oder groß definiert ist, wird die Laufzeit verschlechtert. Bei einem zu großen Wert wird die
Laufzeit der Datenbankanfrage auf circa 20 ms verlängert. Wenn der Wert zu gering gewählt ist, dann wird zwar die
Laufzeit der Datenbankanfrage minimal verkürzt, aber die \texttt{map}"=Funktion wird dadurch verlängert.

Das Aktivieren der Cache"=Optionen wie in \autoref{sec:performance-investigation-application:caching-openjpa} oder in
\autoref{sec:performance-investigation-application:cached-query} dargestellt, haben keine Auswirkung auf die Performance.
Dies ist dadurch erklärbar, dass keine Objekte durch das OpenJPA"=Framework erstellt werden, sondern erst in der
\texttt{map}"=Funktion des eigenen Codes und daher wird der Cache nicht genutzt.

Wie schon ermittelt, benötigt das Erstellen der Objekte den Großteil der Zeit für die Datenermittlung. Auf Grund dessen
wurde die übernommene \texttt{SearchDocument}"=Klasse abermals genauer betrachtet. Beim Erstellen werden die
\textit{Json}"=Daten direkt in Java"=Objekte gewandelt. Im ersten Schritt wird die Parse"=Funktion entfernt und die 
Seite nochmals aufgerufen. Durch diese Umstellung fällt die Laufzeit der Datenermittlung auf circa 4 ms ab. Nun muss 
noch überprüft werden, welche Zeit nun der Client zum parsen der \textit{Json}"=Daten benötigt. Hierfür werden die 
Daten in einem versteckten \texttt{span}"=Element hinterlegt, wie es im \autoref{lst:jsf-datatable-json} zu sehen ist. 
Die hinterlegte \textit{\ac{CSS}}"=Klasse ist zum Auffinden der Elemente für den späteren Javascript. Das 
\textit{ajax}"=Element im Beispiel ist notwendig, damit bei einem Seitenwechsel die gleiche Interpreter"=Funktion für 
die \textit{Json}"=Daten aufgerufen wird, wie beim Laden der Webseite.

\begin{lstlisting}[language=xml,caption={DataTable mit Json},label=lst:jsf-datatable-json]
<p:ajax event="page" oncomplete="convertJsonData()"/>
<p:column id="author" headerText="#{lang.List_Docs_Author}" sortable="true" sortBy="#{myObj.surname}">
    <h:outputText styleClass="json-convert" style="display: none;" value="#{myObj.authorJson}"/>
</p:column>
<p:column id="Addressee"
          headerText="#{lang.List_Docs_Addressee}"
          sortable="true"
          sortBy="#{(myObj.addresseePersonSet!=null and myObj.addresseePersonSet.size() > 0)?myObj.addresseePersonSet[0].addresseePerson:''}">
    <h:outputText styleClass="json-convert" style="display: none;" value="#{myObj.adresseeJson}" data="abc"/>
</p:column>
\end{lstlisting}

Die Interpreter"=Funktion, welche in JavaScript geschrieben ist, wird benötigt, um die übertragenen 
\textit{Json}"=Daten in eine darstellbare Form zu bringen. Die Funktion aus dem \autoref{lst:jsf-datatable-json-convert} 
ermittelt erst alle versteckten Elemente, deserialisiert den Inhalt und erstellt neue \textit{\ac{HTML}}"=Elemente mit dem 
darzustellenden Inhalt. Zusätzlich wird noch eine Zeitmessung eingebaut, um die Laufzeit am Client für das Rendern 
in der Konsole anzuzeigen. Die Funktion wird nun direkt nach dem die Webseite fertig geladen wurde aufgerufen. 

\begin{lstlisting}[language=javascript,caption={Wandeln von Json nach Html},label=lst:jsf-datatable-json-convert]
function isEmpty(str) {
  return (str === null) || (str === undefined) || (typeof str === "string" && str.length === 0);
}
function convertJsonData() {
  let $jsonObj = $(".json-convert")
    , start = new Date()
    ;

  $.each($jsonObj, function() {
    let json = this.innerHTML
      , strEmpty = (json === null) || (typeof json === "string" && json.length === 0)
      , jsonDat = strEmpty ? null : JSON.parse(json)
      ;
    if(!strEmpty) {
      let res = ""
        , $that = $(this)
        , $par = $that.parent()
        ;
      $.each(jsonDat, function() {
        let hasOnlyOne = isEmpty(this.surname) || isEmpty(this.firstname)
          , pseudonymExists = !isEmpty(this.pseudonym)
          , namePart = "<span>" + (hasOnlyOne ? this.surname + this.firstname : this.surname + ", " + this.firstname) + "</span><br/>"
          , pseudoPart = pseudonymExists ? "<span class='w3-small w3-text-dark-gray'>" + this.pseudonym + "</span><br/>" : ""
          ;
        res += namePart + pseudoPart;
      });
      $par.append(res);
    }
  });
  let end = new Date()
    , diff = (end - start)
    ;
  console.log(Math.round(diff) + " ms");
}

$(document).ready(function() {
    convertJsonData();
});    
\end{lstlisting}

Da nun am Client der Code ausgeführt wird, nachdem die Daten übertragen wurden, kann nicht mehr alles über das Script
durchgeführt werden. Daher werden nun die Laufzeiten am Server und am Client zusammenaddiert. Im Schnitt benötigt der
Aufruf auf der Serverseite nun 70 ms und am Client sind es circa 13 ms. Die summierte Laufzeit von Client und Server 
ist geringer als die reine Serverlösung und erzeugt gleichzeit weniger Last am Server.

\section{Optimierung der Abfrage}
\label{sec:performance-investigation-application:optimizing-query}

Für die Optimierung der Abfragen werden diese zuerst mit \texttt{EXPLAIN}, wie in \autoref{lst:explain-diagnostic}
dargestellt, untersucht. Für die einfachere Diagnose wird der erstellte Plan mit Hilfe von 
\textit{Postgres Explain Visualizer 2} (\url{https://github.com/dalibo/pev2}) visualisiert.

\begin{lstlisting}[language=SQL,caption={Explain für Diagnose},label=lst:explain-diagnostic]
explain (analyze, verbose, buffers, summary, format json)
select <Spalten>
from
    public.document t0
    left outer join public.historicalperson t1 on t0.authorperson_id = t1.id
    left outer join public.sitecity t5 on t0.city_id = t5.id
    left outer join public.appuser t6 on t0.editor_id = t6.id
    left outer join public.extendedbiography t2 on t1.extendedbiography_id = t2.id
    left outer join public.sitecity t3 on t1.sitecity_birth_id = t3.id
    left outer join public.sitecity t4 on t1.sitecity_death_id = t4.id
    left outer join public.appuserrole t7 on t6.appuserrole_id = t7.id
where (t0.validuntil > NOW()
    and t0.ispublishedindb = true)
order by startyear DESC, startmonth DESC, startday DESC
limit 400;
\end{lstlisting}

Die erstellte Visualisierung der Abfrage ist in \autoref{fig:explain-visualize} zu sehen. In der Visualisierung wurde
die Darstellung der Kosten gewählt, da ein Vergleich auf Basis der Zeit sehr schwierig ist und von äußeren Faktoren
abhängt, wie zum Beispiel dem Cache. Die Kosten sind stabiler und hängen in erster Linie von der Art des Datenbestandes ab.

\begin{figure}[H]
    \includegraphics[width=\linewidth]{gfx/chapter05_ExplainVisualize.png}
    \caption{Visualisierung EXPLAIN}
    \label{fig:explain-visualize}
\end{figure}

In der Graphik ist zu sehen, dass zum einen die Hauptkosten im untersten Knoten \textit{Seq Scan} und einen der
obersten Knoten dem \textit{HashAggregate} liegen. Zusätzlich sieht man anhand der Stärke von den Verbindungslinien der
Knoten, dass die Menge der Datensätze enorm hoch ist und diese sich bis zum obersten Knoten durchziehen. Dies 
bedeutet, dass die Einschränkung des Datenbestandes erst am Ende der Abfrage durchgeführt wird und diesbezüglich die
Dauer der Abfrage linear mit den Inhalt der \textit{document}"=Tabelle zusammenhängt. Des Weiteren wird für keine
Tabelle ein \textit{Index Scan} verwendet, sondern immer mit einem \textit{Seq Scan} gearbeitet, da durch das Ermitteln
des kompletten Datenbestandes der Optimizer entscheidet, ob der komplette Scan der Tabelle kostengünstiger ist als
die Verwendung eines der vorhandenen Indexe. Dies kann durch den Befehl \lstinline[language=SQL]|SET enable_seqscan = off|
sehr einfach verifiziert werden. Damit wird die Verwendung von \textit{Seq Scan} deaktiviert und es wird anschließend ein
\textit{Index Scan} verwendet. Wenn man nun beide Pläne vergleicht, sieht man die Erhöhung der Kosten bei der Verwendung 
von \textit{Index Scan}.

Die beste Optimierung hierbei ist, die Menge der Datensätze so früh wie möglich einzuschränken. Da die Verwendung von
\texttt{order by} innerhalb eines Sub"=Selects nicht erlaubt ist, wird hierfür eine \textit{Common Table Expression}
verwendet, wie es in \autoref{lst:explain-optimize-cte} zu sehen ist. Zusätzlich wurde noch ein Index auf der 
\textit{document}"=Tabelle für die Spalten der Bedingung und der Sortierung gesetzt, wie in
\autoref{lst:explain-optimize-cte-idx} zu sehen, damit in der \textit{Common Table Expression} nur der Index verwendet
werden kann und kein zusätzlicher Zugriff in die Tabelle notwendig ist.

\begin{lstlisting}[language=SQL,caption={Optimierung mit Common Table Expression},label=lst:explain-optimize-cte]
with doc_limit as (
    select id
    from   public.document
    where  validuntil > now()
    and    ispublishedindb = true
    order  by startyear desc, startmonth desc, startday desc
    limit  400
)
select *
from   doc_limit t
       join public.document t0 on t0.id = t.id
order  by t0.startyear desc, t0.startmonth desc, t0.startday desc
\end{lstlisting}

\begin{lstlisting}[language=SQL,caption={Index für Common Table Expression},label=lst:explain-optimize-cte-idx]
create index idx_document_with_stmt on document using btree
    ( ispublishedindb, validuntil, startyear desc, startmonth desc
    , startday desc, id );
\end{lstlisting}

Mit diesen Umstellungen erkennt man, dass die Kosten entsprechend gefallen sind. Ebenfalls konnte die Laufzeit um
mehr als den Faktor drei reduziert werden. Die Optimierung ist in \autoref{fig:explain-visualize-with} sehr deutlich 
an den dünneren Verbindungslinien zwischen den Knoten und der Umstellung von einigen \textit{Seq Scan} zu 
\textit{Index Scan} ersichtlich. Zeitgleich ist auch der teure \textit{HashAggregate} nicht mehr im Abfrageplan 
vorhanden.

\begin{figure}[H]
    \includegraphics[width=\linewidth]{gfx/chapter05_ExplainVisualize_with.png}
    \caption{Visualisierung EXPLAIN with}
    \label{fig:explain-visualize-with}
\end{figure}

Bei der Untersuchung der Abfrage zur \textit{Materialized View} ist direkt herausgekommen, dass hier keine Optimierung 
mehr möglich ist, da durch die definierten Indexe bei den aktuell möglichen Sortierkriterien direkt ein 
\textit{Index Scan} verwendet wird. Dies ist durch eine Überprüfung der Abfragepläne beweisbar, für diesen Fall wird 
die Abfrage aus \autoref{lst:explain-search-document} verwendet.

\begin{lstlisting}[language=SQL,caption={Untersuchung searchdocument},label=lst:explain-search-document]
explain (analyze)
select sd.id, documentid, datetype, startyear, startmonth, startday
    , startdatestatus , endyear, endmonth, endday, enddatestatus
    , author, coauthors, addressees, city, documentcategory
    , ispublishedindb, createdat, modifiedat, validuntil
from searchdocument sd 
order by startyear desc, startmonth desc, startday desc
limit 400;
\end{lstlisting}

Der dazugehörige Abfrageplan ist in \autoref{lst:explain-search-document-output} zu sehen, hierbei ist die erste 
Ausgabe mit dem erstellten Index. Vor der zweiten Ausgabe wurde dieser Index deaktiviert. Anhand der Ausgabe ist zu
sehen, dass bei der Verwendung des Index weniger Operation notwendig sind und damit auch die teure Sortierung 
eingespart werden konnte. Dies liegt daran, dass der Index entsprechend des Sortierkriterien definiert wurde und
somit ist es möglich, direkt in dem Index die Elemente in der richtigen Reihenfolge zu ermitteln.
Somit ist durch den Index der schnellstmögliche Zugriff gegeben. Bei einem \textit{Seq Scan} ist die Ausgabe immer eine
unsortierte Liste und benötigt daher im Nachgang die zusätzliche Sortierung.

\begin{lstlisting}[basicstyle=\scriptsize,caption={Abfrageplan searchdocument},label=lst:explain-search-document-output]
Limit  (cost=0.28..144.92 rows=400 width=948) (actual time=0.035..0.660 rows=400 loops=1)
  ->  Index Scan Backward using idx_searchdocument_startdate on searchdocument sd (cost=0.28..1911.30 rows=5285 width=948) (actual time=0.033..0.593 rows=400 loops=1)
Planning Time: 0.199 ms
Execution Time: 0.732 ms

Limit  (cost=747.69..748.69 rows=400 width=948) (actual time=2.128..2.146 rows=400 loops=1)
  ->  Sort  (cost=747.69..760.90 rows=5285 width=948) (actual time=2.127..2.135 rows=400 loops=1)
        Sort Key: startyear DESC, startmonth DESC, startday DESC
        Sort Method: top-N heapsort  Memory: 703kB
        ->  Seq Scan on searchdocument sd  (cost=0.00..492.85 rows=5285 width=948) (actual time=0.006..0.943 rows=5285 loops=1)
Planning Time: 0.056 ms
Execution Time: 2.164 ms
\end{lstlisting}
