You are not logged in Log in Join
You are here: Home » Members » jim » ZODB » ApplicationLevelConflictResolution

Log in
Name

Password

 
 

History for ApplicationLevelConflictResolution

??changed:
-
ZODB uses an optimistic concurrency control mechanism.  Objects are
not locked. Conflicting writes are checked when object changes are
committed. If a conflict is detected, then a 'ConflictError' is raised
and the request is retried. This approach works fine if conflicts are
rare. If conflicts occur frequently, then, at best, performance
degrades because requests must be resubmitted, or, at worst, requests
are rejected and raise errors due to conflicts.

Sometimes, there may be "hot spots" in an application
that get a lot of simultaneous writes. These hot spots can often be designed
away. An alternative is to provide application-level logic for sorting
out the changes made by conflicting writes.

A new interface is proposed to allow object authors to provide a
method for resolving conflicts. When a conflict is detected, then the
database checks to see if the class of the object being saved defines
the method, '_p_resolveConflict'. If the method is defined, then the
method is called on the object. If the method succeeds, then the object
change can be committed, otherwise a 'ConflictError' is raised as
usual.

*Conflict resolution, as proposed here, was added to ZODB in Zope 2.3.*

_p_resolveConflict(oldState, savedState, newState) -- 
   Return the state of the object after resolving different changes.

   Arguments:

   oldState -- The state of the object that the changes made by the
               current transaction were based on.

	       The method is permitted to modify this value.

   savedState -- The state of the object that is currently stored in
		 the database. This state was written after oldState and reflects
		 changes made by a transaction that committed before the current
		 transaction. 

  	         The method is permitted to modify this value.

   newState -- The state after changes made by the current transaction.

	       The method is *not* permitted to modify this value.

   This method should compute a new state by merging changes reflected
   in savedState and newState, relative to oldState.

   Consider an extremely simple example, a counter::

     class PCounter(Persistent):
       
       _value = 0

       def inc(self): self._value=self._value+1

       def _p_resolveConflict(self, oldState, savedState, newState):

           # Figure out how each state is different:
           savedDiff=savedState!['_value']-oldState!['_value']
           newDiff=newState!['_value']-oldState!['_value']
	   
	   # Apply both sets of changes to old state:
	   oldState!['_value'] = oldState!['_value'] + savedDiff + newDiff
	   
	   return oldState

   If the method cannot resolve the changes, then it should return
   None.

PJE -- It's an interesting idea, and would certainly address simple
    conflict cases.  But what if one used the value of the counter to
    do something else?

    Jim -- It would depend if the "something else" caused any other
      conflicts. For conflict resolution to prevent a conflict error,
      all conflicting updates must be resolvable.  This is not a
      problem, since, generally, only a few (typically one) of the
      changed in a transaction should conflict.

    In that case, the conflict cannot be "backed
    out" by only considering the state of one object. 

    Jim -- The state's of all conflicting objects will be
    considered. If all of the conflicts can be resolved, the
    transaction will not have to be retried.

    Adding objects to a ZCatalog for example, cannot be de-conflicted
    in this way.

    Jim -- Sure it can, in most cases.  We will update the indexing
    machinery used by the catalog to leverage conflict resolution. If
    a change doesn't cause a bucket to split, then a conflict will be
    avoided, even if changes affect multiple indexes.

    It seems to me that most of the really interesting (to me, anyway)
    conflict cases are the ones that this scheme cannot address,
    because the "state" that you are interested in is spread out
    across more than one Persistent instance.  What are your thoughts
    on this type of scenario?

    Jim -- My thought are that changes affecting multiple objects are
    not a problem, as long as all of the onflicts can be resolved.

Toby Dickenson -- Whats the difference between 'newState' and 'self'?
     What happens to changes to 'self'. 

    Jim -- The conflict resolution method should not modify self.
       Effects of changes to self are undefined. The 'self' argument
       is really there to hand the method on. :)

Toby Dickenson -- How does this interact with subtransactions?

  Jim -- It doesn't really.  Note, however, that conflicts are
    detected when the entire transaction is committed, so conflict 
    resolution will only be performed when the outer transaction commits.

Toby Dickenson -- I assume changes to savedState and oldState are
  discarded (unless that object is the one that gets returned, as in the
  example.)

  Jim -- Right.

John Heintz -- When thinking through this issue I see that many conflicts would occur in PersistentMapping and PersistentList objects, not my own domain objects.  This "one object at a time" approach would really discourage using any persistent collection helpers.  Here is my idea for an alternative mechanism: DualCacheConflictResolution