194 lines
14 KiB
TeX
194 lines
14 KiB
TeX
% !TeX root = ../../thesis.tex
|
|
|
|
\chapter{Grundlagen}
|
|
\label{ch:basics}
|
|
|
|
Da die Anwendung als Webseite umgesetzt ist, ist der zugehörige Client für den Benutzer ein Webbrowser. Dies bedeutet,
|
|
dass jeder Wechsel einer Seite oder eine Suchanfrage als Web"=Request an den Server geschickt wird. Solch ein Web"=Request
|
|
geht durch mehrere Schichten des Server"=System bis die Antwort an den Client zurückgesendet wird, wie in
|
|
\autoref{fig:webrequest} dargestellt.
|
|
|
|
Es wird ab hier von einem \textit{GlassFish}"=Server die Rede sein. In der Praxis wird ein \textit{Payara}"=Server
|
|
verwendet. Der \textit{GlassFish}"=Server ist die Referenz"=Implementierung von Oracle, welche für Entwickler
|
|
bereitgestellt wird und die neuesten Features unterstützt. Der \textit{Payara}"=Server ist aus dessen Quellcode entstanden
|
|
und ist für Produktivumgebungen gedacht, da dieser mit regelmäßigen Sicherheitsupdates versorgt wird. In diesem und dem folgenden Kapitel
|
|
wird für beide Anwendungen der Begriff \textit{GlassFish} verwendet.
|
|
|
|
Angefangen bei der Anfrage die über den Webbrowser an den Server gestellt wird und vom \textit{GlassFish}"=Server
|
|
empfangen wird. In diesem wird anhand des definierten Routings entschieden, an welchem \textit{Controller} im \textit{\ac{JSF}}
|
|
die Anfrage weitergeleitet und verarbeitet wird. In diesem wird die Darstellung der Webseite geladen und die Anfragen
|
|
für den darzustellenden Datenbestand abgeschickt.
|
|
|
|
Die Datenanfragen werden über die \textit{\ac{EJB}} an die \textit{\ac{JPA}} weitergeleitet.
|
|
Hier wird nun geprüft, ob die Daten aus dem \textit{OpenJPA Cache} direkt ermittelt werden können, oder ob die Abfrage
|
|
an das unterlagerte Datenbankmanagementsystem \textit{PostgreSQL} weitergeleitet werden muss. Die ermittelten Daten vom
|
|
\ac{DBMS} werden bei Bedarf im \textit{OpenJPA Cache} aktualisiert.
|
|
|
|
Das \textit{PostgreSQL} besteht aus mehreren Teilen die ineinander greifen, um die Anfragen zu bearbeiten. Dabei
|
|
sind die \textit{Memory Buffers} notwendig, um den Zugriff auf die Festplatte zu reduzieren und die Bearbeitungszeit
|
|
zu verringern. Um Anfragen, die den Zugriff auf die Festplatte benötigen effizienter zu gestalten, bereiten die
|
|
\textit{Services} die Datenstrukturen auf.
|
|
|
|
\begin{figure}[H]
|
|
\begin{tikzpicture}[node distance=5em,
|
|
block/.style={rectangle, rounded corners,minimum width=3cm,minimum height=1cm,text centered, draw=black,fill=green!30},
|
|
lineArrow/.style={arrows={-Latex[length=5pt 3 0]}},
|
|
every fit/.style={inner sep=1em,draw}
|
|
]
|
|
%https://docs.oracle.com/javaee/6/tutorial/doc/bnacj.html
|
|
\node (browser) [block] {Webbrowser};
|
|
\node (fitClient) [fit=(browser)] {};
|
|
\node [left] at (fitClient.west) {Client};
|
|
|
|
\node (JSF) [block,below of=browser,node distance=7em] {Java Server Faces};
|
|
\node (EJB) [block,below of=JSF] {Enterprise Java Beans};
|
|
\node (JPA) [block,below of=EJB] {Java Persistance API};
|
|
\node (openJPA) [block, below of=JPA] {OpenJPA Cache};
|
|
\node (fitGlassFish) [fit=(JSF) (EJB) (JPA) (openJPA)] {};
|
|
\node [left] at (fitGlassFish.west) {GlassFish};
|
|
|
|
\node (memoryBuffers) [block, below of=openJPA] {Memory Buffers};
|
|
\node (services) [block, right of=memoryBuffers, xshift=2cm] {Services};
|
|
\node (database) [block, below of=memoryBuffers] {Database};
|
|
\node (fitPostgreSQL) [fit=(memoryBuffers) (services) (database)] {};
|
|
\node [left] at (fitPostgreSQL.west) {PostgreSQL};
|
|
|
|
\node (fitServer) [fit=(fitGlassFish) (fitPostgreSQL),inner xsep=5em] {};
|
|
\node [left] at (fitServer.west) {Server};
|
|
|
|
\draw[lineArrow] (browser)--(JSF);
|
|
\draw[lineArrow] (JSF)--(EJB);
|
|
\draw[lineArrow] (EJB)--(JPA);
|
|
\draw[lineArrow] (JPA)--(openJPA);
|
|
\draw[lineArrow] (openJPA)--(memoryBuffers);
|
|
\draw[lineArrow] (memoryBuffers)--(database);
|
|
\draw[lineArrow] (services)|-(database);
|
|
\end{tikzpicture}
|
|
\caption{Ablauf einer Web-Anfrage}
|
|
\label{fig:webrequest}
|
|
\end{figure}
|
|
|
|
\section{GlassFish - Enterprise Java Beans}
|
|
\label{sec:basics:ejb}
|
|
|
|
In den Java"=EE"=Anwendungen wird der \textit{Persistenzkontext} für die Anfragen vom \textit{Application"=Server}
|
|
bereitgestellt. Hierfür werden \textit{Application"=Server} wie \textit{GlassFish} genutzt, um die Verwendung eines Pools % nicht eher die EJB innerhalb von glassfish
|
|
von Datenbankverbindungen zu definieren \citep[68]{MüllerWehr2012}. Dadurch kann die Anzahl der Verbindungen geringer
|
|
gehalten werden als die Anzahl der Benutzer, die an der Anwendung arbeiten. Zusätzlich werden die Transaktionen über
|
|
\textit{\ac{SFSB}} gehandhabt, welche automatisch vor dem Aufruf erzeugt und danach wieder gelöscht
|
|
werden. Dies birgt allerdings den Nachteil, dass der \textit{Persistenzkontext} sehr groß werden kann, wenn viele
|
|
Entities in den \textit{Persistenzkontext} geladen werden. Da dies häufig zu Speicher"~ und damit Performance"=Problemen
|
|
\citep[79]{MüllerWehr2012} führen kann, muss hier darauf geachtet werden, nicht mehr benötigte Entities aus dem
|
|
\textit{Persistenzkontext} zu lösen.
|
|
|
|
\section{GlassFish - Java Persistance API}
|
|
\label{sec:basics:jpa}
|
|
|
|
Die \textit{\ac{JPA}} wird als First"=Level"=Cache in Java"=EE"=Anwendung verwendet, hier nehmen die
|
|
Objekte einen von vier Zuständen ein \citep[57]{MüllerWehr2012}. Im Zustand \texttt{Transient} sind die Objekte erzeugt,
|
|
aber noch nicht in den Cache überführt worden. Wenn diese in den Cache überführt worden sind, nehmen sie den Zustand
|
|
\texttt{Verwaltet} ein. Ist das Objekt aus dem Cache und der Datenbank entfernt worden, nimmt es den Zustand
|
|
\texttt{Gelöscht} an. \texttt{Losgelöst} ist der letzte Zustand, bei dem das Objekt aus dem Cache entfernt worden ist,
|
|
aber noch in der Datenbank verbleibt.
|
|
|
|
Eine Menge von Objekten wird als \textit{Persistenzkontext} bezeichnet. Solange die Objekte dem
|
|
\textit{Persistenzkontext} zugeordnet sind, also den Zustand \texttt{Verwaltet} besitzen, werden diese auf Änderungen
|
|
überwacht, um diese am Abschluss mit der Datenbank zu synchronisieren. In der Literatur wird hierzu der Begriff
|
|
\textit{Automatic Dirty Checking} verwendet \citep[61]{MüllerWehr2012}.
|
|
|
|
\section{GlassFish - OpenJPA Cache}
|
|
\label{sec:basics:ojpac}
|
|
|
|
Zusätzlich kann im \textit{\ac{JPA}} ebenfalls noch der \textit{Second Level Cache} (L2-Cache) aktiviert werden. Dieser steht
|
|
jedem \textit{Persistenzkontext} zur Verfügung und kann dadurch die Anzahl der Datenbankzugriffe deutlich reduzieren,
|
|
was bei langsamen Datenbank"=Anbindungen zu hohen Performance"=Gewinnen führen kann \citep[171]{MüllerWehr2012}.
|
|
Zu Beachten ist, dass die Daten im \textit{Second Level Cache} explizit über die Änderungen informiert werden
|
|
müssen, um zu verhindern, dass bei einem nachfolgenden Aufruf veraltete Werte zurückgegeben werden. Ebenfalls benötigt ein Cache einen höheren Bedarf
|
|
an Arbeitsspeicher, in dem die Daten parallel zur Datenbank bereitgestellt werden, daher ist die Benutzung nur
|
|
problemlos bei Entities möglich, auf welche meist lesend zugegriffen wird.
|
|
|
|
In der OpenJPA"=Erweiterung für den L2-Cache wird in \textit{Objekt"=Cache} (in OpenJPA als \textit{DataCache}
|
|
bezeichnet) und \textit{Query"=Cache} unterschieden. Über die Funktionen \texttt{find()} und \texttt{refresh()} oder einer Query
|
|
werden die geladenen Entities in den Cache gebracht. Davon ausgenommen sind \textit{Large Result Sets} (Abfragen die
|
|
nicht alle Daten auf einmal laden), \texttt{Extent}"=Technologien und Queries, die einzelne Attribute von Entities
|
|
zurückliefern, aber nicht das Entity selbst. Hierbei kann genau gesteuert werden, welche Entity in den Cache abgelegt
|
|
werden und welche nicht. Ebenfalls kann auf Klassenbasis der zugehörige Cache definiert werden, um eine bessere
|
|
Last-Verteilung beim Zugriff zu ermöglichen \citep[314]{MüllerWehr2012}.
|
|
|
|
Im \textit{Query"=Cache} werden die Abfragen beziehungsweise die Eigenschaften einer Abfrage und die zurückgelieferten Ids der
|
|
Entities gespeichert. Bei einem erneuten Aufruf dieser Abfrage werden die referenzierten Objekte aus dem
|
|
\textit{Objekt"=Cache} zurückgegeben. Bei veränderten referenzierten Entities wird der \textit{Query"=Cache} nicht
|
|
genutzt und die betroffenen Abfragen werden unverzüglich aus dem \textit{Query"=Cache} entfernt
|
|
\citep[316]{MüllerWehr2012}.
|
|
|
|
Um zu prüfen, ob die Einstellungen sinnvoll gesetzt sind, kann in OpenJPA eine Cache"=Statistik abgefragt werden. Mit
|
|
dieser kann die Anzahl der Lese"~ und Schreibzugriffe im Cache überprüft werden, entsprechend dieser Auswertung sollten
|
|
die Einstellungen an den Entities angepasst werden \citep{IbmOpenJPACaching2023}.
|
|
|
|
\section{PostgreSQL - Memory Buffers}
|
|
\label{sec:basics:memorybuffers}
|
|
|
|
Die Speicherverwaltung des PostgreSQL"=Servers muss für Produktivsysteme angepasst werden \citep[34-38]{Eisentraut2013}.
|
|
Hierunter fallen die \texttt{shared\_buffers}, die bei circa 10 bis 25 Prozent des verfügbaren Arbeitsspeichers liegen
|
|
sollten. Mit dieser Einstellung wird das häufige Schreiben des Buffers durch Änderungen von Daten und Indexen auf die
|
|
Festplatte reduziert.
|
|
|
|
Die Einstellung \texttt{temp\_buffers} definiert, wie groß der Speicher für temporäre Tabellen pro
|
|
Verbindung maximal werden darf und sollte ebenfalls überprüft werden. Ein zu kleiner Wert bei großen temporären Tabellen
|
|
führt zu einem signifikanten Leistungseinbruch, wenn die Tabellen nicht im Hauptspeicher, sondern in einer Datei
|
|
ausgelagert werden.
|
|
|
|
Der \texttt{work\_mem} definiert die Obergrenze des zur Verfügung gestellt Hauptspeichers pro Datenbankoperation wie
|
|
effizientes sortieren, verknüpfen oder filtern. Ebenso wird im Falle eines zu klein gewählten Speichers auf temporäre
|
|
Dateien auf der Festplatte ausgewichen, was signifikante Leistungseinbrüche zur Folge haben kann.
|
|
|
|
Die \texttt{maintenance\_work\_mem} wird bei Verwaltungsoperationen wie Änderungen und Erzeugungen von Datenbankobjekten
|
|
als Obergrenze definiert. Die Wartungsaufgabe \texttt{VACUUM}, welche die fragmentierten Tabellen aufräumt und
|
|
somit die Performance hebt, beachtet die Obergrenze ebenfalls.
|
|
|
|
\section{PostgreSQL - Services}
|
|
\label{sec:basics:services}
|
|
|
|
Die Wartung des Datenbanksystems ist eine der wichtigsten Aufgaben und sollte regelmäßig
|
|
durchgeführt werden, damit die Performance des Systems durch die Änderungen des Datenbestands nicht einbricht
|
|
\citep[75]{Eisentraut2013}. Hierfür gibt es den \texttt{VACUUM}"=Befehl, welcher entweder per Hand oder automatisch durch
|
|
das Datenbanksystem ausgeführt werden soll. Für die automatische Ausführung kann der maximal verwendete Speicher über
|
|
die Einstellung \texttt{autovacuum\_work\_mem} gesondert definiert werden \citep{PostgresPro:Chap20.4:2023}.
|
|
Neben dem Aufräumen durch \texttt{VACUUM} sollten auch die Planerstatistiken mit \texttt{ANALYZE}
|
|
\citep[83]{Eisentraut2013} aktuell gehalten werden, damit die Anfragen durch den Planer richtig optimiert werden können.
|
|
Für beide Wartungsaufgaben gibt es den Autovacuum"=Dienst, dieser sollte aktiv und richtig konfiguriert sein.
|
|
|
|
Mit dem Tool \textit{pgFouine} \citep[155]{Eisentraut2013} können die Logs des PostgreSQL Server analysiert und auf
|
|
Probleme hin untersucht werden. Hiermit können sehr einfach die häufigsten beziehungsweise langsamsten Anfragen
|
|
ermittelt werden.
|
|
|
|
\section{PostgreSQL - Abfragen}
|
|
\label{sec:basics:queries}
|
|
|
|
Für weitere Optimierungen werden anschließend die Anfragen einzeln überprüft. Hierfür ist es sinnvoll, die
|
|
Ausführungspläne der Abfrage zu analysieren \citep[252]{Eisentraut2013}, die verschiedenen Plantypen und ihre Kosten zu
|
|
kennen sowie die angegeben Werte für die Plankosten zu verstehen \citep[24-30]{Dombrovskaya2021}.
|
|
Besonderes Augenmerk gilt dem Vergleichen des tatsächlich ausgeführten mit dem ursprünglichen Plan
|
|
\citep[254]{Eisentraut2013}. Eines der wichtigsten Kennzeichen hierbei ist, ob die Zeilenschätzung akkurat war.
|
|
Größere Abweichungen weisen häufig auf veraltete Statistiken hin.
|
|
|
|
Um die Abfragen selbst zu optimieren, gibt es ein Vorgehen über mehrere Schritte \citep[304-308]{Dombrovskaya2021}.
|
|
Zuerst wird unterschieden, ob es sich um eine \textit{Kurze} oder eine \textit{Lange} Abfrage handelt. Im Falle einer
|
|
\textit{Kurzen} Abfrage werden zuerst die Abfragekriterien überprüft. Sollte dies zu keiner Verbesserung führen,
|
|
werden die Indexe geprüft. Ist dies ebenso erfolglos, wird die Abfrage nochmals genauer analysiert und so
|
|
umgestellt, dass die restriktivste Einschränkung zuerst zutrifft.
|
|
Bei einer \textit{Langen} Abfrage soll überprüft werden, ob es sinnvoll ist, das Ergebnis in einer Tabelle zu
|
|
speichern und bei Änderungen zu aktualisieren. Wenn dies nicht möglich ist, sollten die folgenden Schritte durchgeführt
|
|
werden. Zuerst wird der restriktivste Join gesucht und überprüft, ob dieser als Erstes ausgeführt wird. Anschließend fügt
|
|
man weitere Joins hinzu und prüft die Ausführungszeit und die Abfragepläne. Als Nächstes wird sich vergewissert, ob
|
|
große Tabellen nicht mehrfach durchsucht worden sind. Bei Gruppierungen ist noch zu prüfen, ob diese früher durchgeführt
|
|
werden können, um die Abfragemenge zu verringern.
|
|
|
|
Bei \textit{Langen} Abfragen ist die Abhandlung >>Optimizing Iceberg Queries with Complex Joins<<
|
|
\citep{10.1145/3035918.3064053} ein zusätzlicher Ratgeber, um die Performance zu steigern.
|
|
|
|
Des Weiteren können über das Modul \texttt{pg\_stat\_statements} Statistiken der Aufrufe, die an den Server gestellt
|
|
wurden, ermittelt werden \citep{PostgresF27:2023}. Hierbei können die am häufigsten Aufgerufenen und die Anfragen mit
|
|
der längsten Ausführungszeit ermittelt werden. Ohne das zusätzliche Modul können die Statistiken über die
|
|
Software \textit{pgBadger} erstellt werden. Dafür muss zusätzlich noch die Konfiguration des \textit{PostgreSQL}
|
|
angepasst werden.
|