2024-07-31 22:12:53 +02:00
|
|
|
% !TeX root = ../../thesis.tex
|
2024-01-27 13:37:35 +01:00
|
|
|
|
2024-08-19 21:00:19 +02:00
|
|
|
\chapter{Performance-Untersuchung der Anwendung}
|
|
|
|
\label{ch:performance-investigation-application}
|
2024-01-27 13:37:35 +01:00
|
|
|
|
2024-08-19 21:00:19 +02:00
|
|
|
Nun werden die unterschiedlichen Schichten betrachtet und möglichen Performance-Verbesserungen untersucht und deren
|
|
|
|
Vor"= und Nachteile herausgearbeitet.
|
2024-06-02 15:43:11 +02:00
|
|
|
|
2024-08-19 21:00:19 +02:00
|
|
|
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.
|
2024-01-27 13:37:35 +01:00
|
|
|
|
2024-08-19 21:00:19 +02:00
|
|
|
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.
|
2024-01-27 13:37:35 +01:00
|
|
|
|
2024-08-19 21:00:19 +02:00
|
|
|
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.
|
2024-01-27 13:37:35 +01:00
|
|
|
|
2024-08-19 21:00:19 +02:00
|
|
|
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.
|
2024-01-27 13:37:35 +01:00
|
|
|
|
2024-08-19 21:00:19 +02:00
|
|
|
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.
|
2024-01-27 13:37:35 +01:00
|
|
|
|
2024-08-19 21:00:19 +02:00
|
|
|
\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 !!!!}
|