\chapter{Expose} \label{ch:intro} \section{Ausgangslage} Die Grundlage zu dieser Arbeit bildet das DFG-Projekt \glqq Edition der Korrespondenz Frank Wedekinds als Online-Volltextdatenbank\grqq. Die folgende Übersicht hierzu ist eine Anlehnung an \citep{EffwFrankWedekind}. Die Editions- und Forschungsstelle Frank Wedekind (EFFW) wurde 1987 in der Hochschule Darmstadt gegründet. Ihr Intention ist es, den lange vernachlässigten Autor der europäischen Moderne in die öffentliche Aufmerksamkeit zu bringen. Die Publikation der >>Kritischen Studienausgabe der Werke Frank Wedekinds. Darmstädter Ausgabe<< wurde direkt nach der Erschließung der Wedekind-Nachlässe in Aarau, Lenzburg und München begonnen und im Jahre 2013 abgeschlossen. Da der 1864 geborene Frank Wedekind heute zu einen der bahnbrechenden Autoren der literarischen Moderne zählt, aber bisher sehr wenig erforscht wurde, soll sich dies nun Ändern. Die nationalen und internationalen Korrespondenzen von und an Wedekind zeigen eine starke Vernetzung in der europäischen Avantgarde. Aktuell sind lediglich 710 der 3200 bekannten Korrespondenzstücke veröffentlicht worden. Diese beinhalten substantiell das literarhistorische und kulturgeschichtliche Wissen über die Kultur zwischen 1880 und 1918, indem das überlieferte Material zum einen transkribiert editiert und zum anderen editionswissenschaftlich kommentiert wurde. Um jenes zu verändern entstand das Projekt >>Edition der Korrespondenz Frank Wedekind als Online-Volltextdatenbank<<, welches bei der EFFW angesiedelt ist und als Kooperationsprojekt an der Johannes Gu\-ten\-berg-Universität Mainz, der Hochschule Darmstadt und der Fernuni Hagen umgesetzt und durch die Deutsch Forschungsgemeinschaft (Bonn) gefördert wird. Das entstandene Pilotprojekt ist eine webbasiert Anwendung, die aktuell unter \url{http://briefedition.wedekind.h-da.de} eingesehen werden kann. Hierbei wurden sämtliche bislang bekannte Korrespondenzen in dem System digitalisiert. Die Briefe selbst werden im etablierten TEI-Format gespeichert und über einen WYSIWYG-Editor von den Editoren und Editorinnen eingegeben. Das Projekte wurde anhand von bekannten und etablierten Entwurfsmustern umgesetzt um eine modulare und unabhängige Architektur zu gewährleisten, damit dies für weitere digitale Briefeditionen genutzt werden kann. \section{Ziel} Die aktuelle Umsetzung beinhaltet die bisher definierten Anforderungen vollständig, darunter fallen die Recherchemöglichkeiten, sowie auch die Eingabe und die Verarbeitung der Briefe. Ein größeres Problem hierbei ist die Performance der Oberfläche. Auf Grund der langen Abfragedauer des Datenbestandes leidet die Akzeptanz der Anwendung. Das Ziel der Arbeit ist es, die Abfragedauer zu verringern, wodurch die Performance der Oberfläche signifikant verbessert wird. Hierbei ist auch ein Vergleich mit anderen Technologien angedacht. \section{Aktueller Forschungsstand} Da die Anwendung als Webseite umgesetzt ist, ist der zugehörige Client für den Benutzer ein Webbrowser. Dies bedeutet, das 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 \ref{fig:webrequest} dargestellt. 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 Routing entschieden, an welche \textit{Java Server Page} die Anfrage weitergeleitet und verarbeitet wird. In dieser wird die Darstellung der Webseite geladen und die Anfragen für den darzustellenden Datenbestand abgeschickt. Die Datenanfragen werden über die \textit{Enterprise Java Beans} an die \textit{Java Persistance API} 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 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, um 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} \subsection{Glassfisch - Enterprise Java Beans} In den Java-EE-An\-wen\-dung\-en 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 von Datenbankverbindungen zu definieren \citep[68]{MüllerWehr2012}. Dadurch kann die Anzahl der Verbindung geringer gehalten werden als die Anzahl der Benutzer die an der Anwendung arbeiten. Zusätzlich werden die Transaktionen über \textit{Stateful Session-Bean (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 Performanz-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. \subsection{Glassfish - Java Persinstance API} Die \textit{Java Persistence API (JPA)} wird als First-Level-Cache in Java-EE-An\-wen\-dung verwendet, hier nehmen die Objekte einen von vier Zuständen ein \citep[57]{MüllerWehr2012}. Im Zustand \textit{Transient} sind die Objekt erzeugt, aber noch nicht in den Cache überführt worden. Wenn diese in den Cache überführt worden sind, nehmen sie den Zustand \textit{Verwaltet} ein. Ist das Objekt aus dem Cache und der Datenbank entfernt worden, nimmt es den Zustand \textit{Gelöscht} an. \textit{Losgelöst} ist der letzte Zustand, bei dem das Objekt aus dem Cache entfernt worden ist, aber nicht aus der Datenbank. Eine Menge von Objekten wird als \textit{Persistenzkontext} bezeichnet. Solange die Objekte dem \textit{Persistenzkontext} zugeordnet sind, also den Zustand \textit{Verwaltet} besitzen, werden diese auf Änderungen überwacht, um sie am Abschluss mit der Datenbank zu synchronisieren. In der Literatur wird hierzu der Begriff \textit{Automatic Dirty Checking} verwendet \citep[61]{MüllerWehr2012}. \subsection{Glassfish - OpenJPA Cache} Zusätzlich kann im \textit{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}. Gegen die Verwendung spricht, dass die Daten im \textit{Second Level Cache} explizit über Änderungen informiert werden müssen, welche sonst beim nächsten Aufruf veraltete Werte liefern. Ebenfalls benötigt so 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 die 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 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 wird 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 einen 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}. \subsection{PostgreSQL - Memory Buffers} Die Speicherverwaltung des PostgreSQL-Servers muss für Produktivsysteme angepasst werden \citep[34-38]{Eisentraut2013}. Hierunter fallen die \textit{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 \textit{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 \textit{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 signifikanten Leistungseinbrüchen zur Folge haben kann. Die \textit{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. \subsection{PostgreSQL - 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 \textit{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 rmittelt werden. \subsection{PostgreSQL - Abfragen} 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}. Eine der wichtigsten Kennzeichen hierbei ist, ob die Zeilenschätzung akkurat war, größere Abweichungen weißen 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. \section{Vorgehen bei der Umsetzung} Durch eine Umfrage der Bediener und Entwickler, einer Per\-for\-mance-Mes\-sung in der Webseite und den Statistiken im PostgreSQL, sollen die größten Per\-for\-mance-Pro\-ble\-me in der Webseite ermittelt und der dazugehörigen Quellcode identifiziert werden. Für die Analyse und Optimierung der Abfragen sollen verschiedene Blickwinkel betrachtet werden. Bei den einzelnen Abfragen muss zuerst ermittelt werden, in welchem Teil des Aufrufs die meiste Zeit aufgewendet wird, hierbei wird die Übertragung über das Netzwerk außer acht gelassen, da diese vom Standort und nicht direkt von der Anwendung abhängt. Ein geplantes Vorgehen ist hierbei die Überprüfung von >>unten nach oben<<, wie in \ref{fig:webrequest} dargestellt. Zuerst soll der Aufruf gegen die Datenbank geprüft und die Ausführungszeit sowie die Abfragepläne ermittelt und analysiert. Wenn sich hierbei größere Defizite erkennen lassen, werden die Abfragen direkt optimiert, indem der Aufbau der Abfrage, die Abfragekriterien und die Verwendung der Indexe betrachtet und in Frage gestellt werden. Anschließend erfolgt die Prüfung des OpenJPA-Caches mit den zugehörigen Statistiken. Bei diesen wird einerseits ermittelt, wie sinnvoll der aktuelle Einsatz des Caches für die unterschiedlichen Entitäten ist und andererseits ob es sinnvoll ist die aktuelle Nutzung zu minimieren oder ihn komplett zu entfernen. Ein Ersatz mit besserer Performance soll in diesem Fall ebenso untersucht werden. Anschließend wird der Aufruf über die JPA betrachtet. Dies ist besonders wichtig, wenn die Abfragen dynamisch erzeugt werden und die SQL-Abfrage selbst nicht optimiert werden kann. In diesem Fall sollte nicht nur die reine Abfrage, sondern auch die Verwendung des Caches mit in Betracht gezogen werden. Nun wird die EJB-Schicht überprüft und die aufgerufen Funktionen betrachtet, ob hier einzelne Funktionen zu viele Aufgaben übernehmen und dadurch schlecht optimiert werden können. Solche Funktionen sollten dupliziert werden und auf die jeweilige Aufgabe spezifisch zugeschnitten und optimiert werden. Abschließend ist die \ac{JSF}-Schicht zu betrachten, welche noch logische Anpassungen für die Optimierungen zulässt, wie das Einfügen von Paging. Damit kann die ermittelnde Datenmenge verringert werden, dies führt zu schnelleren Abfragen, weniger Daten die im Cache vorgehalten werden müssen, den Aufbau der Seite zu beschleunigen und damit auch die Datenmenge die an den Browser übermittelt wird zu reduzieren. Zeitgleich werden der PostgreSQL sowie der Server selbst untersucht und die Einstellungen überprüft. Hierzu gehören die Größen der Speicher und die Wartungsaufgaben des Datenbanksystems. In diesem Zuge werden auch die Log-Dateien vom PostgreSQL, unter Zuhilfenahme von pgFouine untersucht und auf Probleme und Unregelmässigkeiten überprüft.