From 65b2315702c40cb2ceb8effcb14a1a43547accdb Mon Sep 17 00:00:00 2001 From: pubkey <8926560+pubkey@users.noreply.github.com> Date: Fri, 10 May 2024 21:30:54 +0200 Subject: [PATCH 01/15] ADD article --- .../localstorage-indexeddb-cookies-opfs-sqlite-wasm.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md diff --git a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md new file mode 100644 index 00000000000..eb672740240 --- /dev/null +++ b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md @@ -0,0 +1,10 @@ +# Localstorage vs. IndexedDB vs. Cookies vs. OPFS vs. Wasm-SQLite + + + +### Things this does not talk about +WebSQL +session storage. +Web Storage API + + From 501718a64eb3e137e43fbc25c1b6eba2f00dfd83 Mon Sep 17 00:00:00 2001 From: pubkey <8926560+pubkey@users.noreply.github.com> Date: Tue, 14 May 2024 01:20:53 +0200 Subject: [PATCH 02/15] ADD content --- ...rage-indexeddb-cookies-opfs-sqlite-wasm.md | 92 ++++++++++++++++++- docs-src/docs/articles/localstorage.md | 2 +- 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md index eb672740240..2d9207aa28b 100644 --- a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md +++ b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md @@ -1,10 +1,100 @@ # Localstorage vs. IndexedDB vs. Cookies vs. OPFS vs. Wasm-SQLite +So it is 2024 and you want to build your this awesome Web Application. To make you app fast and even work [offline](../offline-first.md), you want to store all data on the client device and run operations there, instead of awaiting requests to a backend server. +But there is a problem: -### Things this does not talk about +> Browser are slow and weren't made to run huge database operations. + +Or are they? Over the last few years, a lot of features have been added to JavaScript. New storage APIs such as OPFS and new features like the BroadcastChannel are here to let us reach the limits of JavaScript performance. + +In this article we have look at all previous and new technologies to store and query data in a browser. We will run performance tests and combine many performance hacks to find out how fast we can write and read a **huge amount of data** in a Web App. + + +## What is localstorage +## What is indexeddb +## What are cookies +## What is OPFS +## What is wasm sqlite + +## Things this does not talk about WebSQL session storage. Web Storage API + + +## Feature comparison + +### Multitab support + +A big difference when building a Web App compared to Electron or React-Native, is that the user will open and close the app in multiple browser tabs at the same time. Therefore you have not only one JavaScript process running, but many of them can exist and might have to share state changes between each other to not show outdated data to the user. + +Not all storage APIs support a way to automatically share write events between tabs. Only localstorage has the [storage-event](./localstorage.md#localstorage-vs-indexeddb) which can be used to observe changes. + +```js +// localStorage can observe changes with the storage event. +// This feature is missing in IndexedDB and others +addEventListener("storage", (event) => {}); +``` + +To workaround this problem, there are two solutions: +The first option is to use the [BroadcastChannel API](https://github.com/pubkey/broadcast-channel) which can send messages across browser tabs. So whenever you do a write to the storage, you also send a notification to other tabs to inform them about these changes. + +The other solution is to use the [SharedWorker](https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker) and do all writes inside of the worker. All browser tabs can then subscribe to messages from that SharedWorker and know about changes. + +### Indexing +- IndexedDB does not support boolean indexes +#### iterable indexes +#### secondary indexes +### Storing complex JSON documents + +### Worker Support + +- Localstorage and Cookies [cannot be used in WebWorker or SharedWorker](https://stackoverflow.com/questions/6179159/accessing-localstorage-from-a-webworker). + +- OPFS with the fast `createSyncAccessHandle` method can **only** [be used in a WebWorker](../rx-storage-opfs.md#opfs-limitations). + +## Performance Comparison + +Now that we know about the basics of the various storage solutions, lets compare their performance. As mentioned above, we not only want to store a few documents, instead lets store **many** of them and run **heavy and complex queries**. + +TODO add github repo url with performance tests. + +### Running many small operations + +One aspect of performance is the latency. The time to run a small database operation, either read or write. +Depending on your use case, it might be relevant that many small operations run fast, like when you have a browser game and want to store the game's state. + +### Running single big operations + +### Initial page load +How fast does the first query load when there are many documents +stored already. + + + + +## Lets reach the limits of client side storage performance with RxDB +- indexeddb optimizations +- compression with keycompression +- spliting work load with WebWorker +- sharding +- memory mapped/synced stuff + +- Store metadata in localstorage +- fix initial page load for new tabs with the SharedWorker (only chrome can spawn WebWorkers inside of a SharedWorker) + +- is OPFS faster then indexeddb? + + + +## Read further + +TODO fix links +- Check out the [hackernews discussion of this article](https://news.ycombinator.com/item?id=39745993) +- Shared/Like my [announcement tweet](https://twitter.com/rxdbjs/status/1769507055298064818) + +- Learn how to use RxDB with the [RxDB Quickstart](../quickstart.md) +- Check out the [RxDB github repo](https://github.com/pubkey/rxdb) and leave a star ⭐ diff --git a/docs-src/docs/articles/localstorage.md b/docs-src/docs/articles/localstorage.md index aa5552020f3..b5910a73b7b 100644 --- a/docs-src/docs/articles/localstorage.md +++ b/docs-src/docs/articles/localstorage.md @@ -92,7 +92,7 @@ While **localStorage** serves as a reliable storage solution for simpler data ne complex queries can pose a challenge with IndexedDB, and while its performance is acceptable, IndexedDB can be [too slow](../slow-indexeddb.md) for some use cases. ```js -// localStorage can observe chanes with the storage event. +// localStorage can observe changes with the storage event. // This feature is missing in IndexedDB addEventListener("storage", (event) => {}); ``` From 5386bbd8ccb7bf8fa0683ee9c4bdc7a655fbcaf8 Mon Sep 17 00:00:00 2001 From: pubkey <8926560+pubkey@users.noreply.github.com> Date: Wed, 15 May 2024 14:23:44 +0200 Subject: [PATCH 03/15] chore --- ...alstorage-indexeddb-cookies-opfs-sqlite-wasm.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md index 6946baf747a..162d0ccab5a 100644 --- a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md +++ b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md @@ -47,17 +47,27 @@ The first option is to use the [BroadcastChannel API](https://github.com/pubkey/ The other solution is to use the [SharedWorker](https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker) and do all writes inside of the worker. All browser tabs can then subscribe to messages from that SharedWorker and know about changes. ### Indexing -- IndexedDB does not support boolean indexes + +The big difference between a database and storing data in a plain file, is that a database is writing data in a format that allows running operations over indexes to facilitate fast queries. + #### iterable indexes +- IndexedDB does not support boolean indexes + #### secondary indexes +- Only IndexedDB and SQLite WASM has secondary indexes + + ### Storing complex JSON documents +- IndexedDB can store JSON natively +- SQLite can [store JSON](https://www.sqlite.org/json1.html) from version 3.38.0 (2022-02-22). + ### Worker Support - Localstorage and Cookies [cannot be used in WebWorker or SharedWorker](https://stackoverflow.com/questions/6179159/accessing-localstorage-from-a-webworker). - OPFS with the fast `createSyncAccessHandle` method can **only** [be used in a WebWorker](../rx-storage-opfs.md#opfs-limitations). - + 2 ## Performance Comparison Now that we know about the basics of the various storage solutions, lets compare their performance. As mentioned above, we not only want to store a few documents, instead lets store **many** of them and run **heavy and complex queries**. From c1647ea1ce965ef58625bf3ead77feb5cbcefcc0 Mon Sep 17 00:00:00 2001 From: pubkey <8926560+pubkey@users.noreply.github.com> Date: Sat, 8 Jun 2024 22:39:34 +0200 Subject: [PATCH 04/15] CHORE content --- ...rage-indexeddb-cookies-opfs-sqlite-wasm.md | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md index 162d0ccab5a..ff47dbe2b20 100644 --- a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md +++ b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md @@ -1,22 +1,39 @@ # Localstorage vs. IndexedDB vs. Cookies vs. OPFS vs. Wasm-SQLite -So it is 2024 and you want to build your this awesome Web Application. To make you app fast and even work [offline](../offline-first.md), you want to store all data on the client device and run operations there, instead of awaiting requests to a backend server. -But there is a problem: +Welcome to 2024, a time when building powerful, responsive web applications has never been more exciting. As developers, we constantly seek ways to make our apps faster and more efficient, and one crucial aspect of this is data storage. To ensure our apps not only perform well but also work seamlessly [offline](../offline-first.md), we aim to store data on the client device, minimizing the need to interact with backend servers. -> Browser are slow and weren't made to run huge database operations. +However, a common belief persists: -Or are they? Over the last few years, a lot of features have been added to JavaScript. New storage APIs such as OPFS and new features like the BroadcastChannel are here to let us reach the limits of JavaScript performance. +> Browser are slow and weren't designed to run extensive database operations. -In this article we have look at all previous and new technologies to store and query data in a browser. We will run performance tests and combine many performance hacks to find out how fast we can write and read a **huge amount of data** in a Web App. +Is this really the case? Over recent years, JavaScript has evolved significantly. New storage APIs like the Origin Private File System (OPFS) and new features like the BroadcastChannel have pushed the boundaries of what JavaScript can achieve in terms of performance. +In this article, we will delve into the various technologies available for storing and querying data in a browser. We'll explore traditional methods like Cookies, LocalStorage and IndexedDB, and newer solutions such as OPFS and Wasm-SQLite. Through performance tests we aim to uncover how fast we can write and read **a huge amount of data** in a web application with the various methods. And because you are reading this in the [RxDB](/) docs, we will utilize multiple RxDB plugins that contain innovative hacks to reach the performance limits of a browser in terms of database operations. + +
+ + JavaScript Database + +
+ ## What is localstorage -## What is indexeddb + +LocalStorage provides a simple way to store key-value pairs in a web browser. It's suitable for storing small amounts of data that need to persist across sessions but is [limited by a 5MB storage cap](./localstorage.md#understanding-the-limitations-of-local-storage) and the inability to store complex data types beyond strings. + ## What are cookies + +Cookies store small pieces of data that are sent with every HTTP request. They are mainly used for session management, personalization, and tracking, but are limited to about `4 KB` of data in [RFC-6265](https://datatracker.ietf.org/doc/html/rfc6265#section-6.1). +So we cannot store much data in a cookie but it is still interesting how good cookie access performance compared to the other methods. Especially because cookies are such an important base feature of the web, many performance optimizations have been done and even these days there is still progress being made like the [Shared Memory Versioning](https://blog.chromium.org/2024/06/introducing-shared-memory-versioning-to.html) by chromium. + +## What is IndexedDB + + ## What is OPFS -## What is wasm sqlite +## What is wasm sqlite +https://www.fermyon.com/blog/webassembly-wasi-and-the-component-model ### Things this does not talk about From a9a596ca5fa0893c348d016219ddee1bda7e8b37 Mon Sep 17 00:00:00 2001 From: pubkey <8926560+pubkey@users.noreply.github.com> Date: Tue, 11 Jun 2024 02:26:32 +0200 Subject: [PATCH 05/15] CHORE text --- ...rage-indexeddb-cookies-opfs-sqlite-wasm.md | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md index ff47dbe2b20..3584919339b 100644 --- a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md +++ b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md @@ -10,7 +10,7 @@ However, a common belief persists: Is this really the case? Over recent years, JavaScript has evolved significantly. New storage APIs like the Origin Private File System (OPFS) and new features like the BroadcastChannel have pushed the boundaries of what JavaScript can achieve in terms of performance. -In this article, we will delve into the various technologies available for storing and querying data in a browser. We'll explore traditional methods like Cookies, LocalStorage and IndexedDB, and newer solutions such as OPFS and Wasm-SQLite. Through performance tests we aim to uncover how fast we can write and read **a huge amount of data** in a web application with the various methods. And because you are reading this in the [RxDB](/) docs, we will utilize multiple RxDB plugins that contain innovative hacks to reach the performance limits of a browser in terms of database operations. +In this article, we will dive into the various technologies available for storing and querying data in a browser. We'll explore traditional methods like Cookies, LocalStorage and IndexedDB, and newer solutions such as OPFS and Wasm-SQLite. Through performance tests we aim to uncover how fast we can write and read **a huge amount of data** in a web application with the various methods. And because you are reading this in the [RxDB](/) docs, we will utilize multiple RxDB plugins that contain innovative hacks to reach the performance limits of a browser in terms of database operations.
@@ -18,22 +18,34 @@ In this article, we will delve into the various technologies available for stori
-## What is localstorage +## What is Localstorage LocalStorage provides a simple way to store key-value pairs in a web browser. It's suitable for storing small amounts of data that need to persist across sessions but is [limited by a 5MB storage cap](./localstorage.md#understanding-the-limitations-of-local-storage) and the inability to store complex data types beyond strings. -## What are cookies +## What are Cookies Cookies store small pieces of data that are sent with every HTTP request. They are mainly used for session management, personalization, and tracking, but are limited to about `4 KB` of data in [RFC-6265](https://datatracker.ietf.org/doc/html/rfc6265#section-6.1). -So we cannot store much data in a cookie but it is still interesting how good cookie access performance compared to the other methods. Especially because cookies are such an important base feature of the web, many performance optimizations have been done and even these days there is still progress being made like the [Shared Memory Versioning](https://blog.chromium.org/2024/06/introducing-shared-memory-versioning-to.html) by chromium. +This means we cannot store much data in a cookie but it is still interesting how good cookie access performance compared to the other methods. Especially because cookies are such an important base feature of the web, many performance optimizations have been done and even these days there is still progress being made like the [Shared Memory Versioning](https://blog.chromium.org/2024/06/introducing-shared-memory-versioning-to.html) by chromium. ## What is IndexedDB +IndexedDB is a low-level API for storing large amounts of structured (JSON) data. While the API is a bit hard to use, IndexedDB can utilize indexes and asynchronous operations. It lacks support for complex queries and only allows to iterate over the indexes which makes it more like a base layer for other libraries. The performance of basic IndexedDB operations can be problematic but there exist [several hacks](../slow-indexeddb.md) to improve writes and query speed. + ## What is OPFS +The [Origin Private File System](../rx-storage-opfs.md) (OPFS) is a relatively new API that allows web applications to store large files directly in the browser. It is designed for data-intensive applications that want to write and read binary data. +OPFS can be used in two modes: Either asynchronous on the [main thread](../rx-storage-opfs.md#using-opfs-in-the-main-thread-instead-of-a-worker) or in a WebWorker with the faster, aynchronous access. +Because only binary data can be processed, OPFS is made to be as a base filesystem for database libraries. You will unlikely directly want to use the OPFS in your applications code. + + +## What is WASM-SQLite + +SQLite is a small, fast, self-contained SQL database written in the C programming language. +Because browsers cannot run an applications C code directly, [WebAssembly](https://webassembly.org/) (WASM) is used to compile the SQLite C code into WASM byte code. WASM code can be shipped to browser apps and generally runs much faster compared to JavaScript, but still about [10% slower then native](https://www.usenix.org/conference/atc19/presentation/jangda). +The compiled byte code has a size of [about 938.9 kB](https://sqlite.org/download.html) which must be downloaded and parsed by the users on the first page load. + +WASM cannot directly access any persistend storage API in the browser. Instead it requires data to flow from WASM to the main-thread and then can be put into one of the browser APIs. For reads the same goes the other way round. -## What is wasm sqlite -https://www.fermyon.com/blog/webassembly-wasi-and-the-component-model ### Things this does not talk about From be4128204fe6321bb9ce8b4b417804d95c8face4 Mon Sep 17 00:00:00 2001 From: pubkey <8926560+pubkey@users.noreply.github.com> Date: Mon, 17 Jun 2024 12:51:33 +0200 Subject: [PATCH 06/15] CHORe --- ...rage-indexeddb-cookies-opfs-sqlite-wasm.md | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md index 3584919339b..0be96d1417c 100644 --- a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md +++ b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md @@ -20,7 +20,7 @@ In this article, we will dive into the various technologies available for storin ## What is Localstorage -LocalStorage provides a simple way to store key-value pairs in a web browser. It's suitable for storing small amounts of data that need to persist across sessions but is [limited by a 5MB storage cap](./localstorage.md#understanding-the-limitations-of-local-storage) and the inability to store complex data types beyond strings. +LocalStorage provides a simple API to store key-value pairs in a web browser. It's suitable for storing small amounts of data that need to persist across sessions but is [limited by a 5MB storage cap](./localstorage.md#understanding-the-limitations-of-local-storage) and the inability to store complex data types beyond strings. ## What are Cookies @@ -47,11 +47,23 @@ The compiled byte code has a size of [about 938.9 kB](https://sqlite.org/downlo WASM cannot directly access any persistend storage API in the browser. Instead it requires data to flow from WASM to the main-thread and then can be put into one of the browser APIs. For reads the same goes the other way round. +## Test Setup -### Things this does not talk about -WebSQL -session storage. -Web Storage API +As mentioned above, we will focus on the performance differences of the various technologies. +But we not only want to store a few documents, instead lets store **many** of them and run **heavy and complex queries** to find out about the limits of what can be done in a browser. + +TODO add github repo url with performance tests. https://github.com/pubkey/localstorage-indexeddb-cookies-opfs-sqlite-wasm + +### Running many small operations + +One aspect of performance is the latency. The time to run a small database operation, either read or write. +Depending on your use case, it might be relevant that many small operations run fast, like when you have a browser game and want to store the game's state. + +### Running single big operations + +### Initial page load +How fast does the first query load when there are many documents +stored already. @@ -97,22 +109,6 @@ The big difference between a database and storing data in a plain file, is that - OPFS with the fast `createSyncAccessHandle` method can **only** [be used in a WebWorker](../rx-storage-opfs.md#opfs-limitations). 2 -## Performance Comparison - -Now that we know about the basics of the various storage solutions, lets compare their performance. As mentioned above, we not only want to store a few documents, instead lets store **many** of them and run **heavy and complex queries**. - -TODO add github repo url with performance tests. - -### Running many small operations - -One aspect of performance is the latency. The time to run a small database operation, either read or write. -Depending on your use case, it might be relevant that many small operations run fast, like when you have a browser game and want to store the game's state. - -### Running single big operations - -### Initial page load -How fast does the first query load when there are many documents -stored already. @@ -129,6 +125,15 @@ stored already. - is OPFS faster then indexeddb? +### Things this article does not talk about + +WebSQL +session storage. +Web Storage API +Cross tab support +Observability + + ## Read further @@ -136,6 +141,9 @@ stored already. TODO fix links - Check out the [hackernews discussion of this article](https://news.ycombinator.com/item?id=39745993) - Shared/Like my [announcement tweet](https://twitter.com/rxdbjs/status/1769507055298064818) +- Reproduce the benchmarks at the [github repo](https://github.com/pubkey/localstorage-indexeddb-cookies-opfs-sqlite-wasm) - Learn how to use RxDB with the [RxDB Quickstart](../quickstart.md) - Check out the [RxDB github repo](https://github.com/pubkey/rxdb) and leave a star ⭐ + + From 10cbc9f4c160c752529323f3de468b4ef901d932 Mon Sep 17 00:00:00 2001 From: pubkey <8926560+pubkey@users.noreply.github.com> Date: Wed, 26 Jun 2024 12:11:04 +0200 Subject: [PATCH 07/15] CHORE text --- ...alstorage-indexeddb-cookies-opfs-sqlite-wasm.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md index 0be96d1417c..e2472c74368 100644 --- a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md +++ b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md @@ -10,7 +10,7 @@ However, a common belief persists: Is this really the case? Over recent years, JavaScript has evolved significantly. New storage APIs like the Origin Private File System (OPFS) and new features like the BroadcastChannel have pushed the boundaries of what JavaScript can achieve in terms of performance. -In this article, we will dive into the various technologies available for storing and querying data in a browser. We'll explore traditional methods like Cookies, LocalStorage and IndexedDB, and newer solutions such as OPFS and Wasm-SQLite. Through performance tests we aim to uncover how fast we can write and read **a huge amount of data** in a web application with the various methods. And because you are reading this in the [RxDB](/) docs, we will utilize multiple RxDB plugins that contain innovative hacks to reach the performance limits of a browser in terms of database operations. +In this article, we will dive into the various technologies available for storing and querying data in a browser. We'll explore traditional methods like Cookies, LocalStorage and IndexedDB, and newer solutions such as OPFS and Wasm-SQLite. Through performance tests we aim to uncover how fast we can write and read **a huge amount of data** in a web application with the various methods. And because you are reading this in the [RxDB](/) docs, we will utilize multiple RxDB plugins that contain innovative concepts to reach the performance limits of a browser in terms of database operations.
@@ -24,8 +24,9 @@ LocalStorage provides a simple API to store key-value pairs in a web browser. It ## What are Cookies -Cookies store small pieces of data that are sent with every HTTP request. They are mainly used for session management, personalization, and tracking, but are limited to about `4 KB` of data in [RFC-6265](https://datatracker.ietf.org/doc/html/rfc6265#section-6.1). -This means we cannot store much data in a cookie but it is still interesting how good cookie access performance compared to the other methods. Especially because cookies are such an important base feature of the web, many performance optimizations have been done and even these days there is still progress being made like the [Shared Memory Versioning](https://blog.chromium.org/2024/06/introducing-shared-memory-versioning-to.html) by chromium. +Cookies store small pieces of data that are sent with every HTTP request. They are mainly used for session management, personalization, and tracking, but are limited to about `4 KB` of data in [RFC-6265](https://datatracker.ietf.org/doc/html/rfc6265#section-6.1). You can test your browsers cookie limits [here](http://www.ruslog.com/tools/cookies.html). + +This limitations means we cannot store much data in a cookie but it is still interesting how good cookie access performance compared to the other methods. Especially because cookies are such an important base feature of the web, many performance optimizations have been done and even these days there is still progress being made like the [Shared Memory Versioning](https://blog.chromium.org/2024/06/introducing-shared-memory-versioning-to.html) by chromium. ## What is IndexedDB @@ -67,7 +68,6 @@ stored already. - ## Feature comparison ### Multitab support @@ -147,3 +147,9 @@ TODO fix links - Check out the [RxDB github repo](https://github.com/pubkey/rxdb) and leave a star ⭐ + + + +## TODOs + +- Is indexeddb faster with storage buckets? https://developer.chrome.com/blog/maximum-idb-performance-with-storage-buckets?hl=en From ecb5436de7cb4ff23715700e38ca701d55e9aef8 Mon Sep 17 00:00:00 2001 From: pubkey <8926560+pubkey@users.noreply.github.com> Date: Tue, 8 Oct 2024 12:31:53 +0200 Subject: [PATCH 08/15] CHORE --- ...rage-indexeddb-cookies-opfs-sqlite-wasm.md | 79 +++++++++++++++---- docs-src/sidebars.js | 1 + 2 files changed, 66 insertions(+), 14 deletions(-) diff --git a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md index e2472c74368..ea40a2cd5d9 100644 --- a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md +++ b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md @@ -1,45 +1,91 @@ -# Localstorage vs. IndexedDB vs. Cookies vs. OPFS vs. Wasm-SQLite +--- +title: Localstorage vs. IndexedDB vs. Cookies vs. OPFS vs. Wasm-SQLite +slug: localstorage-indexeddb-cookies-opfs-sqlite-wasm.html +--- + + +# Localstorage vs. IndexedDB vs. Cookies vs. OPFS vs. Wasm-SQLite -Welcome to 2024, a time when building powerful, responsive web applications has never been more exciting. As developers, we constantly seek ways to make our apps faster and more efficient, and one crucial aspect of this is data storage. To ensure our apps not only perform well but also work seamlessly [offline](../offline-first.md), we aim to store data on the client device, minimizing the need to interact with backend servers. +So you build that web application and you want to **store data inside of your users browser**. Maybe you just need to store some small flags or you even need a fully fledged database to store massive amounts of data for your [local first app](../offline-first.md). -However, a common belief persists: +In the beginnings of the Web, we only had cookies to store some small key value assignements. But over the years JavaScript has evolved significantly and better storage APIs have been added to the browsers which pave the way for bigger and more complex data operations. Namely we have [Localstorage](./localstorage.md), WebSQL, IndexedDB, and the Origin Private File System API [(OPFS)](../rx-storage-opfs.md). -> Browser are slow and weren't designed to run extensive database operations. -Is this really the case? Over recent years, JavaScript has evolved significantly. New storage APIs like the Origin Private File System (OPFS) and new features like the BroadcastChannel have pushed the boundaries of what JavaScript can achieve in terms of performance. +In this article, we will dive into the various technologies available for storing and querying data in a browser. We'll explore traditional methods like **Cookies**, **LocalStorage** and **IndexedDB**, and newer solutions such as **OPFS** and **SQLite via WebAssembly**. Through performance tests we aim to uncover how fast we can write and read data in a web application with the various methods. -In this article, we will dive into the various technologies available for storing and querying data in a browser. We'll explore traditional methods like Cookies, LocalStorage and IndexedDB, and newer solutions such as OPFS and Wasm-SQLite. Through performance tests we aim to uncover how fast we can write and read **a huge amount of data** in a web application with the various methods. And because you are reading this in the [RxDB](/) docs, we will utilize multiple RxDB plugins that contain innovative concepts to reach the performance limits of a browser in terms of database operations. +:::note +You are reading this in the [RxDB](/) docs. RxDB is a JavaScript database that has different storage adapters which can utilize the different storage APIs. +Over the last 8 years I spend most of my time working with these APIs, doing performance tests and building [hacks](../slow-indexeddb.md) to reach the limits of browser database operation speed.
JavaScript Database
+::: + + +## The available storage APIs in a modern browser + +Over the years, the type of web applications we build has changed significantly. In the early years of the web we served static html files. Then we served dynamically rendered html and later we build single page applications that run most logic on the client. And for the comming years you might want to build so called [local first apps](../offline-first.md) that handle big and complex data operations solely on the client and even work when offline which gives you the opportunity to build zero-latency user interactions. + +For these increments of use cases, the browser vendors kept in pace providing more and better APIs. Let me give you a brief explanation of them: + +### What are Cookies + +Cookies were first introduced by [netscape in 1994](https://www.baekdal.com/thoughts/the-original-cookie-specification-from-1997-was-gdpr-compliant/). +Cookies store small pieces of key-value data. Cookies are mainly used for session management, personalization, and tracking, but are limited to about `4 KB` of data in [RFC-6265](https://datatracker.ietf.org/doc/html/rfc6265#section-6.1). Because the stored cookies are send to the server with every HTTP request, this limitation is reasonable. You can test your browsers cookie limits [here](http://www.ruslog.com/tools/cookies.html). -## What is Localstorage +This size limitation means we cannot store much data in a cookie but it is still interesting how good cookie access performance compared to the other methods. Especially because cookies are such an important base feature of the web, many performance optimizations have been done and even these days there is still progress being made like the [Shared Memory Versioning](https://blog.chromium.org/2024/06/introducing-shared-memory-versioning-to.html) by chromium or the asynchronous [CookieStore API](https://developer.mozilla.org/en-US/docs/Web/API/Cookie_Store_API). -LocalStorage provides a simple API to store key-value pairs in a web browser. It's suitable for storing small amounts of data that need to persist across sessions but is [limited by a 5MB storage cap](./localstorage.md#understanding-the-limitations-of-local-storage) and the inability to store complex data types beyond strings. -## What are Cookies +### What is Localstorage -Cookies store small pieces of data that are sent with every HTTP request. They are mainly used for session management, personalization, and tracking, but are limited to about `4 KB` of data in [RFC-6265](https://datatracker.ietf.org/doc/html/rfc6265#section-6.1). You can test your browsers cookie limits [here](http://www.ruslog.com/tools/cookies.html). +The [LocalStorage API](./localstorage.md) was first proposed as part of the [WebStorage specification in 2009](https://www.w3.org/TR/2009/WD-webstorage-20090423/#the-localstorage-attribute). +LocalStorage provides a simple API to store key-value pairs inside of a web browser. It has the methods `setItem`, `getItem`, `removeItem` and `clear` which is all you need from a key-value store. Localstorage is only suitable for storing small amounts of data that need to persist across sessions and it is [limited by a 5MB storage cap](./localstorage.md#understanding-the-limitations-of-local-storage). Storing complex data is only possible by transforming it into a string for example with `JSON.stringify()`. +The API is not asynchronous which means if fully blocks your JavaScript process while doing stuff. Therefore running heavy operations on it might block your UI from rendering. -This limitations means we cannot store much data in a cookie but it is still interesting how good cookie access performance compared to the other methods. Especially because cookies are such an important base feature of the web, many performance optimizations have been done and even these days there is still progress being made like the [Shared Memory Versioning](https://blog.chromium.org/2024/06/introducing-shared-memory-versioning-to.html) by chromium. +> There is also the SesssionStorage API. The key difference is that localStorage data persists indefinitely until explicitly cleared, while sessionStorage data is cleared when the browser tab or window is closed -## What is IndexedDB + + + +----------------------------------------------------------------------------- +----------------------------------------------------------------------------- +----------------------------------------------------------------------------- +----------------------------------------------------------------------------- + +### What is IndexedDB IndexedDB is a low-level API for storing large amounts of structured (JSON) data. While the API is a bit hard to use, IndexedDB can utilize indexes and asynchronous operations. It lacks support for complex queries and only allows to iterate over the indexes which makes it more like a base layer for other libraries. The performance of basic IndexedDB operations can be problematic but there exist [several hacks](../slow-indexeddb.md) to improve writes and query speed. -## What is OPFS +### What is OPFS The [Origin Private File System](../rx-storage-opfs.md) (OPFS) is a relatively new API that allows web applications to store large files directly in the browser. It is designed for data-intensive applications that want to write and read binary data. OPFS can be used in two modes: Either asynchronous on the [main thread](../rx-storage-opfs.md#using-opfs-in-the-main-thread-instead-of-a-worker) or in a WebWorker with the faster, aynchronous access. Because only binary data can be processed, OPFS is made to be as a base filesystem for database libraries. You will unlikely directly want to use the OPFS in your applications code. -## What is WASM-SQLite +### What is WASM-SQLite SQLite is a small, fast, self-contained SQL database written in the C programming language. Because browsers cannot run an applications C code directly, [WebAssembly](https://webassembly.org/) (WASM) is used to compile the SQLite C code into WASM byte code. WASM code can be shipped to browser apps and generally runs much faster compared to JavaScript, but still about [10% slower then native](https://www.usenix.org/conference/atc19/presentation/jangda). @@ -48,6 +94,11 @@ The compiled byte code has a size of [about 938.9 kB](https://sqlite.org/downlo WASM cannot directly access any persistend storage API in the browser. Instead it requires data to flow from WASM to the main-thread and then can be put into one of the browser APIs. For reads the same goes the other way round. +### What is WebSQL + + + + ## Test Setup As mentioned above, we will focus on the performance differences of the various technologies. diff --git a/docs-src/sidebars.js b/docs-src/sidebars.js index a6b9000616a..6cdafd2e86c 100644 --- a/docs-src/sidebars.js +++ b/docs-src/sidebars.js @@ -153,6 +153,7 @@ const sidebars = { 'articles/ionic-database', 'articles/json-database', 'articles/websockets-sse-polling-webrtc-webtransport', + 'articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm', 'articles/localstorage', 'articles/mobile-database', 'articles/progressive-web-app-database', From 37e9aa80d8f81db13d319ef1ee02fbe6054407a9 Mon Sep 17 00:00:00 2001 From: pubkey <8926560+pubkey@users.noreply.github.com> Date: Wed, 9 Oct 2024 10:11:47 +0200 Subject: [PATCH 09/15] CHORE --- docs-src/docs/articles/ideas.md | 2 +- ...rage-indexeddb-cookies-opfs-sqlite-wasm.md | 40 +++++++++++-------- src/rx-collection-helper.ts | 1 - 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/docs-src/docs/articles/ideas.md b/docs-src/docs/articles/ideas.md index 0483c6f430e..adaacf8435b 100644 --- a/docs-src/docs/articles/ideas.md +++ b/docs-src/docs/articles/ideas.md @@ -5,7 +5,7 @@ - Finding the optimal way to shorten vector embeddings - Performance and quality of vector comparison functions (euclideanDistance etc) - performance and quality of vector indexing methods - +- What is new in IndexedDB 3.0 ## Seo keywords: diff --git a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md index ea40a2cd5d9..9b6f3ead673 100644 --- a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md +++ b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md @@ -64,39 +64,47 @@ The [LocalStorage API](./localstorage.md) was first proposed as part of the [Web LocalStorage provides a simple API to store key-value pairs inside of a web browser. It has the methods `setItem`, `getItem`, `removeItem` and `clear` which is all you need from a key-value store. Localstorage is only suitable for storing small amounts of data that need to persist across sessions and it is [limited by a 5MB storage cap](./localstorage.md#understanding-the-limitations-of-local-storage). Storing complex data is only possible by transforming it into a string for example with `JSON.stringify()`. The API is not asynchronous which means if fully blocks your JavaScript process while doing stuff. Therefore running heavy operations on it might block your UI from rendering. -> There is also the SesssionStorage API. The key difference is that localStorage data persists indefinitely until explicitly cleared, while sessionStorage data is cleared when the browser tab or window is closed +> There is also the **SessionStorage** API. The key difference is that localStorage data persists indefinitely until explicitly cleared, while sessionStorage data is cleared when the browser tab or window is closed. +### What is IndexedDB +IndexedDB was first introduced as "Indexed Database API" [in 2015](https://www.w3.org/TR/IndexedDB/#sotd). ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ +IndexedDB is a low-level API for storing large amounts of structured JSON data. While the API is a bit hard to use, IndexedDB can utilize indexes and asynchronous operations. It lacks support for complex queries and only allows to iterate over the indexes which makes it more like a base layer for other libraries then a fully fledged database. -### What is IndexedDB +In 2018, IndexedDB version 2.0 [was introduced](https://hacks.mozilla.org/2016/10/whats-new-in-indexeddb-2-0/). This added some major improvements. Most noticeable the `getAll()` method which improves performance dramatically when fetching bulks of JSON documents. -IndexedDB is a low-level API for storing large amounts of structured (JSON) data. While the API is a bit hard to use, IndexedDB can utilize indexes and asynchronous operations. It lacks support for complex queries and only allows to iterate over the indexes which makes it more like a base layer for other libraries. The performance of basic IndexedDB operations can be problematic but there exist [several hacks](../slow-indexeddb.md) to improve writes and query speed. +IndexedDB [version 3.0](https://w3c.github.io/IndexedDB/) is in the workings which contains many improvements. Most important the addition of `Promise` based calls that makes modern JS features like `async/await` more useful. ### What is OPFS -The [Origin Private File System](../rx-storage-opfs.md) (OPFS) is a relatively new API that allows web applications to store large files directly in the browser. It is designed for data-intensive applications that want to write and read binary data. -OPFS can be used in two modes: Either asynchronous on the [main thread](../rx-storage-opfs.md#using-opfs-in-the-main-thread-instead-of-a-worker) or in a WebWorker with the faster, aynchronous access. -Because only binary data can be processed, OPFS is made to be as a base filesystem for database libraries. You will unlikely directly want to use the OPFS in your applications code. +The [Origin Private File System](../rx-storage-opfs.md) (OPFS) is a [relatively new](https://caniuse.com/mdn-api_filesystemfilehandle_createsyncaccesshandle) API that allows web applications to store large files directly in the browser. It is designed for data-intensive applications that want to write and read **binary data** in a simulated file system. -### What is WASM-SQLite +OPFS can be used in two modes: +- Either asynchronous on the [main thread](../rx-storage-opfs.md#using-opfs-in-the-main-thread-instead-of-a-worker) +- Or in a WebWorker with the faster, aynchronous access with the `createSyncAccessHandle()` method. -SQLite is a small, fast, self-contained SQL database written in the C programming language. -Because browsers cannot run an applications C code directly, [WebAssembly](https://webassembly.org/) (WASM) is used to compile the SQLite C code into WASM byte code. WASM code can be shipped to browser apps and generally runs much faster compared to JavaScript, but still about [10% slower then native](https://www.usenix.org/conference/atc19/presentation/jangda). -The compiled byte code has a size of [about 938.9 kB](https://sqlite.org/download.html) which must be downloaded and parsed by the users on the first page load. +Because only binary data can be processed, OPFS is made to be a base filesystem for library developers. You will unlikely directly want to use the OPFS in your code when you build a "normal" application because it is too complex. That would only make sense for storing plain files like images, not to store and query JSON data efficiently. -WASM cannot directly access any persistend storage API in the browser. Instead it requires data to flow from WASM to the main-thread and then can be put into one of the browser APIs. For reads the same goes the other way round. +### What is WASM SQLite +[WebAssembly](https://webassembly.org/) (Wasm) is a binary format that allows high-performance code execution on the web. +Wasm was added to major browsers over the course of 2017 wich opened a wide range of opportunities on what to run inside of a browser. You can compile native libraries to WebAssembly and just run them on the client with just a few adjustments. WASM code can be shipped to browser apps and generally runs much faster compared to JavaScript, but still about [10% slower then native](https://www.usenix.org/conference/atc19/presentation/jangda). -### What is WebSQL +Many people started to use compiled SQLite as a database inside of the browser which is why it makes sense to also compare this setup to the native APIs. + +The compiled byte code of SQLite has a size of [about 938.9 kB](https://sqlite.org/download.html) which must be downloaded and parsed by the users on the first page load. WASM cannot directly access any persistend storage API in the browser. Instead it requires data to flow from WASM to the main-thread and then can be put into one of the browser APIs. For reads the same goes the other way round. +----------------------------------------------------------------------------- +----------------------------------------------------------------------------- +----------------------------------------------------------------------------- +----------------------------------------------------------------------------- + + +### What is WebSQL ## Test Setup diff --git a/src/rx-collection-helper.ts b/src/rx-collection-helper.ts index a52d0a3da5d..f43960a5df7 100644 --- a/src/rx-collection-helper.ts +++ b/src/rx-collection-helper.ts @@ -95,7 +95,6 @@ export async function removeCollectionStorages( ); const relevantCollectionMetaDocs = allCollectionMetaDocs .filter(metaDoc => metaDoc.data.name === collectionName); - let removeStorages: { collectionName: string; schema: RxJsonSchema; From 92905d567debe8e242232936218fbcf002f68f26 Mon Sep 17 00:00:00 2001 From: pubkey <8926560+pubkey@users.noreply.github.com> Date: Thu, 10 Oct 2024 10:58:02 +0200 Subject: [PATCH 10/15] CHORE --- ...rage-indexeddb-cookies-opfs-sqlite-wasm.md | 133 ++++++++++++------ 1 file changed, 93 insertions(+), 40 deletions(-) diff --git a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md index 9b6f3ead673..c04677e6ba9 100644 --- a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md +++ b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md @@ -34,7 +34,7 @@ In this article, we will dive into the various technologies available for storin :::note You are reading this in the [RxDB](/) docs. RxDB is a JavaScript database that has different storage adapters which can utilize the different storage APIs. -Over the last 8 years I spend most of my time working with these APIs, doing performance tests and building [hacks](../slow-indexeddb.md) to reach the limits of browser database operation speed. +**Since 2017** I spend most of my time working with these APIs, doing performance tests and building [hacks](../slow-indexeddb.md) to reach the limits of browser database operation speed.
@@ -86,7 +86,7 @@ OPFS can be used in two modes: - Either asynchronous on the [main thread](../rx-storage-opfs.md#using-opfs-in-the-main-thread-instead-of-a-worker) - Or in a WebWorker with the faster, aynchronous access with the `createSyncAccessHandle()` method. -Because only binary data can be processed, OPFS is made to be a base filesystem for library developers. You will unlikely directly want to use the OPFS in your code when you build a "normal" application because it is too complex. That would only make sense for storing plain files like images, not to store and query JSON data efficiently. +Because only binary data can be processed, OPFS is made to be a base filesystem for library developers. You will unlikely directly want to use the OPFS in your code when you build a "normal" application because it is too complex. That would only make sense for storing plain files like images, not to store and query JSON data efficiently. I have build a [OPFS based storage](../rx-storage-opfs.md) for RxDB with proper indexing and querying and it took me several months. ### What is WASM SQLite @@ -95,45 +95,43 @@ Wasm was added to major browsers over the course of 2017 wich opened a wide rang Many people started to use compiled SQLite as a database inside of the browser which is why it makes sense to also compare this setup to the native APIs. -The compiled byte code of SQLite has a size of [about 938.9 kB](https://sqlite.org/download.html) which must be downloaded and parsed by the users on the first page load. WASM cannot directly access any persistend storage API in the browser. Instead it requires data to flow from WASM to the main-thread and then can be put into one of the browser APIs. For reads the same goes the other way round. +The compiled byte code of SQLite has a size of [about 938.9 kB](https://sqlite.org/download.html) which must be downloaded and parsed by the users on the first page load. WASM cannot directly access any persistend storage API in the browser. Instead it requires data to flow from WASM to the main-thread and then can be put into one of the browser APIs. This is done with so called [VFS adapters](https://www.sqlite.org/vfs.html) that handle data access from SQLite to anything else. +### What was WebSQL ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ +WebSQL **has been** a web API [introduced in 2009](https://www.w3.org/TR/webdatabase/) that allowed browsers to use SQL databases for client-side storage, based on SQLite. The idea was to give developers a way to store and query data using SQL on the client side, similar to server-side databases. +WebSQL has been **removed from browsers** in the current years for multiple good reasons: +- WebSQL was not standardized and having an API based on a single specific implementation in form of the SQLite source code is hard to ever make it to a standard. +- WebSQL required browsers to use a [specific version](https://developer.chrome.com/blog/deprecating-web-sql#reasons_for_deprecating_web_sql) of SQLite (version 3.6.19) which means whenever there would be any update or bugfix to SQLite, it would not be possible to add that to WebSQL without possible breaking the web. +- Major browsers like firefox never supported WebSQL. -### What is WebSQL - - -## Test Setup +Therefore in the following we will **just ignore WebSQL** even if it would be possible to run tests on in by setting specific browser flags or using old versions of chromium. -As mentioned above, we will focus on the performance differences of the various technologies. -But we not only want to store a few documents, instead lets store **many** of them and run **heavy and complex queries** to find out about the limits of what can be done in a browser. +------------- -TODO add github repo url with performance tests. https://github.com/pubkey/localstorage-indexeddb-cookies-opfs-sqlite-wasm +## Feature Comparison -### Running many small operations +Now that you know the basic concepts of the APIs, lets compare some specific features that have shown to be important for people using RxDB and browser based storages in general. -One aspect of performance is the latency. The time to run a small database operation, either read or write. -Depending on your use case, it might be relevant that many small operations run fast, like when you have a browser game and want to store the game's state. +### Storing complex JSON documents -### Running single big operations +When you store data in a web application, most often you want to store complex JSON documents and not only "normal" values like the `integers` and `strings` you store in a server side database. -### Initial page load -How fast does the first query load when there are many documents -stored already. +- Only IndexedDB works with JSON objects natively. +- With SQLite WASM you can [store JSON](https://www.sqlite.org/json1.html) in a `text` column since version 3.38.0 (2022-02-22) and even run deep queries on it and use single attributes as indexes. +Every of the other APIs can only store strings or binary data. Of course you can transform any JSON object to a string with `JSON.stringify()` but not having the JSON support in the API can make things complex when running queries and running `JSON.stringify()` many times can cause performance problems. +### Multi-Tab Support -## Feature comparison +A big difference when building a Web App compared to [Electron](../electron-database.md) or [React-Native](../react-native-database.md), is that the user will open and close the app in **multiple browser tabs at the same time**. Therefore you have not only one JavaScript process running, but many of them can exist and might have to share state changes between each other to not show **outdated data** to the user. -### Multitab support +> If your users' muscle memory puts the left hand on the **F5** key while using your website, you did something wrong! -A big difference when building a Web App compared to Electron or React-Native, is that the user will open and close the app in multiple browser tabs at the same time. Therefore you have not only one JavaScript process running, but many of them can exist and might have to share state changes between each other to not show outdated data to the user. +Not all storage APIs support a way to automatically share write events between tabs. -Not all storage APIs support a way to automatically share write events between tabs. Only localstorage has the [storage-event](./localstorage.md#localstorage-vs-indexeddb) which can be used to observe changes. +Only localstorage has a way to automatically share write events between tabs by the API itself with the [storage-event](./localstorage.md#localstorage-vs-indexeddb) which can be used to observe changes. ```js // localStorage can observe changes with the storage event. @@ -141,33 +139,88 @@ Not all storage APIs support a way to automatically share write events between t addEventListener("storage", (event) => {}); ``` +There was the [experimental IndexedDB observers API](https://stackoverflow.com/a/33270440) for chrome, but the proposal repository has been archived. + To workaround this problem, there are two solutions: -The first option is to use the [BroadcastChannel API](https://github.com/pubkey/broadcast-channel) which can send messages across browser tabs. So whenever you do a write to the storage, you also send a notification to other tabs to inform them about these changes. +- The first option is to use the [BroadcastChannel API](https://github.com/pubkey/broadcast-channel) which can send messages across browser tabs. So whenever you do a write to the storage, you also send a notification to other tabs to inform them about these changes. This is the most common workaround which is also used by RxDB. Notice that there is also the [WebLocks API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API) which can be used to have mutexes accross browser tabs. +- The other solution is to use the [SharedWorker](https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker) and do all writes inside of the worker. All browser tabs can then subscribe to messages from that **single** SharedWorker and know about changes. + +### Indexing Support + +The big difference between a database and storing data in a plain file, is that a database is writing data in a format that allows running operations over indexes to facilitate fast performant queries. From our list of technologies only **IndexedDB** and **WASM SQLite** support for indexing out of the box. In theory you can build indexes on top of any storage like localstorage or OPFS but you likely should not want to do that by yourself. + +In IndexedDB for example, we can fetch a bulk of documents by a given index range: + +```ts +// find all producs with a price between 10 and 50 +const keyRange = IDBKeyRange.bound(10, 50); +const transaction = db.transaction('products', 'readonly'); +const objectStore = transaction.objectStore('products'); +const index = objectStore.index('priceIndex'); +const request = index.getAll(keyRange); +const result = await new Promise((res, rej) => { + request.onsuccess = (event) => res(event.target.result); + request.onerror = (event) => rej(event); +}); +``` -The other solution is to use the [SharedWorker](https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker) and do all writes inside of the worker. All browser tabs can then subscribe to messages from that SharedWorker and know about changes. +Notice that IndexedDB has the limitation of [not having indexes on boolean values](https://github.com/w3c/IndexedDB/issues/76). You can only index strings and numbers. To workaround that you have to transform boolean to numbers and backwards when storing the data. -### Indexing -The big difference between a database and storing data in a plain file, is that a database is writing data in a format that allows running operations over indexes to facilitate fast queries. +### WebWorker Support -#### iterable indexes -- IndexedDB does not support boolean indexes +When running heavy data operations, you might want to move the processing away from the JavaScript main thread. This ensures that our app keeps being responsive and fast while the processing can run in parallel in the background. In a browser you can either use the [WebWorker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API), [SharedWorker](https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker) or the [ServiceWorker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) API to do that. In RxDB you can use the [WebWorker](../rx-storage-worker.md) or [SharedWorker](../rx-storage-shared-worker.md) plugins to move your storage inside of a worker. -#### secondary indexes -- Only IndexedDB and SQLite WASM has secondary indexes +The most common API for that use case is spawning a **WebWorker** and doing most work on that second JavaScript process. The worker is spawned from a seperate JavaScript file (or base64 string) and communicates with the main thread by sending data with `postMessage()`. + +Unfortunately **Localstorage** and **Cookies** [cannot be used in WebWorker or SharedWorker](https://stackoverflow.com/questions/6179159/accessing-localstorage-from-a-webworker) because of the design and security constraints. WebWorkers run in a separate global context from the main browser thread and therefore cannot do stuff that might impact the main thread. They have no direct access to certain web APIs, like the DOM, localStorage, or cookies. + +Everything else can be used from inside a WebWorker. +The fast version of OPFS with the `createSyncAccessHandle` method can **only** [be used in a WebWorker](../rx-storage-opfs.md#opfs-limitations), and **not on the main thread**. This is because all the operations of the returned `AccessHandle` are **not async** and therefore block the JavaScript process, so you do want to do that on the main thread and block everything. + +----------------------------------------------------------------------------- +----------------------------------------------------------------------------- +----------------------------------------------------------------------------- +----------------------------------------------------------------------------- -### Storing complex JSON documents -- IndexedDB can store JSON natively -- SQLite can [store JSON](https://www.sqlite.org/json1.html) from version 3.38.0 (2022-02-22). -### Worker Support -- Localstorage and Cookies [cannot be used in WebWorker or SharedWorker](https://stackoverflow.com/questions/6179159/accessing-localstorage-from-a-webworker). -- OPFS with the fast `createSyncAccessHandle` method can **only** [be used in a WebWorker](../rx-storage-opfs.md#opfs-limitations). - 2 + + +## Performance comparison + +Lets do some performance comparisons. Notice that we only run simple tests and for your specific use case in your application the results might differ. Also we only compare performance in google chrome (version 128.0.6613.137). Firefox and Safari have similar **but not equal** performance patterns. You can run the test by yourself on your own machine from this [github repository](https://github.com/pubkey/localstorage-indexeddb-cookies-opfs-sqlite-wasm). + +### Latency of small writes + +### Latency of small reads + +### Big Bulk Writes + +### Big Bulk Reads + + + +## Test Setup + +As mentioned above, we will focus on the performance differences of the various technologies. +But we not only want to store a few documents, instead lets store **many** of them and run **heavy and complex queries** to find out about the limits of what can be done in a browser. + +TODO add github repo url with performance tests. https://github.com/pubkey/localstorage-indexeddb-cookies-opfs-sqlite-wasm + +### Running many small operations + +One aspect of performance is the latency. The time to run a small database operation, either read or write. +Depending on your use case, it might be relevant that many small operations run fast, like when you have a browser game and want to store the game's state. + +### Running single big operations + +### Initial page load +How fast does the first query load when there are many documents +stored already. From 39e8e9ff37e6cd2104387bffdf7908c62f0fd7f8 Mon Sep 17 00:00:00 2001 From: pubkey <8926560+pubkey@users.noreply.github.com> Date: Mon, 14 Oct 2024 12:56:05 +0200 Subject: [PATCH 11/15] CHORE --- ...rage-indexeddb-cookies-opfs-sqlite-wasm.md | 109 ++++++++++++++---- 1 file changed, 89 insertions(+), 20 deletions(-) diff --git a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md index c04677e6ba9..7d6e1877ea3 100644 --- a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md +++ b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md @@ -44,7 +44,7 @@ You are reading this in the [RxDB](/) docs. RxDB is a JavaScript database that h ::: -## The available storage APIs in a modern browser +## The available Storage APIs in a modern Browser Over the years, the type of web applications we build has changed significantly. In the early years of the web we served static html files. Then we served dynamically rendered html and later we build single page applications that run most logic on the client. And for the comming years you might want to build so called [local first apps](../offline-first.md) that handle big and complex data operations solely on the client and even work when offline which gives you the opportunity to build zero-latency user interactions. @@ -114,7 +114,7 @@ Therefore in the following we will **just ignore WebSQL** even if it would be po Now that you know the basic concepts of the APIs, lets compare some specific features that have shown to be important for people using RxDB and browser based storages in general. -### Storing complex JSON documents +### Storing complex JSON Documents When you store data in a web application, most often you want to store complex JSON documents and not only "normal" values like the `integers` and `strings` you store in a server side database. @@ -178,31 +178,110 @@ Unfortunately **Localstorage** and **Cookies** [cannot be used in WebWorker or S Everything else can be used from inside a WebWorker. The fast version of OPFS with the `createSyncAccessHandle` method can **only** [be used in a WebWorker](../rx-storage-opfs.md#opfs-limitations), and **not on the main thread**. This is because all the operations of the returned `AccessHandle` are **not async** and therefore block the JavaScript process, so you do want to do that on the main thread and block everything. ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ +## Performance Comparison + +Lets do some performance comparisons. Notice that we only run simple tests and for your specific use case in your application the results might differ. Also we only compare performance in google chrome (version 128.0.6613.137). Firefox and Safari have similar **but not equal** performance patterns. You can run the test by yourself on your own machine from this [github repository](https://github.com/pubkey/localstorage-indexeddb-cookies-opfs-sqlite-wasm). Notice that for all tests we throttle the network to behave like the average german internet speed. (download: 135,900 kbit/s, upload: 28,400 kbit/s, latency: 125ms). Also all tests store an "average" JSON object that might be required to be stringified depending on the storage. + +### Initialization Time + +Before you can store any data, many APIs require a setup process like creating databases, spawing WebAssembly processes or downloading additional stuff. To ensure your app starts fast, the initialization time is important. + +LocalStorage and Cookies do not have any setup process and can be directly used. IndexedDB requires to open a database and a store inside of it. WASM SQLite needs to download a WASM file and process it. OPFS needs to download and start a worker file. + +Here are the time measurements from how long it takes until the first bit of data can be stored: + +| Technology | Time in Milliseconds | +| ----------------------- | -------------------- | +| IndexedDB | 46 | +| OPFS Main Thread | 23 | +| OPFS WebWorker | 26.8 | +| WASM SQLite (memory) | 504 | +| WASM SQLite (IndexedDB) | 535 | +Here we can notice a few things: +- Opening a new IndexedDB database with a single store takes suprisingly long +- The latency overhead of sending data from the main thread to a WebWorker OPFS is about 4 milliseconds. Here we only send minimal data to init the OPFS file handler. It will be interesting if that latency increases when more data is processed. +- Downloading and parsing WASM SQLite and creating a single table takes about half a second. Using also the IndexedDB VFS to store data persistently adds additional 31 milliseconds. Reloading the page with enabled caching and already prepared tables is a bit faster with 420 milliseconds (memory). +### Latency of small Writes +Next lets test the latency of small writes. This is important when you do many small data changes that happen independent from each other. Like when you stream data from a websocket or persist pseudo randomly happening events like mouse movements. +| Technology | Time in Milliseconds | +| ----------------------- | -------------------- | +| Cookies | 0.058 | +| Localstorage | 0.017 | +| IndexedDB | 0.17 | +| OPFS Main Thread | 1.46 | +| OPFS WebWorker | 1.54 | +| WASM SQLite (memory) | 0.17 | +| WASM SQLite (IndexedDB) | 3.17 | -## Performance comparison +Here we can notice a few things: -Lets do some performance comparisons. Notice that we only run simple tests and for your specific use case in your application the results might differ. Also we only compare performance in google chrome (version 128.0.6613.137). Firefox and Safari have similar **but not equal** performance patterns. You can run the test by yourself on your own machine from this [github repository](https://github.com/pubkey/localstorage-indexeddb-cookies-opfs-sqlite-wasm). +- Localstorage has the lowest write latency with only 0.017 milliseconds per write. +- IndexedDB writes are about 10 times slower compared to LocalStorage. +- Sending the data to the WASM SQLite process and letting it persist via IndexedDB is slow with over 3 milliseconds per write. -### Latency of small writes +The OPFS operations take about 1.5 seconds to write the JSON data into one document per file. We can see the sending the data to a webworker first ist a bit slower which comes from the overhead of serializing and deserialing the data on both sides. +If we would not create on OPFS file per document but instead append everything to a single file, the performance pattern changes significantly. Then the faster file handle from the `createSyncAccessHandle()` only takes about 1 millisecond per write. But this would require to somehow remember at which position the each document is stored. Therefore in our tests we will continue using one file per document. -### Latency of small reads +### Latency of small Reads + +Now that we have stored some documents, lets measure how long it takes to read single documents by their `id`. + + +| Technology | Time in Milliseconds | +| ----------------------- | -------------------- | +| Cookies | 0.132 | +| Localstorage | 0.0052 | +| IndexedDB | 0.1 | +| OPFS Main Thread | 1.28 | +| OPFS WebWorker | 1.41 | +| WASM SQLite (memory) | 0.45 | +| WASM SQLite (IndexedDB) | 2.93 | + + +Here we can notice a few things: + +- Localstorage reads are **really really fast** with only 0.0052 milliseconds per read. +- The other technologies perform reads in a similar speed to their write latency. ### Big Bulk Writes +As next step, lets do some big bulk operations with 200 documents at once. + +| Technology | Time in Milliseconds | +| ----------------------- | -------------------- | +| Cookies | 20.6 | +| Localstorage | 5.79 | +| IndexedDB | 13.41 | +| OPFS Main Thread | 280 | +| OPFS WebWorker | 104 | +| WASM SQLite (memory) | 19.1 | +| WASM SQLite (IndexedDB) | 37.12 | + + +Here we can notice a few things: + + +- Sending the data to a WebWorker and running it via the faster OPFS API is about twice as fast. +- WASM SQLite performs better on bulk operations compared to its single write latency. This is because sending the data to WASM and backwards is faster if it is done all at once instead of once per document. + + ### Big Bulk Reads +- Parsing the cookies string is slow because there is no way to get a single cookie value in JavaScript. On each read the whole string must be parsed and checked. There might be faster methods to do this on bulk operations. + +----------------------------------------------------------------------------- +----------------------------------------------------------------------------- +----------------------------------------------------------------------------- +----------------------------------------------------------------------------- + ## Test Setup @@ -222,9 +301,6 @@ Depending on your use case, it might be relevant that many small operations run How fast does the first query load when there are many documents stored already. - - - ## Lets reach the limits of client side storage performance with RxDB - indexeddb optimizations - compression with keycompression @@ -245,9 +321,6 @@ Web Storage API Cross tab support Observability - - - ## Read further TODO fix links @@ -258,10 +331,6 @@ TODO fix links - Learn how to use RxDB with the [RxDB Quickstart](../quickstart.md) - Check out the [RxDB github repo](https://github.com/pubkey/rxdb) and leave a star ⭐ - - - - ## TODOs - Is indexeddb faster with storage buckets? https://developer.chrome.com/blog/maximum-idb-performance-with-storage-buckets?hl=en From 4980c7deadb4979e8406e231d7986b1a971f3bbc Mon Sep 17 00:00:00 2001 From: pubkey <8926560+pubkey@users.noreply.github.com> Date: Mon, 14 Oct 2024 13:48:44 +0200 Subject: [PATCH 12/15] CHORE --- ...rage-indexeddb-cookies-opfs-sqlite-wasm.md | 92 ++++++++++--------- docs-src/static/files/icons/sqlite.svg | 67 ++++++++++++++ 2 files changed, 115 insertions(+), 44 deletions(-) create mode 100644 docs-src/static/files/icons/sqlite.svg diff --git a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md index 7d6e1877ea3..d669ac5ce2c 100644 --- a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md +++ b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md @@ -1,5 +1,5 @@ --- -title: Localstorage vs. IndexedDB vs. Cookies vs. OPFS vs. Wasm-SQLite +title: Localstorage vs. IndexedDB vs. Cookies vs. OPFS vs. WASM-SQLite slug: localstorage-indexeddb-cookies-opfs-sqlite-wasm.html --- @@ -22,7 +22,7 @@ GOALS: --> -# Localstorage vs. IndexedDB vs. Cookies vs. OPFS vs. Wasm-SQLite +# Localstorage vs. IndexedDB vs. Cookies vs. OPFS vs. WASM-SQLite So you build that web application and you want to **store data inside of your users browser**. Maybe you just need to store some small flags or you even need a fully fledged database to store massive amounts of data for your [local first app](../offline-first.md). @@ -90,6 +90,11 @@ Because only binary data can be processed, OPFS is made to be a base filesystem ### What is WASM SQLite +
+ WASM SQLite +
+ + [WebAssembly](https://webassembly.org/) (Wasm) is a binary format that allows high-performance code execution on the web. Wasm was added to major browsers over the course of 2017 wich opened a wide range of opportunities on what to run inside of a browser. You can compile native libraries to WebAssembly and just run them on the client with just a few adjustments. WASM code can be shipped to browser apps and generally runs much faster compared to JavaScript, but still about [10% slower then native](https://www.usenix.org/conference/atc19/presentation/jangda). @@ -180,13 +185,13 @@ The fast version of OPFS with the `createSyncAccessHandle` method can **only** [ ## Performance Comparison -Lets do some performance comparisons. Notice that we only run simple tests and for your specific use case in your application the results might differ. Also we only compare performance in google chrome (version 128.0.6613.137). Firefox and Safari have similar **but not equal** performance patterns. You can run the test by yourself on your own machine from this [github repository](https://github.com/pubkey/localstorage-indexeddb-cookies-opfs-sqlite-wasm). Notice that for all tests we throttle the network to behave like the average german internet speed. (download: 135,900 kbit/s, upload: 28,400 kbit/s, latency: 125ms). Also all tests store an "average" JSON object that might be required to be stringified depending on the storage. +Lets do some performance comparisons. Notice that we only run simple tests and for your specific use case in your application the results might differ. Also we only compare performance in google chrome (version 128.0.6613.137). Firefox and Safari have similar **but not equal** performance patterns. You can run the test by yourself on your own machine from this [github repository](https://github.com/pubkey/localstorage-indexeddb-cookies-opfs-sqlite-wasm). Notice that for all tests we throttle the network to behave like the average german internet speed. (download: 135,900 kbit/s, upload: 28,400 kbit/s, latency: 125ms). Also all tests store an "average" JSON object that might be required to be stringified depending on the storage. We also only test the performance of storing documents by id because some of the technologies (cookies, OPFS and localstorage) do not support indexed range operations so it makes no sense to compare the performance of these. ### Initialization Time Before you can store any data, many APIs require a setup process like creating databases, spawing WebAssembly processes or downloading additional stuff. To ensure your app starts fast, the initialization time is important. -LocalStorage and Cookies do not have any setup process and can be directly used. IndexedDB requires to open a database and a store inside of it. WASM SQLite needs to download a WASM file and process it. OPFS needs to download and start a worker file. +LocalStorage and Cookies do not have any setup process and can be directly used. IndexedDB requires to open a database and a store inside of it. WASM SQLite needs to download a WASM file and process it. OPFS needs to download and start a worker file and initialize the virtual file system directory. Here are the time measurements from how long it takes until the first bit of data can be stored: @@ -267,70 +272,69 @@ As next step, lets do some big bulk operations with 200 documents at once. Here we can notice a few things: - - Sending the data to a WebWorker and running it via the faster OPFS API is about twice as fast. - WASM SQLite performs better on bulk operations compared to its single write latency. This is because sending the data to WASM and backwards is faster if it is done all at once instead of once per document. ### Big Bulk Reads +Now lets read 100 documents in a bulk request. -- Parsing the cookies string is slow because there is no way to get a single cookie value in JavaScript. On each read the whole string must be parsed and checked. There might be faster methods to do this on bulk operations. +| Technology | Time in Milliseconds | +| ----------------------- | ------------------------------- | +| Cookies | 6.34 | +| Localstorage | 0.39 | +| IndexedDB | 4.99 | +| OPFS Main Thread | 54.79 | +| OPFS WebWorker | 25.61 | +| WASM SQLite (memory) | 3.59 | +| WASM SQLite (IndexedDB) | 5.84 (35ms without cache) | ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ +Here we can notice a few things: +- Reading many files in the OPFS webworker is about **twice as fast** compared to the slower main thread mode. +- WASM SQLite is suprisingly fast. Further inspection has shown that the WASM SQLite process keeps the documents in memory cached which improves the latency when we do reads directly after writes on the same data. When the browser tab is reloaded between the writes and the reads, finding the 100 documents takes about **35 milliseconds** instead. -## Test Setup -As mentioned above, we will focus on the performance differences of the various technologies. -But we not only want to store a few documents, instead lets store **many** of them and run **heavy and complex queries** to find out about the limits of what can be done in a browser. +## Performance Conclusions -TODO add github repo url with performance tests. https://github.com/pubkey/localstorage-indexeddb-cookies-opfs-sqlite-wasm +- LocalStorage is really fast but remember that is has some downsides: + - Blocks the main JavaScript process and therefore should not be used for big bulk operations. + - Only Key-Value assignements are possible, you cannot use it efficiently when you need to do index based range queries on your data. +- OPFS is way faster when used in the WebWorker with the `createSyncAccessHandle()` method compare to using it directly in the main thread. +- SQLite WASM can be fast but the you have to initally download the full binary and start it up which takes about half a second. This might not be relevant at all if your app is started up once and the used for a very long time. But for web-apps that are opened and closed in many browser tabs many times, this might be a problem. -### Running many small operations -One aspect of performance is the latency. The time to run a small database operation, either read or write. -Depending on your use case, it might be relevant that many small operations run fast, like when you have a browser game and want to store the game's state. +## Possible Improvements -### Running single big operations +There is a wide range of possible improvements and performance hacks to speed up the operations. +- For IndexedDB I have made a list of [performance hacks here](../slow-indexeddb.md). For example you can do sharding between multiple database and webworkers or use a custom index strategy. +- OPFS is slow in writing one file per document. But you do not have to do that and instead you can store everything at a single file like a normal database would do. This improves performance dramatically like it was done with the RxDB [OPFS RxStorage](../rx-storage-opfs.md). +- You can mix up the technologies to optimize for multiple scenarios at once. For example in RxDB there is the [localstorage meta optimizer](../rx-storage-localstorage-meta-optimizer.md) which stores initial metadata in localstorage and "normal" documents inside of IndexedDB. This improves the initial startup time while still having the documents stored in a way to query them efficiently. +- There is the [memory-mapped](../rx-storage-memory-mapped.md) storage plugin in RxDB which maps data directly to memory. Using this in combination with a shared worker can improve pageloads and query time significantly. +- [Compressing](../key-compression.md) data before storing it might improve the performance for some of the storages. +- Splitting work up between [multiple WebWorkers](../rx-storage-worker.md) via [sharding](../rx-storage-sharding.md) can improve performance by utilizing the whole capacity of your users device. -### Initial page load -How fast does the first query load when there are many documents -stored already. +Here you can see the [performance comparison](../rx-storage-performance.md) of various RxDB storage implementations which gives a better view of real world performance: -## Lets reach the limits of client side storage performance with RxDB -- indexeddb optimizations -- compression with keycompression -- spliting work load with WebWorker -- sharding -- memory mapped/synced stuff +
+ RxStorage performance - browser +
-- Store metadata in localstorage -- fix initial page load for new tabs with the SharedWorker (only chrome can spawn WebWorkers inside of a SharedWorker) -- is OPFS faster then indexeddb? +## Future Improvements -### Things this article does not talk about +You are reading this in 2024, but the web does not stand still. There is a good chance that browser get enhanced to allow faster and better data operations. -WebSQL -session storage. -Web Storage API -Cross tab support -Observability +- Currently there is no way to directly access a persistend storage from inside a WebAssembly process. If this changes in the future, running SQLite (or a similar database) in a browser might be the best option. +- Sending data between the main thread and a WebWorker is slow but might be improved in the future. There is a [good article](https://surma.dev/things/is-postmessage-slow/) about why `postMessage()` is slow. +- IndexedDB lately [got support](https://developer.chrome.com/blog/maximum-idb-performance-with-storage-buckets) for storage buckets (chrome only) which might improve performance. -## Read further +## Follow Up TODO fix links -- Check out the [hackernews discussion of this article](https://news.ycombinator.com/item?id=39745993) -- Shared/Like my [announcement tweet](https://twitter.com/rxdbjs/status/1769507055298064818) + + - Reproduce the benchmarks at the [github repo](https://github.com/pubkey/localstorage-indexeddb-cookies-opfs-sqlite-wasm) - - Learn how to use RxDB with the [RxDB Quickstart](../quickstart.md) - Check out the [RxDB github repo](https://github.com/pubkey/rxdb) and leave a star ⭐ - -## TODOs - -- Is indexeddb faster with storage buckets? https://developer.chrome.com/blog/maximum-idb-performance-with-storage-buckets?hl=en diff --git a/docs-src/static/files/icons/sqlite.svg b/docs-src/static/files/icons/sqlite.svg new file mode 100644 index 00000000000..424e0abbe86 --- /dev/null +++ b/docs-src/static/files/icons/sqlite.svg @@ -0,0 +1,67 @@ + + + +SQLiteimage/svg+xmlSQLite \ No newline at end of file From b2f3445821f45f2c190625e15636e17c5c3e1cda Mon Sep 17 00:00:00 2001 From: pubkey <8926560+pubkey@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:10:23 +0200 Subject: [PATCH 13/15] CHORE --- ...rage-indexeddb-cookies-opfs-sqlite-wasm.md | 64 ++++++++++++------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md index d669ac5ce2c..bf583973b76 100644 --- a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md +++ b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md @@ -24,17 +24,17 @@ GOALS: # Localstorage vs. IndexedDB vs. Cookies vs. OPFS vs. WASM-SQLite -So you build that web application and you want to **store data inside of your users browser**. Maybe you just need to store some small flags or you even need a fully fledged database to store massive amounts of data for your [local first app](../offline-first.md). +So you build that web application and you want to **store data inside of your users browser**. Maybe you just need to store some small flags or you even need a fully fledged database. -In the beginnings of the Web, we only had cookies to store some small key value assignements. But over the years JavaScript has evolved significantly and better storage APIs have been added to the browsers which pave the way for bigger and more complex data operations. Namely we have [Localstorage](./localstorage.md), WebSQL, IndexedDB, and the Origin Private File System API [(OPFS)](../rx-storage-opfs.md). +The types of web applications we build has changed significantly. In the early years of the web we served static html files. Then we served dynamically rendered html and later we build **single page applications** that run most logic on the client. And for the coming years you might want to build so called [local first apps](../offline-first.md) that handle big and complex data operations solely on the client and even work when offline, which gives you the opportunity to build **zero-latency** user interactions. +In the beginnings of the Web, we only had **cookies** to store some small key value assignements. But JavaScript and browsers have evolved significantly and better storage APIs have been added which pave the way for bigger and more complex data operations. -In this article, we will dive into the various technologies available for storing and querying data in a browser. We'll explore traditional methods like **Cookies**, **LocalStorage** and **IndexedDB**, and newer solutions such as **OPFS** and **SQLite via WebAssembly**. Through performance tests we aim to uncover how fast we can write and read data in a web application with the various methods. - +In this article, we will dive into the various technologies available for storing and querying data in a browser. We'll explore traditional methods like **Cookies**, **localStorage**, **WebSQL**, **IndexedDB** and newer solutions such as **OPFS** and **SQLite via WebAssembly**. We compare the features and limitations and through performance tests we aim to uncover how fast we can write and read data in a web application with the various methods. :::note You are reading this in the [RxDB](/) docs. RxDB is a JavaScript database that has different storage adapters which can utilize the different storage APIs. -**Since 2017** I spend most of my time working with these APIs, doing performance tests and building [hacks](../slow-indexeddb.md) to reach the limits of browser database operation speed. +**Since 2017** I spend most of my time working with these APIs, doing performance tests and building [hacks](../slow-indexeddb.md) and plugins to reach the limits of browser database operation speed.
@@ -46,21 +46,18 @@ You are reading this in the [RxDB](/) docs. RxDB is a JavaScript database that h ## The available Storage APIs in a modern Browser -Over the years, the type of web applications we build has changed significantly. In the early years of the web we served static html files. Then we served dynamically rendered html and later we build single page applications that run most logic on the client. And for the comming years you might want to build so called [local first apps](../offline-first.md) that handle big and complex data operations solely on the client and even work when offline which gives you the opportunity to build zero-latency user interactions. - -For these increments of use cases, the browser vendors kept in pace providing more and better APIs. Let me give you a brief explanation of them: +First lets have a brief overview of the different APIs, their intentional use case and history: ### What are Cookies Cookies were first introduced by [netscape in 1994](https://www.baekdal.com/thoughts/the-original-cookie-specification-from-1997-was-gdpr-compliant/). -Cookies store small pieces of key-value data. Cookies are mainly used for session management, personalization, and tracking, but are limited to about `4 KB` of data in [RFC-6265](https://datatracker.ietf.org/doc/html/rfc6265#section-6.1). Because the stored cookies are send to the server with every HTTP request, this limitation is reasonable. You can test your browsers cookie limits [here](http://www.ruslog.com/tools/cookies.html). - -This size limitation means we cannot store much data in a cookie but it is still interesting how good cookie access performance compared to the other methods. Especially because cookies are such an important base feature of the web, many performance optimizations have been done and even these days there is still progress being made like the [Shared Memory Versioning](https://blog.chromium.org/2024/06/introducing-shared-memory-versioning-to.html) by chromium or the asynchronous [CookieStore API](https://developer.mozilla.org/en-US/docs/Web/API/Cookie_Store_API). +Cookies store small pieces of key-value data. Cookies are mainly used for session management, personalization, and tracking. Cookies can have several security settings like a time-to-live or the `domain` attribute to share the cookies between several subdomains. +Cookies values are not only stored at the client but also send with **every http request** to the server. This means we cannot store much data in a cookie but it is still interesting how good cookie access performance compared to the other methods. Especially because cookies are such an important base feature of the web, many performance optimizations have been done and even these days there is still progress being made like the [Shared Memory Versioning](https://blog.chromium.org/2024/06/introducing-shared-memory-versioning-to.html) by chromium or the asynchronous [CookieStore API](https://developer.mozilla.org/en-US/docs/Web/API/Cookie_Store_API). ### What is Localstorage -The [LocalStorage API](./localstorage.md) was first proposed as part of the [WebStorage specification in 2009](https://www.w3.org/TR/2009/WD-webstorage-20090423/#the-localstorage-attribute). +The [localStorage API](./localstorage.md) was first proposed as part of the [WebStorage specification in 2009](https://www.w3.org/TR/2009/WD-webstorage-20090423/#the-localstorage-attribute). LocalStorage provides a simple API to store key-value pairs inside of a web browser. It has the methods `setItem`, `getItem`, `removeItem` and `clear` which is all you need from a key-value store. Localstorage is only suitable for storing small amounts of data that need to persist across sessions and it is [limited by a 5MB storage cap](./localstorage.md#understanding-the-limitations-of-local-storage). Storing complex data is only possible by transforming it into a string for example with `JSON.stringify()`. The API is not asynchronous which means if fully blocks your JavaScript process while doing stuff. Therefore running heavy operations on it might block your UI from rendering. @@ -100,7 +97,7 @@ Wasm was added to major browsers over the course of 2017 wich opened a wide rang Many people started to use compiled SQLite as a database inside of the browser which is why it makes sense to also compare this setup to the native APIs. -The compiled byte code of SQLite has a size of [about 938.9 kB](https://sqlite.org/download.html) which must be downloaded and parsed by the users on the first page load. WASM cannot directly access any persistend storage API in the browser. Instead it requires data to flow from WASM to the main-thread and then can be put into one of the browser APIs. This is done with so called [VFS adapters](https://www.sqlite.org/vfs.html) that handle data access from SQLite to anything else. +The compiled byte code of SQLite has a size of [about 938.9 kB](https://sqlite.org/download.html) which must be downloaded and parsed by the users on the first page load. WASM cannot directly access any persistend storage API in the browser. Instead it requires data to flow from WASM to the main-thread and then can be put into one of the browser APIs. This is done with so called [VFS (virtual file system) adapters](https://www.sqlite.org/vfs.html) that handle data access from SQLite to anything else. ### What was WebSQL @@ -113,6 +110,7 @@ WebSQL has been **removed from browsers** in the current years for multiple good Therefore in the following we will **just ignore WebSQL** even if it would be possible to run tests on in by setting specific browser flags or using old versions of chromium. + ------------- ## Feature Comparison @@ -183,6 +181,26 @@ Unfortunately **Localstorage** and **Cookies** [cannot be used in WebWorker or S Everything else can be used from inside a WebWorker. The fast version of OPFS with the `createSyncAccessHandle` method can **only** [be used in a WebWorker](../rx-storage-opfs.md#opfs-limitations), and **not on the main thread**. This is because all the operations of the returned `AccessHandle` are **not async** and therefore block the JavaScript process, so you do want to do that on the main thread and block everything. +------------- + + +## Storage Size Limits + + + +- **Cookies** are limited to about `4 KB` of data in [RFC-6265](https://datatracker.ietf.org/doc/html/rfc6265#section-6.1). Because the stored cookies are send to the server with every HTTP request, this limitation is reasonable. You can test your browsers cookie limits [here](http://www.ruslog.com/tools/cookies.html). Notice that you should never fill up the full `4 KB` of your cookies because your websserver will not accept too long headers and reject the reuqests with `HTTP ERROR 431 - Request header fields too large`. Once you have reached that point you can not even serve updated JavaScript to your user to clean up the cookies and you will have locked out that user until the cookies get cleaned up manually. + +- **LocalStorage** has a storage size limitation that varies depending on the browser, but generally ranges from 4 MB to 10 MB per origin. You can test your localStorage size limit [here](https://arty.name/localstorage.html). + - Chrome/Chromium/Edge: 5 MB per domain + - Firefox: 10 MB per domain + - Safari: 4-5 MB per domain (varies slightly between versions) + +- **IndexedDB** does not have a specific fixed size limitation like localStorage. The maximum storage size for IndexedDB depends on the browser implementation. The upper limit is typically based on the available disk space on the user's device. In chromium browsers it can use up to 80% of total disk space. You can get an estimation about the storage size limit by calling `await navigator.storage.estimate()`. Typically you can store gigabytes of data which can be tried out [here](https://demo.agektmr.com/storage/). + +- **OPFS** has the same storage size limitation as IndexedDB. Its limit depends on the available disc space. This can also be tested [here](https://demo.agektmr.com/storage/). + +------------- + ## Performance Comparison Lets do some performance comparisons. Notice that we only run simple tests and for your specific use case in your application the results might differ. Also we only compare performance in google chrome (version 128.0.6613.137). Firefox and Safari have similar **but not equal** performance patterns. You can run the test by yourself on your own machine from this [github repository](https://github.com/pubkey/localstorage-indexeddb-cookies-opfs-sqlite-wasm). Notice that for all tests we throttle the network to behave like the average german internet speed. (download: 135,900 kbit/s, upload: 28,400 kbit/s, latency: 125ms). Also all tests store an "average" JSON object that might be required to be stringified depending on the storage. We also only test the performance of storing documents by id because some of the technologies (cookies, OPFS and localstorage) do not support indexed range operations so it makes no sense to compare the performance of these. @@ -191,7 +209,7 @@ Lets do some performance comparisons. Notice that we only run simple tests and f Before you can store any data, many APIs require a setup process like creating databases, spawing WebAssembly processes or downloading additional stuff. To ensure your app starts fast, the initialization time is important. -LocalStorage and Cookies do not have any setup process and can be directly used. IndexedDB requires to open a database and a store inside of it. WASM SQLite needs to download a WASM file and process it. OPFS needs to download and start a worker file and initialize the virtual file system directory. +The APIs of localStorage and Cookies do not have any setup process and can be directly used. IndexedDB requires to open a database and a store inside of it. WASM SQLite needs to download a WASM file and process it. OPFS needs to download and start a worker file and initialize the virtual file system directory. Here are the time measurements from how long it takes until the first bit of data can be stored: @@ -205,7 +223,7 @@ Here are the time measurements from how long it takes until the first bit of dat Here we can notice a few things: -- Opening a new IndexedDB database with a single store takes suprisingly long +- Opening a new IndexedDB database with a single store takes surprisingly long - The latency overhead of sending data from the main thread to a WebWorker OPFS is about 4 milliseconds. Here we only send minimal data to init the OPFS file handler. It will be interesting if that latency increases when more data is processed. - Downloading and parsing WASM SQLite and creating a single table takes about half a second. Using also the IndexedDB VFS to store data persistently adds additional 31 milliseconds. Reloading the page with enabled caching and already prepared tables is a bit faster with 420 milliseconds (memory). @@ -228,10 +246,10 @@ Next lets test the latency of small writes. This is important when you do many s Here we can notice a few things: - Localstorage has the lowest write latency with only 0.017 milliseconds per write. -- IndexedDB writes are about 10 times slower compared to LocalStorage. +- IndexedDB writes are about 10 times slower compared to localStorage. - Sending the data to the WASM SQLite process and letting it persist via IndexedDB is slow with over 3 milliseconds per write. -The OPFS operations take about 1.5 seconds to write the JSON data into one document per file. We can see the sending the data to a webworker first ist a bit slower which comes from the overhead of serializing and deserialing the data on both sides. +The OPFS operations take about 1.5 seconds to write the JSON data into one document per file. We can see the sending the data to a webworker first ist a bit slower which comes from the overhead of serializing and deserializing the data on both sides. If we would not create on OPFS file per document but instead append everything to a single file, the performance pattern changes significantly. Then the faster file handle from the `createSyncAccessHandle()` only takes about 1 millisecond per write. But this would require to somehow remember at which position the each document is stored. Therefore in our tests we will continue using one file per document. ### Latency of small Reads @@ -293,18 +311,20 @@ Now lets read 100 documents in a bulk request. Here we can notice a few things: - Reading many files in the OPFS webworker is about **twice as fast** compared to the slower main thread mode. -- WASM SQLite is suprisingly fast. Further inspection has shown that the WASM SQLite process keeps the documents in memory cached which improves the latency when we do reads directly after writes on the same data. When the browser tab is reloaded between the writes and the reads, finding the 100 documents takes about **35 milliseconds** instead. +- WASM SQLite is surprisingly fast. Further inspection has shown that the WASM SQLite process keeps the documents in memory cached which improves the latency when we do reads directly after writes on the same data. When the browser tab is reloaded between the writes and the reads, finding the 100 documents takes about **35 milliseconds** instead. ## Performance Conclusions - LocalStorage is really fast but remember that is has some downsides: - - Blocks the main JavaScript process and therefore should not be used for big bulk operations. - - Only Key-Value assignements are possible, you cannot use it efficiently when you need to do index based range queries on your data. + - It blocks the main JavaScript process and therefore should not be used for big bulk operations. + - Only Key-Value assignments are possible, you cannot use it efficiently when you need to do index based range queries on your data. - OPFS is way faster when used in the WebWorker with the `createSyncAccessHandle()` method compare to using it directly in the main thread. - SQLite WASM can be fast but the you have to initally download the full binary and start it up which takes about half a second. This might not be relevant at all if your app is started up once and the used for a very long time. But for web-apps that are opened and closed in many browser tabs many times, this might be a problem. +------------- + ## Possible Improvements There is a wide range of possible improvements and performance hacks to speed up the operations. @@ -326,13 +346,13 @@ Here you can see the [performance comparison](../rx-storage-performance.md) of v You are reading this in 2024, but the web does not stand still. There is a good chance that browser get enhanced to allow faster and better data operations. -- Currently there is no way to directly access a persistend storage from inside a WebAssembly process. If this changes in the future, running SQLite (or a similar database) in a browser might be the best option. +- Currently there is no way to directly access a persistent storage from inside a WebAssembly process. If this changes in the future, running SQLite (or a similar database) in a browser might be the best option. - Sending data between the main thread and a WebWorker is slow but might be improved in the future. There is a [good article](https://surma.dev/things/is-postmessage-slow/) about why `postMessage()` is slow. - IndexedDB lately [got support](https://developer.chrome.com/blog/maximum-idb-performance-with-storage-buckets) for storage buckets (chrome only) which might improve performance. ## Follow Up -TODO fix links + - Reproduce the benchmarks at the [github repo](https://github.com/pubkey/localstorage-indexeddb-cookies-opfs-sqlite-wasm) From b8dd75bfb65766f665f3eb0f80c7be7ea5e4262b Mon Sep 17 00:00:00 2001 From: pubkey <8926560+pubkey@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:17:53 +0200 Subject: [PATCH 14/15] IMPROVE --- ...rage-indexeddb-cookies-opfs-sqlite-wasm.md | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md index bf583973b76..cb19c5bf663 100644 --- a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md +++ b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md @@ -1,6 +1,7 @@ --- -title: Localstorage vs. IndexedDB vs. Cookies vs. OPFS vs. WASM-SQLite +title: LocalStorage vs. IndexedDB vs. Cookies vs. OPFS vs. WASM-SQLite slug: localstorage-indexeddb-cookies-opfs-sqlite-wasm.html +description: Compare LocalStorage, IndexedDB, Cookies, OPFS, and WASM-SQLite for web storage, performance, limits, and best practices for modern web apps. --- -# Localstorage vs. IndexedDB vs. Cookies vs. OPFS vs. WASM-SQLite +# LocalStorage vs. IndexedDB vs. Cookies vs. OPFS vs. WASM-SQLite -So you build that web application and you want to **store data inside of your users browser**. Maybe you just need to store some small flags or you even need a fully fledged database. +So you are building that web application and you want to **store data inside of your users browser**. Maybe you just need to store some small flags or you even need a fully fledged database. -The types of web applications we build has changed significantly. In the early years of the web we served static html files. Then we served dynamically rendered html and later we build **single page applications** that run most logic on the client. And for the coming years you might want to build so called [local first apps](../offline-first.md) that handle big and complex data operations solely on the client and even work when offline, which gives you the opportunity to build **zero-latency** user interactions. +The types of web applications we build have changed significantly. In the early years of the web we served static html files. Then we served dynamically rendered html and later we build **single page applications** that run most logic on the client. And for the coming years you might want to build so called [local first apps](../offline-first.md) that handle big and complex data operations solely on the client and even work when offline, which gives you the opportunity to build **zero-latency** user interactions. -In the beginnings of the Web, we only had **cookies** to store some small key value assignements. But JavaScript and browsers have evolved significantly and better storage APIs have been added which pave the way for bigger and more complex data operations. +In the early days of the web, **cookies** were the only option for storing small key-value assignments.. But JavaScript and browsers have evolved significantly and better storage APIs have been added which pave the way for bigger and more complex data operations. In this article, we will dive into the various technologies available for storing and querying data in a browser. We'll explore traditional methods like **Cookies**, **localStorage**, **WebSQL**, **IndexedDB** and newer solutions such as **OPFS** and **SQLite via WebAssembly**. We compare the features and limitations and through performance tests we aim to uncover how fast we can write and read data in a web application with the various methods. @@ -51,14 +52,14 @@ First lets have a brief overview of the different APIs, their intentional use ca ### What are Cookies Cookies were first introduced by [netscape in 1994](https://www.baekdal.com/thoughts/the-original-cookie-specification-from-1997-was-gdpr-compliant/). -Cookies store small pieces of key-value data. Cookies are mainly used for session management, personalization, and tracking. Cookies can have several security settings like a time-to-live or the `domain` attribute to share the cookies between several subdomains. +Cookies store small pieces of key-value data that are mainly used for session management, personalization, and tracking. Cookies can have several security settings like a time-to-live or the `domain` attribute to share the cookies between several subdomains. -Cookies values are not only stored at the client but also send with **every http request** to the server. This means we cannot store much data in a cookie but it is still interesting how good cookie access performance compared to the other methods. Especially because cookies are such an important base feature of the web, many performance optimizations have been done and even these days there is still progress being made like the [Shared Memory Versioning](https://blog.chromium.org/2024/06/introducing-shared-memory-versioning-to.html) by chromium or the asynchronous [CookieStore API](https://developer.mozilla.org/en-US/docs/Web/API/Cookie_Store_API). +Cookies values are not only stored at the client but also sent with **every http request** to the server. This means we cannot store much data in a cookie but it is still interesting how good cookie access performance compared to the other methods. Especially because cookies are such an important base feature of the web, many performance optimizations have been done and even these days there is still progress being made like the [Shared Memory Versioning](https://blog.chromium.org/2024/06/introducing-shared-memory-versioning-to.html) by chromium or the asynchronous [CookieStore API](https://developer.mozilla.org/en-US/docs/Web/API/Cookie_Store_API). -### What is Localstorage +### What is LocalStorage The [localStorage API](./localstorage.md) was first proposed as part of the [WebStorage specification in 2009](https://www.w3.org/TR/2009/WD-webstorage-20090423/#the-localstorage-attribute). -LocalStorage provides a simple API to store key-value pairs inside of a web browser. It has the methods `setItem`, `getItem`, `removeItem` and `clear` which is all you need from a key-value store. Localstorage is only suitable for storing small amounts of data that need to persist across sessions and it is [limited by a 5MB storage cap](./localstorage.md#understanding-the-limitations-of-local-storage). Storing complex data is only possible by transforming it into a string for example with `JSON.stringify()`. +LocalStorage provides a simple API to store key-value pairs inside of a web browser. It has the methods `setItem`, `getItem`, `removeItem` and `clear` which is all you need from a key-value store. LocalStorage is only suitable for storing small amounts of data that need to persist across sessions and it is [limited by a 5MB storage cap](./localstorage.md#understanding-the-limitations-of-local-storage). Storing complex data is only possible by transforming it into a string for example with `JSON.stringify()`. The API is not asynchronous which means if fully blocks your JavaScript process while doing stuff. Therefore running heavy operations on it might block your UI from rendering. > There is also the **SessionStorage** API. The key difference is that localStorage data persists indefinitely until explicitly cleared, while sessionStorage data is cleared when the browser tab or window is closed. @@ -93,15 +94,15 @@ Because only binary data can be processed, OPFS is made to be a base filesystem [WebAssembly](https://webassembly.org/) (Wasm) is a binary format that allows high-performance code execution on the web. -Wasm was added to major browsers over the course of 2017 wich opened a wide range of opportunities on what to run inside of a browser. You can compile native libraries to WebAssembly and just run them on the client with just a few adjustments. WASM code can be shipped to browser apps and generally runs much faster compared to JavaScript, but still about [10% slower then native](https://www.usenix.org/conference/atc19/presentation/jangda). +Wasm was added to major browsers over the course of 2017 which opened a wide range of opportunities on what to run inside of a browser. You can compile native libraries to WebAssembly and just run them on the client with just a few adjustments. WASM code can be shipped to browser apps and generally runs much faster compared to JavaScript, but still about [10% slower then native](https://www.usenix.org/conference/atc19/presentation/jangda). Many people started to use compiled SQLite as a database inside of the browser which is why it makes sense to also compare this setup to the native APIs. -The compiled byte code of SQLite has a size of [about 938.9 kB](https://sqlite.org/download.html) which must be downloaded and parsed by the users on the first page load. WASM cannot directly access any persistend storage API in the browser. Instead it requires data to flow from WASM to the main-thread and then can be put into one of the browser APIs. This is done with so called [VFS (virtual file system) adapters](https://www.sqlite.org/vfs.html) that handle data access from SQLite to anything else. +The compiled byte code of SQLite has a size of [about 938.9 kB](https://sqlite.org/download.html) which must be downloaded and parsed by the users on the first page load. WASM cannot directly access any persistent storage API in the browser. Instead it requires data to flow from WASM to the main-thread and then can be put into one of the browser APIs. This is done with so called [VFS (virtual file system) adapters](https://www.sqlite.org/vfs.html) that handle data access from SQLite to anything else. ### What was WebSQL -WebSQL **has been** a web API [introduced in 2009](https://www.w3.org/TR/webdatabase/) that allowed browsers to use SQL databases for client-side storage, based on SQLite. The idea was to give developers a way to store and query data using SQL on the client side, similar to server-side databases. +WebSQL **was** a web API [introduced in 2009](https://www.w3.org/TR/webdatabase/) that allowed browsers to use SQL databases for client-side storage, based on SQLite. The idea was to give developers a way to store and query data using SQL on the client side, similar to server-side databases. WebSQL has been **removed from browsers** in the current years for multiple good reasons: - WebSQL was not standardized and having an API based on a single specific implementation in form of the SQLite source code is hard to ever make it to a standard. @@ -155,7 +156,7 @@ The big difference between a database and storing data in a plain file, is that In IndexedDB for example, we can fetch a bulk of documents by a given index range: ```ts -// find all producs with a price between 10 and 50 +// find all products with a price between 10 and 50 const keyRange = IDBKeyRange.bound(10, 50); const transaction = db.transaction('products', 'readonly'); const objectStore = transaction.objectStore('products'); @@ -176,7 +177,7 @@ When running heavy data operations, you might want to move the processing away f The most common API for that use case is spawning a **WebWorker** and doing most work on that second JavaScript process. The worker is spawned from a seperate JavaScript file (or base64 string) and communicates with the main thread by sending data with `postMessage()`. -Unfortunately **Localstorage** and **Cookies** [cannot be used in WebWorker or SharedWorker](https://stackoverflow.com/questions/6179159/accessing-localstorage-from-a-webworker) because of the design and security constraints. WebWorkers run in a separate global context from the main browser thread and therefore cannot do stuff that might impact the main thread. They have no direct access to certain web APIs, like the DOM, localStorage, or cookies. +Unfortunately **LocalStorage** and **Cookies** [cannot be used in WebWorker or SharedWorker](https://stackoverflow.com/questions/6179159/accessing-localstorage-from-a-webworker) because of the design and security constraints. WebWorkers run in a separate global context from the main browser thread and therefore cannot do stuff that might impact the main thread. They have no direct access to certain web APIs, like the DOM, localStorage, or cookies. Everything else can be used from inside a WebWorker. The fast version of OPFS with the `createSyncAccessHandle` method can **only** [be used in a WebWorker](../rx-storage-opfs.md#opfs-limitations), and **not on the main thread**. This is because all the operations of the returned `AccessHandle` are **not async** and therefore block the JavaScript process, so you do want to do that on the main thread and block everything. @@ -188,14 +189,14 @@ The fast version of OPFS with the `createSyncAccessHandle` method can **only** [ -- **Cookies** are limited to about `4 KB` of data in [RFC-6265](https://datatracker.ietf.org/doc/html/rfc6265#section-6.1). Because the stored cookies are send to the server with every HTTP request, this limitation is reasonable. You can test your browsers cookie limits [here](http://www.ruslog.com/tools/cookies.html). Notice that you should never fill up the full `4 KB` of your cookies because your websserver will not accept too long headers and reject the reuqests with `HTTP ERROR 431 - Request header fields too large`. Once you have reached that point you can not even serve updated JavaScript to your user to clean up the cookies and you will have locked out that user until the cookies get cleaned up manually. +- **Cookies** are limited to about `4 KB` of data in [RFC-6265](https://datatracker.ietf.org/doc/html/rfc6265#section-6.1). Because the stored cookies are send to the server with every HTTP request, this limitation is reasonable. You can test your browsers cookie limits [here](http://www.ruslog.com/tools/cookies.html). Notice that you should never fill up the full `4 KB` of your cookies because your websserver will not accept too long headers and reject the requests with `HTTP ERROR 431 - Request header fields too large`. Once you have reached that point you can not even serve updated JavaScript to your user to clean up the cookies and you will have locked out that user until the cookies get cleaned up manually. - **LocalStorage** has a storage size limitation that varies depending on the browser, but generally ranges from 4 MB to 10 MB per origin. You can test your localStorage size limit [here](https://arty.name/localstorage.html). - Chrome/Chromium/Edge: 5 MB per domain - Firefox: 10 MB per domain - Safari: 4-5 MB per domain (varies slightly between versions) -- **IndexedDB** does not have a specific fixed size limitation like localStorage. The maximum storage size for IndexedDB depends on the browser implementation. The upper limit is typically based on the available disk space on the user's device. In chromium browsers it can use up to 80% of total disk space. You can get an estimation about the storage size limit by calling `await navigator.storage.estimate()`. Typically you can store gigabytes of data which can be tried out [here](https://demo.agektmr.com/storage/). +- **IndexedDB** does not have a specific fixed size limitation like localStorage. The maximum storage size for IndexedDB depends on the browser implementation. The upper limit is typically based on the available disc space on the user's device. In chromium browsers it can use up to 80% of total disk space. You can get an estimation about the storage size limit by calling `await navigator.storage.estimate()`. Typically you can store gigabytes of data which can be tried out [here](https://demo.agektmr.com/storage/). - **OPFS** has the same storage size limitation as IndexedDB. Its limit depends on the available disc space. This can also be tested [here](https://demo.agektmr.com/storage/). @@ -203,11 +204,13 @@ The fast version of OPFS with the `createSyncAccessHandle` method can **only** [ ## Performance Comparison -Lets do some performance comparisons. Notice that we only run simple tests and for your specific use case in your application the results might differ. Also we only compare performance in google chrome (version 128.0.6613.137). Firefox and Safari have similar **but not equal** performance patterns. You can run the test by yourself on your own machine from this [github repository](https://github.com/pubkey/localstorage-indexeddb-cookies-opfs-sqlite-wasm). Notice that for all tests we throttle the network to behave like the average german internet speed. (download: 135,900 kbit/s, upload: 28,400 kbit/s, latency: 125ms). Also all tests store an "average" JSON object that might be required to be stringified depending on the storage. We also only test the performance of storing documents by id because some of the technologies (cookies, OPFS and localstorage) do not support indexed range operations so it makes no sense to compare the performance of these. +Now that we've reviewed the features of each storage method, let's dive into performance comparisons, focusing on initialization times, read/write latencies, and bulk operations. + +Notice that we only run simple tests and for your specific use case in your application the results might differ. Also we only compare performance in google chrome (version 128.0.6613.137). Firefox and Safari have similar **but not equal** performance patterns. You can run the test by yourself on your own machine from this [github repository](https://github.com/pubkey/localstorage-indexeddb-cookies-opfs-sqlite-wasm). For all tests we throttle the network to behave like the average german internet speed. (download: 135,900 kbit/s, upload: 28,400 kbit/s, latency: 125ms). Also all tests store an "average" JSON object that might be required to be stringified depending on the storage. We also only test the performance of storing documents by id because some of the technologies (cookies, OPFS and localstorage) do not support indexed range operations so it makes no sense to compare the performance of these. ### Initialization Time -Before you can store any data, many APIs require a setup process like creating databases, spawing WebAssembly processes or downloading additional stuff. To ensure your app starts fast, the initialization time is important. +Before you can store any data, many APIs require a setup process like creating databases, spawning WebAssembly processes or downloading additional stuff. To ensure your app starts fast, the initialization time is important. The APIs of localStorage and Cookies do not have any setup process and can be directly used. IndexedDB requires to open a database and a store inside of it. WASM SQLite needs to download a WASM file and process it. OPFS needs to download and start a worker file and initialize the virtual file system directory. @@ -235,7 +238,7 @@ Next lets test the latency of small writes. This is important when you do many s | Technology | Time in Milliseconds | | ----------------------- | -------------------- | | Cookies | 0.058 | -| Localstorage | 0.017 | +| LocalStorage | 0.017 | | IndexedDB | 0.17 | | OPFS Main Thread | 1.46 | | OPFS WebWorker | 1.54 | @@ -245,7 +248,7 @@ Next lets test the latency of small writes. This is important when you do many s Here we can notice a few things: -- Localstorage has the lowest write latency with only 0.017 milliseconds per write. +- LocalStorage has the lowest write latency with only 0.017 milliseconds per write. - IndexedDB writes are about 10 times slower compared to localStorage. - Sending the data to the WASM SQLite process and letting it persist via IndexedDB is slow with over 3 milliseconds per write. @@ -260,7 +263,7 @@ Now that we have stored some documents, lets measure how long it takes to read s | Technology | Time in Milliseconds | | ----------------------- | -------------------- | | Cookies | 0.132 | -| Localstorage | 0.0052 | +| LocalStorage | 0.0052 | | IndexedDB | 0.1 | | OPFS Main Thread | 1.28 | | OPFS WebWorker | 1.41 | @@ -270,7 +273,7 @@ Now that we have stored some documents, lets measure how long it takes to read s Here we can notice a few things: -- Localstorage reads are **really really fast** with only 0.0052 milliseconds per read. +- LocalStorage reads are **really really fast** with only 0.0052 milliseconds per read. - The other technologies perform reads in a similar speed to their write latency. ### Big Bulk Writes @@ -280,7 +283,7 @@ As next step, lets do some big bulk operations with 200 documents at once. | Technology | Time in Milliseconds | | ----------------------- | -------------------- | | Cookies | 20.6 | -| Localstorage | 5.79 | +| LocalStorage | 5.79 | | IndexedDB | 13.41 | | OPFS Main Thread | 280 | | OPFS WebWorker | 104 | @@ -301,7 +304,7 @@ Now lets read 100 documents in a bulk request. | Technology | Time in Milliseconds | | ----------------------- | ------------------------------- | | Cookies | 6.34 | -| Localstorage | 0.39 | +| LocalStorage | 0.39 | | IndexedDB | 4.99 | | OPFS Main Thread | 54.79 | | OPFS WebWorker | 25.61 | From 5449c99f8fed480a5e030108f2579e834c5b8d55 Mon Sep 17 00:00:00 2001 From: pubkey <8926560+pubkey@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:21:42 +0200 Subject: [PATCH 15/15] FIX typos --- ...ocalstorage-indexeddb-cookies-opfs-sqlite-wasm.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md index cb19c5bf663..9b2d2a523b0 100644 --- a/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md +++ b/docs-src/docs/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.md @@ -69,7 +69,7 @@ The API is not asynchronous which means if fully blocks your JavaScript process IndexedDB was first introduced as "Indexed Database API" [in 2015](https://www.w3.org/TR/IndexedDB/#sotd). -IndexedDB is a low-level API for storing large amounts of structured JSON data. While the API is a bit hard to use, IndexedDB can utilize indexes and asynchronous operations. It lacks support for complex queries and only allows to iterate over the indexes which makes it more like a base layer for other libraries then a fully fledged database. +[IndexedDB](../rx-storage-indexeddb.md) is a low-level API for storing large amounts of structured JSON data. While the API is a bit hard to use, IndexedDB can utilize indexes and asynchronous operations. It lacks support for complex queries and only allows to iterate over the indexes which makes it more like a base layer for other libraries then a fully fledged database. In 2018, IndexedDB version 2.0 [was introduced](https://hacks.mozilla.org/2016/10/whats-new-in-indexeddb-2-0/). This added some major improvements. Most noticeable the `getAll()` method which improves performance dramatically when fetching bulks of JSON documents. @@ -82,7 +82,7 @@ The [Origin Private File System](../rx-storage-opfs.md) (OPFS) is a [relatively OPFS can be used in two modes: - Either asynchronous on the [main thread](../rx-storage-opfs.md#using-opfs-in-the-main-thread-instead-of-a-worker) -- Or in a WebWorker with the faster, aynchronous access with the `createSyncAccessHandle()` method. +- Or in a WebWorker with the faster, asynchronous access with the `createSyncAccessHandle()` method. Because only binary data can be processed, OPFS is made to be a base filesystem for library developers. You will unlikely directly want to use the OPFS in your code when you build a "normal" application because it is too complex. That would only make sense for storing plain files like images, not to store and query JSON data efficiently. I have build a [OPFS based storage](../rx-storage-opfs.md) for RxDB with proper indexing and querying and it took me several months. @@ -146,7 +146,7 @@ addEventListener("storage", (event) => {}); There was the [experimental IndexedDB observers API](https://stackoverflow.com/a/33270440) for chrome, but the proposal repository has been archived. To workaround this problem, there are two solutions: -- The first option is to use the [BroadcastChannel API](https://github.com/pubkey/broadcast-channel) which can send messages across browser tabs. So whenever you do a write to the storage, you also send a notification to other tabs to inform them about these changes. This is the most common workaround which is also used by RxDB. Notice that there is also the [WebLocks API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API) which can be used to have mutexes accross browser tabs. +- The first option is to use the [BroadcastChannel API](https://github.com/pubkey/broadcast-channel) which can send messages across browser tabs. So whenever you do a write to the storage, you also send a notification to other tabs to inform them about these changes. This is the most common workaround which is also used by RxDB. Notice that there is also the [WebLocks API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API) which can be used to have mutexes across browser tabs. - The other solution is to use the [SharedWorker](https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker) and do all writes inside of the worker. All browser tabs can then subscribe to messages from that **single** SharedWorker and know about changes. ### Indexing Support @@ -175,7 +175,7 @@ Notice that IndexedDB has the limitation of [not having indexes on boolean value When running heavy data operations, you might want to move the processing away from the JavaScript main thread. This ensures that our app keeps being responsive and fast while the processing can run in parallel in the background. In a browser you can either use the [WebWorker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API), [SharedWorker](https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker) or the [ServiceWorker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) API to do that. In RxDB you can use the [WebWorker](../rx-storage-worker.md) or [SharedWorker](../rx-storage-shared-worker.md) plugins to move your storage inside of a worker. -The most common API for that use case is spawning a **WebWorker** and doing most work on that second JavaScript process. The worker is spawned from a seperate JavaScript file (or base64 string) and communicates with the main thread by sending data with `postMessage()`. +The most common API for that use case is spawning a **WebWorker** and doing most work on that second JavaScript process. The worker is spawned from a separate JavaScript file (or base64 string) and communicates with the main thread by sending data with `postMessage()`. Unfortunately **LocalStorage** and **Cookies** [cannot be used in WebWorker or SharedWorker](https://stackoverflow.com/questions/6179159/accessing-localstorage-from-a-webworker) because of the design and security constraints. WebWorkers run in a separate global context from the main browser thread and therefore cannot do stuff that might impact the main thread. They have no direct access to certain web APIs, like the DOM, localStorage, or cookies. @@ -252,7 +252,7 @@ Here we can notice a few things: - IndexedDB writes are about 10 times slower compared to localStorage. - Sending the data to the WASM SQLite process and letting it persist via IndexedDB is slow with over 3 milliseconds per write. -The OPFS operations take about 1.5 seconds to write the JSON data into one document per file. We can see the sending the data to a webworker first ist a bit slower which comes from the overhead of serializing and deserializing the data on both sides. +The OPFS operations take about 1.5 seconds to write the JSON data into one document per file. We can see the sending the data to a webworker first is a bit slower which comes from the overhead of serializing and deserializing the data on both sides. If we would not create on OPFS file per document but instead append everything to a single file, the performance pattern changes significantly. Then the faster file handle from the `createSyncAccessHandle()` only takes about 1 millisecond per write. But this would require to somehow remember at which position the each document is stored. Therefore in our tests we will continue using one file per document. ### Latency of small Reads @@ -323,7 +323,7 @@ Here we can notice a few things: - It blocks the main JavaScript process and therefore should not be used for big bulk operations. - Only Key-Value assignments are possible, you cannot use it efficiently when you need to do index based range queries on your data. - OPFS is way faster when used in the WebWorker with the `createSyncAccessHandle()` method compare to using it directly in the main thread. -- SQLite WASM can be fast but the you have to initally download the full binary and start it up which takes about half a second. This might not be relevant at all if your app is started up once and the used for a very long time. But for web-apps that are opened and closed in many browser tabs many times, this might be a problem. +- SQLite WASM can be fast but the you have to initially download the full binary and start it up which takes about half a second. This might not be relevant at all if your app is started up once and the used for a very long time. But for web-apps that are opened and closed in many browser tabs many times, this might be a problem. -------------