10

I am storing a large amount of small objects in IndexedDB. I would like to give the user the ability to export one of the object stores to a file that they can "download".

I have read this blog article. Which describes reading the data, JSON.stringifying the data, encoding it with encodeURIComponent, and placing it as the href for a link they can use to download the data. Something like this:

var transaction = db.transaction([objectstore], "readonly");
var content = [];
var objectStore = transaction.objectStore(objectstore);

objectStore.openCursor().onsuccess = function(event) {
    var cursor = event.target.result;
    if (cursor) {
        content.push({key:cursor.key,value:cursor.value});
        cursor.continue();
    }
}; 

transaction.oncomplete = function(event) {
    var serializedData = JSON.stringify(dataToStore);
    link.attr("href",'data:Application/octet-stream,'+encodeURIComponent(serializedData));
    link.trigger("click");
};

That is fine, except the object store will have millions of records and I don't feel that this will be performant enough. Is there a way to more directly allow the user to save an object store off as a file (in a way I can import again via the webpage).


Edit From some notes in the comments I rewrote a little of how this works, in order to get a little more juice out of it. The new code is similar to:

var transaction = db.transaction([objectstore], "readonly");
var objectStore = transaction.objectStore(objectstore);

objectStore.getAll().onsuccess = function(evt) {
    var url = window.URL.createObjectURL(new Blob(evt.target.results, {'type': 'application/octet-stream'}));
    link.attr('href', url);
    link.trigger('click');
};

Which will give me results like:

  • 10k records, 975.87ms average export time
  • 100k records, 5,850.10ms average export time
  • 1mil records, 56,681.00ms average export time

As you can see 1 million records takes about a minute to export. Is there any better way to be doing this? (I also tried using a cursor instead of .getAll(), but cursors are slower)

Josh
  • 17,834
  • 7
  • 50
  • 68
Chad
  • 19,219
  • 4
  • 50
  • 73
  • I want to say, "use localStorage for the resource currently viewed (and for saving/loading), and put the DB in a WebWorker," but I feel like even that wouldn't do much for performance. – Jeffrey Sweeney Jan 21 '13 at 13:24
  • Yeah I thought about doing the loading and serializing from the webworker, but it still has to be serialized by the browser to come back; which I think will take the same performance hit. As for local storage, I don't think putting 3 million+ objects in there is a good idea... – Chad Jan 21 '13 at 13:25
  • have you try it? getting out of 3 million objects from IndexedDB should take few second only. Building file through `window.URL.createObjectURL(new Blob(contents, {'type': MIME_TYPE}))` should be fine. – Kyaw Tun Jan 21 '13 at 14:51
  • I'm playing with it right now, I wasn't aware of the `window.URL.createObjectURL(new Blob());` method, which may answer my question as that seems about twice as fast as using the `encodeURI` method. early benches are saying about 6 seconds for 100k entries. – Chad Jan 21 '13 at 18:56
  • @KyawTun Looks like with 1 million records it takes about one minute on average to export to a file. Much fast than using `encodeURI` but still pretty slow... – Chad Jan 21 '13 at 19:44
  • I see. I think that is how far you can go. For compatibility, you will use cursor and push to `ArrayBuffer`. – Kyaw Tun Jan 22 '13 at 02:50
  • I think you are right, post it as an answer and I will accept it – Chad Jan 22 '13 at 12:36

1 Answers1

1

IDBObjectStore.getAll is not part of the IndexedDB standard and it's using a cursor under the covers.

Note: Mozilla has also implemented getAll() to handle this case (and getAllKeys(), which is currently hidden behind the dom.indexedDB.experimental preference in about:config). these aren't part of the IndexedDB standard, so may disappear in the future. We've included them because we think they're useful. The following code does precisely the same thing as above:

objectStore.getAll().onsuccess = function(event) {
  alert("Got all customers: " + event.target.result);
};

There is a performance cost associated with looking at the value property of a cursor, because the object is created lazily. When you use getAll() for example, Gecko must create all the objects at once. If you're just interested in looking at each of the keys, for instance, it is much more efficient to use a cursor than to use getAll(). If you're trying to get an array of all the objects in an object store, though, use getAll().

The only way to fetch records where you don't know the key is to use a cursor. So no I don't think there's a better way. But you need to ask yourself if this is faster than fetching the records from a server.

denov
  • 11,180
  • 2
  • 27
  • 43
  • True, but that doesn't answer the original question of what the fastest way to accomplish what I want to do is. – Chad Feb 21 '14 at 05:12
  • I believe there is only one way. I don't see any other options in the API. – denov Feb 25 '14 at 00:58
  • 2
    Correct, this is an offline application so there is no server. `getAll` was the solution I was using, I was really looking to see if there was something else. – Chad Feb 25 '14 at 04:44
  • if there's no server how are you going to protect your users from clearing the cache or anything else that would cause data loss? – denov Feb 25 '14 at 21:18
  • 1
    Again, this is an offline application. The data stored is for the user alone, they can do whatever they want with it including delete it. – Chad Feb 25 '14 at 21:34
  • yes, I understand. I'm not sure if only storing the data in the browser is the best idea. I think it could be too easy for the user to screw up and lose it all. And only being in the browser doesn't allow them to change machines or browsers. just my .02 – denov Feb 26 '14 at 01:35
  • 1
    Fortunately the question isn't about the system as a whole, since I didn't give nearly enough information about what it does for anyone to discuss it. I just am asking if there is a method of serialization that would be faster than what I have; and I am leaving the question open in case a solution comes around. – Chad Feb 26 '14 at 03:32
  • @Chad any luck there? I'm facing basically the same issue and, now, `getAll` don't even seen to exist anymore. So I've adapted it using `openCursor` but all I get is `[object object]` on the `blob`... :( – cregox Nov 30 '15 at 16:41
  • @Cawas No, unfortunately I didn't get much traction on this. If I were to do it again I would probably serialize my data as a TypedArray and just mnove that around; unfortunately the only way to offer it as a download is still `window.URL.createObjectURL()` which is incredibly slow :/ – Chad Nov 30 '15 at 17:47