ZooKeeper loss of events problem... fixed
In my latest project at LinkedIn, I have been using ZooKeeper to track the state of all deployed services on every machine in production. The state is then used to drive the deployment as well as monitor the system.
I ran into a really tricky bug which took me several months to address (the hardest part was finding what the actual problem was).
The (simplified) code was the following:
What this code essentially does is the following:
- Sets a children watcher on /state to be notified of children addition/deletion
- For each child, sets a node watcher to be notified of child modification and deletion. Note that you need this watcher because ZooKeeper will not call the parent watcher when a child gets modified!
- As a result, the map called state always contain a 'copy' of the data of all children.
- Watches are one time triggers; if you get a watch event and you want to get notified of future changes, you must set another watch.
- Because watches are one time triggers and there is latency between getting the event and sending a new request to get a watch you cannot reliably see every change that happens to a node in ZooKeeper. Be prepared to handle the case where the znode changes multiple times between getting the event and setting the watch again. (You may not care, but at least realize it may happen.)
- A watch object, or function/context pair, will only be triggered once for a given notification. For example, if the same watch object is registered for an exists and a getData call for the same file and that file is then deleted, the watch object would only be invoked once with the deletion notification for the file.
- /state childrenWatcher would be fired with a NodeChildrenChanged event. And the bug gets triggered here. According to key concept #2, essentially events can be collapsed: in this case I am getting only 1 NodeChildrenChanged event and when I list my children (getChildren), I actually see no difference: child1 has been removed and added, but I don't see it. I am effectively loosing the 'add' child event with the code written this way.
- /state/child1 childWatcher would we fired with a NodeDeleted event resulting in state.remove('child1')... at this point I have indeed lost the state of child1.
- The children watcher is only dealing with 'ADDs' and does not handle 'DELETE' anymore
- The child watcher handles 'DELETE' but also tries to set a watcher no matter what
- /state/child1 simply deleted
-
- childrenWatcher receives NodeChildrenChanged => does nothing
- childWatcher receives NodeDeleted => state.remove('child1')
or
-
- childWatcher receives NodeDeleted => state.remove('child1')
- childrenWatcher receives NodeChildrenChanged => does nothing
- /state/child1 deleted / recreated (events get collapsed)
-
- childrenWatcher receives NodeChildrenChanged => does nothing
- childWatcher receives NodeDeleted => state.remove('child1') then state['child1']=new state + watcher
or
-
- childWatcher receives NodeDeleted => state.remove('child1') then state['child1']=new state + watcher
- childrenWatcher receives NodeChildrenChanged => does nothing
- /state/child1 deleted / recreated (events are not collapsed)
-
- childrenWatcher receives NodeChildrenChanged => does nothing
- childWatcher receives NodeDeleted => state.remove('child1')
- childrenWatcher receives NodeChildrenChanged => state['child1']=new state + watcher
or
-
- childWatcher receives NodeDeleted => state.remove('child1')
- childrenWatcher receives NodeChildrenChanged => state['child1']=new state + watcher
or
-
- childrenWatcher receives NodeChildrenChanged => does nothing
- childWatcher receives NodeDeleted => state.remove('child1') then state['child1']=new state + watcher
- childrenWatcher receives NodeChildrenChanged => does nothing
Jul 24 2010 - Posted by Yan in