Rollback is not a feature. It is a workaround for limitations in database technology.
provides all ACID transaction properties without using rollback logic. Since traditional databases, on the other hand, have to use rollback logic to implement ACID properties anyway, they also offer you rollback to use if you want.
People use rollback to undo the operations that they have speculatively
applied to the database until one of the following happens:
#The user (or the master in a distributed transaction cenario) decides to give up.
#They realize the transaction would produce an inconsistent state.
The reason why operations have to be speculatively
applied is that transactions take time to execute in traditional databases. The database cannot simply wait until commit to start applying the operations.
With prevalence you no longer need rollback. InstantaneousTransactions
simply eliminate the need to speculatively apply operations to the system before the user (or the master) commits.
To deal with the committing of inconsistent transactions, you just have to determine wether the transaction is inconsistent BEFORE
starting to apply it. Every operation that could possibly fail must have explicitly verifiable pre-conditions (good practice anyway).
Some people suggested every business object could provide an undo operation for every operation. Having to implement undo methods for every change (and propagating those undo methods through the business object trees) is impractical for the amount of work and is extremely error-prone, though.
Object-oriented databases try to make everything "transparently" undoable. Changes are made to clones of the real business objects; on commit the changes are synched and the clones discarded. This is pretty complex to implement if you want it to be minimally invasive, however, and extremely slow because every single variable access (including reads) requires indirection.
That is why Prevayler
is so much faster and simpler than OO databases even.
, Quartz and CarlosVillela
The hot/replica pair Carlos mentioned earlier, I think, is the best solution I've seen. Commands that are rolled back (for whatever reason, be it an exception or another participant in a distributed transaction) cause the hot instance to be discarded. --MichaelPrescott
To implement that, it seems we would have to be able to copy replicas faster than the rollback rate, which I think we cannot guarantee (not for all applications). --KlausWuestefeld
These are all problems that have been solved many times before.
No. They are all problems that have been worked around many times before. --KlausWuestefeld
's design is very simple and lightweight, but I think the statement: "you just have to determine whether the transaction is inconsistent BEFORE starting to apply it." imposes an object design with which many people are not familiar. It means you must edit all input before changing the state of any objects in memory.
This is exactly what you meant by the words "Every operation that could possibly fail must have explicitly verifiable pre-conditions (good practice anyway)". You may consider it good practice, but it is certainly not commonplace. I'm not even sure it is always good practice. It means each Prevaylent Command object must invoke all the methods which check all the pre-conditions for the things it needs to do. Then it must invoke the methods which really "do the work". The Command can end up needing to know way too much about the internals of the domain objects.
The problem is that if any sort of editting or processing exception occurs after any object has had state changed by the Command, the only way to get back to previous state is to have some sort of rollback mechanism. It should be possible to recover forward from the last known good state and the command logs, but this is probably not a great approach for interactive work.
is essentially, an in-memory database. Even in a fast in-memory database, I may still have "speculative updates". To maintain ACIDity, I must have either a rollback, or recover-forward capability.
I agree that using explicitly verifiable pre-conditions is not commonplace yet. See DesignByContract
The Command does not need to "invoke all the methods which check all the pre-conditions for the things it needs to do". The business object can provide a single method for that.
I agree that if you were going to do "speculative updates" you would need rollback. Because of our rollback-spoilt habits, we find it easier to do certain things with speculative updates.
We also found it easier to do certain things using GOTO statements.
What I don't understand is how to implement isolation with Prevayler
. Assuming two clients want to redraw money from a bank account. Check money, then take it if there is enough. This is usually handled by transactions in databases. The only way to make this work is using synced methods in Java (either in the business method or in a testAndModify method of account), but I can't find something about that on the site. Syncing methods will create bottlenecks, but I assume you do not care because a synced method is still much faster than a DBMS
#Sync'd methods are MUCH faster than DB transactions.
#Sync'd methods will only create bottlenecks on specific objects.
#Only one transaction is executed on a prevalent system at a time.
#All transactions in a prevalent system run in just a few microseconds. See InstantaneousTransactions
What if I have two threads manipulating the business objects simultaneously - both need to access the same two objects, but in reverse order. If I (a) just use plain synchronization, they'll deadlock forever. If I (b) force all operations to execute sequentially by synchronizing on a global object, I ruin throughput. If I (c) use custom lock objects I wait on and a deadlock-avoidance algorithm, I need to be able to roll back one of deadlocked threads by calling Thread.interrupt() and therefore causing InterruptedException while it's wait()ing for a deadlocked lock's monitor. It appears that by saying "rollback is needless" you're forcing me into using global synhronization object (solution b) and therefore killing all performance benefits of multithreading. Do I see right? OTOH, if everything is in memory, then threads are never blocked on I/O, so maybe there's no benefit in multithreading over singlethreading anyway? I have to devote some more thinking to this...
The assumption is invalid. During a single transaction I may query, update, insert, delete, query some more, etc. All of this happens sequentially, not instantaneously. At some point during the process, I may find that, because of some condition of the data (for example account balance has reached zero), I need to rollback all of my work. This isn't because of some clunky database limitation, it's because of a condition that I can't answer until queries and updates have been applied to the database. It's business logic that either all has to happen, or not at all. That's the "A" part of ACID
. I think you REALLY need to rethink transactions!
You are mixing two cases:
#A system using the database rollback "feature" to allow the user to perform "transaction simulations" (holding as many locks as he pleases) and quit without committing. @:p
#The execution of a transaction on behalf of a user that has already issued his OK
by a system that does not know before actual execution wether the transaction will explode in its face.
The system in the second case is untestable at best. How do you know its execution will even return?
"I think you REALLY need to rethink transactions!"
As if I wasn't the one rethinking transactions. @;) --KlausWuestefeld
I'm still trying to wrap my head around object prevalence, but it seems to me that it's the previous comment's assumptions that are invalid. In an RDBMS
each select, insert, update, etc. is a single atomic command, thus you need to use explicit transactions to group several operations in an atomic way. And in a prevalent system (to quote Klaus), "Only one command is executed on a prevalent system at a time." What I'm thinking (someone correct me if I'm wrong) is that executing a (synchronized) command in a prevalent system might be a call to a single method which contains all needed business logic and does (or doesn't do, or undoes, or...) data modifications as appropriate.
Unless you're talking about using transactions with ad-hoc queries, in which case it's more an issue of how to handle ad-hoc queries (discussed elsewhere) than how to handle transactions.
- Joel Fouse
So if I understand this correctly, every single ACID
transaction that a particular system requires gets implemented as one of these Prevayler
command objects, and these objects are globally synchronised (ie. only one of them can execute at once). This would enforce globally serialised access to your "data model", and hence eliminate a lot of the concurrency related problems that traditional RDBMS
transactions attempt to solve. In a traditional database, global serialisation would guarantee truly abysmal performance, but I'm guessing that because everything happens in memory, this isn't an issue with Prevayler
If I've got it right, that makes a fair bit of sense actually...
...although I must admit that I'm not particularly satisfied with the answers that have been given for some of the issues that others have raised. In particular:
1. 2PC (in the case where your transactional boundary encompasses more than just your PrevLayer "data model")
2. rollback support (to avoid having to manually guarantee ALL preconditions BEFORE modifying any of the "data model" objects)
-- Peter Monks
You are right regarding the speed of globally synchronized transactions in RAM (InstantaneousTransactions
) and the fact that this eliminates concurrency problems among them. Before they are executed serially, mind you, they are written to disk in parallel
1.03 does that, although that was not yet migrated to the 2.0 architecture.
As for 2PC see TwoPhaseCommitIsUnnecessary
As for not being able to guarantee all preconditions for your transactions, please keep a secret: thanks to coding by JonTirsen
2.0 will actually only execute transactions which are guaranteed not to throw RuntimeExceptions!
If a transaction would throw a RuntimeException
just does not execute it on your system. "How on earth does Prevayler
know wether a particular transaction would
throw a RuntimeException
?" @??? Download
the 2.0 code from CVS and try it out. It is pretty cool to see that working and, as most of Prevayler
, is based on an anticlimactically simple idea brought up by several people on the PrevaylerTeam
Command objects must be executed serially, obviously. Which as PeterMonks describes means that the only 'writing' to the object graph is done synchronously. However, this does not mean that the Command itself must read & write lock the entire object graph to conduct its business. It should only read & write lock the part it is working on, allowing the rest of the graph to be 'read'. Am I correct in this assumption, Klaus?