PouchDB vs. Couchbase Lite - a performance review

Client side databases with offline support are very important for mobile hybrid apps where performance matters. I’ve developed several apps with Cordova/Phonegap in the last months and always needed a database to save information on the client, that has offline support and allows replicating data back to my cloud database. Two databases came up whenever I was looking for a solution - PouchDB and Couchbase Lite Phonegap. PouchDB works entirely in the browser whereas Couchbase Lite works with native databases on iOS and Android through a JavaScript API.

Today I’d like to draw a comparison between them focusing on performance. How fast do they write data and how fast do they retrieve data? I chose four actions and did ten repetitions each

  • bulk add documents
  • get all documents
  • get document by id
  • query database via view

The code is on GitHub so you can run the tests for yourself. My tests were done on my MacBook Pro running Mavericks inside the iOS emulator. Therefore the exact values will be different depending on which hardware you use. The overall results and findings should still be the same though.

Bulk add documents

The first action is adding random documents to each database. The first run was with 10000 and the second with 25000 documents.

The code snippets look like this:

// add array of documents to PouchDB
function pouch_bulkAdd() {
  var max = 10000;
  var data = generate(max);
  console.time('PouchDB - bulk add');
  db.bulkDocs({docs: data}, function(err, res) {
    if (err) console.log(err);
    console.timeEnd('PouchDB - bulk add');
    // save one user for later query
    dummyId = res[5000].id;
  })
}
// add array of documents to Couchbase Lite
function couchbase_bulkAdd() {
  var max = 10000;
  var data = generate(max);
  console.time('Couchbase lite - bulk add');
  $.ajax({
    type: 'POST',
    url: globalUrl + 'performance/_bulk_docs',
    data: JSON.stringify({'docs': data}),
    contentType : 'application/json'
  })
  .done(function(res) {
    console.timeEnd('Couchbase lite - bulk add');
    dummyId = res[5000].id;
  });
}
# PouchDB (10000 docs) CB Lite (10000 docs) - PouchDB (25000 docs) CB Lite (25000 docs)
1 2214 ms 2459 ms - 5568 ms 6064 ms
2 2391 ms 2777 ms - 5561 ms 6583 ms
3 1532 ms 2966 ms - 5494 ms 6683 ms
4 1506 ms 3071 ms - 5805 ms 6723 ms
5 1539 ms 3022 ms - 5705 ms 6828 ms
6 1607 ms 3087 ms - 5746 ms 6769 ms
7 1596 ms 3141 ms - 5841 ms 6805 ms
8 1694 ms 3239 ms - 5676 ms 6887 ms
9 1600 ms 3245 ms - 5820 ms 6923 ms
10 1579 ms 3278 ms - 5873 ms 6886 ms
Ø 1725.8 ms 3028.5 ms - 5708.9 ms 6715.1 ms

bulk add documents

Get all documents

The second action was getting all documents from each database, again with 10000 documents and then with 25000 documents.

// get all documents from PouchDB
function pouch_allDocs() {
  console.time('PouchDB - all docs');
  db.allDocs(function(err, res) {
    if (err) log(err);
    console.timeEnd('PouchDB - all docs');
  });
}
// get all documents from Couchbase Lite
function couchbase_allDocs() {
  console.time('Couchbase lite - all docs');
  $.get(globalUrl + 'performance/_all_docs')
  .done(function(res) {
    console.time('Couchbase lite - all docs');
  });
}
# PouchDB (10000 docs) CB Lite (10000 docs) - PouchDB (25000 docs) CB Lite (25000 docs)
1 345 ms 126 ms - 852 ms 364 ms
2 325 ms 29 ms - 796 ms 41 ms
3 315 ms 16 ms - 800 ms 40 ms
4 315 ms 16 ms - 782 ms 39 ms
5 311 ms 16 ms - 806 ms 39 ms
6 314 ms 17 ms - 791 ms 39 ms
7 315 ms 22 ms - 791 ms 40 ms
8 308 ms 18 ms - 787 ms 39 ms
9 315 ms 16 ms - 821 ms 37 ms
10 313 ms 16 ms - 801 ms 40 ms
Ø 317.6 ms 29.2 ms - 802.7 ms 71.8 ms

get all documents

Get document by id

The third action was getting a single document by id. First one document was picked out of the 10000 and for the second run out of 25000 documents.

// get a certain document by id
function pouch_get() {
  console.time('PouchDB - get single doc');
  db.get(dummyId, function(err, doc) {
    if (err) console.log(err);
    console.timeEnd('PouchDB - get single doc');
    queryUser = doc;
  });
}
// get a certain document by id
function couchbase_get() {
  console.time('Couchbase lite - get single doc');
  $.get(globalUrl + 'performance/' + dummyId)
  .done(function(doc) {
    console.timeEnd('Couchbase lite - get single doc');
    queryUser = doc;
  });
}
# PouchDB (10000 docs) CB Lite (10000 docs) - PouchDB (25000 docs) CB Lite (25000 docs)
1 5 ms 3 ms - 8 ms 8 ms
2 2 ms 4 ms - 2 ms 4 ms
3 2 ms 4 ms - 3 ms 5 ms
4 3 ms 4 ms - 3 ms 5 ms
5 2 ms 4 ms - 3 ms 3 ms
6 3 ms 3 ms - 4 ms 4 ms
7 3 ms 3 ms - 3 ms 7 ms
8 2 ms 5 ms - 3 ms 4 ms
9 2 ms 4 ms - 3 ms 4 ms
10 2 ms 3 ms - 4 ms 5 ms
Ø 2.6 ms 3.7 ms - 3.6 ms 4.9 ms

get document by id

Query db with view document and key

When you run queries against each database you have to remember that Couchbase Lite needs view documents (like CouchDB) compared to PouchDB that works with on-the-fly map/reduce functions. So whenever we initialize our app we have to save all required view documents to Couchbase Lite (or check if they are already present). As you can see the mapping function is the same for both databases.

// couchbase lite view
var view = {
  "id": "_design/users",
  "views": {
    "email": {
      "map": "function(doc) { emit(doc.email, null) }"
    }
  }
};

// save view to couchbase lite
$.ajax({
  type: 'PUT',
  url: globalUrl + 'performance/_design/users',
  data: JSON.stringify(view),
  contentType : 'application/json'
})
.done(function(res) {
  log('view created');
});

Now you can query each database.

// query pouchdb
function pouch_query() {
  // mapping function
  var map = function(doc) {
    emit(doc.email, null);
  };
  console.time('PouchDB - query single doc');
  // query PouchDB
  db.query({map: map}, {key: queryUser.email}, function(err, res) {
    if (err) console.log(err);
    console.timeEnd('PouchDB - query single doc');
  });
}
// query the database
function couchbase_query() {
  console.time('Couchbase lite - query single doc');
  $.ajax({
    url: globalUrl + 'performance/_design/users/_view/email?key="' + queryUser.email + '"'
  })
  .done(function(doc) {
    console.timeEnd('Couchbase lite - query single doc');
  });
}
# PouchDB (10000 docs) CB Lite (10000 docs) - PouchDB (25000 docs) CB Lite (25000 docs)
1 370 ms 1754 ms - 859 ms 4401 ms
2 338 ms 4 ms - 846 ms 5 ms
3 324 ms 5 ms - 842 ms 4 ms
4 338 ms 3 ms - 873 ms 7 ms
5 346 ms 3 ms - 866 ms 3 ms
6 340 ms 3 ms - 862 ms 4 ms
7 337 ms 3 ms - 838 ms 4 ms
8 330 ms 6 ms - 861 ms 4 ms
9 332 ms 4 ms - 859 ms 3 ms
10 337 ms 3 ms - 844 ms 7 ms
Ø 339.2 ms 178.8 ms - 855 ms 444.2 ms

query via view document

Conclusion

Here is a summary of the results. For the part “query doc via view” I’ll call it a draw. Couchbase Lite is about 5 times slower than PouchDB but has the huge advantage of caching for subsequent requests (and therefore wins when averaging the request times).

PouchDB Couchbase Lite Phonegap
bulk add documents +
get all documents +
get document by id +
query doc via view o o

PouchDB (and CouchDB) are different from Couchbase Lite (and Couchbase). Jan Lehnardt and Scott Hanselman have a great discussion about that topic in the podcast Understanding CouchDB and NoSQL with Jan Lehnardt. The most important difference is caching. Couchbase is a combination between CouchDB and Membase and as it is stated on their homepage “has a built-in, memcached-based caching technology”. The data tells the same story.

Take a look at the last section “Query db with view document and key”. On the first request Couchbase Lite needs much more time than PouchDB to find the document. However on subsequent requests Couchbase Lite is able to find the same document within a few milliseconds whereas PouchDB constantly needs the same amount of time. That means you should always try to guess which documents the user is looking for before the user actually wants to get them. That way a requests only takes a few milliseconds and your app delivers a great user experience. The caching mechanism is also clearly noticeable when getting all documents from the db. The first requests takes much longer than the following ones. However compared to PouchDB all requests, even the first one, are much faster.

PouchDB wins the first section where we add a huge amount of random data to the db. Finding a document by ID is also faster in PouchDB compared to Couchbase Lite. Interesting here is that Couchbase Lite doesn’t seem to cache this requests. Subsequent requests aren’t significantly faster than the first one and PouchDB still wins after a couple of repeated requests.

In total it comes down to the caching feature of Couchbase Lite. PouchDB handles writing documents to db, where caching has no influence, better whereas Couchbase Lite is much faster, especially after the first requests, when it comes to reading documents from db. Therefore it is up to you and your requirements, which database you choose for your next project.

There is also a new kid in town - http://dev.yathit.com/. I haven’t yet tried this one and cannot say anything about performance and overall usability. It has one major disadvantage compared to PouchDB and Couchbase Lite and that is replication / syncing. That feature is not built into the db and you have to implement it yourself.

Google
comments powered by Disqus