[Zope] minimizing conflict errors

Chris McDonough chrism at plope.com
Sun Nov 20 14:46:46 EST 2005


On Nov 20, 2005, at 12:16 PM, Dennis Allison wrote:

> The structure of the naviagation method is simple enough.  
> Everything is
> wrapped in a <dtml-let> which sets a number of parameters mostly by
> reading them from the SESSION (with an interface function) or plucking
> them from the relational database with a query.
>
> In the scope of the let is dtml code which, when rendered, provides  
> the
> various navigation links.  In various sections there are additional
> <dtml-let> blocks and additional queries to the relational database
> and several <dtml-in> loops.
>
> Looking at the code, I don't understand why I am seeing conflicts.
> As I understand things, neither variables in the <dtml-let> space nor
> the REQUEST/RESPONSE space are stored in the ZODB so modifications to
> them don't look like writes to the conflict mechanism.  Am I incorrect
> in my understanding?

Yes, but that's understandable.  It's not exactly obvious.

The sessioning machinery is one of the few places in Zope where it's  
necessary for the code to do what's known as a "write on read" in the  
ZODB database.

Even if you're just "reading" from a session, looking up a session,  
or doing anything otherwise related to sessioning, it's possible for  
your code to generate a ZODB write.
This is why you get conflicts even if you're "just reading"; whenever  
you access the sessioning machinery, you are potentially (but not  
always) causing a ZODB write.  All writes can potentially cause a  
conflict error.

While this might sound fantastic, it's pretty much impossible to  
avoid when using ZODB as a sessioning backend.  The sessioning  
machinery has been tuned to generate as few conflicts as possible,  
and you can help it by doing your own timeout, resolution, and  
housekeeping tuning as has been suggested.  MVCC gets rid of read  
conflicts.  But it's not possible to completely avoid write conflicts  
under the current design.

Here's why.  The sessioning machinery is composed of three major data  
structures:

- an index of "timeslice" to "bucket". A timeslice is an integer  
representing
   some range of time (the range of time is variable, depending on the
   "resolution", but out of the box, it represents 20 seconds).    
This mapping
   is an IOBTree.

- A "bucket" is a mapping from a browser id to "session data  
object" (aka
   transient object).  This mapping is an OOBTree.

- three "increasers" which mark the "last" timeslice in which  
something was done
   (called the garbage collector, called the finalizer, etc).

The point of sessioning is to provide a writable namespace assigned  
to a single user that expires after some period of inactivity by that  
user.  To this end, we need to keep track of when the last time the  
user "accessed" the session was.  This is the point of the index.

When a user accesses his session, we may need to move his session  
data object (identified by his browser id) from one bucket  
(representing an older timeslice) to another (representing a newer  
timeslice).  This needs to happen *even if your code doesn't write  
anything to his session*, because it represents a session access, and  
the session is defined by total inactivity (not just write  
inactivity).  Likewise, when a user runs code that requires access to  
a session, but that user does not yet have a session data object, a  
write may need to occur.  So seemingly innocuous accesses to session  
data can cause a write.  Consider, in a Python script:

req = context.REQUEST
REQUEST.SESSION

Looks pretty harmless and unlikely to cause a write.  However, that's  
not true.  If the "bucket" in which the user's session data object is  
found is not associated with the "current" timeslice, we need to move  
his data object to the bucket that *is* associated with the current  
timeslice, which is a write operation in order to make note of the  
fact that his session is now "current".

Likewise with:

req = context.REQUEST
a = REQUEST.SESSION.get('foo')

Even though this appears to be "only a read", the sessioning  
machinery itself may need to perform a write operation to move the  
user's data object to the current bucket.

Jacking up the resolution time increases the period of time  
represented by a single timeslice, so fewer total writes need to be  
performed to keep a session "current".   Turning on "external  
housekeeping" doesn't prevent this normal movement of data objects  
between buckets, it just causes another process that cleans up  
"stale" data from happening during normal sessioning operations.

The sessioning machinery attempts to minimize conflicts.  The 2.8  
version of the temporarystorage does MVCC, which essentially  
eliminates read conflict errors.  The transience machinery includes  
significantly complicated logic to attempt to prevent conflict errors  
from occurring including code that attempts to prevent two threads  
from doing housekeeping at once as well as application level conflict  
resolution for simultaneous writes to the same session data object.   
However, the machinery uses BTrees to hold indexes.  BTrees also have  
a limited number of conflict avoidance strategies, but under certain  
circumstances (a "bucket split" is the canonical case) it cannot be  
avoided so not all write conflicts can be prevented without using a  
different kind of data structure to hold sessioning data.

A more detailed description of how "transience" works is available  
within the file named "HowTransienceWorks.txt" in the Products/ 
Transience package within Zope in case you're interested.

I hope this explains why you see conflict errors even if your code  
"doesn't do any writes", because actually it probably does by virtue  
of accessing a session.  Tuning the knobs that come with the  
machinery helps.  Causing transactions to be as short as possible  
also helps (by not using ZEO to back the sessioning database or by  
making your code just generally faster) because then there is less of  
a chance of a conflicting change.

- C



More information about the Zope mailing list