Speicherplatz für das Web

Es gibt viele verschiedene Möglichkeiten, Daten im Browser zu speichern. Welches passt am besten zu Ihren Anforderungen?

Internetverbindungen können unterwegs instabil sein oder nicht vorhanden sein. Daher sind Offline-Unterstützung und zuverlässige Leistung gängige Funktionen in progressiven Web-Apps. Selbst in perfekten drahtlosen Umgebungen kann die Nutzerfreundlichkeit durch eine sinnvolle Verwendung von Caching und anderen Speichertechniken erheblich verbessert werden. Es gibt mehrere Möglichkeiten, Ihre statischen Anwendungsressourcen (HTML, JavaScript, CSS, Bilder usw.) und Daten (Nutzerdaten, Nachrichtenartikel usw.) im Cache zu speichern. Aber welche Lösung ist die beste? Wie viel kann ich speichern? Wie kann ich verhindern, dass es entfernt wird?

Was sollte ich verwenden?

Allgemeine Empfehlung zum Speichern von Ressourcen:

IndexedDB, OPFS und die Cache Storage API werden von allen modernen Browsern unterstützt. Sie sind asynchron und blockieren den Hauptthread nicht. Es gibt jedoch auch eine synchrone Variante der OPFS, die ausschließlich in Webworkern verfügbar ist. Sie sind über das window-Objekt, die Web Worker und die Service Worker zugänglich, sodass sie überall im Code verwendet werden können.

Wie sieht es mit anderen Speichermechanismen aus?

Es gibt noch weitere Speichermechanismen im Browser, die jedoch nur eingeschränkt genutzt werden können und zu erheblichen Leistungsproblemen führen können.

SessionStorage ist tabspezifisch und gilt für die Lebensdauer des Tabs. Er kann nützlich sein, um kleine Mengen sitzungsspezifischer Informationen wie einen IndexedDB-Schlüssel zu speichern. Sie sollte mit Vorsicht verwendet werden, da sie synchron ist und den Hauptthread blockiert. Sie ist auf etwa 5 MB begrenzt und kann nur Strings enthalten. Da sie tabulatorspezifisch ist, kann von Web Workern oder Service Workern nicht darauf zugegriffen werden.

LocalStorage sollte vermieden werden, da es synchron ist und den Hauptthread blockiert. Sie ist auf etwa 5 MB begrenzt und kann nur Strings enthalten. Auf LocalStorage kann nicht über Webworker oder Dienstworker zugegriffen werden.

Cookies werden verwendet, sollten jedoch nicht zum Speichern verwendet werden. Cookies werden mit jeder HTTP-Anfrage gesendet. Wenn Sie also mehr als eine kleine Menge an Daten speichern, erhöht sich die Größe jeder Webanfrage erheblich. Sie sind synchron und können nicht von Webworkern aufgerufen werden. Wie LocalStorage und SessionStorage sind auch Cookies auf Strings beschränkt.

Die File System Access API wurde entwickelt, um Nutzern das Lesen und Bearbeiten von Dateien auf ihrem lokalen Dateisystem zu ermöglichen. Der Nutzer muss die Berechtigung gewähren, bevor eine Seite in eine lokale Datei lesen oder in diese schreiben kann. Die Berechtigungen werden nicht über Sitzungen hinweg beibehalten, es sei denn, ein Dateihandle wird in IndexedDB im Cache gespeichert. Die File System Access API eignet sich am besten für Anwendungsfälle wie Editoren, bei denen Sie eine Datei öffnen, ändern und dann möglicherweise die Änderungen in der Datei speichern müssen.

Die File System API und die FileWriter API bieten Methoden zum Lesen und Schreiben von Dateien in einem Sandbox-Dateisystem. Diese Methode ist zwar asynchron, wird aber nicht empfohlen, da sie nur in Chromium-basierten Browsern verfügbar ist.

Wie viel kann ich speichern?

Kurz gesagt: Sehr viel, mindestens ein paar hundert Megabyte und potenziell Hunderte von Gigabyte oder mehr. Browserimplementierungen variieren, aber die Größe des verfügbaren Speichers hängt in der Regel vom verfügbaren Speicher auf dem Gerät ab.

  • Mit Chrome kann der Browser bis zu 80% des gesamten Speicherplatzes nutzen. Ein Ursprung kann bis zu 60 % des gesamten Speicherplatzes belegen. Sie können die StorageManager API verwenden, um das maximale verfügbare Kontingent zu ermitteln. Bei anderen Chromium-basierten Browsern kann es zu Abweichungen kommen.
    • Im Inkognitomodus reduziert Chrome den Speicherplatz, den ein Ursprung verwenden kann, auf etwa 5% des gesamten Speicherplatzes.
    • Wenn der Nutzer in Chrome die Option „Cookies und Websitedaten löschen, wenn alle Fenster geschlossen werden“ aktiviert hat, wird das Speicherkontingent deutlich auf maximal etwa 300 MB reduziert.
  • Firefox erlaubt es dem Browser, bis zu 50 % des freien Speicherplatzes zu nutzen. Eine eTLD+1-Gruppe (z.B. example.com, www.example.com und foo.bar.example.com) können bis zu 2 GB verbrauchen. Mit der StorageManager API können Sie ermitteln, wie viel Speicherplatz noch verfügbar ist.
  • Safari (sowohl auf dem Computer als auch auf Mobilgeräten) erlaubt anscheinend etwa 1 GB. Wenn das Limit erreicht ist, wird der Nutzer von Safari aufgefordert, das Limit in Schritten von 200 MB zu erhöhen. Ich konnte dazu keine offiziellen Dokumente finden.
    • Wenn eine PWA dem Startbildschirm in Safari auf Mobilgeräten hinzugefügt wird, wird ein neuer Speichercontainer erstellt und es wird nichts zwischen der PWA und Safari auf Mobilgeräten geteilt. Sobald das Kontingent für eine installierte PWA erreicht ist, gibt es anscheinend keine Möglichkeit, zusätzlichen Speicherplatz anzufordern.

Wenn eine Website in der Vergangenheit einen bestimmten Grenzwert für gespeicherte Daten überschritten hat, wurde der Nutzer vom Browser aufgefordert, die Berechtigung zu erteilen, mehr Daten zu verwenden. Wenn der Ursprung beispielsweise mehr als 50 MB belegt, wird der Nutzer vom Browser aufgefordert, ihm zu erlauben, bis zu 100 MB zu speichern. Anschließend wird er in 50-MB-Schritten noch einmal gefragt.

Heutzutage werden Nutzer in den meisten modernen Browsern nicht mehr dazu aufgefordert und Websites können ihr zugewiesenes Kontingent voll ausschöpfen. Die einzige Ausnahme ist Safari. Wenn das Speicherkontingent überschritten wird, wird eine Berechtigung angefordert, das zugewiesene Kontingent zu erhöhen. Wenn ein Ursprung versucht, mehr als sein zugewiesenes Kontingent zu verwenden, schlagen weitere Versuche zum Schreiben von Daten fehl.

Wie kann ich überprüfen, wie viel Speicherplatz verfügbar ist?

In vielen Browsern können Sie mit der StorageManager API ermitteln, wie viel Speicherplatz für den Ursprung verfügbar ist und wie viel Speicherplatz er belegt. Sie gibt die Gesamtzahl der von IndexedDB und der Cache API verwendeten Bytes an und ermöglicht die Berechnung des ungefähr verbleibenden verfügbaren Speicherplatzes.

if (navigator.storage && navigator.storage.estimate) {
  const quota = await navigator.storage.estimate();
  // quota.usage -> Number of bytes used.
  // quota.quota -> Maximum number of bytes available.
  const percentageUsed = (quota.usage / quota.quota) * 100;
  console.log(`You've used ${percentageUsed}% of the available storage.`);
  const remaining = quota.quota - quota.usage;
  console.log(`You can write up to ${remaining} more bytes.`);
}

Sie müssen Fehler bei Kontingentüberschreitungen erkennen (siehe unten). In einigen Fällen kann das verfügbare Kontingent den tatsächlich verfügbaren Speicherplatz überschreiten.

Prüfen

Während der Entwicklung können Sie die verschiedenen Speichertypen mit den DevTools Ihres Browsers prüfen und alle gespeicherten Daten löschen.

In Chrome 88 wurde eine neue Funktion hinzugefügt, mit der Sie das Speicherkontingent der Website im Speicherbereich überschreiben können. Mit dieser Funktion können Sie verschiedene Geräte simulieren und das Verhalten Ihrer Apps in Szenarien mit geringer Speicherplatzverfügbarkeit testen. Klicken Sie auf Anwendung und dann auf Speicher. Aktivieren Sie das Kästchen Benutzerdefiniertes Speicherkontingent simulieren und geben Sie eine gültige Zahl ein, um das Speicherkontingent zu simulieren.

Während der Arbeit an diesem Leitfaden habe ich ein einfaches Tool geschrieben, mit dem ich schnell so viel Speicherplatz wie möglich nutzen konnte. So können Sie schnell verschiedene Speichermechanismen ausprobieren und sehen, was passiert, wenn Sie Ihr gesamtes Kontingent aufbrauchen.

Was passiert, wenn ich mein Kontingent überschreite?

Was sollten Sie tun, wenn Sie Ihr Kontingent überschreiten? Am wichtigsten ist, dass Sie Schreibfehler immer abfangen und behandeln, unabhängig davon, ob es sich um einen QuotaExceededError oder einen anderen Fehler handelt. Entscheiden Sie dann je nach App-Design, wie Sie damit umgehen möchten. Sie können beispielsweise Inhalte löschen, auf die seit langem nicht zugegriffen wurde, Daten basierend auf der Größe entfernen oder Nutzern die Möglichkeit geben, auszuwählen, was sie löschen möchten.

Sowohl IndexedDB als auch Cache API geben eine DOMError namens QuotaExceededError aus, wenn Sie das verfügbare Kontingent überschreiten.

IndexedDB

Wenn das Kontingent des Ursprungs überschritten wurde, schlagen Schreibversuche in IndexedDB fehl. Der onabort()-Handler der Transaktion wird aufgerufen und ein Ereignis übergeben. Das Ereignis enthält in der Fehlereigenschaft einen DOMException. Wenn Sie den Fehler name prüfen, wird QuotaExceededError zurückgegeben.

const transaction = idb.transaction(['entries'], 'readwrite');
transaction.onabort = function(event) {
  const error = event.target.error; // DOMException
  if (error.name == 'QuotaExceededError') {
    // Fallback code goes here
  }
};

Cache API

Wenn der Ursprung sein Kontingent überschritten hat, werden Schreibzugriffe auf die Cache API mit der Meldung QuotaExceededError DOMException abgelehnt.

try {
  const cache = await caches.open('my-cache');
  await cache.add(new Request('/sample1.jpg'));
} catch (err) {
  if (error.name === 'QuotaExceededError') {
    // Fallback code goes here
  }
}

Wie funktioniert die Deaktivierung?

Der Webspeicher wird in zwei Kategorien eingeteilt: „Bester Aufwand“ und „Dauerhaft“. „Beste Leistung“ bedeutet, dass der Speicher vom Browser ohne Unterbrechung des Nutzers gelöscht werden kann, aber weniger langlebig für langfristige oder kritische Daten ist. Der nichtflüchtige Speicher wird nicht automatisch gelöscht, wenn der Speicherplatz knapp wird. Der Nutzer muss diesen Speicherplatz manuell über die Browsereinstellungen leeren.

Standardmäßig werden die Daten einer Website (einschließlich IndexedDB, Cache API usw.) der Best-Effort-Kategorie zugeordnet. Das bedeutet, dass der Browser Websitedaten nach eigenem Ermessen entfernen kann, wenn eine Website keinen dauerhaften Speicher angefordert hat, beispielsweise bei geringem Gerätespeicher.

Die Bereinigungsrichtlinie für bestmögliche Ergebnisse lautet:

  • In Chromium-basierten Browsern werden Daten entfernt, wenn der Speicherplatz des Browsers aufgebraucht ist. Dabei werden zuerst alle Websitedaten des am wenigsten verwendeten Ursprungs gelöscht, dann die des nächsten, bis das Limit nicht mehr überschritten wird.
  • Wenn der verfügbare Speicherplatz belegt ist, beginnt Firefox, Daten zu entfernen. Dabei werden zuerst alle Websitedaten aus der am längsten nicht verwendeten Quelle und dann aus der nächsten gelöscht, bis das Limit nicht mehr überschritten wird.
  • Bisher wurden in Safari keine Daten entfernt. Vor Kurzem wurde jedoch eine neue Obergrenze von sieben Tagen für den gesamten beschreibbaren Speicherplatz eingeführt (siehe unten).

Ab iOS und iPadOS 13.4 sowie in Safari 13.1 unter macOS gilt eine Obergrenze von sieben Tagen für den gesamten skriptschreibbaren Speicher, einschließlich IndexedDB, Service-Worker-Registrierung und Cache API. Das bedeutet, dass Safari sieben Tage nach der Nutzung von Safari alle Inhalte aus dem Cache entfernt, wenn der Nutzer nicht mit der Website interagiert. Diese Richtlinie gilt nicht für installierte PWAs, die dem Startbildschirm hinzugefügt wurden. Ausführliche Informationen finden Sie im WebKit-Blog unter Full-Drittanbieter-Cookies Blocking and More (Vollständige Blockierung von Drittanbieter-Cookies).

Storage-Buckets

Der Hauptzweck der Storage Buckets API besteht darin, Websites die Möglichkeit zu geben, mehrere Storage Buckets zu erstellen, wobei der Browser jeden Bucket unabhängig von anderen löschen kann. So können Entwickler die Priorisierung der Bereinigung festlegen, damit die wertvollsten Daten nicht gelöscht werden.

Bonus: Warum einen Wrapper für IndexedDB verwenden

IndexedDB ist eine Low-Level-API, die vor der Verwendung umfassend eingerichtet werden muss. Dies kann besonders mühsam sein, wenn Daten mit geringer Komplexität gespeichert werden. Im Gegensatz zu den meisten modernen versprechenbasierten APIs ist sie ereignisbasiert. Promise-Wrapper wie idb für IndexedDB verbergen einige der leistungsstarken Funktionen, aber vor allem die komplexen Abläufe (z. B. Transaktionen, Schemaversionierung), die mit der IndexedDB-Bibliothek verbunden sind.

Bonus: SQLite Wasm

Nachdem Web SQL eingestellt und aus Chrome entfernt wurde, hat Google mit den Entwicklern der beliebten SQLite-Datenbank zusammengearbeitet, um einen Web SQL-Ersatz auf SQLite-Basis anzubieten. Weitere Informationen zur Verwendung finden Sie unter SQLite Wasm im Browser mit Unterstützung des privaten Dateisystems.

Fazit

Vorbei sind die Zeiten, in denen der Speicherplatz begrenzt war und Nutzer immer mehr Daten speichern mussten. Websites können alle erforderlichen Ressourcen und Daten effizient speichern. Mit der StorageManager API können Sie ermitteln, wie viel Speicherplatz Ihnen zur Verfügung steht und wie viel Sie bereits belegt haben. Und mit persistentem Speicher können Sie die Daten vor dem Löschen schützen, es sei denn, der Nutzer entfernt sie.

Zusätzliche Ressourcen

Vielen Dank

Besonderer Dank geht an Jarryd Goodman, Phil Walton, Eiji Kitamura, Daniel Murphy, Darwin Huang, Josh Bell, Marijn Kruisselbrink und Victor Costan, die diesen Leitfaden geprüft haben. Danke an Eiji Kitamura, Addy Osmani und Marc Cohen, die die ursprünglichen Artikel verfasst haben, auf denen diese Artikel basieren. Eiji hat ein hilfreiches Tool namens Browser Storage Abuser entwickelt, mit dem sich das aktuelle Verhalten überprüfen lässt. Damit können Sie so viele Daten wie möglich speichern und die Speicherlimits in Ihrem Browser sehen. Danke an François Beaufort, der die Speicherlimits in Safari ermittelt hat, und an Thomas Steiner, der Informationen über das private Dateisystem des Ursprungs, Storage-Buckets, SQLite Wasm und ein allgemeines Inhaltsupdate im Jahr 2024 hinzugefügt hat.

Das Hero-Image stammt von Guillaume Bolduc bei Unsplash.