How Pitance Versions Documents in CouchDB

September 17, 2009. Filed under couchdbpitance

One of the most important features for PItance was to support versioning of templates. Having worked some with CouchDB before, I figured I could piggyback on the built-in document versioning exposed by the Document API. Apparently a lot of people have had this idea, as there is a Document Revisions entry in the wiki with a rather blunt statement:

You cannot rely on document revisions for any other purpose than concurrency control.

While brainstorming a versioning solution, I ran into this blog entry which had some helpful advice and got me 80% of the way to what I needed:

  1. Create each document with a version attribute.

    { "name": "erlang_gen_server", "version": 1 }
    

  2. Have the view return keys like ["erlang_gen_server", 1].

  3. Each time a template is updated, retrieve the latest revision

    ?startkey=["erlang_gen_server", "Z"]
    &endkey=["erlang_gen_server", 0]
    &decending=true&count=1
    

    increment its version by one, and then create a new CouchDB document for the new version, where the key is ["erlang_gen_server", N+1].

  4. Get all versions using

    ?startkey=["erlang_gen_server"]
    &endkey=["erlang_gen_server", "Z"]
    
  5. Retrieve a specific version using ?key=["erlang_gen_server", 2]

With that, I was--as I already mentioned--about 80% of the way there, but I still had a glaring issue to issue: CouchDB-Lucene and my views for languages and tags were treating each version of a template as a standalone document. This meant searching for Erlang would return a dozen different versions of erlang_couchdb_app. Generally this made the site miserable to use. My solution here was to maintain a latest attribute in all the docs:

{ "name": "erlang_gen_server", "version": 2, "latest": true}
{ "name": "erlang_gen_server", "version": 1, "latest": false}

As each template is updated, I go in and set the previous latest version such that latest=false, and then create the new version with latest=true. From there I updated the views for tags, languages and those used by CouchDB-Lucene to only emit the latest documents:

fun(doc) { if (doc.latest && doc.language) { emit(doc.language, doc); }}

Although a bit more work than expected (and it requires updating two documents each time a template is modified, which some might find objectionable), this has made it possible to have the best of both worlds, maintaining versioned templates without exposing the versions in places where no one cares.