deciding whether to do work in tpc_vote or tpc_finish
Hi All, I need to write a data manger that interacts with a transactional system that doesn't support two phase commit. Looking for inspiration, I went to look at zope.sqlalchemy and zope.sendmail. In the non-tpc situation, the former does the "commit" in tpc_vote while the latter does it in tpc_finish. Which is "right"? What are the tradeoffs involved? cheers, Chris
On 8 June 2010 09:51, Chris Withers <chris@simplistix.co.uk> wrote:
Hi All,
I need to write a data manger that interacts with a transactional system that doesn't support two phase commit.
Looking for inspiration, I went to look at zope.sqlalchemy and zope.sendmail.
In the non-tpc situation, the former does the "commit" in tpc_vote while the latter does it in tpc_finish.
Which is "right"? What are the tradeoffs involved?
Committing in tpc_vote is right so long as you ensure your data manager sorts last, and that there are no other data managers in the transaction which are using the same trick. See: https://mail.zope.org/pipermail/zodb-dev/2007-May/010996.html For zope.sendmail, committing in tpc_finish makes sense, especially when using QueuedMailDelivery because enqueuing the message in the maildir is guaranteed to succeed (the file is just renamed on commit). Any failure here would lead to a critical error and inconsistent state between the transactional resources. Laurence Laurence.
Laurence Rowe wrote:
Committing in tpc_vote is right so long as you ensure your data manager sorts last, and that there are no other data managers in the transaction which are using the same trick.
Why does the latter part matter? (It is, of course, the situation I'm in, where zope.sqlalchemy is operating in non-tpc mode (sqlite doesn't support tpc :-/) and now I'm writing another data manager for a service that doesn't support tpc) cheers, Chris
On 06/08/2010 12:44 PM, Chris Withers wrote:
Laurence Rowe wrote:
Committing in tpc_vote is right so long as you ensure your data manager sorts last, and that there are no other data managers in the transaction which are using the same trick.
Why does the latter part matter?
(It is, of course, the situation I'm in, where zope.sqlalchemy is operating in non-tpc mode (sqlite doesn't support tpc :-/) and now I'm writing another data manager for a service that doesn't support tpc)
Because TPC requires all participating DMs to be able to get in a state where committment can be guaranteed but also aborted. A single DM may have non-TPC behaviour and you will still be fine. If you have more than one then it can happen that the first one committed, but the second one doesn't and then you can't properly roll back. Christian -- Christian Theune · ct@gocept.com gocept gmbh & co. kg · forsterstraße 29 · 06112 halle (saale) · germany http://gocept.com · tel +49 345 1229889 0 · fax +49 345 1229889 1 Zope and Plone consulting and development
Christian Theune wrote:
If you have more than one then it can happen that the first one committed, but the second one doesn't and then you can't properly roll back.
Okay, but this is quite a common occurrence now. For example, many projects will use zope.sendmail and zope.sqlalchemy together... I'd guess there are a few "transactional file on disk" components out there that play in this area too... cheers, Chris
On 8 June 2010 12:48, Chris Withers <chris@simplistix.co.uk> wrote:
Christian Theune wrote:
If
you have more than one then it can happen that the first one committed, but the second one doesn't and then you can't properly roll back.
Okay, but this is quite a common occurrence now. For example, many projects will use zope.sendmail and zope.sqlalchemy together...
I'd guess there are a few "transactional file on disk" components out there that play in this area too...
As zope.sendmail commits in tpc_finish, there is no additional issue using it with zope.sqlalchemy in 1pc than with a 2pc data manager. If it fails you will end up in an inconsistent state whatever. It's just that with the maildir implementation, it pretty much can't fail as it is only a rename and that should always succeed. Really, it should register as an after commit hook instead. When an after commit hook fails, the error is caught, logged, and then it continues. Laurence
Laurence Rowe wrote:
it fails you will end up in an inconsistent state whatever. It's just that with the maildir implementation, it pretty much can't fail as it is only a rename and that should always succeed. Really, it should register as an after commit hook instead.
How do I do that? Since the data manager I'm working on is for a transcation message sending implementation, maybe it should be an after commit thing too? My other thought was to have it commit the message send in tpc_vote, and make sure it sorts before zope.sqlalchemy. That way, if the message send fails, the "transaction" will be aborted, and that will include rolling back the zope.sqlalchemy session rather than committing it. Views? cheers, Chris
On 8 June 2010 12:59, Chris Withers <chris@simplistix.co.uk> wrote:
Laurence Rowe wrote:
it fails you will end up in an inconsistent state whatever. It's just that with the maildir implementation, it pretty much can't fail as it is only a rename and that should always succeed. Really, it should register as an after commit hook instead.
How do I do that?
transaction.get().addAfterCommitHook(callable, args, kwargs)
Since the data manager I'm working on is for a transcation message sending implementation, maybe it should be an after commit thing too?
My other thought was to have it commit the message send in tpc_vote, and make sure it sorts before zope.sqlalchemy. That way, if the message send fails, the "transaction" will be aborted, and that will include rolling back the zope.sqlalchemy session rather than committing it.
Views?
In that case if the sqlalchemy commit fails, you still sent the message. Laurence
Laurence Rowe wrote:
On 8 June 2010 12:59, Chris Withers <chris@simplistix.co.uk> wrote:
Laurence Rowe wrote:
it fails you will end up in an inconsistent state whatever. It's just that with the maildir implementation, it pretty much can't fail as it is only a rename and that should always succeed. Really, it should register as an after commit hook instead. How do I do that?
transaction.get().addAfterCommitHook(callable, args, kwargs)
Hmm, I realised from looking at the code this morning that this won't. The reason being that there's no equivalent AfterAbortHook where I can abort the messaging transaction in the event of transaction-package transaction abort. I see these things called "synchronizers", though... what are they and what is their intended purpose? Where are they documented?
My other thought was to have it commit the message send in tpc_vote, and make sure it sorts before zope.sqlalchemy. That way, if the message send fails, the "transaction" will be aborted, and that will include rolling back the zope.sqlalchemy session rather than committing it.
In that case if the sqlalchemy commit fails, you still sent the message.
Yeah, I guess given that, of the possible failure modes, I'd prefer the message not to be sent in the event of sqlalchemy commit failure, I should have it sort after sqlalchemy... cheers, Chris -- Simplistix - Content Management, Batch Processing & Python Consulting - http://www.simplistix.co.uk
On 13 June 2010 11:18, Chris Withers <chris@simplistix.co.uk> wrote:
Laurence Rowe wrote:
On 8 June 2010 12:59, Chris Withers <chris@simplistix.co.uk> wrote:
Laurence Rowe wrote:
it fails you will end up in an inconsistent state whatever. It's just that with the maildir implementation, it pretty much can't fail as it is only a rename and that should always succeed. Really, it should register as an after commit hook instead.
How do I do that?
transaction.get().addAfterCommitHook(callable, args, kwargs)
Hmm, I realised from looking at the code this morning that this won't. The reason being that there's no equivalent AfterAbortHook where I can abort the messaging transaction in the event of transaction-package transaction abort.
""" After-commit hook ------------------ Sometimes, applications want to execute code after a transaction is committed or aborted. For example, one might want to launch non transactional code after a successful commit. Or still someone might want to launch asynchronous code after. A post-commit hook is available for such use cases: use addAfterCommitHook(), passing it a callable and arguments. The callable will be called with a Boolean value representing the status of the commit operation as first argument (true if successfull or false iff aborted) preceding its arguments at the start of the commit (but not for substransaction commits). """ http://zope3.pov.lt/trac/browser/transaction/trunk/transaction/_transaction.... Laurence
Laurence Rowe wrote:
""" After-commit hook ------------------
Sometimes, applications want to execute code after a transaction is committed or aborted.
Okay, but they're not going to be able to do that with an after commit hook, since they're never called from abort. The name hints at that, you could also go read the code of the abort method in transaction._transaction.Transaction. Unless I'm missing something, in which case, please lemme know... Chris -- Simplistix - Content Management, Batch Processing & Python Consulting - http://www.simplistix.co.uk
On 8 June 2010 11:44, Chris Withers <chris@simplistix.co.uk> wrote:
Laurence Rowe wrote:
Committing in tpc_vote is right so long as you ensure your data manager sorts last, and that there are no other data managers in the transaction which are using the same trick.
Why does the latter part matter?
(It is, of course, the situation I'm in, where zope.sqlalchemy is operating in non-tpc mode (sqlite doesn't support tpc :-/) and now I'm writing another data manager for a service that doesn't support tpc)
Your options then are: * Use a database that supports two phase commit (you're using sqalchemy, so that should be trivial). * Queue up changes for the other 1pc system in the first, batching updates to the second. While this moves the problem to the batch process, it's easier to solve there as you can just add timestamps to the data, and then know if any particular row was successfully pushed into the second system or not in event of a power cut. * Trust that any sqlite transaction will always be committed successfully and add a data manager variant to zope.sqlalchemy that commits in tpc_finish. You'll never get a write conflict error in SQLite (it uses locking instead of MVCC, see http://www.sqlite.org/lang_transaction.html and http://www.sqlite.org/lockingv3.html), but you might end up in an inconsistent state in event of a power cut or perhaps when you fill up the disk. Laurence
Laurence Rowe wrote:
On 8 June 2010 09:51, Chris Withers <chris@simplistix.co.uk> wrote:
Hi All,
I need to write a data manger that interacts with a transactional system that doesn't support two phase commit.
Looking for inspiration, I went to look at zope.sqlalchemy and zope.sendmail.
In the non-tpc situation, the former does the "commit" in tpc_vote while the latter does it in tpc_finish.
Which is "right"? What are the tradeoffs involved?
Committing in tpc_vote is right so long as you ensure your data manager sorts last, and that there are no other data managers in the transaction which are using the same trick. See: https://mail.zope.org/pipermail/zodb-dev/2007-May/010996.html
For zope.sendmail, committing in tpc_finish makes sense, especially when using QueuedMailDelivery because enqueuing the message in the maildir is guaranteed to succeed (the file is just renamed on commit). Any failure here would lead to a critical error and inconsistent state between the transactional resources.
Okay, I see the two different cases. What's the recommended course of action when you end up with two data managers where tpc_vote is the "right" place to implement? cheers, Chris
This is intended as a broad response to the thread, rather than a response to any specific post. :) I've been thinking of expanding the data manager API to add an optional tpc_rollback method. If tpc_finish returns a value and a data manager provided tpc_rollback and some other data manager fails in tpc_finish, then tpc_rollback would be used to *try* to recover from the other data managers failure. Note that even if tpc_rollback is implemented, it might fail if the transaction can't be rolled back (due, typically, to subsequent conflicting transactions). Jim -- Jim Fulton
On 8 June 2010 14:38, Jim Fulton <jim@zope.com> wrote:
This is intended as a broad response to the thread, rather than a response to any specific post. :)
I've been thinking of expanding the data manager API to add an optional tpc_rollback method. If tpc_finish returns a value and a data manager provided tpc_rollback and some other data manager fails in tpc_finish, then tpc_rollback would be used to *try* to recover from the other data managers failure. Note that even if tpc_rollback is implemented, it might fail if the transaction can't be rolled back (due, typically, to subsequent conflicting transactions).
While I can imagine a ZODB implementation of tpc_rollback that could work in some circumstances for some storages, even then it seems it would be quite complex and perhaps unlikely to succeed - as soon as another connection read anything from the database you would be unable to tpc_rollback, unless you deferred truly committing the transaction to a tpc_truly_finished which would just bring you back where you started. For other systems I can't think how it might be implemented - you can't unsend a mail or uncommit a committed transaction in a relational database. Laurence
On Tue, Jun 8, 2010 at 1:00 PM, Laurence Rowe <l@lrowe.co.uk> wrote:
On 8 June 2010 14:38, Jim Fulton <jim@zope.com> wrote:
This is intended as a broad response to the thread, rather than a response to any specific post. :)
I've been thinking of expanding the data manager API to add an optional tpc_rollback method. If tpc_finish returns a value and a data manager provided tpc_rollback and some other data manager fails in tpc_finish, then tpc_rollback would be used to *try* to recover from the other data managers failure. Note that even if tpc_rollback is implemented, it might fail if the transaction can't be rolled back (due, typically, to subsequent conflicting transactions).
While I can imagine a ZODB implementation of tpc_rollback that could work in some circumstances for some storages, even then it seems it would be quite complex and perhaps unlikely to succeed - as soon as another connection read anything from the database you would be unable to tpc_rollback, unless you deferred truly committing the transaction to a tpc_truly_finished which would just bring you back where you started.
No. It would behave exactly like (probably built on) undo, which generates suitable invalidations. (Of course, undo itself weakens consistency to some degree.)
For other systems I can't think how it might be implemented - you can't unsend a mail or uncommit a committed transaction in a relational database.
Of course not. Those wouldn't implement this method. But it would provide a saner way to deal with *some* failures in 2pc. For example, if you had a ZODB database and a relational db and the rdb raised an error in tpc_finish, you could perhaps roll back the zodb transaction. Jim -- Jim Fulton
On 8 June 2010 18:11, Jim Fulton <jim@zope.com> wrote:
On Tue, Jun 8, 2010 at 1:00 PM, Laurence Rowe <l@lrowe.co.uk> wrote:
On 8 June 2010 14:38, Jim Fulton <jim@zope.com> wrote:
This is intended as a broad response to the thread, rather than a response to any specific post. :)
I've been thinking of expanding the data manager API to add an optional tpc_rollback method. If tpc_finish returns a value and a data manager provided tpc_rollback and some other data manager fails in tpc_finish, then tpc_rollback would be used to *try* to recover from the other data managers failure. Note that even if tpc_rollback is implemented, it might fail if the transaction can't be rolled back (due, typically, to subsequent conflicting transactions).
While I can imagine a ZODB implementation of tpc_rollback that could work in some circumstances for some storages, even then it seems it would be quite complex and perhaps unlikely to succeed - as soon as another connection read anything from the database you would be unable to tpc_rollback, unless you deferred truly committing the transaction to a tpc_truly_finished which would just bring you back where you started.
No. It would behave exactly like (probably built on) undo, which generates suitable invalidations.
(Of course, undo itself weakens consistency to some degree.)
For web applications, one consequence of this is that you could end up with stale pages cached in a proxy. This may well be preferable to the inconstancies following from a failure in tpc_finish though. If it were implemented, I guess it would be desirable for data managers implementing tpc_recover to be called before those implementing only tpc_vote/tpc_finish, which in turn should be sorted before those implementing only 1-phase-commit. If multiple data managers implemented tpc_recover, would a failure of one data manager's tpc_recover prevent other data manager's tpc_recover being run at all? I guess you want to end up in the 'least inconsistent' state, but it's difficult to know whether this would be achieved by attempting to tpc_recover on the other data managers or not. I guess my concern is that the benefits from implementing this should outweigh the cost in higher complexity. Laurence
On Tue, Jun 8, 2010 at 1:44 PM, Laurence Rowe <l@lrowe.co.uk> wrote:
On 8 June 2010 18:11, Jim Fulton <jim@zope.com> wrote:
On Tue, Jun 8, 2010 at 1:00 PM, Laurence Rowe <l@lrowe.co.uk> wrote:
On 8 June 2010 14:38, Jim Fulton <jim@zope.com> wrote:
This is intended as a broad response to the thread, rather than a response to any specific post. :)
I've been thinking of expanding the data manager API to add an optional tpc_rollback method. If tpc_finish returns a value and a data manager provided tpc_rollback and some other data manager fails in tpc_finish, then tpc_rollback would be used to *try* to recover from the other data managers failure. Note that even if tpc_rollback is implemented, it might fail if the transaction can't be rolled back (due, typically, to subsequent conflicting transactions).
While I can imagine a ZODB implementation of tpc_rollback that could work in some circumstances for some storages, even then it seems it would be quite complex and perhaps unlikely to succeed - as soon as another connection read anything from the database you would be unable to tpc_rollback, unless you deferred truly committing the transaction to a tpc_truly_finished which would just bring you back where you started.
No. It would behave exactly like (probably built on) undo, which generates suitable invalidations.
(Of course, undo itself weakens consistency to some degree.)
For web applications, one consequence of this is that you could end up with stale pages cached in a proxy.
Sure. Although, stale pages pretty much define caches. <.5 wink>
This may well be preferable to the inconstancies following from a failure in tpc_finish though.
Yup.
If it were implemented, I guess it would be desirable for data managers implementing tpc_recover to be called before those implementing only tpc_vote/tpc_finish, which in turn should be sorted before those implementing only 1-phase-commit.
This all depends on how the 1-phase commit data managers work.
If multiple data managers implemented tpc_recover, would a failure of one data manager's tpc_recover prevent other data manager's tpc_recover being run at all?
No. This would be equivalent to the original tpc_finish failure.
I guess you want to end up in the 'least inconsistent' state, but it's difficult to know whether this would be achieved by attempting to tpc_recover on the other data managers or not.
I guess my concern is that the benefits from implementing this should outweigh the cost in higher complexity.
I don't think it really increases complexity all that much. I agree the potential benefit is pretty limited. Jim -- Jim Fulton
Jim Fulton wrote:
I guess my concern is that the benefits from implementing this should outweigh the cost in higher complexity.
I don't think it really increases complexity all that much. I agree the potential benefit is pretty limited.
I think, given the transaction packages increasing use outside the realms of ZODB, I'd prefer this not to be implemented, if only to keep the already complex process on transaction commit a little bit simpler... cheers, Chris -- Simplistix - Content Management, Batch Processing & Python Consulting - http://www.simplistix.co.uk
participants (4)
-
Chris Withers -
Christian Theune -
Jim Fulton -
Laurence Rowe