Skip to content

Instantly share code, notes, and snippets.

@nolanlawson
Last active March 1, 2023 23:34
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nolanlawson/dc80e449079c2bc33170 to your computer and use it in GitHub Desktop.
Save nolanlawson/dc80e449079c2bc33170 to your computer and use it in GitHub Desktop.
Misconceptions about PouchDB

Misconceptions about PouchDB

A quick rant.

  1. PouchDB is slow, because it doesn't use bare-metal IndexedDB

OK, first off I want to point out the CanIUse table for IndexedDB. Go ahead, look at it. I'll wait.

There's a good reason we write PouchDB over a unified API of IndexedDB and WebSQL. You want libs that actually run in all the browsers, right? Unless you're happy to slap a "Looks best in Chrome" sticker on your website, you should.

But even if we lived in a world where IndexedDB was supported everywhere, it wouldn't hurt us much to support WebSQL. As it turns out, the lowest common denominator between IndexedDB and WebSQL is... IndexedDB. WebSQL is very high-level. Let's not forget that it has built-in full-text search support (!) and lets you write crazy queries with joins and subqueries and whatever you want. So if anything, our WebSQL adapter is being held back by our IndexedDB adapter, not vice-versa.

The only two cases where PouchDB falls short of a bare-metal IndexedDB approach is in two regards:

1) Maintaining two separate objectStores - one for revisions (by seq) and one for docs (by id). This is due to how PouchDB stores its rev trees, i.e. the thing it uses for syncing.

PouchDB's killer feature is sync, so it's kinda hard to justify throwing this out with the bathwater. But it does impact performance during reads (joining two objectStores together, which is slow in IndexedDB) and writes (writing two IndexedDB docs for every one user doc).

In practice, I don't see this as being a very big performance hit, but we are considering combining the two stores into one. Our current adapter implementation doesn't prohibit us from doing that.

2) Secondary indexes. This is one case where we chose to make LevelDB our lowest common denominator, and LevelDB doesn't have secondary index support. So unfortunately IndexedDB and WebSQL suffer, because we create our secondary indexes by building an entirely new PouchDB rather than using native secondary indexes.

The big benefits to this approach were:

  1. We could move quickly and write map/reduce (and soon pouchdb-find) without a lot of adapter-specific tweaking.
  2. Optimizing map/reduce has brought a lot of performance improvements to PouchDB core (since map/reduce is just a very elaborate use-case of PouchDB). As map/reduce gets faster, PouchDB core gets faster (especially auto-compaction mode).
  3. Map/reduce actually lives as a separate plugin, so it will be easy to swap out for pouchdb-find. Or hell, we could just make them both into optional plugins. Modularity FTW.

The downside is that, yeah, it is pretty slow if you have a big database, or you are emitting a lot of keys. For instance, a large pouchdb-quick-search database (which does an emit() for every token in every document) is about 100x slower than either a WebSQL database using built-in FTS indexes (~6 seconds) or an IndexedDB database using a multiEntry secondary index with Lunr for tokenization (~8 seconds in Chrome, ~13 in Firefox). (Discussion is here.)

100 is a big number, so in the future, we will definitely switch to native indexes. But it's worth acknowledging how far the current implementation has gotten us. I don't regret it at all, and for small databases or small indexes, users are unlikely to notice a performance hit.

  1. PouchDB is too heavyweight, 46KB is too much, I want something "lite"

OK, again I will draw your attention to the CanIUse table for IndexedDB. But that's not even telling the whole story: as it turns out, every browser implements IndexedDB incorrectly, to one degree or another.

Why am I pointing this out? Because that extra code you're downloading is code to work around odd bugs in Chrome, Safari, Firefox, IE, Android, iOS, PhantomJS... And yes, they all have bugs, and PouchDB has ways to work around them.

Furthermore, if you're worried about 46KB, then you're thinking about offline-first wrong. When used correctly, PouchDB allows you to avoid querying your backend altogether during regular user interactions, because you're talking to the local store and syncing in the background. That means zero latency, which means your users aren't sitting there staring at a loading spinner. You wanna talk about perf: well, you can't get much better than zero latency.

It's lots of little requests that kill performance, not a few big requests. so if you're bundling that 46KB into your initial payload, you'll be fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment