diff --git a/chapters/thesis/appendix02_timing.sh b/chapters/thesis/appendix02_timing.sh index ab94d63..d6d2e3f 100644 --- a/chapters/thesis/appendix02_timing.sh +++ b/chapters/thesis/appendix02_timing.sh @@ -88,8 +88,8 @@ hostname="http://localhost:8080/WedekindJSF-1.0.0" # the Array of the Urls url_arr=( "$hostname/index.xhtml" - "$hostname/view/document/list.xhtml" - #"$hostname/view/document/listsearch.xhtml" + #"$hostname/view/document/list.xhtml" + "$hostname/view/document/listsearch.xhtml" #"$hostname/view/correspondent/list.xhtml" #"$hostname/view/person/list.xhtml" ) diff --git a/chapters/thesis/appendix04.tex b/chapters/thesis/appendix04.tex index 9ab0ba8..3a98ae1 100644 --- a/chapters/thesis/appendix04.tex +++ b/chapters/thesis/appendix04.tex @@ -10,14 +10,18 @@ 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. +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} \begin{lstlisting}[language=Bash,caption={Aufrufe des Unterstützungsscriptes},label=lst:calling_script_exec] callscript.sh measinit -callscript.sh measres -callscript.sh meascall -callscript.sh meascall -callscript.sh meascall -callscript.sh meascall +callscript.sh -rppf=_testname measres +callscript.sh -rppf=_testname meascall +callscript.sh -rppf=_testname -rppn=2 meascall +callscript.sh -rppf=_testname -rppn=3 meascall +callscript.sh -rppf=_testname -rppn=4 meascall \end{lstlisting} \ No newline at end of file diff --git a/chapters/thesis/appendix04_calling_script.sh b/chapters/thesis/appendix04_calling_script.sh index b101f55..3d1266c 100644 --- a/chapters/thesis/appendix04_calling_script.sh +++ b/chapters/thesis/appendix04_calling_script.sh @@ -11,6 +11,7 @@ domain_log="$payara_path/logs/server.log" script_path="/opt/docker/timing.sh" pgbadger_out="/opt/docker/pgreport" report_postfix="" +report_postno="" docker_name=dcpgbatch COMPOSE_FILE=/opt/docker/docker-compose.yaml @@ -54,13 +55,18 @@ pgconf() { } pgrp() { mkdir -p $pgbadger_out$report_postfix + mkdir -p $pgbadger_out$report_postfix$report_postno outPath=$pgbadger_out$report_postfix/bash.out touch "$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() { - rm -R $pgbadger_out$report_postfix + if ["$report_postfix" -eq ""]; then + rm -R $pgbadger_out + else + rm -R $pgbadger_out$report_postfix* + fi } dccreate() { sudo docker compose -f $COMPOSE_FILE create --force-recreate @@ -78,13 +84,12 @@ dcstats() { } for name in "$@"; do case $name in - --rppf=*) report_postfix="${1#*=}" ;; + -rppf=*) report_postfix="${name#*=}" ;; + -rppn=*) report_postno="${name#*=}" ;; gflog) gflog ;; gfconf) gfconf ;; gfscript) gfscript ;; - gfrestart) - pgrpinit - ;; + gfrestart) pgrpinit ;; pginit) pginit ;; pglogls) pglogls ;; pglogrm) pglogrm ;; @@ -98,6 +103,7 @@ for name in "$@"; do dcstop) dcstop ;; dcstats) dcstats ;; measinit) + pginit pgrpres pglogrm 0 dccreate @@ -124,7 +130,8 @@ for name in "$@"; do echo "ATTENTION: parameter must be defined in front of the commands!" echo "" echo "*** parameter ***" - echo " --rppf= Postfix name for the report-folder (used by gfscript, pgrp, pgrpres, measres, meascall)" + echo " -rppf= Postfix name for the report-folder (used by gfscript, pgrp, pgrpres, measres, meascall)" + echo " -rppn= Postfix number for the report-folder (used by pgrp, measres, meascall)" echo "" echo "*** glassfish ***" echo " gflog Show and follow the log of the glassfish server with $domain_name" diff --git a/chapters/thesis/chapter04.tex b/chapters/thesis/chapter04.tex index d69fc50..b5a391d 100644 --- a/chapters/thesis/chapter04.tex +++ b/chapters/thesis/chapter04.tex @@ -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{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 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 cq = cb.createQuery(Document.class); -Root from = cq.from(Document.class); -ParameterExpression includedPara = cb.parameter(Boolean.class, "published"); -ParameterExpression validPart = cb.parameter(Date.class, "now"); - -CriteriaQuery select = cq.select(from) - .where(cb.and( - cb.equal(from.get("isPublishedInDb"), includedPara), - cb.greaterThan(from.get("validUntil"), validPart) - )); -TypedQuery 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 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} \ No newline at end of file diff --git a/chapters/thesis/chapter05.tex b/chapters/thesis/chapter05.tex index 6fc5577..8636eed 100644 --- a/chapters/thesis/chapter05.tex +++ b/chapters/thesis/chapter05.tex @@ -1,22 +1,518 @@ % !TeX root = ../../thesis.tex -\chapter{???Optimierung???} -\label{ch:optimizing} +\chapter{Performance-Untersuchung der Anwendung} +\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} -\label{sec:optimizing:performance} +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. -\section{Analyse der Abfrage} -\label{sec:optimizing:query-analyse} +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. -\section{Optimierungen der Abfragen} -\label{sec:optimizing:query-optimizing} +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. -\section{Anpassung der Konfiguration} -\label{sec:optimizing:configuration} +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. -und hier ein sql-beispiel \autoref{lst:tester} -\includecode[SQL]{chapters/thesis/chapter05_example.sql}{lst:tester}{ein sql beispiel} +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} + +\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 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 cq = cb.createQuery(Document.class); +Root from = cq.from(Document.class); +ParameterExpression includedPara = cb.parameter(Boolean.class, "published"); +ParameterExpression validPart = cb.parameter(Date.class, "now"); + +CriteriaQuery select = cq.select(from) + .where(cb.and( + cb.equal(from.get("isPublishedInDb"), includedPara), + cb.greaterThan(from.get("validUntil"), validPart) + )); +TypedQuery 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 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 !!!!} diff --git a/chapters/thesis/chapter05_example.sql b/chapters/thesis/chapter05_example.sql deleted file mode 100644 index 83f8eb1..0000000 --- a/chapters/thesis/chapter05_example.sql +++ /dev/null @@ -1,3 +0,0 @@ -select * -from tblCPDataX -where szName = N'EDA01' \ No newline at end of file diff --git a/thesis-beamer.pdf b/thesis-beamer.pdf index 42ff176..3f62a73 100644 Binary files a/thesis-beamer.pdf and b/thesis-beamer.pdf differ diff --git a/thesis-beamer.tex b/thesis-beamer.tex index aa68c5b..a81e3ad 100644 --- a/thesis-beamer.tex +++ b/thesis-beamer.tex @@ -95,6 +95,9 @@ --- Untersuchung vorher --- \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} \frametitle{Caching mit OpenJPA} --- OpenJPA --- diff --git a/thesis.pdf b/thesis.pdf index b05a61c..7282970 100644 Binary files a/thesis.pdf and b/thesis.pdf differ