Clientseitige Datenspeicherung für Webapplikationen - SOTA

Clientseitige Datenspeicherung für Webapplikationen – SOTA

Von am 04.03.2023

Abstract—Immer mehr Teile von modernen Webapplikationen werden auf den Client verschoben um schneller auf User Interaktionen reagieren zu können. Da eine lokale Verfügbarkeit von Daten ebenfalls einen positiven Einfluss auf die Ladezeiten einer Webseite nehmen kann, wurden Möglichkeiten geschaffen, um diese dauerhaft clientseitig speichern zu können. In Rahmen dieses Papers wurde betrachtet, welche Vorteile die lokale Speicherung von Daten hat und welche relevanten Möglichkeiten es momentan dafür gibt. Für einfache Datensätze hat sich dabei die HTML5 WebStorage API mit dem Localstorage als gute Lösung herausgestellt. Da diese allerdings bei komplexen Daten schnell an ihre Grenzen stößt, empfiehlt es sich für umfangreichere Daten IndexedDB zu verwenden. Diese JavaScript-basierte NoSQL Datenbank ermöglicht es eine Vielzahl von Datentypen direkt im Browser zu speichern und abzufragen. Anschließend wurde noch eine Technik betrachtet, welche das Speichern von relationalen Datenverbindungen auf Clientseite ermöglicht. Dabei werden Abfragen mit einer Kombination aus SQL.JS und einem entsprechenden Erweiterungspackage in IndexedDB gespeichert. Zusätzlich wurde noch die Speicherkapazität von Browsern analysiert. Diese unterscheidet sich in den einzelnen Browsern, ist aber hauptsächlich vom verfügbaren Gerätespeicher abhängt.

EINLEITUNG

Über die Jahre hat sich das Web zur wichtigsten Plattform entwickelt, um Applikationen für Nutzer:innen zugänglich zu machen [1]. Durch diese große Nachfrage mussten neue Technologien entwickelt werden, um das breite Spektrum an benötigten Funktionalitäten abdecken zu können. Hier kommen Rich Internet Applications (RIAs) ins Spiel [2]. Das Ziel dabei ist es eine app-ähnliche User Experience zu erzielen, welche schnell userspezifische Inhalte anzeigen kann. Dabei werden Teile der Webseite auf den Client verschoben um server-unabhängig agieren zu können [2]. Grundsätzlich sind Webapplikationen dabei in drei verschiedenen Schichten unterteilt. Bei traditionellen Webseiten sind sowohl Presentation Layer als auch Business Layer und Data Layer auf dem Server angesiedelt. Moderne Ansätze tendieren dazu sowohl die Präsentation als auch Teile der Business Logik auf den Client zu übertragen um auf Usereingaben ohne Serverabfragen reagieren zu können [3]. Doch wie schaut es jetzt mit dem Data Layer und weiter noch mit der Datenbank selbst aus? Kann es Sinn machen auch diese für gewisse Anwendungsfälle auf den Client auszulagern und wenn ja, welche Möglichkeiten stehen Entwickler:innen zur Verfügung, um eine clientseitige Datenspeicherung umzusetzen?

VORTEILE VON LOKALER DATENSPEICHERUNG

Bei der Frage nach clientseitiger Datenspeicherung dreht sich die Diskussion nicht darum Data Layer und Datenbank auf Serverseite zu ersetzen. Genau wie beim Business Layer wird es immer Aspekte geben, die in einer sicheren Serverumgebung besser aufgehoben sind als am Client. Ganz im Gegenteil, soll die lokale Verfügbarkeit von Daten serverseitige Datenbanken ergänzen und somit zu einer besseren Performance der Webapplikation beitragen [2]. Im Grunde ist es das Ziel dieses Ansatzes die Vergesslichkeit des Browsers zu kompensieren. Diese werden zwar immer mächtiger und leistungsfähiger aber alle Daten, welche einer Webapplikation zu Grunde liegen, müssen zunächst mittels HTTP Request von einem Server abgefragt werden. Dies führt dazu, dass bestimmte Daten bei jedem Seitenaufruf erneut abgerufen werden müssen, auch wenn sich diese in der Zwischenzeit gar nicht verändert haben. Eine lokale Instanz der Daten würde somit die Anzahl der benötigten Request reduzieren. Häufig benötigte Daten könnten so zuerst aus dem lokalen Speicher geladen und nur bei Bedarf vom Server abgefragt werden. Somit wird nicht nur der Netzwerk Traffic und die benötigte Bandbreite reduziert sondern auch die Arbeitslast des Servers verringert [4]. Zudem wirkt sich dieses Vorgehen auch positiv auf die Performance der Applikation aus, da das Abrufen von lokal verfügbaren Daten in der Regel immer schneller ist als der Zugriff auf Server Ressourcen [2]. Ein weiterer großer Vorteil liegt in der Offline-Verfügbarkeit. Gerade wenn man Webapplikationen so nahe wie möglich an native Apps heranführen möchte, müssen diese auch zu einem gewissen Grad offline nutzbar sein. So kann die Interaktion auch ohne Internetverbindung weitergeführt werden. Änderungen werden dabei zunächst lokal festgehalten und bei der nächsten Möglichkeit an den Server übermittelt. So kann die Verfügbarkeit eines Service weiterhin aufrecht gehalten werden [5].

WEBSTORAGE

Der WebStorage stellt die einfachste und bekannteste Möglichkeit dar, um Daten persistent im Browser zu hinterlegen. Er wurde mit HTML5 eingeführt um Cookies als damals alleinstehende Möglichkeit zur clientseitigen
Datenspeicherung zu ergänzen. Der große Vorteil des WebStorage liegt dabei in Speicherplatz und Datensicherheit. Anders als bei Cookies werden Daten nicht mehr mit jedem Request übertragen und können somit auch nicht so leicht ausgelesen werden. Zusätzlich bietet der WebStorage Speicherplatz für mindestens 5 MB, was die maximalen 4 KB von Cookies deutlich übersteigt [6]. Die WebStorage API setzt sich dabei aus zwei verschiedenen APIs zusammen: LocalStorage und SessionStorage. Beide speichern die Datensätze anhand von Key-Value Paaren, wobei jeweils Schlüssel als auch zugehöriger Wert in String Form angegeben werden müssen. Der Hauptanwendungsfall liegt dabei einfache Datensätze wie Access-Tokens zu speichern. Der Unterschied der beiden Schnittstellen zeigt sich in der Speicherdauer ihrer Einträge. Der Session Storage ist, wie sein Name andeutet, für Daten vorbehalten, die nur in der aktuellen Session benötigt werden. Die Einträge werden gelöscht, sobald das zugehörige Fenster geschlossen wird. Somit eignet sich diese API nicht zur persistenten Speicherung von Daten [7]. Für diese Zwecke ist der LocalStorage vorgesehen. Er hat den Vorteil, dass die Einträge auch über eine Session hinaus bestehen bleiben. Die Erreichbarkeit der Daten ist so abgekapselt, dass jede Webseite auf ihre eigene Instanz des LocalStorage zugreift. So können Daten zwar von mehreren Tabs derselben Seite, nicht aber von anderen Webseiten aufgerufen werden [6]. Ein weiterer Vorteil der API ist ihre einfache Nutzung, wobei die Getter und Setter Funktionen die wichtigsten Bestandteile darstellen. Der Funktion „localstorage.getItem()“ wird der gewünschte Key übergeben um den Entsprechenden Wert zu erhalten. Ein neuer Eintrag wird mit „localstorage.setItem()“ angelegt. Die zwei benötigten Parameter sind hierbei Key und Value [8]. Die LocalStorage API wird von allen verfügbaren Browsern unterstützt [9]. Zwar lassen sich einfache Werte leicht mit dieser Technologie speichern aber bei großen und komplexen Datenstrukturen stößt man hier schnell an seine Grenzen, da man auf String Werte limitiert ist. Um Objekte im JSON-Format abzuspeichern, müssen diese folglich vorher serialisiert und anschließend wieder rückgewandelt werden. Vor allem bei größeren Datenmengen wird der API zusätzlich auch ihre Synchronität zum Verhängnis. Da die Ausführung von anderen JavaScript Funktionen blockiert wird bis ein Lese- oder Schreibvorgang durchgeführt wurde, kann es zu Hängern in der Seite kommen. Dabei kann die API auch nicht an Web Worker übergeben werden, um das Problem zu beheben. Auch bei der Suche nach bestimmten Werten stößt man bei Key-Value Paaren an Grenzen, vor allem da Werte nur anhand des Schlüssels aus dem Storage ausgelesen werden können [7].

INDEXEDDB

Wenn einfache Key-Value Pairs nicht mehr ausreichen und komplexere Datenstrukturen am Client abgespeichert werden sollen, kommt IndexedDB ins Spiel. Dabei handelt es sich um eine auf JavaScript basierende objektorientierte Datenbank [10]. Es ist somit eine NoSQL Datenbank auf Browserebene. NoSQL ist dabei ein Überbegriff für Datenbanken, deren Datensätze nicht in relationalen Verbindungen festgehalten sind. Anders als bei SQL gibt es hier keine standardisierte Sprache zur Datenabfrage. Folglich unterscheidet sich der Syntax von Datenbank zu Datenbank. Der Fokus bei NoSQL liegt dabei auf der Verfügbarkeit und Skalierbarkeit der Systeme.
Bei IndexedDB handelt es sich zwar um einen einheitlichen Standard, die Implementierung fällt allerdings in verschiedenen Browsern unterschiedlich aus. Beispielsweise nutzt Chrome LevelDB als Grundlage für ihre Umsetzung, wogegen Firefox auf SQLite setzt. Genau wie beim WebStorage kommt auch hier die Same-origin security policy zum Einsatz. Diese schreibt fest, dass Ressourcen unterschiedlicher Webseiten nicht miteinander interagieren dürfen. Somit sind die gespeicherten Inhalte nur der Webseite zugänglich, welche sie generiert hat [2].
Die Struktur von IndexedDB wurde in Form eines Key-Value Stores umgesetzt. Wie beim WebStorage hat jeder Eintrag einen eindeutigen Key [2]. Dieser Schlüssel kann jedoch auch automatisch erzeugt werden und es besteht die Möglichkeit ihn als Teil in einen Datensatz zu inkludieren. Die Daten selbst werden in Form von Objekten gespeichert [7]. Es wird zunächst ein Objekt Speicher angelegt und an diesen ein JavaScript Objekt gebunden [2]. Hier ergibt sich der größte Vorteil gegenüber den String Values des LocalStorage. Es kann eine Vielzahl an unterschiedlichen Datentypen abgelegt werden, ohne dass diese vorher serialisiert oder umgewandelt werden müssen. Somit können auch komplexere Strukturen abgebildet werden. Ein weiterer Vorteil liegt in der Asynchronität der IndexedDB API. Bei größeren Datenmengen wird der Thread jetzt nicht mehr blockiert und die Applikation kann unbeeinflusst weiterarbeiten, während die Daten im Hintergrund abgefragt werden. Zudem können die Aufrufe jetzt auch einen Web Worker abgegeben werden. Anders als beim Webstorage, bringt IndexedDB wie eine vergleichbare Backend Datenbank Funktionen zur Abfrage von Datensätzen mit. So können diese auch gefiltert und sortiert werden [7]. Zudem unterstützt der Service ebenfalls Versionen und Transaktionen [11].
Aufgrund der umfangreicheren Möglichkeiten gestaltet sich auch die Implementierung komplizierter. Zunächst muss erst einmal eine Verbindung mit der Datenbank hergestellt werden. Dafür wird wie in Listing 1 zu sehen mit der open-Methode ein request-Objekt angefordert. Der Funktionen werden dabei Datenbankname und Version übergeben [12]. Um Datensätze speichern zu können muss zunächst ein ObjectStore instanziiert werden, welcher ebenfalls einen Namen und den Namen des Schlüsselfeldes bekommt. Im angeführten Beispiel wird der Key automatisch generiert. Anschließend werden mit „createIndex“ die weiteren Felder des Objekts hinzugefügt werden. Danach können Transaktionen genutzt werden, um auf einen ObjectStore zuzugreifen. Mit ihnen können Daten hinzugefügt, ausgelesen oder gelöscht werden [13].

<script>
const request = indexedDB.open(dbName, 3);
const objStore = db.createObjectStore("names", { autoIncrement : true });
objectStore.createIndex("name", "name", { unique: false });
</script>
Listing 1: Nutzung von IndexedDB, in Anlehnung an [13]

SQL.JS

Zwar gibt es mit IndexedDB bereits eine Möglichkeit umfangreiche Daten im Frontend zu speichern, allerdings basiert diese auf einer NoSQL Datenbank. Gerade um komplexe Abfragen durchzuführen, bedarf es hier einiges an

Vorwissen und Kreativität, um Daten teils über Umwege zu bekommen. Zudem ist SQL immer noch die vorherrschende Sprache für Datenbanken im Backend. Mit WebSQL gab es bereits den Versuch einer SQL-Datenbank für den Client. Diese scheiterte allerdings an einer einheitlichen Implementierung, woraufhin die Browserunterstützung eingestellt wurde [7]. Mit SQL.JS gibt es eine Möglichkeit, um eine relationale Datenbank im Browser auszuführen. Diese basiert dabei auf SQLite. Somit können auch die Features von SQLite wie Views und komplexe Abfragen genutzt werden. Es können auch bereits vorhandene Datenbankfiles importiert und die Datenbank als JavaScript typed array exportiert werden. Der SQLite Code wird dabei nach WebAssembly kompiliert und läuft damit fast mit nativer Geschwindigkeit. Die einzige Einschränkung, welche sich dabei ergibt ist, dass die Daten nicht persistent im Browser gespeichert werden. Es kann nur die gesamte Datenbank im Anschluss exportiert und im WebStorage oder IndexedDB abgelegt werden [7], [14].
Um dieses Problem zu beheben, wurde mit dem Projekt absurd-sql ein Interessanter Ansatz verfolgt. Dieses Package nutzt IndexedDB um ein Backend für SQL.JS zu erstellen, in welchem die Daten im Endeffekt gespeichert werden. Es wird also im Prinzip eine Datenbank in der Datenbank gespeichert. Somit erhält man die mächtigen Möglichkeiten von SQLite und kann den persistenten Storage des Browsers nutzten. Auch die Geschwindigkeit dürfte dabei nicht beeinflusst werden, denn laut Autor werden die Abfragen in der Regel sogar schneller ausgeführt als die nativen Funktionen von IndexedDB. Auch wenn diese Lösung noch keine standardisierte Technologie für größere Projekte darstellt, zeigt es welche Möglichkeiten sich für die Speicherung von Daten mittels moderner Webtechnologien ergeben [7], [15].

SPEICHERPLATZ

Neben der Frage, wo die Daten auf Clientseite am besten abgespeichert werden sollen, ist es auch relevant zu wissen wie viel Speicherplatz im Browser zur Verfügung steht. Diese Frage kann mit keinem pauschalen Wert beantwortet werden, da die Implementierung von Browser zu Browser unterschiedlich ist. Jedoch ist der Speicherplatz in den meisten Fällen vom verfügbaren Gerätespeicher abhängig. Zusätzlich wird er durch individuelle Usereinstellungen beeinflusst [16].


A. Chrome
Im Chrome Browser kann der Speicherplatz bis zu 80% des verfügbaren Gerätespeichers einnehmen. Eine einzige Webseite bekommt dabei jedoch nur bis zu 60%. Bei der Nutzung des Inkognito Modus wird der Speicherplatz jedoch drastisch eingeschränkt. Hier stehen nur mehr 5% zur Verfügung [16].


B. Firefox
Firefox erlaubt dem Browser bis zu 50% des Speicherplatzes zu beanspruchen. Jedoch wird auch hier der Platz für Inhalte einer einzelnen Domain weiter eingeschränkt. Hier wird ein Maximum von 2GB vorgegeben [16].


C. Safari
Die Kapazitätsgrenze liegt in Safari bei rund 1GB. Wenn diese Grenze erreicht wurde, kann diese vom User mittels eines Dialogs in Schritten von 200MB erhöht werden [16].
Um herauszufinden, wie viel Speicher auf lokaler Ebene noch zur Verfügung steht, kann die StorageManager API genutzt werden. Diese verfügt über eine asynchrone Estimate Funktion, welche sowohl den Wert des bereits belegten Speichers als auch den noch verfügbaren Platz zurückliefert. So kann bereits vorzeitig abgeschätzt werden, ob der benötigte Speicherplatz vorhanden ist. Die API Funktion ist für alle Browser mit der Ausnahme von Safari verfügbar [7], [17].
Wenn die maximale Speicherkapazität des Browsers einmal erreicht ist, können keine Daten mehr auf lokaler Ebenen gespeichert werden. Dies führt zu Fehlermeldungen in den einzelnen Services, welche abgefangen werden müssen [16]. Um dieses Limit gar nicht erst zu erreichen, reagieren chromium-basierte Browser und Firefox, indem sie Daten löschen. Dabei werden zunächst alle Einträge der Seite entfernt, welche schon am längsten nicht mehr aufgerufen wurde. Von dort an wird Seite für Seite abgearbeitet, bis wieder genug Speicherplatz verfügbar ist. Wenn die Webapplikation jetzt kritische Daten lokal gespeichert hat, sollte dieser Vorgang verhindert werden [18]. Dabei kann man sich den Umstand zu Nutze machen, dass der Webstorage aus zwei Teilen besteht. Auf der einen Seite steht der „Best Effort“ Bereich, dessen Inhalte entfernt werden können ohne Nutzer:innen zu unterbrechen. Für Daten, welche möglichst lange erhalten bleiben sollen oder besonders kritisch sind, ist der “Persistent“ Bereich vorgesehen. Die darin befindlichen Daten können nur vom User manuell gelöscht werden. Um Daten in den Persistent-Storage zu schreiben muss allerdings erst die Berechtigung über die Permissions API angefragt werden [16]. Diese ist für alle Browser mit Ausnahme von Safari und Internet Explorer nutzbar [19]. Das Handling der Permission erfolgt dabei wieder unterschiedlich. Bei Chromium-basierten Browsern wird die Entscheidung vom Browser selbst getroffen. Sie basiert unter anderem auf Daten über die Seiteninteraktion und ob diese als Lesezeichen markiert wurde. Firefox überlässt die Entscheidung den Nutzer:innen, welche über einen Dialog festlegen, ob Daten in den persistenten Teil des Webstorages geschrieben werden dürfen [19].

FAZIT

Dieses Paper setzt sich mit der Speicherung von Daten in Webapplikationen auf Clientseite auseinander. Dabei wurden zunächst die Vorteile einer lokalen Datenspeicherung ausgearbeitet. Die Frage bezieht sich hierbei darauf, wie Backend-Datenbanken von Lokalen Datenbanken unterstützt werden können. Es wird nicht der Anspruch gestellt diese zu ersetzten. Anschließend wurden vorhandene Lösungen für diese Problemstellung analysiert und deren Vor- und Nachteile ausgearbeitet. Hierbei zeigt sich, dass der Webstorage und dabei insbesondere der Localstorage einen guten Startpunkt bietet, um Daten auf Clientseite zu speichern. Aufgrund der einfachen Handhabung bedarf es nur geringer Vorkenntnisse. Gerade bei einfachen Datensätzen ist diese Speichermethode damit das Mittel zur Wahl. Dennoch hat sich gezeigt, dass die Technologie gerade bei komplexen Datenstrukturen und großen Datenmengen schnell an ihre Grenzen stößt. Dabei ist vor allem die Speicherung als Key-Value Paare ausschlaggebend, weil diese auf String Werte beschränkt sind. Für umfangreichere Daten bietet sich daher die Nutzung von IndexedDB an. Hierbei handelt es sich um eine JavaScript basierte NoSQL Datenbank, die ihre Daten in Form von Objekten abspeichert. Somit können nahezu alle Datentypen abgelegt werden. Zudem verfügt die Datenbank über alle wichtigen Funktionen, wie sie auch in einer Backend Datenbank zu finden wären. Darunter fallen Versionen, Transaktionen und die Möglichkeit Daten gefiltert abzufragen. Aufgrund ihrer zahlreichen Möglichkeiten steigt allerdings auch die Komplexität der Implementierung. Zusätzlich ist die API nicht sonderlich intuitiv gestaltet, was es vor allem für neue Entwickler:innen zum Einstieg schwer macht. Über die bereits bestehenden Lösungen hinaus, zeigt sich, dass auf Basis dieser auch die Umsetzung von relationalen Datenbanken am Client möglich ist. Trotz aller Vorteile bleiben Einschränkungen, welche sich durch die Client Umgebung ergeben. Hierbei muss vor allem die unterschiedliche Umsetzung einzelner Technologien in verschiedenen Browsern betrachtet werden. Zudem verliert man als Entwickler die Kontrolle über lokale Ressourcen. Die Speicherung von kritischen Daten auf Clientseite sollte also weiterhin gut abgewogen werden.

REFERENZEN

    [1] J. Kuuskeri, „Experiences on a Design Approach for Interactive Web Applications“, 2011.
    [2] A. Al-Shaikh und A. Sleit, „Evaluating IndexedDB performance on web browsers“, in 2017 8th International Conference on Information Technology (ICIT), Mai 2017, S. 488–494. doi: 10.1109/ICITECH.2017.8080047.
    [3] S. Tilkov, J. Heron, und L. Dohmen, „Klassischer Architekturansatz für Webanwendungen – JavaScript? Gern, aber bitte in Maßen“, PHP Magazin, Nr. 4, 2019.
    [4] R. Camden, Client-Side Data Storage: Keeping It Local. O’Reilly Media, Inc., 2015.
    [5] M. Jemel und A. Serhrouchni, „Security assurance of local data stored by HTML5 web application“, in 2014 10th International Conference on Information Assurance and Security, Nov. 2014, S. 47–52. doi: 10.1109/ISIAS.2014.7064619.
    [6] W. Jiang, M. Zhang, Y. Y. Huo, und Y. J. Jiang, „Research on application of the HTML5 local storage in a service system of theater 3D model“, in 2015 IEEE 5th International Conference on Electronics Information and Emergency Communication, Mai 2015, S. 368–371. doi: 10.1109/ICEIEC.2015.7284560.
    [7] R. Grundwald, „Der Browser als Datenbank“, entwickler.de Deine Wissensplattform. https://entwickler.de/reader/ (zugegriffen 7. November 2022).
    [8] „Using the Web Storage API – Web APIs | MDN“. https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API (zugegriffen 7. November 2022).
    [9] „‚localstorage‘ | Can I use… Support tables for HTML5, CSS3, etc“. https://caniuse.com/?search=localstorage (zugegriffen 7. November 2022).
    [10] „IndexedDB API – Web APIs | MDN“. https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API (zugegriffen 8. November 2022).
    [11] „IndexedDB – eines der am meisten unterschätzten Web-APIs“, entwickler.de Deine Wissensplattform, 10. August 2022. https://entwickler.de/javascript/indexeddb-web-api (zugegriffen 30. Oktober 2022).
    [12] S. Springer, „IndexedDB – eines der am meisten unterschätzten Web-APIs“, entwickler.de Deine Wissensplattform. https://entwickler.de/reader/ (zugegriffen 7. November 2022).
    [13] „Using IndexedDB – Web APIs | MDN“. https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB (zugegriffen 8. November 2022).
    [14] „sql.js“. https://sql.js.org/#/ (zugegriffen 9. November 2022).
    [15] J. Long, jlongster/absurd-sql. 2022. Zugegriffen: 9. November 2022. [Online]. Verfügbar unter: https://github.com/jlongster/absurd-sql
    [16] „Storage for the web“, web.dev. https://web.dev/storage-for-the-web/ (zugegriffen 8. November 2022).
    [17] „StorageManager.estimate() – Web APIs | MDN“. https://developer.mozilla.org/en-US/docs/Web/API/StorageManager/estimate (zugegriffen 9. November 2022).
    [18] „Persistent storage“, web.dev. https://web.dev/persistent-storage/ (zugegriffen 8. November 2022).
    [19] „Permissions API: persistent-storage permission | Can I use… Support tables for HTML5, CSS3, etc“. https://caniuse.com/mdn-api_permissions_persistent-storage_permission (zugegriffen 8. November 2022).

    The comments are closed.