Node.js library for a CouchDB cache service


 _____                  _     _____            _          
/  __ \                | |   /  __ \          | |         
| /  \/ ___  _   _  ___| |__ | /  \/ __ _  ___| |__   ___ 
| |    / _ \| | | |/ __| '_ \| |    / _` |/ __| '_ \ / _ \
| \__/\ (_) | |_| | (__| | | | \__/\ (_| | (__| | | |  __/
 \____/\___/ \__,_|\___|_| |_|\____/\__,_|\___|_| |_|\___|

If you want to produce a resilient Memcache cluster, it's harder than you think. If you happen to have a resilient CouchDB cluster to hand, such as the hosted CouchDB service provided by Cloudant, you could use CouchDB as your cache without having to create and maintain another resilient cluster of servers.

This project is software library for Node.js that provides Memcache-like functionality but uses CouchDB as the storage mechanism. This is slower than Memcache, but is persistant and has a larger storage capacity.

  npm install couchcache

You need to initialise CouchCache with the url of the your CouchDb instance:

  var couchcache = require("couchcache");
  couchcache.init("", function(err, data) {
    console.log("CouchCache initialised");

Then you can write items:

  couchcache.set("mykey", "myvalue", function(err, data) {
    console.log("CouchCache initialised");

and read them back:

  couchcache.get("mykey", function(err, data) {

and delete them:

  couchcache.delete("mykey", function(err, data) {

Occasionaly, you can ask CouchCache to purge old keys:

  couchcache.purge(function(err, data) {
    console.log("CouchCache purged");

You can write arbitrary JSON objects: Then you can write items:

  couchcache.set("mykey", { a: "myvalue", b: [1,2,3], c: true }, function(err, data) {
    console.log("CouchCache initialised");

CouchCache simply stores the supplied key/value pairs in CouchDB documents.

  "_id": "106e07e9cfebdf9e4da2d99931af4211",          // CouchDB auto-generated id
  "_rev": "1-96be08c1f3372ab8a1cea2913acaa7f8",       // CouchDB auto-generated revision
  "cacheKey": "mykey",                                // user supplied cache key
  "value": "myvalue",                                 // user supplied cache value
  "ts": 1390118630865                                 // expiry timestamp

Notice we don't store the supplied key in the document's "_id" field; this is because we want to be able to overwrite keys simply by adding another document with the same cacheKey but with a later timestamp.

When we retrieve a cacheKey, we use a CouchDB view that fetches the document that matches the supplied cacheKey and has the newest timestamp.

On startup, a new CouchDB database is created and the CouchCache design document is installed which creates two views:

  • _design/fetch/by_key - used to fetch individual cache keys
  • _desing/fetch/by_ts - used to purge old cache documents

When initialising CouchCache, you can supply an optional "options" object which can contain:

var options = {
  expires: 60*60*24*1000,    // expire cacheKeys one day from now (in milliseconds) 
  turbo: false,              // whether to enable turbo mode
  autopurge: true,           // whether to purge old cacheKeys from the databse every hour
  dbname: "cache"            // the name of the CouchDB database to use
var couchcache = required("couchcache");
couchcache.init("", options, function(err, data) {
  console.log("CouchCache initialised");

The options listed above are the default values. The "turbo" option uses "stale=ok" when fetching cache keys. This yields a performance improvement, at the expense of the possibility of fetching an older version of the cache value.

Helper functions are provider to get and set cache values, compressing them on the way. Only strings are supported:

  var bigObject = { .... };
  couchcache.zset("mykey", JSON.stringify(bigObject), function(err, data) {

In test/benchmark.js, there is a script which calculates the average time to fetch a cache key. Here is how the results stack up:

  1. From my machine to hosted BigCouch CouchDB (Cloudant) over the internet - 100ms
  2. From my machine to local CouchDB - 5ms
  3. From a server reading from a dedicated BigCouch CouchDB (Cloudant) cluster in the same data centre - 16ms

This should be compared to Memcached which has the following benchmarks:

  1. My machine to hosted Memcached over the internet - 20ms
  2. My machine to local Memcached - 0.25ms
  3. A server reading from a 2 Memcached server cluster in the same data centre - 1.5ms

So, not suprisingly, local Memcache is the fastest, but it is not persistent. The closer you are to your cache, the faster it is. Whether 20ms is fast enough for your application depends on your application!

If you are not careful, then your cache can easily become a single point of failure in an IT system. The following sections discuss various solutions for a non-trivial installation e.g. several servers sharing the same cache.

You can install Memcache on each server (the fastest option), but then you have different servers with different cache keys. If you want to invalidate a cache key, it must be done on multiple servers.

All servers point to a single Memcache instance. This becomes your single point of failure and the size of the cache is limited by the cache machine's memory.

Several servers, with the keys distributed using consistent hashing. This spreads the risk of a failure and increases the cache capacity, but each shard of the cache is it's own SPOF.

The most reliable solution, but has the odd effect that a cache key saved may not be fetchable if you end up reading from a different server from the one you saved it to.

Third-party suppliers (such as IronCache) can provide a caching API, but unless you happen to be in their data centre (Amazon US East), then performance is poor.

If you need persistant local Cache, then CouchCache could be a good fit. If you happen to have a BigCouch installation in your data centre (e.g. hosted and managed by Cloudant), then CouchCache allows you to have a bigger and persistant cache with reasonable performance (20ms per fetch).