Example usages of DbUpdateNotification

Update views on document save

CouchDB defaults to regenerating views the first time they are accessed. This behavior is preferable in most cases as it optimizes the resource utilization on the database server. On the other hand, in some situations the benefit of always having fast and updated views far outweigh the cost of regenerating them every time the database server receives updates. This can be achieved by supplying an updater script that calls the views when needed.

Example using ruby

couch.ini

(0.8) Add the following line to the couch.ini file

DbUpdateNotificationProcess=/PATH/TO/view_updater.rb

(0.9+) Add the following section to the local.ini file:

[update_notification]
view_updater=/PATH/TO/view_updater.rb

view_updater.rb

The following script updates the views for each tenth update made to the database or at most once every second when a lot of saves are performed

   1 #!/usr/bin/ruby
   2 
   3 ###################
   4 # CONF            #
   5 ###################
   6 
   7 # The smallest amount of changed documents before the views are updated
   8 MIN_NUM_OF_CHANGED_DOCS = 10
   9 
  10 # URL to the DB on the CouchDB server
  11 URL = "http://localhost:5984"
  12 
  13 # Set the minimum pause between calls to the database
  14 PAUSE = 1 # seconds
  15 
  16 # One entry for each design document 
  17 # in each database
  18 VIEWS = {"my_db"  => {"design_doc" => "view_name"}}
  19 
  20 ###################
  21 # RUNTIME         #
  22 ###################
  23 
  24 run = true
  25 number_of_changed_docs = {}
  26 
  27 threads = []
  28 
  29 # Updates the views
  30 threads << Thread.new do
  31 
  32   while run do
  33 
  34     number_of_changed_docs.each_pair do |db_name, number_of_docs|
  35       if number_of_docs >= MIN_NUM_OF_CHANGED_DOCS
  36         
  37         # Reset the value
  38         number_of_changed_docs[db_name] = 0
  39         
  40         # If there are views in the database, get them
  41         if VIEWS[db_name]
  42           VIEWS[db_name].each do |design, view|
  43             `curl #{URL}/#{db_name}/_design/#{design}/_view/#{view}?limit=0`
  44           end  
  45         end
  46                 
  47       end
  48     end
  49 
  50     # Pause before starting over again
  51     sleep PAUSE
  52     
  53   end
  54   
  55 end
  56 
  57 # Receives the update notification from CouchDB
  58 threads << Thread.new do
  59 
  60   while run do
  61 
  62     STDERR << "Waiting for input\n"
  63     update_call = gets
  64     
  65     # When CouchDB exits the script gets called with
  66     # a never ending series of nil
  67     if update_call == nil
  68       run = false
  69     else
  70       
  71       # Get the database name out of the call data
  72       # The data looks something like this:
  73       # {"type":"updated","db":"DB_NAME"}\n
  74       update_call =~ /\"db\":\"(\w+)\"/
  75       database_name = $1
  76       
  77       # Set to 0 if it hasn't been initialized before
  78       number_of_changed_docs[$1] ||= 0
  79       
  80       # Add one pending changed document to the list of documents
  81       # in the DB
  82       number_of_changed_docs[$1] += 1
  83       
  84     end
  85     
  86   end
  87 
  88 end
  89 
  90 # Good bye
  91 threads.each {|thr| thr.join}

The view_updater.rb itself has to be made executable by CouchDB (chmod 0700?).

Example using Python

   1 #!/usr/bin/env python
   2 # -*- coding: utf-8 -*-
   3 
   4 """Updater script to regenerate couchdb views on update.
   5 """
   6 
   7 import logging
   8 logging.basicConfig(level=logging.INFO)
   9 
  10 import os
  11 import re
  12 import signal
  13 import sys
  14 import time
  15 import urllib2
  16 
  17 from threading import Thread
  18 
  19 flags = {
  20     'is_running': True
  21 }
  22 
  23 changed_docs = {}
  24 
  25 class ViewUpdater(object):
  26     """Updates the views.
  27     """
  28     
  29     # The smallest amount of changed documents before the views are updated
  30     MIN_NUM_OF_CHANGED_DOCS = 50
  31     
  32     # Set the minimum pause between calls to the database
  33     PAUSE = 5 # seconds
  34     
  35     # URL to the DB on the CouchDB server
  36     URL = "http://localhost:5984"
  37     
  38     # One entry for each design document 
  39     # in each database
  40     VIEWS = {
  41         'my_db': {
  42             'design_doc': [
  43                 'view_name',
  44                 # ...
  45             ]
  46         }
  47     }
  48     
  49     def start(self):
  50         Thread(target=self._run).start()
  51     
  52     
  53     def _run(self):
  54         """Loop, checking for enough ``changed_docs`` to trigger a
  55           request to couchdb to re-index.
  56         """
  57         
  58         while flags['is_running']:
  59             try:
  60                 for db_name, number_of_docs in changed_docs.items():
  61                     if number_of_docs >= self.MIN_NUM_OF_CHANGED_DOCS:
  62                         # Reset the value
  63                         del changed_docs[db_name]
  64                         # If there are views in the database, get them
  65                         if db_name in self.VIEWS:
  66                             logging.info('regenerating %s' % db_name)
  67                             db_views = self.VIEWS[db_name]
  68                             for design, views in db_views.iteritems():
  69                                 for view in views:
  70                                     url = '%s/%s/_design/%s/_view/%s?limit=0' % (
  71                                         self.URL, db_name, design, view
  72                                     )
  73                                     urllib2.urlopen(url)
  74                 time.sleep(self.PAUSE)
  75             except:
  76                 flags['is_running'] = False
  77                 raise
  78             
  79         
  80         
  81     
  82     
  83 
  84 
  85 class NotificationConsumer(object):
  86     """Receives the update notification from CouchDB.
  87     """
  88     
  89     DB_NAME_EXPRESSION = re.compile(r'\"db\":\"(\w+)\"')
  90     
  91     def _run(self):
  92         """Consume update notifications from stdin.
  93         """
  94         
  95         while flags['is_running']:
  96             try:
  97                 data = sys.stdin.readline()
  98             except:
  99                 continue
 100             else:
 101                 if not data: # exit
 102                     flags['is_running'] = False
 103                     break
 104                 result = self.DB_NAME_EXPRESSION.search(data)
 105                 if result:
 106                     db_name = result.group(1)
 107                     # Set to 0 if it hasn't been initialized before
 108                     if db_name not in changed_docs:
 109                         changed_docs[db_name] = 0
 110                     # Add one pending changed document to the list
 111                     # of documents in the DB
 112                     changed_docs[db_name] += 1
 113                 
 114             
 115         
 116         
 117     
 118     
 119     def start(self):
 120         t = Thread(target=self._run)
 121         t.start()
 122         return t
 123         
 124     
 125     
 126 
 127 
 128 def main():
 129     logging.info('update_notification handler (re)starting')
 130     consumer = NotificationConsumer()
 131     updater = ViewUpdater()
 132     updater.start()
 133     t = consumer.start()
 134     try:
 135         while flags['is_running']:
 136             t.join(10)
 137     except KeyboardInterrupt, err:
 138         flags['is_running'] = False
 139         
 140     
 141 
 142 
 143 if __name__ == '__main__':
 144     main()