As far as I understand before two phase commit is even run a round trip communication to send the transactions to each site is needed. Each site excecutes their part of the transaction and when the coordinator gets a response from all sites then it runs two phase commit. This initiates the prepare phase, etc.
Why is it necessary to have the prepare phase be separate from the execution that precedes two phase commit? Is there a reason for not merging execution and the prepare phase, thus cutting out a round trip communication cost?
This is a followup to my previous question.
There are several good reasons for doing it like this:
Operations may require data from other sites. How would you implement a swap(a,b) operation between data items at different sites if you merge execution and prepare phases?
The coordinator is likely to become a performance bottleneck. Merging the execution and propose phases would have it involved in relaying application data, further overloading it.
Transparency and encapsulation. Note that code between begin/commit in the reply to your previous question (i.e., business logic) is not concerned with distributed transactions at all. It doesn't need to know which sites, or even how many, will be involved! It invokes arbitrary procedures that may be (or not...) remote calls to unknown sites. To merge execution and prepare you'd have to explicitly package your application logic in independent callbacks for each participant.
Moreover, when thinking about performance with persistence involved, you should be concerned about round-trips that imply flushing the log. Invocations during the execution phase don't need to flush the log and thus should be much cheaper than the prepare and commit phases, that do.
Related
In Stream Processing applications (f. e. based on Apache Flink or Apache Spark Streaming) it is sometimes necessary to process data exactly once.
In the database world something equal be achieved by using databases that follow the ACID criteria (correct me if I'm wrong here).
However there are a lot of (non relational) databases that do not follow ACID but BASE.
Now my question is: If I'm going to integrate such a BASE database into a stream processing application (exactly once), can I still guarantee exactly once processing for the whole pipeline? And if this is possible, under what circumstances?
Exactly Once Semantics means the processing framework such as flink can guarantee each incoming record(event) will be processed exactly one time even if the pineline fails in any way.
This is done by having checkpoints after each operation in the pineline, so that when the application recovers from failure, successful operation will not be executed again.
Depends on what kind of operations you are trying to do with databases, most cases databases are used as sinks for processing result to write into. In that case the operation involving database is just a simple insert and it will not be executed again after one successful run therefore it's still exactly-once regardless of its ACID support.
You might be tempted to group operations together for databases that support ACID but it will be a bad practice in a parallel streaming pineline since they created mutilple transactions and the locks might block the whole process. Instead, use BASE (NoSQL) database that are fast with intensive read and update performance is preferable, you just need to make your operations to be idempotent so that partially re-executed statements (if they failed half way through then after recovery they might be executed all again) won't result in incorrect data.
Going through the microservices docs and talks, I'm getting the point that using distributed transactions over XA protocol is not a good choice for ensuring consistency of data, because it slows down the system and not all the endpoints support it, e.g REST.
Eventual consistency technique, implemented using retry mechanisms or messaging is suggested.
My questions is, is it possible to use eventual consistency in enterprise applications where consistency of the data in each and every point of time is critical? How to eliminate dirty reads in eventual consistency? or in other words, is it possible to ensure that no 3rd party process is getting the stale data, that were part of not committed "transaction"?
Well, that's the point of eventual consistency: You cannot guarantee that no process "is getting stale data". Your system will converge to the updated state in a finite amount of time. But you do not know when.
Using a lightweight compensation pattern, you can look at each operation T(i) within a transaction and define a compensating action C(i). You can then guarantee that either
T(1), ..., T(N) or
T(1), ..., T(N), C(N), ..., C(1)
is executed successfully (in a finite amount of time...). That means: Either all operations are successfully executed or all operations will be retracted by executing the corresponding compensations.
As these operations requires coordination between services and do not run in a real transactional, isolated context, of course it is possible to receive stale data that results from a transaction not fully finished or compensated.
Can somebody please explain me why concurrent execution of transactions is desirable? I couldn't find a clear answer even though searched for hours. Thank you.
why concurrent execution of transactions is desirable
Without concurrent execution of transactions, your application throughput is limited to inverse of your transaction duration.
Eg if each transaction takes 50ms, your throughput limited to 20 transactions per second. Which means you can't perform a lot of transactions or support a large number of users.
Concurrency does not help in the case of a single-core/single-node case. (Except in the case when reading from disk takes a lot more time that can be used for processing something else)
Because at any time, you can only run one transaction. So you can run all the transactions one after the other without any interleaving of operations between different transactions and get the same performance.
In case of multi-core/multi-node case, you can make use of parallel processing and run multiple transactions parallelly.
If you run the transactions one by one, you can't make use of the multiple cores/nodes. So we need to run transactions in parallel on the multiple cores/nodes and make sure the concurrency control protocols are in place to keep the database consistent when multiple transactions running concurrently (simultaneously at the same time).
I have an application that uses LMDB. If multiple processes need to write to the database, only one will be allowed to run at a time and the rest block. Because of this, I want to rewrite the application to use a client-server model.
If the application is written to use a client-server model, the server can manage the writes and the other processes won't block. However, if a client encounters an error and has to roll back its transaction, how can it roll back its data without rolling back what the other clients have written?
I've looked at nested transactions, but write transactions may only have one nested transaction. So while a client can write its data to a nested transaction and roll it back if an error occurs, only one client will be able to run at a time. So while that solves the rollback problem, we're back to the problem that only one client can write at a time.
I've also taken a look at the MDB_NOLOCK option, which causes LMDB to not stop you from creating multiple write transactions. When you try to commit any transaction but the first one, it will return an error. Maybe the clients can pool their writes into their own transactions and when they're ready to commit, the server will dump the entries into the first write transaction, but that is hacky and I'm certain that is NOT what the developers intended it to be used for.
The only other solution I can think of is to keep clients in a separate database, which undoes the entire purpose of switching to a client-server model.
Are there any other ways to allow different processes to write to the database, while being able to roll back one client's data without rolling back everything?
There is no easy solution to the challenge you pose.
One can nest write transactions arbitrarily deep, but as you say that doesn't help you achieve better throughput because you'll still be limited to one thread for that write transaction. So for most intents and purposes, if you use LMDB as it was designed to be used, you're only going to have one thread doing write operations on the database.
You asked how you can abort someone's transaction without rolling back the txn of others. This is largely not an issue because, as mentioned above, you will have only one write transaction active at at time. If you commit at the end of the request and start a new transaction at the beginning of the next request, your write transactions will not overlap. Once you commit the first transaction, you'll not be able to abort it, but if your client informs the server to abort the request before it's committed, your server can abort it. And when it aborts, the database state will resort to the state it was in when that transaction was started. It will not lose any other transactions and not lose the changes created by other requests.
As you point out, you could batch some of the write requests in to a single write txn. You could process some of those write requests in other threads, but all LMDB API calls must still be done in the original thread. And if you try to dedicate a thread to each request to achieve some parallelism, you'll still need to ensure that the requests are mutually compatible and don't interfere with each other. And if one of those requests runs in to trouble, you'll have to abort the transaction and probably restart the transaction. When you restart the transactions, you'll probably only include the requests that did not run in to trouble. -- This is all possible, but only you have enough knowledge about your application to know if this would improve performance much and be worth your effort.
How can I write and run automated tests that check that my database transaction strategy is removing race conditions? At the moment all I do is test it in development by putting a breakpoint in the code and sending two requests, I can then see in slow motion what happens. This is not something I can automate though, it's not even testing really, just part of development.
Your test can spawn threads and run two or more threads making the same request isolated by the transaction.
Perform a load test with a realistic work-load. Unfortunately, this is not easy to do. Race conditions are hard to discover on any platform. I know of no systematic way to find such bugs.
Sometimes you can exclude the possibility of inconsistencies by construction. For example:
A transaction running under SERIALIZABLE behaves as if it was the only transaction in the system. Therefore, there are never data races.
A read-only transaction under SNAPSHOT behaves the same way. Total data consistency.
A UNIQUE INDEX will never violate its integrity guarantees.
As you can see you can sometimes make your code safe by construction so that there is minimal need to test.