Comment

Andrew Sutherland

Yeah, my only real concern with localStorage these days is excess use of it because of that memory cost and the potential for the read costs to add up to a real problem. On non-SSD storage, if you had multiple megabytes of data in there, especially sharded over a high number of keys, given our SQLite page size (32k everywhere but b2g), you could get into a large number of disk seeks.

I do believe that localStorage hits a sweet spot for helping get a populated first render to the user. Anything above the fold, really. For the Firefox OS email app we use localStorage to cache the HTML of the page (more details at http://www.visophyte.org/blog/2015/04/30/talk-script-firefox-os-email-performance-strategies/), which is something that we can hopefully replace with the new cache API at some point. And in a desktop-related effort, I use it for persisting splitter positions too (https://clicky.visophyte.org/files/screenshots/20150830-222533.png).

But once you get to anything that you'd call a database where there's a growth factor expected, I think it's preferable to move to using something IndexedDB-backed. Although obviously, there are always engineering trade-offs to be made. For a tool used by a limited user-base used on desktop where they probably have oodles of RAM, it's not a big deal. The bigger risk when working on Mozilla affiliated projects is the hazard where people replicate your implementation choices without doing a trade-off analysis of their own. (If localStorage is good enough for Mozilla, etc. ;)

Parent comment

Peter Bengtsson

Thank you! That's very insightful. I get a feeling that the "theory" is that it might cause yank on the main thread but it's realistically just very unlikely to happen. Especially if you follow best practice of putting the javascript loading late. See http://www.peterbe.com/localvsxhr/ This experiment is actually about IDB vs. localStorage vs. XHR but one of the outcomes is that I've managed to collect results for a lot of visitors when a .js file has these for the first couple of lines: var a = performance.now(); localStorage.getItem('anything'); var b = performance.now(); That's code that is put at the bottom of the HTML document and loaded after loading some jquery. The results is that the "Time to Boot" median is extremely small. If you look carefully, there is a max number there of 11.72ms. That number I got when I booted a custom build of Nightly, that Boris Zbarsky compiled for me. It was regular Nightly but with the code that pre-heats the disk -> localStorage on load commented out. In other words, even without that code, it's only 11.72ms. What's really interesting is that the 500Kb has to be loaded with that domain ALL the time. Even though it's not used. It might not matter a great deal on a fast desktop but it certainly matters for lower power devices. Especially if it's 500Kb you don't need to get the main task done on that app.

Replies

Peter Bengtsson

I love this "debate"! These are really interesting questions to solve.

We're not disagreeing on anything but one argument I'd like to make is that if you look at http://www.peterbe.com/plog/lovefield-ajax-proxy where I used a wrapper over IDB called Lovefield. It's a similar pattern, load from Lovefield first, then wait for AJAX to get more fresh data. What I found was that loading stuff from Lovefield takes 400-500ms EVERY time. And it only takes 1-2ms with localStorage. (granted the point of something like Lovefield is not to select EVERY record every time but we'll let that slide). So my question is, what's it doing to my CPU/RAM during that half second? Whatever it is, even if it's off the main thread, is bound to cause slowdown on the main thread and strain on the resources.

I.e. localStorage is like a buffalo but it gets the job done. Perhaps that ultimately wins despite it's shortcomings.

Andrew Sutherland

Yeah, 400-500ms is really bad, although I should note I just tried http://www.peterbe.com/ajaxornot/view7a several times in a row and my times on e10s nightly the times reported were largely in the 100ms-150ms time range. (I did get one outlier at 207ms.) Since you're loading all the rows, it's possible the improvement came from https://bugzilla.mozilla.org/show_bug.cgi?id=1168606 landing which implements pre-fetching.

For FxOS we in general have been using the non-standard mozGetAll method to fetch things in batches, which avoids wasteful roundtrips, etc. (They really could get out of hand... IDB was erring on the side of not letting people footgun too much. And people did. Many a tale of woe can be told about overuse of mozGetAll by FxOS apps/custom web APIs!)

There is definitely a non-trivial overhead in the act of opening an IDB database. It looks like your call to connect() is part of your timed logic, and per https://github.com/google/lovefield/blob/master/docs/spec/03_life_of_db.md this would seem to involve the database opening, so I expect that could have a lot to do with it. It would be interesting to split the numbers out to separate the connect(), although obviously that number is very important/significant to user responsiveness at startup!

And yes, absolutely there is a non-trivial cost to spinning up/and opening an IndexedDB database in terms of disk I/O etc. IDB is pretty good about spinning things back down, but it wouldn't surprise me if at 500K of data local storage was better on balance, especially if you're storing the data in only one or a few keys.

Peter Bengtsson

For what it's worth, the 400-500ms I can't get now any more either. Hovering around 190-230ms in my laptop.

It's perhaps silly to compare pure localStorage with Lovefield + connect() + IDB all in one lump but the pragmatist in me is only interested in "getting the data out" :)

Peter Bengtsson

I suspect a lot comes down to how localStorage is capped. I now understand that the reason it's capped is because it's to prevent you from slugging around too much stuff in RAM. So, I'd welcome a nice cap that be easy to business-logic around. Would be nice if the browser vendor could help tell web developers what's too much and too dangerous for the greater benefit of the browser experience.