It is what happens with this retrying that interests me. As the example given below shows, this retry may never succeed. Thus long running operations may never complete and are effectively starved by faster running operations. Assume in Zope I have a folder structure like this. Folder-1 -Folder-2 -Folder-3 .... .. Folder-1000 Suppose I have a script traverseFolder(root) that starts at a given root and traverses sub-folders adding the attribute foo. If I continuously call traversefolder(Folder-1000) how could traverseFolder(Folder-1) ever complete. I mean, by the time traverseFolder(Folder-1) could complete traverseFolder(Folder-1000) would have already committed several times. What would happen with the request that started traverseFolder(Folder-1), would it keep being retried ad infinitum. It is basically starved out of contention by a faster running operations. -----Original Message----- From: Chris McDonough [mailto:chrism@digicool.com] Sent: Thursday, November 01, 2001 11:16 AM To: Kapil Thangavelu Cc: Clark OBrien; 'Chris McDonough'; zope@zope.org Subject: Re: [Zope] zope operations atomic? AFAIK, a read conflict is raised in this circumstance, so there can't be a dirty read. But again, since requests which raise conflicts are retried, there is never a consistency problem. - C Kapil Thangavelu wrote:
even more generally the problem can be stated as transaction A starts reads a bunch of objects, transaction B starts and will read a set of objects that intersects with transaction A. transaction A commits changing objects that B is about to read (after B has read some objects). B finishes, and has possibly commited or done operations on data that is no longer in sync because the set of data may not have been consistent for its operation, hence the dirty read.
this is a more interesting and IMO legitimate concern, i haven't done any testing recently, but i believe as it currently stands this should throw a ConflictError (i'm not sure, though.). Jim and pythonlabs have talked about setting up some sort MVCC for the zodb, whereby transaction B will see a snapshot of objects as it existed when it began. if you're interested in this further i would send mail to the zodb-dev@zope.org list, referencing this thread.
cheers
kapil
--- Clark OBrien <COBrien@isis-server.vuse.vanderbilt.edu> wrote:
Here is the example I was thinking about.
1) Suppose an online store is selling product X and at a given moment its inventory is n. Two shoppers hit the buy button at about the same time.
Then either:
a) No conflict is detected because both threads set the inventory value to the same value, n-1. This would cause a problem if n = 1
Or: b) A conflict is detected when the last thread writes because the inventory is not n as it was when he read it. request is resubmitted but again another thread has changed made the value dirty- thread starves.
-----Original Message----- From: Chris McDonough [mailto:chrism@zope.com] Sent: Thursday, November 01, 2001 6:50 AM To: Clark OBrien; k_vertigo@yahoo.com; zope@zope.org Subject: Re: [Zope] zope operations atomic?
What is "the dirty read problem"?
__________________________________________________ Do You Yahoo!? Make a great connection at Yahoo! Personals. http://personals.yahoo.com
-- Chris McDonough Zope Corporation http://www.zope.org http://www.zope.com "Killing hundreds of birds with thousands of stones"
Clark OBrien wrote:
It is what happens with this retrying that interests me. As the example given below shows, this retry may never succeed. Thus long running operations may never complete and are effectively starved by faster running operations.
Assume in Zope I have a folder structure like this.
Folder-1 -Folder-2 -Folder-3 .... .. Folder-1000
Suppose I have a script traverseFolder(root) that starts at a given root and traverses sub-folders adding the attribute foo.
If I continuously call traversefolder(Folder-1000) how could traverseFolder(Folder-1) ever complete. I mean, by the time traverseFolder(Folder-1) could complete traverseFolder(Folder-1000) would have already committed several times. What would happen with the request that started traverseFolder(Folder-1), would it keep being retried ad infinitum. It is basically starved out of contention by a faster running operations.
It does get interesting when you consider that the longer-running transaction might always tend to lose on read conflicts because: a) read conflicts can't be resolved. b) the longer-running transaction *might* "ghost out" Folder-1000 to conserve RAM during the traversal after the first (aborted) commit. If b) is true in your example for every run of traverseFolder(Folder-1), your contention that it will never commit might prove correct. It'd be slightly interesting to try it to see what the behavior actually is. Would you be willing to do so? In any case, we have an answer to this problem if you're willing to lose a bit of consistency. CoreSessionTracking's LowConflictConnection class comes in handy here. If your application is very write-intensive and you've carefully coded a massive hotspot ala your example into it, you can turn off read conflicts by using this class at the expense of some consistency. With read conflicts turned off, I'm positive your example will eventually resolve itself. Maybe it'll take a few minutes of retries, but it will eventually finish. I say that because the longer running of the two scripts will eventually be able to do the commit because it's statistically as likely to "win" a commit as the shorter-running script when there's a write conflict; it just has fewer opportunities to do so. And actually if you used a special FooFolder instead of a Folder for this demonstration, the only thing that changed was foo, and the value of foo was often the same in both connections on the object upon which the transaction conflicted, you could resolve most of the conflicts that could potentially occur here (save for the read conflicts) by giving it a _p_resolveConflict that looked something like this: class FooFolder: def _p_resolveConflict(self, old, saved, new): marker = [] for k,v in saved.items(): if new.get(k, marker) != v: return None for k,v in new.items(): if saved.get(k, marker) != v: return None return new However, the real answer is: Dont design your application like this if you can help it. This is not a good pattern. It's best to avoid hotspots like Folder-1000 in your example. It's no different than continually beating the snot out of a field in a row in a relational database table with writes from multiple threads, where one of the threads is running an overnight transaction and the others are just incrementing the field every second. You have the same consistency, contention, and timeliness issues there, AFAICT, except that it's expressed in terms of pages, locks, and dirty reads. (I'm sure there's an Oracle person waiting around to "WRONG!" me to death, however. ;-) - C
chris, thanks for the illuminating response. kapil --- Chris McDonough <chrism@digicool.com> wrote:
Clark OBrien wrote:
It is what happens with this retrying that interests me. As the example given below shows, this retry may never succeed. Thus long running operations may never complete and are effectively starved by faster running operations.
Assume in Zope I have a folder structure like this.
Folder-1 -Folder-2 -Folder-3 .... .. Folder-1000
Suppose I have a script traverseFolder(root) that starts at a given root and traverses sub-folders adding the attribute foo.
If I continuously call traversefolder(Folder-1000) how could traverseFolder(Folder-1) ever complete. I mean, by the time traverseFolder(Folder-1) could complete traverseFolder(Folder-1000) would have already committed several times. What would happen with the request that started traverseFolder(Folder-1), would it keep being retried ad infinitum. It is basically starved out of contention by a faster running operations.
It does get interesting when you consider that the longer-running transaction might always tend to lose on read conflicts because:
a) read conflicts can't be resolved. b) the longer-running transaction *might* "ghost out" Folder-1000 to conserve RAM during the traversal after the first (aborted) commit.
If b) is true in your example for every run of traverseFolder(Folder-1), your contention that it will never commit might prove correct. It'd be slightly interesting to try it to see what the behavior actually is. Would you be willing to do so?
In any case, we have an answer to this problem if you're willing to lose a bit of consistency. CoreSessionTracking's LowConflictConnection class comes in handy here. If your application is very write-intensive and you've carefully coded a massive hotspot ala your example into it, you can turn off read conflicts by using this class at the expense of some consistency.
With read conflicts turned off, I'm positive your example will eventually resolve itself. Maybe it'll take a few minutes of retries, but it will eventually finish. I say that because the longer running of the two scripts will eventually be able to do the commit because it's statistically as likely to "win" a commit as the shorter-running script when there's a write conflict; it just has fewer opportunities to do so.
And actually if you used a special FooFolder instead of a Folder for this demonstration, the only thing that changed was foo, and the value of foo was often the same in both connections on the object upon which the transaction conflicted, you could resolve most of the conflicts that could potentially occur here (save for the read conflicts) by giving it a _p_resolveConflict that looked something like this:
class FooFolder: def _p_resolveConflict(self, old, saved, new): marker = [] for k,v in saved.items(): if new.get(k, marker) != v: return None for k,v in new.items(): if saved.get(k, marker) != v: return None return new
However, the real answer is: Dont design your application like this if you can help it. This is not a good pattern. It's best to avoid hotspots like Folder-1000 in your example. It's no different than continually beating the snot out of a field in a row in a relational database table with writes from multiple threads, where one of the threads is running an overnight transaction and the others are just incrementing the field every second. You have the same consistency, contention, and timeliness issues there, AFAICT, except that it's expressed in terms of pages, locks, and dirty reads. (I'm sure there's an Oracle person waiting around to "WRONG!" me to death, however. ;-)
- C
__________________________________________________ Do You Yahoo!? Make a great connection at Yahoo! Personals. http://personals.yahoo.com
participants (3)
-
Chris McDonough -
Clark OBrien -
Kapil Thangavelu