Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should I open an IDBDatabase each time or keep one instance open?

I have a SPA application that will make multiple reads/writes to IndexedDB.

Opening the DB is an asynchronous operation with a callback:

var db; 
var request = window.indexedDB.open("MyDB", 2);

request.onupgradeneeded = function(event) { 
    // Upgrade to latest version...
}

request.onerror = function(event) { 
    // Uh oh...
}

request.onsuccess = function(event) {
    // DB open, now do something
    db = event.target.result;
};

There are two ways I can use this db instance:

  1. Keep a single db instance for the life of the page/SPA?
  2. Call db.close() once the current operation is done and open a new one on the next operation?

Are there pitfalls of either pattern? Does keeping the indexedDB open have any risks/issues? Is there an overhead/delay (past the possible upgrade) to each open action?

like image 623
Keith Avatar asked Nov 14 '16 16:11

Keith


People also ask

How long does IndexedDB last?

Safari will happily delete your IndexedDB database after 7 days of inactivity. Source: https://webkit.org/blog/10218/full-third-party-cookie-blocki...

What should I store in IndexedDB?

Another good use for IndexedDB is to store user-generated content, either as a temporary store before it is uploaded to the server or as a client-side cache of remote data - or, of course, both.

Is IndexedDB shared across tabs?

Data stored in indexedDB is available to all tabs from within the same origin.

Should I use IndexedDB?

You might use IndexedDB to store structured data that's unrelated to any data on the server. An example might be a calendar, a to-do list, or saved games that are played locally. In this case, the application is really a local one, and your web site is just the vehicle for delivering it.


2 Answers

I have found that opening a connection per operation does not substantially degrade performance. I have been running a local Chrome extension for over a year now that involves a ton of indexedDB operations and have analyzed its performance hundreds of times and have never witnessed opening a connection as a bottleneck. The bottlenecks come in doing things like not using an index properly or storing large blobs.

Basically, do not base your decision here on performance. It really isn't the issue in terms of connecting.

The issue is really the ergonomics of your code, how much you are fighting against the APIs, and how intuitive your code feels when you look at it, how understable you think the code is, how welcoming is it to fresh eyes (your own a month later, or someone else). This is very notable when dealing with the blocking issue, which is indirectly dealing with application modality.

My personal opinion is that if you are comfortable with writing async Javascript, use whatever method you prefer. If you struggle with async code, choosing to always open the connection will tend to avoid any issues. I would never recommend using a single global page-lifetime variable to someone who is newer to async code. You are also leaving the variable there for the lifetime of the page. On the other hand, if you find async trivial, and find the global db variable more amenable, by all means use it.

Edit - based on your comment I thought I would share some pseudocode of my personal preference:

function connect(name, version) {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open(name, version);
    request.onupgradeneeded = onupgradeneeded;
    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
    request.onblocked = () => console.warn('pending till unblocked');
  });
}

async foo(bar) {
  let conn;
  try {
    conn = await connect(DBNAME, DBVERSION);
    await storeBar(conn, bar);
  } finally {
    if(conn)
      conn.close();
  }
}

function storeBar(conn, bar) {
  return new Promise((resolve, reject) => {
    const tx = conn.transaction('store');
    const store = tx.objectStore('store');
    const request = store.put(bar);
    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
}

With async/await, there isn't too much friction in having the extra conn = await connect() line in your operational functions.

like image 69
Josh Avatar answered Sep 19 '22 04:09

Josh


Opening a connection each time is likely to be slower just because the browser is doing more work (e.g. it may need to read data from disk). Otherwise, no real down sides.

Since you mention upgrades, either pattern requires a different approach to the scenario where the user opens your page in another tab and it tries to open the database with a higher version (because it downloaded newer code form your server). Let's say the old tab was version 3 and the new tab is version 4.

In the one-connection-per-operation case you'll find that your open() on version 3 fails, because the other tab was able to upgrade to version 4. You can notice that the open failed with VersionError e.g. and inform the user that they need to refresh the page.

In the one-connection-per-page case your connection at version 3 will block the other tab. The v4 tab can respond to the "blocked" event on the request and let the user know that older tabs should be closed. Or the v3 tab can respond to the versionupgrade event on the connection and tell the user that it needs to be closed. Or both.

like image 41
Joshua Bell Avatar answered Sep 21 '22 04:09

Joshua Bell