Example usages of DbUpdateNotification
Update views on document save
Contents
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()