-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
Copy pathslow-indexeddb.html
119 lines (117 loc) · 78.8 KB
/
slow-indexeddb.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
<!doctype html>
<html lang="en" dir="ltr" class="docs-wrapper plugin-docs plugin-id-default docs-version-current docs-doc-page docs-doc-id-slow-indexeddb" data-has-hydrated="false">
<head>
<meta charset="UTF-8">
<meta name="generator" content="Docusaurus v3.7.0">
<title data-rh="true">Solving IndexedDB Slowness for Seamless Apps | RxDB - JavaScript Database</title><meta data-rh="true" name="viewport" content="width=device-width,initial-scale=1"><meta data-rh="true" name="twitter:card" content="summary_large_image"><meta data-rh="true" property="og:image" content="https://rxdb.info/img/rxdb_social_card.png"><meta data-rh="true" name="twitter:image" content="https://rxdb.info/img/rxdb_social_card.png"><meta data-rh="true" property="og:url" content="https://rxdb.info/slow-indexeddb.html"><meta data-rh="true" property="og:locale" content="en"><meta data-rh="true" name="docusaurus_locale" content="en"><meta data-rh="true" name="docsearch:language" content="en"><meta data-rh="true" name="docusaurus_version" content="current"><meta data-rh="true" name="docusaurus_tag" content="docs-default-current"><meta data-rh="true" name="docsearch:version" content="current"><meta data-rh="true" name="docsearch:docusaurus_tag" content="docs-default-current"><meta data-rh="true" property="og:title" content="Solving IndexedDB Slowness for Seamless Apps | RxDB - JavaScript Database"><meta data-rh="true" name="description" content="Struggling with IndexedDB performance? Discover hidden bottlenecks, advanced tuning techniques, and next-gen storage like the File System Access API."><meta data-rh="true" property="og:description" content="Struggling with IndexedDB performance? Discover hidden bottlenecks, advanced tuning techniques, and next-gen storage like the File System Access API."><link data-rh="true" rel="icon" href="/img/favicon.ico"><link data-rh="true" rel="canonical" href="https://rxdb.info/slow-indexeddb.html"><link data-rh="true" rel="alternate" href="https://rxdb.info/slow-indexeddb.html" hreflang="en"><link data-rh="true" rel="alternate" href="https://rxdb.info/slow-indexeddb.html" hreflang="x-default"><link rel="preconnect" href="https://www.google-analytics.com">
<link rel="preconnect" href="https://www.googletagmanager.com">
<script async src="https://www.googletagmanager.com/gtag/js?id=G-62D63SY3S0"></script>
<script>function gtag(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],gtag("js",new Date),gtag("config","G-62D63SY3S0",{})</script>
<link rel="preconnect" href="https://www.googletagmanager.com">
<script>window.dataLayer=window.dataLayer||[]</script>
<script>!function(e,t,a,n){e[n]=e[n]||[],e[n].push({"gtm.start":(new Date).getTime(),event:"gtm.js"});var g=t.getElementsByTagName(a)[0],m=t.createElement(a);m.async=!0,m.src="https://www.googletagmanager.com/gtm.js?id=GTM-PL63TR5",g.parentNode.insertBefore(m,g)}(window,document,"script","dataLayer")</script><link rel="stylesheet" href="/assets/css/styles.bc483400.css">
<script src="/assets/js/runtime~main.68ae4ffa.js" defer="defer"></script>
<script src="/assets/js/main.90e814da.js" defer="defer"></script>
</head>
<body class="navigation-with-keyboard">
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-PL63TR5" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<script>!function(){function t(t){document.documentElement.setAttribute("data-theme",t)}var e=function(){try{return new URLSearchParams(window.location.search).get("docusaurus-theme")}catch(t){}}()||function(){try{return window.localStorage.getItem("theme")}catch(t){}}();t(null!==e?e:"dark")}(),function(){try{const n=new URLSearchParams(window.location.search).entries();for(var[t,e]of n)if(t.startsWith("docusaurus-data-")){var a=t.replace("docusaurus-data-","data-");document.documentElement.setAttribute(a,e)}}catch(t){}}()</script><div id="__docusaurus"><div role="region" aria-label="Skip to main content"><a class="skipToContent_fXgn" href="#__docusaurus_skipToContent_fallback">Skip to main content</a></div><nav aria-label="Main" class="navbar navbar--fixed-top"><div class="navbar__inner"><div class="navbar__items"><button aria-label="Toggle navigation bar" aria-expanded="false" class="navbar__toggle clean-btn" type="button"><svg width="30" height="30" viewBox="0 0 30 30" aria-hidden="true"><path stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2" d="M4 7h22M4 15h22M4 23h22"></path></svg></button><a class="navbar__brand" href="/"><div class="navbar__logo"><img src="/files/logo/logo.svg" alt="RxDB Logo" class="themedComponent_mlkZ themedComponent--light_NVdE"><img src="/files/logo/logo.svg" alt="RxDB Logo" class="themedComponent_mlkZ themedComponent--dark_xIcU"></div><b class="navbar__title text--truncate">RxDB</b></a></div><div class="navbar__items navbar__items--right"><a class="navbar__item navbar__link" href="/consulting/">Support</a><a class="navbar__item navbar__link" href="/premium/">Premium</a><a class="navbar__item navbar__link" href="/overview.html">Docs</a><div class="navbarSearchContainer_Bca1"></div></div></div><div role="presentation" class="navbar-sidebar__backdrop"></div></nav><div style="position:fixed;display:block;z-index:10;height:1.5px;background-color:var(--color-top);top:62.5px;border-top-right-radius:2px;border-bottom-right-radius:2px;width:0%"></div><div id="__docusaurus_skipToContent_fallback" class="main-wrapper mainWrapper_z2l0"><div class="docsWrapper_hBAB"><button aria-label="Scroll back to top" class="clean-btn theme-back-to-top-button backToTopButton_sjWU" type="button"></button><div class="docRoot_UBD9"><aside class="theme-doc-sidebar-container docSidebarContainer_YfHR"><div class="sidebarViewport_aRkj"><div class="sidebar_njMd"><nav aria-label="Docs sidebar" class="menu thin-scrollbar menu_SIkG"><ul class="theme-doc-sidebar-menu menu__list"><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="true" href="/overview.html">Getting Started with RxDB</a></div><ul style="display:block;overflow:visible;height:auto" class="menu__list"><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/overview.html">Overview</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/quickstart.html">🚀 Quickstart</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/install.html">Installation</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/dev-mode.html">Development Mode</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/tutorials/typescript.html">TypeScript Setup</a></li></ul></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="true" href="/rx-database.html">Core Entities</a></div><ul style="display:block;overflow:visible;height:auto" class="menu__list"><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/rx-database.html">RxDatabase</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/rx-schema.html">RxSchema</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/rx-collection.html">RxCollection</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/rx-document.html">RxDocument</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/rx-query.html">RxQuery</a></li></ul></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/rx-storage.html">💾 Storages</a></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/schema-validation.html">Storage Wrappers</a></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/replication.html">🔄 Replication</a></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/rx-server.html">Server</a></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/transactions-conflicts-revisions.html">How RxDB works</a></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/migration-schema.html">Advanced Features</a></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--sublist-caret menu__link--active" role="button" aria-expanded="true" href="/rx-storage-performance.html">Performance</a></div><ul style="display:block;overflow:visible;height:auto" class="menu__list"><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/rx-storage-performance.html">RxStorage Performance</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/nosql-performance-tips.html">NoSQL Performance Tips</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link menu__link--active" aria-current="page" tabindex="0" href="/slow-indexeddb.html">Slow IndexedDB</a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.html">LocalStorage vs. IndexedDB vs. Cookies vs. OPFS vs. WASM-SQLite</a></li></ul></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/releases/16.0.0.html">Releases</a></div></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/offline-first.html">Articles</a></div></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-1 menu__list-item"><a class="menu__link" href="/contribution.html">Contribute & Innovate with RxDB</a></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="false" href="/consulting/">Contact</a></div></li></ul></nav></div></div></aside><main class="docMainContainer_TBSr"><div class="container padding-top--md padding-bottom--lg"><div class="row"><div class="col docItemCol_VOVn"><div class="docItemContainer_Djhp"><article><nav class="theme-doc-breadcrumbs breadcrumbsContainer_Z_bl" aria-label="Breadcrumbs"><ul class="breadcrumbs" itemscope="" itemtype="https://schema.org/BreadcrumbList"><li class="breadcrumbs__item"><a aria-label="Home page" class="breadcrumbs__link" href="/"><svg viewBox="0 0 24 24" class="breadcrumbHomeIcon_YNFT"><path d="M10 19v-5h4v5c0 .55.45 1 1 1h3c.55 0 1-.45 1-1v-7h1.7c.46 0 .68-.57.33-.87L12.67 3.6c-.38-.34-.96-.34-1.34 0l-8.36 7.53c-.34.3-.13.87.33.87H5v7c0 .55.45 1 1 1h3c.55 0 1-.45 1-1z" fill="currentColor"></path></svg></a></li><li class="breadcrumbs__item"><span class="breadcrumbs__link">Performance</span><meta itemprop="position" content="1"></li><li itemscope="" itemprop="itemListElement" itemtype="https://schema.org/ListItem" class="breadcrumbs__item breadcrumbs__item--active"><span class="breadcrumbs__link" itemprop="name">Slow IndexedDB</span><meta itemprop="position" content="2"></li></ul></nav><div class="tocCollapsible_ETCw theme-doc-toc-mobile tocMobile_ITEo"><button type="button" class="clean-btn tocCollapsibleButton_TO0P">On this page</button></div><div class="theme-doc-markdown markdown"><header><h1>Why IndexedDB is slow and what to use instead</h1></header>
<p>So you have a JavaScript web application that needs to store data at the client side, either to make it <a href="/offline-first.html">offline usable</a>, just for caching purposes or for other reasons.</p>
<p>For <a href="/articles/browser-database.html">in-browser data storage</a>, you have some options:</p>
<ul>
<li><strong>Cookies</strong> are sent with each HTTP request, so you cannot store more than a few strings in them.</li>
<li><strong>WebSQL</strong> <a href="https://hacks.mozilla.org/2010/06/beyond-html5-database-apis-and-the-road-to-indexeddb/" target="_blank" rel="noopener noreferrer">is deprecated</a> because it never was a real standard and turning it into a standard would have been too difficult.</li>
<li><a href="/articles/localstorage.html">LocalStorage</a> is a synchronous API over asynchronous IO-access. Storing and reading data can fully block the JavaScript process so you cannot use LocalStorage for more than few simple key-value pairs.</li>
<li>The <strong>FileSystem API</strong> could be used to store plain binary files, but it is <a href="https://caniuse.com/filesystem" target="_blank" rel="noopener noreferrer">only supported in chrome</a> for now.</li>
<li><strong>IndexedDB</strong> is an indexed key-object database. It can store json data and iterate over its indexes. It is <a href="https://caniuse.com/indexeddb" target="_blank" rel="noopener noreferrer">widely supported</a> and stable.</li>
</ul>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>UPDATE April 2023</div><div class="admonitionContent_BuS1"><p>Since beginning of 2023, all modern browsers ship the <strong>File System Access API</strong> which allows to persistently store data in the browser with a way better performance. For <a href="https://rxdb.info/" target="_blank" rel="noopener noreferrer">RxDB</a> you can use the <a href="/rx-storage-opfs.html">OPFS RxStorage</a> to get about 4x performance improvement compared to IndexedDB.</p><center><a href="https://rxdb.info/"><img src="./files/logo/rxdb_javascript_database.svg" alt="IndexedDB Database" width="250"></a></center></div></div>
<p>It becomes clear that the only way to go is IndexedDB. You start developing your app and everything goes fine.
But as soon as your app gets bigger, more complex or just handles more data, you might notice something. <strong>IndexedDB is slow</strong>. Not slow like a database on a cheap server, <strong>even slower</strong>! Inserting a few hundred documents can take up several seconds. Time which can be critical for a fast page load. Even sending data over the internet to the backend can be faster than storing it inside of an IndexedDB database.</p>
<blockquote>
<p>Transactions vs Throughput</p>
</blockquote>
<p>So before we start complaining, lets analyze what exactly is slow. When you run tests on Nolans <a href="http://nolanlawson.github.io/database-comparison/" target="_blank" rel="noopener noreferrer">Browser Database Comparison</a> you can see that inserting 1k documents into IndexedDB takes about 80 milliseconds, 0.08ms per document. This is not really slow. It is quite fast and it is very unlikely that you want to store that many document at the same time at the client side. But the key point here is that all these documents get written in a <code>single transaction</code>.</p>
<p>I forked the comparison tool <a href="https://pubkey.github.io/client-side-databases/database-comparison/index.html" target="_blank" rel="noopener noreferrer">here</a> and changed it to use one transaction per document write. And there we have it. Inserting 1k documents with one transaction per write, takes about 2 seconds. Interestingly if we increase the document size to be 100x bigger, it still takes about the same time to store them. This makes clear that the limiting factor to IndexedDB performance is the transaction handling, not the data throughput.</p>
<p align="center"><img src="./files/indexeddb-transaction-throughput.png" alt="IndexedDB transaction throughput" width="700"></p>
<p>To fix your IndexedDB performance problems you have to make sure to use as less data transfers/transactions as possible.
Sometimes this is easy, as instead of iterating over a documents list and calling single inserts, with RxDB you could use the <a href="https://rxdb.info/rx-collection.html#bulkinsert" target="_blank" rel="noopener noreferrer">bulk methods</a> to store many document at once.
But most of the time is not so easy. Your user clicks around, data gets replicated from the backend, another browser tab writes data. All these things can happen at random time and you cannot crunch all that data in a single transaction.</p>
<p>Another solution is to just not care about performance at all. In a few releases the browser vendors will have optimized IndexedDB and everything is fast again. Well, IndexedDB was slow <a href="https://www.researchgate.net/publication/281065948_Performance_Testing_and_Comparison_of_Client_Side_Databases_Versus_Server_Side" target="_blank" rel="noopener noreferrer">in 2013</a> and it is still slow today. If this trend continues, it will still be slow in a few years from now. Waiting is not an option. The chromium devs made <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1025456#c15" target="_blank" rel="noopener noreferrer">a statement</a> to focus on optimizing read performance, not write performance.</p>
<p>Switching to WebSQL (even if it is deprecated) is also not an option because, like <a href="https://pubkey.github.io/client-side-databases/database-comparison/index.html" target="_blank" rel="noopener noreferrer">the comparison tool shows</a>, it has even slower transactions.</p>
<p>So you need a way to <strong>make IndexedDB faster</strong>. In the following I lay out some performance optimizations than can be made to have faster reads and writes in IndexedDB.</p>
<p><strong>HINT:</strong> You can reproduce all performance tests <a href="https://github.com/pubkey/indexeddb-performance-tests" target="_blank" rel="noopener noreferrer">in this repo</a>. In all tests we work on a dataset of 40000 <code>human</code> documents with a random <code>age</code> between <code>1</code> and <code>100</code>.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="batched-cursor">Batched Cursor<a href="#batched-cursor" class="hash-link" aria-label="Direct link to Batched Cursor" title="Direct link to Batched Cursor"></a></h2>
<p>With <a href="https://caniuse.com/indexeddb2" target="_blank" rel="noopener noreferrer">IndexedDB 2.0</a>, new methods were introduced which can be utilized to improve performance. With the <code>getAll()</code> method, a faster alternative to the old <code>openCursor()</code> can be created which improves performance when reading data from the IndexedDB store.</p>
<p>Lets say we want to query all user documents that have an <code>age</code> greater than <code>25</code> out of the store.
To implement a fast batched cursor that only needs calls to <code>getAll()</code> and not to <code>getAllKeys()</code>, we first need to create an <code>age</code> index that contains the primary <code>id</code> as last field.</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">myIndexedDBObjectStore</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">createIndex</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'age-index'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'age'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'id'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>This is required because the <code>age</code> field is not unique, and we need a way to checkpoint the last returned batch so we can continue from there in the next call to <code>getAll()</code>.</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> maxAge </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">25</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> result </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> tx</span><span class="token operator">:</span><span class="token plain"> IDBTransaction </span><span class="token operator">=</span><span class="token plain"> db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">transaction</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">storeName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'readonly'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token constant" style="color:rgb(189, 147, 249)">TRANSACTION_SETTINGS</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> store </span><span class="token operator">=</span><span class="token plain"> tx</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">objectStore</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">storeName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> index </span><span class="token operator">=</span><span class="token plain"> store</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">index</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'age-index'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> lastDoc</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> done </span><span class="token operator">=</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token doc-comment comment" style="color:rgb(98, 114, 164)">/**</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> * Run the batched cursor until all results are retrieved</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> * or the end of the index is reached.</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">while</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">done </span><span class="token operator">===</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name builtin" style="color:rgb(189, 147, 249)">Promise</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">res</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> rej</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">=></span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> range </span><span class="token operator">=</span><span class="token plain"> IDBKeyRange</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">bound</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token doc-comment comment" style="color:rgb(98, 114, 164)">/**</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> * If we have a previous document as checkpoint,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> * we have to continue from it's age and id values.</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token doc-comment comment" style="color:rgb(98, 114, 164)"> */</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> lastDoc </span><span class="token operator">?</span><span class="token plain"> lastDoc</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">age </span><span class="token operator">:</span><span class="token plain"> </span><span class="token operator">-</span><span class="token number">Infinity</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> lastDoc </span><span class="token operator">?</span><span class="token plain"> lastDoc</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">id </span><span class="token operator">:</span><span class="token plain"> </span><span class="token operator">-</span><span class="token number">Infinity</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> maxAge </span><span class="token operator">+</span><span class="token plain"> </span><span class="token number">0.00000001</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> String</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">fromCharCode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">65535</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token boolean">true</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token boolean">false</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> openCursorRequest </span><span class="token operator">=</span><span class="token plain"> index</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getAll</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">range</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> batchSize</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> openCursorRequest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function-variable function" style="color:rgb(80, 250, 123)">onerror</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> err </span><span class="token operator">=></span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">rej</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">err</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> openCursorRequest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function-variable function" style="color:rgb(80, 250, 123)">onsuccess</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> e </span><span class="token operator">=></span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> subResult</span><span class="token operator">:</span><span class="token plain"> TestDocument</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> e</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">target</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> lastDoc </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">lastOfArray</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">subResult</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">subResult</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">length </span><span class="token operator">===</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> done </span><span class="token operator">=</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> result </span><span class="token operator">=</span><span class="token plain"> result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">concat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">subResult</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">res</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin" style="color:rgb(189, 147, 249)">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">dir</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p align="center"><img src="./files/indexeddb-batched-cursor.png" alt="IndexedDB batched cursor" width="100%"></p>
<p>As the performance test results show, using a batched cursor can give a huge improvement. Interestingly choosing a high batch size is important. When you known that all results of a given <code>IDBKeyRange</code> are needed, you should not set a batch size at all and just directly query all documents via <code>getAll()</code>.</p>
<p>RxDB uses batched cursors in the <a href="/rx-storage-indexeddb.html">IndexedDB RxStorage</a>.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="indexeddb-sharding">IndexedDB Sharding<a href="#indexeddb-sharding" class="hash-link" aria-label="Direct link to IndexedDB Sharding" title="Direct link to IndexedDB Sharding"></a></h2>
<p>Sharding is a technique, normally used in server side databases, where the database is partitioned horizontally. Instead of storing all documents at one table/collection, the documents are split into so called <strong>shards</strong> and each shard is stored on one table/collection. This is done in server side architectures to spread the load between multiple physical servers which <strong>increases scalability</strong>.</p>
<p>When you use IndexedDB in a browser, there is of course no way to split the load between the client and other servers. But you can still benefit from sharding. Partitioning the documents horizontally into <strong>multiple IndexedDB stores</strong>, has shown to have a big performance improvement in write- and read operations while only increasing initial pageload slightly.</p>
<p align="center"><img src="./files/indexeddb-sharding-performance.png" alt="IndexedDB sharding performance" width="100%"></p>
<p>As shown in the performance test results, sharding should always be done by <code>IDBObjectStore</code> and not by database. Running a batched cursor over the whole dataset with 10 store shards in parallel is about <strong>28% faster</strong> then running it over a single store. Initialization time increases minimal from <code>9</code> to <code>17</code> milliseconds.
Getting a quarter of the dataset by batched iterating over an index, is even <strong>43%</strong> faster with sharding then when a single store is queried.</p>
<p>As downside, getting 10k documents by their id is slower when it has to run over the shards.
Also it can be much effort to recombined the results from the different shards into the required query result. When a query without a limit is done, the sharding method might cause a data load huge overhead.</p>
<p>Sharding can be used with RxDB with the <a href="/rx-storage-sharding.html">Sharding Plugin</a>.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="custom-indexes">Custom Indexes<a href="#custom-indexes" class="hash-link" aria-label="Direct link to Custom Indexes" title="Direct link to Custom Indexes"></a></h2>
<p>Indexes improve the query performance of IndexedDB significant. Instead of fetching all data from the storage when you search for a subset of it, you can iterate over the index and stop iterating when all relevant data has been found.</p>
<p>For example to query for all user documents that have an <code>age</code> greater than <code>25</code>, you would create an <code>age+id</code> index.
To be able to run a batched cursor over the index, we always need our primary key (<code>id</code>) as the last index field.</p>
<p>Instead of doing this, you can use a <code>custom index</code> which can improve the performance. The custom index runs over a helper field <code>ageIdCustomIndex</code> which is added to each document on write. Our index now only contains a single <code>string</code> field instead of two (age-<code>number</code> and id-<code>string</code>).</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// On document insert add the ageIdCustomIndex field.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> idMaxLength </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">20</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// must be known to craft a custom index</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">docData</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">ageIdCustomIndex </span><span class="token operator">=</span><span class="token plain"> docData</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">age </span><span class="token operator">+</span><span class="token plain"> docData</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">padStart</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">idMaxLength</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">' '</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">store</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">put</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">docData</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// ...</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// normal index</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">myIndexedDBObjectStore</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">createIndex</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'age-index'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'age'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'id'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// custom index</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">myIndexedDBObjectStore</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">createIndex</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'age-index-custom'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'ageIdCustomIndex'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>To iterate over the index, you also use a custom crafted keyrange, depending on the last batched cursor checkpoint. Therefore the <code>maxLength</code> of <code>id</code> must be known.</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// keyrange for normal index</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> range </span><span class="token operator">=</span><span class="token plain"> IDBKeyRange</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">bound</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">25</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">''</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">Infinity</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">Infinity</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token boolean">true</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token boolean">false</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// keyrange for custom index</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> range </span><span class="token operator">=</span><span class="token plain"> IDBKeyRange</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">bound</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// combine both values to a single string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token number">25</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">''</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">padStart</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">idMaxLength</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">' '</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token number">Infinity</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token boolean">true</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token boolean">false</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p align="center"><img src="./files/indexeddb-custom-index.png" alt="IndexedDB custom index" width="700"></p>
<p>As shown, using a custom index can further improve the performance of running a batched cursor by about <code>10%</code>.</p>
<p>Another big benefit of using custom indexes, is that you can also encode <code>boolean</code> values in them, which <a href="https://github.com/w3c/IndexedDB/issues/76" target="_blank" rel="noopener noreferrer">cannot be done</a> with normal IndexedDB indexes.</p>
<p>RxDB uses custom indexes in the <a href="/rx-storage-indexeddb.html">IndexedDB RxStorage</a>.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="relaxed-durability">Relaxed durability<a href="#relaxed-durability" class="hash-link" aria-label="Direct link to Relaxed durability" title="Direct link to Relaxed durability"></a></h2>
<p>Chromium based browsers allow to set <a href="https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction/durability" target="_blank" rel="noopener noreferrer">durability</a> to <code>relaxed</code> when creating an IndexedDB transaction. Which runs the transaction in a less secure durability mode, which can improve the performance.</p>
<blockquote>
<p>The user agent may consider that the transaction has successfully committed as soon as all outstanding changes have been written to the operating system, without subsequent verification.</p>
</blockquote>
<p>As shown <a href="https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/" target="_blank" rel="noopener noreferrer">here</a>, using the relaxed durability mode can improve performance slightly. The best performance improvement could be measured when many small transactions have to be run. Less, bigger transaction do not benefit that much.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="explicit-transaction-commits">Explicit transaction commits<a href="#explicit-transaction-commits" class="hash-link" aria-label="Direct link to Explicit transaction commits" title="Direct link to Explicit transaction commits"></a></h2>
<p>By explicitly committing a transaction, another slight performance improvement can be achieved. Instead of waiting for the browser to commit an open transaction, we call the <code>commit()</code> method to explicitly close it.</p>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// .commit() is not available on all browsers, so first check if it exists.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">transaction</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">commit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> transaction</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">commit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The improvement of this technique is minimal, but observable as <a href="https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/" target="_blank" rel="noopener noreferrer">these tests</a> show.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="in-memory-on-top-of-indexeddb">In-Memory on top of IndexedDB<a href="#in-memory-on-top-of-indexeddb" class="hash-link" aria-label="Direct link to In-Memory on top of IndexedDB" title="Direct link to In-Memory on top of IndexedDB"></a></h2>
<p>To prevent transaction handling and to fix the performance problems, we need to stop using IndexedDB as a database. Instead all data is loaded into the memory on the initial page load. Here all reads and writes happen in memory which is about 100x faster. Only some time after a write occurred, the memory state is persisted into IndexedDB with a <strong>single write transaction</strong>. In this scenario IndexedDB is used as a filesystem, not as a database.</p>
<p>There are some libraries that already do that:</p>
<ul>
<li>LokiJS with the <a href="https://techfort.github.io/LokiJS/LokiIndexedAdapter.html" target="_blank" rel="noopener noreferrer">IndexedDB Adapter</a></li>
<li><a href="https://github.com/jlongster/absurd-sql" target="_blank" rel="noopener noreferrer">Absurd-SQL</a></li>
<li>SQL.js with the <a href="https://emscripten.org/docs/api_reference/Filesystem-API.html#filesystem-api-idbfs" target="_blank" rel="noopener noreferrer">empscripten Filesystem API</a></li>
<li><a href="https://duckdb.org/2021/10/29/duckdb-wasm.html" target="_blank" rel="noopener noreferrer">DuckDB Wasm</a></li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="in-memory-persistence">In-Memory: Persistence<a href="#in-memory-persistence" class="hash-link" aria-label="Direct link to In-Memory: Persistence" title="Direct link to In-Memory: Persistence"></a></h3>
<p>One downside of not directly using IndexedDB, is that your data is not persistent all the time. And when the JavaScript process exists without having persisted to IndexedDB, data can be lost. To prevent this from happening, we have to ensure that the in-memory state is written down to the disc. One point is make persisting as fast as possible. LokiJS for example has the <code>incremental-indexeddb-adapter</code> which only saves new writes to the disc instead of persisting the whole state. Another point is to run the persisting at the correct point in time. For example the RxDB <a href="https://rxdb.info/rx-storage-lokijs.html" target="_blank" rel="noopener noreferrer">LokiJS storage</a> persists in the following situations:</p>
<ul>
<li>When the database is idle and no write or query is running. In that time we can persist the state if any new writes appeared before.</li>
<li>When the <code>window</code> fires the <a href="https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload" target="_blank" rel="noopener noreferrer">beforeunload event</a> we can assume that the JavaScript process is exited any moment and we have to persist the state. After <code>beforeunload</code> there are several seconds time which are sufficient to store all new changes. This has shown to work quite reliable.</li>
</ul>
<p>The only missing event that can happen is when the browser exists unexpectedly like when it crashes or when the power of the computer is shut of.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="in-memory-multi-tab-support">In-Memory: Multi Tab Support<a href="#in-memory-multi-tab-support" class="hash-link" aria-label="Direct link to In-Memory: Multi Tab Support" title="Direct link to In-Memory: Multi Tab Support"></a></h3>
<p>One big difference between a web application and a 'normal' app, is that your users can use the app in multiple browser tabs at the same time. But when you have all database state in memory and only periodically write it to disc, multiple browser tabs could overwrite each other and you would loose data. This might not be a problem when you rely on a client-server replication, because the lost data might already be replicated with the backend and therefore with the other tabs. But this would not work when the client is offline.</p>
<p>The ideal way to solve that problem, is to use a <a href="https://developer.mozilla.org/en/docs/Web/API/SharedWorker" target="_blank" rel="noopener noreferrer">SharedWorker</a>. A <a href="/rx-storage-shared-worker.html">SharedWorker</a> is like a <a href="https://developer.mozilla.org/en/docs/Web/API/Web_Workers_API" target="_blank" rel="noopener noreferrer">WebWorker</a> that runs its own JavaScript process only that the SharedWorker is shared between multiple contexts. You could create the database in the SharedWorker and then all browser tabs could request the Worker for data instead of having their own database. But unfortunately the SharedWorker API does <a href="https://caniuse.com/sharedworkers" target="_blank" rel="noopener noreferrer">not work</a> in all browsers. Safari <a href="https://bugs.webkit.org/show_bug.cgi?id=140344" target="_blank" rel="noopener noreferrer">dropped</a> its support and InternetExplorer or Android Chrome, never adopted it. Also it cannot be polyfilled. <strong>UPDATE:</strong> <a href="https://developer.apple.com/safari/technology-preview/release-notes/" target="_blank" rel="noopener noreferrer">Apple added SharedWorkers back in Safari 142</a></p>
<p>Instead, we could use the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API" target="_blank" rel="noopener noreferrer">BroadcastChannel API</a> to communicate between tabs and then apply a <a href="https://github.com/pubkey/broadcast-channel#using-the-leaderelection" target="_blank" rel="noopener noreferrer">leader election</a> between them. The <a href="/leader-election.html">leader election</a> ensures that, no matter how many tabs are open, always one tab is the <code>Leader</code>.</p>
<p align="center"><img src="./files/leader-election.gif" alt="Leader Election" width="500"></p>
<p>The disadvantage is that the leader election process takes some time on the initial page load (about 150 milliseconds). Also the leader election can break when a JavaScript process is fully blocked for a longer time. When this happens, a good way is to just reload the browser tab to restart the election process.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="further-read">Further read<a href="#further-read" class="hash-link" aria-label="Direct link to Further read" title="Direct link to Further read"></a></h2>
<ul>
<li><a href="https://github.com/pubkey/client-side-databases" target="_blank" rel="noopener noreferrer">Offline First Database Comparison</a></li>
<li><a href="https://nolanlawson.com/2021/08/22/speeding-up-indexeddb-reads-and-writes/" target="_blank" rel="noopener noreferrer">Speeding up IndexedDB reads and writes</a></li>
<li><a href="https://hackaday.com/2021/08/24/sqlite-on-the-web-absurd-sql/" target="_blank" rel="noopener noreferrer">SQLITE ON THE WEB: ABSURD-SQL</a></li>
<li><a href="https://anita-app.com/blog/articles/sqlite-in-a-pwa-with-file-system-access-api.html" target="_blank" rel="noopener noreferrer">SQLite in a PWA with FileSystemAccessAPI</a></li>
<li><a href="https://ravendb.net/articles/re-why-indexeddb-is-slow-and-what-to-use-instead" target="_blank" rel="noopener noreferrer">Response to this article by Oren Eini</a></li>
</ul></div></article><nav class="pagination-nav docusaurus-mt-lg" aria-label="Docs pages"><a class="pagination-nav__link pagination-nav__link--prev" href="/nosql-performance-tips.html"><div class="pagination-nav__sublabel">Previous</div><div class="pagination-nav__label">NoSQL Performance Tips</div></a><a class="pagination-nav__link pagination-nav__link--next" href="/articles/localstorage-indexeddb-cookies-opfs-sqlite-wasm.html"><div class="pagination-nav__sublabel">Next</div><div class="pagination-nav__label">LocalStorage vs. IndexedDB vs. Cookies vs. OPFS vs. WASM-SQLite</div></a></nav></div></div><div class="col col--3"><div class="tableOfContents_bqdL thin-scrollbar theme-doc-toc-desktop"><ul class="table-of-contents table-of-contents__left-border"><li><a href="#batched-cursor" class="table-of-contents__link toc-highlight">Batched Cursor</a></li><li><a href="#indexeddb-sharding" class="table-of-contents__link toc-highlight">IndexedDB Sharding</a></li><li><a href="#custom-indexes" class="table-of-contents__link toc-highlight">Custom Indexes</a></li><li><a href="#relaxed-durability" class="table-of-contents__link toc-highlight">Relaxed durability</a></li><li><a href="#explicit-transaction-commits" class="table-of-contents__link toc-highlight">Explicit transaction commits</a></li><li><a href="#in-memory-on-top-of-indexeddb" class="table-of-contents__link toc-highlight">In-Memory on top of IndexedDB</a><ul><li><a href="#in-memory-persistence" class="table-of-contents__link toc-highlight">In-Memory: Persistence</a></li><li><a href="#in-memory-multi-tab-support" class="table-of-contents__link toc-highlight">In-Memory: Multi Tab Support</a></li></ul></li><li><a href="#further-read" class="table-of-contents__link toc-highlight">Further read</a></li></ul></div></div></div></div></main></div></div></div><div class="block footer"><div class="footer-block"><div class="footer-links"><span><a variant="text" href="/" class="footer-logo-button"><img src="/files/logo/logo.svg" alt="RxDB" loading="lazy"><div>RxDB</div></a><div class="footer-community-links"><a variant="text" href="/chat/" target="_blank"><img src="/img/community-links/discord-logo.svg" alt="RxDB Discord" loading="lazy"></a><a variant="text" href="/code/" target="_blank"><img src="/img/community-links/github-logo.svg" alt="RxDB Github" loading="lazy"></a><a variant="text" href="https://twitter.com/intent/user?screen_name=rxdbjs" target="_blank"><img src="/img/community-links/x-logo.svg" alt="RxDB Twitter" loading="lazy"></a><a variant="text" href="https://www.linkedin.com/company/rxdb" target="_blank"><img src="/img/community-links/linkedin-logo.svg" alt="RxDB LinkedIn" loading="lazy"></a><a variant="text" href="https://stackoverflow.com/questions/tagged/rxdb" target="_blank"><img src="/img/community-links/stack-overflow-logo.svg" alt="RxDB Stack Overflow" loading="lazy"></a></div></span><div class="footer-nav-links"><a variant="text" href="/premium/" target="">Premium</a><a variant="text" href="/consulting/" target="">Support</a><a variant="text" href="/overview.html" target="">Documentation</a><a variant="text" href="/chat/" target="_blank">Discord</a><a variant="text" href="/code/" target="_blank">Github</a><a variant="text" href="https://twitter.com/intent/user?screen_name=rxdbjs" target="_blank">Twitter</a><a variant="text" href="https://www.linkedin.com/company/rxdb" target="_blank">LinkedIn</a></div></div><div class="footer-policy"><div><a variant="text" href="/legal-notice/" target="_blank">Legal Notice</a></div><span class="footer-rights">© 2025 RxDB. All rights reserved.</span></div><img class="footer-img desktop-img" src="/img/footer-column.svg" alt="columns" loading="lazy"></div></div><div class="call-to-action-popup"><div class="close"><div class="text">✕</div></div></div></div>
</body>
</html>