Daily CheckIn

This commit is contained in:
marcodn 2024-08-19 21:00:19 +02:00
parent a1af60e945
commit 8baff92dcc
9 changed files with 537 additions and 511 deletions

View file

@ -88,8 +88,8 @@ hostname="http://localhost:8080/WedekindJSF-1.0.0"
# the Array of the Urls # the Array of the Urls
url_arr=( url_arr=(
"$hostname/index.xhtml" "$hostname/index.xhtml"
"$hostname/view/document/list.xhtml" #"$hostname/view/document/list.xhtml"
#"$hostname/view/document/listsearch.xhtml" "$hostname/view/document/listsearch.xhtml"
#"$hostname/view/correspondent/list.xhtml" #"$hostname/view/correspondent/list.xhtml"
#"$hostname/view/person/list.xhtml" #"$hostname/view/person/list.xhtml"
) )

View file

@ -10,14 +10,18 @@
Um die Messungen etwas zu vereinfachen wurde ein Skript erstellt um die Aufrufe gesammelt durchzuführen. Um die Um die Messungen etwas zu vereinfachen wurde ein Skript erstellt um die Aufrufe gesammelt durchzuführen. Um die
Messungen durchzuführen werden die Befehl, wie in \ref{lst:calling_script_exec} dargestellt aufgerufen. Messungen durchzuführen werden die Befehl, wie in \ref{lst:calling_script_exec} dargestellt aufgerufen.
Durch die nummerierten Präfixe können im Nachgang über die pgBadger-Berichte die SQL-Abfragen verglichen werden.
Wichtig hierbei ist noch, dass vor dem ersten \textit{meascall}-Aufruf überprüft wird, ob die Docker-Container
gestartet und initialisiert sind. Wenn dies nicht der Fall ist, laufen die Abfragen ins leere. Am einfachsten ist das
über die Statistik von Docker zu ermitteln, ob die CPU-Auslastung auf einen niedrigen Level gefallen ist.
\includecode[bash]{chapters/thesis/appendix04_calling_script.sh}{lst:calling_script}{Calling Script} \includecode[bash]{chapters/thesis/appendix04_calling_script.sh}{lst:calling_script}{Calling Script}
\begin{lstlisting}[language=Bash,caption={Aufrufe des Unterstützungsscriptes},label=lst:calling_script_exec] \begin{lstlisting}[language=Bash,caption={Aufrufe des Unterstützungsscriptes},label=lst:calling_script_exec]
callscript.sh measinit callscript.sh measinit
callscript.sh measres callscript.sh -rppf=_testname measres
callscript.sh meascall callscript.sh -rppf=_testname meascall
callscript.sh meascall callscript.sh -rppf=_testname -rppn=2 meascall
callscript.sh meascall callscript.sh -rppf=_testname -rppn=3 meascall
callscript.sh meascall callscript.sh -rppf=_testname -rppn=4 meascall
\end{lstlisting} \end{lstlisting}

View file

@ -11,6 +11,7 @@ domain_log="$payara_path/logs/server.log"
script_path="/opt/docker/timing.sh" script_path="/opt/docker/timing.sh"
pgbadger_out="/opt/docker/pgreport" pgbadger_out="/opt/docker/pgreport"
report_postfix="" report_postfix=""
report_postno=""
docker_name=dcpgbatch docker_name=dcpgbatch
COMPOSE_FILE=/opt/docker/docker-compose.yaml COMPOSE_FILE=/opt/docker/docker-compose.yaml
@ -54,13 +55,18 @@ pgconf() {
} }
pgrp() { pgrp() {
mkdir -p $pgbadger_out$report_postfix mkdir -p $pgbadger_out$report_postfix
mkdir -p $pgbadger_out$report_postfix$report_postno
outPath=$pgbadger_out$report_postfix/bash.out outPath=$pgbadger_out$report_postfix/bash.out
touch "$outPath" touch "$outPath"
echo "" >>"$outPath" echo "" >>"$outPath"
pgbadger -X -I -f jsonlog -j 10 -O $pgbadger_out$report_postfix $postgres_log_path/postgresql-*.json 2>&1 | tee -a "$outPath" pgbadger -X -I -f jsonlog -j 10 -O $pgbadger_out$report_postfix$report_postno $postgres_log_path/postgresql-*.json 2>&1 | tee -a "$outPath"
} }
pgrpres() { pgrpres() {
rm -R $pgbadger_out$report_postfix if ["$report_postfix" -eq ""]; then
rm -R $pgbadger_out
else
rm -R $pgbadger_out$report_postfix*
fi
} }
dccreate() { dccreate() {
sudo docker compose -f $COMPOSE_FILE create --force-recreate sudo docker compose -f $COMPOSE_FILE create --force-recreate
@ -78,13 +84,12 @@ dcstats() {
} }
for name in "$@"; do for name in "$@"; do
case $name in case $name in
--rppf=*) report_postfix="${1#*=}" ;; -rppf=*) report_postfix="${name#*=}" ;;
-rppn=*) report_postno="${name#*=}" ;;
gflog) gflog ;; gflog) gflog ;;
gfconf) gfconf ;; gfconf) gfconf ;;
gfscript) gfscript ;; gfscript) gfscript ;;
gfrestart) gfrestart) pgrpinit ;;
pgrpinit
;;
pginit) pginit ;; pginit) pginit ;;
pglogls) pglogls ;; pglogls) pglogls ;;
pglogrm) pglogrm ;; pglogrm) pglogrm ;;
@ -98,6 +103,7 @@ for name in "$@"; do
dcstop) dcstop ;; dcstop) dcstop ;;
dcstats) dcstats ;; dcstats) dcstats ;;
measinit) measinit)
pginit
pgrpres pgrpres
pglogrm 0 pglogrm 0
dccreate dccreate
@ -124,7 +130,8 @@ for name in "$@"; do
echo "ATTENTION: parameter must be defined in front of the commands!" echo "ATTENTION: parameter must be defined in front of the commands!"
echo "" echo ""
echo "*** parameter ***" echo "*** parameter ***"
echo " --rppf=<val> Postfix name for the report-folder (used by gfscript, pgrp, pgrpres, measres, meascall)" echo " -rppf=<val> Postfix name for the report-folder (used by gfscript, pgrp, pgrpres, measres, meascall)"
echo " -rppn=<val> Postfix number for the report-folder (used by pgrp, measres, meascall)"
echo "" echo ""
echo "*** glassfish ***" echo "*** glassfish ***"
echo " gflog Show and follow the log of the glassfish server with $domain_name" echo " gflog Show and follow the log of the glassfish server with $domain_name"

View file

@ -24,484 +24,3 @@ prüfen, die den Cache von OpenJPE auswerten}
\mytodos{Einbau der Messungen direkt in die Webseite bzw. in ein Log} \mytodos{Einbau der Messungen direkt in die Webseite bzw. in ein Log}
\mytodos{Einstellung am postgresql um die queries mit zu loggen} \mytodos{Einstellung am postgresql um die queries mit zu loggen}
\section{Untersuchung der Anwendung}
\label{sec:performance-checking:investigation-application}
Nun werden die unterschiedlichen Schichten betrachtet und möglichen 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 an dem Code und der
Konfiguration mehrfach aufgerufen. Hierbei hat sich gezeigt, dass der erste Aufruf nach dem Deployment ca. 1500 ms
gedauert hat. Die weiteren Aufrufe dauert dann im Durchschnitt bei 600 ms. Beim achten Aufruf des Scripts hat der
Server nicht mehr reagiert und im Log ist ein OutOfMemoryError protokolliert worden.
Nach einem Neustart des Servers, konnte das gleiche Verhalten wieder reproduziert werden. Daraufhin wurde das Test-Script
um die Anzeige der aktuellen Speicherverwendung des Payara-Servers erweitert und diese zeitgleich zu beobachten. Diese
Auswertung zeigte, dass der Server mit ca. 1500 MB RSS Nutzung an seine Grenzen stößt. Diese Grenzen wurde durch die
Konfigurationsänderung im Payara-Server von \texttt{-Xmx512m} auf \texttt{-Xmx4096m} nach oben verschoben. Nun werden
ca. 60 Aufrufe des Scripts benötigt, damit der Server nicht mehr reagiert. Hierbei wird aber kein OutOfMemoryError
in der Log-Datei protokolliert und der Server verwendet nun ca. 4700 MB RSS. Bei allen Tests war noch mehr als die
Hälfte des verfügbaren Arbeitsspeichers unbenutzt.
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 verschiebt.
Als Grundlage für die Vergleiche wurden eine Messung durchgeführt, bei der alle Caches deaktiviert wurden und keine
Änderung am Code vorgenommen wurde. Das Ergebnis dieser Messung ist in \ref{tbl:measure-without-cache} zu finden. Diese
zeigen auch direkt ein erwartetes 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 auch relative gleichmässig, was nicht recht ins Bild passt, da hier keine Objekte im Cache
gehalten werden sollten.
\mytodos{hier noch text einfügen, der erklärt wie die Spalten zu werten sind, also Aufrufzeit ist kürzer gleich besser}
\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 wurde 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ört haben, unabhängig von den
konfigurierten Cache Einstellungen.
Da die Abfragezeiten auf der Datenbank zu gering waren, um eine Verbesserung feststellen zu können, wurde für den
PostgreSQL und den Payara-Server ein Docker-Container erzeugt und diese limitiert. Die Konfiguration ist im Anhang
\ref{ap:docker_config} beschrieben.
Mit dem neuen Aufbau ergeben sich nun neue Messungen. 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.
\subsection{Caching im OpenJPA}
\label{sec:performance-checking: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 Elementen im Cache gesteuert werden. Wird diese Anzahl erreicht, dann werden zufällige
Objekte aus dem Cache entfernt und in eine SoftReferenceMap übertragen.
Zuerst wird mit aktivierten Cache mit einer Cache-Größe von 1000 Elemente getestet. Wie in \ref{tbl:measure-ojpa-active}
zu sehen, dauert auch hier der erste Aufruf minimal länger als ohne akiviertem Cache. Alle Nachfolgenden Aufrufe
wiederrum sind um 100ms schneller in der Verarbeitung. Auch bei der Anzahl der Anfragen an die Datenbank kann mehr den
Rückgang der Anfragen sehr gut sehen. Aktuell kann die Verringerung des wachsenden Speicherbedarfs nur nicht erklärt
werden.
\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 & 277 & 469 & 1506 & 7206 & 764,21 & 859.96 & 95.75 \\
2 & 228 & 269 & 384 & 6767 & 848,64 & 908,44 & 59.80 \\
3 & 224 & 238 & 299 & 6656 & 898.71 & 949.94 & 51.23 \\
4 & 214 & 235 & 325 & 6671 & 936.70 & 999.49 & 62.79 \\
\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, zeigt sich auf den ersten Blick ein noch besseres Bild ab, wie in
\ref{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 konnte.
\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 & 178 & 347 & 1507 & 1419 & 752.10 & 862.38 & 110,28 \\
2 & 126 & 152 & 232 & 60 & 853.72 & 875.21 & 21.49 \\
3 & 130 & 134 & 142 & 60 & 880.08 & 880.94 & 0,86 \\
4 & 125 & 128 & 135 & 60 & 865.36 & 897.96 & 32.60 \\
\hline
\end{tabular}
\caption{Messung mit OpenJPA-Cache und Größe auf 10000}
\label{tbl:measure-ojpa-active-bigger}
\end{table}
\mytodos{pin und unpin noch mit einbringen? SoftReferenceMap nochmal genau durchleuchte, laut doku entfällt dort nichts
wenn kein Timeout auf der Klasse definiert ist}
\mytodos{kurzes Fazit fehlt noch!}
\subsection{Caching im \ac{JPA}}
\label{sec:performance-checking:investigation-application:caching-jpa}
Die Cache-Einstellungen von \ac{JPA} werden über mehrere Einstellungen konfiguriert. Anhand von
\texttt{eclipselink.query-results-cache} wird definiert, dass die Ergebnisse von benannten Abfragen im Cache
gespeichert werden. Für den Zugriff in den Cache, wird neben den Namen noch die übergebenen Parameter
berücksichtigt.
% https://eclipse.dev/eclipselink/documentation/2.5/concepts/cache008.htm
Der geteilte Cache, der für die Dauer der persistenten Einheit (EntityManagerFactory oder der Server) vorhanden ist,
kann über \texttt{eclipselink.cache.shared.default} gesteuert werden. Dieser kann nur aktiviert oder deaktiviert werden.
% https://wiki.eclipse.org/EclipseLink/Examples/JPA/Caching
Mit \texttt{eclipselink.cache.size.default} wird die initiale Größe des Caches definiert, hierbei ist der Standardwert
100. Die Objekt werden nicht direkt aus dem Cache entfernt, sondern erst nachdem der \ac{GC} diese freigeben hat.
Zusätzlich wird über \texttt{eclipselink.cache.type.default} die Art des Caching gesteuert. Die Einstellung mit dem
höchsten Speicherbedarf ist \textit{FULL}, bei dem alle Objekte im Cache bleiben, außer sie werden explizit gelöscht.
Die Einstellung \textit{SOFT} und \textit{WEAK} sind sehr ähnlich, der unterschied ist die Referenzierung auf die
Entität. Bei \textit{WEAK} bleiben die Objekte nur solange erhalten, wie die Anwendung selbst eine Referenz auf die
Objekte fest hält. Im Gegensatz dazu bleibt bei \textit{SOFT} die Referenz so lange bestehen, bis der \ac{GC} wegen
zu wenig Speicher Objekte aus dem Cache entfernt.
% https://eclipse.dev/eclipselink/documentation/2.5/concepts/cache002.htm
Um den Cache zu deaktivieren wurden beiden Einstellungen auf \textit{false} gestellt, die Größe auf 0 und der Cache-Typ
auf \textit{NONE}. Hierbei lag die maximale gemessene Laufzeit des ersten Aufrufs bei ca. 1300 ms und es wurden 12219
Abfragen an die Datenbank gestellt. Bei den nachfolgenden Aufrufe lag die Aufrufzeit im Durchschnitt bei 350 ms und
12080 Abfragen.
Um den Cache wieder zu aktivieren wurden die Einstellungen auf \textit{true} gestellt, die Größe auf den Standardwert
von 100 und der Cache-Type auf \textit{SOFT} gestellt. Hierbei wurde eine maximale Laufzeit beim ersten Aufruf ebenfalls
von 1300 ms gemessen und es wurden 12218 Abfragen abgesetzt. Bei den nachfolgenden Aufrufen lag die Aufrufzeit im
Durchschnitt bei 340 ms.
Bei WEAK hat sich die Speichernutzung nur um 5MB gesteigert
\mytodos{in einer Tabelle oder Graphen darstellen?}
Wie man an den Daten erkennen kann, wird der Cache vom \ac{JPA} für diese Abfrage nicht verwendet, sonst müssten die
Anzahl der Abfragen an die Datenbank drastisch reduziert werden. Selbst die Laufzeit ändert sich nur marginal.
\subsection{Caching in \ac{EJB}}
\label{sec:performance-checking:investigation-application:caching-ejb}
Die Cache-Einstellungen des \ac{EJB} sind in der Admin-Oberfläche des Payara-Servers zu erreichen. Hier
\mytodos{Cache config noch definieren}
\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 & 416 & 554 & 1269 & 12237 & 840.31 & 998.07 & 157.76 \\
2 & 299 & 394 & 749 & 12080 & 973.20 & 1101.37 & 128.17 \\
3 & 293 & 324 & 382 & 12080 & 1092.00 & 1192.87 & 100.87 \\
4 & 281 & 318 & 398 & 12080 & 1191.25 & 1305.29 & 114.04 \\
\hline
\end{tabular}
\caption{Messung mit \ac{EJB}-Cache}
\label{tbl:measure-ejb-cache-active}
\end{table}
\subsection{Abfragen \ac{JPQL}}
\label{sec:performance-checking:investigation-application:query-jpql}
Für die \ac{JPQL} wird ein \ac{SQL} ähnlicher Syntax verwendet um die Abfragen an die Datenbank durchzuführen. Für die
Dokumentenliste wird der Code aus \ref{lst:jpql-document-list-jpql} verwendet. Die Namen mit vorangestellten Doppelpunkt
sind Übergabevariablen.
\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}
In dem dazugehörigen Code am Server wird der JPQL-Code als NamedQuery hinterlegt und über den Name \textit{Document.findAll}
referenziert. In eingriff in die Abfrage ist hier leider nicht möglich, wie man im Code \ref{lst:jpql-document-list}
sehen kann.
\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 kommt, wird hierfür keine gesonderte Zeitmessung durchgeführt, da dies durch
\ref{tbl:measure-without-cache} geschehen ist.
\subsection{Abfragen Criteria API}
\label{sec:performance-checking:investigation-application:query-criteria-api}
Für die Criteria API wird die Abfrage nicht in einem SQL-Dialekt beschreiben. 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 durchführen muss nun nur noch Datenklasse angegeben werden und mit den Parametern
versorgt werden, wie es in \ref{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 \ref{tbl:measure-criteria-api} zu sehen, unterscheiden sich die Abfragezeiten nur marginal von
denen mit \ac{JPQL}. Wenn man sich den Code im Debugger anschaut, sieht man auch, dass die zusammengesetzten Abfragen
in den Java-Objekten fast identisch sind. Und in der Datenbank sind die Anfragen identisch zu denen über JPQL.
\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 & 396 & 572 & 1535 & 12173 & 796.59 & 970.10 & 173.51 \\
2 & 333 & 366 & 397 & 12080 & 982.28 & 1064.12 & 81.84 \\
3 & 286 & 339 & 554 & 12080 & 1048.12 & 1162.92 & 114.80 \\
4 & 293 & 317 & 388 & 12080 & 1150.43 & 1263.77 & 113.34 \\
\hline
\end{tabular}
\caption{Messung mit Criteria-API ohne Cache}
\label{tbl:measure-criteria-api}
\end{table}
\subsection{materialized views}
\label{sec:performance-checking:investigation-application:materialized-views}
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.
In diesem Test, wurde zusätzlich zur normalen Abfragen noch die nachfolgenden einzelabfragen als Sub-Selects
hinzugefügt, wie in \ref{lst:sql-materialized-view} zu sehen. Somit können die nachfolgenden einzelnen Abfragen
eingespart werden. Dies wiederrum geht aber auf die Performance der Erstellung der Sicht und ihrer Aktualisierung.
\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}
\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 & 364 & 472 & 1225 & 306 & 821.03 & 890.15 & xxx.xx \\
2 & 345 & 361 & 290 & 100 & 839.89 & 852.26 & xxx.xx \\
3 & xxx & xxx & xxx & xxxxx & xxxx.xx & xxxx.xx & xxx.xx \\
4 & xxx & xxx & xxx & xxxxx & xxxx.xx & xxxx.xx & xxx.xx \\
\hline
\end{tabular}
\caption{Messung mit Materialized View}
\label{tbl:measure-materialized-view}
\end{table}
Wie in Tabelle \ref{tbl:measure-materialized-view} zu sehen, bringt die Verwendung der Materialized View ein Verbesserung
in verschiedenen Punkten. Zum einen ist eine Verbesserung der Aufrufzeiten zu erkennen, zusätzlich fällt der
Speicheranstieg weniger stark aus.
Nach dem der Quellcode nochmal untersucht wurde, konnte man festellen, dass bei jeder Anfrage die gleiche Bedingung
benötigt wurde. Da die Sicht nun explizit für dies Anfrage geschaffen wurde, wurde die Bedingungen nun direkt in Sicht
mit integriert. Dies bedeutet eine Erweiterung der Sicht aus \ref{lst:sql-materialized-view} um
\ref{lst:sql-materialized-view-ext} und das entfernen der Parameter aus dem 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}
\mytodos{Die Indizies noch mit aufnehmen!}
Nach dem Anpassungen haben sich dann die Werte aus \ref{tbl:measure-materialized-view-ext} ergeben.
\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 & 348 & 419 & 869 & 178 & 792.11 & 846.29 & 54.18 \\
2 & 340 & 347 & 367 & 90 & 810.77 & 832.57 & 21.80 \\
3 & 296 & 353 & 491 & 90 & 840.39 & 867.92 & 27.53 \\
4 & 294 & 315 & 392 & 90 & 876.19 & 885.31 & 9.12 \\
\hline
\end{tabular}
\caption{Messung mit erweiterter Materialized View}
\label{tbl:measure-materialized-view-ext}
\end{table}
\subsection{cached queries}
\label{sec:performance-checking: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 \ref{tbl:measure-cached-queries},
dass hier keine Veränderung der Aufrufzeiten stattgefunden hat. Gleich ob man mit \ac{JPQL} oder mit der Criteria API
abfragt.
\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 & 391 & 593 & 1533 & 12256 & 843.63 & 1009.79 & 116.16 \\
2 & 281 & 365 & 584 & 12080 & 996.28 & 1114.60 & 118.32 \\
3 & 295 & 353 & 464 & 12080 & 1103.30 & 1201.47 & 98.17 \\
4 & 280 & 292 & 324 & 12080 & 1191.56 & 1298.46 & 106.90 \\
\hline
\end{tabular}
\caption{Messung mit aktiviertem Cached Queries}
\label{tbl:measure-cached-queries}
\end{table}
\subsection{Umgestalten der Datenbanktabellen}
\label{sec:performance-checking:investigation-application:new-table}
\subsection{Verkleinerung der Abfragen}
\label{sec:performance-checking:investigation-application:smaller-query}
\subsection{Statische Webseiten}
\label{sec:performance-checking:investigation-application:static-website}
Wenn man die Dokumentenliste als statische Webseiten ablegt, werden die Zugriffszeiten sehr kurz sein. Darüber hinaus
funktionieren in statische Webseiten aber keine Suche oder eine Sortierung. Sonst müsste man für jede mögliche
Sortierung und Suchanfrage einen Satz der Dokumentenliste als statische Webseite bereitstellen. Für die Sortierungen
wäre das noch möglich, aber für die Suchanfragen ist dies nicht mehr möglich. Daher ist die Umstellung auf statische
Webseiten nicht sinnvoll.
\mytodos{docker-file und bsopt in den Anhang packen}

View file

@ -1,22 +1,518 @@
% !TeX root = ../../thesis.tex % !TeX root = ../../thesis.tex
\chapter{???Optimierung???} \chapter{Performance-Untersuchung der Anwendung}
\label{ch:optimizing} \label{ch:performance-investigation-application}
\mytodos{Muss noch entsprechend der Auswertungen aus der Performance-Untersuchungen angeapasst werden} Nun werden die unterschiedlichen Schichten betrachtet und möglichen Performance-Verbesserungen untersucht und deren
Vor"= und Nachteile herausgearbeitet.
\section{Ermittlung der Performance-Probleme} Für die Tests wird ein aktuelles Manjaro-System mit frisch installierten Payara als Serverhost und der IntelliJ IDEA
\label{sec:optimizing:performance} als Entwicklungsumgebung verwendet. Der Computer ist mit einer Intel CPU i7-12700K, 32 GB Arbeitsspeicher und einer SSD
als Systemfestplatte ausgestattet.
\section{Analyse der Abfrage} Zur ersten Untersuchung und der Bestimmung der Basis-Linie, wurde das Script ohne eine Änderung an dem Code und der
\label{sec:optimizing:query-analyse} Konfiguration mehrfach aufgerufen. Hierbei hat sich gezeigt, dass der erste Aufruf nach dem Deployment ca. 1500 ms
gedauert hat. Die weiteren Aufrufe dauert dann im Durchschnitt bei 600 ms. Beim achten Aufruf des Scripts hat der
Server nicht mehr reagiert und im Log ist ein OutOfMemoryError protokolliert worden.
\section{Optimierungen der Abfragen} Nach einem Neustart des Servers, konnte das gleiche Verhalten wieder reproduziert werden. Daraufhin wurde das Test-Script
\label{sec:optimizing:query-optimizing} um die Anzeige der aktuellen Speicherverwendung des Payara-Servers erweitert und diese zeitgleich zu beobachten. Diese
Auswertung zeigte, dass der Server mit ca. 1500 MB RSS Nutzung an seine Grenzen stößt. Diese Grenzen wurde durch die
Konfigurationsänderung im Payara-Server von \texttt{-Xmx512m} auf \texttt{-Xmx4096m} nach oben verschoben. Nun werden
ca. 60 Aufrufe des Scripts benötigt, damit der Server nicht mehr reagiert. Hierbei wird aber kein OutOfMemoryError
in der Log-Datei protokolliert und der Server verwendet nun ca. 4700 MB RSS. Bei allen Tests war noch mehr als die
Hälfte des verfügbaren Arbeitsspeichers unbenutzt.
\section{Anpassung der Konfiguration} Dies zeigt direkt, dass es ein problem in der Freigabe der Objekte gibt, da dass erhöhen des verwendbaren Arbeitsspeicher
\label{sec:optimizing:configuration} das Problem nicht löst, sondern nur verschiebt.
und hier ein sql-beispiel \autoref{lst:tester} Als Grundlage für die Vergleiche wurden eine Messung durchgeführt, bei der alle Caches deaktiviert wurden und keine
\includecode[SQL]{chapters/thesis/chapter05_example.sql}{lst:tester}{ein sql beispiel} Änderung am Code vorgenommen wurde. Das Ergebnis dieser Messung ist in \ref{tbl:measure-without-cache} zu finden. Diese
zeigen auch direkt ein erwartetes 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 auch relative gleichmässig, was nicht recht ins Bild passt, da hier keine Objekte im Cache
gehalten werden sollten.
\mytodos{hier noch text einfügen, der erklärt wie die Spalten zu werten sind, also Aufrufzeit ist kürzer gleich besser}
\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}
\mytodos{hier noch beschreiben, wie die Werte zu interpretieren sind, und hervorheben, dass dies nun für alle Tabellen so gilt!}
Vor jedem weiteren Test-Lauf wurde 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ört haben, unabhängig von den
konfigurierten Cache Einstellungen.
Da die Abfragezeiten auf der Datenbank zu gering waren, um eine Verbesserung feststellen zu können, wurde für den
PostgreSQL und den Payara-Server ein Docker-Container erzeugt und diese limitiert. Die Konfiguration ist im Anhang
\ref{ap:docker_config} beschrieben.
Mit dem neuen Aufbau ergeben sich nun neue Messungen. 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. Für die
Ausführungszeiten der SQL-Abfragen wurden nur die 2 Hauptabfragen auf der Document-Tabelle, die Abfrage der 400
Dokumente, sowie den letzten und ersten Eintrag der Tabelle.
\mytodos{es müssen die 6 Anfragen sein, documentaddresseeperson, documentcoauthorperson, documentfacsimile und count}
\begin{table}[h!]
\centering
\begin{tabular}{|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)} \\
\hline
\# & min & avg & max & cnt & sum & davor & danach & diff \\
\hline
1 & 354 & 649 & 2225 & 12240 & 181 & 967 & 1004 & 37 \\
2 & 288 & 328 & 409 & 12080 & 175 & 1004 & 1113 & 109 \\ % 356ms
3 & 294 & 449 & 746 & 12080 & 177 & 1113 & 1258 & 145 \\ % 533ms
4 & 289 & 371 & 634 & 12080 & 180 & 1279 & 1541 & 262 \\ % 713ms
\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 Elementen im Cache gesteuert werden. Wird diese Anzahl erreicht, dann werden zufällige
Objekte aus dem Cache entfernt und in eine SoftReferenceMap übertragen.
Zuerst wird mit aktivierten Cache mit einer Cache-Größe von 1000 Elemente getestet. Wie in \ref{tbl:measure-ojpa-active}
zu sehen, dauert auch hier der erste Aufruf minimal länger als ohne akiviertem Cache. Alle Nachfolgenden Aufrufe
wiederrum sind um 100ms schneller in der Verarbeitung. Auch bei der Anzahl der Anfragen an die Datenbank kann mehr den
Rückgang der Anfragen sehr gut sehen. Aktuell kann die Verringerung des wachsenden Speicherbedarfs nur nicht erklärt
werden.
\begin{table}[h!]
\centering
\begin{tabular}{|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)} \\
\hline
\# & min & avg & max & cnt & sum & davor & danach & diff \\
\hline
1 & 277 & 469 & 1506 & 7206 & 183 & 764,21 & 859.96 & 95.75 \\ % 183ms
2 & 228 & 269 & 384 & 6767 & 173 & 848,64 & 908,44 & 59.80 \\ % 356ms
3 & 224 & 238 & 299 & 6656 & 180 & 898.71 & 949.94 & 51.23 \\ % 535ms
4 & 214 & 235 & 325 & 6671 & 179 & 936.70 & 999.49 & 62.79 \\ % 714ms
\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, zeigt sich auf den ersten Blick ein noch besseres Bild ab, wie in
\ref{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 konnte.
\begin{table}[h!]
\centering
\begin{tabular}{|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)} \\
\hline
\# & min & avg & max & cnt & sum & davor & danach & diff \\
\hline
1 & 178 & 347 & 1507 & 1419 & 214 & 752.10 & 862.38 & 110,28 \\ % 214ms
2 & 126 & 152 & 232 & 60 & 168 & 853.72 & 875.21 & 21.49 \\ % 382ms
3 & 130 & 134 & 142 & 60 & 172 & 880.08 & 880.94 & 0,86 \\ % 554ms
4 & 125 & 128 & 135 & 60 & 177 & 865.36 & 897.96 & 32.60 \\ % 731ms
\hline
\end{tabular}
\caption{Messung mit OpenJPA-Cache und Größe auf 10000}
\label{tbl:measure-ojpa-active-bigger}
\end{table}
\mytodos{pin und unpin noch mit einbringen? SoftReferenceMap nochmal genau durchleuchte, laut doku entfällt dort nichts
wenn kein Timeout auf der Klasse definiert ist}
\mytodos{kurzes Fazit fehlt noch, anzahl der Queries für unterabfragen gehen auf 0 bzw. bleiben bei 400!}
\section{Caching im \ac{JPA}}
\label{sec:performance-investigation-application:caching-jpa}
Die Cache-Einstellungen von \ac{JPA} werden über mehrere Einstellungen konfiguriert. Anhand von
\texttt{eclipselink.query-results-cache} wird definiert, dass die Ergebnisse von benannten Abfragen im Cache
gespeichert werden. Für den Zugriff in den Cache, wird neben den Namen noch die übergebenen Parameter
berücksichtigt.
% https://eclipse.dev/eclipselink/documentation/2.5/concepts/cache008.htm
Der geteilte Cache, der für die Dauer der persistenten Einheit (EntityManagerFactory oder der Server) vorhanden ist,
kann über \texttt{eclipselink.cache.shared.default} gesteuert werden. Dieser kann nur aktiviert oder deaktiviert werden.
% https://wiki.eclipse.org/EclipseLink/Examples/JPA/Caching
Mit \texttt{eclipselink.cache.size.default} wird die initiale Größe des Caches definiert, hierbei ist der Standardwert
100. Die Objekt werden nicht direkt aus dem Cache entfernt, sondern erst nachdem der \ac{GC} diese freigeben hat.
Zusätzlich wird über \texttt{eclipselink.cache.type.default} die Art des Caching gesteuert. Die Einstellung mit dem
höchsten Speicherbedarf ist \textit{FULL}, bei dem alle Objekte im Cache bleiben, außer sie werden explizit gelöscht.
Die Einstellung \textit{SOFT} und \textit{WEAK} sind sehr ähnlich, der unterschied ist die Referenzierung auf die
Entität. Bei \textit{WEAK} bleiben die Objekte nur solange erhalten, wie die Anwendung selbst eine Referenz auf die
Objekte fest hält. Im Gegensatz dazu bleibt bei \textit{SOFT} die Referenz so lange bestehen, bis der \ac{GC} wegen
zu wenig Speicher Objekte aus dem Cache entfernt.
% https://eclipse.dev/eclipselink/documentation/2.5/concepts/cache002.htm
Um den Cache zu deaktivieren wurden beiden Einstellungen auf \textit{false} gestellt, die Größe auf 0 und der Cache-Typ
auf \textit{NONE}. Hierbei lag die maximale gemessene Laufzeit des ersten Aufrufs bei ca. 1300 ms und es wurden 12219
Abfragen an die Datenbank gestellt. Bei den nachfolgenden Aufrufe lag die Aufrufzeit im Durchschnitt bei 350 ms und
12080 Abfragen.
Um den Cache wieder zu aktivieren wurden die Einstellungen auf \textit{true} gestellt, die Größe auf den Standardwert
von 100 und der Cache-Type auf \textit{SOFT} gestellt. Hierbei wurde eine maximale Laufzeit beim ersten Aufruf ebenfalls
von 1300 ms gemessen und es wurden 12218 Abfragen abgesetzt. Bei den nachfolgenden Aufrufen lag die Aufrufzeit im
Durchschnitt bei 340 ms.
Bei WEAK hat sich die Speichernutzung nur um 5MB gesteigert
\mytodos{in einer Tabelle oder Graphen darstellen?}
Wie man an den Daten erkennen kann, wird der Cache vom \ac{JPA} für diese Abfrage nicht verwendet, sonst müssten die
Anzahl der Abfragen an die Datenbank drastisch reduziert werden. Selbst die Laufzeit ändert sich nur marginal.
\section{Caching in \ac{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. Hier
\mytodos{Cache config noch definieren}
\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 & 416 & 554 & 1269 & 12237 & 840.31 & 998.07 & 157.76 \\
2 & 299 & 394 & 749 & 12080 & 973.20 & 1101.37 & 128.17 \\
3 & 293 & 324 & 382 & 12080 & 1092.00 & 1192.87 & 100.87 \\
4 & 281 & 318 & 398 & 12080 & 1191.25 & 1305.29 & 114.04 \\
\hline
\end{tabular}
\caption{Messung mit \ac{EJB}-Cache}
\label{tbl:measure-ejb-cache-active}
\end{table}
\section{Abfragen \ac{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. Für die
Dokumentenliste wird der Code aus \ref{lst:jpql-document-list-jpql} verwendet. Die Namen mit vorangestellten Doppelpunkt
sind Übergabevariablen.
\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}
In dem dazugehörigen Code am Server wird der JPQL-Code als NamedQuery hinterlegt und über den Name \textit{Document.findAll}
referenziert. In eingriff in die Abfrage ist hier leider nicht möglich, wie man im Code \ref{lst:jpql-document-list}
sehen kann.
\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 kommt, wird hierfür keine gesonderte Zeitmessung durchgeführt, da dies durch
\ref{tbl:measure-without-cache} geschehen ist.
\section{Abfragen Criteria API}
\label{sec:performance-investigation-application:query-criteria-api}
Für die Criteria API wird die Abfrage nicht in einem SQL-Dialekt beschreiben. 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 durchführen muss nun nur noch Datenklasse angegeben werden und mit den Parametern
versorgt werden, wie es in \ref{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 \ref{tbl:measure-criteria-api} zu sehen, unterscheiden sich die Abfragezeiten nur marginal von
denen mit \ac{JPQL}. Wenn man sich den Code im Debugger anschaut, sieht man auch, dass die zusammengesetzten Abfragen
in den Java-Objekten fast identisch sind. Und in der Datenbank sind die Anfragen identisch zu denen über JPQL.
\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 & 396 & 572 & 1535 & 12173 & 796.59 & 970.10 & 173.51 \\
2 & 333 & 366 & 397 & 12080 & 982.28 & 1064.12 & 81.84 \\
3 & 286 & 339 & 554 & 12080 & 1048.12 & 1162.92 & 114.80 \\
4 & 293 & 317 & 388 & 12080 & 1150.43 & 1263.77 & 113.34 \\
\hline
\end{tabular}
\caption{Messung mit Criteria-API ohne Cache}
\label{tbl:measure-criteria-api}
\end{table}
\section{materialized views}
\label{sec:performance-investigation-application:materialized-views}
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.
In diesem Test, wurde zusätzlich zur normalen Abfragen noch die nachfolgenden einzelabfragen als Sub-Selects
hinzugefügt, wie in \ref{lst:sql-materialized-view} zu sehen. Somit können die nachfolgenden einzelnen Abfragen
eingespart werden. Dies wiederrum geht aber auf die Performance der Erstellung der Sicht und ihrer Aktualisierung.
\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}
\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 & 364 & 472 & 1225 & 306 & 821.03 & 890.15 & xxx.xx \\
2 & 345 & 361 & 290 & 100 & 839.89 & 852.26 & xxx.xx \\
3 & xxx & xxx & xxx & xxxxx & xxxx.xx & xxxx.xx & xxx.xx \\
4 & xxx & xxx & xxx & xxxxx & xxxx.xx & xxxx.xx & xxx.xx \\
\hline
\end{tabular}
\caption{Messung mit Materialized View}
\label{tbl:measure-materialized-view}
\end{table}
Wie in Tabelle \ref{tbl:measure-materialized-view} zu sehen, bringt die Verwendung der Materialized View ein Verbesserung
in verschiedenen Punkten. Zum einen ist eine Verbesserung der Aufrufzeiten zu erkennen, zusätzlich fällt der
Speicheranstieg weniger stark aus.
Nach dem der Quellcode nochmal untersucht wurde, konnte man festellen, dass bei jeder Anfrage die gleiche Bedingung
benötigt wurde. Da die Sicht nun explizit für dies Anfrage geschaffen wurde, wurde die Bedingungen nun direkt in Sicht
mit integriert. Dies bedeutet eine Erweiterung der Sicht aus \ref{lst:sql-materialized-view} um
\ref{lst:sql-materialized-view-ext} und das entfernen der Parameter aus dem 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}
\mytodos{Die Indizies noch mit aufnehmen!}
Nach dem Anpassungen haben sich dann die Werte aus \ref{tbl:measure-materialized-view-ext} ergeben.
\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 & 348 & 419 & 869 & 178 & 792.11 & 846.29 & 54.18 \\
2 & 340 & 347 & 367 & 90 & 810.77 & 832.57 & 21.80 \\
3 & 296 & 353 & 491 & 90 & 840.39 & 867.92 & 27.53 \\
4 & 294 & 315 & 392 & 90 & 876.19 & 885.31 & 9.12 \\
\hline
\end{tabular}
\caption{Messung mit erweiterter Materialized View}
\label{tbl:measure-materialized-view-ext}
\end{table}
\mytodos{hier noch darauf eingehen, dass die Hauptarbeit nicht beim editieren sondern bei der Anzeige ist}
\mytodos{Das Render des Json in der View Betrachten, scheint der aktuelle Kostenpunkt zu sein}
\mytodos{Hier könnte man auch den Query-Cache nochmal verwenden, da die anfragen nun fix wären}
\mytodos{Grundlagen zur Materialized-View noch hinterlegen}
\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 \ref{tbl:measure-cached-queries},
dass hier keine Veränderung der Aufrufzeiten stattgefunden hat. Gleich ob man mit \ac{JPQL} oder mit der Criteria API
abfragt.
\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 & 391 & 593 & 1533 & 12256 & 843.63 & 1009.79 & 116.16 \\
2 & 281 & 365 & 584 & 12080 & 996.28 & 1114.60 & 118.32 \\
3 & 295 & 353 & 464 & 12080 & 1103.30 & 1201.47 & 98.17 \\
4 & 280 & 292 & 324 & 12080 & 1191.56 & 1298.46 & 106.90 \\
\hline
\end{tabular}
\caption{Messung mit aktiviertem Cached Queries}
\label{tbl:measure-cached-queries}
\end{table}
\section{Umgestalten der Datenbanktabellen}
\label{sec:performance-investigation-application:new-table}
\mytodos{Erwähnen des Ansatz, von denormalisierung inkl. Grund warum es weg gelassen wurde}
\section{Verkleinerung der Abfragen}
\label{sec:performance-investigation-application:smaller-query}
\section{Statische Webseiten}
\label{sec:performance-investigation-application:static-website}
Wenn man die Dokumentenliste als statische Webseiten ablegt, werden die Zugriffszeiten sehr kurz sein. Darüber hinaus
funktionieren in statische Webseiten aber keine Suche oder eine Sortierung. Sonst müsste man für jede mögliche
Sortierung und Suchanfrage einen Satz der Dokumentenliste als statische Webseite bereitstellen. Für die Sortierungen
wäre das noch möglich, aber für die Suchanfragen ist dies nicht mehr möglich. Daher ist die Umstellung auf statische
Webseiten nicht sinnvoll.
\mytodos{Hier noch explizirter definieren, dass die sortierten Daten als statische Seiten abgelegt werden}
\section{Client basierte Webseiten}
\label{sec:performance-investigation-application:client-side-rendering}
\mytodos{Beschreiben, dass alles auf dem client geschickt wird, und dort alles gerendert und sortiert wird,
damit aber schwächere Clients ausgeschlossen werden!
Hätte aber Vorteil für die Kosten des Servers, da dieser schwächer ausgelegt werden könnte und damit Geld einzusparen
wäre. !!!! Der Punkt könnte auch unter QueryCache gelagert werden !!!!}

View file

@ -1,3 +0,0 @@
select *
from tblCPDataX
where szName = N'EDA01'

Binary file not shown.

View file

@ -95,6 +95,9 @@
--- Untersuchung vorher --- --- Untersuchung vorher ---
\end{frame} \end{frame}
% Hier 2-3 der aktuellen erarbeiten Ansätze Vorstellen und nach dem Warum fragen
% Genau beschreiben was Signifikant besser/schlechter ist
\begin{frame} \begin{frame}
\frametitle{Caching mit OpenJPA} \frametitle{Caching mit OpenJPA}
--- OpenJPA --- --- OpenJPA ---

Binary file not shown.