2

I have a nodejs application where i connect to my couchdb using nano with the following script:

const { connectionString } = require('../config');

const nano = require('nano')(connectionString);

// creates database or fails silent if exists
nano.db.create('foo');

module.exports = {
  foo: nano.db.use('foo')
}

This script is running on every server start, so it tries to create the database 'foo' every time the server (re)starts and just fails silently if the database already exists.

I like this idea a lot because this way I'm actually maintaining the database at the application level and don't have to create databases manually when I decide to add a new database.

Taking this approach one step further I also tried to maintain my design docs from application level.

... 
nano.db.create('foo');

const foo = nano.db.use('foo');

const design = {
  _id: "_design/foo",
  views: {
    by_name: {
      map: function(doc) {
        emit(doc.name, null);
      }
    }
  }
}

foo.insert(design, (err) => {
  if(err)
    console.log('design insert failed');
})

module.exports = {
  foo
}

Obviously this will only insert the design doc if it doesn't exist. But what if I updated my design doc and want to update it?

I tried:

foo.get("_design/foo", (err, doc) => {
  if(err)
    return foo.insert(design);

  design._rev = doc._rev
  foo.insert(design);
})

The problem now is that the design document is updated every time the server restarts (e.g it gets a new _rev on every restart).

Now... my question(s) :)

1: Is this a bad approach for bootstrapping my CouchDB with databases and designs? Should I consider some migration steps as part of my deployment process?

2: Is it a problem that my design doc gets many _revs, basically for every deployment and server restart? Even if the document itself hasn't changed? And if so, is there a way to only update the document if it changed? (I thought of manually setting the _rev to some value in my application but very unsure that would be a good idea).

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Sander Garretsen
  • 1,683
  • 10
  • 19

3 Answers3

2
  1. Your approach seems quite reasonable. If the checks happen only at restarts, this won't even be a performance issue.
  2. Too many _revs can become a problem. The history of _revs is kept as _revs_info and stored with the document itself (see the CouchDB docs for details). Depending on your setup, it might be a bad decision to create unnecessary revisions.

We had a similar challenge with some server-side scripts that required certain views. Our solution was to calculate a hash over the old and new design document and compare them. You can use any hashing function for this job, such as sha1 or md5. Just remember to remove the _rev from the old document before hashing it, or otherwise you will get different hash values every time.

Bernhard Gschwantner
  • 1,547
  • 11
  • 12
  • Thank you Bernhard. Your explanation was really helpfull. I took your advice by simply comparing the design docs, but ran into some troubles comparing them with md5... I posted my own solution based on your sugestions below. Could you please give your feedback? – Sander Garretsen Jul 16 '16 at 12:29
2

I tried the md5 comparison like @Bernhard Gschwantner suggested. But I ran into some difficulties because im my case I'd like to write the map/reduce functions in the design documents in pure javascript in my code.

const design = {
  _id: "_design/foo",
  views: {
    by_name: {
      map: function(doc) {
        emit(doc.name, null);
      }
    }
  }
}

while getting the design doc from CouchDb returns the map/reduce functions converted as strings:

  ...
  "by_name": {
    "map": "function (doc) {\n        emit(doc.name, null);\n      }"
  },
  ...

Obviously md5 comparing does not really work here.

I ended up with the very simple solution by just putting a version number on the design doc:

const design = {
  _id: "_design/foo",
  version: 1,
  views: {
    by_name: {
      map: function(doc) {
        emit(doc.name, null);
      }
    }
  }
}

When I update the design doc, I simply increment the version number and compare it with the version number in database:

const fooDesign = {...}
foo.get('_design/foo', (err, design) => {
  if(err)
    return foo.insert(fooDesign);

  console.log('comparing foo design version', design.version, fooDesign.version);
  if(design.version !== fooDisign.version) {
    fooDesign._rev = design._rev;
    foo.insert(fooDesign, (err) => {
      if(err)
        return console.log('error updating foo design', err);
      console.log('foo design updated to version', fooDesign.version)
    });
  }
});
Sander Garretsen
  • 1,683
  • 10
  • 19
  • This is also great and much simpler. If you want to use the md5 hashing, though, you can convert the javascript functions into strings by converting the functions into strings by iterating through the object properties and calling `prop.toString()`. But this is more work, so the version field is the much simpler and more understandable solution. – Bernhard Gschwantner Jul 28 '16 at 09:27
1

Revisiting your question again: In a recent project I used the great couchdb-push module by Johannes Schmidt. You get conditional updates for free, alongside with many other benefits inherited from its dependency couchdb-compile.

That library turned out to be a hidden gem for me. HIGHLY recommended!

Bernhard Gschwantner
  • 1,547
  • 11
  • 12